pi-openai-toolkit 0.20.7 → 0.21.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.
Files changed (55) hide show
  1. package/LICENSE +21 -21
  2. package/NOTICE +83 -83
  3. package/README.md +8 -26
  4. package/README.zh.md +8 -26
  5. package/config.schema.json +278 -366
  6. package/docs/configuration.md +294 -310
  7. package/docs/internals.md +166 -175
  8. package/package.json +3 -13
  9. package/src/compact-client.ts +412 -389
  10. package/src/compaction-errors.ts +96 -0
  11. package/src/compaction-projection.ts +35 -35
  12. package/src/config/legacy.ts +519 -717
  13. package/src/config/migration.ts +103 -100
  14. package/src/config/notifications.ts +36 -28
  15. package/src/config/policy.ts +10 -18
  16. package/src/config/v2.ts +1167 -1380
  17. package/src/config.ts +310 -319
  18. package/src/context-management/codex-provider.ts +270 -270
  19. package/src/context-management/history-notes.ts +357 -357
  20. package/src/context-management/manual-compact.ts +0 -11
  21. package/src/context-management/tools.ts +371 -371
  22. package/src/context-management/types.ts +168 -168
  23. package/src/context-management/window-manager.ts +801 -801
  24. package/src/debug.ts +238 -237
  25. package/src/deferred-tool-carryover.ts +282 -282
  26. package/src/details-store.ts +151 -151
  27. package/src/extension-runtime.ts +1662 -1623
  28. package/src/image-generation/extension.ts +189 -189
  29. package/src/image-generation/protocol.ts +579 -579
  30. package/src/image-generation/types.ts +300 -300
  31. package/src/native-fallback.ts +4 -132
  32. package/src/payload-rewrite.ts +515 -518
  33. package/src/pi-context-hook.ts +117 -117
  34. package/src/remote-v2-client.ts +811 -814
  35. package/src/request-context-cache.ts +155 -155
  36. package/src/responses-headers.ts +180 -180
  37. package/src/runtime.ts +865 -832
  38. package/src/serializer.ts +882 -882
  39. package/src/supported-environment.ts +27 -27
  40. package/src/types.ts +565 -648
  41. package/src/web-search/alpha-client.ts +876 -876
  42. package/src/web-search/extension.ts +530 -530
  43. package/src/web-search/payload.ts +299 -299
  44. package/src/web-search/prompt.ts +60 -60
  45. package/src/web-search/types.ts +166 -166
  46. package/tsconfig.check.json +50 -54
  47. package/src/auto-mode/classifier.ts +0 -161
  48. package/src/auto-mode/extension.ts +0 -691
  49. package/src/auto-mode/model-selection-guard.ts +0 -52
  50. package/src/auto-mode/policy.ts +0 -146
  51. package/src/auto-mode/prompt.ts +0 -279
  52. package/src/auto-mode/reviewer.ts +0 -325
  53. package/src/auto-mode/tool-review-tui.ts +0 -211
  54. package/src/auto-mode/transcript.ts +0 -135
  55. package/src/auto-mode/types.ts +0 -137
@@ -1,814 +1,811 @@
1
- import { isDeepStrictEqual } from "node:util";
2
- import { writeDebugArtifact } from "./debug";
3
- import { buildResponsesRequestHeaders } from "./responses-headers";
4
- import type { NativeCompactionRuntime } from "./runtime";
5
- import type { NativeCompactionRequestBody, ResponsesInputItem } from "./serializer";
6
- import type { ArtifactContext, CompactionConfig } from "./types";
7
-
8
- const JSON_CONTENT_TYPE = "application/json";
9
- const SSE_CONTENT_TYPE = "text/event-stream";
10
-
11
- export type RemoteV2CompactionItem = Record<string, unknown> & {
12
- type: "compaction";
13
- encrypted_content: string;
14
- };
15
-
16
- export type RemoteV2ResponseUsage = {
17
- input_tokens?: number;
18
- output_tokens?: number;
19
- total_tokens?: number;
20
- [key: string]: unknown;
21
- };
22
-
23
- export type RemoteV2ResponseEnvelope = Record<string, unknown> & {
24
- id?: string;
25
- created_at?: number | string;
26
- status: "completed";
27
- /** Normalized output always includes the reconciled checkpoint. */
28
- output: unknown[];
29
- usage?: RemoteV2ResponseUsage;
30
- };
31
-
32
- export type RemoteV2CompactionClientFailureReason =
33
- | "aborted"
34
- | "network-error"
35
- | "non-2xx"
36
- | "empty-body"
37
- | "invalid-sse"
38
- | "error-event"
39
- | "missing-completed-event"
40
- | "incomplete-response"
41
- | "invalid-compaction-count"
42
- | "malformed-compaction-item"
43
- | "conflicting-compaction-item"
44
- | "invalid-compaction-metadata"
45
- | "invalid-event-order"
46
- | "duplicate-completed-event";
47
-
48
- export type RemoteV2CompactionClientSuccess = {
49
- ok: true;
50
- status: number;
51
- compactedWindow: [RemoteV2CompactionItem];
52
- compactResponseId?: string;
53
- createdAt?: string;
54
- usage?: RemoteV2ResponseUsage;
55
- response: RemoteV2ResponseEnvelope;
56
- };
57
-
58
- export type RemoteV2CompactionClientFailure = {
59
- ok: false;
60
- reason: RemoteV2CompactionClientFailureReason;
61
- status?: number;
62
- errorMessage?: string;
63
- responseText?: string;
64
- responseJson?: unknown;
65
- };
66
-
67
- export type RemoteV2CompactionClientResult = RemoteV2CompactionClientSuccess | RemoteV2CompactionClientFailure;
68
-
69
- export type RemoteV2CompactionRequestBody = Omit<NativeCompactionRequestBody, "input"> & {
70
- input: [...ResponsesInputItem[], { type: "compaction_trigger" }];
71
- store: false;
72
- stream: true;
73
- };
74
-
75
- export type ExecuteRemoteV2CompactionOptions = {
76
- runtime: NativeCompactionRuntime;
77
- request: NativeCompactionRequestBody;
78
- signal?: AbortSignal;
79
- settings?: CompactionConfig;
80
- context?: ArtifactContext;
81
- };
82
-
83
- type ParsedSseEvent = {
84
- event?: string;
85
- dataText: string;
86
- data?: unknown;
87
- };
88
-
89
- function isRecord(value: unknown): value is Record<string, unknown> {
90
- return !!value && typeof value === "object" && !Array.isArray(value);
91
- }
92
-
93
- function isAbortError(error: unknown): boolean {
94
- return (
95
- (error instanceof DOMException && error.name === "AbortError") ||
96
- (error instanceof Error && (error.name === "AbortError" || error.name === "ABORT_ERR"))
97
- );
98
- }
99
-
100
- function isNonEmptyString(value: unknown): value is string {
101
- return typeof value === "string" && value.trim().length > 0;
102
- }
103
-
104
- function normalizeResponseTimestamp(value: unknown): string | undefined {
105
- if (typeof value === "number" && Number.isFinite(value)) {
106
- const milliseconds = value > 1_000_000_000_000 ? value : value * 1000;
107
- return new Date(milliseconds).toISOString();
108
- }
109
-
110
- if (typeof value !== "string") {
111
- return undefined;
112
- }
113
-
114
- const trimmed = value.trim();
115
- if (!trimmed) {
116
- return undefined;
117
- }
118
-
119
- const parsed = Date.parse(trimmed);
120
- return Number.isNaN(parsed) ? trimmed : new Date(parsed).toISOString();
121
- }
122
-
123
- function toHeaders(runtime: NativeCompactionRuntime): Record<string, string> {
124
- return Object.fromEntries(
125
- buildResponsesRequestHeaders(runtime, {
126
- accept: SSE_CONTENT_TYPE,
127
- contentType: JSON_CONTENT_TYPE,
128
- sessionId: runtime.sessionId,
129
- }).entries(),
130
- );
131
- }
132
-
133
- /**
134
- * Codex `/responses` rejects histories that mix this Astra-only item into a
135
- * compaction request (verified upstream in Oh My Pi: the compaction path is
136
- * deliberately built outside the effort planner). The host never emits the
137
- * item into a payload we would replay today, but the item is transparently
138
- * carried by our opaque-window replay, so strip it defensively on every
139
- * remote v2 request regardless of API family.
140
- */
141
- export function stripConfigurationUpdateItems(input: readonly unknown[]): unknown[] {
142
- return input.filter((item) => !isRecord(item) || item["type"] !== "configuration_update");
143
- }
144
-
145
- export function buildRemoteV2CompactionRequest(
146
- request: NativeCompactionRequestBody,
147
- ): RemoteV2CompactionRequestBody {
148
- return {
149
- ...structuredClone(request),
150
- input: [
151
- ...(stripConfigurationUpdateItems(structuredClone(request.input)) as ResponsesInputItem[]),
152
- { type: "compaction_trigger" },
153
- ],
154
- store: false,
155
- stream: true,
156
- };
157
- }
158
-
159
- /** Shared line framing; yielding lazily keeps bytes after a terminal out of parsing. */
160
- class SseEventParser {
161
- private line = "";
162
- private skipLf = false;
163
- private event: string | undefined;
164
- private dataLines: string[] = [];
165
-
166
- *push(text: string): Generator<ParsedSseEvent> {
167
- for (const character of text) {
168
- if (this.skipLf) {
169
- this.skipLf = false;
170
- if (character === "\n") continue;
171
- }
172
- if (character !== "\r" && character !== "\n") {
173
- this.line += character;
174
- continue;
175
- }
176
- this.skipLf = character === "\r";
177
- const line = this.line;
178
- this.line = "";
179
- if (line === "") {
180
- const event = this.event;
181
- const dataLines = this.dataLines;
182
- this.event = undefined;
183
- this.dataLines = [];
184
- if (dataLines.length === 0) continue;
185
- const dataText = dataLines.join("\n");
186
- yield dataText === "[DONE]"
187
- ? { event, dataText }
188
- : { event, dataText, data: JSON.parse(dataText) };
189
- } else if (line.startsWith("event:")) {
190
- this.event = line.slice("event:".length).trim();
191
- } else if (line === "data" || line.startsWith("data:")) {
192
- this.dataLines.push(line.slice("data:".length).replace(/^ /, ""));
193
- }
194
- }
195
- }
196
- }
197
-
198
- export function parseSseEvents(raw: string): ParsedSseEvent[] | undefined {
199
- try {
200
- // Retain the buffered helper's historical support for a final unterminated block.
201
- const events = [...new SseEventParser().push(`${raw}\n\n`)];
202
- return events.length > 0 ? events : undefined;
203
- } catch {
204
- return undefined;
205
- }
206
- }
207
-
208
- function isRemoteV2CompactionItem(value: unknown): value is RemoteV2CompactionItem {
209
- return isRecord(value) && value.type === "compaction" && isNonEmptyString(value.encrypted_content);
210
- }
211
-
212
- function getEventType(event: ParsedSseEvent): string | undefined {
213
- return isRecord(event.data) && isNonEmptyString(event.data.type) ? event.data.type : event.event;
214
- }
215
-
216
- function getErrorMessage(value: unknown): string | undefined {
217
- if (!isRecord(value)) return undefined;
218
- if (isNonEmptyString(value.message)) return value.message;
219
- if (isRecord(value.error) && isNonEmptyString(value.error.message)) return value.error.message;
220
- if (isNonEmptyString(value.error)) return value.error;
221
- return undefined;
222
- }
223
-
224
- function writeCompactArtifact(
225
- data: unknown,
226
- settings: CompactionConfig | undefined,
227
- context: ArtifactContext | undefined,
228
- ): void {
229
- if (!settings || !context) {
230
- return;
231
- }
232
-
233
- writeDebugArtifact("compact-response", data, settings, context);
234
- }
235
-
236
- function summarizeEvents(events: readonly ParsedSseEvent[]): unknown[] {
237
- return events.map((event) => {
238
- const data = isRecord(event.data) ? event.data : undefined;
239
- const response = data && isRecord(data.response) ? data.response : undefined;
240
- const item = data && isRecord(data.item) ? data.item : undefined;
241
- return {
242
- event: event.event,
243
- type: data?.type,
244
- responseStatus: response?.status,
245
- responseId: response?.id,
246
- itemType: item?.type,
247
- };
248
- });
249
- }
250
-
251
- type OptionalMetadata<T> =
252
- | { valid: true; value?: T }
253
- | { valid: false };
254
-
255
- type CompactionCheckpointCandidate = {
256
- item: RemoteV2CompactionItem;
257
- responseId?: string;
258
- outputIndex?: number;
259
- outputPosition?: number;
260
- };
261
-
262
- type CheckpointReconciliation =
263
- | {
264
- ok: true;
265
- response: RemoteV2ResponseEnvelope;
266
- compactedWindow: [RemoteV2CompactionItem];
267
- }
268
- | {
269
- ok: false;
270
- reason:
271
- | "error-event"
272
- | "missing-completed-event"
273
- | "incomplete-response"
274
- | "invalid-compaction-count"
275
- | "malformed-compaction-item"
276
- | "conflicting-compaction-item"
277
- | "invalid-compaction-metadata"
278
- | "invalid-event-order"
279
- | "duplicate-completed-event";
280
- responseJson?: unknown;
281
- errorMessage?: string;
282
- };
283
-
284
- function hasOwn(value: Record<string, unknown>, key: string): boolean {
285
- return Object.prototype.hasOwnProperty.call(value, key);
286
- }
287
-
288
- function readOptionalNonEmptyString(value: Record<string, unknown>, key: string): OptionalMetadata<string> {
289
- if (!hasOwn(value, key)) {
290
- return { valid: true };
291
- }
292
- return isNonEmptyString(value[key]) ? { valid: true, value: value[key] } : { valid: false };
293
- }
294
-
295
- function readOptionalOutputIndex(value: Record<string, unknown>): OptionalMetadata<number> {
296
- if (!hasOwn(value, "output_index")) {
297
- return { valid: true };
298
- }
299
-
300
- const outputIndex = value.output_index;
301
- return typeof outputIndex === "number" && Number.isSafeInteger(outputIndex) && outputIndex >= 0
302
- ? { valid: true, value: outputIndex }
303
- : { valid: false };
304
- }
305
-
306
- function mergeCompactionItems(
307
- itemDone: RemoteV2CompactionItem,
308
- terminalOutput: RemoteV2CompactionItem,
309
- ): RemoteV2CompactionItem | undefined {
310
- for (const [key, value] of Object.entries(itemDone)) {
311
- if (hasOwn(terminalOutput, key) && !isDeepStrictEqual(value, terminalOutput[key])) return undefined;
312
- }
313
- return structuredClone({ ...itemDone, ...terminalOutput });
314
- }
315
-
316
- function readResponseId(value: Record<string, unknown>): OptionalMetadata<string> {
317
- const direct = readOptionalNonEmptyString(value, "response_id");
318
- if (!direct.valid) {
319
- return direct;
320
- }
321
-
322
- const nestedResponse: OptionalMetadata<string> = isRecord(value.response) ? readOptionalNonEmptyString(value.response, "id") : { valid: true };
323
- if (!nestedResponse.valid) {
324
- return nestedResponse;
325
- }
326
- if (direct.value !== undefined && nestedResponse.value !== undefined && direct.value !== nestedResponse.value) {
327
- return { valid: false };
328
- }
329
- return { valid: true, value: direct.value ?? nestedResponse.value };
330
- }
331
-
332
- function readCheckpointCandidate(
333
- item: Record<string, unknown>,
334
- metadataSource: Record<string, unknown> | undefined,
335
- outputPosition?: number,
336
- ): { ok: true; candidate: CompactionCheckpointCandidate } | { ok: false; reason: "malformed-compaction-item" | "invalid-compaction-metadata" } {
337
- if (!isRemoteV2CompactionItem(item)) {
338
- return { ok: false, reason: "malformed-compaction-item" };
339
- }
340
-
341
- const itemId = readOptionalNonEmptyString(item, "id");
342
- const itemResponseId = readResponseId(item);
343
- const itemOutputIndex = readOptionalOutputIndex(item);
344
- if (!itemId.valid || !itemResponseId.valid || !itemOutputIndex.valid) {
345
- return { ok: false, reason: "invalid-compaction-metadata" };
346
- }
347
-
348
- const sourceResponseId: OptionalMetadata<string> = metadataSource ? readResponseId(metadataSource) : { valid: true };
349
- const sourceOutputIndex: OptionalMetadata<number> = metadataSource ? readOptionalOutputIndex(metadataSource) : { valid: true };
350
- if (!sourceResponseId.valid || !sourceOutputIndex.valid) {
351
- return { ok: false, reason: "invalid-compaction-metadata" };
352
- }
353
- if (
354
- itemResponseId.value !== undefined &&
355
- sourceResponseId.value !== undefined &&
356
- itemResponseId.value !== sourceResponseId.value
357
- ) {
358
- return { ok: false, reason: "invalid-compaction-metadata" };
359
- }
360
- if (
361
- itemOutputIndex.value !== undefined &&
362
- sourceOutputIndex.value !== undefined &&
363
- itemOutputIndex.value !== sourceOutputIndex.value
364
- ) {
365
- return { ok: false, reason: "invalid-compaction-metadata" };
366
- }
367
-
368
- const outputIndex = sourceOutputIndex.value ?? itemOutputIndex.value;
369
- if (outputPosition !== undefined && outputIndex !== undefined && outputIndex !== outputPosition) {
370
- return { ok: false, reason: "invalid-compaction-metadata" };
371
- }
372
- return {
373
- ok: true,
374
- candidate: {
375
- item: structuredClone(item),
376
- responseId: sourceResponseId.value ?? itemResponseId.value,
377
- outputIndex,
378
- outputPosition,
379
- },
380
- };
381
- }
382
-
383
- function isPostTerminalEvent(event: ParsedSseEvent): boolean {
384
- if (event.dataText === "[DONE]") {
385
- return true;
386
- }
387
- return getEventType(event) === "keepalive";
388
- }
389
-
390
- function reconcileSseCheckpoint(events: readonly ParsedSseEvent[]): CheckpointReconciliation {
391
- const errorEvent = events.find((event) => {
392
- const type = getEventType(event);
393
- return type === "error" || type === "response.failed" || type === "response.incomplete";
394
- });
395
- if (errorEvent) {
396
- return {
397
- ok: false,
398
- reason: "error-event",
399
- responseJson: errorEvent.data,
400
- errorMessage: getErrorMessage(errorEvent.data),
401
- };
402
- }
403
-
404
- const completedIndexes = events.flatMap((event, index) => (getEventType(event) === "response.completed" ? [index] : []));
405
- if (completedIndexes.length === 0) {
406
- return { ok: false, reason: "missing-completed-event" };
407
- }
408
- if (completedIndexes.length > 1) {
409
- return { ok: false, reason: "duplicate-completed-event" };
410
- }
411
- const terminalIndex = completedIndexes[0]!;
412
-
413
- for (let index = 0; index < events.length; index += 1) {
414
- const event = events[index]!;
415
- if (index < terminalIndex && event.dataText === "[DONE]") {
416
- return { ok: false, reason: "invalid-event-order", responseJson: event.dataText };
417
- }
418
- if (index > terminalIndex && !isPostTerminalEvent(event)) {
419
- return { ok: false, reason: "invalid-event-order", responseJson: event.data };
420
- }
421
- }
422
-
423
- const completedEvent = events[terminalIndex]!;
424
- const completedData = isRecord(completedEvent.data) ? completedEvent.data : undefined;
425
- const completedResponse = completedData && isRecord(completedData.response) ? completedData.response : undefined;
426
- if (!completedResponse) {
427
- return { ok: false, reason: "incomplete-response" };
428
- }
429
-
430
- if (completedResponse.status !== "completed") {
431
- return { ok: false, reason: "incomplete-response", responseJson: completedResponse };
432
- }
433
- const hasOutput = hasOwn(completedResponse, "output");
434
- if (hasOutput && !Array.isArray(completedResponse.output)) {
435
- return { ok: false, reason: "incomplete-response", responseJson: completedResponse };
436
- }
437
- const terminalOutput: unknown[] | undefined = Array.isArray(completedResponse.output) ? completedResponse.output : undefined;
438
-
439
- const terminalResponseId = readOptionalNonEmptyString(completedResponse, "id");
440
- const completedEventResponseId: OptionalMetadata<string> = completedData ? readResponseId(completedData) : { valid: true };
441
- if (!terminalResponseId.valid || !completedEventResponseId.valid) {
442
- return { ok: false, reason: "invalid-compaction-metadata", responseJson: completedResponse };
443
- }
444
- if (
445
- terminalResponseId.value !== undefined &&
446
- completedEventResponseId.value !== undefined &&
447
- terminalResponseId.value !== completedEventResponseId.value
448
- ) {
449
- return { ok: false, reason: "invalid-compaction-metadata", responseJson: completedResponse };
450
- }
451
- let responseId = terminalResponseId.value ?? completedEventResponseId.value;
452
- for (const event of events.slice(0, terminalIndex)) {
453
- if (getEventType(event) !== "response.created" || !isRecord(event.data)) continue;
454
- const createdId = readResponseId(event.data);
455
- if (!createdId.valid || (createdId.value !== undefined && responseId !== undefined && createdId.value !== responseId)) {
456
- return { ok: false, reason: "invalid-compaction-metadata", responseJson: event.data };
457
- }
458
- responseId ??= createdId.value;
459
- }
460
-
461
- let itemDoneCandidate: CompactionCheckpointCandidate | undefined;
462
- for (let index = 0; index < terminalIndex; index += 1) {
463
- const event = events[index]!;
464
- if (getEventType(event) !== "response.output_item.done") {
465
- continue;
466
- }
467
- if (!isRecord(event.data) || !isRecord(event.data.item)) {
468
- return { ok: false, reason: "malformed-compaction-item", responseJson: event.data };
469
- }
470
- if (event.data.item.type !== "compaction") {
471
- continue;
472
- }
473
- if (itemDoneCandidate) {
474
- return { ok: false, reason: "invalid-compaction-count", responseJson: event.data.item };
475
- }
476
- const candidate = readCheckpointCandidate(event.data.item, event.data);
477
- if (!candidate.ok) {
478
- return { ok: false, reason: candidate.reason, responseJson: event.data.item };
479
- }
480
- itemDoneCandidate = candidate.candidate;
481
- }
482
-
483
- let terminalCandidate: CompactionCheckpointCandidate | undefined;
484
- let terminalPosition: number | undefined;
485
- if (terminalOutput) {
486
- const compactionPositions = terminalOutput.flatMap((item, index) =>
487
- isRecord(item) && item.type === "compaction" ? [index] : [],
488
- );
489
- if (compactionPositions.length > 1) {
490
- return { ok: false, reason: "invalid-compaction-count", responseJson: completedResponse };
491
- }
492
- if (compactionPositions.length === 1) {
493
- terminalPosition = compactionPositions[0]!;
494
- const outputItem = terminalOutput[terminalPosition];
495
- if (!isRecord(outputItem)) {
496
- return { ok: false, reason: "malformed-compaction-item", responseJson: outputItem };
497
- }
498
- const candidate = readCheckpointCandidate(outputItem, undefined, terminalPosition);
499
- if (!candidate.ok) {
500
- return { ok: false, reason: candidate.reason, responseJson: outputItem };
501
- }
502
- terminalCandidate = candidate.candidate;
503
- }
504
- }
505
-
506
- if (!itemDoneCandidate && !terminalCandidate) {
507
- return { ok: false, reason: "invalid-compaction-count", responseJson: completedResponse };
508
- }
509
-
510
- for (const candidate of [itemDoneCandidate, terminalCandidate]) {
511
- if (
512
- candidate?.responseId !== undefined &&
513
- responseId !== undefined &&
514
- candidate.responseId !== responseId
515
- ) {
516
- return { ok: false, reason: "invalid-compaction-metadata", responseJson: candidate.item };
517
- }
518
- }
519
- if (
520
- itemDoneCandidate?.responseId !== undefined &&
521
- terminalCandidate?.responseId !== undefined &&
522
- itemDoneCandidate.responseId !== terminalCandidate.responseId
523
- ) {
524
- return { ok: false, reason: "invalid-compaction-metadata", responseJson: terminalCandidate.item };
525
- }
526
- if (
527
- itemDoneCandidate?.outputIndex !== undefined &&
528
- terminalCandidate?.outputPosition !== undefined &&
529
- itemDoneCandidate.outputIndex !== terminalCandidate.outputPosition
530
- ) {
531
- return { ok: false, reason: "invalid-compaction-metadata", responseJson: terminalCandidate.item };
532
- }
533
- if (
534
- itemDoneCandidate?.outputIndex !== undefined &&
535
- terminalOutput !== undefined &&
536
- terminalOutput.length > 0 &&
537
- terminalCandidate === undefined &&
538
- itemDoneCandidate.outputIndex > terminalOutput.length
539
- ) {
540
- return { ok: false, reason: "invalid-compaction-metadata", responseJson: completedResponse };
541
- }
542
-
543
- let canonicalItem: RemoteV2CompactionItem;
544
- if (itemDoneCandidate && terminalCandidate) {
545
- const merged = mergeCompactionItems(itemDoneCandidate.item, terminalCandidate.item);
546
- if (!merged) {
547
- return { ok: false, reason: "conflicting-compaction-item", responseJson: terminalCandidate.item };
548
- }
549
- canonicalItem = merged;
550
- } else {
551
- canonicalItem = structuredClone((itemDoneCandidate ?? terminalCandidate)!.item);
552
- }
553
-
554
- const responseEnvelope = structuredClone(completedResponse) as RemoteV2ResponseEnvelope;
555
- if (terminalOutput) {
556
- const output = structuredClone(terminalOutput);
557
- if (terminalPosition !== undefined) {
558
- output[terminalPosition] = structuredClone(canonicalItem);
559
- } else {
560
- const insertionIndex = itemDoneCandidate?.outputIndex ?? output.length;
561
- output.splice(insertionIndex, 0, structuredClone(canonicalItem));
562
- }
563
- responseEnvelope.output = output;
564
- } else {
565
- responseEnvelope.output = [structuredClone(canonicalItem)];
566
- }
567
-
568
- return {
569
- ok: true,
570
- response: responseEnvelope,
571
- compactedWindow: [structuredClone(canonicalItem)],
572
- };
573
- }
574
-
575
- type CollectedSse = {
576
- responseText: string;
577
- events?: ParsedSseEvent[];
578
- reconciliation?: CheckpointReconciliation;
579
- };
580
-
581
- async function collectSseCheckpoint(response: Response, signal?: AbortSignal): Promise<CollectedSse> {
582
- const reader = response.body?.getReader();
583
- if (!reader) {
584
- if (signal?.aborted) throw new DOMException("Compaction aborted", "AbortError");
585
- return { responseText: "" };
586
- }
587
- const decoder = new TextDecoder();
588
- const parser = new SseEventParser();
589
- const events: ParsedSseEvent[] = [];
590
- const text: string[] = [];
591
- let onAbort: (() => void) | undefined;
592
- const aborted = new Promise<never>((_resolve, reject) => {
593
- onAbort = () => reject(new DOMException("Compaction aborted", "AbortError"));
594
- signal?.addEventListener("abort", onAbort, { once: true });
595
- });
596
- try {
597
- while (true) {
598
- if (signal?.aborted) throw new DOMException("Compaction aborted", "AbortError");
599
- const chunk = await Promise.race([reader.read(), aborted]);
600
- if (signal?.aborted) throw new DOMException("Compaction aborted", "AbortError");
601
- const decoded = chunk.done ? decoder.decode() : decoder.decode(chunk.value, { stream: true });
602
- text.push(decoded);
603
- try {
604
- for (const event of parser.push(decoded)) {
605
- events.push(event);
606
- const type = getEventType(event);
607
- if (type === "response.completed" || type === "error" || type === "response.failed" || type === "response.incomplete") {
608
- // Validate synchronously before cleanup; later cancellation cannot undo this boundary.
609
- return { responseText: "", events, reconciliation: reconcileSseCheckpoint(events) };
610
- }
611
- }
612
- } catch (error) {
613
- if (!(error instanceof SyntaxError)) throw error;
614
- return { responseText: text.join("") };
615
- }
616
- // EOF never dispatches an unfinished frame, even if its JSON happens to be complete.
617
- if (chunk.done) return { responseText: text.join(""), events: events.length ? events : undefined };
618
- }
619
- } finally {
620
- if (onAbort) signal?.removeEventListener("abort", onAbort);
621
- // Cancellation is best-effort cleanup, not part of the completion boundary.
622
- // Observe rejection immediately and release even if the transport never settles cancel().
623
- try {
624
- void reader.cancel().catch(() => undefined);
625
- } catch {
626
- // A custom reader may throw synchronously during cleanup.
627
- } finally {
628
- try {
629
- reader.releaseLock();
630
- } catch {
631
- // Cleanup failures must not replace a validated checkpoint or cancellation.
632
- }
633
- }
634
- }
635
- }
636
-
637
- export async function executeRemoteV2Compaction(
638
- options: ExecuteRemoteV2CompactionOptions,
639
- ): Promise<RemoteV2CompactionClientResult> {
640
- const { runtime, signal, settings, context } = options;
641
- const request = buildRemoteV2CompactionRequest(options.request);
642
- const headers = toHeaders(runtime);
643
-
644
- if (signal?.aborted) {
645
- const aborted: RemoteV2CompactionClientFailure = { ok: false, reason: "aborted" };
646
- writeCompactArtifact(
647
- {
648
- protocol: "remote_compaction_v2",
649
- request: { url: runtime.responsesUrl, headers, body: request },
650
- outcome: aborted,
651
- },
652
- settings,
653
- context,
654
- );
655
- return aborted;
656
- }
657
-
658
- try {
659
- const response = await fetch(runtime.responsesUrl, {
660
- method: "POST",
661
- headers,
662
- body: JSON.stringify(request),
663
- signal,
664
- });
665
- const responseHeaders: Record<string, string> = {};
666
- response.headers.forEach((value, key) => {
667
- responseHeaders[key] = value;
668
- });
669
-
670
- if (!response.ok) {
671
- const responseText = await response.text();
672
- let responseJson: unknown;
673
- if (responseText.trim()) {
674
- try {
675
- responseJson = JSON.parse(responseText);
676
- } catch {
677
- responseJson = undefined;
678
- }
679
- }
680
- const failure: RemoteV2CompactionClientFailure = {
681
- ok: false,
682
- reason: "non-2xx",
683
- status: response.status,
684
- responseText: responseText || undefined,
685
- responseJson,
686
- };
687
- writeCompactArtifact(
688
- {
689
- protocol: "remote_compaction_v2",
690
- request: { url: runtime.responsesUrl, headers, body: request },
691
- response: { status: response.status, headers: responseHeaders, body: responseJson ?? responseText },
692
- outcome: failure,
693
- },
694
- settings,
695
- context,
696
- );
697
- return failure;
698
- }
699
-
700
- const collected = await collectSseCheckpoint(response, signal);
701
- const { responseText, events } = collected;
702
- if (!events && !responseText.trim()) {
703
- const failure: RemoteV2CompactionClientFailure = {
704
- ok: false,
705
- reason: "empty-body",
706
- status: response.status,
707
- };
708
- writeCompactArtifact(
709
- {
710
- protocol: "remote_compaction_v2",
711
- request: { url: runtime.responsesUrl, headers, body: request },
712
- response: { status: response.status, headers: responseHeaders },
713
- outcome: failure,
714
- },
715
- settings,
716
- context,
717
- );
718
- return failure;
719
- }
720
-
721
- if (!events) {
722
- const failure: RemoteV2CompactionClientFailure = {
723
- ok: false,
724
- reason: "invalid-sse",
725
- status: response.status,
726
- responseText,
727
- };
728
- writeCompactArtifact(
729
- {
730
- protocol: "remote_compaction_v2",
731
- request: { url: runtime.responsesUrl, headers, body: request },
732
- response: { status: response.status, headers: responseHeaders, body: responseText },
733
- outcome: failure,
734
- },
735
- settings,
736
- context,
737
- );
738
- return failure;
739
- }
740
-
741
- const reconciliation = collected.reconciliation ?? reconcileSseCheckpoint(events);
742
- if (!reconciliation.ok) {
743
- const failure: RemoteV2CompactionClientFailure = {
744
- ok: false,
745
- reason: reconciliation.reason,
746
- status: response.status,
747
- errorMessage: reconciliation.errorMessage,
748
- responseJson: reconciliation.responseJson,
749
- };
750
- writeCompactArtifact(
751
- {
752
- protocol: "remote_compaction_v2",
753
- request: { url: runtime.responsesUrl, headers, body: request },
754
- response: { status: response.status, headers: responseHeaders, events: summarizeEvents(events) },
755
- outcome: failure,
756
- },
757
- settings,
758
- context,
759
- );
760
- return failure;
761
- }
762
-
763
- const responseEnvelope = reconciliation.response;
764
- const success: RemoteV2CompactionClientSuccess = {
765
- ok: true,
766
- status: response.status,
767
- compactedWindow: reconciliation.compactedWindow,
768
- compactResponseId: isNonEmptyString(responseEnvelope.id) ? responseEnvelope.id.trim() : undefined,
769
- createdAt: normalizeResponseTimestamp(responseEnvelope.created_at),
770
- usage: isRecord(responseEnvelope.usage) ? (structuredClone(responseEnvelope.usage) as RemoteV2ResponseUsage) : undefined,
771
- response: responseEnvelope,
772
- };
773
- writeCompactArtifact(
774
- {
775
- protocol: "remote_compaction_v2",
776
- request: { url: runtime.responsesUrl, headers, body: request },
777
- response: {
778
- status: response.status,
779
- headers: responseHeaders,
780
- events: summarizeEvents(events),
781
- completed: responseEnvelope,
782
- },
783
- outcome: {
784
- ok: true,
785
- status: success.status,
786
- compactResponseId: success.compactResponseId,
787
- createdAt: success.createdAt,
788
- compactedItems: 1,
789
- },
790
- },
791
- settings,
792
- context,
793
- );
794
- return success;
795
- } catch (error) {
796
- const failure: RemoteV2CompactionClientFailure = isAbortError(error)
797
- ? { ok: false, reason: "aborted" }
798
- : {
799
- ok: false,
800
- reason: "network-error",
801
- errorMessage: error instanceof Error ? error.message : String(error),
802
- };
803
- writeCompactArtifact(
804
- {
805
- protocol: "remote_compaction_v2",
806
- request: { url: runtime.responsesUrl, headers, body: request },
807
- outcome: failure,
808
- },
809
- settings,
810
- context,
811
- );
812
- return failure;
813
- }
814
- }
1
+ import { isDeepStrictEqual } from "node:util";
2
+ import { writeDebugArtifact } from "./debug";
3
+ import { decodeCompactionError, readCompactionResponseText, type CompactionErrorInfo } from "./compaction-errors";
4
+ import { buildResponsesRequestHeaders } from "./responses-headers";
5
+ import type { NativeCompactionRuntime } from "./runtime";
6
+ import type { NativeCompactionRequestBody, ResponsesInputItem } from "./serializer";
7
+ import type { ArtifactContext, CompactionConfig } from "./types";
8
+
9
+ const JSON_CONTENT_TYPE = "application/json";
10
+ const SSE_CONTENT_TYPE = "text/event-stream";
11
+
12
+ export type RemoteV2CompactionItem = Record<string, unknown> & {
13
+ type: "compaction";
14
+ encrypted_content: string;
15
+ };
16
+
17
+ export type RemoteV2ResponseUsage = {
18
+ input_tokens?: number;
19
+ output_tokens?: number;
20
+ total_tokens?: number;
21
+ [key: string]: unknown;
22
+ };
23
+
24
+ export type RemoteV2ResponseEnvelope = Record<string, unknown> & {
25
+ id?: string;
26
+ created_at?: number | string;
27
+ status: "completed";
28
+ /** Normalized output always includes the reconciled checkpoint. */
29
+ output: unknown[];
30
+ usage?: RemoteV2ResponseUsage;
31
+ };
32
+
33
+ export type RemoteV2CompactionClientFailureReason =
34
+ | "aborted"
35
+ | "network-error"
36
+ | "non-2xx"
37
+ | "empty-body"
38
+ | "invalid-sse"
39
+ | "error-event"
40
+ | "missing-completed-event"
41
+ | "incomplete-response"
42
+ | "invalid-compaction-count"
43
+ | "malformed-compaction-item"
44
+ | "conflicting-compaction-item"
45
+ | "invalid-compaction-metadata"
46
+ | "invalid-event-order"
47
+ | "duplicate-completed-event";
48
+
49
+ export type RemoteV2CompactionClientSuccess = {
50
+ ok: true;
51
+ status: number;
52
+ compactedWindow: [RemoteV2CompactionItem];
53
+ compactResponseId?: string;
54
+ createdAt?: string;
55
+ usage?: RemoteV2ResponseUsage;
56
+ response: RemoteV2ResponseEnvelope;
57
+ };
58
+
59
+ export type RemoteV2CompactionClientFailure = CompactionErrorInfo & {
60
+ ok: false;
61
+ reason: RemoteV2CompactionClientFailureReason;
62
+ status?: number;
63
+ errorMessage?: string;
64
+ responseText?: string;
65
+ responseJson?: unknown;
66
+ };
67
+
68
+ export type RemoteV2CompactionClientResult = RemoteV2CompactionClientSuccess | RemoteV2CompactionClientFailure;
69
+
70
+ export type RemoteV2CompactionRequestBody = Omit<NativeCompactionRequestBody, "input"> & {
71
+ input: [...ResponsesInputItem[], { type: "compaction_trigger" }];
72
+ store: false;
73
+ stream: true;
74
+ };
75
+
76
+ export type ExecuteRemoteV2CompactionOptions = {
77
+ runtime: NativeCompactionRuntime;
78
+ request: NativeCompactionRequestBody;
79
+ signal?: AbortSignal;
80
+ settings?: CompactionConfig;
81
+ context?: ArtifactContext;
82
+ };
83
+
84
+ type ParsedSseEvent = {
85
+ event?: string;
86
+ dataText: string;
87
+ data?: unknown;
88
+ };
89
+
90
+ function isRecord(value: unknown): value is Record<string, unknown> {
91
+ return !!value && typeof value === "object" && !Array.isArray(value);
92
+ }
93
+
94
+ function isAbortError(error: unknown): boolean {
95
+ return (
96
+ (error instanceof DOMException && error.name === "AbortError") ||
97
+ (error instanceof Error && (error.name === "AbortError" || error.name === "ABORT_ERR"))
98
+ );
99
+ }
100
+
101
+ function isNonEmptyString(value: unknown): value is string {
102
+ return typeof value === "string" && value.trim().length > 0;
103
+ }
104
+
105
+ function normalizeResponseTimestamp(value: unknown): string | undefined {
106
+ if (typeof value === "number" && Number.isFinite(value)) {
107
+ const milliseconds = value > 1_000_000_000_000 ? value : value * 1000;
108
+ return new Date(milliseconds).toISOString();
109
+ }
110
+
111
+ if (typeof value !== "string") {
112
+ return undefined;
113
+ }
114
+
115
+ const trimmed = value.trim();
116
+ if (!trimmed) {
117
+ return undefined;
118
+ }
119
+
120
+ const parsed = Date.parse(trimmed);
121
+ return Number.isNaN(parsed) ? trimmed : new Date(parsed).toISOString();
122
+ }
123
+
124
+ function toHeaders(runtime: NativeCompactionRuntime): Record<string, string> {
125
+ return Object.fromEntries(
126
+ buildResponsesRequestHeaders(runtime, {
127
+ accept: SSE_CONTENT_TYPE,
128
+ contentType: JSON_CONTENT_TYPE,
129
+ sessionId: runtime.sessionId,
130
+ }).entries(),
131
+ );
132
+ }
133
+
134
+ /**
135
+ * Codex `/responses` rejects histories that mix this Astra-only item into a
136
+ * compaction request (verified upstream in Oh My Pi: the compaction path is
137
+ * deliberately built outside the effort planner). The host never emits the
138
+ * item into a payload we would replay today, but the item is transparently
139
+ * carried by our opaque-window replay, so strip it defensively on every
140
+ * remote v2 request regardless of API family.
141
+ */
142
+ export function stripConfigurationUpdateItems(input: readonly unknown[]): unknown[] {
143
+ return input.filter((item) => !isRecord(item) || item["type"] !== "configuration_update");
144
+ }
145
+
146
+ export function buildRemoteV2CompactionRequest(
147
+ request: NativeCompactionRequestBody,
148
+ ): RemoteV2CompactionRequestBody {
149
+ return {
150
+ ...structuredClone(request),
151
+ input: [
152
+ ...(stripConfigurationUpdateItems(structuredClone(request.input)) as ResponsesInputItem[]),
153
+ { type: "compaction_trigger" },
154
+ ],
155
+ store: false,
156
+ stream: true,
157
+ };
158
+ }
159
+
160
+ /** Shared line framing; yielding lazily keeps bytes after a terminal out of parsing. */
161
+ class SseEventParser {
162
+ private line = "";
163
+ private skipLf = false;
164
+ private event: string | undefined;
165
+ private dataLines: string[] = [];
166
+
167
+ *push(text: string): Generator<ParsedSseEvent> {
168
+ for (const character of text) {
169
+ if (this.skipLf) {
170
+ this.skipLf = false;
171
+ if (character === "\n") continue;
172
+ }
173
+ if (character !== "\r" && character !== "\n") {
174
+ this.line += character;
175
+ continue;
176
+ }
177
+ this.skipLf = character === "\r";
178
+ const line = this.line;
179
+ this.line = "";
180
+ if (line === "") {
181
+ const event = this.event;
182
+ const dataLines = this.dataLines;
183
+ this.event = undefined;
184
+ this.dataLines = [];
185
+ if (dataLines.length === 0) continue;
186
+ const dataText = dataLines.join("\n");
187
+ yield dataText === "[DONE]"
188
+ ? { event, dataText }
189
+ : { event, dataText, data: JSON.parse(dataText) };
190
+ } else if (line.startsWith("event:")) {
191
+ this.event = line.slice("event:".length).trim();
192
+ } else if (line === "data" || line.startsWith("data:")) {
193
+ this.dataLines.push(line.slice("data:".length).replace(/^ /, ""));
194
+ }
195
+ }
196
+ }
197
+ }
198
+
199
+ export function parseSseEvents(raw: string): ParsedSseEvent[] | undefined {
200
+ try {
201
+ // Retain the buffered helper's historical support for a final unterminated block.
202
+ const events = [...new SseEventParser().push(`${raw}\n\n`)];
203
+ return events.length > 0 ? events : undefined;
204
+ } catch {
205
+ return undefined;
206
+ }
207
+ }
208
+
209
+ function isRemoteV2CompactionItem(value: unknown): value is RemoteV2CompactionItem {
210
+ return isRecord(value) && value.type === "compaction" && isNonEmptyString(value.encrypted_content);
211
+ }
212
+
213
+ function getEventType(event: ParsedSseEvent): string | undefined {
214
+ return isRecord(event.data) && isNonEmptyString(event.data.type) ? event.data.type : event.event;
215
+ }
216
+
217
+ function writeCompactArtifact(
218
+ data: unknown,
219
+ settings: CompactionConfig | undefined,
220
+ context: ArtifactContext | undefined,
221
+ ): void {
222
+ if (!settings || !context) {
223
+ return;
224
+ }
225
+
226
+ writeDebugArtifact("compact-response", data, settings, context);
227
+ }
228
+
229
+ function summarizeEvents(events: readonly ParsedSseEvent[]): unknown[] {
230
+ return events.map((event) => {
231
+ const data = isRecord(event.data) ? event.data : undefined;
232
+ const response = data && isRecord(data.response) ? data.response : undefined;
233
+ const item = data && isRecord(data.item) ? data.item : undefined;
234
+ return {
235
+ event: event.event,
236
+ type: data?.type,
237
+ responseStatus: response?.status,
238
+ responseId: response?.id,
239
+ itemType: item?.type,
240
+ };
241
+ });
242
+ }
243
+
244
+ type OptionalMetadata<T> =
245
+ | { valid: true; value?: T }
246
+ | { valid: false };
247
+
248
+ type CompactionCheckpointCandidate = {
249
+ item: RemoteV2CompactionItem;
250
+ responseId?: string;
251
+ outputIndex?: number;
252
+ outputPosition?: number;
253
+ };
254
+
255
+ type CheckpointReconciliation =
256
+ | {
257
+ ok: true;
258
+ response: RemoteV2ResponseEnvelope;
259
+ compactedWindow: [RemoteV2CompactionItem];
260
+ }
261
+ | {
262
+ ok: false;
263
+ reason:
264
+ | "error-event"
265
+ | "missing-completed-event"
266
+ | "incomplete-response"
267
+ | "invalid-compaction-count"
268
+ | "malformed-compaction-item"
269
+ | "conflicting-compaction-item"
270
+ | "invalid-compaction-metadata"
271
+ | "invalid-event-order"
272
+ | "duplicate-completed-event";
273
+ responseJson?: unknown;
274
+ errorMessage?: string;
275
+ };
276
+
277
+ function hasOwn(value: Record<string, unknown>, key: string): boolean {
278
+ return Object.prototype.hasOwnProperty.call(value, key);
279
+ }
280
+
281
+ function readOptionalNonEmptyString(value: Record<string, unknown>, key: string): OptionalMetadata<string> {
282
+ if (!hasOwn(value, key)) {
283
+ return { valid: true };
284
+ }
285
+ return isNonEmptyString(value[key]) ? { valid: true, value: value[key] } : { valid: false };
286
+ }
287
+
288
+ function readOptionalOutputIndex(value: Record<string, unknown>): OptionalMetadata<number> {
289
+ if (!hasOwn(value, "output_index")) {
290
+ return { valid: true };
291
+ }
292
+
293
+ const outputIndex = value.output_index;
294
+ return typeof outputIndex === "number" && Number.isSafeInteger(outputIndex) && outputIndex >= 0
295
+ ? { valid: true, value: outputIndex }
296
+ : { valid: false };
297
+ }
298
+
299
+ function mergeCompactionItems(
300
+ itemDone: RemoteV2CompactionItem,
301
+ terminalOutput: RemoteV2CompactionItem,
302
+ ): RemoteV2CompactionItem | undefined {
303
+ for (const [key, value] of Object.entries(itemDone)) {
304
+ if (hasOwn(terminalOutput, key) && !isDeepStrictEqual(value, terminalOutput[key])) return undefined;
305
+ }
306
+ return structuredClone({ ...itemDone, ...terminalOutput });
307
+ }
308
+
309
+ function readResponseId(value: Record<string, unknown>): OptionalMetadata<string> {
310
+ const direct = readOptionalNonEmptyString(value, "response_id");
311
+ if (!direct.valid) {
312
+ return direct;
313
+ }
314
+
315
+ const nestedResponse: OptionalMetadata<string> = isRecord(value.response) ? readOptionalNonEmptyString(value.response, "id") : { valid: true };
316
+ if (!nestedResponse.valid) {
317
+ return nestedResponse;
318
+ }
319
+ if (direct.value !== undefined && nestedResponse.value !== undefined && direct.value !== nestedResponse.value) {
320
+ return { valid: false };
321
+ }
322
+ return { valid: true, value: direct.value ?? nestedResponse.value };
323
+ }
324
+
325
+ function readCheckpointCandidate(
326
+ item: Record<string, unknown>,
327
+ metadataSource: Record<string, unknown> | undefined,
328
+ outputPosition?: number,
329
+ ): { ok: true; candidate: CompactionCheckpointCandidate } | { ok: false; reason: "malformed-compaction-item" | "invalid-compaction-metadata" } {
330
+ if (!isRemoteV2CompactionItem(item)) {
331
+ return { ok: false, reason: "malformed-compaction-item" };
332
+ }
333
+
334
+ const itemId = readOptionalNonEmptyString(item, "id");
335
+ const itemResponseId = readResponseId(item);
336
+ const itemOutputIndex = readOptionalOutputIndex(item);
337
+ if (!itemId.valid || !itemResponseId.valid || !itemOutputIndex.valid) {
338
+ return { ok: false, reason: "invalid-compaction-metadata" };
339
+ }
340
+
341
+ const sourceResponseId: OptionalMetadata<string> = metadataSource ? readResponseId(metadataSource) : { valid: true };
342
+ const sourceOutputIndex: OptionalMetadata<number> = metadataSource ? readOptionalOutputIndex(metadataSource) : { valid: true };
343
+ if (!sourceResponseId.valid || !sourceOutputIndex.valid) {
344
+ return { ok: false, reason: "invalid-compaction-metadata" };
345
+ }
346
+ if (
347
+ itemResponseId.value !== undefined &&
348
+ sourceResponseId.value !== undefined &&
349
+ itemResponseId.value !== sourceResponseId.value
350
+ ) {
351
+ return { ok: false, reason: "invalid-compaction-metadata" };
352
+ }
353
+ if (
354
+ itemOutputIndex.value !== undefined &&
355
+ sourceOutputIndex.value !== undefined &&
356
+ itemOutputIndex.value !== sourceOutputIndex.value
357
+ ) {
358
+ return { ok: false, reason: "invalid-compaction-metadata" };
359
+ }
360
+
361
+ const outputIndex = sourceOutputIndex.value ?? itemOutputIndex.value;
362
+ if (outputPosition !== undefined && outputIndex !== undefined && outputIndex !== outputPosition) {
363
+ return { ok: false, reason: "invalid-compaction-metadata" };
364
+ }
365
+ return {
366
+ ok: true,
367
+ candidate: {
368
+ item: structuredClone(item),
369
+ responseId: sourceResponseId.value ?? itemResponseId.value,
370
+ outputIndex,
371
+ outputPosition,
372
+ },
373
+ };
374
+ }
375
+
376
+ function isPostTerminalEvent(event: ParsedSseEvent): boolean {
377
+ if (event.dataText === "[DONE]") {
378
+ return true;
379
+ }
380
+ return getEventType(event) === "keepalive";
381
+ }
382
+
383
+ function reconcileSseCheckpoint(events: readonly ParsedSseEvent[]): CheckpointReconciliation {
384
+ const errorEvent = events.find((event) => {
385
+ const type = getEventType(event);
386
+ return type === "error" || type === "response.failed" || type === "response.incomplete";
387
+ });
388
+ if (errorEvent) {
389
+ return {
390
+ ok: false,
391
+ reason: "error-event",
392
+ responseJson: errorEvent.data,
393
+ errorMessage: decodeCompactionError(errorEvent.data).errorMessage,
394
+ };
395
+ }
396
+
397
+ const completedIndexes = events.flatMap((event, index) => (getEventType(event) === "response.completed" ? [index] : []));
398
+ if (completedIndexes.length === 0) {
399
+ return { ok: false, reason: "missing-completed-event" };
400
+ }
401
+ if (completedIndexes.length > 1) {
402
+ return { ok: false, reason: "duplicate-completed-event" };
403
+ }
404
+ const terminalIndex = completedIndexes[0]!;
405
+
406
+ for (let index = 0; index < events.length; index += 1) {
407
+ const event = events[index]!;
408
+ if (index < terminalIndex && event.dataText === "[DONE]") {
409
+ return { ok: false, reason: "invalid-event-order", responseJson: event.dataText };
410
+ }
411
+ if (index > terminalIndex && !isPostTerminalEvent(event)) {
412
+ return { ok: false, reason: "invalid-event-order", responseJson: event.data };
413
+ }
414
+ }
415
+
416
+ const completedEvent = events[terminalIndex]!;
417
+ const completedData = isRecord(completedEvent.data) ? completedEvent.data : undefined;
418
+ const completedResponse = completedData && isRecord(completedData.response) ? completedData.response : undefined;
419
+ if (!completedResponse) {
420
+ return { ok: false, reason: "incomplete-response" };
421
+ }
422
+
423
+ if (completedResponse.status !== "completed") {
424
+ return { ok: false, reason: "incomplete-response", responseJson: completedResponse };
425
+ }
426
+ const hasOutput = hasOwn(completedResponse, "output");
427
+ if (hasOutput && !Array.isArray(completedResponse.output)) {
428
+ return { ok: false, reason: "incomplete-response", responseJson: completedResponse };
429
+ }
430
+ const terminalOutput: unknown[] | undefined = Array.isArray(completedResponse.output) ? completedResponse.output : undefined;
431
+
432
+ const terminalResponseId = readOptionalNonEmptyString(completedResponse, "id");
433
+ const completedEventResponseId: OptionalMetadata<string> = completedData ? readResponseId(completedData) : { valid: true };
434
+ if (!terminalResponseId.valid || !completedEventResponseId.valid) {
435
+ return { ok: false, reason: "invalid-compaction-metadata", responseJson: completedResponse };
436
+ }
437
+ if (
438
+ terminalResponseId.value !== undefined &&
439
+ completedEventResponseId.value !== undefined &&
440
+ terminalResponseId.value !== completedEventResponseId.value
441
+ ) {
442
+ return { ok: false, reason: "invalid-compaction-metadata", responseJson: completedResponse };
443
+ }
444
+ let responseId = terminalResponseId.value ?? completedEventResponseId.value;
445
+ for (const event of events.slice(0, terminalIndex)) {
446
+ if (getEventType(event) !== "response.created" || !isRecord(event.data)) continue;
447
+ const createdId = readResponseId(event.data);
448
+ if (!createdId.valid || (createdId.value !== undefined && responseId !== undefined && createdId.value !== responseId)) {
449
+ return { ok: false, reason: "invalid-compaction-metadata", responseJson: event.data };
450
+ }
451
+ responseId ??= createdId.value;
452
+ }
453
+
454
+ let itemDoneCandidate: CompactionCheckpointCandidate | undefined;
455
+ for (let index = 0; index < terminalIndex; index += 1) {
456
+ const event = events[index]!;
457
+ if (getEventType(event) !== "response.output_item.done") {
458
+ continue;
459
+ }
460
+ if (!isRecord(event.data) || !isRecord(event.data.item)) {
461
+ return { ok: false, reason: "malformed-compaction-item", responseJson: event.data };
462
+ }
463
+ if (event.data.item.type !== "compaction") {
464
+ continue;
465
+ }
466
+ if (itemDoneCandidate) {
467
+ return { ok: false, reason: "invalid-compaction-count", responseJson: event.data.item };
468
+ }
469
+ const candidate = readCheckpointCandidate(event.data.item, event.data);
470
+ if (!candidate.ok) {
471
+ return { ok: false, reason: candidate.reason, responseJson: event.data.item };
472
+ }
473
+ itemDoneCandidate = candidate.candidate;
474
+ }
475
+
476
+ let terminalCandidate: CompactionCheckpointCandidate | undefined;
477
+ let terminalPosition: number | undefined;
478
+ if (terminalOutput) {
479
+ const compactionPositions = terminalOutput.flatMap((item, index) =>
480
+ isRecord(item) && item.type === "compaction" ? [index] : [],
481
+ );
482
+ if (compactionPositions.length > 1) {
483
+ return { ok: false, reason: "invalid-compaction-count", responseJson: completedResponse };
484
+ }
485
+ if (compactionPositions.length === 1) {
486
+ terminalPosition = compactionPositions[0]!;
487
+ const outputItem = terminalOutput[terminalPosition];
488
+ if (!isRecord(outputItem)) {
489
+ return { ok: false, reason: "malformed-compaction-item", responseJson: outputItem };
490
+ }
491
+ const candidate = readCheckpointCandidate(outputItem, undefined, terminalPosition);
492
+ if (!candidate.ok) {
493
+ return { ok: false, reason: candidate.reason, responseJson: outputItem };
494
+ }
495
+ terminalCandidate = candidate.candidate;
496
+ }
497
+ }
498
+
499
+ if (!itemDoneCandidate && !terminalCandidate) {
500
+ return { ok: false, reason: "invalid-compaction-count", responseJson: completedResponse };
501
+ }
502
+
503
+ for (const candidate of [itemDoneCandidate, terminalCandidate]) {
504
+ if (
505
+ candidate?.responseId !== undefined &&
506
+ responseId !== undefined &&
507
+ candidate.responseId !== responseId
508
+ ) {
509
+ return { ok: false, reason: "invalid-compaction-metadata", responseJson: candidate.item };
510
+ }
511
+ }
512
+ if (
513
+ itemDoneCandidate?.responseId !== undefined &&
514
+ terminalCandidate?.responseId !== undefined &&
515
+ itemDoneCandidate.responseId !== terminalCandidate.responseId
516
+ ) {
517
+ return { ok: false, reason: "invalid-compaction-metadata", responseJson: terminalCandidate.item };
518
+ }
519
+ if (
520
+ itemDoneCandidate?.outputIndex !== undefined &&
521
+ terminalCandidate?.outputPosition !== undefined &&
522
+ itemDoneCandidate.outputIndex !== terminalCandidate.outputPosition
523
+ ) {
524
+ return { ok: false, reason: "invalid-compaction-metadata", responseJson: terminalCandidate.item };
525
+ }
526
+ if (
527
+ itemDoneCandidate?.outputIndex !== undefined &&
528
+ terminalOutput !== undefined &&
529
+ terminalOutput.length > 0 &&
530
+ terminalCandidate === undefined &&
531
+ itemDoneCandidate.outputIndex > terminalOutput.length
532
+ ) {
533
+ return { ok: false, reason: "invalid-compaction-metadata", responseJson: completedResponse };
534
+ }
535
+
536
+ let canonicalItem: RemoteV2CompactionItem;
537
+ if (itemDoneCandidate && terminalCandidate) {
538
+ const merged = mergeCompactionItems(itemDoneCandidate.item, terminalCandidate.item);
539
+ if (!merged) {
540
+ return { ok: false, reason: "conflicting-compaction-item", responseJson: terminalCandidate.item };
541
+ }
542
+ canonicalItem = merged;
543
+ } else {
544
+ canonicalItem = structuredClone((itemDoneCandidate ?? terminalCandidate)!.item);
545
+ }
546
+
547
+ const responseEnvelope = structuredClone(completedResponse) as RemoteV2ResponseEnvelope;
548
+ if (terminalOutput) {
549
+ const output = structuredClone(terminalOutput);
550
+ if (terminalPosition !== undefined) {
551
+ output[terminalPosition] = structuredClone(canonicalItem);
552
+ } else {
553
+ const insertionIndex = itemDoneCandidate?.outputIndex ?? output.length;
554
+ output.splice(insertionIndex, 0, structuredClone(canonicalItem));
555
+ }
556
+ responseEnvelope.output = output;
557
+ } else {
558
+ responseEnvelope.output = [structuredClone(canonicalItem)];
559
+ }
560
+
561
+ return {
562
+ ok: true,
563
+ response: responseEnvelope,
564
+ compactedWindow: [structuredClone(canonicalItem)],
565
+ };
566
+ }
567
+
568
+ type CollectedSse = {
569
+ responseText: string;
570
+ events?: ParsedSseEvent[];
571
+ reconciliation?: CheckpointReconciliation;
572
+ };
573
+
574
+ async function collectSseCheckpoint(response: Response, signal?: AbortSignal): Promise<CollectedSse> {
575
+ const reader = response.body?.getReader();
576
+ if (!reader) {
577
+ if (signal?.aborted) throw new DOMException("Compaction aborted", "AbortError");
578
+ return { responseText: "" };
579
+ }
580
+ const decoder = new TextDecoder();
581
+ const parser = new SseEventParser();
582
+ const events: ParsedSseEvent[] = [];
583
+ const text: string[] = [];
584
+ let onAbort: (() => void) | undefined;
585
+ const aborted = new Promise<never>((_resolve, reject) => {
586
+ onAbort = () => reject(new DOMException("Compaction aborted", "AbortError"));
587
+ signal?.addEventListener("abort", onAbort, { once: true });
588
+ });
589
+ try {
590
+ while (true) {
591
+ if (signal?.aborted) throw new DOMException("Compaction aborted", "AbortError");
592
+ const chunk = await Promise.race([reader.read(), aborted]);
593
+ if (signal?.aborted) throw new DOMException("Compaction aborted", "AbortError");
594
+ const decoded = chunk.done ? decoder.decode() : decoder.decode(chunk.value, { stream: true });
595
+ text.push(decoded);
596
+ try {
597
+ for (const event of parser.push(decoded)) {
598
+ events.push(event);
599
+ const type = getEventType(event);
600
+ if (type === "response.completed" || type === "error" || type === "response.failed" || type === "response.incomplete") {
601
+ // Validate synchronously before cleanup; later cancellation cannot undo this boundary.
602
+ return { responseText: "", events, reconciliation: reconcileSseCheckpoint(events) };
603
+ }
604
+ }
605
+ } catch (error) {
606
+ if (!(error instanceof SyntaxError)) throw error;
607
+ return { responseText: text.join("") };
608
+ }
609
+ // EOF never dispatches an unfinished frame, even if its JSON happens to be complete.
610
+ if (chunk.done) return { responseText: text.join(""), events: events.length ? events : undefined };
611
+ }
612
+ } finally {
613
+ if (onAbort) signal?.removeEventListener("abort", onAbort);
614
+ // Cancellation is best-effort cleanup, not part of the completion boundary.
615
+ // Observe rejection immediately and release even if the transport never settles cancel().
616
+ try {
617
+ void reader.cancel().catch(() => undefined);
618
+ } catch {
619
+ // A custom reader may throw synchronously during cleanup.
620
+ } finally {
621
+ try {
622
+ reader.releaseLock();
623
+ } catch {
624
+ // Cleanup failures must not replace a validated checkpoint or cancellation.
625
+ }
626
+ }
627
+ }
628
+ }
629
+
630
+ export async function executeRemoteV2Compaction(
631
+ options: ExecuteRemoteV2CompactionOptions,
632
+ ): Promise<RemoteV2CompactionClientResult> {
633
+ const { runtime, signal, settings, context } = options;
634
+ const request = buildRemoteV2CompactionRequest(options.request);
635
+ const headers = toHeaders(runtime);
636
+
637
+ if (signal?.aborted) {
638
+ const aborted: RemoteV2CompactionClientFailure = { ok: false, reason: "aborted" };
639
+ writeCompactArtifact(
640
+ {
641
+ protocol: "remote_compaction_v2",
642
+ request: { url: runtime.responsesUrl, headers, body: request },
643
+ outcome: aborted,
644
+ },
645
+ settings,
646
+ context,
647
+ );
648
+ return aborted;
649
+ }
650
+
651
+ try {
652
+ const response = await fetch(runtime.responsesUrl, {
653
+ method: "POST",
654
+ headers,
655
+ body: JSON.stringify(request),
656
+ signal,
657
+ });
658
+ const responseHeaders: Record<string, string> = {};
659
+ response.headers.forEach((value, key) => {
660
+ responseHeaders[key] = value;
661
+ });
662
+
663
+ if (!response.ok) {
664
+ const responseText = await readCompactionResponseText(response, signal);
665
+ let responseJson: unknown;
666
+ if (responseText.trim()) {
667
+ try {
668
+ responseJson = JSON.parse(responseText);
669
+ } catch {
670
+ responseJson = undefined;
671
+ }
672
+ }
673
+ const failure: RemoteV2CompactionClientFailure = {
674
+ ok: false,
675
+ reason: "non-2xx",
676
+ status: response.status,
677
+ ...decodeCompactionError(responseJson),
678
+ responseText: responseText || undefined,
679
+ responseJson,
680
+ };
681
+ writeCompactArtifact(
682
+ {
683
+ protocol: "remote_compaction_v2",
684
+ request: { url: runtime.responsesUrl, headers, body: request },
685
+ response: { status: response.status, headers: responseHeaders, body: responseJson ?? responseText },
686
+ outcome: failure,
687
+ },
688
+ settings,
689
+ context,
690
+ );
691
+ return failure;
692
+ }
693
+
694
+ const collected = await collectSseCheckpoint(response, signal);
695
+ const { responseText, events } = collected;
696
+ if (!events && !responseText.trim()) {
697
+ const failure: RemoteV2CompactionClientFailure = {
698
+ ok: false,
699
+ reason: "empty-body",
700
+ status: response.status,
701
+ };
702
+ writeCompactArtifact(
703
+ {
704
+ protocol: "remote_compaction_v2",
705
+ request: { url: runtime.responsesUrl, headers, body: request },
706
+ response: { status: response.status, headers: responseHeaders },
707
+ outcome: failure,
708
+ },
709
+ settings,
710
+ context,
711
+ );
712
+ return failure;
713
+ }
714
+
715
+ if (!events) {
716
+ let bodyError: CompactionErrorInfo = {};
717
+ try { bodyError = decodeCompactionError(JSON.parse(responseText)); } catch { /* Not a JSON error. */ }
718
+ const failure: RemoteV2CompactionClientFailure = {
719
+ ok: false,
720
+ reason: "invalid-sse",
721
+ status: response.status,
722
+ ...bodyError,
723
+ responseText,
724
+ };
725
+ writeCompactArtifact(
726
+ {
727
+ protocol: "remote_compaction_v2",
728
+ request: { url: runtime.responsesUrl, headers, body: request },
729
+ response: { status: response.status, headers: responseHeaders, body: responseText },
730
+ outcome: failure,
731
+ },
732
+ settings,
733
+ context,
734
+ );
735
+ return failure;
736
+ }
737
+
738
+ const reconciliation = collected.reconciliation ?? reconcileSseCheckpoint(events);
739
+ if (!reconciliation.ok) {
740
+ const failure: RemoteV2CompactionClientFailure = {
741
+ ok: false,
742
+ reason: reconciliation.reason,
743
+ status: response.status,
744
+ ...decodeCompactionError(reconciliation.responseJson),
745
+ responseJson: reconciliation.responseJson,
746
+ };
747
+ writeCompactArtifact(
748
+ {
749
+ protocol: "remote_compaction_v2",
750
+ request: { url: runtime.responsesUrl, headers, body: request },
751
+ response: { status: response.status, headers: responseHeaders, events: summarizeEvents(events) },
752
+ outcome: failure,
753
+ },
754
+ settings,
755
+ context,
756
+ );
757
+ return failure;
758
+ }
759
+
760
+ const responseEnvelope = reconciliation.response;
761
+ const success: RemoteV2CompactionClientSuccess = {
762
+ ok: true,
763
+ status: response.status,
764
+ compactedWindow: reconciliation.compactedWindow,
765
+ compactResponseId: isNonEmptyString(responseEnvelope.id) ? responseEnvelope.id.trim() : undefined,
766
+ createdAt: normalizeResponseTimestamp(responseEnvelope.created_at),
767
+ usage: isRecord(responseEnvelope.usage) ? (structuredClone(responseEnvelope.usage) as RemoteV2ResponseUsage) : undefined,
768
+ response: responseEnvelope,
769
+ };
770
+ writeCompactArtifact(
771
+ {
772
+ protocol: "remote_compaction_v2",
773
+ request: { url: runtime.responsesUrl, headers, body: request },
774
+ response: {
775
+ status: response.status,
776
+ headers: responseHeaders,
777
+ events: summarizeEvents(events),
778
+ completed: responseEnvelope,
779
+ },
780
+ outcome: {
781
+ ok: true,
782
+ status: success.status,
783
+ compactResponseId: success.compactResponseId,
784
+ createdAt: success.createdAt,
785
+ compactedItems: 1,
786
+ },
787
+ },
788
+ settings,
789
+ context,
790
+ );
791
+ return success;
792
+ } catch (error) {
793
+ const failure: RemoteV2CompactionClientFailure = signal?.aborted || isAbortError(error)
794
+ ? { ok: false, reason: "aborted" }
795
+ : {
796
+ ok: false,
797
+ reason: "network-error",
798
+ errorMessage: "Compaction transport failed; no remote checkpoint was produced.",
799
+ };
800
+ writeCompactArtifact(
801
+ {
802
+ protocol: "remote_compaction_v2",
803
+ request: { url: runtime.responsesUrl, headers, body: request },
804
+ outcome: failure,
805
+ },
806
+ settings,
807
+ context,
808
+ );
809
+ return failure;
810
+ }
811
+ }