@agentproto/catalog-sync 0.5.7 → 0.5.9

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/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { mkdirSync, writeFileSync, existsSync, readFileSync } from 'fs';
1
+ import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'fs';
2
2
  import { dirname, join, resolve } from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import { z } from 'zod';
@@ -14,22 +14,111 @@ import { ElevenLabsVoicesSnapshotSchema, mapElevenLabsVoices, MinimaxVoicesSnaps
14
14
  function defineGenerator(g) {
15
15
  return g;
16
16
  }
17
+
18
+ // src/changelog.ts
19
+ var RECORD_KEY_RE = /^ {2}"([^"]+)":\s*\{/gm;
20
+ function extractRecordIds(source) {
21
+ const ids = /* @__PURE__ */ new Set();
22
+ for (const m of source.matchAll(RECORD_KEY_RE)) {
23
+ if (m[1] !== void 0) ids.add(m[1]);
24
+ }
25
+ return ids;
26
+ }
27
+ function diffModelIds(before, after) {
28
+ const beforeIds = before !== void 0 ? extractRecordIds(before) : /* @__PURE__ */ new Set();
29
+ const afterIds = extractRecordIds(after);
30
+ const added = [...afterIds].filter((id) => !beforeIds.has(id)).sort();
31
+ const removed = [...beforeIds].filter((id) => !afterIds.has(id)).sort();
32
+ return { added, removed };
33
+ }
34
+ function renderChangelogSection(date, entries) {
35
+ const nonEmpty = entries.filter((e) => e.added.length > 0 || e.removed.length > 0);
36
+ if (nonEmpty.length === 0) return "";
37
+ const lines = [`## ${date}`, ""];
38
+ for (const e of nonEmpty) {
39
+ lines.push(`### ${e.generator}`);
40
+ if (e.added.length > 0) lines.push(`- Added: ${e.added.join(", ")}`);
41
+ if (e.removed.length > 0) lines.push(`- Removed: ${e.removed.join(", ")}`);
42
+ lines.push("");
43
+ }
44
+ return lines.join("\n").replace(/\n+$/, "\n");
45
+ }
46
+ var CATALOG_CHANGELOG_HEADER = `# Catalog Changelog
47
+
48
+ Human-readable log of model additions/removals detected by
49
+ \`@agentproto/catalog-sync\` runs \u2014 NOT \`CHANGELOG.md\` next to it (that one is
50
+ changesets-driven package-version history). A dated section is appended
51
+ automatically here whenever a sync run adds or removes model ids; sections
52
+ are appended at the END of the file (newest last). Pricing-only drift (no id
53
+ added/removed) is not logged here \u2014 see the generated files' own git history
54
+ for that.
55
+ `;
56
+ function appendChangelog(existing, date, entries) {
57
+ const section = renderChangelogSection(date, entries);
58
+ if (section === "") return void 0;
59
+ const base = existing !== void 0 && existing.length > 0 ? existing.replace(/\n+$/, "\n") : CATALOG_CHANGELOG_HEADER;
60
+ return `${base}
61
+ ${section}`;
62
+ }
17
63
  var __dirname$1 = dirname(fileURLToPath(import.meta.url));
18
64
  function catalogSyncDir() {
19
65
  return resolve(__dirname$1, "..");
20
66
  }
67
+ function ledgerAbsPath(ledgerId) {
68
+ return join(catalogSyncDir(), "ledger", `${ledgerId}.json`);
69
+ }
70
+ function ledgerRelPath(ledgerId) {
71
+ return `packages/catalog-sync/ledger/${ledgerId}.json`;
72
+ }
73
+ function readLedger(ledgerId) {
74
+ const path = ledgerAbsPath(ledgerId);
75
+ if (!existsSync(path)) return {};
76
+ try {
77
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
78
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
79
+ return parsed;
80
+ } catch {
81
+ return {};
82
+ }
83
+ }
84
+ function serializeLedger(ledger) {
85
+ const sorted = {};
86
+ for (const id of Object.keys(ledger).sort()) sorted[id] = ledger[id];
87
+ return `${JSON.stringify(sorted, null, 2)}
88
+ `;
89
+ }
90
+ function computeAddedAtLedger(currentIds, previous, createdAt, today) {
91
+ const next = { ...previous };
92
+ for (const id of currentIds) {
93
+ if (next[id] !== void 0) continue;
94
+ next[id] = createdAt[id] ?? today;
95
+ }
96
+ return next;
97
+ }
98
+ function todayIso() {
99
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
100
+ }
101
+ function isoDateFromUnixSeconds(seconds) {
102
+ return new Date(seconds * 1e3).toISOString().slice(0, 10);
103
+ }
104
+
105
+ // src/runner.ts
106
+ var __dirname2 = dirname(fileURLToPath(import.meta.url));
107
+ function catalogSyncDir2() {
108
+ return resolve(__dirname2, "..");
109
+ }
21
110
  function snapshotPath(id) {
22
- return join(catalogSyncDir(), "snapshots", `${id}.json`);
111
+ return join(catalogSyncDir2(), "snapshots", `${id}.json`);
23
112
  }
24
113
  function repoRoot() {
25
- let dir = catalogSyncDir();
114
+ let dir = catalogSyncDir2();
26
115
  for (let i = 0; i < 12; i++) {
27
116
  if (existsSync(join(dir, "pnpm-workspace.yaml"))) return dir;
28
117
  const parent = dirname(dir);
29
118
  if (parent === dir) break;
30
119
  dir = parent;
31
120
  }
32
- return dirname(dirname(catalogSyncDir()));
121
+ return dirname(dirname(catalogSyncDir2()));
33
122
  }
34
123
  function readIfExists(absPath) {
35
124
  if (!existsSync(absPath)) return void 0;
@@ -51,8 +140,13 @@ function resolveHeaders(headers) {
51
140
  }
52
141
  return { headers: out, missing };
53
142
  }
143
+ var CATALOG_CHANGELOG_PATH = "packages/model-catalog/CATALOG-CHANGELOG.md";
144
+ function isChangelogEligible(relPath) {
145
+ return relPath.endsWith(".generated.ts");
146
+ }
54
147
  async function runGenerators(gens, opts) {
55
148
  const files = {};
149
+ const pathToGenerator = {};
56
150
  for (const gen of gens) {
57
151
  const ctx = {
58
152
  refresh: opts.refresh,
@@ -99,10 +193,12 @@ async function runGenerators(gens, opts) {
99
193
  const out = await gen.generate(ctx);
100
194
  for (const [path, content] of Object.entries(out)) {
101
195
  files[path] = content;
196
+ pathToGenerator[path] = gen.name;
102
197
  }
103
198
  }
104
199
  const root = repoRoot();
105
200
  const changed = [];
201
+ const changelogEntries = [];
106
202
  for (const [relPath, content] of Object.entries(files)) {
107
203
  const abs = join(root, relPath);
108
204
  const existing = readIfExists(abs);
@@ -113,6 +209,23 @@ async function runGenerators(gens, opts) {
113
209
  writeFileSync(abs, content, "utf8");
114
210
  }
115
211
  }
212
+ if (isChangelogEligible(relPath)) {
213
+ const { added, removed } = diffModelIds(existing, content);
214
+ if (added.length > 0 || removed.length > 0) {
215
+ changelogEntries.push({ generator: pathToGenerator[relPath] ?? relPath, added, removed });
216
+ }
217
+ }
218
+ }
219
+ const changelogAbs = join(root, CATALOG_CHANGELOG_PATH);
220
+ const changelogExisting = readIfExists(changelogAbs);
221
+ const newChangelog = appendChangelog(changelogExisting, todayIso(), changelogEntries);
222
+ if (newChangelog !== void 0) {
223
+ files[CATALOG_CHANGELOG_PATH] = newChangelog;
224
+ changed.push(CATALOG_CHANGELOG_PATH);
225
+ if (opts.write) {
226
+ mkdirSync(dirname(changelogAbs), { recursive: true });
227
+ writeFileSync(changelogAbs, newChangelog, "utf8");
228
+ }
116
229
  }
117
230
  return { files, changed };
118
231
  }
@@ -126,7 +239,8 @@ var PricingSchema = z.object({
126
239
  }).passthrough();
127
240
  var ModelSchema = z.object({
128
241
  id: z.string(),
129
- pricing: PricingSchema.optional()
242
+ pricing: PricingSchema.optional(),
243
+ created: z.number().optional()
130
244
  }).passthrough();
131
245
  var SnapshotSchema = z.object({
132
246
  data: z.array(ModelSchema)
@@ -159,6 +273,9 @@ function serializeEntry(e) {
159
273
  if (e.cacheWriteMultiplier !== void 0) {
160
274
  fields.push(`cacheWriteMultiplier: ${fmt(e.cacheWriteMultiplier)}`);
161
275
  }
276
+ if (e.addedAt !== void 0) {
277
+ fields.push(`addedAt: ${JSON.stringify(e.addedAt)}`);
278
+ }
162
279
  fields.push(`vendor: ${JSON.stringify(e.vendor)}`);
163
280
  fields.push(`provider: ${JSON.stringify(e.provider)}`);
164
281
  return `{
@@ -175,6 +292,9 @@ function serializeFile(entries) {
175
292
  "// Pricing carries provider USD (inputPer1M / outputPer1M) plus cache",
176
293
  "// multipliers (cacheReadMultiplier / cacheWriteMultiplier) derived from the",
177
294
  "// source's input_cache_read / input_cache_write per-token fields when present.",
295
+ "// addedAt is the ISO date this id was first seen by a sync run \u2014 backfilled",
296
+ "// from the source's own `created` timestamp, then NEVER mutated; see",
297
+ "// packages/catalog-sync/src/added-at.ts and the package README.",
178
298
  "",
179
299
  'import type { LLMPricing } from "./catalog.js"',
180
300
  "",
@@ -194,11 +314,13 @@ function serializeFile(entries) {
194
314
  lines.push("");
195
315
  return lines.join("\n");
196
316
  }
317
+ var LEDGER_ID = "llm-openrouter";
197
318
  async function generate(ctx) {
198
319
  const src = sources[0];
199
320
  if (!src) throw new Error("llm:openrouter: no source configured");
200
321
  const parsed = SnapshotSchema.parse(await ctx.fetchSource(src));
201
322
  const entries = {};
323
+ const createdAt = {};
202
324
  for (const model of parsed.data) {
203
325
  const pricing = model.pricing;
204
326
  if (!pricing) continue;
@@ -221,8 +343,19 @@ async function generate(ctx) {
221
343
  entry.cacheWriteMultiplier = round6(cacheWritePer1M / inputPer1M);
222
344
  }
223
345
  entries[model.id] = entry;
346
+ if (model.created !== void 0) createdAt[model.id] = isoDateFromUnixSeconds(model.created);
224
347
  }
225
- return { [OUTPUT_PATH]: serializeFile(entries) };
348
+ const ledger = computeAddedAtLedger(
349
+ Object.keys(entries),
350
+ readLedger(LEDGER_ID),
351
+ createdAt,
352
+ todayIso()
353
+ );
354
+ for (const [id, entry] of Object.entries(entries)) entry.addedAt = ledger[id];
355
+ return {
356
+ [OUTPUT_PATH]: serializeFile(entries),
357
+ [ledgerRelPath(LEDGER_ID)]: serializeLedger(ledger)
358
+ };
226
359
  }
227
360
  var sources = [{ id: "llm-openrouter", url: OPENROUTER_MODELS_URL }];
228
361
  var llmOpenRouterGenerator = defineGenerator({
@@ -238,7 +371,8 @@ var ModelSchema2 = z.object({
238
371
  input_price: z.number().optional(),
239
372
  output_price: z.number().optional(),
240
373
  cached_price: z.number().optional(),
241
- supports_caching: z.boolean().optional()
374
+ supports_caching: z.boolean().optional(),
375
+ created: z.number().optional()
242
376
  }).passthrough();
243
377
  var SnapshotSchema2 = z.object({
244
378
  data: z.array(ModelSchema2)
@@ -267,6 +401,9 @@ function serializeEntry2(e) {
267
401
  if (e.cacheReadMultiplier !== void 0) {
268
402
  fields.push(`cacheReadMultiplier: ${fmt2(e.cacheReadMultiplier)}`);
269
403
  }
404
+ if (e.addedAt !== void 0) {
405
+ fields.push(`addedAt: ${JSON.stringify(e.addedAt)}`);
406
+ }
270
407
  fields.push(`vendor: ${JSON.stringify(e.vendor)}`);
271
408
  fields.push(`provider: ${JSON.stringify(e.provider)}`);
272
409
  return `{
@@ -284,6 +421,9 @@ function serializeFile2(entries) {
284
421
  "// multiplier (cacheReadMultiplier) derived from the source's cached_price",
285
422
  "// per-token field when `supports_caching` is true. Requesty has no",
286
423
  "// cache-write price, so cacheWriteMultiplier is never emitted.",
424
+ "// addedAt is the ISO date this id was first seen by a sync run \u2014 backfilled",
425
+ "// from the source's own `created` timestamp, then NEVER mutated; see",
426
+ "// packages/catalog-sync/src/added-at.ts and the package README.",
287
427
  "",
288
428
  'import type { LLMPricing } from "./catalog.js"',
289
429
  "",
@@ -303,11 +443,13 @@ function serializeFile2(entries) {
303
443
  lines.push("");
304
444
  return lines.join("\n");
305
445
  }
446
+ var LEDGER_ID2 = "llm-requesty";
306
447
  async function generate2(ctx) {
307
448
  const src = sources2[0];
308
449
  if (!src) throw new Error("llm:requesty: no source configured");
309
450
  const parsed = SnapshotSchema2.parse(await ctx.fetchSource(src));
310
451
  const entries = {};
452
+ const createdAt = {};
311
453
  for (const model of parsed.data) {
312
454
  const inputPer1M = per1m2(model.input_price);
313
455
  const outputPer1M = per1m2(model.output_price);
@@ -326,8 +468,19 @@ async function generate2(ctx) {
326
468
  }
327
469
  }
328
470
  entries[model.id] = entry;
471
+ if (model.created !== void 0) createdAt[model.id] = isoDateFromUnixSeconds(model.created);
329
472
  }
330
- return { [OUTPUT_PATH2]: serializeFile2(entries) };
473
+ const ledger = computeAddedAtLedger(
474
+ Object.keys(entries),
475
+ readLedger(LEDGER_ID2),
476
+ createdAt,
477
+ todayIso()
478
+ );
479
+ for (const [id, entry] of Object.entries(entries)) entry.addedAt = ledger[id];
480
+ return {
481
+ [OUTPUT_PATH2]: serializeFile2(entries),
482
+ [ledgerRelPath(LEDGER_ID2)]: serializeLedger(ledger)
483
+ };
331
484
  }
332
485
  var sources2 = [
333
486
  {
@@ -844,22 +997,22 @@ var imageReplicate = defineGenerator({
844
997
  return { [outputPath]: lines.join("\n") };
845
998
  }
846
999
  });
847
- var __dirname2 = dirname(fileURLToPath(import.meta.url));
848
- function catalogSyncDir2() {
849
- return resolve(__dirname2, "..");
1000
+ var __dirname3 = dirname(fileURLToPath(import.meta.url));
1001
+ function catalogSyncDir3() {
1002
+ return resolve(__dirname3, "..");
850
1003
  }
851
1004
  function snapshotPath2(id) {
852
- return join(catalogSyncDir2(), "snapshots", `${id}.json`);
1005
+ return join(catalogSyncDir3(), "snapshots", `${id}.json`);
853
1006
  }
854
1007
  function repoRoot2() {
855
- let dir = catalogSyncDir2();
1008
+ let dir = catalogSyncDir3();
856
1009
  for (let i = 0; i < 12; i++) {
857
1010
  if (existsSync(join(dir, "pnpm-workspace.yaml"))) return dir;
858
1011
  const parent = dirname(dir);
859
1012
  if (parent === dir) break;
860
1013
  dir = parent;
861
1014
  }
862
- return dirname(dirname(catalogSyncDir2()));
1015
+ return dirname(dirname(catalogSyncDir3()));
863
1016
  }
864
1017
  function readIfExists2(absPath) {
865
1018
  if (!existsSync(absPath)) return void 0;
@@ -1003,6 +1156,6 @@ var OPENAI_LLM_SOURCE = {
1003
1156
  };
1004
1157
  var OPENAI_SOURCES = [OPENAI_LLM_SOURCE];
1005
1158
 
1006
- export { OPENAI_LLM_SOURCE, OPENAI_SOURCES, defineGenerator, imageReplicate, llmHuggingfaceGenerator, llmOpenRouterGenerator, llmRequestyGenerator, refreshSources, reviewedRefresh, runGenerators, voiceElevenlabs, voiceMinimax };
1159
+ export { CATALOG_CHANGELOG_HEADER, CATALOG_CHANGELOG_PATH, OPENAI_LLM_SOURCE, OPENAI_SOURCES, appendChangelog, computeAddedAtLedger, defineGenerator, diffModelIds, extractRecordIds, imageReplicate, isoDateFromUnixSeconds, ledgerAbsPath, ledgerRelPath, llmHuggingfaceGenerator, llmOpenRouterGenerator, llmRequestyGenerator, readLedger, refreshSources, renderChangelogSection, reviewedRefresh, runGenerators, serializeLedger, todayIso, voiceElevenlabs, voiceMinimax };
1007
1160
  //# sourceMappingURL=index.mjs.map
1008
1161
  //# sourceMappingURL=index.mjs.map