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
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>
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>
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>
%#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>
{"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-layer2026-10-04 08:09:20 -04:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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:
4f26ea5ad6Add 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>Independent review — Codex
gpt-6-sol, reasoning highVerdict: APPROVED
Reviewed SHA:
d11cdbbefb655939696ad674e21dd7060f456646Posted unaltered by
review_agent. This is transport, not a second review.