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