@bitkyc08/opencodex 2.86.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/gui/dist/assets/App-ZfYbLHkB.js +58 -0
- package/gui/dist/assets/App-wT3mXqFz.css +1 -0
- package/gui/dist/assets/{Tray-DyR9yBee.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 -1
- package/src/adapters/anthropic/beta-allowlist.ts +9 -8
- package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
- package/src/adapters/anthropic/passthrough.ts +7 -23
- package/src/adapters/anthropic-progress.ts +84 -0
- package/src/adapters/anthropic.ts +108 -21
- package/src/adapters/base.ts +2 -0
- package/src/adapters/claude-cli/adapter.ts +111 -23
- package/src/adapters/claude-cli/profiles.ts +2 -1
- package/src/adapters/claude-cli/stable-replay.ts +108 -0
- package/src/adapters/claude-cli/usage-admission.ts +230 -0
- package/src/adapters/coding-agent/protocol.ts +1 -1
- package/src/adapters/coding-agent/turn.ts +18 -2
- package/src/adapters/dsh-account.ts +97 -0
- package/src/adapters/google.ts +35 -8
- package/src/adapters/openai-chat/messages.ts +4 -3
- package/src/adapters/openai-chat/passthrough.ts +8 -2
- package/src/adapters/openai-chat/reasoning-replay.ts +52 -0
- package/src/adapters/openai-responses/compaction-search-history.ts +14 -7
- package/src/adapters/openai-responses/passthrough.ts +57 -125
- package/src/adapters/openai-responses/request-strips.ts +25 -3
- package/src/adapters/openai-responses/transport.ts +81 -0
- package/src/adapters/qoder/adapter.ts +4 -2
- package/src/adapters/registry.ts +5 -0
- package/src/adapters/zed.ts +26 -1
- package/src/advisor/activity.ts +55 -0
- package/src/advisor/budget.ts +62 -0
- package/src/advisor/consult.ts +4 -1
- package/src/advisor/context.ts +16 -4
- package/src/advisor/evidence-tool.ts +21 -0
- package/src/advisor/evidence.ts +117 -0
- package/src/advisor/runtime.ts +79 -31
- package/src/advisor/settings.ts +11 -2
- package/src/bridge/errors.ts +19 -1
- package/src/bridge/internal.ts +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/claude/desktop-3p-library.ts +32 -7
- package/src/claude/desktop-3p.ts +55 -13
- package/src/claude/desktop-first-party.ts +1 -1
- package/src/claude/desktop-picker-platform.ts +4 -0
- package/src/claude/desktop-picker.ts +15 -8
- package/src/claude/desktop-system-proxy.ts +50 -4
- package/src/claude/inbound-content-options.ts +11 -0
- package/src/claude/inbound.ts +33 -0
- package/src/claude/intercept/picker-ca-startup.ts +2 -1
- package/src/claude/intercept/picker-runtime.ts +6 -5
- package/src/claude/intercept/picker-trust-windows.ts +338 -0
- package/src/claude/intercept/picker-trust.ts +19 -0
- package/src/claude/intercept/port-policy.ts +30 -0
- package/src/claude/intercept/runtime.ts +97 -94
- package/src/claude/outbound.ts +38 -13
- package/src/cli/account-api.ts +1 -0
- package/src/cli/account-policy-dto.ts +6 -0
- package/src/cli/account-policy.ts +34 -5
- package/src/cli/account-preservation.ts +44 -0
- package/src/cli/account.ts +8 -2
- package/src/cli/advisor.ts +17 -3
- package/src/cli/capabilities-accounts.ts +20 -1
- package/src/cli/capabilities-agents-routing.ts +5 -2
- package/src/cli/capabilities-base.ts +38 -10
- package/src/cli/capabilities-observe-system.ts +63 -3
- package/src/cli/capabilities-provider-models.ts +11 -0
- package/src/cli/codex-cli-update.ts +151 -41
- package/src/cli/codex-provider.ts +44 -0
- package/src/cli/combo-input.ts +10 -1
- package/src/cli/combo-result.ts +1 -1
- package/src/cli/combo.ts +2 -1
- package/src/cli/cross-home-owner.ts +17 -14
- package/src/cli/dispatch.ts +25 -4
- package/src/cli/doctor.ts +6 -1
- package/src/cli/ensure-desktop-cold-start.ts +60 -7
- package/src/cli/help.ts +3 -2
- package/src/cli/index.ts +1 -1
- package/src/cli/integration-plan-dto.ts +1 -0
- package/src/cli/log-history.ts +172 -0
- package/src/cli/models-runtime-subcommands.ts +1 -0
- package/src/cli/models-runtime.ts +72 -8
- package/src/cli/observe-stream.ts +2 -0
- package/src/cli/observe.ts +16 -4
- package/src/cli/provider.ts +27 -5
- package/src/cli/registry.ts +28 -8
- package/src/cli/runtime-api.ts +5 -3
- package/src/cli/system-command.ts +40 -8
- package/src/cli/uninstall-client-state.ts +32 -0
- package/src/cli/usage-compare.ts +309 -0
- package/src/cli/usage-model-search.ts +4 -4
- package/src/cli/usage-report.ts +2 -2
- package/src/cli/v2.ts +1 -1
- package/src/client/machine-listener.ts +1 -3
- package/src/client/runtime.ts +1 -0
- package/src/clients/client-path.ts +51 -0
- package/src/clients/config-export/contracts.ts +2 -1
- package/src/clients/config-export.ts +18 -44
- package/src/clients/gentle-shell-path.ts +87 -0
- package/src/codex/account-auto-switch.ts +2 -0
- package/src/codex/account-identity.ts +29 -0
- package/src/codex/account-label.ts +1 -1
- package/src/codex/account-lifecycle.ts +21 -5
- package/src/codex/account-preservation.ts +28 -0
- package/src/codex/account-use-remaining.ts +23 -0
- package/src/codex/app-server-processes.ts +97 -7
- package/src/codex/app-server-restart-service.ts +55 -4
- package/src/codex/auth-api/account-list.ts +4 -0
- package/src/codex/auth-api/account-pause-group.ts +6 -50
- package/src/codex/auth-api/account-preservation.ts +57 -0
- package/src/codex/auth-api/routes.ts +8 -0
- package/src/codex/auth-api/use-remaining-route.ts +36 -0
- package/src/codex/auth-api.ts +0 -1
- package/src/codex/auth-context.ts +2 -2
- package/src/codex/catalog/auto-review.ts +20 -3
- package/src/codex/catalog/build-entries.ts +9 -1
- package/src/codex/catalog/combo-member.ts +14 -5
- package/src/codex/catalog/configured-tiers.ts +1 -1
- package/src/codex/catalog/gather-capture.ts +2 -0
- package/src/codex/catalog/metadata.ts +11 -9
- package/src/codex/catalog/model-hints.ts +21 -16
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/parsing.ts +30 -86
- package/src/codex/catalog/provider-models.ts +36 -34
- package/src/codex/catalog/routed-gather.ts +12 -13
- package/src/codex/cli-install-provenance.ts +105 -17
- package/src/codex/cli-update-plan.ts +545 -0
- package/src/codex/config-write-lock.ts +23 -2
- package/src/codex/desktop-app/picker-freshness.ts +96 -0
- package/src/codex/desktop-app-restart.ts +21 -0
- package/src/codex/desktop-compatibility/installed-build.ts +24 -6
- package/src/codex/desktop-compatibility/routing-preflight.ts +1 -1
- package/src/codex/desktop-compatibility/runtime.ts +12 -3
- package/src/codex/desktop-compatibility/windows-key-protection.ts +115 -9
- package/src/codex/features.ts +85 -45
- package/src/codex/inject/history-failure.ts +27 -0
- package/src/codex/inject/restore.ts +24 -2
- package/src/codex/inject/routing-classify.ts +4 -3
- package/src/codex/inject.ts +18 -27
- package/src/codex/main-account-hard-lock.ts +30 -1
- package/src/codex/native-profile-startup.ts +1 -1
- package/src/codex/ocx-compaction-history.ts +12 -10
- package/src/codex/paths.ts +86 -1
- package/src/codex/provider-routing.ts +174 -0
- package/src/codex/refresh.ts +2 -2
- package/src/codex/routing/health-store.ts +1 -1
- package/src/codex/routing/selection.ts +4 -3
- package/src/codex/routing/thread-affinity.ts +1 -1
- package/src/codex/signed-in-identity.ts +43 -0
- package/src/codex/subagent-model-fallback.ts +1 -1
- package/src/codex/sync.ts +6 -1
- package/src/codex/user-identity.ts +16 -5
- package/src/codex/windows-local-appdata.ts +84 -0
- package/src/combos/failover.ts +6 -1
- package/src/combos/index.ts +1 -0
- package/src/combos/resolve.ts +46 -10
- package/src/combos/types.ts +15 -0
- package/src/config/diagnostics.ts +33 -6
- package/src/config/live-reconcile.ts +38 -2
- package/src/config/load-degrade.ts +17 -0
- package/src/config/opencode-wire-upgrade.ts +54 -0
- package/src/config/opencode-zen-upgrade.ts +52 -0
- package/src/config/owner-registry.ts +22 -17
- package/src/config/provider-validation.ts +45 -2
- package/src/config/schema/config-schema.ts +22 -0
- package/src/config/schema/leaf-validators.ts +38 -9
- package/src/config/schema/main-account-hard-lock-fallback.ts +26 -0
- package/src/config/schema/native-reasoning-retention.ts +26 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +608 -284
- 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/mutation-plan.ts +1 -0
- package/src/integrations/registry.ts +8 -0
- package/src/lib/codex-restart-contract.ts +15 -0
- package/src/lib/desktop-upstream-tunnel.ts +18 -53
- package/src/lib/empty-tool-catalog.ts +62 -0
- package/src/lib/errors.ts +19 -3
- package/src/lib/gcp-adc.ts +96 -9
- package/src/lib/gcp-credential-input.ts +12 -0
- package/src/lib/lab-live-route-production.ts +5 -0
- package/src/lib/opaque-proxy-tunnel.ts +217 -0
- package/src/lib/redact.ts +2 -2
- package/src/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 +33 -7
- package/src/oauth/generic-session-affinity.ts +125 -0
- package/src/oauth/index.ts +184 -109
- package/src/oauth/local-token-detect.ts +21 -1
- package/src/oauth/login-cli-errors.ts +44 -0
- package/src/oauth/login-cli.ts +69 -7
- package/src/oauth/login-flow-state.ts +371 -23
- package/src/oauth/meta-muse-errors.ts +41 -0
- package/src/oauth/meta-muse-keychain.ts +52 -0
- package/src/oauth/meta-muse.ts +51 -84
- package/src/oauth/model-discovery-auth.ts +3 -0
- package/src/oauth/pool-settings-capability.ts +4 -2
- package/src/oauth/refresh-flight-retirement.ts +48 -0
- package/src/oauth/session-affinity-key.ts +23 -0
- package/src/oauth/store-anthropic-instance.ts +9 -2
- package/src/oauth/store.ts +12 -5
- package/src/protocols/encoders/adapter-events.ts +5 -4
- package/src/protocols/encoders/messages.ts +9 -6
- package/src/protocols/provider-summary.ts +2 -1
- package/src/providers/api-key-cooldowns.ts +49 -0
- package/src/providers/api-key-resolve.ts +11 -6
- package/src/providers/api-key-response-ownership.ts +57 -0
- package/src/providers/api-key-selection.ts +31 -6
- package/src/providers/api-keys.ts +214 -3
- package/src/providers/claude-cli-usage.ts +96 -0
- package/src/providers/dsh-account-destination.ts +31 -0
- package/src/providers/dsh-account-models.ts +41 -0
- package/src/providers/gcp-credential-cleanup.ts +59 -0
- package/src/providers/key-failover.ts +54 -50
- package/src/providers/key-store.ts +7 -2
- package/src/providers/model-rename-fields.ts +3 -0
- package/src/providers/model-rename-migration.ts +2 -2
- package/src/providers/openai-tiers-destination.ts +11 -2
- package/src/providers/openai-tiers.ts +5 -12
- package/src/providers/opencode-go-transport.ts +3 -1
- package/src/providers/quota/account-cache.ts +6 -5
- package/src/providers/quota/account-row.ts +34 -0
- package/src/providers/quota/antigravity.ts +28 -7
- package/src/providers/quota/dsh-account.ts +70 -0
- package/src/providers/quota/vendor-probes-oauth.ts +1 -93
- package/src/providers/quota-types.ts +2 -0
- package/src/providers/quota.ts +14 -20
- package/src/providers/registry/entries-core.ts +37 -8
- package/src/providers/registry/types.ts +1 -1
- package/src/providers/request-pacing.ts +77 -19
- package/src/providers/routed-config.ts +339 -0
- package/src/providers/service-tier.ts +2 -1
- package/src/quota/window-mapping.ts +5 -2
- package/src/responses/anthropic-progress.ts +34 -0
- package/src/responses/assistant-replay.ts +35 -0
- package/src/responses/compaction-history-framing.ts +65 -0
- package/src/responses/compaction.ts +6 -1
- package/src/responses/incomplete-reason.ts +31 -0
- package/src/responses/parser.ts +50 -5
- package/src/responses/reasoning-envelope.ts +4 -0
- package/src/responses/reasoning-replay-cache.ts +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/router.ts +8 -321
- package/src/routing/health.ts +37 -22
- package/src/routing/history/indexer.ts +6 -5
- package/src/routing/history/ingest.ts +6 -2
- package/src/routing/profile.ts +1 -1
- package/src/server/admission-model-scope.ts +17 -0
- package/src/server/auth-cors.ts +11 -0
- package/src/server/chat-completions.ts +7 -1
- package/src/server/chat-native-reasoning.ts +123 -0
- package/src/server/chat-native-sse.ts +110 -7
- package/src/server/chat-native.ts +76 -17
- package/src/server/claude-messages.ts +55 -414
- package/src/server/effort-policy.ts +1 -1
- package/src/server/images.ts +15 -2
- package/src/server/index/model-alias-id.ts +36 -0
- package/src/server/index/optional-listeners.ts +2 -0
- package/src/server/index/serve-options.ts +9 -1
- package/src/server/index/startup-warnings.ts +2 -0
- package/src/server/index.ts +1 -1
- package/src/server/inference/advisor-diagnostics.ts +31 -0
- package/src/server/management/advisor-routes.ts +15 -4
- package/src/server/management/claude-desktop-picker-routes.ts +1 -1
- package/src/server/management/combo-routes.ts +176 -50
- package/src/server/management/companion-routes.ts +1 -1
- package/src/server/management/config-routes.ts +75 -4
- package/src/server/management/cursor-integration-routes.ts +1 -1
- package/src/server/management/model-routes.ts +11 -6
- package/src/server/management/model-rows.ts +14 -0
- package/src/server/management/native-integration-routes.ts +2 -7
- package/src/server/management/native-reasoning-retention-routes.ts +41 -0
- package/src/server/management/oauth-account-routes.ts +87 -14
- package/src/server/management/provider-gcp-credential-routes.ts +29 -0
- package/src/server/management/provider-key-pacing-routes.ts +31 -0
- package/src/server/management/provider-overwrite-carry.ts +7 -0
- package/src/server/management/provider-routes.ts +25 -30
- package/src/server/management/route-registry.ts +10 -1
- package/src/server/management-api.ts +3 -1
- package/src/server/messages-native-tool-guard.ts +185 -0
- package/src/server/messages-native.ts +101 -21
- package/src/server/messages-shared.ts +431 -0
- package/src/server/relay.ts +1 -1
- package/src/server/request-log-terminal-status.ts +1 -1
- package/src/server/request-log.ts +6 -1
- package/src/server/responses/account-change-state.ts +8 -2
- package/src/server/responses/adapter-continuation.ts +13 -1
- package/src/server/responses/adapter-delivery.ts +19 -6
- package/src/server/responses/adapter-dispatch.ts +19 -2
- package/src/server/responses/advisor-retention.ts +117 -0
- package/src/server/responses/advisor-slot.ts +73 -41
- 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 +2 -0
- package/src/server/responses/core-combo.ts +120 -36
- package/src/server/responses/core-errors.ts +3 -3
- package/src/server/responses/core-lifetime.ts +5 -2
- package/src/server/responses/core-opaque-recovery.ts +35 -0
- package/src/server/responses/core-options.ts +5 -0
- package/src/server/responses/core-pipeline.ts +141 -0
- package/src/server/responses/core-replay.ts +18 -2
- package/src/server/responses/core.ts +2 -115
- package/src/server/responses/fetch-helpers.ts +73 -19
- package/src/server/responses/main-hard-lock-routing.ts +109 -0
- package/src/server/responses/native-response-control.ts +4 -2
- package/src/server/responses/passthrough-delivery.ts +3 -1
- package/src/server/responses/passthrough-dispatch.ts +15 -13
- package/src/server/responses/request-prepare.ts +76 -12
- package/src/server/responses/request-sidecar-auth.ts +2 -2
- package/src/server/responses/request-transport.ts +104 -27
- package/src/server/responses/response-effects.ts +1 -0
- package/src/server/responses/run-turn-execution.ts +96 -30
- package/src/server/responses/sidecar-execution.ts +20 -2
- package/src/server/responses/terminal-guard.ts +2 -16
- package/src/service/auth-preflight.ts +71 -0
- package/src/service/cli.ts +4 -2
- package/src/service/definition.ts +97 -0
- package/src/service/guards.ts +1 -61
- package/src/service/health-sentinel-linux.ts +112 -0
- package/src/service/health-sentinel-main.ts +76 -57
- package/src/service/health.ts +5 -89
- package/src/service/launchd.ts +27 -7
- package/src/service/logging.ts +72 -9
- package/src/service/repair.ts +2 -1
- package/src/service/state.ts +7 -8
- package/src/service/systemd.ts +1 -1
- package/src/service/windows-guarded-manager.ts +2 -1
- package/src/service/windows-ops.ts +1 -1
- package/src/service/windows-scheduler.ts +2 -7
- package/src/service/windows-taskxml.ts +2 -2
- package/src/service/windows-wscript.ts +14 -0
- package/src/service.ts +4 -2
- package/src/sidecar/auth.ts +2 -0
- package/src/types/config.ts +64 -3
- package/src/types/opencode-go.ts +61 -0
- package/src/types/opencode-zen.ts +29 -0
- package/src/types/provider.ts +32 -6
- package/src/types/request.ts +23 -2
- package/src/types/wire.ts +29 -37
- package/src/types.ts +1 -0
- package/src/update/npm-invocation.mjs +12 -2
- package/src/usage/cost.ts +48 -12
- package/src/usage/expected-prices.ts +3 -0
- package/src/usage/ledger-retention.ts +2 -1
- package/src/usage/ledger-scanner.ts +6 -5
- package/src/usage/log.ts +17 -8
- package/src/usage/summary.ts +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/loop.ts +55 -5
- package/src/web-search/progress-stream.ts +4 -1
- package/src/web-search/run-turn-loop.ts +9 -1
- package/gui/dist/assets/App-BjlttVLy.js +0 -58
- package/gui/dist/assets/App-CSJvmjNs.css +0 -1
- package/gui/dist/assets/index-BB6uK6tU.css +0 -1
- package/gui/dist/assets/index-BMssP4k3.js +0 -94
- package/gui/dist/assets/quota-summary-D8M1bc_R.js +0 -1
|
@@ -214,6 +214,13 @@ export function applyProviderCompatPatchFields(
|
|
|
214
214
|
else return { error: "preserveResponsesReasoningContent must be a boolean" };
|
|
215
215
|
touched = true;
|
|
216
216
|
}
|
|
217
|
+
if (Object.hasOwn(rawBody, "inferContextWindow")) {
|
|
218
|
+
const value = rawBody.inferContextWindow;
|
|
219
|
+
if (value === null) delete next.inferContextWindow;
|
|
220
|
+
else if (typeof value === "boolean") next.inferContextWindow = value;
|
|
221
|
+
else return { error: "inferContextWindow must be a boolean or null" };
|
|
222
|
+
touched = true;
|
|
223
|
+
}
|
|
217
224
|
if (Object.hasOwn(rawBody, "reasoningWireFormat")) {
|
|
218
225
|
const value = rawBody.reasoningWireFormat;
|
|
219
226
|
if (value === null) {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { contextTierRecordConfigError, modelCapabilitiesConfigError, mergeModelCapabilities } from "../../config/provider-validation";
|
|
2
2
|
import { DECLARABLE_HOSTED_TOOL_TYPES } from "../../responses/hosted-tool-policy";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { cleanupRemovedGcpCredentials, providerGcpCredentialMarkers, snapshotProviderGcpCredentialMarkers } from "../../providers/gcp-credential-cleanup";
|
|
5
|
+
import { providerGcpCredentialInputResponse, removeProviderWithGcpCredentials } from "./provider-gcp-credential-routes";
|
|
4
6
|
import { readFileSync } from "node:fs";
|
|
5
7
|
import { isDeepStrictEqual } from "node:util";
|
|
6
8
|
import type { CatalogModel } from "../../codex/catalog";
|
|
@@ -78,7 +80,7 @@ import { cachedProviderQuotaIsExhausted } from "../../combos/resolve";
|
|
|
78
80
|
import { probeJevDecisionProvider } from "../../combos/jev";
|
|
79
81
|
import { comboDependsOnProviderRoute } from "./decision-model-validation";
|
|
80
82
|
import { clearKeyCooldowns, forgetApiKeyRotationCursor } from "../../providers/key-failover";
|
|
81
|
-
import {
|
|
83
|
+
import { handleProviderKeyPacingRoutes } from "./provider-key-pacing-routes";
|
|
82
84
|
import { CODEX_FORWARD_BASE_URL, isCanonicalOpenAiForwardProvider } from "../../providers/openai-tiers";
|
|
83
85
|
import { codexAccountNamespaceProviderCollisionError } from "../../codex/account-namespace-match";
|
|
84
86
|
import { clearThreadAccountMap } from "../../codex/routing";
|
|
@@ -853,7 +855,7 @@ function providerRoutingQuota(config: OcxConfig, name: string, now: number): Pro
|
|
|
853
855
|
|
|
854
856
|
// Removing search/MCP windows may leave only a timestamp. That is not inference evidence.
|
|
855
857
|
const percentages = [quota.fiveHourPercent, quota.weeklyPercent, quota.monthlyPercent,
|
|
856
|
-
...(quota.customWindows ?? []).map(window => window.percent)];
|
|
858
|
+
...(quota.customWindows ?? []).filter(window => window.modelId === undefined).map(window => window.percent)];
|
|
857
859
|
const hasPercentage = percentages.some(value => typeof value === "number" && Number.isFinite(value) && value >= 0);
|
|
858
860
|
const credits = quota.creditsUsd;
|
|
859
861
|
const hasCredits = credits !== undefined && Number.isFinite(credits.percent)
|
|
@@ -864,7 +866,7 @@ function providerRoutingQuota(config: OcxConfig, name: string, now: number): Pro
|
|
|
864
866
|
let validUntil = quota.updatedAt + PROVIDER_QUOTA_MAX_AGE_MS;
|
|
865
867
|
if (state === "exhausted") {
|
|
866
868
|
const resets = [quota.fiveHourResetAt, quota.weeklyResetAt, quota.monthlyResetAt,
|
|
867
|
-
...(quota.customWindows ?? []).map(window => window.resetAt)]
|
|
869
|
+
...(quota.customWindows ?? []).filter(window => window.modelId === undefined).map(window => window.resetAt)]
|
|
868
870
|
.filter((reset): reset is number => typeof reset === "number" && Number.isFinite(reset)
|
|
869
871
|
&& reset > now && reset < validUntil)
|
|
870
872
|
.sort((left, right) => left - right);
|
|
@@ -895,21 +897,8 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
895
897
|
});
|
|
896
898
|
}
|
|
897
899
|
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
if (name) {
|
|
901
|
-
if (!isValidProviderName(name) || !hasOwnProvider(config.providers, name)) {
|
|
902
|
-
return jsonResponse({ error: "unknown provider" }, 404);
|
|
903
|
-
}
|
|
904
|
-
return jsonResponse(providerRequestPacingStatus(name, config.providers[name]!));
|
|
905
|
-
}
|
|
906
|
-
return jsonResponse(Object.fromEntries(
|
|
907
|
-
Object.entries(config.providers).map(([providerName, provider]) => [
|
|
908
|
-
providerName,
|
|
909
|
-
providerRequestPacingStatus(providerName, provider),
|
|
910
|
-
]),
|
|
911
|
-
));
|
|
912
|
-
}
|
|
900
|
+
const keyPacingResponse = handleProviderKeyPacingRoutes(ctx);
|
|
901
|
+
if (keyPacingResponse) return keyPacingResponse;
|
|
913
902
|
|
|
914
903
|
if (url.pathname === "/api/providers" && req.method === "GET") {
|
|
915
904
|
return jsonResponse(Object.entries(config.providers).map(([name, p]) => ({
|
|
@@ -922,6 +911,7 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
922
911
|
requestPacing: p.requestPacing,
|
|
923
912
|
models: p.models ?? [],
|
|
924
913
|
contextWindow: p.contextWindow,
|
|
914
|
+
inferContextWindow: p.inferContextWindow,
|
|
925
915
|
modelContextWindows: p.modelContextWindows,
|
|
926
916
|
modelContextTiers: p.modelContextTiers,
|
|
927
917
|
modelCapabilities: p.modelCapabilities,
|
|
@@ -1073,6 +1063,7 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
1073
1063
|
if (resolvedError) return jsonResponse({ error: resolvedError, code: "invalid_provider_destination" }, 400);
|
|
1074
1064
|
}
|
|
1075
1065
|
|
|
1066
|
+
let removedCredentials: string[] = [];
|
|
1076
1067
|
const outcome = mutatePersistedConfig<ProviderEditorMutationValue>(persisted => {
|
|
1077
1068
|
if (!isDeepStrictEqual(providerEditorConfigDTO(persisted), baselineResult.value)) {
|
|
1078
1069
|
return {
|
|
@@ -1095,6 +1086,7 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
1095
1086
|
initializeProviderModelSelection(name, provider, undefined, candidate.config);
|
|
1096
1087
|
}
|
|
1097
1088
|
}
|
|
1089
|
+
removedCredentials = Object.values(persisted.providers).flatMap(providerGcpCredentialMarkers);
|
|
1098
1090
|
persisted.defaultProvider = candidate.config.defaultProvider;
|
|
1099
1091
|
persisted.providers = structuredClone(candidate.config.providers);
|
|
1100
1092
|
persisted.disabledModels = candidate.config.disabledModels;
|
|
@@ -1120,6 +1112,7 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
1120
1112
|
return jsonResponse({ error: outcome.value.error, code: outcome.value.code }, outcome.value.status);
|
|
1121
1113
|
}
|
|
1122
1114
|
|
|
1115
|
+
cleanupRemovedGcpCredentials(removedCredentials);
|
|
1123
1116
|
adoptProviderEditorCandidate(config, outcome.value.config);
|
|
1124
1117
|
reconcileLiveStateStores();
|
|
1125
1118
|
refreshConfigDerivedRegistries(outcome.value.config);
|
|
@@ -1137,16 +1130,15 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
1137
1130
|
return jsonResponse({ success: true, catalogRefresh });
|
|
1138
1131
|
}
|
|
1139
1132
|
|
|
1140
|
-
//
|
|
1141
|
-
// persists — existing providers' real keys are never round-tripped (unlike PUT /api/config,
|
|
1142
|
-
// which would re-save the masked keys from GET). Live routing picks it up immediately.
|
|
1133
|
+
// Provider POST merges directly; existing secrets never round-trip through masked DTOs.
|
|
1143
1134
|
if (url.pathname === "/api/providers" && req.method === "POST") {
|
|
1144
1135
|
let body: { name?: unknown; provider?: unknown; setDefault?: boolean };
|
|
1145
1136
|
try { body = await readManagementJsonBody(req); } catch (error) { rethrowManagementBodyTooLarge(error); return jsonResponse({ error: "invalid JSON body" }, 400); }
|
|
1146
1137
|
const name = typeof body.name === "string" ? body.name.trim() : "";
|
|
1147
1138
|
if (!isPlainRecord(body.provider)) return jsonResponse({ error: "provider must be a plain object" }, 400);
|
|
1148
|
-
|
|
1149
|
-
|
|
1139
|
+
const credentialResponse = providerGcpCredentialInputResponse(body.provider);
|
|
1140
|
+
if (credentialResponse) return credentialResponse;
|
|
1141
|
+
// Reject canonical-row clears before normalization hides their ownership.
|
|
1150
1142
|
if (name === "openai" && (Object.hasOwn(body.provider, "autoReviewModel") || Object.hasOwn(body.provider, "autoReviewModelOverrides"))) {
|
|
1151
1143
|
return jsonResponse({ error: "provider openai must not include autoReviewModel or autoReviewModelOverrides" }, 400);
|
|
1152
1144
|
}
|
|
@@ -1357,7 +1349,7 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
1357
1349
|
const candidate = stripRegistryOnlyStaticHeaders(name, prov);
|
|
1358
1350
|
const previous = Object.getOwnPropertyDescriptor(config.providers, name);
|
|
1359
1351
|
const rollback = pinsOwned ? captureConfigTopLevelRollback(config, ["defaultProvider", "modelDiscovery", "disabledModels"]) : undefined;
|
|
1360
|
-
let validationError: string | undefined;
|
|
1352
|
+
let validationError: string | undefined, removedCredentials: string[] = [];
|
|
1361
1353
|
withConfigMutationLockSync(() => {
|
|
1362
1354
|
validationError = anthropicInstancePublicationError(name, candidate, config.providers[name]);
|
|
1363
1355
|
if (validationError) return;
|
|
@@ -1365,6 +1357,12 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
1365
1357
|
if (submittedModelContextTiers && liveTiers) candidate.modelContextTiers = Object.assign(Object.create(null), liveTiers, candidate.modelContextTiers ?? {});
|
|
1366
1358
|
else if (!submittedModelContextTiers && liveTiers) candidate.modelContextTiers = { ...liveTiers };
|
|
1367
1359
|
else if (!submittedModelContextTiers) delete candidate.modelContextTiers;
|
|
1360
|
+
// Full forms omit the opt-in; preserve the latest value, including a concurrent clear.
|
|
1361
|
+
if (!Object.hasOwn(rawProvider, "inferContextWindow")) {
|
|
1362
|
+
const liveInference = config.providers[name]?.inferContextWindow;
|
|
1363
|
+
if (liveInference === undefined) delete candidate.inferContextWindow;
|
|
1364
|
+
else candidate.inferContextWindow = liveInference;
|
|
1365
|
+
}
|
|
1368
1366
|
const draft = { ...(registrationDraft ?? config), providers: { ...config.providers, [name]: candidate }, ...(body.setDefault === true ? { defaultProvider: name } : {}) };
|
|
1369
1367
|
const validation = validateConfigCandidate(draft); if (!validation.ok) { validationError = validation.error; return; }
|
|
1370
1368
|
try {
|
|
@@ -1375,6 +1373,7 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
1375
1373
|
});
|
|
1376
1374
|
}
|
|
1377
1375
|
}
|
|
1376
|
+
removedCredentials = snapshotProviderGcpCredentialMarkers(name, config.providers[name]);
|
|
1378
1377
|
config.providers[name] = candidate;
|
|
1379
1378
|
if (body.setDefault === true) config.defaultProvider = name;
|
|
1380
1379
|
(deps.saveConfigPreservingClaudeCode ?? save)(config);
|
|
@@ -1388,6 +1387,7 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
1388
1387
|
}
|
|
1389
1388
|
});
|
|
1390
1389
|
if (validationError !== undefined) return jsonResponse({ error: validationError }, 400);
|
|
1390
|
+
cleanupRemovedGcpCredentials(removedCredentials);
|
|
1391
1391
|
reconcileLiveStateStores();
|
|
1392
1392
|
if (prov.apiKey && prov.apiKeyPool) {
|
|
1393
1393
|
const { addProviderApiKey } = await import("../../providers/api-keys");
|
|
@@ -1848,12 +1848,7 @@ export async function handleProviderRoutes(ctx: ManagementContext): Promise<Resp
|
|
|
1848
1848
|
const { saveConfigPreservingClaudeCode: save } = await import("../../config");
|
|
1849
1849
|
// Deleting still succeeds; the response names the shadow-call target left without a provider.
|
|
1850
1850
|
const shadowDependency = shadowInterceptProviderDependency(config, name);
|
|
1851
|
-
|
|
1852
|
-
delete config.providers[name];
|
|
1853
|
-
const { dropProviderCustomModels } = await import("../../providers/provider-id-rewrite");
|
|
1854
|
-
const droppedCustomModels = dropProviderCustomModels(config, name);
|
|
1855
|
-
forgetProviderContextCap(config, name);
|
|
1856
|
-
save(config);
|
|
1851
|
+
const droppedCustomModels = removeProviderWithGcpCredentials(config, name, fallbackDefault, save);
|
|
1857
1852
|
await replaceProviderAccountSet(name, null);
|
|
1858
1853
|
reconcileLiveStateStores();
|
|
1859
1854
|
const { clearModelCache: clearCache } = await import("../../codex/model-cache");
|
|
@@ -97,8 +97,12 @@ export const MANAGEMENT_ROUTES: readonly ManagementRoute[] = [
|
|
|
97
97
|
// server/management-api
|
|
98
98
|
{ method: "POST", path: "/api/stop", module: "server/management-api", mutates: true },
|
|
99
99
|
// codex/auth-api
|
|
100
|
+
{ method: "GET", path: "/api/codex-auth/preserve-signed-in", module: "codex/auth-api/routes", mutates: false },
|
|
101
|
+
{ method: "PUT", path: "/api/codex-auth/preserve-signed-in", module: "codex/auth-api/routes", mutates: true },
|
|
100
102
|
{ method: "DELETE", path: "/api/codex-auth/accounts", module: "codex/auth-api/routes", mutates: true },
|
|
101
103
|
{ method: "GET", path: "/api/codex-auth/accounts", module: "codex/auth-api/routes", mutates: false },
|
|
104
|
+
{ method: "GET", path: "/api/codex-auth/accounts/use-remaining", module: "codex/auth-api/routes", mutates: false },
|
|
105
|
+
{ method: "PUT", path: "/api/codex-auth/accounts/use-remaining", module: "codex/auth-api/routes", mutates: true },
|
|
102
106
|
{ method: "GET", path: "/api/codex-auth/active", module: "codex/auth-api/routes", mutates: false },
|
|
103
107
|
{ method: "GET", path: "/api/codex-auth/login-status", module: "codex/auth-api/routes", mutates: false },
|
|
104
108
|
{ method: "GET", path: "/api/codex-auth/quota", module: "codex/auth-api/routes", mutates: false },
|
|
@@ -217,8 +221,11 @@ export const MANAGEMENT_ROUTES: readonly ManagementRoute[] = [
|
|
|
217
221
|
{ method: "POST", path: "/api/combos/decision-test", module: "server/management/decision-routes", mutates: true },
|
|
218
222
|
// server/management/config-routes
|
|
219
223
|
{ method: "GET", path: "/api/config", module: "server/management/config-routes", mutates: false },
|
|
224
|
+
{ method: "GET", path: "/api/auto-review-settings", module: "server/management/config-routes", mutates: false },
|
|
220
225
|
{ method: "GET", path: "/api/diagnostics/project-config", module: "server/management/config-routes", mutates: false },
|
|
221
226
|
{ method: "GET", path: "/api/settings", module: "server/management/config-routes", mutates: false },
|
|
227
|
+
{ method: "GET", path: "/api/native-reasoning-retention", module: "server/management/native-reasoning-retention-routes", mutates: false, exempt: { reason: "local-transport", why: "The CLI reads the same nativeReasoningRetention policy through ocx config get; no separate HTTP-specific settings verb is required." } },
|
|
228
|
+
{ method: "PUT", path: "/api/native-reasoning-retention", module: "server/management/native-reasoning-retention-routes", mutates: true, exempt: { reason: "deferred-verb", why: "A dedicated live CLI verb for this policy is deferred to native-reasoning-retention-live-cli; ocx config set/unset currently edits the same block on disk without live convergence.", owner: "native-reasoning-retention-live-cli", ownerDoc: "structure/transports/native-reasoning-retention.md" } },
|
|
222
229
|
{ method: "GET", path: "/api/shadow-call-settings", module: "server/management/config-routes", mutates: false },
|
|
223
230
|
{ method: "GET", path: "/api/sidecar-settings", module: "server/management/config-routes", mutates: false },
|
|
224
231
|
{ method: "GET", path: "/api/startup-health", module: "server/management/config-routes", mutates: false },
|
|
@@ -231,6 +238,7 @@ export const MANAGEMENT_ROUTES: readonly ManagementRoute[] = [
|
|
|
231
238
|
{ method: "POST", path: "/api/windows-tray", module: "server/management/config-routes", mutates: true },
|
|
232
239
|
{ method: "PUT", path: "/api/config", module: "server/management/config-routes", mutates: true, exempt: { reason: "disabled", why: "Returns 405 by design; provider changes go through POST /api/providers." } },
|
|
233
240
|
{ method: "PUT", path: "/api/settings", module: "server/management/config-routes", mutates: true },
|
|
241
|
+
{ method: "PUT", path: "/api/auto-review-settings", module: "server/management/config-routes", mutates: true },
|
|
234
242
|
{ method: "PUT", path: "/api/shadow-call-settings", module: "server/management/config-routes", mutates: true },
|
|
235
243
|
{ method: "PUT", path: "/api/sidecar-settings", module: "server/management/config-routes", mutates: true },
|
|
236
244
|
// server/management/integration-routes
|
|
@@ -277,6 +285,7 @@ export const MANAGEMENT_ROUTES: readonly ManagementRoute[] = [
|
|
|
277
285
|
{ method: "GET", path: "/api/usage/timeline", module: "server/management/usage-timeline-routes", mutates: false },
|
|
278
286
|
// server/management/advisor-routes
|
|
279
287
|
{ method: "GET", path: "/api/advisor/settings", module: "server/management/advisor-routes", mutates: false },
|
|
288
|
+
{ method: "GET", path: "/api/advisor/activity", module: "server/management/advisor-routes", mutates: false },
|
|
280
289
|
{ method: "PUT", path: "/api/advisor/settings", module: "server/management/advisor-routes", mutates: true },
|
|
281
290
|
// server/management/companion-routes
|
|
282
291
|
{ method: "POST", path: "/api/companion/open-in-browser", module: "server/management/companion-routes", mutates: true, exempt: { reason: "browser-navigation", why: "Opens the companion's current view in the system browser under normal management admission; the CLI reads the underlying settings and usage directly." } },
|
|
@@ -360,7 +369,7 @@ export const MANAGEMENT_ROUTES: readonly ManagementRoute[] = [
|
|
|
360
369
|
{ method: "GET", path: "/api/provider-context-caps", module: "server/management/provider-routes", mutates: false },
|
|
361
370
|
{ method: "GET", path: "/api/provider-presets", module: "server/management/provider-routes", mutates: false },
|
|
362
371
|
{ method: "GET", path: "/api/provider-quotas", module: "server/management/provider-routes", mutates: false },
|
|
363
|
-
{ method: "GET", path: "/api/provider-request-pacing", module: "server/management/provider-routes", mutates: false },
|
|
372
|
+
{ method: "GET", path: "/api/provider-request-pacing", module: "server/management/provider-key-pacing-routes", mutates: false },
|
|
364
373
|
{ method: "GET", path: "/api/providers", module: "server/management/provider-routes", mutates: false },
|
|
365
374
|
{ method: "PATCH", path: "/api/providers", module: "server/management/provider-routes", mutates: true },
|
|
366
375
|
{ method: "POST", path: "/api/providers", module: "server/management/provider-routes", mutates: true },
|
|
@@ -59,6 +59,7 @@ import { applySystemEnvToggle } from "./system-env";
|
|
|
59
59
|
|
|
60
60
|
import type { ManagementApiDeps } from "./management/context";
|
|
61
61
|
import { handleConfigRoutes } from "./management/config-routes";
|
|
62
|
+
import { handleNativeReasoningRetentionRoutes } from "./management/native-reasoning-retention-routes";
|
|
62
63
|
import { handleLogsUsageRoutes } from "./management/logs-usage-routes";
|
|
63
64
|
import { handleStorageLogGuardRoutes } from "./management/storage-log-guard-routes";
|
|
64
65
|
import { handleRequestHistoryRoutes } from "./management/request-history-routes";
|
|
@@ -344,6 +345,7 @@ export async function handleManagementAPI(
|
|
|
344
345
|
?? (await handleLinkRoutesOnDemand(ctx))
|
|
345
346
|
?? (await handleRemoteWorkspaceRoutesOnDemand(ctx))
|
|
346
347
|
?? (await handleConfigRoutes(ctx))
|
|
348
|
+
?? (await handleNativeReasoningRetentionRoutes(ctx))
|
|
347
349
|
?? (await handleStorageLogGuardRoutes(ctx))
|
|
348
350
|
?? (await handleLogsUsageRoutes(ctx))
|
|
349
351
|
?? (await handleRequestHistoryRoutes(ctx))
|
|
@@ -519,7 +521,7 @@ export async function handleManagementAPI(
|
|
|
519
521
|
const { handleMainDeviceReauthAPI } = await import("../codex/main-device-reauth-api");
|
|
520
522
|
return handleMainDeviceReauthAPI(req, url, config);
|
|
521
523
|
}
|
|
522
|
-
const { handleCodexAuthAPI } = await import("../codex/auth-api");
|
|
524
|
+
const { handleCodexAuthAPI } = await import("../codex/auth-api/routes");
|
|
523
525
|
const { ConfigMutationLockError } = await import("../config");
|
|
524
526
|
const { CodexCredentialRefreshLockTimeoutError } = await import("../codex/account-store");
|
|
525
527
|
try {
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { anthropicErrorBody } from "../claude/outbound";
|
|
2
|
+
import { redactSecretString } from "../lib/redact";
|
|
3
|
+
import { sseFieldValue } from "../lib/sse-decoder";
|
|
4
|
+
import { TRANSLATOR_MAX_SSE_EVENT_BYTES, type TranslatorBudget } from "../lib/translator-budget";
|
|
5
|
+
import { UNDECLARED_TOOL_CALL_ERROR_CODE, undeclaredToolCallMessage } from "./responses-undeclared-tool-guard";
|
|
6
|
+
import { createSseBlockBuffer, sseDataPayload } from "./sse-payload-rewrite";
|
|
7
|
+
|
|
8
|
+
type Rec = Record<string, unknown>;
|
|
9
|
+
const isRec = (value: unknown): value is Rec => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
10
|
+
const NO_DECLARED_TOOLS: ReadonlySet<string> = new Set();
|
|
11
|
+
|
|
12
|
+
/** Inline system-message definitions extend an otherwise empty native catalog. */
|
|
13
|
+
export function nativeMessagesInlineToolNames(body: Rec): ReadonlySet<string> {
|
|
14
|
+
const definitions = new Set<string>();
|
|
15
|
+
const active = new Set<string>();
|
|
16
|
+
if (!Array.isArray(body.messages)) return active;
|
|
17
|
+
for (const message of body.messages) {
|
|
18
|
+
if (!isRec(message) || message.role !== "system" || !Array.isArray(message.content)) continue;
|
|
19
|
+
for (const block of message.content) {
|
|
20
|
+
if (!isRec(block) || !isRec(block.tool)) continue;
|
|
21
|
+
const tool = block.tool;
|
|
22
|
+
if (block.type === "tool_addition" && tool.type === "tool_definition" && isRec(tool.definition)
|
|
23
|
+
&& typeof tool.definition.name === "string" && tool.definition.name.length > 0) {
|
|
24
|
+
definitions.add(tool.definition.name);
|
|
25
|
+
active.add(tool.definition.name);
|
|
26
|
+
} else if (tool.type === "tool_reference" && typeof tool.name === "string") {
|
|
27
|
+
if (block.type === "tool_addition" && definitions.has(tool.name)) active.add(tool.name);
|
|
28
|
+
// Like native admission, only a permanent removal withdraws a declaration.
|
|
29
|
+
if (block.type === "tool_removal" && (message.clear_at === undefined || message.clear_at === "never")) active.delete(tool.name);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return active;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Inspect only native message content; opaque extension fields do not carry client calls. */
|
|
37
|
+
export function nativeMessagesClientToolName(value: unknown, declared: ReadonlySet<string> = NO_DECLARED_TOOLS): string | undefined {
|
|
38
|
+
if (!isRec(value)) return undefined;
|
|
39
|
+
const content = Array.isArray(value.content) ? value.content : [];
|
|
40
|
+
const block = content.find(item => isRec(item) && item.type === "tool_use"
|
|
41
|
+
&& (typeof item.name !== "string" || !declared.has(item.name)));
|
|
42
|
+
if (isRec(block)) return typeof block.name === "string" ? block.name : "<unnamed>";
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Used only when the current native request explicitly declares no client tools. */
|
|
47
|
+
export function guardNativeMessagesEmptyToolCatalog(
|
|
48
|
+
source: ReadableStream<Uint8Array>,
|
|
49
|
+
budget: TranslatorBudget,
|
|
50
|
+
onRefusal: (message: string) => void,
|
|
51
|
+
declared: ReadonlySet<string> = NO_DECLARED_TOOLS,
|
|
52
|
+
): ReadableStream<Uint8Array> {
|
|
53
|
+
const reader = source.getReader();
|
|
54
|
+
const decoder = new TextDecoder();
|
|
55
|
+
const encoder = new TextEncoder();
|
|
56
|
+
const scope = { kind: "live_transient" as const };
|
|
57
|
+
const buffer = createSseBlockBuffer(budget, bytes => {
|
|
58
|
+
if (bytes > TRANSLATOR_MAX_SSE_EVENT_BYTES) throw new Error("upstream SSE event exceeded the safe limit");
|
|
59
|
+
});
|
|
60
|
+
// Hosted tools share input_json_delta with client tools, but cannot grant client authority.
|
|
61
|
+
const permittedInputBlocks = new Set<number>();
|
|
62
|
+
let queuedBytes = 0;
|
|
63
|
+
let settled = false;
|
|
64
|
+
const releaseQueued = () => {
|
|
65
|
+
budget.releaseRetained(queuedBytes, scope);
|
|
66
|
+
queuedBytes = 0;
|
|
67
|
+
};
|
|
68
|
+
const clear = () => {
|
|
69
|
+
buffer.clear();
|
|
70
|
+
budget.releaseRetained(permittedInputBlocks.size * 32, scope);
|
|
71
|
+
permittedInputBlocks.clear();
|
|
72
|
+
};
|
|
73
|
+
const enqueue = (controller: ReadableStreamDefaultController<Uint8Array>, text: string) => {
|
|
74
|
+
const bytes = Buffer.byteLength(text);
|
|
75
|
+
const reservation = budget.reserveTransient(bytes, scope);
|
|
76
|
+
try {
|
|
77
|
+
controller.enqueue(encoder.encode(text));
|
|
78
|
+
reservation.commitRetained();
|
|
79
|
+
queuedBytes += bytes;
|
|
80
|
+
} catch (error) {
|
|
81
|
+
reservation.release();
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
const cancelReader = (reason?: unknown) => {
|
|
86
|
+
void reader.cancel(reason).catch(() => {});
|
|
87
|
+
};
|
|
88
|
+
const process = (controller: ReadableStreamDefaultController<Uint8Array>, block: string, delimiter: string) => {
|
|
89
|
+
const payload = sseDataPayload(block);
|
|
90
|
+
let data: unknown;
|
|
91
|
+
let terminal = false;
|
|
92
|
+
try { data = payload === null ? undefined : JSON.parse(payload); } catch {
|
|
93
|
+
// The buffered Messages collector concatenates data lines without separators. Inspect
|
|
94
|
+
// that representation too, so an invalid SSE split cannot become a valid folded tool call.
|
|
95
|
+
try { data = payload === null ? undefined : JSON.parse(payload.replace(/\n/g, "")); } catch { /* preserve opaque frames */ }
|
|
96
|
+
}
|
|
97
|
+
if (isRec(data)) {
|
|
98
|
+
const eventName = block.split(/\r\n|\r|\n/).map(line => sseFieldValue(line, "event"))
|
|
99
|
+
.filter(value => value !== null).at(-1)?.trim();
|
|
100
|
+
// Stream clients use the payload type; the buffered collector uses the event field.
|
|
101
|
+
const isEvent = (type: string) => data.type === type || eventName === type;
|
|
102
|
+
terminal = isEvent("message_stop") || isEvent("error");
|
|
103
|
+
let name = isEvent("message_start") ? nativeMessagesClientToolName(data.message, declared) : undefined;
|
|
104
|
+
if (isEvent("content_block_start") && isRec(data.content_block) && data.content_block.type === "tool_use"
|
|
105
|
+
&& (typeof data.content_block.name !== "string" || !declared.has(data.content_block.name))) {
|
|
106
|
+
name ??= typeof data.content_block.name === "string" ? data.content_block.name : "<unnamed>";
|
|
107
|
+
}
|
|
108
|
+
const index = typeof data.index === "number" && Number.isSafeInteger(data.index) ? data.index : undefined;
|
|
109
|
+
if (isEvent("content_block_delta") && isRec(data.delta) && data.delta.type === "input_json_delta"
|
|
110
|
+
&& (index === undefined || !permittedInputBlocks.has(index))) name ??= "<unnamed>";
|
|
111
|
+
if (name !== undefined) {
|
|
112
|
+
clear();
|
|
113
|
+
const message = redactSecretString(undeclaredToolCallMessage(name));
|
|
114
|
+
onRefusal(message);
|
|
115
|
+
enqueue(controller, `event: error\ndata: ${JSON.stringify(anthropicErrorBody(502, message, "api_error", UNDECLARED_TOOL_CALL_ERROR_CODE))}\n\n`);
|
|
116
|
+
settled = true;
|
|
117
|
+
cancelReader(new Error(message));
|
|
118
|
+
controller.close();
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (isEvent("content_block_start") && index !== undefined && isRec(data.content_block)) {
|
|
122
|
+
if (permittedInputBlocks.delete(index)) budget.releaseRetained(32, scope);
|
|
123
|
+
if (data.content_block.type === "server_tool_use" || data.content_block.type === "mcp_tool_use"
|
|
124
|
+
|| data.content_block.type === "tool_use" && typeof data.content_block.name === "string" && declared.has(data.content_block.name)) {
|
|
125
|
+
budget.chargeRetained(32, scope);
|
|
126
|
+
permittedInputBlocks.add(index);
|
|
127
|
+
}
|
|
128
|
+
} else if (isEvent("content_block_stop") && index !== undefined && permittedInputBlocks.delete(index)) {
|
|
129
|
+
budget.releaseRetained(32, scope);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
enqueue(controller, block + delimiter);
|
|
133
|
+
if (terminal) {
|
|
134
|
+
settled = true;
|
|
135
|
+
clear();
|
|
136
|
+
cancelReader();
|
|
137
|
+
controller.close();
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
return new ReadableStream<Uint8Array>({
|
|
141
|
+
async pull(controller) {
|
|
142
|
+
releaseQueued();
|
|
143
|
+
if (settled) return;
|
|
144
|
+
try {
|
|
145
|
+
for (;;) {
|
|
146
|
+
const block = buffer.next();
|
|
147
|
+
if (block) {
|
|
148
|
+
buffer.compact();
|
|
149
|
+
process(controller, block.block, block.delimiter);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const { done, value } = await reader.read();
|
|
153
|
+
if (settled) return;
|
|
154
|
+
if (!done) {
|
|
155
|
+
buffer.append(decoder.decode(value, { stream: true }));
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
buffer.append(decoder.decode());
|
|
159
|
+
const tail = buffer.tail();
|
|
160
|
+
if (tail) process(controller, tail, "");
|
|
161
|
+
if (!settled) {
|
|
162
|
+
settled = true;
|
|
163
|
+
clear();
|
|
164
|
+
controller.close();
|
|
165
|
+
}
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
} catch (error) {
|
|
169
|
+
if (settled) return;
|
|
170
|
+
settled = true;
|
|
171
|
+
clear();
|
|
172
|
+
cancelReader(error);
|
|
173
|
+
controller.error(error);
|
|
174
|
+
} finally {
|
|
175
|
+
if (settled) releaseQueued();
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
cancel(reason) {
|
|
179
|
+
settled = true;
|
|
180
|
+
clear();
|
|
181
|
+
releaseQueued();
|
|
182
|
+
return reader.cancel(reason);
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
}
|