import { describe, expect, test } from 'bun:test'; import { isRealModel, officerModelForApiModel, SYNTHETIC_MODEL } from './model-aliases'; /** * The five model strings below are not invented — they are every distinct `message.model` found in a real * 59MB transcript on this machine, in order of first appearance. Two of them are the reason this map is * needed at all: `` is not a model, and `claude-haiku-4-5-20251001` carries a date suffix the * others do not, so an exhaustive id list would have missed it. */ describe('officerModelForApiModel', () => { test('resolves the families Officer offers', () => { expect(officerModelForApiModel('claude-opus-5')).toBe('claude-code/opus'); expect(officerModelForApiModel('claude-sonnet-5')).toBe('claude-code/sonnet'); expect(officerModelForApiModel('claude-fable-5')).toBe('claude-code/fable'); // Date-suffixed, which is why the match is a family prefix rather than a lookup table. expect(officerModelForApiModel('claude-haiku-4-5-20251001')).toBe('claude-code/haiku'); }); test('a model released tomorrow still resolves to its family', () => { // The point of prefix matching: a new version must not silently become "unknown". expect(officerModelForApiModel('claude-opus-6')).toBe('claude-code/opus'); expect(officerModelForApiModel('claude-sonnet-9-20301231')).toBe('claude-code/sonnet'); }); test('the synthetic marker is not a model', () => { // Claude Code writes this for entries it generated itself. Storing it would put a value in the picker // that the CLI would reject. expect(officerModelForApiModel(SYNTHETIC_MODEL)).toBeNull(); expect(isRealModel(SYNTHETIC_MODEL)).toBe(false); expect(isRealModel('claude-opus-5')).toBe(true); }); test('mythos resolves to null rather than to a neighbouring family', () => { // Project Glasswing only, so it has no Officer entry. A picker option that 401s is worse than none. expect(officerModelForApiModel('claude-mythos-5')).toBeNull(); expect(officerModelForApiModel('claude-mythos-preview')).toBeNull(); }); test('null for anything it cannot honestly name', () => { for (const input of [null, undefined, '', ' ', 'gpt-5', 'anthropic/claude-3-5', 'opus']) { expect(officerModelForApiModel(input)).toBeNull(); } }); test('the lossy direction is documented by the map itself', () => { // `opus` and `opus[1m]` both serve claude-opus-5, so this can only ever return the bare family — which // is exactly why a real turn must overwrite the guess. If someone "improves" this to return a [1m] // variant, they have invented information the transcript does not contain. expect(officerModelForApiModel('claude-opus-5')).not.toContain('[1m]'); expect(officerModelForApiModel('claude-sonnet-5')).not.toContain('[1m]'); }); });