@iislee/opencodex 2.11.0 → 2.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS_INSTALL.md +109 -0
- package/README.md +117 -22
- package/bin/ocx.mjs +245 -88
- package/bin/package-main.mjs +1 -1
- package/gui/dist/assets/index-Be1sDpWw.css +1 -0
- package/gui/dist/assets/index-D4OKgUV4.js +114 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/alibaba-color.svg +1 -1
- package/gui/dist/provider-icons/antigravity-color.svg +1 -1
- package/gui/dist/provider-icons/claude-color.svg +1 -1
- package/gui/dist/provider-icons/cline-color.svg +16 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/commandcode-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -1
- package/gui/dist/provider-icons/cursor-color.svg +1 -1
- package/gui/dist/provider-icons/deepseek-color.svg +1 -1
- package/gui/dist/provider-icons/firepass-color.svg +1 -1
- package/gui/dist/provider-icons/fireworks-color.svg +1 -1
- package/gui/dist/provider-icons/gemini-color.svg +1 -1
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
- package/gui/dist/provider-icons/grok.svg +1 -1
- package/gui/dist/provider-icons/groq-color.svg +1 -1
- package/gui/dist/provider-icons/huggingface-color.svg +1 -1
- package/gui/dist/provider-icons/kimi-color.svg +1 -1
- package/gui/dist/provider-icons/kiro-color.svg +2 -2
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
- package/gui/dist/provider-icons/mistral-color.svg +1 -1
- package/gui/dist/provider-icons/moonshot-color.svg +1 -1
- package/gui/dist/provider-icons/nvidia-color.svg +1 -1
- package/gui/dist/provider-icons/ollama-color.svg +1 -1
- package/gui/dist/provider-icons/openai.svg +1 -1
- package/gui/dist/provider-icons/opencode.svg +2 -1
- package/gui/dist/provider-icons/openrouter-color.svg +1 -1
- package/gui/dist/provider-icons/pi.svg +2 -2
- package/gui/dist/provider-icons/qianfan-color.svg +1 -1
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/vllm-color.svg +1 -1
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
- package/package.json +21 -10
- package/src/adapters/anthropic-output-schema.ts +137 -0
- package/src/adapters/anthropic.ts +475 -62
- package/src/adapters/base.ts +82 -7
- package/src/adapters/client-fingerprint.ts +18 -12
- package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
- package/src/adapters/command-code.ts +637 -0
- package/src/adapters/cursor/call-id.ts +44 -0
- package/src/adapters/cursor/catalog.ts +541 -0
- package/src/adapters/cursor/checkpoint-store.ts +308 -0
- package/src/adapters/cursor/cursor-errors.ts +144 -6
- package/src/adapters/cursor/discovery.ts +122 -14
- package/src/adapters/cursor/effort-map.ts +106 -4
- package/src/adapters/cursor/envelope-echo.ts +290 -0
- package/src/adapters/cursor/framing.ts +39 -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/live-models.ts +180 -59
- package/src/adapters/cursor/live-transport.ts +623 -170
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-common.ts +23 -2
- package/src/adapters/cursor/native-exec-desktop.ts +23 -0
- package/src/adapters/cursor/native-exec-fs.ts +10 -7
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +5 -3
- package/src/adapters/cursor/native-exec.ts +121 -14
- package/src/adapters/cursor/protobuf-events.ts +829 -11
- package/src/adapters/cursor/protobuf-request.ts +447 -75
- package/src/adapters/cursor/request-builder.ts +271 -35
- package/src/adapters/cursor/tool-definitions.ts +252 -12
- package/src/adapters/cursor/tool-result-normalize.ts +115 -0
- package/src/adapters/cursor/transport.ts +22 -0
- package/src/adapters/cursor/types.ts +48 -1
- package/src/adapters/cursor.ts +336 -39
- package/src/adapters/exec-tool-result-normalize.ts +99 -0
- package/src/adapters/google-antigravity-replay.ts +713 -41
- package/src/adapters/google-antigravity-wire.ts +48 -10
- package/src/adapters/google-errors.ts +44 -12
- package/src/adapters/google-http.ts +38 -10
- package/src/adapters/google-tool-schema.ts +80 -15
- package/src/adapters/google-truncation.ts +11 -0
- package/src/adapters/google.ts +742 -86
- package/src/adapters/identity.ts +39 -6
- package/src/adapters/image.ts +1 -1
- package/src/adapters/kiro-constants.ts +27 -0
- package/src/adapters/kiro-errors.ts +11 -0
- package/src/adapters/kiro-events.ts +19 -1
- package/src/adapters/kiro-thinking.ts +18 -2
- package/src/adapters/kiro-tools.ts +65 -17
- package/src/adapters/kiro.ts +270 -73
- package/src/adapters/mimo-free.ts +17 -0
- package/src/adapters/openai-chat-url.ts +11 -0
- package/src/adapters/openai-chat.ts +1377 -344
- package/src/adapters/openai-responses-url.ts +16 -0
- package/src/adapters/openai-responses.ts +1164 -64
- package/src/adapters/registry.ts +175 -0
- package/src/adapters/responses-tool-schema.ts +67 -0
- package/src/adapters/run-turn-queue.ts +36 -1
- package/src/adapters/tool-call-id.ts +119 -0
- package/src/adapters/tool-catalog-nudge.ts +105 -21
- package/src/adapters/xai-tool-schema.ts +436 -0
- package/src/adapters/xai-web-search.ts +186 -0
- package/src/bridge.ts +512 -92
- package/src/chat/inbound.ts +57 -20
- package/src/chat/outbound.ts +97 -33
- package/src/claude/agents-inject.ts +40 -10
- package/src/claude/context-windows.ts +37 -14
- package/src/claude/desktop-3p.ts +243 -9
- package/src/claude/gateway-cache.ts +41 -4
- package/src/claude/inbound.ts +72 -3
- package/src/claude/model-info.ts +38 -15
- package/src/claude/outbound.ts +88 -21
- package/src/cli/access.ts +46 -3
- package/src/cli/account-api.ts +150 -19
- package/src/cli/account-auth.ts +33 -6
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +648 -28
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +97 -19
- package/src/cli/agent.ts +53 -2
- package/src/cli/alias.ts +66 -0
- package/src/cli/capabilities-command.ts +94 -0
- package/src/cli/capabilities.ts +496 -0
- package/src/cli/claude-agent-startup-sync.ts +73 -0
- package/src/cli/claude-desktop.ts +60 -15
- package/src/cli/claude.ts +129 -35
- package/src/cli/codex-log-guard-doctor.ts +103 -0
- package/src/cli/codex-shim-autorestore.ts +2 -0
- package/src/cli/codex-shim-readiness.ts +76 -0
- package/src/cli/combo.ts +8 -0
- package/src/cli/config-command.ts +74 -10
- package/src/cli/dispatch.ts +821 -0
- package/src/cli/doctor.ts +440 -56
- package/src/cli/ensure-desired-integrations.ts +152 -0
- package/src/cli/export-command.ts +46 -20
- package/src/cli/help.ts +34 -274
- package/src/cli/index.ts +417 -528
- package/src/cli/init.ts +5 -17
- package/src/cli/inspect.ts +230 -0
- package/src/cli/integrations.ts +120 -2
- 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 +130 -2
- package/src/cli/models.ts +118 -16
- package/src/cli/observe.ts +123 -11
- package/src/cli/opencode.ts +4 -2
- package/src/cli/provider-runtime.ts +34 -3
- package/src/cli/provider.ts +25 -3
- package/src/cli/ready.ts +301 -0
- package/src/cli/registry.ts +476 -0
- package/src/cli/root.ts +86 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +57 -10
- package/src/cli/star-prompt.ts +71 -15
- package/src/cli/status.ts +51 -4
- package/src/cli/storage.ts +234 -0
- package/src/cli/system-command.ts +16 -0
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +153 -6
- package/src/cli/usage-report.ts +184 -0
- package/src/cli/v2.ts +137 -18
- package/src/cli/version-skew.ts +46 -0
- package/src/cli.ts +1 -1
- package/src/clients/config-export.ts +1442 -23
- package/src/clients/effective-status.ts +30 -2
- package/src/clients/probes/cline.ts +81 -0
- package/src/codex/account-label.ts +35 -1
- package/src/codex/account-lifecycle.ts +130 -13
- package/src/codex/account-namespaces.ts +49 -3
- package/src/codex/account-pause.ts +2 -1
- package/src/codex/account-priority.ts +84 -0
- package/src/codex/account-store.ts +29 -2
- package/src/codex/account-usability.ts +25 -2
- package/src/codex/admission.ts +256 -0
- package/src/codex/affinity-debug.ts +162 -0
- package/src/codex/app-server-processes.ts +570 -107
- package/src/codex/app-server-restart-service.ts +232 -0
- package/src/codex/auth-api.ts +893 -246
- package/src/codex/auth-collision.ts +5 -3
- package/src/codex/auth-context.ts +348 -32
- package/src/codex/autostart-health.ts +24 -1
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +86 -10
- package/src/codex/catalog/bundled.ts +331 -33
- package/src/codex/catalog/effort.ts +138 -32
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +555 -45
- package/src/codex/catalog/native-models.ts +75 -0
- package/src/codex/catalog/parsing.ts +269 -30
- package/src/codex/catalog/provider-fetch.ts +1626 -144
- package/src/codex/catalog/sync.ts +1576 -187
- 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 +6 -3
- package/src/codex/codex-write-lock.ts +386 -0
- package/src/codex/convergence-types.ts +614 -0
- package/src/codex/convergence.ts +676 -0
- package/src/codex/coordinator-doctor.ts +332 -0
- package/src/codex/custom-model-catalog-migration.ts +176 -0
- package/src/codex/desired-state.ts +230 -0
- package/src/codex/desktop-app-restart.ts +355 -0
- package/src/codex/features.ts +636 -39
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +436 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-manifest.ts +112 -0
- package/src/codex/history-migration-guardian.ts +30 -24
- package/src/codex/history-provider.ts +1016 -235
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +223 -0
- package/src/codex/inject-coordination.ts +373 -0
- package/src/codex/inject.ts +1114 -152
- package/src/codex/injected-marker.ts +37 -3
- package/src/codex/integration-record.ts +266 -0
- package/src/codex/internal/catalog-writer.ts +203 -0
- package/src/codex/internal/history-writer.ts +81 -0
- package/src/codex/journal.ts +66 -4
- package/src/codex/log-guard/inspect.ts +524 -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 +24 -0
- package/src/codex/main-account.ts +29 -1
- package/src/codex/management-convergence.ts +167 -0
- package/src/codex/model-cache.ts +56 -10
- package/src/codex/model-entitlements.ts +360 -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 +622 -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 +675 -0
- package/src/codex/paths.ts +80 -1
- package/src/codex/plan-from-token.ts +140 -0
- package/src/codex/plan.ts +40 -0
- package/src/codex/plugins-doctor.ts +1 -1
- package/src/codex/pool-rotation.ts +74 -4
- package/src/codex/project-config-warnings.ts +20 -6
- package/src/codex/prompt-journal.ts +352 -0
- package/src/codex/prompt-layers.ts +1313 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/prompt-text-probe.ts +238 -0
- package/src/codex/quota-rejection.ts +298 -0
- package/src/codex/quota.ts +264 -21
- package/src/codex/refresh.ts +11 -2
- package/src/codex/reset-credit-recovery.ts +1044 -0
- package/src/codex/routing.ts +514 -94
- package/src/codex/runtime.ts +159 -38
- package/src/codex/shim.ts +1065 -31
- package/src/codex/subagent-model-fallback.ts +437 -43
- package/src/codex/sync.ts +191 -2
- package/src/codex/transition-state.ts +720 -0
- package/src/codex/upstream-host-health.ts +368 -0
- package/src/codex/user-identity.ts +557 -0
- package/src/codex/warmup.ts +187 -81
- package/src/codex/write-coordination.ts +114 -0
- package/src/combos/failover.ts +47 -0
- package/src/combos/index.ts +4 -0
- package/src/combos/request.ts +32 -0
- package/src/combos/types.ts +91 -9
- package/src/compatibility/index.ts +26 -0
- package/src/compatibility/manifest.ts +253 -0
- package/src/compatibility/openai-responses.ts +121 -0
- package/src/config/atomic-write.ts +219 -0
- package/src/config/paths.ts +40 -0
- package/src/config/process-state.ts +309 -0
- package/src/config/provider-name.ts +24 -0
- package/src/config/provider-validation.ts +177 -0
- package/src/config/rebase-provenance.ts +68 -0
- package/src/config.ts +1713 -709
- package/src/generated/compatibility-version.json +3324 -0
- package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
- package/src/grok/inject.ts +16 -5
- package/src/grok/inspect.ts +45 -0
- package/src/grok/sync.ts +2 -2
- package/src/images/loop.ts +157 -33
- package/src/images/plan.ts +24 -13
- 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-policy.ts +160 -0
- package/src/integrations/ownership.ts +155 -0
- package/src/integrations/registry.ts +166 -0
- package/src/integrations/serialize.ts +314 -0
- package/src/integrations/state.ts +433 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer-lock.ts +98 -0
- package/src/integrations/writer.ts +715 -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/admin-secrets.ts +24 -0
- package/src/lib/app-owned-memory-stores.ts +22 -0
- package/src/lib/bounded-body.ts +167 -11
- package/src/lib/bun-runtime.ts +125 -12
- package/src/lib/bun-stream-caps.ts +13 -9
- package/src/lib/codex-restart-contract.ts +120 -0
- package/src/lib/config-ownership.ts +26 -2
- package/src/lib/destination-policy.ts +65 -1
- package/src/lib/errors.ts +80 -5
- package/src/lib/fabric-task-execution-authority.ts +7 -0
- package/src/lib/fabric-task-host.ts +29 -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/optional-shutdown-hooks.ts +57 -0
- package/src/lib/package-tree-integrity.ts +101 -0
- package/src/lib/pinned-http.ts +145 -26
- package/src/lib/process-control.ts +6 -2
- package/src/lib/provider-outbound.ts +49 -9
- package/src/lib/redact.ts +419 -3
- package/src/lib/self-launch-argv.ts +15 -0
- package/src/lib/server-resource-ownership.ts +71 -0
- package/src/lib/service-secrets.ts +15 -0
- package/src/lib/shadow-call.ts +35 -4
- package/src/lib/sse-decoder.ts +41 -0
- package/src/lib/state-store-registrations.ts +10 -2
- package/src/lib/system-restart-contract.ts +73 -0
- package/src/lib/token-estimate.ts +19 -2
- package/src/lib/tool-argument-integers.ts +253 -0
- package/src/lib/translator-budget.ts +44 -0
- package/src/lib/upstream-http-version.ts +57 -0
- package/src/lib/upstream-reachability.ts +95 -0
- package/src/lib/upstream-retry.ts +156 -3
- package/src/lib/windows-atomic-replace.ts +155 -0
- package/src/lib/windows-elevation.ts +70 -2
- package/src/lib/windows-secret-acl.ts +409 -69
- 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 +33 -5
- 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.ts +12 -1
- package/src/oauth/callback-server.ts +30 -4
- package/src/oauth/chatgpt.ts +12 -1
- package/src/oauth/cline.ts +203 -0
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +46 -5
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/google-antigravity.ts +35 -3
- package/src/oauth/health.ts +20 -12
- package/src/oauth/index.ts +462 -71
- package/src/oauth/key-providers.ts +25 -0
- package/src/oauth/kimi.ts +25 -3
- package/src/oauth/kiro.ts +95 -6
- package/src/oauth/local-token-detect.ts +13 -2
- package/src/oauth/log.ts +3 -1
- package/src/oauth/login-cli.ts +88 -28
- package/src/oauth/nous.ts +798 -0
- package/src/oauth/open-browser-choice.ts +26 -0
- package/src/oauth/store.ts +133 -26
- package/src/oauth/token-guardian.ts +9 -3
- package/src/oauth/types.ts +15 -0
- package/src/pi/models.ts +2 -2
- package/src/providers/alibaba-region-migration.ts +1 -1
- package/src/providers/antigravity-models.ts +521 -31
- package/src/providers/auto-compact-budget.ts +65 -0
- package/src/providers/base-url-choices.ts +10 -0
- package/src/providers/codex-capacity.ts +292 -0
- package/src/providers/command-code-efforts.ts +176 -0
- package/src/providers/context-cap.ts +26 -8
- package/src/providers/cursor-pool.ts +72 -0
- package/src/providers/default-aliases.ts +65 -0
- package/src/providers/derive.ts +281 -6
- package/src/providers/fastwire.ts +507 -0
- package/src/providers/free-directory.ts +10 -7
- package/src/providers/google-vertex-location.ts +14 -0
- package/src/providers/key-failover.ts +71 -3
- package/src/providers/label.ts +35 -2
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +115 -22
- package/src/providers/model-presets.ts +119 -0
- package/src/providers/model-rename-migration.ts +255 -0
- package/src/providers/model-rename-startup.ts +28 -0
- package/src/providers/new-model-policy.ts +146 -0
- package/src/providers/openai-sidecar.ts +72 -4
- package/src/providers/openai-tier-startup.ts +31 -2
- package/src/providers/openai-tiers.ts +119 -4
- package/src/providers/openai-virtual-models.ts +1 -0
- package/src/providers/opencode-zen-rate-limit.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +30 -0
- package/src/providers/quota.ts +1379 -40
- package/src/providers/registry.ts +1545 -112
- package/src/providers/request-pacing.ts +310 -0
- package/src/providers/service-tier.ts +277 -0
- package/src/providers/slug-codec.ts +94 -6
- 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 +32 -4
- package/src/reasoning-effort.ts +68 -3
- package/src/responses/apply-patch-envelope.ts +63 -0
- package/src/responses/code-mode-helper-compat.ts +50 -0
- package/src/responses/compaction.ts +26 -1
- package/src/responses/custom-tool-compat.ts +384 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/namespace-tool-compat.ts +435 -0
- package/src/responses/parser.ts +225 -38
- package/src/responses/provider-continuation.ts +98 -0
- package/src/responses/provider-opaque-metadata.ts +73 -0
- package/src/responses/reasoning-envelope.ts +9 -1
- package/src/responses/reasoning-replay-cache.ts +504 -0
- package/src/responses/schema.ts +12 -2
- package/src/responses/spill-store.ts +75 -10
- package/src/responses/state.ts +659 -31
- package/src/responses/thought-signature-replay.ts +364 -0
- package/src/responses/tool-search-compat.ts +301 -0
- package/src/responses/truncated-stop-reason.ts +60 -0
- package/src/responses/turn-termination.ts +107 -0
- package/src/router.ts +405 -31
- package/src/routing/analytics.ts +379 -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 +155 -0
- package/src/routing/request-evidence.ts +45 -0
- package/src/routing/trace.ts +776 -0
- package/src/server/adapter-resolve.ts +2 -29
- package/src/server/auth-cors.ts +289 -48
- package/src/server/background-lifecycle.ts +182 -0
- package/src/server/chat-completions.ts +154 -72
- package/src/server/chat-native-sse.ts +331 -0
- package/src/server/chat-native.ts +445 -0
- package/src/server/claude-messages.ts +160 -44
- package/src/server/direct-local-http.ts +347 -0
- package/src/server/effort-policy.ts +18 -0
- package/src/server/github-copilot-responses-repair.ts +338 -0
- package/src/server/gui-static.ts +39 -10
- package/src/server/images.ts +94 -12
- package/src/server/index.ts +1021 -190
- package/src/server/lifecycle.ts +336 -14
- package/src/server/live.ts +136 -17
- 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 +433 -116
- package/src/server/management/api-key-usage.ts +31 -5
- package/src/server/management/body.ts +6 -0
- package/src/server/management/codex-prompt-routes.ts +570 -0
- package/src/server/management/combo-routes.ts +64 -25
- package/src/server/management/config-routes.ts +511 -51
- package/src/server/management/context.ts +89 -2
- 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 +127 -52
- package/src/server/management/model-routes.ts +461 -133
- package/src/server/management/model-rows.ts +167 -0
- package/src/server/management/native-integration-routes.ts +770 -0
- package/src/server/management/oauth-account-routes.ts +90 -8
- package/src/server/management/provider-capability-config.ts +48 -0
- package/src/server/management/provider-routes.ts +836 -159
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/route-registry.ts +332 -0
- package/src/server/management/routing-analytics-routes.ts +74 -0
- package/src/server/management/routing-profile-routes.ts +380 -0
- package/src/server/management/shared.ts +27 -11
- package/src/server/management/sidebar-routes.ts +47 -31
- 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 +289 -33
- package/src/server/management/system-routes.ts +77 -0
- package/src/server/management/usage-summary-cache.ts +9 -1
- 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 +126 -23
- package/src/server/management-auth.ts +220 -5
- package/src/server/passive-route-linker.ts +66 -0
- package/src/server/port-reclaim.ts +1 -1
- package/src/server/ports.ts +41 -1
- package/src/server/proxy-liveness.ts +161 -10
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +221 -54
- package/src/server/relay.ts +479 -87
- package/src/server/request-decompress.ts +113 -6
- package/src/server/request-log-conversation.ts +51 -0
- package/src/server/request-log.ts +298 -31
- 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/codex-auth-error.ts +55 -0
- package/src/server/responses/collaboration.ts +239 -36
- package/src/server/responses/combo-stream-preflight.ts +178 -0
- package/src/server/responses/compact.ts +444 -72
- package/src/server/responses/core.ts +3579 -436
- package/src/server/responses/empty-completion-guard.ts +333 -0
- package/src/server/responses/encrypted-payload.ts +62 -39
- package/src/server/responses/fetch-helpers.ts +89 -99
- package/src/server/responses/input-admission.ts +187 -0
- package/src/server/responses/pacing-overload.ts +13 -0
- package/src/server/responses/passthrough-error.ts +33 -9
- package/src/server/responses/policy-fallback.ts +178 -0
- package/src/server/responses/responses-field-backfill.ts +343 -0
- package/src/server/responses/terminal-guard.ts +26 -5
- package/src/server/responses/upstream-error.ts +5 -0
- package/src/server/responses/ws-upstream.ts +381 -0
- package/src/server/responses-custom-tool-repair.ts +363 -0
- package/src/server/responses-item-id-repair.ts +54 -6
- 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 +363 -0
- package/src/server/responses-tool-search-repair.ts +267 -0
- package/src/server/responses-undeclared-tool-guard.ts +376 -0
- package/src/server/responses.ts +18 -2
- package/src/server/search.ts +78 -13
- package/src/server/sse-frame-buffer.ts +319 -0
- package/src/server/sse-payload-rewrite.ts +110 -22
- package/src/server/startup-action-control.ts +8 -1
- package/src/server/startup-health-cache.ts +19 -1
- package/src/server/system-env.ts +80 -9
- package/src/server/ws-bridge.ts +53 -40
- package/src/service-manager-probe.ts +892 -0
- package/src/service.ts +1153 -94
- package/src/sidecar/auth.ts +92 -0
- package/src/sidecar/candidates.ts +83 -0
- package/src/storage/cleanup.ts +2 -2
- package/src/storage/policy-job.ts +14 -4
- package/src/storage/policy.ts +88 -23
- package/src/storage/scanner.ts +1 -1
- package/src/storage/worker-lifecycle.ts +14 -14
- package/src/tray/windows-tray.ps1 +83 -9
- package/src/tray/windows.ts +43 -16
- package/src/types/accounts.ts +37 -0
- package/src/types/config.ts +911 -0
- package/src/types/provider.ts +614 -0
- package/src/types/request.ts +397 -0
- package/src/types/tools.ts +236 -0
- package/src/types/wire.ts +80 -0
- package/src/types.ts +107 -1237
- package/src/update/index.ts +37 -23
- package/src/update/job.ts +445 -68
- package/src/update/notify.ts +12 -6
- package/src/update/npm-cache-preflight.d.mts +47 -0
- package/src/update/npm-cache-preflight.mjs +201 -0
- package/src/update/transactional-install.d.mts +22 -0
- package/src/update/transactional-install.mjs +266 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +287 -16
- package/src/usage/log.ts +666 -50
- package/src/usage/summary.ts +388 -17
- 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 +10 -6
- package/src/vision/backends.ts +97 -0
- package/src/vision/describe.ts +27 -16
- package/src/vision/eligibility.ts +250 -0
- package/src/vision/index.ts +238 -24
- 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 +13 -7
- package/src/web-search/backends.ts +108 -0
- package/src/web-search/exa-executor.ts +119 -0
- package/src/web-search/executor.ts +21 -6
- package/src/web-search/gemini-executor.ts +141 -0
- package/src/web-search/index.ts +150 -15
- package/src/web-search/loop.ts +284 -54
- package/src/web-search/parse.ts +125 -30
- package/src/web-search/sources.ts +60 -0
- package/src/web-search/xai-executor.ts +219 -0
- package/gui/dist/assets/index-DTpMHS4F.js +0 -67
- package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
- package/gui/dist/provider-icons/antigravity.svg +0 -1
- package/gui/dist/provider-icons/claude.svg +0 -1
- package/gui/dist/provider-icons/copilot.svg +0 -1
- package/gui/dist/provider-icons/cursor.svg +0 -2
- package/gui/dist/provider-icons/gemini.svg +0 -1
- package/gui/dist/provider-icons/grok-color.svg +0 -1
- package/gui/dist/provider-icons/kiro.svg +0 -14
- package/src/cli/internal-dispatch.ts +0 -20
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { loadConfig } from "../config";
|
|
2
|
+
import { closeSync, openSync, readSync } from "node:fs";
|
|
3
|
+
import {
|
|
4
|
+
MAX_ACCOUNT_PRIORITY,
|
|
5
|
+
MIN_ACCOUNT_PRIORITY,
|
|
6
|
+
normalizeAccountPriority,
|
|
7
|
+
parseAccountPriority,
|
|
8
|
+
} from "../codex/pool-rotation";
|
|
2
9
|
import {
|
|
3
10
|
apiError,
|
|
4
11
|
apiJson,
|
|
@@ -11,6 +18,20 @@ import {
|
|
|
11
18
|
type AccountDeps, type AccountStdin, type FamilyRows,
|
|
12
19
|
type ProviderQuotaDto, type ProviderQuotaReportDto,
|
|
13
20
|
} from "./account-api";
|
|
21
|
+
import {
|
|
22
|
+
codexCatalogRefreshPending,
|
|
23
|
+
warnIfCodexCatalogRefreshPending,
|
|
24
|
+
} from "./account-catalog-refresh";
|
|
25
|
+
import {
|
|
26
|
+
ACCOUNT_IMPORT_DEADLINE_MS,
|
|
27
|
+
ACCOUNT_IMPORT_FORMAT,
|
|
28
|
+
ACCOUNT_IMPORT_MAX_BYTES,
|
|
29
|
+
ACCOUNT_IMPORT_PROVIDER,
|
|
30
|
+
type AccountImportCode,
|
|
31
|
+
type AccountImportRecordResult,
|
|
32
|
+
type AccountImportResult,
|
|
33
|
+
type AccountImportStatus,
|
|
34
|
+
} from "../oauth/account-import";
|
|
14
35
|
|
|
15
36
|
const MAIN_ID = "__main__";
|
|
16
37
|
const AUTO_NOTE = "auto (no pin — lowest-usage account is selected per request)";
|
|
@@ -18,12 +39,20 @@ const EXTENDED_USAGE = `Usage:
|
|
|
18
39
|
ocx account refresh <provider> [--json]
|
|
19
40
|
ocx account auto-switch <provider> <on|off|status|threshold <0-100>> [--json]
|
|
20
41
|
ocx account alias <provider> <id|main> <display-name|-> [--json]
|
|
42
|
+
ocx account priority <provider> <id|main> [<-100..100|first|earlier|normal|later|last|reset>] [--json]
|
|
43
|
+
ocx account pause <provider> <id|main> [--json]
|
|
44
|
+
ocx account resume <provider> <id|main> [--json]
|
|
45
|
+
ocx account pause-exhausted <provider> [--json]
|
|
46
|
+
ocx account strategy <provider> [<quota|round-robin|fill-first>] [--json]
|
|
47
|
+
ocx account sticky <provider> [<1-100>] [--json]
|
|
21
48
|
ocx account remove <provider> <id|main> --yes [--json]
|
|
22
49
|
ocx account clear-cooldown <provider> <id|main> [--json]
|
|
23
|
-
ocx account add-key <provider> [--label <label>] [--json]
|
|
50
|
+
ocx account add-key <provider> [--label <label>] [--json]
|
|
51
|
+
ocx account import <provider> --format <format> (--file <path>|--stdin) [--json]`;
|
|
24
52
|
const PIPE_GUIDANCE = `Pipe the API key on stdin, for example:
|
|
25
53
|
ocx account add-key <provider> <<< "$MY_KEY"
|
|
26
54
|
security find-generic-password -w <item> | ocx account add-key <provider>`;
|
|
55
|
+
const ACCOUNT_IMPORT_TIMEOUT_MS = ACCOUNT_IMPORT_DEADLINE_MS;
|
|
27
56
|
|
|
28
57
|
function flag(args: string[], value: string): boolean {
|
|
29
58
|
const index = args.indexOf(value);
|
|
@@ -41,6 +70,162 @@ function flagValue(args: string[], value: string): { found: boolean; value?: str
|
|
|
41
70
|
return { found: true, value: result };
|
|
42
71
|
}
|
|
43
72
|
|
|
73
|
+
function boundedUtf8(bytes: Uint8Array): string {
|
|
74
|
+
try {
|
|
75
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
76
|
+
} catch {
|
|
77
|
+
throw new Error("invalid_document");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function readBoundedImportFile(path: string): string {
|
|
82
|
+
let descriptor: number | undefined;
|
|
83
|
+
try {
|
|
84
|
+
descriptor = openSync(path, "r");
|
|
85
|
+
const buffer = Buffer.allocUnsafe(ACCOUNT_IMPORT_MAX_BYTES + 1);
|
|
86
|
+
let offset = 0;
|
|
87
|
+
while (offset < buffer.length) {
|
|
88
|
+
const read = readSync(descriptor, buffer, offset, buffer.length - offset, null);
|
|
89
|
+
if (read === 0) break;
|
|
90
|
+
offset += read;
|
|
91
|
+
}
|
|
92
|
+
if (offset > ACCOUNT_IMPORT_MAX_BYTES) throw new Error("invalid_document");
|
|
93
|
+
return boundedUtf8(buffer.subarray(0, offset));
|
|
94
|
+
} catch (error) {
|
|
95
|
+
if (error instanceof Error && error.message === "invalid_document") throw error;
|
|
96
|
+
throw new Error("source_read_failed");
|
|
97
|
+
} finally {
|
|
98
|
+
if (descriptor !== undefined) closeSync(descriptor);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function readBoundedImportStdin(deps: AccountDeps): Promise<string> {
|
|
103
|
+
const input: AccountStdin = deps.stdinImpl ?? process.stdin;
|
|
104
|
+
if (input.isTTY) throw new Error("stdin_required");
|
|
105
|
+
const timeoutMs = deps.stdinTimeoutMs ?? 15_000;
|
|
106
|
+
return await new Promise<string>((resolve, reject) => {
|
|
107
|
+
const chunks: Buffer[] = [];
|
|
108
|
+
let bytes = 0;
|
|
109
|
+
let settled = false;
|
|
110
|
+
const cleanup = () => {
|
|
111
|
+
clearTimeout(timer);
|
|
112
|
+
input.removeListener("data", onData);
|
|
113
|
+
input.removeListener("end", onEnd);
|
|
114
|
+
input.removeListener("error", onError);
|
|
115
|
+
};
|
|
116
|
+
const finish = (fn: () => void) => {
|
|
117
|
+
if (settled) return;
|
|
118
|
+
settled = true;
|
|
119
|
+
cleanup();
|
|
120
|
+
fn();
|
|
121
|
+
};
|
|
122
|
+
const onData = (chunk: unknown) => {
|
|
123
|
+
const encoded = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
|
|
124
|
+
bytes += encoded.byteLength;
|
|
125
|
+
if (bytes > ACCOUNT_IMPORT_MAX_BYTES) {
|
|
126
|
+
finish(() => reject(new Error("invalid_document")));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
chunks.push(encoded);
|
|
130
|
+
};
|
|
131
|
+
const onEnd = () => finish(() => {
|
|
132
|
+
try { resolve(boundedUtf8(Buffer.concat(chunks, bytes))); }
|
|
133
|
+
catch (error) { reject(error); }
|
|
134
|
+
});
|
|
135
|
+
const onError = () => finish(() => reject(new Error("source_read_failed")));
|
|
136
|
+
const timer = setTimeout(() => finish(() => reject(new Error("stdin_timeout"))), timeoutMs);
|
|
137
|
+
input.on("data", onData);
|
|
138
|
+
input.on("end", onEnd);
|
|
139
|
+
input.on("error", onError);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const IMPORT_STATUSES = new Set<AccountImportStatus>(["imported", "updated", "failed", "unsupported"]);
|
|
144
|
+
const IMPORT_CODES = new Set<AccountImportCode>([
|
|
145
|
+
"imported", "updated", "import_cancelled", "unsupported_provider", "unsupported_format", "invalid_document",
|
|
146
|
+
"invalid_record", "credential_rejected", "identity_mismatch", "missing_project", "persist_failed",
|
|
147
|
+
]);
|
|
148
|
+
const IMPORT_RESULT_FIELDS = new Set([
|
|
149
|
+
"totalCount", "importedCount", "updatedCount", "failedCount", "unsupportedCount", "results",
|
|
150
|
+
]);
|
|
151
|
+
const IMPORT_RECORD_FIELDS = new Set(["index", "status", "code"]);
|
|
152
|
+
const IMPORT_STATUS_CODES: Record<AccountImportStatus, ReadonlySet<AccountImportCode>> = {
|
|
153
|
+
imported: new Set(["imported"]),
|
|
154
|
+
updated: new Set(["updated"]),
|
|
155
|
+
failed: new Set(["invalid_record", "credential_rejected", "identity_mismatch", "missing_project", "persist_failed"]),
|
|
156
|
+
unsupported: new Set(["unsupported_provider", "unsupported_format"]),
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
160
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
161
|
+
const prototype = Object.getPrototypeOf(value);
|
|
162
|
+
return prototype === Object.prototype || prototype === null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function hasExactFields(value: Record<string, unknown>, allowed: ReadonlySet<string>): boolean {
|
|
166
|
+
const fields = Object.keys(value);
|
|
167
|
+
return fields.length === allowed.size && fields.every(field => allowed.has(field));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function isNonNegativeSafeInteger(value: unknown): value is number {
|
|
171
|
+
return typeof value === "number" && Number.isFinite(value) && Number.isSafeInteger(value) && value >= 0;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function safeImportResult(json: unknown): AccountImportResult | null {
|
|
175
|
+
if (!isPlainObject(json) || !hasExactFields(json, IMPORT_RESULT_FIELDS)) return null;
|
|
176
|
+
if (
|
|
177
|
+
!isNonNegativeSafeInteger(json.totalCount)
|
|
178
|
+
|| !isNonNegativeSafeInteger(json.importedCount)
|
|
179
|
+
|| !isNonNegativeSafeInteger(json.updatedCount)
|
|
180
|
+
|| !isNonNegativeSafeInteger(json.failedCount)
|
|
181
|
+
|| !isNonNegativeSafeInteger(json.unsupportedCount)
|
|
182
|
+
) return null;
|
|
183
|
+
if (!Array.isArray(json.results)) return null;
|
|
184
|
+
if (json.results.length !== json.totalCount) return null;
|
|
185
|
+
|
|
186
|
+
const results: AccountImportRecordResult[] = [];
|
|
187
|
+
const indices = new Set<number>();
|
|
188
|
+
const aggregate: Record<AccountImportStatus, number> = {
|
|
189
|
+
imported: 0,
|
|
190
|
+
updated: 0,
|
|
191
|
+
failed: 0,
|
|
192
|
+
unsupported: 0,
|
|
193
|
+
};
|
|
194
|
+
for (const raw of json.results) {
|
|
195
|
+
if (!isPlainObject(raw) || !hasExactFields(raw, IMPORT_RECORD_FIELDS)) return null;
|
|
196
|
+
const item = raw;
|
|
197
|
+
if (!isNonNegativeSafeInteger(item.index) || item.index >= json.totalCount || indices.has(item.index)) return null;
|
|
198
|
+
if (!IMPORT_STATUSES.has(item.status as AccountImportStatus)) return null;
|
|
199
|
+
if (!IMPORT_CODES.has(item.code as AccountImportCode)) return null;
|
|
200
|
+
const status = item.status as AccountImportStatus;
|
|
201
|
+
const code = item.code as AccountImportCode;
|
|
202
|
+
if (!IMPORT_STATUS_CODES[status].has(code)) return null;
|
|
203
|
+
indices.add(item.index);
|
|
204
|
+
aggregate[status] += 1;
|
|
205
|
+
results.push({
|
|
206
|
+
index: item.index,
|
|
207
|
+
status,
|
|
208
|
+
code,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
if (
|
|
212
|
+
indices.size !== json.totalCount
|
|
213
|
+
|| aggregate.imported !== json.importedCount
|
|
214
|
+
|| aggregate.updated !== json.updatedCount
|
|
215
|
+
|| aggregate.failed !== json.failedCount
|
|
216
|
+
|| aggregate.unsupported !== json.unsupportedCount
|
|
217
|
+
) return null;
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
totalCount: json.totalCount,
|
|
221
|
+
importedCount: json.importedCount,
|
|
222
|
+
updatedCount: json.updatedCount,
|
|
223
|
+
failedCount: json.failedCount,
|
|
224
|
+
unsupportedCount: json.unsupportedCount,
|
|
225
|
+
results,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
44
229
|
function usage(message?: string): number {
|
|
45
230
|
if (message) console.error(message);
|
|
46
231
|
console.error(EXTENDED_USAGE);
|
|
@@ -52,8 +237,8 @@ function configAndType(deps: AccountDeps, name: string) {
|
|
|
52
237
|
}
|
|
53
238
|
|
|
54
239
|
function familyFailure(result: FamilyRows, fallback: string): number | null {
|
|
55
|
-
if (result.networkDown) return proxyUnreachable();
|
|
56
|
-
if (result.errorJson) return apiError(result.errorJson, fallback);
|
|
240
|
+
if (result.networkDown) return proxyUnreachable(result.transportError);
|
|
241
|
+
if (result.errorJson) return apiError(result.errorJson, fallback, result.status);
|
|
57
242
|
return null;
|
|
58
243
|
}
|
|
59
244
|
|
|
@@ -69,17 +254,14 @@ function resetIso(value: number | undefined): string | null {
|
|
|
69
254
|
|
|
70
255
|
function refreshLine(row: FamilyRows["rows"][number]): string {
|
|
71
256
|
const parts = [row.id === MAIN_ID ? "main" : row.id, row.email, row.plan];
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const monthlyReset = resetIso(quota.monthlyResetAt);
|
|
81
|
-
if (monthlyReset) parts.push(`resets ${monthlyReset}`);
|
|
82
|
-
}
|
|
257
|
+
if (row.paused) parts.push("paused");
|
|
258
|
+
// Was a second quota dialect: it gated the whole block on weekly/monthly, so an account
|
|
259
|
+
// reporting only a 5h window printed `quota: unknown` while `quotaParts` five lines below
|
|
260
|
+
// rendered the same data correctly for the provider path (#2703). Two halves of one file
|
|
261
|
+
// disagreeing about how to read one DTO is the defect; delegating removes it rather than
|
|
262
|
+
// teaching the second dialect a third window.
|
|
263
|
+
const quotaText = row.quota ? quotaParts(row.quota).join(" ") : "";
|
|
264
|
+
parts.push(quotaText.length > 0 ? quotaText : "quota: unknown");
|
|
83
265
|
if (row.needsReauth) parts.push("needs-reauth");
|
|
84
266
|
return parts.filter(Boolean).join(" ");
|
|
85
267
|
}
|
|
@@ -99,7 +281,7 @@ function quotaParts(quota: ProviderQuotaDto): string[] {
|
|
|
99
281
|
return parts;
|
|
100
282
|
}
|
|
101
283
|
|
|
102
|
-
function providerQuotaLine(name: string, report: ProviderQuotaReportDto): string {
|
|
284
|
+
export function providerQuotaLine(name: string, report: ProviderQuotaReportDto): string {
|
|
103
285
|
return [name, ...quotaParts(report.quota)].join(" ");
|
|
104
286
|
}
|
|
105
287
|
|
|
@@ -145,8 +327,8 @@ export async function cmdRefresh(args: string[], deps: AccountDeps): Promise<num
|
|
|
145
327
|
if (!baseUrl) return proxyUnreachable();
|
|
146
328
|
if (classified.type !== "codex") {
|
|
147
329
|
const result = await fetchProviderQuotaReport(deps, baseUrl, name);
|
|
148
|
-
if (result.status === 0) return proxyUnreachable();
|
|
149
|
-
if (result.status !== 200) return apiError(result.errorJson ?? {}, `failed to refresh ${name}
|
|
330
|
+
if (result.status === 0) return proxyUnreachable(result.transportError);
|
|
331
|
+
if (result.status !== 200) return apiError(result.errorJson ?? {}, `failed to refresh ${name}`, result.status);
|
|
150
332
|
if (wantsJson) console.log(JSON.stringify({ provider: name, report: result.report }, null, 2));
|
|
151
333
|
else console.log(result.report ? providerQuotaLine(name, result.report) : `no quota report available for ${name}`);
|
|
152
334
|
return 0;
|
|
@@ -180,15 +362,15 @@ export async function cmdAutoSwitch(args: string[], deps: AccountDeps): Promise<
|
|
|
180
362
|
if (!baseUrl) return proxyUnreachable();
|
|
181
363
|
if (action === "status") {
|
|
182
364
|
const response = await apiJson(deps, baseUrl, "GET", "/api/codex-auth/active");
|
|
183
|
-
if (response.status === 0) return proxyUnreachable();
|
|
365
|
+
if (response.status === 0) return proxyUnreachable(response.transportError);
|
|
184
366
|
if (response.status !== 200 || typeof response.json.autoSwitchThreshold !== "number") {
|
|
185
|
-
return apiError(response.json, "failed to read auto-switch status");
|
|
367
|
+
return apiError(response.json, "failed to read auto-switch status", response.status);
|
|
186
368
|
}
|
|
187
369
|
threshold = response.json.autoSwitchThreshold;
|
|
188
370
|
} else {
|
|
189
371
|
const response = await apiJson(deps, baseUrl, "PUT", "/api/codex-auth/auto-switch", { threshold });
|
|
190
|
-
if (response.status === 0) return proxyUnreachable();
|
|
191
|
-
if (response.status !== 200) return apiError(response.json, "failed to update auto-switch");
|
|
372
|
+
if (response.status === 0) return proxyUnreachable(response.transportError);
|
|
373
|
+
if (response.status !== 200) return apiError(response.json, "failed to update auto-switch", response.status);
|
|
192
374
|
}
|
|
193
375
|
const enabled = threshold! > 0;
|
|
194
376
|
if (wantsJson) console.log(JSON.stringify({ provider: name, autoSwitchThreshold: threshold, enabled }, null, 2));
|
|
@@ -234,18 +416,28 @@ export async function cmdRemove(args: string[], deps: AccountDeps): Promise<numb
|
|
|
234
416
|
const response = await apiJson(deps, baseUrl, "DELETE", deletePath(classified.type, name, id));
|
|
235
417
|
if (response.status === 0) return fail("Proxy not reachable. Start it with 'ocx start' or 'ocx ensure'.");
|
|
236
418
|
if (response.status !== 200) return fail(errorText(response.json, `failed to remove ${requestedId}`));
|
|
419
|
+
const catalogRefreshPending = classified.type === "codex"
|
|
420
|
+
&& codexCatalogRefreshPending(response.json);
|
|
237
421
|
const after = await fetchRows(deps, baseUrl, name, classified.type);
|
|
238
422
|
if (after.networkDown || after.errorJson) {
|
|
239
423
|
const detail = after.networkDown ? "proxy not reachable" : typeof after.errorJson?.error === "string" ? after.errorJson.error : "unknown error";
|
|
240
424
|
return fail(`post-delete verification failed; delete may have succeeded: ${detail}`);
|
|
241
425
|
}
|
|
242
426
|
const removedActive = before.activeId === id;
|
|
243
|
-
const result = {
|
|
427
|
+
const result = {
|
|
428
|
+
ok: true,
|
|
429
|
+
provider: name,
|
|
430
|
+
id,
|
|
431
|
+
removedActive,
|
|
432
|
+
promotedActiveId: after.activeId,
|
|
433
|
+
...(classified.type === "codex" ? { catalogRefreshPending } : {}),
|
|
434
|
+
};
|
|
244
435
|
if (wantsJson) console.log(JSON.stringify(result, null, 2));
|
|
245
436
|
else if (classified.type === "codex" && removedActive && after.activeId === null) console.log(`openai: ${AUTO_NOTE}`);
|
|
246
437
|
else if (classified.type === "oauth") console.log(after.rows.length ? `${name}: active account is now ${after.activeId}` : `${name}: no accounts remaining`);
|
|
247
438
|
else if (classified.type === "api-key") console.log(after.rows.length ? `${name}: active key is now ${after.activeId}` : `${name}: no keys remaining`);
|
|
248
439
|
else console.log(`${name}: removed account ${requestedId}`);
|
|
440
|
+
if (!wantsJson && catalogRefreshPending) warnIfCodexCatalogRefreshPending(response.json);
|
|
249
441
|
return 0;
|
|
250
442
|
}
|
|
251
443
|
|
|
@@ -269,8 +461,8 @@ export async function cmdAddKey(args: string[], deps: AccountDeps): Promise<numb
|
|
|
269
461
|
const baseUrl = await resolveBaseUrl(deps);
|
|
270
462
|
if (!baseUrl) return proxyUnreachable();
|
|
271
463
|
const response = await apiJson(deps, baseUrl, "POST", "/api/providers/keys", { name, key, ...(label ? { label } : {}) });
|
|
272
|
-
if (response.status === 0) return proxyUnreachable();
|
|
273
|
-
if (response.status !== 201) return apiError(response.json, `failed to add a key for ${name}
|
|
464
|
+
if (response.status === 0) return proxyUnreachable(response.transportError);
|
|
465
|
+
if (response.status !== 201) return apiError(response.json, `failed to add a key for ${name}`, response.status);
|
|
274
466
|
const id = typeof response.json.id === "string" ? response.json.id : null;
|
|
275
467
|
// Redact the key inside the label BEFORE serialization — a key containing
|
|
276
468
|
// JSON-escaped characters (" or \) would otherwise survive the whole-output
|
|
@@ -283,6 +475,98 @@ export async function cmdAddKey(args: string[], deps: AccountDeps): Promise<numb
|
|
|
283
475
|
return 0;
|
|
284
476
|
}
|
|
285
477
|
|
|
478
|
+
export async function cmdImport(args: string[], deps: AccountDeps): Promise<number> {
|
|
479
|
+
const wantsJson = flag(args, "--json");
|
|
480
|
+
const formatArg = flagValue(args, "--format");
|
|
481
|
+
const fileArg = flagValue(args, "--file");
|
|
482
|
+
const fromStdin = flag(args, "--stdin");
|
|
483
|
+
const provider = args.shift();
|
|
484
|
+
|
|
485
|
+
// Admission happens before source I/O. In particular, an inline JSON positional argument is
|
|
486
|
+
// rejected without inspecting or echoing it, keeping secrets out of any subsequent surface.
|
|
487
|
+
if (
|
|
488
|
+
provider !== ACCOUNT_IMPORT_PROVIDER
|
|
489
|
+
|| formatArg.value !== ACCOUNT_IMPORT_FORMAT
|
|
490
|
+
|| formatArg.found !== true
|
|
491
|
+
) {
|
|
492
|
+
const code = provider === ACCOUNT_IMPORT_PROVIDER ? "unsupported_format" : "unsupported_provider";
|
|
493
|
+
console.error(`Error: ${code}`);
|
|
494
|
+
return 1;
|
|
495
|
+
}
|
|
496
|
+
const hasFile = fileArg.found && typeof fileArg.value === "string" && fileArg.value.length > 0;
|
|
497
|
+
if (args.length > 0 || hasFile === fromStdin || (fileArg.found && !hasFile)) {
|
|
498
|
+
return usage("Error: choose exactly one bounded source: --file <path> or --stdin");
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
let source: string;
|
|
502
|
+
try {
|
|
503
|
+
source = hasFile ? readBoundedImportFile(fileArg.value!) : await readBoundedImportStdin(deps);
|
|
504
|
+
} catch (error) {
|
|
505
|
+
const code = error instanceof Error ? error.message : "source_read_failed";
|
|
506
|
+
console.error(`Error: ${["invalid_document", "source_read_failed", "stdin_required", "stdin_timeout"].includes(code) ? code : "source_read_failed"}`);
|
|
507
|
+
return 1;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
let document: unknown;
|
|
511
|
+
try {
|
|
512
|
+
document = JSON.parse(source) as unknown;
|
|
513
|
+
} catch {
|
|
514
|
+
console.error("Error: invalid_document");
|
|
515
|
+
return 1;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const baseUrl = await resolveBaseUrl(deps);
|
|
519
|
+
if (!baseUrl) return proxyUnreachable();
|
|
520
|
+
const configuredImportTimeout = deps.importTimeoutMs;
|
|
521
|
+
const importTimeoutMs = typeof configuredImportTimeout === "number" && Number.isFinite(configuredImportTimeout)
|
|
522
|
+
? Math.min(ACCOUNT_IMPORT_TIMEOUT_MS, Math.max(1, Math.floor(configuredImportTimeout)))
|
|
523
|
+
: ACCOUNT_IMPORT_TIMEOUT_MS;
|
|
524
|
+
const controller = new AbortController();
|
|
525
|
+
let timedOut = false;
|
|
526
|
+
const timer = setTimeout(() => {
|
|
527
|
+
timedOut = true;
|
|
528
|
+
controller.abort();
|
|
529
|
+
}, importTimeoutMs);
|
|
530
|
+
let response;
|
|
531
|
+
try {
|
|
532
|
+
response = await apiJson(deps, baseUrl, "POST", "/api/oauth/accounts/import", {
|
|
533
|
+
provider,
|
|
534
|
+
format: formatArg.value,
|
|
535
|
+
document,
|
|
536
|
+
}, { signal: controller.signal });
|
|
537
|
+
} finally {
|
|
538
|
+
clearTimeout(timer);
|
|
539
|
+
}
|
|
540
|
+
if (response.status === 0) {
|
|
541
|
+
if (!timedOut) return proxyUnreachable(response.transportError);
|
|
542
|
+
console.error(`Error: import_timeout after ${importTimeoutMs}ms`);
|
|
543
|
+
return 1;
|
|
544
|
+
}
|
|
545
|
+
if (response.status !== 200) {
|
|
546
|
+
if (IMPORT_CODES.has(response.json.code as AccountImportCode)) {
|
|
547
|
+
console.error(`Error: ${response.json.code as AccountImportCode}`);
|
|
548
|
+
} else {
|
|
549
|
+
console.error(`Error: import_request_failed (status ${response.status})`);
|
|
550
|
+
}
|
|
551
|
+
return 1;
|
|
552
|
+
}
|
|
553
|
+
const result = safeImportResult(response.json);
|
|
554
|
+
if (!result) {
|
|
555
|
+
console.error("Error: invalid_response");
|
|
556
|
+
return 1;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
if (wantsJson) {
|
|
560
|
+
console.log(JSON.stringify(result, null, 2));
|
|
561
|
+
} else {
|
|
562
|
+
console.log(
|
|
563
|
+
`${provider}: ${result.importedCount} imported, ${result.updatedCount} updated, ${result.failedCount} failed, ${result.unsupportedCount} unsupported`,
|
|
564
|
+
);
|
|
565
|
+
for (const item of result.results) console.log(` #${item.index + 1} ${item.status} (${item.code})`);
|
|
566
|
+
}
|
|
567
|
+
return result.failedCount > 0 || result.unsupportedCount > 0 ? 1 : 0;
|
|
568
|
+
}
|
|
569
|
+
|
|
286
570
|
/**
|
|
287
571
|
* Lift a quota cooldown on a Codex account.
|
|
288
572
|
*
|
|
@@ -307,8 +591,8 @@ export async function cmdClearCooldown(args: string[], deps: AccountDeps): Promi
|
|
|
307
591
|
const baseUrl = await resolveBaseUrl(deps);
|
|
308
592
|
if (!baseUrl) return proxyUnreachable();
|
|
309
593
|
const response = await apiJson(deps, baseUrl, "POST", "/api/codex-auth/accounts/clear-cooldown", { id });
|
|
310
|
-
if (response.status === 0) return proxyUnreachable();
|
|
311
|
-
if (response.status !== 200) return apiError(response.json, `failed to clear cooldown for ${requestedId}
|
|
594
|
+
if (response.status === 0) return proxyUnreachable(response.transportError);
|
|
595
|
+
if (response.status !== 200) return apiError(response.json, `failed to clear cooldown for ${requestedId}`, response.status);
|
|
312
596
|
const cleared = response.json?.cleared === true;
|
|
313
597
|
if (wantsJson) console.log(JSON.stringify({ ok: true, provider: name, id, cleared }, null, 2));
|
|
314
598
|
else if (cleared) console.log(`${name}: cooldown lifted for ${requestedId}`);
|
|
@@ -316,6 +600,342 @@ export async function cmdClearCooldown(args: string[], deps: AccountDeps): Promi
|
|
|
316
600
|
return 0;
|
|
317
601
|
}
|
|
318
602
|
|
|
603
|
+
/**
|
|
604
|
+
* Named selection orders. The words convey sequence rather than rank because the
|
|
605
|
+
* pool moves down the list only when everything above it is drained — "high
|
|
606
|
+
* priority" would suggest the account gets more traffic, which is not what
|
|
607
|
+
* ordering does.
|
|
608
|
+
*/
|
|
609
|
+
const PRIORITY_PRESETS: Record<string, number> = {
|
|
610
|
+
first: 2,
|
|
611
|
+
earlier: 1,
|
|
612
|
+
normal: 0,
|
|
613
|
+
later: -1,
|
|
614
|
+
last: -2,
|
|
615
|
+
};
|
|
616
|
+
|
|
617
|
+
function priorityPresetName(priority: number): string | null {
|
|
618
|
+
return Object.entries(PRIORITY_PRESETS).find(([, value]) => value === priority)?.[0] ?? null;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function formatPriority(priority: number): string {
|
|
622
|
+
const preset = priorityPresetName(priority);
|
|
623
|
+
const signed = priority > 0 ? `+${priority}` : String(priority);
|
|
624
|
+
return preset ? `${signed} (${preset})` : signed;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/** `null` = reset to the default; `undefined` = unparseable. */
|
|
628
|
+
function parsePriorityArgument(raw: string): number | null | undefined {
|
|
629
|
+
const word = raw.trim().toLowerCase();
|
|
630
|
+
if (word === "reset") return null;
|
|
631
|
+
// Own keys only: `in` also matches "constructor", "__proto__", and friends.
|
|
632
|
+
if (Object.hasOwn(PRIORITY_PRESETS, word)) return PRIORITY_PRESETS[word];
|
|
633
|
+
// The regex only rules out shapes Number() would coerce ("1e2", " 1 ", ""); the range
|
|
634
|
+
// itself comes from the core parser so the CLI cannot drift from what the API accepts.
|
|
635
|
+
if (!/^[+-]?\d+$/.test(word)) return undefined;
|
|
636
|
+
return parseAccountPriority(Number(word)) ?? undefined;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
export async function cmdPriority(args: string[], deps: AccountDeps): Promise<number> {
|
|
640
|
+
const wantsJson = flag(args, "--json");
|
|
641
|
+
const name = args.shift();
|
|
642
|
+
const requestedId = args.shift();
|
|
643
|
+
const requestedPriority = args.shift();
|
|
644
|
+
if (!name || !requestedId || args.length) return usage();
|
|
645
|
+
const classified = configAndType(deps, name);
|
|
646
|
+
if ("error" in classified) return usage(`Error: ${classified.error}`);
|
|
647
|
+
if (classified.type !== "codex") {
|
|
648
|
+
return usage("Error: selection order only applies to the openai Codex account pool");
|
|
649
|
+
}
|
|
650
|
+
const id = requestedId === "main" ? MAIN_ID : requestedId;
|
|
651
|
+
|
|
652
|
+
// Validate before touching the network so a typo never reaches the proxy.
|
|
653
|
+
let priority: number | null | undefined;
|
|
654
|
+
if (requestedPriority !== undefined) {
|
|
655
|
+
priority = parsePriorityArgument(requestedPriority);
|
|
656
|
+
if (priority === undefined) {
|
|
657
|
+
return usage(`Error: selection order must be an integer ${MIN_ACCOUNT_PRIORITY}..${MAX_ACCOUNT_PRIORITY}, one of ${Object.keys(PRIORITY_PRESETS).join("/")}, or reset`);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const baseUrl = await resolveBaseUrl(deps);
|
|
662
|
+
if (!baseUrl) return proxyUnreachable();
|
|
663
|
+
|
|
664
|
+
// No value means "show" — a read must not rewrite what it is reporting.
|
|
665
|
+
if (priority === undefined) {
|
|
666
|
+
const result = await fetchCodexRows(deps, baseUrl);
|
|
667
|
+
const failed = familyFailure(result, `failed to read ${name} accounts`);
|
|
668
|
+
if (failed !== null) return failed;
|
|
669
|
+
const row = result.rows.find(candidate => candidate.id === id);
|
|
670
|
+
if (!row) return usage(`Error: no ${name} account ${requestedId}`);
|
|
671
|
+
const current = normalizeAccountPriority(row.priority);
|
|
672
|
+
if (wantsJson) {
|
|
673
|
+
console.log(JSON.stringify(
|
|
674
|
+
{ ok: true, provider: name, id, priority: current, preset: priorityPresetName(current) },
|
|
675
|
+
null,
|
|
676
|
+
2,
|
|
677
|
+
));
|
|
678
|
+
} else {
|
|
679
|
+
console.log(`${name}: ${requestedId} selection order is ${formatPriority(current)}`);
|
|
680
|
+
}
|
|
681
|
+
return 0;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
const response = await apiJson(deps, baseUrl, "PUT", "/api/codex-auth/accounts/priority", { id, priority });
|
|
685
|
+
if (response.status === 0) return proxyUnreachable(response.transportError);
|
|
686
|
+
if (response.status !== 200) return apiError(response.json, `failed to set selection order for ${requestedId}`, response.status);
|
|
687
|
+
const applied = typeof response.json.priority === "number" ? response.json.priority : (priority ?? 0);
|
|
688
|
+
if (wantsJson) {
|
|
689
|
+
console.log(JSON.stringify(
|
|
690
|
+
{ ok: true, provider: name, id, priority: applied, preset: priorityPresetName(applied) },
|
|
691
|
+
null,
|
|
692
|
+
2,
|
|
693
|
+
));
|
|
694
|
+
} else {
|
|
695
|
+
console.log(`${name}: ${requestedId} selection order is now ${formatPriority(applied)}`);
|
|
696
|
+
}
|
|
697
|
+
// Not cmdUse's note: re-ordering takes effect on the next unbound request rather than
|
|
698
|
+
// only on new sessions, because preemption moves those requests up immediately.
|
|
699
|
+
console.error("Takes effect from the next unbound request; running threads keep their current account until drained.");
|
|
700
|
+
// The release is not optional and not conditional on the value changing, so it has to be
|
|
701
|
+
// stated: this route is the only way to clear a pin without immediately setting another,
|
|
702
|
+
// which means a write storing the order an account already had still releases it. Without
|
|
703
|
+
// this line that is a silent side effect of a command that looks purely declarative.
|
|
704
|
+
console.error('Also releases any manual "use this account now" pin, on any account.');
|
|
705
|
+
return 0;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* `ocx account pause|resume <provider> <id>` (#2702).
|
|
710
|
+
*
|
|
711
|
+
* The server routes have always existed; only the CLI caller was missing, so pausing an
|
|
712
|
+
* account was dashboard-only. The issue reports these as POST; the code is PUT
|
|
713
|
+
* (`auth-api.ts:1494`), and the route is shared by both directions with a `paused` boolean
|
|
714
|
+
* rather than being two endpoints.
|
|
715
|
+
*/
|
|
716
|
+
export async function cmdPause(args: string[], deps: AccountDeps, paused: boolean): Promise<number> {
|
|
717
|
+
const wantsJson = flag(args, "--json");
|
|
718
|
+
const name = args.shift();
|
|
719
|
+
const requestedId = args.shift();
|
|
720
|
+
const verb = paused ? "pause" : "resume";
|
|
721
|
+
if (!name || !requestedId || args.length) return usage();
|
|
722
|
+
const classified = configAndType(deps, name);
|
|
723
|
+
if ("error" in classified) return usage(`Error: ${classified.error}`);
|
|
724
|
+
if (classified.type !== "codex") {
|
|
725
|
+
return usage(`Error: ${verb} applies to the openai Codex account pool`);
|
|
726
|
+
}
|
|
727
|
+
const id = requestedId === "main" ? MAIN_ID : requestedId;
|
|
728
|
+
|
|
729
|
+
const baseUrl = await resolveBaseUrl(deps);
|
|
730
|
+
if (!baseUrl) return proxyUnreachable();
|
|
731
|
+
|
|
732
|
+
const response = await apiJson(deps, baseUrl, "PUT", "/api/codex-auth/accounts/pause", { id, paused });
|
|
733
|
+
// Transport sentinel first: status 0 means the proxy never answered, and comparing it to
|
|
734
|
+
// 200 would report an unreachable proxy as a management error.
|
|
735
|
+
if (response.status === 0) return proxyUnreachable(response.transportError);
|
|
736
|
+
if (response.status !== 200) return apiError(response.json, `failed to ${verb} ${requestedId}`, response.status);
|
|
737
|
+
|
|
738
|
+
if (wantsJson) {
|
|
739
|
+
console.log(JSON.stringify({ ok: true, provider: name, id, paused }, null, 2));
|
|
740
|
+
} else {
|
|
741
|
+
console.log(`${name}: ${requestedId} ${paused ? "paused" : "resumed"}`);
|
|
742
|
+
}
|
|
743
|
+
if (paused) {
|
|
744
|
+
// Both are server-side effects of this route (auth-api.ts:1508-1510), not consequences
|
|
745
|
+
// the operator would infer from the word "pause".
|
|
746
|
+
console.error("Threads bound to this account are unbound, and a fallback account is selected if this one was active.");
|
|
747
|
+
}
|
|
748
|
+
return 0;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* `ocx account pause-exhausted [--off]` (#2702).
|
|
753
|
+
*
|
|
754
|
+
* Pauses every account whose quota is spent. The route refreshes quota for each account, so
|
|
755
|
+
* it can partially fail; the response distinguishes "checked none and some failed" from
|
|
756
|
+
* "checked some", and that distinction is reported rather than flattened into ok/not-ok.
|
|
757
|
+
*/
|
|
758
|
+
export async function cmdPauseExhausted(args: string[], deps: AccountDeps): Promise<number> {
|
|
759
|
+
const wantsJson = flag(args, "--json");
|
|
760
|
+
const name = args.shift();
|
|
761
|
+
if (!name || args.length) return usage();
|
|
762
|
+
const classified = configAndType(deps, name);
|
|
763
|
+
if ("error" in classified) return usage(`Error: ${classified.error}`);
|
|
764
|
+
if (classified.type !== "codex") {
|
|
765
|
+
return usage("Error: pause-exhausted applies to the openai Codex account pool");
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
const baseUrl = await resolveBaseUrl(deps);
|
|
769
|
+
if (!baseUrl) return proxyUnreachable();
|
|
770
|
+
|
|
771
|
+
const response = await apiJson(deps, baseUrl, "PUT", "/api/codex-auth/accounts/pause-exhausted", {});
|
|
772
|
+
if (response.status === 0) return proxyUnreachable(response.transportError);
|
|
773
|
+
if (response.status !== 200) return apiError(response.json, "failed to pause exhausted accounts", response.status);
|
|
774
|
+
|
|
775
|
+
const pausedIds = Array.isArray(response.json.pausedAccountIds)
|
|
776
|
+
? (response.json.pausedAccountIds as unknown[]).filter((value): value is string => typeof value === "string")
|
|
777
|
+
: [];
|
|
778
|
+
const checked = typeof response.json.checkedAccountCount === "number" ? response.json.checkedAccountCount : null;
|
|
779
|
+
const failed = typeof response.json.failedAccountCount === "number" ? response.json.failedAccountCount : null;
|
|
780
|
+
|
|
781
|
+
const complete = failed === null || failed === 0;
|
|
782
|
+
const ok = complete;
|
|
783
|
+
if (failed !== null && failed > 0) {
|
|
784
|
+
console.error(`Quota refresh failed for ${failed} account(s); those were not evaluated.`);
|
|
785
|
+
}
|
|
786
|
+
if (wantsJson) {
|
|
787
|
+
console.log(JSON.stringify({
|
|
788
|
+
ok,
|
|
789
|
+
complete,
|
|
790
|
+
provider: name,
|
|
791
|
+
pausedAccountIds: pausedIds,
|
|
792
|
+
checkedAccountCount: checked,
|
|
793
|
+
failedAccountCount: failed,
|
|
794
|
+
}, null, 2));
|
|
795
|
+
return ok ? 0 : 1;
|
|
796
|
+
}
|
|
797
|
+
console.log(pausedIds.length > 0
|
|
798
|
+
? `${name}: paused ${pausedIds.length} exhausted account(s): ${pausedIds.join(", ")}`
|
|
799
|
+
: `${name}: no exhausted accounts to pause`);
|
|
800
|
+
return ok ? 0 : 1;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* Two pools expose strategy and sticky, and they are NOT reached the same way:
|
|
805
|
+
*
|
|
806
|
+
* | | Codex pool | Anthropic pool |
|
|
807
|
+
* |---|---|---|
|
|
808
|
+
* | read | `GET /api/codex-auth/active` | `GET /api/oauth/accounts/pool?provider=` |
|
|
809
|
+
* | write | `PUT /api/codex-auth/pool-strategy` | `PUT /api/oauth/accounts/pool` |
|
|
810
|
+
* | keys | `accountPoolStrategy`/`accountPoolStickyLimit` | `strategy`/`stickyLimit` |
|
|
811
|
+
* | body | bare field | field **plus** a mandatory `provider` |
|
|
812
|
+
*
|
|
813
|
+
* Omitting `provider` from the Anthropic write body earns a 400
|
|
814
|
+
* (`oauth-account-routes.ts:344`), so the asymmetry has to be encoded somewhere. Encoding it
|
|
815
|
+
* here keeps ONE verb pair working on both pools. The alternative the plan left open -- a second
|
|
816
|
+
* `provider-strategy`/`provider-sticky` pair -- would double the surface an operator must learn
|
|
817
|
+
* to express one idea, and a CLI that can steer one pool and not the other is exactly the trap
|
|
818
|
+
* this unit exists to remove.
|
|
819
|
+
*/
|
|
820
|
+
interface PoolTransport {
|
|
821
|
+
readPath: string;
|
|
822
|
+
writePath: string;
|
|
823
|
+
/** Response key carrying the applied strategy. */
|
|
824
|
+
strategyKey: string;
|
|
825
|
+
/** Response key carrying the applied sticky limit. */
|
|
826
|
+
stickyKey: string;
|
|
827
|
+
writeBody: (field: "strategy" | "stickyLimit", value: unknown) => Record<string, unknown>;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
const CODEX_POOL_TRANSPORT: PoolTransport = {
|
|
831
|
+
readPath: "/api/codex-auth/active",
|
|
832
|
+
writePath: "/api/codex-auth/pool-strategy",
|
|
833
|
+
strategyKey: "accountPoolStrategy",
|
|
834
|
+
stickyKey: "accountPoolStickyLimit",
|
|
835
|
+
writeBody: (field, value) => ({ [field]: value }),
|
|
836
|
+
};
|
|
837
|
+
|
|
838
|
+
function anthropicPoolTransport(provider: string): PoolTransport {
|
|
839
|
+
return {
|
|
840
|
+
readPath: `/api/oauth/accounts/pool?provider=${encodeURIComponent(provider)}`,
|
|
841
|
+
writePath: "/api/oauth/accounts/pool",
|
|
842
|
+
strategyKey: "strategy",
|
|
843
|
+
stickyKey: "stickyLimit",
|
|
844
|
+
writeBody: (field, value) => ({ provider, [field]: value }),
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* The pool-config route supports `anthropic` only and says so with a 400. Any other OAuth
|
|
850
|
+
* provider is refused here with the same wording rather than spending a round-trip to learn it.
|
|
851
|
+
*/
|
|
852
|
+
function poolTransportFor(
|
|
853
|
+
classified: { type: "codex" | "oauth" | "api-key" },
|
|
854
|
+
name: string,
|
|
855
|
+
): PoolTransport | string {
|
|
856
|
+
if (classified.type === "codex") return CODEX_POOL_TRANSPORT;
|
|
857
|
+
if (classified.type === "oauth" && name === "anthropic") return anthropicPoolTransport(name);
|
|
858
|
+
return `pool settings apply to the openai Codex pool and the anthropic pool, not "${name}"`;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* `ocx account strategy <provider> [<name>]` and `ocx account sticky <provider> [<n>]` (#2702).
|
|
863
|
+
*
|
|
864
|
+
* One implementation rather than two near-duplicates, because strategy and sticky are two
|
|
865
|
+
* fields of one setting on every pool that has them.
|
|
866
|
+
*
|
|
867
|
+
* Values are NOT re-validated here. The server owns both contracts -- three strategy names,
|
|
868
|
+
* a 1-100 sticky bound -- and a duplicated bound is a second thing to keep in sync. Its 400
|
|
869
|
+
* is actionable now that the CLI prints `reason`.
|
|
870
|
+
*/
|
|
871
|
+
async function poolSetting(
|
|
872
|
+
args: string[],
|
|
873
|
+
deps: AccountDeps,
|
|
874
|
+
field: "strategy" | "stickyLimit",
|
|
875
|
+
): Promise<number> {
|
|
876
|
+
const wantsJson = flag(args, "--json");
|
|
877
|
+
const name = args.shift();
|
|
878
|
+
const requested = args.shift();
|
|
879
|
+
const label = field === "strategy" ? "pool strategy" : "sticky limit";
|
|
880
|
+
if (!name || args.length) return usage();
|
|
881
|
+
const classified = configAndType(deps, name);
|
|
882
|
+
if ("error" in classified) return usage(`Error: ${classified.error}`);
|
|
883
|
+
const transport = poolTransportFor(classified, name);
|
|
884
|
+
if (typeof transport === "string") return usage(`Error: ${transport}`);
|
|
885
|
+
|
|
886
|
+
const baseUrl = await resolveBaseUrl(deps);
|
|
887
|
+
if (!baseUrl) return proxyUnreachable();
|
|
888
|
+
|
|
889
|
+
// No value means show. A read must not rewrite what it is reporting.
|
|
890
|
+
if (requested === undefined) {
|
|
891
|
+
const response = await apiJson(deps, baseUrl, "GET", transport.readPath);
|
|
892
|
+
if (response.status === 0) return proxyUnreachable(response.transportError);
|
|
893
|
+
if (response.status !== 200) return apiError(response.json, `failed to read ${label}`, response.status);
|
|
894
|
+
const strategy = response.json[transport.strategyKey];
|
|
895
|
+
const sticky = response.json[transport.stickyKey];
|
|
896
|
+
if (wantsJson) {
|
|
897
|
+
// Pool-neutral key names: the two routes spell the same two settings differently, and a
|
|
898
|
+
// `--json` consumer should not have to branch on which pool answered.
|
|
899
|
+
console.log(JSON.stringify({ ok: true, provider: name, strategy, stickyLimit: sticky }, null, 2));
|
|
900
|
+
} else {
|
|
901
|
+
console.log(`${name}: ${label} is ${String(field === "strategy" ? strategy : sticky)}`);
|
|
902
|
+
}
|
|
903
|
+
return 0;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// Sent as a number when it parses as one so the server sees the type it validates;
|
|
907
|
+
// a non-numeric string still goes through and earns the server's own 400.
|
|
908
|
+
const value = field === "strategy"
|
|
909
|
+
? requested
|
|
910
|
+
: (Number.isNaN(Number(requested)) ? requested : Number(requested));
|
|
911
|
+
const response = await apiJson(deps, baseUrl, "PUT", transport.writePath, transport.writeBody(field, value));
|
|
912
|
+
if (response.status === 0) return proxyUnreachable(response.transportError);
|
|
913
|
+
if (response.status !== 200) return apiError(response.json, `failed to set ${label}`, response.status);
|
|
914
|
+
|
|
915
|
+
if (wantsJson) {
|
|
916
|
+
console.log(JSON.stringify({
|
|
917
|
+
ok: true,
|
|
918
|
+
provider: name,
|
|
919
|
+
strategy: response.json[transport.strategyKey],
|
|
920
|
+
stickyLimit: response.json[transport.stickyKey],
|
|
921
|
+
}, null, 2));
|
|
922
|
+
} else {
|
|
923
|
+
// Echo the APPLIED value from the response, not the requested one: the server normalizes,
|
|
924
|
+
// and printing the request would hide a normalization the operator should see.
|
|
925
|
+
const applied = field === "strategy" ? response.json[transport.strategyKey] : response.json[transport.stickyKey];
|
|
926
|
+
console.log(`${name}: ${label} is now ${String(applied)}`);
|
|
927
|
+
}
|
|
928
|
+
return 0;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
export function cmdStrategy(args: string[], deps: AccountDeps): Promise<number> {
|
|
932
|
+
return poolSetting(args, deps, "strategy");
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
export function cmdSticky(args: string[], deps: AccountDeps): Promise<number> {
|
|
936
|
+
return poolSetting(args, deps, "stickyLimit");
|
|
937
|
+
}
|
|
938
|
+
|
|
319
939
|
export async function cmdAlias(args: string[], deps: AccountDeps): Promise<number> {
|
|
320
940
|
const wantsJson = flag(args, "--json");
|
|
321
941
|
const name = args.shift();
|
|
@@ -341,8 +961,8 @@ export async function cmdAlias(args: string[], deps: AccountDeps): Promise<numbe
|
|
|
341
961
|
? { provider: name, accountId: id, alias }
|
|
342
962
|
: { name, id, alias };
|
|
343
963
|
const response = await apiJson(deps, baseUrl, "PUT", path, body);
|
|
344
|
-
if (response.status === 0) return proxyUnreachable();
|
|
345
|
-
if (response.status !== 200) return apiError(response.json, `failed to rename ${requestedId}
|
|
964
|
+
if (response.status === 0) return proxyUnreachable(response.transportError);
|
|
965
|
+
if (response.status !== 200) return apiError(response.json, `failed to rename ${requestedId}`, response.status);
|
|
346
966
|
const result = { ok: true, provider: name, id, alias: alias || null };
|
|
347
967
|
if (wantsJson) console.log(JSON.stringify(result, null, 2));
|
|
348
968
|
else console.log(alias ? `${name}: ${requestedId} is now “${alias}”` : `${name}: cleared alias for ${requestedId}`);
|