pi-cache-optimizer 2.8.16 → 2.8.17
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/README.md +19 -17
- package/README.zh-CN.md +19 -17
- package/index.ts +246 -11409
- package/package.json +8 -6
- package/src/adapters.ts +250 -0
- package/src/atomic-fs.ts +300 -0
- package/src/command-completion.ts +114 -0
- package/src/command.ts +1012 -0
- package/src/common.ts +49 -0
- package/src/compat-advice.ts +320 -0
- package/src/compat-config.ts +99 -0
- package/src/config.ts +374 -0
- package/src/diagnostics.ts +757 -0
- package/src/fix-types.ts +66 -0
- package/src/jsonc.ts +602 -0
- package/src/model-detect.ts +66 -0
- package/src/model-identity.ts +368 -0
- package/src/models-json-fix.ts +1982 -0
- package/src/paths.ts +18 -0
- package/src/prompt-cache-key-config.ts +406 -0
- package/src/prompt-rewrite.ts +294 -0
- package/src/provider-errors.ts +328 -0
- package/src/request-payload.ts +197 -0
- package/src/request-state.ts +102 -0
- package/src/retention.ts +82 -0
- package/src/routing.ts +551 -0
- package/src/stats-report.ts +771 -0
- package/src/stats-store.ts +500 -0
- package/src/tool-ordering.ts +209 -0
- package/src/usage.ts +172 -0
package/src/routing.ts
ADDED
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
import { LOG_PREFIX, type PiModel, asRecord, getNumber, isNonEmptyString, lower } from "./common.ts";
|
|
2
|
+
import { findLastExactModelDefinition } from "./compat-config.ts";
|
|
3
|
+
import { VIRTUAL_REWRITE_ENV, featureEnabled } from "./config.ts";
|
|
4
|
+
import { ROUTED_FALLBACK_MODEL_SYMBOL, getAssistantRecord, isNativeVirtualModel, isResponsesPromptRewriteBypassApi, modelKey, readEffectiveCompatConfig } from "./model-identity.ts";
|
|
5
|
+
import { clampPromptCacheKey } from "./request-payload.ts";
|
|
6
|
+
import { type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
export const PI_ROUTING_REGISTRY_SYMBOL = Symbol.for("pi.routing.registry.v1");
|
|
11
|
+
|
|
12
|
+
export const PI_CACHE_HINTS_SYMBOL = Symbol.for("pi.cache.hints.v1");
|
|
13
|
+
|
|
14
|
+
export type PiRouteSnapshot = {
|
|
15
|
+
virtualProvider: string;
|
|
16
|
+
virtualModelId: string;
|
|
17
|
+
provider: string;
|
|
18
|
+
modelId: string;
|
|
19
|
+
api?: string;
|
|
20
|
+
canonicalModelId?: string;
|
|
21
|
+
routeLabel?: string;
|
|
22
|
+
status?: "planned" | "trying" | "selected" | "success" | "failed";
|
|
23
|
+
sessionIdHash?: string;
|
|
24
|
+
requestId?: string;
|
|
25
|
+
timestamp: number;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type PiRouteResolveHint = {
|
|
29
|
+
sessionIdHash?: string;
|
|
30
|
+
requestId?: string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type PiRouterAdapterV1 = {
|
|
34
|
+
virtualProvider: string;
|
|
35
|
+
resolveActiveRoute(
|
|
36
|
+
virtualModelId: string,
|
|
37
|
+
hint?: PiRouteResolveHint,
|
|
38
|
+
): PiRouteSnapshot | undefined;
|
|
39
|
+
resolveCandidateRoutes?(virtualModelId: string): PiRouteSnapshot[];
|
|
40
|
+
subscribe?(listener: (event: PiRouteSnapshot) => void): () => void;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type PiRoutingRegistryV1 = {
|
|
44
|
+
version: 1;
|
|
45
|
+
registerRouter(adapter: PiRouterAdapterV1): () => void;
|
|
46
|
+
getRouter(virtualProvider: string): PiRouterAdapterV1 | undefined;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type PiCacheHintsInput = {
|
|
50
|
+
sessionIdHash?: string;
|
|
51
|
+
virtualProvider?: string;
|
|
52
|
+
virtualModelId?: string;
|
|
53
|
+
upstreamProvider?: string;
|
|
54
|
+
upstreamModelId?: string;
|
|
55
|
+
api?: string;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type PiCacheHintsOutput = {
|
|
59
|
+
systemPrompt?: string;
|
|
60
|
+
promptCacheKey?: string;
|
|
61
|
+
cacheRetention?: "long";
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export type PiCacheHintsV1 = {
|
|
65
|
+
version: 1;
|
|
66
|
+
getHints(input: PiCacheHintsInput): PiCacheHintsOutput | undefined;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export type ProtocolGlobal = typeof globalThis & Record<symbol, unknown> & {
|
|
70
|
+
__piCacheOptimizerRouter?: unknown;
|
|
71
|
+
__piCacheOptimizerCacheKey__?: unknown;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export type ModelRegistryLike = {
|
|
75
|
+
find?(provider: string, modelId: string): PiModel | undefined;
|
|
76
|
+
getAvailable?(): PiModel[];
|
|
77
|
+
getAll?(): PiModel[];
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export type ContextWithOptionalModelRegistry = Pick<ExtensionContext, "sessionManager"> & {
|
|
81
|
+
modelRegistry?: ModelRegistryLike;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Hash a session id for use as a non-reversible opaque scope key.
|
|
86
|
+
* Returns a 16-character hex string (64 bits of SHA-256 digest prefix)
|
|
87
|
+
* suitable for scoping stats buckets without exposing the raw session id.
|
|
88
|
+
*/
|
|
89
|
+
export function hashSessionId(sessionId: string): string {
|
|
90
|
+
return createHash("sha256").update(sessionId).digest("hex").slice(0, 16);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function getProtocolGlobal(): ProtocolGlobal {
|
|
94
|
+
return globalThis as ProtocolGlobal;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function firstNonEmptyString(...values: unknown[]): string | undefined {
|
|
98
|
+
for (const value of values) {
|
|
99
|
+
if (isNonEmptyString(value)) return value.trim();
|
|
100
|
+
}
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function sessionHashFromContext(ctx: Pick<ExtensionContext, "sessionManager">): string | undefined {
|
|
105
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
106
|
+
return sessionId ? hashSessionId(sessionId) : undefined;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function isPiRouterAdapterV1(value: unknown): value is PiRouterAdapterV1 {
|
|
110
|
+
const record = asRecord(value);
|
|
111
|
+
return !!record && isNonEmptyString(record.virtualProvider) && typeof record.resolveActiveRoute === "function";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function isRoutingRegistryV1(value: unknown): value is PiRoutingRegistryV1 {
|
|
115
|
+
const record = asRecord(value);
|
|
116
|
+
return !!record && record.version === 1 && typeof record.registerRouter === "function" && typeof record.getRouter === "function";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function createRoutingRegistry(): PiRoutingRegistryV1 {
|
|
120
|
+
const routers = new Map<string, PiRouterAdapterV1>();
|
|
121
|
+
return {
|
|
122
|
+
version: 1,
|
|
123
|
+
registerRouter(adapter: PiRouterAdapterV1): () => void {
|
|
124
|
+
if (!isPiRouterAdapterV1(adapter)) return () => undefined;
|
|
125
|
+
const key = adapter.virtualProvider.trim();
|
|
126
|
+
routers.set(key, adapter);
|
|
127
|
+
return () => {
|
|
128
|
+
if (routers.get(key) === adapter) routers.delete(key);
|
|
129
|
+
};
|
|
130
|
+
},
|
|
131
|
+
getRouter(virtualProvider: string): PiRouterAdapterV1 | undefined {
|
|
132
|
+
return routers.get(virtualProvider);
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function getRoutingRegistry(): PiRoutingRegistryV1 | undefined {
|
|
138
|
+
const candidate = getProtocolGlobal()[PI_ROUTING_REGISTRY_SYMBOL];
|
|
139
|
+
return isRoutingRegistryV1(candidate) ? candidate : undefined;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function ensureRoutingRegistry(): PiRoutingRegistryV1 {
|
|
143
|
+
const existing = getRoutingRegistry();
|
|
144
|
+
if (existing) return existing;
|
|
145
|
+
|
|
146
|
+
const created = createRoutingRegistry();
|
|
147
|
+
getProtocolGlobal()[PI_ROUTING_REGISTRY_SYMBOL] = created;
|
|
148
|
+
return created;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function parseRouteStatus(value: unknown): PiRouteSnapshot["status"] | undefined {
|
|
152
|
+
return value === "planned" || value === "trying" || value === "selected" || value === "success" || value === "failed"
|
|
153
|
+
? value
|
|
154
|
+
: undefined;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function parseRouteSnapshot(
|
|
158
|
+
value: unknown,
|
|
159
|
+
fallbackVirtualProvider?: string,
|
|
160
|
+
fallbackVirtualModelId?: string,
|
|
161
|
+
): PiRouteSnapshot | undefined {
|
|
162
|
+
const record = asRecord(value);
|
|
163
|
+
if (!record) return undefined;
|
|
164
|
+
|
|
165
|
+
const virtualProvider = firstNonEmptyString(record.virtualProvider, fallbackVirtualProvider);
|
|
166
|
+
const virtualModelId = firstNonEmptyString(record.virtualModelId, record.virtualModel, fallbackVirtualModelId);
|
|
167
|
+
const provider = firstNonEmptyString(record.provider, record.upstreamProvider, record.targetProvider);
|
|
168
|
+
const modelId = firstNonEmptyString(record.modelId, record.upstreamModelId, record.targetModelId, record.responseModel);
|
|
169
|
+
if (!virtualProvider || !virtualModelId || !provider || !modelId) return undefined;
|
|
170
|
+
|
|
171
|
+
const timestamp = getNumber(record.timestamp) ?? Date.now();
|
|
172
|
+
return {
|
|
173
|
+
virtualProvider,
|
|
174
|
+
virtualModelId,
|
|
175
|
+
provider,
|
|
176
|
+
modelId,
|
|
177
|
+
api: firstNonEmptyString(record.api),
|
|
178
|
+
canonicalModelId: firstNonEmptyString(record.canonicalModelId),
|
|
179
|
+
routeLabel: firstNonEmptyString(record.routeLabel, record.label),
|
|
180
|
+
status: parseRouteStatus(record.status),
|
|
181
|
+
sessionIdHash: firstNonEmptyString(record.sessionIdHash),
|
|
182
|
+
requestId: firstNonEmptyString(record.requestId),
|
|
183
|
+
timestamp,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function resolveActiveRouteSnapshot(
|
|
188
|
+
model: PiModel | undefined,
|
|
189
|
+
ctx?: Pick<ExtensionContext, "sessionManager">,
|
|
190
|
+
): PiRouteSnapshot | undefined {
|
|
191
|
+
if (!model || isNativeVirtualModel(model)) return undefined;
|
|
192
|
+
const hint: PiRouteResolveHint | undefined = ctx ? { sessionIdHash: sessionHashFromContext(ctx) } : undefined;
|
|
193
|
+
|
|
194
|
+
const adapter = getRoutingRegistry()?.getRouter(model.provider);
|
|
195
|
+
if (adapter) {
|
|
196
|
+
try {
|
|
197
|
+
const snapshot = parseRouteSnapshot(
|
|
198
|
+
adapter.resolveActiveRoute(model.id, hint),
|
|
199
|
+
model.provider,
|
|
200
|
+
model.id,
|
|
201
|
+
);
|
|
202
|
+
if (snapshot) return snapshot;
|
|
203
|
+
} catch (error) {
|
|
204
|
+
console.warn(`${LOG_PREFIX}: routing registry adapter failed`, error);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Temporary migration shim for the prototype global used by early router PRs.
|
|
209
|
+
// New integrations should use Symbol.for("pi.routing.registry.v1") instead.
|
|
210
|
+
const legacy = getProtocolGlobal().__piCacheOptimizerRouter;
|
|
211
|
+
if (!legacy || !lower(model.provider).includes("router")) return undefined;
|
|
212
|
+
try {
|
|
213
|
+
if (typeof legacy === "function") {
|
|
214
|
+
return parseRouteSnapshot(legacy(model.provider, model.id, hint), model.provider, model.id);
|
|
215
|
+
}
|
|
216
|
+
const legacyRecord = asRecord(legacy);
|
|
217
|
+
const resolver = legacyRecord?.resolveActiveRoute;
|
|
218
|
+
if (typeof resolver === "function") {
|
|
219
|
+
return parseRouteSnapshot(resolver.call(legacy, model.id, hint), model.provider, model.id);
|
|
220
|
+
}
|
|
221
|
+
return parseRouteSnapshot(legacy, model.provider, model.id);
|
|
222
|
+
} catch (error) {
|
|
223
|
+
console.warn(`${LOG_PREFIX}: legacy routing global failed`, error);
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function resolveNativeVirtualCandidateModels(
|
|
229
|
+
model: PiModel | undefined,
|
|
230
|
+
ctx?: ContextWithOptionalModelRegistry,
|
|
231
|
+
): PiModel[] | undefined {
|
|
232
|
+
if (!isNativeVirtualModel(model)) return undefined;
|
|
233
|
+
const adapter = model ? getRoutingRegistry()?.getRouter(model.provider) : undefined;
|
|
234
|
+
if (!adapter?.resolveCandidateRoutes) return undefined;
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
const rawSnapshots = adapter.resolveCandidateRoutes(model.id);
|
|
238
|
+
if (!Array.isArray(rawSnapshots) || rawSnapshots.length === 0) return undefined;
|
|
239
|
+
const candidates: PiModel[] = [];
|
|
240
|
+
for (const rawSnapshot of rawSnapshots) {
|
|
241
|
+
const snapshot = parseRouteSnapshot(rawSnapshot, model.provider, model.id);
|
|
242
|
+
if (!snapshot) return undefined;
|
|
243
|
+
const candidate = findModelInRegistry(ctx?.modelRegistry, snapshot.provider, snapshot.modelId)
|
|
244
|
+
?? routeSnapshotToPiModel(snapshot, model);
|
|
245
|
+
if (!isNonEmptyString(candidate.api)) return undefined;
|
|
246
|
+
candidates.push(candidate);
|
|
247
|
+
}
|
|
248
|
+
return candidates;
|
|
249
|
+
} catch {
|
|
250
|
+
return undefined;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function canRewriteNativeVirtualPrompt(
|
|
255
|
+
model: PiModel | undefined,
|
|
256
|
+
ctx?: ContextWithOptionalModelRegistry,
|
|
257
|
+
): boolean {
|
|
258
|
+
if (!isNativeVirtualModel(model) || !featureEnabled("virtualRewrite", VIRTUAL_REWRITE_ENV, false)) return false;
|
|
259
|
+
const candidates = resolveNativeVirtualCandidateModels(model, ctx);
|
|
260
|
+
return !!candidates?.length && candidates.every((candidate) =>
|
|
261
|
+
candidate.api === "openai-completions" || candidate.api === "anthropic-messages",
|
|
262
|
+
) && candidates.every((candidate) => !isResponsesPromptRewriteBypassApi(candidate.api));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function routeSnapshotToPiModel(snapshot: PiRouteSnapshot, fallback?: PiModel): PiModel {
|
|
266
|
+
const sameIdentity = fallback?.provider === snapshot.provider && fallback?.id === snapshot.modelId;
|
|
267
|
+
return {
|
|
268
|
+
...(sameIdentity ? fallback ?? {} : {}),
|
|
269
|
+
id: snapshot.modelId,
|
|
270
|
+
name: snapshot.canonicalModelId ?? snapshot.modelId,
|
|
271
|
+
provider: snapshot.provider,
|
|
272
|
+
api: snapshot.api ?? (sameIdentity ? fallback?.api : undefined) ?? "",
|
|
273
|
+
baseUrl: sameIdentity ? fallback?.baseUrl ?? "" : "",
|
|
274
|
+
reasoning: sameIdentity ? fallback?.reasoning ?? false : false,
|
|
275
|
+
input: sameIdentity ? fallback?.input ?? ["text"] : ["text"],
|
|
276
|
+
cost: sameIdentity
|
|
277
|
+
? fallback?.cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }
|
|
278
|
+
: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
279
|
+
contextWindow: sameIdentity ? fallback?.contextWindow ?? 0 : 0,
|
|
280
|
+
maxTokens: sameIdentity ? fallback?.maxTokens ?? 0 : 0,
|
|
281
|
+
compat: sameIdentity ? fallback?.compat : undefined,
|
|
282
|
+
[ROUTED_FALLBACK_MODEL_SYMBOL]: true,
|
|
283
|
+
} as PiModel;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export function findModelInRegistry(registry: ModelRegistryLike | undefined, provider: string, id: string): PiModel | undefined {
|
|
287
|
+
try {
|
|
288
|
+
const found = registry?.find?.(provider, id);
|
|
289
|
+
if (found) return found;
|
|
290
|
+
|
|
291
|
+
const available = registry?.getAvailable?.() ?? [];
|
|
292
|
+
const availableMatch = available.find((candidate) => candidate.provider === provider && candidate.id === id);
|
|
293
|
+
if (availableMatch) return availableMatch;
|
|
294
|
+
|
|
295
|
+
const all = registry?.getAll?.() ?? [];
|
|
296
|
+
return all.find((candidate) => candidate.provider === provider && candidate.id === id);
|
|
297
|
+
} catch {
|
|
298
|
+
// Registry extensions are optional input; a malformed/throwing registry
|
|
299
|
+
// must not turn a provider error hook into a Pi session failure.
|
|
300
|
+
return undefined;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function applyConfiguredTransportToModel(model: PiModel, config: unknown): PiModel {
|
|
305
|
+
const providers = asRecord(asRecord(config)?.providers);
|
|
306
|
+
const provider = asRecord(providers?.[model.provider]);
|
|
307
|
+
const customModel = findLastExactModelDefinition(
|
|
308
|
+
Array.isArray(provider?.models) ? provider.models : undefined,
|
|
309
|
+
model.id,
|
|
310
|
+
);
|
|
311
|
+
const configuredApi = customModel?.api ?? provider?.api;
|
|
312
|
+
const configuredBaseUrl = customModel?.baseUrl ?? provider?.baseUrl;
|
|
313
|
+
const api = isNonEmptyString(model.api)
|
|
314
|
+
? model.api
|
|
315
|
+
: (isNonEmptyString(configuredApi) ? configuredApi : model.api);
|
|
316
|
+
const baseUrl = isNonEmptyString(model.baseUrl)
|
|
317
|
+
? model.baseUrl
|
|
318
|
+
: (isNonEmptyString(configuredBaseUrl) ? configuredBaseUrl : model.baseUrl);
|
|
319
|
+
return api === model.api && baseUrl === model.baseUrl ? model : { ...model, api, baseUrl };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export function resolveRouteModel(
|
|
323
|
+
model: PiModel | undefined,
|
|
324
|
+
ctx?: ContextWithOptionalModelRegistry,
|
|
325
|
+
): PiModel | undefined {
|
|
326
|
+
const nativeVirtualRoute = resolveNativeVirtualRouteModel(model, ctx);
|
|
327
|
+
if (nativeVirtualRoute) return nativeVirtualRoute;
|
|
328
|
+
|
|
329
|
+
const snapshot = resolveActiveRouteSnapshot(model, ctx);
|
|
330
|
+
if (!snapshot) return undefined;
|
|
331
|
+
|
|
332
|
+
const resolved = findModelInRegistry(ctx?.modelRegistry, snapshot.provider, snapshot.modelId)
|
|
333
|
+
?? routeSnapshotToPiModel(snapshot, model);
|
|
334
|
+
return applyConfiguredTransportToModel(resolved, readEffectiveCompatConfig());
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Pi 0.99+ native virtual models (`pi.registerVirtualModel()`). `ctx.model`
|
|
338
|
+
// stays the virtual selection while Pi dispatches every request to a physical
|
|
339
|
+
// model; only assistant messages and the provider payload name that model.
|
|
340
|
+
// Older Pi hosts never produce this API, so every native-virtual path is inert.
|
|
341
|
+
export type NativeVirtualDispatch = { provider: string; id: string; api: string };
|
|
342
|
+
|
|
343
|
+
export function readSessionBranch(ctx: Pick<ExtensionContext, "sessionManager"> | undefined): unknown[] {
|
|
344
|
+
try {
|
|
345
|
+
const manager = ctx?.sessionManager as { getBranch?: () => unknown } | undefined;
|
|
346
|
+
if (typeof manager?.getBranch !== "function") return [];
|
|
347
|
+
const branch = manager.getBranch();
|
|
348
|
+
return Array.isArray(branch) ? branch : [];
|
|
349
|
+
} catch {
|
|
350
|
+
return [];
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Physical models that answered on the current session branch, newest first:
|
|
356
|
+
* the latest successful response (Pi's `request.previous`) and the latest
|
|
357
|
+
* response of any outcome (a failed attempt that a retry may reuse). The
|
|
358
|
+
* catalog id in `message.model` is preferred over the echoed `responseModel`
|
|
359
|
+
* because it is the id Pi dispatched and the registry knows.
|
|
360
|
+
*/
|
|
361
|
+
export function findNativeVirtualDispatches(ctx: Pick<ExtensionContext, "sessionManager"> | undefined): {
|
|
362
|
+
latestSuccessful?: NativeVirtualDispatch;
|
|
363
|
+
latestAny?: NativeVirtualDispatch;
|
|
364
|
+
} {
|
|
365
|
+
const branch = readSessionBranch(ctx);
|
|
366
|
+
let latestSuccessful: NativeVirtualDispatch | undefined;
|
|
367
|
+
let latestAny: NativeVirtualDispatch | undefined;
|
|
368
|
+
for (let index = branch.length - 1; index >= 0 && !latestSuccessful; index--) {
|
|
369
|
+
const entry = asRecord(branch[index]);
|
|
370
|
+
if (entry?.type !== "message") continue;
|
|
371
|
+
const message = getAssistantRecord(entry.message);
|
|
372
|
+
if (!message || isNativeVirtualModel(message)) continue;
|
|
373
|
+
const dispatch = nativeVirtualDispatchFromMessage(message);
|
|
374
|
+
if (!dispatch) continue;
|
|
375
|
+
latestAny ??= dispatch;
|
|
376
|
+
if (message.stopReason !== "error" && message.stopReason !== "aborted") latestSuccessful = dispatch;
|
|
377
|
+
}
|
|
378
|
+
return { latestSuccessful, latestAny };
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export function nativeVirtualDispatchFromMessage(message: unknown): NativeVirtualDispatch | undefined {
|
|
382
|
+
const record = getAssistantRecord(message);
|
|
383
|
+
if (!record || isNativeVirtualModel(record)) return undefined;
|
|
384
|
+
const provider = firstNonEmptyString(record.provider);
|
|
385
|
+
const id = firstNonEmptyString(record.model, record.responseModel);
|
|
386
|
+
return provider && id ? { provider, id, api: firstNonEmptyString(record.api) ?? "" } : undefined;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export function nativeVirtualDispatchToModel(
|
|
390
|
+
dispatch: NativeVirtualDispatch,
|
|
391
|
+
ctx?: ContextWithOptionalModelRegistry,
|
|
392
|
+
): PiModel {
|
|
393
|
+
// A virtual model hides a same-id physical model in the catalog, so a
|
|
394
|
+
// registry hit that is itself virtual is not physical metadata.
|
|
395
|
+
const registered = findModelInRegistry(ctx?.modelRegistry, dispatch.provider, dispatch.id);
|
|
396
|
+
const model = registered && !isNativeVirtualModel(registered)
|
|
397
|
+
? registered
|
|
398
|
+
: ({
|
|
399
|
+
provider: dispatch.provider,
|
|
400
|
+
id: dispatch.id,
|
|
401
|
+
name: dispatch.id,
|
|
402
|
+
api: dispatch.api,
|
|
403
|
+
baseUrl: "",
|
|
404
|
+
reasoning: false,
|
|
405
|
+
input: ["text"],
|
|
406
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
407
|
+
contextWindow: 0,
|
|
408
|
+
maxTokens: 0,
|
|
409
|
+
[ROUTED_FALLBACK_MODEL_SYMBOL]: true,
|
|
410
|
+
} as PiModel);
|
|
411
|
+
return applyConfiguredTransportToModel(model, readEffectiveCompatConfig());
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Physical model behind a native virtual selection for pre-request UX (footer,
|
|
416
|
+
* doctor, compat, stats, reset, fix): the model that answered last on this
|
|
417
|
+
* session branch, matching Pi's own routed-model display and context limits.
|
|
418
|
+
*/
|
|
419
|
+
export function resolveNativeVirtualRouteModel(
|
|
420
|
+
model: PiModel | undefined,
|
|
421
|
+
ctx?: ContextWithOptionalModelRegistry,
|
|
422
|
+
): PiModel | undefined {
|
|
423
|
+
if (!isNativeVirtualModel(model)) return undefined;
|
|
424
|
+
const { latestSuccessful, latestAny } = findNativeVirtualDispatches(ctx);
|
|
425
|
+
const dispatch = latestSuccessful ?? latestAny;
|
|
426
|
+
return dispatch ? nativeVirtualDispatchToModel(dispatch, ctx) : undefined;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
export function getProviderPayloadModelId(payload: unknown): string | undefined {
|
|
430
|
+
const record = asRecord(payload);
|
|
431
|
+
// Pi's built-in transports put the dispatched catalog id in `model`
|
|
432
|
+
// (OpenAI, Anthropic, Google, Mistral) or `modelId` (Bedrock Converse).
|
|
433
|
+
return firstNonEmptyString(record?.model, record?.modelId);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
export function physicalModelsWithId(candidates: readonly PiModel[] | undefined, id: string): PiModel[] {
|
|
437
|
+
return (candidates ?? []).filter((candidate) => candidate.id === id
|
|
438
|
+
&& !isNativeVirtualModel(candidate)
|
|
439
|
+
&& !isRouterModel(candidate)
|
|
440
|
+
&& !getRoutingRegistry()?.getRouter(candidate.provider));
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Physical model of one native-virtual request. `before_provider_request`
|
|
445
|
+
* carries only the payload and `ctx.model` stays virtual, so the payload's
|
|
446
|
+
* dispatched model id is matched against credentialed physical models (a
|
|
447
|
+
* router may only route to those). A unique provider match wins. An id shared
|
|
448
|
+
* by several credentialed providers is used only when every candidate gets the
|
|
449
|
+
* same request treatment (`requestPolicyKey`); the identity then stays
|
|
450
|
+
* ambiguous so response evidence is never pinned to a guessed provider.
|
|
451
|
+
* Differing policies return undefined so identity-dependent request mutations
|
|
452
|
+
* fail closed. Without a credentialed match, the sticky branch candidates and
|
|
453
|
+
* then the full catalog are consulted.
|
|
454
|
+
*/
|
|
455
|
+
export function resolveNativeVirtualRequestModel(
|
|
456
|
+
model: PiModel | undefined,
|
|
457
|
+
payload: unknown,
|
|
458
|
+
ctx?: ContextWithOptionalModelRegistry,
|
|
459
|
+
requestPolicyKey?: (candidate: PiModel) => string,
|
|
460
|
+
): { model: PiModel; identityAmbiguous: boolean } | undefined {
|
|
461
|
+
if (!isNativeVirtualModel(model)) return undefined;
|
|
462
|
+
const payloadModelId = getProviderPayloadModelId(payload);
|
|
463
|
+
if (!payloadModelId) return undefined;
|
|
464
|
+
const config = readEffectiveCompatConfig();
|
|
465
|
+
// null: no candidate; undefined: ambiguous with differing request policies.
|
|
466
|
+
const pick = (matches: PiModel[]): { model: PiModel; identityAmbiguous: boolean } | undefined | null => {
|
|
467
|
+
if (matches.length === 0) return null;
|
|
468
|
+
const configured = matches.map((candidate) => applyConfiguredTransportToModel(candidate, config));
|
|
469
|
+
if (new Set(configured.map((candidate) => candidate.provider)).size === 1) {
|
|
470
|
+
return { model: configured[0], identityAmbiguous: false };
|
|
471
|
+
}
|
|
472
|
+
const policies = requestPolicyKey ? new Set(configured.map(requestPolicyKey)) : undefined;
|
|
473
|
+
return policies?.size === 1 ? { model: configured[0], identityAmbiguous: true } : undefined;
|
|
474
|
+
};
|
|
475
|
+
try {
|
|
476
|
+
const credentialed = pick(physicalModelsWithId(ctx?.modelRegistry?.getAvailable?.(), payloadModelId));
|
|
477
|
+
if (credentialed !== null) return credentialed;
|
|
478
|
+
|
|
479
|
+
const { latestSuccessful, latestAny } = findNativeVirtualDispatches(ctx);
|
|
480
|
+
for (const dispatch of [latestSuccessful, latestAny]) {
|
|
481
|
+
if (dispatch?.id === payloadModelId) {
|
|
482
|
+
return { model: nativeVirtualDispatchToModel(dispatch, ctx), identityAmbiguous: false };
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return pick(physicalModelsWithId(ctx?.modelRegistry?.getAll?.(), payloadModelId)) ?? undefined;
|
|
487
|
+
} catch {
|
|
488
|
+
// Registry access is optional input; never fail the provider request hook.
|
|
489
|
+
return undefined;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
export function describeNativeVirtualRouteNote(selected: PiModel | undefined, effective: PiModel | undefined): string | undefined {
|
|
494
|
+
if (!selected || !isNativeVirtualModel(selected)) return undefined;
|
|
495
|
+
// Without a routed response, the not-applicable text explains the state.
|
|
496
|
+
if (!effective || isNativeVirtualModel(effective)) return undefined;
|
|
497
|
+
return `🔀 Native virtual model ${modelKey(selected)} → latest routed physical model ${modelKey(effective)}. The diagnostics below apply to that physical model; Pi may route later requests elsewhere.`;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
export function installCacheHintsService(
|
|
501
|
+
service: PiCacheHintsV1,
|
|
502
|
+
options: { discardPrevious?: (value: unknown) => boolean } = {},
|
|
503
|
+
): () => void {
|
|
504
|
+
const globals = getProtocolGlobal();
|
|
505
|
+
const previous = globals[PI_CACHE_HINTS_SYMBOL];
|
|
506
|
+
globals[PI_CACHE_HINTS_SYMBOL] = service;
|
|
507
|
+
return () => {
|
|
508
|
+
if (globals[PI_CACHE_HINTS_SYMBOL] !== service) return;
|
|
509
|
+
if (previous !== undefined && !options.discardPrevious?.(previous)) {
|
|
510
|
+
globals[PI_CACHE_HINTS_SYMBOL] = previous;
|
|
511
|
+
} else {
|
|
512
|
+
delete globals[PI_CACHE_HINTS_SYMBOL];
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// ── Non-GPT OpenAI-compatible model detection ──────────────────────
|
|
518
|
+
|
|
519
|
+
export function isRouterModel(model: PiModel | undefined): boolean {
|
|
520
|
+
return lower(model?.provider) === "router";
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
export const PI_CACHE_HINTS_OWNER_SYMBOL = Symbol.for("pi.cache.optimizer.hints-owner.v1");
|
|
524
|
+
|
|
525
|
+
export type PiCacheHintSnapshot = PiCacheHintsInput & PiCacheHintsOutput & {
|
|
526
|
+
timestamp: number;
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
export function getSessionPromptCacheKey(ctx: ExtensionContext): string | undefined {
|
|
530
|
+
return clampPromptCacheKey(ctx.sessionManager.getSessionId());
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
export function isCacheHintsServiceV1(value: unknown): value is PiCacheHintsV1 {
|
|
534
|
+
const record = asRecord(value);
|
|
535
|
+
return !!record && record.version === 1 && typeof record.getHints === "function";
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
export function getCacheHintsService(): PiCacheHintsV1 | undefined {
|
|
539
|
+
const candidate = getProtocolGlobal()[PI_CACHE_HINTS_SYMBOL];
|
|
540
|
+
return isCacheHintsServiceV1(candidate) ? candidate : undefined;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
export function markOptimizerOwnedCacheHintsService(service: PiCacheHintsV1): PiCacheHintsV1 {
|
|
544
|
+
(service as PiCacheHintsV1 & Record<symbol, unknown>)[PI_CACHE_HINTS_OWNER_SYMBOL] = true;
|
|
545
|
+
return service;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
export function isOptimizerOwnedCacheHintsService(value: unknown): boolean {
|
|
549
|
+
return typeof value === "object" && value !== null &&
|
|
550
|
+
(value as Record<symbol, unknown>)[PI_CACHE_HINTS_OWNER_SYMBOL] === true;
|
|
551
|
+
}
|