@bitkyc08/opencodex 2.82.0 → 2.84.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-CZtqiGcw.js +56 -0
- package/gui/dist/assets/Tray-C9TGtwad.js +1 -0
- package/gui/dist/assets/{index-BAbcj51J.css → index-BB6uK6tU.css} +1 -1
- package/gui/dist/assets/index-CTm4l-HQ.js +94 -0
- package/gui/dist/assets/{quota-summary-DTBC8n7-.js → quota-summary-BbEeRfPY.js} +1 -1
- package/gui/dist/index.html +2 -2
- package/package.json +2 -2
- package/src/adapters/coding-agent/protocol.ts +15 -8
- package/src/adapters/cursor/catalog.ts +2 -2
- package/src/adapters/cursor/effort-map.ts +1 -1
- package/src/adapters/devin/live-models.ts +2 -2
- package/src/adapters/google-truncation.ts +22 -0
- package/src/adapters/google.ts +7 -3
- package/src/adapters/ollama-native.ts +47 -15
- package/src/adapters/openai-chat/messages.ts +8 -4
- package/src/adapters/openai-responses/empty-assistant.ts +32 -0
- package/src/adapters/openai-responses/passthrough.ts +5 -0
- package/src/adapters/openai-responses/plaintext-reasoning-notice.ts +85 -0
- package/src/adapters/openai-responses/reasoning.ts +11 -1
- package/src/adapters/openai-responses/request-strips.ts +10 -2
- package/src/adapters/qoder/adapter.ts +16 -14
- package/src/bridge/errors.ts +26 -1
- package/src/bridge.ts +1 -1
- package/src/claude/context-windows.ts +57 -4
- package/src/claude/desktop-picker.ts +1 -1
- package/src/claude/intercept/connect-proxy.ts +10 -3
- package/src/claude/intercept/picker-ca-persistence.ts +3 -1
- package/src/claude/intercept/picker-listener.ts +49 -1
- package/src/claude/intercept/settings-no-proxy.ts +146 -0
- package/src/claude/intercept/settings.ts +101 -9
- package/src/cli/account-api.ts +65 -0
- package/src/cli/account-auth.ts +44 -12
- package/src/cli/account.ts +41 -5
- package/src/cli/capabilities-access-remote.ts +8 -0
- package/src/cli/capabilities-base.ts +13 -1
- package/src/cli/catalog-command-result.ts +2 -1
- package/src/cli/claude.ts +15 -6
- package/src/cli/config-command.ts +8 -2
- package/src/cli/debug.ts +8 -0
- package/src/cli/dispatch.ts +16 -3
- package/src/cli/export-command.ts +14 -1
- package/src/cli/hub-disable.ts +99 -0
- package/src/cli/hub.ts +4 -2
- package/src/cli/index.ts +13 -4
- package/src/cli/link-child.ts +104 -0
- package/src/cli/link-enrollment.ts +96 -0
- package/src/cli/link.ts +39 -24
- package/src/cli/observe-stream.ts +6 -1
- package/src/cli/observe.ts +10 -2
- package/src/cli/provider.ts +12 -0
- package/src/cli/registry.ts +5 -4
- package/src/cli/resolve.ts +1 -1
- package/src/cli/runtime-api.ts +27 -2
- package/src/cli/status.ts +104 -20
- package/src/client/connect.ts +39 -4
- package/src/client/hub-client.ts +14 -7
- package/src/client/hub-state.ts +25 -14
- package/src/client/link-home.ts +248 -0
- package/src/client/link-management-relay.ts +61 -0
- package/src/client/link-relay.ts +6 -1
- package/src/client/link-status.ts +14 -0
- package/src/client/machine-api.ts +14 -7
- package/src/client/machine-listener.ts +29 -4
- package/src/clients/config-export/model-metadata.ts +16 -0
- package/src/clients/config-export/zcode-store.ts +46 -11
- package/src/clients/config-export/zcode.ts +3 -5
- package/src/clients/config-export.ts +47 -17
- package/src/clients/omo-role-models.ts +6 -3
- package/src/codex/agent-role-models.ts +26 -8
- package/src/codex/auth-api/account-list.ts +17 -2
- package/src/codex/auth-api/account-selection.ts +17 -7
- package/src/codex/auth-api/pool-mode-gate.ts +4 -5
- package/src/codex/auth-api/routes.ts +5 -1
- package/src/codex/auth-context.ts +8 -3
- package/src/codex/autostart-health.ts +3 -1
- package/src/codex/catalog/bundled.ts +5 -1
- package/src/codex/catalog/effort.ts +2 -2
- package/src/codex/catalog/metadata.ts +4 -2
- package/src/codex/catalog/native-models.ts +17 -3
- package/src/codex/catalog/provider-models.ts +25 -0
- package/src/codex/desktop-app/darwin.ts +1 -1
- package/src/codex/desktop-app/handoff.ts +1 -1
- package/src/codex/desktop-app/linux.ts +1 -1
- package/src/codex/desktop-app/lock.ts +1 -1
- package/src/codex/desktop-app/types.ts +1 -1
- package/src/codex/desktop-app/windows.ts +1 -1
- package/src/codex/desktop-app-restart.ts +1 -1
- package/src/codex/main-account.ts +6 -7
- package/src/codex/native-profile-startup.ts +4 -1
- package/src/codex/prompt-journal.ts +20 -2
- package/src/codex/prompt-layers/encoding.ts +49 -0
- package/src/codex/prompt-layers/import-source.ts +30 -0
- package/src/codex/prompt-layers/toml-edit.ts +269 -37
- package/src/codex/prompt-layers/toml-read.ts +52 -4
- package/src/codex/prompt-layers.ts +383 -20
- package/src/codex/prompt-lock-claim.ts +13 -3
- package/src/codex/prompt-lock-evidence.ts +3 -3
- package/src/codex/prompt-lock-owner.ts +14 -6
- package/src/codex/prompt-text-probe.ts +34 -5
- package/src/codex/quota.ts +13 -6
- package/src/codex/role-auto-assign.ts +2 -5
- package/src/codex/routing/active-account.ts +21 -0
- package/src/codex/routing.ts +1 -1
- package/src/codex/runtime.ts +196 -8
- package/src/codex/shim-overlay.ts +8 -4
- package/src/codex/shim.ts +1 -1
- package/src/combos/failover.ts +3 -0
- package/src/config/api-key-reconcile.ts +34 -0
- package/src/config/diagnostics.ts +16 -0
- package/src/config/feature-flags.ts +5 -0
- package/src/config/live-reconcile.ts +43 -29
- package/src/config/live-topology.ts +29 -0
- package/src/config/load-degrade.ts +2 -1
- package/src/config/paths.ts +3 -0
- package/src/config/salvage.ts +3 -1
- package/src/config/schema/config-schema.ts +2 -0
- package/src/generated/compatibility-version.json +373 -217
- package/src/grok/grpc-web.ts +23 -1
- package/src/grok/inject.ts +245 -221
- package/src/grok/reset-coupon-ledger.ts +293 -57
- package/src/grok/reset-coupons.ts +15 -6
- package/src/integrations/cursor-detect.ts +1 -1
- package/src/integrations/mutation-plan.ts +13 -7
- package/src/integrations/registry.ts +45 -11
- package/src/integrations/state.ts +19 -7
- package/src/integrations/target.ts +42 -5
- package/src/integrations/writer.ts +14 -6
- package/src/lib/app-owned-memory-stores.ts +4 -0
- package/src/lib/app-owned-memory.ts +11 -0
- package/src/lib/errors.ts +25 -0
- package/src/lib/json-byte-size.ts +31 -0
- package/src/lib/optional-shutdown-hooks.ts +35 -0
- package/src/lib/outbound-credential-registry.ts +210 -0
- package/src/lib/passive-read.ts +35 -0
- package/src/lib/redact.ts +2 -2
- package/src/lib/shadow-call.ts +44 -5
- package/src/link/codex-settings.ts +155 -0
- package/src/link/management-routes.ts +89 -0
- package/src/link/remote-probe.ts +38 -0
- package/src/link/ssh-argv.ts +26 -5
- package/src/link/ssh-runner.ts +58 -27
- package/src/link/status-projection.ts +22 -4
- package/src/link/supervisor.ts +144 -49
- package/src/link/tunnel-state.ts +11 -4
- package/src/oauth/anthropic-continuity.ts +4 -3
- package/src/oauth/anthropic-identity.ts +13 -2
- package/src/oauth/anthropic-model-quota.ts +13 -1
- package/src/oauth/anthropic-routing.ts +230 -23
- package/src/oauth/anthropic.ts +18 -4
- package/src/oauth/google-antigravity.ts +24 -7
- package/src/oauth/index.ts +25 -16
- package/src/oauth/kiro-account-identity.ts +106 -0
- package/src/oauth/kiro.ts +48 -5
- package/src/oauth/meta-muse-device.ts +1 -1
- package/src/oauth/plan.ts +29 -0
- package/src/oauth/store.ts +40 -3
- package/src/oauth/types.ts +3 -1
- package/src/plugins/loader.ts +11 -0
- package/src/providers/anthropic-billing-ownership.ts +74 -0
- package/src/providers/anthropic-billing-recovery.ts +31 -0
- package/src/providers/anthropic-billing-refusal.ts +55 -0
- package/src/providers/anthropic-fast.ts +1 -1
- package/src/providers/anthropic-reset-admission.ts +62 -0
- package/src/providers/anthropic-reset-grant-ledger.ts +29 -7
- package/src/providers/api-key-resolve.ts +17 -2
- package/src/providers/devin-cli-authmode-migration.ts +1 -1
- package/src/providers/devin-provider-merge-migration.ts +1 -1
- package/src/providers/github-copilot-auto.ts +169 -0
- package/src/providers/key-failover.ts +52 -5
- package/src/providers/model-presets.ts +1 -1
- package/src/providers/model-rename-startup.ts +5 -3
- package/src/providers/muse-subscription-usage.ts +1 -1
- package/src/providers/quota/account-cache.ts +11 -0
- package/src/providers/quota/anthropic-account-quota.ts +4 -2
- package/src/providers/quota/report-cache.ts +14 -0
- package/src/providers/quota/vendor-probes-oauth.ts +3 -3
- package/src/providers/quota.ts +4 -1
- package/src/providers/registry/entries-core.ts +20 -20
- package/src/providers/registry/entries-extended.ts +4 -1
- package/src/providers/registry/model-seeds.ts +20 -3
- package/src/providers/request-pacing.ts +9 -0
- package/src/providers/stale-no-vision-seed-migration.ts +72 -0
- package/src/providers/xai-fast-model.ts +1 -1
- package/src/responses/compaction.ts +14 -0
- package/src/responses/parser.ts +7 -1
- package/src/server/auth-cors.ts +42 -0
- package/src/server/configured-credentials.ts +194 -0
- package/src/server/inbound-body-admission.ts +24 -7
- package/src/server/index/link-listener.ts +30 -17
- package/src/server/index/link-management.ts +58 -0
- package/src/server/index/optional-listeners.ts +5 -1
- package/src/server/index/serve-options.ts +21 -2
- package/src/server/index.ts +1 -1
- package/src/server/lifecycle.ts +33 -5
- package/src/server/management/agent-settings-routes.ts +28 -10
- package/src/server/management/anthropic-reset-grant-routes.ts +17 -1
- package/src/server/management/codex-agent-role-routes.ts +19 -6
- package/src/server/management/codex-prompt-routes.ts +136 -5
- package/src/server/management/codex-role-auto-assign.ts +1 -1
- package/src/server/management/grok-coupon-routes.ts +333 -57
- package/src/server/management/link-enrollment.ts +192 -0
- package/src/server/management/link-route-state.ts +185 -0
- package/src/server/management/link-routes.ts +92 -390
- package/src/server/management/model-rows.ts +3 -1
- package/src/server/management/provider-overwrite-carry.ts +10 -0
- package/src/server/management/provider-routes.ts +1 -0
- package/src/server/management/route-registry.ts +7 -5
- package/src/server/management/shared.ts +3 -0
- package/src/server/management-api.ts +3 -1
- package/src/server/messages-native.ts +68 -42
- package/src/server/relay-eager.ts +4 -0
- package/src/server/relay.ts +18 -7
- package/src/server/request-log-failure-attribution.ts +3 -0
- package/src/server/request-log-filter.ts +6 -1
- package/src/server/request-log-terminal-status.ts +243 -0
- package/src/server/request-log.ts +15 -118
- package/src/server/responses/adapter-continuation.ts +9 -1
- package/src/server/responses/adapter-dispatch.ts +293 -81
- package/src/server/responses/code-mode-collab-surface.ts +205 -0
- package/src/server/responses/codex-ws-exchange.ts +147 -6
- package/src/server/responses/codex-ws-wire.ts +203 -0
- package/src/server/responses/collaboration.ts +36 -7
- package/src/server/responses/compact.ts +129 -50
- package/src/server/responses/context-overflow.ts +13 -1
- package/src/server/responses/core-codex-account.ts +105 -1
- package/src/server/responses/core-combo-failure.ts +14 -0
- package/src/server/responses/core-combo.ts +7 -2
- package/src/server/responses/core-errors.ts +29 -1
- package/src/server/responses/core-opaque-recovery.ts +5 -5
- package/src/server/responses/core-options.ts +2 -0
- package/src/server/responses/core.ts +6 -6
- package/src/server/responses/deferred-collab-surface.ts +158 -0
- package/src/server/responses/deferred-send-reporter.ts +25 -0
- package/src/server/responses/empty-completion-guard.ts +18 -2
- package/src/server/responses/fetch-helpers.ts +12 -1
- package/src/server/responses/jev-model-invoke.ts +1 -1
- package/src/server/responses/passthrough-delivery.ts +4 -1
- package/src/server/responses/passthrough-dispatch.ts +130 -64
- package/src/server/responses/passthrough-execution.ts +3 -3
- package/src/server/responses/request-prepare.ts +6 -4
- package/src/server/responses/request-transport.ts +131 -13
- package/src/server/responses/run-turn-execution.ts +13 -5
- package/src/server/responses/sidecar-execution.ts +16 -2
- package/src/server/responses/spend-storage-error.ts +33 -0
- package/src/server/responses/ws-upstream.ts +9 -1
- package/src/server/startup-action-control.ts +22 -2
- package/src/server/startup-health-cache.ts +4 -0
- package/src/server/system-env-shell.ts +7 -2
- package/src/server/system-env.ts +11 -1
- package/src/service/cli.ts +5 -1
- package/src/service/desktop-startup.ts +147 -34
- package/src/service/desktop-supervision.d.mts +27 -1
- package/src/service/desktop-supervision.mjs +111 -5
- package/src/service/health-sentinel-launch.ts +176 -0
- package/src/service/health-sentinel-main.ts +447 -0
- package/src/service/health-sentinel-policy.ts +241 -0
- package/src/service/health-sentinel-probe.ts +54 -0
- package/src/service/health-sentinel-windows.ts +87 -0
- package/src/service/linux-linger.ts +20 -0
- package/src/types/config.ts +5 -0
- package/src/types/request.ts +5 -0
- package/src/usage/expected-prices.ts +5 -1
- package/src/usage/log.ts +19 -1
- package/gui/dist/assets/App-Ds_qL7o8.js +0 -54
- package/gui/dist/assets/Tray-BqHF5Trd.js +0 -1
- package/gui/dist/assets/index-D3FBCNiP.js +0 -94
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { MAX_CLIENT_SSE_FRAME_BYTES } from "../sse-frame-buffer";
|
|
2
2
|
import {
|
|
3
|
+
isTransientUpstreamStatus,
|
|
3
4
|
markResponseNonReplayable,
|
|
4
5
|
UPSTREAM_CLOSED_BEFORE_RESPONSE_CODE,
|
|
5
6
|
UPSTREAM_NO_RESPONSE_CODE,
|
|
6
7
|
} from "../../lib/upstream-retry";
|
|
8
|
+
import { isRateLimitOrQuotaFailureMessage, recognizedUpstreamError } from "../../lib/errors";
|
|
7
9
|
import type { RequestFailureCause, RequestFailureStage } from "../../lib/request-failure-model";
|
|
8
10
|
import { packageVersion } from "../../lib/package-version";
|
|
9
11
|
// If the 101 never arrives (network black hole), give SSE a chance well before
|
|
@@ -107,8 +109,209 @@ export type CodexWsStageRecord = Omit<CodexWsFailureStage, "requestBytes"> & {
|
|
|
107
109
|
ocxVersion: string;
|
|
108
110
|
/** Bun runtime version the exchange gated on. */
|
|
109
111
|
bunVersion: string;
|
|
112
|
+
/**
|
|
113
|
+
* Normalized type of the Responses terminal frame that ended a plain exchange, as the upstream
|
|
114
|
+
* sent it and before any tail this proxy synthesizes (#6740). It is what separates an upstream
|
|
115
|
+
* `error` frame from a genuine `response.failed` when both reach a row as the same synthetic 502.
|
|
116
|
+
* Null when no terminal frame arrived, and on native-control exchanges, which can end on an
|
|
117
|
+
* acknowledgement instead. Records written before it existed read as null.
|
|
118
|
+
*/
|
|
119
|
+
terminalEvent: CodexWsTerminalEvent | null;
|
|
120
|
+
/** Wrapper status of a terminal `error` frame ({@link codexWsWrapperStatus}); null otherwise. */
|
|
121
|
+
terminalErrorStatus: number | null;
|
|
110
122
|
};
|
|
111
123
|
|
|
124
|
+
/** The frames a plain exchange ends on, closed so a stage record can never carry free text. */
|
|
125
|
+
export const CODEX_WS_TERMINAL_EVENTS = [
|
|
126
|
+
"response.completed",
|
|
127
|
+
"response.failed",
|
|
128
|
+
"response.incomplete",
|
|
129
|
+
"error",
|
|
130
|
+
] as const;
|
|
131
|
+
export type CodexWsTerminalEvent = typeof CODEX_WS_TERMINAL_EVENTS[number];
|
|
132
|
+
|
|
133
|
+
export function isCodexWsTerminalEvent(value: unknown): value is CodexWsTerminalEvent {
|
|
134
|
+
return typeof value === "string" && (CODEX_WS_TERMINAL_EVENTS as readonly string[]).includes(value);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* The HTTP status a Codex WebSocket `error` frame states on its wrapper, or undefined.
|
|
139
|
+
*
|
|
140
|
+
* The backend wraps a failure as `{type:"error", status|status_code, error}` and the native client
|
|
141
|
+
* turns that status into an HTTP error of the same status
|
|
142
|
+
* (codex-rs/codex-api/src/endpoint/responses_websocket.rs map_wrapped_websocket_error_event). Only
|
|
143
|
+
* that contract is read: a top-level integer in 100..599 under exactly one of the two aliases, the
|
|
144
|
+
* same rule the pre-commit rejection projection applies.
|
|
145
|
+
*/
|
|
146
|
+
export function codexWsWrapperStatus(payload: Record<string, unknown>): number | undefined {
|
|
147
|
+
if (payload.type !== "error") return undefined;
|
|
148
|
+
const hasStatus = Object.hasOwn(payload, "status");
|
|
149
|
+
if (hasStatus === Object.hasOwn(payload, "status_code")) return undefined;
|
|
150
|
+
const status = hasStatus ? payload.status : payload.status_code;
|
|
151
|
+
return typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599
|
|
152
|
+
? status
|
|
153
|
+
: undefined;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function plainRecord(value: unknown): value is Record<string, unknown> {
|
|
157
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** An absent or empty `output` array; anything else may already be visible to the client. */
|
|
161
|
+
function emptyOutput(response: Record<string, unknown>): boolean {
|
|
162
|
+
const output = response.output;
|
|
163
|
+
return output === undefined || (Array.isArray(output) && output.length === 0);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Upper bound on how long an exchange may hold empty lifecycle frames while it waits to see
|
|
168
|
+
* whether the upstream refuses the turn before any output (#6969).
|
|
169
|
+
*
|
|
170
|
+
* Field rows put 97.5% of authoritative refusals within 5 s of the first Responses event and
|
|
171
|
+
* 98.2% within 10 s, with nothing gained after that.
|
|
172
|
+
*/
|
|
173
|
+
export const CODEX_WS_PRE_OUTPUT_REFUSAL_HOLD_MAX_MS = 10_000;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The hold one send may use: at most `maxMs` from the first empty lifecycle frame, and never past
|
|
177
|
+
* `until`, an absolute time.
|
|
178
|
+
*/
|
|
179
|
+
export interface CodexWsPreOutputRefusalHold {
|
|
180
|
+
maxMs: number;
|
|
181
|
+
until: number;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* The hold for a send starting now. The caller's header deadline (`connectTimeoutMs`) starts no
|
|
186
|
+
* earlier than this, so ending every hold by half of it means a held turn always commits well before
|
|
187
|
+
* that deadline could abort it, however late the first lifecycle frame arrives.
|
|
188
|
+
*/
|
|
189
|
+
export function codexWsPreOutputRefusalHold(connectMs: number, now = Date.now()): CodexWsPreOutputRefusalHold {
|
|
190
|
+
const half = Math.max(0, Math.floor(connectMs / 2));
|
|
191
|
+
return { maxMs: Math.min(CODEX_WS_PRE_OUTPUT_REFUSAL_HOLD_MAX_MS, half), until: now + half };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* A lifecycle frame that commits nothing the client could see or act on (#6969): created,
|
|
196
|
+
* in-progress or queued, carrying a response object whose output is absent or empty. A snapshot
|
|
197
|
+
* with any output item, an unknown event, or a frame addressed to another stream is not one.
|
|
198
|
+
*/
|
|
199
|
+
export function isEmptyCodexWsLifecycleEvent(payload: Record<string, unknown>): boolean {
|
|
200
|
+
if (payload.type !== "response.created" && payload.type !== "response.in_progress"
|
|
201
|
+
&& payload.type !== "response.queued") return false;
|
|
202
|
+
if (payload.stream_id !== undefined) return false;
|
|
203
|
+
return plainRecord(payload.response) && emptyOutput(payload.response);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const QUOTA_REFUSAL_IDENTITIES: ReadonlySet<string> = new Set(["usage_limit_reached", "usage_not_included"]);
|
|
207
|
+
|
|
208
|
+
/** Quota and rate-limit identities keep their own routing: every `rate_limit*` and `*quota*` name. */
|
|
209
|
+
function quotaRefusalIdentity(value: string): boolean {
|
|
210
|
+
const name = value.toLowerCase();
|
|
211
|
+
return QUOTA_REFUSAL_IDENTITIES.has(name) || name.startsWith("rate_limit") || name.includes("quota");
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* The status of an authoritative upstream refusal that ended a turn before any output (#6969),
|
|
216
|
+
* or undefined.
|
|
217
|
+
*
|
|
218
|
+
* Only structured evidence counts: a bare `error` frame's single wrapper status (the alias the
|
|
219
|
+
* native client turns into an HTTP error of that status, see {@link codexWsWrapperStatus}), or,
|
|
220
|
+
* with no wrapper alias at all, the recognized overload class; and a `response.failed` with an
|
|
221
|
+
* empty output whose error is the recognized overload class. The status must be a transient
|
|
222
|
+
* gateway status. Message wording never authorizes anything here; it can only veto, so a 5xx whose
|
|
223
|
+
* error names quota or a rate limit keeps the routing it has today.
|
|
224
|
+
*/
|
|
225
|
+
export function codexWsPreOutputRefusalStatus(payload: Record<string, unknown>): number | undefined {
|
|
226
|
+
if (payload.stream_id !== undefined) return undefined;
|
|
227
|
+
let error: unknown;
|
|
228
|
+
let status: number | undefined;
|
|
229
|
+
if (payload.type === "error") {
|
|
230
|
+
error = payload.error;
|
|
231
|
+
if (error !== undefined && !plainRecord(error)) return undefined;
|
|
232
|
+
const statedAlias = Object.hasOwn(payload, "status") || Object.hasOwn(payload, "status_code");
|
|
233
|
+
status = codexWsWrapperStatus(payload);
|
|
234
|
+
// A malformed or doubled alias is not evidence of anything.
|
|
235
|
+
if (statedAlias && status === undefined) return undefined;
|
|
236
|
+
if (status === undefined && plainRecord(error)) status = recognizedUpstreamError(error)?.httpStatus;
|
|
237
|
+
} else if (payload.type === "response.failed") {
|
|
238
|
+
const response = payload.response;
|
|
239
|
+
if (!plainRecord(response) || !emptyOutput(response)) return undefined;
|
|
240
|
+
error = response.error;
|
|
241
|
+
if (!plainRecord(error)) return undefined;
|
|
242
|
+
status = recognizedUpstreamError(error)?.httpStatus;
|
|
243
|
+
} else {
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
if (status === undefined || !isTransientUpstreamStatus(status)) return undefined;
|
|
247
|
+
if (plainRecord(error)) {
|
|
248
|
+
const { code, type, message } = error;
|
|
249
|
+
if ([code, type, message].some(value => value != null && typeof value !== "string")) return undefined;
|
|
250
|
+
if (typeof code === "string" && quotaRefusalIdentity(code)) return undefined;
|
|
251
|
+
if (typeof type === "string" && quotaRefusalIdentity(type)) return undefined;
|
|
252
|
+
if (typeof message === "string" && isRateLimitOrQuotaFailureMessage(message)) return undefined;
|
|
253
|
+
}
|
|
254
|
+
return status;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* The response ids a frame names, for the exchange's replay-eligibility latch (#6969): the nested
|
|
259
|
+
* `response.id` and the top-level `response_id`. Null when a present id is not a string, which the
|
|
260
|
+
* latch treats as a conflict.
|
|
261
|
+
*/
|
|
262
|
+
export function codexWsFrameResponseIds(payload: Record<string, unknown>): string[] | null {
|
|
263
|
+
const ids: string[] = [];
|
|
264
|
+
const nested = plainRecord(payload.response) ? payload.response.id : undefined;
|
|
265
|
+
for (const value of [nested, payload.response_id]) {
|
|
266
|
+
if (value === undefined) continue;
|
|
267
|
+
if (typeof value !== "string") return null;
|
|
268
|
+
if (!ids.includes(value)) ids.push(value);
|
|
269
|
+
}
|
|
270
|
+
return ids;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
interface PreOutputRefusalRecord {
|
|
274
|
+
restore: (() => Response) | null;
|
|
275
|
+
outcomeRecorded: boolean;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const preOutputRefusals = new WeakMap<Response, PreOutputRefusalRecord>();
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Register a pre-output refusal projection (#6969): a replayable gateway status standing in for a
|
|
282
|
+
* WebSocket exchange whose upstream refused the turn before any output. `restore` rebuilds the
|
|
283
|
+
* 200 event stream the client would have received without the projection.
|
|
284
|
+
*/
|
|
285
|
+
export function markCodexWsPreOutputRefusal(projection: Response, restore: () => Response): void {
|
|
286
|
+
preOutputRefusals.set(projection, { restore, outcomeRecorded: false });
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export function isCodexWsPreOutputRefusal(response: Response): boolean {
|
|
290
|
+
return preOutputRefusals.has(response);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** Record that this projection's account outcome was written, so no later owner writes it again. */
|
|
294
|
+
export function noteCodexWsPreOutputRefusalOutcome(response: Response): void {
|
|
295
|
+
const record = preOutputRefusals.get(response);
|
|
296
|
+
if (record) record.outcomeRecorded = true;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function codexWsPreOutputRefusalOutcomeRecorded(response: Response): boolean {
|
|
300
|
+
return preOutputRefusals.get(response)?.outcomeRecorded === true;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* The response the client should receive in place of a projection no recovery replaced: the
|
|
305
|
+
* original event stream, byte for byte. Single use; any other response is returned unchanged.
|
|
306
|
+
*/
|
|
307
|
+
export function restoreCodexWsPreOutputRefusal(response: Response): Response {
|
|
308
|
+
const record = preOutputRefusals.get(response);
|
|
309
|
+
const restore = record?.restore;
|
|
310
|
+
if (!record || !restore) return response;
|
|
311
|
+
record.restore = null;
|
|
312
|
+
return restore();
|
|
313
|
+
}
|
|
314
|
+
|
|
112
315
|
const codexWsStageByResponse = new WeakMap<Response, CodexWsStageRecord>();
|
|
113
316
|
|
|
114
317
|
export function markCodexWsStage(response: Response, record: CodexWsStageRecord): void {
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
import { parseRequest } from "../../responses/parser";
|
|
10
10
|
import { externalTaskInputContent } from "../../responses/task-input";
|
|
11
11
|
import { MULTI_AGENT_MODE_HINT_RECOMMENDATION } from "../../codex/multi-agent-mode-policy";
|
|
12
|
+
import { analyzeCodeModeExec } from "./code-mode-collab-surface";
|
|
13
|
+
import { deferredV1Evidence } from "./deferred-collab-surface";
|
|
12
14
|
import { buildCompactV1Output, COMPACT_PROMPT, decodeCompactionSummary, extractCompactUserMessages } from "../../responses/compaction";
|
|
13
15
|
import { FORWARD_HEADERS, sanitizeReasoningInputContent } from "../../adapters/openai-responses";
|
|
14
16
|
import { expandPreviousResponseInput, previousResponseProviderState, rememberResponseState } from "../../responses/state";
|
|
@@ -316,26 +318,53 @@ export function isV1CollabSurface(parsed: OcxParsedRequest): boolean {
|
|
|
316
318
|
|
|
317
319
|
|
|
318
320
|
export function collabSurface(parsed: OcxParsedRequest): "v1" | "v2" | null {
|
|
321
|
+
const direct = directCollabSurface(parsed.context.tools);
|
|
322
|
+
// #6844: code-mode-only turns nest the collaboration tools inside `exec`, and tool-search turns
|
|
323
|
+
// defer them entirely. A rendered V1 listing or the deferred-namespace fragment can each infer V1;
|
|
324
|
+
// neither ever yields v2, overrides a different direct surface, or survives a rendered foreign
|
|
325
|
+
// collaboration claim.
|
|
326
|
+
const codeMode = analyzeCodeModeExec(parsed.context.tools);
|
|
327
|
+
if (codeMode.renderedV1 && codeMode.foreignCollabClaim) return null;
|
|
328
|
+
const inferredV1 = codeMode.renderedV1 || deferredV1Evidence(parsed, codeMode);
|
|
329
|
+
if (!inferredV1) return direct.surface;
|
|
330
|
+
if (codeMode.foreignCollabClaim) return null;
|
|
331
|
+
return !direct.sawNativeCollabTool || direct.surface === "v1" ? "v1" : null;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function directCollabSurface(tools: OcxParsedRequest["context"]["tools"]): {
|
|
335
|
+
surface: "v1" | "v2" | null;
|
|
336
|
+
sawNativeCollabTool: boolean;
|
|
337
|
+
} {
|
|
319
338
|
let namespacedSpawn = false;
|
|
320
339
|
let flatSpawn = false;
|
|
321
340
|
let v1Only = false;
|
|
322
341
|
let v2Only = false;
|
|
323
|
-
|
|
342
|
+
// Collaboration-named direct tools outside an MCP namespace; an MCP server's own send_message or
|
|
343
|
+
// spawn_agent says nothing about the native surface, matching the nested check.
|
|
344
|
+
let sawNativeCollabTool = false;
|
|
345
|
+
for (const t of tools ?? []) {
|
|
346
|
+
const native = !t.namespace?.startsWith("mcp__");
|
|
324
347
|
if (t.name === "spawn_agent") {
|
|
325
348
|
if (t.namespace) namespacedSpawn = true;
|
|
326
349
|
else flatSpawn = true;
|
|
350
|
+
if (native) sawNativeCollabTool = true;
|
|
327
351
|
} else if (t.name === "send_input" || t.name === "resume_agent" || t.name === "close_agent") {
|
|
328
352
|
v1Only = true;
|
|
353
|
+
if (native) sawNativeCollabTool = true;
|
|
329
354
|
} else if (t.name === "send_message" || t.name === "followup_task" || t.name === "interrupt_agent" || t.name === "list_agents") {
|
|
330
355
|
v2Only = true;
|
|
356
|
+
if (native) sawNativeCollabTool = true;
|
|
331
357
|
}
|
|
332
358
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
359
|
+
const surface = (): "v1" | "v2" | null => {
|
|
360
|
+
if (!namespacedSpawn && !flatSpawn) return null; // no spawn_agent -> no collab surface
|
|
361
|
+
if (namespacedSpawn && flatSpawn) return null; // contradictory spawn shapes
|
|
362
|
+
if (v1Only && v2Only) return null; // contradictory companions
|
|
363
|
+
if (v1Only) return "v1";
|
|
364
|
+
if (v2Only) return "v2";
|
|
365
|
+
return namespacedSpawn ? "v1" : "v2"; // companionless fallbacks (legacy defaults)
|
|
366
|
+
};
|
|
367
|
+
return { surface: surface(), sawNativeCollabTool };
|
|
339
368
|
}
|
|
340
369
|
|
|
341
370
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { spendLedgerStorageErrorResponse } from "./spend-storage-error";
|
|
1
2
|
import { createPhysicalSendReporter } from "../../lib/request-execution-budget";
|
|
2
3
|
import { createInferenceSendBudget } from "../inference/context";
|
|
3
4
|
import { unboundPoolSpendRefusalResponse } from "../workflow-refusal";
|
|
@@ -199,6 +200,7 @@ import {
|
|
|
199
200
|
usesCodexForwardPoolAuth,
|
|
200
201
|
} from "./core";
|
|
201
202
|
import { fetchWithHeaderTimeout, providerFetch, safeHostLabel, safeOriginLabel } from "./fetch-helpers";
|
|
203
|
+
import { shouldRetryCodexPoolAccountQuota, shouldRetryCodexPoolAccountTransient } from "./core-codex-account";
|
|
202
204
|
import { mapCodexAuthContextErrorToResponse, nativeMainRefreshFailureResponse } from "./codex-auth-error";
|
|
203
205
|
import { linkRequestSessionLane, sessionLaneIdFromRequest } from "../request-log-conversation";
|
|
204
206
|
import { recallComboForLane } from "./combo-session-recall";
|
|
@@ -1047,7 +1049,11 @@ export async function handleResponsesCompact(
|
|
|
1047
1049
|
targetKey: compactTargetKey,
|
|
1048
1050
|
});
|
|
1049
1051
|
if (!decision.allowed) return Promise.reject(new SendBudgetExhaustedError(safeHostLabel(compactUrl)));
|
|
1050
|
-
if (!decision.permit.use())
|
|
1052
|
+
if (!decision.permit.use()) {
|
|
1053
|
+
// Nothing was sent, so the reservation goes back rather than holding a send against the request.
|
|
1054
|
+
decision.permit.release();
|
|
1055
|
+
return Promise.reject(new SendBudgetExhaustedError(safeHostLabel(compactUrl)));
|
|
1056
|
+
}
|
|
1051
1057
|
return doFetch();
|
|
1052
1058
|
};
|
|
1053
1059
|
const sendCompactAttempt = (
|
|
@@ -1055,6 +1061,7 @@ export async function handleResponsesCompact(
|
|
|
1055
1061
|
sendHeaders: Headers,
|
|
1056
1062
|
recovery: "normal" | "single",
|
|
1057
1063
|
sendAuthCtx: CodexAuthContext,
|
|
1064
|
+
onPhysicalDispatch?: () => void,
|
|
1058
1065
|
): Promise<Response> => {
|
|
1059
1066
|
const doFetch = (upstreamRecovery?: UpstreamSendRecovery) => fetchWithHeaderTimeout(
|
|
1060
1067
|
compactUrl,
|
|
@@ -1071,6 +1078,7 @@ export async function handleResponsesCompact(
|
|
|
1071
1078
|
modelId: route.modelId,
|
|
1072
1079
|
beforeDispatch: isCanonicalOpenAiForwardProvider(sendProvider)
|
|
1073
1080
|
? createCodexAuthDispatchGuard(sendAuthCtx, config, selectedModelId, admission) : undefined,
|
|
1081
|
+
...(onPhysicalDispatch ? { onPhysicalDispatch } : {}),
|
|
1074
1082
|
}),
|
|
1075
1083
|
// Every credential-bearing forward send gets manual redirects, not only
|
|
1076
1084
|
// pool sends: direct mode carries the caller's credential too (#914).
|
|
@@ -1099,11 +1107,11 @@ export async function handleResponsesCompact(
|
|
|
1099
1107
|
let outcomeCtx = authCtx;
|
|
1100
1108
|
const localDispatchRefusal = (error: unknown): Response | undefined => {
|
|
1101
1109
|
const cause = unwrapUpstreamRetryEvidenceError(error);
|
|
1102
|
-
const response = cause instanceof SendBudgetExhaustedError
|
|
1110
|
+
const response = spendLedgerStorageErrorResponse(error, logCtx) ?? (cause instanceof SendBudgetExhaustedError
|
|
1103
1111
|
? unboundPoolSpendRefusalResponse(logCtx) ?? formatErrorResponse(429, "request_send_budget_exhausted", cause.message)
|
|
1104
1112
|
: mapCodexAuthContextErrorToResponse(cause, {
|
|
1105
1113
|
now: Date.now(), accountSelector: route.codexAccountNamespace,
|
|
1106
|
-
});
|
|
1114
|
+
}));
|
|
1107
1115
|
if (response) {
|
|
1108
1116
|
releaseUpstreamHostAdmission(compactHostAdmissionLease);
|
|
1109
1117
|
compactHostAdmissionLease = null;
|
|
@@ -1216,6 +1224,53 @@ export async function handleResponsesCompact(
|
|
|
1216
1224
|
}
|
|
1217
1225
|
}
|
|
1218
1226
|
|
|
1227
|
+
// The settle for an alternate send that threw, shared by the quota and transient moves. By the
|
|
1228
|
+
// time it runs the request already names the alternate (outcomeCtx), so health goes to it.
|
|
1229
|
+
const failedAlternateCompactSend = (err: unknown): Response => {
|
|
1230
|
+
if (req.signal.aborted) {
|
|
1231
|
+
recordCompactPoolOutcome(outcomeCtx, 499);
|
|
1232
|
+
return formatErrorResponse(499, "client_cancelled", "Client cancelled compact request");
|
|
1233
|
+
}
|
|
1234
|
+
const localRefusal = localDispatchRefusal(err);
|
|
1235
|
+
if (localRefusal) return localRefusal;
|
|
1236
|
+
const outcome = classifyTransportFailureKind(err);
|
|
1237
|
+
// Host-level evidence stands regardless of pool membership (#914 review).
|
|
1238
|
+
if (outcome === "connect_neutral") {
|
|
1239
|
+
if (compactHostCircuitEnabled) {
|
|
1240
|
+
recordUpstreamHostFailure(actualCompactHostKey, {
|
|
1241
|
+
code: transportErrorCode(err),
|
|
1242
|
+
threshold: config.upstreamHostCircuitThreshold,
|
|
1243
|
+
lease: compactHostAdmissionLease,
|
|
1244
|
+
});
|
|
1245
|
+
} else {
|
|
1246
|
+
recordUpstreamHostFailure(actualCompactHostKey, { code: transportErrorCode(err) });
|
|
1247
|
+
}
|
|
1248
|
+
} else {
|
|
1249
|
+
releaseUpstreamHostAdmission(compactHostAdmissionLease);
|
|
1250
|
+
}
|
|
1251
|
+
compactHostAdmissionLease = null;
|
|
1252
|
+
recordCompactPoolOutcome(outcomeCtx, outcome);
|
|
1253
|
+
return formatErrorResponse(502, "upstream_error", "Failed to connect to compact upstream");
|
|
1254
|
+
};
|
|
1255
|
+
const scrubCompactStateForAlternate = (alternateAuthCtx: CodexAuthContext): void => {
|
|
1256
|
+
const binding = conversationStateBindingFromAuth(
|
|
1257
|
+
alternateAuthCtx,
|
|
1258
|
+
(authCtx.kind === "pool" || authCtx.kind === "main-pool")
|
|
1259
|
+
? authCtx.affinityKey
|
|
1260
|
+
: codexPoolAffinityKey(req.headers),
|
|
1261
|
+
);
|
|
1262
|
+
if (!binding) return;
|
|
1263
|
+
for (const body of [raw, compactBody]) {
|
|
1264
|
+
applyAccountChangeConversationStateScrub({
|
|
1265
|
+
body,
|
|
1266
|
+
bindingKey: binding.bindingKey,
|
|
1267
|
+
servingAccountId: binding.accountId,
|
|
1268
|
+
priorAccountId: authCtx.accountId,
|
|
1269
|
+
logCtx,
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
};
|
|
1273
|
+
|
|
1219
1274
|
// Bounded same-request alternate: the regular /v1/responses path already does this
|
|
1220
1275
|
// (core.ts:319-423) and recognizes exactly 429/402. Without it a pool rejection
|
|
1221
1276
|
// surfaces to the client, which retries the compact task OUTSIDE the logical request
|
|
@@ -1316,57 +1371,81 @@ export async function handleResponsesCompact(
|
|
|
1316
1371
|
await upstream.body?.cancel().catch(() => undefined);
|
|
1317
1372
|
outcomeCtx = alternate.authCtx;
|
|
1318
1373
|
logCtx.accountLogLabel = codexAuthContextLogLabel(alternate.authCtx, config);
|
|
1319
|
-
|
|
1320
|
-
const binding = conversationStateBindingFromAuth(
|
|
1321
|
-
alternate.authCtx,
|
|
1322
|
-
(authCtx.kind === "pool" || authCtx.kind === "main-pool")
|
|
1323
|
-
? authCtx.affinityKey
|
|
1324
|
-
: codexPoolAffinityKey(req.headers),
|
|
1325
|
-
);
|
|
1326
|
-
if (binding) {
|
|
1327
|
-
applyAccountChangeConversationStateScrub({
|
|
1328
|
-
body: raw,
|
|
1329
|
-
bindingKey: binding.bindingKey,
|
|
1330
|
-
servingAccountId: binding.accountId,
|
|
1331
|
-
priorAccountId: authCtx.accountId,
|
|
1332
|
-
logCtx,
|
|
1333
|
-
});
|
|
1334
|
-
applyAccountChangeConversationStateScrub({
|
|
1335
|
-
body: compactBody,
|
|
1336
|
-
bindingKey: binding.bindingKey,
|
|
1337
|
-
servingAccountId: binding.accountId,
|
|
1338
|
-
priorAccountId: authCtx.accountId,
|
|
1339
|
-
logCtx,
|
|
1340
|
-
});
|
|
1341
|
-
}
|
|
1342
|
-
}
|
|
1374
|
+
scrubCompactStateForAlternate(alternate.authCtx);
|
|
1343
1375
|
try {
|
|
1344
1376
|
upstream = await sendCompactAttempt(alternate.provider, alternate.headers, "single", alternate.authCtx);
|
|
1345
1377
|
} catch (err) {
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1378
|
+
return failedAlternateCompactSend(err);
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
// A transient 5xx that survived the same-account ladder above (#6969). The regular path moves
|
|
1383
|
+
// it to another Pool account; compact used to move only on 429/402, so the client retried the
|
|
1384
|
+
// compact task outside this request while another account sat idle. One bounded alternate send,
|
|
1385
|
+
// the same guards as the quota move, and a 5xx whose body confirms quota keeps today's path.
|
|
1386
|
+
if (
|
|
1387
|
+
shouldRetryCodexPoolAccountTransient(upstream)
|
|
1388
|
+
&& !storedPool401ReplayAttempted
|
|
1389
|
+
&& usesCodexForwardPoolAuth(authCtx, route.provider)
|
|
1390
|
+
&& !authCtx.fixedAccount
|
|
1391
|
+
&& route.codexAccountMode
|
|
1392
|
+
&& outcomeCtx === authCtx
|
|
1393
|
+
&& !req.signal.aborted
|
|
1394
|
+
&& !conversationCarriesUploadedFiles(raw)
|
|
1395
|
+
&& !await shouldRetryCodexPoolAccountQuota(upstream, req.signal)
|
|
1396
|
+
) {
|
|
1397
|
+
const firstStatus = upstream.status;
|
|
1398
|
+
const firstRetryAfter = upstream.headers.get("retry-after");
|
|
1399
|
+
const firstResetAt = [
|
|
1400
|
+
upstream.headers.get("x-codex-primary-reset-at"),
|
|
1401
|
+
upstream.headers.get("x-codex-secondary-reset-at"),
|
|
1402
|
+
upstream.headers.get("x-codex-tertiary-reset-at"),
|
|
1403
|
+
].filter(Boolean);
|
|
1404
|
+
const alternate = await resolveAlternateCompactContext({
|
|
1405
|
+
req,
|
|
1406
|
+
admission,
|
|
1407
|
+
config,
|
|
1408
|
+
route,
|
|
1409
|
+
selectedModelId,
|
|
1410
|
+
excludeAccountId: authCtx.accountId,
|
|
1411
|
+
turnAdmissionLease,
|
|
1412
|
+
});
|
|
1413
|
+
if (alternate && req.signal.aborted) {
|
|
1414
|
+
releaseCodexAuthContextProbeLease(alternate.authCtx);
|
|
1415
|
+
recordCompactPoolOutcome(outcomeCtx, 499);
|
|
1416
|
+
void upstream.body?.cancel(req.signal.reason).catch(() => undefined);
|
|
1417
|
+
return formatErrorResponse(499, "client_cancelled", "Client cancelled compact request");
|
|
1418
|
+
}
|
|
1419
|
+
if (alternate) {
|
|
1420
|
+
const firstLabel = logCtx.accountLogLabel;
|
|
1421
|
+
// B's identity before B's admission, so every budget and spend check runs against B. A's
|
|
1422
|
+
// response stays intact until B is actually sent.
|
|
1423
|
+
logCtx.accountLogLabel = codexAuthContextLogLabel(alternate.authCtx, config);
|
|
1424
|
+
scrubCompactStateForAlternate(alternate.authCtx);
|
|
1425
|
+
const recordFirstMove = () => {
|
|
1426
|
+
recordCompactPoolOutcome(authCtx, firstStatus, { retryAfter: firstRetryAfter, resetAt: firstResetAt });
|
|
1427
|
+
void upstream.body?.cancel().catch(() => undefined);
|
|
1428
|
+
outcomeCtx = alternate.authCtx;
|
|
1429
|
+
};
|
|
1430
|
+
let alternateResponse: Response | undefined;
|
|
1431
|
+
// Whether B's send actually left this process. Every local refusal before that point -- a
|
|
1432
|
+
// spent budget, a dispatch guard such as disabled credits, a pacing refusal -- keeps A.
|
|
1433
|
+
let alternateDispatched = false;
|
|
1434
|
+
try {
|
|
1435
|
+
alternateResponse = await sendCompactAttempt(alternate.provider, alternate.headers, "single", alternate.authCtx,
|
|
1436
|
+
() => { alternateDispatched = true; });
|
|
1437
|
+
} catch (err) {
|
|
1438
|
+
if (alternateDispatched) {
|
|
1439
|
+
recordFirstMove();
|
|
1440
|
+
return failedAlternateCompactSend(err);
|
|
1366
1441
|
}
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1442
|
+
// Refused before anything left: A's answer and identity stand, recorded once below.
|
|
1443
|
+
logCtx.accountLogLabel = firstLabel;
|
|
1444
|
+
releaseCodexAuthContextProbeLease(alternate.authCtx);
|
|
1445
|
+
}
|
|
1446
|
+
if (alternateResponse) {
|
|
1447
|
+
recordFirstMove();
|
|
1448
|
+
upstream = alternateResponse;
|
|
1370
1449
|
}
|
|
1371
1450
|
}
|
|
1372
1451
|
}
|
|
@@ -1,7 +1,19 @@
|
|
|
1
|
-
import { bridgeToResponsesSSE } from "../../bridge";
|
|
1
|
+
import { bridgeToResponsesSSE, formatErrorResponse } from "../../bridge";
|
|
2
|
+
import { OLLAMA_LATE_ATTRIBUTION_LIMIT_MESSAGE } from "../../adapters/ollama-native";
|
|
2
3
|
import type { TranslatorBudget } from "../../lib/translator-budget";
|
|
3
4
|
import type { AdapterEvent } from "../../types";
|
|
4
5
|
|
|
6
|
+
/**
|
|
7
|
+
* The Ollama late-attribution refusal as a client response: the existing translator-budget
|
|
8
|
+
* wire shape (413 request_too_large) with only the proxy-owned constant as its message.
|
|
9
|
+
* It is deliberately not a context overflow: compacting on the same route cannot shrink it.
|
|
10
|
+
*/
|
|
11
|
+
export function lateAttributionLimitResponse(): Response {
|
|
12
|
+
return formatErrorResponse(413, "request_too_large", OLLAMA_LATE_ATTRIBUTION_LIMIT_MESSAGE, {
|
|
13
|
+
code: "translation_buffer_limit",
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
5
17
|
export const PROVIDER_INPUT_TOO_LARGE_MESSAGE =
|
|
6
18
|
"The provider rejected this turn because its input exceeds the provider size or context limit. Reduce the current input or compact the conversation before retrying.";
|
|
7
19
|
|