@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,242 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, join } from "node:path";
|
|
3
|
+
import { CODEX_CONFIG_PATH } from "./paths";
|
|
4
|
+
import { redactUserPath } from "../lib/redact";
|
|
5
|
+
|
|
6
|
+
// Mirrors codex-rs core-plugins/src/marketplace.rs MARKETPLACE_MANIFEST_RELATIVE_PATHS.
|
|
7
|
+
// A marketplace root "resolves" only when one of these files exists under it.
|
|
8
|
+
const MARKETPLACE_MANIFEST_RELATIVE_PATHS = [
|
|
9
|
+
".agents/plugins/marketplace.json",
|
|
10
|
+
".claude-plugin/marketplace.json",
|
|
11
|
+
] as const;
|
|
12
|
+
|
|
13
|
+
const OPENAI_BUNDLED_MARKETPLACE_NAME = "openai-bundled";
|
|
14
|
+
|
|
15
|
+
// Where the Codex desktop app's bundled plugins live, relative to an install
|
|
16
|
+
// root. Windows app-package paths embed the app version, so the actual install
|
|
17
|
+
// root is discovered by scanning candidate bases (LOCALAPPDATA, etc.) rather
|
|
18
|
+
// than hardcoded. The bundled marketplace dir is named after the marketplace.
|
|
19
|
+
const BUNDLED_MARKETPLACE_LEAF = join("plugins", "bundled-marketplaces", OPENAI_BUNDLED_MARKETPLACE_NAME);
|
|
20
|
+
const CODEX_APP_DIR_SEGMENTS = [join("Programs", "@openai", "codex"), join("Programs", "codex"), join("@openai", "codex"), "codex"] as const;
|
|
21
|
+
|
|
22
|
+
// Plugins the issue (#43) calls out. Treated as data, not as an authoritative
|
|
23
|
+
// allowlist: codex-rs only allowlists chrome/computer-use, but the diagnostic
|
|
24
|
+
// just reports presence, so listing browser here is informational only.
|
|
25
|
+
const COMMON_BUNDLED_PLUGINS = ["computer-use", "browser", "chrome"] as const;
|
|
26
|
+
|
|
27
|
+
export type CodexPluginsDiagnostic =
|
|
28
|
+
| { applicable: false; reason: string; summary: string }
|
|
29
|
+
| {
|
|
30
|
+
applicable: true;
|
|
31
|
+
stale: boolean;
|
|
32
|
+
marketplace: {
|
|
33
|
+
name: string;
|
|
34
|
+
present: boolean;
|
|
35
|
+
sourceType: string | null;
|
|
36
|
+
source: string | null;
|
|
37
|
+
resolvesToManifest: boolean;
|
|
38
|
+
currentBundledPath: string | null;
|
|
39
|
+
pathMismatch: boolean;
|
|
40
|
+
};
|
|
41
|
+
bundledPlugins: Array<{ id: string; configured: boolean }>;
|
|
42
|
+
suggestedRepair: string | null;
|
|
43
|
+
summary: string;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/** True when the table at `[marketplaces.<name>]` exists in the config text. */
|
|
47
|
+
function readMarketplaceTable(configText: string, name: string): Record<string, string> | null {
|
|
48
|
+
// Split on CRLF or LF: config.toml on Windows (the platform this diagnostic
|
|
49
|
+
// targets) uses CRLF, and a leftover \r would defeat the `$`-anchored regexes.
|
|
50
|
+
const lines = configText.split(/\r?\n/);
|
|
51
|
+
const header = new RegExp(`^\\s*\\[marketplaces\\.(?:"${escapeRegExp(name)}"|${escapeRegExp(name)})\\]\\s*(?:#.*)?$`);
|
|
52
|
+
let start = -1;
|
|
53
|
+
for (let i = 0; i < lines.length; i++) {
|
|
54
|
+
if (header.test(lines[i] ?? "")) { start = i + 1; break; }
|
|
55
|
+
}
|
|
56
|
+
if (start === -1) return null;
|
|
57
|
+
|
|
58
|
+
const table: Record<string, string> = {};
|
|
59
|
+
for (let i = start; i < lines.length; i++) {
|
|
60
|
+
const line = lines[i] ?? "";
|
|
61
|
+
if (/^\s*\[/.test(line)) break; // next table starts; stop
|
|
62
|
+
const m = line.match(/^\s*([A-Za-z0-9_-]+)\s*=\s*("(?:\\.|[^"])*"|'[^']*'|[^#]+?)\s*(?:#.*)?$/);
|
|
63
|
+
if (!m) continue;
|
|
64
|
+
table[m[1]] = unquoteTomlValue(m[2].trim());
|
|
65
|
+
}
|
|
66
|
+
return table;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function unquoteTomlValue(raw: string): string {
|
|
70
|
+
if (raw.startsWith("\"")) {
|
|
71
|
+
try { return JSON.parse(raw) as string; } catch { return raw.slice(1, -1); }
|
|
72
|
+
}
|
|
73
|
+
if (raw.startsWith("'")) return raw.slice(1, -1);
|
|
74
|
+
return raw;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function escapeRegExp(value: string): string {
|
|
78
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** A directory is a bundled-marketplace root when it holds a supported manifest. */
|
|
82
|
+
function dirHasManifest(dir: string): boolean {
|
|
83
|
+
return MARKETPLACE_MANIFEST_RELATIVE_PATHS.some(rel => existsSync(join(dir, rel)));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Locate the bundled `openai-bundled` marketplace dir under the installed Codex
|
|
88
|
+
* desktop app on Windows. Windows app paths embed the app version, so we scan
|
|
89
|
+
* candidate install bases for a versioned app dir whose
|
|
90
|
+
* `plugins/bundled-marketplaces/openai-bundled` holds a manifest. Returns the
|
|
91
|
+
* newest matching dir (by mtime) or null. Filesystem access is injectable for
|
|
92
|
+
* tests so a Windows layout can be exercised on any OS.
|
|
93
|
+
*/
|
|
94
|
+
export function locateCurrentBundledMarketplace(
|
|
95
|
+
options: {
|
|
96
|
+
env?: NodeJS.ProcessEnv;
|
|
97
|
+
listDir?: (dir: string) => string[];
|
|
98
|
+
isManifestRoot?: (dir: string) => boolean;
|
|
99
|
+
mtimeOf?: (dir: string) => number;
|
|
100
|
+
} = {},
|
|
101
|
+
): string | null {
|
|
102
|
+
const env = options.env ?? process.env;
|
|
103
|
+
const listDir = options.listDir ?? ((dir: string) => {
|
|
104
|
+
try { return readdirSync(dir); } catch { return []; }
|
|
105
|
+
});
|
|
106
|
+
const isManifestRoot = options.isManifestRoot ?? dirHasManifest;
|
|
107
|
+
const mtimeOf = options.mtimeOf ?? ((dir: string) => {
|
|
108
|
+
try { return statSync(dir).mtimeMs; } catch { return 0; }
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const bases = [env.LOCALAPPDATA, env.PROGRAMFILES, env["ProgramFiles(x86)"], env.APPDATA]
|
|
112
|
+
.filter((b): b is string => typeof b === "string" && b.length > 0);
|
|
113
|
+
|
|
114
|
+
const candidates: string[] = [];
|
|
115
|
+
for (const base of bases) {
|
|
116
|
+
for (const seg of CODEX_APP_DIR_SEGMENTS) {
|
|
117
|
+
const appRoot = join(base, seg);
|
|
118
|
+
// Direct (unversioned) layout.
|
|
119
|
+
const direct = join(appRoot, BUNDLED_MARKETPLACE_LEAF);
|
|
120
|
+
if (isManifestRoot(direct)) candidates.push(direct);
|
|
121
|
+
// Versioned layout: appRoot/<version>/plugins/bundled-marketplaces/openai-bundled
|
|
122
|
+
for (const child of listDir(appRoot)) {
|
|
123
|
+
const versioned = join(appRoot, child, BUNDLED_MARKETPLACE_LEAF);
|
|
124
|
+
if (isManifestRoot(versioned)) candidates.push(versioned);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (candidates.length === 0) return null;
|
|
129
|
+
candidates.sort((a, b) => mtimeOf(b) - mtimeOf(a));
|
|
130
|
+
return candidates[0] ?? null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Normalize a path for comparison: lowercase + unify separators (Windows is case-insensitive). */
|
|
134
|
+
function normalizePathForCompare(path: string): string {
|
|
135
|
+
return path.replace(/[\\/]+/g, "\\").replace(/\\+$/, "").toLowerCase();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** A local marketplace `source` resolves when it holds a supported manifest. */
|
|
139
|
+
function sourceResolvesToManifest(source: string): boolean {
|
|
140
|
+
if (!isAbsolute(source)) return false;
|
|
141
|
+
if (!existsSync(source)) return false;
|
|
142
|
+
return MARKETPLACE_MANIFEST_RELATIVE_PATHS.some(rel => existsSync(join(source, rel)));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Read-only diagnostic for the Codex `openai-bundled` plugin marketplace.
|
|
147
|
+
*
|
|
148
|
+
* Only meaningful on Windows, where app-package paths embed the app version and
|
|
149
|
+
* go stale after an update. On other platforms it reports "not applicable".
|
|
150
|
+
* NEVER mutates config.toml, never invokes `codex plugin marketplace add`.
|
|
151
|
+
*/
|
|
152
|
+
export function diagnoseCodexBundledPlugins(
|
|
153
|
+
options: {
|
|
154
|
+
platform?: NodeJS.Platform;
|
|
155
|
+
configPath?: string;
|
|
156
|
+
locateCurrent?: () => string | null;
|
|
157
|
+
} = {},
|
|
158
|
+
): CodexPluginsDiagnostic {
|
|
159
|
+
const platform = options.platform ?? process.platform;
|
|
160
|
+
if (platform !== "win32") {
|
|
161
|
+
return {
|
|
162
|
+
applicable: false,
|
|
163
|
+
reason: "not_windows",
|
|
164
|
+
summary: "not applicable (bundled-marketplace staleness is Windows-specific)",
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const configPath = options.configPath ?? CODEX_CONFIG_PATH;
|
|
169
|
+
let configText: string;
|
|
170
|
+
try {
|
|
171
|
+
configText = readFileSync(configPath, "utf8");
|
|
172
|
+
} catch {
|
|
173
|
+
return {
|
|
174
|
+
applicable: false,
|
|
175
|
+
reason: "config_unreadable",
|
|
176
|
+
summary: "not applicable (Codex config.toml not found or unreadable)",
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const table = readMarketplaceTable(configText, OPENAI_BUNDLED_MARKETPLACE_NAME);
|
|
181
|
+
const present = table !== null;
|
|
182
|
+
const sourceType = table?.source_type ?? null;
|
|
183
|
+
const source = table?.source ?? null;
|
|
184
|
+
const isLocal = sourceType === "local" && !!source;
|
|
185
|
+
const resolvesToManifest = isLocal ? sourceResolvesToManifest(source as string) : false;
|
|
186
|
+
|
|
187
|
+
// Locate the bundled marketplace under the currently installed Codex app, so
|
|
188
|
+
// we can tell "registered path differs from the live app path" (the Windows
|
|
189
|
+
// app-update staleness signal) apart from a merely missing manifest.
|
|
190
|
+
const locateCurrent = options.locateCurrent ?? (() => locateCurrentBundledMarketplace());
|
|
191
|
+
const currentBundledPath = locateCurrent();
|
|
192
|
+
const pathMismatch = !!(
|
|
193
|
+
currentBundledPath && source && isLocal &&
|
|
194
|
+
normalizePathForCompare(currentBundledPath) !== normalizePathForCompare(source)
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
// Stale = a registered local bundled marketplace whose source no longer
|
|
198
|
+
// resolves to a manifest, OR whose registered path differs from the live
|
|
199
|
+
// app's bundled path (the Windows app-update signal). A missing marketplace
|
|
200
|
+
// is "not stale" but flagged separately by `present: false`.
|
|
201
|
+
const stale = present && isLocal && (!resolvesToManifest || pathMismatch);
|
|
202
|
+
// Present but not a usable local entry (wrong source_type or empty source):
|
|
203
|
+
// not "stale" in the app-update sense, but it must NOT be reported as healthy.
|
|
204
|
+
const malformed = present && !isLocal;
|
|
205
|
+
|
|
206
|
+
const bundledPlugins = COMMON_BUNDLED_PLUGINS.map(id => ({
|
|
207
|
+
id,
|
|
208
|
+
configured: new RegExp(`\\[plugins\\.(?:"${escapeRegExp(`${id}@${OPENAI_BUNDLED_MARKETPLACE_NAME}`)}")\\]`).test(configText),
|
|
209
|
+
}));
|
|
210
|
+
|
|
211
|
+
const repairTarget = currentBundledPath ?? `<current ${OPENAI_BUNDLED_MARKETPLACE_NAME} path under the installed Codex app>`;
|
|
212
|
+
const suggestedRepair = (stale || pathMismatch)
|
|
213
|
+
? `codex plugin marketplace add ${currentBundledPath ? redactUserPath(currentBundledPath) : repairTarget}`
|
|
214
|
+
: null;
|
|
215
|
+
|
|
216
|
+
const summary = !present
|
|
217
|
+
? `no [marketplaces.${OPENAI_BUNDLED_MARKETPLACE_NAME}] entry in Codex config`
|
|
218
|
+
: malformed
|
|
219
|
+
? `[marketplaces.${OPENAI_BUNDLED_MARKETPLACE_NAME}] is present but not a usable local source (source_type/source missing)`
|
|
220
|
+
: !resolvesToManifest
|
|
221
|
+
? `stale: registered ${OPENAI_BUNDLED_MARKETPLACE_NAME} source no longer resolves to a marketplace manifest`
|
|
222
|
+
: pathMismatch
|
|
223
|
+
? `stale: registered ${OPENAI_BUNDLED_MARKETPLACE_NAME} path differs from the installed Codex app's bundled path`
|
|
224
|
+
: `ok: ${OPENAI_BUNDLED_MARKETPLACE_NAME} marketplace resolves`;
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
applicable: true,
|
|
228
|
+
stale,
|
|
229
|
+
marketplace: {
|
|
230
|
+
name: OPENAI_BUNDLED_MARKETPLACE_NAME,
|
|
231
|
+
present,
|
|
232
|
+
sourceType,
|
|
233
|
+
source: source ? redactUserPath(source) : null,
|
|
234
|
+
resolvesToManifest,
|
|
235
|
+
currentBundledPath: currentBundledPath ? redactUserPath(currentBundledPath) : null,
|
|
236
|
+
pathMismatch,
|
|
237
|
+
},
|
|
238
|
+
bundledPlugins,
|
|
239
|
+
suggestedRepair,
|
|
240
|
+
summary,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import type { OcxAccountPoolRotationStrategy } from "../types";
|
|
2
|
+
import type { GenerationContext } from "../lib/state-store-sweeper";
|
|
3
|
+
|
|
4
|
+
export const POOL_KEY_CODEX = "codex";
|
|
5
|
+
export const POOL_KEY_ANTHROPIC = "anthropic";
|
|
6
|
+
|
|
7
|
+
interface SelectionState {
|
|
8
|
+
activeKey?: string;
|
|
9
|
+
successes: number;
|
|
10
|
+
currentWeights: Map<string, number>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const selectionState = new Map<string, SelectionState>();
|
|
14
|
+
let lastReconciledGeneration = 0;
|
|
15
|
+
|
|
16
|
+
const DEFAULT_STICKY_LIMIT = 1;
|
|
17
|
+
const MIN_STICKY_LIMIT = 1;
|
|
18
|
+
const MAX_STICKY_LIMIT = 100;
|
|
19
|
+
const DEFAULT_STRATEGY: OcxAccountPoolRotationStrategy = "quota";
|
|
20
|
+
const VALID_STRATEGIES = new Set<OcxAccountPoolRotationStrategy>(["quota", "round-robin", "fill-first"]);
|
|
21
|
+
|
|
22
|
+
/** Strict parse for management APIs — returns null instead of defaulting. */
|
|
23
|
+
export function parseAccountPoolStrategy(raw: unknown): OcxAccountPoolRotationStrategy | null {
|
|
24
|
+
if (typeof raw === "string" && VALID_STRATEGIES.has(raw as OcxAccountPoolRotationStrategy)) {
|
|
25
|
+
return raw as OcxAccountPoolRotationStrategy;
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Strict parse for management APIs — returns null instead of defaulting. */
|
|
31
|
+
export function parseAccountPoolStickyLimit(raw: unknown): number | null {
|
|
32
|
+
if (typeof raw === "number" && Number.isInteger(raw) && raw >= MIN_STICKY_LIMIT && raw <= MAX_STICKY_LIMIT) {
|
|
33
|
+
return raw;
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function normalizeAccountPoolStrategy(raw: unknown): OcxAccountPoolRotationStrategy {
|
|
39
|
+
return parseAccountPoolStrategy(raw) ?? DEFAULT_STRATEGY;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function normalizeAccountPoolStickyLimit(raw: unknown): number {
|
|
43
|
+
return parseAccountPoolStickyLimit(raw) ?? DEFAULT_STICKY_LIMIT;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getOrCreateState(poolKey: string): SelectionState {
|
|
47
|
+
let state = selectionState.get(poolKey);
|
|
48
|
+
if (!state) {
|
|
49
|
+
state = { successes: 0, currentWeights: new Map() };
|
|
50
|
+
selectionState.set(poolKey, state);
|
|
51
|
+
}
|
|
52
|
+
return state;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function cloneSelectionState(state: SelectionState): SelectionState {
|
|
56
|
+
return {
|
|
57
|
+
activeKey: state.activeKey,
|
|
58
|
+
successes: state.successes,
|
|
59
|
+
currentWeights: new Map(state.currentWeights),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function smoothWeightedIndex(ids: string[], state: SelectionState): number {
|
|
64
|
+
let best = -1;
|
|
65
|
+
let bestScore = Number.NEGATIVE_INFINITY;
|
|
66
|
+
let total = 0;
|
|
67
|
+
const weight = 1;
|
|
68
|
+
for (let i = 0; i < ids.length; i++) {
|
|
69
|
+
const id = ids[i]!;
|
|
70
|
+
const score = (state.currentWeights.get(id) ?? 0) + weight;
|
|
71
|
+
state.currentWeights.set(id, score);
|
|
72
|
+
total += weight;
|
|
73
|
+
if (score > bestScore) {
|
|
74
|
+
best = i;
|
|
75
|
+
bestScore = score;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (best >= 0) {
|
|
79
|
+
const key = ids[best]!;
|
|
80
|
+
state.currentWeights.set(key, (state.currentWeights.get(key) ?? 0) - total);
|
|
81
|
+
}
|
|
82
|
+
return best;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Shared pick core. Mutates `state` the same way live resolve does; callers pass
|
|
87
|
+
* either the live map entry or a scratch/clone for dry-run peek.
|
|
88
|
+
*/
|
|
89
|
+
function pickRoundRobinFromState(
|
|
90
|
+
eligibleIds: string[],
|
|
91
|
+
stickyLimit: number,
|
|
92
|
+
state: SelectionState,
|
|
93
|
+
commitSticky: boolean,
|
|
94
|
+
): string | null {
|
|
95
|
+
if (eligibleIds.length === 0) return null;
|
|
96
|
+
|
|
97
|
+
const limit = normalizeAccountPoolStickyLimit(stickyLimit);
|
|
98
|
+
|
|
99
|
+
if (state.activeKey && eligibleIds.includes(state.activeKey)) {
|
|
100
|
+
return state.activeKey;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (state.activeKey) {
|
|
104
|
+
delete state.activeKey;
|
|
105
|
+
state.successes = 0;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const index = smoothWeightedIndex(eligibleIds, state);
|
|
109
|
+
if (index < 0) return null;
|
|
110
|
+
|
|
111
|
+
const picked = eligibleIds[index]!;
|
|
112
|
+
if (commitSticky && limit > 1) {
|
|
113
|
+
state.activeKey = picked;
|
|
114
|
+
state.successes = 0;
|
|
115
|
+
}
|
|
116
|
+
return picked;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function pickRoundRobinAccount(
|
|
120
|
+
poolKey: string,
|
|
121
|
+
eligibleIds: string[],
|
|
122
|
+
stickyLimit: number,
|
|
123
|
+
): string | null {
|
|
124
|
+
return pickRoundRobinFromState(eligibleIds, stickyLimit, getOrCreateState(poolKey), true);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Dry-run of {@link pickRoundRobinAccount}: returns the same account resolve would
|
|
129
|
+
* pick without advancing ring weights, activeKey, or successes.
|
|
130
|
+
*/
|
|
131
|
+
export function peekRoundRobinAccount(
|
|
132
|
+
poolKey: string,
|
|
133
|
+
eligibleIds: string[],
|
|
134
|
+
stickyLimit: number,
|
|
135
|
+
): string | null {
|
|
136
|
+
const live = selectionState.get(poolKey);
|
|
137
|
+
const scratch = live
|
|
138
|
+
? cloneSelectionState(live)
|
|
139
|
+
: { successes: 0, currentWeights: new Map<string, number>() };
|
|
140
|
+
return pickRoundRobinFromState(eligibleIds, stickyLimit, scratch, false);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function notePoolRotationSuccess(
|
|
144
|
+
poolKey: string,
|
|
145
|
+
accountId: string,
|
|
146
|
+
stickyLimit: number,
|
|
147
|
+
): void {
|
|
148
|
+
const limit = normalizeAccountPoolStickyLimit(stickyLimit);
|
|
149
|
+
const state = selectionState.get(poolKey);
|
|
150
|
+
if (!state) return;
|
|
151
|
+
if (state.activeKey !== accountId) {
|
|
152
|
+
state.activeKey = accountId;
|
|
153
|
+
state.successes = 0;
|
|
154
|
+
}
|
|
155
|
+
state.successes += 1;
|
|
156
|
+
if (state.successes >= limit) {
|
|
157
|
+
delete state.activeKey;
|
|
158
|
+
state.successes = 0;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function notePoolRotationFailure(poolKey: string, accountId: string): void {
|
|
163
|
+
const state = selectionState.get(poolKey);
|
|
164
|
+
if (state?.activeKey === accountId) {
|
|
165
|
+
delete state.activeKey;
|
|
166
|
+
state.successes = 0;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Force the next sticky/RR pick onto `accountId` (manual dashboard selection).
|
|
172
|
+
* Clears sticky success counters and ring weights so the seeded account is held
|
|
173
|
+
* for the next new-session pick before ordinary rotation resumes.
|
|
174
|
+
*/
|
|
175
|
+
export function seedPoolRotationAccount(poolKey: string, accountId: string): void {
|
|
176
|
+
const state = getOrCreateState(poolKey);
|
|
177
|
+
state.activeKey = accountId;
|
|
178
|
+
state.successes = 0;
|
|
179
|
+
state.currentWeights.clear();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function clearPoolRotationState(poolKey?: string): void {
|
|
183
|
+
if (poolKey === undefined) {
|
|
184
|
+
selectionState.clear();
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
selectionState.delete(poolKey);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function reconcilePoolRotationState(context: GenerationContext): number {
|
|
191
|
+
if (context.generation <= lastReconciledGeneration) return 0;
|
|
192
|
+
const anthropicIds = new Set<string>();
|
|
193
|
+
for (const key of context.oauthAccountKeys) {
|
|
194
|
+
const separator = key.indexOf("\0");
|
|
195
|
+
if (separator > 0 && key.slice(0, separator) === "anthropic") {
|
|
196
|
+
anthropicIds.add(key.slice(separator + 1));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
let removed = 0;
|
|
200
|
+
for (const [poolKey, state] of selectionState) {
|
|
201
|
+
const valid = poolKey === POOL_KEY_ANTHROPIC
|
|
202
|
+
? anthropicIds
|
|
203
|
+
: poolKey === POOL_KEY_CODEX || poolKey.startsWith(`${POOL_KEY_CODEX}:`)
|
|
204
|
+
? context.codexAccountIds
|
|
205
|
+
: null;
|
|
206
|
+
if (!valid) continue;
|
|
207
|
+
if (valid.size === 0) {
|
|
208
|
+
selectionState.delete(poolKey);
|
|
209
|
+
removed += 1;
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (state.activeKey && !valid.has(state.activeKey)) {
|
|
213
|
+
delete state.activeKey;
|
|
214
|
+
state.successes = 0;
|
|
215
|
+
removed += 1;
|
|
216
|
+
}
|
|
217
|
+
for (const accountId of state.currentWeights.keys()) {
|
|
218
|
+
if (valid.has(accountId)) continue;
|
|
219
|
+
state.currentWeights.delete(accountId);
|
|
220
|
+
removed += 1;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
lastReconciledGeneration = context.generation;
|
|
224
|
+
return removed;
|
|
225
|
+
}
|