statusError capped the body at 2 KiB and then unmarshalled, so any error longer
than that failed to parse and the caller received a raw JSON fragment cut
mid-object instead of the provider's message. Azure content-filter and
structured-output responses, which echo the schema, routinely exceed it.
The cap moves to 64 KiB: still bounded, but above the largest error body these
providers send. The existing cap test is written against the constant and keeps
holding.
PAGI-132
WithMaxTokens above MaxInt32 wrapped: 3_000_000_000 became -1294967296 on the
Vertex generation config (max output tokens, candidate count and top-k) and on
the Converse inference config. The googleai adapter already saturated, so the
same call succeeded there and failed on the sibling doors.
The saturating conversion moves to internal/numutil so the three doors share one
implementation. The Vertex generation config is extracted into a function so the
conversion has a seam a test can reach.
Not changed: the thinking-budget conversion in googleai, where GetTokens is
already clamped to MaxReasoningTokens and cannot overflow.
PAGI-132
The per-chunk loop assigned StopReason and Truncated unconditionally, so a
trailing chunk carrying no finish reason — a usage-only frame, for instance —
erased a truncation the previous chunk had already reported. WithFailOnTruncation
then returned nil on a cut-off answer. The OpenAI streaming client guards the
same assignment with a non-empty check.
The added tests also cover CheckTruncation on the streaming path, which no test
reached: deleting that call left the package green.
PAGI-132
legacyNoStructuredClaude listed "claude-2" but not "claude-v2", so the Bedrock
ids anthropic.claude-v2 and anthropic.claude-v2:1 passed the local guard and
shipped output_config.format to a generation that predates it, while the
first-party claude-2.1 of the same generation was refused. preAdaptiveClaude
already carried both spellings.
PAGI-132
A Claude model reached through an OpenAI-compatible gateway still answers with
Anthropic's sampling rules, but the adapter never consulted the Claude tables.
An adaptive-only model (sonnet-5, opus-4-7, fable-5) happened to pass because
IsReasoningModel caught it and pinned temperature to 1.0, which the gateway
tolerates. claude-haiku-4-5 did not: IsReasoningModel only lists the dotted
"claude-haiku-4.5", so the dash-form id matched nothing, temperature and top_p
both reached the wire, and the pair drew a 400 mutual-exclusion error.
Key the sampling switch on the Claude tables first: ClaudeRejectsSampling drops
temperature and top_p entirely; ClaudeMutuallyExclusiveSampling drops top_p only
when both are set, preserving the caller's temperature — which also stops the
opus-4-5/sonnet-4-5 pair from being pinned to 1.0 needlessly. Measured live
through the adapter against the gateway: haiku-4-5 with temperature+top_p goes
from 400 to a normal answer, and the five other Claude generations keep working.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A reasoning model that refuses non-default sampling refuses it on every
request, not only when the caller passed a reasoning option — the model always
reasons. The guard sat in a deferred block keyed on the request carrying a
reasoning field, so gpt-5 or o4-mini with WithTopP and nothing else answered
400 "Unsupported parameter: 'top_p' is not supported with this model", while
the same call with WithReasoning succeeded. Its sibling, the temperature pin,
was already keyed on the model.
Key both on the model. Measured live against the gateway: gpt-5 and o4-mini go
from 400 to a normal answer with or without a reasoning option, and gpt-5.2,
which accepts top_p, keeps sending it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The legacy door can be driven end to end against a fake endpoint with static
credentials, so each family's own response body exercises the real parse path:
AI21 (both the classic and the Jamba shape), Amazon, Anthropic, Cohere (classic
and Command R), Meta, Nova and DeepSeek. Each case is mutation-checked — drop
the family's Truncated line and its subtest reddens.
Still not covered by a direct test: the seven streaming overrides, which need
AWS event-stream framing rather than a plain HTTP body.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three comments asserted vendor error behaviour — that a generation "rejects
budget_tokens with a 400", that a model "rejects" sampling params, that an
unknown model gets "taught via a 400". Such a claim is invisible to every test
and goes stale silently when a vendor changes it. Restate each as the rule this
library follows, which the capability tests already pin.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ReasoningSupportFor classified an OpenAI reasoning model only when the caller
passed reasoning.ProviderOpenAI, although the effort table itself strips a proxy
prefix and is provider-independent. Measured before the fix: gpt-5-pro reports
Known=true, Efforts=[high] on ProviderOpenAI and Known=false, Efforts=[] on
ProviderUnknown — so a UI driving a gateway it labelled anything else lost the
model's known ladder and its disable rule.
Key the branch on the model being in the table instead, and take CannotDisable
from the same table rather than from the provider-aware wire resolver; on
ProviderOpenAI both yield exactly what they yielded before. Models the table
does not know — Gemini, Grok, GLM — keep the provider-gated branch below and
are unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The streamed choice sets its stop reason through an assignment rather than a
struct literal, so it was missed when the non-streaming path was wired.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bedrock is the widest spread of spellings in the library: "max_tokens" on
Converse, Nova and the Anthropic body, "length" on AI21, Meta and DeepSeek,
"MAX_TOKENS" on Cohere and "LENGTH" on Amazon. All eighteen places that fill a
stop reason — both doors, each family's completion and its streaming override —
now derive Truncated from the same string, and the opt-in check sits once in
GenerateContent so it covers both doors.
Tests cover the Converse door through the mocked runtime client and the legacy
door end to end against a fake endpoint, for the Anthropic and Amazon families;
a table test pins each family's own reason constants to what the shared
predicate recognises, so a family whose spelling changes fails the build.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both spell it "length" — Ollama in done_reason, Mistral in finish_reason — and
both now derive ContentChoice.Truncated from it and honor WithFailOnTruncation.
Mistral reports the opt-in error through its own callbacks handler, matching
how that adapter already reports a failed call.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
googleai derives it from the wire string the SDK exposes ("MAX_TOKENS"), on
both the streaming and non-streaming paths. Vertex compares the typed constant
instead: its StopReason carries the SDK's Go enum name, "FinishReasonMaxTokens",
which is not a wire value and is left untouched for callers reading it.
The googleai fixtures are real Gemini responses recorded at a 24- and a
200-token budget, so the flag is proven against an answer the model actually
cut off rather than a hand-written body.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both adapters derive ContentChoice.Truncated from the stop reason they already
carry — "length" for OpenAI, "max_tokens" for Anthropic — and honor
WithFailOnTruncation. The response is returned alongside the error, matching
how a structured-output failure already behaves, so the caller keeps the
partial answer and the usage figures.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A caller could not tell a complete answer from one the model stopped writing
because it ran out of output budget: GenerateContent returns err == nil either
way, and the only signal is ContentChoice.StopReason, which carries whatever
the vendor called it — "length" (OpenAI, Mistral, Ollama, three Bedrock
families), "LENGTH" (Bedrock Amazon), "max_tokens" (Anthropic, Bedrock
Converse) or "MAX_TOKENS" (Google, Bedrock Cohere). Asking "is this answer
whole?" meant knowing every spelling.
Add ContentChoice.Truncated, derived from the vendor string, and
WithFailOnTruncation for callers who would rather have an error. StopReason
keeps the vendor's own spelling, so code already comparing it is unaffected,
and the option is off by default.
Adapters are wired in following commits.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
googleai carried its own default of 2048 output tokens while the rest of the
library defaults to llms.DefaultMaxTokens, so a caller who set no explicit
limit got Gemini answers cut off mid-sentence with finishReason MAX_TOKENS.
The harm-threshold fixture recorded exactly that: the answer breaks off at
"or proces" and the response reports MAX_TOKENS.
Adopt the library default. Sixteen fixtures pinned the old value in the
request body; re-recorded, and the harm-threshold answer now completes with
finishReason STOP.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Re-recording googleai fixtures takes a direct vendor key, which not every
contributor has. Route the test transport through GOOGLE_BASE_URL so a
compatible proxy serves the same endpoints; the rewrite sits below the
recorder, so the trace still carries the vendor URL and replays offline.
A proxy adds its own X-Litellm-* response headers, one of which reports the
key's accumulated spend. Scrub them out of the trace, and fail
TestRecordingsCarryNoProxyHeaders if such a header ever reaches testdata.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The pin to 1.0 covered every reasoning model, but the API only demands the
default on some of them. Asked with temperature 0.2, gpt-5.1, gpt-5.2,
gpt-5.4 and its mini/nano variants answer 200, while gpt-5, gpt-5-mini,
gpt-5-nano, gpt-5.5, the gpt-5.6 line and the o-series return "Unsupported
value: 'temperature' does not support 0.2 with this model. Only the default
(1) value is supported."
gpt-5.4-mini is the library's own default chat model, so a caller asking for
0.2 there silently got 1. Models outside the measured set keep the pin: a
value the vendor rejects is worse than one it ignores.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The whole Gemini 3 family was classified as unable to stop thinking, so an
explicit disable on gemini-3.1-flash-lite returned "reasoning cannot be
disabled" without a request. Probed on a prompt heavy enough to force
reasoning, the Flash-Lite tier spends no thinking tokens at all —
gemini-2.5-flash-lite, gemini-3.1-flash-lite and gemini-3.5-flash-lite all
answer with 0 thoughts against 3040 for gemini-3.6-flash on the same prompt.
Omission is therefore the disable wire for that tier, which also sidesteps
the split inside the family: gemini-3.1-flash-lite accepts thinkingBudget 0
while gemini-3.5-flash-lite rejects it. The hint stops claiming these models
think by default, and the Gemini 3 models that really cannot be disabled
still report so.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The mutual-exclusion table listed only Haiku 4.5, and the Bedrock paths did
not consult it at all. Probing every reachable Claude model showed the rule
is wider: Sonnet 4.5, Opus 4.5, Sonnet 4.6 and Opus 4.6 answer with the same
"`temperature` and `top_p` cannot both be specified for this model" on the
first-party API and on both Bedrock doors. Captured before the fix, a call
carrying both left the wire intact for all four, so the 400 was guaranteed.
Haiku alone was spared. From 4.7 on the question is moot: sampling is
rejected outright and ClaudeRejectsSampling already drops it.
Two tests asserting that Opus 4.5 carries output_config.effort on Bedrock
were written against the first-party behaviour and are corrected to the
measured one, with the Anthropic side pinned separately so both halves of
the provider boundary stay covered.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Opus 4.5 takes output_config.effort alongside a thinking budget on the
first-party API but not on Amazon Bedrock, so a single model-keyed table
could not be right for both. Measured on all four reachable models: with a
budget and no effort every one answers 200; adding effort keeps 200 on the
4.6 pair and returns 400 "output_config.effort: Extra inputs are not
permitted" on Opus 4.5 and Sonnet 4.5, on the Converse and InvokeModel doors
alike. The first-party API accepts it for Opus 4.5 and rejects it for Sonnet
4.5 with "This model does not support the effort parameter".
The capability now takes the provider, matching ResolveOff. The four Bedrock
reasoning fixtures were stale for the same reason and are re-recorded, so the
package replays offline again.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
int32(*i) wraps silently on a 64-bit int, so WithMaxTokens(3_000_000_000)
reached the wire as maxOutputTokens: -1294967296 and Google answered
"max_output_tokens must be positive" — the caller asked for too much and was
told the value was negative. Saturating keeps the request meaningful: Google
ignores a limit above the model ceiling anyway, measured accepting 3_000_000
with finishReason STOP.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A Claude model reached through an OpenAI-compatible gateway resolved to the
Anthropic-native disable wire, which that transport cannot express. The
openai adapter handles no such case, so WithReasoningDisabled() left the
request untouched: measured on anthropic/claude-sonnet-5, the body went out
as {"model":..,"messages":[..]} with no reasoning field, no error, and
thinking still on — the caller paid for what they turned off. The hint
already reported CannotDisable=true, so the adapter contradicted it.
Sending reasoning_effort:"none" instead is not a fix. The gateway accepts it
and answers with thinking_blocks anyway; against the same prompt the
Anthropic-native door returns 853 output tokens with thinking disabled and
3407 with it on, while the gateway returns 2652 either way.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
strings.Contains("gemini-1") also matches gemini-10 and gemini-15, so a
future generation inherited the rules of a retired one. Measured:
ResolveOff("gemini-10-pro", ProviderGoogleAI) returned OffOmit, turning an
explicit disable into a silent no-op, which is the opposite of what the
classifier's own doc promises for unclassified names. gemma-30b fell into
the same trap via "gemma-3", and "gemini-3" would have sent thinking_level
to a hypothetical gemini-30 on the wire.
Every family token now has to end where the version ends, and both sides of
each boundary are pinned by a test.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Probing the API for every GPT-5 and o-series model showed two places where
the table disagreed with the vendor. The o-series accepts xhigh (verified
live on o3 and o4-mini), but the table capped it at high, so a request for
xhigh was silently downgraded. The first GPT-5 generation rejects
reasoning_effort "none" — its floor is "minimal" — yet ResolveOff sent
"none" and earned a 400 instead of the typed unsupported error.
An invalid reasoning_effort makes the API name the accepted set, which is
how the per-generation lists here were established: gpt-5/-mini/-nano take
minimal..high, gpt-5.1 takes none..high, and gpt-5.2 onward take
none..xhigh. No chat-completions model accepts "max". Pro and codex models
are absent because they are not served on /chat/completions at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ReasoningSupportFor fell back to IsReasoningModel, a catalog of prefixes
for models that exist today, so anything past its last entry answered
Supported=false: gpt-6, claude-opus-6 and gemini-4-pro all reported that
the vendor's next flagship does not think, and a consumer that trusts the
hint hides the reasoning controls entirely.
The hint now asks a separate classifier that fails open — a name from a
family whose current generations reason counts as reasoning unless it
matches a known pre-reasoning generation. Known stays false, so the tiers
are still not asserted and the UI is told to offer everything.
IsReasoningModel is untouched: it gates the temperature pin, the disable
resolver and the OpenAI hint branch, where a guess would change the
request rather than a form.
PAGI-125
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The model was added to budgetEffortClaude when it was first classified,
and the list gained it a second time when the 4.6 pair joined. Matching
is unaffected; the list is not.
PAGI-125
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The documented supported list starts at the 4.5 generation, so Opus 4,
Opus 4.1 and Sonnet 4 never took output_config.format — yet the gate only
named the claude-2, claude-instant and claude-3 families, and let them
through to a guaranteed 4xx. The entries are date-anchored, the way the
pre-adaptive list already spells them, so they cannot swallow 4.5 and
later.
The Bedrock footnote is narrower still, naming only Opus 4.6, Sonnet 4.6,
Sonnet 4.5, Opus 4.5 and Haiku 4.5, but a local block built on a
positive list would refuse models the platform may already serve, and
AWS pages have proven to lag the vendor. That one stays a live check.
PAGI-125
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude 4.6 and later, and Mythos Preview, answer 400 to a conversation
whose last message is an assistant turn: "This model does not support
assistant message prefill. The conversation must end with a user
message." The adapter only refused that shape when structured output was
also requested, so an ordinary prefilled call reached the API and failed
there.
The refusal is now the model's own property, checked on every request,
and the narrower structured-output conflict stays for the older models
that still accept a prefill.
Bedrock carries the same models through both of its doors and is very
likely to need the same guard, but AWS documents neither the restriction
nor its error, so that half waits for a live request rather than an
assumption.
PAGI-125
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Message prefilling is documented as incompatible with JSON outputs, and
removing the guard reddens "assistant prefill conflicts", so restating
the vendor rule in prose protects nothing.
PAGI-125
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The sampling comment stated a rule the vendor never published — that the
API rejects temperature and top_p together — as a general fact; it is
documented only for Haiku 4.5 and the retired Opus 4.1, and with extended
thinking top_p is allowed between 0.95 and 1. Dropping top_p on the
budget path stays as it was, a safe subset; only the wrong explanation
goes.
The rest either restate an assertion below them or guard a line a test
already pins: deleting the Gemini tool-turn guard reddens "a tool turn is
not a final answer" in both packages, so the guard needs no prose.
The capability tests also carried ids from review documents that live
outside the repository, which read like references but resolve to
nothing. The labels beside them already say what the rows are.
PAGI-125
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Opus 4.6 and Sonnet 4.6 are on the vendor's effort compatibility list and
still accept the deprecated budget_tokens mode, and effort works with or
without thinking. The set of models that take both named only Opus 4.5
and Mythos Preview, so a caller who explicitly chose budget thinking on a
4.6 model lost the level in silence — on all three doors.
PAGI-125
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Manual thinking accepts tool_choice "auto" or "none" only: "any" and a
named tool force tool use, which the mode does not allow. The adapter
forwarded the caller's choice unchanged, so the pair reached the API and
came back 400 — and the caller need not have picked it, since a model
that runs manual thinking only (the 4.5 generation) turns an adaptive
request into a budget one, and adaptive does allow forced tool use.
The request now fails locally with the model named, before a round trip.
Bedrock is unaffected: its Converse door pins tool choice to auto.
PAGI-125
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The model was in no table, so it took the unknown path: sampling params
were sent, though it rejects a non-default temperature, top_p or top_k on
every request; an explicit disable was forwarded, though it is always-on
and answers 400; and the hint reported no effort levels for a model that
accepts four.
It also breaks the assumption the sampling rule rested on. Mythos Preview
runs both thinking modes yet still rejects sampling, so that rejection is
now its own model list instead of a property derived from the
adaptive-only tier.
PAGI-125
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
display: "omitted" is the default on Fable 5, Mythos 5, Opus 5, Sonnet 5,
Opus 4.8 and Opus 4.7: a thinking block arrives with an empty thinking
field and a signature carrying the encrypted reasoning. Reading that as
"no reasoning" dropped the block on the response path and refused to emit
it on replay, so the signature never survived a turn.
The same loop also stopped after the first reasoning-bearing part, which
partially drops the block sequence of an interleaved-thinking turn — the
API requires that sequence to arrive complete and rejects a pruned one.
Both paths now treat a bare signature as reasoning and every block of the
turn is replayed. The 400 that the earlier fix chased came from the
omitempty tag on the required thinking field, removed in 31d03d38 and now
held by a test that asserts the field is on the wire.
PAGI-125
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The branch accumulated comments that narrate what the next line already
says, defend an implementation choice, or repeat a fact something else
already guarantees: the provider split in claudeCannotDisable is pinned
by reasoning_support_test.go, and t.Setenv panics on its own if a test
later adds t.Parallel.
One comment did carry a fact nothing checked — that the Claude effort
sets are not nested, so the dual generation takes max but not xhigh.
That one becomes a test.
PAGI-125
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The per-generation effort sets lived in the UI-hint package, where the
wire paths cannot reach them, so any wire-side use would have had to
copy the table. Move it next to the capability tables the wire already
reads and have the hint call it. Behaviour is unchanged; the sets are
identical.
PAGI-125
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both Bedrock doors wrote output_config only from the adaptive closure,
so a budget-only generation never carried an effort — and Opus 4.5 is
the one generation that takes both. The direct Anthropic path already
consults ClaudeSupportsEffortWithBudget for exactly this, and the
Bedrock README documents the behaviour the code was missing.
PAGI-125
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A StreamingFunc that returned an error broke the loop and dropped the
error on the floor, so GenerateContent answered (partial response, nil)
and the deferred callback wrapper reported HandleLLMGenerateContentEnd
on a call that had failed. The googleai twin already returns the pair;
its exported entry point needed the same fix and got it in 14bed4b9.
Both halves are here: the iterator now carries the callback error out,
and GenerateContent returns the accumulated response alongside it rather
than discarding it.
No hermetic test: the streaming iterator is a gRPC-backed struct of the
pinned vertexai SDK with unexported fields, and the client cannot be
built without GCP credentials, so this path is reachable only from the
credentialed integration tests.
PAGI-125
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The adaptive closure called GetEffort(0), which substitutes an internal
8192, while the legacy door passes the real limit — so identical options
produced different efforts on the two doors: a 3000-token budget against
a 32000-token request answered high on Converse and low on legacy. The
budget closure already read input.MaxTokens; hoisting that read above
both makes the doors agree.
PAGI-125
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The reasoning-model pin read the per-call model, so a client that names
its model at construction — or names none at all and falls through to
the package default — escaped it: openai.New() + WithTemperature(0.2)
sent 0.2 to gpt-5.4-mini while the same value with a per-call model
became 1.0. effectiveModel resolves call, client and package default and
is what the neighbouring reasoning-off and clamp paths already use.
Correcting the model to the pin also exposed that it fabricated a
temperature for callers who set none. It now only corrects a value the
caller actually chose; omitting still lands on the model's own 1.0, and
the request shape for those callers is unchanged.
PAGI-125
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Gemini has no separate finish reason for a function call: the turn
arrives as STOP with empty text. Both Google adapters validated every
STOP candidate, so WithStructuredOutput combined with WithTools failed
the legitimate tool call with ErrStructuredOutputValidation and the
agent loop never ran the tool. The googleai doc comment already claimed
tool outcomes were exempt.
The other four adapters get the exemption from their stop reason —
openai answers tool_calls, anthropic and bedrock tool_use — and ollama,
which shares Gemini's ambiguity, already carries this guard.
PAGI-125
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two gaps in the same hint. ReasoningSupportFor answered Known=true with
[low medium high] for any OpenAI reasoning model it does not classify —
gpt-5.5 and the rest of the 5.x line — presenting a guess as a fact and
contradicting the type's own promise to leave unknowns nil. And the hint
never carried the mechanism, so a consumer that needed to know a model
is adaptive-only had to reach past it into the capability tables and
keep a second source of truth.
Efforts are now filled only for a classified model, and Mechanism
reports effort, budget or either, derived from the same Claude table the
wire path reads.
PAGI-97
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
G703 and G704 were switched off for the whole repository on the grounds
that gosec taints any path or URL parameter by type. That is not what
the analyzer does: its sources are *http.Request, *url.URL, *bufio
readers and os.Args/Getenv/ReadFile, so a plain string parameter is
never tainted — and a repo-wide waiver silences the cases that would
matter, such as a handler opening a file named by its request.
Three sites are flagged today and each is legitimate: a test proxy
forwarding what it received, a temp-file name that url.Parse taint
reaches without being able to influence it, and a client fetching the
endpoint its caller configured. Waiving exactly those keeps the check
alive: a probe taking *http.Request into os.Open and http.Get is
reported again, while the tree lints clean.
PAGI-125
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The adapter recovered reasoning only by peeling <think> tags off the
content, while current servers put it in message.thinking instead — the
field went unread on both paths and the streaming assembler dropped it
when rebuilding the final message, so callers saw an empty
choice.Reasoning and no reasoning chunks. The committed cloud recording
carries a populated thinking field the adapter was ignoring.
Both spellings now feed the same reasoning value; a test pins each so
neither can be removed as the other's duplicate.
PAGI-125
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
resolveTemperature returned 1.0 for every gemini-3 model without ever
reading the default it was handed, so WithDefaultTemperature(0.2) was
discarded and the request ran at 1.0. Google recommends 1.0 there, but
the API accepts lower values, so the recommendation belongs in the
default rather than on top of an explicit choice. Options now record
that the caller set a temperature, which the package default alone
could not express — 0.5 is a legitimate value to configure.
PAGI-125
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ReasoningSupportFor derived CannotDisable straight from ResolveOff, so a
default-on Claude reached through an OpenAI-compatible gateway was
reported as disablable: the resolver answers OffDisableClaude, which
only the Anthropic adapter knows how to emit. A UI trusting the hint
rendered a reasoning-off toggle that the request then ignored, leaving
thinking on and billed.
The hint now answers for the provider that will carry the request.
Making the OpenAI adapter honour that outcome instead is the other half
and stays open: on a LiteLLM-fronted Anthropic model the modern
reasoning object is itself rejected.
PAGI-125
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>