@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.
@@ -201,28 +201,60 @@ function ranksInClass(classId: string, model: string, lenient: boolean): Omit<Cl
201
201
  return out;
202
202
  }
203
203
 
204
- function findIdentityOverride(
205
- provider: string,
206
- bareModel: string,
207
- observedAtMs: number | undefined,
208
- ): CompiledIdentityOverride | undefined {
209
- const lowerModel = bareModel.toLowerCase();
210
- const lowerProvider = provider.toLowerCase();
211
- let agnostic: CompiledIdentityOverride | undefined;
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
- if (override.model.toLowerCase() !== lowerModel) continue;
215
- if (override.expiresAtMs !== undefined && observedAtMs !== undefined && observedAtMs >= override.expiresAtMs) {
216
- continue;
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
- if (override.provider.toLowerCase() === lowerProvider) return override;
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
- return agnostic;
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);
@@ -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;
@@ -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 = type("string")(val);
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 = type("number")(val);
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 = type("string")(value);
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 = type("unknown[]")(value);
68
+ const parsed = unknownArraySchema(value);
65
69
  return parsed instanceof type.errors ? [] : parsed;
66
70
  });
67
71
 
@@ -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 cached = modelTokenizerCache.get(modelId);
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
- const identity = classifyModel("", bareModelId(modelId), { lenient: true });
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(modelId, tokenizer ?? null);
70
+ modelTokenizerCache.set(cacheKey, tokenizer ?? null);
67
71
  return tokenizer;
68
72
  }