@iislee/opencodex 2.11.0 → 2.35.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 +117 -22
- package/bin/ocx.mjs +245 -88
- package/bin/package-main.mjs +1 -1
- package/gui/dist/assets/index-Be1sDpWw.css +1 -0
- package/gui/dist/assets/index-D4OKgUV4.js +114 -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 +21 -10
- package/src/adapters/anthropic-output-schema.ts +137 -0
- package/src/adapters/anthropic.ts +475 -62
- package/src/adapters/base.ts +82 -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 +637 -0
- package/src/adapters/cursor/call-id.ts +44 -0
- package/src/adapters/cursor/catalog.ts +541 -0
- package/src/adapters/cursor/checkpoint-store.ts +308 -0
- package/src/adapters/cursor/cursor-errors.ts +144 -6
- package/src/adapters/cursor/discovery.ts +122 -14
- package/src/adapters/cursor/effort-map.ts +106 -4
- package/src/adapters/cursor/envelope-echo.ts +290 -0
- 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 +180 -59
- package/src/adapters/cursor/live-transport.ts +623 -170
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-common.ts +23 -2
- package/src/adapters/cursor/native-exec-desktop.ts +23 -0
- 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 +5 -3
- package/src/adapters/cursor/native-exec.ts +121 -14
- package/src/adapters/cursor/protobuf-events.ts +829 -11
- package/src/adapters/cursor/protobuf-request.ts +447 -75
- package/src/adapters/cursor/request-builder.ts +271 -35
- package/src/adapters/cursor/tool-definitions.ts +252 -12
- package/src/adapters/cursor/tool-result-normalize.ts +115 -0
- package/src/adapters/cursor/transport.ts +22 -0
- package/src/adapters/cursor/types.ts +48 -1
- package/src/adapters/cursor.ts +336 -39
- package/src/adapters/exec-tool-result-normalize.ts +99 -0
- package/src/adapters/google-antigravity-replay.ts +713 -41
- package/src/adapters/google-antigravity-wire.ts +48 -10
- package/src/adapters/google-errors.ts +44 -12
- 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 +742 -86
- package/src/adapters/identity.ts +39 -6
- package/src/adapters/image.ts +1 -1
- package/src/adapters/kiro-constants.ts +27 -0
- 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 +65 -17
- package/src/adapters/kiro.ts +270 -73
- package/src/adapters/mimo-free.ts +17 -0
- package/src/adapters/openai-chat-url.ts +11 -0
- package/src/adapters/openai-chat.ts +1377 -344
- package/src/adapters/openai-responses-url.ts +16 -0
- package/src/adapters/openai-responses.ts +1164 -64
- package/src/adapters/registry.ts +175 -0
- package/src/adapters/responses-tool-schema.ts +67 -0
- package/src/adapters/run-turn-queue.ts +36 -1
- package/src/adapters/tool-call-id.ts +119 -0
- package/src/adapters/tool-catalog-nudge.ts +105 -21
- package/src/adapters/xai-tool-schema.ts +436 -0
- package/src/adapters/xai-web-search.ts +186 -0
- package/src/bridge.ts +512 -92
- package/src/chat/inbound.ts +57 -20
- package/src/chat/outbound.ts +97 -33
- package/src/claude/agents-inject.ts +40 -10
- package/src/claude/context-windows.ts +37 -14
- 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 +88 -21
- package/src/cli/access.ts +46 -3
- package/src/cli/account-api.ts +150 -19
- package/src/cli/account-auth.ts +33 -6
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +648 -28
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +97 -19
- package/src/cli/agent.ts +53 -2
- package/src/cli/alias.ts +66 -0
- package/src/cli/capabilities-command.ts +94 -0
- package/src/cli/capabilities.ts +496 -0
- package/src/cli/claude-agent-startup-sync.ts +73 -0
- package/src/cli/claude-desktop.ts +60 -15
- package/src/cli/claude.ts +129 -35
- 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 +821 -0
- package/src/cli/doctor.ts +440 -56
- package/src/cli/ensure-desired-integrations.ts +152 -0
- package/src/cli/export-command.ts +46 -20
- package/src/cli/help.ts +34 -274
- package/src/cli/index.ts +417 -528
- package/src/cli/init.ts +5 -17
- package/src/cli/inspect.ts +230 -0
- 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 +130 -2
- package/src/cli/models.ts +118 -16
- package/src/cli/observe.ts +123 -11
- package/src/cli/opencode.ts +4 -2
- package/src/cli/provider-runtime.ts +34 -3
- package/src/cli/provider.ts +25 -3
- package/src/cli/ready.ts +301 -0
- package/src/cli/registry.ts +476 -0
- package/src/cli/root.ts +86 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +57 -10
- package/src/cli/star-prompt.ts +71 -15
- package/src/cli/status.ts +51 -4
- package/src/cli/storage.ts +234 -0
- package/src/cli/system-command.ts +16 -0
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +153 -6
- package/src/cli/usage-report.ts +184 -0
- package/src/cli/v2.ts +137 -18
- package/src/cli/version-skew.ts +46 -0
- package/src/cli.ts +1 -1
- package/src/clients/config-export.ts +1442 -23
- package/src/clients/effective-status.ts +30 -2
- package/src/clients/probes/cline.ts +81 -0
- package/src/codex/account-label.ts +35 -1
- package/src/codex/account-lifecycle.ts +130 -13
- package/src/codex/account-namespaces.ts +49 -3
- package/src/codex/account-pause.ts +2 -1
- package/src/codex/account-priority.ts +84 -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 +570 -107
- package/src/codex/app-server-restart-service.ts +232 -0
- package/src/codex/auth-api.ts +893 -246
- package/src/codex/auth-collision.ts +5 -3
- package/src/codex/auth-context.ts +348 -32
- package/src/codex/autostart-health.ts +24 -1
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +86 -10
- package/src/codex/catalog/bundled.ts +331 -33
- package/src/codex/catalog/effort.ts +138 -32
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +555 -45
- package/src/codex/catalog/native-models.ts +75 -0
- package/src/codex/catalog/parsing.ts +269 -30
- package/src/codex/catalog/provider-fetch.ts +1626 -144
- package/src/codex/catalog/sync.ts +1576 -187
- 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 +386 -0
- package/src/codex/convergence-types.ts +614 -0
- package/src/codex/convergence.ts +676 -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/desktop-app-restart.ts +355 -0
- package/src/codex/features.ts +636 -39
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +436 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-manifest.ts +112 -0
- package/src/codex/history-migration-guardian.ts +30 -24
- package/src/codex/history-provider.ts +1016 -235
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +223 -0
- package/src/codex/inject-coordination.ts +373 -0
- package/src/codex/inject.ts +1114 -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 +81 -0
- package/src/codex/journal.ts +66 -4
- package/src/codex/log-guard/inspect.ts +524 -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 +360 -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 +622 -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 +675 -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 +1313 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/prompt-text-probe.ts +238 -0
- package/src/codex/quota-rejection.ts +298 -0
- package/src/codex/quota.ts +264 -21
- package/src/codex/refresh.ts +11 -2
- package/src/codex/reset-credit-recovery.ts +1044 -0
- package/src/codex/routing.ts +514 -94
- package/src/codex/runtime.ts +159 -38
- package/src/codex/shim.ts +1065 -31
- package/src/codex/subagent-model-fallback.ts +437 -43
- package/src/codex/sync.ts +191 -2
- package/src/codex/transition-state.ts +720 -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 +47 -0
- package/src/combos/index.ts +4 -0
- package/src/combos/request.ts +32 -0
- package/src/combos/types.ts +91 -9
- package/src/compatibility/index.ts +26 -0
- package/src/compatibility/manifest.ts +253 -0
- package/src/compatibility/openai-responses.ts +121 -0
- package/src/config/atomic-write.ts +219 -0
- package/src/config/paths.ts +40 -0
- package/src/config/process-state.ts +309 -0
- package/src/config/provider-name.ts +24 -0
- package/src/config/provider-validation.ts +177 -0
- package/src/config/rebase-provenance.ts +68 -0
- package/src/config.ts +1713 -709
- package/src/generated/compatibility-version.json +3324 -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 +157 -33
- package/src/images/plan.ts +24 -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-policy.ts +160 -0
- package/src/integrations/ownership.ts +155 -0
- package/src/integrations/registry.ts +166 -0
- package/src/integrations/serialize.ts +314 -0
- package/src/integrations/state.ts +433 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer-lock.ts +98 -0
- package/src/integrations/writer.ts +715 -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/admin-secrets.ts +24 -0
- package/src/lib/app-owned-memory-stores.ts +22 -0
- package/src/lib/bounded-body.ts +167 -11
- 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 +26 -2
- package/src/lib/destination-policy.ts +65 -1
- package/src/lib/errors.ts +80 -5
- 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/package-tree-integrity.ts +101 -0
- package/src/lib/pinned-http.ts +145 -26
- package/src/lib/process-control.ts +6 -2
- 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/service-secrets.ts +15 -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 +253 -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 +30 -4
- package/src/oauth/chatgpt.ts +12 -1
- package/src/oauth/cline.ts +203 -0
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +46 -5
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/google-antigravity.ts +35 -3
- package/src/oauth/health.ts +20 -12
- package/src/oauth/index.ts +462 -71
- package/src/oauth/key-providers.ts +25 -0
- package/src/oauth/kimi.ts +25 -3
- package/src/oauth/kiro.ts +95 -6
- package/src/oauth/local-token-detect.ts +13 -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/open-browser-choice.ts +26 -0
- package/src/oauth/store.ts +133 -26
- package/src/oauth/token-guardian.ts +9 -3
- package/src/oauth/types.ts +15 -0
- 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/auto-compact-budget.ts +65 -0
- package/src/providers/base-url-choices.ts +10 -0
- package/src/providers/codex-capacity.ts +292 -0
- package/src/providers/command-code-efforts.ts +176 -0
- package/src/providers/context-cap.ts +26 -8
- package/src/providers/cursor-pool.ts +72 -0
- package/src/providers/default-aliases.ts +65 -0
- package/src/providers/derive.ts +281 -6
- package/src/providers/fastwire.ts +507 -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 +35 -2
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +115 -22
- package/src/providers/model-presets.ts +119 -0
- package/src/providers/model-rename-migration.ts +255 -0
- package/src/providers/model-rename-startup.ts +28 -0
- package/src/providers/new-model-policy.ts +146 -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 +30 -0
- package/src/providers/quota.ts +1379 -40
- package/src/providers/registry.ts +1545 -112
- package/src/providers/request-pacing.ts +310 -0
- package/src/providers/service-tier.ts +277 -0
- package/src/providers/slug-codec.ts +94 -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 +32 -4
- package/src/reasoning-effort.ts +68 -3
- package/src/responses/apply-patch-envelope.ts +63 -0
- package/src/responses/code-mode-helper-compat.ts +50 -0
- package/src/responses/compaction.ts +26 -1
- package/src/responses/custom-tool-compat.ts +384 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/namespace-tool-compat.ts +435 -0
- package/src/responses/parser.ts +225 -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 +504 -0
- package/src/responses/schema.ts +12 -2
- package/src/responses/spill-store.ts +75 -10
- package/src/responses/state.ts +659 -31
- package/src/responses/thought-signature-replay.ts +364 -0
- package/src/responses/tool-search-compat.ts +301 -0
- package/src/responses/truncated-stop-reason.ts +60 -0
- package/src/responses/turn-termination.ts +107 -0
- package/src/router.ts +405 -31
- package/src/routing/analytics.ts +379 -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 +155 -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 +289 -48
- package/src/server/background-lifecycle.ts +182 -0
- package/src/server/chat-completions.ts +154 -72
- package/src/server/chat-native-sse.ts +331 -0
- package/src/server/chat-native.ts +445 -0
- package/src/server/claude-messages.ts +160 -44
- 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 +1021 -190
- package/src/server/lifecycle.ts +336 -14
- 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 +433 -116
- package/src/server/management/api-key-usage.ts +31 -5
- package/src/server/management/body.ts +6 -0
- package/src/server/management/codex-prompt-routes.ts +570 -0
- package/src/server/management/combo-routes.ts +64 -25
- package/src/server/management/config-routes.ts +511 -51
- package/src/server/management/context.ts +89 -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 +127 -52
- package/src/server/management/model-routes.ts +461 -133
- package/src/server/management/model-rows.ts +167 -0
- package/src/server/management/native-integration-routes.ts +770 -0
- package/src/server/management/oauth-account-routes.ts +90 -8
- package/src/server/management/provider-capability-config.ts +48 -0
- package/src/server/management/provider-routes.ts +836 -159
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/route-registry.ts +332 -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 +289 -33
- 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 +126 -23
- package/src/server/management-auth.ts +220 -5
- package/src/server/passive-route-linker.ts +66 -0
- package/src/server/port-reclaim.ts +1 -1
- package/src/server/ports.ts +41 -1
- package/src/server/proxy-liveness.ts +161 -10
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +221 -54
- package/src/server/relay.ts +479 -87
- package/src/server/request-decompress.ts +113 -6
- package/src/server/request-log-conversation.ts +51 -0
- package/src/server/request-log.ts +298 -31
- 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/codex-auth-error.ts +55 -0
- package/src/server/responses/collaboration.ts +239 -36
- package/src/server/responses/combo-stream-preflight.ts +178 -0
- package/src/server/responses/compact.ts +444 -72
- package/src/server/responses/core.ts +3579 -436
- package/src/server/responses/empty-completion-guard.ts +333 -0
- package/src/server/responses/encrypted-payload.ts +62 -39
- package/src/server/responses/fetch-helpers.ts +89 -99
- package/src/server/responses/input-admission.ts +187 -0
- package/src/server/responses/pacing-overload.ts +13 -0
- package/src/server/responses/passthrough-error.ts +33 -9
- package/src/server/responses/policy-fallback.ts +178 -0
- package/src/server/responses/responses-field-backfill.ts +343 -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 +381 -0
- package/src/server/responses-custom-tool-repair.ts +363 -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 +363 -0
- package/src/server/responses-tool-search-repair.ts +267 -0
- package/src/server/responses-undeclared-tool-guard.ts +376 -0
- package/src/server/responses.ts +18 -2
- package/src/server/search.ts +78 -13
- package/src/server/sse-frame-buffer.ts +319 -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 +53 -40
- package/src/service-manager-probe.ts +892 -0
- package/src/service.ts +1153 -94
- package/src/sidecar/auth.ts +92 -0
- package/src/sidecar/candidates.ts +83 -0
- package/src/storage/cleanup.ts +2 -2
- package/src/storage/policy-job.ts +14 -4
- package/src/storage/policy.ts +88 -23
- 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 +911 -0
- package/src/types/provider.ts +614 -0
- package/src/types/request.ts +397 -0
- package/src/types/tools.ts +236 -0
- package/src/types/wire.ts +80 -0
- package/src/types.ts +107 -1237
- package/src/update/index.ts +37 -23
- package/src/update/job.ts +445 -68
- 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 +266 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +287 -16
- package/src/usage/log.ts +666 -50
- package/src/usage/summary.ts +388 -17
- 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 +27 -16
- 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 +119 -0
- package/src/web-search/executor.ts +21 -6
- package/src/web-search/gemini-executor.ts +141 -0
- package/src/web-search/index.ts +150 -15
- package/src/web-search/loop.ts +284 -54
- 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,40 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
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";
|
|
10
|
+
import {
|
|
11
|
+
adapterFailureFromMessage,
|
|
12
|
+
classifyError,
|
|
13
|
+
cyberPolicyErrorType,
|
|
14
|
+
CYBER_POLICY_ERROR_CODE,
|
|
15
|
+
isCyberPolicyCode,
|
|
16
|
+
type OcxErrorPayload,
|
|
17
|
+
} from "./lib/errors";
|
|
18
|
+
import { redactSecretString } from "./lib/redact";
|
|
19
|
+
import { repairFreeformToolInput } from "./responses/apply-patch-envelope";
|
|
3
20
|
import { encodeCompactionSummary } from "./responses/compaction";
|
|
21
|
+
import { compileCodeModeHelperInput } from "./responses/code-mode-helper-compat";
|
|
22
|
+
import { isTruncatedStopReason, truncationReasonFor } from "./responses/truncated-stop-reason";
|
|
4
23
|
import { encodeReasoningEnvelope, type ReasoningEnvelope } from "./responses/reasoning-envelope";
|
|
24
|
+
import { rememberReasoningForCall } from "./responses/reasoning-replay-cache";
|
|
25
|
+
import {
|
|
26
|
+
rememberAndSerializeExtraContent,
|
|
27
|
+
rememberExtraContentForReplay,
|
|
28
|
+
awaitThoughtSignatureDurability,
|
|
29
|
+
} from "./responses/thought-signature-replay";
|
|
5
30
|
import { resolveStallTimeoutSec } from "./stall-timeout";
|
|
31
|
+
import { normalizeDeclaredToolName } from "./types";
|
|
6
32
|
import { usageDisplayTotalTokens } from "./usage/totals";
|
|
33
|
+
import { appendSafeWebSearchSource, safeWebSearchSources } from "./web-search/sources";
|
|
7
34
|
import {
|
|
8
35
|
isTranslatorBudgetExceededError,
|
|
9
36
|
releaseTranslatedEvent,
|
|
37
|
+
createTranslatorBudget,
|
|
10
38
|
type TranslatorBudget,
|
|
11
39
|
type TranslatorBufferKind,
|
|
12
40
|
} from "./lib/translator-budget";
|
|
@@ -15,6 +43,13 @@ function uuid(): string {
|
|
|
15
43
|
return crypto.randomUUID().replace(/-/g, "");
|
|
16
44
|
}
|
|
17
45
|
|
|
46
|
+
/** Test-only: bound the abandoned-owned-budget watchdog delay (null restores). */
|
|
47
|
+
let ownedBudgetAbandonedMs = 10 * 60 * 1000;
|
|
48
|
+
const OWNED_BUDGET_ABANDONED_DEFAULT_MS = ownedBudgetAbandonedMs;
|
|
49
|
+
export function setOwnedBudgetAbandonedMsForTests(ms: number | null): void {
|
|
50
|
+
ownedBudgetAbandonedMs = ms ?? OWNED_BUDGET_ABANDONED_DEFAULT_MS;
|
|
51
|
+
}
|
|
52
|
+
|
|
18
53
|
function sseEvent(name: string, data: Record<string, unknown>): string {
|
|
19
54
|
return `event: ${name}\ndata: ${JSON.stringify(data)}\n\n`;
|
|
20
55
|
}
|
|
@@ -76,10 +111,11 @@ function responseError(status: number, type: string, message: string): OcxErrorP
|
|
|
76
111
|
* non-stream adapters degrade a bad payload to `{}`.
|
|
77
112
|
*/
|
|
78
113
|
function toolCallArgumentsUsable(args: string): boolean {
|
|
114
|
+
if (args.length === 0) return true;
|
|
79
115
|
const trimmed = args.trim();
|
|
80
|
-
if (!trimmed) return
|
|
116
|
+
if (!trimmed) return false;
|
|
81
117
|
try {
|
|
82
|
-
JSON.parse(
|
|
118
|
+
JSON.parse(args);
|
|
83
119
|
return true;
|
|
84
120
|
} catch {
|
|
85
121
|
return false;
|
|
@@ -87,18 +123,19 @@ function toolCallArgumentsUsable(args: string): boolean {
|
|
|
87
123
|
}
|
|
88
124
|
|
|
89
125
|
function adapterFailureFromEvent(event: Extract<AdapterEvent, { type: "error" }>): { httpStatus: number; error: OcxErrorPayload } {
|
|
126
|
+
const message = redactSecretString(event.message);
|
|
90
127
|
if (event.status === undefined && event.errorType === undefined && event.code === undefined) {
|
|
91
|
-
return adapterFailureFromMessage(
|
|
128
|
+
return adapterFailureFromMessage(message);
|
|
92
129
|
}
|
|
93
|
-
const fallback = adapterFailureFromMessage(
|
|
130
|
+
const fallback = adapterFailureFromMessage(message);
|
|
94
131
|
let httpStatus = event.status ?? fallback.httpStatus;
|
|
95
|
-
const error = classifyError(httpStatus, event.errorType ?? fallback.error.type,
|
|
132
|
+
const error = classifyError(httpStatus, event.errorType ?? fallback.error.type, message);
|
|
96
133
|
if (event.errorType !== undefined) error.type = event.errorType;
|
|
97
134
|
if (event.code !== undefined) error.code = event.code;
|
|
98
135
|
// Codex maps cyber_policy on HTTP 400 (body) or mid-stream code; never leave it as 502.
|
|
99
136
|
if (isCyberPolicyCode(error.code) || isCyberPolicyCode(event.code)) {
|
|
100
137
|
error.code = CYBER_POLICY_ERROR_CODE;
|
|
101
|
-
error.type =
|
|
138
|
+
error.type = cyberPolicyErrorType(event.errorType);
|
|
102
139
|
httpStatus = 400;
|
|
103
140
|
}
|
|
104
141
|
return { httpStatus, error };
|
|
@@ -107,13 +144,28 @@ function adapterFailureFromEvent(event: Extract<AdapterEvent, { type: "error" }>
|
|
|
107
144
|
export { adapterFailureFromMessage } from "./lib/errors";
|
|
108
145
|
|
|
109
146
|
/**
|
|
110
|
-
* Build the native `WebSearchAction::Search` payload from the queries that ran.
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
147
|
+
* Build the native `WebSearchAction::Search` payload from the queries that ran.
|
|
148
|
+
*
|
|
149
|
+
* Single query → `{ query, queries: [query] }`. Batch → `{ queries }` with NO singular
|
|
150
|
+
* `query`. Empty → `{ query: "", queries: [""] }`.
|
|
151
|
+
*
|
|
152
|
+
* The asymmetry is load-bearing in both directions. codex-rs prefers a non-empty `query`
|
|
153
|
+
* for the cell label and renders "<first> ..." only when `query` is ABSENT and
|
|
154
|
+
* `queries.len() > 1`, so adding `query` to a batch would collapse the plural ellipsis.
|
|
155
|
+
* Meanwhile DeepSeek's native Responses parser makes `queries` a required field, so a
|
|
156
|
+
* replayed one-term `web_search_call` — carried in the history of every subsequent turn
|
|
157
|
+
* — fails deserialization with `missing field 'queries'` and 400s the rest of the
|
|
158
|
+
* conversation (#930). Carrying both keys in the single case satisfies the strict parser
|
|
159
|
+
* without changing what codex-rs displays.
|
|
160
|
+
*
|
|
161
|
+
* This fixes items created from here on. History recorded before it is repaired at the
|
|
162
|
+
* replay boundary by `backfillWebSearchQueries()` in the Responses adapter.
|
|
114
163
|
*/
|
|
115
164
|
function webSearchAction(queries: string[]): Record<string, unknown> {
|
|
116
|
-
if (queries.length <= 1)
|
|
165
|
+
if (queries.length <= 1) {
|
|
166
|
+
const query = queries[0] ?? "";
|
|
167
|
+
return { type: "search", query, queries: [query] };
|
|
168
|
+
}
|
|
117
169
|
return { type: "search", queries };
|
|
118
170
|
}
|
|
119
171
|
|
|
@@ -128,7 +180,7 @@ export type ResponsesTerminalStatus = "completed" | "failed" | "incomplete";
|
|
|
128
180
|
export function bridgeToResponsesSSE(
|
|
129
181
|
events: AsyncIterable<AdapterEvent>,
|
|
130
182
|
modelId: string,
|
|
131
|
-
toolNsMap?: Map<string, { namespace: string; name: string }>,
|
|
183
|
+
toolNsMap?: Map<string, { namespace: string; name: string; freeform?: true }>,
|
|
132
184
|
freeformToolNames?: Set<string>,
|
|
133
185
|
toolSearchToolNames?: Set<string>,
|
|
134
186
|
onCancel?: () => void,
|
|
@@ -156,7 +208,27 @@ export function bridgeToResponsesSSE(
|
|
|
156
208
|
* from this callback instead of re-parsing the bridged SSE.
|
|
157
209
|
*/
|
|
158
210
|
onUsage?: (usage: OcxUsage | undefined) => void;
|
|
211
|
+
/** Request-visible tool names. When present, an upstream call outside this set fails closed. */
|
|
212
|
+
declaredToolNames?: ReadonlySet<string>;
|
|
213
|
+
/** Declared parameter schema per tool name; repairs integral-float integer args (#1611). */
|
|
214
|
+
toolParameterSchemas?: ReadonlyMap<string, Record<string, unknown>>;
|
|
215
|
+
/**
|
|
216
|
+
* Wire keep-alive shape. Codex-rs parses at the EVENT level (timeout(idle_timeout,
|
|
217
|
+
* stream.next()) over an eventsource_stream), so an SSE comment line dispatches no event
|
|
218
|
+
* and does NOT re-arm its idle timer — the keep-alive must be a typed frame the parser
|
|
219
|
+
* ignores via its catch-all (110 RCA, 30_patch-direction.md). grok-build's strict
|
|
220
|
+
* async-openai fork is the opposite: it dies on the unknown `response.heartbeat`
|
|
221
|
+
* variant but, being eventsource-based at the byte level, its idle handling tolerates
|
|
222
|
+
* comment lines. Default stays the typed frame; the grok surface opts into comments.
|
|
223
|
+
*/
|
|
224
|
+
heartbeatStyle?: "typed" | "comment";
|
|
159
225
|
translatorBudget?: TranslatorBudget;
|
|
226
|
+
/**
|
|
227
|
+
* Conversation identity for the reasoning replay cache (issue #950).
|
|
228
|
+
* Provider call ids are not globally unique; scoping by thread keeps one
|
|
229
|
+
* conversation's reasoning out of another's continuations.
|
|
230
|
+
*/
|
|
231
|
+
replayCacheScope?: OcxReasoningReplayScopeRef;
|
|
160
232
|
/**
|
|
161
233
|
* Test seam for the wire/stall beat loop. Production omits this and uses the
|
|
162
234
|
* global timers; injecting here must not change scheduling semantics.
|
|
@@ -167,14 +239,20 @@ export function bridgeToResponsesSSE(
|
|
|
167
239
|
};
|
|
168
240
|
},
|
|
169
241
|
): ReadableStream<Uint8Array> {
|
|
242
|
+
const replayCacheScope = options?.replayCacheScope;
|
|
170
243
|
const setBeatInterval = options?.timers?.setInterval ?? ((handler: () => void, ms: number) => setInterval(handler, ms));
|
|
171
244
|
const clearBeatInterval = options?.timers?.clearInterval ?? ((id: unknown) => clearInterval(id as ReturnType<typeof setInterval>));
|
|
172
|
-
// Freeform/custom tools (apply_patch) carry their body in `input`; the
|
|
173
|
-
// function with `{input:string}`, so unwrap it here when relaying back
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
245
|
+
// Freeform/custom tools (apply_patch, code-mode exec) carry their body in `input`; the
|
|
246
|
+
// model is given a function with `{input:string}`, so unwrap it here when relaying back
|
|
247
|
+
// as a custom_tool_call. Decorated apply_patch envelopes are repaired at this boundary.
|
|
248
|
+
const freeformInput = (
|
|
249
|
+
args: string,
|
|
250
|
+
toolName: string,
|
|
251
|
+
namespace?: string,
|
|
252
|
+
codeModeHelperName?: string,
|
|
253
|
+
): string => codeModeHelperName
|
|
254
|
+
? compileCodeModeHelperInput(args, codeModeHelperName)
|
|
255
|
+
: repairFreeformToolInput(args, toolName, namespace);
|
|
178
256
|
// Best-effort unwrap of a PARTIAL freeform arg buffer for live input streaming
|
|
179
257
|
// (`response.custom_tool_call_input.delta` — codex-rs uses it for UI preview only;
|
|
180
258
|
// the completed custom_tool_call item stays authoritative). Compact `{"input":"...`
|
|
@@ -208,7 +286,25 @@ export function bridgeToResponsesSSE(
|
|
|
208
286
|
try { const o = JSON.parse(args); return o && typeof o === "object" ? o : {}; } catch { return {}; }
|
|
209
287
|
};
|
|
210
288
|
const encoder = new TextEncoder();
|
|
211
|
-
|
|
289
|
+
// Default-budget safety net: omission is SAFE (default turn limits), never
|
|
290
|
+
// unbounded. Production callers always pass one; an owned default is disposed
|
|
291
|
+
// at terminal/cancel below.
|
|
292
|
+
const ownsBudget = !options?.translatorBudget;
|
|
293
|
+
const budget = options?.translatorBudget ?? createTranslatorBudget();
|
|
294
|
+
// Idempotent: safe to call at every stream-death path; disposal must come
|
|
295
|
+
// AFTER the final charges (emitDone), never inside reportTerminal.
|
|
296
|
+
const disposeOwnedBudget = () => { if (ownsBudget) budget.dispose(); };
|
|
297
|
+
// A dropped stream (never read, never cancelled) reaches no terminal path,
|
|
298
|
+
// so the owned budget would sit in liveBudgets for the process lifetime.
|
|
299
|
+
// One unref'd watchdog per owned budget bounds that to a timeout and clears
|
|
300
|
+
// itself on any settle (the delay is test-overridable).
|
|
301
|
+
const ownedWatchdog = ownsBudget
|
|
302
|
+
? setTimeout(() => disposeOwnedBudget(), ownedBudgetAbandonedMs)
|
|
303
|
+
: undefined;
|
|
304
|
+
ownedWatchdog?.unref?.();
|
|
305
|
+
const clearOwnedWatchdog = () => {
|
|
306
|
+
if (ownedWatchdog !== undefined) clearTimeout(ownedWatchdog);
|
|
307
|
+
};
|
|
212
308
|
const bytesOf = (value: string): number => Buffer.byteLength(value);
|
|
213
309
|
const appendString = (
|
|
214
310
|
previous: string,
|
|
@@ -219,7 +315,6 @@ export function bridgeToResponsesSSE(
|
|
|
219
315
|
): { value: string; bytes: number } => {
|
|
220
316
|
const fragmentBytes = bytesOf(fragment);
|
|
221
317
|
const nextBytes = previousBytes + fragmentBytes;
|
|
222
|
-
if (!budget) return { value: previous + fragment, bytes: nextBytes };
|
|
223
318
|
const scope = { kind, ...(callId ? { callId } : {}) };
|
|
224
319
|
const reservation = budget.reserveTransient(nextBytes, scope);
|
|
225
320
|
try {
|
|
@@ -257,12 +352,16 @@ export function bridgeToResponsesSSE(
|
|
|
257
352
|
if (terminalReported || clientCancelled || closed) return;
|
|
258
353
|
terminalReported = true;
|
|
259
354
|
try { options?.onTerminal?.(status); } catch { /* terminal metrics must not break the stream */ }
|
|
355
|
+
clearOwnedWatchdog();
|
|
260
356
|
};
|
|
261
357
|
// RC3 keep-alive: Codex's idle timer is timeout(idle_timeout, stream.next()) over an
|
|
262
|
-
// eventsource_stream
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
//
|
|
358
|
+
// eventsource_stream, which parses at the EVENT level — a comment-only frame dispatches no
|
|
359
|
+
// event, so it does NOT re-arm the timer (110 RCA). The default keep-alive is therefore a
|
|
360
|
+
// typed `response.heartbeat` frame the codex-rs parser ignores via `_ => Ok(None)`. The
|
|
361
|
+
// grok surface (strict async-openai decoder that dies on unknown variants) opts into SSE
|
|
362
|
+
// comment lines instead via options.heartbeatStyle. Emit whenever the *wire* has been
|
|
363
|
+
// silent, even if invisible adapter heartbeats are still flowing (web-search buffering +
|
|
364
|
+
// raw-byte progress). Upstream activity only resets the stall watchdog.
|
|
266
365
|
let upstreamActivity = false;
|
|
267
366
|
let wireActivity = false;
|
|
268
367
|
let beat: unknown;
|
|
@@ -289,6 +388,7 @@ export function bridgeToResponsesSSE(
|
|
|
289
388
|
return;
|
|
290
389
|
}
|
|
291
390
|
closed = true;
|
|
391
|
+
disposeOwnedBudget();
|
|
292
392
|
}
|
|
293
393
|
};
|
|
294
394
|
const emitDone = () => {
|
|
@@ -328,7 +428,9 @@ export function bridgeToResponsesSSE(
|
|
|
328
428
|
...(endTurn !== undefined ? { end_turn: endTurn } : {}),
|
|
329
429
|
});
|
|
330
430
|
|
|
331
|
-
const heartbeatFrame =
|
|
431
|
+
const heartbeatFrame = options?.heartbeatStyle === "comment"
|
|
432
|
+
? encoder.encode(': opencodex heartbeat\n\n')
|
|
433
|
+
: encoder.encode('event: response.heartbeat\ndata: {"type":"response.heartbeat"}\n\n');
|
|
332
434
|
let stallTicks = 0;
|
|
333
435
|
const stallSec = resolveStallTimeoutSec(options?.stallTimeoutSec);
|
|
334
436
|
const maxStallTicks = Math.ceil((stallSec * 1000) / heartbeatMs);
|
|
@@ -384,8 +486,15 @@ export function bridgeToResponsesSSE(
|
|
|
384
486
|
// encodeReasoningEnvelope: takeReasoningEnvelope's sig/red guard would drop txt-only.
|
|
385
487
|
let hiddenRawReasoningText = "";
|
|
386
488
|
let hiddenRawReasoningBytes = 0;
|
|
489
|
+
// Raw reasoning text flushed most recently, waiting for the tool call it
|
|
490
|
+
// preceded. Recorded into the replay cache on tool_call_start so a later
|
|
491
|
+
// continuation can re-attach it when history lost the reasoning item
|
|
492
|
+
// (issue #950). Kept until new reasoning/text arrives: parallel tool
|
|
493
|
+
// calls share the same preceding reasoning block.
|
|
494
|
+
let rawReasoningForNextToolCall = "";
|
|
387
495
|
const flushHiddenRawReasoning = () => {
|
|
388
496
|
if (!hiddenRawReasoningText) return;
|
|
497
|
+
rawReasoningForNextToolCall = hiddenRawReasoningText;
|
|
389
498
|
const previousBytes = hiddenRawReasoningBytes;
|
|
390
499
|
const encrypted = encodeReasoningEnvelope({ txt: hiddenRawReasoningText });
|
|
391
500
|
const reservation = budget?.reserveTransient(bytesOf(encrypted), { kind: "reasoning" });
|
|
@@ -400,11 +509,34 @@ export function bridgeToResponsesSSE(
|
|
|
400
509
|
retainFinishedItem(item as OutputItem, bytesOf(encrypted), "reasoning");
|
|
401
510
|
outputIndex++;
|
|
402
511
|
};
|
|
512
|
+
// Kiro reasoning round-trip. Kiro sends its encrypted blob at the END of a turn, while the
|
|
513
|
+
// assistant message is still open, so this CANNOT emit on arrival: the open message still
|
|
514
|
+
// owns `outputIndex` (it only advances on close), and an item emitted here would both reuse
|
|
515
|
+
// that index and land BEFORE the message — where the parser's backwards pairing drops it as
|
|
516
|
+
// orphaned. Stash it and flush after `done` has closed every open item instead.
|
|
517
|
+
let pendingKiroRedacted: string | undefined;
|
|
518
|
+
let pendingKiroRedactedBytes = 0;
|
|
519
|
+
const flushKiroRedactedReasoning = () => {
|
|
520
|
+
if (!pendingKiroRedacted) return;
|
|
521
|
+
const previousBytes = pendingKiroRedactedBytes;
|
|
522
|
+
const encrypted = encodeReasoningEnvelope({ krc: pendingKiroRedacted });
|
|
523
|
+
const reservation = budget?.reserveTransient(bytesOf(encrypted), { kind: "reasoning" });
|
|
524
|
+
pendingKiroRedacted = undefined;
|
|
525
|
+
pendingKiroRedactedBytes = 0;
|
|
526
|
+
reservation?.commitRetained();
|
|
527
|
+
budget?.releaseRetained(previousBytes, { kind: "reasoning" });
|
|
528
|
+
const itemId = `rs_${uuid()}`;
|
|
529
|
+
const item = { type: "reasoning", id: itemId, summary: [] as never[], encrypted_content: encrypted };
|
|
530
|
+
emit("response.output_item.added", { output_index: outputIndex, item });
|
|
531
|
+
emit("response.output_item.done", { output_index: outputIndex, item });
|
|
532
|
+
retainFinishedItem(item as OutputItem, bytesOf(encrypted), "reasoning");
|
|
533
|
+
outputIndex++;
|
|
534
|
+
};
|
|
403
535
|
// Full assistant text of a compaction turn (across message boundaries) — becomes the
|
|
404
536
|
// synthetic compaction item's payload on done.
|
|
405
537
|
let compactionText = "";
|
|
406
538
|
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;
|
|
539
|
+
let currentToolCall: { itemId: string; outputIndex: number; callId: string; name: string; args: string; argsBytes: number; namespace?: string; freeform?: boolean; toolSearch?: boolean; inputEmitted?: string; codeModeHelperName?: string; providerMetadata?: OcxProviderOpaqueToolCallMetadata } | null = null;
|
|
408
540
|
// Open native web-search cell (between begin and end). Holds the output index allocated on
|
|
409
541
|
// begin so the matching done reuses it; closed as `failed` if the stream terminates early.
|
|
410
542
|
let currentWebSearch: { itemId: string; eventId: string; outputIndex: number } | null = null;
|
|
@@ -412,17 +544,22 @@ export function bridgeToResponsesSSE(
|
|
|
412
544
|
// url_citation annotations on that message (the desktop app's Sources chip), then cleared so
|
|
413
545
|
// they bind to exactly one message. Deduped by URL across multiple searches in the turn.
|
|
414
546
|
let pendingWebSources: { url: string; title?: string }[] = [];
|
|
547
|
+
let pendingWebSourceBytes = 0;
|
|
548
|
+
const releasePendingWebSources = () => {
|
|
549
|
+
if (pendingWebSources.length === 0) return;
|
|
550
|
+
pendingWebSources = [];
|
|
551
|
+
budget?.releaseRetained(pendingWebSourceBytes, { kind: "tool_search_sources" });
|
|
552
|
+
pendingWebSourceBytes = 0;
|
|
553
|
+
};
|
|
415
554
|
const takeWebAnnotations = (): { type: string; url: string; title?: string; start_index: number; end_index: number }[] => {
|
|
416
555
|
if (pendingWebSources.length === 0) return [];
|
|
417
556
|
const anns = pendingWebSources.map(s => ({
|
|
418
557
|
type: "url_citation", url: s.url, ...(s.title ? { title: s.title } : {}), start_index: 0, end_index: 0,
|
|
419
558
|
}));
|
|
420
|
-
const sourceBytes = pendingWebSources.reduce((sum, source) => sum + bytesOf(JSON.stringify(source)), 0);
|
|
421
559
|
const annotationBytes = bytesOf(JSON.stringify(anns));
|
|
422
560
|
const reservation = budget?.reserveTransient(annotationBytes, { kind: "retained_collectors" });
|
|
423
|
-
pendingWebSources = [];
|
|
424
561
|
reservation?.commitRetained();
|
|
425
|
-
|
|
562
|
+
releasePendingWebSources();
|
|
426
563
|
return anns;
|
|
427
564
|
};
|
|
428
565
|
|
|
@@ -477,9 +614,17 @@ export function bridgeToResponsesSSE(
|
|
|
477
614
|
|
|
478
615
|
const closeCurrentRawReasoning = () => {
|
|
479
616
|
if (!currentRawReasoning) return;
|
|
617
|
+
rawReasoningForNextToolCall = currentRawReasoning.text;
|
|
618
|
+
emit("response.reasoning_summary_text.done", {
|
|
619
|
+
item_id: currentRawReasoning.itemId, output_index: currentRawReasoning.outputIndex, summary_index: 0, text: currentRawReasoning.text,
|
|
620
|
+
});
|
|
621
|
+
emit("response.reasoning_summary_part.done", {
|
|
622
|
+
item_id: currentRawReasoning.itemId, output_index: currentRawReasoning.outputIndex, summary_index: 0,
|
|
623
|
+
part: { type: "summary_text", text: currentRawReasoning.text },
|
|
624
|
+
});
|
|
480
625
|
const item = {
|
|
481
|
-
type: "reasoning", id: currentRawReasoning.itemId,
|
|
482
|
-
|
|
626
|
+
type: "reasoning", id: currentRawReasoning.itemId,
|
|
627
|
+
summary: [{ type: "summary_text", text: currentRawReasoning.text }],
|
|
483
628
|
};
|
|
484
629
|
emit("response.output_item.done", { output_index: currentRawReasoning.outputIndex, item });
|
|
485
630
|
retainFinishedItem(item as OutputItem, currentRawReasoning.textBytes, "reasoning");
|
|
@@ -492,7 +637,14 @@ export function bridgeToResponsesSSE(
|
|
|
492
637
|
// Empty input (no-arg tools like computer_use get_app_state / list_apps) must serialize as
|
|
493
638
|
// "{}", never "" — Codex echoes the call back as a function_call next turn, and JSON.parse("")
|
|
494
639
|
// would 400 the whole session ("invalid JSON arguments"), poisoning all later turns.
|
|
495
|
-
|
|
640
|
+
// #1611: Grok serializes integer arguments through a float, so `120000.0`
|
|
641
|
+
// reaches Codex and is REJECTED before the tool runs. Repair integral floats
|
|
642
|
+
// against the declared schema; a non-integral value stays an error.
|
|
643
|
+
const argsStr = coerceIntegerToolArguments(
|
|
644
|
+
currentToolCall.args || "{}",
|
|
645
|
+
options?.toolParameterSchemas?.get(currentToolCall.name),
|
|
646
|
+
currentToolCall.namespace === undefined ? currentToolCall.name : undefined,
|
|
647
|
+
);
|
|
496
648
|
// Finalize streamed function-call arguments so Codex commits the call (incl. MCP / computer_use).
|
|
497
649
|
if (!currentToolCall.freeform && !currentToolCall.toolSearch) {
|
|
498
650
|
emit("response.function_call_arguments.done", {
|
|
@@ -502,9 +654,13 @@ export function bridgeToResponsesSSE(
|
|
|
502
654
|
if (currentToolCall.freeform) {
|
|
503
655
|
emit("response.custom_tool_call_input.done", {
|
|
504
656
|
item_id: currentToolCall.itemId, output_index: currentToolCall.outputIndex,
|
|
505
|
-
|
|
657
|
+
...(currentToolCall.namespace ? { namespace: currentToolCall.namespace } : {}),
|
|
658
|
+
input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace, currentToolCall.codeModeHelperName),
|
|
506
659
|
});
|
|
507
660
|
}
|
|
661
|
+
// Freeform tools serialize as custom_tool_call without extra_content; remember the
|
|
662
|
+
// signature server-side regardless so the replayed call can be re-signed (#1735).
|
|
663
|
+
void rememberExtraContentForReplay(currentToolCall.callId, currentToolCall.providerMetadata, replayCacheScope);
|
|
508
664
|
const item = currentToolCall.toolSearch
|
|
509
665
|
? {
|
|
510
666
|
type: "tool_search_call", id: currentToolCall.itemId,
|
|
@@ -515,13 +671,18 @@ export function bridgeToResponsesSSE(
|
|
|
515
671
|
? {
|
|
516
672
|
type: "custom_tool_call", id: currentToolCall.itemId,
|
|
517
673
|
call_id: currentToolCall.callId, name: currentToolCall.name,
|
|
518
|
-
|
|
674
|
+
...(currentToolCall.namespace ? { namespace: currentToolCall.namespace } : {}),
|
|
675
|
+
input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace, currentToolCall.codeModeHelperName), status: "completed",
|
|
519
676
|
}
|
|
520
677
|
: {
|
|
521
678
|
type: "function_call", id: currentToolCall.itemId,
|
|
522
679
|
call_id: currentToolCall.callId, name: currentToolCall.name,
|
|
523
680
|
arguments: argsStr, status: "completed",
|
|
524
681
|
...(currentToolCall.namespace ? { namespace: currentToolCall.namespace } : {}),
|
|
682
|
+
// Provider-opaque metadata (issue #1735) rides the item so a client that replays
|
|
683
|
+
// this history can hand the signature back on the part it belongs to. The proxy
|
|
684
|
+
// also remembers it server-side for clients that never echo extra_content.
|
|
685
|
+
...(rememberAndSerializeExtraContent(currentToolCall.callId, currentToolCall.providerMetadata, replayCacheScope).extra ?? {}),
|
|
525
686
|
};
|
|
526
687
|
emit("response.output_item.done", { output_index: currentToolCall.outputIndex, item });
|
|
527
688
|
retainFinishedItem(item as OutputItem);
|
|
@@ -539,6 +700,7 @@ export function bridgeToResponsesSSE(
|
|
|
539
700
|
const failCurrentToolCall = () => {
|
|
540
701
|
if (!currentToolCall) return;
|
|
541
702
|
const argsStr = currentToolCall.args || "{}";
|
|
703
|
+
void rememberExtraContentForReplay(currentToolCall.callId, currentToolCall.providerMetadata, replayCacheScope);
|
|
542
704
|
const item = currentToolCall.toolSearch
|
|
543
705
|
? {
|
|
544
706
|
type: "tool_search_call", id: currentToolCall.itemId,
|
|
@@ -549,13 +711,18 @@ export function bridgeToResponsesSSE(
|
|
|
549
711
|
? {
|
|
550
712
|
type: "custom_tool_call", id: currentToolCall.itemId,
|
|
551
713
|
call_id: currentToolCall.callId, name: currentToolCall.name,
|
|
552
|
-
|
|
714
|
+
...(currentToolCall.namespace ? { namespace: currentToolCall.namespace } : {}),
|
|
715
|
+
input: freeformInput(currentToolCall.args, currentToolCall.name, currentToolCall.namespace, currentToolCall.codeModeHelperName), status: "incomplete",
|
|
553
716
|
}
|
|
554
717
|
: {
|
|
555
718
|
type: "function_call", id: currentToolCall.itemId,
|
|
556
719
|
call_id: currentToolCall.callId, name: currentToolCall.name,
|
|
557
720
|
arguments: argsStr, status: "incomplete",
|
|
558
721
|
...(currentToolCall.namespace ? { namespace: currentToolCall.namespace } : {}),
|
|
722
|
+
// An incomplete call can still be persisted and replayed (max_output_tokens), so it
|
|
723
|
+
// carries the same metadata as the completed item — otherwise SSE and buffered JSON
|
|
724
|
+
// would disagree about whether the signature survives.
|
|
725
|
+
...(rememberAndSerializeExtraContent(currentToolCall.callId, currentToolCall.providerMetadata, replayCacheScope).extra ?? {}),
|
|
559
726
|
};
|
|
560
727
|
emit("response.output_item.done", { output_index: currentToolCall.outputIndex, item });
|
|
561
728
|
retainFinishedItem(item as OutputItem);
|
|
@@ -652,6 +819,7 @@ export function bridgeToResponsesSSE(
|
|
|
652
819
|
handlingTranslatorOverflow = true;
|
|
653
820
|
abortCurrentToolCallForTranslatorOverflow();
|
|
654
821
|
currentWebSearch = null;
|
|
822
|
+
releasePendingWebSources();
|
|
655
823
|
const failure = adapterFailureFromEvent({
|
|
656
824
|
type: "error",
|
|
657
825
|
status: 502,
|
|
@@ -682,6 +850,7 @@ export function bridgeToResponsesSSE(
|
|
|
682
850
|
beat = undefined;
|
|
683
851
|
try { controller.close(); } catch { /* already closed */ }
|
|
684
852
|
closed = true;
|
|
853
|
+
disposeOwnedBudget();
|
|
685
854
|
gated = true;
|
|
686
855
|
stepping = false;
|
|
687
856
|
};
|
|
@@ -694,6 +863,15 @@ export function bridgeToResponsesSSE(
|
|
|
694
863
|
while (!terminated && !closed && emittedFrames === emittedAtStart) {
|
|
695
864
|
iteratorStarted = true;
|
|
696
865
|
const next = await it.next();
|
|
866
|
+
// A cancel during this await disposes the owned budget; a late event
|
|
867
|
+
// must never be processed or charged against it. Exit step() outright:
|
|
868
|
+
// falling into EOF synthesis would let closeCurrentMessage() charge
|
|
869
|
+
// finished-item retention against the disposed budget.
|
|
870
|
+
if (closed || clientCancelled) {
|
|
871
|
+
gated = true;
|
|
872
|
+
stepping = false;
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
697
875
|
if (next.done) { upstreamDone = true; break; }
|
|
698
876
|
const event = next.value;
|
|
699
877
|
let terminalEvent = false;
|
|
@@ -727,6 +905,7 @@ export function bridgeToResponsesSSE(
|
|
|
727
905
|
if (currentReasoning) closeCurrentReasoning();
|
|
728
906
|
if (currentRawReasoning) closeCurrentRawReasoning();
|
|
729
907
|
flushHiddenRawReasoning();
|
|
908
|
+
rawReasoningForNextToolCall = "";
|
|
730
909
|
if (currentToolCall) closeCurrentToolCall();
|
|
731
910
|
flushHiddenReasoningEnvelope();
|
|
732
911
|
break;
|
|
@@ -735,6 +914,10 @@ export function bridgeToResponsesSSE(
|
|
|
735
914
|
if (currentReasoning) closeCurrentReasoning();
|
|
736
915
|
if (currentRawReasoning) closeCurrentRawReasoning();
|
|
737
916
|
flushHiddenRawReasoning();
|
|
917
|
+
// Reasoning consumed by a REAL text turn, not a tool call: no cache target.
|
|
918
|
+
// Empty text deltas must not wipe reasoning that precedes a tool call
|
|
919
|
+
// (chat-completions providers emit empty content deltas mid-tool-turn).
|
|
920
|
+
if (event.text.length > 0) rawReasoningForNextToolCall = "";
|
|
738
921
|
if (currentToolCall) closeCurrentToolCall();
|
|
739
922
|
// Only flush on an explicit phase change. A later delta that omits `phase` must
|
|
740
923
|
// keep appending to the current message rather than wiping the earlier phase.
|
|
@@ -769,6 +952,12 @@ export function bridgeToResponsesSSE(
|
|
|
769
952
|
}
|
|
770
953
|
case "thinking_delta": {
|
|
771
954
|
if (options?.hideThinkingSummary) {
|
|
955
|
+
// The hidden branch returns early, so flush any raw reasoning
|
|
956
|
+
// that preceded the thinking block and clear the replay-cache
|
|
957
|
+
// candidate — otherwise a stale reasoning_raw_delta would be
|
|
958
|
+
// recorded for a LATER tool call (CodeRabbit on #971).
|
|
959
|
+
flushHiddenRawReasoning();
|
|
960
|
+
rawReasoningForNextToolCall = "";
|
|
772
961
|
({ value: hiddenThinkingText, bytes: hiddenThinkingBytes } = appendString(
|
|
773
962
|
hiddenThinkingText,
|
|
774
963
|
hiddenThinkingBytes,
|
|
@@ -780,6 +969,7 @@ export function bridgeToResponsesSSE(
|
|
|
780
969
|
if (currentMsg) closeCurrentMessage("commentary");
|
|
781
970
|
if (currentRawReasoning) closeCurrentRawReasoning();
|
|
782
971
|
flushHiddenRawReasoning();
|
|
972
|
+
if (event.thinking.length > 0) rawReasoningForNextToolCall = "";
|
|
783
973
|
if (currentToolCall) closeCurrentToolCall();
|
|
784
974
|
if (!currentReasoning) {
|
|
785
975
|
const itemId = `rs_${uuid()}`;
|
|
@@ -817,6 +1007,12 @@ export function bridgeToResponsesSSE(
|
|
|
817
1007
|
pendingRedacted.push(event.data);
|
|
818
1008
|
break;
|
|
819
1009
|
}
|
|
1010
|
+
case "kiro_redacted_reasoning": {
|
|
1011
|
+
// Stash only — see flushKiroRedactedReasoning. One blob per turn, so last wins.
|
|
1012
|
+
pendingKiroRedactedBytes = replaceRetainedString(pendingKiroRedactedBytes, event.data, "reasoning");
|
|
1013
|
+
pendingKiroRedacted = event.data;
|
|
1014
|
+
break;
|
|
1015
|
+
}
|
|
820
1016
|
case "reasoning_raw_delta": {
|
|
821
1017
|
if (options?.hideThinkingSummary) {
|
|
822
1018
|
({ value: hiddenRawReasoningText, bytes: hiddenRawReasoningBytes } = appendString(
|
|
@@ -832,8 +1028,12 @@ export function bridgeToResponsesSSE(
|
|
|
832
1028
|
if (currentToolCall) closeCurrentToolCall();
|
|
833
1029
|
if (!currentRawReasoning) {
|
|
834
1030
|
const itemId = `rs_${uuid()}`;
|
|
835
|
-
const item = { type: "reasoning", id: itemId, summary: [] as
|
|
1031
|
+
const item = { type: "reasoning", id: itemId, summary: [] as { type: string; text: string }[] };
|
|
836
1032
|
emit("response.output_item.added", { output_index: outputIndex, item });
|
|
1033
|
+
emit("response.reasoning_summary_part.added", {
|
|
1034
|
+
item_id: itemId, output_index: outputIndex, summary_index: 0,
|
|
1035
|
+
part: { type: "summary_text", text: "" },
|
|
1036
|
+
});
|
|
837
1037
|
currentRawReasoning = { itemId, outputIndex, text: "", textBytes: 0 };
|
|
838
1038
|
}
|
|
839
1039
|
({ value: currentRawReasoning.text, bytes: currentRawReasoning.textBytes } = appendString(
|
|
@@ -842,9 +1042,9 @@ export function bridgeToResponsesSSE(
|
|
|
842
1042
|
event.text,
|
|
843
1043
|
"reasoning",
|
|
844
1044
|
));
|
|
845
|
-
emit("response.
|
|
1045
|
+
emit("response.reasoning_summary_text.delta", {
|
|
846
1046
|
item_id: currentRawReasoning.itemId, output_index: currentRawReasoning.outputIndex,
|
|
847
|
-
|
|
1047
|
+
summary_index: 0, delta: event.text,
|
|
848
1048
|
});
|
|
849
1049
|
break;
|
|
850
1050
|
}
|
|
@@ -853,20 +1053,46 @@ export function bridgeToResponsesSSE(
|
|
|
853
1053
|
if (currentReasoning) closeCurrentReasoning();
|
|
854
1054
|
if (currentRawReasoning) closeCurrentRawReasoning();
|
|
855
1055
|
flushHiddenRawReasoning();
|
|
1056
|
+
if (rawReasoningForNextToolCall) {
|
|
1057
|
+
rememberReasoningForCall(event.id, rawReasoningForNextToolCall, replayCacheScope);
|
|
1058
|
+
}
|
|
856
1059
|
if (currentToolCall) closeCurrentToolCall();
|
|
857
|
-
const
|
|
858
|
-
const
|
|
1060
|
+
const effectiveName = normalizeDeclaredToolName(event.name, options?.declaredToolNames);
|
|
1061
|
+
const codeModeHelperName = effectiveName === "exec" && event.name !== effectiveName
|
|
1062
|
+
? event.name
|
|
1063
|
+
: undefined;
|
|
1064
|
+
const mapped = toolNsMap?.get(effectiveName);
|
|
1065
|
+
const realName = mapped?.name ?? effectiveName;
|
|
1066
|
+
if (options?.declaredToolNames && !options.declaredToolNames.has(effectiveName)) {
|
|
1067
|
+
const failure = responseError(
|
|
1068
|
+
502,
|
|
1069
|
+
"upstream_error",
|
|
1070
|
+
`routed provider emitted undeclared client tool "${effectiveName}"; only request-declared tools may be called`,
|
|
1071
|
+
);
|
|
1072
|
+
emit("response.failed", {
|
|
1073
|
+
response: {
|
|
1074
|
+
...responseSnapshot("failed", finishedItems),
|
|
1075
|
+
error: failure,
|
|
1076
|
+
last_error: failure,
|
|
1077
|
+
},
|
|
1078
|
+
});
|
|
1079
|
+
reportTerminal("failed");
|
|
1080
|
+
terminalEvent = true;
|
|
1081
|
+
break;
|
|
1082
|
+
}
|
|
859
1083
|
const ns = mapped?.namespace;
|
|
860
1084
|
const toolSearch = toolSearchToolNames?.has(realName) ?? false;
|
|
861
|
-
const freeform = !toolSearch && (
|
|
1085
|
+
const freeform = !toolSearch && (mapped
|
|
1086
|
+
? mapped.freeform === true
|
|
1087
|
+
: (freeformToolNames?.has(realName) ?? false));
|
|
862
1088
|
const itemId = `${toolSearch ? "tsc" : freeform ? "ctc" : "fc"}_${uuid()}`;
|
|
863
1089
|
const item = toolSearch
|
|
864
1090
|
? { type: "tool_search_call", id: itemId, call_id: event.id, execution: "client", arguments: {}, status: "in_progress" }
|
|
865
1091
|
: freeform
|
|
866
|
-
? { type: "custom_tool_call", id: itemId, call_id: event.id, name: realName, input: "", status: "in_progress" }
|
|
1092
|
+
? { type: "custom_tool_call", id: itemId, call_id: event.id, name: realName, ...(ns ? { namespace: ns } : {}), input: "", status: "in_progress" }
|
|
867
1093
|
: { type: "function_call", id: itemId, call_id: event.id, name: realName, arguments: "", status: "in_progress", ...(ns ? { namespace: ns } : {}) };
|
|
868
1094
|
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 };
|
|
1095
|
+
currentToolCall = { itemId, outputIndex, callId: event.id, name: realName, args: "", argsBytes: 0, namespace: ns, freeform, toolSearch, codeModeHelperName, providerMetadata: event.providerMetadata };
|
|
870
1096
|
budget?.openCall(event.id);
|
|
871
1097
|
break;
|
|
872
1098
|
}
|
|
@@ -885,7 +1111,7 @@ export function bridgeToResponsesSSE(
|
|
|
885
1111
|
delta: event.arguments,
|
|
886
1112
|
});
|
|
887
1113
|
}
|
|
888
|
-
if (currentToolCall.freeform) {
|
|
1114
|
+
if (currentToolCall.freeform && !currentToolCall.codeModeHelperName) {
|
|
889
1115
|
// Hold while the buffer is still an ambiguous prefix of the JSON wrapper,
|
|
890
1116
|
// then stream only the unwrapped input suffix (never rewind on mode flips).
|
|
891
1117
|
if (!FREEFORM_WRAP_PREFIX.startsWith(currentToolCall.args)) {
|
|
@@ -963,15 +1189,13 @@ export function bridgeToResponsesSSE(
|
|
|
963
1189
|
});
|
|
964
1190
|
currentWebSearch = { itemId: wsItemId2, eventId: event.id, outputIndex };
|
|
965
1191
|
}
|
|
966
|
-
|
|
1192
|
+
const safeSources = safeWebSearchSources(event.sources);
|
|
1193
|
+
closeCurrentWebSearch(event.status ?? "completed", event.queries, safeSources);
|
|
967
1194
|
// 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);
|
|
1195
|
+
if (safeSources.length > 0) {
|
|
1196
|
+
for (const source of safeSources) {
|
|
1197
|
+
if (appendSafeWebSearchSource(pendingWebSources, source)) {
|
|
1198
|
+
pendingWebSourceBytes += chargeValue(source, "tool_search_sources");
|
|
975
1199
|
}
|
|
976
1200
|
}
|
|
977
1201
|
}
|
|
@@ -984,10 +1208,18 @@ export function bridgeToResponsesSSE(
|
|
|
984
1208
|
flushHiddenRawReasoning();
|
|
985
1209
|
if (currentToolCall) closeCurrentToolCall();
|
|
986
1210
|
if (currentWebSearch) closeCurrentWebSearch("completed", []);
|
|
1211
|
+
releasePendingWebSources();
|
|
987
1212
|
// Redacted-only turns (or hidden thinking without a trailing signature event) still
|
|
988
1213
|
// need their envelope-only reasoning item so the blocks replay next turn.
|
|
989
1214
|
flushHiddenReasoningEnvelope();
|
|
990
|
-
|
|
1215
|
+
// After every close above, so the blob lands AFTER the assistant message it belongs
|
|
1216
|
+
// to and the parser's backwards pairing finds it.
|
|
1217
|
+
flushKiroRedactedReasoning();
|
|
1218
|
+
// Truncated turns must never install replacement history (#422). The buffered path
|
|
1219
|
+
// has always checked this; streaming emitted the item BEFORE reading stopReason, so
|
|
1220
|
+
// a max_tokens/content_filter turn shipped a half-written summary and then declared
|
|
1221
|
+
// itself incomplete — the same hazard, one branch over.
|
|
1222
|
+
if (options?.compaction && !isTruncatedStopReason(event.stopReason)) {
|
|
991
1223
|
// Exactly one compaction item per turn; codex-rs takes the first and fatals on 0.
|
|
992
1224
|
const item = {
|
|
993
1225
|
type: "compaction", id: `cmp_${uuid()}`,
|
|
@@ -997,14 +1229,21 @@ export function bridgeToResponsesSSE(
|
|
|
997
1229
|
retainFinishedItem(item as OutputItem, compactionTextBytes);
|
|
998
1230
|
outputIndex++;
|
|
999
1231
|
}
|
|
1000
|
-
|
|
1232
|
+
// Recognize every adapter's truncation vocabulary, not just the canonical pair.
|
|
1233
|
+
// Suppression and terminal status must agree: withholding the compaction item while
|
|
1234
|
+
// still reporting success hands codex-rs a completed response with zero compaction
|
|
1235
|
+
// items, which it treats as fatal.
|
|
1236
|
+
if (truncationReasonFor(event.stopReason)) {
|
|
1001
1237
|
// Upstream stopped before a normal completion. Surface as incomplete so the
|
|
1002
1238
|
// client can distinguish a truncated/filtered turn from a finished one.
|
|
1239
|
+
// #1926 gap 2: bound the window in which a handed-out thought signature is
|
|
1240
|
+
// not yet durable before the turn becomes externally terminal.
|
|
1241
|
+
await awaitThoughtSignatureDurability();
|
|
1003
1242
|
const response = {
|
|
1004
1243
|
...responseSnapshot("incomplete", finishedItems, event.endTurn),
|
|
1005
1244
|
usage: responsesUsage(event.usage),
|
|
1006
1245
|
incomplete_details: {
|
|
1007
|
-
reason: event.stopReason
|
|
1246
|
+
reason: truncationReasonFor(event.stopReason) ?? "content_filter",
|
|
1008
1247
|
},
|
|
1009
1248
|
};
|
|
1010
1249
|
// Cache max-output partials so previous_response_id replay can continue them;
|
|
@@ -1014,6 +1253,7 @@ export function bridgeToResponsesSSE(
|
|
|
1014
1253
|
emit("response.incomplete", { response });
|
|
1015
1254
|
reportTerminal("incomplete");
|
|
1016
1255
|
} else {
|
|
1256
|
+
await awaitThoughtSignatureDurability();
|
|
1017
1257
|
const response = { ...responseSnapshot("completed", finishedItems, event.endTurn), usage: responsesUsage(event.usage) };
|
|
1018
1258
|
options?.onCompletedResponse?.(response, event.providerState);
|
|
1019
1259
|
options?.onUsage?.(event.usage);
|
|
@@ -1032,8 +1272,10 @@ export function bridgeToResponsesSSE(
|
|
|
1032
1272
|
flushHiddenRawReasoning();
|
|
1033
1273
|
if (currentToolCall) failCurrentToolCall();
|
|
1034
1274
|
if (currentWebSearch) closeCurrentWebSearch("failed", []);
|
|
1275
|
+
releasePendingWebSources();
|
|
1035
1276
|
flushHiddenReasoningEnvelope();
|
|
1036
1277
|
options?.onUsage?.(event.usage);
|
|
1278
|
+
await awaitThoughtSignatureDurability();
|
|
1037
1279
|
emit("response.incomplete", {
|
|
1038
1280
|
response: {
|
|
1039
1281
|
...responseSnapshot("incomplete", finishedItems, event.endTurn),
|
|
@@ -1060,8 +1302,10 @@ export function bridgeToResponsesSSE(
|
|
|
1060
1302
|
flushHiddenRawReasoning();
|
|
1061
1303
|
if (currentToolCall) failCurrentToolCall();
|
|
1062
1304
|
if (currentWebSearch) closeCurrentWebSearch("failed", []);
|
|
1305
|
+
releasePendingWebSources();
|
|
1063
1306
|
const failure = adapterFailureFromEvent(event);
|
|
1064
1307
|
if (event.usage) options?.onUsage?.(event.usage);
|
|
1308
|
+
await awaitThoughtSignatureDurability();
|
|
1065
1309
|
emit("response.failed", {
|
|
1066
1310
|
response: {
|
|
1067
1311
|
...responseSnapshot("failed", finishedItems),
|
|
@@ -1070,7 +1314,9 @@ export function bridgeToResponsesSSE(
|
|
|
1070
1314
|
...(event.usage ? { usage: responsesUsage(event.usage) } : {}),
|
|
1071
1315
|
error: failure.error,
|
|
1072
1316
|
last_error: failure.error,
|
|
1073
|
-
...(
|
|
1317
|
+
...(isCyberPolicyCode(failure.error.code)
|
|
1318
|
+
? { retryable: false }
|
|
1319
|
+
: event.retryable !== undefined ? { retryable: event.retryable } : {}),
|
|
1074
1320
|
},
|
|
1075
1321
|
});
|
|
1076
1322
|
reportTerminal("failed");
|
|
@@ -1093,11 +1339,18 @@ export function bridgeToResponsesSSE(
|
|
|
1093
1339
|
flushHiddenRawReasoning();
|
|
1094
1340
|
if (currentToolCall) failCurrentToolCall();
|
|
1095
1341
|
if (currentWebSearch) closeCurrentWebSearch("failed", []);
|
|
1342
|
+
releasePendingWebSources();
|
|
1343
|
+
const failure = responseError(
|
|
1344
|
+
500,
|
|
1345
|
+
"proxy_error",
|
|
1346
|
+
redactSecretString(err instanceof Error ? err.message : String(err)),
|
|
1347
|
+
);
|
|
1096
1348
|
emit("response.failed", {
|
|
1097
1349
|
response: {
|
|
1098
1350
|
...responseSnapshot("failed", finishedItems),
|
|
1099
|
-
error:
|
|
1100
|
-
last_error:
|
|
1351
|
+
error: failure,
|
|
1352
|
+
last_error: failure,
|
|
1353
|
+
...(isCyberPolicyCode(failure.code) ? { retryable: false } : {}),
|
|
1101
1354
|
},
|
|
1102
1355
|
});
|
|
1103
1356
|
reportTerminal("failed");
|
|
@@ -1122,7 +1375,9 @@ export function bridgeToResponsesSSE(
|
|
|
1122
1375
|
flushHiddenRawReasoning();
|
|
1123
1376
|
if (currentToolCall) failCurrentToolCall();
|
|
1124
1377
|
if (currentWebSearch) closeCurrentWebSearch("failed", []);
|
|
1378
|
+
releasePendingWebSources();
|
|
1125
1379
|
options?.onUsage?.(undefined);
|
|
1380
|
+
await awaitThoughtSignatureDurability();
|
|
1126
1381
|
emit("response.incomplete", {
|
|
1127
1382
|
response: {
|
|
1128
1383
|
...responseSnapshot("incomplete", finishedItems),
|
|
@@ -1141,12 +1396,15 @@ export function bridgeToResponsesSSE(
|
|
|
1141
1396
|
/* already closed (e.g. client cancelled) */
|
|
1142
1397
|
}
|
|
1143
1398
|
closed = true;
|
|
1399
|
+
disposeOwnedBudget();
|
|
1144
1400
|
gated = true;
|
|
1145
1401
|
stepping = false;
|
|
1146
1402
|
};
|
|
1147
1403
|
|
|
1148
1404
|
const startStream = () => {
|
|
1149
1405
|
emit("response.created", { response: responseSnapshot("in_progress", []) });
|
|
1406
|
+
// Responses spec parity: clients expect an explicit in_progress frame after created.
|
|
1407
|
+
emit("response.in_progress", { response: responseSnapshot("in_progress", []) });
|
|
1150
1408
|
// The default ReadableStream strategy has HWM=1. Once one event's frames fill that
|
|
1151
1409
|
// queue, pull stepping pauses; no custom FIFO or queuing strategy is layered on top.
|
|
1152
1410
|
gated = true;
|
|
@@ -1162,6 +1420,11 @@ export function bridgeToResponsesSSE(
|
|
|
1162
1420
|
flushHiddenRawReasoning();
|
|
1163
1421
|
if (currentToolCall) failCurrentToolCall();
|
|
1164
1422
|
if (currentWebSearch) closeCurrentWebSearch("failed", []);
|
|
1423
|
+
releasePendingWebSources();
|
|
1424
|
+
// #1926 gap 2 residual: this beat callback is synchronous, so the durability
|
|
1425
|
+
// barrier is not awaited on the stall-timeout kill path. The in-memory store is
|
|
1426
|
+
// already updated; only a crash between here and the queued write loses it,
|
|
1427
|
+
// which is the pre-#1926 status quo for an already-abnormal termination.
|
|
1165
1428
|
emit("response.incomplete", {
|
|
1166
1429
|
response: {
|
|
1167
1430
|
...responseSnapshot("incomplete", finishedItems),
|
|
@@ -1176,6 +1439,7 @@ export function bridgeToResponsesSSE(
|
|
|
1176
1439
|
beat = undefined;
|
|
1177
1440
|
try { controller.close(); } catch { /* already closed */ }
|
|
1178
1441
|
closed = true;
|
|
1442
|
+
disposeOwnedBudget();
|
|
1179
1443
|
return;
|
|
1180
1444
|
}
|
|
1181
1445
|
// Wire silence is independent of upstream adapter heartbeats.
|
|
@@ -1188,6 +1452,7 @@ export function bridgeToResponsesSSE(
|
|
|
1188
1452
|
emittedFrames++;
|
|
1189
1453
|
} catch {
|
|
1190
1454
|
closed = true;
|
|
1455
|
+
disposeOwnedBudget();
|
|
1191
1456
|
}
|
|
1192
1457
|
}, heartbeatMs);
|
|
1193
1458
|
};
|
|
@@ -1205,18 +1470,41 @@ export function bridgeToResponsesSSE(
|
|
|
1205
1470
|
// cancelled turn does not leak the upstream stream or keep draining tokens (RC2).
|
|
1206
1471
|
clientCancelled = true;
|
|
1207
1472
|
closed = true;
|
|
1473
|
+
clearOwnedWatchdog();
|
|
1208
1474
|
if (beat !== undefined) clearBeatInterval(beat);
|
|
1209
1475
|
cancelUpstreamOnce();
|
|
1476
|
+
releasePendingWebSources();
|
|
1477
|
+
disposeOwnedBudget();
|
|
1210
1478
|
},
|
|
1211
1479
|
});
|
|
1212
1480
|
}
|
|
1213
1481
|
|
|
1214
1482
|
export function buildResponseJSON(
|
|
1483
|
+
events: AdapterEvent[],
|
|
1484
|
+
modelId: string,
|
|
1485
|
+
options?: Parameters<typeof buildResponseJSONWithBudget>[2],
|
|
1486
|
+
): Record<string, unknown> {
|
|
1487
|
+
// Default-budget safety net: a caller that omits the budget gets a bounded
|
|
1488
|
+
// default (disposed with the call), never the unbounded append path.
|
|
1489
|
+
if (options?.translatorBudget) return buildResponseJSONWithBudget(events, modelId, options);
|
|
1490
|
+
const budget = createTranslatorBudget();
|
|
1491
|
+
try {
|
|
1492
|
+
return buildResponseJSONWithBudget(events, modelId, { ...options, translatorBudget: budget });
|
|
1493
|
+
} finally {
|
|
1494
|
+
budget.dispose();
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
function buildResponseJSONWithBudget(
|
|
1215
1499
|
events: AdapterEvent[],
|
|
1216
1500
|
modelId: string,
|
|
1217
1501
|
options?: {
|
|
1218
1502
|
hideThinkingSummary?: boolean;
|
|
1219
|
-
toolNsMap?: Map<string, { namespace: string; name: string }>;
|
|
1503
|
+
toolNsMap?: Map<string, { namespace: string; name: string; freeform?: true }>;
|
|
1504
|
+
/** Request-visible tool names. When present, an upstream call outside this set fails closed. */
|
|
1505
|
+
declaredToolNames?: ReadonlySet<string>;
|
|
1506
|
+
/** Declared parameter schema per tool name; repairs integral-float integer args (#1611). */
|
|
1507
|
+
toolParameterSchemas?: ReadonlyMap<string, Record<string, unknown>>;
|
|
1220
1508
|
freeformToolNames?: Set<string>;
|
|
1221
1509
|
toolSearchToolNames?: Set<string>;
|
|
1222
1510
|
/** Remote compaction v2 turn — append one synthetic compaction output item (see bridgeToResponsesSSE). */
|
|
@@ -1225,9 +1513,12 @@ export function buildResponseJSON(
|
|
|
1225
1513
|
/** Raw adapter-reported usage before wire normalization (see bridgeToResponsesSSE onUsage). */
|
|
1226
1514
|
onUsage?: (usage: OcxUsage | undefined) => void;
|
|
1227
1515
|
translatorBudget?: TranslatorBudget;
|
|
1516
|
+
/** Conversation identity for the reasoning replay cache (issue #950). */
|
|
1517
|
+
replayCacheScope?: OcxReasoningReplayScopeRef;
|
|
1228
1518
|
},
|
|
1229
1519
|
): Record<string, unknown> {
|
|
1230
1520
|
const responseId = `resp_${uuid()}`;
|
|
1521
|
+
const replayCacheScope = options?.replayCacheScope;
|
|
1231
1522
|
const output: OutputItem[] = [];
|
|
1232
1523
|
const budget = options?.translatorBudget;
|
|
1233
1524
|
const encoder = new TextEncoder();
|
|
@@ -1272,7 +1563,15 @@ export function buildResponseJSON(
|
|
|
1272
1563
|
let incompleteEvent: Extract<AdapterEvent, { type: "incomplete" }> | undefined;
|
|
1273
1564
|
let endTurn: boolean | undefined;
|
|
1274
1565
|
let stopReason: string | undefined;
|
|
1566
|
+
// The adapter's stop reason exactly as it arrived. `stopReason` above is deliberately narrowed
|
|
1567
|
+
// to the two reasons that map onto a Responses `incomplete_details`; the raw value is what the
|
|
1568
|
+
// truncation guard needs, because adapters disagree on vocabulary (`length`, `refusal`, ...).
|
|
1569
|
+
let rawStopReason: string | undefined;
|
|
1275
1570
|
let cleanDone = false;
|
|
1571
|
+
// Whether the adapter emitted ANY terminal (done/error/incomplete). Distinct from `cleanDone`,
|
|
1572
|
+
// which is only true for a `done` without a stop reason. A buffered turn whose adapter simply
|
|
1573
|
+
// stopped emitting has no terminal at all, and must not be reported as a success.
|
|
1574
|
+
let sawTerminal = false;
|
|
1276
1575
|
let compactionText = "";
|
|
1277
1576
|
let compactionTextBytes = 0;
|
|
1278
1577
|
|
|
@@ -1283,22 +1582,35 @@ export function buildResponseJSON(
|
|
|
1283
1582
|
let currentSummaryReasoningBytes = 0;
|
|
1284
1583
|
let currentRawReasoning = "";
|
|
1285
1584
|
let currentRawReasoningBytes = 0;
|
|
1585
|
+
// Same replay-cache handoff as the streaming path (issue #950): the most
|
|
1586
|
+
// recently flushed raw reasoning waits for the tool call it preceded.
|
|
1587
|
+
let rawReasoningForNextToolCall = "";
|
|
1286
1588
|
// Anthropic extended-thinking round-trip (batch): see bridgeToResponsesSSE counterpart.
|
|
1287
1589
|
let batchSignature: string | undefined;
|
|
1288
1590
|
let batchSignatureBytes = 0;
|
|
1289
1591
|
let batchRedacted: string[] = [];
|
|
1290
1592
|
let batchRedactedBytes = 0;
|
|
1593
|
+
// Kiro reasoning blob, held until after the trailing flushes so it lands AFTER the assistant
|
|
1594
|
+
// message (see the streaming path). Retained because it outlives releaseTranslatedEvent.
|
|
1595
|
+
let batchKiroRedacted: string | undefined;
|
|
1596
|
+
let batchKiroRedactedBytes = 0;
|
|
1291
1597
|
let currentToolCallId = "";
|
|
1292
1598
|
let currentToolCallName = "";
|
|
1599
|
+
let currentToolCallCodeModeHelperName: string | undefined;
|
|
1293
1600
|
let currentToolCallArgs = "";
|
|
1601
|
+
let currentToolCallProviderMetadata: OcxProviderOpaqueToolCallMetadata | undefined;
|
|
1294
1602
|
let currentToolCallArgsBytes = 0;
|
|
1295
1603
|
// Web-search citations awaiting the next assistant message (attached as url_citation annotations).
|
|
1296
1604
|
let pendingWebSources: { url: string; title?: string }[] = [];
|
|
1297
1605
|
|
|
1298
|
-
const freeformInput = (
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1606
|
+
const freeformInput = (
|
|
1607
|
+
args: string,
|
|
1608
|
+
toolName: string,
|
|
1609
|
+
namespace?: string,
|
|
1610
|
+
codeModeHelperName?: string,
|
|
1611
|
+
): string => codeModeHelperName
|
|
1612
|
+
? compileCodeModeHelperInput(args, codeModeHelperName)
|
|
1613
|
+
: repairFreeformToolInput(args, toolName, namespace);
|
|
1302
1614
|
const parseArgsObj = (args: string): Record<string, unknown> => {
|
|
1303
1615
|
try { const o = JSON.parse(args); return o && typeof o === "object" ? o : {}; } catch { return {}; }
|
|
1304
1616
|
};
|
|
@@ -1306,6 +1618,7 @@ export function buildResponseJSON(
|
|
|
1306
1618
|
const flushText = (inferredPhase?: OcxMessagePhase) => {
|
|
1307
1619
|
if (!currentText) return;
|
|
1308
1620
|
const phase = currentTextPhase ?? inferredPhase;
|
|
1621
|
+
const sourceBytes = pendingWebSources.reduce((sum, source) => sum + bytesOf(JSON.stringify(source)), 0);
|
|
1309
1622
|
const annotations = pendingWebSources.map(s => ({
|
|
1310
1623
|
type: "url_citation", url: s.url, ...(s.title ? { title: s.title } : {}), start_index: 0, end_index: 0,
|
|
1311
1624
|
}));
|
|
@@ -1316,6 +1629,7 @@ export function buildResponseJSON(
|
|
|
1316
1629
|
...(phase ? { phase } : {}),
|
|
1317
1630
|
} as OutputItem;
|
|
1318
1631
|
pushOutput(item, currentTextBytes);
|
|
1632
|
+
budget?.releaseRetained(sourceBytes, { kind: "tool_search_sources" });
|
|
1319
1633
|
currentText = "";
|
|
1320
1634
|
currentTextBytes = 0;
|
|
1321
1635
|
currentTextPhase = undefined;
|
|
@@ -1350,6 +1664,7 @@ export function buildResponseJSON(
|
|
|
1350
1664
|
};
|
|
1351
1665
|
const flushRawReasoning = () => {
|
|
1352
1666
|
if (!currentRawReasoning) return;
|
|
1667
|
+
rawReasoningForNextToolCall = currentRawReasoning;
|
|
1353
1668
|
if (options?.hideThinkingSummary === true) {
|
|
1354
1669
|
// Same contract as the streaming path: no visible reasoning, txt-only envelope round-trip.
|
|
1355
1670
|
pushOutput({
|
|
@@ -1361,8 +1676,8 @@ export function buildResponseJSON(
|
|
|
1361
1676
|
return;
|
|
1362
1677
|
}
|
|
1363
1678
|
pushOutput({
|
|
1364
|
-
type: "reasoning", id: `rs_${uuid()}`,
|
|
1365
|
-
|
|
1679
|
+
type: "reasoning", id: `rs_${uuid()}`,
|
|
1680
|
+
summary: [{ type: "summary_text", text: currentRawReasoning }],
|
|
1366
1681
|
}, currentRawReasoningBytes, "reasoning");
|
|
1367
1682
|
currentRawReasoning = "";
|
|
1368
1683
|
currentRawReasoningBytes = 0;
|
|
@@ -1373,40 +1688,66 @@ export function buildResponseJSON(
|
|
|
1373
1688
|
const realName = mapped?.name ?? currentToolCallName;
|
|
1374
1689
|
const ns = mapped?.namespace;
|
|
1375
1690
|
const toolSearch = options?.toolSearchToolNames?.has(realName) ?? false;
|
|
1376
|
-
const freeform = !toolSearch && (
|
|
1691
|
+
const freeform = !toolSearch && (mapped
|
|
1692
|
+
? mapped.freeform === true
|
|
1693
|
+
: (options?.freeformToolNames?.has(realName) ?? false));
|
|
1694
|
+
// #1611: same integral-float repair as the streaming path. Keyed by the wire name
|
|
1695
|
+
// the request declared, which is the pre-namespace-mapping `currentToolCallName`.
|
|
1696
|
+
const coercedArgs = coerceIntegerToolArguments(
|
|
1697
|
+
currentToolCallArgs,
|
|
1698
|
+
options?.toolParameterSchemas?.get(currentToolCallName),
|
|
1699
|
+
ns === undefined ? realName : undefined,
|
|
1700
|
+
);
|
|
1701
|
+
// Freeform tools serialize as custom_tool_call without extra_content; remember the
|
|
1702
|
+
// signature server-side regardless so the replayed call can be re-signed (#1735).
|
|
1703
|
+
void rememberExtraContentForReplay(currentToolCallId, currentToolCallProviderMetadata, replayCacheScope);
|
|
1377
1704
|
if (toolSearch) {
|
|
1378
1705
|
pushOutput({
|
|
1379
1706
|
type: "tool_search_call", id: `tsc_${uuid()}`,
|
|
1380
1707
|
call_id: currentToolCallId, execution: "client",
|
|
1381
|
-
arguments: parseArgsObj(
|
|
1708
|
+
arguments: parseArgsObj(coercedArgs), status,
|
|
1382
1709
|
});
|
|
1383
1710
|
} else if (freeform) {
|
|
1384
1711
|
pushOutput({
|
|
1385
1712
|
type: "custom_tool_call", id: `ctc_${uuid()}`,
|
|
1386
1713
|
call_id: currentToolCallId, name: realName,
|
|
1387
|
-
|
|
1714
|
+
...(ns ? { namespace: ns } : {}),
|
|
1715
|
+
input: freeformInput(currentToolCallArgs, realName, ns, currentToolCallCodeModeHelperName), status,
|
|
1388
1716
|
});
|
|
1389
1717
|
} else {
|
|
1390
1718
|
pushOutput({
|
|
1391
1719
|
type: "function_call", id: `fc_${uuid()}`,
|
|
1392
1720
|
call_id: currentToolCallId, name: realName,
|
|
1393
|
-
arguments:
|
|
1721
|
+
arguments: coercedArgs || "{}", status,
|
|
1394
1722
|
...(ns ? { namespace: ns } : {}),
|
|
1723
|
+
...(rememberAndSerializeExtraContent(currentToolCallId, currentToolCallProviderMetadata, replayCacheScope).extra ?? {}),
|
|
1395
1724
|
});
|
|
1396
1725
|
}
|
|
1397
1726
|
budget?.closeCall(currentToolCallId);
|
|
1398
1727
|
currentToolCallId = "";
|
|
1399
1728
|
currentToolCallName = "";
|
|
1729
|
+
currentToolCallCodeModeHelperName = undefined;
|
|
1730
|
+
currentToolCallProviderMetadata = undefined;
|
|
1400
1731
|
currentToolCallArgs = "";
|
|
1401
1732
|
currentToolCallArgsBytes = 0;
|
|
1402
1733
|
};
|
|
1403
1734
|
|
|
1404
1735
|
for (const e of events) {
|
|
1736
|
+
if (errorEvent) {
|
|
1737
|
+
// Match streaming: once the turn fails, later parallel calls must not become executable
|
|
1738
|
+
// completed output. Still release every retained event in order and preserve terminal usage.
|
|
1739
|
+
if (e.type === "error" || e.type === "incomplete" || e.type === "done") {
|
|
1740
|
+
usage = e.usage ?? usage;
|
|
1741
|
+
}
|
|
1742
|
+
if (budget) releaseTranslatedEvent(e, budget);
|
|
1743
|
+
continue;
|
|
1744
|
+
}
|
|
1405
1745
|
switch (e.type) {
|
|
1406
1746
|
case "assistant_boundary":
|
|
1407
1747
|
flushText("commentary");
|
|
1408
1748
|
flushSummaryReasoning();
|
|
1409
1749
|
flushRawReasoning();
|
|
1750
|
+
rawReasoningForNextToolCall = "";
|
|
1410
1751
|
flushToolCall();
|
|
1411
1752
|
break;
|
|
1412
1753
|
case "text_delta":
|
|
@@ -1415,6 +1756,9 @@ export function buildResponseJSON(
|
|
|
1415
1756
|
if (currentText && e.phase !== undefined && currentTextPhase !== e.phase) flushText("commentary");
|
|
1416
1757
|
if (currentSummaryReasoning) flushSummaryReasoning();
|
|
1417
1758
|
if (currentRawReasoning) flushRawReasoning();
|
|
1759
|
+
// Empty text deltas (batch chat responses always carry content, often "") must
|
|
1760
|
+
// not wipe reasoning that precedes a tool call (#950 non-streaming path).
|
|
1761
|
+
if (e.text.length > 0) rawReasoningForNextToolCall = "";
|
|
1418
1762
|
if (currentToolCallId) flushToolCall();
|
|
1419
1763
|
// Compaction turns keep the summary out of normal message output (replay dedup — see
|
|
1420
1764
|
// bridgeToResponsesSSE); it ships only inside the synthetic compaction item below.
|
|
@@ -1433,6 +1777,7 @@ export function buildResponseJSON(
|
|
|
1433
1777
|
case "thinking_delta":
|
|
1434
1778
|
if (currentText) flushText("commentary");
|
|
1435
1779
|
if (currentRawReasoning) flushRawReasoning();
|
|
1780
|
+
if (e.thinking.length > 0) rawReasoningForNextToolCall = "";
|
|
1436
1781
|
if (currentToolCallId) flushToolCall();
|
|
1437
1782
|
{
|
|
1438
1783
|
({ value: currentSummaryReasoning, bytes: currentSummaryReasoningBytes } = appendBatchString(
|
|
@@ -1455,6 +1800,16 @@ export function buildResponseJSON(
|
|
|
1455
1800
|
}
|
|
1456
1801
|
batchRedacted.push(e.data);
|
|
1457
1802
|
break;
|
|
1803
|
+
case "kiro_redacted_reasoning":
|
|
1804
|
+
// Stash only — pushed after the trailing flushes. One blob per turn, so last wins.
|
|
1805
|
+
{
|
|
1806
|
+
const dataBytes = bytesOf(e.data);
|
|
1807
|
+
budget?.chargeRetained(dataBytes, { kind: "reasoning" });
|
|
1808
|
+
if (batchKiroRedactedBytes > 0) budget?.releaseRetained(batchKiroRedactedBytes, { kind: "reasoning" });
|
|
1809
|
+
batchKiroRedactedBytes = dataBytes;
|
|
1810
|
+
}
|
|
1811
|
+
batchKiroRedacted = e.data;
|
|
1812
|
+
break;
|
|
1458
1813
|
case "reasoning_raw_delta":
|
|
1459
1814
|
if (currentText) flushText("commentary");
|
|
1460
1815
|
if (currentSummaryReasoning) flushSummaryReasoning();
|
|
@@ -1465,17 +1820,35 @@ export function buildResponseJSON(
|
|
|
1465
1820
|
));
|
|
1466
1821
|
}
|
|
1467
1822
|
break;
|
|
1468
|
-
case "tool_call_start":
|
|
1823
|
+
case "tool_call_start": {
|
|
1469
1824
|
if (currentText) flushText("commentary");
|
|
1470
1825
|
if (currentSummaryReasoning) flushSummaryReasoning();
|
|
1471
1826
|
if (currentRawReasoning) flushRawReasoning();
|
|
1827
|
+
if (rawReasoningForNextToolCall) {
|
|
1828
|
+
rememberReasoningForCall(e.id, rawReasoningForNextToolCall, replayCacheScope);
|
|
1829
|
+
}
|
|
1472
1830
|
flushToolCall();
|
|
1831
|
+
const effectiveName = normalizeDeclaredToolName(e.name, options?.declaredToolNames);
|
|
1832
|
+
if (options?.declaredToolNames && !options.declaredToolNames.has(effectiveName)) {
|
|
1833
|
+
errorEvent = {
|
|
1834
|
+
type: "error",
|
|
1835
|
+
message: `routed provider emitted undeclared client tool "${effectiveName}"; only request-declared tools may be called`,
|
|
1836
|
+
status: 502,
|
|
1837
|
+
errorType: "upstream_error",
|
|
1838
|
+
};
|
|
1839
|
+
break;
|
|
1840
|
+
}
|
|
1473
1841
|
currentToolCallId = e.id;
|
|
1474
1842
|
budget?.openCall(e.id);
|
|
1475
|
-
currentToolCallName =
|
|
1843
|
+
currentToolCallName = effectiveName;
|
|
1844
|
+
currentToolCallCodeModeHelperName = effectiveName === "exec" && e.name !== effectiveName
|
|
1845
|
+
? e.name
|
|
1846
|
+
: undefined;
|
|
1476
1847
|
currentToolCallArgs = "";
|
|
1477
1848
|
currentToolCallArgsBytes = 0;
|
|
1849
|
+
currentToolCallProviderMetadata = e.providerMetadata;
|
|
1478
1850
|
break;
|
|
1851
|
+
}
|
|
1479
1852
|
case "tool_call_delta":
|
|
1480
1853
|
{
|
|
1481
1854
|
({ value: currentToolCallArgs, bytes: currentToolCallArgsBytes } = appendBatchString(
|
|
@@ -1489,7 +1862,9 @@ export function buildResponseJSON(
|
|
|
1489
1862
|
const mapped = options?.toolNsMap?.get(currentToolCallName);
|
|
1490
1863
|
const realName = mapped?.name ?? currentToolCallName;
|
|
1491
1864
|
const toolSearch = options?.toolSearchToolNames?.has(realName) ?? false;
|
|
1492
|
-
const freeform = !toolSearch && (
|
|
1865
|
+
const freeform = !toolSearch && (mapped
|
|
1866
|
+
? mapped.freeform === true
|
|
1867
|
+
: (options?.freeformToolNames?.has(realName) ?? false));
|
|
1493
1868
|
if (!freeform && !toolSearch) {
|
|
1494
1869
|
flushToolCall("incomplete");
|
|
1495
1870
|
errorEvent = {
|
|
@@ -1507,60 +1882,90 @@ export function buildResponseJSON(
|
|
|
1507
1882
|
// Batch/non-streaming output has no in_progress phase to animate — the search cell is a
|
|
1508
1883
|
// single finalized item, emitted on `end`. Begin is a no-op here.
|
|
1509
1884
|
break;
|
|
1510
|
-
case "web_search_call_end":
|
|
1885
|
+
case "web_search_call_end": {
|
|
1511
1886
|
if (currentText) flushText("commentary");
|
|
1512
1887
|
if (currentSummaryReasoning) flushSummaryReasoning();
|
|
1513
1888
|
if (currentRawReasoning) flushRawReasoning();
|
|
1514
1889
|
flushToolCall();
|
|
1890
|
+
const safeSources = safeWebSearchSources(e.sources);
|
|
1515
1891
|
pushOutput({
|
|
1516
1892
|
type: "web_search_call", id: `ws_${uuid()}`, status: e.status ?? "completed",
|
|
1517
1893
|
action: webSearchAction(e.queries),
|
|
1518
|
-
...(
|
|
1894
|
+
...(safeSources.length > 0 ? { sources: safeSources } : {}),
|
|
1519
1895
|
});
|
|
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);
|
|
1896
|
+
if (safeSources.length > 0) {
|
|
1897
|
+
for (const source of safeSources) {
|
|
1898
|
+
if (appendSafeWebSearchSource(pendingWebSources, source)) {
|
|
1899
|
+
budget?.chargeRetained(bytesOf(JSON.stringify(source)), { kind: "tool_search_sources" });
|
|
1527
1900
|
}
|
|
1528
1901
|
}
|
|
1529
1902
|
}
|
|
1530
1903
|
break;
|
|
1904
|
+
}
|
|
1531
1905
|
case "error":
|
|
1532
1906
|
errorEvent = e;
|
|
1907
|
+
sawTerminal = true;
|
|
1533
1908
|
usage = e.usage ?? usage;
|
|
1534
1909
|
break;
|
|
1535
1910
|
case "incomplete":
|
|
1536
1911
|
incompleteEvent = e;
|
|
1912
|
+
sawTerminal = true;
|
|
1537
1913
|
endTurn = e.endTurn;
|
|
1538
1914
|
if (e.providerState) options?.onProviderState?.(e.providerState);
|
|
1539
1915
|
break;
|
|
1540
1916
|
case "done":
|
|
1541
1917
|
usage = e.usage;
|
|
1918
|
+
sawTerminal = true;
|
|
1542
1919
|
endTurn = e.endTurn;
|
|
1543
1920
|
cleanDone = e.stopReason === undefined;
|
|
1921
|
+
rawStopReason = e.stopReason;
|
|
1544
1922
|
if (e.providerState) options?.onProviderState?.(e.providerState);
|
|
1545
1923
|
// Match streaming: max_tokens and content_filter both terminate as incomplete.
|
|
1546
|
-
|
|
1924
|
+
// Normalize every adapter's truncation vocabulary to the canonical pair, so a raw
|
|
1925
|
+
// `length` or `refusal` reaches the status/incomplete_details logic below instead of
|
|
1926
|
+
// silently reading as a clean stop.
|
|
1927
|
+
{
|
|
1928
|
+
const truncation = truncationReasonFor(e.stopReason);
|
|
1929
|
+
if (truncation) stopReason = truncation === "max_output_tokens" ? "max_tokens" : "content_filter";
|
|
1930
|
+
}
|
|
1547
1931
|
break;
|
|
1548
1932
|
}
|
|
1549
1933
|
if (budget) releaseTranslatedEvent(e, budget);
|
|
1550
1934
|
}
|
|
1551
1935
|
flushText(cleanDone && !errorEvent && !incompleteEvent ? "final_answer" : undefined);
|
|
1936
|
+
if (pendingWebSources.length > 0) {
|
|
1937
|
+
const sourceBytes = pendingWebSources.reduce((sum, source) => sum + bytesOf(JSON.stringify(source)), 0);
|
|
1938
|
+
pendingWebSources = [];
|
|
1939
|
+
budget?.releaseRetained(sourceBytes, { kind: "tool_search_sources" });
|
|
1940
|
+
}
|
|
1552
1941
|
flushSummaryReasoning();
|
|
1553
1942
|
flushRawReasoning();
|
|
1554
|
-
// Open tool call on a failed/incomplete turn must not land as status:"completed"
|
|
1555
|
-
|
|
1943
|
+
// Open tool call on a failed/incomplete turn must not land as status:"completed" — and neither
|
|
1944
|
+
// must one left open by a stream that stopped without any terminal at all. That case previously
|
|
1945
|
+
// fell through to "completed", handing back a function_call whose arguments were half-written
|
|
1946
|
+
// JSON, inside a turn also marked completed.
|
|
1947
|
+
if (currentToolCallId) flushToolCall(errorEvent || incompleteEvent || !sawTerminal ? "incomplete" : "completed");
|
|
1948
|
+
if (batchKiroRedacted) {
|
|
1949
|
+
// pushOutput reserves the item itself and releases the retained raw blob it replaces.
|
|
1950
|
+
pushOutput({
|
|
1951
|
+
type: "reasoning", id: `rs_${uuid()}`, summary: [],
|
|
1952
|
+
encrypted_content: encodeReasoningEnvelope({ krc: batchKiroRedacted }),
|
|
1953
|
+
}, batchKiroRedactedBytes, "reasoning");
|
|
1954
|
+
batchKiroRedacted = undefined;
|
|
1955
|
+
batchKiroRedactedBytes = 0;
|
|
1956
|
+
}
|
|
1556
1957
|
// A truncated turn must never be installed as replacement history: emit the
|
|
1557
1958
|
// compaction item only when the turn actually completed (#422).
|
|
1558
1959
|
if (
|
|
1559
1960
|
options?.compaction
|
|
1560
1961
|
&& !errorEvent
|
|
1561
1962
|
&& !incompleteEvent
|
|
1562
|
-
|
|
1563
|
-
|
|
1963
|
+
// A stream that stopped without any terminal did not complete either. The original guard
|
|
1964
|
+
// could only see explicit failure events, so an adapter EOF slipped past it and installed a
|
|
1965
|
+
// truncated summary as replacement history — the exact #422 hazard, reached by a route that
|
|
1966
|
+
// did not exist when the guard was written.
|
|
1967
|
+
&& sawTerminal
|
|
1968
|
+
&& !isTruncatedStopReason(rawStopReason)
|
|
1564
1969
|
) {
|
|
1565
1970
|
pushOutput({ type: "compaction", id: `cmp_${uuid()}`, encrypted_content: encodeCompactionSummary(compactionText) }, compactionTextBytes);
|
|
1566
1971
|
}
|
|
@@ -1570,7 +1975,13 @@ export function buildResponseJSON(
|
|
|
1570
1975
|
? "failed"
|
|
1571
1976
|
: incompleteEvent || stopReason === "max_tokens" || stopReason === "content_filter"
|
|
1572
1977
|
? "incomplete"
|
|
1573
|
-
:
|
|
1978
|
+
: sawTerminal
|
|
1979
|
+
? "completed"
|
|
1980
|
+
// The adapter stopped emitting without any terminal, so the turn was cut short. Streaming
|
|
1981
|
+
// already reports this as response.incomplete / adapter_eof (see the !terminated branch);
|
|
1982
|
+
// defaulting the buffered path to "completed" handed callers a truncated turn — including
|
|
1983
|
+
// one carrying a never-closed tool call with half-written JSON arguments — as a success.
|
|
1984
|
+
: "incomplete";
|
|
1574
1985
|
options?.onUsage?.(incompleteEvent?.usage ?? usage);
|
|
1575
1986
|
return {
|
|
1576
1987
|
id: responseId, object: "response",
|
|
@@ -1579,7 +1990,9 @@ export function buildResponseJSON(
|
|
|
1579
1990
|
model: modelId, output,
|
|
1580
1991
|
...(endTurn !== undefined ? { end_turn: endTurn } : {}),
|
|
1581
1992
|
...(failure ? { error: failure.error, last_error: failure.error } : {}),
|
|
1582
|
-
...(
|
|
1993
|
+
...(failure && isCyberPolicyCode(failure.error.code)
|
|
1994
|
+
? { retryable: false }
|
|
1995
|
+
: errorEvent?.retryable !== undefined ? { retryable: errorEvent.retryable } : {}),
|
|
1583
1996
|
...(incompleteEvent ? {
|
|
1584
1997
|
incomplete_details: {
|
|
1585
1998
|
reason: incompleteEvent.reason,
|
|
@@ -1590,6 +2003,10 @@ export function buildResponseJSON(
|
|
|
1590
2003
|
incomplete_details: { reason: "max_output_tokens" },
|
|
1591
2004
|
} : stopReason === "content_filter" ? {
|
|
1592
2005
|
incomplete_details: { reason: "content_filter" },
|
|
2006
|
+
} : !sawTerminal ? {
|
|
2007
|
+
// Same reason string the streaming path uses, so a caller sees one signal for one condition
|
|
2008
|
+
// regardless of which surface it asked for.
|
|
2009
|
+
incomplete_details: { reason: "adapter_eof" },
|
|
1593
2010
|
} : {}),
|
|
1594
2011
|
usage: responsesUsage(incompleteEvent?.usage ?? usage),
|
|
1595
2012
|
};
|
|
@@ -1604,12 +2021,15 @@ export function formatErrorResponse(
|
|
|
1604
2021
|
const error = classifyError(status, type, message);
|
|
1605
2022
|
if (isCyberPolicyCode(options?.code)) {
|
|
1606
2023
|
error.code = CYBER_POLICY_ERROR_CODE;
|
|
1607
|
-
error.type =
|
|
2024
|
+
error.type = cyberPolicyErrorType(type);
|
|
1608
2025
|
}
|
|
1609
2026
|
const finalStatus = error.code === CYBER_POLICY_ERROR_CODE ? 400 : status;
|
|
1610
2027
|
const headers = new Headers({ "Content-Type": "application/json" });
|
|
1611
2028
|
const retryAfter = options?.retryAfter?.trim();
|
|
1612
|
-
if (
|
|
2029
|
+
if (error.code !== CYBER_POLICY_ERROR_CODE
|
|
2030
|
+
&& retryAfter
|
|
2031
|
+
&& retryAfter.length > 0
|
|
2032
|
+
&& retryAfter.length <= 128) {
|
|
1613
2033
|
headers.set("Retry-After", retryAfter);
|
|
1614
2034
|
}
|
|
1615
2035
|
return new Response(JSON.stringify({ error }), {
|