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.
@@ -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
- rememberPhysicalModel,
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
- contextWindows: CodexContextWindowManager;
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
- type RemoteContextActive = (
139
- ctx: ExtensionContext,
140
- config: CompactionConfig,
141
- model?: ExtensionContext["model"],
142
- ) => Promise<boolean>;
143
- type ContextToolRegistrationStateReader = () => ContextToolRegistrationState;
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
- type ContextToolSyncOutcome = {
146
- eligible: boolean;
147
- toolsSynced: boolean;
148
- registrationState: ContextToolRegistrationState;
149
- windowInitialized: boolean;
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 = canonicalMessages ? structuredClone([...canonicalMessages]) : readSessionContextMessages(ctx);
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
- /** Only ordinary compaction supplies this; managed recovery remains V2-only. */
549
- standaloneCompact?: typeof executeNativeCompaction,
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 = scopedInput ? scopedInput.deferredToolCarryover : getLatestDeferredToolCarryover(branchEntries);
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 (scopedInput) {
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, ...(scopedInput ? { tools: scopedInput.request.tools } : {}) };
408
+ request = { ...request, ...extras };
687
409
  }
688
410
 
689
- if (event.signal?.aborted || (scopedInput && !scopedInput.isCurrent())) return { outcome: "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 (scopedInput && !scopedInput.isCurrent()) return { outcome: "aborted" };
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: scopedInput?.firstKeptEntryId ?? event.preparation.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 (scopedInput && !scopedInput.isCurrent()) return { outcome: "aborted" };
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
- // Window ownership survives model-policy changes, including Pi/disabled mode.
984
- // Resolve it before any ordinary or default compactor can receive this input.
985
- try {
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: compactGatewayModels(resolved),
596
+ codexGatewayModels: resolved.gatewayModelKeys,
1062
597
  },
1063
598
  config.remoteCompactModel,
1064
599
  );
1065
- if (scope && !isCurrent()) return { cancel: true };
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
- scopedInput,
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 (scope && !nativeScopeSafe()) return { cancel: true };
1156
- const fallback = await dependencies.nativeFallback({ ctx, event, config, modelSpec: fallbackModelSpec, ...(scope ? { scoped: true, isCurrent } : {}) });
1157
- if (scope && !isCurrent()) return { cancel: true };
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
- try {
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
- // Disabling checkpoint generation cannot turn an inherited opaque
1345
- // checkpoint into a usable text summary. Replay checks transport support.
1346
- responsesApis: inheritedCheckpoint ? undefined : config.responsesApis,
1347
- codexGatewayModels: compactGatewayModels(resolved),
759
+ responsesApis: config.responsesApis,
760
+ codexGatewayModels: resolved.gatewayModelKeys,
1348
761
  });
1349
- if (!resolution.ok) {
1350
- return inheritedCheckpoint
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: replayEvent.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 finishProjection(result.messages);
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, pi, loadConfig, contextWindows, remoteContextActive, getContextToolRegistrationState);
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
- try {
1411
- contextWindows.synchronize(ctx);
1412
- } catch {
1413
- notifyRemoteContextFailure(ctx, "malformed-window-state");
1414
- ctx.abort();
1415
- return undefined;
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 remotePayload;
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 remotePayload;
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
- let tools!: ReturnType<typeof registerContextManagementTools>;
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
- const resolved = contextOperation(dependencies.loadConfig, ctx);
1707
- const syncOutcome = await syncTools(ctx, ctx.model, { notifyWindowFailure: true }, resolved);
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 > 0) return;
1714
- if (!config.enabled) return;
1715
- const contextModel = await resolveContextCapabilityModel(ctx);
1716
-
1717
- let activationReason: string | undefined;
1718
- // Only models the built-in Remote Context coverage targets may activate or
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
- const getContextToolRegistrationState: ContextToolRegistrationStateReader = () => tools.registrationState;
1771
- pi.on("context_with_system", (event, ctx) => {
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
- const generation = compactionGeneration;
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 result = await handleSessionBeforeCompact(event, ctx, { ...dependencies, isCurrentCompaction: isCurrent }, remoteContextActive, getContextToolRegistrationState);
1789
- if (contextWindows.currentIdentity() && !manualCompact.isMaintenance && result && "compaction" in result && result.compaction
1790
- && result.compaction.summary !== CONTEXT_WINDOW_COMPACTION_SUMMARY) {
1791
- // Pi consumes this after every later before-compact observer. Reject
1792
- // stale work then, without broad aborts that can drain host queues.
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 treats a rejected hook as absent and can invoke its implicit summary.
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", (event, ctx) => {
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", async (_event, ctx) => {
1809
- invalidateCompaction();
1810
- await manualCompact.shutdown(ctx);
1811
- contextWindowReady = false;
1812
- contextWindows.reset();
1813
- tools.reset();
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("turn_end", (_event, ctx) => manualCompact.turnEnded(ctx));
1843
- pi.on("tool_call", (event) => {
1844
- invalidateCompaction();
1845
- return manualCompact.guardTool(event.toolName);
1846
- });
1847
- pi.on("session_before_switch", () => invalidateCompaction());
1848
- pi.on("session_before_fork", () => invalidateCompaction());
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
- const resolved = contextOperation(dependencies.loadConfig, ctx);
1062
+ if (refuseRetiredSession(ctx, true)) return;
1063
+ const resolved = contextOperation(loadConfig, ctx);
1862
1064
  requireContextPolicy(resolved, ctx);
1863
- const config = resolved.config.compaction;
1864
- const contextModel = await resolveContextCapabilityModel(ctx);
1865
- if (!isCodexContextModel(contextModel, config)) return;
1866
- const active = await remoteContextActive(ctx, config, contextModel);
1867
- if (!active) {
1868
- const registrationState = getContextToolRegistrationState();
1869
- if (isExcludedGatewayContext(contextModel, config, registrationState)) return;
1870
- if (isCodexGatewayModel(contextModel, config.gatewayContextModels)) {
1871
- const reason = contextManagementFailureReason(registrationState);
1872
- notifyRemoteContextFailure(ctx, reason);
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 provider = await resolveCodexContextProvider(ctx, ctx.model, config.gatewayContextModels);
1878
- if (provider.ok && provider.provider.kind === "codex-gateway") {
1879
- const sessionId = getSessionId(ctx);
1880
- const gatewayHeaders = codexContextProviderHeaders(provider.provider, {
1881
- sessionId,
1882
- clientRequestId: sessionId,
1883
- });
1884
- const allowedGatewayHeaders = new Set<string>(CODEX_GATEWAY_FORWARD_HEADERS);
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 (!allowedGatewayHeaders.has(existing.toLowerCase())) delete event.headers[existing];
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
  }