diff --git a/src/models.ts b/src/models.ts index 03807be..ccc2d0c 100644 --- a/src/models.ts +++ b/src/models.ts @@ -922,6 +922,27 @@ const SHORT_NAMES: Record = { 'glm-5p2': 'GLM-5.2', 'qwen3p7-plus': 'Qwen 3.7 Plus', 'kimi-k2p7-code': 'Kimi K2.7 Code', + // Ids that price correctly but had no display entry, so reports showed the + // raw slug. All display-only. The GPT-5.6 variants are listed individually + // rather than as a bare `gpt-5.6`: a base entry would swallow every future + // `gpt-5.6-*` via the prefix match and hide the variant, which is exactly + // what getShortModelName's version-boundary rule is there to prevent. + 'gpt-5.6-sol': 'GPT-5.6 Sol', + 'gpt-5.6-terra': 'GPT-5.6 Terra', + 'gpt-5.6-luna': 'GPT-5.6 Luna', + // The Grok Build harness reports the model it runs (`grok-4.5`), so this is + // the model's own name; `grok-build*` ids still resolve to "Grok Build". + 'grok-4.5': 'Grok 4.5', + // ClinePass routes models as `cline-pass/`; getShortModelName's path + // fallback strips the prefix and re-resolves the bare slug through this + // table, the same way it handles `accounts/fireworks/models/`. + 'qwen3.7-max': 'Qwen 3.7 Max', + 'mimo-v2.5-pro': 'MiMo v2.5 Pro', + // Both spellings occur in the wild: OpenRouter gap-filled keys are lowercase + // slugs while sessions report the capitalized name (see the case-insensitive + // pricing index above). SHORT_NAMES matching is case-sensitive, so map both. + 'minimax-m3': 'MiniMax M3', + 'MiniMax-M3': 'MiniMax M3', } // Sorted longest-first so more-specific prefixes match before shorter ones. diff --git a/tests/models.test.ts b/tests/models.test.ts index 476eca7..adbf9fa 100644 --- a/tests/models.test.ts +++ b/tests/models.test.ts @@ -123,6 +123,36 @@ describe('getShortModelName', () => { expect(getShortModelName('accounts/fireworks/models/some-unlisted-slug')).toBe('some-unlisted-slug') }) + it('names GPT-5.6 variants individually rather than collapsing them', () => { + expect(getShortModelName('gpt-5.6-sol')).toBe('GPT-5.6 Sol') + expect(getShortModelName('gpt-5.6-terra')).toBe('GPT-5.6 Terra') + expect(getShortModelName('gpt-5.6-luna')).toBe('GPT-5.6 Luna') + // No bare `gpt-5.6` entry exists, so an unlisted future variant must still + // fall through to its raw id rather than borrow a sibling's label. + expect(getShortModelName('gpt-5.6-unlisted')).toBe('gpt-5.6-unlisted') + }) + + it('names grok-4.5 without disturbing the Grok Build harness label', () => { + // The Grok Build CLI reports the model it runs, so the model id gets the + // model's name; ids that really are grok-build keep the harness label. + expect(getShortModelName('grok-4.5')).toBe('Grok 4.5') + expect(getShortModelName('grok-build-0.1')).toBe('Grok Build') + }) + + it('names ClinePass-routed slugs through the path fallback', () => { + // ClinePass ids arrive as `cline-pass/`; the path fallback strips the + // prefix and re-resolves the bare slug, as it does for Fireworks ids. + expect(getShortModelName('cline-pass/qwen3.7-max')).toBe('Qwen 3.7 Max') + expect(getShortModelName('cline-pass/minimax-m3')).toBe('MiniMax M3') + expect(getShortModelName('cline-pass/mimo-v2.5-pro')).toBe('MiMo v2.5 Pro') + expect(getShortModelName('cline-pass/kimi-k3')).toBe('Kimi K3') + }) + + it('names MiniMax M3 in both the lowercase-slug and capitalized spellings', () => { + expect(getShortModelName('minimax-m3')).toBe('MiniMax M3') + expect(getShortModelName('MiniMax-M3')).toBe('MiniMax M3') + }) + it('resolves Fireworks-hosted fleet models to friendly names via the path fallback', () => { // Real ids are the full Fireworks path `accounts/fireworks/models/`. expect(getShortModelName('accounts/fireworks/models/glm-5p2')).toBe('GLM-5.2')