@narumitw/pi-codex-compact 0.51.3 → 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/README.md +90 -90
- package/dist/chunks/chunk-7H7JJ7ZV.js +37 -0
- package/dist/chunks/chunk-7H7JJ7ZV.js.map +7 -0
- package/dist/chunks/{settings-menu-PEDLURUH.js → settings-menu-TVWWDPBF.js} +52 -35
- package/dist/chunks/settings-menu-TVWWDPBF.js.map +7 -0
- package/dist/index.ts +541 -101
- package/dist/index.ts.map +4 -4
- package/package.json +53 -57
- package/src/checkpoint.ts +265 -218
- package/src/codex-compact.ts +252 -229
- package/src/model-api.ts +56 -5
- package/src/protocol.ts +318 -179
- package/src/remote-compact.ts +276 -0
- package/src/remote-shared.ts +16 -0
- package/src/remote-types.ts +46 -0
- package/src/remote-v2.ts +66 -0
- package/src/remote.ts +14 -116
- package/src/settings-menu.ts +213 -201
- package/src/settings.ts +228 -176
- package/src/terminal.ts +6 -0
- package/dist/chunks/chunk-6TZ2L2BM.js +0 -13
- package/dist/chunks/chunk-6TZ2L2BM.js.map +0 -7
- package/dist/chunks/settings-menu-PEDLURUH.js.map +0 -7
package/src/checkpoint.ts
CHANGED
|
@@ -1,282 +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";
|
|
5
|
+
import type { RemoteCompactionProtocol, ResponsesCompactionProfile } from "./model-api.js";
|
|
4
6
|
import { type JsonObject, validateCompactionItem } from "./protocol.js";
|
|
5
7
|
|
|
6
8
|
export const CHECKPOINT_KIND = "pi-codex-remote-compaction";
|
|
7
|
-
export const CHECKPOINT_VERSION =
|
|
9
|
+
export const CHECKPOINT_VERSION = 3;
|
|
8
10
|
export const REPLACEMENT_TOKEN_BUDGET = 64_000;
|
|
9
11
|
export const REPLACEMENT_BYTE_BUDGET = 8 * 1024 * 1024;
|
|
10
12
|
const MAX_MEDIA_ITEM_BYTES = 2 * 1024 * 1024;
|
|
13
|
+
const MAX_CHECKPOINT_DETAILS_BYTES = 10 * 1024 * 1024;
|
|
14
|
+
const MAX_CHECKPOINT_ID_LENGTH = 128;
|
|
11
15
|
const MAX_PROVIDER_ID_LENGTH = 256;
|
|
16
|
+
const MAX_API_ID_LENGTH = 256;
|
|
17
|
+
const MAX_MODEL_ID_LENGTH = 512;
|
|
18
|
+
const MAX_KEPT_FINGERPRINTS = 100_000;
|
|
12
19
|
|
|
13
20
|
export interface CodexCheckpointDetails {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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;
|
|
24
32
|
}
|
|
25
33
|
|
|
26
34
|
function isObject(value: unknown): value is JsonObject {
|
|
27
|
-
|
|
35
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
28
36
|
}
|
|
29
37
|
|
|
30
38
|
function stableValue(value: unknown): unknown {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
+
);
|
|
38
46
|
}
|
|
39
47
|
|
|
40
48
|
function serializedBytes(value: unknown): number {
|
|
41
|
-
|
|
49
|
+
return Buffer.byteLength(JSON.stringify(value), "utf8");
|
|
42
50
|
}
|
|
43
51
|
|
|
44
52
|
export function fingerprintMessage(message: AgentMessage): string {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
53
|
+
return createHash("sha256")
|
|
54
|
+
.update(JSON.stringify(stableValue(message)))
|
|
55
|
+
.digest("hex");
|
|
48
56
|
}
|
|
49
57
|
|
|
50
58
|
export function checkpointMarker(checkpointId: string): string {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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(" ");
|
|
56
64
|
}
|
|
57
65
|
|
|
58
66
|
export function fallbackSummary(checkpointId: string): string {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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(" ");
|
|
64
72
|
}
|
|
65
73
|
|
|
66
74
|
function markerMessage(checkpointId: string, timestamp: number): AgentMessage {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
75
|
+
return {
|
|
76
|
+
role: "user",
|
|
77
|
+
content: [{ type: "text", text: checkpointMarker(checkpointId) }],
|
|
78
|
+
timestamp,
|
|
79
|
+
};
|
|
72
80
|
}
|
|
73
81
|
|
|
74
82
|
export function parseCheckpointDetails(value: unknown): CodexCheckpointDetails | undefined {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
+
};
|
|
110
170
|
}
|
|
111
171
|
|
|
112
172
|
export function latestCheckpoint(entries: readonly SessionEntry[]):
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
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;
|
|
127
185
|
}
|
|
128
186
|
|
|
129
187
|
function isOlderCompactionSummary(message: AgentMessage, timestamp: number): boolean {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
188
|
+
return (
|
|
189
|
+
message.role === "compactionSummary" &&
|
|
190
|
+
Number.isFinite(message.timestamp) &&
|
|
191
|
+
Number.isFinite(timestamp) &&
|
|
192
|
+
message.timestamp < timestamp
|
|
193
|
+
);
|
|
136
194
|
}
|
|
137
195
|
|
|
138
196
|
export function projectCheckpointContext(
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
197
|
+
messages: readonly AgentMessage[],
|
|
198
|
+
details: CodexCheckpointDetails,
|
|
199
|
+
checkpointSummary: string,
|
|
142
200
|
): AgentMessage[] | undefined {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
markerMessage(details.checkpointId, timestamp),
|
|
173
|
-
...messages.slice(messageIndex),
|
|
174
|
-
];
|
|
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
|
+
];
|
|
175
230
|
}
|
|
176
231
|
|
|
177
232
|
function rawText(item: JsonObject): string {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
)
|
|
185
|
-
.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");
|
|
186
239
|
}
|
|
187
240
|
|
|
188
241
|
function hasMedia(item: JsonObject): boolean {
|
|
189
|
-
|
|
190
|
-
Array.isArray(item.content) &&
|
|
191
|
-
item.content.some((part) => isObject(part) && part.type === "input_image")
|
|
192
|
-
);
|
|
242
|
+
return Array.isArray(item.content) && item.content.some((part) => isObject(part) && part.type === "input_image");
|
|
193
243
|
}
|
|
194
244
|
|
|
195
245
|
function truncateTextItem(item: JsonObject, maxChars: number): JsonObject | undefined {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
remaining -= text.length;
|
|
209
|
-
return [{ ...part, text: `[truncated]\n${text}` }];
|
|
210
|
-
});
|
|
211
|
-
if (content.length === 0) return undefined;
|
|
212
|
-
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() };
|
|
213
258
|
}
|
|
214
259
|
|
|
215
260
|
export function buildReplacementHistory(
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
261
|
+
input: readonly unknown[],
|
|
262
|
+
compactionItem: JsonObject,
|
|
263
|
+
options: { tokenBudget?: number; byteBudget?: number } = {},
|
|
219
264
|
): JsonObject[] {
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
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];
|
|
257
300
|
}
|
|
258
301
|
|
|
259
302
|
export function createCheckpointDetails(input: {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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;
|
|
266
312
|
}): CodexCheckpointDetails {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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;
|
|
282
329
|
}
|