@bitkyc08/opencodex 2.7.22 → 2.7.24-preview.20260718
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +37 -7
- package/README.md +52 -11
- package/README.zh-CN.md +36 -7
- package/bin/ocx.mjs +5 -3
- package/gui/dist/assets/index-DDs2IUqv.js +40 -0
- package/gui/dist/assets/index-Dq3eZ1cU.css +1 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/opencode.svg +1 -1
- package/package.json +5 -2
- package/src/adapters/anthropic-image-normalize.ts +70 -29
- package/src/adapters/cursor/transport-retry.ts +20 -1
- package/src/adapters/run-turn-queue.ts +40 -0
- package/src/codex/auth-api.ts +10 -1
- package/src/codex/auth-context.ts +33 -7
- package/src/codex/catalog.ts +357 -23
- package/src/codex/routing.ts +10 -4
- package/src/combos/failover.ts +102 -0
- package/src/combos/index.ts +37 -0
- package/src/combos/request.ts +31 -0
- package/src/combos/resolve.ts +171 -0
- package/src/combos/types.ts +203 -0
- package/src/config.ts +280 -11
- package/src/lib/errors.ts +86 -24
- package/src/lib/upstream-retry.ts +8 -4
- package/src/oauth/index.ts +7 -1
- package/src/oauth/key-providers.ts +2 -32
- package/src/oauth/login-cli.ts +4 -3
- package/src/oauth/token-guardian.ts +38 -3
- package/src/providers/derive.ts +27 -2
- package/src/providers/kiro-models.ts +8 -3
- package/src/providers/label.ts +3 -1
- package/src/providers/openai-sidecar.ts +94 -0
- package/src/providers/openai-tier-startup.ts +27 -0
- package/src/providers/openai-tiers.ts +283 -0
- package/src/providers/openai-virtual-models.ts +82 -0
- package/src/providers/quota.ts +344 -24
- package/src/providers/registry.ts +148 -31
- package/src/reasoning-effort.ts +12 -11
- package/src/router.ts +80 -36
- package/src/server/auth-cors.ts +85 -9
- package/src/server/images.ts +31 -75
- package/src/server/index.ts +45 -86
- package/src/server/management-api.ts +273 -21
- package/src/server/request-log.ts +221 -20
- package/src/server/responses.ts +594 -75
- package/src/server/search.ts +22 -37
- package/src/types.ts +49 -1
- package/src/update/index.ts +50 -6
- package/src/update/job.ts +21 -4
- package/src/usage/log.ts +124 -1
- package/src/usage/summary.ts +147 -56
- package/src/vision/index.ts +20 -19
- package/src/web-search/index.ts +15 -17
- package/gui/dist/assets/index-Bk_GgFrh.css +0 -1
- package/gui/dist/assets/index-BqbMn1sv.js +0 -40
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { OcxProviderConfig } from "../types";
|
|
1
|
+
import type { CodexAccountMode, OcxProviderConfig } from "../types";
|
|
2
2
|
import { KIRO_MODELS, KIRO_MODEL_CONTEXT_WINDOWS, KIRO_MODEL_REASONING_EFFORTS } from "./kiro-models";
|
|
3
3
|
import { ANTIGRAVITY_MODELS, ANTIGRAVITY_MODEL_CONTEXT_WINDOWS } from "./antigravity-models";
|
|
4
4
|
import {
|
|
@@ -18,10 +18,16 @@ export interface ProviderRegistryEntry {
|
|
|
18
18
|
adapter: string;
|
|
19
19
|
baseUrl: string;
|
|
20
20
|
authKind: ProviderAuthKind;
|
|
21
|
+
codexAccountMode?: CodexAccountMode;
|
|
21
22
|
/** OAuth preset may explicitly honor a persisted API-key billing mode. */
|
|
22
23
|
allowKeyAuthOverride?: boolean;
|
|
23
24
|
allowPrivateNetworkByDefault?: boolean;
|
|
24
25
|
keyOptional?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Free-tier pricing (no paid subscription required). Distinct from `keyOptional`:
|
|
28
|
+
* free tiers may still require an API key (e.g. NVIDIA NIM free credits).
|
|
29
|
+
*/
|
|
30
|
+
freeTier?: boolean;
|
|
25
31
|
allowBaseUrlOverride?: boolean;
|
|
26
32
|
/** Static headers merged into every upstream request for this provider. */
|
|
27
33
|
staticHeaders?: Record<string, string>;
|
|
@@ -38,6 +44,7 @@ export interface ProviderRegistryEntry {
|
|
|
38
44
|
modelInputModalities?: Record<string, string[]>;
|
|
39
45
|
reasoningEfforts?: string[];
|
|
40
46
|
modelReasoningEfforts?: Record<string, string[]>;
|
|
47
|
+
modelDefaultReasoningEfforts?: Record<string, string>;
|
|
41
48
|
reasoningEffortMap?: Record<string, string>;
|
|
42
49
|
modelReasoningEffortMap?: Record<string, Record<string, string>>;
|
|
43
50
|
noVisionModels?: string[];
|
|
@@ -53,6 +60,8 @@ export interface ProviderRegistryEntry {
|
|
|
53
60
|
thinkingBudgetModels?: string[];
|
|
54
61
|
escapeBuiltinToolNames?: boolean;
|
|
55
62
|
oauthId?: string;
|
|
63
|
+
virtualModels?: Record<string, { wireModelId: string; reasoningMode: "pro" }>;
|
|
64
|
+
modelMaxInputTokens?: Record<string, number>;
|
|
56
65
|
jawcodeBundle?: string;
|
|
57
66
|
extraMetadataAliases?: string[];
|
|
58
67
|
metadataModelIdNormalize?: MetadataModelIdNormalize;
|
|
@@ -63,9 +72,10 @@ export interface ProviderRegistryEntry {
|
|
|
63
72
|
|
|
64
73
|
export type ProviderConfigSeed = Pick<
|
|
65
74
|
OcxProviderConfig,
|
|
66
|
-
"adapter" | "baseUrl" | "authMode" | "keyOptional" | "modelSuffixBracketStrip" | "defaultModel" | "models"
|
|
75
|
+
"adapter" | "baseUrl" | "authMode" | "keyOptional" | "freeTier" | "modelSuffixBracketStrip" | "defaultModel" | "models"
|
|
67
76
|
| "liveModels" | "contextWindow" | "modelContextWindows" | "modelInputModalities"
|
|
68
|
-
| "
|
|
77
|
+
| "modelMaxInputTokens"
|
|
78
|
+
| "reasoningEfforts" | "modelReasoningEfforts" | "modelDefaultReasoningEfforts" | "reasoningEffortMap" | "modelReasoningEffortMap"
|
|
69
79
|
| "noVisionModels" | "noReasoningModels" | "noTemperatureModels" | "noTopPModels" | "noPenaltyModels"
|
|
70
80
|
| "autoToolChoiceOnlyModels" | "preserveReasoningContentModels" | "thinkingToggleModels" | "thinkingBudgetModels" | "escapeBuiltinToolNames"
|
|
71
81
|
| "googleMode" | "project" | "location" | "headers"
|
|
@@ -92,13 +102,29 @@ const MINIMAX_MODELS = [
|
|
|
92
102
|
const MINIMAX_MODEL_CONTEXT_WINDOWS: Record<string, number> = Object.fromEntries(
|
|
93
103
|
MINIMAX_MODELS.map(id => [id, id === "MiniMax-M3" ? 1_000_000 : 204_800]),
|
|
94
104
|
);
|
|
95
|
-
const OPENAI_GPT56_MODELS = ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"];
|
|
96
|
-
const
|
|
105
|
+
const OPENAI_GPT56_MODELS = ["gpt-5.6", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"];
|
|
106
|
+
const OPENAI_GPT56_PRO_MODELS = ["gpt-5.6-sol-pro", "gpt-5.6-terra-pro", "gpt-5.6-luna-pro"];
|
|
107
|
+
const OPENAI_API_GPT56_CONTEXT_WINDOW = 1_050_000;
|
|
108
|
+
const OPENAI_CODEX_GPT56_CONTEXT_WINDOW = 372_000;
|
|
97
109
|
const OPENAI_GPT56_CONTEXT_WINDOWS = {
|
|
98
|
-
"gpt-5.6-sol":
|
|
99
|
-
"gpt-5.6-terra":
|
|
100
|
-
"gpt-5.6-luna":
|
|
110
|
+
"gpt-5.6-sol": OPENAI_CODEX_GPT56_CONTEXT_WINDOW,
|
|
111
|
+
"gpt-5.6-terra": OPENAI_CODEX_GPT56_CONTEXT_WINDOW,
|
|
112
|
+
"gpt-5.6-luna": OPENAI_CODEX_GPT56_CONTEXT_WINDOW,
|
|
101
113
|
};
|
|
114
|
+
const OPENAI_API_GPT56_CONTEXT_WINDOWS: Record<string, number> = {
|
|
115
|
+
...Object.fromEntries([...OPENAI_GPT56_MODELS, ...OPENAI_GPT56_PRO_MODELS].map(id => [id, OPENAI_API_GPT56_CONTEXT_WINDOW])),
|
|
116
|
+
"gpt-5.5": OPENAI_API_GPT56_CONTEXT_WINDOW,
|
|
117
|
+
};
|
|
118
|
+
const OPENAI_API_GPT56_MAX_INPUT_TOKENS: Record<string, number> = {
|
|
119
|
+
...Object.fromEntries([...OPENAI_GPT56_MODELS, ...OPENAI_GPT56_PRO_MODELS].map(id => [id, 922_000])),
|
|
120
|
+
"gpt-5.5": 922_000,
|
|
121
|
+
};
|
|
122
|
+
const OPENAI_API_GPT56_VIRTUAL_MODELS: Record<string, { wireModelId: string; reasoningMode: "pro" }> = {
|
|
123
|
+
"gpt-5.6-sol-pro": { wireModelId: "gpt-5.6-sol", reasoningMode: "pro" },
|
|
124
|
+
"gpt-5.6-terra-pro": { wireModelId: "gpt-5.6-terra", reasoningMode: "pro" },
|
|
125
|
+
"gpt-5.6-luna-pro": { wireModelId: "gpt-5.6-luna", reasoningMode: "pro" },
|
|
126
|
+
};
|
|
127
|
+
const OPENAI_API_GPT56_REASONING_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
|
|
102
128
|
const OPENROUTER_GPT56_MODELS = OPENAI_GPT56_MODELS.map(id => `openai/${id}`);
|
|
103
129
|
// OpenRouter's live /endpoints routes report 1,050,000; keep this separate from the
|
|
104
130
|
// unverified OpenAI API-key seed. Evidence: devlog/_plan/260710_provider_hardening/003_research_aggregators.md.
|
|
@@ -146,16 +172,48 @@ const DEEPSEEK_THINKING_REASONING_MAP: Record<string, string> = {
|
|
|
146
172
|
xhigh: "max",
|
|
147
173
|
max: "max",
|
|
148
174
|
};
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
|
|
152
|
-
|
|
175
|
+
// 260717 Kimi K3: the subscription endpoint uses one upstream id (`k3`) for both
|
|
176
|
+
// entitlement tiers. Bare `k3` advertises the Moderato 256K ceiling; the local `[1m]`
|
|
177
|
+
// alias advertises Allegretto's 1M ceiling and is stripped before the upstream request.
|
|
178
|
+
// The separately billed Moonshot API uses `kimi-k3`.
|
|
179
|
+
// Evidence: https://www.kimi.com/code/docs/en/kimi-code/models.html
|
|
180
|
+
// https://www.kimi.com/code/docs/en/kimi-code/error-reference.html
|
|
181
|
+
const KIMI_K3_STANDARD_CONTEXT_WINDOW = 262_144;
|
|
182
|
+
const KIMI_K3_1M_CONTEXT_WINDOW = 1_048_576;
|
|
183
|
+
const KIMI_CODING_K3_MODELS = ["k3", "k3[1m]"];
|
|
184
|
+
const KIMI_LEGACY_API_MODELS = ["kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2.5"];
|
|
185
|
+
const KIMI_API_MODELS = ["kimi-k3", ...KIMI_LEGACY_API_MODELS];
|
|
186
|
+
const KIMI_CODING_MODELS = [...KIMI_CODING_K3_MODELS, ...KIMI_LEGACY_API_MODELS, "kimi-for-coding"];
|
|
153
187
|
const KIMI_THINKING_MODELS = KIMI_CODING_MODELS;
|
|
188
|
+
const KIMI_CODING_NO_REASONING_MODELS = KIMI_CODING_MODELS.filter(id => !KIMI_CODING_K3_MODELS.includes(id));
|
|
189
|
+
const KIMI_API_NO_REASONING_MODELS = KIMI_API_MODELS.filter(id => id !== "kimi-k3");
|
|
190
|
+
const KIMI_CODING_K3_REASONING_EFFORTS = ["low", "high", "max"];
|
|
191
|
+
const KIMI_CODING_K3_REASONING_EFFORT_MAP: Record<string, string> = {
|
|
192
|
+
none: "none",
|
|
193
|
+
low: "low",
|
|
194
|
+
medium: "high",
|
|
195
|
+
high: "high",
|
|
196
|
+
xhigh: "max",
|
|
197
|
+
max: "max",
|
|
198
|
+
};
|
|
199
|
+
const KIMI_CODING_REASONING_EFFORTS = Object.fromEntries(
|
|
200
|
+
KIMI_CODING_MODELS.map(id => [id, KIMI_CODING_K3_MODELS.includes(id) ? KIMI_CODING_K3_REASONING_EFFORTS : []]),
|
|
201
|
+
);
|
|
202
|
+
const KIMI_CODING_DEFAULT_REASONING_EFFORTS = Object.fromEntries(
|
|
203
|
+
KIMI_CODING_K3_MODELS.map(id => [id, "max"]),
|
|
204
|
+
);
|
|
205
|
+
const KIMI_CODING_REASONING_EFFORT_MAPS = Object.fromEntries(
|
|
206
|
+
KIMI_CODING_K3_MODELS.map(id => [id, KIMI_CODING_K3_REASONING_EFFORT_MAP]),
|
|
207
|
+
);
|
|
208
|
+
const KIMI_API_REASONING_EFFORTS = Object.fromEntries(
|
|
209
|
+
KIMI_API_MODELS.map(id => [id, id === "kimi-k3" ? ["max"] : []]),
|
|
210
|
+
);
|
|
154
211
|
const KIMI_LOCKED_PARAMETER_MODELS = KIMI_CODING_MODELS;
|
|
155
212
|
const KIMI_AUTO_TOOL_CHOICE_ONLY_MODELS = ["kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-for-coding"];
|
|
156
213
|
const KIMI_API_MODEL_CONTEXT_WINDOWS: Record<string, number> = Object.fromEntries(
|
|
157
|
-
KIMI_API_MODELS.map(id => [id, 262_144]),
|
|
214
|
+
KIMI_API_MODELS.map(id => [id, id === "kimi-k3" ? KIMI_K3_1M_CONTEXT_WINDOW : 262_144]),
|
|
158
215
|
);
|
|
216
|
+
const KIMI_API_MODEL_INPUT_MODALITIES = { "kimi-k3": ["text", "image"] };
|
|
159
217
|
|
|
160
218
|
// 260715 NVIDIA NIM kimi family (issue #126): documented served ids on integrate
|
|
161
219
|
// chat/completions per docs.api.nvidia.com/nim/reference/llm-apis; live /v1/models
|
|
@@ -168,7 +226,10 @@ const NVIDIA_NIM_KIMI_MODELS = [
|
|
|
168
226
|
"moonshotai/kimi-k2-instruct", "moonshotai/kimi-k2-instruct-0905",
|
|
169
227
|
];
|
|
170
228
|
const KIMI_CODING_MODEL_CONTEXT_WINDOWS: Record<string, number> = Object.fromEntries(
|
|
171
|
-
KIMI_CODING_MODELS.map(id => [id,
|
|
229
|
+
KIMI_CODING_MODELS.map(id => [id, id === "k3[1m]" ? KIMI_K3_1M_CONTEXT_WINDOW : KIMI_K3_STANDARD_CONTEXT_WINDOW]),
|
|
230
|
+
);
|
|
231
|
+
const KIMI_CODING_MODEL_INPUT_MODALITIES = Object.fromEntries(
|
|
232
|
+
KIMI_CODING_K3_MODELS.map(id => [id, ["text", "image"]]),
|
|
172
233
|
);
|
|
173
234
|
const NEURALWATT_REASONING_HISTORY_MODELS = [
|
|
174
235
|
"glm-5.2", "glm-5.2-short",
|
|
@@ -201,12 +262,13 @@ const UMANS_MODEL_INPUT_MODALITIES: Record<string, string[]> = Object.fromEntrie
|
|
|
201
262
|
export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
202
263
|
{
|
|
203
264
|
id: "openai",
|
|
204
|
-
label: "OpenAI (
|
|
265
|
+
label: "OpenAI (Codex login)",
|
|
205
266
|
adapter: "openai-responses",
|
|
206
267
|
baseUrl: "https://chatgpt.com/backend-api/codex",
|
|
207
268
|
authKind: "forward",
|
|
269
|
+
codexAccountMode: "pool",
|
|
208
270
|
featured: true,
|
|
209
|
-
note: "
|
|
271
|
+
note: "Codex login account pool (default) or Direct main-account mode via codexAccountMode",
|
|
210
272
|
},
|
|
211
273
|
{
|
|
212
274
|
id: "cursor",
|
|
@@ -250,20 +312,21 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
250
312
|
// 260709 refresh: lineup + metadata from official docs.x.ai (grok-4.5 announced 07-08);
|
|
251
313
|
// grok-composer-2.5-fast kept as account-verified (absent from public docs). Evidence:
|
|
252
314
|
// devlog/model_update/260709_model_refresh/001_xai_lineup.md.
|
|
253
|
-
|
|
315
|
+
// grok-4.20-multi-agent-0309 is intentionally absent: the OAuth chat-completions
|
|
316
|
+
// transport returns 400 ("Multi Agent requests are not allowed on chat completions").
|
|
317
|
+
models: ["grok-4.5", "grok-4.3", "grok-4.20-0309-reasoning", "grok-4.20-0309-non-reasoning", "grok-build-0.1", "grok-composer-2.5-fast"],
|
|
254
318
|
defaultModel: "grok-4.5",
|
|
255
319
|
noReasoningModels: ["grok-4.20-0309-non-reasoning", "grok-build-0.1", "grok-composer-2.5-fast"],
|
|
256
320
|
// Replay assistant reasoning_content for grok reasoning models: xAI documents dropped
|
|
257
321
|
// reasoning_content as the top cause of prompt-cache misses on multi-turn conversations
|
|
258
322
|
// (docs.x.ai prompt-caching/multi-turn, verified 2026-07-13 — devlog/_plan/260713_grok_caching).
|
|
259
323
|
// Models that never emit reasoning simply have no thinking parts to replay (no-op).
|
|
260
|
-
preserveReasoningContentModels: ["grok-4.5", "grok-4.3", "grok-4.20-
|
|
324
|
+
preserveReasoningContentModels: ["grok-4.5", "grok-4.3", "grok-4.20-0309-reasoning"],
|
|
261
325
|
// grok-4.5 reasoning is always-on with low/medium/high control (no off tier upstream).
|
|
262
326
|
modelReasoningEfforts: { "grok-4.5": ["low", "medium", "high"] },
|
|
263
327
|
modelContextWindows: {
|
|
264
328
|
"grok-4.5": 500_000,
|
|
265
329
|
"grok-4.3": 1_000_000,
|
|
266
|
-
"grok-4.20-multi-agent-0309": 1_000_000,
|
|
267
330
|
"grok-4.20-0309-reasoning": 1_000_000,
|
|
268
331
|
"grok-4.20-0309-non-reasoning": 1_000_000,
|
|
269
332
|
"grok-build-0.1": 256_000,
|
|
@@ -306,6 +369,7 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
306
369
|
adapter: "openai-chat",
|
|
307
370
|
baseUrl: "https://api.kimi.com/coding/v1",
|
|
308
371
|
authKind: "oauth",
|
|
372
|
+
modelSuffixBracketStrip: true,
|
|
309
373
|
featured: true,
|
|
310
374
|
oauthId: "kimi",
|
|
311
375
|
jawcodeBundle: "moonshot",
|
|
@@ -313,9 +377,12 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
313
377
|
models: KIMI_CODING_MODELS,
|
|
314
378
|
defaultModel: "kimi-k2.7-code",
|
|
315
379
|
modelContextWindows: KIMI_CODING_MODEL_CONTEXT_WINDOWS,
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
380
|
+
modelInputModalities: KIMI_CODING_MODEL_INPUT_MODALITIES,
|
|
381
|
+
// K3 accepts low/high/max; Codex aliases are normalized by the model-scoped wire map.
|
|
382
|
+
noReasoningModels: KIMI_CODING_NO_REASONING_MODELS,
|
|
383
|
+
modelReasoningEfforts: KIMI_CODING_REASONING_EFFORTS,
|
|
384
|
+
modelDefaultReasoningEfforts: KIMI_CODING_DEFAULT_REASONING_EFFORTS,
|
|
385
|
+
modelReasoningEffortMap: KIMI_CODING_REASONING_EFFORT_MAPS,
|
|
319
386
|
noTemperatureModels: KIMI_LOCKED_PARAMETER_MODELS,
|
|
320
387
|
noTopPModels: KIMI_LOCKED_PARAMETER_MODELS,
|
|
321
388
|
noPenaltyModels: KIMI_LOCKED_PARAMETER_MODELS,
|
|
@@ -332,11 +399,35 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
332
399
|
note: "Import-first: reuses your installed kiro-cli login (no browser). Experimental third-party harness — see Kiro ToS.",
|
|
333
400
|
models: KIRO_MODELS,
|
|
334
401
|
defaultModel: "kiro-auto",
|
|
402
|
+
// Kiro speaks CodeWhisperer wire, not OpenAI-style GET /models. Keep the static
|
|
403
|
+
// catalog authoritative so a spurious 2xx from runtime.../models cannot drop seeded ids
|
|
404
|
+
// (e.g. newly listed GPT-5.6 tiers) via live-discovery reconciliation.
|
|
405
|
+
liveModels: false,
|
|
335
406
|
// Per-model context metadata is maintained next to the Kiro model list.
|
|
336
407
|
modelContextWindows: KIRO_MODEL_CONTEXT_WINDOWS,
|
|
337
408
|
modelReasoningEfforts: KIRO_MODEL_REASONING_EFFORTS,
|
|
338
409
|
},
|
|
339
|
-
{
|
|
410
|
+
{
|
|
411
|
+
id: "openai-apikey",
|
|
412
|
+
label: "OpenAI API",
|
|
413
|
+
adapter: "openai-responses",
|
|
414
|
+
baseUrl: "https://api.openai.com/v1",
|
|
415
|
+
authKind: "key",
|
|
416
|
+
featured: true,
|
|
417
|
+
dashboardUrl: "https://platform.openai.com/api-keys",
|
|
418
|
+
defaultModel: "gpt-5.5",
|
|
419
|
+
models: ["gpt-5.5", ...OPENAI_GPT56_MODELS, ...OPENAI_GPT56_PRO_MODELS],
|
|
420
|
+
liveModels: true,
|
|
421
|
+
modelContextWindows: OPENAI_API_GPT56_CONTEXT_WINDOWS,
|
|
422
|
+
modelMaxInputTokens: OPENAI_API_GPT56_MAX_INPUT_TOKENS,
|
|
423
|
+
modelInputModalities: Object.fromEntries(
|
|
424
|
+
["gpt-5.5", ...OPENAI_GPT56_MODELS, ...OPENAI_GPT56_PRO_MODELS].map(id => [id, ["text", "image"]]),
|
|
425
|
+
),
|
|
426
|
+
modelReasoningEfforts: Object.fromEntries(
|
|
427
|
+
[...OPENAI_GPT56_MODELS, ...OPENAI_GPT56_PRO_MODELS].map(id => [id, OPENAI_API_GPT56_REASONING_EFFORTS]),
|
|
428
|
+
),
|
|
429
|
+
virtualModels: OPENAI_API_GPT56_VIRTUAL_MODELS,
|
|
430
|
+
},
|
|
340
431
|
{
|
|
341
432
|
id: "umans",
|
|
342
433
|
label: "Umans AI Coding Plan",
|
|
@@ -365,17 +456,22 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
365
456
|
id: "opencode-go", label: "opencode go", adapter: "openai-chat", baseUrl: "https://opencode.ai/zen/go/v1",
|
|
366
457
|
authKind: "key", featured: true, dashboardUrl: "https://opencode.ai/auth", defaultModel: "kimi-k2.7-code",
|
|
367
458
|
jawcodeBundle: "opencode-go", note: "GLM, DeepSeek, Kimi, Qwen, MiMo…",
|
|
459
|
+
modelContextWindows: { "kimi-k3": KIMI_K3_STANDARD_CONTEXT_WINDOW },
|
|
460
|
+
modelInputModalities: { "kimi-k3": ["text", "image"] },
|
|
368
461
|
modelReasoningEfforts: {
|
|
369
462
|
"glm-5.2": ZAI_GLM_52_REASONING_EFFORTS,
|
|
463
|
+
"kimi-k3": KIMI_CODING_K3_REASONING_EFFORTS,
|
|
370
464
|
"kimi-k2.7-code": [],
|
|
371
465
|
"kimi-k2.7-code-highspeed": [],
|
|
372
466
|
...Object.fromEntries(OPENCODE_GO_THINKING_TOGGLE_MODELS.map(id => [id, THINKING_TOGGLE_EFFORTS])),
|
|
373
467
|
...Object.fromEntries(OPENCODE_GO_THINKING_BUDGET_MODELS.map(id => [id, THINKING_BUDGET_EFFORTS])),
|
|
374
468
|
...Object.fromEntries(DEEPSEEK_THINKING_MODELS.map(id => [id, DEEPSEEK_THINKING_EFFORTS])),
|
|
375
469
|
},
|
|
470
|
+
modelDefaultReasoningEfforts: { "kimi-k3": "max" },
|
|
376
471
|
// glm-5.2 uses identity labels now that `max` is a native Codex level (no alias map);
|
|
377
472
|
// the thinking-toggle map is a REAL wire alias (effort -> enabled/disabled) and stays.
|
|
378
473
|
modelReasoningEffortMap: {
|
|
474
|
+
"kimi-k3": KIMI_CODING_K3_REASONING_EFFORT_MAP,
|
|
379
475
|
...Object.fromEntries(OPENCODE_GO_THINKING_TOGGLE_MODELS.map(id => [id, THINKING_TOGGLE_MAP])),
|
|
380
476
|
...Object.fromEntries(DEEPSEEK_THINKING_MODELS.map(id => [id, DEEPSEEK_THINKING_REASONING_MAP])),
|
|
381
477
|
},
|
|
@@ -392,12 +488,12 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
392
488
|
"minimax-m2.5", "minimax-m2.7",
|
|
393
489
|
"qwen3.7-max",
|
|
394
490
|
],
|
|
395
|
-
noTemperatureModels: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
|
|
396
|
-
noTopPModels: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
|
|
397
|
-
noPenaltyModels: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
|
|
491
|
+
noTemperatureModels: ["kimi-k3", "kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
|
|
492
|
+
noTopPModels: ["kimi-k3", "kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
|
|
493
|
+
noPenaltyModels: ["kimi-k3", "kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
|
|
398
494
|
autoToolChoiceOnlyModels: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed"],
|
|
399
495
|
// Issue #78: DeepSeek V4 thinking mode requires reasoning_content replay on tool-call turns.
|
|
400
|
-
preserveReasoningContentModels: ["glm-5.2", "kimi-k2.7-code", "kimi-k2.7-code-highspeed", ...DEEPSEEK_THINKING_MODELS],
|
|
496
|
+
preserveReasoningContentModels: ["glm-5.2", "kimi-k3", "kimi-k2.7-code", "kimi-k2.7-code-highspeed", ...DEEPSEEK_THINKING_MODELS],
|
|
401
497
|
},
|
|
402
498
|
{
|
|
403
499
|
id: "neuralwatt",
|
|
@@ -502,8 +598,9 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
502
598
|
dashboardUrl: "https://platform.moonshot.ai/console/api-keys", defaultModel: "kimi-k2.7-code", jawcodeBundle: "moonshot",
|
|
503
599
|
models: KIMI_API_MODELS,
|
|
504
600
|
modelContextWindows: KIMI_API_MODEL_CONTEXT_WINDOWS,
|
|
505
|
-
|
|
506
|
-
|
|
601
|
+
modelInputModalities: KIMI_API_MODEL_INPUT_MODALITIES,
|
|
602
|
+
noReasoningModels: KIMI_API_NO_REASONING_MODELS,
|
|
603
|
+
modelReasoningEfforts: KIMI_API_REASONING_EFFORTS,
|
|
507
604
|
noTemperatureModels: KIMI_API_MODELS,
|
|
508
605
|
noTopPModels: KIMI_API_MODELS,
|
|
509
606
|
noPenaltyModels: KIMI_API_MODELS,
|
|
@@ -521,10 +618,13 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
521
618
|
// its working reasoning_effort. Future kimi ids must be appended individually.
|
|
522
619
|
{
|
|
523
620
|
id: "nvidia", label: "NVIDIA NIM", baseUrl: "https://integrate.api.nvidia.com/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://build.nvidia.com",
|
|
621
|
+
// Free pricing, but an API key is still required (free key from build.nvidia.com).
|
|
622
|
+
freeTier: true,
|
|
524
623
|
parallelToolCalls: false,
|
|
525
624
|
noReasoningModels: NVIDIA_NIM_KIMI_MODELS,
|
|
526
625
|
modelReasoningEfforts: Object.fromEntries(NVIDIA_NIM_KIMI_MODELS.map(id => [id, []])),
|
|
527
626
|
preserveReasoningContentModels: NVIDIA_NIM_KIMI_THINKING_MODELS,
|
|
627
|
+
note: "Free tier on NVIDIA NIM — API key still required (get a free key at build.nvidia.com).",
|
|
528
628
|
},
|
|
529
629
|
{ id: "venice", label: "Venice", baseUrl: "https://api.venice.ai/api/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://venice.ai/settings/api" },
|
|
530
630
|
// 260710 GLM-5.2 context and path-specific ids: Tier-2 evidence in
|
|
@@ -599,10 +699,14 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
|
|
|
599
699
|
{
|
|
600
700
|
id: "kimi-code", label: "Kimi (coding)", baseUrl: "https://api.kimi.com/coding/v1", adapter: "openai-chat", authKind: "key",
|
|
601
701
|
dashboardUrl: "https://platform.moonshot.cn/console/api-keys", defaultModel: "kimi-k2.7-code",
|
|
702
|
+
modelSuffixBracketStrip: true,
|
|
602
703
|
models: KIMI_CODING_MODELS,
|
|
603
704
|
modelContextWindows: KIMI_CODING_MODEL_CONTEXT_WINDOWS,
|
|
604
|
-
|
|
605
|
-
|
|
705
|
+
modelInputModalities: KIMI_CODING_MODEL_INPUT_MODALITIES,
|
|
706
|
+
noReasoningModels: KIMI_CODING_NO_REASONING_MODELS,
|
|
707
|
+
modelReasoningEfforts: KIMI_CODING_REASONING_EFFORTS,
|
|
708
|
+
modelDefaultReasoningEfforts: KIMI_CODING_DEFAULT_REASONING_EFFORTS,
|
|
709
|
+
modelReasoningEffortMap: KIMI_CODING_REASONING_EFFORT_MAPS,
|
|
606
710
|
noTemperatureModels: KIMI_LOCKED_PARAMETER_MODELS,
|
|
607
711
|
noTopPModels: KIMI_LOCKED_PARAMETER_MODELS,
|
|
608
712
|
noPenaltyModels: KIMI_LOCKED_PARAMETER_MODELS,
|
|
@@ -657,6 +761,19 @@ export function getProviderRegistryEntry(id: string): ProviderRegistryEntry | un
|
|
|
657
761
|
return PROVIDER_REGISTRY.find(entry => entry.id === id);
|
|
658
762
|
}
|
|
659
763
|
|
|
764
|
+
/**
|
|
765
|
+
* Effective Codex account mode for a provider. For canonical `openai`, a valid persisted
|
|
766
|
+
* `codexAccountMode` on the provider config wins and a missing/invalid value defaults to
|
|
767
|
+
* `"pool"`. Other providers keep registry-only metadata (there is no mode for `openai-apikey`).
|
|
768
|
+
*/
|
|
769
|
+
export function providerCodexAccountMode(id: string, provider?: OcxProviderConfig): CodexAccountMode | undefined {
|
|
770
|
+
const registryMode = getProviderRegistryEntry(id)?.codexAccountMode;
|
|
771
|
+
if (id !== "openai") return registryMode;
|
|
772
|
+
const persisted = provider?.codexAccountMode;
|
|
773
|
+
if (persisted === "pool" || persisted === "direct") return persisted;
|
|
774
|
+
return registryMode ?? "pool";
|
|
775
|
+
}
|
|
776
|
+
|
|
660
777
|
/**
|
|
661
778
|
* Effective Google wire mode for a provider: config value, else registry backfill (a saved
|
|
662
779
|
* key-login config may omit `googleMode` — mirrors the router's backfill), else "ai-studio"
|
package/src/reasoning-effort.ts
CHANGED
|
@@ -58,23 +58,24 @@ export function sanitizeCodexReasoningEfforts(efforts: readonly string[] | undef
|
|
|
58
58
|
export function configuredReasoningEfforts(provider: OcxProviderConfig, modelId: string): string[] | undefined {
|
|
59
59
|
if (modelInList(provider.noReasoningModels, modelId)) return [];
|
|
60
60
|
const modelEfforts = modelRecordValue(provider.modelReasoningEfforts, modelId);
|
|
61
|
-
if (modelEfforts !== undefined) return
|
|
62
|
-
if (provider.reasoningEfforts !== undefined) return
|
|
61
|
+
if (modelEfforts !== undefined) return healMappedTiers(provider, modelId, sanitizeCodexReasoningEfforts(modelEfforts) ?? []);
|
|
62
|
+
if (provider.reasoningEfforts !== undefined) return healMappedTiers(provider, modelId, sanitizeCodexReasoningEfforts(provider.reasoningEfforts) ?? []);
|
|
63
63
|
return undefined;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
|
-
* Stale-ladder self-heal:
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* maps (xhigh -> "enabled") never match, so binary-toggle models stay two-step.
|
|
67
|
+
* Stale-ladder self-heal: a registry wire map is authoritative evidence of the upstream tiers
|
|
68
|
+
* it can emit. Merge Codex-native map values into an older persisted ladder so newly documented
|
|
69
|
+
* tiers appear without rewriting the user's config. Non-Codex values such as enabled/disabled
|
|
70
|
+
* and Kimi's none sentinel are ignored here; they remain request-only wire aliases.
|
|
72
71
|
*/
|
|
73
|
-
function
|
|
74
|
-
if (efforts.
|
|
72
|
+
function healMappedTiers(provider: OcxProviderConfig, modelId: string, efforts: string[]): string[] {
|
|
73
|
+
if (efforts.length === 0) return efforts;
|
|
75
74
|
const wireMap = reasoningEffortMapFor(provider, modelId);
|
|
76
|
-
if (wireMap
|
|
77
|
-
|
|
75
|
+
if (!wireMap) return efforts;
|
|
76
|
+
const mappedTiers = Object.values(wireMap).filter(isCodexReasoningEffort);
|
|
77
|
+
if (mappedTiers.length === 0) return efforts;
|
|
78
|
+
return sanitizeCodexReasoningEfforts([...efforts, ...mappedTiers]) ?? efforts;
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
function requestToCodexEffort(requested: string): string | undefined {
|
package/src/router.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
-
import type { OcxConfig, OcxProviderConfig } from "./types";
|
|
1
|
+
import type { CodexAccountMode, OcxConfig, OcxProviderConfig } from "./types";
|
|
2
|
+
import { COMBO_NAMESPACE, tryPickComboModel, type ComboPick } from "./combos";
|
|
2
3
|
import { hasOwnProvider, resolveEnvValue } from "./config";
|
|
3
4
|
import { assertProviderDestinationAllowed } from "./lib/destination-policy";
|
|
4
|
-
import { PROVIDER_REGISTRY } from "./providers/registry";
|
|
5
|
+
import { PROVIDER_REGISTRY, providerCodexAccountMode } from "./providers/registry";
|
|
6
|
+
import { LEGACY_CHATGPT_PROVIDER_ID, LEGACY_OPENAI_MULTI_PROVIDER_ID, OPENAI_API_PROVIDER_ID, OPENAI_CODEX_PROVIDER_ID } from "./providers/openai-tiers";
|
|
5
7
|
|
|
6
|
-
interface RouteResult {
|
|
8
|
+
export interface RouteResult {
|
|
7
9
|
providerName: string;
|
|
8
10
|
provider: OcxProviderConfig;
|
|
9
11
|
modelId: string;
|
|
12
|
+
codexAccountMode?: CodexAccountMode;
|
|
13
|
+
combo?: ComboPick;
|
|
10
14
|
}
|
|
11
15
|
|
|
12
16
|
const MODEL_PROVIDER_PATTERNS: Array<{ providerNames: string[]; prefixes: string[] }> = [
|
|
@@ -16,12 +20,6 @@ const MODEL_PROVIDER_PATTERNS: Array<{ providerNames: string[]; prefixes: string
|
|
|
16
20
|
"claude-", "claude-sonnet-", "claude-opus-", "claude-haiku-",
|
|
17
21
|
],
|
|
18
22
|
},
|
|
19
|
-
{
|
|
20
|
-
providerNames: ["openai", "chatgpt", "openai-apikey"],
|
|
21
|
-
prefixes: [
|
|
22
|
-
"gpt-", "o1-", "o3-", "o4-",
|
|
23
|
-
],
|
|
24
|
-
},
|
|
25
23
|
{
|
|
26
24
|
providerNames: ["groq"],
|
|
27
25
|
prefixes: [
|
|
@@ -67,6 +65,18 @@ function mergeRecordFill<T>(
|
|
|
67
65
|
return { ...(seed ?? {}), ...(user ?? {}) };
|
|
68
66
|
}
|
|
69
67
|
|
|
68
|
+
function mergePositiveNumberCaps(
|
|
69
|
+
seed: Record<string, number> | undefined,
|
|
70
|
+
user: Record<string, number> | undefined,
|
|
71
|
+
): Record<string, number> | undefined {
|
|
72
|
+
if (!seed && !user) return undefined;
|
|
73
|
+
const out = { ...(seed ?? {}) };
|
|
74
|
+
for (const [key, value] of Object.entries(user ?? {})) {
|
|
75
|
+
out[key] = typeof out[key] === "number" ? Math.min(out[key]!, value) : value;
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
|
|
70
80
|
function mergeStringArrayRecord(
|
|
71
81
|
seed: Record<string, string[]> | undefined,
|
|
72
82
|
user: Record<string, string[]> | undefined,
|
|
@@ -95,8 +105,14 @@ function routedProviderConfig(providerName: string, provider: OcxProviderConfig)
|
|
|
95
105
|
const reasoningEffortMap = mergeRecord(registryEntry.reasoningEffortMap, provider.reasoningEffortMap);
|
|
96
106
|
const modelReasoningEffortMap = mergeNestedRecord(registryEntry.modelReasoningEffortMap, provider.modelReasoningEffortMap);
|
|
97
107
|
const modelReasoningEfforts = mergeStringArrayRecord(registryEntry.modelReasoningEfforts, provider.modelReasoningEfforts);
|
|
98
|
-
const
|
|
108
|
+
const modelDefaultReasoningEfforts = mergeRecordFill(registryEntry.modelDefaultReasoningEfforts, provider.modelDefaultReasoningEfforts);
|
|
109
|
+
const modelContextWindows = providerName === OPENAI_API_PROVIDER_ID
|
|
110
|
+
? mergePositiveNumberCaps(registryEntry.modelContextWindows, provider.modelContextWindows)
|
|
111
|
+
: mergeRecordFill(registryEntry.modelContextWindows, provider.modelContextWindows);
|
|
99
112
|
const modelInputModalities = mergeRecordFill(registryEntry.modelInputModalities, provider.modelInputModalities);
|
|
113
|
+
const modelMaxInputTokens = providerName === OPENAI_API_PROVIDER_ID
|
|
114
|
+
? mergePositiveNumberCaps(registryEntry.modelMaxInputTokens, provider.modelMaxInputTokens)
|
|
115
|
+
: mergeRecordFill(registryEntry.modelMaxInputTokens, provider.modelMaxInputTokens);
|
|
100
116
|
const noVisionModels = mergeStringArray(registryEntry.noVisionModels, provider.noVisionModels);
|
|
101
117
|
const noReasoningModels = mergeStringArray(registryEntry.noReasoningModels, provider.noReasoningModels);
|
|
102
118
|
const noTemperatureModels = mergeStringArray(registryEntry.noTemperatureModels, provider.noTemperatureModels);
|
|
@@ -140,7 +156,9 @@ function routedProviderConfig(providerName: string, provider: OcxProviderConfig)
|
|
|
140
156
|
...(provider.parallelToolCalls === undefined && registryEntry.parallelToolCalls !== undefined ? { parallelToolCalls: registryEntry.parallelToolCalls } : {}),
|
|
141
157
|
...(modelContextWindows ? { modelContextWindows } : {}),
|
|
142
158
|
...(modelInputModalities ? { modelInputModalities } : {}),
|
|
159
|
+
...(modelMaxInputTokens ? { modelMaxInputTokens } : {}),
|
|
143
160
|
...(modelReasoningEfforts ? { modelReasoningEfforts } : {}),
|
|
161
|
+
...(modelDefaultReasoningEfforts ? { modelDefaultReasoningEfforts } : {}),
|
|
144
162
|
...(reasoningEffortMap ? { reasoningEffortMap } : {}),
|
|
145
163
|
...(modelReasoningEffortMap ? { modelReasoningEffortMap } : {}),
|
|
146
164
|
...(noVisionModels ? { noVisionModels } : {}),
|
|
@@ -156,10 +174,44 @@ function routedProviderConfig(providerName: string, provider: OcxProviderConfig)
|
|
|
156
174
|
}
|
|
157
175
|
|
|
158
176
|
function activeProviderEntries(config: OcxConfig): [string, OcxProviderConfig][] {
|
|
159
|
-
return Object.entries(config.providers)
|
|
177
|
+
return Object.entries(config.providers)
|
|
178
|
+
.filter(([name, provider]) => name !== LEGACY_CHATGPT_PROVIDER_ID && provider.disabled !== true);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export class NoEnabledOpenAiProviderError extends Error {
|
|
182
|
+
constructor(modelId: string) {
|
|
183
|
+
super(`No enabled canonical OpenAI provider for model: ${modelId}`);
|
|
184
|
+
this.name = "NoEnabledOpenAiProviderError";
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function isBareOpenAiFamilyModel(modelId: string): boolean {
|
|
189
|
+
return !modelId.includes("/") && /^(?:gpt-|o1-|o3-|o4-)/.test(modelId);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function routeResult(providerName: string, provider: OcxProviderConfig, modelId: string): RouteResult {
|
|
193
|
+
const codexAccountMode = providerCodexAccountMode(providerName, provider);
|
|
194
|
+
return {
|
|
195
|
+
providerName,
|
|
196
|
+
provider: routedProviderConfig(providerName, provider),
|
|
197
|
+
modelId,
|
|
198
|
+
...(codexAccountMode ? { codexAccountMode } : {}),
|
|
199
|
+
};
|
|
160
200
|
}
|
|
161
201
|
|
|
162
202
|
export function routeModel(config: OcxConfig, modelId: string): RouteResult {
|
|
203
|
+
const preservePhysicalComboProvider =
|
|
204
|
+
hasOwnProvider(config.providers, COMBO_NAMESPACE)
|
|
205
|
+
&& Object.keys(config.combos ?? {}).length === 0;
|
|
206
|
+
if (!preservePhysicalComboProvider) {
|
|
207
|
+
const combo = tryPickComboModel(config, modelId);
|
|
208
|
+
if (combo) {
|
|
209
|
+
const concrete = `${combo.target.provider}/${combo.target.model}`;
|
|
210
|
+
const routed = routeModel(config, concrete);
|
|
211
|
+
return { ...routed, combo };
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
163
215
|
// 0. Explicit "<provider>/<model>" namespace (e.g. "opencode-go/deepseek-v4-pro").
|
|
164
216
|
// Only triggers when the prefix matches a CONFIGURED provider, so genuine
|
|
165
217
|
// slash-containing model ids (e.g. "anthropic/claude-...") fall through when
|
|
@@ -167,24 +219,25 @@ export function routeModel(config: OcxConfig, modelId: string): RouteResult {
|
|
|
167
219
|
const slash = modelId.indexOf("/");
|
|
168
220
|
if (slash > 0) {
|
|
169
221
|
const provName = modelId.slice(0, slash);
|
|
222
|
+
if (provName === LEGACY_CHATGPT_PROVIDER_ID || provName === LEGACY_OPENAI_MULTI_PROVIDER_ID) {
|
|
223
|
+
throw new Error(`No provider configured for model: ${modelId}`);
|
|
224
|
+
}
|
|
170
225
|
if (hasOwnProvider(config.providers, provName)) {
|
|
171
226
|
const prov = config.providers[provName];
|
|
172
227
|
if (prov.disabled === true) throw new Error(`Provider is disabled: ${provName}`);
|
|
173
|
-
return
|
|
174
|
-
providerName: provName,
|
|
175
|
-
provider: routedProviderConfig(provName, prov),
|
|
176
|
-
modelId: modelId.slice(slash + 1),
|
|
177
|
-
};
|
|
228
|
+
return routeResult(provName, prov, modelId.slice(slash + 1));
|
|
178
229
|
}
|
|
179
230
|
}
|
|
180
231
|
|
|
232
|
+
if (isBareOpenAiFamilyModel(modelId)) {
|
|
233
|
+
const provider = config.providers[OPENAI_CODEX_PROVIDER_ID];
|
|
234
|
+
if (provider && provider.disabled !== true) return routeResult(OPENAI_CODEX_PROVIDER_ID, provider, modelId);
|
|
235
|
+
throw new NoEnabledOpenAiProviderError(modelId);
|
|
236
|
+
}
|
|
237
|
+
|
|
181
238
|
for (const [provName, prov] of activeProviderEntries(config)) {
|
|
182
239
|
if (prov.defaultModel === modelId) {
|
|
183
|
-
return
|
|
184
|
-
providerName: provName,
|
|
185
|
-
provider: routedProviderConfig(provName, prov),
|
|
186
|
-
modelId,
|
|
187
|
-
};
|
|
240
|
+
return routeResult(provName, prov, modelId);
|
|
188
241
|
}
|
|
189
242
|
}
|
|
190
243
|
|
|
@@ -193,22 +246,17 @@ export function routeModel(config: OcxConfig, modelId: string): RouteResult {
|
|
|
193
246
|
|
|
194
247
|
for (const [provName, prov] of activeProviderEntries(config)) {
|
|
195
248
|
if (prov.models && Array.isArray(prov.models) && (prov.models as string[]).includes(modelId)) {
|
|
196
|
-
return
|
|
197
|
-
providerName: provName,
|
|
198
|
-
provider: routedProviderConfig(provName, prov),
|
|
199
|
-
modelId,
|
|
200
|
-
};
|
|
249
|
+
return routeResult(provName, prov, modelId);
|
|
201
250
|
}
|
|
202
251
|
}
|
|
203
252
|
|
|
253
|
+
if (config.defaultProvider === LEGACY_CHATGPT_PROVIDER_ID) {
|
|
254
|
+
throw new Error(`No provider configured for model: ${modelId}`);
|
|
255
|
+
}
|
|
204
256
|
if (hasOwnProvider(config.providers, config.defaultProvider)) {
|
|
205
257
|
const defaultProv = config.providers[config.defaultProvider];
|
|
206
258
|
if (defaultProv.disabled === true) throw new Error(`Default provider is disabled: ${config.defaultProvider}`);
|
|
207
|
-
return
|
|
208
|
-
providerName: config.defaultProvider,
|
|
209
|
-
provider: routedProviderConfig(config.defaultProvider, defaultProv),
|
|
210
|
-
modelId,
|
|
211
|
-
};
|
|
259
|
+
return routeResult(config.defaultProvider, defaultProv, modelId);
|
|
212
260
|
}
|
|
213
261
|
|
|
214
262
|
throw new Error(`No provider configured for model: ${modelId}`);
|
|
@@ -222,11 +270,7 @@ function routeByKnownModelPattern(config: OcxConfig, modelId: string): RouteResu
|
|
|
222
270
|
);
|
|
223
271
|
if (matchingProvider) {
|
|
224
272
|
const [provName, prov] = matchingProvider;
|
|
225
|
-
return
|
|
226
|
-
providerName: provName,
|
|
227
|
-
provider: routedProviderConfig(provName, prov),
|
|
228
|
-
modelId,
|
|
229
|
-
};
|
|
273
|
+
return routeResult(provName, prov, modelId);
|
|
230
274
|
}
|
|
231
275
|
}
|
|
232
276
|
}
|