@usewhisper/sdk 3.10.0 → 3.10.1

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.
@@ -0,0 +1,3080 @@
1
+ // ../src/sdk/errors.ts
2
+ var WhisperError = class extends Error {
3
+ code;
4
+ status;
5
+ retryable;
6
+ hint;
7
+ requestId;
8
+ details;
9
+ constructor(args) {
10
+ super(args.message, args.cause ? { cause: args.cause } : void 0);
11
+ this.name = "WhisperError";
12
+ this.code = args.code;
13
+ this.status = args.status;
14
+ this.retryable = args.retryable ?? false;
15
+ this.hint = args.hint;
16
+ this.requestId = args.requestId;
17
+ this.details = args.details;
18
+ }
19
+ };
20
+
21
+ // ../src/sdk/core/telemetry.ts
22
+ var DiagnosticsStore = class {
23
+ maxEntries;
24
+ records = [];
25
+ subscribers = /* @__PURE__ */ new Set();
26
+ constructor(maxEntries = 1e3) {
27
+ this.maxEntries = Math.max(1, maxEntries);
28
+ }
29
+ add(record) {
30
+ this.records.push(record);
31
+ if (this.records.length > this.maxEntries) {
32
+ this.records.splice(0, this.records.length - this.maxEntries);
33
+ }
34
+ for (const fn of this.subscribers) {
35
+ try {
36
+ fn(record);
37
+ } catch {
38
+ }
39
+ }
40
+ }
41
+ getLast(limit = 25) {
42
+ const count = Math.max(1, limit);
43
+ return this.records.slice(-count);
44
+ }
45
+ snapshot() {
46
+ const total = this.records.length;
47
+ const success = this.records.filter((r) => r.success).length;
48
+ const failure = total - success;
49
+ const duration = this.records.reduce((acc, item) => acc + item.durationMs, 0);
50
+ return {
51
+ total,
52
+ success,
53
+ failure,
54
+ avgDurationMs: total > 0 ? duration / total : 0,
55
+ lastTraceId: this.records[this.records.length - 1]?.traceId
56
+ };
57
+ }
58
+ subscribe(fn) {
59
+ this.subscribers.add(fn);
60
+ return () => {
61
+ this.subscribers.delete(fn);
62
+ };
63
+ }
64
+ };
65
+
66
+ // ../src/sdk/core/utils.ts
67
+ function normalizeBaseUrl(url) {
68
+ let normalized = url.trim().replace(/\/+$/, "");
69
+ normalized = normalized.replace(/\/api\/v1$/i, "");
70
+ normalized = normalized.replace(/\/v1$/i, "");
71
+ normalized = normalized.replace(/\/api$/i, "");
72
+ return normalized;
73
+ }
74
+ function normalizeEndpoint(endpoint) {
75
+ const withLeadingSlash = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
76
+ if (/^\/api\/v1(\/|$)/i.test(withLeadingSlash)) {
77
+ return withLeadingSlash.replace(/^\/api/i, "");
78
+ }
79
+ return withLeadingSlash;
80
+ }
81
+ function nowIso() {
82
+ return (/* @__PURE__ */ new Date()).toISOString();
83
+ }
84
+ function stableHash(input) {
85
+ let hash = 2166136261;
86
+ for (let i = 0; i < input.length; i += 1) {
87
+ hash ^= input.charCodeAt(i);
88
+ hash = Math.imul(hash, 16777619);
89
+ }
90
+ return (hash >>> 0).toString(16).padStart(8, "0");
91
+ }
92
+ function normalizeQuery(query) {
93
+ return query.trim().toLowerCase().replace(/\s+/g, " ");
94
+ }
95
+ function randomId(prefix = "id") {
96
+ return `${prefix}_${stableHash(`${Date.now()}_${Math.random()}`)}`;
97
+ }
98
+
99
+ // ../src/sdk/core/client.ts
100
+ var DEFAULT_TIMEOUTS = {
101
+ searchMs: 3e3,
102
+ writeAckMs: 2e3,
103
+ bulkMs: 1e4,
104
+ profileMs: 2500,
105
+ sessionMs: 2500
106
+ };
107
+ var DEFAULT_RETRYABLE_STATUS = [408, 429, 500, 502, 503, 504];
108
+ var DEFAULT_API_KEY_ONLY_PREFIXES = ["/v1/memory", "/v1/context/query"];
109
+ var DEFAULT_RETRY_ATTEMPTS = {
110
+ search: 3,
111
+ writeAck: 2,
112
+ bulk: 2,
113
+ profile: 2,
114
+ session: 2,
115
+ query: 3,
116
+ get: 2
117
+ };
118
+ function isObject(value) {
119
+ return typeof value === "object" && value !== null;
120
+ }
121
+ function toMessage(payload, status, statusText) {
122
+ if (typeof payload === "string" && payload.trim()) return payload;
123
+ if (isObject(payload)) {
124
+ const maybeError = payload.error;
125
+ const maybeMessage = payload.message;
126
+ if (typeof maybeError === "string" && maybeError.trim()) return maybeError;
127
+ if (typeof maybeMessage === "string" && maybeMessage.trim()) return maybeMessage;
128
+ if (isObject(maybeError) && typeof maybeError.message === "string") return maybeError.message;
129
+ }
130
+ return `HTTP ${status}: ${statusText}`;
131
+ }
132
+ var RuntimeClientError = class extends Error {
133
+ status;
134
+ retryable;
135
+ code;
136
+ details;
137
+ traceId;
138
+ hint;
139
+ requestId;
140
+ constructor(args) {
141
+ super(args.message);
142
+ this.name = "RuntimeClientError";
143
+ this.status = args.status;
144
+ this.retryable = args.retryable;
145
+ this.code = args.code;
146
+ this.details = args.details;
147
+ this.traceId = args.traceId;
148
+ this.hint = args.hint;
149
+ this.requestId = args.requestId;
150
+ }
151
+ };
152
+ var RuntimeClient = class {
153
+ apiKey;
154
+ baseUrl;
155
+ sdkVersion;
156
+ compatMode;
157
+ retryPolicy;
158
+ timeouts;
159
+ diagnostics;
160
+ inFlight = /* @__PURE__ */ new Map();
161
+ sendApiKeyHeader;
162
+ fetchImpl;
163
+ constructor(options, diagnostics) {
164
+ if (!options.apiKey) {
165
+ throw new RuntimeClientError({
166
+ code: "INVALID_API_KEY",
167
+ message: "API key is required",
168
+ retryable: false
169
+ });
170
+ }
171
+ this.apiKey = options.apiKey;
172
+ this.baseUrl = normalizeBaseUrl(options.baseUrl || "https://context.usewhisper.dev");
173
+ this.sdkVersion = options.sdkVersion || "2.x-runtime";
174
+ this.compatMode = options.compatMode || "fallback";
175
+ this.retryPolicy = {
176
+ retryableStatusCodes: options.retryPolicy?.retryableStatusCodes || DEFAULT_RETRYABLE_STATUS,
177
+ retryOnNetworkError: options.retryPolicy?.retryOnNetworkError ?? true,
178
+ maxBackoffMs: options.retryPolicy?.maxBackoffMs ?? 1200,
179
+ baseBackoffMs: options.retryPolicy?.baseBackoffMs ?? 250,
180
+ maxAttemptsByOperation: options.retryPolicy?.maxAttemptsByOperation || {}
181
+ };
182
+ this.timeouts = {
183
+ ...DEFAULT_TIMEOUTS,
184
+ ...options.timeouts || {}
185
+ };
186
+ this.sendApiKeyHeader = process.env.WHISPER_SEND_X_API_KEY === "1";
187
+ this.fetchImpl = options.fetchImpl || fetch;
188
+ this.diagnostics = diagnostics || new DiagnosticsStore(1e3);
189
+ }
190
+ getDiagnosticsStore() {
191
+ return this.diagnostics;
192
+ }
193
+ getCompatMode() {
194
+ return this.compatMode;
195
+ }
196
+ timeoutFor(operation) {
197
+ switch (operation) {
198
+ case "search":
199
+ return this.timeouts.searchMs;
200
+ case "writeAck":
201
+ return this.timeouts.writeAckMs;
202
+ case "bulk":
203
+ return this.timeouts.bulkMs;
204
+ case "profile":
205
+ return this.timeouts.profileMs;
206
+ case "session":
207
+ return this.timeouts.sessionMs;
208
+ case "query":
209
+ case "get":
210
+ default:
211
+ return this.timeouts.searchMs;
212
+ }
213
+ }
214
+ maxAttemptsFor(operation) {
215
+ const override = this.retryPolicy.maxAttemptsByOperation?.[operation];
216
+ return Math.max(1, override ?? DEFAULT_RETRY_ATTEMPTS[operation]);
217
+ }
218
+ shouldRetryStatus(status) {
219
+ return status !== void 0 && this.retryPolicy.retryableStatusCodes?.includes(status) === true;
220
+ }
221
+ backoff(attempt) {
222
+ const base = this.retryPolicy.baseBackoffMs ?? 250;
223
+ const max = this.retryPolicy.maxBackoffMs ?? 1200;
224
+ const jitter = 0.8 + Math.random() * 0.4;
225
+ return Math.min(max, Math.floor(base * Math.pow(2, attempt) * jitter));
226
+ }
227
+ runtimeName() {
228
+ const maybeWindow = globalThis.window;
229
+ return maybeWindow && typeof maybeWindow === "object" ? "browser" : "node";
230
+ }
231
+ apiKeyOnlyPrefixes() {
232
+ const raw = process.env.WHISPER_API_KEY_ONLY_PREFIXES;
233
+ if (!raw || !raw.trim()) return DEFAULT_API_KEY_ONLY_PREFIXES;
234
+ return raw.split(",").map((p) => p.trim()).filter((p) => p.length > 0);
235
+ }
236
+ shouldAttachApiKeyHeader(endpoint) {
237
+ if (this.sendApiKeyHeader) return true;
238
+ const prefixes = this.apiKeyOnlyPrefixes();
239
+ return prefixes.some((prefix) => endpoint === prefix || endpoint.startsWith(`${prefix}/`));
240
+ }
241
+ createRequestFingerprint(options) {
242
+ const normalizedEndpoint = normalizeEndpoint(options.endpoint);
243
+ const authFingerprint = stableHash(this.apiKey.replace(/^Bearer\s+/i, ""));
244
+ const payload = JSON.stringify({
245
+ method: options.method || "GET",
246
+ endpoint: normalizedEndpoint,
247
+ body: options.body || null,
248
+ extra: options.dedupeKeyExtra || "",
249
+ authFingerprint
250
+ });
251
+ return stableHash(payload);
252
+ }
253
+ async request(options) {
254
+ const dedupeKey = options.idempotent ? this.createRequestFingerprint(options) : null;
255
+ if (dedupeKey) {
256
+ const inFlight = this.inFlight.get(dedupeKey);
257
+ if (inFlight) {
258
+ const data = await inFlight;
259
+ this.diagnostics.add({
260
+ id: randomId("diag"),
261
+ startedAt: nowIso(),
262
+ endedAt: nowIso(),
263
+ traceId: data.traceId,
264
+ spanId: randomId("span"),
265
+ operation: options.operation,
266
+ method: options.method || "GET",
267
+ endpoint: normalizeEndpoint(options.endpoint),
268
+ status: data.status,
269
+ durationMs: 0,
270
+ success: true,
271
+ deduped: true
272
+ });
273
+ const cloned = {
274
+ data: data.data,
275
+ status: data.status,
276
+ traceId: data.traceId
277
+ };
278
+ return cloned;
279
+ }
280
+ }
281
+ const runner = this.performRequest(options).then((data) => {
282
+ if (dedupeKey) this.inFlight.delete(dedupeKey);
283
+ return data;
284
+ }).catch((error) => {
285
+ if (dedupeKey) this.inFlight.delete(dedupeKey);
286
+ throw error;
287
+ });
288
+ if (dedupeKey) {
289
+ this.inFlight.set(dedupeKey, runner);
290
+ }
291
+ return runner;
292
+ }
293
+ async performRequest(options) {
294
+ const method = options.method || "GET";
295
+ const normalizedEndpoint = normalizeEndpoint(options.endpoint);
296
+ const operation = options.operation;
297
+ const maxAttempts = this.maxAttemptsFor(operation);
298
+ const timeoutMs = this.timeoutFor(operation);
299
+ const traceId = options.traceId || randomId("trace");
300
+ let lastError = null;
301
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
302
+ const spanId = randomId("span");
303
+ const startedAt = Date.now();
304
+ const controller = new AbortController();
305
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
306
+ try {
307
+ const attachApiKeyHeader = this.shouldAttachApiKeyHeader(normalizedEndpoint);
308
+ const response = await this.fetchImpl(`${this.baseUrl}${normalizedEndpoint}`, {
309
+ method,
310
+ signal: controller.signal,
311
+ keepalive: method !== "GET",
312
+ headers: {
313
+ "Content-Type": "application/json",
314
+ Authorization: this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`,
315
+ ...attachApiKeyHeader ? { "X-API-Key": this.apiKey.replace(/^Bearer\s+/i, "") } : {},
316
+ "x-trace-id": traceId,
317
+ "x-span-id": spanId,
318
+ "x-sdk-version": this.sdkVersion,
319
+ "x-sdk-runtime": this.runtimeName(),
320
+ ...options.headers || {}
321
+ },
322
+ body: method === "GET" || method === "DELETE" ? void 0 : JSON.stringify(options.body || {})
323
+ });
324
+ clearTimeout(timeout);
325
+ let payload = null;
326
+ try {
327
+ payload = await response.json();
328
+ } catch {
329
+ payload = await response.text().catch(() => "");
330
+ }
331
+ const durationMs = Date.now() - startedAt;
332
+ const record = {
333
+ id: randomId("diag"),
334
+ startedAt: new Date(startedAt).toISOString(),
335
+ endedAt: nowIso(),
336
+ traceId,
337
+ spanId,
338
+ operation,
339
+ method,
340
+ endpoint: normalizedEndpoint,
341
+ status: response.status,
342
+ durationMs,
343
+ success: response.ok
344
+ };
345
+ this.diagnostics.add(record);
346
+ if (response.ok) {
347
+ return {
348
+ data: payload,
349
+ status: response.status,
350
+ traceId
351
+ };
352
+ }
353
+ const message = toMessage(payload, response.status, response.statusText);
354
+ const payloadObject = isObject(payload) ? payload : {};
355
+ const payloadCode = typeof payloadObject.code === "string" ? payloadObject.code : void 0;
356
+ const payloadHint = typeof payloadObject.hint === "string" ? payloadObject.hint : void 0;
357
+ const payloadRequestId = typeof payloadObject.requestId === "string" ? payloadObject.requestId : typeof payloadObject.request_id === "string" ? payloadObject.request_id : void 0;
358
+ const payloadRetryable = typeof payloadObject.retryable === "boolean" ? payloadObject.retryable : void 0;
359
+ const statusRetryable = this.shouldRetryStatus(response.status);
360
+ const retryable = payloadRetryable ?? statusRetryable;
361
+ const error = new RuntimeClientError({
362
+ message,
363
+ status: response.status,
364
+ retryable,
365
+ code: payloadCode || (response.status === 404 ? "NOT_FOUND" : "REQUEST_FAILED"),
366
+ details: payload,
367
+ traceId: payloadRequestId || traceId,
368
+ requestId: payloadRequestId || traceId,
369
+ hint: payloadHint
370
+ });
371
+ lastError = error;
372
+ if (!retryable || attempt === maxAttempts - 1) {
373
+ throw error;
374
+ }
375
+ } catch (error) {
376
+ clearTimeout(timeout);
377
+ const durationMs = Date.now() - startedAt;
378
+ const isAbort = isObject(error) && error.name === "AbortError";
379
+ const mapped = error instanceof RuntimeClientError ? error : new RuntimeClientError({
380
+ message: isAbort ? "Request timed out" : error instanceof Error ? error.message : "Network error",
381
+ retryable: this.retryPolicy.retryOnNetworkError ?? true,
382
+ code: isAbort ? "TIMEOUT" : "NETWORK_ERROR",
383
+ traceId,
384
+ requestId: traceId
385
+ });
386
+ lastError = mapped;
387
+ this.diagnostics.add({
388
+ id: randomId("diag"),
389
+ startedAt: new Date(startedAt).toISOString(),
390
+ endedAt: nowIso(),
391
+ traceId,
392
+ spanId,
393
+ operation,
394
+ method,
395
+ endpoint: normalizedEndpoint,
396
+ durationMs,
397
+ success: false,
398
+ errorCode: mapped.code,
399
+ errorMessage: mapped.message
400
+ });
401
+ if (!mapped.retryable || attempt === maxAttempts - 1) {
402
+ throw mapped;
403
+ }
404
+ }
405
+ await new Promise((resolve) => setTimeout(resolve, this.backoff(attempt)));
406
+ }
407
+ throw lastError || new RuntimeClientError({
408
+ message: "Request failed",
409
+ retryable: false,
410
+ code: "REQUEST_FAILED"
411
+ });
412
+ }
413
+ };
414
+
415
+ // ../src/sdk/core/cache.ts
416
+ var SearchResponseCache = class {
417
+ ttlMs;
418
+ capacity;
419
+ byKey = /* @__PURE__ */ new Map();
420
+ scopeIndex = /* @__PURE__ */ new Map();
421
+ constructor(ttlMs = 7e3, capacity = 500) {
422
+ this.ttlMs = Math.max(1e3, ttlMs);
423
+ this.capacity = Math.max(10, capacity);
424
+ }
425
+ makeScopeKey(project, userId, sessionId) {
426
+ return `${project}:${userId || "_"}:${sessionId || "_"}`;
427
+ }
428
+ makeKey(input) {
429
+ const normalized = {
430
+ project: input.project,
431
+ userId: input.userId || "",
432
+ sessionId: input.sessionId || "",
433
+ query: normalizeQuery(input.query),
434
+ topK: input.topK,
435
+ profile: input.profile,
436
+ includePending: input.includePending
437
+ };
438
+ return `search:${stableHash(JSON.stringify(normalized))}`;
439
+ }
440
+ get(key) {
441
+ const found = this.byKey.get(key);
442
+ if (!found) return null;
443
+ if (found.expiresAt <= Date.now()) {
444
+ this.deleteByKey(key);
445
+ return null;
446
+ }
447
+ found.touchedAt = Date.now();
448
+ return found.value;
449
+ }
450
+ set(key, scopeKey, value) {
451
+ this.byKey.set(key, {
452
+ value,
453
+ scopeKey,
454
+ touchedAt: Date.now(),
455
+ expiresAt: Date.now() + this.ttlMs
456
+ });
457
+ if (!this.scopeIndex.has(scopeKey)) {
458
+ this.scopeIndex.set(scopeKey, /* @__PURE__ */ new Set());
459
+ }
460
+ this.scopeIndex.get(scopeKey).add(key);
461
+ this.evictIfNeeded();
462
+ }
463
+ invalidateScope(scopeKey) {
464
+ const keys = this.scopeIndex.get(scopeKey);
465
+ if (!keys || keys.size === 0) {
466
+ return 0;
467
+ }
468
+ const toDelete = Array.from(keys);
469
+ for (const key of toDelete) {
470
+ this.deleteByKey(key);
471
+ }
472
+ this.scopeIndex.delete(scopeKey);
473
+ return toDelete.length;
474
+ }
475
+ evictIfNeeded() {
476
+ if (this.byKey.size <= this.capacity) return;
477
+ const ordered = Array.from(this.byKey.entries()).sort((a, b) => a[1].touchedAt - b[1].touchedAt);
478
+ const removeCount = this.byKey.size - this.capacity;
479
+ for (let i = 0; i < removeCount; i += 1) {
480
+ this.deleteByKey(ordered[i][0]);
481
+ }
482
+ }
483
+ deleteByKey(key) {
484
+ const found = this.byKey.get(key);
485
+ if (!found) return;
486
+ this.byKey.delete(key);
487
+ const scopeKeys = this.scopeIndex.get(found.scopeKey);
488
+ if (!scopeKeys) return;
489
+ scopeKeys.delete(key);
490
+ if (scopeKeys.size === 0) {
491
+ this.scopeIndex.delete(found.scopeKey);
492
+ }
493
+ }
494
+ };
495
+
496
+ // ../src/sdk/core/queue.ts
497
+ var InMemoryQueueStore = class {
498
+ items = [];
499
+ async load() {
500
+ return [...this.items];
501
+ }
502
+ async save(items) {
503
+ this.items = [...items];
504
+ }
505
+ };
506
+ var WriteQueue = class {
507
+ flushHandler;
508
+ store;
509
+ maxBatchSize;
510
+ flushIntervalMs;
511
+ maxAttempts;
512
+ queue = [];
513
+ flushTimer = null;
514
+ flushing = false;
515
+ lastFlushAt;
516
+ lastFlushCount = 0;
517
+ constructor(args) {
518
+ this.flushHandler = args.flushHandler;
519
+ this.store = args.store || new InMemoryQueueStore();
520
+ this.maxBatchSize = Math.max(1, args.maxBatchSize ?? 50);
521
+ this.flushIntervalMs = Math.max(10, args.flushIntervalMs ?? 100);
522
+ this.maxAttempts = Math.max(1, args.maxAttempts ?? 2);
523
+ }
524
+ async start() {
525
+ const pending = await this.store.load();
526
+ if (pending.length > 0) {
527
+ this.queue.push(...pending);
528
+ }
529
+ if (!this.flushTimer) {
530
+ this.flushTimer = setInterval(() => {
531
+ void this.flush();
532
+ }, this.flushIntervalMs);
533
+ const timer = this.flushTimer;
534
+ if (typeof timer.unref === "function") {
535
+ timer.unref();
536
+ }
537
+ }
538
+ this.bindProcessHooks();
539
+ }
540
+ async stop() {
541
+ if (this.flushTimer) {
542
+ clearInterval(this.flushTimer);
543
+ this.flushTimer = null;
544
+ }
545
+ await this.flush();
546
+ }
547
+ status() {
548
+ return {
549
+ queued: this.queue.length,
550
+ flushing: this.flushing,
551
+ lastFlushAt: this.lastFlushAt,
552
+ lastFlushCount: this.lastFlushCount
553
+ };
554
+ }
555
+ async enqueue(input) {
556
+ const eventId = input.eventId || this.makeEventId(input);
557
+ const item = {
558
+ ...input,
559
+ eventId,
560
+ createdAt: nowIso()
561
+ };
562
+ this.queue.push(item);
563
+ await this.store.save(this.queue);
564
+ if (this.queue.length >= this.maxBatchSize) {
565
+ void this.flush();
566
+ }
567
+ return item;
568
+ }
569
+ async flush() {
570
+ if (this.flushing || this.queue.length === 0) return;
571
+ this.flushing = true;
572
+ try {
573
+ while (this.queue.length > 0) {
574
+ const batch = this.queue.slice(0, this.maxBatchSize);
575
+ let done = false;
576
+ let error = null;
577
+ for (let attempt = 0; attempt < this.maxAttempts; attempt += 1) {
578
+ try {
579
+ await this.flushHandler(batch);
580
+ done = true;
581
+ break;
582
+ } catch (err) {
583
+ error = err;
584
+ await new Promise((resolve) => setTimeout(resolve, (attempt + 1) * 180));
585
+ }
586
+ }
587
+ if (!done) {
588
+ throw error instanceof Error ? error : new Error("Queue flush failed");
589
+ }
590
+ this.queue.splice(0, batch.length);
591
+ this.lastFlushAt = nowIso();
592
+ this.lastFlushCount = batch.length;
593
+ await this.store.save(this.queue);
594
+ }
595
+ } finally {
596
+ this.flushing = false;
597
+ }
598
+ }
599
+ makeEventId(input) {
600
+ const source = JSON.stringify({
601
+ project: input.project,
602
+ userId: input.userId || "",
603
+ sessionId: input.sessionId || "",
604
+ payload: input.payload
605
+ });
606
+ return `evt_${stableHash(source)}`;
607
+ }
608
+ bindProcessHooks() {
609
+ if (typeof process === "undefined") return;
610
+ const proc = process;
611
+ const flushOnExit = () => {
612
+ void this.flush();
613
+ };
614
+ proc.once("beforeExit", flushOnExit);
615
+ proc.once("SIGINT", flushOnExit);
616
+ proc.once("SIGTERM", flushOnExit);
617
+ }
618
+ };
619
+ function createStorageQueueStore(key = "whisper_sdk_queue") {
620
+ const getStorage = () => {
621
+ const maybeStorage = globalThis.localStorage;
622
+ if (!maybeStorage || typeof maybeStorage !== "object") return null;
623
+ const candidate = maybeStorage;
624
+ if (typeof candidate.getItem !== "function" || typeof candidate.setItem !== "function") {
625
+ return null;
626
+ }
627
+ return {
628
+ getItem: candidate.getItem,
629
+ setItem: candidate.setItem
630
+ };
631
+ };
632
+ return {
633
+ async load() {
634
+ const storage = getStorage();
635
+ if (!storage) return [];
636
+ const raw = storage.getItem(key);
637
+ if (!raw) return [];
638
+ try {
639
+ const parsed = JSON.parse(raw);
640
+ return Array.isArray(parsed) ? parsed : [];
641
+ } catch {
642
+ return [];
643
+ }
644
+ },
645
+ async save(items) {
646
+ const storage = getStorage();
647
+ if (!storage) return;
648
+ storage.setItem(key, JSON.stringify(items));
649
+ }
650
+ };
651
+ }
652
+ function createFileQueueStore(filePath) {
653
+ return {
654
+ async load() {
655
+ if (typeof process === "undefined") return [];
656
+ const fs = await import("fs/promises");
657
+ try {
658
+ const raw = await fs.readFile(filePath, "utf8");
659
+ const parsed = JSON.parse(raw);
660
+ return Array.isArray(parsed) ? parsed : [];
661
+ } catch (error) {
662
+ const nodeError = error;
663
+ if (nodeError?.code === "ENOENT") {
664
+ return [];
665
+ }
666
+ return [];
667
+ }
668
+ },
669
+ async save(items) {
670
+ if (typeof process === "undefined") return;
671
+ const fs = await import("fs/promises");
672
+ const path = await import("path");
673
+ const dir = path.dirname(filePath);
674
+ await fs.mkdir(dir, { recursive: true });
675
+ await fs.writeFile(filePath, JSON.stringify(items), "utf8");
676
+ }
677
+ };
678
+ }
679
+
680
+ // ../src/sdk/modules/memory.ts
681
+ function isEndpointNotFound(error) {
682
+ return error instanceof RuntimeClientError && error.status === 404;
683
+ }
684
+ function toSotaType(memoryType) {
685
+ if (!memoryType) return void 0;
686
+ switch (memoryType) {
687
+ case "episodic":
688
+ return "event";
689
+ case "semantic":
690
+ return "factual";
691
+ case "procedural":
692
+ return "instruction";
693
+ default:
694
+ return memoryType;
695
+ }
696
+ }
697
+ function toLegacyType(memoryType) {
698
+ if (!memoryType) return void 0;
699
+ switch (memoryType) {
700
+ case "event":
701
+ return "episodic";
702
+ case "instruction":
703
+ return "procedural";
704
+ case "preference":
705
+ case "relationship":
706
+ case "opinion":
707
+ case "goal":
708
+ return "semantic";
709
+ default:
710
+ return memoryType;
711
+ }
712
+ }
713
+ var MemoryModule = class {
714
+ constructor(client, cache, queue, options = {}) {
715
+ this.client = client;
716
+ this.cache = cache;
717
+ this.queue = queue;
718
+ this.options = options;
719
+ }
720
+ resolveProject(project) {
721
+ const value = project || this.options.defaultProject;
722
+ if (!value) {
723
+ throw new RuntimeClientError({
724
+ code: "MISSING_PROJECT",
725
+ message: "Project is required",
726
+ retryable: false
727
+ });
728
+ }
729
+ return value;
730
+ }
731
+ invalidate(project, userId, sessionId) {
732
+ if (this.options.cacheEnabled === false) {
733
+ return;
734
+ }
735
+ const scope = this.cache.makeScopeKey(project, userId, sessionId);
736
+ this.cache.invalidateScope(scope);
737
+ }
738
+ async add(params) {
739
+ const project = this.resolveProject(params.project);
740
+ const queueEnabled = this.options.queueEnabled !== false;
741
+ const useQueue = queueEnabled && (params.write_mode === "async" || params.async === true);
742
+ if (useQueue) {
743
+ const queued = await this.queue.enqueue({
744
+ project,
745
+ userId: params.user_id,
746
+ sessionId: params.session_id,
747
+ payload: {
748
+ content: params.content,
749
+ memory_type: toSotaType(params.memory_type),
750
+ user_id: params.user_id,
751
+ session_id: params.session_id,
752
+ agent_id: params.agent_id,
753
+ importance: params.importance,
754
+ confidence: params.confidence,
755
+ metadata: params.metadata,
756
+ document_date: params.document_date,
757
+ event_date: params.event_date
758
+ }
759
+ });
760
+ this.invalidate(project, params.user_id, params.session_id);
761
+ return {
762
+ success: true,
763
+ mode: "async",
764
+ queued: true,
765
+ event_id: queued.eventId,
766
+ accepted_at: queued.createdAt
767
+ };
768
+ }
769
+ try {
770
+ const response = await this.client.request({
771
+ endpoint: "/v1/memory",
772
+ method: "POST",
773
+ operation: "writeAck",
774
+ body: {
775
+ project,
776
+ content: params.content,
777
+ memory_type: toSotaType(params.memory_type),
778
+ user_id: params.user_id,
779
+ session_id: params.session_id,
780
+ agent_id: params.agent_id,
781
+ importance: params.importance,
782
+ confidence: params.confidence,
783
+ metadata: params.metadata,
784
+ document_date: params.document_date,
785
+ event_date: params.event_date,
786
+ write_mode: params.write_mode === "async" || params.async === true ? "async" : "sync"
787
+ }
788
+ });
789
+ this.invalidate(project, params.user_id, params.session_id);
790
+ return {
791
+ success: true,
792
+ mode: "sync",
793
+ trace_id: response.trace_id || response.traceId,
794
+ memory_id: response.memory_id || response.memory?.id,
795
+ semantic_status: response.semantic_status || response.memory?.semantic_status,
796
+ pending_visibility: Boolean(response.pending_visibility),
797
+ visibility_sla_ms: response.visibility_sla_ms
798
+ };
799
+ } catch (error) {
800
+ if (this.client.getCompatMode() !== "fallback" || !isEndpointNotFound(error)) {
801
+ throw error;
802
+ }
803
+ await this.client.request({
804
+ endpoint: "/v1/memories",
805
+ method: "POST",
806
+ operation: "writeAck",
807
+ body: {
808
+ project,
809
+ content: params.content,
810
+ memory_type: toLegacyType(params.memory_type),
811
+ user_id: params.user_id,
812
+ session_id: params.session_id,
813
+ agent_id: params.agent_id,
814
+ importance: params.importance,
815
+ metadata: params.metadata
816
+ }
817
+ });
818
+ this.invalidate(project, params.user_id, params.session_id);
819
+ return {
820
+ success: true,
821
+ mode: "sync"
822
+ };
823
+ }
824
+ }
825
+ async addBulk(params) {
826
+ const project = this.resolveProject(params.project);
827
+ if (!Array.isArray(params.memories) || params.memories.length === 0) {
828
+ throw new RuntimeClientError({
829
+ code: "VALIDATION_ERROR",
830
+ message: "memories is required",
831
+ retryable: false
832
+ });
833
+ }
834
+ const queueEnabled = this.options.queueEnabled !== false;
835
+ const useQueue = queueEnabled && (params.write_mode === "async" || params.async === true);
836
+ if (useQueue) {
837
+ const queued = await Promise.all(
838
+ params.memories.map(
839
+ (memory) => this.queue.enqueue({
840
+ project,
841
+ userId: memory.user_id,
842
+ sessionId: memory.session_id,
843
+ payload: {
844
+ content: memory.content,
845
+ memory_type: toSotaType(memory.memory_type),
846
+ user_id: memory.user_id,
847
+ session_id: memory.session_id,
848
+ agent_id: memory.agent_id,
849
+ importance: memory.importance,
850
+ confidence: memory.confidence,
851
+ metadata: memory.metadata,
852
+ document_date: memory.document_date,
853
+ event_date: memory.event_date
854
+ }
855
+ })
856
+ )
857
+ );
858
+ for (const memory of params.memories) {
859
+ this.invalidate(project, memory.user_id, memory.session_id);
860
+ }
861
+ return {
862
+ success: true,
863
+ mode: "async",
864
+ queued: true,
865
+ created: queued.length
866
+ };
867
+ }
868
+ try {
869
+ const response = await this.client.request({
870
+ endpoint: "/v1/memory/bulk",
871
+ method: "POST",
872
+ operation: "bulk",
873
+ body: {
874
+ project,
875
+ memories: params.memories.map((memory) => ({
876
+ ...memory,
877
+ memory_type: toSotaType(memory.memory_type)
878
+ })),
879
+ write_mode: params.write_mode === "async" || params.async === true ? "async" : "sync"
880
+ }
881
+ });
882
+ for (const memory of params.memories) {
883
+ this.invalidate(project, memory.user_id, memory.session_id);
884
+ }
885
+ return {
886
+ success: true,
887
+ mode: "sync",
888
+ trace_id: response.traceId
889
+ };
890
+ } catch (error) {
891
+ if (this.client.getCompatMode() !== "fallback" || !isEndpointNotFound(error)) {
892
+ throw error;
893
+ }
894
+ await Promise.all(
895
+ params.memories.map(
896
+ (memory) => this.add({
897
+ project,
898
+ ...memory,
899
+ write_mode: "sync"
900
+ })
901
+ )
902
+ );
903
+ return {
904
+ success: true,
905
+ mode: "sync"
906
+ };
907
+ }
908
+ }
909
+ async search(params) {
910
+ const project = this.resolveProject(params.project);
911
+ const topK = params.top_k || 10;
912
+ const profile = params.profile || "fast";
913
+ const includePending = params.include_pending !== false;
914
+ const cacheKey = this.cache.makeKey({
915
+ project,
916
+ userId: params.user_id,
917
+ sessionId: params.session_id,
918
+ query: params.query,
919
+ topK,
920
+ profile,
921
+ includePending
922
+ });
923
+ if (this.options.cacheEnabled !== false) {
924
+ const cached = this.cache.get(cacheKey);
925
+ if (cached) {
926
+ return {
927
+ ...cached,
928
+ cache_hit: true
929
+ };
930
+ }
931
+ }
932
+ try {
933
+ const response = await this.client.request({
934
+ endpoint: "/v1/memory/search",
935
+ method: "POST",
936
+ operation: "search",
937
+ idempotent: true,
938
+ body: {
939
+ project,
940
+ query: params.query,
941
+ user_id: params.user_id,
942
+ session_id: params.session_id,
943
+ top_k: topK,
944
+ profile,
945
+ include_pending: includePending,
946
+ memory_types: params.memory_type ? [toSotaType(params.memory_type)] : void 0
947
+ }
948
+ });
949
+ const data = {
950
+ ...response.data || {},
951
+ cache_hit: false
952
+ };
953
+ if (this.options.cacheEnabled !== false) {
954
+ const scope = this.cache.makeScopeKey(project, params.user_id, params.session_id);
955
+ this.cache.set(cacheKey, scope, data);
956
+ }
957
+ return data;
958
+ } catch (error) {
959
+ if (this.client.getCompatMode() !== "fallback" || !isEndpointNotFound(error)) {
960
+ throw error;
961
+ }
962
+ const legacy = await this.client.request({
963
+ endpoint: "/v1/memories/search",
964
+ method: "POST",
965
+ operation: "search",
966
+ idempotent: true,
967
+ body: {
968
+ project,
969
+ query: params.query,
970
+ user_id: params.user_id,
971
+ session_id: params.session_id,
972
+ top_k: topK,
973
+ memory_type: toLegacyType(params.memory_type)
974
+ }
975
+ });
976
+ const data = {
977
+ ...legacy.data || {},
978
+ cache_hit: false
979
+ };
980
+ if (this.options.cacheEnabled !== false) {
981
+ const scope = this.cache.makeScopeKey(project, params.user_id, params.session_id);
982
+ this.cache.set(cacheKey, scope, data);
983
+ }
984
+ return data;
985
+ }
986
+ }
987
+ async getUserProfile(params) {
988
+ const project = this.resolveProject(params.project);
989
+ const query = new URLSearchParams({
990
+ project,
991
+ ...params.include_pending !== void 0 ? { include_pending: String(params.include_pending) } : {},
992
+ ...params.memory_types ? { memory_types: params.memory_types } : {}
993
+ });
994
+ try {
995
+ const response = await this.client.request({
996
+ endpoint: `/v1/memory/profile/${params.user_id}?${query}`,
997
+ method: "GET",
998
+ operation: "profile",
999
+ idempotent: true
1000
+ });
1001
+ return response.data;
1002
+ } catch (error) {
1003
+ if (this.client.getCompatMode() !== "fallback" || !isEndpointNotFound(error)) {
1004
+ throw error;
1005
+ }
1006
+ const legacyQuery = new URLSearchParams({
1007
+ project,
1008
+ user_id: params.user_id,
1009
+ limit: "200"
1010
+ });
1011
+ const legacy = await this.client.request({
1012
+ endpoint: `/v1/memories?${legacyQuery}`,
1013
+ method: "GET",
1014
+ operation: "profile",
1015
+ idempotent: true
1016
+ });
1017
+ const memories = Array.isArray(legacy.data?.memories) ? legacy.data.memories : [];
1018
+ return {
1019
+ user_id: params.user_id,
1020
+ memories,
1021
+ count: memories.length
1022
+ };
1023
+ }
1024
+ }
1025
+ async getSessionMemories(params) {
1026
+ const project = this.resolveProject(params.project);
1027
+ const query = new URLSearchParams({
1028
+ project,
1029
+ ...params.limit ? { limit: String(params.limit) } : {},
1030
+ ...params.include_pending !== void 0 ? { include_pending: String(params.include_pending) } : {}
1031
+ });
1032
+ const response = await this.client.request({
1033
+ endpoint: `/v1/memory/session/${params.session_id}?${query}`,
1034
+ method: "GET",
1035
+ operation: "profile",
1036
+ idempotent: true
1037
+ });
1038
+ return response.data;
1039
+ }
1040
+ async get(memoryId) {
1041
+ try {
1042
+ const response = await this.client.request({
1043
+ endpoint: `/v1/memory/${memoryId}`,
1044
+ method: "GET",
1045
+ operation: "get",
1046
+ idempotent: true
1047
+ });
1048
+ return response.data;
1049
+ } catch (error) {
1050
+ if (this.client.getCompatMode() !== "fallback" || !isEndpointNotFound(error)) {
1051
+ throw error;
1052
+ }
1053
+ const legacy = await this.client.request({
1054
+ endpoint: `/v1/memories/${memoryId}`,
1055
+ method: "GET",
1056
+ operation: "get",
1057
+ idempotent: true
1058
+ });
1059
+ return legacy.data;
1060
+ }
1061
+ }
1062
+ async update(memoryId, params) {
1063
+ try {
1064
+ await this.client.request({
1065
+ endpoint: `/v1/memory/${memoryId}`,
1066
+ method: "PUT",
1067
+ operation: "writeAck",
1068
+ body: params
1069
+ });
1070
+ return { success: true };
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: "PUT",
1078
+ operation: "writeAck",
1079
+ body: { content: params.content }
1080
+ });
1081
+ return { success: true };
1082
+ }
1083
+ }
1084
+ async delete(memoryId) {
1085
+ try {
1086
+ await this.client.request({
1087
+ endpoint: `/v1/memory/${memoryId}`,
1088
+ method: "DELETE",
1089
+ operation: "writeAck"
1090
+ });
1091
+ return { success: true, deleted: memoryId };
1092
+ } catch (error) {
1093
+ if (this.client.getCompatMode() !== "fallback" || !isEndpointNotFound(error)) {
1094
+ throw error;
1095
+ }
1096
+ await this.client.request({
1097
+ endpoint: `/v1/memories/${memoryId}`,
1098
+ method: "DELETE",
1099
+ operation: "writeAck"
1100
+ });
1101
+ return { success: true, deleted: memoryId };
1102
+ }
1103
+ }
1104
+ async flag(params) {
1105
+ try {
1106
+ await this.client.request({
1107
+ endpoint: `/v1/memory/${params.memoryId}/flag`,
1108
+ method: "POST",
1109
+ operation: "writeAck",
1110
+ body: {
1111
+ reason: params.reason,
1112
+ severity: params.severity || "medium"
1113
+ }
1114
+ });
1115
+ return { success: true };
1116
+ } catch (error) {
1117
+ if (this.client.getCompatMode() !== "fallback" || !isEndpointNotFound(error)) {
1118
+ throw error;
1119
+ }
1120
+ await this.client.request({
1121
+ endpoint: `/v1/memory/${params.memoryId}`,
1122
+ method: "PUT",
1123
+ operation: "writeAck",
1124
+ body: {
1125
+ content: `[FLAGGED:${params.severity || "medium"}] ${params.reason}`
1126
+ }
1127
+ });
1128
+ return { success: true };
1129
+ }
1130
+ }
1131
+ };
1132
+
1133
+ // ../src/sdk/modules/session.ts
1134
+ function randomSessionId() {
1135
+ return `sess_${stableHash(`${Date.now()}_${Math.random()}`)}`;
1136
+ }
1137
+ function assertTransition(current, next) {
1138
+ const allowed = {
1139
+ created: ["active", "ended", "archived"],
1140
+ active: ["suspended", "ended", "archived"],
1141
+ suspended: ["resumed", "ended", "archived"],
1142
+ resumed: ["suspended", "ended", "archived"],
1143
+ ended: ["archived"],
1144
+ archived: []
1145
+ };
1146
+ if (!allowed[current].includes(next)) {
1147
+ throw new RuntimeClientError({
1148
+ code: "INVALID_SESSION_STATE",
1149
+ message: `Invalid session transition ${current} -> ${next}`,
1150
+ retryable: false
1151
+ });
1152
+ }
1153
+ }
1154
+ var SessionModule = class {
1155
+ constructor(memory, defaultProject) {
1156
+ this.memory = memory;
1157
+ this.defaultProject = defaultProject;
1158
+ }
1159
+ sessions = /* @__PURE__ */ new Map();
1160
+ resolveProject(project) {
1161
+ const value = project || this.defaultProject;
1162
+ if (!value) {
1163
+ throw new RuntimeClientError({
1164
+ code: "MISSING_PROJECT",
1165
+ message: "Project is required",
1166
+ retryable: false
1167
+ });
1168
+ }
1169
+ return value;
1170
+ }
1171
+ ensure(sessionId) {
1172
+ const found = this.sessions.get(sessionId);
1173
+ if (!found) {
1174
+ throw new RuntimeClientError({
1175
+ code: "SESSION_NOT_FOUND",
1176
+ message: `Unknown session ${sessionId}`,
1177
+ retryable: false
1178
+ });
1179
+ }
1180
+ return found;
1181
+ }
1182
+ async start(params) {
1183
+ const project = this.resolveProject(params.project);
1184
+ const sessionId = params.sessionId || randomSessionId();
1185
+ const now = nowIso();
1186
+ const record = {
1187
+ sessionId,
1188
+ project,
1189
+ userId: params.userId,
1190
+ state: "active",
1191
+ sequence: 0,
1192
+ metadata: params.metadata,
1193
+ createdAt: now,
1194
+ updatedAt: now
1195
+ };
1196
+ this.sessions.set(sessionId, record);
1197
+ return {
1198
+ sessionId,
1199
+ state: record.state,
1200
+ createdAt: now
1201
+ };
1202
+ }
1203
+ async event(params) {
1204
+ const session = this.ensure(params.sessionId);
1205
+ if (session.state !== "active" && session.state !== "resumed") {
1206
+ throw new RuntimeClientError({
1207
+ code: "INVALID_SESSION_STATE",
1208
+ message: `Cannot append event in ${session.state} state`,
1209
+ retryable: false
1210
+ });
1211
+ }
1212
+ session.sequence += 1;
1213
+ session.updatedAt = nowIso();
1214
+ const eventId = `evt_${stableHash(JSON.stringify({
1215
+ sessionId: session.sessionId,
1216
+ seq: session.sequence,
1217
+ type: params.type,
1218
+ content: params.content,
1219
+ parent: params.parentEventId || ""
1220
+ }))}`;
1221
+ await this.memory.add({
1222
+ project: session.project,
1223
+ content: `${params.type}: ${params.content}`,
1224
+ memory_type: "event",
1225
+ user_id: session.userId,
1226
+ session_id: session.sessionId,
1227
+ metadata: {
1228
+ session_event: true,
1229
+ event_id: eventId,
1230
+ sequence: session.sequence,
1231
+ parent_event_id: params.parentEventId,
1232
+ ...session.metadata,
1233
+ ...params.metadata || {}
1234
+ },
1235
+ write_mode: "async"
1236
+ });
1237
+ return {
1238
+ success: true,
1239
+ eventId,
1240
+ sequence: session.sequence
1241
+ };
1242
+ }
1243
+ async suspend(params) {
1244
+ const session = this.ensure(params.sessionId);
1245
+ assertTransition(session.state, "suspended");
1246
+ session.state = "suspended";
1247
+ session.updatedAt = nowIso();
1248
+ return { sessionId: session.sessionId, state: session.state };
1249
+ }
1250
+ async resume(params) {
1251
+ const session = this.ensure(params.sessionId);
1252
+ const target = session.state === "suspended" ? "resumed" : "active";
1253
+ assertTransition(session.state, target);
1254
+ session.state = target;
1255
+ session.updatedAt = nowIso();
1256
+ return { sessionId: session.sessionId, state: session.state };
1257
+ }
1258
+ async end(params) {
1259
+ const session = this.ensure(params.sessionId);
1260
+ assertTransition(session.state, "ended");
1261
+ session.state = "ended";
1262
+ session.updatedAt = nowIso();
1263
+ return { sessionId: session.sessionId, state: session.state };
1264
+ }
1265
+ async archive(params) {
1266
+ const session = this.ensure(params.sessionId);
1267
+ assertTransition(session.state, "archived");
1268
+ session.state = "archived";
1269
+ session.updatedAt = nowIso();
1270
+ return { sessionId: session.sessionId, state: session.state };
1271
+ }
1272
+ };
1273
+
1274
+ // ../src/sdk/modules/profile.ts
1275
+ var ProfileModule = class {
1276
+ constructor(memory) {
1277
+ this.memory = memory;
1278
+ }
1279
+ async getUserProfile(params) {
1280
+ return this.memory.getUserProfile(params);
1281
+ }
1282
+ async getSessionMemories(params) {
1283
+ return this.memory.getSessionMemories(params);
1284
+ }
1285
+ };
1286
+
1287
+ // ../src/sdk/modules/analytics.ts
1288
+ var AnalyticsModule = class {
1289
+ constructor(diagnostics, queue) {
1290
+ this.diagnostics = diagnostics;
1291
+ this.queue = queue;
1292
+ }
1293
+ diagnosticsSnapshot() {
1294
+ return this.diagnostics.snapshot();
1295
+ }
1296
+ queueStatus() {
1297
+ return this.queue.status();
1298
+ }
1299
+ };
1300
+
1301
+ // ../src/sdk/agent-runtime.ts
1302
+ function detectBrowserStorage() {
1303
+ const maybeStorage = globalThis.localStorage;
1304
+ if (!maybeStorage || typeof maybeStorage !== "object") return null;
1305
+ const candidate = maybeStorage;
1306
+ if (typeof candidate.getItem !== "function" || typeof candidate.setItem !== "function") {
1307
+ return null;
1308
+ }
1309
+ return {
1310
+ getItem: candidate.getItem,
1311
+ setItem: candidate.setItem
1312
+ };
1313
+ }
1314
+ function createBindingStore(filePath) {
1315
+ const storage = detectBrowserStorage();
1316
+ if (storage) {
1317
+ const key = "whisper_agent_runtime_bindings";
1318
+ return {
1319
+ async load() {
1320
+ const raw = storage.getItem(key);
1321
+ if (!raw) return {};
1322
+ try {
1323
+ const parsed = JSON.parse(raw);
1324
+ return parsed && typeof parsed === "object" ? parsed : {};
1325
+ } catch {
1326
+ return {};
1327
+ }
1328
+ },
1329
+ async save(bindings) {
1330
+ storage.setItem(key, JSON.stringify(bindings));
1331
+ }
1332
+ };
1333
+ }
1334
+ return {
1335
+ async load() {
1336
+ if (typeof process === "undefined") return {};
1337
+ const fs = await import("fs/promises");
1338
+ const path = filePath || `${process.env.USERPROFILE || process.env.HOME || "."}/.whisper/sdk/agent-bindings.json`;
1339
+ try {
1340
+ const raw = await fs.readFile(path, "utf8");
1341
+ const parsed = JSON.parse(raw);
1342
+ return parsed && typeof parsed === "object" ? parsed : {};
1343
+ } catch {
1344
+ return {};
1345
+ }
1346
+ },
1347
+ async save(bindings) {
1348
+ if (typeof process === "undefined") return;
1349
+ const fs = await import("fs/promises");
1350
+ const pathMod = await import("path");
1351
+ const path = filePath || `${process.env.USERPROFILE || process.env.HOME || "."}/.whisper/sdk/agent-bindings.json`;
1352
+ await fs.mkdir(pathMod.dirname(path), { recursive: true });
1353
+ await fs.writeFile(path, JSON.stringify(bindings), "utf8");
1354
+ }
1355
+ };
1356
+ }
1357
+ function normalizeWorkspacePath(value) {
1358
+ const trimmed = value?.trim();
1359
+ if (!trimmed) return null;
1360
+ return trimmed.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
1361
+ }
1362
+ function pathBase(value) {
1363
+ const normalized = value.replace(/\\/g, "/");
1364
+ const parts = normalized.split("/").filter(Boolean);
1365
+ return parts[parts.length - 1] || normalized;
1366
+ }
1367
+ function defaultSalience(kind, success) {
1368
+ if (kind === "decision" || kind === "constraint" || kind === "failure") return "high";
1369
+ if (kind === "outcome" || kind === "task_update") return "medium";
1370
+ if (kind === "tool_result" && success === false) return "high";
1371
+ return "low";
1372
+ }
1373
+ function toMemoryType(kind) {
1374
+ if (kind === "decision" || kind === "constraint") return "instruction";
1375
+ return "event";
1376
+ }
1377
+ function summarizeLowSalience(events) {
1378
+ const lines = events.slice(-10).map((event) => {
1379
+ const fileSuffix = event.filePaths?.length ? ` [files: ${event.filePaths.join(", ")}]` : "";
1380
+ const toolSuffix = event.toolName ? ` [tool: ${event.toolName}]` : "";
1381
+ return `- ${event.kind}: ${event.summary}${fileSuffix}${toolSuffix}`;
1382
+ });
1383
+ return `Recent low-salience work:
1384
+ ${lines.join("\n")}`;
1385
+ }
1386
+ function compactWhitespace(value) {
1387
+ return value.replace(/\s+/g, " ").trim();
1388
+ }
1389
+ function normalizeSummary(value) {
1390
+ return compactWhitespace(String(value || "").toLowerCase());
1391
+ }
1392
+ function tokenize(value) {
1393
+ return normalizeSummary(value).split(/[^a-z0-9_./-]+/i).map((token) => token.trim()).filter(Boolean);
1394
+ }
1395
+ function jaccardOverlap(left, right) {
1396
+ const leftTokens = new Set(tokenize(left));
1397
+ const rightTokens = new Set(tokenize(right));
1398
+ if (leftTokens.size === 0 || rightTokens.size === 0) return 0;
1399
+ let intersection = 0;
1400
+ for (const token of leftTokens) {
1401
+ if (rightTokens.has(token)) intersection += 1;
1402
+ }
1403
+ const union = (/* @__PURE__ */ new Set([...leftTokens, ...rightTokens])).size;
1404
+ return union > 0 ? intersection / union : 0;
1405
+ }
1406
+ function clamp01(value) {
1407
+ if (!Number.isFinite(value)) return 0;
1408
+ if (value < 0) return 0;
1409
+ if (value > 1) return 1;
1410
+ return value;
1411
+ }
1412
+ function withTimeout(promise, timeoutMs) {
1413
+ return new Promise((resolve, reject) => {
1414
+ const timeout = setTimeout(() => {
1415
+ reject(new Error("timeout"));
1416
+ }, timeoutMs);
1417
+ promise.then(
1418
+ (value) => {
1419
+ clearTimeout(timeout);
1420
+ resolve(value);
1421
+ },
1422
+ (error) => {
1423
+ clearTimeout(timeout);
1424
+ reject(error);
1425
+ }
1426
+ );
1427
+ });
1428
+ }
1429
+ function extractTimestamp(metadata) {
1430
+ const candidates = [
1431
+ metadata?.updatedAt,
1432
+ metadata?.createdAt,
1433
+ metadata?.timestamp,
1434
+ metadata?.event_date,
1435
+ metadata?.eventDate
1436
+ ];
1437
+ for (const value of candidates) {
1438
+ if (typeof value === "string") {
1439
+ const parsed = Date.parse(value);
1440
+ if (!Number.isNaN(parsed)) return parsed;
1441
+ }
1442
+ }
1443
+ return 0;
1444
+ }
1445
+ var DEFAULT_RANK_WEIGHTS = {
1446
+ focusedPassBonus: 0.2,
1447
+ sourceMatchBonus: 0.18,
1448
+ touchedFileBonus: 0.12,
1449
+ clientMatchBonus: 0.1,
1450
+ highSalienceBonus: 0.12,
1451
+ mediumSalienceBonus: 0.06,
1452
+ staleBroadPenalty: -0.1,
1453
+ unrelatedClientPenalty: -0.18,
1454
+ lowSaliencePenalty: -0.12
1455
+ };
1456
+ var DEFAULT_SOURCE_ACTIVITY = {
1457
+ maxTurns: 10,
1458
+ maxIdleMs: 30 * 60 * 1e3,
1459
+ decayAfterTurns: 5,
1460
+ decayAfterIdleMs: 15 * 60 * 1e3,
1461
+ evictOnTaskSwitch: true
1462
+ };
1463
+ var WhisperAgentRuntime = class {
1464
+ constructor(args) {
1465
+ this.args = args;
1466
+ this.bindingStore = createBindingStore(args.options.bindingStorePath);
1467
+ const retrieval = args.options.retrieval || {};
1468
+ this.focusedTopK = retrieval.focusedTopK ?? args.options.topK ?? 6;
1469
+ this.broadTopK = retrieval.broadTopK ?? Math.max(args.options.topK ?? 6, 10);
1470
+ this.maxTokens = args.options.maxTokens ?? 4e3;
1471
+ this.targetRetrievalMs = args.options.targetRetrievalMs ?? 2500;
1472
+ this.hardRetrievalTimeoutMs = args.options.hardRetrievalTimeoutMs ?? 4e3;
1473
+ this.recentWorkLimit = args.options.recentWorkLimit ?? 40;
1474
+ this.baseContext = args.baseContext;
1475
+ this.clientName = args.baseContext.clientName || "whisper-agent-runtime";
1476
+ this.minFocusedResults = retrieval.minFocusedResults ?? 3;
1477
+ this.minFocusedTopScore = retrieval.minFocusedTopScore ?? 0.55;
1478
+ this.minProjectScore = retrieval.minProjectScore ?? 0.5;
1479
+ this.minMemoryScore = retrieval.minMemoryScore ?? 0.6;
1480
+ this.rankWeights = { ...DEFAULT_RANK_WEIGHTS, ...retrieval.rankWeights || {} };
1481
+ this.sourceActivityOptions = { ...DEFAULT_SOURCE_ACTIVITY, ...retrieval.sourceActivity || {} };
1482
+ }
1483
+ bindingStore;
1484
+ focusedTopK;
1485
+ broadTopK;
1486
+ maxTokens;
1487
+ targetRetrievalMs;
1488
+ hardRetrievalTimeoutMs;
1489
+ recentWorkLimit;
1490
+ baseContext;
1491
+ clientName;
1492
+ minFocusedResults;
1493
+ minFocusedTopScore;
1494
+ minProjectScore;
1495
+ minMemoryScore;
1496
+ rankWeights;
1497
+ sourceActivityOptions;
1498
+ bindings = null;
1499
+ touchedFiles = [];
1500
+ recentWork = [];
1501
+ recentSourceActivity = [];
1502
+ bufferedLowSalience = [];
1503
+ lastPreparedTurn = null;
1504
+ mergedCount = 0;
1505
+ droppedCount = 0;
1506
+ focusedPassHits = 0;
1507
+ fallbackTriggers = 0;
1508
+ floorDroppedCount = 0;
1509
+ injectedItemCount = 0;
1510
+ sourceScopedTurns = 0;
1511
+ broadScopedTurns = 0;
1512
+ totalTurns = 0;
1513
+ currentTurn = 0;
1514
+ lastTaskSummary = "";
1515
+ lastScope = {};
1516
+ async getBindings() {
1517
+ if (!this.bindings) {
1518
+ this.bindings = await this.bindingStore.load();
1519
+ }
1520
+ return this.bindings;
1521
+ }
1522
+ pushTouchedFiles(paths) {
1523
+ if (!paths || paths.length === 0) return;
1524
+ for (const path of paths) {
1525
+ if (!path) continue;
1526
+ this.touchedFiles = [...this.touchedFiles.filter((entry) => entry !== path), path].slice(-20);
1527
+ }
1528
+ }
1529
+ pushWorkEvent(event) {
1530
+ this.recentWork = [...this.recentWork, event].slice(-this.recentWorkLimit);
1531
+ }
1532
+ noteSourceActivity(sourceIds) {
1533
+ const now = Date.now();
1534
+ for (const sourceId of [...new Set((sourceIds || []).map((value) => String(value || "").trim()).filter(Boolean))]) {
1535
+ this.recentSourceActivity = [
1536
+ ...this.recentSourceActivity.filter((entry) => entry.sourceId !== sourceId),
1537
+ { sourceId, turn: this.currentTurn, at: now }
1538
+ ].slice(-24);
1539
+ }
1540
+ }
1541
+ refreshTaskSummary(taskSummary) {
1542
+ const next = normalizeSummary(taskSummary);
1543
+ if (!next) return;
1544
+ if (this.sourceActivityOptions.evictOnTaskSwitch && this.lastTaskSummary && this.lastTaskSummary !== next && jaccardOverlap(this.lastTaskSummary, next) < 0.6) {
1545
+ this.recentSourceActivity = [];
1546
+ }
1547
+ this.lastTaskSummary = next;
1548
+ }
1549
+ activeSourceIds() {
1550
+ const now = Date.now();
1551
+ const active = /* @__PURE__ */ new Map();
1552
+ const maxTurns = this.sourceActivityOptions.maxTurns;
1553
+ const maxIdleMs = this.sourceActivityOptions.maxIdleMs;
1554
+ const decayAfterTurns = this.sourceActivityOptions.decayAfterTurns;
1555
+ const decayAfterIdleMs = this.sourceActivityOptions.decayAfterIdleMs;
1556
+ const fresh = [];
1557
+ for (const entry of this.recentSourceActivity) {
1558
+ const turnDelta = this.currentTurn - entry.turn;
1559
+ const idleDelta = now - entry.at;
1560
+ if (turnDelta > maxTurns || idleDelta > maxIdleMs) continue;
1561
+ fresh.push(entry);
1562
+ let weight = 1;
1563
+ if (turnDelta > decayAfterTurns || idleDelta > decayAfterIdleMs) {
1564
+ weight = 0.5;
1565
+ }
1566
+ const current = active.get(entry.sourceId) || 0;
1567
+ active.set(entry.sourceId, Math.max(current, weight));
1568
+ }
1569
+ this.recentSourceActivity = fresh.slice(-24);
1570
+ return [...active.entries()].sort((left, right) => right[1] - left[1]).map(([sourceId]) => sourceId).slice(0, 4);
1571
+ }
1572
+ focusedScope(input) {
1573
+ const sourceIds = this.activeSourceIds();
1574
+ const fileHints = [...new Set([
1575
+ ...input.touchedFiles || [],
1576
+ ...this.touchedFiles,
1577
+ ...this.recentWork.flatMap((event) => event.filePaths || [])
1578
+ ].map((value) => String(value || "").trim()).filter(Boolean))].slice(-4);
1579
+ return {
1580
+ sourceIds,
1581
+ fileHints,
1582
+ clientName: this.clientName || void 0
1583
+ };
1584
+ }
1585
+ exactFileMetadataFilter(fileHints) {
1586
+ const exact = fileHints.find((value) => /[\\/]/.test(value));
1587
+ if (!exact) return void 0;
1588
+ return { filePath: exact };
1589
+ }
1590
+ makeTaskFrameQuery(input) {
1591
+ const task = compactWhitespace(input.taskSummary || "");
1592
+ const salient = this.recentWork.filter((event) => event.salience === "high").slice(-3).map((event) => `${event.kind}: ${event.summary}`);
1593
+ const files = [...input.touchedFiles || [], ...this.touchedFiles].slice(-3).map((file) => pathBase(file));
1594
+ const parts = [
1595
+ task ? `task ${task}` : "",
1596
+ salient.length > 0 ? `recent ${salient.join(" ; ")}` : "",
1597
+ files.length > 0 ? `files ${files.join(" ")}` : "",
1598
+ input.toolContext ? `tool context ${compactWhitespace(input.toolContext)}` : ""
1599
+ ].filter(Boolean);
1600
+ if (parts.length === 0) return null;
1601
+ return parts.join(" | ");
1602
+ }
1603
+ async resolveScope(overrides) {
1604
+ const merged = {
1605
+ ...this.baseContext,
1606
+ ...overrides
1607
+ };
1608
+ const normalizedWorkspace = normalizeWorkspacePath(merged.workspacePath);
1609
+ const bindings = await this.getBindings();
1610
+ const workspaceProject = normalizedWorkspace ? bindings[normalizedWorkspace] : void 0;
1611
+ const configuredProject = merged.project;
1612
+ let projectRef = configuredProject;
1613
+ let projectSource = overrides?.project ? "explicit" : "generated";
1614
+ let warning;
1615
+ if (workspaceProject) {
1616
+ projectRef = workspaceProject;
1617
+ projectSource = "workspace";
1618
+ if (configuredProject && workspaceProject !== configuredProject) {
1619
+ warning = `workspace mapping '${workspaceProject}' overrides configured project '${configuredProject}'`;
1620
+ }
1621
+ } else if (configuredProject) {
1622
+ projectRef = configuredProject;
1623
+ projectSource = overrides?.project ? "explicit" : "config";
1624
+ }
1625
+ const project = (await this.args.adapter.resolveProject(projectRef)).id;
1626
+ if (normalizedWorkspace) {
1627
+ bindings[normalizedWorkspace] = project;
1628
+ await this.bindingStore.save(bindings);
1629
+ }
1630
+ const scope = {
1631
+ ...merged,
1632
+ project,
1633
+ userId: merged.userId || `${this.clientName}-user`,
1634
+ sessionId: merged.sessionId || `sess_${stableHash(`${this.clientName}_${normalizedWorkspace || "default"}`)}`
1635
+ };
1636
+ this.lastScope = {
1637
+ project: scope.project,
1638
+ userId: scope.userId,
1639
+ sessionId: scope.sessionId,
1640
+ source: projectSource,
1641
+ warning
1642
+ };
1643
+ return { scope, projectSource, warning };
1644
+ }
1645
+ async runBranch(name, task) {
1646
+ const startedAt = Date.now();
1647
+ try {
1648
+ const value = await withTimeout(task(), this.hardRetrievalTimeoutMs);
1649
+ return {
1650
+ name,
1651
+ status: "ok",
1652
+ durationMs: Date.now() - startedAt,
1653
+ value
1654
+ };
1655
+ } catch (error) {
1656
+ const durationMs = Date.now() - startedAt;
1657
+ const reason = error instanceof Error ? error.message : String(error);
1658
+ return {
1659
+ name,
1660
+ status: reason === "timeout" ? "timeout" : "error",
1661
+ durationMs,
1662
+ reason
1663
+ };
1664
+ }
1665
+ }
1666
+ contextItems(result, sourceQuery, pass) {
1667
+ const sourceScope = result.meta?.source_scope;
1668
+ if (sourceScope?.mode === "auto" || sourceScope?.mode === "explicit") {
1669
+ this.noteSourceActivity(sourceScope.source_ids || []);
1670
+ }
1671
+ return (result.results || []).map((item) => ({
1672
+ id: item.id,
1673
+ content: item.content,
1674
+ type: "project",
1675
+ score: item.score ?? 0,
1676
+ sourceQuery,
1677
+ pass,
1678
+ metadata: item.metadata || {}
1679
+ }));
1680
+ }
1681
+ memoryItems(result, sourceQuery, pass) {
1682
+ return (result.results || []).map((item, index) => ({
1683
+ id: item.memory?.id || item.chunk?.id || `${sourceQuery}_memory_${index}`,
1684
+ content: item.chunk?.content || item.memory?.content || "",
1685
+ type: "memory",
1686
+ score: item.similarity ?? 0,
1687
+ sourceQuery,
1688
+ pass,
1689
+ metadata: {
1690
+ ...item.chunk?.metadata || {},
1691
+ ...item.memory?.temporal || {},
1692
+ confidence: item.memory?.confidence
1693
+ }
1694
+ })).filter((item) => item.content);
1695
+ }
1696
+ stableItemKey(item) {
1697
+ const metadata = item.metadata || {};
1698
+ const sourceId = String(metadata.source_id || "");
1699
+ const documentId = String(metadata.document_id || metadata.documentId || "");
1700
+ const chunkId = String(metadata.chunk_id || metadata.chunkId || item.id || "");
1701
+ return stableHash(`${sourceId}|${documentId}|${chunkId}|${item.content.slice(0, 256)}`);
1702
+ }
1703
+ metadataStrings(item) {
1704
+ const metadata = item.metadata || {};
1705
+ return [
1706
+ metadata.filePath,
1707
+ metadata.file_path,
1708
+ metadata.path,
1709
+ metadata.section_path,
1710
+ metadata.parent_section_path,
1711
+ metadata.web_url,
1712
+ metadata.url
1713
+ ].map((value) => String(value || "").toLowerCase()).filter(Boolean);
1714
+ }
1715
+ hasSourceMatch(item, scope) {
1716
+ const sourceId = String(item.metadata?.source_id || "");
1717
+ return Boolean(sourceId && scope.sourceIds.includes(sourceId));
1718
+ }
1719
+ hasFileMatch(item, scope) {
1720
+ if (scope.fileHints.length === 0) return false;
1721
+ const metadata = this.metadataStrings(item);
1722
+ const lowerHints = scope.fileHints.map((hint) => hint.toLowerCase());
1723
+ return lowerHints.some((hint) => {
1724
+ const base = pathBase(hint).toLowerCase();
1725
+ return metadata.some((value) => value.includes(hint) || value.endsWith(base));
1726
+ });
1727
+ }
1728
+ hasClientMatch(item, scope) {
1729
+ const itemClient = String(item.metadata?.client_name || "");
1730
+ return Boolean(scope.clientName && itemClient && itemClient === scope.clientName);
1731
+ }
1732
+ salienceAdjustment(item) {
1733
+ const salience = item.metadata?.salience;
1734
+ if (salience === "high") return this.rankWeights.highSalienceBonus;
1735
+ if (salience === "medium") return this.rankWeights.mediumSalienceBonus;
1736
+ if (salience === "low") return this.rankWeights.lowSaliencePenalty;
1737
+ return 0;
1738
+ }
1739
+ narrowFocusedMemories(items, scope) {
1740
+ const hasSignals = scope.sourceIds.length > 0 || scope.fileHints.length > 0 || Boolean(scope.clientName);
1741
+ if (!hasSignals) return items;
1742
+ const narrowed = items.filter((item) => {
1743
+ const matchesClient = this.hasClientMatch(item, scope);
1744
+ const matchesFile = this.hasFileMatch(item, scope);
1745
+ const matchesSource = this.hasSourceMatch(item, scope);
1746
+ const salience = item.metadata?.salience;
1747
+ if (scope.clientName && item.metadata?.client_name && !matchesClient) {
1748
+ return false;
1749
+ }
1750
+ if (salience === "low" && !matchesFile && !matchesSource) {
1751
+ return false;
1752
+ }
1753
+ return matchesClient || matchesFile || matchesSource || !scope.clientName;
1754
+ });
1755
+ return narrowed.length > 0 ? narrowed : items;
1756
+ }
1757
+ applyRelevanceFloor(items) {
1758
+ const filtered = items.filter(
1759
+ (item) => item.type === "project" ? item.score >= this.minProjectScore : item.score >= this.minMemoryScore
1760
+ );
1761
+ return { items: filtered, dropped: Math.max(0, items.length - filtered.length) };
1762
+ }
1763
+ rerank(items, scope) {
1764
+ const deduped = /* @__PURE__ */ new Map();
1765
+ for (const item of items) {
1766
+ const key = this.stableItemKey(item);
1767
+ const recency = extractTimestamp(item.metadata) > 0 ? 0.04 : 0;
1768
+ const queryBonus = item.sourceQuery === "primary" ? 0.08 : item.sourceQuery === "task_frame" ? 0.04 : 0.03;
1769
+ const sourceMatch = this.hasSourceMatch(item, scope);
1770
+ const fileMatch = this.hasFileMatch(item, scope);
1771
+ const clientMatch = this.hasClientMatch(item, scope);
1772
+ const broadPenalty = item.pass === "broad" && !sourceMatch && !fileMatch && !clientMatch ? this.rankWeights.staleBroadPenalty : 0;
1773
+ const clientPenalty = scope.clientName && item.metadata?.client_name && !clientMatch ? this.rankWeights.unrelatedClientPenalty : 0;
1774
+ const next = {
1775
+ ...item,
1776
+ score: clamp01(
1777
+ item.score + queryBonus + recency + (item.pass === "focused" ? this.rankWeights.focusedPassBonus : 0) + (sourceMatch ? this.rankWeights.sourceMatchBonus : 0) + (fileMatch ? this.rankWeights.touchedFileBonus : 0) + (clientMatch ? this.rankWeights.clientMatchBonus : 0) + this.salienceAdjustment(item) + broadPenalty + clientPenalty
1778
+ )
1779
+ };
1780
+ const existing = deduped.get(key);
1781
+ if (!existing || next.score > existing.score) {
1782
+ deduped.set(key, next);
1783
+ }
1784
+ }
1785
+ return {
1786
+ items: [...deduped.values()].sort((left, right) => right.score - left.score),
1787
+ dedupedCount: Math.max(0, items.length - deduped.size)
1788
+ };
1789
+ }
1790
+ buildContext(items) {
1791
+ const maxChars = this.maxTokens * 4;
1792
+ const lines = [];
1793
+ let used = 0;
1794
+ for (const item of items) {
1795
+ const label = item.type === "memory" ? "memory" : "context";
1796
+ const content = compactWhitespace(item.content);
1797
+ if (!content) continue;
1798
+ const line = `[${label}] ${content}`;
1799
+ if (used + line.length > maxChars) break;
1800
+ lines.push(line);
1801
+ used += line.length;
1802
+ }
1803
+ if (lines.length === 0) return "";
1804
+ return `Relevant context:
1805
+ ${lines.join("\n")}`;
1806
+ }
1807
+ async bootstrap(context = {}) {
1808
+ const { scope, warning } = await this.resolveScope(context);
1809
+ const warnings = warning ? [warning] : [];
1810
+ const startedAt = Date.now();
1811
+ const branches = await Promise.all([
1812
+ this.runBranch("session_recent", () => this.args.adapter.getSessionMemories({
1813
+ project: scope.project,
1814
+ session_id: scope.sessionId,
1815
+ include_pending: true,
1816
+ limit: 12
1817
+ })),
1818
+ this.runBranch("user_profile", () => scope.userId ? this.args.adapter.getUserProfile({
1819
+ project: scope.project,
1820
+ user_id: scope.userId,
1821
+ include_pending: true,
1822
+ memory_types: "preference,instruction,goal"
1823
+ }) : Promise.resolve({ user_id: scope.userId, memories: [], count: 0 })),
1824
+ this.runBranch("project_rules", () => this.args.adapter.query({
1825
+ project: scope.project,
1826
+ query: "project rules instructions constraints conventions open threads",
1827
+ top_k: this.focusedTopK,
1828
+ include_memories: false,
1829
+ user_id: scope.userId,
1830
+ session_id: scope.sessionId,
1831
+ max_tokens: this.maxTokens,
1832
+ compress: true,
1833
+ compression_strategy: "adaptive"
1834
+ }))
1835
+ ]);
1836
+ const items = [];
1837
+ const branchStatus = {};
1838
+ for (const branch of branches) {
1839
+ branchStatus[branch.name] = branch.status;
1840
+ if (branch.status !== "ok") {
1841
+ if (branch.reason) warnings.push(`${branch.name}:${branch.reason}`);
1842
+ continue;
1843
+ }
1844
+ if (branch.name === "project_rules") {
1845
+ items.push(...this.contextItems(branch.value, "bootstrap", "bootstrap"));
1846
+ continue;
1847
+ }
1848
+ const records = branch.value.memories || [];
1849
+ items.push(...records.map((memory, index) => ({
1850
+ id: String(memory.id || `${branch.name}_${index}`),
1851
+ content: String(memory.content || ""),
1852
+ type: "memory",
1853
+ score: 0.4,
1854
+ sourceQuery: "bootstrap",
1855
+ pass: "bootstrap",
1856
+ metadata: memory
1857
+ })).filter((item) => item.content));
1858
+ }
1859
+ const reranked = this.rerank(items, { sourceIds: [], fileHints: [], clientName: this.clientName });
1860
+ const ranked = reranked.items.slice(0, this.broadTopK * 2);
1861
+ const prepared = {
1862
+ scope,
1863
+ retrieval: {
1864
+ primaryQuery: "bootstrap",
1865
+ taskFrameQuery: null,
1866
+ warnings,
1867
+ degraded: warnings.length > 0,
1868
+ degradedReason: warnings.length > 0 ? "partial_bootstrap" : void 0,
1869
+ durationMs: Date.now() - startedAt,
1870
+ targetBudgetMs: this.targetRetrievalMs,
1871
+ hardTimeoutMs: this.hardRetrievalTimeoutMs,
1872
+ branchStatus,
1873
+ focusedScopeApplied: false,
1874
+ focusedSourceIds: [],
1875
+ focusedFileHints: [],
1876
+ clientScoped: false,
1877
+ fallbackUsed: false,
1878
+ droppedBelowFloor: 0,
1879
+ dedupedCount: reranked.dedupedCount
1880
+ },
1881
+ context: this.buildContext(ranked),
1882
+ items: ranked
1883
+ };
1884
+ this.lastPreparedTurn = prepared.retrieval;
1885
+ return prepared;
1886
+ }
1887
+ async beforeTurn(input, context = {}) {
1888
+ this.currentTurn += 1;
1889
+ this.pushTouchedFiles(input.touchedFiles);
1890
+ this.refreshTaskSummary(input.taskSummary);
1891
+ const { scope, warning } = await this.resolveScope(context);
1892
+ const primaryQuery = compactWhitespace(input.userMessage);
1893
+ const taskFrameQuery = this.makeTaskFrameQuery(input);
1894
+ const focusedScope = this.focusedScope(input);
1895
+ const focusedMetadataFilter = this.exactFileMetadataFilter(focusedScope.fileHints);
1896
+ const focusedScopeApplied = focusedScope.sourceIds.length > 0 || focusedScope.fileHints.length > 0 || Boolean(focusedScope.clientName);
1897
+ const warnings = warning ? [warning] : [];
1898
+ const startedAt = Date.now();
1899
+ const branchStatus = {};
1900
+ const collectFromBranches = (branches, pass) => {
1901
+ const collected = [];
1902
+ let okCount = 0;
1903
+ for (const branch of branches) {
1904
+ branchStatus[branch.name] = branch.status;
1905
+ if (branch.status !== "ok") {
1906
+ if (branch.status !== "skipped" && branch.reason) warnings.push(`${branch.name}:${branch.reason}`);
1907
+ continue;
1908
+ }
1909
+ okCount += 1;
1910
+ if (branch.name.startsWith("context")) {
1911
+ collected.push(...this.contextItems(
1912
+ branch.value,
1913
+ branch.name.includes("task_frame") ? "task_frame" : "primary",
1914
+ pass
1915
+ ));
1916
+ } else {
1917
+ const memoryItems = this.memoryItems(
1918
+ branch.value,
1919
+ branch.name.includes("task_frame") ? "task_frame" : "primary",
1920
+ pass
1921
+ );
1922
+ collected.push(...pass === "focused" ? this.narrowFocusedMemories(memoryItems, focusedScope) : memoryItems);
1923
+ }
1924
+ }
1925
+ return { collected, okCount };
1926
+ };
1927
+ const focusedBranches = await Promise.all([
1928
+ this.runBranch("context_primary_focused", () => this.args.adapter.query({
1929
+ project: scope.project,
1930
+ query: primaryQuery,
1931
+ top_k: this.focusedTopK,
1932
+ include_memories: false,
1933
+ user_id: scope.userId,
1934
+ session_id: scope.sessionId,
1935
+ source_ids: focusedScope.sourceIds.length > 0 ? focusedScope.sourceIds : void 0,
1936
+ metadata_filter: focusedMetadataFilter,
1937
+ max_tokens: this.maxTokens,
1938
+ compress: true,
1939
+ compression_strategy: "adaptive"
1940
+ })),
1941
+ this.runBranch("memory_primary_focused", () => this.args.adapter.searchMemories({
1942
+ project: scope.project,
1943
+ query: primaryQuery,
1944
+ user_id: scope.userId,
1945
+ session_id: scope.sessionId,
1946
+ top_k: this.focusedTopK,
1947
+ include_pending: true,
1948
+ profile: "balanced"
1949
+ })),
1950
+ taskFrameQuery ? this.runBranch("context_task_frame_focused", () => this.args.adapter.query({
1951
+ project: scope.project,
1952
+ query: taskFrameQuery,
1953
+ top_k: this.focusedTopK,
1954
+ include_memories: false,
1955
+ user_id: scope.userId,
1956
+ session_id: scope.sessionId,
1957
+ source_ids: focusedScope.sourceIds.length > 0 ? focusedScope.sourceIds : void 0,
1958
+ metadata_filter: focusedMetadataFilter,
1959
+ max_tokens: this.maxTokens,
1960
+ compress: true,
1961
+ compression_strategy: "adaptive"
1962
+ })) : Promise.resolve({ name: "context_task_frame_focused", status: "skipped", durationMs: 0 }),
1963
+ taskFrameQuery ? this.runBranch("memory_task_frame_focused", () => this.args.adapter.searchMemories({
1964
+ project: scope.project,
1965
+ query: taskFrameQuery,
1966
+ user_id: scope.userId,
1967
+ session_id: scope.sessionId,
1968
+ top_k: this.focusedTopK,
1969
+ include_pending: true,
1970
+ profile: "balanced"
1971
+ })) : Promise.resolve({ name: "memory_task_frame_focused", status: "skipped", durationMs: 0 })
1972
+ ]);
1973
+ const focusedCollected = collectFromBranches(focusedBranches, "focused");
1974
+ const focusedRanked = this.rerank(focusedCollected.collected, focusedScope);
1975
+ const focusedFloored = this.applyRelevanceFloor(focusedRanked.items);
1976
+ let allCollected = [...focusedFloored.items];
1977
+ let totalOkCount = focusedCollected.okCount;
1978
+ let dedupedCount = focusedRanked.dedupedCount;
1979
+ let droppedBelowFloor = focusedFloored.dropped;
1980
+ const focusedTopScore = focusedFloored.items[0]?.score ?? 0;
1981
+ const fallbackUsed = focusedFloored.items.length < this.minFocusedResults || focusedTopScore < this.minFocusedTopScore;
1982
+ if (focusedScopeApplied) {
1983
+ this.sourceScopedTurns += 1;
1984
+ }
1985
+ if (!fallbackUsed) {
1986
+ this.focusedPassHits += 1;
1987
+ }
1988
+ const broadBranches = fallbackUsed ? await Promise.all([
1989
+ this.runBranch("context_primary_broad", () => this.args.adapter.query({
1990
+ project: scope.project,
1991
+ query: primaryQuery,
1992
+ top_k: this.broadTopK,
1993
+ include_memories: false,
1994
+ user_id: scope.userId,
1995
+ session_id: scope.sessionId,
1996
+ max_tokens: this.maxTokens,
1997
+ compress: true,
1998
+ compression_strategy: "adaptive"
1999
+ })),
2000
+ this.runBranch("memory_primary_broad", () => this.args.adapter.searchMemories({
2001
+ project: scope.project,
2002
+ query: primaryQuery,
2003
+ user_id: scope.userId,
2004
+ session_id: scope.sessionId,
2005
+ top_k: this.broadTopK,
2006
+ include_pending: true,
2007
+ profile: "balanced"
2008
+ })),
2009
+ taskFrameQuery ? this.runBranch("context_task_frame_broad", () => this.args.adapter.query({
2010
+ project: scope.project,
2011
+ query: taskFrameQuery,
2012
+ top_k: this.broadTopK,
2013
+ include_memories: false,
2014
+ user_id: scope.userId,
2015
+ session_id: scope.sessionId,
2016
+ max_tokens: this.maxTokens,
2017
+ compress: true,
2018
+ compression_strategy: "adaptive"
2019
+ })) : Promise.resolve({ name: "context_task_frame_broad", status: "skipped", durationMs: 0 }),
2020
+ taskFrameQuery ? this.runBranch("memory_task_frame_broad", () => this.args.adapter.searchMemories({
2021
+ project: scope.project,
2022
+ query: taskFrameQuery,
2023
+ user_id: scope.userId,
2024
+ session_id: scope.sessionId,
2025
+ top_k: this.broadTopK,
2026
+ include_pending: true,
2027
+ profile: "balanced"
2028
+ })) : Promise.resolve({ name: "memory_task_frame_broad", status: "skipped", durationMs: 0 })
2029
+ ]) : [
2030
+ { name: "context_primary_broad", status: "skipped", durationMs: 0 },
2031
+ { name: "memory_primary_broad", status: "skipped", durationMs: 0 },
2032
+ { name: "context_task_frame_broad", status: "skipped", durationMs: 0 },
2033
+ { name: "memory_task_frame_broad", status: "skipped", durationMs: 0 }
2034
+ ];
2035
+ const broadCollected = collectFromBranches(broadBranches, "broad");
2036
+ totalOkCount += broadCollected.okCount;
2037
+ if (fallbackUsed) {
2038
+ this.fallbackTriggers += 1;
2039
+ this.broadScopedTurns += 1;
2040
+ allCollected = [...allCollected, ...broadCollected.collected];
2041
+ }
2042
+ const ranked = this.rerank(allCollected, focusedScope);
2043
+ dedupedCount += ranked.dedupedCount;
2044
+ const floored = this.applyRelevanceFloor(ranked.items);
2045
+ droppedBelowFloor += floored.dropped;
2046
+ this.floorDroppedCount += droppedBelowFloor;
2047
+ this.droppedCount += droppedBelowFloor;
2048
+ const finalItems = floored.items.slice(0, this.broadTopK);
2049
+ this.injectedItemCount += finalItems.length;
2050
+ this.totalTurns += 1;
2051
+ const executedBranches = [...focusedBranches, ...broadBranches].filter((branch) => branch.status !== "skipped");
2052
+ for (const branch of [...focusedBranches, ...broadBranches]) {
2053
+ branchStatus[branch.name] = branch.status;
2054
+ }
2055
+ const prepared = {
2056
+ scope,
2057
+ retrieval: {
2058
+ primaryQuery,
2059
+ taskFrameQuery,
2060
+ warnings,
2061
+ degraded: totalOkCount < executedBranches.length,
2062
+ degradedReason: totalOkCount === 0 ? "all_retrieval_failed" : warnings.length > 0 ? "partial_retrieval_failed" : void 0,
2063
+ durationMs: Date.now() - startedAt,
2064
+ targetBudgetMs: this.targetRetrievalMs,
2065
+ hardTimeoutMs: this.hardRetrievalTimeoutMs,
2066
+ branchStatus,
2067
+ focusedScopeApplied,
2068
+ focusedSourceIds: focusedScope.sourceIds,
2069
+ focusedFileHints: focusedScope.fileHints.map((value) => pathBase(value)),
2070
+ clientScoped: Boolean(focusedScope.clientName),
2071
+ fallbackUsed,
2072
+ droppedBelowFloor,
2073
+ dedupedCount
2074
+ },
2075
+ context: this.buildContext(finalItems),
2076
+ items: finalItems
2077
+ };
2078
+ this.lastPreparedTurn = prepared.retrieval;
2079
+ return prepared;
2080
+ }
2081
+ async recordWork(event, context = {}) {
2082
+ const normalized = {
2083
+ ...event,
2084
+ salience: event.salience || defaultSalience(event.kind, event.success),
2085
+ timestamp: event.timestamp || nowIso()
2086
+ };
2087
+ this.pushTouchedFiles(normalized.filePaths);
2088
+ this.pushWorkEvent(normalized);
2089
+ if (normalized.salience === "low") {
2090
+ this.bufferedLowSalience = [...this.bufferedLowSalience, normalized].slice(-20);
2091
+ return { success: true, buffered: true };
2092
+ }
2093
+ const { scope } = await this.resolveScope(context);
2094
+ return this.args.adapter.addMemory({
2095
+ project: scope.project,
2096
+ user_id: scope.userId,
2097
+ session_id: scope.sessionId,
2098
+ content: `${normalized.kind}: ${normalized.summary}${normalized.details ? ` (${normalized.details})` : ""}`,
2099
+ memory_type: toMemoryType(normalized.kind),
2100
+ event_date: normalized.timestamp,
2101
+ write_mode: "async",
2102
+ metadata: {
2103
+ runtime_auto: true,
2104
+ client_name: this.clientName,
2105
+ work_event_kind: normalized.kind,
2106
+ salience: normalized.salience,
2107
+ file_paths: normalized.filePaths || [],
2108
+ tool_name: normalized.toolName,
2109
+ success: normalized.success
2110
+ }
2111
+ });
2112
+ }
2113
+ async resolveLearningScope(overrides) {
2114
+ const merged = {
2115
+ ...this.baseContext,
2116
+ ...overrides
2117
+ };
2118
+ const { scope } = await this.resolveScope(overrides);
2119
+ return {
2120
+ project: scope.project,
2121
+ sessionId: merged.sessionId || scope.sessionId,
2122
+ userId: merged.userId
2123
+ };
2124
+ }
2125
+ async afterTurn(input, context = {}) {
2126
+ this.pushTouchedFiles(input.touchedFiles);
2127
+ if (input.auto_learn === false) {
2128
+ return {
2129
+ success: true,
2130
+ sessionIngested: false,
2131
+ memoriesCreated: 0,
2132
+ relationsCreated: 0,
2133
+ invalidatedCount: 0,
2134
+ mergedCount: 0,
2135
+ droppedCount: 0,
2136
+ warnings: []
2137
+ };
2138
+ }
2139
+ const scope = await this.resolveLearningScope(context);
2140
+ const result = await this.args.adapter.ingestSession({
2141
+ project: scope.project,
2142
+ session_id: scope.sessionId,
2143
+ user_id: scope.userId,
2144
+ messages: [
2145
+ { role: "user", content: input.userMessage, timestamp: nowIso() },
2146
+ { role: "assistant", content: input.assistantMessage, timestamp: nowIso() }
2147
+ ],
2148
+ write_mode: "async"
2149
+ });
2150
+ this.mergedCount += result.memories_invalidated || 0;
2151
+ return {
2152
+ success: Boolean(result.success),
2153
+ sessionIngested: true,
2154
+ memoriesCreated: result.memories_created || 0,
2155
+ relationsCreated: result.relations_created || 0,
2156
+ invalidatedCount: result.memories_invalidated || 0,
2157
+ mergedCount: result.memories_invalidated || 0,
2158
+ droppedCount: 0,
2159
+ warnings: result.errors || []
2160
+ };
2161
+ }
2162
+ async flush(reason = "manual", context = {}) {
2163
+ if (this.bufferedLowSalience.length > 0) {
2164
+ const { scope } = await this.resolveScope(context);
2165
+ const summary = summarizeLowSalience(this.bufferedLowSalience);
2166
+ await this.args.adapter.addMemory({
2167
+ project: scope.project,
2168
+ user_id: scope.userId,
2169
+ session_id: scope.sessionId,
2170
+ content: summary,
2171
+ memory_type: "event",
2172
+ write_mode: "async",
2173
+ metadata: {
2174
+ runtime_auto: true,
2175
+ client_name: this.clientName,
2176
+ flush_reason: reason,
2177
+ salience: "low",
2178
+ summarized_count: this.bufferedLowSalience.length
2179
+ }
2180
+ });
2181
+ this.bufferedLowSalience = [];
2182
+ }
2183
+ await this.args.adapter.flushQueue();
2184
+ return this.status();
2185
+ }
2186
+ status() {
2187
+ return {
2188
+ clientName: this.clientName,
2189
+ scope: this.lastScope,
2190
+ queue: this.args.adapter.queueStatus(),
2191
+ retrieval: this.lastPreparedTurn,
2192
+ counters: {
2193
+ mergedCount: this.mergedCount,
2194
+ droppedCount: this.droppedCount,
2195
+ bufferedLowSalience: this.bufferedLowSalience.length,
2196
+ focusedPassHits: this.focusedPassHits,
2197
+ fallbackTriggers: this.fallbackTriggers,
2198
+ floorDroppedCount: this.floorDroppedCount,
2199
+ injectedItemCount: this.injectedItemCount,
2200
+ sourceScopedTurns: this.sourceScopedTurns,
2201
+ broadScopedTurns: this.broadScopedTurns,
2202
+ totalTurns: this.totalTurns
2203
+ }
2204
+ };
2205
+ }
2206
+ };
2207
+
2208
+ // ../src/sdk/whisper.ts
2209
+ var PROJECT_CACHE_TTL_MS = 3e4;
2210
+ var IDENTITY_WARNINGS = /* @__PURE__ */ new Set();
2211
+ function isLikelyProjectId(projectRef) {
2212
+ 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);
2213
+ }
2214
+ function randomRequestId() {
2215
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
2216
+ return crypto.randomUUID();
2217
+ }
2218
+ return `req_${Math.random().toString(36).slice(2, 11)}`;
2219
+ }
2220
+ function parseIdentityMode(value) {
2221
+ return value === "app-identity" ? "app-identity" : "demo-local";
2222
+ }
2223
+ function parseEnvironment(value) {
2224
+ if (value === "staging" || value === "production") return value;
2225
+ return "local";
2226
+ }
2227
+ function classifyRuntimeErrorCode(error) {
2228
+ if (error.code === "MISSING_PROJECT") return "MISSING_PROJECT";
2229
+ if (error.code === "TIMEOUT") return "TIMEOUT";
2230
+ if (error.code === "NETWORK_ERROR") return "NETWORK_ERROR";
2231
+ if (error.code === "VALIDATION_ERROR") return "VALIDATION_ERROR";
2232
+ if (error.status === 401 || error.status === 403) return "INVALID_API_KEY";
2233
+ if (error.status === 408) return "TIMEOUT";
2234
+ if (error.status === 429) return "RATE_LIMITED";
2235
+ if (error.status && error.status >= 500) return "TEMPORARY_UNAVAILABLE";
2236
+ if (error.code === "PROJECT_NOT_FOUND" || error.code === "NOT_FOUND") return "PROJECT_NOT_FOUND";
2237
+ return "REQUEST_FAILED";
2238
+ }
2239
+ var WhisperClient = class _WhisperClient {
2240
+ constructor(config) {
2241
+ this.config = config;
2242
+ const env = typeof process !== "undefined" ? process.env : {};
2243
+ this.identityMode = parseIdentityMode(config.identityMode || env.WHISPER_IDENTITY_MODE);
2244
+ this.environment = parseEnvironment(config.environment || env.WHISPER_ENV || (env.NODE_ENV === "production" ? "production" : "local"));
2245
+ this.strictIdentityMode = config.strictIdentityMode ?? env.WHISPER_DEMO_LOCAL_STRICT === "true";
2246
+ this.getIdentity = config.getIdentity;
2247
+ this.enforceIdentityModeGuardrail();
2248
+ this.diagnosticsStore = new DiagnosticsStore(config.telemetry?.maxEntries || 1e3);
2249
+ this.runtimeClient = new RuntimeClient(
2250
+ {
2251
+ apiKey: config.apiKey,
2252
+ baseUrl: config.baseUrl,
2253
+ compatMode: config.compatMode || "fallback",
2254
+ timeouts: config.timeouts,
2255
+ retryPolicy: config.retryPolicy,
2256
+ fetchImpl: config.fetch
2257
+ },
2258
+ this.diagnosticsStore
2259
+ );
2260
+ this.searchCache = new SearchResponseCache(
2261
+ config.cache?.ttlMs ?? 7e3,
2262
+ config.cache?.capacity ?? 500
2263
+ );
2264
+ const queueStore = this.createQueueStore(config);
2265
+ this.writeQueue = new WriteQueue({
2266
+ store: queueStore,
2267
+ maxBatchSize: config.queue?.maxBatchSize ?? 50,
2268
+ flushIntervalMs: config.queue?.flushIntervalMs ?? 100,
2269
+ maxAttempts: config.queue?.maxAttempts ?? 2,
2270
+ flushHandler: async (items) => {
2271
+ if (items.length === 0) return;
2272
+ const project = items[0].project;
2273
+ const memories = items.map((item) => ({
2274
+ ...item.payload,
2275
+ user_id: item.payload.user_id ?? item.userId,
2276
+ session_id: item.payload.session_id ?? item.sessionId,
2277
+ metadata: {
2278
+ ...item.payload.metadata || {},
2279
+ event_id: item.eventId,
2280
+ queued_at: item.createdAt
2281
+ }
2282
+ }));
2283
+ try {
2284
+ await this.runtimeClient.request({
2285
+ endpoint: "/v1/memory/bulk",
2286
+ method: "POST",
2287
+ operation: "bulk",
2288
+ body: {
2289
+ project,
2290
+ write_mode: "async",
2291
+ memories
2292
+ }
2293
+ });
2294
+ } catch (error) {
2295
+ if (this.runtimeClient.getCompatMode() !== "fallback" || !(error instanceof RuntimeClientError) || error.status !== 404) {
2296
+ throw error;
2297
+ }
2298
+ await Promise.all(
2299
+ memories.map(async (memory) => {
2300
+ try {
2301
+ await this.runtimeClient.request({
2302
+ endpoint: "/v1/memory",
2303
+ method: "POST",
2304
+ operation: "writeAck",
2305
+ body: {
2306
+ project,
2307
+ ...memory,
2308
+ write_mode: "sync"
2309
+ }
2310
+ });
2311
+ } catch (fallbackError) {
2312
+ if (this.runtimeClient.getCompatMode() !== "fallback" || !(fallbackError instanceof RuntimeClientError) || fallbackError.status !== 404) {
2313
+ throw fallbackError;
2314
+ }
2315
+ await this.runtimeClient.request({
2316
+ endpoint: "/v1/memories",
2317
+ method: "POST",
2318
+ operation: "writeAck",
2319
+ body: {
2320
+ project,
2321
+ ...memory,
2322
+ memory_type: memory.memory_type === "event" ? "episodic" : memory.memory_type
2323
+ }
2324
+ });
2325
+ }
2326
+ })
2327
+ );
2328
+ }
2329
+ }
2330
+ });
2331
+ if (config.queue?.enabled !== false) {
2332
+ void this.writeQueue.start();
2333
+ }
2334
+ this.memoryModule = new MemoryModule(
2335
+ this.runtimeClient,
2336
+ this.searchCache,
2337
+ this.writeQueue,
2338
+ {
2339
+ defaultProject: config.project,
2340
+ cacheEnabled: config.cache?.enabled !== false,
2341
+ queueEnabled: config.queue?.enabled !== false
2342
+ }
2343
+ );
2344
+ this.sessionModule = new SessionModule(this.memoryModule, config.project);
2345
+ this.profileModule = new ProfileModule(this.memoryModule);
2346
+ this.analyticsModule = new AnalyticsModule(this.diagnosticsStore, this.writeQueue);
2347
+ this.diagnostics = {
2348
+ getLast: (limit) => this.diagnosticsStore.getLast(limit),
2349
+ subscribe: (fn) => this.diagnosticsStore.subscribe(fn),
2350
+ snapshot: () => this.diagnosticsStore.snapshot()
2351
+ };
2352
+ this.queue = {
2353
+ flush: () => this.writeQueue.flush(),
2354
+ status: () => this.writeQueue.status()
2355
+ };
2356
+ this.memory = {
2357
+ add: (params) => this.runOrThrow(async () => this.memoryModule.add(await this.withIdentity(params))),
2358
+ addBulk: (params) => this.runOrThrow(async () => this.memoryModule.addBulk({
2359
+ ...params,
2360
+ memories: await Promise.all(params.memories.map((memory) => this.withIdentity(memory)))
2361
+ })),
2362
+ search: (params) => this.runOrThrow(async () => this.memoryModule.search(await this.withIdentity(params))),
2363
+ get: (memoryId) => this.runOrThrow(async () => this.memoryModule.get(memoryId)),
2364
+ getUserProfile: (params) => this.runOrThrow(async () => this.profileModule.getUserProfile(await this.withIdentity(params, true))),
2365
+ getSessionMemories: (params) => this.runOrThrow(async () => this.profileModule.getSessionMemories(await this.withIdentity(params))),
2366
+ update: (memoryId, params) => this.runOrThrow(async () => this.memoryModule.update(memoryId, params)),
2367
+ delete: (memoryId) => this.runOrThrow(async () => this.memoryModule.delete(memoryId)),
2368
+ flag: (params) => this.runOrThrow(async () => this.memoryModule.flag(params))
2369
+ };
2370
+ this.session = {
2371
+ start: (params) => this.runOrThrow(async () => this.sessionModule.start(await this.withSessionIdentity(params))),
2372
+ event: (params) => this.runOrThrow(async () => this.sessionModule.event(params)),
2373
+ suspend: (params) => this.runOrThrow(async () => this.sessionModule.suspend(params)),
2374
+ resume: (params) => this.runOrThrow(async () => this.sessionModule.resume(params)),
2375
+ end: (params) => this.runOrThrow(async () => this.sessionModule.end(params))
2376
+ };
2377
+ this.profile = {
2378
+ getUserProfile: (params) => this.runOrThrow(async () => this.profileModule.getUserProfile(await this.withIdentity(params, true))),
2379
+ getSessionMemories: (params) => this.runOrThrow(async () => this.profileModule.getSessionMemories(await this.withIdentity(params)))
2380
+ };
2381
+ this.analytics = {
2382
+ diagnosticsSnapshot: () => this.analyticsModule.diagnosticsSnapshot(),
2383
+ queueStatus: () => this.analyticsModule.queueStatus()
2384
+ };
2385
+ }
2386
+ diagnostics;
2387
+ queue;
2388
+ memory;
2389
+ session;
2390
+ profile;
2391
+ analytics;
2392
+ runtimeClient;
2393
+ diagnosticsStore;
2394
+ searchCache;
2395
+ writeQueue;
2396
+ memoryModule;
2397
+ sessionModule;
2398
+ profileModule;
2399
+ analyticsModule;
2400
+ projectRefToId = /* @__PURE__ */ new Map();
2401
+ identityMode;
2402
+ environment;
2403
+ strictIdentityMode;
2404
+ getIdentity;
2405
+ projectCache = [];
2406
+ projectCacheExpiresAt = 0;
2407
+ enforceIdentityModeGuardrail(requestId = randomRequestId()) {
2408
+ if (this.identityMode !== "demo-local") return;
2409
+ if (this.environment === "local") return;
2410
+ const message = "[Whisper SDK] WHISPER_IDENTITY_MODE=demo-local is intended only for local development. Switch to app-identity and provide getIdentity() or per-call user_id/session_id.";
2411
+ if (this.strictIdentityMode || this.environment === "production") {
2412
+ throw new WhisperError({
2413
+ code: "MISCONFIGURED_IDENTITY_MODE",
2414
+ message,
2415
+ retryable: false,
2416
+ hint: "Set identityMode: 'app-identity' and provide a getIdentity() function that returns { userId, sessionId? }. To override for testing, set environment: 'local' explicitly."
2417
+ });
2418
+ }
2419
+ const warningKey = `${this.environment}:${this.identityMode}`;
2420
+ if (!IDENTITY_WARNINGS.has(warningKey)) {
2421
+ IDENTITY_WARNINGS.add(warningKey);
2422
+ if (typeof console !== "undefined" && typeof console.warn === "function") {
2423
+ console.warn(`${message} requestId=${requestId}`);
2424
+ }
2425
+ }
2426
+ }
2427
+ toWhisperError(error, hint) {
2428
+ if (error instanceof WhisperError) return error;
2429
+ if (error instanceof RuntimeClientError) {
2430
+ return new WhisperError({
2431
+ code: classifyRuntimeErrorCode(error),
2432
+ message: error.message,
2433
+ status: error.status,
2434
+ retryable: error.retryable,
2435
+ hint: error.hint || hint,
2436
+ requestId: error.requestId || error.traceId,
2437
+ details: error.details,
2438
+ cause: error
2439
+ });
2440
+ }
2441
+ if (error instanceof Error) {
2442
+ return new WhisperError({
2443
+ code: "REQUEST_FAILED",
2444
+ message: error.message,
2445
+ retryable: false,
2446
+ hint,
2447
+ cause: error
2448
+ });
2449
+ }
2450
+ return new WhisperError({
2451
+ code: "REQUEST_FAILED",
2452
+ message: "Unknown SDK error",
2453
+ retryable: false,
2454
+ hint,
2455
+ details: error
2456
+ });
2457
+ }
2458
+ async runOrThrow(work, hint) {
2459
+ try {
2460
+ return await work();
2461
+ } catch (error) {
2462
+ throw this.toWhisperError(error, hint);
2463
+ }
2464
+ }
2465
+ async resolveIdentityOverride() {
2466
+ if (!this.getIdentity) return null;
2467
+ const resolved = await this.getIdentity();
2468
+ const userId = String(resolved?.userId || "").trim();
2469
+ const sessionId = resolved?.sessionId ? String(resolved.sessionId).trim() : void 0;
2470
+ if (!userId) {
2471
+ throw new WhisperError({
2472
+ code: "AUTH_IDENTITY_INVALID",
2473
+ message: "getIdentity() returned an invalid identity payload.",
2474
+ retryable: false,
2475
+ hint: "Return { userId, sessionId? } from getIdentity()."
2476
+ });
2477
+ }
2478
+ return {
2479
+ userId,
2480
+ sessionId: sessionId || void 0
2481
+ };
2482
+ }
2483
+ async withIdentity(params, requireUser = false) {
2484
+ const currentUser = params.user_id ? String(params.user_id).trim() : "";
2485
+ const currentSession = params.session_id ? String(params.session_id).trim() : "";
2486
+ if (currentUser) {
2487
+ return {
2488
+ ...params,
2489
+ user_id: currentUser,
2490
+ session_id: currentSession || params.session_id
2491
+ };
2492
+ }
2493
+ const resolved = await this.resolveIdentityOverride();
2494
+ if (resolved) {
2495
+ return {
2496
+ ...params,
2497
+ user_id: resolved.userId,
2498
+ session_id: currentSession || resolved.sessionId
2499
+ };
2500
+ }
2501
+ if (requireUser || this.identityMode === "app-identity") {
2502
+ throw new WhisperError({
2503
+ code: "AUTH_IDENTITY_REQUIRED",
2504
+ message: "A user identity is required in app-identity mode.",
2505
+ retryable: false,
2506
+ hint: "Provide user_id/session_id per call or configure getIdentity() in WhisperClient."
2507
+ });
2508
+ }
2509
+ return params;
2510
+ }
2511
+ async withSessionIdentity(params) {
2512
+ const userId = params.userId ? String(params.userId).trim() : "";
2513
+ const sessionId = params.sessionId ? String(params.sessionId).trim() : "";
2514
+ if (userId) {
2515
+ return {
2516
+ ...params,
2517
+ userId,
2518
+ sessionId: sessionId || params.sessionId
2519
+ };
2520
+ }
2521
+ const resolved = await this.resolveIdentityOverride();
2522
+ if (resolved?.userId) {
2523
+ return {
2524
+ ...params,
2525
+ userId: resolved.userId,
2526
+ sessionId: sessionId || resolved.sessionId
2527
+ };
2528
+ }
2529
+ throw new WhisperError({
2530
+ code: "AUTH_IDENTITY_REQUIRED",
2531
+ message: "Session operations require a user identity.",
2532
+ retryable: false,
2533
+ hint: "Pass userId explicitly or configure getIdentity() in WhisperClient."
2534
+ });
2535
+ }
2536
+ static fromEnv(overrides = {}) {
2537
+ const env = typeof process !== "undefined" ? process.env : {};
2538
+ const apiKey = overrides.apiKey || env.WHISPER_API_KEY || env.USEWHISPER_API_KEY || env.API_KEY;
2539
+ if (!apiKey) {
2540
+ throw new WhisperError({
2541
+ code: "INVALID_API_KEY",
2542
+ message: "Missing API key. Set WHISPER_API_KEY / USEWHISPER_API_KEY / API_KEY.",
2543
+ retryable: false
2544
+ });
2545
+ }
2546
+ return new _WhisperClient({
2547
+ apiKey,
2548
+ baseUrl: overrides.baseUrl || env.WHISPER_BASE_URL || env.API_BASE_URL || "https://context.usewhisper.dev",
2549
+ project: overrides.project || env.WHISPER_PROJECT || env.PROJECT,
2550
+ identityMode: overrides.identityMode || parseIdentityMode(env.WHISPER_IDENTITY_MODE),
2551
+ environment: overrides.environment || parseEnvironment(env.WHISPER_ENV || (env.NODE_ENV === "production" ? "production" : "local")),
2552
+ strictIdentityMode: overrides.strictIdentityMode ?? env.WHISPER_DEMO_LOCAL_STRICT === "true",
2553
+ ...overrides
2554
+ });
2555
+ }
2556
+ createQueueStore(config) {
2557
+ const persistence = config.queue?.persistence || this.defaultQueuePersistence();
2558
+ if (persistence === "storage") {
2559
+ return createStorageQueueStore();
2560
+ }
2561
+ if (persistence === "file") {
2562
+ const filePath = config.queue?.filePath || this.defaultQueueFilePath();
2563
+ if (filePath) {
2564
+ return createFileQueueStore(filePath);
2565
+ }
2566
+ }
2567
+ return new InMemoryQueueStore();
2568
+ }
2569
+ defaultQueuePersistence() {
2570
+ const maybeWindow = globalThis.window;
2571
+ if (maybeWindow && typeof maybeWindow === "object") {
2572
+ const maybeStorage = globalThis.localStorage;
2573
+ return maybeStorage && typeof maybeStorage === "object" ? "storage" : "memory";
2574
+ }
2575
+ return "file";
2576
+ }
2577
+ defaultQueueFilePath() {
2578
+ if (typeof process === "undefined") return void 0;
2579
+ const path = process.env.WHISPER_QUEUE_FILE_PATH;
2580
+ if (path) return path;
2581
+ const home = process.env.USERPROFILE || process.env.HOME;
2582
+ if (!home) return void 0;
2583
+ const normalizedHome = home.replace(/[\\\/]+$/, "");
2584
+ return `${normalizedHome}/.whisper/sdk/queue.json`;
2585
+ }
2586
+ getRequiredProject(project) {
2587
+ const resolved = project || this.config.project;
2588
+ if (!resolved) {
2589
+ throw new WhisperError({
2590
+ code: "MISSING_PROJECT",
2591
+ message: "Project is required",
2592
+ retryable: false,
2593
+ hint: "Pass project in the call or configure a default project in WhisperClient."
2594
+ });
2595
+ }
2596
+ return resolved;
2597
+ }
2598
+ async refreshProjectCache(force = false) {
2599
+ if (!force && Date.now() < this.projectCacheExpiresAt && this.projectCache.length > 0) {
2600
+ return this.projectCache;
2601
+ }
2602
+ const response = await this.runtimeClient.request({
2603
+ endpoint: "/v1/projects",
2604
+ method: "GET",
2605
+ operation: "get",
2606
+ idempotent: true
2607
+ });
2608
+ this.projectRefToId.clear();
2609
+ this.projectCache = response.data?.projects || [];
2610
+ for (const project of this.projectCache) {
2611
+ this.projectRefToId.set(project.id, project.id);
2612
+ this.projectRefToId.set(project.slug, project.id);
2613
+ this.projectRefToId.set(project.name, project.id);
2614
+ }
2615
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
2616
+ return this.projectCache;
2617
+ }
2618
+ async fetchResolvedProject(projectRef) {
2619
+ try {
2620
+ const response = await this.runtimeClient.request({
2621
+ endpoint: `/v1/projects/resolve?project=${encodeURIComponent(projectRef)}`,
2622
+ method: "GET",
2623
+ operation: "get",
2624
+ idempotent: true
2625
+ });
2626
+ return response.data?.resolved || null;
2627
+ } catch (error) {
2628
+ if (error instanceof RuntimeClientError && error.status === 404) {
2629
+ return null;
2630
+ }
2631
+ throw error;
2632
+ }
2633
+ }
2634
+ async resolveProject(projectRef) {
2635
+ return this.runOrThrow(async () => {
2636
+ const resolvedRef = this.getRequiredProject(projectRef);
2637
+ const cachedProjects = await this.refreshProjectCache(false);
2638
+ const cachedProject = cachedProjects.find(
2639
+ (project) => project.id === resolvedRef || project.slug === resolvedRef || project.name === resolvedRef
2640
+ );
2641
+ if (cachedProject) {
2642
+ return cachedProject;
2643
+ }
2644
+ const resolvedProject = await this.fetchResolvedProject(resolvedRef);
2645
+ if (resolvedProject) {
2646
+ this.projectRefToId.set(resolvedProject.id, resolvedProject.id);
2647
+ this.projectRefToId.set(resolvedProject.slug, resolvedProject.id);
2648
+ this.projectRefToId.set(resolvedProject.name, resolvedProject.id);
2649
+ this.projectCache = [
2650
+ ...this.projectCache.filter((project) => project.id !== resolvedProject.id),
2651
+ resolvedProject
2652
+ ];
2653
+ this.projectCacheExpiresAt = Date.now() + PROJECT_CACHE_TTL_MS;
2654
+ return resolvedProject;
2655
+ }
2656
+ if (isLikelyProjectId(resolvedRef)) {
2657
+ return {
2658
+ id: resolvedRef,
2659
+ orgId: "",
2660
+ name: resolvedRef,
2661
+ slug: resolvedRef,
2662
+ createdAt: (/* @__PURE__ */ new Date(0)).toISOString(),
2663
+ updatedAt: (/* @__PURE__ */ new Date(0)).toISOString()
2664
+ };
2665
+ }
2666
+ throw new WhisperError({
2667
+ code: "PROJECT_NOT_FOUND",
2668
+ message: `Project '${resolvedRef}' not found`,
2669
+ retryable: false
2670
+ });
2671
+ });
2672
+ }
2673
+ async preflight(options) {
2674
+ const requestId = randomRequestId();
2675
+ const checks = [];
2676
+ try {
2677
+ this.enforceIdentityModeGuardrail(requestId);
2678
+ } catch (error) {
2679
+ throw this.toWhisperError(error, "Update identity mode before running preflight.");
2680
+ }
2681
+ const apiKeyOk = typeof this.config.apiKey === "string" && this.config.apiKey.trim().length > 0;
2682
+ checks.push({
2683
+ check: "api_key",
2684
+ ok: apiKeyOk,
2685
+ message: apiKeyOk ? "API key is configured." : "Missing API key.",
2686
+ hint: apiKeyOk ? void 0 : "Set WHISPER_API_KEY or pass apiKey to WhisperClient."
2687
+ });
2688
+ try {
2689
+ await this.runtimeClient.request({
2690
+ endpoint: "/v1/projects",
2691
+ method: "GET",
2692
+ operation: "get",
2693
+ idempotent: true,
2694
+ traceId: requestId
2695
+ });
2696
+ checks.push({
2697
+ check: "api_connectivity",
2698
+ ok: true,
2699
+ message: "Connected to Whisper API."
2700
+ });
2701
+ } catch (error) {
2702
+ const mapped = this.toWhisperError(error, "Confirm WHISPER_BASE_URL and API key permissions.");
2703
+ checks.push({
2704
+ check: "api_connectivity",
2705
+ ok: false,
2706
+ message: mapped.message,
2707
+ hint: mapped.hint
2708
+ });
2709
+ }
2710
+ const projectRef = options?.project || this.config.project;
2711
+ if (projectRef) {
2712
+ try {
2713
+ await this.resolveProject(projectRef);
2714
+ checks.push({
2715
+ check: "project_access",
2716
+ ok: true,
2717
+ message: `Project '${projectRef}' is reachable.`
2718
+ });
2719
+ } catch (error) {
2720
+ const mapped = this.toWhisperError(error, "Create or grant access to the configured project.");
2721
+ checks.push({
2722
+ check: "project_access",
2723
+ ok: false,
2724
+ message: mapped.message,
2725
+ hint: mapped.hint
2726
+ });
2727
+ }
2728
+ } else {
2729
+ checks.push({
2730
+ check: "project_access",
2731
+ ok: true,
2732
+ message: "No default project configured (project will be required per call)."
2733
+ });
2734
+ }
2735
+ if (options?.requireIdentity || this.identityMode === "app-identity") {
2736
+ try {
2737
+ const identity = await this.resolveIdentityOverride();
2738
+ const ok = Boolean(identity?.userId);
2739
+ checks.push({
2740
+ check: "identity_resolution",
2741
+ ok,
2742
+ message: ok ? "Identity resolver is configured." : "Identity resolver is missing.",
2743
+ hint: ok ? void 0 : "Provide getIdentity() or pass user_id/session_id per call."
2744
+ });
2745
+ } catch (error) {
2746
+ const mapped = this.toWhisperError(error, "Fix identity resolver output before production usage.");
2747
+ checks.push({
2748
+ check: "identity_resolution",
2749
+ ok: false,
2750
+ message: mapped.message,
2751
+ hint: mapped.hint
2752
+ });
2753
+ }
2754
+ }
2755
+ return {
2756
+ ok: checks.every((check) => check.ok),
2757
+ checks,
2758
+ requestId,
2759
+ identityMode: this.identityMode,
2760
+ environment: this.environment
2761
+ };
2762
+ }
2763
+ async query(params) {
2764
+ return this.runOrThrow(async () => {
2765
+ const identityParams = await this.withIdentity(params);
2766
+ const project = (await this.resolveProject(identityParams.project)).id;
2767
+ const response = await this.runtimeClient.request({
2768
+ endpoint: "/v1/context/query",
2769
+ method: "POST",
2770
+ operation: "search",
2771
+ body: {
2772
+ ...identityParams,
2773
+ project
2774
+ },
2775
+ idempotent: true
2776
+ });
2777
+ return response.data;
2778
+ });
2779
+ }
2780
+ async ingestSession(params) {
2781
+ return this.runOrThrow(async () => {
2782
+ const identityParams = await this.withIdentity(params);
2783
+ const project = (await this.resolveProject(identityParams.project)).id;
2784
+ const response = await this.runtimeClient.request({
2785
+ endpoint: "/v1/memory/ingest/session",
2786
+ method: "POST",
2787
+ operation: "session",
2788
+ body: {
2789
+ ...identityParams,
2790
+ project
2791
+ }
2792
+ });
2793
+ return response.data;
2794
+ });
2795
+ }
2796
+ async learn(params) {
2797
+ return this.runOrThrow(async () => {
2798
+ const identityParams = params.mode === "conversation" ? await this.withIdentity(params) : params;
2799
+ const project = (await this.resolveProject(identityParams.project)).id;
2800
+ const response = await this.runtimeClient.request({
2801
+ endpoint: "/v1/learn",
2802
+ method: "POST",
2803
+ operation: params.mode === "conversation" ? "session" : "bulk",
2804
+ body: {
2805
+ ...identityParams,
2806
+ project
2807
+ }
2808
+ });
2809
+ return response.data;
2810
+ });
2811
+ }
2812
+ createAgentRuntime(options = {}) {
2813
+ const baseContext = {
2814
+ workspacePath: options.workspacePath,
2815
+ project: options.project || this.config.project,
2816
+ userId: options.userId,
2817
+ sessionId: options.sessionId,
2818
+ traceId: options.traceId,
2819
+ clientName: options.clientName
2820
+ };
2821
+ return new WhisperAgentRuntime({
2822
+ baseContext,
2823
+ options,
2824
+ adapter: {
2825
+ resolveProject: (project) => this.resolveProject(project),
2826
+ query: (params) => this.query(params),
2827
+ ingestSession: (params) => this.ingestSession(params),
2828
+ getSessionMemories: (params) => this.memory.getSessionMemories(params),
2829
+ getUserProfile: (params) => this.memory.getUserProfile(params),
2830
+ searchMemories: (params) => this.memory.search(params),
2831
+ addMemory: (params) => this.memory.add(params),
2832
+ queueStatus: () => this.queue.status(),
2833
+ flushQueue: () => this.queue.flush()
2834
+ }
2835
+ });
2836
+ }
2837
+ withRunContext(context) {
2838
+ const base = this;
2839
+ return {
2840
+ memory: {
2841
+ add: (params) => base.memory.add({
2842
+ ...params,
2843
+ project: params.project || context.project || base.config.project,
2844
+ user_id: params.user_id || context.userId,
2845
+ session_id: params.session_id || context.sessionId
2846
+ }),
2847
+ search: (params) => base.memory.search({
2848
+ ...params,
2849
+ project: params.project || context.project || base.config.project,
2850
+ user_id: params.user_id || context.userId,
2851
+ session_id: params.session_id || context.sessionId
2852
+ })
2853
+ },
2854
+ session: {
2855
+ event: (params) => base.session.event({
2856
+ ...params,
2857
+ sessionId: params.sessionId || context.sessionId || ""
2858
+ })
2859
+ },
2860
+ learn: (params) => base.learn({
2861
+ ...params,
2862
+ project: params.project || context.project || base.config.project,
2863
+ ...params.mode === "conversation" ? {
2864
+ user_id: params.user_id ?? context.userId,
2865
+ session_id: params.session_id || context.sessionId || ""
2866
+ } : {}
2867
+ }),
2868
+ queue: base.queue,
2869
+ diagnostics: base.diagnostics
2870
+ };
2871
+ }
2872
+ async deleteSource(sourceId) {
2873
+ return this.runOrThrow(async () => {
2874
+ const response = await this.runtimeClient.request({
2875
+ endpoint: `/v1/sources/${sourceId}`,
2876
+ method: "DELETE",
2877
+ operation: "writeAck"
2878
+ });
2879
+ return response.data;
2880
+ });
2881
+ }
2882
+ async extractMemories(params) {
2883
+ return this.runOrThrow(async () => {
2884
+ const project = (await this.resolveProject(params.project)).id;
2885
+ const response = await this.runtimeClient.request({
2886
+ endpoint: "/v1/memory/extract",
2887
+ method: "POST",
2888
+ operation: "writeAck",
2889
+ body: { project, message: params.message }
2890
+ });
2891
+ return response.data;
2892
+ });
2893
+ }
2894
+ async shutdown() {
2895
+ await this.writeQueue.stop();
2896
+ }
2897
+ };
2898
+
2899
+ // ../src/sdk/router/memory-router.ts
2900
+ function randomRequestId2() {
2901
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
2902
+ return crypto.randomUUID();
2903
+ }
2904
+ return `req_${Math.random().toString(36).slice(2, 11)}`;
2905
+ }
2906
+ function normalizeProviderUrl(baseUrl, routePath) {
2907
+ const trimmedBase = baseUrl.replace(/\/+$/, "");
2908
+ const normalizedPath = routePath.startsWith("/") ? routePath : `/${routePath}`;
2909
+ return `${trimmedBase}${normalizedPath}`;
2910
+ }
2911
+ function trimText(value) {
2912
+ return typeof value === "string" ? value.trim() : "";
2913
+ }
2914
+ function extractUserPrompt(messages) {
2915
+ if (!Array.isArray(messages)) return "";
2916
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
2917
+ const item = messages[i];
2918
+ if (!item || typeof item !== "object") continue;
2919
+ if (trimText(item.role).toLowerCase() !== "user") continue;
2920
+ const content = item.content;
2921
+ if (typeof content === "string" && content.trim()) return content.trim();
2922
+ if (Array.isArray(content)) {
2923
+ const text = content.map((part) => {
2924
+ if (typeof part === "string") return part;
2925
+ if (!part || typeof part !== "object") return "";
2926
+ const record = part;
2927
+ if (trimText(record.type).toLowerCase() !== "text") return "";
2928
+ return trimText(record.text);
2929
+ }).filter((part) => part.length > 0).join("\n");
2930
+ if (text) return text;
2931
+ }
2932
+ }
2933
+ return "";
2934
+ }
2935
+ function buildClient(config) {
2936
+ if (config.client) return config.client;
2937
+ const env = typeof process !== "undefined" ? process.env : {};
2938
+ const apiKey = config.apiKey || env.WHISPER_API_KEY || env.USEWHISPER_API_KEY || env.API_KEY;
2939
+ if (!apiKey) {
2940
+ throw new WhisperError({
2941
+ code: "INVALID_API_KEY",
2942
+ message: "Missing API key. Pass apiKey to createMemoryRouter(...) or set WHISPER_API_KEY.",
2943
+ retryable: false
2944
+ });
2945
+ }
2946
+ return new WhisperClient({
2947
+ apiKey,
2948
+ baseUrl: config.baseUrl,
2949
+ project: config.project,
2950
+ identityMode: config.identityMode,
2951
+ getIdentity: config.getIdentity,
2952
+ environment: config.environment,
2953
+ strictIdentityMode: config.strictIdentityMode,
2954
+ compatMode: config.compatMode,
2955
+ fetch: config.fetch,
2956
+ timeouts: config.timeouts,
2957
+ retryPolicy: config.retryPolicy,
2958
+ cache: config.cache,
2959
+ queue: config.queue,
2960
+ telemetry: config.telemetry
2961
+ });
2962
+ }
2963
+ function ensureBetaEnabled(_explicitBeta) {
2964
+ }
2965
+ var WhisperMemoryRouter = class {
2966
+ config;
2967
+ providerUrl;
2968
+ fetchImpl;
2969
+ client = null;
2970
+ lastTrace = null;
2971
+ constructor(config) {
2972
+ ensureBetaEnabled(config.beta);
2973
+ this.config = config;
2974
+ this.providerUrl = normalizeProviderUrl(
2975
+ config.providerBaseUrl,
2976
+ config.routePath || "/v1/chat/completions"
2977
+ );
2978
+ this.fetchImpl = config.fetch || fetch;
2979
+ }
2980
+ getClient() {
2981
+ if (!this.client) this.client = buildClient(this.config);
2982
+ return this.client;
2983
+ }
2984
+ getLastTrace() {
2985
+ return this.lastTrace ? { ...this.lastTrace } : null;
2986
+ }
2987
+ async chatCompletions(payload) {
2988
+ const requestId = randomRequestId2();
2989
+ const messages = Array.isArray(payload.messages) ? payload.messages : [];
2990
+ const prompt = extractUserPrompt(messages);
2991
+ const contextPrefix = this.config.contextPrefix || "Relevant memory context:";
2992
+ let usedMemory = false;
2993
+ let fallbackReason = "none";
2994
+ let contextText = "";
2995
+ if (!prompt) {
2996
+ fallbackReason = "no_user_prompt";
2997
+ } else {
2998
+ try {
2999
+ const queryResult = await this.getClient().query({
3000
+ query: prompt,
3001
+ project: this.config.project,
3002
+ user_id: trimText(payload.user) || void 0
3003
+ });
3004
+ if (queryResult.context && queryResult.context.trim()) {
3005
+ contextText = queryResult.context.trim();
3006
+ usedMemory = true;
3007
+ }
3008
+ } catch (error) {
3009
+ if (this.config.bestEffort === false) {
3010
+ throw error;
3011
+ }
3012
+ fallbackReason = "memory_query_failed";
3013
+ }
3014
+ }
3015
+ const providerPayload = {
3016
+ ...payload,
3017
+ messages: usedMemory ? [
3018
+ {
3019
+ role: "system",
3020
+ content: `${contextPrefix}
3021
+ ${contextText}`
3022
+ },
3023
+ ...messages
3024
+ ] : messages
3025
+ };
3026
+ const env = typeof process !== "undefined" ? process.env : {};
3027
+ const providerApiKey = this.config.providerApiKey || env.MEMORY_ROUTER_PROVIDER_API_KEY || "";
3028
+ const headers = {
3029
+ "content-type": "application/json",
3030
+ "x-whisper-memory-router": "beta",
3031
+ "x-whisper-request-id": requestId,
3032
+ ...this.config.providerHeaders || {}
3033
+ };
3034
+ if (providerApiKey) {
3035
+ headers.Authorization = providerApiKey.startsWith("Bearer ") ? providerApiKey : `Bearer ${providerApiKey}`;
3036
+ }
3037
+ const response = await this.fetchImpl(this.providerUrl, {
3038
+ method: "POST",
3039
+ headers,
3040
+ body: JSON.stringify(providerPayload)
3041
+ });
3042
+ const parsed = await response.json().catch(async () => await response.text().catch(() => null));
3043
+ const trace = {
3044
+ providerUrl: this.providerUrl,
3045
+ requestId,
3046
+ usedMemory,
3047
+ fallbackReason,
3048
+ providerStatus: response.status,
3049
+ model: trimText(payload.model) || void 0
3050
+ };
3051
+ this.lastTrace = trace;
3052
+ if (this.config.logger) this.config.logger(trace);
3053
+ if (!response.ok) {
3054
+ throw new WhisperError({
3055
+ code: response.status >= 500 ? "TEMPORARY_UNAVAILABLE" : "REQUEST_FAILED",
3056
+ status: response.status,
3057
+ message: typeof parsed === "object" && parsed && "error" in parsed ? String(parsed.error?.message || "Provider request failed") : "Provider request failed",
3058
+ retryable: response.status >= 500 || response.status === 429,
3059
+ requestId,
3060
+ details: {
3061
+ providerUrl: this.providerUrl,
3062
+ body: parsed,
3063
+ trace
3064
+ }
3065
+ });
3066
+ }
3067
+ return {
3068
+ status: response.status,
3069
+ data: parsed,
3070
+ trace
3071
+ };
3072
+ }
3073
+ };
3074
+ function createMemoryRouter(config) {
3075
+ return new WhisperMemoryRouter(config);
3076
+ }
3077
+ export {
3078
+ WhisperMemoryRouter,
3079
+ createMemoryRouter
3080
+ };