@x12i/ai-tools 1.0.4 → 2.0.0

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 (153) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +146 -204
  3. package/dist/AiModelsCatalogClient-B5FMI9gj.d.cts +58 -0
  4. package/dist/AiModelsCatalogClient-CPPNI6Ry.d.ts +58 -0
  5. package/dist/aliases/index.cjs +4 -3
  6. package/dist/aliases/index.cjs.map +1 -1
  7. package/dist/aliases/index.d.cts +3 -5
  8. package/dist/aliases/index.d.ts +3 -5
  9. package/dist/aliases/index.js +2 -2
  10. package/dist/catalog/index.cjs +18 -9
  11. package/dist/catalog/index.cjs.map +1 -1
  12. package/dist/catalog/index.d.cts +13 -100
  13. package/dist/catalog/index.d.ts +13 -100
  14. package/dist/catalog/index.js +31 -23
  15. package/dist/{chunk-AJEKEWWB.js → chunk-2PTCWPHV.js} +17 -3
  16. package/dist/chunk-2PTCWPHV.js.map +1 -0
  17. package/dist/chunk-56R4XA2S.js +1 -0
  18. package/dist/chunk-5GUKLOEK.cjs +1 -0
  19. package/dist/chunk-5GUKLOEK.cjs.map +1 -0
  20. package/dist/chunk-5XAAMBDO.cjs +1988 -0
  21. package/dist/chunk-5XAAMBDO.cjs.map +1 -0
  22. package/dist/chunk-6BQBKROR.js +95 -0
  23. package/dist/chunk-6BQBKROR.js.map +1 -0
  24. package/dist/chunk-AB5GNXJ4.js +46 -0
  25. package/dist/chunk-AB5GNXJ4.js.map +1 -0
  26. package/dist/{chunk-O2A6OVEH.js → chunk-ANVONYJF.js} +2 -2
  27. package/dist/{chunk-O2A6OVEH.js.map → chunk-ANVONYJF.js.map} +1 -1
  28. package/dist/chunk-B3V2EHRY.js +225 -0
  29. package/dist/chunk-B3V2EHRY.js.map +1 -0
  30. package/dist/{chunk-QWAX7VQO.cjs → chunk-BAHBDADJ.cjs} +11 -11
  31. package/dist/{chunk-QWAX7VQO.cjs.map → chunk-BAHBDADJ.cjs.map} +1 -1
  32. package/dist/{chunk-TF4L2NEC.cjs → chunk-DXZOL3VN.cjs} +62 -313
  33. package/dist/chunk-DXZOL3VN.cjs.map +1 -0
  34. package/dist/chunk-EDMCKHO6.cjs +225 -0
  35. package/dist/chunk-EDMCKHO6.cjs.map +1 -0
  36. package/dist/{chunk-DJ5SWJDY.js → chunk-EYHMQVAL.js} +48 -299
  37. package/dist/chunk-EYHMQVAL.js.map +1 -0
  38. package/dist/chunk-GS7T56RP.cjs +8 -0
  39. package/dist/chunk-GS7T56RP.cjs.map +1 -0
  40. package/dist/chunk-NF2SKQR7.cjs +973 -0
  41. package/dist/chunk-NF2SKQR7.cjs.map +1 -0
  42. package/dist/chunk-OPN6BGNH.js +1985 -0
  43. package/dist/chunk-OPN6BGNH.js.map +1 -0
  44. package/dist/{chunk-7Q742NI3.cjs → chunk-PADNCGZB.cjs} +17 -3
  45. package/dist/chunk-PADNCGZB.cjs.map +1 -0
  46. package/dist/chunk-PRCICORG.cjs +95 -0
  47. package/dist/chunk-PRCICORG.cjs.map +1 -0
  48. package/dist/{chunk-6QGDZTGH.js → chunk-SIH4GPV4.js} +4 -29
  49. package/dist/chunk-SIH4GPV4.js.map +1 -0
  50. package/dist/chunk-U2YDDUVP.js +970 -0
  51. package/dist/chunk-U2YDDUVP.js.map +1 -0
  52. package/dist/{chunk-4NAY6HRP.js → chunk-VJHLO2R3.js} +7 -58
  53. package/dist/chunk-VJHLO2R3.js.map +1 -0
  54. package/dist/chunk-XAWBTX3N.cjs +46 -0
  55. package/dist/chunk-XAWBTX3N.cjs.map +1 -0
  56. package/dist/{chunk-AV6OE2YQ.cjs → chunk-XOKUDUUI.cjs} +14 -39
  57. package/dist/chunk-XOKUDUUI.cjs.map +1 -0
  58. package/dist/chunk-YQDSN6R6.cjs +86 -0
  59. package/dist/chunk-YQDSN6R6.cjs.map +1 -0
  60. package/dist/cli/index.cjs +59 -201
  61. package/dist/cli/index.cjs.map +1 -1
  62. package/dist/cli/index.js +53 -198
  63. package/dist/cli/index.js.map +1 -1
  64. package/dist/cost/index.cjs +19 -3
  65. package/dist/cost/index.cjs.map +1 -1
  66. package/dist/cost/index.d.cts +10 -50
  67. package/dist/cost/index.d.ts +10 -50
  68. package/dist/cost/index.js +18 -3
  69. package/dist/index.cjs +24 -16
  70. package/dist/index.cjs.map +1 -1
  71. package/dist/index.d.cts +13 -13
  72. package/dist/index.d.ts +13 -13
  73. package/dist/index.js +44 -37
  74. package/dist/modelCache-BzRn6t_C.d.ts +113 -0
  75. package/dist/modelCache-CJftI-Ko.d.cts +113 -0
  76. package/dist/{modelNameResolver-DqFt7g6W.d.ts → modelNameResolver-5XkBMctP.d.ts} +2 -6
  77. package/dist/{modelNameResolver-D9V_GfUK.d.cts → modelNameResolver-C5CSTGFF.d.cts} +2 -6
  78. package/dist/models/index.cjs +9 -6
  79. package/dist/models/index.cjs.map +1 -1
  80. package/dist/models/index.d.cts +9 -31
  81. package/dist/models/index.d.ts +9 -31
  82. package/dist/models/index.js +9 -7
  83. package/dist/resolveUsageModel-BFwf80Hz.d.ts +140 -0
  84. package/dist/resolveUsageModel-C_YmGR1M.d.cts +140 -0
  85. package/dist/sync/index.cjs +7 -9
  86. package/dist/sync/index.cjs.map +1 -1
  87. package/dist/sync/index.d.cts +3 -8
  88. package/dist/sync/index.d.ts +3 -8
  89. package/dist/sync/index.js +8 -11
  90. package/dist/toolbox/index.cjs +1 -0
  91. package/dist/toolbox/index.cjs.map +1 -1
  92. package/dist/types-BrzJWsTU.d.cts +277 -0
  93. package/dist/types-BrzJWsTU.d.ts +277 -0
  94. package/package.json +9 -20
  95. package/src/data/models-catalog.json +670 -0
  96. package/src/data/openrouter-models-catalog.json +857 -0
  97. package/dist/AiModelsCatalogClient-4RF5BCDL.cjs +0 -9
  98. package/dist/AiModelsCatalogClient-4RF5BCDL.cjs.map +0 -1
  99. package/dist/AiModelsCatalogClient-CNeqFiFs.d.cts +0 -30
  100. package/dist/AiModelsCatalogClient-NUF3CBLW.js +0 -9
  101. package/dist/AiModelsCatalogClient-nwFoEaqL.d.ts +0 -30
  102. package/dist/catalox/index.cjs +0 -21
  103. package/dist/catalox/index.cjs.map +0 -1
  104. package/dist/catalox/index.d.cts +0 -11
  105. package/dist/catalox/index.d.ts +0 -11
  106. package/dist/catalox/index.js +0 -21
  107. package/dist/catalox/index.js.map +0 -1
  108. package/dist/chunk-4NAY6HRP.js.map +0 -1
  109. package/dist/chunk-6QGDZTGH.js.map +0 -1
  110. package/dist/chunk-7Q742NI3.cjs.map +0 -1
  111. package/dist/chunk-AJEKEWWB.js.map +0 -1
  112. package/dist/chunk-AV6OE2YQ.cjs.map +0 -1
  113. package/dist/chunk-C3H7RTFR.cjs +0 -1
  114. package/dist/chunk-C3H7RTFR.cjs.map +0 -1
  115. package/dist/chunk-DJ5SWJDY.js.map +0 -1
  116. package/dist/chunk-DKHGWHXP.cjs +0 -169
  117. package/dist/chunk-DKHGWHXP.cjs.map +0 -1
  118. package/dist/chunk-F2F4UEFD.cjs +0 -75
  119. package/dist/chunk-F2F4UEFD.cjs.map +0 -1
  120. package/dist/chunk-FGP3QXWL.cjs +0 -163
  121. package/dist/chunk-FGP3QXWL.cjs.map +0 -1
  122. package/dist/chunk-G2G4KSC5.js +0 -30
  123. package/dist/chunk-G2G4KSC5.js.map +0 -1
  124. package/dist/chunk-HN6UAQAE.cjs +0 -83
  125. package/dist/chunk-HN6UAQAE.cjs.map +0 -1
  126. package/dist/chunk-HS74X2OJ.cjs +0 -172
  127. package/dist/chunk-HS74X2OJ.cjs.map +0 -1
  128. package/dist/chunk-HYGXZY25.js +0 -163
  129. package/dist/chunk-HYGXZY25.js.map +0 -1
  130. package/dist/chunk-KQOALKKX.js +0 -75
  131. package/dist/chunk-KQOALKKX.js.map +0 -1
  132. package/dist/chunk-LYOU7CA2.cjs +0 -30
  133. package/dist/chunk-LYOU7CA2.cjs.map +0 -1
  134. package/dist/chunk-M5TMA73F.js +0 -1
  135. package/dist/chunk-M5TMA73F.js.map +0 -1
  136. package/dist/chunk-MX3AMQFC.js +0 -172
  137. package/dist/chunk-MX3AMQFC.js.map +0 -1
  138. package/dist/chunk-QCRLKVB3.cjs +0 -137
  139. package/dist/chunk-QCRLKVB3.cjs.map +0 -1
  140. package/dist/chunk-TF4L2NEC.cjs.map +0 -1
  141. package/dist/chunk-VRFVF5RH.js +0 -169
  142. package/dist/chunk-VRFVF5RH.js.map +0 -1
  143. package/dist/chunk-YHO57D2V.js +0 -83
  144. package/dist/chunk-YHO57D2V.js.map +0 -1
  145. package/dist/syncAiModelsCatalog-CnXRLm2c.d.cts +0 -32
  146. package/dist/syncAiModelsCatalog-DpkN_w7S.d.ts +0 -32
  147. package/dist/types-BYXnCvKx.d.cts +0 -137
  148. package/dist/types-BYXnCvKx.d.ts +0 -137
  149. package/dist/types-CX6QFNNy.d.cts +0 -144
  150. package/dist/types-CuiPDcVs.d.ts +0 -144
  151. package/dist/upsertAiModelRecord-C831wOIF.d.ts +0 -35
  152. package/dist/upsertAiModelRecord-CjY-sny0.d.cts +0 -35
  153. /package/dist/{AiModelsCatalogClient-NUF3CBLW.js.map → chunk-56R4XA2S.js.map} +0 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,23 @@
1
+ # Changelog
2
+
3
+ ## 2.0.0 — 2026-05-27
4
+
5
+ ### Breaking
6
+
7
+ - Removed Catalox/Firestore catalog sync (`ensureAiModelsCatalog`, `runAiModelsCatalogSync`, `ai-tools sync`).
8
+ - Pricing catalogs load from [open-assets.x12i.com](https://open-assets.x12i.com/) JSON with bundled `src/data` fallbacks.
9
+ - Default catalog cache TTL is **24 hours** (was 1 hour in earlier CLI defaults).
10
+
11
+ ### Added
12
+
13
+ - `AiModelsCatalogClient` with dual catalogs (direct vendor + OpenRouter pricing).
14
+ - `loadCatalogSources`, `loadCatalogSourcesCached`, `refreshAiModelsCatalog`, `verifyAiModelsCatalog`.
15
+ - `CostCalculator.calculateFromRecord()` for activity/gateway JSON.
16
+ - Smart usage extraction (`extractUsageInput`) with model/token field priorities.
17
+ - Versioned runtime model id resolution (`gpt-5.5-2026-04-23` → `openai/gpt-5.5`) with `VERSION_SUFFIX_PRICING` warning.
18
+ - CLI: `catalog refresh`, `catalog verify`.
19
+
20
+ ### Notes
21
+
22
+ - Catalog JSON `version` / `schema` fields are informational; the library does not validate or compare them.
23
+ - Remote catalogs are always treated as the latest pricing source when the network is available.
package/README.md CHANGED
@@ -1,315 +1,257 @@
1
1
  # @x12i/ai-tools
2
2
 
3
- TypeScript-first npm package for AI model catalog management, runtime cost calculation, project-local model aliases, and an integrated LLM utility belt — backed by [@x12i/catalox](https://www.npmjs.com/package/@x12i/catalox) and OpenRouter.
3
+ TypeScript package for AI model catalogs, runtime cost calculation, project-local model aliases, and LLM utilities.
4
+
5
+ Pricing data is loaded from two JSON catalogs on [open-assets.x12i.com](https://open-assets.x12i.com/), with bundled fallbacks shipped in the published package (`src/data`):
6
+
7
+ | Catalog | URL | Used when |
8
+ |---------|-----|-----------|
9
+ | Direct vendor | [models-catalog.json](https://open-assets.x12i.com/models-catalog.json) | `provider` is `openai`, `anthropic`, `google`, … |
10
+ | OpenRouter | [openrouter-models-catalog.json](https://open-assets.x12i.com/openrouter-models-catalog.json) | `provider` is `openrouter`, or as fallback |
11
+
12
+ Remote catalogs are always treated as the latest source. The JSON `version` field (e.g. `"0.1.0"`) is publisher metadata only — this library does not read or compare it.
13
+
14
+ ## v2.0 breaking changes
15
+
16
+ - **Catalog source:** Pricing comes from open-assets JSON (+ bundled `src/data`), not Catalox/Firestore sync.
17
+ - **CLI:** Use `ai-tools catalog refresh` and `ai-tools catalog verify` (removed `sync` / `ensure` commands).
18
+ - **Cache:** In-memory catalog cache defaults to **24 hours** (`AI_TOOLS_CACHE_TTL_MS`, default `86400000`).
4
19
 
5
20
  ## Install
6
21
 
7
22
  ```bash
8
- npm install @x12i/ai-tools @x12i/catalox
23
+ npm install @x12i/ai-tools
9
24
  ```
10
25
 
11
- Optional peers: `firebase-admin`, `@x12i/helpers`, `openai`, `better-sqlite3` (for toolbox SQLite storage).
26
+ Requires **Node.js 20+**.
27
+
28
+ Optional peers: `@x12i/helpers`, `openai`, `better-sqlite3` (toolbox SQLite storage).
12
29
 
13
30
  ## Quick start
14
31
 
15
32
  ```ts
16
- import { createCataloxFromEnv } from "@x12i/catalox/firebase";
17
- import {
18
- ensureAiModelsCatalog,
19
- AiModelsCatalogClient,
20
- CostCalculator,
21
- } from "@x12i/ai-tools";
33
+ import { AiModelsCatalogClient, CostCalculator } from "@x12i/ai-tools";
22
34
 
23
- const { catalox } = createCataloxFromEnv();
24
- await ensureAiModelsCatalog(catalox);
25
-
26
- const catalog = new AiModelsCatalogClient({ catalox });
35
+ const catalog = new AiModelsCatalogClient();
27
36
  const calculator = new CostCalculator(catalog);
28
37
 
29
38
  const result = await calculator.calculate({
30
39
  tokens: { prompt: 1000, completion: 500, total: 1500 },
31
- provider: "openrouter",
32
- modelUsed: "openai/gpt-4o",
40
+ provider: "openai",
41
+ usedModel: "gpt-5.5-2026-04-23",
33
42
  });
34
43
 
35
44
  console.log(result.cost);
45
+ console.log(result.resolvedModelId); // e.g. openai/gpt-5.5
46
+ console.log(result.usedModel); // gpt-5.5-2026-04-23
47
+ console.log(result.usage);
36
48
  ```
37
49
 
38
- ## Reasoning models
39
-
40
- After sync, each catalog record includes **`supportsReasoning`** — use it to branch prompts, token limits, or cost handling.
50
+ From activity or gateway JSON:
41
51
 
42
52
  ```ts
43
- import { getModelInfo, isReasoningModel } from "@x12i/ai-tools";
44
-
45
- const model = await getModelInfo("openai/o3-mini");
46
- if (model?.supportsReasoning) {
47
- // pass OpenRouter `reasoning: { effort: "medium" }`, bill reasoning tokens, etc.
48
- }
49
-
50
- // Same check without loading the full record field:
51
- isReasoningModel(model!);
52
- ```
53
-
54
- Detection uses OpenRouter metadata:
55
-
56
- - `supported_parameters` includes `reasoning` (or `include_reasoning`)
57
- - pricing includes `internal_reasoning` (exposed as `pricing.reasoningUsdPerToken`)
58
-
59
- ```bash
60
- npx ai-tools models list --reasoning
61
- npx ai-tools models count --reasoning
53
+ const result = await calculator.calculateFromRecord(activityDocument);
62
54
  ```
63
55
 
64
- Re-run [Updating the model catalog](#updating-the-model-catalog) after upgrading `@x12i/ai-tools` so existing Firestore rows pick up new fields (e.g. `supportsReasoning`).
56
+ ### Catalog loading and cache
65
57
 
66
- ## Updating the model catalog
58
+ Catalogs are fetched on first use and cached in memory for **24 hours**. Later calls reuse the cache without network I/O. Override TTL with `AI_TOOLS_CACHE_TTL_MS`. If a fetch fails, bundled `src/data/*.json` is used automatically.
67
59
 
68
- The **ai-models** catalog in Catalox/Firestore is a mirror of the [OpenRouter Models API](https://openrouter.ai/docs/api-reference/models). OpenRouter’s public `/models` endpoint needs **no API key**; optional `OPENROUTER_API_KEY` only helps with rate limits.
69
-
70
- ### Prerequisites
60
+ ```ts
61
+ import {
62
+ AiModelsCatalogClient,
63
+ DEFAULT_CATALOG_CACHE_TTL_MS,
64
+ invalidateCatalogLoadCache,
65
+ resolveCatalogCacheTtlMs,
66
+ } from "@x12i/ai-tools";
71
67
 
72
- Add to `.env` (see [Environment variables](#environment-variables)):
68
+ const client = new AiModelsCatalogClient({
69
+ cacheTtlMs: resolveCatalogCacheTtlMs(), // or DEFAULT_CATALOG_CACHE_TTL_MS
70
+ });
73
71
 
74
- | Variable | Required for sync |
75
- |----------|-------------------|
76
- | `GOOGLE_SERVICE_ACCOUNT_BASE64` | Yes |
77
- | `FIREBASE_PROJECT_ID` | Yes |
78
- | `FIRESTORE_DATABASE_ID` | No (defaults to `(default)`) |
79
- | `AI_TOOLS_APP_ID` | No (default: `ai-tools`) |
80
- | `AI_TOOLS_CATALOG_ID` | No (default: `ai-models`) |
72
+ await client.getAllModels(); // first call may fetch
73
+ await client.getAllModels(); // cached
81
74
 
82
- First time only, register the catalog descriptor and app binding:
75
+ await client.refresh(); // force reload now
83
76
 
84
- ```bash
85
- npx ai-tools catalog ensure
77
+ invalidateCatalogLoadCache(); // process-wide cache clear
86
78
  ```
87
79
 
88
- ### When to run an update
89
-
90
- | Situation | What to run |
91
- |-----------|-------------|
92
- | **First deploy** | `npx ai-tools catalog ensure` then `npx ai-tools sync` |
93
- | **Routine refresh** (new models/pricing on OpenRouter) | `npx ai-tools sync` |
94
- | **After upgrading `@x12i/ai-tools`** (new indexed fields) | `npx ai-tools sync` |
95
- | **Health check only** (no writes) | `npx ai-tools catalog verify` |
96
- | **Remove models OpenRouter dropped** | `npx ai-tools sync --prune-stale` |
97
-
98
- ### Recommended commands
99
-
100
- **Full update (production default)** — fetch OpenRouter, upsert every model, then verify counts match:
80
+ CLI:
101
81
 
102
82
  ```bash
103
- npx ai-tools sync
83
+ npx ai-tools catalog refresh
84
+ npx ai-tools catalog verify
85
+ npx ai-tools catalog verify --json
104
86
  ```
105
87
 
106
- Same job via npm script:
88
+ Offline (bundled data only):
107
89
 
108
90
  ```bash
109
- npm run seed
110
- npm run seed:verbose # progress lines
91
+ npx ai-tools catalog refresh --bundled-only
92
+ npx ai-tools models list --bundled-only
111
93
  ```
112
94
 
113
- **CI / cron** JSON on stdout, non-zero exit if sync or verify fails:
95
+ | npm script | Command |
96
+ |------------|---------|
97
+ | `npm run catalog:refresh` | Fetch catalogs and warm cache |
98
+ | `npm run catalog:verify` | Verify both catalogs load |
114
99
 
115
- ```bash
116
- npx ai-tools sync --json
117
- # or
118
- npm run verify:seed
119
- ```
100
+ ## Cost calculation
120
101
 
121
- **Verify only** (read-only; good between scheduled syncs):
102
+ ### Flat usage input
122
103
 
123
- ```bash
124
- npx ai-tools catalog verify
125
- npx ai-tools catalog verify --json
126
- # or
127
- npm run verify:sync
104
+ ```ts
105
+ await calculator.calculate({
106
+ tokens: { prompt: 1000, completion: 500, total: 1500 },
107
+ provider: "openrouter",
108
+ usedModel: "openai/gpt-4o",
109
+ });
128
110
  ```
129
111
 
130
- **Dry run** (fetch OpenRouter, no Firestore writes):
112
+ Runtime model priority: `usedModel` `modelUsed` → `model`.
131
113
 
132
- ```bash
133
- npx ai-tools sync --dry-run
134
- ```
114
+ Every result includes `cost`, `resolvedModelId`, `provider`, `usage`, and optional `usedModel`, `model`, `breakdown`, `warnings`.
135
115
 
136
- | npm script | Equivalent |
137
- |------------|------------|
138
- | `npm run seed` | `npx ai-tools sync` |
139
- | `npm run seed:verbose` | `npx ai-tools sync --verbose` |
140
- | `npm run verify:seed` | `npx ai-tools sync` (via seed script, sync + verify) |
141
- | `npm run verify:sync` | `npx ai-tools catalog verify` |
116
+ ### Activity / gateway records
142
117
 
143
- ### What `sync` does
118
+ Pass nested activity or MongoDB export JSON; model, provider, and tokens are extracted automatically:
144
119
 
145
- 1. Ensures catalog + descriptor exist (`catalog ensure` logic).
146
- 2. Fetches all models from OpenRouter (`output_modalities=all`).
147
- 3. Upserts each row into Firestore/Catalox (`data` + `indexed` fields).
148
- 4. Verifies Catalox count equals OpenRouter count (unless `--no-verify`).
120
+ ```ts
121
+ const result = await calculator.calculateFromRecord(activityDocument);
122
+ ```
149
123
 
150
- On re-sync, existing rows are **updated** (`syncedAt`, pricing, metadata); `createdAt` is preserved and `version` increments.
124
+ **Model field priority:** `usedModel` / `modelUsed` / `resolvedModel` `model` on `outer.input` or `config` `xynthesisModel` / `skillModel` in `rawConfig` (lowest).
151
125
 
152
- ### Cron example
126
+ **Token sources:** `usage` → `tokens` → `metadata.diagnostics` (flagged as `ESTIMATED_TOKEN_USAGE`).
153
127
 
154
- ```bash
155
- # Daily at 04:00 — fail the job if sync or verify fails
156
- 0 4 * * * cd /path/to/app && npx ai-tools sync --json >> /var/log/ai-tools-sync.log 2>&1
157
- ```
128
+ ### Versioned runtime model ids
158
129
 
159
- ### Programmatic update
130
+ `gpt-5.5-2026-04-23` resolves to catalog id `openai/gpt-5.5` when only the base model is listed. The response keeps `usedModel` as the original string and sets `resolvedModelId` to the catalog id used for pricing. A `VERSION_SUFFIX_PRICING` warning is included when suffix stripping was used.
131
+
132
+ ## Model catalogs
160
133
 
161
134
  ```ts
162
- import { createCataloxFromEnv } from "@x12i/catalox/firebase";
163
- import { runAiModelsCatalogSync, verifyAiModelsCatalog } from "@x12i/ai-tools/catalog";
135
+ import {
136
+ AiModelsCatalogClient,
137
+ loadCatalogSources,
138
+ loadCatalogSourcesCached,
139
+ refreshAiModelsCatalog,
140
+ verifyAiModelsCatalog,
141
+ } from "@x12i/ai-tools";
164
142
 
165
- const { catalox, firestore } = createCataloxFromEnv();
143
+ await refreshAiModelsCatalog(); // force network fetch + warm cache
166
144
 
167
- // Sync + verify (throws CatalogSyncJobError on failure)
168
- const job = await runAiModelsCatalogSync({ catalox, firestore, verifyAfter: true });
169
- console.log(job.sync.upserted, job.verify?.ok);
145
+ const client = new AiModelsCatalogClient();
146
+ const models = await client.getAllModels();
170
147
 
171
- // Verify only
172
- const report = await verifyAiModelsCatalog({ catalox });
173
- if (!report.ok) throw new Error(`catalog drift: missing=${report.missingInCatalox.length}`);
148
+ const loaded = await loadCatalogSources({ bundledOnly: true });
149
+ console.log(loaded.meta.directCount, loaded.meta.openRouterCount);
174
150
  ```
175
151
 
176
- ## Smart model name resolution
152
+ `OpenRouterSyncProvider` remains available to fetch live model metadata from the [OpenRouter Models API](https://openrouter.ai/docs/api-reference/models) (optional; catalogs used for pricing are the x12i JSON files above).
153
+
154
+ ## Smart model resolution
177
155
 
178
- Callers often pass messy `provider` + `model` pairs (typos, missing namespaces, version suffixes, aliases used as provider names, or no provider at all). **`ModelNameResolver`** normalises input and runs a deterministic strategy pipeline against the cached catalog (no network calls during resolution).
156
+ `ModelNameResolver` normalises messy `provider` + `model` input against the cached catalog.
179
157
 
180
158
  ```ts
181
159
  import { AiModelsCatalogClient, ModelNameResolver } from "@x12i/ai-tools";
182
160
 
183
- const catalog = new AiModelsCatalogClient({ catalox });
161
+ const catalog = new AiModelsCatalogClient();
184
162
  const models = await catalog.getAllModels();
185
-
186
163
  const resolver = new ModelNameResolver(models, { aliasRegistry });
164
+
187
165
  const result = await catalog.resolveModel({
188
166
  provider: "openrouter",
189
167
  model: "gpt4o",
190
168
  });
191
-
192
- if (result.found) {
193
- console.log(result.modelId); // openai/gpt-4o
194
- console.log(result.confidence, result.resolvedVia);
195
- console.log(result.routedViaOpenRouter);
196
- }
197
169
  ```
198
170
 
199
- ### What it handles
200
-
201
- | Input | Resolves to |
202
- |-------|-------------|
203
- | `openrouter` + `gpt-4o` | `openai/gpt-4o` (provider prefix inference) |
204
- | `openrouter` + `gpt4o` | `openai/gpt-4o` (shorthand expansion) |
205
- | `openrouter` + `gpt-4o-2024-08-06` | `openai/gpt-4o` (version suffix strip) |
206
- | `openrouter` + `openai/claude-3-5-sonnet` | `anthropic/claude-3-5-sonnet-…` (cross-provider correction) |
207
- | provider `best` (alias name) + any model | alias target (alias-as-provider correction) |
208
- | model only (`gpt-4o`) | catalog alias or prefix inference |
209
- | `ollama` + `llama3:8b` | passthrough (`record: null`, not an error) |
210
-
211
- Each success includes `confidence`, `resolvedVia[]`, and `resolvedReason`. Below-threshold matches return `found: false` with `bestRejectedCandidate` when applicable.
171
+ | Input | Typical resolution |
172
+ |-------|-------------------|
173
+ | `openrouter` + `gpt-4o` | `openai/gpt-4o` |
174
+ | `openrouter` + `gpt-4o-2024-08-06` | `openai/gpt-4o` (suffix strip) |
175
+ | `gpt-4o` only | catalog alias or prefix inference |
176
+ | `ollama` + `llama3:8b` | local passthrough |
212
177
 
213
- ### OpenRouter vs direct provider (env defaults)
178
+ ### OpenRouter vs direct routing
214
179
 
215
- When the **provider is omitted** or ambiguous, routing defaults come from your `.env` (loaded via [@x12/env](https://www.npmjs.com/package/@x12i/env)):
180
+ Routing defaults use `.env` via [@x12i/env](https://www.npmjs.com/package/@x12i/env):
216
181
 
217
- | Condition | Default routing |
218
- |-----------|-----------------|
219
- | `OPENROUTER_API_KEY` is set **and** `USE_OPENROUTER=true` or `USE_OPENROUTER=1` | OpenRouter |
220
- | `OPENROUTER_API_KEY` is set **and** `{VENDOR}_API_KEY` is missing for the resolved vendor | OpenRouter |
221
- | `{VENDOR}_API_KEY` is set (and `USE_OPENROUTER` not forcing OR) | Direct to that vendor |
222
-
223
- **Vendor API key naming:** `{VENDOR}_API_KEY` where `VENDOR` is the catalog `providerId` in `UPPER_SNAKE_CASE` (hyphens → underscores).
224
-
225
- Examples:
226
-
227
- - `openai` → `OPENAI_API_KEY`
228
- - `anthropic` → `ANTHROPIC_API_KEY`
229
- - `meta-llama` → `META_LLAMA_API_KEY`
230
- - `x-ai` → `X_AI_API_KEY`
182
+ | Condition | Routes via OpenRouter |
183
+ |-----------|----------------------|
184
+ | `USE_OPENROUTER=true` and `OPENROUTER_API_KEY` set | Yes |
185
+ | `OPENROUTER_API_KEY` set, vendor `{VENDOR}_API_KEY` missing | Yes |
186
+ | Vendor key present, `USE_OPENROUTER` not forcing OR | Direct |
231
187
 
232
188
  ```ts
233
- import {
234
- loadOpenRouterRoutingEnv,
235
- shouldDefaultRouteViaOpenRouter,
236
- } from "@x12i/ai-tools";
237
-
238
- const routing = loadOpenRouterRoutingEnv();
239
- shouldDefaultRouteViaOpenRouter("openai", routing); // true if OR key set but OPENAI_API_KEY missing
189
+ import { loadOpenRouterRoutingEnv, shouldDefaultRouteViaOpenRouter } from "@x12i/ai-tools";
240
190
  ```
241
191
 
242
- Explicit `provider: "openrouter"` always sets `routedViaOpenRouter: true`. Explicit direct providers (`openai`, `anthropic`, …) use direct routing unless env rules above apply.
243
-
244
- ### CLI
192
+ ## Reasoning models
245
193
 
246
- ```bash
247
- npx ai-tools models resolve --provider openrouter --model gpt4o
248
- npx ai-tools models resolve --model claude-sonnet --verbose
249
- npx ai-tools models resolve --model turbomax-9000 --json
250
- ```
194
+ Catalog records expose `supportsReasoning` when the model accepts reasoning parameters or has reasoning pricing.
251
195
 
252
- ## Tests
196
+ ```ts
197
+ import { getModelInfo, isReasoningModel } from "@x12i/ai-tools";
253
198
 
254
- ```bash
255
- npm test # unit tests (mocked)
256
- npm run test:live # live Firestore/Catalox tests (uses .env)
257
- npm run test:all # both
199
+ const model = await getModelInfo("openai/o3-mini", { bundledOnly: true });
200
+ if (model && isReasoningModel(model)) {
201
+ // bill reasoning tokens, pass OpenRouter reasoning params, etc.
202
+ }
258
203
  ```
259
204
 
260
205
  ## CLI
261
206
 
262
- Catalog update commands are documented in [Updating the model catalog](#updating-the-model-catalog). Other commands:
263
-
264
207
  ```bash
208
+ npx ai-tools catalog refresh
209
+ npx ai-tools catalog verify --json
265
210
  npx ai-tools models list --provider openai
266
- npx ai-tools models list --reasoning
267
211
  npx ai-tools models resolve --model gpt4o --provider openrouter --verbose
268
- npx ai-tools cost --model openai/gpt-4o --prompt-tokens 2000 --completion-tokens 800
212
+ npx ai-tools cost --model openai/gpt-5.5 --prompt-tokens 1000 --completion-tokens 500 --provider openai
269
213
  npx ai-tools alias init
270
214
  npx ai-tools alias set best anthropic/claude-opus-4 --provider openrouter
271
215
  npx ai-tools alias check
272
216
  ```
273
217
 
274
- ### Environment variables
218
+ ## Environment variables
219
+
220
+ Copy `.env.example` to `.env` in your app.
275
221
 
276
222
  | Variable | Purpose |
277
223
  |----------|---------|
278
- | `OPENROUTER_API_KEY` | OpenRouter API key; used for routing defaults and optional sync auth |
279
- | `USE_OPENROUTER` | Set to `true` or `1` to default API routing via OpenRouter when `OPENROUTER_API_KEY` is set |
280
- | `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, … | Direct vendor keys — pattern `{PROVIDER_ID}_API_KEY` (see smart resolver above) |
281
- | `GOOGLE_SERVICE_ACCOUNT_BASE64` | Firebase / Firestore credentials |
282
- | `FIREBASE_PROJECT_ID` | GCP project |
283
- | `FIRESTORE_DATABASE_ID` | Firestore database (default: `(default)`) |
284
- | `AI_TOOLS_APP_ID` | Catalox appId (default: `ai-tools`) |
285
- | `AI_TOOLS_CATALOG_ID` | Catalog id (default: `ai-models`) |
286
- | `AI_TOOLS_CACHE_TTL_MS` | nx-cache TTL override |
224
+ | `OPENROUTER_API_KEY` | OpenRouter routing defaults |
225
+ | `USE_OPENROUTER` | `true` / `1` to prefer OpenRouter when key is set |
226
+ | `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, … | Direct vendor keys (`{PROVIDER_ID}_API_KEY`) |
227
+ | `AI_TOOLS_CACHE_TTL_MS` | Catalog in-memory cache TTL (default: `86400000` = 24h) |
287
228
  | `AI_TOOLS_ALIASES_PATH` | Path to `aliases.json` |
288
229
 
289
- ## Subpath exports
290
-
291
- - `@x12i/ai-tools` — main API (`ModelNameResolver`, `loadOpenRouterRoutingEnv`, catalog, cost, aliases)
292
- - `@x12i/ai-tools/cost`
293
- - `@x12i/ai-tools/toolbox`
294
- - `@x12i/ai-tools/sync` — sync + resolver types
295
- - `@x12i/ai-tools/catalox`
296
- - `@x12i/ai-tools/aliases`
297
- - `@x12i/ai-tools/models`
298
- - `@x12i/ai-tools/catalog` — `runAiModelsCatalogSync`, `verifyAiModelsCatalog`, bootstrap
299
-
300
- ## Project aliases
301
-
302
- Commit `./ai-tools/aliases.json` in your application repo so dev/staging/prod share the same model vocabulary. Alias targets that use shorthand or versioned ids are resolved through the same smart resolver as runtime model strings.
230
+ ## Package exports
303
231
 
304
- ## Publishing
232
+ | Subpath | Contents |
233
+ |---------|----------|
234
+ | `@x12i/ai-tools` | Catalog client, cost calculator, resolver, aliases |
235
+ | `@x12i/ai-tools/cost` | Cost types, `extractUsageInput`, `CostCalculator` |
236
+ | `@x12i/ai-tools/catalog` | `loadCatalogSources`, `loadCatalogSourcesCached`, refresh/verify |
237
+ | `@x12i/ai-tools/models` | Model listing and filters |
238
+ | `@x12i/ai-tools/sync` | `ModelNameResolver`, OpenRouter fetch helper |
239
+ | `@x12i/ai-tools/aliases` | Alias registry |
240
+ | `@x12i/ai-tools/toolbox` | Tracker, router, guard |
305
241
 
306
- Scoped packages (`@x12i/...`) require public access on the free npm plan:
242
+ ## Tests
307
243
 
308
244
  ```bash
309
- npm publish
245
+ npm test
246
+ npm run build
247
+ npm run test:live # optional OpenRouter API live test
310
248
  ```
311
249
 
312
- `package.json` includes `"publishConfig": { "access": "public" }`. If publish still fails, run `npm publish --access public`.
250
+ `prepublishOnly` runs build + tests before publish.
251
+
252
+ ## Project aliases
253
+
254
+ Commit `./ai-tools/aliases.json` in your app repo. Alias targets are resolved with the same `ModelNameResolver` pipeline as runtime model strings.
313
255
 
314
256
  ## License
315
257
 
@@ -0,0 +1,58 @@
1
+ import { a as AiModelRecord, l as ModelResolverOptions, h as ModelResolutionInput, j as ModelResolutionResult } from './types-BrzJWsTU.cjs';
2
+
3
+ type LoadCatalogOptions = {
4
+ directCatalogUrl?: string;
5
+ openRouterCatalogUrl?: string;
6
+ /** When true, skip HTTP and use bundled `src/data` only. */
7
+ bundledOnly?: boolean;
8
+ fetchTimeoutMs?: number;
9
+ };
10
+ type LoadedCatalogs = {
11
+ direct: Map<string, AiModelRecord>;
12
+ openrouter: Map<string, AiModelRecord>;
13
+ meta: {
14
+ directSource: "remote" | "bundled";
15
+ openRouterSource: "remote" | "bundled";
16
+ directUrl: string;
17
+ openRouterUrl: string;
18
+ directCount: number;
19
+ openRouterCount: number;
20
+ };
21
+ };
22
+ /** Load direct-provider and OpenRouter catalogs (remote with bundled fallback). */
23
+ declare function loadCatalogSources(options?: LoadCatalogOptions): Promise<LoadedCatalogs>;
24
+ /** Read bundled JSON from disk (CLI / tests without import assertions). */
25
+ declare function readBundledCatalogFiles(): Promise<LoadedCatalogs>;
26
+
27
+ type AiModelsCatalogClientOptions = LoadCatalogOptions & {
28
+ cacheTtlMs?: number;
29
+ /** Cache scope key (default: `default`). */
30
+ cacheKey?: string;
31
+ resolverOptions?: Omit<ModelResolverOptions, "aliasRegistry">;
32
+ };
33
+ declare class AiModelsCatalogClient {
34
+ private readonly cacheTtlMs;
35
+ private readonly cacheKey;
36
+ private readonly loadOptions;
37
+ private readonly resolverOptions?;
38
+ private directModels;
39
+ private openRouterModels;
40
+ private loadedAt;
41
+ private loadPromise;
42
+ constructor(options?: AiModelsCatalogClientOptions);
43
+ private isInstanceCacheValid;
44
+ private applyLoaded;
45
+ private ensureLoaded;
46
+ mergedModels(): Map<string, AiModelRecord>;
47
+ private catalogForProvider;
48
+ private resolver;
49
+ getAllModels(): Promise<Map<string, AiModelRecord>>;
50
+ getDirectModels(): Promise<Map<string, AiModelRecord>>;
51
+ getOpenRouterModels(): Promise<Map<string, AiModelRecord>>;
52
+ resolveModel(input: ModelResolutionInput, options?: ModelResolverOptions): Promise<ModelResolutionResult>;
53
+ getModel(modelId: string, provider?: string, options?: ModelResolverOptions): Promise<AiModelRecord | null>;
54
+ /** Clear caches and fetch catalogs again immediately. */
55
+ refresh(): Promise<void>;
56
+ }
57
+
58
+ export { AiModelsCatalogClient as A, type LoadCatalogOptions as L, type AiModelsCatalogClientOptions as a, type LoadedCatalogs as b, loadCatalogSources as l, readBundledCatalogFiles as r };
@@ -0,0 +1,58 @@
1
+ import { a as AiModelRecord, l as ModelResolverOptions, h as ModelResolutionInput, j as ModelResolutionResult } from './types-BrzJWsTU.js';
2
+
3
+ type LoadCatalogOptions = {
4
+ directCatalogUrl?: string;
5
+ openRouterCatalogUrl?: string;
6
+ /** When true, skip HTTP and use bundled `src/data` only. */
7
+ bundledOnly?: boolean;
8
+ fetchTimeoutMs?: number;
9
+ };
10
+ type LoadedCatalogs = {
11
+ direct: Map<string, AiModelRecord>;
12
+ openrouter: Map<string, AiModelRecord>;
13
+ meta: {
14
+ directSource: "remote" | "bundled";
15
+ openRouterSource: "remote" | "bundled";
16
+ directUrl: string;
17
+ openRouterUrl: string;
18
+ directCount: number;
19
+ openRouterCount: number;
20
+ };
21
+ };
22
+ /** Load direct-provider and OpenRouter catalogs (remote with bundled fallback). */
23
+ declare function loadCatalogSources(options?: LoadCatalogOptions): Promise<LoadedCatalogs>;
24
+ /** Read bundled JSON from disk (CLI / tests without import assertions). */
25
+ declare function readBundledCatalogFiles(): Promise<LoadedCatalogs>;
26
+
27
+ type AiModelsCatalogClientOptions = LoadCatalogOptions & {
28
+ cacheTtlMs?: number;
29
+ /** Cache scope key (default: `default`). */
30
+ cacheKey?: string;
31
+ resolverOptions?: Omit<ModelResolverOptions, "aliasRegistry">;
32
+ };
33
+ declare class AiModelsCatalogClient {
34
+ private readonly cacheTtlMs;
35
+ private readonly cacheKey;
36
+ private readonly loadOptions;
37
+ private readonly resolverOptions?;
38
+ private directModels;
39
+ private openRouterModels;
40
+ private loadedAt;
41
+ private loadPromise;
42
+ constructor(options?: AiModelsCatalogClientOptions);
43
+ private isInstanceCacheValid;
44
+ private applyLoaded;
45
+ private ensureLoaded;
46
+ mergedModels(): Map<string, AiModelRecord>;
47
+ private catalogForProvider;
48
+ private resolver;
49
+ getAllModels(): Promise<Map<string, AiModelRecord>>;
50
+ getDirectModels(): Promise<Map<string, AiModelRecord>>;
51
+ getOpenRouterModels(): Promise<Map<string, AiModelRecord>>;
52
+ resolveModel(input: ModelResolutionInput, options?: ModelResolverOptions): Promise<ModelResolutionResult>;
53
+ getModel(modelId: string, provider?: string, options?: ModelResolverOptions): Promise<AiModelRecord | null>;
54
+ /** Clear caches and fetch catalogs again immediately. */
55
+ refresh(): Promise<void>;
56
+ }
57
+
58
+ export { AiModelsCatalogClient as A, type LoadCatalogOptions as L, type AiModelsCatalogClientOptions as a, type LoadedCatalogs as b, loadCatalogSources as l, readBundledCatalogFiles as r };
@@ -2,10 +2,11 @@
2
2
 
3
3
 
4
4
 
5
- var _chunkQWAX7VQOcjs = require('../chunk-QWAX7VQO.cjs');
6
- require('../chunk-7Q742NI3.cjs');
5
+ var _chunkBAHBDADJcjs = require('../chunk-BAHBDADJ.cjs');
6
+ require('../chunk-PADNCGZB.cjs');
7
+ require('../chunk-GS7T56RP.cjs');
7
8
 
8
9
 
9
10
 
10
- exports.AliasRegistry = _chunkQWAX7VQOcjs.AliasRegistry; exports.AliasResolver = _chunkQWAX7VQOcjs.AliasResolver;
11
+ exports.AliasRegistry = _chunkBAHBDADJcjs.AliasRegistry; exports.AliasResolver = _chunkBAHBDADJcjs.AliasResolver;
11
12
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/Users/ami/Documents/prometheus/x12i/ai-tools/dist/aliases/index.cjs"],"names":[],"mappings":"AAAA,0GAA8B;AAC9B;AACE;AACA;AACF,yDAA8B;AAC9B,iCAA8B;AAC9B;AACE;AACA;AACF,iHAAC","file":"/Users/ami/Documents/prometheus/x12i/ai-tools/dist/aliases/index.cjs"}
1
+ {"version":3,"sources":["/Users/ami/Documents/prometheus/x12i/ai-tools/dist/aliases/index.cjs"],"names":[],"mappings":"AAAA,0GAA8B;AAC9B;AACE;AACA;AACF,yDAA8B;AAC9B,iCAA8B;AAC9B,iCAA8B;AAC9B;AACE;AACA;AACF,iHAAC","file":"/Users/ami/Documents/prometheus/x12i/ai-tools/dist/aliases/index.cjs"}
@@ -1,8 +1,6 @@
1
- import { b as AliasRegistry, j as ResolvedModelRef, d as AliasValidationReport } from '../types-CX6QFNNy.cjs';
2
- export { A as AliasEntry, a as AliasFileSchema, c as AliasRegistryOptions } from '../types-CX6QFNNy.cjs';
3
- import { A as AiModelsCatalogClient } from '../AiModelsCatalogClient-CNeqFiFs.cjs';
4
- import '../types-BYXnCvKx.cjs';
5
- import '@x12i/catalox';
1
+ import { d as AliasRegistry, u as ResolvedModelRef, f as AliasValidationReport } from '../types-BrzJWsTU.cjs';
2
+ export { b as AliasEntry, c as AliasFileSchema, e as AliasRegistryOptions } from '../types-BrzJWsTU.cjs';
3
+ import { A as AiModelsCatalogClient } from '../AiModelsCatalogClient-B5FMI9gj.cjs';
6
4
 
7
5
  type AliasResolverOptions = {
8
6
  registry: AliasRegistry;
@@ -1,8 +1,6 @@
1
- import { b as AliasRegistry, j as ResolvedModelRef, d as AliasValidationReport } from '../types-CuiPDcVs.js';
2
- export { A as AliasEntry, a as AliasFileSchema, c as AliasRegistryOptions } from '../types-CuiPDcVs.js';
3
- import { A as AiModelsCatalogClient } from '../AiModelsCatalogClient-nwFoEaqL.js';
4
- import '../types-BYXnCvKx.js';
5
- import '@x12i/catalox';
1
+ import { d as AliasRegistry, u as ResolvedModelRef, f as AliasValidationReport } from '../types-BrzJWsTU.js';
2
+ export { b as AliasEntry, c as AliasFileSchema, e as AliasRegistryOptions } from '../types-BrzJWsTU.js';
3
+ import { A as AiModelsCatalogClient } from '../AiModelsCatalogClient-CPPNI6Ry.js';
6
4
 
7
5
  type AliasResolverOptions = {
8
6
  registry: AliasRegistry;