@bitkyc08/opencodex 2.86.0 → 2.88.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/gui/dist/assets/App-CHC89EXQ.js +58 -0
- package/gui/dist/assets/App-wT3mXqFz.css +1 -0
- package/gui/dist/assets/{Tray-DyR9yBee.js → Tray-B5bC7AWL.js} +1 -1
- package/gui/dist/assets/index-B5bsvxor.js +94 -0
- package/gui/dist/assets/index-DPeB9HpV.css +1 -0
- package/gui/dist/assets/quota-summary-D8y2wTsB.js +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic/beta-allowlist.ts +9 -8
- package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
- package/src/adapters/anthropic/passthrough.ts +7 -23
- package/src/adapters/anthropic-progress.ts +84 -0
- package/src/adapters/anthropic-rejection-memo.ts +71 -0
- package/src/adapters/anthropic-replay.ts +135 -0
- package/src/adapters/anthropic-tool-catalog.ts +11 -0
- package/src/adapters/anthropic.ts +151 -33
- package/src/adapters/base.ts +2 -0
- package/src/adapters/claude-cli/adapter.ts +111 -23
- package/src/adapters/claude-cli/profiles.ts +2 -1
- package/src/adapters/claude-cli/stable-replay.ts +108 -0
- package/src/adapters/claude-cli/usage-admission.ts +230 -0
- package/src/adapters/coding-agent/protocol.ts +1 -1
- package/src/adapters/coding-agent/turn.ts +18 -2
- package/src/adapters/dsh-account.ts +97 -0
- package/src/adapters/google-progress-history.ts +53 -0
- package/src/adapters/google.ts +52 -9
- package/src/adapters/openai-chat/messages.ts +4 -3
- package/src/adapters/openai-chat/passthrough.ts +8 -2
- package/src/adapters/openai-chat/reasoning-replay.ts +52 -0
- package/src/adapters/openai-responses/compaction-search-history.ts +14 -7
- package/src/adapters/openai-responses/passthrough.ts +57 -125
- package/src/adapters/openai-responses/request-strips.ts +25 -3
- package/src/adapters/openai-responses/transport.ts +81 -0
- package/src/adapters/qoder/adapter.ts +4 -2
- package/src/adapters/registry.ts +5 -0
- package/src/adapters/zed.ts +26 -1
- package/src/advisor/activity.ts +55 -0
- package/src/advisor/budget.ts +62 -0
- package/src/advisor/consult.ts +4 -1
- package/src/advisor/context.ts +16 -4
- package/src/advisor/evidence-tool.ts +21 -0
- package/src/advisor/evidence.ts +117 -0
- package/src/advisor/runtime.ts +79 -31
- package/src/advisor/settings.ts +11 -2
- package/src/bridge/errors.ts +19 -1
- package/src/bridge/internal.ts +7 -1
- package/src/bridge/response-json.ts +18 -3
- package/src/bridge/sse.ts +59 -8
- package/src/bridge/terminal-refusal.ts +59 -0
- package/src/claude/desktop-3p-library.ts +32 -7
- package/src/claude/desktop-3p.ts +55 -13
- package/src/claude/desktop-first-party.ts +1 -1
- package/src/claude/desktop-picker-platform.ts +4 -0
- package/src/claude/desktop-picker.ts +15 -8
- package/src/claude/desktop-system-proxy.ts +50 -4
- package/src/claude/inbound-content-options.ts +77 -0
- package/src/claude/inbound.ts +33 -0
- package/src/claude/intercept/picker-ca-startup.ts +2 -1
- package/src/claude/intercept/picker-runtime.ts +6 -5
- package/src/claude/intercept/picker-trust-windows.ts +338 -0
- package/src/claude/intercept/picker-trust.ts +19 -0
- package/src/claude/intercept/port-policy.ts +30 -0
- package/src/claude/intercept/runtime.ts +97 -94
- package/src/claude/outbound.ts +46 -13
- package/src/cli/account-api.ts +1 -0
- package/src/cli/account-policy-dto.ts +6 -0
- package/src/cli/account-policy.ts +34 -5
- package/src/cli/account-preservation.ts +44 -0
- package/src/cli/account.ts +8 -2
- package/src/cli/advisor.ts +17 -3
- package/src/cli/capabilities-accounts.ts +20 -1
- package/src/cli/capabilities-agents-routing.ts +5 -2
- package/src/cli/capabilities-base.ts +38 -10
- package/src/cli/capabilities-observe-system.ts +63 -3
- package/src/cli/capabilities-provider-models.ts +11 -0
- package/src/cli/codex-cli-update.ts +151 -41
- package/src/cli/codex-provider.ts +44 -0
- package/src/cli/combo-input.ts +10 -1
- package/src/cli/combo-result.ts +1 -1
- package/src/cli/combo.ts +2 -1
- package/src/cli/cross-home-owner.ts +33 -16
- package/src/cli/dispatch.ts +25 -4
- package/src/cli/doctor.ts +6 -1
- package/src/cli/ensure-desktop-cold-start.ts +60 -7
- package/src/cli/help.ts +3 -2
- package/src/cli/index.ts +1 -1
- package/src/cli/integration-plan-dto.ts +1 -0
- package/src/cli/log-history.ts +172 -0
- package/src/cli/loopback-family.ts +31 -0
- package/src/cli/models-runtime-subcommands.ts +1 -0
- package/src/cli/models-runtime.ts +72 -8
- package/src/cli/observe-stream.ts +2 -0
- package/src/cli/observe.ts +16 -4
- package/src/cli/provider.ts +27 -5
- package/src/cli/registry.ts +28 -8
- package/src/cli/runtime-api.ts +5 -3
- package/src/cli/system-command.ts +40 -8
- package/src/cli/uninstall-client-state.ts +32 -0
- package/src/cli/usage-compare.ts +309 -0
- package/src/cli/usage-model-search.ts +4 -4
- package/src/cli/usage-report.ts +2 -2
- package/src/cli/v2.ts +1 -1
- package/src/client/machine-listener.ts +1 -3
- package/src/client/runtime.ts +1 -0
- package/src/clients/client-path.ts +51 -0
- package/src/clients/config-export/contracts.ts +2 -1
- package/src/clients/config-export.ts +18 -44
- package/src/clients/gentle-shell-path.ts +87 -0
- package/src/codex/account-auto-switch.ts +2 -0
- package/src/codex/account-identity.ts +29 -0
- package/src/codex/account-label.ts +1 -1
- package/src/codex/account-lifecycle.ts +21 -5
- package/src/codex/account-preservation.ts +28 -0
- package/src/codex/account-use-remaining.ts +23 -0
- package/src/codex/app-server-processes.ts +97 -7
- package/src/codex/app-server-restart-service.ts +55 -4
- package/src/codex/auth-api/account-list.ts +4 -0
- package/src/codex/auth-api/account-pause-group.ts +6 -50
- package/src/codex/auth-api/account-preservation.ts +57 -0
- package/src/codex/auth-api/routes.ts +8 -0
- package/src/codex/auth-api/use-remaining-route.ts +36 -0
- package/src/codex/auth-api.ts +0 -1
- package/src/codex/auth-context.ts +2 -2
- package/src/codex/catalog/auto-review.ts +20 -3
- package/src/codex/catalog/build-entries.ts +9 -1
- package/src/codex/catalog/combo-member.ts +14 -5
- package/src/codex/catalog/configured-tiers.ts +1 -1
- package/src/codex/catalog/gather-capture.ts +2 -0
- package/src/codex/catalog/metadata.ts +11 -9
- package/src/codex/catalog/model-hints.ts +21 -16
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/parsing.ts +30 -86
- package/src/codex/catalog/provider-models.ts +36 -34
- package/src/codex/catalog/routed-gather.ts +14 -13
- package/src/codex/cli-install-provenance.ts +105 -17
- package/src/codex/cli-update-plan.ts +545 -0
- package/src/codex/config-write-lock.ts +23 -2
- package/src/codex/desktop-app/picker-freshness.ts +96 -0
- package/src/codex/desktop-app-restart.ts +21 -0
- package/src/codex/desktop-compatibility/installed-build.ts +24 -6
- package/src/codex/desktop-compatibility/routing-preflight.ts +1 -1
- package/src/codex/desktop-compatibility/runtime.ts +12 -3
- package/src/codex/desktop-compatibility/windows-key-protection.ts +115 -9
- package/src/codex/features.ts +85 -45
- package/src/codex/inject/history-failure.ts +27 -0
- package/src/codex/inject/restore.ts +24 -2
- package/src/codex/inject/routing-classify.ts +4 -3
- package/src/codex/inject.ts +18 -27
- package/src/codex/main-account-hard-lock.ts +30 -1
- package/src/codex/native-profile-startup.ts +1 -1
- package/src/codex/ocx-compaction-history.ts +12 -10
- package/src/codex/paths.ts +86 -1
- package/src/codex/provider-routing.ts +174 -0
- package/src/codex/refresh.ts +2 -2
- package/src/codex/routing/health-store.ts +1 -1
- package/src/codex/routing/selection.ts +4 -3
- package/src/codex/routing/thread-affinity.ts +1 -1
- package/src/codex/signed-in-identity.ts +43 -0
- package/src/codex/subagent-model-fallback.ts +1 -1
- package/src/codex/sync.ts +6 -1
- package/src/codex/user-identity.ts +16 -5
- package/src/codex/websocket-activity.ts +161 -0
- package/src/codex/websocket-registry.ts +25 -1
- package/src/codex/windows-local-appdata.ts +84 -0
- package/src/combos/failover.ts +6 -1
- package/src/combos/index.ts +1 -0
- package/src/combos/resolve.ts +46 -10
- package/src/combos/types.ts +15 -0
- package/src/config/diagnostics.ts +33 -6
- package/src/config/live-reconcile.ts +38 -2
- package/src/config/load-degrade.ts +17 -0
- package/src/config/opencode-wire-upgrade.ts +54 -0
- package/src/config/opencode-zen-upgrade.ts +53 -0
- package/src/config/owner-registry.ts +22 -17
- package/src/config/provider-validation.ts +45 -2
- package/src/config/schema/config-schema.ts +22 -0
- package/src/config/schema/leaf-validators.ts +38 -9
- package/src/config/schema/main-account-hard-lock-fallback.ts +26 -0
- package/src/config/schema/native-reasoning-retention.ts +26 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +657 -297
- package/src/grok/sync.ts +1 -1
- package/src/images/loop.ts +116 -27
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/mutation-plan.ts +1 -0
- package/src/integrations/registry.ts +8 -0
- package/src/lib/codex-restart-contract.ts +15 -0
- package/src/lib/desktop-upstream-tunnel.ts +18 -53
- package/src/lib/empty-tool-catalog.ts +62 -0
- package/src/lib/errors.ts +19 -3
- package/src/lib/gcp-adc.ts +96 -9
- package/src/lib/gcp-credential-input.ts +12 -0
- package/src/lib/lab-live-route-production.ts +5 -0
- package/src/lib/opaque-proxy-tunnel.ts +217 -0
- package/src/lib/redact.ts +2 -2
- package/src/lib/request-failure-model.ts +2 -0
- package/src/oauth/account-quota-rank.ts +69 -2
- package/src/oauth/anthropic-account-refusal.ts +20 -2
- package/src/oauth/anthropic-oauth-definitions.ts +3 -1
- package/src/oauth/anthropic-routing.ts +1 -21
- package/src/oauth/anthropic.ts +38 -7
- package/src/oauth/callback-server.ts +27 -1
- package/src/oauth/chatgpt-device.ts +1 -1
- package/src/oauth/chatgpt-shared.ts +83 -0
- package/src/oauth/chatgpt.ts +8 -72
- package/src/oauth/dsh.ts +203 -0
- package/src/oauth/generic-account-failover.ts +40 -11
- package/src/oauth/generic-session-affinity.ts +125 -0
- package/src/oauth/index.ts +184 -109
- package/src/oauth/local-token-detect.ts +21 -1
- package/src/oauth/login-cli-errors.ts +44 -0
- package/src/oauth/login-cli.ts +69 -7
- package/src/oauth/login-flow-state.ts +371 -23
- package/src/oauth/meta-muse-errors.ts +41 -0
- package/src/oauth/meta-muse-keychain.ts +52 -0
- package/src/oauth/meta-muse.ts +51 -84
- package/src/oauth/model-discovery-auth.ts +3 -0
- package/src/oauth/pool-settings-capability.ts +4 -2
- package/src/oauth/refresh-flight-retirement.ts +48 -0
- package/src/oauth/session-affinity-key.ts +23 -0
- package/src/oauth/store-anthropic-instance.ts +9 -2
- package/src/oauth/store.ts +12 -5
- package/src/protocols/encoders/adapter-events.ts +6 -5
- package/src/protocols/encoders/messages.ts +9 -6
- package/src/protocols/provider-summary.ts +2 -1
- package/src/providers/api-key-cooldowns.ts +49 -0
- package/src/providers/api-key-resolve.ts +11 -6
- package/src/providers/api-key-response-ownership.ts +57 -0
- package/src/providers/api-key-selection.ts +31 -6
- package/src/providers/api-keys.ts +214 -3
- package/src/providers/claude-cli-usage.ts +96 -0
- package/src/providers/dsh-account-destination.ts +31 -0
- package/src/providers/dsh-account-models.ts +41 -0
- package/src/providers/gcp-credential-cleanup.ts +59 -0
- package/src/providers/key-failover.ts +54 -50
- package/src/providers/key-store.ts +7 -2
- package/src/providers/model-rename-fields.ts +3 -0
- package/src/providers/model-rename-migration.ts +2 -2
- package/src/providers/openai-tiers-destination.ts +11 -2
- package/src/providers/openai-tiers.ts +5 -12
- package/src/providers/opencode-go-transport.ts +3 -1
- package/src/providers/opencode-zen-rate-limit.ts +7 -2
- package/src/providers/quota/account-cache.ts +6 -5
- package/src/providers/quota/account-row.ts +34 -0
- package/src/providers/quota/antigravity.ts +28 -7
- package/src/providers/quota/dsh-account.ts +70 -0
- package/src/providers/quota/vendor-probes-oauth.ts +1 -93
- package/src/providers/quota-types.ts +2 -0
- package/src/providers/quota.ts +14 -20
- package/src/providers/registry/entries-core.ts +37 -8
- package/src/providers/registry/types.ts +1 -1
- package/src/providers/request-pacing.ts +77 -19
- package/src/providers/routed-config.ts +339 -0
- package/src/providers/service-tier.ts +2 -1
- package/src/quota/window-mapping.ts +5 -2
- package/src/responses/anthropic-progress.ts +34 -0
- package/src/responses/assistant-replay.ts +35 -0
- package/src/responses/compaction-history-framing.ts +65 -0
- package/src/responses/compaction.ts +6 -1
- package/src/responses/incomplete-reason.ts +31 -0
- package/src/responses/parser.ts +50 -5
- package/src/responses/reasoning-envelope.ts +4 -0
- package/src/responses/reasoning-replay-cache.ts +74 -6
- package/src/responses/reasoning-retention.ts +173 -0
- package/src/responses/retained-compaction.ts +46 -0
- package/src/responses/schema.ts +23 -0
- package/src/router.ts +8 -321
- package/src/routing/analytics.ts +7 -15
- package/src/routing/health.ts +37 -22
- package/src/routing/history/indexer.ts +6 -5
- package/src/routing/history/ingest.ts +6 -2
- package/src/routing/profile.ts +1 -1
- package/src/server/admission-model-scope.ts +17 -0
- package/src/server/auth-cors.ts +11 -0
- package/src/server/chat-completions.ts +7 -1
- package/src/server/chat-native-reasoning.ts +123 -0
- package/src/server/chat-native-sse.ts +110 -7
- package/src/server/chat-native.ts +76 -17
- package/src/server/claude-messages.ts +56 -414
- package/src/server/effort-policy.ts +1 -1
- package/src/server/images.ts +15 -2
- package/src/server/index/live-sideband.ts +3 -2
- package/src/server/index/model-alias-id.ts +36 -0
- package/src/server/index/optional-listeners.ts +2 -0
- package/src/server/index/serve-options.ts +20 -6
- package/src/server/index/startup-warnings.ts +2 -0
- package/src/server/index/websocket-handler.ts +18 -4
- package/src/server/index.ts +8 -8
- package/src/server/inference/advisor-diagnostics.ts +31 -0
- package/src/server/lifecycle.ts +13 -1
- package/src/server/management/advisor-routes.ts +15 -4
- package/src/server/management/claude-desktop-picker-routes.ts +1 -1
- package/src/server/management/combo-routes.ts +176 -50
- package/src/server/management/companion-routes.ts +1 -1
- package/src/server/management/config-routes.ts +75 -4
- package/src/server/management/cursor-integration-routes.ts +1 -1
- package/src/server/management/model-routes.ts +11 -6
- package/src/server/management/model-rows.ts +14 -0
- package/src/server/management/native-integration-routes.ts +2 -7
- package/src/server/management/native-reasoning-retention-routes.ts +41 -0
- package/src/server/management/oauth-account-routes.ts +87 -14
- package/src/server/management/provider-gcp-credential-routes.ts +29 -0
- package/src/server/management/provider-key-pacing-routes.ts +31 -0
- package/src/server/management/provider-overwrite-carry.ts +7 -0
- package/src/server/management/provider-routes.ts +25 -30
- package/src/server/management/route-registry.ts +10 -1
- package/src/server/management-api.ts +3 -1
- package/src/server/messages-native-tool-guard.ts +185 -0
- package/src/server/messages-native.ts +117 -24
- package/src/server/messages-shared.ts +431 -0
- package/src/server/relay.ts +1 -1
- package/src/server/request-log-terminal-status.ts +1 -1
- package/src/server/request-log.ts +6 -1
- package/src/server/responses/account-change-state.ts +8 -2
- package/src/server/responses/adapter-continuation.ts +47 -19
- package/src/server/responses/adapter-delivery.ts +72 -12
- package/src/server/responses/adapter-dispatch.ts +46 -3
- package/src/server/responses/advisor-retention.ts +117 -0
- package/src/server/responses/advisor-slot.ts +73 -41
- package/src/server/responses/anthropic-thinking-recovery.ts +153 -0
- package/src/server/responses/codex-ws-wire.ts +15 -8
- package/src/server/responses/combo-stream-preflight.ts +3 -2
- package/src/server/responses/compact.ts +303 -125
- package/src/server/responses/compaction-recovery.ts +20 -10
- package/src/server/responses/compaction-retention.ts +61 -0
- package/src/server/responses/context-overflow.ts +11 -6
- package/src/server/responses/core-auth.ts +6 -0
- package/src/server/responses/core-codex-account.ts +11 -1
- package/src/server/responses/core-combo-failure.ts +2 -0
- package/src/server/responses/core-combo.ts +121 -36
- package/src/server/responses/core-errors.ts +3 -3
- package/src/server/responses/core-lifetime.ts +8 -4
- package/src/server/responses/core-opaque-recovery.ts +35 -0
- package/src/server/responses/core-options.ts +7 -0
- package/src/server/responses/core-pipeline.ts +141 -0
- package/src/server/responses/core-replay.ts +23 -3
- package/src/server/responses/core.ts +2 -115
- package/src/server/responses/fetch-helpers.ts +73 -19
- package/src/server/responses/main-hard-lock-routing.ts +109 -0
- package/src/server/responses/native-response-control.ts +4 -2
- package/src/server/responses/passthrough-delivery.ts +3 -1
- package/src/server/responses/passthrough-dispatch.ts +15 -13
- package/src/server/responses/passthrough-execution.ts +3 -2
- package/src/server/responses/request-prepare.ts +77 -12
- package/src/server/responses/request-sidecar-auth.ts +2 -2
- package/src/server/responses/request-transport.ts +104 -27
- package/src/server/responses/response-effects.ts +2 -0
- package/src/server/responses/response-tool-ownership.ts +21 -0
- package/src/server/responses/run-turn-execution.ts +96 -30
- package/src/server/responses/sidecar-execution.ts +63 -9
- package/src/server/responses/sidecar-send-budget.ts +2 -2
- package/src/server/responses/terminal-guard.ts +2 -16
- package/src/server/responses-undeclared-tool-guard.ts +1 -1
- package/src/server/ws-bridge.ts +2 -0
- package/src/service/auth-preflight.ts +71 -0
- package/src/service/cli.ts +4 -2
- package/src/service/definition.ts +97 -0
- package/src/service/guards.ts +1 -61
- package/src/service/health-sentinel-linux.ts +112 -0
- package/src/service/health-sentinel-main.ts +76 -57
- package/src/service/health.ts +5 -89
- package/src/service/launchd.ts +27 -7
- package/src/service/logging.ts +72 -9
- package/src/service/repair.ts +2 -1
- package/src/service/state.ts +7 -8
- package/src/service/systemd.ts +1 -1
- package/src/service/windows-guarded-manager.ts +2 -1
- package/src/service/windows-ops.ts +1 -1
- package/src/service/windows-scheduler.ts +2 -7
- package/src/service/windows-taskxml.ts +2 -2
- package/src/service/windows-wscript.ts +14 -0
- package/src/service.ts +4 -2
- package/src/sidecar/auth.ts +2 -0
- package/src/types/config.ts +64 -3
- package/src/types/opencode-go.ts +61 -0
- package/src/types/opencode-zen.ts +35 -0
- package/src/types/provider.ts +32 -6
- package/src/types/request.ts +31 -2
- package/src/types/wire.ts +31 -37
- package/src/types.ts +2 -0
- package/src/update/npm-invocation.mjs +12 -2
- package/src/usage/cost.ts +48 -12
- package/src/usage/expected-prices.ts +3 -0
- package/src/usage/ledger-retention.ts +2 -1
- package/src/usage/ledger-scanner.ts +6 -5
- package/src/usage/log.ts +17 -8
- package/src/usage/summary.ts +30 -11
- package/src/usage/telemetry-contract.ts +1 -0
- package/src/usage/user-cost-overlays.ts +14 -19
- package/src/usage/user-cost-validation.ts +38 -0
- package/src/vision/eligibility.ts +13 -0
- package/src/web-search/anthropic-history.ts +59 -0
- package/src/web-search/loop.ts +236 -73
- package/src/web-search/progress-stream.ts +15 -2
- package/src/web-search/run-turn-loop.ts +9 -1
- package/gui/dist/assets/App-BjlttVLy.js +0 -58
- package/gui/dist/assets/App-CSJvmjNs.css +0 -1
- package/gui/dist/assets/index-BB6uK6tU.css +0 -1
- package/gui/dist/assets/index-BMssP4k3.js +0 -94
- package/gui/dist/assets/quota-summary-D8M1bc_R.js +0 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assistantReplayContent } from "../../responses/assistant-replay";
|
|
1
2
|
/**
|
|
2
3
|
* Core-owned registration slot for the optional advisor subsystem (src/advisor).
|
|
3
4
|
*
|
|
@@ -28,7 +29,9 @@ import type {
|
|
|
28
29
|
OcxToolResultMessage,
|
|
29
30
|
OcxUsage,
|
|
30
31
|
} from "../../types";
|
|
31
|
-
import {
|
|
32
|
+
import { jsonStringPartsUtf8Bytes, jsonUtf8Bytes } from "../../lib/json-byte-size";
|
|
33
|
+
import { isTranslatorBudgetExceededError, TRANSLATOR_MAX_CALL_ARGUMENT_BYTES, TranslatorBudgetExceededError, type TranslatorBudget } from "../../lib/translator-budget";
|
|
34
|
+
import { AdvisorRetention } from "./advisor-retention";
|
|
32
35
|
|
|
33
36
|
export const ADVISOR_TOOL_NAME = "advisor";
|
|
34
37
|
|
|
@@ -53,7 +56,7 @@ export interface AdvisorConsultOutcome {
|
|
|
53
56
|
* Operator config blocked the consultation before any outbound call. Not a provider
|
|
54
57
|
* failure: preflight must release a claim instead of recording a cooldown.
|
|
55
58
|
*/
|
|
56
|
-
blocked?: "consent" | "settings";
|
|
59
|
+
blocked?: "consent" | "settings" | "budget" | "limit";
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
/** The structural plan the optional advisor subsystem registers per request. */
|
|
@@ -78,12 +81,19 @@ export interface AdvisorPlan {
|
|
|
78
81
|
interface HeldAdvisorCall {
|
|
79
82
|
id: string;
|
|
80
83
|
name: string;
|
|
81
|
-
|
|
84
|
+
args: string[];
|
|
85
|
+
argumentBytes: number;
|
|
86
|
+
question?: string;
|
|
82
87
|
closed: boolean;
|
|
83
88
|
providerMetadata?: import("../../types").OcxProviderOpaqueToolCallMetadata;
|
|
84
89
|
}
|
|
85
90
|
|
|
86
|
-
function parseAdvisorArgs(
|
|
91
|
+
function parseAdvisorArgs(call: HeldAdvisorCall, retention: AdvisorRetention): { question?: string } {
|
|
92
|
+
// Reserve both the joined input and parsed JSON before either is materialized.
|
|
93
|
+
return retention.copy(call.argumentBytes * 2 + 4, () => parseArgs(call.args.join("")));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function parseArgs(argsBuf: string): { question?: string } {
|
|
87
97
|
if (argsBuf.trim() === "") return {};
|
|
88
98
|
try {
|
|
89
99
|
const parsed: unknown = JSON.parse(argsBuf);
|
|
@@ -99,31 +109,18 @@ function assistantMessageFromLeg(
|
|
|
99
109
|
events: readonly AdapterEvent[],
|
|
100
110
|
held: readonly HeldAdvisorCall[],
|
|
101
111
|
timestamp: number,
|
|
112
|
+
retention: AdvisorRetention,
|
|
102
113
|
): OcxAssistantMessage | undefined {
|
|
103
|
-
|
|
104
|
-
let thinking = "";
|
|
105
|
-
let signature: string | undefined;
|
|
106
|
-
const redacted: string[] = [];
|
|
107
|
-
for (const event of events) {
|
|
108
|
-
if (event.type === "text_delta") text += event.text;
|
|
109
|
-
else if (event.type === "thinking_delta") thinking += event.thinking;
|
|
110
|
-
else if (event.type === "thinking_signature") signature = event.signature;
|
|
111
|
-
else if (event.type === "redacted_thinking") redacted.push(event.data);
|
|
112
|
-
}
|
|
113
|
-
if (text.length > MAX_LEG_TEXT_CHARS) text = text.slice(0, MAX_LEG_TEXT_CHARS);
|
|
114
|
-
if (thinking.length > MAX_LEG_THINKING_CHARS) thinking = thinking.slice(0, MAX_LEG_THINKING_CHARS);
|
|
115
|
-
const content: OcxAssistantContentPart[] = [];
|
|
116
|
-
if (thinking || signature || redacted.length > 0) {
|
|
117
|
-
content.push({ type: "thinking", thinking, ...(signature ? { signature } : {}), ...(redacted.length > 0 ? { redacted } : {}) });
|
|
118
|
-
}
|
|
119
|
-
if (text) content.push({ type: "text", text });
|
|
114
|
+
const content = assistantReplayContent(events);
|
|
120
115
|
for (const call of held) {
|
|
121
116
|
if (!call.closed) continue;
|
|
117
|
+
const args = parseAdvisorArgs(call, retention);
|
|
118
|
+
call.question = args.question;
|
|
122
119
|
content.push({
|
|
123
120
|
type: "toolCall",
|
|
124
121
|
id: call.id,
|
|
125
122
|
name: call.name,
|
|
126
|
-
arguments:
|
|
123
|
+
arguments: args,
|
|
127
124
|
...(call.providerMetadata ? { providerMetadata: call.providerMetadata } : {}),
|
|
128
125
|
});
|
|
129
126
|
}
|
|
@@ -150,6 +147,8 @@ export interface AdvisorGuardOptions {
|
|
|
150
147
|
parsed: OcxParsedRequest;
|
|
151
148
|
plan: AdvisorPlan;
|
|
152
149
|
firstEvents: AsyncIterable<AdapterEvent>;
|
|
150
|
+
/** Independent guard ownership within the optional shared request budget. */
|
|
151
|
+
translatorBudget?: TranslatorBudget;
|
|
153
152
|
/** One bounded worker continuation re-dispatch (same machinery as the terminal guard). */
|
|
154
153
|
continuation: (parsed: OcxParsedRequest) => AsyncIterable<AdapterEvent> | Promise<AsyncIterable<AdapterEvent>>;
|
|
155
154
|
}
|
|
@@ -172,7 +171,8 @@ export function createAdvisorGuard(plan: AdvisorPlan): NonNullable<OcxParsedRequ
|
|
|
172
171
|
let parsed = options.parsed;
|
|
173
172
|
let accumulatedUsage: OcxUsage | undefined;
|
|
174
173
|
let source: AsyncIterable<AdapterEvent> = options.firstEvents;
|
|
175
|
-
|
|
174
|
+
const retention = new AdvisorRetention(options.translatorBudget);
|
|
175
|
+
try {
|
|
176
176
|
while (true) {
|
|
177
177
|
const held: HeldAdvisorCall[] = [];
|
|
178
178
|
const legEvents: AdapterEvent[] = [];
|
|
@@ -189,15 +189,33 @@ export function createAdvisorGuard(plan: AdvisorPlan): NonNullable<OcxParsedRequ
|
|
|
189
189
|
for await (const event of source) {
|
|
190
190
|
if (event.type === "tool_call_start") {
|
|
191
191
|
if (pending) { held.push(pending); pending = null; }
|
|
192
|
-
pending = { id: event.id, name: event.name, argsBuf: "", closed: false, ...(event.providerMetadata ? { providerMetadata: event.providerMetadata } : {}) };
|
|
193
192
|
holdingCurrent = event.name === ADVISOR_TOOL_NAME;
|
|
194
|
-
if (!holdingCurrent)
|
|
195
|
-
|
|
193
|
+
if (!holdingCurrent) {
|
|
194
|
+
hasRealToolCall = true;
|
|
195
|
+
// No continuation can use this leg. Real-tool arguments are never copied.
|
|
196
|
+
held.length = legEvents.length = 0;
|
|
197
|
+
retention.releaseLeg();
|
|
198
|
+
} else {
|
|
199
|
+
if (!hasRealToolCall) {
|
|
200
|
+
retention.retain(event, true);
|
|
201
|
+
pending = { id: event.id, name: event.name, args: [], argumentBytes: 0, closed: false, ...(event.providerMetadata ? { providerMetadata: event.providerMetadata } : {}) };
|
|
202
|
+
}
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
196
205
|
} else if (event.type === "tool_call_delta") {
|
|
197
|
-
if (pending)
|
|
206
|
+
if (pending) {
|
|
207
|
+
const bytes = jsonStringPartsUtf8Bytes([event.arguments], TRANSLATOR_MAX_CALL_ARGUMENT_BYTES + 2) - 2;
|
|
208
|
+
if (bytes > TRANSLATOR_MAX_CALL_ARGUMENT_BYTES - pending.argumentBytes) {
|
|
209
|
+
throw new TranslatorBudgetExceededError("tool_args", TRANSLATOR_MAX_CALL_ARGUMENT_BYTES);
|
|
210
|
+
}
|
|
211
|
+
retention.retain(event);
|
|
212
|
+
pending.argumentBytes += bytes;
|
|
213
|
+
pending.args.push(event.arguments);
|
|
214
|
+
}
|
|
198
215
|
if (holdingCurrent) continue;
|
|
199
216
|
} else if (event.type === "tool_call_end") {
|
|
200
217
|
if (pending) {
|
|
218
|
+
retention.retain(event);
|
|
201
219
|
pending.closed = true;
|
|
202
220
|
if (pending.name === ADVISOR_TOOL_NAME) held.push(pending);
|
|
203
221
|
pending = null;
|
|
@@ -207,6 +225,7 @@ export function createAdvisorGuard(plan: AdvisorPlan): NonNullable<OcxParsedRequ
|
|
|
207
225
|
continue;
|
|
208
226
|
}
|
|
209
227
|
} else if (event.type === "done") {
|
|
228
|
+
if (!hasRealToolCall) retention.retain(event);
|
|
210
229
|
if (pending) { held.push(pending); pending = null; }
|
|
211
230
|
terminalEvent = event;
|
|
212
231
|
terminalConsumed = true;
|
|
@@ -214,22 +233,23 @@ export function createAdvisorGuard(plan: AdvisorPlan): NonNullable<OcxParsedRequ
|
|
|
214
233
|
} else if (event.type === "incomplete" || event.type === "error") {
|
|
215
234
|
// A broken leg cannot safely anchor a continuation: surface the terminal as-is.
|
|
216
235
|
if (pending) { pending = null; }
|
|
217
|
-
|
|
218
|
-
yield
|
|
236
|
+
accumulatedUsage = retention.usage(accumulatedUsage, event.usage);
|
|
237
|
+
yield accumulatedUsage ? { ...event, usage: accumulatedUsage } : event;
|
|
219
238
|
return;
|
|
220
239
|
} else {
|
|
221
240
|
// Retain (bounded) the leg's visible content so the rebuilt assistant message is complete.
|
|
222
241
|
if (event.type === "text_delta") {
|
|
223
242
|
legTextChars += event.text.length;
|
|
224
|
-
|
|
243
|
+
// Empty deltas add nothing to the rebuilt message; non-empty ones are bounded by the cap.
|
|
244
|
+
if (!hasRealToolCall && event.text !== "" && legTextChars <= MAX_LEG_TEXT_CHARS) { retention.retain(event, false, false); legEvents.push(event); }
|
|
225
245
|
} else if (event.type === "thinking_delta") {
|
|
226
246
|
legThinkingChars += event.thinking.length;
|
|
227
|
-
if (legThinkingChars <= MAX_LEG_THINKING_CHARS) legEvents.push(event);
|
|
247
|
+
if (!hasRealToolCall && event.thinking !== "" && legThinkingChars <= MAX_LEG_THINKING_CHARS) { retention.retain(event, false, false); legEvents.push(event); }
|
|
228
248
|
} else if (
|
|
229
249
|
event.type === "thinking_signature"
|
|
230
250
|
|| event.type === "redacted_thinking"
|
|
231
251
|
) {
|
|
232
|
-
legEvents.push(event);
|
|
252
|
+
if (!hasRealToolCall) { retention.retain(event); legEvents.push(event); }
|
|
233
253
|
}
|
|
234
254
|
}
|
|
235
255
|
yield event;
|
|
@@ -245,14 +265,14 @@ export function createAdvisorGuard(plan: AdvisorPlan): NonNullable<OcxParsedRequ
|
|
|
245
265
|
if (!shouldIntercept) {
|
|
246
266
|
// Plain leg: surface the terminal with merged usage from any earlier intercepted legs.
|
|
247
267
|
if (terminalEvent) {
|
|
248
|
-
|
|
249
|
-
yield
|
|
268
|
+
accumulatedUsage = retention.usage(accumulatedUsage, terminalEvent.usage);
|
|
269
|
+
yield accumulatedUsage ? { ...terminalEvent, usage: accumulatedUsage } : terminalEvent;
|
|
250
270
|
}
|
|
251
271
|
return;
|
|
252
272
|
}
|
|
253
273
|
|
|
254
274
|
// Consume this leg's done — the continuation replaces it.
|
|
255
|
-
accumulatedUsage =
|
|
275
|
+
accumulatedUsage = retention.usage(accumulatedUsage, terminalEvent?.usage);
|
|
256
276
|
if (continuations >= MAX_ADVISOR_CONTINUATIONS_PER_REQUEST || finalContinuationUsed) {
|
|
257
277
|
yield {
|
|
258
278
|
type: "error",
|
|
@@ -269,22 +289,22 @@ export function createAdvisorGuard(plan: AdvisorPlan): NonNullable<OcxParsedRequ
|
|
|
269
289
|
continuations += 1;
|
|
270
290
|
finalContinuationUsed ||= isFinalContinuation;
|
|
271
291
|
const timestamp = Date.now();
|
|
272
|
-
const assistant = assistantMessageFromLeg(legEvents, advisorCalls, timestamp);
|
|
292
|
+
const assistant = retention.history(retention.replayReservation(), () => assistantMessageFromLeg(legEvents, advisorCalls, timestamp, retention));
|
|
273
293
|
const messages: OcxMessage[] = [...parsed.context.messages];
|
|
274
294
|
if (assistant) messages.push(assistant);
|
|
275
295
|
|
|
276
296
|
for (const call of advisorCalls) {
|
|
277
297
|
if (consultations >= maxConsultations) {
|
|
278
298
|
// Runtime-owned limit text: not advice, and never a genuine advice wrapper.
|
|
279
|
-
|
|
299
|
+
const outcome = {
|
|
280
300
|
ok: false,
|
|
281
301
|
isError: true,
|
|
282
302
|
content: plan.formatUnavailable("limit", "consultation limit reached for this request"),
|
|
283
|
-
}
|
|
303
|
+
};
|
|
304
|
+
messages.push(retention.history(jsonUtf8Bytes(outcome) + jsonUtf8Bytes(call.id) + 256, () => advisorToolResult(call, outcome, timestamp))!);
|
|
284
305
|
continue;
|
|
285
306
|
}
|
|
286
307
|
consultations += 1;
|
|
287
|
-
const args = parseAdvisorArgs(call.argsBuf);
|
|
288
308
|
// Later calls in the same leg must see what came before them: the rebuilt worker turn
|
|
289
309
|
// and every earlier advisor result in this leg's accumulated messages.
|
|
290
310
|
const consultParsed: OcxParsedRequest = {
|
|
@@ -293,8 +313,9 @@ export function createAdvisorGuard(plan: AdvisorPlan): NonNullable<OcxParsedRequ
|
|
|
293
313
|
};
|
|
294
314
|
let outcome: AdvisorConsultOutcome;
|
|
295
315
|
try {
|
|
296
|
-
outcome = await plan.consult(consultParsed, "manual",
|
|
316
|
+
outcome = await plan.consult(consultParsed, "manual", call.question);
|
|
297
317
|
} catch (error) {
|
|
318
|
+
if (isTranslatorBudgetExceededError(error)) throw error;
|
|
298
319
|
// Runtime-owned failure text only: the implementation neutralizes untrusted exception
|
|
299
320
|
// text so it can never forge a genuine advice wrapper.
|
|
300
321
|
outcome = {
|
|
@@ -303,7 +324,7 @@ export function createAdvisorGuard(plan: AdvisorPlan): NonNullable<OcxParsedRequ
|
|
|
303
324
|
content: plan.formatUnavailable("manual", error instanceof Error ? error.message : String(error)),
|
|
304
325
|
};
|
|
305
326
|
}
|
|
306
|
-
messages.push(advisorToolResult(call, outcome, timestamp));
|
|
327
|
+
messages.push(retention.history(jsonUtf8Bytes(outcome) + jsonUtf8Bytes(call.id) + 256, () => advisorToolResult(call, outcome, timestamp))!);
|
|
307
328
|
}
|
|
308
329
|
|
|
309
330
|
const exhausted = consultations >= maxConsultations;
|
|
@@ -320,10 +341,16 @@ export function createAdvisorGuard(plan: AdvisorPlan): NonNullable<OcxParsedRequ
|
|
|
320
341
|
: parsed.options,
|
|
321
342
|
};
|
|
322
343
|
parsed = nextParsed;
|
|
344
|
+
// All replay owners were admitted while the raw leg was still live.
|
|
345
|
+
held.length = legEvents.length = advisorCalls.length = 0;
|
|
346
|
+
pending = null;
|
|
347
|
+
terminalEvent = undefined;
|
|
348
|
+
retention.releaseLeg();
|
|
323
349
|
yield { type: "assistant_boundary" };
|
|
324
350
|
try {
|
|
325
351
|
source = await options.continuation(parsed);
|
|
326
352
|
} catch (error) {
|
|
353
|
+
if (isTranslatorBudgetExceededError(error)) throw error;
|
|
327
354
|
yield {
|
|
328
355
|
type: "error",
|
|
329
356
|
message: error instanceof Error ? error.message : String(error),
|
|
@@ -332,5 +359,10 @@ export function createAdvisorGuard(plan: AdvisorPlan): NonNullable<OcxParsedRequ
|
|
|
332
359
|
return;
|
|
333
360
|
}
|
|
334
361
|
}
|
|
362
|
+
} catch (error) {
|
|
363
|
+
if (!isTranslatorBudgetExceededError(error)) throw error;
|
|
364
|
+
yield { type: "error", status: 502, errorType: "upstream_error", code: "translation_buffer_limit",
|
|
365
|
+
message: "upstream translation buffer exceeded the safe limit", ...(accumulatedUsage ? { usage: accumulatedUsage } : {}) };
|
|
366
|
+
} finally { retention.release(); }
|
|
335
367
|
};
|
|
336
368
|
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { anthropicRejectionPolicy, bindAnthropicRejectionPolicy, rejectedAnthropicPayloads } from "../../adapters/anthropic-rejection-memo";
|
|
2
|
+
import { normalizeUpstreamErrorText, readDisplaySafeErrorText } from "./core-errors";
|
|
3
|
+
import { sanitizeNonReplayableUpstreamError } from "./non-replayable-error";
|
|
4
|
+
import { isLikelyRealAnthropicThinkingSignature } from "../../adapters/anthropic";
|
|
5
|
+
import type { AdapterEvent, OcxParsedRequest } from "../../types";
|
|
6
|
+
import { upstreamErrorMessageFromPayload } from "../../lib/errors";
|
|
7
|
+
import { readBoundedResponseBody } from "../../lib/bounded-body";
|
|
8
|
+
import { isNonReplayableResponse } from "../../lib/upstream-retry";
|
|
9
|
+
import { rememberReasoningReplayOpaqueBlobRejection } from "../../responses/reasoning-replay-cache";
|
|
10
|
+
import { prepareOpaqueBlobRecovery, type OpaqueBlobRecoveryGuard } from "./core-opaque-recovery";
|
|
11
|
+
|
|
12
|
+
/** Only the explicit conversation-binding refusal authorizes removing signed history. */
|
|
13
|
+
export function isAnthropicThinkingSignatureBindingRejectionMessage(message: string): boolean {
|
|
14
|
+
return message.includes("Invalid `signature` in `thinking` block")
|
|
15
|
+
&& message.includes("The block is bound to a different conversation");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function carriesThinkingState(parsed: OcxParsedRequest): boolean {
|
|
19
|
+
return parsed.context.messages.some(message => message.role === "assistant"
|
|
20
|
+
&& message.content.some(part => part.type === "thinking"
|
|
21
|
+
&& (isLikelyRealAnthropicThinkingSignature(part.signature)
|
|
22
|
+
|| part.redacted?.some(data => typeof data === "string" && data.length > 0))));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** One request owns both the HTTP refusal and initial-event recovery, never a fresh retry guard. */
|
|
26
|
+
export function createAnthropicThinkingRecovery(
|
|
27
|
+
parsed: OcxParsedRequest,
|
|
28
|
+
signal: AbortSignal,
|
|
29
|
+
guard: OpaqueBlobRecoveryGuard,
|
|
30
|
+
canRetry: () => boolean,
|
|
31
|
+
) {
|
|
32
|
+
let rejectedScope: OcxParsedRequest["_reasoningReplayScope"];
|
|
33
|
+
let rejectedPayloads: ReadonlySet<string> | undefined;
|
|
34
|
+
let armed = false;
|
|
35
|
+
let outputSeen = false;
|
|
36
|
+
let completed = false;
|
|
37
|
+
let failed = false;
|
|
38
|
+
let remembered = false;
|
|
39
|
+
|
|
40
|
+
const prepare = (message: string, adapterName: string, attempt = parsed, response?: Response): boolean => {
|
|
41
|
+
if (signal.aborted || !canRetry() || adapterName !== "anthropic" || guard.attempted
|
|
42
|
+
|| parsed._stripReasoningEncryptedContent === true || !carriesThinkingState(attempt)
|
|
43
|
+
|| !isAnthropicThinkingSignatureBindingRejectionMessage(message)) return false;
|
|
44
|
+
rejectedPayloads = rejectedAnthropicPayloads(response);
|
|
45
|
+
// A real send must prove that it serialized opaque history; otherwise rebuilding could
|
|
46
|
+
// repeat the same request. Direct event-policy callers have no HTTP body to identify.
|
|
47
|
+
if (response && !rejectedPayloads?.size) return false;
|
|
48
|
+
const previous = anthropicRejectionPolicy(parsed);
|
|
49
|
+
if (rejectedPayloads && previous?.mode === "selective") {
|
|
50
|
+
const merged = new Set(previous.fingerprints);
|
|
51
|
+
for (const payload of rejectedPayloads) merged.add(payload);
|
|
52
|
+
rejectedPayloads = merged.size <= 256 ? merged : undefined;
|
|
53
|
+
}
|
|
54
|
+
guard.attempted = true;
|
|
55
|
+
armed = true;
|
|
56
|
+
const scope = parsed._reasoningReplayScope;
|
|
57
|
+
rejectedScope = scope ? { ...scope, current: scope.current ? { ...scope.current } : undefined } : undefined;
|
|
58
|
+
if (!rejectedPayloads) {
|
|
59
|
+
prepareOpaqueBlobRecovery(parsed);
|
|
60
|
+
if (attempt !== parsed) prepareOpaqueBlobRecovery(attempt);
|
|
61
|
+
}
|
|
62
|
+
const policy = rejectedPayloads ? { mode: "selective" as const, fingerprints: rejectedPayloads } : { mode: "blanket" as const };
|
|
63
|
+
bindAnthropicRejectionPolicy(parsed, policy);
|
|
64
|
+
bindAnthropicRejectionPolicy(attempt, policy);
|
|
65
|
+
return true;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const prepareHttp = async (response: Response, adapterName: string, attempt = parsed): Promise<boolean> => {
|
|
69
|
+
if (signal.aborted || !canRetry() || response.status !== 400 || isNonReplayableResponse(response)
|
|
70
|
+
|| adapterName !== "anthropic" || guard.attempted
|
|
71
|
+
|| parsed._stripReasoningEncryptedContent === true || !carriesThinkingState(attempt)) return false;
|
|
72
|
+
try {
|
|
73
|
+
const body = await readBoundedResponseBody(response.clone(), { signal });
|
|
74
|
+
if (!body.displaySafe || body.truncated) return false;
|
|
75
|
+
const message = upstreamErrorMessageFromPayload(JSON.parse(body.text) as unknown);
|
|
76
|
+
return typeof message === "string" && prepare(message, adapterName, attempt, response);
|
|
77
|
+
} catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const observe = (event: AdapterEvent): void => {
|
|
83
|
+
if (!armed) return;
|
|
84
|
+
if (event.type === "error" || event.type === "incomplete") failed = true;
|
|
85
|
+
if (event.type === "done") completed = true;
|
|
86
|
+
if ((event.type === "text_delta" && event.text.length > 0) || event.type === "tool_call_end") outputSeen = true;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
async function* wrap(
|
|
90
|
+
events: AsyncIterable<AdapterEvent>,
|
|
91
|
+
retry: () => AsyncIterable<AdapterEvent>,
|
|
92
|
+
adapterName: () => string,
|
|
93
|
+
replayAllowed = true,
|
|
94
|
+
response?: Response,
|
|
95
|
+
attempt = parsed,
|
|
96
|
+
): AsyncGenerator<AdapterEvent> {
|
|
97
|
+
let source = events;
|
|
98
|
+
let replaySafe = replayAllowed;
|
|
99
|
+
while (true) {
|
|
100
|
+
const iterator = source[Symbol.asyncIterator]();
|
|
101
|
+
let recovered = false;
|
|
102
|
+
try {
|
|
103
|
+
while (true) {
|
|
104
|
+
const next = await iterator.next();
|
|
105
|
+
if (next.done) return;
|
|
106
|
+
const event = next.value;
|
|
107
|
+
if (replaySafe && event.type === "error" && prepare(event.message, adapterName(), attempt, response)) {
|
|
108
|
+
// Close the rejected body before acquiring another dispatch/pacing slot.
|
|
109
|
+
await iterator.return?.();
|
|
110
|
+
source = retry();
|
|
111
|
+
recovered = true;
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
// Even hidden reasoning, boundaries, and tool activity fence a replay. A heartbeat
|
|
115
|
+
// carrying a side-effect latch is never evidence that a turn is safe to repeat.
|
|
116
|
+
replaySafe &&= event.type === "heartbeat" && event.replayUnsafe !== true;
|
|
117
|
+
observe(event);
|
|
118
|
+
yield event;
|
|
119
|
+
}
|
|
120
|
+
} finally {
|
|
121
|
+
if (!recovered) await iterator.return?.();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const rememberSuccess = (response: Readonly<Record<string, unknown>>): void => {
|
|
127
|
+
// HTTP 200, EOF, or a guard-generated completion alone cannot prove the stripped send worked.
|
|
128
|
+
// The delivery owner calls this only after its bridge/fold validated a successful terminal.
|
|
129
|
+
if (!armed || remembered || signal.aborted || failed || !completed || !outputSeen
|
|
130
|
+
|| response.status !== "completed" || !Array.isArray(response.output) || response.output.length === 0
|
|
131
|
+
|| !rejectedScope
|
|
132
|
+
|| rejectedScope.clientThreadId !== parsed._reasoningReplayScope?.clientThreadId
|
|
133
|
+
|| JSON.stringify(rejectedScope.current) !== JSON.stringify(parsed._reasoningReplayScope?.current)) return;
|
|
134
|
+
remembered = true;
|
|
135
|
+
rememberReasoningReplayOpaqueBlobRejection(rejectedScope, rejectedPayloads);
|
|
136
|
+
};
|
|
137
|
+
// The search owner supplies only a fully validated current iteration, never search-card output.
|
|
138
|
+
const observeIteration = (events: readonly AdapterEvent[]): void => {
|
|
139
|
+
outputSeen = false;
|
|
140
|
+
completed = false;
|
|
141
|
+
for (const event of events) observe(event);
|
|
142
|
+
};
|
|
143
|
+
const projectHttpFailure = async (response: Response, abortSignal: AbortSignal): Promise<Extract<AdapterEvent, { type: "error" }>> => {
|
|
144
|
+
const nonReplayable = isNonReplayableResponse(response);
|
|
145
|
+
if (nonReplayable) response = await sanitizeNonReplayableUpstreamError(response, abortSignal);
|
|
146
|
+
const text = await readDisplaySafeErrorText(response, abortSignal, `Provider error ${response.status}`);
|
|
147
|
+
const error = normalizeUpstreamErrorText(text, `Provider error ${response.status}`);
|
|
148
|
+
return { type: "error", status: response.status, message: error.message ?? error.safeText,
|
|
149
|
+
errorType: error.type ?? "upstream_error", ...(error.code ? { code: error.code } : {}),
|
|
150
|
+
...(nonReplayable ? { retryable: false } : {}) };
|
|
151
|
+
};
|
|
152
|
+
return { prepareHttp, prepareEvent: prepare, observeIteration, projectHttpFailure, wrap, rememberSuccess };
|
|
153
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeResponsesIncompleteReason } from "../../responses/incomplete-reason";
|
|
1
2
|
import { MAX_CLIENT_SSE_FRAME_BYTES } from "../sse-frame-buffer";
|
|
2
3
|
import {
|
|
3
4
|
isTransientUpstreamStatus,
|
|
@@ -8,6 +9,7 @@ import {
|
|
|
8
9
|
import { isRateLimitOrQuotaFailureMessage, recognizedUpstreamError } from "../../lib/errors";
|
|
9
10
|
import type { RequestFailureCause, RequestFailureStage } from "../../lib/request-failure-model";
|
|
10
11
|
import { packageVersion } from "../../lib/package-version";
|
|
12
|
+
import { registerProviderResponseProvenance } from "../../providers/request-pacing";
|
|
11
13
|
// If the 101 never arrives (network black hole), give SSE a chance well before
|
|
12
14
|
// the caller's connect timeout (default 200s) would fire.
|
|
13
15
|
export const UPGRADE_DEADLINE_MS = 10_000;
|
|
@@ -49,9 +51,12 @@ export const CODEX_WS_CREATE_FRAME_LIMIT_BYTES =
|
|
|
49
51
|
/** Close code the backend uses for an oversized message (RFC 6455 "message too big"). */
|
|
50
52
|
const WS_CLOSE_MESSAGE_TOO_BIG = 1009;
|
|
51
53
|
|
|
52
|
-
const codexWsUpstreamResponses = new
|
|
53
|
-
const quotaObservedResponses = new
|
|
54
|
-
const codexWsPreludeProjections = new
|
|
54
|
+
const codexWsUpstreamResponses = new WeakMap<Response, true>();
|
|
55
|
+
const quotaObservedResponses = new WeakMap<Response, true>();
|
|
56
|
+
const codexWsPreludeProjections = new WeakMap<Response, true>();
|
|
57
|
+
registerProviderResponseProvenance(codexWsUpstreamResponses);
|
|
58
|
+
registerProviderResponseProvenance(quotaObservedResponses);
|
|
59
|
+
registerProviderResponseProvenance(codexWsPreludeProjections);
|
|
55
60
|
|
|
56
61
|
/** Quota arrived directly at its captured account; do not replay old HTTP prelude headers. */
|
|
57
62
|
export function isCodexWsQuotaObservedResponse(response: Response): boolean {
|
|
@@ -65,7 +70,7 @@ export function isCodexWsUpstreamResponse(response: Response): boolean {
|
|
|
65
70
|
|
|
66
71
|
/** Pre-response projection; its headers retain the exchange's prelude snapshot. */
|
|
67
72
|
export function markCodexWsPreludeProjection(response: Response): void {
|
|
68
|
-
codexWsPreludeProjections.
|
|
73
|
+
codexWsPreludeProjections.set(response, true);
|
|
69
74
|
}
|
|
70
75
|
|
|
71
76
|
export function isCodexWsPreludeProjection(response: Response): boolean {
|
|
@@ -73,8 +78,8 @@ export function isCodexWsPreludeProjection(response: Response): boolean {
|
|
|
73
78
|
}
|
|
74
79
|
|
|
75
80
|
export function markCodexWsResponse(response: Response, observed: boolean): void {
|
|
76
|
-
codexWsUpstreamResponses.
|
|
77
|
-
if (observed) quotaObservedResponses.
|
|
81
|
+
codexWsUpstreamResponses.set(response, true);
|
|
82
|
+
if (observed) quotaObservedResponses.set(response, true);
|
|
78
83
|
}
|
|
79
84
|
|
|
80
85
|
/**
|
|
@@ -313,6 +318,7 @@ export function restoreCodexWsPreOutputRefusal(response: Response): Response {
|
|
|
313
318
|
}
|
|
314
319
|
|
|
315
320
|
const codexWsStageByResponse = new WeakMap<Response, CodexWsStageRecord>();
|
|
321
|
+
registerProviderResponseProvenance(codexWsStageByResponse);
|
|
316
322
|
|
|
317
323
|
export function markCodexWsStage(response: Response, record: CodexWsStageRecord): void {
|
|
318
324
|
const current = codexWsStageByResponse.get(response);
|
|
@@ -457,6 +463,7 @@ export function projectCodexWsFailure(
|
|
|
457
463
|
}
|
|
458
464
|
|
|
459
465
|
const socketDeathStages = new WeakMap<Response, RequestFailureStage>();
|
|
466
|
+
registerProviderResponseProvenance(socketDeathStages);
|
|
460
467
|
|
|
461
468
|
/**
|
|
462
469
|
* Record that a pre-response settle came from the socket closing or failing under the send (#4191),
|
|
@@ -517,12 +524,12 @@ export function normalizeResponsesWsRelayEvent(text: string): ResponsesWsRelayEv
|
|
|
517
524
|
return null;
|
|
518
525
|
}
|
|
519
526
|
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return null;
|
|
520
|
-
const record = payload as Record<string, unknown
|
|
527
|
+
const record = normalizeResponsesIncompleteReason(payload as Record<string, unknown>);
|
|
521
528
|
if (typeof record.type !== "string") return null;
|
|
522
529
|
// A native error may be pretty-printed JSON. SSE prefixes one data line;
|
|
523
530
|
// embedded physical newlines would otherwise truncate the JSON for readers.
|
|
524
531
|
if (record.type !== "response.done") return {
|
|
525
|
-
type: record.type, text: /[\r\n]/.test(text) ? JSON.stringify(record) : text, payload: record,
|
|
532
|
+
type: record.type, text: record !== payload || /[\r\n]/.test(text) ? JSON.stringify(record) : text, payload: record,
|
|
526
533
|
};
|
|
527
534
|
|
|
528
535
|
const response = record.response;
|
|
@@ -4,6 +4,7 @@ import { comboFailureDecision } from "../../combos";
|
|
|
4
4
|
import { httpStatusFromTerminalError } from "../../lib/errors";
|
|
5
5
|
import type { RequestFailureStage } from "../../lib/request-failure-model";
|
|
6
6
|
import type { RequestLogContext } from "../request-log";
|
|
7
|
+
import { carryResponseToolOwnership } from "./response-tool-ownership";
|
|
7
8
|
import { createSseInspector } from "../relay";
|
|
8
9
|
import { MAX_CLIENT_SSE_FRAME_BYTES } from "../sse-frame-buffer";
|
|
9
10
|
|
|
@@ -198,11 +199,11 @@ function replayBufferedResponse(
|
|
|
198
199
|
reader.cancel(reason).catch(() => undefined);
|
|
199
200
|
},
|
|
200
201
|
});
|
|
201
|
-
return new Response(body, {
|
|
202
|
+
return carryResponseToolOwnership(response, new Response(body, {
|
|
202
203
|
status: response.status,
|
|
203
204
|
statusText: response.statusText,
|
|
204
205
|
headers: response.headers,
|
|
205
|
-
});
|
|
206
|
+
}));
|
|
206
207
|
}
|
|
207
208
|
|
|
208
209
|
function failedTerminalResponse(
|