@bitkyc08/opencodex 2.55.0 → 2.57.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/bin/ocx.mjs +10 -0
- package/gui/dist/assets/{index-BBOZWGB6.css → index-C5-RdDmD.css} +1 -1
- package/gui/dist/assets/{index-VuoiWj9J.js → index-Cz7CLdif.js} +21 -21
- package/gui/dist/index.html +2 -2
- package/package.json +4 -3
- package/src/adapters/base.ts +21 -0
- package/src/adapters/codebuddy/adapter.ts +2 -1
- package/src/adapters/codebuddy/scaffold-guard.ts +248 -0
- package/src/adapters/command-code.ts +1 -1
- package/src/adapters/cursor/envelope-echo.ts +8 -2
- package/src/adapters/cursor/transport-retry.ts +46 -1
- package/src/adapters/cursor.ts +4 -0
- package/src/adapters/google.ts +7 -7
- package/src/adapters/kiro/adapter.ts +42 -1
- package/src/adapters/kiro/payload.ts +17 -3
- package/src/adapters/kiro/reasoning.ts +70 -7
- package/src/adapters/kiro/stream.ts +8 -2
- package/src/adapters/kiro/wire.ts +2 -1
- package/src/adapters/kiro-events.ts +21 -13
- package/src/adapters/kiro-retry.ts +23 -4
- package/src/adapters/openai-chat/errors.ts +116 -0
- package/src/adapters/openai-chat/messages.ts +346 -0
- package/src/adapters/openai-chat/passthrough.ts +146 -0
- package/src/adapters/openai-chat/response-events.ts +117 -0
- package/src/adapters/openai-chat/tool-call-validation.ts +200 -0
- package/src/adapters/openai-chat/tool-name-registry.ts +166 -0
- package/src/adapters/openai-chat/tool-schema.ts +495 -0
- package/src/adapters/openai-chat/wire.ts +50 -0
- package/src/adapters/openai-chat.ts +40 -1452
- package/src/adapters/openai-responses/canonical-forward.ts +202 -0
- package/src/adapters/openai-responses/image-gen.ts +406 -0
- package/src/adapters/openai-responses/internal.ts +3 -0
- package/src/adapters/openai-responses/passthrough.ts +642 -0
- package/src/adapters/openai-responses/prompt-cache.ts +83 -0
- package/src/adapters/openai-responses/reasoning.ts +220 -0
- package/src/adapters/openai-responses/request-strips.ts +185 -0
- package/src/adapters/openai-responses/tool-output-recovery.ts +509 -0
- package/src/adapters/openai-responses/tool-schema.ts +293 -0
- package/src/adapters/openai-responses/web-search.ts +156 -0
- package/src/adapters/openai-responses.ts +4 -2625
- package/src/bridge/errors.ts +58 -0
- package/src/bridge/internal.ts +174 -0
- package/src/bridge/response-json.ts +630 -0
- package/src/bridge/sse.ts +1462 -0
- package/src/bridge.ts +5 -2204
- package/src/chat/inbound.ts +12 -1
- package/src/claude/desktop-profile.ts +66 -9
- package/src/claude/outbound.ts +18 -0
- package/src/cli/account-main.ts +1 -1
- package/src/cli/capabilities.ts +2 -2
- package/src/cli/combo.ts +10 -1
- package/src/cli/index.ts +48 -5
- package/src/cli/registry.ts +2 -1
- package/src/cli/system-command.ts +4 -4
- package/src/clients/config-export.ts +7 -3
- package/src/codex/account-label.ts +14 -3
- package/src/codex/account-lifecycle.ts +3 -0
- package/src/codex/account-store.ts +184 -35
- package/src/codex/account-usability.ts +21 -0
- package/src/codex/auth-api/account-list.ts +507 -0
- package/src/codex/auth-api/http.ts +32 -0
- package/src/codex/auth-api/login-flow.ts +566 -0
- package/src/codex/auth-api/login-state.ts +64 -0
- package/src/codex/auth-api/main-account-probe.ts +331 -0
- package/src/codex/auth-api/pool-mode-gate.ts +274 -0
- package/src/codex/auth-api/pool-quota-probe.ts +512 -0
- package/src/codex/auth-api/reset-credit-service.ts +431 -0
- package/src/codex/auth-api/routes.ts +425 -0
- package/src/codex/auth-api/runtime-config.ts +48 -0
- package/src/codex/auth-api.ts +27 -3118
- package/src/codex/auth-context.ts +252 -35
- package/src/codex/catalog/aggregation.ts +80 -1
- package/src/codex/catalog/auto-review.ts +507 -0
- package/src/codex/catalog/build-entries.ts +981 -0
- package/src/codex/catalog/combo-member.ts +375 -0
- package/src/codex/catalog/derive-entry.ts +229 -0
- package/src/codex/catalog/effort.ts +0 -1
- package/src/codex/catalog/gated-native-warn.ts +63 -0
- package/src/codex/catalog/gather-capture.ts +533 -0
- package/src/codex/catalog/model-hints.ts +691 -0
- package/src/codex/catalog/model-visibility.ts +305 -0
- package/src/codex/catalog/provider-fetch.ts +52 -2942
- package/src/codex/catalog/provider-models.ts +685 -0
- package/src/codex/catalog/remote.ts +30 -0
- package/src/codex/catalog/restore.ts +132 -0
- package/src/codex/catalog/retained-sync.ts +714 -0
- package/src/codex/catalog/routed-gather.ts +895 -0
- package/src/codex/catalog/subagent-roster.ts +176 -0
- package/src/codex/catalog/sync.ts +52 -2698
- package/src/codex/cli-install-provenance.ts +7 -1
- package/src/codex/convergence.ts +7 -2
- package/src/codex/desktop-app/types.ts +11 -2
- package/src/codex/desktop-app/windows.ts +5 -5
- package/src/codex/inject/config-toml.ts +563 -0
- package/src/codex/inject/remove.ts +192 -0
- package/src/codex/inject/restore.ts +567 -0
- package/src/codex/inject/routing-classify.ts +109 -0
- package/src/codex/inject/routing-target.ts +125 -0
- package/src/codex/inject.ts +89 -1444
- package/src/codex/lineage.ts +458 -0
- package/src/codex/model-entitlements.ts +152 -15
- package/src/codex/pool-refresh-backoff.ts +161 -0
- package/src/codex/quota-rejection.ts +104 -15
- package/src/codex/routing/active-account.ts +194 -0
- package/src/codex/routing/cache-affinity.ts +70 -0
- package/src/codex/routing/cooldown-math.ts +285 -0
- package/src/codex/routing/health-store.ts +402 -0
- package/src/codex/routing/probe-lease.ts +358 -0
- package/src/codex/routing/selection.ts +780 -0
- package/src/codex/routing/thread-affinity.ts +586 -0
- package/src/codex/routing/transient-hold-dispatch.ts +141 -0
- package/src/codex/routing.ts +370 -2271
- package/src/codex/shim-fingerprint.ts +223 -0
- package/src/codex/shim-inspect.ts +175 -0
- package/src/codex/shim-probe.ts +367 -0
- package/src/codex/shim-restore-lock.ts +169 -0
- package/src/codex/shim-state-file.ts +151 -0
- package/src/codex/shim-templates.ts +265 -0
- package/src/codex/shim.ts +48 -1268
- package/src/codex/warmup.ts +1 -1
- package/src/combos/failover.ts +85 -0
- package/src/combos/request.ts +17 -10
- package/src/combos/types.ts +23 -2
- package/src/config/diagnostics.ts +705 -0
- package/src/config/feature-flags.ts +55 -0
- package/src/config/live-reconcile.ts +403 -0
- package/src/config/load-degrade.ts +880 -0
- package/src/config/mutation-lock.ts +244 -0
- package/src/config/openai-tier-backup.ts +268 -0
- package/src/config/pending-teardown.ts +31 -0
- package/src/config/persist-unlocked.ts +92 -0
- package/src/config/proxy-env.ts +188 -0
- package/src/config/salvage.ts +244 -0
- package/src/config/schema/config-schema.ts +640 -0
- package/src/config/schema/leaf-validators.ts +855 -0
- package/src/config/warn-memo.ts +28 -0
- package/src/config.ts +234 -4481
- package/src/generated/compatibility-version.json +649 -121
- package/src/images/loop.ts +1 -1
- package/src/lib/errors.ts +17 -0
- package/src/lib/request-execution-budget.ts +198 -23
- package/src/lib/spend-reservation-ledger.ts +958 -0
- package/src/lib/state-store-registrations.ts +6 -2
- package/src/lib/test-home-guard.ts +85 -1
- package/src/lib/upstream-retry.ts +132 -21
- package/src/lib/windows-elevation.ts +76 -14
- package/src/lib/workflow-budget.ts +553 -30
- package/src/oauth/index.ts +2 -2
- package/src/oauth/key-providers.ts +2 -2
- package/src/providers/kiro-models.ts +4 -3
- package/src/providers/label.ts +19 -1
- package/src/providers/model-discovery.ts +16 -0
- package/src/providers/quota/account-cache.ts +441 -0
- package/src/providers/quota/antigravity.ts +295 -0
- package/src/providers/quota/report-cache.ts +320 -0
- package/src/providers/quota/vendor-probes-key.ts +1243 -0
- package/src/providers/quota/vendor-probes-oauth.ts +590 -0
- package/src/providers/quota.ts +324 -3079
- package/src/providers/registry/entries-core.ts +1228 -0
- package/src/providers/registry/entries-extended.ts +1213 -0
- package/src/providers/registry/model-seeds.ts +912 -0
- package/src/providers/registry/types.ts +352 -0
- package/src/providers/registry.ts +24 -3536
- package/src/responses/continuation-ownership.ts +29 -0
- package/src/responses/reasoning-envelope.ts +6 -3
- package/src/responses/state/replay-fingerprint.ts +80 -0
- package/src/responses/state/snapshot-codec.ts +104 -0
- package/src/responses/state/spill-failure.ts +118 -0
- package/src/responses/state/spill-queue.ts +665 -0
- package/src/responses/state/temp-recovery.ts +257 -0
- package/src/responses/state.ts +82 -1143
- package/src/routing/identity-domains.ts +456 -0
- package/src/routing/probe-lease.ts +613 -0
- package/src/server/chat-completions.ts +3 -1
- package/src/server/chat-native.ts +37 -9
- package/src/server/index/bounded-request.ts +88 -0
- package/src/server/index/live-sideband.ts +601 -0
- package/src/server/index/serve-options.ts +1766 -0
- package/src/server/index/startup-warnings.ts +213 -0
- package/src/server/index/websocket-handler.ts +339 -0
- package/src/server/index.ts +45 -2552
- package/src/server/inspection-tee.ts +107 -0
- package/src/server/live.ts +46 -1
- package/src/server/management/combo-routes.ts +10 -1
- package/src/server/management/route-registry.ts +26 -23
- package/src/server/management/shared.ts +8 -5
- package/src/server/management/workflow-budget-routes.ts +133 -0
- package/src/server/management-api.ts +12 -0
- package/src/server/relay-eager.ts +2 -0
- package/src/server/relay.ts +14 -19
- package/src/server/request-log-conversation.ts +9 -7
- package/src/server/request-log.ts +372 -4
- package/src/server/response-log-body.ts +153 -0
- package/src/server/responses/account-change-state.ts +307 -0
- package/src/server/responses/adapter-continuation.ts +540 -0
- package/src/server/responses/adapter-delivery.ts +208 -0
- package/src/server/responses/adapter-dispatch.ts +1042 -0
- package/src/server/responses/codex-ws-wire.ts +5 -0
- package/src/server/responses/collaboration.ts +74 -4
- package/src/server/responses/combo-session-recall.ts +68 -8
- package/src/server/responses/compact.ts +113 -17
- package/src/server/responses/completion-policy.ts +33 -0
- package/src/server/responses/core-auth.ts +529 -0
- package/src/server/responses/core-codex-account.ts +907 -0
- package/src/server/responses/core-combo-failure.ts +210 -0
- package/src/server/responses/core-combo.ts +787 -0
- package/src/server/responses/core-errors.ts +170 -0
- package/src/server/responses/core-lifetime.ts +95 -0
- package/src/server/responses/core-normalize.ts +350 -0
- package/src/server/responses/core-opaque-recovery.ts +380 -0
- package/src/server/responses/core-options.ts +159 -0
- package/src/server/responses/core-replay.ts +298 -0
- package/src/server/responses/core.ts +192 -8893
- package/src/server/responses/encrypted-payload.ts +0 -1
- package/src/server/responses/input-admission.ts +126 -6
- package/src/server/responses/passthrough-delivery.ts +869 -0
- package/src/server/responses/passthrough-dispatch.ts +1494 -0
- package/src/server/responses/passthrough-error.ts +38 -2
- package/src/server/responses/passthrough-execution.ts +54 -0
- package/src/server/responses/request-prepare.ts +1080 -0
- package/src/server/responses/request-send-budget.ts +259 -0
- package/src/server/responses/request-sidecar-auth.ts +149 -0
- package/src/server/responses/request-spend.ts +147 -0
- package/src/server/responses/request-transport.ts +803 -0
- package/src/server/responses/response-effects.ts +157 -0
- package/src/server/responses/run-turn-execution.ts +476 -0
- package/src/server/responses/sidecar-execution.ts +463 -0
- package/src/server/responses/terminal-guard.ts +65 -4
- package/src/server/responses-image-gen-repair.ts +1 -1
- package/src/server/responses-undeclared-tool-guard.ts +9 -5
- package/src/server/workflow-refusal.ts +84 -0
- package/src/service/windows-ops.ts +210 -16
- package/src/service/windows-scheduler.ts +28 -21
- package/src/service.ts +1 -1
- package/src/types/config.ts +34 -1
- package/src/types/request.ts +8 -5
- package/src/types/tools.ts +24 -0
- package/src/types.ts +2 -0
- package/src/update/index.ts +10 -0
- package/src/update/stop-contract.d.mts +1 -0
- package/src/update/stop-contract.mjs +19 -0
- package/src/update/stop-decision.d.mts +1 -1
- package/src/update/stop-decision.mjs +12 -3
- package/src/usage/log.ts +147 -1
- package/src/usage/summary.ts +171 -21
- package/src/vision/anthropic-describe.ts +1 -1
- package/src/vision/describe.ts +5 -5
- package/src/web-search/anthropic-executor.ts +1 -1
- package/src/web-search/exa-executor.ts +1 -1
- package/src/web-search/executor.ts +1 -1
- package/src/web-search/gemini-executor.ts +1 -1
- package/src/web-search/loop.ts +1 -1
- package/src/web-search/ollama-executor.ts +1 -1
- package/src/web-search/parse.ts +67 -14
- package/src/web-search/passthrough-bridge.ts +64 -31
- package/src/web-search/xai-executor.ts +1 -1
package/src/web-search/parse.ts
CHANGED
|
@@ -12,7 +12,7 @@ export type WebSearchSource = SafeWebSearchSource;
|
|
|
12
12
|
export interface WebSearchResult {
|
|
13
13
|
text: string;
|
|
14
14
|
sources: WebSearchSource[];
|
|
15
|
-
/** Set
|
|
15
|
+
/** Set when the stream failed, including when partial text was decoded before failure. */
|
|
16
16
|
error?: string;
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -31,9 +31,12 @@ interface OutputItem {
|
|
|
31
31
|
content?: OutputTextBlock[];
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
//
|
|
35
|
-
// streamed response here, before decoded text and authoritative/delta copies can accumulate.
|
|
34
|
+
// Keep this compatibility export for non-Responses sidecar executors and bounded error bodies.
|
|
36
35
|
export const MAX_SIDECAR_RESPONSE_BYTES = 64 * 1024;
|
|
36
|
+
// Responses SSE can spend far more wire bytes on JSON framing than on useful model output. Keep a
|
|
37
|
+
// larger finite wire ceiling while separately bounding the decoded text copies accumulated below.
|
|
38
|
+
export const MAX_SIDECAR_STREAM_BYTES = MAX_SIDECAR_RESPONSE_BYTES * 16;
|
|
39
|
+
export const MAX_SIDECAR_DECODED_CHARS = 64 * 1024;
|
|
37
40
|
|
|
38
41
|
/** Push a `url_citation` annotation as a source, de-duplicated by URL. */
|
|
39
42
|
function collectAnnotation(ann: AnnotationLike | undefined, sources: WebSearchSource[], seen: Set<string>): void {
|
|
@@ -208,10 +211,11 @@ export function cancelReaderWithoutWaiting(
|
|
|
208
211
|
* `response.output_text.done` text; falls back to accumulated `response.output_text.delta`. Sources are
|
|
209
212
|
* collected from EVERY shape they arrive in — `response.output_text.annotation.added` events (the
|
|
210
213
|
* streaming path, which earlier testing missed → empty citations), `done`-block `annotations[]`, and
|
|
211
|
-
* the final output[].
|
|
214
|
+
* the final output[]. A terminal failure, a safety bound, or EOF before a terminal event surfaces as
|
|
215
|
+
* `error` even when partial answer text was decoded.
|
|
212
216
|
*/
|
|
213
217
|
export async function parseSidecarSSE(response: Response): Promise<WebSearchResult> {
|
|
214
|
-
if (!response.body) return { text: "", sources: [] };
|
|
218
|
+
if (!response.body) return { text: "", sources: [], error: "sidecar stream returned no response body" };
|
|
215
219
|
const reader = response.body.getReader();
|
|
216
220
|
const decoder = new TextDecoder();
|
|
217
221
|
let buffer = "";
|
|
@@ -224,10 +228,37 @@ export async function parseSidecarSSE(response: Response): Promise<WebSearchResu
|
|
|
224
228
|
final: WebSearchResult | null;
|
|
225
229
|
streamSources: WebSearchSource[];
|
|
226
230
|
error: string | null;
|
|
227
|
-
|
|
231
|
+
decodedChars: number;
|
|
232
|
+
terminalEvent: boolean;
|
|
233
|
+
limitReached: boolean;
|
|
234
|
+
} = {
|
|
235
|
+
deltaText: "",
|
|
236
|
+
doneText: "",
|
|
237
|
+
final: null,
|
|
238
|
+
streamSources: [],
|
|
239
|
+
error: null,
|
|
240
|
+
decodedChars: 0,
|
|
241
|
+
terminalEvent: false,
|
|
242
|
+
limitReached: false,
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const acceptDecodedChars = (count: number): boolean => {
|
|
246
|
+
if (count > MAX_SIDECAR_DECODED_CHARS - acc.decodedChars) {
|
|
247
|
+
acc.error = "sidecar response decoded text limit reached";
|
|
248
|
+
acc.limitReached = true;
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
acc.decodedChars += count;
|
|
252
|
+
return true;
|
|
253
|
+
};
|
|
228
254
|
|
|
229
255
|
const handle = (payload: string): void => {
|
|
230
|
-
if (!payload
|
|
256
|
+
if (!payload) return;
|
|
257
|
+
if (payload === "[DONE]") {
|
|
258
|
+
acc.terminalEvent = true;
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (acc.limitReached) return;
|
|
231
262
|
// Neither warning below copies the frame's content. An upstream SSE payload can carry model
|
|
232
263
|
// output or credential material, and a malformed frame is exactly the case where the content
|
|
233
264
|
// is least trustworthy. Length plus a classification separates the two failure modes in a log
|
|
@@ -247,19 +278,27 @@ export async function parseSidecarSSE(response: Response): Promise<WebSearchResu
|
|
|
247
278
|
const data = parsed as Record<string, unknown>;
|
|
248
279
|
const type = data.type as string | undefined;
|
|
249
280
|
if (type === "response.output_text.delta" && typeof data.delta === "string") {
|
|
250
|
-
acc.deltaText += data.delta;
|
|
281
|
+
if (acceptDecodedChars(data.delta.length)) acc.deltaText += data.delta;
|
|
251
282
|
} else if (type === "response.output_text.done" && typeof data.text === "string") {
|
|
252
283
|
// The `done` event carries the full, authoritative text for one content part.
|
|
253
|
-
acc.doneText += data.text;
|
|
284
|
+
if (acceptDecodedChars(data.text.length)) acc.doneText += data.text;
|
|
254
285
|
} else if (type === "response.completed" || type === "response.done") {
|
|
286
|
+
acc.terminalEvent = true;
|
|
255
287
|
const resp = data.response as { output?: OutputItem[] } | undefined;
|
|
256
|
-
if (resp?.output)
|
|
288
|
+
if (resp?.output) {
|
|
289
|
+
const final = fromOutputArray(resp.output, seen);
|
|
290
|
+
if (acceptDecodedChars(final.text.length)) acc.final = final;
|
|
291
|
+
}
|
|
257
292
|
} else if (type === "response.failed" || type === "response.incomplete" || type === "error") {
|
|
293
|
+
acc.terminalEvent = true;
|
|
258
294
|
const resp = data.response as { error?: { message?: string } } | undefined;
|
|
259
295
|
const msg = resp?.error?.message
|
|
260
296
|
?? (data.error as { message?: string } | undefined)?.message
|
|
261
297
|
?? (typeof data.message === "string" ? data.message : undefined);
|
|
262
|
-
|
|
298
|
+
acc.error = msg ?? `sidecar stream ended with ${type}`;
|
|
299
|
+
} else if (type?.includes("reasoning") && typeof data.delta === "string") {
|
|
300
|
+
// Reasoning is not returned, but it is still decoded payload retained transiently by JSON.parse.
|
|
301
|
+
acceptDecodedChars(data.delta.length);
|
|
263
302
|
}
|
|
264
303
|
// Citations stream as a dedicated `response.output_text.annotation.added` event (singular
|
|
265
304
|
// `annotation`); capture it regardless of the exact event name so they aren't lost.
|
|
@@ -270,7 +309,7 @@ export async function parseSidecarSSE(response: Response): Promise<WebSearchResu
|
|
|
270
309
|
while (true) {
|
|
271
310
|
const { done, value } = await reader.read();
|
|
272
311
|
if (done) break;
|
|
273
|
-
const remaining =
|
|
312
|
+
const remaining = MAX_SIDECAR_STREAM_BYTES - responseBytes;
|
|
274
313
|
const accepted = value.byteLength <= remaining ? value : value.subarray(0, remaining);
|
|
275
314
|
responseBytes += accepted.byteLength;
|
|
276
315
|
buffer += decoder.decode(accepted, { stream: true });
|
|
@@ -280,11 +319,24 @@ export async function parseSidecarSSE(response: Response): Promise<WebSearchResu
|
|
|
280
319
|
const data = sseFieldValue(line, "data");
|
|
281
320
|
if (data !== null) handle(data.trim());
|
|
282
321
|
}
|
|
283
|
-
if (
|
|
322
|
+
if (acc.limitReached) {
|
|
323
|
+
cancelReaderWithoutWaiting(reader, "sidecar response decoded text limit reached");
|
|
324
|
+
buffer = "";
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
if (acc.terminalEvent) {
|
|
328
|
+
cancelReaderWithoutWaiting(reader, "sidecar terminal event received");
|
|
329
|
+
buffer = "";
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
if (responseBytes >= MAX_SIDECAR_STREAM_BYTES) {
|
|
284
333
|
// Preserve complete events accepted up to the cap, but discard any unterminated line and
|
|
285
334
|
// TextDecoder carry. Do not let a rejecting/hung cancel turn bounded partial output into
|
|
286
335
|
// an error or keep this parser waiting on upstream teardown.
|
|
287
336
|
cancelReaderWithoutWaiting(reader, "sidecar response byte limit reached");
|
|
337
|
+
acc.error = "sidecar response byte limit reached before terminal event";
|
|
338
|
+
acc.limitReached = true;
|
|
339
|
+
buffer = "";
|
|
288
340
|
break;
|
|
289
341
|
}
|
|
290
342
|
}
|
|
@@ -310,6 +362,7 @@ export async function parseSidecarSSE(response: Response): Promise<WebSearchResu
|
|
|
310
362
|
appendSafeWebSearchSource(sources, s);
|
|
311
363
|
}
|
|
312
364
|
const finalText = stripped ? body : (typeof text === "string" ? text : "");
|
|
313
|
-
|
|
365
|
+
const error = acc.error ?? (!acc.terminalEvent ? "sidecar stream ended before terminal event" : null);
|
|
366
|
+
if (error) return { text: finalText, sources, error };
|
|
314
367
|
return { text: finalText, sources };
|
|
315
368
|
}
|
|
@@ -128,6 +128,22 @@ const MAX_QUERIES_PER_CALL = 3;
|
|
|
128
128
|
const MAX_RETAINED_OUTPUT_ITEMS = 500;
|
|
129
129
|
/** Refuse to buffer an unbounded partial SSE event from a misbehaving upstream. */
|
|
130
130
|
const MAX_SSE_BUFFER_CHARS = 8 * 1024 * 1024;
|
|
131
|
+
/** UTF-16 code units in SSE data payloads, not a byte or total-heap measurement. */
|
|
132
|
+
const MAX_HELD_CALL_CHARS = 8 * 1024 * 1024;
|
|
133
|
+
/**
|
|
134
|
+
* Derived from MAX_HELD_CALL_CHARS rather than picked, so the two bounds bind at the same
|
|
135
|
+
* scale. The character budget is the real memory guard; this count only adds the per-event
|
|
136
|
+
* object overhead the character budget cannot see. A fine-grained argument delta serializes
|
|
137
|
+
* to roughly 128 code units -- an envelope of about 110 characters carrying the item id and
|
|
138
|
+
* output index, plus a token-sized fragment -- so 8 MiB of them is 65,536 events. The count
|
|
139
|
+
* therefore bites only for events smaller than that average. A flat 1,000 discarded a
|
|
140
|
+
* legitimate client-executed tool call: a sizeable apply_patch streamed as fine-grained
|
|
141
|
+
* deltas is ordinary, not exotic, and failing its leg trades one failure for another.
|
|
142
|
+
*/
|
|
143
|
+
export const MAX_HELD_CALL_EVENTS = MAX_HELD_CALL_CHARS / 128;
|
|
144
|
+
|
|
145
|
+
/** A proxy-side admission bound, never an upstream transport failure. */
|
|
146
|
+
class HeldCallBudgetExceededError extends Error {}
|
|
131
147
|
|
|
132
148
|
/**
|
|
133
149
|
* Retained for importers that pinned the first slice's contract: a leg mixing the search with
|
|
@@ -488,6 +504,7 @@ class BridgeStreamState {
|
|
|
488
504
|
* failing the turn would let Codex start running a tool for a turn that never completes.
|
|
489
505
|
*/
|
|
490
506
|
private heldCalls: HeldCallEvent[] = [];
|
|
507
|
+
private heldCallChars = 0;
|
|
491
508
|
private heldIndexes = new Set<number>();
|
|
492
509
|
private heldItemIds = new Set<string>();
|
|
493
510
|
private terminalPayload: Record<string, unknown> | undefined;
|
|
@@ -497,9 +514,7 @@ class BridgeStreamState {
|
|
|
497
514
|
this.suppressedSearches = new Map();
|
|
498
515
|
this.suppressedItemIds = new Map();
|
|
499
516
|
this.searches = [];
|
|
500
|
-
this.
|
|
501
|
-
this.heldIndexes = new Set();
|
|
502
|
-
this.heldItemIds = new Set();
|
|
517
|
+
this.dropHeldCalls();
|
|
503
518
|
this.terminalPayload = undefined;
|
|
504
519
|
}
|
|
505
520
|
|
|
@@ -507,6 +522,17 @@ class BridgeStreamState {
|
|
|
507
522
|
return this.heldCalls.length > 0;
|
|
508
523
|
}
|
|
509
524
|
|
|
525
|
+
private holdCall(payload: Record<string, unknown>, dataChars: number, upstreamIndex?: number): void {
|
|
526
|
+
if (this.heldCalls.length >= MAX_HELD_CALL_EVENTS
|
|
527
|
+
|| dataChars > MAX_HELD_CALL_CHARS - this.heldCallChars) {
|
|
528
|
+
throw new HeldCallBudgetExceededError(
|
|
529
|
+
"web-search bridge withheld more client tool events than its per-leg buffer bound allows",
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
this.heldCalls.push({ payload, ...(upstreamIndex === undefined ? {} : { upstreamIndex }) });
|
|
533
|
+
this.heldCallChars += dataChars;
|
|
534
|
+
}
|
|
535
|
+
|
|
510
536
|
private clientIndexFor(upstreamIndex: number): number {
|
|
511
537
|
const existing = this.indexMap.get(upstreamIndex);
|
|
512
538
|
if (existing !== undefined) return existing;
|
|
@@ -626,7 +652,7 @@ class BridgeStreamState {
|
|
|
626
652
|
if (isClientExecutedItem(item)) {
|
|
627
653
|
if (upstreamIndex !== undefined) this.heldIndexes.add(upstreamIndex);
|
|
628
654
|
if (typeof item.id === "string") this.heldItemIds.add(item.id);
|
|
629
|
-
this.
|
|
655
|
+
this.holdCall(payload, data.length, upstreamIndex);
|
|
630
656
|
return [];
|
|
631
657
|
}
|
|
632
658
|
}
|
|
@@ -648,7 +674,7 @@ class BridgeStreamState {
|
|
|
648
674
|
|
|
649
675
|
if ((upstreamIndex !== undefined && this.heldIndexes.has(upstreamIndex))
|
|
650
676
|
|| (itemId !== undefined && this.heldItemIds.has(itemId))) {
|
|
651
|
-
this.
|
|
677
|
+
this.holdCall(payload, data.length, upstreamIndex);
|
|
652
678
|
return [];
|
|
653
679
|
}
|
|
654
680
|
|
|
@@ -658,19 +684,20 @@ class BridgeStreamState {
|
|
|
658
684
|
return [this.render(payload.type, rewritten)];
|
|
659
685
|
}
|
|
660
686
|
|
|
661
|
-
/** Release
|
|
662
|
-
flushHeldCalls(): string
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
687
|
+
/** Release lazily so flushing does not allocate a second full set of serialized events. */
|
|
688
|
+
*flushHeldCalls(): Generator<string> {
|
|
689
|
+
try {
|
|
690
|
+
for (const held of this.heldCalls) {
|
|
691
|
+
const rewritten: Record<string, unknown> = { ...held.payload };
|
|
692
|
+
if (held.upstreamIndex !== undefined) {
|
|
693
|
+
rewritten.output_index = this.clientIndexFor(held.upstreamIndex);
|
|
694
|
+
}
|
|
695
|
+
if (held.payload.type === "response.output_item.done") this.retain(held.payload.item);
|
|
696
|
+
yield this.render(String(held.payload.type), rewritten);
|
|
668
697
|
}
|
|
669
|
-
|
|
670
|
-
|
|
698
|
+
} finally {
|
|
699
|
+
this.dropHeldCalls();
|
|
671
700
|
}
|
|
672
|
-
this.heldCalls = [];
|
|
673
|
-
return blocks;
|
|
674
701
|
}
|
|
675
702
|
|
|
676
703
|
/**
|
|
@@ -680,6 +707,18 @@ class BridgeStreamState {
|
|
|
680
707
|
*/
|
|
681
708
|
dropHeldCalls(): void {
|
|
682
709
|
this.heldCalls = [];
|
|
710
|
+
this.heldCallChars = 0;
|
|
711
|
+
this.heldIndexes.clear();
|
|
712
|
+
this.heldItemIds.clear();
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/** Fail before executing this leg's searches, closing every cell already shown to the client. */
|
|
716
|
+
*failLegFrames(code: string, message: string): Generator<string> {
|
|
717
|
+
this.dropHeldCalls();
|
|
718
|
+
for (const call of this.searches) {
|
|
719
|
+
yield* this.searchEndFrames(call, [], { text: "", sources: [], error: message });
|
|
720
|
+
}
|
|
721
|
+
yield* this.failureFrames(code, message);
|
|
683
722
|
}
|
|
684
723
|
|
|
685
724
|
/** Decide what the leg's terminal means once the whole leg has been read. */
|
|
@@ -979,7 +1018,7 @@ async function* bridgeStreamBlocks(
|
|
|
979
1018
|
// One continuation leg per allowed search, plus one final leg for the answer itself.
|
|
980
1019
|
let legsRemaining = options.plan.maxSearches + 1;
|
|
981
1020
|
|
|
982
|
-
const emit = function* (blocks:
|
|
1021
|
+
const emit = function* (blocks: Iterable<string>): Generator<string> {
|
|
983
1022
|
for (const block of blocks) yield block + "\n\n";
|
|
984
1023
|
};
|
|
985
1024
|
|
|
@@ -991,10 +1030,15 @@ async function* bridgeStreamBlocks(
|
|
|
991
1030
|
if (aborted()) return;
|
|
992
1031
|
}
|
|
993
1032
|
} catch (error) {
|
|
1033
|
+
if (aborted()) return;
|
|
994
1034
|
const message = error instanceof Error ? error.message : String(error);
|
|
995
|
-
|
|
1035
|
+
// A held-event overflow is this proxy's own bound. Attributing it to an upstream read
|
|
1036
|
+
// failure would blame the provider for a refusal the bridge made.
|
|
1037
|
+
yield* emit(state.failLegFrames(
|
|
996
1038
|
WEB_SEARCH_BRIDGE_ERROR_CODE,
|
|
997
|
-
|
|
1039
|
+
error instanceof HeldCallBudgetExceededError
|
|
1040
|
+
? message
|
|
1041
|
+
: "web-search bridge upstream read failed: " + message,
|
|
998
1042
|
));
|
|
999
1043
|
return;
|
|
1000
1044
|
}
|
|
@@ -1003,18 +1047,7 @@ async function* bridgeStreamBlocks(
|
|
|
1003
1047
|
|
|
1004
1048
|
const decision = state.decide(legsRemaining);
|
|
1005
1049
|
if (decision.kind === "fail") {
|
|
1006
|
-
|
|
1007
|
-
// under a failed turn (the same reason src/bridge.ts closes a dangling search on teardown).
|
|
1008
|
-
for (const call of decision.searches) {
|
|
1009
|
-
yield* emit(state.searchEndFrames(call, [], {
|
|
1010
|
-
text: "",
|
|
1011
|
-
sources: [],
|
|
1012
|
-
error: decision.message!,
|
|
1013
|
-
}));
|
|
1014
|
-
}
|
|
1015
|
-
// The withheld client call is deliberately dropped: the turn is ending as failed, and
|
|
1016
|
-
// releasing a tool call Codex would start executing is exactly what must not happen.
|
|
1017
|
-
yield* emit(state.failureFrames(decision.code!, decision.message!));
|
|
1050
|
+
yield* emit(state.failLegFrames(decision.code!, decision.message!));
|
|
1018
1051
|
return;
|
|
1019
1052
|
}
|
|
1020
1053
|
if (decision.kind === "end") {
|
|
@@ -110,7 +110,7 @@ export async function runXaiWebSearch(
|
|
|
110
110
|
// Credential-bearing: never follow a redirect off the pinned origin.
|
|
111
111
|
redirect: "manual",
|
|
112
112
|
}, recovery)),
|
|
113
|
-
{ abortSignal: linkedSignal.signal, label: "xai-web-search-sidecar" },
|
|
113
|
+
{ replaySafe: true, abortSignal: linkedSignal.signal, label: "xai-web-search-sidecar" },
|
|
114
114
|
);
|
|
115
115
|
const detachBodyGuard = cancelBodyOnAbort(res.body, linkedSignal.signal);
|
|
116
116
|
if (!res.ok) {
|