@routstr/sdk 0.1.0 → 0.1.2

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.
@@ -0,0 +1,581 @@
1
+ 'use strict';
2
+
3
+ var applesauceRelay = require('applesauce-relay');
4
+ var applesauceCore = require('applesauce-core');
5
+ var rxjs = require('rxjs');
6
+
7
+ // core/errors.ts
8
+ var ProviderBootstrapError = class extends Error {
9
+ constructor(failedProviders, message) {
10
+ super(
11
+ message || `Failed to bootstrap providers. Tried: ${failedProviders.join(", ")}`
12
+ );
13
+ this.failedProviders = failedProviders;
14
+ this.name = "ProviderBootstrapError";
15
+ }
16
+ };
17
+ var NoProvidersAvailableError = class extends Error {
18
+ constructor() {
19
+ super("No providers are available for model discovery");
20
+ this.name = "NoProvidersAvailableError";
21
+ }
22
+ };
23
+ var ModelManager = class _ModelManager {
24
+ constructor(adapter, config = {}) {
25
+ this.adapter = adapter;
26
+ this.providerDirectoryUrl = config.providerDirectoryUrl || "https://api.routstr.com/v1/providers/";
27
+ this.cacheTTL = config.cacheTTL || 210 * 60 * 1e3;
28
+ this.includeProviderUrls = config.includeProviderUrls || [];
29
+ this.excludeProviderUrls = config.excludeProviderUrls || [];
30
+ }
31
+ cacheTTL;
32
+ providerDirectoryUrl;
33
+ includeProviderUrls;
34
+ excludeProviderUrls;
35
+ /**
36
+ * Get the list of bootstrapped provider base URLs
37
+ * @returns Array of provider base URLs
38
+ */
39
+ getBaseUrls() {
40
+ return this.adapter.getBaseUrlsList();
41
+ }
42
+ static async init(adapter, config = {}, options = {}) {
43
+ const manager = new _ModelManager(adapter, config);
44
+ const torMode = options.torMode ?? false;
45
+ const forceRefresh = options.forceRefresh ?? false;
46
+ const providers = await manager.bootstrapProviders(torMode);
47
+ await manager.fetchModels(providers, forceRefresh);
48
+ return manager;
49
+ }
50
+ /**
51
+ * Bootstrap provider list from the provider directory
52
+ * First tries to fetch from Nostr (kind 30421), falls back to HTTP
53
+ * @param torMode Whether running in Tor context
54
+ * @returns Array of provider base URLs
55
+ * @throws ProviderBootstrapError if all providers fail to fetch
56
+ */
57
+ async bootstrapProviders(torMode = false) {
58
+ const cachedUrls = this.adapter.getBaseUrlsList();
59
+ if (cachedUrls.length > 0) {
60
+ const lastUpdate = this.adapter.getBaseUrlsLastUpdate();
61
+ const cacheValid = lastUpdate && Date.now() - lastUpdate <= this.cacheTTL;
62
+ if (cacheValid) {
63
+ return this.filterBaseUrlsForTor(cachedUrls, torMode);
64
+ }
65
+ }
66
+ try {
67
+ const nostrProviders = await this.bootstrapFromNostr(38421, torMode);
68
+ if (nostrProviders.length > 0) {
69
+ const filtered = this.filterBaseUrlsForTor(nostrProviders, torMode);
70
+ this.adapter.setBaseUrlsList(filtered);
71
+ this.adapter.setBaseUrlsLastUpdate(Date.now());
72
+ await this.fetchRoutstr21Models();
73
+ return filtered;
74
+ }
75
+ } catch (e) {
76
+ console.warn("Nostr bootstrap failed, falling back to HTTP:", e);
77
+ }
78
+ return this.bootstrapFromHttp(torMode);
79
+ }
80
+ /**
81
+ * Bootstrap providers from Nostr network (kind 30421)
82
+ * @param kind The Nostr kind to fetch
83
+ * @param torMode Whether running in Tor context
84
+ * @returns Array of provider base URLs
85
+ */
86
+ async bootstrapFromNostr(kind, torMode) {
87
+ const DEFAULT_RELAYS = [
88
+ "wss://relay.primal.net",
89
+ "wss://nos.lol",
90
+ "wss://relay.routstr.com"
91
+ ];
92
+ const pool = new applesauceRelay.RelayPool();
93
+ const localEventStore = new applesauceCore.EventStore();
94
+ const timeoutMs = 5e3;
95
+ await new Promise((resolve) => {
96
+ pool.req(DEFAULT_RELAYS, {
97
+ kinds: [kind],
98
+ limit: 100
99
+ }).pipe(
100
+ applesauceRelay.onlyEvents(),
101
+ rxjs.tap((event) => {
102
+ localEventStore.add(event);
103
+ })
104
+ ).subscribe({
105
+ complete: () => {
106
+ resolve();
107
+ }
108
+ });
109
+ setTimeout(() => {
110
+ resolve();
111
+ }, timeoutMs);
112
+ });
113
+ const timeline = localEventStore.getTimeline({ kinds: [kind] });
114
+ const bases = /* @__PURE__ */ new Set();
115
+ for (const event of timeline) {
116
+ const eventUrls = [];
117
+ for (const tag of event.tags) {
118
+ if (tag[0] === "u" && typeof tag[1] === "string") {
119
+ eventUrls.push(tag[1]);
120
+ }
121
+ }
122
+ if (eventUrls.length > 0) {
123
+ for (const url of eventUrls) {
124
+ const normalized = this.normalizeUrl(url);
125
+ if (!torMode || normalized.includes(".onion")) {
126
+ bases.add(normalized);
127
+ }
128
+ }
129
+ continue;
130
+ }
131
+ try {
132
+ const content = JSON.parse(event.content);
133
+ const providers = Array.isArray(content) ? content : content.providers || [];
134
+ for (const p of providers) {
135
+ const endpoints = this.getProviderEndpoints(p, torMode);
136
+ for (const endpoint of endpoints) {
137
+ bases.add(endpoint);
138
+ }
139
+ }
140
+ } catch {
141
+ try {
142
+ const providers = JSON.parse(event.content);
143
+ if (Array.isArray(providers)) {
144
+ for (const p of providers) {
145
+ const endpoints = this.getProviderEndpoints(p, torMode);
146
+ for (const endpoint of endpoints) {
147
+ bases.add(endpoint);
148
+ }
149
+ }
150
+ }
151
+ } catch {
152
+ console.warn(
153
+ "[NostrBootstrap] Failed to parse Nostr event content:",
154
+ event.id
155
+ );
156
+ }
157
+ }
158
+ }
159
+ for (const url of this.includeProviderUrls) {
160
+ const normalized = this.normalizeUrl(url);
161
+ if (!torMode || normalized.includes(".onion")) {
162
+ bases.add(normalized);
163
+ }
164
+ }
165
+ const excluded = new Set(
166
+ this.excludeProviderUrls.map((url) => this.normalizeUrl(url))
167
+ );
168
+ const result = Array.from(bases).filter((base) => !excluded.has(base));
169
+ return result;
170
+ }
171
+ /**
172
+ * Bootstrap providers from HTTP endpoint
173
+ * @param torMode Whether running in Tor context
174
+ * @returns Array of provider base URLs
175
+ */
176
+ async bootstrapFromHttp(torMode) {
177
+ try {
178
+ const res = await fetch(this.providerDirectoryUrl);
179
+ if (!res.ok) {
180
+ throw new Error(`Failed to fetch providers: ${res.status}`);
181
+ }
182
+ const data = await res.json();
183
+ const providers = Array.isArray(data?.providers) ? data.providers : [];
184
+ const bases = /* @__PURE__ */ new Set();
185
+ for (const p of providers) {
186
+ const endpoints = this.getProviderEndpoints(p, torMode);
187
+ for (const endpoint of endpoints) {
188
+ bases.add(endpoint);
189
+ }
190
+ }
191
+ for (const url of this.includeProviderUrls) {
192
+ const normalized = this.normalizeUrl(url);
193
+ if (!torMode || normalized.includes(".onion")) {
194
+ bases.add(normalized);
195
+ }
196
+ }
197
+ const excluded = new Set(
198
+ this.excludeProviderUrls.map((url) => this.normalizeUrl(url))
199
+ );
200
+ const list = Array.from(bases).filter((base) => !excluded.has(base));
201
+ if (list.length > 0) {
202
+ this.adapter.setBaseUrlsList(list);
203
+ this.adapter.setBaseUrlsLastUpdate(Date.now());
204
+ await this.fetchRoutstr21Models();
205
+ }
206
+ return list;
207
+ } catch (e) {
208
+ console.error("Failed to bootstrap providers", e);
209
+ throw new ProviderBootstrapError([], `Provider bootstrap failed: ${e}`);
210
+ }
211
+ }
212
+ /**
213
+ * Fetch models from all providers and select best-priced options
214
+ * Uses cache if available and not expired
215
+ * @param baseUrls List of provider base URLs to fetch from
216
+ * @param forceRefresh Ignore cache and fetch fresh data
217
+ * @returns Array of unique models with best prices selected
218
+ */
219
+ async fetchModels(baseUrls, forceRefresh = false) {
220
+ if (baseUrls.length === 0) {
221
+ throw new NoProvidersAvailableError();
222
+ }
223
+ const bestById = /* @__PURE__ */ new Map();
224
+ const modelsFromAllProviders = {};
225
+ const disabledProviders = this.adapter.getDisabledProviders();
226
+ const estimateMinCost = (m) => {
227
+ return m?.sats_pricing?.completion ?? 0;
228
+ };
229
+ const fetchPromises = baseUrls.map(async (url) => {
230
+ const base = url.endsWith("/") ? url : `${url}/`;
231
+ try {
232
+ let list;
233
+ if (!forceRefresh) {
234
+ const lastUpdate = this.adapter.getProviderLastUpdate(base);
235
+ const cacheValid = lastUpdate && Date.now() - lastUpdate <= this.cacheTTL;
236
+ if (cacheValid) {
237
+ const cachedModels = this.adapter.getCachedModels();
238
+ const cachedList = cachedModels[base] || [];
239
+ list = cachedList;
240
+ } else {
241
+ list = await this.fetchModelsFromProvider(base);
242
+ }
243
+ } else {
244
+ list = await this.fetchModelsFromProvider(base);
245
+ }
246
+ modelsFromAllProviders[base] = list;
247
+ this.adapter.setProviderLastUpdate(base, Date.now());
248
+ if (!disabledProviders.includes(base)) {
249
+ for (const m of list) {
250
+ const existing = bestById.get(m.id);
251
+ if (!m.sats_pricing) continue;
252
+ if (!existing) {
253
+ bestById.set(m.id, { model: m, base });
254
+ continue;
255
+ }
256
+ const currentCost = estimateMinCost(m);
257
+ const existingCost = estimateMinCost(existing.model);
258
+ if (currentCost < existingCost && m.sats_pricing) {
259
+ bestById.set(m.id, { model: m, base });
260
+ }
261
+ }
262
+ }
263
+ return { success: true, base, list };
264
+ } catch (error) {
265
+ if (this.isProviderDownError(error)) {
266
+ console.warn(`Provider ${base} is down right now.`);
267
+ } else {
268
+ console.warn(`Failed to fetch models from ${base}:`, error);
269
+ }
270
+ this.adapter.setProviderLastUpdate(base, Date.now());
271
+ return { success: false, base };
272
+ }
273
+ });
274
+ await Promise.allSettled(fetchPromises);
275
+ const existingCache = this.adapter.getCachedModels();
276
+ this.adapter.setCachedModels({
277
+ ...existingCache,
278
+ ...modelsFromAllProviders
279
+ });
280
+ return Array.from(bestById.values()).map((v) => v.model);
281
+ }
282
+ /**
283
+ * Fetch models from a single provider
284
+ * @param baseUrl Provider base URL
285
+ * @returns Array of models from provider
286
+ */
287
+ async fetchModelsFromProvider(baseUrl) {
288
+ const res = await fetch(`${baseUrl}v1/models`);
289
+ if (!res.ok) {
290
+ throw new Error(`Failed to fetch models: ${res.status}`);
291
+ }
292
+ const json = await res.json();
293
+ const list = Array.isArray(json?.data) ? json.data.map((m) => ({
294
+ ...m,
295
+ id: m.id.split("/").pop() || m.id
296
+ })) : [];
297
+ return list;
298
+ }
299
+ isProviderDownError(error) {
300
+ if (!(error instanceof Error)) return false;
301
+ const msg = error.message.toLowerCase();
302
+ if (msg.includes("fetch failed")) return true;
303
+ if (msg.includes("502")) return true;
304
+ if (msg.includes("503")) return true;
305
+ if (msg.includes("504")) return true;
306
+ const cause = error.cause;
307
+ return cause?.code === "ENOTFOUND";
308
+ }
309
+ /**
310
+ * Get all cached models from all providers
311
+ * @returns Record mapping baseUrl -> models
312
+ */
313
+ getAllCachedModels() {
314
+ return this.adapter.getCachedModels();
315
+ }
316
+ /**
317
+ * Clear cache for a specific provider
318
+ * @param baseUrl Provider base URL
319
+ */
320
+ clearProviderCache(baseUrl) {
321
+ const base = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
322
+ const cached = this.adapter.getCachedModels();
323
+ delete cached[base];
324
+ this.adapter.setCachedModels(cached);
325
+ this.adapter.setProviderLastUpdate(base, 0);
326
+ }
327
+ /**
328
+ * Clear all model caches
329
+ */
330
+ clearAllCache() {
331
+ this.adapter.setCachedModels({});
332
+ }
333
+ /**
334
+ * Filter base URLs based on Tor context
335
+ * @param baseUrls Provider URLs to filter
336
+ * @param torMode Whether in Tor context
337
+ * @returns Filtered URLs appropriate for Tor mode
338
+ */
339
+ filterBaseUrlsForTor(baseUrls, torMode) {
340
+ if (!torMode) {
341
+ return baseUrls.filter((url) => !url.includes(".onion"));
342
+ }
343
+ return baseUrls.filter((url) => url.includes(".onion"));
344
+ }
345
+ /**
346
+ * Get provider endpoints from provider info
347
+ * @param provider Provider object from directory
348
+ * @param torMode Whether in Tor context
349
+ * @returns Array of endpoint URLs
350
+ */
351
+ getProviderEndpoints(provider, torMode) {
352
+ const endpoints = [];
353
+ if (torMode && provider.onion_url) {
354
+ endpoints.push(this.normalizeUrl(provider.onion_url));
355
+ } else if (provider.endpoint_url) {
356
+ endpoints.push(this.normalizeUrl(provider.endpoint_url));
357
+ }
358
+ return endpoints;
359
+ }
360
+ /**
361
+ * Normalize provider URL with trailing slash
362
+ * @param url URL to normalize
363
+ * @returns Normalized URL
364
+ */
365
+ normalizeUrl(url) {
366
+ if (!url.startsWith("http")) {
367
+ url = `https://${url}`;
368
+ }
369
+ return url.endsWith("/") ? url : `${url}/`;
370
+ }
371
+ /**
372
+ * Fetch routstr21 models from Nostr network (kind 38423)
373
+ * @returns Array of model IDs or empty array if not found
374
+ */
375
+ async fetchRoutstr21Models() {
376
+ const DEFAULT_RELAYS = [
377
+ "wss://relay.primal.net",
378
+ "wss://nos.lol",
379
+ "wss://relay.routstr.com"
380
+ ];
381
+ const pool = new applesauceRelay.RelayPool();
382
+ const localEventStore = new applesauceCore.EventStore();
383
+ const timeoutMs = 5e3;
384
+ await new Promise((resolve) => {
385
+ pool.req(DEFAULT_RELAYS, {
386
+ kinds: [38423],
387
+ "#d": ["routstr-21-models"],
388
+ limit: 1,
389
+ authors: ["4ad6fa2d16e2a9b576c863b4cf7404a70d4dc320c0c447d10ad6ff58993eacc8"]
390
+ }).pipe(
391
+ applesauceRelay.onlyEvents(),
392
+ rxjs.tap((event2) => {
393
+ localEventStore.add(event2);
394
+ })
395
+ ).subscribe({
396
+ complete: () => {
397
+ resolve();
398
+ }
399
+ });
400
+ setTimeout(() => {
401
+ resolve();
402
+ }, timeoutMs);
403
+ });
404
+ const timeline = localEventStore.getTimeline({ kinds: [38423] });
405
+ if (timeline.length === 0) {
406
+ return [];
407
+ }
408
+ const event = timeline[0];
409
+ try {
410
+ const content = JSON.parse(event.content);
411
+ const models = Array.isArray(content?.models) ? content.models : [];
412
+ this.adapter.setRoutstr21Models(models);
413
+ return models;
414
+ } catch {
415
+ console.warn(
416
+ "[Routstr21Models] Failed to parse Nostr event content:",
417
+ event.id
418
+ );
419
+ return [];
420
+ }
421
+ }
422
+ };
423
+
424
+ // discovery/MintDiscovery.ts
425
+ var MintDiscovery = class {
426
+ constructor(adapter, config = {}) {
427
+ this.adapter = adapter;
428
+ this.cacheTTL = config.cacheTTL || 21 * 60 * 1e3;
429
+ }
430
+ cacheTTL;
431
+ /**
432
+ * Fetch mints from all providers via their /v1/info endpoints
433
+ * Caches mints and full provider info for later access
434
+ * @param baseUrls List of provider base URLs to fetch from
435
+ * @returns Object with mints and provider info from all providers
436
+ */
437
+ async discoverMints(baseUrls, options = {}) {
438
+ if (baseUrls.length === 0) {
439
+ return { mintsFromProviders: {}, infoFromProviders: {} };
440
+ }
441
+ const mintsFromAllProviders = {};
442
+ const infoFromAllProviders = {};
443
+ const forceRefresh = options.forceRefresh ?? false;
444
+ const fetchPromises = baseUrls.map(async (url) => {
445
+ const base = url.endsWith("/") ? url : `${url}/`;
446
+ try {
447
+ if (!forceRefresh) {
448
+ const lastUpdate = this.adapter.getProviderLastUpdate(base);
449
+ const cacheValid = lastUpdate && Date.now() - lastUpdate <= this.cacheTTL;
450
+ if (cacheValid) {
451
+ const cachedMints = this.adapter.getCachedMints()[base] || [];
452
+ const cachedInfo = this.adapter.getCachedProviderInfo()[base];
453
+ mintsFromAllProviders[base] = cachedMints;
454
+ if (cachedInfo) {
455
+ infoFromAllProviders[base] = cachedInfo;
456
+ }
457
+ return {
458
+ success: true,
459
+ base,
460
+ mints: cachedMints,
461
+ info: cachedInfo
462
+ };
463
+ }
464
+ }
465
+ const res = await fetch(`${base}v1/info`);
466
+ if (!res.ok) {
467
+ throw new Error(`Failed to fetch info: ${res.status}`);
468
+ }
469
+ const json = await res.json();
470
+ const mints = Array.isArray(json?.mints) ? json.mints : [];
471
+ const normalizedMints = mints.map(
472
+ (mint) => mint.endsWith("/") ? mint.slice(0, -1) : mint
473
+ );
474
+ mintsFromAllProviders[base] = normalizedMints;
475
+ infoFromAllProviders[base] = json;
476
+ this.adapter.setProviderLastUpdate(base, Date.now());
477
+ return { success: true, base, mints: normalizedMints, info: json };
478
+ } catch (error) {
479
+ this.adapter.setProviderLastUpdate(base, Date.now());
480
+ if (this.isProviderDownError(error)) {
481
+ console.warn(`Provider ${base} is down right now.`);
482
+ } else {
483
+ console.warn(`Failed to fetch mints from ${base}:`, error);
484
+ }
485
+ return { success: false, base, mints: [], info: null };
486
+ }
487
+ });
488
+ const results = await Promise.allSettled(fetchPromises);
489
+ for (const result of results) {
490
+ if (result.status === "fulfilled") {
491
+ const { base, mints, info } = result.value;
492
+ mintsFromAllProviders[base] = mints;
493
+ if (info) {
494
+ infoFromAllProviders[base] = info;
495
+ }
496
+ } else {
497
+ console.error("Mint discovery error:", result.reason);
498
+ }
499
+ }
500
+ try {
501
+ this.adapter.setCachedMints(mintsFromAllProviders);
502
+ this.adapter.setCachedProviderInfo(infoFromAllProviders);
503
+ } catch (error) {
504
+ console.error("Error caching mint discovery results:", error);
505
+ }
506
+ return {
507
+ mintsFromProviders: mintsFromAllProviders,
508
+ infoFromProviders: infoFromAllProviders
509
+ };
510
+ }
511
+ /**
512
+ * Get cached mints from all providers
513
+ * @returns Record mapping baseUrl -> mint URLs
514
+ */
515
+ getCachedMints() {
516
+ return this.adapter.getCachedMints();
517
+ }
518
+ /**
519
+ * Get cached provider info from all providers
520
+ * @returns Record mapping baseUrl -> provider info
521
+ */
522
+ getCachedProviderInfo() {
523
+ return this.adapter.getCachedProviderInfo();
524
+ }
525
+ /**
526
+ * Get mints for a specific provider
527
+ * @param baseUrl Provider base URL
528
+ * @returns Array of mint URLs for the provider
529
+ */
530
+ getProviderMints(baseUrl) {
531
+ const normalized = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
532
+ const allMints = this.getCachedMints();
533
+ return allMints[normalized] || [];
534
+ }
535
+ /**
536
+ * Get info for a specific provider
537
+ * @param baseUrl Provider base URL
538
+ * @returns Provider info object or null if not found
539
+ */
540
+ getProviderInfo(baseUrl) {
541
+ const normalized = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
542
+ const allInfo = this.getCachedProviderInfo();
543
+ return allInfo[normalized] || null;
544
+ }
545
+ /**
546
+ * Clear mint cache for a specific provider
547
+ * @param baseUrl Provider base URL
548
+ */
549
+ clearProviderMintCache(baseUrl) {
550
+ const normalized = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
551
+ const mints = this.getCachedMints();
552
+ delete mints[normalized];
553
+ this.adapter.setCachedMints(mints);
554
+ const info = this.getCachedProviderInfo();
555
+ delete info[normalized];
556
+ this.adapter.setCachedProviderInfo(info);
557
+ }
558
+ /**
559
+ * Clear all mint caches
560
+ */
561
+ clearAllCache() {
562
+ this.adapter.setCachedMints({});
563
+ this.adapter.setCachedProviderInfo({});
564
+ }
565
+ isProviderDownError(error) {
566
+ if (!(error instanceof Error)) return false;
567
+ const msg = error.message.toLowerCase();
568
+ if (msg.includes("fetch failed")) return true;
569
+ if (msg.includes("502")) return true;
570
+ if (msg.includes("503")) return true;
571
+ if (msg.includes("504")) return true;
572
+ const cause = error.cause;
573
+ if (cause?.code === "ENOTFOUND") return true;
574
+ return false;
575
+ }
576
+ };
577
+
578
+ exports.MintDiscovery = MintDiscovery;
579
+ exports.ModelManager = ModelManager;
580
+ //# sourceMappingURL=index.js.map
581
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../core/errors.ts","../../discovery/ModelManager.ts","../../discovery/MintDiscovery.ts"],"names":["RelayPool","EventStore","onlyEvents","tap","event"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Custom error classes for the Routstr SDK\n * Provides specific error types for different failure modes\n */\n\n/**\n * Error thrown when balance is insufficient for an operation\n */\nexport class InsufficientBalanceError extends Error {\n constructor(\n public required: number,\n public available: number,\n public maxMintBalance: number = 0,\n public maxMintUrl: string = \"\"\n ) {\n super(\n `Insufficient balance: need ${required} sats, have ${available} sats available. ` +\n (maxMintBalance > 0\n ? `Largest mint balance: ${maxMintBalance} sats from ${maxMintUrl}`\n : \"\")\n );\n this.name = \"InsufficientBalanceError\";\n }\n}\n\n/**\n * Error thrown when a provider returns an error response\n */\nexport class ProviderError extends Error {\n constructor(\n public baseUrl: string,\n public statusCode: number,\n message: string,\n public requestId?: string\n ) {\n super(\n `Provider ${baseUrl} returned ${statusCode}: ${message}` +\n (requestId ? ` (Request ID: ${requestId})` : \"\")\n );\n this.name = \"ProviderError\";\n }\n}\n\n/**\n * Error thrown when a mint is unreachable\n */\nexport class MintUnreachableError extends Error {\n constructor(public mintUrl: string) {\n super(\n `Your mint ${mintUrl} is unreachable or is blocking your IP. Please try again later or switch mints.`\n );\n this.name = \"MintUnreachableError\";\n }\n}\n\n/**\n * Error thrown when a token operation fails\n */\nexport class TokenOperationError extends Error {\n constructor(\n message: string,\n public operation: \"send\" | \"receive\" | \"refund\",\n public mintUrl?: string\n ) {\n super(message);\n this.name = \"TokenOperationError\";\n }\n}\n\n/**\n * Error thrown when provider failover fails\n */\nexport class FailoverError extends Error {\n constructor(\n public originalProvider: string,\n public failedProviders: string[],\n message?: string\n ) {\n super(\n message ||\n `All providers failed. Original: ${originalProvider}, Failed: ${failedProviders.join(\", \")}`\n );\n this.name = \"FailoverError\";\n }\n}\n\n/**\n * Error thrown when streaming response processing fails\n */\nexport class StreamingError extends Error {\n constructor(\n message: string,\n public finishReason?: string,\n public accumulatedContent?: string\n ) {\n super(message);\n this.name = \"StreamingError\";\n }\n}\n\n/**\n * Error thrown when model is not found on a provider\n */\nexport class ModelNotFoundError extends Error {\n constructor(public modelId: string, public baseUrl: string) {\n super(`Model '${modelId}' not found on provider ${baseUrl}`);\n this.name = \"ModelNotFoundError\";\n }\n}\n\n/**\n * Error thrown when provider bootstrap fails\n */\nexport class ProviderBootstrapError extends Error {\n constructor(\n public failedProviders: string[],\n message?: string\n ) {\n super(\n message || `Failed to bootstrap providers. Tried: ${failedProviders.join(\", \")}`\n );\n this.name = \"ProviderBootstrapError\";\n }\n}\n\n/**\n * Error thrown when no providers are available\n */\nexport class NoProvidersAvailableError extends Error {\n constructor() {\n super(\"No providers are available for model discovery\");\n this.name = \"NoProvidersAvailableError\";\n }\n}\n\n/**\n * Error thrown when mint discovery fails\n */\nexport class MintDiscoveryError extends Error {\n constructor(\n public baseUrl: string,\n message?: string\n ) {\n super(message || `Failed to discover mints from provider ${baseUrl}`);\n this.name = \"MintDiscoveryError\";\n }\n}\n","/**\n * ModelManager class for discovering, fetching, and managing models from providers\n * Core responsibility: fetching models from providers, caching them, and selecting the best option\n * (lowest cost) across multiple providers\n */\n\nimport type { Model } from \"../core/types\";\nimport type { DiscoveryAdapter, ProviderInfo } from \"./interfaces\";\nimport {\n NoProvidersAvailableError,\n ProviderBootstrapError,\n} from \"../core/errors\";\nimport { onlyEvents, RelayPool } from \"applesauce-relay\";\nimport { EventStore } from \"applesauce-core\";\nimport { tap } from \"rxjs\";\n\n/**\n * Configuration for ModelManager\n */\nexport interface ModelManagerConfig {\n /** URL to fetch provider directory from */\n providerDirectoryUrl?: string;\n /** Additional provider base URLs to include */\n includeProviderUrls?: string[];\n /** Provider base URLs to exclude */\n excludeProviderUrls?: string[];\n /** Cache TTL in milliseconds (default: 21 minutes) */\n cacheTTL?: number;\n}\n\n/**\n * ModelManager handles all model discovery and caching logic\n * Abstracts away storage details via DiscoveryAdapter\n */\nexport class ModelManager {\n private readonly cacheTTL: number;\n private readonly providerDirectoryUrl: string;\n private readonly includeProviderUrls: string[];\n private readonly excludeProviderUrls: string[];\n\n constructor(\n private adapter: DiscoveryAdapter,\n config: ModelManagerConfig = {}\n ) {\n this.providerDirectoryUrl =\n config.providerDirectoryUrl || \"https://api.routstr.com/v1/providers/\";\n this.cacheTTL = config.cacheTTL || 210 * 60 * 1000; // 21 minutes\n this.includeProviderUrls = config.includeProviderUrls || [];\n this.excludeProviderUrls = config.excludeProviderUrls || [];\n }\n\n /**\n * Get the list of bootstrapped provider base URLs\n * @returns Array of provider base URLs\n */\n getBaseUrls(): string[] {\n return this.adapter.getBaseUrlsList();\n }\n\n static async init(\n adapter: DiscoveryAdapter,\n config: ModelManagerConfig = {},\n options: { torMode?: boolean; forceRefresh?: boolean } = {}\n ): Promise<ModelManager> {\n const manager = new ModelManager(adapter, config);\n const torMode = options.torMode ?? false;\n const forceRefresh = options.forceRefresh ?? false;\n const providers = await manager.bootstrapProviders(torMode);\n await manager.fetchModels(providers, forceRefresh);\n return manager;\n }\n\n /**\n * Bootstrap provider list from the provider directory\n * First tries to fetch from Nostr (kind 30421), falls back to HTTP\n * @param torMode Whether running in Tor context\n * @returns Array of provider base URLs\n * @throws ProviderBootstrapError if all providers fail to fetch\n */\n async bootstrapProviders(torMode: boolean = false): Promise<string[]> {\n // First try cache\n const cachedUrls = this.adapter.getBaseUrlsList();\n if (cachedUrls.length > 0) {\n const lastUpdate = this.adapter.getBaseUrlsLastUpdate();\n const cacheValid = lastUpdate && Date.now() - lastUpdate <= this.cacheTTL;\n if (cacheValid) {\n return this.filterBaseUrlsForTor(cachedUrls, torMode);\n }\n }\n\n // Try Nostr first (kind 38421)\n try {\n const nostrProviders = await this.bootstrapFromNostr(38421, torMode);\n if (nostrProviders.length > 0) {\n const filtered = this.filterBaseUrlsForTor(nostrProviders, torMode);\n this.adapter.setBaseUrlsList(filtered);\n this.adapter.setBaseUrlsLastUpdate(Date.now());\n await this.fetchRoutstr21Models();\n return filtered;\n }\n } catch (e) {\n console.warn(\"Nostr bootstrap failed, falling back to HTTP:\", e);\n }\n\n // Fall back to HTTP\n return this.bootstrapFromHttp(torMode);\n }\n\n /**\n * Bootstrap providers from Nostr network (kind 30421)\n * @param kind The Nostr kind to fetch\n * @param torMode Whether running in Tor context\n * @returns Array of provider base URLs\n */\n private async bootstrapFromNostr(\n kind: number,\n torMode: boolean\n ): Promise<string[]> {\n const DEFAULT_RELAYS = [\n \"wss://relay.primal.net\",\n \"wss://nos.lol\",\n \"wss://relay.routstr.com\",\n ];\n\n const pool = new RelayPool();\n const localEventStore = new EventStore();\n\n const timeoutMs = 5000;\n\n await new Promise<void>((resolve) => {\n pool\n .req(DEFAULT_RELAYS, {\n kinds: [kind],\n limit: 100,\n })\n .pipe(\n onlyEvents(),\n tap((event) => {\n localEventStore.add(event);\n })\n )\n .subscribe({\n complete: () => {\n resolve();\n },\n });\n\n setTimeout(() => {\n resolve();\n }, timeoutMs);\n });\n\n const timeline = localEventStore.getTimeline({ kinds: [kind] });\n\n const bases = new Set<string>();\n\n for (const event of timeline) {\n const eventUrls: string[] = [];\n\n for (const tag of event.tags) {\n if (tag[0] === \"u\" && typeof tag[1] === \"string\") {\n eventUrls.push(tag[1]);\n }\n }\n\n if (eventUrls.length > 0) {\n for (const url of eventUrls) {\n const normalized = this.normalizeUrl(url);\n if (!torMode || normalized.includes(\".onion\")) {\n bases.add(normalized);\n }\n }\n continue;\n }\n\n try {\n const content = JSON.parse(event.content);\n const providers = Array.isArray(content)\n ? content\n : content.providers || [];\n\n for (const p of providers) {\n const endpoints = this.getProviderEndpoints(p, torMode);\n for (const endpoint of endpoints) {\n bases.add(endpoint);\n }\n }\n } catch {\n try {\n const providers = JSON.parse(event.content);\n if (Array.isArray(providers)) {\n for (const p of providers) {\n const endpoints = this.getProviderEndpoints(p, torMode);\n for (const endpoint of endpoints) {\n bases.add(endpoint);\n }\n }\n }\n } catch {\n console.warn(\n \"[NostrBootstrap] Failed to parse Nostr event content:\",\n event.id\n );\n }\n }\n }\n\n // Add additional configured providers\n for (const url of this.includeProviderUrls) {\n const normalized = this.normalizeUrl(url);\n if (!torMode || normalized.includes(\".onion\")) {\n bases.add(normalized);\n }\n }\n\n const excluded = new Set(\n this.excludeProviderUrls.map((url) => this.normalizeUrl(url))\n );\n\n const result = Array.from(bases).filter((base) => !excluded.has(base));\n\n return result;\n }\n\n /**\n * Bootstrap providers from HTTP endpoint\n * @param torMode Whether running in Tor context\n * @returns Array of provider base URLs\n */\n private async bootstrapFromHttp(torMode: boolean): Promise<string[]> {\n try {\n const res = await fetch(this.providerDirectoryUrl);\n if (!res.ok) {\n throw new Error(`Failed to fetch providers: ${res.status}`);\n }\n\n const data = await res.json();\n const providers = Array.isArray(data?.providers) ? data.providers : [];\n\n const bases = new Set<string>();\n for (const p of providers) {\n const endpoints = this.getProviderEndpoints(p, torMode);\n for (const endpoint of endpoints) {\n bases.add(endpoint);\n }\n }\n\n for (const url of this.includeProviderUrls) {\n const normalized = this.normalizeUrl(url);\n if (!torMode || normalized.includes(\".onion\")) {\n bases.add(normalized);\n }\n }\n\n const excluded = new Set(\n this.excludeProviderUrls.map((url) => this.normalizeUrl(url))\n );\n\n const list = Array.from(bases).filter((base) => !excluded.has(base));\n\n if (list.length > 0) {\n this.adapter.setBaseUrlsList(list);\n this.adapter.setBaseUrlsLastUpdate(Date.now());\n await this.fetchRoutstr21Models();\n }\n\n return list;\n } catch (e) {\n console.error(\"Failed to bootstrap providers\", e);\n throw new ProviderBootstrapError([], `Provider bootstrap failed: ${e}`);\n }\n }\n\n /**\n * Fetch models from all providers and select best-priced options\n * Uses cache if available and not expired\n * @param baseUrls List of provider base URLs to fetch from\n * @param forceRefresh Ignore cache and fetch fresh data\n * @returns Array of unique models with best prices selected\n */\n async fetchModels(\n baseUrls: string[],\n forceRefresh: boolean = false\n ): Promise<Model[]> {\n if (baseUrls.length === 0) {\n throw new NoProvidersAvailableError();\n }\n\n const bestById = new Map<string, { model: Model; base: string }>();\n const modelsFromAllProviders: Record<string, Model[]> = {};\n const disabledProviders = this.adapter.getDisabledProviders();\n\n // Helper to estimate minimum cost for a model\n const estimateMinCost = (m: Model): number => {\n return m?.sats_pricing?.completion ?? 0;\n };\n\n // Fetch from all providers in parallel\n const fetchPromises = baseUrls.map(async (url) => {\n const base = url.endsWith(\"/\") ? url : `${url}/`;\n try {\n // Check cache if not forcing refresh\n let list: Model[];\n\n if (!forceRefresh) {\n const lastUpdate = this.adapter.getProviderLastUpdate(base);\n const cacheValid =\n lastUpdate && Date.now() - lastUpdate <= this.cacheTTL;\n\n if (cacheValid) {\n const cachedModels = this.adapter.getCachedModels();\n const cachedList = cachedModels[base] || [];\n list = cachedList;\n } else {\n // Cache expired or doesn't exist, fetch fresh\n list = await this.fetchModelsFromProvider(base);\n }\n } else {\n // Force refresh\n list = await this.fetchModelsFromProvider(base);\n }\n\n modelsFromAllProviders[base] = list;\n this.adapter.setProviderLastUpdate(base, Date.now());\n\n // Update best-priced models if provider not disabled\n if (!disabledProviders.includes(base)) {\n for (const m of list) {\n const existing = bestById.get(m.id);\n\n // Skip models without sats pricing\n if (!m.sats_pricing) continue;\n\n if (!existing) {\n bestById.set(m.id, { model: m, base });\n continue;\n }\n\n // Replace if this provider has lower cost\n const currentCost = estimateMinCost(m);\n const existingCost = estimateMinCost(existing.model);\n if (currentCost < existingCost && m.sats_pricing) {\n bestById.set(m.id, { model: m, base });\n }\n }\n }\n\n return { success: true, base, list };\n } catch (error) {\n if (this.isProviderDownError(error)) {\n console.warn(`Provider ${base} is down right now.`);\n } else {\n console.warn(`Failed to fetch models from ${base}:`, error);\n }\n this.adapter.setProviderLastUpdate(base, Date.now());\n return { success: false, base };\n }\n });\n\n // Wait for all to complete\n await Promise.allSettled(fetchPromises);\n\n // Cache all provider results\n const existingCache = this.adapter.getCachedModels();\n this.adapter.setCachedModels({\n ...existingCache,\n ...modelsFromAllProviders,\n });\n\n // Return combined models array\n return Array.from(bestById.values()).map((v) => v.model);\n }\n\n /**\n * Fetch models from a single provider\n * @param baseUrl Provider base URL\n * @returns Array of models from provider\n */\n private async fetchModelsFromProvider(baseUrl: string): Promise<Model[]> {\n const res = await fetch(`${baseUrl}v1/models`);\n if (!res.ok) {\n throw new Error(`Failed to fetch models: ${res.status}`);\n }\n\n const json = await res.json();\n const list = Array.isArray(json?.data)\n ? json.data.map((m: Model) => ({\n ...m,\n id: m.id.split(\"/\").pop() || m.id,\n }))\n : [];\n\n return list;\n }\n\n private isProviderDownError(error: unknown): boolean {\n if (!(error instanceof Error)) return false;\n const msg = error.message.toLowerCase();\n if (msg.includes(\"fetch failed\")) return true;\n if (msg.includes(\"502\")) return true;\n if (msg.includes(\"503\")) return true;\n if (msg.includes(\"504\")) return true;\n const cause = error.cause as { code?: string } | undefined;\n return cause?.code === \"ENOTFOUND\";\n }\n\n /**\n * Get all cached models from all providers\n * @returns Record mapping baseUrl -> models\n */\n getAllCachedModels(): Record<string, Model[]> {\n return this.adapter.getCachedModels();\n }\n\n /**\n * Clear cache for a specific provider\n * @param baseUrl Provider base URL\n */\n clearProviderCache(baseUrl: string): void {\n const base = baseUrl.endsWith(\"/\") ? baseUrl : `${baseUrl}/`;\n const cached = this.adapter.getCachedModels();\n delete cached[base];\n this.adapter.setCachedModels(cached);\n this.adapter.setProviderLastUpdate(base, 0);\n }\n\n /**\n * Clear all model caches\n */\n clearAllCache(): void {\n this.adapter.setCachedModels({});\n }\n\n /**\n * Filter base URLs based on Tor context\n * @param baseUrls Provider URLs to filter\n * @param torMode Whether in Tor context\n * @returns Filtered URLs appropriate for Tor mode\n */\n filterBaseUrlsForTor(baseUrls: string[], torMode: boolean): string[] {\n if (!torMode) {\n // In normal mode, exclude onion URLs\n return baseUrls.filter((url) => !url.includes(\".onion\"));\n }\n // In Tor mode, only include onion URLs\n return baseUrls.filter((url) => url.includes(\".onion\"));\n }\n\n /**\n * Get provider endpoints from provider info\n * @param provider Provider object from directory\n * @param torMode Whether in Tor context\n * @returns Array of endpoint URLs\n */\n private getProviderEndpoints(provider: any, torMode: boolean): string[] {\n const endpoints: string[] = [];\n\n if (torMode && provider.onion_url) {\n endpoints.push(this.normalizeUrl(provider.onion_url));\n } else if (provider.endpoint_url) {\n endpoints.push(this.normalizeUrl(provider.endpoint_url));\n }\n\n return endpoints;\n }\n\n /**\n * Normalize provider URL with trailing slash\n * @param url URL to normalize\n * @returns Normalized URL\n */\n private normalizeUrl(url: string): string {\n if (!url.startsWith(\"http\")) {\n url = `https://${url}`;\n }\n return url.endsWith(\"/\") ? url : `${url}/`;\n }\n\n /**\n * Fetch routstr21 models from Nostr network (kind 38423)\n * @returns Array of model IDs or empty array if not found\n */\n async fetchRoutstr21Models(): Promise<string[]> {\n const DEFAULT_RELAYS = [\n \"wss://relay.primal.net\",\n \"wss://nos.lol\",\n \"wss://relay.routstr.com\",\n ];\n\n const pool = new RelayPool();\n const localEventStore = new EventStore();\n\n const timeoutMs = 5000;\n\n await new Promise<void>((resolve) => {\n pool\n .req(DEFAULT_RELAYS, {\n kinds: [38423],\n \"#d\": [\"routstr-21-models\"],\n limit: 1,\n authors: [\"4ad6fa2d16e2a9b576c863b4cf7404a70d4dc320c0c447d10ad6ff58993eacc8\"]\n })\n .pipe(\n onlyEvents(),\n tap((event) => {\n localEventStore.add(event);\n })\n )\n .subscribe({\n complete: () => {\n resolve();\n },\n });\n\n setTimeout(() => {\n resolve();\n }, timeoutMs);\n });\n\n const timeline = localEventStore.getTimeline({ kinds: [38423] });\n\n if (timeline.length === 0) {\n return [];\n }\n\n const event = timeline[0];\n\n try {\n const content = JSON.parse(event.content);\n const models = Array.isArray(content?.models) ? content.models : [];\n this.adapter.setRoutstr21Models(models);\n return models;\n } catch {\n console.warn(\n \"[Routstr21Models] Failed to parse Nostr event content:\",\n event.id\n );\n return [];\n }\n }\n}\n","/**\n * MintDiscovery class for discovering mints and provider info\n * Core responsibility: fetching mint information from providers and caching it\n */\n\nimport type { DiscoveryAdapter, ProviderInfo } from \"./interfaces\";\n\n/**\n * Configuration for MintDiscovery\n */\nexport interface MintDiscoveryConfig {\n /** Cache TTL in milliseconds (default: 21 minutes) */\n cacheTTL?: number;\n}\n\n/**\n * MintDiscovery handles mint and provider info discovery\n * Abstracts away storage details via DiscoveryAdapter\n */\nexport class MintDiscovery {\n private readonly cacheTTL: number;\n\n constructor(\n private adapter: DiscoveryAdapter,\n config: MintDiscoveryConfig = {}\n ) {\n this.cacheTTL = config.cacheTTL || 21 * 60 * 1000; // 21 minutes\n }\n\n /**\n * Fetch mints from all providers via their /v1/info endpoints\n * Caches mints and full provider info for later access\n * @param baseUrls List of provider base URLs to fetch from\n * @returns Object with mints and provider info from all providers\n */\n async discoverMints(\n baseUrls: string[],\n options: { forceRefresh?: boolean } = {}\n ): Promise<{\n mintsFromProviders: Record<string, string[]>;\n infoFromProviders: Record<string, ProviderInfo>;\n }> {\n if (baseUrls.length === 0) {\n return { mintsFromProviders: {}, infoFromProviders: {} };\n }\n\n const mintsFromAllProviders: Record<string, string[]> = {};\n const infoFromAllProviders: Record<string, ProviderInfo> = {};\n const forceRefresh = options.forceRefresh ?? false;\n\n // Fetch info from each provider\n const fetchPromises = baseUrls.map(async (url) => {\n const base = url.endsWith(\"/\") ? url : `${url}/`;\n try {\n if (!forceRefresh) {\n const lastUpdate = this.adapter.getProviderLastUpdate(base);\n const cacheValid =\n lastUpdate && Date.now() - lastUpdate <= this.cacheTTL;\n if (cacheValid) {\n const cachedMints = this.adapter.getCachedMints()[base] || [];\n const cachedInfo = this.adapter.getCachedProviderInfo()[base];\n mintsFromAllProviders[base] = cachedMints;\n if (cachedInfo) {\n infoFromAllProviders[base] = cachedInfo;\n }\n return {\n success: true,\n base,\n mints: cachedMints,\n info: cachedInfo,\n };\n }\n }\n\n const res = await fetch(`${base}v1/info`);\n if (!res.ok) {\n throw new Error(`Failed to fetch info: ${res.status}`);\n }\n\n const json = await res.json();\n\n // Extract mints array from response\n const mints: string[] = Array.isArray(json?.mints) ? json.mints : [];\n\n // Normalize mint URLs (remove trailing slashes for consistency)\n const normalizedMints = mints.map((mint) =>\n mint.endsWith(\"/\") ? mint.slice(0, -1) : mint\n );\n\n // Save provider mints and full info\n mintsFromAllProviders[base] = normalizedMints;\n infoFromAllProviders[base] = json;\n this.adapter.setProviderLastUpdate(base, Date.now());\n\n return { success: true, base, mints: normalizedMints, info: json };\n } catch (error) {\n this.adapter.setProviderLastUpdate(base, Date.now());\n if (this.isProviderDownError(error)) {\n console.warn(`Provider ${base} is down right now.`);\n } else {\n console.warn(`Failed to fetch mints from ${base}:`, error);\n }\n return { success: false, base, mints: [], info: null };\n }\n });\n\n // Wait for all to complete (but allow individual failures)\n const results = await Promise.allSettled(fetchPromises);\n\n // Handle results\n for (const result of results) {\n if (result.status === \"fulfilled\") {\n const { base, mints, info } = result.value;\n mintsFromAllProviders[base] = mints;\n if (info) {\n infoFromAllProviders[base] = info;\n }\n } else {\n // Log but don't throw - continue with partial results\n console.error(\"Mint discovery error:\", result.reason);\n }\n }\n\n // Cache all results\n try {\n this.adapter.setCachedMints(mintsFromAllProviders);\n this.adapter.setCachedProviderInfo(infoFromAllProviders);\n } catch (error) {\n console.error(\"Error caching mint discovery results:\", error);\n }\n\n return {\n mintsFromProviders: mintsFromAllProviders,\n infoFromProviders: infoFromAllProviders,\n };\n }\n\n /**\n * Get cached mints from all providers\n * @returns Record mapping baseUrl -> mint URLs\n */\n getCachedMints(): Record<string, string[]> {\n return this.adapter.getCachedMints();\n }\n\n /**\n * Get cached provider info from all providers\n * @returns Record mapping baseUrl -> provider info\n */\n getCachedProviderInfo(): Record<string, ProviderInfo> {\n return this.adapter.getCachedProviderInfo();\n }\n\n /**\n * Get mints for a specific provider\n * @param baseUrl Provider base URL\n * @returns Array of mint URLs for the provider\n */\n getProviderMints(baseUrl: string): string[] {\n const normalized = baseUrl.endsWith(\"/\") ? baseUrl : `${baseUrl}/`;\n const allMints = this.getCachedMints();\n return allMints[normalized] || [];\n }\n\n /**\n * Get info for a specific provider\n * @param baseUrl Provider base URL\n * @returns Provider info object or null if not found\n */\n getProviderInfo(baseUrl: string): ProviderInfo | null {\n const normalized = baseUrl.endsWith(\"/\") ? baseUrl : `${baseUrl}/`;\n const allInfo = this.getCachedProviderInfo();\n return allInfo[normalized] || null;\n }\n\n /**\n * Clear mint cache for a specific provider\n * @param baseUrl Provider base URL\n */\n clearProviderMintCache(baseUrl: string): void {\n const normalized = baseUrl.endsWith(\"/\") ? baseUrl : `${baseUrl}/`;\n\n const mints = this.getCachedMints();\n delete mints[normalized];\n this.adapter.setCachedMints(mints);\n\n const info = this.getCachedProviderInfo();\n delete info[normalized];\n this.adapter.setCachedProviderInfo(info);\n }\n\n /**\n * Clear all mint caches\n */\n clearAllCache(): void {\n this.adapter.setCachedMints({});\n this.adapter.setCachedProviderInfo({});\n }\n\n private isProviderDownError(error: unknown): boolean {\n if (!(error instanceof Error)) return false;\n const msg = error.message.toLowerCase();\n if (msg.includes(\"fetch failed\")) return true;\n if (msg.includes(\"502\")) return true;\n if (msg.includes(\"503\")) return true;\n if (msg.includes(\"504\")) return true;\n const cause = error.cause as { code?: string } | undefined;\n if (cause?.code === \"ENOTFOUND\") return true;\n return false;\n }\n}\n"]}