@routstr/sdk 0.3.8 → 0.3.10

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 (65) hide show
  1. package/dist/browser.d.mts +12 -0
  2. package/dist/browser.d.ts +12 -0
  3. package/dist/browser.js +6278 -0
  4. package/dist/browser.js.map +1 -0
  5. package/dist/browser.mjs +6230 -0
  6. package/dist/browser.mjs.map +1 -0
  7. package/dist/bun.d.mts +29 -0
  8. package/dist/bun.d.ts +29 -0
  9. package/dist/bun.js +6586 -0
  10. package/dist/bun.js.map +1 -0
  11. package/dist/bun.mjs +6532 -0
  12. package/dist/bun.mjs.map +1 -0
  13. package/dist/bunSqlite-BMTseLIz.d.ts +18 -0
  14. package/dist/bunSqlite-D6AreVE2.d.mts +18 -0
  15. package/dist/client/index.d.mts +63 -41
  16. package/dist/client/index.d.ts +63 -41
  17. package/dist/client/index.js +1223 -1658
  18. package/dist/client/index.js.map +1 -1
  19. package/dist/client/index.mjs +1223 -1659
  20. package/dist/client/index.mjs.map +1 -1
  21. package/dist/discovery/index.d.mts +67 -3
  22. package/dist/discovery/index.d.ts +67 -3
  23. package/dist/discovery/index.js +242 -79
  24. package/dist/discovery/index.js.map +1 -1
  25. package/dist/discovery/index.mjs +242 -79
  26. package/dist/discovery/index.mjs.map +1 -1
  27. package/dist/index.d.mts +5 -4
  28. package/dist/index.d.ts +5 -4
  29. package/dist/index.js +1975 -2004
  30. package/dist/index.js.map +1 -1
  31. package/dist/index.mjs +1973 -2001
  32. package/dist/index.mjs.map +1 -1
  33. package/dist/node.d.mts +22 -0
  34. package/dist/node.d.ts +22 -0
  35. package/dist/node.js +6651 -0
  36. package/dist/node.js.map +1 -0
  37. package/dist/node.mjs +6599 -0
  38. package/dist/node.mjs.map +1 -0
  39. package/dist/storage/bun.d.mts +16 -0
  40. package/dist/storage/bun.d.ts +16 -0
  41. package/dist/storage/bun.js +1801 -0
  42. package/dist/storage/bun.js.map +1 -0
  43. package/dist/storage/bun.mjs +1777 -0
  44. package/dist/storage/bun.mjs.map +1 -0
  45. package/dist/storage/index.d.mts +30 -30
  46. package/dist/storage/index.d.ts +30 -30
  47. package/dist/storage/index.js +393 -625
  48. package/dist/storage/index.js.map +1 -1
  49. package/dist/storage/index.mjs +392 -622
  50. package/dist/storage/index.mjs.map +1 -1
  51. package/dist/storage/node.d.mts +22 -0
  52. package/dist/storage/node.d.ts +22 -0
  53. package/dist/storage/node.js +1864 -0
  54. package/dist/storage/node.js.map +1 -0
  55. package/dist/storage/node.mjs +1842 -0
  56. package/dist/storage/node.mjs.map +1 -0
  57. package/dist/{store-C6dfj1cc.d.mts → store-BiuM2V9N.d.mts} +14 -0
  58. package/dist/{store-58VcEUoA.d.ts → store-C8MZlfuz.d.ts} +14 -0
  59. package/dist/wallet/index.d.mts +4 -0
  60. package/dist/wallet/index.d.ts +4 -0
  61. package/dist/wallet/index.js +11 -4
  62. package/dist/wallet/index.js.map +1 -1
  63. package/dist/wallet/index.mjs +11 -4
  64. package/dist/wallet/index.mjs.map +1 -1
  65. package/package.json +28 -2
@@ -1,5 +1,6 @@
1
1
  import { D as DiscoveryAdapter } from '../interfaces-Cv1k2EUK.mjs';
2
2
  import { S as SdkLogger, e as Model, k as ProviderInfo } from '../types-_21yYFZG.mjs';
3
+ import { IEventDatabase, EventStore } from 'applesauce-core';
3
4
 
4
5
  /**
5
6
  * ModelManager class for discovering, fetching, and managing models from providers
@@ -7,6 +8,18 @@ import { S as SdkLogger, e as Model, k as ProviderInfo } from '../types-_21yYFZG
7
8
  * (lowest cost) across multiple providers
8
9
  */
9
10
 
11
+ type SqliteStatement = {
12
+ run?: (...params: any[]) => unknown;
13
+ get?: (...params: any[]) => any;
14
+ };
15
+ type PersistentEventDatabase = IEventDatabase & {
16
+ db?: {
17
+ exec: (sql: string) => void;
18
+ prepare: (sql: string) => SqliteStatement;
19
+ };
20
+ close?: () => void;
21
+ };
22
+ type PersistentEventDatabaseFactory = (dbPath: string) => Promise<PersistentEventDatabase> | PersistentEventDatabase;
10
23
  /**
11
24
  * Configuration for ModelManager
12
25
  */
@@ -17,12 +30,28 @@ interface ModelManagerConfig {
17
30
  includeProviderUrls?: string[];
18
31
  /** Provider base URLs to exclude */
19
32
  excludeProviderUrls?: string[];
20
- /** Cache TTL in milliseconds (default: 21 minutes) */
33
+ /** Cache TTL in milliseconds (default: 210 minutes) */
21
34
  cacheTTL?: number;
22
35
  /** Nostr pubkey for routstr review/model events (kind 38425/38423). Defaults to routstr's key. */
23
36
  routstrPubkey?: string;
37
+ /** Nostr relay URLs for provider/model discovery.
38
+ * When set, these relays are used for all Nostr queries (kinds 38421, 38423, 38425).
39
+ * When unset, each method uses its own default relay set. */
40
+ nostrRelays?: string[];
24
41
  /** Optional injectable logger */
25
42
  logger?: SdkLogger;
43
+ /** Path to database for persistent Nostr event storage.
44
+ * If provided, events fetched by ModelManager from relays (kinds 38421,
45
+ * 38423, 38425) are persisted and survive process restarts. The underlying
46
+ * EventStore can also be accessed for advanced/manual event management.
47
+ *
48
+ * Runtime-specific SQLite implementations are intentionally not imported by
49
+ * the browser-safe default SDK entrypoint. Use @routstr/sdk/node or
50
+ * @routstr/sdk/bun to get a ModelManager preconfigured with a SQLite-backed
51
+ * persistentEventDatabaseFactory, or inject your own factory here. */
52
+ eventStoreDbPath?: string;
53
+ /** Factory used with eventStoreDbPath to create the persistent event DB. */
54
+ persistentEventDatabaseFactory?: PersistentEventDatabaseFactory;
26
55
  }
27
56
  /**
28
57
  * ModelManager handles all model discovery and caching logic
@@ -35,14 +64,44 @@ declare class ModelManager {
35
64
  private readonly includeProviderUrls;
36
65
  private readonly excludeProviderUrls;
37
66
  private readonly routstrPubkey;
67
+ private readonly nostrRelays;
38
68
  private readonly logger;
39
69
  private providerNodePubkeysByUrl;
70
+ /** Persistent event store for relay-fetched events (null if not configured/initialized) */
71
+ private eventStore;
72
+ private eventStoreDb;
73
+ private eventStoreInitPromise;
74
+ private readonly eventStoreDbPath?;
75
+ private readonly persistentEventDatabaseFactory?;
40
76
  constructor(adapter: DiscoveryAdapter, config?: ModelManagerConfig);
41
77
  /**
42
78
  * Get the list of bootstrapped provider base URLs
43
79
  * @returns Array of provider base URLs
44
80
  */
45
81
  getBaseUrls(): string[];
82
+ /**
83
+ * Lazily initialize the persistent event store.
84
+ * Returns null if no eventStoreDbPath was provided.
85
+ */
86
+ private ensureEventStore;
87
+ /**
88
+ * Get the persistent event store, initializing it if configured.
89
+ * Returns null if no eventStoreDbPath was provided.
90
+ */
91
+ getEventStore(): Promise<EventStore | null>;
92
+ private createPersistentEventDatabase;
93
+ /** Close the persistent event store database handle, if configured. */
94
+ closeEventStore(): void;
95
+ private initializeEventStoreMetadata;
96
+ private markEventFetched;
97
+ private getEventFetchedAt;
98
+ /**
99
+ * Check the persistent event store for fresh cached events.
100
+ * Returns events from SQLite if they were fetched within `maxAge`, otherwise
101
+ * returns empty array (caller should hit relays). Events without local fetch
102
+ * metadata fall back to Nostr created_at for backwards compatibility.
103
+ */
104
+ private getCachedNostrEvents;
46
105
  static init(adapter: DiscoveryAdapter, config?: ModelManagerConfig, options?: {
47
106
  torMode?: boolean;
48
107
  forceRefresh?: boolean;
@@ -57,7 +116,12 @@ declare class ModelManager {
57
116
  */
58
117
  bootstrapProviders(torMode?: boolean, forceRefresh?: boolean): Promise<string[]>;
59
118
  /**
60
- * Bootstrap providers from Nostr network (kind 30421)
119
+ * Resolve Nostr relay URLs for a given use case.
120
+ * Returns user-configured relays if set, otherwise the provided defaults.
121
+ */
122
+ private getNostrRelays;
123
+ /**
124
+ * Bootstrap providers from Nostr network (kind 38421)
61
125
  * @param kind The Nostr kind to fetch
62
126
  * @param torMode Whether running in Tor context
63
127
  * @returns Array of provider base URLs
@@ -81,7 +145,7 @@ declare class ModelManager {
81
145
  * @param baseUrls Current provider base URLs to evaluate
82
146
  * @returns Array of provider base URLs disabled by the review set
83
147
  */
84
- syncReviewedProvidersFromNostr(baseUrls?: string[], providerNodes?: Map<string, Set<string>>): Promise<string[]>;
148
+ syncReviewedProvidersFromNostr(baseUrls?: string[], providerNodes?: Map<string, Set<string>>, forceRefresh?: boolean): Promise<string[]>;
85
149
  private addProviderNode;
86
150
  /**
87
151
  * Fetch models from all providers and select best-priced options
@@ -1,5 +1,6 @@
1
1
  import { D as DiscoveryAdapter } from '../interfaces-iL7CWeG5.js';
2
2
  import { S as SdkLogger, e as Model, k as ProviderInfo } from '../types-_21yYFZG.js';
3
+ import { IEventDatabase, EventStore } from 'applesauce-core';
3
4
 
4
5
  /**
5
6
  * ModelManager class for discovering, fetching, and managing models from providers
@@ -7,6 +8,18 @@ import { S as SdkLogger, e as Model, k as ProviderInfo } from '../types-_21yYFZG
7
8
  * (lowest cost) across multiple providers
8
9
  */
9
10
 
11
+ type SqliteStatement = {
12
+ run?: (...params: any[]) => unknown;
13
+ get?: (...params: any[]) => any;
14
+ };
15
+ type PersistentEventDatabase = IEventDatabase & {
16
+ db?: {
17
+ exec: (sql: string) => void;
18
+ prepare: (sql: string) => SqliteStatement;
19
+ };
20
+ close?: () => void;
21
+ };
22
+ type PersistentEventDatabaseFactory = (dbPath: string) => Promise<PersistentEventDatabase> | PersistentEventDatabase;
10
23
  /**
11
24
  * Configuration for ModelManager
12
25
  */
@@ -17,12 +30,28 @@ interface ModelManagerConfig {
17
30
  includeProviderUrls?: string[];
18
31
  /** Provider base URLs to exclude */
19
32
  excludeProviderUrls?: string[];
20
- /** Cache TTL in milliseconds (default: 21 minutes) */
33
+ /** Cache TTL in milliseconds (default: 210 minutes) */
21
34
  cacheTTL?: number;
22
35
  /** Nostr pubkey for routstr review/model events (kind 38425/38423). Defaults to routstr's key. */
23
36
  routstrPubkey?: string;
37
+ /** Nostr relay URLs for provider/model discovery.
38
+ * When set, these relays are used for all Nostr queries (kinds 38421, 38423, 38425).
39
+ * When unset, each method uses its own default relay set. */
40
+ nostrRelays?: string[];
24
41
  /** Optional injectable logger */
25
42
  logger?: SdkLogger;
43
+ /** Path to database for persistent Nostr event storage.
44
+ * If provided, events fetched by ModelManager from relays (kinds 38421,
45
+ * 38423, 38425) are persisted and survive process restarts. The underlying
46
+ * EventStore can also be accessed for advanced/manual event management.
47
+ *
48
+ * Runtime-specific SQLite implementations are intentionally not imported by
49
+ * the browser-safe default SDK entrypoint. Use @routstr/sdk/node or
50
+ * @routstr/sdk/bun to get a ModelManager preconfigured with a SQLite-backed
51
+ * persistentEventDatabaseFactory, or inject your own factory here. */
52
+ eventStoreDbPath?: string;
53
+ /** Factory used with eventStoreDbPath to create the persistent event DB. */
54
+ persistentEventDatabaseFactory?: PersistentEventDatabaseFactory;
26
55
  }
27
56
  /**
28
57
  * ModelManager handles all model discovery and caching logic
@@ -35,14 +64,44 @@ declare class ModelManager {
35
64
  private readonly includeProviderUrls;
36
65
  private readonly excludeProviderUrls;
37
66
  private readonly routstrPubkey;
67
+ private readonly nostrRelays;
38
68
  private readonly logger;
39
69
  private providerNodePubkeysByUrl;
70
+ /** Persistent event store for relay-fetched events (null if not configured/initialized) */
71
+ private eventStore;
72
+ private eventStoreDb;
73
+ private eventStoreInitPromise;
74
+ private readonly eventStoreDbPath?;
75
+ private readonly persistentEventDatabaseFactory?;
40
76
  constructor(adapter: DiscoveryAdapter, config?: ModelManagerConfig);
41
77
  /**
42
78
  * Get the list of bootstrapped provider base URLs
43
79
  * @returns Array of provider base URLs
44
80
  */
45
81
  getBaseUrls(): string[];
82
+ /**
83
+ * Lazily initialize the persistent event store.
84
+ * Returns null if no eventStoreDbPath was provided.
85
+ */
86
+ private ensureEventStore;
87
+ /**
88
+ * Get the persistent event store, initializing it if configured.
89
+ * Returns null if no eventStoreDbPath was provided.
90
+ */
91
+ getEventStore(): Promise<EventStore | null>;
92
+ private createPersistentEventDatabase;
93
+ /** Close the persistent event store database handle, if configured. */
94
+ closeEventStore(): void;
95
+ private initializeEventStoreMetadata;
96
+ private markEventFetched;
97
+ private getEventFetchedAt;
98
+ /**
99
+ * Check the persistent event store for fresh cached events.
100
+ * Returns events from SQLite if they were fetched within `maxAge`, otherwise
101
+ * returns empty array (caller should hit relays). Events without local fetch
102
+ * metadata fall back to Nostr created_at for backwards compatibility.
103
+ */
104
+ private getCachedNostrEvents;
46
105
  static init(adapter: DiscoveryAdapter, config?: ModelManagerConfig, options?: {
47
106
  torMode?: boolean;
48
107
  forceRefresh?: boolean;
@@ -57,7 +116,12 @@ declare class ModelManager {
57
116
  */
58
117
  bootstrapProviders(torMode?: boolean, forceRefresh?: boolean): Promise<string[]>;
59
118
  /**
60
- * Bootstrap providers from Nostr network (kind 30421)
119
+ * Resolve Nostr relay URLs for a given use case.
120
+ * Returns user-configured relays if set, otherwise the provided defaults.
121
+ */
122
+ private getNostrRelays;
123
+ /**
124
+ * Bootstrap providers from Nostr network (kind 38421)
61
125
  * @param kind The Nostr kind to fetch
62
126
  * @param torMode Whether running in Tor context
63
127
  * @returns Array of provider base URLs
@@ -81,7 +145,7 @@ declare class ModelManager {
81
145
  * @param baseUrls Current provider base URLs to evaluate
82
146
  * @returns Array of provider base URLs disabled by the review set
83
147
  */
84
- syncReviewedProvidersFromNostr(baseUrls?: string[], providerNodes?: Map<string, Set<string>>): Promise<string[]>;
148
+ syncReviewedProvidersFromNostr(baseUrls?: string[], providerNodes?: Map<string, Set<string>>, forceRefresh?: boolean): Promise<string[]>;
85
149
  private addProviderNode;
86
150
  /**
87
151
  * Fetch models from all providers and select best-priced options
@@ -42,7 +42,10 @@ var ModelManager = class _ModelManager {
42
42
  this.includeProviderUrls = config.includeProviderUrls || [];
43
43
  this.excludeProviderUrls = config.excludeProviderUrls || [];
44
44
  this.routstrPubkey = config.routstrPubkey || "4ad6fa2d16e2a9b576c863b4cf7404a70d4dc320c0c447d10ad6ff58993eacc8";
45
+ this.nostrRelays = config.nostrRelays;
45
46
  this.logger = (config.logger ?? consoleLogger).child("ModelManager");
47
+ this.eventStoreDbPath = config.eventStoreDbPath;
48
+ this.persistentEventDatabaseFactory = config.persistentEventDatabaseFactory;
46
49
  }
47
50
  adapter;
48
51
  cacheTTL;
@@ -50,8 +53,15 @@ var ModelManager = class _ModelManager {
50
53
  includeProviderUrls;
51
54
  excludeProviderUrls;
52
55
  routstrPubkey;
56
+ nostrRelays;
53
57
  logger;
54
58
  providerNodePubkeysByUrl = /* @__PURE__ */ new Map();
59
+ /** Persistent event store for relay-fetched events (null if not configured/initialized) */
60
+ eventStore = null;
61
+ eventStoreDb = null;
62
+ eventStoreInitPromise = null;
63
+ eventStoreDbPath;
64
+ persistentEventDatabaseFactory;
55
65
  /**
56
66
  * Get the list of bootstrapped provider base URLs
57
67
  * @returns Array of provider base URLs
@@ -59,6 +69,103 @@ var ModelManager = class _ModelManager {
59
69
  getBaseUrls() {
60
70
  return this.adapter.getBaseUrlsList();
61
71
  }
72
+ /**
73
+ * Lazily initialize the persistent event store.
74
+ * Returns null if no eventStoreDbPath was provided.
75
+ */
76
+ async ensureEventStore() {
77
+ if (!this.eventStoreDbPath) return null;
78
+ if (this.eventStore) return this.eventStore;
79
+ if (!this.eventStoreInitPromise) {
80
+ this.eventStoreInitPromise = (async () => {
81
+ try {
82
+ const db = await this.createPersistentEventDatabase();
83
+ this.eventStoreDb = db;
84
+ this.eventStore = new applesauceCore.EventStore({ database: db });
85
+ this.initializeEventStoreMetadata();
86
+ this.logger.log(
87
+ `Persistent event store initialized at ${this.eventStoreDbPath}`
88
+ );
89
+ return this.eventStore;
90
+ } catch (error) {
91
+ this.eventStoreInitPromise = null;
92
+ throw new Error(
93
+ `Persistent Nostr event storage requires a runtime-specific database factory. Use @routstr/sdk/node, @routstr/sdk/bun, inject persistentEventDatabaseFactory, or omit eventStoreDbPath. (${error})`
94
+ );
95
+ }
96
+ })();
97
+ }
98
+ return this.eventStoreInitPromise;
99
+ }
100
+ /**
101
+ * Get the persistent event store, initializing it if configured.
102
+ * Returns null if no eventStoreDbPath was provided.
103
+ */
104
+ async getEventStore() {
105
+ return this.ensureEventStore();
106
+ }
107
+ async createPersistentEventDatabase() {
108
+ if (!this.eventStoreDbPath) {
109
+ throw new Error("eventStoreDbPath is required");
110
+ }
111
+ if (!this.persistentEventDatabaseFactory) {
112
+ throw new Error(
113
+ "persistentEventDatabaseFactory is required. Import ModelManager from @routstr/sdk/node or @routstr/sdk/bun for SQLite-backed persistent event storage."
114
+ );
115
+ }
116
+ return this.persistentEventDatabaseFactory(this.eventStoreDbPath);
117
+ }
118
+ /** Close the persistent event store database handle, if configured. */
119
+ closeEventStore() {
120
+ this.eventStoreDb?.close?.();
121
+ this.eventStore = null;
122
+ this.eventStoreDb = null;
123
+ this.eventStoreInitPromise = null;
124
+ }
125
+ initializeEventStoreMetadata() {
126
+ this.eventStoreDb?.db?.exec(
127
+ `CREATE TABLE IF NOT EXISTS routstr_event_cache_metadata (
128
+ event_id TEXT PRIMARY KEY,
129
+ fetched_at INTEGER NOT NULL
130
+ )`
131
+ );
132
+ }
133
+ markEventFetched(event, fetchedAt = Date.now()) {
134
+ const db = this.eventStoreDb?.db;
135
+ if (!db) return;
136
+ db.prepare(
137
+ `INSERT INTO routstr_event_cache_metadata (event_id, fetched_at)
138
+ VALUES (?, ?)
139
+ ON CONFLICT(event_id) DO UPDATE SET fetched_at = excluded.fetched_at`
140
+ ).run?.(event.id, fetchedAt);
141
+ }
142
+ getEventFetchedAt(event) {
143
+ const db = this.eventStoreDb?.db;
144
+ if (!db) return void 0;
145
+ const row = db.prepare(
146
+ `SELECT fetched_at FROM routstr_event_cache_metadata WHERE event_id = ?`
147
+ ).get?.(event.id);
148
+ return typeof row?.fetched_at === "number" ? row.fetched_at : void 0;
149
+ }
150
+ /**
151
+ * Check the persistent event store for fresh cached events.
152
+ * Returns events from SQLite if they were fetched within `maxAge`, otherwise
153
+ * returns empty array (caller should hit relays). Events without local fetch
154
+ * metadata fall back to Nostr created_at for backwards compatibility.
155
+ */
156
+ async getCachedNostrEvents(filter, maxAge, forceRefresh = false) {
157
+ const eventStore = await this.ensureEventStore();
158
+ if (forceRefresh) return [];
159
+ if (!eventStore) return [];
160
+ const timeline = eventStore.getTimeline(filter);
161
+ if (timeline.length === 0) return [];
162
+ const cutoff = Date.now() - maxAge;
163
+ const freshest = Math.max(
164
+ ...timeline.map((e) => this.getEventFetchedAt(e) ?? e.created_at * 1e3)
165
+ );
166
+ if (freshest < cutoff) return [];
167
+ return timeline;
168
+ }
62
169
  static async init(adapter, config = {}, options = {}) {
63
170
  const manager = new _ModelManager(adapter, config);
64
171
  const torMode = options.torMode ?? false;
@@ -87,19 +194,31 @@ var ModelManager = class _ModelManager {
87
194
  torMode
88
195
  );
89
196
  await this.fetchRoutstr21Models(forceRefresh);
90
- await this.syncReviewedProvidersFromNostr(filteredCachedUrls);
197
+ await this.syncReviewedProvidersFromNostr(
198
+ filteredCachedUrls,
199
+ this.providerNodePubkeysByUrl,
200
+ forceRefresh
201
+ );
91
202
  return filteredCachedUrls;
92
203
  }
93
204
  }
94
205
  }
95
206
  try {
96
- const nostrProviders = await this.bootstrapFromNostr(38421, torMode);
207
+ const nostrProviders = await this.bootstrapFromNostr(
208
+ 38421,
209
+ torMode,
210
+ forceRefresh
211
+ );
97
212
  if (nostrProviders.length > 0) {
98
213
  const filtered = this.filterBaseUrlsForTor(nostrProviders, torMode);
99
214
  this.adapter.setBaseUrlsList(filtered);
100
215
  this.adapter.setBaseUrlsLastUpdate(Date.now());
101
216
  await this.fetchRoutstr21Models(forceRefresh);
102
- await this.syncReviewedProvidersFromNostr(filtered);
217
+ await this.syncReviewedProvidersFromNostr(
218
+ filtered,
219
+ this.providerNodePubkeysByUrl,
220
+ forceRefresh
221
+ );
103
222
  return filtered;
104
223
  }
105
224
  } catch (e) {
@@ -108,42 +227,59 @@ var ModelManager = class _ModelManager {
108
227
  return this.bootstrapFromHttp(torMode, forceRefresh);
109
228
  }
110
229
  /**
111
- * Bootstrap providers from Nostr network (kind 30421)
230
+ * Resolve Nostr relay URLs for a given use case.
231
+ * Returns user-configured relays if set, otherwise the provided defaults.
232
+ */
233
+ getNostrRelays(defaults) {
234
+ return this.nostrRelays && this.nostrRelays.length > 0 ? this.nostrRelays : defaults;
235
+ }
236
+ /**
237
+ * Bootstrap providers from Nostr network (kind 38421)
112
238
  * @param kind The Nostr kind to fetch
113
239
  * @param torMode Whether running in Tor context
114
240
  * @returns Array of provider base URLs
115
241
  */
116
- async bootstrapFromNostr(kind, torMode) {
117
- const DEFAULT_RELAYS = [
242
+ async bootstrapFromNostr(kind, torMode, forceRefresh = false) {
243
+ const relays = this.getNostrRelays([
118
244
  "wss://relay.primal.net",
119
245
  "wss://nos.lol",
120
246
  "wss://relay.damus.io"
121
- ];
122
- const pool = new applesauceRelay.RelayPool();
123
- const localEventStore = new applesauceCore.EventStore();
124
- const timeoutMs = 5e3;
125
- await new Promise((resolve) => {
126
- pool.req(DEFAULT_RELAYS, {
127
- kinds: [kind],
128
- limit: 100
129
- }).pipe(
130
- applesauceRelay.onlyEvents(),
131
- rxjs.tap((event) => {
132
- localEventStore.add(event);
133
- })
134
- ).subscribe({
135
- complete: () => {
247
+ ]);
248
+ const cached = await this.getCachedNostrEvents(
249
+ { kinds: [kind] },
250
+ this.cacheTTL,
251
+ forceRefresh
252
+ );
253
+ let sessionEvents = cached;
254
+ if (cached.length === 0) {
255
+ const pool = new applesauceRelay.RelayPool();
256
+ const timeoutMs = 5e3;
257
+ await new Promise((resolve) => {
258
+ pool.req(relays, {
259
+ kinds: [kind],
260
+ limit: 100
261
+ }).pipe(
262
+ applesauceRelay.onlyEvents(),
263
+ rxjs.tap((event) => {
264
+ sessionEvents.push(event);
265
+ this.eventStore?.add(event);
266
+ this.markEventFetched(event);
267
+ })
268
+ ).subscribe({
269
+ complete: () => {
270
+ resolve();
271
+ }
272
+ });
273
+ setTimeout(() => {
136
274
  resolve();
137
- }
275
+ }, timeoutMs);
138
276
  });
139
- setTimeout(() => {
140
- resolve();
141
- }, timeoutMs);
142
- });
143
- const timeline = localEventStore.getTimeline({ kinds: [kind] });
277
+ } else {
278
+ this.logger.log(`Using ${cached.length} cached kind ${kind} events from persistent store`);
279
+ }
144
280
  const bases = /* @__PURE__ */ new Set();
145
281
  this.providerNodePubkeysByUrl = /* @__PURE__ */ new Map();
146
- for (const event of timeline) {
282
+ for (const event of sessionEvents) {
147
283
  const eventUrls = [];
148
284
  for (const tag of event.tags) {
149
285
  if (tag[0] === "u" && typeof tag[1] === "string") {
@@ -251,7 +387,11 @@ var ModelManager = class _ModelManager {
251
387
  this.adapter.setBaseUrlsList(list);
252
388
  this.adapter.setBaseUrlsLastUpdate(Date.now());
253
389
  await this.fetchRoutstr21Models(forceRefresh);
254
- await this.syncReviewedProvidersFromNostr(list);
390
+ await this.syncReviewedProvidersFromNostr(
391
+ list,
392
+ this.providerNodePubkeysByUrl,
393
+ forceRefresh
394
+ );
255
395
  }
256
396
  return list;
257
397
  } catch (e) {
@@ -270,7 +410,7 @@ var ModelManager = class _ModelManager {
270
410
  * @param baseUrls Current provider base URLs to evaluate
271
411
  * @returns Array of provider base URLs disabled by the review set
272
412
  */
273
- async syncReviewedProvidersFromNostr(baseUrls = this.adapter.getBaseUrlsList(), providerNodes = this.providerNodePubkeysByUrl) {
413
+ async syncReviewedProvidersFromNostr(baseUrls = this.adapter.getBaseUrlsList(), providerNodes = this.providerNodePubkeysByUrl, forceRefresh = false) {
274
414
  if (baseUrls.length === 0) return [];
275
415
  if (!this.adapter.setDisabledProviders) {
276
416
  this.logger.warn(
@@ -278,30 +418,43 @@ var ModelManager = class _ModelManager {
278
418
  );
279
419
  return [];
280
420
  }
281
- const LGTM_RELAYS = [
282
- "wss://relay.primal.net",
283
- "wss://nos.lol",
284
- "wss://relay.damus.io",
285
- "wss://relay.routstr.com"
286
- ];
287
421
  const reviewedNodePubkeys = /* @__PURE__ */ new Set();
288
422
  {
289
- const pool = new applesauceRelay.RelayPool();
290
- const store = new applesauceCore.EventStore();
291
- const timeoutMs = 5e3;
292
- await new Promise((resolve) => {
293
- pool.req(LGTM_RELAYS, {
294
- kinds: [38425],
295
- "#t": ["lgtm"],
296
- limit: 500,
297
- authors: [this.routstrPubkey]
298
- }).pipe(
299
- applesauceRelay.onlyEvents(),
300
- rxjs.tap((event) => store.add(event))
301
- ).subscribe({ complete: () => resolve() });
302
- setTimeout(() => resolve(), timeoutMs);
303
- });
304
- for (const event of store.getTimeline({ kinds: [38425] })) {
423
+ const cached = await this.getCachedNostrEvents(
424
+ { kinds: [38425], "#t": ["lgtm"], authors: [this.routstrPubkey] },
425
+ this.cacheTTL,
426
+ forceRefresh
427
+ );
428
+ let sessionEvents = cached;
429
+ if (cached.length === 0) {
430
+ const lgtmRelays = this.getNostrRelays([
431
+ "wss://relay.primal.net",
432
+ "wss://nos.lol",
433
+ "wss://relay.damus.io",
434
+ "wss://relay.routstr.com"
435
+ ]);
436
+ const pool = new applesauceRelay.RelayPool();
437
+ const timeoutMs = 5e3;
438
+ await new Promise((resolve) => {
439
+ pool.req(lgtmRelays, {
440
+ kinds: [38425],
441
+ "#t": ["lgtm"],
442
+ limit: 500,
443
+ authors: [this.routstrPubkey]
444
+ }).pipe(
445
+ applesauceRelay.onlyEvents(),
446
+ rxjs.tap((event) => {
447
+ sessionEvents.push(event);
448
+ this.eventStore?.add(event);
449
+ this.markEventFetched(event);
450
+ })
451
+ ).subscribe({ complete: () => resolve() });
452
+ setTimeout(() => resolve(), timeoutMs);
453
+ });
454
+ } else {
455
+ this.logger.log(`Using ${cached.length} cached kind 38425 events from persistent store`);
456
+ }
457
+ for (const event of sessionEvents) {
305
458
  const hasLgtmTag = event.tags.some(
306
459
  (tag) => tag[0] === "t" && tag[1]?.toLowerCase() === "lgtm"
307
460
  );
@@ -410,7 +563,7 @@ var ModelManager = class _ModelManager {
410
563
  if (this.isProviderDownError(error)) {
411
564
  this.logger.warn(`Provider ${base} is down right now.`);
412
565
  } else {
413
- this.logger.warn(`Failed to fetch models from ${base}:`, error);
566
+ this.logger.warn(`Provider ${base} unreachable: ${error.message}`);
414
567
  }
415
568
  this.adapter.setProviderLastUpdate(base, Date.now());
416
569
  return { success: false, base };
@@ -525,39 +678,49 @@ var ModelManager = class _ModelManager {
525
678
  return cachedModels;
526
679
  }
527
680
  }
528
- const DEFAULT_RELAYS = [
681
+ const relays = this.getNostrRelays([
529
682
  "wss://relay.damus.io",
530
683
  "wss://nos.lol",
531
684
  "wss://relay.routstr.com"
532
- ];
533
- const pool = new applesauceRelay.RelayPool();
534
- const localEventStore = new applesauceCore.EventStore();
535
- const timeoutMs = 5e3;
536
- await new Promise((resolve) => {
537
- pool.req(DEFAULT_RELAYS, {
538
- kinds: [38423],
539
- "#d": ["routstr-21-models"],
540
- limit: 1,
541
- authors: [this.routstrPubkey]
542
- }).pipe(
543
- applesauceRelay.onlyEvents(),
544
- rxjs.tap((event2) => {
545
- localEventStore.add(event2);
546
- })
547
- ).subscribe({
548
- complete: () => {
685
+ ]);
686
+ const cached = await this.getCachedNostrEvents(
687
+ { kinds: [38423], "#d": ["routstr-21-models"], authors: [this.routstrPubkey] },
688
+ this.cacheTTL,
689
+ forceRefresh
690
+ );
691
+ let sessionEvents = cached;
692
+ if (cached.length === 0) {
693
+ const pool = new applesauceRelay.RelayPool();
694
+ const timeoutMs = 5e3;
695
+ await new Promise((resolve) => {
696
+ pool.req(relays, {
697
+ kinds: [38423],
698
+ "#d": ["routstr-21-models"],
699
+ limit: 1,
700
+ authors: [this.routstrPubkey]
701
+ }).pipe(
702
+ applesauceRelay.onlyEvents(),
703
+ rxjs.tap((event2) => {
704
+ sessionEvents.push(event2);
705
+ this.eventStore?.add(event2);
706
+ this.markEventFetched(event2);
707
+ })
708
+ ).subscribe({
709
+ complete: () => {
710
+ resolve();
711
+ }
712
+ });
713
+ setTimeout(() => {
549
714
  resolve();
550
- }
715
+ }, timeoutMs);
551
716
  });
552
- setTimeout(() => {
553
- resolve();
554
- }, timeoutMs);
555
- });
556
- const timeline = localEventStore.getTimeline({ kinds: [38423] });
557
- if (timeline.length === 0) {
717
+ } else {
718
+ this.logger.log(`Using ${cached.length} cached kind 38423 events from persistent store`);
719
+ }
720
+ if (sessionEvents.length === 0) {
558
721
  return cachedModels.length > 0 ? cachedModels : [];
559
722
  }
560
- const event = timeline[0];
723
+ const event = sessionEvents[0];
561
724
  try {
562
725
  const content = JSON.parse(event.content);
563
726
  const models = Array.isArray(content?.models) ? content.models : [];