Files
gbrain/test/anthropic-model-ids.test.ts
T
447e57ec41 fix(ai): tier-configured models reach the recipe allowlist — refresh Anthropic models, register tier resolutions, honest probe labels (#2800)
Setting `models.tier.deep anthropic:claude-opus-4-8` silently disabled
think and auto_think: the Anthropic recipe's chat allowlist stopped at
Opus 4.7, the tier-resolved model never joined the extended set that
assertTouchpoint's contract promises for config-chosen models, and the
resulting probe failure was stamped NO_ANTHROPIC_API_KEY — sending the
operator to debug env/keychain when the fix was the model id. Three
fixes, one per layer:

- recipes/anthropic.ts: add claude-fable-5, claude-opus-4-8, and
  claude-sonnet-5 to chat models; claude-sonnet-5 to expansion models.
- gateway.ts reconfigureGatewayWithEngine: resolve all four tiers and
  register the results as extended models, honoring the documented
  contract for models.default / models.tier.* (model-resolver.ts
  docstring). A tier-only model now validates like a chat/expansion one.
- think/index.ts: when the gateway client can't be built, re-probe and
  label honestly — MODEL_NOT_USABLE:<reason> for unknown_model /
  unknown_provider, NO_ANTHROPIC_API_KEY only for the actual missing-key
  case; the stub answer carries the probe detail and fix hint.

Tests: recipe-list presence pins; a new gateway-tier-extended-models
suite proving a fictional tier model validates post-reconfigure (and an
unconfigured one still doesn't); think-pipeline coverage for the honest
label (unknown_model beats missing-key even keyless); the existing
non-explicit bogus-provider test updated from the old catch-all label to
the honest one (no-throw contract unchanged).

Verified live: a brain with tier.deep=claude-opus-4-8 had think degrade
to gather-only with the misleading key warning; with this change the
probe passes and synthesis runs.

Co-authored-by: Paolo Belcastro <p3ob7o@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 13:06:32 -07:00

63 lines
2.8 KiB
TypeScript

import { describe, it, expect } from 'bun:test';
import { anthropic } from '../src/core/ai/recipes/anthropic.ts';
describe('Anthropic recipe model IDs', () => {
it('chat models use canonical Anthropic API IDs (no phantom dates)', () => {
const chatModels = anthropic.touchpoints?.chat?.models ?? [];
// claude-sonnet-4-6 is the correct API ID per Anthropic docs.
// The dated form claude-sonnet-4-6-20250929 returns 404 on the API.
expect(chatModels).toContain('claude-sonnet-4-6');
expect(chatModels).not.toContain('claude-sonnet-4-6-20250929');
});
it('expansion models use canonical IDs', () => {
const expansionModels = anthropic.touchpoints?.expansion?.models ?? [];
expect(expansionModels).toContain('claude-sonnet-4-6');
expect(expansionModels).not.toContain('claude-sonnet-4-6-20250929');
});
it('does not forward-alias dateless 4.6+ models (they ARE the canonical ID)', () => {
// Starting with Claude 4.6, Anthropic API IDs are dateless and pinned.
// No forward alias needed — the dateless form IS the model ID.
expect(anthropic.aliases?.['claude-sonnet-4-6']).toBeUndefined();
expect(anthropic.aliases?.['claude-opus-4-7']).toBeUndefined();
});
it('pre-4.6 models still have date-based aliases', () => {
// Haiku 4.5 predates the dateless convention, keeps its alias
expect(anthropic.aliases?.['claude-haiku-4-5']).toBe('claude-haiku-4-5-20251001');
});
it('reverse-alias rescues stale broken Sonnet 4.6 ID', () => {
// v0.31.6 shipped 'claude-sonnet-4-6-20250929' as a hardcoded default.
// Users with stale config (models.dream.synthesize, facts.extraction_model)
// must keep working — the reverse alias rewrites broken → canonical.
expect(anthropic.aliases?.['claude-sonnet-4-6-20250929']).toBe('claude-sonnet-4-6');
});
it('current-generation models are listed for chat (Fable 5 / Opus 4.8 / Sonnet 5)', () => {
// Regression guard for the tier-config incident: a brain with
// `models.tier.deep = anthropic:claude-opus-4-8` had think/auto_think
// silently degrade because the recipe list stopped at Opus 4.7.
const chatModels = anthropic.touchpoints?.chat?.models ?? [];
expect(chatModels).toContain('claude-fable-5');
expect(chatModels).toContain('claude-opus-4-8');
expect(chatModels).toContain('claude-sonnet-5');
});
it('Sonnet 5 is listed for expansion', () => {
expect(anthropic.touchpoints?.expansion?.models ?? []).toContain('claude-sonnet-5');
});
it('all listed models follow naming conventions', () => {
const allModels = [
...(anthropic.touchpoints?.chat?.models ?? []),
...(anthropic.touchpoints?.expansion?.models ?? []),
];
for (const m of allModels) {
// No model should contain a date that doesn't exist on the Anthropic API
expect(m).not.toMatch(/claude-sonnet-4-6-\d{8}/);
}
});
});