@bitkyc08/opencodex 2.7.23 → 2.7.24
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-BzhyTAco.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 +112 -20
- 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-DQjt6Hly.js +0 -40
|
@@ -24,6 +24,9 @@ import {
|
|
|
24
24
|
TokenRefreshError,
|
|
25
25
|
} from "../codex/account-store";
|
|
26
26
|
import { codexWarmupFailureReason, warmCodexAccount } from "../codex/warmup";
|
|
27
|
+
import { getMainAccountToken, MAIN_CODEX_ACCOUNT_ID } from "../codex/main-account";
|
|
28
|
+
import { isCanonicalOpenAiForwardProvider, OPENAI_CODEX_PROVIDER_ID } from "../providers/openai-tiers";
|
|
29
|
+
import { providerCodexAccountMode } from "../providers/registry";
|
|
27
30
|
|
|
28
31
|
export interface TokenGuardianHandle {
|
|
29
32
|
stop(): void;
|
|
@@ -147,9 +150,41 @@ export async function guardianSweep(nowMs: number = Date.now()): Promise<Guardia
|
|
|
147
150
|
}
|
|
148
151
|
}
|
|
149
152
|
|
|
150
|
-
// B)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
+
// B) canonical OpenAI Codex login. Direct warms main only; pool additionally maintains every
|
|
154
|
+
// added account. The direct branch returns before the managed account store is enumerated.
|
|
155
|
+
const openai = config.providers[OPENAI_CODEX_PROVIDER_ID];
|
|
156
|
+
if (
|
|
157
|
+
openai
|
|
158
|
+
&& openai.disabled !== true
|
|
159
|
+
&& isCanonicalOpenAiForwardProvider(openai)
|
|
160
|
+
&& resolveRefreshPolicy(OPENAI_CODEX_PROVIDER_ID, config) === "proactive"
|
|
161
|
+
) {
|
|
162
|
+
const mode = providerCodexAccountMode(OPENAI_CODEX_PROVIDER_ID, openai) ?? "pool";
|
|
163
|
+
if (opts.codexWarmupEnabled) {
|
|
164
|
+
const mainToken = getMainAccountToken();
|
|
165
|
+
const key = `codex:${MAIN_CODEX_ACCOUNT_ID}`;
|
|
166
|
+
if (mainToken && !inBackoff(key, nowMs)) {
|
|
167
|
+
tasks.push(async () => {
|
|
168
|
+
try {
|
|
169
|
+
await warmCodexAccount({
|
|
170
|
+
accessToken: mainToken.accessToken,
|
|
171
|
+
chatgptAccountId: mainToken.chatgptAccountId,
|
|
172
|
+
model: opts.codexWarmupModel,
|
|
173
|
+
});
|
|
174
|
+
backoff.delete(key);
|
|
175
|
+
result.warmed.push(key);
|
|
176
|
+
} catch (err) {
|
|
177
|
+
recordFailure(key, nowMs, opts.backoffBaseSeconds, opts.backoffMaxSeconds, false);
|
|
178
|
+
result.failed.push(key);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
} else if (mainToken) {
|
|
182
|
+
result.skippedBackoff.push(key);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const addedAccountIds = mode === "pool" ? listCodexAccountIds() : [];
|
|
187
|
+
for (const id of addedAccountIds) {
|
|
153
188
|
const record = readCodexAccountRecord(id);
|
|
154
189
|
if (!record || record.deletedAt != null) continue;
|
|
155
190
|
const cred = record.credential;
|
package/src/providers/derive.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { OcxProviderConfig } from "../types";
|
|
1
|
+
import type { CodexAccountMode, OcxProviderConfig } from "../types";
|
|
2
2
|
import { PROVIDER_REGISTRY, type ProviderRegistryEntry } from "./registry";
|
|
3
3
|
|
|
4
4
|
export interface DerivedKeyLoginProvider {
|
|
@@ -12,8 +12,10 @@ export interface DerivedKeyLoginProvider {
|
|
|
12
12
|
contextWindow?: number;
|
|
13
13
|
modelContextWindows?: Record<string, number>;
|
|
14
14
|
modelInputModalities?: Record<string, string[]>;
|
|
15
|
+
modelMaxInputTokens?: Record<string, number>;
|
|
15
16
|
reasoningEfforts?: string[];
|
|
16
17
|
modelReasoningEfforts?: Record<string, string[]>;
|
|
18
|
+
modelDefaultReasoningEfforts?: Record<string, string>;
|
|
17
19
|
reasoningEffortMap?: Record<string, string>;
|
|
18
20
|
modelReasoningEffortMap?: Record<string, Record<string, string>>;
|
|
19
21
|
noVisionModels?: string[];
|
|
@@ -26,6 +28,9 @@ export interface DerivedKeyLoginProvider {
|
|
|
26
28
|
thinkingToggleModels?: string[];
|
|
27
29
|
thinkingBudgetModels?: string[];
|
|
28
30
|
escapeBuiltinToolNames?: boolean;
|
|
31
|
+
googleMode?: "ai-studio" | "vertex" | "cloud-code-assist";
|
|
32
|
+
project?: string;
|
|
33
|
+
location?: string;
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
export interface DerivedInitProvider {
|
|
@@ -34,6 +39,7 @@ export interface DerivedInitProvider {
|
|
|
34
39
|
adapter: string;
|
|
35
40
|
baseUrl: string;
|
|
36
41
|
kind: "forward" | "oauth" | "key" | "local";
|
|
42
|
+
codexAccountMode?: CodexAccountMode;
|
|
37
43
|
dashboardUrl?: string;
|
|
38
44
|
defaultModel?: string;
|
|
39
45
|
}
|
|
@@ -45,10 +51,15 @@ export interface DerivedProviderPreset {
|
|
|
45
51
|
baseUrl: string;
|
|
46
52
|
defaultModel?: string;
|
|
47
53
|
auth: "oauth" | "forward" | "key" | "local";
|
|
54
|
+
codexAccountMode?: CodexAccountMode;
|
|
48
55
|
oauthProvider?: string;
|
|
49
56
|
dashboardUrl?: string;
|
|
50
57
|
note?: string;
|
|
51
58
|
keyOptional?: boolean;
|
|
59
|
+
/** Free pricing (may still require a key). */
|
|
60
|
+
freeTier?: boolean;
|
|
61
|
+
/** Immutable canonical provider config seed for the reserved canonical `openai` forward preset. */
|
|
62
|
+
provider?: OcxProviderConfig;
|
|
52
63
|
}
|
|
53
64
|
|
|
54
65
|
export function listRegistryEntries(): readonly ProviderRegistryEntry[] {
|
|
@@ -68,7 +79,9 @@ export function providerConfigSeed(entry: ProviderRegistryEntry): OcxProviderCon
|
|
|
68
79
|
adapter: entry.adapter,
|
|
69
80
|
baseUrl: entry.baseUrl,
|
|
70
81
|
authMode: entry.authKind === "local" ? undefined : entry.authKind,
|
|
82
|
+
...(entry.codexAccountMode ? { codexAccountMode: entry.codexAccountMode } : {}),
|
|
71
83
|
...(entry.keyOptional !== undefined ? { keyOptional: entry.keyOptional } : {}),
|
|
84
|
+
...(entry.freeTier !== undefined ? { freeTier: entry.freeTier } : {}),
|
|
72
85
|
...(entry.modelSuffixBracketStrip !== undefined ? { modelSuffixBracketStrip: entry.modelSuffixBracketStrip } : {}),
|
|
73
86
|
...(entry.staticHeaders ? { headers: { ...entry.staticHeaders } } : {}),
|
|
74
87
|
...(entry.defaultModel ? { defaultModel: entry.defaultModel } : {}),
|
|
@@ -77,8 +90,10 @@ export function providerConfigSeed(entry: ProviderRegistryEntry): OcxProviderCon
|
|
|
77
90
|
...(entry.contextWindow !== undefined ? { contextWindow: entry.contextWindow } : {}),
|
|
78
91
|
...(entry.modelContextWindows ? { modelContextWindows: { ...entry.modelContextWindows } } : {}),
|
|
79
92
|
...(entry.modelInputModalities ? { modelInputModalities: cloneRecordOfArrays(entry.modelInputModalities) } : {}),
|
|
93
|
+
...(entry.modelMaxInputTokens ? { modelMaxInputTokens: { ...entry.modelMaxInputTokens } } : {}),
|
|
80
94
|
...(entry.reasoningEfforts ? { reasoningEfforts: [...entry.reasoningEfforts] } : {}),
|
|
81
95
|
...(entry.modelReasoningEfforts ? { modelReasoningEfforts: cloneRecordOfArrays(entry.modelReasoningEfforts) } : {}),
|
|
96
|
+
...(entry.modelDefaultReasoningEfforts ? { modelDefaultReasoningEfforts: { ...entry.modelDefaultReasoningEfforts } } : {}),
|
|
82
97
|
...(entry.reasoningEffortMap ? { reasoningEffortMap: { ...entry.reasoningEffortMap } } : {}),
|
|
83
98
|
...(entry.modelReasoningEffortMap ? { modelReasoningEffortMap: cloneNestedRecord(entry.modelReasoningEffortMap) } : {}),
|
|
84
99
|
...(entry.noVisionModels ? { noVisionModels: [...entry.noVisionModels] } : {}),
|
|
@@ -114,8 +129,10 @@ export function deriveKeyLoginMap(): Record<string, DerivedKeyLoginProvider> {
|
|
|
114
129
|
...(entry.contextWindow !== undefined ? { contextWindow: entry.contextWindow } : {}),
|
|
115
130
|
...(entry.modelContextWindows ? { modelContextWindows: { ...entry.modelContextWindows } } : {}),
|
|
116
131
|
...(entry.modelInputModalities ? { modelInputModalities: cloneRecordOfArrays(entry.modelInputModalities) } : {}),
|
|
132
|
+
...(entry.modelMaxInputTokens ? { modelMaxInputTokens: { ...entry.modelMaxInputTokens } } : {}),
|
|
117
133
|
...(entry.reasoningEfforts ? { reasoningEfforts: [...entry.reasoningEfforts] } : {}),
|
|
118
134
|
...(entry.modelReasoningEfforts ? { modelReasoningEfforts: cloneRecordOfArrays(entry.modelReasoningEfforts) } : {}),
|
|
135
|
+
...(entry.modelDefaultReasoningEfforts ? { modelDefaultReasoningEfforts: { ...entry.modelDefaultReasoningEfforts } } : {}),
|
|
119
136
|
...(entry.reasoningEffortMap ? { reasoningEffortMap: { ...entry.reasoningEffortMap } } : {}),
|
|
120
137
|
...(entry.modelReasoningEffortMap ? { modelReasoningEffortMap: cloneNestedRecord(entry.modelReasoningEffortMap) } : {}),
|
|
121
138
|
...(entry.noVisionModels ? { noVisionModels: [...entry.noVisionModels] } : {}),
|
|
@@ -143,6 +160,7 @@ export function deriveInitProviders(): DerivedInitProvider[] {
|
|
|
143
160
|
adapter: entry.adapter,
|
|
144
161
|
baseUrl: entry.baseUrl,
|
|
145
162
|
kind: entry.authKind,
|
|
163
|
+
...(entry.codexAccountMode ? { codexAccountMode: entry.codexAccountMode } : {}),
|
|
146
164
|
...(entry.dashboardUrl ? { dashboardUrl: entry.dashboardUrl } : {}),
|
|
147
165
|
...(entry.defaultModel ? { defaultModel: entry.defaultModel } : {}),
|
|
148
166
|
}));
|
|
@@ -173,6 +191,8 @@ export function enrichProviderFromRegistry(name: string, prov: OcxProviderConfig
|
|
|
173
191
|
if (!entry) return;
|
|
174
192
|
const seed = providerConfigSeed(entry);
|
|
175
193
|
if (!prov.defaultModel && seed.defaultModel) prov.defaultModel = seed.defaultModel;
|
|
194
|
+
// Fill mode only when absent: an explicit persisted `direct` must never be overwritten.
|
|
195
|
+
if (prov.codexAccountMode === undefined && seed.codexAccountMode !== undefined) prov.codexAccountMode = seed.codexAccountMode;
|
|
176
196
|
if (!prov.models && seed.models) prov.models = [...seed.models];
|
|
177
197
|
if (prov.liveModels === undefined && seed.liveModels !== undefined) prov.liveModels = seed.liveModels;
|
|
178
198
|
if (prov.contextWindow === undefined && seed.contextWindow !== undefined) prov.contextWindow = seed.contextWindow;
|
|
@@ -180,6 +200,7 @@ export function enrichProviderFromRegistry(name: string, prov: OcxProviderConfig
|
|
|
180
200
|
if (!prov.modelInputModalities && seed.modelInputModalities) prov.modelInputModalities = cloneRecordOfArrays(seed.modelInputModalities);
|
|
181
201
|
if (!prov.reasoningEfforts && seed.reasoningEfforts) prov.reasoningEfforts = [...seed.reasoningEfforts];
|
|
182
202
|
if (!prov.modelReasoningEfforts && seed.modelReasoningEfforts) prov.modelReasoningEfforts = cloneRecordOfArrays(seed.modelReasoningEfforts);
|
|
203
|
+
if (!prov.modelDefaultReasoningEfforts && seed.modelDefaultReasoningEfforts) prov.modelDefaultReasoningEfforts = { ...seed.modelDefaultReasoningEfforts };
|
|
183
204
|
if (!prov.reasoningEffortMap && seed.reasoningEffortMap) prov.reasoningEffortMap = { ...seed.reasoningEffortMap };
|
|
184
205
|
if (!prov.modelReasoningEffortMap && seed.modelReasoningEffortMap) prov.modelReasoningEffortMap = cloneNestedRecord(seed.modelReasoningEffortMap);
|
|
185
206
|
if (!prov.noVisionModels && seed.noVisionModels) prov.noVisionModels = [...seed.noVisionModels];
|
|
@@ -194,6 +215,7 @@ export function enrichProviderFromRegistry(name: string, prov: OcxProviderConfig
|
|
|
194
215
|
if (!prov.thinkingBudgetModels && seed.thinkingBudgetModels) prov.thinkingBudgetModels = [...seed.thinkingBudgetModels];
|
|
195
216
|
if (prov.escapeBuiltinToolNames === undefined && seed.escapeBuiltinToolNames !== undefined) prov.escapeBuiltinToolNames = seed.escapeBuiltinToolNames;
|
|
196
217
|
if (prov.keyOptional === undefined && seed.keyOptional !== undefined) prov.keyOptional = seed.keyOptional;
|
|
218
|
+
if (prov.freeTier === undefined && seed.freeTier !== undefined) prov.freeTier = seed.freeTier;
|
|
197
219
|
if (prov.modelSuffixBracketStrip === undefined && seed.modelSuffixBracketStrip !== undefined) prov.modelSuffixBracketStrip = seed.modelSuffixBracketStrip;
|
|
198
220
|
if (!prov.headers && seed.headers) prov.headers = { ...seed.headers };
|
|
199
221
|
}
|
|
@@ -226,11 +248,14 @@ function entryToPreset(entry: ProviderRegistryEntry): DerivedProviderPreset {
|
|
|
226
248
|
adapter: entry.adapter,
|
|
227
249
|
baseUrl: entry.baseUrl,
|
|
228
250
|
auth: entry.authKind === "forward" ? "forward" : entry.authKind === "oauth" ? "oauth" : entry.authKind === "local" ? "local" : "key",
|
|
251
|
+
...(entry.codexAccountMode ? { codexAccountMode: entry.codexAccountMode } : {}),
|
|
252
|
+
...(entry.codexAccountMode ? { provider: providerConfigSeed(entry) } : {}),
|
|
229
253
|
...(entry.defaultModel ? { defaultModel: entry.defaultModel } : {}),
|
|
230
254
|
...(entry.authKind === "oauth" ? { oauthProvider: entry.oauthId ?? entry.id } : {}),
|
|
231
255
|
...(entry.dashboardUrl ? { dashboardUrl: entry.dashboardUrl } : {}),
|
|
232
256
|
...(entry.note ? { note: entry.note } : {}),
|
|
233
257
|
...(entry.keyOptional ? { keyOptional: true } : {}),
|
|
258
|
+
...(entry.freeTier ? { freeTier: true } : {}),
|
|
234
259
|
};
|
|
235
260
|
}
|
|
236
261
|
|
|
@@ -250,7 +275,7 @@ function customPreset(): DerivedProviderPreset {
|
|
|
250
275
|
}
|
|
251
276
|
|
|
252
277
|
function formatInitLabel(entry: ProviderRegistryEntry): string {
|
|
253
|
-
if (entry.authKind === "forward") return "OpenAI — ChatGPT login (no key)";
|
|
278
|
+
if (entry.authKind === "forward") return "OpenAI — ChatGPT login (no key; account pool default, Direct selectable)";
|
|
254
279
|
if (entry.authKind === "oauth") {
|
|
255
280
|
if (entry.id === "xai") return "xAI (Grok) — account login";
|
|
256
281
|
if (entry.id === "anthropic") return "Anthropic (Claude) — account login";
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export const KIRO_MODELS = [
|
|
2
2
|
"kiro-auto",
|
|
3
|
+
// OpenAI GPT-5.6 (Kiro experimental, us-east-1) — added to official catalog 2026-07-13
|
|
4
|
+
"gpt-5.6-sol",
|
|
5
|
+
"gpt-5.6-terra",
|
|
6
|
+
"gpt-5.6-luna",
|
|
3
7
|
"claude-sonnet-5",
|
|
4
8
|
"claude-opus-4.8",
|
|
5
9
|
"claude-opus-4.7",
|
|
@@ -17,11 +21,12 @@ export const KIRO_MODELS = [
|
|
|
17
21
|
];
|
|
18
22
|
|
|
19
23
|
// Per-model context windows as documented on Kiro's official model catalog
|
|
20
|
-
// (https://kiro.dev/docs/models/ — "Quick comparison", page updated 2026-
|
|
21
|
-
// claude-sonnet-5 is pre-seeded from Anthropic/OpenRouter 1M metadata until Kiro's
|
|
22
|
-
// public catalog catches up.
|
|
24
|
+
// (https://kiro.dev/docs/models/ — "Quick comparison", page updated 2026-07-14).
|
|
23
25
|
// "Auto" is a router with no fixed window on Kiro's table, so it is intentionally omitted.
|
|
24
26
|
export const KIRO_MODEL_CONTEXT_WINDOWS: Record<string, number> = {
|
|
27
|
+
"gpt-5.6-sol": 272_000,
|
|
28
|
+
"gpt-5.6-terra": 272_000,
|
|
29
|
+
"gpt-5.6-luna": 272_000,
|
|
25
30
|
"claude-sonnet-5": 1_000_000,
|
|
26
31
|
"claude-opus-4.8": 1_000_000,
|
|
27
32
|
"claude-opus-4.7": 1_000_000,
|
package/src/providers/label.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { CODEX_ACCOUNT_LOG_LABEL_RE } from "../codex/account-label";
|
|
2
2
|
|
|
3
3
|
function canonicalUsageProviderLabel(provider: string): string {
|
|
4
|
-
return provider === "chatgpt" ? "openai" : provider;
|
|
4
|
+
return provider === "chatgpt" || provider === "openai-multi" ? "openai" : provider;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
export function baseProviderLabel(provider: string): string {
|
|
8
|
+
const canonical = canonicalUsageProviderLabel(provider);
|
|
9
|
+
if (canonical !== provider) return canonical;
|
|
8
10
|
const cut = provider.lastIndexOf("-");
|
|
9
11
|
if (cut <= 0) return canonicalUsageProviderLabel(provider);
|
|
10
12
|
const suffix = provider.slice(cut + 1);
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { resolveEnvValue } from "../config";
|
|
2
|
+
import {
|
|
3
|
+
headersForCodexAuthContext,
|
|
4
|
+
hasCallerCodexBearer,
|
|
5
|
+
isCodexAuthContextUsable,
|
|
6
|
+
resolveCodexAuthContext,
|
|
7
|
+
type CodexAuthContext,
|
|
8
|
+
} from "../codex/auth-context";
|
|
9
|
+
import { recordCodexUpstreamOutcome, type CodexUpstreamOutcome } from "../codex/routing";
|
|
10
|
+
import { ForwardAdmissionCredentialError, validateForwardAdmissionCredential } from "../server/auth-cors";
|
|
11
|
+
import type { CodexAccountMode, OcxConfig, OcxProviderConfig } from "../types";
|
|
12
|
+
import {
|
|
13
|
+
isCanonicalOpenAiForwardProvider,
|
|
14
|
+
OPENAI_API_PROVIDER_ID,
|
|
15
|
+
OPENAI_CODEX_PROVIDER_ID,
|
|
16
|
+
} from "./openai-tiers";
|
|
17
|
+
import { providerCodexAccountMode } from "./registry";
|
|
18
|
+
|
|
19
|
+
export interface OpenAiForwardSidecarCandidate {
|
|
20
|
+
providerName: typeof OPENAI_CODEX_PROVIDER_ID;
|
|
21
|
+
provider: OcxProviderConfig;
|
|
22
|
+
accountMode: CodexAccountMode;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ResolvedOpenAiForwardSidecar extends OpenAiForwardSidecarCandidate {
|
|
26
|
+
authContext: CodexAuthContext;
|
|
27
|
+
headers: Headers;
|
|
28
|
+
recordOutcome?: (outcome: CodexUpstreamOutcome) => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface OpenAiImagesProviderSelection {
|
|
32
|
+
forwardCandidates: OpenAiForwardSidecarCandidate[];
|
|
33
|
+
keyed?: {
|
|
34
|
+
providerName: typeof OPENAI_API_PROVIDER_ID;
|
|
35
|
+
provider: OcxProviderConfig;
|
|
36
|
+
apiKey: string;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function listOpenAiForwardSidecarCandidates(config: OcxConfig): OpenAiForwardSidecarCandidate[] {
|
|
41
|
+
const provider = config.providers[OPENAI_CODEX_PROVIDER_ID];
|
|
42
|
+
if (!provider || provider.disabled === true || !isCanonicalOpenAiForwardProvider(provider)) return [];
|
|
43
|
+
return [{
|
|
44
|
+
providerName: OPENAI_CODEX_PROVIDER_ID,
|
|
45
|
+
provider,
|
|
46
|
+
accountMode: providerCodexAccountMode(OPENAI_CODEX_PROVIDER_ID, provider) ?? "pool",
|
|
47
|
+
}];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function resolveFirstUsableOpenAiSidecar(
|
|
51
|
+
candidates: readonly OpenAiForwardSidecarCandidate[],
|
|
52
|
+
incomingHeaders: Headers,
|
|
53
|
+
config: OcxConfig,
|
|
54
|
+
): Promise<ResolvedOpenAiForwardSidecar | undefined> {
|
|
55
|
+
let callerBearerMayBeForwarded = true;
|
|
56
|
+
try {
|
|
57
|
+
validateForwardAdmissionCredential(incomingHeaders, config);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (!(error instanceof ForwardAdmissionCredentialError)) throw error;
|
|
60
|
+
callerBearerMayBeForwarded = false;
|
|
61
|
+
}
|
|
62
|
+
for (const candidate of candidates) {
|
|
63
|
+
if (candidate.accountMode === "direct" && (!callerBearerMayBeForwarded || !hasCallerCodexBearer(incomingHeaders))) continue;
|
|
64
|
+
const authContext = await resolveCodexAuthContext(incomingHeaders, config, candidate.accountMode);
|
|
65
|
+
if (!isCodexAuthContextUsable(authContext, config)) continue;
|
|
66
|
+
return {
|
|
67
|
+
...candidate,
|
|
68
|
+
authContext,
|
|
69
|
+
headers: headersForCodexAuthContext(incomingHeaders, authContext),
|
|
70
|
+
...(authContext.kind === "pool" || authContext.kind === "main-pool"
|
|
71
|
+
? { recordOutcome: (outcome: CodexUpstreamOutcome) => recordCodexUpstreamOutcome(config, authContext.accountId, outcome) }
|
|
72
|
+
: {}),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function selectOpenAiImagesProvider(config: OcxConfig): OpenAiImagesProviderSelection {
|
|
79
|
+
const selection: OpenAiImagesProviderSelection = {
|
|
80
|
+
forwardCandidates: listOpenAiForwardSidecarCandidates(config),
|
|
81
|
+
};
|
|
82
|
+
const provider = config.providers[OPENAI_API_PROVIDER_ID];
|
|
83
|
+
if (
|
|
84
|
+
provider
|
|
85
|
+
&& provider.disabled !== true
|
|
86
|
+
&& provider.adapter === "openai-responses"
|
|
87
|
+
&& provider.authMode !== "forward"
|
|
88
|
+
&& provider.baseUrl.replace(/\/+$/, "") === "https://api.openai.com/v1"
|
|
89
|
+
) {
|
|
90
|
+
const apiKey = resolveEnvValue(provider.apiKey)?.trim();
|
|
91
|
+
if (apiKey) selection.keyed = { providerName: OPENAI_API_PROVIDER_ID, provider, apiKey };
|
|
92
|
+
}
|
|
93
|
+
return selection;
|
|
94
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { backupConfigBeforeOpenAiTierMigration, saveConfig } from "../config";
|
|
2
|
+
import type { OcxConfig } from "../types";
|
|
3
|
+
import { projectOpenAiTierMigration } from "./openai-tiers";
|
|
4
|
+
|
|
5
|
+
export interface OpenAiTierStartupDeps {
|
|
6
|
+
project: typeof projectOpenAiTierMigration;
|
|
7
|
+
backup: () => void;
|
|
8
|
+
save: (config: OcxConfig) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const DEFAULT_DEPS: OpenAiTierStartupDeps = {
|
|
12
|
+
project: projectOpenAiTierMigration,
|
|
13
|
+
backup: backupConfigBeforeOpenAiTierMigration,
|
|
14
|
+
save: saveConfig,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function runOpenAiTierStartupMigration(
|
|
18
|
+
config: OcxConfig,
|
|
19
|
+
deps: OpenAiTierStartupDeps = DEFAULT_DEPS,
|
|
20
|
+
): OcxConfig {
|
|
21
|
+
const projection = deps.project(config);
|
|
22
|
+
if (!projection.changed) return projection.config;
|
|
23
|
+
deps.backup();
|
|
24
|
+
deps.save(projection.config);
|
|
25
|
+
for (const warning of projection.warnings) console.warn(`[openai-provider-migration] ${warning}`);
|
|
26
|
+
return projection.config;
|
|
27
|
+
}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import type { CodexAccountMode, OcxConfig, OcxProviderConfig } from "../types";
|
|
2
|
+
import { OPENAI_PROVIDER_TIER_VERSION } from "../types";
|
|
3
|
+
|
|
4
|
+
export const OPENAI_CODEX_PROVIDER_ID = "openai";
|
|
5
|
+
export const LEGACY_OPENAI_MULTI_PROVIDER_ID = "openai-multi";
|
|
6
|
+
export const OPENAI_API_PROVIDER_ID = "openai-apikey";
|
|
7
|
+
export const LEGACY_CHATGPT_PROVIDER_ID = "chatgpt";
|
|
8
|
+
|
|
9
|
+
const CODEX_FORWARD_BASE_URL = "https://chatgpt.com/backend-api/codex";
|
|
10
|
+
const LEGACY_OPENAI_MULTI_PREFIX = `${LEGACY_OPENAI_MULTI_PROVIDER_ID}/`;
|
|
11
|
+
|
|
12
|
+
function canonicalCodexForwardProvider(mode: CodexAccountMode): OcxProviderConfig {
|
|
13
|
+
return {
|
|
14
|
+
adapter: "openai-responses",
|
|
15
|
+
baseUrl: CODEX_FORWARD_BASE_URL,
|
|
16
|
+
authMode: "forward",
|
|
17
|
+
codexAccountMode: mode,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function normalizedBaseUrl(value: string): string | undefined {
|
|
22
|
+
try {
|
|
23
|
+
const url = new URL(value.trim());
|
|
24
|
+
if (url.username || url.password || url.search || url.hash) return undefined;
|
|
25
|
+
const path = url.pathname.replace(/\/+$/, "");
|
|
26
|
+
return `${url.origin}${path}`;
|
|
27
|
+
} catch {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function isCanonicalOpenAiForwardProvider(provider: OcxProviderConfig): boolean {
|
|
33
|
+
return provider.adapter === "openai-responses"
|
|
34
|
+
&& provider.authMode === "forward"
|
|
35
|
+
&& normalizedBaseUrl(provider.baseUrl) === CODEX_FORWARD_BASE_URL;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface OpenAiTierMigrationProjection {
|
|
39
|
+
config: OcxConfig;
|
|
40
|
+
changed: boolean;
|
|
41
|
+
resolvedMode: CodexAccountMode;
|
|
42
|
+
warnings: string[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export class OpenAiTierMigrationCollisionError extends Error {
|
|
46
|
+
readonly providerName = LEGACY_OPENAI_MULTI_PROVIDER_ID;
|
|
47
|
+
|
|
48
|
+
constructor() {
|
|
49
|
+
super(`Reserved provider id "${LEGACY_OPENAI_MULTI_PROVIDER_ID}" is already configured with a noncanonical shape`);
|
|
50
|
+
this.name = "OpenAiTierMigrationCollisionError";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function managedLegacyMultiOverlay(provider: OcxProviderConfig): Pick<OcxProviderConfig, "disabled" | "selectedModels"> | null {
|
|
55
|
+
const allowed = new Set(["adapter", "authMode", "baseUrl", "disabled", "selectedModels"]);
|
|
56
|
+
if (!Object.keys(provider).every(key => allowed.has(key))) return null;
|
|
57
|
+
if (!isCanonicalOpenAiForwardProvider(provider)) return null;
|
|
58
|
+
if (provider.disabled !== undefined && typeof provider.disabled !== "boolean") return null;
|
|
59
|
+
if (provider.selectedModels !== undefined && (
|
|
60
|
+
!Array.isArray(provider.selectedModels)
|
|
61
|
+
|| provider.selectedModels.some(model => typeof model !== "string")
|
|
62
|
+
)) return null;
|
|
63
|
+
return {
|
|
64
|
+
...(provider.disabled !== undefined ? { disabled: provider.disabled } : {}),
|
|
65
|
+
...(provider.selectedModels !== undefined ? { selectedModels: [...provider.selectedModels] } : {}),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function rewriteLegacyOpenAiSelectedId(value: string): string {
|
|
70
|
+
return value.startsWith(LEGACY_OPENAI_MULTI_PREFIX)
|
|
71
|
+
? value.slice(LEGACY_OPENAI_MULTI_PREFIX.length)
|
|
72
|
+
: value;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function rewriteLegacyOpenAiModelList(values: string[] | undefined): string[] | undefined {
|
|
76
|
+
if (!values) return undefined;
|
|
77
|
+
return [...new Set(values.map(rewriteLegacyOpenAiSelectedId))];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function mergeLegacyOpenAiProviderRows(
|
|
81
|
+
openai: OcxProviderConfig | undefined,
|
|
82
|
+
legacyMulti: OcxProviderConfig | undefined,
|
|
83
|
+
mode: CodexAccountMode,
|
|
84
|
+
): OcxProviderConfig {
|
|
85
|
+
const selectedModels = rewriteLegacyOpenAiModelList([
|
|
86
|
+
...(openai?.selectedModels ?? []),
|
|
87
|
+
...(legacyMulti?.selectedModels ?? []),
|
|
88
|
+
]);
|
|
89
|
+
const formerRows = [openai, legacyMulti].filter((row): row is OcxProviderConfig => row !== undefined);
|
|
90
|
+
const disabled = formerRows.length > 0 && formerRows.every(row => row.disabled === true);
|
|
91
|
+
return {
|
|
92
|
+
...canonicalCodexForwardProvider(mode),
|
|
93
|
+
...(disabled ? { disabled: true } : {}),
|
|
94
|
+
...(selectedModels && selectedModels.length > 0 ? { selectedModels } : {}),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function hasKnownLegacyOpenAiReference(config: OcxConfig): boolean {
|
|
99
|
+
const matches = (value: unknown): boolean => typeof value === "string" && value.startsWith(LEGACY_OPENAI_MULTI_PREFIX);
|
|
100
|
+
const matchesList = (value: unknown): boolean => Array.isArray(value) && value.some(matches);
|
|
101
|
+
const claude = config.claudeCode;
|
|
102
|
+
return config.defaultProvider === LEGACY_OPENAI_MULTI_PROVIDER_ID
|
|
103
|
+
|| matchesList(config.disabledModels)
|
|
104
|
+
|| matchesList(config.subagentModels)
|
|
105
|
+
|| matches(config.injectionModel)
|
|
106
|
+
|| matches(config.shadowCallIntercept?.model)
|
|
107
|
+
|| matches(config.webSearchSidecar?.model)
|
|
108
|
+
|| matches(config.visionSidecar?.model)
|
|
109
|
+
|| matches(claude?.webSearchSidecar?.model)
|
|
110
|
+
|| matches(claude?.visionSidecar?.model)
|
|
111
|
+
|| matches(claude?.model)
|
|
112
|
+
|| matches(claude?.smallFastModel)
|
|
113
|
+
|| Object.values(claude?.tierModels ?? {}).some(matches)
|
|
114
|
+
|| Object.values(claude?.modelMap ?? {}).some(matches)
|
|
115
|
+
|| matchesList(config.providers[OPENAI_CODEX_PROVIDER_ID]?.selectedModels)
|
|
116
|
+
|| matchesList(config.providers[LEGACY_OPENAI_MULTI_PROVIDER_ID]?.selectedModels)
|
|
117
|
+
|| config.providerContextCaps?.[LEGACY_OPENAI_MULTI_PROVIDER_ID] !== undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function rewriteLegacyOpenAiReferences(config: OcxConfig, warnings: string[]): void {
|
|
121
|
+
config.disabledModels = rewriteLegacyOpenAiModelList(config.disabledModels);
|
|
122
|
+
config.subagentModels = rewriteLegacyOpenAiModelList(config.subagentModels);
|
|
123
|
+
if (config.injectionModel) config.injectionModel = rewriteLegacyOpenAiSelectedId(config.injectionModel);
|
|
124
|
+
if (config.shadowCallIntercept?.model) {
|
|
125
|
+
config.shadowCallIntercept.model = rewriteLegacyOpenAiSelectedId(config.shadowCallIntercept.model);
|
|
126
|
+
}
|
|
127
|
+
if (config.webSearchSidecar?.model) config.webSearchSidecar.model = rewriteLegacyOpenAiSelectedId(config.webSearchSidecar.model);
|
|
128
|
+
if (config.visionSidecar?.model) config.visionSidecar.model = rewriteLegacyOpenAiSelectedId(config.visionSidecar.model);
|
|
129
|
+
|
|
130
|
+
const claude = config.claudeCode;
|
|
131
|
+
if (claude?.webSearchSidecar?.model) claude.webSearchSidecar.model = rewriteLegacyOpenAiSelectedId(claude.webSearchSidecar.model);
|
|
132
|
+
if (claude?.visionSidecar?.model) claude.visionSidecar.model = rewriteLegacyOpenAiSelectedId(claude.visionSidecar.model);
|
|
133
|
+
if (claude?.model) claude.model = rewriteLegacyOpenAiSelectedId(claude.model);
|
|
134
|
+
if (claude?.smallFastModel) claude.smallFastModel = rewriteLegacyOpenAiSelectedId(claude.smallFastModel);
|
|
135
|
+
if (claude?.tierModels) {
|
|
136
|
+
for (const tier of ["opus", "sonnet", "haiku", "fable"] as const) {
|
|
137
|
+
const value = claude.tierModels[tier];
|
|
138
|
+
if (value) claude.tierModels[tier] = rewriteLegacyOpenAiSelectedId(value);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (claude?.modelMap) {
|
|
142
|
+
for (const key of Object.keys(claude.modelMap)) {
|
|
143
|
+
claude.modelMap[key] = rewriteLegacyOpenAiSelectedId(claude.modelMap[key]!);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const caps = config.providerContextCaps;
|
|
148
|
+
const legacyCap = caps?.[LEGACY_OPENAI_MULTI_PROVIDER_ID];
|
|
149
|
+
if (caps && legacyCap !== undefined) {
|
|
150
|
+
const currentCap = caps[OPENAI_CODEX_PROVIDER_ID];
|
|
151
|
+
if (currentCap !== undefined) {
|
|
152
|
+
caps[OPENAI_CODEX_PROVIDER_ID] = Math.min(currentCap, legacyCap);
|
|
153
|
+
warnings.push("providerContextCaps.openai + providerContextCaps.openai-multi: kept lower positive cap");
|
|
154
|
+
} else {
|
|
155
|
+
caps[OPENAI_CODEX_PROVIDER_ID] = legacyCap;
|
|
156
|
+
}
|
|
157
|
+
delete caps[LEGACY_OPENAI_MULTI_PROVIDER_ID];
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function isKnownLegacyValuePath(path: readonly string[]): boolean {
|
|
162
|
+
const joined = path.join(".");
|
|
163
|
+
if (joined === "defaultProvider") return true;
|
|
164
|
+
if (/^(disabledModels|subagentModels)\.\d+$/.test(joined)) return true;
|
|
165
|
+
if (/^providers\.(openai|openai-multi)\.selectedModels\.\d+$/.test(joined)) return true;
|
|
166
|
+
return new Set([
|
|
167
|
+
"injectionModel",
|
|
168
|
+
"shadowCallIntercept.model",
|
|
169
|
+
"webSearchSidecar.model",
|
|
170
|
+
"visionSidecar.model",
|
|
171
|
+
"claudeCode.webSearchSidecar.model",
|
|
172
|
+
"claudeCode.visionSidecar.model",
|
|
173
|
+
"claudeCode.model",
|
|
174
|
+
"claudeCode.smallFastModel",
|
|
175
|
+
"claudeCode.tierModels.opus",
|
|
176
|
+
"claudeCode.tierModels.sonnet",
|
|
177
|
+
"claudeCode.tierModels.haiku",
|
|
178
|
+
"claudeCode.tierModels.fable",
|
|
179
|
+
]).has(joined) || /^claudeCode\.modelMap\..+$/.test(joined);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function unknownLegacyOpenAiWarnings(config: OcxConfig): string[] {
|
|
183
|
+
const warnings = new Set<string>();
|
|
184
|
+
const visit = (value: unknown, path: string[]): void => {
|
|
185
|
+
if (typeof value === "string") {
|
|
186
|
+
if (value.includes(LEGACY_OPENAI_MULTI_PROVIDER_ID) && !isKnownLegacyValuePath(path)) {
|
|
187
|
+
warnings.add(`${path.join(".")}: legacy OpenAI provider id left unchanged`);
|
|
188
|
+
}
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (!value || typeof value !== "object") return;
|
|
192
|
+
if (Array.isArray(value)) {
|
|
193
|
+
value.forEach((item, index) => visit(item, [...path, String(index)]));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
for (const [key, item] of Object.entries(value as Record<string, unknown>)) {
|
|
197
|
+
visit(item, [...path, key]);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
visit(config, []);
|
|
201
|
+
return [...warnings];
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function resolvedOpenAiMode(
|
|
205
|
+
config: OcxConfig,
|
|
206
|
+
openai: OcxProviderConfig | undefined,
|
|
207
|
+
legacyMulti: OcxProviderConfig | undefined,
|
|
208
|
+
knownLegacyReference: boolean,
|
|
209
|
+
): CodexAccountMode {
|
|
210
|
+
if (openai?.codexAccountMode === "pool" || openai?.codexAccountMode === "direct") {
|
|
211
|
+
return openai.codexAccountMode;
|
|
212
|
+
}
|
|
213
|
+
if (
|
|
214
|
+
(legacyMulti && legacyMulti.disabled !== true)
|
|
215
|
+
|| config.defaultProvider === LEGACY_OPENAI_MULTI_PROVIDER_ID
|
|
216
|
+
|| knownLegacyReference
|
|
217
|
+
) return "pool";
|
|
218
|
+
if (config.openaiProviderTierVersion === 1 && openai) return "direct";
|
|
219
|
+
if (config.openaiProviderTierVersion === 1 && legacyMulti) return "pool";
|
|
220
|
+
return "pool";
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function projectOpenAiTierMigration(config: OcxConfig): OpenAiTierMigrationProjection {
|
|
224
|
+
const projected = structuredClone(config);
|
|
225
|
+
const legacyMulti = projected.providers[LEGACY_OPENAI_MULTI_PROVIDER_ID];
|
|
226
|
+
if (legacyMulti && !managedLegacyMultiOverlay(legacyMulti)) {
|
|
227
|
+
throw new OpenAiTierMigrationCollisionError();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const openai = projected.providers[OPENAI_CODEX_PROVIDER_ID];
|
|
231
|
+
const knownLegacyReference = hasKnownLegacyOpenAiReference(projected);
|
|
232
|
+
const resolvedMode = resolvedOpenAiMode(projected, openai, legacyMulti, knownLegacyReference);
|
|
233
|
+
if (
|
|
234
|
+
projected.openaiProviderTierVersion === OPENAI_PROVIDER_TIER_VERSION
|
|
235
|
+
&& !legacyMulti
|
|
236
|
+
&& !Object.hasOwn(projected.providers, LEGACY_CHATGPT_PROVIDER_ID)
|
|
237
|
+
&& projected.defaultProvider !== LEGACY_OPENAI_MULTI_PROVIDER_ID
|
|
238
|
+
&& !knownLegacyReference
|
|
239
|
+
) {
|
|
240
|
+
return { config: projected, changed: false, resolvedMode, warnings: [] };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const warnings = unknownLegacyOpenAiWarnings(projected);
|
|
244
|
+
const referencesCodexForward = !!openai
|
|
245
|
+
|| !!legacyMulti
|
|
246
|
+
|| Object.hasOwn(projected.providers, LEGACY_CHATGPT_PROVIDER_ID)
|
|
247
|
+
|| projected.defaultProvider === OPENAI_CODEX_PROVIDER_ID
|
|
248
|
+
|| projected.defaultProvider === LEGACY_OPENAI_MULTI_PROVIDER_ID
|
|
249
|
+
|| projected.defaultProvider === LEGACY_CHATGPT_PROVIDER_ID
|
|
250
|
+
|| knownLegacyReference;
|
|
251
|
+
const mergedOpenAi = referencesCodexForward
|
|
252
|
+
? mergeLegacyOpenAiProviderRows(openai, legacyMulti, resolvedMode)
|
|
253
|
+
: undefined;
|
|
254
|
+
|
|
255
|
+
const nextProviders: Array<[string, OcxProviderConfig]> = [];
|
|
256
|
+
let inserted = false;
|
|
257
|
+
for (const [name, provider] of Object.entries(projected.providers)) {
|
|
258
|
+
if (name === OPENAI_CODEX_PROVIDER_ID) {
|
|
259
|
+
if (mergedOpenAi && !inserted) nextProviders.push([OPENAI_CODEX_PROVIDER_ID, mergedOpenAi]);
|
|
260
|
+
inserted = true;
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (name === LEGACY_OPENAI_MULTI_PROVIDER_ID || name === LEGACY_CHATGPT_PROVIDER_ID) {
|
|
264
|
+
if (mergedOpenAi && !inserted && !openai) {
|
|
265
|
+
nextProviders.push([OPENAI_CODEX_PROVIDER_ID, mergedOpenAi]);
|
|
266
|
+
inserted = true;
|
|
267
|
+
}
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
nextProviders.push([name, provider]);
|
|
271
|
+
}
|
|
272
|
+
if (mergedOpenAi && !inserted) nextProviders.push([OPENAI_CODEX_PROVIDER_ID, mergedOpenAi]);
|
|
273
|
+
projected.providers = Object.fromEntries(nextProviders);
|
|
274
|
+
|
|
275
|
+
if (
|
|
276
|
+
projected.defaultProvider === LEGACY_OPENAI_MULTI_PROVIDER_ID
|
|
277
|
+
|| projected.defaultProvider === LEGACY_CHATGPT_PROVIDER_ID
|
|
278
|
+
) projected.defaultProvider = OPENAI_CODEX_PROVIDER_ID;
|
|
279
|
+
|
|
280
|
+
rewriteLegacyOpenAiReferences(projected, warnings);
|
|
281
|
+
projected.openaiProviderTierVersion = OPENAI_PROVIDER_TIER_VERSION;
|
|
282
|
+
return { config: projected, changed: true, resolvedMode, warnings: [...new Set(warnings)] };
|
|
283
|
+
}
|