Files
gbrain/test/think-max-output-tokens.test.ts
MasaandClaude Fable 5 485c4773ca fix(think): extend 16K output headroom to OpenAI reasoning models (#3916)
maxOutputTokensFor() gave the 16000-token output budget only to
thinking-by-default Anthropic Claude 5 models; every other model kept the
4000 default. OpenAI reasoning models (the gpt-5 family and the numbered
o-series) bill internal reasoning tokens as output tokens counting against
max_tokens, so the 4000 cap starved think's actual answer the same way.

Add OPENAI_REASONING_MODEL_RE alongside the existing Claude-5 regex,
deliberately scoped to the gpt-5 family + numbered o-series with the
non-reasoning ChatGPT -chat snapshots (gpt-5-chat-latest,
gpt-5.2-chat-latest) excluded. Defaults (4000/16000) are unchanged and no
config knob is added; gpt-4o, other providers, and provider-prefixed
spellings outside openai:/openai/ keep 4000.

Tests pin the new 16000 cases (colon + slash forms), the chat-snapshot and
codex-mini-latest exclusions, and name/version boundaries (gpt-50, o3foo,
bare model names, openrouter-nested ids).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 19:14:00 +07:00

60 lines
3.3 KiB
TypeScript

/**
* Pins `maxOutputTokensFor` — the per-model output-token budget `runThink`
* passes to `client.create`. Thinking-by-default Claude 5 models
* (`anthropic:claude-*-5`) and OpenAI reasoning models (gpt-5 family,
* o-series) spend a large share of the budget on internal reasoning before
* emitting an answer, so the 4000 default left `think` with empty/truncated
* text. They get 16000; everything else stays 4000.
*/
import { describe, test, expect } from 'bun:test';
import { maxOutputTokensFor } from '../src/core/think/index.ts';
describe('maxOutputTokensFor — thinking-default headroom', () => {
test('Claude 5 family gets 16000', () => {
expect(maxOutputTokensFor('anthropic:claude-sonnet-5')).toBe(16000);
expect(maxOutputTokensFor('anthropic:claude-opus-5')).toBe(16000);
expect(maxOutputTokensFor('anthropic:claude-fable-5')).toBe(16000);
expect(maxOutputTokensFor('anthropic:claude-haiku-5')).toBe(16000);
expect(maxOutputTokensFor('anthropic/claude-sonnet-5')).toBe(16000); // slash form
});
test('OpenAI reasoning models (gpt-5 family, o-series) get 16000', () => {
expect(maxOutputTokensFor('openai:gpt-5')).toBe(16000);
expect(maxOutputTokensFor('openai:gpt-5.2')).toBe(16000);
expect(maxOutputTokensFor('openai:gpt-5.5')).toBe(16000);
expect(maxOutputTokensFor('openai:gpt-5-mini')).toBe(16000);
expect(maxOutputTokensFor('openai:o1')).toBe(16000);
expect(maxOutputTokensFor('openai:o3')).toBe(16000);
expect(maxOutputTokensFor('openai:o4-mini')).toBe(16000);
expect(maxOutputTokensFor('openai/gpt-5.2')).toBe(16000); // slash form
});
test('non-Claude-5 and non-reasoning models keep 4000', () => {
expect(maxOutputTokensFor('anthropic:claude-opus-4-8')).toBe(4000);
expect(maxOutputTokensFor('anthropic:claude-haiku-4-5')).toBe(4000);
expect(maxOutputTokensFor('anthropic:claude-sonnet-4-6')).toBe(4000);
expect(maxOutputTokensFor('anthropic:claude-3-haiku')).toBe(4000);
expect(maxOutputTokensFor('openai:gpt-4o')).toBe(4000);
expect(maxOutputTokensFor('openai:gpt-4o-mini')).toBe(4000);
expect(maxOutputTokensFor('openai:gpt-4.1')).toBe(4000);
// Non-reasoning ChatGPT snapshots of the gpt-5 family stay at 4000.
expect(maxOutputTokensFor('openai:gpt-5-chat-latest')).toBe(4000);
expect(maxOutputTokensFor('openai:gpt-5.2-chat-latest')).toBe(4000);
// Scope is the gpt-5 family + numbered o-series only — other OpenAI
// reasoning-capable ids (e.g. codex-mini-latest) keep the conservative
// default until deliberately added.
expect(maxOutputTokensFor('openai:codex-mini-latest')).toBe(4000);
// Version/name boundaries: `gpt-50` and `o3foo` are not gpt-5 / o3.
expect(maxOutputTokensFor('openai:gpt-50')).toBe(4000);
expect(maxOutputTokensFor('openai:o3foo')).toBe(4000);
// Other providers' reasoning models are out of scope here — the routed
// provider recipe, not this budget, is what changes for them.
expect(maxOutputTokensFor('deepseek:deepseek-reasoner')).toBe(4000);
// Prefix must be the openai provider — a bare model name or another
// provider's gpt-5 spelling doesn't match.
expect(maxOutputTokensFor('o3')).toBe(4000);
expect(maxOutputTokensFor('gpt-5.2')).toBe(4000);
expect(maxOutputTokensFor('openrouter:openai/gpt-5.2')).toBe(4000);
});
});