@oh-my-pi/pi-catalog 18.2.2 → 18.2.4
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/CHANGELOG.md +12 -0
- package/dist/types/compat/auth-ids.d.ts +2 -2
- package/dist/types/model-manager.d.ts +7 -0
- package/dist/types/types.d.ts +6 -0
- package/package.json +4 -4
- package/src/compat/auth-ids.ts +2 -0
- package/src/compat/rules/auth/_order.kdl +1 -1
- package/src/compat/rules/auth/typesafe.kdl +16 -0
- package/src/compat/rules.json +1 -1
- package/src/model-cache.ts +7 -4
- package/src/model-manager.ts +30 -3
- package/src/types.ts +6 -0
package/src/model-cache.ts
CHANGED
|
@@ -394,6 +394,7 @@ function readRowUncached<TApi extends Api>(
|
|
|
394
394
|
|
|
395
395
|
/** Whether a live model carries at least one request header. */
|
|
396
396
|
function hasModelHeaders(model: Model<Api>): boolean {
|
|
397
|
+
if (model.resolveHeaders) return true;
|
|
397
398
|
const headers = model.headers;
|
|
398
399
|
if (!headers) return false;
|
|
399
400
|
for (const _key in headers) return true;
|
|
@@ -409,7 +410,7 @@ function hasModelHeaders(model: Model<Api>): boolean {
|
|
|
409
410
|
* so a compatible cache can be consumed without running `buildModel`.
|
|
410
411
|
*/
|
|
411
412
|
function toCachedModel<TApi extends Api>(model: Model<TApi>): PersistedModel<TApi> {
|
|
412
|
-
const { headers: _headers, ...rest } = model;
|
|
413
|
+
const { headers: _headers, resolveHeaders: _resolveHeaders, ...rest } = model;
|
|
413
414
|
return {
|
|
414
415
|
...rest,
|
|
415
416
|
supportsComputerUseConfig: model.supportsComputerUseConfig ?? null,
|
|
@@ -458,9 +459,11 @@ export function writeModelCache<TApi extends Api>(
|
|
|
458
459
|
// headers equal a trusted provider-wide fallback that the reader can
|
|
459
460
|
// re-derive without persisting it. This keeps reference-less models
|
|
460
461
|
// with constant or configured headers alive offline.
|
|
461
|
-
const matchesStatic =
|
|
462
|
-
?
|
|
463
|
-
:
|
|
462
|
+
const matchesStatic = model.resolveHeaders
|
|
463
|
+
? staticHeaderSource?.resolveHeaders === model.resolveHeaders
|
|
464
|
+
: staticHeaderSource
|
|
465
|
+
? headersEqual(model.headers, staticHeaderSource.headers)
|
|
466
|
+
: headersEqual(model.headers, restorableHeaderFallback);
|
|
464
467
|
if (!matchesStatic) {
|
|
465
468
|
unrestorableHeaderModelIds.push(model.id);
|
|
466
469
|
}
|
package/src/model-manager.ts
CHANGED
|
@@ -59,6 +59,13 @@ export interface ModelManagerOptions<TApi extends Api = Api, TModelsDevPayload =
|
|
|
59
59
|
* bearer header comes from the current local config.
|
|
60
60
|
*/
|
|
61
61
|
restorableHeaderFallback?: Record<string, string>;
|
|
62
|
+
/**
|
|
63
|
+
* Reconstruct omitted request headers from authoritative local configuration
|
|
64
|
+
* when no trusted static source remains. Return undefined to keep the row
|
|
65
|
+
* unavailable. This hook must not perform I/O; attach `resolveHeaders` for
|
|
66
|
+
* command-backed credentials that must wait until an actual request.
|
|
67
|
+
*/
|
|
68
|
+
restoreCachedHeaders?: (model: Readonly<Model>) => Pick<Model, "headers" | "resolveHeaders"> | undefined;
|
|
62
69
|
/** Optional dynamic endpoint fetcher. */
|
|
63
70
|
fetchDynamicModels?: () => Promise<readonly ModelSpec<TApi>[] | null>;
|
|
64
71
|
/** Optional stencil.so fallback hook. */
|
|
@@ -153,6 +160,7 @@ function restoreCachedModelHeaders<TApi extends Api>(
|
|
|
153
160
|
unrestorableHeaderModelIds: readonly string[],
|
|
154
161
|
legacyHeaderRestoreMarkers: boolean,
|
|
155
162
|
restorableHeaderFallback: Record<string, string> | undefined,
|
|
163
|
+
restoreCachedHeaders: ModelManagerOptions<TApi>["restoreCachedHeaders"],
|
|
156
164
|
): CachedHeaderRestoreResult<TApi> {
|
|
157
165
|
if (headerOmittedModelIds.length === 0) {
|
|
158
166
|
return { models: [...cachedModels], unresolvedModelIds: new Set() };
|
|
@@ -172,7 +180,7 @@ function restoreCachedModelHeaders<TApi extends Api>(
|
|
|
172
180
|
? staticById.get(model.requestModelId)
|
|
173
181
|
: undefined
|
|
174
182
|
: (staticById.get(model.id) ?? (model.requestModelId ? staticById.get(model.requestModelId) : undefined));
|
|
175
|
-
if (!staticModel?.headers) {
|
|
183
|
+
if (!staticModel?.headers && !staticModel?.resolveHeaders) {
|
|
176
184
|
// A non-unrestorable row whose static source is gone was cached with
|
|
177
185
|
// headers matching the provider's trusted constant (e.g. a Copilot
|
|
178
186
|
// model with no bundled entry). Reattach the constant by value instead
|
|
@@ -180,10 +188,12 @@ function restoreCachedModelHeaders<TApi extends Api>(
|
|
|
180
188
|
if (!unrestorable && restorableHeaderFallback) {
|
|
181
189
|
return { ...model, headers: { ...restorableHeaderFallback } };
|
|
182
190
|
}
|
|
191
|
+
const configured = restoreCachedHeaders?.(model);
|
|
192
|
+
if (configured?.headers || configured?.resolveHeaders) return { ...model, ...configured };
|
|
183
193
|
unresolvedModelIds.add(model.id);
|
|
184
194
|
return model;
|
|
185
195
|
}
|
|
186
|
-
return { ...model, headers: staticModel.headers };
|
|
196
|
+
return { ...model, headers: staticModel.headers, resolveHeaders: staticModel.resolveHeaders };
|
|
187
197
|
});
|
|
188
198
|
return { models: restored, unresolvedModelIds };
|
|
189
199
|
}
|
|
@@ -219,6 +229,7 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
|
|
|
219
229
|
cache?.unrestorableHeaderModelIds ?? [],
|
|
220
230
|
cache?.legacyHeaderRestoreMarkers ?? false,
|
|
221
231
|
restorableHeaderFallback,
|
|
232
|
+
options.restoreCachedHeaders,
|
|
222
233
|
);
|
|
223
234
|
const usableCachedModels = restoredCache.models.filter(model => !restoredCache.unresolvedModelIds.has(model.id));
|
|
224
235
|
const cacheHasUnresolvedHeaders = restoredCache.unresolvedModelIds.size > 0;
|
|
@@ -361,6 +372,7 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
|
|
|
361
372
|
latestCache?.unrestorableHeaderModelIds ?? cache?.unrestorableHeaderModelIds ?? [],
|
|
362
373
|
latestCache?.legacyHeaderRestoreMarkers ?? cache?.legacyHeaderRestoreMarkers ?? false,
|
|
363
374
|
restorableHeaderFallback,
|
|
375
|
+
options.restoreCachedHeaders,
|
|
364
376
|
);
|
|
365
377
|
const latestUsableCacheModels = latestRestoredCache.models.filter(
|
|
366
378
|
model => !latestRestoredCache.unresolvedModelIds.has(model.id),
|
|
@@ -582,6 +594,16 @@ function mergeDynamicModel<TApi extends Api>(existingModel: Model<TApi>, dynamic
|
|
|
582
594
|
: existingModel.reasoning || dynamicModel.reasoning;
|
|
583
595
|
const longContextCost = dynamicModel.cost.longContext ?? existingModel.cost.longContext;
|
|
584
596
|
const timeBasedCost = dynamicModel.cost.timeBased ?? existingModel.cost.timeBased;
|
|
597
|
+
const existingHeaders = existingModel.resolveHeaders ?? existingModel.headers;
|
|
598
|
+
const dynamicHeaders = dynamicModel.resolveHeaders ?? dynamicModel.headers;
|
|
599
|
+
let resolveHeaders = dynamicModel.resolveHeaders ?? existingModel.resolveHeaders;
|
|
600
|
+
if (resolveHeaders && existingHeaders && dynamicHeaders && existingHeaders !== dynamicHeaders) {
|
|
601
|
+
resolveHeaders = async signal => {
|
|
602
|
+
const previous = typeof existingHeaders === "function" ? await existingHeaders(signal) : existingHeaders;
|
|
603
|
+
const next = typeof dynamicHeaders === "function" ? await dynamicHeaders(signal) : dynamicHeaders;
|
|
604
|
+
return { ...previous, ...next };
|
|
605
|
+
};
|
|
606
|
+
}
|
|
585
607
|
// Re-build from spec stage: sparse compat comes from `compatConfig` (the
|
|
586
608
|
// verbatim override vocabulary), never the resolved `compat` record.
|
|
587
609
|
return buildModel({
|
|
@@ -600,7 +622,12 @@ function mergeDynamicModel<TApi extends Api>(existingModel: Model<TApi>, dynamic
|
|
|
600
622
|
},
|
|
601
623
|
contextWindow: preferDiscoveryLimit(dynamicModel.contextWindow, existingModel.contextWindow),
|
|
602
624
|
maxTokens: preferDiscoveryLimit(dynamicModel.maxTokens, existingModel.maxTokens),
|
|
603
|
-
headers:
|
|
625
|
+
headers: resolveHeaders
|
|
626
|
+
? undefined
|
|
627
|
+
: dynamicModel.headers
|
|
628
|
+
? { ...existingModel.headers, ...dynamicModel.headers }
|
|
629
|
+
: existingModel.headers,
|
|
630
|
+
resolveHeaders,
|
|
604
631
|
compat: dynamicModel.compatConfig ?? existingModel.compatConfig,
|
|
605
632
|
contextPromotionTarget: dynamicModel.contextPromotionTarget ?? existingModel.contextPromotionTarget,
|
|
606
633
|
} as ModelSpec<TApi>);
|
package/src/types.ts
CHANGED
|
@@ -1226,6 +1226,12 @@ export interface Model<TApi extends Api = Api> {
|
|
|
1226
1226
|
*/
|
|
1227
1227
|
omitMaxOutputTokens?: boolean;
|
|
1228
1228
|
headers?: Record<string, string>;
|
|
1229
|
+
/**
|
|
1230
|
+
* Materialize config-backed headers immediately before a request. Catalog
|
|
1231
|
+
* inspection never invokes this hook; transports receive a cloned model
|
|
1232
|
+
* whose `headers` is a plain resolved record and whose hook is removed.
|
|
1233
|
+
*/
|
|
1234
|
+
resolveHeaders?: (signal?: AbortSignal) => Promise<Record<string, string> | undefined>;
|
|
1229
1235
|
/**
|
|
1230
1236
|
* Streaming transport override. When `"pi-native"`, `streamSimple` routes
|
|
1231
1237
|
* the request to the model's `baseUrl` via the auth-gateway's
|