@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/README.md +34 -46
- package/dist/chunks/{chunk-JYXZMCXD.js → chunk-7H7JJ7ZV.js} +9 -15
- package/dist/chunks/chunk-7H7JJ7ZV.js.map +7 -0
- package/dist/chunks/{settings-menu-VYQ6ABCP.js → settings-menu-OAWFW2PH.js} +5 -15
- package/dist/chunks/settings-menu-OAWFW2PH.js.map +7 -0
- package/dist/index.ts +91 -94
- package/dist/index.ts.map +2 -2
- package/package.json +53 -54
- package/src/checkpoint.ts +261 -249
- package/src/codex-compact.ts +250 -254
- package/src/model-api.ts +40 -29
- package/src/protocol.ts +293 -312
- package/src/remote-compact.ts +230 -252
- package/src/remote-shared.ts +12 -15
- package/src/remote-types.ts +27 -26
- package/src/remote-v2.ts +57 -61
- package/src/remote.ts +7 -9
- package/src/settings-menu.ts +207 -231
- package/src/settings.ts +227 -190
- package/src/terminal.ts +4 -4
- package/dist/chunks/chunk-JYXZMCXD.js.map +0 -7
- package/dist/chunks/settings-menu-VYQ6ABCP.js.map +0 -7
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
15
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
function byteLength(value: unknown): number {
|
|
19
|
-
|
|
19
|
+
return Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export function isCompactionItem(value: unknown): value is JsonObject {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
48
|
-
|
|
42
|
+
item: JsonObject;
|
|
43
|
+
completedResponse?: JsonObject;
|
|
49
44
|
}
|
|
50
45
|
|
|
51
46
|
export interface CollectedCompactResponse {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
47
|
+
item: JsonObject;
|
|
48
|
+
output: JsonObject[];
|
|
49
|
+
response: JsonObject;
|
|
55
50
|
}
|
|
56
51
|
|
|
57
52
|
function compactionItemsFromEvent(event: JsonObject): unknown[] {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
65
|
+
stream: ReadableStream<Uint8Array>,
|
|
66
|
+
options: {
|
|
67
|
+
signal?: AbortSignal;
|
|
68
|
+
maxBytes?: number;
|
|
69
|
+
maxItemBytes?: number;
|
|
70
|
+
} = {},
|
|
76
71
|
): Promise<CollectedCompaction> {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
207
|
-
|
|
197
|
+
value: unknown,
|
|
198
|
+
options: { maxBytes?: number; maxItemBytes?: number } = {},
|
|
208
199
|
): CollectedCompactResponse {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
|
|
250
|
-
|
|
236
|
+
response: Response,
|
|
237
|
+
options: { signal?: AbortSignal; maxBytes?: number; maxItemBytes?: number } = {},
|
|
251
238
|
): Promise<CollectedCompactResponse> {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
-
|
|
319
|
-
|
|
320
|
-
|
|
301
|
+
payload: unknown,
|
|
302
|
+
marker: string,
|
|
303
|
+
replacementHistory: readonly unknown[],
|
|
321
304
|
): JsonObject {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
-
|
|
358
|
-
|
|
338
|
+
payload: unknown,
|
|
339
|
+
checkpoint?: { marker: string; replacementHistory: readonly unknown[] },
|
|
359
340
|
): JsonObject {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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
|
-
|
|
371
|
-
|
|
351
|
+
payload: unknown,
|
|
352
|
+
checkpoint?: { marker: string; replacementHistory: readonly unknown[] },
|
|
372
353
|
): JsonObject {
|
|
373
|
-
|
|
354
|
+
return appendCompactionTrigger(expandRemoteCompactionPayload(payload, checkpoint));
|
|
374
355
|
}
|
|
375
356
|
|
|
376
357
|
export function hasCheckpointMarker(payload: unknown, marker: string): boolean {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
358
|
+
return (
|
|
359
|
+
isObject(payload) &&
|
|
360
|
+
Array.isArray(payload.input) &&
|
|
361
|
+
payload.input.some((item) => markerTextFromItem(item) === marker)
|
|
362
|
+
);
|
|
382
363
|
}
|