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