Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/adapters/anthropic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1050,7 +1050,17 @@ export function createAnthropicAdapter(provider: OcxProviderConfig, cacheRetenti
// always exceeds the budget within a model-safe ceiling, reserving room for visible output.
const maxOut = parsed.options.maxOutputTokens ?? omittedMaxTokens;
const wantBudget = reasoningBudget(effectiveReasoning);
const maxTokens = Math.min(REASONING_MAX_TOKENS_CEILING, Math.max(maxOut, wantBudget + OUTPUT_HEADROOM));
// Clamp to what the TARGET can emit, not to a flat 32k. REASONING_MAX_TOKENS_CEILING
// dates from Claude 4.x models that really stopped near 32k; Opus 4.6 and Sonnet 4.6
// document 128K and Haiku 4.5 64K, so the flat ceiling silently cut an explicit
// caller `max_tokens` to a quarter of what the model supports — the adaptive branch
// above honours the caller's figure, and this one did not. A provider that states the
// model's real maximum is more authoritative than the constant; it stays the fallback
// for a target whose ceiling is unknown.
const modelCeiling = typeof configuredMaxOut === "number" && configuredMaxOut > 0
? configuredMaxOut
: REASONING_MAX_TOKENS_CEILING;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
const maxTokens = Math.min(modelCeiling, Math.max(maxOut, wantBudget + OUTPUT_HEADROOM));
const budget = Math.max(MIN_THINKING_BUDGET, Math.min(wantBudget, maxTokens - OUTPUT_FLOOR));
body.max_tokens = maxTokens;
body.thinking = { type: "enabled", budget_tokens: budget };
Expand Down
26 changes: 26 additions & 0 deletions tests/adapters/anthropic/anthropic-reasoning.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,32 @@ describe("anthropic extended-thinking gate", () => {
expect(b.max_tokens as number).toBe(64000);
});

// The adaptive branch above already honours an explicit limit above 32k. Budget thinking did
// not: it clamped to a flat REASONING_MAX_TOKENS_CEILING (32000) even for Opus 4.6 and Sonnet
// 4.6, which document 128K output, so a caller asking for 128000 silently got a quarter of it.
test("budget thinking clamps to the model's real maximum, not a flat 32k", async () => {
const seeded = {
...provider,
defaultMaxOutputTokens: 64_000,
modelMaxOutputTokens: { "claude-opus-4-6": 128_000, "claude-sonnet-4-6": 128_000 },
};
for (const modelId of ["claude-opus-4-6", "claude-sonnet-4-6"]) {
for (const asked of [undefined, 128_000]) {
const b = await bodyOf(parsed("high", asked === undefined ? {} : { maxOutputTokens: asked }, modelId), seeded);
expect(b.max_tokens as number, modelId).toBe(128_000);
expect(b.max_tokens as number).toBeGreaterThan((b.thinking as { budget_tokens: number }).budget_tokens);
}
}
// Haiku 4.5 really is 64K, so it must be capped there rather than raised to 128000.
const haiku = await bodyOf(parsed("high", { maxOutputTokens: 128_000 }, "claude-haiku-4-5"), seeded);
expect(haiku.max_tokens as number).toBe(64_000);
// A small explicit limit is still only lifted to budget+headroom, exactly as before.
expect((await bodyOf(parsed("high", { maxOutputTokens: 10_000 }, "claude-opus-4-6"), seeded)).max_tokens).toBe(24_576);
// With no configured maxima the flat 32k ceiling remains the fallback.
const bare = { adapter: "anthropic", baseUrl: "https://api.anthropic.com", apiKey: "sk-x", authMode: "apiKey" } as unknown as OcxProviderConfig;
expect((await bodyOf(parsed("high", { maxOutputTokens: 128_000 }, "claude-opus-4-6"), bare)).max_tokens).toBe(32_000);
});

test("configured provider output budget replaces the 8192 default when the caller omits max_output_tokens", async () => {
const budgeted = { ...provider, defaultMaxOutputTokens: 64_000, modelMaxOutputTokens: { "claude-fable-5": 32_000 } };
// No reasoning: the configured budget is the wire max_tokens.
Expand Down
Loading