model.utils.test.ts161 lines · main
1import { describe, expect, it } from 'vitest'
2
3import {
4 ASSISTANT_MODELS,
5 DEFAULT_ASSISTANT_ADVANCE_MODEL_ID,
6 DEFAULT_ASSISTANT_BASE_MODEL_ID,
7 DEFAULT_COMPLETION_MODEL,
8 defaultAssistantModelId,
9 getAssistantModelEntry,
10 getDefaultModelForProvider,
11 isAdvanceOnlyModelId,
12 isAssistantBaseModelId,
13 isKnownAssistantModelId,
14 openaiModelEntry,
15 PROVIDERS,
16} from './model.utils'
17import type { ProviderName } from './model.utils'
18
19describe('model.utils', () => {
20 describe('getDefaultModelForProvider', () => {
21 it('should return correct default for bedrock provider', () => {
22 const result = getDefaultModelForProvider('bedrock')
23 expect(result).toBe('openai.gpt-oss-120b-1:0')
24 })
25
26 it('should return correct default for openai provider', () => {
27 const result = getDefaultModelForProvider('openai')
28 expect(result).toBe('gpt-5.4-nano')
29 })
30
31 it('should return undefined for unknown provider', () => {
32 const result = getDefaultModelForProvider('unknown' as ProviderName)
33 expect(result).toBeUndefined()
34 })
35 })
36
37 describe('PROVIDERS registry', () => {
38 it('should have bedrock provider with models', () => {
39 expect(PROVIDERS.bedrock).toBeDefined()
40 expect(PROVIDERS.bedrock.models).toBeDefined()
41 expect(Object.keys(PROVIDERS.bedrock.models)).toContain(
42 'anthropic.claude-3-7-sonnet-20250219-v1:0'
43 )
44 expect(Object.keys(PROVIDERS.bedrock.models)).toContain('openai.gpt-oss-120b-1:0')
45 })
46
47 it('should have openai provider with models', () => {
48 expect(PROVIDERS.openai).toBeDefined()
49 expect(PROVIDERS.openai.models).toBeDefined()
50 expect(Object.keys(PROVIDERS.openai.models)).toContain('gpt-5.3-codex')
51 expect(Object.keys(PROVIDERS.openai.models)).toContain('gpt-5.4-nano')
52 })
53
54 it('should have exactly one default model per provider', () => {
55 const providers: ProviderName[] = ['bedrock', 'openai']
56
57 providers.forEach((provider) => {
58 const models = PROVIDERS[provider].models
59 const defaultModels = Object.entries(models).filter(([_, config]) => config.default)
60 expect(defaultModels.length).toBe(1)
61 })
62 })
63
64 it('should have valid model configurations', () => {
65 const providers: ProviderName[] = ['bedrock', 'openai']
66
67 providers.forEach((provider) => {
68 const models = PROVIDERS[provider].models
69 Object.entries(models).forEach(([_modelId, config]) => {
70 expect(config).toHaveProperty('default')
71 expect(typeof config.default).toBe('boolean')
72 })
73 })
74 })
75
76 it('should have bedrock model with systemProviderOptions', () => {
77 const sonnetModel = PROVIDERS.bedrock.models['anthropic.claude-3-7-sonnet-20250219-v1:0']
78 expect(sonnetModel.systemProviderOptions).toBeDefined()
79 expect(sonnetModel.systemProviderOptions?.bedrock).toBeDefined()
80 expect(sonnetModel.systemProviderOptions?.bedrock?.cachePoint).toEqual({
81 type: 'default',
82 })
83 })
84
85 it('should have openai provider with providerOptions', () => {
86 expect(PROVIDERS.openai.providerOptions).toBeDefined()
87 expect(PROVIDERS.openai.providerOptions?.openai).toBeDefined()
88 expect(PROVIDERS.openai.providerOptions?.openai?.reasoningEffort).toBeUndefined()
89 })
90 })
91
92 describe('assistant model registry', () => {
93 it('should have non-empty base and advance tiers', () => {
94 expect(
95 ASSISTANT_MODELS.filter((m) => !m.requiresAdvanceModelEntitlement).length
96 ).toBeGreaterThan(0)
97 expect(
98 ASSISTANT_MODELS.filter((m) => m.requiresAdvanceModelEntitlement).length
99 ).toBeGreaterThan(0)
100 })
101
102 it('all model IDs should be unique', () => {
103 const ids = ASSISTANT_MODELS.map((m) => m.id)
104 expect(new Set(ids).size).toBe(ids.length)
105 })
106
107 it('should have all models in openai provider registry', () => {
108 ASSISTANT_MODELS.forEach((entry) => {
109 expect(Object.keys(PROVIDERS.openai.models)).toContain(entry.id)
110 })
111 })
112
113 it('defaults should satisfy unions', () => {
114 expect(DEFAULT_ASSISTANT_BASE_MODEL_ID).toBe('gpt-5.4-nano')
115 expect(DEFAULT_ASSISTANT_ADVANCE_MODEL_ID).toBe('gpt-5.3-codex')
116 expect(defaultAssistantModelId(false)).toBe(DEFAULT_ASSISTANT_BASE_MODEL_ID)
117 expect(defaultAssistantModelId(true)).toBe(DEFAULT_ASSISTANT_ADVANCE_MODEL_ID)
118 })
119
120 it('isAssistantBaseModelId / isAdvanceOnlyModelId', () => {
121 expect(isAssistantBaseModelId('gpt-5.4-nano')).toBe(true)
122 expect(isAssistantBaseModelId('gpt-5.3-codex')).toBe(false)
123 expect(isAdvanceOnlyModelId('gpt-5.3-codex')).toBe(true)
124 expect(isAdvanceOnlyModelId('gpt-5.4-nano')).toBe(false)
125 })
126
127 it('isKnownAssistantModelId', () => {
128 expect(isKnownAssistantModelId('gpt-5.4-nano')).toBe(true)
129 expect(isKnownAssistantModelId('gpt-5.3-codex')).toBe(true)
130 expect(isKnownAssistantModelId('gpt-5')).toBe(false)
131 expect(isKnownAssistantModelId('gpt-5-mini')).toBe(false)
132 expect(isKnownAssistantModelId('unknown')).toBe(false)
133 })
134
135 it('getAssistantModelEntry returns config for known ids', () => {
136 expect(getAssistantModelEntry('gpt-5.4-nano').reasoningEffort).toBe('low')
137 expect(getAssistantModelEntry('gpt-5.3-codex').reasoningEffort).toBe('low')
138 expect(getAssistantModelEntry('gpt-5.4-nano')).toEqual(
139 ASSISTANT_MODELS.find((m) => m.id === 'gpt-5.4-nano')
140 )
141 })
142
143 it('DEFAULT_COMPLETION_MODEL is gpt-5.4-nano with no reasoning effort', () => {
144 expect(DEFAULT_COMPLETION_MODEL.id).toBe(DEFAULT_ASSISTANT_BASE_MODEL_ID)
145 expect(DEFAULT_COMPLETION_MODEL.reasoningEffort).toBe('none')
146 })
147
148 it('openaiModelEntry enforces valid reasoning effort at compile time', () => {
149 // Valid: supported effort level
150 const withEffort = openaiModelEntry({
151 id: 'gpt-5.4-nano',
152 reasoningEffort: 'low',
153 })
154 expect(withEffort.reasoningEffort).toBe('low')
155
156 // Valid: no effort
157 const withoutEffort = openaiModelEntry({ id: 'gpt-5.4-nano' })
158 expect(withoutEffort.reasoningEffort).toBeUndefined()
159 })
160 })
161})