@bitkyc08/opencodex 2.84.0 → 2.87.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/README.md +0 -4
- package/bin/ocx.mjs +61 -3
- package/gui/dist/assets/App-ZfYbLHkB.js +58 -0
- package/gui/dist/assets/App-wT3mXqFz.css +1 -0
- package/gui/dist/assets/{Tray-C9TGtwad.js → Tray-DCd-A1az.js} +1 -1
- package/gui/dist/assets/index-CfYDMj34.js +94 -0
- package/gui/dist/assets/index-DPeB9HpV.css +1 -0
- package/gui/dist/assets/quota-summary-ChvmKGOk.js +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -2
- package/src/adapters/anthropic/beta-allowlist.ts +91 -11
- package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
- package/src/adapters/anthropic/client-identity.ts +1 -1
- package/src/adapters/anthropic/passthrough.ts +16 -3
- package/src/adapters/anthropic-progress.ts +84 -0
- package/src/adapters/anthropic.ts +108 -21
- package/src/adapters/azure.ts +19 -1
- 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/client-fingerprint.ts +24 -0
- package/src/adapters/coding-agent/protocol.ts +1 -1
- package/src/adapters/coding-agent/turn.ts +40 -11
- package/src/adapters/cursor/arg-codec.ts +17 -5
- package/src/adapters/cursor/catalog.ts +8 -0
- package/src/adapters/cursor/effort-map.ts +3 -0
- package/src/adapters/cursor/freeform-args.ts +75 -0
- package/src/adapters/cursor/protobuf-events.ts +36 -23
- package/src/adapters/dsh-account.ts +97 -0
- package/src/adapters/google-antigravity-wire.ts +22 -1
- package/src/adapters/google.ts +37 -10
- 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-chat/response-events.ts +59 -1
- package/src/adapters/openai-chat.ts +8 -5
- 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 +44 -3
- 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 +223 -0
- package/src/advisor/context.ts +238 -0
- package/src/advisor/disclosure.ts +20 -0
- package/src/advisor/evidence-tool.ts +21 -0
- package/src/advisor/evidence.ts +117 -0
- package/src/advisor/runtime.ts +353 -0
- package/src/advisor/settings.ts +144 -0
- package/src/advisor/state.ts +369 -0
- package/src/advisor/synthetic-tool.ts +33 -0
- package/src/bridge/errors.ts +24 -1
- package/src/bridge/internal.ts +3 -1
- package/src/bridge/response-json.ts +18 -3
- package/src/bridge/sse.ts +58 -8
- package/src/bridge/terminal-refusal.ts +59 -0
- package/src/chat/inbound.ts +30 -6
- package/src/chat/outbound.ts +5 -0
- package/src/claude/desktop-3p-library.ts +72 -7
- package/src/claude/desktop-3p.ts +75 -19
- package/src/claude/desktop-first-party.ts +1 -1
- package/src/claude/desktop-picker-platform.ts +4 -0
- package/src/claude/desktop-picker-profile.ts +11 -4
- package/src/claude/desktop-picker.ts +15 -8
- package/src/claude/desktop-profile.ts +10 -1
- package/src/claude/desktop-remote-store-io.ts +5 -3
- package/src/claude/desktop-system-proxy.ts +50 -4
- package/src/claude/inbound-content-options.ts +11 -0
- package/src/claude/inbound.ts +154 -50
- package/src/claude/intercept/local-ca.ts +8 -1
- 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/model-default-settings.ts +154 -0
- package/src/claude/model-info.ts +7 -7
- package/src/claude/outbound.ts +51 -19
- 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 +165 -0
- package/src/cli/auxiliary-listener-preflight.ts +37 -0
- package/src/cli/capabilities-accounts.ts +20 -1
- package/src/cli/capabilities-agents-routing.ts +23 -2
- package/src/cli/capabilities-base.ts +39 -11
- package/src/cli/capabilities-observe-system.ts +63 -3
- package/src/cli/capabilities-provider-models.ts +11 -0
- package/src/cli/chatgpt-command.ts +35 -14
- package/src/cli/claude.ts +46 -6
- 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 +17 -14
- package/src/cli/dispatch.ts +30 -5
- package/src/cli/doctor.ts +15 -1
- package/src/cli/ensure-desktop-cold-start.ts +382 -0
- package/src/cli/export-command.ts +1 -1
- package/src/cli/help.ts +4 -2
- package/src/cli/index.ts +14 -5
- package/src/cli/integration-plan-dto.ts +3 -0
- package/src/cli/log-history.ts +172 -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 +41 -9
- package/src/cli/restart-scope.ts +3 -1
- 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 +3 -1
- package/src/clients/config-export/model-metadata.ts +3 -2
- package/src/clients/config-export/qoder.ts +93 -0
- package/src/clients/config-export.ts +35 -48
- 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/pool-mode-gate.ts +5 -2
- 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 +81 -7
- package/src/codex/autostart-health.ts +4 -0
- package/src/codex/catalog/auto-review.ts +20 -3
- package/src/codex/catalog/build-entries.ts +19 -2
- package/src/codex/catalog/combo-member.ts +14 -5
- package/src/codex/catalog/configured-tiers.ts +50 -0
- package/src/codex/catalog/effort.ts +4 -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 +42 -19
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/native-ultrafast.ts +106 -0
- package/src/codex/catalog/parsing.ts +35 -86
- package/src/codex/catalog/provider-models.ts +40 -34
- package/src/codex/catalog/retained-sync.ts +2 -0
- package/src/codex/catalog/routed-gather.ts +33 -16
- package/src/codex/catalog/vendor-context.ts +82 -0
- 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 +63 -4
- package/src/codex/convergence.ts +2 -0
- package/src/codex/desktop-app/picker-freshness.ts +96 -0
- package/src/codex/desktop-app/types.ts +2 -0
- package/src/codex/desktop-app/windows.ts +29 -16
- package/src/codex/desktop-app-restart.ts +25 -2
- package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
- package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
- package/src/codex/desktop-compatibility/connection-store.ts +87 -0
- package/src/codex/desktop-compatibility/installed-build.ts +73 -0
- package/src/codex/desktop-compatibility/json-body.ts +29 -0
- package/src/codex/desktop-compatibility/native-identity.ts +62 -0
- package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
- package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
- package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
- package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
- package/src/codex/desktop-compatibility/runtime.ts +182 -0
- package/src/codex/desktop-compatibility/service.ts +30 -0
- package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
- package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
- package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
- package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
- package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
- package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
- package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
- package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
- package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
- package/src/codex/desktop-compatibility/windows-key-protection.ts +165 -0
- package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
- package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
- package/src/codex/features.ts +85 -45
- package/src/codex/history-provider.ts +1 -1
- package/src/codex/inject/config-write-section.ts +4 -1
- package/src/codex/inject/history-failure.ts +27 -0
- package/src/codex/inject/remove.ts +18 -6
- 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/journal.ts +16 -1
- package/src/codex/main-account-hard-lock.ts +30 -1
- package/src/codex/main-account.ts +99 -15
- package/src/codex/native-main-refresh-mode.ts +217 -0
- 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 +2 -2
- package/src/codex/routing/pool-recovery.ts +79 -0
- package/src/codex/routing/selection.ts +4 -3
- package/src/codex/routing/thread-affinity.ts +1 -1
- package/src/codex/shim-diagnostics.ts +6 -0
- package/src/codex/shim-overlay.ts +7 -2
- 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/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 +35 -7
- package/src/config/live-reconcile.ts +51 -2
- package/src/config/load-degrade.ts +23 -1
- package/src/config/opencode-wire-upgrade.ts +54 -0
- package/src/config/opencode-zen-upgrade.ts +52 -0
- package/src/config/owner-registry.ts +22 -17
- package/src/config/paths.ts +10 -3
- package/src/config/provider-validation.ts +45 -2
- package/src/config/schema/config-schema.ts +39 -0
- package/src/config/schema/desktop-compatibility.ts +9 -0
- package/src/config/schema/leaf-validators.ts +39 -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 +962 -350
- package/src/generated/model-metadata.ts +1 -1
- package/src/grok/sync.ts +1 -1
- package/src/images/loop.ts +46 -8
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/config-io.ts +20 -2
- package/src/integrations/journal.ts +15 -1
- package/src/integrations/mutation-plan.ts +2 -0
- package/src/integrations/registry.ts +15 -0
- package/src/integrations/store.ts +3 -0
- package/src/integrations/writer.ts +10 -1
- package/src/lib/advisor-activation.ts +16 -0
- package/src/lib/background-work.ts +65 -0
- package/src/lib/bounded-file-read.ts +58 -0
- package/src/lib/bun-path-runtime.d.mts +10 -1
- package/src/lib/bun-path-runtime.mjs +62 -3
- package/src/lib/codex-restart-contract.ts +15 -0
- package/src/lib/desktop-proxy-route.ts +30 -0
- package/src/lib/desktop-upstream-tunnel.ts +72 -0
- package/src/lib/empty-tool-catalog.ts +62 -0
- package/src/lib/errors.ts +26 -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/local-internal-call-capability.ts +55 -0
- package/src/lib/opaque-proxy-tunnel.ts +217 -0
- package/src/lib/package-tree-integrity.ts +2 -2
- package/src/lib/redact.ts +2 -2
- package/src/lib/socks5-fetch.ts +48 -71
- package/src/lib/socks5-handshake.ts +112 -0
- package/src/lib/translator-budget.ts +7 -1
- 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 +62 -12
- package/src/oauth/generic-session-affinity.ts +125 -0
- package/src/oauth/index.ts +188 -111
- package/src/oauth/kiro-device-login.ts +2 -1
- package/src/oauth/kiro.ts +7 -1
- 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/codecs/chat-ir.ts +467 -0
- package/src/protocols/codecs/chat.ts +10 -1
- package/src/protocols/codecs/messages-ir.ts +500 -0
- package/src/protocols/encoders/adapter-events.ts +5 -4
- package/src/protocols/encoders/messages.ts +9 -6
- package/src/protocols/features.ts +10 -2
- package/src/protocols/path.ts +11 -1
- package/src/protocols/plan-snapshot.ts +39 -16
- package/src/protocols/plan.ts +9 -1
- package/src/protocols/provider-summary.ts +2 -1
- package/src/protocols/settings.ts +8 -0
- package/src/providers/antigravity-account-roster.ts +102 -0
- package/src/providers/antigravity-models.ts +85 -7
- 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 +5 -0
- package/src/providers/model-rename-migration.ts +7 -5
- 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/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 +44 -14
- package/src/providers/registry/entries-extended.ts +19 -0
- 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 +78 -22
- package/src/responses/reasoning-envelope.ts +4 -0
- package/src/responses/reasoning-replay-cache.ts +24 -2
- 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/responses/state.ts +32 -29
- package/src/router.ts +8 -321
- package/src/routing/health.ts +37 -22
- package/src/routing/history/build-file.ts +92 -0
- package/src/routing/history/indexer.ts +254 -221
- package/src/routing/history/ingest.ts +242 -0
- package/src/routing/history/rebuild-job.ts +111 -0
- package/src/routing/history/rebuild-protocol.ts +15 -0
- package/src/routing/history/rebuild-worker.ts +34 -0
- package/src/routing/profile.ts +1 -1
- package/src/server/admission-model-scope.ts +21 -1
- package/src/server/antigravity-direct-eligibility.ts +208 -0
- package/src/server/auth-cors.ts +15 -0
- package/src/server/chat-completions.ts +82 -10
- 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 +164 -433
- package/src/server/effort-policy.ts +1 -1
- package/src/server/images.ts +15 -2
- package/src/server/index/desktop-compatibility-startup.ts +56 -0
- 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 +11 -3
- package/src/server/index/startup-warnings.ts +11 -0
- package/src/server/index/websocket-handler.ts +9 -0
- package/src/server/index.ts +14 -11
- package/src/server/inference/advisor-diagnostics.ts +31 -0
- package/src/server/inference/client-encoder-delivery.ts +6 -5
- package/src/server/login-shell-path.ts +146 -0
- package/src/server/management/advisor-routes.ts +202 -0
- package/src/server/management/agent-settings-routes.ts +22 -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 +4 -0
- package/src/server/management/config-routes.ts +77 -6
- package/src/server/management/context.ts +4 -0
- package/src/server/management/cursor-integration-routes.ts +1 -1
- package/src/server/management/desktop-compatibility-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
- package/src/server/management/model-routes.ts +13 -8
- 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/protocol-settings-patch.ts +1 -0
- 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 +21 -1
- package/src/server/management/sibling-guard.ts +1 -0
- package/src/server/management-api.ts +9 -1
- package/src/server/messages-antigravity-direct.ts +195 -0
- package/src/server/messages-native-tool-guard.ts +185 -0
- package/src/server/messages-native.ts +106 -22
- 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 +13 -1
- package/src/server/responses/account-change-state.ts +8 -2
- package/src/server/responses/adapter-continuation.ts +48 -19
- package/src/server/responses/adapter-delivery.ts +43 -8
- package/src/server/responses/adapter-dispatch.ts +48 -4
- package/src/server/responses/advisor-plan-slot.ts +27 -0
- package/src/server/responses/advisor-retention.ts +117 -0
- package/src/server/responses/advisor-slot.ts +368 -0
- package/src/server/responses/code-mode-collab-surface.ts +116 -8
- package/src/server/responses/codex-ws-wire.ts +15 -8
- package/src/server/responses/compact.ts +303 -125
- package/src/server/responses/compaction-recovery.ts +18 -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 +12 -1
- package/src/server/responses/core-combo.ts +141 -36
- package/src/server/responses/core-errors.ts +3 -3
- package/src/server/responses/core-lifetime.ts +7 -2
- package/src/server/responses/core-normalize.ts +11 -0
- package/src/server/responses/core-opaque-recovery.ts +35 -0
- package/src/server/responses/core-options.ts +51 -0
- package/src/server/responses/core-pipeline.ts +141 -0
- package/src/server/responses/core-replay.ts +25 -6
- package/src/server/responses/core.ts +3 -116
- 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 +19 -11
- package/src/server/responses/passthrough-dispatch.ts +29 -16
- package/src/server/responses/request-prepare.ts +144 -17
- package/src/server/responses/request-sidecar-auth.ts +2 -2
- package/src/server/responses/request-transport.ts +120 -27
- package/src/server/responses/response-effects.ts +1 -0
- package/src/server/responses/run-turn-execution.ts +166 -38
- package/src/server/responses/sidecar-execution.ts +98 -5
- package/src/server/responses/terminal-guard.ts +4 -17
- package/src/server/responses-undeclared-tool-guard.ts +30 -9
- package/src/server/startup-action-control.ts +16 -2
- package/src/server/startup-health-cache.ts +1 -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/desktop-startup.ts +14 -0
- package/src/service/guards.ts +1 -61
- package/src/service/health-sentinel-linux.ts +112 -0
- package/src/service/health-sentinel-main.ts +99 -61
- package/src/service/health-sentinel-probe.ts +24 -7
- package/src/service/health.ts +5 -89
- package/src/service/launchd.ts +27 -7
- package/src/service/logging.ts +135 -0
- 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/tray/windows-tray.ps1 +43 -24
- package/src/types/config.ts +124 -6
- package/src/types/opencode-go.ts +61 -0
- package/src/types/opencode-zen.ts +29 -0
- package/src/types/provider.ts +39 -6
- package/src/types/request.ts +38 -2
- package/src/types/tools.ts +2 -0
- package/src/types/wire.ts +29 -37
- package/src/types.ts +1 -0
- package/src/update/async-check.ts +1 -1
- package/src/update/badge.ts +2 -2
- package/src/update/index.ts +88 -15
- package/src/update/job-supervised-restart.ts +107 -0
- package/src/update/job.ts +32 -32
- package/src/update/notify.ts +1 -1
- package/src/update/npm-invocation.mjs +12 -2
- package/src/update/refresh-scheduler.ts +2 -2
- package/src/update/supervised-recovery.d.mts +27 -0
- package/src/update/supervised-recovery.mjs +157 -0
- package/src/usage/cache-diagnostic.ts +2 -0
- package/src/usage/cost.ts +48 -12
- package/src/usage/expected-prices.ts +15 -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 +22 -9
- 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/gemini-executor.ts +2 -2
- package/src/web-search/loop.ts +55 -5
- package/src/web-search/progress-stream.ts +4 -1
- package/src/web-search/run-turn-loop.ts +9 -1
- package/assets/sponsors/packycode.png +0 -0
- package/gui/dist/assets/App-CSJvmjNs.css +0 -1
- package/gui/dist/assets/App-CZtqiGcw.js +0 -56
- package/gui/dist/assets/index-BB6uK6tU.css +0 -1
- package/gui/dist/assets/index-CTm4l-HQ.js +0 -94
- package/gui/dist/assets/quota-summary-BbEeRfPY.js +0 -1
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accounting and measurement pieces shared by the two Claude Messages lanes.
|
|
3
|
+
*
|
|
4
|
+
* claude-messages.ts (caller-credential passthrough and translate-and-replay) and
|
|
5
|
+
* messages-native.ts (the proxy-managed native lane) both need this layer, and
|
|
6
|
+
* neither may import the other. It once closed a cycle: messages-native.ts took this
|
|
7
|
+
* code statically from claude-messages.ts while claude-messages.ts reached back
|
|
8
|
+
* dynamically to dispatch, so every install that only serves caller-forward
|
|
9
|
+
* passthrough still loaded the whole native lane.
|
|
10
|
+
*
|
|
11
|
+
* Everything here is a leaf: lib/, adapters/ primitives, and types only. Nothing
|
|
12
|
+
* reaches another server/ module, so either lane can take it without pulling in the
|
|
13
|
+
* other.
|
|
14
|
+
*/
|
|
15
|
+
import { sniffImageDimensions } from "../adapters/anthropic-image-guard";
|
|
16
|
+
import { createToolCallIdAllocator } from "../adapters/tool-call-id";
|
|
17
|
+
import { idleDeadline } from "../lib/abort";
|
|
18
|
+
import {
|
|
19
|
+
CLAUDE_NATIVE_THINKING,
|
|
20
|
+
projectClaudeRequest,
|
|
21
|
+
type ClaudeThinkingProjection,
|
|
22
|
+
} from "../lib/claude-request-projection";
|
|
23
|
+
import { redactSecretString } from "../lib/redact";
|
|
24
|
+
import { sseFieldValue } from "../lib/sse-decoder";
|
|
25
|
+
import { estimateTokens } from "../lib/token-estimate";
|
|
26
|
+
import { isTranslatorBudgetExceededError } from "../lib/translator-budget";
|
|
27
|
+
import { AnthropicRequestError, isRec, type Rec } from "../claude/inbound-records";
|
|
28
|
+
import type { RequestLogContext } from "./request-log";
|
|
29
|
+
import { recordGenerationEvent } from "./request-log-generation-window";
|
|
30
|
+
import type { OcxConfig } from "../types";
|
|
31
|
+
|
|
32
|
+
export function anthropicUsageToOcx(usage: Rec | undefined): { inputTokens: number; outputTokens: number; cachedInputTokens?: number; cacheReadInputTokens?: number; cacheCreationInputTokens?: number } | undefined {
|
|
33
|
+
if (!usage) return undefined;
|
|
34
|
+
const num = (v: unknown) => typeof v === "number" ? v : 0;
|
|
35
|
+
const hasCache = usage.cache_read_input_tokens !== undefined || usage.cache_creation_input_tokens !== undefined;
|
|
36
|
+
const read = num(usage.cache_read_input_tokens);
|
|
37
|
+
const write = num(usage.cache_creation_input_tokens);
|
|
38
|
+
// Anthropic input_tokens excludes cache read/write; normalize to the canonical
|
|
39
|
+
// inclusive convention (types.ts OcxUsage / devlog 070). cached = READS only.
|
|
40
|
+
return {
|
|
41
|
+
inputTokens: num(usage.input_tokens) + read + write,
|
|
42
|
+
outputTokens: num(usage.output_tokens),
|
|
43
|
+
...(hasCache ? {
|
|
44
|
+
cachedInputTokens: read,
|
|
45
|
+
cacheReadInputTokens: read,
|
|
46
|
+
cacheCreationInputTokens: write,
|
|
47
|
+
} : {}),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Body-occupancy guard for the native passthrough (devlog 260716_passthrough_followups/010). */
|
|
52
|
+
export interface PassthroughBodyGuard {
|
|
53
|
+
/** Idle window in ms — raw upstream-byte inactivity while a read is pending. 0 disables. */
|
|
54
|
+
stallMs: number;
|
|
55
|
+
/** Cumulative body byte cap. 0 disables. */
|
|
56
|
+
maxBytes: number;
|
|
57
|
+
/** Client request signal for deterministic cancel classification. */
|
|
58
|
+
reqSignal?: AbortSignal;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type PassthroughCloseReason = "terminal" | "client_cancel" | "body_stall" | "body_overflow";
|
|
62
|
+
export type PassthroughFinalizeMeta = { closeReason: PassthroughCloseReason; terminalStatus?: "failed" | "incomplete" };
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Tap an Anthropic-vocabulary SSE stream for the request log (usage + terminal),
|
|
66
|
+
* bounding body occupancy: idle (silence-only, timed ONLY while a reader.read() is
|
|
67
|
+
* pending so downstream backpressure never counts as upstream inactivity) and a
|
|
68
|
+
* cumulative byte cap. On stall/overflow, or when the upstream read fails after the
|
|
69
|
+
* headers went out, it appends a protocol-compatible Anthropic `event: error` terminal
|
|
70
|
+
* frame after a blank-line boundary, closes, and cancels the upstream reader — never a
|
|
71
|
+
* total-wall-clock bound (slow-but-alive streams live).
|
|
72
|
+
* Exported for deterministic unit tests.
|
|
73
|
+
*/
|
|
74
|
+
export function tapAnthropicSseForLog(
|
|
75
|
+
upstream: ReadableStream<Uint8Array>,
|
|
76
|
+
logCtx: RequestLogContext,
|
|
77
|
+
finalize: (status: number, meta: PassthroughFinalizeMeta) => void,
|
|
78
|
+
guard?: PassthroughBodyGuard,
|
|
79
|
+
): ReadableStream<Uint8Array> {
|
|
80
|
+
const decoder = new TextDecoder();
|
|
81
|
+
const encoder = new TextEncoder();
|
|
82
|
+
let buffer = "";
|
|
83
|
+
let usageAcc: Rec = {};
|
|
84
|
+
// message_stop or an upstream error event: the turn's own terminal already went through.
|
|
85
|
+
let terminalSeen = false;
|
|
86
|
+
const inspectFrame = (frame: string) => {
|
|
87
|
+
const dataLine = frame
|
|
88
|
+
.split("\n")
|
|
89
|
+
.map(l => sseFieldValue(l, "data"))
|
|
90
|
+
.filter((v): v is string => v !== null)
|
|
91
|
+
.join("");
|
|
92
|
+
if (!dataLine) return;
|
|
93
|
+
let data: unknown;
|
|
94
|
+
try { data = JSON.parse(dataLine); } catch { return; }
|
|
95
|
+
if (!isRec(data)) return;
|
|
96
|
+
recordGenerationEvent(logCtx, data.type);
|
|
97
|
+
if (data.type === "message_start" && isRec(data.message) && isRec(data.message.usage)) {
|
|
98
|
+
usageAcc = { ...usageAcc, ...data.message.usage };
|
|
99
|
+
} else if (data.type === "message_delta" && isRec(data.usage)) {
|
|
100
|
+
usageAcc = { ...usageAcc, ...data.usage };
|
|
101
|
+
} else if (data.type === "message_stop" || data.type === "error") {
|
|
102
|
+
terminalSeen = true;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
const inspect = (chunk: Uint8Array) => {
|
|
106
|
+
buffer += decoder.decode(chunk, { stream: true });
|
|
107
|
+
// SSE lines may end in CRLF, LF or CR. Normalize the inspection copy to LF (the forwarded
|
|
108
|
+
// bytes are untouched), holding a trailing CR until the next chunk shows whether an LF
|
|
109
|
+
// follows it, so a CRLF split across chunks stays one line ending.
|
|
110
|
+
const heldCr = buffer.endsWith("\r");
|
|
111
|
+
buffer = (heldCr ? buffer.slice(0, -1) : buffer).replace(/\r\n?/g, "\n") + (heldCr ? "\r" : "");
|
|
112
|
+
let sep: number;
|
|
113
|
+
while ((sep = buffer.indexOf("\n\n")) !== -1) {
|
|
114
|
+
const frame = buffer.slice(0, sep);
|
|
115
|
+
buffer = buffer.slice(sep + 2);
|
|
116
|
+
inspectFrame(frame);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
// The last block can sit in the buffer without its blank line: an upstream that stopped after
|
|
120
|
+
// it, or a held trailing CR. Count it before deciding how the turn ended (the Responses relay
|
|
121
|
+
// flushes the same candidate). Returns true when this flush found the terminal in a block the
|
|
122
|
+
// client has not seen a blank line after, so the caller restores one.
|
|
123
|
+
const flushTail = (): boolean => {
|
|
124
|
+
const terminalBefore = terminalSeen;
|
|
125
|
+
const tail = (buffer + decoder.decode()).replace(/\r\n?/g, "\n");
|
|
126
|
+
buffer = "";
|
|
127
|
+
if (tail) inspectFrame(tail);
|
|
128
|
+
return terminalSeen && !terminalBefore && !tail.endsWith("\n\n");
|
|
129
|
+
};
|
|
130
|
+
const reader = upstream.getReader();
|
|
131
|
+
let settled = false;
|
|
132
|
+
let bodyBytes = 0;
|
|
133
|
+
let tapController: ReadableStreamDefaultController<Uint8Array> | undefined;
|
|
134
|
+
|
|
135
|
+
const recordUsage = () => {
|
|
136
|
+
logCtx.usage = anthropicUsageToOcx(Object.keys(usageAcc).length > 0 ? usageAcc : undefined);
|
|
137
|
+
};
|
|
138
|
+
const closeWithErrorFrame = (errType: string, message: string) => {
|
|
139
|
+
const payload = JSON.stringify({ type: "error", error: { type: errType, message } });
|
|
140
|
+
try {
|
|
141
|
+
// Leading blank line terminates any partial SSE block so the frame parses cleanly
|
|
142
|
+
// (relaySseWithFailedTail policy, Anthropic wire shape).
|
|
143
|
+
tapController?.enqueue(encoder.encode(`\n\nevent: error\ndata: ${payload}\n\n`));
|
|
144
|
+
tapController?.close();
|
|
145
|
+
} catch { /* client already torn down */ }
|
|
146
|
+
};
|
|
147
|
+
const failBody = (closeReason: "body_stall" | "body_overflow", errType: string, message: string) => {
|
|
148
|
+
if (settled) return;
|
|
149
|
+
settled = true;
|
|
150
|
+
idle.cancel();
|
|
151
|
+
detachAbort();
|
|
152
|
+
const terminalInTail = flushTail();
|
|
153
|
+
recordUsage();
|
|
154
|
+
if (terminalSeen) {
|
|
155
|
+
// The turn already ended (message_stop or an upstream error event): an upstream that then
|
|
156
|
+
// idles or keeps sending did not cut it short. Same rule as the read-error branch,
|
|
157
|
+
// including the restored blank line for a terminal found only in the tail.
|
|
158
|
+
finalize(200, { closeReason: "terminal" });
|
|
159
|
+
try {
|
|
160
|
+
if (terminalInTail) tapController?.enqueue(encoder.encode("\n\n"));
|
|
161
|
+
tapController?.close();
|
|
162
|
+
} catch { /* client already torn down */ }
|
|
163
|
+
} else {
|
|
164
|
+
// A cut-short turn, logged as the Responses relay logs a stall-timeout incomplete
|
|
165
|
+
// (httpStatusForRequestLogTerminal: only a max_output_tokens incomplete is a 200). A 200
|
|
166
|
+
// row with no terminalStatus also lost its failure diagnostics in usage.jsonl.
|
|
167
|
+
logCtx.upstreamError = message.slice(0, 500);
|
|
168
|
+
finalize(502, { terminalStatus: "incomplete", closeReason });
|
|
169
|
+
closeWithErrorFrame(errType, message);
|
|
170
|
+
}
|
|
171
|
+
reader.cancel(new DOMException(message, closeReason === "body_stall" ? "TimeoutError" : "QuotaExceededError")).catch(() => {});
|
|
172
|
+
};
|
|
173
|
+
const idle = idleDeadline(guard?.stallMs ?? 0, () => {
|
|
174
|
+
failBody(
|
|
175
|
+
"body_stall",
|
|
176
|
+
"timeout_error",
|
|
177
|
+
`anthropic passthrough body stalled: no upstream bytes for ${Math.round((guard?.stallMs ?? 0) / 1000)}s`,
|
|
178
|
+
);
|
|
179
|
+
});
|
|
180
|
+
// Deterministic client-cancel classification: Bun may surface a client abort as a
|
|
181
|
+
// reader.read() rejection OR a resolved done (src/lib/abort.ts cancelBodyOnAbort
|
|
182
|
+
// rationale), so the listener performs first-wins settlement itself instead of
|
|
183
|
+
// relying on which shape the read takes.
|
|
184
|
+
const onClientAbort = () => {
|
|
185
|
+
if (settled) return;
|
|
186
|
+
settled = true;
|
|
187
|
+
idle.cancel();
|
|
188
|
+
detachAbort();
|
|
189
|
+
finalize(499, { closeReason: "client_cancel" });
|
|
190
|
+
try { tapController?.close(); } catch { /* downstream already torn down */ }
|
|
191
|
+
reader.cancel(guard?.reqSignal?.reason).catch(() => {});
|
|
192
|
+
};
|
|
193
|
+
const detachAbort = (() => {
|
|
194
|
+
const signal = guard?.reqSignal;
|
|
195
|
+
if (!signal) return () => {};
|
|
196
|
+
if (signal.aborted) {
|
|
197
|
+
queueMicrotask(onClientAbort);
|
|
198
|
+
return () => {};
|
|
199
|
+
}
|
|
200
|
+
signal.addEventListener("abort", onClientAbort, { once: true });
|
|
201
|
+
return () => signal.removeEventListener("abort", onClientAbort);
|
|
202
|
+
})();
|
|
203
|
+
|
|
204
|
+
return new ReadableStream<Uint8Array>({
|
|
205
|
+
start(controller) {
|
|
206
|
+
tapController = controller;
|
|
207
|
+
},
|
|
208
|
+
async pull(controller) {
|
|
209
|
+
if (settled) return;
|
|
210
|
+
try {
|
|
211
|
+
idle.reset();
|
|
212
|
+
const { done, value } = await reader.read();
|
|
213
|
+
idle.pause();
|
|
214
|
+
if (settled) return; // stall/overflow/abort won the race while we awaited
|
|
215
|
+
if (done) {
|
|
216
|
+
settled = true;
|
|
217
|
+
idle.cancel();
|
|
218
|
+
detachAbort();
|
|
219
|
+
recordUsage();
|
|
220
|
+
finalize(200, { closeReason: "terminal" });
|
|
221
|
+
controller.close();
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (value.byteLength > 0) {
|
|
225
|
+
bodyBytes += value.byteLength;
|
|
226
|
+
if (guard && guard.maxBytes > 0 && bodyBytes > guard.maxBytes) {
|
|
227
|
+
failBody(
|
|
228
|
+
"body_overflow",
|
|
229
|
+
"api_error",
|
|
230
|
+
`anthropic passthrough body exceeded ${guard.maxBytes} bytes`,
|
|
231
|
+
);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
inspect(value);
|
|
236
|
+
controller.enqueue(value);
|
|
237
|
+
} catch (err) {
|
|
238
|
+
if (settled) return;
|
|
239
|
+
// Bun can settle a fetch body read before it dispatches the abort listeners
|
|
240
|
+
// (consumeForInspection in relay.ts): read the signal itself before calling this
|
|
241
|
+
// rejection an upstream failure.
|
|
242
|
+
if (guard?.reqSignal?.aborted) {
|
|
243
|
+
onClientAbort();
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
settled = true;
|
|
247
|
+
idle.cancel();
|
|
248
|
+
detachAbort();
|
|
249
|
+
const terminalInTail = flushTail();
|
|
250
|
+
recordUsage();
|
|
251
|
+
if (isTranslatorBudgetExceededError(err)) {
|
|
252
|
+
// A local cap, not an upstream failure: the non-streaming native Messages fold
|
|
253
|
+
// maps this error to a 413 itself, so it still errors the stream.
|
|
254
|
+
finalize(200, { closeReason: "terminal" });
|
|
255
|
+
try { controller.error(err); } catch { /* torn down */ }
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (terminalSeen) {
|
|
259
|
+
// Only the transport trailer was lost; the client already has the turn's terminal.
|
|
260
|
+
// The Responses relay likewise reports a read error only without a seen terminal.
|
|
261
|
+
finalize(200, { closeReason: "terminal" });
|
|
262
|
+
try {
|
|
263
|
+
// An SSE parser drops an event that EOF cuts off before its blank line, so restore
|
|
264
|
+
// the delimiter when the terminal was only found in that unterminated tail.
|
|
265
|
+
if (terminalInTail) controller.enqueue(encoder.encode("\n\n"));
|
|
266
|
+
controller.close();
|
|
267
|
+
} catch { /* torn down */ }
|
|
268
|
+
reader.cancel(err).catch(() => {});
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
// The upstream read failed after the 200 went out (a mid-stream socket reset). Log it
|
|
272
|
+
// the way the Responses relay does (onReadError): a truncated body is a failed turn,
|
|
273
|
+
// not a completed one. The client gets the same Anthropic error terminal as a stall,
|
|
274
|
+
// instead of a connection reset — or, on some Bun releases, a bare EOF that reads as
|
|
275
|
+
// a finished message.
|
|
276
|
+
const message = redactSecretString(`anthropic passthrough upstream stream failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
277
|
+
logCtx.transportPhase = "mid_stream";
|
|
278
|
+
logCtx.terminalSource = "synthetic";
|
|
279
|
+
logCtx.upstreamError = message.slice(0, 500);
|
|
280
|
+
if (logCtx.activeAttempt) logCtx.activeAttempt.streamAborted = true;
|
|
281
|
+
finalize(502, { terminalStatus: "failed", closeReason: "terminal" });
|
|
282
|
+
closeWithErrorFrame("api_error", message);
|
|
283
|
+
reader.cancel(err).catch(() => {});
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
cancel(reason) {
|
|
287
|
+
if (!settled) {
|
|
288
|
+
settled = true;
|
|
289
|
+
idle.cancel();
|
|
290
|
+
detachAbort();
|
|
291
|
+
finalize(499, { closeReason: "client_cancel" });
|
|
292
|
+
}
|
|
293
|
+
reader.cancel(reason).catch(() => {});
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* `tool_use.id` / `tool_result.tool_use_id` must match Anthropic's wire contract
|
|
300
|
+
* (`^[a-zA-Z0-9_-]+$`, <=64 chars). Third-party models mint other shapes — Devin's
|
|
301
|
+
* swe-2 emits `Bash:0#<hex>` — and a session history carrying them 400s the moment it
|
|
302
|
+
* is switched to a native Anthropic model ("messages.N.content.M.tool_use.id: String
|
|
303
|
+
* should match pattern"). The adapter path normalizes these via
|
|
304
|
+
* adapters/tool-call-id.ts (#1780); this passthrough bypasses that adapter, so the same
|
|
305
|
+
* allocator runs here. Stateless per request: conforming ids pass through byte-identical
|
|
306
|
+
* (prompt-cache keys untouched), rewritten ids keep call/result pairing stable.
|
|
307
|
+
* An empty id has no representable wire form, and forwarding `""` is what Anthropic
|
|
308
|
+
* rejects (#1767), so the request fails locally with a 400 before any upstream fetch.
|
|
309
|
+
*/
|
|
310
|
+
export function sanitizePassthroughToolCallIds(messages: unknown[]): void {
|
|
311
|
+
const blocks: Rec[] = [];
|
|
312
|
+
for (const message of messages) {
|
|
313
|
+
if (!isRec(message) || !Array.isArray(message.content)) continue;
|
|
314
|
+
for (const block of message.content) if (isRec(block)) blocks.push(block);
|
|
315
|
+
}
|
|
316
|
+
const fieldOf = (block: Rec): "id" | "tool_use_id" | undefined => {
|
|
317
|
+
if (typeof block.type !== "string") return undefined;
|
|
318
|
+
if (block.type.endsWith("tool_use")) return "id";
|
|
319
|
+
if (block.type.endsWith("tool_result")) return "tool_use_id";
|
|
320
|
+
return undefined;
|
|
321
|
+
};
|
|
322
|
+
const callIds = createToolCallIdAllocator();
|
|
323
|
+
for (const block of blocks) {
|
|
324
|
+
const field = fieldOf(block);
|
|
325
|
+
if (field && typeof block[field] === "string") callIds.reserve(block[field] as string);
|
|
326
|
+
}
|
|
327
|
+
for (const block of blocks) {
|
|
328
|
+
const field = fieldOf(block);
|
|
329
|
+
if (!field || typeof block[field] !== "string") continue;
|
|
330
|
+
const wire = callIds.allocate(block[field] as string);
|
|
331
|
+
if (wire === undefined) throw new AnthropicRequestError(`${block.type} block has an empty ${field}`);
|
|
332
|
+
block[field] = wire;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const DEFAULT_BODY_STALL_SEC = 90;
|
|
337
|
+
const DEFAULT_BODY_MAX_BYTES = 64 * 1024 * 1024;
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Normalize the claudeCode body-guard config (devlog 260716_passthrough_followups/010).
|
|
341
|
+
* Policy: exactly 0 disables; finite positive values are honored (stall clamped to
|
|
342
|
+
* min 1s); negative/non-finite/absent values fall back to the defaults.
|
|
343
|
+
*/
|
|
344
|
+
export function resolvePassthroughBodyGuard(config: OcxConfig, reqSignal?: AbortSignal): PassthroughBodyGuard {
|
|
345
|
+
const rawSec = config.claudeCode?.bodyStallSec;
|
|
346
|
+
const stallSec = rawSec === 0
|
|
347
|
+
? 0
|
|
348
|
+
: typeof rawSec === "number" && Number.isFinite(rawSec) && rawSec > 0
|
|
349
|
+
? Math.max(1, rawSec)
|
|
350
|
+
: DEFAULT_BODY_STALL_SEC;
|
|
351
|
+
const rawBytes = config.claudeCode?.bodyMaxBytes;
|
|
352
|
+
const maxBytes = rawBytes === 0
|
|
353
|
+
? 0
|
|
354
|
+
: typeof rawBytes === "number" && Number.isFinite(rawBytes) && rawBytes > 0
|
|
355
|
+
? Math.floor(rawBytes)
|
|
356
|
+
: DEFAULT_BODY_MAX_BYTES;
|
|
357
|
+
return { stallMs: stallSec * 1000, maxBytes, ...(reqSignal ? { reqSignal } : {}) };
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** Per-attachment token estimate for a base64 payload: real image dimensions when the
|
|
361
|
+
* header is sniffable (Anthropic prices images at ~pixels/750), else decoded bytes/512,
|
|
362
|
+
* min 256 — the same shape as the Kiro usage estimator (estimateKiroImageTokens). */
|
|
363
|
+
function estimateBase64AttachmentTokens(data: string): number {
|
|
364
|
+
const dims = sniffImageDimensions(data);
|
|
365
|
+
if (dims) return Math.max(256, Math.ceil((dims.width * dims.height) / 750));
|
|
366
|
+
const unpadded = data.endsWith("==") ? data.length - 2 : data.endsWith("=") ? data.length - 1 : data.length;
|
|
367
|
+
return Math.max(256, Math.ceil(Math.floor((unpadded * 3) / 4) / 512));
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Char-based token estimate for an Anthropic-shaped request body. Base64 attachment
|
|
372
|
+
* payloads (image/document blocks in message content, including blocks nested in
|
|
373
|
+
* tool_result.content) are counted as a bounded per-attachment estimate instead of raw
|
|
374
|
+
* characters: one 2MB screenshot is ~2.7M base64 chars, which the plain chars/token
|
|
375
|
+
* divide reports as hundreds of thousands of tokens versus a real cost around 1.6k.
|
|
376
|
+
* That breaks the >2x drift bound the estimator is held to (devlog 260711_claude_inbound
|
|
377
|
+
* 040 §3); a live 260-message turn whose replayed thinking was 78.8% of the body breached
|
|
378
|
+
* it at 3.28x, which is why the estimate is projected onto the settled route. Text and url
|
|
379
|
+
* sources are left in place and counted as characters, as is
|
|
380
|
+
* anything outside protocol content positions (tool_use.input, tool schemas).
|
|
381
|
+
*
|
|
382
|
+
* `thinking` selects which replayed thinking fields the SETTLED route serializes, so the measure
|
|
383
|
+
* describes the prompt this proxy forwards rather than the one the caller typed. Omitted, the
|
|
384
|
+
* whole body counts — correct for the Anthropic-native wire, where nothing is projected away.
|
|
385
|
+
* See `claude-request-projection.ts` for why a routed wire must project it out.
|
|
386
|
+
*/
|
|
387
|
+
export function estimateClaudeRequestTokens(
|
|
388
|
+
raw: { system?: unknown; messages?: unknown; tools?: unknown },
|
|
389
|
+
modelId: string | undefined,
|
|
390
|
+
thinking: ClaudeThinkingProjection = CLAUDE_NATIVE_THINKING,
|
|
391
|
+
): number {
|
|
392
|
+
let attachmentTokens = 0;
|
|
393
|
+
// Blank base64 payloads ONLY in protocol content positions: message content blocks and
|
|
394
|
+
// blocks nested in tool_result.content. tool_use.input and tool schemas can legitimately
|
|
395
|
+
// contain attachment-shaped JSON, and those bytes ARE serialized into function_call
|
|
396
|
+
// arguments / tool definitions for routed providers, so they must keep counting as text.
|
|
397
|
+
// system is text-only per the Anthropic protocol (no attachment sources), so it is
|
|
398
|
+
// stringified as-is.
|
|
399
|
+
const sanitizeBlock = (block: unknown): unknown => {
|
|
400
|
+
if (!block || typeof block !== "object") return block;
|
|
401
|
+
const b = block as Record<string, unknown>;
|
|
402
|
+
if (b.type === "image" || b.type === "document") {
|
|
403
|
+
const source = b.source as { type?: unknown; data?: unknown } | undefined;
|
|
404
|
+
if (source && typeof source === "object" && source.type === "base64" && typeof source.data === "string") {
|
|
405
|
+
attachmentTokens += estimateBase64AttachmentTokens(source.data);
|
|
406
|
+
return { ...b, source: { ...(source as Record<string, unknown>), data: "" } };
|
|
407
|
+
}
|
|
408
|
+
return block;
|
|
409
|
+
}
|
|
410
|
+
if (b.type === "tool_result" && Array.isArray(b.content)) {
|
|
411
|
+
return { ...b, content: (b.content as unknown[]).map(sanitizeBlock) };
|
|
412
|
+
}
|
|
413
|
+
return block;
|
|
414
|
+
};
|
|
415
|
+
const sanitizedMessages = (messages: unknown): unknown =>
|
|
416
|
+
Array.isArray(messages)
|
|
417
|
+
? messages.map(message => {
|
|
418
|
+
if (!message || typeof message !== "object") return message;
|
|
419
|
+
const m = message as Record<string, unknown>;
|
|
420
|
+
return Array.isArray(m.content) ? { ...m, content: (m.content as unknown[]).map(sanitizeBlock) } : message;
|
|
421
|
+
})
|
|
422
|
+
: messages;
|
|
423
|
+
const parts: string[] = [];
|
|
424
|
+
if (raw.system !== undefined) parts.push(typeof raw.system === "string" ? raw.system : JSON.stringify(raw.system));
|
|
425
|
+
if (raw.messages !== undefined) {
|
|
426
|
+
const projected = projectClaudeRequest(raw, thinking);
|
|
427
|
+
parts.push(JSON.stringify(sanitizedMessages(projected.messages)));
|
|
428
|
+
}
|
|
429
|
+
if (raw.tools !== undefined) parts.push(JSON.stringify(raw.tools));
|
|
430
|
+
return Math.max(1, estimateTokens(parts.join("\n"), modelId) + attachmentTokens);
|
|
431
|
+
}
|
package/src/server/relay.ts
CHANGED
|
@@ -170,7 +170,7 @@ export function failedTailFrame(encoder: TextEncoder, err: unknown, maskCredenti
|
|
|
170
170
|
* its fatal set (codex-rs/codex-api/src/sse/responses.rs:423-450), so stamping
|
|
171
171
|
* `upstream_server_error` on a refusal delivered it as a retryable disconnect
|
|
172
172
|
* and drove the reconnect loop in #5176. Without a refusal code the terminal
|
|
173
|
-
* carries only recognized rate-limit/overload classes; unknown codes stay generic.
|
|
173
|
+
* carries only recognized client-refusal/rate-limit/overload classes; unknown codes stay generic.
|
|
174
174
|
*/
|
|
175
175
|
export function upstreamErrorTailFrame(
|
|
176
176
|
encoder: TextEncoder,
|
|
@@ -67,7 +67,7 @@ const KNOWN_UPSTREAM_ERROR_TYPES: ReadonlySet<string> = new Set([
|
|
|
67
67
|
"RESOURCE_EXHAUSTED", "INTERNAL", "UNAVAILABLE", "DEADLINE_EXCEEDED",
|
|
68
68
|
]);
|
|
69
69
|
|
|
70
|
-
/** Bare top-level error codes worth keeping: the classes recognizedUpstreamError maps, plus server_error. */
|
|
70
|
+
/** Bare top-level error codes worth keeping: the rate-limit and overload classes recognizedUpstreamError maps, plus server_error. */
|
|
71
71
|
const KNOWN_BARE_ERROR_CODES: ReadonlySet<string> = new Set([
|
|
72
72
|
"rate_limit_exceeded", "rate_limit_error", "server_is_overloaded", "overloaded_error", "server_error",
|
|
73
73
|
]);
|
|
@@ -96,6 +96,8 @@ import type {
|
|
|
96
96
|
|
|
97
97
|
const CACHE_DIAGNOSTIC_HOOK = Symbol.for("opencodex.cache-diagnostic.v1");
|
|
98
98
|
interface CacheDiagnosticHooks {
|
|
99
|
+
/** Whether inbound bodies are actually recorded; the hook is registered at import either way. */
|
|
100
|
+
enabled(): boolean;
|
|
99
101
|
observeInbound(body: unknown, headers: Headers, source: PromptCacheKeySource): CacheDiagnosticDraft;
|
|
100
102
|
rebind(body: unknown, draft: CacheDiagnosticDraft | undefined): void;
|
|
101
103
|
finalize(facts: CacheDiagnosticFinalFacts): void;
|
|
@@ -104,6 +106,11 @@ function cacheDiagnosticHooks(): CacheDiagnosticHooks | undefined {
|
|
|
104
106
|
return (globalThis as Record<symbol, CacheDiagnosticHooks | undefined>)[CACHE_DIAGNOSTIC_HOOK];
|
|
105
107
|
}
|
|
106
108
|
|
|
109
|
+
/** Whether the cache diagnostic recorder will record inbound bodies (`OPENCODEX_CACHE_DEBUG=1`). */
|
|
110
|
+
export function cacheDiagnosticsObserving(): boolean {
|
|
111
|
+
return cacheDiagnosticHooks()?.enabled() === true;
|
|
112
|
+
}
|
|
113
|
+
|
|
107
114
|
export interface RequestLogContext extends UpstreamErrorDiagnostics {
|
|
108
115
|
model: string;
|
|
109
116
|
provider: string;
|
|
@@ -1426,7 +1433,12 @@ export function addFinalRequestLog(
|
|
|
1426
1433
|
...(attempt.deliverySummary ? { deliverySummary: { ...attempt.deliverySummary } } : {}),
|
|
1427
1434
|
}));
|
|
1428
1435
|
const isCombo = logCtx.comboId !== undefined && (attempts?.length ?? 0) > 0;
|
|
1429
|
-
|
|
1436
|
+
// A key or account rotation splits the attempt and clears `logCtx.usage`, so a multi-attempt
|
|
1437
|
+
// request totals from its attempts, combo or not. One attempt keeps the request-level usage
|
|
1438
|
+
// (and its input estimate); a locally answered row sent nothing and keeps its own.
|
|
1439
|
+
const aggregate = isCombo || ((attempts?.length ?? 0) > 1 && logCtx.localTerminalReason === undefined)
|
|
1440
|
+
? aggregateAttemptUsage(attempts ?? [])
|
|
1441
|
+
: null;
|
|
1430
1442
|
const loggedUsage = aggregate?.usage ?? existing.usage;
|
|
1431
1443
|
const usageStatus = aggregate?.status ?? existing.status;
|
|
1432
1444
|
const totalTokens = aggregate?.totalTokens ?? existing.totalTokens;
|
|
@@ -234,7 +234,9 @@ export interface ApplyAccountChangeConversationStateScrubArgs {
|
|
|
234
234
|
servingAccountId: string;
|
|
235
235
|
/** Account this request body was prepared for, when this is an in-request move. */
|
|
236
236
|
priorAccountId?: string | null;
|
|
237
|
-
parsed?: Pick<OcxParsedRequest, "previousResponseId" | "_stripReasoningEncryptedContent">;
|
|
237
|
+
parsed?: Pick<OcxParsedRequest, "previousResponseId" | "_stripReasoningEncryptedContent" | "_stripNativeCompactionEncryptedContent" | "_dropForeignReasoningItemIds">;
|
|
238
|
+
/** Trusted canonical-native caller policy; does not make continuation or file ids portable. */
|
|
239
|
+
preserveReasoningEncryptedContent?: boolean;
|
|
238
240
|
logCtx?: RequestLogContext;
|
|
239
241
|
}
|
|
240
242
|
|
|
@@ -252,12 +254,16 @@ export function applyAccountChangeConversationStateScrub(
|
|
|
252
254
|
const accountChanged = (issuer != null && issuer !== servingAccountId)
|
|
253
255
|
|| (priorAccountId != null && priorAccountId !== servingAccountId);
|
|
254
256
|
if (!accountChanged) return false;
|
|
257
|
+
// The issuer record can outlive the serving-route cache. Even a retained ciphertext cannot
|
|
258
|
+
// make its reasoning item id resolvable in the new account's store.
|
|
259
|
+
if (parsed) parsed._dropForeignReasoningItemIds = true;
|
|
255
260
|
if (canPortConversationState(collectConversationStateCarriers(body)).portable) return false;
|
|
256
261
|
const scrubbed = scrubUnportableConversationStateInPlace(body);
|
|
257
262
|
if (!scrubbed) return false;
|
|
258
263
|
if (parsed) {
|
|
259
264
|
delete parsed.previousResponseId;
|
|
260
|
-
parsed.
|
|
265
|
+
parsed._stripNativeCompactionEncryptedContent = true;
|
|
266
|
+
if (args.preserveReasoningEncryptedContent !== true) parsed._stripReasoningEncryptedContent = true;
|
|
261
267
|
}
|
|
262
268
|
if (logCtx && logCtx.conversationStateScrub !== "account-change") {
|
|
263
269
|
console.warn(
|
|
@@ -30,6 +30,8 @@ import {
|
|
|
30
30
|
isNonReplayableResponse,
|
|
31
31
|
prepareSameTarget429Wait,
|
|
32
32
|
} from "../../lib/upstream-retry";
|
|
33
|
+
import { unwrapUpstreamRetryEvidenceError } from "../../codex/auth-context";
|
|
34
|
+
import { AdmissionModelDeniedError, MODEL_NOT_ALLOWED_FOR_KEY } from "../admission-model-scope";
|
|
33
35
|
import { redactSecretString } from "../../lib/redact";
|
|
34
36
|
import { resolveWireProtocolOverride } from "../adapter-resolve";
|
|
35
37
|
import { bindRouteReasoningReplayScope } from "./core-replay";
|
|
@@ -146,6 +148,22 @@ export function createAdapterContinuations(
|
|
|
146
148
|
sendBudgetExhausted,
|
|
147
149
|
} = sendBudgetState;
|
|
148
150
|
|
|
151
|
+
const continuationFailureEvent = (error: unknown): Extract<AdapterEvent, { type: "error" }> => {
|
|
152
|
+
if (options.abortSignal?.aborted || upstream.signal.aborted) {
|
|
153
|
+
return { type: "error", message: "client closed request during terminal continuation", status: 499 };
|
|
154
|
+
}
|
|
155
|
+
const refusal = unwrapUpstreamRetryEvidenceError(error);
|
|
156
|
+
if (refusal instanceof AdmissionModelDeniedError) {
|
|
157
|
+
return {
|
|
158
|
+
type: "error", message: refusal.message, status: 403,
|
|
159
|
+
errorType: MODEL_NOT_ALLOWED_FOR_KEY, code: MODEL_NOT_ALLOWED_FOR_KEY, retryable: false,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
type: "error",
|
|
164
|
+
message: `Provider continuation failed: ${redactSecretString(error instanceof Error ? error.message : String(error))}`,
|
|
165
|
+
};
|
|
166
|
+
};
|
|
149
167
|
|
|
150
168
|
// One bounded internal continuation re-ask for clean end_turn turns that announced an edit
|
|
151
169
|
// without emitting a tool call. Anthropic gets this by default; openai-chat providers opt in
|
|
@@ -304,12 +322,7 @@ export function createAdapterContinuations(
|
|
|
304
322
|
kiroRefusalPendingReplay = undefined;
|
|
305
323
|
kiroReplaySetupFailed = true;
|
|
306
324
|
} else {
|
|
307
|
-
|
|
308
|
-
yield { type: "error", message: "client closed request during terminal continuation", status: 499 };
|
|
309
|
-
} else {
|
|
310
|
-
yield { type: "error", message: `Provider continuation failed: ${redactSecretString(error instanceof Error ? error.message : String(error))}` };
|
|
311
|
-
}
|
|
312
|
-
return;
|
|
325
|
+
yield continuationFailureEvent(error); return;
|
|
313
326
|
}
|
|
314
327
|
}
|
|
315
328
|
|
|
@@ -360,12 +373,7 @@ export function createAdapterContinuations(
|
|
|
360
373
|
try {
|
|
361
374
|
response = await fetchContinuation("rate-limit-429");
|
|
362
375
|
} catch (error) {
|
|
363
|
-
|
|
364
|
-
yield { type: "error", message: "client closed request during terminal continuation", status: 499 };
|
|
365
|
-
} else {
|
|
366
|
-
yield { type: "error", message: `Provider continuation failed: ${redactSecretString(error instanceof Error ? error.message : String(error))}` };
|
|
367
|
-
}
|
|
368
|
-
return;
|
|
376
|
+
yield continuationFailureEvent(error); return;
|
|
369
377
|
}
|
|
370
378
|
}
|
|
371
379
|
|
|
@@ -381,9 +389,7 @@ export function createAdapterContinuations(
|
|
|
381
389
|
route.provider = rotated;
|
|
382
390
|
try { await transportState.resolveCopilotSelection(nextParsed); }
|
|
383
391
|
catch (error) {
|
|
384
|
-
yield
|
|
385
|
-
...(options.abortSignal?.aborted || upstream.signal.aborted ? { status: 499 } : {}) };
|
|
386
|
-
return;
|
|
392
|
+
yield continuationFailureEvent(error); return;
|
|
387
393
|
}
|
|
388
394
|
invalidateSameTargetRequest();
|
|
389
395
|
transportState.activeAdapter = resolveSelectionAdapter(
|
|
@@ -434,9 +440,20 @@ export function createAdapterContinuations(
|
|
|
434
440
|
resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy),
|
|
435
441
|
config.cacheRetention,
|
|
436
442
|
);
|
|
443
|
+
// Rebind both the continuation clone and the outer request that persists the response.
|
|
444
|
+
for (const requestParsed of [nextParsed, parsed]) {
|
|
445
|
+
bindRouteReasoningReplayScope({
|
|
446
|
+
parsed: requestParsed,
|
|
447
|
+
providerName: route.providerName,
|
|
448
|
+
provider: route.provider,
|
|
449
|
+
adapterName: transportState.activeAdapter.name,
|
|
450
|
+
oauthCredentialSnapshot: transportState.replayOAuthCredentialSnapshot,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
437
453
|
sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, transportState.activeAdapter.name, logCtx.accountLogLabel);
|
|
438
454
|
recordAttemptCredentialSource(logCtx.activeAttempt, route.providerName, route.provider, transportState.activeAdapter.name);
|
|
439
|
-
nextContinuationRecoveryKind = response.status === 401 ? "oauth-401"
|
|
455
|
+
nextContinuationRecoveryKind = response.status === 401 ? "oauth-401"
|
|
456
|
+
: response.status === 403 ? "oauth-account-403" : "anthropic-oauth-429";
|
|
440
457
|
continue;
|
|
441
458
|
} catch {
|
|
442
459
|
// fall through to emit continuation error below
|
|
@@ -630,13 +647,17 @@ export function createAdapterContinuations(
|
|
|
630
647
|
nextContinuationRecoveryKind = "oauth-account-429";
|
|
631
648
|
continue;
|
|
632
649
|
}
|
|
633
|
-
} catch {
|
|
650
|
+
} catch (error) {
|
|
634
651
|
// Everything in this try runs before the replay: the send happens on the next
|
|
635
652
|
// iteration, after `continue`. A throw here therefore leaves a reservation that
|
|
636
653
|
// never dispatched, and holding it would refuse a later recovery in this same
|
|
637
654
|
// request for a send that never left the process.
|
|
638
655
|
hop.permit?.release();
|
|
639
|
-
|
|
656
|
+
if (unwrapUpstreamRetryEvidenceError(error) instanceof AdmissionModelDeniedError) {
|
|
657
|
+
yield continuationFailureEvent(error);
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
// Other negotiation failures preserve the original provider refusal.
|
|
640
661
|
}
|
|
641
662
|
}
|
|
642
663
|
}
|
|
@@ -717,7 +738,7 @@ export function createAdapterContinuations(
|
|
|
717
738
|
|
|
718
739
|
const fetchGuardedEmptyCompletionRetry = (): AsyncIterable<AdapterEvent> => {
|
|
719
740
|
const retryEvents = fetchTerminalGuardContinuation(parsed, "empty-completion", true);
|
|
720
|
-
|
|
741
|
+
const terminalGuardedRetry = terminalGuardEnabled
|
|
721
742
|
? guardTerminalEventStream({
|
|
722
743
|
parsed,
|
|
723
744
|
firstEvents: retryEvents,
|
|
@@ -726,6 +747,14 @@ export function createAdapterContinuations(
|
|
|
726
747
|
continuation: next => fetchTerminalGuardContinuation(next, undefined, !parsed.stream),
|
|
727
748
|
})
|
|
728
749
|
: retryEvents;
|
|
750
|
+
return parsed._advisorGuard
|
|
751
|
+
? parsed._advisorGuard({
|
|
752
|
+
parsed,
|
|
753
|
+
translatorBudget,
|
|
754
|
+
firstEvents: terminalGuardedRetry,
|
|
755
|
+
continuation: fetchTerminalGuardContinuation,
|
|
756
|
+
})
|
|
757
|
+
: terminalGuardedRetry;
|
|
729
758
|
};
|
|
730
759
|
|
|
731
760
|
return {
|