@bitkyc08/opencodex 2.80.0 → 2.82.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 +3 -3
- package/bin/ocx.mjs +166 -73
- package/gui/dist/assets/App-Ds_qL7o8.js +54 -0
- package/gui/dist/assets/Tray-BqHF5Trd.js +1 -0
- package/gui/dist/assets/{index-Brk6F3fn.css → index-BAbcj51J.css} +1 -1
- package/gui/dist/assets/index-D3FBCNiP.js +94 -0
- package/gui/dist/assets/quota-summary-DTBC8n7-.js +1 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/claude-green.svg +1 -0
- package/package.json +3 -2
- package/src/adapters/anthropic-model-contract.ts +8 -2
- package/src/adapters/anthropic.ts +8 -3
- package/src/adapters/codebuddy/mcp-server.ts +2 -25
- package/src/adapters/codebuddy/tool-bridge.ts +1 -7
- package/src/adapters/coding-agent/mcp-server.ts +33 -0
- package/src/adapters/coding-agent/protocol.ts +122 -17
- package/src/adapters/coding-agent/tool-catalog.ts +75 -0
- package/src/adapters/coding-agent/turn.ts +101 -44
- package/src/adapters/cursor/catalog.ts +8 -0
- package/src/adapters/cursor/effort-map.ts +2 -0
- package/src/adapters/devin/live-models.ts +2 -0
- package/src/adapters/kiro-retry.ts +9 -4
- package/src/adapters/openai-chat/messages.ts +7 -6
- package/src/adapters/openai-responses/compaction-search-history.ts +109 -0
- package/src/adapters/openai-responses/passthrough.ts +9 -4
- package/src/adapters/openai-responses/tool-output-recovery.ts +20 -0
- package/src/adapters/physical-send.ts +8 -5
- package/src/adapters/qoder/adapter.ts +41 -6
- package/src/adapters/qoder/scaffold-guard.ts +2 -3
- package/src/adapters/zed.ts +16 -1
- package/src/claude/agents-inject.ts +14 -3
- package/src/claude/context-windows.ts +19 -17
- package/src/claude/desktop-3p.ts +88 -12
- package/src/claude/desktop-profile.ts +2 -1
- package/src/claude/gateway-cache.ts +18 -1
- package/src/claude/inbound-cache-stabilize.ts +4 -9
- package/src/claude/inbound.ts +78 -3
- package/src/claude/intercept/cli-catalog.ts +3 -0
- package/src/claude/intercept/model-bindings.ts +1 -0
- package/src/claude/intercept/picker-ca-persistence.ts +7 -6
- package/src/claude/intercept/picker-listener.ts +71 -4
- package/src/claude/intercept/picker-models.ts +14 -6
- package/src/claude/intercept/settings.ts +3 -0
- package/src/claude/long-context.ts +66 -0
- package/src/claude/model-info.ts +19 -16
- package/src/claude/outbound.ts +94 -9
- package/src/claude/subagent-model.ts +40 -11
- package/src/cli/account-anthropic-threshold.ts +14 -5
- package/src/cli/account-api.ts +3 -1
- package/src/cli/account-extended.ts +13 -9
- package/src/cli/account-policy-dto.ts +1 -1
- package/src/cli/account-policy.ts +13 -4
- package/src/cli/account.ts +3 -3
- package/src/cli/agent.ts +9 -5
- package/src/cli/aside-profile-recovery.ts +33 -0
- package/src/cli/capabilities-accounts.ts +2 -2
- package/src/cli/capabilities-agents-routing.ts +24 -0
- package/src/cli/capabilities-base.ts +5 -5
- package/src/cli/capabilities-integrations.ts +18 -2
- package/src/cli/cli-path-diagnostics.ts +132 -0
- package/src/cli/codex-shim-autorestore.ts +3 -0
- package/src/cli/desktop-runtime-guidance.ts +65 -0
- package/src/cli/dispatch.ts +15 -1
- package/src/cli/doctor.ts +16 -3
- package/src/cli/help.ts +4 -1
- package/src/cli/index.ts +25 -14
- package/src/cli/integration-aside-sync.ts +2 -1
- package/src/cli/integration-plan-dto.ts +1 -0
- package/src/cli/integration-preview.ts +8 -1
- package/src/cli/integrations.ts +48 -6
- package/src/cli/message-args.ts +34 -0
- package/src/cli/message-command.ts +43 -0
- package/src/cli/message-runtime.ts +6 -0
- package/src/cli/observe.ts +3 -1
- package/src/cli/provider.ts +29 -6
- package/src/cli/registry.ts +24 -2
- package/src/cli/resolve.ts +25 -2
- package/src/cli/root.ts +4 -2
- package/src/cli/status.ts +64 -12
- package/src/cli/update-restart-child.ts +4 -2
- package/src/cli/update-restart-home.ts +28 -10
- package/src/cli/update-restart-service-record.ts +116 -0
- package/src/cli/update-restart-supervision.ts +96 -0
- package/src/cli/update-restart.ts +59 -13
- package/src/cli/v2.ts +27 -20
- package/src/cli/version-skew-notice.ts +77 -0
- package/src/clients/config-export/commandcode.ts +131 -0
- package/src/clients/config-export/contracts.ts +12 -0
- package/src/clients/config-export.ts +64 -1
- package/src/clients/lazycodex.ts +1 -1
- package/src/clients/omo-role-models.ts +32 -7
- package/src/codex/auth-api/account-list.ts +11 -3
- package/src/codex/auth-api/account-selection.ts +107 -0
- package/src/codex/auth-api/main-account-probe.ts +25 -8
- package/src/codex/auth-api/pool-mode-gate.ts +33 -9
- package/src/codex/auth-api/pool-quota-probe.ts +15 -1
- package/src/codex/auth-api/routes.ts +11 -49
- package/src/codex/auth-context.ts +20 -1
- package/src/codex/autostart-health.ts +12 -3
- package/src/codex/catalog/auto-review.ts +13 -6
- package/src/codex/catalog/build-entries.ts +33 -7
- package/src/codex/catalog/bundled.ts +155 -26
- package/src/codex/catalog/combo-member.ts +3 -2
- package/src/codex/catalog/control-plane.ts +45 -0
- package/src/codex/catalog/effort.ts +37 -6
- package/src/codex/catalog/gather-capture.ts +10 -3
- package/src/codex/catalog/model-hints.ts +15 -8
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/operator-tiers.ts +38 -0
- package/src/codex/catalog/parsing.ts +2 -1
- package/src/codex/catalog/provider-models.ts +58 -44
- package/src/codex/catalog/retained-sync.ts +7 -5
- package/src/codex/catalog/routed-gather.ts +15 -1
- package/src/codex/catalog-auto-refresh-sources.ts +3 -4
- package/src/codex/catalog-auto-refresh.ts +19 -0
- package/src/codex/catalog.ts +1 -1
- package/src/codex/codex-home-owner.ts +3 -0
- package/src/codex/config-write-lock.ts +333 -0
- package/src/codex/control-plane-models.ts +6 -0
- package/src/codex/convergence.ts +2 -1
- package/src/codex/features.ts +257 -200
- package/src/codex/inject/config-write-section.ts +34 -0
- package/src/codex/inject/multi-agent-v2.ts +14 -6
- package/src/codex/inject/remove.ts +36 -11
- package/src/codex/inject/restore.ts +85 -20
- package/src/codex/inject-coordination.ts +15 -6
- package/src/codex/inject.ts +62 -20
- package/src/codex/journal.ts +62 -17
- package/src/codex/main-account-cache.ts +43 -0
- package/src/codex/paths.ts +2 -0
- package/src/codex/prepared-home.ts +18 -0
- package/src/codex/prompt-journal.ts +25 -12
- package/src/codex/prompt-layers.ts +49 -26
- package/src/codex/prompt-lock-claim.ts +134 -0
- package/src/codex/prompt-lock-evidence.ts +26 -0
- package/src/codex/prompt-lock-io.ts +16 -0
- package/src/codex/prompt-lock-owner.ts +130 -0
- package/src/codex/prompt-lock.ts +84 -53
- package/src/codex/routing/failure-window.ts +146 -0
- package/src/codex/routing/selection.ts +4 -1
- package/src/codex/routing.ts +13 -17
- package/src/codex/runtime.ts +106 -18
- package/src/codex/shim.ts +45 -19
- package/src/combos/jev-decision-contract.ts +25 -2
- package/src/combos/jev-service-exchange.ts +281 -0
- package/src/combos/jev.ts +34 -219
- package/src/combos/types.ts +1 -1
- package/src/config/atomic-write.ts +1 -1
- package/src/config/codex-ws-reuse-setting.ts +10 -0
- package/src/config/diagnostics.ts +27 -16
- package/src/config/live-reconcile.ts +17 -0
- package/src/config/load-degrade.ts +42 -0
- package/src/config/rebase-provenance.ts +11 -0
- package/src/config/schema/anthropic-account-pool.ts +143 -0
- package/src/config/schema/config-schema.ts +11 -1
- package/src/config/schema/leaf-validators.ts +4 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +566 -298
- package/src/generated/model-metadata.ts +7 -6
- package/src/grok/inject.ts +50 -4
- package/src/grok/sync.ts +2 -1
- package/src/images/loop.ts +30 -11
- package/src/integrations/aside-profiles.ts +10 -3
- package/src/integrations/catalog-refresh.ts +19 -3
- package/src/integrations/mutation-plan.ts +7 -0
- package/src/integrations/registry.ts +8 -0
- package/src/integrations/state.ts +9 -2
- package/src/integrations/writer.ts +17 -5
- package/src/lib/bun-path-runtime.d.mts +32 -0
- package/src/lib/bun-path-runtime.mjs +95 -0
- package/src/lib/bun-runtime-preflight.ts +140 -0
- package/src/lib/desktop-cli-handoff.d.mts +20 -0
- package/src/lib/desktop-cli-handoff.mjs +92 -0
- package/src/lib/desktop-cli-record.d.mts +29 -0
- package/src/lib/desktop-cli-record.mjs +102 -0
- package/src/lib/lab-live-route-production.ts +9 -1
- package/src/lib/request-execution-budget.ts +244 -58
- package/src/lib/spend-pool-alias-validation.ts +79 -0
- package/src/lib/spend-pool-continuity.ts +34 -0
- package/src/lib/spend-reservation-ledger.ts +412 -50
- package/src/lib/state-store-registrations.ts +20 -3
- package/src/lib/synced-state-location.ts +46 -6
- package/src/lib/test-home-guard.ts +87 -1
- package/src/lib/translator-budget.ts +17 -4
- package/src/lib/upstream-retry.ts +17 -5
- package/src/lib/windows-atomic-replace.ts +38 -16
- package/src/lib/windows-elevation.ts +6 -0
- package/src/lib/windows-secret-acl.ts +92 -25
- package/src/lib/winsw.ts +2 -3
- package/src/lib/workflow-budget.ts +53 -11
- package/src/messaging/budget.ts +45 -0
- package/src/messaging/discovery.ts +57 -0
- package/src/messaging/envelope.ts +49 -0
- package/src/messaging/input.ts +37 -0
- package/src/messaging/rpc.ts +196 -0
- package/src/messaging/send.ts +58 -0
- package/src/messaging/socket.ts +72 -0
- package/src/messaging/types.ts +29 -0
- package/src/oauth/anthropic-account-refusal.ts +90 -18
- package/src/oauth/anthropic-account-threshold.ts +8 -2
- package/src/oauth/anthropic-continuity.ts +15 -1
- package/src/oauth/anthropic-model-quota.ts +120 -48
- package/src/oauth/anthropic-model-routes.ts +11 -3
- package/src/oauth/anthropic-oauth-definitions.ts +98 -0
- package/src/oauth/anthropic-pool-config.ts +30 -0
- package/src/oauth/anthropic-rate-limit-policy.ts +64 -9
- package/src/oauth/anthropic-routing.ts +1202 -866
- package/src/oauth/anthropic-send-ownership.ts +41 -0
- package/src/oauth/anthropic.ts +4 -1
- package/src/oauth/chatgpt.ts +113 -41
- package/src/oauth/generic-account-failover.ts +3 -1
- package/src/oauth/health.ts +8 -6
- package/src/oauth/index.ts +96 -175
- package/src/oauth/kiro.ts +22 -9
- package/src/oauth/login-cli.ts +2 -0
- package/src/oauth/model-discovery-auth.ts +43 -0
- package/src/oauth/pool-kernel.ts +11 -5
- package/src/oauth/pool-settings-capability.ts +5 -3
- package/src/oauth/store-anthropic-instance.ts +87 -0
- package/src/oauth/store.ts +68 -10
- package/src/oauth/token-guardian.ts +2 -0
- package/src/oauth/types.ts +2 -0
- package/src/protocols/plan-snapshot.ts +5 -0
- package/src/protocols/settings.ts +20 -5
- package/src/providers/anthropic-instance-id.ts +47 -0
- package/src/providers/anthropic-instance.ts +48 -0
- package/src/providers/anthropic-reset-grant-ledger.ts +11 -1
- package/src/providers/azure-model-metadata.ts +115 -0
- package/src/providers/derive.ts +23 -0
- package/src/providers/kiro-models.ts +3 -0
- package/src/providers/label.ts +1 -1
- package/src/providers/model-rename-fields.ts +2 -0
- package/src/providers/quota/account-cache.ts +37 -11
- package/src/providers/quota/anthropic-account-quota.ts +93 -0
- package/src/providers/quota/anthropic-cooldown-recovery.ts +222 -111
- package/src/providers/quota/report-cache.ts +24 -1
- package/src/providers/quota/vendor-probes-key.ts +127 -17
- package/src/providers/quota/vendor-probes-oauth.ts +46 -14
- package/src/providers/quota.ts +23 -33
- package/src/providers/registry/entries-core.ts +35 -30
- package/src/providers/registry/model-ids.ts +1 -0
- package/src/providers/registry/model-seeds.ts +7 -4
- package/src/providers/registry/types.ts +3 -0
- package/src/providers/registry.ts +3 -3
- package/src/responses/reasoning-envelope.ts +29 -1
- package/src/responses/reasoning-replay-cache.ts +17 -1
- package/src/responses/spill-store.ts +20 -3
- package/src/responses/state/metrics.ts +95 -0
- package/src/responses/state/snapshot-codec.ts +6 -0
- package/src/responses/state/spill-failure.ts +12 -0
- package/src/responses/state/spill-queue.ts +13 -5
- package/src/responses/state/unforced-store-false.ts +55 -0
- package/src/responses/state.ts +68 -76
- package/src/responses/task-input.ts +17 -0
- package/src/router.ts +28 -4
- package/src/routing/compatibility/assemble.ts +1 -1
- package/src/routing/quota.ts +7 -3
- package/src/server/auth-cors.ts +5 -0
- package/src/server/background-lifecycle.ts +27 -0
- package/src/server/chat-completions.ts +7 -1
- package/src/server/chat-native.ts +64 -28
- package/src/server/claude-messages.ts +59 -8
- package/src/server/gui-static.ts +38 -4
- package/src/server/inbound-body-admission.ts +1 -0
- package/src/server/index/claude-intercept-lifecycle.ts +3 -1
- package/src/server/index/serve-options.ts +10 -2
- package/src/server/index/spend-ledger-lifecycle.ts +12 -5
- package/src/server/index/startup-warnings.ts +19 -0
- package/src/server/index.ts +5 -2
- package/src/server/inference/context.ts +1 -1
- package/src/server/lifecycle.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +123 -38
- package/src/server/management/anthropic-account-threshold.ts +12 -9
- package/src/server/management/anthropic-pool-settings.ts +72 -0
- package/src/server/management/anthropic-reset-grant-routes.ts +98 -39
- package/src/server/management/codex-agent-role-routes.ts +4 -2
- package/src/server/management/codex-prompt-routes.ts +1 -0
- package/src/server/management/codex-role-auto-assign.ts +13 -2
- package/src/server/management/config-routes.ts +85 -14
- package/src/server/management/context.ts +2 -2
- package/src/server/management/decision-routes.ts +2 -1
- package/src/server/management/integration-routes.ts +25 -2
- package/src/server/management/oauth-account-routes.ts +79 -98
- package/src/server/management/provider-instance-ownership.ts +44 -0
- package/src/server/management/provider-routes.ts +21 -28
- package/src/server/management/route-registry.ts +3 -1
- package/src/server/management/routing-profile-routes.ts +4 -4
- package/src/server/management/shared.ts +3 -3
- package/src/server/management/system-restart.ts +44 -14
- package/src/server/management/system-routes.ts +24 -2
- package/src/server/management/vision-sidecar-options.ts +9 -6
- package/src/server/management/web-search-sidecar-options.ts +51 -2
- package/src/server/management-auth.ts +6 -0
- package/src/server/messages-native-eligibility.ts +3 -2
- package/src/server/messages-native-oauth.ts +91 -45
- package/src/server/messages-native-selector.ts +39 -0
- package/src/server/messages-native.ts +133 -38
- package/src/server/messages-response-headers.ts +14 -0
- package/src/server/request-log-conversation.ts +48 -5
- package/src/server/request-log.ts +36 -2
- package/src/server/responses/adapter-continuation.ts +31 -19
- package/src/server/responses/adapter-dispatch.ts +75 -24
- package/src/server/responses/antigravity-validation-refusal.ts +20 -6
- package/src/server/responses/codex-ws-exchange.ts +17 -2
- package/src/server/responses/codex-ws-pool.ts +100 -13
- package/src/server/responses/codex-ws-wire.ts +9 -0
- package/src/server/responses/combo-requested-effort.ts +3 -1
- package/src/server/responses/compact.ts +25 -8
- package/src/server/responses/compaction-recovery.ts +1 -1
- package/src/server/responses/core-codex-account.ts +22 -1
- package/src/server/responses/core-combo-native.ts +24 -4
- package/src/server/responses/core-combo.ts +38 -25
- package/src/server/responses/core-lifetime.ts +3 -36
- package/src/server/responses/core-normalize.ts +1 -0
- package/src/server/responses/core-options.ts +8 -1
- package/src/server/responses/core-replay.ts +78 -0
- package/src/server/responses/core.ts +3 -3
- package/src/server/responses/fetch-helpers.ts +26 -3
- package/src/server/responses/passthrough-delivery.ts +30 -4
- package/src/server/responses/passthrough-dispatch.ts +39 -9
- package/src/server/responses/request-prepare.ts +41 -5
- package/src/server/responses/request-send-budget.ts +150 -50
- package/src/server/responses/request-spend.ts +325 -33
- package/src/server/responses/request-transport.ts +132 -52
- package/src/server/responses/response-effects.ts +1 -0
- package/src/server/responses/run-turn-execution.ts +13 -3
- package/src/server/responses/sidecar-execution.ts +113 -22
- package/src/server/responses/sidecar-send-budget.ts +58 -0
- package/src/server/responses/ws-upstream.ts +19 -6
- package/src/server/responses-undeclared-tool-guard.ts +5 -1
- package/src/server/startup-health-cache.ts +2 -1
- package/src/server/workflow-refusal.ts +33 -0
- package/src/service/cli.ts +20 -4
- package/src/service/desktop-command-guard.ts +32 -0
- package/src/service/desktop-startup.ts +66 -40
- package/src/service/desktop-supervision.d.mts +26 -0
- package/src/service/desktop-supervision.mjs +140 -0
- package/src/service/managing-cli.ts +1 -0
- package/src/service/orchestration.ts +46 -20
- package/src/service/ownership-mutation-lease.d.mts +1 -1
- package/src/service/ownership-mutation-lease.mjs +5 -7
- package/src/service/repair.ts +25 -9
- package/src/service/state.ts +4 -3
- package/src/service/systemd.ts +17 -8
- package/src/service/windows-ops.ts +61 -22
- package/src/sidecar/anthropic-binding.ts +79 -0
- package/src/sidecar/auth.ts +76 -2
- package/src/storage/policy-job.ts +5 -1
- package/src/storage/policy-worker.ts +3 -1
- package/src/storage/policy.ts +9 -0
- package/src/types/anthropic-account-pool.ts +33 -0
- package/src/types/config.ts +33 -28
- package/src/types/provider.ts +5 -0
- package/src/types/request.ts +5 -0
- package/src/types/tools.ts +7 -0
- package/src/types/wire.ts +1 -0
- package/src/types.ts +4 -0
- package/src/update/index.ts +57 -9
- package/src/update/restart-ownership.ts +10 -3
- package/src/update/runtime-ownership.d.mts +6 -0
- package/src/update/runtime-ownership.mjs +23 -7
- package/src/usage/cost.ts +3 -2
- package/src/usage/expected-prices.ts +66 -14
- package/src/vision/anthropic-describe.ts +16 -3
- package/src/vision/index.ts +3 -0
- package/src/vision/plan.ts +19 -6
- package/src/web-search/alpha-search.ts +7 -1
- package/src/web-search/anthropic-executor.ts +16 -4
- package/src/web-search/index.ts +7 -3
- package/src/web-search/loop.ts +32 -7
- package/src/web-search/passthrough-bridge.ts +4 -1
- package/src/web-search/sidecar-providers.ts +4 -3
- package/gui/dist/assets/App-MbDVfUeC.js +0 -54
- package/gui/dist/assets/Tray-CzkxK82A.js +0 -1
- package/gui/dist/assets/index-UJt6npXk.js +0 -94
- package/gui/dist/assets/quota-summary-DU3sD6Z-.js +0 -1
package/src/responses/state.ts
CHANGED
|
@@ -21,6 +21,9 @@ import {
|
|
|
21
21
|
import { collectReferencedSpillFileNames, snapshotReferencedSpillFileNames } from "./state/spill-inspect";
|
|
22
22
|
import { selectSnapshotEntries, snapshotPayload } from "./state/snapshot-select";
|
|
23
23
|
import { clientCarriedPrefixLength, providerIssuedIdentity } from "./state/replay-fingerprint";
|
|
24
|
+
import { hasPendingClientToolCall, allowsUnforcedStoreFalseReplay } from "./state/unforced-store-false";
|
|
25
|
+
import { computeResponseStateMetrics, type ResponseStateMetrics } from "./state/metrics";
|
|
26
|
+
export type { ResponseStateMetrics } from "./state/metrics";
|
|
24
27
|
export type { ResponseStateTempRecoveryResult, ResponseStateTempRecoveryOptions } from "./state/temp-recovery";
|
|
25
28
|
export type { ResponseSpillDirInspection } from "./spill-store";
|
|
26
29
|
export { recoverStaleResponseStateTemps, reclaimAbandonedResponseStateTemps, inspectAbandonedResponseStateTemps, sweepAbandonedResponseStateTemps } from "./state/temp-recovery";
|
|
@@ -28,7 +31,7 @@ import { recoverStaleResponseStateTemps } from "./state/temp-recovery";
|
|
|
28
31
|
export type { ResponseSpillWriteFailureCode, ResponseSpillWriteStatus, ResponseSpillWriteFailureOrigin } from "./state/spill-failure";
|
|
29
32
|
import type { ResponseSpillWriteFailureCode, ResponseSpillWriteStatus, ResponseSpillWriteFailureOrigin } from "./state/spill-failure";
|
|
30
33
|
export { responseAdmissionCountersForTests } from "./state/spill-failure";
|
|
31
|
-
import { admissionCounters, noteSpillWriteFailure, noteSpillWriteSuccess, spillCounters, spillWriteHealth } from "./state/spill-failure";
|
|
34
|
+
import { admissionCounters, noteSpillWriteFailure, noteSpillWriteSuccess, resetSpillCountersForTests, spillCounters, spillWriteHealth } from "./state/spill-failure";
|
|
32
35
|
import { loadSnapshotEntry } from "./state/snapshot-codec";
|
|
33
36
|
import { isBodyNonPersistable } from "./state/body-policy";
|
|
34
37
|
export { isBodyNonPersistable, markBodyNonPersistable } from "./state/body-policy";
|
|
@@ -118,6 +121,7 @@ export interface ResidentResponseState {
|
|
|
118
121
|
/** Index in `items` where provider output begins; see clientCarriedPrefixLength. */
|
|
119
122
|
providerOutputStart?: number;
|
|
120
123
|
providers?: OcxProviderContinuationState;
|
|
124
|
+
unforcedStoreFalse?: boolean;
|
|
121
125
|
sizeBytes: number;
|
|
122
126
|
}
|
|
123
127
|
|
|
@@ -128,6 +132,7 @@ export interface SpilledResponseState {
|
|
|
128
132
|
/** Mirrors the spilled payload boundary so a spilled entry keeps its anchor. */
|
|
129
133
|
providerOutputStart?: number;
|
|
130
134
|
providers?: OcxProviderContinuationState;
|
|
135
|
+
unforcedStoreFalse?: boolean;
|
|
131
136
|
spill: ResponseSpillRef;
|
|
132
137
|
sizeBytes: number;
|
|
133
138
|
}
|
|
@@ -315,6 +320,7 @@ function measureResidentEntry(id: string, entry: ResidentInput): ResidentRespons
|
|
|
315
320
|
items: entry.items,
|
|
316
321
|
...(entry.providerOutputStart !== undefined ? { providerOutputStart: entry.providerOutputStart } : {}),
|
|
317
322
|
...(entry.providers ? { providers: entry.providers } : {}),
|
|
323
|
+
...(entry.unforcedStoreFalse ? { unforcedStoreFalse: entry.unforcedStoreFalse } : {}),
|
|
318
324
|
});
|
|
319
325
|
return sizeBytes === null ? null : { kind: "resident", ...entry, sizeBytes };
|
|
320
326
|
}
|
|
@@ -417,7 +423,9 @@ function swapResidentForSpill(id: string, expected: ResidentResponseState, ref:
|
|
|
417
423
|
kind: "spill",
|
|
418
424
|
createdAt: expected.createdAt,
|
|
419
425
|
...(expected.clientThreadId ? { clientThreadId: expected.clientThreadId } : {}),
|
|
426
|
+
...(expected.providerOutputStart !== undefined ? { providerOutputStart: expected.providerOutputStart } : {}),
|
|
420
427
|
...(expected.providers ? { providers: expected.providers } : {}),
|
|
428
|
+
...(expected.unforcedStoreFalse ? { unforcedStoreFalse: expected.unforcedStoreFalse } : {}),
|
|
421
429
|
spill: ref,
|
|
422
430
|
};
|
|
423
431
|
const next: SpilledResponseState = { ...base, sizeBytes: stubSize(id, base) };
|
|
@@ -441,6 +449,7 @@ function replaceSpillEntryAtomically(
|
|
|
441
449
|
items: candidate.items,
|
|
442
450
|
...(candidate.providerOutputStart !== undefined ? { providerOutputStart: candidate.providerOutputStart } : {}),
|
|
443
451
|
...(candidate.providers ? { providers: candidate.providers } : {}),
|
|
452
|
+
...(candidate.unforcedStoreFalse ? { unforcedStoreFalse: candidate.unforcedStoreFalse } : {}),
|
|
444
453
|
});
|
|
445
454
|
const base: Omit<SpilledResponseState, "sizeBytes"> = {
|
|
446
455
|
kind: "spill",
|
|
@@ -448,6 +457,7 @@ function replaceSpillEntryAtomically(
|
|
|
448
457
|
...(candidate.clientThreadId ? { clientThreadId: candidate.clientThreadId } : {}),
|
|
449
458
|
...(candidate.providerOutputStart !== undefined ? { providerOutputStart: candidate.providerOutputStart } : {}),
|
|
450
459
|
...(candidate.providers ? { providers: candidate.providers } : {}),
|
|
460
|
+
...(candidate.unforcedStoreFalse ? { unforcedStoreFalse: candidate.unforcedStoreFalse } : {}),
|
|
451
461
|
spill: ref,
|
|
452
462
|
};
|
|
453
463
|
const next: SpilledResponseState = { ...base, sizeBytes: stubSize(id, base) };
|
|
@@ -533,6 +543,7 @@ function admitOversizedCandidate(
|
|
|
533
543
|
items: candidate.items,
|
|
534
544
|
...(candidate.providerOutputStart !== undefined ? { providerOutputStart: candidate.providerOutputStart } : {}),
|
|
535
545
|
...(candidate.providers ? { providers: candidate.providers } : {}),
|
|
546
|
+
...(candidate.unforcedStoreFalse ? { unforcedStoreFalse: candidate.unforcedStoreFalse } : {}),
|
|
536
547
|
});
|
|
537
548
|
// Enforce the ceiling against the REAL envelope: the spill payload adds
|
|
538
549
|
// the {version, responseId, ...} wrapper, so a candidate within the
|
|
@@ -547,7 +558,9 @@ function admitOversizedCandidate(
|
|
|
547
558
|
kind: "spill",
|
|
548
559
|
createdAt: candidate.createdAt,
|
|
549
560
|
...(candidate.clientThreadId ? { clientThreadId: candidate.clientThreadId } : {}),
|
|
561
|
+
...(candidate.providerOutputStart !== undefined ? { providerOutputStart: candidate.providerOutputStart } : {}),
|
|
550
562
|
...(candidate.providers ? { providers: candidate.providers } : {}),
|
|
563
|
+
...(candidate.unforcedStoreFalse ? { unforcedStoreFalse: candidate.unforcedStoreFalse } : {}),
|
|
551
564
|
spill: ref,
|
|
552
565
|
};
|
|
553
566
|
const next: SpilledResponseState = { ...base, sizeBytes: stubSize(id, base) };
|
|
@@ -863,20 +876,29 @@ export function replayOverlapSkipsForTests(): number {
|
|
|
863
876
|
* This ceiling therefore bounds what the store owns, which is every file it can
|
|
864
877
|
* account for, and not the directory as a whole.
|
|
865
878
|
*/
|
|
866
|
-
function enforceSpilledResponseBudget(): number {
|
|
879
|
+
function enforceSpilledResponseBudget(requiredHeadroomBytes?: number): number {
|
|
867
880
|
// Price in-flight publications too: a file being created by
|
|
868
881
|
// `writeResponseSpillDurablyAsync` occupies the volume before it reaches `states`.
|
|
869
882
|
let spilledBytes = accountedResponseSpillBytes();
|
|
870
|
-
|
|
883
|
+
// Admission (#6747) passes the room its publication needs, 0 included. Evict only while that
|
|
884
|
+
// room is still reachable: re-read accounting after each attempt, since a failed unlink stays
|
|
885
|
+
// charged, and stop the moment the publication cannot fit so no unrelated continuation pays.
|
|
886
|
+
const admission = requiredHeadroomBytes !== undefined;
|
|
887
|
+
const headroom = admission && requiredHeadroomBytes > 0 ? requiredHeadroomBytes : 0;
|
|
888
|
+
const feasible = (): boolean => !admission
|
|
889
|
+
|| (spilledBytes = accountedResponseSpillBytes()) - spilledResponseBytes() + headroom <= spillByteCap();
|
|
890
|
+
if (!feasible() || spilledBytes <= spillByteCap() - headroom) return 0;
|
|
871
891
|
const before = spilledBytes;
|
|
872
892
|
// Deferred generations go first. They are already superseded, so releasing one
|
|
873
893
|
// costs only the crash window the queue exists to cover — the same trade
|
|
874
894
|
// PENDING_SPILL_UNLINKS_MAX already makes against unbounded disk. Evicting a
|
|
875
895
|
// live continuation to make room for a dead file would be the wrong order.
|
|
876
|
-
while (spilledBytes > spillByteCap() && pendingSpillUnlinks.length > 0) {
|
|
896
|
+
while (spilledBytes > spillByteCap() - headroom && pendingSpillUnlinks.length > 0) {
|
|
877
897
|
const ref = pendingSpillUnlinks.shift()!;
|
|
878
898
|
spilledBytes -= ref.payloadBytes;
|
|
879
899
|
deleteResponseSpill(ref);
|
|
900
|
+
if (admission) spillCounters.headroomEvictions += 1;
|
|
901
|
+
if (!feasible()) return before - spilledBytes;
|
|
880
902
|
}
|
|
881
903
|
// Ordered by createdAt, not by map order. `states` is not an age index:
|
|
882
904
|
// demotion and spill replacement delete and reinsert entries, and
|
|
@@ -893,9 +915,11 @@ function enforceSpilledResponseBudget(): number {
|
|
|
893
915
|
.sort((a, b) => a[1].createdAt - b[1].createdAt
|
|
894
916
|
|| (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
|
|
895
917
|
for (const [id, entry] of spilled) {
|
|
896
|
-
if (spilledBytes <= spillByteCap()) break;
|
|
918
|
+
if (spilledBytes <= spillByteCap() - headroom) break;
|
|
897
919
|
spilledBytes -= entry.spill.payloadBytes;
|
|
898
920
|
deleteEntry(id);
|
|
921
|
+
if (admission) spillCounters.headroomEvictions += 1;
|
|
922
|
+
if (!feasible()) return before - spilledBytes;
|
|
899
923
|
}
|
|
900
924
|
return before - spilledBytes;
|
|
901
925
|
}
|
|
@@ -935,6 +959,7 @@ function pruneResponses(at = now()): void {
|
|
|
935
959
|
items: entry.items,
|
|
936
960
|
...(entry.providerOutputStart !== undefined ? { providerOutputStart: entry.providerOutputStart } : {}),
|
|
937
961
|
...(entry.providers ? { providers: entry.providers } : {}),
|
|
962
|
+
...(entry.unforcedStoreFalse ? { unforcedStoreFalse: entry.unforcedStoreFalse } : {}),
|
|
938
963
|
});
|
|
939
964
|
if (swapResidentForSpill(oldestId, entry, ref)) noteSpillWriteSuccess();
|
|
940
965
|
} catch (error) {
|
|
@@ -994,6 +1019,7 @@ export function evictOldestResponseContinuationForBudget(): number {
|
|
|
994
1019
|
items: entry.items,
|
|
995
1020
|
...(entry.providerOutputStart !== undefined ? { providerOutputStart: entry.providerOutputStart } : {}),
|
|
996
1021
|
...(entry.providers ? { providers: entry.providers } : {}),
|
|
1022
|
+
...(entry.unforcedStoreFalse ? { unforcedStoreFalse: entry.unforcedStoreFalse } : {}),
|
|
997
1023
|
});
|
|
998
1024
|
if (swapResidentForSpill(id, entry, ref)) noteSpillWriteSuccess();
|
|
999
1025
|
} catch (error) {
|
|
@@ -1030,6 +1056,15 @@ function materializeEntry(
|
|
|
1030
1056
|
schedulePersist();
|
|
1031
1057
|
return { ok: false, failure };
|
|
1032
1058
|
}
|
|
1059
|
+
// A restricted snapshot stub must agree with the validated spill's provenance.
|
|
1060
|
+
if (entry.unforcedStoreFalse && (result.payload.unforcedStoreFalse !== true
|
|
1061
|
+
|| entry.providerOutputStart === undefined || entry.providerOutputStart > result.payload.items.length
|
|
1062
|
+
|| entry.providerOutputStart !== result.payload.providerOutputStart)) {
|
|
1063
|
+
spillCounters.readFailures += 1;
|
|
1064
|
+
replaceWithSpillFailure(id, entry);
|
|
1065
|
+
schedulePersist();
|
|
1066
|
+
return { ok: false, failure: { code: "previous_response_not_found", reason: "spill_corrupt" } };
|
|
1067
|
+
}
|
|
1033
1068
|
const state = measureResidentEntry(id, {
|
|
1034
1069
|
createdAt: result.payload.createdAt,
|
|
1035
1070
|
...(result.payload.clientThreadId ? { clientThreadId: result.payload.clientThreadId } : {}),
|
|
@@ -1038,6 +1073,7 @@ function materializeEntry(
|
|
|
1038
1073
|
? { providerOutputStart: result.payload.providerOutputStart }
|
|
1039
1074
|
: {}),
|
|
1040
1075
|
...(result.payload.providers ? { providers: result.payload.providers } : {}),
|
|
1076
|
+
...(result.payload.unforcedStoreFalse ? { unforcedStoreFalse: result.payload.unforcedStoreFalse } : {}),
|
|
1041
1077
|
});
|
|
1042
1078
|
if (!state) {
|
|
1043
1079
|
spillCounters.readFailures += 1;
|
|
@@ -1075,6 +1111,15 @@ export function expandPreviousResponseInput(body: unknown, clientThreadId?: stri
|
|
|
1075
1111
|
replayScopeMismatchDrops += 1;
|
|
1076
1112
|
return body;
|
|
1077
1113
|
}
|
|
1114
|
+
const clientInput = inputItems(request.input);
|
|
1115
|
+
const stored = materialized.state.items;
|
|
1116
|
+
const carried = clientCarriedPrefixLength(stored, clientInput);
|
|
1117
|
+
if (
|
|
1118
|
+
materialized.state.unforcedStoreFalse
|
|
1119
|
+
&& !allowsUnforcedStoreFalseReplay(stored, materialized.state.providerOutputStart, clientInput, carried)
|
|
1120
|
+
) {
|
|
1121
|
+
return body;
|
|
1122
|
+
}
|
|
1078
1123
|
// The client already replayed this history verbatim. Prepending the stored copy would
|
|
1079
1124
|
// double it, and the doubled turn is stored again, so the next turn triples (#1412 saw
|
|
1080
1125
|
// 127k of real context reach 1.3M tokens this way).
|
|
@@ -1087,10 +1132,7 @@ export function expandPreviousResponseInput(body: unknown, clientThreadId?: stri
|
|
|
1087
1132
|
// There is no invariant that provider output always carries ids, so an entry whose output
|
|
1088
1133
|
// has none simply never skips.
|
|
1089
1134
|
{
|
|
1090
|
-
const clientInput = inputItems(request.input);
|
|
1091
|
-
const stored = materialized.state.items;
|
|
1092
1135
|
const anchor = materialized.state.providerOutputStart;
|
|
1093
|
-
const carried = clientCarriedPrefixLength(stored, clientInput);
|
|
1094
1136
|
if (
|
|
1095
1137
|
carried === stored.length
|
|
1096
1138
|
&& anchor !== undefined
|
|
@@ -1110,9 +1152,9 @@ export function expandPreviousResponseInput(body: unknown, clientThreadId?: stri
|
|
|
1110
1152
|
}
|
|
1111
1153
|
const expanded = {
|
|
1112
1154
|
...request,
|
|
1113
|
-
input: [...
|
|
1155
|
+
input: [...stored, ...clientInput],
|
|
1114
1156
|
};
|
|
1115
|
-
replayedInputPrefixLengths.set(expanded,
|
|
1157
|
+
replayedInputPrefixLengths.set(expanded, stored.length);
|
|
1116
1158
|
return expanded;
|
|
1117
1159
|
}
|
|
1118
1160
|
|
|
@@ -1156,29 +1198,6 @@ export function previousResponseProviderState(responseId: string | undefined): O
|
|
|
1156
1198
|
return providers ? structuredClone(providers) : undefined;
|
|
1157
1199
|
}
|
|
1158
1200
|
|
|
1159
|
-
export interface ResponseStateMetrics {
|
|
1160
|
-
count: number;
|
|
1161
|
-
residentCount: number;
|
|
1162
|
-
spillStubCount: number;
|
|
1163
|
-
tombstoneCount: number;
|
|
1164
|
-
totalBytes: number;
|
|
1165
|
-
spillPayloadBytes: number;
|
|
1166
|
-
largestBytes: number;
|
|
1167
|
-
oldestAgeMs: number;
|
|
1168
|
-
spillWrites: number;
|
|
1169
|
-
spillWriteFailures: number;
|
|
1170
|
-
spillWriteStatus: ResponseSpillWriteStatus;
|
|
1171
|
-
spillWriteConsecutiveFailures: number;
|
|
1172
|
-
spillLastWriteFailureCode: ResponseSpillWriteFailureCode | null;
|
|
1173
|
-
spillLastWriteFailureOrigin: ResponseSpillWriteFailureOrigin | null;
|
|
1174
|
-
spillAclRetryReturnedTimeouts: number;
|
|
1175
|
-
spillAclTimeoutMemoRefusals: number;
|
|
1176
|
-
spillLastWriteFailureAt: number | null;
|
|
1177
|
-
spillLastWriteSuccessAt: number | null;
|
|
1178
|
-
spillReadFailures: number;
|
|
1179
|
-
replayScopeMismatchDrops: number;
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
1201
|
/**
|
|
1183
1202
|
* Observe-only snapshot of the in-RAM continuation store, surfaced via GET /api/system/memory.
|
|
1184
1203
|
* Additive and side-effect free — it does NOT lazy-load the disk snapshot, prune, or evict — so a
|
|
@@ -1189,50 +1208,24 @@ export interface ResponseStateMetrics {
|
|
|
1189
1208
|
* store (JS heap) or in the runtime allocator (native).
|
|
1190
1209
|
*/
|
|
1191
1210
|
export function responseStateMetrics(): ResponseStateMetrics {
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
let spillStubCount = 0;
|
|
1197
|
-
let tombstoneCount = 0;
|
|
1198
|
-
let spillPayloadBytes = 0;
|
|
1199
|
-
for (const state of states.values()) {
|
|
1200
|
-
const bytes = state.sizeBytes;
|
|
1201
|
-
if (bytes > largestBytes) largestBytes = bytes;
|
|
1202
|
-
if (state.createdAt < oldestCreatedAt) oldestCreatedAt = state.createdAt;
|
|
1203
|
-
if (state.kind === "resident") {
|
|
1204
|
-
residentCount += 1;
|
|
1205
|
-
} else if (state.kind === "spill") {
|
|
1206
|
-
spillStubCount += 1;
|
|
1207
|
-
spillPayloadBytes += state.spill.payloadBytes;
|
|
1208
|
-
} else tombstoneCount += 1;
|
|
1209
|
-
}
|
|
1210
|
-
return {
|
|
1211
|
-
count: states.size,
|
|
1212
|
-
residentCount,
|
|
1213
|
-
spillStubCount,
|
|
1214
|
-
tombstoneCount,
|
|
1211
|
+
return computeResponseStateMetrics({
|
|
1212
|
+
states: states.values(),
|
|
1213
|
+
stateCount: states.size,
|
|
1214
|
+
now: now(),
|
|
1215
1215
|
totalBytes: responseContinuationRetainedStoreSnapshot().bytes,
|
|
1216
|
-
spillPayloadBytes,
|
|
1217
|
-
largestBytes,
|
|
1218
|
-
oldestAgeMs: states.size > 0 ? at - oldestCreatedAt : 0,
|
|
1219
1216
|
spillWrites: spillCounters.writes,
|
|
1220
1217
|
spillWriteFailures: spillCounters.writeFailures,
|
|
1221
|
-
spillWriteStatus: spillWriteHealth.consecutiveFailures > 0
|
|
1222
|
-
? "degraded"
|
|
1223
|
-
: spillWriteHealth.lastSuccessAt !== null
|
|
1224
|
-
? "healthy"
|
|
1225
|
-
: "initial",
|
|
1226
1218
|
spillWriteConsecutiveFailures: spillWriteHealth.consecutiveFailures,
|
|
1227
|
-
|
|
1228
|
-
|
|
1219
|
+
spillLastFailureCode: spillWriteHealth.lastFailureCode,
|
|
1220
|
+
spillLastFailureOrigin: spillWriteHealth.lastFailureOrigin,
|
|
1221
|
+
spillLastSuccessAt: spillWriteHealth.lastSuccessAt,
|
|
1229
1222
|
spillAclRetryReturnedTimeouts: spillCounters.aclRetryReturnedTimeouts,
|
|
1230
1223
|
spillAclTimeoutMemoRefusals: spillCounters.aclTimeoutMemoRefusals,
|
|
1231
|
-
|
|
1232
|
-
|
|
1224
|
+
spillCapacityRefusals: spillCounters.capacityRefusals, spillHeadroomEvictions: spillCounters.headroomEvictions,
|
|
1225
|
+
spillLastFailureAt: spillWriteHealth.lastFailureAt,
|
|
1233
1226
|
spillReadFailures: spillCounters.readFailures,
|
|
1234
1227
|
replayScopeMismatchDrops,
|
|
1235
|
-
};
|
|
1228
|
+
});
|
|
1236
1229
|
}
|
|
1237
1230
|
|
|
1238
1231
|
/**
|
|
@@ -1256,7 +1249,7 @@ export function rememberResponseState(
|
|
|
1256
1249
|
requestBody: unknown,
|
|
1257
1250
|
response: { id?: unknown; output?: unknown; status?: unknown; incomplete_details?: unknown },
|
|
1258
1251
|
providerState?: OcxProviderContinuationState | string,
|
|
1259
|
-
opts?: { force?: boolean; clientThreadId?: string },
|
|
1252
|
+
opts?: { force?: boolean; clientThreadId?: string; retainForToolContinuation?: boolean },
|
|
1260
1253
|
): void {
|
|
1261
1254
|
if (!requestBody || typeof requestBody !== "object" || Array.isArray(requestBody)) return;
|
|
1262
1255
|
const request = requestBody as Record<string, unknown>;
|
|
@@ -1266,8 +1259,10 @@ export function rememberResponseState(
|
|
|
1266
1259
|
// The passthrough branch records with force so those chains can be expanded locally; the
|
|
1267
1260
|
// store stays in-memory under RESPONSE_TTL_MS, so this is a proxy-internal continuation cache, not
|
|
1268
1261
|
// real server-side response storage.
|
|
1269
|
-
if (request.store === false && !opts?.force) return;
|
|
1270
1262
|
if (typeof response.id !== "string" || !Array.isArray(response.output)) return;
|
|
1263
|
+
// Only an explicitly opted-in adapter may retain pending client calls despite store:false.
|
|
1264
|
+
const unforcedStoreFalse = request.store === false && !opts?.force;
|
|
1265
|
+
if (unforcedStoreFalse && !(opts?.retainForToolContinuation === true && hasPendingClientToolCall(response.output))) return;
|
|
1271
1266
|
if (response.status === "incomplete") {
|
|
1272
1267
|
const details = response.incomplete_details;
|
|
1273
1268
|
if (!details || typeof details !== "object" || Array.isArray(details)
|
|
@@ -1300,6 +1295,7 @@ export function rememberResponseState(
|
|
|
1300
1295
|
// incomplete agent turn on the Cursor side (we suspended without a real mcpResult), so its
|
|
1301
1296
|
// checkpoint must not be reused — but the conversation id string itself is still valid.
|
|
1302
1297
|
...(Object.keys(normalizedProviderState).length > 0 ? { providers: normalizedProviderState } : {}),
|
|
1298
|
+
...(unforcedStoreFalse ? { unforcedStoreFalse: true } : {}),
|
|
1303
1299
|
});
|
|
1304
1300
|
enforceAppOwnedMemoryBudget();
|
|
1305
1301
|
schedulePersist();
|
|
@@ -1337,11 +1333,7 @@ export function clearResponseStateMemoryForTests(): void {
|
|
|
1337
1333
|
oldestResidentAt = null;
|
|
1338
1334
|
stateRevision = 0;
|
|
1339
1335
|
pendingSpillUnlinks.length = 0;
|
|
1340
|
-
|
|
1341
|
-
spillCounters.writeFailures = 0;
|
|
1342
|
-
spillCounters.readFailures = 0;
|
|
1343
|
-
spillCounters.aclRetryReturnedTimeouts = 0;
|
|
1344
|
-
spillCounters.aclTimeoutMemoRefusals = 0;
|
|
1336
|
+
resetSpillCountersForTests();
|
|
1345
1337
|
spillWriteHealth.consecutiveFailures = 0;
|
|
1346
1338
|
spillWriteHealth.lastFailureCode = null;
|
|
1347
1339
|
spillWriteHealth.lastFailureOrigin = null;
|
|
@@ -54,3 +54,20 @@ export function externalTaskInputContent(item: unknown): string | OcxContentPart
|
|
|
54
54
|
block.type === "output_text" ? { ...block, type: "input_text" } : block,
|
|
55
55
|
));
|
|
56
56
|
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The same recognition, emitted as Responses-wire user content for the raw-body repairs (#6764).
|
|
60
|
+
*
|
|
61
|
+
* The passthrough and routed-compaction paths never read the parsed context, so they used to
|
|
62
|
+
* send this envelope through the generic orphan repair, which prefixes
|
|
63
|
+
* "[tool output for unknown call]". A summarizer then read an authorized handover as a stray tool
|
|
64
|
+
* result. Recognition is shared with the parser so both paths agree on what is task input.
|
|
65
|
+
*/
|
|
66
|
+
export function externalTaskInputResponsesContent(item: unknown): Record<string, unknown>[] | undefined {
|
|
67
|
+
if (externalTaskInputContent(item) === undefined) return undefined;
|
|
68
|
+
const output = (item as { output: string | TaskInputBlock[] }).output;
|
|
69
|
+
if (typeof output === "string") return [{ type: "input_text", text: output }];
|
|
70
|
+
return output.map(block => block.type === "input_image"
|
|
71
|
+
? { type: "input_image", image_url: block.image_url, ...(block.detail ? { detail: block.detail === "original" ? "high" : block.detail } : {}) }
|
|
72
|
+
: { type: "input_text", text: block.text });
|
|
73
|
+
}
|
package/src/router.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CODEX_INTERNAL_OPENAI_MODELS } from "./codex/control-plane-models";
|
|
1
2
|
import type { CodexAccountMode, OcxConfig, OcxProviderConfig } from "./types";
|
|
2
3
|
import { createHash } from "node:crypto";
|
|
3
4
|
import { peekAuthStore } from "./oauth/store";
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
} from "./combos";
|
|
13
14
|
import type { NormalizedComboConfig } from "./combos/types";
|
|
14
15
|
import { hasOwnProvider } from "./config/provider-name";
|
|
16
|
+
import { anthropicInstanceRowShapeMatches } from "./providers/anthropic-instance-id";
|
|
15
17
|
import { providerUsesKeyAuthOverride, resolveProviderApiKey } from "./providers/key-store";
|
|
16
18
|
import { captureProviderApiKeySelection } from "./providers/api-key-selection-capture";
|
|
17
19
|
import { assertProviderDestinationAllowed } from "./lib/destination-policy";
|
|
@@ -53,6 +55,14 @@ import { evaluatePolicyProfile, type PolicyRequestEvidence } from "./routing/eva
|
|
|
53
55
|
import { assemblePolicyCandidateEvidence } from "./routing/compatibility/assemble";
|
|
54
56
|
import { resolveModelPolicy, type ResolvedModelPolicy } from "./providers/resolved-model-policy";
|
|
55
57
|
|
|
58
|
+
/** Explicit Pool 2 admission refusal; ingress must not recover it through default routing. */
|
|
59
|
+
export class AnthropicSecondaryInstanceUnavailableError extends Error {
|
|
60
|
+
constructor() {
|
|
61
|
+
super("Anthropic Pool 2 requires an enabled configured provider with valid OAuth ownership");
|
|
62
|
+
this.name = "AnthropicSecondaryInstanceUnavailableError";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
56
66
|
export class UnknownRoutingPolicyError extends Error {
|
|
57
67
|
constructor(readonly profileId: string) {
|
|
58
68
|
super(`Unknown routing policy: ${profileId}`);
|
|
@@ -521,9 +531,18 @@ export function routedProviderConfig(providerName: string, provider: OcxProvider
|
|
|
521
531
|
return resolved;
|
|
522
532
|
}
|
|
523
533
|
|
|
534
|
+
/**
|
|
535
|
+
* Rows eligible for bare-model inference (configured default model, model list, model alias).
|
|
536
|
+
*
|
|
537
|
+
* The marked builtin Pool 2 row is excluded: it seeds the same catalog as `anthropic`, so letting it
|
|
538
|
+
* compete would move a bare `claude-*` request onto Pool 2 by insertion order, or make a shared alias
|
|
539
|
+
* ambiguous. Pool 2 is reached only through `anthropic2/<model>`, its provider alias, or an explicit
|
|
540
|
+
* `defaultProvider`. An unmarked custom row named `anthropic2` keeps its ordinary custom meaning.
|
|
541
|
+
*/
|
|
524
542
|
function activeProviderEntries(config: OcxConfig): [string, OcxProviderConfig][] {
|
|
525
543
|
return Object.entries(config.providers)
|
|
526
|
-
.filter(([name, provider]) => name !== LEGACY_CHATGPT_PROVIDER_ID && provider.disabled !== true
|
|
544
|
+
.filter(([name, provider]) => name !== LEGACY_CHATGPT_PROVIDER_ID && provider.disabled !== true
|
|
545
|
+
&& !(name === "anthropic2" && anthropicInstanceRowShapeMatches(name, provider)));
|
|
527
546
|
}
|
|
528
547
|
|
|
529
548
|
export class NoEnabledOpenAiProviderError extends Error {
|
|
@@ -564,9 +583,6 @@ export function comboRouteDecisionTrace(
|
|
|
564
583
|
});
|
|
565
584
|
}
|
|
566
585
|
|
|
567
|
-
// Codex uses a small number of control-plane model ids that are not part of the public GPT/o
|
|
568
|
-
// naming families. Keep this exact: a broad `codex-*` rule could capture a third-party model.
|
|
569
|
-
const CODEX_INTERNAL_OPENAI_MODELS = new Set(["codex-auto-review"]);
|
|
570
586
|
const MAX_BLOCKED_MODEL_REDIRECT_EDGES = 5;
|
|
571
587
|
|
|
572
588
|
interface BlockedModelRedirectState {
|
|
@@ -620,6 +636,9 @@ function routeResult(
|
|
|
620
636
|
routeReason: string,
|
|
621
637
|
redirectState: BlockedModelRedirectState,
|
|
622
638
|
): RouteResult {
|
|
639
|
+
if (providerName === "anthropic2" && provider.authMode === "oauth" && !anthropicInstanceRowShapeMatches(providerName, provider)) {
|
|
640
|
+
throw new AnthropicSecondaryInstanceUnavailableError();
|
|
641
|
+
}
|
|
623
642
|
const qualified = resolveBlockedModelRedirect(config, `${providerName}/${modelId}`);
|
|
624
643
|
const bare = resolveBlockedModelRedirect(config, modelId);
|
|
625
644
|
const crossTarget = config && (
|
|
@@ -813,6 +832,11 @@ function routeModelInternal(
|
|
|
813
832
|
if (slash > 0) {
|
|
814
833
|
const requestedProvider = modelId.slice(0, slash);
|
|
815
834
|
const requestedLower = requestedProvider.toLowerCase();
|
|
835
|
+
// Literal B is a reserved instance selector even before its row exists. Keep other
|
|
836
|
+
// qualifiers, including exact uppercase custom keys, on the ordinary routing path.
|
|
837
|
+
if (requestedProvider === "anthropic2" && !hasOwnProvider(config.providers, requestedProvider)) {
|
|
838
|
+
throw new AnthropicSecondaryInstanceUnavailableError();
|
|
839
|
+
}
|
|
816
840
|
let provName: string | undefined;
|
|
817
841
|
|
|
818
842
|
if (hasOwnProvider(config.providers, requestedProvider)) {
|
|
@@ -76,7 +76,7 @@ export function assemblePolicyCandidateEvidence(
|
|
|
76
76
|
quota: quotaEvidenceForCandidate({
|
|
77
77
|
provider: candidate.provider,
|
|
78
78
|
model: candidate.model,
|
|
79
|
-
}),
|
|
79
|
+
}, config),
|
|
80
80
|
cost: costEvidenceForCandidate({
|
|
81
81
|
provider: candidate.provider,
|
|
82
82
|
model: candidate.model,
|
package/src/routing/quota.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { OcxConfig } from "../types";
|
|
2
|
+
import { configuredAnthropicInstance } from "../providers/anthropic-instance";
|
|
1
3
|
/**
|
|
2
4
|
* Quota-aware policy scoring (RI-07).
|
|
3
5
|
*
|
|
@@ -98,7 +100,7 @@ export function codexPoolQuotaEvidence(accounts: readonly CodexPoolQuotaAccount[
|
|
|
98
100
|
};
|
|
99
101
|
}
|
|
100
102
|
|
|
101
|
-
export function quotaEvidenceForCandidate(input: QuotaEvidenceInput): RouteQuotaEvidence {
|
|
103
|
+
export function quotaEvidenceForCandidate(input: QuotaEvidenceInput, config?: Pick<OcxConfig, "providers">): RouteQuotaEvidence {
|
|
102
104
|
if (input.provider === "openai" && input.codexAccountId) {
|
|
103
105
|
// listAccountQuotas() is the reconciled quota snapshot: config-generation
|
|
104
106
|
// reconciliation prunes removed accounts. Other eligibility dimensions
|
|
@@ -113,8 +115,10 @@ export function quotaEvidenceForCandidate(input: QuotaEvidenceInput): RouteQuota
|
|
|
113
115
|
return codexAccountQuotaEvidence(input.codexAccountId, input.codexAccountPlan);
|
|
114
116
|
}
|
|
115
117
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
+
const instance = config ? configuredAnthropicInstance(config, input.provider)
|
|
119
|
+
: input.provider === "anthropic" ? "anthropic" : undefined;
|
|
120
|
+
if (instance && input.accountRef) {
|
|
121
|
+
const quota = getCachedProviderAccountQuota(instance, input.accountRef);
|
|
118
122
|
if (quota) {
|
|
119
123
|
const family = anthropicFamilyWindow(input.model, quota.customWindows ?? []);
|
|
120
124
|
const percents = [quota.fiveHourPercent, quota.weeklyPercent, quota.monthlyPercent, family?.percent]
|
package/src/server/auth-cors.ts
CHANGED
|
@@ -38,6 +38,7 @@ import { redactSecretString } from "../lib/redact";
|
|
|
38
38
|
import { DECLARABLE_HOSTED_TOOL_TYPES } from "../responses/hosted-tool-policy";
|
|
39
39
|
import { effectiveGoogleMode, getProviderRegistryEntry, providerCodexAccountMode, providerMatchesRegistryTransport, registryEntryForProviderDestination } from "../providers/registry";
|
|
40
40
|
import { providerConfigSeed } from "../providers/derive";
|
|
41
|
+
import { anthropicOAuthInstanceConfigError } from "../config/schema/anthropic-account-pool";
|
|
41
42
|
import type { OcxConfig, OcxProviderConfig } from "../types";
|
|
42
43
|
import { openRouterRoutingConfigError } from "../providers/openrouter-routing";
|
|
43
44
|
import { modelAutoCompactTokenLimitsConfigError } from "../providers/auto-compact-budget";
|
|
@@ -785,6 +786,8 @@ export function providerManagementConfigError(
|
|
|
785
786
|
return `provider ${name} must not include codexAccountMode`;
|
|
786
787
|
}
|
|
787
788
|
const typed = provider as unknown as OcxProviderConfig;
|
|
789
|
+
const markerError = anthropicOAuthInstanceConfigError({ providers: { [name]: provider } });
|
|
790
|
+
if (markerError) return markerError;
|
|
788
791
|
// Every write path (POST, PUT, reload, both PATCH passes) funnels through here, so a PATCH
|
|
789
792
|
// that changes authMode/baseUrl/adapter under a retained tlsProfile is refused before it
|
|
790
793
|
// persists a row the config schema would later reject as document-fatal.
|
|
@@ -1057,6 +1060,8 @@ const PROVIDER_CONFIG_FIELD_POLICY = {
|
|
|
1057
1060
|
modelVercelGatewayRouting: "editor",
|
|
1058
1061
|
authMode: "editor",
|
|
1059
1062
|
oauthAccountFailover: "editor",
|
|
1063
|
+
anthropicAccountPool: "editor",
|
|
1064
|
+
anthropicOAuthInstance: "editor",
|
|
1060
1065
|
keyOptional: "editor",
|
|
1061
1066
|
freeTier: "editor",
|
|
1062
1067
|
note: "editor",
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
} from "../storage/policy-scheduler";
|
|
15
15
|
import { startQuotaResetPoller, stopQuotaResetPoller } from "../quota/reset-poller";
|
|
16
16
|
import {
|
|
17
|
+
setCatalogAutoRefreshClientFanout,
|
|
17
18
|
startCatalogAutoRefresh,
|
|
18
19
|
stopCatalogAutoRefresh,
|
|
19
20
|
syncCatalogAutoRefreshCadence,
|
|
@@ -40,6 +41,7 @@ type ProcessLoops = {
|
|
|
40
41
|
|
|
41
42
|
type LeaseOwner = {
|
|
42
43
|
token: symbol;
|
|
44
|
+
fanoutRevoked: boolean;
|
|
43
45
|
applyPolicy: PolicyApply;
|
|
44
46
|
resources: ServerResourceOwnerLease;
|
|
45
47
|
lowQuota: LowQuotaRegistration;
|
|
@@ -48,6 +50,7 @@ type LeaseOwner = {
|
|
|
48
50
|
export type ServerBackgroundLifecycleLease = {
|
|
49
51
|
scheduleStartupRun(): void;
|
|
50
52
|
listLowQuotaEvents(limit?: number): LowQuotaEvent[];
|
|
53
|
+
revokeClientFanout(): void;
|
|
51
54
|
release(): Promise<void>;
|
|
52
55
|
releaseAfterFailedStart(): void;
|
|
53
56
|
};
|
|
@@ -61,6 +64,25 @@ function setLivePolicyOwner(applyPolicy: PolicyApply | null): void {
|
|
|
61
64
|
setStorageCleanupPolicyJobLiveApply(applyPolicy);
|
|
62
65
|
}
|
|
63
66
|
|
|
67
|
+
/** Resolve the current process owner at delivery time so lease hand-over cannot retain an old port. */
|
|
68
|
+
async function refreshConnectedClientsAfterCatalogChange(isCurrent: () => boolean) {
|
|
69
|
+
const captured = owners.findLast(owner => !owner.fanoutRevoked);
|
|
70
|
+
if (!captured) return [];
|
|
71
|
+
const current = () => isCurrent() && !captured.fanoutRevoked && owners.includes(captured);
|
|
72
|
+
const [{ readRuntimePort }, { loadConfig }, { localClientSyncAllowed }, { syncEnabledClientIntegrations }] =
|
|
73
|
+
await Promise.all([
|
|
74
|
+
import("../config/process-state"),
|
|
75
|
+
import("../config"),
|
|
76
|
+
import("../codex/desired-state"),
|
|
77
|
+
import("./management/config-routes"),
|
|
78
|
+
]);
|
|
79
|
+
const runtime = readRuntimePort(process.pid);
|
|
80
|
+
const config = loadConfig();
|
|
81
|
+
// An explicit catalog tick on a hub is not permission to enroll its local clients.
|
|
82
|
+
if (!runtime || !current() || !localClientSyncAllowed(config)) return [];
|
|
83
|
+
return syncEnabledClientIntegrations(runtime.port, config, {}, { unattended: { isCurrent: current } });
|
|
84
|
+
}
|
|
85
|
+
|
|
64
86
|
function startProcessLoops(applyPolicy: PolicyApply): ProcessLoops {
|
|
65
87
|
let memoryWatchdog: MemoryWatchdog | null = null;
|
|
66
88
|
let stateStoreSweeper: ReturnType<typeof startStateStoreSweeper> | null = null;
|
|
@@ -83,6 +105,7 @@ function startProcessLoops(applyPolicy: PolicyApply): ProcessLoops {
|
|
|
83
105
|
// and interval timers are unref'd. The scheduler
|
|
84
106
|
// module keeps every heavy import inside its tick, so naming it statically here
|
|
85
107
|
// costs a module record and nothing else.
|
|
108
|
+
setCatalogAutoRefreshClientFanout(refreshConnectedClientsAfterCatalogChange);
|
|
86
109
|
startCatalogAutoRefresh();
|
|
87
110
|
// The scheduler starts at its default cadence because resolving the operator's value
|
|
88
111
|
// reads the config barrel. Fire-and-forget: startup must not await an optional
|
|
@@ -106,6 +129,7 @@ function startProcessLoops(applyPolicy: PolicyApply): ProcessLoops {
|
|
|
106
129
|
stopStorageCleanupScheduler();
|
|
107
130
|
stopQuotaResetPoller();
|
|
108
131
|
stopCatalogAutoRefresh();
|
|
132
|
+
setCatalogAutoRefreshClientFanout(null);
|
|
109
133
|
setLivePolicyOwner(null);
|
|
110
134
|
throw error;
|
|
111
135
|
}
|
|
@@ -119,6 +143,7 @@ function stopProcessLoops(): void {
|
|
|
119
143
|
stopStorageCleanupScheduler();
|
|
120
144
|
stopQuotaResetPoller();
|
|
121
145
|
stopCatalogAutoRefresh();
|
|
146
|
+
setCatalogAutoRefreshClientFanout(null);
|
|
122
147
|
setLivePolicyOwner(null);
|
|
123
148
|
}
|
|
124
149
|
|
|
@@ -181,6 +206,7 @@ export function acquireServerBackgroundLifecycle(
|
|
|
181
206
|
|
|
182
207
|
const owner: LeaseOwner = {
|
|
183
208
|
token: Symbol("server-background-lifecycle"),
|
|
209
|
+
fanoutRevoked: false,
|
|
184
210
|
applyPolicy,
|
|
185
211
|
resources: acquireServerResourceOwner(),
|
|
186
212
|
lowQuota: registerCodexLowQuotaProtection(config),
|
|
@@ -206,6 +232,7 @@ export function acquireServerBackgroundLifecycle(
|
|
|
206
232
|
return stopStoragePolicyWorker().finally(() => { cleanupInProgress = false; });
|
|
207
233
|
}
|
|
208
234
|
return {
|
|
235
|
+
revokeClientFanout() { owner.fanoutRevoked = true; },
|
|
209
236
|
listLowQuotaEvents(limit) { return owner.lowQuota.listEvents(limit); },
|
|
210
237
|
scheduleStartupRun() {
|
|
211
238
|
if (owners.some(candidate => candidate.token === owner.token)) {
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
UPSTREAM_RESET_REPLAY_REFUSED_CODE,
|
|
33
33
|
} from "../lib/upstream-retry";
|
|
34
34
|
import { estimateTokens } from "../lib/token-estimate";
|
|
35
|
-
import { captureRouteStaticPolicy, NoEligiblePolicyCandidateError, UnknownRoutingPolicyError, routeModel } from "../router";
|
|
35
|
+
import { AnthropicSecondaryInstanceUnavailableError, captureRouteStaticPolicy, NoEligiblePolicyCandidateError, UnknownRoutingPolicyError, routeModel } from "../router";
|
|
36
36
|
import { evidenceFromBody } from "../routing/request-evidence";
|
|
37
37
|
import { resolveWireProtocolOverride } from "./adapter-resolve";
|
|
38
38
|
import { resolveOpenCodeGoTransport } from "../providers/opencode-go-transport";
|
|
@@ -113,6 +113,7 @@ export async function handleChatCompletions(
|
|
|
113
113
|
return finalizeTranslatorBudgetResponse(
|
|
114
114
|
await handleChatCompletionsWithBudget(req, config, logCtx, translatorBudget, logIds),
|
|
115
115
|
translatorBudget,
|
|
116
|
+
req.signal,
|
|
116
117
|
);
|
|
117
118
|
} catch (error) {
|
|
118
119
|
translatorBudget.dispose();
|
|
@@ -242,6 +243,11 @@ async function handleChatCompletionsWithBudget(
|
|
|
242
243
|
}
|
|
243
244
|
}
|
|
244
245
|
} catch (err) {
|
|
246
|
+
if (err instanceof AnthropicSecondaryInstanceUnavailableError) {
|
|
247
|
+
logCtx.requestedModel = requestedModel;
|
|
248
|
+
if (logIds) addFinalRequestLog(logIds.requestId, logIds.start, logCtx, 401, { closeReason: "non_stream" });
|
|
249
|
+
return chatCompletionsErrorResponse(401, err.message, "authentication_error");
|
|
250
|
+
}
|
|
245
251
|
if (err instanceof AdmissionModelDeniedError) {
|
|
246
252
|
logCtx.requestedModel = requestedModel;
|
|
247
253
|
if (logIds) addFinalRequestLog(logIds.requestId, logIds.start, logCtx, 403, { closeReason: "non_stream" });
|