mirror of
https://github.com/garrytan/gbrain.git
synced 2026-08-14 00:48:18 +00:00
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>
60 lines
3.3 KiB
TypeScript
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);
|
|
});
|
|
});
|