Anthropic Messages adapter #40

Member

Task: f6ae8d56-af10-400c-ab38-777571a797b5 Anthropic Messages adapter.

What changed: internal/ai/llm/anthropic implements Provider for POST {base}/v1/messages and GET {base}/v1/models with x-api-key and anthropic-version. Structured output is a forced tool. Errors are mapped and redacted. Also adds GoString on OpenAI and Anthropic redacted errors so %#v cannot print the API key.

Why: Feature 08 task 3. The key must not appear in Error(), unwrap layers, errors.As bodies, or fmt %#v.

Done-condition: contract tests cover tool-forced JSON, plain text, each error mapping, and key redaction. Local gofmt, go vet, go test -race, and CGO_ENABLED=0 go build passed in the developer worktree.

Base: feature/5dd6e253-cc9b-41f3-9091-bb834f7a373a-llm-provider-layer at acd29c8221.
Head: 4f26ea5ad6

Task: f6ae8d56-af10-400c-ab38-777571a797b5 Anthropic Messages adapter. What changed: internal/ai/llm/anthropic implements Provider for POST {base}/v1/messages and GET {base}/v1/models with x-api-key and anthropic-version. Structured output is a forced tool. Errors are mapped and redacted. Also adds GoString on OpenAI and Anthropic redacted errors so %#v cannot print the API key. Why: Feature 08 task 3. The key must not appear in Error(), unwrap layers, errors.As bodies, or fmt %#v. Done-condition: contract tests cover tool-forced JSON, plain text, each error mapping, and key redaction. Local gofmt, go vet, go test -race, and CGO_ENABLED=0 go build passed in the developer worktree. Base: feature/5dd6e253-cc9b-41f3-9091-bb834f7a373a-llm-provider-layer at acd29c82212dc6f7c21f87f75697e0c7620d8373. Head: 4f26ea5ad6ede161a87b2e8ab04d66ec845b656f
dev_agent added 2 commits 2026-10-04 07:52:35 -04:00
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>
Keep the API key out of %#v for redacted LLM errors
CI / ci (pull_request) Successful in 1m28s
CI / ci (push) Successful in 1m28s
4f26ea5ad6
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>
dev_agent added 1 commit 2026-10-04 07:58:05 -04:00
Stop LLM adapters following redirects and cap Retry-After
CI / ci (push) Successful in 1m29s
CI / ci (pull_request) Successful in 1m29s
cdf59c4eee
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>
dev_agent added 1 commit 2026-10-04 08:05:45 -04:00
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
d11cdbbefb
%#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>
review_agent left a comment
Member

Independent review — Codex gpt-6-sol, reasoning high

Verdict: APPROVED
Reviewed SHA: d11cdbbefb655939696ad674e21dd7060f456646

{
  "verdict": "APPROVED",
  "reviewed_sha": "d11cdbbefb655939696ad674e21dd7060f456646",
  "summary": "The Anthropic adapter matches the requested endpoint, header, response, and error contracts. Reviewed the key redaction paths, including the OpenAI GoString change, and found no remaining leak in the requested formatting, unwrapping, or errors.As checks. gofmt -l ., go vet ./..., and go test -race -count=1 ./... passed.",
  "findings": []
}

Posted unaltered by review_agent. This is transport, not a second review.

## Independent review — Codex `gpt-6-sol`, reasoning high Verdict: **APPROVED** Reviewed SHA: `d11cdbbefb655939696ad674e21dd7060f456646` ```json { "verdict": "APPROVED", "reviewed_sha": "d11cdbbefb655939696ad674e21dd7060f456646", "summary": "The Anthropic adapter matches the requested endpoint, header, response, and error contracts. Reviewed the key redaction paths, including the OpenAI GoString change, and found no remaining leak in the requested formatting, unwrapping, or errors.As checks. gofmt -l ., go vet ./..., and go test -race -count=1 ./... passed.", "findings": [] } ``` --- Posted unaltered by `review_agent`. This is transport, not a second review.
approver_agent merged commit 15886f2c55 into feature/5dd6e253-cc9b-41f3-9091-bb834f7a373a-llm-provider-layer 2026-10-04 08:09:20 -04:00
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Maniacs_Mansion/MealQuest#40