vecmindb 1.0.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,1154 @@
1
+ // src/errors.ts
2
+ var VecminError = class extends Error {
3
+ /** HTTP-style status code from the server response. */
4
+ code;
5
+ constructor(message, code) {
6
+ super(message);
7
+ this.name = "VecminError";
8
+ this.code = code;
9
+ Object.setPrototypeOf(this, new.target.prototype);
10
+ }
11
+ };
12
+ var AuthenticationError = class extends VecminError {
13
+ constructor(message = "Authentication failed or missing credentials") {
14
+ super(message, 401);
15
+ this.name = "AuthenticationError";
16
+ Object.setPrototypeOf(this, new.target.prototype);
17
+ }
18
+ };
19
+ var PermissionError = class extends VecminError {
20
+ constructor(message = "Insufficient permissions") {
21
+ super(message, 403);
22
+ this.name = "PermissionError";
23
+ Object.setPrototypeOf(this, new.target.prototype);
24
+ }
25
+ };
26
+ var PaymentRequiredError = class extends VecminError {
27
+ constructor(message = "Subscription payment or valid license key required. Please register/activate your license at https://lingxinmind.com") {
28
+ super(message, 402);
29
+ this.name = "PaymentRequiredError";
30
+ Object.setPrototypeOf(this, new.target.prototype);
31
+ }
32
+ };
33
+ var NotFoundError = class extends VecminError {
34
+ constructor(message = "Resource not found") {
35
+ super(message, 404);
36
+ this.name = "NotFoundError";
37
+ Object.setPrototypeOf(this, new.target.prototype);
38
+ }
39
+ };
40
+ var RateLimitError = class extends VecminError {
41
+ constructor(message = "Rate limit exceeded") {
42
+ super(message, 429);
43
+ this.name = "RateLimitError";
44
+ Object.setPrototypeOf(this, new.target.prototype);
45
+ }
46
+ };
47
+ var ServerError = class extends VecminError {
48
+ constructor(message = "Internal server error", code = 500) {
49
+ super(message, code);
50
+ this.name = "ServerError";
51
+ Object.setPrototypeOf(this, new.target.prototype);
52
+ }
53
+ };
54
+ function createErrorFromStatus(status, message) {
55
+ switch (status) {
56
+ case 401:
57
+ return new AuthenticationError(message);
58
+ case 402:
59
+ return new PaymentRequiredError(message);
60
+ case 403:
61
+ return new PermissionError(message);
62
+ case 404:
63
+ return new NotFoundError(message);
64
+ case 429:
65
+ return new RateLimitError(message);
66
+ default:
67
+ if (status >= 500 && status < 600) {
68
+ return new ServerError(message, status);
69
+ }
70
+ return new VecminError(message, status);
71
+ }
72
+ }
73
+
74
+ // src/retry.ts
75
+ var DEFAULTS = {
76
+ maxRetries: 3,
77
+ backoffFactor: 0.5,
78
+ retryableStatusCodes: [429, 500, 502, 503, 504]
79
+ };
80
+ function resolveRetryOptions(opts) {
81
+ return {
82
+ maxRetries: opts?.maxRetries ?? DEFAULTS.maxRetries,
83
+ backoffFactor: opts?.backoffFactor ?? DEFAULTS.backoffFactor,
84
+ retryableStatusCodes: opts?.retryableStatusCodes ?? DEFAULTS.retryableStatusCodes
85
+ };
86
+ }
87
+ function sleep(ms) {
88
+ return new Promise((resolve) => setTimeout(resolve, ms));
89
+ }
90
+ function isRetryable(status, retryableCodes) {
91
+ return retryableCodes.includes(status);
92
+ }
93
+ async function withRetry(fn, options) {
94
+ const opts = resolveRetryOptions(options);
95
+ let lastError;
96
+ for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
97
+ try {
98
+ return await fn();
99
+ } catch (err) {
100
+ if (err instanceof VecminError && isRetryable(err.code, opts.retryableStatusCodes)) {
101
+ lastError = err;
102
+ let delayMs;
103
+ if (err instanceof RateLimitError && err.retryAfter) {
104
+ delayMs = (err.retryAfter ?? 1) * 1e3;
105
+ } else {
106
+ delayMs = opts.backoffFactor * Math.pow(2, attempt) * 1e3;
107
+ }
108
+ const jitter = Math.random() * 200 - 100;
109
+ await sleep(Math.max(0, delayMs + jitter));
110
+ continue;
111
+ }
112
+ throw err;
113
+ }
114
+ }
115
+ throw lastError ?? new VecminError("All retry attempts exhausted", 0);
116
+ }
117
+ async function fetchJson(url, init, retryOpts) {
118
+ return withRetry(async () => {
119
+ const res = await fetch(url, init);
120
+ const body = await res.json();
121
+ if (!res.ok) {
122
+ const message = body?.error ?? body?.message ?? `HTTP ${res.status}`;
123
+ const code = body?.code ?? res.status;
124
+ throw createErrorFromStatus(code, message);
125
+ }
126
+ return body?.data !== void 0 ? body.data : body;
127
+ }, retryOpts);
128
+ }
129
+
130
+ // src/auth.ts
131
+ function decodeJwtPayload(token) {
132
+ try {
133
+ const parts = token.split(".");
134
+ if (parts.length < 2) return null;
135
+ const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
136
+ const json = decodeURIComponent(
137
+ atob(base64).split("").map((c) => "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2)).join("")
138
+ );
139
+ return JSON.parse(json);
140
+ } catch {
141
+ return null;
142
+ }
143
+ }
144
+ function isJwtExpiringSoon(token, bufferMs = 5 * 60 * 1e3) {
145
+ const payload = decodeJwtPayload(token);
146
+ if (!payload) return true;
147
+ const exp = payload["exp"];
148
+ if (typeof exp !== "number") return true;
149
+ return Date.now() >= exp * 1e3 - bufferMs;
150
+ }
151
+ var AuthManager = class {
152
+ apiKey;
153
+ jwt;
154
+ loginParams;
155
+ baseUrl;
156
+ refreshPromise = null;
157
+ constructor(baseUrl, options) {
158
+ this.baseUrl = baseUrl;
159
+ this.apiKey = options.apiKey;
160
+ this.jwt = options.jwt;
161
+ }
162
+ /**
163
+ * Store login credentials so the manager can automatically refresh the JWT.
164
+ * Called internally by {@link VecminClient.login}.
165
+ */
166
+ setLoginCredentials(params, jwt) {
167
+ this.loginParams = params;
168
+ this.jwt = jwt;
169
+ }
170
+ /**
171
+ * Return the HTTP headers needed for the next request.
172
+ *
173
+ * If a JWT is present and not expiring soon, uses `Authorization: Bearer`.
174
+ * Otherwise falls back to `x-api-key` (if configured).
175
+ *
176
+ * When both are configured, JWT takes precedence; if the JWT is expiring
177
+ * the method triggers a background refresh and falls back to the API key
178
+ * for the current request.
179
+ */
180
+ async getHeaders() {
181
+ const headers = {};
182
+ if (this.jwt) {
183
+ if (isJwtExpiringSoon(this.jwt)) {
184
+ const freshJwt = await this.refreshJwt();
185
+ if (freshJwt) {
186
+ headers["Authorization"] = `Bearer ${freshJwt}`;
187
+ } else if (this.apiKey) {
188
+ headers["x-api-key"] = this.apiKey;
189
+ }
190
+ } else {
191
+ headers["Authorization"] = `Bearer ${this.jwt}`;
192
+ }
193
+ } else if (this.apiKey) {
194
+ headers["x-api-key"] = this.apiKey;
195
+ }
196
+ return headers;
197
+ }
198
+ /**
199
+ * Attempt to refresh the JWT by re-posting the stored login credentials.
200
+ * Uses a singleton promise so concurrent calls don't trigger multiple logins.
201
+ */
202
+ async refreshJwt() {
203
+ if (!this.loginParams) return null;
204
+ if (this.refreshPromise) return this.refreshPromise;
205
+ this.refreshPromise = (async () => {
206
+ try {
207
+ const result = await fetchJson(
208
+ `${this.baseUrl}/api/v1/cluster/login`,
209
+ {
210
+ method: "POST",
211
+ headers: { "Content-Type": "application/json" },
212
+ body: JSON.stringify(this.loginParams)
213
+ },
214
+ { maxRetries: 1 }
215
+ // Don't retry aggressively on auth failures.
216
+ );
217
+ this.jwt = result;
218
+ return result;
219
+ } catch {
220
+ return null;
221
+ } finally {
222
+ this.refreshPromise = null;
223
+ }
224
+ })();
225
+ return this.refreshPromise;
226
+ }
227
+ };
228
+
229
+ // src/client.ts
230
+ var VecminClient = class {
231
+ /** Resolved base URL (trailing slash stripped). */
232
+ baseUrl;
233
+ /** API path prefix. */
234
+ apiUrl;
235
+ /** MCP path prefix. */
236
+ mcpUrl;
237
+ /** Authentication manager. */
238
+ auth;
239
+ /** Request timeout in milliseconds. */
240
+ timeout;
241
+ /** Retry configuration propagated to every request. */
242
+ retryOptions;
243
+ /** Default headers merged into every request. */
244
+ defaultHeaders;
245
+ /** Abort controllers for in-flight requests (used by `close()`). */
246
+ controllers = /* @__PURE__ */ new Set();
247
+ /** Whether the client has been closed. */
248
+ closed = false;
249
+ constructor(options) {
250
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
251
+ this.apiUrl = `${this.baseUrl}/api/v1`;
252
+ this.mcpUrl = `${this.baseUrl}/mcp`;
253
+ this.auth = new AuthManager(this.baseUrl, options);
254
+ this.timeout = options.timeout ?? 3e4;
255
+ this.retryOptions = {
256
+ maxRetries: options.maxRetries ?? 3,
257
+ backoffFactor: options.backoffFactor ?? 0.5
258
+ };
259
+ this.defaultHeaders = {
260
+ "Content-Type": "application/json",
261
+ ...options.defaultHeaders
262
+ };
263
+ if (options.agentId) this.defaultHeaders["x-agent-id"] = options.agentId;
264
+ if (options.modelId) this.defaultHeaders["x-model-id"] = options.modelId;
265
+ }
266
+ // -----------------------------------------------------------------------
267
+ // Internal helpers
268
+ // -----------------------------------------------------------------------
269
+ /** Build the full headers map for an outgoing request. */
270
+ async buildHeaders() {
271
+ const authHeaders = await this.auth.getHeaders();
272
+ return { ...this.defaultHeaders, ...authHeaders };
273
+ }
274
+ /** Create an AbortController with the configured timeout and track it. */
275
+ createController() {
276
+ const controller = new AbortController();
277
+ this.controllers.add(controller);
278
+ const timer = setTimeout(() => controller.abort(), this.timeout);
279
+ if (timer.unref) timer.unref();
280
+ controller.signal.addEventListener("abort", () => {
281
+ this.controllers.delete(controller);
282
+ });
283
+ return controller;
284
+ }
285
+ /** Perform a JSON GET request. */
286
+ async get(path) {
287
+ this.assertOpen();
288
+ const controller = this.createController();
289
+ try {
290
+ return await fetchJson(
291
+ `${this.apiUrl}${path}`,
292
+ {
293
+ method: "GET",
294
+ headers: await this.buildHeaders(),
295
+ signal: controller.signal
296
+ },
297
+ this.retryOptions
298
+ );
299
+ } finally {
300
+ this.controllers.delete(controller);
301
+ }
302
+ }
303
+ /** Perform a JSON POST request. */
304
+ async post(path, body) {
305
+ this.assertOpen();
306
+ const controller = this.createController();
307
+ try {
308
+ return await fetchJson(
309
+ `${this.apiUrl}${path}`,
310
+ {
311
+ method: "POST",
312
+ headers: await this.buildHeaders(),
313
+ body: body !== void 0 ? JSON.stringify(body) : void 0,
314
+ signal: controller.signal
315
+ },
316
+ this.retryOptions
317
+ );
318
+ } finally {
319
+ this.controllers.delete(controller);
320
+ }
321
+ }
322
+ /** Perform a JSON DELETE request. */
323
+ async del(path) {
324
+ this.assertOpen();
325
+ const controller = this.createController();
326
+ try {
327
+ return await fetchJson(
328
+ `${this.apiUrl}${path}`,
329
+ {
330
+ method: "DELETE",
331
+ headers: await this.buildHeaders(),
332
+ signal: controller.signal
333
+ },
334
+ this.retryOptions
335
+ );
336
+ } finally {
337
+ this.controllers.delete(controller);
338
+ }
339
+ }
340
+ /** Throw if the client has been closed. */
341
+ assertOpen() {
342
+ if (this.closed) {
343
+ throw new Error("VecminClient has been closed");
344
+ }
345
+ }
346
+ // -----------------------------------------------------------------------
347
+ // Collection Management
348
+ // -----------------------------------------------------------------------
349
+ /**
350
+ * Create a new collection.
351
+ *
352
+ * @example
353
+ * ```ts
354
+ * const col = await client.createCollection({
355
+ * name: "docs",
356
+ * dimension: 1536,
357
+ * metric_type: "Cosine",
358
+ * index_type: "HNSW",
359
+ * });
360
+ * ```
361
+ */
362
+ async createCollection(params) {
363
+ return this.post("/collections", params);
364
+ }
365
+ /**
366
+ * List all collections.
367
+ *
368
+ * @example
369
+ * ```ts
370
+ * const collections = await client.listCollections();
371
+ * ```
372
+ */
373
+ async listCollections() {
374
+ return this.get("/collections");
375
+ }
376
+ /**
377
+ * Get details for a specific collection.
378
+ *
379
+ * @example
380
+ * ```ts
381
+ * const col = await client.getCollection("docs");
382
+ * ```
383
+ */
384
+ async getCollection(name) {
385
+ return this.get(`/collections/${encodeURIComponent(name)}`);
386
+ }
387
+ /**
388
+ * Delete a collection and all its data.
389
+ *
390
+ * @example
391
+ * ```ts
392
+ * await client.deleteCollection("docs");
393
+ * ```
394
+ */
395
+ async deleteCollection(name) {
396
+ await this.del(`/collections/${encodeURIComponent(name)}`);
397
+ }
398
+ /**
399
+ * Get statistics for a collection.
400
+ *
401
+ * @example
402
+ * ```ts
403
+ * const stats = await client.getCollectionStats("docs");
404
+ * console.log(`Vectors: ${stats.vector_count}`);
405
+ * ```
406
+ */
407
+ async getCollectionStats(name) {
408
+ return this.get(`/collections/${encodeURIComponent(name)}/stats`);
409
+ }
410
+ // -----------------------------------------------------------------------
411
+ // Vector Operations
412
+ // -----------------------------------------------------------------------
413
+ /**
414
+ * Insert a single vector into a collection.
415
+ *
416
+ * @returns The vector ID (either the caller-supplied ID or a server-generated one).
417
+ *
418
+ * @example
419
+ * ```ts
420
+ * const id = await client.insert("docs", {
421
+ * vector: [0.1, 0.2, /* ... *\/],
422
+ * metadata: { title: "Hello" },
423
+ * });
424
+ * ```
425
+ */
426
+ async insert(collection, params) {
427
+ return this.post(`/collections/${encodeURIComponent(collection)}/insert`, params);
428
+ }
429
+ /**
430
+ * Insert multiple vectors in a single request.
431
+ *
432
+ * @returns An array of vector IDs in the same order as the input.
433
+ *
434
+ * @example
435
+ * ```ts
436
+ * const ids = await client.batchInsert("docs", [
437
+ * { vector: [0.1, 0.2], metadata: { title: "A" } },
438
+ * { vector: [0.3, 0.4], metadata: { title: "B" } },
439
+ * ]);
440
+ * ```
441
+ */
442
+ async batchInsert(collection, vectors) {
443
+ return this.post(`/collections/${encodeURIComponent(collection)}/batch`, vectors);
444
+ }
445
+ /**
446
+ * Search for the nearest neighbours of a query vector.
447
+ *
448
+ * @example
449
+ * ```ts
450
+ * const results = await client.search("docs", {
451
+ * query: [0.1, 0.2, /* ... *\/],
452
+ * k: 5,
453
+ * ef_search: 100,
454
+ * });
455
+ * for (const r of results) {
456
+ * console.log(r.id, r.score);
457
+ * }
458
+ * ```
459
+ */
460
+ async search(collection, params) {
461
+ return this.post(`/collections/${encodeURIComponent(collection)}/search`, params);
462
+ }
463
+ /**
464
+ * Retrieve a single vector by its ID.
465
+ *
466
+ * @example
467
+ * ```ts
468
+ * const vec = await client.getVector("docs", "abc-123");
469
+ * ```
470
+ */
471
+ async getVector(collection, id) {
472
+ return this.get(`/collections/${encodeURIComponent(collection)}/vectors/${encodeURIComponent(id)}`);
473
+ }
474
+ /**
475
+ * Delete a single vector by its ID.
476
+ *
477
+ * @example
478
+ * ```ts
479
+ * await client.deleteVector("docs", "abc-123");
480
+ * ```
481
+ */
482
+ async deleteVector(collection, id) {
483
+ await this.del(`/collections/${encodeURIComponent(collection)}/vectors/${encodeURIComponent(id)}`);
484
+ }
485
+ // -----------------------------------------------------------------------
486
+ // Index Management
487
+ // -----------------------------------------------------------------------
488
+ /**
489
+ * Trigger an index rebuild for a collection.
490
+ *
491
+ * @example
492
+ * ```ts
493
+ * await client.rebuildIndex("docs");
494
+ * ```
495
+ */
496
+ async rebuildIndex(collection) {
497
+ await this.post(`/collections/${encodeURIComponent(collection)}/rebuild_index`);
498
+ }
499
+ /**
500
+ * List all indexes in the cluster.
501
+ *
502
+ * @example
503
+ * ```ts
504
+ * const indexes = await client.listIndexes();
505
+ * ```
506
+ */
507
+ async listIndexes() {
508
+ return this.get("/indexes");
509
+ }
510
+ /**
511
+ * Rebuild a named index.
512
+ *
513
+ * @example
514
+ * ```ts
515
+ * await client.rebuildNamedIndex("docs_hnsw");
516
+ * ```
517
+ */
518
+ async rebuildNamedIndex(name) {
519
+ await this.post(`/indexes/${encodeURIComponent(name)}/rebuild`);
520
+ }
521
+ /**
522
+ * Optimize a named index.
523
+ *
524
+ * @example
525
+ * ```ts
526
+ * await client.optimizeIndex("docs_hnsw");
527
+ * ```
528
+ */
529
+ async optimizeIndex(name) {
530
+ await this.post(`/indexes/${encodeURIComponent(name)}/optimize`);
531
+ }
532
+ // -----------------------------------------------------------------------
533
+ // Cluster Management
534
+ // -----------------------------------------------------------------------
535
+ /**
536
+ * Authenticate and obtain a JWT token.
537
+ * The token is cached and automatically refreshed before expiry.
538
+ *
539
+ * @returns The JWT string.
540
+ *
541
+ * @example
542
+ * ```ts
543
+ * const jwt = await client.login({ username: "admin", password: "secret" });
544
+ * ```
545
+ */
546
+ async login(credentials) {
547
+ const jwt = await this.post("/cluster/login", credentials);
548
+ this.auth.setLoginCredentials(credentials, jwt);
549
+ return jwt;
550
+ }
551
+ /**
552
+ * List all nodes in the cluster.
553
+ *
554
+ * @example
555
+ * ```ts
556
+ * const nodes = await client.listNodes();
557
+ * ```
558
+ */
559
+ async listNodes() {
560
+ return this.get("/cluster/nodes");
561
+ }
562
+ /**
563
+ * Get the overall cluster status.
564
+ *
565
+ * @example
566
+ * ```ts
567
+ * const status = await client.clusterStatus();
568
+ * console.log(status.status, status.leader_id);
569
+ * ```
570
+ */
571
+ async clusterStatus() {
572
+ return this.get("/cluster/status");
573
+ }
574
+ /**
575
+ * Create a cluster snapshot.
576
+ *
577
+ * @example
578
+ * ```ts
579
+ * await client.createSnapshot();
580
+ * ```
581
+ */
582
+ async createSnapshot() {
583
+ await this.post("/cluster/snapshot");
584
+ }
585
+ // -----------------------------------------------------------------------
586
+ // MCP Convenience Methods
587
+ // -----------------------------------------------------------------------
588
+ /**
589
+ * Store a memory via the MCP `store_memory` tool.
590
+ *
591
+ * This is a convenience wrapper that calls the MCP JSON-RPC endpoint.
592
+ *
593
+ * @example
594
+ * ```ts
595
+ * await client.mcpStoreMemory("User prefers dark mode", "agent-1");
596
+ * ```
597
+ */
598
+ async mcpStoreMemory(text, agentId, options) {
599
+ const controller = this.createController();
600
+ try {
601
+ await fetchJson(
602
+ `${this.mcpUrl}/messages`,
603
+ {
604
+ method: "POST",
605
+ headers: await this.buildHeaders(),
606
+ body: JSON.stringify({
607
+ jsonrpc: "2.0",
608
+ id: Date.now(),
609
+ method: "tools/call",
610
+ params: {
611
+ name: "store_memory",
612
+ arguments: {
613
+ agent_id: agentId,
614
+ content: text,
615
+ source: options?.source ?? "typescript-sdk",
616
+ collection: options?.collection ?? "agent_memory_mcp"
617
+ }
618
+ }
619
+ }),
620
+ signal: controller.signal
621
+ },
622
+ this.retryOptions
623
+ );
624
+ } finally {
625
+ this.controllers.delete(controller);
626
+ }
627
+ }
628
+ /**
629
+ * Search memories via the MCP `search_memory` tool.
630
+ *
631
+ * @example
632
+ * ```ts
633
+ * const results = await client.mcpSearchMemory("user preferences", "agent-1", 5);
634
+ * ```
635
+ */
636
+ async mcpSearchMemory(query, agentId, topK = 5) {
637
+ const controller = this.createController();
638
+ try {
639
+ const result = await fetchJson(
640
+ `${this.mcpUrl}/messages`,
641
+ {
642
+ method: "POST",
643
+ headers: await this.buildHeaders(),
644
+ body: JSON.stringify({
645
+ jsonrpc: "2.0",
646
+ id: Date.now(),
647
+ method: "tools/call",
648
+ params: {
649
+ name: "search_memory",
650
+ arguments: {
651
+ agent_id: agentId,
652
+ query,
653
+ top_k: topK
654
+ }
655
+ }
656
+ }),
657
+ signal: controller.signal
658
+ },
659
+ this.retryOptions
660
+ );
661
+ return result;
662
+ } finally {
663
+ this.controllers.delete(controller);
664
+ }
665
+ }
666
+ // -----------------------------------------------------------------------
667
+ // Lifecycle
668
+ // -----------------------------------------------------------------------
669
+ /**
670
+ * Gracefully shut down the client.
671
+ * Aborts all in-flight requests and releases resources.
672
+ *
673
+ * @example
674
+ * ```ts
675
+ * await client.close();
676
+ * ```
677
+ */
678
+ async close() {
679
+ this.closed = true;
680
+ for (const controller of this.controllers) {
681
+ controller.abort();
682
+ }
683
+ this.controllers.clear();
684
+ }
685
+ };
686
+
687
+ // src/mcp.ts
688
+ var VecminMCPClient = class {
689
+ baseUrl;
690
+ mcpUrl;
691
+ options;
692
+ handlers = /* @__PURE__ */ new Map();
693
+ rpcId = 0;
694
+ pendingRpc = /* @__PURE__ */ new Map();
695
+ abortController = null;
696
+ reader = null;
697
+ connected = false;
698
+ constructor(baseUrl, options) {
699
+ this.baseUrl = baseUrl.replace(/\/+$/, "");
700
+ this.mcpUrl = `${this.baseUrl}/mcp`;
701
+ this.options = options ?? {};
702
+ }
703
+ // -----------------------------------------------------------------------
704
+ // Event emitter
705
+ // -----------------------------------------------------------------------
706
+ /**
707
+ * Register an event handler.
708
+ *
709
+ * @param event - One of the {@link MCPEventMap} keys.
710
+ * @param handler - Callback invoked when the event fires.
711
+ */
712
+ on(event, handler) {
713
+ if (!this.handlers.has(event)) {
714
+ this.handlers.set(event, /* @__PURE__ */ new Set());
715
+ }
716
+ this.handlers.get(event).add(handler);
717
+ }
718
+ /**
719
+ * Remove a previously registered event handler.
720
+ */
721
+ off(event, handler) {
722
+ this.handlers.get(event)?.delete(handler);
723
+ }
724
+ emit(event, data) {
725
+ for (const handler of this.handlers.get(event) ?? []) {
726
+ try {
727
+ handler(data);
728
+ } catch {
729
+ }
730
+ }
731
+ }
732
+ // -----------------------------------------------------------------------
733
+ // SSE Connection
734
+ // -----------------------------------------------------------------------
735
+ /**
736
+ * Open the SSE event-stream connection to `GET /mcp/sse`.
737
+ *
738
+ * The connection remains open until {@link disconnect} is called.
739
+ * Events are parsed and emitted via the `on("event", ...)` interface.
740
+ */
741
+ async connect() {
742
+ if (this.connected) return;
743
+ this.abortController = new AbortController();
744
+ const headers = {
745
+ Accept: "text/event-stream"
746
+ };
747
+ if (this.options.apiKey) headers["x-api-key"] = this.options.apiKey;
748
+ if (this.options.jwt) headers["Authorization"] = `Bearer ${this.options.jwt}`;
749
+ if (this.options.agentId) headers["x-agent-id"] = this.options.agentId;
750
+ if (this.options.modelId) headers["x-model-id"] = this.options.modelId;
751
+ const response = await fetch(`${this.mcpUrl}/sse`, {
752
+ method: "GET",
753
+ headers,
754
+ signal: this.abortController.signal
755
+ });
756
+ if (!response.ok) {
757
+ throw createErrorFromStatus(
758
+ response.status,
759
+ `SSE connection failed: HTTP ${response.status}`
760
+ );
761
+ }
762
+ const body = response.body;
763
+ if (!body) {
764
+ throw new VecminError("SSE connection returned no body", 0);
765
+ }
766
+ this.connected = true;
767
+ this.emit("connected", void 0);
768
+ this.readSSEStream(body).catch((err) => {
769
+ if (this.connected) {
770
+ this.emit("error", err instanceof Error ? err : new Error(String(err)));
771
+ }
772
+ });
773
+ }
774
+ /**
775
+ * Read and parse an SSE stream using `eventsource-parser`.
776
+ */
777
+ async readSSEStream(stream) {
778
+ const { createParser } = await import("eventsource-parser");
779
+ const parser = createParser((event) => {
780
+ if (!("data" in event)) return;
781
+ this.emit("event", { event: event.event ?? "message", data: event.data });
782
+ if (event.data) {
783
+ try {
784
+ const json = JSON.parse(event.data);
785
+ if (json.id !== void 0 && json.id !== null) {
786
+ const pending = this.pendingRpc.get(json.id);
787
+ if (pending) {
788
+ this.pendingRpc.delete(json.id);
789
+ if (json.error) {
790
+ pending.reject(
791
+ new VecminError(json.error.message ?? "JSON-RPC error", json.error.code)
792
+ );
793
+ } else {
794
+ pending.resolve(json.result);
795
+ }
796
+ this.emit("result", { id: json.id, result: json.result });
797
+ }
798
+ }
799
+ } catch {
800
+ }
801
+ }
802
+ });
803
+ this.reader = stream.getReader();
804
+ const decoder = new TextDecoder();
805
+ try {
806
+ while (true) {
807
+ const { done, value } = await this.reader.read();
808
+ if (done) break;
809
+ parser.feed(decoder.decode(value, { stream: true }));
810
+ }
811
+ } finally {
812
+ this.connected = false;
813
+ this.emit("disconnected", void 0);
814
+ }
815
+ }
816
+ // -----------------------------------------------------------------------
817
+ // JSON-RPC Calls
818
+ // -----------------------------------------------------------------------
819
+ /**
820
+ * Send a JSON-RPC 2.0 request to `POST /mcp/messages`.
821
+ *
822
+ * @returns The `result` field of the JSON-RPC response.
823
+ */
824
+ async rpc(method, params) {
825
+ const id = ++this.rpcId;
826
+ const request = {
827
+ jsonrpc: "2.0",
828
+ id,
829
+ method,
830
+ params
831
+ };
832
+ const headers = {
833
+ "Content-Type": "application/json"
834
+ };
835
+ if (this.options.apiKey) headers["x-api-key"] = this.options.apiKey;
836
+ if (this.options.jwt) headers["Authorization"] = `Bearer ${this.options.jwt}`;
837
+ if (this.options.agentId) headers["x-agent-id"] = this.options.agentId;
838
+ if (this.options.modelId) headers["x-model-id"] = this.options.modelId;
839
+ const rpcPromise = new Promise((resolve, reject) => {
840
+ this.pendingRpc.set(id, { resolve, reject });
841
+ });
842
+ const res = await fetch(`${this.mcpUrl}/messages`, {
843
+ method: "POST",
844
+ headers,
845
+ body: JSON.stringify(request)
846
+ });
847
+ if (!res.ok) {
848
+ this.pendingRpc.delete(id);
849
+ throw createErrorFromStatus(res.status, `MCP RPC call failed: HTTP ${res.status}`);
850
+ }
851
+ const text = await res.text();
852
+ if (text) {
853
+ try {
854
+ const json = JSON.parse(text);
855
+ this.pendingRpc.delete(id);
856
+ if (json.error) {
857
+ throw new VecminError(json.error.message ?? "JSON-RPC error", json.error.code);
858
+ }
859
+ return json.result ?? json;
860
+ } catch (err) {
861
+ if (err instanceof VecminError) throw err;
862
+ }
863
+ }
864
+ return rpcPromise;
865
+ }
866
+ // -----------------------------------------------------------------------
867
+ // MCP Tool Methods
868
+ // -----------------------------------------------------------------------
869
+ /**
870
+ * Call the `store_memory` MCP tool.
871
+ *
872
+ * @example
873
+ * ```ts
874
+ * await mcp.storeMemory({ text: "User prefers dark mode", agent_id: "agent-1" });
875
+ * ```
876
+ */
877
+ async storeMemory(params) {
878
+ await this.rpc("tools/call", {
879
+ name: "store_memory",
880
+ arguments: {
881
+ agent_id: params.agent_id,
882
+ text: params.text,
883
+ source: params.source ?? "typescript-sdk",
884
+ collection: params.collection ?? "agent_memory_mcp"
885
+ }
886
+ });
887
+ }
888
+ /**
889
+ * Call the `search_memory` MCP tool.
890
+ *
891
+ * @example
892
+ * ```ts
893
+ * const results = await mcp.searchMemory({ query: "preferences", agent_id: "agent-1", top_k: 5 });
894
+ * ```
895
+ */
896
+ async searchMemory(params) {
897
+ const result = await this.rpc("tools/call", {
898
+ name: "search_memory",
899
+ arguments: {
900
+ agent_id: params.agent_id,
901
+ query: params.query,
902
+ top_k: params.top_k ?? 5,
903
+ collection: params.collection ?? "agent_memory_mcp"
904
+ }
905
+ });
906
+ return result;
907
+ }
908
+ // -----------------------------------------------------------------------
909
+ // Lifecycle
910
+ // -----------------------------------------------------------------------
911
+ /**
912
+ * Close the SSE connection and release all resources.
913
+ */
914
+ async disconnect() {
915
+ this.connected = false;
916
+ this.abortController?.abort();
917
+ this.abortController = null;
918
+ try {
919
+ await this.reader?.cancel();
920
+ } catch {
921
+ }
922
+ this.reader = null;
923
+ for (const [id, { reject }] of this.pendingRpc) {
924
+ reject(new VecminError("MCP client disconnected", 0));
925
+ this.pendingRpc.delete(id);
926
+ }
927
+ }
928
+ };
929
+
930
+ // src/langchain.ts
931
+ var LangChain = null;
932
+ var LangChainDocs = null;
933
+ async function ensureLangChain() {
934
+ if (LangChain && LangChainDocs) {
935
+ return {
936
+ VectorStore: LangChain.VectorStore,
937
+ Document: LangChainDocs.Document,
938
+ Embeddings: (await import("@langchain/core/embeddings")).Embeddings
939
+ };
940
+ }
941
+ try {
942
+ const vc = await import("@langchain/core/vectorstores");
943
+ const docs = await import("@langchain/core/documents");
944
+ const emb = await import("@langchain/core/embeddings");
945
+ LangChain = vc;
946
+ LangChainDocs = docs;
947
+ return {
948
+ VectorStore: vc.VectorStore,
949
+ Document: docs.Document,
950
+ Embeddings: emb.Embeddings
951
+ };
952
+ } catch {
953
+ throw new Error(
954
+ "@langchain/core is required for VecminDBVectorStore. Install it with: npm install @langchain/core"
955
+ );
956
+ }
957
+ }
958
+ var VecminDBVectorStore = class _VecminDBVectorStore {
959
+ /** Underlying VecminDB client used for all I/O. */
960
+ client;
961
+ /** LangChain embeddings instance. */
962
+ _embeddings;
963
+ // typed as unknown to avoid requiring @langchain/core at import time
964
+ /** Collection name in VecminDB. */
965
+ collectionName;
966
+ /** Vector dimensionality. */
967
+ dimension;
968
+ /** Whether the collection has been ensured to exist. */
969
+ ensured = false;
970
+ // LangChain VectorStore interface fields
971
+ /** @internal */
972
+ lc_namespace = ["vecmindb", "vectorstores"];
973
+ /** @internal */
974
+ lc_serializable = true;
975
+ constructor(embeddings, options) {
976
+ this._embeddings = embeddings;
977
+ this.collectionName = options.collectionName ?? "langchain_memory";
978
+ this.dimension = options.dimension ?? 1536;
979
+ this.client = new VecminClient({
980
+ baseUrl: options.baseUrl,
981
+ apiKey: options.apiKey,
982
+ timeout: options.timeout
983
+ });
984
+ }
985
+ // -----------------------------------------------------------------------
986
+ // Collection bootstrap
987
+ // -----------------------------------------------------------------------
988
+ /**
989
+ * Ensure the target collection exists in VecminDB.
990
+ * Called lazily before the first write operation.
991
+ */
992
+ async ensureCollection() {
993
+ if (this.ensured) return;
994
+ try {
995
+ await this.client.createCollection({
996
+ name: this.collectionName,
997
+ dimension: this.dimension,
998
+ metric_type: "Cosine",
999
+ index_type: "HNSW",
1000
+ index_params: { m: 16, ef_construction: 100 }
1001
+ });
1002
+ } catch (err) {
1003
+ if (err instanceof Error && err.message?.includes("already exists")) {
1004
+ } else if (err instanceof Error && (err.message?.includes("409") || err.message?.includes("Conflict"))) {
1005
+ } else if (err instanceof Error && err.message?.includes("404") === false) {
1006
+ throw err;
1007
+ }
1008
+ }
1009
+ this.ensured = true;
1010
+ }
1011
+ // -----------------------------------------------------------------------
1012
+ // LangChain VectorStore interface
1013
+ // -----------------------------------------------------------------------
1014
+ /**
1015
+ * Add documents to the vector store.
1016
+ * Embeds the document texts and inserts them into VecminDB.
1017
+ */
1018
+ async addDocuments(documents) {
1019
+ const { Embeddings } = await ensureLangChain();
1020
+ const emb = this._embeddings;
1021
+ const texts = documents.map((d) => d.pageContent);
1022
+ const metadatas = documents.map((d) => d.metadata ?? {});
1023
+ const vectors = await emb.embedDocuments(texts);
1024
+ return this.addVectors(vectors, documents, metadatas);
1025
+ }
1026
+ /**
1027
+ * Add pre-computed vectors to the vector store.
1028
+ */
1029
+ async addVectors(vectors, documents, metadatas) {
1030
+ await this.ensureCollection();
1031
+ const ids = [];
1032
+ for (let i = 0; i < vectors.length; i++) {
1033
+ const doc = documents[i];
1034
+ const meta = {
1035
+ ...metadatas?.[i] ?? doc.metadata ?? {},
1036
+ text: doc.pageContent
1037
+ // Store the text in metadata for retrieval.
1038
+ };
1039
+ const id = await this.client.insert(this.collectionName, {
1040
+ vector: vectors[i],
1041
+ metadata: meta
1042
+ });
1043
+ ids.push(id);
1044
+ }
1045
+ return ids;
1046
+ }
1047
+ /**
1048
+ * Search for the most similar vectors and return results with scores.
1049
+ *
1050
+ * @param query - Query vector.
1051
+ * @param k - Number of results. @default 4
1052
+ */
1053
+ async similaritySearchVectorWithScore(query, k = 4) {
1054
+ const results = await this.client.search(this.collectionName, {
1055
+ query,
1056
+ k
1057
+ });
1058
+ return results.map((r) => {
1059
+ const meta = r.metadata ?? {};
1060
+ const text = typeof meta["text"] === "string" ? meta["text"] : "";
1061
+ const cleanMeta = { ...meta };
1062
+ delete cleanMeta["text"];
1063
+ return [
1064
+ { pageContent: text, metadata: cleanMeta },
1065
+ r.score
1066
+ ];
1067
+ });
1068
+ }
1069
+ /**
1070
+ * Convenience method: search for similar documents (without scores).
1071
+ */
1072
+ async similaritySearch(query, k = 4) {
1073
+ const { Embeddings } = await ensureLangChain();
1074
+ const emb = this._embeddings;
1075
+ const queryVector = await emb.embedQuery(query);
1076
+ const results = await this.similaritySearchVectorWithScore(queryVector, k);
1077
+ return results.map(([doc]) => doc);
1078
+ }
1079
+ /**
1080
+ * Convenience method: search for similar documents with scores.
1081
+ */
1082
+ async similaritySearchWithScore(query, k = 4) {
1083
+ const { Embeddings } = await ensureLangChain();
1084
+ const emb = this._embeddings;
1085
+ const queryVector = await emb.embedQuery(query);
1086
+ return this.similaritySearchVectorWithScore(queryVector, k);
1087
+ }
1088
+ // -----------------------------------------------------------------------
1089
+ // Static factory methods
1090
+ // -----------------------------------------------------------------------
1091
+ /**
1092
+ * Create a VecminDBVectorStore from an array of Document objects.
1093
+ *
1094
+ * @example
1095
+ * ```ts
1096
+ * const store = await VecminDBVectorStore.fromDocuments(
1097
+ * [{ pageContent: "Hello", metadata: {} }],
1098
+ * embeddings,
1099
+ * { baseUrl: "http://localhost:8080" },
1100
+ * );
1101
+ * ```
1102
+ */
1103
+ static async fromDocuments(documents, embeddings, options) {
1104
+ const store = new _VecminDBVectorStore(embeddings, options);
1105
+ await store.addDocuments(documents);
1106
+ return store;
1107
+ }
1108
+ /**
1109
+ * Create a VecminDBVectorStore from an array of texts.
1110
+ *
1111
+ * @example
1112
+ * ```ts
1113
+ * const store = await VecminDBVectorStore.fromTexts(
1114
+ * ["Hello world", "Goodbye world"],
1115
+ * [{}, {}],
1116
+ * embeddings,
1117
+ * { baseUrl: "http://localhost:8080" },
1118
+ * );
1119
+ * ```
1120
+ */
1121
+ static async fromTexts(texts, metadatas, embeddings, options) {
1122
+ const documents = texts.map((text, i) => ({
1123
+ pageContent: text,
1124
+ metadata: metadatas[i] ?? {}
1125
+ }));
1126
+ return _VecminDBVectorStore.fromDocuments(documents, embeddings, options);
1127
+ }
1128
+ // -----------------------------------------------------------------------
1129
+ // Cleanup
1130
+ // -----------------------------------------------------------------------
1131
+ /**
1132
+ * Close the underlying client connection.
1133
+ */
1134
+ async close() {
1135
+ await this.client.close();
1136
+ }
1137
+ };
1138
+ export {
1139
+ AuthManager,
1140
+ AuthenticationError,
1141
+ NotFoundError,
1142
+ PaymentRequiredError,
1143
+ PermissionError,
1144
+ RateLimitError,
1145
+ ServerError,
1146
+ VecminClient,
1147
+ VecminDBVectorStore,
1148
+ VecminError,
1149
+ VecminMCPClient,
1150
+ createErrorFromStatus,
1151
+ fetchJson,
1152
+ resolveRetryOptions,
1153
+ withRetry
1154
+ };