fix(providers): send maxOutputTokens under the name the SDK reads - #1335
Conversation
The AI SDK renamed this setting to maxOutputTokens in v5. Nanocoder was still sending the v4 name maxTokens, and because object spreads bypass excess-property checking it was dropped silently rather than failing to compile. Every request fell back to whatever ceiling the provider inferred from the model id, and /tune's max-tokens control has been inert since the v6 upgrade. That is worst on sdkProvider: 'anthropic', where @ai-sdk/anthropic reads the ceiling off the model id and falls back to 4096 for anything it does not recognise as a Claude model. An Anthropic-compatible endpoint serving some other model had every reply truncated at 4096 with no error. Also add maxOutputTokens to provider entries in agents.config.json. Headless runs build modeOverrides without modelParameters, so /tune is not a route CI can use; the provider entry is. A /tune value still wins where one is set, and the user-facing name stays maxTokens because it is persisted in tune preferences. Tests assert what reaches the wire rather than what typechecks, since typechecking demonstrably could not catch this. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
nc-review: nothing to raise@will-lamerton — nothing to raise from the automated review. Renames the SDK-bound ceiling setting from 🔴 blocking · 🟠 a reviewer would ask for a change · ⚪ optional Automated code review — correctness, security, design, tests, plus duplicates and scope. A human still decides; this is not a substitute for review and is not exhaustive. The required status checks separately cover lint, formatting, types, unused dependencies, the test suite and the build. This bot never merges. Maintainers can rerun with |
@ai-sdk/anthropic reads the ceiling off the model id and falls back to 4096 for anything that is not a known Claude model. `minimax-m3` is not one, so every review turn was capped at 4096 tokens and stopped mid-sentence, having never called write_file. That is the whole of "nc-review could not produce a verdict this run": three runs on prompt-scrubber died that way, twice on the same PR. Nothing in the logs said so. A truncated turn arrives as content with no tool calls, which is what a finished turn looks like. Default 32000, deliberately conservative. MiniMax documents 131072 as the recommended output limit and OpenRouter reports 262144 for the same model; the numbers disagree across providers and an oversized value is rejected outright by some endpoints rather than clamped. A verdict is a few thousand tokens, so there is nothing to buy by going near either. Guarded with jq rather than trusted: a missing ceiling does not fail the run, it just silently truncates, and that is expensive to diagnose from a transcript that simply stops. Requires Nano-Collective/nanocoder#1335 for the setting to be read. Until that lands the key is ignored, so this is safe to land first. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The bug
The AI SDK renamed this setting to
maxOutputTokensin v5. We're onai@6.0.193and still sendingmaxTokens(chat-handler.ts). The v6 typings contain 8 references tomaxOutputTokensand zeromaxTokens:declarations, so the value was being dropped on the floor.It typechecked because the call site spreads it:
Object spreads bypass excess-property checking. No compile error, no runtime error, no log - the ceiling just quietly became whatever the provider inferred from the model id.
/tune's max-tokens control has therefore been inert for every provider and every user since the v6 upgrade.Why it's worse than it sounds
@ai-sdk/anthropicderives the ceiling from the model id, with this fallback (dist/index.mjs:5222):Point
sdkProvider: "anthropic"at an Anthropic-compatible endpoint serving a non-Claude model and every reply is capped at 4096 tokens, truncated mid-sentence with no error.That is what has been breaking
nc-reviewonNano-Collective/prompt-scrubber: reviews died mid-analysis without ever writing their verdict. nanocoder#1332 made the truncation visible and recoverable; this removes the cause.Changes
maxOutputTokens, restoring/tune.maxOutputTokensto provider entries inagents.config.json. Headless runs buildmodeOverrideswithoutmodelParameters, so/tuneis not a route CI can use - the provider entry is./tunestill wins where set.maxTokensinModelParameters; it's persisted in tune preferences, so renaming would silently discard existing user config. The SDK name is applied at the boundary.Behaviour change worth flagging
Fixing this means tune values start applying. The
Nano (low-end hardware)preset setsmaxTokens: 2048, which until now did nothing and will now actually cap output. That is the documented intent of the preset, but anyone on it will notice shorter replies.Tests
Three in
chat-handler.spec.ts, asserting the request body over an interceptedfetchrather than trusting types - typechecking demonstrably cannot catch this class of bug:max_tokens: 4096(pins the default that makes this necessary)maxOutputTokens: 32000reaches the wire asmax_tokens: 32000maxTokens: 32000does not - a guard that fails loudly if the SDK ever renames backPlus
maxOutputTokensplumbing asserted inclient-factory.spec.ts.chat-handler.spec.ts(29),client-factory.spec.ts(43),config-schema.spec.ts(47) pass;tsc --noEmitandbiome ciclean. I did not run the full ava suite locally.🤖 Generated with Claude Code