@bitkyc08/opencodex 2.7.36 → 2.7.37
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.ja.md +8 -1
- package/README.ko.md +7 -1
- package/README.md +7 -1
- package/README.ru.md +7 -1
- package/README.zh-CN.md +7 -1
- package/gui/dist/assets/index-BhUTxmCy.js +52 -0
- package/gui/dist/assets/index-oOZcqVmj.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic.ts +22 -2
- package/src/adapters/cursor/live-transport.ts +7 -0
- package/src/adapters/cursor/message-mapper.ts +3 -0
- package/src/adapters/cursor/protobuf-request.ts +223 -27
- package/src/adapters/cursor/request-builder.ts +41 -15
- package/src/adapters/cursor/thread-continuity.ts +67 -0
- package/src/adapters/cursor/types.ts +3 -1
- package/src/adapters/cursor.ts +44 -9
- package/src/adapters/google.ts +115 -62
- package/src/adapters/kiro.ts +3 -17
- package/src/adapters/openai-chat.ts +16 -5
- package/src/adapters/openai-responses.ts +56 -1
- package/src/adapters/run-turn-queue.ts +11 -1
- package/src/bridge.ts +139 -69
- package/src/chat/outbound.ts +135 -73
- package/src/cli/codex-shim-autorestore.ts +45 -0
- package/src/cli/doctor.ts +197 -2
- package/src/cli/index.ts +17 -3
- package/src/cli/status.ts +80 -0
- package/src/cli/v2.ts +14 -2
- package/src/codex/auth-context.ts +18 -2
- package/src/codex/catalog/bundled.ts +83 -27
- package/src/codex/catalog/effort.ts +95 -3
- package/src/codex/catalog/parsing.ts +17 -0
- package/src/codex/catalog/provider-fetch.ts +31 -8
- package/src/codex/exec-invocation.ts +22 -0
- package/src/codex/model-cache.ts +44 -0
- package/src/codex/runtime.ts +529 -0
- package/src/codex/shim.ts +608 -10
- package/src/combos/resolve.ts +7 -2
- package/src/config.ts +32 -1
- package/src/lib/bun-stream-caps.ts +88 -0
- package/src/lib/crash-guard.ts +3 -1
- package/src/lib/sse-decoder.ts +25 -6
- package/src/responses/parser.ts +2 -1
- package/src/responses/state.ts +10 -2
- package/src/server/auth-cors.ts +4 -1
- package/src/server/index.ts +191 -1
- package/src/server/live.ts +491 -0
- package/src/server/management/config-routes.ts +79 -3
- package/src/server/management/provider-routes.ts +2 -0
- package/src/server/management/shared.ts +6 -6
- package/src/server/management/system-routes.ts +65 -0
- package/src/server/management-api.ts +3 -1
- package/src/server/memory-watchdog.ts +112 -0
- package/src/server/relay-eager.ts +199 -0
- package/src/server/relay.ts +131 -81
- package/src/server/responses/collaboration.ts +20 -3
- package/src/server/responses/core.ts +236 -21
- package/src/server/responses/encrypted-payload.ts +118 -41
- package/src/server/ws-bridge.ts +7 -0
- package/src/types.ts +25 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +19 -0
- package/src/usage/summary.ts +11 -8
- package/gui/dist/assets/index-BpX-hoSd.css +0 -1
- package/gui/dist/assets/index-ZmFopEYw.js +0 -52
package/src/adapters/google.ts
CHANGED
|
@@ -345,6 +345,86 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
345
345
|
let pendingUsage: OcxUsage | undefined;
|
|
346
346
|
let toolCallsStarted = 0;
|
|
347
347
|
let lastFinishReason: string | undefined;
|
|
348
|
+
let sawAnyFrame = false;
|
|
349
|
+
let sawTerminalSignal = false;
|
|
350
|
+
|
|
351
|
+
const handleDataLine = function* (line: string): Generator<AdapterEvent, "continue" | "content" | "terminate"> {
|
|
352
|
+
const payload = line.slice(5).trim();
|
|
353
|
+
if (!payload) return "continue";
|
|
354
|
+
let emittedContentEvent = false;
|
|
355
|
+
|
|
356
|
+
let chunk: Record<string, unknown>;
|
|
357
|
+
try {
|
|
358
|
+
chunk = JSON.parse(payload);
|
|
359
|
+
} catch {
|
|
360
|
+
yield { type: "error", message: "malformed upstream SSE data frame" };
|
|
361
|
+
return "terminate";
|
|
362
|
+
}
|
|
363
|
+
sawAnyFrame = true;
|
|
364
|
+
|
|
365
|
+
// Inline provider error inside a 200 stream → terminal error (see openai-chat.ts).
|
|
366
|
+
if (chunk.error) {
|
|
367
|
+
const err = chunk.error as { message?: string } | undefined;
|
|
368
|
+
// Clear-on-invalid: a signature rejection means our replayed thoughtSignatures are stale.
|
|
369
|
+
// Drop the cache entry so the next turn starts clean instead of re-injecting a bad sig.
|
|
370
|
+
if (provider.googleMode === "cloud-code-assist" && antigravityModel && antigravitySession
|
|
371
|
+
&& /signature|invalid_argument|invalid argument/i.test(err?.message ?? "")) {
|
|
372
|
+
clearAntigravityReplay(antigravityModel, antigravitySession);
|
|
373
|
+
}
|
|
374
|
+
yield { type: "error", message: err?.message ?? "upstream error" };
|
|
375
|
+
return "terminate";
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Antigravity (CCA) nests the standard Gemini payload under `response`.
|
|
379
|
+
let root = chunk;
|
|
380
|
+
if (provider.googleMode === "cloud-code-assist") {
|
|
381
|
+
const wrapped = chunk.response;
|
|
382
|
+
if (!wrapped || typeof wrapped !== "object" || Array.isArray(wrapped)) {
|
|
383
|
+
yield { type: "error", message: "google-antigravity response missing response wrapper" };
|
|
384
|
+
return "terminate";
|
|
385
|
+
}
|
|
386
|
+
root = wrapped as Record<string, unknown>;
|
|
387
|
+
}
|
|
388
|
+
// usageMetadata is a top-level field independent of candidates; read it BEFORE the
|
|
389
|
+
// candidates guard so a usage-only final chunk is not dropped.
|
|
390
|
+
const usageMeta = root.usageMetadata as Record<string, number> | undefined;
|
|
391
|
+
if (usageMeta) {
|
|
392
|
+
// Accumulate usage; emit a single terminal `done` post-loop so usage is never
|
|
393
|
+
// dropped on EOF and the stream never yields two `done` events.
|
|
394
|
+
pendingUsage = usageFromGemini(usageMeta);
|
|
395
|
+
sawTerminalSignal = true;
|
|
396
|
+
}
|
|
397
|
+
const candidates = root.candidates as { content?: { parts?: unknown[] }; finishReason?: string }[] | undefined;
|
|
398
|
+
if (!candidates?.length) return "continue";
|
|
399
|
+
|
|
400
|
+
if (typeof candidates[0].finishReason === "string" && candidates[0].finishReason) {
|
|
401
|
+
lastFinishReason = candidates[0].finishReason;
|
|
402
|
+
sawTerminalSignal = true;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const parts = candidates[0].content?.parts as { text?: string; functionCall?: { name: string; args: unknown } }[] | undefined;
|
|
406
|
+
// Antigravity reasoning-replay: record thoughtSignatures from the model parts for the next turn.
|
|
407
|
+
if (provider.googleMode === "cloud-code-assist" && parts && antigravityModel && antigravitySession) {
|
|
408
|
+
observeAntigravityReplay(antigravityModel, antigravitySession, parts as unknown[]);
|
|
409
|
+
}
|
|
410
|
+
if (parts) {
|
|
411
|
+
for (const part of parts) {
|
|
412
|
+
if (part.text) {
|
|
413
|
+
emittedContentEvent = true;
|
|
414
|
+
yield { type: "text_delta", text: part.text };
|
|
415
|
+
}
|
|
416
|
+
if (part.functionCall) {
|
|
417
|
+
const id = `call_${crypto.randomUUID().slice(0, 8)}`;
|
|
418
|
+
toolCallsStarted++;
|
|
419
|
+
emittedContentEvent = true;
|
|
420
|
+
yield { type: "tool_call_start", id, name: restoreGoogleToolName(part.functionCall.name) };
|
|
421
|
+
yield { type: "tool_call_delta", arguments: JSON.stringify(part.functionCall.args ?? {}) };
|
|
422
|
+
yield { type: "tool_call_end" };
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return emittedContentEvent ? "content" : "continue";
|
|
427
|
+
};
|
|
348
428
|
|
|
349
429
|
try {
|
|
350
430
|
while (true) {
|
|
@@ -355,70 +435,30 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
355
435
|
const lines = buffer.split("\n");
|
|
356
436
|
buffer = lines.pop() ?? "";
|
|
357
437
|
|
|
438
|
+
let sawLiveness = false;
|
|
439
|
+
let sawContentEvent = false;
|
|
358
440
|
for (const line of lines) {
|
|
359
|
-
if (
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
try { chunk = JSON.parse(payload); } catch { debugDroppedFrame("google", payload); continue; }
|
|
365
|
-
|
|
366
|
-
// Inline provider error inside a 200 stream → terminal error (see openai-chat.ts).
|
|
367
|
-
if (chunk.error) {
|
|
368
|
-
const err = chunk.error as { message?: string } | undefined;
|
|
369
|
-
// Clear-on-invalid: a signature rejection means our replayed thoughtSignatures are stale.
|
|
370
|
-
// Drop the cache entry so the next turn starts clean instead of re-injecting a bad sig.
|
|
371
|
-
if (provider.googleMode === "cloud-code-assist" && antigravityModel && antigravitySession
|
|
372
|
-
&& /signature|invalid_argument|invalid argument/i.test(err?.message ?? "")) {
|
|
373
|
-
clearAntigravityReplay(antigravityModel, antigravitySession);
|
|
374
|
-
}
|
|
375
|
-
yield { type: "error", message: err?.message ?? "upstream error" };
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
// Antigravity (CCA) nests the standard Gemini payload under `response`.
|
|
380
|
-
let root = chunk;
|
|
381
|
-
if (provider.googleMode === "cloud-code-assist") {
|
|
382
|
-
const wrapped = chunk.response;
|
|
383
|
-
if (!wrapped || typeof wrapped !== "object" || Array.isArray(wrapped)) {
|
|
384
|
-
yield { type: "error", message: "google-antigravity response missing response wrapper" };
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
root = wrapped as Record<string, unknown>;
|
|
388
|
-
}
|
|
389
|
-
// usageMetadata is a top-level field independent of candidates; read it BEFORE the
|
|
390
|
-
// candidates guard so a usage-only final chunk is not dropped.
|
|
391
|
-
const usageMeta = root.usageMetadata as Record<string, number> | undefined;
|
|
392
|
-
if (usageMeta) {
|
|
393
|
-
// Accumulate usage; emit a single terminal `done` post-loop so usage is never
|
|
394
|
-
// dropped on EOF and the stream never yields two `done` events.
|
|
395
|
-
pendingUsage = usageFromGemini(usageMeta);
|
|
396
|
-
}
|
|
397
|
-
const candidates = root.candidates as { content?: { parts?: unknown[] }; finishReason?: string }[] | undefined;
|
|
398
|
-
if (!candidates?.length) continue;
|
|
399
|
-
|
|
400
|
-
lastFinishReason = candidates[0].finishReason ?? lastFinishReason;
|
|
401
|
-
|
|
402
|
-
const parts = candidates[0].content?.parts as { text?: string; functionCall?: { name: string; args: unknown } }[] | undefined;
|
|
403
|
-
// Antigravity reasoning-replay: record thoughtSignatures from the model parts for the next turn.
|
|
404
|
-
if (provider.googleMode === "cloud-code-assist" && parts && antigravityModel && antigravitySession) {
|
|
405
|
-
observeAntigravityReplay(antigravityModel, antigravitySession, parts as unknown[]);
|
|
406
|
-
}
|
|
407
|
-
if (parts) {
|
|
408
|
-
for (const part of parts) {
|
|
409
|
-
if (part.text) {
|
|
410
|
-
yield { type: "text_delta", text: part.text };
|
|
411
|
-
}
|
|
412
|
-
if (part.functionCall) {
|
|
413
|
-
const id = `call_${crypto.randomUUID().slice(0, 8)}`;
|
|
414
|
-
toolCallsStarted++;
|
|
415
|
-
yield { type: "tool_call_start", id, name: restoreGoogleToolName(part.functionCall.name) };
|
|
416
|
-
yield { type: "tool_call_delta", arguments: JSON.stringify(part.functionCall.args ?? {}) };
|
|
417
|
-
yield { type: "tool_call_end" };
|
|
418
|
-
}
|
|
419
|
-
}
|
|
441
|
+
if (line.startsWith("data:")) {
|
|
442
|
+
const result = yield* handleDataLine(line);
|
|
443
|
+
if (result === "terminate") return;
|
|
444
|
+
if (result === "content") sawContentEvent = true;
|
|
445
|
+
continue;
|
|
420
446
|
}
|
|
447
|
+
sawLiveness = true;
|
|
448
|
+
if (line.startsWith(":") || !line.trim()) continue;
|
|
449
|
+
debugDroppedFrame("google", line);
|
|
421
450
|
}
|
|
451
|
+
if (sawLiveness && !sawContentEvent) yield { type: "heartbeat" };
|
|
452
|
+
}
|
|
453
|
+
buffer += decoder.decode();
|
|
454
|
+
if (buffer.trim().length > 0) {
|
|
455
|
+
const residual = buffer.trim();
|
|
456
|
+
if (residual.startsWith(":")) {
|
|
457
|
+
yield { type: "heartbeat" };
|
|
458
|
+
} else if (!residual.startsWith("data:")) {
|
|
459
|
+
yield { type: "error", message: "upstream stream ended with an incomplete SSE frame — possible truncation" };
|
|
460
|
+
return;
|
|
461
|
+
} else if ((yield* handleDataLine(residual)) === "terminate") return;
|
|
422
462
|
}
|
|
423
463
|
// Fail-closed: a turn cut off mid tool call (MAX_TOKENS / MALFORMED_FUNCTION_CALL) surfaces
|
|
424
464
|
// an error instead of a silently-incomplete done. Mirrors kiro-truncation.
|
|
@@ -427,7 +467,20 @@ export function createGoogleAdapter(provider: OcxProviderConfig): ProviderAdapte
|
|
|
427
467
|
yield { type: "error", message: vertexTruncationErrorMessage(lastFinishReason) };
|
|
428
468
|
return;
|
|
429
469
|
}
|
|
430
|
-
|
|
470
|
+
if (!sawAnyFrame || !sawTerminalSignal) {
|
|
471
|
+
yield { type: "error", message: "upstream stream ended without a terminal signal — possible truncation" };
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
const stopReason = lastFinishReason === "MAX_TOKENS"
|
|
475
|
+
? "max_tokens"
|
|
476
|
+
: ["SAFETY", "RECITATION", "BLOCKLIST", "PROHIBITED_CONTENT", "SPII"].includes(lastFinishReason ?? "")
|
|
477
|
+
? "content_filter"
|
|
478
|
+
: undefined;
|
|
479
|
+
yield {
|
|
480
|
+
type: "done",
|
|
481
|
+
usage: pendingUsage,
|
|
482
|
+
...(stopReason ? { stopReason } : {}),
|
|
483
|
+
};
|
|
431
484
|
} finally {
|
|
432
485
|
reader.releaseLock();
|
|
433
486
|
}
|
package/src/adapters/kiro.ts
CHANGED
|
@@ -917,23 +917,9 @@ async function* parseKiroAttempt(
|
|
|
917
917
|
terminal: { type: "done", usage: finalUsage, endTurn: false, ...(finalProviderState ? { providerState: finalProviderState } : {}) },
|
|
918
918
|
};
|
|
919
919
|
}
|
|
920
|
-
//
|
|
921
|
-
//
|
|
922
|
-
|
|
923
|
-
// Reasoning-only output still needs the bounded fallback because it has no user-facing text.
|
|
924
|
-
if (mode === "required" && sawText) {
|
|
925
|
-
return {
|
|
926
|
-
assistantText,
|
|
927
|
-
sawReasoning,
|
|
928
|
-
terminal: {
|
|
929
|
-
type: "done",
|
|
930
|
-
usage: finalUsage,
|
|
931
|
-
endTurn: true,
|
|
932
|
-
...(finalProviderState ? { providerState: finalProviderState } : {}),
|
|
933
|
-
},
|
|
934
|
-
};
|
|
935
|
-
}
|
|
936
|
-
if (mode === "required" && sawReasoning) {
|
|
920
|
+
// Kiro text has no trustworthy final/progress marker. When completion is required, ordinary
|
|
921
|
+
// text and reasoning remain unfinished until the one bounded fallback validates the turn.
|
|
922
|
+
if (mode === "required" && (sawText || sawReasoning)) {
|
|
937
923
|
return { assistantText, sawReasoning, needsFallback: true, usage: finalUsage, providerState: finalProviderState };
|
|
938
924
|
}
|
|
939
925
|
if (!sawText && !sawReasoning) {
|
|
@@ -604,7 +604,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
604
604
|
// being reported as a clean completion (silent truncation). A graceful close is either an
|
|
605
605
|
// explicit `[DONE]` sentinel OR a chunk carrying a non-null `finish_reason` (some
|
|
606
606
|
// OpenAI-compatible providers omit `[DONE]` but do send finish_reason).
|
|
607
|
-
let
|
|
607
|
+
let finishReason: string | undefined;
|
|
608
608
|
|
|
609
609
|
// Single per-line handler shared by the streaming loop and the EOF residual-frame flush, so
|
|
610
610
|
// a final frame is parsed identically wherever it lands (no duplicated, drift-prone parsing).
|
|
@@ -615,7 +615,12 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
615
615
|
const payload = line.slice(6).trim();
|
|
616
616
|
if (payload === "[DONE]") {
|
|
617
617
|
yield* flushToolCalls();
|
|
618
|
-
|
|
618
|
+
const stopReason = finishReason === "length"
|
|
619
|
+
? "max_tokens"
|
|
620
|
+
: finishReason === "content_filter"
|
|
621
|
+
? "content_filter"
|
|
622
|
+
: undefined;
|
|
623
|
+
yield { type: "done", usage: pendingUsage, ...(stopReason ? { stopReason } : {}) };
|
|
619
624
|
return "terminate";
|
|
620
625
|
}
|
|
621
626
|
|
|
@@ -647,9 +652,9 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
647
652
|
const choices = chunk.choices as { delta?: Record<string, unknown>; finish_reason?: string }[] | undefined;
|
|
648
653
|
if (!choices || choices.length === 0) return "continue";
|
|
649
654
|
// Observe the terminator BEFORE the delta guard: a finish-only chunk (finish_reason set,
|
|
650
|
-
// no delta) is a graceful close and must
|
|
655
|
+
// no delta) is a graceful close and must record finishReason even though we skip it below.
|
|
651
656
|
if (typeof choices[0].finish_reason === "string" && choices[0].finish_reason) {
|
|
652
|
-
|
|
657
|
+
finishReason = choices[0].finish_reason;
|
|
653
658
|
}
|
|
654
659
|
const delta = choices[0].delta;
|
|
655
660
|
if (delta) {
|
|
@@ -720,12 +725,18 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
720
725
|
// a non-null finish_reason (sawFinish), or a trailing usage chunk (providers emit usage only
|
|
721
726
|
// at end-of-generation). If NONE of those were seen, the stream was cut mid-flight — fail
|
|
722
727
|
// closed so the bridge emits a classified response.failed rather than a silent truncation.
|
|
728
|
+
const sawFinish = finishReason !== undefined;
|
|
723
729
|
if (!sawFinish && pendingUsage === undefined) {
|
|
724
730
|
yield { type: "error", message: "upstream stream ended without a terminal signal ([DONE] or finish_reason) — possible truncation" };
|
|
725
731
|
return;
|
|
726
732
|
}
|
|
727
733
|
// Graceful close that omitted [DONE] but delivered finish_reason and/or final usage.
|
|
728
|
-
|
|
734
|
+
const stopReason = finishReason === "length"
|
|
735
|
+
? "max_tokens"
|
|
736
|
+
: finishReason === "content_filter"
|
|
737
|
+
? "content_filter"
|
|
738
|
+
: undefined;
|
|
739
|
+
yield { type: "done", usage: pendingUsage, ...(stopReason ? { stopReason } : {}) };
|
|
729
740
|
} finally {
|
|
730
741
|
reader.releaseLock();
|
|
731
742
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import type { IncomingMeta, ProviderAdapter } from "./base";
|
|
2
3
|
import type { AdapterEvent, OcxParsedRequest, OcxProviderConfig } from "../types";
|
|
3
4
|
import { catalogModelSupportsReasoningSummaries } from "../codex/catalog";
|
|
@@ -328,6 +329,55 @@ function isPlainObject(v: unknown): v is Record<string, unknown> {
|
|
|
328
329
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
329
330
|
}
|
|
330
331
|
|
|
332
|
+
const MAX_RESPONSES_CALL_ID_LENGTH = 64;
|
|
333
|
+
const REPAIRED_CALL_ID_PREFIX = "call_ocx_";
|
|
334
|
+
const REPAIRED_CALL_ID_DIGEST_LENGTH = MAX_RESPONSES_CALL_ID_LENGTH - REPAIRED_CALL_ID_PREFIX.length;
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* The ChatGPT Responses backend rejects input `call_id` values longer than 64 characters. Codex
|
|
338
|
+
* sidechat/fork replay can namespace call ids from routed providers past that limit. Forward mode
|
|
339
|
+
* already sends explicit replay input without `previous_response_id`, so it is safe to replace each
|
|
340
|
+
* oversized id and every matching call/output occurrence with one deterministic request-local alias.
|
|
341
|
+
* Raw API-key continuations are intentionally excluded because an output-only continuation may
|
|
342
|
+
* reference a call stored upstream under the original id. Proxy-expanded API-key replays are
|
|
343
|
+
* explicit and stateless here, so they are safe to repair too.
|
|
344
|
+
*/
|
|
345
|
+
function repairOversizedReplayCallIds(body: unknown): unknown {
|
|
346
|
+
if (!isPlainObject(body) || !Array.isArray(body.input)) return body;
|
|
347
|
+
|
|
348
|
+
const occupied = new Set<string>();
|
|
349
|
+
for (const item of body.input) {
|
|
350
|
+
if (!isPlainObject(item) || typeof item.call_id !== "string") continue;
|
|
351
|
+
if (item.call_id.length <= MAX_RESPONSES_CALL_ID_LENGTH) occupied.add(item.call_id);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const aliases = new Map<string, string>();
|
|
355
|
+
let changed = false;
|
|
356
|
+
const input = body.input.map(item => {
|
|
357
|
+
if (!isPlainObject(item) || typeof item.call_id !== "string") return item;
|
|
358
|
+
const original = item.call_id;
|
|
359
|
+
if (original.length <= MAX_RESPONSES_CALL_ID_LENGTH) return item;
|
|
360
|
+
|
|
361
|
+
let alias = aliases.get(original);
|
|
362
|
+
if (!alias) {
|
|
363
|
+
let salt = 0;
|
|
364
|
+
do {
|
|
365
|
+
const hashInput = salt === 0 ? original : `${original}\0${salt}`;
|
|
366
|
+
const digest = createHash("sha256").update(hashInput).digest("hex");
|
|
367
|
+
alias = `${REPAIRED_CALL_ID_PREFIX}${digest.slice(0, REPAIRED_CALL_ID_DIGEST_LENGTH)}`;
|
|
368
|
+
salt += 1;
|
|
369
|
+
} while (occupied.has(alias));
|
|
370
|
+
aliases.set(original, alias);
|
|
371
|
+
occupied.add(alias);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
changed = true;
|
|
375
|
+
return { ...item, call_id: alias };
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
return changed ? { ...body, input } : body;
|
|
379
|
+
}
|
|
380
|
+
|
|
331
381
|
/** Flatten a Responses tool-output `output` value (string or content-part array) to plain text. */
|
|
332
382
|
function toolOutputText(output: unknown): string {
|
|
333
383
|
if (typeof output === "string") return output;
|
|
@@ -515,8 +565,13 @@ export function createResponsesPassthroughAdapter(provider: OcxProviderConfig):
|
|
|
515
565
|
parsed._rawBody,
|
|
516
566
|
forward || parsed._previousResponseInputExpanded === true,
|
|
517
567
|
);
|
|
518
|
-
if (forward)
|
|
568
|
+
if (forward) {
|
|
569
|
+
outBody = repairOrphanedInputItems(outBody, unexpandedMiss);
|
|
570
|
+
}
|
|
519
571
|
else outBody = stripConflictingHostedTools(outBody);
|
|
572
|
+
if (forward || parsed._previousResponseInputExpanded === true) {
|
|
573
|
+
outBody = repairOversizedReplayCallIds(outBody);
|
|
574
|
+
}
|
|
520
575
|
outBody = stripUnsupportedReasoningSummaryDelivery(outBody, parsed.modelId);
|
|
521
576
|
return {
|
|
522
577
|
url,
|
|
@@ -49,9 +49,13 @@ export async function preflightAdapterEvents(
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
export function createAdapterEventQueue(
|
|
52
|
+
export function createAdapterEventQueue(opts?: {
|
|
53
|
+
maxBacklog?: number;
|
|
54
|
+
onBacklogExceeded?: () => void;
|
|
55
|
+
}): AdapterEventQueue {
|
|
53
56
|
const queued: AdapterEvent[] = [];
|
|
54
57
|
const readers: QueueReader[] = [];
|
|
58
|
+
const maxBacklog = opts?.maxBacklog ?? 1_024;
|
|
55
59
|
let closed = false;
|
|
56
60
|
|
|
57
61
|
const push = (event: AdapterEvent): void => {
|
|
@@ -61,6 +65,12 @@ export function createAdapterEventQueue(): AdapterEventQueue {
|
|
|
61
65
|
reader({ done: false, value: event });
|
|
62
66
|
return;
|
|
63
67
|
}
|
|
68
|
+
if (queued.length >= maxBacklog) {
|
|
69
|
+
opts?.onBacklogExceeded?.();
|
|
70
|
+
queued.push({ type: "error", message: "consumer backlog exceeded — turn aborted" });
|
|
71
|
+
close();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
64
74
|
queued.push(event);
|
|
65
75
|
};
|
|
66
76
|
|