model.utils.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. export type ProviderName = 'bedrock' | 'openai'
  2. export type BedrockModel = 'anthropic.claude-3-7-sonnet-20250219-v1:0' | 'openai.gpt-oss-120b-1:0'
  3. export type OpenAIModelId = 'gpt-5.4-nano' | 'gpt-5.3-codex'
  4. // Source: https://developers.openai.com/api/docs/guides/reasoning + per-model pages
  5. export type ReasoningEffort = 'none' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh'
  6. // Per-model reasoning effort compatibility.
  7. // Sources: https://developers.openai.com/api/docs/models/gpt-5.4-nano
  8. // https://developers.openai.com/api/docs/models/gpt-5.3-codex
  9. type ModelReasoningSupport = {
  10. 'gpt-5.4-nano': 'none' | 'low' | 'medium' | 'high' | 'xhigh'
  11. 'gpt-5.3-codex': 'low' | 'medium' | 'high' | 'xhigh'
  12. }
  13. type ReasoningEffortFor<ModelId extends OpenAIModelId> = ModelId extends keyof ModelReasoningSupport
  14. ? ModelReasoningSupport[ModelId]
  15. : never
  16. /** Type-safe factory for configuring OpenAI models with compatible reasoning efforts. */
  17. export function openaiModelEntry<
  18. ModelId extends OpenAIModelId,
  19. RequiresAdvance extends boolean = false,
  20. >(config: {
  21. id: ModelId
  22. /** When true, the model requires the `assistant.advance_model` entitlement (paid plans). Defaults to false. */
  23. requiresAdvanceModelEntitlement?: RequiresAdvance
  24. /**
  25. * When omitted, OpenAI applies its own default reasoning effort for the model,
  26. * which may not be zero. Use an explicit level to control cost and latency.
  27. */
  28. reasoningEffort?: ReasoningEffortFor<ModelId>
  29. }): {
  30. id: ModelId
  31. requiresAdvanceModelEntitlement: RequiresAdvance
  32. reasoningEffort?: ReasoningEffortFor<ModelId>
  33. } {
  34. return {
  35. requiresAdvanceModelEntitlement: false as RequiresAdvance,
  36. ...config,
  37. }
  38. }
  39. export type OpenAIModelEntry = ReturnType<typeof openaiModelEntry>
  40. /** Default model entry for simple completion endpoints where latency is more important than reasoning. */
  41. export const DEFAULT_COMPLETION_MODEL = openaiModelEntry({
  42. id: 'gpt-5.4-nano',
  43. reasoningEffort: 'none',
  44. })
  45. // Single source of truth for all Assistant chat model variants and their reasoning levels.
  46. // Models with requiresAdvanceModelEntitlement false are available to all users; true requires the assistant.advance_model entitlement.
  47. export const ASSISTANT_MODELS = [
  48. openaiModelEntry({
  49. id: 'gpt-5.4-nano',
  50. requiresAdvanceModelEntitlement: false,
  51. reasoningEffort: 'low',
  52. }),
  53. openaiModelEntry({
  54. id: 'gpt-5.3-codex',
  55. requiresAdvanceModelEntitlement: true,
  56. reasoningEffort: 'low',
  57. }),
  58. ] as const
  59. export type AssistantBaseModelId = Extract<
  60. (typeof ASSISTANT_MODELS)[number],
  61. { requiresAdvanceModelEntitlement: false }
  62. >['id']
  63. export type AssistantModelId = (typeof ASSISTANT_MODELS)[number]['id']
  64. const ASSISTANT_MODELS_MAP = Object.fromEntries(ASSISTANT_MODELS.map((m) => [m.id, m])) as Record<
  65. AssistantModelId,
  66. (typeof ASSISTANT_MODELS)[number]
  67. >
  68. export const DEFAULT_ASSISTANT_BASE_MODEL_ID = 'gpt-5.4-nano' satisfies AssistantBaseModelId
  69. export const DEFAULT_ASSISTANT_ADVANCE_MODEL_ID = 'gpt-5.3-codex' satisfies AssistantModelId
  70. export function defaultAssistantModelId(hasAccessToAdvanceModel: boolean): AssistantModelId {
  71. return hasAccessToAdvanceModel
  72. ? DEFAULT_ASSISTANT_ADVANCE_MODEL_ID
  73. : DEFAULT_ASSISTANT_BASE_MODEL_ID
  74. }
  75. export function isKnownAssistantModelId(id: string): id is AssistantModelId {
  76. return Object.hasOwn(ASSISTANT_MODELS_MAP, id)
  77. }
  78. export function isAssistantBaseModelId(id: string): id is AssistantBaseModelId {
  79. return (
  80. id in ASSISTANT_MODELS_MAP &&
  81. !ASSISTANT_MODELS_MAP[id as AssistantModelId].requiresAdvanceModelEntitlement
  82. )
  83. }
  84. export function isAdvanceOnlyModelId(id: string): boolean {
  85. return (
  86. id in ASSISTANT_MODELS_MAP &&
  87. ASSISTANT_MODELS_MAP[id as AssistantModelId].requiresAdvanceModelEntitlement
  88. )
  89. }
  90. export function getAssistantModelEntry(id: AssistantModelId): (typeof ASSISTANT_MODELS)[number] {
  91. return ASSISTANT_MODELS_MAP[id]
  92. }
  93. export type Model = BedrockModel | OpenAIModelId
  94. export type ProviderModelConfig = {
  95. /** Optional providerOptions to attach to the system message for this model */
  96. systemProviderOptions?: Record<string, any>
  97. /** The default model for this provider (used when limited or no preferred specified) */
  98. default: boolean
  99. }
  100. export type ProviderRegistry = {
  101. bedrock: {
  102. models: Record<BedrockModel, ProviderModelConfig>
  103. providerOptions?: Record<string, any>
  104. }
  105. openai: {
  106. models: Record<OpenAIModelId, ProviderModelConfig>
  107. providerOptions?: Record<string, any>
  108. }
  109. }
  110. export const PROVIDERS: ProviderRegistry = {
  111. bedrock: {
  112. models: {
  113. 'anthropic.claude-3-7-sonnet-20250219-v1:0': {
  114. systemProviderOptions: {
  115. bedrock: {
  116. // Always cache the system prompt (must not contain dynamic content)
  117. cachePoint: { type: 'default' },
  118. },
  119. },
  120. default: false,
  121. },
  122. 'openai.gpt-oss-120b-1:0': {
  123. default: true,
  124. },
  125. },
  126. },
  127. openai: {
  128. models: {
  129. 'gpt-5.3-codex': { default: false },
  130. 'gpt-5.4-nano': { default: true },
  131. },
  132. providerOptions: {
  133. openai: {
  134. store: false,
  135. },
  136. },
  137. },
  138. }
  139. export function getDefaultModelForProvider(provider: ProviderName): Model | undefined {
  140. const models = PROVIDERS[provider]?.models as Record<Model, ProviderModelConfig>
  141. if (!models) return undefined
  142. return Object.keys(models).find((id) => models[id as Model]?.default) as Model | undefined
  143. }