Commit Graph
10 Commits
Author SHA1 Message Date
dev_agentandClaude Opus 5.5 cceeafd200 Redact every layer of a returned error chain, not just the top
CI / ci (push) Successful in 1m29s
CI / ci (pull_request) Successful in 1m28s
redactedError.Unwrap returned the original error, so errors.Unwrap could
reach an inner error whose text still held the API key. Unwrap now passes
each cause through redact, so every error reached by unwrapping has the
key removed, and errors with several causes are handled the same way.
redact also checks the whole chain, catching a layer that hides the key
from its own text but wraps an error that shows it. Is and As consult the
original chain, so sentinels and *llm.ProviderError still match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 07:26:37 -04:00
dev_agentandClaude Opus 5.5 2afd6a9338 Never quote request or reply text in errors and redact the key as a backstop
Request.Validate no longer quotes the role or schema name, and the
bad-output error no longer quotes finish_reason: any of that text may
equal the API key. New, Generate, ListModels and Verify now pass every
error through redact, which removes the key (and so "Bearer "+key) from
the error text, wrapped errors included, while Unwrap keeps the chain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 07:24:37 -04:00
dev_agentandClaude Opus 5.5 f333440256 Never quote caller-supplied text in New errors
CI / ci (push) Successful in 1m29s
Independent review Codex gpt-6-sol high: CHANGES_REQUIRED (1 high)
CI / ci (pull_request) Successful in 1m30s
A managed-header error named the canonical header, which equals the API
key when the key is itself a managed header name such as Authorization.
The unknown structured-output error quoted the mode after redacting the
whole key, which still leaked any fragment of the key. Both now return
fixed messages, so no New error interpolates caller input.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 18:07:31 -04:00
dev_agentandClaude Opus 5.5 095c03f112 Stop echoing header names in header value errors
CI / ci (push) Successful in 1m30s
Independent review Codex gpt-6-sol high: CHANGES_REQUIRED (1 high)
CI / ci (pull_request) Successful in 1m28s
Canonicalising a header name changes its case, so a key embedded in the
name survived exact-match redaction. A bad header value now gets a fixed
message; only managed header names, which are fixed known strings, are
named. assertNoSecret now matches case-insensitively.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 16:46:52 -04:00
dev_agentandClaude Opus 5.5 3ddc18fa0d Redact the API key from all config errors and reject empty URL delimiters
An invalid extra-header name is no longer echoed, and every other New
error that quotes caller-supplied text (structured output mode, header
names) has the API key redacted first via a shared redactKey helper, also
used for transport errors.

A base URL containing "?" or "#" is rejected even when the query or
fragment is empty, so endpoint paths can no longer land in either.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 16:45:44 -04:00
dev_agentandClaude Opus 5.5 adfd84810e Validate extra headers in New and honour a configured Accept header
CI / ci (push) Successful in 1m29s
CI / ci (pull_request) Successful in 1m29s
Config.Headers names must be RFC 9110 tokens and values must hold no
control characters other than horizontal tab, so bad configuration fails
at construction instead of on the first request. Headers the adapter or
net/http manage (Authorization, Content-Type, Content-Length,
Transfer-Encoding, Host, Connection) are rejected. A configured Accept
header is sent as given; without one the adapter sends application/json.
Header values are never echoed in errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 16:40:29 -04:00
dev_agentandClaude Opus 5.5 80d91eba70 Keep the request URL out of transport errors and document fixture provenance
CI / ci (push) Successful in 1m30s
CI / ci (pull_request) Successful in 1m28s
A base URL path may hold the API key, and net/http's *url.Error names the
full request URL. Transport failures now keep only the wrapped cause, with
the key redacted, and unwrap only to llm.ErrTimeout or context.Canceled.
The request-build error no longer echoes the URL either. A new test fails
the exchange (connection refused and connection dropped) against a base
URL whose path holds the key.

testdata/README.md records each fixture's source in the vendor's public
API documentation (Ollama: its openai middleware source), how it was
sanitized, and which fields are not in the public example.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 16:33:41 -04:00
dev_agentandClaude Opus 5.5 1f4356cd17 Keep base URL out of config errors and always keep a bounded error excerpt
Independent review Codex gpt-6-sol high: CHANGES_REQUIRED (1 high, 1 medium)
CI / ci (push) Successful in 1m27s
CI / ci (pull_request) Successful in 1m29s
New no longer echoes the supplied base URL in any configuration error, so
a credential in a rejected URL's query or fragment cannot leak.

llm.NewProviderError now takes the single credential sent with the
request ("" when none) and always keeps a body excerpt bounded to
MaxBodyExcerpt bytes, redacting the credential first. Keyless Ollama
errors therefore keep the vendor's explanation, e.g. model not found.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 16:28:21 -04:00
dev_agentandClaude Opus 5.5 6d0bea10b5 Add OpenAI-compatible llm.Provider adapter
Independent review Codex gpt-6-sol high: CHANGES_REQUIRED (1 high, 1 medium)
CI / ci (push) Successful in 1m29s
CI / ci (pull_request) Successful in 1m30s
internal/ai/llm/openai implements Provider over plain net/http against
POST {base}/chat/completions and GET {base}/models, with Bearer auth and
optional extra headers. Structured output uses response_format
json_schema (strict), json_object, or prompt-only with first-JSON-object
extraction, per the configured mode. Usage tokens, model and
finish_reason are parsed.

Errors map 401/403 to ErrAuth, 429 to ErrRateLimited with Retry-After
(seconds or HTTP-date), 408 and transport timeouts to ErrTimeout, and
other non-2xx statuses (and OpenRouter error bodies in a 2xx) to
ErrProvider with a key-redacted excerpt.

Contract tests replay OpenAI, OpenRouter and Ollama /v1 payloads from
httptest servers and assert the key and Authorization value never
appear in an error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 16:20:23 -04:00
dev_agent 72042516e2 Feature 08 task 1: Provider interface, typed errors and Fake provider (#33)
CI / ci (push) Successful in 1m29s
Co-authored-by: dev_agent <dev_agent@cubecraftlabs.com>
Co-committed-by: dev_agent <dev_agent@cubecraftlabs.com>
2026-10-03 16:14:12 -04:00