@iislee/opencodex 2.11.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/LICENSE +21 -0
- package/README.md +225 -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 +451 -0
- package/bin/package-main.mjs +9 -0
- package/gui/dist/assets/index-DTpMHS4F.js +67 -0
- package/gui/dist/assets/index-ZNVDE3C7.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/antigravity.svg +1 -0
- package/gui/dist/provider-icons/claude-color.svg +1 -0
- package/gui/dist/provider-icons/claude.svg +1 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -0
- package/gui/dist/provider-icons/copilot.svg +1 -0
- package/gui/dist/provider-icons/cursor-color.svg +2 -0
- package/gui/dist/provider-icons/cursor.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/gemini.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-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/kiro.svg +14 -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 +1 -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 +102 -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.ts +1003 -0
- package/src/adapters/azure.ts +36 -0
- package/src/adapters/base.ts +72 -0
- package/src/adapters/client-fingerprint.ts +59 -0
- package/src/adapters/cursor/arg-codec.ts +38 -0
- package/src/adapters/cursor/arg-normalize.ts +104 -0
- package/src/adapters/cursor/cursor-errors.ts +165 -0
- package/src/adapters/cursor/discovery.ts +276 -0
- package/src/adapters/cursor/effort-map.ts +127 -0
- package/src/adapters/cursor/exec-policy.ts +88 -0
- package/src/adapters/cursor/framing.ts +211 -0
- package/src/adapters/cursor/gen/agent_pb.ts +15274 -0
- package/src/adapters/cursor/kv-store.ts +52 -0
- package/src/adapters/cursor/live-models.ts +153 -0
- package/src/adapters/cursor/live-smoke-gate.ts +41 -0
- package/src/adapters/cursor/live-transport.ts +1214 -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 +55 -0
- package/src/adapters/cursor/native-exec-desktop.ts +184 -0
- package/src/adapters/cursor/native-exec-fs.ts +329 -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 +548 -0
- package/src/adapters/cursor/native-exec-tools.ts +118 -0
- package/src/adapters/cursor/native-exec.ts +576 -0
- package/src/adapters/cursor/protobuf-events.ts +563 -0
- package/src/adapters/cursor/protobuf-request.ts +714 -0
- package/src/adapters/cursor/request-builder.ts +255 -0
- package/src/adapters/cursor/thread-continuity.ts +67 -0
- package/src/adapters/cursor/tool-definitions.ts +505 -0
- package/src/adapters/cursor/transport-retry.ts +132 -0
- package/src/adapters/cursor/transport.ts +57 -0
- package/src/adapters/cursor/types.ts +52 -0
- package/src/adapters/cursor.ts +196 -0
- package/src/adapters/google-antigravity-replay.ts +303 -0
- package/src/adapters/google-antigravity-wire.ts +108 -0
- package/src/adapters/google-errors.ts +85 -0
- package/src/adapters/google-http.ts +100 -0
- package/src/adapters/google-tool-schema.ts +173 -0
- package/src/adapters/google-truncation.ts +13 -0
- package/src/adapters/google-wire-compiler.ts +232 -0
- package/src/adapters/google.ts +758 -0
- package/src/adapters/identity.ts +44 -0
- package/src/adapters/image.ts +23 -0
- package/src/adapters/kiro-constants.ts +16 -0
- package/src/adapters/kiro-errors.ts +197 -0
- package/src/adapters/kiro-events.ts +179 -0
- package/src/adapters/kiro-images.ts +129 -0
- package/src/adapters/kiro-retry.ts +312 -0
- package/src/adapters/kiro-thinking.ts +96 -0
- package/src/adapters/kiro-tool-fallback.ts +36 -0
- package/src/adapters/kiro-tools.ts +215 -0
- package/src/adapters/kiro-truncation.ts +33 -0
- package/src/adapters/kiro-wire.ts +129 -0
- package/src/adapters/kiro.ts +1898 -0
- package/src/adapters/mimo-free.ts +263 -0
- package/src/adapters/openai-chat.ts +1005 -0
- package/src/adapters/openai-responses.ts +1137 -0
- package/src/adapters/run-turn-queue.ts +114 -0
- package/src/adapters/tool-catalog-nudge.ts +71 -0
- package/src/adapters/upstream-http-error.ts +48 -0
- package/src/bridge.ts +1619 -0
- package/src/chat/inbound.ts +295 -0
- package/src/chat/outbound.ts +765 -0
- package/src/claude/agents-inject.ts +243 -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 +189 -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 +381 -0
- package/src/claude/desktop-health.ts +26 -0
- package/src/claude/desktop-profile.ts +263 -0
- package/src/claude/gateway-cache.ts +70 -0
- package/src/claude/inbound-debug.ts +163 -0
- package/src/claude/inbound.ts +509 -0
- package/src/claude/model-info.ts +151 -0
- package/src/claude/outbound.ts +872 -0
- package/src/cli/access.ts +108 -0
- package/src/cli/account-api.ts +268 -0
- package/src/cli/account-auth.ts +223 -0
- package/src/cli/account-extended.ts +350 -0
- package/src/cli/account.ts +275 -0
- package/src/cli/agent-driven.ts +70 -0
- package/src/cli/agent.ts +184 -0
- package/src/cli/catalog-prewarm.ts +27 -0
- package/src/cli/claude-desktop.ts +188 -0
- package/src/cli/claude.ts +286 -0
- package/src/cli/codex-shim-autorestore.ts +45 -0
- package/src/cli/combo.ts +119 -0
- package/src/cli/config-command.ts +145 -0
- package/src/cli/debug.ts +228 -0
- package/src/cli/doctor.ts +930 -0
- package/src/cli/export-command.ts +187 -0
- package/src/cli/help.ts +354 -0
- package/src/cli/index.ts +1113 -0
- package/src/cli/init.ts +224 -0
- package/src/cli/integrations.ts +142 -0
- package/src/cli/interactive-confirm.ts +133 -0
- package/src/cli/internal-dispatch.ts +20 -0
- package/src/cli/models-runtime.ts +212 -0
- package/src/cli/models.ts +336 -0
- package/src/cli/observe.ts +117 -0
- package/src/cli/opencode.ts +586 -0
- package/src/cli/pi.ts +188 -0
- package/src/cli/provider-runtime.ts +162 -0
- package/src/cli/provider.ts +463 -0
- package/src/cli/runtime-api.ts +325 -0
- package/src/cli/star-prompt.ts +155 -0
- package/src/cli/status-oauth.ts +78 -0
- package/src/cli/status.ts +321 -0
- package/src/cli/sync-cloud.ts +283 -0
- package/src/cli/system-command.ts +112 -0
- package/src/cli/tray-proxy.ts +52 -0
- package/src/cli/v2.ts +173 -0
- package/src/cli.ts +10 -0
- package/src/clients/config-export.ts +377 -0
- package/src/clients/effective-status.ts +385 -0
- package/src/clients/probes/agy.ts +55 -0
- package/src/clients/probes/cc-switch.ts +110 -0
- package/src/clients/probes/claude.ts +90 -0
- package/src/clients/probes/codex.ts +125 -0
- package/src/clients/probes/grok.ts +29 -0
- package/src/clients/probes/opencode.ts +109 -0
- package/src/clients/probes/paseo.ts +55 -0
- package/src/clients/probes/pi.ts +55 -0
- package/src/cloud/onedrive-auth.ts +666 -0
- package/src/cloud/onedrive-graph.ts +108 -0
- package/src/cloud/settings.ts +75 -0
- package/src/cloud/sync.ts +212 -0
- package/src/cloud/types.ts +56 -0
- package/src/cloud/vault.ts +89 -0
- package/src/codex/account-id.ts +34 -0
- package/src/codex/account-label.ts +34 -0
- package/src/codex/account-lifecycle.ts +55 -0
- package/src/codex/account-namespace-match.ts +63 -0
- package/src/codex/account-namespaces.ts +149 -0
- package/src/codex/account-pause.ts +20 -0
- package/src/codex/account-runtime-state.ts +31 -0
- package/src/codex/account-store.ts +517 -0
- package/src/codex/account-usability.ts +20 -0
- package/src/codex/app-server-processes.ts +756 -0
- package/src/codex/auth-api.ts +1540 -0
- package/src/codex/auth-collision.ts +107 -0
- package/src/codex/auth-context.ts +352 -0
- package/src/codex/autostart-health.ts +149 -0
- package/src/codex/catalog/aggregation.ts +378 -0
- package/src/codex/catalog/bundled.ts +251 -0
- package/src/codex/catalog/effort.ts +355 -0
- package/src/codex/catalog/metadata.ts +180 -0
- package/src/codex/catalog/parsing.ts +456 -0
- package/src/codex/catalog/provider-fetch.ts +902 -0
- package/src/codex/catalog/sync.ts +620 -0
- package/src/codex/catalog.ts +12 -0
- package/src/codex/data/upstream-models.json +830 -0
- package/src/codex/exec-invocation.ts +22 -0
- package/src/codex/features.ts +969 -0
- package/src/codex/history-migration-guardian.ts +102 -0
- package/src/codex/history-provider.ts +776 -0
- package/src/codex/home.ts +206 -0
- package/src/codex/inject.ts +799 -0
- package/src/codex/injected-marker.ts +72 -0
- package/src/codex/journal.ts +163 -0
- package/src/codex/main-account-cache.ts +32 -0
- package/src/codex/main-account.ts +40 -0
- package/src/codex/model-cache.ts +227 -0
- package/src/codex/paths.ts +65 -0
- package/src/codex/plugins-doctor.ts +242 -0
- package/src/codex/pool-rotation.ts +225 -0
- package/src/codex/project-config-warnings.ts +411 -0
- package/src/codex/quota.ts +411 -0
- package/src/codex/refresh.ts +53 -0
- package/src/codex/routing.ts +1477 -0
- package/src/codex/runtime.ts +538 -0
- package/src/codex/shim.ts +1189 -0
- package/src/codex/subagent-defaults.ts +550 -0
- package/src/codex/subagent-model-fallback.ts +469 -0
- package/src/codex/sync.ts +130 -0
- package/src/codex/warmup.ts +192 -0
- package/src/codex/websocket-registry.ts +100 -0
- package/src/combos/failover.ts +140 -0
- package/src/combos/index.ts +41 -0
- package/src/combos/request.ts +62 -0
- package/src/combos/resolve.ts +232 -0
- package/src/combos/types.ts +326 -0
- package/src/config.ts +2356 -0
- package/src/generated/jawcode-model-metadata.ts +104 -0
- package/src/github/star-state.ts +203 -0
- package/src/grok/inject.ts +545 -0
- package/src/grok/status.ts +121 -0
- package/src/grok/sync.ts +103 -0
- package/src/grok/usage-hook/report.mjs +348 -0
- package/src/grok/usage-hook.ts +278 -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 +829 -0
- package/src/images/plan.ts +133 -0
- package/src/images/synthetic-tool.ts +133 -0
- package/src/images/types.ts +41 -0
- package/src/images/xai-client.ts +141 -0
- package/src/images/xai-video-client.ts +163 -0
- package/src/index.ts +22 -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 +173 -0
- package/src/lib/app-owned-memory.ts +265 -0
- package/src/lib/bounded-body.ts +202 -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 +71 -0
- package/src/lib/bun-stream-caps.ts +126 -0
- package/src/lib/config-ownership.ts +360 -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 +316 -0
- package/src/lib/errors.ts +364 -0
- package/src/lib/eventstream-decoder.ts +253 -0
- package/src/lib/gcp-adc.ts +341 -0
- package/src/lib/injection-debug-log.ts +58 -0
- package/src/lib/open-url.ts +25 -0
- package/src/lib/pinned-http.ts +151 -0
- package/src/lib/privacy.ts +20 -0
- package/src/lib/process-control.ts +165 -0
- package/src/lib/provider-outbound.ts +170 -0
- package/src/lib/provider-url.ts +14 -0
- package/src/lib/proxy-env.ts +18 -0
- package/src/lib/redact.ts +105 -0
- package/src/lib/retry-after.ts +55 -0
- package/src/lib/service-secrets.ts +25 -0
- package/src/lib/shadow-call.ts +30 -0
- package/src/lib/sidecar-tracker.ts +52 -0
- package/src/lib/sse-decoder.ts +323 -0
- package/src/lib/state-store-registrations.ts +109 -0
- package/src/lib/state-store-sweeper.ts +184 -0
- package/src/lib/test-home-guard.ts +90 -0
- package/src/lib/token-estimate.ts +69 -0
- package/src/lib/translator-budget.ts +356 -0
- package/src/lib/upstream-retry.ts +239 -0
- package/src/lib/win-exec.ts +115 -0
- package/src/lib/win-paths.ts +68 -0
- package/src/lib/windows-elevation.ts +705 -0
- package/src/lib/windows-secret-acl.ts +514 -0
- package/src/lib/winsw.ts +375 -0
- package/src/oauth/anthropic-routing.ts +594 -0
- package/src/oauth/anthropic.ts +177 -0
- package/src/oauth/callback-server.ts +294 -0
- package/src/oauth/chatgpt.ts +150 -0
- package/src/oauth/cursor.ts +211 -0
- package/src/oauth/github-copilot.ts +428 -0
- package/src/oauth/google-antigravity.ts +230 -0
- package/src/oauth/health.ts +399 -0
- package/src/oauth/index.ts +1174 -0
- package/src/oauth/key-providers.ts +108 -0
- package/src/oauth/kimi.ts +213 -0
- package/src/oauth/kiro-credentials.ts +726 -0
- package/src/oauth/kiro.ts +577 -0
- package/src/oauth/local-token-detect.ts +121 -0
- package/src/oauth/log.ts +48 -0
- package/src/oauth/login-cli.ts +163 -0
- package/src/oauth/pkce.ts +15 -0
- package/src/oauth/store.ts +630 -0
- package/src/oauth/token-guardian.ts +303 -0
- package/src/oauth/types.ts +62 -0
- package/src/oauth/xai.ts +241 -0
- package/src/pi/extensions.ts +72 -0
- package/src/pi/home.ts +42 -0
- package/src/pi/index.ts +40 -0
- package/src/pi/models.ts +278 -0
- package/src/pi/packages.ts +219 -0
- package/src/pi/settings.ts +365 -0
- package/src/pi/status.ts +68 -0
- package/src/pi/sync.ts +75 -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 +205 -0
- package/src/providers/api-keys.ts +140 -0
- package/src/providers/base-url-choices.ts +64 -0
- package/src/providers/context-cap.ts +65 -0
- package/src/providers/derive.ts +339 -0
- package/src/providers/free-directory.ts +184 -0
- package/src/providers/github-copilot-transport.ts +56 -0
- package/src/providers/key-failover.ts +203 -0
- package/src/providers/kiro-models.ts +67 -0
- package/src/providers/label.ts +19 -0
- package/src/providers/model-discovery.ts +356 -0
- package/src/providers/openai-sidecar.ts +175 -0
- package/src/providers/openai-tier-startup.ts +27 -0
- package/src/providers/openai-tiers.ts +301 -0
- package/src/providers/openai-virtual-models.ts +82 -0
- package/src/providers/openrouter-routing.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +150 -0
- package/src/providers/quota.ts +1260 -0
- package/src/providers/registry.ts +1600 -0
- package/src/providers/slug-codec.ts +67 -0
- package/src/providers/xai-transport.ts +141 -0
- package/src/reasoning-effort.ts +135 -0
- package/src/responses/compaction.ts +117 -0
- package/src/responses/parser.ts +656 -0
- package/src/responses/reasoning-envelope.ts +52 -0
- package/src/responses/schema.ts +159 -0
- package/src/responses/spill-store.ts +394 -0
- package/src/responses/state.ts +895 -0
- package/src/responses/tool-groups.ts +19 -0
- package/src/router.ts +425 -0
- package/src/server/adapter-resolve.ts +80 -0
- package/src/server/auth-cors.ts +530 -0
- package/src/server/chat-completions.ts +368 -0
- package/src/server/claude-messages.ts +914 -0
- package/src/server/effort-policy.ts +172 -0
- package/src/server/gui-static.ts +123 -0
- package/src/server/image-retry.ts +42 -0
- package/src/server/images.ts +476 -0
- package/src/server/index.ts +1126 -0
- package/src/server/lifecycle.ts +227 -0
- package/src/server/live.ts +598 -0
- package/src/server/management/agent-settings-routes.ts +1169 -0
- package/src/server/management/api-access.ts +141 -0
- package/src/server/management/api-key-usage.ts +167 -0
- package/src/server/management/body.ts +35 -0
- package/src/server/management/clients-routes.ts +63 -0
- package/src/server/management/cloud-sync-routes.ts +266 -0
- package/src/server/management/combo-routes.ts +220 -0
- package/src/server/management/config-routes.ts +422 -0
- package/src/server/management/context.ts +31 -0
- package/src/server/management/logs-usage-routes.ts +707 -0
- package/src/server/management/model-routes.ts +525 -0
- package/src/server/management/oauth-account-routes.ts +563 -0
- package/src/server/management/provider-routes.ts +556 -0
- package/src/server/management/shared.ts +277 -0
- package/src/server/management/sidebar-routes.ts +90 -0
- package/src/server/management/system-restart.ts +179 -0
- package/src/server/management/system-routes.ts +117 -0
- package/src/server/management/usage-summary-cache.ts +86 -0
- package/src/server/management-api.ts +215 -0
- package/src/server/management-auth.ts +267 -0
- package/src/server/memory-watchdog.ts +156 -0
- package/src/server/port-reclaim.ts +307 -0
- package/src/server/ports.ts +116 -0
- package/src/server/proxy-liveness.ts +201 -0
- package/src/server/relay-eager.ts +313 -0
- package/src/server/relay.ts +1049 -0
- package/src/server/request-decompress.ts +132 -0
- package/src/server/request-log-conversation.ts +168 -0
- package/src/server/request-log.ts +1046 -0
- package/src/server/responses/collaboration.ts +354 -0
- package/src/server/responses/compact.ts +384 -0
- package/src/server/responses/core.ts +2758 -0
- package/src/server/responses/encrypted-payload.ts +308 -0
- package/src/server/responses/fetch-helpers.ts +157 -0
- package/src/server/responses/passthrough-error.ts +78 -0
- package/src/server/responses/terminal-guard.ts +230 -0
- package/src/server/responses/upstream-error.ts +48 -0
- package/src/server/responses-image-gen-repair.ts +132 -0
- package/src/server/responses-item-id-repair.ts +224 -0
- package/src/server/responses.ts +9 -0
- package/src/server/search.ts +136 -0
- package/src/server/sse-payload-rewrite.ts +175 -0
- package/src/server/startup-action-control.ts +308 -0
- package/src/server/startup-health-cache.ts +113 -0
- package/src/server/system-env.ts +413 -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 +471 -0
- package/src/service.ts +2554 -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 +290 -0
- package/src/tray/windows.ts +730 -0
- package/src/types.ts +1237 -0
- package/src/update/badge.ts +72 -0
- package/src/update/index.ts +407 -0
- package/src/update/job.ts +1520 -0
- package/src/update/notify.ts +257 -0
- package/src/update/npm-invocation.d.mts +23 -0
- package/src/update/npm-invocation.mjs +94 -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 +0 -0
- package/src/usage/debug.ts +97 -0
- package/src/usage/expected-prices.ts +164 -0
- package/src/usage/log.ts +658 -0
- package/src/usage/summary.ts +585 -0
- package/src/usage/totals.ts +14 -0
- package/src/vision/anthropic-describe.ts +185 -0
- package/src/vision/describe.ts +125 -0
- package/src/vision/index.ts +467 -0
- package/src/web-search/anthropic-executor.ts +189 -0
- package/src/web-search/executor.ts +105 -0
- package/src/web-search/format-result.ts +89 -0
- package/src/web-search/index.ts +196 -0
- package/src/web-search/loop.ts +664 -0
- package/src/web-search/parse.ts +220 -0
- package/src/web-search/progress-stream.ts +342 -0
- package/src/web-search/synthetic-tool.ts +47 -0
|
@@ -0,0 +1,469 @@
|
|
|
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
|
+
computeCodexUsageScore,
|
|
19
|
+
getPoolAccountPlan,
|
|
20
|
+
isCodexAccountInCooldown,
|
|
21
|
+
} from "./routing";
|
|
22
|
+
import { isCodexAccountUsable } from "./account-usability";
|
|
23
|
+
import { isCodexAccountPaused } from "./account-pause";
|
|
24
|
+
import { slugEquals } from "../providers/slug-codec";
|
|
25
|
+
import { isThreadSpawnRequest } from "../server/effort-policy";
|
|
26
|
+
import { PROVIDER_REGISTRY } from "../providers/registry";
|
|
27
|
+
import { isCanonicalOpenAiForwardProvider } from "../providers/openai-tiers";
|
|
28
|
+
import { routeModel, type RouteResult } from "../router";
|
|
29
|
+
import { sweepExpiredOnWrite } from "../lib/state-store-sweeper";
|
|
30
|
+
export const DEFAULT_SUBAGENT_MODEL_FALLBACK_POLL_MS = 60_000;
|
|
31
|
+
|
|
32
|
+
type SubagentQuotaPrimeFn = (config: OcxConfig, reason: string) => Promise<void>;
|
|
33
|
+
let subagentQuotaPrimeForTests: SubagentQuotaPrimeFn | null = null;
|
|
34
|
+
let quotaPrimeInFlight: Promise<void> | null = null;
|
|
35
|
+
|
|
36
|
+
type ModelHealth = {
|
|
37
|
+
unavailableUntil: number;
|
|
38
|
+
reason: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const modelHealth = new Map<string, ModelHealth>();
|
|
42
|
+
const quotaPrimedAt = new Map<string, number>();
|
|
43
|
+
const knownProviderIdSet = new Set(PROVIDER_REGISTRY.map(entry => entry.id.toLowerCase()));
|
|
44
|
+
|
|
45
|
+
function tryRouteFallbackModel(config: OcxConfig, model: string): RouteResult | null {
|
|
46
|
+
try {
|
|
47
|
+
return routeModel(config, model);
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isPoolCodexRoute(route: RouteResult): boolean {
|
|
54
|
+
return route.codexAccountMode === "pool";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function healthKey(model: string, accountId: string | null, poolScoped: boolean): string {
|
|
58
|
+
const scopedAccountId = poolScoped ? accountId : null;
|
|
59
|
+
return `${scopedAccountId ?? "none"}::${model.toLowerCase()}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isDisabledFallbackModel(model: string, config: OcxConfig): boolean {
|
|
63
|
+
const disabled = config.disabledModels ?? [];
|
|
64
|
+
if (disabled.length === 0) return false;
|
|
65
|
+
if (!model.includes("/")) {
|
|
66
|
+
return disabled.some(stored => stored === model || slugEquals(stored, "openai", model));
|
|
67
|
+
}
|
|
68
|
+
const slash = model.indexOf("/");
|
|
69
|
+
const provider = model.slice(0, slash);
|
|
70
|
+
const modelId = model.slice(slash + 1);
|
|
71
|
+
return disabled.some(stored => stored === model || slugEquals(stored, provider, modelId));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function pollIntervalMs(config: OcxConfig): number {
|
|
75
|
+
const configured = config.subagentModelFallbackPollMs;
|
|
76
|
+
if (typeof configured !== "number" || !Number.isFinite(configured) || configured < 1_000) {
|
|
77
|
+
return DEFAULT_SUBAGENT_MODEL_FALLBACK_POLL_MS;
|
|
78
|
+
}
|
|
79
|
+
return configured;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function normalizedChain(primary: string, config: OcxConfig, extra: readonly string[] = []): string[] {
|
|
83
|
+
const chain: string[] = [];
|
|
84
|
+
const seen = new Set<string>();
|
|
85
|
+
const push = (model: string | undefined) => {
|
|
86
|
+
if (!model || model.trim() === "") return;
|
|
87
|
+
const trimmed = model.trim();
|
|
88
|
+
const key = trimmed.toLowerCase();
|
|
89
|
+
if (seen.has(key)) return;
|
|
90
|
+
seen.add(key);
|
|
91
|
+
chain.push(trimmed);
|
|
92
|
+
};
|
|
93
|
+
push(primary);
|
|
94
|
+
for (const model of extra) push(model);
|
|
95
|
+
for (const model of config.subagentModelFallback ?? []) push(model);
|
|
96
|
+
return chain;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function buildSubagentModelChain(
|
|
100
|
+
primary: string,
|
|
101
|
+
config: OcxConfig,
|
|
102
|
+
extraFallback: readonly string[] = [],
|
|
103
|
+
): string[] {
|
|
104
|
+
return normalizedChain(primary, config, extraFallback);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function quotaThreshold(config: OcxConfig): number {
|
|
108
|
+
const threshold = config.autoSwitchThreshold ?? 80;
|
|
109
|
+
return threshold > 0 ? threshold : Number.POSITIVE_INFINITY;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function activeCodexAccountId(config: OcxConfig): string | null {
|
|
113
|
+
return config.activeCodexAccountId ?? null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Resolve the account id used for pool-scoped quota/health checks.
|
|
118
|
+
* Explicit `null` means the pre-fallback preview found no usable account — do not
|
|
119
|
+
* substitute `activeCodexAccountId` (that active id may itself be unusable).
|
|
120
|
+
*/
|
|
121
|
+
function resolvePoolFallbackAccountId(
|
|
122
|
+
config: OcxConfig,
|
|
123
|
+
accountId?: string | null,
|
|
124
|
+
): string | null {
|
|
125
|
+
if (typeof accountId === "string") return accountId;
|
|
126
|
+
if (accountId === null) return null;
|
|
127
|
+
return activeCodexAccountId(config);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function isRoutableFallbackModel(model: string, config: OcxConfig): boolean {
|
|
131
|
+
const slash = model.indexOf("/");
|
|
132
|
+
if (slash > 0) {
|
|
133
|
+
const providerName = model.slice(0, slash);
|
|
134
|
+
if (!hasOwnProvider(config.providers, providerName)) {
|
|
135
|
+
// Allow well-known "vendor/model" ids (e.g. anthropic/claude-*) to flow as
|
|
136
|
+
// raw model ids through the default provider, but reject stale/typo prefixes.
|
|
137
|
+
return knownProviderIdSet.has(providerName.toLowerCase());
|
|
138
|
+
}
|
|
139
|
+
const provider = config.providers[providerName];
|
|
140
|
+
if (provider?.disabled === true) return false;
|
|
141
|
+
}
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function isNativeModelQuotaExhausted(
|
|
146
|
+
model: string,
|
|
147
|
+
config: OcxConfig,
|
|
148
|
+
accountId?: string | null,
|
|
149
|
+
now = Date.now(),
|
|
150
|
+
): boolean {
|
|
151
|
+
const route = tryRouteFallbackModel(config, model);
|
|
152
|
+
if (!route || !isPoolCodexRoute(route)) return false;
|
|
153
|
+
const resolvedAccountId = resolvePoolFallbackAccountId(config, accountId);
|
|
154
|
+
if (!resolvedAccountId) return false;
|
|
155
|
+
const quota = getAccountQuota(resolvedAccountId);
|
|
156
|
+
const usage = computeCodexUsageScore(quota, getPoolAccountPlan(config, resolvedAccountId));
|
|
157
|
+
if (usage >= CODEX_UNKNOWN_USAGE_SCORE) return false;
|
|
158
|
+
return usage >= quotaThreshold(config);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function isModelHealthBlocked(
|
|
162
|
+
model: string,
|
|
163
|
+
config: OcxConfig,
|
|
164
|
+
accountId?: string | null,
|
|
165
|
+
now = Date.now(),
|
|
166
|
+
): boolean {
|
|
167
|
+
const route = tryRouteFallbackModel(config, model);
|
|
168
|
+
const poolScoped = !!route && isPoolCodexRoute(route);
|
|
169
|
+
const health = modelHealth.get(
|
|
170
|
+
healthKey(model, resolvePoolFallbackAccountId(config, accountId), poolScoped),
|
|
171
|
+
);
|
|
172
|
+
return !!health && health.unavailableUntil > now;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function isSubagentModelUnavailable(
|
|
176
|
+
model: string,
|
|
177
|
+
config: OcxConfig,
|
|
178
|
+
accountId?: string | null,
|
|
179
|
+
now = Date.now(),
|
|
180
|
+
): boolean {
|
|
181
|
+
if (isDisabledFallbackModel(model, config)) return true;
|
|
182
|
+
if (!isRoutableFallbackModel(model, config)) return true;
|
|
183
|
+
const route = tryRouteFallbackModel(config, model);
|
|
184
|
+
if (!route || route.provider.disabled === true) return true;
|
|
185
|
+
if (isModelHealthBlocked(model, config, accountId, now)) return true;
|
|
186
|
+
if (!isPoolCodexRoute(route)) return false;
|
|
187
|
+
|
|
188
|
+
// Pool candidates need a usable account. Derive requirement from the resolved
|
|
189
|
+
// route (canonical openai defaults to pool even when codexAccountMode is omitted).
|
|
190
|
+
const resolvedAccountId = resolvePoolFallbackAccountId(config, accountId);
|
|
191
|
+
if (!resolvedAccountId) return true;
|
|
192
|
+
if (isCodexAccountPaused(config, resolvedAccountId)) return true;
|
|
193
|
+
if (!isCodexAccountUsable(config, resolvedAccountId)) return true;
|
|
194
|
+
if (
|
|
195
|
+
isCodexAccountInCooldown(resolvedAccountId, now)
|
|
196
|
+
&& !canAcquireCodexQuotaProbeLease(resolvedAccountId, now)
|
|
197
|
+
) {
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
return isNativeModelQuotaExhausted(model, config, accountId, now);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function selectAvailableSubagentModel(
|
|
204
|
+
primary: string,
|
|
205
|
+
config: OcxConfig,
|
|
206
|
+
extraFallback: readonly string[] = [],
|
|
207
|
+
accountId?: string | null,
|
|
208
|
+
now = Date.now(),
|
|
209
|
+
nativeFallbackOnly = false,
|
|
210
|
+
): { model: string; rewritten: boolean; skipped: string[] } {
|
|
211
|
+
const chain = normalizedChain(primary, config, extraFallback);
|
|
212
|
+
const skipped: string[] = [];
|
|
213
|
+
for (const candidate of chain) {
|
|
214
|
+
if (nativeFallbackOnly) {
|
|
215
|
+
const route = tryRouteFallbackModel(config, candidate);
|
|
216
|
+
if (!route || !isCanonicalOpenAiForwardProvider(route.provider)) {
|
|
217
|
+
skipped.push(candidate);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (isSubagentModelUnavailable(candidate, config, accountId, now)) {
|
|
222
|
+
skipped.push(candidate);
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
return { model: candidate, rewritten: !slugsEquivalent(candidate, primary), skipped };
|
|
226
|
+
}
|
|
227
|
+
return { model: primary, rewritten: false, skipped };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function noteSubagentModelFailure(
|
|
231
|
+
model: string,
|
|
232
|
+
message: string,
|
|
233
|
+
config: OcxConfig,
|
|
234
|
+
accountId?: string | null,
|
|
235
|
+
now = Date.now(),
|
|
236
|
+
ttlMs?: number,
|
|
237
|
+
): void {
|
|
238
|
+
const interval = ttlMs ?? DEFAULT_SUBAGENT_MODEL_FALLBACK_POLL_MS;
|
|
239
|
+
if (!isRateLimitOrQuotaFailureMessage(message)) return;
|
|
240
|
+
const route = tryRouteFallbackModel(config, model);
|
|
241
|
+
const poolScoped = !!route && isPoolCodexRoute(route);
|
|
242
|
+
modelHealth.set(
|
|
243
|
+
healthKey(model, resolvePoolFallbackAccountId(config, accountId), poolScoped),
|
|
244
|
+
{
|
|
245
|
+
unavailableUntil: now + interval,
|
|
246
|
+
reason: "quota_exhausted",
|
|
247
|
+
},
|
|
248
|
+
);
|
|
249
|
+
sweepExpiredOnWrite(now);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function sweepExpiredSubagentModelHealth(now = Date.now()): number {
|
|
253
|
+
let removed = 0;
|
|
254
|
+
for (const [key, health] of modelHealth) {
|
|
255
|
+
if (health.unavailableUntil > now) continue;
|
|
256
|
+
modelHealth.delete(key);
|
|
257
|
+
removed += 1;
|
|
258
|
+
}
|
|
259
|
+
return removed;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function resetSubagentModelFallbackStateForTests(): void {
|
|
263
|
+
modelHealth.clear();
|
|
264
|
+
quotaPrimedAt.clear();
|
|
265
|
+
quotaPrimeInFlight = null;
|
|
266
|
+
subagentQuotaPrimeForTests = null;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** Test-only: inject the quota prime implementation used by {@link maybePrimeSubagentQuota}. */
|
|
270
|
+
export function setSubagentQuotaPrimeForTests(fn: SubagentQuotaPrimeFn | null): void {
|
|
271
|
+
subagentQuotaPrimeForTests = fn;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Test-only: inspect shared prime TTL / in-flight state. */
|
|
275
|
+
export function getSubagentQuotaPrimeStateForTests(): {
|
|
276
|
+
primedAt: number;
|
|
277
|
+
inFlight: boolean;
|
|
278
|
+
} {
|
|
279
|
+
return {
|
|
280
|
+
primedAt: quotaPrimedAt.get("global") ?? 0,
|
|
281
|
+
inFlight: quotaPrimeInFlight !== null,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function rewriteParsedModel(parsed: OcxParsedRequest, model: string): void {
|
|
286
|
+
parsed.modelId = model;
|
|
287
|
+
if (parsed._rawBody && typeof parsed._rawBody === "object") {
|
|
288
|
+
(parsed._rawBody as { model?: string }).model = model;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const TOML_MODEL = /^(model)\s*=\s*("(?:\\.|[^"\\])*")\s*$/;
|
|
293
|
+
|
|
294
|
+
function parseTomlQuotedString(raw: string): string {
|
|
295
|
+
const trimmed = raw.trim();
|
|
296
|
+
if ((trimmed.startsWith("\"") && trimmed.endsWith("\""))
|
|
297
|
+
|| (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
298
|
+
return trimmed.slice(1, -1).replace(/\\"/g, "\"");
|
|
299
|
+
}
|
|
300
|
+
return trimmed;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function readAgentModel(filePath: string): string | null {
|
|
304
|
+
try {
|
|
305
|
+
const content = readFileSync(filePath, "utf8");
|
|
306
|
+
for (const line of content.split(/\r?\n/)) {
|
|
307
|
+
const match = line.match(TOML_MODEL);
|
|
308
|
+
if (!match) continue;
|
|
309
|
+
const model = parseTomlQuotedString(match[2] ?? "");
|
|
310
|
+
return model.trim() === "" ? null : model.trim();
|
|
311
|
+
}
|
|
312
|
+
} catch {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export function readCodexAgentModel(role: string, codexHome = CODEX_HOME): string | null {
|
|
319
|
+
const file = join(codexHome, "agents", `${role}.toml`);
|
|
320
|
+
if (!existsSync(file)) return null;
|
|
321
|
+
return readAgentModel(file);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
export function resolveAgentModelFallbackForPrimary(
|
|
325
|
+
primary: string,
|
|
326
|
+
codexHome = CODEX_HOME,
|
|
327
|
+
): string[] {
|
|
328
|
+
const merged: string[] = [];
|
|
329
|
+
const seen = new Set<string>();
|
|
330
|
+
const push = (model: string | null | undefined) => {
|
|
331
|
+
if (!model || model.trim() === "") return;
|
|
332
|
+
const trimmed = model.trim();
|
|
333
|
+
const key = trimmed.toLowerCase();
|
|
334
|
+
if (seen.has(key)) return;
|
|
335
|
+
seen.add(key);
|
|
336
|
+
merged.push(trimmed);
|
|
337
|
+
};
|
|
338
|
+
for (const role of listCodexAgentRoles(codexHome)) {
|
|
339
|
+
const model = readCodexAgentModel(role, codexHome);
|
|
340
|
+
if (!model || !slugsEquivalent(model, primary)) continue;
|
|
341
|
+
for (const fallback of readCodexAgentModelFallback(role, codexHome)) push(fallback);
|
|
342
|
+
}
|
|
343
|
+
return merged;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Best-effort quota refresh before subagent model selection.
|
|
348
|
+
* Concurrent callers share one in-flight promise. The success TTL is updated only
|
|
349
|
+
* after a successful refresh so failures remain retryable. Errors are swallowed so
|
|
350
|
+
* spawn routing can continue.
|
|
351
|
+
*/
|
|
352
|
+
export function maybePrimeSubagentQuota(config: OcxConfig, now = Date.now()): Promise<void> {
|
|
353
|
+
if (quotaPrimeInFlight) return quotaPrimeInFlight;
|
|
354
|
+
if (!shouldPrimeSubagentQuota(config, now)) return Promise.resolve();
|
|
355
|
+
|
|
356
|
+
quotaPrimeInFlight = (async () => {
|
|
357
|
+
try {
|
|
358
|
+
if (subagentQuotaPrimeForTests) {
|
|
359
|
+
await subagentQuotaPrimeForTests(config, "subagent-spawn");
|
|
360
|
+
} else {
|
|
361
|
+
const { primeCodexPoolQuotas } = await import("./auth-api");
|
|
362
|
+
await primeCodexPoolQuotas(config, "subagent-spawn");
|
|
363
|
+
}
|
|
364
|
+
quotaPrimedAt.set("global", Date.now());
|
|
365
|
+
} catch {
|
|
366
|
+
// Owning boundary: do not fail the spawn path when priming is unavailable.
|
|
367
|
+
// Leave quotaPrimedAt untouched so a later spawn can retry.
|
|
368
|
+
} finally {
|
|
369
|
+
quotaPrimeInFlight = null;
|
|
370
|
+
}
|
|
371
|
+
})();
|
|
372
|
+
return quotaPrimeInFlight;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export function recordSubagentQuotaFailureForThreadSpawn(
|
|
376
|
+
headers: Headers,
|
|
377
|
+
model: string,
|
|
378
|
+
message: string | number,
|
|
379
|
+
config: OcxConfig,
|
|
380
|
+
accountId?: string | null,
|
|
381
|
+
now = Date.now(),
|
|
382
|
+
): void {
|
|
383
|
+
if (!isThreadSpawnRequest(headers)) return;
|
|
384
|
+
noteSubagentModelFailure(model, String(message), config, accountId, now, pollIntervalMs(config));
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export function applySubagentModelFallback(
|
|
388
|
+
parsed: OcxParsedRequest,
|
|
389
|
+
headers: Headers,
|
|
390
|
+
config: OcxConfig,
|
|
391
|
+
accountId?: string | null,
|
|
392
|
+
now = Date.now(),
|
|
393
|
+
nativeFallbackOnly = false,
|
|
394
|
+
): { from?: string; to?: string; skipped?: string[] } | null {
|
|
395
|
+
if (!isThreadSpawnRequest(headers)) return null;
|
|
396
|
+
const roleFallback = resolveAgentModelFallbackForPrimary(parsed.modelId, getCodexHome());
|
|
397
|
+
const globalFallback = config.subagentModelFallback ?? [];
|
|
398
|
+
if (globalFallback.length === 0 && roleFallback.length === 0) return null;
|
|
399
|
+
const selection = selectAvailableSubagentModel(
|
|
400
|
+
parsed.modelId,
|
|
401
|
+
config,
|
|
402
|
+
roleFallback,
|
|
403
|
+
accountId,
|
|
404
|
+
now,
|
|
405
|
+
nativeFallbackOnly,
|
|
406
|
+
);
|
|
407
|
+
if (!selection.rewritten) return selection.skipped.length > 0
|
|
408
|
+
? { from: parsed.modelId, to: parsed.modelId, skipped: selection.skipped }
|
|
409
|
+
: null;
|
|
410
|
+
const from = parsed.modelId;
|
|
411
|
+
rewriteParsedModel(parsed, selection.model);
|
|
412
|
+
return { from, to: selection.model, skipped: selection.skipped };
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export function subagentFallbackGuidanceText(config: OcxConfig): string {
|
|
416
|
+
const chain = config.subagentModelFallback ?? [];
|
|
417
|
+
if (chain.length === 0) return "";
|
|
418
|
+
const quoted = chain.map(model => `"${model}"`).join(", ");
|
|
419
|
+
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.`;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const TOML_STRING_ARRAY = /^(model_fallback)\s*=\s*\[(.*)\]\s*$/s;
|
|
423
|
+
|
|
424
|
+
function parseTomlStringArray(raw: string): string[] {
|
|
425
|
+
const matches = [...raw.matchAll(/"((?:\\.|[^"\\])*)"/g)];
|
|
426
|
+
return matches.map(match => match[1]!.replace(/\\"/g, "\""));
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function parseTomlModelFallback(content: string): string[] | null {
|
|
430
|
+
const match = content.match(/^\s*model_fallback\s*=\s*\[(.*?)\]\s*$/ms);
|
|
431
|
+
if (!match) return null;
|
|
432
|
+
return parseTomlStringArray(match[1] ?? "");
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export function readAgentModelFallback(filePath: string): string[] | null {
|
|
436
|
+
try {
|
|
437
|
+
const content = readFileSync(filePath, "utf8");
|
|
438
|
+
const multiline = parseTomlModelFallback(content);
|
|
439
|
+
if (multiline) return multiline;
|
|
440
|
+
for (const line of content.split(/\r?\n/)) {
|
|
441
|
+
const match = line.match(TOML_STRING_ARRAY);
|
|
442
|
+
if (!match) continue;
|
|
443
|
+
return parseTomlStringArray(match[2] ?? "");
|
|
444
|
+
}
|
|
445
|
+
} catch {
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
return null;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
export function readCodexAgentModelFallback(role: string, codexHome = CODEX_HOME): string[] {
|
|
452
|
+
const file = join(codexHome, "agents", `${role}.toml`);
|
|
453
|
+
if (!existsSync(file)) return [];
|
|
454
|
+
return readAgentModelFallback(file) ?? [];
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export function listCodexAgentRoles(codexHome = CODEX_HOME): string[] {
|
|
458
|
+
const dir = join(codexHome, "agents");
|
|
459
|
+
if (!existsSync(dir)) return [];
|
|
460
|
+
return readdirSync(dir)
|
|
461
|
+
.filter(name => name.endsWith(".toml"))
|
|
462
|
+
.map(name => name.slice(0, -".toml".length));
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/** True when a new quota prime should start (no success within the poll interval). */
|
|
466
|
+
export function shouldPrimeSubagentQuota(config: OcxConfig, now = Date.now()): boolean {
|
|
467
|
+
const last = quotaPrimedAt.get("global") ?? 0;
|
|
468
|
+
return now - last >= pollIntervalMs(config);
|
|
469
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { currentExternalCodexModelProvider, injectCodexConfig } from "./inject";
|
|
2
|
+
import { printProjectCodexConfigWarnings, groupProjectCodexConfigWarningsByPath, type ProjectCodexConfigWarning } from "./project-config-warnings";
|
|
3
|
+
import { refreshCodexModelCatalog } from "./refresh";
|
|
4
|
+
import { applyProxyEnv, loadConfig } from "../config";
|
|
5
|
+
import type { OcxConfig } from "../types";
|
|
6
|
+
import { collectOrcaCodexHomeDiagnostic } from "./home";
|
|
7
|
+
import { summarizeComboCatalogOmissions, type ComboCatalogOmission } from "./catalog/aggregation";
|
|
8
|
+
|
|
9
|
+
export interface CodexSyncResult {
|
|
10
|
+
ok: boolean;
|
|
11
|
+
added: number;
|
|
12
|
+
catalogPath: string | null;
|
|
13
|
+
catalogExists: boolean;
|
|
14
|
+
catalogWritten: boolean;
|
|
15
|
+
cacheSynced: boolean;
|
|
16
|
+
message: string;
|
|
17
|
+
warning?: string;
|
|
18
|
+
comboOmissions?: ComboCatalogOmission[];
|
|
19
|
+
nativeSubagentDefaultsWarning?: string;
|
|
20
|
+
projectConfigWarnings?: ProjectCodexConfigWarning[];
|
|
21
|
+
projectConfigGrouped?: { path: string; issues: string[]; bypass: string }[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface CodexSyncDeps {
|
|
25
|
+
refreshCodexModelCatalog: typeof refreshCodexModelCatalog;
|
|
26
|
+
injectCodexConfig: typeof injectCodexConfig;
|
|
27
|
+
currentExternalCodexModelProvider?: typeof currentExternalCodexModelProvider;
|
|
28
|
+
collectCodexHomeDiagnostic?: typeof collectOrcaCodexHomeDiagnostic;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const defaultDeps: CodexSyncDeps = {
|
|
32
|
+
refreshCodexModelCatalog,
|
|
33
|
+
injectCodexConfig,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function reportCodexHomeTarget(
|
|
37
|
+
log: Pick<Console, "log" | "error"> | null,
|
|
38
|
+
collectDiagnostic: typeof collectOrcaCodexHomeDiagnostic,
|
|
39
|
+
): void {
|
|
40
|
+
if (!log) return;
|
|
41
|
+
const target = collectDiagnostic();
|
|
42
|
+
log.log(` Target Codex home: ${target.effectiveCodexHome}`);
|
|
43
|
+
if (target.warning) {
|
|
44
|
+
log.error(`WARNING: ${target.warning}`);
|
|
45
|
+
log.error(`Action: ${target.action}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function syncModelsToCodex(
|
|
50
|
+
port?: number,
|
|
51
|
+
config: OcxConfig = loadConfig(),
|
|
52
|
+
log: Pick<Console, "log" | "error"> | null = console,
|
|
53
|
+
deps: CodexSyncDeps = defaultDeps,
|
|
54
|
+
): Promise<CodexSyncResult> {
|
|
55
|
+
const p = port ?? config.port ?? 10100;
|
|
56
|
+
const externalProvider = (deps.currentExternalCodexModelProvider ?? currentExternalCodexModelProvider)();
|
|
57
|
+
if (externalProvider) {
|
|
58
|
+
const result = await deps.injectCodexConfig(p, config, {});
|
|
59
|
+
log?.log(result.message);
|
|
60
|
+
reportCodexHomeTarget(log, deps.collectCodexHomeDiagnostic ?? collectOrcaCodexHomeDiagnostic);
|
|
61
|
+
return {
|
|
62
|
+
ok: result.success,
|
|
63
|
+
added: 0,
|
|
64
|
+
catalogPath: null,
|
|
65
|
+
catalogExists: false,
|
|
66
|
+
catalogWritten: false,
|
|
67
|
+
cacheSynced: false,
|
|
68
|
+
message: result.message,
|
|
69
|
+
...(result.nativeSubagentDefaultsWarning ? { nativeSubagentDefaultsWarning: result.nativeSubagentDefaultsWarning } : {}),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
applyProxyEnv(config); // `ocx ensure`/`ocx sync` fetch provider models outside the server process
|
|
74
|
+
let added = 0;
|
|
75
|
+
let catalogPath: string | null = null;
|
|
76
|
+
let catalogPathForInjection: string | null | undefined;
|
|
77
|
+
let catalogExists = false;
|
|
78
|
+
let catalogWritten = false;
|
|
79
|
+
let cacheSynced = false;
|
|
80
|
+
let warning: string | undefined;
|
|
81
|
+
let comboOmissions: ComboCatalogOmission[] = [];
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const cat = await deps.refreshCodexModelCatalog(config);
|
|
85
|
+
added = cat.added;
|
|
86
|
+
catalogExists = cat.catalogExists;
|
|
87
|
+
catalogWritten = cat.catalogWritten;
|
|
88
|
+
cacheSynced = cat.cacheSynced;
|
|
89
|
+
catalogPathForInjection = cat.catalogExists ? cat.path : null;
|
|
90
|
+
catalogPath = catalogPathForInjection;
|
|
91
|
+
comboOmissions = cat.comboOmissions ?? [];
|
|
92
|
+
if (cat.added > 0) {
|
|
93
|
+
log?.log(` + ${cat.added} models appended to Codex catalog (${cat.path})`);
|
|
94
|
+
} else if (!cat.catalogExists) {
|
|
95
|
+
warning = "catalog sync skipped: no Codex catalog source found; keeping Codex's native catalog.";
|
|
96
|
+
log?.error(warning);
|
|
97
|
+
}
|
|
98
|
+
if (comboOmissions.length > 0) {
|
|
99
|
+
// Individual omission lines already went through console.warn during gather;
|
|
100
|
+
// keep a single summary on the sync logger to avoid duplicate stderr noise.
|
|
101
|
+
const summary = summarizeComboCatalogOmissions(comboOmissions);
|
|
102
|
+
log?.error(summary);
|
|
103
|
+
warning = warning ? `${warning} ${summary}` : summary;
|
|
104
|
+
}
|
|
105
|
+
} catch (e) {
|
|
106
|
+
warning = `catalog sync skipped: ${e instanceof Error ? e.message : String(e)}`;
|
|
107
|
+
log?.error(warning);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const result = await deps.injectCodexConfig(p, config, { catalogPath: catalogPathForInjection });
|
|
111
|
+
log?.log(result.message);
|
|
112
|
+
reportCodexHomeTarget(log, deps.collectCodexHomeDiagnostic ?? collectOrcaCodexHomeDiagnostic);
|
|
113
|
+
const projectConfigWarnings = printProjectCodexConfigWarnings(log, { cwd: process.cwd() });
|
|
114
|
+
return {
|
|
115
|
+
ok: result.success,
|
|
116
|
+
added,
|
|
117
|
+
catalogPath,
|
|
118
|
+
catalogExists,
|
|
119
|
+
catalogWritten,
|
|
120
|
+
cacheSynced,
|
|
121
|
+
message: result.message,
|
|
122
|
+
...(warning ? { warning } : {}),
|
|
123
|
+
...(comboOmissions.length > 0 ? { comboOmissions } : {}),
|
|
124
|
+
...(result.nativeSubagentDefaultsWarning ? { nativeSubagentDefaultsWarning: result.nativeSubagentDefaultsWarning } : {}),
|
|
125
|
+
...(projectConfigWarnings.length > 0 ? {
|
|
126
|
+
projectConfigWarnings,
|
|
127
|
+
projectConfigGrouped: groupProjectCodexConfigWarningsByPath(projectConfigWarnings),
|
|
128
|
+
} : {}),
|
|
129
|
+
};
|
|
130
|
+
}
|