@bitkyc08/opencodex 2.54.0-preview.20260914 → 2.55.0-preview.20260914
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/{index-B4VYfZcY.js → index-DH2PUHqr.js} +10 -10
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapters/anthropic-image-codec.ts +57 -0
- package/src/adapters/anthropic-image-normalize.ts +28 -1
- package/src/adapters/anthropic.ts +68 -6
- package/src/adapters/base.ts +8 -0
- package/src/adapters/coding-agent/protocol.ts +41 -16
- package/src/adapters/cursor/cursor-errors.ts +1 -1
- package/src/adapters/cursor/live-transport.ts +5 -1
- package/src/adapters/cursor/native-exec-fs.ts +10 -10
- package/src/adapters/cursor/native-exec-network.ts +2 -2
- package/src/adapters/cursor/native-exec-shell.ts +13 -12
- package/src/adapters/cursor/native-exec.ts +51 -10
- package/src/adapters/cursor/policy-error.ts +75 -0
- package/src/adapters/cursor/protobuf-request.ts +105 -1
- package/src/adapters/devin/cloud-direct/catalog.ts +34 -2
- package/src/adapters/devin/live-models.ts +33 -2
- package/src/adapters/google-wire-compiler.ts +8 -0
- package/src/adapters/google.ts +46 -0
- package/src/adapters/input-media-guard.ts +45 -0
- package/src/adapters/kiro/adapter.ts +8 -0
- package/src/adapters/kiro/payload.ts +28 -6
- package/src/adapters/kiro-events.ts +25 -6
- package/src/adapters/kiro-images.ts +30 -0
- package/src/adapters/kiro-retry.ts +8 -0
- package/src/adapters/openai-chat.ts +33 -4
- package/src/adapters/openai-responses.ts +26 -0
- package/src/adapters/registry.ts +4 -0
- package/src/bridge.ts +163 -116
- package/src/chat/image-parts.ts +151 -0
- package/src/chat/inbound.ts +70 -33
- package/src/cli/connect.ts +30 -9
- package/src/cli/dispatch.ts +7 -3
- package/src/cli/index.ts +3 -0
- package/src/cli/runtime-api.ts +25 -0
- package/src/cli/status.ts +21 -19
- package/src/cli/system-restart-client.ts +25 -0
- package/src/clients/config-export.ts +14 -4
- package/src/codex/app-server-processes.ts +25 -0
- package/src/codex/auth-context.ts +8 -0
- package/src/codex/autostart-health.ts +36 -2
- package/src/codex/catalog/provider-fetch.ts +41 -0
- package/src/codex/catalog-auto-refresh.ts +182 -0
- package/src/codex/catalog-refresh-status.ts +93 -0
- package/src/codex/history-provider.ts +55 -0
- package/src/codex/model-entitlements.ts +78 -0
- package/src/codex/native-profile-processes.ts +114 -15
- package/src/codex/prompt-text-probe.ts +274 -41
- package/src/codex/routing-adoption.ts +189 -0
- package/src/codex/routing.ts +520 -48
- package/src/codex/runtime.ts +249 -7
- package/src/combos/failover.ts +45 -0
- package/src/config.ts +124 -4
- package/src/generated/compatibility-version.json +110 -74
- package/src/generated/model-metadata.ts +1 -0
- package/src/lib/request-execution-budget.ts +202 -0
- package/src/lib/upstream-retry.ts +95 -8
- package/src/lib/workflow-budget.ts +172 -0
- package/src/oauth/devin.ts +57 -12
- package/src/providers/quota.ts +37 -6
- package/src/providers/registry.ts +53 -6
- package/src/responses/input-media.ts +65 -0
- package/src/responses/parser-content.ts +42 -0
- package/src/responses/schema.ts +12 -2
- package/src/server/audio-live.ts +1 -2
- package/src/server/audio-transcriptions.ts +1 -2
- package/src/server/auth-cors.ts +1 -1
- package/src/server/background-lifecycle.ts +18 -0
- package/src/server/chat-completions.ts +23 -8
- package/src/server/chat-native.ts +17 -17
- package/src/server/index.ts +24 -0
- package/src/server/management/request-history-routes.ts +5 -0
- package/src/server/request-log.ts +8 -2
- package/src/server/responses/compact.ts +51 -3
- package/src/server/responses/core.ts +238 -28
- package/src/server/search.ts +7 -9
- package/src/types/config.ts +51 -6
- package/src/usage/log.ts +37 -0
- package/src/vision/eligibility.ts +37 -4
- package/src/vision/index.ts +1 -0
- package/src/vision/plan.ts +45 -10
- package/src/web-search/alpha-search.ts +324 -0
- package/src/web-search/index.ts +13 -22
- package/src/web-search/passthrough-bridge.ts +195 -22
- package/src/web-search/sidecar-providers.ts +22 -0
package/src/bridge.ts
CHANGED
|
@@ -204,6 +204,14 @@ interface OutputItem {
|
|
|
204
204
|
|
|
205
205
|
export type ResponsesTerminalStatus = "completed" | "failed" | "incomplete";
|
|
206
206
|
|
|
207
|
+
/** Accumulates string fragments and their total byte length without concatenating. */
|
|
208
|
+
interface StringChunks {
|
|
209
|
+
chunks: string[];
|
|
210
|
+
bytes: number;
|
|
211
|
+
}
|
|
212
|
+
const emptyChunks = (): StringChunks => ({ chunks: [], bytes: 0 });
|
|
213
|
+
const joinChunks = (sc: StringChunks): string => sc.chunks.join("");
|
|
214
|
+
|
|
207
215
|
export function bridgeToResponsesSSE(
|
|
208
216
|
events: AsyncIterable<AdapterEvent>,
|
|
209
217
|
modelId: string,
|
|
@@ -337,6 +345,31 @@ export function bridgeToResponsesSSE(
|
|
|
337
345
|
};
|
|
338
346
|
const bytesOf = (value: string): number => Buffer.byteLength(value);
|
|
339
347
|
const appendString = (
|
|
348
|
+
previous: StringChunks,
|
|
349
|
+
fragment: string,
|
|
350
|
+
kind: TranslatorBufferKind,
|
|
351
|
+
callId?: string,
|
|
352
|
+
): StringChunks => {
|
|
353
|
+
const fragmentBytes = bytesOf(fragment);
|
|
354
|
+
if (fragmentBytes === 0) return previous;
|
|
355
|
+
const nextBytes = previous.bytes + fragmentBytes;
|
|
356
|
+
const scope = { kind, ...(callId ? { callId } : {}) };
|
|
357
|
+
const reservation = budget.reserveTransient(nextBytes, scope);
|
|
358
|
+
try {
|
|
359
|
+
previous.chunks.push(fragment);
|
|
360
|
+
const result: StringChunks = { chunks: previous.chunks, bytes: nextBytes };
|
|
361
|
+
reservation.commitRetained();
|
|
362
|
+
budget.releaseRetained(previous.bytes, scope);
|
|
363
|
+
return result;
|
|
364
|
+
} catch (error) {
|
|
365
|
+
reservation.release();
|
|
366
|
+
throw error;
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
// Tool-call arguments deliberately use plain string concatenation because
|
|
370
|
+
// downstream parsers and intermediate inspectors perform incremental JSON reads mid-stream.
|
|
371
|
+
// Converting tool args to StringChunks would require frequent join operations.
|
|
372
|
+
const appendStringDirect = (
|
|
340
373
|
previous: string,
|
|
341
374
|
previousBytes: number,
|
|
342
375
|
fragment: string,
|
|
@@ -468,13 +501,12 @@ export function bridgeToResponsesSSE(
|
|
|
468
501
|
let currentMsg: {
|
|
469
502
|
itemId: string;
|
|
470
503
|
outputIndex: number;
|
|
471
|
-
text:
|
|
472
|
-
textBytes: number;
|
|
504
|
+
text: StringChunks;
|
|
473
505
|
citationFilter: CitationMarkerFilter;
|
|
474
506
|
phase?: OcxMessagePhase;
|
|
475
507
|
} | null = null;
|
|
476
|
-
let currentReasoning: { itemId: string; outputIndex: number; text:
|
|
477
|
-
let currentRawReasoning: { itemId: string; outputIndex: number; text:
|
|
508
|
+
let currentReasoning: { itemId: string; outputIndex: number; text: StringChunks } | null = null;
|
|
509
|
+
let currentRawReasoning: { itemId: string; outputIndex: number; text: StringChunks } | null = null;
|
|
478
510
|
// Anthropic extended-thinking round-trip state: the signature signs the CURRENT thinking
|
|
479
511
|
// block; redacted blocks are opaque payloads replayed verbatim. Attached to the reasoning
|
|
480
512
|
// item as an ocxr1 encrypted_content envelope on close. hiddenThinkingText collects the
|
|
@@ -482,8 +514,7 @@ export function bridgeToResponsesSSE(
|
|
|
482
514
|
let pendingSignature: string | undefined;
|
|
483
515
|
let pendingSignatureBytes = 0;
|
|
484
516
|
let pendingRedacted: string[] = [];
|
|
485
|
-
let
|
|
486
|
-
let hiddenThinkingBytes = 0;
|
|
517
|
+
let hiddenThinking = emptyChunks();
|
|
487
518
|
const takeReasoningEnvelope = (hiddenText?: string): string | undefined => {
|
|
488
519
|
if (!pendingSignature && pendingRedacted.length === 0) return undefined;
|
|
489
520
|
const envelope: ReasoningEnvelope = {};
|
|
@@ -492,7 +523,7 @@ export function bridgeToResponsesSSE(
|
|
|
492
523
|
if (hiddenText) envelope.txt = hiddenText;
|
|
493
524
|
const previousBytes = pendingSignatureBytes
|
|
494
525
|
+ pendingRedacted.reduce((sum, value) => sum + bytesOf(value), 0)
|
|
495
|
-
+ (hiddenText ?
|
|
526
|
+
+ (hiddenText ? hiddenThinking.bytes : 0);
|
|
496
527
|
const encoded = encodeReasoningEnvelope(envelope, budget);
|
|
497
528
|
const reservation = budget?.reserveTransient(bytesOf(encoded), { kind: "reasoning" });
|
|
498
529
|
pendingSignature = undefined;
|
|
@@ -505,9 +536,9 @@ export function bridgeToResponsesSSE(
|
|
|
505
536
|
// hideThinkingSummary path: no visible reasoning item exists, but a signed thinking block
|
|
506
537
|
// must still round-trip — emit an envelope-only reasoning item (empty summary, no text leak).
|
|
507
538
|
const flushHiddenReasoningEnvelope = () => {
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
|
|
539
|
+
const hiddenText = joinChunks(hiddenThinking);
|
|
540
|
+
const encrypted = takeReasoningEnvelope(hiddenText || undefined);
|
|
541
|
+
hiddenThinking = emptyChunks();
|
|
511
542
|
if (!encrypted) return;
|
|
512
543
|
const itemId = `rs_${uuid()}`;
|
|
513
544
|
const item = { type: "reasoning", id: itemId, summary: [] as never[], encrypted_content: encrypted };
|
|
@@ -521,8 +552,7 @@ export function bridgeToResponsesSSE(
|
|
|
521
552
|
// like native models — but the text still round-trips in a txt-only ocxr1 envelope so
|
|
522
553
|
// preserveReasoningContentModels replay (GLM interleaved thinking) keeps working. Direct
|
|
523
554
|
// encodeReasoningEnvelope: takeReasoningEnvelope's sig/red guard would drop txt-only.
|
|
524
|
-
let
|
|
525
|
-
let hiddenRawReasoningBytes = 0;
|
|
555
|
+
let hiddenRawReasoning = emptyChunks();
|
|
526
556
|
// Raw reasoning text flushed most recently, waiting for the tool call it
|
|
527
557
|
// preceded. Recorded into the replay cache on tool_call_start so a later
|
|
528
558
|
// continuation can re-attach it when history lost the reasoning item
|
|
@@ -530,13 +560,13 @@ export function bridgeToResponsesSSE(
|
|
|
530
560
|
// calls share the same preceding reasoning block.
|
|
531
561
|
let rawReasoningForNextToolCall = "";
|
|
532
562
|
const flushHiddenRawReasoning = () => {
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
const
|
|
563
|
+
const hiddenRawText = joinChunks(hiddenRawReasoning);
|
|
564
|
+
if (!hiddenRawText) return;
|
|
565
|
+
rawReasoningForNextToolCall = hiddenRawText;
|
|
566
|
+
const previousBytes = hiddenRawReasoning.bytes;
|
|
567
|
+
const encrypted = encodeReasoningEnvelope({ txt: hiddenRawText }, budget);
|
|
537
568
|
const reservation = budget?.reserveTransient(bytesOf(encrypted), { kind: "reasoning" });
|
|
538
|
-
|
|
539
|
-
hiddenRawReasoningBytes = 0;
|
|
569
|
+
hiddenRawReasoning = emptyChunks();
|
|
540
570
|
reservation?.commitRetained();
|
|
541
571
|
budget?.releaseRetained(previousBytes, { kind: "reasoning" });
|
|
542
572
|
const itemId = `rs_${uuid()}`;
|
|
@@ -571,8 +601,7 @@ export function bridgeToResponsesSSE(
|
|
|
571
601
|
};
|
|
572
602
|
// Full assistant text of a compaction turn (across message boundaries) — becomes the
|
|
573
603
|
// synthetic compaction item's payload on done.
|
|
574
|
-
let
|
|
575
|
-
let compactionTextBytes = 0;
|
|
604
|
+
let compaction = emptyChunks();
|
|
576
605
|
let currentToolCall: { itemId: string; outputIndex: number; callId: string; name: string; args: string; argsBytes: number; namespace?: string; freeform?: boolean; toolSearch?: boolean; inputEmitted?: string; codeModeHelperName?: string; providerMetadata?: OcxProviderOpaqueToolCallMetadata } | null = null;
|
|
577
606
|
// Open native web-search cell (between begin and end). Holds the output index allocated on
|
|
578
607
|
// begin so the matching done reuses it; closed as `failed` if the stream terminates early.
|
|
@@ -612,7 +641,7 @@ export function bridgeToResponsesSSE(
|
|
|
612
641
|
content_index: 0, delta: trailing,
|
|
613
642
|
});
|
|
614
643
|
}
|
|
615
|
-
const messageText = stripCitationMarkers(currentMsg.text);
|
|
644
|
+
const messageText = stripCitationMarkers(joinChunks(currentMsg.text));
|
|
616
645
|
// Chat Completions has no message-phase field. Keep its live item provisional, then
|
|
617
646
|
// classify it only when the next adapter event proves whether this text led into more
|
|
618
647
|
// work or completed the turn. Explicit adapter phases always outrank this inference.
|
|
@@ -634,45 +663,47 @@ export function bridgeToResponsesSSE(
|
|
|
634
663
|
...(phase ? { phase } : {}),
|
|
635
664
|
};
|
|
636
665
|
emit("response.output_item.done", { output_index: currentMsg.outputIndex, item });
|
|
637
|
-
retainFinishedItem(item as OutputItem, currentMsg.
|
|
666
|
+
retainFinishedItem(item as OutputItem, currentMsg.text.bytes + bytesOf(JSON.stringify(annotations)));
|
|
638
667
|
outputIndex++;
|
|
639
668
|
currentMsg = null;
|
|
640
669
|
};
|
|
641
670
|
|
|
642
671
|
const closeCurrentReasoning = () => {
|
|
643
672
|
if (!currentReasoning) return;
|
|
673
|
+
const reasoningText = joinChunks(currentReasoning.text);
|
|
644
674
|
emit("response.reasoning_summary_text.done", {
|
|
645
|
-
item_id: currentReasoning.itemId, output_index: currentReasoning.outputIndex, summary_index: 0, text:
|
|
675
|
+
item_id: currentReasoning.itemId, output_index: currentReasoning.outputIndex, summary_index: 0, text: reasoningText,
|
|
646
676
|
});
|
|
647
677
|
emit("response.reasoning_summary_part.done", {
|
|
648
678
|
item_id: currentReasoning.itemId, output_index: currentReasoning.outputIndex, summary_index: 0,
|
|
649
|
-
part: { type: "summary_text", text:
|
|
679
|
+
part: { type: "summary_text", text: reasoningText },
|
|
650
680
|
});
|
|
651
681
|
const encrypted = takeReasoningEnvelope();
|
|
652
682
|
const item = {
|
|
653
683
|
type: "reasoning", id: currentReasoning.itemId,
|
|
654
|
-
summary: [{ type: "summary_text", text:
|
|
684
|
+
summary: [{ type: "summary_text", text: reasoningText }],
|
|
655
685
|
...(encrypted ? { encrypted_content: encrypted } : {}),
|
|
656
686
|
};
|
|
657
687
|
emit("response.output_item.done", { output_index: currentReasoning.outputIndex, item });
|
|
658
|
-
retainFinishedItem(item as OutputItem, currentReasoning.
|
|
688
|
+
retainFinishedItem(item as OutputItem, currentReasoning.text.bytes + bytesOf(encrypted ?? ""), "reasoning");
|
|
659
689
|
outputIndex++;
|
|
660
690
|
currentReasoning = null;
|
|
661
691
|
};
|
|
662
692
|
|
|
663
693
|
const closeCurrentRawReasoning = () => {
|
|
664
694
|
if (!currentRawReasoning) return;
|
|
665
|
-
|
|
695
|
+
const rawText = joinChunks(currentRawReasoning.text);
|
|
696
|
+
rawReasoningForNextToolCall = rawText;
|
|
666
697
|
emit("response.reasoning_text.done", {
|
|
667
|
-
item_id: currentRawReasoning.itemId, output_index: currentRawReasoning.outputIndex, content_index: 0, text:
|
|
698
|
+
item_id: currentRawReasoning.itemId, output_index: currentRawReasoning.outputIndex, content_index: 0, text: rawText,
|
|
668
699
|
});
|
|
669
700
|
const item = {
|
|
670
701
|
type: "reasoning", id: currentRawReasoning.itemId,
|
|
671
702
|
summary: [] as never[],
|
|
672
|
-
content: [{ type: "reasoning_text", text:
|
|
703
|
+
content: [{ type: "reasoning_text", text: rawText }],
|
|
673
704
|
};
|
|
674
705
|
emit("response.output_item.done", { output_index: currentRawReasoning.outputIndex, item });
|
|
675
|
-
retainFinishedItem(item as OutputItem, currentRawReasoning.
|
|
706
|
+
retainFinishedItem(item as OutputItem, currentRawReasoning.text.bytes, "reasoning");
|
|
676
707
|
outputIndex++;
|
|
677
708
|
currentRawReasoning = null;
|
|
678
709
|
};
|
|
@@ -942,12 +973,11 @@ export function bridgeToResponsesSSE(
|
|
|
942
973
|
// its compaction UI renders nothing mid-turn, so nothing is lost visually.
|
|
943
974
|
if (options?.compaction) {
|
|
944
975
|
if (event.type === "text_delta") {
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
compactionTextBytes,
|
|
976
|
+
compaction = appendString(
|
|
977
|
+
compaction,
|
|
948
978
|
event.text,
|
|
949
979
|
"retained_collectors",
|
|
950
|
-
)
|
|
980
|
+
);
|
|
951
981
|
continue;
|
|
952
982
|
}
|
|
953
983
|
if (event.type !== "done" && event.type !== "incomplete" && event.type !== "error") continue;
|
|
@@ -999,17 +1029,16 @@ export function bridgeToResponsesSSE(
|
|
|
999
1029
|
part: { type: "output_text", text: "", annotations: [] },
|
|
1000
1030
|
});
|
|
1001
1031
|
currentMsg = {
|
|
1002
|
-
itemId, outputIndex, text:
|
|
1032
|
+
itemId, outputIndex, text: emptyChunks(),
|
|
1003
1033
|
citationFilter: createCitationMarkerFilter(),
|
|
1004
1034
|
...(event.phase ? { phase: event.phase } : {}),
|
|
1005
1035
|
};
|
|
1006
1036
|
}
|
|
1007
|
-
|
|
1037
|
+
currentMsg.text = appendString(
|
|
1008
1038
|
currentMsg.text,
|
|
1009
|
-
currentMsg.textBytes,
|
|
1010
1039
|
event.text,
|
|
1011
1040
|
"retained_collectors",
|
|
1012
|
-
)
|
|
1041
|
+
);
|
|
1013
1042
|
// A citation span can straddle a delta boundary, so the filter withholds an
|
|
1014
1043
|
// unterminated tail and releases it at close (#3150). The accumulator above
|
|
1015
1044
|
// keeps the raw text; it is stripped once in closeCurrentMessage.
|
|
@@ -1030,12 +1059,11 @@ export function bridgeToResponsesSSE(
|
|
|
1030
1059
|
// recorded for a LATER tool call (CodeRabbit on #971).
|
|
1031
1060
|
flushHiddenRawReasoning();
|
|
1032
1061
|
rawReasoningForNextToolCall = "";
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
hiddenThinkingBytes,
|
|
1062
|
+
hiddenThinking = appendString(
|
|
1063
|
+
hiddenThinking,
|
|
1036
1064
|
event.thinking,
|
|
1037
1065
|
"reasoning",
|
|
1038
|
-
)
|
|
1066
|
+
);
|
|
1039
1067
|
break;
|
|
1040
1068
|
}
|
|
1041
1069
|
if (currentMsg) closeCurrentMessage("commentary");
|
|
@@ -1051,14 +1079,13 @@ export function bridgeToResponsesSSE(
|
|
|
1051
1079
|
item_id: itemId, output_index: outputIndex, summary_index: 0,
|
|
1052
1080
|
part: { type: "summary_text", text: "" },
|
|
1053
1081
|
});
|
|
1054
|
-
currentReasoning = { itemId, outputIndex, text:
|
|
1082
|
+
currentReasoning = { itemId, outputIndex, text: emptyChunks() };
|
|
1055
1083
|
}
|
|
1056
|
-
|
|
1084
|
+
currentReasoning.text = appendString(
|
|
1057
1085
|
currentReasoning.text,
|
|
1058
|
-
currentReasoning.textBytes,
|
|
1059
1086
|
event.thinking,
|
|
1060
1087
|
"reasoning",
|
|
1061
|
-
)
|
|
1088
|
+
);
|
|
1062
1089
|
emit("response.reasoning_summary_text.delta", {
|
|
1063
1090
|
item_id: currentReasoning.itemId, output_index: currentReasoning.outputIndex,
|
|
1064
1091
|
summary_index: 0, delta: event.thinking,
|
|
@@ -1093,12 +1120,11 @@ export function bridgeToResponsesSSE(
|
|
|
1093
1120
|
}
|
|
1094
1121
|
case "reasoning_raw_delta": {
|
|
1095
1122
|
if (options?.hideThinkingSummary) {
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
hiddenRawReasoningBytes,
|
|
1123
|
+
hiddenRawReasoning = appendString(
|
|
1124
|
+
hiddenRawReasoning,
|
|
1099
1125
|
event.text,
|
|
1100
1126
|
"reasoning",
|
|
1101
|
-
)
|
|
1127
|
+
);
|
|
1102
1128
|
break;
|
|
1103
1129
|
}
|
|
1104
1130
|
if (currentMsg) closeCurrentMessage("commentary");
|
|
@@ -1108,14 +1134,13 @@ export function bridgeToResponsesSSE(
|
|
|
1108
1134
|
const itemId = `rs_${uuid()}`;
|
|
1109
1135
|
const item = { type: "reasoning", id: itemId, summary: [] as { type: string; text: string }[] };
|
|
1110
1136
|
emit("response.output_item.added", { output_index: outputIndex, item });
|
|
1111
|
-
currentRawReasoning = { itemId, outputIndex, text:
|
|
1137
|
+
currentRawReasoning = { itemId, outputIndex, text: emptyChunks() };
|
|
1112
1138
|
}
|
|
1113
|
-
|
|
1139
|
+
currentRawReasoning.text = appendString(
|
|
1114
1140
|
currentRawReasoning.text,
|
|
1115
|
-
currentRawReasoning.textBytes,
|
|
1116
1141
|
event.text,
|
|
1117
1142
|
"reasoning",
|
|
1118
|
-
)
|
|
1143
|
+
);
|
|
1119
1144
|
// Raw reasoning (openai-chat reasoning_content, kiro tags) rides the CONTENT
|
|
1120
1145
|
// channel. Clients control raw-reasoning display; this text is not a
|
|
1121
1146
|
// provider-authored summary.
|
|
@@ -1175,7 +1200,7 @@ export function bridgeToResponsesSSE(
|
|
|
1175
1200
|
}
|
|
1176
1201
|
case "tool_call_delta": {
|
|
1177
1202
|
if (currentToolCall) {
|
|
1178
|
-
({ value: currentToolCall.args, bytes: currentToolCall.argsBytes } =
|
|
1203
|
+
({ value: currentToolCall.args, bytes: currentToolCall.argsBytes } = appendStringDirect(
|
|
1179
1204
|
currentToolCall.args,
|
|
1180
1205
|
currentToolCall.argsBytes,
|
|
1181
1206
|
event.arguments,
|
|
@@ -1311,12 +1336,12 @@ export function bridgeToResponsesSSE(
|
|
|
1311
1336
|
// Exactly one compaction item per turn; codex-rs takes the first and fatals on 0.
|
|
1312
1337
|
const item = {
|
|
1313
1338
|
type: "compaction", id: `cmp_${uuid()}`,
|
|
1314
|
-
encrypted_content: event.compactionEncryptedContent ?? encodeCompactionSummary(
|
|
1339
|
+
encrypted_content: event.compactionEncryptedContent ?? encodeCompactionSummary(joinChunks(compaction)),
|
|
1315
1340
|
};
|
|
1316
1341
|
emit("response.output_item.done", { output_index: outputIndex, item });
|
|
1317
1342
|
retainFinishedItem(item as OutputItem, event.compactionEncryptedContent
|
|
1318
1343
|
? bytesOf(event.compactionEncryptedContent)
|
|
1319
|
-
:
|
|
1344
|
+
: compaction.bytes);
|
|
1320
1345
|
outputIndex++;
|
|
1321
1346
|
}
|
|
1322
1347
|
// Recognize every adapter's truncation vocabulary, not just the canonical pair.
|
|
@@ -1620,6 +1645,34 @@ function buildResponseJSONWithBudget(
|
|
|
1620
1645
|
const encoder = new TextEncoder();
|
|
1621
1646
|
const bytesOf = (value: string): number => Buffer.byteLength(value);
|
|
1622
1647
|
const appendBatchString = (
|
|
1648
|
+
previous: StringChunks,
|
|
1649
|
+
fragment: string,
|
|
1650
|
+
kind: TranslatorBufferKind,
|
|
1651
|
+
callId?: string,
|
|
1652
|
+
): StringChunks => {
|
|
1653
|
+
const fragmentBytes = bytesOf(fragment);
|
|
1654
|
+
if (fragmentBytes === 0) return previous;
|
|
1655
|
+
const nextBytes = previous.bytes + fragmentBytes;
|
|
1656
|
+
if (!budget) {
|
|
1657
|
+
previous.chunks.push(fragment);
|
|
1658
|
+
return { chunks: previous.chunks, bytes: nextBytes };
|
|
1659
|
+
}
|
|
1660
|
+
const scope = { kind, ...(callId ? { callId } : {}) };
|
|
1661
|
+
const reservation = budget.reserveTransient(nextBytes, scope);
|
|
1662
|
+
try {
|
|
1663
|
+
previous.chunks.push(fragment);
|
|
1664
|
+
const result: StringChunks = { chunks: previous.chunks, bytes: nextBytes };
|
|
1665
|
+
reservation.commitRetained();
|
|
1666
|
+
budget.releaseRetained(previous.bytes, scope);
|
|
1667
|
+
return result;
|
|
1668
|
+
} catch (error) {
|
|
1669
|
+
reservation.release();
|
|
1670
|
+
throw error;
|
|
1671
|
+
}
|
|
1672
|
+
};
|
|
1673
|
+
// Batch counterpart: tool-call arguments require direct string representation for immediate
|
|
1674
|
+
// JSON serialization compatibility.
|
|
1675
|
+
const appendBatchStringDirect = (
|
|
1623
1676
|
previous: string,
|
|
1624
1677
|
previousBytes: number,
|
|
1625
1678
|
fragment: string,
|
|
@@ -1668,17 +1721,13 @@ function buildResponseJSONWithBudget(
|
|
|
1668
1721
|
// which is only true for a `done` without a stop reason. A buffered turn whose adapter simply
|
|
1669
1722
|
// stopped emitting has no terminal at all, and must not be reported as a success.
|
|
1670
1723
|
let sawTerminal = false;
|
|
1671
|
-
let
|
|
1672
|
-
let compactionTextBytes = 0;
|
|
1724
|
+
let batchCompaction = emptyChunks();
|
|
1673
1725
|
let compactionEncryptedContent: string | undefined;
|
|
1674
1726
|
|
|
1675
|
-
let currentText =
|
|
1676
|
-
let currentTextBytes = 0;
|
|
1727
|
+
let currentText = emptyChunks();
|
|
1677
1728
|
let currentTextPhase: OcxMessagePhase | undefined;
|
|
1678
|
-
let currentSummaryReasoning =
|
|
1679
|
-
let
|
|
1680
|
-
let currentRawReasoning = "";
|
|
1681
|
-
let currentRawReasoningBytes = 0;
|
|
1729
|
+
let currentSummaryReasoning = emptyChunks();
|
|
1730
|
+
let currentRawReasoning = emptyChunks();
|
|
1682
1731
|
// Same replay-cache handoff as the streaming path (issue #950): the most
|
|
1683
1732
|
// recently flushed raw reasoning waits for the tool call it preceded.
|
|
1684
1733
|
let rawReasoningForNextToolCall = "";
|
|
@@ -1716,12 +1765,13 @@ function buildResponseJSONWithBudget(
|
|
|
1716
1765
|
};
|
|
1717
1766
|
|
|
1718
1767
|
const flushText = (inferredPhase?: OcxMessagePhase) => {
|
|
1719
|
-
|
|
1768
|
+
const currentTextStr = joinChunks(currentText);
|
|
1769
|
+
if (!currentTextStr) return;
|
|
1720
1770
|
const phase = currentTextPhase ?? inferredPhase;
|
|
1721
1771
|
// ChatGPT-backend citation markers arrive as literal private-use characters that the
|
|
1722
1772
|
// Codex TUI prints verbatim (#3150). Strip them here rather than at the accumulator so
|
|
1723
1773
|
// the retained byte accounting above still describes what the upstream actually sent.
|
|
1724
|
-
const text = stripCitationMarkers(
|
|
1774
|
+
const text = stripCitationMarkers(currentTextStr);
|
|
1725
1775
|
const sourceBytes = pendingWebSources.reduce((sum, source) => sum + bytesOf(JSON.stringify(source)), 0);
|
|
1726
1776
|
const annotations = pendingWebSources.map(s => ({
|
|
1727
1777
|
type: "url_citation", url: s.url, ...(s.title ? { title: s.title } : {}), start_index: 0, end_index: 0,
|
|
@@ -1732,60 +1782,57 @@ function buildResponseJSONWithBudget(
|
|
|
1732
1782
|
content: [{ type: "output_text", text, annotations }],
|
|
1733
1783
|
...(phase ? { phase } : {}),
|
|
1734
1784
|
} as OutputItem;
|
|
1735
|
-
pushOutput(item,
|
|
1785
|
+
pushOutput(item, currentText.bytes);
|
|
1736
1786
|
budget?.releaseRetained(sourceBytes, { kind: "tool_search_sources" });
|
|
1737
|
-
currentText =
|
|
1738
|
-
currentTextBytes = 0;
|
|
1787
|
+
currentText = emptyChunks();
|
|
1739
1788
|
currentTextPhase = undefined;
|
|
1740
1789
|
};
|
|
1741
1790
|
const flushSummaryReasoning = () => {
|
|
1742
|
-
|
|
1791
|
+
const summaryText = joinChunks(currentSummaryReasoning);
|
|
1792
|
+
if (!summaryText && !batchSignature && batchRedacted.length === 0) return;
|
|
1743
1793
|
const envelope: ReasoningEnvelope = {};
|
|
1744
1794
|
if (batchSignature) envelope.sig = batchSignature;
|
|
1745
1795
|
if (batchRedacted.length > 0) envelope.red = batchRedacted;
|
|
1746
1796
|
const hidden = options?.hideThinkingSummary === true;
|
|
1747
|
-
if (hidden &&
|
|
1797
|
+
if (hidden && summaryText && (envelope.sig || envelope.red)) envelope.txt = summaryText;
|
|
1748
1798
|
const encrypted = envelope.sig || envelope.red || envelope.txt ? encodeReasoningEnvelope(envelope, budget) : undefined;
|
|
1749
|
-
const sourceBytes =
|
|
1799
|
+
const sourceBytes = currentSummaryReasoning.bytes + batchSignatureBytes + batchRedactedBytes;
|
|
1750
1800
|
batchSignature = undefined;
|
|
1751
1801
|
batchSignatureBytes = 0;
|
|
1752
1802
|
batchRedacted = [];
|
|
1753
1803
|
batchRedactedBytes = 0;
|
|
1754
1804
|
if (hidden && !encrypted) {
|
|
1755
1805
|
budget?.releaseRetained(sourceBytes, { kind: "reasoning" });
|
|
1756
|
-
currentSummaryReasoning =
|
|
1757
|
-
currentSummaryReasoningBytes = 0;
|
|
1806
|
+
currentSummaryReasoning = emptyChunks();
|
|
1758
1807
|
return;
|
|
1759
1808
|
}
|
|
1760
1809
|
const item = {
|
|
1761
1810
|
type: "reasoning", id: `rs_${uuid()}`,
|
|
1762
|
-
summary: !hidden &&
|
|
1811
|
+
summary: !hidden && summaryText ? [{ type: "summary_text", text: summaryText }] : [],
|
|
1763
1812
|
...(encrypted ? { encrypted_content: encrypted } : {}),
|
|
1764
1813
|
} as OutputItem;
|
|
1765
1814
|
pushOutput(item, sourceBytes, "reasoning");
|
|
1766
|
-
currentSummaryReasoning =
|
|
1767
|
-
currentSummaryReasoningBytes = 0;
|
|
1815
|
+
currentSummaryReasoning = emptyChunks();
|
|
1768
1816
|
};
|
|
1769
1817
|
const flushRawReasoning = () => {
|
|
1770
|
-
|
|
1771
|
-
|
|
1818
|
+
const rawText = joinChunks(currentRawReasoning);
|
|
1819
|
+
if (!rawText) return;
|
|
1820
|
+
rawReasoningForNextToolCall = rawText;
|
|
1772
1821
|
if (options?.hideThinkingSummary === true) {
|
|
1773
1822
|
// Same contract as the streaming path: no visible reasoning, txt-only envelope round-trip.
|
|
1774
1823
|
pushOutput({
|
|
1775
1824
|
type: "reasoning", id: `rs_${uuid()}`, summary: [],
|
|
1776
|
-
encrypted_content: encodeReasoningEnvelope({ txt:
|
|
1777
|
-
},
|
|
1778
|
-
currentRawReasoning =
|
|
1779
|
-
currentRawReasoningBytes = 0;
|
|
1825
|
+
encrypted_content: encodeReasoningEnvelope({ txt: rawText }, budget),
|
|
1826
|
+
}, currentRawReasoning.bytes, "reasoning");
|
|
1827
|
+
currentRawReasoning = emptyChunks();
|
|
1780
1828
|
return;
|
|
1781
1829
|
}
|
|
1782
1830
|
pushOutput({
|
|
1783
1831
|
type: "reasoning", id: `rs_${uuid()}`,
|
|
1784
1832
|
summary: [],
|
|
1785
|
-
content: [{ type: "reasoning_text", text:
|
|
1786
|
-
},
|
|
1787
|
-
currentRawReasoning =
|
|
1788
|
-
currentRawReasoningBytes = 0;
|
|
1833
|
+
content: [{ type: "reasoning_text", text: rawText }],
|
|
1834
|
+
}, currentRawReasoning.bytes, "reasoning");
|
|
1835
|
+
currentRawReasoning = emptyChunks();
|
|
1789
1836
|
};
|
|
1790
1837
|
const flushToolCall = (status: "completed" | "incomplete" = "completed") => {
|
|
1791
1838
|
if (!currentToolCallId) return;
|
|
@@ -1861,9 +1908,9 @@ function buildResponseJSONWithBudget(
|
|
|
1861
1908
|
case "text_delta":
|
|
1862
1909
|
// Only flush on an explicit phase change. A later delta that omits `phase` must keep
|
|
1863
1910
|
// appending under the previously established phase.
|
|
1864
|
-
if (currentText && e.phase !== undefined && currentTextPhase !== e.phase) flushText("commentary");
|
|
1865
|
-
if (currentSummaryReasoning) flushSummaryReasoning();
|
|
1866
|
-
if (currentRawReasoning) flushRawReasoning();
|
|
1911
|
+
if (currentText.bytes > 0 && e.phase !== undefined && currentTextPhase !== e.phase) flushText("commentary");
|
|
1912
|
+
if (currentSummaryReasoning.bytes > 0) flushSummaryReasoning();
|
|
1913
|
+
if (currentRawReasoning.bytes > 0) flushRawReasoning();
|
|
1867
1914
|
// Empty text deltas (batch chat responses always carry content, often "") must
|
|
1868
1915
|
// not wipe reasoning that precedes a tool call (#950 non-streaming path).
|
|
1869
1916
|
if (e.text.length > 0) rawReasoningForNextToolCall = "";
|
|
@@ -1871,26 +1918,26 @@ function buildResponseJSONWithBudget(
|
|
|
1871
1918
|
// Compaction turns keep the summary out of normal message output (replay dedup — see
|
|
1872
1919
|
// bridgeToResponsesSSE); it ships only inside the synthetic compaction item below.
|
|
1873
1920
|
if (options?.compaction) {
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
)
|
|
1921
|
+
batchCompaction = appendBatchString(
|
|
1922
|
+
batchCompaction, e.text, "retained_collectors",
|
|
1923
|
+
);
|
|
1877
1924
|
}
|
|
1878
1925
|
else {
|
|
1879
1926
|
if (e.phase !== undefined) currentTextPhase = e.phase;
|
|
1880
|
-
|
|
1881
|
-
currentText,
|
|
1882
|
-
)
|
|
1927
|
+
currentText = appendBatchString(
|
|
1928
|
+
currentText, e.text, "retained_collectors",
|
|
1929
|
+
);
|
|
1883
1930
|
}
|
|
1884
1931
|
break;
|
|
1885
1932
|
case "thinking_delta":
|
|
1886
|
-
if (currentText) flushText("commentary");
|
|
1887
|
-
if (currentRawReasoning) flushRawReasoning();
|
|
1933
|
+
if (currentText.bytes > 0) flushText("commentary");
|
|
1934
|
+
if (currentRawReasoning.bytes > 0) flushRawReasoning();
|
|
1888
1935
|
if (e.thinking.length > 0) rawReasoningForNextToolCall = "";
|
|
1889
1936
|
if (currentToolCallId) flushToolCall();
|
|
1890
1937
|
{
|
|
1891
|
-
|
|
1892
|
-
currentSummaryReasoning,
|
|
1893
|
-
)
|
|
1938
|
+
currentSummaryReasoning = appendBatchString(
|
|
1939
|
+
currentSummaryReasoning, e.thinking, "reasoning",
|
|
1940
|
+
);
|
|
1894
1941
|
}
|
|
1895
1942
|
break;
|
|
1896
1943
|
case "thinking_signature":
|
|
@@ -1923,19 +1970,19 @@ function buildResponseJSONWithBudget(
|
|
|
1923
1970
|
batchKiroRedacted = e.data;
|
|
1924
1971
|
break;
|
|
1925
1972
|
case "reasoning_raw_delta":
|
|
1926
|
-
if (currentText) flushText("commentary");
|
|
1927
|
-
if (currentSummaryReasoning) flushSummaryReasoning();
|
|
1973
|
+
if (currentText.bytes > 0) flushText("commentary");
|
|
1974
|
+
if (currentSummaryReasoning.bytes > 0) flushSummaryReasoning();
|
|
1928
1975
|
if (currentToolCallId) flushToolCall();
|
|
1929
1976
|
{
|
|
1930
|
-
|
|
1931
|
-
currentRawReasoning,
|
|
1932
|
-
)
|
|
1977
|
+
currentRawReasoning = appendBatchString(
|
|
1978
|
+
currentRawReasoning, e.text, "reasoning",
|
|
1979
|
+
);
|
|
1933
1980
|
}
|
|
1934
1981
|
break;
|
|
1935
1982
|
case "tool_call_start": {
|
|
1936
|
-
if (currentText) flushText("commentary");
|
|
1937
|
-
if (currentSummaryReasoning) flushSummaryReasoning();
|
|
1938
|
-
if (currentRawReasoning) flushRawReasoning();
|
|
1983
|
+
if (currentText.bytes > 0) flushText("commentary");
|
|
1984
|
+
if (currentSummaryReasoning.bytes > 0) flushSummaryReasoning();
|
|
1985
|
+
if (currentRawReasoning.bytes > 0) flushRawReasoning();
|
|
1939
1986
|
if (rawReasoningForNextToolCall) {
|
|
1940
1987
|
rememberReasoningForCall(e.id, rawReasoningForNextToolCall, replayCacheScope);
|
|
1941
1988
|
}
|
|
@@ -1963,7 +2010,7 @@ function buildResponseJSONWithBudget(
|
|
|
1963
2010
|
}
|
|
1964
2011
|
case "tool_call_delta":
|
|
1965
2012
|
{
|
|
1966
|
-
({ value: currentToolCallArgs, bytes: currentToolCallArgsBytes } =
|
|
2013
|
+
({ value: currentToolCallArgs, bytes: currentToolCallArgsBytes } = appendBatchStringDirect(
|
|
1967
2014
|
currentToolCallArgs, currentToolCallArgsBytes, e.arguments, "tool_args", currentToolCallId,
|
|
1968
2015
|
));
|
|
1969
2016
|
}
|
|
@@ -1995,9 +2042,9 @@ function buildResponseJSONWithBudget(
|
|
|
1995
2042
|
// single finalized item, emitted on `end`. Begin is a no-op here.
|
|
1996
2043
|
break;
|
|
1997
2044
|
case "web_search_call_end": {
|
|
1998
|
-
if (currentText) flushText("commentary");
|
|
1999
|
-
if (currentSummaryReasoning) flushSummaryReasoning();
|
|
2000
|
-
if (currentRawReasoning) flushRawReasoning();
|
|
2045
|
+
if (currentText.bytes > 0) flushText("commentary");
|
|
2046
|
+
if (currentSummaryReasoning.bytes > 0) flushSummaryReasoning();
|
|
2047
|
+
if (currentRawReasoning.bytes > 0) flushRawReasoning();
|
|
2001
2048
|
flushToolCall();
|
|
2002
2049
|
const safeSources = safeWebSearchSources(e.sources);
|
|
2003
2050
|
pushOutput({
|
|
@@ -2085,9 +2132,9 @@ function buildResponseJSONWithBudget(
|
|
|
2085
2132
|
) {
|
|
2086
2133
|
const item = {
|
|
2087
2134
|
type: "compaction", id: `cmp_${uuid()}`,
|
|
2088
|
-
encrypted_content: compactionEncryptedContent ?? encodeCompactionSummary(
|
|
2135
|
+
encrypted_content: compactionEncryptedContent ?? encodeCompactionSummary(joinChunks(batchCompaction)),
|
|
2089
2136
|
};
|
|
2090
|
-
pushOutput(item, compactionEncryptedContent ? bytesOf(compactionEncryptedContent) :
|
|
2137
|
+
pushOutput(item, compactionEncryptedContent ? bytesOf(compactionEncryptedContent) : batchCompaction.bytes);
|
|
2091
2138
|
}
|
|
2092
2139
|
|
|
2093
2140
|
const failure = errorEvent ? adapterFailureFromEvent(errorEvent) : undefined;
|