@coseung2/opencodex 2.8.0-cs.1
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 +219 -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-notch.mjs +78 -0
- package/bin/ocx.mjs +454 -0
- package/bin/package-main.mjs +9 -0
- package/gui/dist/assets/index-CAnnes06.js +67 -0
- package/gui/dist/assets/index-OY43ubAq.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 +107 -0
- package/packages/ocx-notch/README.md +55 -0
- package/packages/ocx-notch/THIRD_PARTY_NOTICES.md +45 -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 +330 -0
- package/src/cli/index.ts +1103 -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/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 +134 -0
- package/src/cli/status-oauth.ts +78 -0
- package/src/cli/status.ts +321 -0
- package/src/cli/system-command.ts +114 -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/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 +2355 -0
- package/src/generated/jawcode-model-metadata.ts +104 -0
- package/src/github/star-state.ts +203 -0
- package/src/grok/inject.ts +519 -0
- package/src/grok/status.ts +121 -0
- package/src/grok/sync.ts +66 -0
- package/src/images/artifacts.ts +516 -0
- package/src/images/fulfill-video.ts +163 -0
- package/src/images/fulfill.ts +149 -0
- package/src/images/index.ts +4 -0
- package/src/images/loop.ts +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 +1173 -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/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 +1045 -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 +305 -0
- package/src/server/live.ts +598 -0
- package/src/server/management/agent-settings-routes.ts +1039 -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/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 +516 -0
- package/src/server/management/model-routes.ts +525 -0
- package/src/server/management/oauth-account-routes.ts +561 -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 +88 -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 +211 -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 +1060 -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 +2756 -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 +392 -0
- package/src/update/job.ts +1513 -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
- package/vendor/ocx-notch/win32-x64/ocx-notch.exe +0 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-neutral config export core.
|
|
3
|
+
*
|
|
4
|
+
* One pure function per client, one shared input type. Every export surface (CLI,
|
|
5
|
+
* management API, GUI) consumes this module so the bytes a user copies, downloads, or
|
|
6
|
+
* curls can never drift between surfaces.
|
|
7
|
+
*
|
|
8
|
+
* Two invariants carried over from `ocx opencode` (src/cli/opencode.ts), which owned the
|
|
9
|
+
* OpenCode serializer before it moved here:
|
|
10
|
+
*
|
|
11
|
+
* - **No secret is ever serialized.** Configs carry only the client's documented env
|
|
12
|
+
* reference (`{env:VAR}` for OpenCode, `$VAR` for Pi); the real admission key travels
|
|
13
|
+
* through the environment. AGENTS.md treats token serialization as a release blocker.
|
|
14
|
+
* - **No metadata is guessed.** A model with no authoritative context window ships
|
|
15
|
+
* without context/output fields, and the client applies its own defaults. Pi's `cost`
|
|
16
|
+
* is omitted entirely rather than zero-filled, because zeros would assert "free",
|
|
17
|
+
* which is false for routed providers.
|
|
18
|
+
*
|
|
19
|
+
* This module never writes a file. `destination` names the canonical path for a human;
|
|
20
|
+
* targeting it is the caller's explicit act.
|
|
21
|
+
*/
|
|
22
|
+
import { homedir } from "node:os";
|
|
23
|
+
import { join } from "node:path";
|
|
24
|
+
import { shouldInjectApiAuthHeader } from "../codex/inject";
|
|
25
|
+
import { probeHostname } from "../server/proxy-liveness";
|
|
26
|
+
import type { OcxConfig } from "../types";
|
|
27
|
+
|
|
28
|
+
export interface OpencodeLaunchEnv {
|
|
29
|
+
[key: string]: string | undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Visible catalog entry keyed by the proxy's canonical namespaced selector. */
|
|
33
|
+
export interface OpencodeCatalogModel {
|
|
34
|
+
namespaced: string;
|
|
35
|
+
native?: boolean;
|
|
36
|
+
provider?: string;
|
|
37
|
+
id?: string;
|
|
38
|
+
contextWindow?: number;
|
|
39
|
+
displayName?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface OpencodeModelEntry {
|
|
43
|
+
name: string;
|
|
44
|
+
limit?: { context: number; output: number };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface OpencodeProviderBlock {
|
|
48
|
+
npm: string;
|
|
49
|
+
name: string;
|
|
50
|
+
options: {
|
|
51
|
+
baseURL: string;
|
|
52
|
+
apiKey?: string;
|
|
53
|
+
headers?: Record<string, string>;
|
|
54
|
+
};
|
|
55
|
+
models: Record<string, OpencodeModelEntry>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface OpencodeGeneratedConfig {
|
|
59
|
+
$schema: string;
|
|
60
|
+
provider: Record<string, OpencodeProviderBlock>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Provider key owned by this project; the only key any exporter ever emits. */
|
|
64
|
+
export const OPENCODE_PROVIDER_ID = "opencodex";
|
|
65
|
+
|
|
66
|
+
export const OPENCODE_CONFIG_SCHEMA = "https://opencode.ai/config.json";
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The proxy speaks the OpenAI-compatible shape at /v1, which opencode reaches through
|
|
70
|
+
* the AI SDK's openai-compatible package (the same wiring users hand-write today).
|
|
71
|
+
*/
|
|
72
|
+
const OPENCODE_PROVIDER_NPM = "@ai-sdk/openai-compatible";
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Env var carrying the proxy admission key to opencode. The config only ever holds the
|
|
76
|
+
* `{env:...}` reference, so the secret never lands on disk. opencode substitutes it at
|
|
77
|
+
* load time.
|
|
78
|
+
*/
|
|
79
|
+
export const OPENCODE_API_KEY_ENV = "OPENCODEX_OPENCODE_API_KEY";
|
|
80
|
+
|
|
81
|
+
/** Env reference shared by apiKey and the dedicated proxy admission header. */
|
|
82
|
+
export const OPENCODE_API_KEY_ENV_REF = `{env:${OPENCODE_API_KEY_ENV}}`;
|
|
83
|
+
|
|
84
|
+
/** Env var Pi interpolates. Pi takes bare `$NAME`, not opencode's `{env:NAME}`. */
|
|
85
|
+
export const PI_API_KEY_ENV = "OPENCODEX_API_KEY";
|
|
86
|
+
|
|
87
|
+
/** Pi's reference form for the admission key. Never the value. */
|
|
88
|
+
export const PI_API_KEY_ENV_REF = `$${PI_API_KEY_ENV}`;
|
|
89
|
+
|
|
90
|
+
/** Pi's wire-dialect selector for an OpenAI-compatible endpoint. */
|
|
91
|
+
const PI_API_DIALECT = "openai-completions";
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* opencode's config schema rejects a `limit` block that carries `context` without
|
|
95
|
+
* `output`, but CatalogModel has no authoritative per-model output field. Dropping
|
|
96
|
+
* `limit` entirely would also throw away the authoritative context window we DO have,
|
|
97
|
+
* so the block is emitted with this budget standing in for the missing half.
|
|
98
|
+
*
|
|
99
|
+
* The value matches REASONING_MAX_TOKENS_CEILING in src/adapters/anthropic.ts — the
|
|
100
|
+
* project's existing "safe ceiling across current models" figure. It is a ceiling for
|
|
101
|
+
* schema validity, NOT a claim about any specific model's true maximum, and it is
|
|
102
|
+
* clamped to the context window so a small-context model can never be emitted with
|
|
103
|
+
* output > context. Pi's `maxTokens` uses the same stand-in and the same clamp.
|
|
104
|
+
*/
|
|
105
|
+
export const SCHEMA_REQUIRED_OUTPUT_BUDGET = 32_000;
|
|
106
|
+
|
|
107
|
+
/** Deterministic loopback default for exported provider-block helpers in tests. */
|
|
108
|
+
export const OPENCODE_PROVIDER_BLOCK_DEFAULT_CONFIG: OcxConfig = {
|
|
109
|
+
port: 10100,
|
|
110
|
+
hostname: "127.0.0.1",
|
|
111
|
+
defaultProvider: "mock",
|
|
112
|
+
providers: { mock: { adapter: "openai-chat", baseUrl: "http://127.0.0.1/v1" } },
|
|
113
|
+
} as OcxConfig;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Resolve the user's global opencode config path. opencode uses the XDG layout on every
|
|
117
|
+
* platform (including Windows, where it is %USERPROFILE%\.config\opencode).
|
|
118
|
+
*/
|
|
119
|
+
export function opencodeGlobalConfigPath(
|
|
120
|
+
env: OpencodeLaunchEnv = process.env,
|
|
121
|
+
home: string = homedir(),
|
|
122
|
+
): string {
|
|
123
|
+
const xdg = env.XDG_CONFIG_HOME && env.XDG_CONFIG_HOME.length > 0 ? env.XDG_CONFIG_HOME : join(home, ".config");
|
|
124
|
+
return join(xdg, "opencode", "opencode.json");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Compose the OpenAI-compatible proxy base URL from a live probe result. */
|
|
128
|
+
export function opencodeProxyBaseUrl(port: number, hostname?: string): string {
|
|
129
|
+
return `http://${probeHostname(hostname)}:${port}/v1`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* One proxy-routed model destined for a client config. Deliberately narrower than
|
|
134
|
+
* `CatalogModel` so a serializer cannot reach for a field that does not survive the
|
|
135
|
+
* `/api/models` boundary.
|
|
136
|
+
*/
|
|
137
|
+
export interface ExportModel {
|
|
138
|
+
/** Canonical proxy selector: `provider/id`, or bare slug for native. */
|
|
139
|
+
namespaced: string;
|
|
140
|
+
provider: string;
|
|
141
|
+
id: string;
|
|
142
|
+
/** Native OpenAI entry. Read by the shared label rule. */
|
|
143
|
+
native?: boolean;
|
|
144
|
+
displayName?: string;
|
|
145
|
+
contextWindow?: number;
|
|
146
|
+
inputModalities?: string[];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface ExportContext {
|
|
150
|
+
/** `http://host:port/v1` — the OpenAI-compatible surface the client dials. */
|
|
151
|
+
baseUrl: string;
|
|
152
|
+
models: readonly ExportModel[];
|
|
153
|
+
/**
|
|
154
|
+
* Live proxy config. Only the OpenCode path reads it: a non-loopback bind moves
|
|
155
|
+
* admission from `apiKey` to the `x-opencodex-api-key` header.
|
|
156
|
+
*/
|
|
157
|
+
config?: OcxConfig;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export type ExportClientId = "opencode" | "pi";
|
|
161
|
+
|
|
162
|
+
export interface ExportClientSpec {
|
|
163
|
+
id: ExportClientId;
|
|
164
|
+
/** Download filename; matches the destination file's own name (003 §5). */
|
|
165
|
+
filename: string;
|
|
166
|
+
/** Canonical destination for humans. Never written to. */
|
|
167
|
+
destination: (env: NodeJS.ProcessEnv) => string;
|
|
168
|
+
/** Env var the config references; the value is never serialized. */
|
|
169
|
+
apiKeyEnv: string;
|
|
170
|
+
/** Shell line the user runs before launching the client. */
|
|
171
|
+
exportHint: string;
|
|
172
|
+
build: (ctx: ExportContext) => unknown;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Authoritative context window, or undefined. Never guesses: a missing, non-finite, or
|
|
177
|
+
* non-positive value means the serializer omits every context-derived field.
|
|
178
|
+
*/
|
|
179
|
+
function authoritativeContextWindow(contextWindow: number | undefined): number | undefined {
|
|
180
|
+
if (typeof contextWindow === "number" && Number.isFinite(contextWindow) && contextWindow > 0) {
|
|
181
|
+
return Math.floor(contextWindow);
|
|
182
|
+
}
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Schema-required output budget for a known context window. */
|
|
187
|
+
function outputBudgetFor(context: number): number {
|
|
188
|
+
return Math.min(SCHEMA_REQUIRED_OUTPUT_BUDGET, context);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Label shared by every client: `"<displayName|id> (<native|provider|routed>)"`. The
|
|
193
|
+
* provider suffix is what makes two same-named models from different upstreams
|
|
194
|
+
* distinguishable in a client's model picker.
|
|
195
|
+
*/
|
|
196
|
+
function exportModelLabel(model: OpencodeCatalogModel): string {
|
|
197
|
+
const providerLabel = model.native ? "native" : (model.provider ?? "routed");
|
|
198
|
+
const id = model.id ?? model.namespaced;
|
|
199
|
+
if (model.displayName && model.displayName.length > 0) {
|
|
200
|
+
return `${model.displayName} (${providerLabel})`;
|
|
201
|
+
}
|
|
202
|
+
return `${id} (${providerLabel})`;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function opencodeProviderOptions(baseURL: string, config: OcxConfig): OpencodeProviderBlock["options"] {
|
|
206
|
+
const options: OpencodeProviderBlock["options"] = { baseURL };
|
|
207
|
+
// Non-loopback binds accept proxy admission only via x-opencodex-api-key so Authorization
|
|
208
|
+
// stays free for Codex Direct upstream credentials when applicable.
|
|
209
|
+
if (shouldInjectApiAuthHeader(config)) {
|
|
210
|
+
options.headers = { "x-opencodex-api-key": OPENCODE_API_KEY_ENV_REF };
|
|
211
|
+
return options;
|
|
212
|
+
}
|
|
213
|
+
options.apiKey = OPENCODE_API_KEY_ENV_REF;
|
|
214
|
+
return options;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* `opencodex` provider block for a resolved base URL.
|
|
219
|
+
*
|
|
220
|
+
* `limit.context` is emitted ONLY from an authoritative context window — never guessed.
|
|
221
|
+
* When none is available the whole `limit` block is dropped and opencode keeps its own
|
|
222
|
+
* defaults; when one is present, `limit.output` rides along (opencode's schema requires
|
|
223
|
+
* the pair) clamped to the context window.
|
|
224
|
+
*/
|
|
225
|
+
function opencodeProviderBlock(
|
|
226
|
+
baseURL: string,
|
|
227
|
+
catalogModels: readonly OpencodeCatalogModel[],
|
|
228
|
+
config: OcxConfig,
|
|
229
|
+
): OpencodeProviderBlock {
|
|
230
|
+
const models: Record<string, OpencodeModelEntry> = {};
|
|
231
|
+
for (const model of catalogModels) {
|
|
232
|
+
const key = model.namespaced;
|
|
233
|
+
if (models[key]) continue; // first entry wins; native rows lead /api/models
|
|
234
|
+
const entry: OpencodeModelEntry = { name: exportModelLabel(model) };
|
|
235
|
+
const context = authoritativeContextWindow(model.contextWindow);
|
|
236
|
+
if (context !== undefined) {
|
|
237
|
+
entry.limit = { context, output: outputBudgetFor(context) };
|
|
238
|
+
}
|
|
239
|
+
models[key] = entry;
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
npm: OPENCODE_PROVIDER_NPM,
|
|
243
|
+
name: "OpenCodex",
|
|
244
|
+
options: opencodeProviderOptions(baseURL, config),
|
|
245
|
+
models,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Build the `opencodex` provider block from proxy catalog rows keyed by each row's
|
|
251
|
+
* canonical `namespaced` selector. Used by the `ocx opencode` launcher, which injects
|
|
252
|
+
* the block through OpenCode's inline runtime layer rather than any file.
|
|
253
|
+
*/
|
|
254
|
+
export function buildOpencodeProviderBlockFromCatalog(
|
|
255
|
+
port: number,
|
|
256
|
+
catalogModels: readonly OpencodeCatalogModel[],
|
|
257
|
+
hostname?: string,
|
|
258
|
+
config: OcxConfig = OPENCODE_PROVIDER_BLOCK_DEFAULT_CONFIG,
|
|
259
|
+
): OpencodeProviderBlock {
|
|
260
|
+
return opencodeProviderBlock(opencodeProxyBaseUrl(port, hostname), catalogModels, config);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Shared precondition for every serializer: drop duplicate `namespaced` (first wins,
|
|
265
|
+
* native rows lead `/api/models`) and sort by `namespaced` so two calls with the same
|
|
266
|
+
* models produce identical bytes. Stability matters because the GUI shows a diffable
|
|
267
|
+
* preview and agents may checksum the payload.
|
|
268
|
+
*/
|
|
269
|
+
export function normalizeExportModels(models: readonly ExportModel[]): ExportModel[] {
|
|
270
|
+
const seen = new Set<string>();
|
|
271
|
+
const unique: ExportModel[] = [];
|
|
272
|
+
for (const model of models) {
|
|
273
|
+
if (seen.has(model.namespaced)) continue;
|
|
274
|
+
seen.add(model.namespaced);
|
|
275
|
+
unique.push(model);
|
|
276
|
+
}
|
|
277
|
+
return unique.sort((a, b) => (a.namespaced < b.namespaced ? -1 : a.namespaced > b.namespaced ? 1 : 0));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** OpenCode V1 document: our provider block plus `$schema`, and nothing else. */
|
|
281
|
+
function buildOpencodeClientConfig(ctx: ExportContext): OpencodeGeneratedConfig {
|
|
282
|
+
const block = opencodeProviderBlock(
|
|
283
|
+
ctx.baseUrl,
|
|
284
|
+
normalizeExportModels(ctx.models),
|
|
285
|
+
ctx.config ?? OPENCODE_PROVIDER_BLOCK_DEFAULT_CONFIG,
|
|
286
|
+
);
|
|
287
|
+
return { $schema: OPENCODE_CONFIG_SCHEMA, provider: { [OPENCODE_PROVIDER_ID]: block } };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export interface PiModelEntry {
|
|
291
|
+
id: string;
|
|
292
|
+
name: string;
|
|
293
|
+
input: string[];
|
|
294
|
+
contextWindow?: number;
|
|
295
|
+
maxTokens?: number;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export interface PiProviderBlock {
|
|
299
|
+
baseUrl: string;
|
|
300
|
+
api: string;
|
|
301
|
+
apiKey: string;
|
|
302
|
+
models: PiModelEntry[];
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export interface PiGeneratedConfig {
|
|
306
|
+
providers: Record<string, PiProviderBlock>;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Pi's `~/.pi/agent/models.json` shape. `models` is an ARRAY (identity lives in `id`),
|
|
311
|
+
* unlike OpenCode's keyed object.
|
|
312
|
+
*
|
|
313
|
+
* Two fields are deliberately absent. `cost` requires all four price fields and we have
|
|
314
|
+
* no price data at all, so emitting zeros would assert every routed model is free.
|
|
315
|
+
* `reasoning` is a boolean in Pi while our catalog carries an effort list — mapping one
|
|
316
|
+
* to the other would be a guess.
|
|
317
|
+
*
|
|
318
|
+
* Pi's schema is UNVERIFIED against a real installation (001 §2); this contract is ours,
|
|
319
|
+
* not a claim about Pi's acceptance.
|
|
320
|
+
*/
|
|
321
|
+
function buildPiClientConfig(ctx: ExportContext): PiGeneratedConfig {
|
|
322
|
+
const models: PiModelEntry[] = normalizeExportModels(ctx.models).map(model => {
|
|
323
|
+
const entry: PiModelEntry = {
|
|
324
|
+
id: model.namespaced,
|
|
325
|
+
name: exportModelLabel(model),
|
|
326
|
+
// Text is the one modality every routed model supports; anything richer must come
|
|
327
|
+
// from the catalog rather than an assumption.
|
|
328
|
+
input: model.inputModalities && model.inputModalities.length > 0 ? [...model.inputModalities] : ["text"],
|
|
329
|
+
};
|
|
330
|
+
const context = authoritativeContextWindow(model.contextWindow);
|
|
331
|
+
if (context !== undefined) {
|
|
332
|
+
entry.contextWindow = context;
|
|
333
|
+
entry.maxTokens = outputBudgetFor(context);
|
|
334
|
+
}
|
|
335
|
+
return entry;
|
|
336
|
+
});
|
|
337
|
+
return {
|
|
338
|
+
providers: {
|
|
339
|
+
[OPENCODE_PROVIDER_ID]: {
|
|
340
|
+
baseUrl: ctx.baseUrl,
|
|
341
|
+
api: PI_API_DIALECT,
|
|
342
|
+
apiKey: PI_API_KEY_ENV_REF,
|
|
343
|
+
models,
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export const EXPORT_CLIENTS: Record<ExportClientId, ExportClientSpec> = {
|
|
350
|
+
opencode: {
|
|
351
|
+
id: "opencode",
|
|
352
|
+
filename: "opencode.json",
|
|
353
|
+
destination: env => opencodeGlobalConfigPath(env),
|
|
354
|
+
apiKeyEnv: OPENCODE_API_KEY_ENV,
|
|
355
|
+
exportHint: `export ${OPENCODE_API_KEY_ENV}=<your key>`,
|
|
356
|
+
build: buildOpencodeClientConfig,
|
|
357
|
+
},
|
|
358
|
+
pi: {
|
|
359
|
+
id: "pi",
|
|
360
|
+
filename: "pi-models.json",
|
|
361
|
+
destination: () => join(homedir(), ".pi", "agent", "models.json"),
|
|
362
|
+
apiKeyEnv: PI_API_KEY_ENV,
|
|
363
|
+
exportHint: `export ${PI_API_KEY_ENV}=<your key>`,
|
|
364
|
+
build: buildPiClientConfig,
|
|
365
|
+
},
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
export const EXPORT_CLIENT_IDS: readonly ExportClientId[] = Object.keys(EXPORT_CLIENTS) as ExportClientId[];
|
|
369
|
+
|
|
370
|
+
export function isExportClientId(value: string): value is ExportClientId {
|
|
371
|
+
return Object.prototype.hasOwnProperty.call(EXPORT_CLIENTS, value);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/** Single entry point every export surface calls. */
|
|
375
|
+
export function buildClientConfig(client: ExportClientId, ctx: ExportContext): unknown {
|
|
376
|
+
return EXPORT_CLIENTS[client].build(ctx);
|
|
377
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Canonical persisted Codex pool-account id format. */
|
|
2
|
+
export const CODEX_ACCOUNT_ID_RE = /^[A-Za-z0-9._-]{1,64}$/;
|
|
3
|
+
|
|
4
|
+
// Account credentials are persisted in JSON object maps. These names have
|
|
5
|
+
// special meaning on ordinary JavaScript objects and must never be admitted as
|
|
6
|
+
// user-controlled keys.
|
|
7
|
+
const RESERVED_CODEX_ACCOUNT_IDS = new Set(["__proto__", "prototype", "constructor"]);
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Stable internal id under which the Codex Desktop login participates in account rotation.
|
|
11
|
+
* It is reserved and must never be used as a public namespace target or pool-account id.
|
|
12
|
+
*/
|
|
13
|
+
export const MAIN_CODEX_ACCOUNT_ID = "__main__";
|
|
14
|
+
|
|
15
|
+
export function isValidCodexAccountId(accountId: unknown): accountId is string {
|
|
16
|
+
return typeof accountId === "string"
|
|
17
|
+
&& accountId !== MAIN_CODEX_ACCOUNT_ID
|
|
18
|
+
&& CODEX_ACCOUNT_ID_RE.test(accountId)
|
|
19
|
+
&& !RESERVED_CODEX_ACCOUNT_IDS.has(accountId.toLowerCase());
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type CodexAccountIdentityRow = { id: string; isMain: boolean };
|
|
23
|
+
|
|
24
|
+
/** Legacy invalid rows remain loadable for cleanup, but never participate in routing. */
|
|
25
|
+
export function isSelectableCodexPoolAccount(account: CodexAccountIdentityRow): boolean {
|
|
26
|
+
return !account.isMain && isValidCodexAccountId(account.id);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** A pre-validation pool row that collides with the internal Desktop-account sentinel. */
|
|
30
|
+
export function hasLegacyMainCodexPoolAccount(
|
|
31
|
+
accounts: readonly CodexAccountIdentityRow[] | undefined,
|
|
32
|
+
): boolean {
|
|
33
|
+
return accounts?.some(account => !account.isMain && account.id === MAIN_CODEX_ACCOUNT_ID) ?? false;
|
|
34
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
import type { CodexAccount } from "../types";
|
|
3
|
+
|
|
4
|
+
export const CODEX_ACCOUNT_LOG_LABEL_RE = /^p[a-f0-9]{6}$/;
|
|
5
|
+
|
|
6
|
+
export function createCodexAccountLogLabel(existingLabels: Iterable<string | undefined | null> = []): string {
|
|
7
|
+
const used = new Set([...existingLabels].filter((value): value is string => !!value));
|
|
8
|
+
for (let i = 0; i < 16; i++) {
|
|
9
|
+
const label = `p${randomBytes(3).toString("hex")}`;
|
|
10
|
+
if (!used.has(label)) return label;
|
|
11
|
+
}
|
|
12
|
+
return `p${randomBytes(6).toString("hex").slice(0, 6)}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function fallbackCodexAccountLogLabel(accountId: string): string {
|
|
16
|
+
return `p${createHash("sha256").update(accountId).digest("hex").slice(0, 6)}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function codexAccountLogLabel(account: CodexAccount): string {
|
|
20
|
+
return CODEX_ACCOUNT_LOG_LABEL_RE.test(account.logLabel ?? "")
|
|
21
|
+
? account.logLabel!
|
|
22
|
+
: fallbackCodexAccountLogLabel(account.id);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function withCodexAccountLogLabel(
|
|
26
|
+
account: Omit<CodexAccount, "logLabel"> & Partial<Pick<CodexAccount, "logLabel">>,
|
|
27
|
+
existingAccounts: readonly CodexAccount[],
|
|
28
|
+
): CodexAccount {
|
|
29
|
+
if (account.logLabel && CODEX_ACCOUNT_LOG_LABEL_RE.test(account.logLabel)) return account as CodexAccount;
|
|
30
|
+
return {
|
|
31
|
+
...account,
|
|
32
|
+
logLabel: createCodexAccountLogLabel(existingAccounts.map(existing => existing.logLabel)),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { removeCodexAccountCredential } from "./account-store";
|
|
2
|
+
import { clearAccountNeedsReauth } from "./account-runtime-state";
|
|
3
|
+
import { getMainChatgptAccountId } from "./auth-collision";
|
|
4
|
+
import { MAIN_CODEX_ACCOUNT_ID, setMainAccountPlan } from "./main-account";
|
|
5
|
+
import { clearAccountQuota } from "./quota";
|
|
6
|
+
import { clearCodexUpstreamHealthForAccount, clearThreadAccountMapForAccount } from "./routing";
|
|
7
|
+
import { invalidateCodexWebSocketsForAccount } from "./websocket-registry";
|
|
8
|
+
import { clearMainAccountInfoCache } from "./main-account-cache";
|
|
9
|
+
import { forgetCodexAccountPause } from "./account-pause";
|
|
10
|
+
import type { OcxConfig } from "../types";
|
|
11
|
+
|
|
12
|
+
let observedMainChatgptAccountId: string | undefined;
|
|
13
|
+
|
|
14
|
+
export function purgeCodexAccountRuntimeState(accountId: string): void {
|
|
15
|
+
clearAccountNeedsReauth(accountId);
|
|
16
|
+
clearAccountQuota(accountId);
|
|
17
|
+
clearThreadAccountMapForAccount(accountId);
|
|
18
|
+
clearCodexUpstreamHealthForAccount(accountId);
|
|
19
|
+
if (accountId === MAIN_CODEX_ACCOUNT_ID) clearMainAccountInfoCache();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The main Codex login is stored under the stable `__main__` alias, while
|
|
24
|
+
* `~/.codex/auth.json` can be replaced with credentials for another physical
|
|
25
|
+
* ChatGPT account. Drop alias-keyed runtime state when that identity changes so
|
|
26
|
+
* cooldown, quota, reauth, and thread affinity do not leak across accounts.
|
|
27
|
+
*/
|
|
28
|
+
export function reconcileMainCodexAccountRuntimeState(): boolean {
|
|
29
|
+
const currentAccountId = getMainChatgptAccountId();
|
|
30
|
+
// A missing/malformed auth.json is an unknown identity, not a confirmed account switch. Keep the
|
|
31
|
+
// prior observation and its safety state until a real account id can be read again.
|
|
32
|
+
if (currentAccountId === null) return false;
|
|
33
|
+
const previousAccountId = observedMainChatgptAccountId;
|
|
34
|
+
observedMainChatgptAccountId = currentAccountId;
|
|
35
|
+
if (previousAccountId === undefined || previousAccountId === currentAccountId) return false;
|
|
36
|
+
|
|
37
|
+
purgeCodexAccountRuntimeState(MAIN_CODEX_ACCOUNT_ID);
|
|
38
|
+
setMainAccountPlan(null);
|
|
39
|
+
invalidateCodexWebSocketsForAccount(MAIN_CODEX_ACCOUNT_ID);
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function resetMainCodexAccountIdentityTrackingForTests(): void {
|
|
44
|
+
observedMainChatgptAccountId = undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function deleteCodexAccount(runtimeConfig: OcxConfig, accountId: string): void {
|
|
48
|
+
removeCodexAccountCredential(accountId);
|
|
49
|
+
runtimeConfig.codexAccounts = (runtimeConfig.codexAccounts ?? [])
|
|
50
|
+
.filter(account => account.isMain || account.id !== accountId);
|
|
51
|
+
forgetCodexAccountPause(runtimeConfig, accountId);
|
|
52
|
+
if (runtimeConfig.activeCodexAccountId === accountId) runtimeConfig.activeCodexAccountId = undefined;
|
|
53
|
+
purgeCodexAccountRuntimeState(accountId);
|
|
54
|
+
invalidateCodexWebSocketsForAccount(accountId);
|
|
55
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { isValidCodexAccountId } from "./account-id";
|
|
2
|
+
|
|
3
|
+
/** Config-only sentinel for the Codex Desktop account; outside the pool-account id grammar. */
|
|
4
|
+
export const MAIN_CODEX_ACCOUNT_NAMESPACE_TARGET = "@main";
|
|
5
|
+
|
|
6
|
+
export const CODEX_ACCOUNT_NAMESPACE_COMBO_ALIAS_COLLISION_ERROR =
|
|
7
|
+
"combo alias must not use a configured Codex account namespace";
|
|
8
|
+
|
|
9
|
+
const CODEX_ACCOUNT_ID_NAMESPACE_COLLISION_ERROR =
|
|
10
|
+
"account id must not collide with a configured Codex account namespace";
|
|
11
|
+
|
|
12
|
+
/** Provider ids are compared case-insensitively at namespace admission boundaries. */
|
|
13
|
+
export function codexProviderNamespaceKey(value: string): string {
|
|
14
|
+
return value.toLowerCase();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function hasCodexAccountNamespace(
|
|
18
|
+
namespaces: unknown,
|
|
19
|
+
namespace: string,
|
|
20
|
+
): boolean {
|
|
21
|
+
return !!namespaces
|
|
22
|
+
&& typeof namespaces === "object"
|
|
23
|
+
&& !Array.isArray(namespaces)
|
|
24
|
+
&& Object.hasOwn(namespaces, namespace);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function codexAccountNamespaceProviderCollisionError(
|
|
28
|
+
namespaces: unknown,
|
|
29
|
+
providerName: string,
|
|
30
|
+
): string | undefined {
|
|
31
|
+
const normalizedProvider = codexProviderNamespaceKey(providerName);
|
|
32
|
+
const collides = !!namespaces
|
|
33
|
+
&& typeof namespaces === "object"
|
|
34
|
+
&& !Array.isArray(namespaces)
|
|
35
|
+
&& Object.keys(namespaces)
|
|
36
|
+
.some(namespace => codexProviderNamespaceKey(namespace) === normalizedProvider);
|
|
37
|
+
return collides
|
|
38
|
+
? "provider name must not collide with a configured Codex account namespace"
|
|
39
|
+
: undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function codexAccountIdNamespaceCollisionError(
|
|
43
|
+
namespaces: unknown,
|
|
44
|
+
accountId: string,
|
|
45
|
+
): string | undefined {
|
|
46
|
+
return hasCodexAccountNamespace(namespaces, accountId)
|
|
47
|
+
? CODEX_ACCOUNT_ID_NAMESPACE_COLLISION_ERROR
|
|
48
|
+
: undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function codexAccountNamespaceForModel(
|
|
52
|
+
namespaces: unknown,
|
|
53
|
+
modelId: string,
|
|
54
|
+
): string | undefined {
|
|
55
|
+
const slash = modelId.indexOf("/");
|
|
56
|
+
if (slash <= 0) return undefined;
|
|
57
|
+
const namespace = modelId.slice(0, slash);
|
|
58
|
+
return hasCodexAccountNamespace(namespaces, namespace) ? namespace : undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function isValidCodexAccountNamespaceTarget(accountId: unknown): accountId is string {
|
|
62
|
+
return accountId === MAIN_CODEX_ACCOUNT_NAMESPACE_TARGET || isValidCodexAccountId(accountId);
|
|
63
|
+
}
|