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