@usewhisper/sdk 3.4.0 → 3.6.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.
Files changed (5) hide show
  1. package/index.d.mts +249 -1
  2. package/index.d.ts +249 -1
  3. package/index.js +1304 -541
  4. package/index.mjs +1304 -541
  5. package/package.json +1 -1
package/index.mjs CHANGED
@@ -379,368 +379,83 @@ var RuntimeClient = class {
379
379
  }
380
380
  };
381
381
 
382
- // ../src/sdk/whisper-agent.ts
383
- var DEPRECATION_WARNINGS = /* @__PURE__ */ new Set();
384
- function warnDeprecatedOnce(key, message) {
385
- if (DEPRECATION_WARNINGS.has(key)) return;
386
- DEPRECATION_WARNINGS.add(key);
387
- if (typeof console !== "undefined" && typeof console.warn === "function") {
388
- console.warn(message);
382
+ // ../src/sdk/core/cache.ts
383
+ var SearchResponseCache = class {
384
+ ttlMs;
385
+ capacity;
386
+ byKey = /* @__PURE__ */ new Map();
387
+ scopeIndex = /* @__PURE__ */ new Map();
388
+ constructor(ttlMs = 7e3, capacity = 500) {
389
+ this.ttlMs = Math.max(1e3, ttlMs);
390
+ this.capacity = Math.max(10, capacity);
389
391
  }
390
- }
391
- var Whisper = class {
392
- client;
393
- options;
394
- sessionId;
395
- userId;
396
- constructor(options) {
397
- if (!options.apiKey) {
398
- throw new Error("API key is required");
399
- }
400
- const clientConfig = {
401
- apiKey: options.apiKey,
402
- baseUrl: options.baseUrl,
403
- project: options.project || "default"
404
- };
405
- if (options.timeoutMs) clientConfig.timeoutMs = options.timeoutMs;
406
- if (options.retry) clientConfig.retry = options.retry;
407
- this.client = new WhisperContext(clientConfig);
408
- warnDeprecatedOnce(
409
- "whisper_agent_wrapper",
410
- "[Whisper SDK] Whisper wrapper is supported for v2 compatibility. Prefer WhisperClient for new integrations."
411
- );
412
- const finalRetry = options.retry || { maxAttempts: 3, baseDelayMs: 250, maxDelayMs: 2e3 };
413
- this.options = {
414
- apiKey: options.apiKey,
415
- baseUrl: options.baseUrl || "https://context.usewhisper.dev",
416
- project: options.project || "default",
417
- timeoutMs: options.timeoutMs || 15e3,
418
- retry: finalRetry,
419
- contextLimit: options.contextLimit ?? 10,
420
- memoryTypes: options.memoryTypes ?? ["factual", "preference", "event", "goal", "relationship", "opinion", "instruction"],
421
- contextPrefix: options.contextPrefix ?? "Relevant context:",
422
- autoExtract: options.autoExtract ?? true,
423
- autoExtractMinConfidence: options.autoExtractMinConfidence ?? 0.65,
424
- maxMemoriesPerCapture: options.maxMemoriesPerCapture ?? 5
425
- };
392
+ makeScopeKey(project, userId, sessionId) {
393
+ return `${project}:${userId || "_"}:${sessionId || "_"}`;
426
394
  }
427
- /**
428
- * Set session ID for conversation tracking
429
- */
430
- session(sessionId) {
431
- this.sessionId = sessionId;
432
- return this;
395
+ makeKey(input) {
396
+ const normalized = {
397
+ project: input.project,
398
+ userId: input.userId || "",
399
+ sessionId: input.sessionId || "",
400
+ query: normalizeQuery(input.query),
401
+ topK: input.topK,
402
+ profile: input.profile,
403
+ includePending: input.includePending
404
+ };
405
+ return `search:${stableHash(JSON.stringify(normalized))}`;
433
406
  }
434
- /**
435
- * Set user ID for user-specific memories
436
- */
437
- user(userId) {
438
- this.userId = userId;
439
- return this;
407
+ get(key) {
408
+ const found = this.byKey.get(key);
409
+ if (!found) return null;
410
+ if (found.expiresAt <= Date.now()) {
411
+ this.deleteByKey(key);
412
+ return null;
413
+ }
414
+ found.touchedAt = Date.now();
415
+ return found.value;
440
416
  }
441
- /**
442
- * Get relevant context BEFORE your LLM call
443
- *
444
- * @param query - What you want to know / user question
445
- * @returns Context string and raw results
446
- *
447
- * @example
448
- * ```typescript
449
- * const { context, results, count } = await whisper.getContext(
450
- * "What are user's preferences?",
451
- * { userId: "user-123" }
452
- * );
453
- *
454
- * // Results: [
455
- * // { content: "User prefers dark mode", type: "preference", score: 0.95 },
456
- * // { content: "Allergic to nuts", type: "factual", score: 0.89 }
457
- * // ]
458
- * ```
459
- */
460
- async getContext(query, options) {
461
- const result = await this.client.query({
462
- project: options?.project ?? this.options.project,
463
- query,
464
- top_k: options?.limit ?? this.options.contextLimit,
465
- include_memories: true,
466
- user_id: options?.userId ?? this.userId,
467
- session_id: options?.sessionId ?? this.sessionId
417
+ set(key, scopeKey, value) {
418
+ this.byKey.set(key, {
419
+ value,
420
+ scopeKey,
421
+ touchedAt: Date.now(),
422
+ expiresAt: Date.now() + this.ttlMs
468
423
  });
469
- const context = result.results.map((r, i) => `[${i + 1}] ${r.content}`).join("\n");
470
- return {
471
- context: context ? `${this.options.contextPrefix}
472
- ${context}` : "",
473
- results: result.results,
474
- count: result.meta.total
475
- };
424
+ if (!this.scopeIndex.has(scopeKey)) {
425
+ this.scopeIndex.set(scopeKey, /* @__PURE__ */ new Set());
426
+ }
427
+ this.scopeIndex.get(scopeKey).add(key);
428
+ this.evictIfNeeded();
476
429
  }
477
- /**
478
- * Remember what happened AFTER your LLM response
479
- *
480
- * Fire-and-forget - doesn't block your response
481
- *
482
- * @param content - What your LLM responded with
483
- * @returns Promise that resolves when stored (or fails silently)
484
- *
485
- * @example
486
- * ```typescript
487
- * const llmResponse = "I've set your theme to dark mode and removed nuts from recommendations.";
488
- *
489
- * await whisper.remember(llmResponse, { userId: "user-123" });
490
- * // → Auto-extracts: "theme set to dark mode", "nut allergy"
491
- * // → Stored as preferences
492
- * ```
493
- */
494
- async remember(content, options) {
495
- if (!content || content.length < 5) {
496
- return { success: false };
430
+ invalidateScope(scopeKey) {
431
+ const keys = this.scopeIndex.get(scopeKey);
432
+ if (!keys || keys.size === 0) {
433
+ return 0;
497
434
  }
498
- try {
499
- if (this.options.autoExtract) {
500
- const extraction = await this.client.extractMemories({
501
- project: options?.project ?? this.options.project,
502
- message: content,
503
- user_id: options?.userId ?? this.userId,
504
- session_id: options?.sessionId ?? this.sessionId,
505
- enable_pattern: true,
506
- enable_inference: true,
507
- min_confidence: this.options.autoExtractMinConfidence
508
- });
509
- const extractedMemories = (extraction.all || []).filter((m) => (m.confidence || 0) >= this.options.autoExtractMinConfidence).slice(0, this.options.maxMemoriesPerCapture);
510
- if (extractedMemories.length > 0) {
511
- const bulk = await this.client.addMemoriesBulk({
512
- project: options?.project ?? this.options.project,
513
- write_mode: "async",
514
- memories: extractedMemories.map((m) => ({
515
- content: m.content,
516
- memory_type: m.memoryType,
517
- user_id: options?.userId ?? this.userId,
518
- session_id: options?.sessionId ?? this.sessionId,
519
- importance: Math.max(0.5, Math.min(1, m.confidence || 0.7)),
520
- confidence: m.confidence || 0.7,
521
- entity_mentions: m.entityMentions || [],
522
- event_date: m.eventDate || void 0,
523
- metadata: {
524
- extracted: true,
525
- extraction_method: extraction.extractionMethod,
526
- extraction_reasoning: m.reasoning,
527
- inferred: Boolean(m.inferred)
528
- }
529
- }))
530
- });
531
- const memoryIds = this.extractMemoryIdsFromBulkResponse(bulk);
532
- return {
533
- success: true,
534
- memoryId: memoryIds[0],
535
- memoryIds: memoryIds.length > 0 ? memoryIds : void 0,
536
- extracted: extractedMemories.length
537
- };
538
- }
539
- }
540
- const result = await this.client.addMemory({
541
- project: options?.project ?? this.options.project,
542
- content,
543
- user_id: options?.userId ?? this.userId,
544
- session_id: options?.sessionId ?? this.sessionId
545
- });
546
- return {
547
- success: true,
548
- memoryId: result?.id
549
- };
550
- } catch (error) {
551
- console.error("[Whisper] Remember failed:", error);
552
- return { success: false };
435
+ const toDelete = Array.from(keys);
436
+ for (const key of toDelete) {
437
+ this.deleteByKey(key);
553
438
  }
439
+ this.scopeIndex.delete(scopeKey);
440
+ return toDelete.length;
554
441
  }
555
- /**
556
- * Alias for remember() - same thing
557
- */
558
- async capture(content, options) {
559
- return this.remember(content, options);
442
+ evictIfNeeded() {
443
+ if (this.byKey.size <= this.capacity) return;
444
+ const ordered = Array.from(this.byKey.entries()).sort((a, b) => a[1].touchedAt - b[1].touchedAt);
445
+ const removeCount = this.byKey.size - this.capacity;
446
+ for (let i = 0; i < removeCount; i += 1) {
447
+ this.deleteByKey(ordered[i][0]);
448
+ }
560
449
  }
561
- /**
562
- * Capture from multiple messages (e.g., full conversation)
563
- */
564
- async captureSession(messages, options) {
565
- try {
566
- const result = await this.client.ingestSession({
567
- project: options?.project ?? this.options.project,
568
- session_id: options?.sessionId ?? this.sessionId ?? "default",
569
- user_id: options?.userId ?? this.userId,
570
- messages: messages.filter((m) => m.role !== "system").map((m) => ({
571
- role: m.role,
572
- content: m.content,
573
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
574
- }))
575
- });
576
- return {
577
- success: true,
578
- extracted: result?.memories_created ?? 0
579
- };
580
- } catch (error) {
581
- const fallback = await this.fallbackCaptureViaAddMemory(messages, options);
582
- if (fallback.success) {
583
- return fallback;
584
- }
585
- console.error("[Whisper] Session capture failed:", error);
586
- return { success: false, extracted: 0 };
587
- }
588
- }
589
- /**
590
- * Run a full agent turn with automatic memory read (before) + write (after).
591
- */
592
- async runTurn(params) {
593
- const contextResult = await this.getContext(params.userMessage, {
594
- userId: params.userId,
595
- sessionId: params.sessionId,
596
- project: params.project,
597
- limit: params.limit
598
- });
599
- const prompt = contextResult.context ? `${contextResult.context}
600
-
601
- User: ${params.userMessage}` : params.userMessage;
602
- const response = await params.generate(prompt);
603
- const captureResult = await this.captureSession(
604
- [
605
- { role: "user", content: params.userMessage },
606
- { role: "assistant", content: response }
607
- ],
608
- {
609
- userId: params.userId,
610
- sessionId: params.sessionId,
611
- project: params.project
612
- }
613
- );
614
- return {
615
- response,
616
- context: contextResult.context,
617
- count: contextResult.count,
618
- extracted: captureResult.extracted
619
- };
620
- }
621
- /**
622
- * Direct access to WhisperContext for advanced usage
623
- */
624
- raw() {
625
- return this.client;
626
- }
627
- extractMemoryIdsFromBulkResponse(bulkResponse) {
628
- const ids = [];
629
- if (Array.isArray(bulkResponse?.memories)) {
630
- for (const memory of bulkResponse.memories) {
631
- if (memory?.id) ids.push(memory.id);
632
- }
633
- }
634
- if (bulkResponse?.memory?.id) {
635
- ids.push(bulkResponse.memory.id);
636
- }
637
- if (bulkResponse?.id) {
638
- ids.push(bulkResponse.id);
639
- }
640
- return Array.from(new Set(ids));
641
- }
642
- async fallbackCaptureViaAddMemory(messages, options) {
643
- const userMessages = messages.filter((m) => m.role === "user").map((m) => (m.content || "").trim()).filter((content) => content.length >= 5).slice(-2);
644
- if (userMessages.length === 0) {
645
- return { success: false, extracted: 0 };
646
- }
647
- let extracted = 0;
648
- for (const content of userMessages) {
649
- try {
650
- await this.client.addMemory({
651
- project: options?.project ?? this.options.project,
652
- content,
653
- memory_type: "factual",
654
- user_id: options?.userId ?? this.userId,
655
- session_id: options?.sessionId ?? this.sessionId,
656
- allow_legacy_fallback: true
657
- });
658
- extracted += 1;
659
- } catch {
660
- }
661
- }
662
- return { success: extracted > 0, extracted };
663
- }
664
- };
665
- var whisper_agent_default = Whisper;
666
-
667
- // ../src/sdk/core/cache.ts
668
- var SearchResponseCache = class {
669
- ttlMs;
670
- capacity;
671
- byKey = /* @__PURE__ */ new Map();
672
- scopeIndex = /* @__PURE__ */ new Map();
673
- constructor(ttlMs = 7e3, capacity = 500) {
674
- this.ttlMs = Math.max(1e3, ttlMs);
675
- this.capacity = Math.max(10, capacity);
676
- }
677
- makeScopeKey(project, userId, sessionId) {
678
- return `${project}:${userId || "_"}:${sessionId || "_"}`;
679
- }
680
- makeKey(input) {
681
- const normalized = {
682
- project: input.project,
683
- userId: input.userId || "",
684
- sessionId: input.sessionId || "",
685
- query: normalizeQuery(input.query),
686
- topK: input.topK,
687
- profile: input.profile,
688
- includePending: input.includePending
689
- };
690
- return `search:${stableHash(JSON.stringify(normalized))}`;
691
- }
692
- get(key) {
693
- const found = this.byKey.get(key);
694
- if (!found) return null;
695
- if (found.expiresAt <= Date.now()) {
696
- this.deleteByKey(key);
697
- return null;
698
- }
699
- found.touchedAt = Date.now();
700
- return found.value;
701
- }
702
- set(key, scopeKey, value) {
703
- this.byKey.set(key, {
704
- value,
705
- scopeKey,
706
- touchedAt: Date.now(),
707
- expiresAt: Date.now() + this.ttlMs
708
- });
709
- if (!this.scopeIndex.has(scopeKey)) {
710
- this.scopeIndex.set(scopeKey, /* @__PURE__ */ new Set());
711
- }
712
- this.scopeIndex.get(scopeKey).add(key);
713
- this.evictIfNeeded();
714
- }
715
- invalidateScope(scopeKey) {
716
- const keys = this.scopeIndex.get(scopeKey);
717
- if (!keys || keys.size === 0) {
718
- return 0;
719
- }
720
- const toDelete = Array.from(keys);
721
- for (const key of toDelete) {
722
- this.deleteByKey(key);
723
- }
724
- this.scopeIndex.delete(scopeKey);
725
- return toDelete.length;
726
- }
727
- evictIfNeeded() {
728
- if (this.byKey.size <= this.capacity) return;
729
- const ordered = Array.from(this.byKey.entries()).sort((a, b) => a[1].touchedAt - b[1].touchedAt);
730
- const removeCount = this.byKey.size - this.capacity;
731
- for (let i = 0; i < removeCount; i += 1) {
732
- this.deleteByKey(ordered[i][0]);
733
- }
734
- }
735
- deleteByKey(key) {
736
- const found = this.byKey.get(key);
737
- if (!found) return;
738
- this.byKey.delete(key);
739
- const scopeKeys = this.scopeIndex.get(found.scopeKey);
740
- if (!scopeKeys) return;
741
- scopeKeys.delete(key);
742
- if (scopeKeys.size === 0) {
743
- this.scopeIndex.delete(found.scopeKey);
450
+ deleteByKey(key) {
451
+ const found = this.byKey.get(key);
452
+ if (!found) return;
453
+ this.byKey.delete(key);
454
+ const scopeKeys = this.scopeIndex.get(found.scopeKey);
455
+ if (!scopeKeys) return;
456
+ scopeKeys.delete(key);
457
+ if (scopeKeys.size === 0) {
458
+ this.scopeIndex.delete(found.scopeKey);
744
459
  }
745
460
  }
746
461
  };
@@ -1550,204 +1265,1252 @@ var AnalyticsModule = class {
1550
1265
  }
1551
1266
  };
1552
1267
 
1553
- // ../src/sdk/whisper.ts
1554
- var WhisperClient = class _WhisperClient {
1555
- constructor(config) {
1556
- this.config = config;
1557
- this.diagnosticsStore = new DiagnosticsStore(config.telemetry?.maxEntries || 1e3);
1558
- this.runtimeClient = new RuntimeClient(
1559
- {
1560
- apiKey: config.apiKey,
1561
- baseUrl: config.baseUrl,
1562
- compatMode: config.compatMode || "fallback",
1563
- timeouts: config.timeouts,
1564
- retryPolicy: config.retryPolicy,
1565
- fetchImpl: config.fetch
1268
+ // ../src/sdk/agent-runtime.ts
1269
+ function detectBrowserStorage() {
1270
+ const maybeStorage = globalThis.localStorage;
1271
+ if (!maybeStorage || typeof maybeStorage !== "object") return null;
1272
+ const candidate = maybeStorage;
1273
+ if (typeof candidate.getItem !== "function" || typeof candidate.setItem !== "function") {
1274
+ return null;
1275
+ }
1276
+ return {
1277
+ getItem: candidate.getItem,
1278
+ setItem: candidate.setItem
1279
+ };
1280
+ }
1281
+ function createBindingStore(filePath) {
1282
+ const storage = detectBrowserStorage();
1283
+ if (storage) {
1284
+ const key = "whisper_agent_runtime_bindings";
1285
+ return {
1286
+ async load() {
1287
+ const raw = storage.getItem(key);
1288
+ if (!raw) return {};
1289
+ try {
1290
+ const parsed = JSON.parse(raw);
1291
+ return parsed && typeof parsed === "object" ? parsed : {};
1292
+ } catch {
1293
+ return {};
1294
+ }
1566
1295
  },
1567
- this.diagnosticsStore
1568
- );
1569
- this.searchCache = new SearchResponseCache(
1570
- config.cache?.ttlMs ?? 7e3,
1571
- config.cache?.capacity ?? 500
1296
+ async save(bindings) {
1297
+ storage.setItem(key, JSON.stringify(bindings));
1298
+ }
1299
+ };
1300
+ }
1301
+ return {
1302
+ async load() {
1303
+ if (typeof process === "undefined") return {};
1304
+ const fs = await import("fs/promises");
1305
+ const path = filePath || `${process.env.USERPROFILE || process.env.HOME || "."}/.whisper/sdk/agent-bindings.json`;
1306
+ try {
1307
+ const raw = await fs.readFile(path, "utf8");
1308
+ const parsed = JSON.parse(raw);
1309
+ return parsed && typeof parsed === "object" ? parsed : {};
1310
+ } catch {
1311
+ return {};
1312
+ }
1313
+ },
1314
+ async save(bindings) {
1315
+ if (typeof process === "undefined") return;
1316
+ const fs = await import("fs/promises");
1317
+ const pathMod = await import("path");
1318
+ const path = filePath || `${process.env.USERPROFILE || process.env.HOME || "."}/.whisper/sdk/agent-bindings.json`;
1319
+ await fs.mkdir(pathMod.dirname(path), { recursive: true });
1320
+ await fs.writeFile(path, JSON.stringify(bindings), "utf8");
1321
+ }
1322
+ };
1323
+ }
1324
+ function normalizeWorkspacePath(value) {
1325
+ const trimmed = value?.trim();
1326
+ if (!trimmed) return null;
1327
+ return trimmed.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
1328
+ }
1329
+ function pathBase(value) {
1330
+ const normalized = value.replace(/\\/g, "/");
1331
+ const parts = normalized.split("/").filter(Boolean);
1332
+ return parts[parts.length - 1] || normalized;
1333
+ }
1334
+ function defaultSalience(kind, success) {
1335
+ if (kind === "decision" || kind === "constraint" || kind === "failure") return "high";
1336
+ if (kind === "outcome" || kind === "task_update") return "medium";
1337
+ if (kind === "tool_result" && success === false) return "high";
1338
+ return "low";
1339
+ }
1340
+ function toMemoryType(kind) {
1341
+ if (kind === "decision" || kind === "constraint") return "instruction";
1342
+ return "event";
1343
+ }
1344
+ function summarizeLowSalience(events) {
1345
+ const lines = events.slice(-10).map((event) => {
1346
+ const fileSuffix = event.filePaths?.length ? ` [files: ${event.filePaths.join(", ")}]` : "";
1347
+ const toolSuffix = event.toolName ? ` [tool: ${event.toolName}]` : "";
1348
+ return `- ${event.kind}: ${event.summary}${fileSuffix}${toolSuffix}`;
1349
+ });
1350
+ return `Recent low-salience work:
1351
+ ${lines.join("\n")}`;
1352
+ }
1353
+ function compactWhitespace(value) {
1354
+ return value.replace(/\s+/g, " ").trim();
1355
+ }
1356
+ function withTimeout(promise, timeoutMs) {
1357
+ return new Promise((resolve, reject) => {
1358
+ const timeout = setTimeout(() => {
1359
+ reject(new Error("timeout"));
1360
+ }, timeoutMs);
1361
+ promise.then(
1362
+ (value) => {
1363
+ clearTimeout(timeout);
1364
+ resolve(value);
1365
+ },
1366
+ (error) => {
1367
+ clearTimeout(timeout);
1368
+ reject(error);
1369
+ }
1572
1370
  );
1573
- const queueStore = config.queue?.persistence === "storage" ? createStorageQueueStore() : config.queue?.persistence === "file" && config.queue.filePath ? createFileQueueStore(config.queue.filePath) : new InMemoryQueueStore();
1574
- this.writeQueue = new WriteQueue({
1575
- store: queueStore,
1576
- maxBatchSize: config.queue?.maxBatchSize ?? 50,
1577
- flushIntervalMs: config.queue?.flushIntervalMs ?? 100,
1578
- maxAttempts: config.queue?.maxAttempts ?? 2,
1579
- flushHandler: async (items) => {
1580
- if (items.length === 0) return;
1581
- const project = items[0].project;
1582
- const memories = items.map((item) => ({
1583
- ...item.payload,
1584
- user_id: item.payload.user_id ?? item.userId,
1585
- session_id: item.payload.session_id ?? item.sessionId,
1586
- metadata: {
1587
- ...item.payload.metadata || {},
1588
- event_id: item.eventId,
1589
- queued_at: item.createdAt
1590
- }
1591
- }));
1592
- try {
1593
- await this.runtimeClient.request({
1594
- endpoint: "/v1/memory/bulk",
1595
- method: "POST",
1596
- operation: "bulk",
1597
- body: {
1598
- project,
1599
- write_mode: "async",
1600
- memories
1601
- }
1602
- });
1603
- } catch (error) {
1604
- if (this.runtimeClient.getCompatMode() !== "fallback" || !(error instanceof RuntimeClientError) || error.status !== 404) {
1605
- throw error;
1606
- }
1607
- await Promise.all(
1608
- memories.map(async (memory) => {
1609
- try {
1610
- await this.runtimeClient.request({
1611
- endpoint: "/v1/memory",
1612
- method: "POST",
1613
- operation: "writeAck",
1614
- body: {
1615
- project,
1616
- ...memory,
1617
- write_mode: "sync"
1618
- }
1619
- });
1620
- } catch (fallbackError) {
1621
- if (this.runtimeClient.getCompatMode() !== "fallback" || !(fallbackError instanceof RuntimeClientError) || fallbackError.status !== 404) {
1622
- throw fallbackError;
1623
- }
1624
- await this.runtimeClient.request({
1625
- endpoint: "/v1/memories",
1626
- method: "POST",
1627
- operation: "writeAck",
1628
- body: {
1629
- project,
1371
+ });
1372
+ }
1373
+ function extractTimestamp(metadata) {
1374
+ const candidates = [
1375
+ metadata?.updatedAt,
1376
+ metadata?.createdAt,
1377
+ metadata?.timestamp,
1378
+ metadata?.event_date,
1379
+ metadata?.eventDate
1380
+ ];
1381
+ for (const value of candidates) {
1382
+ if (typeof value === "string") {
1383
+ const parsed = Date.parse(value);
1384
+ if (!Number.isNaN(parsed)) return parsed;
1385
+ }
1386
+ }
1387
+ return 0;
1388
+ }
1389
+ function salienceBoost(metadata) {
1390
+ const value = metadata?.salience;
1391
+ if (value === "high") return 0.12;
1392
+ if (value === "medium") return 0.06;
1393
+ return 0;
1394
+ }
1395
+ var WhisperAgentRuntime = class {
1396
+ constructor(args) {
1397
+ this.args = args;
1398
+ this.bindingStore = createBindingStore(args.options.bindingStorePath);
1399
+ this.topK = args.options.topK ?? 6;
1400
+ this.maxTokens = args.options.maxTokens ?? 4e3;
1401
+ this.targetRetrievalMs = args.options.targetRetrievalMs ?? 2500;
1402
+ this.hardRetrievalTimeoutMs = args.options.hardRetrievalTimeoutMs ?? 4e3;
1403
+ this.recentWorkLimit = args.options.recentWorkLimit ?? 40;
1404
+ this.baseContext = args.baseContext;
1405
+ this.clientName = args.baseContext.clientName || "whisper-agent-runtime";
1406
+ }
1407
+ bindingStore;
1408
+ topK;
1409
+ maxTokens;
1410
+ targetRetrievalMs;
1411
+ hardRetrievalTimeoutMs;
1412
+ recentWorkLimit;
1413
+ baseContext;
1414
+ clientName;
1415
+ bindings = null;
1416
+ touchedFiles = [];
1417
+ recentWork = [];
1418
+ bufferedLowSalience = [];
1419
+ lastPreparedTurn = null;
1420
+ mergedCount = 0;
1421
+ droppedCount = 0;
1422
+ lastScope = {};
1423
+ async getBindings() {
1424
+ if (!this.bindings) {
1425
+ this.bindings = await this.bindingStore.load();
1426
+ }
1427
+ return this.bindings;
1428
+ }
1429
+ pushTouchedFiles(paths) {
1430
+ if (!paths || paths.length === 0) return;
1431
+ for (const path of paths) {
1432
+ if (!path) continue;
1433
+ this.touchedFiles = [...this.touchedFiles.filter((entry) => entry !== path), path].slice(-20);
1434
+ }
1435
+ }
1436
+ pushWorkEvent(event) {
1437
+ this.recentWork = [...this.recentWork, event].slice(-this.recentWorkLimit);
1438
+ }
1439
+ makeTaskFrameQuery(input) {
1440
+ const task = compactWhitespace(input.taskSummary || "");
1441
+ const salient = this.recentWork.filter((event) => event.salience === "high").slice(-3).map((event) => `${event.kind}: ${event.summary}`);
1442
+ const files = [...input.touchedFiles || [], ...this.touchedFiles].slice(-3).map((file) => pathBase(file));
1443
+ const parts = [
1444
+ task ? `task ${task}` : "",
1445
+ salient.length > 0 ? `recent ${salient.join(" ; ")}` : "",
1446
+ files.length > 0 ? `files ${files.join(" ")}` : "",
1447
+ input.toolContext ? `tool context ${compactWhitespace(input.toolContext)}` : ""
1448
+ ].filter(Boolean);
1449
+ if (parts.length === 0) return null;
1450
+ return parts.join(" | ");
1451
+ }
1452
+ async resolveScope(overrides) {
1453
+ const merged = {
1454
+ ...this.baseContext,
1455
+ ...overrides
1456
+ };
1457
+ const normalizedWorkspace = normalizeWorkspacePath(merged.workspacePath);
1458
+ const bindings = await this.getBindings();
1459
+ const workspaceProject = normalizedWorkspace ? bindings[normalizedWorkspace] : void 0;
1460
+ const configuredProject = merged.project;
1461
+ let projectRef = configuredProject;
1462
+ let projectSource = overrides?.project ? "explicit" : "generated";
1463
+ let warning;
1464
+ if (workspaceProject) {
1465
+ projectRef = workspaceProject;
1466
+ projectSource = "workspace";
1467
+ if (configuredProject && workspaceProject !== configuredProject) {
1468
+ warning = `workspace mapping '${workspaceProject}' overrides configured project '${configuredProject}'`;
1469
+ }
1470
+ } else if (configuredProject) {
1471
+ projectRef = configuredProject;
1472
+ projectSource = overrides?.project ? "explicit" : "config";
1473
+ }
1474
+ const project = (await this.args.adapter.resolveProject(projectRef)).id;
1475
+ if (normalizedWorkspace) {
1476
+ bindings[normalizedWorkspace] = project;
1477
+ await this.bindingStore.save(bindings);
1478
+ }
1479
+ const scope = {
1480
+ ...merged,
1481
+ project,
1482
+ userId: merged.userId || `${this.clientName}-user`,
1483
+ sessionId: merged.sessionId || `sess_${stableHash(`${this.clientName}_${normalizedWorkspace || "default"}`)}`
1484
+ };
1485
+ this.lastScope = {
1486
+ project: scope.project,
1487
+ userId: scope.userId,
1488
+ sessionId: scope.sessionId,
1489
+ source: projectSource,
1490
+ warning
1491
+ };
1492
+ return { scope, projectSource, warning };
1493
+ }
1494
+ async runBranch(name, task) {
1495
+ const startedAt = Date.now();
1496
+ try {
1497
+ const value = await withTimeout(task(), this.hardRetrievalTimeoutMs);
1498
+ return {
1499
+ name,
1500
+ status: "ok",
1501
+ durationMs: Date.now() - startedAt,
1502
+ value
1503
+ };
1504
+ } catch (error) {
1505
+ const durationMs = Date.now() - startedAt;
1506
+ const reason = error instanceof Error ? error.message : String(error);
1507
+ return {
1508
+ name,
1509
+ status: reason === "timeout" ? "timeout" : "error",
1510
+ durationMs,
1511
+ reason
1512
+ };
1513
+ }
1514
+ }
1515
+ contextItems(result, sourceQuery) {
1516
+ return (result.results || []).map((item) => ({
1517
+ id: item.id,
1518
+ content: item.content,
1519
+ type: "project",
1520
+ score: item.score ?? 0,
1521
+ sourceQuery,
1522
+ metadata: item.metadata || {}
1523
+ }));
1524
+ }
1525
+ memoryItems(result, sourceQuery) {
1526
+ return (result.results || []).map((item, index) => ({
1527
+ id: item.memory?.id || item.chunk?.id || `${sourceQuery}_memory_${index}`,
1528
+ content: item.chunk?.content || item.memory?.content || "",
1529
+ type: "memory",
1530
+ score: item.similarity ?? 0,
1531
+ sourceQuery,
1532
+ metadata: {
1533
+ ...item.chunk?.metadata || {},
1534
+ ...item.memory?.temporal || {},
1535
+ confidence: item.memory?.confidence
1536
+ }
1537
+ })).filter((item) => item.content);
1538
+ }
1539
+ rerank(items) {
1540
+ const deduped = /* @__PURE__ */ new Map();
1541
+ for (const item of items) {
1542
+ const key = `${item.id}:${item.content.toLowerCase()}`;
1543
+ const recency = extractTimestamp(item.metadata) > 0 ? 0.04 : 0;
1544
+ const queryBonus = item.sourceQuery === "primary" ? 0.08 : item.sourceQuery === "task_frame" ? 0.04 : 0.03;
1545
+ const next = {
1546
+ ...item,
1547
+ score: item.score + queryBonus + salienceBoost(item.metadata) + recency
1548
+ };
1549
+ const existing = deduped.get(key);
1550
+ if (!existing || next.score > existing.score) {
1551
+ deduped.set(key, next);
1552
+ }
1553
+ }
1554
+ return [...deduped.values()].sort((left, right) => right.score - left.score);
1555
+ }
1556
+ buildContext(items) {
1557
+ const maxChars = this.maxTokens * 4;
1558
+ const lines = [];
1559
+ let used = 0;
1560
+ for (const item of items) {
1561
+ const label = item.type === "memory" ? "memory" : "context";
1562
+ const content = compactWhitespace(item.content);
1563
+ if (!content) continue;
1564
+ const line = `[${label}] ${content}`;
1565
+ if (used + line.length > maxChars) break;
1566
+ lines.push(line);
1567
+ used += line.length;
1568
+ }
1569
+ if (lines.length === 0) return "";
1570
+ return `Relevant context:
1571
+ ${lines.join("\n")}`;
1572
+ }
1573
+ async bootstrap(context = {}) {
1574
+ const { scope, warning } = await this.resolveScope(context);
1575
+ const warnings = warning ? [warning] : [];
1576
+ const startedAt = Date.now();
1577
+ const branches = await Promise.all([
1578
+ this.runBranch("session_recent", () => this.args.adapter.getSessionMemories({
1579
+ project: scope.project,
1580
+ session_id: scope.sessionId,
1581
+ include_pending: true,
1582
+ limit: 12
1583
+ })),
1584
+ this.runBranch("user_profile", () => scope.userId ? this.args.adapter.getUserProfile({
1585
+ project: scope.project,
1586
+ user_id: scope.userId,
1587
+ include_pending: true,
1588
+ memory_types: "preference,instruction,goal"
1589
+ }) : Promise.resolve({ user_id: scope.userId, memories: [], count: 0 })),
1590
+ this.runBranch("project_rules", () => this.args.adapter.query({
1591
+ project: scope.project,
1592
+ query: "project rules instructions constraints conventions open threads",
1593
+ top_k: this.topK,
1594
+ include_memories: false,
1595
+ user_id: scope.userId,
1596
+ session_id: scope.sessionId,
1597
+ max_tokens: this.maxTokens,
1598
+ compress: true,
1599
+ compression_strategy: "adaptive"
1600
+ }))
1601
+ ]);
1602
+ const items = [];
1603
+ const branchStatus = {};
1604
+ for (const branch of branches) {
1605
+ branchStatus[branch.name] = branch.status;
1606
+ if (branch.status !== "ok") {
1607
+ if (branch.reason) warnings.push(`${branch.name}:${branch.reason}`);
1608
+ continue;
1609
+ }
1610
+ if (branch.name === "project_rules") {
1611
+ items.push(...this.contextItems(branch.value, "bootstrap"));
1612
+ continue;
1613
+ }
1614
+ const records = branch.value.memories || [];
1615
+ items.push(...records.map((memory, index) => ({
1616
+ id: String(memory.id || `${branch.name}_${index}`),
1617
+ content: String(memory.content || ""),
1618
+ type: "memory",
1619
+ score: 0.4,
1620
+ sourceQuery: "bootstrap",
1621
+ metadata: memory
1622
+ })).filter((item) => item.content));
1623
+ }
1624
+ const ranked = this.rerank(items).slice(0, this.topK * 2);
1625
+ const prepared = {
1626
+ scope,
1627
+ retrieval: {
1628
+ primaryQuery: "bootstrap",
1629
+ taskFrameQuery: null,
1630
+ warnings,
1631
+ degraded: warnings.length > 0,
1632
+ degradedReason: warnings.length > 0 ? "partial_bootstrap" : void 0,
1633
+ durationMs: Date.now() - startedAt,
1634
+ targetBudgetMs: this.targetRetrievalMs,
1635
+ hardTimeoutMs: this.hardRetrievalTimeoutMs,
1636
+ branchStatus
1637
+ },
1638
+ context: this.buildContext(ranked),
1639
+ items: ranked
1640
+ };
1641
+ this.lastPreparedTurn = prepared.retrieval;
1642
+ return prepared;
1643
+ }
1644
+ async beforeTurn(input, context = {}) {
1645
+ this.pushTouchedFiles(input.touchedFiles);
1646
+ const { scope, warning } = await this.resolveScope(context);
1647
+ const primaryQuery = compactWhitespace(input.userMessage);
1648
+ const taskFrameQuery = this.makeTaskFrameQuery(input);
1649
+ const warnings = warning ? [warning] : [];
1650
+ const startedAt = Date.now();
1651
+ const branches = await Promise.all([
1652
+ this.runBranch("context_primary", () => this.args.adapter.query({
1653
+ project: scope.project,
1654
+ query: primaryQuery,
1655
+ top_k: this.topK,
1656
+ include_memories: false,
1657
+ user_id: scope.userId,
1658
+ session_id: scope.sessionId,
1659
+ max_tokens: this.maxTokens,
1660
+ compress: true,
1661
+ compression_strategy: "adaptive"
1662
+ })),
1663
+ this.runBranch("memory_primary", () => this.args.adapter.searchMemories({
1664
+ project: scope.project,
1665
+ query: primaryQuery,
1666
+ user_id: scope.userId,
1667
+ session_id: scope.sessionId,
1668
+ top_k: this.topK,
1669
+ include_pending: true,
1670
+ profile: "balanced"
1671
+ })),
1672
+ taskFrameQuery ? this.runBranch("context_task_frame", () => this.args.adapter.query({
1673
+ project: scope.project,
1674
+ query: taskFrameQuery,
1675
+ top_k: this.topK,
1676
+ include_memories: false,
1677
+ user_id: scope.userId,
1678
+ session_id: scope.sessionId,
1679
+ max_tokens: this.maxTokens,
1680
+ compress: true,
1681
+ compression_strategy: "adaptive"
1682
+ })) : Promise.resolve({
1683
+ name: "context_task_frame",
1684
+ status: "skipped",
1685
+ durationMs: 0
1686
+ }),
1687
+ taskFrameQuery ? this.runBranch("memory_task_frame", () => this.args.adapter.searchMemories({
1688
+ project: scope.project,
1689
+ query: taskFrameQuery,
1690
+ user_id: scope.userId,
1691
+ session_id: scope.sessionId,
1692
+ top_k: this.topK,
1693
+ include_pending: true,
1694
+ profile: "balanced"
1695
+ })) : Promise.resolve({
1696
+ name: "memory_task_frame",
1697
+ status: "skipped",
1698
+ durationMs: 0
1699
+ })
1700
+ ]);
1701
+ const branchStatus = {};
1702
+ const collected = [];
1703
+ let okCount = 0;
1704
+ for (const branch of branches) {
1705
+ branchStatus[branch.name] = branch.status;
1706
+ if (branch.status !== "ok") {
1707
+ if (branch.status !== "skipped" && branch.reason) warnings.push(`${branch.name}:${branch.reason}`);
1708
+ continue;
1709
+ }
1710
+ okCount += 1;
1711
+ if (branch.name.startsWith("context")) {
1712
+ collected.push(...this.contextItems(
1713
+ branch.value,
1714
+ branch.name.includes("task_frame") ? "task_frame" : "primary"
1715
+ ));
1716
+ } else {
1717
+ collected.push(...this.memoryItems(
1718
+ branch.value,
1719
+ branch.name.includes("task_frame") ? "task_frame" : "primary"
1720
+ ));
1721
+ }
1722
+ }
1723
+ const ranked = this.rerank(collected).slice(0, this.topK * 2);
1724
+ const prepared = {
1725
+ scope,
1726
+ retrieval: {
1727
+ primaryQuery,
1728
+ taskFrameQuery,
1729
+ warnings,
1730
+ degraded: okCount < branches.filter((branch) => branch.status !== "skipped").length,
1731
+ degradedReason: okCount === 0 ? "all_retrieval_failed" : warnings.length > 0 ? "partial_retrieval_failed" : void 0,
1732
+ durationMs: Date.now() - startedAt,
1733
+ targetBudgetMs: this.targetRetrievalMs,
1734
+ hardTimeoutMs: this.hardRetrievalTimeoutMs,
1735
+ branchStatus
1736
+ },
1737
+ context: this.buildContext(ranked),
1738
+ items: ranked
1739
+ };
1740
+ this.lastPreparedTurn = prepared.retrieval;
1741
+ return prepared;
1742
+ }
1743
+ async recordWork(event, context = {}) {
1744
+ const normalized = {
1745
+ ...event,
1746
+ salience: event.salience || defaultSalience(event.kind, event.success),
1747
+ timestamp: event.timestamp || nowIso()
1748
+ };
1749
+ this.pushTouchedFiles(normalized.filePaths);
1750
+ this.pushWorkEvent(normalized);
1751
+ if (normalized.salience === "low") {
1752
+ this.bufferedLowSalience = [...this.bufferedLowSalience, normalized].slice(-20);
1753
+ return { success: true, buffered: true };
1754
+ }
1755
+ const { scope } = await this.resolveScope(context);
1756
+ return this.args.adapter.addMemory({
1757
+ project: scope.project,
1758
+ user_id: scope.userId,
1759
+ session_id: scope.sessionId,
1760
+ content: `${normalized.kind}: ${normalized.summary}${normalized.details ? ` (${normalized.details})` : ""}`,
1761
+ memory_type: toMemoryType(normalized.kind),
1762
+ event_date: normalized.timestamp,
1763
+ write_mode: "async",
1764
+ metadata: {
1765
+ runtime_auto: true,
1766
+ client_name: this.clientName,
1767
+ work_event_kind: normalized.kind,
1768
+ salience: normalized.salience,
1769
+ file_paths: normalized.filePaths || [],
1770
+ tool_name: normalized.toolName,
1771
+ success: normalized.success
1772
+ }
1773
+ });
1774
+ }
1775
+ async afterTurn(input, context = {}) {
1776
+ this.pushTouchedFiles(input.touchedFiles);
1777
+ const { scope } = await this.resolveScope(context);
1778
+ const result = await this.args.adapter.ingestSession({
1779
+ project: scope.project,
1780
+ session_id: scope.sessionId,
1781
+ user_id: scope.userId,
1782
+ messages: [
1783
+ { role: "user", content: input.userMessage, timestamp: nowIso() },
1784
+ { role: "assistant", content: input.assistantMessage, timestamp: nowIso() }
1785
+ ],
1786
+ write_mode: "async"
1787
+ });
1788
+ this.mergedCount += result.memories_invalidated || 0;
1789
+ return {
1790
+ success: Boolean(result.success),
1791
+ sessionIngested: true,
1792
+ memoriesCreated: result.memories_created || 0,
1793
+ relationsCreated: result.relations_created || 0,
1794
+ invalidatedCount: result.memories_invalidated || 0,
1795
+ mergedCount: result.memories_invalidated || 0,
1796
+ droppedCount: 0,
1797
+ warnings: result.errors || []
1798
+ };
1799
+ }
1800
+ async flush(reason = "manual", context = {}) {
1801
+ if (this.bufferedLowSalience.length > 0) {
1802
+ const { scope } = await this.resolveScope(context);
1803
+ const summary = summarizeLowSalience(this.bufferedLowSalience);
1804
+ await this.args.adapter.addMemory({
1805
+ project: scope.project,
1806
+ user_id: scope.userId,
1807
+ session_id: scope.sessionId,
1808
+ content: summary,
1809
+ memory_type: "event",
1810
+ write_mode: "async",
1811
+ metadata: {
1812
+ runtime_auto: true,
1813
+ client_name: this.clientName,
1814
+ flush_reason: reason,
1815
+ salience: "low",
1816
+ summarized_count: this.bufferedLowSalience.length
1817
+ }
1818
+ });
1819
+ this.bufferedLowSalience = [];
1820
+ }
1821
+ await this.args.adapter.flushQueue();
1822
+ return this.status();
1823
+ }
1824
+ status() {
1825
+ return {
1826
+ clientName: this.clientName,
1827
+ scope: this.lastScope,
1828
+ queue: this.args.adapter.queueStatus(),
1829
+ retrieval: this.lastPreparedTurn,
1830
+ counters: {
1831
+ mergedCount: this.mergedCount,
1832
+ droppedCount: this.droppedCount,
1833
+ bufferedLowSalience: this.bufferedLowSalience.length
1834
+ }
1835
+ };
1836
+ }
1837
+ };
1838
+
1839
+ // ../src/sdk/whisper.ts
1840
+ var PROJECT_CACHE_TTL_MS = 3e4;
1841
+ function isLikelyProjectId(projectRef) {
1842
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(projectRef);
1843
+ }
1844
+ var WhisperClient = class _WhisperClient {
1845
+ constructor(config) {
1846
+ this.config = config;
1847
+ this.diagnosticsStore = new DiagnosticsStore(config.telemetry?.maxEntries || 1e3);
1848
+ this.runtimeClient = new RuntimeClient(
1849
+ {
1850
+ apiKey: config.apiKey,
1851
+ baseUrl: config.baseUrl,
1852
+ compatMode: config.compatMode || "fallback",
1853
+ timeouts: config.timeouts,
1854
+ retryPolicy: config.retryPolicy,
1855
+ fetchImpl: config.fetch
1856
+ },
1857
+ this.diagnosticsStore
1858
+ );
1859
+ this.searchCache = new SearchResponseCache(
1860
+ config.cache?.ttlMs ?? 7e3,
1861
+ config.cache?.capacity ?? 500
1862
+ );
1863
+ const queueStore = this.createQueueStore(config);
1864
+ this.writeQueue = new WriteQueue({
1865
+ store: queueStore,
1866
+ maxBatchSize: config.queue?.maxBatchSize ?? 50,
1867
+ flushIntervalMs: config.queue?.flushIntervalMs ?? 100,
1868
+ maxAttempts: config.queue?.maxAttempts ?? 2,
1869
+ flushHandler: async (items) => {
1870
+ if (items.length === 0) return;
1871
+ const project = items[0].project;
1872
+ const memories = items.map((item) => ({
1873
+ ...item.payload,
1874
+ user_id: item.payload.user_id ?? item.userId,
1875
+ session_id: item.payload.session_id ?? item.sessionId,
1876
+ metadata: {
1877
+ ...item.payload.metadata || {},
1878
+ event_id: item.eventId,
1879
+ queued_at: item.createdAt
1880
+ }
1881
+ }));
1882
+ try {
1883
+ await this.runtimeClient.request({
1884
+ endpoint: "/v1/memory/bulk",
1885
+ method: "POST",
1886
+ operation: "bulk",
1887
+ body: {
1888
+ project,
1889
+ write_mode: "async",
1890
+ memories
1891
+ }
1892
+ });
1893
+ } catch (error) {
1894
+ if (this.runtimeClient.getCompatMode() !== "fallback" || !(error instanceof RuntimeClientError) || error.status !== 404) {
1895
+ throw error;
1896
+ }
1897
+ await Promise.all(
1898
+ memories.map(async (memory) => {
1899
+ try {
1900
+ await this.runtimeClient.request({
1901
+ endpoint: "/v1/memory",
1902
+ method: "POST",
1903
+ operation: "writeAck",
1904
+ body: {
1905
+ project,
1906
+ ...memory,
1907
+ write_mode: "sync"
1908
+ }
1909
+ });
1910
+ } catch (fallbackError) {
1911
+ if (this.runtimeClient.getCompatMode() !== "fallback" || !(fallbackError instanceof RuntimeClientError) || fallbackError.status !== 404) {
1912
+ throw fallbackError;
1913
+ }
1914
+ await this.runtimeClient.request({
1915
+ endpoint: "/v1/memories",
1916
+ method: "POST",
1917
+ operation: "writeAck",
1918
+ body: {
1919
+ project,
1630
1920
  ...memory,
1631
1921
  memory_type: memory.memory_type === "event" ? "episodic" : memory.memory_type
1632
1922
  }
1633
1923
  });
1634
1924
  }
1635
- })
1636
- );
1925
+ })
1926
+ );
1927
+ }
1928
+ }
1929
+ });
1930
+ if (config.queue?.enabled !== false) {
1931
+ void this.writeQueue.start();
1932
+ }
1933
+ this.memoryModule = new MemoryModule(
1934
+ this.runtimeClient,
1935
+ this.searchCache,
1936
+ this.writeQueue,
1937
+ {
1938
+ defaultProject: config.project,
1939
+ cacheEnabled: config.cache?.enabled !== false,
1940
+ queueEnabled: config.queue?.enabled !== false
1941
+ }
1942
+ );
1943
+ this.sessionModule = new SessionModule(this.memoryModule, config.project);
1944
+ this.profileModule = new ProfileModule(this.memoryModule);
1945
+ this.analyticsModule = new AnalyticsModule(this.diagnosticsStore, this.writeQueue);
1946
+ this.diagnostics = {
1947
+ getLast: (limit) => this.diagnosticsStore.getLast(limit),
1948
+ subscribe: (fn) => this.diagnosticsStore.subscribe(fn),
1949
+ snapshot: () => this.diagnosticsStore.snapshot()
1950
+ };
1951
+ this.queue = {
1952
+ flush: () => this.writeQueue.flush(),
1953
+ status: () => this.writeQueue.status()
1954
+ };
1955
+ this.memory = {
1956
+ add: (params) => this.memoryModule.add(params),
1957
+ addBulk: (params) => this.memoryModule.addBulk(params),
1958
+ search: (params) => this.memoryModule.search(params),
1959
+ get: (memoryId) => this.memoryModule.get(memoryId),
1960
+ getUserProfile: (params) => this.memoryModule.getUserProfile(params),
1961
+ getSessionMemories: (params) => this.memoryModule.getSessionMemories(params),
1962
+ update: (memoryId, params) => this.memoryModule.update(memoryId, params),
1963
+ delete: (memoryId) => this.memoryModule.delete(memoryId),
1964
+ flag: (params) => this.memoryModule.flag(params)
1965
+ };
1966
+ this.session = {
1967
+ start: (params) => this.sessionModule.start(params),
1968
+ event: (params) => this.sessionModule.event(params),
1969
+ suspend: (params) => this.sessionModule.suspend(params),
1970
+ resume: (params) => this.sessionModule.resume(params),
1971
+ end: (params) => this.sessionModule.end(params)
1972
+ };
1973
+ this.profile = {
1974
+ getUserProfile: (params) => this.profileModule.getUserProfile(params),
1975
+ getSessionMemories: (params) => this.profileModule.getSessionMemories(params)
1976
+ };
1977
+ this.analytics = {
1978
+ diagnosticsSnapshot: () => this.analyticsModule.diagnosticsSnapshot(),
1979
+ queueStatus: () => this.analyticsModule.queueStatus()
1980
+ };
1981
+ }
1982
+ diagnostics;
1983
+ queue;
1984
+ memory;
1985
+ session;
1986
+ profile;
1987
+ analytics;
1988
+ runtimeClient;
1989
+ diagnosticsStore;
1990
+ searchCache;
1991
+ writeQueue;
1992
+ memoryModule;
1993
+ sessionModule;
1994
+ profileModule;
1995
+ analyticsModule;
1996
+ projectRefToId = /* @__PURE__ */ new Map();
1997
+ projectCache = [];
1998
+ projectCacheExpiresAt = 0;
1999
+ static fromEnv(overrides = {}) {
2000
+ const env = typeof process !== "undefined" ? process.env : {};
2001
+ const apiKey = overrides.apiKey || env.WHISPER_API_KEY || env.USEWHISPER_API_KEY || env.API_KEY;
2002
+ if (!apiKey) {
2003
+ throw new Error("Missing API key. Set WHISPER_API_KEY / USEWHISPER_API_KEY / API_KEY.");
2004
+ }
2005
+ return new _WhisperClient({
2006
+ apiKey,
2007
+ baseUrl: overrides.baseUrl || env.WHISPER_BASE_URL || env.API_BASE_URL || "https://context.usewhisper.dev",
2008
+ project: overrides.project || env.WHISPER_PROJECT || env.PROJECT,
2009
+ ...overrides
2010
+ });
2011
+ }
2012
+ createQueueStore(config) {
2013
+ const persistence = config.queue?.persistence || this.defaultQueuePersistence();
2014
+ if (persistence === "storage") {
2015
+ return createStorageQueueStore();
2016
+ }
2017
+ if (persistence === "file") {
2018
+ const filePath = config.queue?.filePath || this.defaultQueueFilePath();
2019
+ if (filePath) {
2020
+ return createFileQueueStore(filePath);
2021
+ }
2022
+ }
2023
+ return new InMemoryQueueStore();
2024
+ }
2025
+ defaultQueuePersistence() {
2026
+ const maybeWindow = globalThis.window;
2027
+ if (maybeWindow && typeof maybeWindow === "object") {
2028
+ const maybeStorage = globalThis.localStorage;
2029
+ return maybeStorage && typeof maybeStorage === "object" ? "storage" : "memory";
2030
+ }
2031
+ return "file";
2032
+ }
2033
+ defaultQueueFilePath() {
2034
+ if (typeof process === "undefined") return void 0;
2035
+ const path = process.env.WHISPER_QUEUE_FILE_PATH;
2036
+ if (path) return path;
2037
+ const home = process.env.USERPROFILE || process.env.HOME;
2038
+ if (!home) return void 0;
2039
+ const normalizedHome = home.replace(/[\\\/]+$/, "");
2040
+ return `${normalizedHome}/.whisper/sdk/queue.json`;
2041
+ }
2042
+ getRequiredProject(project) {
2043
+ const resolved = project || this.config.project;
2044
+ if (!resolved) {
2045
+ throw new RuntimeClientError({
2046
+ code: "MISSING_PROJECT",
2047
+ message: "Project is required",
2048
+ retryable: false
2049
+ });
2050
+ }
2051
+ return resolved;
2052
+ }
2053
+ async refreshProjectCache(force = false) {
2054
+ if (!force && Date.now() < this.projectCacheExpiresAt && this.projectCache.length > 0) {
2055
+ return this.projectCache;
2056
+ }
2057
+ const response = await this.runtimeClient.request({
2058
+ endpoint: "/v1/projects",
2059
+ method: "GET",
2060
+ operation: "get",
2061
+ idempotent: true
2062
+ });
2063
+ this.projectRefToId.clear();
2064
+ this.projectCache = response.data?.projects || [];
2065
+ for (const project of this.projectCache) {
2066
+ this.projectRefToId.set(project.id, project.id);
2067
+ this.projectRefToId.set(project.slug, project.id);
2068
+ this.projectRefToId.set(project.name, project.id);
2069
+ }
2070
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
2071
+ return this.projectCache;
2072
+ }
2073
+ async fetchResolvedProject(projectRef) {
2074
+ try {
2075
+ const response = await this.runtimeClient.request({
2076
+ endpoint: `/v1/projects/resolve?project=${encodeURIComponent(projectRef)}`,
2077
+ method: "GET",
2078
+ operation: "get",
2079
+ idempotent: true
2080
+ });
2081
+ return response.data?.resolved || null;
2082
+ } catch (error) {
2083
+ if (error instanceof RuntimeClientError && error.status === 404) {
2084
+ return null;
2085
+ }
2086
+ throw error;
2087
+ }
2088
+ }
2089
+ async resolveProject(projectRef) {
2090
+ const resolvedRef = this.getRequiredProject(projectRef);
2091
+ const cachedProjects = await this.refreshProjectCache(false);
2092
+ const cachedProject = cachedProjects.find(
2093
+ (project) => project.id === resolvedRef || project.slug === resolvedRef || project.name === resolvedRef
2094
+ );
2095
+ if (cachedProject) {
2096
+ return cachedProject;
2097
+ }
2098
+ const resolvedProject = await this.fetchResolvedProject(resolvedRef);
2099
+ if (resolvedProject) {
2100
+ this.projectRefToId.set(resolvedProject.id, resolvedProject.id);
2101
+ this.projectRefToId.set(resolvedProject.slug, resolvedProject.id);
2102
+ this.projectRefToId.set(resolvedProject.name, resolvedProject.id);
2103
+ this.projectCache = [
2104
+ ...this.projectCache.filter((project) => project.id !== resolvedProject.id),
2105
+ resolvedProject
2106
+ ];
2107
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
2108
+ return resolvedProject;
2109
+ }
2110
+ if (isLikelyProjectId(resolvedRef)) {
2111
+ return {
2112
+ id: resolvedRef,
2113
+ orgId: "",
2114
+ name: resolvedRef,
2115
+ slug: resolvedRef,
2116
+ createdAt: (/* @__PURE__ */ new Date(0)).toISOString(),
2117
+ updatedAt: (/* @__PURE__ */ new Date(0)).toISOString()
2118
+ };
2119
+ }
2120
+ throw new RuntimeClientError({
2121
+ code: "PROJECT_NOT_FOUND",
2122
+ message: `Project '${resolvedRef}' not found`,
2123
+ retryable: false
2124
+ });
2125
+ }
2126
+ async query(params) {
2127
+ const project = (await this.resolveProject(params.project)).id;
2128
+ const response = await this.runtimeClient.request({
2129
+ endpoint: "/v1/context/query",
2130
+ method: "POST",
2131
+ operation: "search",
2132
+ body: {
2133
+ ...params,
2134
+ project
2135
+ },
2136
+ idempotent: true
2137
+ });
2138
+ return response.data;
2139
+ }
2140
+ async ingestSession(params) {
2141
+ const project = (await this.resolveProject(params.project)).id;
2142
+ const response = await this.runtimeClient.request({
2143
+ endpoint: "/v1/memory/ingest/session",
2144
+ method: "POST",
2145
+ operation: "session",
2146
+ body: {
2147
+ ...params,
2148
+ project
2149
+ }
2150
+ });
2151
+ return response.data;
2152
+ }
2153
+ createAgentRuntime(options = {}) {
2154
+ const baseContext = {
2155
+ workspacePath: options.workspacePath,
2156
+ project: options.project || this.config.project,
2157
+ userId: options.userId,
2158
+ sessionId: options.sessionId,
2159
+ traceId: options.traceId,
2160
+ clientName: options.clientName
2161
+ };
2162
+ return new WhisperAgentRuntime({
2163
+ baseContext,
2164
+ options,
2165
+ adapter: {
2166
+ resolveProject: (project) => this.resolveProject(project),
2167
+ query: (params) => this.query(params),
2168
+ ingestSession: (params) => this.ingestSession(params),
2169
+ getSessionMemories: (params) => this.memory.getSessionMemories(params),
2170
+ getUserProfile: (params) => this.memory.getUserProfile(params),
2171
+ searchMemories: (params) => this.memory.search(params),
2172
+ addMemory: (params) => this.memory.add(params),
2173
+ queueStatus: () => this.queue.status(),
2174
+ flushQueue: () => this.queue.flush()
2175
+ }
2176
+ });
2177
+ }
2178
+ withRunContext(context) {
2179
+ const base = this;
2180
+ return {
2181
+ memory: {
2182
+ add: (params) => base.memory.add({
2183
+ ...params,
2184
+ project: params.project || context.project || base.config.project,
2185
+ user_id: params.user_id || context.userId,
2186
+ session_id: params.session_id || context.sessionId
2187
+ }),
2188
+ search: (params) => base.memory.search({
2189
+ ...params,
2190
+ project: params.project || context.project || base.config.project,
2191
+ user_id: params.user_id || context.userId,
2192
+ session_id: params.session_id || context.sessionId
2193
+ })
2194
+ },
2195
+ session: {
2196
+ event: (params) => base.session.event({
2197
+ ...params,
2198
+ sessionId: params.sessionId || context.sessionId || ""
2199
+ })
2200
+ },
2201
+ queue: base.queue,
2202
+ diagnostics: base.diagnostics
2203
+ };
2204
+ }
2205
+ async shutdown() {
2206
+ await this.writeQueue.stop();
2207
+ }
2208
+ };
2209
+ var whisper_default = WhisperClient;
2210
+
2211
+ // ../src/sdk/whisper-agent.ts
2212
+ var DEPRECATION_WARNINGS = /* @__PURE__ */ new Set();
2213
+ function warnDeprecatedOnce(key, message) {
2214
+ if (DEPRECATION_WARNINGS.has(key)) return;
2215
+ DEPRECATION_WARNINGS.add(key);
2216
+ if (typeof console !== "undefined" && typeof console.warn === "function") {
2217
+ console.warn(message);
2218
+ }
2219
+ }
2220
+ var Whisper = class {
2221
+ client;
2222
+ runtimeClient;
2223
+ options;
2224
+ sessionId;
2225
+ userId;
2226
+ constructor(options) {
2227
+ if (!options.apiKey) {
2228
+ throw new Error("API key is required");
2229
+ }
2230
+ const clientConfig = {
2231
+ apiKey: options.apiKey,
2232
+ baseUrl: options.baseUrl,
2233
+ project: options.project || "default"
2234
+ };
2235
+ if (options.timeoutMs) clientConfig.timeoutMs = options.timeoutMs;
2236
+ if (options.retry) clientConfig.retry = options.retry;
2237
+ this.client = new WhisperContext(clientConfig);
2238
+ this.runtimeClient = new WhisperClient({
2239
+ apiKey: options.apiKey,
2240
+ baseUrl: options.baseUrl,
2241
+ project: options.project || "default"
2242
+ });
2243
+ warnDeprecatedOnce(
2244
+ "whisper_agent_wrapper",
2245
+ "[Whisper SDK] Whisper wrapper is supported for v2 compatibility. Prefer WhisperClient for new integrations."
2246
+ );
2247
+ const finalRetry = options.retry || { maxAttempts: 3, baseDelayMs: 250, maxDelayMs: 2e3 };
2248
+ this.options = {
2249
+ apiKey: options.apiKey,
2250
+ baseUrl: options.baseUrl || "https://context.usewhisper.dev",
2251
+ project: options.project || "default",
2252
+ timeoutMs: options.timeoutMs || 15e3,
2253
+ retry: finalRetry,
2254
+ contextLimit: options.contextLimit ?? 10,
2255
+ memoryTypes: options.memoryTypes ?? ["factual", "preference", "event", "goal", "relationship", "opinion", "instruction"],
2256
+ contextPrefix: options.contextPrefix ?? "Relevant context:",
2257
+ autoExtract: options.autoExtract ?? true,
2258
+ autoExtractMinConfidence: options.autoExtractMinConfidence ?? 0.65,
2259
+ maxMemoriesPerCapture: options.maxMemoriesPerCapture ?? 5
2260
+ };
2261
+ }
2262
+ /**
2263
+ * Set session ID for conversation tracking
2264
+ */
2265
+ session(sessionId) {
2266
+ this.sessionId = sessionId;
2267
+ return this;
2268
+ }
2269
+ /**
2270
+ * Set user ID for user-specific memories
2271
+ */
2272
+ user(userId) {
2273
+ this.userId = userId;
2274
+ return this;
2275
+ }
2276
+ /**
2277
+ * Get relevant context BEFORE your LLM call
2278
+ *
2279
+ * @param query - What you want to know / user question
2280
+ * @returns Context string and raw results
2281
+ *
2282
+ * @example
2283
+ * ```typescript
2284
+ * const { context, results, count } = await whisper.getContext(
2285
+ * "What are user's preferences?",
2286
+ * { userId: "user-123" }
2287
+ * );
2288
+ *
2289
+ * // Results: [
2290
+ * // { content: "User prefers dark mode", type: "preference", score: 0.95 },
2291
+ * // { content: "Allergic to nuts", type: "factual", score: 0.89 }
2292
+ * // ]
2293
+ * ```
2294
+ */
2295
+ async getContext(query, options) {
2296
+ const runtime = this.runtimeClient.createAgentRuntime({
2297
+ project: options?.project ?? this.options.project,
2298
+ userId: options?.userId ?? this.userId,
2299
+ sessionId: options?.sessionId ?? this.sessionId,
2300
+ topK: options?.limit ?? this.options.contextLimit,
2301
+ clientName: "whisper-wrapper"
2302
+ });
2303
+ const prepared = await runtime.beforeTurn({
2304
+ userMessage: query
2305
+ });
2306
+ const results = prepared.items.map((item, index) => ({
2307
+ id: item.id || `runtime_${index}`,
2308
+ content: item.content,
2309
+ score: item.score,
2310
+ metadata: item.metadata || {},
2311
+ source: item.type === "memory" ? "memory" : "runtime",
2312
+ document: item.sourceQuery,
2313
+ type: item.type,
2314
+ retrieval_source: item.type === "memory" ? "memory" : "runtime"
2315
+ }));
2316
+ const context = results.map((r, i) => `[${i + 1}] ${r.content}`).join("\n");
2317
+ return {
2318
+ context: context ? `${this.options.contextPrefix}
2319
+ ${context}` : "",
2320
+ results,
2321
+ count: prepared.items.length
2322
+ };
2323
+ }
2324
+ /**
2325
+ * Remember what happened AFTER your LLM response
2326
+ *
2327
+ * Fire-and-forget - doesn't block your response
2328
+ *
2329
+ * @param content - What your LLM responded with
2330
+ * @returns Promise that resolves when stored (or fails silently)
2331
+ *
2332
+ * @example
2333
+ * ```typescript
2334
+ * const llmResponse = "I've set your theme to dark mode and removed nuts from recommendations.";
2335
+ *
2336
+ * await whisper.remember(llmResponse, { userId: "user-123" });
2337
+ * // → Auto-extracts: "theme set to dark mode", "nut allergy"
2338
+ * // → Stored as preferences
2339
+ * ```
2340
+ */
2341
+ async remember(content, options) {
2342
+ if (!content || content.length < 5) {
2343
+ return { success: false };
2344
+ }
2345
+ try {
2346
+ if (this.options.autoExtract) {
2347
+ const extraction = await this.client.extractMemories({
2348
+ project: options?.project ?? this.options.project,
2349
+ message: content,
2350
+ user_id: options?.userId ?? this.userId,
2351
+ session_id: options?.sessionId ?? this.sessionId,
2352
+ enable_pattern: true,
2353
+ enable_inference: true,
2354
+ min_confidence: this.options.autoExtractMinConfidence
2355
+ });
2356
+ const extractedMemories = (extraction.all || []).filter((m) => (m.confidence || 0) >= this.options.autoExtractMinConfidence).slice(0, this.options.maxMemoriesPerCapture);
2357
+ if (extractedMemories.length > 0) {
2358
+ const bulk = await this.client.addMemoriesBulk({
2359
+ project: options?.project ?? this.options.project,
2360
+ write_mode: "async",
2361
+ memories: extractedMemories.map((m) => ({
2362
+ content: m.content,
2363
+ memory_type: m.memoryType,
2364
+ user_id: options?.userId ?? this.userId,
2365
+ session_id: options?.sessionId ?? this.sessionId,
2366
+ importance: Math.max(0.5, Math.min(1, m.confidence || 0.7)),
2367
+ confidence: m.confidence || 0.7,
2368
+ entity_mentions: m.entityMentions || [],
2369
+ event_date: m.eventDate || void 0,
2370
+ metadata: {
2371
+ extracted: true,
2372
+ extraction_method: extraction.extractionMethod,
2373
+ extraction_reasoning: m.reasoning,
2374
+ inferred: Boolean(m.inferred)
2375
+ }
2376
+ }))
2377
+ });
2378
+ const memoryIds = this.extractMemoryIdsFromBulkResponse(bulk);
2379
+ return {
2380
+ success: true,
2381
+ memoryId: memoryIds[0],
2382
+ memoryIds: memoryIds.length > 0 ? memoryIds : void 0,
2383
+ extracted: extractedMemories.length
2384
+ };
1637
2385
  }
1638
2386
  }
1639
- });
1640
- if (config.queue?.enabled !== false) {
1641
- void this.writeQueue.start();
2387
+ const result = await this.client.addMemory({
2388
+ project: options?.project ?? this.options.project,
2389
+ content,
2390
+ user_id: options?.userId ?? this.userId,
2391
+ session_id: options?.sessionId ?? this.sessionId
2392
+ });
2393
+ return {
2394
+ success: true,
2395
+ memoryId: result?.id
2396
+ };
2397
+ } catch (error) {
2398
+ console.error("[Whisper] Remember failed:", error);
2399
+ return { success: false };
1642
2400
  }
1643
- this.memoryModule = new MemoryModule(
1644
- this.runtimeClient,
1645
- this.searchCache,
1646
- this.writeQueue,
2401
+ }
2402
+ /**
2403
+ * Alias for remember() - same thing
2404
+ */
2405
+ async capture(content, options) {
2406
+ return this.remember(content, options);
2407
+ }
2408
+ /**
2409
+ * Capture from multiple messages (e.g., full conversation)
2410
+ */
2411
+ async captureSession(messages, options) {
2412
+ try {
2413
+ const filteredMessages = messages.filter((m) => m.role !== "system");
2414
+ const runtime = this.runtimeClient.createAgentRuntime({
2415
+ project: options?.project ?? this.options.project,
2416
+ userId: options?.userId ?? this.userId,
2417
+ sessionId: options?.sessionId ?? this.sessionId ?? "default",
2418
+ clientName: "whisper-wrapper"
2419
+ });
2420
+ const result = await runtime.afterTurn({
2421
+ userMessage: [...filteredMessages].reverse().find((m) => m.role === "user")?.content || "",
2422
+ assistantMessage: [...filteredMessages].reverse().find((m) => m.role === "assistant")?.content || ""
2423
+ });
2424
+ return {
2425
+ success: true,
2426
+ extracted: result.memoriesCreated ?? 0
2427
+ };
2428
+ } catch (error) {
2429
+ const fallback = await this.fallbackCaptureViaAddMemory(messages, options);
2430
+ if (fallback.success) {
2431
+ return fallback;
2432
+ }
2433
+ console.error("[Whisper] Session capture failed:", error);
2434
+ return { success: false, extracted: 0 };
2435
+ }
2436
+ }
2437
+ /**
2438
+ * Run a full agent turn with automatic memory read (before) + write (after).
2439
+ */
2440
+ async runTurn(params) {
2441
+ const contextResult = await this.getContext(params.userMessage, {
2442
+ userId: params.userId,
2443
+ sessionId: params.sessionId,
2444
+ project: params.project,
2445
+ limit: params.limit
2446
+ });
2447
+ const prompt = contextResult.context ? `${contextResult.context}
2448
+
2449
+ User: ${params.userMessage}` : params.userMessage;
2450
+ const response = await params.generate(prompt);
2451
+ const captureResult = await this.captureSession(
2452
+ [
2453
+ { role: "user", content: params.userMessage },
2454
+ { role: "assistant", content: response }
2455
+ ],
1647
2456
  {
1648
- defaultProject: config.project,
1649
- cacheEnabled: config.cache?.enabled !== false,
1650
- queueEnabled: config.queue?.enabled !== false
2457
+ userId: params.userId,
2458
+ sessionId: params.sessionId,
2459
+ project: params.project
1651
2460
  }
1652
2461
  );
1653
- this.sessionModule = new SessionModule(this.memoryModule, config.project);
1654
- this.profileModule = new ProfileModule(this.memoryModule);
1655
- this.analyticsModule = new AnalyticsModule(this.diagnosticsStore, this.writeQueue);
1656
- this.diagnostics = {
1657
- getLast: (limit) => this.diagnosticsStore.getLast(limit),
1658
- subscribe: (fn) => this.diagnosticsStore.subscribe(fn),
1659
- snapshot: () => this.diagnosticsStore.snapshot()
1660
- };
1661
- this.queue = {
1662
- flush: () => this.writeQueue.flush(),
1663
- status: () => this.writeQueue.status()
1664
- };
1665
- this.memory = {
1666
- add: (params) => this.memoryModule.add(params),
1667
- addBulk: (params) => this.memoryModule.addBulk(params),
1668
- search: (params) => this.memoryModule.search(params),
1669
- get: (memoryId) => this.memoryModule.get(memoryId),
1670
- getUserProfile: (params) => this.memoryModule.getUserProfile(params),
1671
- getSessionMemories: (params) => this.memoryModule.getSessionMemories(params),
1672
- update: (memoryId, params) => this.memoryModule.update(memoryId, params),
1673
- delete: (memoryId) => this.memoryModule.delete(memoryId),
1674
- flag: (params) => this.memoryModule.flag(params)
1675
- };
1676
- this.session = {
1677
- start: (params) => this.sessionModule.start(params),
1678
- event: (params) => this.sessionModule.event(params),
1679
- suspend: (params) => this.sessionModule.suspend(params),
1680
- resume: (params) => this.sessionModule.resume(params),
1681
- end: (params) => this.sessionModule.end(params)
1682
- };
1683
- this.profile = {
1684
- getUserProfile: (params) => this.profileModule.getUserProfile(params),
1685
- getSessionMemories: (params) => this.profileModule.getSessionMemories(params)
1686
- };
1687
- this.analytics = {
1688
- diagnosticsSnapshot: () => this.analyticsModule.diagnosticsSnapshot(),
1689
- queueStatus: () => this.analyticsModule.queueStatus()
2462
+ return {
2463
+ response,
2464
+ context: contextResult.context,
2465
+ count: contextResult.count,
2466
+ extracted: captureResult.extracted
1690
2467
  };
1691
2468
  }
1692
- diagnostics;
1693
- queue;
1694
- memory;
1695
- session;
1696
- profile;
1697
- analytics;
1698
- runtimeClient;
1699
- diagnosticsStore;
1700
- searchCache;
1701
- writeQueue;
1702
- memoryModule;
1703
- sessionModule;
1704
- profileModule;
1705
- analyticsModule;
1706
- static fromEnv(overrides = {}) {
1707
- const env = typeof process !== "undefined" ? process.env : {};
1708
- const apiKey = overrides.apiKey || env.WHISPER_API_KEY || env.USEWHISPER_API_KEY || env.API_KEY;
1709
- if (!apiKey) {
1710
- throw new Error("Missing API key. Set WHISPER_API_KEY / USEWHISPER_API_KEY / API_KEY.");
1711
- }
1712
- return new _WhisperClient({
1713
- apiKey,
1714
- baseUrl: overrides.baseUrl || env.WHISPER_BASE_URL || env.API_BASE_URL || "https://context.usewhisper.dev",
1715
- project: overrides.project || env.WHISPER_PROJECT || env.PROJECT,
1716
- ...overrides
1717
- });
2469
+ /**
2470
+ * Direct access to WhisperContext for advanced usage
2471
+ */
2472
+ raw() {
2473
+ return this.client;
1718
2474
  }
1719
- withRunContext(context) {
1720
- const base = this;
1721
- return {
1722
- memory: {
1723
- add: (params) => base.memory.add({
1724
- ...params,
1725
- project: params.project || context.project || base.config.project,
1726
- user_id: params.user_id || context.userId,
1727
- session_id: params.session_id || context.sessionId
1728
- }),
1729
- search: (params) => base.memory.search({
1730
- ...params,
1731
- project: params.project || context.project || base.config.project,
1732
- user_id: params.user_id || context.userId,
1733
- session_id: params.session_id || context.sessionId
1734
- })
1735
- },
1736
- session: {
1737
- event: (params) => base.session.event({
1738
- ...params,
1739
- sessionId: params.sessionId || context.sessionId || ""
1740
- })
1741
- },
1742
- queue: base.queue,
1743
- diagnostics: base.diagnostics
1744
- };
2475
+ extractMemoryIdsFromBulkResponse(bulkResponse) {
2476
+ const ids = [];
2477
+ if (Array.isArray(bulkResponse?.memories)) {
2478
+ for (const memory of bulkResponse.memories) {
2479
+ if (memory?.id) ids.push(memory.id);
2480
+ }
2481
+ }
2482
+ if (bulkResponse?.memory?.id) {
2483
+ ids.push(bulkResponse.memory.id);
2484
+ }
2485
+ if (bulkResponse?.id) {
2486
+ ids.push(bulkResponse.id);
2487
+ }
2488
+ return Array.from(new Set(ids));
1745
2489
  }
1746
- async shutdown() {
1747
- await this.writeQueue.stop();
2490
+ async fallbackCaptureViaAddMemory(messages, options) {
2491
+ const userMessages = messages.filter((m) => m.role === "user").map((m) => (m.content || "").trim()).filter((content) => content.length >= 5).slice(-2);
2492
+ if (userMessages.length === 0) {
2493
+ return { success: false, extracted: 0 };
2494
+ }
2495
+ let extracted = 0;
2496
+ for (const content of userMessages) {
2497
+ try {
2498
+ await this.client.addMemory({
2499
+ project: options?.project ?? this.options.project,
2500
+ content,
2501
+ memory_type: "factual",
2502
+ user_id: options?.userId ?? this.userId,
2503
+ session_id: options?.sessionId ?? this.sessionId,
2504
+ allow_legacy_fallback: true
2505
+ });
2506
+ extracted += 1;
2507
+ } catch {
2508
+ }
2509
+ }
2510
+ return { success: extracted > 0, extracted };
1748
2511
  }
1749
2512
  };
1750
- var whisper_default = WhisperClient;
2513
+ var whisper_agent_default = Whisper;
1751
2514
 
1752
2515
  // ../src/sdk/middleware.ts
1753
2516
  var WhisperAgentMiddleware = class {
@@ -2257,7 +3020,7 @@ var DEFAULT_MAX_ATTEMPTS = 3;
2257
3020
  var DEFAULT_BASE_DELAY_MS = 250;
2258
3021
  var DEFAULT_MAX_DELAY_MS = 2e3;
2259
3022
  var DEFAULT_TIMEOUT_MS = 15e3;
2260
- var PROJECT_CACHE_TTL_MS = 3e4;
3023
+ var PROJECT_CACHE_TTL_MS2 = 3e4;
2261
3024
  var DEPRECATION_WARNINGS2 = /* @__PURE__ */ new Set();
2262
3025
  function warnDeprecatedOnce2(key, message) {
2263
3026
  if (DEPRECATION_WARNINGS2.has(key)) return;
@@ -2266,7 +3029,7 @@ function warnDeprecatedOnce2(key, message) {
2266
3029
  console.warn(message);
2267
3030
  }
2268
3031
  }
2269
- function isLikelyProjectId(projectRef) {
3032
+ function isLikelyProjectId2(projectRef) {
2270
3033
  return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(projectRef);
2271
3034
  }
2272
3035
  function normalizeBaseUrl2(url) {
@@ -2388,7 +3151,7 @@ var WhisperContext = class _WhisperContext {
2388
3151
  }
2389
3152
  }
2390
3153
  }
2391
- this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
3154
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS2;
2392
3155
  return this.projectCache;
2393
3156
  }
2394
3157
  async fetchResolvedProject(projectRef) {
@@ -2421,10 +3184,10 @@ var WhisperContext = class _WhisperContext {
2421
3184
  ...this.projectCache.filter((project) => project.id !== resolvedProject.id),
2422
3185
  resolvedProject
2423
3186
  ];
2424
- this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
3187
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS2;
2425
3188
  return resolvedProject;
2426
3189
  }
2427
- if (isLikelyProjectId(resolvedRef)) {
3190
+ if (isLikelyProjectId2(resolvedRef)) {
2428
3191
  return {
2429
3192
  id: resolvedRef,
2430
3193
  orgId: "",
@@ -2456,7 +3219,7 @@ var WhisperContext = class _WhisperContext {
2456
3219
  message: `Project reference '${projectRef}' matched multiple projects. Use project id instead.`
2457
3220
  });
2458
3221
  }
2459
- if (isLikelyProjectId(projectRef)) {
3222
+ if (isLikelyProjectId2(projectRef)) {
2460
3223
  return projectRef;
2461
3224
  }
2462
3225
  const resolvedProject = await this.fetchResolvedProject(projectRef);
@@ -2468,7 +3231,7 @@ var WhisperContext = class _WhisperContext {
2468
3231
  ...this.projectCache.filter((project) => project.id !== resolvedProject.id),
2469
3232
  resolvedProject
2470
3233
  ];
2471
- this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
3234
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS2;
2472
3235
  return resolvedProject.id;
2473
3236
  }
2474
3237
  throw new WhisperError({
@@ -2485,7 +3248,7 @@ var WhisperContext = class _WhisperContext {
2485
3248
  candidates.add(match.id);
2486
3249
  candidates.add(match.slug);
2487
3250
  candidates.add(match.name);
2488
- } else if (isLikelyProjectId(projectRef)) {
3251
+ } else if (isLikelyProjectId2(projectRef)) {
2489
3252
  const byId = projects.find((p) => p.id === projectRef);
2490
3253
  if (byId) {
2491
3254
  candidates.add(byId.slug);
@@ -2646,7 +3409,7 @@ var WhisperContext = class _WhisperContext {
2646
3409
  ...this.projectCache.filter((p) => p.id !== project.id),
2647
3410
  project
2648
3411
  ];
2649
- this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
3412
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS2;
2650
3413
  return project;
2651
3414
  }
2652
3415
  async listProjects() {
@@ -2657,7 +3420,7 @@ var WhisperContext = class _WhisperContext {
2657
3420
  this.projectRefToId.set(p.slug, p.id);
2658
3421
  this.projectRefToId.set(p.name, p.id);
2659
3422
  }
2660
- this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
3423
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS2;
2661
3424
  return projects;
2662
3425
  }
2663
3426
  async getProject(id) {