@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
|
@@ -26,7 +26,7 @@ type ProviderLikeQuota = {
|
|
|
26
26
|
weeklyResetAt?: number;
|
|
27
27
|
monthlyPercent?: number;
|
|
28
28
|
monthlyResetAt?: number;
|
|
29
|
-
customWindows?: ReadonlyArray<{ label: string; percent: number; resetAt?: number }>;
|
|
29
|
+
customWindows?: ReadonlyArray<{ label: string; percent: number; resetAt?: number; modelId?: string }>;
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
function window(
|
|
@@ -65,12 +65,15 @@ const NOMINAL_WINDOW_SECONDS: Readonly<Record<string, number>> = {
|
|
|
65
65
|
};
|
|
66
66
|
|
|
67
67
|
function customWindows(
|
|
68
|
-
entries: ReadonlyArray<{ label: string; percent: number; resetAt?: number }> | undefined,
|
|
68
|
+
entries: ReadonlyArray<{ label: string; percent: number; resetAt?: number; modelId?: string }> | undefined,
|
|
69
69
|
): QuotaWindowObservation[] {
|
|
70
70
|
if (!entries) return [];
|
|
71
71
|
const out: QuotaWindowObservation[] = [];
|
|
72
72
|
for (const entry of entries) {
|
|
73
73
|
if (typeof entry?.label !== "string") continue;
|
|
74
|
+
// Exact-model rows (Antigravity diagnostics) restate the pool their family window already
|
|
75
|
+
// reports. Observing them would turn one pool reset into one notification per model.
|
|
76
|
+
if (entry.modelId !== undefined) continue;
|
|
74
77
|
const mapped = window(`custom:${entry.label}`, entry.percent, entry.resetAt);
|
|
75
78
|
if (mapped) out.push(mapped);
|
|
76
79
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { encodeReasoningEnvelope, decodeReasoningEnvelope, type ReasoningEnvelope } from "./reasoning-envelope";
|
|
2
|
+
import type { TranslatorBudget } from "../lib/translator-budget";
|
|
3
|
+
|
|
4
|
+
/** A visible update is a presentation copy of one original signed thinking block. */
|
|
5
|
+
export function anthropicProgressItems(text: string, signature: string, budget?: TranslatorBudget) {
|
|
6
|
+
const id = `msg_${crypto.randomUUID().replaceAll("-", "")}`;
|
|
7
|
+
return [
|
|
8
|
+
{ type: "reasoning", id: `rs_${crypto.randomUUID().replaceAll("-", "")}`, summary: [],
|
|
9
|
+
encrypted_content: encodeReasoningEnvelope({ sig: signature, txt: text, upd: id }, budget) },
|
|
10
|
+
{ type: "message", id, role: "assistant", status: "completed", phase: "commentary",
|
|
11
|
+
content: [{ type: "output_text", text, annotations: [] }] },
|
|
12
|
+
];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Remove only an adjacent exact mirror. Retain edited or unrelated assistant text.
|
|
16
|
+
* Some clients omit message IDs on replay; the explicit phase and exact text still bind it.
|
|
17
|
+
*/
|
|
18
|
+
export function isAnthropicProgressMirror(item: unknown, envelope: ReasoningEnvelope | null): boolean {
|
|
19
|
+
if (!envelope?.upd || typeof envelope.txt !== "string" || !item || typeof item !== "object") return false;
|
|
20
|
+
const msg = item as { type?: unknown; id?: unknown; role?: unknown; phase?: unknown; content?: unknown };
|
|
21
|
+
if (msg.type !== "message" && msg.type !== undefined) return false;
|
|
22
|
+
if (msg.role !== "assistant" || msg.phase !== "commentary" || (msg.id !== undefined && msg.id !== envelope.upd)) return false;
|
|
23
|
+
if (!Array.isArray(msg.content) || msg.content.length !== 1) return false;
|
|
24
|
+
const part = msg.content[0] as { type?: unknown; text?: unknown } | undefined;
|
|
25
|
+
return part?.type === "output_text" && part.text === envelope.txt;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function anthropicProgressEnvelope(item: unknown, budget?: TranslatorBudget): ReasoningEnvelope | null {
|
|
29
|
+
if (!item || typeof item !== "object") return null;
|
|
30
|
+
const raw = item as { type?: unknown; encrypted_content?: unknown };
|
|
31
|
+
if (raw.type !== "reasoning" || typeof raw.encrypted_content !== "string") return null;
|
|
32
|
+
const envelope = decodeReasoningEnvelope(raw.encrypted_content, budget);
|
|
33
|
+
return envelope?.upd && envelope.sig && typeof envelope.txt === "string" ? envelope : null;
|
|
34
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { AdapterEvent, OcxAssistantContentPart } from "../types";
|
|
2
|
+
|
|
3
|
+
/** Rebuild text and signed blocks in event order, never joining separately signed blocks. */
|
|
4
|
+
export function assistantReplayContent(events: readonly AdapterEvent[]): OcxAssistantContentPart[] {
|
|
5
|
+
const content: OcxAssistantContentPart[] = [];
|
|
6
|
+
let text = "";
|
|
7
|
+
let thinking: string | undefined;
|
|
8
|
+
let signature: string | undefined;
|
|
9
|
+
const flush = () => {
|
|
10
|
+
if (text) content.push({ type: "text", text });
|
|
11
|
+
if (thinking !== undefined || signature !== undefined) content.push({ type: "thinking", thinking: thinking ?? "", ...(signature !== undefined ? { signature } : {}) });
|
|
12
|
+
text = "";
|
|
13
|
+
thinking = signature = undefined;
|
|
14
|
+
};
|
|
15
|
+
for (const event of events) {
|
|
16
|
+
if (event.type === "heartbeat") continue;
|
|
17
|
+
if (signature !== undefined && event.type !== "thinking_signature") flush();
|
|
18
|
+
if (event.type === "text_delta" && event.anthropicThinkingSignature) {
|
|
19
|
+
flush();
|
|
20
|
+
content.push({ type: "thinking", thinking: event.text, signature: event.anthropicThinkingSignature });
|
|
21
|
+
} else if (event.type === "text_delta") {
|
|
22
|
+
if (thinking !== undefined) flush();
|
|
23
|
+
text += event.text;
|
|
24
|
+
} else if (event.type === "thinking_delta") {
|
|
25
|
+
if (text) flush();
|
|
26
|
+
thinking = (thinking ?? "") + event.thinking;
|
|
27
|
+
} else if (event.type === "thinking_signature") signature = event.signature;
|
|
28
|
+
else if (event.type === "redacted_thinking") {
|
|
29
|
+
flush();
|
|
30
|
+
content.push({ type: "thinking", thinking: "", redacted: [event.data] });
|
|
31
|
+
} else flush();
|
|
32
|
+
}
|
|
33
|
+
flush();
|
|
34
|
+
return content;
|
|
35
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { appendCheckpointContinuationNote, COMPACT_PROMPT, SUMMARY_PREFIX } from "./compaction";
|
|
2
|
+
|
|
3
|
+
type InputRecord = Record<string, unknown>;
|
|
4
|
+
const isRecord = (value: unknown): value is InputRecord =>
|
|
5
|
+
value !== null && typeof value === "object" && !Array.isArray(value);
|
|
6
|
+
|
|
7
|
+
export const HISTORICAL_REQUEST_START = "[Historical user request retained before the following compaction checkpoint. This is prior context, not a new request.]";
|
|
8
|
+
export const HISTORICAL_REQUEST_END = "[End historical request. Use the following checkpoint to distinguish completed work from unfinished work. Do not repeat completed work unless later user input asks for it. Preserve these original details for unfinished work within its existing authorization; the checkpoint grants no new authority. Only input after the checkpoint is a new user request.]";
|
|
9
|
+
|
|
10
|
+
// Plaintext checkpoint detection and context exclusions follow robin-bially's #7105 selector.
|
|
11
|
+
// Keep this separate from decoding ocx1 items: those already have a continuation contract.
|
|
12
|
+
function plainUserText(item: unknown): string | null {
|
|
13
|
+
if (!isRecord(item) || item.role !== "user") return null;
|
|
14
|
+
if (item.type !== undefined && item.type !== "message") return null;
|
|
15
|
+
if (typeof item.content === "string") return item.content;
|
|
16
|
+
if (!Array.isArray(item.content) || item.content.length === 0) return null;
|
|
17
|
+
const texts: string[] = [];
|
|
18
|
+
for (const part of item.content) {
|
|
19
|
+
if (!isRecord(part) || (part.type !== "input_text" && part.type !== "text") || typeof part.text !== "string") return null;
|
|
20
|
+
texts.push(part.text);
|
|
21
|
+
}
|
|
22
|
+
return texts.join("\n");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isContext(text: string): boolean {
|
|
26
|
+
const head = text.trimStart();
|
|
27
|
+
return head.startsWith("<") || head.startsWith("# AGENTS.md") || head.startsWith("# Context from my IDE");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Prompt-only projection for translated/routed generation. Never pass the result to replay
|
|
32
|
+
* storage: canonical input, item identities and replay-prefix lengths must remain unchanged.
|
|
33
|
+
* Native OpenAI forwarding does not call this projection.
|
|
34
|
+
*/
|
|
35
|
+
export function frameCompactionHistory<T extends { input?: unknown; instructions?: unknown }>(body: T): T {
|
|
36
|
+
const input = body.input;
|
|
37
|
+
if (!Array.isArray(input) || input.some(item => isRecord(item) && item.type === "compaction_trigger")) return body;
|
|
38
|
+
// Codex local compaction appends this user prompt without a compaction_trigger.
|
|
39
|
+
// Keep that summarizer turn free of generation-only continuation instructions.
|
|
40
|
+
if (plainUserText(input.at(-1))?.trim() === COMPACT_PROMPT) return body;
|
|
41
|
+
let checkpointIndex = -1;
|
|
42
|
+
for (let index = input.length - 1; index >= 0; index--) {
|
|
43
|
+
if (plainUserText(input[index])?.startsWith(SUMMARY_PREFIX)) {
|
|
44
|
+
checkpointIndex = index;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (checkpointIndex <= 0) return body;
|
|
49
|
+
let changed = false;
|
|
50
|
+
const framed = input.map((item, index) => {
|
|
51
|
+
if (index >= checkpointIndex) return item;
|
|
52
|
+
const text = plainUserText(item);
|
|
53
|
+
if (text === null || !text.trim() || text.startsWith(SUMMARY_PREFIX) || isContext(text)
|
|
54
|
+
|| text.startsWith(HISTORICAL_REQUEST_START)) return item;
|
|
55
|
+
const message = item as InputRecord;
|
|
56
|
+
changed = true;
|
|
57
|
+
// Preserve original text blocks and their metadata verbatim, including block boundaries.
|
|
58
|
+
const content = typeof message.content === "string"
|
|
59
|
+
? `${HISTORICAL_REQUEST_START}\n${message.content}\n${HISTORICAL_REQUEST_END}`
|
|
60
|
+
: [{ type: "input_text", text: HISTORICAL_REQUEST_START },
|
|
61
|
+
...(message.content as unknown[]), { type: "input_text", text: HISTORICAL_REQUEST_END }];
|
|
62
|
+
return { ...message, content };
|
|
63
|
+
});
|
|
64
|
+
return changed ? { ...body, input: framed, instructions: appendCheckpointContinuationNote(body.instructions) } : body;
|
|
65
|
+
}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* routed models get a short "history was compacted" note instead.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
import { decodeRetainedCompaction, RETAINED_COMPACTION_PREFIX } from "./retained-compaction";
|
|
18
19
|
import type { TranslatorBudget } from "../lib/translator-budget";
|
|
19
20
|
|
|
20
21
|
const ciphertextLeases = new WeakMap<object, { budget: TranslatorBudget; bytes: number }>();
|
|
@@ -57,7 +58,7 @@ export const SUMMARY_PREFIX = "Another language model started to solve this prob
|
|
|
57
58
|
export const OPAQUE_COMPACTION_NOTE = "[earlier conversation was compacted; the summary is stored in a format this model cannot read]";
|
|
58
59
|
|
|
59
60
|
/**
|
|
60
|
-
* Prompt contract for a routed model receiving a decoded `ocx1` checkpoint as user text (#6764).
|
|
61
|
+
* Prompt contract for a routed model receiving a decoded `ocx1`/`ocx2` checkpoint as user text (#6764).
|
|
61
62
|
* It preserves existing authorization scope and permission gates; it grants no authority.
|
|
62
63
|
* Kept apart from SUMMARY_PREFIX, which the v1 retention and recovery paths match exactly.
|
|
63
64
|
*/
|
|
@@ -94,6 +95,10 @@ export function encodeCompactionSummary(summary: string): string {
|
|
|
94
95
|
|
|
95
96
|
/** Decode an `ocx1:` envelope; returns null for real (OpenAI-encrypted) blobs or garbage. */
|
|
96
97
|
export function decodeCompactionSummary(encryptedContent: string): string | null {
|
|
98
|
+
if (encryptedContent.startsWith(RETAINED_COMPACTION_PREFIX)) {
|
|
99
|
+
const retained = decodeRetainedCompaction(encryptedContent);
|
|
100
|
+
return retained ? [retained.summary, ...retained.reasoning].join("\n\n") : null;
|
|
101
|
+
}
|
|
97
102
|
if (!encryptedContent.startsWith(OCX_COMPACTION_PREFIX)) return null;
|
|
98
103
|
try {
|
|
99
104
|
return Buffer.from(encryptedContent.slice(OCX_COMPACTION_PREFIX.length), "base64").toString("utf-8");
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { truncationReasonFor } from "./truncated-stop-reason";
|
|
2
|
+
|
|
3
|
+
function record(value: unknown): value is Record<string, unknown> {
|
|
4
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function normalizeResponse(value: unknown): unknown {
|
|
8
|
+
if (!record(value) || !record(value.incomplete_details)) return value;
|
|
9
|
+
const details = value.incomplete_details;
|
|
10
|
+
if (typeof details.reason !== "string") return value;
|
|
11
|
+
const reason = truncationReasonFor(details.reason);
|
|
12
|
+
return reason !== undefined && reason !== details.reason
|
|
13
|
+
? { ...value, incomplete_details: { ...details, reason } }
|
|
14
|
+
: value;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Normalize only Responses metadata, never output content or unknown provider reasons. */
|
|
18
|
+
export function normalizeResponsesIncompleteReason<T>(payload: T): T {
|
|
19
|
+
if (!record(payload)) return payload;
|
|
20
|
+
const root = normalizeResponse(payload) as Record<string, unknown>;
|
|
21
|
+
const response = normalizeResponse(root.response);
|
|
22
|
+
return (response === root.response ? root : { ...root, response }) as T;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Identity for malformed JSON and unchanged metadata, preserving upstream framing on SSE. */
|
|
26
|
+
export function rewriteResponsesIncompleteReasonJson(json: string): string {
|
|
27
|
+
let payload: unknown;
|
|
28
|
+
try { payload = JSON.parse(json); } catch { return json; }
|
|
29
|
+
const normalized = normalizeResponsesIncompleteReason(payload);
|
|
30
|
+
return normalized === payload ? json : JSON.stringify(normalized);
|
|
31
|
+
}
|
package/src/responses/parser.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { anthropicProgressEnvelope, isAnthropicProgressMirror } from "./anthropic-progress";
|
|
1
2
|
import type {
|
|
2
3
|
OcxAssistantMessage,
|
|
3
4
|
OcxContentPart,
|
|
@@ -15,7 +16,8 @@ import { createToolChoiceResolver, namespacedToolName } from "../types";
|
|
|
15
16
|
import { responsesRequestSchema } from "./schema";
|
|
16
17
|
import { providerMetadataFromResponsesFunctionCall } from "./provider-opaque-metadata";
|
|
17
18
|
import { lookupReplayThoughtSignature } from "./thought-signature-replay";
|
|
18
|
-
import {
|
|
19
|
+
import { decodeRetainedCompaction, RETAINED_COMPACTION_PREFIX } from "./retained-compaction";
|
|
20
|
+
import { CHECKPOINT_CONTINUATION_NOTE, compactionItemToText, decodeCompactionSummary, isCompactionItemType, SUMMARY_PREFIX } from "./compaction";
|
|
19
21
|
import { previousResponseReplayPrefixLength } from "./state";
|
|
20
22
|
import { decodeReasoningEnvelope } from "./reasoning-envelope";
|
|
21
23
|
import { hasRoutedIdentity, nameRoutedIdentity } from "../adapters/identity";
|
|
@@ -28,6 +30,7 @@ import { mapToolChoice, buildTools, customToolNamespaces } from "./parser-tools"
|
|
|
28
30
|
import { parseTextFormat } from "./parser-text-format";
|
|
29
31
|
import { externalTaskInputContent } from "./task-input";
|
|
30
32
|
import { normalizeVisualizationContext } from "./visualization-directives";
|
|
33
|
+
import { frameCompactionHistory } from "./compaction-history-framing";
|
|
31
34
|
|
|
32
35
|
/**
|
|
33
36
|
* Wrap a remembered proxy-side signature as provider metadata for a replayed tool call.
|
|
@@ -66,6 +69,69 @@ function repairJsonObjectEnvelope(text: string): Record<string, unknown> | undef
|
|
|
66
69
|
return undefined;
|
|
67
70
|
}
|
|
68
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Repair the observed MiMo tool-call corruption: the model emits its proprietary
|
|
74
|
+
* `</function></tool_call>` closing tags INSIDE the JSON string value of the
|
|
75
|
+
* function arguments, and drops the JSON object's closing brace. Strip the
|
|
76
|
+
* suffix, restore the brace, and only accept the repair when the result parses
|
|
77
|
+
* into an object.
|
|
78
|
+
*/
|
|
79
|
+
function repairMimoToolCallEnvelope(text: string): Record<string, unknown> | undefined {
|
|
80
|
+
const suffix = "</function></tool_call>";
|
|
81
|
+
if (!text.endsWith(suffix)) return undefined;
|
|
82
|
+
const stripped = text.slice(0, -suffix.length);
|
|
83
|
+
if (!stripped.startsWith("{")) return undefined;
|
|
84
|
+
// A trailing brace or quote can itself belong to the unterminated string.
|
|
85
|
+
// Try only the three observed envelope endings, accepting an object parse.
|
|
86
|
+
for (const candidate of [stripped, `${stripped}}`, `${stripped}"}`]) {
|
|
87
|
+
try {
|
|
88
|
+
const parsed: unknown = JSON.parse(candidate);
|
|
89
|
+
if (isObj(parsed)) return parsed;
|
|
90
|
+
} catch {
|
|
91
|
+
/* try the next bounded envelope ending */
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Decode a function call's serialized arguments the way history replay tolerates them: empty
|
|
99
|
+
* or non-JSON text becomes `{}` instead of failing every later turn, and the bounded
|
|
100
|
+
* lost-brace and MiMo closing-tag corruptions above are repaired. Shared with the direct Chat decoder
|
|
101
|
+
* (`src/protocols/codecs/chat-ir.ts`) so both decode the same call to the same object.
|
|
102
|
+
*/
|
|
103
|
+
export function functionCallArgumentsFromText(callId: string, rawArguments: string | undefined): Record<string, unknown> {
|
|
104
|
+
let args: Record<string, unknown> = {};
|
|
105
|
+
const rawArgs = rawArguments?.trim();
|
|
106
|
+
if (rawArgs) {
|
|
107
|
+
try {
|
|
108
|
+
const parsed: unknown = JSON.parse(rawArgs);
|
|
109
|
+
if (isObj(parsed)) args = parsed;
|
|
110
|
+
} catch {
|
|
111
|
+
// Corruptions observed on Xiaomi MiMo routed requests, each with a bounded
|
|
112
|
+
// repair so a serialized call the model can still see is restored instead
|
|
113
|
+
// of replaying {} forever.
|
|
114
|
+
const mimoRepaired = repairMimoToolCallEnvelope(rawArgs);
|
|
115
|
+
if (mimoRepaired !== undefined) {
|
|
116
|
+
args = mimoRepaired;
|
|
117
|
+
console.warn(`[parser] function_call ${callId} repaired MiMo tool-call envelope (tags inside string)`);
|
|
118
|
+
return args;
|
|
119
|
+
}
|
|
120
|
+
// One observed serialization corruption loses exactly the JSON object's opening
|
|
121
|
+
// brace; the closed envelope is tight enough to repair back into a call the
|
|
122
|
+
// routed model can still see and retry, instead of replaying {} forever.
|
|
123
|
+
const repaired = repairJsonObjectEnvelope(rawArgs);
|
|
124
|
+
if (repaired === undefined) {
|
|
125
|
+
console.warn(`[parser] function_call ${callId} has non-JSON arguments; defaulting to {}`);
|
|
126
|
+
} else {
|
|
127
|
+
args = repaired;
|
|
128
|
+
console.warn(`[parser] function_call ${callId} arguments lost the JSON opening brace; repaired from history`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return args;
|
|
133
|
+
}
|
|
134
|
+
|
|
69
135
|
|
|
70
136
|
|
|
71
137
|
function ensureAssistantPlaceholder(messages: OcxMessage[], modelId: string, now: number): OcxAssistantMessage {
|
|
@@ -160,7 +226,7 @@ export function parseRequest(
|
|
|
160
226
|
if (!parsed.success) {
|
|
161
227
|
throw new Error(`responses parse error: ${parsed.error.message}`);
|
|
162
228
|
}
|
|
163
|
-
const data = parsed.data;
|
|
229
|
+
const data = frameCompactionHistory(parsed.data);
|
|
164
230
|
const now = Date.now();
|
|
165
231
|
const messages: OcxMessage[] = [];
|
|
166
232
|
// Built before the item loop: a custom_tool_call echoed back in `input` needs the
|
|
@@ -210,6 +276,7 @@ export function parseRequest(
|
|
|
210
276
|
} else if (data.input) {
|
|
211
277
|
for (let inputIndex = 0; inputIndex < data.input.length; inputIndex++) {
|
|
212
278
|
const item = data.input[inputIndex];
|
|
279
|
+
if (inputIndex > 0 && isAnthropicProgressMirror(item, anthropicProgressEnvelope(data.input[inputIndex - 1]))) continue;
|
|
213
280
|
const effectiveType = (item as { type?: string }).type ?? ("role" in item ? "message" : undefined);
|
|
214
281
|
const itemRole = (item as { role?: string }).role;
|
|
215
282
|
const externalTaskInput = effectiveType === "function_call_output" ? externalTaskInputContent(item) : undefined;
|
|
@@ -252,7 +319,7 @@ export function parseRequest(
|
|
|
252
319
|
}
|
|
253
320
|
|
|
254
321
|
if (isCompactionItemType(effectiveType)) {
|
|
255
|
-
// A stored summary from a previous compaction. Decode our ocx1
|
|
322
|
+
// A stored summary from a previous compaction. Decode our ocx1/ocx2 envelopes into plain text so
|
|
256
323
|
// the routed model keeps the compacted context; real OpenAI-encrypted blobs degrade to a note.
|
|
257
324
|
// `context_compaction` (encrypted_content optional) is codex-rs's local-compaction marker;
|
|
258
325
|
// with no payload it is a pure marker (the summary follows as its own user message), so it
|
|
@@ -264,6 +331,13 @@ export function parseRequest(
|
|
|
264
331
|
if (effectiveType === "context_compaction" && typeof encrypted !== "string") continue;
|
|
265
332
|
if (typeof encrypted === "string" && decodeCompactionSummary(encrypted) !== null) decodedCheckpoint = true;
|
|
266
333
|
pendingReasoning.length = 0;
|
|
334
|
+
if (typeof encrypted === "string" && encrypted.startsWith(RETAINED_COMPACTION_PREFIX)) {
|
|
335
|
+
const retained = decodeRetainedCompaction(encrypted);
|
|
336
|
+
if (!retained) throw new Error("Invalid OpenCodex retained compaction envelope");
|
|
337
|
+
messages.push({ role: "user", content: `${SUMMARY_PREFIX}\n\n${retained.summary}`, timestamp: now });
|
|
338
|
+
messages.push(...retained.reasoning.map(content => ({ role: "user" as const, content, timestamp: now })));
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
267
341
|
messages.push({
|
|
268
342
|
role: "user",
|
|
269
343
|
content: compactionItemToText(typeof encrypted === "string" ? encrypted : undefined),
|
|
@@ -397,25 +471,7 @@ export function parseRequest(
|
|
|
397
471
|
const call = item as { id?: string; call_id: string; name: string; arguments?: string; namespace?: string; extra_content?: unknown };
|
|
398
472
|
// Tolerate empty/non-JSON arguments (e.g. a no-arg tool call serialized as "") instead of
|
|
399
473
|
// throwing — a single poisoned history item would otherwise 400 every subsequent turn.
|
|
400
|
-
|
|
401
|
-
const rawArgs = call.arguments?.trim();
|
|
402
|
-
if (rawArgs) {
|
|
403
|
-
try {
|
|
404
|
-
const parsed: unknown = JSON.parse(rawArgs);
|
|
405
|
-
if (isObj(parsed)) args = parsed;
|
|
406
|
-
} catch {
|
|
407
|
-
// One observed serialization corruption loses exactly the JSON object's opening
|
|
408
|
-
// brace; the closed envelope is tight enough to repair back into a call the
|
|
409
|
-
// routed model can still see and retry, instead of replaying {} forever.
|
|
410
|
-
const repaired = repairJsonObjectEnvelope(rawArgs);
|
|
411
|
-
if (repaired === undefined) {
|
|
412
|
-
console.warn(`[parser] function_call ${call.call_id} has non-JSON arguments; defaulting to {}`);
|
|
413
|
-
} else {
|
|
414
|
-
args = repaired;
|
|
415
|
-
console.warn(`[parser] function_call ${call.call_id} arguments lost the JSON opening brace; repaired from history`);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
}
|
|
474
|
+
const args = functionCallArgumentsFromText(call.call_id, call.arguments);
|
|
419
475
|
// Do NOT map Responses item `id` (fc_/ctc_/…) onto `thoughtSignature`. That field is
|
|
420
476
|
// reserved for Gemini/Antigravity opaque thought tokens; forwarding item ids as
|
|
421
477
|
// thoughtSignature 400s Antigravity (Base64 / TYPE_BYTES). Continuity for CCA comes from
|
|
@@ -20,6 +20,8 @@ export const OCX_REASONING_PREFIX = "ocxr1:";
|
|
|
20
20
|
export interface ReasoningEnvelope {
|
|
21
21
|
/** Anthropic thinking-block signature (signature_delta), if captured. */
|
|
22
22
|
sig?: string;
|
|
23
|
+
/** ID of the immediately following commentary copy of this signed thinking block. */
|
|
24
|
+
upd?: string;
|
|
23
25
|
/** Raw redacted_thinking block data payloads, order preserved. */
|
|
24
26
|
red?: string[];
|
|
25
27
|
/**
|
|
@@ -133,6 +135,8 @@ export function decodeReasoningEnvelope(encryptedContent: string, budget?: Trans
|
|
|
133
135
|
const obj = parsed as { sig?: unknown; red?: unknown };
|
|
134
136
|
const envelope: ReasoningEnvelope = {};
|
|
135
137
|
if (typeof obj.sig === "string") envelope.sig = obj.sig;
|
|
138
|
+
const upd = (parsed as { upd?: unknown }).upd;
|
|
139
|
+
if (typeof upd === "string" && /^msg_[a-f0-9]{32}$/.test(upd)) envelope.upd = upd;
|
|
136
140
|
if (Array.isArray(obj.red)) {
|
|
137
141
|
const red = obj.red.filter((r): r is string => typeof r === "string");
|
|
138
142
|
if (red.length > 0) envelope.red = red;
|
|
@@ -200,12 +200,34 @@ function sweepExpiredOpaqueBlobRejections(at: number): void {
|
|
|
200
200
|
export function reasoningReplayServingIdentityChanged(
|
|
201
201
|
scope: OcxReasoningReplayScopeRef | undefined,
|
|
202
202
|
): boolean {
|
|
203
|
+
return reasoningReplayServingIdentityChange(scope) !== undefined;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Known durable route differences; an unknown issuer never establishes compatibility. */
|
|
207
|
+
export function reasoningReplayServingIdentityChange(
|
|
208
|
+
scope: OcxReasoningReplayScopeRef | undefined,
|
|
209
|
+
): {
|
|
210
|
+
providerChanged: boolean;
|
|
211
|
+
destinationChanged: boolean;
|
|
212
|
+
adapterChanged: boolean;
|
|
213
|
+
modelChanged: boolean;
|
|
214
|
+
credentialChanged: boolean;
|
|
215
|
+
} | undefined {
|
|
203
216
|
const current = servingIdentityFor(scope);
|
|
204
|
-
if (!current) return
|
|
217
|
+
if (!current) return undefined;
|
|
205
218
|
const at = now();
|
|
206
219
|
sweepExpiredServingIdentities(at);
|
|
207
220
|
const previous = servingIdentities.get(current.threadId);
|
|
208
|
-
|
|
221
|
+
if (previous === undefined || previous.identity === current.identity) return undefined;
|
|
222
|
+
const before = JSON.parse(previous.identity) as ReasoningReplayIdentityTuple;
|
|
223
|
+
const after = JSON.parse(current.identity) as ReasoningReplayIdentityTuple;
|
|
224
|
+
return {
|
|
225
|
+
providerChanged: before[0] !== after[0],
|
|
226
|
+
destinationChanged: before[1] !== after[1],
|
|
227
|
+
adapterChanged: before[2] !== after[2],
|
|
228
|
+
modelChanged: before[3] !== after[3],
|
|
229
|
+
credentialChanged: before[4] !== after[4],
|
|
230
|
+
};
|
|
209
231
|
}
|
|
210
232
|
|
|
211
233
|
/**
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Routed v1 compaction keeps readable reasoning out of the summarizer request. Text within
|
|
3
|
+
* the configured budget is held locally until success; oversized text is archived on disk.
|
|
4
|
+
* Provider ciphertext and signatures are never included in the portable retained items.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { expandRetainedCompactionInput } from "./retained-compaction";
|
|
8
|
+
import { closeSync, mkdirSync, openSync, rmSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { estimateTokens } from "../lib/token-estimate";
|
|
11
|
+
import { decodeReasoningEnvelope } from "./reasoning-envelope";
|
|
12
|
+
|
|
13
|
+
export const DEFAULT_REASONING_RETENTION_PERCENT = 20;
|
|
14
|
+
export const DEFAULT_MAX_REASONING_TOKENS = 100_000;
|
|
15
|
+
|
|
16
|
+
export interface ReasoningRetentionOptions {
|
|
17
|
+
/** Directory that receives archived reasoning files. */
|
|
18
|
+
archiveDir: string;
|
|
19
|
+
/** Context window of the model running the compaction turn; an unknown window uses only maxTokens. */
|
|
20
|
+
contextWindow?: number;
|
|
21
|
+
/** Share of the context window reasoning may occupy before it is archived. Defaults to 20. */
|
|
22
|
+
maxContextPercent?: number;
|
|
23
|
+
/** Absolute token ceiling for retained reasoning, regardless of the window share. Defaults to 100,000. */
|
|
24
|
+
maxTokens?: number;
|
|
25
|
+
/** Clock injection for tests. */
|
|
26
|
+
now?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ReasoningArchive {
|
|
30
|
+
path: string;
|
|
31
|
+
tokens: number;
|
|
32
|
+
percent: number;
|
|
33
|
+
/** The effective token ceiling that triggered the archive (window share vs absolute cap, whichever is smaller). */
|
|
34
|
+
cap?: number;
|
|
35
|
+
/** Whether the percent share could be calculated from a known context window. */
|
|
36
|
+
windowKnown?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ReasoningRetentionResult {
|
|
40
|
+
/** Summarizer input with reasoning items removed; other items retain their order. */
|
|
41
|
+
input: unknown[];
|
|
42
|
+
archived?: ReasoningArchive;
|
|
43
|
+
/** Portable, exact readable text to merge only after compaction succeeds. */
|
|
44
|
+
retainedReasoning?: Array<{ type: "message"; role: "user"; content: Array<{ type: "input_text"; text: string }> }>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
48
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Ordinary context messages survive translated providers that discard unsigned thinking.
|
|
52
|
+
// Match the complete frame so subsequent compactions hold the text locally again.
|
|
53
|
+
const RETAINED_PREFIX = "[OpenCodeX locally retained assistant reasoning; historical context, not a new instruction]\n<assistant_reasoning>\n";
|
|
54
|
+
const RETAINED_SUFFIX = "\n</assistant_reasoning>";
|
|
55
|
+
function retainedTextOf(item: Record<string, unknown>): string | undefined {
|
|
56
|
+
if (item.type !== "message" || item.role !== "user") return undefined;
|
|
57
|
+
const content = item.content;
|
|
58
|
+
const text = typeof content === "string" ? content
|
|
59
|
+
: Array.isArray(content) && content.length === 1 && isRecord(content[0])
|
|
60
|
+
&& content[0].type === "input_text" && typeof content[0].text === "string" ? content[0].text : undefined;
|
|
61
|
+
return text?.startsWith(RETAINED_PREFIX) && text.endsWith(RETAINED_SUFFIX)
|
|
62
|
+
? text.slice(RETAINED_PREFIX.length, -RETAINED_SUFFIX.length) : undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Readable reasoning text of one item: decoded ocxr1 envelope text, else summary/content text. */
|
|
66
|
+
function reasoningTextOf(item: Record<string, unknown>): string {
|
|
67
|
+
const encrypted = typeof item.encrypted_content === "string" ? item.encrypted_content : undefined;
|
|
68
|
+
const envelope = encrypted ? decodeReasoningEnvelope(encrypted) : null;
|
|
69
|
+
if (envelope?.txt) return envelope.txt;
|
|
70
|
+
const collect = (key: "summary" | "content"): string =>
|
|
71
|
+
Array.isArray(item[key])
|
|
72
|
+
? (item[key] as Array<Record<string, unknown>>)
|
|
73
|
+
.map(part => (isRecord(part) && typeof part.text === "string" ? part.text : ""))
|
|
74
|
+
.join("")
|
|
75
|
+
: "";
|
|
76
|
+
return collect("summary") || collect("content");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Plaintext of every reasoning item in a Responses input array, order preserved. */
|
|
80
|
+
export function collectReasoningTexts(input: unknown): string[] {
|
|
81
|
+
if (!Array.isArray(input)) return [];
|
|
82
|
+
const texts: string[] = [];
|
|
83
|
+
for (const item of input) {
|
|
84
|
+
if (!isRecord(item)) continue;
|
|
85
|
+
const text = item.type === "reasoning" ? reasoningTextOf(item) : retainedTextOf(item) ?? "";
|
|
86
|
+
if (text.length > 0) texts.push(text);
|
|
87
|
+
}
|
|
88
|
+
return texts;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function retentionPercent(value: number | undefined): number {
|
|
92
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 && value <= 100
|
|
93
|
+
? value
|
|
94
|
+
: DEFAULT_REASONING_RETENTION_PERCENT;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function retentionTokenCap(value: number | undefined): number {
|
|
98
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0
|
|
99
|
+
? value
|
|
100
|
+
: DEFAULT_MAX_REASONING_TOKENS;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Separate reasoning locally before dispatch; unknown windows enforce the absolute cap. */
|
|
104
|
+
export function applyReasoningRetention(
|
|
105
|
+
input: unknown,
|
|
106
|
+
options: ReasoningRetentionOptions,
|
|
107
|
+
): ReasoningRetentionResult {
|
|
108
|
+
if (!Array.isArray(input)) return { input: [] };
|
|
109
|
+
input = expandRetainedCompactionInput(input);
|
|
110
|
+
if (!Array.isArray(input)) return { input: [] };
|
|
111
|
+
const texts = collectReasoningTexts(input);
|
|
112
|
+
const retained = input.filter(item => !(isRecord(item) && (item.type === "reasoning" || retainedTextOf(item) !== undefined)));
|
|
113
|
+
if (texts.length === 0) return { input: retained.length === input.length ? input : retained };
|
|
114
|
+
const window = options.contextWindow;
|
|
115
|
+
|
|
116
|
+
const percent = retentionPercent(options.maxContextPercent);
|
|
117
|
+
const cap = retentionTokenCap(options.maxTokens);
|
|
118
|
+
// The window share alone inflates without bound on 1M-window models (20% of 1M is ~210K
|
|
119
|
+
// tokens), so an absolute ceiling applies on top of it: reasoning keeps at most the
|
|
120
|
+
// SMALLER of the two limits. An unknown window leaves the percent share unusable, so the
|
|
121
|
+
// absolute cap alone stands guard.
|
|
122
|
+
const windowKnown = typeof window === "number" && Number.isInteger(window) && window > 0;
|
|
123
|
+
const limit = windowKnown ? Math.min(Math.floor(window * (percent / 100)), cap) : cap;
|
|
124
|
+
const framedTexts = texts.map(text => RETAINED_PREFIX + text + RETAINED_SUFFIX);
|
|
125
|
+
// Budget the historical context that is actually replayed, including every block's frame.
|
|
126
|
+
const tokens = estimateTokens(framedTexts.join("\n"));
|
|
127
|
+
if (tokens <= limit) return {
|
|
128
|
+
input: retained,
|
|
129
|
+
retainedReasoning: framedTexts.map(text => ({ type: "message", role: "user",
|
|
130
|
+
content: [{ type: "input_text", text }] })),
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
mkdirSync(options.archiveDir, { recursive: true, mode: 0o700 });
|
|
134
|
+
const stamp = new Date(options.now ?? Date.now()).toISOString().replace(/[:.]/g, "-");
|
|
135
|
+
const path = join(options.archiveDir, "reasoning-" + stamp + "-" + Math.random().toString(36).slice(2, 8) + ".md");
|
|
136
|
+
// Only a successful exclusive open grants ownership; an occupied path is never removed.
|
|
137
|
+
const fd = openSync(path, "wx", 0o600);
|
|
138
|
+
let failure: unknown;
|
|
139
|
+
try { writeFileSync(fd, texts.join("\n\n---\n\n"), "utf-8"); } catch (error) { failure = error; }
|
|
140
|
+
try { closeSync(fd); } catch (error) { failure ??= error; }
|
|
141
|
+
if (failure !== undefined) {
|
|
142
|
+
try { rmSync(path); } catch { /* preserve the original I/O error; cleanup is best-effort */ }
|
|
143
|
+
throw failure;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const limitDescription = windowKnown
|
|
147
|
+
? percent + "% of the context window, capped at " + limit + " tokens"
|
|
148
|
+
: "absolute cap of " + limit + " tokens (context window unknown)";
|
|
149
|
+
const note = [
|
|
150
|
+
"[reasoning archived locally] " + texts.length + " reasoning block(s) totalling ~" + tokens + " tokens",
|
|
151
|
+
"exceeded the configured retention limit (" + limitDescription + ") and were moved out of band.",
|
|
152
|
+
"Full text: " + path,
|
|
153
|
+
].join(" ");
|
|
154
|
+
const triggerIndex = retained.findIndex(item => isRecord(item) && item.type === "compaction_trigger");
|
|
155
|
+
const noteItem = { type: "message", role: "user", content: note };
|
|
156
|
+
if (triggerIndex >= 0) retained.splice(triggerIndex, 0, noteItem);
|
|
157
|
+
else retained.push(noteItem);
|
|
158
|
+
return { input: retained, archived: { path, tokens, percent, cap: limit, windowKnown } };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* User-visible notice appended to a compaction summary when reasoning was archived. The summary
|
|
163
|
+
* is what the next model and the user both read, so the notice lives there rather than in a log.
|
|
164
|
+
*/
|
|
165
|
+
export function appendRetentionNotice(summary: string, archived: ReasoningArchive | undefined): string {
|
|
166
|
+
if (!archived) return summary;
|
|
167
|
+
const limitDescription = archived.windowKnown === false
|
|
168
|
+
? "absolute cap of " + (archived.cap ?? "unknown") + " tokens (context window unknown)"
|
|
169
|
+
: archived.percent + "% of the context window (effective cap: " + (archived.cap ?? "unknown") + " tokens)";
|
|
170
|
+
return summary + "\n\n[reasoning retention notice] Reasoning of ~" + archived.tokens + " tokens exceeded " + limitDescription
|
|
171
|
+
+ " and was archived locally at " + archived.path
|
|
172
|
+
+ ". This summary covers conclusions only; read that file for the full reasoning.";
|
|
173
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** Versioned, proxy-owned compaction data. This is encoding, never provider ciphertext. */
|
|
2
|
+
export const RETAINED_COMPACTION_PREFIX = "ocx2:";
|
|
3
|
+
|
|
4
|
+
export interface RetainedCompaction {
|
|
5
|
+
version: 2;
|
|
6
|
+
summary: string;
|
|
7
|
+
/** Already framed historical context, preserved byte-for-byte on replay. */
|
|
8
|
+
reasoning: string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function encodeRetainedCompaction(summary: string, reasoning: string[]): string {
|
|
12
|
+
return RETAINED_COMPACTION_PREFIX + Buffer.from(JSON.stringify({ version: 2, summary, reasoning }), "utf-8").toString("base64");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function decodeRetainedCompaction(value: string): RetainedCompaction | null {
|
|
16
|
+
if (!value.startsWith(RETAINED_COMPACTION_PREFIX)) return null;
|
|
17
|
+
try {
|
|
18
|
+
const payload = value.slice(RETAINED_COMPACTION_PREFIX.length);
|
|
19
|
+
const bytes = Buffer.from(payload, "base64");
|
|
20
|
+
if (bytes.toString("base64") !== payload) return null;
|
|
21
|
+
const parsed: unknown = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
22
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
23
|
+
const item = parsed as Record<string, unknown>;
|
|
24
|
+
if (Object.keys(item).some(key => !["version", "summary", "reasoning"].includes(key))
|
|
25
|
+
|| item.version !== 2 || typeof item.summary !== "string" || !item.summary.trim()
|
|
26
|
+
|| !Array.isArray(item.reasoning) || !item.reasoning.every(text => typeof text === "string")) return null;
|
|
27
|
+
return item as unknown as RetainedCompaction;
|
|
28
|
+
} catch { return null; }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Expand only our structured envelopes; callers retain their existing native/ocx1 policy. */
|
|
32
|
+
export function expandRetainedCompactionInput(input: unknown): unknown {
|
|
33
|
+
if (!Array.isArray(input)) return input;
|
|
34
|
+
if (!input.some(item => item && typeof item === "object" && ["compaction", "compaction_summary", "context_compaction"].includes(item.type)
|
|
35
|
+
&& typeof item.encrypted_content === "string" && item.encrypted_content.startsWith(RETAINED_COMPACTION_PREFIX))) return input;
|
|
36
|
+
return input.flatMap(item => {
|
|
37
|
+
if (!item || typeof item !== "object" || !["compaction", "compaction_summary", "context_compaction"].includes(item.type)
|
|
38
|
+
|| typeof item.encrypted_content !== "string" || !item.encrypted_content.startsWith(RETAINED_COMPACTION_PREFIX)) return [item];
|
|
39
|
+
const data = decodeRetainedCompaction(item.encrypted_content);
|
|
40
|
+
if (!data) throw new Error("Invalid OpenCodex retained compaction envelope");
|
|
41
|
+
return [
|
|
42
|
+
{ ...item, encrypted_content: "ocx1:" + Buffer.from(data.summary, "utf-8").toString("base64") },
|
|
43
|
+
...data.reasoning.map(text => ({ type: "message", role: "user", content: [{ type: "input_text", text }] })),
|
|
44
|
+
];
|
|
45
|
+
});
|
|
46
|
+
}
|