@usewhisper/mcp-server 2.3.0 → 2.5.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/dist/server.js +2068 -146
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -81,6 +81,9 @@ function stableHash(input) {
|
|
|
81
81
|
}
|
|
82
82
|
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
83
83
|
}
|
|
84
|
+
function normalizeQuery(query) {
|
|
85
|
+
return query.trim().toLowerCase().replace(/\s+/g, " ");
|
|
86
|
+
}
|
|
84
87
|
function randomId(prefix = "id") {
|
|
85
88
|
return `${prefix}_${stableHash(`${Date.now()}_${Math.random()}`)}`;
|
|
86
89
|
}
|
|
@@ -263,129 +266,1957 @@ var RuntimeClient = class {
|
|
|
263
266
|
return cloned;
|
|
264
267
|
}
|
|
265
268
|
}
|
|
266
|
-
const runner = this.performRequest(options).then((data) => {
|
|
267
|
-
if (dedupeKey) this.inFlight.delete(dedupeKey);
|
|
268
|
-
return data;
|
|
269
|
-
}).catch((error) => {
|
|
270
|
-
if (dedupeKey) this.inFlight.delete(dedupeKey);
|
|
271
|
-
throw error;
|
|
269
|
+
const runner = this.performRequest(options).then((data) => {
|
|
270
|
+
if (dedupeKey) this.inFlight.delete(dedupeKey);
|
|
271
|
+
return data;
|
|
272
|
+
}).catch((error) => {
|
|
273
|
+
if (dedupeKey) this.inFlight.delete(dedupeKey);
|
|
274
|
+
throw error;
|
|
275
|
+
});
|
|
276
|
+
if (dedupeKey) {
|
|
277
|
+
this.inFlight.set(dedupeKey, runner);
|
|
278
|
+
}
|
|
279
|
+
return runner;
|
|
280
|
+
}
|
|
281
|
+
async performRequest(options) {
|
|
282
|
+
const method = options.method || "GET";
|
|
283
|
+
const normalizedEndpoint = normalizeEndpoint(options.endpoint);
|
|
284
|
+
const operation = options.operation;
|
|
285
|
+
const maxAttempts = this.maxAttemptsFor(operation);
|
|
286
|
+
const timeoutMs = this.timeoutFor(operation);
|
|
287
|
+
const traceId = options.traceId || randomId("trace");
|
|
288
|
+
let lastError = null;
|
|
289
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
290
|
+
const spanId = randomId("span");
|
|
291
|
+
const startedAt = Date.now();
|
|
292
|
+
const controller = new AbortController();
|
|
293
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
294
|
+
try {
|
|
295
|
+
const attachApiKeyHeader = this.shouldAttachApiKeyHeader(normalizedEndpoint);
|
|
296
|
+
const response = await this.fetchImpl(`${this.baseUrl}${normalizedEndpoint}`, {
|
|
297
|
+
method,
|
|
298
|
+
signal: controller.signal,
|
|
299
|
+
keepalive: method !== "GET",
|
|
300
|
+
headers: {
|
|
301
|
+
"Content-Type": "application/json",
|
|
302
|
+
Authorization: this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`,
|
|
303
|
+
...attachApiKeyHeader ? { "X-API-Key": this.apiKey.replace(/^Bearer\s+/i, "") } : {},
|
|
304
|
+
"x-trace-id": traceId,
|
|
305
|
+
"x-span-id": spanId,
|
|
306
|
+
"x-sdk-version": this.sdkVersion,
|
|
307
|
+
"x-sdk-runtime": this.runtimeName(),
|
|
308
|
+
...options.headers || {}
|
|
309
|
+
},
|
|
310
|
+
body: method === "GET" || method === "DELETE" ? void 0 : JSON.stringify(options.body || {})
|
|
311
|
+
});
|
|
312
|
+
clearTimeout(timeout);
|
|
313
|
+
let payload = null;
|
|
314
|
+
try {
|
|
315
|
+
payload = await response.json();
|
|
316
|
+
} catch {
|
|
317
|
+
payload = await response.text().catch(() => "");
|
|
318
|
+
}
|
|
319
|
+
const durationMs = Date.now() - startedAt;
|
|
320
|
+
const record = {
|
|
321
|
+
id: randomId("diag"),
|
|
322
|
+
startedAt: new Date(startedAt).toISOString(),
|
|
323
|
+
endedAt: nowIso(),
|
|
324
|
+
traceId,
|
|
325
|
+
spanId,
|
|
326
|
+
operation,
|
|
327
|
+
method,
|
|
328
|
+
endpoint: normalizedEndpoint,
|
|
329
|
+
status: response.status,
|
|
330
|
+
durationMs,
|
|
331
|
+
success: response.ok
|
|
332
|
+
};
|
|
333
|
+
this.diagnostics.add(record);
|
|
334
|
+
if (response.ok) {
|
|
335
|
+
return {
|
|
336
|
+
data: payload,
|
|
337
|
+
status: response.status,
|
|
338
|
+
traceId
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
const message = toMessage(payload, response.status, response.statusText);
|
|
342
|
+
const retryable = this.shouldRetryStatus(response.status);
|
|
343
|
+
const error = new RuntimeClientError({
|
|
344
|
+
message,
|
|
345
|
+
status: response.status,
|
|
346
|
+
retryable,
|
|
347
|
+
code: response.status === 404 ? "NOT_FOUND" : "REQUEST_FAILED",
|
|
348
|
+
details: payload,
|
|
349
|
+
traceId
|
|
350
|
+
});
|
|
351
|
+
lastError = error;
|
|
352
|
+
if (!retryable || attempt === maxAttempts - 1) {
|
|
353
|
+
throw error;
|
|
354
|
+
}
|
|
355
|
+
} catch (error) {
|
|
356
|
+
clearTimeout(timeout);
|
|
357
|
+
const durationMs = Date.now() - startedAt;
|
|
358
|
+
const isAbort = isObject(error) && error.name === "AbortError";
|
|
359
|
+
const mapped = error instanceof RuntimeClientError ? error : new RuntimeClientError({
|
|
360
|
+
message: isAbort ? "Request timed out" : error instanceof Error ? error.message : "Network error",
|
|
361
|
+
retryable: this.retryPolicy.retryOnNetworkError ?? true,
|
|
362
|
+
code: isAbort ? "TIMEOUT" : "NETWORK_ERROR",
|
|
363
|
+
traceId
|
|
364
|
+
});
|
|
365
|
+
lastError = mapped;
|
|
366
|
+
this.diagnostics.add({
|
|
367
|
+
id: randomId("diag"),
|
|
368
|
+
startedAt: new Date(startedAt).toISOString(),
|
|
369
|
+
endedAt: nowIso(),
|
|
370
|
+
traceId,
|
|
371
|
+
spanId,
|
|
372
|
+
operation,
|
|
373
|
+
method,
|
|
374
|
+
endpoint: normalizedEndpoint,
|
|
375
|
+
durationMs,
|
|
376
|
+
success: false,
|
|
377
|
+
errorCode: mapped.code,
|
|
378
|
+
errorMessage: mapped.message
|
|
379
|
+
});
|
|
380
|
+
if (!mapped.retryable || attempt === maxAttempts - 1) {
|
|
381
|
+
throw mapped;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
await new Promise((resolve) => setTimeout(resolve, this.backoff(attempt)));
|
|
385
|
+
}
|
|
386
|
+
throw lastError || new RuntimeClientError({
|
|
387
|
+
message: "Request failed",
|
|
388
|
+
retryable: false,
|
|
389
|
+
code: "REQUEST_FAILED"
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
// ../src/sdk/core/cache.ts
|
|
395
|
+
var SearchResponseCache = class {
|
|
396
|
+
ttlMs;
|
|
397
|
+
capacity;
|
|
398
|
+
byKey = /* @__PURE__ */ new Map();
|
|
399
|
+
scopeIndex = /* @__PURE__ */ new Map();
|
|
400
|
+
constructor(ttlMs = 7e3, capacity = 500) {
|
|
401
|
+
this.ttlMs = Math.max(1e3, ttlMs);
|
|
402
|
+
this.capacity = Math.max(10, capacity);
|
|
403
|
+
}
|
|
404
|
+
makeScopeKey(project, userId, sessionId) {
|
|
405
|
+
return `${project}:${userId || "_"}:${sessionId || "_"}`;
|
|
406
|
+
}
|
|
407
|
+
makeKey(input) {
|
|
408
|
+
const normalized = {
|
|
409
|
+
project: input.project,
|
|
410
|
+
userId: input.userId || "",
|
|
411
|
+
sessionId: input.sessionId || "",
|
|
412
|
+
query: normalizeQuery(input.query),
|
|
413
|
+
topK: input.topK,
|
|
414
|
+
profile: input.profile,
|
|
415
|
+
includePending: input.includePending
|
|
416
|
+
};
|
|
417
|
+
return `search:${stableHash(JSON.stringify(normalized))}`;
|
|
418
|
+
}
|
|
419
|
+
get(key) {
|
|
420
|
+
const found = this.byKey.get(key);
|
|
421
|
+
if (!found) return null;
|
|
422
|
+
if (found.expiresAt <= Date.now()) {
|
|
423
|
+
this.deleteByKey(key);
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
found.touchedAt = Date.now();
|
|
427
|
+
return found.value;
|
|
428
|
+
}
|
|
429
|
+
set(key, scopeKey, value) {
|
|
430
|
+
this.byKey.set(key, {
|
|
431
|
+
value,
|
|
432
|
+
scopeKey,
|
|
433
|
+
touchedAt: Date.now(),
|
|
434
|
+
expiresAt: Date.now() + this.ttlMs
|
|
435
|
+
});
|
|
436
|
+
if (!this.scopeIndex.has(scopeKey)) {
|
|
437
|
+
this.scopeIndex.set(scopeKey, /* @__PURE__ */ new Set());
|
|
438
|
+
}
|
|
439
|
+
this.scopeIndex.get(scopeKey).add(key);
|
|
440
|
+
this.evictIfNeeded();
|
|
441
|
+
}
|
|
442
|
+
invalidateScope(scopeKey) {
|
|
443
|
+
const keys = this.scopeIndex.get(scopeKey);
|
|
444
|
+
if (!keys || keys.size === 0) {
|
|
445
|
+
return 0;
|
|
446
|
+
}
|
|
447
|
+
const toDelete = Array.from(keys);
|
|
448
|
+
for (const key of toDelete) {
|
|
449
|
+
this.deleteByKey(key);
|
|
450
|
+
}
|
|
451
|
+
this.scopeIndex.delete(scopeKey);
|
|
452
|
+
return toDelete.length;
|
|
453
|
+
}
|
|
454
|
+
evictIfNeeded() {
|
|
455
|
+
if (this.byKey.size <= this.capacity) return;
|
|
456
|
+
const ordered = Array.from(this.byKey.entries()).sort((a, b) => a[1].touchedAt - b[1].touchedAt);
|
|
457
|
+
const removeCount = this.byKey.size - this.capacity;
|
|
458
|
+
for (let i = 0; i < removeCount; i += 1) {
|
|
459
|
+
this.deleteByKey(ordered[i][0]);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
deleteByKey(key) {
|
|
463
|
+
const found = this.byKey.get(key);
|
|
464
|
+
if (!found) return;
|
|
465
|
+
this.byKey.delete(key);
|
|
466
|
+
const scopeKeys = this.scopeIndex.get(found.scopeKey);
|
|
467
|
+
if (!scopeKeys) return;
|
|
468
|
+
scopeKeys.delete(key);
|
|
469
|
+
if (scopeKeys.size === 0) {
|
|
470
|
+
this.scopeIndex.delete(found.scopeKey);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
// ../src/sdk/core/queue.ts
|
|
476
|
+
var InMemoryQueueStore = class {
|
|
477
|
+
items = [];
|
|
478
|
+
async load() {
|
|
479
|
+
return [...this.items];
|
|
480
|
+
}
|
|
481
|
+
async save(items) {
|
|
482
|
+
this.items = [...items];
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
var WriteQueue = class {
|
|
486
|
+
flushHandler;
|
|
487
|
+
store;
|
|
488
|
+
maxBatchSize;
|
|
489
|
+
flushIntervalMs;
|
|
490
|
+
maxAttempts;
|
|
491
|
+
queue = [];
|
|
492
|
+
flushTimer = null;
|
|
493
|
+
flushing = false;
|
|
494
|
+
lastFlushAt;
|
|
495
|
+
lastFlushCount = 0;
|
|
496
|
+
constructor(args) {
|
|
497
|
+
this.flushHandler = args.flushHandler;
|
|
498
|
+
this.store = args.store || new InMemoryQueueStore();
|
|
499
|
+
this.maxBatchSize = Math.max(1, args.maxBatchSize ?? 50);
|
|
500
|
+
this.flushIntervalMs = Math.max(10, args.flushIntervalMs ?? 100);
|
|
501
|
+
this.maxAttempts = Math.max(1, args.maxAttempts ?? 2);
|
|
502
|
+
}
|
|
503
|
+
async start() {
|
|
504
|
+
const pending = await this.store.load();
|
|
505
|
+
if (pending.length > 0) {
|
|
506
|
+
this.queue.push(...pending);
|
|
507
|
+
}
|
|
508
|
+
if (!this.flushTimer) {
|
|
509
|
+
this.flushTimer = setInterval(() => {
|
|
510
|
+
void this.flush();
|
|
511
|
+
}, this.flushIntervalMs);
|
|
512
|
+
const timer = this.flushTimer;
|
|
513
|
+
if (typeof timer.unref === "function") {
|
|
514
|
+
timer.unref();
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
this.bindProcessHooks();
|
|
518
|
+
}
|
|
519
|
+
async stop() {
|
|
520
|
+
if (this.flushTimer) {
|
|
521
|
+
clearInterval(this.flushTimer);
|
|
522
|
+
this.flushTimer = null;
|
|
523
|
+
}
|
|
524
|
+
await this.flush();
|
|
525
|
+
}
|
|
526
|
+
status() {
|
|
527
|
+
return {
|
|
528
|
+
queued: this.queue.length,
|
|
529
|
+
flushing: this.flushing,
|
|
530
|
+
lastFlushAt: this.lastFlushAt,
|
|
531
|
+
lastFlushCount: this.lastFlushCount
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
async enqueue(input) {
|
|
535
|
+
const eventId = input.eventId || this.makeEventId(input);
|
|
536
|
+
const item = {
|
|
537
|
+
...input,
|
|
538
|
+
eventId,
|
|
539
|
+
createdAt: nowIso()
|
|
540
|
+
};
|
|
541
|
+
this.queue.push(item);
|
|
542
|
+
await this.store.save(this.queue);
|
|
543
|
+
if (this.queue.length >= this.maxBatchSize) {
|
|
544
|
+
void this.flush();
|
|
545
|
+
}
|
|
546
|
+
return item;
|
|
547
|
+
}
|
|
548
|
+
async flush() {
|
|
549
|
+
if (this.flushing || this.queue.length === 0) return;
|
|
550
|
+
this.flushing = true;
|
|
551
|
+
try {
|
|
552
|
+
while (this.queue.length > 0) {
|
|
553
|
+
const batch = this.queue.slice(0, this.maxBatchSize);
|
|
554
|
+
let done = false;
|
|
555
|
+
let error = null;
|
|
556
|
+
for (let attempt = 0; attempt < this.maxAttempts; attempt += 1) {
|
|
557
|
+
try {
|
|
558
|
+
await this.flushHandler(batch);
|
|
559
|
+
done = true;
|
|
560
|
+
break;
|
|
561
|
+
} catch (err) {
|
|
562
|
+
error = err;
|
|
563
|
+
await new Promise((resolve) => setTimeout(resolve, (attempt + 1) * 180));
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
if (!done) {
|
|
567
|
+
throw error instanceof Error ? error : new Error("Queue flush failed");
|
|
568
|
+
}
|
|
569
|
+
this.queue.splice(0, batch.length);
|
|
570
|
+
this.lastFlushAt = nowIso();
|
|
571
|
+
this.lastFlushCount = batch.length;
|
|
572
|
+
await this.store.save(this.queue);
|
|
573
|
+
}
|
|
574
|
+
} finally {
|
|
575
|
+
this.flushing = false;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
makeEventId(input) {
|
|
579
|
+
const source = JSON.stringify({
|
|
580
|
+
project: input.project,
|
|
581
|
+
userId: input.userId || "",
|
|
582
|
+
sessionId: input.sessionId || "",
|
|
583
|
+
payload: input.payload
|
|
584
|
+
});
|
|
585
|
+
return `evt_${stableHash(source)}`;
|
|
586
|
+
}
|
|
587
|
+
bindProcessHooks() {
|
|
588
|
+
if (typeof process === "undefined") return;
|
|
589
|
+
const proc = process;
|
|
590
|
+
const flushOnExit = () => {
|
|
591
|
+
void this.flush();
|
|
592
|
+
};
|
|
593
|
+
proc.once("beforeExit", flushOnExit);
|
|
594
|
+
proc.once("SIGINT", flushOnExit);
|
|
595
|
+
proc.once("SIGTERM", flushOnExit);
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
function createStorageQueueStore(key = "whisper_sdk_queue") {
|
|
599
|
+
const getStorage = () => {
|
|
600
|
+
const maybeStorage = globalThis.localStorage;
|
|
601
|
+
if (!maybeStorage || typeof maybeStorage !== "object") return null;
|
|
602
|
+
const candidate = maybeStorage;
|
|
603
|
+
if (typeof candidate.getItem !== "function" || typeof candidate.setItem !== "function") {
|
|
604
|
+
return null;
|
|
605
|
+
}
|
|
606
|
+
return {
|
|
607
|
+
getItem: candidate.getItem,
|
|
608
|
+
setItem: candidate.setItem
|
|
609
|
+
};
|
|
610
|
+
};
|
|
611
|
+
return {
|
|
612
|
+
async load() {
|
|
613
|
+
const storage = getStorage();
|
|
614
|
+
if (!storage) return [];
|
|
615
|
+
const raw = storage.getItem(key);
|
|
616
|
+
if (!raw) return [];
|
|
617
|
+
try {
|
|
618
|
+
const parsed = JSON.parse(raw);
|
|
619
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
620
|
+
} catch {
|
|
621
|
+
return [];
|
|
622
|
+
}
|
|
623
|
+
},
|
|
624
|
+
async save(items) {
|
|
625
|
+
const storage = getStorage();
|
|
626
|
+
if (!storage) return;
|
|
627
|
+
storage.setItem(key, JSON.stringify(items));
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
function createFileQueueStore(filePath) {
|
|
632
|
+
return {
|
|
633
|
+
async load() {
|
|
634
|
+
if (typeof process === "undefined") return [];
|
|
635
|
+
const fs = await import("fs/promises");
|
|
636
|
+
try {
|
|
637
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
638
|
+
const parsed = JSON.parse(raw);
|
|
639
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
640
|
+
} catch (error) {
|
|
641
|
+
const nodeError = error;
|
|
642
|
+
if (nodeError?.code === "ENOENT") {
|
|
643
|
+
return [];
|
|
644
|
+
}
|
|
645
|
+
return [];
|
|
646
|
+
}
|
|
647
|
+
},
|
|
648
|
+
async save(items) {
|
|
649
|
+
if (typeof process === "undefined") return;
|
|
650
|
+
const fs = await import("fs/promises");
|
|
651
|
+
const path = await import("path");
|
|
652
|
+
const dir = path.dirname(filePath);
|
|
653
|
+
await fs.mkdir(dir, { recursive: true });
|
|
654
|
+
await fs.writeFile(filePath, JSON.stringify(items), "utf8");
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// ../src/sdk/modules/memory.ts
|
|
660
|
+
function isEndpointNotFound(error) {
|
|
661
|
+
return error instanceof RuntimeClientError && error.status === 404;
|
|
662
|
+
}
|
|
663
|
+
function toSotaType(memoryType) {
|
|
664
|
+
if (!memoryType) return void 0;
|
|
665
|
+
switch (memoryType) {
|
|
666
|
+
case "episodic":
|
|
667
|
+
return "event";
|
|
668
|
+
case "semantic":
|
|
669
|
+
return "factual";
|
|
670
|
+
case "procedural":
|
|
671
|
+
return "instruction";
|
|
672
|
+
default:
|
|
673
|
+
return memoryType;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
function toLegacyType(memoryType) {
|
|
677
|
+
if (!memoryType) return void 0;
|
|
678
|
+
switch (memoryType) {
|
|
679
|
+
case "event":
|
|
680
|
+
return "episodic";
|
|
681
|
+
case "instruction":
|
|
682
|
+
return "procedural";
|
|
683
|
+
case "preference":
|
|
684
|
+
case "relationship":
|
|
685
|
+
case "opinion":
|
|
686
|
+
case "goal":
|
|
687
|
+
return "semantic";
|
|
688
|
+
default:
|
|
689
|
+
return memoryType;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
var MemoryModule = class {
|
|
693
|
+
constructor(client, cache, queue, options = {}) {
|
|
694
|
+
this.client = client;
|
|
695
|
+
this.cache = cache;
|
|
696
|
+
this.queue = queue;
|
|
697
|
+
this.options = options;
|
|
698
|
+
}
|
|
699
|
+
resolveProject(project) {
|
|
700
|
+
const value = project || this.options.defaultProject;
|
|
701
|
+
if (!value) {
|
|
702
|
+
throw new RuntimeClientError({
|
|
703
|
+
code: "MISSING_PROJECT",
|
|
704
|
+
message: "Project is required",
|
|
705
|
+
retryable: false
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
return value;
|
|
709
|
+
}
|
|
710
|
+
invalidate(project, userId, sessionId) {
|
|
711
|
+
if (this.options.cacheEnabled === false) {
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
const scope = this.cache.makeScopeKey(project, userId, sessionId);
|
|
715
|
+
this.cache.invalidateScope(scope);
|
|
716
|
+
}
|
|
717
|
+
async add(params) {
|
|
718
|
+
const project = this.resolveProject(params.project);
|
|
719
|
+
const queueEnabled = this.options.queueEnabled !== false;
|
|
720
|
+
const useQueue = queueEnabled && (params.write_mode === "async" || params.async === true);
|
|
721
|
+
if (useQueue) {
|
|
722
|
+
const queued = await this.queue.enqueue({
|
|
723
|
+
project,
|
|
724
|
+
userId: params.user_id,
|
|
725
|
+
sessionId: params.session_id,
|
|
726
|
+
payload: {
|
|
727
|
+
content: params.content,
|
|
728
|
+
memory_type: toSotaType(params.memory_type),
|
|
729
|
+
user_id: params.user_id,
|
|
730
|
+
session_id: params.session_id,
|
|
731
|
+
agent_id: params.agent_id,
|
|
732
|
+
importance: params.importance,
|
|
733
|
+
confidence: params.confidence,
|
|
734
|
+
metadata: params.metadata,
|
|
735
|
+
document_date: params.document_date,
|
|
736
|
+
event_date: params.event_date
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
this.invalidate(project, params.user_id, params.session_id);
|
|
740
|
+
return {
|
|
741
|
+
success: true,
|
|
742
|
+
mode: "async",
|
|
743
|
+
queued: true,
|
|
744
|
+
event_id: queued.eventId,
|
|
745
|
+
accepted_at: queued.createdAt
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
try {
|
|
749
|
+
const response = await this.client.request({
|
|
750
|
+
endpoint: "/v1/memory",
|
|
751
|
+
method: "POST",
|
|
752
|
+
operation: "writeAck",
|
|
753
|
+
body: {
|
|
754
|
+
project,
|
|
755
|
+
content: params.content,
|
|
756
|
+
memory_type: toSotaType(params.memory_type),
|
|
757
|
+
user_id: params.user_id,
|
|
758
|
+
session_id: params.session_id,
|
|
759
|
+
agent_id: params.agent_id,
|
|
760
|
+
importance: params.importance,
|
|
761
|
+
confidence: params.confidence,
|
|
762
|
+
metadata: params.metadata,
|
|
763
|
+
document_date: params.document_date,
|
|
764
|
+
event_date: params.event_date,
|
|
765
|
+
write_mode: params.write_mode === "async" || params.async === true ? "async" : "sync"
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
this.invalidate(project, params.user_id, params.session_id);
|
|
769
|
+
return {
|
|
770
|
+
success: true,
|
|
771
|
+
mode: "sync",
|
|
772
|
+
trace_id: response.trace_id || response.traceId,
|
|
773
|
+
memory_id: response.memory_id || response.memory?.id,
|
|
774
|
+
semantic_status: response.semantic_status || response.memory?.semantic_status,
|
|
775
|
+
pending_visibility: Boolean(response.pending_visibility),
|
|
776
|
+
visibility_sla_ms: response.visibility_sla_ms
|
|
777
|
+
};
|
|
778
|
+
} catch (error) {
|
|
779
|
+
if (this.client.getCompatMode() !== "fallback" || !isEndpointNotFound(error)) {
|
|
780
|
+
throw error;
|
|
781
|
+
}
|
|
782
|
+
await this.client.request({
|
|
783
|
+
endpoint: "/v1/memories",
|
|
784
|
+
method: "POST",
|
|
785
|
+
operation: "writeAck",
|
|
786
|
+
body: {
|
|
787
|
+
project,
|
|
788
|
+
content: params.content,
|
|
789
|
+
memory_type: toLegacyType(params.memory_type),
|
|
790
|
+
user_id: params.user_id,
|
|
791
|
+
session_id: params.session_id,
|
|
792
|
+
agent_id: params.agent_id,
|
|
793
|
+
importance: params.importance,
|
|
794
|
+
metadata: params.metadata
|
|
795
|
+
}
|
|
796
|
+
});
|
|
797
|
+
this.invalidate(project, params.user_id, params.session_id);
|
|
798
|
+
return {
|
|
799
|
+
success: true,
|
|
800
|
+
mode: "sync"
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
async addBulk(params) {
|
|
805
|
+
const project = this.resolveProject(params.project);
|
|
806
|
+
if (!Array.isArray(params.memories) || params.memories.length === 0) {
|
|
807
|
+
throw new RuntimeClientError({
|
|
808
|
+
code: "VALIDATION_ERROR",
|
|
809
|
+
message: "memories is required",
|
|
810
|
+
retryable: false
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
const queueEnabled = this.options.queueEnabled !== false;
|
|
814
|
+
const useQueue = queueEnabled && (params.write_mode === "async" || params.async === true);
|
|
815
|
+
if (useQueue) {
|
|
816
|
+
const queued = await Promise.all(
|
|
817
|
+
params.memories.map(
|
|
818
|
+
(memory) => this.queue.enqueue({
|
|
819
|
+
project,
|
|
820
|
+
userId: memory.user_id,
|
|
821
|
+
sessionId: memory.session_id,
|
|
822
|
+
payload: {
|
|
823
|
+
content: memory.content,
|
|
824
|
+
memory_type: toSotaType(memory.memory_type),
|
|
825
|
+
user_id: memory.user_id,
|
|
826
|
+
session_id: memory.session_id,
|
|
827
|
+
agent_id: memory.agent_id,
|
|
828
|
+
importance: memory.importance,
|
|
829
|
+
confidence: memory.confidence,
|
|
830
|
+
metadata: memory.metadata,
|
|
831
|
+
document_date: memory.document_date,
|
|
832
|
+
event_date: memory.event_date
|
|
833
|
+
}
|
|
834
|
+
})
|
|
835
|
+
)
|
|
836
|
+
);
|
|
837
|
+
for (const memory of params.memories) {
|
|
838
|
+
this.invalidate(project, memory.user_id, memory.session_id);
|
|
839
|
+
}
|
|
840
|
+
return {
|
|
841
|
+
success: true,
|
|
842
|
+
mode: "async",
|
|
843
|
+
queued: true,
|
|
844
|
+
created: queued.length
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
try {
|
|
848
|
+
const response = await this.client.request({
|
|
849
|
+
endpoint: "/v1/memory/bulk",
|
|
850
|
+
method: "POST",
|
|
851
|
+
operation: "bulk",
|
|
852
|
+
body: {
|
|
853
|
+
project,
|
|
854
|
+
memories: params.memories.map((memory) => ({
|
|
855
|
+
...memory,
|
|
856
|
+
memory_type: toSotaType(memory.memory_type)
|
|
857
|
+
})),
|
|
858
|
+
write_mode: params.write_mode === "async" || params.async === true ? "async" : "sync"
|
|
859
|
+
}
|
|
860
|
+
});
|
|
861
|
+
for (const memory of params.memories) {
|
|
862
|
+
this.invalidate(project, memory.user_id, memory.session_id);
|
|
863
|
+
}
|
|
864
|
+
return {
|
|
865
|
+
success: true,
|
|
866
|
+
mode: "sync",
|
|
867
|
+
trace_id: response.traceId
|
|
868
|
+
};
|
|
869
|
+
} catch (error) {
|
|
870
|
+
if (this.client.getCompatMode() !== "fallback" || !isEndpointNotFound(error)) {
|
|
871
|
+
throw error;
|
|
872
|
+
}
|
|
873
|
+
await Promise.all(
|
|
874
|
+
params.memories.map(
|
|
875
|
+
(memory) => this.add({
|
|
876
|
+
project,
|
|
877
|
+
...memory,
|
|
878
|
+
write_mode: "sync"
|
|
879
|
+
})
|
|
880
|
+
)
|
|
881
|
+
);
|
|
882
|
+
return {
|
|
883
|
+
success: true,
|
|
884
|
+
mode: "sync"
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
async search(params) {
|
|
889
|
+
const project = this.resolveProject(params.project);
|
|
890
|
+
const topK = params.top_k || 10;
|
|
891
|
+
const profile = params.profile || "fast";
|
|
892
|
+
const includePending = params.include_pending !== false;
|
|
893
|
+
const cacheKey = this.cache.makeKey({
|
|
894
|
+
project,
|
|
895
|
+
userId: params.user_id,
|
|
896
|
+
sessionId: params.session_id,
|
|
897
|
+
query: params.query,
|
|
898
|
+
topK,
|
|
899
|
+
profile,
|
|
900
|
+
includePending
|
|
901
|
+
});
|
|
902
|
+
if (this.options.cacheEnabled !== false) {
|
|
903
|
+
const cached = this.cache.get(cacheKey);
|
|
904
|
+
if (cached) {
|
|
905
|
+
return {
|
|
906
|
+
...cached,
|
|
907
|
+
cache_hit: true
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
try {
|
|
912
|
+
const response = await this.client.request({
|
|
913
|
+
endpoint: "/v1/memory/search",
|
|
914
|
+
method: "POST",
|
|
915
|
+
operation: "search",
|
|
916
|
+
idempotent: true,
|
|
917
|
+
body: {
|
|
918
|
+
project,
|
|
919
|
+
query: params.query,
|
|
920
|
+
user_id: params.user_id,
|
|
921
|
+
session_id: params.session_id,
|
|
922
|
+
top_k: topK,
|
|
923
|
+
profile,
|
|
924
|
+
include_pending: includePending,
|
|
925
|
+
memory_types: params.memory_type ? [toSotaType(params.memory_type)] : void 0
|
|
926
|
+
}
|
|
927
|
+
});
|
|
928
|
+
const data = {
|
|
929
|
+
...response.data || {},
|
|
930
|
+
cache_hit: false
|
|
931
|
+
};
|
|
932
|
+
if (this.options.cacheEnabled !== false) {
|
|
933
|
+
const scope = this.cache.makeScopeKey(project, params.user_id, params.session_id);
|
|
934
|
+
this.cache.set(cacheKey, scope, data);
|
|
935
|
+
}
|
|
936
|
+
return data;
|
|
937
|
+
} catch (error) {
|
|
938
|
+
if (this.client.getCompatMode() !== "fallback" || !isEndpointNotFound(error)) {
|
|
939
|
+
throw error;
|
|
940
|
+
}
|
|
941
|
+
const legacy = await this.client.request({
|
|
942
|
+
endpoint: "/v1/memories/search",
|
|
943
|
+
method: "POST",
|
|
944
|
+
operation: "search",
|
|
945
|
+
idempotent: true,
|
|
946
|
+
body: {
|
|
947
|
+
project,
|
|
948
|
+
query: params.query,
|
|
949
|
+
user_id: params.user_id,
|
|
950
|
+
session_id: params.session_id,
|
|
951
|
+
top_k: topK,
|
|
952
|
+
memory_type: toLegacyType(params.memory_type)
|
|
953
|
+
}
|
|
954
|
+
});
|
|
955
|
+
const data = {
|
|
956
|
+
...legacy.data || {},
|
|
957
|
+
cache_hit: false
|
|
958
|
+
};
|
|
959
|
+
if (this.options.cacheEnabled !== false) {
|
|
960
|
+
const scope = this.cache.makeScopeKey(project, params.user_id, params.session_id);
|
|
961
|
+
this.cache.set(cacheKey, scope, data);
|
|
962
|
+
}
|
|
963
|
+
return data;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
async getUserProfile(params) {
|
|
967
|
+
const project = this.resolveProject(params.project);
|
|
968
|
+
const query = new URLSearchParams({
|
|
969
|
+
project,
|
|
970
|
+
...params.include_pending !== void 0 ? { include_pending: String(params.include_pending) } : {},
|
|
971
|
+
...params.memory_types ? { memory_types: params.memory_types } : {}
|
|
972
|
+
});
|
|
973
|
+
try {
|
|
974
|
+
const response = await this.client.request({
|
|
975
|
+
endpoint: `/v1/memory/profile/${params.user_id}?${query}`,
|
|
976
|
+
method: "GET",
|
|
977
|
+
operation: "profile",
|
|
978
|
+
idempotent: true
|
|
979
|
+
});
|
|
980
|
+
return response.data;
|
|
981
|
+
} catch (error) {
|
|
982
|
+
if (this.client.getCompatMode() !== "fallback" || !isEndpointNotFound(error)) {
|
|
983
|
+
throw error;
|
|
984
|
+
}
|
|
985
|
+
const legacyQuery = new URLSearchParams({
|
|
986
|
+
project,
|
|
987
|
+
user_id: params.user_id,
|
|
988
|
+
limit: "200"
|
|
989
|
+
});
|
|
990
|
+
const legacy = await this.client.request({
|
|
991
|
+
endpoint: `/v1/memories?${legacyQuery}`,
|
|
992
|
+
method: "GET",
|
|
993
|
+
operation: "profile",
|
|
994
|
+
idempotent: true
|
|
995
|
+
});
|
|
996
|
+
const memories = Array.isArray(legacy.data?.memories) ? legacy.data.memories : [];
|
|
997
|
+
return {
|
|
998
|
+
user_id: params.user_id,
|
|
999
|
+
memories,
|
|
1000
|
+
count: memories.length
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
async getSessionMemories(params) {
|
|
1005
|
+
const project = this.resolveProject(params.project);
|
|
1006
|
+
const query = new URLSearchParams({
|
|
1007
|
+
project,
|
|
1008
|
+
...params.limit ? { limit: String(params.limit) } : {},
|
|
1009
|
+
...params.include_pending !== void 0 ? { include_pending: String(params.include_pending) } : {}
|
|
1010
|
+
});
|
|
1011
|
+
const response = await this.client.request({
|
|
1012
|
+
endpoint: `/v1/memory/session/${params.session_id}?${query}`,
|
|
1013
|
+
method: "GET",
|
|
1014
|
+
operation: "profile",
|
|
1015
|
+
idempotent: true
|
|
1016
|
+
});
|
|
1017
|
+
return response.data;
|
|
1018
|
+
}
|
|
1019
|
+
async get(memoryId) {
|
|
1020
|
+
try {
|
|
1021
|
+
const response = await this.client.request({
|
|
1022
|
+
endpoint: `/v1/memory/${memoryId}`,
|
|
1023
|
+
method: "GET",
|
|
1024
|
+
operation: "get",
|
|
1025
|
+
idempotent: true
|
|
1026
|
+
});
|
|
1027
|
+
return response.data;
|
|
1028
|
+
} catch (error) {
|
|
1029
|
+
if (this.client.getCompatMode() !== "fallback" || !isEndpointNotFound(error)) {
|
|
1030
|
+
throw error;
|
|
1031
|
+
}
|
|
1032
|
+
const legacy = await this.client.request({
|
|
1033
|
+
endpoint: `/v1/memories/${memoryId}`,
|
|
1034
|
+
method: "GET",
|
|
1035
|
+
operation: "get",
|
|
1036
|
+
idempotent: true
|
|
1037
|
+
});
|
|
1038
|
+
return legacy.data;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
async update(memoryId, params) {
|
|
1042
|
+
try {
|
|
1043
|
+
await this.client.request({
|
|
1044
|
+
endpoint: `/v1/memory/${memoryId}`,
|
|
1045
|
+
method: "PUT",
|
|
1046
|
+
operation: "writeAck",
|
|
1047
|
+
body: params
|
|
1048
|
+
});
|
|
1049
|
+
return { success: true };
|
|
1050
|
+
} catch (error) {
|
|
1051
|
+
if (this.client.getCompatMode() !== "fallback" || !isEndpointNotFound(error)) {
|
|
1052
|
+
throw error;
|
|
1053
|
+
}
|
|
1054
|
+
await this.client.request({
|
|
1055
|
+
endpoint: `/v1/memories/${memoryId}`,
|
|
1056
|
+
method: "PUT",
|
|
1057
|
+
operation: "writeAck",
|
|
1058
|
+
body: { content: params.content }
|
|
1059
|
+
});
|
|
1060
|
+
return { success: true };
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
async delete(memoryId) {
|
|
1064
|
+
try {
|
|
1065
|
+
await this.client.request({
|
|
1066
|
+
endpoint: `/v1/memory/${memoryId}`,
|
|
1067
|
+
method: "DELETE",
|
|
1068
|
+
operation: "writeAck"
|
|
1069
|
+
});
|
|
1070
|
+
return { success: true, deleted: memoryId };
|
|
1071
|
+
} catch (error) {
|
|
1072
|
+
if (this.client.getCompatMode() !== "fallback" || !isEndpointNotFound(error)) {
|
|
1073
|
+
throw error;
|
|
1074
|
+
}
|
|
1075
|
+
await this.client.request({
|
|
1076
|
+
endpoint: `/v1/memories/${memoryId}`,
|
|
1077
|
+
method: "DELETE",
|
|
1078
|
+
operation: "writeAck"
|
|
1079
|
+
});
|
|
1080
|
+
return { success: true, deleted: memoryId };
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
async flag(params) {
|
|
1084
|
+
try {
|
|
1085
|
+
await this.client.request({
|
|
1086
|
+
endpoint: `/v1/memory/${params.memoryId}/flag`,
|
|
1087
|
+
method: "POST",
|
|
1088
|
+
operation: "writeAck",
|
|
1089
|
+
body: {
|
|
1090
|
+
reason: params.reason,
|
|
1091
|
+
severity: params.severity || "medium"
|
|
1092
|
+
}
|
|
1093
|
+
});
|
|
1094
|
+
return { success: true };
|
|
1095
|
+
} catch (error) {
|
|
1096
|
+
if (this.client.getCompatMode() !== "fallback" || !isEndpointNotFound(error)) {
|
|
1097
|
+
throw error;
|
|
1098
|
+
}
|
|
1099
|
+
await this.client.request({
|
|
1100
|
+
endpoint: `/v1/memory/${params.memoryId}`,
|
|
1101
|
+
method: "PUT",
|
|
1102
|
+
operation: "writeAck",
|
|
1103
|
+
body: {
|
|
1104
|
+
content: `[FLAGGED:${params.severity || "medium"}] ${params.reason}`
|
|
1105
|
+
}
|
|
1106
|
+
});
|
|
1107
|
+
return { success: true };
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
};
|
|
1111
|
+
|
|
1112
|
+
// ../src/sdk/modules/session.ts
|
|
1113
|
+
function randomSessionId() {
|
|
1114
|
+
return `sess_${stableHash(`${Date.now()}_${Math.random()}`)}`;
|
|
1115
|
+
}
|
|
1116
|
+
function assertTransition(current, next) {
|
|
1117
|
+
const allowed = {
|
|
1118
|
+
created: ["active", "ended", "archived"],
|
|
1119
|
+
active: ["suspended", "ended", "archived"],
|
|
1120
|
+
suspended: ["resumed", "ended", "archived"],
|
|
1121
|
+
resumed: ["suspended", "ended", "archived"],
|
|
1122
|
+
ended: ["archived"],
|
|
1123
|
+
archived: []
|
|
1124
|
+
};
|
|
1125
|
+
if (!allowed[current].includes(next)) {
|
|
1126
|
+
throw new RuntimeClientError({
|
|
1127
|
+
code: "INVALID_SESSION_STATE",
|
|
1128
|
+
message: `Invalid session transition ${current} -> ${next}`,
|
|
1129
|
+
retryable: false
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
var SessionModule = class {
|
|
1134
|
+
constructor(memory, defaultProject) {
|
|
1135
|
+
this.memory = memory;
|
|
1136
|
+
this.defaultProject = defaultProject;
|
|
1137
|
+
}
|
|
1138
|
+
sessions = /* @__PURE__ */ new Map();
|
|
1139
|
+
resolveProject(project) {
|
|
1140
|
+
const value = project || this.defaultProject;
|
|
1141
|
+
if (!value) {
|
|
1142
|
+
throw new RuntimeClientError({
|
|
1143
|
+
code: "MISSING_PROJECT",
|
|
1144
|
+
message: "Project is required",
|
|
1145
|
+
retryable: false
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
return value;
|
|
1149
|
+
}
|
|
1150
|
+
ensure(sessionId) {
|
|
1151
|
+
const found = this.sessions.get(sessionId);
|
|
1152
|
+
if (!found) {
|
|
1153
|
+
throw new RuntimeClientError({
|
|
1154
|
+
code: "SESSION_NOT_FOUND",
|
|
1155
|
+
message: `Unknown session ${sessionId}`,
|
|
1156
|
+
retryable: false
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
return found;
|
|
1160
|
+
}
|
|
1161
|
+
async start(params) {
|
|
1162
|
+
const project = this.resolveProject(params.project);
|
|
1163
|
+
const sessionId = params.sessionId || randomSessionId();
|
|
1164
|
+
const now = nowIso();
|
|
1165
|
+
const record = {
|
|
1166
|
+
sessionId,
|
|
1167
|
+
project,
|
|
1168
|
+
userId: params.userId,
|
|
1169
|
+
state: "active",
|
|
1170
|
+
sequence: 0,
|
|
1171
|
+
metadata: params.metadata,
|
|
1172
|
+
createdAt: now,
|
|
1173
|
+
updatedAt: now
|
|
1174
|
+
};
|
|
1175
|
+
this.sessions.set(sessionId, record);
|
|
1176
|
+
return {
|
|
1177
|
+
sessionId,
|
|
1178
|
+
state: record.state,
|
|
1179
|
+
createdAt: now
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
async event(params) {
|
|
1183
|
+
const session = this.ensure(params.sessionId);
|
|
1184
|
+
if (session.state !== "active" && session.state !== "resumed") {
|
|
1185
|
+
throw new RuntimeClientError({
|
|
1186
|
+
code: "INVALID_SESSION_STATE",
|
|
1187
|
+
message: `Cannot append event in ${session.state} state`,
|
|
1188
|
+
retryable: false
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
session.sequence += 1;
|
|
1192
|
+
session.updatedAt = nowIso();
|
|
1193
|
+
const eventId = `evt_${stableHash(JSON.stringify({
|
|
1194
|
+
sessionId: session.sessionId,
|
|
1195
|
+
seq: session.sequence,
|
|
1196
|
+
type: params.type,
|
|
1197
|
+
content: params.content,
|
|
1198
|
+
parent: params.parentEventId || ""
|
|
1199
|
+
}))}`;
|
|
1200
|
+
await this.memory.add({
|
|
1201
|
+
project: session.project,
|
|
1202
|
+
content: `${params.type}: ${params.content}`,
|
|
1203
|
+
memory_type: "event",
|
|
1204
|
+
user_id: session.userId,
|
|
1205
|
+
session_id: session.sessionId,
|
|
1206
|
+
metadata: {
|
|
1207
|
+
session_event: true,
|
|
1208
|
+
event_id: eventId,
|
|
1209
|
+
sequence: session.sequence,
|
|
1210
|
+
parent_event_id: params.parentEventId,
|
|
1211
|
+
...session.metadata,
|
|
1212
|
+
...params.metadata || {}
|
|
1213
|
+
},
|
|
1214
|
+
write_mode: "async"
|
|
1215
|
+
});
|
|
1216
|
+
return {
|
|
1217
|
+
success: true,
|
|
1218
|
+
eventId,
|
|
1219
|
+
sequence: session.sequence
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
async suspend(params) {
|
|
1223
|
+
const session = this.ensure(params.sessionId);
|
|
1224
|
+
assertTransition(session.state, "suspended");
|
|
1225
|
+
session.state = "suspended";
|
|
1226
|
+
session.updatedAt = nowIso();
|
|
1227
|
+
return { sessionId: session.sessionId, state: session.state };
|
|
1228
|
+
}
|
|
1229
|
+
async resume(params) {
|
|
1230
|
+
const session = this.ensure(params.sessionId);
|
|
1231
|
+
const target = session.state === "suspended" ? "resumed" : "active";
|
|
1232
|
+
assertTransition(session.state, target);
|
|
1233
|
+
session.state = target;
|
|
1234
|
+
session.updatedAt = nowIso();
|
|
1235
|
+
return { sessionId: session.sessionId, state: session.state };
|
|
1236
|
+
}
|
|
1237
|
+
async end(params) {
|
|
1238
|
+
const session = this.ensure(params.sessionId);
|
|
1239
|
+
assertTransition(session.state, "ended");
|
|
1240
|
+
session.state = "ended";
|
|
1241
|
+
session.updatedAt = nowIso();
|
|
1242
|
+
return { sessionId: session.sessionId, state: session.state };
|
|
1243
|
+
}
|
|
1244
|
+
async archive(params) {
|
|
1245
|
+
const session = this.ensure(params.sessionId);
|
|
1246
|
+
assertTransition(session.state, "archived");
|
|
1247
|
+
session.state = "archived";
|
|
1248
|
+
session.updatedAt = nowIso();
|
|
1249
|
+
return { sessionId: session.sessionId, state: session.state };
|
|
1250
|
+
}
|
|
1251
|
+
};
|
|
1252
|
+
|
|
1253
|
+
// ../src/sdk/modules/profile.ts
|
|
1254
|
+
var ProfileModule = class {
|
|
1255
|
+
constructor(memory) {
|
|
1256
|
+
this.memory = memory;
|
|
1257
|
+
}
|
|
1258
|
+
async getUserProfile(params) {
|
|
1259
|
+
return this.memory.getUserProfile(params);
|
|
1260
|
+
}
|
|
1261
|
+
async getSessionMemories(params) {
|
|
1262
|
+
return this.memory.getSessionMemories(params);
|
|
1263
|
+
}
|
|
1264
|
+
};
|
|
1265
|
+
|
|
1266
|
+
// ../src/sdk/modules/analytics.ts
|
|
1267
|
+
var AnalyticsModule = class {
|
|
1268
|
+
constructor(diagnostics, queue) {
|
|
1269
|
+
this.diagnostics = diagnostics;
|
|
1270
|
+
this.queue = queue;
|
|
1271
|
+
}
|
|
1272
|
+
diagnosticsSnapshot() {
|
|
1273
|
+
return this.diagnostics.snapshot();
|
|
1274
|
+
}
|
|
1275
|
+
queueStatus() {
|
|
1276
|
+
return this.queue.status();
|
|
1277
|
+
}
|
|
1278
|
+
};
|
|
1279
|
+
|
|
1280
|
+
// ../src/sdk/agent-runtime.ts
|
|
1281
|
+
function detectBrowserStorage() {
|
|
1282
|
+
const maybeStorage = globalThis.localStorage;
|
|
1283
|
+
if (!maybeStorage || typeof maybeStorage !== "object") return null;
|
|
1284
|
+
const candidate = maybeStorage;
|
|
1285
|
+
if (typeof candidate.getItem !== "function" || typeof candidate.setItem !== "function") {
|
|
1286
|
+
return null;
|
|
1287
|
+
}
|
|
1288
|
+
return {
|
|
1289
|
+
getItem: candidate.getItem,
|
|
1290
|
+
setItem: candidate.setItem
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
function createBindingStore(filePath) {
|
|
1294
|
+
const storage = detectBrowserStorage();
|
|
1295
|
+
if (storage) {
|
|
1296
|
+
const key = "whisper_agent_runtime_bindings";
|
|
1297
|
+
return {
|
|
1298
|
+
async load() {
|
|
1299
|
+
const raw = storage.getItem(key);
|
|
1300
|
+
if (!raw) return {};
|
|
1301
|
+
try {
|
|
1302
|
+
const parsed = JSON.parse(raw);
|
|
1303
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
1304
|
+
} catch {
|
|
1305
|
+
return {};
|
|
1306
|
+
}
|
|
1307
|
+
},
|
|
1308
|
+
async save(bindings) {
|
|
1309
|
+
storage.setItem(key, JSON.stringify(bindings));
|
|
1310
|
+
}
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1313
|
+
return {
|
|
1314
|
+
async load() {
|
|
1315
|
+
if (typeof process === "undefined") return {};
|
|
1316
|
+
const fs = await import("fs/promises");
|
|
1317
|
+
const path = filePath || `${process.env.USERPROFILE || process.env.HOME || "."}/.whisper/sdk/agent-bindings.json`;
|
|
1318
|
+
try {
|
|
1319
|
+
const raw = await fs.readFile(path, "utf8");
|
|
1320
|
+
const parsed = JSON.parse(raw);
|
|
1321
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
1322
|
+
} catch {
|
|
1323
|
+
return {};
|
|
1324
|
+
}
|
|
1325
|
+
},
|
|
1326
|
+
async save(bindings) {
|
|
1327
|
+
if (typeof process === "undefined") return;
|
|
1328
|
+
const fs = await import("fs/promises");
|
|
1329
|
+
const pathMod = await import("path");
|
|
1330
|
+
const path = filePath || `${process.env.USERPROFILE || process.env.HOME || "."}/.whisper/sdk/agent-bindings.json`;
|
|
1331
|
+
await fs.mkdir(pathMod.dirname(path), { recursive: true });
|
|
1332
|
+
await fs.writeFile(path, JSON.stringify(bindings), "utf8");
|
|
1333
|
+
}
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
function normalizeWorkspacePath(value) {
|
|
1337
|
+
const trimmed = value?.trim();
|
|
1338
|
+
if (!trimmed) return null;
|
|
1339
|
+
return trimmed.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
1340
|
+
}
|
|
1341
|
+
function pathBase(value) {
|
|
1342
|
+
const normalized = value.replace(/\\/g, "/");
|
|
1343
|
+
const parts = normalized.split("/").filter(Boolean);
|
|
1344
|
+
return parts[parts.length - 1] || normalized;
|
|
1345
|
+
}
|
|
1346
|
+
function defaultSalience(kind, success) {
|
|
1347
|
+
if (kind === "decision" || kind === "constraint" || kind === "failure") return "high";
|
|
1348
|
+
if (kind === "outcome" || kind === "task_update") return "medium";
|
|
1349
|
+
if (kind === "tool_result" && success === false) return "high";
|
|
1350
|
+
return "low";
|
|
1351
|
+
}
|
|
1352
|
+
function toMemoryType(kind) {
|
|
1353
|
+
if (kind === "decision" || kind === "constraint") return "instruction";
|
|
1354
|
+
return "event";
|
|
1355
|
+
}
|
|
1356
|
+
function summarizeLowSalience(events) {
|
|
1357
|
+
const lines = events.slice(-10).map((event) => {
|
|
1358
|
+
const fileSuffix = event.filePaths?.length ? ` [files: ${event.filePaths.join(", ")}]` : "";
|
|
1359
|
+
const toolSuffix = event.toolName ? ` [tool: ${event.toolName}]` : "";
|
|
1360
|
+
return `- ${event.kind}: ${event.summary}${fileSuffix}${toolSuffix}`;
|
|
1361
|
+
});
|
|
1362
|
+
return `Recent low-salience work:
|
|
1363
|
+
${lines.join("\n")}`;
|
|
1364
|
+
}
|
|
1365
|
+
function compactWhitespace(value) {
|
|
1366
|
+
return value.replace(/\s+/g, " ").trim();
|
|
1367
|
+
}
|
|
1368
|
+
function withTimeout(promise, timeoutMs) {
|
|
1369
|
+
return new Promise((resolve, reject) => {
|
|
1370
|
+
const timeout = setTimeout(() => {
|
|
1371
|
+
reject(new Error("timeout"));
|
|
1372
|
+
}, timeoutMs);
|
|
1373
|
+
promise.then(
|
|
1374
|
+
(value) => {
|
|
1375
|
+
clearTimeout(timeout);
|
|
1376
|
+
resolve(value);
|
|
1377
|
+
},
|
|
1378
|
+
(error) => {
|
|
1379
|
+
clearTimeout(timeout);
|
|
1380
|
+
reject(error);
|
|
1381
|
+
}
|
|
1382
|
+
);
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
function extractTimestamp(metadata) {
|
|
1386
|
+
const candidates = [
|
|
1387
|
+
metadata?.updatedAt,
|
|
1388
|
+
metadata?.createdAt,
|
|
1389
|
+
metadata?.timestamp,
|
|
1390
|
+
metadata?.event_date,
|
|
1391
|
+
metadata?.eventDate
|
|
1392
|
+
];
|
|
1393
|
+
for (const value of candidates) {
|
|
1394
|
+
if (typeof value === "string") {
|
|
1395
|
+
const parsed = Date.parse(value);
|
|
1396
|
+
if (!Number.isNaN(parsed)) return parsed;
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
return 0;
|
|
1400
|
+
}
|
|
1401
|
+
function salienceBoost(metadata) {
|
|
1402
|
+
const value = metadata?.salience;
|
|
1403
|
+
if (value === "high") return 0.12;
|
|
1404
|
+
if (value === "medium") return 0.06;
|
|
1405
|
+
return 0;
|
|
1406
|
+
}
|
|
1407
|
+
var WhisperAgentRuntime = class {
|
|
1408
|
+
constructor(args) {
|
|
1409
|
+
this.args = args;
|
|
1410
|
+
this.bindingStore = createBindingStore(args.options.bindingStorePath);
|
|
1411
|
+
this.topK = args.options.topK ?? 6;
|
|
1412
|
+
this.maxTokens = args.options.maxTokens ?? 4e3;
|
|
1413
|
+
this.targetRetrievalMs = args.options.targetRetrievalMs ?? 2500;
|
|
1414
|
+
this.hardRetrievalTimeoutMs = args.options.hardRetrievalTimeoutMs ?? 4e3;
|
|
1415
|
+
this.recentWorkLimit = args.options.recentWorkLimit ?? 40;
|
|
1416
|
+
this.baseContext = args.baseContext;
|
|
1417
|
+
this.clientName = args.baseContext.clientName || "whisper-agent-runtime";
|
|
1418
|
+
}
|
|
1419
|
+
bindingStore;
|
|
1420
|
+
topK;
|
|
1421
|
+
maxTokens;
|
|
1422
|
+
targetRetrievalMs;
|
|
1423
|
+
hardRetrievalTimeoutMs;
|
|
1424
|
+
recentWorkLimit;
|
|
1425
|
+
baseContext;
|
|
1426
|
+
clientName;
|
|
1427
|
+
bindings = null;
|
|
1428
|
+
touchedFiles = [];
|
|
1429
|
+
recentWork = [];
|
|
1430
|
+
bufferedLowSalience = [];
|
|
1431
|
+
lastPreparedTurn = null;
|
|
1432
|
+
mergedCount = 0;
|
|
1433
|
+
droppedCount = 0;
|
|
1434
|
+
lastScope = {};
|
|
1435
|
+
async getBindings() {
|
|
1436
|
+
if (!this.bindings) {
|
|
1437
|
+
this.bindings = await this.bindingStore.load();
|
|
1438
|
+
}
|
|
1439
|
+
return this.bindings;
|
|
1440
|
+
}
|
|
1441
|
+
pushTouchedFiles(paths) {
|
|
1442
|
+
if (!paths || paths.length === 0) return;
|
|
1443
|
+
for (const path of paths) {
|
|
1444
|
+
if (!path) continue;
|
|
1445
|
+
this.touchedFiles = [...this.touchedFiles.filter((entry) => entry !== path), path].slice(-20);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
pushWorkEvent(event) {
|
|
1449
|
+
this.recentWork = [...this.recentWork, event].slice(-this.recentWorkLimit);
|
|
1450
|
+
}
|
|
1451
|
+
makeTaskFrameQuery(input) {
|
|
1452
|
+
const task = compactWhitespace(input.taskSummary || "");
|
|
1453
|
+
const salient = this.recentWork.filter((event) => event.salience === "high").slice(-3).map((event) => `${event.kind}: ${event.summary}`);
|
|
1454
|
+
const files = [...input.touchedFiles || [], ...this.touchedFiles].slice(-3).map((file) => pathBase(file));
|
|
1455
|
+
const parts = [
|
|
1456
|
+
task ? `task ${task}` : "",
|
|
1457
|
+
salient.length > 0 ? `recent ${salient.join(" ; ")}` : "",
|
|
1458
|
+
files.length > 0 ? `files ${files.join(" ")}` : "",
|
|
1459
|
+
input.toolContext ? `tool context ${compactWhitespace(input.toolContext)}` : ""
|
|
1460
|
+
].filter(Boolean);
|
|
1461
|
+
if (parts.length === 0) return null;
|
|
1462
|
+
return parts.join(" | ");
|
|
1463
|
+
}
|
|
1464
|
+
async resolveScope(overrides) {
|
|
1465
|
+
const merged = {
|
|
1466
|
+
...this.baseContext,
|
|
1467
|
+
...overrides
|
|
1468
|
+
};
|
|
1469
|
+
const normalizedWorkspace = normalizeWorkspacePath(merged.workspacePath);
|
|
1470
|
+
const bindings = await this.getBindings();
|
|
1471
|
+
const workspaceProject = normalizedWorkspace ? bindings[normalizedWorkspace] : void 0;
|
|
1472
|
+
const configuredProject = merged.project;
|
|
1473
|
+
let projectRef = configuredProject;
|
|
1474
|
+
let projectSource = overrides?.project ? "explicit" : "generated";
|
|
1475
|
+
let warning;
|
|
1476
|
+
if (workspaceProject) {
|
|
1477
|
+
projectRef = workspaceProject;
|
|
1478
|
+
projectSource = "workspace";
|
|
1479
|
+
if (configuredProject && workspaceProject !== configuredProject) {
|
|
1480
|
+
warning = `workspace mapping '${workspaceProject}' overrides configured project '${configuredProject}'`;
|
|
1481
|
+
}
|
|
1482
|
+
} else if (configuredProject) {
|
|
1483
|
+
projectRef = configuredProject;
|
|
1484
|
+
projectSource = overrides?.project ? "explicit" : "config";
|
|
1485
|
+
}
|
|
1486
|
+
const project = (await this.args.adapter.resolveProject(projectRef)).id;
|
|
1487
|
+
if (normalizedWorkspace) {
|
|
1488
|
+
bindings[normalizedWorkspace] = project;
|
|
1489
|
+
await this.bindingStore.save(bindings);
|
|
1490
|
+
}
|
|
1491
|
+
const scope = {
|
|
1492
|
+
...merged,
|
|
1493
|
+
project,
|
|
1494
|
+
userId: merged.userId || `${this.clientName}-user`,
|
|
1495
|
+
sessionId: merged.sessionId || `sess_${stableHash(`${this.clientName}_${normalizedWorkspace || "default"}`)}`
|
|
1496
|
+
};
|
|
1497
|
+
this.lastScope = {
|
|
1498
|
+
project: scope.project,
|
|
1499
|
+
userId: scope.userId,
|
|
1500
|
+
sessionId: scope.sessionId,
|
|
1501
|
+
source: projectSource,
|
|
1502
|
+
warning
|
|
1503
|
+
};
|
|
1504
|
+
return { scope, projectSource, warning };
|
|
1505
|
+
}
|
|
1506
|
+
async runBranch(name, task) {
|
|
1507
|
+
const startedAt = Date.now();
|
|
1508
|
+
try {
|
|
1509
|
+
const value = await withTimeout(task(), this.hardRetrievalTimeoutMs);
|
|
1510
|
+
return {
|
|
1511
|
+
name,
|
|
1512
|
+
status: "ok",
|
|
1513
|
+
durationMs: Date.now() - startedAt,
|
|
1514
|
+
value
|
|
1515
|
+
};
|
|
1516
|
+
} catch (error) {
|
|
1517
|
+
const durationMs = Date.now() - startedAt;
|
|
1518
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
1519
|
+
return {
|
|
1520
|
+
name,
|
|
1521
|
+
status: reason === "timeout" ? "timeout" : "error",
|
|
1522
|
+
durationMs,
|
|
1523
|
+
reason
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
contextItems(result, sourceQuery) {
|
|
1528
|
+
return (result.results || []).map((item) => ({
|
|
1529
|
+
id: item.id,
|
|
1530
|
+
content: item.content,
|
|
1531
|
+
type: "project",
|
|
1532
|
+
score: item.score ?? 0,
|
|
1533
|
+
sourceQuery,
|
|
1534
|
+
metadata: item.metadata || {}
|
|
1535
|
+
}));
|
|
1536
|
+
}
|
|
1537
|
+
memoryItems(result, sourceQuery) {
|
|
1538
|
+
return (result.results || []).map((item, index) => ({
|
|
1539
|
+
id: item.memory?.id || item.chunk?.id || `${sourceQuery}_memory_${index}`,
|
|
1540
|
+
content: item.chunk?.content || item.memory?.content || "",
|
|
1541
|
+
type: "memory",
|
|
1542
|
+
score: item.similarity ?? 0,
|
|
1543
|
+
sourceQuery,
|
|
1544
|
+
metadata: {
|
|
1545
|
+
...item.chunk?.metadata || {},
|
|
1546
|
+
...item.memory?.temporal || {},
|
|
1547
|
+
confidence: item.memory?.confidence
|
|
1548
|
+
}
|
|
1549
|
+
})).filter((item) => item.content);
|
|
1550
|
+
}
|
|
1551
|
+
rerank(items) {
|
|
1552
|
+
const deduped = /* @__PURE__ */ new Map();
|
|
1553
|
+
for (const item of items) {
|
|
1554
|
+
const key = `${item.id}:${item.content.toLowerCase()}`;
|
|
1555
|
+
const recency = extractTimestamp(item.metadata) > 0 ? 0.04 : 0;
|
|
1556
|
+
const queryBonus = item.sourceQuery === "primary" ? 0.08 : item.sourceQuery === "task_frame" ? 0.04 : 0.03;
|
|
1557
|
+
const next = {
|
|
1558
|
+
...item,
|
|
1559
|
+
score: item.score + queryBonus + salienceBoost(item.metadata) + recency
|
|
1560
|
+
};
|
|
1561
|
+
const existing = deduped.get(key);
|
|
1562
|
+
if (!existing || next.score > existing.score) {
|
|
1563
|
+
deduped.set(key, next);
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
return [...deduped.values()].sort((left, right) => right.score - left.score);
|
|
1567
|
+
}
|
|
1568
|
+
buildContext(items) {
|
|
1569
|
+
const maxChars = this.maxTokens * 4;
|
|
1570
|
+
const lines = [];
|
|
1571
|
+
let used = 0;
|
|
1572
|
+
for (const item of items) {
|
|
1573
|
+
const label = item.type === "memory" ? "memory" : "context";
|
|
1574
|
+
const content = compactWhitespace(item.content);
|
|
1575
|
+
if (!content) continue;
|
|
1576
|
+
const line = `[${label}] ${content}`;
|
|
1577
|
+
if (used + line.length > maxChars) break;
|
|
1578
|
+
lines.push(line);
|
|
1579
|
+
used += line.length;
|
|
1580
|
+
}
|
|
1581
|
+
if (lines.length === 0) return "";
|
|
1582
|
+
return `Relevant context:
|
|
1583
|
+
${lines.join("\n")}`;
|
|
1584
|
+
}
|
|
1585
|
+
async bootstrap(context = {}) {
|
|
1586
|
+
const { scope, warning } = await this.resolveScope(context);
|
|
1587
|
+
const warnings = warning ? [warning] : [];
|
|
1588
|
+
const startedAt = Date.now();
|
|
1589
|
+
const branches = await Promise.all([
|
|
1590
|
+
this.runBranch("session_recent", () => this.args.adapter.getSessionMemories({
|
|
1591
|
+
project: scope.project,
|
|
1592
|
+
session_id: scope.sessionId,
|
|
1593
|
+
include_pending: true,
|
|
1594
|
+
limit: 12
|
|
1595
|
+
})),
|
|
1596
|
+
this.runBranch("user_profile", () => scope.userId ? this.args.adapter.getUserProfile({
|
|
1597
|
+
project: scope.project,
|
|
1598
|
+
user_id: scope.userId,
|
|
1599
|
+
include_pending: true,
|
|
1600
|
+
memory_types: "preference,instruction,goal"
|
|
1601
|
+
}) : Promise.resolve({ user_id: scope.userId, memories: [], count: 0 })),
|
|
1602
|
+
this.runBranch("project_rules", () => this.args.adapter.query({
|
|
1603
|
+
project: scope.project,
|
|
1604
|
+
query: "project rules instructions constraints conventions open threads",
|
|
1605
|
+
top_k: this.topK,
|
|
1606
|
+
include_memories: false,
|
|
1607
|
+
user_id: scope.userId,
|
|
1608
|
+
session_id: scope.sessionId,
|
|
1609
|
+
max_tokens: this.maxTokens,
|
|
1610
|
+
compress: true,
|
|
1611
|
+
compression_strategy: "adaptive"
|
|
1612
|
+
}))
|
|
1613
|
+
]);
|
|
1614
|
+
const items = [];
|
|
1615
|
+
const branchStatus = {};
|
|
1616
|
+
for (const branch of branches) {
|
|
1617
|
+
branchStatus[branch.name] = branch.status;
|
|
1618
|
+
if (branch.status !== "ok") {
|
|
1619
|
+
if (branch.reason) warnings.push(`${branch.name}:${branch.reason}`);
|
|
1620
|
+
continue;
|
|
1621
|
+
}
|
|
1622
|
+
if (branch.name === "project_rules") {
|
|
1623
|
+
items.push(...this.contextItems(branch.value, "bootstrap"));
|
|
1624
|
+
continue;
|
|
1625
|
+
}
|
|
1626
|
+
const records = branch.value.memories || [];
|
|
1627
|
+
items.push(...records.map((memory, index) => ({
|
|
1628
|
+
id: String(memory.id || `${branch.name}_${index}`),
|
|
1629
|
+
content: String(memory.content || ""),
|
|
1630
|
+
type: "memory",
|
|
1631
|
+
score: 0.4,
|
|
1632
|
+
sourceQuery: "bootstrap",
|
|
1633
|
+
metadata: memory
|
|
1634
|
+
})).filter((item) => item.content));
|
|
1635
|
+
}
|
|
1636
|
+
const ranked = this.rerank(items).slice(0, this.topK * 2);
|
|
1637
|
+
const prepared = {
|
|
1638
|
+
scope,
|
|
1639
|
+
retrieval: {
|
|
1640
|
+
primaryQuery: "bootstrap",
|
|
1641
|
+
taskFrameQuery: null,
|
|
1642
|
+
warnings,
|
|
1643
|
+
degraded: warnings.length > 0,
|
|
1644
|
+
degradedReason: warnings.length > 0 ? "partial_bootstrap" : void 0,
|
|
1645
|
+
durationMs: Date.now() - startedAt,
|
|
1646
|
+
targetBudgetMs: this.targetRetrievalMs,
|
|
1647
|
+
hardTimeoutMs: this.hardRetrievalTimeoutMs,
|
|
1648
|
+
branchStatus
|
|
1649
|
+
},
|
|
1650
|
+
context: this.buildContext(ranked),
|
|
1651
|
+
items: ranked
|
|
1652
|
+
};
|
|
1653
|
+
this.lastPreparedTurn = prepared.retrieval;
|
|
1654
|
+
return prepared;
|
|
1655
|
+
}
|
|
1656
|
+
async beforeTurn(input, context = {}) {
|
|
1657
|
+
this.pushTouchedFiles(input.touchedFiles);
|
|
1658
|
+
const { scope, warning } = await this.resolveScope(context);
|
|
1659
|
+
const primaryQuery = compactWhitespace(input.userMessage);
|
|
1660
|
+
const taskFrameQuery = this.makeTaskFrameQuery(input);
|
|
1661
|
+
const warnings = warning ? [warning] : [];
|
|
1662
|
+
const startedAt = Date.now();
|
|
1663
|
+
const branches = await Promise.all([
|
|
1664
|
+
this.runBranch("context_primary", () => this.args.adapter.query({
|
|
1665
|
+
project: scope.project,
|
|
1666
|
+
query: primaryQuery,
|
|
1667
|
+
top_k: this.topK,
|
|
1668
|
+
include_memories: false,
|
|
1669
|
+
user_id: scope.userId,
|
|
1670
|
+
session_id: scope.sessionId,
|
|
1671
|
+
max_tokens: this.maxTokens,
|
|
1672
|
+
compress: true,
|
|
1673
|
+
compression_strategy: "adaptive"
|
|
1674
|
+
})),
|
|
1675
|
+
this.runBranch("memory_primary", () => this.args.adapter.searchMemories({
|
|
1676
|
+
project: scope.project,
|
|
1677
|
+
query: primaryQuery,
|
|
1678
|
+
user_id: scope.userId,
|
|
1679
|
+
session_id: scope.sessionId,
|
|
1680
|
+
top_k: this.topK,
|
|
1681
|
+
include_pending: true,
|
|
1682
|
+
profile: "balanced"
|
|
1683
|
+
})),
|
|
1684
|
+
taskFrameQuery ? this.runBranch("context_task_frame", () => this.args.adapter.query({
|
|
1685
|
+
project: scope.project,
|
|
1686
|
+
query: taskFrameQuery,
|
|
1687
|
+
top_k: this.topK,
|
|
1688
|
+
include_memories: false,
|
|
1689
|
+
user_id: scope.userId,
|
|
1690
|
+
session_id: scope.sessionId,
|
|
1691
|
+
max_tokens: this.maxTokens,
|
|
1692
|
+
compress: true,
|
|
1693
|
+
compression_strategy: "adaptive"
|
|
1694
|
+
})) : Promise.resolve({
|
|
1695
|
+
name: "context_task_frame",
|
|
1696
|
+
status: "skipped",
|
|
1697
|
+
durationMs: 0
|
|
1698
|
+
}),
|
|
1699
|
+
taskFrameQuery ? this.runBranch("memory_task_frame", () => this.args.adapter.searchMemories({
|
|
1700
|
+
project: scope.project,
|
|
1701
|
+
query: taskFrameQuery,
|
|
1702
|
+
user_id: scope.userId,
|
|
1703
|
+
session_id: scope.sessionId,
|
|
1704
|
+
top_k: this.topK,
|
|
1705
|
+
include_pending: true,
|
|
1706
|
+
profile: "balanced"
|
|
1707
|
+
})) : Promise.resolve({
|
|
1708
|
+
name: "memory_task_frame",
|
|
1709
|
+
status: "skipped",
|
|
1710
|
+
durationMs: 0
|
|
1711
|
+
})
|
|
1712
|
+
]);
|
|
1713
|
+
const branchStatus = {};
|
|
1714
|
+
const collected = [];
|
|
1715
|
+
let okCount = 0;
|
|
1716
|
+
for (const branch of branches) {
|
|
1717
|
+
branchStatus[branch.name] = branch.status;
|
|
1718
|
+
if (branch.status !== "ok") {
|
|
1719
|
+
if (branch.status !== "skipped" && branch.reason) warnings.push(`${branch.name}:${branch.reason}`);
|
|
1720
|
+
continue;
|
|
1721
|
+
}
|
|
1722
|
+
okCount += 1;
|
|
1723
|
+
if (branch.name.startsWith("context")) {
|
|
1724
|
+
collected.push(...this.contextItems(
|
|
1725
|
+
branch.value,
|
|
1726
|
+
branch.name.includes("task_frame") ? "task_frame" : "primary"
|
|
1727
|
+
));
|
|
1728
|
+
} else {
|
|
1729
|
+
collected.push(...this.memoryItems(
|
|
1730
|
+
branch.value,
|
|
1731
|
+
branch.name.includes("task_frame") ? "task_frame" : "primary"
|
|
1732
|
+
));
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
const ranked = this.rerank(collected).slice(0, this.topK * 2);
|
|
1736
|
+
const prepared = {
|
|
1737
|
+
scope,
|
|
1738
|
+
retrieval: {
|
|
1739
|
+
primaryQuery,
|
|
1740
|
+
taskFrameQuery,
|
|
1741
|
+
warnings,
|
|
1742
|
+
degraded: okCount < branches.filter((branch) => branch.status !== "skipped").length,
|
|
1743
|
+
degradedReason: okCount === 0 ? "all_retrieval_failed" : warnings.length > 0 ? "partial_retrieval_failed" : void 0,
|
|
1744
|
+
durationMs: Date.now() - startedAt,
|
|
1745
|
+
targetBudgetMs: this.targetRetrievalMs,
|
|
1746
|
+
hardTimeoutMs: this.hardRetrievalTimeoutMs,
|
|
1747
|
+
branchStatus
|
|
1748
|
+
},
|
|
1749
|
+
context: this.buildContext(ranked),
|
|
1750
|
+
items: ranked
|
|
1751
|
+
};
|
|
1752
|
+
this.lastPreparedTurn = prepared.retrieval;
|
|
1753
|
+
return prepared;
|
|
1754
|
+
}
|
|
1755
|
+
async recordWork(event, context = {}) {
|
|
1756
|
+
const normalized = {
|
|
1757
|
+
...event,
|
|
1758
|
+
salience: event.salience || defaultSalience(event.kind, event.success),
|
|
1759
|
+
timestamp: event.timestamp || nowIso()
|
|
1760
|
+
};
|
|
1761
|
+
this.pushTouchedFiles(normalized.filePaths);
|
|
1762
|
+
this.pushWorkEvent(normalized);
|
|
1763
|
+
if (normalized.salience === "low") {
|
|
1764
|
+
this.bufferedLowSalience = [...this.bufferedLowSalience, normalized].slice(-20);
|
|
1765
|
+
return { success: true, buffered: true };
|
|
1766
|
+
}
|
|
1767
|
+
const { scope } = await this.resolveScope(context);
|
|
1768
|
+
return this.args.adapter.addMemory({
|
|
1769
|
+
project: scope.project,
|
|
1770
|
+
user_id: scope.userId,
|
|
1771
|
+
session_id: scope.sessionId,
|
|
1772
|
+
content: `${normalized.kind}: ${normalized.summary}${normalized.details ? ` (${normalized.details})` : ""}`,
|
|
1773
|
+
memory_type: toMemoryType(normalized.kind),
|
|
1774
|
+
event_date: normalized.timestamp,
|
|
1775
|
+
write_mode: "async",
|
|
1776
|
+
metadata: {
|
|
1777
|
+
runtime_auto: true,
|
|
1778
|
+
client_name: this.clientName,
|
|
1779
|
+
work_event_kind: normalized.kind,
|
|
1780
|
+
salience: normalized.salience,
|
|
1781
|
+
file_paths: normalized.filePaths || [],
|
|
1782
|
+
tool_name: normalized.toolName,
|
|
1783
|
+
success: normalized.success
|
|
1784
|
+
}
|
|
1785
|
+
});
|
|
1786
|
+
}
|
|
1787
|
+
async afterTurn(input, context = {}) {
|
|
1788
|
+
this.pushTouchedFiles(input.touchedFiles);
|
|
1789
|
+
const { scope } = await this.resolveScope(context);
|
|
1790
|
+
const result = await this.args.adapter.ingestSession({
|
|
1791
|
+
project: scope.project,
|
|
1792
|
+
session_id: scope.sessionId,
|
|
1793
|
+
user_id: scope.userId,
|
|
1794
|
+
messages: [
|
|
1795
|
+
{ role: "user", content: input.userMessage, timestamp: nowIso() },
|
|
1796
|
+
{ role: "assistant", content: input.assistantMessage, timestamp: nowIso() }
|
|
1797
|
+
],
|
|
1798
|
+
write_mode: "async"
|
|
1799
|
+
});
|
|
1800
|
+
this.mergedCount += result.memories_invalidated || 0;
|
|
1801
|
+
return {
|
|
1802
|
+
success: Boolean(result.success),
|
|
1803
|
+
sessionIngested: true,
|
|
1804
|
+
memoriesCreated: result.memories_created || 0,
|
|
1805
|
+
relationsCreated: result.relations_created || 0,
|
|
1806
|
+
invalidatedCount: result.memories_invalidated || 0,
|
|
1807
|
+
mergedCount: result.memories_invalidated || 0,
|
|
1808
|
+
droppedCount: 0,
|
|
1809
|
+
warnings: result.errors || []
|
|
1810
|
+
};
|
|
1811
|
+
}
|
|
1812
|
+
async flush(reason = "manual", context = {}) {
|
|
1813
|
+
if (this.bufferedLowSalience.length > 0) {
|
|
1814
|
+
const { scope } = await this.resolveScope(context);
|
|
1815
|
+
const summary = summarizeLowSalience(this.bufferedLowSalience);
|
|
1816
|
+
await this.args.adapter.addMemory({
|
|
1817
|
+
project: scope.project,
|
|
1818
|
+
user_id: scope.userId,
|
|
1819
|
+
session_id: scope.sessionId,
|
|
1820
|
+
content: summary,
|
|
1821
|
+
memory_type: "event",
|
|
1822
|
+
write_mode: "async",
|
|
1823
|
+
metadata: {
|
|
1824
|
+
runtime_auto: true,
|
|
1825
|
+
client_name: this.clientName,
|
|
1826
|
+
flush_reason: reason,
|
|
1827
|
+
salience: "low",
|
|
1828
|
+
summarized_count: this.bufferedLowSalience.length
|
|
1829
|
+
}
|
|
1830
|
+
});
|
|
1831
|
+
this.bufferedLowSalience = [];
|
|
1832
|
+
}
|
|
1833
|
+
await this.args.adapter.flushQueue();
|
|
1834
|
+
return this.status();
|
|
1835
|
+
}
|
|
1836
|
+
status() {
|
|
1837
|
+
return {
|
|
1838
|
+
clientName: this.clientName,
|
|
1839
|
+
scope: this.lastScope,
|
|
1840
|
+
queue: this.args.adapter.queueStatus(),
|
|
1841
|
+
retrieval: this.lastPreparedTurn,
|
|
1842
|
+
counters: {
|
|
1843
|
+
mergedCount: this.mergedCount,
|
|
1844
|
+
droppedCount: this.droppedCount,
|
|
1845
|
+
bufferedLowSalience: this.bufferedLowSalience.length
|
|
1846
|
+
}
|
|
1847
|
+
};
|
|
1848
|
+
}
|
|
1849
|
+
};
|
|
1850
|
+
|
|
1851
|
+
// ../src/sdk/whisper.ts
|
|
1852
|
+
var PROJECT_CACHE_TTL_MS = 3e4;
|
|
1853
|
+
function isLikelyProjectId(projectRef) {
|
|
1854
|
+
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);
|
|
1855
|
+
}
|
|
1856
|
+
var WhisperClient = class _WhisperClient {
|
|
1857
|
+
constructor(config) {
|
|
1858
|
+
this.config = config;
|
|
1859
|
+
this.diagnosticsStore = new DiagnosticsStore(config.telemetry?.maxEntries || 1e3);
|
|
1860
|
+
this.runtimeClient = new RuntimeClient(
|
|
1861
|
+
{
|
|
1862
|
+
apiKey: config.apiKey,
|
|
1863
|
+
baseUrl: config.baseUrl,
|
|
1864
|
+
compatMode: config.compatMode || "fallback",
|
|
1865
|
+
timeouts: config.timeouts,
|
|
1866
|
+
retryPolicy: config.retryPolicy,
|
|
1867
|
+
fetchImpl: config.fetch
|
|
1868
|
+
},
|
|
1869
|
+
this.diagnosticsStore
|
|
1870
|
+
);
|
|
1871
|
+
this.searchCache = new SearchResponseCache(
|
|
1872
|
+
config.cache?.ttlMs ?? 7e3,
|
|
1873
|
+
config.cache?.capacity ?? 500
|
|
1874
|
+
);
|
|
1875
|
+
const queueStore = this.createQueueStore(config);
|
|
1876
|
+
this.writeQueue = new WriteQueue({
|
|
1877
|
+
store: queueStore,
|
|
1878
|
+
maxBatchSize: config.queue?.maxBatchSize ?? 50,
|
|
1879
|
+
flushIntervalMs: config.queue?.flushIntervalMs ?? 100,
|
|
1880
|
+
maxAttempts: config.queue?.maxAttempts ?? 2,
|
|
1881
|
+
flushHandler: async (items) => {
|
|
1882
|
+
if (items.length === 0) return;
|
|
1883
|
+
const project = items[0].project;
|
|
1884
|
+
const memories = items.map((item) => ({
|
|
1885
|
+
...item.payload,
|
|
1886
|
+
user_id: item.payload.user_id ?? item.userId,
|
|
1887
|
+
session_id: item.payload.session_id ?? item.sessionId,
|
|
1888
|
+
metadata: {
|
|
1889
|
+
...item.payload.metadata || {},
|
|
1890
|
+
event_id: item.eventId,
|
|
1891
|
+
queued_at: item.createdAt
|
|
1892
|
+
}
|
|
1893
|
+
}));
|
|
1894
|
+
try {
|
|
1895
|
+
await this.runtimeClient.request({
|
|
1896
|
+
endpoint: "/v1/memory/bulk",
|
|
1897
|
+
method: "POST",
|
|
1898
|
+
operation: "bulk",
|
|
1899
|
+
body: {
|
|
1900
|
+
project,
|
|
1901
|
+
write_mode: "async",
|
|
1902
|
+
memories
|
|
1903
|
+
}
|
|
1904
|
+
});
|
|
1905
|
+
} catch (error) {
|
|
1906
|
+
if (this.runtimeClient.getCompatMode() !== "fallback" || !(error instanceof RuntimeClientError) || error.status !== 404) {
|
|
1907
|
+
throw error;
|
|
1908
|
+
}
|
|
1909
|
+
await Promise.all(
|
|
1910
|
+
memories.map(async (memory) => {
|
|
1911
|
+
try {
|
|
1912
|
+
await this.runtimeClient.request({
|
|
1913
|
+
endpoint: "/v1/memory",
|
|
1914
|
+
method: "POST",
|
|
1915
|
+
operation: "writeAck",
|
|
1916
|
+
body: {
|
|
1917
|
+
project,
|
|
1918
|
+
...memory,
|
|
1919
|
+
write_mode: "sync"
|
|
1920
|
+
}
|
|
1921
|
+
});
|
|
1922
|
+
} catch (fallbackError) {
|
|
1923
|
+
if (this.runtimeClient.getCompatMode() !== "fallback" || !(fallbackError instanceof RuntimeClientError) || fallbackError.status !== 404) {
|
|
1924
|
+
throw fallbackError;
|
|
1925
|
+
}
|
|
1926
|
+
await this.runtimeClient.request({
|
|
1927
|
+
endpoint: "/v1/memories",
|
|
1928
|
+
method: "POST",
|
|
1929
|
+
operation: "writeAck",
|
|
1930
|
+
body: {
|
|
1931
|
+
project,
|
|
1932
|
+
...memory,
|
|
1933
|
+
memory_type: memory.memory_type === "event" ? "episodic" : memory.memory_type
|
|
1934
|
+
}
|
|
1935
|
+
});
|
|
1936
|
+
}
|
|
1937
|
+
})
|
|
1938
|
+
);
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
});
|
|
1942
|
+
if (config.queue?.enabled !== false) {
|
|
1943
|
+
void this.writeQueue.start();
|
|
1944
|
+
}
|
|
1945
|
+
this.memoryModule = new MemoryModule(
|
|
1946
|
+
this.runtimeClient,
|
|
1947
|
+
this.searchCache,
|
|
1948
|
+
this.writeQueue,
|
|
1949
|
+
{
|
|
1950
|
+
defaultProject: config.project,
|
|
1951
|
+
cacheEnabled: config.cache?.enabled !== false,
|
|
1952
|
+
queueEnabled: config.queue?.enabled !== false
|
|
1953
|
+
}
|
|
1954
|
+
);
|
|
1955
|
+
this.sessionModule = new SessionModule(this.memoryModule, config.project);
|
|
1956
|
+
this.profileModule = new ProfileModule(this.memoryModule);
|
|
1957
|
+
this.analyticsModule = new AnalyticsModule(this.diagnosticsStore, this.writeQueue);
|
|
1958
|
+
this.diagnostics = {
|
|
1959
|
+
getLast: (limit) => this.diagnosticsStore.getLast(limit),
|
|
1960
|
+
subscribe: (fn) => this.diagnosticsStore.subscribe(fn),
|
|
1961
|
+
snapshot: () => this.diagnosticsStore.snapshot()
|
|
1962
|
+
};
|
|
1963
|
+
this.queue = {
|
|
1964
|
+
flush: () => this.writeQueue.flush(),
|
|
1965
|
+
status: () => this.writeQueue.status()
|
|
1966
|
+
};
|
|
1967
|
+
this.memory = {
|
|
1968
|
+
add: (params) => this.memoryModule.add(params),
|
|
1969
|
+
addBulk: (params) => this.memoryModule.addBulk(params),
|
|
1970
|
+
search: (params) => this.memoryModule.search(params),
|
|
1971
|
+
get: (memoryId) => this.memoryModule.get(memoryId),
|
|
1972
|
+
getUserProfile: (params) => this.memoryModule.getUserProfile(params),
|
|
1973
|
+
getSessionMemories: (params) => this.memoryModule.getSessionMemories(params),
|
|
1974
|
+
update: (memoryId, params) => this.memoryModule.update(memoryId, params),
|
|
1975
|
+
delete: (memoryId) => this.memoryModule.delete(memoryId),
|
|
1976
|
+
flag: (params) => this.memoryModule.flag(params)
|
|
1977
|
+
};
|
|
1978
|
+
this.session = {
|
|
1979
|
+
start: (params) => this.sessionModule.start(params),
|
|
1980
|
+
event: (params) => this.sessionModule.event(params),
|
|
1981
|
+
suspend: (params) => this.sessionModule.suspend(params),
|
|
1982
|
+
resume: (params) => this.sessionModule.resume(params),
|
|
1983
|
+
end: (params) => this.sessionModule.end(params)
|
|
1984
|
+
};
|
|
1985
|
+
this.profile = {
|
|
1986
|
+
getUserProfile: (params) => this.profileModule.getUserProfile(params),
|
|
1987
|
+
getSessionMemories: (params) => this.profileModule.getSessionMemories(params)
|
|
1988
|
+
};
|
|
1989
|
+
this.analytics = {
|
|
1990
|
+
diagnosticsSnapshot: () => this.analyticsModule.diagnosticsSnapshot(),
|
|
1991
|
+
queueStatus: () => this.analyticsModule.queueStatus()
|
|
1992
|
+
};
|
|
1993
|
+
}
|
|
1994
|
+
diagnostics;
|
|
1995
|
+
queue;
|
|
1996
|
+
memory;
|
|
1997
|
+
session;
|
|
1998
|
+
profile;
|
|
1999
|
+
analytics;
|
|
2000
|
+
runtimeClient;
|
|
2001
|
+
diagnosticsStore;
|
|
2002
|
+
searchCache;
|
|
2003
|
+
writeQueue;
|
|
2004
|
+
memoryModule;
|
|
2005
|
+
sessionModule;
|
|
2006
|
+
profileModule;
|
|
2007
|
+
analyticsModule;
|
|
2008
|
+
projectRefToId = /* @__PURE__ */ new Map();
|
|
2009
|
+
projectCache = [];
|
|
2010
|
+
projectCacheExpiresAt = 0;
|
|
2011
|
+
static fromEnv(overrides = {}) {
|
|
2012
|
+
const env = typeof process !== "undefined" ? process.env : {};
|
|
2013
|
+
const apiKey = overrides.apiKey || env.WHISPER_API_KEY || env.USEWHISPER_API_KEY || env.API_KEY;
|
|
2014
|
+
if (!apiKey) {
|
|
2015
|
+
throw new Error("Missing API key. Set WHISPER_API_KEY / USEWHISPER_API_KEY / API_KEY.");
|
|
2016
|
+
}
|
|
2017
|
+
return new _WhisperClient({
|
|
2018
|
+
apiKey,
|
|
2019
|
+
baseUrl: overrides.baseUrl || env.WHISPER_BASE_URL || env.API_BASE_URL || "https://context.usewhisper.dev",
|
|
2020
|
+
project: overrides.project || env.WHISPER_PROJECT || env.PROJECT,
|
|
2021
|
+
...overrides
|
|
2022
|
+
});
|
|
2023
|
+
}
|
|
2024
|
+
createQueueStore(config) {
|
|
2025
|
+
const persistence = config.queue?.persistence || this.defaultQueuePersistence();
|
|
2026
|
+
if (persistence === "storage") {
|
|
2027
|
+
return createStorageQueueStore();
|
|
2028
|
+
}
|
|
2029
|
+
if (persistence === "file") {
|
|
2030
|
+
const filePath = config.queue?.filePath || this.defaultQueueFilePath();
|
|
2031
|
+
if (filePath) {
|
|
2032
|
+
return createFileQueueStore(filePath);
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
return new InMemoryQueueStore();
|
|
2036
|
+
}
|
|
2037
|
+
defaultQueuePersistence() {
|
|
2038
|
+
const maybeWindow = globalThis.window;
|
|
2039
|
+
if (maybeWindow && typeof maybeWindow === "object") {
|
|
2040
|
+
const maybeStorage = globalThis.localStorage;
|
|
2041
|
+
return maybeStorage && typeof maybeStorage === "object" ? "storage" : "memory";
|
|
2042
|
+
}
|
|
2043
|
+
return "file";
|
|
2044
|
+
}
|
|
2045
|
+
defaultQueueFilePath() {
|
|
2046
|
+
if (typeof process === "undefined") return void 0;
|
|
2047
|
+
const path = process.env.WHISPER_QUEUE_FILE_PATH;
|
|
2048
|
+
if (path) return path;
|
|
2049
|
+
const home = process.env.USERPROFILE || process.env.HOME;
|
|
2050
|
+
if (!home) return void 0;
|
|
2051
|
+
const normalizedHome = home.replace(/[\\\/]+$/, "");
|
|
2052
|
+
return `${normalizedHome}/.whisper/sdk/queue.json`;
|
|
2053
|
+
}
|
|
2054
|
+
getRequiredProject(project) {
|
|
2055
|
+
const resolved = project || this.config.project;
|
|
2056
|
+
if (!resolved) {
|
|
2057
|
+
throw new RuntimeClientError({
|
|
2058
|
+
code: "MISSING_PROJECT",
|
|
2059
|
+
message: "Project is required",
|
|
2060
|
+
retryable: false
|
|
2061
|
+
});
|
|
2062
|
+
}
|
|
2063
|
+
return resolved;
|
|
2064
|
+
}
|
|
2065
|
+
async refreshProjectCache(force = false) {
|
|
2066
|
+
if (!force && Date.now() < this.projectCacheExpiresAt && this.projectCache.length > 0) {
|
|
2067
|
+
return this.projectCache;
|
|
2068
|
+
}
|
|
2069
|
+
const response = await this.runtimeClient.request({
|
|
2070
|
+
endpoint: "/v1/projects",
|
|
2071
|
+
method: "GET",
|
|
2072
|
+
operation: "get",
|
|
2073
|
+
idempotent: true
|
|
272
2074
|
});
|
|
273
|
-
|
|
274
|
-
|
|
2075
|
+
this.projectRefToId.clear();
|
|
2076
|
+
this.projectCache = response.data?.projects || [];
|
|
2077
|
+
for (const project of this.projectCache) {
|
|
2078
|
+
this.projectRefToId.set(project.id, project.id);
|
|
2079
|
+
this.projectRefToId.set(project.slug, project.id);
|
|
2080
|
+
this.projectRefToId.set(project.name, project.id);
|
|
275
2081
|
}
|
|
276
|
-
|
|
2082
|
+
this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
|
|
2083
|
+
return this.projectCache;
|
|
277
2084
|
}
|
|
278
|
-
async
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
291
|
-
try {
|
|
292
|
-
const attachApiKeyHeader = this.shouldAttachApiKeyHeader(normalizedEndpoint);
|
|
293
|
-
const response = await this.fetchImpl(`${this.baseUrl}${normalizedEndpoint}`, {
|
|
294
|
-
method,
|
|
295
|
-
signal: controller.signal,
|
|
296
|
-
keepalive: method !== "GET",
|
|
297
|
-
headers: {
|
|
298
|
-
"Content-Type": "application/json",
|
|
299
|
-
Authorization: this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`,
|
|
300
|
-
...attachApiKeyHeader ? { "X-API-Key": this.apiKey.replace(/^Bearer\s+/i, "") } : {},
|
|
301
|
-
"x-trace-id": traceId,
|
|
302
|
-
"x-span-id": spanId,
|
|
303
|
-
"x-sdk-version": this.sdkVersion,
|
|
304
|
-
"x-sdk-runtime": this.runtimeName(),
|
|
305
|
-
...options.headers || {}
|
|
306
|
-
},
|
|
307
|
-
body: method === "GET" || method === "DELETE" ? void 0 : JSON.stringify(options.body || {})
|
|
308
|
-
});
|
|
309
|
-
clearTimeout(timeout);
|
|
310
|
-
let payload = null;
|
|
311
|
-
try {
|
|
312
|
-
payload = await response.json();
|
|
313
|
-
} catch {
|
|
314
|
-
payload = await response.text().catch(() => "");
|
|
315
|
-
}
|
|
316
|
-
const durationMs = Date.now() - startedAt;
|
|
317
|
-
const record = {
|
|
318
|
-
id: randomId("diag"),
|
|
319
|
-
startedAt: new Date(startedAt).toISOString(),
|
|
320
|
-
endedAt: nowIso(),
|
|
321
|
-
traceId,
|
|
322
|
-
spanId,
|
|
323
|
-
operation,
|
|
324
|
-
method,
|
|
325
|
-
endpoint: normalizedEndpoint,
|
|
326
|
-
status: response.status,
|
|
327
|
-
durationMs,
|
|
328
|
-
success: response.ok
|
|
329
|
-
};
|
|
330
|
-
this.diagnostics.add(record);
|
|
331
|
-
if (response.ok) {
|
|
332
|
-
return {
|
|
333
|
-
data: payload,
|
|
334
|
-
status: response.status,
|
|
335
|
-
traceId
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
const message = toMessage(payload, response.status, response.statusText);
|
|
339
|
-
const retryable = this.shouldRetryStatus(response.status);
|
|
340
|
-
const error = new RuntimeClientError({
|
|
341
|
-
message,
|
|
342
|
-
status: response.status,
|
|
343
|
-
retryable,
|
|
344
|
-
code: response.status === 404 ? "NOT_FOUND" : "REQUEST_FAILED",
|
|
345
|
-
details: payload,
|
|
346
|
-
traceId
|
|
347
|
-
});
|
|
348
|
-
lastError = error;
|
|
349
|
-
if (!retryable || attempt === maxAttempts - 1) {
|
|
350
|
-
throw error;
|
|
351
|
-
}
|
|
352
|
-
} catch (error) {
|
|
353
|
-
clearTimeout(timeout);
|
|
354
|
-
const durationMs = Date.now() - startedAt;
|
|
355
|
-
const isAbort = isObject(error) && error.name === "AbortError";
|
|
356
|
-
const mapped = error instanceof RuntimeClientError ? error : new RuntimeClientError({
|
|
357
|
-
message: isAbort ? "Request timed out" : error instanceof Error ? error.message : "Network error",
|
|
358
|
-
retryable: this.retryPolicy.retryOnNetworkError ?? true,
|
|
359
|
-
code: isAbort ? "TIMEOUT" : "NETWORK_ERROR",
|
|
360
|
-
traceId
|
|
361
|
-
});
|
|
362
|
-
lastError = mapped;
|
|
363
|
-
this.diagnostics.add({
|
|
364
|
-
id: randomId("diag"),
|
|
365
|
-
startedAt: new Date(startedAt).toISOString(),
|
|
366
|
-
endedAt: nowIso(),
|
|
367
|
-
traceId,
|
|
368
|
-
spanId,
|
|
369
|
-
operation,
|
|
370
|
-
method,
|
|
371
|
-
endpoint: normalizedEndpoint,
|
|
372
|
-
durationMs,
|
|
373
|
-
success: false,
|
|
374
|
-
errorCode: mapped.code,
|
|
375
|
-
errorMessage: mapped.message
|
|
376
|
-
});
|
|
377
|
-
if (!mapped.retryable || attempt === maxAttempts - 1) {
|
|
378
|
-
throw mapped;
|
|
379
|
-
}
|
|
2085
|
+
async fetchResolvedProject(projectRef) {
|
|
2086
|
+
try {
|
|
2087
|
+
const response = await this.runtimeClient.request({
|
|
2088
|
+
endpoint: `/v1/projects/resolve?project=${encodeURIComponent(projectRef)}`,
|
|
2089
|
+
method: "GET",
|
|
2090
|
+
operation: "get",
|
|
2091
|
+
idempotent: true
|
|
2092
|
+
});
|
|
2093
|
+
return response.data?.resolved || null;
|
|
2094
|
+
} catch (error) {
|
|
2095
|
+
if (error instanceof RuntimeClientError && error.status === 404) {
|
|
2096
|
+
return null;
|
|
380
2097
|
}
|
|
381
|
-
|
|
2098
|
+
throw error;
|
|
382
2099
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
2100
|
+
}
|
|
2101
|
+
async resolveProject(projectRef) {
|
|
2102
|
+
const resolvedRef = this.getRequiredProject(projectRef);
|
|
2103
|
+
const cachedProjects = await this.refreshProjectCache(false);
|
|
2104
|
+
const cachedProject = cachedProjects.find(
|
|
2105
|
+
(project) => project.id === resolvedRef || project.slug === resolvedRef || project.name === resolvedRef
|
|
2106
|
+
);
|
|
2107
|
+
if (cachedProject) {
|
|
2108
|
+
return cachedProject;
|
|
2109
|
+
}
|
|
2110
|
+
const resolvedProject = await this.fetchResolvedProject(resolvedRef);
|
|
2111
|
+
if (resolvedProject) {
|
|
2112
|
+
this.projectRefToId.set(resolvedProject.id, resolvedProject.id);
|
|
2113
|
+
this.projectRefToId.set(resolvedProject.slug, resolvedProject.id);
|
|
2114
|
+
this.projectRefToId.set(resolvedProject.name, resolvedProject.id);
|
|
2115
|
+
this.projectCache = [
|
|
2116
|
+
...this.projectCache.filter((project) => project.id !== resolvedProject.id),
|
|
2117
|
+
resolvedProject
|
|
2118
|
+
];
|
|
2119
|
+
this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
|
|
2120
|
+
return resolvedProject;
|
|
2121
|
+
}
|
|
2122
|
+
if (isLikelyProjectId(resolvedRef)) {
|
|
2123
|
+
return {
|
|
2124
|
+
id: resolvedRef,
|
|
2125
|
+
orgId: "",
|
|
2126
|
+
name: resolvedRef,
|
|
2127
|
+
slug: resolvedRef,
|
|
2128
|
+
createdAt: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
2129
|
+
updatedAt: (/* @__PURE__ */ new Date(0)).toISOString()
|
|
2130
|
+
};
|
|
2131
|
+
}
|
|
2132
|
+
throw new RuntimeClientError({
|
|
2133
|
+
code: "PROJECT_NOT_FOUND",
|
|
2134
|
+
message: `Project '${resolvedRef}' not found`,
|
|
2135
|
+
retryable: false
|
|
2136
|
+
});
|
|
2137
|
+
}
|
|
2138
|
+
async query(params) {
|
|
2139
|
+
const project = (await this.resolveProject(params.project)).id;
|
|
2140
|
+
const response = await this.runtimeClient.request({
|
|
2141
|
+
endpoint: "/v1/context/query",
|
|
2142
|
+
method: "POST",
|
|
2143
|
+
operation: "search",
|
|
2144
|
+
body: {
|
|
2145
|
+
...params,
|
|
2146
|
+
project
|
|
2147
|
+
},
|
|
2148
|
+
idempotent: true
|
|
2149
|
+
});
|
|
2150
|
+
return response.data;
|
|
2151
|
+
}
|
|
2152
|
+
async ingestSession(params) {
|
|
2153
|
+
const project = (await this.resolveProject(params.project)).id;
|
|
2154
|
+
const response = await this.runtimeClient.request({
|
|
2155
|
+
endpoint: "/v1/memory/ingest/session",
|
|
2156
|
+
method: "POST",
|
|
2157
|
+
operation: "session",
|
|
2158
|
+
body: {
|
|
2159
|
+
...params,
|
|
2160
|
+
project
|
|
2161
|
+
}
|
|
2162
|
+
});
|
|
2163
|
+
return response.data;
|
|
2164
|
+
}
|
|
2165
|
+
createAgentRuntime(options = {}) {
|
|
2166
|
+
const baseContext = {
|
|
2167
|
+
workspacePath: options.workspacePath,
|
|
2168
|
+
project: options.project || this.config.project,
|
|
2169
|
+
userId: options.userId,
|
|
2170
|
+
sessionId: options.sessionId,
|
|
2171
|
+
traceId: options.traceId,
|
|
2172
|
+
clientName: options.clientName
|
|
2173
|
+
};
|
|
2174
|
+
return new WhisperAgentRuntime({
|
|
2175
|
+
baseContext,
|
|
2176
|
+
options,
|
|
2177
|
+
adapter: {
|
|
2178
|
+
resolveProject: (project) => this.resolveProject(project),
|
|
2179
|
+
query: (params) => this.query(params),
|
|
2180
|
+
ingestSession: (params) => this.ingestSession(params),
|
|
2181
|
+
getSessionMemories: (params) => this.memory.getSessionMemories(params),
|
|
2182
|
+
getUserProfile: (params) => this.memory.getUserProfile(params),
|
|
2183
|
+
searchMemories: (params) => this.memory.search(params),
|
|
2184
|
+
addMemory: (params) => this.memory.add(params),
|
|
2185
|
+
queueStatus: () => this.queue.status(),
|
|
2186
|
+
flushQueue: () => this.queue.flush()
|
|
2187
|
+
}
|
|
387
2188
|
});
|
|
388
2189
|
}
|
|
2190
|
+
withRunContext(context) {
|
|
2191
|
+
const base = this;
|
|
2192
|
+
return {
|
|
2193
|
+
memory: {
|
|
2194
|
+
add: (params) => base.memory.add({
|
|
2195
|
+
...params,
|
|
2196
|
+
project: params.project || context.project || base.config.project,
|
|
2197
|
+
user_id: params.user_id || context.userId,
|
|
2198
|
+
session_id: params.session_id || context.sessionId
|
|
2199
|
+
}),
|
|
2200
|
+
search: (params) => base.memory.search({
|
|
2201
|
+
...params,
|
|
2202
|
+
project: params.project || context.project || base.config.project,
|
|
2203
|
+
user_id: params.user_id || context.userId,
|
|
2204
|
+
session_id: params.session_id || context.sessionId
|
|
2205
|
+
})
|
|
2206
|
+
},
|
|
2207
|
+
session: {
|
|
2208
|
+
event: (params) => base.session.event({
|
|
2209
|
+
...params,
|
|
2210
|
+
sessionId: params.sessionId || context.sessionId || ""
|
|
2211
|
+
})
|
|
2212
|
+
},
|
|
2213
|
+
queue: base.queue,
|
|
2214
|
+
diagnostics: base.diagnostics
|
|
2215
|
+
};
|
|
2216
|
+
}
|
|
2217
|
+
async shutdown() {
|
|
2218
|
+
await this.writeQueue.stop();
|
|
2219
|
+
}
|
|
389
2220
|
};
|
|
390
2221
|
|
|
391
2222
|
// ../src/sdk/index.ts
|
|
@@ -407,7 +2238,7 @@ var DEFAULT_MAX_ATTEMPTS = 3;
|
|
|
407
2238
|
var DEFAULT_BASE_DELAY_MS = 250;
|
|
408
2239
|
var DEFAULT_MAX_DELAY_MS = 2e3;
|
|
409
2240
|
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
410
|
-
var
|
|
2241
|
+
var PROJECT_CACHE_TTL_MS2 = 3e4;
|
|
411
2242
|
var DEPRECATION_WARNINGS = /* @__PURE__ */ new Set();
|
|
412
2243
|
function warnDeprecatedOnce(key, message) {
|
|
413
2244
|
if (DEPRECATION_WARNINGS.has(key)) return;
|
|
@@ -416,7 +2247,7 @@ function warnDeprecatedOnce(key, message) {
|
|
|
416
2247
|
console.warn(message);
|
|
417
2248
|
}
|
|
418
2249
|
}
|
|
419
|
-
function
|
|
2250
|
+
function isLikelyProjectId2(projectRef) {
|
|
420
2251
|
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);
|
|
421
2252
|
}
|
|
422
2253
|
function normalizeBaseUrl2(url) {
|
|
@@ -538,7 +2369,7 @@ var WhisperContext = class _WhisperContext {
|
|
|
538
2369
|
}
|
|
539
2370
|
}
|
|
540
2371
|
}
|
|
541
|
-
this.projectCacheExpiresAt = Date.now() +
|
|
2372
|
+
this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS2;
|
|
542
2373
|
return this.projectCache;
|
|
543
2374
|
}
|
|
544
2375
|
async fetchResolvedProject(projectRef) {
|
|
@@ -571,10 +2402,10 @@ var WhisperContext = class _WhisperContext {
|
|
|
571
2402
|
...this.projectCache.filter((project) => project.id !== resolvedProject.id),
|
|
572
2403
|
resolvedProject
|
|
573
2404
|
];
|
|
574
|
-
this.projectCacheExpiresAt = Date.now() +
|
|
2405
|
+
this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS2;
|
|
575
2406
|
return resolvedProject;
|
|
576
2407
|
}
|
|
577
|
-
if (
|
|
2408
|
+
if (isLikelyProjectId2(resolvedRef)) {
|
|
578
2409
|
return {
|
|
579
2410
|
id: resolvedRef,
|
|
580
2411
|
orgId: "",
|
|
@@ -606,7 +2437,7 @@ var WhisperContext = class _WhisperContext {
|
|
|
606
2437
|
message: `Project reference '${projectRef}' matched multiple projects. Use project id instead.`
|
|
607
2438
|
});
|
|
608
2439
|
}
|
|
609
|
-
if (
|
|
2440
|
+
if (isLikelyProjectId2(projectRef)) {
|
|
610
2441
|
return projectRef;
|
|
611
2442
|
}
|
|
612
2443
|
const resolvedProject = await this.fetchResolvedProject(projectRef);
|
|
@@ -618,7 +2449,7 @@ var WhisperContext = class _WhisperContext {
|
|
|
618
2449
|
...this.projectCache.filter((project) => project.id !== resolvedProject.id),
|
|
619
2450
|
resolvedProject
|
|
620
2451
|
];
|
|
621
|
-
this.projectCacheExpiresAt = Date.now() +
|
|
2452
|
+
this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS2;
|
|
622
2453
|
return resolvedProject.id;
|
|
623
2454
|
}
|
|
624
2455
|
throw new WhisperError({
|
|
@@ -635,7 +2466,7 @@ var WhisperContext = class _WhisperContext {
|
|
|
635
2466
|
candidates.add(match.id);
|
|
636
2467
|
candidates.add(match.slug);
|
|
637
2468
|
candidates.add(match.name);
|
|
638
|
-
} else if (
|
|
2469
|
+
} else if (isLikelyProjectId2(projectRef)) {
|
|
639
2470
|
const byId = projects.find((p) => p.id === projectRef);
|
|
640
2471
|
if (byId) {
|
|
641
2472
|
candidates.add(byId.slug);
|
|
@@ -796,7 +2627,7 @@ var WhisperContext = class _WhisperContext {
|
|
|
796
2627
|
...this.projectCache.filter((p) => p.id !== project.id),
|
|
797
2628
|
project
|
|
798
2629
|
];
|
|
799
|
-
this.projectCacheExpiresAt = Date.now() +
|
|
2630
|
+
this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS2;
|
|
800
2631
|
return project;
|
|
801
2632
|
}
|
|
802
2633
|
async listProjects() {
|
|
@@ -807,7 +2638,7 @@ var WhisperContext = class _WhisperContext {
|
|
|
807
2638
|
this.projectRefToId.set(p.slug, p.id);
|
|
808
2639
|
this.projectRefToId.set(p.name, p.id);
|
|
809
2640
|
}
|
|
810
|
-
this.projectCacheExpiresAt = Date.now() +
|
|
2641
|
+
this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS2;
|
|
811
2642
|
return projects;
|
|
812
2643
|
}
|
|
813
2644
|
async getProject(id) {
|
|
@@ -929,7 +2760,7 @@ var WhisperContext = class _WhisperContext {
|
|
|
929
2760
|
async addMemory(params) {
|
|
930
2761
|
const projectRef = this.getRequiredProject(params.project);
|
|
931
2762
|
return this.withProjectRefFallback(projectRef, async (project) => {
|
|
932
|
-
const
|
|
2763
|
+
const toSotaType2 = (memoryType) => {
|
|
933
2764
|
switch (memoryType) {
|
|
934
2765
|
case "episodic":
|
|
935
2766
|
return "event";
|
|
@@ -941,7 +2772,7 @@ var WhisperContext = class _WhisperContext {
|
|
|
941
2772
|
return memoryType;
|
|
942
2773
|
}
|
|
943
2774
|
};
|
|
944
|
-
const
|
|
2775
|
+
const toLegacyType2 = (memoryType) => {
|
|
945
2776
|
switch (memoryType) {
|
|
946
2777
|
case "event":
|
|
947
2778
|
return "episodic";
|
|
@@ -962,7 +2793,7 @@ var WhisperContext = class _WhisperContext {
|
|
|
962
2793
|
body: JSON.stringify({
|
|
963
2794
|
project,
|
|
964
2795
|
content: params.content,
|
|
965
|
-
memory_type:
|
|
2796
|
+
memory_type: toSotaType2(params.memory_type),
|
|
966
2797
|
user_id: params.user_id,
|
|
967
2798
|
session_id: params.session_id,
|
|
968
2799
|
agent_id: params.agent_id,
|
|
@@ -1016,7 +2847,7 @@ var WhisperContext = class _WhisperContext {
|
|
|
1016
2847
|
body: JSON.stringify({
|
|
1017
2848
|
project,
|
|
1018
2849
|
content: params.content,
|
|
1019
|
-
memory_type:
|
|
2850
|
+
memory_type: toLegacyType2(params.memory_type),
|
|
1020
2851
|
user_id: params.user_id,
|
|
1021
2852
|
session_id: params.session_id,
|
|
1022
2853
|
agent_id: params.agent_id,
|
|
@@ -1577,7 +3408,19 @@ function createWhisperMcpClient(options) {
|
|
|
1577
3408
|
...(options?.baseUrl ?? BASE_URL) && { baseUrl: options?.baseUrl ?? BASE_URL }
|
|
1578
3409
|
});
|
|
1579
3410
|
}
|
|
3411
|
+
function createWhisperMcpRuntimeClient(options) {
|
|
3412
|
+
const apiKey = options?.apiKey ?? API_KEY;
|
|
3413
|
+
if (!apiKey || IS_MANAGEMENT_ONLY) {
|
|
3414
|
+
return null;
|
|
3415
|
+
}
|
|
3416
|
+
return new WhisperClient({
|
|
3417
|
+
apiKey,
|
|
3418
|
+
project: options?.project ?? DEFAULT_PROJECT,
|
|
3419
|
+
...(options?.baseUrl ?? BASE_URL) && { baseUrl: options?.baseUrl ?? BASE_URL }
|
|
3420
|
+
});
|
|
3421
|
+
}
|
|
1580
3422
|
var whisper = createWhisperMcpClient();
|
|
3423
|
+
var runtimeClient = createWhisperMcpRuntimeClient();
|
|
1581
3424
|
var server = new McpServer({
|
|
1582
3425
|
name: "whisper-context",
|
|
1583
3426
|
version: "0.2.8"
|
|
@@ -1739,6 +3582,7 @@ function computeChecksum(value) {
|
|
|
1739
3582
|
return createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
1740
3583
|
}
|
|
1741
3584
|
var cachedProjectRef = DEFAULT_PROJECT || void 0;
|
|
3585
|
+
var cachedMcpSessionId = process.env.WHISPER_SESSION_ID || `mcp_${randomUUID().slice(0, 12)}`;
|
|
1742
3586
|
async function resolveProjectRef(explicit) {
|
|
1743
3587
|
if (explicit?.trim()) {
|
|
1744
3588
|
const requestedRef = explicit.trim();
|
|
@@ -1762,6 +3606,37 @@ async function resolveProjectRef(explicit) {
|
|
|
1762
3606
|
return void 0;
|
|
1763
3607
|
}
|
|
1764
3608
|
}
|
|
3609
|
+
function defaultMcpUserId() {
|
|
3610
|
+
const explicit = process.env.WHISPER_USER_ID?.trim();
|
|
3611
|
+
if (explicit) return explicit;
|
|
3612
|
+
const seed = `${process.cwd()}|${DEFAULT_PROJECT || "default"}|${API_KEY.slice(0, 12) || "anon"}`;
|
|
3613
|
+
return `mcp-user-${createHash("sha256").update(seed).digest("hex").slice(0, 12)}`;
|
|
3614
|
+
}
|
|
3615
|
+
function resolveMcpScope(params) {
|
|
3616
|
+
return {
|
|
3617
|
+
project: params?.project,
|
|
3618
|
+
userId: params?.user_id?.trim() || defaultMcpUserId(),
|
|
3619
|
+
sessionId: params?.session_id?.trim() || cachedMcpSessionId,
|
|
3620
|
+
workspacePath: process.env.WHISPER_WORKSPACE_PATH || process.cwd()
|
|
3621
|
+
};
|
|
3622
|
+
}
|
|
3623
|
+
async function prepareAutomaticQuery(params) {
|
|
3624
|
+
if (!runtimeClient) {
|
|
3625
|
+
throw new Error("Whisper runtime client unavailable.");
|
|
3626
|
+
}
|
|
3627
|
+
const scope = resolveMcpScope(params);
|
|
3628
|
+
const runtime = runtimeClient.createAgentRuntime({
|
|
3629
|
+
project: params.project,
|
|
3630
|
+
userId: scope.userId,
|
|
3631
|
+
sessionId: scope.sessionId,
|
|
3632
|
+
workspacePath: scope.workspacePath,
|
|
3633
|
+
topK: params.top_k,
|
|
3634
|
+
clientName: "whisper-mcp"
|
|
3635
|
+
});
|
|
3636
|
+
return runtime.beforeTurn({
|
|
3637
|
+
userMessage: params.query
|
|
3638
|
+
});
|
|
3639
|
+
}
|
|
1765
3640
|
function buildAbstain(args) {
|
|
1766
3641
|
return {
|
|
1767
3642
|
status: "abstained",
|
|
@@ -1857,6 +3732,8 @@ async function queryWithDegradedFallback(params) {
|
|
|
1857
3732
|
top_k: params.top_k,
|
|
1858
3733
|
include_memories: params.include_memories,
|
|
1859
3734
|
include_graph: params.include_graph,
|
|
3735
|
+
user_id: params.user_id,
|
|
3736
|
+
session_id: params.session_id,
|
|
1860
3737
|
hybrid: true,
|
|
1861
3738
|
rerank: true
|
|
1862
3739
|
});
|
|
@@ -1869,6 +3746,8 @@ async function queryWithDegradedFallback(params) {
|
|
|
1869
3746
|
top_k: params.top_k,
|
|
1870
3747
|
include_memories: params.include_memories,
|
|
1871
3748
|
include_graph: false,
|
|
3749
|
+
user_id: params.user_id,
|
|
3750
|
+
session_id: params.session_id,
|
|
1872
3751
|
hybrid: false,
|
|
1873
3752
|
rerank: false,
|
|
1874
3753
|
vector_weight: 0,
|
|
@@ -2583,7 +4462,7 @@ server.tool(
|
|
|
2583
4462
|
query: z.string().describe("What are you looking for?"),
|
|
2584
4463
|
top_k: z.number().optional().default(10).describe("Number of results"),
|
|
2585
4464
|
chunk_types: z.array(z.string()).optional().describe("Filter: code, function, class, documentation, api_spec, schema, config, text"),
|
|
2586
|
-
include_memories: z.boolean().optional().
|
|
4465
|
+
include_memories: z.boolean().optional().describe("Include relevant memories. Omit to use automatic runtime defaults."),
|
|
2587
4466
|
include_graph: z.boolean().optional().default(false).describe("Include knowledge graph traversal"),
|
|
2588
4467
|
user_id: z.string().optional().describe("User ID for memory scoping"),
|
|
2589
4468
|
session_id: z.string().optional().describe("Session ID for memory scoping"),
|
|
@@ -2595,20 +4474,47 @@ server.tool(
|
|
|
2595
4474
|
if (!resolvedProject) {
|
|
2596
4475
|
return { content: [{ type: "text", text: "Error: No project resolved. Set WHISPER_PROJECT or pass project." }] };
|
|
2597
4476
|
}
|
|
4477
|
+
const automaticMode = include_memories !== false && include_graph !== true && !(chunk_types && chunk_types.length > 0) && max_tokens === void 0 && runtimeClient;
|
|
4478
|
+
if (automaticMode) {
|
|
4479
|
+
const prepared = await prepareAutomaticQuery({
|
|
4480
|
+
project: resolvedProject,
|
|
4481
|
+
query,
|
|
4482
|
+
top_k,
|
|
4483
|
+
user_id,
|
|
4484
|
+
session_id
|
|
4485
|
+
});
|
|
4486
|
+
if (!prepared.items.length) {
|
|
4487
|
+
return { content: [{ type: "text", text: "No relevant context found." }] };
|
|
4488
|
+
}
|
|
4489
|
+
const warnings = prepared.retrieval.warnings.length ? `
|
|
4490
|
+
|
|
4491
|
+
[automatic_runtime]
|
|
4492
|
+
${prepared.retrieval.warnings.join("\n")}` : "";
|
|
4493
|
+
const scope2 = `project=${prepared.scope.project} user=${prepared.scope.userId} session=${prepared.scope.sessionId}`;
|
|
4494
|
+
return {
|
|
4495
|
+
content: [{
|
|
4496
|
+
type: "text",
|
|
4497
|
+
text: `Found ${prepared.items.length} runtime-ranked items (${prepared.retrieval.durationMs}ms, ${scope2}):
|
|
4498
|
+
|
|
4499
|
+
${prepared.context}${warnings}`
|
|
4500
|
+
}]
|
|
4501
|
+
};
|
|
4502
|
+
}
|
|
2598
4503
|
const queryResult = await queryWithDegradedFallback({
|
|
2599
4504
|
project: resolvedProject,
|
|
2600
4505
|
query,
|
|
2601
4506
|
top_k,
|
|
2602
|
-
include_memories,
|
|
4507
|
+
include_memories: include_memories === true,
|
|
2603
4508
|
include_graph,
|
|
2604
|
-
user_id,
|
|
2605
|
-
session_id
|
|
4509
|
+
user_id: user_id || resolveMcpScope({ user_id }).userId,
|
|
4510
|
+
session_id: session_id || resolveMcpScope({ session_id }).sessionId
|
|
2606
4511
|
});
|
|
2607
4512
|
const response = queryResult.response;
|
|
2608
4513
|
if (response.results.length === 0) {
|
|
2609
4514
|
return { content: [{ type: "text", text: "No relevant context found." }] };
|
|
2610
4515
|
}
|
|
2611
|
-
const
|
|
4516
|
+
const scope = resolveMcpScope({ user_id, session_id });
|
|
4517
|
+
const header = `Found ${response.meta.total} results (${response.meta.latency_ms}ms${response.meta.cache_hit ? ", cached" : ""}, project=${resolvedProject}, user=${scope.userId}, session=${scope.sessionId}):
|
|
2612
4518
|
|
|
2613
4519
|
`;
|
|
2614
4520
|
const suffix = queryResult.degraded_mode ? `
|
|
@@ -2635,12 +4541,13 @@ server.tool(
|
|
|
2635
4541
|
},
|
|
2636
4542
|
async ({ project, content, memory_type, user_id, session_id, agent_id, importance }) => {
|
|
2637
4543
|
try {
|
|
4544
|
+
const scope = resolveMcpScope({ project, user_id, session_id });
|
|
2638
4545
|
const result = await whisper.addMemory({
|
|
2639
|
-
project,
|
|
4546
|
+
project: scope.project,
|
|
2640
4547
|
content,
|
|
2641
4548
|
memory_type,
|
|
2642
|
-
user_id,
|
|
2643
|
-
session_id,
|
|
4549
|
+
user_id: scope.userId,
|
|
4550
|
+
session_id: scope.sessionId,
|
|
2644
4551
|
agent_id,
|
|
2645
4552
|
importance
|
|
2646
4553
|
});
|
|
@@ -2649,7 +4556,7 @@ server.tool(
|
|
|
2649
4556
|
const mode = result?.mode;
|
|
2650
4557
|
const semanticStatus = result?.semantic_status;
|
|
2651
4558
|
const typeLabel = memory_type || "factual";
|
|
2652
|
-
const text = mode === "async" || jobId ? `Memory queued (job_id: ${jobId || result.id}, type: ${typeLabel}).` : `Memory stored (id: ${memoryId}, type: ${typeLabel}${semanticStatus ? `, semantic_status: ${semanticStatus}` : ""}).`;
|
|
4559
|
+
const text = mode === "async" || jobId ? `Memory queued (job_id: ${jobId || result.id}, type: ${typeLabel}, user=${scope.userId}, session=${scope.sessionId}).` : `Memory stored (id: ${memoryId}, type: ${typeLabel}, user=${scope.userId}, session=${scope.sessionId}${semanticStatus ? `, semantic_status: ${semanticStatus}` : ""}).`;
|
|
2653
4560
|
return { content: [{ type: "text", text }] };
|
|
2654
4561
|
} catch (error) {
|
|
2655
4562
|
return { content: [{ type: "text", text: `Error: ${error.message}` }] };
|
|
@@ -2669,11 +4576,21 @@ server.tool(
|
|
|
2669
4576
|
},
|
|
2670
4577
|
async ({ project, query, user_id, session_id, top_k, memory_types }) => {
|
|
2671
4578
|
try {
|
|
2672
|
-
const
|
|
2673
|
-
|
|
4579
|
+
const scope = resolveMcpScope({ project, user_id, session_id });
|
|
4580
|
+
const results = runtimeClient && (!memory_types || memory_types.length <= 1) ? await runtimeClient.memory.search({
|
|
4581
|
+
project: scope.project,
|
|
2674
4582
|
query,
|
|
2675
|
-
user_id,
|
|
2676
|
-
session_id,
|
|
4583
|
+
user_id: scope.userId,
|
|
4584
|
+
session_id: scope.sessionId,
|
|
4585
|
+
top_k,
|
|
4586
|
+
include_pending: true,
|
|
4587
|
+
profile: "balanced",
|
|
4588
|
+
...memory_types?.length === 1 ? { memory_type: memory_types[0] } : {}
|
|
4589
|
+
}) : await whisper.searchMemoriesSOTA({
|
|
4590
|
+
project: scope.project,
|
|
4591
|
+
query,
|
|
4592
|
+
user_id: scope.userId,
|
|
4593
|
+
session_id: scope.sessionId,
|
|
2677
4594
|
top_k,
|
|
2678
4595
|
memory_types
|
|
2679
4596
|
});
|
|
@@ -2681,6 +4598,8 @@ server.tool(
|
|
|
2681
4598
|
return primaryToolSuccess({
|
|
2682
4599
|
tool: "memory.search",
|
|
2683
4600
|
query,
|
|
4601
|
+
user_id: scope.userId,
|
|
4602
|
+
session_id: scope.sessionId,
|
|
2684
4603
|
results: normalizedResults,
|
|
2685
4604
|
count: normalizedResults.length
|
|
2686
4605
|
});
|
|
@@ -2968,21 +4887,23 @@ server.tool(
|
|
|
2968
4887
|
},
|
|
2969
4888
|
async ({ project, session_id, user_id, messages }) => {
|
|
2970
4889
|
try {
|
|
4890
|
+
const scope = resolveMcpScope({ project, user_id, session_id });
|
|
2971
4891
|
const normalizedMessages = messages.map((message) => ({
|
|
2972
4892
|
role: message.role,
|
|
2973
4893
|
content: message.content,
|
|
2974
4894
|
timestamp: message.timestamp
|
|
2975
4895
|
}));
|
|
2976
4896
|
const result = await whisper.ingestSession({
|
|
2977
|
-
project,
|
|
2978
|
-
session_id,
|
|
2979
|
-
user_id,
|
|
4897
|
+
project: scope.project,
|
|
4898
|
+
session_id: scope.sessionId,
|
|
4899
|
+
user_id: scope.userId,
|
|
2980
4900
|
messages: normalizedMessages
|
|
2981
4901
|
});
|
|
2982
4902
|
return {
|
|
2983
4903
|
content: [{
|
|
2984
4904
|
type: "text",
|
|
2985
4905
|
text: `Processed ${normalizedMessages.length} messages:
|
|
4906
|
+
- Scope ${scope.project || "auto-project"} / ${scope.userId} / ${scope.sessionId}
|
|
2986
4907
|
- Created ${result.memories_created} memories
|
|
2987
4908
|
- Detected ${result.relations_created} relations
|
|
2988
4909
|
- Updated ${result.memories_invalidated} outdated memories` + (result.errors && result.errors.length > 0 ? `
|
|
@@ -4503,5 +6424,6 @@ if (process.argv[1] && /server\.(mjs|cjs|js|ts)$/.test(process.argv[1])) {
|
|
|
4503
6424
|
export {
|
|
4504
6425
|
createMcpServer,
|
|
4505
6426
|
createWhisperMcpClient,
|
|
6427
|
+
createWhisperMcpRuntimeClient,
|
|
4506
6428
|
renderScopedMcpConfig
|
|
4507
6429
|
};
|