@sovant/sdk 1.0.4 → 1.1.0

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,809 @@
1
+ // src/errors.ts
2
+ var SovantError = class _SovantError extends Error {
3
+ constructor(message, code, status, requestId) {
4
+ super(message);
5
+ this.name = "SovantError";
6
+ this.code = code;
7
+ this.status = status;
8
+ this.requestId = requestId;
9
+ if (Error.captureStackTrace) {
10
+ Error.captureStackTrace(this, _SovantError);
11
+ }
12
+ }
13
+ static fromResponse(status, body, requestId) {
14
+ const statusCodeMap = {
15
+ 400: "bad_request",
16
+ 401: "unauthorized",
17
+ 403: "forbidden",
18
+ 404: "not_found",
19
+ 409: "conflict",
20
+ 422: "validation_error",
21
+ 429: "rate_limited",
22
+ 500: "internal_error",
23
+ 502: "bad_gateway",
24
+ 503: "service_unavailable",
25
+ 504: "gateway_timeout"
26
+ };
27
+ let message = "An error occurred";
28
+ let code = statusCodeMap[status] || "unknown_error";
29
+ if (body?.error) {
30
+ if (typeof body.error === "string") {
31
+ message = body.error;
32
+ } else if (body.error.message) {
33
+ message = body.error.message;
34
+ code = body.error.code || code;
35
+ }
36
+ } else if (body?.message) {
37
+ message = body.message;
38
+ } else if (typeof body === "string") {
39
+ message = body;
40
+ }
41
+ return new _SovantError(message, code, status, requestId);
42
+ }
43
+ toJSON() {
44
+ return {
45
+ name: this.name,
46
+ message: this.message,
47
+ code: this.code,
48
+ status: this.status,
49
+ requestId: this.requestId
50
+ };
51
+ }
52
+ };
53
+ var NetworkError = class _NetworkError extends SovantError {
54
+ constructor(message, cause) {
55
+ super(message, "network_error");
56
+ this.name = "NetworkError";
57
+ if (Error.captureStackTrace) {
58
+ Error.captureStackTrace(this, _NetworkError);
59
+ }
60
+ }
61
+ };
62
+ var TimeoutError = class _TimeoutError extends SovantError {
63
+ constructor(message = "Request timed out") {
64
+ super(message, "timeout");
65
+ this.name = "TimeoutError";
66
+ if (Error.captureStackTrace) {
67
+ Error.captureStackTrace(this, _TimeoutError);
68
+ }
69
+ }
70
+ };
71
+
72
+ // src/http.ts
73
+ var RETRY_STATUS_CODES = [429, 502, 503, 504];
74
+ var DEFAULT_TIMEOUT = 3e4;
75
+ var DEFAULT_MAX_RETRIES = 3;
76
+ var IDEMPOTENT_METHODS = ["GET", "DELETE"];
77
+ var HttpClient = class {
78
+ constructor(baseUrl, apiKey, globalOptions = {}) {
79
+ this.baseUrl = baseUrl;
80
+ this.apiKey = apiKey;
81
+ this.globalOptions = globalOptions;
82
+ }
83
+ async request(path, options = {}) {
84
+ const url = `${this.baseUrl}${path}`;
85
+ const timeout = options.timeout ?? this.globalOptions.timeout ?? DEFAULT_TIMEOUT;
86
+ const maxRetries = options.maxRetries ?? this.globalOptions.maxRetries ?? DEFAULT_MAX_RETRIES;
87
+ const debug = options.debug ?? this.globalOptions.debug ?? false;
88
+ const method = options.method || "GET";
89
+ const shouldRetry = IDEMPOTENT_METHODS.includes(method) || method === "GET";
90
+ let lastError;
91
+ let attempt = 0;
92
+ while (attempt <= maxRetries) {
93
+ try {
94
+ if (debug && attempt > 0) {
95
+ console.log(`[Sovant SDK] Retry attempt ${attempt} for ${method} ${url}`);
96
+ }
97
+ const response = await this.makeRequest(url, options, timeout, debug);
98
+ if (shouldRetry && RETRY_STATUS_CODES.includes(response.status) && attempt < maxRetries) {
99
+ const delay = this.calculateRetryDelay(attempt, response.headers["retry-after"]);
100
+ if (debug) {
101
+ console.log(`[Sovant SDK] Got ${response.status}, retrying after ${delay}ms...`);
102
+ }
103
+ await this.sleep(delay);
104
+ attempt++;
105
+ continue;
106
+ }
107
+ if (response.status >= 400) {
108
+ throw SovantError.fromResponse(response.status, response.data, response.requestId);
109
+ }
110
+ return response;
111
+ } catch (error) {
112
+ lastError = error;
113
+ if (error instanceof SovantError) {
114
+ if (error.status && error.status < 500) {
115
+ throw error;
116
+ }
117
+ if (!shouldRetry || attempt >= maxRetries) {
118
+ throw error;
119
+ }
120
+ }
121
+ if (error instanceof TimeoutError || error instanceof NetworkError) {
122
+ if (shouldRetry && attempt < maxRetries) {
123
+ const delay = this.calculateRetryDelay(attempt);
124
+ if (debug) {
125
+ console.log(`[Sovant SDK] Network/Timeout error, retrying after ${delay}ms...`);
126
+ }
127
+ await this.sleep(delay);
128
+ attempt++;
129
+ continue;
130
+ }
131
+ }
132
+ throw error;
133
+ }
134
+ }
135
+ throw lastError || new NetworkError("Max retries exceeded");
136
+ }
137
+ async makeRequest(url, options, timeout, debug) {
138
+ const controller = new AbortController();
139
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
140
+ const headers = {
141
+ "Authorization": `Bearer ${this.apiKey}`,
142
+ "Content-Type": "application/json",
143
+ ...options.headers
144
+ };
145
+ const fetchOptions = {
146
+ method: options.method || "GET",
147
+ headers,
148
+ signal: controller.signal
149
+ };
150
+ if (options.body !== void 0 && options.method !== "GET" && options.method !== "HEAD") {
151
+ fetchOptions.body = typeof options.body === "string" ? options.body : JSON.stringify(options.body);
152
+ }
153
+ if (debug) {
154
+ console.log(`[Sovant SDK] ${fetchOptions.method} ${url}`);
155
+ if (options.body) {
156
+ console.log("[Sovant SDK] Body:", options.body);
157
+ }
158
+ }
159
+ try {
160
+ const response = await fetch(url, fetchOptions);
161
+ clearTimeout(timeoutId);
162
+ const responseHeaders = {};
163
+ response.headers.forEach((value, key) => {
164
+ responseHeaders[key.toLowerCase()] = value;
165
+ });
166
+ const requestId = responseHeaders["x-request-id"];
167
+ let data;
168
+ const contentType = responseHeaders["content-type"] || "";
169
+ if (response.status === 204) {
170
+ data = { ok: true };
171
+ } else if (contentType.includes("application/json")) {
172
+ const text = await response.text();
173
+ try {
174
+ data = text ? JSON.parse(text) : {};
175
+ } catch (e) {
176
+ data = { message: text };
177
+ }
178
+ } else {
179
+ data = await response.text();
180
+ }
181
+ if (debug) {
182
+ console.log(`[Sovant SDK] Response ${response.status}:`, data);
183
+ if (requestId) {
184
+ console.log(`[Sovant SDK] Request ID: ${requestId}`);
185
+ }
186
+ }
187
+ return {
188
+ data,
189
+ status: response.status,
190
+ headers: responseHeaders,
191
+ requestId
192
+ };
193
+ } catch (error) {
194
+ clearTimeout(timeoutId);
195
+ if (error instanceof Error) {
196
+ if (error.name === "AbortError") {
197
+ throw new TimeoutError(`Request timed out after ${timeout}ms`);
198
+ }
199
+ throw new NetworkError(`Network request failed: ${error.message}`, error);
200
+ }
201
+ throw error;
202
+ }
203
+ }
204
+ calculateRetryDelay(attempt, retryAfter) {
205
+ if (retryAfter) {
206
+ const retryAfterNum = parseInt(retryAfter, 10);
207
+ if (!isNaN(retryAfterNum)) {
208
+ return retryAfterNum * 1e3;
209
+ }
210
+ }
211
+ const baseDelay = Math.min(1e3 * Math.pow(2, attempt), 1e4);
212
+ const jitter = Math.random() * baseDelay * 0.1;
213
+ return Math.floor(baseDelay + jitter);
214
+ }
215
+ sleep(ms) {
216
+ return new Promise((resolve) => setTimeout(resolve, ms));
217
+ }
218
+ };
219
+
220
+ // src/sse-parser.ts
221
+ function parseSSE(buffer) {
222
+ const events = [];
223
+ const lines = buffer.split("\n");
224
+ let currentEvent = {};
225
+ let remaining = "";
226
+ for (let i = 0; i < lines.length; i++) {
227
+ const line = lines[i];
228
+ if (i === lines.length - 1 && !buffer.endsWith("\n")) {
229
+ remaining = line;
230
+ break;
231
+ }
232
+ if (line.trim() === "") {
233
+ if (currentEvent.data !== void 0) {
234
+ events.push({
235
+ event: currentEvent.event,
236
+ data: currentEvent.data,
237
+ id: currentEvent.id,
238
+ retry: currentEvent.retry
239
+ });
240
+ currentEvent = {};
241
+ }
242
+ continue;
243
+ }
244
+ if (line.startsWith(":")) {
245
+ continue;
246
+ }
247
+ const colonIndex = line.indexOf(":");
248
+ if (colonIndex === -1) {
249
+ const field2 = line.trim();
250
+ if (field2 === "data") {
251
+ currentEvent.data = "";
252
+ }
253
+ continue;
254
+ }
255
+ const field = line.substring(0, colonIndex).trim();
256
+ let value = line.substring(colonIndex + 1);
257
+ if (value.startsWith(" ")) {
258
+ value = value.substring(1);
259
+ }
260
+ switch (field) {
261
+ case "event":
262
+ currentEvent.event = value;
263
+ break;
264
+ case "data":
265
+ if (currentEvent.data === void 0) {
266
+ currentEvent.data = value;
267
+ } else {
268
+ currentEvent.data += "\n" + value;
269
+ }
270
+ break;
271
+ case "id":
272
+ currentEvent.id = value;
273
+ break;
274
+ case "retry":
275
+ const retryTime = parseInt(value, 10);
276
+ if (!isNaN(retryTime)) {
277
+ currentEvent.retry = retryTime;
278
+ }
279
+ break;
280
+ }
281
+ }
282
+ return {
283
+ events,
284
+ buffer: remaining
285
+ };
286
+ }
287
+
288
+ // src/index.ts
289
+ var Sovant = class {
290
+ constructor(config) {
291
+ if (!config.apiKey) {
292
+ throw new Error("API key is required");
293
+ }
294
+ const baseUrl = (config.baseUrl || "https://sovant.ai").replace(/\/$/, "");
295
+ this.http = new HttpClient(baseUrl, config.apiKey, {
296
+ timeout: config.timeout || 3e4,
297
+ maxRetries: config.maxRetries || 3,
298
+ debug: config.debug || false
299
+ });
300
+ this.memory = new MemoryNamespace(this.http);
301
+ this.threads = new ThreadsNamespace(this.http);
302
+ this.chat = new ChatNamespace(this.http);
303
+ this.keys = new KeysNamespace(this.http);
304
+ this.recall = new RecallNamespace(this.http);
305
+ }
306
+ };
307
+ var MemoryNamespace = class {
308
+ constructor(http) {
309
+ this.http = http;
310
+ }
311
+ /**
312
+ * Create a new memory
313
+ */
314
+ async create(input) {
315
+ const response = await this.http.request("/api/v1/memory", {
316
+ method: "POST",
317
+ body: input
318
+ });
319
+ const { ok, ...memory } = response.data;
320
+ return memory;
321
+ }
322
+ /**
323
+ * List memories with optional filters
324
+ */
325
+ async list(params) {
326
+ const queryParams = new URLSearchParams();
327
+ if (params?.limit !== void 0) queryParams.append("limit", params.limit.toString());
328
+ if (params?.offset !== void 0) queryParams.append("offset", params.offset.toString());
329
+ if (params?.type) queryParams.append("type", params.type);
330
+ if (params?.thread_id) queryParams.append("thread_id", params.thread_id);
331
+ if (params?.is_archived !== void 0) queryParams.append("is_archived", params.is_archived.toString());
332
+ if (params?.tags && params.tags.length > 0) {
333
+ params.tags.forEach((tag) => queryParams.append("tags", tag));
334
+ }
335
+ const query = queryParams.toString();
336
+ const path = query ? `/api/v1/memory?${query}` : "/api/v1/memory";
337
+ const response = await this.http.request(path, {
338
+ method: "GET"
339
+ });
340
+ return response.data;
341
+ }
342
+ /**
343
+ * Get a specific memory by ID
344
+ */
345
+ async get(id) {
346
+ if (!id) throw new Error("Memory ID is required");
347
+ const response = await this.http.request(`/api/v1/memories/${id}`, {
348
+ method: "GET"
349
+ });
350
+ return response.data;
351
+ }
352
+ /**
353
+ * Update a memory (partial update using PATCH)
354
+ */
355
+ async update(id, patch) {
356
+ if (!id) throw new Error("Memory ID is required");
357
+ const response = await this.http.request(`/api/v1/memories/${id}`, {
358
+ method: "PATCH",
359
+ body: patch
360
+ });
361
+ return response.data;
362
+ }
363
+ /**
364
+ * Replace a memory (full update using PUT)
365
+ */
366
+ async put(id, body) {
367
+ if (!id) throw new Error("Memory ID is required");
368
+ if (!body.content) throw new Error("Content is required for PUT operation");
369
+ if (!body.type) throw new Error("Type is required for PUT operation");
370
+ const response = await this.http.request(`/api/v1/memories/${id}`, {
371
+ method: "PUT",
372
+ body
373
+ });
374
+ return response.data;
375
+ }
376
+ /**
377
+ * Delete a memory
378
+ */
379
+ async delete(id) {
380
+ if (!id) throw new Error("Memory ID is required");
381
+ const response = await this.http.request(`/api/v1/memories/${id}`, {
382
+ method: "DELETE"
383
+ });
384
+ return response.data;
385
+ }
386
+ /**
387
+ * Search memories using semantic search or filters
388
+ */
389
+ async search(params) {
390
+ const queryParams = new URLSearchParams();
391
+ if (params.query) queryParams.append("query", params.query);
392
+ if (params.type) queryParams.append("type", params.type);
393
+ if (params.limit !== void 0) queryParams.append("limit", params.limit.toString());
394
+ if (params.thread_id) queryParams.append("thread_id", params.thread_id);
395
+ if (params.from_date) queryParams.append("from_date", params.from_date);
396
+ if (params.to_date) queryParams.append("to_date", params.to_date);
397
+ if (params.tags && params.tags.length > 0) {
398
+ params.tags.forEach((tag) => queryParams.append("tags", tag));
399
+ }
400
+ const query = queryParams.toString();
401
+ const path = `/api/v1/memory/search${query ? `?${query}` : ""}`;
402
+ const response = await this.http.request(path, {
403
+ method: "GET"
404
+ });
405
+ return response.data;
406
+ }
407
+ /**
408
+ * Execute batch operations atomically
409
+ */
410
+ async batch(body) {
411
+ if (!body.operations || body.operations.length === 0) {
412
+ throw new Error("At least one operation is required");
413
+ }
414
+ const apiBody = {
415
+ operations: body.operations.map((op) => ({
416
+ ...op,
417
+ operation: op.op,
418
+ op: void 0
419
+ }))
420
+ };
421
+ const response = await this.http.request("/api/v1/memory/batch", {
422
+ method: "POST",
423
+ body: apiBody
424
+ });
425
+ return response.data;
426
+ }
427
+ };
428
+ var ThreadsNamespace = class {
429
+ constructor(http) {
430
+ this.http = http;
431
+ }
432
+ /**
433
+ * Create a new thread
434
+ */
435
+ async create(input) {
436
+ const response = await this.http.request("/api/v1/threads", {
437
+ method: "POST",
438
+ body: input || {}
439
+ });
440
+ if (response.data.thread) {
441
+ return response.data.thread;
442
+ } else if (response.data.ok !== void 0) {
443
+ const { ok, ...thread } = response.data;
444
+ return thread;
445
+ } else {
446
+ return response.data;
447
+ }
448
+ }
449
+ /**
450
+ * List threads with pagination
451
+ */
452
+ async list(params) {
453
+ const queryParams = new URLSearchParams();
454
+ if (params?.limit) queryParams.append("limit", params.limit.toString());
455
+ if (params?.offset) queryParams.append("offset", params.offset.toString());
456
+ const path = queryParams.toString() ? `/api/v1/threads?${queryParams.toString()}` : "/api/v1/threads";
457
+ const response = await this.http.request(path, {
458
+ method: "GET"
459
+ });
460
+ return response.data;
461
+ }
462
+ /**
463
+ * Get a thread by ID, optionally with memories
464
+ */
465
+ async get(id, options) {
466
+ const queryParams = new URLSearchParams();
467
+ if (options?.includeMemories) queryParams.append("include", "memories");
468
+ if (options?.limit) queryParams.append("limit", options.limit.toString());
469
+ if (options?.offset) queryParams.append("offset", options.offset.toString());
470
+ const path = queryParams.toString() ? `/api/v1/threads/${id}?${queryParams.toString()}` : `/api/v1/threads/${id}`;
471
+ const response = await this.http.request(path, {
472
+ method: "GET"
473
+ });
474
+ return response.data;
475
+ }
476
+ /**
477
+ * Update a thread
478
+ */
479
+ async update(id, input) {
480
+ const response = await this.http.request(`/api/v1/threads/${id}`, {
481
+ method: "PATCH",
482
+ body: input
483
+ });
484
+ const { ok, ...thread } = response.data;
485
+ return thread;
486
+ }
487
+ /**
488
+ * Delete a thread (memories are unlinked, not deleted)
489
+ */
490
+ async delete(id) {
491
+ const response = await this.http.request(
492
+ `/api/v1/threads/${id}`,
493
+ {
494
+ method: "DELETE"
495
+ }
496
+ );
497
+ return response.data;
498
+ }
499
+ /**
500
+ * Link a memory to a thread
501
+ */
502
+ async linkMemory(threadId, memoryId) {
503
+ const response = await this.http.request(
504
+ `/api/v1/threads/${threadId}/link`,
505
+ {
506
+ method: "POST",
507
+ body: { memory_id: memoryId }
508
+ }
509
+ );
510
+ return response.data;
511
+ }
512
+ };
513
+ var ChatNamespace = class {
514
+ constructor(http) {
515
+ this.http = http;
516
+ }
517
+ /**
518
+ * Create a new chat session
519
+ */
520
+ async createSession(input) {
521
+ const response = await this.http.request("/api/v1/chat/sessions", {
522
+ method: "POST",
523
+ body: input || {}
524
+ });
525
+ return response.data.session;
526
+ }
527
+ /**
528
+ * Get a chat session by ID
529
+ */
530
+ async getSession(sessionId) {
531
+ const response = await this.http.request(`/api/v1/chat/sessions/${sessionId}`, {
532
+ method: "GET"
533
+ });
534
+ return response.data;
535
+ }
536
+ /**
537
+ * List chat sessions
538
+ */
539
+ async listSessions(params) {
540
+ const queryParams = new URLSearchParams();
541
+ if (params?.limit) queryParams.append("limit", params.limit.toString());
542
+ if (params?.offset) queryParams.append("offset", params.offset.toString());
543
+ const path = queryParams.toString() ? `/api/v1/chat/sessions?${queryParams.toString()}` : "/api/v1/chat/sessions";
544
+ const response = await this.http.request(path, {
545
+ method: "GET"
546
+ });
547
+ return response.data.sessions;
548
+ }
549
+ /**
550
+ * Get messages for a session
551
+ */
552
+ async getMessages(sessionId, limit) {
553
+ const queryParams = new URLSearchParams();
554
+ if (limit) queryParams.append("limit", limit.toString());
555
+ const path = queryParams.toString() ? `/api/v1/chat/sessions/${sessionId}/messages?${queryParams.toString()}` : `/api/v1/chat/sessions/${sessionId}/messages`;
556
+ const response = await this.http.request(path, {
557
+ method: "GET"
558
+ });
559
+ return response.data.messages;
560
+ }
561
+ /**
562
+ * Send a message with SSE streaming
563
+ */
564
+ async *sendMessage(sessionId, message, opts) {
565
+ const body = {
566
+ message,
567
+ provider: opts?.provider,
568
+ model: opts?.model,
569
+ useMemory: opts?.useMemory ?? true,
570
+ captureToMemory: opts?.captureToMemory ?? true
571
+ };
572
+ const response = await fetch(`${this.http.baseUrl}/api/v1/chat/sessions/${sessionId}/messages`, {
573
+ method: "POST",
574
+ headers: {
575
+ "authorization": `Bearer ${this.http.apiKey}`,
576
+ "content-type": "application/json",
577
+ "accept": "text/event-stream"
578
+ },
579
+ body: JSON.stringify(body),
580
+ signal: opts?.signal
581
+ });
582
+ if (!response.ok) {
583
+ const error = await response.json().catch(() => ({ message: "Request failed" }));
584
+ yield { type: "error", data: error.message || "Request failed" };
585
+ return;
586
+ }
587
+ const reader = response.body?.getReader();
588
+ if (!reader) {
589
+ yield { type: "error", data: "No response body" };
590
+ return;
591
+ }
592
+ const decoder = new TextDecoder();
593
+ let buffer = "";
594
+ try {
595
+ while (true) {
596
+ const { done, value } = await reader.read();
597
+ if (done) break;
598
+ buffer += decoder.decode(value, { stream: true });
599
+ const events = parseSSE(buffer);
600
+ buffer = events.buffer;
601
+ for (const event of events.events) {
602
+ if (event.data === "[DONE]") {
603
+ yield { type: "done" };
604
+ return;
605
+ }
606
+ try {
607
+ const parsed = JSON.parse(event.data);
608
+ if (parsed.content) {
609
+ yield { type: "delta", data: parsed.content };
610
+ }
611
+ } catch {
612
+ yield { type: "delta", data: event.data };
613
+ }
614
+ }
615
+ }
616
+ } catch (error) {
617
+ if (error.name === "AbortError") {
618
+ yield { type: "error", data: "Request cancelled" };
619
+ } else {
620
+ yield { type: "error", data: error.message };
621
+ }
622
+ } finally {
623
+ reader.releaseLock();
624
+ }
625
+ }
626
+ };
627
+ var KeysNamespace = class {
628
+ constructor(http) {
629
+ this.http = http;
630
+ }
631
+ /**
632
+ * List API keys
633
+ */
634
+ async list() {
635
+ const response = await this.http.request("/api/v1/keys", {
636
+ method: "GET"
637
+ });
638
+ return response.data.keys;
639
+ }
640
+ /**
641
+ * Create a new API key
642
+ */
643
+ async create(name) {
644
+ const response = await this.http.request("/api/v1/keys", {
645
+ method: "POST",
646
+ body: { name }
647
+ });
648
+ return response.data;
649
+ }
650
+ /**
651
+ * Update an API key
652
+ */
653
+ async update(id, patch) {
654
+ const response = await this.http.request("/api/v1/keys", {
655
+ method: "PATCH",
656
+ body: { id, ...patch }
657
+ });
658
+ return response.data;
659
+ }
660
+ /**
661
+ * Revoke an API key
662
+ */
663
+ async revoke(id) {
664
+ await this.http.request(`/api/v1/keys/${id}`, {
665
+ method: "DELETE"
666
+ });
667
+ }
668
+ };
669
+ var RecallNamespace = class {
670
+ constructor(http) {
671
+ this.http = http;
672
+ }
673
+ /**
674
+ * Extract profile entity from text (client-side)
675
+ */
676
+ extractProfile(text) {
677
+ const s = (text || "").toLowerCase().trim();
678
+ const sanitized = s.replace(/'/g, "'");
679
+ if (/\bmy\s+name\s+is\s+([a-z\-.''\s]{2,}?)(?:[.!?\s]|$)/i.test(sanitized)) {
680
+ const match = sanitized.match(/\bmy\s+name\s+is\s+([a-z\-.''\s]{2,}?)(?:[.!?\s]|$)/i);
681
+ if (match) {
682
+ const name = match[1].trim().replace(/\s+/g, " ").replace(/\.$/, "");
683
+ return { entity: "name", value: name };
684
+ }
685
+ }
686
+ if (/\b(i am|i'm|im)\s+(\d{1,3})\s*(years old)?/i.test(sanitized)) {
687
+ const match = sanitized.match(/\b(i am|i'm|im)\s+(\d{1,3})/i);
688
+ if (match) {
689
+ return { entity: "age", value: match[2] };
690
+ }
691
+ }
692
+ if (/\bmy\s+age\s+is\s+(\d{1,3})/i.test(sanitized)) {
693
+ const match = sanitized.match(/\bmy\s+age\s+is\s+(\d{1,3})/i);
694
+ if (match) {
695
+ return { entity: "age", value: match[1] };
696
+ }
697
+ }
698
+ const locationPatterns = [
699
+ /\bi\s+live\s+in\s+([a-z\-.''\s]{2,}?)(?:[.!?\s]|$)/i,
700
+ /\bi\s+am\s+based\s+in\s+([a-z\-.''\s]{2,}?)(?:[.!?\s]|$)/i,
701
+ /\bi'm\s+from\s+([a-z\-.''\s]{2,}?)(?:[.!?\s]|$)/i,
702
+ /\bi\s+am\s+from\s+([a-z\-.''\s]{2,}?)(?:[.!?\s]|$)/i,
703
+ /\bresiding\s+in\s+([a-z\-.''\s]{2,}?)(?:[.!?\s]|$)/i,
704
+ /\bcurrently\s+in\s+([a-z\-.''\s]{2,}?)(?:[.!?\s]|$)/i,
705
+ /\bmoved\s+to\s+([a-z\-.''\s]{2,}?)(?:[.!?\s]|$)/i,
706
+ /\blocated\s+in\s+([a-z\-.''\s]{2,}?)(?:[.!?\s]|$)/i
707
+ ];
708
+ for (const pattern of locationPatterns) {
709
+ if (pattern.test(sanitized)) {
710
+ const match = sanitized.match(pattern);
711
+ if (match) {
712
+ const location = match[1].trim().replace(/\s+/g, " ").replace(/\.$/, "");
713
+ return { entity: "location", value: location };
714
+ }
715
+ }
716
+ }
717
+ if (/\bi\s+(like|love|prefer)\s+([^.!?]{2,})/i.test(sanitized)) {
718
+ const match = sanitized.match(/\bi\s+(like|love|prefer)\s+([^.!?]{2,})/i);
719
+ if (match) {
720
+ const pref = match[2].trim().replace(/\s+/g, " ").replace(/\.$/, "");
721
+ return { entity: "preference", value: pref };
722
+ }
723
+ }
724
+ return { entity: null };
725
+ }
726
+ /**
727
+ * Get profile facts from memories
728
+ */
729
+ async getProfileFacts() {
730
+ const response = await this.http.request(
731
+ "/api/v1/memory/search?query=user%20name%20age%20location&limit=20",
732
+ { method: "GET" }
733
+ );
734
+ const facts = {
735
+ preferences: []
736
+ };
737
+ for (const memory of response.data.results) {
738
+ const content = memory.content.toLowerCase();
739
+ if (/^user\s+name\s+is\s+(.+)$/i.test(content)) {
740
+ const match = content.match(/^user\s+name\s+is\s+(.+)$/i);
741
+ if (match && !facts.name) {
742
+ facts.name = match[1].trim();
743
+ }
744
+ }
745
+ if (/^user\s+age\s+is\s+(\d+)$/i.test(content)) {
746
+ const match = content.match(/^user\s+age\s+is\s+(\d+)$/i);
747
+ if (match && !facts.age) {
748
+ facts.age = match[1];
749
+ }
750
+ }
751
+ if (/^user\s+location\s+is\s+(.+)$/i.test(content)) {
752
+ const match = content.match(/^user\s+location\s+is\s+(.+)$/i);
753
+ if (match && !facts.location) {
754
+ facts.location = match[1].trim();
755
+ }
756
+ }
757
+ if (/^user\s+(likes|loves|prefers)\s+(.+)$/i.test(content)) {
758
+ const match = content.match(/^user\s+(likes|loves|prefers)\s+(.+)$/i);
759
+ if (match) {
760
+ const pref = match[2].trim();
761
+ if (!facts.preferences?.includes(pref)) {
762
+ facts.preferences?.push(pref);
763
+ }
764
+ }
765
+ }
766
+ }
767
+ return facts;
768
+ }
769
+ /**
770
+ * Save a profile fact in canonical form
771
+ */
772
+ async saveProfileFact(kind, value) {
773
+ let content;
774
+ switch (kind) {
775
+ case "name":
776
+ content = `user name is ${value}`;
777
+ break;
778
+ case "age":
779
+ content = `user age is ${value}`;
780
+ break;
781
+ case "location":
782
+ content = `user location is ${value}`;
783
+ break;
784
+ case "preference":
785
+ content = `user likes ${value}`;
786
+ break;
787
+ default:
788
+ throw new Error(`Unknown profile entity: ${kind}`);
789
+ }
790
+ const response = await this.http.request("/api/v1/memory", {
791
+ method: "POST",
792
+ body: {
793
+ content,
794
+ type: "preference",
795
+ tags: ["profile", kind]
796
+ }
797
+ });
798
+ const { ok, ...memory } = response.data;
799
+ return memory;
800
+ }
801
+ };
802
+ var index_default = Sovant;
803
+ export {
804
+ NetworkError,
805
+ Sovant,
806
+ SovantError,
807
+ TimeoutError,
808
+ index_default as default
809
+ };