pi-openai-toolkit 0.23.2 → 0.24.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 +4 -15
- package/README.zh.md +4 -15
- package/config.schema.json +5 -19
- package/docs/configuration.md +19 -21
- package/docs/internals.md +34 -120
- package/package.json +3 -16
- package/src/config/legacy.ts +12 -62
- package/src/config/migration.ts +19 -7
- package/src/config/notifications.ts +10 -4
- package/src/config/policy.ts +9 -10
- package/src/config/v2.ts +12 -51
- package/src/config.ts +20 -10
- package/src/extension-runtime.ts +163 -987
- package/src/native-fallback.ts +11 -23
- package/src/responses-headers.ts +1 -1
- package/src/retired-remote-windows.ts +35 -0
- package/src/runtime.ts +12 -2
- package/src/types.ts +4 -28
- package/src/context-management/codex-provider.ts +0 -270
- package/src/context-management/history-notes.ts +0 -357
- package/src/context-management/manual-compact.ts +0 -247
- package/src/context-management/messages.ts +0 -238
- package/src/context-management/namespace-tools.ts +0 -426
- package/src/context-management/responses-tools.ts +0 -238
- package/src/context-management/scoped-compaction.ts +0 -89
- package/src/context-management/tool-contract.ts +0 -62
- package/src/context-management/tools.ts +0 -371
- package/src/context-management/types.ts +0 -168
- package/src/context-management/window-budget.ts +0 -83
- package/src/context-management/window-bulk.ts +0 -155
- package/src/context-management/window-manager.ts +0 -912
- package/src/context-management/window-request.ts +0 -125
package/src/extension-runtime.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from "node:util";
|
|
2
2
|
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
3
|
-
import { declarationsEqual, getCurrentSystemMessage, getCurrentTools } from "@earendil-works/pi-ai";
|
|
4
|
-
import { convertResponsesTools } from "./context-management/responses-tools";
|
|
5
3
|
import type {
|
|
6
4
|
BeforeProviderRequestEvent,
|
|
7
5
|
ContextWithSystemEvent,
|
|
@@ -13,25 +11,6 @@ import type {
|
|
|
13
11
|
import { assertConfigValid, loadToolkitConfig, resolveToolkitConfig, type ResolvedToolkitConfig } from "./config";
|
|
14
12
|
import { notifyConfigIssues } from "./config/notifications";
|
|
15
13
|
import { registerToolkitConfigCommand } from "./config-command";
|
|
16
|
-
import {
|
|
17
|
-
codexContextProviderHeaders,
|
|
18
|
-
resolveCodexContextProvider,
|
|
19
|
-
isCodexGatewayModel,
|
|
20
|
-
isNativeCodexModel,
|
|
21
|
-
} from "./context-management/codex-provider";
|
|
22
|
-
import { rewriteContextNamespaceCalls, rewriteContextNamespaceTools, routeContextNamespaceToolMessage } from "./context-management/namespace-tools";
|
|
23
|
-
import { rewriteEncryptedToolOutputs } from "./context-management/window-request";
|
|
24
|
-
import { loadHistoryNotesThreadHint } from "./context-management/history-notes";
|
|
25
|
-
import { CodexContextWindowManager } from "./context-management/window-manager";
|
|
26
|
-
import { ManagedManualCompact } from "./context-management/manual-compact";
|
|
27
|
-
import { prepareScopedNativeCompaction } from "./context-management/scoped-compaction";
|
|
28
|
-
import type { RemoteWindowCompactionInput } from "./context-management/window-manager";
|
|
29
|
-
import { CONTEXT_WINDOW_COMPACTION_SUMMARY, isSystemTranscriptMessage } from "./context-management/messages";
|
|
30
|
-
import { isRecord } from "./context-management/types";
|
|
31
|
-
import {
|
|
32
|
-
registerContextManagementTools,
|
|
33
|
-
type ContextToolRegistrationState,
|
|
34
|
-
} from "./context-management/tools";
|
|
35
14
|
import { writeDebugArtifact, writeReplayFailureArtifact } from "./debug";
|
|
36
15
|
import {
|
|
37
16
|
COMPACTION_CHECKPOINT_PROVENANCE_UNAVAILABLE,
|
|
@@ -42,7 +21,9 @@ import {
|
|
|
42
21
|
hasVerifiedCompactionInputProvenance,
|
|
43
22
|
type UnprojectedCompactionReason,
|
|
44
23
|
} from "./compaction-projection";
|
|
45
|
-
import { CODEX_GATEWAY_FORWARD_HEADERS } from "./responses-headers";
|
|
24
|
+
import { buildResponsesRequestHeaders, CODEX_GATEWAY_FORWARD_HEADERS } from "./responses-headers";
|
|
25
|
+
import { isExactModelAllowed } from "./model-scope";
|
|
26
|
+
import { detectRetiredRemoteWindows, RETIRED_REMOTE_WINDOWS_MESSAGE } from "./retired-remote-windows";
|
|
46
27
|
import { executeNativeCompaction, type NativeCompactionClientResult } from "./compact-client";
|
|
47
28
|
import { qualifiesForStandaloneCompaction, type CompactionErrorInfo } from "./compaction-errors";
|
|
48
29
|
import { resolveLatestNativeCompactionEntry } from "./details-store";
|
|
@@ -63,8 +44,7 @@ import { getCompactionRequestExtras, rememberRequestContext } from "./request-co
|
|
|
63
44
|
import { resolveWebSearchRoute } from "./web-search/types";
|
|
64
45
|
import { executeRemoteV2Compaction } from "./remote-v2-client";
|
|
65
46
|
import {
|
|
66
|
-
|
|
67
|
-
resolveEffectiveModel,
|
|
47
|
+
registerPhysicalModelObserver,
|
|
68
48
|
resolveNativeCompactionEnvironment,
|
|
69
49
|
resolveRemoteCompactionExecution,
|
|
70
50
|
isStandaloneCompactionEligible,
|
|
@@ -83,15 +63,11 @@ import {
|
|
|
83
63
|
COMPACTION_EXTENSION_ID,
|
|
84
64
|
LEGACY_NATIVE_COMPACTION_STRATEGY,
|
|
85
65
|
REMOTE_V2_COMPACTION_STRATEGY,
|
|
86
|
-
NATIVE_COMPACTION_FALLBACK_SUMMARY,
|
|
87
|
-
STANDALONE_COMPACTION_FALLBACK_SUMMARY,
|
|
88
66
|
getLatestDeferredToolCarryover,
|
|
89
67
|
getRemoteV2InputProvenance,
|
|
90
68
|
isNativeCompactionDetails,
|
|
91
69
|
type CompactionConfig,
|
|
92
|
-
type DeferredToolCarryoverV1,
|
|
93
70
|
type NativeCompactionDetails,
|
|
94
|
-
type NativeCompactionEntry,
|
|
95
71
|
type NativeCompactionRequestMeta,
|
|
96
72
|
} from "./types";
|
|
97
73
|
|
|
@@ -116,38 +92,52 @@ function requireContextPolicy(resolved: ResolvedToolkitConfig, ctx: ExtensionCon
|
|
|
116
92
|
}
|
|
117
93
|
}
|
|
118
94
|
|
|
119
|
-
function compactGatewayModels(resolved: ResolvedToolkitConfig): readonly string[] {
|
|
120
|
-
return resolved.format === "v2" || resolved.config.compaction.contextManagement === "remote"
|
|
121
|
-
? resolved.gatewayModelKeys : [];
|
|
122
|
-
}
|
|
123
|
-
|
|
124
95
|
type CompactionDependencies = {
|
|
125
96
|
loadConfig: typeof loadToolkitConfig;
|
|
126
97
|
remoteCompact: typeof executeRemoteV2Compaction;
|
|
127
98
|
standaloneCompact: typeof executeNativeCompaction;
|
|
128
99
|
nativeFallback: typeof runNativeFallbackCompaction;
|
|
129
|
-
|
|
130
|
-
manualCompact?: ManagedManualCompact;
|
|
131
|
-
/** Read-only fence shared by all scoped producer tiers. */
|
|
100
|
+
/** Revalidate operation ownership after awaits and at result consumption. */
|
|
132
101
|
isCurrentCompaction?: () => boolean;
|
|
133
102
|
/** Test seam for the same ordered projection exposed by the Pi host patch. */
|
|
134
103
|
projectCompactionContext?: CompactionContextProjection;
|
|
135
104
|
piContextHookPatch: PiContextHookPatchResult;
|
|
136
105
|
};
|
|
137
106
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
107
|
+
// Notices are bounded, but every boundary freshly reads the current branch.
|
|
108
|
+
const retirementNotices = new WeakMap<object, string>();
|
|
109
|
+
function retirementReason(ctx: ExtensionContext): string | undefined {
|
|
110
|
+
try { return detectRetiredRemoteWindows(ctx.sessionManager.getBranch()); }
|
|
111
|
+
catch { return "branch-unavailable"; }
|
|
112
|
+
}
|
|
144
113
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
114
|
+
function refuseRetiredSession(ctx: ExtensionContext, request = false): boolean {
|
|
115
|
+
const reason = retirementReason(ctx);
|
|
116
|
+
const owner = ctx.sessionManager;
|
|
117
|
+
if (!reason) {
|
|
118
|
+
retirementNotices.delete(owner);
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
const fingerprint = `${getSessionId(ctx) ?? ""}:${reason}`;
|
|
122
|
+
if (ctx.hasUI && retirementNotices.get(owner) !== fingerprint) {
|
|
123
|
+
retirementNotices.set(owner, fingerprint);
|
|
124
|
+
try {
|
|
125
|
+
ctx.ui.notify(
|
|
126
|
+
reason === "branch-unavailable"
|
|
127
|
+
? `${COMPACTION_EXTENSION_ID}: Cannot verify the active session branch; request blocked. Reopen the session before continuing.`
|
|
128
|
+
: `${COMPACTION_EXTENSION_ID}: ${RETIRED_REMOTE_WINDOWS_MESSAGE}`,
|
|
129
|
+
"error",
|
|
130
|
+
);
|
|
131
|
+
} catch {
|
|
132
|
+
// A detached UI must not prevent abort or turn compact refusal into fallback.
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (request) {
|
|
136
|
+
reportPiContextHookFailure(ctx, `retired-remote-windows:${reason}`);
|
|
137
|
+
ctx.abort();
|
|
138
|
+
}
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
151
141
|
|
|
152
142
|
type ResponsesCompactOutcome =
|
|
153
143
|
| { outcome: "success"; compaction: CompactionResult<NativeCompactionDetails> }
|
|
@@ -200,87 +190,6 @@ function notifyWarning(ctx: ExtensionContext, message: string): void {
|
|
|
200
190
|
}
|
|
201
191
|
}
|
|
202
192
|
|
|
203
|
-
async function resolveContextCapabilityModel(
|
|
204
|
-
ctx: ExtensionContext,
|
|
205
|
-
model: ExtensionContext["model"] = ctx.model,
|
|
206
|
-
): Promise<ExtensionContext["model"]> {
|
|
207
|
-
const effective = await resolveEffectiveModel(ctx, model);
|
|
208
|
-
return effective.ok ? effective.model : undefined;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
async function isRemoteContextActive(
|
|
212
|
-
ctx: ExtensionContext,
|
|
213
|
-
config: CompactionConfig,
|
|
214
|
-
model: ExtensionContext["model"] = ctx.model,
|
|
215
|
-
): Promise<boolean> {
|
|
216
|
-
if (!config.enabled || config.contextManagement !== "remote") return false;
|
|
217
|
-
const resolution = await resolveCodexContextProvider(ctx, model, config.gatewayContextModels);
|
|
218
|
-
return resolution.ok;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function isCodexContextModel(
|
|
222
|
-
model: ExtensionContext["model"] | undefined,
|
|
223
|
-
config: CompactionConfig,
|
|
224
|
-
): boolean {
|
|
225
|
-
return config.contextManagement === "remote"
|
|
226
|
-
&& (isNativeCodexModel(model) || isCodexGatewayModel(model, config.gatewayContextModels));
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function isExcludedGatewayContext(
|
|
230
|
-
model: ExtensionContext["model"] | undefined,
|
|
231
|
-
config: CompactionConfig,
|
|
232
|
-
registrationState: ContextToolRegistrationState,
|
|
233
|
-
): boolean {
|
|
234
|
-
return registrationState === "excluded" && isCodexGatewayModel(model, config.gatewayContextModels);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function contextManagementFailureReason(registrationState: ContextToolRegistrationState): string {
|
|
238
|
-
switch (registrationState) {
|
|
239
|
-
case "excluded":
|
|
240
|
-
return "context-tools-excluded";
|
|
241
|
-
case "conflict":
|
|
242
|
-
return "tool-name-conflict";
|
|
243
|
-
case "unverified":
|
|
244
|
-
return "tool-registration-pending";
|
|
245
|
-
case "verified":
|
|
246
|
-
return "context-management-unavailable";
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function notifyRemoteContextFailure(ctx: ExtensionContext, reason: string): void {
|
|
251
|
-
if (ctx.hasUI) {
|
|
252
|
-
ctx.ui.notify(`${COMPACTION_EXTENSION_ID}: Remote Context management inactive (${reason})`, "warning");
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Surface every window trim that had to be refused to keep the tool loadout intact.
|
|
258
|
-
*
|
|
259
|
-
* The refusal itself is safe: the model keeps the previous turns instead of losing its
|
|
260
|
-
* tools. It is still a continuity incident, so it is recorded once per window and reason
|
|
261
|
-
* instead of being allowed to pass silently.
|
|
262
|
-
*/
|
|
263
|
-
function reportProjectionDiagnostics(
|
|
264
|
-
contextWindows: CodexContextWindowManager,
|
|
265
|
-
config: CompactionConfig,
|
|
266
|
-
ctx: ExtensionContext,
|
|
267
|
-
): void {
|
|
268
|
-
const diagnostics = contextWindows.takeProjectionDiagnostics();
|
|
269
|
-
if (diagnostics.length === 0) return;
|
|
270
|
-
writeDebugArtifact(
|
|
271
|
-
"compaction-event",
|
|
272
|
-
{ event: "context.projection.tool_loadout_shrink", diagnostics },
|
|
273
|
-
config,
|
|
274
|
-
ctx,
|
|
275
|
-
);
|
|
276
|
-
if (ctx.hasUI) {
|
|
277
|
-
ctx.ui.notify(
|
|
278
|
-
`${COMPACTION_EXTENSION_ID}: kept previous context to preserve tools (${diagnostics.join("; ")})`,
|
|
279
|
-
"warning",
|
|
280
|
-
);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
193
|
function cloneOpaqueWindow(window: readonly unknown[]): unknown[] {
|
|
285
194
|
return window.map((item) => structuredClone(item));
|
|
286
195
|
}
|
|
@@ -325,9 +234,8 @@ type CompactionProjectionResult =
|
|
|
325
234
|
async function projectSessionContextForCompaction(
|
|
326
235
|
ctx: ExtensionContext,
|
|
327
236
|
projectCompactionContext: CompactionContextProjection | undefined,
|
|
328
|
-
canonicalMessages?: readonly AgentMessage[],
|
|
329
237
|
): Promise<CompactionProjectionResult> {
|
|
330
|
-
const messages =
|
|
238
|
+
const messages = readSessionContextMessages(ctx);
|
|
331
239
|
if (!messages) {
|
|
332
240
|
return { ok: false, reason: COMPACTION_SESSION_CONTEXT_UNAVAILABLE };
|
|
333
241
|
}
|
|
@@ -357,187 +265,6 @@ function getLegacySessionContextMessages(
|
|
|
357
265
|
];
|
|
358
266
|
}
|
|
359
267
|
|
|
360
|
-
type ScopedResponsesCompactInput = {
|
|
361
|
-
request: NativeCompactionRequestBody;
|
|
362
|
-
firstKeptEntryId: string;
|
|
363
|
-
deferredToolCarryover?: DeferredToolCarryoverV1;
|
|
364
|
-
requestSource: "session-context" | "non-native-session-context" | "latest-native-replay";
|
|
365
|
-
isCurrent: () => boolean;
|
|
366
|
-
};
|
|
367
|
-
|
|
368
|
-
/** Reject incomplete/ambiguous tool batches rather than let serialization invent results. */
|
|
369
|
-
function requireCompleteCompactionToolPairs(messages: readonly AgentMessage[]): ReadonlyMap<string, string | undefined> {
|
|
370
|
-
const calls = new Map<string, string | undefined>();
|
|
371
|
-
const pending = new Set<string>();
|
|
372
|
-
for (const message of messages) {
|
|
373
|
-
if (message.role === "assistant") {
|
|
374
|
-
if (pending.size) throw new Error("compaction-tool-pair-mismatch");
|
|
375
|
-
for (const block of message.content) {
|
|
376
|
-
if (block.type !== "toolCall") continue;
|
|
377
|
-
const callId = block.id.split("|")[0];
|
|
378
|
-
if (!callId || calls.has(callId)) throw new Error("compaction-tool-pair-mismatch");
|
|
379
|
-
calls.set(callId, block.namespace);
|
|
380
|
-
pending.add(block.id);
|
|
381
|
-
}
|
|
382
|
-
} else if (message.role === "toolResult") {
|
|
383
|
-
if (!pending.delete(message.toolCallId)) throw new Error("compaction-tool-pair-mismatch");
|
|
384
|
-
} else if (pending.size && !isSystemTranscriptMessage(message)) {
|
|
385
|
-
throw new Error("compaction-tool-pair-mismatch");
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
if (pending.size) throw new Error("compaction-tool-pair-mismatch");
|
|
389
|
-
return calls;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
/** Serialization must preserve every verified pair, with unique provider call IDs. */
|
|
393
|
-
function requireCompactionWireToolPairs(input: readonly unknown[], namespaces: ReadonlyMap<string, string | undefined>): void {
|
|
394
|
-
const calls = new Set<string>();
|
|
395
|
-
const pending = new Set<string>();
|
|
396
|
-
for (const item of input) {
|
|
397
|
-
if (!isRecord(item)) throw new Error("compaction-tool-pair-mismatch");
|
|
398
|
-
if (item.type === "function_call") {
|
|
399
|
-
if (typeof item.call_id !== "string" || !namespaces.has(item.call_id) || calls.has(item.call_id)) {
|
|
400
|
-
throw new Error("compaction-tool-pair-mismatch");
|
|
401
|
-
}
|
|
402
|
-
calls.add(item.call_id);
|
|
403
|
-
pending.add(item.call_id);
|
|
404
|
-
} else if (item.type === "function_call_output") {
|
|
405
|
-
if (typeof item.call_id !== "string" || !pending.delete(item.call_id)) throw new Error("compaction-tool-pair-mismatch");
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
if (pending.size || calls.size !== namespaces.size) throw new Error("compaction-tool-pair-mismatch");
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
function requireCompactionHeadAndRetention(
|
|
412
|
-
canonical: readonly AgentMessage[],
|
|
413
|
-
projected: readonly AgentMessage[],
|
|
414
|
-
retained: readonly AgentMessage[],
|
|
415
|
-
): void {
|
|
416
|
-
const before = getCurrentTools(canonical);
|
|
417
|
-
const after = getCurrentTools(projected);
|
|
418
|
-
if (!getCurrentSystemMessage(projected) || before.length !== after.length
|
|
419
|
-
|| before.some((tool, index) => !after[index] || !declarationsEqual(tool, after[index]!))) {
|
|
420
|
-
throw new Error("compaction-system-tool-head-lost");
|
|
421
|
-
}
|
|
422
|
-
// These exact source contributions will accompany the new summary in Pi.
|
|
423
|
-
// If a hook rewrites/omits one, future strict retained-copy removal cannot be proven.
|
|
424
|
-
let cursor = 0;
|
|
425
|
-
for (const message of retained) {
|
|
426
|
-
if (message.role === "custom" || message.role === "compactionSummary" || isSystemTranscriptMessage(message)) continue;
|
|
427
|
-
const index = projected.findIndex((candidate, index) => index >= cursor && isDeepStrictEqual(candidate, message));
|
|
428
|
-
if (index < 0) throw new Error("compaction-retained-context-mismatch");
|
|
429
|
-
cursor = index + 1;
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
/** Apply only pure wire adapters, never ordinary turn/history-ingest hooks. */
|
|
434
|
-
function adaptCompactionWireInput(input: ResponsesInputItem[], namespaces: ReadonlyMap<string, string | undefined>): ResponsesInputItem[] {
|
|
435
|
-
// The shared legacy serializer omits namespace. Restore it from the verified
|
|
436
|
-
// canonical call before applying the existing context-router wire adapter.
|
|
437
|
-
const namespaced = input.map((item: unknown) => {
|
|
438
|
-
if (!isRecord(item) || item.type !== "function_call" || typeof item.call_id !== "string") return item;
|
|
439
|
-
const namespace = namespaces.get(item.call_id);
|
|
440
|
-
return namespace ? { ...item, namespace } : item;
|
|
441
|
-
});
|
|
442
|
-
const adapted = rewriteEncryptedToolOutputs(rewriteContextNamespaceCalls({ input: namespaced }));
|
|
443
|
-
if (!isRecord(adapted) || !Array.isArray(adapted.input)) throw new Error("compaction-wire-adaptation-failed");
|
|
444
|
-
// The adapters preserve the validated array shape, with native encrypted outputs.
|
|
445
|
-
return adapted.input as ResponsesInputItem[];
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
async function prepareScopedResponsesCompaction(
|
|
449
|
-
event: SessionBeforeCompactEvent,
|
|
450
|
-
ctx: ExtensionContext,
|
|
451
|
-
config: CompactionConfig,
|
|
452
|
-
execution: RemoteCompactionExecution,
|
|
453
|
-
dependencies: CompactionDependencies,
|
|
454
|
-
isCurrent: () => boolean,
|
|
455
|
-
prepared: RemoteWindowCompactionInput,
|
|
456
|
-
): Promise<ScopedResponsesCompactInput> {
|
|
457
|
-
const windows = dependencies.contextWindows;
|
|
458
|
-
const branch = ctx.sessionManager.getBranch();
|
|
459
|
-
const provenance = getRemoteV2InputProvenance(config.remoteV2ContextSource);
|
|
460
|
-
let checkpoint: NativeCompactionEntry | undefined;
|
|
461
|
-
let requestSource: ScopedResponsesCompactInput["requestSource"] = "session-context";
|
|
462
|
-
if (prepared.owningCompaction) {
|
|
463
|
-
const latest = resolveLatestNativeCompactionEntry(branch, { baseUrl: execution.consumer.baseUrl });
|
|
464
|
-
if (latest.ok) {
|
|
465
|
-
if (latest.entry.id !== prepared.owningCompaction.id) throw new Error("compaction-owner-mismatch");
|
|
466
|
-
if (hasInvalidatedCompactionContext(branch, latest.entry.id)) throw new Error(COMPACTION_CHECKPOINT_CONTEXT_EDITED);
|
|
467
|
-
if (!hasVerifiedCompactionInputProvenance(latest.entry.details, provenance)) throw new Error(COMPACTION_CHECKPOINT_PROVENANCE_UNAVAILABLE);
|
|
468
|
-
checkpoint = latest.entry;
|
|
469
|
-
requestSource = "latest-native-replay";
|
|
470
|
-
} else {
|
|
471
|
-
if (latest.reason !== "latest-compaction-not-native" || !config.allowCompactionContinuityBreak) throw new Error("compaction-continuity-unavailable");
|
|
472
|
-
requestSource = "non-native-session-context";
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
let messages = prepared.messages;
|
|
476
|
-
if (config.remoteV2ContextSource === "pi-context-hook") {
|
|
477
|
-
const projection = await windows.withCompactionProjection(() => projectSessionContextForCompaction(
|
|
478
|
-
ctx, dependencies.projectCompactionContext, messages,
|
|
479
|
-
));
|
|
480
|
-
if (!isCurrent()) throw new Error("compaction-operation-superseded");
|
|
481
|
-
if (!projection.ok) throw new Error(projection.reason);
|
|
482
|
-
messages = windows.projectCompactionContext(projection.messages);
|
|
483
|
-
}
|
|
484
|
-
if (!isCurrent()) throw new Error("compaction-operation-superseded");
|
|
485
|
-
requireCompactionHeadAndRetention(prepared.messages, messages, prepared.retainedMessages);
|
|
486
|
-
if (checkpoint) {
|
|
487
|
-
// The synthetic Toolkit hook deliberately leaves these copies alone. Match
|
|
488
|
-
// exactly once, BEFORE encrypted-output markers change the canonical values.
|
|
489
|
-
const pruned = removeNativeCompactionRetainedMessages({
|
|
490
|
-
messages, branchEntries: branch, compactionEntry: checkpoint, expectedInputProvenance: provenance,
|
|
491
|
-
});
|
|
492
|
-
if (!pruned.ok) throw new Error(pruned.reason);
|
|
493
|
-
messages = pruned.messages;
|
|
494
|
-
}
|
|
495
|
-
const namespaces = requireCompleteCompactionToolPairs(messages);
|
|
496
|
-
const tools = getCurrentTools(messages);
|
|
497
|
-
const model = execution.compactor.currentModel;
|
|
498
|
-
const compat = isRecord(model.compat) ? model.compat : {};
|
|
499
|
-
const nativeCodex = model.api === "openai-codex-responses";
|
|
500
|
-
const encoded = windows.projectCompactionContext(messages, true);
|
|
501
|
-
let input: ResponsesInputItem[];
|
|
502
|
-
if (checkpoint) {
|
|
503
|
-
const summaryIndex = findLatestProjectedCompactionSummaryIndex(encoded, checkpoint.summary);
|
|
504
|
-
if (summaryIndex < 0 || encoded.filter((message) => message.role === "compactionSummary").length !== 1) {
|
|
505
|
-
throw new Error(COMPACTION_PROJECTED_CONTEXT_UNAVAILABLE);
|
|
506
|
-
}
|
|
507
|
-
const leading = await serializeMessagesToResponsesInputWithLimits(model, encoded.slice(0, summaryIndex));
|
|
508
|
-
if (!isCurrent()) throw new Error("compaction-operation-superseded");
|
|
509
|
-
const tail = await serializeMessagesToResponsesInputWithLimits(model, encoded.slice(summaryIndex + 1), { firstSystemMessageIsUpdate: true });
|
|
510
|
-
requireCompactionWireToolPairs([...leading, ...tail], namespaces);
|
|
511
|
-
const details = checkpoint.details;
|
|
512
|
-
if (!details) throw new Error("compaction-owner-mismatch");
|
|
513
|
-
input = [...adaptCompactionWireInput(leading, namespaces), ...(cloneOpaqueWindow(details.compactedWindow) as ResponsesInputItem[]), ...adaptCompactionWireInput(tail, namespaces)];
|
|
514
|
-
} else {
|
|
515
|
-
const serialized = await serializeMessagesToResponsesInputWithLimits(model, encoded);
|
|
516
|
-
requireCompactionWireToolPairs(serialized, namespaces);
|
|
517
|
-
input = adaptCompactionWireInput(serialized, namespaces);
|
|
518
|
-
}
|
|
519
|
-
if (!isCurrent()) throw new Error("compaction-operation-superseded");
|
|
520
|
-
const toolPayload = rewriteContextNamespaceTools({ tools: convertResponsesTools(tools, {
|
|
521
|
-
...(nativeCodex ? { strict: null } : {}),
|
|
522
|
-
supportsStrictMode: typeof compat.supportsStrictMode === "boolean" ? compat.supportsStrictMode : nativeCodex,
|
|
523
|
-
supportsOpenAIGrammarTools: compat.supportsOpenAIGrammarTools === true,
|
|
524
|
-
}) }, { encrypted: true });
|
|
525
|
-
if (!isRecord(toolPayload) || !Array.isArray(toolPayload.tools)) throw new Error("compaction-wire-adaptation-failed");
|
|
526
|
-
// Current cache-stack state is authoritative; never borrow deferred metadata
|
|
527
|
-
// from an unrelated pre-window checkpoint. The canonical head proves which
|
|
528
|
-
// of those schemas are still live, including tools activated before rollover.
|
|
529
|
-
const activation = getLatestDeferredToolCarryover(branch.slice(prepared.windowBoundaryIndex)) ?? checkpoint?.details?.deferredToolCarryover;
|
|
530
|
-
const names = new Set(tools.map((tool) => tool.name));
|
|
531
|
-
const deferredToolCarryover = activation ? { ...activation, toolNames: activation.toolNames.filter((name) => names.has(name)) } : undefined;
|
|
532
|
-
return {
|
|
533
|
-
request: { model: execution.compactor.requestModel ?? execution.compactor.model, input,
|
|
534
|
-
instructions: buildCompactionInstructions(ctx.getSystemPrompt(), event.customInstructions), tools: toolPayload.tools },
|
|
535
|
-
firstKeptEntryId: prepared.firstKeptEntryId,
|
|
536
|
-
deferredToolCarryover: deferredToolCarryover?.toolNames.length ? deferredToolCarryover : undefined,
|
|
537
|
-
requestSource, isCurrent,
|
|
538
|
-
};
|
|
539
|
-
}
|
|
540
|
-
|
|
541
268
|
async function runResponsesNativeCompact(
|
|
542
269
|
event: SessionBeforeCompactEvent,
|
|
543
270
|
ctx: ExtensionContext,
|
|
@@ -545,15 +272,13 @@ async function runResponsesNativeCompact(
|
|
|
545
272
|
execution: RemoteCompactionExecution,
|
|
546
273
|
remoteCompact: typeof executeRemoteV2Compaction,
|
|
547
274
|
projectCompactionContext: CompactionContextProjection | undefined,
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
/** Window scope is independent of the ordinary producer/fallback policy. */
|
|
551
|
-
scopedInput?: ScopedResponsesCompactInput,
|
|
275
|
+
standaloneCompact: typeof executeNativeCompaction,
|
|
276
|
+
isCurrent: () => boolean,
|
|
552
277
|
): Promise<ResponsesCompactOutcome> {
|
|
553
278
|
const { consumer, compactor } = execution;
|
|
554
279
|
const instructions = buildCompactionInstructions(ctx.getSystemPrompt(), event.customInstructions);
|
|
555
280
|
const branchEntries = event.branchEntries ?? ctx.sessionManager.getBranch();
|
|
556
|
-
const deferredToolCarryover =
|
|
281
|
+
const deferredToolCarryover = getLatestDeferredToolCarryover(branchEntries);
|
|
557
282
|
const latestNativeCompaction = resolveLatestNativeCompactionEntry(branchEntries, {
|
|
558
283
|
baseUrl: consumer.baseUrl,
|
|
559
284
|
});
|
|
@@ -561,10 +286,7 @@ async function runResponsesNativeCompact(
|
|
|
561
286
|
const inputProvenance = getRemoteV2InputProvenance(config.remoteV2ContextSource);
|
|
562
287
|
let requestSource: "session-context" | "non-native-session-context" | "latest-native-replay";
|
|
563
288
|
let request: NativeCompactionRequestBody;
|
|
564
|
-
if (
|
|
565
|
-
request = scopedInput.request;
|
|
566
|
-
requestSource = scopedInput.requestSource;
|
|
567
|
-
} else if (latestNativeCompaction.ok) {
|
|
289
|
+
if (latestNativeCompaction.ok) {
|
|
568
290
|
if (hasInvalidatedCompactionContext(branchEntries, latestNativeCompaction.entry.id)) {
|
|
569
291
|
return { outcome: "unprojected-input", reason: COMPACTION_CHECKPOINT_CONTEXT_EDITED };
|
|
570
292
|
}
|
|
@@ -683,10 +405,10 @@ async function runResponsesNativeCompact(
|
|
|
683
405
|
sessionId: getSessionId(ctx),
|
|
684
406
|
}, compactor.currentModel);
|
|
685
407
|
if (extras) {
|
|
686
|
-
request = { ...request, ...extras
|
|
408
|
+
request = { ...request, ...extras };
|
|
687
409
|
}
|
|
688
410
|
|
|
689
|
-
if (event.signal?.aborted || (
|
|
411
|
+
if (event.signal?.aborted || !isCurrent() || refuseRetiredSession(ctx)) return { outcome: "aborted" };
|
|
690
412
|
let protocol: "remote-v2" | "standalone-compact" = "remote-v2";
|
|
691
413
|
let compactResult: Awaited<ReturnType<typeof executeRemoteV2Compaction>> | NativeCompactionClientResult = await remoteCompact({
|
|
692
414
|
runtime: compactor,
|
|
@@ -696,7 +418,7 @@ async function runResponsesNativeCompact(
|
|
|
696
418
|
context: ctx,
|
|
697
419
|
});
|
|
698
420
|
|
|
699
|
-
if (
|
|
421
|
+
if (!isCurrent() || refuseRetiredSession(ctx)) return { outcome: "aborted" };
|
|
700
422
|
if (!compactResult.ok) {
|
|
701
423
|
if (compactResult.reason === "aborted" || event.signal?.aborted) return { outcome: "aborted" };
|
|
702
424
|
const tryStandalone = !!standaloneCompact && isStandaloneCompactionEligible(compactor) && qualifiesForStandaloneCompaction(compactResult);
|
|
@@ -712,7 +434,7 @@ async function runResponsesNativeCompact(
|
|
|
712
434
|
nextStep: tryStandalone ? "standalone-compact" : "native-summary",
|
|
713
435
|
}, config, ctx);
|
|
714
436
|
if (tryStandalone && standaloneCompact) {
|
|
715
|
-
if (event.signal?.aborted) return { outcome: "aborted" };
|
|
437
|
+
if (event.signal?.aborted || !isCurrent() || refuseRetiredSession(ctx)) return { outcome: "aborted" };
|
|
716
438
|
protocol = "standalone-compact";
|
|
717
439
|
compactResult = await standaloneCompact({ runtime: compactor, request, signal: event.signal, settings: config, context: ctx });
|
|
718
440
|
}
|
|
@@ -780,7 +502,7 @@ async function runResponsesNativeCompact(
|
|
|
780
502
|
}
|
|
781
503
|
|
|
782
504
|
const compaction = createNativeCompactionResult({
|
|
783
|
-
firstKeptEntryId:
|
|
505
|
+
firstKeptEntryId: event.preparation.firstKeptEntryId,
|
|
784
506
|
tokensBefore: event.preparation.tokensBefore,
|
|
785
507
|
details,
|
|
786
508
|
});
|
|
@@ -815,145 +537,16 @@ async function runResponsesNativeCompact(
|
|
|
815
537
|
ctx,
|
|
816
538
|
);
|
|
817
539
|
|
|
818
|
-
if (
|
|
540
|
+
if (!isCurrent() || refuseRetiredSession(ctx)) return { outcome: "aborted" };
|
|
819
541
|
return { outcome: "success", compaction };
|
|
820
542
|
}
|
|
821
543
|
|
|
822
|
-
type ContextUnavailableCompactionResult =
|
|
823
|
-
| { compaction: CompactionResult<unknown> }
|
|
824
|
-
| { cancel: true };
|
|
825
|
-
|
|
826
|
-
async function runUnavailableContextCompaction(
|
|
827
|
-
event: SessionBeforeCompactEvent,
|
|
828
|
-
ctx: ExtensionContext,
|
|
829
|
-
dependencies: CompactionDependencies,
|
|
830
|
-
resolved: ResolvedToolkitConfig,
|
|
831
|
-
config: CompactionConfig,
|
|
832
|
-
registrationState: ContextToolRegistrationState,
|
|
833
|
-
): Promise<ContextUnavailableCompactionResult> {
|
|
834
|
-
const contextReason = contextManagementFailureReason(registrationState);
|
|
835
|
-
const remoteModelSpec = config.remoteCompactModel?.trim();
|
|
836
|
-
let remoteFailure: string | undefined;
|
|
837
|
-
|
|
838
|
-
if (remoteModelSpec) {
|
|
839
|
-
const resolution = await resolveRemoteCompactionExecution(
|
|
840
|
-
ctx,
|
|
841
|
-
{
|
|
842
|
-
enabled: config.enabled,
|
|
843
|
-
responsesApis: config.responsesApis,
|
|
844
|
-
codexGatewayModels: compactGatewayModels(resolved),
|
|
845
|
-
},
|
|
846
|
-
remoteModelSpec,
|
|
847
|
-
);
|
|
848
|
-
if (resolution.ok) {
|
|
849
|
-
const outcome = await runResponsesNativeCompact(
|
|
850
|
-
event,
|
|
851
|
-
ctx,
|
|
852
|
-
config,
|
|
853
|
-
resolution.execution,
|
|
854
|
-
dependencies.remoteCompact,
|
|
855
|
-
dependencies.projectCompactionContext,
|
|
856
|
-
);
|
|
857
|
-
if (outcome.outcome === "success") {
|
|
858
|
-
writeDebugArtifact(
|
|
859
|
-
"compaction-event",
|
|
860
|
-
{
|
|
861
|
-
event: "session_before_compact.context-fallback-success",
|
|
862
|
-
contextReason,
|
|
863
|
-
registrationState,
|
|
864
|
-
compactor: remoteModelSpec,
|
|
865
|
-
},
|
|
866
|
-
config,
|
|
867
|
-
ctx,
|
|
868
|
-
);
|
|
869
|
-
return { compaction: outcome.compaction };
|
|
870
|
-
}
|
|
871
|
-
if (outcome.outcome === "aborted" || outcome.outcome === "unprojected-input") {
|
|
872
|
-
return { cancel: true };
|
|
873
|
-
}
|
|
874
|
-
remoteFailure = "remote-v2-failed";
|
|
875
|
-
} else {
|
|
876
|
-
remoteFailure = resolution.reason;
|
|
877
|
-
writeDebugArtifact(
|
|
878
|
-
"compaction-event",
|
|
879
|
-
{
|
|
880
|
-
event: "session_before_compact.context-fallback-unavailable",
|
|
881
|
-
contextReason,
|
|
882
|
-
registrationState,
|
|
883
|
-
reason: resolution.reason,
|
|
884
|
-
modelSpec: remoteModelSpec,
|
|
885
|
-
errorMessage: resolution.errorMessage,
|
|
886
|
-
},
|
|
887
|
-
config,
|
|
888
|
-
ctx,
|
|
889
|
-
);
|
|
890
|
-
}
|
|
891
|
-
} else {
|
|
892
|
-
remoteFailure = "missing-remote-compaction-model";
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
const nativeModelSpec = config.nativeFallback.model?.trim();
|
|
896
|
-
if (config.nativeFallback.enabled && nativeModelSpec) {
|
|
897
|
-
const fallback = await dependencies.nativeFallback({
|
|
898
|
-
ctx,
|
|
899
|
-
event,
|
|
900
|
-
config,
|
|
901
|
-
modelSpec: nativeModelSpec,
|
|
902
|
-
});
|
|
903
|
-
if (fallback.ok) {
|
|
904
|
-
writeDebugArtifact(
|
|
905
|
-
"compaction-event",
|
|
906
|
-
{
|
|
907
|
-
event: "session_before_compact.context-fallback-native-success",
|
|
908
|
-
contextReason,
|
|
909
|
-
registrationState,
|
|
910
|
-
remoteFailure,
|
|
911
|
-
fallbackModel: nativeModelSpec,
|
|
912
|
-
},
|
|
913
|
-
config,
|
|
914
|
-
ctx,
|
|
915
|
-
);
|
|
916
|
-
return { compaction: fallback.result };
|
|
917
|
-
}
|
|
918
|
-
writeDebugArtifact(
|
|
919
|
-
"compaction-event",
|
|
920
|
-
{
|
|
921
|
-
event: "session_before_compact.context-fallback-native-failure",
|
|
922
|
-
contextReason,
|
|
923
|
-
registrationState,
|
|
924
|
-
remoteFailure,
|
|
925
|
-
fallbackReason: fallback.reason,
|
|
926
|
-
fallbackModel: nativeModelSpec,
|
|
927
|
-
errorMessage: fallback.errorMessage,
|
|
928
|
-
},
|
|
929
|
-
config,
|
|
930
|
-
ctx,
|
|
931
|
-
);
|
|
932
|
-
} else {
|
|
933
|
-
writeDebugArtifact(
|
|
934
|
-
"compaction-event",
|
|
935
|
-
{
|
|
936
|
-
event: "session_before_compact.context-fallback-cancelled",
|
|
937
|
-
contextReason,
|
|
938
|
-
registrationState,
|
|
939
|
-
remoteFailure,
|
|
940
|
-
reason: config.nativeFallback.enabled ? "missing-native-fallback-model" : "native-fallback-disabled",
|
|
941
|
-
},
|
|
942
|
-
config,
|
|
943
|
-
ctx,
|
|
944
|
-
);
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
return { cancel: true };
|
|
948
|
-
}
|
|
949
|
-
|
|
950
544
|
async function handleSessionBeforeCompact(
|
|
951
545
|
event: SessionBeforeCompactEvent,
|
|
952
546
|
ctx: ExtensionContext,
|
|
953
547
|
dependencies: CompactionDependencies,
|
|
954
|
-
remoteContextActive: RemoteContextActive,
|
|
955
|
-
getContextToolRegistrationState: ContextToolRegistrationStateReader,
|
|
956
548
|
) {
|
|
549
|
+
if (refuseRetiredSession(ctx)) return { cancel: true };
|
|
957
550
|
const resolved = contextOperation(dependencies.loadConfig, ctx);
|
|
958
551
|
if (resolved.invalidFeatures.some((feature) => ["context", "compatibility", "diagnostics"].includes(feature))) return { cancel: true };
|
|
959
552
|
const config = resolved.config.compaction;
|
|
@@ -980,67 +573,9 @@ async function handleSessionBeforeCompact(
|
|
|
980
573
|
return { cancel: true };
|
|
981
574
|
}
|
|
982
575
|
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
dependencies.contextWindows.synchronize(ctx);
|
|
987
|
-
} catch {
|
|
988
|
-
notifyRemoteContextFailure(ctx, "malformed-window-state");
|
|
989
|
-
return { cancel: true };
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
// A rollover trim is owned maintenance, not user /compact.
|
|
993
|
-
if (dependencies.manualCompact?.isMaintenance) return dependencies.contextWindows.prepareCompaction(event);
|
|
994
|
-
|
|
995
|
-
// Preserve the existing automatic Remote Windows protocol. Manual operations
|
|
996
|
-
// select their engine by current policy, never by persisted marker ownership.
|
|
997
|
-
if (event.reason !== "manual" && config.enabled && config.contextManagement === "remote") {
|
|
998
|
-
const contextModel = await resolveContextCapabilityModel(ctx);
|
|
999
|
-
if (isCodexContextModel(contextModel, config)) {
|
|
1000
|
-
if (await remoteContextActive(ctx, config, contextModel)) return dependencies.contextWindows.prepareCompaction(event);
|
|
1001
|
-
return runUnavailableContextCompaction(event, ctx, dependencies, resolved, config, getContextToolRegistrationState());
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
const isCurrent = dependencies.isCurrentCompaction ?? (() => !event.signal.aborted);
|
|
1006
|
-
let scope: RemoteWindowCompactionInput | undefined;
|
|
1007
|
-
if (dependencies.contextWindows.currentIdentity()) {
|
|
1008
|
-
scope = dependencies.contextWindows.prepareRemoteCompaction(event, ctx);
|
|
1009
|
-
requireCompleteCompactionToolPairs(scope.messages);
|
|
1010
|
-
event = prepareScopedNativeCompaction(event, scope);
|
|
1011
|
-
const keptIndex = event.branchEntries.findIndex((entry) => entry.id === event.preparation.firstKeptEntryId);
|
|
1012
|
-
scope = { ...scope, firstKeptEntryId: event.preparation.firstKeptEntryId,
|
|
1013
|
-
retainedMessages: scope.entries.filter((entry) => entry.sourceEntry.type !== "compaction"
|
|
1014
|
-
&& event.branchEntries.findIndex((raw) => raw.id === entry.sourceEntry.id) >= keptIndex)
|
|
1015
|
-
.flatMap((entry) => entry.messages) };
|
|
1016
|
-
if (!isCurrent()) return { cancel: true };
|
|
1017
|
-
}
|
|
1018
|
-
const nativeScopeSafe = () => {
|
|
1019
|
-
if (scope && !event.preparation.messagesToSummarize.length && !event.preparation.turnPrefixMessages.length && !event.preparation.previousSummary) {
|
|
1020
|
-
notifyWarning(ctx, "Current window has no eligible native summary span; context preserved.");
|
|
1021
|
-
return false;
|
|
1022
|
-
}
|
|
1023
|
-
const owner = scope?.owningCompaction;
|
|
1024
|
-
if (!owner) return true;
|
|
1025
|
-
const opaque = owner.summary === NATIVE_COMPACTION_FALLBACK_SUMMARY || owner.summary === STANDALONE_COMPACTION_FALLBACK_SUMMARY
|
|
1026
|
-
|| (isRecord(owner.details) && (owner.details.strategy === REMOTE_V2_COMPACTION_STRATEGY || owner.details.strategy === LEGACY_NATIVE_COMPACTION_STRATEGY));
|
|
1027
|
-
if (!opaque) return true;
|
|
1028
|
-
// Pi's text summarizer cannot interpret a Toolkit encrypted checkpoint.
|
|
1029
|
-
// Replacing it with a summary of its sentinel would silently lose history.
|
|
1030
|
-
notifyWarning(ctx, "Native compression cannot merge the current encrypted checkpoint; context preserved.");
|
|
1031
|
-
return false;
|
|
1032
|
-
};
|
|
1033
|
-
const nativeDefault = async () => {
|
|
1034
|
-
if (!scope) return undefined;
|
|
1035
|
-
if (!nativeScopeSafe()) return { cancel: true };
|
|
1036
|
-
const result = await dependencies.nativeFallback({ ctx, event, config, scoped: true, useCurrentModel: true, isCurrent });
|
|
1037
|
-
if (!isCurrent() || !result.ok) {
|
|
1038
|
-
if (!result.ok) notifyWarning(ctx, `Current-window native compression stopped (${result.reason}); context preserved.`);
|
|
1039
|
-
return { cancel: true };
|
|
1040
|
-
}
|
|
1041
|
-
return { compaction: result.result };
|
|
1042
|
-
};
|
|
1043
|
-
if (!config.enabled || resolved.policy.context.mode !== "remote-compaction") return nativeDefault();
|
|
576
|
+
const isCurrent = dependencies.isCurrentCompaction ?? (() => true);
|
|
577
|
+
if (!isCurrent()) return { cancel: true };
|
|
578
|
+
if (!config.enabled || resolved.policy.context.mode !== "remote-compaction") return undefined;
|
|
1044
579
|
|
|
1045
580
|
// Resolve producer protocol policy from this operation's document, not a later disk read.
|
|
1046
581
|
if (resolved.format === "v2" && config.remoteCompactModel) {
|
|
@@ -1058,13 +593,12 @@ async function handleSessionBeforeCompact(
|
|
|
1058
593
|
{
|
|
1059
594
|
enabled: config.enabled,
|
|
1060
595
|
responsesApis: config.responsesApis,
|
|
1061
|
-
codexGatewayModels:
|
|
596
|
+
codexGatewayModels: resolved.gatewayModelKeys,
|
|
1062
597
|
},
|
|
1063
598
|
config.remoteCompactModel,
|
|
1064
599
|
);
|
|
1065
|
-
if (
|
|
600
|
+
if (!isCurrent() || event.signal.aborted || refuseRetiredSession(ctx)) return { cancel: true };
|
|
1066
601
|
if (resolution.ok) {
|
|
1067
|
-
const scopedInput = scope ? await prepareScopedResponsesCompaction(event, ctx, config, resolution.execution, dependencies, isCurrent, scope) : undefined;
|
|
1068
602
|
remoteAttempted = true;
|
|
1069
603
|
const responsesOutcome = await runResponsesNativeCompact(
|
|
1070
604
|
event,
|
|
@@ -1074,8 +608,9 @@ async function handleSessionBeforeCompact(
|
|
|
1074
608
|
dependencies.remoteCompact,
|
|
1075
609
|
dependencies.projectCompactionContext,
|
|
1076
610
|
dependencies.standaloneCompact,
|
|
1077
|
-
|
|
611
|
+
isCurrent,
|
|
1078
612
|
);
|
|
613
|
+
if (!isCurrent() || refuseRetiredSession(ctx)) return { cancel: true };
|
|
1079
614
|
if (responsesOutcome.outcome === "success") {
|
|
1080
615
|
return { compaction: responsesOutcome.compaction };
|
|
1081
616
|
}
|
|
@@ -1152,9 +687,9 @@ async function handleSessionBeforeCompact(
|
|
|
1152
687
|
// Branch 2: run pi's native compaction method. A failed remote request is compacted by the
|
|
1153
688
|
// remote producer itself; a model that cannot use remote v2 at all uses nativeFallback.model.
|
|
1154
689
|
const fallbackModelSpec = remoteAttempted ? config.remoteCompactModel : config.nativeFallback.model;
|
|
1155
|
-
if (
|
|
1156
|
-
const fallback = await dependencies.nativeFallback({ ctx, event, config, modelSpec: fallbackModelSpec,
|
|
1157
|
-
if (
|
|
690
|
+
if (!isCurrent() || event.signal.aborted || refuseRetiredSession(ctx)) return { cancel: true };
|
|
691
|
+
const fallback = await dependencies.nativeFallback({ ctx, event, config, modelSpec: fallbackModelSpec, isCurrent });
|
|
692
|
+
if (!isCurrent() || refuseRetiredSession(ctx)) return { cancel: true };
|
|
1158
693
|
if (fallback.ok) {
|
|
1159
694
|
if (ctx.hasUI) {
|
|
1160
695
|
ctx.ui.notify(
|
|
@@ -1205,160 +740,31 @@ async function handleSessionBeforeCompact(
|
|
|
1205
740
|
return { cancel: true };
|
|
1206
741
|
}
|
|
1207
742
|
|
|
1208
|
-
// An unscoped undefined handoff would reopen archived windows. A scoped
|
|
1209
|
-
// current-model selection uses the public native primitive instead.
|
|
1210
|
-
return nativeDefault();
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
function rejectInheritedCheckpointReplay(
|
|
1214
|
-
ctx: ExtensionContext,
|
|
1215
|
-
config: CompactionConfig,
|
|
1216
|
-
compactionEntryId: string,
|
|
1217
|
-
reason: string,
|
|
1218
|
-
): undefined {
|
|
1219
|
-
writeReplayFailureArtifact({ reason, compactionEntryId }, config, ctx);
|
|
1220
|
-
reportPiContextHookFailure(ctx, `replay-failed:${reason}`);
|
|
1221
|
-
if (ctx.hasUI) ctx.ui.notify(`${COMPACTION_EXTENSION_ID}: inherited checkpoint replay failed (${reason}); request aborted`, "error");
|
|
1222
|
-
ctx.abort();
|
|
1223
743
|
return undefined;
|
|
1224
744
|
}
|
|
1225
745
|
|
|
1226
746
|
async function handleContextInternal(
|
|
1227
747
|
event: ContextWithSystemEvent,
|
|
1228
748
|
ctx: ExtensionContext,
|
|
1229
|
-
pi: ExtensionAPI,
|
|
1230
749
|
loadConfig: typeof loadToolkitConfig,
|
|
1231
|
-
contextWindows: CodexContextWindowManager,
|
|
1232
|
-
remoteContextActive: RemoteContextActive,
|
|
1233
|
-
getContextToolRegistrationState: ContextToolRegistrationStateReader,
|
|
1234
750
|
) {
|
|
751
|
+
if (refuseRetiredSession(ctx, true)) return undefined;
|
|
1235
752
|
const resolved = contextOperation(loadConfig, ctx);
|
|
1236
753
|
requireContextPolicy(resolved, ctx);
|
|
1237
754
|
const config = resolved.config.compaction;
|
|
1238
|
-
|
|
1239
|
-
contextWindows.synchronize(ctx);
|
|
1240
|
-
} catch {
|
|
1241
|
-
notifyRemoteContextFailure(ctx, "malformed-window-state");
|
|
1242
|
-
reportPiContextHookFailure(ctx, "malformed-window-state");
|
|
1243
|
-
ctx.abort();
|
|
1244
|
-
return undefined;
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
// A synthetic compression projection is read-only in EVERY mode. The
|
|
1248
|
-
// caller performs the sole retained-copy match after all ordered hooks;
|
|
1249
|
-
// ordinary replay here would remove the same copies a second time.
|
|
1250
|
-
if (contextWindows.isPreparingCompaction) return { messages: contextWindows.projectCompactionContext(event.messages) };
|
|
1251
|
-
|
|
1252
|
-
let localMessages: AgentMessage[] | undefined;
|
|
1253
|
-
let remoteReplayProjection = false;
|
|
1254
|
-
// Disabled and intentionally downgraded runtimes never enter opaque replay,
|
|
1255
|
-
// but still honor a window already owned by this session.
|
|
1256
|
-
let localProjectionOnly = !config.enabled;
|
|
1257
|
-
if (config.enabled && config.contextManagement === "remote") {
|
|
1258
|
-
const contextModel = await resolveContextCapabilityModel(ctx);
|
|
1259
|
-
const remoteActive = await remoteContextActive(ctx, config, contextModel);
|
|
1260
|
-
if (remoteActive) {
|
|
1261
|
-
try {
|
|
1262
|
-
// Measure the provider-visible current window, not Pi's durable transcript.
|
|
1263
|
-
// The latter still contains retired remote windows and can be inflated by
|
|
1264
|
-
// an upstream error even though this request only carries the live window.
|
|
1265
|
-
let projected = contextWindows.project(event.messages, "remote", false);
|
|
1266
|
-
// Projection may observe the exact queued rollover before Pi persists
|
|
1267
|
-
// it. Decide replay ownership from this request, not the old branch.
|
|
1268
|
-
const ownsWindow = contextWindows.hasEffectiveWindow() || !contextWindows.currentIdentity();
|
|
1269
|
-
if (ownsWindow) projected = contextWindows.project(projected, "remote");
|
|
1270
|
-
contextWindows.recordBudget(
|
|
1271
|
-
pi,
|
|
1272
|
-
ctx,
|
|
1273
|
-
true,
|
|
1274
|
-
config.contextReminderThresholdPercent,
|
|
1275
|
-
);
|
|
1276
|
-
reportProjectionDiagnostics(contextWindows, config, ctx);
|
|
1277
|
-
if (ownsWindow) {
|
|
1278
|
-
return projected.length === event.messages.length && projected.every((message, index) => message === event.messages[index])
|
|
1279
|
-
? undefined
|
|
1280
|
-
: { messages: projected };
|
|
1281
|
-
}
|
|
1282
|
-
localMessages = projected;
|
|
1283
|
-
remoteReplayProjection = true;
|
|
1284
|
-
} catch (error) {
|
|
1285
|
-
notifyRemoteContextFailure(ctx, "malformed-window-state");
|
|
1286
|
-
reportPiContextHookFailure(ctx, "malformed-window-state");
|
|
1287
|
-
ctx.abort();
|
|
1288
|
-
return undefined;
|
|
1289
|
-
}
|
|
1290
|
-
}
|
|
1291
|
-
|
|
1292
|
-
// An unavailable covered runtime must not silently become an ordinary
|
|
1293
|
-
// replay consumer. Explicit host exclusion remains the one intentional
|
|
1294
|
-
// downgrade; active Remote can replay a newer inherited checkpoint.
|
|
1295
|
-
if (!remoteActive && isCodexContextModel(contextModel, config)) {
|
|
1296
|
-
const registrationState = getContextToolRegistrationState();
|
|
1297
|
-
if (!isExcludedGatewayContext(contextModel, config, registrationState)
|
|
1298
|
-
&& isCodexGatewayModel(contextModel, config.gatewayContextModels)) {
|
|
1299
|
-
const reason = contextManagementFailureReason(registrationState);
|
|
1300
|
-
notifyRemoteContextFailure(ctx, reason);
|
|
1301
|
-
reportPiContextHookFailure(ctx, reason);
|
|
1302
|
-
ctx.abort();
|
|
1303
|
-
return undefined;
|
|
1304
|
-
}
|
|
1305
|
-
localProjectionOnly = true;
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
if (!remoteReplayProjection) {
|
|
1310
|
-
try {
|
|
1311
|
-
const projected = contextWindows.project(event.messages, "local");
|
|
1312
|
-
localMessages = projected;
|
|
1313
|
-
if (localProjectionOnly || contextWindows.hasEffectiveWindow()) {
|
|
1314
|
-
// A persisted boundary owns earlier history regardless of the selected
|
|
1315
|
-
// mode. Neither disabled policy nor ordinary replay may expand it.
|
|
1316
|
-
return projected.length === event.messages.length && projected.every((message, index) => message === event.messages[index])
|
|
1317
|
-
? undefined
|
|
1318
|
-
: { messages: projected };
|
|
1319
|
-
}
|
|
1320
|
-
} catch (error) {
|
|
1321
|
-
notifyRemoteContextFailure(ctx, "malformed-window-state");
|
|
1322
|
-
reportPiContextHookFailure(ctx, error instanceof Error ? error.message : String(error));
|
|
1323
|
-
ctx.abort();
|
|
1324
|
-
return undefined;
|
|
1325
|
-
}
|
|
1326
|
-
}
|
|
1327
|
-
|
|
1328
|
-
// Without an effective Remote boundary, the latest checkpoint owns replay.
|
|
1329
|
-
// This also covers a newer checkpoint superseding a historical window.
|
|
1330
|
-
const visibleMessages = localMessages ?? contextWindows.project(event.messages, "off");
|
|
1331
|
-
const finishProjection = (messages: AgentMessage[]) => {
|
|
1332
|
-
// Compare canonical retained copies before Remote encrypted-output encoding.
|
|
1333
|
-
const projected = remoteReplayProjection ? contextWindows.project(messages, "remote") : messages;
|
|
1334
|
-
return projected.length === event.messages.length && projected.every((message, index) => message === event.messages[index])
|
|
1335
|
-
? undefined : { messages: projected };
|
|
1336
|
-
};
|
|
1337
|
-
const replayEvent = visibleMessages === event.messages ? event : { ...event, messages: visibleMessages };
|
|
755
|
+
if (!config.enabled) return undefined;
|
|
1338
756
|
const branchEntries = ctx.sessionManager.getBranch();
|
|
1339
|
-
const inherited = config.contextManagement === "remote" && contextWindows.hasSupersededWindow()
|
|
1340
|
-
? resolveLatestNativeCompactionEntry(branchEntries) : undefined;
|
|
1341
|
-
const inheritedCheckpoint = inherited?.ok ? inherited.entry : undefined;
|
|
1342
757
|
const resolution = await resolveNativeCompactionEnvironment(ctx, {
|
|
1343
758
|
enabled: config.enabled,
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
responsesApis: inheritedCheckpoint ? undefined : config.responsesApis,
|
|
1347
|
-
codexGatewayModels: compactGatewayModels(resolved),
|
|
759
|
+
responsesApis: config.responsesApis,
|
|
760
|
+
codexGatewayModels: resolved.gatewayModelKeys,
|
|
1348
761
|
});
|
|
1349
|
-
if (
|
|
1350
|
-
|
|
1351
|
-
? rejectInheritedCheckpointReplay(ctx, config, inheritedCheckpoint.id, resolution.reason)
|
|
1352
|
-
: finishProjection(visibleMessages);
|
|
1353
|
-
}
|
|
762
|
+
if (refuseRetiredSession(ctx, true)) return undefined;
|
|
763
|
+
if (!resolution.ok) return undefined;
|
|
1354
764
|
const latest = resolveLatestNativeCompactionEntry(branchEntries, { baseUrl: resolution.runtime.baseUrl });
|
|
1355
|
-
if (!latest.ok)
|
|
1356
|
-
return inheritedCheckpoint
|
|
1357
|
-
? rejectInheritedCheckpointReplay(ctx, config, inheritedCheckpoint.id, latest.reason)
|
|
1358
|
-
: finishProjection(visibleMessages);
|
|
1359
|
-
}
|
|
765
|
+
if (!latest.ok) return undefined;
|
|
1360
766
|
const result = removeNativeCompactionRetainedMessages({
|
|
1361
|
-
messages:
|
|
767
|
+
messages: event.messages,
|
|
1362
768
|
branchEntries,
|
|
1363
769
|
compactionEntry: latest.entry,
|
|
1364
770
|
expectedInputProvenance: getRemoteV2InputProvenance(config.remoteV2ContextSource),
|
|
@@ -1370,7 +776,8 @@ async function handleContextInternal(
|
|
|
1370
776
|
ctx.abort();
|
|
1371
777
|
return undefined;
|
|
1372
778
|
}
|
|
1373
|
-
return
|
|
779
|
+
return result.messages.length === event.messages.length && result.messages.every((message, index) => message === event.messages[index])
|
|
780
|
+
? undefined : { messages: result.messages };
|
|
1374
781
|
}
|
|
1375
782
|
|
|
1376
783
|
/**
|
|
@@ -1381,14 +788,10 @@ async function handleContextInternal(
|
|
|
1381
788
|
async function handleContext(
|
|
1382
789
|
event: ContextWithSystemEvent,
|
|
1383
790
|
ctx: ExtensionContext,
|
|
1384
|
-
pi: ExtensionAPI,
|
|
1385
791
|
loadConfig: typeof loadToolkitConfig,
|
|
1386
|
-
contextWindows: CodexContextWindowManager,
|
|
1387
|
-
remoteContextActive: RemoteContextActive,
|
|
1388
|
-
getContextToolRegistrationState: ContextToolRegistrationStateReader,
|
|
1389
792
|
) {
|
|
1390
793
|
try {
|
|
1391
|
-
return await handleContextInternal(event, ctx,
|
|
794
|
+
return await handleContextInternal(event, ctx, loadConfig);
|
|
1392
795
|
} catch (error) {
|
|
1393
796
|
reportPiContextHookFailure(ctx, error instanceof Error ? error.message : String(error));
|
|
1394
797
|
throw error;
|
|
@@ -1399,78 +802,21 @@ async function handleBeforeProviderRequest(
|
|
|
1399
802
|
event: BeforeProviderRequestEvent,
|
|
1400
803
|
ctx: ExtensionContext,
|
|
1401
804
|
loadConfig: typeof loadToolkitConfig,
|
|
1402
|
-
contextWindows: CodexContextWindowManager,
|
|
1403
|
-
remoteContextActive: RemoteContextActive,
|
|
1404
|
-
getContextToolRegistrationState: ContextToolRegistrationStateReader,
|
|
1405
805
|
) {
|
|
806
|
+
if (refuseRetiredSession(ctx, true)) return undefined;
|
|
1406
807
|
const resolved = contextOperation(loadConfig, ctx);
|
|
1407
808
|
requireContextPolicy(resolved, ctx);
|
|
1408
809
|
const toolkitConfig = resolved.config;
|
|
1409
810
|
const config = toolkitConfig.compaction;
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
if (!config.enabled) {
|
|
1418
|
-
return undefined;
|
|
1419
|
-
}
|
|
1420
|
-
|
|
1421
|
-
const contextModel = config.contextManagement === "remote"
|
|
1422
|
-
? await resolveContextCapabilityModel(ctx)
|
|
1423
|
-
: undefined;
|
|
1424
|
-
const remoteActive = config.contextManagement === "remote" && await remoteContextActive(ctx, config, contextModel);
|
|
1425
|
-
let remotePayload: unknown;
|
|
1426
|
-
let activeWindowPayload = false;
|
|
1427
|
-
if (remoteActive) {
|
|
1428
|
-
try {
|
|
1429
|
-
remotePayload = contextWindows.rewritePayload(event.payload, ctx);
|
|
1430
|
-
activeWindowPayload = contextWindows.hasEffectiveWindow() || !contextWindows.currentIdentity();
|
|
1431
|
-
event = { ...event, payload: remotePayload };
|
|
1432
|
-
} catch {
|
|
1433
|
-
notifyRemoteContextFailure(ctx, "malformed-request-state");
|
|
1434
|
-
ctx.abort();
|
|
1435
|
-
return undefined;
|
|
1436
|
-
}
|
|
1437
|
-
}
|
|
1438
|
-
if (!remoteActive && isCodexContextModel(contextModel, config)) {
|
|
1439
|
-
const registrationState = getContextToolRegistrationState();
|
|
1440
|
-
// A readable empty registry is an intentional host exclusion. Do not
|
|
1441
|
-
// rewrite gateway identity or abort an ordinary child-session request.
|
|
1442
|
-
if (isExcludedGatewayContext(contextModel, config, registrationState)) return undefined;
|
|
1443
|
-
// Keep Codex Remote mutually exclusive with the legacy replay pipeline
|
|
1444
|
-
// when authentication or tool ownership is unavailable. Gateway traffic
|
|
1445
|
-
// cannot continue as an ordinary Responses request because that would
|
|
1446
|
-
// silently lose the required CPA session/account selection.
|
|
1447
|
-
if (isCodexGatewayModel(contextModel, config.gatewayContextModels)) {
|
|
1448
|
-
const reason = contextManagementFailureReason(registrationState);
|
|
1449
|
-
notifyRemoteContextFailure(ctx, reason);
|
|
1450
|
-
ctx.abort();
|
|
1451
|
-
}
|
|
1452
|
-
return undefined;
|
|
1453
|
-
}
|
|
1454
|
-
|
|
1455
|
-
// Do not reintroduce a pre-window opaque checkpoint after local projection.
|
|
1456
|
-
// Request affinity/encrypted-output rewriting is reserved for active Remote.
|
|
1457
|
-
if (contextWindows.hasEffectiveWindow() && !activeWindowPayload) return undefined;
|
|
1458
|
-
|
|
811
|
+
if (!config.enabled) return undefined;
|
|
812
|
+
const resolution = await resolveNativeCompactionEnvironment(ctx, {
|
|
813
|
+
enabled: config.enabled,
|
|
814
|
+
responsesApis: config.responsesApis,
|
|
815
|
+
codexGatewayModels: resolved.gatewayModelKeys,
|
|
816
|
+
}, event.payload);
|
|
817
|
+
if (refuseRetiredSession(ctx, true)) return undefined;
|
|
1459
818
|
const branchEntries = ctx.sessionManager.getBranch();
|
|
1460
|
-
const inherited = !activeWindowPayload && config.contextManagement === "remote" && contextWindows.hasSupersededWindow()
|
|
1461
|
-
? resolveLatestNativeCompactionEntry(branchEntries) : undefined;
|
|
1462
|
-
const inheritedCheckpoint = inherited?.ok ? inherited.entry : undefined;
|
|
1463
|
-
const resolution = await resolveNativeCompactionEnvironment(
|
|
1464
|
-
ctx,
|
|
1465
|
-
{
|
|
1466
|
-
enabled: config.enabled,
|
|
1467
|
-
responsesApis: inheritedCheckpoint ? undefined : config.responsesApis,
|
|
1468
|
-
codexGatewayModels: compactGatewayModels(resolved),
|
|
1469
|
-
},
|
|
1470
|
-
event.payload,
|
|
1471
|
-
);
|
|
1472
819
|
if (resolution.ok === false) {
|
|
1473
|
-
if (inheritedCheckpoint) return rejectInheritedCheckpointReplay(ctx, config, inheritedCheckpoint.id, resolution.reason);
|
|
1474
820
|
writeDebugArtifact(
|
|
1475
821
|
"provider-request",
|
|
1476
822
|
{
|
|
@@ -1487,7 +833,7 @@ async function handleBeforeProviderRequest(
|
|
|
1487
833
|
config,
|
|
1488
834
|
ctx,
|
|
1489
835
|
);
|
|
1490
|
-
return
|
|
836
|
+
return undefined;
|
|
1491
837
|
}
|
|
1492
838
|
|
|
1493
839
|
const runtime = resolution.runtime;
|
|
@@ -1515,15 +861,10 @@ async function handleBeforeProviderRequest(
|
|
|
1515
861
|
},
|
|
1516
862
|
);
|
|
1517
863
|
|
|
1518
|
-
// Capture is pre-Web-Search metadata only. An effective Remote window still
|
|
1519
|
-
// owns this live payload and must NEVER enter ordinary opaque replay below.
|
|
1520
|
-
if (activeWindowPayload) return remotePayload;
|
|
1521
|
-
|
|
1522
864
|
const latestNativeCompaction = resolveLatestNativeCompactionEntry(branchEntries, {
|
|
1523
865
|
baseUrl: runtime.baseUrl,
|
|
1524
866
|
});
|
|
1525
867
|
if (!latestNativeCompaction.ok) {
|
|
1526
|
-
if (inheritedCheckpoint) return rejectInheritedCheckpointReplay(ctx, config, inheritedCheckpoint.id, latestNativeCompaction.reason);
|
|
1527
868
|
writeDebugArtifact(
|
|
1528
869
|
"provider-request",
|
|
1529
870
|
{
|
|
@@ -1541,7 +882,7 @@ async function handleBeforeProviderRequest(
|
|
|
1541
882
|
config,
|
|
1542
883
|
ctx,
|
|
1543
884
|
);
|
|
1544
|
-
return
|
|
885
|
+
return undefined;
|
|
1545
886
|
}
|
|
1546
887
|
|
|
1547
888
|
const latestNativeCompactionEntry = latestNativeCompaction.entry;
|
|
@@ -1629,291 +970,126 @@ export default function registerCompactionExtension(
|
|
|
1629
970
|
) {
|
|
1630
971
|
const loadConfig = overrides.loadConfig ?? loadToolkitConfig;
|
|
1631
972
|
registerToolkitConfigCommand(pi, loadConfig);
|
|
973
|
+
registerPhysicalModelObserver(pi);
|
|
1632
974
|
const piContextHookPatch = installPiContextHookPatch();
|
|
1633
|
-
const contextWindows = overrides.contextWindows ?? new CodexContextWindowManager((ctx, signal, gatewayModels) =>
|
|
1634
|
-
loadHistoryNotesThreadHint(ctx, signal, gatewayModels)
|
|
1635
|
-
);
|
|
1636
|
-
const manualCompact = new ManagedManualCompact(pi);
|
|
1637
975
|
let compactionGeneration = 0;
|
|
1638
976
|
const invalidateCompaction = () => { compactionGeneration++; };
|
|
1639
977
|
const dependencies: CompactionDependencies = {
|
|
1640
978
|
remoteCompact: executeRemoteV2Compaction,
|
|
1641
979
|
standaloneCompact: executeNativeCompaction,
|
|
1642
980
|
nativeFallback: runNativeFallbackCompaction,
|
|
1643
|
-
contextWindows,
|
|
1644
981
|
...overrides,
|
|
1645
|
-
manualCompact,
|
|
1646
982
|
loadConfig,
|
|
1647
983
|
piContextHookPatch: overrides.piContextHookPatch ?? piContextHookPatch,
|
|
1648
984
|
};
|
|
1649
|
-
|
|
1650
|
-
let contextWindowReady = false;
|
|
1651
|
-
const isContextRuntimeActive = async (
|
|
1652
|
-
ctx: ExtensionContext,
|
|
1653
|
-
config: CompactionConfig,
|
|
1654
|
-
model = ctx.model,
|
|
1655
|
-
): Promise<boolean> =>
|
|
1656
|
-
contextWindowReady && tools.isRegistered && await isRemoteContextActive(ctx, config, model);
|
|
1657
|
-
tools = registerContextManagementTools(
|
|
1658
|
-
pi,
|
|
1659
|
-
contextWindows,
|
|
1660
|
-
async (ctx) => {
|
|
1661
|
-
const resolved = contextOperation(dependencies.loadConfig, ctx);
|
|
1662
|
-
assertConfigValid(resolved, "context", "compatibility");
|
|
1663
|
-
return { active: await isContextRuntimeActive(ctx, resolved.config.compaction), gatewayModels: resolved.gatewayModelKeys };
|
|
1664
|
-
},
|
|
1665
|
-
undefined,
|
|
1666
|
-
manualCompact,
|
|
1667
|
-
);
|
|
1668
|
-
const remoteContextActive: RemoteContextActive = isContextRuntimeActive;
|
|
1669
|
-
const syncTools = async (
|
|
1670
|
-
ctx: ExtensionContext,
|
|
1671
|
-
model = ctx.model,
|
|
1672
|
-
options: { notifyWindowFailure?: boolean } = {},
|
|
1673
|
-
resolved = contextOperation(dependencies.loadConfig, ctx, model),
|
|
1674
|
-
): Promise<ContextToolSyncOutcome> => {
|
|
1675
|
-
// Until this call proves otherwise, do not let a previous session/window
|
|
1676
|
-
// identity make a failed activation look usable.
|
|
1677
|
-
contextWindowReady = false;
|
|
1678
|
-
const config = resolved.config.compaction;
|
|
1679
|
-
const eligible = !resolved.invalidFeatures.some((feature) => ["context", "compatibility"].includes(feature)) && await isRemoteContextActive(ctx, config, model);
|
|
1680
|
-
const toolSync = tools.sync(eligible);
|
|
1681
|
-
const outcome: ContextToolSyncOutcome = {
|
|
1682
|
-
eligible,
|
|
1683
|
-
toolsSynced: toolSync.synced,
|
|
1684
|
-
registrationState: toolSync.registrationState,
|
|
1685
|
-
windowInitialized: false,
|
|
1686
|
-
};
|
|
1687
|
-
if (!eligible || !toolSync.synced) return outcome;
|
|
1688
|
-
// Activation can succeed after `session_start` missed it, because Pi 0.86 may
|
|
1689
|
-
// reject the registration read while a session replacement is still binding.
|
|
1690
|
-
// The window lifecycle has to open on that later activation too, or requests
|
|
1691
|
-
// carry no window metadata and the backend never ingests the turns.
|
|
1692
|
-
try {
|
|
1693
|
-
contextWindows.ensureInitialized(pi, ctx, true);
|
|
1694
|
-
contextWindowReady = true;
|
|
1695
|
-
return { ...outcome, windowInitialized: true };
|
|
1696
|
-
} catch {
|
|
1697
|
-
// Do not leave tools exposed while the request path has no valid window
|
|
1698
|
-
// identity. The next lifecycle hook may retry once Pi is usable again.
|
|
1699
|
-
tools.sync(false);
|
|
1700
|
-
if (options.notifyWindowFailure) notifyRemoteContextFailure(ctx, "malformed-window-state");
|
|
1701
|
-
return outcome;
|
|
1702
|
-
}
|
|
1703
|
-
};
|
|
1704
|
-
pi.on("session_start", async (_event, ctx) => {
|
|
985
|
+
pi.on("session_start", (_event, ctx) => {
|
|
1705
986
|
invalidateCompaction();
|
|
1706
|
-
|
|
1707
|
-
const
|
|
1708
|
-
await manualCompact.recover(ctx);
|
|
1709
|
-
const active = syncOutcome.eligible && syncOutcome.toolsSynced && syncOutcome.windowInitialized;
|
|
1710
|
-
const { source } = resolved;
|
|
1711
|
-
const warnings = resolved.issues.map((issue) => `${issue.path}: ${issue.code}`);
|
|
987
|
+
if (refuseRetiredSession(ctx)) return;
|
|
988
|
+
const resolved = contextOperation(loadConfig, ctx);
|
|
1712
989
|
const config = resolved.config.compaction;
|
|
1713
|
-
if (resolved.invalidFeatures.length
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
// warn; everything else silently runs Pi's normal compaction path.
|
|
1720
|
-
if (config.contextManagement === "remote" && isCodexContextModel(contextModel, config) && !active) {
|
|
1721
|
-
if (syncOutcome.registrationState === "excluded") {
|
|
1722
|
-
activationReason = "context-tools-excluded";
|
|
1723
|
-
} else if (syncOutcome.registrationState === "conflict") {
|
|
1724
|
-
activationReason = "tool-name-conflict";
|
|
1725
|
-
notifyRemoteContextFailure(ctx, activationReason);
|
|
1726
|
-
} else if (syncOutcome.registrationState === "unverified" || !syncOutcome.toolsSynced) {
|
|
1727
|
-
// Transient: the runtime has not published or accepted our tools yet. A
|
|
1728
|
-
// later before_agent_start re-verifies, so do not report provider failure.
|
|
1729
|
-
activationReason = "tool-registration-pending";
|
|
1730
|
-
} else if (syncOutcome.eligible && !syncOutcome.windowInitialized) {
|
|
1731
|
-
activationReason = "malformed-window-state";
|
|
1732
|
-
} else {
|
|
1733
|
-
const remoteResolution = await resolveCodexContextProvider(ctx, ctx.model, config.gatewayContextModels);
|
|
1734
|
-
activationReason = remoteResolution.ok ? "codex-context-unavailable" : remoteResolution.reason;
|
|
1735
|
-
notifyRemoteContextFailure(ctx, activationReason);
|
|
1736
|
-
}
|
|
1737
|
-
}
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
const artifactPath = writeDebugArtifact(
|
|
1741
|
-
"lifecycle",
|
|
1742
|
-
{
|
|
1743
|
-
event: "session_start",
|
|
1744
|
-
config,
|
|
1745
|
-
configSource: source,
|
|
1746
|
-
warnings,
|
|
1747
|
-
activation: {
|
|
1748
|
-
active,
|
|
1749
|
-
contextManagement: config.contextManagement,
|
|
1750
|
-
remoteV2ContextSource: config.remoteV2ContextSource,
|
|
1751
|
-
model: ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined,
|
|
1752
|
-
...(activationReason ? { reason: activationReason } : {}),
|
|
1753
|
-
},
|
|
1754
|
-
piContextHookPatch: dependencies.piContextHookPatch,
|
|
1755
|
-
},
|
|
1756
|
-
config,
|
|
1757
|
-
ctx,
|
|
1758
|
-
);
|
|
1759
|
-
|
|
990
|
+
if (resolved.invalidFeatures.length || !config.enabled) return;
|
|
991
|
+
const artifactPath = writeDebugArtifact("lifecycle", {
|
|
992
|
+
event: "session_start", config, configSource: resolved.source,
|
|
993
|
+
warnings: resolved.issues.map((issue) => `${issue.path}: ${issue.code}`),
|
|
994
|
+
piContextHookPatch: dependencies.piContextHookPatch,
|
|
995
|
+
}, config, ctx);
|
|
1760
996
|
if (ctx.hasUI && ["info", "debug"].includes(resolved.policy.diagnostics.level) && (config.notifyOnLoad || config.debug)) {
|
|
1761
|
-
ctx.ui.notify(
|
|
1762
|
-
artifactPath
|
|
1763
|
-
? `${COMPACTION_EXTENSION_ID} loaded • debug artifacts → ${artifactPath}`
|
|
1764
|
-
: `${COMPACTION_EXTENSION_ID} loaded`,
|
|
1765
|
-
"info",
|
|
1766
|
-
);
|
|
997
|
+
ctx.ui.notify(artifactPath ? `${COMPACTION_EXTENSION_ID} loaded • debug artifacts → ${artifactPath}` : `${COMPACTION_EXTENSION_ID} loaded`, "info");
|
|
1767
998
|
}
|
|
1768
999
|
});
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
manualCompact.guardRequest(ctx);
|
|
1773
|
-
return handleContext(event, ctx, pi, dependencies.loadConfig, contextWindows, remoteContextActive, getContextToolRegistrationState);
|
|
1000
|
+
pi.on("session_tree", (_event, ctx) => {
|
|
1001
|
+
invalidateCompaction();
|
|
1002
|
+
refuseRetiredSession(ctx);
|
|
1774
1003
|
});
|
|
1004
|
+
pi.on("context_with_system", (event, ctx) => handleContext(event, ctx, loadConfig));
|
|
1005
|
+
pi.on("before_provider_request", (event, ctx) => handleBeforeProviderRequest(event, ctx, loadConfig));
|
|
1775
1006
|
pi.on("session_before_compact", async (event, ctx) => {
|
|
1776
1007
|
invalidateCompaction();
|
|
1777
|
-
|
|
1778
|
-
const branch = structuredClone(event.branchEntries);
|
|
1779
|
-
const sessionId = getSessionId(ctx);
|
|
1780
|
-
const model = ctx.model ? { ...ctx.model } : undefined;
|
|
1781
|
-
const thinking = ctx.thinkingLevel;
|
|
1782
|
-
const isCurrent = () => generation === compactionGeneration && !event.signal.aborted
|
|
1783
|
-
&& getSessionId(ctx) === sessionId && isDeepStrictEqual(branch, ctx.sessionManager.getBranch())
|
|
1784
|
-
&& isDeepStrictEqual(model, ctx.model)
|
|
1785
|
-
&& thinking === ctx.thinkingLevel
|
|
1786
|
-
&& !(event.reason === "manual" && typeof ctx.hasPendingMessages === "function" && ctx.hasPendingMessages());
|
|
1008
|
+
if (refuseRetiredSession(ctx)) return { cancel: true };
|
|
1787
1009
|
try {
|
|
1788
|
-
const
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1010
|
+
const generation = compactionGeneration;
|
|
1011
|
+
const branch = structuredClone(ctx.sessionManager.getBranch());
|
|
1012
|
+
const sessionId = getSessionId(ctx);
|
|
1013
|
+
const model = ctx.model ? { ...ctx.model } : undefined;
|
|
1014
|
+
const thinking = ctx.thinkingLevel;
|
|
1015
|
+
// Clients classify aborts. This fence handles stale ownership: a
|
|
1016
|
+
// validated V2 terminal still wins over cleanup-triggered cancellation.
|
|
1017
|
+
const isCurrent = () => {
|
|
1018
|
+
try {
|
|
1019
|
+
return generation === compactionGeneration
|
|
1020
|
+
&& getSessionId(ctx) === sessionId && isDeepStrictEqual(branch, ctx.sessionManager.getBranch())
|
|
1021
|
+
&& isDeepStrictEqual(model, ctx.model) && thinking === ctx.thinkingLevel
|
|
1022
|
+
&& !retirementReason(ctx)
|
|
1023
|
+
&& !(event.reason === "manual" && typeof ctx.hasPendingMessages === "function" && ctx.hasPendingMessages());
|
|
1024
|
+
} catch { return false; }
|
|
1025
|
+
};
|
|
1026
|
+
const result = await handleSessionBeforeCompact(event, ctx, { ...dependencies, isCurrentCompaction: isCurrent });
|
|
1027
|
+
if (!isCurrent()) return { cancel: true };
|
|
1028
|
+
if (result && "compaction" in result && result.compaction) {
|
|
1029
|
+
// Pi consumes results after later awaited observers. Never persist a
|
|
1030
|
+
// stale candidate or drain/consume the host's queues.
|
|
1793
1031
|
return { compaction: result.compaction, get cancel() { return !isCurrent(); } };
|
|
1794
1032
|
}
|
|
1033
|
+
if (result === undefined) {
|
|
1034
|
+
// Default summarization starts only after later observers finish too.
|
|
1035
|
+
// Keep the host handoff revocable without claiming its compaction.
|
|
1036
|
+
return { get cancel() { return !isCurrent(); } };
|
|
1037
|
+
}
|
|
1795
1038
|
return result;
|
|
1796
1039
|
} catch {
|
|
1797
|
-
// Pi
|
|
1798
|
-
// Unexpected preparation/service failures must never authorize that work.
|
|
1040
|
+
// Pi can treat a rejected hook as absent and run its implicit summary.
|
|
1799
1041
|
try { notifyWarning(ctx, "Compaction stopped; existing context preserved. Resume manually."); } catch { /* stale UI */ }
|
|
1800
1042
|
return { cancel: true };
|
|
1801
1043
|
}
|
|
1802
1044
|
});
|
|
1803
|
-
pi.on("session_compact",
|
|
1804
|
-
contextWindows.synchronize(ctx);
|
|
1805
|
-
invalidateCompaction();
|
|
1806
|
-
});
|
|
1045
|
+
pi.on("session_compact", invalidateCompaction);
|
|
1807
1046
|
pi.on("session_compact_failed", invalidateCompaction);
|
|
1808
|
-
pi.on("session_shutdown",
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
});
|
|
1815
|
-
pi.on("agent_settled", async (_event, ctx) => {
|
|
1816
|
-
// Only the owned manual handoff restores/continues here; Pi owns normal
|
|
1817
|
-
// compaction scheduling. Never compact retired windows on model changes.
|
|
1818
|
-
await manualCompact.settled(ctx);
|
|
1819
|
-
});
|
|
1820
|
-
pi.on("model_select", async (event, ctx) => {
|
|
1821
|
-
invalidateCompaction();
|
|
1822
|
-
manualCompact.modelSelected(event.model, ctx);
|
|
1823
|
-
// Switching into a covered model mid-session must open the window
|
|
1824
|
-
// lifecycle immediately: without an identity the request rewrite skips
|
|
1825
|
-
// window metadata, the backend never ingests those turns, and the first
|
|
1826
|
-
// new_context would trim pre-switch history that no history can recover.
|
|
1827
|
-
// syncTools() activates and initializes the window when the model is covered.
|
|
1828
|
-
const resolved = contextOperation(dependencies.loadConfig, ctx, event.model);
|
|
1829
|
-
await syncTools(ctx, event.model, { notifyWindowFailure: true }, resolved);
|
|
1830
|
-
});
|
|
1831
|
-
pi.on("before_agent_start", async (_event, ctx) => {
|
|
1832
|
-
invalidateCompaction();
|
|
1833
|
-
await syncTools(ctx, ctx.model, { notifyWindowFailure: true });
|
|
1834
|
-
});
|
|
1835
|
-
pi.on("agent_start", () => invalidateCompaction());
|
|
1836
|
-
pi.on("input", () => { invalidateCompaction(); });
|
|
1837
|
-
pi.on("thinking_level_select", (event, ctx) => {
|
|
1838
|
-
invalidateCompaction();
|
|
1839
|
-
manualCompact.thinkingSelected(event.level, ctx);
|
|
1840
|
-
});
|
|
1047
|
+
pi.on("session_shutdown", invalidateCompaction);
|
|
1048
|
+
pi.on("model_select", invalidateCompaction);
|
|
1049
|
+
pi.on("before_agent_start", invalidateCompaction);
|
|
1050
|
+
pi.on("agent_start", invalidateCompaction);
|
|
1051
|
+
pi.on("input", invalidateCompaction);
|
|
1052
|
+
pi.on("thinking_level_select", invalidateCompaction);
|
|
1841
1053
|
pi.on("message_start", invalidateCompaction);
|
|
1842
|
-
pi.on("
|
|
1843
|
-
pi.on("
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
pi.on("session_before_tree", () => invalidateCompaction());
|
|
1850
|
-
pi.on("session_tree", async (_event, ctx) => {
|
|
1851
|
-
invalidateCompaction();
|
|
1852
|
-
contextWindows.synchronize(ctx);
|
|
1853
|
-
await manualCompact.recover(ctx);
|
|
1854
|
-
await syncTools(ctx);
|
|
1855
|
-
});
|
|
1856
|
-
pi.on("before_provider_request", (event, ctx) => {
|
|
1857
|
-
manualCompact.guardRequest(ctx);
|
|
1858
|
-
return handleBeforeProviderRequest(event, ctx, dependencies.loadConfig, contextWindows, remoteContextActive, getContextToolRegistrationState);
|
|
1859
|
-
});
|
|
1054
|
+
pi.on("tool_call", invalidateCompaction);
|
|
1055
|
+
pi.on("session_before_switch", invalidateCompaction);
|
|
1056
|
+
pi.on("session_before_fork", invalidateCompaction);
|
|
1057
|
+
pi.on("session_before_tree", invalidateCompaction);
|
|
1058
|
+
|
|
1059
|
+
// Exact gateway compatibility is shared transport policy, independent of
|
|
1060
|
+
// compaction enablement. No window identities or ingest headers are generated.
|
|
1860
1061
|
pi.on("before_provider_headers", async (event, ctx) => {
|
|
1861
|
-
|
|
1062
|
+
if (refuseRetiredSession(ctx, true)) return;
|
|
1063
|
+
const resolved = contextOperation(loadConfig, ctx);
|
|
1862
1064
|
requireContextPolicy(resolved, ctx);
|
|
1863
|
-
|
|
1864
|
-
const
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
if (
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1065
|
+
if (!resolved.gatewayModelKeys.length) return;
|
|
1066
|
+
const resolution = await resolveNativeCompactionEnvironment(ctx, {
|
|
1067
|
+
responsesApis: ["openai-responses"], codexGatewayModels: resolved.gatewayModelKeys,
|
|
1068
|
+
});
|
|
1069
|
+
if (refuseRetiredSession(ctx, true)) return;
|
|
1070
|
+
if (!resolution.ok) {
|
|
1071
|
+
const selected = isExactModelAllowed(ctx.model, resolved.gatewayModelKeys);
|
|
1072
|
+
const physical = resolution.provider && resolution.model
|
|
1073
|
+
? isExactModelAllowed({ provider: resolution.provider, id: resolution.model }, resolved.gatewayModelKeys) : false;
|
|
1074
|
+
if (selected || physical) {
|
|
1075
|
+
notifyWarning(ctx, "Configured gateway transport is unavailable; provider request aborted.");
|
|
1873
1076
|
ctx.abort();
|
|
1874
1077
|
}
|
|
1875
1078
|
return;
|
|
1876
1079
|
}
|
|
1877
|
-
const
|
|
1878
|
-
if (
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1080
|
+
const runtime = resolution.runtime;
|
|
1081
|
+
if (!runtime.codexAffinity) return;
|
|
1082
|
+
const gatewayHeaders = buildResponsesRequestHeaders(runtime, { accept: "text/event-stream" });
|
|
1083
|
+
const allowed = new Set<string>(CODEX_GATEWAY_FORWARD_HEADERS);
|
|
1084
|
+
for (const existing of Object.keys(event.headers)) {
|
|
1085
|
+
if (!allowed.has(existing.toLowerCase())) delete event.headers[existing];
|
|
1086
|
+
}
|
|
1087
|
+
for (const name of ["authorization", "originator", "user-agent", "session-id", "x-client-request-id", "x-codex-affinity-scope", "x-codex-model"]) {
|
|
1885
1088
|
for (const existing of Object.keys(event.headers)) {
|
|
1886
|
-
if (
|
|
1887
|
-
}
|
|
1888
|
-
// Keep any caller-supplied version without forcing a Toolkit pin.
|
|
1889
|
-
// The gateway allowlist above already permits it.
|
|
1890
|
-
for (const name of [
|
|
1891
|
-
"authorization",
|
|
1892
|
-
"originator",
|
|
1893
|
-
"user-agent",
|
|
1894
|
-
"session-id",
|
|
1895
|
-
"x-client-request-id",
|
|
1896
|
-
"x-codex-affinity-scope",
|
|
1897
|
-
"x-codex-model",
|
|
1898
|
-
]) {
|
|
1899
|
-
for (const existing of Object.keys(event.headers)) {
|
|
1900
|
-
if (existing.toLowerCase() === name) delete event.headers[existing];
|
|
1901
|
-
}
|
|
1902
|
-
const value = gatewayHeaders.get(name);
|
|
1903
|
-
if (value) event.headers[name] = value;
|
|
1089
|
+
if (existing.toLowerCase() === name) delete event.headers[existing];
|
|
1904
1090
|
}
|
|
1091
|
+
const value = gatewayHeaders.get(name);
|
|
1092
|
+
if (value) event.headers[name] = value;
|
|
1905
1093
|
}
|
|
1906
|
-
contextWindows.rewriteHeaders(event.headers, ctx);
|
|
1907
|
-
});
|
|
1908
|
-
pi.on("message_end", async (event, ctx) => {
|
|
1909
|
-
rememberPhysicalModel(ctx, event.message);
|
|
1910
|
-
const resolved = contextOperation(dependencies.loadConfig, ctx);
|
|
1911
|
-
requireContextPolicy(resolved, ctx);
|
|
1912
|
-
const config = resolved.config.compaction;
|
|
1913
|
-
const contextModel = await resolveContextCapabilityModel(ctx);
|
|
1914
|
-
if (!isCodexContextModel(contextModel, config) || !tools.isRegistered) return undefined;
|
|
1915
|
-
if (!(await remoteContextActive(ctx, config, contextModel))) return undefined;
|
|
1916
|
-
const message = routeContextNamespaceToolMessage(event.message);
|
|
1917
|
-
return message === event.message ? undefined : { message };
|
|
1918
1094
|
});
|
|
1919
1095
|
}
|