@bitkyc08/opencodex 2.7.43-preview.20260728 → 2.8.0
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.md +8 -1
- package/bin/ocx.mjs +47 -22
- package/gui/dist/assets/index-BDjpkcRN.js +67 -0
- package/gui/dist/assets/index-BHsKRFh9.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/AGENTS.md +28 -0
- package/src/adapters/anthropic.ts +15 -6
- package/src/adapters/cursor/discovery.ts +4 -1
- package/src/adapters/cursor/effort-map.ts +3 -0
- package/src/adapters/cursor/native-exec-shell.ts +18 -6
- package/src/adapters/cursor/protobuf-events.ts +24 -2
- package/src/adapters/cursor/protobuf-request.ts +1 -2
- package/src/adapters/cursor/tool-definitions.ts +68 -29
- package/src/adapters/google-wire-compiler.ts +4 -0
- package/src/adapters/google.ts +128 -2
- package/src/adapters/identity.ts +12 -2
- package/src/adapters/kiro.ts +64 -7
- package/src/adapters/mimo-free.ts +2 -0
- package/src/adapters/openai-responses.ts +246 -59
- package/src/claude/agents-inject.ts +5 -0
- package/src/claude/alias.ts +94 -14
- package/src/claude/inbound.ts +26 -9
- package/src/claude/outbound.ts +6 -3
- package/src/cli/account-auth.ts +1 -1
- package/src/cli/agent-driven.ts +37 -0
- package/src/cli/catalog-prewarm.ts +24 -0
- package/src/cli/claude.ts +35 -10
- package/src/cli/doctor.ts +71 -19
- package/src/cli/help.ts +42 -6
- package/src/cli/index.ts +93 -19
- package/src/cli/interactive-confirm.ts +133 -0
- package/src/cli/opencode.ts +701 -0
- package/src/cli/provider-runtime.ts +3 -0
- package/src/cli/provider.ts +31 -10
- package/src/cli/star-prompt.ts +79 -18
- package/src/cli/status.ts +47 -13
- package/src/cli/v2.ts +10 -1
- package/src/codex/account-id.ts +34 -0
- package/src/codex/account-lifecycle.ts +4 -1
- package/src/codex/account-namespace-match.ts +63 -0
- package/src/codex/account-namespaces.ts +149 -0
- package/src/codex/account-pause.ts +20 -0
- package/src/codex/account-store.ts +2 -0
- package/src/codex/account-usability.ts +6 -1
- package/src/codex/app-server-processes.ts +511 -0
- package/src/codex/auth-api.ts +293 -34
- package/src/codex/auth-collision.ts +2 -1
- package/src/codex/auth-context.ts +60 -17
- package/src/codex/catalog/bundled.ts +9 -2
- package/src/codex/catalog/parsing.ts +42 -2
- package/src/codex/catalog/provider-fetch.ts +264 -70
- package/src/codex/catalog/sync.ts +45 -8
- package/src/codex/catalog.ts +2 -2
- package/src/codex/features.ts +524 -5
- package/src/codex/history-provider.ts +145 -1
- package/src/codex/inject.ts +114 -14
- package/src/codex/main-account.ts +2 -8
- package/src/codex/pool-rotation.ts +186 -0
- package/src/codex/quota.ts +92 -2
- package/src/codex/routing.ts +695 -106
- package/src/codex/runtime.ts +10 -1
- package/src/codex/shim.ts +4 -1
- package/src/codex/subagent-defaults.ts +550 -0
- package/src/codex/subagent-model-fallback.ts +2 -0
- package/src/codex/sync.ts +3 -0
- package/src/config.ts +574 -25
- package/src/generated/jawcode-model-metadata.ts +12 -12
- package/src/github/star-state.ts +191 -0
- package/src/images/artifacts.ts +516 -0
- package/src/images/fulfill-video.ts +163 -0
- package/src/images/fulfill.ts +111 -0
- package/src/images/index.ts +4 -0
- package/src/images/loop.ts +789 -0
- package/src/images/plan.ts +133 -0
- package/src/images/synthetic-tool.ts +133 -0
- package/src/images/types.ts +41 -0
- package/src/images/xai-client.ts +141 -0
- package/src/images/xai-video-client.ts +163 -0
- package/src/lib/admin-secrets.ts +25 -0
- package/src/lib/bun-binary-validator.d.mts +3 -0
- package/src/lib/bun-binary-validator.mjs +18 -0
- package/src/lib/bun-runtime.ts +6 -20
- package/src/lib/config-ownership.ts +327 -0
- package/src/lib/crash-guard.ts +2 -0
- package/src/lib/destination-policy.ts +132 -7
- package/src/lib/pinned-http.ts +151 -0
- package/src/lib/process-control.ts +2 -2
- package/src/lib/provider-outbound.ts +167 -0
- package/src/lib/provider-url.ts +14 -0
- package/src/lib/proxy-env.ts +18 -0
- package/src/lib/shadow-call.ts +30 -0
- package/src/lib/test-home-guard.ts +90 -0
- package/src/lib/win-exec.ts +12 -2
- package/src/lib/windows-elevation.ts +81 -3
- package/src/lib/windows-secret-acl.ts +189 -12
- package/src/lib/winsw.ts +2 -0
- package/src/oauth/anthropic-routing.ts +570 -0
- package/src/oauth/health.ts +6 -0
- package/src/oauth/index.ts +310 -75
- package/src/oauth/key-providers.ts +38 -8
- package/src/oauth/kimi.ts +2 -0
- package/src/oauth/kiro-credentials.ts +373 -12
- package/src/oauth/kiro.ts +424 -43
- package/src/oauth/login-cli.ts +33 -6
- package/src/oauth/store.ts +56 -4
- package/src/oauth/types.ts +11 -0
- package/src/providers/alibaba-region-migration.ts +16 -3
- package/src/providers/antigravity-models.ts +3 -0
- package/src/providers/api-keys.ts +13 -6
- package/src/providers/derive.ts +8 -2
- package/src/providers/key-failover.ts +24 -4
- package/src/providers/model-discovery.ts +356 -0
- package/src/providers/quota.ts +233 -29
- package/src/providers/registry.ts +125 -3
- package/src/responses/parser.ts +11 -0
- package/src/responses/state.ts +22 -8
- package/src/responses/tool-groups.ts +19 -0
- package/src/router.ts +19 -7
- package/src/server/auth-cors.ts +114 -24
- package/src/server/claude-messages.ts +8 -1
- package/src/server/gui-static.ts +30 -6
- package/src/server/images.ts +303 -9
- package/src/server/index.ts +77 -9
- package/src/server/lifecycle.ts +25 -1
- package/src/server/live.ts +75 -25
- package/src/server/management/agent-settings-routes.ts +106 -8
- package/src/server/management/combo-routes.ts +7 -0
- package/src/server/management/config-routes.ts +22 -7
- package/src/server/management/context.ts +11 -1
- package/src/server/management/logs-usage-routes.ts +167 -3
- package/src/server/management/model-routes.ts +46 -13
- package/src/server/management/oauth-account-routes.ts +163 -17
- package/src/server/management/provider-routes.ts +73 -10
- package/src/server/management/shared.ts +2 -2
- package/src/server/management/sidebar-routes.ts +39 -0
- package/src/server/management/system-restart.ts +172 -0
- package/src/server/management/system-routes.ts +33 -10
- package/src/server/management-api.ts +5 -3
- package/src/server/management-auth.ts +216 -0
- package/src/server/proxy-liveness.ts +14 -3
- package/src/server/responses/compact.ts +21 -13
- package/src/server/responses/core.ts +614 -172
- package/src/server/responses/upstream-error.ts +48 -0
- package/src/server/responses-image-gen-repair.ts +118 -0
- package/src/server/responses-item-id-repair.ts +10 -85
- package/src/server/sse-payload-rewrite.ts +116 -0
- package/src/server/startup-action-control.ts +30 -14
- package/src/server/system-env.ts +28 -10
- package/src/service.ts +284 -19
- package/src/storage/cleanup-job.ts +57 -0
- package/src/storage/cleanup.ts +1504 -28
- package/src/storage/policy-job.ts +387 -0
- package/src/storage/policy-scheduler.ts +40 -0
- package/src/storage/policy-worker.ts +53 -0
- package/src/storage/policy.ts +522 -0
- package/src/storage/restore-job.ts +253 -0
- package/src/storage/restore-worker.ts +52 -0
- package/src/storage/storage-mutation-coordinator.ts +109 -0
- package/src/storage/worker-lifecycle.ts +81 -0
- package/src/tray/windows.ts +34 -4
- package/src/types.ts +107 -1
- package/src/update/badge.ts +72 -0
- package/src/update/index.ts +36 -18
- package/src/update/job.ts +111 -16
- package/src/update/npm-invocation.d.mts +23 -0
- package/src/update/npm-invocation.mjs +94 -0
- package/src/usage/debug.ts +2 -0
- package/src/usage/expected-prices.ts +6 -5
- package/src/usage/log.ts +12 -0
- package/src/web-search/loop.ts +57 -16
- package/gui/dist/assets/index-CjKFJHSC.js +0 -65
- package/gui/dist/assets/index-DfVGuN88.css +0 -1
|
@@ -9,13 +9,16 @@ import { isCodexAccountUsable } from "./account-usability";
|
|
|
9
9
|
import { reconcileMainCodexAccountRuntimeState } from "./account-lifecycle";
|
|
10
10
|
import { MAIN_CODEX_ACCOUNT_ID, getMainAccountToken } from "./main-account";
|
|
11
11
|
import {
|
|
12
|
-
|
|
12
|
+
codexQuotaScopeForModel,
|
|
13
|
+
getCodexQuotaHealthSnapshot,
|
|
13
14
|
releaseCodexQuotaProbeLease,
|
|
15
|
+
releaseCodexQuotaScopeProbeLease,
|
|
14
16
|
tryAcquireCodexQuotaProbeLease,
|
|
15
|
-
|
|
17
|
+
tryAcquireCodexQuotaScopeProbeLease,
|
|
18
|
+
pickAlternateCodexAccount,
|
|
16
19
|
resolveCodexAccountForThreadDetailed,
|
|
17
20
|
} from "./routing";
|
|
18
|
-
import type { CodexCooldownSource } from "./routing";
|
|
21
|
+
import type { CodexCooldownSource, CodexQuotaScope } from "./routing";
|
|
19
22
|
import { maskAccountId } from "../lib/privacy";
|
|
20
23
|
import { formatErrorResponse } from "../bridge";
|
|
21
24
|
import { getAccountQuota } from "./quota";
|
|
@@ -34,8 +37,12 @@ export type CodexAuthContext =
|
|
|
34
37
|
* Set when this request was admitted through an active quota cooldown as
|
|
35
38
|
* the account's single probe. Must be echoed into the upstream outcome so
|
|
36
39
|
* only this request can clear the cooldown (#433).
|
|
37
|
-
|
|
40
|
+
*/
|
|
38
41
|
probeLeaseId?: string;
|
|
42
|
+
/** Native model quota group selected for this request, when known. */
|
|
43
|
+
quotaScope?: CodexQuotaScope;
|
|
44
|
+
/** Scope that owns `probeLeaseId`, when it is a scoped recovery probe. */
|
|
45
|
+
probeQuotaScope?: CodexQuotaScope;
|
|
39
46
|
}
|
|
40
47
|
| {
|
|
41
48
|
// Main Codex account participating in rotation: token injected from ~/.codex/auth.json
|
|
@@ -46,6 +53,8 @@ export type CodexAuthContext =
|
|
|
46
53
|
chatgptAccountId: string;
|
|
47
54
|
/** See `pool.probeLeaseId`. */
|
|
48
55
|
probeLeaseId?: string;
|
|
56
|
+
quotaScope?: CodexQuotaScope;
|
|
57
|
+
probeQuotaScope?: CodexQuotaScope;
|
|
49
58
|
};
|
|
50
59
|
|
|
51
60
|
/** Probe lease carried by this context, when it holds one. */
|
|
@@ -53,13 +62,20 @@ export function codexProbeLeaseId(ctx: CodexAuthContext | undefined): string | u
|
|
|
53
62
|
return ctx?.kind === "pool" || ctx?.kind === "main-pool" ? ctx.probeLeaseId : undefined;
|
|
54
63
|
}
|
|
55
64
|
|
|
65
|
+
/** Scope of a lease carried by this context, when it probes a model-specific quota. */
|
|
66
|
+
export function codexProbeQuotaScope(ctx: CodexAuthContext | undefined): CodexQuotaScope | undefined {
|
|
67
|
+
return ctx?.kind === "pool" || ctx?.kind === "main-pool" ? ctx.probeQuotaScope : undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
56
70
|
/**
|
|
57
71
|
* Hand back a probe lease for a request that will not reach upstream. Safe to
|
|
58
72
|
* call with a context that holds no lease.
|
|
59
73
|
*/
|
|
60
74
|
export function releaseCodexAuthContextProbeLease(ctx: CodexAuthContext | undefined): void {
|
|
61
75
|
const leaseId = codexProbeLeaseId(ctx);
|
|
62
|
-
if (ctx
|
|
76
|
+
if (!ctx || ctx.kind === "main" || !leaseId) return;
|
|
77
|
+
if (ctx.probeQuotaScope) releaseCodexQuotaScopeProbeLease(ctx.accountId!, ctx.probeQuotaScope, leaseId);
|
|
78
|
+
else releaseCodexQuotaProbeLease(ctx.accountId!, leaseId);
|
|
63
79
|
}
|
|
64
80
|
|
|
65
81
|
export type OcxRuntimeProviderConfig = OcxProviderConfig & {
|
|
@@ -99,13 +115,20 @@ export class CodexAccountCooldownError extends Error {
|
|
|
99
115
|
accountId: string;
|
|
100
116
|
cooldownUntil: number;
|
|
101
117
|
cooldownSource?: CodexCooldownSource;
|
|
118
|
+
quotaScope?: CodexQuotaScope;
|
|
102
119
|
|
|
103
|
-
constructor(
|
|
120
|
+
constructor(
|
|
121
|
+
accountId: string,
|
|
122
|
+
cooldownUntil: number,
|
|
123
|
+
cooldownSource?: CodexCooldownSource,
|
|
124
|
+
quotaScope?: CodexQuotaScope,
|
|
125
|
+
) {
|
|
104
126
|
super("Selected Codex account is cooling down");
|
|
105
127
|
this.name = "CodexAccountCooldownError";
|
|
106
128
|
this.accountId = accountId;
|
|
107
129
|
this.cooldownUntil = cooldownUntil;
|
|
108
130
|
this.cooldownSource = cooldownSource;
|
|
131
|
+
this.quotaScope = quotaScope;
|
|
109
132
|
}
|
|
110
133
|
}
|
|
111
134
|
|
|
@@ -127,7 +150,12 @@ export function cooldownAccountLabel(accountId: string): string {
|
|
|
127
150
|
*/
|
|
128
151
|
export function cooldownErrorMessage(err: CodexAccountCooldownError): string {
|
|
129
152
|
const until = new Date(err.cooldownUntil).toISOString();
|
|
130
|
-
|
|
153
|
+
const scope = err.quotaScope === "spark"
|
|
154
|
+
? "Spark quota"
|
|
155
|
+
: err.quotaScope === "shared"
|
|
156
|
+
? "shared native quota"
|
|
157
|
+
: null;
|
|
158
|
+
return `Selected Codex account (${cooldownAccountLabel(err.accountId)})${scope ? ` ${scope} is` : " is"} cooling down until ${until}`
|
|
131
159
|
+ ` (source: ${err.cooldownSource ?? "default"}).`
|
|
132
160
|
+ ` Run 'ocx account list openai' to find the id, then`
|
|
133
161
|
+ ` 'ocx account clear-cooldown openai <id>' to lift it, or switch accounts with 'ocx account use openai <id>'.`;
|
|
@@ -157,6 +185,8 @@ export function shouldMarkAccountNeedsReauthForCodexAuthFailure(cause: unknown):
|
|
|
157
185
|
|
|
158
186
|
export interface ResolveCodexAuthContextOptions {
|
|
159
187
|
excludeAccountId?: string;
|
|
188
|
+
/** Final native model selected for this request, used to select its quota group. */
|
|
189
|
+
modelId?: string;
|
|
160
190
|
}
|
|
161
191
|
|
|
162
192
|
export async function resolveCodexAuthContext(
|
|
@@ -171,16 +201,17 @@ export async function resolveCodexAuthContext(
|
|
|
171
201
|
}
|
|
172
202
|
reconcileMainCodexAccountRuntimeState();
|
|
173
203
|
const threadId = headers.get("x-codex-parent-thread-id");
|
|
204
|
+
const quotaScope = codexQuotaScopeForModel(options.modelId);
|
|
174
205
|
const resolution = options.excludeAccountId
|
|
175
206
|
? (() => {
|
|
176
|
-
const accountId =
|
|
207
|
+
const accountId = pickAlternateCodexAccount(config, options.excludeAccountId!, Date.now(), quotaScope);
|
|
177
208
|
return accountId
|
|
178
209
|
? { status: "selected" as const, accountId }
|
|
179
210
|
: { status: "none" as const };
|
|
180
211
|
})()
|
|
181
|
-
: resolveCodexAccountForThreadDetailed(threadId, config);
|
|
212
|
+
: resolveCodexAccountForThreadDetailed(threadId, config, Date.now(), quotaScope);
|
|
182
213
|
if (resolution.status === "expired") throw new CodexThreadAffinityExpiredError(resolution.accountId);
|
|
183
|
-
|
|
214
|
+
let accountId = resolution.status === "selected" ? resolution.accountId : null;
|
|
184
215
|
if (!accountId) throw new CodexPoolAuthenticationError();
|
|
185
216
|
// Lazy prime: if the selected account has no quota yet, the pool is likely
|
|
186
217
|
// unprimed (dashboard never opened, or startup prime was blocked). Kick a
|
|
@@ -194,15 +225,21 @@ export async function resolveCodexAuthContext(
|
|
|
194
225
|
}
|
|
195
226
|
// Snapshot (not just the deadline) so a refused request can report WHY it is cooled:
|
|
196
227
|
// a literal Retry-After reads very differently to a user than a reset-derived guess.
|
|
197
|
-
const cooldown =
|
|
228
|
+
const cooldown = getCodexQuotaHealthSnapshot(accountId, quotaScope);
|
|
198
229
|
const cooldownUntil = cooldown?.cooldownUntil;
|
|
199
230
|
// A cooled-down account never sends traffic, so upstream recovery can never be
|
|
200
231
|
// observed and the cooldown outlives the real limit. Admit one probe per
|
|
201
232
|
// interval; its outcome decides whether the cooldown ends (#433).
|
|
202
233
|
let probeLeaseId: string | undefined;
|
|
234
|
+
let probeQuotaScope: CodexQuotaScope | undefined;
|
|
203
235
|
if (cooldownUntil) {
|
|
204
|
-
|
|
205
|
-
|
|
236
|
+
probeQuotaScope = cooldown?.quotaScope;
|
|
237
|
+
probeLeaseId = probeQuotaScope
|
|
238
|
+
? tryAcquireCodexQuotaScopeProbeLease(accountId, probeQuotaScope) ?? undefined
|
|
239
|
+
: tryAcquireCodexQuotaProbeLease(accountId) ?? undefined;
|
|
240
|
+
if (!probeLeaseId) {
|
|
241
|
+
throw new CodexAccountCooldownError(accountId, cooldownUntil, cooldown?.cooldownSource, cooldown?.quotaScope);
|
|
242
|
+
}
|
|
206
243
|
}
|
|
207
244
|
|
|
208
245
|
if (accountId === MAIN_CODEX_ACCOUNT_ID) {
|
|
@@ -210,7 +247,8 @@ export async function resolveCodexAuthContext(
|
|
|
210
247
|
const token = getMainAccountToken();
|
|
211
248
|
if (!token) {
|
|
212
249
|
// Nothing will reach upstream, so give the probe back instead of burning it.
|
|
213
|
-
if (probeLeaseId)
|
|
250
|
+
if (probeLeaseId && probeQuotaScope) releaseCodexQuotaScopeProbeLease(accountId, probeQuotaScope, probeLeaseId);
|
|
251
|
+
else if (probeLeaseId) releaseCodexQuotaProbeLease(accountId, probeLeaseId);
|
|
214
252
|
throw new CodexPoolAuthenticationError();
|
|
215
253
|
}
|
|
216
254
|
return {
|
|
@@ -218,7 +256,9 @@ export async function resolveCodexAuthContext(
|
|
|
218
256
|
accountId,
|
|
219
257
|
accessToken: token.accessToken,
|
|
220
258
|
chatgptAccountId: token.chatgptAccountId,
|
|
259
|
+
...(quotaScope ? { quotaScope } : {}),
|
|
221
260
|
...(probeLeaseId ? { probeLeaseId } : {}),
|
|
261
|
+
...(probeQuotaScope ? { probeQuotaScope } : {}),
|
|
222
262
|
};
|
|
223
263
|
}
|
|
224
264
|
|
|
@@ -230,10 +270,13 @@ export async function resolveCodexAuthContext(
|
|
|
230
270
|
generation: token.generation,
|
|
231
271
|
accessToken: token.accessToken,
|
|
232
272
|
chatgptAccountId: token.chatgptAccountId,
|
|
273
|
+
...(quotaScope ? { quotaScope } : {}),
|
|
233
274
|
...(probeLeaseId ? { probeLeaseId } : {}),
|
|
275
|
+
...(probeQuotaScope ? { probeQuotaScope } : {}),
|
|
234
276
|
};
|
|
235
277
|
} catch (cause) {
|
|
236
|
-
if (probeLeaseId)
|
|
278
|
+
if (probeLeaseId && probeQuotaScope) releaseCodexQuotaScopeProbeLease(accountId, probeQuotaScope, probeLeaseId);
|
|
279
|
+
else if (probeLeaseId) releaseCodexQuotaProbeLease(accountId, probeLeaseId);
|
|
237
280
|
if (shouldMarkAccountNeedsReauthForCodexAuthFailure(cause)) {
|
|
238
281
|
markAccountNeedsReauth(accountId);
|
|
239
282
|
}
|
|
@@ -245,9 +288,9 @@ export function assertCodexAuthContextNotCooled(ctx: CodexAuthContext | undefine
|
|
|
245
288
|
if (ctx?.kind !== "pool" && ctx?.kind !== "main-pool") return;
|
|
246
289
|
// A context holding the probe lease was deliberately admitted through the cooldown.
|
|
247
290
|
if (ctx.probeLeaseId) return;
|
|
248
|
-
const cooldown =
|
|
291
|
+
const cooldown = getCodexQuotaHealthSnapshot(ctx.accountId, ctx.quotaScope);
|
|
249
292
|
if (cooldown?.cooldownUntil) {
|
|
250
|
-
throw new CodexAccountCooldownError(ctx.accountId, cooldown.cooldownUntil, cooldown.cooldownSource);
|
|
293
|
+
throw new CodexAccountCooldownError(ctx.accountId, cooldown.cooldownUntil, cooldown.cooldownSource, cooldown.quotaScope);
|
|
251
294
|
}
|
|
252
295
|
}
|
|
253
296
|
|
|
@@ -151,14 +151,21 @@ export function loadBundledCodexCatalog(deps: BundledCatalogDeps = {}): RawCatal
|
|
|
151
151
|
let cacheKey: string | null = null;
|
|
152
152
|
const candidates = deps.commandCandidates?.() ?? (() => {
|
|
153
153
|
const resolved = resolveAndPersistCodexRuntime({
|
|
154
|
-
execFileSync
|
|
154
|
+
// Forward an INJECTED execFileSync only. Passing the real one unconditionally made
|
|
155
|
+
// resolveCacheKey() bail out (it refuses to memoize injected-dep resolves), so every
|
|
156
|
+
// catalog read re-ran the ~1s `codex --version` probe even on a warm cache hit.
|
|
157
|
+
...(deps.execFileSync ? { execFileSync: deps.execFileSync } : {}),
|
|
155
158
|
configDir: deps.configDir,
|
|
156
159
|
env: deps.env,
|
|
157
160
|
platform: deps.platform,
|
|
158
161
|
existsSync: deps.existsSync,
|
|
159
162
|
readFileSync: deps.readFileSync,
|
|
160
163
|
now: deps.now,
|
|
161
|
-
|
|
164
|
+
// Catalog loading only consumes `resolved.runtime.command`, never `newerAvailable`.
|
|
165
|
+
// Full PATH discovery probes every candidate launcher (100+ on a dev machine, ~1.2s),
|
|
166
|
+
// which alone can exceed the 3s budget `ocx claude` allows /api/claude-code. Priority
|
|
167
|
+
// selection is identical either way; callers wanting discovery diagnostics opt in.
|
|
168
|
+
discoverAlternatives: deps.discoverAlternatives ?? false,
|
|
162
169
|
});
|
|
163
170
|
if (useCache) {
|
|
164
171
|
cacheKey = [
|
|
@@ -110,6 +110,8 @@ export interface CatalogModel {
|
|
|
110
110
|
/** Whether Codex may send Responses text.verbosity for this routed model. */
|
|
111
111
|
supportsVerbosity?: boolean;
|
|
112
112
|
supportsReasoningSummaries?: boolean;
|
|
113
|
+
/** Normalized upstream capability names retained for management/API consumers (#485 follow-up). */
|
|
114
|
+
capabilities?: string[];
|
|
113
115
|
}
|
|
114
116
|
|
|
115
117
|
export type RawEntry = Record<string, unknown>;
|
|
@@ -141,9 +143,24 @@ export function isMediaGenerationModelId(id: string): boolean {
|
|
|
141
143
|
return MEDIA_GEN_ID_RE.test(id);
|
|
142
144
|
}
|
|
143
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Gemini image-capable chat models produce inline images within text responses
|
|
148
|
+
* via the Responses API. Explicit allowlist only — a broad `/gemini/ && /image/`
|
|
149
|
+
* heuristic resurrects standalone media-gen IDs (e.g. gemini-3-pro-image).
|
|
150
|
+
*/
|
|
151
|
+
const GEMINI_IMAGE_CHAT_MODEL_IDS = new Set([
|
|
152
|
+
"gemini-3.1-flash-image",
|
|
153
|
+
"gemini-2.0-flash-preview-image-generation",
|
|
154
|
+
"gemini-3-pro-image-preview",
|
|
155
|
+
]);
|
|
156
|
+
|
|
157
|
+
function isGeminiImageChatModel(id: string): boolean {
|
|
158
|
+
return GEMINI_IMAGE_CHAT_MODEL_IDS.has(id);
|
|
159
|
+
}
|
|
160
|
+
|
|
144
161
|
export function shouldExposeRoutedModel(model: CatalogModel): boolean {
|
|
145
162
|
if (isRoutedModelCompatibilityExcluded(`${model.provider}/${model.id}`)) return false;
|
|
146
|
-
if (model.
|
|
163
|
+
if (isGeminiImageChatModel(model.id)) return true;
|
|
147
164
|
return !isMediaGenerationModelId(model.id);
|
|
148
165
|
}
|
|
149
166
|
|
|
@@ -257,6 +274,16 @@ export function ensureStrictCatalogFields(
|
|
|
257
274
|
if (!Array.isArray(entry.input_modalities) && !options.preserveExactInputModalities) {
|
|
258
275
|
entry.input_modalities = ["text"];
|
|
259
276
|
}
|
|
277
|
+
// Codex parses `input_modalities` as a closed enum. One out-of-enum value (zenmux advertises
|
|
278
|
+
// "video") makes its config loader reject the entire catalog, which takes down plugins, apps and
|
|
279
|
+
// MCP servers — not just that model. Normalize at the single point every entry passes through,
|
|
280
|
+
// because provider metadata, jawcode metadata and effort sync each write this field.
|
|
281
|
+
if (Array.isArray(entry.input_modalities)) {
|
|
282
|
+
const accepted = entry.input_modalities.filter(value =>
|
|
283
|
+
value === "text" || value === "image" || value === "audio");
|
|
284
|
+
// Never leave it empty: an entry with no modality at all is worse than a text-only one.
|
|
285
|
+
entry.input_modalities = accepted.length > 0 ? accepted : ["text"];
|
|
286
|
+
}
|
|
260
287
|
const contextWindow = typeof entry.context_window === "number" && entry.context_window > 0 ? entry.context_window : 128000;
|
|
261
288
|
entry.context_window = contextWindow;
|
|
262
289
|
if (
|
|
@@ -273,7 +300,18 @@ export function ensureStrictCatalogFields(
|
|
|
273
300
|
|
|
274
301
|
export type MultiAgentMode = "v1" | "default" | "v2";
|
|
275
302
|
|
|
276
|
-
|
|
303
|
+
/**
|
|
304
|
+
* @param v2FeatureEnabled When the native multi_agent_v2 feature is on, "default"
|
|
305
|
+
* mode stamps unpinned entries as "v2" instead of deleting the key. The native
|
|
306
|
+
* binary validates spawn_agent models against THIS catalog with its own
|
|
307
|
+
* `multi_agent_version == Some(V2)` test (codex-rs multi_agents_common.rs), so an
|
|
308
|
+
* absent pin means a clean refusal at spawn time — exactly the cross-provider
|
|
309
|
+
* spawns opencodex exists to enable (option B, devlog
|
|
310
|
+
* 260730_codex_rs_upstream_v2_live_handoff/060). Upstream pins are always
|
|
311
|
+
* preserved: a genuine "v1" pin is a real capability statement and stays excluded.
|
|
312
|
+
* With the feature off the output is byte-identical to the historical behavior.
|
|
313
|
+
*/
|
|
314
|
+
export function applyMultiAgentMode(entries: RawEntry[], mode: MultiAgentMode, v2FeatureEnabled = false): RawEntry[] {
|
|
277
315
|
if (mode === "default") {
|
|
278
316
|
// Restore upstream defaults: clear any stale forced multi_agent_version and
|
|
279
317
|
// re-apply upstream pins from the snapshot for native entries that have one.
|
|
@@ -283,6 +321,8 @@ export function applyMultiAgentMode(entries: RawEntry[], mode: MultiAgentMode):
|
|
|
283
321
|
const upstreamPin = upstream?.multi_agent_version;
|
|
284
322
|
if (typeof upstreamPin === "string") {
|
|
285
323
|
entry.multi_agent_version = upstreamPin;
|
|
324
|
+
} else if (v2FeatureEnabled) {
|
|
325
|
+
entry.multi_agent_version = "v2";
|
|
286
326
|
} else {
|
|
287
327
|
delete entry.multi_agent_version;
|
|
288
328
|
}
|