@nijaru/pi-provider-compaction 0.0.3 → 0.0.4
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 +55 -41
- package/dist/index.js +991 -591
- package/index.ts +279 -456
- package/package.json +9 -8
- package/protocol.ts +50 -12
- package/provider.ts +85 -0
- package/replay.ts +140 -0
package/dist/index.js
CHANGED
|
@@ -1,22 +1,239 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
// index.ts
|
|
2
|
+
import { createHash, createHmac, randomBytes, randomUUID } from "node:crypto";
|
|
3
|
+
import { normalizeContext as normalizeContext2 } from "@earendil-works/pi-ai";
|
|
4
|
+
import { compact, convertToLlm } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
|
|
6
|
+
// policy.ts
|
|
7
|
+
import {
|
|
8
|
+
CONFIG_DIR_NAME,
|
|
9
|
+
getAgentDir,
|
|
10
|
+
hasTrustRequiringProjectResources,
|
|
11
|
+
ProjectTrustStore
|
|
12
|
+
} from "@earendil-works/pi-coding-agent";
|
|
13
|
+
import { closeSync, constants as fsConstants, fstatSync, openSync, readSync } from "node:fs";
|
|
14
|
+
import { join, resolve, sep } from "node:path";
|
|
15
|
+
var COMPACTION_MODEL_FLAG = "compaction-model";
|
|
16
|
+
var PROJECT_POLICY = "compaction-policy.json";
|
|
17
|
+
var AGENT_DIR_OVERRIDE = "PI_COMPACTOR_AGENT_DIR";
|
|
18
|
+
var MAX_POLICY_BYTES = 64 * 1024;
|
|
19
|
+
var MAX_MODEL_SELECTORS = 8;
|
|
20
|
+
var MAX_SELECTOR_LENGTH = 512;
|
|
21
|
+
function normalizeSelector(value) {
|
|
22
|
+
if (typeof value !== "string")
|
|
23
|
+
return;
|
|
24
|
+
const selector = value.trim();
|
|
25
|
+
if (selector.length === 0 || selector.length > MAX_SELECTOR_LENGTH)
|
|
26
|
+
return;
|
|
27
|
+
return selector;
|
|
28
|
+
}
|
|
29
|
+
function parsePolicySelectors(value) {
|
|
30
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
31
|
+
return;
|
|
32
|
+
if (!("models" in value) || !Array.isArray(value.models))
|
|
33
|
+
return;
|
|
34
|
+
const selectors = [];
|
|
35
|
+
for (const model of value.models) {
|
|
36
|
+
const selector = normalizeSelector(model);
|
|
37
|
+
if (selector === undefined || selectors.includes(selector))
|
|
38
|
+
continue;
|
|
39
|
+
selectors.push(selector);
|
|
40
|
+
if (selectors.length === MAX_MODEL_SELECTORS)
|
|
41
|
+
break;
|
|
9
42
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
43
|
+
return selectors;
|
|
44
|
+
}
|
|
45
|
+
function readPolicySelectors(configPath) {
|
|
46
|
+
let fd;
|
|
47
|
+
try {
|
|
48
|
+
fd = openSync(configPath, fsConstants.O_RDONLY | fsConstants.O_NONBLOCK);
|
|
49
|
+
const stat = fstatSync(fd);
|
|
50
|
+
if (!stat.isFile() || stat.size > MAX_POLICY_BYTES)
|
|
51
|
+
return;
|
|
52
|
+
const buffer = Buffer.allocUnsafe(MAX_POLICY_BYTES + 1);
|
|
53
|
+
let bytesRead = 0;
|
|
54
|
+
while (bytesRead < buffer.length) {
|
|
55
|
+
const count = readSync(fd, buffer, bytesRead, buffer.length - bytesRead, null);
|
|
56
|
+
if (count === 0)
|
|
57
|
+
break;
|
|
58
|
+
bytesRead += count;
|
|
59
|
+
}
|
|
60
|
+
if (bytesRead > MAX_POLICY_BYTES)
|
|
61
|
+
return;
|
|
62
|
+
return parsePolicySelectors(JSON.parse(buffer.subarray(0, bytesRead).toString("utf8")));
|
|
63
|
+
} catch {
|
|
64
|
+
return;
|
|
65
|
+
} finally {
|
|
66
|
+
if (fd !== undefined) {
|
|
67
|
+
try {
|
|
68
|
+
closeSync(fd);
|
|
69
|
+
} catch {}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function activeAgentDir(ctx) {
|
|
74
|
+
const override = process.env[AGENT_DIR_OVERRIDE]?.trim();
|
|
75
|
+
if (override)
|
|
76
|
+
return resolve(override);
|
|
77
|
+
try {
|
|
78
|
+
const sessionDir = ctx.sessionManager?.getSessionDir();
|
|
79
|
+
if (sessionDir) {
|
|
80
|
+
const resolvedCwd = resolve(ctx.cwd);
|
|
81
|
+
const safePath = `--${resolvedCwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
|
82
|
+
const suffix = `${sep}${join("sessions", safePath)}`;
|
|
83
|
+
const resolvedSessionDir = resolve(sessionDir);
|
|
84
|
+
if (resolvedSessionDir.endsWith(suffix)) {
|
|
85
|
+
return resolvedSessionDir.slice(0, -suffix.length);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} catch {}
|
|
89
|
+
return getAgentDir();
|
|
90
|
+
}
|
|
91
|
+
function isProjectPolicyTrusted(ctx, agentDir) {
|
|
92
|
+
try {
|
|
93
|
+
if (!ctx.isProjectTrusted())
|
|
94
|
+
return false;
|
|
95
|
+
return hasTrustRequiringProjectResources(ctx.cwd) || new ProjectTrustStore(agentDir).get(ctx.cwd) === true;
|
|
96
|
+
} catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function readCompactionModelFlagFromArgv(argv = process.argv.slice(2)) {
|
|
101
|
+
let value;
|
|
102
|
+
const prefix = `--${COMPACTION_MODEL_FLAG}=`;
|
|
103
|
+
for (let i = 0;i < argv.length; i++) {
|
|
104
|
+
const arg = argv[i];
|
|
105
|
+
if (arg === "--")
|
|
106
|
+
break;
|
|
107
|
+
if (arg === `--${COMPACTION_MODEL_FLAG}`) {
|
|
108
|
+
const next = argv[i + 1];
|
|
109
|
+
if (next !== undefined && !next.startsWith("-") && !next.startsWith("@")) {
|
|
110
|
+
value = next;
|
|
111
|
+
i++;
|
|
112
|
+
} else {
|
|
113
|
+
value = true;
|
|
114
|
+
}
|
|
115
|
+
} else if (arg.startsWith(prefix)) {
|
|
116
|
+
value = arg.slice(prefix.length);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return value;
|
|
120
|
+
}
|
|
121
|
+
function resolveCompactionModelPolicy(pi, ctx, argv = process.argv.slice(2)) {
|
|
122
|
+
const argvRaw = readCompactionModelFlagFromArgv(argv);
|
|
123
|
+
if (typeof argvRaw === "string" && argvRaw.trim()) {
|
|
124
|
+
const selector = normalizeSelector(argvRaw);
|
|
125
|
+
return selector ? { hasSelectors: true, source: "flag" } : { hasSelectors: false };
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const rawFlag = pi.getFlag(COMPACTION_MODEL_FLAG);
|
|
129
|
+
if (typeof rawFlag === "string" && rawFlag.trim()) {
|
|
130
|
+
const selector = normalizeSelector(rawFlag);
|
|
131
|
+
return selector ? { hasSelectors: true, source: "flag" } : { hasSelectors: false };
|
|
132
|
+
}
|
|
133
|
+
} catch {}
|
|
134
|
+
const agentDir = activeAgentDir(ctx);
|
|
135
|
+
const candidates = [];
|
|
136
|
+
if (isProjectPolicyTrusted(ctx, agentDir)) {
|
|
137
|
+
candidates.push({ path: join(ctx.cwd, CONFIG_DIR_NAME, PROJECT_POLICY), source: "project-policy" });
|
|
138
|
+
}
|
|
139
|
+
candidates.push({ path: join(agentDir, PROJECT_POLICY), source: "agent-policy" });
|
|
140
|
+
for (const candidate of candidates) {
|
|
141
|
+
const selectors = readPolicySelectors(candidate.path);
|
|
142
|
+
if (selectors !== undefined) {
|
|
143
|
+
return selectors.length > 0 ? { hasSelectors: true, source: candidate.source } : { hasSelectors: false };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return { hasSelectors: false };
|
|
13
147
|
}
|
|
14
148
|
|
|
15
|
-
//
|
|
16
|
-
function
|
|
17
|
-
|
|
149
|
+
// provider.ts
|
|
150
|
+
function optionsWithObserver(model, context, options, observe) {
|
|
151
|
+
const handler = observe(model, context, options ?? {});
|
|
152
|
+
if (!handler)
|
|
153
|
+
return options;
|
|
154
|
+
return {
|
|
155
|
+
...options,
|
|
156
|
+
onPayload: async (payload, requestModel) => {
|
|
157
|
+
const replacement = await options?.onPayload?.(payload, requestModel);
|
|
158
|
+
const prepared = replacement === undefined ? payload : replacement;
|
|
159
|
+
try {
|
|
160
|
+
return await handler(prepared);
|
|
161
|
+
} catch {
|
|
162
|
+
return prepared;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function decorateProvider(base, observe) {
|
|
168
|
+
const stream = (model, context, options) => base.stream(model, context, optionsWithObserver(model, context, options, observe));
|
|
169
|
+
const streamSimple = (model, context, options) => base.streamSimple(model, context, optionsWithObserver(model, context, options, observe));
|
|
170
|
+
const bound = new Map;
|
|
171
|
+
return new Proxy(base, {
|
|
172
|
+
get(target, key) {
|
|
173
|
+
if (key === "stream")
|
|
174
|
+
return stream;
|
|
175
|
+
if (key === "streamSimple")
|
|
176
|
+
return streamSimple;
|
|
177
|
+
const value = Reflect.get(target, key, target);
|
|
178
|
+
if (typeof value !== "function")
|
|
179
|
+
return value;
|
|
180
|
+
if (!bound.has(key))
|
|
181
|
+
bound.set(key, value.bind(target));
|
|
182
|
+
return bound.get(key);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
function installObserver(pi, ctx, observe) {
|
|
187
|
+
const model = ctx.model;
|
|
188
|
+
if (!model)
|
|
189
|
+
return;
|
|
190
|
+
const registry = ctx.modelRegistry;
|
|
191
|
+
const id = model.provider;
|
|
192
|
+
const native = registry.getRegisteredNativeProvider(id);
|
|
193
|
+
if (native) {
|
|
194
|
+
const guarded = (...args) => {
|
|
195
|
+
const handler = observe(...args);
|
|
196
|
+
return handler ? (payload) => registry.getRegisteredNativeProvider(id) === wrapper ? handler(payload) : payload : undefined;
|
|
197
|
+
};
|
|
198
|
+
const wrapper = decorateProvider(native, guarded);
|
|
199
|
+
pi.registerProvider(wrapper);
|
|
200
|
+
return () => {
|
|
201
|
+
if (registry.getRegisteredNativeProvider(id) === wrapper)
|
|
202
|
+
pi.registerProvider(native);
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
const original = registry.getRegisteredProviderConfig(id);
|
|
206
|
+
if (!original?.streamSimple || original.refreshModels || original.oauth?.modifyModels)
|
|
207
|
+
return;
|
|
208
|
+
if (original.api !== model.api)
|
|
209
|
+
return;
|
|
210
|
+
const delegate = original.streamSimple.bind(original);
|
|
211
|
+
const guarded = (...args) => {
|
|
212
|
+
const handler = observe(...args);
|
|
213
|
+
return handler ? (payload) => registry.getRegisteredProviderConfig(id)?.streamSimple === wrapper ? handler(payload) : payload : undefined;
|
|
214
|
+
};
|
|
215
|
+
const wrapper = (requestModel, context, options) => delegate(requestModel, context, optionsWithObserver(requestModel, context, options, guarded));
|
|
216
|
+
pi.registerProvider(id, { ...original, api: original?.api ?? model.api, streamSimple: wrapper });
|
|
217
|
+
return () => {
|
|
218
|
+
const current = registry.getRegisteredProviderConfig(id);
|
|
219
|
+
if (current?.streamSimple !== wrapper || registry.getRegisteredNativeProvider(id))
|
|
220
|
+
return;
|
|
221
|
+
const restored = { ...current };
|
|
222
|
+
if (original?.streamSimple)
|
|
223
|
+
restored.streamSimple = original.streamSimple;
|
|
224
|
+
else
|
|
225
|
+
delete restored.streamSimple;
|
|
226
|
+
if (!original?.api && restored.api === model.api)
|
|
227
|
+
delete restored.api;
|
|
228
|
+
pi.unregisterProvider(id);
|
|
229
|
+
if (Object.keys(restored).length)
|
|
230
|
+
pi.registerProvider(id, restored);
|
|
231
|
+
};
|
|
18
232
|
}
|
|
19
233
|
|
|
234
|
+
// replay.ts
|
|
235
|
+
import { getCurrentSystemPrompt, getCurrentTools as getCurrentTools2, getDeclaredTools, normalizeContext, resolveTranscript as resolveTranscript2 } from "@earendil-works/pi-ai";
|
|
236
|
+
|
|
20
237
|
// node_modules/@earendil-works/pi-ai/dist/api/constrained-sampling.js
|
|
21
238
|
class UnsupportedStrictJsonSchemaError extends Error {
|
|
22
239
|
}
|
|
@@ -197,6 +414,149 @@ function resolveGrammarConstrainedSampling(tool, supportsOpenAIGrammarTools) {
|
|
|
197
414
|
throw new Error(`Tool "${tool.name}" cannot use grammar constrained sampling: ${message}.`);
|
|
198
415
|
}
|
|
199
416
|
}
|
|
417
|
+
function createGrammarToolInputProperties(tools, supportsOpenAIGrammarTools) {
|
|
418
|
+
const properties = new Map;
|
|
419
|
+
for (const tool of tools ?? []) {
|
|
420
|
+
const grammar = resolveGrammarConstrainedSampling(tool, supportsOpenAIGrammarTools);
|
|
421
|
+
if (grammar) {
|
|
422
|
+
properties.set(tool.name, grammar.inputProperty);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return properties;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// node_modules/@earendil-works/pi-ai/dist/utils/text.js
|
|
429
|
+
function contentText(content, separator = `
|
|
430
|
+
`) {
|
|
431
|
+
if (typeof content === "string")
|
|
432
|
+
return content;
|
|
433
|
+
return content.filter((block) => block.type === "text").map((block) => block.text).join(separator);
|
|
434
|
+
}
|
|
435
|
+
function getSystemMessageText(message) {
|
|
436
|
+
const parts = [contentText(message.content)];
|
|
437
|
+
for (const text of Object.values(message.sections ?? {})) {
|
|
438
|
+
if (text !== null)
|
|
439
|
+
parts.push(text);
|
|
440
|
+
}
|
|
441
|
+
return parts.filter((part) => part.length > 0).join(`
|
|
442
|
+
|
|
443
|
+
`);
|
|
444
|
+
}
|
|
445
|
+
function renderSystemMessageUpdate(message) {
|
|
446
|
+
const parts = [];
|
|
447
|
+
const text = contentText(message.content);
|
|
448
|
+
if (text.length > 0)
|
|
449
|
+
parts.push(text);
|
|
450
|
+
for (const [name, value] of Object.entries(message.sections ?? {})) {
|
|
451
|
+
parts.push(value === null ? `Removed system prompt section "${name}".` : `Updated system prompt section "${name}":
|
|
452
|
+
|
|
453
|
+
${value}`);
|
|
454
|
+
}
|
|
455
|
+
return parts.join(`
|
|
456
|
+
|
|
457
|
+
`);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// node_modules/@earendil-works/pi-ai/dist/utils/transcript.js
|
|
461
|
+
function isSystemMessage(message) {
|
|
462
|
+
return message.role === "system";
|
|
463
|
+
}
|
|
464
|
+
function getInitialSystemMessage(messages) {
|
|
465
|
+
const first = messages[0];
|
|
466
|
+
return first && isSystemMessage(first) ? first : undefined;
|
|
467
|
+
}
|
|
468
|
+
function getCurrentTools(messages) {
|
|
469
|
+
const tools = new Map;
|
|
470
|
+
for (const message of messages) {
|
|
471
|
+
if (!isSystemMessage(message))
|
|
472
|
+
continue;
|
|
473
|
+
for (const tool of message.toolsRemoved ?? [])
|
|
474
|
+
tools.delete(tool.name);
|
|
475
|
+
for (const tool of message.toolsAdded ?? [])
|
|
476
|
+
tools.set(tool.name, tool);
|
|
477
|
+
}
|
|
478
|
+
return [...tools.values()];
|
|
479
|
+
}
|
|
480
|
+
function getCurrentSystemMessage(messages) {
|
|
481
|
+
const content = [];
|
|
482
|
+
const sections = new Map;
|
|
483
|
+
let timestamp;
|
|
484
|
+
for (const message of messages) {
|
|
485
|
+
if (!isSystemMessage(message))
|
|
486
|
+
continue;
|
|
487
|
+
timestamp ??= message.timestamp;
|
|
488
|
+
const text = contentText(message.content);
|
|
489
|
+
if (text.length > 0)
|
|
490
|
+
content.push(text);
|
|
491
|
+
for (const [name, value] of Object.entries(message.sections ?? {})) {
|
|
492
|
+
if (value === null)
|
|
493
|
+
sections.delete(name);
|
|
494
|
+
else
|
|
495
|
+
sections.set(name, value);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
const tools = getCurrentTools(messages);
|
|
499
|
+
if (timestamp === undefined && tools.length === 0)
|
|
500
|
+
return;
|
|
501
|
+
return {
|
|
502
|
+
role: "system",
|
|
503
|
+
content: content.join(`
|
|
504
|
+
|
|
505
|
+
`),
|
|
506
|
+
...sections.size > 0 ? { sections: Object.fromEntries(sections) } : {},
|
|
507
|
+
...tools.length > 0 ? { toolsAdded: tools } : {},
|
|
508
|
+
timestamp: timestamp ?? 0
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
function collapseSystemMessages(context) {
|
|
512
|
+
const head = getCurrentSystemMessage(context.messages);
|
|
513
|
+
const messages = context.messages.filter((message) => message.role !== "system");
|
|
514
|
+
return { messages: head ? [head, ...messages] : messages };
|
|
515
|
+
}
|
|
516
|
+
function resolveTranscript(context, supportsMidConvoSystemMessages) {
|
|
517
|
+
return supportsMidConvoSystemMessages ? context : collapseSystemMessages(context);
|
|
518
|
+
}
|
|
519
|
+
function hasNonAdditiveToolChanges(messages) {
|
|
520
|
+
const declared = new Set;
|
|
521
|
+
for (const message of messages) {
|
|
522
|
+
if (!isSystemMessage(message))
|
|
523
|
+
continue;
|
|
524
|
+
if ((message.toolsRemoved?.length ?? 0) > 0)
|
|
525
|
+
return true;
|
|
526
|
+
for (const tool of message.toolsAdded ?? []) {
|
|
527
|
+
if (declared.has(tool.name))
|
|
528
|
+
return true;
|
|
529
|
+
declared.add(tool.name);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
534
|
+
function resolveTranscriptTools(messages, supportsToolAdditions) {
|
|
535
|
+
const anchorsAdditions = supportsToolAdditions && !hasNonAdditiveToolChanges(messages);
|
|
536
|
+
return {
|
|
537
|
+
requestTools: anchorsAdditions ? getInitialSystemMessage(messages)?.toolsAdded ?? [] : getCurrentTools(messages),
|
|
538
|
+
anchorsAdditions
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// node_modules/@earendil-works/pi-ai/dist/utils/hash.js
|
|
543
|
+
function shortHash(str) {
|
|
544
|
+
let h1 = 3735928559;
|
|
545
|
+
let h2 = 1103547991;
|
|
546
|
+
for (let i = 0;i < str.length; i++) {
|
|
547
|
+
const ch = str.charCodeAt(i);
|
|
548
|
+
h1 = Math.imul(h1 ^ ch, 2654435761);
|
|
549
|
+
h2 = Math.imul(h2 ^ ch, 1597334677);
|
|
550
|
+
}
|
|
551
|
+
h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
|
|
552
|
+
h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
|
|
553
|
+
return (h2 >>> 0).toString(36) + (h1 >>> 0).toString(36);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// node_modules/@earendil-works/pi-ai/dist/utils/sanitize-unicode.js
|
|
557
|
+
function sanitizeSurrogates(text) {
|
|
558
|
+
return text.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "");
|
|
559
|
+
}
|
|
200
560
|
|
|
201
561
|
// node_modules/@earendil-works/pi-ai/dist/api/transform-messages.js
|
|
202
562
|
var NON_VISION_USER_IMAGE_PLACEHOLDER = "(image omitted: model does not support images)";
|
|
@@ -242,7 +602,7 @@ function transformMessages(messages, model, normalizeToolCallId) {
|
|
|
242
602
|
const normalizedMessages = messages.map((msg) => msg.content == null ? { ...msg, content: [] } : msg);
|
|
243
603
|
const imageAwareMessages = downgradeUnsupportedImages(normalizedMessages, model);
|
|
244
604
|
const transformed = imageAwareMessages.map((msg) => {
|
|
245
|
-
if (msg.role === "user") {
|
|
605
|
+
if (msg.role === "system" || msg.role === "user") {
|
|
246
606
|
return msg;
|
|
247
607
|
}
|
|
248
608
|
if (msg.role === "toolResult") {
|
|
@@ -307,7 +667,8 @@ function transformMessages(messages, model, normalizeToolCallId) {
|
|
|
307
667
|
const result = [];
|
|
308
668
|
let pendingToolCalls = [];
|
|
309
669
|
let existingToolResultIds = new Set;
|
|
310
|
-
const
|
|
670
|
+
const heldSystemMessages = [];
|
|
671
|
+
const closePendingToolCalls = () => {
|
|
311
672
|
if (pendingToolCalls.length > 0) {
|
|
312
673
|
for (const tc of pendingToolCalls) {
|
|
313
674
|
if (!existingToolResultIds.has(tc.id)) {
|
|
@@ -324,11 +685,13 @@ function transformMessages(messages, model, normalizeToolCallId) {
|
|
|
324
685
|
pendingToolCalls = [];
|
|
325
686
|
existingToolResultIds = new Set;
|
|
326
687
|
}
|
|
688
|
+
result.push(...heldSystemMessages);
|
|
689
|
+
heldSystemMessages.length = 0;
|
|
327
690
|
};
|
|
328
691
|
for (let i = 0;i < transformed.length; i++) {
|
|
329
692
|
const msg = transformed[i];
|
|
330
693
|
if (msg.role === "assistant") {
|
|
331
|
-
|
|
694
|
+
closePendingToolCalls();
|
|
332
695
|
const assistantMsg = msg;
|
|
333
696
|
if (assistantMsg.stopReason === "error" || assistantMsg.stopReason === "aborted") {
|
|
334
697
|
continue;
|
|
@@ -342,14 +705,20 @@ function transformMessages(messages, model, normalizeToolCallId) {
|
|
|
342
705
|
} else if (msg.role === "toolResult") {
|
|
343
706
|
existingToolResultIds.add(msg.toolCallId);
|
|
344
707
|
result.push(msg);
|
|
708
|
+
} else if (msg.role === "system") {
|
|
709
|
+
if (pendingToolCalls.length > 0) {
|
|
710
|
+
heldSystemMessages.push(msg);
|
|
711
|
+
} else {
|
|
712
|
+
result.push(msg);
|
|
713
|
+
}
|
|
345
714
|
} else if (msg.role === "user") {
|
|
346
|
-
|
|
715
|
+
closePendingToolCalls();
|
|
347
716
|
result.push(msg);
|
|
348
717
|
} else {
|
|
349
718
|
result.push(msg);
|
|
350
719
|
}
|
|
351
720
|
}
|
|
352
|
-
|
|
721
|
+
closePendingToolCalls();
|
|
353
722
|
return result;
|
|
354
723
|
}
|
|
355
724
|
|
|
@@ -392,8 +761,8 @@ function convertToolResultOutput(model, content) {
|
|
|
392
761
|
return output;
|
|
393
762
|
}
|
|
394
763
|
function convertResponsesMessages(model, context, allowedToolCallProviders, options) {
|
|
764
|
+
const normalizedContext = resolveTranscript(context, options?.supportsMidConvoSystemMessages);
|
|
395
765
|
const messages = [];
|
|
396
|
-
const loadedToolNames = new Set;
|
|
397
766
|
const normalizeIdPart = (part) => {
|
|
398
767
|
const sanitized = part.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
399
768
|
const normalized = sanitized.length > 64 ? sanitized.slice(0, 64) : sanitized;
|
|
@@ -417,19 +786,56 @@ function convertResponsesMessages(model, context, allowedToolCallProviders, opti
|
|
|
417
786
|
}
|
|
418
787
|
return `${normalizedCallId}|${normalizedItemId}`;
|
|
419
788
|
};
|
|
420
|
-
const transformedMessages = transformMessages(
|
|
421
|
-
const
|
|
422
|
-
|
|
423
|
-
const
|
|
424
|
-
|
|
789
|
+
const transformedMessages = transformMessages(normalizedContext.messages, model, normalizeToolCallId);
|
|
790
|
+
const transcriptTools = resolveTranscriptTools(normalizedContext.messages, (options?.supportsAdditionalTools ?? false) || (options?.supportsToolSearch ?? false));
|
|
791
|
+
const appendSystemToolAdditions = (message, seed) => {
|
|
792
|
+
const tools = transcriptTools.anchorsAdditions ? message.toolsAdded ?? [] : [];
|
|
793
|
+
if (tools.length === 0)
|
|
794
|
+
return;
|
|
795
|
+
if (options?.supportsAdditionalTools) {
|
|
796
|
+
messages.push({
|
|
797
|
+
type: "additional_tools",
|
|
798
|
+
role: "developer",
|
|
799
|
+
tools: convertResponsesTools(tools, options.toolOptions)
|
|
800
|
+
});
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
if (!options?.supportsToolSearch)
|
|
804
|
+
return;
|
|
805
|
+
const names = tools.map((tool) => tool.name);
|
|
806
|
+
const callId = `pi_tool_load_${shortHash(`${seed}:${names.join(",")}`)}`;
|
|
425
807
|
messages.push({
|
|
426
|
-
|
|
427
|
-
|
|
808
|
+
type: "tool_search_call",
|
|
809
|
+
call_id: callId,
|
|
810
|
+
execution: "client",
|
|
811
|
+
status: "completed",
|
|
812
|
+
arguments: { query: names.join(" "), limit: names.length }
|
|
428
813
|
});
|
|
429
|
-
|
|
814
|
+
messages.push({
|
|
815
|
+
type: "tool_search_output",
|
|
816
|
+
call_id: callId,
|
|
817
|
+
execution: "client",
|
|
818
|
+
status: "completed",
|
|
819
|
+
tools: convertResponsesTools(tools, { ...options.toolOptions, toolSearchResult: true })
|
|
820
|
+
});
|
|
821
|
+
};
|
|
822
|
+
const includeInitialSystemMessage = options?.includeSystemPrompt ?? true;
|
|
823
|
+
const compat = model.compat;
|
|
824
|
+
const instructionRole = model.reasoning && compat?.supportsDeveloperRole !== false ? "developer" : "system";
|
|
430
825
|
let msgIndex = 0;
|
|
826
|
+
let sourceIndex = 0;
|
|
431
827
|
for (const msg of transformedMessages) {
|
|
432
|
-
|
|
828
|
+
const isLeadingSystemMessage = sourceIndex++ === 0 && msg.role === "system";
|
|
829
|
+
if (msg.role === "system") {
|
|
830
|
+
if (!isLeadingSystemMessage)
|
|
831
|
+
appendSystemToolAdditions(msg, `system:${msgIndex}`);
|
|
832
|
+
if (!isLeadingSystemMessage || includeInitialSystemMessage) {
|
|
833
|
+
const text = isLeadingSystemMessage ? getSystemMessageText(msg) : renderSystemMessageUpdate(msg);
|
|
834
|
+
if (text.length > 0) {
|
|
835
|
+
messages.push({ role: instructionRole, content: sanitizeSurrogates(text) });
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
} else if (msg.role === "user") {
|
|
433
839
|
if (typeof msg.content === "string") {
|
|
434
840
|
messages.push({
|
|
435
841
|
role: "user",
|
|
@@ -496,7 +902,6 @@ function convertResponsesMessages(model, context, allowedToolCallProviders, opti
|
|
|
496
902
|
if (isDifferentModel && itemId?.startsWith("fc_") || customInputProperty === undefined && !itemId?.startsWith("fc_")) {
|
|
497
903
|
itemId = undefined;
|
|
498
904
|
}
|
|
499
|
-
const canReplayNamespace = isSameModel || options?.deferredTools?.has(toolCall.name) === true;
|
|
500
905
|
if (customInputProperty !== undefined) {
|
|
501
906
|
output.push({
|
|
502
907
|
type: "custom_tool_call",
|
|
@@ -504,7 +909,7 @@ function convertResponsesMessages(model, context, allowedToolCallProviders, opti
|
|
|
504
909
|
call_id: callId,
|
|
505
910
|
name: toolCall.name,
|
|
506
911
|
input: sanitizeSurrogates(getGrammarToolInput(toolCall.name, toolCall.arguments, customInputProperty)),
|
|
507
|
-
...
|
|
912
|
+
...isSameModel && toolCall.namespace !== undefined ? { namespace: toolCall.namespace } : {}
|
|
508
913
|
});
|
|
509
914
|
} else {
|
|
510
915
|
output.push({
|
|
@@ -513,7 +918,7 @@ function convertResponsesMessages(model, context, allowedToolCallProviders, opti
|
|
|
513
918
|
call_id: callId,
|
|
514
919
|
name: toolCall.name,
|
|
515
920
|
arguments: JSON.stringify(toolCall.arguments),
|
|
516
|
-
...
|
|
921
|
+
...isSameModel && toolCall.namespace !== undefined ? { namespace: toolCall.namespace } : {}
|
|
517
922
|
});
|
|
518
923
|
}
|
|
519
924
|
}
|
|
@@ -537,43 +942,9 @@ function convertResponsesMessages(model, context, allowedToolCallProviders, opti
|
|
|
537
942
|
output
|
|
538
943
|
});
|
|
539
944
|
}
|
|
540
|
-
const deferredTools = [];
|
|
541
|
-
for (const name of msg.addedToolNames ?? []) {
|
|
542
|
-
const tool = options?.deferredTools?.get(name);
|
|
543
|
-
if (!tool || loadedToolNames.has(name))
|
|
544
|
-
continue;
|
|
545
|
-
loadedToolNames.add(name);
|
|
546
|
-
deferredTools.push(tool);
|
|
547
|
-
}
|
|
548
|
-
if (deferredTools.length > 0 && options?.deferredToolsMode === "additional-tools") {
|
|
549
|
-
messages.push({
|
|
550
|
-
type: "additional_tools",
|
|
551
|
-
role: "developer",
|
|
552
|
-
tools: convertResponsesTools(deferredTools, options.toolOptions)
|
|
553
|
-
});
|
|
554
|
-
} else if (deferredTools.length > 0 && options?.deferredToolsMode === "tool-search") {
|
|
555
|
-
const names = deferredTools.map((tool) => tool.name);
|
|
556
|
-
const searchCallId = `pi_tool_load_${shortHash(`${msg.toolCallId}:${names.join(",")}`)}`;
|
|
557
|
-
messages.push({
|
|
558
|
-
type: "tool_search_call",
|
|
559
|
-
call_id: searchCallId,
|
|
560
|
-
execution: "client",
|
|
561
|
-
status: "completed",
|
|
562
|
-
arguments: { query: names.join(" "), limit: names.length }
|
|
563
|
-
});
|
|
564
|
-
messages.push({
|
|
565
|
-
type: "tool_search_output",
|
|
566
|
-
call_id: searchCallId,
|
|
567
|
-
execution: "client",
|
|
568
|
-
status: "completed",
|
|
569
|
-
tools: convertResponsesTools(deferredTools, {
|
|
570
|
-
...options.toolOptions,
|
|
571
|
-
deferLoading: true
|
|
572
|
-
})
|
|
573
|
-
});
|
|
574
|
-
}
|
|
575
945
|
}
|
|
576
|
-
|
|
946
|
+
if (!isLeadingSystemMessage)
|
|
947
|
+
msgIndex++;
|
|
577
948
|
}
|
|
578
949
|
return messages;
|
|
579
950
|
}
|
|
@@ -593,7 +964,7 @@ function convertResponsesTools(tools, options) {
|
|
|
593
964
|
syntax: grammar.format,
|
|
594
965
|
definition: grammar.definition
|
|
595
966
|
},
|
|
596
|
-
...options?.
|
|
967
|
+
...options?.toolSearchResult ? { defer_loading: true } : {}
|
|
597
968
|
};
|
|
598
969
|
}
|
|
599
970
|
const constrainedStrict = resolveJsonSchemaStrictSampling(tool, supportsStrictMode);
|
|
@@ -603,7 +974,7 @@ function convertResponsesTools(tools, options) {
|
|
|
603
974
|
name: tool.name,
|
|
604
975
|
description: tool.description,
|
|
605
976
|
parameters: getJsonSchemaToolParameters(tool, strict === true),
|
|
606
|
-
...options?.
|
|
977
|
+
...options?.toolSearchResult ? { defer_loading: true } : {}
|
|
607
978
|
};
|
|
608
979
|
if (supportsStrictMode) {
|
|
609
980
|
functionTool.strict = strict;
|
|
@@ -612,157 +983,6 @@ function convertResponsesTools(tools, options) {
|
|
|
612
983
|
});
|
|
613
984
|
}
|
|
614
985
|
|
|
615
|
-
// index.ts
|
|
616
|
-
import { calculateCost } from "@earendil-works/pi-ai";
|
|
617
|
-
import {
|
|
618
|
-
compact,
|
|
619
|
-
convertToLlm,
|
|
620
|
-
sessionEntryToContextMessages
|
|
621
|
-
} from "@earendil-works/pi-coding-agent";
|
|
622
|
-
|
|
623
|
-
// policy.ts
|
|
624
|
-
import {
|
|
625
|
-
CONFIG_DIR_NAME,
|
|
626
|
-
getAgentDir,
|
|
627
|
-
hasTrustRequiringProjectResources,
|
|
628
|
-
ProjectTrustStore
|
|
629
|
-
} from "@earendil-works/pi-coding-agent";
|
|
630
|
-
import { closeSync, constants as fsConstants, fstatSync, openSync, readSync } from "node:fs";
|
|
631
|
-
import { join, resolve, sep } from "node:path";
|
|
632
|
-
var COMPACTION_MODEL_FLAG = "compaction-model";
|
|
633
|
-
var PROJECT_POLICY = "compaction-policy.json";
|
|
634
|
-
var AGENT_DIR_OVERRIDE = "PI_COMPACTOR_AGENT_DIR";
|
|
635
|
-
var MAX_POLICY_BYTES = 64 * 1024;
|
|
636
|
-
var MAX_MODEL_SELECTORS = 8;
|
|
637
|
-
var MAX_SELECTOR_LENGTH = 512;
|
|
638
|
-
function normalizeSelector(value) {
|
|
639
|
-
if (typeof value !== "string")
|
|
640
|
-
return;
|
|
641
|
-
const selector = value.trim();
|
|
642
|
-
if (selector.length === 0 || selector.length > MAX_SELECTOR_LENGTH)
|
|
643
|
-
return;
|
|
644
|
-
return selector;
|
|
645
|
-
}
|
|
646
|
-
function parsePolicySelectors(value) {
|
|
647
|
-
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
648
|
-
return;
|
|
649
|
-
if (!("models" in value) || !Array.isArray(value.models))
|
|
650
|
-
return;
|
|
651
|
-
const selectors = [];
|
|
652
|
-
for (const model of value.models) {
|
|
653
|
-
const selector = normalizeSelector(model);
|
|
654
|
-
if (selector === undefined || selectors.includes(selector))
|
|
655
|
-
continue;
|
|
656
|
-
selectors.push(selector);
|
|
657
|
-
if (selectors.length === MAX_MODEL_SELECTORS)
|
|
658
|
-
break;
|
|
659
|
-
}
|
|
660
|
-
return selectors;
|
|
661
|
-
}
|
|
662
|
-
function readPolicySelectors(configPath) {
|
|
663
|
-
let fd;
|
|
664
|
-
try {
|
|
665
|
-
fd = openSync(configPath, fsConstants.O_RDONLY | fsConstants.O_NONBLOCK);
|
|
666
|
-
const stat = fstatSync(fd);
|
|
667
|
-
if (!stat.isFile() || stat.size > MAX_POLICY_BYTES)
|
|
668
|
-
return;
|
|
669
|
-
const buffer = Buffer.allocUnsafe(MAX_POLICY_BYTES + 1);
|
|
670
|
-
let bytesRead = 0;
|
|
671
|
-
while (bytesRead < buffer.length) {
|
|
672
|
-
const count = readSync(fd, buffer, bytesRead, buffer.length - bytesRead, null);
|
|
673
|
-
if (count === 0)
|
|
674
|
-
break;
|
|
675
|
-
bytesRead += count;
|
|
676
|
-
}
|
|
677
|
-
if (bytesRead > MAX_POLICY_BYTES)
|
|
678
|
-
return;
|
|
679
|
-
return parsePolicySelectors(JSON.parse(buffer.subarray(0, bytesRead).toString("utf8")));
|
|
680
|
-
} catch {
|
|
681
|
-
return;
|
|
682
|
-
} finally {
|
|
683
|
-
if (fd !== undefined) {
|
|
684
|
-
try {
|
|
685
|
-
closeSync(fd);
|
|
686
|
-
} catch {}
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
function activeAgentDir(ctx) {
|
|
691
|
-
const override = process.env[AGENT_DIR_OVERRIDE]?.trim();
|
|
692
|
-
if (override)
|
|
693
|
-
return resolve(override);
|
|
694
|
-
try {
|
|
695
|
-
const sessionDir = ctx.sessionManager?.getSessionDir();
|
|
696
|
-
if (sessionDir) {
|
|
697
|
-
const resolvedCwd = resolve(ctx.cwd);
|
|
698
|
-
const safePath = `--${resolvedCwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
|
699
|
-
const suffix = `${sep}${join("sessions", safePath)}`;
|
|
700
|
-
const resolvedSessionDir = resolve(sessionDir);
|
|
701
|
-
if (resolvedSessionDir.endsWith(suffix)) {
|
|
702
|
-
return resolvedSessionDir.slice(0, -suffix.length);
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
} catch {}
|
|
706
|
-
return getAgentDir();
|
|
707
|
-
}
|
|
708
|
-
function isProjectPolicyTrusted(ctx, agentDir) {
|
|
709
|
-
try {
|
|
710
|
-
if (!ctx.isProjectTrusted())
|
|
711
|
-
return false;
|
|
712
|
-
return hasTrustRequiringProjectResources(ctx.cwd) || new ProjectTrustStore(agentDir).get(ctx.cwd) === true;
|
|
713
|
-
} catch {
|
|
714
|
-
return false;
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
function readCompactionModelFlagFromArgv(argv = process.argv.slice(2)) {
|
|
718
|
-
let value;
|
|
719
|
-
const prefix = `--${COMPACTION_MODEL_FLAG}=`;
|
|
720
|
-
for (let i = 0;i < argv.length; i++) {
|
|
721
|
-
const arg = argv[i];
|
|
722
|
-
if (arg === "--")
|
|
723
|
-
break;
|
|
724
|
-
if (arg === `--${COMPACTION_MODEL_FLAG}`) {
|
|
725
|
-
const next = argv[i + 1];
|
|
726
|
-
if (next !== undefined && !next.startsWith("-") && !next.startsWith("@")) {
|
|
727
|
-
value = next;
|
|
728
|
-
i++;
|
|
729
|
-
} else {
|
|
730
|
-
value = true;
|
|
731
|
-
}
|
|
732
|
-
} else if (arg.startsWith(prefix)) {
|
|
733
|
-
value = arg.slice(prefix.length);
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
return value;
|
|
737
|
-
}
|
|
738
|
-
function resolveCompactionModelPolicy(pi, ctx, argv = process.argv.slice(2)) {
|
|
739
|
-
const argvRaw = readCompactionModelFlagFromArgv(argv);
|
|
740
|
-
if (typeof argvRaw === "string" && argvRaw.trim()) {
|
|
741
|
-
const selector = normalizeSelector(argvRaw);
|
|
742
|
-
return selector ? { hasSelectors: true, source: "flag" } : { hasSelectors: false };
|
|
743
|
-
}
|
|
744
|
-
try {
|
|
745
|
-
const rawFlag = pi.getFlag(COMPACTION_MODEL_FLAG);
|
|
746
|
-
if (typeof rawFlag === "string" && rawFlag.trim()) {
|
|
747
|
-
const selector = normalizeSelector(rawFlag);
|
|
748
|
-
return selector ? { hasSelectors: true, source: "flag" } : { hasSelectors: false };
|
|
749
|
-
}
|
|
750
|
-
} catch {}
|
|
751
|
-
const agentDir = activeAgentDir(ctx);
|
|
752
|
-
const candidates = [];
|
|
753
|
-
if (isProjectPolicyTrusted(ctx, agentDir)) {
|
|
754
|
-
candidates.push({ path: join(ctx.cwd, CONFIG_DIR_NAME, PROJECT_POLICY), source: "project-policy" });
|
|
755
|
-
}
|
|
756
|
-
candidates.push({ path: join(agentDir, PROJECT_POLICY), source: "agent-policy" });
|
|
757
|
-
for (const candidate of candidates) {
|
|
758
|
-
const selectors = readPolicySelectors(candidate.path);
|
|
759
|
-
if (selectors !== undefined) {
|
|
760
|
-
return selectors.length > 0 ? { hasSelectors: true, source: candidate.source } : { hasSelectors: false };
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
return { hasSelectors: false };
|
|
764
|
-
}
|
|
765
|
-
|
|
766
986
|
// protocol.ts
|
|
767
987
|
var RESPONSES_APIS = new Set([
|
|
768
988
|
"openai-responses",
|
|
@@ -794,14 +1014,52 @@ function validateItemSize(item) {
|
|
|
794
1014
|
if (byteLength(item) > MAX_ITEM_BYTES)
|
|
795
1015
|
throw new Error("Native compaction output item exceeded 2 MiB");
|
|
796
1016
|
}
|
|
1017
|
+
function validContent(value) {
|
|
1018
|
+
return typeof value === "string" || Array.isArray(value) && value.every((part) => {
|
|
1019
|
+
if (!isObject(part))
|
|
1020
|
+
return false;
|
|
1021
|
+
if (part.type === "input_text" || part.type === "output_text" || part.type === "summary_text")
|
|
1022
|
+
return typeof part.text === "string";
|
|
1023
|
+
if (part.type === "input_image")
|
|
1024
|
+
return typeof part.image_url === "string" || typeof part.file_id === "string";
|
|
1025
|
+
if (part.type === "refusal")
|
|
1026
|
+
return typeof part.refusal === "string";
|
|
1027
|
+
return false;
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
797
1030
|
function validateCompactedOutput(value) {
|
|
798
1031
|
if (!isObject(value) || !Array.isArray(value.output) || value.output.length === 0) {
|
|
799
1032
|
throw new Error("Responses Compact returned an invalid output window");
|
|
800
1033
|
}
|
|
1034
|
+
if (byteLength(value.output) > MAX_RESPONSE_BYTES)
|
|
1035
|
+
throw new Error("Native compaction output exceeded 8 MiB");
|
|
801
1036
|
const output = value.output.map((item) => {
|
|
802
1037
|
if (!isObject(item))
|
|
803
1038
|
throw new Error("Responses Compact returned a non-object output item");
|
|
804
1039
|
validateItemSize(item);
|
|
1040
|
+
const type = item.type ?? (typeof item.role === "string" ? "message" : undefined);
|
|
1041
|
+
if (!["compaction", "message", "reasoning", "function_call", "function_call_output", "custom_tool_call", "custom_tool_call_output", "tool_search_call", "tool_search_output", "additional_tools"].includes(String(type)))
|
|
1042
|
+
throw new Error("Responses Compact returned an unsupported output item");
|
|
1043
|
+
if (type === "message" && (!["user", "assistant", "system", "developer"].includes(String(item.role)) || !validContent(item.content)))
|
|
1044
|
+
throw new Error("Responses Compact returned an invalid message item");
|
|
1045
|
+
if (type === "compaction" && !isCompactionItem(item))
|
|
1046
|
+
throw new Error("Responses Compact returned an invalid checkpoint");
|
|
1047
|
+
if (type === "reasoning" && !(typeof item.encrypted_content === "string" || Array.isArray(item.summary) && validContent(item.summary)))
|
|
1048
|
+
throw new Error("Responses Compact returned invalid reasoning");
|
|
1049
|
+
if (String(type).includes("call") || type === "tool_search_output") {
|
|
1050
|
+
if (typeof item.call_id !== "string" || !item.call_id)
|
|
1051
|
+
throw new Error("Responses Compact returned an invalid tool id");
|
|
1052
|
+
if (type === "function_call" && (typeof item.name !== "string" || typeof item.arguments !== "string"))
|
|
1053
|
+
throw new Error("Responses Compact returned an invalid function call");
|
|
1054
|
+
if (type === "custom_tool_call" && (typeof item.name !== "string" || typeof item.input !== "string"))
|
|
1055
|
+
throw new Error("Responses Compact returned an invalid custom call");
|
|
1056
|
+
if ((type === "function_call_output" || type === "custom_tool_call_output") && !validContent(item.output))
|
|
1057
|
+
throw new Error("Responses Compact returned an invalid tool output");
|
|
1058
|
+
if (type === "tool_search_call" && !isObject(item.arguments))
|
|
1059
|
+
throw new Error("Responses Compact returned an invalid tool search");
|
|
1060
|
+
}
|
|
1061
|
+
if ((type === "additional_tools" || type === "tool_search_output") && (!Array.isArray(item.tools) || !item.tools.every(isObject)))
|
|
1062
|
+
throw new Error("Responses Compact returned invalid tools");
|
|
805
1063
|
return structuredClone(item);
|
|
806
1064
|
});
|
|
807
1065
|
const checkpoints = output.filter(isCompactionItem);
|
|
@@ -841,6 +1099,9 @@ async function readJsonBounded(response, signal) {
|
|
|
841
1099
|
}
|
|
842
1100
|
} finally {
|
|
843
1101
|
signal.removeEventListener("abort", onAbort);
|
|
1102
|
+
reader.cancel().catch(() => {
|
|
1103
|
+
return;
|
|
1104
|
+
});
|
|
844
1105
|
reader.releaseLock();
|
|
845
1106
|
}
|
|
846
1107
|
const bytes = new Uint8Array(total);
|
|
@@ -988,7 +1249,9 @@ async function requestResponsesCompact(request) {
|
|
|
988
1249
|
onPayload: (payload) => {
|
|
989
1250
|
if (!isObject(payload))
|
|
990
1251
|
throw new Error("Responses provider exposed a non-object payload");
|
|
991
|
-
|
|
1252
|
+
if (request.preparedPayload && request.preparedPayload.model !== payload.model)
|
|
1253
|
+
throw new Error("Native route model changed since capture");
|
|
1254
|
+
preparedPayload = { ...structuredClone(request.preparedPayload ?? payload), input: structuredClone(request.input) };
|
|
992
1255
|
return preparedPayload;
|
|
993
1256
|
}
|
|
994
1257
|
});
|
|
@@ -1081,6 +1344,9 @@ async function collectCompactionSse(stream, signal) {
|
|
|
1081
1344
|
dispatch();
|
|
1082
1345
|
} finally {
|
|
1083
1346
|
signal.removeEventListener("abort", onAbort);
|
|
1347
|
+
reader.cancel().catch(() => {
|
|
1348
|
+
return;
|
|
1349
|
+
});
|
|
1084
1350
|
reader.releaseLock();
|
|
1085
1351
|
}
|
|
1086
1352
|
if (!completed)
|
|
@@ -1118,13 +1384,15 @@ function buildRemoteV2History(input, item) {
|
|
|
1118
1384
|
async function requestRemoteV2(request) {
|
|
1119
1385
|
const baseFetch = request.fetch ?? globalThis.fetch;
|
|
1120
1386
|
let sentInput;
|
|
1387
|
+
const controller = new AbortController;
|
|
1388
|
+
const signal = AbortSignal.any([request.signal, controller.signal]);
|
|
1121
1389
|
const inspections = [];
|
|
1122
1390
|
const inspectedFetch = async (input, init) => {
|
|
1123
1391
|
const response = await baseFetch(input, init);
|
|
1124
1392
|
if (!response.ok || !response.body)
|
|
1125
1393
|
return response;
|
|
1126
1394
|
const [providerBody, inspectionBody] = response.body.tee();
|
|
1127
|
-
inspections.push(collectCompactionSse(inspectionBody,
|
|
1395
|
+
inspections.push(collectCompactionSse(inspectionBody, signal).then((result) => ({ result }), (error) => ({ error })));
|
|
1128
1396
|
return new Response(providerBody, {
|
|
1129
1397
|
status: response.status,
|
|
1130
1398
|
statusText: response.statusText,
|
|
@@ -1144,440 +1412,572 @@ async function requestRemoteV2(request) {
|
|
|
1144
1412
|
if (!isObject(payload))
|
|
1145
1413
|
throw new Error("Codex provider exposed a non-object payload");
|
|
1146
1414
|
sentInput = structuredClone(request.input);
|
|
1147
|
-
|
|
1415
|
+
if (request.preparedPayload && request.preparedPayload.model !== payload.model)
|
|
1416
|
+
throw new Error("Native route model changed since capture");
|
|
1417
|
+
return { ...structuredClone(request.preparedPayload ?? payload), stream: true, input: [...sentInput, { type: "compaction_trigger" }] };
|
|
1148
1418
|
}
|
|
1149
1419
|
});
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1420
|
+
try {
|
|
1421
|
+
const usage = await collectProviderUsage(stream, request.signal);
|
|
1422
|
+
if (!sentInput || inspections.length !== 1)
|
|
1423
|
+
throw new Error(`Remote V2 observed ${inspections.length} successful provider responses; expected exactly one`);
|
|
1424
|
+
const inspection = await inspections[0];
|
|
1425
|
+
if ("error" in inspection)
|
|
1426
|
+
throw inspection.error;
|
|
1427
|
+
return { output: buildRemoteV2History(sentInput, inspection.result.item), usage };
|
|
1428
|
+
} finally {
|
|
1429
|
+
controller.abort();
|
|
1153
1430
|
}
|
|
1154
|
-
const inspection = await inspections[0];
|
|
1155
|
-
return { output: buildRemoteV2History(sentInput, inspection.item), usage };
|
|
1156
1431
|
}
|
|
1157
1432
|
async function requestProviderCompaction(request) {
|
|
1158
|
-
|
|
1433
|
+
const result = await (request.protocol === "remote-v2" ? requestRemoteV2(request) : requestResponsesCompact(request));
|
|
1434
|
+
return { ...result, output: validateCompactedOutput(result) };
|
|
1159
1435
|
}
|
|
1160
1436
|
|
|
1161
|
-
//
|
|
1162
|
-
var NATIVE_COMPACTION_TYPE = "pi-provider-compaction/openai-responses";
|
|
1163
|
-
var NATIVE_COMPACTION_VERSION = 2;
|
|
1164
|
-
var NATIVE_COMPACTION_SUMMARY = "This context was compacted using the provider's native OpenAI Responses state.";
|
|
1437
|
+
// replay.ts
|
|
1165
1438
|
var TOOL_CALL_PROVIDERS = new Set(["openai", "openai-codex", "opencode"]);
|
|
1166
|
-
|
|
1167
|
-
|
|
1439
|
+
var MAX_SNAPSHOT_BYTES = 8 * 1024 * 1024;
|
|
1440
|
+
var SNAPSHOT_TTL_MS = 5 * 60000;
|
|
1441
|
+
function equal(left, right) {
|
|
1442
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
1443
|
+
}
|
|
1444
|
+
function serializeInput(model, context, declarations = context) {
|
|
1445
|
+
const compat = model.compat;
|
|
1446
|
+
const flag = (name) => compat?.[name] === true;
|
|
1447
|
+
const codex = model.api === "openai-codex-responses";
|
|
1448
|
+
const normalized = resolveTranscript2(context, flag("supportsMidConvoSystemMessages"));
|
|
1449
|
+
const tools = resolveTranscript2(declarations, flag("supportsMidConvoSystemMessages"));
|
|
1450
|
+
return convertResponsesMessages(model, normalized, model.api === "azure-openai-responses" ? new Set([...TOOL_CALL_PROVIDERS, "azure-openai-responses"]) : TOOL_CALL_PROVIDERS, {
|
|
1451
|
+
includeSystemPrompt: !codex,
|
|
1452
|
+
grammarToolInputProperties: createGrammarToolInputProperties(getDeclaredTools(tools.messages), flag("supportsOpenAIGrammarTools")),
|
|
1453
|
+
supportsMidConvoSystemMessages: flag("supportsMidConvoSystemMessages"),
|
|
1454
|
+
supportsAdditionalTools: flag("supportsAdditionalTools"),
|
|
1455
|
+
supportsToolSearch: flag("supportsToolSearch"),
|
|
1456
|
+
toolOptions: {
|
|
1457
|
+
...codex ? { strict: null } : {},
|
|
1458
|
+
supportsStrictMode: compat?.supportsStrictMode ?? model.api !== "openai-responses",
|
|
1459
|
+
supportsOpenAIGrammarTools: flag("supportsOpenAIGrammarTools")
|
|
1460
|
+
}
|
|
1461
|
+
});
|
|
1168
1462
|
}
|
|
1169
|
-
function
|
|
1170
|
-
if (!isObject(
|
|
1463
|
+
function capturePreparedRequest(model, context, canonical, payload, reject = () => {}) {
|
|
1464
|
+
if (!isObject(payload) || !Array.isArray(payload.input) || !payload.input.every(isObject)) {
|
|
1465
|
+
reject("unsupported payload shape");
|
|
1171
1466
|
return;
|
|
1172
|
-
for (const key of ["input", "output", "cacheRead", "cacheWrite", "totalTokens"]) {
|
|
1173
|
-
if (typeof value[key] !== "number" || !Number.isFinite(value[key]))
|
|
1174
|
-
return;
|
|
1175
1467
|
}
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1468
|
+
if (payload.previous_response_id || payload.conversation) {
|
|
1469
|
+
reject("stateful request");
|
|
1470
|
+
return;
|
|
1179
1471
|
}
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
function readIdentity(value) {
|
|
1183
|
-
if (!isObject(value) || typeof value.endpoint !== "string" || !value.endpoint)
|
|
1472
|
+
if (Buffer.byteLength(JSON.stringify({ context, payload })) > MAX_SNAPSHOT_BYTES) {
|
|
1473
|
+
reject("snapshot exceeds size limit");
|
|
1184
1474
|
return;
|
|
1185
|
-
for (const key of ["organization", "project", "accountId", "azureApiVersion", "azureDeployment"]) {
|
|
1186
|
-
if (value[key] !== undefined && typeof value[key] !== "string")
|
|
1187
|
-
return;
|
|
1188
1475
|
}
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1476
|
+
const expected = serializeInput(model, context);
|
|
1477
|
+
if (!equal(expected, payload.input)) {
|
|
1478
|
+
reject("prepared input differs from serializer");
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1481
|
+
return { context: structuredClone(context), canonical: structuredClone(canonical), payload: structuredClone(payload), portableInput: expected, createdAt: Date.now() };
|
|
1482
|
+
}
|
|
1483
|
+
function promptCount(input) {
|
|
1484
|
+
let count = 0;
|
|
1485
|
+
while (input[count]?.role === "system" || input[count]?.role === "developer")
|
|
1486
|
+
count++;
|
|
1487
|
+
return count;
|
|
1488
|
+
}
|
|
1489
|
+
function closedTools(context) {
|
|
1490
|
+
const pending = new Set;
|
|
1491
|
+
for (const message of context.messages) {
|
|
1492
|
+
if (message.role === "assistant") {
|
|
1493
|
+
if (message.stopReason === "error" || message.stopReason === "aborted")
|
|
1494
|
+
return false;
|
|
1495
|
+
for (const block of message.content)
|
|
1496
|
+
if (block.type === "toolCall") {
|
|
1497
|
+
if (pending.has(block.id))
|
|
1498
|
+
return false;
|
|
1499
|
+
pending.add(block.id);
|
|
1500
|
+
}
|
|
1501
|
+
} else if (message.role === "toolResult") {
|
|
1502
|
+
if (!pending.delete(message.toolCallId))
|
|
1503
|
+
return false;
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
return pending.size === 0;
|
|
1197
1507
|
}
|
|
1198
|
-
function
|
|
1199
|
-
if (!
|
|
1508
|
+
function mappedPrefix(snapshot, prefix) {
|
|
1509
|
+
if (!equal(snapshot.canonical.messages.slice(0, prefix.messages.length), prefix.messages))
|
|
1200
1510
|
return;
|
|
1201
|
-
|
|
1511
|
+
const head = prefix.messages[0]?.role === "system" ? 1 : 0;
|
|
1512
|
+
if (head && snapshot.context.messages[0]?.role !== "system")
|
|
1202
1513
|
return;
|
|
1203
|
-
if (
|
|
1514
|
+
if (!equal(snapshot.context.messages.slice(head, prefix.messages.length), prefix.messages.slice(head)))
|
|
1204
1515
|
return;
|
|
1205
|
-
|
|
1516
|
+
return normalizeContext({ messages: snapshot.context.messages.slice(0, prefix.messages.length) });
|
|
1517
|
+
}
|
|
1518
|
+
function selectCoveredPrefix(model, snapshot, prefix, reject = () => {}) {
|
|
1519
|
+
if (Date.now() - snapshot.createdAt > SNAPSHOT_TTL_MS) {
|
|
1520
|
+
reject("snapshot expired");
|
|
1206
1521
|
return;
|
|
1207
|
-
|
|
1208
|
-
if (!
|
|
1522
|
+
}
|
|
1523
|
+
if (!closedTools(prefix)) {
|
|
1524
|
+
reject("prefix has an incomplete tool exchange or failed assistant");
|
|
1209
1525
|
return;
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
provider: value.provider,
|
|
1215
|
-
api: value.api,
|
|
1216
|
-
model: value.model,
|
|
1217
|
-
protocol,
|
|
1218
|
-
identity: value.version === 1 ? undefined : readIdentity(value.identity),
|
|
1219
|
-
output: structuredClone(value.output),
|
|
1220
|
-
nativeUsage: readUsage(value.nativeUsage ?? value.usage),
|
|
1221
|
-
usage: readUsage(value.usage),
|
|
1222
|
-
portableUsage: readUsage(value.portableUsage),
|
|
1223
|
-
readFiles: strings(value.readFiles),
|
|
1224
|
-
modifiedFiles: strings(value.modifiedFiles)
|
|
1225
|
-
};
|
|
1226
|
-
}
|
|
1227
|
-
function identityEqual(left, right) {
|
|
1228
|
-
if (!left)
|
|
1229
|
-
return true;
|
|
1230
|
-
if (!right)
|
|
1231
|
-
return false;
|
|
1232
|
-
return left.endpoint === right.endpoint && left.organization === right.organization && left.project === right.project && left.accountId === right.accountId && left.azureApiVersion === right.azureApiVersion && left.azureDeployment === right.azureDeployment;
|
|
1233
|
-
}
|
|
1234
|
-
function findLatestNativeCompaction(entries, model, identity) {
|
|
1235
|
-
if (!supportsNativeCompaction(model))
|
|
1526
|
+
}
|
|
1527
|
+
const mapped = mappedPrefix(snapshot, prefix);
|
|
1528
|
+
if (!mapped) {
|
|
1529
|
+
reject("projected prefix differs from request context");
|
|
1236
1530
|
return;
|
|
1237
|
-
for (let index = entries.length - 1;index >= 0; index--) {
|
|
1238
|
-
const entry = entries[index];
|
|
1239
|
-
if (entry.type !== "compaction")
|
|
1240
|
-
continue;
|
|
1241
|
-
const details = readNativeCompactionDetails(entry.details);
|
|
1242
|
-
if (!details)
|
|
1243
|
-
return;
|
|
1244
|
-
if (details.provider !== model.provider || details.api !== model.api || details.model !== model.id)
|
|
1245
|
-
return;
|
|
1246
|
-
if (!identityEqual(details.identity, identity))
|
|
1247
|
-
return;
|
|
1248
|
-
return { ...details, entryId: entry.id };
|
|
1249
1531
|
}
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
const previousIndex = branchEntries.findIndex((entry) => entry.id === previous.entryId);
|
|
1262
|
-
if (previousIndex >= 0) {
|
|
1263
|
-
return [...structuredClone(previous.output), ...responseItemsFromEntries(model, branchEntries.slice(previousIndex + 1))];
|
|
1532
|
+
const expected = serializeInput(model, mapped, snapshot.context);
|
|
1533
|
+
if (!equal(snapshot.portableInput.slice(0, expected.length), expected)) {
|
|
1534
|
+
reject("serialized prefix does not match prepared input");
|
|
1535
|
+
return;
|
|
1536
|
+
}
|
|
1537
|
+
const start = promptCount(expected);
|
|
1538
|
+
let end = expected.length;
|
|
1539
|
+
if (snapshot.replacement) {
|
|
1540
|
+
if (snapshot.replacement.index < start || snapshot.replacement.index >= end) {
|
|
1541
|
+
reject("previous native slot is outside covered prefix");
|
|
1542
|
+
return;
|
|
1264
1543
|
}
|
|
1544
|
+
end += snapshot.replacement.count - 1;
|
|
1265
1545
|
}
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
const result = { "content-type": "application/json" };
|
|
1270
|
-
let hasAuthorization = false;
|
|
1271
|
-
for (const [name, value] of Object.entries(headers ?? {})) {
|
|
1272
|
-
if (value === null)
|
|
1273
|
-
continue;
|
|
1274
|
-
result[name] = value;
|
|
1275
|
-
if (name.toLowerCase() === "authorization" && value.trim())
|
|
1276
|
-
hasAuthorization = true;
|
|
1546
|
+
if (end <= start) {
|
|
1547
|
+
reject("empty covered prefix");
|
|
1548
|
+
return;
|
|
1277
1549
|
}
|
|
1278
|
-
|
|
1279
|
-
result.authorization = `Bearer ${apiKey}`;
|
|
1280
|
-
return result;
|
|
1550
|
+
return structuredClone(snapshot.payload.input.slice(start, end));
|
|
1281
1551
|
}
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
if (
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
body: JSON.stringify(body),
|
|
1291
|
-
signal: options.signal
|
|
1292
|
-
});
|
|
1293
|
-
if (!response.ok) {
|
|
1294
|
-
const detail = await response.text().catch(() => "");
|
|
1295
|
-
throw new Error(`OpenAI Responses compact request failed (${response.status}${detail ? `: ${detail.slice(0, 400)}` : ""})`);
|
|
1296
|
-
}
|
|
1297
|
-
const result = await response.json();
|
|
1298
|
-
if (!isObject(result) || !Array.isArray(result.output) || !result.output.every(isObject) || !result.output.some(isCompactionItem)) {
|
|
1299
|
-
throw new Error("OpenAI Responses compact response did not contain opaque compaction state");
|
|
1300
|
-
}
|
|
1301
|
-
const usage = compactUsage(options.model, result);
|
|
1302
|
-
return {
|
|
1303
|
-
type: NATIVE_COMPACTION_TYPE,
|
|
1304
|
-
version: NATIVE_COMPACTION_VERSION,
|
|
1305
|
-
provider: options.model.provider,
|
|
1306
|
-
api: options.model.api,
|
|
1307
|
-
model: options.model.id,
|
|
1308
|
-
protocol: "responses-compact",
|
|
1309
|
-
output: structuredClone(result.output),
|
|
1310
|
-
nativeUsage: usage,
|
|
1311
|
-
usage
|
|
1312
|
-
};
|
|
1552
|
+
function substituteSummary(payload, index, summary, output) {
|
|
1553
|
+
if (!isObject(payload) || !Array.isArray(payload.input) || !payload.input.every(isObject))
|
|
1554
|
+
return;
|
|
1555
|
+
if (!equal(payload.input[index], summary))
|
|
1556
|
+
return;
|
|
1557
|
+
if (payload.input.filter((item) => equal(item, summary)).length !== 1)
|
|
1558
|
+
return;
|
|
1559
|
+
return { ...payload, input: [...payload.input.slice(0, index), ...structuredClone(output), ...payload.input.slice(index + 1)] };
|
|
1313
1560
|
}
|
|
1314
|
-
function
|
|
1315
|
-
const
|
|
1316
|
-
if (!
|
|
1561
|
+
function summarySlot(model, snapshot, summaryPrefix) {
|
|
1562
|
+
const mapped = mappedPrefix(snapshot, summaryPrefix);
|
|
1563
|
+
if (!mapped)
|
|
1317
1564
|
return;
|
|
1318
|
-
const
|
|
1319
|
-
const
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
cacheRead: cached,
|
|
1324
|
-
cacheWrite,
|
|
1325
|
-
reasoning: nestedNumber(usage.output_tokens_details, "reasoning_tokens"),
|
|
1326
|
-
totalTokens: readNumber(usage.total_tokens),
|
|
1327
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
|
|
1328
|
-
};
|
|
1329
|
-
try {
|
|
1330
|
-
calculateCost(model, mapped);
|
|
1331
|
-
} catch {}
|
|
1332
|
-
return mapped;
|
|
1333
|
-
}
|
|
1334
|
-
function readNumber(value) {
|
|
1335
|
-
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
1336
|
-
}
|
|
1337
|
-
function nestedNumber(value, key) {
|
|
1338
|
-
return isObject(value) ? readNumber(value[key]) : 0;
|
|
1339
|
-
}
|
|
1340
|
-
function stableStringify(value) {
|
|
1341
|
-
if (Array.isArray(value))
|
|
1342
|
-
return `[${value.map(stableStringify).join(",")}]`;
|
|
1343
|
-
if (isObject(value)) {
|
|
1344
|
-
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(",")}}`;
|
|
1345
|
-
}
|
|
1346
|
-
return JSON.stringify(value) ?? "null";
|
|
1347
|
-
}
|
|
1348
|
-
function itemMatches(left, right) {
|
|
1349
|
-
const leftId = typeof left.id === "string" ? left.id : undefined;
|
|
1350
|
-
const rightId = typeof right.id === "string" ? right.id : undefined;
|
|
1351
|
-
if (leftId && rightId)
|
|
1352
|
-
return leftId === rightId;
|
|
1353
|
-
const leftCallId = typeof left.call_id === "string" ? left.call_id : undefined;
|
|
1354
|
-
const rightCallId = typeof right.call_id === "string" ? right.call_id : undefined;
|
|
1355
|
-
if (leftCallId && rightCallId)
|
|
1356
|
-
return leftCallId === rightCallId && left.type === right.type;
|
|
1357
|
-
if (left.type === "message" && right.type === "message") {
|
|
1358
|
-
return left.role === right.role && stableStringify(left.content) === stableStringify(right.content);
|
|
1359
|
-
}
|
|
1360
|
-
return stableStringify(left) === stableStringify(right);
|
|
1361
|
-
}
|
|
1362
|
-
function findLastSubsequence(items, sequence) {
|
|
1363
|
-
if (sequence.length === 0)
|
|
1364
|
-
return -1;
|
|
1365
|
-
for (let start = items.length - sequence.length;start >= 0; start--) {
|
|
1366
|
-
if (sequence.every((item, offset) => itemMatches(items[start + offset], item)))
|
|
1367
|
-
return start;
|
|
1368
|
-
}
|
|
1369
|
-
return -1;
|
|
1370
|
-
}
|
|
1371
|
-
function rewriteResponsesPayload(payload, details, postCompactionItems) {
|
|
1372
|
-
if (!isObject(payload) || !Array.isArray(payload.input))
|
|
1373
|
-
return payload;
|
|
1374
|
-
const currentInput = payload.input.filter(isObject);
|
|
1375
|
-
if (currentInput.length !== payload.input.length)
|
|
1376
|
-
return payload;
|
|
1377
|
-
const postStart = findLastSubsequence(currentInput, postCompactionItems);
|
|
1378
|
-
if (postStart >= 0)
|
|
1379
|
-
return { ...payload, input: [...structuredClone(details.output), ...currentInput.slice(postStart)] };
|
|
1380
|
-
const retainedOutput = details.output.filter((item) => item.type !== "compaction");
|
|
1381
|
-
const retainedStart = findLastSubsequence(currentInput, retainedOutput);
|
|
1382
|
-
if (retainedStart >= 0) {
|
|
1383
|
-
return { ...payload, input: [...structuredClone(details.output), ...currentInput.slice(retainedStart + retainedOutput.length)] };
|
|
1384
|
-
}
|
|
1385
|
-
return { ...payload, input: [...structuredClone(details.output), ...structuredClone(postCompactionItems)] };
|
|
1386
|
-
}
|
|
1387
|
-
function genericCompactionSelected(pi, ctx) {
|
|
1388
|
-
return resolveCompactionModelPolicy(pi, ctx).hasSelectors;
|
|
1389
|
-
}
|
|
1390
|
-
function normalizedEndpoint(value) {
|
|
1391
|
-
if (!value)
|
|
1392
|
-
return "";
|
|
1393
|
-
try {
|
|
1394
|
-
const url = new URL(value);
|
|
1395
|
-
url.username = "";
|
|
1396
|
-
url.password = "";
|
|
1397
|
-
url.search = "";
|
|
1398
|
-
url.hash = "";
|
|
1399
|
-
return url.toString().replace(/\/+$/, "");
|
|
1400
|
-
} catch {
|
|
1401
|
-
return value.replace(/\/+$/, "");
|
|
1402
|
-
}
|
|
1565
|
+
const prefix = serializeInput(model, mapped, snapshot.context);
|
|
1566
|
+
const index = promptCount(prefix);
|
|
1567
|
+
if (prefix.length !== index + 1 || !equal(snapshot.portableInput.slice(0, prefix.length), prefix))
|
|
1568
|
+
return;
|
|
1569
|
+
return index;
|
|
1403
1570
|
}
|
|
1404
|
-
function
|
|
1405
|
-
|
|
1406
|
-
|
|
1571
|
+
function requestPolicy(model, snapshot) {
|
|
1572
|
+
return {
|
|
1573
|
+
model,
|
|
1574
|
+
system: getCurrentSystemPrompt(snapshot.context.messages),
|
|
1575
|
+
currentTools: getCurrentTools2(snapshot.context.messages),
|
|
1576
|
+
declarations: getDeclaredTools(resolveTranscript2(snapshot.context, model.compat?.supportsMidConvoSystemMessages).messages),
|
|
1577
|
+
instructions: snapshot.payload.instructions,
|
|
1578
|
+
tools: snapshot.payload.tools,
|
|
1579
|
+
prompt: snapshot.portableInput.slice(0, promptCount(snapshot.portableInput))
|
|
1580
|
+
};
|
|
1407
1581
|
}
|
|
1408
|
-
|
|
1409
|
-
|
|
1582
|
+
|
|
1583
|
+
// index.ts
|
|
1584
|
+
var NATIVE_COMPACTION_TYPE = "pi-provider-compaction/openai-responses";
|
|
1585
|
+
var NATIVE_COMPACTION_VERSION = 3;
|
|
1586
|
+
var NATIVE_COMPACTION_SUMMARY = "This context was compacted using the provider's native OpenAI Responses state.";
|
|
1587
|
+
var supportsNativeCompaction = supportsResponsesApi;
|
|
1588
|
+
var digest = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
1589
|
+
var transcript = (messages) => normalizeContext2({ messages: convertToLlm(messages) });
|
|
1590
|
+
function readNativeCompactionDetails(value) {
|
|
1591
|
+
if (!isObject(value) || value.type !== NATIVE_COMPACTION_TYPE || value.version !== 1 && value.version !== 2 && value.version !== 3)
|
|
1410
1592
|
return;
|
|
1411
|
-
|
|
1412
|
-
|
|
1593
|
+
if (typeof value.provider !== "string" || typeof value.api !== "string" || typeof value.model !== "string")
|
|
1594
|
+
return;
|
|
1595
|
+
const protocol = value.version === 1 ? "responses-compact" : value.protocol;
|
|
1596
|
+
if (protocol !== "responses-compact" && protocol !== "remote-v2")
|
|
1413
1597
|
return;
|
|
1414
1598
|
try {
|
|
1415
|
-
const
|
|
1416
|
-
if (
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1599
|
+
const output = validateCompactedOutput(value);
|
|
1600
|
+
if (value.version === 3) {
|
|
1601
|
+
const coverage = value.coverage;
|
|
1602
|
+
if (!isObject(coverage) || coverage.kind !== "summarized-prefix")
|
|
1603
|
+
return;
|
|
1604
|
+
for (const field of ["firstKeptEntryId", "sourceLeafId", "sourceHash", "policyHash", "inputHash", "runtimeId"]) {
|
|
1605
|
+
if (typeof coverage[field] !== "string" || !coverage[field])
|
|
1606
|
+
return;
|
|
1607
|
+
}
|
|
1608
|
+
if (!isObject(value.identity) || typeof value.identity.endpoint !== "string")
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
return { ...structuredClone(value), protocol, output };
|
|
1420
1612
|
} catch {
|
|
1421
1613
|
return;
|
|
1422
1614
|
}
|
|
1423
1615
|
}
|
|
1424
|
-
function
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
const [candidate, deployment] = entry.split("=", 2).map((part) => part?.trim());
|
|
1430
|
-
if (candidate === modelId && deployment)
|
|
1431
|
-
return deployment;
|
|
1616
|
+
function latestCompaction(branch) {
|
|
1617
|
+
for (let index = branch.length - 1;index >= 0; index--) {
|
|
1618
|
+
const entry = branch[index];
|
|
1619
|
+
if (entry.type === "compaction")
|
|
1620
|
+
return entry;
|
|
1432
1621
|
}
|
|
1433
|
-
return
|
|
1434
|
-
}
|
|
1435
|
-
function routeIdentity(model, auth) {
|
|
1436
|
-
const azure = model.api === "azure-openai-responses";
|
|
1437
|
-
return {
|
|
1438
|
-
endpoint: normalizedEndpoint(auth.baseUrl ?? auth.env?.AZURE_OPENAI_BASE_URL ?? model.baseUrl),
|
|
1439
|
-
organization: headerValue(auth.headers, "openai-organization"),
|
|
1440
|
-
project: headerValue(auth.headers, "openai-project"),
|
|
1441
|
-
accountId: model.api === "openai-codex-responses" ? codexAccountId(auth.apiKey) : undefined,
|
|
1442
|
-
azureApiVersion: azure ? auth.env?.AZURE_OPENAI_API_VERSION || "v1" : undefined,
|
|
1443
|
-
azureDeployment: azure ? azureDeployment(model.id, auth.env) : undefined
|
|
1444
|
-
};
|
|
1622
|
+
return;
|
|
1445
1623
|
}
|
|
1446
|
-
function
|
|
1447
|
-
const
|
|
1448
|
-
return
|
|
1449
|
-
const tool = available.get(name);
|
|
1450
|
-
return tool ? [{ name: tool.name, description: tool.description, parameters: tool.parameters }] : [];
|
|
1451
|
-
});
|
|
1624
|
+
function header(headers, name) {
|
|
1625
|
+
const value = Object.entries(headers ?? {}).find(([key]) => key.toLowerCase() === name)?.[1];
|
|
1626
|
+
return typeof value === "string" && value ? value : undefined;
|
|
1452
1627
|
}
|
|
1453
|
-
function
|
|
1454
|
-
const
|
|
1628
|
+
function identity(model, options) {
|
|
1629
|
+
const azure = model.api === "azure-openai-responses";
|
|
1630
|
+
const azureBase = options.env?.AZURE_OPENAI_BASE_URL || (options.env?.AZURE_OPENAI_RESOURCE_NAME ? `https://${options.env.AZURE_OPENAI_RESOURCE_NAME}.openai.azure.com/openai/v1` : undefined);
|
|
1631
|
+
const url = new URL(azure ? azureBase ?? model.baseUrl : model.baseUrl);
|
|
1632
|
+
url.username = "";
|
|
1633
|
+
url.password = "";
|
|
1634
|
+
url.search = "";
|
|
1635
|
+
url.hash = "";
|
|
1636
|
+
let accountId = header(options.headers, "chatgpt-account-id");
|
|
1637
|
+
if (!accountId && model.api === "openai-codex-responses" && options.apiKey) {
|
|
1638
|
+
try {
|
|
1639
|
+
const token = JSON.parse(Buffer.from(options.apiKey.split(".")[1], "base64url").toString("utf8"));
|
|
1640
|
+
const id = token["https://api.openai.com/auth"]?.chatgpt_account_id;
|
|
1641
|
+
if (typeof id === "string")
|
|
1642
|
+
accountId = id;
|
|
1643
|
+
} catch {}
|
|
1644
|
+
}
|
|
1645
|
+
const map = options.env?.AZURE_OPENAI_DEPLOYMENT_NAME_MAP?.split(",").map((entry) => entry.split("=").map((part) => part.trim()));
|
|
1455
1646
|
return {
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1647
|
+
endpoint: url.toString().replace(/\/+$/, ""),
|
|
1648
|
+
organization: header(options.headers, "openai-organization"),
|
|
1649
|
+
project: header(options.headers, "openai-project"),
|
|
1650
|
+
accountId,
|
|
1651
|
+
azureApiVersion: azure ? options.env?.AZURE_OPENAI_API_VERSION ?? "v1" : undefined,
|
|
1652
|
+
azureDeployment: azure ? map?.find(([id]) => id === model.id)?.[1] ?? model.id : undefined
|
|
1459
1653
|
};
|
|
1460
1654
|
}
|
|
1461
|
-
function cleanHeaders(headers) {
|
|
1462
|
-
if (!headers)
|
|
1463
|
-
return;
|
|
1464
|
-
return Object.fromEntries(Object.entries(headers).filter((entry) => entry[1] !== null));
|
|
1465
|
-
}
|
|
1466
1655
|
function combineUsage(first, second) {
|
|
1467
1656
|
if (!first)
|
|
1468
1657
|
return second;
|
|
1469
1658
|
if (!second)
|
|
1470
1659
|
return first;
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
input: first.cost.input + second.cost.input,
|
|
1481
|
-
output: first.cost.output + second.cost.output,
|
|
1482
|
-
cacheRead: first.cost.cacheRead + second.cost.cacheRead,
|
|
1483
|
-
cacheWrite: first.cost.cacheWrite + second.cost.cacheWrite,
|
|
1484
|
-
total: first.cost.total + second.cost.total
|
|
1485
|
-
}
|
|
1486
|
-
};
|
|
1660
|
+
const result = structuredClone(first);
|
|
1661
|
+
for (const key of ["input", "output", "cacheRead", "cacheWrite", "totalTokens"])
|
|
1662
|
+
result[key] += second[key];
|
|
1663
|
+
for (const key of ["reasoning", "cacheWrite1h"])
|
|
1664
|
+
if (first[key] !== undefined || second[key] !== undefined)
|
|
1665
|
+
result[key] = (first[key] ?? 0) + (second[key] ?? 0);
|
|
1666
|
+
for (const key of ["input", "output", "cacheRead", "cacheWrite", "total"])
|
|
1667
|
+
result.cost[key] += second.cost[key];
|
|
1668
|
+
return result;
|
|
1487
1669
|
}
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
headers: auth.headers,
|
|
1512
|
-
env: auth.env,
|
|
1513
|
-
signal: event.signal
|
|
1514
|
-
});
|
|
1515
|
-
const portable = await compact(event.preparation, model, auth.apiKey, cleanHeaders(auth.headers), event.customInstructions, event.signal, ctx.thinkingLevel, provider.streamSimple.bind(provider), auth.env, undefined, undefined, ctx.sessionManager.getSessionId());
|
|
1516
|
-
const portableDetails = isObject(portable.details) ? portable.details : {};
|
|
1517
|
-
const details = {
|
|
1518
|
-
type: NATIVE_COMPACTION_TYPE,
|
|
1519
|
-
version: NATIVE_COMPACTION_VERSION,
|
|
1520
|
-
provider: model.provider,
|
|
1521
|
-
api: model.api,
|
|
1522
|
-
model: model.id,
|
|
1523
|
-
protocol,
|
|
1524
|
-
identity,
|
|
1525
|
-
output: native.output,
|
|
1526
|
-
nativeUsage: native.usage,
|
|
1527
|
-
portableUsage: portable.usage,
|
|
1528
|
-
readFiles: Array.isArray(portableDetails.readFiles) ? portableDetails.readFiles.filter((item) => typeof item === "string") : undefined,
|
|
1529
|
-
modifiedFiles: Array.isArray(portableDetails.modifiedFiles) ? portableDetails.modifiedFiles.filter((item) => typeof item === "string") : undefined
|
|
1670
|
+
function supportedRouteOptions(model, options) {
|
|
1671
|
+
if (model.api !== "azure-openai-responses")
|
|
1672
|
+
return true;
|
|
1673
|
+
const extra = options;
|
|
1674
|
+
if (["azureBaseUrl", "azureResourceName", "azureApiVersion", "azureDeploymentName"].some((key) => extra[key] !== undefined))
|
|
1675
|
+
return false;
|
|
1676
|
+
return !["AZURE_OPENAI_BASE_URL", "AZURE_OPENAI_RESOURCE_NAME", "AZURE_OPENAI_API_VERSION", "AZURE_OPENAI_DEPLOYMENT_NAME_MAP"].some((key) => process.env[key] !== undefined && options.env?.[key] === undefined);
|
|
1677
|
+
}
|
|
1678
|
+
function pi_provider_compaction_default(pi) {
|
|
1679
|
+
const secret = randomBytes(32);
|
|
1680
|
+
const routeDigest = (value) => createHmac("sha256", secret).update(JSON.stringify(value)).digest("hex");
|
|
1681
|
+
let runtimeId = randomUUID();
|
|
1682
|
+
let ctx;
|
|
1683
|
+
let snapshot;
|
|
1684
|
+
let active;
|
|
1685
|
+
let pending;
|
|
1686
|
+
let uninstall;
|
|
1687
|
+
let installedProvider;
|
|
1688
|
+
let generation = 0;
|
|
1689
|
+
let captureStatus = "no request observed";
|
|
1690
|
+
let compactionStatus = "not attempted in this runtime";
|
|
1691
|
+
const captureRejected = (reason) => {
|
|
1692
|
+
captureStatus = reason;
|
|
1530
1693
|
};
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1694
|
+
const fallback = (reason) => {
|
|
1695
|
+
compactionStatus = `portable: ${reason}`;
|
|
1696
|
+
};
|
|
1697
|
+
pi.registerCommand("provider-compaction-status", {
|
|
1698
|
+
description: "Show content-free native compaction eligibility and last attempt",
|
|
1699
|
+
handler: async (_args, context) => {
|
|
1700
|
+
context.ui.notify(`Provider compaction
|
|
1701
|
+
Observer: ${installedProvider ? "installed" : "unavailable for current registration"}
|
|
1702
|
+
Capture: ${captureStatus}
|
|
1703
|
+
Last attempt: ${compactionStatus}`, "info");
|
|
1536
1704
|
}
|
|
1705
|
+
});
|
|
1706
|
+
const invalidate = () => {
|
|
1707
|
+
generation++;
|
|
1708
|
+
runtimeId = randomUUID();
|
|
1709
|
+
snapshot = undefined;
|
|
1710
|
+
active = undefined;
|
|
1711
|
+
pending = undefined;
|
|
1712
|
+
captureStatus = "invalidated by lifecycle/history/policy change";
|
|
1537
1713
|
};
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1714
|
+
const generic = (context) => resolveCompactionModelPolicy(pi, context).hasSelectors;
|
|
1715
|
+
const route = (model, options) => routeDigest({ model, apiKey: options.apiKey, headers: options.headers, env: options.env });
|
|
1716
|
+
const unchanged = (branch, length, hash) => branch.length >= length && digest(branch.slice(0, length)) === hash && !branch.slice(length).some((entry) => entry.type === "context_edit");
|
|
1717
|
+
const observe = (model, context, options) => {
|
|
1718
|
+
const owner = ctx;
|
|
1719
|
+
if (!owner || options.sessionId !== owner.sessionManager.getSessionId())
|
|
1543
1720
|
return;
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
if (!candidate || genericCompactionSelected(pi, ctx))
|
|
1721
|
+
if (!supportsNativeCompaction(model)) {
|
|
1722
|
+
captureRejected("unsupported API");
|
|
1547
1723
|
return;
|
|
1548
|
-
|
|
1549
|
-
if (!
|
|
1724
|
+
}
|
|
1725
|
+
if (!options.onPayload) {
|
|
1726
|
+
captureRejected("no payload callback");
|
|
1727
|
+
return;
|
|
1728
|
+
}
|
|
1729
|
+
if (generic(owner)) {
|
|
1730
|
+
captureRejected("generic compaction model takes precedence");
|
|
1731
|
+
return;
|
|
1732
|
+
}
|
|
1733
|
+
if (!supportedRouteOptions(model, options)) {
|
|
1734
|
+
captureRejected("unsupported route overrides");
|
|
1735
|
+
return;
|
|
1736
|
+
}
|
|
1737
|
+
const ticket = generation;
|
|
1738
|
+
const branch = owner.sessionManager.getBranch();
|
|
1739
|
+
const branchHash = digest(branch);
|
|
1740
|
+
const canonical = transcript(owner.sessionManager.buildSessionProjection().messages);
|
|
1741
|
+
const current = () => generation === ticket && ctx === owner && !options.signal?.aborted && supportedRouteOptions(model, options) && digest(owner.sessionManager.getBranch()) === branchHash;
|
|
1742
|
+
return async (payload) => {
|
|
1743
|
+
if (!current())
|
|
1744
|
+
return payload;
|
|
1745
|
+
snapshot = undefined;
|
|
1746
|
+
const prepared = capturePreparedRequest(model, context, canonical, payload, captureRejected);
|
|
1747
|
+
if (!prepared) {
|
|
1748
|
+
active = undefined;
|
|
1749
|
+
return payload;
|
|
1750
|
+
}
|
|
1751
|
+
const auth = await owner.modelRegistry.getApiKeyAndHeaders(model);
|
|
1752
|
+
if (!current())
|
|
1753
|
+
return payload;
|
|
1754
|
+
const normalizedHeaders = (headers) => Object.entries(headers ?? {}).map(([name, value]) => [name.toLowerCase(), value]).sort(([a], [b]) => String(a).localeCompare(String(b)));
|
|
1755
|
+
if (!auth.ok || generic(owner) || auth.apiKey !== options.apiKey || (auth.baseUrl ?? model.baseUrl) !== model.baseUrl || !equal(normalizedHeaders(auth.headers), normalizedHeaders(options.headers)) || !equal(auth.env ?? {}, options.env ?? {})) {
|
|
1756
|
+
active = undefined;
|
|
1757
|
+
captureRejected("resolved auth/route differs from prepared request, or generic policy changed");
|
|
1758
|
+
return payload;
|
|
1759
|
+
}
|
|
1760
|
+
const provider = owner.modelRegistry.getProvider(model.provider);
|
|
1761
|
+
if (!provider) {
|
|
1762
|
+
captureRejected("provider unavailable");
|
|
1763
|
+
return payload;
|
|
1764
|
+
}
|
|
1765
|
+
const routeHash = route(model, options);
|
|
1766
|
+
const policyHash = digest(requestPolicy(model, prepared));
|
|
1767
|
+
const latest = latestCompaction(branch);
|
|
1768
|
+
let result = payload;
|
|
1769
|
+
if (active && latest?.id === active.entryId && latest.summary === active.summary && digest(latest.details) === active.detailsHash && unchanged(branch, active.branchLength, active.branchHash) && active.routeHash === routeHash && active.policyHash === policyHash) {
|
|
1770
|
+
const details = readNativeCompactionDetails(latest.details);
|
|
1771
|
+
const projection = owner.sessionManager.buildSessionProjection();
|
|
1772
|
+
const contribution = projection.entries.find((entry) => entry.sourceEntry.id === latest.id);
|
|
1773
|
+
const prefix = contribution ? transcript(contribution.messages) : undefined;
|
|
1774
|
+
const slot = prefix ? summarySlot(model, prepared, prefix) : undefined;
|
|
1775
|
+
if (details?.version === 3 && details.coverage?.runtimeId === runtimeId && details.coverage.firstKeptEntryId === latest.firstKeptEntryId && slot !== undefined) {
|
|
1776
|
+
const replaced = substituteSummary(payload, slot, prepared.portableInput[slot], details.output);
|
|
1777
|
+
if (replaced) {
|
|
1778
|
+
result = replaced;
|
|
1779
|
+
prepared.payload = structuredClone(replaced);
|
|
1780
|
+
prepared.replacement = { index: slot, count: details.output.length };
|
|
1781
|
+
} else
|
|
1782
|
+
active = undefined;
|
|
1783
|
+
} else
|
|
1784
|
+
active = undefined;
|
|
1785
|
+
} else
|
|
1786
|
+
active = undefined;
|
|
1787
|
+
captureStatus = "eligible prepared request captured";
|
|
1788
|
+
snapshot = {
|
|
1789
|
+
prepared,
|
|
1790
|
+
model: structuredClone(model),
|
|
1791
|
+
provider,
|
|
1792
|
+
options: { apiKey: options.apiKey, headers: structuredClone(options.headers), env: structuredClone(options.env), fetch: options.fetch },
|
|
1793
|
+
branchLength: branch.length,
|
|
1794
|
+
branchHash,
|
|
1795
|
+
routeHash,
|
|
1796
|
+
authHash: routeDigest(auth),
|
|
1797
|
+
policyHash,
|
|
1798
|
+
sessionId: owner.sessionManager.getSessionId()
|
|
1799
|
+
};
|
|
1800
|
+
return result;
|
|
1801
|
+
};
|
|
1802
|
+
};
|
|
1803
|
+
const install = (context) => {
|
|
1804
|
+
ctx = context;
|
|
1805
|
+
if (installedProvider === context.model?.provider)
|
|
1550
1806
|
return;
|
|
1551
|
-
|
|
1552
|
-
|
|
1807
|
+
uninstall?.();
|
|
1808
|
+
uninstall = undefined;
|
|
1809
|
+
installedProvider = undefined;
|
|
1810
|
+
if (!supportsNativeCompaction(context.model))
|
|
1553
1811
|
return;
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1812
|
+
uninstall = installObserver(pi, context, observe);
|
|
1813
|
+
if (uninstall)
|
|
1814
|
+
installedProvider = context.model.provider;
|
|
1815
|
+
captureStatus = uninstall ? "awaiting prepared request" : "unsupported provider registration";
|
|
1816
|
+
};
|
|
1817
|
+
pi.on("session_start", (_event, context) => {
|
|
1818
|
+
invalidate();
|
|
1819
|
+
install(context);
|
|
1820
|
+
const latest = latestCompaction(context.sessionManager.getBranch());
|
|
1821
|
+
if (latest?.summary === NATIVE_COMPACTION_SUMMARY)
|
|
1822
|
+
context.ui.notify("Legacy native compaction has no portable summary. Native replay is disabled; recover from earlier history or start a new session.", "warning");
|
|
1561
1823
|
});
|
|
1562
|
-
pi.on("
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1824
|
+
pi.on("before_agent_start", (_event, context) => {
|
|
1825
|
+
install(context);
|
|
1826
|
+
});
|
|
1827
|
+
pi.on("model_select", (_event, context) => {
|
|
1828
|
+
invalidate();
|
|
1829
|
+
install(context);
|
|
1830
|
+
});
|
|
1831
|
+
pi.on("session_tree", () => {
|
|
1832
|
+
invalidate();
|
|
1833
|
+
});
|
|
1834
|
+
pi.on("session_shutdown", () => {
|
|
1835
|
+
invalidate();
|
|
1836
|
+
uninstall?.();
|
|
1837
|
+
uninstall = undefined;
|
|
1838
|
+
installedProvider = undefined;
|
|
1839
|
+
ctx = undefined;
|
|
1840
|
+
});
|
|
1841
|
+
const removeInvalidation = pi.events.on("pi-provider-compaction:invalidate", invalidate);
|
|
1842
|
+
pi.on("session_shutdown", () => {
|
|
1843
|
+
if (typeof removeInvalidation === "function")
|
|
1844
|
+
removeInvalidation();
|
|
1845
|
+
});
|
|
1846
|
+
pi.on("session_before_compact", async (event, context) => {
|
|
1847
|
+
const capture = snapshot;
|
|
1848
|
+
snapshot = undefined;
|
|
1849
|
+
pending = undefined;
|
|
1850
|
+
if (!capture) {
|
|
1851
|
+
fallback(`no eligible snapshot (${captureStatus})`);
|
|
1852
|
+
return;
|
|
1853
|
+
}
|
|
1854
|
+
if (!context.model || generic(context) || event.signal.aborted) {
|
|
1855
|
+
fallback("missing model, generic model override, or aborted attempt");
|
|
1856
|
+
return;
|
|
1857
|
+
}
|
|
1858
|
+
const protocol = resolveNativeProtocol(context.model);
|
|
1859
|
+
if (!protocol || context.sessionManager.getSessionId() !== capture.sessionId) {
|
|
1860
|
+
fallback("unsupported protocol or changed session");
|
|
1861
|
+
return;
|
|
1862
|
+
}
|
|
1863
|
+
const ticket = generation;
|
|
1864
|
+
const branch = context.sessionManager.getBranch();
|
|
1865
|
+
const branchHash = digest(branch);
|
|
1866
|
+
if (!equal(branch, event.branchEntries) || !unchanged(branch, capture.branchLength, capture.branchHash)) {
|
|
1867
|
+
fallback("history changed since capture");
|
|
1569
1868
|
return;
|
|
1570
1869
|
}
|
|
1870
|
+
const projection = context.sessionManager.buildSessionProjection();
|
|
1871
|
+
const boundary = projection.entries.findIndex((entry) => entry.sourceEntry.id === event.preparation.firstKeptEntryId);
|
|
1872
|
+
if (boundary < 0) {
|
|
1873
|
+
fallback("retained boundary missing from projection");
|
|
1874
|
+
return;
|
|
1875
|
+
}
|
|
1876
|
+
const prefix = transcript(projection.entries.slice(0, boundary).flatMap((entry) => entry.messages));
|
|
1877
|
+
const input = selectCoveredPrefix(capture.model, capture.prepared, prefix, fallback);
|
|
1878
|
+
if (!input)
|
|
1879
|
+
return;
|
|
1880
|
+
const current = () => ticket === generation && !event.signal.aborted && supportedRouteOptions(capture.model, capture.options) && !generic(context) && context.modelRegistry.getProvider(capture.model.provider) === capture.provider && digest(context.sessionManager.getBranch()) === branchHash;
|
|
1881
|
+
const auth = await context.modelRegistry.getApiKeyAndHeaders(context.model);
|
|
1882
|
+
if (!current())
|
|
1883
|
+
return { cancel: true };
|
|
1884
|
+
if (!auth.ok || routeDigest(auth) !== capture.authHash) {
|
|
1885
|
+
fallback("auth changed since capture");
|
|
1886
|
+
return;
|
|
1887
|
+
}
|
|
1888
|
+
compactionStatus = "generating portable summary";
|
|
1889
|
+
let portable;
|
|
1890
|
+
try {
|
|
1891
|
+
portable = await compact(event.preparation, capture.model, auth.apiKey, auth.headers, event.customInstructions, event.signal, context.thinkingLevel, capture.provider.streamSimple.bind(capture.provider), auth.env, undefined, undefined, capture.sessionId);
|
|
1892
|
+
} catch {
|
|
1893
|
+
return !current() ? { cancel: true } : undefined;
|
|
1894
|
+
}
|
|
1895
|
+
if (!current() || !portable.summary.trim() || portable.summary === NATIVE_COMPACTION_SUMMARY)
|
|
1896
|
+
return { cancel: true };
|
|
1897
|
+
try {
|
|
1898
|
+
const freshAuth = await context.modelRegistry.getApiKeyAndHeaders(context.model);
|
|
1899
|
+
if (!current())
|
|
1900
|
+
return { cancel: true };
|
|
1901
|
+
if (!freshAuth.ok || routeDigest(freshAuth) !== capture.authHash) {
|
|
1902
|
+
fallback("auth changed after portable generation");
|
|
1903
|
+
return { compaction: portable };
|
|
1904
|
+
}
|
|
1905
|
+
compactionStatus = "requesting native checkpoint";
|
|
1906
|
+
const native = await requestProviderCompaction({
|
|
1907
|
+
provider: capture.provider,
|
|
1908
|
+
model: capture.model,
|
|
1909
|
+
context: capture.prepared.context,
|
|
1910
|
+
input,
|
|
1911
|
+
preparedPayload: capture.prepared.payload,
|
|
1912
|
+
protocol,
|
|
1913
|
+
apiKey: capture.options.apiKey,
|
|
1914
|
+
headers: capture.options.headers,
|
|
1915
|
+
env: capture.options.env,
|
|
1916
|
+
signal: event.signal,
|
|
1917
|
+
fetch: capture.options.fetch
|
|
1918
|
+
});
|
|
1919
|
+
if (!current())
|
|
1920
|
+
return { cancel: true };
|
|
1921
|
+
const finalAuth = await context.modelRegistry.getApiKeyAndHeaders(context.model);
|
|
1922
|
+
if (!current())
|
|
1923
|
+
return { cancel: true };
|
|
1924
|
+
if (!finalAuth.ok || routeDigest(finalAuth) !== capture.authHash) {
|
|
1925
|
+
fallback("auth changed after native generation");
|
|
1926
|
+
return { compaction: portable };
|
|
1927
|
+
}
|
|
1928
|
+
const details = {
|
|
1929
|
+
...isObject(portable.details) ? portable.details : {},
|
|
1930
|
+
type: NATIVE_COMPACTION_TYPE,
|
|
1931
|
+
version: 3,
|
|
1932
|
+
provider: capture.model.provider,
|
|
1933
|
+
api: capture.model.api,
|
|
1934
|
+
model: capture.model.id,
|
|
1935
|
+
protocol,
|
|
1936
|
+
identity: identity(capture.model, capture.options),
|
|
1937
|
+
output: native.output,
|
|
1938
|
+
nativeUsage: native.usage,
|
|
1939
|
+
portableUsage: portable.usage,
|
|
1940
|
+
coverage: { kind: "summarized-prefix", firstKeptEntryId: portable.firstKeptEntryId, sourceLeafId: branch.at(-1).id, sourceHash: branchHash, policyHash: capture.policyHash, inputHash: digest(input), runtimeId }
|
|
1941
|
+
};
|
|
1942
|
+
pending = { details, capture, summary: portable.summary };
|
|
1943
|
+
return { compaction: { ...portable, usage: combineUsage(native.usage, portable.usage), details } };
|
|
1944
|
+
} catch {
|
|
1945
|
+
if (!current())
|
|
1946
|
+
return { cancel: true };
|
|
1947
|
+
fallback("native acquisition or validation failed");
|
|
1948
|
+
return { compaction: portable };
|
|
1949
|
+
}
|
|
1950
|
+
});
|
|
1951
|
+
pi.on("session_compact", (event, context) => {
|
|
1952
|
+
if (readNativeCompactionDetails(event.compactionEntry.details)?.version === 3)
|
|
1953
|
+
compactionStatus = "native checkpoint committed";
|
|
1954
|
+
else if (!compactionStatus.startsWith("portable:"))
|
|
1955
|
+
fallback("another handler supplied the committed result");
|
|
1956
|
+
if (compactionStatus.startsWith("portable:"))
|
|
1957
|
+
pi.appendEntry("pi-provider-compaction:diagnostic", { status: compactionStatus });
|
|
1958
|
+
active = undefined;
|
|
1959
|
+
snapshot = undefined;
|
|
1960
|
+
const candidate = pending;
|
|
1961
|
+
pending = undefined;
|
|
1962
|
+
if (!candidate || !equal(event.compactionEntry.details, candidate.details) || event.compactionEntry.summary !== candidate.summary)
|
|
1963
|
+
return;
|
|
1964
|
+
const branch = context.sessionManager.getBranch();
|
|
1965
|
+
const coverage = candidate.details.coverage;
|
|
1966
|
+
if (!coverage || event.compactionEntry.firstKeptEntryId !== coverage.firstKeptEntryId || event.compactionEntry.parentId !== coverage.sourceLeafId || branch.at(-1)?.id !== event.compactionEntry.id || digest(branch.slice(0, -1)) !== coverage.sourceHash)
|
|
1967
|
+
return;
|
|
1968
|
+
active = { entryId: event.compactionEntry.id, detailsHash: digest(candidate.details), summary: candidate.summary, branchLength: branch.length, branchHash: digest(branch), routeHash: candidate.capture.routeHash, policyHash: candidate.capture.policyHash };
|
|
1969
|
+
});
|
|
1970
|
+
pi.on("session_compact_failed", () => {
|
|
1971
|
+
pending = undefined;
|
|
1972
|
+
snapshot = undefined;
|
|
1973
|
+
compactionStatus = "compaction failed or cancelled";
|
|
1571
1974
|
});
|
|
1572
1975
|
}
|
|
1573
1976
|
export {
|
|
1574
1977
|
NATIVE_COMPACTION_SUMMARY,
|
|
1575
1978
|
NATIVE_COMPACTION_TYPE,
|
|
1576
1979
|
NATIVE_COMPACTION_VERSION,
|
|
1577
|
-
compactOpenAIResponses,
|
|
1578
1980
|
pi_provider_compaction_default as default,
|
|
1579
|
-
findLatestNativeCompaction,
|
|
1580
1981
|
readNativeCompactionDetails,
|
|
1581
|
-
rewriteResponsesPayload,
|
|
1582
1982
|
supportsNativeCompaction
|
|
1583
1983
|
};
|