@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
package/src/bridge.ts
CHANGED
|
@@ -1,12 +1,29 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
AdapterEvent,
|
|
3
|
+
OcxMessagePhase,
|
|
4
|
+
OcxProviderContinuationState,
|
|
5
|
+
OcxProviderOpaqueToolCallMetadata,
|
|
6
|
+
OcxReasoningReplayScopeRef,
|
|
7
|
+
OcxUsage,
|
|
8
|
+
} from "./types";
|
|
9
|
+
import { coerceIntegerToolArguments } from "./lib/tool-argument-integers";
|
|
2
10
|
import { adapterFailureFromMessage, classifyError, CYBER_POLICY_ERROR_CODE, isCyberPolicyCode, type OcxErrorPayload } from "./lib/errors";
|
|
3
11
|
import { encodeCompactionSummary } from "./responses/compaction";
|
|
12
|
+
import { isTruncatedStopReason, truncationReasonFor } from "./responses/truncated-stop-reason";
|
|
4
13
|
import { encodeReasoningEnvelope, type ReasoningEnvelope } from "./responses/reasoning-envelope";
|
|
14
|
+
import { rememberReasoningForCall } from "./responses/reasoning-replay-cache";
|
|
15
|
+
import {
|
|
16
|
+
rememberAndSerializeExtraContent,
|
|
17
|
+
rememberExtraContentForReplay,
|
|
18
|
+
awaitThoughtSignatureDurability,
|
|
19
|
+
} from "./responses/thought-signature-replay";
|
|
5
20
|
import { resolveStallTimeoutSec } from "./stall-timeout";
|
|
6
21
|
import { usageDisplayTotalTokens } from "./usage/totals";
|
|
22
|
+
import { appendSafeWebSearchSource, safeWebSearchSources } from "./web-search/sources";
|
|
7
23
|
import {
|
|
8
24
|
isTranslatorBudgetExceededError,
|
|
9
25
|
releaseTranslatedEvent,
|
|
26
|
+
createTranslatorBudget,
|
|
10
27
|
type TranslatorBudget,
|
|
11
28
|
type TranslatorBufferKind,
|
|
12
29
|
} from "./lib/translator-budget";
|
|
@@ -15,6 +32,13 @@ function uuid(): string {
|
|
|
15
32
|
return crypto.randomUUID().replace(/-/g, "");
|
|
16
33
|
}
|
|
17
34
|
|
|
35
|
+
/** Test-only: bound the abandoned-owned-budget watchdog delay (null restores). */
|
|
36
|
+
let ownedBudgetAbandonedMs = 10 * 60 * 1000;
|
|
37
|
+
const OWNED_BUDGET_ABANDONED_DEFAULT_MS = ownedBudgetAbandonedMs;
|
|
38
|
+
export function setOwnedBudgetAbandonedMsForTests(ms: number | null): void {
|
|
39
|
+
ownedBudgetAbandonedMs = ms ?? OWNED_BUDGET_ABANDONED_DEFAULT_MS;
|
|
40
|
+
}
|
|
41
|
+
|
|
18
42
|
function sseEvent(name: string, data: Record<string, unknown>): string {
|
|
19
43
|
return `event: ${name}\ndata: ${JSON.stringify(data)}\n\n`;
|
|
20
44
|
}
|
|
@@ -76,10 +100,11 @@ function responseError(status: number, type: string, message: string): OcxErrorP
|
|
|
76
100
|
* non-stream adapters degrade a bad payload to `{}`.
|
|
77
101
|
*/
|
|
78
102
|
function toolCallArgumentsUsable(args: string): boolean {
|
|
103
|
+
if (args.length === 0) return true;
|
|
79
104
|
const trimmed = args.trim();
|
|
80
|
-
if (!trimmed) return
|
|
105
|
+
if (!trimmed) return false;
|
|
81
106
|
try {
|
|
82
|
-
JSON.parse(
|
|
107
|
+
JSON.parse(args);
|
|
83
108
|
return true;
|
|
84
109
|
} catch {
|
|
85
110
|
return false;
|
|
@@ -107,13 +132,28 @@ function adapterFailureFromEvent(event: Extract<AdapterEvent, { type: "error" }>
|
|
|
107
132
|
export { adapterFailureFromMessage } from "./lib/errors";
|
|
108
133
|
|
|
109
134
|
/**
|
|
110
|
-
* Build the native `WebSearchAction::Search` payload from the queries that ran.
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
135
|
+
* Build the native `WebSearchAction::Search` payload from the queries that ran.
|
|
136
|
+
*
|
|
137
|
+
* Single query → `{ query, queries: [query] }`. Batch → `{ queries }` with NO singular
|
|
138
|
+
* `query`. Empty → `{ query: "", queries: [""] }`.
|
|
139
|
+
*
|
|
140
|
+
* The asymmetry is load-bearing in both directions. codex-rs prefers a non-empty `query`
|
|
141
|
+
* for the cell label and renders "<first> ..." only when `query` is ABSENT and
|
|
142
|
+
* `queries.len() > 1`, so adding `query` to a batch would collapse the plural ellipsis.
|
|
143
|
+
* Meanwhile DeepSeek's native Responses parser makes `queries` a required field, so a
|
|
144
|
+
* replayed one-term `web_search_call` — carried in the history of every subsequent turn
|
|
145
|
+
* — fails deserialization with `missing field 'queries'` and 400s the rest of the
|
|
146
|
+
* conversation (#930). Carrying both keys in the single case satisfies the strict parser
|
|
147
|
+
* without changing what codex-rs displays.
|
|
148
|
+
*
|
|
149
|
+
* This fixes items created from here on. History recorded before it is repaired at the
|
|
150
|
+
* replay boundary by `backfillWebSearchQueries()` in the Responses adapter.
|
|
114
151
|
*/
|
|
115
152
|
function webSearchAction(queries: string[]): Record<string, unknown> {
|
|
116
|
-
if (queries.length <= 1)
|
|
153
|
+
if (queries.length <= 1) {
|
|
154
|
+
const query = queries[0] ?? "";
|
|
155
|
+
return { type: "search", query, queries: [query] };
|
|
156
|
+
}
|
|
117
157
|
return { type: "search", queries };
|
|
118
158
|
}
|
|
119
159
|
|
|
@@ -128,7 +168,7 @@ export type ResponsesTerminalStatus = "completed" | "failed" | "incomplete";
|
|
|
128
168
|
export function bridgeToResponsesSSE(
|
|
129
169
|
events: AsyncIterable<AdapterEvent>,
|
|
130
170
|
modelId: string,
|
|
131
|
-
toolNsMap?: Map<string, { namespace: string; name: string }>,
|
|
171
|
+
toolNsMap?: Map<string, { namespace: string; name: string; freeform?: true }>,
|
|
132
172
|
freeformToolNames?: Set<string>,
|
|
133
173
|
toolSearchToolNames?: Set<string>,
|
|
134
174
|
onCancel?: () => void,
|
|
@@ -156,7 +196,27 @@ export function bridgeToResponsesSSE(
|
|
|
156
196
|
* from this callback instead of re-parsing the bridged SSE.
|
|
157
197
|
*/
|
|
158
198
|
onUsage?: (usage: OcxUsage | undefined) => void;
|
|
199
|
+
/** Request-visible tool names. When present, an upstream call outside this set fails closed. */
|
|
200
|
+
declaredToolNames?: ReadonlySet<string>;
|
|
201
|
+
/** Declared parameter schema per tool name; repairs integral-float integer args (#1611). */
|
|
202
|
+
toolParameterSchemas?: ReadonlyMap<string, Record<string, unknown>>;
|
|
203
|
+
/**
|
|
204
|
+
* Wire keep-alive shape. Codex-rs parses at the EVENT level (timeout(idle_timeout,
|
|
205
|
+
* stream.next()) over an eventsource_stream), so an SSE comment line dispatches no event
|
|
206
|
+
* and does NOT re-arm its idle timer — the keep-alive must be a typed frame the parser
|
|
207
|
+
* ignores via its catch-all (110 RCA, 30_patch-direction.md). grok-build's strict
|
|
208
|
+
* async-openai fork is the opposite: it dies on the unknown `response.heartbeat`
|
|
209
|
+
* variant but, being eventsource-based at the byte level, its idle handling tolerates
|
|
210
|
+
* comment lines. Default stays the typed frame; the grok surface opts into comments.
|
|
211
|
+
*/
|
|
212
|
+
heartbeatStyle?: "typed" | "comment";
|
|
159
213
|
translatorBudget?: TranslatorBudget;
|
|
214
|
+
/**
|
|
215
|
+
* Conversation identity for the reasoning replay cache (issue #950).
|
|
216
|
+
* Provider call ids are not globally unique; scoping by thread keeps one
|
|
217
|
+
* conversation's reasoning out of another's continuations.
|
|
218
|
+
*/
|
|
219
|
+
replayCacheScope?: OcxReasoningReplayScopeRef;
|
|
160
220
|
/**
|
|
161
221
|
* Test seam for the wire/stall beat loop. Production omits this and uses the
|
|
162
222
|
* global timers; injecting here must not change scheduling semantics.
|
|
@@ -167,6 +227,7 @@ export function bridgeToResponsesSSE(
|
|
|
167
227
|
};
|
|
168
228
|
},
|
|
169
229
|
): ReadableStream<Uint8Array> {
|
|
230
|
+
const replayCacheScope = options?.replayCacheScope;
|
|
170
231
|
const setBeatInterval = options?.timers?.setInterval ?? ((handler: () => void, ms: number) => setInterval(handler, ms));
|
|
171
232
|
const clearBeatInterval = options?.timers?.clearInterval ?? ((id: unknown) => clearInterval(id as ReturnType<typeof setInterval>));
|
|
172
233
|
// Freeform/custom tools (apply_patch) carry their body in `input`; the model is given a
|
|
@@ -208,7 +269,25 @@ export function bridgeToResponsesSSE(
|
|
|
208
269
|
try { const o = JSON.parse(args); return o && typeof o === "object" ? o : {}; } catch { return {}; }
|
|
209
270
|
};
|
|
210
271
|
const encoder = new TextEncoder();
|
|
211
|
-
|
|
272
|
+
// Default-budget safety net: omission is SAFE (default turn limits), never
|
|
273
|
+
// unbounded. Production callers always pass one; an owned default is disposed
|
|
274
|
+
// at terminal/cancel below.
|
|
275
|
+
const ownsBudget = !options?.translatorBudget;
|
|
276
|
+
const budget = options?.translatorBudget ?? createTranslatorBudget();
|
|
277
|
+
// Idempotent: safe to call at every stream-death path; disposal must come
|
|
278
|
+
// AFTER the final charges (emitDone), never inside reportTerminal.
|
|
279
|
+
const disposeOwnedBudget = () => { if (ownsBudget) budget.dispose(); };
|
|
280
|
+
// A dropped stream (never read, never cancelled) reaches no terminal path,
|
|
281
|
+
// so the owned budget would sit in liveBudgets for the process lifetime.
|
|
282
|
+
// One unref'd watchdog per owned budget bounds that to a timeout and clears
|
|
283
|
+
// itself on any settle (the delay is test-overridable).
|
|
284
|
+
const ownedWatchdog = ownsBudget
|
|
285
|
+
? setTimeout(() => disposeOwnedBudget(), ownedBudgetAbandonedMs)
|
|
286
|
+
: undefined;
|
|
287
|
+
ownedWatchdog?.unref?.();
|
|
288
|
+
const clearOwnedWatchdog = () => {
|
|
289
|
+
if (ownedWatchdog !== undefined) clearTimeout(ownedWatchdog);
|
|
290
|
+
};
|
|
212
291
|
const bytesOf = (value: string): number => Buffer.byteLength(value);
|
|
213
292
|
const appendString = (
|
|
214
293
|
previous: string,
|
|
@@ -219,7 +298,6 @@ export function bridgeToResponsesSSE(
|
|
|
219
298
|
): { value: string; bytes: number } => {
|
|
220
299
|
const fragmentBytes = bytesOf(fragment);
|
|
221
300
|
const nextBytes = previousBytes + fragmentBytes;
|
|
222
|
-
if (!budget) return { value: previous + fragment, bytes: nextBytes };
|
|
223
301
|
const scope = { kind, ...(callId ? { callId } : {}) };
|
|
224
302
|
const reservation = budget.reserveTransient(nextBytes, scope);
|
|
225
303
|
try {
|
|
@@ -257,12 +335,16 @@ export function bridgeToResponsesSSE(
|
|
|
257
335
|
if (terminalReported || clientCancelled || closed) return;
|
|
258
336
|
terminalReported = true;
|
|
259
337
|
try { options?.onTerminal?.(status); } catch { /* terminal metrics must not break the stream */ }
|
|
338
|
+
clearOwnedWatchdog();
|
|
260
339
|
};
|
|
261
340
|
// RC3 keep-alive: Codex's idle timer is timeout(idle_timeout, stream.next()) over an
|
|
262
|
-
// eventsource_stream
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
//
|
|
341
|
+
// eventsource_stream, which parses at the EVENT level — a comment-only frame dispatches no
|
|
342
|
+
// event, so it does NOT re-arm the timer (110 RCA). The default keep-alive is therefore a
|
|
343
|
+
// typed `response.heartbeat` frame the codex-rs parser ignores via `_ => Ok(None)`. The
|
|
344
|
+
// grok surface (strict async-openai decoder that dies on unknown variants) opts into SSE
|
|
345
|
+
// comment lines instead via options.heartbeatStyle. Emit whenever the *wire* has been
|
|
346
|
+
// silent, even if invisible adapter heartbeats are still flowing (web-search buffering +
|
|
347
|
+
// raw-byte progress). Upstream activity only resets the stall watchdog.
|
|
266
348
|
let upstreamActivity = false;
|
|
267
349
|
let wireActivity = false;
|
|
268
350
|
let beat: unknown;
|
|
@@ -289,6 +371,7 @@ export function bridgeToResponsesSSE(
|
|
|
289
371
|
return;
|
|
290
372
|
}
|
|
291
373
|
closed = true;
|
|
374
|
+
disposeOwnedBudget();
|
|
292
375
|
}
|
|
293
376
|
};
|
|
294
377
|
const emitDone = () => {
|
|
@@ -328,7 +411,9 @@ export function bridgeToResponsesSSE(
|
|
|
328
411
|
...(endTurn !== undefined ? { end_turn: endTurn } : {}),
|
|
329
412
|
});
|
|
330
413
|
|
|
331
|
-
const heartbeatFrame =
|
|
414
|
+
const heartbeatFrame = options?.heartbeatStyle === "comment"
|
|
415
|
+
? encoder.encode(': opencodex heartbeat\n\n')
|
|
416
|
+
: encoder.encode('event: response.heartbeat\ndata: {"type":"response.heartbeat"}\n\n');
|
|
332
417
|
let stallTicks = 0;
|
|
333
418
|
const stallSec = resolveStallTimeoutSec(options?.stallTimeoutSec);
|
|
334
419
|
const maxStallTicks = Math.ceil((stallSec * 1000) / heartbeatMs);
|
|
@@ -384,8 +469,15 @@ export function bridgeToResponsesSSE(
|
|
|
384
469
|
// encodeReasoningEnvelope: takeReasoningEnvelope's sig/red guard would drop txt-only.
|
|
385
470
|
let hiddenRawReasoningText = "";
|
|
386
471
|
let hiddenRawReasoningBytes = 0;
|
|
472
|
+
// Raw reasoning text flushed most recently, waiting for the tool call it
|
|
473
|
+
// preceded. Recorded into the replay cache on tool_call_start so a later
|
|
474
|
+
// continuation can re-attach it when history lost the reasoning item
|
|
475
|
+
// (issue #950). Kept until new reasoning/text arrives: parallel tool
|
|
476
|
+
// calls share the same preceding reasoning block.
|
|
477
|
+
let rawReasoningForNextToolCall = "";
|
|
387
478
|
const flushHiddenRawReasoning = () => {
|
|
388
479
|
if (!hiddenRawReasoningText) return;
|
|
480
|
+
rawReasoningForNextToolCall = hiddenRawReasoningText;
|
|
389
481
|
const previousBytes = hiddenRawReasoningBytes;
|
|
390
482
|
const encrypted = encodeReasoningEnvelope({ txt: hiddenRawReasoningText });
|
|
391
483
|
const reservation = budget?.reserveTransient(bytesOf(encrypted), { kind: "reasoning" });
|
|
@@ -400,11 +492,34 @@ export function bridgeToResponsesSSE(
|
|
|
400
492
|
retainFinishedItem(item as OutputItem, bytesOf(encrypted), "reasoning");
|
|
401
493
|
outputIndex++;
|
|
402
494
|
};
|
|
495
|
+
// Kiro reasoning round-trip. Kiro sends its encrypted blob at the END of a turn, while the
|
|
496
|
+
// assistant message is still open, so this CANNOT emit on arrival: the open message still
|
|
497
|
+
// owns `outputIndex` (it only advances on close), and an item emitted here would both reuse
|
|
498
|
+
// that index and land BEFORE the message — where the parser's backwards pairing drops it as
|
|
499
|
+
// orphaned. Stash it and flush after `done` has closed every open item instead.
|
|
500
|
+
let pendingKiroRedacted: string | undefined;
|
|
501
|
+
let pendingKiroRedactedBytes = 0;
|
|
502
|
+
const flushKiroRedactedReasoning = () => {
|
|
503
|
+
if (!pendingKiroRedacted) return;
|
|
504
|
+
const previousBytes = pendingKiroRedactedBytes;
|
|
505
|
+
const encrypted = encodeReasoningEnvelope({ krc: pendingKiroRedacted });
|
|
506
|
+
const reservation = budget?.reserveTransient(bytesOf(encrypted), { kind: "reasoning" });
|
|
507
|
+
pendingKiroRedacted = undefined;
|
|
508
|
+
pendingKiroRedactedBytes = 0;
|
|
509
|
+
reservation?.commitRetained();
|
|
510
|
+
budget?.releaseRetained(previousBytes, { kind: "reasoning" });
|
|
511
|
+
const itemId = `rs_${uuid()}`;
|
|
512
|
+
const item = { type: "reasoning", id: itemId, summary: [] as never[], encrypted_content: encrypted };
|
|
513
|
+
emit("response.output_item.added", { output_index: outputIndex, item });
|
|
514
|
+
emit("response.output_item.done", { output_index: outputIndex, item });
|
|
515
|
+
retainFinishedItem(item as OutputItem, bytesOf(encrypted), "reasoning");
|
|
516
|
+
outputIndex++;
|
|
517
|
+
};
|
|
403
518
|
// Full assistant text of a compaction turn (across message boundaries) — becomes the
|
|
404
519
|
// synthetic compaction item's payload on done.
|
|
405
520
|
let compactionText = "";
|
|
406
521
|
let compactionTextBytes = 0;
|
|
407
|
-
let currentToolCall: { itemId: string; outputIndex: number; callId: string; name: string; args: string; argsBytes: number; namespace?: string; freeform?: boolean; toolSearch?: boolean; inputEmitted?: string } | null = null;
|
|
522
|
+
let currentToolCall: { itemId: string; outputIndex: number; callId: string; name: string; args: string; argsBytes: number; namespace?: string; freeform?: boolean; toolSearch?: boolean; inputEmitted?: string; providerMetadata?: OcxProviderOpaqueToolCallMetadata } | null = null;
|
|
408
523
|
// Open native web-search cell (between begin and end). Holds the output index allocated on
|
|
409
524
|
// begin so the matching done reuses it; closed as `failed` if the stream terminates early.
|
|
410
525
|
let currentWebSearch: { itemId: string; eventId: string; outputIndex: number } | null = null;
|
|
@@ -412,17 +527,22 @@ export function bridgeToResponsesSSE(
|
|
|
412
527
|
// url_citation annotations on that message (the desktop app's Sources chip), then cleared so
|
|
413
528
|
// they bind to exactly one message. Deduped by URL across multiple searches in the turn.
|
|
414
529
|
let pendingWebSources: { url: string; title?: string }[] = [];
|
|
530
|
+
let pendingWebSourceBytes = 0;
|
|
531
|
+
const releasePendingWebSources = () => {
|
|
532
|
+
if (pendingWebSources.length === 0) return;
|
|
533
|
+
pendingWebSources = [];
|
|
534
|
+
budget?.releaseRetained(pendingWebSourceBytes, { kind: "tool_search_sources" });
|
|
535
|
+
pendingWebSourceBytes = 0;
|
|
536
|
+
};
|
|
415
537
|
const takeWebAnnotations = (): { type: string; url: string; title?: string; start_index: number; end_index: number }[] => {
|
|
416
538
|
if (pendingWebSources.length === 0) return [];
|
|
417
539
|
const anns = pendingWebSources.map(s => ({
|
|
418
540
|
type: "url_citation", url: s.url, ...(s.title ? { title: s.title } : {}), start_index: 0, end_index: 0,
|
|
419
541
|
}));
|
|
420
|
-
const sourceBytes = pendingWebSources.reduce((sum, source) => sum + bytesOf(JSON.stringify(source)), 0);
|
|
421
542
|
const annotationBytes = bytesOf(JSON.stringify(anns));
|
|
422
543
|
const reservation = budget?.reserveTransient(annotationBytes, { kind: "retained_collectors" });
|
|
423
|
-
pendingWebSources = [];
|
|
424
544
|
reservation?.commitRetained();
|
|
425
|
-
|
|
545
|
+
releasePendingWebSources();
|
|
426
546
|
return anns;
|
|
427
547
|
};
|
|
428
548
|
|
|
@@ -477,9 +597,17 @@ export function bridgeToResponsesSSE(
|
|
|
477
597
|
|
|
478
598
|
const closeCurrentRawReasoning = () => {
|
|
479
599
|
if (!currentRawReasoning) return;
|
|
600
|
+
rawReasoningForNextToolCall = currentRawReasoning.text;
|
|
601
|
+
emit("response.reasoning_summary_text.done", {
|
|
602
|
+
item_id: currentRawReasoning.itemId, output_index: currentRawReasoning.outputIndex, summary_index: 0, text: currentRawReasoning.text,
|
|
603
|
+
});
|
|
604
|
+
emit("response.reasoning_summary_part.done", {
|
|
605
|
+
item_id: currentRawReasoning.itemId, output_index: currentRawReasoning.outputIndex, summary_index: 0,
|
|
606
|
+
part: { type: "summary_text", text: currentRawReasoning.text },
|
|
607
|
+
});
|
|
480
608
|
const item = {
|
|
481
|
-
type: "reasoning", id: currentRawReasoning.itemId,
|
|
482
|
-
|
|
609
|
+
type: "reasoning", id: currentRawReasoning.itemId,
|
|
610
|
+
summary: [{ type: "summary_text", text: currentRawReasoning.text }],
|
|
483
611
|
};
|
|
484
612
|
emit("response.output_item.done", { output_index: currentRawReasoning.outputIndex, item });
|
|
485
613
|
retainFinishedItem(item as OutputItem, currentRawReasoning.textBytes, "reasoning");
|
|
@@ -492,7 +620,13 @@ export function bridgeToResponsesSSE(
|
|
|
492
620
|
// Empty input (no-arg tools like computer_use get_app_state / list_apps) must serialize as
|
|
493
621
|
// "{}", never "" — Codex echoes the call back as a function_call next turn, and JSON.parse("")
|
|
494
622
|
// would 400 the whole session ("invalid JSON arguments"), poisoning all later turns.
|
|
495
|
-
|
|
623
|
+
// #1611: Grok serializes integer arguments through a float, so `120000.0`
|
|
624
|
+
// reaches Codex and is REJECTED before the tool runs. Repair integral floats
|
|
625
|
+
// against the declared schema; a non-integral value stays an error.
|
|
626
|
+
const argsStr = coerceIntegerToolArguments(
|
|
627
|
+
currentToolCall.args || "{}",
|
|
628
|
+
options?.toolParameterSchemas?.get(currentToolCall.name),
|
|
629
|
+
);
|
|
496
630
|
// Finalize streamed function-call arguments so Codex commits the call (incl. MCP / computer_use).
|
|
497
631
|
if (!currentToolCall.freeform && !currentToolCall.toolSearch) {
|
|
498
632
|
emit("response.function_call_arguments.done", {
|
|
@@ -505,6 +639,9 @@ export function bridgeToResponsesSSE(
|
|
|
505
639
|
input: freeformInput(currentToolCall.args),
|
|
506
640
|
});
|
|
507
641
|
}
|
|
642
|
+
// Freeform tools serialize as custom_tool_call without extra_content; remember the
|
|
643
|
+
// signature server-side regardless so the replayed call can be re-signed (#1735).
|
|
644
|
+
void rememberExtraContentForReplay(currentToolCall.callId, currentToolCall.providerMetadata, replayCacheScope);
|
|
508
645
|
const item = currentToolCall.toolSearch
|
|
509
646
|
? {
|
|
510
647
|
type: "tool_search_call", id: currentToolCall.itemId,
|
|
@@ -522,6 +659,10 @@ export function bridgeToResponsesSSE(
|
|
|
522
659
|
call_id: currentToolCall.callId, name: currentToolCall.name,
|
|
523
660
|
arguments: argsStr, status: "completed",
|
|
524
661
|
...(currentToolCall.namespace ? { namespace: currentToolCall.namespace } : {}),
|
|
662
|
+
// Provider-opaque metadata (issue #1735) rides the item so a client that replays
|
|
663
|
+
// this history can hand the signature back on the part it belongs to. The proxy
|
|
664
|
+
// also remembers it server-side for clients that never echo extra_content.
|
|
665
|
+
...(rememberAndSerializeExtraContent(currentToolCall.callId, currentToolCall.providerMetadata, replayCacheScope).extra ?? {}),
|
|
525
666
|
};
|
|
526
667
|
emit("response.output_item.done", { output_index: currentToolCall.outputIndex, item });
|
|
527
668
|
retainFinishedItem(item as OutputItem);
|
|
@@ -539,6 +680,7 @@ export function bridgeToResponsesSSE(
|
|
|
539
680
|
const failCurrentToolCall = () => {
|
|
540
681
|
if (!currentToolCall) return;
|
|
541
682
|
const argsStr = currentToolCall.args || "{}";
|
|
683
|
+
void rememberExtraContentForReplay(currentToolCall.callId, currentToolCall.providerMetadata, replayCacheScope);
|
|
542
684
|
const item = currentToolCall.toolSearch
|
|
543
685
|
? {
|
|
544
686
|
type: "tool_search_call", id: currentToolCall.itemId,
|
|
@@ -556,6 +698,10 @@ export function bridgeToResponsesSSE(
|
|
|
556
698
|
call_id: currentToolCall.callId, name: currentToolCall.name,
|
|
557
699
|
arguments: argsStr, status: "incomplete",
|
|
558
700
|
...(currentToolCall.namespace ? { namespace: currentToolCall.namespace } : {}),
|
|
701
|
+
// An incomplete call can still be persisted and replayed (max_output_tokens), so it
|
|
702
|
+
// carries the same metadata as the completed item — otherwise SSE and buffered JSON
|
|
703
|
+
// would disagree about whether the signature survives.
|
|
704
|
+
...(rememberAndSerializeExtraContent(currentToolCall.callId, currentToolCall.providerMetadata, replayCacheScope).extra ?? {}),
|
|
559
705
|
};
|
|
560
706
|
emit("response.output_item.done", { output_index: currentToolCall.outputIndex, item });
|
|
561
707
|
retainFinishedItem(item as OutputItem);
|
|
@@ -652,6 +798,7 @@ export function bridgeToResponsesSSE(
|
|
|
652
798
|
handlingTranslatorOverflow = true;
|
|
653
799
|
abortCurrentToolCallForTranslatorOverflow();
|
|
654
800
|
currentWebSearch = null;
|
|
801
|
+
releasePendingWebSources();
|
|
655
802
|
const failure = adapterFailureFromEvent({
|
|
656
803
|
type: "error",
|
|
657
804
|
status: 502,
|
|
@@ -682,6 +829,7 @@ export function bridgeToResponsesSSE(
|
|
|
682
829
|
beat = undefined;
|
|
683
830
|
try { controller.close(); } catch { /* already closed */ }
|
|
684
831
|
closed = true;
|
|
832
|
+
disposeOwnedBudget();
|
|
685
833
|
gated = true;
|
|
686
834
|
stepping = false;
|
|
687
835
|
};
|
|
@@ -694,6 +842,15 @@ export function bridgeToResponsesSSE(
|
|
|
694
842
|
while (!terminated && !closed && emittedFrames === emittedAtStart) {
|
|
695
843
|
iteratorStarted = true;
|
|
696
844
|
const next = await it.next();
|
|
845
|
+
// A cancel during this await disposes the owned budget; a late event
|
|
846
|
+
// must never be processed or charged against it. Exit step() outright:
|
|
847
|
+
// falling into EOF synthesis would let closeCurrentMessage() charge
|
|
848
|
+
// finished-item retention against the disposed budget.
|
|
849
|
+
if (closed || clientCancelled) {
|
|
850
|
+
gated = true;
|
|
851
|
+
stepping = false;
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
697
854
|
if (next.done) { upstreamDone = true; break; }
|
|
698
855
|
const event = next.value;
|
|
699
856
|
let terminalEvent = false;
|
|
@@ -727,6 +884,7 @@ export function bridgeToResponsesSSE(
|
|
|
727
884
|
if (currentReasoning) closeCurrentReasoning();
|
|
728
885
|
if (currentRawReasoning) closeCurrentRawReasoning();
|
|
729
886
|
flushHiddenRawReasoning();
|
|
887
|
+
rawReasoningForNextToolCall = "";
|
|
730
888
|
if (currentToolCall) closeCurrentToolCall();
|
|
731
889
|
flushHiddenReasoningEnvelope();
|
|
732
890
|
break;
|
|
@@ -735,6 +893,10 @@ export function bridgeToResponsesSSE(
|
|
|
735
893
|
if (currentReasoning) closeCurrentReasoning();
|
|
736
894
|
if (currentRawReasoning) closeCurrentRawReasoning();
|
|
737
895
|
flushHiddenRawReasoning();
|
|
896
|
+
// Reasoning consumed by a REAL text turn, not a tool call: no cache target.
|
|
897
|
+
// Empty text deltas must not wipe reasoning that precedes a tool call
|
|
898
|
+
// (chat-completions providers emit empty content deltas mid-tool-turn).
|
|
899
|
+
if (event.text.length > 0) rawReasoningForNextToolCall = "";
|
|
738
900
|
if (currentToolCall) closeCurrentToolCall();
|
|
739
901
|
// Only flush on an explicit phase change. A later delta that omits `phase` must
|
|
740
902
|
// keep appending to the current message rather than wiping the earlier phase.
|
|
@@ -769,6 +931,12 @@ export function bridgeToResponsesSSE(
|
|
|
769
931
|
}
|
|
770
932
|
case "thinking_delta": {
|
|
771
933
|
if (options?.hideThinkingSummary) {
|
|
934
|
+
// The hidden branch returns early, so flush any raw reasoning
|
|
935
|
+
// that preceded the thinking block and clear the replay-cache
|
|
936
|
+
// candidate — otherwise a stale reasoning_raw_delta would be
|
|
937
|
+
// recorded for a LATER tool call (CodeRabbit on #971).
|
|
938
|
+
flushHiddenRawReasoning();
|
|
939
|
+
rawReasoningForNextToolCall = "";
|
|
772
940
|
({ value: hiddenThinkingText, bytes: hiddenThinkingBytes } = appendString(
|
|
773
941
|
hiddenThinkingText,
|
|
774
942
|
hiddenThinkingBytes,
|
|
@@ -780,6 +948,7 @@ export function bridgeToResponsesSSE(
|
|
|
780
948
|
if (currentMsg) closeCurrentMessage("commentary");
|
|
781
949
|
if (currentRawReasoning) closeCurrentRawReasoning();
|
|
782
950
|
flushHiddenRawReasoning();
|
|
951
|
+
if (event.thinking.length > 0) rawReasoningForNextToolCall = "";
|
|
783
952
|
if (currentToolCall) closeCurrentToolCall();
|
|
784
953
|
if (!currentReasoning) {
|
|
785
954
|
const itemId = `rs_${uuid()}`;
|
|
@@ -817,6 +986,12 @@ export function bridgeToResponsesSSE(
|
|
|
817
986
|
pendingRedacted.push(event.data);
|
|
818
987
|
break;
|
|
819
988
|
}
|
|
989
|
+
case "kiro_redacted_reasoning": {
|
|
990
|
+
// Stash only — see flushKiroRedactedReasoning. One blob per turn, so last wins.
|
|
991
|
+
pendingKiroRedactedBytes = replaceRetainedString(pendingKiroRedactedBytes, event.data, "reasoning");
|
|
992
|
+
pendingKiroRedacted = event.data;
|
|
993
|
+
break;
|
|
994
|
+
}
|
|
820
995
|
case "reasoning_raw_delta": {
|
|
821
996
|
if (options?.hideThinkingSummary) {
|
|
822
997
|
({ value: hiddenRawReasoningText, bytes: hiddenRawReasoningBytes } = appendString(
|
|
@@ -832,8 +1007,12 @@ export function bridgeToResponsesSSE(
|
|
|
832
1007
|
if (currentToolCall) closeCurrentToolCall();
|
|
833
1008
|
if (!currentRawReasoning) {
|
|
834
1009
|
const itemId = `rs_${uuid()}`;
|
|
835
|
-
const item = { type: "reasoning", id: itemId, summary: [] as
|
|
1010
|
+
const item = { type: "reasoning", id: itemId, summary: [] as { type: string; text: string }[] };
|
|
836
1011
|
emit("response.output_item.added", { output_index: outputIndex, item });
|
|
1012
|
+
emit("response.reasoning_summary_part.added", {
|
|
1013
|
+
item_id: itemId, output_index: outputIndex, summary_index: 0,
|
|
1014
|
+
part: { type: "summary_text", text: "" },
|
|
1015
|
+
});
|
|
837
1016
|
currentRawReasoning = { itemId, outputIndex, text: "", textBytes: 0 };
|
|
838
1017
|
}
|
|
839
1018
|
({ value: currentRawReasoning.text, bytes: currentRawReasoning.textBytes } = appendString(
|
|
@@ -842,9 +1021,9 @@ export function bridgeToResponsesSSE(
|
|
|
842
1021
|
event.text,
|
|
843
1022
|
"reasoning",
|
|
844
1023
|
));
|
|
845
|
-
emit("response.
|
|
1024
|
+
emit("response.reasoning_summary_text.delta", {
|
|
846
1025
|
item_id: currentRawReasoning.itemId, output_index: currentRawReasoning.outputIndex,
|
|
847
|
-
|
|
1026
|
+
summary_index: 0, delta: event.text,
|
|
848
1027
|
});
|
|
849
1028
|
break;
|
|
850
1029
|
}
|
|
@@ -853,12 +1032,34 @@ export function bridgeToResponsesSSE(
|
|
|
853
1032
|
if (currentReasoning) closeCurrentReasoning();
|
|
854
1033
|
if (currentRawReasoning) closeCurrentRawReasoning();
|
|
855
1034
|
flushHiddenRawReasoning();
|
|
1035
|
+
if (rawReasoningForNextToolCall) {
|
|
1036
|
+
rememberReasoningForCall(event.id, rawReasoningForNextToolCall, replayCacheScope);
|
|
1037
|
+
}
|
|
856
1038
|
if (currentToolCall) closeCurrentToolCall();
|
|
857
1039
|
const mapped = toolNsMap?.get(event.name);
|
|
858
1040
|
const realName = mapped?.name ?? event.name;
|
|
1041
|
+
if (options?.declaredToolNames && !options.declaredToolNames.has(event.name)) {
|
|
1042
|
+
const failure = responseError(
|
|
1043
|
+
502,
|
|
1044
|
+
"upstream_error",
|
|
1045
|
+
`routed provider emitted undeclared client tool "${event.name}"; only request-declared tools may be called`,
|
|
1046
|
+
);
|
|
1047
|
+
emit("response.failed", {
|
|
1048
|
+
response: {
|
|
1049
|
+
...responseSnapshot("failed", finishedItems),
|
|
1050
|
+
error: failure,
|
|
1051
|
+
last_error: failure,
|
|
1052
|
+
},
|
|
1053
|
+
});
|
|
1054
|
+
reportTerminal("failed");
|
|
1055
|
+
terminalEvent = true;
|
|
1056
|
+
break;
|
|
1057
|
+
}
|
|
859
1058
|
const ns = mapped?.namespace;
|
|
860
1059
|
const toolSearch = toolSearchToolNames?.has(realName) ?? false;
|
|
861
|
-
const freeform = !toolSearch && (
|
|
1060
|
+
const freeform = !toolSearch && (mapped
|
|
1061
|
+
? mapped.freeform === true
|
|
1062
|
+
: (freeformToolNames?.has(realName) ?? false));
|
|
862
1063
|
const itemId = `${toolSearch ? "tsc" : freeform ? "ctc" : "fc"}_${uuid()}`;
|
|
863
1064
|
const item = toolSearch
|
|
864
1065
|
? { type: "tool_search_call", id: itemId, call_id: event.id, execution: "client", arguments: {}, status: "in_progress" }
|
|
@@ -866,7 +1067,7 @@ export function bridgeToResponsesSSE(
|
|
|
866
1067
|
? { type: "custom_tool_call", id: itemId, call_id: event.id, name: realName, input: "", status: "in_progress" }
|
|
867
1068
|
: { type: "function_call", id: itemId, call_id: event.id, name: realName, arguments: "", status: "in_progress", ...(ns ? { namespace: ns } : {}) };
|
|
868
1069
|
emit("response.output_item.added", { output_index: outputIndex, item });
|
|
869
|
-
currentToolCall = { itemId, outputIndex, callId: event.id, name: realName, args: "", argsBytes: 0, namespace: ns, freeform, toolSearch };
|
|
1070
|
+
currentToolCall = { itemId, outputIndex, callId: event.id, name: realName, args: "", argsBytes: 0, namespace: ns, freeform, toolSearch, providerMetadata: event.providerMetadata };
|
|
870
1071
|
budget?.openCall(event.id);
|
|
871
1072
|
break;
|
|
872
1073
|
}
|
|
@@ -963,15 +1164,13 @@ export function bridgeToResponsesSSE(
|
|
|
963
1164
|
});
|
|
964
1165
|
currentWebSearch = { itemId: wsItemId2, eventId: event.id, outputIndex };
|
|
965
1166
|
}
|
|
966
|
-
|
|
1167
|
+
const safeSources = safeWebSearchSources(event.sources);
|
|
1168
|
+
closeCurrentWebSearch(event.status ?? "completed", event.queries, safeSources);
|
|
967
1169
|
// Queue this search's sources for the next assistant message (dedup by URL).
|
|
968
|
-
if (
|
|
969
|
-
const
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
seen.add(s.url);
|
|
973
|
-
chargeValue(s, "tool_search_sources");
|
|
974
|
-
pendingWebSources.push(s);
|
|
1170
|
+
if (safeSources.length > 0) {
|
|
1171
|
+
for (const source of safeSources) {
|
|
1172
|
+
if (appendSafeWebSearchSource(pendingWebSources, source)) {
|
|
1173
|
+
pendingWebSourceBytes += chargeValue(source, "tool_search_sources");
|
|
975
1174
|
}
|
|
976
1175
|
}
|
|
977
1176
|
}
|
|
@@ -984,10 +1183,18 @@ export function bridgeToResponsesSSE(
|
|
|
984
1183
|
flushHiddenRawReasoning();
|
|
985
1184
|
if (currentToolCall) closeCurrentToolCall();
|
|
986
1185
|
if (currentWebSearch) closeCurrentWebSearch("completed", []);
|
|
1186
|
+
releasePendingWebSources();
|
|
987
1187
|
// Redacted-only turns (or hidden thinking without a trailing signature event) still
|
|
988
1188
|
// need their envelope-only reasoning item so the blocks replay next turn.
|
|
989
1189
|
flushHiddenReasoningEnvelope();
|
|
990
|
-
|
|
1190
|
+
// After every close above, so the blob lands AFTER the assistant message it belongs
|
|
1191
|
+
// to and the parser's backwards pairing finds it.
|
|
1192
|
+
flushKiroRedactedReasoning();
|
|
1193
|
+
// Truncated turns must never install replacement history (#422). The buffered path
|
|
1194
|
+
// has always checked this; streaming emitted the item BEFORE reading stopReason, so
|
|
1195
|
+
// a max_tokens/content_filter turn shipped a half-written summary and then declared
|
|
1196
|
+
// itself incomplete — the same hazard, one branch over.
|
|
1197
|
+
if (options?.compaction && !isTruncatedStopReason(event.stopReason)) {
|
|
991
1198
|
// Exactly one compaction item per turn; codex-rs takes the first and fatals on 0.
|
|
992
1199
|
const item = {
|
|
993
1200
|
type: "compaction", id: `cmp_${uuid()}`,
|
|
@@ -997,14 +1204,21 @@ export function bridgeToResponsesSSE(
|
|
|
997
1204
|
retainFinishedItem(item as OutputItem, compactionTextBytes);
|
|
998
1205
|
outputIndex++;
|
|
999
1206
|
}
|
|
1000
|
-
|
|
1207
|
+
// Recognize every adapter's truncation vocabulary, not just the canonical pair.
|
|
1208
|
+
// Suppression and terminal status must agree: withholding the compaction item while
|
|
1209
|
+
// still reporting success hands codex-rs a completed response with zero compaction
|
|
1210
|
+
// items, which it treats as fatal.
|
|
1211
|
+
if (truncationReasonFor(event.stopReason)) {
|
|
1001
1212
|
// Upstream stopped before a normal completion. Surface as incomplete so the
|
|
1002
1213
|
// client can distinguish a truncated/filtered turn from a finished one.
|
|
1214
|
+
// #1926 gap 2: bound the window in which a handed-out thought signature is
|
|
1215
|
+
// not yet durable before the turn becomes externally terminal.
|
|
1216
|
+
await awaitThoughtSignatureDurability();
|
|
1003
1217
|
const response = {
|
|
1004
1218
|
...responseSnapshot("incomplete", finishedItems, event.endTurn),
|
|
1005
1219
|
usage: responsesUsage(event.usage),
|
|
1006
1220
|
incomplete_details: {
|
|
1007
|
-
reason: event.stopReason
|
|
1221
|
+
reason: truncationReasonFor(event.stopReason) ?? "content_filter",
|
|
1008
1222
|
},
|
|
1009
1223
|
};
|
|
1010
1224
|
// Cache max-output partials so previous_response_id replay can continue them;
|
|
@@ -1014,6 +1228,7 @@ export function bridgeToResponsesSSE(
|
|
|
1014
1228
|
emit("response.incomplete", { response });
|
|
1015
1229
|
reportTerminal("incomplete");
|
|
1016
1230
|
} else {
|
|
1231
|
+
await awaitThoughtSignatureDurability();
|
|
1017
1232
|
const response = { ...responseSnapshot("completed", finishedItems, event.endTurn), usage: responsesUsage(event.usage) };
|
|
1018
1233
|
options?.onCompletedResponse?.(response, event.providerState);
|
|
1019
1234
|
options?.onUsage?.(event.usage);
|
|
@@ -1032,8 +1247,10 @@ export function bridgeToResponsesSSE(
|
|
|
1032
1247
|
flushHiddenRawReasoning();
|
|
1033
1248
|
if (currentToolCall) failCurrentToolCall();
|
|
1034
1249
|
if (currentWebSearch) closeCurrentWebSearch("failed", []);
|
|
1250
|
+
releasePendingWebSources();
|
|
1035
1251
|
flushHiddenReasoningEnvelope();
|
|
1036
1252
|
options?.onUsage?.(event.usage);
|
|
1253
|
+
await awaitThoughtSignatureDurability();
|
|
1037
1254
|
emit("response.incomplete", {
|
|
1038
1255
|
response: {
|
|
1039
1256
|
...responseSnapshot("incomplete", finishedItems, event.endTurn),
|
|
@@ -1060,8 +1277,10 @@ export function bridgeToResponsesSSE(
|
|
|
1060
1277
|
flushHiddenRawReasoning();
|
|
1061
1278
|
if (currentToolCall) failCurrentToolCall();
|
|
1062
1279
|
if (currentWebSearch) closeCurrentWebSearch("failed", []);
|
|
1280
|
+
releasePendingWebSources();
|
|
1063
1281
|
const failure = adapterFailureFromEvent(event);
|
|
1064
1282
|
if (event.usage) options?.onUsage?.(event.usage);
|
|
1283
|
+
await awaitThoughtSignatureDurability();
|
|
1065
1284
|
emit("response.failed", {
|
|
1066
1285
|
response: {
|
|
1067
1286
|
...responseSnapshot("failed", finishedItems),
|
|
@@ -1093,6 +1312,7 @@ export function bridgeToResponsesSSE(
|
|
|
1093
1312
|
flushHiddenRawReasoning();
|
|
1094
1313
|
if (currentToolCall) failCurrentToolCall();
|
|
1095
1314
|
if (currentWebSearch) closeCurrentWebSearch("failed", []);
|
|
1315
|
+
releasePendingWebSources();
|
|
1096
1316
|
emit("response.failed", {
|
|
1097
1317
|
response: {
|
|
1098
1318
|
...responseSnapshot("failed", finishedItems),
|
|
@@ -1122,7 +1342,9 @@ export function bridgeToResponsesSSE(
|
|
|
1122
1342
|
flushHiddenRawReasoning();
|
|
1123
1343
|
if (currentToolCall) failCurrentToolCall();
|
|
1124
1344
|
if (currentWebSearch) closeCurrentWebSearch("failed", []);
|
|
1345
|
+
releasePendingWebSources();
|
|
1125
1346
|
options?.onUsage?.(undefined);
|
|
1347
|
+
await awaitThoughtSignatureDurability();
|
|
1126
1348
|
emit("response.incomplete", {
|
|
1127
1349
|
response: {
|
|
1128
1350
|
...responseSnapshot("incomplete", finishedItems),
|
|
@@ -1141,6 +1363,7 @@ export function bridgeToResponsesSSE(
|
|
|
1141
1363
|
/* already closed (e.g. client cancelled) */
|
|
1142
1364
|
}
|
|
1143
1365
|
closed = true;
|
|
1366
|
+
disposeOwnedBudget();
|
|
1144
1367
|
gated = true;
|
|
1145
1368
|
stepping = false;
|
|
1146
1369
|
};
|
|
@@ -1162,6 +1385,11 @@ export function bridgeToResponsesSSE(
|
|
|
1162
1385
|
flushHiddenRawReasoning();
|
|
1163
1386
|
if (currentToolCall) failCurrentToolCall();
|
|
1164
1387
|
if (currentWebSearch) closeCurrentWebSearch("failed", []);
|
|
1388
|
+
releasePendingWebSources();
|
|
1389
|
+
// #1926 gap 2 residual: this beat callback is synchronous, so the durability
|
|
1390
|
+
// barrier is not awaited on the stall-timeout kill path. The in-memory store is
|
|
1391
|
+
// already updated; only a crash between here and the queued write loses it,
|
|
1392
|
+
// which is the pre-#1926 status quo for an already-abnormal termination.
|
|
1165
1393
|
emit("response.incomplete", {
|
|
1166
1394
|
response: {
|
|
1167
1395
|
...responseSnapshot("incomplete", finishedItems),
|
|
@@ -1176,6 +1404,7 @@ export function bridgeToResponsesSSE(
|
|
|
1176
1404
|
beat = undefined;
|
|
1177
1405
|
try { controller.close(); } catch { /* already closed */ }
|
|
1178
1406
|
closed = true;
|
|
1407
|
+
disposeOwnedBudget();
|
|
1179
1408
|
return;
|
|
1180
1409
|
}
|
|
1181
1410
|
// Wire silence is independent of upstream adapter heartbeats.
|
|
@@ -1188,6 +1417,7 @@ export function bridgeToResponsesSSE(
|
|
|
1188
1417
|
emittedFrames++;
|
|
1189
1418
|
} catch {
|
|
1190
1419
|
closed = true;
|
|
1420
|
+
disposeOwnedBudget();
|
|
1191
1421
|
}
|
|
1192
1422
|
}, heartbeatMs);
|
|
1193
1423
|
};
|
|
@@ -1205,18 +1435,41 @@ export function bridgeToResponsesSSE(
|
|
|
1205
1435
|
// cancelled turn does not leak the upstream stream or keep draining tokens (RC2).
|
|
1206
1436
|
clientCancelled = true;
|
|
1207
1437
|
closed = true;
|
|
1438
|
+
clearOwnedWatchdog();
|
|
1208
1439
|
if (beat !== undefined) clearBeatInterval(beat);
|
|
1209
1440
|
cancelUpstreamOnce();
|
|
1441
|
+
releasePendingWebSources();
|
|
1442
|
+
disposeOwnedBudget();
|
|
1210
1443
|
},
|
|
1211
1444
|
});
|
|
1212
1445
|
}
|
|
1213
1446
|
|
|
1214
1447
|
export function buildResponseJSON(
|
|
1448
|
+
events: AdapterEvent[],
|
|
1449
|
+
modelId: string,
|
|
1450
|
+
options?: Parameters<typeof buildResponseJSONWithBudget>[2],
|
|
1451
|
+
): Record<string, unknown> {
|
|
1452
|
+
// Default-budget safety net: a caller that omits the budget gets a bounded
|
|
1453
|
+
// default (disposed with the call), never the unbounded append path.
|
|
1454
|
+
if (options?.translatorBudget) return buildResponseJSONWithBudget(events, modelId, options);
|
|
1455
|
+
const budget = createTranslatorBudget();
|
|
1456
|
+
try {
|
|
1457
|
+
return buildResponseJSONWithBudget(events, modelId, { ...options, translatorBudget: budget });
|
|
1458
|
+
} finally {
|
|
1459
|
+
budget.dispose();
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
function buildResponseJSONWithBudget(
|
|
1215
1464
|
events: AdapterEvent[],
|
|
1216
1465
|
modelId: string,
|
|
1217
1466
|
options?: {
|
|
1218
1467
|
hideThinkingSummary?: boolean;
|
|
1219
|
-
toolNsMap?: Map<string, { namespace: string; name: string }>;
|
|
1468
|
+
toolNsMap?: Map<string, { namespace: string; name: string; freeform?: true }>;
|
|
1469
|
+
/** Request-visible tool names. When present, an upstream call outside this set fails closed. */
|
|
1470
|
+
declaredToolNames?: ReadonlySet<string>;
|
|
1471
|
+
/** Declared parameter schema per tool name; repairs integral-float integer args (#1611). */
|
|
1472
|
+
toolParameterSchemas?: ReadonlyMap<string, Record<string, unknown>>;
|
|
1220
1473
|
freeformToolNames?: Set<string>;
|
|
1221
1474
|
toolSearchToolNames?: Set<string>;
|
|
1222
1475
|
/** Remote compaction v2 turn — append one synthetic compaction output item (see bridgeToResponsesSSE). */
|
|
@@ -1225,9 +1478,12 @@ export function buildResponseJSON(
|
|
|
1225
1478
|
/** Raw adapter-reported usage before wire normalization (see bridgeToResponsesSSE onUsage). */
|
|
1226
1479
|
onUsage?: (usage: OcxUsage | undefined) => void;
|
|
1227
1480
|
translatorBudget?: TranslatorBudget;
|
|
1481
|
+
/** Conversation identity for the reasoning replay cache (issue #950). */
|
|
1482
|
+
replayCacheScope?: OcxReasoningReplayScopeRef;
|
|
1228
1483
|
},
|
|
1229
1484
|
): Record<string, unknown> {
|
|
1230
1485
|
const responseId = `resp_${uuid()}`;
|
|
1486
|
+
const replayCacheScope = options?.replayCacheScope;
|
|
1231
1487
|
const output: OutputItem[] = [];
|
|
1232
1488
|
const budget = options?.translatorBudget;
|
|
1233
1489
|
const encoder = new TextEncoder();
|
|
@@ -1272,7 +1528,15 @@ export function buildResponseJSON(
|
|
|
1272
1528
|
let incompleteEvent: Extract<AdapterEvent, { type: "incomplete" }> | undefined;
|
|
1273
1529
|
let endTurn: boolean | undefined;
|
|
1274
1530
|
let stopReason: string | undefined;
|
|
1531
|
+
// The adapter's stop reason exactly as it arrived. `stopReason` above is deliberately narrowed
|
|
1532
|
+
// to the two reasons that map onto a Responses `incomplete_details`; the raw value is what the
|
|
1533
|
+
// truncation guard needs, because adapters disagree on vocabulary (`length`, `refusal`, ...).
|
|
1534
|
+
let rawStopReason: string | undefined;
|
|
1275
1535
|
let cleanDone = false;
|
|
1536
|
+
// Whether the adapter emitted ANY terminal (done/error/incomplete). Distinct from `cleanDone`,
|
|
1537
|
+
// which is only true for a `done` without a stop reason. A buffered turn whose adapter simply
|
|
1538
|
+
// stopped emitting has no terminal at all, and must not be reported as a success.
|
|
1539
|
+
let sawTerminal = false;
|
|
1276
1540
|
let compactionText = "";
|
|
1277
1541
|
let compactionTextBytes = 0;
|
|
1278
1542
|
|
|
@@ -1283,14 +1547,22 @@ export function buildResponseJSON(
|
|
|
1283
1547
|
let currentSummaryReasoningBytes = 0;
|
|
1284
1548
|
let currentRawReasoning = "";
|
|
1285
1549
|
let currentRawReasoningBytes = 0;
|
|
1550
|
+
// Same replay-cache handoff as the streaming path (issue #950): the most
|
|
1551
|
+
// recently flushed raw reasoning waits for the tool call it preceded.
|
|
1552
|
+
let rawReasoningForNextToolCall = "";
|
|
1286
1553
|
// Anthropic extended-thinking round-trip (batch): see bridgeToResponsesSSE counterpart.
|
|
1287
1554
|
let batchSignature: string | undefined;
|
|
1288
1555
|
let batchSignatureBytes = 0;
|
|
1289
1556
|
let batchRedacted: string[] = [];
|
|
1290
1557
|
let batchRedactedBytes = 0;
|
|
1558
|
+
// Kiro reasoning blob, held until after the trailing flushes so it lands AFTER the assistant
|
|
1559
|
+
// message (see the streaming path). Retained because it outlives releaseTranslatedEvent.
|
|
1560
|
+
let batchKiroRedacted: string | undefined;
|
|
1561
|
+
let batchKiroRedactedBytes = 0;
|
|
1291
1562
|
let currentToolCallId = "";
|
|
1292
1563
|
let currentToolCallName = "";
|
|
1293
1564
|
let currentToolCallArgs = "";
|
|
1565
|
+
let currentToolCallProviderMetadata: OcxProviderOpaqueToolCallMetadata | undefined;
|
|
1294
1566
|
let currentToolCallArgsBytes = 0;
|
|
1295
1567
|
// Web-search citations awaiting the next assistant message (attached as url_citation annotations).
|
|
1296
1568
|
let pendingWebSources: { url: string; title?: string }[] = [];
|
|
@@ -1306,6 +1578,7 @@ export function buildResponseJSON(
|
|
|
1306
1578
|
const flushText = (inferredPhase?: OcxMessagePhase) => {
|
|
1307
1579
|
if (!currentText) return;
|
|
1308
1580
|
const phase = currentTextPhase ?? inferredPhase;
|
|
1581
|
+
const sourceBytes = pendingWebSources.reduce((sum, source) => sum + bytesOf(JSON.stringify(source)), 0);
|
|
1309
1582
|
const annotations = pendingWebSources.map(s => ({
|
|
1310
1583
|
type: "url_citation", url: s.url, ...(s.title ? { title: s.title } : {}), start_index: 0, end_index: 0,
|
|
1311
1584
|
}));
|
|
@@ -1316,6 +1589,7 @@ export function buildResponseJSON(
|
|
|
1316
1589
|
...(phase ? { phase } : {}),
|
|
1317
1590
|
} as OutputItem;
|
|
1318
1591
|
pushOutput(item, currentTextBytes);
|
|
1592
|
+
budget?.releaseRetained(sourceBytes, { kind: "tool_search_sources" });
|
|
1319
1593
|
currentText = "";
|
|
1320
1594
|
currentTextBytes = 0;
|
|
1321
1595
|
currentTextPhase = undefined;
|
|
@@ -1350,6 +1624,7 @@ export function buildResponseJSON(
|
|
|
1350
1624
|
};
|
|
1351
1625
|
const flushRawReasoning = () => {
|
|
1352
1626
|
if (!currentRawReasoning) return;
|
|
1627
|
+
rawReasoningForNextToolCall = currentRawReasoning;
|
|
1353
1628
|
if (options?.hideThinkingSummary === true) {
|
|
1354
1629
|
// Same contract as the streaming path: no visible reasoning, txt-only envelope round-trip.
|
|
1355
1630
|
pushOutput({
|
|
@@ -1361,8 +1636,8 @@ export function buildResponseJSON(
|
|
|
1361
1636
|
return;
|
|
1362
1637
|
}
|
|
1363
1638
|
pushOutput({
|
|
1364
|
-
type: "reasoning", id: `rs_${uuid()}`,
|
|
1365
|
-
|
|
1639
|
+
type: "reasoning", id: `rs_${uuid()}`,
|
|
1640
|
+
summary: [{ type: "summary_text", text: currentRawReasoning }],
|
|
1366
1641
|
}, currentRawReasoningBytes, "reasoning");
|
|
1367
1642
|
currentRawReasoning = "";
|
|
1368
1643
|
currentRawReasoningBytes = 0;
|
|
@@ -1373,12 +1648,23 @@ export function buildResponseJSON(
|
|
|
1373
1648
|
const realName = mapped?.name ?? currentToolCallName;
|
|
1374
1649
|
const ns = mapped?.namespace;
|
|
1375
1650
|
const toolSearch = options?.toolSearchToolNames?.has(realName) ?? false;
|
|
1376
|
-
const freeform = !toolSearch && (
|
|
1651
|
+
const freeform = !toolSearch && (mapped
|
|
1652
|
+
? mapped.freeform === true
|
|
1653
|
+
: (options?.freeformToolNames?.has(realName) ?? false));
|
|
1654
|
+
// #1611: same integral-float repair as the streaming path. Keyed by the wire name
|
|
1655
|
+
// the request declared, which is the pre-namespace-mapping `currentToolCallName`.
|
|
1656
|
+
const coercedArgs = coerceIntegerToolArguments(
|
|
1657
|
+
currentToolCallArgs,
|
|
1658
|
+
options?.toolParameterSchemas?.get(currentToolCallName),
|
|
1659
|
+
);
|
|
1660
|
+
// Freeform tools serialize as custom_tool_call without extra_content; remember the
|
|
1661
|
+
// signature server-side regardless so the replayed call can be re-signed (#1735).
|
|
1662
|
+
void rememberExtraContentForReplay(currentToolCallId, currentToolCallProviderMetadata, replayCacheScope);
|
|
1377
1663
|
if (toolSearch) {
|
|
1378
1664
|
pushOutput({
|
|
1379
1665
|
type: "tool_search_call", id: `tsc_${uuid()}`,
|
|
1380
1666
|
call_id: currentToolCallId, execution: "client",
|
|
1381
|
-
arguments: parseArgsObj(
|
|
1667
|
+
arguments: parseArgsObj(coercedArgs), status,
|
|
1382
1668
|
});
|
|
1383
1669
|
} else if (freeform) {
|
|
1384
1670
|
pushOutput({
|
|
@@ -1390,23 +1676,35 @@ export function buildResponseJSON(
|
|
|
1390
1676
|
pushOutput({
|
|
1391
1677
|
type: "function_call", id: `fc_${uuid()}`,
|
|
1392
1678
|
call_id: currentToolCallId, name: realName,
|
|
1393
|
-
arguments:
|
|
1679
|
+
arguments: coercedArgs || "{}", status,
|
|
1394
1680
|
...(ns ? { namespace: ns } : {}),
|
|
1681
|
+
...(rememberAndSerializeExtraContent(currentToolCallId, currentToolCallProviderMetadata, replayCacheScope).extra ?? {}),
|
|
1395
1682
|
});
|
|
1396
1683
|
}
|
|
1397
1684
|
budget?.closeCall(currentToolCallId);
|
|
1398
1685
|
currentToolCallId = "";
|
|
1399
1686
|
currentToolCallName = "";
|
|
1687
|
+
currentToolCallProviderMetadata = undefined;
|
|
1400
1688
|
currentToolCallArgs = "";
|
|
1401
1689
|
currentToolCallArgsBytes = 0;
|
|
1402
1690
|
};
|
|
1403
1691
|
|
|
1404
1692
|
for (const e of events) {
|
|
1693
|
+
if (errorEvent) {
|
|
1694
|
+
// Match streaming: once the turn fails, later parallel calls must not become executable
|
|
1695
|
+
// completed output. Still release every retained event in order and preserve terminal usage.
|
|
1696
|
+
if (e.type === "error" || e.type === "incomplete" || e.type === "done") {
|
|
1697
|
+
usage = e.usage ?? usage;
|
|
1698
|
+
}
|
|
1699
|
+
if (budget) releaseTranslatedEvent(e, budget);
|
|
1700
|
+
continue;
|
|
1701
|
+
}
|
|
1405
1702
|
switch (e.type) {
|
|
1406
1703
|
case "assistant_boundary":
|
|
1407
1704
|
flushText("commentary");
|
|
1408
1705
|
flushSummaryReasoning();
|
|
1409
1706
|
flushRawReasoning();
|
|
1707
|
+
rawReasoningForNextToolCall = "";
|
|
1410
1708
|
flushToolCall();
|
|
1411
1709
|
break;
|
|
1412
1710
|
case "text_delta":
|
|
@@ -1415,6 +1713,9 @@ export function buildResponseJSON(
|
|
|
1415
1713
|
if (currentText && e.phase !== undefined && currentTextPhase !== e.phase) flushText("commentary");
|
|
1416
1714
|
if (currentSummaryReasoning) flushSummaryReasoning();
|
|
1417
1715
|
if (currentRawReasoning) flushRawReasoning();
|
|
1716
|
+
// Empty text deltas (batch chat responses always carry content, often "") must
|
|
1717
|
+
// not wipe reasoning that precedes a tool call (#950 non-streaming path).
|
|
1718
|
+
if (e.text.length > 0) rawReasoningForNextToolCall = "";
|
|
1418
1719
|
if (currentToolCallId) flushToolCall();
|
|
1419
1720
|
// Compaction turns keep the summary out of normal message output (replay dedup — see
|
|
1420
1721
|
// bridgeToResponsesSSE); it ships only inside the synthetic compaction item below.
|
|
@@ -1433,6 +1734,7 @@ export function buildResponseJSON(
|
|
|
1433
1734
|
case "thinking_delta":
|
|
1434
1735
|
if (currentText) flushText("commentary");
|
|
1435
1736
|
if (currentRawReasoning) flushRawReasoning();
|
|
1737
|
+
if (e.thinking.length > 0) rawReasoningForNextToolCall = "";
|
|
1436
1738
|
if (currentToolCallId) flushToolCall();
|
|
1437
1739
|
{
|
|
1438
1740
|
({ value: currentSummaryReasoning, bytes: currentSummaryReasoningBytes } = appendBatchString(
|
|
@@ -1455,6 +1757,16 @@ export function buildResponseJSON(
|
|
|
1455
1757
|
}
|
|
1456
1758
|
batchRedacted.push(e.data);
|
|
1457
1759
|
break;
|
|
1760
|
+
case "kiro_redacted_reasoning":
|
|
1761
|
+
// Stash only — pushed after the trailing flushes. One blob per turn, so last wins.
|
|
1762
|
+
{
|
|
1763
|
+
const dataBytes = bytesOf(e.data);
|
|
1764
|
+
budget?.chargeRetained(dataBytes, { kind: "reasoning" });
|
|
1765
|
+
if (batchKiroRedactedBytes > 0) budget?.releaseRetained(batchKiroRedactedBytes, { kind: "reasoning" });
|
|
1766
|
+
batchKiroRedactedBytes = dataBytes;
|
|
1767
|
+
}
|
|
1768
|
+
batchKiroRedacted = e.data;
|
|
1769
|
+
break;
|
|
1458
1770
|
case "reasoning_raw_delta":
|
|
1459
1771
|
if (currentText) flushText("commentary");
|
|
1460
1772
|
if (currentSummaryReasoning) flushSummaryReasoning();
|
|
@@ -1469,12 +1781,25 @@ export function buildResponseJSON(
|
|
|
1469
1781
|
if (currentText) flushText("commentary");
|
|
1470
1782
|
if (currentSummaryReasoning) flushSummaryReasoning();
|
|
1471
1783
|
if (currentRawReasoning) flushRawReasoning();
|
|
1784
|
+
if (rawReasoningForNextToolCall) {
|
|
1785
|
+
rememberReasoningForCall(e.id, rawReasoningForNextToolCall, replayCacheScope);
|
|
1786
|
+
}
|
|
1472
1787
|
flushToolCall();
|
|
1788
|
+
if (options?.declaredToolNames && !options.declaredToolNames.has(e.name)) {
|
|
1789
|
+
errorEvent = {
|
|
1790
|
+
type: "error",
|
|
1791
|
+
message: `routed provider emitted undeclared client tool "${e.name}"; only request-declared tools may be called`,
|
|
1792
|
+
status: 502,
|
|
1793
|
+
errorType: "upstream_error",
|
|
1794
|
+
};
|
|
1795
|
+
break;
|
|
1796
|
+
}
|
|
1473
1797
|
currentToolCallId = e.id;
|
|
1474
1798
|
budget?.openCall(e.id);
|
|
1475
1799
|
currentToolCallName = e.name;
|
|
1476
1800
|
currentToolCallArgs = "";
|
|
1477
1801
|
currentToolCallArgsBytes = 0;
|
|
1802
|
+
currentToolCallProviderMetadata = e.providerMetadata;
|
|
1478
1803
|
break;
|
|
1479
1804
|
case "tool_call_delta":
|
|
1480
1805
|
{
|
|
@@ -1489,7 +1814,9 @@ export function buildResponseJSON(
|
|
|
1489
1814
|
const mapped = options?.toolNsMap?.get(currentToolCallName);
|
|
1490
1815
|
const realName = mapped?.name ?? currentToolCallName;
|
|
1491
1816
|
const toolSearch = options?.toolSearchToolNames?.has(realName) ?? false;
|
|
1492
|
-
const freeform = !toolSearch && (
|
|
1817
|
+
const freeform = !toolSearch && (mapped
|
|
1818
|
+
? mapped.freeform === true
|
|
1819
|
+
: (options?.freeformToolNames?.has(realName) ?? false));
|
|
1493
1820
|
if (!freeform && !toolSearch) {
|
|
1494
1821
|
flushToolCall("incomplete");
|
|
1495
1822
|
errorEvent = {
|
|
@@ -1507,60 +1834,90 @@ export function buildResponseJSON(
|
|
|
1507
1834
|
// Batch/non-streaming output has no in_progress phase to animate — the search cell is a
|
|
1508
1835
|
// single finalized item, emitted on `end`. Begin is a no-op here.
|
|
1509
1836
|
break;
|
|
1510
|
-
case "web_search_call_end":
|
|
1837
|
+
case "web_search_call_end": {
|
|
1511
1838
|
if (currentText) flushText("commentary");
|
|
1512
1839
|
if (currentSummaryReasoning) flushSummaryReasoning();
|
|
1513
1840
|
if (currentRawReasoning) flushRawReasoning();
|
|
1514
1841
|
flushToolCall();
|
|
1842
|
+
const safeSources = safeWebSearchSources(e.sources);
|
|
1515
1843
|
pushOutput({
|
|
1516
1844
|
type: "web_search_call", id: `ws_${uuid()}`, status: e.status ?? "completed",
|
|
1517
1845
|
action: webSearchAction(e.queries),
|
|
1518
|
-
...(
|
|
1846
|
+
...(safeSources.length > 0 ? { sources: safeSources } : {}),
|
|
1519
1847
|
});
|
|
1520
|
-
if (
|
|
1521
|
-
const
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
seen.add(s.url);
|
|
1525
|
-
budget?.chargeRetained(bytesOf(JSON.stringify(s)), { kind: "tool_search_sources" });
|
|
1526
|
-
pendingWebSources.push(s);
|
|
1848
|
+
if (safeSources.length > 0) {
|
|
1849
|
+
for (const source of safeSources) {
|
|
1850
|
+
if (appendSafeWebSearchSource(pendingWebSources, source)) {
|
|
1851
|
+
budget?.chargeRetained(bytesOf(JSON.stringify(source)), { kind: "tool_search_sources" });
|
|
1527
1852
|
}
|
|
1528
1853
|
}
|
|
1529
1854
|
}
|
|
1530
1855
|
break;
|
|
1856
|
+
}
|
|
1531
1857
|
case "error":
|
|
1532
1858
|
errorEvent = e;
|
|
1859
|
+
sawTerminal = true;
|
|
1533
1860
|
usage = e.usage ?? usage;
|
|
1534
1861
|
break;
|
|
1535
1862
|
case "incomplete":
|
|
1536
1863
|
incompleteEvent = e;
|
|
1864
|
+
sawTerminal = true;
|
|
1537
1865
|
endTurn = e.endTurn;
|
|
1538
1866
|
if (e.providerState) options?.onProviderState?.(e.providerState);
|
|
1539
1867
|
break;
|
|
1540
1868
|
case "done":
|
|
1541
1869
|
usage = e.usage;
|
|
1870
|
+
sawTerminal = true;
|
|
1542
1871
|
endTurn = e.endTurn;
|
|
1543
1872
|
cleanDone = e.stopReason === undefined;
|
|
1873
|
+
rawStopReason = e.stopReason;
|
|
1544
1874
|
if (e.providerState) options?.onProviderState?.(e.providerState);
|
|
1545
1875
|
// Match streaming: max_tokens and content_filter both terminate as incomplete.
|
|
1546
|
-
|
|
1876
|
+
// Normalize every adapter's truncation vocabulary to the canonical pair, so a raw
|
|
1877
|
+
// `length` or `refusal` reaches the status/incomplete_details logic below instead of
|
|
1878
|
+
// silently reading as a clean stop.
|
|
1879
|
+
{
|
|
1880
|
+
const truncation = truncationReasonFor(e.stopReason);
|
|
1881
|
+
if (truncation) stopReason = truncation === "max_output_tokens" ? "max_tokens" : "content_filter";
|
|
1882
|
+
}
|
|
1547
1883
|
break;
|
|
1548
1884
|
}
|
|
1549
1885
|
if (budget) releaseTranslatedEvent(e, budget);
|
|
1550
1886
|
}
|
|
1551
1887
|
flushText(cleanDone && !errorEvent && !incompleteEvent ? "final_answer" : undefined);
|
|
1888
|
+
if (pendingWebSources.length > 0) {
|
|
1889
|
+
const sourceBytes = pendingWebSources.reduce((sum, source) => sum + bytesOf(JSON.stringify(source)), 0);
|
|
1890
|
+
pendingWebSources = [];
|
|
1891
|
+
budget?.releaseRetained(sourceBytes, { kind: "tool_search_sources" });
|
|
1892
|
+
}
|
|
1552
1893
|
flushSummaryReasoning();
|
|
1553
1894
|
flushRawReasoning();
|
|
1554
|
-
// Open tool call on a failed/incomplete turn must not land as status:"completed"
|
|
1555
|
-
|
|
1895
|
+
// Open tool call on a failed/incomplete turn must not land as status:"completed" — and neither
|
|
1896
|
+
// must one left open by a stream that stopped without any terminal at all. That case previously
|
|
1897
|
+
// fell through to "completed", handing back a function_call whose arguments were half-written
|
|
1898
|
+
// JSON, inside a turn also marked completed.
|
|
1899
|
+
if (currentToolCallId) flushToolCall(errorEvent || incompleteEvent || !sawTerminal ? "incomplete" : "completed");
|
|
1900
|
+
if (batchKiroRedacted) {
|
|
1901
|
+
// pushOutput reserves the item itself and releases the retained raw blob it replaces.
|
|
1902
|
+
pushOutput({
|
|
1903
|
+
type: "reasoning", id: `rs_${uuid()}`, summary: [],
|
|
1904
|
+
encrypted_content: encodeReasoningEnvelope({ krc: batchKiroRedacted }),
|
|
1905
|
+
}, batchKiroRedactedBytes, "reasoning");
|
|
1906
|
+
batchKiroRedacted = undefined;
|
|
1907
|
+
batchKiroRedactedBytes = 0;
|
|
1908
|
+
}
|
|
1556
1909
|
// A truncated turn must never be installed as replacement history: emit the
|
|
1557
1910
|
// compaction item only when the turn actually completed (#422).
|
|
1558
1911
|
if (
|
|
1559
1912
|
options?.compaction
|
|
1560
1913
|
&& !errorEvent
|
|
1561
1914
|
&& !incompleteEvent
|
|
1562
|
-
|
|
1563
|
-
|
|
1915
|
+
// A stream that stopped without any terminal did not complete either. The original guard
|
|
1916
|
+
// could only see explicit failure events, so an adapter EOF slipped past it and installed a
|
|
1917
|
+
// truncated summary as replacement history — the exact #422 hazard, reached by a route that
|
|
1918
|
+
// did not exist when the guard was written.
|
|
1919
|
+
&& sawTerminal
|
|
1920
|
+
&& !isTruncatedStopReason(rawStopReason)
|
|
1564
1921
|
) {
|
|
1565
1922
|
pushOutput({ type: "compaction", id: `cmp_${uuid()}`, encrypted_content: encodeCompactionSummary(compactionText) }, compactionTextBytes);
|
|
1566
1923
|
}
|
|
@@ -1570,7 +1927,13 @@ export function buildResponseJSON(
|
|
|
1570
1927
|
? "failed"
|
|
1571
1928
|
: incompleteEvent || stopReason === "max_tokens" || stopReason === "content_filter"
|
|
1572
1929
|
? "incomplete"
|
|
1573
|
-
:
|
|
1930
|
+
: sawTerminal
|
|
1931
|
+
? "completed"
|
|
1932
|
+
// The adapter stopped emitting without any terminal, so the turn was cut short. Streaming
|
|
1933
|
+
// already reports this as response.incomplete / adapter_eof (see the !terminated branch);
|
|
1934
|
+
// defaulting the buffered path to "completed" handed callers a truncated turn — including
|
|
1935
|
+
// one carrying a never-closed tool call with half-written JSON arguments — as a success.
|
|
1936
|
+
: "incomplete";
|
|
1574
1937
|
options?.onUsage?.(incompleteEvent?.usage ?? usage);
|
|
1575
1938
|
return {
|
|
1576
1939
|
id: responseId, object: "response",
|
|
@@ -1590,6 +1953,10 @@ export function buildResponseJSON(
|
|
|
1590
1953
|
incomplete_details: { reason: "max_output_tokens" },
|
|
1591
1954
|
} : stopReason === "content_filter" ? {
|
|
1592
1955
|
incomplete_details: { reason: "content_filter" },
|
|
1956
|
+
} : !sawTerminal ? {
|
|
1957
|
+
// Same reason string the streaming path uses, so a caller sees one signal for one condition
|
|
1958
|
+
// regardless of which surface it asked for.
|
|
1959
|
+
incomplete_details: { reason: "adapter_eof" },
|
|
1593
1960
|
} : {}),
|
|
1594
1961
|
usage: responsesUsage(incompleteEvent?.usage ?? usage),
|
|
1595
1962
|
};
|