@oh-my-pi/pi-catalog 18.2.4 → 18.2.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/CHANGELOG.md +10 -0
- package/dist/types/compat/auth-ids.d.ts +2 -2
- package/dist/types/compat/cascade.d.ts +0 -4
- package/dist/types/compat/taxonomy.d.ts +0 -7
- package/dist/types/compat/types.d.ts +4 -0
- package/dist/types/model-tokenizer.d.ts +1 -1
- package/package.json +4 -4
- package/src/build.ts +1 -1
- package/src/compat/auth-ids.ts +2 -0
- package/src/compat/cascade.ts +19 -4
- package/src/compat/rules/README.md +2 -0
- package/src/compat/rules/auth/_order.kdl +1 -1
- package/src/compat/rules/auth/stencil.kdl +36 -0
- package/src/compat/rules/providers/yolo-auto.kdl +38 -7
- package/src/compat/rules/taxonomy/qwen.kdl +1 -0
- package/src/compat/rules.json +1 -1
- package/src/compat/taxonomy.ts +73 -14
- package/src/compat/types.ts +4 -0
- package/src/discovery/gemini.ts +7 -2
- package/src/discovery/gitlab-duo-workflow.ts +6 -2
- package/src/model-tokenizer.ts +8 -4
- package/src/models.json +1 -1
package/src/compat/taxonomy.ts
CHANGED
|
@@ -201,28 +201,60 @@ function ranksInClass(classId: string, model: string, lenient: boolean): Omit<Cl
|
|
|
201
201
|
return out;
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
204
|
+
// Lowercase override index built once per process: avoids re-lowercasing every
|
|
205
|
+
// override model/provider string on every classifyModel call. First override
|
|
206
|
+
// in tree order wins per (model, provider) slot, matching the linear scan it
|
|
207
|
+
// replaced.
|
|
208
|
+
interface OverrideBucket {
|
|
209
|
+
byProvider: Map<string, CompiledIdentityOverride>;
|
|
210
|
+
agnostic: CompiledIdentityOverride | undefined;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
let overrideIndex: Map<string, OverrideBucket> | undefined;
|
|
214
|
+
|
|
215
|
+
function getOverrideIndex(): Map<string, OverrideBucket> {
|
|
216
|
+
if (overrideIndex !== undefined) return overrideIndex;
|
|
217
|
+
const index = new Map<string, OverrideBucket>();
|
|
212
218
|
for (const cls of rules.taxonomy.classes) {
|
|
213
219
|
for (const override of cls.overrides) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
220
|
+
const key = override.model.toLowerCase();
|
|
221
|
+
let bucket = index.get(key);
|
|
222
|
+
if (bucket === undefined) {
|
|
223
|
+
bucket = { byProvider: new Map(), agnostic: undefined };
|
|
224
|
+
index.set(key, bucket);
|
|
217
225
|
}
|
|
218
226
|
if (override.provider !== undefined) {
|
|
219
|
-
|
|
227
|
+
const providerKey = override.provider.toLowerCase();
|
|
228
|
+
if (!bucket.byProvider.has(providerKey)) bucket.byProvider.set(providerKey, override);
|
|
220
229
|
} else {
|
|
221
|
-
agnostic ??= override;
|
|
230
|
+
bucket.agnostic ??= override;
|
|
222
231
|
}
|
|
223
232
|
}
|
|
224
233
|
}
|
|
225
|
-
|
|
234
|
+
overrideIndex = index;
|
|
235
|
+
return index;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function findIdentityOverride(
|
|
239
|
+
provider: string,
|
|
240
|
+
bareModel: string,
|
|
241
|
+
observedAtMs: number | undefined,
|
|
242
|
+
): CompiledIdentityOverride | undefined {
|
|
243
|
+
const bucket = getOverrideIndex().get(bareModel.toLowerCase());
|
|
244
|
+
if (bucket === undefined) return undefined;
|
|
245
|
+
const scoped = bucket.byProvider.get(provider.toLowerCase());
|
|
246
|
+
if (scoped !== undefined) {
|
|
247
|
+
if (scoped.expiresAtMs === undefined || observedAtMs === undefined || observedAtMs < scoped.expiresAtMs) {
|
|
248
|
+
return scoped;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const agnostic = bucket.agnostic;
|
|
252
|
+
if (agnostic !== undefined) {
|
|
253
|
+
if (agnostic.expiresAtMs === undefined || observedAtMs === undefined || observedAtMs < agnostic.expiresAtMs) {
|
|
254
|
+
return agnostic;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return undefined;
|
|
226
258
|
}
|
|
227
259
|
|
|
228
260
|
/** Result of collapsing a wire id through the suffix vocabulary. */
|
|
@@ -330,10 +362,37 @@ export function stripThinkingVariantSuffix(model: string): string | undefined {
|
|
|
330
362
|
* Classifies a model into its structured identity: reviewed override first,
|
|
331
363
|
* then suffix collapse, then class/family/revision ranks over the logical id.
|
|
332
364
|
*
|
|
365
|
+
* Results are memoized per (provider, model, lenient) — the rule tree is
|
|
366
|
+
* static per process. Cached identities are frozen and each caller receives
|
|
367
|
+
* a shallow copy, so a consumer that mutates its copy (e.g. `buildModel`
|
|
368
|
+
* storing `policy.identity` on the model) cannot poison the cache or any
|
|
369
|
+
* other model classified under the same key. Calls with an explicit
|
|
370
|
+
* observedAtMs skip the memo so override expiry stays exact.
|
|
371
|
+
*
|
|
333
372
|
* @throws AmbiguousIdentityError on equal-rank cross-class or cross-family
|
|
334
373
|
* matches unless `opts.lenient`.
|
|
335
374
|
*/
|
|
375
|
+
const classifyMemo = new Map<string, ModelIdentity>();
|
|
376
|
+
const CLASSIFY_MEMO_MAX = 4096;
|
|
377
|
+
|
|
378
|
+
function classifyMemoKey(provider: string, modelId: string, lenient: boolean): string {
|
|
379
|
+
return `${provider.length}:${provider}${modelId.length}:${modelId}${lenient ? 1 : 0}`;
|
|
380
|
+
}
|
|
381
|
+
|
|
336
382
|
export function classifyModel(provider: string, modelId: string, opts?: ClassifyOptions): ModelIdentity {
|
|
383
|
+
if (opts?.observedAtMs === undefined) {
|
|
384
|
+
const key = classifyMemoKey(provider, modelId, opts?.lenient === true);
|
|
385
|
+
const cached = classifyMemo.get(key);
|
|
386
|
+
if (cached !== undefined) return { ...cached };
|
|
387
|
+
const identity = classifyModelUncached(provider, modelId, opts);
|
|
388
|
+
if (classifyMemo.size >= CLASSIFY_MEMO_MAX) classifyMemo.clear();
|
|
389
|
+
classifyMemo.set(key, Object.freeze(identity));
|
|
390
|
+
return { ...identity };
|
|
391
|
+
}
|
|
392
|
+
return classifyModelUncached(provider, modelId, opts);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function classifyModelUncached(provider: string, modelId: string, opts?: ClassifyOptions): ModelIdentity {
|
|
337
396
|
const lenient = opts?.lenient === true;
|
|
338
397
|
const trimmed = modelId.trim();
|
|
339
398
|
const bare = bareOf(trimmed);
|
package/src/compat/types.ts
CHANGED
|
@@ -456,6 +456,10 @@ export interface CompiledOAuthCodeLogin {
|
|
|
456
456
|
kind: "oauth-code";
|
|
457
457
|
clientId?: CompiledAuthValue;
|
|
458
458
|
clientSecret?: CompiledAuthValue;
|
|
459
|
+
/** `{base}` placeholder source (the provider's API origin). */
|
|
460
|
+
baseUrl?: CompiledAuthValue;
|
|
461
|
+
/** `{auth}` placeholder source for the authorize, token, and userinfo URLs when the issuer is a separate host. */
|
|
462
|
+
authUrl?: CompiledAuthValue;
|
|
459
463
|
authorizeUrl: CompiledAuthValue;
|
|
460
464
|
scopes: string[];
|
|
461
465
|
scopeSeparator: string;
|
package/src/discovery/gemini.ts
CHANGED
|
@@ -8,15 +8,20 @@ const GOOGLE_GENERATIVE_AI_BASE_URL = "https://generativelanguage.googleapis.com
|
|
|
8
8
|
const DEFAULT_PAGE_SIZE = 100;
|
|
9
9
|
const DEFAULT_MAX_PAGES = 25;
|
|
10
10
|
|
|
11
|
+
// Hoisted: constructing type("string")/type("number") per property per row
|
|
12
|
+
// builds a fresh schema (~2 closures + 13 slots) on a discovery hot path.
|
|
13
|
+
const stringSchema = type("string");
|
|
14
|
+
const numberSchema = type("number");
|
|
15
|
+
|
|
11
16
|
const resilientString = type("unknown").pipe(val => {
|
|
12
17
|
if (val === undefined) return undefined;
|
|
13
|
-
const out =
|
|
18
|
+
const out = stringSchema(val);
|
|
14
19
|
return out instanceof type.errors ? undefined : out;
|
|
15
20
|
});
|
|
16
21
|
|
|
17
22
|
const resilientNumber = type("unknown").pipe(val => {
|
|
18
23
|
if (val === undefined) return undefined;
|
|
19
|
-
const out =
|
|
24
|
+
const out = numberSchema(val);
|
|
20
25
|
return out instanceof type.errors ? undefined : out;
|
|
21
26
|
});
|
|
22
27
|
|
|
@@ -53,15 +53,19 @@ const ProjectRootNamespaceQuery = `query omp_gitlabDuoWorkflowProjectRootNamespa
|
|
|
53
53
|
}
|
|
54
54
|
}`;
|
|
55
55
|
|
|
56
|
+
// Hoisted: per-element schema construction on a discovery hot path.
|
|
57
|
+
const stringSchema = type("string");
|
|
58
|
+
const unknownArraySchema = type("unknown[]");
|
|
59
|
+
|
|
56
60
|
const resilientString = type("unknown").pipe(value => {
|
|
57
61
|
if (value === undefined) return undefined;
|
|
58
|
-
const parsed =
|
|
62
|
+
const parsed = stringSchema(value);
|
|
59
63
|
return parsed instanceof type.errors ? undefined : parsed;
|
|
60
64
|
});
|
|
61
65
|
|
|
62
66
|
const resilientUnknownArray = type("unknown").pipe(value => {
|
|
63
67
|
if (value === undefined || value === null) return value;
|
|
64
|
-
const parsed =
|
|
68
|
+
const parsed = unknownArraySchema(value);
|
|
65
69
|
return parsed instanceof type.errors ? [] : parsed;
|
|
66
70
|
});
|
|
67
71
|
|
package/src/model-tokenizer.ts
CHANGED
|
@@ -52,10 +52,14 @@ function glmTokenizer(identity: ModelIdentity): ModelTokenizer | undefined {
|
|
|
52
52
|
* This is catalog policy, not a runtime caller heuristic: [`buildModel`](./build.ts)
|
|
53
53
|
* materializes the result as `Model.tokenizer`; consumers read that property.
|
|
54
54
|
*/
|
|
55
|
-
export function resolveModelTokenizer(modelId: string): ModelTokenizer | undefined {
|
|
56
|
-
const
|
|
55
|
+
export function resolveModelTokenizer(modelId: string, provider = ""): ModelTokenizer | undefined {
|
|
56
|
+
const cacheKey = `${provider}\0${modelId}`;
|
|
57
|
+
const cached = modelTokenizerCache.get(cacheKey);
|
|
57
58
|
if (cached !== undefined) return cached ?? undefined;
|
|
58
|
-
|
|
59
|
+
// Provider-scoped identity overrides (e.g. Yolo-Auto's `yolo` alias for
|
|
60
|
+
// Qwen3.8 Flash) only apply when the provider is known; bare ids stay
|
|
61
|
+
// provider-agnostic so unrelated namespaces never inherit them.
|
|
62
|
+
const identity = classifyModel(provider, bareModelId(modelId), { lenient: true });
|
|
59
63
|
const tokenizer =
|
|
60
64
|
claudeTokenizer(identity) ??
|
|
61
65
|
qwenTokenizer(identity) ??
|
|
@@ -63,6 +67,6 @@ export function resolveModelTokenizer(modelId: string): ModelTokenizer | undefin
|
|
|
63
67
|
kimiTokenizer(identity) ??
|
|
64
68
|
glmTokenizer(identity);
|
|
65
69
|
if (modelTokenizerCache.size === MAX_TOKENIZER_CACHE_ENTRIES) modelTokenizerCache.clear();
|
|
66
|
-
modelTokenizerCache.set(
|
|
70
|
+
modelTokenizerCache.set(cacheKey, tokenizer ?? null);
|
|
67
71
|
return tokenizer;
|
|
68
72
|
}
|