model.utils.test.ts 6.0 KB

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