@yansigit/opencodex 2.31.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/AGENTS_INSTALL.md +109 -0
- package/LICENSE +21 -0
- package/README.md +303 -0
- package/assets/architecture.png +0 -0
- package/assets/banner.png +0 -0
- package/assets/claude-code-models.gif +0 -0
- package/assets/codex-app-picker.png +0 -0
- package/bin/ocx.mjs +587 -0
- package/bin/package-main.mjs +9 -0
- package/gui/dist/assets/index-BNJ7r4Gd.js +102 -0
- package/gui/dist/assets/index-CGoDO3uO.css +1 -0
- package/gui/dist/favicon.png +0 -0
- package/gui/dist/icons.svg +24 -0
- package/gui/dist/index.html +25 -0
- package/gui/dist/logo.png +0 -0
- package/gui/dist/provider-icons/alibaba-color.svg +1 -0
- package/gui/dist/provider-icons/antigravity-color.svg +1 -0
- package/gui/dist/provider-icons/claude-color.svg +1 -0
- package/gui/dist/provider-icons/cline-color.svg +16 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
- package/gui/dist/provider-icons/commandcode-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -0
- package/gui/dist/provider-icons/cursor-color.svg +2 -0
- package/gui/dist/provider-icons/deepseek-color.svg +1 -0
- package/gui/dist/provider-icons/discord.svg +1 -0
- package/gui/dist/provider-icons/firepass-color.svg +1 -0
- package/gui/dist/provider-icons/fireworks-color.svg +1 -0
- package/gui/dist/provider-icons/gemini-color.svg +1 -0
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -0
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -0
- package/gui/dist/provider-icons/grok.svg +1 -0
- package/gui/dist/provider-icons/groq-color.svg +1 -0
- package/gui/dist/provider-icons/huggingface-color.svg +1 -0
- package/gui/dist/provider-icons/kimi-color.svg +1 -0
- package/gui/dist/provider-icons/kiro-color.svg +15 -0
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -0
- package/gui/dist/provider-icons/mistral-color.svg +1 -0
- package/gui/dist/provider-icons/moonshot-color.svg +1 -0
- package/gui/dist/provider-icons/nvidia-color.svg +1 -0
- package/gui/dist/provider-icons/ollama-color.svg +1 -0
- package/gui/dist/provider-icons/openai.svg +1 -0
- package/gui/dist/provider-icons/opencode.svg +2 -0
- package/gui/dist/provider-icons/openrouter-color.svg +1 -0
- package/gui/dist/provider-icons/pi.svg +21 -0
- package/gui/dist/provider-icons/qianfan-color.svg +1 -0
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -0
- package/gui/dist/provider-icons/telegram.svg +1 -0
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -0
- package/gui/dist/provider-icons/vllm-color.svg +1 -0
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -0
- package/package.json +108 -0
- package/src/AGENTS.md +28 -0
- package/src/adapters/anthropic-image-guard.ts +251 -0
- package/src/adapters/anthropic-image-normalize.ts +518 -0
- package/src/adapters/anthropic-output-schema.ts +137 -0
- package/src/adapters/anthropic.ts +1327 -0
- package/src/adapters/azure.ts +36 -0
- package/src/adapters/base.ts +121 -0
- package/src/adapters/client-fingerprint.ts +65 -0
- package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
- package/src/adapters/command-code.ts +601 -0
- package/src/adapters/cursor/arg-codec.ts +38 -0
- package/src/adapters/cursor/arg-normalize.ts +104 -0
- package/src/adapters/cursor/checkpoint-store.ts +303 -0
- package/src/adapters/cursor/cursor-errors.ts +288 -0
- package/src/adapters/cursor/discovery.ts +333 -0
- package/src/adapters/cursor/effort-map.ts +151 -0
- package/src/adapters/cursor/exec-policy.ts +88 -0
- package/src/adapters/cursor/framing.ts +250 -0
- package/src/adapters/cursor/gen/agent_pb.ts +15274 -0
- package/src/adapters/cursor/h2-pool.ts +123 -0
- package/src/adapters/cursor/http1-bidi.ts +361 -0
- package/src/adapters/cursor/images.ts +704 -0
- package/src/adapters/cursor/kv-store.ts +52 -0
- package/src/adapters/cursor/live-models.ts +269 -0
- package/src/adapters/cursor/live-smoke-gate.ts +41 -0
- package/src/adapters/cursor/live-transport.ts +1653 -0
- package/src/adapters/cursor/mcp-config.ts +42 -0
- package/src/adapters/cursor/mcp-manager.ts +333 -0
- package/src/adapters/cursor/message-mapper.ts +49 -0
- package/src/adapters/cursor/native-exec-common.ts +76 -0
- package/src/adapters/cursor/native-exec-desktop.ts +184 -0
- package/src/adapters/cursor/native-exec-fs.ts +332 -0
- package/src/adapters/cursor/native-exec-mcp.ts +153 -0
- package/src/adapters/cursor/native-exec-network.ts +43 -0
- package/src/adapters/cursor/native-exec-shell.ts +547 -0
- package/src/adapters/cursor/native-exec-tools.ts +118 -0
- package/src/adapters/cursor/native-exec.ts +663 -0
- package/src/adapters/cursor/protobuf-events.ts +1381 -0
- package/src/adapters/cursor/protobuf-request.ts +1032 -0
- package/src/adapters/cursor/request-builder.ts +461 -0
- package/src/adapters/cursor/thread-continuity.ts +67 -0
- package/src/adapters/cursor/tool-definitions.ts +735 -0
- package/src/adapters/cursor/tool-result-normalize.ts +92 -0
- package/src/adapters/cursor/transport-retry.ts +132 -0
- package/src/adapters/cursor/transport.ts +79 -0
- package/src/adapters/cursor/types.ts +79 -0
- package/src/adapters/cursor.ts +322 -0
- package/src/adapters/google-antigravity-hosts.ts +48 -0
- package/src/adapters/google-antigravity-replay.ts +827 -0
- package/src/adapters/google-antigravity-tools.ts +105 -0
- package/src/adapters/google-antigravity-wire.ts +141 -0
- package/src/adapters/google-errors.ts +92 -0
- package/src/adapters/google-http.ts +460 -0
- package/src/adapters/google-tool-schema.ts +238 -0
- package/src/adapters/google-truncation.ts +24 -0
- package/src/adapters/google-wire-compiler.ts +232 -0
- package/src/adapters/google.ts +1377 -0
- package/src/adapters/identity.ts +77 -0
- package/src/adapters/image.ts +23 -0
- package/src/adapters/kiro-constants.ts +16 -0
- package/src/adapters/kiro-errors.ts +208 -0
- package/src/adapters/kiro-events.ts +197 -0
- package/src/adapters/kiro-images.ts +129 -0
- package/src/adapters/kiro-retry.ts +312 -0
- package/src/adapters/kiro-thinking.ts +112 -0
- package/src/adapters/kiro-tool-fallback.ts +36 -0
- package/src/adapters/kiro-tools.ts +224 -0
- package/src/adapters/kiro-truncation.ts +33 -0
- package/src/adapters/kiro-wire.ts +129 -0
- package/src/adapters/kiro.ts +1936 -0
- package/src/adapters/mimo-free.ts +280 -0
- package/src/adapters/openai-chat-url.ts +11 -0
- package/src/adapters/openai-chat.ts +1980 -0
- package/src/adapters/openai-responses-url.ts +16 -0
- package/src/adapters/openai-responses.ts +1911 -0
- package/src/adapters/registry.ts +175 -0
- package/src/adapters/responses-tool-schema.ts +67 -0
- package/src/adapters/run-turn-queue.ts +114 -0
- package/src/adapters/tool-call-id.ts +119 -0
- package/src/adapters/tool-catalog-nudge.ts +154 -0
- package/src/adapters/upstream-http-error.ts +48 -0
- package/src/adapters/xai-web-search.ts +185 -0
- package/src/bridge.ts +1986 -0
- package/src/chat/inbound.ts +319 -0
- package/src/chat/outbound.ts +821 -0
- package/src/claude/agents-inject.ts +266 -0
- package/src/claude/alias.ts +149 -0
- package/src/claude/auth-detect.ts +229 -0
- package/src/claude/auth-mode-migration.ts +32 -0
- package/src/claude/auth-mode.ts +62 -0
- package/src/claude/context-windows.ts +205 -0
- package/src/claude/desktop-3p-guard.ts +35 -0
- package/src/claude/desktop-3p-paths.ts +84 -0
- package/src/claude/desktop-3p.ts +615 -0
- package/src/claude/desktop-health.ts +26 -0
- package/src/claude/desktop-profile.ts +263 -0
- package/src/claude/gateway-cache.ts +107 -0
- package/src/claude/inbound-debug.ts +163 -0
- package/src/claude/inbound.ts +578 -0
- package/src/claude/model-info.ts +174 -0
- package/src/claude/outbound.ts +926 -0
- package/src/cli/access.ts +108 -0
- package/src/cli/account-api.ts +302 -0
- package/src/cli/account-auth.ts +250 -0
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +737 -0
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +299 -0
- package/src/cli/agent-driven.ts +70 -0
- package/src/cli/agent.ts +290 -0
- package/src/cli/catalog-prewarm.ts +27 -0
- package/src/cli/claude-agent-startup-sync.ts +73 -0
- package/src/cli/claude-desktop.ts +213 -0
- package/src/cli/claude.ts +355 -0
- package/src/cli/codex-log-guard-doctor.ts +103 -0
- package/src/cli/codex-shim-autorestore.ts +47 -0
- package/src/cli/codex-shim-readiness.ts +76 -0
- package/src/cli/combo.ts +127 -0
- package/src/cli/config-command.ts +209 -0
- package/src/cli/debug.ts +228 -0
- package/src/cli/dispatch.ts +585 -0
- package/src/cli/doctor.ts +1202 -0
- package/src/cli/ensure-desired-integrations.ts +152 -0
- package/src/cli/export-command.ts +213 -0
- package/src/cli/help.ts +101 -0
- package/src/cli/index.ts +973 -0
- package/src/cli/init.ts +211 -0
- package/src/cli/integrations.ts +260 -0
- package/src/cli/interactive-confirm.ts +133 -0
- package/src/cli/lab.ts +607 -0
- package/src/cli/launcher-context.ts +77 -0
- package/src/cli/minimax.ts +497 -0
- package/src/cli/models-runtime.ts +245 -0
- package/src/cli/models.ts +422 -0
- package/src/cli/observe.ts +206 -0
- package/src/cli/opencode.ts +588 -0
- package/src/cli/provider-replit.ts +232 -0
- package/src/cli/provider-runtime.ts +179 -0
- package/src/cli/provider.ts +492 -0
- package/src/cli/ready.ts +301 -0
- package/src/cli/registry.ts +422 -0
- package/src/cli/replit-gateway-key-input.ts +138 -0
- package/src/cli/root.ts +86 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +328 -0
- package/src/cli/star-prompt.ts +211 -0
- package/src/cli/status-oauth.ts +78 -0
- package/src/cli/status.ts +328 -0
- package/src/cli/system-command.ts +112 -0
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +199 -0
- package/src/cli/v2.ts +268 -0
- package/src/cli.ts +10 -0
- package/src/clients/config-export.ts +1704 -0
- package/src/codex/account-id.ts +34 -0
- package/src/codex/account-label.ts +47 -0
- package/src/codex/account-lifecycle.ts +172 -0
- package/src/codex/account-namespace-match.ts +63 -0
- package/src/codex/account-namespaces.ts +195 -0
- package/src/codex/account-pause.ts +20 -0
- package/src/codex/account-priority.ts +83 -0
- package/src/codex/account-runtime-state.ts +31 -0
- package/src/codex/account-store.ts +544 -0
- package/src/codex/account-usability.ts +43 -0
- package/src/codex/admission.ts +256 -0
- package/src/codex/affinity-debug.ts +162 -0
- package/src/codex/agent-roles-sync.ts +225 -0
- package/src/codex/agent-roles.ts +238 -0
- package/src/codex/app-server-processes.ts +1143 -0
- package/src/codex/app-server-restart-service.ts +232 -0
- package/src/codex/auth-api.ts +2147 -0
- package/src/codex/auth-collision.ts +109 -0
- package/src/codex/auth-context.ts +665 -0
- package/src/codex/autostart-health.ts +156 -0
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +436 -0
- package/src/codex/catalog/bundled.ts +549 -0
- package/src/codex/catalog/effort.ts +446 -0
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +664 -0
- package/src/codex/catalog/native-models.ts +72 -0
- package/src/codex/catalog/parsing.ts +650 -0
- package/src/codex/catalog/provider-fetch.ts +2064 -0
- package/src/codex/catalog/sync.ts +1883 -0
- package/src/codex/catalog-admission.ts +199 -0
- package/src/codex/catalog-refresh-status.ts +105 -0
- package/src/codex/catalog-write-serialization.ts +242 -0
- package/src/codex/catalog.ts +14 -0
- package/src/codex/codex-write-lock.ts +384 -0
- package/src/codex/convergence-types.ts +614 -0
- package/src/codex/convergence.ts +651 -0
- package/src/codex/coordinator-doctor.ts +332 -0
- package/src/codex/custom-model-catalog-migration.ts +176 -0
- package/src/codex/data/upstream-models.json +830 -0
- package/src/codex/desired-state.ts +230 -0
- package/src/codex/exec-invocation.ts +22 -0
- package/src/codex/features.ts +1566 -0
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +407 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-migration-guardian.ts +108 -0
- package/src/codex/history-provider.ts +979 -0
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +220 -0
- package/src/codex/home.ts +206 -0
- package/src/codex/inject-coordination.ts +290 -0
- package/src/codex/inject.ts +1733 -0
- package/src/codex/injected-marker.ts +106 -0
- package/src/codex/integration-record.ts +266 -0
- package/src/codex/internal/catalog-writer.ts +203 -0
- package/src/codex/internal/history-writer.ts +80 -0
- package/src/codex/journal.ts +225 -0
- package/src/codex/log-guard/inspect.ts +506 -0
- package/src/codex/log-guard/lock.ts +150 -0
- package/src/codex/log-guard/maintenance.ts +403 -0
- package/src/codex/log-guard/path-safety.ts +88 -0
- package/src/codex/log-guard/policy.ts +44 -0
- package/src/codex/log-guard/processes.ts +205 -0
- package/src/codex/log-guard/protection.ts +489 -0
- package/src/codex/log-guard/sqlite-errors.ts +9 -0
- package/src/codex/main-account-cache.ts +56 -0
- package/src/codex/main-account.ts +68 -0
- package/src/codex/management-convergence.ts +167 -0
- package/src/codex/model-cache.ts +273 -0
- package/src/codex/model-entitlements.ts +353 -0
- package/src/codex/native-main-admission.ts +47 -0
- package/src/codex/native-main-auth-temp.ts +187 -0
- package/src/codex/native-main-claim.ts +178 -0
- package/src/codex/native-main-lock-file.ts +162 -0
- package/src/codex/native-main-owner.ts +329 -0
- package/src/codex/native-profile-api.ts +247 -0
- package/src/codex/native-profile-manager.ts +1531 -0
- package/src/codex/native-profile-processes.ts +121 -0
- package/src/codex/native-profile-recovery.ts +99 -0
- package/src/codex/native-profile-stage-store.ts +387 -0
- package/src/codex/native-profile-startup.ts +492 -0
- package/src/codex/native-profile-store.ts +855 -0
- package/src/codex/native-profile-types.ts +120 -0
- package/src/codex/native-residue.ts +682 -0
- package/src/codex/paths.ts +144 -0
- package/src/codex/plan-from-token.ts +140 -0
- package/src/codex/plan.ts +40 -0
- package/src/codex/plugins-doctor.ts +242 -0
- package/src/codex/pool-rotation.ts +295 -0
- package/src/codex/project-config-warnings.ts +425 -0
- package/src/codex/prompt-journal.ts +352 -0
- package/src/codex/prompt-layers.ts +967 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/quota-rejection.ts +298 -0
- package/src/codex/quota.ts +573 -0
- package/src/codex/refresh.ts +62 -0
- package/src/codex/reset-credit-recovery.ts +1044 -0
- package/src/codex/routing.ts +1888 -0
- package/src/codex/runtime.ts +659 -0
- package/src/codex/shim.ts +2170 -0
- package/src/codex/subagent-defaults.ts +550 -0
- package/src/codex/subagent-model-fallback.ts +784 -0
- package/src/codex/sync.ts +319 -0
- package/src/codex/transition-state.ts +612 -0
- package/src/codex/upstream-host-health.ts +368 -0
- package/src/codex/user-identity.ts +557 -0
- package/src/codex/warmup.ts +298 -0
- package/src/codex/websocket-registry.ts +100 -0
- package/src/codex/write-coordination.ts +114 -0
- package/src/combos/failover.ts +160 -0
- package/src/combos/index.ts +45 -0
- package/src/combos/request.ts +94 -0
- package/src/combos/resolve.ts +232 -0
- package/src/combos/types.ts +398 -0
- package/src/config/provider-name.ts +24 -0
- package/src/config.ts +4041 -0
- package/src/fork/register.ts +3 -0
- package/src/generated/compatibility-version.json +3116 -0
- package/src/generated/model-metadata.ts +106 -0
- package/src/github/star-state.ts +203 -0
- package/src/grok/inject.ts +530 -0
- package/src/grok/inspect.ts +45 -0
- package/src/grok/status.ts +121 -0
- package/src/grok/sync.ts +66 -0
- package/src/images/artifacts.ts +516 -0
- package/src/images/fulfill-video.ts +163 -0
- package/src/images/fulfill.ts +149 -0
- package/src/images/index.ts +4 -0
- package/src/images/loop.ts +955 -0
- package/src/images/plan.ts +143 -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/index.ts +22 -0
- package/src/integrations/config-io.ts +269 -0
- package/src/integrations/journal.ts +315 -0
- package/src/integrations/merge.ts +135 -0
- package/src/integrations/mutation-flight.ts +71 -0
- package/src/integrations/native/ownership-preflight.ts +202 -0
- package/src/integrations/omp-yaml-source.ts +358 -0
- package/src/integrations/owned-refresh.ts +74 -0
- package/src/integrations/ownership.ts +111 -0
- package/src/integrations/registry.ts +159 -0
- package/src/integrations/serialize.ts +314 -0
- package/src/integrations/state.ts +361 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer-lock.ts +98 -0
- package/src/integrations/writer.ts +691 -0
- package/src/lab/artifacts/sanitize.ts +586 -0
- package/src/lab/artifacts/secure-fs.ts +475 -0
- package/src/lab/artifacts/store.ts +310 -0
- package/src/lab/automation/budgets.ts +78 -0
- package/src/lab/automation/config-persistence.ts +256 -0
- package/src/lab/automation/constants.ts +39 -0
- package/src/lab/automation/cooldown.ts +103 -0
- package/src/lab/automation/dispatch.ts +211 -0
- package/src/lab/automation/index.ts +13 -0
- package/src/lab/automation/orchestrator.ts +499 -0
- package/src/lab/automation/persistence.ts +512 -0
- package/src/lab/automation/planner.ts +371 -0
- package/src/lab/automation/policy.ts +136 -0
- package/src/lab/automation/queue.ts +191 -0
- package/src/lab/automation/recovery.ts +24 -0
- package/src/lab/automation/route-context.ts +21 -0
- package/src/lab/automation/run-key.ts +44 -0
- package/src/lab/automation/runs-query.ts +34 -0
- package/src/lab/automation/types.ts +160 -0
- package/src/lab/conformance/assertion.ts +325 -0
- package/src/lab/conformance/digest.ts +22 -0
- package/src/lab/conformance/executor.ts +741 -0
- package/src/lab/conformance/fixture-provider.ts +27 -0
- package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
- package/src/lab/conformance/harness-budget.ts +47 -0
- package/src/lab/conformance/index.ts +5 -0
- package/src/lab/conformance/jcs.ts +64 -0
- package/src/lab/conformance/json-pointer.ts +39 -0
- package/src/lab/conformance/manifest.ts +180 -0
- package/src/lab/conformance/mcp-stub.ts +179 -0
- package/src/lab/conformance/negative-controls.ts +164 -0
- package/src/lab/conformance/observation.ts +355 -0
- package/src/lab/conformance/runner.ts +68 -0
- package/src/lab/conformance/sse-normalize.ts +59 -0
- package/src/lab/conformance/suite-manifest.ts +78 -0
- package/src/lab/conformance/types.ts +214 -0
- package/src/lab/constants.ts +126 -0
- package/src/lab/digest.ts +64 -0
- package/src/lab/events/errors.ts +9 -0
- package/src/lab/events/limits.ts +117 -0
- package/src/lab/events/types.ts +229 -0
- package/src/lab/events/validate.ts +781 -0
- package/src/lab/fabric/constants.ts +40 -0
- package/src/lab/fabric/executor.ts +492 -0
- package/src/lab/fabric/index.ts +80 -0
- package/src/lab/fabric/manifest.ts +222 -0
- package/src/lab/fabric/observe.ts +489 -0
- package/src/lab/fabric/patch.ts +79 -0
- package/src/lab/fabric/producer-child.ts +139 -0
- package/src/lab/fabric/producer-isolate.ts +276 -0
- package/src/lab/fabric/producer-protocol.ts +61 -0
- package/src/lab/fabric/scratch.ts +439 -0
- package/src/lab/fabric/subject.ts +106 -0
- package/src/lab/fabric/types.ts +134 -0
- package/src/lab/fabric/verifier.ts +98 -0
- package/src/lab/index.ts +54 -0
- package/src/lab/ledger/artifact-refs.ts +127 -0
- package/src/lab/ledger/invalidation.ts +136 -0
- package/src/lab/ledger/purge.ts +310 -0
- package/src/lab/ledger/store.ts +532 -0
- package/src/lab/live/credential-lease.ts +53 -0
- package/src/lab/live/destination.ts +155 -0
- package/src/lab/live/executor.ts +336 -0
- package/src/lab/live/inert-tools.ts +56 -0
- package/src/lab/live/manifest.ts +85 -0
- package/src/lab/live/mcp-loopback.ts +57 -0
- package/src/lab/live/runner.ts +19 -0
- package/src/lab/live/sandbox.ts +61 -0
- package/src/lab/live/suite-manifest.ts +41 -0
- package/src/lab/live/transport.ts +118 -0
- package/src/lab/live/types.ts +197 -0
- package/src/lab/observe/from-conformance.ts +301 -0
- package/src/lab/observe/from-live.ts +117 -0
- package/src/lab/paths.ts +153 -0
- package/src/lab/projection/rebuild.ts +495 -0
- package/src/lab/projection/schema.ts +135 -0
- package/src/lab/projection/verdicts.ts +474 -0
- package/src/lab/projection/verification.ts +412 -0
- package/src/lab/public/bundle.ts +217 -0
- package/src/lab/public/community-authority.ts +175 -0
- package/src/lab/public/community-files.ts +29 -0
- package/src/lab/public/community.ts +479 -0
- package/src/lab/public/file-safety.ts +155 -0
- package/src/lab/public/ids.ts +26 -0
- package/src/lab/public/index.ts +16 -0
- package/src/lab/public/mutation-lock.ts +424 -0
- package/src/lab/public/operator.ts +353 -0
- package/src/lab/public/origin-purge.ts +79 -0
- package/src/lab/public/origin.ts +203 -0
- package/src/lab/public/privacy.ts +143 -0
- package/src/lab/public/private-file.ts +261 -0
- package/src/lab/public/project.ts +124 -0
- package/src/lab/public/purge-test-fault.ts +21 -0
- package/src/lab/public/purge.ts +223 -0
- package/src/lab/public/registry.ts +44 -0
- package/src/lab/public/revocation.ts +252 -0
- package/src/lab/public/signature.ts +243 -0
- package/src/lab/public/storage.ts +105 -0
- package/src/lab/public/strict-json.ts +206 -0
- package/src/lab/public/time.ts +26 -0
- package/src/lab/public/types.ts +172 -0
- package/src/lab/public/validate.ts +391 -0
- package/src/lab/query/catalog.ts +101 -0
- package/src/lab/query/connection.ts +107 -0
- package/src/lab/query/constants.ts +4 -0
- package/src/lab/query/cursor.ts +132 -0
- package/src/lab/query/dto-map.ts +277 -0
- package/src/lab/query/errors.ts +22 -0
- package/src/lab/query/freshness.ts +53 -0
- package/src/lab/query/index.ts +45 -0
- package/src/lab/query/latest-observation.ts +59 -0
- package/src/lab/query/passive-production.ts +159 -0
- package/src/lab/query/queries.ts +444 -0
- package/src/lab/query/types.ts +266 -0
- package/src/lab/subject/behavior-fingerprint.ts +77 -0
- package/src/lab/subject/installation-salt.ts +112 -0
- package/src/lab/subject/protocol-subject.ts +80 -0
- package/src/lab/subject/route-subject.ts +74 -0
- package/src/lib/abort.ts +146 -0
- package/src/lib/admin-secrets.ts +25 -0
- package/src/lib/admission.ts +83 -0
- package/src/lib/app-owned-memory-stores.ts +195 -0
- package/src/lib/app-owned-memory.ts +265 -0
- package/src/lib/bounded-body.ts +346 -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 +184 -0
- package/src/lib/bun-stream-caps.ts +130 -0
- package/src/lib/codex-restart-contract.ts +120 -0
- package/src/lib/config-ownership.ts +364 -0
- package/src/lib/crash-guard.ts +344 -0
- package/src/lib/debug-log-buffer.ts +83 -0
- package/src/lib/debug-settings.ts +108 -0
- package/src/lib/debug.ts +31 -0
- package/src/lib/destination-policy.ts +380 -0
- package/src/lib/errors.ts +406 -0
- package/src/lib/eventstream-decoder.ts +253 -0
- package/src/lib/fabric-task-execution-authority.ts +7 -0
- package/src/lib/fabric-task-host.ts +29 -0
- package/src/lib/gcp-adc.ts +341 -0
- package/src/lib/injection-debug-log.ts +58 -0
- package/src/lib/lab-activation.ts +223 -0
- package/src/lib/lab-live-execution-authority.ts +13 -0
- package/src/lib/lab-live-host.ts +30 -0
- package/src/lib/lab-live-pinned-sender.ts +56 -0
- package/src/lib/lab-live-route-production.ts +130 -0
- package/src/lib/lab-passive-linker-registration.ts +26 -0
- package/src/lib/local-management-attestation.ts +51 -0
- package/src/lib/local-management-capability.ts +100 -0
- package/src/lib/local-provider-reload-contract.ts +100 -0
- package/src/lib/open-url.ts +25 -0
- package/src/lib/optional-shutdown-hooks.ts +57 -0
- package/src/lib/pinned-http.ts +270 -0
- package/src/lib/privacy.ts +20 -0
- package/src/lib/process-control.ts +168 -0
- package/src/lib/provider-outbound.ts +210 -0
- package/src/lib/provider-url.ts +14 -0
- package/src/lib/proxy-env.ts +18 -0
- package/src/lib/redact.ts +521 -0
- package/src/lib/retry-after.ts +55 -0
- package/src/lib/self-launch-argv.ts +15 -0
- package/src/lib/server-resource-ownership.ts +71 -0
- package/src/lib/service-secrets.ts +25 -0
- package/src/lib/shadow-call.ts +61 -0
- package/src/lib/sidecar-tracker.ts +52 -0
- package/src/lib/sse-decoder.ts +364 -0
- package/src/lib/state-store-registrations.ts +119 -0
- package/src/lib/state-store-sweeper.ts +184 -0
- package/src/lib/system-restart-contract.ts +73 -0
- package/src/lib/test-home-guard.ts +90 -0
- package/src/lib/token-estimate.ts +86 -0
- package/src/lib/tool-argument-integers.ts +202 -0
- package/src/lib/translator-budget.ts +400 -0
- package/src/lib/upstream-http-version.ts +57 -0
- package/src/lib/upstream-reachability.ts +95 -0
- package/src/lib/upstream-retry.ts +392 -0
- package/src/lib/win-exec.ts +115 -0
- package/src/lib/win-paths.ts +68 -0
- package/src/lib/windows-atomic-replace.ts +156 -0
- package/src/lib/windows-elevation.ts +773 -0
- package/src/lib/windows-secret-acl.ts +854 -0
- package/src/lib/windows-service-wrappers.ts +72 -0
- package/src/lib/windows-text.ts +106 -0
- package/src/lib/windows-user-principal.ts +341 -0
- package/src/lib/winsw.ts +403 -0
- package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
- package/src/oauth/account-import/index.ts +15 -0
- package/src/oauth/account-import/parser.ts +83 -0
- package/src/oauth/account-import/registry.ts +18 -0
- package/src/oauth/account-import/service.ts +75 -0
- package/src/oauth/account-import/types.ts +91 -0
- package/src/oauth/anthropic-routing.ts +594 -0
- package/src/oauth/anthropic.ts +188 -0
- package/src/oauth/antigravity-routing.ts +151 -0
- package/src/oauth/callback-server.ts +300 -0
- package/src/oauth/chatgpt.ts +161 -0
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +252 -0
- package/src/oauth/github-copilot.ts +428 -0
- package/src/oauth/google-antigravity.ts +262 -0
- package/src/oauth/health.ts +407 -0
- package/src/oauth/index.ts +1504 -0
- package/src/oauth/key-providers.ts +124 -0
- package/src/oauth/kimi.ts +227 -0
- package/src/oauth/kiro-credentials.ts +726 -0
- package/src/oauth/kiro.ts +621 -0
- package/src/oauth/local-token-detect.ts +130 -0
- package/src/oauth/log.ts +50 -0
- package/src/oauth/login-cli.ts +223 -0
- package/src/oauth/nous.ts +798 -0
- package/src/oauth/pkce.ts +15 -0
- package/src/oauth/store.ts +728 -0
- package/src/oauth/token-guardian.ts +309 -0
- package/src/oauth/types.ts +62 -0
- package/src/oauth/xai.ts +241 -0
- package/src/providers/alibaba-region-backup.ts +75 -0
- package/src/providers/alibaba-region-migration.ts +156 -0
- package/src/providers/alibaba-region-startup.ts +36 -0
- package/src/providers/antigravity-models.ts +695 -0
- package/src/providers/antigravity-quota.ts +216 -0
- package/src/providers/api-keys.ts +140 -0
- package/src/providers/base-url-choices.ts +74 -0
- package/src/providers/codex-capacity.ts +292 -0
- package/src/providers/command-code-efforts.ts +144 -0
- package/src/providers/context-cap.ts +82 -0
- package/src/providers/cursor-pool.ts +72 -0
- package/src/providers/derive.ts +586 -0
- package/src/providers/fastwire.ts +501 -0
- package/src/providers/free-directory.ts +187 -0
- package/src/providers/github-copilot-transport.ts +56 -0
- package/src/providers/google-vertex-location.ts +14 -0
- package/src/providers/key-failover.ts +271 -0
- package/src/providers/kiro-models.ts +67 -0
- package/src/providers/label.ts +19 -0
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +449 -0
- package/src/providers/model-rename-migration.ts +255 -0
- package/src/providers/model-rename-startup.ts +28 -0
- package/src/providers/openai-sidecar.ts +243 -0
- package/src/providers/openai-tier-startup.ts +56 -0
- package/src/providers/openai-tiers.ts +423 -0
- package/src/providers/openai-virtual-models.ts +83 -0
- package/src/providers/opencode-zen-rate-limit.ts +102 -0
- package/src/providers/openrouter-routing.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +185 -0
- package/src/providers/quota.ts +2345 -0
- package/src/providers/registry.ts +2918 -0
- package/src/providers/replit/constants.ts +27 -0
- package/src/providers/replit/derive.ts +85 -0
- package/src/providers/replit/headers.ts +28 -0
- package/src/providers/replit/origin.ts +55 -0
- package/src/providers/replit/pair-install-response.ts +72 -0
- package/src/providers/replit/probe.ts +199 -0
- package/src/providers/replit/setup.ts +350 -0
- package/src/providers/request-pacing.ts +310 -0
- package/src/providers/service-tier.ts +277 -0
- package/src/providers/slug-codec.ts +103 -0
- package/src/providers/static-model-discovery.ts +86 -0
- package/src/providers/xai-responses-opt-in.ts +15 -0
- package/src/providers/xai-transport.ts +148 -0
- package/src/reasoning-effort.ts +183 -0
- package/src/responses/compaction.ts +142 -0
- package/src/responses/custom-tool-compat.ts +266 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/namespace-tool-compat.ts +355 -0
- package/src/responses/parser.ts +838 -0
- package/src/responses/provider-continuation.ts +98 -0
- package/src/responses/provider-opaque-metadata.ts +73 -0
- package/src/responses/reasoning-envelope.ts +60 -0
- package/src/responses/reasoning-replay-cache.ts +426 -0
- package/src/responses/schema.ts +165 -0
- package/src/responses/spill-store.ts +459 -0
- package/src/responses/state.ts +1433 -0
- package/src/responses/thought-signature-replay.ts +347 -0
- package/src/responses/tool-groups.ts +19 -0
- package/src/responses/tool-search-compat.ts +301 -0
- package/src/responses/truncated-stop-reason.ts +60 -0
- package/src/router.ts +761 -0
- package/src/routing/analytics.ts +378 -0
- package/src/routing/capability.ts +244 -0
- package/src/routing/compatibility/assemble.ts +73 -0
- package/src/routing/compatibility/behavior.ts +278 -0
- package/src/routing/compatibility/catalog.ts +99 -0
- package/src/routing/compatibility/endpoint.ts +52 -0
- package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
- package/src/routing/compatibility/policy.ts +181 -0
- package/src/routing/compatibility/provider-slot.ts +56 -0
- package/src/routing/compatibility/reader.ts +110 -0
- package/src/routing/compatibility/subject.ts +191 -0
- package/src/routing/compatibility/types.ts +64 -0
- package/src/routing/compatibility/version.ts +104 -0
- package/src/routing/cost.ts +77 -0
- package/src/routing/evaluator.ts +495 -0
- package/src/routing/health.ts +412 -0
- package/src/routing/history/cursor.ts +43 -0
- package/src/routing/history/indexer.ts +605 -0
- package/src/routing/history/schema.ts +72 -0
- package/src/routing/profile-namespace.ts +15 -0
- package/src/routing/profile.ts +547 -0
- package/src/routing/quota.ts +145 -0
- package/src/routing/request-evidence.ts +45 -0
- package/src/routing/trace.ts +776 -0
- package/src/server/adapter-resolve.ts +53 -0
- package/src/server/auth-cors.ts +751 -0
- package/src/server/background-lifecycle.ts +182 -0
- package/src/server/chat-completions.ts +442 -0
- package/src/server/chat-native-sse.ts +331 -0
- package/src/server/chat-native.ts +426 -0
- package/src/server/claude-messages.ts +1030 -0
- package/src/server/direct-local-http.ts +347 -0
- package/src/server/effort-policy.ts +190 -0
- package/src/server/github-copilot-responses-repair.ts +338 -0
- package/src/server/gui-static.ts +152 -0
- package/src/server/image-retry.ts +42 -0
- package/src/server/images.ts +568 -0
- package/src/server/index.ts +1813 -0
- package/src/server/lifecycle.ts +498 -0
- package/src/server/live.ts +717 -0
- package/src/server/local-management-read-client.ts +90 -0
- package/src/server/local-provider-reload-client.ts +137 -0
- package/src/server/management/agent-settings-routes.ts +1433 -0
- package/src/server/management/api-access.ts +141 -0
- package/src/server/management/api-key-usage.ts +193 -0
- package/src/server/management/body.ts +41 -0
- package/src/server/management/combo-routes.ts +263 -0
- package/src/server/management/config-routes.ts +835 -0
- package/src/server/management/context.ts +113 -0
- package/src/server/management/integration-routes.ts +498 -0
- package/src/server/management/lab-automation-routes.ts +206 -0
- package/src/server/management/lab-routes.ts +563 -0
- package/src/server/management/logs-usage-routes.ts +586 -0
- package/src/server/management/model-routes.ts +560 -0
- package/src/server/management/model-rows.ts +163 -0
- package/src/server/management/native-integration-routes.ts +769 -0
- package/src/server/management/oauth-account-routes.ts +637 -0
- package/src/server/management/provider-capability-config.ts +48 -0
- package/src/server/management/provider-routes.ts +1033 -0
- package/src/server/management/replit-provider-routes.ts +86 -0
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/routing-analytics-routes.ts +74 -0
- package/src/server/management/routing-profile-routes.ts +385 -0
- package/src/server/management/shared.ts +286 -0
- package/src/server/management/sidebar-routes.ts +106 -0
- package/src/server/management/storage-log-guard-routes.ts +186 -0
- package/src/server/management/sync-response.ts +69 -0
- package/src/server/management/system-restart.ts +435 -0
- package/src/server/management/system-routes.ts +194 -0
- package/src/server/management/usage-summary-cache.ts +94 -0
- package/src/server/management/vision-sidecar-options.ts +167 -0
- package/src/server/management/web-search-sidecar-options.ts +120 -0
- package/src/server/management-api.ts +314 -0
- package/src/server/management-auth.ts +482 -0
- package/src/server/memory-watchdog.ts +156 -0
- package/src/server/passive-route-linker.ts +66 -0
- package/src/server/port-reclaim.ts +307 -0
- package/src/server/ports.ts +156 -0
- package/src/server/proxy-liveness.ts +328 -0
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +353 -0
- package/src/server/relay.ts +1209 -0
- package/src/server/request-decompress.ts +239 -0
- package/src/server/request-log-conversation.ts +168 -0
- package/src/server/request-log.ts +1259 -0
- package/src/server/responses/agent-task-recovery-cache.ts +143 -0
- package/src/server/responses/agent-task-recovery.ts +465 -0
- package/src/server/responses/collaboration.ts +551 -0
- package/src/server/responses/compact.ts +771 -0
- package/src/server/responses/core.ts +5389 -0
- package/src/server/responses/empty-completion-guard.ts +276 -0
- package/src/server/responses/encrypted-payload.ts +331 -0
- package/src/server/responses/fetch-helpers.ts +232 -0
- package/src/server/responses/input-admission.ts +185 -0
- package/src/server/responses/pacing-overload.ts +13 -0
- package/src/server/responses/passthrough-error.ts +78 -0
- package/src/server/responses/policy-fallback.ts +178 -0
- package/src/server/responses/responses-field-backfill.ts +251 -0
- package/src/server/responses/terminal-guard.ts +251 -0
- package/src/server/responses/upstream-error.ts +53 -0
- package/src/server/responses/ws-upstream.ts +308 -0
- package/src/server/responses-custom-tool-repair.ts +282 -0
- package/src/server/responses-image-gen-repair.ts +132 -0
- package/src/server/responses-item-id-repair.ts +272 -0
- package/src/server/responses-json-events.ts +90 -0
- package/src/server/responses-model-rewrite.ts +29 -0
- package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
- package/src/server/responses-snapshot-repair.ts +621 -0
- package/src/server/responses-terminal-repair.ts +342 -0
- package/src/server/responses-tool-search-repair.ts +267 -0
- package/src/server/responses-undeclared-tool-guard.ts +153 -0
- package/src/server/responses.ts +25 -0
- package/src/server/search.ts +201 -0
- package/src/server/sse-frame-buffer.ts +292 -0
- package/src/server/sse-payload-rewrite.ts +263 -0
- package/src/server/startup-action-control.ts +315 -0
- package/src/server/startup-health-cache.ts +131 -0
- package/src/server/system-env.ts +484 -0
- package/src/server/windows-tcp-drop.ts +184 -0
- package/src/server/windows-tray-control.ts +41 -0
- package/src/server/ws-bridge.ts +472 -0
- package/src/service-manager-probe.ts +892 -0
- package/src/service.ts +3575 -0
- package/src/sidecar/auth.ts +92 -0
- package/src/sidecar/candidates.ts +83 -0
- package/src/stall-timeout.ts +20 -0
- package/src/storage/cleanup-job.ts +57 -0
- package/src/storage/cleanup.ts +3085 -0
- package/src/storage/policy-job.ts +457 -0
- package/src/storage/policy-scheduler.ts +40 -0
- package/src/storage/policy-worker.ts +59 -0
- package/src/storage/policy.ts +527 -0
- package/src/storage/restore-job.ts +299 -0
- package/src/storage/restore-worker.ts +58 -0
- package/src/storage/scanner.ts +238 -0
- package/src/storage/storage-mutation-coordinator.ts +139 -0
- package/src/storage/worker-lifecycle.ts +215 -0
- package/src/tray/assets/opencodex-tray-offline.ico +0 -0
- package/src/tray/assets/opencodex-tray-online.ico +0 -0
- package/src/tray/assets/opencodex-tray-warning.ico +0 -0
- package/src/tray/assets/opencodex-tray.png +0 -0
- package/src/tray/windows-tray.ps1 +364 -0
- package/src/tray/windows.ts +757 -0
- package/src/types/accounts.ts +37 -0
- package/src/types/config.ts +876 -0
- package/src/types/provider.ts +545 -0
- package/src/types/request.ts +384 -0
- package/src/types/tools.ts +131 -0
- package/src/types/wire.ts +80 -0
- package/src/types.ts +106 -0
- package/src/update/badge.ts +72 -0
- package/src/update/index.ts +415 -0
- package/src/update/job.ts +1887 -0
- package/src/update/notify.ts +263 -0
- package/src/update/npm-cache-preflight.d.mts +47 -0
- package/src/update/npm-cache-preflight.mjs +201 -0
- package/src/update/npm-invocation.d.mts +23 -0
- package/src/update/npm-invocation.mjs +94 -0
- package/src/update/transactional-install.d.mts +22 -0
- package/src/update/transactional-install.mjs +259 -0
- package/src/update/tray-update-plan.d.mts +18 -0
- package/src/update/tray-update-plan.mjs +38 -0
- package/src/usage/cost.ts +625 -0
- package/src/usage/debug.ts +97 -0
- package/src/usage/expected-prices.ts +416 -0
- package/src/usage/log.ts +1223 -0
- package/src/usage/summary.ts +753 -0
- package/src/usage/totals.ts +14 -0
- package/src/usage/user-cost-overlay-reconciler.ts +313 -0
- package/src/usage/user-cost-overlays.ts +314 -0
- package/src/vision/anthropic-describe.ts +189 -0
- package/src/vision/backends.ts +97 -0
- package/src/vision/describe.ts +131 -0
- package/src/vision/eligibility.ts +250 -0
- package/src/vision/index.ts +681 -0
- package/src/vision/reasoning.ts +55 -0
- package/src/vision/routed-describe.ts +175 -0
- package/src/vision/timeout-bounds.ts +9 -0
- package/src/web-search/anthropic-executor.ts +195 -0
- package/src/web-search/backends.ts +108 -0
- package/src/web-search/exa-executor.ts +88 -0
- package/src/web-search/executor.ts +113 -0
- package/src/web-search/format-result.ts +89 -0
- package/src/web-search/gemini-executor.ts +141 -0
- package/src/web-search/index.ts +331 -0
- package/src/web-search/loop.ts +896 -0
- package/src/web-search/parse.ts +315 -0
- package/src/web-search/progress-stream.ts +342 -0
- package/src/web-search/sources.ts +60 -0
- package/src/web-search/synthetic-tool.ts +47 -0
- package/src/web-search/xai-executor.ts +219 -0
|
@@ -0,0 +1,784 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quota-aware subagent model fallback (issue #374).
|
|
3
|
+
*
|
|
4
|
+
* codex-rs spawns children with the agent-role TOML `model` pinned; when that model's
|
|
5
|
+
* provider quota is exhausted the child fails immediately. This module rewrites thread_spawn
|
|
6
|
+
* requests at the proxy choke point to the next healthy model in a configured fallback chain.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { hasOwnProvider } from "../config";
|
|
11
|
+
import { isRateLimitOrQuotaFailureMessage } from "../lib/errors";
|
|
12
|
+
import type { OcxParsedRequest, OcxConfig } from "../types";
|
|
13
|
+
import { slugsEquivalent } from "../providers/slug-codec";
|
|
14
|
+
import { CODEX_HOME, getCodexHome } from "./paths";
|
|
15
|
+
import { CODEX_UNKNOWN_USAGE_SCORE, getAccountQuota } from "./quota";
|
|
16
|
+
import {
|
|
17
|
+
canAcquireCodexQuotaProbeLease,
|
|
18
|
+
codexQuotaScopeForModel,
|
|
19
|
+
computeCodexUsageScore,
|
|
20
|
+
getCodexQuotaHealthSnapshot,
|
|
21
|
+
getEffectiveActiveCodexAccountId,
|
|
22
|
+
getPoolAccountPlan,
|
|
23
|
+
isCodexAccountInCooldown,
|
|
24
|
+
} from "./routing";
|
|
25
|
+
import {
|
|
26
|
+
isCodexAccountUsable,
|
|
27
|
+
type CodexAccountUsabilityOptions,
|
|
28
|
+
} from "./account-usability";
|
|
29
|
+
import { isCodexAccountPaused } from "./account-pause";
|
|
30
|
+
import { slugEquals } from "../providers/slug-codec";
|
|
31
|
+
import { isThreadSpawnRequest } from "../server/effort-policy";
|
|
32
|
+
import { PROVIDER_REGISTRY } from "../providers/registry";
|
|
33
|
+
import {
|
|
34
|
+
CODEX_FORWARD_BASE_URL,
|
|
35
|
+
OPENAI_CODEX_PROVIDER_ID,
|
|
36
|
+
isCanonicalOpenAiForwardProvider,
|
|
37
|
+
} from "../providers/openai-tiers";
|
|
38
|
+
import { routeModel, type RouteResult } from "../router";
|
|
39
|
+
import { sweepExpiredOnWrite } from "../lib/state-store-sweeper";
|
|
40
|
+
import { codexAccountNamespaceForModel } from "./account-namespace-match";
|
|
41
|
+
import {
|
|
42
|
+
getUpstreamHostHealth,
|
|
43
|
+
normalizeUpstreamHostCircuitThreshold,
|
|
44
|
+
upstreamHostHealthKey,
|
|
45
|
+
} from "./upstream-host-health";
|
|
46
|
+
export const DEFAULT_SUBAGENT_MODEL_FALLBACK_POLL_MS = 60_000;
|
|
47
|
+
|
|
48
|
+
const CODEX_FORWARD_ORIGIN = new URL(CODEX_FORWARD_BASE_URL).origin.toLowerCase();
|
|
49
|
+
|
|
50
|
+
type SubagentQuotaPrimeFn = (config: OcxConfig, reason: string) => Promise<void>;
|
|
51
|
+
let subagentQuotaPrimeForTests: SubagentQuotaPrimeFn | null = null;
|
|
52
|
+
let quotaPrimeInFlight: Promise<void> | null = null;
|
|
53
|
+
|
|
54
|
+
type ModelHealth = {
|
|
55
|
+
unavailableUntil: number;
|
|
56
|
+
reason: string;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const modelHealth = new Map<string, ModelHealth>();
|
|
60
|
+
const quotaPrimedAt = new Map<string, number>();
|
|
61
|
+
const knownProviderIdSet = new Set(PROVIDER_REGISTRY.map(entry => entry.id.toLowerCase()));
|
|
62
|
+
|
|
63
|
+
function tryRouteFallbackModel(config: OcxConfig, model: string): RouteResult | null {
|
|
64
|
+
try {
|
|
65
|
+
return routeModel(config, model);
|
|
66
|
+
} catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function isPoolCodexRoute(route: RouteResult): boolean {
|
|
72
|
+
return route.codexAccountMode === "pool";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function healthKey(model: string, accountId: string | null, poolScoped: boolean): string {
|
|
76
|
+
const scopedAccountId = poolScoped ? accountId : null;
|
|
77
|
+
return `${scopedAccountId ?? "none"}::${model.toLowerCase()}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isDisabledFallbackModel(model: string, config: OcxConfig): boolean {
|
|
81
|
+
const disabled = config.disabledModels ?? [];
|
|
82
|
+
if (disabled.length === 0) return false;
|
|
83
|
+
if (!model.includes("/")) {
|
|
84
|
+
return disabled.some(stored => stored === model || slugEquals(stored, "openai", model));
|
|
85
|
+
}
|
|
86
|
+
const slash = model.indexOf("/");
|
|
87
|
+
const provider = model.slice(0, slash);
|
|
88
|
+
const modelId = model.slice(slash + 1);
|
|
89
|
+
if (codexAccountNamespaceForModel(config.codexAccountNamespaces, model)) {
|
|
90
|
+
return disabled.some(stored =>
|
|
91
|
+
stored === model
|
|
92
|
+
|| stored === modelId
|
|
93
|
+
|| slugEquals(stored, "openai", modelId)
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
return disabled.some(stored => stored === model || slugEquals(stored, provider, modelId));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function pollIntervalMs(config: OcxConfig): number {
|
|
100
|
+
const configured = config.subagentModelFallbackPollMs;
|
|
101
|
+
if (typeof configured !== "number" || !Number.isFinite(configured) || configured < 1_000) {
|
|
102
|
+
return DEFAULT_SUBAGENT_MODEL_FALLBACK_POLL_MS;
|
|
103
|
+
}
|
|
104
|
+
return configured;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function fallbackChainKey(model: string, namespaces: unknown): string {
|
|
108
|
+
const selector = codexAccountNamespaceForModel(namespaces, model);
|
|
109
|
+
if (!selector) return JSON.stringify(["model", model.toLowerCase()]);
|
|
110
|
+
const slash = model.indexOf("/");
|
|
111
|
+
// Selector keys are exact-case account boundaries. Keep that segment distinct while
|
|
112
|
+
// retaining legacy case-insensitive de-duplication for the native model suffix.
|
|
113
|
+
return JSON.stringify(["account", selector, model.slice(slash + 1).toLowerCase()]);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function normalizedChain(
|
|
117
|
+
primary: string,
|
|
118
|
+
config: OcxConfig,
|
|
119
|
+
extra: readonly string[] = [],
|
|
120
|
+
trailing: readonly string[] = [],
|
|
121
|
+
): string[] {
|
|
122
|
+
const chain: string[] = [];
|
|
123
|
+
const seen = new Set<string>();
|
|
124
|
+
const push = (model: string | undefined) => {
|
|
125
|
+
if (!model || model.trim() === "") return;
|
|
126
|
+
const trimmed = model.trim();
|
|
127
|
+
const key = fallbackChainKey(trimmed, config.codexAccountNamespaces);
|
|
128
|
+
if (seen.has(key)) return;
|
|
129
|
+
seen.add(key);
|
|
130
|
+
chain.push(trimmed);
|
|
131
|
+
};
|
|
132
|
+
push(primary);
|
|
133
|
+
for (const model of extra) push(model);
|
|
134
|
+
for (const model of config.subagentModelFallback ?? []) push(model);
|
|
135
|
+
for (const model of trailing) push(model);
|
|
136
|
+
return chain;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function buildSubagentModelChain(
|
|
140
|
+
primary: string,
|
|
141
|
+
config: OcxConfig,
|
|
142
|
+
extraFallback: readonly string[] = [],
|
|
143
|
+
): string[] {
|
|
144
|
+
return normalizedChain(primary, config, extraFallback);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function quotaThreshold(config: OcxConfig): number {
|
|
148
|
+
const threshold = config.autoSwitchThreshold ?? 80;
|
|
149
|
+
return threshold > 0 ? threshold : Number.POSITIVE_INFINITY;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The account routing would actually use, not just the persisted operator
|
|
154
|
+
* selection: round-robin, fill-first, failover, and priority preemption all
|
|
155
|
+
* move the cursor in memory only, so reading the raw field would check quota
|
|
156
|
+
* against an account this request is not going to touch.
|
|
157
|
+
*/
|
|
158
|
+
function activeCodexAccountId(config: OcxConfig): string | null {
|
|
159
|
+
return getEffectiveActiveCodexAccountId(config) ?? null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Resolve the account id used for pool-scoped quota/health checks.
|
|
164
|
+
* Explicit `null` means the pre-fallback preview found no usable account — do not
|
|
165
|
+
* substitute `activeCodexAccountId` (that active id may itself be unusable).
|
|
166
|
+
*/
|
|
167
|
+
function resolvePoolFallbackAccountId(
|
|
168
|
+
config: OcxConfig,
|
|
169
|
+
accountId?: string | null,
|
|
170
|
+
): string | null {
|
|
171
|
+
if (typeof accountId === "string") return accountId;
|
|
172
|
+
if (accountId === null) return null;
|
|
173
|
+
return activeCodexAccountId(config);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function resolveRouteFallbackAccountId(
|
|
177
|
+
route: RouteResult | null,
|
|
178
|
+
config: OcxConfig,
|
|
179
|
+
accountId?: string | null,
|
|
180
|
+
): string | null {
|
|
181
|
+
return route?.codexAccountId ?? resolvePoolFallbackAccountId(config, accountId);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function isRoutableFallbackModel(model: string, config: OcxConfig): boolean {
|
|
185
|
+
const slash = model.indexOf("/");
|
|
186
|
+
if (slash > 0) {
|
|
187
|
+
if (codexAccountNamespaceForModel(config.codexAccountNamespaces, model)) return true;
|
|
188
|
+
const providerName = model.slice(0, slash);
|
|
189
|
+
if (!hasOwnProvider(config.providers, providerName)) {
|
|
190
|
+
// Allow well-known "vendor/model" ids (e.g. anthropic/claude-*) to flow as
|
|
191
|
+
// raw model ids through the default provider, but reject stale/typo prefixes.
|
|
192
|
+
return knownProviderIdSet.has(providerName.toLowerCase());
|
|
193
|
+
}
|
|
194
|
+
const provider = config.providers[providerName];
|
|
195
|
+
if (provider?.disabled === true) return false;
|
|
196
|
+
}
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function isNativeModelQuotaExhausted(
|
|
201
|
+
model: string,
|
|
202
|
+
config: OcxConfig,
|
|
203
|
+
accountId?: string | null,
|
|
204
|
+
now = Date.now(),
|
|
205
|
+
): boolean {
|
|
206
|
+
const route = tryRouteFallbackModel(config, model);
|
|
207
|
+
if (!route || !isPoolCodexRoute(route)) return false;
|
|
208
|
+
const resolvedAccountId = resolveRouteFallbackAccountId(route, config, accountId);
|
|
209
|
+
if (!resolvedAccountId) return false;
|
|
210
|
+
const quota = getAccountQuota(resolvedAccountId);
|
|
211
|
+
const usage = computeCodexUsageScore(quota, getPoolAccountPlan(config, resolvedAccountId));
|
|
212
|
+
if (usage >= CODEX_UNKNOWN_USAGE_SCORE) return false;
|
|
213
|
+
return usage >= quotaThreshold(config);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function isModelHealthBlocked(
|
|
217
|
+
model: string,
|
|
218
|
+
config: OcxConfig,
|
|
219
|
+
accountId?: string | null,
|
|
220
|
+
now = Date.now(),
|
|
221
|
+
): boolean {
|
|
222
|
+
const route = tryRouteFallbackModel(config, model);
|
|
223
|
+
const poolScoped = !!route && isPoolCodexRoute(route);
|
|
224
|
+
const health = modelHealth.get(
|
|
225
|
+
healthKey(
|
|
226
|
+
model,
|
|
227
|
+
resolveRouteFallbackAccountId(route, config, accountId),
|
|
228
|
+
poolScoped,
|
|
229
|
+
),
|
|
230
|
+
);
|
|
231
|
+
return !!health && health.unavailableUntil > now;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function isSubagentModelUnavailable(
|
|
235
|
+
model: string,
|
|
236
|
+
config: OcxConfig,
|
|
237
|
+
accountId?: string | null,
|
|
238
|
+
now = Date.now(),
|
|
239
|
+
accountUsabilityOptions?: CodexAccountUsabilityOptions,
|
|
240
|
+
): boolean {
|
|
241
|
+
if (isDisabledFallbackModel(model, config)) return true;
|
|
242
|
+
if (!isRoutableFallbackModel(model, config)) return true;
|
|
243
|
+
const route = tryRouteFallbackModel(config, model);
|
|
244
|
+
if (!route || route.provider.disabled === true) return true;
|
|
245
|
+
if (isModelHealthBlocked(model, config, accountId, now)) return true;
|
|
246
|
+
if (!isPoolCodexRoute(route)) return false;
|
|
247
|
+
|
|
248
|
+
// Pool candidates need a usable account. Derive requirement from the resolved
|
|
249
|
+
// route (canonical openai defaults to pool even when codexAccountMode is omitted).
|
|
250
|
+
const resolvedAccountId = resolveRouteFallbackAccountId(route, config, accountId);
|
|
251
|
+
if (!resolvedAccountId) return true;
|
|
252
|
+
if (isCodexAccountPaused(config, resolvedAccountId)) return true;
|
|
253
|
+
if (!isCodexAccountUsable(config, resolvedAccountId, accountUsabilityOptions)) return true;
|
|
254
|
+
if (route.codexAccountId !== undefined) {
|
|
255
|
+
// An account-qualified route is pinned and cannot consume Pool's recovery-probe
|
|
256
|
+
// escape hatch. Honor both account-wide and model-scoped cooldowns so fallback
|
|
257
|
+
// advances instead of selecting a candidate that exact auth will reject.
|
|
258
|
+
const quotaScope = codexQuotaScopeForModel(route.modelId);
|
|
259
|
+
if (getCodexQuotaHealthSnapshot(resolvedAccountId, quotaScope, now) !== null) return true;
|
|
260
|
+
} else if (
|
|
261
|
+
isCodexAccountInCooldown(resolvedAccountId, now)
|
|
262
|
+
&& !canAcquireCodexQuotaProbeLease(resolvedAccountId, now)
|
|
263
|
+
) {
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
return isNativeModelQuotaExhausted(model, config, accountId, now);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function selectAvailableSubagentModel(
|
|
270
|
+
primary: string,
|
|
271
|
+
config: OcxConfig,
|
|
272
|
+
extraFallback: readonly string[] = [],
|
|
273
|
+
accountId?: string | null,
|
|
274
|
+
now = Date.now(),
|
|
275
|
+
nativeFallbackOnly = false,
|
|
276
|
+
accountUsabilityOptions?: CodexAccountUsabilityOptions,
|
|
277
|
+
trailingFallback: readonly string[] = [],
|
|
278
|
+
): { model: string; rewritten: boolean; skipped: string[] } {
|
|
279
|
+
const chain = normalizedChain(primary, config, extraFallback, trailingFallback);
|
|
280
|
+
const skipped: string[] = [];
|
|
281
|
+
for (const candidate of chain) {
|
|
282
|
+
if (nativeFallbackOnly) {
|
|
283
|
+
const route = tryRouteFallbackModel(config, candidate);
|
|
284
|
+
if (!route || !isCanonicalOpenAiForwardProvider(route.provider)) {
|
|
285
|
+
skipped.push(candidate);
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (isSubagentModelUnavailable(candidate, config, accountId, now, accountUsabilityOptions)) {
|
|
290
|
+
skipped.push(candidate);
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
return { model: candidate, rewritten: !slugsEquivalent(candidate, primary), skipped };
|
|
294
|
+
}
|
|
295
|
+
return { model: primary, rewritten: false, skipped };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export function noteSubagentModelFailure(
|
|
299
|
+
model: string,
|
|
300
|
+
message: string,
|
|
301
|
+
config: OcxConfig,
|
|
302
|
+
accountId?: string | null,
|
|
303
|
+
now = Date.now(),
|
|
304
|
+
ttlMs?: number,
|
|
305
|
+
): void {
|
|
306
|
+
const interval = ttlMs ?? DEFAULT_SUBAGENT_MODEL_FALLBACK_POLL_MS;
|
|
307
|
+
if (!isRateLimitOrQuotaFailureMessage(message)) return;
|
|
308
|
+
const route = tryRouteFallbackModel(config, model);
|
|
309
|
+
const poolScoped = !!route && isPoolCodexRoute(route);
|
|
310
|
+
modelHealth.set(
|
|
311
|
+
healthKey(
|
|
312
|
+
model,
|
|
313
|
+
resolveRouteFallbackAccountId(route, config, accountId),
|
|
314
|
+
poolScoped,
|
|
315
|
+
),
|
|
316
|
+
{
|
|
317
|
+
unavailableUntil: now + interval,
|
|
318
|
+
reason: "quota_exhausted",
|
|
319
|
+
},
|
|
320
|
+
);
|
|
321
|
+
sweepExpiredOnWrite(now);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export function sweepExpiredSubagentModelHealth(now = Date.now()): number {
|
|
325
|
+
let removed = 0;
|
|
326
|
+
for (const [key, health] of modelHealth) {
|
|
327
|
+
if (health.unavailableUntil > now) continue;
|
|
328
|
+
modelHealth.delete(key);
|
|
329
|
+
removed += 1;
|
|
330
|
+
}
|
|
331
|
+
return removed;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export function resetSubagentModelFallbackStateForTests(): void {
|
|
335
|
+
modelHealth.clear();
|
|
336
|
+
quotaPrimedAt.clear();
|
|
337
|
+
quotaPrimeInFlight = null;
|
|
338
|
+
subagentQuotaPrimeForTests = null;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** Test-only: inject the quota prime implementation used by {@link maybePrimeSubagentQuota}. */
|
|
342
|
+
export function setSubagentQuotaPrimeForTests(fn: SubagentQuotaPrimeFn | null): void {
|
|
343
|
+
subagentQuotaPrimeForTests = fn;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** Test-only: inspect shared prime TTL / in-flight state. */
|
|
347
|
+
export function getSubagentQuotaPrimeStateForTests(): {
|
|
348
|
+
primedAt: number;
|
|
349
|
+
inFlight: boolean;
|
|
350
|
+
} {
|
|
351
|
+
return {
|
|
352
|
+
primedAt: quotaPrimedAt.get("global") ?? 0,
|
|
353
|
+
inFlight: quotaPrimeInFlight !== null,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function rewriteParsedModel(parsed: OcxParsedRequest, model: string): void {
|
|
358
|
+
parsed.modelId = model;
|
|
359
|
+
if (parsed._rawBody && typeof parsed._rawBody === "object") {
|
|
360
|
+
(parsed._rawBody as { model?: string }).model = model;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const TOML_MODEL = /^(model)\s*=\s*("(?:\\.|[^"\\])*")\s*$/;
|
|
365
|
+
|
|
366
|
+
function parseTomlQuotedString(raw: string): string {
|
|
367
|
+
const trimmed = raw.trim();
|
|
368
|
+
if ((trimmed.startsWith("\"") && trimmed.endsWith("\""))
|
|
369
|
+
|| (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
370
|
+
return trimmed.slice(1, -1).replace(/\\"/g, "\"");
|
|
371
|
+
}
|
|
372
|
+
return trimmed;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function readAgentModel(filePath: string): string | null {
|
|
376
|
+
try {
|
|
377
|
+
const content = readFileSync(filePath, "utf8");
|
|
378
|
+
for (const line of content.split(/\r?\n/)) {
|
|
379
|
+
const match = line.match(TOML_MODEL);
|
|
380
|
+
if (!match) continue;
|
|
381
|
+
const model = parseTomlQuotedString(match[2] ?? "");
|
|
382
|
+
return model.trim() === "" ? null : model.trim();
|
|
383
|
+
}
|
|
384
|
+
} catch {
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export function readCodexAgentModel(role: string, codexHome = CODEX_HOME): string | null {
|
|
391
|
+
const file = join(codexHome, "agents", `${role}.toml`);
|
|
392
|
+
if (!existsSync(file)) return null;
|
|
393
|
+
return readAgentModel(file);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
export function resolveAgentModelFallbackForPrimary(
|
|
397
|
+
primary: string,
|
|
398
|
+
codexHome = CODEX_HOME,
|
|
399
|
+
namespaces?: unknown,
|
|
400
|
+
): string[] {
|
|
401
|
+
const merged: string[] = [];
|
|
402
|
+
const seen = new Set<string>();
|
|
403
|
+
const push = (model: string | null | undefined) => {
|
|
404
|
+
if (!model || model.trim() === "") return;
|
|
405
|
+
const trimmed = model.trim();
|
|
406
|
+
// Match global-chain de-dupe: keep account-selector prefixes case-sensitive when
|
|
407
|
+
// those selectors are configured, while ordinary provider/model ids stay case-insensitive.
|
|
408
|
+
const key = fallbackChainKey(trimmed, namespaces);
|
|
409
|
+
if (seen.has(key)) return;
|
|
410
|
+
seen.add(key);
|
|
411
|
+
merged.push(trimmed);
|
|
412
|
+
};
|
|
413
|
+
for (const role of listCodexAgentRoles(codexHome)) {
|
|
414
|
+
const model = readCodexAgentModel(role, codexHome);
|
|
415
|
+
if (!model || !slugsEquivalent(model, primary)) continue;
|
|
416
|
+
for (const fallback of readCodexAgentModelFallback(role, codexHome)) push(fallback);
|
|
417
|
+
}
|
|
418
|
+
return merged;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function subagentQuotaPrimeBlockedByHostCircuit(config: OcxConfig): boolean {
|
|
422
|
+
if (normalizeUpstreamHostCircuitThreshold(config.upstreamHostCircuitThreshold) === 0) return false;
|
|
423
|
+
const key = upstreamHostHealthKey(OPENAI_CODEX_PROVIDER_ID, CODEX_FORWARD_ORIGIN);
|
|
424
|
+
return getUpstreamHostHealth(key)?.cooldownUntil !== undefined;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Per-primary-model fallback chains from opencodex config (#1190).
|
|
429
|
+
*
|
|
430
|
+
* Storing `model_fallback` inside `$CODEX_HOME/agents/*.toml` makes Codex >= 0.146
|
|
431
|
+
* reject the whole role file as an unknown field. The config-keyed map is the
|
|
432
|
+
* supported home for that metadata; keys match the requested primary model id,
|
|
433
|
+
* using the same raw/encoded slug tolerance as the TOML role lookup.
|
|
434
|
+
*/
|
|
435
|
+
export function resolveConfiguredModelFallbackForPrimary(
|
|
436
|
+
primary: string,
|
|
437
|
+
config: OcxConfig,
|
|
438
|
+
): string[] {
|
|
439
|
+
const byModel = config.subagentModelFallbackByModel;
|
|
440
|
+
if (!byModel || typeof byModel !== "object") return [];
|
|
441
|
+
const entries: string[] = [];
|
|
442
|
+
const seen = new Set<string>();
|
|
443
|
+
const push = (model: string) => {
|
|
444
|
+
const trimmed = model.trim();
|
|
445
|
+
if (trimmed === "") return;
|
|
446
|
+
const key = fallbackChainKey(trimmed, config.codexAccountNamespaces);
|
|
447
|
+
if (seen.has(key)) return;
|
|
448
|
+
seen.add(key);
|
|
449
|
+
entries.push(trimmed);
|
|
450
|
+
};
|
|
451
|
+
for (const [key, chain] of Object.entries(byModel)) {
|
|
452
|
+
if (!slugsEquivalent(key, primary)) continue;
|
|
453
|
+
for (const model of chain) push(model);
|
|
454
|
+
}
|
|
455
|
+
return entries;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Best-effort quota refresh before subagent model selection.
|
|
460
|
+
* Concurrent callers share one in-flight promise. The success TTL is updated only
|
|
461
|
+
* after a successful refresh so failures remain retryable. Errors are swallowed so
|
|
462
|
+
* spawn routing can continue. When the canonical ChatGPT origin is circuit-blocked,
|
|
463
|
+
* cached quota is used instead of sending credential-bearing usage probes to the
|
|
464
|
+
* same origin before the request's final host admission check.
|
|
465
|
+
*/
|
|
466
|
+
export function maybePrimeSubagentQuota(
|
|
467
|
+
config: OcxConfig,
|
|
468
|
+
now = Date.now(),
|
|
469
|
+
options: { nativeMainReadsForbidden?: boolean } = {},
|
|
470
|
+
): Promise<void> {
|
|
471
|
+
if (options.nativeMainReadsForbidden) return Promise.resolve();
|
|
472
|
+
if (subagentQuotaPrimeBlockedByHostCircuit(config)) return Promise.resolve();
|
|
473
|
+
if (quotaPrimeInFlight) return quotaPrimeInFlight;
|
|
474
|
+
if (!shouldPrimeSubagentQuota(config, now)) return Promise.resolve();
|
|
475
|
+
|
|
476
|
+
quotaPrimeInFlight = (async () => {
|
|
477
|
+
try {
|
|
478
|
+
// Re-check after claiming single-flight ownership so a circuit opened by
|
|
479
|
+
// a concurrent request cannot race us into a fresh usage-probe pass.
|
|
480
|
+
if (subagentQuotaPrimeBlockedByHostCircuit(config)) return;
|
|
481
|
+
if (subagentQuotaPrimeForTests) {
|
|
482
|
+
await subagentQuotaPrimeForTests(config, "subagent-spawn");
|
|
483
|
+
} else {
|
|
484
|
+
const { primeCodexPoolQuotas } = await import("./auth-api");
|
|
485
|
+
await primeCodexPoolQuotas(config, "subagent-spawn");
|
|
486
|
+
}
|
|
487
|
+
quotaPrimedAt.set("global", Date.now());
|
|
488
|
+
} catch {
|
|
489
|
+
// Owning boundary: do not fail the spawn path when priming is unavailable.
|
|
490
|
+
// Leave quotaPrimedAt untouched so a later spawn can retry.
|
|
491
|
+
} finally {
|
|
492
|
+
quotaPrimeInFlight = null;
|
|
493
|
+
}
|
|
494
|
+
})();
|
|
495
|
+
return quotaPrimeInFlight;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
export function recordSubagentQuotaFailureForThreadSpawn(
|
|
499
|
+
headers: Headers,
|
|
500
|
+
model: string,
|
|
501
|
+
message: string | number,
|
|
502
|
+
config: OcxConfig,
|
|
503
|
+
accountId?: string | null,
|
|
504
|
+
now = Date.now(),
|
|
505
|
+
): void {
|
|
506
|
+
if (!isThreadSpawnRequest(headers)) return;
|
|
507
|
+
noteSubagentModelFailure(model, String(message), config, accountId, now, pollIntervalMs(config));
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
export function applySubagentModelFallback(
|
|
511
|
+
parsed: OcxParsedRequest,
|
|
512
|
+
headers: Headers,
|
|
513
|
+
config: OcxConfig,
|
|
514
|
+
accountId?: string | null,
|
|
515
|
+
now = Date.now(),
|
|
516
|
+
nativeFallbackOnly = false,
|
|
517
|
+
accountUsabilityOptions?: CodexAccountUsabilityOptions,
|
|
518
|
+
): { from?: string; to?: string; skipped?: string[] } | null {
|
|
519
|
+
if (!isThreadSpawnRequest(headers)) return null;
|
|
520
|
+
const tomlRoleFallback = resolveAgentModelFallbackForPrimary(
|
|
521
|
+
parsed.modelId,
|
|
522
|
+
getCodexHome(),
|
|
523
|
+
config.codexAccountNamespaces,
|
|
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;
|
|
530
|
+
const selection = selectAvailableSubagentModel(
|
|
531
|
+
parsed.modelId,
|
|
532
|
+
config,
|
|
533
|
+
configuredFallback,
|
|
534
|
+
accountId,
|
|
535
|
+
now,
|
|
536
|
+
nativeFallbackOnly,
|
|
537
|
+
accountUsabilityOptions,
|
|
538
|
+
tomlRoleFallback,
|
|
539
|
+
);
|
|
540
|
+
if (!selection.rewritten) return selection.skipped.length > 0
|
|
541
|
+
? { from: parsed.modelId, to: parsed.modelId, skipped: selection.skipped }
|
|
542
|
+
: null;
|
|
543
|
+
const from = parsed.modelId;
|
|
544
|
+
rewriteParsedModel(parsed, selection.model);
|
|
545
|
+
return { from, to: selection.model, skipped: selection.skipped };
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
export function subagentFallbackGuidanceText(config: OcxConfig): string {
|
|
549
|
+
const chain = config.subagentModelFallback ?? [];
|
|
550
|
+
if (chain.length === 0) return "";
|
|
551
|
+
const quoted = chain.map(model => `"${model}"`).join(", ");
|
|
552
|
+
return ` Subagent model fallback chain (priority order): ${quoted}. When the primary model is quota-exhausted, opencodex rewrites thread_spawn requests to the next available model automatically.`;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const TOML_MODEL_FALLBACK_KEY = /^\s*(?:model_fallback|"model_fallback"|'model_fallback')\s*=/;
|
|
556
|
+
|
|
557
|
+
type TomlModelFallbackField = { present: false; value: null } | { present: true; value: string[] | null };
|
|
558
|
+
|
|
559
|
+
type TomlScanState = {
|
|
560
|
+
inMultilineString: '"""' | "'''" | null;
|
|
561
|
+
arrayDepth: number;
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
/** Track TOML strings, comments, and array brackets on one line. */
|
|
565
|
+
function scanTomlLine(line: string, state: TomlScanState): void {
|
|
566
|
+
let i = 0;
|
|
567
|
+
while (i < line.length) {
|
|
568
|
+
const ch = line[i]!;
|
|
569
|
+
if (ch === "#") return;
|
|
570
|
+
if (ch === '"' || ch === "'") {
|
|
571
|
+
const delimiter = ch.repeat(3);
|
|
572
|
+
if (line.startsWith(delimiter, i)) {
|
|
573
|
+
const end = findTomlMultilineStringEnd(line, i + 3, ch);
|
|
574
|
+
if (end === -1) {
|
|
575
|
+
state.inMultilineString = delimiter as '"""' | "'''";
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
i = end + 3;
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
i++;
|
|
582
|
+
while (i < line.length) {
|
|
583
|
+
if (ch === '"' && line[i] === "\\") {
|
|
584
|
+
i += 2;
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
if (line[i] === ch) break;
|
|
588
|
+
i++;
|
|
589
|
+
}
|
|
590
|
+
i++;
|
|
591
|
+
continue;
|
|
592
|
+
}
|
|
593
|
+
if (ch === "[") state.arrayDepth++;
|
|
594
|
+
else if (ch === "]") state.arrayDepth = Math.max(0, state.arrayDepth - 1);
|
|
595
|
+
i++;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/** Parse a TOML string starting at `start`; returns the decoded value and end offset. */
|
|
600
|
+
function parseTomlStringAt(text: string, start: number): { value: string; end: number } | null {
|
|
601
|
+
const quote = text[start]!;
|
|
602
|
+
const delimiter = quote.repeat(3);
|
|
603
|
+
if (text.startsWith(delimiter, start)) {
|
|
604
|
+
const end = findTomlMultilineStringEnd(text, start + 3, quote);
|
|
605
|
+
if (end === -1) return null;
|
|
606
|
+
let value = text.slice(start + 3, end).trim();
|
|
607
|
+
if (quote === '"') value = value.replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
608
|
+
return { value, end: end + 3 };
|
|
609
|
+
}
|
|
610
|
+
let i = start + 1;
|
|
611
|
+
let value = "";
|
|
612
|
+
while (i < text.length) {
|
|
613
|
+
const ch = text[i]!;
|
|
614
|
+
if (quote === '"' && ch === "\\") {
|
|
615
|
+
const next = text[i + 1];
|
|
616
|
+
if (next === '"' || next === "\\") {
|
|
617
|
+
value += next;
|
|
618
|
+
i += 2;
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
if (ch === quote) return { value, end: i + 1 };
|
|
623
|
+
value += ch;
|
|
624
|
+
i++;
|
|
625
|
+
}
|
|
626
|
+
return null;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function findTomlMultilineStringEnd(text: string, from: number, quote: string): number {
|
|
630
|
+
const delimiter = quote.repeat(3);
|
|
631
|
+
let index = text.indexOf(delimiter, from);
|
|
632
|
+
while (index !== -1) {
|
|
633
|
+
if (quote === "'") return index;
|
|
634
|
+
let backslashes = 0;
|
|
635
|
+
for (let j = index - 1; j >= 0 && text[j] === "\\"; j--) backslashes += 1;
|
|
636
|
+
if (backslashes % 2 === 0) return index;
|
|
637
|
+
index = text.indexOf(delimiter, index + 1);
|
|
638
|
+
}
|
|
639
|
+
return -1;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/** After an array close, only horizontal whitespace, an inline comment, or the line end is valid. */
|
|
643
|
+
function isValidTomlArrayTail(text: string, from: number): boolean {
|
|
644
|
+
let i = from;
|
|
645
|
+
while (i < text.length && (text[i] === " " || text[i] === "\t")) i += 1;
|
|
646
|
+
if (i >= text.length || text[i] === "\n" || text[i] === "\r") return true;
|
|
647
|
+
if (text[i] === "#") {
|
|
648
|
+
while (i < text.length && text[i] !== "\n") i += 1;
|
|
649
|
+
return true;
|
|
650
|
+
}
|
|
651
|
+
return false;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/** Parse a TOML string-array value; null when the value is not a well-formed array of strings. */
|
|
655
|
+
function parseTomlStringArrayValue(text: string): string[] | null {
|
|
656
|
+
const values: string[] = [];
|
|
657
|
+
let i = 0;
|
|
658
|
+
const skipIgnored = () => {
|
|
659
|
+
while (i < text.length) {
|
|
660
|
+
const ch = text[i]!;
|
|
661
|
+
if (ch === "#") {
|
|
662
|
+
while (i < text.length && text[i] !== "\n") i += 1;
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
if (/\s/.test(ch)) {
|
|
666
|
+
i += 1;
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
break;
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
skipIgnored();
|
|
673
|
+
if (text[i] !== "[") return null;
|
|
674
|
+
i += 1;
|
|
675
|
+
let expectValue = true;
|
|
676
|
+
for (;;) {
|
|
677
|
+
skipIgnored();
|
|
678
|
+
if (i >= text.length) return null;
|
|
679
|
+
const ch = text[i]!;
|
|
680
|
+
if (ch === "]") {
|
|
681
|
+
if (!isValidTomlArrayTail(text, i + 1)) return null;
|
|
682
|
+
return values;
|
|
683
|
+
}
|
|
684
|
+
if (ch === ",") {
|
|
685
|
+
if (expectValue) return null; // leading or doubled comma
|
|
686
|
+
expectValue = true;
|
|
687
|
+
i += 1;
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
690
|
+
if (ch === '"' || ch === "'") {
|
|
691
|
+
if (!expectValue) return null; // adjacent strings must be comma-separated
|
|
692
|
+
const parsed = parseTomlStringAt(text, i);
|
|
693
|
+
if (!parsed) return null;
|
|
694
|
+
values.push(parsed.value);
|
|
695
|
+
i = parsed.end;
|
|
696
|
+
expectValue = false;
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
return null;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* TOML-aware, presence-aware parse of the `model_fallback` field. Quoted keys
|
|
705
|
+
* are recognized, and text inside strings (including multiline strings) is
|
|
706
|
+
* never treated as a key. `present` is true whenever the key exists, even when
|
|
707
|
+
* the value is not a readable string array; `value` is null in that case.
|
|
708
|
+
*/
|
|
709
|
+
function parseTomlModelFallbackField(content: string): TomlModelFallbackField {
|
|
710
|
+
const lines = content.split(/\r?\n/);
|
|
711
|
+
const state: TomlScanState = { inMultilineString: null, arrayDepth: 0 };
|
|
712
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
713
|
+
const line = lines[i]!;
|
|
714
|
+
if (state.inMultilineString) {
|
|
715
|
+
const end = findTomlMultilineStringEnd(line, 0, state.inMultilineString[0]!);
|
|
716
|
+
if (end === -1) continue;
|
|
717
|
+
state.inMultilineString = null;
|
|
718
|
+
scanTomlLine(line.slice(end + 3), state);
|
|
719
|
+
continue;
|
|
720
|
+
}
|
|
721
|
+
if (state.arrayDepth === 0) {
|
|
722
|
+
const key = line.match(TOML_MODEL_FALLBACK_KEY);
|
|
723
|
+
if (key) {
|
|
724
|
+
const rest = `${line.slice(key[0].length)}\n${lines.slice(i + 1).join("\n")}`;
|
|
725
|
+
return { present: true, value: parseTomlStringArrayValue(rest) };
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
scanTomlLine(line, state);
|
|
729
|
+
}
|
|
730
|
+
return { present: false, value: null };
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
export function readAgentModelFallback(filePath: string): string[] | null {
|
|
734
|
+
try {
|
|
735
|
+
const content = readFileSync(filePath, "utf8");
|
|
736
|
+
const parsed = parseTomlModelFallbackField(content);
|
|
737
|
+
return parsed.present ? parsed.value : null;
|
|
738
|
+
} catch {
|
|
739
|
+
return null;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
export function readCodexAgentModelFallback(role: string, codexHome = CODEX_HOME): string[] {
|
|
744
|
+
const file = join(codexHome, "agents", `${role}.toml`);
|
|
745
|
+
if (!existsSync(file)) return [];
|
|
746
|
+
return readAgentModelFallback(file) ?? [];
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* True when the role TOML carries a `model_fallback` key, even an empty array.
|
|
751
|
+
* Presence is what matters for the doctor scan: Codex >= 0.146 rejects the
|
|
752
|
+
* field as unknown and skips the whole role regardless of its value. Uses the
|
|
753
|
+
* same TOML-aware parse as the fallback-reading path, so quoted keys count and
|
|
754
|
+
* text inside string literals does not.
|
|
755
|
+
*/
|
|
756
|
+
export function hasCodexAgentModelFallbackField(role: string, codexHome = CODEX_HOME): boolean {
|
|
757
|
+
const file = join(codexHome, "agents", `${role}.toml`);
|
|
758
|
+
if (!existsSync(file)) return false;
|
|
759
|
+
try {
|
|
760
|
+
const content = readFileSync(file, "utf8");
|
|
761
|
+
return parseTomlModelFallbackField(content).present;
|
|
762
|
+
} catch {
|
|
763
|
+
return false;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/** Roles whose TOML still carries `model_fallback`, including empty arrays. */
|
|
768
|
+
export function scanCodexAgentRolesWithTomlModelFallback(codexHome = CODEX_HOME): string[] {
|
|
769
|
+
return listCodexAgentRoles(codexHome).filter(role => hasCodexAgentModelFallbackField(role, codexHome));
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
export function listCodexAgentRoles(codexHome = CODEX_HOME): string[] {
|
|
773
|
+
const dir = join(codexHome, "agents");
|
|
774
|
+
if (!existsSync(dir)) return [];
|
|
775
|
+
return readdirSync(dir)
|
|
776
|
+
.filter(name => name.endsWith(".toml"))
|
|
777
|
+
.map(name => name.slice(0, -".toml".length));
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/** True when a new quota prime should start (no success within the poll interval). */
|
|
781
|
+
export function shouldPrimeSubagentQuota(config: OcxConfig, now = Date.now()): boolean {
|
|
782
|
+
const last = quotaPrimedAt.get("global") ?? 0;
|
|
783
|
+
return now - last >= pollIntervalMs(config);
|
|
784
|
+
}
|