@usewhisper/sdk 3.5.0 → 3.7.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/README.md +51 -49
- package/index.d.mts +83 -2
- package/index.d.ts +83 -2
- package/index.js +890 -559
- package/index.mjs +890 -559
- package/package.json +2 -2
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
|
};
|
|
@@ -1687,6 +1402,29 @@ ${lines.join("\n")}`;
|
|
|
1687
1402
|
function compactWhitespace(value) {
|
|
1688
1403
|
return value.replace(/\s+/g, " ").trim();
|
|
1689
1404
|
}
|
|
1405
|
+
function normalizeSummary(value) {
|
|
1406
|
+
return compactWhitespace(String(value || "").toLowerCase());
|
|
1407
|
+
}
|
|
1408
|
+
function tokenize(value) {
|
|
1409
|
+
return normalizeSummary(value).split(/[^a-z0-9_./-]+/i).map((token) => token.trim()).filter(Boolean);
|
|
1410
|
+
}
|
|
1411
|
+
function jaccardOverlap(left, right) {
|
|
1412
|
+
const leftTokens = new Set(tokenize(left));
|
|
1413
|
+
const rightTokens = new Set(tokenize(right));
|
|
1414
|
+
if (leftTokens.size === 0 || rightTokens.size === 0) return 0;
|
|
1415
|
+
let intersection = 0;
|
|
1416
|
+
for (const token of leftTokens) {
|
|
1417
|
+
if (rightTokens.has(token)) intersection += 1;
|
|
1418
|
+
}
|
|
1419
|
+
const union = (/* @__PURE__ */ new Set([...leftTokens, ...rightTokens])).size;
|
|
1420
|
+
return union > 0 ? intersection / union : 0;
|
|
1421
|
+
}
|
|
1422
|
+
function clamp01(value) {
|
|
1423
|
+
if (!Number.isFinite(value)) return 0;
|
|
1424
|
+
if (value < 0) return 0;
|
|
1425
|
+
if (value > 1) return 1;
|
|
1426
|
+
return value;
|
|
1427
|
+
}
|
|
1690
1428
|
function withTimeout(promise, timeoutMs) {
|
|
1691
1429
|
return new Promise((resolve, reject) => {
|
|
1692
1430
|
const timeout = setTimeout(() => {
|
|
@@ -1720,39 +1458,76 @@ function extractTimestamp(metadata) {
|
|
|
1720
1458
|
}
|
|
1721
1459
|
return 0;
|
|
1722
1460
|
}
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1461
|
+
var DEFAULT_RANK_WEIGHTS = {
|
|
1462
|
+
focusedPassBonus: 0.2,
|
|
1463
|
+
sourceMatchBonus: 0.18,
|
|
1464
|
+
touchedFileBonus: 0.12,
|
|
1465
|
+
clientMatchBonus: 0.1,
|
|
1466
|
+
highSalienceBonus: 0.12,
|
|
1467
|
+
mediumSalienceBonus: 0.06,
|
|
1468
|
+
staleBroadPenalty: -0.1,
|
|
1469
|
+
unrelatedClientPenalty: -0.18,
|
|
1470
|
+
lowSaliencePenalty: -0.12
|
|
1471
|
+
};
|
|
1472
|
+
var DEFAULT_SOURCE_ACTIVITY = {
|
|
1473
|
+
maxTurns: 10,
|
|
1474
|
+
maxIdleMs: 30 * 60 * 1e3,
|
|
1475
|
+
decayAfterTurns: 5,
|
|
1476
|
+
decayAfterIdleMs: 15 * 60 * 1e3,
|
|
1477
|
+
evictOnTaskSwitch: true
|
|
1478
|
+
};
|
|
1729
1479
|
var WhisperAgentRuntime = class {
|
|
1730
1480
|
constructor(args) {
|
|
1731
1481
|
this.args = args;
|
|
1732
1482
|
this.bindingStore = createBindingStore(args.options.bindingStorePath);
|
|
1733
|
-
|
|
1483
|
+
const retrieval = args.options.retrieval || {};
|
|
1484
|
+
this.focusedTopK = retrieval.focusedTopK ?? args.options.topK ?? 6;
|
|
1485
|
+
this.broadTopK = retrieval.broadTopK ?? Math.max(args.options.topK ?? 6, 10);
|
|
1734
1486
|
this.maxTokens = args.options.maxTokens ?? 4e3;
|
|
1735
1487
|
this.targetRetrievalMs = args.options.targetRetrievalMs ?? 2500;
|
|
1736
1488
|
this.hardRetrievalTimeoutMs = args.options.hardRetrievalTimeoutMs ?? 4e3;
|
|
1737
1489
|
this.recentWorkLimit = args.options.recentWorkLimit ?? 40;
|
|
1738
1490
|
this.baseContext = args.baseContext;
|
|
1739
1491
|
this.clientName = args.baseContext.clientName || "whisper-agent-runtime";
|
|
1492
|
+
this.minFocusedResults = retrieval.minFocusedResults ?? 3;
|
|
1493
|
+
this.minFocusedTopScore = retrieval.minFocusedTopScore ?? 0.55;
|
|
1494
|
+
this.minProjectScore = retrieval.minProjectScore ?? 0.5;
|
|
1495
|
+
this.minMemoryScore = retrieval.minMemoryScore ?? 0.6;
|
|
1496
|
+
this.rankWeights = { ...DEFAULT_RANK_WEIGHTS, ...retrieval.rankWeights || {} };
|
|
1497
|
+
this.sourceActivityOptions = { ...DEFAULT_SOURCE_ACTIVITY, ...retrieval.sourceActivity || {} };
|
|
1740
1498
|
}
|
|
1741
1499
|
bindingStore;
|
|
1742
|
-
|
|
1500
|
+
focusedTopK;
|
|
1501
|
+
broadTopK;
|
|
1743
1502
|
maxTokens;
|
|
1744
1503
|
targetRetrievalMs;
|
|
1745
1504
|
hardRetrievalTimeoutMs;
|
|
1746
1505
|
recentWorkLimit;
|
|
1747
1506
|
baseContext;
|
|
1748
1507
|
clientName;
|
|
1508
|
+
minFocusedResults;
|
|
1509
|
+
minFocusedTopScore;
|
|
1510
|
+
minProjectScore;
|
|
1511
|
+
minMemoryScore;
|
|
1512
|
+
rankWeights;
|
|
1513
|
+
sourceActivityOptions;
|
|
1749
1514
|
bindings = null;
|
|
1750
1515
|
touchedFiles = [];
|
|
1751
1516
|
recentWork = [];
|
|
1517
|
+
recentSourceActivity = [];
|
|
1752
1518
|
bufferedLowSalience = [];
|
|
1753
1519
|
lastPreparedTurn = null;
|
|
1754
1520
|
mergedCount = 0;
|
|
1755
1521
|
droppedCount = 0;
|
|
1522
|
+
focusedPassHits = 0;
|
|
1523
|
+
fallbackTriggers = 0;
|
|
1524
|
+
floorDroppedCount = 0;
|
|
1525
|
+
injectedItemCount = 0;
|
|
1526
|
+
sourceScopedTurns = 0;
|
|
1527
|
+
broadScopedTurns = 0;
|
|
1528
|
+
totalTurns = 0;
|
|
1529
|
+
currentTurn = 0;
|
|
1530
|
+
lastTaskSummary = "";
|
|
1756
1531
|
lastScope = {};
|
|
1757
1532
|
async getBindings() {
|
|
1758
1533
|
if (!this.bindings) {
|
|
@@ -1770,6 +1545,64 @@ var WhisperAgentRuntime = class {
|
|
|
1770
1545
|
pushWorkEvent(event) {
|
|
1771
1546
|
this.recentWork = [...this.recentWork, event].slice(-this.recentWorkLimit);
|
|
1772
1547
|
}
|
|
1548
|
+
noteSourceActivity(sourceIds) {
|
|
1549
|
+
const now = Date.now();
|
|
1550
|
+
for (const sourceId of [...new Set((sourceIds || []).map((value) => String(value || "").trim()).filter(Boolean))]) {
|
|
1551
|
+
this.recentSourceActivity = [
|
|
1552
|
+
...this.recentSourceActivity.filter((entry) => entry.sourceId !== sourceId),
|
|
1553
|
+
{ sourceId, turn: this.currentTurn, at: now }
|
|
1554
|
+
].slice(-24);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
refreshTaskSummary(taskSummary) {
|
|
1558
|
+
const next = normalizeSummary(taskSummary);
|
|
1559
|
+
if (!next) return;
|
|
1560
|
+
if (this.sourceActivityOptions.evictOnTaskSwitch && this.lastTaskSummary && this.lastTaskSummary !== next && jaccardOverlap(this.lastTaskSummary, next) < 0.6) {
|
|
1561
|
+
this.recentSourceActivity = [];
|
|
1562
|
+
}
|
|
1563
|
+
this.lastTaskSummary = next;
|
|
1564
|
+
}
|
|
1565
|
+
activeSourceIds() {
|
|
1566
|
+
const now = Date.now();
|
|
1567
|
+
const active = /* @__PURE__ */ new Map();
|
|
1568
|
+
const maxTurns = this.sourceActivityOptions.maxTurns;
|
|
1569
|
+
const maxIdleMs = this.sourceActivityOptions.maxIdleMs;
|
|
1570
|
+
const decayAfterTurns = this.sourceActivityOptions.decayAfterTurns;
|
|
1571
|
+
const decayAfterIdleMs = this.sourceActivityOptions.decayAfterIdleMs;
|
|
1572
|
+
const fresh = [];
|
|
1573
|
+
for (const entry of this.recentSourceActivity) {
|
|
1574
|
+
const turnDelta = this.currentTurn - entry.turn;
|
|
1575
|
+
const idleDelta = now - entry.at;
|
|
1576
|
+
if (turnDelta > maxTurns || idleDelta > maxIdleMs) continue;
|
|
1577
|
+
fresh.push(entry);
|
|
1578
|
+
let weight = 1;
|
|
1579
|
+
if (turnDelta > decayAfterTurns || idleDelta > decayAfterIdleMs) {
|
|
1580
|
+
weight = 0.5;
|
|
1581
|
+
}
|
|
1582
|
+
const current = active.get(entry.sourceId) || 0;
|
|
1583
|
+
active.set(entry.sourceId, Math.max(current, weight));
|
|
1584
|
+
}
|
|
1585
|
+
this.recentSourceActivity = fresh.slice(-24);
|
|
1586
|
+
return [...active.entries()].sort((left, right) => right[1] - left[1]).map(([sourceId]) => sourceId).slice(0, 4);
|
|
1587
|
+
}
|
|
1588
|
+
focusedScope(input) {
|
|
1589
|
+
const sourceIds = this.activeSourceIds();
|
|
1590
|
+
const fileHints = [...new Set([
|
|
1591
|
+
...input.touchedFiles || [],
|
|
1592
|
+
...this.touchedFiles,
|
|
1593
|
+
...this.recentWork.flatMap((event) => event.filePaths || [])
|
|
1594
|
+
].map((value) => String(value || "").trim()).filter(Boolean))].slice(-4);
|
|
1595
|
+
return {
|
|
1596
|
+
sourceIds,
|
|
1597
|
+
fileHints,
|
|
1598
|
+
clientName: this.clientName || void 0
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
exactFileMetadataFilter(fileHints) {
|
|
1602
|
+
const exact = fileHints.find((value) => /[\\/]/.test(value));
|
|
1603
|
+
if (!exact) return void 0;
|
|
1604
|
+
return { filePath: exact };
|
|
1605
|
+
}
|
|
1773
1606
|
makeTaskFrameQuery(input) {
|
|
1774
1607
|
const task = compactWhitespace(input.taskSummary || "");
|
|
1775
1608
|
const salient = this.recentWork.filter((event) => event.salience === "high").slice(-3).map((event) => `${event.kind}: ${event.summary}`);
|
|
@@ -1846,23 +1679,29 @@ var WhisperAgentRuntime = class {
|
|
|
1846
1679
|
};
|
|
1847
1680
|
}
|
|
1848
1681
|
}
|
|
1849
|
-
contextItems(result, sourceQuery) {
|
|
1682
|
+
contextItems(result, sourceQuery, pass) {
|
|
1683
|
+
const sourceScope = result.meta?.source_scope;
|
|
1684
|
+
if (sourceScope?.mode === "auto" || sourceScope?.mode === "explicit") {
|
|
1685
|
+
this.noteSourceActivity(sourceScope.source_ids || []);
|
|
1686
|
+
}
|
|
1850
1687
|
return (result.results || []).map((item) => ({
|
|
1851
1688
|
id: item.id,
|
|
1852
1689
|
content: item.content,
|
|
1853
1690
|
type: "project",
|
|
1854
1691
|
score: item.score ?? 0,
|
|
1855
1692
|
sourceQuery,
|
|
1693
|
+
pass,
|
|
1856
1694
|
metadata: item.metadata || {}
|
|
1857
1695
|
}));
|
|
1858
1696
|
}
|
|
1859
|
-
memoryItems(result, sourceQuery) {
|
|
1697
|
+
memoryItems(result, sourceQuery, pass) {
|
|
1860
1698
|
return (result.results || []).map((item, index) => ({
|
|
1861
1699
|
id: item.memory?.id || item.chunk?.id || `${sourceQuery}_memory_${index}`,
|
|
1862
1700
|
content: item.chunk?.content || item.memory?.content || "",
|
|
1863
1701
|
type: "memory",
|
|
1864
1702
|
score: item.similarity ?? 0,
|
|
1865
1703
|
sourceQuery,
|
|
1704
|
+
pass,
|
|
1866
1705
|
metadata: {
|
|
1867
1706
|
...item.chunk?.metadata || {},
|
|
1868
1707
|
...item.memory?.temporal || {},
|
|
@@ -1870,22 +1709,99 @@ var WhisperAgentRuntime = class {
|
|
|
1870
1709
|
}
|
|
1871
1710
|
})).filter((item) => item.content);
|
|
1872
1711
|
}
|
|
1873
|
-
|
|
1712
|
+
stableItemKey(item) {
|
|
1713
|
+
const metadata = item.metadata || {};
|
|
1714
|
+
const sourceId = String(metadata.source_id || "");
|
|
1715
|
+
const documentId = String(metadata.document_id || metadata.documentId || "");
|
|
1716
|
+
const chunkId = String(metadata.chunk_id || metadata.chunkId || item.id || "");
|
|
1717
|
+
return stableHash(`${sourceId}|${documentId}|${chunkId}|${item.content.slice(0, 256)}`);
|
|
1718
|
+
}
|
|
1719
|
+
metadataStrings(item) {
|
|
1720
|
+
const metadata = item.metadata || {};
|
|
1721
|
+
return [
|
|
1722
|
+
metadata.filePath,
|
|
1723
|
+
metadata.file_path,
|
|
1724
|
+
metadata.path,
|
|
1725
|
+
metadata.section_path,
|
|
1726
|
+
metadata.parent_section_path,
|
|
1727
|
+
metadata.web_url,
|
|
1728
|
+
metadata.url
|
|
1729
|
+
].map((value) => String(value || "").toLowerCase()).filter(Boolean);
|
|
1730
|
+
}
|
|
1731
|
+
hasSourceMatch(item, scope) {
|
|
1732
|
+
const sourceId = String(item.metadata?.source_id || "");
|
|
1733
|
+
return Boolean(sourceId && scope.sourceIds.includes(sourceId));
|
|
1734
|
+
}
|
|
1735
|
+
hasFileMatch(item, scope) {
|
|
1736
|
+
if (scope.fileHints.length === 0) return false;
|
|
1737
|
+
const metadata = this.metadataStrings(item);
|
|
1738
|
+
const lowerHints = scope.fileHints.map((hint) => hint.toLowerCase());
|
|
1739
|
+
return lowerHints.some((hint) => {
|
|
1740
|
+
const base = pathBase(hint).toLowerCase();
|
|
1741
|
+
return metadata.some((value) => value.includes(hint) || value.endsWith(base));
|
|
1742
|
+
});
|
|
1743
|
+
}
|
|
1744
|
+
hasClientMatch(item, scope) {
|
|
1745
|
+
const itemClient = String(item.metadata?.client_name || "");
|
|
1746
|
+
return Boolean(scope.clientName && itemClient && itemClient === scope.clientName);
|
|
1747
|
+
}
|
|
1748
|
+
salienceAdjustment(item) {
|
|
1749
|
+
const salience = item.metadata?.salience;
|
|
1750
|
+
if (salience === "high") return this.rankWeights.highSalienceBonus;
|
|
1751
|
+
if (salience === "medium") return this.rankWeights.mediumSalienceBonus;
|
|
1752
|
+
if (salience === "low") return this.rankWeights.lowSaliencePenalty;
|
|
1753
|
+
return 0;
|
|
1754
|
+
}
|
|
1755
|
+
narrowFocusedMemories(items, scope) {
|
|
1756
|
+
const hasSignals = scope.sourceIds.length > 0 || scope.fileHints.length > 0 || Boolean(scope.clientName);
|
|
1757
|
+
if (!hasSignals) return items;
|
|
1758
|
+
const narrowed = items.filter((item) => {
|
|
1759
|
+
const matchesClient = this.hasClientMatch(item, scope);
|
|
1760
|
+
const matchesFile = this.hasFileMatch(item, scope);
|
|
1761
|
+
const matchesSource = this.hasSourceMatch(item, scope);
|
|
1762
|
+
const salience = item.metadata?.salience;
|
|
1763
|
+
if (scope.clientName && item.metadata?.client_name && !matchesClient) {
|
|
1764
|
+
return false;
|
|
1765
|
+
}
|
|
1766
|
+
if (salience === "low" && !matchesFile && !matchesSource) {
|
|
1767
|
+
return false;
|
|
1768
|
+
}
|
|
1769
|
+
return matchesClient || matchesFile || matchesSource || !scope.clientName;
|
|
1770
|
+
});
|
|
1771
|
+
return narrowed.length > 0 ? narrowed : items;
|
|
1772
|
+
}
|
|
1773
|
+
applyRelevanceFloor(items) {
|
|
1774
|
+
const filtered = items.filter(
|
|
1775
|
+
(item) => item.type === "project" ? item.score >= this.minProjectScore : item.score >= this.minMemoryScore
|
|
1776
|
+
);
|
|
1777
|
+
return { items: filtered, dropped: Math.max(0, items.length - filtered.length) };
|
|
1778
|
+
}
|
|
1779
|
+
rerank(items, scope) {
|
|
1874
1780
|
const deduped = /* @__PURE__ */ new Map();
|
|
1875
1781
|
for (const item of items) {
|
|
1876
|
-
const key =
|
|
1782
|
+
const key = this.stableItemKey(item);
|
|
1877
1783
|
const recency = extractTimestamp(item.metadata) > 0 ? 0.04 : 0;
|
|
1878
1784
|
const queryBonus = item.sourceQuery === "primary" ? 0.08 : item.sourceQuery === "task_frame" ? 0.04 : 0.03;
|
|
1785
|
+
const sourceMatch = this.hasSourceMatch(item, scope);
|
|
1786
|
+
const fileMatch = this.hasFileMatch(item, scope);
|
|
1787
|
+
const clientMatch = this.hasClientMatch(item, scope);
|
|
1788
|
+
const broadPenalty = item.pass === "broad" && !sourceMatch && !fileMatch && !clientMatch ? this.rankWeights.staleBroadPenalty : 0;
|
|
1789
|
+
const clientPenalty = scope.clientName && item.metadata?.client_name && !clientMatch ? this.rankWeights.unrelatedClientPenalty : 0;
|
|
1879
1790
|
const next = {
|
|
1880
1791
|
...item,
|
|
1881
|
-
score:
|
|
1792
|
+
score: clamp01(
|
|
1793
|
+
item.score + queryBonus + recency + (item.pass === "focused" ? this.rankWeights.focusedPassBonus : 0) + (sourceMatch ? this.rankWeights.sourceMatchBonus : 0) + (fileMatch ? this.rankWeights.touchedFileBonus : 0) + (clientMatch ? this.rankWeights.clientMatchBonus : 0) + this.salienceAdjustment(item) + broadPenalty + clientPenalty
|
|
1794
|
+
)
|
|
1882
1795
|
};
|
|
1883
1796
|
const existing = deduped.get(key);
|
|
1884
1797
|
if (!existing || next.score > existing.score) {
|
|
1885
1798
|
deduped.set(key, next);
|
|
1886
1799
|
}
|
|
1887
1800
|
}
|
|
1888
|
-
return
|
|
1801
|
+
return {
|
|
1802
|
+
items: [...deduped.values()].sort((left, right) => right.score - left.score),
|
|
1803
|
+
dedupedCount: Math.max(0, items.length - deduped.size)
|
|
1804
|
+
};
|
|
1889
1805
|
}
|
|
1890
1806
|
buildContext(items) {
|
|
1891
1807
|
const maxChars = this.maxTokens * 4;
|
|
@@ -1924,7 +1840,7 @@ ${lines.join("\n")}`;
|
|
|
1924
1840
|
this.runBranch("project_rules", () => this.args.adapter.query({
|
|
1925
1841
|
project: scope.project,
|
|
1926
1842
|
query: "project rules instructions constraints conventions open threads",
|
|
1927
|
-
top_k: this.
|
|
1843
|
+
top_k: this.focusedTopK,
|
|
1928
1844
|
include_memories: false,
|
|
1929
1845
|
user_id: scope.userId,
|
|
1930
1846
|
session_id: scope.sessionId,
|
|
@@ -1942,7 +1858,7 @@ ${lines.join("\n")}`;
|
|
|
1942
1858
|
continue;
|
|
1943
1859
|
}
|
|
1944
1860
|
if (branch.name === "project_rules") {
|
|
1945
|
-
items.push(...this.contextItems(branch.value, "bootstrap"));
|
|
1861
|
+
items.push(...this.contextItems(branch.value, "bootstrap", "bootstrap"));
|
|
1946
1862
|
continue;
|
|
1947
1863
|
}
|
|
1948
1864
|
const records = branch.value.memories || [];
|
|
@@ -1952,10 +1868,12 @@ ${lines.join("\n")}`;
|
|
|
1952
1868
|
type: "memory",
|
|
1953
1869
|
score: 0.4,
|
|
1954
1870
|
sourceQuery: "bootstrap",
|
|
1871
|
+
pass: "bootstrap",
|
|
1955
1872
|
metadata: memory
|
|
1956
1873
|
})).filter((item) => item.content));
|
|
1957
1874
|
}
|
|
1958
|
-
const
|
|
1875
|
+
const reranked = this.rerank(items, { sourceIds: [], fileHints: [], clientName: this.clientName });
|
|
1876
|
+
const ranked = reranked.items.slice(0, this.broadTopK * 2);
|
|
1959
1877
|
const prepared = {
|
|
1960
1878
|
scope,
|
|
1961
1879
|
retrieval: {
|
|
@@ -1967,7 +1885,14 @@ ${lines.join("\n")}`;
|
|
|
1967
1885
|
durationMs: Date.now() - startedAt,
|
|
1968
1886
|
targetBudgetMs: this.targetRetrievalMs,
|
|
1969
1887
|
hardTimeoutMs: this.hardRetrievalTimeoutMs,
|
|
1970
|
-
branchStatus
|
|
1888
|
+
branchStatus,
|
|
1889
|
+
focusedScopeApplied: false,
|
|
1890
|
+
focusedSourceIds: [],
|
|
1891
|
+
focusedFileHints: [],
|
|
1892
|
+
clientScoped: false,
|
|
1893
|
+
fallbackUsed: false,
|
|
1894
|
+
droppedBelowFloor: 0,
|
|
1895
|
+
dedupedCount: reranked.dedupedCount
|
|
1971
1896
|
},
|
|
1972
1897
|
context: this.buildContext(ranked),
|
|
1973
1898
|
items: ranked
|
|
@@ -1976,100 +1901,195 @@ ${lines.join("\n")}`;
|
|
|
1976
1901
|
return prepared;
|
|
1977
1902
|
}
|
|
1978
1903
|
async beforeTurn(input, context = {}) {
|
|
1904
|
+
this.currentTurn += 1;
|
|
1979
1905
|
this.pushTouchedFiles(input.touchedFiles);
|
|
1906
|
+
this.refreshTaskSummary(input.taskSummary);
|
|
1980
1907
|
const { scope, warning } = await this.resolveScope(context);
|
|
1981
1908
|
const primaryQuery = compactWhitespace(input.userMessage);
|
|
1982
1909
|
const taskFrameQuery = this.makeTaskFrameQuery(input);
|
|
1910
|
+
const focusedScope = this.focusedScope(input);
|
|
1911
|
+
const focusedMetadataFilter = this.exactFileMetadataFilter(focusedScope.fileHints);
|
|
1912
|
+
const focusedScopeApplied = focusedScope.sourceIds.length > 0 || focusedScope.fileHints.length > 0 || Boolean(focusedScope.clientName);
|
|
1983
1913
|
const warnings = warning ? [warning] : [];
|
|
1984
1914
|
const startedAt = Date.now();
|
|
1985
|
-
const
|
|
1986
|
-
|
|
1915
|
+
const branchStatus = {};
|
|
1916
|
+
const collectFromBranches = (branches, pass) => {
|
|
1917
|
+
const collected = [];
|
|
1918
|
+
let okCount = 0;
|
|
1919
|
+
for (const branch of branches) {
|
|
1920
|
+
branchStatus[branch.name] = branch.status;
|
|
1921
|
+
if (branch.status !== "ok") {
|
|
1922
|
+
if (branch.status !== "skipped" && branch.reason) warnings.push(`${branch.name}:${branch.reason}`);
|
|
1923
|
+
continue;
|
|
1924
|
+
}
|
|
1925
|
+
okCount += 1;
|
|
1926
|
+
if (branch.name.startsWith("context")) {
|
|
1927
|
+
collected.push(...this.contextItems(
|
|
1928
|
+
branch.value,
|
|
1929
|
+
branch.name.includes("task_frame") ? "task_frame" : "primary",
|
|
1930
|
+
pass
|
|
1931
|
+
));
|
|
1932
|
+
} else {
|
|
1933
|
+
const memoryItems = this.memoryItems(
|
|
1934
|
+
branch.value,
|
|
1935
|
+
branch.name.includes("task_frame") ? "task_frame" : "primary",
|
|
1936
|
+
pass
|
|
1937
|
+
);
|
|
1938
|
+
collected.push(...pass === "focused" ? this.narrowFocusedMemories(memoryItems, focusedScope) : memoryItems);
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
return { collected, okCount };
|
|
1942
|
+
};
|
|
1943
|
+
const focusedBranches = await Promise.all([
|
|
1944
|
+
this.runBranch("context_primary_focused", () => this.args.adapter.query({
|
|
1987
1945
|
project: scope.project,
|
|
1988
1946
|
query: primaryQuery,
|
|
1989
|
-
top_k: this.
|
|
1947
|
+
top_k: this.focusedTopK,
|
|
1990
1948
|
include_memories: false,
|
|
1991
1949
|
user_id: scope.userId,
|
|
1992
1950
|
session_id: scope.sessionId,
|
|
1951
|
+
source_ids: focusedScope.sourceIds.length > 0 ? focusedScope.sourceIds : void 0,
|
|
1952
|
+
metadata_filter: focusedMetadataFilter,
|
|
1993
1953
|
max_tokens: this.maxTokens,
|
|
1994
1954
|
compress: true,
|
|
1995
1955
|
compression_strategy: "adaptive"
|
|
1996
1956
|
})),
|
|
1997
|
-
this.runBranch("
|
|
1957
|
+
this.runBranch("memory_primary_focused", () => this.args.adapter.searchMemories({
|
|
1998
1958
|
project: scope.project,
|
|
1999
1959
|
query: primaryQuery,
|
|
2000
1960
|
user_id: scope.userId,
|
|
2001
1961
|
session_id: scope.sessionId,
|
|
2002
|
-
top_k: this.
|
|
1962
|
+
top_k: this.focusedTopK,
|
|
2003
1963
|
include_pending: true,
|
|
2004
1964
|
profile: "balanced"
|
|
2005
1965
|
})),
|
|
2006
|
-
taskFrameQuery ? this.runBranch("
|
|
1966
|
+
taskFrameQuery ? this.runBranch("context_task_frame_focused", () => this.args.adapter.query({
|
|
2007
1967
|
project: scope.project,
|
|
2008
1968
|
query: taskFrameQuery,
|
|
2009
|
-
top_k: this.
|
|
1969
|
+
top_k: this.focusedTopK,
|
|
2010
1970
|
include_memories: false,
|
|
2011
1971
|
user_id: scope.userId,
|
|
2012
1972
|
session_id: scope.sessionId,
|
|
1973
|
+
source_ids: focusedScope.sourceIds.length > 0 ? focusedScope.sourceIds : void 0,
|
|
1974
|
+
metadata_filter: focusedMetadataFilter,
|
|
2013
1975
|
max_tokens: this.maxTokens,
|
|
2014
1976
|
compress: true,
|
|
2015
1977
|
compression_strategy: "adaptive"
|
|
2016
|
-
})) : Promise.resolve({
|
|
2017
|
-
|
|
2018
|
-
status: "skipped",
|
|
2019
|
-
durationMs: 0
|
|
2020
|
-
}),
|
|
2021
|
-
taskFrameQuery ? this.runBranch("memory_task_frame", () => this.args.adapter.searchMemories({
|
|
1978
|
+
})) : Promise.resolve({ name: "context_task_frame_focused", status: "skipped", durationMs: 0 }),
|
|
1979
|
+
taskFrameQuery ? this.runBranch("memory_task_frame_focused", () => this.args.adapter.searchMemories({
|
|
2022
1980
|
project: scope.project,
|
|
2023
1981
|
query: taskFrameQuery,
|
|
2024
1982
|
user_id: scope.userId,
|
|
2025
1983
|
session_id: scope.sessionId,
|
|
2026
|
-
top_k: this.
|
|
1984
|
+
top_k: this.focusedTopK,
|
|
2027
1985
|
include_pending: true,
|
|
2028
1986
|
profile: "balanced"
|
|
2029
|
-
})) : Promise.resolve({
|
|
2030
|
-
name: "memory_task_frame",
|
|
2031
|
-
status: "skipped",
|
|
2032
|
-
durationMs: 0
|
|
2033
|
-
})
|
|
1987
|
+
})) : Promise.resolve({ name: "memory_task_frame_focused", status: "skipped", durationMs: 0 })
|
|
2034
1988
|
]);
|
|
2035
|
-
const
|
|
2036
|
-
const
|
|
2037
|
-
|
|
2038
|
-
|
|
1989
|
+
const focusedCollected = collectFromBranches(focusedBranches, "focused");
|
|
1990
|
+
const focusedRanked = this.rerank(focusedCollected.collected, focusedScope);
|
|
1991
|
+
const focusedFloored = this.applyRelevanceFloor(focusedRanked.items);
|
|
1992
|
+
let allCollected = [...focusedFloored.items];
|
|
1993
|
+
let totalOkCount = focusedCollected.okCount;
|
|
1994
|
+
let dedupedCount = focusedRanked.dedupedCount;
|
|
1995
|
+
let droppedBelowFloor = focusedFloored.dropped;
|
|
1996
|
+
const focusedTopScore = focusedFloored.items[0]?.score ?? 0;
|
|
1997
|
+
const fallbackUsed = focusedFloored.items.length < this.minFocusedResults || focusedTopScore < this.minFocusedTopScore;
|
|
1998
|
+
if (focusedScopeApplied) {
|
|
1999
|
+
this.sourceScopedTurns += 1;
|
|
2000
|
+
}
|
|
2001
|
+
if (!fallbackUsed) {
|
|
2002
|
+
this.focusedPassHits += 1;
|
|
2003
|
+
}
|
|
2004
|
+
const broadBranches = fallbackUsed ? await Promise.all([
|
|
2005
|
+
this.runBranch("context_primary_broad", () => this.args.adapter.query({
|
|
2006
|
+
project: scope.project,
|
|
2007
|
+
query: primaryQuery,
|
|
2008
|
+
top_k: this.broadTopK,
|
|
2009
|
+
include_memories: false,
|
|
2010
|
+
user_id: scope.userId,
|
|
2011
|
+
session_id: scope.sessionId,
|
|
2012
|
+
max_tokens: this.maxTokens,
|
|
2013
|
+
compress: true,
|
|
2014
|
+
compression_strategy: "adaptive"
|
|
2015
|
+
})),
|
|
2016
|
+
this.runBranch("memory_primary_broad", () => this.args.adapter.searchMemories({
|
|
2017
|
+
project: scope.project,
|
|
2018
|
+
query: primaryQuery,
|
|
2019
|
+
user_id: scope.userId,
|
|
2020
|
+
session_id: scope.sessionId,
|
|
2021
|
+
top_k: this.broadTopK,
|
|
2022
|
+
include_pending: true,
|
|
2023
|
+
profile: "balanced"
|
|
2024
|
+
})),
|
|
2025
|
+
taskFrameQuery ? this.runBranch("context_task_frame_broad", () => this.args.adapter.query({
|
|
2026
|
+
project: scope.project,
|
|
2027
|
+
query: taskFrameQuery,
|
|
2028
|
+
top_k: this.broadTopK,
|
|
2029
|
+
include_memories: false,
|
|
2030
|
+
user_id: scope.userId,
|
|
2031
|
+
session_id: scope.sessionId,
|
|
2032
|
+
max_tokens: this.maxTokens,
|
|
2033
|
+
compress: true,
|
|
2034
|
+
compression_strategy: "adaptive"
|
|
2035
|
+
})) : Promise.resolve({ name: "context_task_frame_broad", status: "skipped", durationMs: 0 }),
|
|
2036
|
+
taskFrameQuery ? this.runBranch("memory_task_frame_broad", () => this.args.adapter.searchMemories({
|
|
2037
|
+
project: scope.project,
|
|
2038
|
+
query: taskFrameQuery,
|
|
2039
|
+
user_id: scope.userId,
|
|
2040
|
+
session_id: scope.sessionId,
|
|
2041
|
+
top_k: this.broadTopK,
|
|
2042
|
+
include_pending: true,
|
|
2043
|
+
profile: "balanced"
|
|
2044
|
+
})) : Promise.resolve({ name: "memory_task_frame_broad", status: "skipped", durationMs: 0 })
|
|
2045
|
+
]) : [
|
|
2046
|
+
{ name: "context_primary_broad", status: "skipped", durationMs: 0 },
|
|
2047
|
+
{ name: "memory_primary_broad", status: "skipped", durationMs: 0 },
|
|
2048
|
+
{ name: "context_task_frame_broad", status: "skipped", durationMs: 0 },
|
|
2049
|
+
{ name: "memory_task_frame_broad", status: "skipped", durationMs: 0 }
|
|
2050
|
+
];
|
|
2051
|
+
const broadCollected = collectFromBranches(broadBranches, "broad");
|
|
2052
|
+
totalOkCount += broadCollected.okCount;
|
|
2053
|
+
if (fallbackUsed) {
|
|
2054
|
+
this.fallbackTriggers += 1;
|
|
2055
|
+
this.broadScopedTurns += 1;
|
|
2056
|
+
allCollected = [...allCollected, ...broadCollected.collected];
|
|
2057
|
+
}
|
|
2058
|
+
const ranked = this.rerank(allCollected, focusedScope);
|
|
2059
|
+
dedupedCount += ranked.dedupedCount;
|
|
2060
|
+
const floored = this.applyRelevanceFloor(ranked.items);
|
|
2061
|
+
droppedBelowFloor += floored.dropped;
|
|
2062
|
+
this.floorDroppedCount += droppedBelowFloor;
|
|
2063
|
+
this.droppedCount += droppedBelowFloor;
|
|
2064
|
+
const finalItems = floored.items.slice(0, this.broadTopK);
|
|
2065
|
+
this.injectedItemCount += finalItems.length;
|
|
2066
|
+
this.totalTurns += 1;
|
|
2067
|
+
const executedBranches = [...focusedBranches, ...broadBranches].filter((branch) => branch.status !== "skipped");
|
|
2068
|
+
for (const branch of [...focusedBranches, ...broadBranches]) {
|
|
2039
2069
|
branchStatus[branch.name] = branch.status;
|
|
2040
|
-
if (branch.status !== "ok") {
|
|
2041
|
-
if (branch.status !== "skipped" && branch.reason) warnings.push(`${branch.name}:${branch.reason}`);
|
|
2042
|
-
continue;
|
|
2043
|
-
}
|
|
2044
|
-
okCount += 1;
|
|
2045
|
-
if (branch.name.startsWith("context")) {
|
|
2046
|
-
collected.push(...this.contextItems(
|
|
2047
|
-
branch.value,
|
|
2048
|
-
branch.name.includes("task_frame") ? "task_frame" : "primary"
|
|
2049
|
-
));
|
|
2050
|
-
} else {
|
|
2051
|
-
collected.push(...this.memoryItems(
|
|
2052
|
-
branch.value,
|
|
2053
|
-
branch.name.includes("task_frame") ? "task_frame" : "primary"
|
|
2054
|
-
));
|
|
2055
|
-
}
|
|
2056
2070
|
}
|
|
2057
|
-
const ranked = this.rerank(collected).slice(0, this.topK * 2);
|
|
2058
2071
|
const prepared = {
|
|
2059
2072
|
scope,
|
|
2060
2073
|
retrieval: {
|
|
2061
2074
|
primaryQuery,
|
|
2062
2075
|
taskFrameQuery,
|
|
2063
2076
|
warnings,
|
|
2064
|
-
degraded:
|
|
2065
|
-
degradedReason:
|
|
2077
|
+
degraded: totalOkCount < executedBranches.length,
|
|
2078
|
+
degradedReason: totalOkCount === 0 ? "all_retrieval_failed" : warnings.length > 0 ? "partial_retrieval_failed" : void 0,
|
|
2066
2079
|
durationMs: Date.now() - startedAt,
|
|
2067
2080
|
targetBudgetMs: this.targetRetrievalMs,
|
|
2068
2081
|
hardTimeoutMs: this.hardRetrievalTimeoutMs,
|
|
2069
|
-
branchStatus
|
|
2082
|
+
branchStatus,
|
|
2083
|
+
focusedScopeApplied,
|
|
2084
|
+
focusedSourceIds: focusedScope.sourceIds,
|
|
2085
|
+
focusedFileHints: focusedScope.fileHints.map((value) => pathBase(value)),
|
|
2086
|
+
clientScoped: Boolean(focusedScope.clientName),
|
|
2087
|
+
fallbackUsed,
|
|
2088
|
+
droppedBelowFloor,
|
|
2089
|
+
dedupedCount
|
|
2070
2090
|
},
|
|
2071
|
-
context: this.buildContext(
|
|
2072
|
-
items:
|
|
2091
|
+
context: this.buildContext(finalItems),
|
|
2092
|
+
items: finalItems
|
|
2073
2093
|
};
|
|
2074
2094
|
this.lastPreparedTurn = prepared.retrieval;
|
|
2075
2095
|
return prepared;
|
|
@@ -2164,7 +2184,14 @@ ${lines.join("\n")}`;
|
|
|
2164
2184
|
counters: {
|
|
2165
2185
|
mergedCount: this.mergedCount,
|
|
2166
2186
|
droppedCount: this.droppedCount,
|
|
2167
|
-
bufferedLowSalience: this.bufferedLowSalience.length
|
|
2187
|
+
bufferedLowSalience: this.bufferedLowSalience.length,
|
|
2188
|
+
focusedPassHits: this.focusedPassHits,
|
|
2189
|
+
fallbackTriggers: this.fallbackTriggers,
|
|
2190
|
+
floorDroppedCount: this.floorDroppedCount,
|
|
2191
|
+
injectedItemCount: this.injectedItemCount,
|
|
2192
|
+
sourceScopedTurns: this.sourceScopedTurns,
|
|
2193
|
+
broadScopedTurns: this.broadScopedTurns,
|
|
2194
|
+
totalTurns: this.totalTurns
|
|
2168
2195
|
}
|
|
2169
2196
|
};
|
|
2170
2197
|
}
|
|
@@ -2382,165 +2409,469 @@ var WhisperClient = class _WhisperClient {
|
|
|
2382
2409
|
retryable: false
|
|
2383
2410
|
});
|
|
2384
2411
|
}
|
|
2385
|
-
return resolved;
|
|
2412
|
+
return resolved;
|
|
2413
|
+
}
|
|
2414
|
+
async refreshProjectCache(force = false) {
|
|
2415
|
+
if (!force && Date.now() < this.projectCacheExpiresAt && this.projectCache.length > 0) {
|
|
2416
|
+
return this.projectCache;
|
|
2417
|
+
}
|
|
2418
|
+
const response = await this.runtimeClient.request({
|
|
2419
|
+
endpoint: "/v1/projects",
|
|
2420
|
+
method: "GET",
|
|
2421
|
+
operation: "get",
|
|
2422
|
+
idempotent: true
|
|
2423
|
+
});
|
|
2424
|
+
this.projectRefToId.clear();
|
|
2425
|
+
this.projectCache = response.data?.projects || [];
|
|
2426
|
+
for (const project of this.projectCache) {
|
|
2427
|
+
this.projectRefToId.set(project.id, project.id);
|
|
2428
|
+
this.projectRefToId.set(project.slug, project.id);
|
|
2429
|
+
this.projectRefToId.set(project.name, project.id);
|
|
2430
|
+
}
|
|
2431
|
+
this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
|
|
2432
|
+
return this.projectCache;
|
|
2433
|
+
}
|
|
2434
|
+
async fetchResolvedProject(projectRef) {
|
|
2435
|
+
try {
|
|
2436
|
+
const response = await this.runtimeClient.request({
|
|
2437
|
+
endpoint: `/v1/projects/resolve?project=${encodeURIComponent(projectRef)}`,
|
|
2438
|
+
method: "GET",
|
|
2439
|
+
operation: "get",
|
|
2440
|
+
idempotent: true
|
|
2441
|
+
});
|
|
2442
|
+
return response.data?.resolved || null;
|
|
2443
|
+
} catch (error) {
|
|
2444
|
+
if (error instanceof RuntimeClientError && error.status === 404) {
|
|
2445
|
+
return null;
|
|
2446
|
+
}
|
|
2447
|
+
throw error;
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
async resolveProject(projectRef) {
|
|
2451
|
+
const resolvedRef = this.getRequiredProject(projectRef);
|
|
2452
|
+
const cachedProjects = await this.refreshProjectCache(false);
|
|
2453
|
+
const cachedProject = cachedProjects.find(
|
|
2454
|
+
(project) => project.id === resolvedRef || project.slug === resolvedRef || project.name === resolvedRef
|
|
2455
|
+
);
|
|
2456
|
+
if (cachedProject) {
|
|
2457
|
+
return cachedProject;
|
|
2458
|
+
}
|
|
2459
|
+
const resolvedProject = await this.fetchResolvedProject(resolvedRef);
|
|
2460
|
+
if (resolvedProject) {
|
|
2461
|
+
this.projectRefToId.set(resolvedProject.id, resolvedProject.id);
|
|
2462
|
+
this.projectRefToId.set(resolvedProject.slug, resolvedProject.id);
|
|
2463
|
+
this.projectRefToId.set(resolvedProject.name, resolvedProject.id);
|
|
2464
|
+
this.projectCache = [
|
|
2465
|
+
...this.projectCache.filter((project) => project.id !== resolvedProject.id),
|
|
2466
|
+
resolvedProject
|
|
2467
|
+
];
|
|
2468
|
+
this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
|
|
2469
|
+
return resolvedProject;
|
|
2470
|
+
}
|
|
2471
|
+
if (isLikelyProjectId(resolvedRef)) {
|
|
2472
|
+
return {
|
|
2473
|
+
id: resolvedRef,
|
|
2474
|
+
orgId: "",
|
|
2475
|
+
name: resolvedRef,
|
|
2476
|
+
slug: resolvedRef,
|
|
2477
|
+
createdAt: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
2478
|
+
updatedAt: (/* @__PURE__ */ new Date(0)).toISOString()
|
|
2479
|
+
};
|
|
2480
|
+
}
|
|
2481
|
+
throw new RuntimeClientError({
|
|
2482
|
+
code: "PROJECT_NOT_FOUND",
|
|
2483
|
+
message: `Project '${resolvedRef}' not found`,
|
|
2484
|
+
retryable: false
|
|
2485
|
+
});
|
|
2486
|
+
}
|
|
2487
|
+
async query(params) {
|
|
2488
|
+
const project = (await this.resolveProject(params.project)).id;
|
|
2489
|
+
const response = await this.runtimeClient.request({
|
|
2490
|
+
endpoint: "/v1/context/query",
|
|
2491
|
+
method: "POST",
|
|
2492
|
+
operation: "search",
|
|
2493
|
+
body: {
|
|
2494
|
+
...params,
|
|
2495
|
+
project
|
|
2496
|
+
},
|
|
2497
|
+
idempotent: true
|
|
2498
|
+
});
|
|
2499
|
+
return response.data;
|
|
2500
|
+
}
|
|
2501
|
+
async ingestSession(params) {
|
|
2502
|
+
const project = (await this.resolveProject(params.project)).id;
|
|
2503
|
+
const response = await this.runtimeClient.request({
|
|
2504
|
+
endpoint: "/v1/memory/ingest/session",
|
|
2505
|
+
method: "POST",
|
|
2506
|
+
operation: "session",
|
|
2507
|
+
body: {
|
|
2508
|
+
...params,
|
|
2509
|
+
project
|
|
2510
|
+
}
|
|
2511
|
+
});
|
|
2512
|
+
return response.data;
|
|
2513
|
+
}
|
|
2514
|
+
createAgentRuntime(options = {}) {
|
|
2515
|
+
const baseContext = {
|
|
2516
|
+
workspacePath: options.workspacePath,
|
|
2517
|
+
project: options.project || this.config.project,
|
|
2518
|
+
userId: options.userId,
|
|
2519
|
+
sessionId: options.sessionId,
|
|
2520
|
+
traceId: options.traceId,
|
|
2521
|
+
clientName: options.clientName
|
|
2522
|
+
};
|
|
2523
|
+
return new WhisperAgentRuntime({
|
|
2524
|
+
baseContext,
|
|
2525
|
+
options,
|
|
2526
|
+
adapter: {
|
|
2527
|
+
resolveProject: (project) => this.resolveProject(project),
|
|
2528
|
+
query: (params) => this.query(params),
|
|
2529
|
+
ingestSession: (params) => this.ingestSession(params),
|
|
2530
|
+
getSessionMemories: (params) => this.memory.getSessionMemories(params),
|
|
2531
|
+
getUserProfile: (params) => this.memory.getUserProfile(params),
|
|
2532
|
+
searchMemories: (params) => this.memory.search(params),
|
|
2533
|
+
addMemory: (params) => this.memory.add(params),
|
|
2534
|
+
queueStatus: () => this.queue.status(),
|
|
2535
|
+
flushQueue: () => this.queue.flush()
|
|
2536
|
+
}
|
|
2537
|
+
});
|
|
2538
|
+
}
|
|
2539
|
+
withRunContext(context) {
|
|
2540
|
+
const base = this;
|
|
2541
|
+
return {
|
|
2542
|
+
memory: {
|
|
2543
|
+
add: (params) => base.memory.add({
|
|
2544
|
+
...params,
|
|
2545
|
+
project: params.project || context.project || base.config.project,
|
|
2546
|
+
user_id: params.user_id || context.userId,
|
|
2547
|
+
session_id: params.session_id || context.sessionId
|
|
2548
|
+
}),
|
|
2549
|
+
search: (params) => base.memory.search({
|
|
2550
|
+
...params,
|
|
2551
|
+
project: params.project || context.project || base.config.project,
|
|
2552
|
+
user_id: params.user_id || context.userId,
|
|
2553
|
+
session_id: params.session_id || context.sessionId
|
|
2554
|
+
})
|
|
2555
|
+
},
|
|
2556
|
+
session: {
|
|
2557
|
+
event: (params) => base.session.event({
|
|
2558
|
+
...params,
|
|
2559
|
+
sessionId: params.sessionId || context.sessionId || ""
|
|
2560
|
+
})
|
|
2561
|
+
},
|
|
2562
|
+
queue: base.queue,
|
|
2563
|
+
diagnostics: base.diagnostics
|
|
2564
|
+
};
|
|
2565
|
+
}
|
|
2566
|
+
async shutdown() {
|
|
2567
|
+
await this.writeQueue.stop();
|
|
2568
|
+
}
|
|
2569
|
+
};
|
|
2570
|
+
var whisper_default = WhisperClient;
|
|
2571
|
+
|
|
2572
|
+
// ../src/sdk/whisper-agent.ts
|
|
2573
|
+
var DEPRECATION_WARNINGS = /* @__PURE__ */ new Set();
|
|
2574
|
+
function warnDeprecatedOnce(key, message) {
|
|
2575
|
+
if (DEPRECATION_WARNINGS.has(key)) return;
|
|
2576
|
+
DEPRECATION_WARNINGS.add(key);
|
|
2577
|
+
if (typeof console !== "undefined" && typeof console.warn === "function") {
|
|
2578
|
+
console.warn(message);
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
var Whisper = class {
|
|
2582
|
+
client;
|
|
2583
|
+
runtimeClient;
|
|
2584
|
+
options;
|
|
2585
|
+
sessionId;
|
|
2586
|
+
userId;
|
|
2587
|
+
constructor(options) {
|
|
2588
|
+
if (!options.apiKey) {
|
|
2589
|
+
throw new Error("API key is required");
|
|
2590
|
+
}
|
|
2591
|
+
const clientConfig = {
|
|
2592
|
+
apiKey: options.apiKey,
|
|
2593
|
+
baseUrl: options.baseUrl,
|
|
2594
|
+
project: options.project || "default"
|
|
2595
|
+
};
|
|
2596
|
+
if (options.timeoutMs) clientConfig.timeoutMs = options.timeoutMs;
|
|
2597
|
+
if (options.retry) clientConfig.retry = options.retry;
|
|
2598
|
+
this.client = new WhisperContext(clientConfig);
|
|
2599
|
+
this.runtimeClient = new WhisperClient({
|
|
2600
|
+
apiKey: options.apiKey,
|
|
2601
|
+
baseUrl: options.baseUrl,
|
|
2602
|
+
project: options.project || "default"
|
|
2603
|
+
});
|
|
2604
|
+
warnDeprecatedOnce(
|
|
2605
|
+
"whisper_agent_wrapper",
|
|
2606
|
+
"[Whisper SDK] Whisper wrapper is supported for v2 compatibility. Prefer WhisperClient for new integrations."
|
|
2607
|
+
);
|
|
2608
|
+
const finalRetry = options.retry || { maxAttempts: 3, baseDelayMs: 250, maxDelayMs: 2e3 };
|
|
2609
|
+
this.options = {
|
|
2610
|
+
apiKey: options.apiKey,
|
|
2611
|
+
baseUrl: options.baseUrl || "https://context.usewhisper.dev",
|
|
2612
|
+
project: options.project || "default",
|
|
2613
|
+
timeoutMs: options.timeoutMs || 15e3,
|
|
2614
|
+
retry: finalRetry,
|
|
2615
|
+
contextLimit: options.contextLimit ?? 10,
|
|
2616
|
+
memoryTypes: options.memoryTypes ?? ["factual", "preference", "event", "goal", "relationship", "opinion", "instruction"],
|
|
2617
|
+
contextPrefix: options.contextPrefix ?? "Relevant context:",
|
|
2618
|
+
autoExtract: options.autoExtract ?? true,
|
|
2619
|
+
autoExtractMinConfidence: options.autoExtractMinConfidence ?? 0.65,
|
|
2620
|
+
maxMemoriesPerCapture: options.maxMemoriesPerCapture ?? 5
|
|
2621
|
+
};
|
|
2622
|
+
}
|
|
2623
|
+
/**
|
|
2624
|
+
* Set session ID for conversation tracking
|
|
2625
|
+
*/
|
|
2626
|
+
session(sessionId) {
|
|
2627
|
+
this.sessionId = sessionId;
|
|
2628
|
+
return this;
|
|
2629
|
+
}
|
|
2630
|
+
/**
|
|
2631
|
+
* Set user ID for user-specific memories
|
|
2632
|
+
*/
|
|
2633
|
+
user(userId) {
|
|
2634
|
+
this.userId = userId;
|
|
2635
|
+
return this;
|
|
2636
|
+
}
|
|
2637
|
+
/**
|
|
2638
|
+
* Get relevant context BEFORE your LLM call
|
|
2639
|
+
*
|
|
2640
|
+
* @param query - What you want to know / user question
|
|
2641
|
+
* @returns Context string and raw results
|
|
2642
|
+
*
|
|
2643
|
+
* @example
|
|
2644
|
+
* ```typescript
|
|
2645
|
+
* const { context, results, count } = await whisper.getContext(
|
|
2646
|
+
* "What are user's preferences?",
|
|
2647
|
+
* { userId: "user-123" }
|
|
2648
|
+
* );
|
|
2649
|
+
*
|
|
2650
|
+
* // Results: [
|
|
2651
|
+
* // { content: "User prefers dark mode", type: "preference", score: 0.95 },
|
|
2652
|
+
* // { content: "Allergic to nuts", type: "factual", score: 0.89 }
|
|
2653
|
+
* // ]
|
|
2654
|
+
* ```
|
|
2655
|
+
*/
|
|
2656
|
+
async getContext(query, options) {
|
|
2657
|
+
const runtime = this.runtimeClient.createAgentRuntime({
|
|
2658
|
+
project: options?.project ?? this.options.project,
|
|
2659
|
+
userId: options?.userId ?? this.userId,
|
|
2660
|
+
sessionId: options?.sessionId ?? this.sessionId,
|
|
2661
|
+
topK: options?.limit ?? this.options.contextLimit,
|
|
2662
|
+
clientName: "whisper-wrapper"
|
|
2663
|
+
});
|
|
2664
|
+
const prepared = await runtime.beforeTurn({
|
|
2665
|
+
userMessage: query
|
|
2666
|
+
});
|
|
2667
|
+
const results = prepared.items.map((item, index) => ({
|
|
2668
|
+
id: item.id || `runtime_${index}`,
|
|
2669
|
+
content: item.content,
|
|
2670
|
+
score: item.score,
|
|
2671
|
+
metadata: item.metadata || {},
|
|
2672
|
+
source: item.type === "memory" ? "memory" : "runtime",
|
|
2673
|
+
document: item.sourceQuery,
|
|
2674
|
+
type: item.type,
|
|
2675
|
+
retrieval_source: item.type === "memory" ? "memory" : "runtime"
|
|
2676
|
+
}));
|
|
2677
|
+
const context = results.map((r, i) => `[${i + 1}] ${r.content}`).join("\n");
|
|
2678
|
+
return {
|
|
2679
|
+
context: context ? `${this.options.contextPrefix}
|
|
2680
|
+
${context}` : "",
|
|
2681
|
+
results,
|
|
2682
|
+
count: prepared.items.length
|
|
2683
|
+
};
|
|
2684
|
+
}
|
|
2685
|
+
/**
|
|
2686
|
+
* Remember what happened AFTER your LLM response
|
|
2687
|
+
*
|
|
2688
|
+
* Fire-and-forget - doesn't block your response
|
|
2689
|
+
*
|
|
2690
|
+
* @param content - What your LLM responded with
|
|
2691
|
+
* @returns Promise that resolves when stored (or fails silently)
|
|
2692
|
+
*
|
|
2693
|
+
* @example
|
|
2694
|
+
* ```typescript
|
|
2695
|
+
* const llmResponse = "I've set your theme to dark mode and removed nuts from recommendations.";
|
|
2696
|
+
*
|
|
2697
|
+
* await whisper.remember(llmResponse, { userId: "user-123" });
|
|
2698
|
+
* // → Auto-extracts: "theme set to dark mode", "nut allergy"
|
|
2699
|
+
* // → Stored as preferences
|
|
2700
|
+
* ```
|
|
2701
|
+
*/
|
|
2702
|
+
async remember(content, options) {
|
|
2703
|
+
if (!content || content.length < 5) {
|
|
2704
|
+
return { success: false };
|
|
2705
|
+
}
|
|
2706
|
+
try {
|
|
2707
|
+
if (this.options.autoExtract) {
|
|
2708
|
+
const extraction = await this.client.extractMemories({
|
|
2709
|
+
project: options?.project ?? this.options.project,
|
|
2710
|
+
message: content,
|
|
2711
|
+
user_id: options?.userId ?? this.userId,
|
|
2712
|
+
session_id: options?.sessionId ?? this.sessionId,
|
|
2713
|
+
enable_pattern: true,
|
|
2714
|
+
enable_inference: true,
|
|
2715
|
+
min_confidence: this.options.autoExtractMinConfidence
|
|
2716
|
+
});
|
|
2717
|
+
const extractedMemories = (extraction.all || []).filter((m) => (m.confidence || 0) >= this.options.autoExtractMinConfidence).slice(0, this.options.maxMemoriesPerCapture);
|
|
2718
|
+
if (extractedMemories.length > 0) {
|
|
2719
|
+
const bulk = await this.client.addMemoriesBulk({
|
|
2720
|
+
project: options?.project ?? this.options.project,
|
|
2721
|
+
write_mode: "async",
|
|
2722
|
+
memories: extractedMemories.map((m) => ({
|
|
2723
|
+
content: m.content,
|
|
2724
|
+
memory_type: m.memoryType,
|
|
2725
|
+
user_id: options?.userId ?? this.userId,
|
|
2726
|
+
session_id: options?.sessionId ?? this.sessionId,
|
|
2727
|
+
importance: Math.max(0.5, Math.min(1, m.confidence || 0.7)),
|
|
2728
|
+
confidence: m.confidence || 0.7,
|
|
2729
|
+
entity_mentions: m.entityMentions || [],
|
|
2730
|
+
event_date: m.eventDate || void 0,
|
|
2731
|
+
metadata: {
|
|
2732
|
+
extracted: true,
|
|
2733
|
+
extraction_method: extraction.extractionMethod,
|
|
2734
|
+
extraction_reasoning: m.reasoning,
|
|
2735
|
+
inferred: Boolean(m.inferred)
|
|
2736
|
+
}
|
|
2737
|
+
}))
|
|
2738
|
+
});
|
|
2739
|
+
const memoryIds = this.extractMemoryIdsFromBulkResponse(bulk);
|
|
2740
|
+
return {
|
|
2741
|
+
success: true,
|
|
2742
|
+
memoryId: memoryIds[0],
|
|
2743
|
+
memoryIds: memoryIds.length > 0 ? memoryIds : void 0,
|
|
2744
|
+
extracted: extractedMemories.length
|
|
2745
|
+
};
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
const result = await this.client.addMemory({
|
|
2749
|
+
project: options?.project ?? this.options.project,
|
|
2750
|
+
content,
|
|
2751
|
+
user_id: options?.userId ?? this.userId,
|
|
2752
|
+
session_id: options?.sessionId ?? this.sessionId
|
|
2753
|
+
});
|
|
2754
|
+
return {
|
|
2755
|
+
success: true,
|
|
2756
|
+
memoryId: result?.id
|
|
2757
|
+
};
|
|
2758
|
+
} catch (error) {
|
|
2759
|
+
console.error("[Whisper] Remember failed:", error);
|
|
2760
|
+
return { success: false };
|
|
2761
|
+
}
|
|
2386
2762
|
}
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
endpoint: "/v1/projects",
|
|
2393
|
-
method: "GET",
|
|
2394
|
-
operation: "get",
|
|
2395
|
-
idempotent: true
|
|
2396
|
-
});
|
|
2397
|
-
this.projectRefToId.clear();
|
|
2398
|
-
this.projectCache = response.data?.projects || [];
|
|
2399
|
-
for (const project of this.projectCache) {
|
|
2400
|
-
this.projectRefToId.set(project.id, project.id);
|
|
2401
|
-
this.projectRefToId.set(project.slug, project.id);
|
|
2402
|
-
this.projectRefToId.set(project.name, project.id);
|
|
2403
|
-
}
|
|
2404
|
-
this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
|
|
2405
|
-
return this.projectCache;
|
|
2763
|
+
/**
|
|
2764
|
+
* Alias for remember() - same thing
|
|
2765
|
+
*/
|
|
2766
|
+
async capture(content, options) {
|
|
2767
|
+
return this.remember(content, options);
|
|
2406
2768
|
}
|
|
2407
|
-
|
|
2769
|
+
/**
|
|
2770
|
+
* Capture from multiple messages (e.g., full conversation)
|
|
2771
|
+
*/
|
|
2772
|
+
async captureSession(messages, options) {
|
|
2408
2773
|
try {
|
|
2409
|
-
const
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2774
|
+
const filteredMessages = messages.filter((m) => m.role !== "system");
|
|
2775
|
+
const runtime = this.runtimeClient.createAgentRuntime({
|
|
2776
|
+
project: options?.project ?? this.options.project,
|
|
2777
|
+
userId: options?.userId ?? this.userId,
|
|
2778
|
+
sessionId: options?.sessionId ?? this.sessionId ?? "default",
|
|
2779
|
+
clientName: "whisper-wrapper"
|
|
2414
2780
|
});
|
|
2415
|
-
|
|
2781
|
+
const result = await runtime.afterTurn({
|
|
2782
|
+
userMessage: [...filteredMessages].reverse().find((m) => m.role === "user")?.content || "",
|
|
2783
|
+
assistantMessage: [...filteredMessages].reverse().find((m) => m.role === "assistant")?.content || ""
|
|
2784
|
+
});
|
|
2785
|
+
return {
|
|
2786
|
+
success: true,
|
|
2787
|
+
extracted: result.memoriesCreated ?? 0
|
|
2788
|
+
};
|
|
2416
2789
|
} catch (error) {
|
|
2417
|
-
|
|
2418
|
-
|
|
2790
|
+
const fallback = await this.fallbackCaptureViaAddMemory(messages, options);
|
|
2791
|
+
if (fallback.success) {
|
|
2792
|
+
return fallback;
|
|
2419
2793
|
}
|
|
2420
|
-
|
|
2794
|
+
console.error("[Whisper] Session capture failed:", error);
|
|
2795
|
+
return { success: false, extracted: 0 };
|
|
2421
2796
|
}
|
|
2422
2797
|
}
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
const resolvedProject = await this.fetchResolvedProject(resolvedRef);
|
|
2433
|
-
if (resolvedProject) {
|
|
2434
|
-
this.projectRefToId.set(resolvedProject.id, resolvedProject.id);
|
|
2435
|
-
this.projectRefToId.set(resolvedProject.slug, resolvedProject.id);
|
|
2436
|
-
this.projectRefToId.set(resolvedProject.name, resolvedProject.id);
|
|
2437
|
-
this.projectCache = [
|
|
2438
|
-
...this.projectCache.filter((project) => project.id !== resolvedProject.id),
|
|
2439
|
-
resolvedProject
|
|
2440
|
-
];
|
|
2441
|
-
this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
|
|
2442
|
-
return resolvedProject;
|
|
2443
|
-
}
|
|
2444
|
-
if (isLikelyProjectId(resolvedRef)) {
|
|
2445
|
-
return {
|
|
2446
|
-
id: resolvedRef,
|
|
2447
|
-
orgId: "",
|
|
2448
|
-
name: resolvedRef,
|
|
2449
|
-
slug: resolvedRef,
|
|
2450
|
-
createdAt: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
2451
|
-
updatedAt: (/* @__PURE__ */ new Date(0)).toISOString()
|
|
2452
|
-
};
|
|
2453
|
-
}
|
|
2454
|
-
throw new RuntimeClientError({
|
|
2455
|
-
code: "PROJECT_NOT_FOUND",
|
|
2456
|
-
message: `Project '${resolvedRef}' not found`,
|
|
2457
|
-
retryable: false
|
|
2798
|
+
/**
|
|
2799
|
+
* Run a full agent turn with automatic memory read (before) + write (after).
|
|
2800
|
+
*/
|
|
2801
|
+
async runTurn(params) {
|
|
2802
|
+
const contextResult = await this.getContext(params.userMessage, {
|
|
2803
|
+
userId: params.userId,
|
|
2804
|
+
sessionId: params.sessionId,
|
|
2805
|
+
project: params.project,
|
|
2806
|
+
limit: params.limit
|
|
2458
2807
|
});
|
|
2808
|
+
const prompt = contextResult.context ? `${contextResult.context}
|
|
2809
|
+
|
|
2810
|
+
User: ${params.userMessage}` : params.userMessage;
|
|
2811
|
+
const response = await params.generate(prompt);
|
|
2812
|
+
const captureResult = await this.captureSession(
|
|
2813
|
+
[
|
|
2814
|
+
{ role: "user", content: params.userMessage },
|
|
2815
|
+
{ role: "assistant", content: response }
|
|
2816
|
+
],
|
|
2817
|
+
{
|
|
2818
|
+
userId: params.userId,
|
|
2819
|
+
sessionId: params.sessionId,
|
|
2820
|
+
project: params.project
|
|
2821
|
+
}
|
|
2822
|
+
);
|
|
2823
|
+
return {
|
|
2824
|
+
response,
|
|
2825
|
+
context: contextResult.context,
|
|
2826
|
+
count: contextResult.count,
|
|
2827
|
+
extracted: captureResult.extracted
|
|
2828
|
+
};
|
|
2459
2829
|
}
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
operation: "search",
|
|
2466
|
-
body: {
|
|
2467
|
-
...params,
|
|
2468
|
-
project
|
|
2469
|
-
},
|
|
2470
|
-
idempotent: true
|
|
2471
|
-
});
|
|
2472
|
-
return response.data;
|
|
2830
|
+
/**
|
|
2831
|
+
* Direct access to WhisperContext for advanced usage
|
|
2832
|
+
*/
|
|
2833
|
+
raw() {
|
|
2834
|
+
return this.client;
|
|
2473
2835
|
}
|
|
2474
|
-
|
|
2475
|
-
const
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
operation: "session",
|
|
2480
|
-
body: {
|
|
2481
|
-
...params,
|
|
2482
|
-
project
|
|
2836
|
+
extractMemoryIdsFromBulkResponse(bulkResponse) {
|
|
2837
|
+
const ids = [];
|
|
2838
|
+
if (Array.isArray(bulkResponse?.memories)) {
|
|
2839
|
+
for (const memory of bulkResponse.memories) {
|
|
2840
|
+
if (memory?.id) ids.push(memory.id);
|
|
2483
2841
|
}
|
|
2484
|
-
}
|
|
2485
|
-
|
|
2842
|
+
}
|
|
2843
|
+
if (bulkResponse?.memory?.id) {
|
|
2844
|
+
ids.push(bulkResponse.memory.id);
|
|
2845
|
+
}
|
|
2846
|
+
if (bulkResponse?.id) {
|
|
2847
|
+
ids.push(bulkResponse.id);
|
|
2848
|
+
}
|
|
2849
|
+
return Array.from(new Set(ids));
|
|
2486
2850
|
}
|
|
2487
|
-
|
|
2488
|
-
const
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
searchMemories: (params) => this.memory.search(params),
|
|
2506
|
-
addMemory: (params) => this.memory.add(params),
|
|
2507
|
-
queueStatus: () => this.queue.status(),
|
|
2508
|
-
flushQueue: () => this.queue.flush()
|
|
2851
|
+
async fallbackCaptureViaAddMemory(messages, options) {
|
|
2852
|
+
const userMessages = messages.filter((m) => m.role === "user").map((m) => (m.content || "").trim()).filter((content) => content.length >= 5).slice(-2);
|
|
2853
|
+
if (userMessages.length === 0) {
|
|
2854
|
+
return { success: false, extracted: 0 };
|
|
2855
|
+
}
|
|
2856
|
+
let extracted = 0;
|
|
2857
|
+
for (const content of userMessages) {
|
|
2858
|
+
try {
|
|
2859
|
+
await this.client.addMemory({
|
|
2860
|
+
project: options?.project ?? this.options.project,
|
|
2861
|
+
content,
|
|
2862
|
+
memory_type: "factual",
|
|
2863
|
+
user_id: options?.userId ?? this.userId,
|
|
2864
|
+
session_id: options?.sessionId ?? this.sessionId,
|
|
2865
|
+
allow_legacy_fallback: true
|
|
2866
|
+
});
|
|
2867
|
+
extracted += 1;
|
|
2868
|
+
} catch {
|
|
2509
2869
|
}
|
|
2510
|
-
}
|
|
2511
|
-
|
|
2512
|
-
withRunContext(context) {
|
|
2513
|
-
const base = this;
|
|
2514
|
-
return {
|
|
2515
|
-
memory: {
|
|
2516
|
-
add: (params) => base.memory.add({
|
|
2517
|
-
...params,
|
|
2518
|
-
project: params.project || context.project || base.config.project,
|
|
2519
|
-
user_id: params.user_id || context.userId,
|
|
2520
|
-
session_id: params.session_id || context.sessionId
|
|
2521
|
-
}),
|
|
2522
|
-
search: (params) => base.memory.search({
|
|
2523
|
-
...params,
|
|
2524
|
-
project: params.project || context.project || base.config.project,
|
|
2525
|
-
user_id: params.user_id || context.userId,
|
|
2526
|
-
session_id: params.session_id || context.sessionId
|
|
2527
|
-
})
|
|
2528
|
-
},
|
|
2529
|
-
session: {
|
|
2530
|
-
event: (params) => base.session.event({
|
|
2531
|
-
...params,
|
|
2532
|
-
sessionId: params.sessionId || context.sessionId || ""
|
|
2533
|
-
})
|
|
2534
|
-
},
|
|
2535
|
-
queue: base.queue,
|
|
2536
|
-
diagnostics: base.diagnostics
|
|
2537
|
-
};
|
|
2538
|
-
}
|
|
2539
|
-
async shutdown() {
|
|
2540
|
-
await this.writeQueue.stop();
|
|
2870
|
+
}
|
|
2871
|
+
return { success: extracted > 0, extracted };
|
|
2541
2872
|
}
|
|
2542
2873
|
};
|
|
2543
|
-
var
|
|
2874
|
+
var whisper_agent_default = Whisper;
|
|
2544
2875
|
|
|
2545
2876
|
// ../src/sdk/middleware.ts
|
|
2546
2877
|
var WhisperAgentMiddleware = class {
|