@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,230 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AdapterEvent,
|
|
3
|
+
OcxAssistantContentPart,
|
|
4
|
+
OcxAssistantMessage,
|
|
5
|
+
OcxParsedRequest,
|
|
6
|
+
OcxUsage,
|
|
7
|
+
} from "../../types";
|
|
8
|
+
|
|
9
|
+
const ACTIONABLE_REQUEST_RE = /(?:\b(?:add|change|check|continue|create|debug|delete|deploy|edit|execute|fix|implement|inspect|keep going|modify|patch|proceed|refactor|remove|review|run|test|update|write)\b|继续|接着|往下|升级|修改|改(?:一下|下)?|修复|实现|添加|新增|删除|重构|更新|运行|执行|检查|查看|排查|调试|创建|写入|提交|推送|部署|看下|改成|修一下)/iu;
|
|
10
|
+
const PLAN_ONLY_REQUEST_RE = /(?:暂时不要(?:调用|使用)工具|不要调用工具|不用执行|只回复(?:计划|方案)|(?:只|先|给|说|写|出|来)(?:我)?(?:一个|个|一下|份)?[^。!?\n]{0,8}?(?:计划|方案|草案|提案|思路)|(?:计划|方案|草案|提案|思路)(?:就(?:行|好|可以)|即可)|\b(?:just give|provide)\s+(?:me\s+)?(?:a\s+)?plan\b|\b(?:do not|don't)\s+(?:use|call)\s+tools?\b|\b(?:write|draft|outline|propose|give|provide|create|make|share|suggest|sketch)\s+(?:me\s+)?(?:a\s+|an\s+|the\s+|your\s+)?(?:(?:brief|concise|short|detailed|high[-\s]?level|rough|quick|step[-\s]?by[-\s]?step|implementation|migration|refactor(?:ing)?|design|technical)\s+)*(?:plan|proposal|draft|outline|approach|strategy|design\s+doc)\b)/iu;
|
|
11
|
+
const PLAN_INTENT_RE = /(?:\b(?:i(?:'|’)m going to|i will|i(?:'|’)ll|let me|next i)\b|我(?:先|会|将|接下来)|下一步)/iu;
|
|
12
|
+
const PLAN_OR_COMPLETION_RE = /(?:\b(?:i(?:'|’)m going to|i will|i(?:'|’)ll|let me|next i|i(?:'|’)ve (?:already )?(?:added|applied|changed|completed|fixed|implemented|modified|updated))\b|\b(?:done|completed|fixed|implemented|updated|applied)\b|我(?:先|会|将|接下来)|下一步|已(?:经)?(?:修改|修复|完成|应用|更新|实现|处理)|完成了|已经好了)/iu;
|
|
13
|
+
const WAITING_FOR_USER_RE = /(?:[??]\s*$|需要我|请(?:确认|选择|提供)|是否|要不要|可以吗|\b(?:do you want|should i|which file|please confirm|please provide)\b)/iu;
|
|
14
|
+
const EXPLICIT_CONTINUE_RE = /^(?:继续|接着|往下|go on|continue|proceed|keep going)\s*[.!。!]?$/iu;
|
|
15
|
+
const MAX_ANNOUNCEMENT_CHARS = 280;
|
|
16
|
+
|
|
17
|
+
export const TERMINAL_GUARD_NUDGE =
|
|
18
|
+
"你刚才只描述了计划,没有执行任何工具。不要再次解释计划,现在立即调用必要工具执行用户任务。" +
|
|
19
|
+
"只有工具返回后才总结;如果确实无法执行,请明确说明阻塞原因。";
|
|
20
|
+
|
|
21
|
+
export type TerminalGuardDecision = "pass" | "continue" | "ambiguous";
|
|
22
|
+
|
|
23
|
+
export interface TerminalTurnAnalysis {
|
|
24
|
+
decision: TerminalGuardDecision;
|
|
25
|
+
reason: "normal" | "waiting_for_user" | "no_tools" | "no_actionable_request" | "no_execution_claim" | "recent_tool_activity" | "substantive_answer" | "suspicious_no_tool";
|
|
26
|
+
assistantText: string;
|
|
27
|
+
userText: string;
|
|
28
|
+
hasToolCall: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function messageText(value: unknown): string {
|
|
32
|
+
if (typeof value === "string") return value;
|
|
33
|
+
if (!Array.isArray(value)) return "";
|
|
34
|
+
return value
|
|
35
|
+
.filter((part): part is { type?: unknown; text?: unknown } => !!part && typeof part === "object")
|
|
36
|
+
.filter(part => part.type === "text" && typeof part.text === "string")
|
|
37
|
+
.map(part => part.text as string)
|
|
38
|
+
.join("");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function latestUserText(parsed: OcxParsedRequest): string {
|
|
42
|
+
for (let i = parsed.context.messages.length - 1; i >= 0; i -= 1) {
|
|
43
|
+
const message = parsed.context.messages[i];
|
|
44
|
+
if (message.role === "user") return messageText(message.content);
|
|
45
|
+
}
|
|
46
|
+
return "";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function recentTurnHasToolActivity(parsed: OcxParsedRequest): boolean {
|
|
50
|
+
let latestUserIndex = -1;
|
|
51
|
+
for (let i = parsed.context.messages.length - 1; i >= 0; i -= 1) {
|
|
52
|
+
if (parsed.context.messages[i]?.role === "user") {
|
|
53
|
+
latestUserIndex = i;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (latestUserIndex < 0) return false;
|
|
58
|
+
let lastAssistant: OcxAssistantMessage | undefined;
|
|
59
|
+
for (let i = latestUserIndex - 1; i >= 0; i -= 1) {
|
|
60
|
+
const message = parsed.context.messages[i];
|
|
61
|
+
if (message.role === "assistant") {
|
|
62
|
+
lastAssistant = message;
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (lastAssistant?.role === "assistant") {
|
|
67
|
+
const hasToolCall = lastAssistant.content.some(part => part.type === "toolCall");
|
|
68
|
+
const text = lastAssistant.content
|
|
69
|
+
.filter(part => part.type === "text")
|
|
70
|
+
.map(part => part.text)
|
|
71
|
+
.join("");
|
|
72
|
+
if (!hasToolCall && PLAN_INTENT_RE.test(text)) return false;
|
|
73
|
+
}
|
|
74
|
+
for (let i = latestUserIndex - 1; i >= 0 && parsed.context.messages[i]?.role !== "user"; i -= 1) {
|
|
75
|
+
const message = parsed.context.messages[i];
|
|
76
|
+
if (message.role === "toolResult") return true;
|
|
77
|
+
if (message.role === "assistant" && message.content.some(part => part.type === "toolCall")) return true;
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function assistantText(events: readonly AdapterEvent[]): string {
|
|
83
|
+
return events
|
|
84
|
+
.filter((event): event is Extract<AdapterEvent, { type: "text_delta" }> => event.type === "text_delta")
|
|
85
|
+
.map(event => event.text)
|
|
86
|
+
.join("");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function analyzeTerminalTurn(parsed: OcxParsedRequest, events: readonly AdapterEvent[]): TerminalTurnAnalysis {
|
|
90
|
+
const userText = latestUserText(parsed);
|
|
91
|
+
const text = assistantText(events);
|
|
92
|
+
const hasToolCall = events.some(event => event.type === "tool_call_start");
|
|
93
|
+
if (hasToolCall) {
|
|
94
|
+
return { decision: "pass", reason: "normal", assistantText: text, userText, hasToolCall };
|
|
95
|
+
}
|
|
96
|
+
if (!parsed.context.tools || parsed.context.tools.length === 0 || parsed.options.toolChoice === "none") {
|
|
97
|
+
return { decision: "pass", reason: "no_tools", assistantText: text, userText, hasToolCall };
|
|
98
|
+
}
|
|
99
|
+
if (!ACTIONABLE_REQUEST_RE.test(userText)) {
|
|
100
|
+
return { decision: "pass", reason: "no_actionable_request", assistantText: text, userText, hasToolCall };
|
|
101
|
+
}
|
|
102
|
+
if (PLAN_ONLY_REQUEST_RE.test(userText)) {
|
|
103
|
+
return { decision: "pass", reason: "no_actionable_request", assistantText: text, userText, hasToolCall };
|
|
104
|
+
}
|
|
105
|
+
if (EXPLICIT_CONTINUE_RE.test(userText) && recentTurnHasToolActivity(parsed)) {
|
|
106
|
+
return { decision: "pass", reason: "recent_tool_activity", assistantText: text, userText, hasToolCall };
|
|
107
|
+
}
|
|
108
|
+
if (WAITING_FOR_USER_RE.test(text)) {
|
|
109
|
+
return { decision: "pass", reason: "waiting_for_user", assistantText: text, userText, hasToolCall };
|
|
110
|
+
}
|
|
111
|
+
if (text.trim().length > MAX_ANNOUNCEMENT_CHARS) {
|
|
112
|
+
return { decision: "pass", reason: "substantive_answer", assistantText: text, userText, hasToolCall };
|
|
113
|
+
}
|
|
114
|
+
if (!PLAN_OR_COMPLETION_RE.test(text)) {
|
|
115
|
+
return { decision: "ambiguous", reason: "no_execution_claim", assistantText: text, userText, hasToolCall };
|
|
116
|
+
}
|
|
117
|
+
return { decision: "continue", reason: "suspicious_no_tool", assistantText: text, userText, hasToolCall };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function assistantMessageFromEvents(events: readonly AdapterEvent[]): OcxAssistantMessage | undefined {
|
|
121
|
+
let text = "";
|
|
122
|
+
let thinking = "";
|
|
123
|
+
let signature: string | undefined;
|
|
124
|
+
const redacted: string[] = [];
|
|
125
|
+
for (const event of events) {
|
|
126
|
+
if (event.type === "text_delta") text += event.text;
|
|
127
|
+
else if (event.type === "thinking_delta") thinking += event.thinking;
|
|
128
|
+
else if (event.type === "thinking_signature") signature = event.signature;
|
|
129
|
+
else if (event.type === "redacted_thinking") redacted.push(event.data);
|
|
130
|
+
}
|
|
131
|
+
const content: OcxAssistantContentPart[] = [];
|
|
132
|
+
if (thinking || signature || redacted.length > 0) {
|
|
133
|
+
content.push({ type: "thinking", thinking, ...(signature ? { signature } : {}), ...(redacted.length > 0 ? { redacted } : {}) });
|
|
134
|
+
}
|
|
135
|
+
if (text) content.push({ type: "text", text });
|
|
136
|
+
if (content.length === 0) return undefined;
|
|
137
|
+
return { role: "assistant", content, timestamp: Date.now() };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function buildContinuationRequest(parsed: OcxParsedRequest, events: readonly AdapterEvent[]): OcxParsedRequest {
|
|
141
|
+
const messages = [...parsed.context.messages];
|
|
142
|
+
const assistant = assistantMessageFromEvents(events);
|
|
143
|
+
if (assistant) messages.push(assistant);
|
|
144
|
+
messages.push({ role: "developer", content: TERMINAL_GUARD_NUDGE, timestamp: Date.now() });
|
|
145
|
+
return { ...parsed, context: { ...parsed.context, messages } };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface GuardedEventStreamOptions {
|
|
149
|
+
parsed: OcxParsedRequest;
|
|
150
|
+
firstEvents: AsyncIterable<AdapterEvent>;
|
|
151
|
+
continuation: (parsed: OcxParsedRequest) => AsyncIterable<AdapterEvent> | Promise<AsyncIterable<AdapterEvent>>;
|
|
152
|
+
adapterName?: string;
|
|
153
|
+
maxAutoContinuations?: number;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function mergeUsage(first: OcxUsage | undefined, second: OcxUsage | undefined): OcxUsage | undefined {
|
|
157
|
+
if (!first) return second;
|
|
158
|
+
if (!second) return first;
|
|
159
|
+
const sumOptional = (key: keyof OcxUsage): number | undefined => {
|
|
160
|
+
const left = first[key];
|
|
161
|
+
const right = second[key];
|
|
162
|
+
return typeof left === "number" || typeof right === "number"
|
|
163
|
+
? (typeof left === "number" ? left : 0) + (typeof right === "number" ? right : 0)
|
|
164
|
+
: undefined;
|
|
165
|
+
};
|
|
166
|
+
const cachedInputTokens = sumOptional("cachedInputTokens");
|
|
167
|
+
const cacheReadInputTokens = sumOptional("cacheReadInputTokens");
|
|
168
|
+
const cacheCreationInputTokens = sumOptional("cacheCreationInputTokens");
|
|
169
|
+
const reasoningOutputTokens = sumOptional("reasoningOutputTokens");
|
|
170
|
+
const inputTokens = first.inputTokens + second.inputTokens;
|
|
171
|
+
const outputTokens = first.outputTokens + second.outputTokens;
|
|
172
|
+
return {
|
|
173
|
+
inputTokens,
|
|
174
|
+
outputTokens,
|
|
175
|
+
totalTokens: inputTokens + outputTokens,
|
|
176
|
+
...(cachedInputTokens !== undefined ? { cachedInputTokens } : {}),
|
|
177
|
+
...(cacheReadInputTokens !== undefined ? { cacheReadInputTokens } : {}),
|
|
178
|
+
...(cacheCreationInputTokens !== undefined ? { cacheCreationInputTokens } : {}),
|
|
179
|
+
...(reasoningOutputTokens !== undefined ? { reasoningOutputTokens } : {}),
|
|
180
|
+
...(first.estimated || second.estimated ? { estimated: true } : {}),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Preserve normal terminals, but withhold one suspicious no-tool terminal for a bounded re-ask. */
|
|
185
|
+
export async function* guardTerminalEventStream(options: GuardedEventStreamOptions): AsyncGenerator<AdapterEvent> {
|
|
186
|
+
const maxContinuations = Math.max(0, Math.min(2, Math.floor(options.maxAutoContinuations ?? 1)));
|
|
187
|
+
let parsed = options.parsed;
|
|
188
|
+
let continuations = 0;
|
|
189
|
+
let accumulatedUsage: OcxUsage | undefined;
|
|
190
|
+
let source: AsyncIterable<AdapterEvent> = options.firstEvents;
|
|
191
|
+
|
|
192
|
+
while (true) {
|
|
193
|
+
const seen: AdapterEvent[] = [];
|
|
194
|
+
let terminalSeen = false;
|
|
195
|
+
for await (const event of source) {
|
|
196
|
+
if (event.type === "done") {
|
|
197
|
+
terminalSeen = true;
|
|
198
|
+
const analysis = options.adapterName === "anthropic"
|
|
199
|
+
? analyzeTerminalTurn(parsed, seen)
|
|
200
|
+
: { decision: "pass" as const };
|
|
201
|
+
const normalStop = event.stopReason !== "max_tokens" && event.stopReason !== "content_filter";
|
|
202
|
+
if (normalStop && analysis.decision === "continue" && continuations < maxContinuations) {
|
|
203
|
+
accumulatedUsage = mergeUsage(accumulatedUsage, event.usage);
|
|
204
|
+
continuations += 1;
|
|
205
|
+
parsed = buildContinuationRequest(parsed, seen);
|
|
206
|
+
yield { type: "assistant_boundary" };
|
|
207
|
+
try {
|
|
208
|
+
source = await options.continuation(parsed);
|
|
209
|
+
} catch (error) {
|
|
210
|
+
yield { type: "error", message: error instanceof Error ? error.message : String(error) };
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
const usage = mergeUsage(accumulatedUsage, event.usage);
|
|
216
|
+
yield usage ? { ...event, usage } : event;
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (event.type === "incomplete" || event.type === "error") {
|
|
220
|
+
terminalSeen = true;
|
|
221
|
+
const usage = mergeUsage(accumulatedUsage, event.usage);
|
|
222
|
+
yield usage ? { ...event, usage } : event;
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
seen.push(event);
|
|
226
|
+
yield event;
|
|
227
|
+
}
|
|
228
|
+
if (!terminalSeen) return;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upstream connection failures share one message shape across the three catch sites in
|
|
3
|
+
* core.ts. A TLS certificate/hostname mismatch deserves its own wording: the generic
|
|
4
|
+
* "Provider unreachable" reads as if opencodex built a wrong endpoint, which sent issue
|
|
5
|
+
* #553 looking for an adapter URL bug that does not exist. Name the likely cause and the
|
|
6
|
+
* command that settles it.
|
|
7
|
+
*/
|
|
8
|
+
export function describeUpstreamConnectFailure(err: unknown, connectMs: number): string {
|
|
9
|
+
if (err instanceof Error && err.name === "TimeoutError") {
|
|
10
|
+
return `Provider connect timeout after ${connectMs}ms`;
|
|
11
|
+
}
|
|
12
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
13
|
+
const code = err instanceof Error ? (err as { code?: unknown }).code : undefined;
|
|
14
|
+
// `code` is the reliable signal. The message fallback is anchored to the head because Bun
|
|
15
|
+
// renders this rejection as `ERR_TLS_CERT_ALTNAME_INVALID fetching "<url>"`; matching the
|
|
16
|
+
// bare substring anywhere would also fire on text that merely quotes the code back at us.
|
|
17
|
+
// Only transport failures reach these call sites, so that is defensive rather than load-bearing.
|
|
18
|
+
if (code === "ERR_TLS_CERT_ALTNAME_INVALID" || detail.startsWith("ERR_TLS_CERT_ALTNAME_INVALID")) {
|
|
19
|
+
const host = extractHostname(detail);
|
|
20
|
+
const target = host ?? "the provider host";
|
|
21
|
+
const probe = host ?? "<host>";
|
|
22
|
+
return `Provider TLS certificate does not match ${target}: ${redactUrlUserinfo(detail)}. `
|
|
23
|
+
+ "opencodex did not rewrite this hostname — a certificate that does not cover it normally "
|
|
24
|
+
+ "means TLS interception (corporate proxy, VPN, or local MITM tooling) or a poisoned DNS "
|
|
25
|
+
+ `answer. Check with: openssl s_client -connect ${probe}:443 -servername ${probe} `
|
|
26
|
+
+ "</dev/null | openssl x509 -noout -subject -ext subjectAltName";
|
|
27
|
+
}
|
|
28
|
+
return `Provider unreachable: ${redactUrlUserinfo(detail)}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* A provider base URL may carry credentials as userinfo (`https://user:token@host/`), and the
|
|
33
|
+
* runtime error echoes the URL it was fetching. Strip it before the message reaches a client
|
|
34
|
+
* or a log line.
|
|
35
|
+
*/
|
|
36
|
+
function redactUrlUserinfo(detail: string): string {
|
|
37
|
+
return detail.replace(/(https?:\/\/)[^/\s"'@]*@/g, "$1<redacted>@");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function extractHostname(detail: string): string | null {
|
|
41
|
+
const match = detail.match(/https?:\/\/([^/\s"']+)/);
|
|
42
|
+
if (!match?.[1]) return null;
|
|
43
|
+
try {
|
|
44
|
+
return new URL(`https://${match[1]}`).hostname || null;
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { collectResponsesToolGroups } from "../responses/tool-groups";
|
|
2
|
+
import { relaySseWithPayloadRewrite, type SsePayloadRewrite } from "./sse-payload-rewrite";
|
|
3
|
+
import type { TranslatorBudget } from "../lib/translator-budget";
|
|
4
|
+
|
|
5
|
+
interface NamespacedTool {
|
|
6
|
+
namespace: string;
|
|
7
|
+
name: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const IMAGE_GEN_NAMESPACE = "image_gen";
|
|
11
|
+
const IMAGE_GEN_DOTTED_PREFIX = `${IMAGE_GEN_NAMESPACE}.`;
|
|
12
|
+
|
|
13
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
14
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Restrict the generic bridge map to Codex's standalone image namespace and accept the dotted
|
|
19
|
+
* alias emitted by earlier compatibility attempts. Legacy flat dotted declarations are recovered
|
|
20
|
+
* from the raw request because the generic parser does not assign them a namespace. Exact declared
|
|
21
|
+
* names avoid splitting arbitrary double-underscore function names that belong to unrelated tools.
|
|
22
|
+
*/
|
|
23
|
+
export function imageGenToolCallAliases(
|
|
24
|
+
toolNsMap: ReadonlyMap<string, NamespacedTool>,
|
|
25
|
+
requestBody?: unknown,
|
|
26
|
+
translatorBudget?: TranslatorBudget,
|
|
27
|
+
): Map<string, NamespacedTool> {
|
|
28
|
+
const aliases = new Map<string, NamespacedTool>();
|
|
29
|
+
const retainAlias = (key: string, tool: NamespacedTool) => {
|
|
30
|
+
if (aliases.has(key)) {
|
|
31
|
+
aliases.set(key, tool);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
translatorBudget?.chargeRetained(
|
|
35
|
+
Buffer.byteLength(key) + Buffer.byteLength(tool.namespace) + Buffer.byteLength(tool.name),
|
|
36
|
+
{ kind: "request_copies" },
|
|
37
|
+
);
|
|
38
|
+
aliases.set(key, tool);
|
|
39
|
+
};
|
|
40
|
+
for (const [wireName, tool] of toolNsMap) {
|
|
41
|
+
if (tool.namespace !== IMAGE_GEN_NAMESPACE) continue;
|
|
42
|
+
retainAlias(wireName, tool);
|
|
43
|
+
retainAlias(`${tool.namespace}.${tool.name}`, tool);
|
|
44
|
+
}
|
|
45
|
+
for (const group of collectResponsesToolGroups(requestBody)) {
|
|
46
|
+
for (const tool of group) {
|
|
47
|
+
if (
|
|
48
|
+
!isPlainObject(tool)
|
|
49
|
+
|| tool.type !== "function"
|
|
50
|
+
|| typeof tool.name !== "string"
|
|
51
|
+
|| !tool.name.startsWith(IMAGE_GEN_DOTTED_PREFIX)
|
|
52
|
+
|| tool.name.length === IMAGE_GEN_DOTTED_PREFIX.length
|
|
53
|
+
) continue;
|
|
54
|
+
const localName = tool.name.slice(IMAGE_GEN_DOTTED_PREFIX.length);
|
|
55
|
+
const target = { namespace: IMAGE_GEN_NAMESPACE, name: localName };
|
|
56
|
+
retainAlias(`${IMAGE_GEN_NAMESPACE}__${localName}`, target);
|
|
57
|
+
retainAlias(tool.name, target);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return aliases;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Recursively restore only exact image-gen function-call aliases in a parsed Responses payload. */
|
|
64
|
+
function restoreImageGenCalls(
|
|
65
|
+
value: unknown,
|
|
66
|
+
aliases: ReadonlyMap<string, NamespacedTool>,
|
|
67
|
+
): { value: unknown; changed: boolean } {
|
|
68
|
+
if (Array.isArray(value)) {
|
|
69
|
+
let changed = false;
|
|
70
|
+
const restored = value.map(entry => {
|
|
71
|
+
const result = restoreImageGenCalls(entry, aliases);
|
|
72
|
+
changed ||= result.changed;
|
|
73
|
+
return result.value;
|
|
74
|
+
});
|
|
75
|
+
return changed ? { value: restored, changed: true } : { value, changed: false };
|
|
76
|
+
}
|
|
77
|
+
if (!isPlainObject(value)) return { value, changed: false };
|
|
78
|
+
|
|
79
|
+
let changed = false;
|
|
80
|
+
const restored: Record<string, unknown> = {};
|
|
81
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
82
|
+
const result = restoreImageGenCalls(entry, aliases);
|
|
83
|
+
restored[key] = result.value;
|
|
84
|
+
changed ||= result.changed;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const name = typeof value.name === "string" ? value.name : undefined;
|
|
88
|
+
const target = value.type === "function_call" && name ? aliases.get(name) : undefined;
|
|
89
|
+
if (target) {
|
|
90
|
+
restored.name = target.name;
|
|
91
|
+
restored.namespace = target.namespace;
|
|
92
|
+
changed = true;
|
|
93
|
+
}
|
|
94
|
+
return changed ? { value: restored, changed: true } : { value, changed: false };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Restore image-gen function calls in a non-streaming Responses JSON document. */
|
|
98
|
+
export function restoreImageGenCallsInJson(
|
|
99
|
+
text: string,
|
|
100
|
+
aliases: ReadonlyMap<string, NamespacedTool>,
|
|
101
|
+
): string {
|
|
102
|
+
if (aliases.size === 0) return text;
|
|
103
|
+
let payload: unknown;
|
|
104
|
+
try {
|
|
105
|
+
payload = JSON.parse(text);
|
|
106
|
+
} catch {
|
|
107
|
+
return text;
|
|
108
|
+
}
|
|
109
|
+
const restored = restoreImageGenCalls(payload, aliases);
|
|
110
|
+
return restored.changed ? JSON.stringify(restored.value) : text;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Payload rewrite for composition with other client-facing SSE transforms. */
|
|
114
|
+
export function createImageGenCallRestoreRewrite(
|
|
115
|
+
aliases: ReadonlyMap<string, NamespacedTool>,
|
|
116
|
+
): SsePayloadRewrite | undefined {
|
|
117
|
+
if (aliases.size === 0) return undefined;
|
|
118
|
+
return (payload) => restoreImageGenCallsInJson(payload, aliases);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Restore upstream-safe image-gen aliases only on the client-facing SSE branch. The inspection and
|
|
123
|
+
* continuation-cache branch must retain raw upstream names so a replay can be sent back unchanged.
|
|
124
|
+
*/
|
|
125
|
+
export function relaySseWithImageGenCallRestore(
|
|
126
|
+
body: ReadableStream<Uint8Array>,
|
|
127
|
+
aliases: ReadonlyMap<string, NamespacedTool>,
|
|
128
|
+
translatorBudget: TranslatorBudget,
|
|
129
|
+
): ReadableStream<Uint8Array> {
|
|
130
|
+
const rewrite = createImageGenCallRestoreRewrite(aliases);
|
|
131
|
+
return rewrite ? relaySseWithPayloadRewrite(body, rewrite, translatorBudget) : body;
|
|
132
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import type { ResponsesItemIdRepairConfig } from "../types";
|
|
3
|
+
import { relaySseWithPayloadRewrite, type SsePayloadRewrite } from "./sse-payload-rewrite";
|
|
4
|
+
import type { TranslatorBudget } from "../lib/translator-budget";
|
|
5
|
+
|
|
6
|
+
type RepairableItemType = "message" | "reasoning";
|
|
7
|
+
|
|
8
|
+
interface ResponsesItemIdRepairState {
|
|
9
|
+
readonly repairMissingTerminalIds: boolean;
|
|
10
|
+
readonly placeholders: Record<RepairableItemType, ReadonlySet<string>>;
|
|
11
|
+
readonly outputIds: Record<RepairableItemType, Map<number, string>>;
|
|
12
|
+
readonly scope: string;
|
|
13
|
+
readonly budget?: TranslatorBudget;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const REPAIRABLE_PREFIXES: Record<RepairableItemType, string> = {
|
|
17
|
+
message: "msg_",
|
|
18
|
+
reasoning: "rs_",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const ITEM_ID_EVENT_TYPES: Readonly<Record<string, RepairableItemType>> = {
|
|
22
|
+
"response.content_part.added": "message",
|
|
23
|
+
"response.content_part.done": "message",
|
|
24
|
+
"response.output_text.annotation.added": "message",
|
|
25
|
+
"response.output_text.delta": "message",
|
|
26
|
+
"response.output_text.done": "message",
|
|
27
|
+
"response.refusal.delta": "message",
|
|
28
|
+
"response.refusal.done": "message",
|
|
29
|
+
"response.reasoning_summary_part.added": "reasoning",
|
|
30
|
+
"response.reasoning_summary_part.done": "reasoning",
|
|
31
|
+
"response.reasoning_summary_text.delta": "reasoning",
|
|
32
|
+
"response.reasoning_summary_text.done": "reasoning",
|
|
33
|
+
"response.reasoning_text.delta": "reasoning",
|
|
34
|
+
"response.reasoning_text.done": "reasoning",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
38
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function asOutputIndex(value: unknown): number | null {
|
|
42
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function repairableItemType(item: Record<string, unknown>): RepairableItemType | null {
|
|
46
|
+
return item.type === "message" || item.type === "reasoning" ? item.type : null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function mintCanonicalId(type: RepairableItemType, scope: string, outputIndex: number): string {
|
|
50
|
+
return `${REPAIRABLE_PREFIXES[type]}ocx_${scope}_${outputIndex}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function createRepairState(config: ResponsesItemIdRepairConfig, budget?: TranslatorBudget): ResponsesItemIdRepairState {
|
|
54
|
+
const state = {
|
|
55
|
+
repairMissingTerminalIds: config.repairMissingTerminalIds === true,
|
|
56
|
+
placeholders: {
|
|
57
|
+
message: new Set(config.message ?? []),
|
|
58
|
+
reasoning: new Set(config.reasoning ?? []),
|
|
59
|
+
},
|
|
60
|
+
outputIds: {
|
|
61
|
+
message: new Map<number, string>(),
|
|
62
|
+
reasoning: new Map<number, string>(),
|
|
63
|
+
},
|
|
64
|
+
scope: randomUUID().replace(/-/g, ""),
|
|
65
|
+
budget,
|
|
66
|
+
};
|
|
67
|
+
budget?.chargeRetained(new TextEncoder().encode(JSON.stringify({
|
|
68
|
+
message: [...state.placeholders.message],
|
|
69
|
+
reasoning: [...state.placeholders.reasoning],
|
|
70
|
+
scope: state.scope,
|
|
71
|
+
})).byteLength, { kind: "item_ids" });
|
|
72
|
+
return state;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function rememberMappedId(
|
|
76
|
+
state: ResponsesItemIdRepairState,
|
|
77
|
+
outputIndex: number,
|
|
78
|
+
item: Record<string, unknown>,
|
|
79
|
+
): string | null {
|
|
80
|
+
const type = repairableItemType(item);
|
|
81
|
+
if (!type) return null;
|
|
82
|
+
const existing = state.outputIds[type].get(outputIndex);
|
|
83
|
+
if (existing) return existing;
|
|
84
|
+
const rawId = typeof item.id === "string" ? item.id : undefined;
|
|
85
|
+
if (!rawId) return null;
|
|
86
|
+
const mapped = state.placeholders[type].has(rawId)
|
|
87
|
+
? mintCanonicalId(type, state.scope, outputIndex)
|
|
88
|
+
: state.repairMissingTerminalIds
|
|
89
|
+
? rawId
|
|
90
|
+
: null;
|
|
91
|
+
if (!mapped) return null;
|
|
92
|
+
state.budget?.chargeRetained(new TextEncoder().encode(JSON.stringify([outputIndex, rawId, mapped])).byteLength, { kind: "item_ids" });
|
|
93
|
+
state.outputIds[type].set(outputIndex, mapped);
|
|
94
|
+
return mapped;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function rewriteOutputItem(
|
|
98
|
+
state: ResponsesItemIdRepairState,
|
|
99
|
+
outputIndex: number,
|
|
100
|
+
item: Record<string, unknown>,
|
|
101
|
+
): { item: Record<string, unknown>; changed: boolean } {
|
|
102
|
+
const mapped = rememberMappedId(state, outputIndex, item);
|
|
103
|
+
if (!mapped) return { item, changed: false };
|
|
104
|
+
const currentId = typeof item.id === "string" ? item.id : undefined;
|
|
105
|
+
if (currentId === mapped) return { item, changed: false };
|
|
106
|
+
if (currentId === undefined && !state.repairMissingTerminalIds) return { item, changed: false };
|
|
107
|
+
return { item: { ...item, id: mapped }, changed: true };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function rewriteItemIdField(
|
|
111
|
+
state: ResponsesItemIdRepairState,
|
|
112
|
+
event: Record<string, unknown>,
|
|
113
|
+
outputIndex: number,
|
|
114
|
+
): { event: Record<string, unknown>; changed: boolean } {
|
|
115
|
+
const eventType = typeof event.type === "string" ? ITEM_ID_EVENT_TYPES[event.type] : undefined;
|
|
116
|
+
if (!eventType) return { event, changed: false };
|
|
117
|
+
const mapped = state.outputIds[eventType].get(outputIndex);
|
|
118
|
+
if (!mapped) return { event, changed: false };
|
|
119
|
+
const currentId = typeof event.item_id === "string" ? event.item_id : undefined;
|
|
120
|
+
if (currentId === mapped) return { event, changed: false };
|
|
121
|
+
if (currentId === undefined && !state.repairMissingTerminalIds) return { event, changed: false };
|
|
122
|
+
return { event: { ...event, item_id: mapped }, changed: true };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function rewriteResponseSnapshot(
|
|
126
|
+
state: ResponsesItemIdRepairState,
|
|
127
|
+
response: Record<string, unknown>,
|
|
128
|
+
): { response: Record<string, unknown>; changed: boolean } {
|
|
129
|
+
if (!Array.isArray(response.output)) return { response, changed: false };
|
|
130
|
+
let changed = false;
|
|
131
|
+
const output = response.output.map((item, outputIndex) => {
|
|
132
|
+
if (!isPlainObject(item)) return item;
|
|
133
|
+
const rewritten = rewriteOutputItem(state, outputIndex, item);
|
|
134
|
+
changed = changed || rewritten.changed;
|
|
135
|
+
return rewritten.item;
|
|
136
|
+
});
|
|
137
|
+
return changed ? { response: { ...response, output }, changed: true } : { response, changed: false };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function repairEventPayload(
|
|
141
|
+
payload: string,
|
|
142
|
+
state: ResponsesItemIdRepairState,
|
|
143
|
+
): string {
|
|
144
|
+
let event: unknown;
|
|
145
|
+
try {
|
|
146
|
+
event = JSON.parse(payload);
|
|
147
|
+
} catch {
|
|
148
|
+
return payload;
|
|
149
|
+
}
|
|
150
|
+
if (!isPlainObject(event)) return payload;
|
|
151
|
+
|
|
152
|
+
let changed = false;
|
|
153
|
+
let nextEvent = event;
|
|
154
|
+
const outputIndex = asOutputIndex(event.output_index);
|
|
155
|
+
if (outputIndex !== null && isPlainObject(event.item)) {
|
|
156
|
+
const rewritten = rewriteOutputItem(state, outputIndex, event.item);
|
|
157
|
+
if (rewritten.changed) {
|
|
158
|
+
nextEvent = { ...nextEvent, item: rewritten.item };
|
|
159
|
+
changed = true;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (outputIndex !== null) {
|
|
163
|
+
const rewritten = rewriteItemIdField(state, nextEvent, outputIndex);
|
|
164
|
+
if (rewritten.changed) {
|
|
165
|
+
nextEvent = rewritten.event;
|
|
166
|
+
changed = true;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (isPlainObject(event.response)) {
|
|
170
|
+
const rewritten = rewriteResponseSnapshot(state, event.response);
|
|
171
|
+
if (rewritten.changed) {
|
|
172
|
+
nextEvent = { ...nextEvent, response: rewritten.response };
|
|
173
|
+
changed = true;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (!changed) return payload;
|
|
177
|
+
const rewritten = JSON.stringify(nextEvent);
|
|
178
|
+
const bytes = new TextEncoder().encode(rewritten).byteLength;
|
|
179
|
+
const reservation = state.budget?.reserveTransient(bytes, { kind: "item_ids" });
|
|
180
|
+
reservation?.commitRetained();
|
|
181
|
+
if (state.budget) queueMicrotask(() => state.budget?.releaseRetained(bytes, { kind: "item_ids" }));
|
|
182
|
+
return rewritten;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* [Decision Log]
|
|
187
|
+
* - 목적과 의도: 일부 openai-responses 호환 게이트웨이가 재사용/누락하는 message·reasoning item id를
|
|
188
|
+
* downstream SSE에서만 선택적으로 보정해 Codex Desktop 카드 상관관계를 안정화한다.
|
|
189
|
+
* - 기존 구현 및 제약 조건: 기본 passthrough는 바이트 단위 그대로 relay되고, local replay 상태는 raw
|
|
190
|
+
* upstream 응답을 기억한다. function_call id / call_id는 upstream 의미가 있으므로 절대 바꾸면 안 된다.
|
|
191
|
+
* - 검토한 주요 대안: 모든 passthrough SSE를 항상 재작성하기, raw inspect 분기까지 함께 재작성하기,
|
|
192
|
+
* function_call 포함 전체 item id를 정규화하기.
|
|
193
|
+
* - 선택한 방식: provider-local opt-in 설정이 있을 때만 client-facing SSE 분기에 한정해 exact
|
|
194
|
+
* message/reasoning placeholder id와 missing terminal id를 item type + output_index 기준으로 보정하고,
|
|
195
|
+
* event-level item_id는 명시적인 message/reasoning lifecycle allowlist에서만 바꾼다.
|
|
196
|
+
* - 다른 대안 대신 이 방식을 선택한 이유: disabled-by-default byte-for-byte passthrough를 유지하면서,
|
|
197
|
+
* previous_response_id replay는 raw upstream snapshot을 계속 사용해 synthetic id가 upstream으로
|
|
198
|
+
* 역류하지 않게 막을 수 있다.
|
|
199
|
+
* - 장점, 단점 및 영향: 기본 경로는 변하지 않는다. malformed stream이 output_index를 다른 item
|
|
200
|
+
* type에 재사용해도 function_call id/call_id는 보존된다. opt-in 게이트웨이는 sequential streams에서도
|
|
201
|
+
* 고유한 canonical id를 얻지만, 보정이 필요한 경우에만 JS stream 재작성 비용을 지불한다.
|
|
202
|
+
*/
|
|
203
|
+
/** Stateful payload rewrite for composition with other client-facing SSE transforms. */
|
|
204
|
+
export function createResponsesItemIdPayloadRewrite(
|
|
205
|
+
config: ResponsesItemIdRepairConfig,
|
|
206
|
+
budget?: TranslatorBudget,
|
|
207
|
+
): SsePayloadRewrite {
|
|
208
|
+
const state = createRepairState(config, budget);
|
|
209
|
+
return (payload) => repairEventPayload(payload, state);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function relaySseWithResponsesItemIdRepair(
|
|
213
|
+
body: ReadableStream<Uint8Array>,
|
|
214
|
+
config: ResponsesItemIdRepairConfig,
|
|
215
|
+
budget: TranslatorBudget,
|
|
216
|
+
): ReadableStream<Uint8Array> {
|
|
217
|
+
return relaySseWithPayloadRewrite(body, createResponsesItemIdPayloadRewrite(config, budget), budget);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function hasResponsesItemIdRepair(config: ResponsesItemIdRepairConfig | undefined): boolean {
|
|
221
|
+
return config?.repairMissingTerminalIds === true
|
|
222
|
+
|| (config?.message?.length ?? 0) > 0
|
|
223
|
+
|| (config?.reasoning?.length ?? 0) > 0;
|
|
224
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// AUTO-SPLIT facade: original responses.ts body moved into ./responses/* modules.
|
|
2
|
+
// Public surface preserved exactly; importers keep using "src/server/responses".
|
|
3
|
+
export { buildToolBridgeMaps, isV1CollabSurface, collabSurface, multiAgentGuidanceText, V2_GUIDANCE_CHAR_BUDGET, injectDeveloperMessage } from "./responses/collaboration";
|
|
4
|
+
export type { MultiAgentGuidanceOptions, MultiAgentGuidanceDeps } from "./responses/collaboration";
|
|
5
|
+
export { hasUnreadableEncryptedAgentTask, sanitizeEncryptedContentInPlace } from "./responses/encrypted-payload";
|
|
6
|
+
export { COMPACT_RESPONSE_MAX_BYTES, bufferCompactResponse, handleResponsesCompact } from "./responses/compact";
|
|
7
|
+
export { disableResponsesRequestTimeout, safeHostLabel, fetchWithHeaderTimeout } from "./responses/fetch-helpers";
|
|
8
|
+
export { sidecarOutcomeRecorder, isShadowSourceModel, codexLogAccountId, usesCodexForwardPoolAuth, codexForwardTerminalOutcomeRecorder, decodeRequestErrorResponse, buildComboChildHeaders, handleResponses, linkAbortSignal } from "./responses/core";
|
|
9
|
+
export { adapterNeedsForcedContinuation } from "./responses/core";
|