@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/server/relay.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import type { ResponsesTerminalStatus } from "../bridge";
|
|
2
|
+
import {
|
|
3
|
+
cyberPolicyErrorType,
|
|
4
|
+
CYBER_POLICY_ERROR_CODE,
|
|
5
|
+
CYBER_POLICY_FALLBACK_MESSAGE,
|
|
6
|
+
isCyberPolicyCode,
|
|
7
|
+
isCyberPolicyMessage,
|
|
8
|
+
} from "../lib/errors";
|
|
9
|
+
import { redactSecretString } from "../lib/redact";
|
|
2
10
|
import { isTranslatorBudgetExceededError } from "../lib/translator-budget";
|
|
3
11
|
import { isUsageDebugEnabled } from "../usage/debug";
|
|
4
12
|
import {
|
|
@@ -6,20 +14,49 @@ import {
|
|
|
6
14
|
addFinalRequestLog,
|
|
7
15
|
httpStatusForRequestLogTerminal,
|
|
8
16
|
inspectResponseLogJson,
|
|
9
|
-
inspectResponseLogSsePayload,
|
|
10
17
|
inspectResponseLogSsePayloadParsed,
|
|
11
18
|
recordFirstOutput,
|
|
12
19
|
type RequestLogContext,
|
|
13
20
|
type RequestLogEntry,
|
|
14
21
|
} from "./request-log";
|
|
22
|
+
import {
|
|
23
|
+
BoundedSseFrameBuffer,
|
|
24
|
+
joinSseFrameBytes,
|
|
25
|
+
MAX_CLIENT_SSE_FRAME_BYTES,
|
|
26
|
+
} from "./sse-frame-buffer";
|
|
27
|
+
import { replaceSseDataPayload } from "./sse-payload-rewrite";
|
|
15
28
|
|
|
16
29
|
const nativePassthroughSseResponses = new WeakSet<Response>();
|
|
17
30
|
const eagerRelaySseResponses = new WeakSet<Response>();
|
|
18
31
|
|
|
19
|
-
export const MAX_INSPECTION_SSE_FRAME_BYTES =
|
|
32
|
+
export const MAX_INSPECTION_SSE_FRAME_BYTES = MAX_CLIENT_SSE_FRAME_BYTES;
|
|
20
33
|
export const MAX_COMPLETED_OUTPUT_ITEMS = 256;
|
|
21
34
|
export const MAX_COMPLETED_OUTPUT_ITEM_SOURCE_BYTES = 8 * 1024 * 1024;
|
|
22
35
|
export const MAX_TAIL_ERROR_MESSAGE_CHARS = 512;
|
|
36
|
+
const ADAPTER_EOF_INCOMPLETE_PAYLOAD = JSON.stringify({
|
|
37
|
+
type: "response.incomplete",
|
|
38
|
+
response: {
|
|
39
|
+
status: "incomplete",
|
|
40
|
+
incomplete_details: { reason: "adapter_eof" },
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
const DONE_SSE_FRAME_TEXT = "data: [DONE]\n\n";
|
|
44
|
+
const FAILED_TAIL_FALLBACK_PAYLOAD = JSON.stringify({
|
|
45
|
+
type: "response.failed",
|
|
46
|
+
response: {
|
|
47
|
+
status: "failed",
|
|
48
|
+
error: {
|
|
49
|
+
type: "upstream_error",
|
|
50
|
+
code: "upstream_reset",
|
|
51
|
+
message: "Upstream stream terminated unexpectedly",
|
|
52
|
+
},
|
|
53
|
+
last_error: {
|
|
54
|
+
type: "upstream_error",
|
|
55
|
+
code: "upstream_reset",
|
|
56
|
+
message: "Upstream stream terminated unexpectedly",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
});
|
|
23
60
|
|
|
24
61
|
export type InspectionCounters = {
|
|
25
62
|
frameBufferHighWaterBytes: number;
|
|
@@ -95,6 +132,122 @@ export function buildFailedTailPayload(err: unknown): string {
|
|
|
95
132
|
});
|
|
96
133
|
}
|
|
97
134
|
|
|
135
|
+
function buildFailedTailPayloadOrFallback(err: unknown): string {
|
|
136
|
+
try {
|
|
137
|
+
return buildFailedTailPayload(err);
|
|
138
|
+
} catch {
|
|
139
|
+
// Error.message and String(error) may execute hostile accessors. Preserve a
|
|
140
|
+
// bounded protocol terminal even when diagnostic serialization is unsafe.
|
|
141
|
+
return FAILED_TAIL_FALLBACK_PAYLOAD;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function failedTailFrame(encoder: TextEncoder, err: unknown): Uint8Array {
|
|
146
|
+
const payload = buildFailedTailPayloadOrFallback(err);
|
|
147
|
+
return encoder.encode(`\n\nevent: response.failed\ndata: ${payload}\n\n${DONE_SSE_FRAME_TEXT}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export type SseTerminalOutputBoundary = {
|
|
151
|
+
feed(chunk: Uint8Array): Uint8Array;
|
|
152
|
+
finish(): Uint8Array;
|
|
153
|
+
terminalSeen(): boolean;
|
|
154
|
+
doneSeen(): boolean;
|
|
155
|
+
dispose(): void;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Frame-aware client output boundary shared by both native Responses relays.
|
|
160
|
+
* It buffers only the current incomplete SSE block under the same hard byte
|
|
161
|
+
* cap as inspection, forwards complete blocks through the first Responses
|
|
162
|
+
* terminal, and drops every later block/byte. A premature [DONE] is held until
|
|
163
|
+
* a terminal arrives so clean EOF can synthesize one terminal and one sentinel.
|
|
164
|
+
*/
|
|
165
|
+
export function createSseTerminalOutputBoundary(): SseTerminalOutputBoundary {
|
|
166
|
+
const decoder = new TextDecoder();
|
|
167
|
+
const encoder = new TextEncoder();
|
|
168
|
+
const framer = new BoundedSseFrameBuffer(MAX_INSPECTION_SSE_FRAME_BYTES);
|
|
169
|
+
let terminal = false;
|
|
170
|
+
let done = false;
|
|
171
|
+
let pendingDone: { block: Uint8Array; delimiter: Uint8Array } | null = null;
|
|
172
|
+
let disposed = false;
|
|
173
|
+
|
|
174
|
+
const processFrames = (
|
|
175
|
+
frames: ReturnType<BoundedSseFrameBuffer["feed"]>,
|
|
176
|
+
): Uint8Array => {
|
|
177
|
+
if (disposed || terminal || frames.length === 0) return new Uint8Array(0);
|
|
178
|
+
const output: Uint8Array[] = [];
|
|
179
|
+
let responsesTerminal = false;
|
|
180
|
+
for (const frame of frames) {
|
|
181
|
+
const payload = sseDataPayload(decoder.decode(frame.block));
|
|
182
|
+
const isDone = payload === "[DONE]";
|
|
183
|
+
const parsed = payload === null ? undefined : parseSsePayload(payload);
|
|
184
|
+
const policyError = parsed !== undefined && isPolicyRewriteType(parsed)
|
|
185
|
+
? cyberPolicyTerminalError(parsed)
|
|
186
|
+
: undefined;
|
|
187
|
+
const outboundBlock = policyError
|
|
188
|
+
? encoder.encode(rewritePolicyTerminalBlock(
|
|
189
|
+
decoder.decode(frame.block),
|
|
190
|
+
policyFailurePayload(policyError, parsed),
|
|
191
|
+
))
|
|
192
|
+
: frame.block;
|
|
193
|
+
if (isDone) {
|
|
194
|
+
done = true;
|
|
195
|
+
if (responsesTerminal) {
|
|
196
|
+
output.push(outboundBlock, frame.delimiter);
|
|
197
|
+
} else if (!pendingDone) {
|
|
198
|
+
// Do not expose a sentinel before a Responses terminal. If EOF
|
|
199
|
+
// follows, the synthetic incomplete path owns the one sentinel;
|
|
200
|
+
// if a terminal arrives later, this pending frame is emitted then.
|
|
201
|
+
pendingDone = { block: outboundBlock, delimiter: frame.delimiter };
|
|
202
|
+
}
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
// Preserve every frame through the first Responses terminal. Every
|
|
206
|
+
// later non-DONE frame is dropped.
|
|
207
|
+
if (!responsesTerminal) output.push(outboundBlock, frame.delimiter);
|
|
208
|
+
if (!responsesTerminal && payload && terminalStatusFromParsed(parsed)) {
|
|
209
|
+
responsesTerminal = true;
|
|
210
|
+
if (pendingDone) {
|
|
211
|
+
output.push(pendingDone.block, pendingDone.delimiter);
|
|
212
|
+
pendingDone = null;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (responsesTerminal) {
|
|
217
|
+
terminal = true;
|
|
218
|
+
framer.dispose();
|
|
219
|
+
}
|
|
220
|
+
return joinSseFrameBytes(output);
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
feed(chunk) {
|
|
225
|
+
if (disposed || terminal) return new Uint8Array(0);
|
|
226
|
+
return processFrames(framer.feed(chunk));
|
|
227
|
+
},
|
|
228
|
+
finish() {
|
|
229
|
+
if (disposed || terminal) return new Uint8Array(0);
|
|
230
|
+
const tail = framer.finish();
|
|
231
|
+
if (tail.byteLength === 0) return new Uint8Array(0);
|
|
232
|
+
// EOF may cut off the final SSE block before its blank-line delimiter.
|
|
233
|
+
// Feed it through the exact same parser/rewrite/terminal path as a
|
|
234
|
+
// complete frame, using a synthetic delimiter so the client receives a
|
|
235
|
+
// dispatchable event rather than an unterminated tail.
|
|
236
|
+
const tailText = decoder.decode(tail);
|
|
237
|
+
const delimiter = encoder.encode(tailText.includes("\r\n") ? "\r\n\r\n" : "\n\n");
|
|
238
|
+
return processFrames([{ block: tail, delimiter }]);
|
|
239
|
+
},
|
|
240
|
+
terminalSeen: () => terminal,
|
|
241
|
+
doneSeen: () => done,
|
|
242
|
+
dispose() {
|
|
243
|
+
if (disposed) return;
|
|
244
|
+
disposed = true;
|
|
245
|
+
pendingDone = null;
|
|
246
|
+
framer.dispose();
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
98
251
|
/**
|
|
99
252
|
* Relay a passthrough SSE body like relayWithAbort, but convert a MID-STREAM failure (upstream
|
|
100
253
|
* reset after headers) into a clean terminal: any partial block is closed off, then a synthetic
|
|
@@ -110,26 +263,88 @@ export function relaySseWithFailedTail(
|
|
|
110
263
|
): ReadableStream<Uint8Array> {
|
|
111
264
|
const reader = body.getReader();
|
|
112
265
|
const encoder = new TextEncoder();
|
|
266
|
+
const terminalBoundary = createSseTerminalOutputBoundary();
|
|
267
|
+
let closed = false;
|
|
268
|
+
const relayChunk = (
|
|
269
|
+
controller: ReadableStreamDefaultController<Uint8Array>,
|
|
270
|
+
value: Uint8Array,
|
|
271
|
+
): "terminal" | "output" | "buffered" => {
|
|
272
|
+
const outbound = terminalBoundary.feed(value);
|
|
273
|
+
if (outbound.byteLength > 0) controller.enqueue(outbound);
|
|
274
|
+
if (!terminalBoundary.terminalSeen()) return outbound.byteLength > 0 ? "output" : "buffered";
|
|
275
|
+
|
|
276
|
+
// A Responses terminal frame is the protocol boundary. Some compatible
|
|
277
|
+
// gateways leave the HTTP connection open after response.completed, which
|
|
278
|
+
// otherwise leaves Codex waiting forever even though the model turn is done.
|
|
279
|
+
// Preserve through the terminal block only, add the conventional sentinel
|
|
280
|
+
// when there was no real [DONE] data event, then stop reading upstream.
|
|
281
|
+
if (!terminalBoundary.doneSeen()) {
|
|
282
|
+
controller.enqueue(doneFrame(encoder));
|
|
283
|
+
}
|
|
284
|
+
closed = true;
|
|
285
|
+
controller.close();
|
|
286
|
+
const reason = "Responses terminal event received";
|
|
287
|
+
// Notify the tee inspection branch as well. It has already received the
|
|
288
|
+
// same terminal-bearing upstream chunk, so its bounded drain records the
|
|
289
|
+
// real terminal and then releases the turn/upstream keep-alive connection.
|
|
290
|
+
onClientGone?.(reason);
|
|
291
|
+
reader.cancel(reason).catch(() => {});
|
|
292
|
+
terminalBoundary.dispose();
|
|
293
|
+
return "terminal";
|
|
294
|
+
};
|
|
113
295
|
return new ReadableStream<Uint8Array>({
|
|
114
296
|
async pull(controller) {
|
|
115
297
|
try {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
298
|
+
for (;;) {
|
|
299
|
+
const { done, value } = await reader.read();
|
|
300
|
+
if (done) {
|
|
301
|
+
const tail = terminalBoundary.finish();
|
|
302
|
+
if (tail.byteLength > 0) controller.enqueue(tail);
|
|
303
|
+
if (terminalBoundary.terminalSeen()) {
|
|
304
|
+
if (!terminalBoundary.doneSeen()) controller.enqueue(doneFrame(encoder));
|
|
305
|
+
} else {
|
|
306
|
+
// A clean upstream EOF is still a failed Responses turn when no
|
|
307
|
+
// protocol terminal arrived. Make that state explicit so Codex
|
|
308
|
+
// does not treat HTTP 200 + bare EOF as a retryable disconnect.
|
|
309
|
+
const incomplete = adapterEofIncompleteFrame(encoder);
|
|
310
|
+
controller.enqueue(incomplete);
|
|
311
|
+
controller.enqueue(doneFrame(encoder));
|
|
312
|
+
}
|
|
313
|
+
terminalBoundary.dispose();
|
|
314
|
+
controller.close();
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const result = relayChunk(controller, value);
|
|
318
|
+
if (result !== "buffered") return;
|
|
120
319
|
}
|
|
121
|
-
controller.enqueue(value);
|
|
122
320
|
} catch (err) {
|
|
123
|
-
|
|
321
|
+
let partial: Uint8Array = new Uint8Array(0);
|
|
322
|
+
let tailTerminal = false;
|
|
323
|
+
try {
|
|
324
|
+
partial = terminalBoundary.finish();
|
|
325
|
+
tailTerminal = terminalBoundary.terminalSeen();
|
|
326
|
+
} catch {
|
|
327
|
+
// A near-cap ambiguous delimiter tail may itself overflow at EOF.
|
|
328
|
+
// Preserve the original read/framing failure and continue emitting
|
|
329
|
+
// the bounded failed tail instead of letting cleanup throw again.
|
|
330
|
+
}
|
|
331
|
+
terminalBoundary.dispose();
|
|
332
|
+
if (closed) return;
|
|
124
333
|
try {
|
|
125
|
-
|
|
126
|
-
|
|
334
|
+
if (partial.byteLength > 0) controller.enqueue(partial);
|
|
335
|
+
if (tailTerminal) {
|
|
336
|
+
if (!terminalBoundary.doneSeen()) controller.enqueue(doneFrame(encoder));
|
|
337
|
+
} else {
|
|
338
|
+
// Leading blank line terminates a partial SSE block so the failed frame parses cleanly.
|
|
339
|
+
controller.enqueue(failedTailFrame(encoder, err));
|
|
340
|
+
}
|
|
127
341
|
controller.close();
|
|
128
342
|
} catch { /* client already torn down */ }
|
|
129
343
|
upstream.abort();
|
|
130
344
|
}
|
|
131
345
|
},
|
|
132
346
|
cancel(reason) {
|
|
347
|
+
terminalBoundary.dispose();
|
|
133
348
|
if (onClientGone) onClientGone(reason);
|
|
134
349
|
else upstream.abort(reason);
|
|
135
350
|
reader.cancel(reason).catch(() => {});
|
|
@@ -137,11 +352,12 @@ export function relaySseWithFailedTail(
|
|
|
137
352
|
});
|
|
138
353
|
}
|
|
139
354
|
|
|
140
|
-
export function nextSseBlock(buffer: string): { block: string; rest: string } | null {
|
|
355
|
+
export function nextSseBlock(buffer: string): { block: string; delimiter: string; rest: string } | null {
|
|
141
356
|
const match = buffer.match(/\r?\n\r?\n/);
|
|
142
357
|
if (!match || match.index === undefined) return null;
|
|
143
358
|
return {
|
|
144
359
|
block: buffer.slice(0, match.index),
|
|
360
|
+
delimiter: match[0],
|
|
145
361
|
rest: buffer.slice(match.index + match[0].length),
|
|
146
362
|
};
|
|
147
363
|
}
|
|
@@ -156,15 +372,139 @@ export function sseDataPayload(block: string): string | null {
|
|
|
156
372
|
return data.length > 0 ? data.join("\n") : null;
|
|
157
373
|
}
|
|
158
374
|
|
|
159
|
-
|
|
160
|
-
|
|
375
|
+
type JsonRecord = Record<string, unknown>;
|
|
376
|
+
|
|
377
|
+
function asJsonRecord(value: unknown): JsonRecord | null {
|
|
378
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
379
|
+
? value as JsonRecord
|
|
380
|
+
: null;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function stringField(record: JsonRecord | null, key: string): string | undefined {
|
|
384
|
+
const value = record?.[key];
|
|
385
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Return a high-confidence policy error carried by an upstream terminal shape.
|
|
390
|
+
* Deliberately inspect structured error fields and known refusal copy only — a
|
|
391
|
+
* bare `cyber_policy` token in an unrelated payload is not sufficient.
|
|
392
|
+
*/
|
|
393
|
+
function cyberPolicyTerminalError(parsed: unknown): { message: string; type?: string } | undefined {
|
|
394
|
+
const root = asJsonRecord(parsed);
|
|
395
|
+
if (!root) return undefined;
|
|
396
|
+
const response = asJsonRecord(root.response);
|
|
397
|
+
const candidates = [
|
|
398
|
+
asJsonRecord(root.error),
|
|
399
|
+
asJsonRecord(root.last_error),
|
|
400
|
+
asJsonRecord(response?.error),
|
|
401
|
+
asJsonRecord(response?.incomplete_details),
|
|
402
|
+
root,
|
|
403
|
+
response,
|
|
404
|
+
];
|
|
405
|
+
for (const candidate of candidates) {
|
|
406
|
+
if (!candidate) continue;
|
|
407
|
+
if (isCyberPolicyCode(stringField(candidate, "code"))) {
|
|
408
|
+
return {
|
|
409
|
+
message: stringField(candidate, "message")
|
|
410
|
+
?? CYBER_POLICY_FALLBACK_MESSAGE,
|
|
411
|
+
...(stringField(candidate, "type") ? { type: stringField(candidate, "type") } : {}),
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
for (const candidate of candidates) {
|
|
416
|
+
const message = stringField(candidate, "message");
|
|
417
|
+
if (message && isCyberPolicyMessage(message)) {
|
|
418
|
+
return {
|
|
419
|
+
message,
|
|
420
|
+
...(stringField(candidate, "type") ? { type: stringField(candidate, "type") } : {}),
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return undefined;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function parseSsePayload(payload: string): unknown | undefined {
|
|
428
|
+
if (payload === "[DONE]") return undefined;
|
|
161
429
|
try {
|
|
162
|
-
return
|
|
430
|
+
return JSON.parse(payload);
|
|
163
431
|
} catch {
|
|
164
|
-
return
|
|
432
|
+
return undefined;
|
|
165
433
|
}
|
|
166
434
|
}
|
|
167
435
|
|
|
436
|
+
function isPolicyRewriteType(parsed: unknown): boolean {
|
|
437
|
+
const type = asJsonRecord(parsed)?.type;
|
|
438
|
+
return type === "response.failed" || type === "response.incomplete" || type === "error";
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function rewritePolicyTerminalBlock(block: string, payload: string): string {
|
|
442
|
+
const newline = block.includes("\r\n") ? "\r\n" : "\n";
|
|
443
|
+
const rewritten = replaceSseDataPayload(block, payload);
|
|
444
|
+
const lines = rewritten.split(/\r?\n/);
|
|
445
|
+
let eventRewritten = false;
|
|
446
|
+
const withEvent = lines.map(line => {
|
|
447
|
+
if (!eventRewritten && line.startsWith("event:")) {
|
|
448
|
+
eventRewritten = true;
|
|
449
|
+
return "event: response.failed";
|
|
450
|
+
}
|
|
451
|
+
return line;
|
|
452
|
+
});
|
|
453
|
+
if (!eventRewritten) withEvent.unshift("event: response.failed");
|
|
454
|
+
return withEvent.join(newline);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function policyFailurePayload(policyError: { message: string; type?: string }, parsed: unknown): string {
|
|
458
|
+
const error = {
|
|
459
|
+
type: cyberPolicyErrorType(policyError.type),
|
|
460
|
+
code: CYBER_POLICY_ERROR_CODE,
|
|
461
|
+
message: redactSecretString(policyError.message).slice(0, MAX_TAIL_ERROR_MESSAGE_CHARS),
|
|
462
|
+
};
|
|
463
|
+
const root = asJsonRecord(parsed);
|
|
464
|
+
const originalResponse = asJsonRecord(root?.response);
|
|
465
|
+
const preservedResponse = Object.fromEntries(
|
|
466
|
+
Object.entries(originalResponse ?? {}).filter(([key]) => key !== "incomplete_details"),
|
|
467
|
+
);
|
|
468
|
+
const response = {
|
|
469
|
+
...preservedResponse,
|
|
470
|
+
status: "failed",
|
|
471
|
+
error,
|
|
472
|
+
last_error: error,
|
|
473
|
+
retryable: false,
|
|
474
|
+
};
|
|
475
|
+
// Responses event metadata such as sequence_number is normally top-level.
|
|
476
|
+
// Keep it (and any other non-error, non-response fields) while replacing only
|
|
477
|
+
// the protocol type and error envelope.
|
|
478
|
+
const preservedRoot = root
|
|
479
|
+
? Object.fromEntries(Object.entries(root).filter(([key]) => (
|
|
480
|
+
key !== "type"
|
|
481
|
+
&& key !== "response"
|
|
482
|
+
&& key !== "error"
|
|
483
|
+
&& key !== "last_error"
|
|
484
|
+
&& key !== "retryable"
|
|
485
|
+
)))
|
|
486
|
+
: {};
|
|
487
|
+
return JSON.stringify({
|
|
488
|
+
...preservedRoot,
|
|
489
|
+
type: "response.failed",
|
|
490
|
+
retryable: false,
|
|
491
|
+
response,
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
export function adapterEofIncompleteFrame(encoder: TextEncoder): Uint8Array {
|
|
496
|
+
return encoder.encode(`event: response.incomplete\ndata: ${ADAPTER_EOF_INCOMPLETE_PAYLOAD}\n\n`);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
export function doneFrame(encoder: TextEncoder): Uint8Array {
|
|
500
|
+
return encoder.encode(DONE_SSE_FRAME_TEXT);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
export function terminalStatusFromSsePayload(payload: string): ResponsesTerminalStatus | null {
|
|
504
|
+
if (payload === "[DONE]") return null;
|
|
505
|
+
return terminalStatusFromParsed(parseSsePayload(payload));
|
|
506
|
+
}
|
|
507
|
+
|
|
168
508
|
/** True when a native Responses SSE payload carries the FIRST kind of non-empty model output. */
|
|
169
509
|
export function isFirstOutputSsePayload(payload: string | null): boolean {
|
|
170
510
|
if (!payload || payload === "[DONE]") return false;
|
|
@@ -202,14 +542,16 @@ function createFirstOutputReporter(onFirstOutput?: () => void): {
|
|
|
202
542
|
}
|
|
203
543
|
|
|
204
544
|
export function terminalStatusFromParsed(parsed: unknown): ResponsesTerminalStatus | null {
|
|
205
|
-
|
|
206
|
-
switch (
|
|
545
|
+
const type = asJsonRecord(parsed)?.type;
|
|
546
|
+
switch (type) {
|
|
207
547
|
case "response.completed":
|
|
208
548
|
return "completed";
|
|
209
549
|
case "response.failed":
|
|
210
550
|
return "failed";
|
|
211
551
|
case "response.incomplete":
|
|
212
|
-
return "incomplete";
|
|
552
|
+
return cyberPolicyTerminalError(parsed) ? "failed" : "incomplete";
|
|
553
|
+
case "error":
|
|
554
|
+
return cyberPolicyTerminalError(parsed) ? "failed" : null;
|
|
213
555
|
default:
|
|
214
556
|
return null;
|
|
215
557
|
}
|
|
@@ -245,53 +587,52 @@ export function trackSseForRequestLog(
|
|
|
245
587
|
onFirstOutput?: () => void,
|
|
246
588
|
): ReadableStream<Uint8Array> {
|
|
247
589
|
const reader = body.getReader();
|
|
248
|
-
const decoder = new TextDecoder();
|
|
249
|
-
let buffer = "";
|
|
250
590
|
let terminalReported = false;
|
|
251
|
-
|
|
591
|
+
let cancelled = false;
|
|
252
592
|
|
|
253
593
|
const reportTerminal = (status: ResponsesTerminalStatus) => {
|
|
254
594
|
if (terminalReported) return;
|
|
255
595
|
terminalReported = true;
|
|
256
596
|
onTerminal(status);
|
|
257
597
|
};
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
const inspectChunk = (value: Uint8Array) => {
|
|
268
|
-
buffer += decoder.decode(value, { stream: true });
|
|
269
|
-
let next: { block: string; rest: string } | null;
|
|
270
|
-
while ((next = nextSseBlock(buffer))) {
|
|
271
|
-
buffer = next.rest;
|
|
272
|
-
inspectPayload(sseDataPayload(next.block));
|
|
273
|
-
}
|
|
274
|
-
};
|
|
598
|
+
// Reuse the byte-bounded inspector so translated responses cannot retain an
|
|
599
|
+
// unterminated upstream frame or parse the same event once per observer.
|
|
600
|
+
const inspector = createSseInspector({
|
|
601
|
+
onTerminal: reportTerminal,
|
|
602
|
+
logCtx,
|
|
603
|
+
onFirstOutput,
|
|
604
|
+
});
|
|
275
605
|
|
|
276
606
|
return new ReadableStream<Uint8Array>({
|
|
277
607
|
async pull(controller) {
|
|
278
608
|
try {
|
|
279
609
|
const { done, value } = await reader.read();
|
|
280
610
|
if (done) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
611
|
+
if (!cancelled) {
|
|
612
|
+
inspector.finish();
|
|
613
|
+
if (!terminalReported) reportTerminal("incomplete");
|
|
614
|
+
}
|
|
615
|
+
inspector.dispose();
|
|
284
616
|
controller.close();
|
|
285
617
|
return;
|
|
286
618
|
}
|
|
287
|
-
|
|
619
|
+
inspector.feed(value);
|
|
288
620
|
controller.enqueue(value);
|
|
289
621
|
} catch (err) {
|
|
290
|
-
|
|
622
|
+
// The upstream read rejected: the 200 body died mid-flight. Client
|
|
623
|
+
// cancellation is the caller's separate 499 path, so a cancel-drained
|
|
624
|
+
// pending read (cancelled=true) must not carry the truncation marker.
|
|
625
|
+
if (!cancelled && !terminalReported && logCtx?.activeAttempt) {
|
|
626
|
+
logCtx.activeAttempt.streamAborted = true;
|
|
627
|
+
}
|
|
628
|
+
if (!cancelled && !terminalReported) reportTerminal("incomplete");
|
|
629
|
+
inspector.dispose();
|
|
291
630
|
try { controller.error(err); } catch { /* already torn down */ }
|
|
292
631
|
}
|
|
293
632
|
},
|
|
294
633
|
cancel(reason) {
|
|
634
|
+
cancelled = true;
|
|
635
|
+
inspector.dispose();
|
|
295
636
|
onCancel();
|
|
296
637
|
reader.cancel(reason).catch(() => {});
|
|
297
638
|
},
|
|
@@ -402,38 +743,23 @@ export function relaySseWithHeartbeat(
|
|
|
402
743
|
): ReadableStream<Uint8Array> | null {
|
|
403
744
|
if (!body) return null;
|
|
404
745
|
const reader = body.getReader();
|
|
405
|
-
const decoder = new TextDecoder();
|
|
406
746
|
const heartbeat = new TextEncoder().encode(": opencodex keepalive\n\n");
|
|
407
747
|
let timer: ReturnType<typeof setInterval> | undefined;
|
|
408
748
|
let closed = false;
|
|
409
749
|
let clientCancelled = false;
|
|
410
750
|
let terminalReported = false;
|
|
411
|
-
let buffer = "";
|
|
412
751
|
|
|
413
752
|
const reportTerminal = (status: ResponsesTerminalStatus) => {
|
|
414
753
|
if (terminalReported || clientCancelled || closed) return;
|
|
415
754
|
terminalReported = true;
|
|
416
755
|
onTerminal?.(status);
|
|
417
756
|
};
|
|
418
|
-
|
|
419
|
-
const inspectPayload = (payload: string | null) => {
|
|
420
|
-
if (!payload) return;
|
|
421
|
-
const status = terminalStatusFromSsePayload(payload);
|
|
422
|
-
if (status) reportTerminal(status);
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
const inspectChunk = (value: Uint8Array) => {
|
|
426
|
-
buffer += decoder.decode(value, { stream: true });
|
|
427
|
-
let next: { block: string; rest: string } | null;
|
|
428
|
-
while ((next = nextSseBlock(buffer))) {
|
|
429
|
-
buffer = next.rest;
|
|
430
|
-
inspectPayload(sseDataPayload(next.block));
|
|
431
|
-
}
|
|
432
|
-
};
|
|
757
|
+
const inspector = createSseInspector({ onTerminal: reportTerminal });
|
|
433
758
|
|
|
434
759
|
const cleanup = () => {
|
|
435
760
|
if (closed) return;
|
|
436
761
|
closed = true;
|
|
762
|
+
inspector.dispose();
|
|
437
763
|
if (timer) clearInterval(timer);
|
|
438
764
|
timer = undefined;
|
|
439
765
|
options?.onDone?.();
|
|
@@ -455,14 +781,13 @@ export function relaySseWithHeartbeat(
|
|
|
455
781
|
try {
|
|
456
782
|
const { done, value } = await reader.read();
|
|
457
783
|
if (done) {
|
|
458
|
-
|
|
459
|
-
if (buffer.trim()) inspectPayload(sseDataPayload(buffer));
|
|
784
|
+
inspector.finish();
|
|
460
785
|
if (!terminalReported && !clientCancelled) reportTerminal("incomplete");
|
|
461
786
|
cleanup();
|
|
462
787
|
controller.close();
|
|
463
788
|
return;
|
|
464
789
|
}
|
|
465
|
-
|
|
790
|
+
inspector.feed(value);
|
|
466
791
|
controller.enqueue(value);
|
|
467
792
|
} catch (err) {
|
|
468
793
|
if (!clientCancelled) reportTerminal("incomplete");
|
|
@@ -500,7 +825,20 @@ export type SseInspectorHandlers = {
|
|
|
500
825
|
onTerminal?: (status: ResponsesTerminalStatus, httpStatusOverride?: number) => void;
|
|
501
826
|
logCtx?: RequestLogContext;
|
|
502
827
|
onCompletedResponse?: (response: { id?: unknown; output?: unknown; status?: unknown }) => void;
|
|
828
|
+
/**
|
|
829
|
+
* Every parsed SSE payload, delivered BEFORE any onCompletedResponse derived from that same
|
|
830
|
+
* payload. A caller that must decide on the whole turn -- not just its terminal snapshot --
|
|
831
|
+
* needs to see the incremental events, because a stream can announce an item and then close
|
|
832
|
+
* with an empty `output`.
|
|
833
|
+
*/
|
|
834
|
+
onParsedPayload?: (payload: unknown) => void;
|
|
503
835
|
onFirstOutput?: () => void;
|
|
836
|
+
/**
|
|
837
|
+
* Provider-scoped compatibility: persist the completed snapshot under the
|
|
838
|
+
* first response id exposed to the client when an upstream changes ids
|
|
839
|
+
* between `response.created` and `response.completed`.
|
|
840
|
+
*/
|
|
841
|
+
pinCompletedResponseIdToFirstSeen?: boolean;
|
|
504
842
|
};
|
|
505
843
|
|
|
506
844
|
type CompletedOutputItem = { item: unknown; sourceBytes: number };
|
|
@@ -530,17 +868,6 @@ function delimiterLengthAt(
|
|
|
530
868
|
return byteAt(index + 3) === 10 ? 4 : 0;
|
|
531
869
|
}
|
|
532
870
|
|
|
533
|
-
function joinedBytes(slices: readonly Uint8Array[], byteLength: number): Uint8Array {
|
|
534
|
-
if (slices.length === 1 && slices[0]!.byteLength === byteLength) return slices[0]!;
|
|
535
|
-
const joined = new Uint8Array(byteLength);
|
|
536
|
-
let offset = 0;
|
|
537
|
-
for (const slice of slices) {
|
|
538
|
-
joined.set(slice, offset);
|
|
539
|
-
offset += slice.byteLength;
|
|
540
|
-
}
|
|
541
|
-
return joined;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
871
|
/**
|
|
545
872
|
* Per-chunk SSE inspection state machine shared by consumeForInspection,
|
|
546
873
|
* consumeForResponseLogMetadata, and the eager bounded relay (relay-eager.ts).
|
|
@@ -561,8 +888,8 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
|
|
|
561
888
|
let reported = false;
|
|
562
889
|
let sawTerminal = false;
|
|
563
890
|
let disposed = false;
|
|
564
|
-
let delimiterTail = new Uint8Array(0);
|
|
565
|
-
let
|
|
891
|
+
let delimiterTail: Uint8Array = new Uint8Array(0);
|
|
892
|
+
let candidate: Uint8Array = new Uint8Array(0);
|
|
566
893
|
let candidateBytes = 0;
|
|
567
894
|
let discardingOversizedFrame = false;
|
|
568
895
|
const reportFirstOutput = createFirstOutputReporter(handlers.onFirstOutput);
|
|
@@ -572,10 +899,11 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
|
|
|
572
899
|
: null;
|
|
573
900
|
let aggregateItemBytes = 0;
|
|
574
901
|
let reconstructionTainted = false;
|
|
902
|
+
let firstResponseId: string | undefined;
|
|
575
903
|
|
|
576
904
|
const clearFrameState = (): void => {
|
|
577
905
|
delimiterTail = new Uint8Array(0);
|
|
578
|
-
|
|
906
|
+
candidate = new Uint8Array(0);
|
|
579
907
|
candidateBytes = 0;
|
|
580
908
|
discardingOversizedFrame = false;
|
|
581
909
|
};
|
|
@@ -592,6 +920,31 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
|
|
|
592
920
|
decoder = null;
|
|
593
921
|
clearFrameState();
|
|
594
922
|
clearCompletedItems();
|
|
923
|
+
firstResponseId = undefined;
|
|
924
|
+
};
|
|
925
|
+
|
|
926
|
+
const ensureCandidateCapacity = (requiredBytes: number): void => {
|
|
927
|
+
if (candidate.byteLength >= requiredBytes) return;
|
|
928
|
+
let capacity = candidate.byteLength === 0
|
|
929
|
+
? Math.min(MAX_INSPECTION_SSE_FRAME_BYTES, Math.max(requiredBytes, 4096))
|
|
930
|
+
: candidate.byteLength;
|
|
931
|
+
while (capacity < requiredBytes) {
|
|
932
|
+
capacity = Math.min(
|
|
933
|
+
MAX_INSPECTION_SSE_FRAME_BYTES,
|
|
934
|
+
Math.max(requiredBytes, capacity * 2),
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
const grown = new Uint8Array(capacity);
|
|
938
|
+
if (candidateBytes > 0) grown.set(candidate.subarray(0, candidateBytes));
|
|
939
|
+
candidate = grown;
|
|
940
|
+
};
|
|
941
|
+
|
|
942
|
+
const takeCandidate = (): Uint8Array => {
|
|
943
|
+
if (candidateBytes === 0) return new Uint8Array(0);
|
|
944
|
+
const frame = candidate.slice(0, candidateBytes);
|
|
945
|
+
candidate = new Uint8Array(0);
|
|
946
|
+
candidateBytes = 0;
|
|
947
|
+
return frame;
|
|
595
948
|
};
|
|
596
949
|
|
|
597
950
|
const retainCandidateSlice = (slice: Uint8Array): void => {
|
|
@@ -602,7 +955,7 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
|
|
|
602
955
|
Math.min(nextBytes, MAX_INSPECTION_SSE_FRAME_BYTES),
|
|
603
956
|
);
|
|
604
957
|
if (nextBytes > MAX_INSPECTION_SSE_FRAME_BYTES) {
|
|
605
|
-
|
|
958
|
+
candidate = new Uint8Array(0);
|
|
606
959
|
candidateBytes = 0;
|
|
607
960
|
discardingOversizedFrame = true;
|
|
608
961
|
inspectionCounters.frameCapOverflows += 1;
|
|
@@ -612,10 +965,8 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
|
|
|
612
965
|
reconstructionTainted = true;
|
|
613
966
|
return;
|
|
614
967
|
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
// chunk whose preceding frames have already been consumed.
|
|
618
|
-
candidateSlices.push(slice.slice());
|
|
968
|
+
ensureCandidateCapacity(nextBytes);
|
|
969
|
+
candidate.set(slice, candidateBytes);
|
|
619
970
|
candidateBytes = nextBytes;
|
|
620
971
|
};
|
|
621
972
|
|
|
@@ -664,8 +1015,16 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
|
|
|
664
1015
|
if (!reported && handlers.logCtx) {
|
|
665
1016
|
inspectResponseLogSsePayloadParsed(handlers.logCtx, payload, parsed);
|
|
666
1017
|
}
|
|
1018
|
+
// Before any terminal handling: a consumer deciding on the whole turn must observe this
|
|
1019
|
+
// payload even when the terminal snapshot that follows no longer mentions it.
|
|
1020
|
+
if (handlers.onParsedPayload && parsed !== undefined) {
|
|
1021
|
+
try { handlers.onParsedPayload(parsed); } catch { /* inspection must never throw into the pump */ }
|
|
1022
|
+
}
|
|
667
1023
|
reportFirstOutput.parsed(parsed);
|
|
668
1024
|
const status = terminalStatusFromParsed(parsed);
|
|
1025
|
+
const policyTerminal = status === "failed"
|
|
1026
|
+
&& isPolicyRewriteType(parsed)
|
|
1027
|
+
&& cyberPolicyTerminalError(parsed) !== undefined;
|
|
669
1028
|
if (status) sawTerminal = true;
|
|
670
1029
|
if (!reported && handlers.onTerminal && status) {
|
|
671
1030
|
try {
|
|
@@ -674,7 +1033,7 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
|
|
|
674
1033
|
handlers.logCtx.transportPhase = "terminal_sse";
|
|
675
1034
|
handlers.logCtx.terminalSource = "upstream";
|
|
676
1035
|
}
|
|
677
|
-
handlers.onTerminal(status);
|
|
1036
|
+
handlers.onTerminal(status, policyTerminal ? 400 : undefined);
|
|
678
1037
|
} finally {
|
|
679
1038
|
if (status === "failed" || status === "incomplete") clearCompletedItems();
|
|
680
1039
|
}
|
|
@@ -686,6 +1045,17 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
|
|
|
686
1045
|
const parsedEvent = parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
687
1046
|
? parsed as ParsedSseEvent
|
|
688
1047
|
: null;
|
|
1048
|
+
const responseRecord = parsedEvent
|
|
1049
|
+
&& typeof parsedEvent.response === "object"
|
|
1050
|
+
&& parsedEvent.response !== null
|
|
1051
|
+
&& !Array.isArray(parsedEvent.response)
|
|
1052
|
+
? parsedEvent.response as { id?: unknown }
|
|
1053
|
+
: null;
|
|
1054
|
+
if (handlers.pinCompletedResponseIdToFirstSeen
|
|
1055
|
+
&& responseRecord
|
|
1056
|
+
&& typeof responseRecord.id === "string") {
|
|
1057
|
+
firstResponseId ??= responseRecord.id;
|
|
1058
|
+
}
|
|
689
1059
|
const doneItem = parsedEvent?.type === "response.output_item.done" ? parsedEvent.item : undefined;
|
|
690
1060
|
if (parsedEvent
|
|
691
1061
|
&& doneItem !== undefined
|
|
@@ -700,6 +1070,11 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
|
|
|
700
1070
|
|
|
701
1071
|
let response = completedResponseFromParsedEvent(parsedEvent);
|
|
702
1072
|
if (response) {
|
|
1073
|
+
if (handlers.pinCompletedResponseIdToFirstSeen
|
|
1074
|
+
&& firstResponseId !== undefined
|
|
1075
|
+
&& response.id !== firstResponseId) {
|
|
1076
|
+
response = { ...response, id: firstResponseId };
|
|
1077
|
+
}
|
|
703
1078
|
// Authoritative output is a NON-EMPTY ARRAY only. Anything else
|
|
704
1079
|
// (missing, null, scalar, object) keeps the historical backfill
|
|
705
1080
|
// behavior so a malformed terminal cannot reach rememberResponseState
|
|
@@ -735,9 +1110,7 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
|
|
|
735
1110
|
return;
|
|
736
1111
|
}
|
|
737
1112
|
const sourceBytes = candidateBytes;
|
|
738
|
-
const frame =
|
|
739
|
-
candidateSlices = [];
|
|
740
|
-
candidateBytes = 0;
|
|
1113
|
+
const frame = takeCandidate();
|
|
741
1114
|
if (reported && !handlers.onCompletedResponse) return;
|
|
742
1115
|
const decoded = decoder!.decode(frame);
|
|
743
1116
|
scanPayload(sseDataPayload(decoded), sourceBytes);
|
|
@@ -794,7 +1167,7 @@ export function createSseInspector(handlers: SseInspectorHandlers): SseInspector
|
|
|
794
1167
|
delimiterTail = new Uint8Array(0);
|
|
795
1168
|
if (!discardingOversizedFrame && candidateBytes > 0 && !reported) {
|
|
796
1169
|
const sourceBytes = candidateBytes;
|
|
797
|
-
const decoded = decoder!.decode(
|
|
1170
|
+
const decoded = decoder!.decode(takeCandidate());
|
|
798
1171
|
scanPayload(decoded.trim() ? sseDataPayload(decoded) : null, sourceBytes);
|
|
799
1172
|
}
|
|
800
1173
|
} finally {
|
|
@@ -815,6 +1188,10 @@ export type InspectionConsumerOptions = {
|
|
|
815
1188
|
drainBounds?: Partial<InspectionDrainBounds>;
|
|
816
1189
|
upstream?: AbortController;
|
|
817
1190
|
now?: () => number;
|
|
1191
|
+
/** Forward provider-scoped response-id pinning to the owned inspector. */
|
|
1192
|
+
pinCompletedResponseIdToFirstSeen?: boolean;
|
|
1193
|
+
/** Observe every parsed SSE payload on the inspection side; see SseInspectorHandlers. */
|
|
1194
|
+
onParsedPayload?: (payload: unknown) => void;
|
|
818
1195
|
/** Test seam for proving both public consumers dispose their owned inspector. */
|
|
819
1196
|
inspectorFactory?: (handlers: SseInspectorHandlers) => SseInspector;
|
|
820
1197
|
};
|
|
@@ -935,6 +1312,13 @@ function startBoundedInspectionPump(options: InspectionPumpOptions): void {
|
|
|
935
1312
|
}
|
|
936
1313
|
}
|
|
937
1314
|
} catch {
|
|
1315
|
+
// A read error can follow a final SSE block without its blank-line
|
|
1316
|
+
// delimiter. Flush that candidate before classifying the transport as a
|
|
1317
|
+
// synthetic reset; otherwise a real completed/failed/policy terminal is
|
|
1318
|
+
// downgraded to 502 on the inspection branch.
|
|
1319
|
+
if (!cancelled) {
|
|
1320
|
+
try { inspector.finish(); } catch { /* preserve the original read error */ }
|
|
1321
|
+
}
|
|
938
1322
|
if (clientGone) clientGoneWithoutTerminal = !inspector.terminalSeen();
|
|
939
1323
|
else if (!cancelled) options.onReadError?.();
|
|
940
1324
|
} finally {
|
|
@@ -970,7 +1354,9 @@ export function consumeForInspection(
|
|
|
970
1354
|
onTerminal,
|
|
971
1355
|
logCtx,
|
|
972
1356
|
onCompletedResponse,
|
|
1357
|
+
onParsedPayload: options?.onParsedPayload,
|
|
973
1358
|
onFirstOutput,
|
|
1359
|
+
pinCompletedResponseIdToFirstSeen: options?.pinCompletedResponseIdToFirstSeen,
|
|
974
1360
|
});
|
|
975
1361
|
startBoundedInspectionPump({
|
|
976
1362
|
...options,
|
|
@@ -993,6 +1379,10 @@ export function consumeForInspection(
|
|
|
993
1379
|
if (logCtx) {
|
|
994
1380
|
logCtx.transportPhase = "mid_stream";
|
|
995
1381
|
logCtx.terminalSource = "synthetic";
|
|
1382
|
+
// A truncated 200 body must not meter as a success the client never
|
|
1383
|
+
// received; the router's equivalent turn carries 502 + streamAborted
|
|
1384
|
+
// (codex-router #139).
|
|
1385
|
+
if (logCtx.activeAttempt) logCtx.activeAttempt.streamAborted = true;
|
|
996
1386
|
}
|
|
997
1387
|
onTerminal("failed", 502);
|
|
998
1388
|
}
|
|
@@ -1015,7 +1405,9 @@ export function consumeForResponseLogMetadata(
|
|
|
1015
1405
|
const inspector = (options?.inspectorFactory ?? createSseInspector)({
|
|
1016
1406
|
logCtx,
|
|
1017
1407
|
onCompletedResponse,
|
|
1408
|
+
onParsedPayload: options?.onParsedPayload,
|
|
1018
1409
|
onFirstOutput,
|
|
1410
|
+
pinCompletedResponseIdToFirstSeen: options?.pinCompletedResponseIdToFirstSeen,
|
|
1019
1411
|
});
|
|
1020
1412
|
startBoundedInspectionPump({ ...options, reader, inspector, signal, onDone });
|
|
1021
1413
|
}
|