@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,509 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code inbound: Anthropic Messages API request -> internal /v1/responses body.
|
|
3
|
+
*
|
|
4
|
+
* Design (devlog/260711_claude_inbound/010, 003_evidence.md):
|
|
5
|
+
* - translate-and-replay: the produced body MUST pass the real responsesRequestSchema
|
|
6
|
+
* parse so routing/OAuth/pool/failover are inherited unchanged.
|
|
7
|
+
* - thinking/redacted_thinking blocks on replay are DROPPED (v1 policy) — routed
|
|
8
|
+
* providers carry reasoning in Responses items/ocxr1 envelopes instead.
|
|
9
|
+
* - thinking.budget_tokens is NEVER forwarded raw; it maps to an effort tier.
|
|
10
|
+
* - top_k is accepted and silently dropped (no Responses equivalent, CCR parity).
|
|
11
|
+
*/
|
|
12
|
+
import type { OcxClaudeCodeConfig } from "../types";
|
|
13
|
+
import { resolveAlias } from "./alias";
|
|
14
|
+
import { stripOneMillionMarker } from "./context-windows";
|
|
15
|
+
import { resolveDesktop3pAlias } from "./desktop-3p";
|
|
16
|
+
import { createHash } from "node:crypto";
|
|
17
|
+
|
|
18
|
+
export class AnthropicRequestError extends Error {}
|
|
19
|
+
|
|
20
|
+
type Rec = Record<string, unknown>;
|
|
21
|
+
|
|
22
|
+
function isRec(v: unknown): v is Rec {
|
|
23
|
+
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Alias first, then modelMap: exact id, then date-suffix-stripped (`-\d{8}$`), else passthrough. */
|
|
27
|
+
export function resolveInboundModel(model: string, cc?: OcxClaudeCodeConfig): string {
|
|
28
|
+
// Defensive: Desktop/CLI strip the [1m] context-variant marker client-side, but a
|
|
29
|
+
// leaking build must not break alias decode (devlog 138 — the 1M signal is the
|
|
30
|
+
// anthropic-beta header, never the id). Case-insensitive: the CLI matches /\[1m\]/i.
|
|
31
|
+
model = stripOneMillionMarker(model);
|
|
32
|
+
const aliased = resolveAlias(model);
|
|
33
|
+
if (aliased) return aliased;
|
|
34
|
+
// Desktop 3P aliases: claude-opus-4-{code} → provider/model route key
|
|
35
|
+
const desktop3p = resolveDesktop3pAlias(model);
|
|
36
|
+
if (desktop3p) {
|
|
37
|
+
// Native pseudo-provider returns bare slug; routed returns provider/model
|
|
38
|
+
const sep = desktop3p.indexOf("/");
|
|
39
|
+
if (sep > 0 && desktop3p.slice(0, sep) === "native") return desktop3p.slice(sep + 1);
|
|
40
|
+
return desktop3p;
|
|
41
|
+
}
|
|
42
|
+
const map = cc?.modelMap ?? {};
|
|
43
|
+
const exact = map[model];
|
|
44
|
+
if (typeof exact === "string" && exact.length > 0) return exact;
|
|
45
|
+
const stripped = model.replace(/-\d{8}$/, "");
|
|
46
|
+
const dateless = map[stripped];
|
|
47
|
+
if (typeof dateless === "string" && dateless.length > 0) return dateless;
|
|
48
|
+
return model;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** budget_tokens ladder -> Responses reasoning effort (003: real API min is 1024; never forward raw). */
|
|
52
|
+
export function effortForThinkingBudget(budget: number): string {
|
|
53
|
+
if (budget <= 4096) return "low";
|
|
54
|
+
if (budget <= 16384) return "medium";
|
|
55
|
+
return "high";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Adaptive-thinking wire (devlog 080): Claude Code /effort sends
|
|
60
|
+
* `thinking:{type:"adaptive"}` + `output_config:{effort:"..."}` (verified by local
|
|
61
|
+
* capture of claude 2.1.207 and CLIProxyAPI#1540). Forward the level verbatim when it
|
|
62
|
+
* is a known Responses effort; unknown strings are dropped so downstream defaults win.
|
|
63
|
+
*/
|
|
64
|
+
const OUTPUT_CONFIG_EFFORTS = new Set(["minimal", "low", "medium", "high", "xhigh", "max", "ultra"]);
|
|
65
|
+
export function effortFromOutputConfig(outputConfig: unknown): string | undefined {
|
|
66
|
+
if (!isRec(outputConfig)) return undefined;
|
|
67
|
+
const effort = outputConfig.effort;
|
|
68
|
+
return typeof effort === "string" && OUTPUT_CONFIG_EFFORTS.has(effort) ? effort : undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function systemToInstructions(system: unknown): string | undefined {
|
|
72
|
+
if (typeof system === "string") return system.length > 0 ? system : undefined;
|
|
73
|
+
if (Array.isArray(system)) {
|
|
74
|
+
const parts: string[] = [];
|
|
75
|
+
for (const block of system) {
|
|
76
|
+
if (isRec(block) && block.type === "text" && typeof block.text === "string") parts.push(block.text);
|
|
77
|
+
}
|
|
78
|
+
return parts.length > 0 ? parts.join("\n\n") : undefined;
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function imageBlockToInputImage(block: Rec): Rec | null {
|
|
84
|
+
const source = block.source;
|
|
85
|
+
if (!isRec(source)) return null;
|
|
86
|
+
if (source.type === "base64" && typeof source.data === "string") {
|
|
87
|
+
const media = typeof source.media_type === "string" ? source.media_type : "image/png";
|
|
88
|
+
return { type: "input_image", image_url: `data:${media};base64,${source.data}` };
|
|
89
|
+
}
|
|
90
|
+
if (source.type === "url" && typeof source.url === "string") {
|
|
91
|
+
return { type: "input_image", image_url: source.url };
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function toolResultOutput(block: Rec): string | Rec[] {
|
|
97
|
+
const isError = block.is_error === true;
|
|
98
|
+
const content = block.content;
|
|
99
|
+
if (typeof content === "string") return isError ? `[tool error] ${content}` : content;
|
|
100
|
+
if (Array.isArray(content)) {
|
|
101
|
+
const out: Rec[] = [];
|
|
102
|
+
for (const item of content) {
|
|
103
|
+
if (!isRec(item)) continue;
|
|
104
|
+
if (item.type === "text" && typeof item.text === "string") {
|
|
105
|
+
out.push({ type: "input_text", text: item.text });
|
|
106
|
+
} else if (item.type === "image") {
|
|
107
|
+
const img = imageBlockToInputImage(item);
|
|
108
|
+
if (img) out.push(img);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (isError) out.unshift({ type: "input_text", text: "[tool error]" });
|
|
112
|
+
if (out.length === 0) return isError ? "[tool error]" : "";
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
return isError ? "[tool error]" : "";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function pushUserMessage(input: Rec[], blocks: Rec[]): void {
|
|
119
|
+
if (blocks.length === 0) return;
|
|
120
|
+
input.push({ type: "message", role: "user", content: blocks });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Bundled-skill elision for routed models (devlog 060). Claude Code loads a skill
|
|
125
|
+
* by calling the `Skill` tool; the ~136k-token document bundle then rides the
|
|
126
|
+
* paired tool_result on EVERY subsequent turn. Third-party models are not trained
|
|
127
|
+
* on these Anthropic bundles, so for blocked skills we substitute the result body
|
|
128
|
+
* with a short stub — the function_call_output item itself stays (pairing intact).
|
|
129
|
+
* Native Anthropic passthrough never reaches this translation.
|
|
130
|
+
*/
|
|
131
|
+
export const DEFAULT_BLOCKED_SKILLS = ["claude-api"];
|
|
132
|
+
|
|
133
|
+
/** Shared effective policy for proxy elision and generated routed-agent guards. */
|
|
134
|
+
export function effectiveBlockedSkillNames(cc?: Pick<OcxClaudeCodeConfig, "blockedSkills">): string[] {
|
|
135
|
+
const names = cc?.blockedSkills ?? DEFAULT_BLOCKED_SKILLS;
|
|
136
|
+
return [...new Set(names
|
|
137
|
+
.filter((name): name is string => typeof name === "string")
|
|
138
|
+
.map(name => name.trim().toLowerCase())
|
|
139
|
+
.filter(name => name.length > 0))];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* ocx-route directive (devlog 072): injected agent-definition bodies carry
|
|
144
|
+
* `<!-- ocx-route: <model> -->` because Claude Code 2.1.207 ignores custom
|
|
145
|
+
* gateway ids in agent frontmatter (live-proven fallback to sonnet). The body
|
|
146
|
+
* rides the subagent's system prompt, so the proxy re-routes here. Only the
|
|
147
|
+
* FIRST directive wins; the scan is bounded to the system field.
|
|
148
|
+
*/
|
|
149
|
+
const OCX_ROUTE_RE = /<!--\s*ocx-route:\s*([^\s]+)\s*-->/;
|
|
150
|
+
const OCX_EFFORT_RE = /<!--\s*ocx-effort:\s*(low|medium|high|xhigh|max)\s*-->/;
|
|
151
|
+
|
|
152
|
+
function systemText(body: unknown): string | null {
|
|
153
|
+
if (!isRec(body)) return null;
|
|
154
|
+
const system = body.system;
|
|
155
|
+
if (typeof system === "string") return system || null;
|
|
156
|
+
if (!Array.isArray(system)) return null;
|
|
157
|
+
const text = system
|
|
158
|
+
.filter((b): b is Rec => isRec(b) && b.type === "text" && typeof b.text === "string")
|
|
159
|
+
.map(b => b.text as string)
|
|
160
|
+
.join("\n");
|
|
161
|
+
return text || null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function extractOcxRouteDirective(body: unknown): string | null {
|
|
165
|
+
const text = systemText(body);
|
|
166
|
+
if (!text) return null;
|
|
167
|
+
const match = OCX_ROUTE_RE.exec(text);
|
|
168
|
+
return match ? match[1]! : null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Claude Code 2.1.220 collapses custom-agent frontmatter `effort: max` and
|
|
173
|
+
* `effort: xhigh` into the legacy `thinking.budget_tokens` shape. Preserve the
|
|
174
|
+
* exact generated-agent setting through the same trusted system-body channel as
|
|
175
|
+
* ocx-route so the inbound translator can restore `output_config.effort`.
|
|
176
|
+
*/
|
|
177
|
+
export function extractOcxEffortDirective(body: unknown): NonNullable<OcxClaudeCodeConfig["subagentEffort"]> | null {
|
|
178
|
+
const text = systemText(body);
|
|
179
|
+
if (!text) return null;
|
|
180
|
+
const match = OCX_EFFORT_RE.exec(text);
|
|
181
|
+
return match ? match[1] as NonNullable<OcxClaudeCodeConfig["subagentEffort"]> : null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Injected-skill payloads below this size are never stubbed (not worth it). */
|
|
185
|
+
const SKILL_ELISION_MIN_CHARS = 10_000;
|
|
186
|
+
const SKILL_TEXT_MARKER = "Base directory for this skill: ";
|
|
187
|
+
|
|
188
|
+
interface SkillElisionContext {
|
|
189
|
+
/** Skill-tool call ids whose input names a blocked skill (result-body carrier). */
|
|
190
|
+
callIds: ReadonlySet<string>;
|
|
191
|
+
/** Lowercased blocked skill names (text-block carrier). */
|
|
192
|
+
names: readonly string[];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const NO_ELISION: SkillElisionContext = { callIds: new Set(), names: [] };
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Claude Code 2.1.207 (live capture, devlog 060 follow-up): the Skill tool_result is
|
|
199
|
+
* a tiny "Launching skill: <name>" note; the actual ~570k-char document bundle rides
|
|
200
|
+
* as a SEPARATE text block in the same user message, whose first line is
|
|
201
|
+
* `Base directory for this skill: <dir>/<skill-name>`. Stub that block when the
|
|
202
|
+
* directory basename matches a blocked skill.
|
|
203
|
+
*/
|
|
204
|
+
function maybeElideSkillText(text: string, names: readonly string[]): string {
|
|
205
|
+
if (names.length === 0 || text.length < SKILL_ELISION_MIN_CHARS) return text;
|
|
206
|
+
if (!text.startsWith(SKILL_TEXT_MARKER)) return text;
|
|
207
|
+
const firstLineEnd = text.indexOf("\n");
|
|
208
|
+
const dir = text.slice(SKILL_TEXT_MARKER.length, firstLineEnd === -1 ? text.length : firstLineEnd).trim();
|
|
209
|
+
// Windows clients send `C:\Users\...\claude-api`; normalize separators before
|
|
210
|
+
// basenaming (repo precedent: src/codex/inject.ts isOpencodexCatalogPath).
|
|
211
|
+
const base = dir.replace(/\\/g, "/").split("/").filter(Boolean).pop()?.toLowerCase() ?? "";
|
|
212
|
+
if (!names.includes(base)) return text;
|
|
213
|
+
return `[opencodex] '${base}' skill document bundle (${text.length} chars) elided for routed models `
|
|
214
|
+
+ "(claudeCode.blockedSkills). The skill is loaded; answer from general knowledge instead of citing the bundle.";
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function skillElisionStub(callId: string): string {
|
|
218
|
+
return "[opencodex] Skill document bundle elided for routed models (claudeCode.blockedSkills). "
|
|
219
|
+
+ `The skill loaded, but its reference documents were removed to save context (call ${callId}). `
|
|
220
|
+
+ "Answer from general knowledge instead of citing the bundle.";
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Collect Skill-tool call ids whose input names a blocked skill. */
|
|
224
|
+
function blockedSkillCallIds(messages: readonly unknown[], blocked: readonly string[]): Set<string> {
|
|
225
|
+
const ids = new Set<string>();
|
|
226
|
+
if (blocked.length === 0) return ids;
|
|
227
|
+
const needles = blocked.map(name => name.toLowerCase()).filter(name => name.length > 0);
|
|
228
|
+
if (needles.length === 0) return ids;
|
|
229
|
+
for (const msg of messages) {
|
|
230
|
+
if (!isRec(msg) || msg.role !== "assistant" || !Array.isArray(msg.content)) continue;
|
|
231
|
+
for (const block of msg.content) {
|
|
232
|
+
if (!isRec(block) || block.type !== "tool_use" || block.name !== "Skill") continue;
|
|
233
|
+
if (typeof block.id !== "string" || block.id.length === 0) continue;
|
|
234
|
+
const inputJson = JSON.stringify(block.input ?? {}).toLowerCase();
|
|
235
|
+
if (needles.some(name => inputJson.includes(name))) ids.add(block.id);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return ids;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Claude Code (observed 2026-07-11, real CLI smoke) sends `role:"system"` entries in
|
|
243
|
+
* `messages` despite the published API having no system role. Map them to Responses
|
|
244
|
+
* instructions text: the native ChatGPT backend rejects system message items in
|
|
245
|
+
* `input` ("System messages are not allowed", verified live), so folding into
|
|
246
|
+
* `instructions` is the only shape that works on every route.
|
|
247
|
+
*/
|
|
248
|
+
function systemMessageText(content: unknown): string {
|
|
249
|
+
if (typeof content === "string") return content;
|
|
250
|
+
if (!Array.isArray(content)) return "";
|
|
251
|
+
const parts: string[] = [];
|
|
252
|
+
for (const raw of content) {
|
|
253
|
+
if (isRec(raw) && raw.type === "text" && typeof raw.text === "string") parts.push(raw.text);
|
|
254
|
+
}
|
|
255
|
+
return parts.join("\n\n");
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function userMessageToItems(content: unknown, input: Rec[], elide: SkillElisionContext = NO_ELISION): void {
|
|
259
|
+
if (typeof content === "string") {
|
|
260
|
+
if (content.length > 0) pushUserMessage(input, [{ type: "input_text", text: content }]);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (!Array.isArray(content)) return;
|
|
264
|
+
// Preserve block order: tool_result blocks become standalone function_call_output
|
|
265
|
+
// items; contiguous text/image runs become one user message.
|
|
266
|
+
let pending: Rec[] = [];
|
|
267
|
+
for (const raw of content) {
|
|
268
|
+
if (!isRec(raw)) continue;
|
|
269
|
+
switch (raw.type) {
|
|
270
|
+
case "text":
|
|
271
|
+
if (typeof raw.text === "string") pending.push({ type: "input_text", text: maybeElideSkillText(raw.text, elide.names) });
|
|
272
|
+
break;
|
|
273
|
+
case "image": {
|
|
274
|
+
const img = imageBlockToInputImage(raw);
|
|
275
|
+
if (img) pending.push(img);
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
case "tool_result": {
|
|
279
|
+
pushUserMessage(input, pending);
|
|
280
|
+
pending = [];
|
|
281
|
+
if (typeof raw.tool_use_id !== "string" || raw.tool_use_id.length === 0) {
|
|
282
|
+
throw new AnthropicRequestError("tool_result requires tool_use_id");
|
|
283
|
+
}
|
|
284
|
+
input.push({
|
|
285
|
+
type: "function_call_output",
|
|
286
|
+
call_id: raw.tool_use_id,
|
|
287
|
+
// Blocked-skill bundles are stubbed out for routed models (devlog 060).
|
|
288
|
+
output: elide.callIds.has(raw.tool_use_id) ? skillElisionStub(raw.tool_use_id) : toolResultOutput(raw),
|
|
289
|
+
});
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
case "document":
|
|
293
|
+
// No Responses equivalent for raw document blocks; surface the title so the
|
|
294
|
+
// model at least sees the attachment happened.
|
|
295
|
+
pending.push({ type: "input_text", text: `[document${typeof raw.title === "string" ? `: ${raw.title}` : ""}]` });
|
|
296
|
+
break;
|
|
297
|
+
default:
|
|
298
|
+
break; // thinking/redacted_thinking never appear in user messages; ignore unknowns
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
pushUserMessage(input, pending);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function assistantMessageToItems(content: unknown, input: Rec[]): void {
|
|
305
|
+
if (typeof content === "string") {
|
|
306
|
+
if (content.length > 0) input.push({ type: "message", role: "assistant", content: [{ type: "output_text", text: content }] });
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (!Array.isArray(content)) return;
|
|
310
|
+
let pendingText: Rec[] = [];
|
|
311
|
+
const flush = () => {
|
|
312
|
+
if (pendingText.length > 0) input.push({ type: "message", role: "assistant", content: pendingText });
|
|
313
|
+
pendingText = [];
|
|
314
|
+
};
|
|
315
|
+
for (const raw of content) {
|
|
316
|
+
if (!isRec(raw)) continue;
|
|
317
|
+
switch (raw.type) {
|
|
318
|
+
case "text":
|
|
319
|
+
if (typeof raw.text === "string") pendingText.push({ type: "output_text", text: raw.text });
|
|
320
|
+
break;
|
|
321
|
+
case "tool_use": {
|
|
322
|
+
flush();
|
|
323
|
+
if (typeof raw.id !== "string" || raw.id.length === 0 || typeof raw.name !== "string" || raw.name.length === 0) {
|
|
324
|
+
throw new AnthropicRequestError("tool_use requires id and name");
|
|
325
|
+
}
|
|
326
|
+
input.push({ type: "function_call", call_id: raw.id, name: raw.name, arguments: JSON.stringify(raw.input ?? {}) });
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
case "thinking":
|
|
330
|
+
case "redacted_thinking":
|
|
331
|
+
break; // v1 policy: dropped on replay (003 evidence — safe for routed providers)
|
|
332
|
+
default:
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
flush();
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function toolsToResponses(tools: unknown): Rec[] | undefined {
|
|
340
|
+
if (!Array.isArray(tools) || tools.length === 0) return undefined;
|
|
341
|
+
const out: Rec[] = [];
|
|
342
|
+
for (const raw of tools) {
|
|
343
|
+
if (!isRec(raw)) continue;
|
|
344
|
+
const type = typeof raw.type === "string" ? raw.type : "";
|
|
345
|
+
if (type.startsWith("web_search")) {
|
|
346
|
+
out.push({ type: "web_search" }); // hosted sidecar path
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
if (typeof raw.name === "string" && raw.name.length > 0 && isRec(raw.input_schema)) {
|
|
350
|
+
out.push({
|
|
351
|
+
type: "function",
|
|
352
|
+
name: raw.name,
|
|
353
|
+
...(typeof raw.description === "string" ? { description: raw.description } : {}),
|
|
354
|
+
parameters: raw.input_schema as Record<string, unknown>,
|
|
355
|
+
});
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
// Other server tools (bash_*, text_editor_*, ...) have no routed equivalent: drop.
|
|
359
|
+
}
|
|
360
|
+
return out.length > 0 ? out : undefined;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function toolChoiceToResponses(choice: unknown, body: Rec): void {
|
|
364
|
+
if (!isRec(choice)) return;
|
|
365
|
+
if (choice.disable_parallel_tool_use === true) body.parallel_tool_calls = false;
|
|
366
|
+
switch (choice.type) {
|
|
367
|
+
case "auto": body.tool_choice = "auto"; break;
|
|
368
|
+
case "none": body.tool_choice = "none"; break;
|
|
369
|
+
case "any": body.tool_choice = "required"; break;
|
|
370
|
+
case "tool":
|
|
371
|
+
if (typeof choice.name !== "string" || choice.name.length === 0) {
|
|
372
|
+
throw new AnthropicRequestError("tool_choice.tool requires a name");
|
|
373
|
+
}
|
|
374
|
+
body.tool_choice = { type: "function", name: choice.name };
|
|
375
|
+
break;
|
|
376
|
+
default: break;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/** Recursive canonical JSON (keys sorted at every depth) — stable cache-cohort input. */
|
|
381
|
+
function canonicalJson(value: unknown): string {
|
|
382
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
383
|
+
if (value && typeof value === "object") {
|
|
384
|
+
const entries = Object.entries(value as Rec).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
385
|
+
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`).join(",")}}`;
|
|
386
|
+
}
|
|
387
|
+
return JSON.stringify(value) ?? "null";
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/** Provenance of the generated prompt_cache_key (never serialized into the wire body). */
|
|
391
|
+
export type ClaudeCacheKeySource = "metadata" | "system" | null;
|
|
392
|
+
|
|
393
|
+
export interface ClaudeInboundTranslation {
|
|
394
|
+
body: Rec;
|
|
395
|
+
cacheKeySource: ClaudeCacheKeySource;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Translate an Anthropic Messages request body into a /v1/responses request body.
|
|
400
|
+
* Throws AnthropicRequestError (-> 400 invalid_request_error) on malformed input.
|
|
401
|
+
*/
|
|
402
|
+
export function anthropicToResponsesBody(raw: unknown, cc?: OcxClaudeCodeConfig): Rec {
|
|
403
|
+
return anthropicToResponsesTranslation(raw, cc).body;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Full translation result: the wire body plus the prompt-cache-key provenance as an
|
|
408
|
+
* OUT-OF-BODY tuple (audit 133 R3#1 — an in-body marker would leak upstream through
|
|
409
|
+
* the native Responses forward and 400).
|
|
410
|
+
*/
|
|
411
|
+
export function anthropicToResponsesTranslation(raw: unknown, cc?: OcxClaudeCodeConfig): ClaudeInboundTranslation {
|
|
412
|
+
if (!isRec(raw)) throw new AnthropicRequestError("request body must be a JSON object");
|
|
413
|
+
if (typeof raw.model !== "string" || raw.model.length === 0) {
|
|
414
|
+
throw new AnthropicRequestError("model is required");
|
|
415
|
+
}
|
|
416
|
+
if (!Array.isArray(raw.messages) || raw.messages.length === 0) {
|
|
417
|
+
throw new AnthropicRequestError("messages must be a non-empty array");
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const input: Rec[] = [];
|
|
421
|
+
const systemParts: string[] = [];
|
|
422
|
+
const topLevelSystem = systemToInstructions(raw.system);
|
|
423
|
+
if (topLevelSystem !== undefined) systemParts.push(topLevelSystem);
|
|
424
|
+
const blockedNames = effectiveBlockedSkillNames(cc);
|
|
425
|
+
const elide: SkillElisionContext = {
|
|
426
|
+
callIds: blockedSkillCallIds(raw.messages, blockedNames),
|
|
427
|
+
names: blockedNames,
|
|
428
|
+
};
|
|
429
|
+
for (const msg of raw.messages) {
|
|
430
|
+
if (!isRec(msg)) throw new AnthropicRequestError("each message must be an object");
|
|
431
|
+
if (msg.role === "user") userMessageToItems(msg.content, input, elide);
|
|
432
|
+
else if (msg.role === "assistant") assistantMessageToItems(msg.content, input);
|
|
433
|
+
else if (msg.role === "system") {
|
|
434
|
+
const text = systemMessageText(msg.content);
|
|
435
|
+
if (text.length > 0) systemParts.push(text);
|
|
436
|
+
}
|
|
437
|
+
else throw new AnthropicRequestError(`unsupported message role: ${String(msg.role)}`);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const body: Rec = {
|
|
441
|
+
model: resolveInboundModel(raw.model, cc),
|
|
442
|
+
input,
|
|
443
|
+
store: false,
|
|
444
|
+
stream: raw.stream === true,
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
if (systemParts.length > 0) body.instructions = systemParts.join("\n\n");
|
|
448
|
+
|
|
449
|
+
const tools = toolsToResponses(raw.tools);
|
|
450
|
+
if (tools) body.tools = tools;
|
|
451
|
+
toolChoiceToResponses(raw.tool_choice, body);
|
|
452
|
+
|
|
453
|
+
if (typeof raw.max_tokens === "number") body.max_output_tokens = raw.max_tokens;
|
|
454
|
+
if (typeof raw.temperature === "number") body.temperature = raw.temperature;
|
|
455
|
+
if (typeof raw.top_p === "number") body.top_p = raw.top_p;
|
|
456
|
+
// top_k: accepted and dropped (no Responses equivalent).
|
|
457
|
+
if (Array.isArray(raw.stop_sequences) && raw.stop_sequences.length > 0) {
|
|
458
|
+
body.stop = raw.stop_sequences.filter((s): s is string => typeof s === "string");
|
|
459
|
+
}
|
|
460
|
+
let cacheKeySource: ClaudeCacheKeySource = null;
|
|
461
|
+
if (isRec(raw.metadata) && typeof raw.metadata.user_id === "string") {
|
|
462
|
+
body.user = raw.metadata.user_id;
|
|
463
|
+
// OpenAI-side prompt caching is routed by prompt_cache_key (Codex clients send
|
|
464
|
+
// their session id; without it consecutive /v1/messages turns reported
|
|
465
|
+
// cached_tokens: 0 on the ChatGPT backend — devlog 090). Claude Code's
|
|
466
|
+
// metadata.user_id embeds the session uuid, so hashing it yields a stable
|
|
467
|
+
// per-session key with a bounded length/charset.
|
|
468
|
+
body.prompt_cache_key = createHash("sha256").update(raw.metadata.user_id).digest("hex").slice(0, 32);
|
|
469
|
+
cacheKeySource = "metadata";
|
|
470
|
+
} else if (systemParts.length > 0) {
|
|
471
|
+
// Claude Desktop sends no metadata.user_id (H1, devlog 130): without any key the
|
|
472
|
+
// ChatGPT/OpenAI backends reported cached_tokens:0 on every turn. Fall back to a
|
|
473
|
+
// cache-cohort hash (devlog 260712 B4 + Pro review 012): fingerprint what the
|
|
474
|
+
// upstream actually receives — resolved model, post-translation system, and the
|
|
475
|
+
// FULL translated tool definitions in WIRE ORDER (sorting the hash while sending
|
|
476
|
+
// a different order would break the key↔prefix correspondence). canonical JSON
|
|
477
|
+
// (recursive key sort) + a version field so future normalization changes never
|
|
478
|
+
// mix cohorts. system-only keys herded different models/toolsets into one key
|
|
479
|
+
// and burned OpenAI's ~15 RPM per-key routing budget (audit R1#4/R2#5/R1#10).
|
|
480
|
+
// Exact-prefix matching still isolates content; the key only steers routing
|
|
481
|
+
// affinity. Callers must NOT synthesize a session_id header from this fallback
|
|
482
|
+
// (audit 133 R2#3).
|
|
483
|
+
body.prompt_cache_key = createHash("sha256")
|
|
484
|
+
.update(canonicalJson({
|
|
485
|
+
version: 2,
|
|
486
|
+
model: body.model,
|
|
487
|
+
system: systemParts,
|
|
488
|
+
tools: Array.isArray(body.tools) ? body.tools : [],
|
|
489
|
+
}))
|
|
490
|
+
.digest("hex").slice(0, 32);
|
|
491
|
+
cacheKeySource = "system";
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const thinking = raw.thinking;
|
|
495
|
+
const outputConfigEffort = effortFromOutputConfig(raw.output_config);
|
|
496
|
+
const thinkingDisabled = isRec(thinking) && thinking.type === "disabled";
|
|
497
|
+
if (!thinkingDisabled && (isRec(thinking) || outputConfigEffort !== undefined)) {
|
|
498
|
+
const reasoning: Rec = { summary: "auto" };
|
|
499
|
+
if (outputConfigEffort !== undefined) {
|
|
500
|
+
// Adaptive wire: /effort arrives as output_config.effort (devlog 080).
|
|
501
|
+
reasoning.effort = outputConfigEffort;
|
|
502
|
+
} else if (isRec(thinking) && thinking.type === "enabled" && typeof thinking.budget_tokens === "number") {
|
|
503
|
+
reasoning.effort = effortForThinkingBudget(thinking.budget_tokens);
|
|
504
|
+
}
|
|
505
|
+
body.reasoning = reasoning;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
return { body, cacheKeySource };
|
|
509
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anthropic-flavor /v1/models entries in the official ModelInfo shape
|
|
3
|
+
* (anthropic-sdk-typescript@9e46760 src/resources/models.ts — devlog 131).
|
|
4
|
+
*
|
|
5
|
+
* Why full ModelInfo: Claude Desktop 3P discovery is the only channel that can
|
|
6
|
+
* carry per-model capabilities (effort ladder / thinking types); the static
|
|
7
|
+
* inferenceModels schema has no capability fields. Claude Code CLI 2.1.207 strips
|
|
8
|
+
* unknown fields, so the richer shape is backward-safe (audit 133 R1#4).
|
|
9
|
+
*
|
|
10
|
+
* Honesty rules (audit 133 R2#1/R2#2/R3#2/R4#1):
|
|
11
|
+
* - native ladders start from the injected catalog but advertise ONLY rungs that
|
|
12
|
+
* survive nativeEffortClamp as identity (`(clamp(r) ?? r) === r`), ultra excluded;
|
|
13
|
+
* - routed ladders use the adapter-reported CatalogModel.reasoningEfforts only —
|
|
14
|
+
* no ladder means effort.supported:false, never a guess;
|
|
15
|
+
* - created_at is a fixed constant; max_input_tokens is authoritative-or-null;
|
|
16
|
+
* max_tokens is always null (no authoritative output limit exists proxy-side).
|
|
17
|
+
*/
|
|
18
|
+
import { catalogModelEfforts, nativeEffortClamp, nativeOpenAiContextWindow, type CatalogModel } from "../codex/catalog";
|
|
19
|
+
import { claudeCodeAlias, claudeCodeNativeAlias } from "./alias";
|
|
20
|
+
import { desktop3pAlias } from "./desktop-3p";
|
|
21
|
+
import { AUTO_CONTEXT_OFF, shouldMarkOneMillion, type AutoContextMode } from "./context-windows";
|
|
22
|
+
|
|
23
|
+
const MODEL_INFO_CREATED_AT = "2026-01-01T00:00:00Z";
|
|
24
|
+
const ANTHROPIC_EFFORT_RUNGS = new Set(["low", "medium", "high", "xhigh", "max"]);
|
|
25
|
+
const ONE_MILLION = 1_000_000;
|
|
26
|
+
|
|
27
|
+
interface CapabilitySupport { supported: boolean }
|
|
28
|
+
|
|
29
|
+
function cap(supported: boolean): CapabilitySupport {
|
|
30
|
+
return { supported };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function effortCapability(ladder: readonly string[]) {
|
|
34
|
+
const rungs = new Set(ladder.filter(r => ANTHROPIC_EFFORT_RUNGS.has(r)));
|
|
35
|
+
const supported = rungs.size > 0;
|
|
36
|
+
return {
|
|
37
|
+
supported,
|
|
38
|
+
low: cap(rungs.has("low")),
|
|
39
|
+
medium: cap(rungs.has("medium")),
|
|
40
|
+
high: cap(rungs.has("high")),
|
|
41
|
+
max: cap(rungs.has("max")),
|
|
42
|
+
xhigh: supported ? cap(rungs.has("xhigh")) : null,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function modelCapabilities(ladder: readonly string[], imageInput: boolean) {
|
|
47
|
+
const reasons = ladder.length > 0;
|
|
48
|
+
return {
|
|
49
|
+
batch: cap(false),
|
|
50
|
+
citations: cap(false),
|
|
51
|
+
code_execution: cap(false),
|
|
52
|
+
context_management: {
|
|
53
|
+
supported: false,
|
|
54
|
+
clear_thinking_20251015: null,
|
|
55
|
+
clear_tool_uses_20250919: null,
|
|
56
|
+
compact_20260112: null,
|
|
57
|
+
},
|
|
58
|
+
effort: effortCapability(ladder),
|
|
59
|
+
image_input: cap(imageInput),
|
|
60
|
+
pdf_input: cap(false),
|
|
61
|
+
structured_outputs: cap(false),
|
|
62
|
+
thinking: reasons
|
|
63
|
+
? { supported: true, types: { adaptive: cap(true), enabled: cap(true) } }
|
|
64
|
+
: { supported: false, types: { adaptive: cap(false), enabled: cap(false) } },
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Native ladder: catalog rungs that the native effort clamp passes through as identity. */
|
|
69
|
+
export function nativeEffectiveLadder(slug: string): string[] {
|
|
70
|
+
const ladder = catalogModelEfforts([slug]).get(slug) ?? [];
|
|
71
|
+
return ladder.filter(r => r !== "ultra" && (nativeEffortClamp(slug, r) ?? r) === r);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface AnthropicModelInfo {
|
|
75
|
+
id: string;
|
|
76
|
+
display_name: string;
|
|
77
|
+
type: "model";
|
|
78
|
+
created_at: string;
|
|
79
|
+
capabilities: ReturnType<typeof modelCapabilities>;
|
|
80
|
+
max_input_tokens: number | null;
|
|
81
|
+
max_tokens: null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function modelInfo(id: string, displayName: string, ladder: readonly string[], imageInput: boolean, contextWindow?: number): AnthropicModelInfo {
|
|
85
|
+
return {
|
|
86
|
+
id,
|
|
87
|
+
display_name: displayName,
|
|
88
|
+
type: "model",
|
|
89
|
+
created_at: MODEL_INFO_CREATED_AT,
|
|
90
|
+
capabilities: modelCapabilities(ladder, imageInput),
|
|
91
|
+
max_input_tokens: typeof contextWindow === "number" && contextWindow > 0 ? contextWindow : null,
|
|
92
|
+
max_tokens: null,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Which id family the discovery list carries (devlog 050): Claude Code (CLI)
|
|
98
|
+
* gets readable `claude-ocx-*` ids; Claude Desktop keeps the hashed
|
|
99
|
+
* `claude-opus-4-8-<code>` family its 3P config was written with. Both families
|
|
100
|
+
* decode in resolveInboundModel regardless of the style served here.
|
|
101
|
+
*/
|
|
102
|
+
export type AnthropicIdStyle = "desktop3p" | "readable";
|
|
103
|
+
|
|
104
|
+
/** Build the full anthropic-flavor discovery list (ids are Desktop 3P aliases). */
|
|
105
|
+
export function buildAnthropicModelInfos(
|
|
106
|
+
nativeSlugs: readonly string[],
|
|
107
|
+
routedModels: readonly CatalogModel[],
|
|
108
|
+
auto: AutoContextMode = AUTO_CONTEXT_OFF,
|
|
109
|
+
idStyle: AnthropicIdStyle = "desktop3p",
|
|
110
|
+
aliasForRoute: (provider: string, modelId: string) => string = desktop3pAlias,
|
|
111
|
+
): AnthropicModelInfo[] {
|
|
112
|
+
const out: AnthropicModelInfo[] = [];
|
|
113
|
+
const seen = new Set<string>();
|
|
114
|
+
// [1m] picker variant (devlog 260712 B1): Claude Code accounts exactly 1M for ids
|
|
115
|
+
// carrying the marker (2.1.207 binary: /\[1m\]/i → 1e6, compaction preserved), so
|
|
116
|
+
// models with an authoritative >=1M window get a second selectable row. In
|
|
117
|
+
// auto-context mode (devlog 020) the predicate widens to windows > 200k that can
|
|
118
|
+
// host the compact window — display stays honest (real window, not "1M"). Guards
|
|
119
|
+
// (audit R1#11): same dedupe set, never double-suffix.
|
|
120
|
+
const push1mVariant = (base: AnthropicModelInfo, contextWindow: number | undefined, mode: AutoContextMode = auto) => {
|
|
121
|
+
if (!shouldMarkOneMillion(contextWindow, mode)) return;
|
|
122
|
+
if (base.id.includes("[1m]")) return;
|
|
123
|
+
const id = `${base.id}[1m]`;
|
|
124
|
+
if (seen.has(id)) return;
|
|
125
|
+
seen.add(id);
|
|
126
|
+
const window = contextWindow as number;
|
|
127
|
+
const label = window >= ONE_MILLION ? "1M" : `${Math.round(window / 1_000)}k`;
|
|
128
|
+
out.push({ ...base, id, display_name: `${base.display_name} · ${label}`, max_input_tokens: Math.min(window, ONE_MILLION) });
|
|
129
|
+
};
|
|
130
|
+
for (const slug of nativeSlugs) {
|
|
131
|
+
const id = idStyle === "readable" ? claudeCodeNativeAlias(slug) : aliasForRoute("native", slug);
|
|
132
|
+
if (seen.has(id)) continue;
|
|
133
|
+
seen.add(id);
|
|
134
|
+
const info = modelInfo(id, `${slug} (native)`, nativeEffectiveLadder(slug), true);
|
|
135
|
+
out.push(info);
|
|
136
|
+
push1mVariant(info, nativeOpenAiContextWindow(slug));
|
|
137
|
+
}
|
|
138
|
+
for (const m of routedModels) {
|
|
139
|
+
const id = idStyle === "readable" ? claudeCodeAlias(m.provider, m.id) : aliasForRoute(m.provider, m.id);
|
|
140
|
+
if (seen.has(id)) continue;
|
|
141
|
+
seen.add(id);
|
|
142
|
+
const ladder = Array.isArray(m.reasoningEfforts) ? m.reasoningEfforts : [];
|
|
143
|
+
const imageInput = Array.isArray(m.inputModalities) ? m.inputModalities.includes("image") : false;
|
|
144
|
+
const info = modelInfo(id, `${m.id} (${m.provider})`, ladder, imageInput, m.contextWindow);
|
|
145
|
+
out.push(info);
|
|
146
|
+
// Anthropic passthrough guard (audit 021 #3): never auto-widen canonical claude
|
|
147
|
+
// routes — only a genuine >=1M window earns the variant row there.
|
|
148
|
+
push1mVariant(info, m.contextWindow, m.provider === "anthropic" ? AUTO_CONTEXT_OFF : auto);
|
|
149
|
+
}
|
|
150
|
+
return out;
|
|
151
|
+
}
|