pi-openai-toolkit 0.20.5 → 0.20.6
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 +1 -1
- package/src/extension-runtime.ts +3 -1
- package/src/native-fallback.ts +89 -30
package/package.json
CHANGED
package/src/extension-runtime.ts
CHANGED
|
@@ -882,7 +882,9 @@ async function handleSessionBeforeCompact(
|
|
|
882
882
|
config,
|
|
883
883
|
ctx,
|
|
884
884
|
);
|
|
885
|
-
|
|
885
|
+
const expectedConsumerCapabilityMiss =
|
|
886
|
+
resolution.reason === "unsupported-api" && resolution.modelSpec === undefined;
|
|
887
|
+
if (config.remoteCompactModel && !expectedConsumerCapabilityMiss) {
|
|
886
888
|
notifyWarning(
|
|
887
889
|
ctx,
|
|
888
890
|
`remote compaction model "${config.remoteCompactModel}" unusable (${resolution.reason}); using the native fallback chain`,
|
package/src/native-fallback.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
compact,
|
|
3
|
+
convertToLlm,
|
|
3
4
|
DEFAULT_COMPACTION_SETTINGS,
|
|
4
|
-
|
|
5
|
+
serializeConversation,
|
|
5
6
|
type CompactionResult,
|
|
6
7
|
type ExtensionContext,
|
|
7
8
|
type SessionBeforeCompactEvent,
|
|
@@ -59,16 +60,10 @@ function toErrorMessage(error: unknown): string {
|
|
|
59
60
|
return error instanceof Error ? error.message : String(error);
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
/**
|
|
63
|
-
* Output budget Pi gives the summary itself: `generateSummary` caps `maxTokens` at
|
|
64
|
-
* `min(floor(0.8 * reserveTokens), model.maxTokens)`.
|
|
65
|
-
*/
|
|
66
|
-
const SUMMARIZATION_OUTPUT_RESERVE_TOKENS = Math.floor(
|
|
67
|
-
DEFAULT_COMPACTION_SETTINGS.reserveTokens * 0.8,
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
/** Summarization instructions plus the `<conversation>` framing around the transcript. */
|
|
63
|
+
/** Summarization instructions plus the framing around the serialized transcript. */
|
|
71
64
|
const SUMMARIZATION_TEMPLATE_TOKENS = 1024;
|
|
65
|
+
const HISTORY_OUTPUT_RESERVE_RATIO = 0.8;
|
|
66
|
+
const TURN_PREFIX_OUTPUT_RESERVE_RATIO = 0.5;
|
|
72
67
|
|
|
73
68
|
export type SummarizationRequestSize = {
|
|
74
69
|
inputTokens: number;
|
|
@@ -76,33 +71,93 @@ export type SummarizationRequestSize = {
|
|
|
76
71
|
totalTokens: number;
|
|
77
72
|
};
|
|
78
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
|
+
|
|
79
117
|
/**
|
|
80
|
-
* Size of the request Pi's `compact()` is about to send
|
|
81
|
-
* measures context (the exported `estimateTokens`, a conservative chars/4).
|
|
118
|
+
* Size of the individual request(s) Pi's `compact()` is about to send.
|
|
82
119
|
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
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.
|
|
88
130
|
*/
|
|
89
131
|
export function estimateSummarizationRequest(
|
|
90
132
|
preparation: SessionBeforeCompactEvent["preparation"],
|
|
91
133
|
model: { contextWindow?: number; maxTokens?: number },
|
|
134
|
+
customInstructions?: string,
|
|
92
135
|
): SummarizationRequestSize {
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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;
|
|
101
149
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
:
|
|
105
|
-
|
|
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
|
+
);
|
|
106
161
|
}
|
|
107
162
|
|
|
108
163
|
/** Whether the summary model can accept this request at all; unknown windows always pass. */
|
|
@@ -163,7 +218,11 @@ export async function runNativeFallbackCompaction(args: {
|
|
|
163
218
|
return { ok: false, reason: "same-as-current-model", modelSpec: spec };
|
|
164
219
|
}
|
|
165
220
|
|
|
166
|
-
const size = estimateSummarizationRequest(
|
|
221
|
+
const size = estimateSummarizationRequest(
|
|
222
|
+
event.preparation,
|
|
223
|
+
model as { contextWindow?: number; maxTokens?: number },
|
|
224
|
+
event.customInstructions,
|
|
225
|
+
);
|
|
167
226
|
if (!fitsSummarizationRequest(size, model as { contextWindow?: number })) {
|
|
168
227
|
return {
|
|
169
228
|
ok: false,
|