pi-openai-toolkit 0.20.7 → 0.20.8
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/docs/configuration.md +1 -1
- package/docs/internals.md +1 -1
- package/package.json +1 -1
- package/src/extension-runtime.ts +5 -12
- package/src/native-fallback.ts +4 -132
package/docs/configuration.md
CHANGED
|
@@ -63,7 +63,7 @@ Place `context` under `defaults` or an exact `models` entry.
|
|
|
63
63
|
| `remoteCompaction.allowContinuityBreak` | `false` | Allow restarting from Pi context after a foreign compaction entry. It does not make malformed opaque checkpoints replayable. |
|
|
64
64
|
| `remoteCompaction.apis` | `["openai-responses", "openai-codex-responses", "azure-openai-responses"]` | May narrow this set; `[]` permits no remote-compaction API. Azure uses Pi's resolved `AZURE_OPENAI_*` environment values for the `/openai/v1` Responses path, deployment mapping, and `api-version`; unsupported entries are errors. |
|
|
65
65
|
| `nativeFallback.enabled` | `true` | Enable the existing native-method fallback tier. |
|
|
66
|
-
| `nativeFallback.model` | `null` |
|
|
66
|
+
| `nativeFallback.model` | `null` | Summary-model reference on the remote-ineligible path. Character-count estimates do not bypass it; resolution, auth or summary failure cancels compaction without retrying the active model. After a remote attempt fails, the existing producer-first selection is retained. Disabled/unset/same-model selections keep Pi's default path. |
|
|
67
67
|
| `nativeFallback.thinkingLevel` | `"off"` | `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, or `max`; actual model support still applies. |
|
|
68
68
|
| `remoteWindows.leaveManagedMode` | `"warn"` | Legacy compatibility field. Both values preserve the same behavior: non-window models receive only the current local window, and no bulk close-out compaction is started for retired Remote Context history. |
|
|
69
69
|
| `remoteWindows.reminderThresholdPercent` | `5` | Integer 0-100. `0` disables both the once-per-window reminder and exhausted-window fallback. |
|
package/docs/internals.md
CHANGED
|
@@ -97,7 +97,7 @@ State invariants the lifecycle code must keep honest:
|
|
|
97
97
|
- Window identity and cleanup eligibility are separate. A persisted `codex-context-window` message restores identity after reload/fork, but its historical `trimPreviousWindow` flag is not an outstanding command. A newer native summary or opaque checkpoint supersedes old cleanup without erasing window identity. Missing or edited boundary evidence is never repaired by loading raw history.
|
|
98
98
|
- Maintenance compaction and local-consumer detachment use the same effective-boundary rules. Only a safe current boundary can produce a no-summary compaction; obsolete cleanup must not move the retained start backwards or replace a newer summary/checkpoint. Preparation alone changes no durable state, and cancellation, retry and branch navigation are resolved from current evidence rather than callback acknowledgments. User manual `/compact` still follows the managed handoff below.
|
|
99
99
|
- A non-window consumer receives only the effective current context, with the system/tool head preserved and internal window markers removed. When detachment is needed, no compaction producer receives retired Remote history. Once a later ordinary compaction owns effective context, the historical window does not suppress its summary or valid opaque replay. A genuinely new window can retire that checkpoint. Disabling opaque checkpoint generation through `responsesApis` does not disable required replay of an inherited checkpoint; an unsupported transport, authentication failure, or endpoint mismatch aborts the request instead of exposing its placeholder. The legacy `leaveManagedMode` setting remains compatible but does not start a bulk close-out from `model_select` or `agent_settled`.
|
|
100
|
-
-
|
|
100
|
+
- Native text summarization delegates prompt construction, tool-result truncation, split-turn requests and output limits to Pi's `compact()`. Toolkit does not use character-count estimates to reject the configured summary model. If its resolution, authentication or summary generation fails, compaction is cancelled with the error and context is preserved; the active model is not an implicit retry target. Disabled/unset/same-model selections retain Pi's default path. Remote-window projection still excludes retired managed history before any producer call.
|
|
101
101
|
|
|
102
102
|
---
|
|
103
103
|
|
package/package.json
CHANGED
package/src/extension-runtime.ts
CHANGED
|
@@ -926,8 +926,6 @@ async function handleSessionBeforeCompact(
|
|
|
926
926
|
reason: fallback.reason,
|
|
927
927
|
modelSpec: fallback.modelSpec,
|
|
928
928
|
errorMessage: fallback.errorMessage,
|
|
929
|
-
...(fallback.estimatedTokens === undefined ? {} : { estimatedTokens: fallback.estimatedTokens }),
|
|
930
|
-
...(fallback.contextWindow === undefined ? {} : { contextWindow: fallback.contextWindow }),
|
|
931
929
|
},
|
|
932
930
|
config,
|
|
933
931
|
ctx,
|
|
@@ -938,19 +936,14 @@ async function handleSessionBeforeCompact(
|
|
|
938
936
|
fallback.reason === "disabled" ||
|
|
939
937
|
fallback.reason === "no-model-configured" ||
|
|
940
938
|
fallback.reason === "same-as-current-model";
|
|
941
|
-
if (
|
|
942
|
-
//
|
|
943
|
-
//
|
|
944
|
-
// the provider terminating the oversized summary.
|
|
939
|
+
if (!intentionalSkip) {
|
|
940
|
+
// An explicit summary model is authoritative: the active model may be
|
|
941
|
+
// unable to summarize at all. Preserve context on failure, never reroute.
|
|
945
942
|
notifyWarning(
|
|
946
943
|
ctx,
|
|
947
|
-
`compaction
|
|
948
|
-
);
|
|
949
|
-
} else if (!intentionalSkip) {
|
|
950
|
-
notifyWarning(
|
|
951
|
-
ctx,
|
|
952
|
-
`compaction model "${fallback.modelSpec}" unusable (${fallback.reason}${fallback.errorMessage ? `: ${fallback.errorMessage}` : ""}); using pi's default compaction`,
|
|
944
|
+
`compaction model "${fallback.modelSpec ?? fallbackModelSpec}" failed (${fallback.reason}${fallback.errorMessage ? `: ${fallback.errorMessage}` : ""}); compaction cancelled, context preserved; not switching to the current model`,
|
|
953
945
|
);
|
|
946
|
+
return { cancel: true };
|
|
954
947
|
}
|
|
955
948
|
|
|
956
949
|
// Branch 3: pi's default native compaction with the current model.
|
package/src/native-fallback.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
2
|
compact,
|
|
3
|
-
convertToLlm,
|
|
4
|
-
DEFAULT_COMPACTION_SETTINGS,
|
|
5
|
-
serializeConversation,
|
|
6
3
|
type CompactionResult,
|
|
7
4
|
type ExtensionContext,
|
|
8
5
|
type SessionBeforeCompactEvent,
|
|
@@ -23,9 +20,7 @@ export type NativeFallbackFailureReason =
|
|
|
23
20
|
| "auth-failed"
|
|
24
21
|
| "aborted"
|
|
25
22
|
| "empty-summary"
|
|
26
|
-
| "compact-failed"
|
|
27
|
-
/** The configured summary model cannot fit the request it would have to send. */
|
|
28
|
-
| "model-window-too-small";
|
|
23
|
+
| "compact-failed";
|
|
29
24
|
|
|
30
25
|
export type NativeFallbackResult =
|
|
31
26
|
| {
|
|
@@ -38,8 +33,6 @@ export type NativeFallbackResult =
|
|
|
38
33
|
reason: NativeFallbackFailureReason;
|
|
39
34
|
modelSpec?: string;
|
|
40
35
|
errorMessage?: string;
|
|
41
|
-
estimatedTokens?: number;
|
|
42
|
-
contextWindow?: number;
|
|
43
36
|
};
|
|
44
37
|
|
|
45
38
|
/** pi's exported native compact(); injectable for tests. */
|
|
@@ -60,116 +53,6 @@ function toErrorMessage(error: unknown): string {
|
|
|
60
53
|
return error instanceof Error ? error.message : String(error);
|
|
61
54
|
}
|
|
62
55
|
|
|
63
|
-
/** Summarization instructions plus the framing around the serialized transcript. */
|
|
64
|
-
const SUMMARIZATION_TEMPLATE_TOKENS = 1024;
|
|
65
|
-
const HISTORY_OUTPUT_RESERVE_RATIO = 0.8;
|
|
66
|
-
const TURN_PREFIX_OUTPUT_RESERVE_RATIO = 0.5;
|
|
67
|
-
|
|
68
|
-
export type SummarizationRequestSize = {
|
|
69
|
-
inputTokens: number;
|
|
70
|
-
outputTokens: number;
|
|
71
|
-
totalTokens: number;
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
function estimateTextTokens(text: string | undefined): number {
|
|
75
|
-
return text ? Math.ceil(String(text).length / 4) : 0;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function estimateSerializedConversationTokens(
|
|
79
|
-
messages: SessionBeforeCompactEvent["preparation"]["messagesToSummarize"],
|
|
80
|
-
): number {
|
|
81
|
-
const serialized = serializeConversation(convertToLlm(messages));
|
|
82
|
-
return estimateTextTokens(serialized);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function getEffectiveReserveTokens(preparation: SessionBeforeCompactEvent["preparation"]): number {
|
|
86
|
-
const reserveTokens = preparation.settings?.reserveTokens;
|
|
87
|
-
return typeof reserveTokens === "number" && Number.isFinite(reserveTokens) && reserveTokens >= 0
|
|
88
|
-
? reserveTokens
|
|
89
|
-
: DEFAULT_COMPACTION_SETTINGS.reserveTokens;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function getOutputReserveTokens(
|
|
93
|
-
reserveTokens: number,
|
|
94
|
-
model: { maxTokens?: number },
|
|
95
|
-
ratio: number,
|
|
96
|
-
): number {
|
|
97
|
-
const outputTokens = Math.floor(ratio * reserveTokens);
|
|
98
|
-
return typeof model.maxTokens === "number" && model.maxTokens > 0
|
|
99
|
-
? Math.min(outputTokens, model.maxTokens)
|
|
100
|
-
: outputTokens;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function estimateSummaryRequest(
|
|
104
|
-
messages: SessionBeforeCompactEvent["preparation"]["messagesToSummarize"],
|
|
105
|
-
outputTokens: number,
|
|
106
|
-
previousSummary?: string,
|
|
107
|
-
customInstructions?: string,
|
|
108
|
-
): SummarizationRequestSize {
|
|
109
|
-
const inputTokens =
|
|
110
|
-
SUMMARIZATION_TEMPLATE_TOKENS
|
|
111
|
-
+ estimateSerializedConversationTokens(messages)
|
|
112
|
-
+ estimateTextTokens(previousSummary)
|
|
113
|
-
+ estimateTextTokens(customInstructions);
|
|
114
|
-
return { inputTokens, outputTokens, totalTokens: inputTokens + outputTokens };
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Size of the individual request(s) Pi's `compact()` is about to send.
|
|
119
|
-
*
|
|
120
|
-
* Pi first converts messages with `convertToLlm()` and then applies
|
|
121
|
-
* `serializeConversation()`, which truncates tool results before they enter the
|
|
122
|
-
* summarization prompt. Split-turn compaction sends history and turn-prefix
|
|
123
|
-
* summaries as separate requests, so this returns the largest individual request
|
|
124
|
-
* rather than adding their inputs together. The fixed allowance remains
|
|
125
|
-
* deliberately conservative for Pi's system prompt, tags, and instructions.
|
|
126
|
-
*
|
|
127
|
-
* A session driven by a wide-window model (400k) can produce a preparation that a
|
|
128
|
-
* narrower summary model (272k) cannot accept. Callers must not attempt that request;
|
|
129
|
-
* a missing `contextWindow` skips the check instead of guessing.
|
|
130
|
-
*/
|
|
131
|
-
export function estimateSummarizationRequest(
|
|
132
|
-
preparation: SessionBeforeCompactEvent["preparation"],
|
|
133
|
-
model: { contextWindow?: number; maxTokens?: number },
|
|
134
|
-
customInstructions?: string,
|
|
135
|
-
): SummarizationRequestSize {
|
|
136
|
-
const reserveTokens = getEffectiveReserveTokens(preparation);
|
|
137
|
-
const historyMessages = preparation.messagesToSummarize ?? [];
|
|
138
|
-
const turnPrefixMessages = preparation.turnPrefixMessages ?? [];
|
|
139
|
-
const historyOutputTokens = getOutputReserveTokens(reserveTokens, model, HISTORY_OUTPUT_RESERVE_RATIO);
|
|
140
|
-
const historyRequest = estimateSummaryRequest(
|
|
141
|
-
historyMessages,
|
|
142
|
-
historyOutputTokens,
|
|
143
|
-
preparation.previousSummary,
|
|
144
|
-
customInstructions,
|
|
145
|
-
);
|
|
146
|
-
|
|
147
|
-
if (!preparation.isSplitTurn || turnPrefixMessages.length === 0) {
|
|
148
|
-
return historyRequest;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const requests = [
|
|
152
|
-
...(historyMessages.length > 0 ? [historyRequest] : []),
|
|
153
|
-
estimateSummaryRequest(
|
|
154
|
-
turnPrefixMessages,
|
|
155
|
-
getOutputReserveTokens(reserveTokens, model, TURN_PREFIX_OUTPUT_RESERVE_RATIO),
|
|
156
|
-
),
|
|
157
|
-
];
|
|
158
|
-
return requests.reduce((largest, request) =>
|
|
159
|
-
request.totalTokens > largest.totalTokens ? request : largest,
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/** Whether the summary model can accept this request at all; unknown windows always pass. */
|
|
164
|
-
export function fitsSummarizationRequest(
|
|
165
|
-
size: SummarizationRequestSize,
|
|
166
|
-
model: { contextWindow?: number },
|
|
167
|
-
): boolean {
|
|
168
|
-
const contextWindow = model.contextWindow;
|
|
169
|
-
if (typeof contextWindow !== "number" || contextWindow <= 0) return true;
|
|
170
|
-
return size.totalTokens <= contextWindow;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
56
|
/**
|
|
174
57
|
* Run pi's native compaction method with a caller-resolved summary model.
|
|
175
58
|
*
|
|
@@ -218,20 +101,9 @@ export async function runNativeFallbackCompaction(args: {
|
|
|
218
101
|
return { ok: false, reason: "same-as-current-model", modelSpec: spec };
|
|
219
102
|
}
|
|
220
103
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
event.customInstructions,
|
|
225
|
-
);
|
|
226
|
-
if (!fitsSummarizationRequest(size, model as { contextWindow?: number })) {
|
|
227
|
-
return {
|
|
228
|
-
ok: false,
|
|
229
|
-
reason: "model-window-too-small",
|
|
230
|
-
modelSpec: spec,
|
|
231
|
-
estimatedTokens: size.totalTokens,
|
|
232
|
-
contextWindow: (model as { contextWindow?: number }).contextWindow,
|
|
233
|
-
};
|
|
234
|
-
}
|
|
104
|
+
// Pi owns prompt construction, tool-result truncation and split-turn requests.
|
|
105
|
+
// A chars/4 estimate is not target-model token accounting and must not veto
|
|
106
|
+
// the user's summary model. Surface actual provider failures to the caller.
|
|
235
107
|
|
|
236
108
|
let auth: ResolvedAuth;
|
|
237
109
|
try {
|