@iislee/opencodex 2.11.0 → 2.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS_INSTALL.md +109 -0
- package/README.md +114 -19
- package/bin/ocx.mjs +164 -36
- package/bin/package-main.mjs +1 -1
- package/gui/dist/assets/index-BF38heuV.js +104 -0
- package/gui/dist/assets/index-DMiI18Kv.css +1 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/alibaba-color.svg +1 -1
- package/gui/dist/provider-icons/antigravity-color.svg +1 -1
- package/gui/dist/provider-icons/claude-color.svg +1 -1
- package/gui/dist/provider-icons/cline-color.svg +16 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/commandcode-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -1
- package/gui/dist/provider-icons/cursor-color.svg +1 -1
- package/gui/dist/provider-icons/deepseek-color.svg +1 -1
- package/gui/dist/provider-icons/firepass-color.svg +1 -1
- package/gui/dist/provider-icons/fireworks-color.svg +1 -1
- package/gui/dist/provider-icons/gemini-color.svg +1 -1
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
- package/gui/dist/provider-icons/grok.svg +1 -1
- package/gui/dist/provider-icons/groq-color.svg +1 -1
- package/gui/dist/provider-icons/huggingface-color.svg +1 -1
- package/gui/dist/provider-icons/kimi-color.svg +1 -1
- package/gui/dist/provider-icons/kiro-color.svg +2 -2
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
- package/gui/dist/provider-icons/mistral-color.svg +1 -1
- package/gui/dist/provider-icons/moonshot-color.svg +1 -1
- package/gui/dist/provider-icons/nvidia-color.svg +1 -1
- package/gui/dist/provider-icons/ollama-color.svg +1 -1
- package/gui/dist/provider-icons/openai.svg +1 -1
- package/gui/dist/provider-icons/opencode.svg +2 -1
- package/gui/dist/provider-icons/openrouter-color.svg +1 -1
- package/gui/dist/provider-icons/pi.svg +2 -2
- package/gui/dist/provider-icons/qianfan-color.svg +1 -1
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/vllm-color.svg +1 -1
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
- package/package.json +19 -10
- package/src/adapters/anthropic-output-schema.ts +137 -0
- package/src/adapters/anthropic.ts +376 -52
- package/src/adapters/base.ts +54 -7
- package/src/adapters/client-fingerprint.ts +18 -12
- package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
- package/src/adapters/command-code.ts +601 -0
- package/src/adapters/cursor/checkpoint-store.ts +303 -0
- package/src/adapters/cursor/cursor-errors.ts +129 -6
- package/src/adapters/cursor/discovery.ts +61 -4
- package/src/adapters/cursor/effort-map.ts +27 -3
- package/src/adapters/cursor/framing.ts +39 -0
- package/src/adapters/cursor/h2-pool.ts +123 -0
- package/src/adapters/cursor/http1-bidi.ts +361 -0
- package/src/adapters/cursor/images.ts +704 -0
- package/src/adapters/cursor/live-models.ts +174 -58
- package/src/adapters/cursor/live-transport.ts +609 -170
- package/src/adapters/cursor/native-exec-common.ts +23 -2
- package/src/adapters/cursor/native-exec-fs.ts +10 -7
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +0 -1
- package/src/adapters/cursor/native-exec.ts +101 -14
- package/src/adapters/cursor/protobuf-events.ts +829 -11
- package/src/adapters/cursor/protobuf-request.ts +383 -65
- package/src/adapters/cursor/request-builder.ts +237 -31
- package/src/adapters/cursor/tool-definitions.ts +242 -12
- package/src/adapters/cursor/tool-result-normalize.ts +92 -0
- package/src/adapters/cursor/transport.ts +22 -0
- package/src/adapters/cursor/types.ts +28 -1
- package/src/adapters/cursor.ts +133 -7
- package/src/adapters/google-antigravity-replay.ts +558 -34
- package/src/adapters/google-antigravity-wire.ts +43 -10
- package/src/adapters/google-http.ts +38 -10
- package/src/adapters/google-tool-schema.ts +80 -15
- package/src/adapters/google-truncation.ts +11 -0
- package/src/adapters/google.ts +618 -74
- package/src/adapters/identity.ts +39 -6
- package/src/adapters/kiro-errors.ts +11 -0
- package/src/adapters/kiro-events.ts +19 -1
- package/src/adapters/kiro-thinking.ts +18 -2
- package/src/adapters/kiro-tools.ts +10 -1
- package/src/adapters/kiro.ts +92 -54
- package/src/adapters/mimo-free.ts +17 -0
- package/src/adapters/openai-chat-url.ts +11 -0
- package/src/adapters/openai-chat.ts +1311 -336
- package/src/adapters/openai-responses-url.ts +16 -0
- package/src/adapters/openai-responses.ts +830 -56
- package/src/adapters/registry.ts +175 -0
- package/src/adapters/responses-tool-schema.ts +67 -0
- package/src/adapters/tool-call-id.ts +119 -0
- package/src/adapters/tool-catalog-nudge.ts +104 -21
- package/src/adapters/xai-web-search.ts +185 -0
- package/src/bridge.ts +428 -61
- package/src/chat/inbound.ts +43 -19
- package/src/chat/outbound.ts +82 -26
- package/src/claude/agents-inject.ts +32 -9
- package/src/claude/context-windows.ts +21 -5
- package/src/claude/desktop-3p.ts +243 -9
- package/src/claude/gateway-cache.ts +41 -4
- package/src/claude/inbound.ts +72 -3
- package/src/claude/model-info.ts +38 -15
- package/src/claude/outbound.ts +70 -16
- package/src/cli/account-api.ts +35 -1
- package/src/cli/account-auth.ts +33 -6
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +389 -2
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +30 -6
- package/src/cli/agent.ts +45 -1
- package/src/cli/claude-agent-startup-sync.ts +73 -0
- package/src/cli/claude-desktop.ts +29 -4
- package/src/cli/claude.ts +103 -34
- package/src/cli/codex-log-guard-doctor.ts +103 -0
- package/src/cli/codex-shim-autorestore.ts +2 -0
- package/src/cli/codex-shim-readiness.ts +76 -0
- package/src/cli/combo.ts +8 -0
- package/src/cli/config-command.ts +74 -10
- package/src/cli/dispatch.ts +593 -0
- package/src/cli/doctor.ts +315 -43
- package/src/cli/ensure-desired-integrations.ts +152 -0
- package/src/cli/export-command.ts +46 -20
- package/src/cli/help.ts +20 -271
- package/src/cli/index.ts +378 -519
- package/src/cli/init.ts +4 -17
- package/src/cli/integrations.ts +120 -2
- package/src/cli/lab.ts +607 -0
- package/src/cli/launcher-context.ts +77 -0
- package/src/cli/minimax.ts +497 -0
- package/src/cli/models-runtime.ts +35 -2
- package/src/cli/models.ts +100 -14
- package/src/cli/observe.ts +92 -3
- package/src/cli/opencode.ts +4 -2
- package/src/cli/provider-runtime.ts +18 -1
- package/src/cli/provider.ts +24 -3
- package/src/cli/ready.ts +301 -0
- package/src/cli/registry.ts +437 -0
- package/src/cli/root.ts +86 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +6 -3
- package/src/cli/star-prompt.ts +71 -15
- package/src/cli/status.ts +10 -3
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +153 -6
- package/src/cli/v2.ts +105 -10
- package/src/cli.ts +1 -1
- package/src/clients/config-export.ts +1358 -21
- package/src/codex/account-label.ts +14 -1
- package/src/codex/account-lifecycle.ts +130 -13
- package/src/codex/account-namespaces.ts +49 -3
- package/src/codex/account-priority.ts +83 -0
- package/src/codex/account-store.ts +29 -2
- package/src/codex/account-usability.ts +25 -2
- package/src/codex/admission.ts +256 -0
- package/src/codex/affinity-debug.ts +162 -0
- package/src/codex/app-server-processes.ts +493 -106
- package/src/codex/app-server-restart-service.ts +232 -0
- package/src/codex/auth-api.ts +849 -242
- package/src/codex/auth-collision.ts +5 -3
- package/src/codex/auth-context.ts +345 -32
- package/src/codex/autostart-health.ts +8 -1
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +68 -10
- package/src/codex/catalog/bundled.ts +331 -33
- package/src/codex/catalog/effort.ts +121 -30
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +529 -45
- package/src/codex/catalog/native-models.ts +72 -0
- package/src/codex/catalog/parsing.ts +224 -30
- package/src/codex/catalog/provider-fetch.ts +1460 -134
- package/src/codex/catalog/sync.ts +1449 -186
- package/src/codex/catalog-admission.ts +199 -0
- package/src/codex/catalog-refresh-status.ts +105 -0
- package/src/codex/catalog-write-serialization.ts +242 -0
- package/src/codex/catalog.ts +6 -3
- package/src/codex/codex-write-lock.ts +384 -0
- package/src/codex/convergence-types.ts +614 -0
- package/src/codex/convergence.ts +651 -0
- package/src/codex/coordinator-doctor.ts +332 -0
- package/src/codex/custom-model-catalog-migration.ts +176 -0
- package/src/codex/desired-state.ts +230 -0
- package/src/codex/features.ts +636 -39
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +407 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-migration-guardian.ts +26 -20
- package/src/codex/history-provider.ts +231 -28
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +220 -0
- package/src/codex/inject-coordination.ts +290 -0
- package/src/codex/inject.ts +1073 -152
- package/src/codex/injected-marker.ts +37 -3
- package/src/codex/integration-record.ts +266 -0
- package/src/codex/internal/catalog-writer.ts +203 -0
- package/src/codex/internal/history-writer.ts +80 -0
- package/src/codex/journal.ts +66 -4
- package/src/codex/log-guard/inspect.ts +506 -0
- package/src/codex/log-guard/lock.ts +150 -0
- package/src/codex/log-guard/maintenance.ts +403 -0
- package/src/codex/log-guard/path-safety.ts +88 -0
- package/src/codex/log-guard/policy.ts +44 -0
- package/src/codex/log-guard/processes.ts +205 -0
- package/src/codex/log-guard/protection.ts +489 -0
- package/src/codex/log-guard/sqlite-errors.ts +9 -0
- package/src/codex/main-account-cache.ts +24 -0
- package/src/codex/main-account.ts +29 -1
- package/src/codex/management-convergence.ts +167 -0
- package/src/codex/model-cache.ts +56 -10
- package/src/codex/model-entitlements.ts +353 -0
- package/src/codex/native-main-admission.ts +47 -0
- package/src/codex/native-main-auth-temp.ts +187 -0
- package/src/codex/native-main-claim.ts +178 -0
- package/src/codex/native-main-lock-file.ts +162 -0
- package/src/codex/native-main-owner.ts +329 -0
- package/src/codex/native-profile-api.ts +247 -0
- package/src/codex/native-profile-manager.ts +1531 -0
- package/src/codex/native-profile-processes.ts +121 -0
- package/src/codex/native-profile-recovery.ts +99 -0
- package/src/codex/native-profile-stage-store.ts +387 -0
- package/src/codex/native-profile-startup.ts +492 -0
- package/src/codex/native-profile-store.ts +855 -0
- package/src/codex/native-profile-types.ts +120 -0
- package/src/codex/native-residue.ts +682 -0
- package/src/codex/paths.ts +80 -1
- package/src/codex/plan-from-token.ts +140 -0
- package/src/codex/plan.ts +40 -0
- package/src/codex/plugins-doctor.ts +1 -1
- package/src/codex/pool-rotation.ts +74 -4
- package/src/codex/project-config-warnings.ts +20 -6
- package/src/codex/prompt-journal.ts +352 -0
- package/src/codex/prompt-layers.ts +967 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/quota-rejection.ts +298 -0
- package/src/codex/quota.ts +175 -13
- package/src/codex/refresh.ts +11 -2
- package/src/codex/reset-credit-recovery.ts +1044 -0
- package/src/codex/routing.ts +505 -94
- package/src/codex/runtime.ts +159 -38
- package/src/codex/shim.ts +1009 -28
- package/src/codex/subagent-model-fallback.ts +350 -35
- package/src/codex/sync.ts +191 -2
- package/src/codex/transition-state.ts +612 -0
- package/src/codex/upstream-host-health.ts +368 -0
- package/src/codex/user-identity.ts +557 -0
- package/src/codex/warmup.ts +187 -81
- package/src/codex/write-coordination.ts +114 -0
- package/src/combos/failover.ts +20 -0
- package/src/combos/index.ts +4 -0
- package/src/combos/request.ts +32 -0
- package/src/combos/types.ts +81 -9
- package/src/config/provider-name.ts +24 -0
- package/src/config.ts +1762 -140
- package/src/generated/compatibility-version.json +3164 -0
- package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
- package/src/grok/inject.ts +16 -5
- package/src/grok/inspect.ts +45 -0
- package/src/grok/sync.ts +2 -2
- package/src/images/loop.ts +152 -29
- package/src/images/plan.ts +23 -13
- package/src/integrations/config-io.ts +269 -0
- package/src/integrations/journal.ts +315 -0
- package/src/integrations/merge.ts +135 -0
- package/src/integrations/mutation-flight.ts +71 -0
- package/src/integrations/native/ownership-preflight.ts +202 -0
- package/src/integrations/omp-yaml-source.ts +358 -0
- package/src/integrations/owned-refresh.ts +74 -0
- package/src/integrations/ownership.ts +111 -0
- package/src/integrations/registry.ts +159 -0
- package/src/integrations/serialize.ts +314 -0
- package/src/integrations/state.ts +361 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer-lock.ts +98 -0
- package/src/integrations/writer.ts +691 -0
- package/src/lab/artifacts/sanitize.ts +586 -0
- package/src/lab/artifacts/secure-fs.ts +475 -0
- package/src/lab/artifacts/store.ts +310 -0
- package/src/lab/automation/budgets.ts +78 -0
- package/src/lab/automation/config-persistence.ts +256 -0
- package/src/lab/automation/constants.ts +39 -0
- package/src/lab/automation/cooldown.ts +103 -0
- package/src/lab/automation/dispatch.ts +211 -0
- package/src/lab/automation/index.ts +13 -0
- package/src/lab/automation/orchestrator.ts +499 -0
- package/src/lab/automation/persistence.ts +512 -0
- package/src/lab/automation/planner.ts +371 -0
- package/src/lab/automation/policy.ts +136 -0
- package/src/lab/automation/queue.ts +191 -0
- package/src/lab/automation/recovery.ts +24 -0
- package/src/lab/automation/route-context.ts +21 -0
- package/src/lab/automation/run-key.ts +44 -0
- package/src/lab/automation/runs-query.ts +34 -0
- package/src/lab/automation/types.ts +160 -0
- package/src/lab/conformance/assertion.ts +325 -0
- package/src/lab/conformance/digest.ts +22 -0
- package/src/lab/conformance/executor.ts +741 -0
- package/src/lab/conformance/fixture-provider.ts +27 -0
- package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
- package/src/lab/conformance/harness-budget.ts +47 -0
- package/src/lab/conformance/index.ts +5 -0
- package/src/lab/conformance/jcs.ts +64 -0
- package/src/lab/conformance/json-pointer.ts +39 -0
- package/src/lab/conformance/manifest.ts +180 -0
- package/src/lab/conformance/mcp-stub.ts +179 -0
- package/src/lab/conformance/negative-controls.ts +164 -0
- package/src/lab/conformance/observation.ts +355 -0
- package/src/lab/conformance/runner.ts +68 -0
- package/src/lab/conformance/sse-normalize.ts +59 -0
- package/src/lab/conformance/suite-manifest.ts +78 -0
- package/src/lab/conformance/types.ts +214 -0
- package/src/lab/constants.ts +126 -0
- package/src/lab/digest.ts +64 -0
- package/src/lab/events/errors.ts +9 -0
- package/src/lab/events/limits.ts +117 -0
- package/src/lab/events/types.ts +229 -0
- package/src/lab/events/validate.ts +781 -0
- package/src/lab/fabric/constants.ts +40 -0
- package/src/lab/fabric/executor.ts +492 -0
- package/src/lab/fabric/index.ts +80 -0
- package/src/lab/fabric/manifest.ts +222 -0
- package/src/lab/fabric/observe.ts +489 -0
- package/src/lab/fabric/patch.ts +79 -0
- package/src/lab/fabric/producer-child.ts +139 -0
- package/src/lab/fabric/producer-isolate.ts +276 -0
- package/src/lab/fabric/producer-protocol.ts +61 -0
- package/src/lab/fabric/scratch.ts +439 -0
- package/src/lab/fabric/subject.ts +106 -0
- package/src/lab/fabric/types.ts +134 -0
- package/src/lab/fabric/verifier.ts +98 -0
- package/src/lab/index.ts +54 -0
- package/src/lab/ledger/artifact-refs.ts +127 -0
- package/src/lab/ledger/invalidation.ts +136 -0
- package/src/lab/ledger/purge.ts +310 -0
- package/src/lab/ledger/store.ts +532 -0
- package/src/lab/live/credential-lease.ts +53 -0
- package/src/lab/live/destination.ts +155 -0
- package/src/lab/live/executor.ts +336 -0
- package/src/lab/live/inert-tools.ts +56 -0
- package/src/lab/live/manifest.ts +85 -0
- package/src/lab/live/mcp-loopback.ts +57 -0
- package/src/lab/live/runner.ts +19 -0
- package/src/lab/live/sandbox.ts +61 -0
- package/src/lab/live/suite-manifest.ts +41 -0
- package/src/lab/live/transport.ts +118 -0
- package/src/lab/live/types.ts +197 -0
- package/src/lab/observe/from-conformance.ts +301 -0
- package/src/lab/observe/from-live.ts +117 -0
- package/src/lab/paths.ts +153 -0
- package/src/lab/projection/rebuild.ts +495 -0
- package/src/lab/projection/schema.ts +135 -0
- package/src/lab/projection/verdicts.ts +474 -0
- package/src/lab/projection/verification.ts +412 -0
- package/src/lab/public/bundle.ts +217 -0
- package/src/lab/public/community-authority.ts +175 -0
- package/src/lab/public/community-files.ts +29 -0
- package/src/lab/public/community.ts +479 -0
- package/src/lab/public/file-safety.ts +155 -0
- package/src/lab/public/ids.ts +26 -0
- package/src/lab/public/index.ts +16 -0
- package/src/lab/public/mutation-lock.ts +424 -0
- package/src/lab/public/operator.ts +353 -0
- package/src/lab/public/origin-purge.ts +79 -0
- package/src/lab/public/origin.ts +203 -0
- package/src/lab/public/privacy.ts +143 -0
- package/src/lab/public/private-file.ts +261 -0
- package/src/lab/public/project.ts +124 -0
- package/src/lab/public/purge-test-fault.ts +21 -0
- package/src/lab/public/purge.ts +223 -0
- package/src/lab/public/registry.ts +44 -0
- package/src/lab/public/revocation.ts +252 -0
- package/src/lab/public/signature.ts +243 -0
- package/src/lab/public/storage.ts +105 -0
- package/src/lab/public/strict-json.ts +206 -0
- package/src/lab/public/time.ts +26 -0
- package/src/lab/public/types.ts +172 -0
- package/src/lab/public/validate.ts +391 -0
- package/src/lab/query/catalog.ts +101 -0
- package/src/lab/query/connection.ts +107 -0
- package/src/lab/query/constants.ts +4 -0
- package/src/lab/query/cursor.ts +132 -0
- package/src/lab/query/dto-map.ts +277 -0
- package/src/lab/query/errors.ts +22 -0
- package/src/lab/query/freshness.ts +53 -0
- package/src/lab/query/index.ts +45 -0
- package/src/lab/query/latest-observation.ts +59 -0
- package/src/lab/query/passive-production.ts +159 -0
- package/src/lab/query/queries.ts +444 -0
- package/src/lab/query/types.ts +266 -0
- package/src/lab/subject/behavior-fingerprint.ts +77 -0
- package/src/lab/subject/installation-salt.ts +112 -0
- package/src/lab/subject/protocol-subject.ts +80 -0
- package/src/lab/subject/route-subject.ts +74 -0
- package/src/lib/app-owned-memory-stores.ts +22 -0
- package/src/lib/bounded-body.ts +153 -9
- package/src/lib/bun-runtime.ts +125 -12
- package/src/lib/bun-stream-caps.ts +13 -9
- package/src/lib/codex-restart-contract.ts +120 -0
- package/src/lib/config-ownership.ts +6 -2
- package/src/lib/destination-policy.ts +65 -1
- package/src/lib/errors.ts +44 -2
- package/src/lib/fabric-task-execution-authority.ts +7 -0
- package/src/lib/fabric-task-host.ts +29 -0
- package/src/lib/lab-activation.ts +223 -0
- package/src/lib/lab-live-execution-authority.ts +13 -0
- package/src/lib/lab-live-host.ts +30 -0
- package/src/lib/lab-live-pinned-sender.ts +56 -0
- package/src/lib/lab-live-route-production.ts +130 -0
- package/src/lib/lab-passive-linker-registration.ts +26 -0
- package/src/lib/local-management-attestation.ts +51 -0
- package/src/lib/local-management-capability.ts +100 -0
- package/src/lib/local-provider-reload-contract.ts +100 -0
- package/src/lib/optional-shutdown-hooks.ts +57 -0
- package/src/lib/pinned-http.ts +145 -26
- package/src/lib/process-control.ts +4 -1
- package/src/lib/provider-outbound.ts +49 -9
- package/src/lib/redact.ts +419 -3
- package/src/lib/self-launch-argv.ts +15 -0
- package/src/lib/server-resource-ownership.ts +71 -0
- package/src/lib/shadow-call.ts +35 -4
- package/src/lib/sse-decoder.ts +41 -0
- package/src/lib/state-store-registrations.ts +10 -2
- package/src/lib/system-restart-contract.ts +73 -0
- package/src/lib/token-estimate.ts +19 -2
- package/src/lib/tool-argument-integers.ts +202 -0
- package/src/lib/translator-budget.ts +44 -0
- package/src/lib/upstream-http-version.ts +57 -0
- package/src/lib/upstream-reachability.ts +95 -0
- package/src/lib/upstream-retry.ts +156 -3
- package/src/lib/windows-atomic-replace.ts +155 -0
- package/src/lib/windows-elevation.ts +70 -2
- package/src/lib/windows-secret-acl.ts +409 -69
- package/src/lib/windows-service-wrappers.ts +72 -0
- package/src/lib/windows-text.ts +106 -0
- package/src/lib/windows-user-principal.ts +341 -0
- package/src/lib/winsw.ts +33 -5
- package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
- package/src/oauth/account-import/index.ts +15 -0
- package/src/oauth/account-import/parser.ts +83 -0
- package/src/oauth/account-import/registry.ts +18 -0
- package/src/oauth/account-import/service.ts +75 -0
- package/src/oauth/account-import/types.ts +91 -0
- package/src/oauth/anthropic.ts +12 -1
- package/src/oauth/callback-server.ts +8 -2
- package/src/oauth/chatgpt.ts +12 -1
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +46 -5
- package/src/oauth/google-antigravity.ts +35 -3
- package/src/oauth/health.ts +20 -12
- package/src/oauth/index.ts +398 -66
- package/src/oauth/key-providers.ts +16 -0
- package/src/oauth/kimi.ts +16 -2
- package/src/oauth/kiro.ts +50 -6
- package/src/oauth/local-token-detect.ts +11 -2
- package/src/oauth/log.ts +3 -1
- package/src/oauth/login-cli.ts +88 -28
- package/src/oauth/nous.ts +798 -0
- package/src/oauth/store.ts +119 -21
- package/src/oauth/token-guardian.ts +9 -3
- package/src/pi/models.ts +2 -2
- package/src/providers/alibaba-region-migration.ts +1 -1
- package/src/providers/antigravity-models.ts +521 -31
- package/src/providers/base-url-choices.ts +10 -0
- package/src/providers/codex-capacity.ts +292 -0
- package/src/providers/command-code-efforts.ts +144 -0
- package/src/providers/context-cap.ts +22 -5
- package/src/providers/cursor-pool.ts +72 -0
- package/src/providers/derive.ts +253 -6
- package/src/providers/fastwire.ts +501 -0
- package/src/providers/free-directory.ts +10 -7
- package/src/providers/google-vertex-location.ts +14 -0
- package/src/providers/key-failover.ts +71 -3
- package/src/providers/label.ts +1 -1
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +115 -22
- package/src/providers/model-rename-migration.ts +255 -0
- package/src/providers/model-rename-startup.ts +28 -0
- package/src/providers/openai-sidecar.ts +72 -4
- package/src/providers/openai-tier-startup.ts +31 -2
- package/src/providers/openai-tiers.ts +119 -4
- package/src/providers/openai-virtual-models.ts +1 -0
- package/src/providers/opencode-zen-rate-limit.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +29 -0
- package/src/providers/quota.ts +1319 -38
- package/src/providers/registry.ts +1429 -111
- package/src/providers/request-pacing.ts +310 -0
- package/src/providers/service-tier.ts +277 -0
- package/src/providers/slug-codec.ts +42 -6
- package/src/providers/static-model-discovery.ts +86 -0
- package/src/providers/xai-responses-opt-in.ts +15 -0
- package/src/providers/xai-transport.ts +11 -4
- package/src/reasoning-effort.ts +49 -1
- package/src/responses/compaction.ts +26 -1
- package/src/responses/custom-tool-compat.ts +266 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/namespace-tool-compat.ts +355 -0
- package/src/responses/parser.ts +220 -38
- package/src/responses/provider-continuation.ts +98 -0
- package/src/responses/provider-opaque-metadata.ts +73 -0
- package/src/responses/reasoning-envelope.ts +9 -1
- package/src/responses/reasoning-replay-cache.ts +426 -0
- package/src/responses/schema.ts +7 -1
- package/src/responses/spill-store.ts +75 -10
- package/src/responses/state.ts +565 -27
- package/src/responses/thought-signature-replay.ts +347 -0
- package/src/responses/tool-search-compat.ts +301 -0
- package/src/responses/truncated-stop-reason.ts +60 -0
- package/src/router.ts +366 -30
- package/src/routing/analytics.ts +378 -0
- package/src/routing/capability.ts +244 -0
- package/src/routing/compatibility/assemble.ts +73 -0
- package/src/routing/compatibility/behavior.ts +278 -0
- package/src/routing/compatibility/catalog.ts +99 -0
- package/src/routing/compatibility/endpoint.ts +52 -0
- package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
- package/src/routing/compatibility/policy.ts +181 -0
- package/src/routing/compatibility/provider-slot.ts +56 -0
- package/src/routing/compatibility/reader.ts +110 -0
- package/src/routing/compatibility/subject.ts +191 -0
- package/src/routing/compatibility/types.ts +64 -0
- package/src/routing/compatibility/version.ts +104 -0
- package/src/routing/cost.ts +77 -0
- package/src/routing/evaluator.ts +495 -0
- package/src/routing/health.ts +412 -0
- package/src/routing/history/cursor.ts +43 -0
- package/src/routing/history/indexer.ts +605 -0
- package/src/routing/history/schema.ts +72 -0
- package/src/routing/profile-namespace.ts +15 -0
- package/src/routing/profile.ts +547 -0
- package/src/routing/quota.ts +145 -0
- package/src/routing/request-evidence.ts +45 -0
- package/src/routing/trace.ts +776 -0
- package/src/server/adapter-resolve.ts +2 -29
- package/src/server/auth-cors.ts +267 -46
- package/src/server/background-lifecycle.ts +182 -0
- package/src/server/chat-completions.ts +130 -56
- package/src/server/chat-native-sse.ts +331 -0
- package/src/server/chat-native.ts +426 -0
- package/src/server/claude-messages.ts +159 -43
- package/src/server/direct-local-http.ts +347 -0
- package/src/server/effort-policy.ts +18 -0
- package/src/server/github-copilot-responses-repair.ts +338 -0
- package/src/server/gui-static.ts +39 -10
- package/src/server/images.ts +94 -12
- package/src/server/index.ts +865 -181
- package/src/server/lifecycle.ts +284 -13
- package/src/server/live.ts +136 -17
- package/src/server/local-management-read-client.ts +90 -0
- package/src/server/local-provider-reload-client.ts +137 -0
- package/src/server/management/agent-settings-routes.ts +398 -103
- package/src/server/management/api-key-usage.ts +31 -5
- package/src/server/management/body.ts +6 -0
- package/src/server/management/combo-routes.ts +62 -24
- package/src/server/management/config-routes.ts +464 -51
- package/src/server/management/context.ts +80 -2
- package/src/server/management/integration-routes.ts +498 -0
- package/src/server/management/lab-automation-routes.ts +206 -0
- package/src/server/management/lab-routes.ts +563 -0
- package/src/server/management/logs-usage-routes.ts +101 -32
- package/src/server/management/model-routes.ts +189 -131
- package/src/server/management/model-rows.ts +163 -0
- package/src/server/management/native-integration-routes.ts +769 -0
- package/src/server/management/oauth-account-routes.ts +80 -4
- package/src/server/management/provider-capability-config.ts +48 -0
- package/src/server/management/provider-routes.ts +764 -157
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/routing-analytics-routes.ts +74 -0
- package/src/server/management/routing-profile-routes.ts +380 -0
- package/src/server/management/shared.ts +27 -11
- package/src/server/management/sidebar-routes.ts +47 -31
- package/src/server/management/storage-log-guard-routes.ts +186 -0
- package/src/server/management/sync-response.ts +69 -0
- package/src/server/management/system-restart.ts +288 -32
- package/src/server/management/system-routes.ts +77 -0
- package/src/server/management/usage-summary-cache.ts +9 -1
- package/src/server/management/vision-sidecar-options.ts +167 -0
- package/src/server/management/web-search-sidecar-options.ts +120 -0
- package/src/server/management-api.ts +115 -14
- package/src/server/management-auth.ts +220 -5
- package/src/server/passive-route-linker.ts +66 -0
- package/src/server/ports.ts +41 -1
- package/src/server/proxy-liveness.ts +132 -5
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +82 -42
- package/src/server/relay.ts +236 -76
- package/src/server/request-decompress.ts +113 -6
- package/src/server/request-log.ts +235 -22
- package/src/server/responses/agent-task-recovery-cache.ts +143 -0
- package/src/server/responses/agent-task-recovery.ts +465 -0
- package/src/server/responses/collaboration.ts +204 -35
- package/src/server/responses/compact.ts +442 -55
- package/src/server/responses/core.ts +2872 -331
- package/src/server/responses/empty-completion-guard.ts +276 -0
- package/src/server/responses/encrypted-payload.ts +62 -39
- package/src/server/responses/fetch-helpers.ts +79 -4
- package/src/server/responses/input-admission.ts +185 -0
- package/src/server/responses/pacing-overload.ts +13 -0
- package/src/server/responses/policy-fallback.ts +178 -0
- package/src/server/responses/responses-field-backfill.ts +251 -0
- package/src/server/responses/terminal-guard.ts +26 -5
- package/src/server/responses/upstream-error.ts +5 -0
- package/src/server/responses/ws-upstream.ts +308 -0
- package/src/server/responses-custom-tool-repair.ts +282 -0
- package/src/server/responses-item-id-repair.ts +54 -6
- package/src/server/responses-json-events.ts +90 -0
- package/src/server/responses-model-rewrite.ts +29 -0
- package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
- package/src/server/responses-snapshot-repair.ts +621 -0
- package/src/server/responses-terminal-repair.ts +342 -0
- package/src/server/responses-tool-search-repair.ts +267 -0
- package/src/server/responses-undeclared-tool-guard.ts +153 -0
- package/src/server/responses.ts +18 -2
- package/src/server/search.ts +78 -13
- package/src/server/sse-frame-buffer.ts +292 -0
- package/src/server/sse-payload-rewrite.ts +110 -22
- package/src/server/startup-action-control.ts +8 -1
- package/src/server/startup-health-cache.ts +19 -1
- package/src/server/system-env.ts +80 -9
- package/src/server/ws-bridge.ts +39 -38
- package/src/service-manager-probe.ts +892 -0
- package/src/service.ts +1111 -90
- package/src/sidecar/auth.ts +92 -0
- package/src/sidecar/candidates.ts +83 -0
- package/src/storage/cleanup.ts +2 -2
- package/src/storage/scanner.ts +1 -1
- package/src/storage/worker-lifecycle.ts +14 -14
- package/src/tray/windows-tray.ps1 +83 -9
- package/src/tray/windows.ts +43 -16
- package/src/types/accounts.ts +37 -0
- package/src/types/config.ts +845 -0
- package/src/types/provider.ts +545 -0
- package/src/types/request.ts +384 -0
- package/src/types/tools.ts +131 -0
- package/src/types/wire.ts +80 -0
- package/src/types.ts +104 -1236
- package/src/update/index.ts +32 -19
- package/src/update/job.ts +442 -67
- package/src/update/notify.ts +12 -6
- package/src/update/npm-cache-preflight.d.mts +47 -0
- package/src/update/npm-cache-preflight.mjs +201 -0
- package/src/update/transactional-install.d.mts +22 -0
- package/src/update/transactional-install.mjs +259 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +268 -16
- package/src/usage/log.ts +606 -41
- package/src/usage/summary.ts +177 -9
- package/src/usage/user-cost-overlay-reconciler.ts +313 -0
- package/src/usage/user-cost-overlays.ts +314 -0
- package/src/vision/anthropic-describe.ts +10 -6
- package/src/vision/backends.ts +97 -0
- package/src/vision/describe.ts +9 -3
- package/src/vision/eligibility.ts +250 -0
- package/src/vision/index.ts +238 -24
- package/src/vision/reasoning.ts +55 -0
- package/src/vision/routed-describe.ts +175 -0
- package/src/vision/timeout-bounds.ts +9 -0
- package/src/web-search/anthropic-executor.ts +13 -7
- package/src/web-search/backends.ts +108 -0
- package/src/web-search/exa-executor.ts +88 -0
- package/src/web-search/executor.ts +11 -3
- package/src/web-search/gemini-executor.ts +141 -0
- package/src/web-search/index.ts +150 -15
- package/src/web-search/loop.ts +279 -50
- package/src/web-search/parse.ts +125 -30
- package/src/web-search/sources.ts +60 -0
- package/src/web-search/xai-executor.ts +219 -0
- package/gui/dist/assets/index-DTpMHS4F.js +0 -67
- package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
- package/gui/dist/provider-icons/antigravity.svg +0 -1
- package/gui/dist/provider-icons/claude.svg +0 -1
- package/gui/dist/provider-icons/copilot.svg +0 -1
- package/gui/dist/provider-icons/cursor.svg +0 -2
- package/gui/dist/provider-icons/gemini.svg +0 -1
- package/gui/dist/provider-icons/grok-color.svg +0 -1
- package/gui/dist/provider-icons/kiro.svg +0 -14
- package/src/cli/internal-dispatch.ts +0 -20
|
@@ -117,7 +117,7 @@ export function analyzeTerminalTurn(parsed: OcxParsedRequest, events: readonly A
|
|
|
117
117
|
return { decision: "continue", reason: "suspicious_no_tool", assistantText: text, userText, hasToolCall };
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
function assistantMessageFromEvents(events: readonly AdapterEvent[]): OcxAssistantMessage | undefined {
|
|
120
|
+
function assistantMessageFromEvents(events: readonly AdapterEvent[], timestamp: number): OcxAssistantMessage | undefined {
|
|
121
121
|
let text = "";
|
|
122
122
|
let thinking = "";
|
|
123
123
|
let signature: string | undefined;
|
|
@@ -134,14 +134,18 @@ function assistantMessageFromEvents(events: readonly AdapterEvent[]): OcxAssista
|
|
|
134
134
|
}
|
|
135
135
|
if (text) content.push({ type: "text", text });
|
|
136
136
|
if (content.length === 0) return undefined;
|
|
137
|
-
return { role: "assistant", content, timestamp
|
|
137
|
+
return { role: "assistant", content, timestamp };
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
export function buildContinuationRequest(parsed: OcxParsedRequest, events: readonly AdapterEvent[]): OcxParsedRequest {
|
|
141
141
|
const messages = [...parsed.context.messages];
|
|
142
|
-
|
|
142
|
+
// One clock read for the whole rebuild. Two Date.now() calls could straddle a millisecond
|
|
143
|
+
// boundary, which made the retained-heartbeat contract test fail intermittently in CI on a
|
|
144
|
+
// 1ms skew between the assistant message and the nudge that follows it.
|
|
145
|
+
const timestamp = Date.now();
|
|
146
|
+
const assistant = assistantMessageFromEvents(events, timestamp);
|
|
143
147
|
if (assistant) messages.push(assistant);
|
|
144
|
-
messages.push({ role: "developer", content: TERMINAL_GUARD_NUDGE, timestamp
|
|
148
|
+
messages.push({ role: "developer", content: TERMINAL_GUARD_NUDGE, timestamp });
|
|
145
149
|
return { ...parsed, context: { ...parsed.context, messages } };
|
|
146
150
|
}
|
|
147
151
|
|
|
@@ -153,6 +157,16 @@ export interface GuardedEventStreamOptions {
|
|
|
153
157
|
maxAutoContinuations?: number;
|
|
154
158
|
}
|
|
155
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Events that are useful to the downstream stream consumer but carry no state used by the
|
|
162
|
+
* terminal-continuation decision or its rebuilt request. Keep them out of the retained event
|
|
163
|
+
* list so adapter liveness markers and arbitrarily large tool-argument fragments cannot make
|
|
164
|
+
* the guard's per-turn memory grow without adding any continuation semantics.
|
|
165
|
+
*/
|
|
166
|
+
export function isTerminalGuardPassthroughOnly(event: AdapterEvent): boolean {
|
|
167
|
+
return event.type === "heartbeat" || event.type === "tool_call_delta";
|
|
168
|
+
}
|
|
169
|
+
|
|
156
170
|
function mergeUsage(first: OcxUsage | undefined, second: OcxUsage | undefined): OcxUsage | undefined {
|
|
157
171
|
if (!first) return second;
|
|
158
172
|
if (!second) return first;
|
|
@@ -193,9 +207,16 @@ export async function* guardTerminalEventStream(options: GuardedEventStreamOptio
|
|
|
193
207
|
const seen: AdapterEvent[] = [];
|
|
194
208
|
let terminalSeen = false;
|
|
195
209
|
for await (const event of source) {
|
|
210
|
+
// Liveness markers and tool argument fragments are passed through to the bridge, but
|
|
211
|
+
// neither analyzeTerminalTurn nor buildContinuationRequest consumes them. Retaining the
|
|
212
|
+
// fragments would duplicate arbitrarily large argument payloads in `seen` for no effect.
|
|
213
|
+
if (isTerminalGuardPassthroughOnly(event)) {
|
|
214
|
+
yield event;
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
196
217
|
if (event.type === "done") {
|
|
197
218
|
terminalSeen = true;
|
|
198
|
-
const analysis = options.adapterName === "anthropic"
|
|
219
|
+
const analysis = (options.adapterName === "anthropic" || options.adapterName === "openai-chat")
|
|
199
220
|
? analyzeTerminalTurn(parsed, seen)
|
|
200
221
|
: { decision: "pass" as const };
|
|
201
222
|
const normalStop = event.stopReason !== "max_tokens" && event.stopReason !== "content_filter";
|
|
@@ -5,7 +5,12 @@
|
|
|
5
5
|
* #553 looking for an adapter URL bug that does not exist. Name the likely cause and the
|
|
6
6
|
* command that settles it.
|
|
7
7
|
*/
|
|
8
|
+
import { RequestPacingQueueOverloadError } from "../../providers/request-pacing";
|
|
9
|
+
|
|
8
10
|
export function describeUpstreamConnectFailure(err: unknown, connectMs: number): string {
|
|
11
|
+
// Local pacing admission is not a transport failure. Let the outer response boundary
|
|
12
|
+
// preserve its retryable 429 identity instead of laundering it into a 502.
|
|
13
|
+
if (err instanceof RequestPacingQueueOverloadError) throw err;
|
|
9
14
|
if (err instanceof Error && err.name === "TimeoutError") {
|
|
10
15
|
return `Provider connect timeout after ${connectMs}ms`;
|
|
11
16
|
}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
// Upstream WebSocket transport for the ChatGPT Codex backend.
|
|
2
|
+
//
|
|
3
|
+
// Why this exists: the Codex backend serves the responses_websockets path from
|
|
4
|
+
// a measurably faster queue than the plain SSE POST path. Measured 2026-08-12
|
|
5
|
+
// KST (same account, same payload, strictly sequential): gpt-5.6-luna TTFT p50
|
|
6
|
+
// ~1.0s over WS vs ~3.9s over SSE. Codex CLI itself defaults to the WS
|
|
7
|
+
// transport; opencodex previously always POSTed SSE, which is where its extra
|
|
8
|
+
// 2-3s of TTFT came from.
|
|
9
|
+
//
|
|
10
|
+
// The wrapper only swaps the transport. It dials wss:// with the same headers,
|
|
11
|
+
// sends the JSON body as a single `response.create` frame, and re-encodes the
|
|
12
|
+
// returned event frames as an SSE byte stream, so every downstream consumer
|
|
13
|
+
// (passthrough relay, adapter parsers, usage sniffing) is unchanged.
|
|
14
|
+
|
|
15
|
+
import { MAX_CLIENT_SSE_FRAME_BYTES } from "../sse-frame-buffer";
|
|
16
|
+
import { compareBunVersions } from "../../lib/bun-stream-caps";
|
|
17
|
+
|
|
18
|
+
const CODEX_RESPONSES_HTTP_URL = "https://chatgpt.com/backend-api/codex/responses";
|
|
19
|
+
const CODEX_RESPONSES_WS_URL = "wss://chatgpt.com/backend-api/codex/responses";
|
|
20
|
+
const WS_BETA = "responses_websockets=2026-02-06";
|
|
21
|
+
// If the 101 never arrives (network black hole), give SSE a chance well before
|
|
22
|
+
// the caller's connect timeout (default 200s) would fire.
|
|
23
|
+
const UPGRADE_DEADLINE_MS = 10_000;
|
|
24
|
+
// Keep the push-based WS transport inside the same memory envelope as the
|
|
25
|
+
// bounded SSE relays that consume this response. Unlike fetch response bodies,
|
|
26
|
+
// a WebSocket cannot be paused when a ReadableStream applies backpressure, so
|
|
27
|
+
// an upstream that outruns the consumer must be disconnected.
|
|
28
|
+
export const MAX_CODEX_WS_FRAME_BYTES = MAX_CLIENT_SSE_FRAME_BYTES;
|
|
29
|
+
export const MAX_CODEX_WS_QUEUE_BYTES = 8 * 1024 * 1024;
|
|
30
|
+
export const MIN_BOUNDED_CODEX_WS_BUN_VERSION = "1.4.0";
|
|
31
|
+
|
|
32
|
+
export type BunRuntimeIdentity = {
|
|
33
|
+
version: string;
|
|
34
|
+
versionWithSha: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type BunRuntimeGateInput = string | BunRuntimeIdentity;
|
|
38
|
+
|
|
39
|
+
const codexWsUpstreamResponses = new WeakSet<Response>();
|
|
40
|
+
|
|
41
|
+
/** True only for a successful Codex WebSocket upgrade, never an HTTP fallback. */
|
|
42
|
+
export function isCodexWsUpstreamResponse(response: Response): boolean {
|
|
43
|
+
return codexWsUpstreamResponses.has(response);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function currentBunRuntimeIdentity(): BunRuntimeIdentity {
|
|
47
|
+
return {
|
|
48
|
+
version: Bun.version,
|
|
49
|
+
versionWithSha: Bun.version_with_sha,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function boundedRelayVersion(input: BunRuntimeGateInput): string | null {
|
|
54
|
+
if (typeof input === "string") return input.trim() || null;
|
|
55
|
+
const numericVersion = input.version.trim();
|
|
56
|
+
const numericMatch = /^(\d+\.\d+\.\d+)$/.exec(numericVersion);
|
|
57
|
+
const detailedMatch = /^v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)\s+\([0-9a-fA-F]+\)$/.exec(
|
|
58
|
+
input.versionWithSha.trim(),
|
|
59
|
+
);
|
|
60
|
+
if (!numericMatch || !detailedMatch) return null;
|
|
61
|
+
const detailedNumeric = /^(\d+\.\d+\.\d+)/.exec(detailedMatch[1])?.[1];
|
|
62
|
+
return detailedNumeric === numericMatch[1] ? detailedMatch[1] : null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Bun 1.3.14 does not propagate a stalled HTTP response socket back to a JS
|
|
67
|
+
* ReadableStream producer on Windows. A real raw-TCP slow-client probe drained
|
|
68
|
+
* the entire upstream despite the eager relay queue; Bun 1.4.0-canary.1 stopped
|
|
69
|
+
* below one MiB. Prereleases still fail closed; release builds before 1.4.0
|
|
70
|
+
* fall back to HTTP SSE.
|
|
71
|
+
*/
|
|
72
|
+
export function bunSupportsBoundedCodexWsRelay(
|
|
73
|
+
runtime: BunRuntimeGateInput = currentBunRuntimeIdentity(),
|
|
74
|
+
): boolean {
|
|
75
|
+
const version = boundedRelayVersion(runtime);
|
|
76
|
+
if (!version) return false;
|
|
77
|
+
if (/^\d+\.\d+\.\d+-/.test(version.trim())) return false;
|
|
78
|
+
const comparison = compareBunVersions(version, MIN_BOUNDED_CODEX_WS_BUN_VERSION);
|
|
79
|
+
return comparison !== null && comparison >= 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function shouldUseCodexWsUpstream(
|
|
83
|
+
url: string,
|
|
84
|
+
init?: RequestInit,
|
|
85
|
+
runtime: BunRuntimeGateInput = currentBunRuntimeIdentity(),
|
|
86
|
+
): boolean {
|
|
87
|
+
if (!bunSupportsBoundedCodexWsRelay(runtime)) return false;
|
|
88
|
+
if (url !== CODEX_RESPONSES_HTTP_URL) return false;
|
|
89
|
+
if ((init?.method ?? "GET").toUpperCase() !== "POST") return false;
|
|
90
|
+
const body = init?.body;
|
|
91
|
+
if (typeof body !== "string") return false;
|
|
92
|
+
// Only root-level stream:true selects WS: JSON-mode calls keep the HTTP path
|
|
93
|
+
// because the WS path only speaks the event protocol, and a nested
|
|
94
|
+
// {"metadata":{"stream":true}} must not flip the transport. Parsing (not
|
|
95
|
+
// substring matching) also keeps whitespace-formatted bodies routable.
|
|
96
|
+
try {
|
|
97
|
+
const parsed = JSON.parse(body) as unknown;
|
|
98
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)
|
|
99
|
+
&& (parsed as Record<string, unknown>).stream === true;
|
|
100
|
+
} catch {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function codexWsUpstreamFetch(
|
|
106
|
+
url: string,
|
|
107
|
+
init: RequestInit,
|
|
108
|
+
sseFallback: typeof globalThis.fetch,
|
|
109
|
+
runtime: BunRuntimeGateInput = currentBunRuntimeIdentity(),
|
|
110
|
+
): Promise<Response> {
|
|
111
|
+
if (!bunSupportsBoundedCodexWsRelay(runtime)) {
|
|
112
|
+
return sseFallback(url, init);
|
|
113
|
+
}
|
|
114
|
+
const signal = init.signal ?? undefined;
|
|
115
|
+
if (signal?.aborted) {
|
|
116
|
+
return Promise.reject(signal.reason ?? new DOMException("The operation was aborted.", "AbortError"));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let frameText: string;
|
|
120
|
+
try {
|
|
121
|
+
const body = JSON.parse(init.body as string) as Record<string, unknown>;
|
|
122
|
+
// The WS create frame is implicitly streaming; the backend rejects the
|
|
123
|
+
// HTTP-only `stream` flag inside a frame.
|
|
124
|
+
delete body.stream;
|
|
125
|
+
frameText = JSON.stringify({ ...body, type: "response.create" });
|
|
126
|
+
} catch {
|
|
127
|
+
return sseFallback(url, init);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const headers: Record<string, string> = {};
|
|
131
|
+
new Headers(init.headers ?? {}).forEach((value, key) => {
|
|
132
|
+
// HTTP-body framing headers do not apply to a WS handshake.
|
|
133
|
+
if (key === "content-type" || key === "content-length" || key === "accept" || key === "accept-encoding") return;
|
|
134
|
+
headers[key] = value;
|
|
135
|
+
});
|
|
136
|
+
headers["openai-beta"] = headers["openai-beta"]
|
|
137
|
+
? headers["openai-beta"].includes("responses_websockets")
|
|
138
|
+
? headers["openai-beta"]
|
|
139
|
+
: `${headers["openai-beta"]}, ${WS_BETA}`
|
|
140
|
+
: WS_BETA;
|
|
141
|
+
// A genuine caller `originator` is already in these headers via the forward
|
|
142
|
+
// set. Never fabricate one here: pool/forward traffic must not impersonate
|
|
143
|
+
// Codex CLI, per the metadata-integrity contract. (The backend's fast lane
|
|
144
|
+
// keys on WS + originator, so callers without the tag simply keep their own
|
|
145
|
+
// provenance and scheduling.)
|
|
146
|
+
|
|
147
|
+
return new Promise<Response>((resolve, reject) => {
|
|
148
|
+
let ws: WebSocket;
|
|
149
|
+
try {
|
|
150
|
+
// Bun accepts per-handshake headers; the DOM lib types only list protocol arrays.
|
|
151
|
+
ws = new WebSocket(CODEX_RESPONSES_WS_URL, { headers } as unknown as string[]);
|
|
152
|
+
} catch {
|
|
153
|
+
resolve(sseFallback(url, init));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let opened = false;
|
|
158
|
+
let settledPreOpen = false;
|
|
159
|
+
let terminal = false;
|
|
160
|
+
let controller: ReadableStreamDefaultController<Uint8Array> | null = null;
|
|
161
|
+
const encoder = new TextEncoder();
|
|
162
|
+
|
|
163
|
+
const failStream = (message: string) => {
|
|
164
|
+
if (terminal) return;
|
|
165
|
+
terminal = true;
|
|
166
|
+
try { controller?.error(new Error(message)); } catch { /* stream already done */ }
|
|
167
|
+
try { ws.close(); } catch { /* already closing */ }
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const upgradeTimer = setTimeout(() => {
|
|
171
|
+
if (opened || settledPreOpen) return;
|
|
172
|
+
settledPreOpen = true;
|
|
173
|
+
try { ws.close(); } catch { /* already closing */ }
|
|
174
|
+
resolve(sseFallback(url, init));
|
|
175
|
+
}, UPGRADE_DEADLINE_MS);
|
|
176
|
+
|
|
177
|
+
const onAbort = () => {
|
|
178
|
+
if (!opened) {
|
|
179
|
+
if (settledPreOpen) return;
|
|
180
|
+
// Settle BEFORE close(): the close handler treats a pre-open close as
|
|
181
|
+
// an upgrade rejection and would dial the SSE fallback for a request
|
|
182
|
+
// the caller just cancelled.
|
|
183
|
+
settledPreOpen = true;
|
|
184
|
+
clearTimeout(upgradeTimer);
|
|
185
|
+
try { ws.close(); } catch { /* already closing */ }
|
|
186
|
+
reject(signal?.reason ?? new DOMException("The operation was aborted.", "AbortError"));
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (controller && !terminal) {
|
|
190
|
+
terminal = true;
|
|
191
|
+
// Mirror an aborted fetch: the body read rejects with the abort reason.
|
|
192
|
+
try { controller.error(signal?.reason ?? new DOMException("The operation was aborted.", "AbortError")); } catch { /* stream already done */ }
|
|
193
|
+
}
|
|
194
|
+
// Error the body before close(): test doubles and some runtimes dispatch
|
|
195
|
+
// close synchronously, and the caller's abort reason must stay authoritative.
|
|
196
|
+
try { ws.close(); } catch { /* already closing */ }
|
|
197
|
+
};
|
|
198
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
199
|
+
|
|
200
|
+
ws.addEventListener("open", () => {
|
|
201
|
+
if (settledPreOpen) return;
|
|
202
|
+
clearTimeout(upgradeTimer);
|
|
203
|
+
try {
|
|
204
|
+
ws.send(frameText);
|
|
205
|
+
} catch {
|
|
206
|
+
// send() throwing means the frame never left, so no upstream turn
|
|
207
|
+
// started and the SSE resend cannot double-generate. Falling back
|
|
208
|
+
// (instead of erroring a synthetic 200 body) keeps the pre-stream
|
|
209
|
+
// HTTP error/refresh/failover machinery in charge.
|
|
210
|
+
settledPreOpen = true;
|
|
211
|
+
try { ws.close(); } catch { /* already closing */ }
|
|
212
|
+
resolve(sseFallback(url, init));
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
opened = true;
|
|
216
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
217
|
+
start(c) { controller = c; },
|
|
218
|
+
cancel() { try { ws.close(); } catch { /* already closing */ } },
|
|
219
|
+
}, new ByteLengthQueuingStrategy({ highWaterMark: MAX_CODEX_WS_QUEUE_BYTES }));
|
|
220
|
+
const response = new Response(stream, {
|
|
221
|
+
status: 200,
|
|
222
|
+
// The 101 response headers (x-codex-*-reset-at quota hints) are not
|
|
223
|
+
// exposed by Bun's WebSocket; the periodic quota poller covers those.
|
|
224
|
+
headers: { "content-type": "text/event-stream; charset=utf-8" },
|
|
225
|
+
});
|
|
226
|
+
codexWsUpstreamResponses.add(response);
|
|
227
|
+
resolve(response);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
ws.addEventListener("message", (event) => {
|
|
231
|
+
if (!controller || terminal) return;
|
|
232
|
+
const text = typeof event.data === "string" ? event.data : "";
|
|
233
|
+
if (!text) return;
|
|
234
|
+
// UTF-8 byte length is always at least the JS string length. Reject this
|
|
235
|
+
// cheap lower bound before parsing so an obviously oversized frame does
|
|
236
|
+
// not create another large object graph.
|
|
237
|
+
if (text.length > MAX_CODEX_WS_FRAME_BYTES) {
|
|
238
|
+
failStream("codex websocket frame exceeds the response size limit");
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const encodedText = encoder.encode(text);
|
|
242
|
+
if (encodedText.byteLength > MAX_CODEX_WS_FRAME_BYTES) {
|
|
243
|
+
failStream("codex websocket frame exceeds the response size limit");
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
let type: unknown;
|
|
247
|
+
try { type = (JSON.parse(text) as { type?: unknown }).type; } catch { return; }
|
|
248
|
+
if (typeof type !== "string") return;
|
|
249
|
+
// Relay only the event surface the SSE path produces today. WS-only
|
|
250
|
+
// frames (codex.rate_limits, responsesapi.websocket_timing) are dropped
|
|
251
|
+
// so downstream clients see exactly the stream shape they always got.
|
|
252
|
+
if (!type.startsWith("response.") && type !== "error") return;
|
|
253
|
+
const prefix = encoder.encode(`event: ${type}\ndata: `);
|
|
254
|
+
const suffix = encoder.encode("\n\n");
|
|
255
|
+
const frameBytes = prefix.byteLength + encodedText.byteLength + suffix.byteLength;
|
|
256
|
+
if (frameBytes > MAX_CLIENT_SSE_FRAME_BYTES) {
|
|
257
|
+
failStream("codex websocket frame exceeds the response size limit");
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const availableBytes = controller.desiredSize ?? 0;
|
|
261
|
+
if (frameBytes > availableBytes) {
|
|
262
|
+
failStream("codex websocket response exceeded the buffered queue limit");
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
const sseFrame = new Uint8Array(frameBytes);
|
|
266
|
+
sseFrame.set(prefix);
|
|
267
|
+
sseFrame.set(encodedText, prefix.byteLength);
|
|
268
|
+
sseFrame.set(suffix, prefix.byteLength + encodedText.byteLength);
|
|
269
|
+
try {
|
|
270
|
+
controller.enqueue(sseFrame);
|
|
271
|
+
} catch {
|
|
272
|
+
failStream("codex websocket response stream closed while enqueueing");
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (type === "response.completed" || type === "response.failed" || type === "response.incomplete" || type === "error") {
|
|
276
|
+
terminal = true;
|
|
277
|
+
try { controller.close(); } catch { /* already closed */ }
|
|
278
|
+
try { ws.close(); } catch { /* already closing */ }
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
ws.addEventListener("close", () => {
|
|
283
|
+
signal?.removeEventListener("abort", onAbort);
|
|
284
|
+
if (!opened) {
|
|
285
|
+
if (settledPreOpen) return;
|
|
286
|
+
settledPreOpen = true;
|
|
287
|
+
clearTimeout(upgradeTimer);
|
|
288
|
+
// Upgrade rejected (401/403/429/5xx). Retry over plain SSE so the real
|
|
289
|
+
// HTTP status reaches the existing refresh/rotation handlers. No turn
|
|
290
|
+
// started upstream, so the resend cannot double-generate.
|
|
291
|
+
resolve(sseFallback(url, init));
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (controller && !terminal) {
|
|
295
|
+
terminal = true;
|
|
296
|
+
// Connection dropped before a Responses terminal event. A clean EOF
|
|
297
|
+
// here would reach clients with no response.completed/failed at all —
|
|
298
|
+
// relaySseWithFailedTail() only synthesizes a failed terminal when the
|
|
299
|
+
// body read THROWS. Error the stream like a reset TCP socket.
|
|
300
|
+
try { controller.error(new Error("codex websocket closed before a Responses terminal event")); } catch { /* stream already done */ }
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
ws.addEventListener("error", () => {
|
|
305
|
+
/* Bun always follows error with close; the close handler settles. */
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
}
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import type { TranslatorBudget } from "../lib/translator-budget";
|
|
2
|
+
import {
|
|
3
|
+
customToolItemId,
|
|
4
|
+
restoreRoutedCustomCalls,
|
|
5
|
+
routedCustomToolWireName,
|
|
6
|
+
unwrapRoutedCustomToolArguments,
|
|
7
|
+
} from "../responses/custom-tool-compat";
|
|
8
|
+
import {
|
|
9
|
+
replaceSseDataPayload,
|
|
10
|
+
sseDataPayload,
|
|
11
|
+
type SseBlockRewrite,
|
|
12
|
+
} from "./sse-payload-rewrite";
|
|
13
|
+
|
|
14
|
+
/** Exact compact prefix used by our upstream rewriter; progressive matching also
|
|
15
|
+
* tolerates insignificant JSON whitespace via FREEFORM_WRAP_PREFIX_RE. */
|
|
16
|
+
const FREEFORM_WRAP_PREFIX = '{"input":"';
|
|
17
|
+
const FREEFORM_WRAP_PREFIX_RE = /^\s*\{\s*"input"\s*:\s*"/;
|
|
18
|
+
|
|
19
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
20
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Progressive decode of the freeform `{ "input": "…" }` wrapper.
|
|
25
|
+
* Returns null when the accumulated text does not (yet) match that wrapper so
|
|
26
|
+
* callers suppress deltas and rely on `response.custom_tool_call_input.done`.
|
|
27
|
+
*/
|
|
28
|
+
function partialCustomToolInput(argumentsText: string): string | null {
|
|
29
|
+
const match = FREEFORM_WRAP_PREFIX_RE.exec(argumentsText);
|
|
30
|
+
if (!match) return null;
|
|
31
|
+
const body = argumentsText.slice(match[0]!.length);
|
|
32
|
+
let output = "";
|
|
33
|
+
for (let index = 0; index < body.length; index++) {
|
|
34
|
+
const char = body[index];
|
|
35
|
+
if (char === '"') break;
|
|
36
|
+
if (char !== "\\") {
|
|
37
|
+
output += char;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const escaped = body[index + 1];
|
|
41
|
+
if (escaped === undefined) break;
|
|
42
|
+
index += 1;
|
|
43
|
+
if (escaped === "n") output += "\n";
|
|
44
|
+
else if (escaped === "t") output += "\t";
|
|
45
|
+
else if (escaped === "r") output += "\r";
|
|
46
|
+
else if (escaped === "b") output += "\b";
|
|
47
|
+
else if (escaped === "f") output += "\f";
|
|
48
|
+
else if (escaped === "u") {
|
|
49
|
+
const hex = body.slice(index + 1, index + 5);
|
|
50
|
+
if (hex.length !== 4 || !/^[0-9a-fA-F]{4}$/.test(hex)) break;
|
|
51
|
+
output += String.fromCharCode(Number.parseInt(hex, 16));
|
|
52
|
+
index += 4;
|
|
53
|
+
} else output += escaped;
|
|
54
|
+
}
|
|
55
|
+
return output;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function replaceSseEventName(block: string, type: string): string {
|
|
59
|
+
const newline = block.includes("\r\n") ? "\r\n" : "\n";
|
|
60
|
+
const lines = block.split(/\r?\n/);
|
|
61
|
+
let replaced = false;
|
|
62
|
+
const next = lines.map(line => {
|
|
63
|
+
if (!replaced && line.startsWith("event:")) {
|
|
64
|
+
replaced = true;
|
|
65
|
+
return `event: ${type}`;
|
|
66
|
+
}
|
|
67
|
+
return line;
|
|
68
|
+
});
|
|
69
|
+
return next.join(newline);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
type OpenCustomCall = {
|
|
73
|
+
argumentsText: string;
|
|
74
|
+
emittedInput: string;
|
|
75
|
+
retainedBytes: number;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
type PendingArgumentBlock = {
|
|
79
|
+
block: string;
|
|
80
|
+
itemId?: string;
|
|
81
|
+
outputIndex?: number;
|
|
82
|
+
retainedBytes: number;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export function createRoutedCustomToolRestoreBlockRewrite(
|
|
86
|
+
names: ReadonlySet<string>,
|
|
87
|
+
budget?: TranslatorBudget,
|
|
88
|
+
): SseBlockRewrite {
|
|
89
|
+
const itemNames = new Map<string, string>();
|
|
90
|
+
const ordinaryItemIds = new Set<string>();
|
|
91
|
+
const openCalls = new Map<string, OpenCustomCall>();
|
|
92
|
+
let pendingArguments: PendingArgumentBlock[] = [];
|
|
93
|
+
let disposed = false;
|
|
94
|
+
|
|
95
|
+
const releaseCall = (itemId: string): void => {
|
|
96
|
+
const open = openCalls.get(itemId);
|
|
97
|
+
if (!open) return;
|
|
98
|
+
if (open.retainedBytes > 0) {
|
|
99
|
+
budget?.releaseRetained(open.retainedBytes, { kind: "retained_collectors" });
|
|
100
|
+
}
|
|
101
|
+
openCalls.delete(itemId);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const releaseAll = (): void => {
|
|
105
|
+
if (disposed) return;
|
|
106
|
+
disposed = true;
|
|
107
|
+
for (const itemId of openCalls.keys()) releaseCall(itemId);
|
|
108
|
+
const pendingBytes = pendingArguments.reduce((total, pending) => total + pending.retainedBytes, 0);
|
|
109
|
+
if (pendingBytes > 0) {
|
|
110
|
+
budget?.releaseRetained(pendingBytes, { kind: "retained_collectors" });
|
|
111
|
+
}
|
|
112
|
+
pendingArguments = [];
|
|
113
|
+
itemNames.clear();
|
|
114
|
+
ordinaryItemIds.clear();
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const retainPendingArgument = (
|
|
118
|
+
block: string,
|
|
119
|
+
itemId: string | undefined,
|
|
120
|
+
outputIndex: number | undefined,
|
|
121
|
+
): void => {
|
|
122
|
+
const retainedBytes = Buffer.byteLength(block, "utf8");
|
|
123
|
+
if (retainedBytes > 0) budget?.chargeRetained(retainedBytes, { kind: "retained_collectors" });
|
|
124
|
+
pendingArguments.push({ block, itemId, outputIndex, retainedBytes });
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const takePendingArguments = (
|
|
128
|
+
itemId: string | undefined,
|
|
129
|
+
outputIndex: number | undefined,
|
|
130
|
+
): string[] => {
|
|
131
|
+
const matched: PendingArgumentBlock[] = [];
|
|
132
|
+
const remaining: PendingArgumentBlock[] = [];
|
|
133
|
+
for (const pending of pendingArguments) {
|
|
134
|
+
const matches = pending.itemId !== undefined
|
|
135
|
+
? itemId !== undefined && pending.itemId === itemId
|
|
136
|
+
: outputIndex !== undefined && pending.outputIndex === outputIndex;
|
|
137
|
+
(matches ? matched : remaining).push(pending);
|
|
138
|
+
}
|
|
139
|
+
pendingArguments = remaining;
|
|
140
|
+
const retainedBytes = matched.reduce((total, pending) => total + pending.retainedBytes, 0);
|
|
141
|
+
if (retainedBytes > 0) {
|
|
142
|
+
budget?.releaseRetained(retainedBytes, { kind: "retained_collectors" });
|
|
143
|
+
}
|
|
144
|
+
// Index-matched entries carry no item id. Stamp the resolved id so replay
|
|
145
|
+
// classifies the event instead of buffering it a second time.
|
|
146
|
+
return matched.map(pending => {
|
|
147
|
+
if (pending.itemId !== undefined || itemId === undefined) return pending.block;
|
|
148
|
+
const payload = sseDataPayload(pending.block);
|
|
149
|
+
if (payload === null) return pending.block;
|
|
150
|
+
try {
|
|
151
|
+
const parsed: unknown = JSON.parse(payload);
|
|
152
|
+
if (!isPlainObject(parsed)) return pending.block;
|
|
153
|
+
return replaceSseDataPayload(pending.block, JSON.stringify({ ...parsed, item_id: itemId }));
|
|
154
|
+
} catch {
|
|
155
|
+
return pending.block;
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const rewrite: SseBlockRewrite = (block: string): readonly string[] => {
|
|
161
|
+
if (disposed) return [block];
|
|
162
|
+
const payload = sseDataPayload(block);
|
|
163
|
+
if (payload === null || payload === "[DONE]") return [block];
|
|
164
|
+
let parsed: unknown;
|
|
165
|
+
try {
|
|
166
|
+
parsed = JSON.parse(payload);
|
|
167
|
+
} catch {
|
|
168
|
+
return [block];
|
|
169
|
+
}
|
|
170
|
+
if (!isPlainObject(parsed)) return [block];
|
|
171
|
+
|
|
172
|
+
const type = typeof parsed.type === "string" ? parsed.type : "";
|
|
173
|
+
const outputIndex = typeof parsed.output_index === "number"
|
|
174
|
+
&& Number.isInteger(parsed.output_index)
|
|
175
|
+
&& parsed.output_index >= 0
|
|
176
|
+
? parsed.output_index
|
|
177
|
+
: undefined;
|
|
178
|
+
if (
|
|
179
|
+
(type === "response.output_item.added" || type === "response.output_item.done")
|
|
180
|
+
&& isPlainObject(parsed.item)
|
|
181
|
+
&& parsed.item.type === "function_call"
|
|
182
|
+
&& typeof parsed.item.name === "string"
|
|
183
|
+
) {
|
|
184
|
+
const upstreamItemId = typeof parsed.item.id === "string" ? parsed.item.id : undefined;
|
|
185
|
+
const wireName = routedCustomToolWireName(parsed.item);
|
|
186
|
+
const routed = wireName !== undefined && names.has(wireName);
|
|
187
|
+
if (upstreamItemId) {
|
|
188
|
+
if (routed) {
|
|
189
|
+
itemNames.set(upstreamItemId, parsed.item.name);
|
|
190
|
+
ordinaryItemIds.delete(upstreamItemId);
|
|
191
|
+
} else {
|
|
192
|
+
ordinaryItemIds.add(upstreamItemId);
|
|
193
|
+
}
|
|
194
|
+
if (routed && type === "response.output_item.added") {
|
|
195
|
+
openCalls.set(upstreamItemId, { argumentsText: "", emittedInput: "", retainedBytes: 0 });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const pending = takePendingArguments(upstreamItemId, outputIndex);
|
|
199
|
+
if (!routed) {
|
|
200
|
+
if (type === "response.output_item.done" && upstreamItemId) ordinaryItemIds.delete(upstreamItemId);
|
|
201
|
+
return [...pending, block];
|
|
202
|
+
}
|
|
203
|
+
if (upstreamItemId && pending.length > 0 && !openCalls.has(upstreamItemId)) {
|
|
204
|
+
openCalls.set(upstreamItemId, { argumentsText: "", emittedInput: "", retainedBytes: 0 });
|
|
205
|
+
}
|
|
206
|
+
const restored = restoreRoutedCustomCalls(parsed, names);
|
|
207
|
+
const restoredBlock = restored.changed
|
|
208
|
+
? replaceSseDataPayload(block, JSON.stringify(restored.value))
|
|
209
|
+
: block;
|
|
210
|
+
const replayed = pending.flatMap(pendingBlock => rewrite(pendingBlock));
|
|
211
|
+
if (type === "response.output_item.done" && upstreamItemId) releaseCall(upstreamItemId);
|
|
212
|
+
return type === "response.output_item.added"
|
|
213
|
+
? [restoredBlock, ...replayed]
|
|
214
|
+
: [...replayed, restoredBlock];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const upstreamItemId = typeof parsed.item_id === "string" ? parsed.item_id : undefined;
|
|
218
|
+
const argumentEvent = type === "response.function_call_arguments.delta"
|
|
219
|
+
|| type === "response.function_call_arguments.done";
|
|
220
|
+
if (argumentEvent && (!upstreamItemId || (!itemNames.has(upstreamItemId) && !ordinaryItemIds.has(upstreamItemId)))) {
|
|
221
|
+
retainPendingArgument(block, upstreamItemId, outputIndex);
|
|
222
|
+
return [];
|
|
223
|
+
}
|
|
224
|
+
if (
|
|
225
|
+
type === "response.function_call_arguments.delta"
|
|
226
|
+
&& upstreamItemId
|
|
227
|
+
&& itemNames.has(upstreamItemId)
|
|
228
|
+
) {
|
|
229
|
+
const open = openCalls.get(upstreamItemId) ?? { argumentsText: "", emittedInput: "", retainedBytes: 0 };
|
|
230
|
+
const delta = typeof parsed.delta === "string" ? parsed.delta : "";
|
|
231
|
+
const deltaBytes = Buffer.byteLength(delta, "utf8");
|
|
232
|
+
if (deltaBytes > 0) budget?.chargeRetained(deltaBytes, { kind: "retained_collectors" });
|
|
233
|
+
open.argumentsText += delta;
|
|
234
|
+
open.retainedBytes += deltaBytes;
|
|
235
|
+
openCalls.set(upstreamItemId, open);
|
|
236
|
+
// Still accumulating toward the compact wrapper, or an unrecognized shape:
|
|
237
|
+
// suppress progressive emission and let the done event carry input.
|
|
238
|
+
if (FREEFORM_WRAP_PREFIX.startsWith(open.argumentsText)) return [];
|
|
239
|
+
const fullInput = partialCustomToolInput(open.argumentsText);
|
|
240
|
+
if (fullInput === null) return [];
|
|
241
|
+
if (!fullInput.startsWith(open.emittedInput) || fullInput.length === open.emittedInput.length) return [];
|
|
242
|
+
const inputDelta = fullInput.slice(open.emittedInput.length);
|
|
243
|
+
open.emittedInput = fullInput;
|
|
244
|
+
const nextType = "response.custom_tool_call_input.delta";
|
|
245
|
+
const next = {
|
|
246
|
+
...parsed,
|
|
247
|
+
type: nextType,
|
|
248
|
+
item_id: customToolItemId(upstreamItemId),
|
|
249
|
+
delta: inputDelta,
|
|
250
|
+
};
|
|
251
|
+
return [replaceSseDataPayload(replaceSseEventName(block, nextType), JSON.stringify(next))];
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (
|
|
255
|
+
type === "response.function_call_arguments.done"
|
|
256
|
+
&& upstreamItemId
|
|
257
|
+
&& itemNames.has(upstreamItemId)
|
|
258
|
+
) {
|
|
259
|
+
const nextType = "response.custom_tool_call_input.done";
|
|
260
|
+
const source = typeof parsed.arguments === "string"
|
|
261
|
+
? parsed.arguments
|
|
262
|
+
: openCalls.get(upstreamItemId)?.argumentsText ?? "";
|
|
263
|
+
const { arguments: _arguments, ...rest } = parsed;
|
|
264
|
+
const next = {
|
|
265
|
+
...rest,
|
|
266
|
+
type: nextType,
|
|
267
|
+
item_id: customToolItemId(upstreamItemId),
|
|
268
|
+
input: unwrapRoutedCustomToolArguments(source),
|
|
269
|
+
};
|
|
270
|
+
return [replaceSseDataPayload(replaceSseEventName(block, nextType), JSON.stringify(next))];
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const restored = restoreRoutedCustomCalls(parsed, names);
|
|
274
|
+
const terminal = type === "response.completed" || type === "response.failed" || type === "response.incomplete";
|
|
275
|
+
if (terminal) releaseAll();
|
|
276
|
+
return restored.changed
|
|
277
|
+
? [replaceSseDataPayload(block, JSON.stringify(restored.value))]
|
|
278
|
+
: [block];
|
|
279
|
+
};
|
|
280
|
+
rewrite.dispose = releaseAll;
|
|
281
|
+
return rewrite;
|
|
282
|
+
}
|