@@ -4,30 +4,52 @@ import {
44 getAllowedModels ,
55 getModel ,
66 getModelMultiplier ,
7+ inferProvider ,
78 resolveModel ,
89 validateModelAccess ,
910} from "./catalog.js" ;
1011
1112describe ( "getModel" , ( ) => {
12- it ( "returns the model def for a known ID" , ( ) => {
13- const m = getModel ( "claude-haiku-4-5-20251001 " ) ;
13+ it ( "returns the model def for a known Claude ID" , ( ) => {
14+ const m = getModel ( "claude-haiku-4-5" ) ;
1415 expect ( m ) . toBeDefined ( ) ;
1516 expect ( m ?. provider ) . toBe ( "anthropic" ) ;
1617 expect ( m ?. multiplier ) . toBe ( 1 ) ;
1718 } ) ;
1819
20+ it ( "returns the model def for a known OpenAI ID" , ( ) => {
21+ const m = getModel ( "gpt-5" ) ;
22+ expect ( m ) . toBeDefined ( ) ;
23+ expect ( m ?. provider ) . toBe ( "openai" ) ;
24+ expect ( m ?. multiplier ) . toBe ( 8 ) ;
25+ } ) ;
26+
1927 it ( "returns undefined for an unknown ID" , ( ) => {
2028 expect ( getModel ( "gpt-99" ) ) . toBeUndefined ( ) ;
2129 } ) ;
2230} ) ;
2331
2432describe ( "getModelMultiplier" , ( ) => {
25- it ( "returns 1 for haiku" , ( ) => expect ( getModelMultiplier ( "claude-haiku-4-5-20251001 " ) ) . toBe ( 1 ) ) ;
33+ it ( "returns 1 for haiku" , ( ) => expect ( getModelMultiplier ( "claude-haiku-4-5" ) ) . toBe ( 1 ) ) ;
2634 it ( "returns 3 for sonnet" , ( ) => expect ( getModelMultiplier ( "claude-sonnet-4-6" ) ) . toBe ( 3 ) ) ;
2735 it ( "returns 15 for opus" , ( ) => expect ( getModelMultiplier ( "claude-opus-4-6" ) ) . toBe ( 15 ) ) ;
2836 it ( "returns 0 for ollama models" , ( ) => expect ( getModelMultiplier ( "ollama/llama3.2" ) ) . toBe ( 0 ) ) ;
29- it ( "returns 1 for unknown model (safe default)" , ( ) =>
30- expect ( getModelMultiplier ( "unknown" ) ) . toBe ( 1 ) ) ;
37+ it ( "returns 1 for gpt-5-mini" , ( ) => expect ( getModelMultiplier ( "gpt-5-mini" ) ) . toBe ( 1 ) ) ;
38+ it ( "returns 8 for gpt-5" , ( ) => expect ( getModelMultiplier ( "gpt-5" ) ) . toBe ( 8 ) ) ;
39+ it ( "returns 20 for o3" , ( ) => expect ( getModelMultiplier ( "o3" ) ) . toBe ( 20 ) ) ;
40+ it ( "returns 2 for unknown model (passthrough rate)" , ( ) =>
41+ expect ( getModelMultiplier ( "unknown-future-model" ) ) . toBe ( 2 ) ) ;
42+ } ) ;
43+
44+ describe ( "inferProvider" , ( ) => {
45+ it ( "infers anthropic for claude- prefix" , ( ) =>
46+ expect ( inferProvider ( "claude-sonnet-4-6" ) ) . toBe ( "anthropic" ) ) ;
47+ it ( "infers openai for gpt- prefix" , ( ) => expect ( inferProvider ( "gpt-5" ) ) . toBe ( "openai" ) ) ;
48+ it ( "infers openai for o3 prefix" , ( ) => expect ( inferProvider ( "o3-pro" ) ) . toBe ( "openai" ) ) ;
49+ it ( "infers openai for o4 prefix" , ( ) => expect ( inferProvider ( "o4-mini" ) ) . toBe ( "openai" ) ) ;
50+ it ( "infers ollama for ollama/ prefix" , ( ) =>
51+ expect ( inferProvider ( "ollama/llama3.2" ) ) . toBe ( "ollama" ) ) ;
52+ it ( "returns null for unknown prefix" , ( ) => expect ( inferProvider ( "gemini-pro" ) ) . toBeNull ( ) ) ;
3153} ) ;
3254
3355describe ( "getAllowedModels" , ( ) => {
@@ -36,42 +58,40 @@ describe("getAllowedModels", () => {
3658 expect ( allowed . every ( ( m ) => m . isLocal ) ) . toBe ( true ) ;
3759 } ) ;
3860
39- it ( "m1 tier can use haiku and ollama" , ( ) => {
61+ it ( "m1 tier can use haiku, gpt-5-mini, o4-mini, and ollama" , ( ) => {
4062 const ids = getAllowedModels ( "m1" ) . map ( ( m ) => m . id ) ;
41- expect ( ids ) . toContain ( "claude-haiku-4-5-20251001" ) ;
63+ expect ( ids ) . toContain ( "claude-haiku-4-5" ) ;
64+ expect ( ids ) . toContain ( "gpt-5-mini" ) ;
65+ expect ( ids ) . toContain ( "o4-mini" ) ;
4266 expect ( ids ) . toContain ( "ollama/llama3.2" ) ;
4367 expect ( ids ) . not . toContain ( "claude-sonnet-4-6" ) ;
4468 expect ( ids ) . not . toContain ( "claude-opus-4-6" ) ;
4569 } ) ;
4670
47- it ( "m5 tier can use haiku and sonnet but not opus" , ( ) => {
71+ it ( "m5 tier can use sonnet and gpt-5 but not opus or o3 " , ( ) => {
4872 const ids = getAllowedModels ( "m5" ) . map ( ( m ) => m . id ) ;
49- expect ( ids ) . toContain ( "claude-haiku-4-5-20251001" ) ;
5073 expect ( ids ) . toContain ( "claude-sonnet-4-6" ) ;
74+ expect ( ids ) . toContain ( "gpt-5" ) ;
5175 expect ( ids ) . not . toContain ( "claude-opus-4-6" ) ;
76+ expect ( ids ) . not . toContain ( "o3" ) ;
5277 } ) ;
5378
54- it ( "m10 tier can use all models" , ( ) => {
79+ it ( "m10 tier can use all models including opus and o3 " , ( ) => {
5580 const ids = getAllowedModels ( "m10" ) . map ( ( m ) => m . id ) ;
56- expect ( ids ) . toContain ( "claude-haiku-4-5-20251001" ) ;
57- expect ( ids ) . toContain ( "claude-sonnet-4-6" ) ;
58- expect ( ids ) . toContain ( "claude-opus-4-6" ) ;
59- } ) ;
60-
61- it ( "internal tier can use all models" , ( ) => {
62- const ids = getAllowedModels ( "internal" ) . map ( ( m ) => m . id ) ;
6381 expect ( ids ) . toContain ( "claude-opus-4-6" ) ;
82+ expect ( ids ) . toContain ( "o3" ) ;
83+ expect ( ids ) . toContain ( "o3-pro" ) ;
84+ expect ( ids ) . toContain ( "gpt-5.4-pro" ) ;
6485 } ) ;
6586} ) ;
6687
6788describe ( "validateModelAccess" , ( ) => {
6889 it ( "allows access tier to use ollama" , ( ) => {
69- const result = validateModelAccess ( "access" , "ollama/llama3.2" ) ;
70- expect ( result . allowed ) . toBe ( true ) ;
90+ expect ( validateModelAccess ( "access" , "ollama/llama3.2" ) . allowed ) . toBe ( true ) ;
7191 } ) ;
7292
7393 it ( "denies access tier from using haiku" , ( ) => {
74- const result = validateModelAccess ( "access" , "claude-haiku-4-5-20251001 " ) ;
94+ const result = validateModelAccess ( "access" , "claude-haiku-4-5" ) ;
7595 expect ( result . allowed ) . toBe ( false ) ;
7696 expect ( result . reason ) . toMatch ( / m 1 / ) ;
7797 } ) ;
@@ -89,30 +109,53 @@ describe("validateModelAccess", () => {
89109 } ) ;
90110
91111 it ( "allows m10 tier to use opus" , ( ) => {
92- const result = validateModelAccess ( "m10" , "claude-opus-4-6" ) ;
112+ expect ( validateModelAccess ( "m10" , "claude-opus-4-6" ) . allowed ) . toBe ( true ) ;
113+ } ) ;
114+
115+ it ( "allows m1 tier to use gpt-5-mini" , ( ) => {
116+ expect ( validateModelAccess ( "m1" , "gpt-5-mini" ) . allowed ) . toBe ( true ) ;
117+ } ) ;
118+
119+ it ( "allows m5 tier to use gpt-5" , ( ) => {
120+ expect ( validateModelAccess ( "m5" , "gpt-5" ) . allowed ) . toBe ( true ) ;
121+ } ) ;
122+
123+ it ( "allows m1+ passthrough for unknown claude model" , ( ) => {
124+ const result = validateModelAccess ( "m1" , "claude-future-model-9" ) ;
93125 expect ( result . allowed ) . toBe ( true ) ;
126+ expect ( result . passthrough ) . toBe ( true ) ;
127+ } ) ;
128+
129+ it ( "allows m1+ passthrough for unknown gpt model" , ( ) => {
130+ const result = validateModelAccess ( "m1" , "gpt-6" ) ;
131+ expect ( result . allowed ) . toBe ( true ) ;
132+ expect ( result . passthrough ) . toBe ( true ) ;
133+ } ) ;
134+
135+ it ( "denies access tier passthrough" , ( ) => {
136+ const result = validateModelAccess ( "access" , "gpt-6" ) ;
137+ expect ( result . allowed ) . toBe ( false ) ;
94138 } ) ;
95139
96- it ( "denies unknown model with clear error " , ( ) => {
97- const result = validateModelAccess ( "enterprise" , "gpt-99 " ) ;
140+ it ( "denies unknown prefix with no inferred provider " , ( ) => {
141+ const result = validateModelAccess ( "enterprise" , "gemini-pro " ) ;
98142 expect ( result . allowed ) . toBe ( false ) ;
99143 expect ( result . reason ) . toMatch ( / U n k n o w n m o d e l / ) ;
100144 } ) ;
101145} ) ;
102146
103147describe ( "resolveModel" , ( ) => {
104148 it ( "returns the requested model if allowed" , ( ) => {
105- expect ( resolveModel ( "m5" , "claude-haiku-4-5-20251001 " ) ) . toBe ( "claude-haiku-4-5-20251001 " ) ;
149+ expect ( resolveModel ( "m5" , "claude-haiku-4-5" ) ) . toBe ( "claude-haiku-4-5" ) ;
106150 } ) ;
107151
108152 it ( "falls back to tier default if requested model is denied" , ( ) => {
109- // m1 requesting opus → should fall back to m1 default
110153 expect ( resolveModel ( "m1" , "claude-opus-4-6" ) ) . toBe ( DEFAULT_MODEL . m1 ) ;
111154 } ) ;
112155
113156 it ( "returns tier default when no model is requested" , ( ) => {
114157 expect ( resolveModel ( "access" ) ) . toBe ( "ollama/llama3.2" ) ;
115- expect ( resolveModel ( "m1" ) ) . toBe ( "claude-haiku-4-5-20251001 " ) ;
158+ expect ( resolveModel ( "m1" ) ) . toBe ( "claude-haiku-4-5" ) ;
116159 expect ( resolveModel ( "m5" ) ) . toBe ( "claude-sonnet-4-6" ) ;
117160 expect ( resolveModel ( "m10" ) ) . toBe ( "claude-opus-4-6" ) ;
118161 } ) ;
0 commit comments