@lll9p/pi-better-compaction 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +183 -0
- package/index.ts +1 -0
- package/package.json +59 -0
- package/src/compact-client.ts +428 -0
- package/src/config.ts +157 -0
- package/src/debug.ts +165 -0
- package/src/details-store.ts +151 -0
- package/src/extension-runtime.ts +499 -0
- package/src/native-fallback.ts +149 -0
- package/src/payload-rewrite.ts +548 -0
- package/src/request-context-cache.ts +84 -0
- package/src/runtime.ts +250 -0
- package/src/serializer.ts +555 -0
- package/src/supported-environment.ts +16 -0
- package/src/types.ts +296 -0
package/src/runtime.ts
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
2
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { RESPONSES_COMPACT_CAPABLE_APIS } from "./types";
|
|
4
|
+
|
|
5
|
+
const OPENAI_COMPACT_PATH = "responses/compact";
|
|
6
|
+
const CODEX_COMPACT_PATH = "codex/responses/compact";
|
|
7
|
+
|
|
8
|
+
type ResponsesCompactApi = (typeof RESPONSES_COMPACT_CAPABLE_APIS)[number];
|
|
9
|
+
|
|
10
|
+
type RuntimeModel = Model<Api>;
|
|
11
|
+
|
|
12
|
+
type NativeCompactionFailureReason =
|
|
13
|
+
| "disabled"
|
|
14
|
+
| "missing-model"
|
|
15
|
+
| "unsupported-api"
|
|
16
|
+
| "missing-base-url"
|
|
17
|
+
| "missing-api-key"
|
|
18
|
+
| "unsupported-payload"
|
|
19
|
+
| "payload-model-mismatch";
|
|
20
|
+
|
|
21
|
+
export type NativeCompactionSupportOptions = {
|
|
22
|
+
enabled?: boolean;
|
|
23
|
+
/** Which Responses APIs should use the compact endpoint; defaults to all capable APIs. */
|
|
24
|
+
responsesCompactApis?: readonly string[];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type ResponsesCompatibleRequestPayload = {
|
|
28
|
+
model: string;
|
|
29
|
+
input: unknown[];
|
|
30
|
+
instructions?: unknown;
|
|
31
|
+
[key: string]: unknown;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type NativeCompactionRuntime = {
|
|
35
|
+
provider: string;
|
|
36
|
+
api: ResponsesCompactApi;
|
|
37
|
+
model: string;
|
|
38
|
+
baseUrl: string;
|
|
39
|
+
apiKey: string;
|
|
40
|
+
headers?: Record<string, string>;
|
|
41
|
+
compactPath: string;
|
|
42
|
+
compactUrl: string;
|
|
43
|
+
payload?: ResponsesCompatibleRequestPayload;
|
|
44
|
+
currentModel: RuntimeModel;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type NativeCompactionEnvironmentFailure = {
|
|
48
|
+
ok: false;
|
|
49
|
+
reason: NativeCompactionFailureReason;
|
|
50
|
+
provider?: string;
|
|
51
|
+
api?: string;
|
|
52
|
+
model?: string;
|
|
53
|
+
baseUrl?: string;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type NativeCompactionEnvironmentSuccess = {
|
|
57
|
+
ok: true;
|
|
58
|
+
runtime: NativeCompactionRuntime;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export type NativeCompactionEnvironmentResolution =
|
|
62
|
+
| NativeCompactionEnvironmentFailure
|
|
63
|
+
| NativeCompactionEnvironmentSuccess;
|
|
64
|
+
|
|
65
|
+
function normalizeConfiguredApis(values: readonly string[] | undefined): Set<string> {
|
|
66
|
+
if (values === undefined) {
|
|
67
|
+
return new Set(RESPONSES_COMPACT_CAPABLE_APIS);
|
|
68
|
+
}
|
|
69
|
+
return new Set(values.map((value) => value.trim()).filter((value) => value.length > 0));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function normalizeBaseUrl(baseUrl: string | undefined | null): string | undefined {
|
|
73
|
+
const normalized = baseUrl?.trim().replace(/\/+$/, "");
|
|
74
|
+
return normalized ? normalized : undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function buildOpenAICompactUrl(baseUrl: string): string {
|
|
78
|
+
const normalized = normalizeBaseUrl(baseUrl) ?? baseUrl;
|
|
79
|
+
if (normalized.endsWith("/responses")) {
|
|
80
|
+
return `${normalized}/compact`;
|
|
81
|
+
}
|
|
82
|
+
return `${normalized}/${OPENAI_COMPACT_PATH}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function buildCodexCompactUrl(baseUrl: string): string {
|
|
86
|
+
const normalized = normalizeBaseUrl(baseUrl) ?? baseUrl;
|
|
87
|
+
if (normalized.endsWith("/codex/responses")) {
|
|
88
|
+
return `${normalized}/compact`;
|
|
89
|
+
}
|
|
90
|
+
if (normalized.endsWith("/codex")) {
|
|
91
|
+
return `${normalized}/responses/compact`;
|
|
92
|
+
}
|
|
93
|
+
return `${normalized}/${CODEX_COMPACT_PATH}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function buildCompactUrl(baseUrl: string, api: ResponsesCompactApi): string {
|
|
97
|
+
return api === "openai-codex-responses" ? buildCodexCompactUrl(baseUrl) : buildOpenAICompactUrl(baseUrl);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function buildCompactPath(api: ResponsesCompactApi): string {
|
|
101
|
+
return api === "openai-codex-responses" ? CODEX_COMPACT_PATH : OPENAI_COMPACT_PATH;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function resolveRequestAuth(
|
|
105
|
+
ctx: ExtensionContext,
|
|
106
|
+
model: RuntimeModel,
|
|
107
|
+
): Promise<{ apiKey?: string; headers?: Record<string, string> }> {
|
|
108
|
+
const modelRegistry = ctx.modelRegistry as {
|
|
109
|
+
getApiKeyAndHeaders?: (currentModel: RuntimeModel) => Promise<
|
|
110
|
+
| { ok: true; apiKey?: string; headers?: Record<string, string> }
|
|
111
|
+
| { ok: false; error: string }
|
|
112
|
+
>;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
if (typeof modelRegistry.getApiKeyAndHeaders !== "function") {
|
|
116
|
+
return {};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const auth = await modelRegistry.getApiKeyAndHeaders(model);
|
|
120
|
+
return auth.ok ? { apiKey: auth.apiKey, headers: auth.headers } : {};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function isSupportedApi(api: string): api is ResponsesCompactApi {
|
|
124
|
+
return (RESPONSES_COMPACT_CAPABLE_APIS as readonly string[]).includes(api);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function isResponsesCompatiblePayload(payload: unknown): payload is ResponsesCompatibleRequestPayload {
|
|
128
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const candidate = payload as Record<string, unknown>;
|
|
133
|
+
return typeof candidate.model === "string" && Array.isArray(candidate.input);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function getRuntimeModelDescriptor(model: RuntimeModel | undefined): {
|
|
137
|
+
provider?: string;
|
|
138
|
+
api?: string;
|
|
139
|
+
model?: string;
|
|
140
|
+
baseUrl?: string;
|
|
141
|
+
} {
|
|
142
|
+
if (!model) {
|
|
143
|
+
return {};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
provider: model.provider,
|
|
148
|
+
api: model.api,
|
|
149
|
+
model: model.id,
|
|
150
|
+
baseUrl: normalizeBaseUrl(model.baseUrl),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function resolveNativeCompactionEnvironment(
|
|
155
|
+
ctx: ExtensionContext,
|
|
156
|
+
options: NativeCompactionSupportOptions = {},
|
|
157
|
+
payload?: unknown,
|
|
158
|
+
): Promise<NativeCompactionEnvironmentResolution> {
|
|
159
|
+
if (options.enabled === false) {
|
|
160
|
+
return {
|
|
161
|
+
ok: false,
|
|
162
|
+
reason: "disabled",
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const currentModel = ctx.model;
|
|
167
|
+
const descriptor = getRuntimeModelDescriptor(currentModel);
|
|
168
|
+
if (!currentModel || !descriptor.provider || !descriptor.api || !descriptor.model) {
|
|
169
|
+
return {
|
|
170
|
+
ok: false,
|
|
171
|
+
reason: "missing-model",
|
|
172
|
+
...descriptor,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// The compact endpoint is selected purely by API family: any provider speaking
|
|
177
|
+
// openai-responses/openai-codex-responses gets a native compact attempt and fails
|
|
178
|
+
// open (to the fallback model or pi's default) when the endpoint is missing.
|
|
179
|
+
const configuredApis = normalizeConfiguredApis(options.responsesCompactApis);
|
|
180
|
+
if (!configuredApis.has(descriptor.api) || !isSupportedApi(descriptor.api)) {
|
|
181
|
+
return {
|
|
182
|
+
ok: false,
|
|
183
|
+
reason: "unsupported-api",
|
|
184
|
+
...descriptor,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (!descriptor.baseUrl) {
|
|
189
|
+
return {
|
|
190
|
+
ok: false,
|
|
191
|
+
reason: "missing-base-url",
|
|
192
|
+
...descriptor,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let requestPayload: ResponsesCompatibleRequestPayload | undefined;
|
|
197
|
+
if (payload !== undefined) {
|
|
198
|
+
if (!isResponsesCompatiblePayload(payload)) {
|
|
199
|
+
return {
|
|
200
|
+
ok: false,
|
|
201
|
+
reason: "unsupported-payload",
|
|
202
|
+
...descriptor,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (payload.model !== descriptor.model) {
|
|
207
|
+
return {
|
|
208
|
+
ok: false,
|
|
209
|
+
reason: "payload-model-mismatch",
|
|
210
|
+
...descriptor,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
requestPayload = payload;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const { apiKey, headers } = await resolveRequestAuth(ctx, currentModel);
|
|
218
|
+
if (!apiKey) {
|
|
219
|
+
return {
|
|
220
|
+
ok: false,
|
|
221
|
+
reason: "missing-api-key",
|
|
222
|
+
...descriptor,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
ok: true,
|
|
228
|
+
runtime: {
|
|
229
|
+
provider: descriptor.provider,
|
|
230
|
+
api: descriptor.api,
|
|
231
|
+
model: descriptor.model,
|
|
232
|
+
baseUrl: descriptor.baseUrl,
|
|
233
|
+
apiKey,
|
|
234
|
+
headers,
|
|
235
|
+
compactPath: buildCompactPath(descriptor.api),
|
|
236
|
+
compactUrl: buildCompactUrl(descriptor.baseUrl, descriptor.api),
|
|
237
|
+
payload: requestPayload,
|
|
238
|
+
currentModel,
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export async function getNativeCompactionRuntime(
|
|
244
|
+
ctx: ExtensionContext,
|
|
245
|
+
options: NativeCompactionSupportOptions = {},
|
|
246
|
+
payload?: unknown,
|
|
247
|
+
): Promise<NativeCompactionRuntime | undefined> {
|
|
248
|
+
const resolution = await resolveNativeCompactionEnvironment(ctx, options, payload);
|
|
249
|
+
return resolution.ok ? resolution.runtime : undefined;
|
|
250
|
+
}
|