@routstr/sdk 0.3.6 → 0.3.7

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 (39) hide show
  1. package/package.json +2 -2
  2. package/dist/client/index.d.mts +0 -411
  3. package/dist/client/index.d.ts +0 -411
  4. package/dist/client/index.js +0 -4824
  5. package/dist/client/index.js.map +0 -1
  6. package/dist/client/index.mjs +0 -4818
  7. package/dist/client/index.mjs.map +0 -1
  8. package/dist/discovery/index.d.mts +0 -213
  9. package/dist/discovery/index.d.ts +0 -213
  10. package/dist/discovery/index.js +0 -735
  11. package/dist/discovery/index.js.map +0 -1
  12. package/dist/discovery/index.mjs +0 -732
  13. package/dist/discovery/index.mjs.map +0 -1
  14. package/dist/index.d.mts +0 -192
  15. package/dist/index.d.ts +0 -192
  16. package/dist/index.js +0 -6240
  17. package/dist/index.js.map +0 -1
  18. package/dist/index.mjs +0 -6191
  19. package/dist/index.mjs.map +0 -1
  20. package/dist/interfaces-Bp0Ngmqv.d.mts +0 -176
  21. package/dist/interfaces-Cqkt41QR.d.mts +0 -118
  22. package/dist/interfaces-D2FDCLyP.d.ts +0 -176
  23. package/dist/interfaces-D9qI1ym6.d.ts +0 -118
  24. package/dist/storage/index.d.mts +0 -87
  25. package/dist/storage/index.d.ts +0 -87
  26. package/dist/storage/index.js +0 -1740
  27. package/dist/storage/index.js.map +0 -1
  28. package/dist/storage/index.mjs +0 -1718
  29. package/dist/storage/index.mjs.map +0 -1
  30. package/dist/store-BFUGGr_v.d.ts +0 -172
  31. package/dist/store-C4FyyOnO.d.mts +0 -172
  32. package/dist/types-DPQM6tIG.d.mts +0 -234
  33. package/dist/types-DPQM6tIG.d.ts +0 -234
  34. package/dist/wallet/index.d.mts +0 -245
  35. package/dist/wallet/index.d.ts +0 -245
  36. package/dist/wallet/index.js +0 -1329
  37. package/dist/wallet/index.js.map +0 -1
  38. package/dist/wallet/index.mjs +0 -1326
  39. package/dist/wallet/index.mjs.map +0 -1
@@ -1,735 +0,0 @@
1
- 'use strict';
2
-
3
- var applesauceRelay = require('applesauce-relay');
4
- var applesauceCore = require('applesauce-core');
5
- var rxjs = require('rxjs');
6
-
7
- // core/types.ts
8
- function makeConsoleLogger(prefix) {
9
- const fmt = (args) => prefix ? [prefix, ...args] : args;
10
- return {
11
- log: (...args) => console.log(...fmt(args)),
12
- warn: (...args) => console.warn(...fmt(args)),
13
- error: (...args) => console.error(...fmt(args)),
14
- debug: (...args) => console.log(...fmt(args)),
15
- child: (p) => makeConsoleLogger(prefix ? `${prefix}:${p}` : p)
16
- };
17
- }
18
- var consoleLogger = makeConsoleLogger();
19
-
20
- // core/errors.ts
21
- var ProviderBootstrapError = class extends Error {
22
- constructor(failedProviders, message) {
23
- super(
24
- message || `Failed to bootstrap providers. Tried: ${failedProviders.join(", ")}`
25
- );
26
- this.failedProviders = failedProviders;
27
- this.name = "ProviderBootstrapError";
28
- }
29
- };
30
- var NoProvidersAvailableError = class extends Error {
31
- constructor() {
32
- super("No providers are available for model discovery");
33
- this.name = "NoProvidersAvailableError";
34
- }
35
- };
36
- var ModelManager = class _ModelManager {
37
- constructor(adapter, config = {}) {
38
- this.adapter = adapter;
39
- this.providerDirectoryUrl = config.providerDirectoryUrl || "https://api.routstr.com/v1/providers/";
40
- this.cacheTTL = config.cacheTTL || 210 * 60 * 1e3;
41
- this.includeProviderUrls = config.includeProviderUrls || [];
42
- this.excludeProviderUrls = config.excludeProviderUrls || [];
43
- this.routstrPubkey = config.routstrPubkey || "4ad6fa2d16e2a9b576c863b4cf7404a70d4dc320c0c447d10ad6ff58993eacc8";
44
- this.logger = (config.logger ?? consoleLogger).child("ModelManager");
45
- }
46
- cacheTTL;
47
- providerDirectoryUrl;
48
- includeProviderUrls;
49
- excludeProviderUrls;
50
- routstrPubkey;
51
- logger;
52
- providerNodePubkeysByUrl = /* @__PURE__ */ new Map();
53
- /**
54
- * Get the list of bootstrapped provider base URLs
55
- * @returns Array of provider base URLs
56
- */
57
- getBaseUrls() {
58
- return this.adapter.getBaseUrlsList();
59
- }
60
- static async init(adapter, config = {}, options = {}) {
61
- const manager = new _ModelManager(adapter, config);
62
- const torMode = options.torMode ?? false;
63
- const forceRefresh = options.forceRefresh ?? false;
64
- const providers = await manager.bootstrapProviders(torMode, forceRefresh);
65
- await manager.fetchModels(providers, forceRefresh);
66
- return manager;
67
- }
68
- /**
69
- * Bootstrap provider list from the provider directory
70
- * First tries to fetch from Nostr (kind 30421), falls back to HTTP
71
- * @param torMode Whether running in Tor context
72
- * @param forceRefresh Ignore provider cache and refresh provider sources
73
- * @returns Array of provider base URLs
74
- * @throws ProviderBootstrapError if all providers fail to fetch
75
- */
76
- async bootstrapProviders(torMode = false, forceRefresh = false) {
77
- if (!forceRefresh) {
78
- const cachedUrls = this.adapter.getBaseUrlsList();
79
- if (cachedUrls.length > 0) {
80
- const lastUpdate = this.adapter.getBaseUrlsLastUpdate();
81
- const cacheValid = lastUpdate && Date.now() - lastUpdate <= this.cacheTTL;
82
- if (cacheValid) {
83
- const filteredCachedUrls = this.filterBaseUrlsForTor(
84
- cachedUrls,
85
- torMode
86
- );
87
- await this.fetchRoutstr21Models(forceRefresh);
88
- await this.syncReviewedProvidersFromNostr(filteredCachedUrls);
89
- return filteredCachedUrls;
90
- }
91
- }
92
- }
93
- try {
94
- const nostrProviders = await this.bootstrapFromNostr(38421, torMode);
95
- if (nostrProviders.length > 0) {
96
- const filtered = this.filterBaseUrlsForTor(nostrProviders, torMode);
97
- this.adapter.setBaseUrlsList(filtered);
98
- this.adapter.setBaseUrlsLastUpdate(Date.now());
99
- await this.fetchRoutstr21Models(forceRefresh);
100
- await this.syncReviewedProvidersFromNostr(filtered);
101
- return filtered;
102
- }
103
- } catch (e) {
104
- this.logger.warn("Nostr bootstrap failed, falling back to HTTP:", e);
105
- }
106
- return this.bootstrapFromHttp(torMode, forceRefresh);
107
- }
108
- /**
109
- * Bootstrap providers from Nostr network (kind 30421)
110
- * @param kind The Nostr kind to fetch
111
- * @param torMode Whether running in Tor context
112
- * @returns Array of provider base URLs
113
- */
114
- async bootstrapFromNostr(kind, torMode) {
115
- const DEFAULT_RELAYS = [
116
- "wss://relay.primal.net",
117
- "wss://nos.lol",
118
- "wss://relay.damus.io"
119
- ];
120
- const pool = new applesauceRelay.RelayPool();
121
- const localEventStore = new applesauceCore.EventStore();
122
- const timeoutMs = 5e3;
123
- await new Promise((resolve) => {
124
- pool.req(DEFAULT_RELAYS, {
125
- kinds: [kind],
126
- limit: 100
127
- }).pipe(
128
- applesauceRelay.onlyEvents(),
129
- rxjs.tap((event) => {
130
- localEventStore.add(event);
131
- })
132
- ).subscribe({
133
- complete: () => {
134
- resolve();
135
- }
136
- });
137
- setTimeout(() => {
138
- resolve();
139
- }, timeoutMs);
140
- });
141
- const timeline = localEventStore.getTimeline({ kinds: [kind] });
142
- const bases = /* @__PURE__ */ new Set();
143
- this.providerNodePubkeysByUrl = /* @__PURE__ */ new Map();
144
- for (const event of timeline) {
145
- const eventUrls = [];
146
- for (const tag of event.tags) {
147
- if (tag[0] === "u" && typeof tag[1] === "string") {
148
- eventUrls.push(tag[1]);
149
- }
150
- }
151
- if (eventUrls.length > 0) {
152
- for (const url of eventUrls) {
153
- const normalized = this.normalizeUrl(url);
154
- if (!torMode || normalized.includes(".onion")) {
155
- bases.add(normalized);
156
- this.addProviderNode(
157
- this.providerNodePubkeysByUrl,
158
- normalized,
159
- event.pubkey
160
- );
161
- }
162
- }
163
- continue;
164
- }
165
- try {
166
- const content = JSON.parse(event.content);
167
- const providers = Array.isArray(content) ? content : content.providers || [];
168
- for (const p of providers) {
169
- const endpoints = this.getProviderEndpoints(p, torMode);
170
- for (const endpoint of endpoints) {
171
- bases.add(endpoint);
172
- this.addProviderNode(
173
- this.providerNodePubkeysByUrl,
174
- endpoint,
175
- p?.pubkey || event.pubkey
176
- );
177
- }
178
- }
179
- } catch {
180
- try {
181
- const providers = JSON.parse(event.content);
182
- if (Array.isArray(providers)) {
183
- for (const p of providers) {
184
- const endpoints = this.getProviderEndpoints(p, torMode);
185
- for (const endpoint of endpoints) {
186
- bases.add(endpoint);
187
- this.addProviderNode(
188
- this.providerNodePubkeysByUrl,
189
- endpoint,
190
- p?.pubkey || event.pubkey
191
- );
192
- }
193
- }
194
- }
195
- } catch {
196
- this.logger.warn(
197
- "NostrBootstrap: failed to parse event content:",
198
- event.id
199
- );
200
- }
201
- }
202
- }
203
- for (const url of this.includeProviderUrls) {
204
- const normalized = this.normalizeUrl(url);
205
- if (!torMode || normalized.includes(".onion")) {
206
- bases.add(normalized);
207
- }
208
- }
209
- const excluded = new Set(
210
- this.excludeProviderUrls.map((url) => this.normalizeUrl(url))
211
- );
212
- const result = Array.from(bases).filter((base) => !excluded.has(base));
213
- return result;
214
- }
215
- /**
216
- * Bootstrap providers from HTTP endpoint
217
- * @param torMode Whether running in Tor context
218
- * @param forceRefresh Ignore routstr21 cache and fetch fresh data
219
- * @returns Array of provider base URLs
220
- */
221
- async bootstrapFromHttp(torMode, forceRefresh = false) {
222
- try {
223
- const res = await fetch(this.providerDirectoryUrl);
224
- if (!res.ok) {
225
- throw new Error(`Failed to fetch providers: ${res.status}`);
226
- }
227
- const data = await res.json();
228
- const providers = Array.isArray(data?.providers) ? data.providers : [];
229
- const bases = /* @__PURE__ */ new Set();
230
- this.providerNodePubkeysByUrl = /* @__PURE__ */ new Map();
231
- for (const p of providers) {
232
- const endpoints = this.getProviderEndpoints(p, torMode);
233
- for (const endpoint of endpoints) {
234
- bases.add(endpoint);
235
- this.addProviderNode(this.providerNodePubkeysByUrl, endpoint, p?.pubkey);
236
- }
237
- }
238
- for (const url of this.includeProviderUrls) {
239
- const normalized = this.normalizeUrl(url);
240
- if (!torMode || normalized.includes(".onion")) {
241
- bases.add(normalized);
242
- }
243
- }
244
- const excluded = new Set(
245
- this.excludeProviderUrls.map((url) => this.normalizeUrl(url))
246
- );
247
- const list = Array.from(bases).filter((base) => !excluded.has(base));
248
- if (list.length > 0) {
249
- this.adapter.setBaseUrlsList(list);
250
- this.adapter.setBaseUrlsLastUpdate(Date.now());
251
- await this.fetchRoutstr21Models(forceRefresh);
252
- await this.syncReviewedProvidersFromNostr(list);
253
- }
254
- return list;
255
- } catch (e) {
256
- this.logger.error("Failed to bootstrap providers", e);
257
- throw new ProviderBootstrapError([], `Provider bootstrap failed: ${e}`);
258
- }
259
- }
260
- /**
261
- * Fetch Routstr review events from Nostr (kind 38425) and disable providers
262
- * whose 38421 node pubkey does not have at least one review tagged `t=lgtm`.
263
- *
264
- * Review events are expected to have:
265
- * - `node`: the reviewed 38421 provider event pubkey
266
- * - `t`: review label, where `lgtm` means the node looks good
267
- *
268
- * @param baseUrls Current provider base URLs to evaluate
269
- * @returns Array of provider base URLs disabled by the review set
270
- */
271
- async syncReviewedProvidersFromNostr(baseUrls = this.adapter.getBaseUrlsList(), providerNodes = this.providerNodePubkeysByUrl) {
272
- if (baseUrls.length === 0) return [];
273
- if (!this.adapter.setDisabledProviders) {
274
- this.logger.warn(
275
- "NostrReviews: adapter does not support setDisabledProviders; skipping provider disable sync"
276
- );
277
- return [];
278
- }
279
- const LGTM_RELAYS = [
280
- "wss://relay.primal.net",
281
- "wss://nos.lol",
282
- "wss://relay.damus.io",
283
- "wss://relay.routstr.com"
284
- ];
285
- const reviewedNodePubkeys = /* @__PURE__ */ new Set();
286
- {
287
- const pool = new applesauceRelay.RelayPool();
288
- const store = new applesauceCore.EventStore();
289
- const timeoutMs = 5e3;
290
- await new Promise((resolve) => {
291
- pool.req(LGTM_RELAYS, {
292
- kinds: [38425],
293
- "#t": ["lgtm"],
294
- limit: 500,
295
- authors: [this.routstrPubkey]
296
- }).pipe(
297
- applesauceRelay.onlyEvents(),
298
- rxjs.tap((event) => store.add(event))
299
- ).subscribe({ complete: () => resolve() });
300
- setTimeout(() => resolve(), timeoutMs);
301
- });
302
- for (const event of store.getTimeline({ kinds: [38425] })) {
303
- const hasLgtmTag = event.tags.some(
304
- (tag) => tag[0] === "t" && tag[1]?.toLowerCase() === "lgtm"
305
- );
306
- if (!hasLgtmTag) continue;
307
- for (const tag of event.tags) {
308
- if (tag[0] === "node" && typeof tag[1] === "string" && tag[1]) {
309
- reviewedNodePubkeys.add(tag[1]);
310
- }
311
- }
312
- }
313
- }
314
- if (reviewedNodePubkeys.size === 0) {
315
- this.logger.warn(
316
- "NostrReviews: no kind 38425 lgtm reviews found; keeping disabled providers unchanged"
317
- );
318
- return [];
319
- }
320
- if (providerNodes.size === 0) {
321
- this.logger.warn(
322
- "NostrReviews: no kind 38421 provider node metadata found; keeping disabled providers unchanged"
323
- );
324
- return [];
325
- }
326
- const disabledByReview = [];
327
- for (const url of baseUrls) {
328
- const normalized = this.normalizeUrl(url);
329
- const nodePubkeys = providerNodes.get(normalized) || /* @__PURE__ */ new Set();
330
- const hasLgtmReview = Array.from(nodePubkeys).some(
331
- (pubkey) => reviewedNodePubkeys.has(pubkey)
332
- );
333
- if (!hasLgtmReview) {
334
- disabledByReview.push(normalized);
335
- }
336
- }
337
- this.adapter.setDisabledProviders(Array.from(new Set(disabledByReview)));
338
- return disabledByReview;
339
- }
340
- addProviderNode(map, url, pubkey) {
341
- if (!pubkey) return;
342
- const normalized = this.normalizeUrl(url);
343
- const existing = map.get(normalized) || /* @__PURE__ */ new Set();
344
- existing.add(pubkey);
345
- map.set(normalized, existing);
346
- }
347
- /**
348
- * Fetch models from all providers and select best-priced options
349
- * Uses cache if available and not expired
350
- * @param baseUrls List of provider base URLs to fetch from
351
- * @param forceRefresh Ignore cache and fetch fresh data
352
- * @param onProgress Callback fired after each provider completes with current combined models
353
- * @returns Array of unique models with best prices selected
354
- */
355
- async fetchModels(baseUrls, forceRefresh = false, onProgress) {
356
- if (baseUrls.length === 0) {
357
- throw new NoProvidersAvailableError();
358
- }
359
- const bestById = /* @__PURE__ */ new Map();
360
- const modelsFromAllProviders = {};
361
- const disabledProviders = this.adapter.getDisabledProviders();
362
- const estimateMinCost = (m) => {
363
- return m?.sats_pricing?.completion ?? 0;
364
- };
365
- const emitProgress = () => {
366
- if (onProgress) {
367
- const currentModels = Array.from(bestById.values()).map((v) => v.model);
368
- onProgress(currentModels);
369
- }
370
- };
371
- const fetchPromises = baseUrls.map(async (url) => {
372
- const base = url.endsWith("/") ? url : `${url}/`;
373
- try {
374
- let list;
375
- if (!forceRefresh) {
376
- const lastUpdate = this.adapter.getProviderLastUpdate(base);
377
- const cacheValid = lastUpdate && Date.now() - lastUpdate <= this.cacheTTL;
378
- if (cacheValid) {
379
- const cachedModels = this.adapter.getCachedModels();
380
- const cachedList = cachedModels[base] || [];
381
- list = cachedList;
382
- } else {
383
- list = await this.fetchModelsFromProvider(base);
384
- }
385
- } else {
386
- list = await this.fetchModelsFromProvider(base);
387
- }
388
- modelsFromAllProviders[base] = list;
389
- this.adapter.setProviderLastUpdate(base, Date.now());
390
- if (!disabledProviders.includes(base)) {
391
- for (const m of list) {
392
- const existing = bestById.get(m.id);
393
- if (!m.sats_pricing) continue;
394
- if (!existing) {
395
- bestById.set(m.id, { model: m, base });
396
- continue;
397
- }
398
- const currentCost = estimateMinCost(m);
399
- const existingCost = estimateMinCost(existing.model);
400
- if (currentCost < existingCost && m.sats_pricing) {
401
- bestById.set(m.id, { model: m, base });
402
- }
403
- }
404
- }
405
- emitProgress();
406
- return { success: true, base, list };
407
- } catch (error) {
408
- if (this.isProviderDownError(error)) {
409
- this.logger.warn(`Provider ${base} is down right now.`);
410
- } else {
411
- this.logger.warn(`Failed to fetch models from ${base}:`, error);
412
- }
413
- this.adapter.setProviderLastUpdate(base, Date.now());
414
- return { success: false, base };
415
- }
416
- });
417
- await Promise.allSettled(fetchPromises);
418
- const existingCache = this.adapter.getCachedModels();
419
- this.adapter.setCachedModels({
420
- ...existingCache,
421
- ...modelsFromAllProviders
422
- });
423
- return Array.from(bestById.values()).map((v) => v.model);
424
- }
425
- /**
426
- * Fetch models from a single provider
427
- * @param baseUrl Provider base URL
428
- * @returns Array of models from provider
429
- */
430
- async fetchModelsFromProvider(baseUrl) {
431
- const res = await fetch(`${baseUrl}v1/models`);
432
- if (!res.ok) {
433
- throw new Error(`Failed to fetch models: ${res.status}`);
434
- }
435
- const json = await res.json();
436
- const list = Array.isArray(json?.data) ? json.data : [];
437
- return list;
438
- }
439
- isProviderDownError(error) {
440
- if (!(error instanceof Error)) return false;
441
- const msg = error.message.toLowerCase();
442
- if (msg.includes("fetch failed")) return true;
443
- if (msg.includes("429")) return true;
444
- if (msg.includes("502")) return true;
445
- if (msg.includes("503")) return true;
446
- if (msg.includes("504")) return true;
447
- const cause = error.cause;
448
- return cause?.code === "ENOTFOUND";
449
- }
450
- /**
451
- * Get all cached models from all providers
452
- * @returns Record mapping baseUrl -> models
453
- */
454
- getAllCachedModels() {
455
- return this.adapter.getCachedModels();
456
- }
457
- /**
458
- * Clear cache for a specific provider
459
- * @param baseUrl Provider base URL
460
- */
461
- clearProviderCache(baseUrl) {
462
- const base = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
463
- const cached = this.adapter.getCachedModels();
464
- delete cached[base];
465
- this.adapter.setCachedModels(cached);
466
- this.adapter.setProviderLastUpdate(base, 0);
467
- }
468
- /**
469
- * Clear all model caches
470
- */
471
- clearAllCache() {
472
- this.adapter.setCachedModels({});
473
- }
474
- /**
475
- * Filter base URLs based on Tor context
476
- * @param baseUrls Provider URLs to filter
477
- * @param torMode Whether in Tor context
478
- * @returns Filtered URLs appropriate for Tor mode
479
- */
480
- filterBaseUrlsForTor(baseUrls, torMode) {
481
- if (!torMode) {
482
- return baseUrls.filter((url) => !url.includes(".onion"));
483
- }
484
- return baseUrls.filter((url) => url.includes(".onion"));
485
- }
486
- /**
487
- * Get provider endpoints from provider info
488
- * @param provider Provider object from directory
489
- * @param torMode Whether in Tor context
490
- * @returns Array of endpoint URLs
491
- */
492
- getProviderEndpoints(provider, torMode) {
493
- const endpoints = [];
494
- if (torMode && provider.onion_url) {
495
- endpoints.push(this.normalizeUrl(provider.onion_url));
496
- } else if (provider.endpoint_url) {
497
- endpoints.push(this.normalizeUrl(provider.endpoint_url));
498
- }
499
- return endpoints;
500
- }
501
- /**
502
- * Normalize provider URL with trailing slash
503
- * @param url URL to normalize
504
- * @returns Normalized URL
505
- */
506
- normalizeUrl(url) {
507
- if (!url.startsWith("http")) {
508
- url = `https://${url}`;
509
- }
510
- return url.endsWith("/") ? url : `${url}/`;
511
- }
512
- /**
513
- * Fetch routstr21 models from Nostr network (kind 38423)
514
- * Uses cache if available and not expired
515
- * @returns Array of model IDs or empty array if not found
516
- */
517
- async fetchRoutstr21Models(forceRefresh = false) {
518
- const cachedModels = this.adapter.getRoutstr21Models();
519
- if (!forceRefresh && cachedModels.length > 0) {
520
- const lastUpdate = this.adapter.getRoutstr21ModelsLastUpdate();
521
- const cacheValid = lastUpdate && Date.now() - lastUpdate <= this.cacheTTL;
522
- if (cacheValid) {
523
- return cachedModels;
524
- }
525
- }
526
- const DEFAULT_RELAYS = [
527
- "wss://relay.damus.io",
528
- "wss://nos.lol",
529
- "wss://relay.routstr.com"
530
- ];
531
- const pool = new applesauceRelay.RelayPool();
532
- const localEventStore = new applesauceCore.EventStore();
533
- const timeoutMs = 5e3;
534
- await new Promise((resolve) => {
535
- pool.req(DEFAULT_RELAYS, {
536
- kinds: [38423],
537
- "#d": ["routstr-21-models"],
538
- limit: 1,
539
- authors: [this.routstrPubkey]
540
- }).pipe(
541
- applesauceRelay.onlyEvents(),
542
- rxjs.tap((event2) => {
543
- localEventStore.add(event2);
544
- })
545
- ).subscribe({
546
- complete: () => {
547
- resolve();
548
- }
549
- });
550
- setTimeout(() => {
551
- resolve();
552
- }, timeoutMs);
553
- });
554
- const timeline = localEventStore.getTimeline({ kinds: [38423] });
555
- if (timeline.length === 0) {
556
- return cachedModels.length > 0 ? cachedModels : [];
557
- }
558
- const event = timeline[0];
559
- try {
560
- const content = JSON.parse(event.content);
561
- const models = Array.isArray(content?.models) ? content.models : [];
562
- this.adapter.setRoutstr21Models(models);
563
- this.adapter.setRoutstr21ModelsLastUpdate(Date.now());
564
- return models;
565
- } catch {
566
- this.logger.warn(
567
- "Routstr21Models: failed to parse Nostr event content:",
568
- event.id
569
- );
570
- return cachedModels.length > 0 ? cachedModels : [];
571
- }
572
- }
573
- };
574
-
575
- // discovery/MintDiscovery.ts
576
- var MintDiscovery = class {
577
- constructor(adapter, config = {}) {
578
- this.adapter = adapter;
579
- this.cacheTTL = config.cacheTTL || 21 * 60 * 1e3;
580
- this.logger = (config.logger ?? consoleLogger).child("MintDiscovery");
581
- }
582
- cacheTTL;
583
- logger;
584
- /**
585
- * Fetch mints from all providers via their /v1/info endpoints
586
- * Caches mints and full provider info for later access
587
- * @param baseUrls List of provider base URLs to fetch from
588
- * @returns Object with mints and provider info from all providers
589
- */
590
- async discoverMints(baseUrls, options = {}) {
591
- if (baseUrls.length === 0) {
592
- return { mintsFromProviders: {}, infoFromProviders: {} };
593
- }
594
- const mintsFromAllProviders = {};
595
- const infoFromAllProviders = {};
596
- const forceRefresh = options.forceRefresh ?? false;
597
- const fetchPromises = baseUrls.map(async (url) => {
598
- const base = url.endsWith("/") ? url : `${url}/`;
599
- try {
600
- if (!forceRefresh) {
601
- const lastUpdate = this.adapter.getProviderLastUpdate(base);
602
- const cacheValid = lastUpdate && Date.now() - lastUpdate <= this.cacheTTL;
603
- if (cacheValid) {
604
- const cachedMints = this.adapter.getCachedMints()[base] || [];
605
- const cachedInfo = this.adapter.getCachedProviderInfo()[base];
606
- mintsFromAllProviders[base] = cachedMints;
607
- if (cachedInfo) {
608
- infoFromAllProviders[base] = cachedInfo;
609
- }
610
- return {
611
- success: true,
612
- base,
613
- mints: cachedMints,
614
- info: cachedInfo
615
- };
616
- }
617
- }
618
- const res = await fetch(`${base}v1/info`);
619
- if (!res.ok) {
620
- throw new Error(`Failed to fetch info: ${res.status}`);
621
- }
622
- const json = await res.json();
623
- const mints = Array.isArray(json?.mints) ? json.mints : [];
624
- const normalizedMints = mints.map(
625
- (mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint
626
- );
627
- mintsFromAllProviders[base] = normalizedMints;
628
- infoFromAllProviders[base] = json;
629
- this.adapter.setProviderLastUpdate(base, Date.now());
630
- return { success: true, base, mints: normalizedMints, info: json };
631
- } catch (error) {
632
- this.adapter.setProviderLastUpdate(base, Date.now());
633
- if (this.isProviderDownError(error)) {
634
- this.logger.warn(`Provider ${base} is down right now.`);
635
- } else {
636
- this.logger.warn(`Failed to fetch mints from ${base}:`, error);
637
- }
638
- return { success: false, base, mints: [], info: null };
639
- }
640
- });
641
- const results = await Promise.allSettled(fetchPromises);
642
- for (const result of results) {
643
- if (result.status === "fulfilled") {
644
- const { base, mints, info } = result.value;
645
- mintsFromAllProviders[base] = mints;
646
- if (info) {
647
- infoFromAllProviders[base] = info;
648
- }
649
- } else {
650
- this.logger.error("Mint discovery error:", result.reason);
651
- }
652
- }
653
- try {
654
- this.adapter.setCachedMints(mintsFromAllProviders);
655
- this.adapter.setCachedProviderInfo(infoFromAllProviders);
656
- } catch (error) {
657
- this.logger.error("Error caching mint discovery results:", error);
658
- }
659
- return {
660
- mintsFromProviders: mintsFromAllProviders,
661
- infoFromProviders: infoFromAllProviders
662
- };
663
- }
664
- /**
665
- * Get cached mints from all providers
666
- * @returns Record mapping baseUrl -> mint URLs
667
- */
668
- getCachedMints() {
669
- return this.adapter.getCachedMints();
670
- }
671
- /**
672
- * Get cached provider info from all providers
673
- * @returns Record mapping baseUrl -> provider info
674
- */
675
- getCachedProviderInfo() {
676
- return this.adapter.getCachedProviderInfo();
677
- }
678
- /**
679
- * Get mints for a specific provider
680
- * @param baseUrl Provider base URL
681
- * @returns Array of mint URLs for the provider
682
- */
683
- getProviderMints(baseUrl) {
684
- const normalized = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
685
- const allMints = this.getCachedMints();
686
- return allMints[normalized] || [];
687
- }
688
- /**
689
- * Get info for a specific provider
690
- * @param baseUrl Provider base URL
691
- * @returns Provider info object or null if not found
692
- */
693
- getProviderInfo(baseUrl) {
694
- const normalized = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
695
- const allInfo = this.getCachedProviderInfo();
696
- return allInfo[normalized] || null;
697
- }
698
- /**
699
- * Clear mint cache for a specific provider
700
- * @param baseUrl Provider base URL
701
- */
702
- clearProviderMintCache(baseUrl) {
703
- const normalized = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
704
- const mints = this.getCachedMints();
705
- delete mints[normalized];
706
- this.adapter.setCachedMints(mints);
707
- const info = this.getCachedProviderInfo();
708
- delete info[normalized];
709
- this.adapter.setCachedProviderInfo(info);
710
- }
711
- /**
712
- * Clear all mint caches
713
- */
714
- clearAllCache() {
715
- this.adapter.setCachedMints({});
716
- this.adapter.setCachedProviderInfo({});
717
- }
718
- isProviderDownError(error) {
719
- if (!(error instanceof Error)) return false;
720
- const msg = error.message.toLowerCase();
721
- if (msg.includes("fetch failed")) return true;
722
- if (msg.includes("429")) return true;
723
- if (msg.includes("502")) return true;
724
- if (msg.includes("503")) return true;
725
- if (msg.includes("504")) return true;
726
- const cause = error.cause;
727
- if (cause?.code === "ENOTFOUND") return true;
728
- return false;
729
- }
730
- };
731
-
732
- exports.MintDiscovery = MintDiscovery;
733
- exports.ModelManager = ModelManager;
734
- //# sourceMappingURL=index.js.map
735
- //# sourceMappingURL=index.js.map