@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/codex-compact.ts
CHANGED
|
@@ -1,279 +1,302 @@
|
|
|
1
1
|
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
2
2
|
import type { Api, Context, Model, Tool } from "@earendil-works/pi-ai";
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
buildContextEntries,
|
|
5
|
+
buildSessionContext,
|
|
6
|
+
convertToLlm,
|
|
7
|
+
type ExtensionAPI,
|
|
8
|
+
type ExtensionContext,
|
|
9
|
+
type SessionBeforeCompactEvent,
|
|
10
|
+
sessionEntryToContextMessages,
|
|
11
11
|
} from "@earendil-works/pi-coding-agent";
|
|
12
12
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
buildReplacementHistory,
|
|
14
|
+
type CodexCheckpointDetails,
|
|
15
|
+
checkpointMarker,
|
|
16
|
+
createCheckpointDetails,
|
|
17
|
+
fallbackSummary,
|
|
18
|
+
latestCheckpoint,
|
|
19
|
+
projectCheckpointContext,
|
|
20
20
|
} from "./checkpoint.js";
|
|
21
|
-
import {
|
|
21
|
+
import { type CompactionRoute, resolveCompactionRoute } from "./model-api.js";
|
|
22
22
|
import { hasCheckpointMarker, rewriteCheckpointMarker } from "./protocol.js";
|
|
23
23
|
import { requestRemoteCompaction } from "./remote.js";
|
|
24
24
|
import {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
type CodexCompactSettings,
|
|
26
|
+
type CodexCompactSettingsRuntime,
|
|
27
|
+
type CodexCompactSettingsState,
|
|
28
|
+
createCodexCompactSettingsRuntime,
|
|
29
29
|
} from "./settings.js";
|
|
30
|
+
import { terminalText } from "./terminal.js";
|
|
30
31
|
|
|
31
32
|
const STATUS_KEY = "codex-compact";
|
|
32
33
|
|
|
33
34
|
function activeCheckpoint(ctx: ExtensionContext) {
|
|
34
|
-
|
|
35
|
+
return latestCheckpoint(ctx.sessionManager.getBranch());
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
function isCheckpointCompatible(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
details: CodexCheckpointDetails,
|
|
40
|
+
model: Model<Api> | undefined,
|
|
41
|
+
settings: CodexCompactSettings,
|
|
42
|
+
): boolean {
|
|
43
|
+
const route = resolveCompactionRoute(model, settings);
|
|
44
|
+
return (
|
|
45
|
+
route.kind === "remote" &&
|
|
46
|
+
model !== undefined &&
|
|
47
|
+
route.api === details.api &&
|
|
48
|
+
route.profile === details.profile &&
|
|
49
|
+
model.id === details.modelId
|
|
50
|
+
);
|
|
42
51
|
}
|
|
43
52
|
|
|
44
53
|
function keptMessages(event: SessionBeforeCompactEvent): AgentMessage[] {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
return contextEntries.slice(keptIndex).flatMap(sessionEntryToContextMessages);
|
|
54
|
+
const leafId = event.branchEntries.at(-1)?.id ?? null;
|
|
55
|
+
const contextEntries = buildContextEntries(event.branchEntries, leafId);
|
|
56
|
+
const keptIndex = contextEntries.findIndex((entry) => entry.id === event.preparation.firstKeptEntryId);
|
|
57
|
+
if (keptIndex < 0) {
|
|
58
|
+
throw new Error("Pi compaction cut point is not present in the active context");
|
|
59
|
+
}
|
|
60
|
+
return contextEntries.slice(keptIndex).flatMap(sessionEntryToContextMessages);
|
|
54
61
|
}
|
|
55
62
|
|
|
56
63
|
function activeTools(pi: ExtensionAPI): Tool[] {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
const available = new Map(pi.getAllTools().map((tool) => [tool.name, tool]));
|
|
65
|
+
return pi.getActiveTools().flatMap((name) => {
|
|
66
|
+
const tool = available.get(name);
|
|
67
|
+
return tool
|
|
68
|
+
? [
|
|
69
|
+
{
|
|
70
|
+
name: tool.name,
|
|
71
|
+
description: tool.description,
|
|
72
|
+
parameters: tool.parameters,
|
|
73
|
+
},
|
|
74
|
+
]
|
|
75
|
+
: [];
|
|
76
|
+
});
|
|
66
77
|
}
|
|
67
78
|
|
|
68
79
|
function projectedCurrentMessages(
|
|
69
|
-
|
|
70
|
-
|
|
80
|
+
event: SessionBeforeCompactEvent,
|
|
81
|
+
model: Model<Api>,
|
|
82
|
+
route: Extract<CompactionRoute, { kind: "remote" }>,
|
|
71
83
|
): { messages: AgentMessage[]; prior?: CodexCheckpointDetails } {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
const leafId = event.branchEntries.at(-1)?.id ?? null;
|
|
85
|
+
const session = buildSessionContext(event.branchEntries, leafId);
|
|
86
|
+
const prior = latestCheckpoint(event.branchEntries);
|
|
87
|
+
if (!prior) return { messages: session.messages };
|
|
88
|
+
if (
|
|
89
|
+
prior.details.api !== route.api ||
|
|
90
|
+
prior.details.profile !== route.profile ||
|
|
91
|
+
prior.details.modelId !== model.id
|
|
92
|
+
) {
|
|
93
|
+
throw new Error("The active opaque checkpoint belongs to a different Responses model");
|
|
94
|
+
}
|
|
95
|
+
const projected = projectCheckpointContext(session.messages, prior.details, prior.entry.summary);
|
|
96
|
+
if (!projected) {
|
|
97
|
+
throw new Error("The previous opaque checkpoint could not be projected safely");
|
|
98
|
+
}
|
|
99
|
+
return { messages: projected, prior: prior.details };
|
|
84
100
|
}
|
|
85
101
|
|
|
86
|
-
function notifyFailure(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
): void {
|
|
91
|
-
if (!ctx.hasUI || !settings.notifyOnFallback) return;
|
|
92
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
93
|
-
ctx.ui.notify(`Codex remote compaction failed; using Pi compaction. ${message}`, "warning");
|
|
102
|
+
function notifyFailure(ctx: ExtensionContext, error: unknown, settings: CodexCompactSettings): void {
|
|
103
|
+
if (!ctx.hasUI || !settings.notifyOnFallback) return;
|
|
104
|
+
const message = terminalText(error instanceof Error ? error.message : String(error));
|
|
105
|
+
ctx.ui.notify(`Responses compaction failed; using Pi compaction. ${message}`, "warning");
|
|
94
106
|
}
|
|
95
107
|
|
|
96
108
|
function sessionStillOwned(ctx: ExtensionContext, sessionId: string, signal: AbortSignal): boolean {
|
|
97
|
-
|
|
109
|
+
return !signal.aborted && ctx.sessionManager.getSessionId() === sessionId;
|
|
98
110
|
}
|
|
99
111
|
|
|
100
112
|
async function compactRemotely(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
113
|
+
pi: ExtensionAPI,
|
|
114
|
+
event: SessionBeforeCompactEvent,
|
|
115
|
+
ctx: ExtensionContext,
|
|
116
|
+
settings: CodexCompactSettings,
|
|
117
|
+
ownerSignal: AbortSignal,
|
|
118
|
+
fetch?: typeof globalThis.fetch,
|
|
106
119
|
) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
120
|
+
const model = ctx.model;
|
|
121
|
+
const route = resolveCompactionRoute(model, settings);
|
|
122
|
+
if (route.kind === "native" || !model) return undefined;
|
|
123
|
+
const signal = AbortSignal.any([event.signal, ownerSignal]);
|
|
124
|
+
if (signal.aborted) return { cancel: true };
|
|
125
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
126
|
+
ctx.ui.setStatus(STATUS_KEY, route.protocol === "remote-v2" ? "Responses Remote V2…" : "Responses Compact API…");
|
|
127
|
+
try {
|
|
128
|
+
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
|
|
129
|
+
if (!sessionStillOwned(ctx, sessionId, signal)) return { cancel: true };
|
|
130
|
+
if (!auth.ok) throw new Error(auth.error);
|
|
131
|
+
const provider = ctx.modelRegistry.getProvider(model.provider);
|
|
132
|
+
if (!provider) throw new Error("The active Responses provider is unavailable");
|
|
133
|
+
const current = projectedCurrentMessages(event, model, route);
|
|
134
|
+
const context: Context = {
|
|
135
|
+
systemPrompt: ctx.getSystemPrompt(),
|
|
136
|
+
messages: convertToLlm(current.messages),
|
|
137
|
+
tools: activeTools(pi),
|
|
138
|
+
};
|
|
139
|
+
const response = await requestRemoteCompaction({
|
|
140
|
+
provider,
|
|
141
|
+
model,
|
|
142
|
+
context,
|
|
143
|
+
protocol: route.protocol,
|
|
144
|
+
profile: route.profile,
|
|
145
|
+
apiKey: auth.apiKey,
|
|
146
|
+
headers: auth.headers,
|
|
147
|
+
env: auth.env,
|
|
148
|
+
signal,
|
|
149
|
+
priorCheckpoint: current.prior
|
|
150
|
+
? {
|
|
151
|
+
marker: checkpointMarker(current.prior.checkpointId),
|
|
152
|
+
replacementHistory: current.prior.replacementHistory,
|
|
153
|
+
}
|
|
154
|
+
: undefined,
|
|
155
|
+
requestTimeoutMs: settings.requestTimeoutMs,
|
|
156
|
+
maxRetries: settings.maxRetries,
|
|
157
|
+
fetch,
|
|
158
|
+
});
|
|
159
|
+
if (!sessionStillOwned(ctx, sessionId, signal)) return { cancel: true };
|
|
160
|
+
const replacementHistory = buildReplacementHistory(
|
|
161
|
+
response.compactedOutput?.slice(0, -1) ?? response.promptInput,
|
|
162
|
+
response.item,
|
|
163
|
+
{ tokenBudget: settings.replacementTokenBudget },
|
|
164
|
+
);
|
|
165
|
+
const details = createCheckpointDetails({
|
|
166
|
+
provider: model.provider,
|
|
167
|
+
api: route.api,
|
|
168
|
+
profile: route.profile,
|
|
169
|
+
modelId: model.id,
|
|
170
|
+
protocol: route.protocol,
|
|
171
|
+
replacementHistory,
|
|
172
|
+
keptMessages: keptMessages(event),
|
|
173
|
+
});
|
|
174
|
+
return {
|
|
175
|
+
compaction: {
|
|
176
|
+
summary: fallbackSummary(details.checkpointId),
|
|
177
|
+
firstKeptEntryId: event.preparation.firstKeptEntryId,
|
|
178
|
+
tokensBefore: event.preparation.tokensBefore,
|
|
179
|
+
usage: response.usage,
|
|
180
|
+
details,
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
} catch (error) {
|
|
184
|
+
if (signal.aborted || ctx.sessionManager.getSessionId() !== sessionId) {
|
|
185
|
+
return { cancel: true };
|
|
186
|
+
}
|
|
187
|
+
notifyFailure(ctx, error, settings);
|
|
188
|
+
return undefined;
|
|
189
|
+
} finally {
|
|
190
|
+
if (ctx.sessionManager.getSessionId() === sessionId) ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
191
|
+
}
|
|
169
192
|
}
|
|
170
193
|
|
|
171
194
|
export function createCodexCompactExtension(
|
|
172
|
-
|
|
195
|
+
options: { fetch?: typeof globalThis.fetch; settingsRuntime?: CodexCompactSettingsRuntime } = {},
|
|
173
196
|
): (pi: ExtensionAPI) => void {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
197
|
+
return (pi) => {
|
|
198
|
+
const providerWarnings = new Set<string>();
|
|
199
|
+
const settingsRuntime = options.settingsRuntime ?? createCodexCompactSettingsRuntime();
|
|
200
|
+
let sessionController = new AbortController();
|
|
201
|
+
let generation = 0;
|
|
179
202
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
203
|
+
pi.registerCommand("codex-compact", {
|
|
204
|
+
description: "Compact now or configure Responses compaction",
|
|
205
|
+
handler: async (args, ctx) => {
|
|
206
|
+
if (args.trim()) throw new Error("Usage: /codex-compact");
|
|
207
|
+
const ownerGeneration = generation;
|
|
208
|
+
const controller = sessionController;
|
|
209
|
+
const { showCodexCompactMenu } = await import("./settings-menu.js");
|
|
210
|
+
if (ownerGeneration !== generation || controller.signal.aborted) return;
|
|
211
|
+
await showCodexCompactMenu(settingsRuntime, ctx, {
|
|
212
|
+
signal: controller.signal,
|
|
213
|
+
isCurrent: () => ownerGeneration === generation && !controller.signal.aborted,
|
|
214
|
+
});
|
|
215
|
+
},
|
|
216
|
+
});
|
|
193
217
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
218
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
219
|
+
sessionController.abort();
|
|
220
|
+
sessionController = new AbortController();
|
|
221
|
+
generation += 1;
|
|
222
|
+
const ownerGeneration = generation;
|
|
223
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
224
|
+
providerWarnings.clear();
|
|
225
|
+
let state: Readonly<CodexCompactSettingsState>;
|
|
226
|
+
try {
|
|
227
|
+
state = await settingsRuntime.reload(sessionController.signal);
|
|
228
|
+
} catch (error) {
|
|
229
|
+
if (sessionController.signal.aborted || ownerGeneration !== generation) return;
|
|
230
|
+
if (ctx.hasUI) {
|
|
231
|
+
ctx.ui.notify(
|
|
232
|
+
`Could not load pi-codex-compact.json; using defaults. ${terminalText(error instanceof Error ? error.message : String(error))}`,
|
|
233
|
+
"warning",
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (
|
|
239
|
+
sessionController.signal.aborted ||
|
|
240
|
+
ownerGeneration !== generation ||
|
|
241
|
+
ctx.sessionManager.getSessionId() !== sessionId
|
|
242
|
+
) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (ctx.hasUI && state.kind === "invalid") {
|
|
246
|
+
ctx.ui.notify(
|
|
247
|
+
`Invalid pi-codex-compact.json; using defaults without overwriting it. ${terminalText(state.issue ?? "unknown validation error")}`,
|
|
248
|
+
"warning",
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
228
252
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
253
|
+
pi.on("session_before_compact", (event, ctx) =>
|
|
254
|
+
compactRemotely(pi, event, ctx, settingsRuntime.get().settings, sessionController.signal, options.fetch),
|
|
255
|
+
);
|
|
232
256
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
);
|
|
242
|
-
return messages ? { messages } : undefined;
|
|
243
|
-
});
|
|
257
|
+
pi.on("context", (event, ctx) => {
|
|
258
|
+
if (!settingsRuntime.get().settings.enabled) return undefined;
|
|
259
|
+
const settings = settingsRuntime.get().settings;
|
|
260
|
+
const checkpoint = activeCheckpoint(ctx);
|
|
261
|
+
if (!checkpoint || !isCheckpointCompatible(checkpoint.details, ctx.model, settings)) return undefined;
|
|
262
|
+
const messages = projectCheckpointContext(event.messages, checkpoint.details, checkpoint.entry.summary);
|
|
263
|
+
return messages ? { messages } : undefined;
|
|
264
|
+
});
|
|
244
265
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
266
|
+
pi.on("before_provider_request", (event, ctx) => {
|
|
267
|
+
if (!settingsRuntime.get().settings.enabled) return undefined;
|
|
268
|
+
const settings = settingsRuntime.get().settings;
|
|
269
|
+
const checkpoint = activeCheckpoint(ctx);
|
|
270
|
+
if (!checkpoint || !isCheckpointCompatible(checkpoint.details, ctx.model, settings)) return undefined;
|
|
271
|
+
const marker = checkpointMarker(checkpoint.details.checkpointId);
|
|
272
|
+
if (!hasCheckpointMarker(event.payload, marker)) return undefined;
|
|
273
|
+
return rewriteCheckpointMarker(event.payload, marker, checkpoint.details.replacementHistory);
|
|
274
|
+
});
|
|
253
275
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
276
|
+
pi.on("model_select", (event, ctx) => {
|
|
277
|
+
if (!settingsRuntime.get().settings.enabled) return;
|
|
278
|
+
const settings = settingsRuntime.get().settings;
|
|
279
|
+
const checkpoint = activeCheckpoint(ctx);
|
|
280
|
+
if (!checkpoint || isCheckpointCompatible(checkpoint.details, event.model, settings)) return;
|
|
281
|
+
const key = `${ctx.sessionManager.getSessionId()}:${event.model.provider}:${event.model.id}`;
|
|
282
|
+
if (providerWarnings.has(key)) return;
|
|
283
|
+
providerWarnings.add(key);
|
|
284
|
+
if (ctx.hasUI) {
|
|
285
|
+
ctx.ui.notify(
|
|
286
|
+
"The active Responses checkpoint cannot replay on this model; Pi will expose only its fallback marker and retained recent messages.",
|
|
287
|
+
"warning",
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
268
291
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
292
|
+
pi.on("session_shutdown", async (_event, ctx) => {
|
|
293
|
+
generation += 1;
|
|
294
|
+
sessionController.abort();
|
|
295
|
+
providerWarnings.clear();
|
|
296
|
+
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
297
|
+
await settingsRuntime.flush();
|
|
298
|
+
});
|
|
299
|
+
};
|
|
277
300
|
}
|
|
278
301
|
|
|
279
302
|
export default createCodexCompactExtension();
|
package/src/model-api.ts
CHANGED
|
@@ -1,8 +1,59 @@
|
|
|
1
1
|
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
2
|
-
import {
|
|
2
|
+
import type { CodexCompactSettings } from "./settings.js";
|
|
3
3
|
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
export const RESPONSES_COMPACTION_APIS = [
|
|
5
|
+
"openai-codex-responses",
|
|
6
|
+
"openai-responses",
|
|
7
|
+
"azure-openai-responses",
|
|
8
|
+
] as const;
|
|
9
|
+
|
|
10
|
+
export type BuiltInResponsesCompactionApi = (typeof RESPONSES_COMPACTION_APIS)[number];
|
|
11
|
+
export type ResponsesCompactionApi = BuiltInResponsesCompactionApi;
|
|
12
|
+
export type ResponsesCompactionProfile = "codex-responses-v1" | "openai-responses-v1";
|
|
13
|
+
export type RemoteCompactionProtocol = "remote-v2" | "responses-compact";
|
|
14
|
+
export type RemoteCompactionProtocolSetting = "auto" | RemoteCompactionProtocol;
|
|
15
|
+
|
|
16
|
+
export type CompactionRoute =
|
|
17
|
+
| { kind: "remote"; protocol: RemoteCompactionProtocol; api: Api; profile: ResponsesCompactionProfile }
|
|
18
|
+
| { kind: "native"; reason: string };
|
|
19
|
+
|
|
20
|
+
function resolveResponsesCompactionProfile(
|
|
21
|
+
api: Api | undefined,
|
|
22
|
+
apiProfiles: Readonly<Record<string, "codex-responses-v1">> = {},
|
|
23
|
+
): ResponsesCompactionProfile | undefined {
|
|
24
|
+
if (api === "openai-codex-responses") return "codex-responses-v1";
|
|
25
|
+
if (api === "openai-responses" || api === "azure-openai-responses") return "openai-responses-v1";
|
|
26
|
+
return api && Object.hasOwn(apiProfiles, api) && apiProfiles[api] === "codex-responses-v1"
|
|
27
|
+
? "codex-responses-v1"
|
|
28
|
+
: undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function resolveCompactionRouteForApi(
|
|
32
|
+
api: Api | undefined,
|
|
33
|
+
options: Pick<CodexCompactSettings, "enabled" | "protocol"> & {
|
|
34
|
+
apiProfiles?: Readonly<Record<string, "codex-responses-v1">>;
|
|
35
|
+
},
|
|
36
|
+
): CompactionRoute {
|
|
37
|
+
if (!options.enabled) return { kind: "native", reason: "remote compaction is disabled" };
|
|
38
|
+
if (!api) return { kind: "native", reason: "no active model" };
|
|
39
|
+
const profile = resolveResponsesCompactionProfile(api, options.apiProfiles);
|
|
40
|
+
if (!profile) {
|
|
41
|
+
return { kind: "native", reason: `API ${api} does not support Responses compaction` };
|
|
42
|
+
}
|
|
43
|
+
const protocol =
|
|
44
|
+
options.protocol === "auto"
|
|
45
|
+
? profile === "codex-responses-v1"
|
|
46
|
+
? "remote-v2"
|
|
47
|
+
: "responses-compact"
|
|
48
|
+
: options.protocol;
|
|
49
|
+
return { kind: "remote", protocol, api, profile };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function resolveCompactionRoute(
|
|
53
|
+
model: Model<Api> | undefined,
|
|
54
|
+
options: Pick<CodexCompactSettings, "enabled" | "protocol"> & {
|
|
55
|
+
apiProfiles?: Readonly<Record<string, "codex-responses-v1">>;
|
|
56
|
+
},
|
|
57
|
+
): CompactionRoute {
|
|
58
|
+
return resolveCompactionRouteForApi(model?.api, options);
|
|
8
59
|
}
|