@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.mjs
CHANGED
|
@@ -379,368 +379,83 @@ var RuntimeClient = class {
|
|
|
379
379
|
}
|
|
380
380
|
};
|
|
381
381
|
|
|
382
|
-
// ../src/sdk/
|
|
383
|
-
var
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
382
|
+
// ../src/sdk/core/cache.ts
|
|
383
|
+
var SearchResponseCache = class {
|
|
384
|
+
ttlMs;
|
|
385
|
+
capacity;
|
|
386
|
+
byKey = /* @__PURE__ */ new Map();
|
|
387
|
+
scopeIndex = /* @__PURE__ */ new Map();
|
|
388
|
+
constructor(ttlMs = 7e3, capacity = 500) {
|
|
389
|
+
this.ttlMs = Math.max(1e3, ttlMs);
|
|
390
|
+
this.capacity = Math.max(10, capacity);
|
|
389
391
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
client;
|
|
393
|
-
options;
|
|
394
|
-
sessionId;
|
|
395
|
-
userId;
|
|
396
|
-
constructor(options) {
|
|
397
|
-
if (!options.apiKey) {
|
|
398
|
-
throw new Error("API key is required");
|
|
399
|
-
}
|
|
400
|
-
const clientConfig = {
|
|
401
|
-
apiKey: options.apiKey,
|
|
402
|
-
baseUrl: options.baseUrl,
|
|
403
|
-
project: options.project || "default"
|
|
404
|
-
};
|
|
405
|
-
if (options.timeoutMs) clientConfig.timeoutMs = options.timeoutMs;
|
|
406
|
-
if (options.retry) clientConfig.retry = options.retry;
|
|
407
|
-
this.client = new WhisperContext(clientConfig);
|
|
408
|
-
warnDeprecatedOnce(
|
|
409
|
-
"whisper_agent_wrapper",
|
|
410
|
-
"[Whisper SDK] Whisper wrapper is supported for v2 compatibility. Prefer WhisperClient for new integrations."
|
|
411
|
-
);
|
|
412
|
-
const finalRetry = options.retry || { maxAttempts: 3, baseDelayMs: 250, maxDelayMs: 2e3 };
|
|
413
|
-
this.options = {
|
|
414
|
-
apiKey: options.apiKey,
|
|
415
|
-
baseUrl: options.baseUrl || "https://context.usewhisper.dev",
|
|
416
|
-
project: options.project || "default",
|
|
417
|
-
timeoutMs: options.timeoutMs || 15e3,
|
|
418
|
-
retry: finalRetry,
|
|
419
|
-
contextLimit: options.contextLimit ?? 10,
|
|
420
|
-
memoryTypes: options.memoryTypes ?? ["factual", "preference", "event", "goal", "relationship", "opinion", "instruction"],
|
|
421
|
-
contextPrefix: options.contextPrefix ?? "Relevant context:",
|
|
422
|
-
autoExtract: options.autoExtract ?? true,
|
|
423
|
-
autoExtractMinConfidence: options.autoExtractMinConfidence ?? 0.65,
|
|
424
|
-
maxMemoriesPerCapture: options.maxMemoriesPerCapture ?? 5
|
|
425
|
-
};
|
|
392
|
+
makeScopeKey(project, userId, sessionId) {
|
|
393
|
+
return `${project}:${userId || "_"}:${sessionId || "_"}`;
|
|
426
394
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
395
|
+
makeKey(input) {
|
|
396
|
+
const normalized = {
|
|
397
|
+
project: input.project,
|
|
398
|
+
userId: input.userId || "",
|
|
399
|
+
sessionId: input.sessionId || "",
|
|
400
|
+
query: normalizeQuery(input.query),
|
|
401
|
+
topK: input.topK,
|
|
402
|
+
profile: input.profile,
|
|
403
|
+
includePending: input.includePending
|
|
404
|
+
};
|
|
405
|
+
return `search:${stableHash(JSON.stringify(normalized))}`;
|
|
433
406
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
407
|
+
get(key) {
|
|
408
|
+
const found = this.byKey.get(key);
|
|
409
|
+
if (!found) return null;
|
|
410
|
+
if (found.expiresAt <= Date.now()) {
|
|
411
|
+
this.deleteByKey(key);
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
found.touchedAt = Date.now();
|
|
415
|
+
return found.value;
|
|
440
416
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
* @example
|
|
448
|
-
* ```typescript
|
|
449
|
-
* const { context, results, count } = await whisper.getContext(
|
|
450
|
-
* "What are user's preferences?",
|
|
451
|
-
* { userId: "user-123" }
|
|
452
|
-
* );
|
|
453
|
-
*
|
|
454
|
-
* // Results: [
|
|
455
|
-
* // { content: "User prefers dark mode", type: "preference", score: 0.95 },
|
|
456
|
-
* // { content: "Allergic to nuts", type: "factual", score: 0.89 }
|
|
457
|
-
* // ]
|
|
458
|
-
* ```
|
|
459
|
-
*/
|
|
460
|
-
async getContext(query, options) {
|
|
461
|
-
const result = await this.client.query({
|
|
462
|
-
project: options?.project ?? this.options.project,
|
|
463
|
-
query,
|
|
464
|
-
top_k: options?.limit ?? this.options.contextLimit,
|
|
465
|
-
include_memories: true,
|
|
466
|
-
user_id: options?.userId ?? this.userId,
|
|
467
|
-
session_id: options?.sessionId ?? this.sessionId
|
|
417
|
+
set(key, scopeKey, value) {
|
|
418
|
+
this.byKey.set(key, {
|
|
419
|
+
value,
|
|
420
|
+
scopeKey,
|
|
421
|
+
touchedAt: Date.now(),
|
|
422
|
+
expiresAt: Date.now() + this.ttlMs
|
|
468
423
|
});
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
count: result.meta.total
|
|
475
|
-
};
|
|
424
|
+
if (!this.scopeIndex.has(scopeKey)) {
|
|
425
|
+
this.scopeIndex.set(scopeKey, /* @__PURE__ */ new Set());
|
|
426
|
+
}
|
|
427
|
+
this.scopeIndex.get(scopeKey).add(key);
|
|
428
|
+
this.evictIfNeeded();
|
|
476
429
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
*
|
|
482
|
-
* @param content - What your LLM responded with
|
|
483
|
-
* @returns Promise that resolves when stored (or fails silently)
|
|
484
|
-
*
|
|
485
|
-
* @example
|
|
486
|
-
* ```typescript
|
|
487
|
-
* const llmResponse = "I've set your theme to dark mode and removed nuts from recommendations.";
|
|
488
|
-
*
|
|
489
|
-
* await whisper.remember(llmResponse, { userId: "user-123" });
|
|
490
|
-
* // → Auto-extracts: "theme set to dark mode", "nut allergy"
|
|
491
|
-
* // → Stored as preferences
|
|
492
|
-
* ```
|
|
493
|
-
*/
|
|
494
|
-
async remember(content, options) {
|
|
495
|
-
if (!content || content.length < 5) {
|
|
496
|
-
return { success: false };
|
|
430
|
+
invalidateScope(scopeKey) {
|
|
431
|
+
const keys = this.scopeIndex.get(scopeKey);
|
|
432
|
+
if (!keys || keys.size === 0) {
|
|
433
|
+
return 0;
|
|
497
434
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
project: options?.project ?? this.options.project,
|
|
502
|
-
message: content,
|
|
503
|
-
user_id: options?.userId ?? this.userId,
|
|
504
|
-
session_id: options?.sessionId ?? this.sessionId,
|
|
505
|
-
enable_pattern: true,
|
|
506
|
-
enable_inference: true,
|
|
507
|
-
min_confidence: this.options.autoExtractMinConfidence
|
|
508
|
-
});
|
|
509
|
-
const extractedMemories = (extraction.all || []).filter((m) => (m.confidence || 0) >= this.options.autoExtractMinConfidence).slice(0, this.options.maxMemoriesPerCapture);
|
|
510
|
-
if (extractedMemories.length > 0) {
|
|
511
|
-
const bulk = await this.client.addMemoriesBulk({
|
|
512
|
-
project: options?.project ?? this.options.project,
|
|
513
|
-
write_mode: "async",
|
|
514
|
-
memories: extractedMemories.map((m) => ({
|
|
515
|
-
content: m.content,
|
|
516
|
-
memory_type: m.memoryType,
|
|
517
|
-
user_id: options?.userId ?? this.userId,
|
|
518
|
-
session_id: options?.sessionId ?? this.sessionId,
|
|
519
|
-
importance: Math.max(0.5, Math.min(1, m.confidence || 0.7)),
|
|
520
|
-
confidence: m.confidence || 0.7,
|
|
521
|
-
entity_mentions: m.entityMentions || [],
|
|
522
|
-
event_date: m.eventDate || void 0,
|
|
523
|
-
metadata: {
|
|
524
|
-
extracted: true,
|
|
525
|
-
extraction_method: extraction.extractionMethod,
|
|
526
|
-
extraction_reasoning: m.reasoning,
|
|
527
|
-
inferred: Boolean(m.inferred)
|
|
528
|
-
}
|
|
529
|
-
}))
|
|
530
|
-
});
|
|
531
|
-
const memoryIds = this.extractMemoryIdsFromBulkResponse(bulk);
|
|
532
|
-
return {
|
|
533
|
-
success: true,
|
|
534
|
-
memoryId: memoryIds[0],
|
|
535
|
-
memoryIds: memoryIds.length > 0 ? memoryIds : void 0,
|
|
536
|
-
extracted: extractedMemories.length
|
|
537
|
-
};
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
const result = await this.client.addMemory({
|
|
541
|
-
project: options?.project ?? this.options.project,
|
|
542
|
-
content,
|
|
543
|
-
user_id: options?.userId ?? this.userId,
|
|
544
|
-
session_id: options?.sessionId ?? this.sessionId
|
|
545
|
-
});
|
|
546
|
-
return {
|
|
547
|
-
success: true,
|
|
548
|
-
memoryId: result?.id
|
|
549
|
-
};
|
|
550
|
-
} catch (error) {
|
|
551
|
-
console.error("[Whisper] Remember failed:", error);
|
|
552
|
-
return { success: false };
|
|
435
|
+
const toDelete = Array.from(keys);
|
|
436
|
+
for (const key of toDelete) {
|
|
437
|
+
this.deleteByKey(key);
|
|
553
438
|
}
|
|
439
|
+
this.scopeIndex.delete(scopeKey);
|
|
440
|
+
return toDelete.length;
|
|
554
441
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
442
|
+
evictIfNeeded() {
|
|
443
|
+
if (this.byKey.size <= this.capacity) return;
|
|
444
|
+
const ordered = Array.from(this.byKey.entries()).sort((a, b) => a[1].touchedAt - b[1].touchedAt);
|
|
445
|
+
const removeCount = this.byKey.size - this.capacity;
|
|
446
|
+
for (let i = 0; i < removeCount; i += 1) {
|
|
447
|
+
this.deleteByKey(ordered[i][0]);
|
|
448
|
+
}
|
|
560
449
|
}
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
messages: messages.filter((m) => m.role !== "system").map((m) => ({
|
|
571
|
-
role: m.role,
|
|
572
|
-
content: m.content,
|
|
573
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
574
|
-
}))
|
|
575
|
-
});
|
|
576
|
-
return {
|
|
577
|
-
success: true,
|
|
578
|
-
extracted: result?.memories_created ?? 0
|
|
579
|
-
};
|
|
580
|
-
} catch (error) {
|
|
581
|
-
const fallback = await this.fallbackCaptureViaAddMemory(messages, options);
|
|
582
|
-
if (fallback.success) {
|
|
583
|
-
return fallback;
|
|
584
|
-
}
|
|
585
|
-
console.error("[Whisper] Session capture failed:", error);
|
|
586
|
-
return { success: false, extracted: 0 };
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
/**
|
|
590
|
-
* Run a full agent turn with automatic memory read (before) + write (after).
|
|
591
|
-
*/
|
|
592
|
-
async runTurn(params) {
|
|
593
|
-
const contextResult = await this.getContext(params.userMessage, {
|
|
594
|
-
userId: params.userId,
|
|
595
|
-
sessionId: params.sessionId,
|
|
596
|
-
project: params.project,
|
|
597
|
-
limit: params.limit
|
|
598
|
-
});
|
|
599
|
-
const prompt = contextResult.context ? `${contextResult.context}
|
|
600
|
-
|
|
601
|
-
User: ${params.userMessage}` : params.userMessage;
|
|
602
|
-
const response = await params.generate(prompt);
|
|
603
|
-
const captureResult = await this.captureSession(
|
|
604
|
-
[
|
|
605
|
-
{ role: "user", content: params.userMessage },
|
|
606
|
-
{ role: "assistant", content: response }
|
|
607
|
-
],
|
|
608
|
-
{
|
|
609
|
-
userId: params.userId,
|
|
610
|
-
sessionId: params.sessionId,
|
|
611
|
-
project: params.project
|
|
612
|
-
}
|
|
613
|
-
);
|
|
614
|
-
return {
|
|
615
|
-
response,
|
|
616
|
-
context: contextResult.context,
|
|
617
|
-
count: contextResult.count,
|
|
618
|
-
extracted: captureResult.extracted
|
|
619
|
-
};
|
|
620
|
-
}
|
|
621
|
-
/**
|
|
622
|
-
* Direct access to WhisperContext for advanced usage
|
|
623
|
-
*/
|
|
624
|
-
raw() {
|
|
625
|
-
return this.client;
|
|
626
|
-
}
|
|
627
|
-
extractMemoryIdsFromBulkResponse(bulkResponse) {
|
|
628
|
-
const ids = [];
|
|
629
|
-
if (Array.isArray(bulkResponse?.memories)) {
|
|
630
|
-
for (const memory of bulkResponse.memories) {
|
|
631
|
-
if (memory?.id) ids.push(memory.id);
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
if (bulkResponse?.memory?.id) {
|
|
635
|
-
ids.push(bulkResponse.memory.id);
|
|
636
|
-
}
|
|
637
|
-
if (bulkResponse?.id) {
|
|
638
|
-
ids.push(bulkResponse.id);
|
|
639
|
-
}
|
|
640
|
-
return Array.from(new Set(ids));
|
|
641
|
-
}
|
|
642
|
-
async fallbackCaptureViaAddMemory(messages, options) {
|
|
643
|
-
const userMessages = messages.filter((m) => m.role === "user").map((m) => (m.content || "").trim()).filter((content) => content.length >= 5).slice(-2);
|
|
644
|
-
if (userMessages.length === 0) {
|
|
645
|
-
return { success: false, extracted: 0 };
|
|
646
|
-
}
|
|
647
|
-
let extracted = 0;
|
|
648
|
-
for (const content of userMessages) {
|
|
649
|
-
try {
|
|
650
|
-
await this.client.addMemory({
|
|
651
|
-
project: options?.project ?? this.options.project,
|
|
652
|
-
content,
|
|
653
|
-
memory_type: "factual",
|
|
654
|
-
user_id: options?.userId ?? this.userId,
|
|
655
|
-
session_id: options?.sessionId ?? this.sessionId,
|
|
656
|
-
allow_legacy_fallback: true
|
|
657
|
-
});
|
|
658
|
-
extracted += 1;
|
|
659
|
-
} catch {
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
return { success: extracted > 0, extracted };
|
|
663
|
-
}
|
|
664
|
-
};
|
|
665
|
-
var whisper_agent_default = Whisper;
|
|
666
|
-
|
|
667
|
-
// ../src/sdk/core/cache.ts
|
|
668
|
-
var SearchResponseCache = class {
|
|
669
|
-
ttlMs;
|
|
670
|
-
capacity;
|
|
671
|
-
byKey = /* @__PURE__ */ new Map();
|
|
672
|
-
scopeIndex = /* @__PURE__ */ new Map();
|
|
673
|
-
constructor(ttlMs = 7e3, capacity = 500) {
|
|
674
|
-
this.ttlMs = Math.max(1e3, ttlMs);
|
|
675
|
-
this.capacity = Math.max(10, capacity);
|
|
676
|
-
}
|
|
677
|
-
makeScopeKey(project, userId, sessionId) {
|
|
678
|
-
return `${project}:${userId || "_"}:${sessionId || "_"}`;
|
|
679
|
-
}
|
|
680
|
-
makeKey(input) {
|
|
681
|
-
const normalized = {
|
|
682
|
-
project: input.project,
|
|
683
|
-
userId: input.userId || "",
|
|
684
|
-
sessionId: input.sessionId || "",
|
|
685
|
-
query: normalizeQuery(input.query),
|
|
686
|
-
topK: input.topK,
|
|
687
|
-
profile: input.profile,
|
|
688
|
-
includePending: input.includePending
|
|
689
|
-
};
|
|
690
|
-
return `search:${stableHash(JSON.stringify(normalized))}`;
|
|
691
|
-
}
|
|
692
|
-
get(key) {
|
|
693
|
-
const found = this.byKey.get(key);
|
|
694
|
-
if (!found) return null;
|
|
695
|
-
if (found.expiresAt <= Date.now()) {
|
|
696
|
-
this.deleteByKey(key);
|
|
697
|
-
return null;
|
|
698
|
-
}
|
|
699
|
-
found.touchedAt = Date.now();
|
|
700
|
-
return found.value;
|
|
701
|
-
}
|
|
702
|
-
set(key, scopeKey, value) {
|
|
703
|
-
this.byKey.set(key, {
|
|
704
|
-
value,
|
|
705
|
-
scopeKey,
|
|
706
|
-
touchedAt: Date.now(),
|
|
707
|
-
expiresAt: Date.now() + this.ttlMs
|
|
708
|
-
});
|
|
709
|
-
if (!this.scopeIndex.has(scopeKey)) {
|
|
710
|
-
this.scopeIndex.set(scopeKey, /* @__PURE__ */ new Set());
|
|
711
|
-
}
|
|
712
|
-
this.scopeIndex.get(scopeKey).add(key);
|
|
713
|
-
this.evictIfNeeded();
|
|
714
|
-
}
|
|
715
|
-
invalidateScope(scopeKey) {
|
|
716
|
-
const keys = this.scopeIndex.get(scopeKey);
|
|
717
|
-
if (!keys || keys.size === 0) {
|
|
718
|
-
return 0;
|
|
719
|
-
}
|
|
720
|
-
const toDelete = Array.from(keys);
|
|
721
|
-
for (const key of toDelete) {
|
|
722
|
-
this.deleteByKey(key);
|
|
723
|
-
}
|
|
724
|
-
this.scopeIndex.delete(scopeKey);
|
|
725
|
-
return toDelete.length;
|
|
726
|
-
}
|
|
727
|
-
evictIfNeeded() {
|
|
728
|
-
if (this.byKey.size <= this.capacity) return;
|
|
729
|
-
const ordered = Array.from(this.byKey.entries()).sort((a, b) => a[1].touchedAt - b[1].touchedAt);
|
|
730
|
-
const removeCount = this.byKey.size - this.capacity;
|
|
731
|
-
for (let i = 0; i < removeCount; i += 1) {
|
|
732
|
-
this.deleteByKey(ordered[i][0]);
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
deleteByKey(key) {
|
|
736
|
-
const found = this.byKey.get(key);
|
|
737
|
-
if (!found) return;
|
|
738
|
-
this.byKey.delete(key);
|
|
739
|
-
const scopeKeys = this.scopeIndex.get(found.scopeKey);
|
|
740
|
-
if (!scopeKeys) return;
|
|
741
|
-
scopeKeys.delete(key);
|
|
742
|
-
if (scopeKeys.size === 0) {
|
|
743
|
-
this.scopeIndex.delete(found.scopeKey);
|
|
450
|
+
deleteByKey(key) {
|
|
451
|
+
const found = this.byKey.get(key);
|
|
452
|
+
if (!found) return;
|
|
453
|
+
this.byKey.delete(key);
|
|
454
|
+
const scopeKeys = this.scopeIndex.get(found.scopeKey);
|
|
455
|
+
if (!scopeKeys) return;
|
|
456
|
+
scopeKeys.delete(key);
|
|
457
|
+
if (scopeKeys.size === 0) {
|
|
458
|
+
this.scopeIndex.delete(found.scopeKey);
|
|
744
459
|
}
|
|
745
460
|
}
|
|
746
461
|
};
|
|
@@ -1638,6 +1353,29 @@ ${lines.join("\n")}`;
|
|
|
1638
1353
|
function compactWhitespace(value) {
|
|
1639
1354
|
return value.replace(/\s+/g, " ").trim();
|
|
1640
1355
|
}
|
|
1356
|
+
function normalizeSummary(value) {
|
|
1357
|
+
return compactWhitespace(String(value || "").toLowerCase());
|
|
1358
|
+
}
|
|
1359
|
+
function tokenize(value) {
|
|
1360
|
+
return normalizeSummary(value).split(/[^a-z0-9_./-]+/i).map((token) => token.trim()).filter(Boolean);
|
|
1361
|
+
}
|
|
1362
|
+
function jaccardOverlap(left, right) {
|
|
1363
|
+
const leftTokens = new Set(tokenize(left));
|
|
1364
|
+
const rightTokens = new Set(tokenize(right));
|
|
1365
|
+
if (leftTokens.size === 0 || rightTokens.size === 0) return 0;
|
|
1366
|
+
let intersection = 0;
|
|
1367
|
+
for (const token of leftTokens) {
|
|
1368
|
+
if (rightTokens.has(token)) intersection += 1;
|
|
1369
|
+
}
|
|
1370
|
+
const union = (/* @__PURE__ */ new Set([...leftTokens, ...rightTokens])).size;
|
|
1371
|
+
return union > 0 ? intersection / union : 0;
|
|
1372
|
+
}
|
|
1373
|
+
function clamp01(value) {
|
|
1374
|
+
if (!Number.isFinite(value)) return 0;
|
|
1375
|
+
if (value < 0) return 0;
|
|
1376
|
+
if (value > 1) return 1;
|
|
1377
|
+
return value;
|
|
1378
|
+
}
|
|
1641
1379
|
function withTimeout(promise, timeoutMs) {
|
|
1642
1380
|
return new Promise((resolve, reject) => {
|
|
1643
1381
|
const timeout = setTimeout(() => {
|
|
@@ -1671,39 +1409,76 @@ function extractTimestamp(metadata) {
|
|
|
1671
1409
|
}
|
|
1672
1410
|
return 0;
|
|
1673
1411
|
}
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1412
|
+
var DEFAULT_RANK_WEIGHTS = {
|
|
1413
|
+
focusedPassBonus: 0.2,
|
|
1414
|
+
sourceMatchBonus: 0.18,
|
|
1415
|
+
touchedFileBonus: 0.12,
|
|
1416
|
+
clientMatchBonus: 0.1,
|
|
1417
|
+
highSalienceBonus: 0.12,
|
|
1418
|
+
mediumSalienceBonus: 0.06,
|
|
1419
|
+
staleBroadPenalty: -0.1,
|
|
1420
|
+
unrelatedClientPenalty: -0.18,
|
|
1421
|
+
lowSaliencePenalty: -0.12
|
|
1422
|
+
};
|
|
1423
|
+
var DEFAULT_SOURCE_ACTIVITY = {
|
|
1424
|
+
maxTurns: 10,
|
|
1425
|
+
maxIdleMs: 30 * 60 * 1e3,
|
|
1426
|
+
decayAfterTurns: 5,
|
|
1427
|
+
decayAfterIdleMs: 15 * 60 * 1e3,
|
|
1428
|
+
evictOnTaskSwitch: true
|
|
1429
|
+
};
|
|
1680
1430
|
var WhisperAgentRuntime = class {
|
|
1681
1431
|
constructor(args) {
|
|
1682
1432
|
this.args = args;
|
|
1683
1433
|
this.bindingStore = createBindingStore(args.options.bindingStorePath);
|
|
1684
|
-
|
|
1434
|
+
const retrieval = args.options.retrieval || {};
|
|
1435
|
+
this.focusedTopK = retrieval.focusedTopK ?? args.options.topK ?? 6;
|
|
1436
|
+
this.broadTopK = retrieval.broadTopK ?? Math.max(args.options.topK ?? 6, 10);
|
|
1685
1437
|
this.maxTokens = args.options.maxTokens ?? 4e3;
|
|
1686
1438
|
this.targetRetrievalMs = args.options.targetRetrievalMs ?? 2500;
|
|
1687
1439
|
this.hardRetrievalTimeoutMs = args.options.hardRetrievalTimeoutMs ?? 4e3;
|
|
1688
1440
|
this.recentWorkLimit = args.options.recentWorkLimit ?? 40;
|
|
1689
1441
|
this.baseContext = args.baseContext;
|
|
1690
1442
|
this.clientName = args.baseContext.clientName || "whisper-agent-runtime";
|
|
1443
|
+
this.minFocusedResults = retrieval.minFocusedResults ?? 3;
|
|
1444
|
+
this.minFocusedTopScore = retrieval.minFocusedTopScore ?? 0.55;
|
|
1445
|
+
this.minProjectScore = retrieval.minProjectScore ?? 0.5;
|
|
1446
|
+
this.minMemoryScore = retrieval.minMemoryScore ?? 0.6;
|
|
1447
|
+
this.rankWeights = { ...DEFAULT_RANK_WEIGHTS, ...retrieval.rankWeights || {} };
|
|
1448
|
+
this.sourceActivityOptions = { ...DEFAULT_SOURCE_ACTIVITY, ...retrieval.sourceActivity || {} };
|
|
1691
1449
|
}
|
|
1692
1450
|
bindingStore;
|
|
1693
|
-
|
|
1451
|
+
focusedTopK;
|
|
1452
|
+
broadTopK;
|
|
1694
1453
|
maxTokens;
|
|
1695
1454
|
targetRetrievalMs;
|
|
1696
1455
|
hardRetrievalTimeoutMs;
|
|
1697
1456
|
recentWorkLimit;
|
|
1698
1457
|
baseContext;
|
|
1699
1458
|
clientName;
|
|
1459
|
+
minFocusedResults;
|
|
1460
|
+
minFocusedTopScore;
|
|
1461
|
+
minProjectScore;
|
|
1462
|
+
minMemoryScore;
|
|
1463
|
+
rankWeights;
|
|
1464
|
+
sourceActivityOptions;
|
|
1700
1465
|
bindings = null;
|
|
1701
1466
|
touchedFiles = [];
|
|
1702
1467
|
recentWork = [];
|
|
1468
|
+
recentSourceActivity = [];
|
|
1703
1469
|
bufferedLowSalience = [];
|
|
1704
1470
|
lastPreparedTurn = null;
|
|
1705
1471
|
mergedCount = 0;
|
|
1706
1472
|
droppedCount = 0;
|
|
1473
|
+
focusedPassHits = 0;
|
|
1474
|
+
fallbackTriggers = 0;
|
|
1475
|
+
floorDroppedCount = 0;
|
|
1476
|
+
injectedItemCount = 0;
|
|
1477
|
+
sourceScopedTurns = 0;
|
|
1478
|
+
broadScopedTurns = 0;
|
|
1479
|
+
totalTurns = 0;
|
|
1480
|
+
currentTurn = 0;
|
|
1481
|
+
lastTaskSummary = "";
|
|
1707
1482
|
lastScope = {};
|
|
1708
1483
|
async getBindings() {
|
|
1709
1484
|
if (!this.bindings) {
|
|
@@ -1721,6 +1496,64 @@ var WhisperAgentRuntime = class {
|
|
|
1721
1496
|
pushWorkEvent(event) {
|
|
1722
1497
|
this.recentWork = [...this.recentWork, event].slice(-this.recentWorkLimit);
|
|
1723
1498
|
}
|
|
1499
|
+
noteSourceActivity(sourceIds) {
|
|
1500
|
+
const now = Date.now();
|
|
1501
|
+
for (const sourceId of [...new Set((sourceIds || []).map((value) => String(value || "").trim()).filter(Boolean))]) {
|
|
1502
|
+
this.recentSourceActivity = [
|
|
1503
|
+
...this.recentSourceActivity.filter((entry) => entry.sourceId !== sourceId),
|
|
1504
|
+
{ sourceId, turn: this.currentTurn, at: now }
|
|
1505
|
+
].slice(-24);
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
refreshTaskSummary(taskSummary) {
|
|
1509
|
+
const next = normalizeSummary(taskSummary);
|
|
1510
|
+
if (!next) return;
|
|
1511
|
+
if (this.sourceActivityOptions.evictOnTaskSwitch && this.lastTaskSummary && this.lastTaskSummary !== next && jaccardOverlap(this.lastTaskSummary, next) < 0.6) {
|
|
1512
|
+
this.recentSourceActivity = [];
|
|
1513
|
+
}
|
|
1514
|
+
this.lastTaskSummary = next;
|
|
1515
|
+
}
|
|
1516
|
+
activeSourceIds() {
|
|
1517
|
+
const now = Date.now();
|
|
1518
|
+
const active = /* @__PURE__ */ new Map();
|
|
1519
|
+
const maxTurns = this.sourceActivityOptions.maxTurns;
|
|
1520
|
+
const maxIdleMs = this.sourceActivityOptions.maxIdleMs;
|
|
1521
|
+
const decayAfterTurns = this.sourceActivityOptions.decayAfterTurns;
|
|
1522
|
+
const decayAfterIdleMs = this.sourceActivityOptions.decayAfterIdleMs;
|
|
1523
|
+
const fresh = [];
|
|
1524
|
+
for (const entry of this.recentSourceActivity) {
|
|
1525
|
+
const turnDelta = this.currentTurn - entry.turn;
|
|
1526
|
+
const idleDelta = now - entry.at;
|
|
1527
|
+
if (turnDelta > maxTurns || idleDelta > maxIdleMs) continue;
|
|
1528
|
+
fresh.push(entry);
|
|
1529
|
+
let weight = 1;
|
|
1530
|
+
if (turnDelta > decayAfterTurns || idleDelta > decayAfterIdleMs) {
|
|
1531
|
+
weight = 0.5;
|
|
1532
|
+
}
|
|
1533
|
+
const current = active.get(entry.sourceId) || 0;
|
|
1534
|
+
active.set(entry.sourceId, Math.max(current, weight));
|
|
1535
|
+
}
|
|
1536
|
+
this.recentSourceActivity = fresh.slice(-24);
|
|
1537
|
+
return [...active.entries()].sort((left, right) => right[1] - left[1]).map(([sourceId]) => sourceId).slice(0, 4);
|
|
1538
|
+
}
|
|
1539
|
+
focusedScope(input) {
|
|
1540
|
+
const sourceIds = this.activeSourceIds();
|
|
1541
|
+
const fileHints = [...new Set([
|
|
1542
|
+
...input.touchedFiles || [],
|
|
1543
|
+
...this.touchedFiles,
|
|
1544
|
+
...this.recentWork.flatMap((event) => event.filePaths || [])
|
|
1545
|
+
].map((value) => String(value || "").trim()).filter(Boolean))].slice(-4);
|
|
1546
|
+
return {
|
|
1547
|
+
sourceIds,
|
|
1548
|
+
fileHints,
|
|
1549
|
+
clientName: this.clientName || void 0
|
|
1550
|
+
};
|
|
1551
|
+
}
|
|
1552
|
+
exactFileMetadataFilter(fileHints) {
|
|
1553
|
+
const exact = fileHints.find((value) => /[\\/]/.test(value));
|
|
1554
|
+
if (!exact) return void 0;
|
|
1555
|
+
return { filePath: exact };
|
|
1556
|
+
}
|
|
1724
1557
|
makeTaskFrameQuery(input) {
|
|
1725
1558
|
const task = compactWhitespace(input.taskSummary || "");
|
|
1726
1559
|
const salient = this.recentWork.filter((event) => event.salience === "high").slice(-3).map((event) => `${event.kind}: ${event.summary}`);
|
|
@@ -1797,23 +1630,29 @@ var WhisperAgentRuntime = class {
|
|
|
1797
1630
|
};
|
|
1798
1631
|
}
|
|
1799
1632
|
}
|
|
1800
|
-
contextItems(result, sourceQuery) {
|
|
1633
|
+
contextItems(result, sourceQuery, pass) {
|
|
1634
|
+
const sourceScope = result.meta?.source_scope;
|
|
1635
|
+
if (sourceScope?.mode === "auto" || sourceScope?.mode === "explicit") {
|
|
1636
|
+
this.noteSourceActivity(sourceScope.source_ids || []);
|
|
1637
|
+
}
|
|
1801
1638
|
return (result.results || []).map((item) => ({
|
|
1802
1639
|
id: item.id,
|
|
1803
1640
|
content: item.content,
|
|
1804
1641
|
type: "project",
|
|
1805
1642
|
score: item.score ?? 0,
|
|
1806
1643
|
sourceQuery,
|
|
1644
|
+
pass,
|
|
1807
1645
|
metadata: item.metadata || {}
|
|
1808
1646
|
}));
|
|
1809
1647
|
}
|
|
1810
|
-
memoryItems(result, sourceQuery) {
|
|
1648
|
+
memoryItems(result, sourceQuery, pass) {
|
|
1811
1649
|
return (result.results || []).map((item, index) => ({
|
|
1812
1650
|
id: item.memory?.id || item.chunk?.id || `${sourceQuery}_memory_${index}`,
|
|
1813
1651
|
content: item.chunk?.content || item.memory?.content || "",
|
|
1814
1652
|
type: "memory",
|
|
1815
1653
|
score: item.similarity ?? 0,
|
|
1816
1654
|
sourceQuery,
|
|
1655
|
+
pass,
|
|
1817
1656
|
metadata: {
|
|
1818
1657
|
...item.chunk?.metadata || {},
|
|
1819
1658
|
...item.memory?.temporal || {},
|
|
@@ -1821,22 +1660,99 @@ var WhisperAgentRuntime = class {
|
|
|
1821
1660
|
}
|
|
1822
1661
|
})).filter((item) => item.content);
|
|
1823
1662
|
}
|
|
1824
|
-
|
|
1663
|
+
stableItemKey(item) {
|
|
1664
|
+
const metadata = item.metadata || {};
|
|
1665
|
+
const sourceId = String(metadata.source_id || "");
|
|
1666
|
+
const documentId = String(metadata.document_id || metadata.documentId || "");
|
|
1667
|
+
const chunkId = String(metadata.chunk_id || metadata.chunkId || item.id || "");
|
|
1668
|
+
return stableHash(`${sourceId}|${documentId}|${chunkId}|${item.content.slice(0, 256)}`);
|
|
1669
|
+
}
|
|
1670
|
+
metadataStrings(item) {
|
|
1671
|
+
const metadata = item.metadata || {};
|
|
1672
|
+
return [
|
|
1673
|
+
metadata.filePath,
|
|
1674
|
+
metadata.file_path,
|
|
1675
|
+
metadata.path,
|
|
1676
|
+
metadata.section_path,
|
|
1677
|
+
metadata.parent_section_path,
|
|
1678
|
+
metadata.web_url,
|
|
1679
|
+
metadata.url
|
|
1680
|
+
].map((value) => String(value || "").toLowerCase()).filter(Boolean);
|
|
1681
|
+
}
|
|
1682
|
+
hasSourceMatch(item, scope) {
|
|
1683
|
+
const sourceId = String(item.metadata?.source_id || "");
|
|
1684
|
+
return Boolean(sourceId && scope.sourceIds.includes(sourceId));
|
|
1685
|
+
}
|
|
1686
|
+
hasFileMatch(item, scope) {
|
|
1687
|
+
if (scope.fileHints.length === 0) return false;
|
|
1688
|
+
const metadata = this.metadataStrings(item);
|
|
1689
|
+
const lowerHints = scope.fileHints.map((hint) => hint.toLowerCase());
|
|
1690
|
+
return lowerHints.some((hint) => {
|
|
1691
|
+
const base = pathBase(hint).toLowerCase();
|
|
1692
|
+
return metadata.some((value) => value.includes(hint) || value.endsWith(base));
|
|
1693
|
+
});
|
|
1694
|
+
}
|
|
1695
|
+
hasClientMatch(item, scope) {
|
|
1696
|
+
const itemClient = String(item.metadata?.client_name || "");
|
|
1697
|
+
return Boolean(scope.clientName && itemClient && itemClient === scope.clientName);
|
|
1698
|
+
}
|
|
1699
|
+
salienceAdjustment(item) {
|
|
1700
|
+
const salience = item.metadata?.salience;
|
|
1701
|
+
if (salience === "high") return this.rankWeights.highSalienceBonus;
|
|
1702
|
+
if (salience === "medium") return this.rankWeights.mediumSalienceBonus;
|
|
1703
|
+
if (salience === "low") return this.rankWeights.lowSaliencePenalty;
|
|
1704
|
+
return 0;
|
|
1705
|
+
}
|
|
1706
|
+
narrowFocusedMemories(items, scope) {
|
|
1707
|
+
const hasSignals = scope.sourceIds.length > 0 || scope.fileHints.length > 0 || Boolean(scope.clientName);
|
|
1708
|
+
if (!hasSignals) return items;
|
|
1709
|
+
const narrowed = items.filter((item) => {
|
|
1710
|
+
const matchesClient = this.hasClientMatch(item, scope);
|
|
1711
|
+
const matchesFile = this.hasFileMatch(item, scope);
|
|
1712
|
+
const matchesSource = this.hasSourceMatch(item, scope);
|
|
1713
|
+
const salience = item.metadata?.salience;
|
|
1714
|
+
if (scope.clientName && item.metadata?.client_name && !matchesClient) {
|
|
1715
|
+
return false;
|
|
1716
|
+
}
|
|
1717
|
+
if (salience === "low" && !matchesFile && !matchesSource) {
|
|
1718
|
+
return false;
|
|
1719
|
+
}
|
|
1720
|
+
return matchesClient || matchesFile || matchesSource || !scope.clientName;
|
|
1721
|
+
});
|
|
1722
|
+
return narrowed.length > 0 ? narrowed : items;
|
|
1723
|
+
}
|
|
1724
|
+
applyRelevanceFloor(items) {
|
|
1725
|
+
const filtered = items.filter(
|
|
1726
|
+
(item) => item.type === "project" ? item.score >= this.minProjectScore : item.score >= this.minMemoryScore
|
|
1727
|
+
);
|
|
1728
|
+
return { items: filtered, dropped: Math.max(0, items.length - filtered.length) };
|
|
1729
|
+
}
|
|
1730
|
+
rerank(items, scope) {
|
|
1825
1731
|
const deduped = /* @__PURE__ */ new Map();
|
|
1826
1732
|
for (const item of items) {
|
|
1827
|
-
const key =
|
|
1733
|
+
const key = this.stableItemKey(item);
|
|
1828
1734
|
const recency = extractTimestamp(item.metadata) > 0 ? 0.04 : 0;
|
|
1829
1735
|
const queryBonus = item.sourceQuery === "primary" ? 0.08 : item.sourceQuery === "task_frame" ? 0.04 : 0.03;
|
|
1736
|
+
const sourceMatch = this.hasSourceMatch(item, scope);
|
|
1737
|
+
const fileMatch = this.hasFileMatch(item, scope);
|
|
1738
|
+
const clientMatch = this.hasClientMatch(item, scope);
|
|
1739
|
+
const broadPenalty = item.pass === "broad" && !sourceMatch && !fileMatch && !clientMatch ? this.rankWeights.staleBroadPenalty : 0;
|
|
1740
|
+
const clientPenalty = scope.clientName && item.metadata?.client_name && !clientMatch ? this.rankWeights.unrelatedClientPenalty : 0;
|
|
1830
1741
|
const next = {
|
|
1831
1742
|
...item,
|
|
1832
|
-
score:
|
|
1743
|
+
score: clamp01(
|
|
1744
|
+
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
|
|
1745
|
+
)
|
|
1833
1746
|
};
|
|
1834
1747
|
const existing = deduped.get(key);
|
|
1835
1748
|
if (!existing || next.score > existing.score) {
|
|
1836
1749
|
deduped.set(key, next);
|
|
1837
1750
|
}
|
|
1838
1751
|
}
|
|
1839
|
-
return
|
|
1752
|
+
return {
|
|
1753
|
+
items: [...deduped.values()].sort((left, right) => right.score - left.score),
|
|
1754
|
+
dedupedCount: Math.max(0, items.length - deduped.size)
|
|
1755
|
+
};
|
|
1840
1756
|
}
|
|
1841
1757
|
buildContext(items) {
|
|
1842
1758
|
const maxChars = this.maxTokens * 4;
|
|
@@ -1875,7 +1791,7 @@ ${lines.join("\n")}`;
|
|
|
1875
1791
|
this.runBranch("project_rules", () => this.args.adapter.query({
|
|
1876
1792
|
project: scope.project,
|
|
1877
1793
|
query: "project rules instructions constraints conventions open threads",
|
|
1878
|
-
top_k: this.
|
|
1794
|
+
top_k: this.focusedTopK,
|
|
1879
1795
|
include_memories: false,
|
|
1880
1796
|
user_id: scope.userId,
|
|
1881
1797
|
session_id: scope.sessionId,
|
|
@@ -1893,7 +1809,7 @@ ${lines.join("\n")}`;
|
|
|
1893
1809
|
continue;
|
|
1894
1810
|
}
|
|
1895
1811
|
if (branch.name === "project_rules") {
|
|
1896
|
-
items.push(...this.contextItems(branch.value, "bootstrap"));
|
|
1812
|
+
items.push(...this.contextItems(branch.value, "bootstrap", "bootstrap"));
|
|
1897
1813
|
continue;
|
|
1898
1814
|
}
|
|
1899
1815
|
const records = branch.value.memories || [];
|
|
@@ -1903,10 +1819,12 @@ ${lines.join("\n")}`;
|
|
|
1903
1819
|
type: "memory",
|
|
1904
1820
|
score: 0.4,
|
|
1905
1821
|
sourceQuery: "bootstrap",
|
|
1822
|
+
pass: "bootstrap",
|
|
1906
1823
|
metadata: memory
|
|
1907
1824
|
})).filter((item) => item.content));
|
|
1908
1825
|
}
|
|
1909
|
-
const
|
|
1826
|
+
const reranked = this.rerank(items, { sourceIds: [], fileHints: [], clientName: this.clientName });
|
|
1827
|
+
const ranked = reranked.items.slice(0, this.broadTopK * 2);
|
|
1910
1828
|
const prepared = {
|
|
1911
1829
|
scope,
|
|
1912
1830
|
retrieval: {
|
|
@@ -1918,7 +1836,14 @@ ${lines.join("\n")}`;
|
|
|
1918
1836
|
durationMs: Date.now() - startedAt,
|
|
1919
1837
|
targetBudgetMs: this.targetRetrievalMs,
|
|
1920
1838
|
hardTimeoutMs: this.hardRetrievalTimeoutMs,
|
|
1921
|
-
branchStatus
|
|
1839
|
+
branchStatus,
|
|
1840
|
+
focusedScopeApplied: false,
|
|
1841
|
+
focusedSourceIds: [],
|
|
1842
|
+
focusedFileHints: [],
|
|
1843
|
+
clientScoped: false,
|
|
1844
|
+
fallbackUsed: false,
|
|
1845
|
+
droppedBelowFloor: 0,
|
|
1846
|
+
dedupedCount: reranked.dedupedCount
|
|
1922
1847
|
},
|
|
1923
1848
|
context: this.buildContext(ranked),
|
|
1924
1849
|
items: ranked
|
|
@@ -1927,100 +1852,195 @@ ${lines.join("\n")}`;
|
|
|
1927
1852
|
return prepared;
|
|
1928
1853
|
}
|
|
1929
1854
|
async beforeTurn(input, context = {}) {
|
|
1855
|
+
this.currentTurn += 1;
|
|
1930
1856
|
this.pushTouchedFiles(input.touchedFiles);
|
|
1857
|
+
this.refreshTaskSummary(input.taskSummary);
|
|
1931
1858
|
const { scope, warning } = await this.resolveScope(context);
|
|
1932
1859
|
const primaryQuery = compactWhitespace(input.userMessage);
|
|
1933
1860
|
const taskFrameQuery = this.makeTaskFrameQuery(input);
|
|
1861
|
+
const focusedScope = this.focusedScope(input);
|
|
1862
|
+
const focusedMetadataFilter = this.exactFileMetadataFilter(focusedScope.fileHints);
|
|
1863
|
+
const focusedScopeApplied = focusedScope.sourceIds.length > 0 || focusedScope.fileHints.length > 0 || Boolean(focusedScope.clientName);
|
|
1934
1864
|
const warnings = warning ? [warning] : [];
|
|
1935
1865
|
const startedAt = Date.now();
|
|
1936
|
-
const
|
|
1937
|
-
|
|
1866
|
+
const branchStatus = {};
|
|
1867
|
+
const collectFromBranches = (branches, pass) => {
|
|
1868
|
+
const collected = [];
|
|
1869
|
+
let okCount = 0;
|
|
1870
|
+
for (const branch of branches) {
|
|
1871
|
+
branchStatus[branch.name] = branch.status;
|
|
1872
|
+
if (branch.status !== "ok") {
|
|
1873
|
+
if (branch.status !== "skipped" && branch.reason) warnings.push(`${branch.name}:${branch.reason}`);
|
|
1874
|
+
continue;
|
|
1875
|
+
}
|
|
1876
|
+
okCount += 1;
|
|
1877
|
+
if (branch.name.startsWith("context")) {
|
|
1878
|
+
collected.push(...this.contextItems(
|
|
1879
|
+
branch.value,
|
|
1880
|
+
branch.name.includes("task_frame") ? "task_frame" : "primary",
|
|
1881
|
+
pass
|
|
1882
|
+
));
|
|
1883
|
+
} else {
|
|
1884
|
+
const memoryItems = this.memoryItems(
|
|
1885
|
+
branch.value,
|
|
1886
|
+
branch.name.includes("task_frame") ? "task_frame" : "primary",
|
|
1887
|
+
pass
|
|
1888
|
+
);
|
|
1889
|
+
collected.push(...pass === "focused" ? this.narrowFocusedMemories(memoryItems, focusedScope) : memoryItems);
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
return { collected, okCount };
|
|
1893
|
+
};
|
|
1894
|
+
const focusedBranches = await Promise.all([
|
|
1895
|
+
this.runBranch("context_primary_focused", () => this.args.adapter.query({
|
|
1938
1896
|
project: scope.project,
|
|
1939
1897
|
query: primaryQuery,
|
|
1940
|
-
top_k: this.
|
|
1898
|
+
top_k: this.focusedTopK,
|
|
1941
1899
|
include_memories: false,
|
|
1942
1900
|
user_id: scope.userId,
|
|
1943
1901
|
session_id: scope.sessionId,
|
|
1902
|
+
source_ids: focusedScope.sourceIds.length > 0 ? focusedScope.sourceIds : void 0,
|
|
1903
|
+
metadata_filter: focusedMetadataFilter,
|
|
1944
1904
|
max_tokens: this.maxTokens,
|
|
1945
1905
|
compress: true,
|
|
1946
1906
|
compression_strategy: "adaptive"
|
|
1947
1907
|
})),
|
|
1948
|
-
this.runBranch("
|
|
1908
|
+
this.runBranch("memory_primary_focused", () => this.args.adapter.searchMemories({
|
|
1949
1909
|
project: scope.project,
|
|
1950
1910
|
query: primaryQuery,
|
|
1951
1911
|
user_id: scope.userId,
|
|
1952
1912
|
session_id: scope.sessionId,
|
|
1953
|
-
top_k: this.
|
|
1913
|
+
top_k: this.focusedTopK,
|
|
1954
1914
|
include_pending: true,
|
|
1955
1915
|
profile: "balanced"
|
|
1956
1916
|
})),
|
|
1957
|
-
taskFrameQuery ? this.runBranch("
|
|
1917
|
+
taskFrameQuery ? this.runBranch("context_task_frame_focused", () => this.args.adapter.query({
|
|
1958
1918
|
project: scope.project,
|
|
1959
1919
|
query: taskFrameQuery,
|
|
1960
|
-
top_k: this.
|
|
1920
|
+
top_k: this.focusedTopK,
|
|
1961
1921
|
include_memories: false,
|
|
1962
1922
|
user_id: scope.userId,
|
|
1963
1923
|
session_id: scope.sessionId,
|
|
1924
|
+
source_ids: focusedScope.sourceIds.length > 0 ? focusedScope.sourceIds : void 0,
|
|
1925
|
+
metadata_filter: focusedMetadataFilter,
|
|
1964
1926
|
max_tokens: this.maxTokens,
|
|
1965
1927
|
compress: true,
|
|
1966
1928
|
compression_strategy: "adaptive"
|
|
1967
|
-
})) : Promise.resolve({
|
|
1968
|
-
|
|
1969
|
-
status: "skipped",
|
|
1970
|
-
durationMs: 0
|
|
1971
|
-
}),
|
|
1972
|
-
taskFrameQuery ? this.runBranch("memory_task_frame", () => this.args.adapter.searchMemories({
|
|
1929
|
+
})) : Promise.resolve({ name: "context_task_frame_focused", status: "skipped", durationMs: 0 }),
|
|
1930
|
+
taskFrameQuery ? this.runBranch("memory_task_frame_focused", () => this.args.adapter.searchMemories({
|
|
1973
1931
|
project: scope.project,
|
|
1974
1932
|
query: taskFrameQuery,
|
|
1975
1933
|
user_id: scope.userId,
|
|
1976
1934
|
session_id: scope.sessionId,
|
|
1977
|
-
top_k: this.
|
|
1935
|
+
top_k: this.focusedTopK,
|
|
1978
1936
|
include_pending: true,
|
|
1979
1937
|
profile: "balanced"
|
|
1980
|
-
})) : Promise.resolve({
|
|
1981
|
-
name: "memory_task_frame",
|
|
1982
|
-
status: "skipped",
|
|
1983
|
-
durationMs: 0
|
|
1984
|
-
})
|
|
1938
|
+
})) : Promise.resolve({ name: "memory_task_frame_focused", status: "skipped", durationMs: 0 })
|
|
1985
1939
|
]);
|
|
1986
|
-
const
|
|
1987
|
-
const
|
|
1988
|
-
|
|
1989
|
-
|
|
1940
|
+
const focusedCollected = collectFromBranches(focusedBranches, "focused");
|
|
1941
|
+
const focusedRanked = this.rerank(focusedCollected.collected, focusedScope);
|
|
1942
|
+
const focusedFloored = this.applyRelevanceFloor(focusedRanked.items);
|
|
1943
|
+
let allCollected = [...focusedFloored.items];
|
|
1944
|
+
let totalOkCount = focusedCollected.okCount;
|
|
1945
|
+
let dedupedCount = focusedRanked.dedupedCount;
|
|
1946
|
+
let droppedBelowFloor = focusedFloored.dropped;
|
|
1947
|
+
const focusedTopScore = focusedFloored.items[0]?.score ?? 0;
|
|
1948
|
+
const fallbackUsed = focusedFloored.items.length < this.minFocusedResults || focusedTopScore < this.minFocusedTopScore;
|
|
1949
|
+
if (focusedScopeApplied) {
|
|
1950
|
+
this.sourceScopedTurns += 1;
|
|
1951
|
+
}
|
|
1952
|
+
if (!fallbackUsed) {
|
|
1953
|
+
this.focusedPassHits += 1;
|
|
1954
|
+
}
|
|
1955
|
+
const broadBranches = fallbackUsed ? await Promise.all([
|
|
1956
|
+
this.runBranch("context_primary_broad", () => this.args.adapter.query({
|
|
1957
|
+
project: scope.project,
|
|
1958
|
+
query: primaryQuery,
|
|
1959
|
+
top_k: this.broadTopK,
|
|
1960
|
+
include_memories: false,
|
|
1961
|
+
user_id: scope.userId,
|
|
1962
|
+
session_id: scope.sessionId,
|
|
1963
|
+
max_tokens: this.maxTokens,
|
|
1964
|
+
compress: true,
|
|
1965
|
+
compression_strategy: "adaptive"
|
|
1966
|
+
})),
|
|
1967
|
+
this.runBranch("memory_primary_broad", () => this.args.adapter.searchMemories({
|
|
1968
|
+
project: scope.project,
|
|
1969
|
+
query: primaryQuery,
|
|
1970
|
+
user_id: scope.userId,
|
|
1971
|
+
session_id: scope.sessionId,
|
|
1972
|
+
top_k: this.broadTopK,
|
|
1973
|
+
include_pending: true,
|
|
1974
|
+
profile: "balanced"
|
|
1975
|
+
})),
|
|
1976
|
+
taskFrameQuery ? this.runBranch("context_task_frame_broad", () => this.args.adapter.query({
|
|
1977
|
+
project: scope.project,
|
|
1978
|
+
query: taskFrameQuery,
|
|
1979
|
+
top_k: this.broadTopK,
|
|
1980
|
+
include_memories: false,
|
|
1981
|
+
user_id: scope.userId,
|
|
1982
|
+
session_id: scope.sessionId,
|
|
1983
|
+
max_tokens: this.maxTokens,
|
|
1984
|
+
compress: true,
|
|
1985
|
+
compression_strategy: "adaptive"
|
|
1986
|
+
})) : Promise.resolve({ name: "context_task_frame_broad", status: "skipped", durationMs: 0 }),
|
|
1987
|
+
taskFrameQuery ? this.runBranch("memory_task_frame_broad", () => this.args.adapter.searchMemories({
|
|
1988
|
+
project: scope.project,
|
|
1989
|
+
query: taskFrameQuery,
|
|
1990
|
+
user_id: scope.userId,
|
|
1991
|
+
session_id: scope.sessionId,
|
|
1992
|
+
top_k: this.broadTopK,
|
|
1993
|
+
include_pending: true,
|
|
1994
|
+
profile: "balanced"
|
|
1995
|
+
})) : Promise.resolve({ name: "memory_task_frame_broad", status: "skipped", durationMs: 0 })
|
|
1996
|
+
]) : [
|
|
1997
|
+
{ name: "context_primary_broad", status: "skipped", durationMs: 0 },
|
|
1998
|
+
{ name: "memory_primary_broad", status: "skipped", durationMs: 0 },
|
|
1999
|
+
{ name: "context_task_frame_broad", status: "skipped", durationMs: 0 },
|
|
2000
|
+
{ name: "memory_task_frame_broad", status: "skipped", durationMs: 0 }
|
|
2001
|
+
];
|
|
2002
|
+
const broadCollected = collectFromBranches(broadBranches, "broad");
|
|
2003
|
+
totalOkCount += broadCollected.okCount;
|
|
2004
|
+
if (fallbackUsed) {
|
|
2005
|
+
this.fallbackTriggers += 1;
|
|
2006
|
+
this.broadScopedTurns += 1;
|
|
2007
|
+
allCollected = [...allCollected, ...broadCollected.collected];
|
|
2008
|
+
}
|
|
2009
|
+
const ranked = this.rerank(allCollected, focusedScope);
|
|
2010
|
+
dedupedCount += ranked.dedupedCount;
|
|
2011
|
+
const floored = this.applyRelevanceFloor(ranked.items);
|
|
2012
|
+
droppedBelowFloor += floored.dropped;
|
|
2013
|
+
this.floorDroppedCount += droppedBelowFloor;
|
|
2014
|
+
this.droppedCount += droppedBelowFloor;
|
|
2015
|
+
const finalItems = floored.items.slice(0, this.broadTopK);
|
|
2016
|
+
this.injectedItemCount += finalItems.length;
|
|
2017
|
+
this.totalTurns += 1;
|
|
2018
|
+
const executedBranches = [...focusedBranches, ...broadBranches].filter((branch) => branch.status !== "skipped");
|
|
2019
|
+
for (const branch of [...focusedBranches, ...broadBranches]) {
|
|
1990
2020
|
branchStatus[branch.name] = branch.status;
|
|
1991
|
-
if (branch.status !== "ok") {
|
|
1992
|
-
if (branch.status !== "skipped" && branch.reason) warnings.push(`${branch.name}:${branch.reason}`);
|
|
1993
|
-
continue;
|
|
1994
|
-
}
|
|
1995
|
-
okCount += 1;
|
|
1996
|
-
if (branch.name.startsWith("context")) {
|
|
1997
|
-
collected.push(...this.contextItems(
|
|
1998
|
-
branch.value,
|
|
1999
|
-
branch.name.includes("task_frame") ? "task_frame" : "primary"
|
|
2000
|
-
));
|
|
2001
|
-
} else {
|
|
2002
|
-
collected.push(...this.memoryItems(
|
|
2003
|
-
branch.value,
|
|
2004
|
-
branch.name.includes("task_frame") ? "task_frame" : "primary"
|
|
2005
|
-
));
|
|
2006
|
-
}
|
|
2007
2021
|
}
|
|
2008
|
-
const ranked = this.rerank(collected).slice(0, this.topK * 2);
|
|
2009
2022
|
const prepared = {
|
|
2010
2023
|
scope,
|
|
2011
2024
|
retrieval: {
|
|
2012
2025
|
primaryQuery,
|
|
2013
2026
|
taskFrameQuery,
|
|
2014
2027
|
warnings,
|
|
2015
|
-
degraded:
|
|
2016
|
-
degradedReason:
|
|
2028
|
+
degraded: totalOkCount < executedBranches.length,
|
|
2029
|
+
degradedReason: totalOkCount === 0 ? "all_retrieval_failed" : warnings.length > 0 ? "partial_retrieval_failed" : void 0,
|
|
2017
2030
|
durationMs: Date.now() - startedAt,
|
|
2018
2031
|
targetBudgetMs: this.targetRetrievalMs,
|
|
2019
2032
|
hardTimeoutMs: this.hardRetrievalTimeoutMs,
|
|
2020
|
-
branchStatus
|
|
2033
|
+
branchStatus,
|
|
2034
|
+
focusedScopeApplied,
|
|
2035
|
+
focusedSourceIds: focusedScope.sourceIds,
|
|
2036
|
+
focusedFileHints: focusedScope.fileHints.map((value) => pathBase(value)),
|
|
2037
|
+
clientScoped: Boolean(focusedScope.clientName),
|
|
2038
|
+
fallbackUsed,
|
|
2039
|
+
droppedBelowFloor,
|
|
2040
|
+
dedupedCount
|
|
2021
2041
|
},
|
|
2022
|
-
context: this.buildContext(
|
|
2023
|
-
items:
|
|
2042
|
+
context: this.buildContext(finalItems),
|
|
2043
|
+
items: finalItems
|
|
2024
2044
|
};
|
|
2025
2045
|
this.lastPreparedTurn = prepared.retrieval;
|
|
2026
2046
|
return prepared;
|
|
@@ -2115,7 +2135,14 @@ ${lines.join("\n")}`;
|
|
|
2115
2135
|
counters: {
|
|
2116
2136
|
mergedCount: this.mergedCount,
|
|
2117
2137
|
droppedCount: this.droppedCount,
|
|
2118
|
-
bufferedLowSalience: this.bufferedLowSalience.length
|
|
2138
|
+
bufferedLowSalience: this.bufferedLowSalience.length,
|
|
2139
|
+
focusedPassHits: this.focusedPassHits,
|
|
2140
|
+
fallbackTriggers: this.fallbackTriggers,
|
|
2141
|
+
floorDroppedCount: this.floorDroppedCount,
|
|
2142
|
+
injectedItemCount: this.injectedItemCount,
|
|
2143
|
+
sourceScopedTurns: this.sourceScopedTurns,
|
|
2144
|
+
broadScopedTurns: this.broadScopedTurns,
|
|
2145
|
+
totalTurns: this.totalTurns
|
|
2119
2146
|
}
|
|
2120
2147
|
};
|
|
2121
2148
|
}
|
|
@@ -2333,165 +2360,469 @@ var WhisperClient = class _WhisperClient {
|
|
|
2333
2360
|
retryable: false
|
|
2334
2361
|
});
|
|
2335
2362
|
}
|
|
2336
|
-
return resolved;
|
|
2363
|
+
return resolved;
|
|
2364
|
+
}
|
|
2365
|
+
async refreshProjectCache(force = false) {
|
|
2366
|
+
if (!force && Date.now() < this.projectCacheExpiresAt && this.projectCache.length > 0) {
|
|
2367
|
+
return this.projectCache;
|
|
2368
|
+
}
|
|
2369
|
+
const response = await this.runtimeClient.request({
|
|
2370
|
+
endpoint: "/v1/projects",
|
|
2371
|
+
method: "GET",
|
|
2372
|
+
operation: "get",
|
|
2373
|
+
idempotent: true
|
|
2374
|
+
});
|
|
2375
|
+
this.projectRefToId.clear();
|
|
2376
|
+
this.projectCache = response.data?.projects || [];
|
|
2377
|
+
for (const project of this.projectCache) {
|
|
2378
|
+
this.projectRefToId.set(project.id, project.id);
|
|
2379
|
+
this.projectRefToId.set(project.slug, project.id);
|
|
2380
|
+
this.projectRefToId.set(project.name, project.id);
|
|
2381
|
+
}
|
|
2382
|
+
this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
|
|
2383
|
+
return this.projectCache;
|
|
2384
|
+
}
|
|
2385
|
+
async fetchResolvedProject(projectRef) {
|
|
2386
|
+
try {
|
|
2387
|
+
const response = await this.runtimeClient.request({
|
|
2388
|
+
endpoint: `/v1/projects/resolve?project=${encodeURIComponent(projectRef)}`,
|
|
2389
|
+
method: "GET",
|
|
2390
|
+
operation: "get",
|
|
2391
|
+
idempotent: true
|
|
2392
|
+
});
|
|
2393
|
+
return response.data?.resolved || null;
|
|
2394
|
+
} catch (error) {
|
|
2395
|
+
if (error instanceof RuntimeClientError && error.status === 404) {
|
|
2396
|
+
return null;
|
|
2397
|
+
}
|
|
2398
|
+
throw error;
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
async resolveProject(projectRef) {
|
|
2402
|
+
const resolvedRef = this.getRequiredProject(projectRef);
|
|
2403
|
+
const cachedProjects = await this.refreshProjectCache(false);
|
|
2404
|
+
const cachedProject = cachedProjects.find(
|
|
2405
|
+
(project) => project.id === resolvedRef || project.slug === resolvedRef || project.name === resolvedRef
|
|
2406
|
+
);
|
|
2407
|
+
if (cachedProject) {
|
|
2408
|
+
return cachedProject;
|
|
2409
|
+
}
|
|
2410
|
+
const resolvedProject = await this.fetchResolvedProject(resolvedRef);
|
|
2411
|
+
if (resolvedProject) {
|
|
2412
|
+
this.projectRefToId.set(resolvedProject.id, resolvedProject.id);
|
|
2413
|
+
this.projectRefToId.set(resolvedProject.slug, resolvedProject.id);
|
|
2414
|
+
this.projectRefToId.set(resolvedProject.name, resolvedProject.id);
|
|
2415
|
+
this.projectCache = [
|
|
2416
|
+
...this.projectCache.filter((project) => project.id !== resolvedProject.id),
|
|
2417
|
+
resolvedProject
|
|
2418
|
+
];
|
|
2419
|
+
this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
|
|
2420
|
+
return resolvedProject;
|
|
2421
|
+
}
|
|
2422
|
+
if (isLikelyProjectId(resolvedRef)) {
|
|
2423
|
+
return {
|
|
2424
|
+
id: resolvedRef,
|
|
2425
|
+
orgId: "",
|
|
2426
|
+
name: resolvedRef,
|
|
2427
|
+
slug: resolvedRef,
|
|
2428
|
+
createdAt: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
2429
|
+
updatedAt: (/* @__PURE__ */ new Date(0)).toISOString()
|
|
2430
|
+
};
|
|
2431
|
+
}
|
|
2432
|
+
throw new RuntimeClientError({
|
|
2433
|
+
code: "PROJECT_NOT_FOUND",
|
|
2434
|
+
message: `Project '${resolvedRef}' not found`,
|
|
2435
|
+
retryable: false
|
|
2436
|
+
});
|
|
2437
|
+
}
|
|
2438
|
+
async query(params) {
|
|
2439
|
+
const project = (await this.resolveProject(params.project)).id;
|
|
2440
|
+
const response = await this.runtimeClient.request({
|
|
2441
|
+
endpoint: "/v1/context/query",
|
|
2442
|
+
method: "POST",
|
|
2443
|
+
operation: "search",
|
|
2444
|
+
body: {
|
|
2445
|
+
...params,
|
|
2446
|
+
project
|
|
2447
|
+
},
|
|
2448
|
+
idempotent: true
|
|
2449
|
+
});
|
|
2450
|
+
return response.data;
|
|
2451
|
+
}
|
|
2452
|
+
async ingestSession(params) {
|
|
2453
|
+
const project = (await this.resolveProject(params.project)).id;
|
|
2454
|
+
const response = await this.runtimeClient.request({
|
|
2455
|
+
endpoint: "/v1/memory/ingest/session",
|
|
2456
|
+
method: "POST",
|
|
2457
|
+
operation: "session",
|
|
2458
|
+
body: {
|
|
2459
|
+
...params,
|
|
2460
|
+
project
|
|
2461
|
+
}
|
|
2462
|
+
});
|
|
2463
|
+
return response.data;
|
|
2464
|
+
}
|
|
2465
|
+
createAgentRuntime(options = {}) {
|
|
2466
|
+
const baseContext = {
|
|
2467
|
+
workspacePath: options.workspacePath,
|
|
2468
|
+
project: options.project || this.config.project,
|
|
2469
|
+
userId: options.userId,
|
|
2470
|
+
sessionId: options.sessionId,
|
|
2471
|
+
traceId: options.traceId,
|
|
2472
|
+
clientName: options.clientName
|
|
2473
|
+
};
|
|
2474
|
+
return new WhisperAgentRuntime({
|
|
2475
|
+
baseContext,
|
|
2476
|
+
options,
|
|
2477
|
+
adapter: {
|
|
2478
|
+
resolveProject: (project) => this.resolveProject(project),
|
|
2479
|
+
query: (params) => this.query(params),
|
|
2480
|
+
ingestSession: (params) => this.ingestSession(params),
|
|
2481
|
+
getSessionMemories: (params) => this.memory.getSessionMemories(params),
|
|
2482
|
+
getUserProfile: (params) => this.memory.getUserProfile(params),
|
|
2483
|
+
searchMemories: (params) => this.memory.search(params),
|
|
2484
|
+
addMemory: (params) => this.memory.add(params),
|
|
2485
|
+
queueStatus: () => this.queue.status(),
|
|
2486
|
+
flushQueue: () => this.queue.flush()
|
|
2487
|
+
}
|
|
2488
|
+
});
|
|
2489
|
+
}
|
|
2490
|
+
withRunContext(context) {
|
|
2491
|
+
const base = this;
|
|
2492
|
+
return {
|
|
2493
|
+
memory: {
|
|
2494
|
+
add: (params) => base.memory.add({
|
|
2495
|
+
...params,
|
|
2496
|
+
project: params.project || context.project || base.config.project,
|
|
2497
|
+
user_id: params.user_id || context.userId,
|
|
2498
|
+
session_id: params.session_id || context.sessionId
|
|
2499
|
+
}),
|
|
2500
|
+
search: (params) => base.memory.search({
|
|
2501
|
+
...params,
|
|
2502
|
+
project: params.project || context.project || base.config.project,
|
|
2503
|
+
user_id: params.user_id || context.userId,
|
|
2504
|
+
session_id: params.session_id || context.sessionId
|
|
2505
|
+
})
|
|
2506
|
+
},
|
|
2507
|
+
session: {
|
|
2508
|
+
event: (params) => base.session.event({
|
|
2509
|
+
...params,
|
|
2510
|
+
sessionId: params.sessionId || context.sessionId || ""
|
|
2511
|
+
})
|
|
2512
|
+
},
|
|
2513
|
+
queue: base.queue,
|
|
2514
|
+
diagnostics: base.diagnostics
|
|
2515
|
+
};
|
|
2516
|
+
}
|
|
2517
|
+
async shutdown() {
|
|
2518
|
+
await this.writeQueue.stop();
|
|
2519
|
+
}
|
|
2520
|
+
};
|
|
2521
|
+
var whisper_default = WhisperClient;
|
|
2522
|
+
|
|
2523
|
+
// ../src/sdk/whisper-agent.ts
|
|
2524
|
+
var DEPRECATION_WARNINGS = /* @__PURE__ */ new Set();
|
|
2525
|
+
function warnDeprecatedOnce(key, message) {
|
|
2526
|
+
if (DEPRECATION_WARNINGS.has(key)) return;
|
|
2527
|
+
DEPRECATION_WARNINGS.add(key);
|
|
2528
|
+
if (typeof console !== "undefined" && typeof console.warn === "function") {
|
|
2529
|
+
console.warn(message);
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
var Whisper = class {
|
|
2533
|
+
client;
|
|
2534
|
+
runtimeClient;
|
|
2535
|
+
options;
|
|
2536
|
+
sessionId;
|
|
2537
|
+
userId;
|
|
2538
|
+
constructor(options) {
|
|
2539
|
+
if (!options.apiKey) {
|
|
2540
|
+
throw new Error("API key is required");
|
|
2541
|
+
}
|
|
2542
|
+
const clientConfig = {
|
|
2543
|
+
apiKey: options.apiKey,
|
|
2544
|
+
baseUrl: options.baseUrl,
|
|
2545
|
+
project: options.project || "default"
|
|
2546
|
+
};
|
|
2547
|
+
if (options.timeoutMs) clientConfig.timeoutMs = options.timeoutMs;
|
|
2548
|
+
if (options.retry) clientConfig.retry = options.retry;
|
|
2549
|
+
this.client = new WhisperContext(clientConfig);
|
|
2550
|
+
this.runtimeClient = new WhisperClient({
|
|
2551
|
+
apiKey: options.apiKey,
|
|
2552
|
+
baseUrl: options.baseUrl,
|
|
2553
|
+
project: options.project || "default"
|
|
2554
|
+
});
|
|
2555
|
+
warnDeprecatedOnce(
|
|
2556
|
+
"whisper_agent_wrapper",
|
|
2557
|
+
"[Whisper SDK] Whisper wrapper is supported for v2 compatibility. Prefer WhisperClient for new integrations."
|
|
2558
|
+
);
|
|
2559
|
+
const finalRetry = options.retry || { maxAttempts: 3, baseDelayMs: 250, maxDelayMs: 2e3 };
|
|
2560
|
+
this.options = {
|
|
2561
|
+
apiKey: options.apiKey,
|
|
2562
|
+
baseUrl: options.baseUrl || "https://context.usewhisper.dev",
|
|
2563
|
+
project: options.project || "default",
|
|
2564
|
+
timeoutMs: options.timeoutMs || 15e3,
|
|
2565
|
+
retry: finalRetry,
|
|
2566
|
+
contextLimit: options.contextLimit ?? 10,
|
|
2567
|
+
memoryTypes: options.memoryTypes ?? ["factual", "preference", "event", "goal", "relationship", "opinion", "instruction"],
|
|
2568
|
+
contextPrefix: options.contextPrefix ?? "Relevant context:",
|
|
2569
|
+
autoExtract: options.autoExtract ?? true,
|
|
2570
|
+
autoExtractMinConfidence: options.autoExtractMinConfidence ?? 0.65,
|
|
2571
|
+
maxMemoriesPerCapture: options.maxMemoriesPerCapture ?? 5
|
|
2572
|
+
};
|
|
2573
|
+
}
|
|
2574
|
+
/**
|
|
2575
|
+
* Set session ID for conversation tracking
|
|
2576
|
+
*/
|
|
2577
|
+
session(sessionId) {
|
|
2578
|
+
this.sessionId = sessionId;
|
|
2579
|
+
return this;
|
|
2580
|
+
}
|
|
2581
|
+
/**
|
|
2582
|
+
* Set user ID for user-specific memories
|
|
2583
|
+
*/
|
|
2584
|
+
user(userId) {
|
|
2585
|
+
this.userId = userId;
|
|
2586
|
+
return this;
|
|
2587
|
+
}
|
|
2588
|
+
/**
|
|
2589
|
+
* Get relevant context BEFORE your LLM call
|
|
2590
|
+
*
|
|
2591
|
+
* @param query - What you want to know / user question
|
|
2592
|
+
* @returns Context string and raw results
|
|
2593
|
+
*
|
|
2594
|
+
* @example
|
|
2595
|
+
* ```typescript
|
|
2596
|
+
* const { context, results, count } = await whisper.getContext(
|
|
2597
|
+
* "What are user's preferences?",
|
|
2598
|
+
* { userId: "user-123" }
|
|
2599
|
+
* );
|
|
2600
|
+
*
|
|
2601
|
+
* // Results: [
|
|
2602
|
+
* // { content: "User prefers dark mode", type: "preference", score: 0.95 },
|
|
2603
|
+
* // { content: "Allergic to nuts", type: "factual", score: 0.89 }
|
|
2604
|
+
* // ]
|
|
2605
|
+
* ```
|
|
2606
|
+
*/
|
|
2607
|
+
async getContext(query, options) {
|
|
2608
|
+
const runtime = this.runtimeClient.createAgentRuntime({
|
|
2609
|
+
project: options?.project ?? this.options.project,
|
|
2610
|
+
userId: options?.userId ?? this.userId,
|
|
2611
|
+
sessionId: options?.sessionId ?? this.sessionId,
|
|
2612
|
+
topK: options?.limit ?? this.options.contextLimit,
|
|
2613
|
+
clientName: "whisper-wrapper"
|
|
2614
|
+
});
|
|
2615
|
+
const prepared = await runtime.beforeTurn({
|
|
2616
|
+
userMessage: query
|
|
2617
|
+
});
|
|
2618
|
+
const results = prepared.items.map((item, index) => ({
|
|
2619
|
+
id: item.id || `runtime_${index}`,
|
|
2620
|
+
content: item.content,
|
|
2621
|
+
score: item.score,
|
|
2622
|
+
metadata: item.metadata || {},
|
|
2623
|
+
source: item.type === "memory" ? "memory" : "runtime",
|
|
2624
|
+
document: item.sourceQuery,
|
|
2625
|
+
type: item.type,
|
|
2626
|
+
retrieval_source: item.type === "memory" ? "memory" : "runtime"
|
|
2627
|
+
}));
|
|
2628
|
+
const context = results.map((r, i) => `[${i + 1}] ${r.content}`).join("\n");
|
|
2629
|
+
return {
|
|
2630
|
+
context: context ? `${this.options.contextPrefix}
|
|
2631
|
+
${context}` : "",
|
|
2632
|
+
results,
|
|
2633
|
+
count: prepared.items.length
|
|
2634
|
+
};
|
|
2635
|
+
}
|
|
2636
|
+
/**
|
|
2637
|
+
* Remember what happened AFTER your LLM response
|
|
2638
|
+
*
|
|
2639
|
+
* Fire-and-forget - doesn't block your response
|
|
2640
|
+
*
|
|
2641
|
+
* @param content - What your LLM responded with
|
|
2642
|
+
* @returns Promise that resolves when stored (or fails silently)
|
|
2643
|
+
*
|
|
2644
|
+
* @example
|
|
2645
|
+
* ```typescript
|
|
2646
|
+
* const llmResponse = "I've set your theme to dark mode and removed nuts from recommendations.";
|
|
2647
|
+
*
|
|
2648
|
+
* await whisper.remember(llmResponse, { userId: "user-123" });
|
|
2649
|
+
* // → Auto-extracts: "theme set to dark mode", "nut allergy"
|
|
2650
|
+
* // → Stored as preferences
|
|
2651
|
+
* ```
|
|
2652
|
+
*/
|
|
2653
|
+
async remember(content, options) {
|
|
2654
|
+
if (!content || content.length < 5) {
|
|
2655
|
+
return { success: false };
|
|
2656
|
+
}
|
|
2657
|
+
try {
|
|
2658
|
+
if (this.options.autoExtract) {
|
|
2659
|
+
const extraction = await this.client.extractMemories({
|
|
2660
|
+
project: options?.project ?? this.options.project,
|
|
2661
|
+
message: content,
|
|
2662
|
+
user_id: options?.userId ?? this.userId,
|
|
2663
|
+
session_id: options?.sessionId ?? this.sessionId,
|
|
2664
|
+
enable_pattern: true,
|
|
2665
|
+
enable_inference: true,
|
|
2666
|
+
min_confidence: this.options.autoExtractMinConfidence
|
|
2667
|
+
});
|
|
2668
|
+
const extractedMemories = (extraction.all || []).filter((m) => (m.confidence || 0) >= this.options.autoExtractMinConfidence).slice(0, this.options.maxMemoriesPerCapture);
|
|
2669
|
+
if (extractedMemories.length > 0) {
|
|
2670
|
+
const bulk = await this.client.addMemoriesBulk({
|
|
2671
|
+
project: options?.project ?? this.options.project,
|
|
2672
|
+
write_mode: "async",
|
|
2673
|
+
memories: extractedMemories.map((m) => ({
|
|
2674
|
+
content: m.content,
|
|
2675
|
+
memory_type: m.memoryType,
|
|
2676
|
+
user_id: options?.userId ?? this.userId,
|
|
2677
|
+
session_id: options?.sessionId ?? this.sessionId,
|
|
2678
|
+
importance: Math.max(0.5, Math.min(1, m.confidence || 0.7)),
|
|
2679
|
+
confidence: m.confidence || 0.7,
|
|
2680
|
+
entity_mentions: m.entityMentions || [],
|
|
2681
|
+
event_date: m.eventDate || void 0,
|
|
2682
|
+
metadata: {
|
|
2683
|
+
extracted: true,
|
|
2684
|
+
extraction_method: extraction.extractionMethod,
|
|
2685
|
+
extraction_reasoning: m.reasoning,
|
|
2686
|
+
inferred: Boolean(m.inferred)
|
|
2687
|
+
}
|
|
2688
|
+
}))
|
|
2689
|
+
});
|
|
2690
|
+
const memoryIds = this.extractMemoryIdsFromBulkResponse(bulk);
|
|
2691
|
+
return {
|
|
2692
|
+
success: true,
|
|
2693
|
+
memoryId: memoryIds[0],
|
|
2694
|
+
memoryIds: memoryIds.length > 0 ? memoryIds : void 0,
|
|
2695
|
+
extracted: extractedMemories.length
|
|
2696
|
+
};
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
const result = await this.client.addMemory({
|
|
2700
|
+
project: options?.project ?? this.options.project,
|
|
2701
|
+
content,
|
|
2702
|
+
user_id: options?.userId ?? this.userId,
|
|
2703
|
+
session_id: options?.sessionId ?? this.sessionId
|
|
2704
|
+
});
|
|
2705
|
+
return {
|
|
2706
|
+
success: true,
|
|
2707
|
+
memoryId: result?.id
|
|
2708
|
+
};
|
|
2709
|
+
} catch (error) {
|
|
2710
|
+
console.error("[Whisper] Remember failed:", error);
|
|
2711
|
+
return { success: false };
|
|
2712
|
+
}
|
|
2337
2713
|
}
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
endpoint: "/v1/projects",
|
|
2344
|
-
method: "GET",
|
|
2345
|
-
operation: "get",
|
|
2346
|
-
idempotent: true
|
|
2347
|
-
});
|
|
2348
|
-
this.projectRefToId.clear();
|
|
2349
|
-
this.projectCache = response.data?.projects || [];
|
|
2350
|
-
for (const project of this.projectCache) {
|
|
2351
|
-
this.projectRefToId.set(project.id, project.id);
|
|
2352
|
-
this.projectRefToId.set(project.slug, project.id);
|
|
2353
|
-
this.projectRefToId.set(project.name, project.id);
|
|
2354
|
-
}
|
|
2355
|
-
this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
|
|
2356
|
-
return this.projectCache;
|
|
2714
|
+
/**
|
|
2715
|
+
* Alias for remember() - same thing
|
|
2716
|
+
*/
|
|
2717
|
+
async capture(content, options) {
|
|
2718
|
+
return this.remember(content, options);
|
|
2357
2719
|
}
|
|
2358
|
-
|
|
2720
|
+
/**
|
|
2721
|
+
* Capture from multiple messages (e.g., full conversation)
|
|
2722
|
+
*/
|
|
2723
|
+
async captureSession(messages, options) {
|
|
2359
2724
|
try {
|
|
2360
|
-
const
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2725
|
+
const filteredMessages = messages.filter((m) => m.role !== "system");
|
|
2726
|
+
const runtime = this.runtimeClient.createAgentRuntime({
|
|
2727
|
+
project: options?.project ?? this.options.project,
|
|
2728
|
+
userId: options?.userId ?? this.userId,
|
|
2729
|
+
sessionId: options?.sessionId ?? this.sessionId ?? "default",
|
|
2730
|
+
clientName: "whisper-wrapper"
|
|
2365
2731
|
});
|
|
2366
|
-
|
|
2732
|
+
const result = await runtime.afterTurn({
|
|
2733
|
+
userMessage: [...filteredMessages].reverse().find((m) => m.role === "user")?.content || "",
|
|
2734
|
+
assistantMessage: [...filteredMessages].reverse().find((m) => m.role === "assistant")?.content || ""
|
|
2735
|
+
});
|
|
2736
|
+
return {
|
|
2737
|
+
success: true,
|
|
2738
|
+
extracted: result.memoriesCreated ?? 0
|
|
2739
|
+
};
|
|
2367
2740
|
} catch (error) {
|
|
2368
|
-
|
|
2369
|
-
|
|
2741
|
+
const fallback = await this.fallbackCaptureViaAddMemory(messages, options);
|
|
2742
|
+
if (fallback.success) {
|
|
2743
|
+
return fallback;
|
|
2370
2744
|
}
|
|
2371
|
-
|
|
2745
|
+
console.error("[Whisper] Session capture failed:", error);
|
|
2746
|
+
return { success: false, extracted: 0 };
|
|
2372
2747
|
}
|
|
2373
2748
|
}
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
const resolvedProject = await this.fetchResolvedProject(resolvedRef);
|
|
2384
|
-
if (resolvedProject) {
|
|
2385
|
-
this.projectRefToId.set(resolvedProject.id, resolvedProject.id);
|
|
2386
|
-
this.projectRefToId.set(resolvedProject.slug, resolvedProject.id);
|
|
2387
|
-
this.projectRefToId.set(resolvedProject.name, resolvedProject.id);
|
|
2388
|
-
this.projectCache = [
|
|
2389
|
-
...this.projectCache.filter((project) => project.id !== resolvedProject.id),
|
|
2390
|
-
resolvedProject
|
|
2391
|
-
];
|
|
2392
|
-
this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
|
|
2393
|
-
return resolvedProject;
|
|
2394
|
-
}
|
|
2395
|
-
if (isLikelyProjectId(resolvedRef)) {
|
|
2396
|
-
return {
|
|
2397
|
-
id: resolvedRef,
|
|
2398
|
-
orgId: "",
|
|
2399
|
-
name: resolvedRef,
|
|
2400
|
-
slug: resolvedRef,
|
|
2401
|
-
createdAt: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
2402
|
-
updatedAt: (/* @__PURE__ */ new Date(0)).toISOString()
|
|
2403
|
-
};
|
|
2404
|
-
}
|
|
2405
|
-
throw new RuntimeClientError({
|
|
2406
|
-
code: "PROJECT_NOT_FOUND",
|
|
2407
|
-
message: `Project '${resolvedRef}' not found`,
|
|
2408
|
-
retryable: false
|
|
2749
|
+
/**
|
|
2750
|
+
* Run a full agent turn with automatic memory read (before) + write (after).
|
|
2751
|
+
*/
|
|
2752
|
+
async runTurn(params) {
|
|
2753
|
+
const contextResult = await this.getContext(params.userMessage, {
|
|
2754
|
+
userId: params.userId,
|
|
2755
|
+
sessionId: params.sessionId,
|
|
2756
|
+
project: params.project,
|
|
2757
|
+
limit: params.limit
|
|
2409
2758
|
});
|
|
2759
|
+
const prompt = contextResult.context ? `${contextResult.context}
|
|
2760
|
+
|
|
2761
|
+
User: ${params.userMessage}` : params.userMessage;
|
|
2762
|
+
const response = await params.generate(prompt);
|
|
2763
|
+
const captureResult = await this.captureSession(
|
|
2764
|
+
[
|
|
2765
|
+
{ role: "user", content: params.userMessage },
|
|
2766
|
+
{ role: "assistant", content: response }
|
|
2767
|
+
],
|
|
2768
|
+
{
|
|
2769
|
+
userId: params.userId,
|
|
2770
|
+
sessionId: params.sessionId,
|
|
2771
|
+
project: params.project
|
|
2772
|
+
}
|
|
2773
|
+
);
|
|
2774
|
+
return {
|
|
2775
|
+
response,
|
|
2776
|
+
context: contextResult.context,
|
|
2777
|
+
count: contextResult.count,
|
|
2778
|
+
extracted: captureResult.extracted
|
|
2779
|
+
};
|
|
2410
2780
|
}
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
operation: "search",
|
|
2417
|
-
body: {
|
|
2418
|
-
...params,
|
|
2419
|
-
project
|
|
2420
|
-
},
|
|
2421
|
-
idempotent: true
|
|
2422
|
-
});
|
|
2423
|
-
return response.data;
|
|
2781
|
+
/**
|
|
2782
|
+
* Direct access to WhisperContext for advanced usage
|
|
2783
|
+
*/
|
|
2784
|
+
raw() {
|
|
2785
|
+
return this.client;
|
|
2424
2786
|
}
|
|
2425
|
-
|
|
2426
|
-
const
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
operation: "session",
|
|
2431
|
-
body: {
|
|
2432
|
-
...params,
|
|
2433
|
-
project
|
|
2787
|
+
extractMemoryIdsFromBulkResponse(bulkResponse) {
|
|
2788
|
+
const ids = [];
|
|
2789
|
+
if (Array.isArray(bulkResponse?.memories)) {
|
|
2790
|
+
for (const memory of bulkResponse.memories) {
|
|
2791
|
+
if (memory?.id) ids.push(memory.id);
|
|
2434
2792
|
}
|
|
2435
|
-
}
|
|
2436
|
-
|
|
2793
|
+
}
|
|
2794
|
+
if (bulkResponse?.memory?.id) {
|
|
2795
|
+
ids.push(bulkResponse.memory.id);
|
|
2796
|
+
}
|
|
2797
|
+
if (bulkResponse?.id) {
|
|
2798
|
+
ids.push(bulkResponse.id);
|
|
2799
|
+
}
|
|
2800
|
+
return Array.from(new Set(ids));
|
|
2437
2801
|
}
|
|
2438
|
-
|
|
2439
|
-
const
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
searchMemories: (params) => this.memory.search(params),
|
|
2457
|
-
addMemory: (params) => this.memory.add(params),
|
|
2458
|
-
queueStatus: () => this.queue.status(),
|
|
2459
|
-
flushQueue: () => this.queue.flush()
|
|
2802
|
+
async fallbackCaptureViaAddMemory(messages, options) {
|
|
2803
|
+
const userMessages = messages.filter((m) => m.role === "user").map((m) => (m.content || "").trim()).filter((content) => content.length >= 5).slice(-2);
|
|
2804
|
+
if (userMessages.length === 0) {
|
|
2805
|
+
return { success: false, extracted: 0 };
|
|
2806
|
+
}
|
|
2807
|
+
let extracted = 0;
|
|
2808
|
+
for (const content of userMessages) {
|
|
2809
|
+
try {
|
|
2810
|
+
await this.client.addMemory({
|
|
2811
|
+
project: options?.project ?? this.options.project,
|
|
2812
|
+
content,
|
|
2813
|
+
memory_type: "factual",
|
|
2814
|
+
user_id: options?.userId ?? this.userId,
|
|
2815
|
+
session_id: options?.sessionId ?? this.sessionId,
|
|
2816
|
+
allow_legacy_fallback: true
|
|
2817
|
+
});
|
|
2818
|
+
extracted += 1;
|
|
2819
|
+
} catch {
|
|
2460
2820
|
}
|
|
2461
|
-
}
|
|
2462
|
-
|
|
2463
|
-
withRunContext(context) {
|
|
2464
|
-
const base = this;
|
|
2465
|
-
return {
|
|
2466
|
-
memory: {
|
|
2467
|
-
add: (params) => base.memory.add({
|
|
2468
|
-
...params,
|
|
2469
|
-
project: params.project || context.project || base.config.project,
|
|
2470
|
-
user_id: params.user_id || context.userId,
|
|
2471
|
-
session_id: params.session_id || context.sessionId
|
|
2472
|
-
}),
|
|
2473
|
-
search: (params) => base.memory.search({
|
|
2474
|
-
...params,
|
|
2475
|
-
project: params.project || context.project || base.config.project,
|
|
2476
|
-
user_id: params.user_id || context.userId,
|
|
2477
|
-
session_id: params.session_id || context.sessionId
|
|
2478
|
-
})
|
|
2479
|
-
},
|
|
2480
|
-
session: {
|
|
2481
|
-
event: (params) => base.session.event({
|
|
2482
|
-
...params,
|
|
2483
|
-
sessionId: params.sessionId || context.sessionId || ""
|
|
2484
|
-
})
|
|
2485
|
-
},
|
|
2486
|
-
queue: base.queue,
|
|
2487
|
-
diagnostics: base.diagnostics
|
|
2488
|
-
};
|
|
2489
|
-
}
|
|
2490
|
-
async shutdown() {
|
|
2491
|
-
await this.writeQueue.stop();
|
|
2821
|
+
}
|
|
2822
|
+
return { success: extracted > 0, extracted };
|
|
2492
2823
|
}
|
|
2493
2824
|
};
|
|
2494
|
-
var
|
|
2825
|
+
var whisper_agent_default = Whisper;
|
|
2495
2826
|
|
|
2496
2827
|
// ../src/sdk/middleware.ts
|
|
2497
2828
|
var WhisperAgentMiddleware = class {
|