@bitkyc08/opencodex 2.88.0 → 2.90.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 +11 -1
- package/bin/ocx.mjs +15 -4
- package/gui/dist/assets/App-DgxkE6Md.js +58 -0
- package/gui/dist/assets/App-dnkJ85te.css +1 -0
- package/gui/dist/assets/{Tray-B5bC7AWL.js → Tray-DbTNc_88.js} +1 -1
- package/gui/dist/assets/{index-B5bsvxor.js → index-TmaHa54u.js} +15 -15
- package/gui/dist/assets/quota-summary-B16x5DZZ.js +1 -0
- package/gui/dist/index.html +1 -1
- package/package.json +2 -2
- package/src/adapters/anthropic/cache-retention.ts +117 -0
- package/src/adapters/anthropic/passthrough.ts +7 -4
- package/src/adapters/anthropic.ts +10 -3
- package/src/adapters/base.ts +4 -0
- package/src/adapters/claude-cli/adapter.ts +13 -7
- package/src/adapters/claude-cli/usage-admission.ts +14 -2
- package/src/adapters/coding-agent/protocol.ts +6 -0
- package/src/adapters/coding-agent/turn.ts +14 -4
- package/src/adapters/cursor/protobuf-request.ts +3 -0
- package/src/adapters/google-http.ts +3 -2
- package/src/adapters/google-progress-history.ts +19 -1
- package/src/adapters/google-vertex-endpoint.ts +67 -0
- package/src/adapters/google.ts +62 -37
- package/src/adapters/image.ts +5 -3
- package/src/adapters/input-media-guard.ts +5 -2
- package/src/adapters/kiro-refusal.ts +41 -1
- package/src/adapters/ollama-native.ts +1 -0
- package/src/adapters/openai-chat/messages.ts +34 -4
- package/src/adapters/openai-chat/passthrough.ts +37 -4
- package/src/adapters/openai-chat/reasoning-replay.ts +98 -13
- package/src/adapters/openai-chat/serialized-tool-call-content.ts +95 -134
- package/src/adapters/openai-chat/serialized-tool-call-markup.ts +108 -0
- package/src/adapters/openai-chat/standalone-bare-tool-calls.ts +136 -0
- package/src/adapters/openai-chat.ts +7 -6
- package/src/adapters/openai-responses/compaction-search-history.ts +27 -0
- package/src/adapters/openai-responses/opengateway-input.ts +139 -0
- package/src/adapters/openai-responses/passthrough.ts +22 -13
- package/src/adapters/openai-responses/reasoning.ts +68 -1
- package/src/adapters/openai-responses/transport.ts +13 -1
- package/src/adapters/registry.ts +1 -1
- package/src/adapters/upstream-http-error.ts +3 -2
- package/src/adapters/zed.ts +7 -4
- package/src/bridge/internal.ts +11 -1
- package/src/bridge/response-json.ts +2 -2
- package/src/bridge/sse.ts +10 -4
- package/src/claude/desktop-3p-library.ts +43 -3
- package/src/claude/desktop-3p.ts +20 -10
- package/src/claude/desktop-picker-profile.ts +18 -9
- package/src/claude/desktop-remote-store-io.ts +2 -2
- package/src/claude/inbound.ts +12 -26
- package/src/claude/outbound.ts +5 -0
- package/src/cli/account-api.ts +13 -1
- package/src/cli/account-extended.ts +17 -5
- package/src/cli/account-key-quota.ts +4 -1
- package/src/cli/account.ts +1 -1
- package/src/cli/capabilities-agents-routing.ts +45 -8
- package/src/cli/capabilities-base.ts +5 -5
- package/src/cli/capabilities-provider-models.ts +5 -4
- package/src/cli/combo-result.ts +8 -2
- package/src/cli/combo.ts +17 -0
- package/src/cli/ensure-desktop-cold-start.ts +2 -2
- package/src/cli/help.ts +1 -1
- package/src/cli/message-command.ts +4 -1
- package/src/cli/message-remote-args.ts +69 -0
- package/src/cli/message-remote-command.ts +77 -0
- package/src/cli/provider-runtime.ts +69 -1
- package/src/cli/registry.ts +3 -3
- package/src/codex/account-lifecycle.ts +19 -6
- package/src/codex/account-pause.ts +6 -1
- package/src/codex/account-runtime-state.ts +5 -0
- package/src/codex/account-store.ts +55 -9
- package/src/codex/account-usability.ts +4 -1
- package/src/codex/auth-api/account-list.ts +13 -5
- package/src/codex/auth-api/account-pause-group.ts +2 -22
- package/src/codex/auth-api/login-collision.ts +59 -0
- package/src/codex/auth-api/login-flow.ts +12 -7
- package/src/codex/auth-api/main-account-probe.ts +3 -2
- package/src/codex/auth-api/pool-mode-gate.ts +81 -13
- package/src/codex/auth-collision.ts +31 -4
- package/src/codex/auth-context.ts +57 -10
- package/src/codex/catalog/aggregation.ts +15 -0
- package/src/codex/catalog/auto-review.ts +2 -1
- package/src/codex/catalog/build-entries.ts +17 -7
- package/src/codex/catalog/derive-entry.ts +3 -1
- package/src/codex/catalog/metadata.ts +2 -0
- package/src/codex/catalog/model-visibility.ts +0 -2
- package/src/codex/catalog/ownership.ts +71 -0
- package/src/codex/catalog/parsing.ts +12 -3
- package/src/codex/catalog/retained-sync.ts +2 -1
- package/src/codex/catalog/routed-removal.ts +4 -4
- package/src/codex/cli-update-plan.ts +18 -3
- package/src/codex/collision-identity.ts +78 -0
- package/src/codex/convergence.ts +2 -1
- package/src/codex/low-quota-desktop-notifier.ts +125 -0
- package/src/codex/low-quota-protection.ts +57 -31
- package/src/codex/main-account-cache.ts +77 -3
- package/src/codex/main-account-hard-lock.ts +14 -0
- package/src/codex/main-account.ts +6 -2
- package/src/codex/native-residue.ts +2 -3
- package/src/codex/quota.ts +26 -2
- package/src/codex/routing/cooldown-math.ts +5 -0
- package/src/codex/routing/failure-window.ts +61 -9
- package/src/codex/routing.ts +4 -3
- package/src/codex/shim-diagnostics.ts +2 -1
- package/src/codex/shim-fingerprint.ts +11 -2
- package/src/codex/shim-templates.ts +16 -5
- package/src/codex/shim-windows-refresh.ts +224 -0
- package/src/codex/shim.ts +45 -21
- package/src/combos/failover.ts +10 -2
- package/src/combos/jev-model-backend.ts +30 -7
- package/src/combos/jev-model-request.ts +20 -0
- package/src/combos/jev-quota-activation.ts +153 -0
- package/src/combos/jev-quota-config.ts +62 -0
- package/src/combos/jev-quota-route.ts +26 -0
- package/src/combos/jev-quota.ts +88 -0
- package/src/combos/jev.ts +25 -12
- package/src/combos/types.ts +27 -0
- package/src/compatibility/index.ts +3 -0
- package/src/compatibility/openai-responses.ts +8 -1
- package/src/compatibility/opengateway-responses.ts +61 -0
- package/src/config/account-auto-pause-revisions.ts +136 -0
- package/src/config/diagnostics.ts +13 -0
- package/src/config/live-reconcile.ts +9 -0
- package/src/config/persist-unlocked.ts +10 -1
- package/src/config/provider-validation.ts +7 -2
- package/src/config/removed-opencode-free.ts +135 -0
- package/src/config/schema/account-auto-pause.ts +14 -0
- package/src/config/schema/config-schema.ts +15 -0
- package/src/config/schema/leaf-validators.ts +7 -0
- package/src/config.ts +10 -3
- package/src/generated/compatibility-version.json +476 -216
- package/src/generated/model-metadata.ts +1 -1
- package/src/images/loop.ts +34 -11
- package/src/lib/account-auto-pause-response.ts +16 -0
- package/src/lib/bun-runtime.ts +18 -1
- package/src/lib/bun-transpiler-cache-scope.d.mts +8 -0
- package/src/lib/bun-transpiler-cache-scope.mjs +82 -0
- package/src/lib/decision-quota-sink.ts +76 -0
- package/src/lib/provider-admission-wait.ts +14 -0
- package/src/lib/service-secrets.ts +37 -2
- package/src/lib/state-store-registrations.ts +25 -0
- package/src/lib/translator-budget.ts +1 -1
- package/src/lib/upstream-retry.ts +15 -0
- package/src/link/ssh-argv.ts +3 -0
- package/src/messaging/envelope.ts +6 -3
- package/src/messaging/remote-auth.ts +83 -0
- package/src/messaging/remote-bridge.ts +270 -0
- package/src/messaging/remote-contract.ts +108 -0
- package/src/messaging/remote-enrollment-recovery.ts +59 -0
- package/src/messaging/remote-enrollment.ts +244 -0
- package/src/messaging/remote-envelope.ts +40 -0
- package/src/messaging/remote-files.ts +117 -0
- package/src/messaging/remote-owner.ts +169 -0
- package/src/messaging/remote-ports.ts +56 -0
- package/src/messaging/remote-process.ts +131 -0
- package/src/messaging/remote-replies.ts +98 -0
- package/src/messaging/remote-rpc-admission.ts +32 -0
- package/src/messaging/remote-send.ts +72 -0
- package/src/messaging/remote-store.ts +111 -0
- package/src/messaging/remote-tunnels.ts +65 -0
- package/src/messaging/rpc.ts +13 -1
- package/src/oauth/account-auto-pause-policy.ts +66 -0
- package/src/oauth/account-auto-pause.ts +171 -0
- package/src/oauth/account-import/google-antigravity-adapter.ts +38 -2
- package/src/oauth/account-import/parser.ts +15 -2
- package/src/oauth/account-import/types.ts +7 -0
- package/src/oauth/account-quota-rank.ts +2 -1
- package/src/oauth/anthropic-account-refusal.ts +6 -0
- package/src/oauth/anthropic-model-family.ts +8 -0
- package/src/oauth/anthropic-model-quota.ts +2 -6
- package/src/oauth/api-key-pool-preserve.ts +37 -0
- package/src/oauth/chatgpt-shared.ts +10 -1
- package/src/oauth/dsh.ts +45 -2
- package/src/oauth/index.ts +5 -35
- package/src/oauth/kiro.ts +44 -1
- package/src/oauth/login-flow-state.ts +11 -2
- package/src/oauth/store.ts +35 -8
- package/src/oauth/types.ts +2 -0
- package/src/plugins/upstream-hooks.ts +14 -1
- package/src/providers/api-key-pause.ts +32 -0
- package/src/providers/api-key-resolve.ts +10 -4
- package/src/providers/api-key-selection.ts +6 -1
- package/src/providers/api-keys.ts +8 -4
- package/src/providers/claude-cli-usage.ts +25 -5
- package/src/providers/initial-model-selection.ts +2 -2
- package/src/providers/key-failover.ts +21 -13
- package/src/providers/key-store.ts +8 -3
- package/src/providers/mistral-transport.ts +207 -0
- package/src/providers/model-rename-fields.ts +1 -0
- package/src/providers/openai-sidecar.ts +5 -0
- package/src/providers/opencode-zen-rate-limit.ts +6 -72
- package/src/providers/quota/account-cache.ts +15 -1
- package/src/providers/quota/anthropic-account-quota.ts +20 -7
- package/src/providers/quota/anthropic-cooldown-recovery.ts +22 -4
- package/src/providers/quota/anthropic-decision-windows.ts +47 -0
- package/src/providers/quota/antigravity.ts +45 -8
- package/src/providers/quota/failure-codes.ts +18 -0
- package/src/providers/quota/report-cache.ts +14 -0
- package/src/providers/quota/vendor-probes-oauth.ts +15 -6
- package/src/providers/quota-decision-publication.ts +77 -0
- package/src/providers/quota-decision-snapshot.ts +145 -0
- package/src/providers/quota-routing-cache.ts +4 -1
- package/src/providers/quota-types.ts +6 -1
- package/src/providers/quota.ts +8 -3
- package/src/providers/registry/entries-extended.ts +14 -53
- package/src/providers/registry/model-seeds.ts +1 -1
- package/src/providers/registry.ts +1 -2
- package/src/providers/routed-config.ts +1 -1
- package/src/providers/stale-vision-classification-migration.ts +1 -1
- package/src/providers/xai-transport.ts +2 -1
- package/src/responses/audio.ts +46 -0
- package/src/responses/input-media.ts +37 -15
- package/src/responses/parser-content.ts +22 -27
- package/src/responses/parser.ts +18 -10
- package/src/responses/reasoning-retention.ts +218 -11
- package/src/responses/schema.ts +3 -4
- package/src/responses/web-search-replay.ts +147 -0
- package/src/routing/compatibility/behavior.ts +4 -1
- package/src/server/auth-cors.ts +7 -0
- package/src/server/background-lifecycle.ts +2 -1
- package/src/server/chat-native.ts +36 -20
- package/src/server/claude-messages.ts +2 -1
- package/src/server/index/optional-listeners.ts +6 -0
- package/src/server/index/serve-options.ts +8 -2
- package/src/server/index/websocket-handler.ts +10 -0
- package/src/server/lifecycle.ts +17 -1
- package/src/server/management/combo-routes.ts +17 -2
- package/src/server/management/oauth-account-routes.ts +12 -2
- package/src/server/management/provider-completion-test.ts +289 -0
- package/src/server/management/provider-routes.ts +9 -1
- package/src/server/management/route-registry.ts +2 -0
- package/src/server/messages-native.ts +29 -4
- package/src/server/messages-shared.ts +4 -1
- package/src/server/request-log.ts +23 -9
- package/src/server/responses/account-auto-pause-recovery.ts +26 -0
- package/src/server/responses/adapter-continuation.ts +2 -2
- package/src/server/responses/adapter-dispatch.ts +79 -30
- package/src/server/responses/codex-ws-exchange.ts +3 -0
- package/src/server/responses/codex-ws-metadata.ts +44 -0
- package/src/server/responses/codex-ws-session.ts +21 -1
- package/src/server/responses/combo-session-recall.ts +25 -0
- package/src/server/responses/combo-stream-preflight.ts +10 -3
- package/src/server/responses/combo-tool-loop-pin.ts +70 -0
- package/src/server/responses/compact.ts +10 -5
- package/src/server/responses/context-overflow.ts +141 -2
- package/src/server/responses/core-auth.ts +20 -0
- package/src/server/responses/core-codex-account.ts +28 -15
- package/src/server/responses/core-combo.ts +34 -5
- package/src/server/responses/core-opaque-recovery.ts +20 -11
- package/src/server/responses/core.ts +12 -9
- package/src/server/responses/deepseek-image-files.ts +121 -0
- package/src/server/responses/empty-completion-guard.ts +10 -0
- package/src/server/responses/fetch-helpers.ts +65 -14
- package/src/server/responses/input-admission.ts +6 -2
- package/src/server/responses/jev-model-invoke.ts +13 -14
- package/src/server/responses/main-hard-lock-routing.ts +33 -8
- package/src/server/responses/native-steering-log.ts +2 -2
- package/src/server/responses/opengateway-completion-guard.ts +232 -0
- package/src/server/responses/passthrough-completion.ts +41 -0
- package/src/server/responses/passthrough-delivery.ts +5 -2
- package/src/server/responses/passthrough-dispatch.ts +42 -45
- package/src/server/responses/passthrough-key-rotation.ts +40 -0
- package/src/server/responses/passthrough-observations.ts +38 -0
- package/src/server/responses/reasoning-archive-error.ts +21 -0
- package/src/server/responses/request-compression.ts +98 -0
- package/src/server/responses/request-prepare.ts +11 -1
- package/src/server/responses/request-transport.ts +92 -5
- package/src/server/responses/run-turn-execution.ts +2 -0
- package/src/server/responses/sidecar-execution.ts +16 -3
- package/src/server/responses/terminal-guard.ts +2 -0
- package/src/server/startup-action-control.ts +2 -2
- package/src/server/startup-health-cache.ts +2 -2
- package/src/server/windows-tray-control.ts +2 -2
- package/src/service/definition.ts +9 -1
- package/src/service/orchestration.ts +17 -0
- package/src/types/account-auto-pause.ts +15 -0
- package/src/types/config.ts +23 -1
- package/src/types/provider.ts +11 -5
- package/src/types/request.ts +36 -3
- package/src/types.ts +2 -0
- package/src/usage/cost.ts +17 -1
- package/src/usage/expected-prices.ts +3 -1
- package/src/usage/jev-stats.ts +4 -0
- package/src/usage/log.ts +2 -0
- package/src/usage/summary.ts +7 -0
- package/src/web-search/loop.ts +39 -13
- package/src/web-search/progress-stream.ts +4 -1
- package/src/web-search/run-turn-loop.ts +9 -3
- package/src/web-search/sidecar-search-replay.ts +96 -0
- package/gui/dist/assets/App-CHC89EXQ.js +0 -58
- package/gui/dist/assets/App-wT3mXqFz.css +0 -1
- package/gui/dist/assets/quota-summary-D8y2wTsB.js +0 -1
package/src/types/config.ts
CHANGED
|
@@ -1089,7 +1089,11 @@ export interface OcxConfig {
|
|
|
1089
1089
|
modelCacheTtlMs?: number;
|
|
1090
1090
|
/** Evictable retained app-state budget in MiB. Default 256; valid 64..4096. */
|
|
1091
1091
|
appOwnedMemoryBudgetMb?: number;
|
|
1092
|
-
/**
|
|
1092
|
+
/**
|
|
1093
|
+
* Anthropic prompt-cache retention: "short" = 5-min ephemeral, "long" = 1-hour extended,
|
|
1094
|
+
* "none" = disabled. Unset = "long" on first-party api.anthropic.com, "short" on
|
|
1095
|
+
* Anthropic-compatible endpoints (adapters/anthropic/cache-retention.ts).
|
|
1096
|
+
*/
|
|
1093
1097
|
cacheRetention?: "none" | "short" | "long";
|
|
1094
1098
|
/** Web-search sidecar: route web_search for non-OpenAI models through a gpt-mini via ChatGPT passthrough. */
|
|
1095
1099
|
webSearchSidecar?: OcxWebSearchSidecarConfig;
|
|
@@ -1113,6 +1117,9 @@ export interface OcxConfig {
|
|
|
1113
1117
|
codexAccounts?: CodexAccount[];
|
|
1114
1118
|
/** Account ids administratively excluded from future pool selection until resumed. */
|
|
1115
1119
|
pausedCodexAccountIds?: string[];
|
|
1120
|
+
/** Internal policy epochs; null marks malformed disk input and suppresses automatic pause. */
|
|
1121
|
+
accountAutoPausePolicyRevisions?: Record<string, string> | null;
|
|
1122
|
+
codexAccountPauseState?: Record<string, { revision: string; details?: import("./account-auto-pause").AccountPauseDetails }>;
|
|
1116
1123
|
/**
|
|
1117
1124
|
* Account ids, `__main__` included, allowed to keep serving from ChatGPT credits once one of
|
|
1118
1125
|
* their usage windows is full. Every other account is skipped by selection at 100% until its
|
|
@@ -1293,6 +1300,13 @@ export interface OcxConfig {
|
|
|
1293
1300
|
* that work today — on Azure and custom Responses gateways as well, whose limits are unknown.
|
|
1294
1301
|
*/
|
|
1295
1302
|
maxUpstreamBodyBytes?: number;
|
|
1303
|
+
/**
|
|
1304
|
+
* Opt-in compression of native ChatGPT Responses HTTP request bodies (#7247): "gzip" or "zstd".
|
|
1305
|
+
* Omitted, "off", or any other value = identity, the default. Applies only at the final HTTP send
|
|
1306
|
+
* to chatgpt.com Codex responses/compact, to string bodies from 1 KiB to 16 MiB; WebSocket turns
|
|
1307
|
+
* and every other destination are unchanged. Compression runs synchronously on the event loop.
|
|
1308
|
+
*/
|
|
1309
|
+
upstreamRequestCompression?: "off" | "gzip" | "zstd";
|
|
1296
1310
|
/**
|
|
1297
1311
|
* Opt-in ceiling, in bytes, on a decompressed INBOUND data-plane request body (#3573).
|
|
1298
1312
|
*
|
|
@@ -1454,6 +1468,8 @@ export interface OcxComboConfig {
|
|
|
1454
1468
|
nativeAlias?: boolean;
|
|
1455
1469
|
/** Display-only label for the public catalog row. Required for native aliases. */
|
|
1456
1470
|
displayName?: string;
|
|
1471
|
+
/** Display-only description for the public catalog row; unset keeps the generated text (#7229). */
|
|
1472
|
+
description?: string;
|
|
1457
1473
|
/**
|
|
1458
1474
|
* `strategy: "jev"` only: provider id of the decision service. Omitted or `"jev"` uses the
|
|
1459
1475
|
* canonical TypeSafe endpoint; any other id must name a configured `adapter: "jev-decision"`
|
|
@@ -1469,6 +1485,10 @@ export interface OcxComboConfig {
|
|
|
1469
1485
|
* whose first call may include a cold model load.
|
|
1470
1486
|
*/
|
|
1471
1487
|
decisionTimeoutMs?: number;
|
|
1488
|
+
/** JEV-only advisory loaded quota evidence; off/unset leaves request bytes unchanged. */
|
|
1489
|
+
decisionQuotaSignals?: boolean | null;
|
|
1490
|
+
/** Partial 0..100 tier overrides; defaults limited 70/nearlyExhausted 90. null clears. */
|
|
1491
|
+
decisionQuotaTiers?: import("../combos/jev-quota-config").JevQuotaTiers | null;
|
|
1472
1492
|
}
|
|
1473
1493
|
|
|
1474
1494
|
export type OcxRoutingUnknownEvidenceMode = "allow" | "penalize" | "exclude";
|
|
@@ -1757,6 +1777,8 @@ export interface CodexLowQuotaProtectionConfig {
|
|
|
1757
1777
|
actions: {
|
|
1758
1778
|
pause: boolean;
|
|
1759
1779
|
notify: boolean;
|
|
1780
|
+
/** Opt-in: also hand each notice to this host's OS notifier (macOS, Linux). Requires notify. */
|
|
1781
|
+
desktop?: boolean;
|
|
1760
1782
|
};
|
|
1761
1783
|
windows: {
|
|
1762
1784
|
short: boolean;
|
package/src/types/provider.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { UpstreamHttpVersion, ReasoningSummaryDelivery, CodexAccountMode } from "./wire";
|
|
2
|
+
import type { AccountAutoPausePolicy, AccountPauseDetails } from "./account-auto-pause";
|
|
2
3
|
import type { AnthropicAccountPoolConfig } from "./anthropic-account-pool";
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -287,6 +288,8 @@ export interface ModelCapabilities {
|
|
|
287
288
|
/** Requested tier only; does not imply an upstream window or activate an unverified wire. */
|
|
288
289
|
contextTier?: "default" | "long_context";
|
|
289
290
|
video?: { processing?: "static" | "agentic" };
|
|
291
|
+
/** Opt in exact first-party DeepSeek models to one-hour image file persistence. */
|
|
292
|
+
deepseekImageFiles?: boolean;
|
|
290
293
|
}
|
|
291
294
|
|
|
292
295
|
export interface OcxProviderConfig {
|
|
@@ -448,8 +451,9 @@ export interface OcxProviderConfig {
|
|
|
448
451
|
*/
|
|
449
452
|
preserveResponsesMessageMetadata?: boolean;
|
|
450
453
|
/**
|
|
451
|
-
* Opt in to lossy hosted-search history notes on
|
|
452
|
-
*
|
|
454
|
+
* Opt in to lossy hosted-search history notes on canonical Codex Lite ordinary turns and
|
|
455
|
+
* native v2 compaction triggers, even when search is declared. Default off; only literal true
|
|
456
|
+
* enables it. History without a search declaration is always projected, independent of this.
|
|
453
457
|
*/
|
|
454
458
|
projectResponsesLiteHostedSearchHistory?: boolean;
|
|
455
459
|
/**
|
|
@@ -567,7 +571,9 @@ export interface OcxProviderConfig {
|
|
|
567
571
|
* entry so routing stays single-key; managed via /api/providers/keys. A legacy bare
|
|
568
572
|
* `apiKey` seeds a one-entry pool on first management touch.
|
|
569
573
|
*/
|
|
570
|
-
apiKeyPool?: Array<{ id: string; key: string; label?: string; addedAt?: number }>;
|
|
574
|
+
apiKeyPool?: Array<{ id: string; key: string; label?: string; addedAt?: number; disabled?: boolean; pauseDetails?: AccountPauseDetails; pauseRevision?: string }>;
|
|
575
|
+
/** Explicit opt-in permanent account refusal policy; default off. */
|
|
576
|
+
accountAutoPause?: AccountAutoPausePolicy;
|
|
571
577
|
/**
|
|
572
578
|
* Optional proactive ordering for `apiKeyPool` when the committed key is already
|
|
573
579
|
* cooling. Deliberately NOT named like the OAuth `accountPoolStrategy`: an API key
|
|
@@ -1110,9 +1116,9 @@ export interface OcxProviderConfig {
|
|
|
1110
1116
|
googleMode?: "ai-studio" | "vertex" | "cloud-code-assist";
|
|
1111
1117
|
/** Google tool-schema compatibility policy. Omitted preserves compatible report-only behavior. */
|
|
1112
1118
|
googleToolSchemaPolicy?: "compatible" | "reject-lossy";
|
|
1113
|
-
/** Vertex AI GCP project id
|
|
1119
|
+
/** Vertex AI GCP project id. API keys use it to select the project endpoint; ADC falls back to GOOGLE_CLOUD_PROJECT / GCLOUD_PROJECT env. */
|
|
1114
1120
|
project?: string;
|
|
1115
|
-
/** Vertex AI location, e.g. "us-central1" or "global"
|
|
1121
|
+
/** Vertex AI location, e.g. "us-central1" or "global". API-key project endpoints default to "global"; ADC falls back to GOOGLE_CLOUD_LOCATION env. */
|
|
1116
1122
|
location?: string;
|
|
1117
1123
|
/**
|
|
1118
1124
|
* Cursor adapter only: MCP servers opencodex starts/connects and exposes to the Cursor agent
|
package/src/types/request.ts
CHANGED
|
@@ -46,6 +46,14 @@ export interface OcxReasoningReplayScopeRef {
|
|
|
46
46
|
current?: Readonly<OcxReasoningReplayIdentity>;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
/** One replayed hosted search cell, rebuilt as the `web_search` call/result pair `callId`. */
|
|
50
|
+
export interface OcxReplayedWebSearchCell {
|
|
51
|
+
/** The client-visible cell id; absent when the cell carried no usable id (no recorded result). */
|
|
52
|
+
cellId?: string;
|
|
53
|
+
callId: string;
|
|
54
|
+
failed: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
49
57
|
export interface OcxParsedRequest {
|
|
50
58
|
modelId: string;
|
|
51
59
|
/** Client-facing model selector retained for Anthropic routes after wire-model normalization. */
|
|
@@ -152,6 +160,12 @@ export interface OcxParsedRequest {
|
|
|
152
160
|
* executes searches via the gpt-5.6-luna sidecar (see src/web-search). Absent when not requested.
|
|
153
161
|
*/
|
|
154
162
|
_webSearch?: Record<string, unknown>;
|
|
163
|
+
/**
|
|
164
|
+
* Replayed hosted `web_search_call` cells the parser rebuilt as `web_search` call/result pairs
|
|
165
|
+
* (src/responses/web-search-replay.ts). The web-search sidecar swaps in the result it recorded for
|
|
166
|
+
* a cell, or removes the pairs when it does not run (src/web-search/sidecar-search-replay.ts).
|
|
167
|
+
*/
|
|
168
|
+
_replayedWebSearchCells?: OcxReplayedWebSearchCell[];
|
|
155
169
|
/** Hosted image_generation tool config stashed for the image bridge sidecar (see src/images). */
|
|
156
170
|
_imageGeneration?: { toolNames: Set<string>; originalTool?: Record<string, unknown> };
|
|
157
171
|
/**
|
|
@@ -308,8 +322,23 @@ export interface OcxDocumentContent {
|
|
|
308
322
|
filename?: string;
|
|
309
323
|
}
|
|
310
324
|
|
|
325
|
+
/**
|
|
326
|
+
* Inline audio carried by Codex's Responses input. Chat Completions accepts this as an
|
|
327
|
+
* `input_audio` content part only for WAV and MP3 payloads, so the normalized form keeps the
|
|
328
|
+
* base64 bytes and a wire-safe format label rather than the original data URL.
|
|
329
|
+
*/
|
|
330
|
+
export interface OcxAudioContent {
|
|
331
|
+
type: "audio";
|
|
332
|
+
/** Base64 payload with no `data:` prefix. */
|
|
333
|
+
data: string;
|
|
334
|
+
/** Chat Completions input_audio supports WAV and MP3. */
|
|
335
|
+
format: "mp3" | "wav";
|
|
336
|
+
/** Text marker for wires that cannot carry the bytes. */
|
|
337
|
+
text: string;
|
|
338
|
+
}
|
|
339
|
+
|
|
311
340
|
/** A user/developer message content part: text, native media, or an attached document. */
|
|
312
|
-
export type OcxContentPart = OcxTextContent | OcxImageContent | OcxVideoContent | OcxDocumentContent;
|
|
341
|
+
export type OcxContentPart = OcxTextContent | OcxImageContent | OcxVideoContent | OcxDocumentContent | OcxAudioContent;
|
|
313
342
|
|
|
314
343
|
export interface OcxThinkingContent {
|
|
315
344
|
type: "thinking";
|
|
@@ -448,9 +477,10 @@ export type AdapterEvent =
|
|
|
448
477
|
// "Searching the web" spinner, then `end` once it resolves. The bridge maps begin → an
|
|
449
478
|
// output_item.added(in_progress) and end → the matching output_item.done(completed|failed) under
|
|
450
479
|
// the SAME output index, so the activity animates instead of flashing completed instantly.
|
|
451
|
-
|
|
480
|
+
// `itemId` lets the loop name the client-visible cell so it can record the result for replay.
|
|
481
|
+
| { type: "web_search_call_begin"; id: string; itemId?: string }
|
|
452
482
|
| {
|
|
453
|
-
type: "web_search_call_end"; id: string; queries: string[]; status?: "completed" | "failed"; sources?: OcxUrlCitation[];
|
|
483
|
+
type: "web_search_call_end"; id: string; itemId?: string; queries: string[]; status?: "completed" | "failed"; sources?: OcxUrlCitation[];
|
|
454
484
|
/** Anthropic's server-side call (adapters/anthropic.ts): its `server_tool_use` and result block, verbatim. */
|
|
455
485
|
anthropic?: AnthropicServerBlocks;
|
|
456
486
|
}
|
|
@@ -510,6 +540,8 @@ export interface OcxUrlCitation {
|
|
|
510
540
|
* - `cachedInputTokens` is cache READ tokens only (a subset of `inputTokens`).
|
|
511
541
|
* - `cacheReadInputTokens`/`cacheCreationInputTokens` carry the read/write split when
|
|
512
542
|
* the provider reports both; reads mirror `cachedInputTokens`.
|
|
543
|
+
* - `cacheCreation1hInputTokens` is the 1-hour-TTL subset of `cacheCreationInputTokens`
|
|
544
|
+
* (Anthropic `cache_creation.ephemeral_1h_input_tokens`), priced at the 1h write tier.
|
|
513
545
|
* - `totalTokens` = inputTokens + outputTokens. Never re-add cache detail on top.
|
|
514
546
|
*/
|
|
515
547
|
export interface OcxUsage {
|
|
@@ -527,6 +559,7 @@ export interface OcxUsage {
|
|
|
527
559
|
cachedInputTokens?: number;
|
|
528
560
|
cacheReadInputTokens?: number;
|
|
529
561
|
cacheCreationInputTokens?: number;
|
|
562
|
+
cacheCreation1hInputTokens?: number;
|
|
530
563
|
reasoningOutputTokens?: number;
|
|
531
564
|
estimated?: boolean;
|
|
532
565
|
/**
|
package/src/types.ts
CHANGED
|
@@ -41,6 +41,7 @@ export {
|
|
|
41
41
|
export type {
|
|
42
42
|
OcxReasoningReplayIdentity,
|
|
43
43
|
OcxReasoningReplayScopeRef,
|
|
44
|
+
OcxReplayedWebSearchCell,
|
|
44
45
|
OcxParsedRequest,
|
|
45
46
|
OcxContext,
|
|
46
47
|
OcxMessage,
|
|
@@ -50,6 +51,7 @@ export type {
|
|
|
50
51
|
OcxToolResultMessage,
|
|
51
52
|
OcxTextContent,
|
|
52
53
|
OcxImageContent,
|
|
54
|
+
OcxAudioContent,
|
|
53
55
|
OcxDocumentContent,
|
|
54
56
|
OcxContentPart,
|
|
55
57
|
OcxThinkingContent,
|
package/src/usage/cost.ts
CHANGED
|
@@ -65,6 +65,8 @@ export interface CostTokens {
|
|
|
65
65
|
output: number;
|
|
66
66
|
cacheRead: number;
|
|
67
67
|
cacheWrite: number;
|
|
68
|
+
/** The 1-hour-TTL subset of `cacheWrite`, when the provider reports the split. */
|
|
69
|
+
cacheWrite1h?: number;
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
export interface CostBreakdown {
|
|
@@ -163,22 +165,35 @@ export function normalizeCostTokens(usage: OcxUsage): CostTokens | null {
|
|
|
163
165
|
for (const cacheRead of candidates) {
|
|
164
166
|
if (![input, output, cacheRead, cacheWrite].every(finiteNonNegative)) return null;
|
|
165
167
|
if (cacheRead + cacheWrite > input) continue;
|
|
168
|
+
const cacheWrite1h = usage.cacheCreation1hInputTokens;
|
|
166
169
|
return {
|
|
167
170
|
input: Math.max(0, input - cacheRead - cacheWrite),
|
|
168
171
|
output,
|
|
169
172
|
cacheRead,
|
|
170
173
|
cacheWrite,
|
|
174
|
+
...(typeof cacheWrite1h === "number" && finiteNonNegative(cacheWrite1h) && cacheWrite1h > 0
|
|
175
|
+
? { cacheWrite1h: Math.min(cacheWrite1h, cacheWrite) } : {}),
|
|
171
176
|
};
|
|
172
177
|
}
|
|
173
178
|
return null;
|
|
174
179
|
}
|
|
175
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Price of a 1-hour cache write relative to the 5-minute rate that Cost4.cacheWrite records.
|
|
183
|
+
* Anthropic bills 5m writes at 1.25x and 1h writes at 2x base input, so 2 / 1.25. Scaling the
|
|
184
|
+
* 5m rate (rather than input) keeps every tier multiplier already applied to the Cost4 and keeps
|
|
185
|
+
* a free-write row (cacheWrite 0) free.
|
|
186
|
+
*/
|
|
187
|
+
export const CACHE_WRITE_1H_OVER_5M = 2 / 1.25;
|
|
188
|
+
|
|
176
189
|
/** jawcode unit convention: USD per 1M tokens (jawcode stats/db.ts calculateCatalogCost). */
|
|
177
190
|
export function calculateCost(tokens: CostTokens, cost4: Cost4): CostBreakdown {
|
|
178
191
|
const input = cost4.input * tokens.input / 1_000_000;
|
|
179
192
|
const output = cost4.output * tokens.output / 1_000_000;
|
|
180
193
|
const cacheRead = cost4.cacheRead * tokens.cacheRead / 1_000_000;
|
|
181
|
-
const
|
|
194
|
+
const write1h = Math.min(tokens.cacheWrite1h ?? 0, tokens.cacheWrite);
|
|
195
|
+
const cacheWrite = (cost4.cacheWrite * (tokens.cacheWrite - write1h)
|
|
196
|
+
+ cost4.cacheWrite * CACHE_WRITE_1H_OVER_5M * write1h) / 1_000_000;
|
|
182
197
|
return { input, output, cacheRead, cacheWrite, total: input + output + cacheRead + cacheWrite };
|
|
183
198
|
}
|
|
184
199
|
|
|
@@ -656,6 +671,7 @@ export function estimateComboCost(
|
|
|
656
671
|
tokens.output += est.tokens.output;
|
|
657
672
|
tokens.cacheRead += est.tokens.cacheRead;
|
|
658
673
|
tokens.cacheWrite += est.tokens.cacheWrite;
|
|
674
|
+
if (est.tokens.cacheWrite1h) tokens.cacheWrite1h = (tokens.cacheWrite1h ?? 0) + est.tokens.cacheWrite1h;
|
|
659
675
|
cost.input += est.cost.input;
|
|
660
676
|
cost.output += est.cost.output;
|
|
661
677
|
cost.cacheRead += est.cost.cacheRead;
|
|
@@ -125,7 +125,9 @@ const GLM_52: Cost4 = { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0
|
|
|
125
125
|
const GLM_53: Cost4 = { input: 1.4, output: 4.4, cacheRead: 0.26, cacheWrite: 0 };
|
|
126
126
|
const GLM_53_FLASH: Cost4 = { input: 0.15, output: 0.5, cacheRead: 0.03, cacheWrite: 0 };
|
|
127
127
|
const QWEN38_MAX: Cost4 = { input: 2, output: 6, cacheRead: 0, cacheWrite: 0 };
|
|
128
|
-
// Anthropic official list prices (USD / 1M tokens). Cache write uses the published 5-minute rate
|
|
128
|
+
// Anthropic official list prices (USD / 1M tokens). Cache write uses the published 5-minute rate;
|
|
129
|
+
// writes the usage reports as 1-hour (cache_creation.ephemeral_1h_input_tokens) are priced at
|
|
130
|
+
// 2x base input through CACHE_WRITE_1H_OVER_5M in cost.ts.
|
|
129
131
|
const CLAUDE_SONNET_46: Cost4 = { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 };
|
|
130
132
|
const CLAUDE_OPUS_46: Cost4 = { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 };
|
|
131
133
|
// Claude Fable 5.1: 10 / 50, 5m cache write 12.50. Cache hits are 0.025x base input
|
package/src/usage/jev-stats.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeJevQuotaSummary, type JevQuotaDecisionSummary } from "../combos/jev-quota-config";
|
|
1
2
|
import type { OcxComboDefaultEffort } from "../types";
|
|
2
3
|
import type { PersistedUsageEntry } from "./log";
|
|
3
4
|
import { usageDisplayTotalTokens } from "./totals";
|
|
@@ -39,6 +40,7 @@ export interface PersistedJevDecisionV1 {
|
|
|
39
40
|
};
|
|
40
41
|
gate: JevDecisionGate;
|
|
41
42
|
latencyMs: number;
|
|
43
|
+
quota?: JevQuotaDecisionSummary;
|
|
42
44
|
confidence?: number;
|
|
43
45
|
chosenProbability?: number;
|
|
44
46
|
usage?: {
|
|
@@ -182,6 +184,7 @@ export function normalizePersistedJevDecision(value: unknown): PersistedJevDecis
|
|
|
182
184
|
const confidence = probability(value.confidence);
|
|
183
185
|
const chosenProbability = probability(value.chosenProbability);
|
|
184
186
|
const usage = normalizedDecisionUsage(value.usage);
|
|
187
|
+
const quota = normalizeJevQuotaSummary(value.quota);
|
|
185
188
|
const backend = value.backend === "typesafe" || value.backend === "systemone" || value.backend === "model"
|
|
186
189
|
? value.backend
|
|
187
190
|
: undefined;
|
|
@@ -196,6 +199,7 @@ export function normalizePersistedJevDecision(value: unknown): PersistedJevDecis
|
|
|
196
199
|
},
|
|
197
200
|
gate: gate as JevDecisionGate,
|
|
198
201
|
latencyMs,
|
|
202
|
+
...(quota ? { quota } : {}),
|
|
199
203
|
...(confidence !== undefined ? { confidence } : {}),
|
|
200
204
|
...(chosenProbability !== undefined ? { chosenProbability } : {}),
|
|
201
205
|
...(usage ? { usage } : {}),
|
package/src/usage/log.ts
CHANGED
|
@@ -581,6 +581,7 @@ function normalizeUsageValue(usage: OcxUsage | undefined): OcxUsage | undefined
|
|
|
581
581
|
...(typeof usage.cachedInputTokens === "number" ? { cachedInputTokens: usage.cachedInputTokens } : {}),
|
|
582
582
|
...(typeof usage.cacheReadInputTokens === "number" ? { cacheReadInputTokens: usage.cacheReadInputTokens } : {}),
|
|
583
583
|
...(typeof usage.cacheCreationInputTokens === "number" ? { cacheCreationInputTokens: usage.cacheCreationInputTokens } : {}),
|
|
584
|
+
...(typeof usage.cacheCreation1hInputTokens === "number" ? { cacheCreation1hInputTokens: usage.cacheCreation1hInputTokens } : {}),
|
|
584
585
|
...(typeof usage.reasoningOutputTokens === "number" ? { reasoningOutputTokens: usage.reasoningOutputTokens } : {}),
|
|
585
586
|
...(isNonNegativeFiniteNumber(usage.providerCredits) ? { providerCredits: usage.providerCredits } : {}),
|
|
586
587
|
...(usage.estimated ? { estimated: true } : {}),
|
|
@@ -625,6 +626,7 @@ function normalizeAttemptUsage(raw: unknown): OcxUsage | null {
|
|
|
625
626
|
"cachedInputTokens",
|
|
626
627
|
"cacheReadInputTokens",
|
|
627
628
|
"cacheCreationInputTokens",
|
|
629
|
+
"cacheCreation1hInputTokens",
|
|
628
630
|
"reasoningOutputTokens",
|
|
629
631
|
"providerCredits",
|
|
630
632
|
] as const) {
|
package/src/usage/summary.ts
CHANGED
|
@@ -520,6 +520,8 @@ function projectedComboUsage(
|
|
|
520
520
|
let cachedInputTokens = 0;
|
|
521
521
|
let cacheReadInputTokens = 0;
|
|
522
522
|
let cacheCreationInputTokens = 0;
|
|
523
|
+
let cacheCreation1hInputTokens = 0;
|
|
524
|
+
let hasCacheWrite1h = false;
|
|
523
525
|
let reasoningOutputTokens = 0;
|
|
524
526
|
let hasUsage = false;
|
|
525
527
|
let hasCacheTelemetry = false;
|
|
@@ -540,6 +542,10 @@ function projectedComboUsage(
|
|
|
540
542
|
cacheReadInputTokens += cache.read;
|
|
541
543
|
}
|
|
542
544
|
if (typeof cache.creation === "number") cacheCreationInputTokens += cache.creation;
|
|
545
|
+
if (typeof attempt.usage.cacheCreation1hInputTokens === "number") {
|
|
546
|
+
hasCacheWrite1h = true;
|
|
547
|
+
cacheCreation1hInputTokens += attempt.usage.cacheCreation1hInputTokens;
|
|
548
|
+
}
|
|
543
549
|
if (typeof attempt.usage.reasoningOutputTokens === "number") {
|
|
544
550
|
hasReasoningTelemetry = true;
|
|
545
551
|
reasoningOutputTokens += attempt.usage.reasoningOutputTokens;
|
|
@@ -559,6 +565,7 @@ function projectedComboUsage(
|
|
|
559
565
|
inputTokens,
|
|
560
566
|
outputTokens,
|
|
561
567
|
...(hasCacheTelemetry ? { cachedInputTokens, cacheReadInputTokens, cacheCreationInputTokens } : {}),
|
|
568
|
+
...(hasCacheWrite1h ? { cacheCreation1hInputTokens } : {}),
|
|
562
569
|
...(hasReasoningTelemetry ? { reasoningOutputTokens } : {}),
|
|
563
570
|
...(estimated ? { estimated: true } : {}),
|
|
564
571
|
}
|
package/src/web-search/loop.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { bridgeToResponsesSSE } from "../bridge";
|
|
|
14
14
|
import { isTerminalRefusalIncomplete } from "../bridge/terminal-refusal";
|
|
15
15
|
import { guardEmptyToolCatalogStream, type ClientToolCatalogRestriction } from "../lib/empty-tool-catalog";
|
|
16
16
|
import { mergeUsage } from "../server/responses/empty-completion-guard";
|
|
17
|
+
import { markSidecarContextOverflow, sidecarLoopTerminalEvent, upstreamErrorCode, type SidecarLoopFailure } from "../server/responses/context-overflow";
|
|
17
18
|
import { runWebSearch, type SidecarOutcome, type SidecarOutcomeRecorder, type SidecarSettings } from "./executor";
|
|
18
19
|
import { runAnthropicWebSearch } from "./anthropic-executor";
|
|
19
20
|
import { runXaiWebSearch, type XaiSearchOptions } from "./xai-executor";
|
|
@@ -23,8 +24,9 @@ import type { WebSearchBackendId } from "./index";
|
|
|
23
24
|
import { pacingHeaderDeadline } from "./header-deadline";
|
|
24
25
|
import { redactSecretString } from "../lib/redact";
|
|
25
26
|
import { readBoundedResponseBody } from "../lib/bounded-body";
|
|
26
|
-
import { applyUpstreamRecoveryInit, fetchWithResetRetry, prepareSameTarget429Wait, isNonReplayableResponse, SendBudgetExhaustedError } from "../lib/upstream-retry";
|
|
27
|
+
import { applyUpstreamRecoveryInit, fetchWithResetRetry, prepareSameTarget429Wait, isNonReplayableResponse, isSameTargetRetryHold, SendBudgetExhaustedError } from "../lib/upstream-retry";
|
|
27
28
|
import { rateLimitRetryDelayMs } from "../providers/key-failover";
|
|
29
|
+
import { RequestPacingQueueOverloadError } from "../providers/request-pacing";
|
|
28
30
|
import {
|
|
29
31
|
isTranslatorBudgetExceededError,
|
|
30
32
|
TRANSLATOR_MAX_TURN_BYTES,
|
|
@@ -33,6 +35,7 @@ import {
|
|
|
33
35
|
import { formatWebSearchResults } from "./format-result";
|
|
34
36
|
import { parseStreamWithProgress, RoutedModelInactivityError, WebSearchStreamProtocolError, WebSearchAdapterEventError } from "./progress-stream";
|
|
35
37
|
import { WEB_SEARCH_TOOL_NAME } from "./synthetic-tool";
|
|
38
|
+
import { newSidecarSearchCellId, rememberSidecarSearch, restoreSidecarSearchResults } from "./sidecar-search-replay";
|
|
36
39
|
|
|
37
40
|
const SSE_HEADERS = {
|
|
38
41
|
"Content-Type": "text/event-stream",
|
|
@@ -284,12 +287,21 @@ function jsonError(status: number, message: string, event?: Extract<AdapterEvent
|
|
|
284
287
|
/** Hard provider/parse failure inside an iteration. The eager first iteration converts it to a
|
|
285
288
|
* non-200 jsonError; later (already-streaming) iterations surface it as an in-stream error event. */
|
|
286
289
|
class LoopError extends Error {
|
|
287
|
-
constructor(
|
|
290
|
+
constructor(
|
|
291
|
+
readonly status: number,
|
|
292
|
+
message: string,
|
|
293
|
+
readonly event?: Extract<AdapterEvent, { type: "error" }>,
|
|
294
|
+
readonly detail?: Pick<SidecarLoopFailure, "upstreamCode" | "nonReplayable">,
|
|
295
|
+
) {
|
|
288
296
|
super(message);
|
|
289
297
|
this.name = "LoopError";
|
|
290
298
|
}
|
|
291
299
|
}
|
|
292
300
|
|
|
301
|
+
function loopFailure(e: LoopError): SidecarLoopFailure {
|
|
302
|
+
return { status: e.status, message: e.message, ...(e.event ? { event: e.event } : {}), ...e.detail };
|
|
303
|
+
}
|
|
304
|
+
|
|
293
305
|
/**
|
|
294
306
|
* Dependencies for one web-search loop iteration: parsed request, active adapter,
|
|
295
307
|
* incoming metadata, and the configured search executor.
|
|
@@ -417,6 +429,8 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
|
|
|
417
429
|
? Math.floor(deps.stallTimeoutSec * 1000)
|
|
418
430
|
: 300_000;
|
|
419
431
|
|
|
432
|
+
// Put recorded results back into replayed search pairs before the first iteration (#7269).
|
|
433
|
+
restoreSidecarSearchResults(parsed);
|
|
420
434
|
const messages: OcxMessage[] = [...parsed.context.messages];
|
|
421
435
|
const replaySnapshots = new Set<AnthropicReplaySnapshot>();
|
|
422
436
|
const loopT0 = Date.now();
|
|
@@ -491,9 +505,11 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
|
|
|
491
505
|
// through the adapter formatter or the generic transport error, which could expose its
|
|
492
506
|
// raw message. A parent/client cancellation still owns the request lifecycle as 499.
|
|
493
507
|
if (signal.aborted) throw new LoopError(499, "client closed request during web-search");
|
|
494
|
-
throw new LoopError(prepared.response.status, `Provider error ${prepared.response.status}
|
|
508
|
+
throw new LoopError(prepared.response.status, `Provider error ${prepared.response.status}`, undefined,
|
|
509
|
+
{ nonReplayable: isNonReplayableResponse(prepared.response) });
|
|
495
510
|
}
|
|
496
511
|
let formatted = "";
|
|
512
|
+
let upstreamCode: string | undefined;
|
|
497
513
|
if (body.displaySafe && !body.truncated && body.text.trim() && prepared.responseAdapter.formatErrorBody) {
|
|
498
514
|
try {
|
|
499
515
|
formatted = prepared.responseAdapter.formatErrorBody(
|
|
@@ -503,8 +519,10 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
|
|
|
503
519
|
).trim();
|
|
504
520
|
} catch { /* formatter hooks are best-effort; unsafe raw text is never the fallback */ }
|
|
505
521
|
}
|
|
522
|
+
if (body.displaySafe && !body.truncated) upstreamCode = upstreamErrorCode(body.text);
|
|
506
523
|
const suffix = formatted ? `: ${formatted.slice(0, 400)}` : "";
|
|
507
|
-
throw new LoopError(prepared.response.status, `Provider error ${prepared.response.status}${suffix}
|
|
524
|
+
throw new LoopError(prepared.response.status, `Provider error ${prepared.response.status}${suffix}`, undefined,
|
|
525
|
+
{ upstreamCode, nonReplayable: isNonReplayableResponse(prepared.response) });
|
|
508
526
|
}
|
|
509
527
|
};
|
|
510
528
|
|
|
@@ -652,7 +670,7 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
|
|
|
652
670
|
// Same-target 429 wait-and-retry (opt-in `retryOn429`) BEFORE key rotation: a primary-key
|
|
653
671
|
// rate-limit blip replays on the SAME key; rotation only runs after attempts exhaust.
|
|
654
672
|
while (
|
|
655
|
-
prepared.response.status === 429
|
|
673
|
+
prepared.response.status === 429 && !isSameTargetRetryHold(prepared.response)
|
|
656
674
|
&& rateLimitRetryPolicy !== null
|
|
657
675
|
&& rateLimitRetries < rateLimitRetryPolicy.attempts
|
|
658
676
|
) {
|
|
@@ -736,7 +754,7 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
|
|
|
736
754
|
throw new LoopError(504, `Provider response-header timeout after ${connectTimeoutMs}ms during web-search`);
|
|
737
755
|
}
|
|
738
756
|
if (signal.aborted) throw new LoopError(499, "client closed request during web-search");
|
|
739
|
-
if (error instanceof LoopError) throw error;
|
|
757
|
+
if (error instanceof LoopError || error instanceof RequestPacingQueueOverloadError) throw error;
|
|
740
758
|
throw new LoopError(502, `Provider unreachable: ${error instanceof Error ? error.message : String(error)}`);
|
|
741
759
|
} finally {
|
|
742
760
|
headerDeadline.clear();
|
|
@@ -834,7 +852,8 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
|
|
|
834
852
|
if (terminal.code === "translation_buffer_limit") {
|
|
835
853
|
throw new TranslatorBudgetExceededError("retained_collectors", TRANSLATOR_MAX_TURN_BYTES);
|
|
836
854
|
}
|
|
837
|
-
|
|
855
|
+
// Keep an adapter's own overflow verdict; every other terminal stays a generic 502.
|
|
856
|
+
throw new LoopError(502, terminal.message, terminal.code === "context_length_exceeded" ? terminal : undefined);
|
|
838
857
|
}
|
|
839
858
|
deps.thinkingRecovery?.observeIteration(events);
|
|
840
859
|
return { ...scanEventsForWebSearch(events), streamedPassthroughCount, events, anthropicReplay };
|
|
@@ -849,6 +868,7 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
|
|
|
849
868
|
async function* runSearchCall(call: WebSearchCall, precedingThinking: OcxThinkingContent[] = [], appendAssistant = true): AsyncGenerator<AdapterEvent> {
|
|
850
869
|
const results: { query: string; outcome: SidecarOutcome }[] = [];
|
|
851
870
|
let beganCell = false;
|
|
871
|
+
const cellItemId = newSidecarSearchCellId();
|
|
852
872
|
if (call.queries.length === 0) {
|
|
853
873
|
// The model called web_search with neither query nor queries — count it against the budget
|
|
854
874
|
// (loop-bounding) exactly as the old empty-query placeholder did, but emit no cell.
|
|
@@ -870,7 +890,7 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
|
|
|
870
890
|
// Real sidecar search. Open the cell once, before the first real query runs.
|
|
871
891
|
if (!beganCell) {
|
|
872
892
|
beganCell = true;
|
|
873
|
-
yield { type: "web_search_call_begin", id: call.id };
|
|
893
|
+
yield { type: "web_search_call_begin", id: call.id, itemId: cellItemId };
|
|
874
894
|
}
|
|
875
895
|
// F5: the anthropic sidecar authenticates with its own stored OAuth — it never touches the
|
|
876
896
|
// ChatGPT forward headers and must NOT record a Codex/OpenAI pool outcome.
|
|
@@ -945,12 +965,15 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
|
|
|
945
965
|
});
|
|
946
966
|
// One aggregated tool result. isError only when EVERY query failed (a partial success is usable).
|
|
947
967
|
const allFailed = results.every(r => !!r.outcome.error);
|
|
968
|
+
const resultText = formatWebSearchResults(results, !!parsed._structuredOutput);
|
|
948
969
|
messages.push({
|
|
949
970
|
role: "toolResult", toolCallId: call.id, toolName: WEB_SEARCH_TOOL_NAME,
|
|
950
|
-
content:
|
|
971
|
+
content: resultText,
|
|
951
972
|
isError: allFailed, timestamp: now,
|
|
952
973
|
});
|
|
953
974
|
if (beganCell) {
|
|
975
|
+
// The client sees only the cell; keep the result so its replay can carry it (#7269).
|
|
976
|
+
rememberSidecarSearch(parsed, cellItemId, { id: call.id, arguments: callArgs }, resultText);
|
|
954
977
|
// The cell is "completed" if any query produced a usable result, else "failed". `queries`
|
|
955
978
|
// carries every attempted query so Codex renders the native plural label.
|
|
956
979
|
const anySuccess = results.some(r => !r.outcome.error);
|
|
@@ -966,7 +989,7 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
|
|
|
966
989
|
}
|
|
967
990
|
}
|
|
968
991
|
yield {
|
|
969
|
-
type: "web_search_call_end", id: call.id,
|
|
992
|
+
type: "web_search_call_end", id: call.id, itemId: cellItemId,
|
|
970
993
|
queries: call.queries,
|
|
971
994
|
status: anySuccess ? "completed" : "failed",
|
|
972
995
|
...(sources.length > 0 ? { sources } : {}),
|
|
@@ -982,7 +1005,7 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
|
|
|
982
1005
|
firstPrepared = await prepareIterationDrained(false);
|
|
983
1006
|
} catch (e) {
|
|
984
1007
|
if (abortSignal) abortSignal.removeEventListener("abort", linkAbort);
|
|
985
|
-
if (e instanceof LoopError) return jsonError(e.status, e.message, e.event);
|
|
1008
|
+
if (e instanceof LoopError) return markSidecarContextOverflow(jsonError(e.status, e.message, e.event), loopFailure(e));
|
|
986
1009
|
if (e instanceof SendBudgetExhaustedError) return jsonError(429, e.message,
|
|
987
1010
|
{ type: "error", message: e.message, errorType: "rate_limit_error", code: e.code, retryable: false });
|
|
988
1011
|
throw e;
|
|
@@ -1137,10 +1160,13 @@ export async function runWithWebSearch(deps: WebSearchLoopDeps): Promise<Respons
|
|
|
1137
1160
|
} else {
|
|
1138
1161
|
if (e instanceof SendBudgetExhaustedError) yield { type: "error", status: 429,
|
|
1139
1162
|
errorType: "rate_limit_error", code: e.code, message: e.message, retryable: false };
|
|
1140
|
-
|
|
1163
|
+
// A later iteration's overflow ends the turn as the main path's terminal
|
|
1164
|
+
// context_length_exceeded, so Codex compacts instead of resending (#7316).
|
|
1165
|
+
else yield (e instanceof LoopError ? sidecarLoopTerminalEvent(loopFailure(e)) : undefined)
|
|
1166
|
+
?? (e instanceof LoopError && e.event ? e.event
|
|
1141
1167
|
: e instanceof LoopError && deps.thinkingRecovery
|
|
1142
1168
|
? { type: "error", status: e.status, errorType: "upstream_error", message: e.message }
|
|
1143
|
-
: { type: "error", message: e instanceof Error ? e.message : String(e) };
|
|
1169
|
+
: { type: "error", message: e instanceof Error ? e.message : String(e) });
|
|
1144
1170
|
}
|
|
1145
1171
|
return;
|
|
1146
1172
|
}
|
|
@@ -308,9 +308,12 @@ export async function* parseStreamWithProgress(
|
|
|
308
308
|
}
|
|
309
309
|
if (event.type === "error") {
|
|
310
310
|
if (event.usage) options.onErrorUsage?.(event.usage);
|
|
311
|
+
// An adapter's own overflow verdict keeps its typed event so the sidecar can end the
|
|
312
|
+
// turn as context_length_exceeded (#7316); other errors keep the plain-message shape.
|
|
313
|
+
const preserve = options.preserveAdapterError || event.code === "context_length_exceeded";
|
|
311
314
|
fail(event.code === "translation_buffer_limit"
|
|
312
315
|
? new TranslatorBudgetExceededError("live_transient", TRANSLATOR_MAX_TURN_BYTES)
|
|
313
|
-
:
|
|
316
|
+
: preserve ? new WebSearchAdapterEventError(event, replaySafe) : new Error(event.message));
|
|
314
317
|
return;
|
|
315
318
|
}
|
|
316
319
|
replaySafe &&= event.type === "heartbeat" && event.replayUnsafe !== true;
|
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
type TranslatorBudget,
|
|
44
44
|
} from "../lib/translator-budget";
|
|
45
45
|
import { jsonUtf8Bytes } from "../lib/json-byte-size";
|
|
46
|
+
import { newSidecarSearchCellId, rememberSidecarSearch, restoreSidecarSearchResults } from "./sidecar-search-replay";
|
|
46
47
|
|
|
47
48
|
export interface RunTurnWebSearchDeps {
|
|
48
49
|
parsed: OcxParsedRequest;
|
|
@@ -77,6 +78,8 @@ export interface RunTurnWebSearchDeps {
|
|
|
77
78
|
* active: same request plus the synthetic tool so the model can ask to search.
|
|
78
79
|
*/
|
|
79
80
|
export function runTurnWebSearchInitialParsed(parsed: OcxParsedRequest): OcxParsedRequest {
|
|
81
|
+
// Runs before the first dispatch: put recorded results back into replayed search pairs (#7269).
|
|
82
|
+
restoreSidecarSearchResults(parsed);
|
|
80
83
|
return {
|
|
81
84
|
...parsed,
|
|
82
85
|
context: {
|
|
@@ -296,6 +299,7 @@ export async function* runTurnWebSearchLoop(
|
|
|
296
299
|
): AsyncGenerator<AdapterEvent> {
|
|
297
300
|
const results: { query: string; outcome: SidecarOutcome }[] = [];
|
|
298
301
|
let beganCell = false;
|
|
302
|
+
const cellItemId = newSidecarSearchCellId();
|
|
299
303
|
if (call.queries.length === 0) {
|
|
300
304
|
searchesExecuted++;
|
|
301
305
|
results.push({ query: "", outcome: { text: "", sources: [], error: "the model called web_search with an empty query" } });
|
|
@@ -318,7 +322,7 @@ export async function* runTurnWebSearchLoop(
|
|
|
318
322
|
} else {
|
|
319
323
|
if (!beganCell) {
|
|
320
324
|
beganCell = true;
|
|
321
|
-
yield { type: "web_search_call_begin", id: call.id };
|
|
325
|
+
yield { type: "web_search_call_begin", id: call.id, itemId: cellItemId };
|
|
322
326
|
}
|
|
323
327
|
if (abortSignal?.aborted) {
|
|
324
328
|
yield { type: "web_search_call_end", id: call.id, queries: call.queries, status: "failed" };
|
|
@@ -358,12 +362,14 @@ export async function* runTurnWebSearchLoop(
|
|
|
358
362
|
timestamp: now,
|
|
359
363
|
});
|
|
360
364
|
const allFailed = results.every(r => !!r.outcome.error);
|
|
365
|
+
const resultText = formatWebSearchResults(results, !!parsed._structuredOutput);
|
|
361
366
|
appendMessage({
|
|
362
367
|
role: "toolResult", toolCallId: call.id, toolName: WEB_SEARCH_TOOL_NAME,
|
|
363
|
-
content:
|
|
368
|
+
content: resultText,
|
|
364
369
|
isError: allFailed, timestamp: now,
|
|
365
370
|
});
|
|
366
371
|
if (beganCell) {
|
|
372
|
+
rememberSidecarSearch(parsed, cellItemId, { id: call.id, arguments: callArgs }, resultText);
|
|
367
373
|
const anySuccess = results.some(r => !r.outcome.error);
|
|
368
374
|
const sources: { url: string; title?: string }[] = [];
|
|
369
375
|
const seenSrc = new Set<string>();
|
|
@@ -375,7 +381,7 @@ export async function* runTurnWebSearchLoop(
|
|
|
375
381
|
}
|
|
376
382
|
}
|
|
377
383
|
yield {
|
|
378
|
-
type: "web_search_call_end", id: call.id,
|
|
384
|
+
type: "web_search_call_end", id: call.id, itemId: cellItemId,
|
|
379
385
|
queries: call.queries,
|
|
380
386
|
status: anySuccess ? "completed" : "failed",
|
|
381
387
|
...(sources.length > 0 ? { sources } : {}),
|