@x12i/ai-profiles 4.1.1 → 4.2.1

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.
Files changed (60) hide show
  1. package/README.md +48 -17
  2. package/dist/data/models-catalog.json +2183 -264
  3. package/dist/data/models-profiles.json +8 -63
  4. package/dist/data/models-registry.json +1 -1
  5. package/dist/deriveModelTags.d.ts +18 -0
  6. package/dist/deriveModelTags.d.ts.map +1 -0
  7. package/dist/deriveModelTags.js +148 -0
  8. package/dist/deriveModelTags.js.map +1 -0
  9. package/dist/index.d.ts +11 -4
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +7 -3
  12. package/dist/index.js.map +1 -1
  13. package/dist/isKnownProfileChoice.d.ts +21 -0
  14. package/dist/isKnownProfileChoice.d.ts.map +1 -1
  15. package/dist/isKnownProfileChoice.js +25 -0
  16. package/dist/isKnownProfileChoice.js.map +1 -1
  17. package/dist/listAICatalogModels.d.ts +45 -0
  18. package/dist/listAICatalogModels.d.ts.map +1 -0
  19. package/dist/listAICatalogModels.js +229 -0
  20. package/dist/listAICatalogModels.js.map +1 -0
  21. package/dist/listAIProfiles.d.ts.map +1 -1
  22. package/dist/listAIProfiles.js +8 -4
  23. package/dist/listAIProfiles.js.map +1 -1
  24. package/dist/modelsCatalog.d.ts.map +1 -1
  25. package/dist/modelsCatalog.js +5 -0
  26. package/dist/modelsCatalog.js.map +1 -1
  27. package/dist/profileDiscovery.d.ts +38 -6
  28. package/dist/profileDiscovery.d.ts.map +1 -1
  29. package/dist/profileDiscovery.js +70 -22
  30. package/dist/profileDiscovery.js.map +1 -1
  31. package/dist/profileTags.d.ts +5 -0
  32. package/dist/profileTags.d.ts.map +1 -1
  33. package/dist/profileTags.js +26 -0
  34. package/dist/profileTags.js.map +1 -1
  35. package/dist/resolveAIProfileByTags.d.ts +10 -2
  36. package/dist/resolveAIProfileByTags.d.ts.map +1 -1
  37. package/dist/resolveAIProfileByTags.js +21 -5
  38. package/dist/resolveAIProfileByTags.js.map +1 -1
  39. package/dist/resolveCatalogModelByTags.d.ts +39 -0
  40. package/dist/resolveCatalogModelByTags.d.ts.map +1 -0
  41. package/dist/resolveCatalogModelByTags.js +112 -0
  42. package/dist/resolveCatalogModelByTags.js.map +1 -0
  43. package/dist/resolveInput.d.ts +29 -0
  44. package/dist/resolveInput.d.ts.map +1 -0
  45. package/dist/resolveInput.js +41 -0
  46. package/dist/resolveInput.js.map +1 -0
  47. package/dist/resolveModelIdentity.d.ts +4 -0
  48. package/dist/resolveModelIdentity.d.ts.map +1 -1
  49. package/dist/resolveModelIdentity.js +8 -0
  50. package/dist/resolveModelIdentity.js.map +1 -1
  51. package/dist/types.d.ts +11 -1
  52. package/dist/types.d.ts.map +1 -1
  53. package/dist/validation/validateModelsCatalog.d.ts.map +1 -1
  54. package/dist/validation/validateModelsCatalog.js +11 -0
  55. package/dist/validation/validateModelsCatalog.js.map +1 -1
  56. package/dist/validation/validateModelsProfilesJson.d.ts +5 -1
  57. package/dist/validation/validateModelsProfilesJson.d.ts.map +1 -1
  58. package/dist/validation/validateModelsProfilesJson.js +6 -13
  59. package/dist/validation/validateModelsProfilesJson.js.map +1 -1
  60. package/package.json +1 -1
package/README.md CHANGED
@@ -47,7 +47,7 @@ Graph JSON, studio defaults, and run overrides store **profile slot strings only
47
47
  | `skillModel` | `balanced/default` | MAIN (skill gateway) |
48
48
  | `postActionModel` | `cheap/default` | POST (xynthesis / wrap-up) |
49
49
 
50
- Canonical storage form is **`profile/choice`** (separator `/`). Use **`formatProfileChoiceKey(profile, choice)`** when writing slots and **`isKnownProfileChoice(input)`** (or **`matchStrictProfileInput`**) before persist or execute. Always include the choice segment — even for the profile default (`cheap/default`, not bare `cheap`).
50
+ Canonical storage form is **`profile/choice`** (separator `/`). Use **`formatProfileChoiceKey(profile, choice)`** when writing slots and **`isKnownProfileInput(input)`** (or **`isKnownProfileChoice`** / **`matchStrictProfileInput`**) before persist or execute. Always include the choice segment — even for the profile default (`cheap/default`, not bare `cheap`).
51
51
 
52
52
  ```json
53
53
  "modelConfig": {
@@ -249,10 +249,13 @@ npm install @x12i/ai-profiles
249
249
  | `parseProfileChoiceKey(input)` | Yes | Parse `profile/choice` (accepts `\` as separator on input) |
250
250
  | `matchStrictProfileInput(registry, input)` | Yes | Resolve input to profile+choice or `invalidChoice` |
251
251
  | `isKnownProfileChoice(input)` | Yes (bundled only) | Whether input is a known valid `profile/choice` key |
252
+ | `isKnownProfileInput(input)` | Yes (bundled only) | Profile-intent gate: known `profile/choice` → `true`; concrete model ids → `false` |
253
+ | `isKnownProfileOrShortcut(input)` | Yes (bundled only) | Alias of `isKnownProfileInput` (shortcuts are not supported) |
252
254
  | `isKnownProfileChoiceInput(registry, input)` | Yes | Same as above with an explicit registry |
253
255
  | `getBundledRegistry()` | Yes | Sync bundled profiles registry (no network) |
254
256
  | `resolveBundledAIProfile(input, options)` | Yes | Strict `profile/choice` → model, pricing, runtime (`source: "bundled"`) |
255
- | `resolveBundledInput(input, options)` | Yes | Unified resolver: `profile/choice` → registry; vendor slug / gateway wire / `{ provider, modelId }` → catalog |
257
+ | `resolveBundledInput(input, options)` | Yes | Unified sync resolver: `profile/choice` → registry; vendor slug / gateway wire / `{ provider, modelId }` → catalog |
258
+ | `resolveInput(input, options)` | No (async) | Unified async resolver (same order as `resolveBundledInput`; `source: "auto" \| "remote" \| "bundled"`) |
256
259
  | `resolveBundledCatalogModel(input, options?)` | Yes | Sync strict catalog resolution (exact/high confidence only) |
257
260
  | `ensureConcreteModel(input, options?)` | Yes | String normalizer: `profile/choice` (strict) or concrete model (catalog best-effort) → invoke-time `{ provider, modelId }` |
258
261
  | `normalizeInvokeModel(input, options?)` | Yes | Idempotent gateway invoke tuple: strips `openrouter/` display slugs; rejects unresolved `profile/choice` aliases |
@@ -271,8 +274,10 @@ npm install @x12i/ai-profiles
271
274
  | `resolveSecondaryModel(input, options?)` | No (async) | Resolve primary choice + optional companion `secondary` from profile `secondary` refs |
272
275
  | `getSecondaryForResolvedProfile(resolved, registry, catalog?)` | Yes | Secondary for an already-resolved primary (no extra I/O when catalog is passed) |
273
276
  | `listProfileSecondaries(profileKey, registry)` | Yes | List per-choice `secondary` declarations on a profile |
274
- | `resolveAIProfileByTags(tags, options?)` | No (async) | Pick best profile by tag overlap (partial match OK), then resolve |
275
- | `scoreProfileTags` / `rankProfilesByTags` | Yes | Tag overlap scoring for discovery and custom UIs |
277
+ | `resolveAIProfileByTags(tags, options?)` | No (async) | Pick best profile by weighted tags (`tagMatch`, excludes discovery by default), then resolve |
278
+ | `listAICatalogModels(options?)` | No (async) | Filter/rank catalog models by tags, provider, status, modalities, tools/reasoning; attaches intent `profileChoices` |
279
+ | `resolveCatalogModelByTags(tags, options?)` | No (async) | Best catalog model by weighted tags (+ optional profile branch map) |
280
+ | `scoreProfileTags` / `rankProfilesByTags` | Yes | Weighted tag scoring for discovery and custom UIs |
276
281
  | `AIProfilesError` / `AIProfilesErrorCode` | — | Typed errors (`UNKNOWN_PROFILE`, `INVOKE_MODEL_ALIAS_AT_GATEWAY`, `INVOKE_MODEL_SHAPE_INVALID`, …) |
277
282
 
278
283
  ### Model identity (vendor + model strings)
@@ -282,7 +287,7 @@ Use these when callers pass **raw model ids or OpenRouter slugs**, not profile n
282
287
  | Export | Sync? | Purpose |
283
288
  |--------|-------|---------|
284
289
  | `resolveCatalogModel(input, options?)` | No (async) | Resolve a model SKU or OpenRouter slug via `models-catalog.json` (strict; no profile intent) |
285
- | `resolveModelIdentity(input, options?)` | Yes | Given a bare id or `vendor/slug`, return `{ provider, modelId, invocation? }` via bundled registry + catalog fallback |
290
+ | `resolveModelIdentity(input, options?)` | Yes | Given a bare id or `vendor/slug`, return `{ provider, modelId, invocation? }` via bundled registry + catalog fallback. Returns `null` for known `profile/choice` keys. |
286
291
  | `buildModelInvocation(provider, modelId)` | Yes | `{ direct, openrouter }` endpoints for a provider hint + model string |
287
292
  | `buildDirectInvocation(provider, modelId)` | Yes | Vendor-direct `{ provider, modelId }`; splits prefixed slugs for all vendors |
288
293
  | `buildOpenRouterModelId(provider, modelId)` | Yes | Build an OpenRouter slug (`openai/gpt-5.4`) from registry fields |
@@ -327,7 +332,7 @@ For consumers that need **no network** and a single entry point:
327
332
  ```ts
328
333
  import {
329
334
  ensureConcreteModel,
330
- isKnownProfileChoice,
335
+ isKnownProfileInput,
331
336
  isReasoningModel,
332
337
  resolveBundledInput,
333
338
  resolveBundledAIProfile,
@@ -352,8 +357,9 @@ ensureConcreteModel("google/gemini-2.5-flash-lite");
352
357
  ensureConcreteModel("openrouter/openai/gpt-5.5");
353
358
  // → { provider: "openrouter", modelId: "openai/gpt-5.5" }
354
359
 
355
- if (isKnownProfileChoice("cheap/default")) {
360
+ if (isKnownProfileInput("cheap/default")) {
356
361
  const resolved = resolveBundledInput("cheap/default", { catalogLane: "text" });
362
+ // resolved.provider is never "cheap"
357
363
  }
358
364
 
359
365
  const wire = normalizeGatewayWireId("openrouter/openai/gpt-5.5");
@@ -376,7 +382,16 @@ const routed = applyModelRouting(
376
382
  );
377
383
  ```
378
384
 
379
- `isKnownProfileChoice` returns **false** for bare profiles (`cheap`), legacy shortcuts (`cheapest`), and concrete slugs (`google/gemini-2.5-flash-lite`).
385
+ For the same profile-first order with optional remote registry/catalog (`source: "auto"` / `"remote"` / `"bundled"`), use **`resolveInput`**:
386
+
387
+ ```ts
388
+ import { resolveInput } from "@x12i/ai-profiles";
389
+
390
+ await resolveInput("cheap/default", { catalogLane: "text", source: "auto" });
391
+ await resolveInput("google/gemma-3-4b-it", { source: "bundled" });
392
+ ```
393
+
394
+ `isKnownProfileInput` / `isKnownProfileChoice` return **false** for bare profiles (`cheap`), legacy shortcuts (`cheapest`), and concrete slugs (`google/gemini-2.5-flash-lite`).
380
395
 
381
396
  ## Basic usage
382
397
 
@@ -384,9 +399,10 @@ const routed = applyModelRouting(
384
399
  import {
385
400
  listAIProfiles,
386
401
  listAIProfileChoices,
387
- isKnownProfileChoice,
402
+ isKnownProfileInput,
388
403
  resolveAIProfile,
389
404
  resolveCatalogModel,
405
+ resolveInput,
390
406
  } from "@x12i/ai-profiles";
391
407
 
392
408
  const profiles = await listAIProfiles();
@@ -394,6 +410,7 @@ const choices = await listAIProfileChoices();
394
410
 
395
411
  // Strict lookup key: profile/choice
396
412
  const model = await resolveAIProfile("cheap/default", { catalogLane: "text" });
413
+ // Or unified async: await resolveInput("cheap/default", { catalogLane: "text" });
397
414
 
398
415
  console.log(model.profile); // canonical primary key: cheap
399
416
  console.log(model.provider); // "openrouter" by default for remote profiles
@@ -412,8 +429,8 @@ const sku = await resolveCatalogModel("google/gemini-2.5-flash-lite", {
412
429
  preferOpenRouter: false,
413
430
  });
414
431
 
415
- console.log(isKnownProfileChoice("cheap/default")); // true
416
- console.log(isKnownProfileChoice("cheap")); // false
432
+ console.log(isKnownProfileInput("cheap/default")); // true
433
+ console.log(isKnownProfileInput("cheap")); // false
417
434
  ```
418
435
 
419
436
  ## Registry statistics
@@ -455,7 +472,7 @@ Resolution accepts exactly **four** input forms:
455
472
 
456
473
  **Not supported:** shortcuts (`cheapest`), bare profiles (`cheap`), aliases (`cost-sensitive`). Use discovery to pick a profile, then resolve an explicit `profile/choice`.
457
474
 
458
- **Tags exist to filter.** Short codes on profiles (`cyb`, `eco`, `cod`, `oai`, …). Get the tag list, then filter profiles with those tags:
475
+ **Tags exist to filter.** Short codes on profiles and catalog models (`cyb`, `eco`, `cod`, `oai`, …). Get the tag list, then filter:
459
476
 
460
477
  ```ts
461
478
  import {
@@ -463,6 +480,8 @@ import {
463
480
  listUsedAIProfileTags, // tags present in the loaded registry
464
481
  listAIProfiles, // filter profiles by tags
465
482
  resolveAIProfileByTags, // pick best matching profile → resolve it
483
+ listAICatalogModels, // filter catalog models by tags
484
+ resolveCatalogModelByTags, // pick best matching model (+ optional profile branch)
466
485
  } from "@x12i/ai-profiles";
467
486
 
468
487
  listAIProfileTags(); // glossary for pickers
@@ -474,9 +493,21 @@ await listAIProfiles({ tags: ["cyb", "sec"], tagMatch: "all" }); // all of
474
493
 
475
494
  // Filter + resolve in one step
476
495
  await resolveAIProfileByTags(["cyb", "eco", "ds"], { catalogLane: "text" });
496
+
497
+ // Catalog models (tags are derived + inherited during registry:finish)
498
+ await listAICatalogModels({
499
+ tags: ["cod", "eco"],
500
+ tagMatch: "all",
501
+ provider: "google",
502
+ status: "active",
503
+ supportsTools: true,
504
+ });
505
+ await resolveCatalogModelByTags(["cod", "eco"], { catalogLane: "text" });
477
506
  ```
478
507
 
479
- `tagMatch: "any"` (default) = match if any requested tag is present. `"all"` = every requested tag must be present. Results are ranked by how many requested tags match.
508
+ `tagMatch: "any"` (default) = match if any requested tag is present. `"all"` = every requested tag must be present. Results are ranked by **weighted** tag score (domain/task 3, capability 2, tier/vendor 1, `wf` 0.25), then coverage ratio, then fewer tags.
509
+
510
+ **Discovery exclusion:** by default, tag list/resolve pools skip `vendorFamily` and `category: "discovery"` profiles (`catalog`, `claude`, `grok`, …). Pass `includeDiscovery: true`, or include the `vnd` tag, to include them.
480
511
 
481
512
  Input is normalized (trimmed, lowercased) before matching. Composite keys accept **`/` or `\`** as the separator (`cyber\default` → `cyber/default`). **Profile and choice segment names treat `-` and `_` as equivalent** (`cheap/google-floor` → `cheap/google_floor`).
482
513
 
@@ -563,12 +594,12 @@ resolveAndNormalizeBundledInvokeModel("cyber/deep_forensics", {
563
594
 
564
595
  ## Sync validation (bundled registry)
565
596
 
566
- Use **`isKnownProfileChoice(input)`** before run/execute or on first paint when the remote registry may not be loaded yet. It reads the **bundled** JSON only (no network, no in-memory cache race).
597
+ Use **`isKnownProfileInput(input)`** (or **`isKnownProfileChoice`**) before run/execute or on first paint when the remote registry may not be loaded yet. It reads the **bundled** JSON only (no network, no in-memory cache race).
567
598
 
568
599
  ```ts
569
600
  const canRun =
570
- isKnownProfileChoice(aiProfileKey) &&
571
- isKnownProfileChoice(fallbackKey);
601
+ isKnownProfileInput(aiProfileKey) &&
602
+ isKnownProfileInput(fallbackKey);
572
603
  ```
573
604
 
574
605
  Accepts **valid `profile/choice` keys only**. Unknown strings return `false`.
@@ -1014,7 +1045,7 @@ Workflow YAML is in [`docs/github-workflows/`](docs/github-workflows/). Copy int
1014
1045
  Consumers install with:
1015
1046
 
1016
1047
  ```bash
1017
- npm install @x12i/ai-profiles@^4.1.0
1048
+ npm install @x12i/ai-profiles@^4.2.0
1018
1049
  ```
1019
1050
 
1020
1051
  Peer dependency for downstream repos (graphs-studio, graph-engine, ai-tasks):