pi-openai-toolkit 0.17.1 → 0.17.2
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/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import type { AgentMessage } from "@earendil-works/pi-agent-core";
|
|
3
3
|
import type { ProviderHeaders } from "@earendil-works/pi-ai";
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
import {
|
|
5
|
+
calculateContextTokens,
|
|
6
|
+
estimateTokens,
|
|
7
|
+
type CompactionResult,
|
|
8
|
+
type ExtensionAPI,
|
|
9
|
+
type ExtensionContext,
|
|
10
|
+
type SessionBeforeCompactEvent,
|
|
11
|
+
type SessionEntry,
|
|
10
12
|
} from "@earendil-works/pi-coding-agent";
|
|
11
13
|
import { ContextWindowBudget, type ContextRemaining } from "./window-budget";
|
|
12
14
|
import type { WindowBulkReport } from "./window-bulk";
|
|
@@ -86,6 +88,12 @@ export class CodexContextWindowManager {
|
|
|
86
88
|
private lastKnownSystemHead: AgentMessage | undefined;
|
|
87
89
|
private bulkReport: WindowBulkReport | undefined;
|
|
88
90
|
private bulkSurfacedKey: string | undefined;
|
|
91
|
+
/**
|
|
92
|
+
* Pi's getContextUsage() measures the durable transcript. Remote windows are
|
|
93
|
+
* projected only by this extension, so remember the last provider-visible
|
|
94
|
+
* estimate for budget decisions and get_context_remaining.
|
|
95
|
+
*/
|
|
96
|
+
private projectedContextTokens: number | undefined;
|
|
89
97
|
private readonly loadThreadHint: ThreadHintLoader;
|
|
90
98
|
|
|
91
99
|
constructor(loadThreadHint?: ThreadHintLoader) {
|
|
@@ -149,6 +157,7 @@ export class CodexContextWindowManager {
|
|
|
149
157
|
this.branchStateInvalidated = false;
|
|
150
158
|
this.budget.reset();
|
|
151
159
|
this.trimPendingWindowId = undefined;
|
|
160
|
+
this.projectedContextTokens = undefined;
|
|
152
161
|
}
|
|
153
162
|
|
|
154
163
|
restore(entries: readonly SessionEntry[], sessionId?: string): void {
|
|
@@ -240,6 +249,7 @@ export class CodexContextWindowManager {
|
|
|
240
249
|
mode: "off" | "remote",
|
|
241
250
|
): AgentMessage[] {
|
|
242
251
|
if (mode === "off") {
|
|
252
|
+
this.projectedContextTokens = undefined;
|
|
243
253
|
return messages.filter(
|
|
244
254
|
(message) => message.role !== "custom" || message.customType !== CODEX_CONTEXT_WINDOW_MESSAGE_TYPE,
|
|
245
255
|
);
|
|
@@ -266,11 +276,16 @@ export class CodexContextWindowManager {
|
|
|
266
276
|
this.restoredMarkerId = undefined;
|
|
267
277
|
this.budget.reset();
|
|
268
278
|
this.trimPendingWindowId = undefined;
|
|
279
|
+
this.projectedContextTokens = undefined;
|
|
269
280
|
}
|
|
270
281
|
// Projection observes the request view, never durable session state. A
|
|
271
282
|
// queued rollover marker must not clear the duplicate guard here.
|
|
272
283
|
const trimmed = boundaryIndex < 0 ? [...messages] : this.trimToWindow(messages, boundaryIndex);
|
|
273
|
-
|
|
284
|
+
const projected = mode === "remote" ? projectEncryptedToolResults(trimmed) : trimmed;
|
|
285
|
+
this.projectedContextTokens = mode === "remote" && this.identity
|
|
286
|
+
? estimateProjectedWindowTokens(projected)
|
|
287
|
+
: undefined;
|
|
288
|
+
return projected;
|
|
274
289
|
}
|
|
275
290
|
|
|
276
291
|
/**
|
|
@@ -363,7 +378,12 @@ export class CodexContextWindowManager {
|
|
|
363
378
|
// once-per-window reminder on a false alarm seconds after a successful
|
|
364
379
|
// rollover, leaving the window silent for the rest of its life.
|
|
365
380
|
if (!hasAssistantUsageSinceWindowBoundary(ctx.sessionManager.getBranch(), this.sessionId)) return;
|
|
366
|
-
const reminder = this.budget.record(
|
|
381
|
+
const reminder = this.budget.record(
|
|
382
|
+
ctx,
|
|
383
|
+
this.identity,
|
|
384
|
+
contextTokens ?? this.projectedContextTokens,
|
|
385
|
+
contextReminderThresholdPercent,
|
|
386
|
+
);
|
|
367
387
|
if (!reminder) return;
|
|
368
388
|
sendContextWindowMessage(
|
|
369
389
|
pi,
|
|
@@ -375,7 +395,7 @@ export class CodexContextWindowManager {
|
|
|
375
395
|
}
|
|
376
396
|
|
|
377
397
|
remaining(ctx: ExtensionContext, contextTokens?: number): ContextRemaining {
|
|
378
|
-
return this.budget.remaining(ctx, this.identity, contextTokens);
|
|
398
|
+
return this.budget.remaining(ctx, this.identity, contextTokens ?? this.projectedContextTokens);
|
|
379
399
|
}
|
|
380
400
|
|
|
381
401
|
/**
|
|
@@ -703,6 +723,35 @@ function matchesSession(markerSessionId: string | undefined, sessionId: string |
|
|
|
703
723
|
return sessionId === undefined || markerSessionId === sessionId;
|
|
704
724
|
}
|
|
705
725
|
|
|
726
|
+
function estimateProjectedWindowTokens(messages: readonly AgentMessage[]): number {
|
|
727
|
+
let lastUsageIndex = -1;
|
|
728
|
+
let usageTokens = 0;
|
|
729
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
730
|
+
const message = messages[index]!;
|
|
731
|
+
if (message.role !== "assistant" || message.stopReason === "aborted" || message.stopReason === "error" || !message.usage) continue;
|
|
732
|
+
const tokens = calculateContextTokens(message.usage);
|
|
733
|
+
if (tokens <= 0) continue;
|
|
734
|
+
lastUsageIndex = index;
|
|
735
|
+
usageTokens = tokens;
|
|
736
|
+
break;
|
|
737
|
+
}
|
|
738
|
+
if (lastUsageIndex < 0) return messages.reduce((total, message) => total + estimateProjectedMessageTokens(message), 0);
|
|
739
|
+
return usageTokens + messages
|
|
740
|
+
.slice(lastUsageIndex + 1)
|
|
741
|
+
.reduce((total, message) => total + estimateProjectedMessageTokens(message), 0);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
function estimateProjectedMessageTokens(message: AgentMessage): number {
|
|
745
|
+
// A synthetic custom marker may be represented without content in a test or
|
|
746
|
+
// by an older host. It contributes no measurable text; it must not make the
|
|
747
|
+
// context hook fail closed merely because the diagnostic estimate is missing.
|
|
748
|
+
if (
|
|
749
|
+
!("content" in message) ||
|
|
750
|
+
(typeof message.content !== "string" && !Array.isArray(message.content))
|
|
751
|
+
) return 0;
|
|
752
|
+
return estimateTokens(message);
|
|
753
|
+
}
|
|
754
|
+
|
|
706
755
|
function projectEncryptedToolResults(messages: readonly AgentMessage[]): AgentMessage[] {
|
|
707
756
|
let changed = false;
|
|
708
757
|
const projected = messages.map((message) => {
|
package/src/extension-runtime.ts
CHANGED
|
@@ -876,13 +876,16 @@ async function handleContextInternal(
|
|
|
876
876
|
const remoteActive = await remoteContextActive(ctx, config);
|
|
877
877
|
if (remoteActive) {
|
|
878
878
|
try {
|
|
879
|
+
// Measure the provider-visible current window, not Pi's durable transcript.
|
|
880
|
+
// The latter still contains retired remote windows and can be inflated by
|
|
881
|
+
// an upstream error even though this request only carries the live window.
|
|
882
|
+
const projected = contextWindows.project(event.messages, "remote");
|
|
879
883
|
contextWindows.recordBudget(
|
|
880
884
|
pi,
|
|
881
885
|
ctx,
|
|
882
886
|
true,
|
|
883
887
|
config.contextReminderThresholdPercent,
|
|
884
888
|
);
|
|
885
|
-
const projected = contextWindows.project(event.messages, "remote");
|
|
886
889
|
reportProjectionDiagnostics(contextWindows, config, ctx);
|
|
887
890
|
return projected.length === event.messages.length && projected.every((message, index) => message === event.messages[index])
|
|
888
891
|
? undefined
|