@narumitw/pi-codex-compact 0.52.0 → 0.53.1

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/src/protocol.ts CHANGED
@@ -5,378 +5,359 @@ export const MAX_COMPACTION_ITEM_BYTES = 2 * 1024 * 1024;
5
5
  export type JsonObject = Record<string, unknown>;
6
6
 
7
7
  export class CodexCompactionProtocolError extends Error {
8
- constructor(message: string) {
9
- super(message);
10
- this.name = "CodexCompactionProtocolError";
11
- }
8
+ constructor(message: string) {
9
+ super(message);
10
+ this.name = "CodexCompactionProtocolError";
11
+ }
12
12
  }
13
13
 
14
14
  function isObject(value: unknown): value is JsonObject {
15
- return typeof value === "object" && value !== null && !Array.isArray(value);
15
+ return typeof value === "object" && value !== null && !Array.isArray(value);
16
16
  }
17
17
 
18
18
  function byteLength(value: unknown): number {
19
- return Buffer.byteLength(JSON.stringify(value), "utf8");
19
+ return Buffer.byteLength(JSON.stringify(value), "utf8");
20
20
  }
21
21
 
22
22
  export function isCompactionItem(value: unknown): value is JsonObject {
23
- return (
24
- isObject(value) &&
25
- value.type === "compaction" &&
26
- typeof value.encrypted_content === "string" &&
27
- value.encrypted_content.length > 0
28
- );
23
+ return (
24
+ isObject(value) &&
25
+ value.type === "compaction" &&
26
+ typeof value.encrypted_content === "string" &&
27
+ value.encrypted_content.length > 0
28
+ );
29
29
  }
30
30
 
31
- export function validateCompactionItem(
32
- value: unknown,
33
- maxBytes = MAX_COMPACTION_ITEM_BYTES,
34
- ): JsonObject {
35
- if (!isCompactionItem(value)) {
36
- throw new CodexCompactionProtocolError(
37
- "Remote response did not contain a valid compaction item",
38
- );
39
- }
40
- if (byteLength(value) > maxBytes) {
41
- throw new CodexCompactionProtocolError("Remote compaction item exceeded the size limit");
42
- }
43
- return structuredClone(value);
31
+ export function validateCompactionItem(value: unknown, maxBytes = MAX_COMPACTION_ITEM_BYTES): JsonObject {
32
+ if (!isCompactionItem(value)) {
33
+ throw new CodexCompactionProtocolError("Remote response did not contain a valid compaction item");
34
+ }
35
+ if (byteLength(value) > maxBytes) {
36
+ throw new CodexCompactionProtocolError("Remote compaction item exceeded the size limit");
37
+ }
38
+ return structuredClone(value);
44
39
  }
45
40
 
46
41
  export interface CollectedCompaction {
47
- item: JsonObject;
48
- completedResponse?: JsonObject;
42
+ item: JsonObject;
43
+ completedResponse?: JsonObject;
49
44
  }
50
45
 
51
46
  export interface CollectedCompactResponse {
52
- item: JsonObject;
53
- output: JsonObject[];
54
- response: JsonObject;
47
+ item: JsonObject;
48
+ output: JsonObject[];
49
+ response: JsonObject;
55
50
  }
56
51
 
57
52
  function compactionItemsFromEvent(event: JsonObject): unknown[] {
58
- const items: unknown[] = [];
59
- if (event.type === "response.output_item.done" && isObject(event.item)) {
60
- items.push(event.item);
61
- }
62
- if (event.type === "response.completed" && isObject(event.response)) {
63
- const output = event.response.output;
64
- if (Array.isArray(output)) items.push(...output);
65
- }
66
- return items.filter((item) => isObject(item) && item.type === "compaction");
53
+ const items: unknown[] = [];
54
+ if (event.type === "response.output_item.done" && isObject(event.item)) {
55
+ items.push(event.item);
56
+ }
57
+ if (event.type === "response.completed" && isObject(event.response)) {
58
+ const output = event.response.output;
59
+ if (Array.isArray(output)) items.push(...output);
60
+ }
61
+ return items.filter((item) => isObject(item) && item.type === "compaction");
67
62
  }
68
63
 
69
64
  export async function collectCompactionSse(
70
- stream: ReadableStream<Uint8Array>,
71
- options: {
72
- signal?: AbortSignal;
73
- maxBytes?: number;
74
- maxItemBytes?: number;
75
- } = {},
65
+ stream: ReadableStream<Uint8Array>,
66
+ options: {
67
+ signal?: AbortSignal;
68
+ maxBytes?: number;
69
+ maxItemBytes?: number;
70
+ } = {},
76
71
  ): Promise<CollectedCompaction> {
77
- const maxBytes = options.maxBytes ?? MAX_SSE_BYTES;
78
- const reader = stream.getReader();
79
- const onAbort = () => {
80
- void reader.cancel(new DOMException("Compaction aborted", "AbortError")).catch(() => undefined);
81
- };
82
- options.signal?.addEventListener("abort", onAbort, { once: true });
83
- const decoder = new TextDecoder();
84
- let bytes = 0;
85
- let pending = "";
86
- let dataLines: string[] = [];
87
- let completedResponse: JsonObject | undefined;
88
- const items = new Map<string, JsonObject>();
72
+ const maxBytes = options.maxBytes ?? MAX_SSE_BYTES;
73
+ const reader = stream.getReader();
74
+ const onAbort = () => {
75
+ void reader.cancel(new DOMException("Compaction aborted", "AbortError")).catch(() => undefined);
76
+ };
77
+ options.signal?.addEventListener("abort", onAbort, { once: true });
78
+ const decoder = new TextDecoder();
79
+ let bytes = 0;
80
+ let pending = "";
81
+ let dataLines: string[] = [];
82
+ let completedResponse: JsonObject | undefined;
83
+ const items = new Map<string, JsonObject>();
89
84
 
90
- const checkAbort = () => {
91
- if (options.signal?.aborted) throw new DOMException("Compaction aborted", "AbortError");
92
- };
93
- const dispatch = () => {
94
- if (dataLines.length === 0) return;
95
- const data = dataLines.join("\n");
96
- dataLines = [];
97
- if (data === "[DONE]") return;
98
- let parsed: unknown;
99
- try {
100
- parsed = JSON.parse(data);
101
- } catch {
102
- throw new CodexCompactionProtocolError("Remote compaction returned malformed SSE JSON");
103
- }
104
- if (!isObject(parsed)) return;
105
- if (parsed.type === "response.completed") {
106
- completedResponse = isObject(parsed.response) ? parsed.response : {};
107
- }
108
- for (const candidate of compactionItemsFromEvent(parsed)) {
109
- const item = validateCompactionItem(candidate, options.maxItemBytes);
110
- items.set(JSON.stringify(item), item);
111
- }
112
- };
113
- const processLine = (line: string) => {
114
- if (line === "") {
115
- dispatch();
116
- return;
117
- }
118
- if (line.startsWith(":")) return;
119
- if (line === "data") dataLines.push("");
120
- else if (line.startsWith("data:")) dataLines.push(line.slice(5).replace(/^ /, ""));
121
- };
85
+ const checkAbort = () => {
86
+ if (options.signal?.aborted) throw new DOMException("Compaction aborted", "AbortError");
87
+ };
88
+ const dispatch = () => {
89
+ if (dataLines.length === 0) return;
90
+ const data = dataLines.join("\n");
91
+ dataLines = [];
92
+ if (data === "[DONE]") return;
93
+ let parsed: unknown;
94
+ try {
95
+ parsed = JSON.parse(data);
96
+ } catch {
97
+ throw new CodexCompactionProtocolError("Remote compaction returned malformed SSE JSON");
98
+ }
99
+ if (!isObject(parsed)) return;
100
+ if (parsed.type === "response.completed") {
101
+ completedResponse = isObject(parsed.response) ? parsed.response : {};
102
+ }
103
+ for (const candidate of compactionItemsFromEvent(parsed)) {
104
+ const item = validateCompactionItem(candidate, options.maxItemBytes);
105
+ items.set(JSON.stringify(item), item);
106
+ }
107
+ };
108
+ const processLine = (line: string) => {
109
+ if (line === "") {
110
+ dispatch();
111
+ return;
112
+ }
113
+ if (line.startsWith(":")) return;
114
+ if (line === "data") dataLines.push("");
115
+ else if (line.startsWith("data:")) dataLines.push(line.slice(5).replace(/^ /, ""));
116
+ };
122
117
 
123
- try {
124
- while (true) {
125
- checkAbort();
126
- const { done, value } = await reader.read();
127
- if (done) break;
128
- bytes += value.byteLength;
129
- if (bytes > maxBytes) {
130
- throw new CodexCompactionProtocolError("Remote compaction stream exceeded the size limit");
131
- }
132
- pending += decoder.decode(value, { stream: true });
133
- let newline = pending.indexOf("\n");
134
- while (newline !== -1) {
135
- const rawLine = pending.slice(0, newline);
136
- pending = pending.slice(newline + 1);
137
- processLine(rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine);
138
- newline = pending.indexOf("\n");
139
- }
140
- }
141
- pending += decoder.decode();
142
- if (pending.length > 0) processLine(pending.endsWith("\r") ? pending.slice(0, -1) : pending);
143
- dispatch();
144
- checkAbort();
145
- } catch (error) {
146
- await reader.cancel(error).catch(() => undefined);
147
- throw error;
148
- } finally {
149
- options.signal?.removeEventListener("abort", onAbort);
150
- reader.releaseLock();
151
- }
118
+ try {
119
+ while (true) {
120
+ checkAbort();
121
+ const { done, value } = await reader.read();
122
+ if (done) break;
123
+ bytes += value.byteLength;
124
+ if (bytes > maxBytes) {
125
+ throw new CodexCompactionProtocolError("Remote compaction stream exceeded the size limit");
126
+ }
127
+ pending += decoder.decode(value, { stream: true });
128
+ let newline = pending.indexOf("\n");
129
+ while (newline !== -1) {
130
+ const rawLine = pending.slice(0, newline);
131
+ pending = pending.slice(newline + 1);
132
+ processLine(rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine);
133
+ newline = pending.indexOf("\n");
134
+ }
135
+ }
136
+ pending += decoder.decode();
137
+ if (pending.length > 0) processLine(pending.endsWith("\r") ? pending.slice(0, -1) : pending);
138
+ dispatch();
139
+ checkAbort();
140
+ } catch (error) {
141
+ await reader.cancel(error).catch(() => undefined);
142
+ throw error;
143
+ } finally {
144
+ options.signal?.removeEventListener("abort", onAbort);
145
+ reader.releaseLock();
146
+ }
152
147
 
153
- if (!completedResponse) {
154
- throw new CodexCompactionProtocolError(
155
- "Remote compaction stream ended without response.completed",
156
- );
157
- }
158
- if (items.size !== 1) {
159
- throw new CodexCompactionProtocolError(
160
- `Remote compaction returned ${items.size} distinct compaction items; expected exactly one`,
161
- );
162
- }
163
- return { item: [...items.values()][0], completedResponse };
148
+ if (!completedResponse) {
149
+ throw new CodexCompactionProtocolError("Remote compaction stream ended without response.completed");
150
+ }
151
+ if (items.size !== 1) {
152
+ throw new CodexCompactionProtocolError(
153
+ `Remote compaction returned ${items.size} distinct compaction items; expected exactly one`,
154
+ );
155
+ }
156
+ return { item: [...items.values()][0], completedResponse };
164
157
  }
165
158
 
166
159
  function isRetainedCompactContent(value: unknown): value is JsonObject {
167
- if (!isObject(value)) return false;
168
- if (value.type === "input_text") return typeof value.text === "string";
169
- if (value.type !== "input_image") return false;
170
- if (
171
- value.detail !== undefined &&
172
- value.detail !== null &&
173
- value.detail !== "auto" &&
174
- value.detail !== "low" &&
175
- value.detail !== "high" &&
176
- value.detail !== "original"
177
- ) {
178
- return false;
179
- }
180
- if (
181
- (value.file_id !== undefined && value.file_id !== null && typeof value.file_id !== "string") ||
182
- (value.image_url !== undefined &&
183
- value.image_url !== null &&
184
- typeof value.image_url !== "string")
185
- ) {
186
- return false;
187
- }
188
- return (
189
- (typeof value.file_id === "string" && value.file_id.length > 0) ||
190
- (typeof value.image_url === "string" && value.image_url.length > 0)
191
- );
160
+ if (!isObject(value)) return false;
161
+ if (value.type === "input_text") return typeof value.text === "string";
162
+ if (value.type !== "input_image") return false;
163
+ if (
164
+ value.detail !== undefined &&
165
+ value.detail !== null &&
166
+ value.detail !== "auto" &&
167
+ value.detail !== "low" &&
168
+ value.detail !== "high" &&
169
+ value.detail !== "original"
170
+ ) {
171
+ return false;
172
+ }
173
+ if (
174
+ (value.file_id !== undefined && value.file_id !== null && typeof value.file_id !== "string") ||
175
+ (value.image_url !== undefined && value.image_url !== null && typeof value.image_url !== "string")
176
+ ) {
177
+ return false;
178
+ }
179
+ return (
180
+ (typeof value.file_id === "string" && value.file_id.length > 0) ||
181
+ (typeof value.image_url === "string" && value.image_url.length > 0)
182
+ );
192
183
  }
193
184
 
194
185
  function isRetainedCompactMessage(value: unknown): value is JsonObject {
195
- return (
196
- isObject(value) &&
197
- value.role === "user" &&
198
- (value.type === undefined || value.type === "message") &&
199
- Array.isArray(value.content) &&
200
- value.content.length > 0 &&
201
- value.content.every(isRetainedCompactContent)
202
- );
186
+ return (
187
+ isObject(value) &&
188
+ value.role === "user" &&
189
+ (value.type === undefined || value.type === "message") &&
190
+ Array.isArray(value.content) &&
191
+ value.content.length > 0 &&
192
+ value.content.every(isRetainedCompactContent)
193
+ );
203
194
  }
204
195
 
205
196
  export function validateCompactedResponse(
206
- value: unknown,
207
- options: { maxBytes?: number; maxItemBytes?: number } = {},
197
+ value: unknown,
198
+ options: { maxBytes?: number; maxItemBytes?: number } = {},
208
199
  ): CollectedCompactResponse {
209
- if (!isObject(value) || !Array.isArray(value.output)) {
210
- throw new CodexCompactionProtocolError("Responses Compact returned an invalid response object");
211
- }
212
- const maxBytes = options.maxBytes ?? MAX_COMPACT_JSON_BYTES;
213
- const maxItemBytes = options.maxItemBytes ?? MAX_COMPACTION_ITEM_BYTES;
214
- if (byteLength(value) > maxBytes) {
215
- throw new CodexCompactionProtocolError("Responses Compact response exceeded the size limit");
216
- }
217
- if (value.output.length === 0) {
218
- throw new CodexCompactionProtocolError("Responses Compact returned no output items");
219
- }
220
- const output = value.output.map((item) => {
221
- if (!isObject(item)) {
222
- throw new CodexCompactionProtocolError("Responses Compact returned a non-object output item");
223
- }
224
- if (byteLength(item) > maxItemBytes) {
225
- throw new CodexCompactionProtocolError(
226
- "Responses Compact output item exceeded the size limit",
227
- );
228
- }
229
- return structuredClone(item);
230
- });
231
- const compactionItems = output.filter((item) => item.type === "compaction");
232
- if (compactionItems.length !== 1 || output.at(-1)?.type !== "compaction") {
233
- throw new CodexCompactionProtocolError(
234
- "Responses Compact must return retained messages followed by one compaction item",
235
- );
236
- }
237
- for (const item of output.slice(0, -1)) {
238
- if (!isRetainedCompactMessage(item)) {
239
- throw new CodexCompactionProtocolError(
240
- "Responses Compact returned an unsupported retained output item",
241
- );
242
- }
243
- }
244
- const item = validateCompactionItem(output.at(-1), maxItemBytes);
245
- return { item, output: [...output.slice(0, -1), item], response: structuredClone(value) };
200
+ if (!isObject(value) || !Array.isArray(value.output)) {
201
+ throw new CodexCompactionProtocolError("Responses Compact returned an invalid response object");
202
+ }
203
+ const maxBytes = options.maxBytes ?? MAX_COMPACT_JSON_BYTES;
204
+ const maxItemBytes = options.maxItemBytes ?? MAX_COMPACTION_ITEM_BYTES;
205
+ if (byteLength(value) > maxBytes) {
206
+ throw new CodexCompactionProtocolError("Responses Compact response exceeded the size limit");
207
+ }
208
+ if (value.output.length === 0) {
209
+ throw new CodexCompactionProtocolError("Responses Compact returned no output items");
210
+ }
211
+ const output = value.output.map((item) => {
212
+ if (!isObject(item)) {
213
+ throw new CodexCompactionProtocolError("Responses Compact returned a non-object output item");
214
+ }
215
+ if (byteLength(item) > maxItemBytes) {
216
+ throw new CodexCompactionProtocolError("Responses Compact output item exceeded the size limit");
217
+ }
218
+ return structuredClone(item);
219
+ });
220
+ const compactionItems = output.filter((item) => item.type === "compaction");
221
+ if (compactionItems.length !== 1 || output.at(-1)?.type !== "compaction") {
222
+ throw new CodexCompactionProtocolError(
223
+ "Responses Compact must return retained messages followed by one compaction item",
224
+ );
225
+ }
226
+ for (const item of output.slice(0, -1)) {
227
+ if (!isRetainedCompactMessage(item)) {
228
+ throw new CodexCompactionProtocolError("Responses Compact returned an unsupported retained output item");
229
+ }
230
+ }
231
+ const item = validateCompactionItem(output.at(-1), maxItemBytes);
232
+ return { item, output: [...output.slice(0, -1), item], response: structuredClone(value) };
246
233
  }
247
234
 
248
235
  export async function collectCompactResponse(
249
- response: Response,
250
- options: { signal?: AbortSignal; maxBytes?: number; maxItemBytes?: number } = {},
236
+ response: Response,
237
+ options: { signal?: AbortSignal; maxBytes?: number; maxItemBytes?: number } = {},
251
238
  ): Promise<CollectedCompactResponse> {
252
- const maxBytes = options.maxBytes ?? MAX_COMPACT_JSON_BYTES;
253
- const declaredLength = Number(response.headers.get("content-length"));
254
- if (Number.isFinite(declaredLength) && declaredLength > maxBytes) {
255
- const error = new CodexCompactionProtocolError(
256
- "Responses Compact response exceeded the size limit",
257
- );
258
- await response.body?.cancel(error).catch(() => undefined);
259
- throw error;
260
- }
261
- if (!response.body) {
262
- throw new CodexCompactionProtocolError("Responses Compact response did not contain a body");
263
- }
264
- const reader = response.body.getReader();
265
- const chunks: Uint8Array[] = [];
266
- let bytes = 0;
267
- const onAbort = () => {
268
- void reader.cancel(new DOMException("Compaction aborted", "AbortError")).catch(() => undefined);
269
- };
270
- options.signal?.addEventListener("abort", onAbort, { once: true });
271
- try {
272
- while (true) {
273
- if (options.signal?.aborted) throw new DOMException("Compaction aborted", "AbortError");
274
- const { done, value } = await reader.read();
275
- if (done) break;
276
- bytes += value.byteLength;
277
- if (bytes > maxBytes) {
278
- throw new CodexCompactionProtocolError(
279
- "Responses Compact response exceeded the size limit",
280
- );
281
- }
282
- chunks.push(value);
283
- }
284
- if (options.signal?.aborted) throw new DOMException("Compaction aborted", "AbortError");
285
- } catch (error) {
286
- await reader.cancel(error).catch(() => undefined);
287
- throw error;
288
- } finally {
289
- options.signal?.removeEventListener("abort", onAbort);
290
- reader.releaseLock();
291
- }
292
- const body = new Uint8Array(bytes);
293
- let offset = 0;
294
- for (const chunk of chunks) {
295
- body.set(chunk, offset);
296
- offset += chunk.byteLength;
297
- }
298
- let parsed: unknown;
299
- try {
300
- parsed = JSON.parse(new TextDecoder().decode(body));
301
- } catch {
302
- throw new CodexCompactionProtocolError("Responses Compact returned malformed JSON");
303
- }
304
- return validateCompactedResponse(parsed, options);
239
+ const maxBytes = options.maxBytes ?? MAX_COMPACT_JSON_BYTES;
240
+ const declaredLength = Number(response.headers.get("content-length"));
241
+ if (Number.isFinite(declaredLength) && declaredLength > maxBytes) {
242
+ const error = new CodexCompactionProtocolError("Responses Compact response exceeded the size limit");
243
+ await response.body?.cancel(error).catch(() => undefined);
244
+ throw error;
245
+ }
246
+ if (!response.body) {
247
+ throw new CodexCompactionProtocolError("Responses Compact response did not contain a body");
248
+ }
249
+ const reader = response.body.getReader();
250
+ const chunks: Uint8Array[] = [];
251
+ let bytes = 0;
252
+ const onAbort = () => {
253
+ void reader.cancel(new DOMException("Compaction aborted", "AbortError")).catch(() => undefined);
254
+ };
255
+ options.signal?.addEventListener("abort", onAbort, { once: true });
256
+ try {
257
+ while (true) {
258
+ if (options.signal?.aborted) throw new DOMException("Compaction aborted", "AbortError");
259
+ const { done, value } = await reader.read();
260
+ if (done) break;
261
+ bytes += value.byteLength;
262
+ if (bytes > maxBytes) {
263
+ throw new CodexCompactionProtocolError("Responses Compact response exceeded the size limit");
264
+ }
265
+ chunks.push(value);
266
+ }
267
+ if (options.signal?.aborted) throw new DOMException("Compaction aborted", "AbortError");
268
+ } catch (error) {
269
+ await reader.cancel(error).catch(() => undefined);
270
+ throw error;
271
+ } finally {
272
+ options.signal?.removeEventListener("abort", onAbort);
273
+ reader.releaseLock();
274
+ }
275
+ const body = new Uint8Array(bytes);
276
+ let offset = 0;
277
+ for (const chunk of chunks) {
278
+ body.set(chunk, offset);
279
+ offset += chunk.byteLength;
280
+ }
281
+ let parsed: unknown;
282
+ try {
283
+ parsed = JSON.parse(new TextDecoder().decode(body));
284
+ } catch {
285
+ throw new CodexCompactionProtocolError("Responses Compact returned malformed JSON");
286
+ }
287
+ return validateCompactedResponse(parsed, options);
305
288
  }
306
289
 
307
290
  function markerTextFromItem(item: unknown): string | undefined {
308
- if (!isObject(item) || item.role !== "user" || !Array.isArray(item.content)) return undefined;
309
- if (item.content.length !== 1) return undefined;
310
- const content = item.content[0];
311
- if (!isObject(content) || content.type !== "input_text" || typeof content.text !== "string") {
312
- return undefined;
313
- }
314
- return content.text;
291
+ if (!isObject(item) || item.role !== "user" || !Array.isArray(item.content)) return undefined;
292
+ if (item.content.length !== 1) return undefined;
293
+ const content = item.content[0];
294
+ if (!isObject(content) || content.type !== "input_text" || typeof content.text !== "string") {
295
+ return undefined;
296
+ }
297
+ return content.text;
315
298
  }
316
299
 
317
300
  export function rewriteCheckpointMarker(
318
- payload: unknown,
319
- marker: string,
320
- replacementHistory: readonly unknown[],
301
+ payload: unknown,
302
+ marker: string,
303
+ replacementHistory: readonly unknown[],
321
304
  ): JsonObject {
322
- if (!isObject(payload) || !Array.isArray(payload.input)) {
323
- throw new CodexCompactionProtocolError("Codex Responses payload is missing an input array");
324
- }
325
- const matches = payload.input
326
- .map((item, index) => (markerTextFromItem(item) === marker ? index : -1))
327
- .filter((index) => index >= 0);
328
- if (matches.length !== 1) {
329
- throw new CodexCompactionProtocolError(
330
- `Provider payload contained ${matches.length} checkpoint markers; expected exactly one`,
331
- );
332
- }
333
- const index = matches[0];
334
- return {
335
- ...payload,
336
- input: [
337
- ...payload.input.slice(0, index),
338
- ...structuredClone(replacementHistory),
339
- ...payload.input.slice(index + 1),
340
- ],
341
- };
305
+ if (!isObject(payload) || !Array.isArray(payload.input)) {
306
+ throw new CodexCompactionProtocolError("Codex Responses payload is missing an input array");
307
+ }
308
+ const matches = payload.input
309
+ .map((item, index) => (markerTextFromItem(item) === marker ? index : -1))
310
+ .filter((index) => index >= 0);
311
+ if (matches.length !== 1) {
312
+ throw new CodexCompactionProtocolError(
313
+ `Provider payload contained ${matches.length} checkpoint markers; expected exactly one`,
314
+ );
315
+ }
316
+ const index = matches[0];
317
+ return {
318
+ ...payload,
319
+ input: [
320
+ ...payload.input.slice(0, index),
321
+ ...structuredClone(replacementHistory),
322
+ ...payload.input.slice(index + 1),
323
+ ],
324
+ };
342
325
  }
343
326
 
344
327
  export function appendCompactionTrigger(payload: unknown): JsonObject {
345
- if (!isObject(payload) || !Array.isArray(payload.input)) {
346
- throw new CodexCompactionProtocolError("Codex Responses payload is missing an input array");
347
- }
348
- if (payload.input.some((item) => isObject(item) && item.type === "compaction_trigger")) {
349
- throw new CodexCompactionProtocolError(
350
- "Provider payload already contains a compaction trigger",
351
- );
352
- }
353
- return { ...payload, input: [...payload.input, { type: "compaction_trigger" }] };
328
+ if (!isObject(payload) || !Array.isArray(payload.input)) {
329
+ throw new CodexCompactionProtocolError("Codex Responses payload is missing an input array");
330
+ }
331
+ if (payload.input.some((item) => isObject(item) && item.type === "compaction_trigger")) {
332
+ throw new CodexCompactionProtocolError("Provider payload already contains a compaction trigger");
333
+ }
334
+ return { ...payload, input: [...payload.input, { type: "compaction_trigger" }] };
354
335
  }
355
336
 
356
337
  export function expandRemoteCompactionPayload(
357
- payload: unknown,
358
- checkpoint?: { marker: string; replacementHistory: readonly unknown[] },
338
+ payload: unknown,
339
+ checkpoint?: { marker: string; replacementHistory: readonly unknown[] },
359
340
  ): JsonObject {
360
- if (checkpoint) {
361
- return rewriteCheckpointMarker(payload, checkpoint.marker, checkpoint.replacementHistory);
362
- }
363
- if (!isObject(payload) || !Array.isArray(payload.input)) {
364
- throw new CodexCompactionProtocolError("Responses payload is missing an input array");
365
- }
366
- return structuredClone(payload);
341
+ if (checkpoint) {
342
+ return rewriteCheckpointMarker(payload, checkpoint.marker, checkpoint.replacementHistory);
343
+ }
344
+ if (!isObject(payload) || !Array.isArray(payload.input)) {
345
+ throw new CodexCompactionProtocolError("Responses payload is missing an input array");
346
+ }
347
+ return structuredClone(payload);
367
348
  }
368
349
 
369
350
  export function prepareRemoteCompactionPayload(
370
- payload: unknown,
371
- checkpoint?: { marker: string; replacementHistory: readonly unknown[] },
351
+ payload: unknown,
352
+ checkpoint?: { marker: string; replacementHistory: readonly unknown[] },
372
353
  ): JsonObject {
373
- return appendCompactionTrigger(expandRemoteCompactionPayload(payload, checkpoint));
354
+ return appendCompactionTrigger(expandRemoteCompactionPayload(payload, checkpoint));
374
355
  }
375
356
 
376
357
  export function hasCheckpointMarker(payload: unknown, marker: string): boolean {
377
- return (
378
- isObject(payload) &&
379
- Array.isArray(payload.input) &&
380
- payload.input.some((item) => markerTextFromItem(item) === marker)
381
- );
358
+ return (
359
+ isObject(payload) &&
360
+ Array.isArray(payload.input) &&
361
+ payload.input.some((item) => markerTextFromItem(item) === marker)
362
+ );
382
363
  }