@usewhisper/sdk 3.10.0 → 3.11.0

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