@narumitw/pi-codex-compact 0.52.0 → 0.53.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.
package/src/checkpoint.ts CHANGED
@@ -1,317 +1,329 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
2
  import type { AgentMessage } from "@earendil-works/pi-agent-core";
3
+ import type { Api } from "@earendil-works/pi-ai";
3
4
  import type { CompactionEntry, SessionEntry } from "@earendil-works/pi-coding-agent";
4
- import type { RemoteCompactionProtocol, ResponsesCompactionApi } from "./model-api.js";
5
- import { RESPONSES_COMPACTION_APIS } from "./model-api.js";
5
+ import type { RemoteCompactionProtocol, ResponsesCompactionProfile } from "./model-api.js";
6
6
  import { type JsonObject, validateCompactionItem } from "./protocol.js";
7
7
 
8
8
  export const CHECKPOINT_KIND = "pi-codex-remote-compaction";
9
- export const CHECKPOINT_VERSION = 2;
9
+ export const CHECKPOINT_VERSION = 3;
10
10
  export const REPLACEMENT_TOKEN_BUDGET = 64_000;
11
11
  export const REPLACEMENT_BYTE_BUDGET = 8 * 1024 * 1024;
12
12
  const MAX_MEDIA_ITEM_BYTES = 2 * 1024 * 1024;
13
13
  const MAX_CHECKPOINT_DETAILS_BYTES = 10 * 1024 * 1024;
14
14
  const MAX_CHECKPOINT_ID_LENGTH = 128;
15
15
  const MAX_PROVIDER_ID_LENGTH = 256;
16
+ const MAX_API_ID_LENGTH = 256;
16
17
  const MAX_MODEL_ID_LENGTH = 512;
17
18
  const MAX_KEPT_FINGERPRINTS = 100_000;
18
19
 
19
20
  export interface CodexCheckpointDetails {
20
- kind: typeof CHECKPOINT_KIND;
21
- version: typeof CHECKPOINT_VERSION;
22
- checkpointId: string;
23
- provider: string;
24
- api: ResponsesCompactionApi;
25
- modelId: string;
26
- protocol: RemoteCompactionProtocol;
27
- replacementHistory: JsonObject[];
28
- keptMessageFingerprints: string[];
29
- createdAt: string;
21
+ kind: typeof CHECKPOINT_KIND;
22
+ version: typeof CHECKPOINT_VERSION;
23
+ checkpointId: string;
24
+ provider: string;
25
+ api: Api;
26
+ profile: ResponsesCompactionProfile;
27
+ modelId: string;
28
+ protocol: RemoteCompactionProtocol;
29
+ replacementHistory: JsonObject[];
30
+ keptMessageFingerprints: string[];
31
+ createdAt: string;
30
32
  }
31
33
 
32
34
  function isObject(value: unknown): value is JsonObject {
33
- return typeof value === "object" && value !== null && !Array.isArray(value);
35
+ return typeof value === "object" && value !== null && !Array.isArray(value);
34
36
  }
35
37
 
36
38
  function stableValue(value: unknown): unknown {
37
- if (Array.isArray(value)) return value.map(stableValue);
38
- if (!isObject(value)) return value;
39
- return Object.fromEntries(
40
- Object.entries(value)
41
- .sort(([left], [right]) => left.localeCompare(right))
42
- .map(([key, child]) => [key, stableValue(child)]),
43
- );
39
+ if (Array.isArray(value)) return value.map(stableValue);
40
+ if (!isObject(value)) return value;
41
+ return Object.fromEntries(
42
+ Object.entries(value)
43
+ .sort(([left], [right]) => left.localeCompare(right))
44
+ .map(([key, child]) => [key, stableValue(child)]),
45
+ );
44
46
  }
45
47
 
46
48
  function serializedBytes(value: unknown): number {
47
- return Buffer.byteLength(JSON.stringify(value), "utf8");
49
+ return Buffer.byteLength(JSON.stringify(value), "utf8");
48
50
  }
49
51
 
50
52
  export function fingerprintMessage(message: AgentMessage): string {
51
- return createHash("sha256")
52
- .update(JSON.stringify(stableValue(message)))
53
- .digest("hex");
53
+ return createHash("sha256")
54
+ .update(JSON.stringify(stableValue(message)))
55
+ .digest("hex");
54
56
  }
55
57
 
56
58
  export function checkpointMarker(checkpointId: string): string {
57
- return [
58
- `[PI_CODEX_REMOTE_CHECKPOINT:${checkpointId}]`,
59
- "Opaque checkpoint injection failed. Do not infer missing history; tell the user to re-enable",
60
- "@narumitw/pi-codex-compact with the same model and Responses API.",
61
- ].join(" ");
59
+ return [
60
+ `[PI_CODEX_REMOTE_CHECKPOINT:${checkpointId}]`,
61
+ "Opaque checkpoint injection failed. Do not infer missing history; tell the user to re-enable",
62
+ "@narumitw/pi-codex-compact with the same model and Responses API.",
63
+ ].join(" ");
62
64
  }
63
65
 
64
66
  export function fallbackSummary(checkpointId: string): string {
65
- return [
66
- `Responses compaction checkpoint ${checkpointId} stores the older history opaquely.`,
67
- "Full replay requires @narumitw/pi-codex-compact and the same model through a compatible Responses provider.",
68
- "Without them, only Pi's retained recent messages remain available.",
69
- ].join(" ");
67
+ return [
68
+ `Responses compaction checkpoint ${checkpointId} stores the older history opaquely.`,
69
+ "Full replay requires @narumitw/pi-codex-compact and the same model through a compatible Responses provider.",
70
+ "Without them, only Pi's retained recent messages remain available.",
71
+ ].join(" ");
70
72
  }
71
73
 
72
74
  function markerMessage(checkpointId: string, timestamp: number): AgentMessage {
73
- return {
74
- role: "user",
75
- content: [{ type: "text", text: checkpointMarker(checkpointId) }],
76
- timestamp,
77
- };
75
+ return {
76
+ role: "user",
77
+ content: [{ type: "text", text: checkpointMarker(checkpointId) }],
78
+ timestamp,
79
+ };
78
80
  }
79
81
 
80
82
  export function parseCheckpointDetails(value: unknown): CodexCheckpointDetails | undefined {
81
- if (!isObject(value)) return undefined;
82
- try {
83
- if (serializedBytes(value) > MAX_CHECKPOINT_DETAILS_BYTES) return undefined;
84
- } catch {
85
- return undefined;
86
- }
87
- const isVersionOne =
88
- value.version === 1 &&
89
- value.api === "openai-codex-responses" &&
90
- value.protocol === "remote-compaction-v2";
91
- const isVersionTwo =
92
- value.version === CHECKPOINT_VERSION &&
93
- RESPONSES_COMPACTION_APIS.includes(value.api as ResponsesCompactionApi) &&
94
- (value.protocol === "remote-v2" || value.protocol === "responses-compact");
95
- if (
96
- value.kind !== CHECKPOINT_KIND ||
97
- (!isVersionOne && !isVersionTwo) ||
98
- typeof value.checkpointId !== "string" ||
99
- value.checkpointId.length < 8 ||
100
- value.checkpointId.length > MAX_CHECKPOINT_ID_LENGTH ||
101
- typeof value.provider !== "string" ||
102
- value.provider.length === 0 ||
103
- value.provider.length > MAX_PROVIDER_ID_LENGTH ||
104
- typeof value.modelId !== "string" ||
105
- value.modelId.length === 0 ||
106
- value.modelId.length > MAX_MODEL_ID_LENGTH ||
107
- !Array.isArray(value.replacementHistory) ||
108
- !Array.isArray(value.keptMessageFingerprints) ||
109
- value.keptMessageFingerprints.length > MAX_KEPT_FINGERPRINTS ||
110
- typeof value.createdAt !== "string" ||
111
- value.createdAt.length > 64
112
- ) {
113
- return undefined;
114
- }
115
- if (
116
- value.replacementHistory.length === 0 ||
117
- !value.replacementHistory.every(isObject) ||
118
- !value.keptMessageFingerprints.every(
119
- (fingerprint) => typeof fingerprint === "string" && /^[a-f0-9]{64}$/.test(fingerprint),
120
- ) ||
121
- serializedBytes(value.replacementHistory) > REPLACEMENT_BYTE_BUDGET
122
- ) {
123
- return undefined;
124
- }
125
- const last = value.replacementHistory.at(-1);
126
- try {
127
- validateCompactionItem(last);
128
- } catch {
129
- return undefined;
130
- }
131
- return {
132
- kind: CHECKPOINT_KIND,
133
- version: CHECKPOINT_VERSION,
134
- checkpointId: value.checkpointId,
135
- provider: value.provider,
136
- api: isVersionOne ? "openai-codex-responses" : (value.api as ResponsesCompactionApi),
137
- modelId: value.modelId,
138
- protocol: isVersionOne ? "remote-v2" : (value.protocol as RemoteCompactionProtocol),
139
- replacementHistory: structuredClone(value.replacementHistory),
140
- keptMessageFingerprints: [...value.keptMessageFingerprints],
141
- createdAt: value.createdAt,
142
- };
83
+ if (!isObject(value)) return undefined;
84
+ try {
85
+ if (serializedBytes(value) > MAX_CHECKPOINT_DETAILS_BYTES) return undefined;
86
+ } catch {
87
+ return undefined;
88
+ }
89
+ const isVersionOne =
90
+ value.version === 1 && value.api === "openai-codex-responses" && value.protocol === "remote-compaction-v2";
91
+ const isVersionTwo =
92
+ value.version === 2 &&
93
+ (value.api === "openai-codex-responses" ||
94
+ value.api === "openai-responses" ||
95
+ value.api === "azure-openai-responses") &&
96
+ (value.protocol === "remote-v2" || value.protocol === "responses-compact");
97
+ const isVersionThree =
98
+ value.version === CHECKPOINT_VERSION &&
99
+ typeof value.api === "string" &&
100
+ value.api.length > 0 &&
101
+ value.api.length <= MAX_API_ID_LENGTH &&
102
+ (value.profile === "codex-responses-v1" || value.profile === "openai-responses-v1") &&
103
+ (value.protocol === "remote-v2" || value.protocol === "responses-compact");
104
+ if (
105
+ value.kind !== CHECKPOINT_KIND ||
106
+ (!isVersionOne && !isVersionTwo && !isVersionThree) ||
107
+ typeof value.checkpointId !== "string" ||
108
+ value.checkpointId.length < 8 ||
109
+ value.checkpointId.length > MAX_CHECKPOINT_ID_LENGTH ||
110
+ typeof value.provider !== "string" ||
111
+ value.provider.length === 0 ||
112
+ value.provider.length > MAX_PROVIDER_ID_LENGTH ||
113
+ typeof value.modelId !== "string" ||
114
+ value.modelId.length === 0 ||
115
+ value.modelId.length > MAX_MODEL_ID_LENGTH ||
116
+ !Array.isArray(value.replacementHistory) ||
117
+ !Array.isArray(value.keptMessageFingerprints) ||
118
+ value.keptMessageFingerprints.length > MAX_KEPT_FINGERPRINTS ||
119
+ typeof value.createdAt !== "string" ||
120
+ value.createdAt.length > 64
121
+ ) {
122
+ return undefined;
123
+ }
124
+ const api = value.api as Api;
125
+ const profile =
126
+ api === "openai-codex-responses"
127
+ ? "codex-responses-v1"
128
+ : api === "openai-responses" || api === "azure-openai-responses"
129
+ ? "openai-responses-v1"
130
+ : value.profile;
131
+ if (
132
+ (profile !== "codex-responses-v1" && profile !== "openai-responses-v1") ||
133
+ (api !== "openai-codex-responses" &&
134
+ api !== "openai-responses" &&
135
+ api !== "azure-openai-responses" &&
136
+ profile !== "codex-responses-v1") ||
137
+ (isVersionThree && value.profile !== profile)
138
+ ) {
139
+ return undefined;
140
+ }
141
+ if (
142
+ value.replacementHistory.length === 0 ||
143
+ !value.replacementHistory.every(isObject) ||
144
+ !value.keptMessageFingerprints.every(
145
+ (fingerprint) => typeof fingerprint === "string" && /^[a-f0-9]{64}$/.test(fingerprint),
146
+ ) ||
147
+ serializedBytes(value.replacementHistory) > REPLACEMENT_BYTE_BUDGET
148
+ ) {
149
+ return undefined;
150
+ }
151
+ const last = value.replacementHistory.at(-1);
152
+ try {
153
+ validateCompactionItem(last);
154
+ } catch {
155
+ return undefined;
156
+ }
157
+ return {
158
+ kind: CHECKPOINT_KIND,
159
+ version: CHECKPOINT_VERSION,
160
+ checkpointId: value.checkpointId,
161
+ provider: value.provider,
162
+ api,
163
+ profile,
164
+ modelId: value.modelId,
165
+ protocol: isVersionOne ? "remote-v2" : (value.protocol as RemoteCompactionProtocol),
166
+ replacementHistory: structuredClone(value.replacementHistory),
167
+ keptMessageFingerprints: [...value.keptMessageFingerprints],
168
+ createdAt: value.createdAt,
169
+ };
143
170
  }
144
171
 
145
172
  export function latestCheckpoint(entries: readonly SessionEntry[]):
146
- | {
147
- entry: CompactionEntry<CodexCheckpointDetails>;
148
- details: CodexCheckpointDetails;
149
- }
150
- | undefined {
151
- for (let index = entries.length - 1; index >= 0; index--) {
152
- const entry = entries[index];
153
- if (entry.type !== "compaction") continue;
154
- const details = parseCheckpointDetails(entry.details);
155
- return details
156
- ? { entry: entry as CompactionEntry<CodexCheckpointDetails>, details }
157
- : undefined;
158
- }
159
- return undefined;
173
+ | {
174
+ entry: CompactionEntry<CodexCheckpointDetails>;
175
+ details: CodexCheckpointDetails;
176
+ }
177
+ | undefined {
178
+ for (let index = entries.length - 1; index >= 0; index--) {
179
+ const entry = entries[index];
180
+ if (entry.type !== "compaction") continue;
181
+ const details = parseCheckpointDetails(entry.details);
182
+ return details ? { entry: entry as CompactionEntry<CodexCheckpointDetails>, details } : undefined;
183
+ }
184
+ return undefined;
160
185
  }
161
186
 
162
187
  function isOlderCompactionSummary(message: AgentMessage, timestamp: number): boolean {
163
- return (
164
- message.role === "compactionSummary" &&
165
- Number.isFinite(message.timestamp) &&
166
- Number.isFinite(timestamp) &&
167
- message.timestamp < timestamp
168
- );
188
+ return (
189
+ message.role === "compactionSummary" &&
190
+ Number.isFinite(message.timestamp) &&
191
+ Number.isFinite(timestamp) &&
192
+ message.timestamp < timestamp
193
+ );
169
194
  }
170
195
 
171
196
  export function projectCheckpointContext(
172
- messages: readonly AgentMessage[],
173
- details: CodexCheckpointDetails,
174
- checkpointSummary: string,
197
+ messages: readonly AgentMessage[],
198
+ details: CodexCheckpointDetails,
199
+ checkpointSummary: string,
175
200
  ): AgentMessage[] | undefined {
176
- const summaryIndex = messages.findIndex(
177
- (message) => message.role === "compactionSummary" && message.summary === checkpointSummary,
178
- );
179
- if (summaryIndex < 0) return undefined;
180
- const timestamp = messages[summaryIndex].timestamp;
181
- let messageIndex = summaryIndex + 1;
182
- let fingerprintIndex = 0;
183
- while (fingerprintIndex < details.keptMessageFingerprints.length) {
184
- if (messageIndex >= messages.length) return undefined;
185
- const message = messages[messageIndex];
186
- if (fingerprintMessage(message) === details.keptMessageFingerprints[fingerprintIndex]) {
187
- messageIndex += 1;
188
- fingerprintIndex += 1;
189
- continue;
190
- }
191
- if (isOlderCompactionSummary(message, timestamp)) {
192
- messageIndex += 1;
193
- continue;
194
- }
195
- return undefined;
196
- }
197
- while (
198
- messageIndex < messages.length &&
199
- isOlderCompactionSummary(messages[messageIndex], timestamp)
200
- ) {
201
- messageIndex += 1;
202
- }
203
- return [
204
- ...messages.slice(0, summaryIndex),
205
- markerMessage(details.checkpointId, timestamp),
206
- ...messages.slice(messageIndex),
207
- ];
201
+ const summaryIndex = messages.findIndex(
202
+ (message) => message.role === "compactionSummary" && message.summary === checkpointSummary,
203
+ );
204
+ if (summaryIndex < 0) return undefined;
205
+ const timestamp = messages[summaryIndex].timestamp;
206
+ let messageIndex = summaryIndex + 1;
207
+ let fingerprintIndex = 0;
208
+ while (fingerprintIndex < details.keptMessageFingerprints.length) {
209
+ if (messageIndex >= messages.length) return undefined;
210
+ const message = messages[messageIndex];
211
+ if (fingerprintMessage(message) === details.keptMessageFingerprints[fingerprintIndex]) {
212
+ messageIndex += 1;
213
+ fingerprintIndex += 1;
214
+ continue;
215
+ }
216
+ if (isOlderCompactionSummary(message, timestamp)) {
217
+ messageIndex += 1;
218
+ continue;
219
+ }
220
+ return undefined;
221
+ }
222
+ while (messageIndex < messages.length && isOlderCompactionSummary(messages[messageIndex], timestamp)) {
223
+ messageIndex += 1;
224
+ }
225
+ return [
226
+ ...messages.slice(0, summaryIndex),
227
+ markerMessage(details.checkpointId, timestamp),
228
+ ...messages.slice(messageIndex),
229
+ ];
208
230
  }
209
231
 
210
232
  function rawText(item: JsonObject): string {
211
- if (!Array.isArray(item.content)) return "";
212
- return item.content
213
- .flatMap((part) =>
214
- isObject(part) && typeof part.text === "string" && part.type === "input_text"
215
- ? [part.text]
216
- : [],
217
- )
218
- .join("\n");
233
+ if (!Array.isArray(item.content)) return "";
234
+ return item.content
235
+ .flatMap((part) =>
236
+ isObject(part) && typeof part.text === "string" && part.type === "input_text" ? [part.text] : [],
237
+ )
238
+ .join("\n");
219
239
  }
220
240
 
221
241
  function hasMedia(item: JsonObject): boolean {
222
- return (
223
- Array.isArray(item.content) &&
224
- item.content.some((part) => isObject(part) && part.type === "input_image")
225
- );
242
+ return Array.isArray(item.content) && item.content.some((part) => isObject(part) && part.type === "input_image");
226
243
  }
227
244
 
228
245
  function truncateTextItem(item: JsonObject, maxChars: number): JsonObject | undefined {
229
- if (!Array.isArray(item.content) || maxChars <= 32) return undefined;
230
- let remaining = maxChars - 16;
231
- const content = [...item.content].reverse().flatMap((part) => {
232
- if (
233
- !isObject(part) ||
234
- part.type !== "input_text" ||
235
- typeof part.text !== "string" ||
236
- remaining <= 0
237
- ) {
238
- return [];
239
- }
240
- const text = part.text.slice(-remaining);
241
- remaining -= text.length;
242
- return [{ ...part, text: `[truncated]\n${text}` }];
243
- });
244
- if (content.length === 0) return undefined;
245
- return { ...item, content: content.reverse() };
246
+ if (!Array.isArray(item.content) || maxChars <= 32) return undefined;
247
+ let remaining = maxChars - 16;
248
+ const content = [...item.content].reverse().flatMap((part) => {
249
+ if (!isObject(part) || part.type !== "input_text" || typeof part.text !== "string" || remaining <= 0) {
250
+ return [];
251
+ }
252
+ const text = part.text.slice(-remaining);
253
+ remaining -= text.length;
254
+ return [{ ...part, text: `[truncated]\n${text}` }];
255
+ });
256
+ if (content.length === 0) return undefined;
257
+ return { ...item, content: content.reverse() };
246
258
  }
247
259
 
248
260
  export function buildReplacementHistory(
249
- input: readonly unknown[],
250
- compactionItem: JsonObject,
251
- options: { tokenBudget?: number; byteBudget?: number } = {},
261
+ input: readonly unknown[],
262
+ compactionItem: JsonObject,
263
+ options: { tokenBudget?: number; byteBudget?: number } = {},
252
264
  ): JsonObject[] {
253
- const tokenBudget = options.tokenBudget ?? REPLACEMENT_TOKEN_BUDGET;
254
- const byteBudget = options.byteBudget ?? REPLACEMENT_BYTE_BUDGET;
255
- const opaque = validateCompactionItem(compactionItem);
256
- let remainingBytes = byteBudget - serializedBytes(opaque);
257
- let remainingChars = tokenBudget * 4;
258
- if (remainingBytes <= 0)
259
- throw new Error("Opaque compaction item exceeds replacement history budget");
260
- const retainedNewestFirst: JsonObject[] = [];
261
- const candidates = input.filter(
262
- (item): item is JsonObject =>
263
- isObject(item) && item.role === "user" && item.type !== "compaction_trigger",
264
- );
265
- for (let index = candidates.length - 1; index >= 0; index--) {
266
- const candidate = candidates[index];
267
- const bytes = serializedBytes(candidate);
268
- if (hasMedia(candidate) && bytes > MAX_MEDIA_ITEM_BYTES) continue;
269
- const text = rawText(candidate);
270
- let retained = candidate;
271
- if (text.length > remainingChars) {
272
- if (hasMedia(candidate)) continue;
273
- const truncated = truncateTextItem(candidate, remainingChars);
274
- if (!truncated) continue;
275
- retained = truncated;
276
- }
277
- if (serializedBytes(retained) > remainingBytes) {
278
- if (hasMedia(retained)) continue;
279
- const maxCharsByBytes = Math.max(0, remainingBytes - 128);
280
- const truncated = truncateTextItem(retained, Math.min(remainingChars, maxCharsByBytes));
281
- if (!truncated || serializedBytes(truncated) > remainingBytes) continue;
282
- retained = truncated;
283
- }
284
- retainedNewestFirst.push(structuredClone(retained));
285
- remainingBytes -= serializedBytes(retained);
286
- remainingChars -= Math.min(remainingChars, rawText(retained).length);
287
- if (remainingBytes <= 128 || remainingChars <= 32) break;
288
- }
289
- return [...retainedNewestFirst.reverse(), opaque];
265
+ const tokenBudget = options.tokenBudget ?? REPLACEMENT_TOKEN_BUDGET;
266
+ const byteBudget = options.byteBudget ?? REPLACEMENT_BYTE_BUDGET;
267
+ const opaque = validateCompactionItem(compactionItem);
268
+ let remainingBytes = byteBudget - serializedBytes(opaque);
269
+ let remainingChars = tokenBudget * 4;
270
+ if (remainingBytes <= 0) throw new Error("Opaque compaction item exceeds replacement history budget");
271
+ const retainedNewestFirst: JsonObject[] = [];
272
+ const candidates = input.filter(
273
+ (item): item is JsonObject => isObject(item) && item.role === "user" && item.type !== "compaction_trigger",
274
+ );
275
+ for (let index = candidates.length - 1; index >= 0; index--) {
276
+ const candidate = candidates[index];
277
+ const bytes = serializedBytes(candidate);
278
+ if (hasMedia(candidate) && bytes > MAX_MEDIA_ITEM_BYTES) continue;
279
+ const text = rawText(candidate);
280
+ let retained = candidate;
281
+ if (text.length > remainingChars) {
282
+ if (hasMedia(candidate)) continue;
283
+ const truncated = truncateTextItem(candidate, remainingChars);
284
+ if (!truncated) continue;
285
+ retained = truncated;
286
+ }
287
+ if (serializedBytes(retained) > remainingBytes) {
288
+ if (hasMedia(retained)) continue;
289
+ const maxCharsByBytes = Math.max(0, remainingBytes - 128);
290
+ const truncated = truncateTextItem(retained, Math.min(remainingChars, maxCharsByBytes));
291
+ if (!truncated || serializedBytes(truncated) > remainingBytes) continue;
292
+ retained = truncated;
293
+ }
294
+ retainedNewestFirst.push(structuredClone(retained));
295
+ remainingBytes -= serializedBytes(retained);
296
+ remainingChars -= Math.min(remainingChars, rawText(retained).length);
297
+ if (remainingBytes <= 128 || remainingChars <= 32) break;
298
+ }
299
+ return [...retainedNewestFirst.reverse(), opaque];
290
300
  }
291
301
 
292
302
  export function createCheckpointDetails(input: {
293
- provider: string;
294
- api: ResponsesCompactionApi;
295
- modelId: string;
296
- protocol: RemoteCompactionProtocol;
297
- replacementHistory: JsonObject[];
298
- keptMessages: readonly AgentMessage[];
299
- checkpointId?: string;
300
- createdAt?: string;
303
+ provider: string;
304
+ api: Api;
305
+ profile: ResponsesCompactionProfile;
306
+ modelId: string;
307
+ protocol: RemoteCompactionProtocol;
308
+ replacementHistory: JsonObject[];
309
+ keptMessages: readonly AgentMessage[];
310
+ checkpointId?: string;
311
+ createdAt?: string;
301
312
  }): CodexCheckpointDetails {
302
- const details: CodexCheckpointDetails = {
303
- kind: CHECKPOINT_KIND,
304
- version: CHECKPOINT_VERSION,
305
- checkpointId: input.checkpointId ?? randomUUID(),
306
- provider: input.provider,
307
- api: input.api,
308
- modelId: input.modelId,
309
- protocol: input.protocol,
310
- replacementHistory: structuredClone(input.replacementHistory),
311
- keptMessageFingerprints: input.keptMessages.map(fingerprintMessage),
312
- createdAt: input.createdAt ?? new Date().toISOString(),
313
- };
314
- const parsed = parseCheckpointDetails(details);
315
- if (!parsed) throw new Error("Created an invalid Codex checkpoint");
316
- return parsed;
313
+ const details: CodexCheckpointDetails = {
314
+ kind: CHECKPOINT_KIND,
315
+ version: CHECKPOINT_VERSION,
316
+ checkpointId: input.checkpointId ?? randomUUID(),
317
+ provider: input.provider,
318
+ api: input.api,
319
+ profile: input.profile,
320
+ modelId: input.modelId,
321
+ protocol: input.protocol,
322
+ replacementHistory: structuredClone(input.replacementHistory),
323
+ keptMessageFingerprints: input.keptMessages.map(fingerprintMessage),
324
+ createdAt: input.createdAt ?? new Date().toISOString(),
325
+ };
326
+ const parsed = parseCheckpointDetails(details);
327
+ if (!parsed) throw new Error("Created an invalid Codex checkpoint");
328
+ return parsed;
317
329
  }