@iislee/opencodex 2.31.0 → 2.35.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 +3 -3
- package/bin/ocx.mjs +99 -70
- package/gui/dist/assets/index-Be1sDpWw.css +1 -0
- package/gui/dist/assets/index-D4OKgUV4.js +114 -0
- package/gui/dist/index.html +2 -2
- package/package.json +3 -1
- package/src/adapters/anthropic.ts +104 -15
- package/src/adapters/base.ts +29 -1
- package/src/adapters/command-code.ts +42 -6
- package/src/adapters/cursor/call-id.ts +44 -0
- package/src/adapters/cursor/catalog.ts +541 -0
- package/src/adapters/cursor/checkpoint-store.ts +15 -10
- package/src/adapters/cursor/cursor-errors.ts +15 -0
- package/src/adapters/cursor/discovery.ts +69 -18
- package/src/adapters/cursor/effort-map.ts +79 -1
- package/src/adapters/cursor/envelope-echo.ts +290 -0
- package/src/adapters/cursor/live-models.ts +7 -2
- package/src/adapters/cursor/live-transport.ts +14 -0
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-desktop.ts +23 -0
- package/src/adapters/cursor/native-exec-fs.ts +2 -2
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +5 -2
- package/src/adapters/cursor/native-exec.ts +20 -0
- package/src/adapters/cursor/protobuf-request.ts +68 -14
- package/src/adapters/cursor/request-builder.ts +40 -10
- package/src/adapters/cursor/tool-definitions.ts +14 -4
- package/src/adapters/cursor/tool-result-normalize.ts +28 -5
- package/src/adapters/cursor/types.ts +20 -0
- package/src/adapters/cursor.ts +209 -38
- package/src/adapters/exec-tool-result-normalize.ts +99 -0
- package/src/adapters/google-antigravity-replay.ts +173 -25
- package/src/adapters/google-antigravity-wire.ts +5 -0
- package/src/adapters/google-errors.ts +44 -12
- package/src/adapters/google.ts +136 -24
- package/src/adapters/image.ts +1 -1
- package/src/adapters/kiro-constants.ts +27 -0
- package/src/adapters/kiro-tools.ts +55 -16
- package/src/adapters/kiro.ts +182 -23
- package/src/adapters/openai-chat.ts +300 -242
- package/src/adapters/openai-responses.ts +353 -27
- package/src/adapters/run-turn-queue.ts +36 -1
- package/src/adapters/tool-catalog-nudge.ts +4 -3
- package/src/adapters/xai-tool-schema.ts +436 -0
- package/src/adapters/xai-web-search.ts +17 -16
- package/src/bridge.ts +90 -37
- package/src/chat/inbound.ts +14 -1
- package/src/chat/outbound.ts +15 -7
- package/src/claude/agents-inject.ts +8 -1
- package/src/claude/context-windows.ts +16 -9
- package/src/claude/outbound.ts +24 -11
- package/src/cli/access.ts +46 -3
- package/src/cli/account-api.ts +116 -19
- package/src/cli/account-extended.ts +262 -29
- package/src/cli/account-main.ts +12 -12
- package/src/cli/account.ts +69 -15
- package/src/cli/agent.ts +8 -1
- package/src/cli/alias.ts +66 -0
- package/src/cli/capabilities-command.ts +94 -0
- package/src/cli/capabilities.ts +496 -0
- package/src/cli/claude-desktop.ts +31 -11
- package/src/cli/claude.ts +26 -1
- package/src/cli/dispatch.ts +256 -28
- package/src/cli/doctor.ts +126 -14
- package/src/cli/help.ts +17 -6
- package/src/cli/index.ts +39 -9
- package/src/cli/init.ts +1 -0
- package/src/cli/inspect.ts +230 -0
- package/src/cli/models-runtime.ts +95 -0
- package/src/cli/models.ts +26 -10
- package/src/cli/observe.ts +31 -8
- package/src/cli/provider-runtime.ts +16 -2
- package/src/cli/provider.ts +2 -1
- package/src/cli/registry.ts +49 -10
- package/src/cli/runtime-api.ts +51 -7
- package/src/cli/status.ts +41 -1
- package/src/cli/storage.ts +234 -0
- package/src/cli/system-command.ts +16 -0
- package/src/cli/system-restart-client.ts +1 -1
- package/src/cli/usage-report.ts +184 -0
- package/src/cli/v2.ts +34 -10
- package/src/cli/version-skew.ts +46 -0
- package/src/clients/config-export.ts +85 -3
- package/src/clients/effective-status.ts +30 -2
- package/src/clients/probes/cline.ts +81 -0
- package/src/codex/account-label.ts +21 -0
- package/src/codex/account-pause.ts +2 -1
- package/src/codex/account-priority.ts +3 -2
- package/src/codex/app-server-processes.ts +83 -7
- package/src/codex/auth-api.ts +50 -10
- package/src/codex/auth-context.ts +21 -18
- package/src/codex/autostart-health.ts +16 -0
- package/src/codex/catalog/aggregation.ts +18 -0
- package/src/codex/catalog/effort.ts +18 -3
- package/src/codex/catalog/metadata.ts +27 -1
- package/src/codex/catalog/native-models.ts +5 -2
- package/src/codex/catalog/parsing.ts +72 -27
- package/src/codex/catalog/provider-fetch.ts +187 -31
- package/src/codex/catalog/sync.ts +131 -5
- package/src/codex/catalog.ts +1 -1
- package/src/codex/codex-write-lock.ts +3 -1
- package/src/codex/convergence-types.ts +1 -1
- package/src/codex/convergence.ts +27 -2
- package/src/codex/desired-state.ts +2 -2
- package/src/codex/desktop-app-restart.ts +355 -0
- package/src/codex/history-job.ts +32 -3
- package/src/codex/history-manifest.ts +112 -0
- package/src/codex/history-migration-guardian.ts +5 -5
- package/src/codex/history-provider.ts +825 -247
- package/src/codex/history-worker.ts +8 -5
- package/src/codex/inject-coordination.ts +83 -0
- package/src/codex/inject.ts +63 -22
- package/src/codex/injected-marker.ts +1 -1
- package/src/codex/internal/history-writer.ts +4 -3
- package/src/codex/log-guard/inspect.ts +22 -4
- package/src/codex/model-entitlements.ts +9 -2
- package/src/codex/native-profile-startup.ts +157 -27
- package/src/codex/native-residue.ts +26 -33
- package/src/codex/prompt-layers.ts +371 -25
- package/src/codex/prompt-text-probe.ts +238 -0
- package/src/codex/quota.ts +91 -10
- package/src/codex/routing.ts +9 -0
- package/src/codex/shim.ts +56 -3
- package/src/codex/subagent-model-fallback.ts +104 -25
- package/src/codex/transition-state.ts +116 -8
- package/src/combos/failover.ts +27 -0
- package/src/combos/types.ts +10 -0
- package/src/compatibility/index.ts +26 -0
- package/src/compatibility/manifest.ts +253 -0
- package/src/compatibility/openai-responses.ts +121 -0
- package/src/config/atomic-write.ts +219 -0
- package/src/config/paths.ts +40 -0
- package/src/config/process-state.ts +309 -0
- package/src/config/provider-validation.ts +177 -0
- package/src/config/rebase-provenance.ts +68 -0
- package/src/config.ts +206 -824
- package/src/generated/compatibility-version.json +373 -213
- package/src/images/loop.ts +5 -4
- package/src/images/plan.ts +5 -4
- package/src/integrations/ownership-policy.ts +160 -0
- package/src/integrations/ownership.ts +46 -2
- package/src/integrations/registry.ts +7 -0
- package/src/integrations/state.ts +79 -7
- package/src/integrations/writer.ts +26 -2
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +1 -1
- package/src/lab/fabric/producer-child.ts +1 -1
- package/src/lib/admin-secrets.ts +24 -0
- package/src/lib/bounded-body.ts +14 -2
- package/src/lib/config-ownership.ts +20 -0
- package/src/lib/errors.ts +36 -3
- package/src/lib/package-tree-integrity.ts +101 -0
- package/src/lib/process-control.ts +2 -1
- package/src/lib/service-secrets.ts +15 -0
- package/src/lib/tool-argument-integers.ts +56 -5
- package/src/oauth/callback-server.ts +22 -2
- package/src/oauth/cline.ts +203 -0
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/health.ts +1 -1
- package/src/oauth/index.ts +65 -6
- package/src/oauth/key-providers.ts +10 -1
- package/src/oauth/kimi.ts +9 -1
- package/src/oauth/kiro.ts +45 -0
- package/src/oauth/local-token-detect.ts +2 -0
- package/src/oauth/open-browser-choice.ts +26 -0
- package/src/oauth/store.ts +14 -5
- package/src/oauth/types.ts +15 -0
- package/src/providers/auto-compact-budget.ts +65 -0
- package/src/providers/command-code-efforts.ts +38 -6
- package/src/providers/context-cap.ts +4 -3
- package/src/providers/default-aliases.ts +65 -0
- package/src/providers/derive.ts +28 -0
- package/src/providers/fastwire.ts +7 -1
- package/src/providers/label.ts +34 -1
- package/src/providers/model-presets.ts +119 -0
- package/src/providers/new-model-policy.ts +146 -0
- package/src/providers/provider-id-rewrite.ts +2 -1
- package/src/providers/quota.ts +65 -7
- package/src/providers/registry.ts +184 -69
- package/src/providers/slug-codec.ts +52 -0
- package/src/providers/xai-transport.ts +21 -0
- package/src/reasoning-effort.ts +19 -2
- package/src/responses/apply-patch-envelope.ts +63 -0
- package/src/responses/code-mode-helper-compat.ts +50 -0
- package/src/responses/custom-tool-compat.ts +157 -39
- package/src/responses/namespace-tool-compat.ts +84 -4
- package/src/responses/parser.ts +7 -2
- package/src/responses/reasoning-replay-cache.ts +81 -3
- package/src/responses/schema.ts +5 -1
- package/src/responses/state.ts +94 -4
- package/src/responses/thought-signature-replay.ts +17 -0
- package/src/responses/turn-termination.ts +107 -0
- package/src/router.ts +40 -2
- package/src/routing/analytics.ts +1 -0
- package/src/routing/quota.ts +10 -0
- package/src/server/auth-cors.ts +27 -7
- package/src/server/chat-completions.ts +24 -16
- package/src/server/chat-native-sse.ts +3 -3
- package/src/server/chat-native.ts +30 -11
- package/src/server/claude-messages.ts +1 -1
- package/src/server/index.ts +179 -32
- package/src/server/lifecycle.ts +52 -1
- package/src/server/local-management-read-client.ts +1 -1
- package/src/server/local-provider-reload-client.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +45 -23
- package/src/server/management/codex-prompt-routes.ts +570 -0
- package/src/server/management/combo-routes.ts +2 -1
- package/src/server/management/config-routes.ts +59 -12
- package/src/server/management/context.ts +10 -1
- package/src/server/management/logs-usage-routes.ts +30 -24
- package/src/server/management/model-routes.ts +274 -4
- package/src/server/management/model-rows.ts +4 -0
- package/src/server/management/native-integration-routes.ts +2 -1
- package/src/server/management/oauth-account-routes.ts +10 -4
- package/src/server/management/provider-capability-config.ts +1 -1
- package/src/server/management/provider-routes.ts +81 -11
- package/src/server/management/route-registry.ts +332 -0
- package/src/server/management/routing-profile-routes.ts +2 -2
- package/src/server/management/system-restart.ts +1 -1
- package/src/server/management-api.ts +18 -16
- package/src/server/port-reclaim.ts +1 -1
- package/src/server/proxy-liveness.ts +29 -5
- package/src/server/relay-eager.ts +147 -20
- package/src/server/relay.ts +251 -19
- package/src/server/request-log-conversation.ts +51 -0
- package/src/server/request-log.ts +70 -16
- package/src/server/responses/codex-auth-error.ts +55 -0
- package/src/server/responses/collaboration.ts +37 -3
- package/src/server/responses/combo-stream-preflight.ts +178 -0
- package/src/server/responses/compact.ts +6 -21
- package/src/server/responses/core.ts +810 -208
- package/src/server/responses/empty-completion-guard.ts +63 -6
- package/src/server/responses/fetch-helpers.ts +17 -102
- package/src/server/responses/input-admission.ts +3 -1
- package/src/server/responses/passthrough-error.ts +33 -9
- package/src/server/responses/responses-field-backfill.ts +105 -13
- package/src/server/responses/ws-upstream.ts +75 -2
- package/src/server/responses-custom-tool-repair.ts +87 -6
- package/src/server/responses-terminal-repair.ts +25 -4
- package/src/server/responses-undeclared-tool-guard.ts +241 -18
- package/src/server/sse-frame-buffer.ts +31 -4
- package/src/server/ws-bridge.ts +14 -2
- package/src/service.ts +42 -4
- package/src/storage/policy-job.ts +14 -4
- package/src/storage/policy.ts +88 -23
- package/src/types/config.ts +66 -0
- package/src/types/provider.ts +69 -0
- package/src/types/request.ts +17 -4
- package/src/types/tools.ts +116 -11
- package/src/types.ts +3 -1
- package/src/update/index.ts +5 -4
- package/src/update/job.ts +3 -1
- package/src/update/transactional-install.mjs +8 -1
- package/src/usage/expected-prices.ts +43 -24
- package/src/usage/log.ts +63 -12
- package/src/usage/summary.ts +211 -8
- package/src/vision/describe.ts +18 -13
- package/src/web-search/exa-executor.ts +40 -9
- package/src/web-search/executor.ts +10 -3
- package/src/web-search/loop.ts +5 -4
- package/gui/dist/assets/index-BF38heuV.js +0 -104
- package/gui/dist/assets/index-DMiI18Kv.css +0 -1
|
@@ -15,12 +15,12 @@ import { CODEX_HOME, getCodexHome } from "./paths";
|
|
|
15
15
|
import { CODEX_UNKNOWN_USAGE_SCORE, getAccountQuota } from "./quota";
|
|
16
16
|
import {
|
|
17
17
|
canAcquireCodexQuotaProbeLease,
|
|
18
|
+
canAcquireCodexQuotaScopeProbeLease,
|
|
18
19
|
codexQuotaScopeForModel,
|
|
19
20
|
computeCodexUsageScore,
|
|
20
21
|
getCodexQuotaHealthSnapshot,
|
|
21
22
|
getEffectiveActiveCodexAccountId,
|
|
22
23
|
getPoolAccountPlan,
|
|
23
|
-
isCodexAccountInCooldown,
|
|
24
24
|
} from "./routing";
|
|
25
25
|
import {
|
|
26
26
|
isCodexAccountUsable,
|
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
import { routeModel, type RouteResult } from "../router";
|
|
39
39
|
import { sweepExpiredOnWrite } from "../lib/state-store-sweeper";
|
|
40
40
|
import { codexAccountNamespaceForModel } from "./account-namespace-match";
|
|
41
|
+
import { ACCOUNT_GATED_NATIVE_OPENAI_MODELS } from "./catalog/native-models";
|
|
41
42
|
import {
|
|
42
43
|
getUpstreamHostHealth,
|
|
43
44
|
normalizeUpstreamHostCircuitThreshold,
|
|
@@ -48,6 +49,15 @@ export const DEFAULT_SUBAGENT_MODEL_FALLBACK_POLL_MS = 60_000;
|
|
|
48
49
|
const CODEX_FORWARD_ORIGIN = new URL(CODEX_FORWARD_BASE_URL).origin.toLowerCase();
|
|
49
50
|
|
|
50
51
|
type SubagentQuotaPrimeFn = (config: OcxConfig, reason: string) => Promise<void>;
|
|
52
|
+
/** Side-effect-free Pool account preview for one resolved fallback candidate. */
|
|
53
|
+
export type SubagentPoolAccountPreview = (
|
|
54
|
+
modelId: string | undefined,
|
|
55
|
+
now: number,
|
|
56
|
+
modelEligibleAccountIds?: ReadonlySet<string>,
|
|
57
|
+
) => string | null;
|
|
58
|
+
export type SubagentModelEligibleAccountIds = (
|
|
59
|
+
modelId: string | undefined,
|
|
60
|
+
) => ReadonlySet<string> | undefined;
|
|
51
61
|
let subagentQuotaPrimeForTests: SubagentQuotaPrimeFn | null = null;
|
|
52
62
|
let quotaPrimeInFlight: Promise<void> | null = null;
|
|
53
63
|
|
|
@@ -177,8 +187,15 @@ function resolveRouteFallbackAccountId(
|
|
|
177
187
|
route: RouteResult | null,
|
|
178
188
|
config: OcxConfig,
|
|
179
189
|
accountId?: string | null,
|
|
190
|
+
now = Date.now(),
|
|
191
|
+
poolAccountPreview?: SubagentPoolAccountPreview,
|
|
192
|
+
modelEligibleAccountIds?: ReadonlySet<string>,
|
|
180
193
|
): string | null {
|
|
181
|
-
|
|
194
|
+
if (route?.codexAccountId !== undefined) return route.codexAccountId;
|
|
195
|
+
if (route && isPoolCodexRoute(route) && poolAccountPreview) {
|
|
196
|
+
return poolAccountPreview(route.modelId, now, modelEligibleAccountIds);
|
|
197
|
+
}
|
|
198
|
+
return resolvePoolFallbackAccountId(config, accountId);
|
|
182
199
|
}
|
|
183
200
|
|
|
184
201
|
function isRoutableFallbackModel(model: string, config: OcxConfig): boolean {
|
|
@@ -237,33 +254,53 @@ export function isSubagentModelUnavailable(
|
|
|
237
254
|
accountId?: string | null,
|
|
238
255
|
now = Date.now(),
|
|
239
256
|
accountUsabilityOptions?: CodexAccountUsabilityOptions,
|
|
257
|
+
poolAccountPreview?: SubagentPoolAccountPreview,
|
|
258
|
+
modelEligibleAccountIdsForModel?: SubagentModelEligibleAccountIds,
|
|
240
259
|
): boolean {
|
|
241
260
|
if (isDisabledFallbackModel(model, config)) return true;
|
|
242
261
|
if (!isRoutableFallbackModel(model, config)) return true;
|
|
243
262
|
const route = tryRouteFallbackModel(config, model);
|
|
244
263
|
if (!route || route.provider.disabled === true) return true;
|
|
245
|
-
|
|
264
|
+
const modelEligibleAccountIds = modelEligibleAccountIdsForModel?.(route.modelId);
|
|
265
|
+
const candidateAccountUsabilityOptions = modelEligibleAccountIds !== undefined
|
|
266
|
+
? {
|
|
267
|
+
...accountUsabilityOptions,
|
|
268
|
+
modelEligibleAccountIds,
|
|
269
|
+
}
|
|
270
|
+
: accountUsabilityOptions;
|
|
271
|
+
const resolvedAccountId = resolveRouteFallbackAccountId(
|
|
272
|
+
route,
|
|
273
|
+
config,
|
|
274
|
+
accountId,
|
|
275
|
+
now,
|
|
276
|
+
poolAccountPreview,
|
|
277
|
+
candidateAccountUsabilityOptions?.modelEligibleAccountIds,
|
|
278
|
+
);
|
|
279
|
+
if (isModelHealthBlocked(model, config, resolvedAccountId, now)) return true;
|
|
246
280
|
if (!isPoolCodexRoute(route)) return false;
|
|
247
281
|
|
|
248
282
|
// Pool candidates need a usable account. Derive requirement from the resolved
|
|
249
283
|
// route (canonical openai defaults to pool even when codexAccountMode is omitted).
|
|
250
|
-
const resolvedAccountId = resolveRouteFallbackAccountId(route, config, accountId);
|
|
251
284
|
if (!resolvedAccountId) return true;
|
|
252
285
|
if (isCodexAccountPaused(config, resolvedAccountId)) return true;
|
|
253
|
-
if (!isCodexAccountUsable(config, resolvedAccountId,
|
|
286
|
+
if (!isCodexAccountUsable(config, resolvedAccountId, candidateAccountUsabilityOptions)) return true;
|
|
254
287
|
if (route.codexAccountId !== undefined) {
|
|
255
288
|
// An account-qualified route is pinned and cannot consume Pool's recovery-probe
|
|
256
289
|
// escape hatch. Honor both account-wide and model-scoped cooldowns so fallback
|
|
257
290
|
// advances instead of selecting a candidate that exact auth will reject.
|
|
258
291
|
const quotaScope = codexQuotaScopeForModel(route.modelId);
|
|
259
292
|
if (getCodexQuotaHealthSnapshot(resolvedAccountId, quotaScope, now) !== null) return true;
|
|
260
|
-
} else
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
293
|
+
} else {
|
|
294
|
+
const quotaScope = codexQuotaScopeForModel(route.modelId);
|
|
295
|
+
const cooldown = getCodexQuotaHealthSnapshot(resolvedAccountId, quotaScope, now);
|
|
296
|
+
if (cooldown !== null) {
|
|
297
|
+
const probeAvailable = cooldown.quotaScope
|
|
298
|
+
? canAcquireCodexQuotaScopeProbeLease(resolvedAccountId, cooldown.quotaScope, now)
|
|
299
|
+
: canAcquireCodexQuotaProbeLease(resolvedAccountId, now);
|
|
300
|
+
if (!probeAvailable) return true;
|
|
301
|
+
}
|
|
265
302
|
}
|
|
266
|
-
return isNativeModelQuotaExhausted(model, config,
|
|
303
|
+
return isNativeModelQuotaExhausted(model, config, resolvedAccountId, now);
|
|
267
304
|
}
|
|
268
305
|
|
|
269
306
|
export function selectAvailableSubagentModel(
|
|
@@ -275,8 +312,11 @@ export function selectAvailableSubagentModel(
|
|
|
275
312
|
nativeFallbackOnly = false,
|
|
276
313
|
accountUsabilityOptions?: CodexAccountUsabilityOptions,
|
|
277
314
|
trailingFallback: readonly string[] = [],
|
|
315
|
+
poolAccountPreview?: SubagentPoolAccountPreview,
|
|
316
|
+
modelEligibleAccountIdsForModel?: SubagentModelEligibleAccountIds,
|
|
317
|
+
resolvedChain?: readonly string[],
|
|
278
318
|
): { model: string; rewritten: boolean; skipped: string[] } {
|
|
279
|
-
const chain = normalizedChain(primary, config, extraFallback, trailingFallback);
|
|
319
|
+
const chain = resolvedChain ?? normalizedChain(primary, config, extraFallback, trailingFallback);
|
|
280
320
|
const skipped: string[] = [];
|
|
281
321
|
for (const candidate of chain) {
|
|
282
322
|
if (nativeFallbackOnly) {
|
|
@@ -286,7 +326,15 @@ export function selectAvailableSubagentModel(
|
|
|
286
326
|
continue;
|
|
287
327
|
}
|
|
288
328
|
}
|
|
289
|
-
if (isSubagentModelUnavailable(
|
|
329
|
+
if (isSubagentModelUnavailable(
|
|
330
|
+
candidate,
|
|
331
|
+
config,
|
|
332
|
+
accountId,
|
|
333
|
+
now,
|
|
334
|
+
accountUsabilityOptions,
|
|
335
|
+
poolAccountPreview,
|
|
336
|
+
modelEligibleAccountIdsForModel,
|
|
337
|
+
)) {
|
|
290
338
|
skipped.push(candidate);
|
|
291
339
|
continue;
|
|
292
340
|
}
|
|
@@ -515,27 +563,27 @@ export function applySubagentModelFallback(
|
|
|
515
563
|
now = Date.now(),
|
|
516
564
|
nativeFallbackOnly = false,
|
|
517
565
|
accountUsabilityOptions?: CodexAccountUsabilityOptions,
|
|
566
|
+
poolAccountPreview?: SubagentPoolAccountPreview,
|
|
567
|
+
modelEligibleAccountIdsForModel?: SubagentModelEligibleAccountIds,
|
|
568
|
+
resolvedFallbackChain?: readonly string[] | null,
|
|
518
569
|
): { from?: string; to?: string; skipped?: string[] } | null {
|
|
519
570
|
if (!isThreadSpawnRequest(headers)) return null;
|
|
520
|
-
const
|
|
521
|
-
parsed
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
);
|
|
525
|
-
// Config-keyed chains are the supported per-role home (#1190); TOML `model_fallback`
|
|
526
|
-
// stays readable for backwards compatibility with homes written before Codex 0.146.
|
|
527
|
-
const configuredFallback = resolveConfiguredModelFallbackForPrimary(parsed.modelId, config);
|
|
528
|
-
const globalFallback = config.subagentModelFallback ?? [];
|
|
529
|
-
if (globalFallback.length === 0 && configuredFallback.length === 0 && tomlRoleFallback.length === 0) return null;
|
|
571
|
+
const fallbackChain = resolvedFallbackChain === undefined
|
|
572
|
+
? resolveSubagentFallbackChain(parsed, config)
|
|
573
|
+
: resolvedFallbackChain;
|
|
574
|
+
if (!fallbackChain) return null;
|
|
530
575
|
const selection = selectAvailableSubagentModel(
|
|
531
576
|
parsed.modelId,
|
|
532
577
|
config,
|
|
533
|
-
|
|
578
|
+
[],
|
|
534
579
|
accountId,
|
|
535
580
|
now,
|
|
536
581
|
nativeFallbackOnly,
|
|
537
582
|
accountUsabilityOptions,
|
|
538
|
-
|
|
583
|
+
[],
|
|
584
|
+
poolAccountPreview,
|
|
585
|
+
modelEligibleAccountIdsForModel,
|
|
586
|
+
fallbackChain,
|
|
539
587
|
);
|
|
540
588
|
if (!selection.rewritten) return selection.skipped.length > 0
|
|
541
589
|
? { from: parsed.modelId, to: parsed.modelId, skipped: selection.skipped }
|
|
@@ -545,6 +593,37 @@ export function applySubagentModelFallback(
|
|
|
545
593
|
return { from, to: selection.model, skipped: selection.skipped };
|
|
546
594
|
}
|
|
547
595
|
|
|
596
|
+
/** Resolve the effective fallback chain once for one logical spawn request. */
|
|
597
|
+
export function resolveSubagentFallbackChain(
|
|
598
|
+
parsed: OcxParsedRequest,
|
|
599
|
+
config: OcxConfig,
|
|
600
|
+
): readonly string[] | null {
|
|
601
|
+
const tomlRoleFallback = resolveAgentModelFallbackForPrimary(
|
|
602
|
+
parsed.modelId,
|
|
603
|
+
getCodexHome(),
|
|
604
|
+
config.codexAccountNamespaces,
|
|
605
|
+
);
|
|
606
|
+
// Config-keyed chains are the supported per-role home (#1190); TOML `model_fallback`
|
|
607
|
+
// stays readable for backwards compatibility with homes written before Codex 0.146.
|
|
608
|
+
const configuredFallback = resolveConfiguredModelFallbackForPrimary(parsed.modelId, config);
|
|
609
|
+
const globalFallback = config.subagentModelFallback ?? [];
|
|
610
|
+
if (globalFallback.length === 0 && configuredFallback.length === 0 && tomlRoleFallback.length === 0) return null;
|
|
611
|
+
return normalizedChain(parsed.modelId, config, configuredFallback, tomlRoleFallback);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/** Whether the effective fallback chain crosses an account-gated native Pool model. */
|
|
615
|
+
export function subagentFallbackNeedsModelEntitlements(
|
|
616
|
+
fallbackChain: readonly string[] | null,
|
|
617
|
+
config: OcxConfig,
|
|
618
|
+
): boolean {
|
|
619
|
+
return fallbackChain?.some((candidate) => {
|
|
620
|
+
const route = tryRouteFallbackModel(config, candidate);
|
|
621
|
+
return !!route
|
|
622
|
+
&& isPoolCodexRoute(route)
|
|
623
|
+
&& ACCOUNT_GATED_NATIVE_OPENAI_MODELS.has(route.modelId);
|
|
624
|
+
}) === true;
|
|
625
|
+
}
|
|
626
|
+
|
|
548
627
|
export function subagentFallbackGuidanceText(config: OcxConfig): string {
|
|
549
628
|
const chain = config.subagentModelFallback ?? [];
|
|
550
629
|
if (chain.length === 0) return "";
|
|
@@ -10,7 +10,17 @@
|
|
|
10
10
|
* Design record: devlog/_fin/260804_codex_write_substrate/005_contract.md §1.
|
|
11
11
|
*/
|
|
12
12
|
import { randomUUID } from "node:crypto";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
chmodSync,
|
|
15
|
+
closeSync,
|
|
16
|
+
fsyncSync,
|
|
17
|
+
linkSync,
|
|
18
|
+
lstatSync,
|
|
19
|
+
openSync,
|
|
20
|
+
realpathSync,
|
|
21
|
+
unlinkSync,
|
|
22
|
+
} from "node:fs";
|
|
23
|
+
import { basename, dirname, join } from "node:path";
|
|
14
24
|
|
|
15
25
|
import { Database } from "bun:sqlite";
|
|
16
26
|
|
|
@@ -38,7 +48,7 @@ import {
|
|
|
38
48
|
} from "./user-identity";
|
|
39
49
|
|
|
40
50
|
export const CODEX_COORDINATOR_SCHEMA_VERSION = 1;
|
|
41
|
-
const DURABLE_HISTORY_STATUSES = new Set(["converged", "pending", "running", "blocked", "unknown"]);
|
|
51
|
+
const DURABLE_HISTORY_STATUSES = new Set(["adoption-pending", "converged", "pending", "running", "blocked", "unknown"]);
|
|
42
52
|
const DURABLE_HISTORY_REASONS = new Set([
|
|
43
53
|
"db-busy",
|
|
44
54
|
"permission",
|
|
@@ -66,7 +76,7 @@ const CREATE_TRANSITION_TABLE = `
|
|
|
66
76
|
history_pending_rows INTEGER,
|
|
67
77
|
history_backup_entries INTEGER,
|
|
68
78
|
updated_at TEXT NOT NULL,
|
|
69
|
-
CHECK (history_status IN ('converged', 'pending', 'running', 'blocked', 'unknown')),
|
|
79
|
+
CHECK (history_status IN ('adoption-pending', 'converged', 'pending', 'running', 'blocked', 'unknown')),
|
|
70
80
|
CHECK (history_reason IS NULL OR history_reason IN
|
|
71
81
|
('db-busy', 'permission', 'unreadable', 'schema', 'timeout',
|
|
72
82
|
'shutdown-cancelled', 'worker-died', 'overtaken', 'record-write-failed')),
|
|
@@ -75,14 +85,20 @@ const CREATE_TRANSITION_TABLE = `
|
|
|
75
85
|
CHECK ((native_generation = 0 AND current_tx_id IS NULL)
|
|
76
86
|
OR (native_generation > 0 AND length(trim(current_tx_id)) > 0)),
|
|
77
87
|
CHECK ((native_generation = 0
|
|
88
|
+
AND history_status = 'unknown'
|
|
78
89
|
AND history_tx_id IS NULL
|
|
79
90
|
AND history_direction IS NULL
|
|
80
91
|
AND history_authority_snapshot_id IS NULL)
|
|
92
|
+
OR (native_generation = 0
|
|
93
|
+
AND history_status = 'adoption-pending'
|
|
94
|
+
AND length(trim(history_tx_id)) > 0
|
|
95
|
+
AND history_direction IS NOT NULL
|
|
96
|
+
AND length(trim(history_authority_snapshot_id)) > 0)
|
|
81
97
|
OR (native_generation > 0
|
|
82
98
|
AND history_tx_id = current_tx_id
|
|
83
99
|
AND history_direction IS NOT NULL
|
|
84
100
|
AND length(trim(history_authority_snapshot_id)) > 0)),
|
|
85
|
-
CHECK (native_generation > 0 OR
|
|
101
|
+
CHECK (native_generation > 0 OR history_status = 'adoption-pending' OR
|
|
86
102
|
(history_status = 'unknown'
|
|
87
103
|
AND history_reason IS NULL
|
|
88
104
|
AND history_attempts = 0
|
|
@@ -100,6 +116,15 @@ const INITIALIZE_TRANSITION_ROW = `
|
|
|
100
116
|
history_backup_entries, updated_at
|
|
101
117
|
) VALUES (1, 0, NULL, 'unknown', NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, ?)`;
|
|
102
118
|
|
|
119
|
+
const INITIALIZE_ADOPTION_ROW = `
|
|
120
|
+
INSERT INTO codex_transition_state (
|
|
121
|
+
singleton, native_generation, current_tx_id,
|
|
122
|
+
history_status, history_reason, history_attempts,
|
|
123
|
+
history_next_retry_at, history_tx_id, history_direction,
|
|
124
|
+
history_authority_snapshot_id, history_pending_rows,
|
|
125
|
+
history_backup_entries, updated_at
|
|
126
|
+
) VALUES (1, 0, NULL, 'adoption-pending', NULL, 0, NULL, ?, ?, ?, NULL, NULL, ?)`;
|
|
127
|
+
|
|
103
128
|
const SELECT_TRANSITION_ROW = `
|
|
104
129
|
SELECT native_generation, current_tx_id,
|
|
105
130
|
history_status, history_reason, history_attempts,
|
|
@@ -210,8 +235,15 @@ function rowToState(row: TransitionRow | null): CodexTransitionState {
|
|
|
210
235
|
|
|
211
236
|
const generation = row.native_generation;
|
|
212
237
|
if (generation === 0) {
|
|
213
|
-
|
|
214
|
-
|
|
238
|
+
const ordinaryInitial = row.history_status === "unknown"
|
|
239
|
+
&& row.history_tx_id === null
|
|
240
|
+
&& row.history_direction === null
|
|
241
|
+
&& row.history_authority_snapshot_id === null;
|
|
242
|
+
const adoptionInitial = row.history_status === "adoption-pending"
|
|
243
|
+
&& Boolean(row.history_tx_id?.trim())
|
|
244
|
+
&& (row.history_direction === "apply" || row.history_direction === "remove")
|
|
245
|
+
&& Boolean(row.history_authority_snapshot_id?.trim());
|
|
246
|
+
if (row.current_tx_id !== null || (!ordinaryInitial && !adoptionInitial)) {
|
|
215
247
|
throw new CodexCoordinatorTransactionError("The initial coordinator row contains transition metadata.");
|
|
216
248
|
}
|
|
217
249
|
} else if (!row.current_tx_id?.trim()
|
|
@@ -234,13 +266,82 @@ function rowToState(row: TransitionRow | null): CodexTransitionState {
|
|
|
234
266
|
nativeGeneration: generation,
|
|
235
267
|
currentTxId: row.current_tx_id,
|
|
236
268
|
history,
|
|
237
|
-
historySchedule: generation === 0 ? null : {
|
|
269
|
+
historySchedule: generation === 0 && row.history_status !== "adoption-pending" ? null : {
|
|
238
270
|
direction: row.history_direction as "apply" | "remove",
|
|
239
271
|
authoritySnapshotId: row.history_authority_snapshot_id as string,
|
|
240
272
|
},
|
|
241
273
|
};
|
|
242
274
|
}
|
|
243
275
|
|
|
276
|
+
export type CodexCoordinatorAdoptionCheckpoint = "temp-created" | "temp-committed" | "published";
|
|
277
|
+
|
|
278
|
+
export interface CodexCoordinatorAdoptionOptions {
|
|
279
|
+
readonly direction: "apply" | "remove";
|
|
280
|
+
readonly onCheckpoint?: (checkpoint: CodexCoordinatorAdoptionCheckpoint) => void;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function fsyncFile(path: string): void {
|
|
284
|
+
// Writable fd on purpose. Windows FlushFileBuffers needs GENERIC_WRITE;
|
|
285
|
+
// openSync(path, "r") is GENERIC_READ only and fails with EPERM (measured on
|
|
286
|
+
// Windows 11 25H2 / bun 1.3.14). prompt-journal.ts uses the same "r+" open.
|
|
287
|
+
const fd = openSync(path, "r+");
|
|
288
|
+
try { fsyncSync(fd); } finally { closeSync(fd); }
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function fsyncDir(path: string): void {
|
|
292
|
+
// POSIX directory fsync is O_RDONLY. O_RDWR ("r+") is EISDIR.
|
|
293
|
+
const fd = openSync(path, "r");
|
|
294
|
+
try { fsyncSync(fd); } finally { closeSync(fd); }
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function publishAdoptionDatabase(
|
|
298
|
+
finalDatabasePath: string,
|
|
299
|
+
options: CodexCoordinatorAdoptionOptions,
|
|
300
|
+
): void {
|
|
301
|
+
const parent = dirname(finalDatabasePath);
|
|
302
|
+
const tempPath = join(parent, `.${basename(finalDatabasePath)}.adopt-${process.pid}-${randomUUID()}.tmp`);
|
|
303
|
+
let tempPresent = false;
|
|
304
|
+
try {
|
|
305
|
+
const fd = openSync(tempPath, "wx", 0o600);
|
|
306
|
+
closeSync(fd);
|
|
307
|
+
tempPresent = true;
|
|
308
|
+
options.onCheckpoint?.("temp-created");
|
|
309
|
+
|
|
310
|
+
const temp = new Database(tempPath, { readwrite: true, create: false });
|
|
311
|
+
try {
|
|
312
|
+
temp.exec("PRAGMA journal_mode = DELETE; BEGIN IMMEDIATE");
|
|
313
|
+
temp.exec(CREATE_TRANSITION_TABLE);
|
|
314
|
+
temp.query(INITIALIZE_ADOPTION_ROW).run(
|
|
315
|
+
randomUUID(),
|
|
316
|
+
options.direction,
|
|
317
|
+
randomUUID(),
|
|
318
|
+
new Date().toISOString(),
|
|
319
|
+
);
|
|
320
|
+
temp.exec(`PRAGMA user_version = ${CODEX_COORDINATOR_SCHEMA_VERSION}; COMMIT`);
|
|
321
|
+
readCodexCoordinatorState(temp);
|
|
322
|
+
} catch (error) {
|
|
323
|
+
try { temp.exec("ROLLBACK"); } catch { /* commit may already have completed */ }
|
|
324
|
+
throw error;
|
|
325
|
+
} finally {
|
|
326
|
+
temp.close();
|
|
327
|
+
}
|
|
328
|
+
fsyncFile(tempPath);
|
|
329
|
+
options.onCheckpoint?.("temp-committed");
|
|
330
|
+
|
|
331
|
+
linkSync(tempPath, finalDatabasePath);
|
|
332
|
+
options.onCheckpoint?.("published");
|
|
333
|
+
if (process.platform !== "win32") fsyncDir(parent);
|
|
334
|
+
} catch (error) {
|
|
335
|
+
if (errorCode(error) !== "EEXIST") throw error;
|
|
336
|
+
} finally {
|
|
337
|
+
if (tempPresent) {
|
|
338
|
+
try { unlinkSync(tempPath); } catch (error) {
|
|
339
|
+
if (errorCode(error) !== "ENOENT") throw error;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
244
345
|
export function readCodexCoordinatorState(database: Database): CodexTransitionState {
|
|
245
346
|
const row = database.query<TransitionRow, []>(SELECT_TRANSITION_ROW).get();
|
|
246
347
|
return rowToState(row);
|
|
@@ -346,7 +447,10 @@ function createCapability(
|
|
|
346
447
|
};
|
|
347
448
|
}
|
|
348
449
|
|
|
349
|
-
export function openCodexCoordinatorTransaction(
|
|
450
|
+
export function openCodexCoordinatorTransaction(
|
|
451
|
+
finalDatabasePath: string,
|
|
452
|
+
adoption?: CodexCoordinatorAdoptionOptions,
|
|
453
|
+
): CodexCoordinatorTransactionController {
|
|
350
454
|
let database: Database | undefined;
|
|
351
455
|
let transactionOpen = false;
|
|
352
456
|
let closed = false;
|
|
@@ -389,6 +493,10 @@ export function openCodexCoordinatorTransaction(finalDatabasePath: string): Code
|
|
|
389
493
|
if (errorCode(cause) !== "ENOENT") throw cause;
|
|
390
494
|
databaseWasAbsent = true;
|
|
391
495
|
}
|
|
496
|
+
if (databaseWasAbsent && adoption) {
|
|
497
|
+
publishAdoptionDatabase(finalDatabasePath, adoption);
|
|
498
|
+
databaseWasAbsent = false;
|
|
499
|
+
}
|
|
392
500
|
database = new Database(finalDatabasePath, { create: true });
|
|
393
501
|
if (databaseWasAbsent) {
|
|
394
502
|
try { chmodSync(finalDatabasePath, 0o600); } catch { /* Windows applies ACLs in WP11. */ }
|
package/src/combos/failover.ts
CHANGED
|
@@ -109,6 +109,28 @@ export function clearComboTargetCooldowns(comboId?: string): void {
|
|
|
109
109
|
|
|
110
110
|
export type ComboFailureDecision = "hop" | "stop";
|
|
111
111
|
|
|
112
|
+
function isModelLifecycleGone(
|
|
113
|
+
status: number,
|
|
114
|
+
message: string,
|
|
115
|
+
code?: string | null,
|
|
116
|
+
): boolean {
|
|
117
|
+
if (status !== 410) return false;
|
|
118
|
+
const normalizedCode = code?.trim().toLowerCase().replaceAll("-", "_");
|
|
119
|
+
if ([
|
|
120
|
+
"model_deprecated",
|
|
121
|
+
"model_end_of_life",
|
|
122
|
+
"model_eol",
|
|
123
|
+
"model_not_found",
|
|
124
|
+
"model_retired",
|
|
125
|
+
].includes(normalizedCode ?? "")) return true;
|
|
126
|
+
const text = message.toLowerCase();
|
|
127
|
+
return /\bmodel\b/.test(text) && (
|
|
128
|
+
/\bend[ -]of[ -]life\b/.test(text)
|
|
129
|
+
|| /\bno longer available\b/.test(text)
|
|
130
|
+
|| /\b(?:deprecated|retired|retirement|sunset|decommissioned)\b/.test(text)
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
112
134
|
export function comboFailureDecision(
|
|
113
135
|
status: number,
|
|
114
136
|
message: string,
|
|
@@ -119,6 +141,11 @@ export function comboFailureDecision(
|
|
|
119
141
|
// Cyber policy is a hard non-retryable refusal — honor structured code even when
|
|
120
142
|
// classificationText was truncated before the JSON code field.
|
|
121
143
|
if (isCyberPolicyCode(options?.code)) return "stop";
|
|
144
|
+
// HTTP 410 is normally terminal. A model-specific lifecycle verdict is target-local,
|
|
145
|
+
// however: another provider/model in the declared combo can still serve the request.
|
|
146
|
+
// Require structured lifecycle code or explicit model+lifecycle prose so unrelated
|
|
147
|
+
// application-level 410 responses remain fail-closed.
|
|
148
|
+
if (isModelLifecycleGone(status, message, options?.code)) return "hop";
|
|
122
149
|
const error = classifyError(status, "upstream_error", message);
|
|
123
150
|
if (isCyberPolicyCode(error.code)) return "stop";
|
|
124
151
|
// A local input-admission refusal (#1524) says "this candidate cannot fit the request",
|
package/src/combos/types.ts
CHANGED
|
@@ -203,6 +203,16 @@ export function comboConfigIssues(
|
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
const body = raw as Record<string, unknown>;
|
|
206
|
+
if (typeof body.alias === "string") {
|
|
207
|
+
const alias = body.alias.toLowerCase();
|
|
208
|
+
for (const [providerName, provider] of Object.entries(providers)) {
|
|
209
|
+
if (provider.alias?.toLowerCase() === alias) {
|
|
210
|
+
issues.push({ path: ["alias"], message: `alias "${body.alias}" is already used by provider "${providerName}"` });
|
|
211
|
+
}
|
|
212
|
+
const model = Object.entries(provider.modelAliases ?? {}).find(([, value]) => value.toLowerCase() === alias);
|
|
213
|
+
if (model) issues.push({ path: ["alias"], message: `alias "${body.alias}" is already used by model "${providerName}/${model[0]}"` });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
206
216
|
if (body.strategy !== undefined
|
|
207
217
|
&& body.strategy !== "failover"
|
|
208
218
|
&& body.strategy !== "round-robin") {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { OPENAI_CODEX_FORWARD_GPT56_SOL_MANIFEST } from "./openai-responses";
|
|
2
|
+
import type { CompatibilityManifestV1 } from "./manifest";
|
|
3
|
+
|
|
4
|
+
export {
|
|
5
|
+
COMPATIBILITY_DISPOSITIONS,
|
|
6
|
+
COMPATIBILITY_MANIFEST_SCHEMA_VERSION,
|
|
7
|
+
compatibilityManifestIssues,
|
|
8
|
+
defineCompatibilityManifest,
|
|
9
|
+
} from "./manifest";
|
|
10
|
+
export type {
|
|
11
|
+
CompatibilityClaimV1,
|
|
12
|
+
CompatibilityDisposition,
|
|
13
|
+
CompatibilityEvidenceKind,
|
|
14
|
+
CompatibilityEvidenceRefV1,
|
|
15
|
+
CompatibilityManifestV1,
|
|
16
|
+
CompatibilitySubjectV1,
|
|
17
|
+
} from "./manifest";
|
|
18
|
+
export { OPENAI_CODEX_FORWARD_GPT56_SOL_MANIFEST } from "./openai-responses";
|
|
19
|
+
|
|
20
|
+
export const COMPATIBILITY_MANIFESTS: readonly CompatibilityManifestV1[] = Object.freeze([
|
|
21
|
+
OPENAI_CODEX_FORWARD_GPT56_SOL_MANIFEST,
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
export function getCompatibilityManifest(id: string): CompatibilityManifestV1 | undefined {
|
|
25
|
+
return COMPATIBILITY_MANIFESTS.find(manifest => manifest.id === id);
|
|
26
|
+
}
|