@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,368 @@
|
|
|
1
|
+
import { assistantReplayContent } from "../../responses/assistant-replay";
|
|
2
|
+
/**
|
|
3
|
+
* Core-owned registration slot for the optional advisor subsystem (src/advisor).
|
|
4
|
+
*
|
|
5
|
+
* This file is the ONLY thing the core Responses path knows about the advisor. It holds the
|
|
6
|
+
* structural plan interface and the event-stream guard — pure protocol machinery over
|
|
7
|
+
* src/types — and imports nothing from src/advisor at runtime. The optional subsystem registers
|
|
8
|
+
* a factory through advisor-plan-slot at host activation; an advisor-disabled install therefore executes no advisor
|
|
9
|
+
* code and imports no advisor module (same seam discipline as src/lab).
|
|
10
|
+
*
|
|
11
|
+
* Guard semantics (mirrors guardTerminalEventStream):
|
|
12
|
+
* - synthetic `advisor` tool-call events are HELD — the Codex client never sees the tool, the
|
|
13
|
+
* call, or its arguments;
|
|
14
|
+
* - on a clean `done`, each held call is consulted through the plan, the advice is appended as a
|
|
15
|
+
* paired assistant-toolCall + toolResult message pair, and the worker is re-dispatched via the
|
|
16
|
+
* SAME continuation machinery the terminal guard uses;
|
|
17
|
+
* - real (non-advisor) tool calls end interception for the leg: the turn belongs to the client;
|
|
18
|
+
* - usage from intercepted legs is merged into the final terminal event so worker accounting
|
|
19
|
+
* stays complete; the advisor's own usage is a separate loopback request and never merges here;
|
|
20
|
+
* - consultations and worker continuations have separate hard per-request bounds. Exhaustion
|
|
21
|
+
* removes the tool; one final limit-result continuation is allowed, then a typed error ends it.
|
|
22
|
+
*/
|
|
23
|
+
import type {
|
|
24
|
+
AdapterEvent,
|
|
25
|
+
OcxAssistantContentPart,
|
|
26
|
+
OcxAssistantMessage,
|
|
27
|
+
OcxMessage,
|
|
28
|
+
OcxParsedRequest,
|
|
29
|
+
OcxToolResultMessage,
|
|
30
|
+
OcxUsage,
|
|
31
|
+
} from "../../types";
|
|
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";
|
|
35
|
+
|
|
36
|
+
export const ADVISOR_TOOL_NAME = "advisor";
|
|
37
|
+
|
|
38
|
+
/** Hard bound on advisor consultations per worker request (recursion guard). */
|
|
39
|
+
export const MAX_ADVISOR_CONSULTATIONS_PER_REQUEST = 3;
|
|
40
|
+
/** At most one final worker continuation after consultation exhaustion. */
|
|
41
|
+
export const MAX_ADVISOR_CONTINUATIONS_PER_REQUEST = MAX_ADVISOR_CONSULTATIONS_PER_REQUEST + 1;
|
|
42
|
+
|
|
43
|
+
/** Per-leg retention caps for rebuilding the assistant message (see terminal-guard's bounded retention). */
|
|
44
|
+
const MAX_LEG_TEXT_CHARS = 16 * 1_024;
|
|
45
|
+
const MAX_LEG_THINKING_CHARS = 16 * 1_024;
|
|
46
|
+
|
|
47
|
+
/** Outcome of one consultation, as the plan reports it to the guard. */
|
|
48
|
+
export interface AdvisorConsultOutcome {
|
|
49
|
+
ok: boolean;
|
|
50
|
+
/** Formatted, wrapper-marked advice text ready to hand to the worker. */
|
|
51
|
+
content: string;
|
|
52
|
+
isError: boolean;
|
|
53
|
+
/** The consultation was cancelled by the caller (client abort) — not a provider failure. */
|
|
54
|
+
cancelled?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Operator config blocked the consultation before any outbound call. Not a provider
|
|
57
|
+
* failure: preflight must release a claim instead of recording a cooldown.
|
|
58
|
+
*/
|
|
59
|
+
blocked?: "consent" | "settings" | "budget" | "limit";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The structural plan the optional advisor subsystem registers per request. */
|
|
63
|
+
export interface AdvisorPlan {
|
|
64
|
+
/**
|
|
65
|
+
* Run one consultation for the current conversation state. Implementations own dedup,
|
|
66
|
+
* logging, and usage accounting; the guard only consumes the outcome.
|
|
67
|
+
*/
|
|
68
|
+
consult(
|
|
69
|
+
parsed: OcxParsedRequest,
|
|
70
|
+
reason: "manual",
|
|
71
|
+
question: string | undefined,
|
|
72
|
+
): Promise<AdvisorConsultOutcome>;
|
|
73
|
+
/**
|
|
74
|
+
* Runtime-owned text for a failed or limited consultation. The guard never composes failure
|
|
75
|
+
* prose itself: the implementation neutralizes untrusted text so no guard path can emit
|
|
76
|
+
* something the provenance detector would read as genuine advice.
|
|
77
|
+
*/
|
|
78
|
+
formatUnavailable(kind: "manual" | "limit", error: string): string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
interface HeldAdvisorCall {
|
|
82
|
+
id: string;
|
|
83
|
+
name: string;
|
|
84
|
+
args: string[];
|
|
85
|
+
argumentBytes: number;
|
|
86
|
+
question?: string;
|
|
87
|
+
closed: boolean;
|
|
88
|
+
providerMetadata?: import("../../types").OcxProviderOpaqueToolCallMetadata;
|
|
89
|
+
}
|
|
90
|
+
|
|
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 } {
|
|
97
|
+
if (argsBuf.trim() === "") return {};
|
|
98
|
+
try {
|
|
99
|
+
const parsed: unknown = JSON.parse(argsBuf);
|
|
100
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
101
|
+
const question = (parsed as { question?: unknown }).question;
|
|
102
|
+
if (typeof question === "string" && question.trim() !== "") return { question: question.slice(0, 2_000) };
|
|
103
|
+
}
|
|
104
|
+
} catch { /* malformed args → no focus question */ }
|
|
105
|
+
return {};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function assistantMessageFromLeg(
|
|
109
|
+
events: readonly AdapterEvent[],
|
|
110
|
+
held: readonly HeldAdvisorCall[],
|
|
111
|
+
timestamp: number,
|
|
112
|
+
retention: AdvisorRetention,
|
|
113
|
+
): OcxAssistantMessage | undefined {
|
|
114
|
+
const content = assistantReplayContent(events);
|
|
115
|
+
for (const call of held) {
|
|
116
|
+
if (!call.closed) continue;
|
|
117
|
+
const args = parseAdvisorArgs(call, retention);
|
|
118
|
+
call.question = args.question;
|
|
119
|
+
content.push({
|
|
120
|
+
type: "toolCall",
|
|
121
|
+
id: call.id,
|
|
122
|
+
name: call.name,
|
|
123
|
+
arguments: args,
|
|
124
|
+
...(call.providerMetadata ? { providerMetadata: call.providerMetadata } : {}),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
if (content.length === 0) return undefined;
|
|
128
|
+
return { role: "assistant", content, timestamp };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function advisorToolResult(
|
|
132
|
+
call: HeldAdvisorCall,
|
|
133
|
+
outcome: AdvisorConsultOutcome,
|
|
134
|
+
timestamp: number,
|
|
135
|
+
): OcxToolResultMessage {
|
|
136
|
+
return {
|
|
137
|
+
role: "toolResult",
|
|
138
|
+
toolCallId: call.id,
|
|
139
|
+
toolName: ADVISOR_TOOL_NAME,
|
|
140
|
+
content: outcome.content,
|
|
141
|
+
isError: outcome.isError,
|
|
142
|
+
timestamp,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface AdvisorGuardOptions {
|
|
147
|
+
parsed: OcxParsedRequest;
|
|
148
|
+
plan: AdvisorPlan;
|
|
149
|
+
firstEvents: AsyncIterable<AdapterEvent>;
|
|
150
|
+
/** Independent guard ownership within the optional shared request budget. */
|
|
151
|
+
translatorBudget?: TranslatorBudget;
|
|
152
|
+
/** One bounded worker continuation re-dispatch (same machinery as the terminal guard). */
|
|
153
|
+
continuation: (parsed: OcxParsedRequest) => AsyncIterable<AdapterEvent> | Promise<AsyncIterable<AdapterEvent>>;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Wrap a worker event stream with advisor interception. Registered on the parsed request as
|
|
158
|
+
* `_advisorGuard` by the sidecar planner; adapter delivery applies it when present.
|
|
159
|
+
*/
|
|
160
|
+
export function createAdvisorStreamGuard(options: AdvisorGuardOptions): AsyncGenerator<AdapterEvent> {
|
|
161
|
+
const guard = createAdvisorGuard(options.plan);
|
|
162
|
+
return guard(options);
|
|
163
|
+
}
|
|
164
|
+
export function createAdvisorGuard(plan: AdvisorPlan): NonNullable<OcxParsedRequest["_advisorGuard"]> {
|
|
165
|
+
// Shared across invocations of this request's guard, including an empty-completion retry.
|
|
166
|
+
let consultations = 0;
|
|
167
|
+
let continuations = 0;
|
|
168
|
+
let finalContinuationUsed = false;
|
|
169
|
+
return async function* guardAdvisorStream(options: Omit<AdvisorGuardOptions, "plan">): AsyncGenerator<AdapterEvent> {
|
|
170
|
+
const maxConsultations = MAX_ADVISOR_CONSULTATIONS_PER_REQUEST;
|
|
171
|
+
let parsed = options.parsed;
|
|
172
|
+
let accumulatedUsage: OcxUsage | undefined;
|
|
173
|
+
let source: AsyncIterable<AdapterEvent> = options.firstEvents;
|
|
174
|
+
const retention = new AdvisorRetention(options.translatorBudget);
|
|
175
|
+
try {
|
|
176
|
+
while (true) {
|
|
177
|
+
const held: HeldAdvisorCall[] = [];
|
|
178
|
+
const legEvents: AdapterEvent[] = [];
|
|
179
|
+
let legTextChars = 0;
|
|
180
|
+
let legThinkingChars = 0;
|
|
181
|
+
let pending: HeldAdvisorCall | null = null;
|
|
182
|
+
// While a tool call is open, its delta/end events belong to that call. Advisor calls are
|
|
183
|
+
// held from the output; real calls pass through untouched.
|
|
184
|
+
let holdingCurrent = false;
|
|
185
|
+
let hasRealToolCall = false;
|
|
186
|
+
let terminalConsumed = false;
|
|
187
|
+
let terminalEvent: Extract<AdapterEvent, { type: "done" }> | undefined;
|
|
188
|
+
|
|
189
|
+
for await (const event of source) {
|
|
190
|
+
if (event.type === "tool_call_start") {
|
|
191
|
+
if (pending) { held.push(pending); pending = null; }
|
|
192
|
+
holdingCurrent = event.name === ADVISOR_TOOL_NAME;
|
|
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
|
+
}
|
|
205
|
+
} else if (event.type === "tool_call_delta") {
|
|
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
|
+
}
|
|
215
|
+
if (holdingCurrent) continue;
|
|
216
|
+
} else if (event.type === "tool_call_end") {
|
|
217
|
+
if (pending) {
|
|
218
|
+
retention.retain(event);
|
|
219
|
+
pending.closed = true;
|
|
220
|
+
if (pending.name === ADVISOR_TOOL_NAME) held.push(pending);
|
|
221
|
+
pending = null;
|
|
222
|
+
}
|
|
223
|
+
if (holdingCurrent) {
|
|
224
|
+
holdingCurrent = false;
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
} else if (event.type === "done") {
|
|
228
|
+
if (!hasRealToolCall) retention.retain(event);
|
|
229
|
+
if (pending) { held.push(pending); pending = null; }
|
|
230
|
+
terminalEvent = event;
|
|
231
|
+
terminalConsumed = true;
|
|
232
|
+
break;
|
|
233
|
+
} else if (event.type === "incomplete" || event.type === "error") {
|
|
234
|
+
// A broken leg cannot safely anchor a continuation: surface the terminal as-is.
|
|
235
|
+
if (pending) { pending = null; }
|
|
236
|
+
accumulatedUsage = retention.usage(accumulatedUsage, event.usage);
|
|
237
|
+
yield accumulatedUsage ? { ...event, usage: accumulatedUsage } : event;
|
|
238
|
+
return;
|
|
239
|
+
} else {
|
|
240
|
+
// Retain (bounded) the leg's visible content so the rebuilt assistant message is complete.
|
|
241
|
+
if (event.type === "text_delta") {
|
|
242
|
+
legTextChars += event.text.length;
|
|
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); }
|
|
245
|
+
} else if (event.type === "thinking_delta") {
|
|
246
|
+
legThinkingChars += event.thinking.length;
|
|
247
|
+
if (!hasRealToolCall && event.thinking !== "" && legThinkingChars <= MAX_LEG_THINKING_CHARS) { retention.retain(event, false, false); legEvents.push(event); }
|
|
248
|
+
} else if (
|
|
249
|
+
event.type === "thinking_signature"
|
|
250
|
+
|| event.type === "redacted_thinking"
|
|
251
|
+
) {
|
|
252
|
+
if (!hasRealToolCall) { retention.retain(event); legEvents.push(event); }
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
yield event;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const advisorCalls = held.filter(call => call.name === ADVISOR_TOOL_NAME && call.closed);
|
|
259
|
+
const shouldIntercept = terminalConsumed
|
|
260
|
+
&& terminalEvent?.stopReason !== "max_tokens"
|
|
261
|
+
&& terminalEvent?.stopReason !== "content_filter"
|
|
262
|
+
&& advisorCalls.length > 0
|
|
263
|
+
&& !hasRealToolCall;
|
|
264
|
+
|
|
265
|
+
if (!shouldIntercept) {
|
|
266
|
+
// Plain leg: surface the terminal with merged usage from any earlier intercepted legs.
|
|
267
|
+
if (terminalEvent) {
|
|
268
|
+
accumulatedUsage = retention.usage(accumulatedUsage, terminalEvent.usage);
|
|
269
|
+
yield accumulatedUsage ? { ...terminalEvent, usage: accumulatedUsage } : terminalEvent;
|
|
270
|
+
}
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Consume this leg's done — the continuation replaces it.
|
|
275
|
+
accumulatedUsage = retention.usage(accumulatedUsage, terminalEvent?.usage);
|
|
276
|
+
if (continuations >= MAX_ADVISOR_CONTINUATIONS_PER_REQUEST || finalContinuationUsed) {
|
|
277
|
+
yield {
|
|
278
|
+
type: "error",
|
|
279
|
+
status: 502,
|
|
280
|
+
errorType: "advisor_continuation_limit",
|
|
281
|
+
message: "Advisor worker continuation limit reached; no further worker calls were sent.",
|
|
282
|
+
...(accumulatedUsage ? { usage: accumulatedUsage } : {}),
|
|
283
|
+
};
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
// A repeated call after tool removal gets one last paired limit result, never a loop.
|
|
287
|
+
const isFinalContinuation = consultations >= maxConsultations;
|
|
288
|
+
// Reserve before any consultation await, so simultaneous guard entries share the bound.
|
|
289
|
+
continuations += 1;
|
|
290
|
+
finalContinuationUsed ||= isFinalContinuation;
|
|
291
|
+
const timestamp = Date.now();
|
|
292
|
+
const assistant = retention.history(retention.replayReservation(), () => assistantMessageFromLeg(legEvents, advisorCalls, timestamp, retention));
|
|
293
|
+
const messages: OcxMessage[] = [...parsed.context.messages];
|
|
294
|
+
if (assistant) messages.push(assistant);
|
|
295
|
+
|
|
296
|
+
for (const call of advisorCalls) {
|
|
297
|
+
if (consultations >= maxConsultations) {
|
|
298
|
+
// Runtime-owned limit text: not advice, and never a genuine advice wrapper.
|
|
299
|
+
const outcome = {
|
|
300
|
+
ok: false,
|
|
301
|
+
isError: true,
|
|
302
|
+
content: plan.formatUnavailable("limit", "consultation limit reached for this request"),
|
|
303
|
+
};
|
|
304
|
+
messages.push(retention.history(jsonUtf8Bytes(outcome) + jsonUtf8Bytes(call.id) + 256, () => advisorToolResult(call, outcome, timestamp))!);
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
consultations += 1;
|
|
308
|
+
// Later calls in the same leg must see what came before them: the rebuilt worker turn
|
|
309
|
+
// and every earlier advisor result in this leg's accumulated messages.
|
|
310
|
+
const consultParsed: OcxParsedRequest = {
|
|
311
|
+
...parsed,
|
|
312
|
+
context: { ...parsed.context, messages },
|
|
313
|
+
};
|
|
314
|
+
let outcome: AdvisorConsultOutcome;
|
|
315
|
+
try {
|
|
316
|
+
outcome = await plan.consult(consultParsed, "manual", call.question);
|
|
317
|
+
} catch (error) {
|
|
318
|
+
if (isTranslatorBudgetExceededError(error)) throw error;
|
|
319
|
+
// Runtime-owned failure text only: the implementation neutralizes untrusted exception
|
|
320
|
+
// text so it can never forge a genuine advice wrapper.
|
|
321
|
+
outcome = {
|
|
322
|
+
ok: false,
|
|
323
|
+
isError: true,
|
|
324
|
+
content: plan.formatUnavailable("manual", error instanceof Error ? error.message : String(error)),
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
messages.push(retention.history(jsonUtf8Bytes(outcome) + jsonUtf8Bytes(call.id) + 256, () => advisorToolResult(call, outcome, timestamp))!);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const exhausted = consultations >= maxConsultations;
|
|
331
|
+
const tools = exhausted
|
|
332
|
+
? parsed.context.tools?.filter(tool => !tool.advisor && tool.name !== ADVISOR_TOOL_NAME)
|
|
333
|
+
: parsed.context.tools;
|
|
334
|
+
const choice = parsed.options.toolChoice;
|
|
335
|
+
const nextParsed: OcxParsedRequest = {
|
|
336
|
+
...parsed,
|
|
337
|
+
context: { ...parsed.context, messages, tools },
|
|
338
|
+
options: exhausted && ((typeof choice === "object" && choice !== null && "name" in choice && choice.name === ADVISOR_TOOL_NAME)
|
|
339
|
+
|| (choice === "required" && !tools?.length))
|
|
340
|
+
? { ...parsed.options, toolChoice: "auto" }
|
|
341
|
+
: parsed.options,
|
|
342
|
+
};
|
|
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();
|
|
349
|
+
yield { type: "assistant_boundary" };
|
|
350
|
+
try {
|
|
351
|
+
source = await options.continuation(parsed);
|
|
352
|
+
} catch (error) {
|
|
353
|
+
if (isTranslatorBudgetExceededError(error)) throw error;
|
|
354
|
+
yield {
|
|
355
|
+
type: "error",
|
|
356
|
+
message: error instanceof Error ? error.message : String(error),
|
|
357
|
+
...(accumulatedUsage ? { usage: accumulatedUsage } : {}),
|
|
358
|
+
};
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
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(); }
|
|
367
|
+
};
|
|
368
|
+
}
|
|
@@ -77,13 +77,104 @@ interface RenderedTool { name: string; raw: string }
|
|
|
77
77
|
/** The declaration fence codex-rs opens: three backticks and "ts". */
|
|
78
78
|
const DECLARATION_FENCE_SHAPE: Fence = { char: "`", length: 3 };
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
const DECLARATION_PREFIX = "declare const tools: { ";
|
|
81
|
+
const PROMISE_SEPARATOR = "): Promise<";
|
|
82
|
+
const DECLARATION_SUFFIX = ">; };";
|
|
83
|
+
// Bounds keep a caller-controlled description linear work: a rendered listing is a heading, a
|
|
84
|
+
// description, and a small declaration per tool, so anything this large cannot be the codex-rs
|
|
85
|
+
// shape this parser exists to recognize.
|
|
86
|
+
const MAX_DESCRIPTION_LENGTH = 1 << 20;
|
|
87
|
+
const MAX_DECLARATION_LENGTH = 64 * 1024;
|
|
88
|
+
|
|
89
|
+
const isSpace = (ch: string | undefined): boolean => ch === undefined || /\s/.test(ch);
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The declared tool name when body is a complete declaration as render_code_mode_sample emits it,
|
|
93
|
+
* single- or multi-line, else undefined. Fixed boundaries plus one forward scan that balances the
|
|
94
|
+
* parameter list's brackets and the return type's generics: no backtracking, so a fence body full
|
|
95
|
+
* of candidate separators stays linear work.
|
|
96
|
+
*/
|
|
97
|
+
function completeDeclarationName(body: readonly string[]): string | undefined {
|
|
82
98
|
const text = body.join("\n");
|
|
83
|
-
|
|
84
|
-
if (!text.startsWith(
|
|
99
|
+
if (text.length > MAX_DECLARATION_LENGTH) return undefined;
|
|
100
|
+
if (!text.startsWith(DECLARATION_PREFIX) || !text.endsWith(DECLARATION_SUFFIX)) return undefined;
|
|
101
|
+
const signature = text.slice(DECLARATION_PREFIX.length, -DECLARATION_SUFFIX.length);
|
|
102
|
+
const openParen = signature.indexOf("(");
|
|
103
|
+
const name = openParen > 0 ? signature.slice(0, openParen) : "";
|
|
104
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(name)) return undefined;
|
|
85
105
|
// A named, typed parameter (`args` for functions, `input` for freeform) and a non-empty return type.
|
|
86
|
-
|
|
106
|
+
const rest = signature.slice(openParen + 1);
|
|
107
|
+
const typed = rest.startsWith("args: ") ? 6 : rest.startsWith("input: ") ? 7 : -1;
|
|
108
|
+
if (typed < 0 || isSpace(rest[typed])) return undefined;
|
|
109
|
+
// Quoted text and comments are opaque to delimiter matching. Each skip advances
|
|
110
|
+
// past its text once; an unterminated region cannot contain a closing declaration.
|
|
111
|
+
const isOpaqueTypeText = (at: number): boolean => {
|
|
112
|
+
const ch = rest[at];
|
|
113
|
+
return ch === '"' || ch === "'" || ch === "`"
|
|
114
|
+
|| ch === "/" && (rest[at + 1] === "/" || rest[at + 1] === "*");
|
|
115
|
+
};
|
|
116
|
+
const skipTypeText = (at: number): number | undefined => {
|
|
117
|
+
const quote = rest[at]!;
|
|
118
|
+
if (quote === "/" && rest[at + 1] === "/") {
|
|
119
|
+
let i = at + 2;
|
|
120
|
+
while (i < rest.length && rest[i] !== "\n" && rest[i] !== "\r"
|
|
121
|
+
&& rest[i] !== "\u2028" && rest[i] !== "\u2029") i += 1;
|
|
122
|
+
return i < rest.length ? i : undefined;
|
|
123
|
+
}
|
|
124
|
+
if (quote === "/" && rest[at + 1] === "*") {
|
|
125
|
+
const end = rest.indexOf("*/", at + 2);
|
|
126
|
+
return end < 0 ? undefined : end + 1;
|
|
127
|
+
}
|
|
128
|
+
let i = at + 1;
|
|
129
|
+
while (i < rest.length) {
|
|
130
|
+
const ch = rest[i]!;
|
|
131
|
+
if (ch === "\\") i += 2;
|
|
132
|
+
else if (ch === quote) return i;
|
|
133
|
+
else i += 1;
|
|
134
|
+
}
|
|
135
|
+
return undefined;
|
|
136
|
+
};
|
|
137
|
+
// The parameter list ends at the ')' that balances the opening '(': a ')' nested inside the
|
|
138
|
+
// parameter's own type (e.g. `args: (a: string) => Promise<void>`) cannot pose as the separator.
|
|
139
|
+
// The type must also say something before that ')': `args: )` or `args: /* note */)` names no type.
|
|
140
|
+
let depth = 1;
|
|
141
|
+
let close = -1;
|
|
142
|
+
let typeContent = false;
|
|
143
|
+
for (let at = typed; at < rest.length; at += 1) {
|
|
144
|
+
const ch = rest[at]!;
|
|
145
|
+
if (isOpaqueTypeText(at)) {
|
|
146
|
+
if (ch !== "/") typeContent = true; // a quoted literal is a type, a comment is not
|
|
147
|
+
const end = skipTypeText(at);
|
|
148
|
+
if (end === undefined) return undefined;
|
|
149
|
+
at = end;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (ch === "(" || ch === "[" || ch === "{") depth += 1;
|
|
153
|
+
else if (ch === ")" || ch === "]" || ch === "}") depth -= 1;
|
|
154
|
+
if (depth === 0) { close = at; break; }
|
|
155
|
+
if (!isSpace(ch)) typeContent = true;
|
|
156
|
+
}
|
|
157
|
+
if (close < 0 || !typeContent || !rest.startsWith(PROMISE_SEPARATOR, close)) return undefined;
|
|
158
|
+
const head = rest[close + PROMISE_SEPARATOR.length];
|
|
159
|
+
if (head === ">" || isSpace(head)) return undefined;
|
|
160
|
+
// The return type runs to the stripped suffix, which already supplied exactly one '>' for the
|
|
161
|
+
// Promise's '<': generic depth must stay positive to the end and land at exactly 1 — a '>' that
|
|
162
|
+
// reaches zero ('Promise<unknown>; garbage') is surplus text, a depth left above 1
|
|
163
|
+
// ('Promise<Map<string, unknown') never closed its own generic, and a '=>' arrow never counts.
|
|
164
|
+
let generic = 1;
|
|
165
|
+
for (let at = close + PROMISE_SEPARATOR.length; at < rest.length; at += 1) {
|
|
166
|
+
const ch = rest[at]!;
|
|
167
|
+
if (isOpaqueTypeText(at)) {
|
|
168
|
+
const end = skipTypeText(at);
|
|
169
|
+
if (end === undefined) return undefined;
|
|
170
|
+
at = end;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (ch === "<") generic += 1;
|
|
174
|
+
else if (ch === ">" && rest[at - 1] !== "=") generic -= 1;
|
|
175
|
+
if (generic === 0) return undefined;
|
|
176
|
+
}
|
|
177
|
+
return generic === 1 ? name : undefined;
|
|
87
178
|
}
|
|
88
179
|
|
|
89
180
|
/** codex-rs normalize_code_mode_identifier: invalid identifier characters become `_`. */
|
|
@@ -114,9 +205,21 @@ function renderedHeading(match: RegExpExecArray): RenderedTool | undefined {
|
|
|
114
205
|
* alone. Lines inside a quoted fence can neither open a section nor declare a tool.
|
|
115
206
|
*/
|
|
116
207
|
function renderedNestedTools(description: string): RenderedTool[] {
|
|
208
|
+
if (description.length > MAX_DESCRIPTION_LENGTH) return [];
|
|
117
209
|
const lines = description.split("\n");
|
|
118
210
|
const rendered: RenderedTool[] = [];
|
|
119
211
|
const pending: RenderedTool[] = [];
|
|
212
|
+
// Ascending indexes into `pending` per name, so a declaration finds its owner in one lookup
|
|
213
|
+
// instead of rescanning every pending heading.
|
|
214
|
+
const pendingByName = new Map<string, number[]>();
|
|
215
|
+
const dropPendingTo = (length: number): void => {
|
|
216
|
+
while (pending.length > length) {
|
|
217
|
+
const removed = pending.pop()!;
|
|
218
|
+
const indexes = pendingByName.get(removed.name)!;
|
|
219
|
+
indexes.pop();
|
|
220
|
+
if (indexes.length === 0) pendingByName.delete(removed.name);
|
|
221
|
+
}
|
|
222
|
+
};
|
|
120
223
|
let fence: Fence | undefined;
|
|
121
224
|
for (let i = 0; i < lines.length; i += 1) {
|
|
122
225
|
const line = lines[i]!;
|
|
@@ -127,7 +230,11 @@ function renderedNestedTools(description: string): RenderedTool[] {
|
|
|
127
230
|
const match = TOOL_HEADING.exec(line);
|
|
128
231
|
if (match) {
|
|
129
232
|
const heading = renderedHeading(match);
|
|
130
|
-
if (heading)
|
|
233
|
+
if (heading) {
|
|
234
|
+
const indexes = pendingByName.get(heading.name);
|
|
235
|
+
if (indexes) indexes.push(pending.length); else pendingByName.set(heading.name, [pending.length]);
|
|
236
|
+
pending.push(heading);
|
|
237
|
+
}
|
|
131
238
|
continue;
|
|
132
239
|
}
|
|
133
240
|
if (line === DECLARATION_LEAD && lines[i + 1] === DECLARATION_FENCE) {
|
|
@@ -137,10 +244,11 @@ function renderedNestedTools(description: string): RenderedTool[] {
|
|
|
137
244
|
const body = lines.slice(i + 2, close);
|
|
138
245
|
// The outermost pending heading with the declared name owns it, so a same-name heading quoted
|
|
139
246
|
// inside the tool's own description cannot take over the genuine section.
|
|
140
|
-
const
|
|
247
|
+
const declared = completeDeclarationName(body);
|
|
248
|
+
const owner = declared === undefined ? -1 : pendingByName.get(declared)?.[0] ?? -1;
|
|
141
249
|
if (owner >= 0) {
|
|
142
250
|
rendered.push(pending[owner]!);
|
|
143
|
-
|
|
251
|
+
dropPendingTo(owner);
|
|
144
252
|
}
|
|
145
253
|
i = close;
|
|
146
254
|
continue;
|
|
@@ -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;
|