Anthropic Messages adapter #40

4 Commits
Author SHA1 Message Date
dev_agentandClaude Opus 5.5 d11cdbbefb Keep %#v of every LLM adapter error free of the API key (MQ-03)
CI / ci (push) Successful in 1m29s
Independent review Codex gpt-6-sol high: APPROVED (0 findings)
CI / ci (pull_request) Successful in 1m28s
%#v prints struct field names, so a key that spells one (for example
"body" on ProviderError) leaked even though Error() was redacted.

- llm.ProviderError and llm.RateLimitedError gain GoString returning
  their already-redacted Error text.
- Both adapters' transportFailure now holds its fully redacted text and
  gains GoString.
- The redact backstop now wraps every non-nil error when a key is set,
  so the returned error and every unwrapped layer print through
  redactedError.GoString, never a raw struct such as fmt.wrapError.
- redactedError.As also refuses a match whose %#v form holds the key.
- Contract test in both adapters: field-name keys (body, status, text,
  RetryAfter, msg, err, key, kind) on HTTP 500 "down" and 429, checking
  %#v of the returned error, every unwrapped layer and errors.As results.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 08:05:17 -04:00
dev_agentandClaude Opus 5.5 cdf59c4eee Stop LLM adapters following redirects and cap Retry-After
CI / ci (push) Successful in 1m29s
CI / ci (pull_request) Successful in 1m29s
MQ-01: the Anthropic adapter used a default http.Client, so a 3xx from the
configured endpoint was followed and the x-api-key header resent to the
redirect target; net/http strips only its own sensitive headers. Both
adapters now use a client whose CheckRedirect returns ErrUseLastResponse,
so a 3xx comes back as an llm.ProviderError and nothing is requested again.

The OpenAI-compatible adapter gets the same change: it forwards configured
extra headers (which may carry a credential and which net/http does not
treat as sensitive) across hosts, and net/http keeps Authorization on a
same-host https-to-http redirect.

MQ-02: a delay-seconds Retry-After too large for time.Duration overflowed
into a negative wait. retryAfter now caps the value at the most whole
seconds a time.Duration holds (also for values beyond int64) and the
response still maps to ErrRateLimited.

Tests: TestRedirectIsNotFollowed (302 to a second server, which must get
no request) and TestRateLimitedHugeRetryAfterIsCapped in both packages,
plus huge-number cases in TestRetryAfter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 07:57:37 -04:00
dev_agentandClaude Opus 5.5 4f26ea5ad6 Keep the API key out of %#v for redacted LLM errors
CI / ci (pull_request) Successful in 1m28s
CI / ci (push) Successful in 1m28s
The OpenAI adapter's redactedError had no GoString, so %#v printed its
fields, including the raw API key. Add GoString returning the redacted
text, matching the Anthropic adapter; redactedJoin inherits it.

Add TestRedactedErrorsFormatWithoutKey to both adapters, formatting
redactedError, redactedJoin and transportFailure with %v, %+v and %#v.
llm.ProviderError and llm.RateLimitedError keep no secret and are
already covered by TestTypedErrorsDoNotKeepSecret.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 07:52:04 -04:00
dev_agentandClaude Opus 5.5 3b36f07cb8 Feature 08 task 3: Anthropic Messages adapter
Add internal/ai/llm/anthropic, an llm.Provider over plain net/http:

- POST {base}/v1/messages and GET {base}/v1/models with x-api-key and
  anthropic-version 2023-06-01. An empty key, a non-positive
  Config.MaxTokens and a base URL that is not absolute http(s), or that
  carries userinfo, a query or a fragment, are construction errors.
- System text goes in the top-level system field. A Request with a
  Schema becomes one tool whose input_schema is the schema, forced by
  tool_choice; Response.JSON is that tool's input. Otherwise the text
  blocks are Response.Text. Usage and stop_reason are parsed.
- ListModels follows has_more/after_id pages; Verify fetches one page
  with limit=1.
- 401/403 map to ErrAuth, 429 and 529 to ErrRateLimited with
  Retry-After, 408 and deadlines to ErrTimeout, other statuses to
  ErrProvider with a redacted excerpt.
- Errors carry the OpenAI adapter's redaction chain, so no Error(),
  Unwrap layer, errors.As target or %#v output holds the key.

Contract tests replay sanitized fixtures adapted from Anthropic's public
Messages API docs over httptest, covering tool-forced JSON, text, each
error mapping and key redaction with the keys x-api-key, anthropic,
HTTP, rate, a one-letter key and a URL-escaped key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 07:49:52 -04:00