@linxiraos/pi-catalog 1.1.8 → 1.1.10

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.
@@ -6224,7 +6224,46 @@
6224
6224
  }
6225
6225
  },
6226
6226
  {
6227
- "source": "classes/openai.kdl:36",
6227
+ "source": "classes/openai.kdl:37",
6228
+ "class": "openai",
6229
+ "revision": [
6230
+ {
6231
+ "op": "=",
6232
+ "revision": "5.6.0"
6233
+ }
6234
+ ],
6235
+ "catalog": {
6236
+ "delegationBias": "gated"
6237
+ }
6238
+ },
6239
+ {
6240
+ "source": "classes/openai.kdl:40",
6241
+ "class": "openai",
6242
+ "revision": [
6243
+ {
6244
+ "op": ">=",
6245
+ "revision": "6.0.0"
6246
+ }
6247
+ ],
6248
+ "catalog": {
6249
+ "delegationBias": "restrained"
6250
+ }
6251
+ },
6252
+ {
6253
+ "source": "classes/openai.kdl:46",
6254
+ "class": "openai",
6255
+ "models": [
6256
+ {
6257
+ "kind": "exact",
6258
+ "value": "gpt-6-astra"
6259
+ }
6260
+ ],
6261
+ "wire": {
6262
+ "supportsConfigurationUpdate": true
6263
+ }
6264
+ },
6265
+ {
6266
+ "source": "classes/openai.kdl:52",
6228
6267
  "class": "openai",
6229
6268
  "family": "codex",
6230
6269
  "revision": [
@@ -10650,7 +10689,7 @@
10650
10689
  },
10651
10690
  {
10652
10691
  "op": "<",
10653
- "revision": "6.0.0"
10692
+ "revision": "7.0.0"
10654
10693
  }
10655
10694
  ],
10656
10695
  "catalog": {
@@ -10955,7 +10994,7 @@
10955
10994
  },
10956
10995
  {
10957
10996
  "op": "<",
10958
- "revision": "6.0.0"
10997
+ "revision": "7.0.0"
10959
10998
  }
10960
10999
  ],
10961
11000
  "catalog": {
@@ -14919,7 +14958,7 @@
14919
14958
  "authorizeParams": {
14920
14959
  "id_token_add_organizations": "true",
14921
14960
  "codex_cli_simplified_flow": "true",
14922
- "originator": "pi"
14961
+ "originator": "omp"
14923
14962
  },
14924
14963
  "clientId": {
14925
14964
  "value": "app_EMoamEEZ73f0CkXaXp7hrann"
@@ -14934,6 +14973,7 @@
14934
14973
  "hostname": "localhost",
14935
14974
  "portFallback": false,
14936
14975
  "manualOnly": false,
14976
+ "nativeScheme": false,
14937
14977
  "redirectUri": {
14938
14978
  "value": "http://localhost:1455/auth/callback"
14939
14979
  }
@@ -15031,7 +15071,8 @@
15031
15071
  "path": "/callback",
15032
15072
  "hostname": "localhost",
15033
15073
  "portFallback": true,
15034
- "manualOnly": false
15074
+ "manualOnly": false,
15075
+ "nativeScheme": false
15035
15076
  },
15036
15077
  "token": {
15037
15078
  "url": {
@@ -15153,16 +15194,20 @@
15153
15194
  ]
15154
15195
  },
15155
15196
  "callback": {
15156
- "port": 9999,
15197
+ "port": 0,
15157
15198
  "path": "/callback",
15158
- "hostname": "127.0.0.1",
15159
- "portFallback": false,
15160
- "manualOnly": false,
15199
+ "hostname": "localhost",
15200
+ "portFallback": true,
15201
+ "manualOnly": true,
15202
+ "nativeScheme": true,
15161
15203
  "redirectUri": {
15162
- "value": "http://127.0.0.1:9999/callback"
15204
+ "value": "zcode://zai-auth/callback",
15205
+ "env": [
15206
+ "ZAI_OAUTH_REDIRECT_URI"
15207
+ ]
15163
15208
  }
15164
15209
  },
15165
- "instructions": "Complete Z.ai login in your browser. If the browser cannot reach this machine, paste the final redirect URL or authorization code when prompted.",
15210
+ "instructions": "Complete Z.ai login in your browser. On supported local desktops, omp captures the zcode:// callback and restores its previous handler automatically. You can also paste the final redirect URL or authorization code when prompted.",
15166
15211
  "token": {
15167
15212
  "url": {
15168
15213
  "value": "https://zcode.z.ai/api/v1/oauth/token",
@@ -15203,7 +15248,7 @@
15203
15248
  "refresh": {
15204
15249
  "kind": "none"
15205
15250
  },
15206
- "callbackPort": 9999,
15251
+ "callbackPort": 0,
15207
15252
  "pasteCode": true
15208
15253
  },
15209
15254
  {
@@ -15330,7 +15375,8 @@
15330
15375
  "path": "/callback",
15331
15376
  "hostname": "localhost",
15332
15377
  "portFallback": true,
15333
- "manualOnly": false
15378
+ "manualOnly": false,
15379
+ "nativeScheme": false
15334
15380
  },
15335
15381
  "token": {
15336
15382
  "url": {
@@ -15417,7 +15463,8 @@
15417
15463
  "path": "/callback",
15418
15464
  "hostname": "127.0.0.1",
15419
15465
  "portFallback": true,
15420
- "manualOnly": false
15466
+ "manualOnly": false,
15467
+ "nativeScheme": false
15421
15468
  },
15422
15469
  "token": {
15423
15470
  "url": {
@@ -15497,7 +15544,8 @@
15497
15544
  "path": "/oauth-callback",
15498
15545
  "hostname": "127.0.0.1",
15499
15546
  "portFallback": true,
15500
- "manualOnly": false
15547
+ "manualOnly": false,
15548
+ "nativeScheme": false
15501
15549
  },
15502
15550
  "token": {
15503
15551
  "url": {
@@ -15595,7 +15643,8 @@
15595
15643
  "path": "/oauth2callback",
15596
15644
  "hostname": "127.0.0.1",
15597
15645
  "portFallback": true,
15598
- "manualOnly": false
15646
+ "manualOnly": false,
15647
+ "nativeScheme": false
15599
15648
  },
15600
15649
  "token": {
15601
15650
  "url": {
@@ -15861,6 +15910,7 @@
15861
15910
  "hostname": "localhost",
15862
15911
  "portFallback": true,
15863
15912
  "manualOnly": false,
15913
+ "nativeScheme": false,
15864
15914
  "redirectUri": {
15865
15915
  "env": [
15866
15916
  "GITLAB_REDIRECT_URI"
@@ -15955,6 +16005,7 @@
15955
16005
  "hostname": "localhost",
15956
16006
  "portFallback": true,
15957
16007
  "manualOnly": true,
16008
+ "nativeScheme": false,
15958
16009
  "redirectUri": {
15959
16010
  "value": "vscode://gitlab.gitlab-workflow/authentication"
15960
16011
  }
@@ -422,7 +422,7 @@ export interface CompiledOAuthRequest {
422
422
  timeoutMs?: number;
423
423
  }
424
424
 
425
- /** Loopback callback configuration for authorization-code logins. */
425
+ /** Callback transport configuration for authorization-code logins. */
426
426
  export interface CompiledCallback {
427
427
  port: number;
428
428
  path: string;
@@ -430,9 +430,11 @@ export interface CompiledCallback {
430
430
  redirectUri?: CompiledAuthValue;
431
431
  portFallback: boolean;
432
432
  manualOnly: boolean;
433
+ /** Temporarily receive a custom-scheme redirect through the native OS handler. */
434
+ nativeScheme: boolean;
433
435
  }
434
436
 
435
- /** Authorization-code login through the loopback callback server. */
437
+ /** Authorization-code login through a loopback, native-scheme, or manual callback. */
436
438
  export interface CompiledOAuthCodeLogin {
437
439
  kind: "oauth-code";
438
440
  clientId?: CompiledAuthValue;
@@ -110,6 +110,13 @@ export interface CodexModelDiscoveryOptions {
110
110
  export interface CodexModelDiscoveryResult {
111
111
  models: ModelSpec<"openai-codex-responses">[];
112
112
  etag?: string;
113
+ /**
114
+ * Set when the backend rejected the credential itself (401/403, e.g.
115
+ * `token_revoked`); `models` is empty. A definitive per-account denial,
116
+ * unlike the `null` result for transport/parse failures, so multi-account
117
+ * discovery can skip the account instead of aborting.
118
+ */
119
+ rejectedStatus?: 401 | 403;
113
120
  }
114
121
 
115
122
  /**
@@ -139,6 +146,9 @@ export async function fetchCodexModels(options: CodexModelDiscoveryOptions): Pro
139
146
  continue;
140
147
  }
141
148
 
149
+ if (response.status === 401 || response.status === 403) {
150
+ return { models: [], rejectedStatus: response.status };
151
+ }
142
152
  if (!response.ok) {
143
153
  continue;
144
154
  }
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Catalog metric lookup (intelligence / speed scores) across provider id
3
+ * dialects. The shared catalog scores a model once under its vendor id
4
+ * (`claude-fable-5-1`); hosts re-spell it (`anthropic/claude-fable-5.1`,
5
+ * `global.anthropic.claude-fable-5-1`, `claude-fable-5-1-high`, `k3`), so an
6
+ * index keyed by exact id leaves most of the picker blank.
7
+ *
8
+ * Resolution order, first hit wins:
9
+ * 1. exact lowercased id;
10
+ * 2. candidate ids derived from the wire id (namespace and dotted vendor/region
11
+ * prefixes, bracket affixes, date/`vN:0` suffixes, declared markers, the
12
+ * taxonomy logical id) compared on a dot/colon-folded bare name, accepted
13
+ * only when the classified identities agree.
14
+ *
15
+ * Identity alone (class/family/revision) is deliberately not a key: it
16
+ * conflates `gpt-5.5` with `gpt-5.5-pro` and every Llama with the one that
17
+ * happens to be scored.
18
+ */
19
+ import type { ModelIdentity } from "../compat/types";
20
+ import type { Api, Model } from "../types";
21
+ import { bareModelId } from "./id";
22
+ import { getReferenceCandidateIds } from "./reference";
23
+
24
+ /** Catalog-delivered intelligence score and output speed for one model. */
25
+ export interface CatalogMetrics {
26
+ int?: number;
27
+ tps?: number;
28
+ }
29
+
30
+ interface ScoredEntry extends CatalogMetrics {
31
+ identity: ModelIdentity;
32
+ }
33
+
34
+ /** Trailing tokens Bedrock-style ids append without changing the model: `-v1:0`, `-v2`, `:0`, `-20251001`, `-2026-04-23`. */
35
+ const STRIPPABLE_SUFFIX_PATTERN = /(?:-v\d+(?::\d+)?|:\d+|-\d{2,})$/;
36
+ /** Leading alphabetic dotted namespaces: `global.anthropic.`, `us-gov.`, `openai.`. */
37
+ const DOTTED_PREFIX_PATTERN = /^[a-z][a-z-]*\./;
38
+
39
+ function canonicalKey(id: string): string {
40
+ return bareModelId(id).toLowerCase().replace(/[.:]/g, "-");
41
+ }
42
+
43
+ /** Whether two classified identities may describe the same scored model. Unset ranks on either side are not evidence. */
44
+ function identitiesAgree(left: ModelIdentity, right: ModelIdentity): boolean {
45
+ if (left.class !== right.class || left.class === "unknown") return false;
46
+ if (left.family !== undefined && right.family !== undefined && left.family !== right.family) return false;
47
+ if (left.revision !== undefined && right.revision !== undefined && left.revision !== right.revision) return false;
48
+ if (left.effort !== undefined && right.effort !== undefined && left.effort !== right.effort) return false;
49
+ return (left.thinkingVariant ?? false) === (right.thinkingVariant ?? false);
50
+ }
51
+
52
+ /** The catalog metrics a model carries, or undefined when it reports none. A zero speed is "unmeasured", not a score. */
53
+ export function catalogMetricsOf(model: Model<Api>): CatalogMetrics | undefined {
54
+ const int = model.int != null && Number.isFinite(model.int) ? model.int : undefined;
55
+ const tps = model.tps != null && Number.isFinite(model.tps) && model.tps > 0 ? model.tps : undefined;
56
+ if (int === undefined && tps === undefined) return undefined;
57
+ return { ...(int !== undefined ? { int } : {}), ...(tps !== undefined ? { tps } : {}) };
58
+ }
59
+
60
+ // Wire ids form a bounded set (bundled + discovered), so no eviction is needed.
61
+ const candidateCache = new Map<string, string[]>();
62
+
63
+ /** Candidate ids for `modelId`, least-stripped first, each already canonical-keyed. */
64
+ function metricCandidateKeys(modelId: string): string[] {
65
+ const cached = candidateCache.get(modelId);
66
+ if (cached) return cached;
67
+ const keys: string[] = [];
68
+ const seen = new Set<string>();
69
+ const queue = getReferenceCandidateIds(modelId);
70
+ for (let index = 0; index < queue.length; index++) {
71
+ const candidate = queue[index].toLowerCase();
72
+ const key = canonicalKey(candidate);
73
+ if (!seen.has(key)) {
74
+ seen.add(key);
75
+ keys.push(key);
76
+ }
77
+ const bare = bareModelId(candidate);
78
+ const withoutPrefix = bare.replace(DOTTED_PREFIX_PATTERN, "");
79
+ if (withoutPrefix !== bare && withoutPrefix.length > 0) queue.push(withoutPrefix);
80
+ const withoutSuffix = bare.replace(STRIPPABLE_SUFFIX_PATTERN, "");
81
+ if (withoutSuffix !== bare && withoutSuffix.length > 0) queue.push(withoutSuffix);
82
+ }
83
+ candidateCache.set(modelId, keys);
84
+ return keys;
85
+ }
86
+
87
+ /**
88
+ * Index of catalog metrics over every scored model seen so far. Built once per
89
+ * discovery cycle by the model registry and per provider by the model manager;
90
+ * `add` accumulates across providers so a proxy id resolves against any host's
91
+ * scored row.
92
+ */
93
+ export class CatalogMetricsIndex {
94
+ #exact = new Map<string, CatalogMetrics>();
95
+ #canonical = new Map<string, ScoredEntry>();
96
+
97
+ constructor(models?: Iterable<Model<Api>>) {
98
+ if (models) this.add(models);
99
+ }
100
+
101
+ get isEmpty(): boolean {
102
+ return this.#exact.size === 0;
103
+ }
104
+
105
+ /** Record the metrics of every scored model; later rows fill fields earlier rows left unset. */
106
+ add(models: Iterable<Model<Api>>): void {
107
+ for (const model of models) {
108
+ const metrics = catalogMetricsOf(model);
109
+ if (!metrics) continue;
110
+ const exactKey = model.id.toLowerCase();
111
+ const existing = this.#exact.get(exactKey);
112
+ this.#exact.set(exactKey, existing ? { ...metrics, ...existing } : metrics);
113
+
114
+ const canonical = canonicalKey(model.id);
115
+ const scored = this.#canonical.get(canonical);
116
+ if (!scored) this.#canonical.set(canonical, { ...metrics, identity: model.identity });
117
+ else if (scored.int === undefined || scored.tps === undefined) {
118
+ this.#canonical.set(canonical, { ...metrics, ...scored });
119
+ }
120
+ }
121
+ }
122
+
123
+ /** Metrics for `model` by exact id, else by dialect-normalized id when the classified identities agree. */
124
+ resolve(model: Model<Api>): CatalogMetrics | undefined {
125
+ const exact = this.#exact.get(model.id.toLowerCase());
126
+ if (exact) return exact;
127
+ const identity = model.identity;
128
+ const ids = identity.logicalId ? [model.id, identity.logicalId] : [model.id];
129
+ for (const id of ids) {
130
+ for (const key of metricCandidateKeys(id)) {
131
+ const scored = this.#canonical.get(key);
132
+ if (scored && identitiesAgree(identity, scored.identity)) return scored;
133
+ }
134
+ }
135
+ return undefined;
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Fill each model's `int`/`tps` from `index`. Returns the input array when no
141
+ * model changed so callers can keep identity-based caches.
142
+ */
143
+ export function applyCatalogMetrics<TApi extends Api>(
144
+ models: Model<TApi>[],
145
+ index: CatalogMetricsIndex,
146
+ ): Model<TApi>[] {
147
+ if (index.isEmpty) return models;
148
+ let changed: Model<TApi>[] | undefined;
149
+ for (let position = 0; position < models.length; position++) {
150
+ const model = models[position];
151
+ if (model.int != null && model.tps != null) continue;
152
+ const metrics = index.resolve(model);
153
+ if (!metrics) continue;
154
+ const int = metrics.int ?? model.int;
155
+ const tps = metrics.tps ?? model.tps;
156
+ if (int === model.int && tps === model.tps) continue;
157
+ changed ??= [...models];
158
+ changed[position] = {
159
+ ...model,
160
+ ...(int != null ? { int } : {}),
161
+ ...(tps != null ? { tps } : {}),
162
+ };
163
+ }
164
+ return changed ?? models;
165
+ }
@@ -119,7 +119,13 @@ function stripReferenceTrailingMarker(candidate: string): string | undefined {
119
119
  return suffixLength > 0 && suffixLength < candidate.length ? candidate.slice(0, -suffixLength) : undefined;
120
120
  }
121
121
 
122
- function getReferenceCandidateIds(modelId: string): string[] {
122
+ /**
123
+ * Expand a proxied/affixed model id into the ids it may be catalogued under,
124
+ * least-stripped first: bracket affixes, model-like segments, `:cloud`,
125
+ * namespace prefix, `:`→`-`, lowercase, and declared trailing markers.
126
+ * Shared by reference recovery and catalog-metric matching (`./metrics`).
127
+ */
128
+ export function getReferenceCandidateIds(modelId: string): string[] {
123
129
  const candidates = new Set<string>();
124
130
  const queue = [modelId];
125
131
  for (let index = 0; index < queue.length; index += 1) {
@@ -1,5 +1,6 @@
1
1
  import { buildModel } from "./build";
2
2
  import { collapseBuiltVariants } from "./compat/collapse";
3
+ import { applyCatalogMetrics, CatalogMetricsIndex } from "./identity/metrics";
3
4
  import { readModelCache, writeModelCache } from "./model-cache";
4
5
  import { type GeneratedProvider, getBundledModels } from "./models";
5
6
  import type { Api, Model, ModelCost, ModelSpec, Provider, TokenCost } from "./types";
@@ -257,7 +258,7 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
257
258
  ? restoredCache.models.filter(model => !additiveStaticModelIds.has(model.id))
258
259
  : restoredCache.models;
259
260
  const cachedModels = additiveStaticModelIds
260
- ? mergeDynamicModels(staticModels, cacheContribution)
261
+ ? mergeCatalogMetrics(mergeDynamicModels(staticModels, cacheContribution), restoredCache.models)
261
262
  : restoredCache.models;
262
263
  const source: ModelResolutionSource = cacheContribution.length > 0 ? "cache" : "bundled";
263
264
  return {
@@ -312,7 +313,11 @@ export async function resolveProviderModels<TApi extends Api = Api, TModelsDevPa
312
313
  : modelsDevFetchSucceeded;
313
314
  const mergedWithCache = mergeDynamicModels(staticModels, cacheModels);
314
315
  const mergedWithModelsDev = mergeDynamicModels(mergedWithCache, modelsDevModels);
315
- const mergedModels = mergeDynamicModels(mergedWithModelsDev, dynamicModels);
316
+ const catalogMetricsSource = modelsDevFetchSucceeded ? normalizedModelsDevModels : preparedCacheModels;
317
+ const mergedWithCatalogMetrics = additiveStaticModelIds
318
+ ? mergeCatalogMetrics(mergedWithModelsDev, catalogMetricsSource)
319
+ : mergedWithModelsDev;
320
+ const mergedModels = mergeDynamicModels(mergedWithCatalogMetrics, dynamicModels);
316
321
  const models = collapseBuiltVariants(
317
322
  authoritativeDynamicFetchSucceeded ? retainModelIds(mergedModels, dynamicModels) : mergedModels,
318
323
  );
@@ -467,6 +472,14 @@ function prepareCacheModelsForStaticMismatch<TApi extends Api>(
467
472
  return sanitizedModels;
468
473
  }
469
474
 
475
+ function mergeCatalogMetrics<TApi extends Api>(
476
+ models: Model<TApi>[],
477
+ catalogModels: readonly Model<TApi>[],
478
+ ): Model<TApi>[] {
479
+ if (models.length === 0 || catalogModels.length === 0) return models;
480
+ return applyCatalogMetrics(models, new CatalogMetricsIndex(catalogModels));
481
+ }
482
+
470
483
  function mergeDynamicModels<TApi extends Api>(
471
484
  baseModels: readonly Model<TApi>[],
472
485
  dynamicModels: readonly Model<TApi>[],