@radaros/core 0.3.6 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/agent/agent.ts
2
- import { v4 as uuidv42 } from "uuid";
2
+ import { v4 as uuidv43 } from "uuid";
3
3
 
4
4
  // src/events/event-bus.ts
5
5
  import { EventEmitter } from "events";
@@ -65,9 +65,11 @@ var InMemoryStorage = class {
65
65
  // src/session/session-manager.ts
66
66
  var NAMESPACE = "sessions";
67
67
  var SessionManager = class {
68
- constructor(storage) {
68
+ constructor(storage, config) {
69
69
  this.storage = storage;
70
+ this.maxMessages = config?.maxMessages;
70
71
  }
72
+ maxMessages;
71
73
  async getOrCreate(sessionId, userId) {
72
74
  const existing = await this.storage.get(NAMESPACE, sessionId);
73
75
  if (existing) return existing;
@@ -83,16 +85,21 @@ var SessionManager = class {
83
85
  return session;
84
86
  }
85
87
  async appendMessage(sessionId, msg) {
86
- const session = await this.getOrCreate(sessionId);
87
- session.messages.push(msg);
88
- session.updatedAt = /* @__PURE__ */ new Date();
89
- await this.storage.set(NAMESPACE, sessionId, session);
88
+ return this.appendMessages(sessionId, [msg]);
90
89
  }
91
90
  async appendMessages(sessionId, msgs) {
92
91
  const session = await this.getOrCreate(sessionId);
93
92
  session.messages.push(...msgs);
93
+ let overflow = [];
94
+ if (this.maxMessages && session.messages.length > this.maxMessages) {
95
+ overflow = session.messages.splice(
96
+ 0,
97
+ session.messages.length - this.maxMessages
98
+ );
99
+ }
94
100
  session.updatedAt = /* @__PURE__ */ new Date();
95
101
  await this.storage.set(NAMESPACE, sessionId, session);
102
+ return { overflow };
96
103
  }
97
104
  async getHistory(sessionId, limit) {
98
105
  const session = await this.storage.get(NAMESPACE, sessionId);
@@ -119,17 +126,224 @@ var SessionManager = class {
119
126
 
120
127
  // src/tools/tool-executor.ts
121
128
  import { createRequire } from "module";
129
+
130
+ // src/tools/sandbox.ts
131
+ import { fork } from "child_process";
132
+ import { fileURLToPath } from "url";
133
+ import path from "path";
134
+ var DEFAULT_TIMEOUT = 3e4;
135
+ var DEFAULT_MAX_MEMORY_MB = 256;
136
+ function resolveSandboxConfig(toolLevel, agentLevel) {
137
+ if (toolLevel === false) return null;
138
+ const effective = toolLevel ?? agentLevel;
139
+ if (!effective) return null;
140
+ if (effective === true) return { enabled: true };
141
+ if (effective.enabled === false) return null;
142
+ return { ...effective, enabled: true };
143
+ }
144
+ var Sandbox = class {
145
+ config;
146
+ constructor(config) {
147
+ this.config = {
148
+ ...config,
149
+ enabled: config.enabled !== false,
150
+ timeout: config.timeout ?? DEFAULT_TIMEOUT,
151
+ maxMemoryMB: config.maxMemoryMB ?? DEFAULT_MAX_MEMORY_MB
152
+ };
153
+ }
154
+ async execute(toolExecuteFn, args, ctx) {
155
+ const fnSource = toolExecuteFn.toString();
156
+ const wrappedBody = `
157
+ const __fn = ${fnSource};
158
+ return await __fn(args, {});
159
+ `;
160
+ return new Promise((resolve, reject) => {
161
+ const workerPath = this.getWorkerPath();
162
+ const execArgv = [
163
+ `--max-old-space-size=${this.config.maxMemoryMB}`
164
+ ];
165
+ const env = {};
166
+ if (this.config.env) {
167
+ for (const [key, val] of Object.entries(this.config.env)) {
168
+ env[key] = val;
169
+ }
170
+ }
171
+ if (!this.config.allowNetwork) {
172
+ env.__SANDBOX_NO_NETWORK = "1";
173
+ }
174
+ const child = fork(workerPath, [], {
175
+ execArgv,
176
+ env: { ...env, NODE_OPTIONS: execArgv.join(" ") },
177
+ stdio: ["pipe", "pipe", "pipe", "ipc"],
178
+ serialization: "json"
179
+ });
180
+ const timer = setTimeout(() => {
181
+ child.kill("SIGKILL");
182
+ reject(new Error(`Sandbox execution timed out after ${this.config.timeout}ms`));
183
+ }, this.config.timeout);
184
+ child.on("message", (msg) => {
185
+ clearTimeout(timer);
186
+ if (msg.type === "result") {
187
+ resolve(msg.value);
188
+ } else if (msg.type === "error") {
189
+ reject(new Error(`Sandbox error: ${msg.message}`));
190
+ }
191
+ child.kill();
192
+ });
193
+ child.on("error", (err) => {
194
+ clearTimeout(timer);
195
+ reject(new Error(`Sandbox process error: ${err.message}`));
196
+ });
197
+ child.on("exit", (code, signal) => {
198
+ clearTimeout(timer);
199
+ if (signal === "SIGKILL") return;
200
+ if (code !== 0 && code !== null) {
201
+ reject(new Error(`Sandbox process exited with code ${code}`));
202
+ }
203
+ });
204
+ child.send({
205
+ type: "execute",
206
+ functionBody: wrappedBody,
207
+ args
208
+ });
209
+ });
210
+ }
211
+ getWorkerPath() {
212
+ const currentFile = fileURLToPath(import.meta.url);
213
+ const distDir = path.dirname(currentFile);
214
+ return path.join(distDir, "tools", "sandbox-worker.js");
215
+ }
216
+ };
217
+
218
+ // src/tools/approval.ts
219
+ import { v4 as uuidv4 } from "uuid";
220
+ var DEFAULT_TIMEOUT2 = 3e5;
221
+ var ApprovalManager = class {
222
+ policy;
223
+ onApproval;
224
+ timeout;
225
+ eventBus;
226
+ pending = /* @__PURE__ */ new Map();
227
+ constructor(config) {
228
+ this.policy = config.policy;
229
+ this.onApproval = config.onApproval;
230
+ this.timeout = config.timeout ?? DEFAULT_TIMEOUT2;
231
+ this.eventBus = config.eventBus;
232
+ }
233
+ needsApproval(toolName, args, toolRequiresApproval) {
234
+ if (toolRequiresApproval !== void 0) {
235
+ if (typeof toolRequiresApproval === "function") {
236
+ return toolRequiresApproval(args);
237
+ }
238
+ return toolRequiresApproval;
239
+ }
240
+ if (this.policy === "none") return false;
241
+ if (this.policy === "all") return true;
242
+ return this.policy.includes(toolName);
243
+ }
244
+ async check(toolName, args, ctx, agentName) {
245
+ const request = {
246
+ requestId: uuidv4(),
247
+ toolName,
248
+ args,
249
+ agentName,
250
+ runId: ctx.runId
251
+ };
252
+ this.eventBus?.emit("tool.approval.request", request);
253
+ if (this.onApproval) {
254
+ return this.callbackMode(request);
255
+ }
256
+ return this.eventMode(request);
257
+ }
258
+ /** Externally approve a pending request (for event-driven mode). */
259
+ approve(requestId, reason) {
260
+ const entry = this.pending.get(requestId);
261
+ if (!entry) return;
262
+ clearTimeout(entry.timer);
263
+ this.pending.delete(requestId);
264
+ const decision = { approved: true, reason };
265
+ this.eventBus?.emit("tool.approval.response", { requestId, ...decision });
266
+ entry.resolve(decision);
267
+ }
268
+ /** Externally deny a pending request (for event-driven mode). */
269
+ deny(requestId, reason) {
270
+ const entry = this.pending.get(requestId);
271
+ if (!entry) return;
272
+ clearTimeout(entry.timer);
273
+ this.pending.delete(requestId);
274
+ const decision = { approved: false, reason };
275
+ this.eventBus?.emit("tool.approval.response", { requestId, ...decision });
276
+ entry.resolve(decision);
277
+ }
278
+ async callbackMode(request) {
279
+ const timer = setTimeout(() => {
280
+ }, this.timeout);
281
+ try {
282
+ const result = await Promise.race([
283
+ this.onApproval(request),
284
+ new Promise(
285
+ (resolve) => setTimeout(
286
+ () => resolve({ approved: false, reason: "Approval timed out" }),
287
+ this.timeout
288
+ )
289
+ )
290
+ ]);
291
+ this.eventBus?.emit("tool.approval.response", {
292
+ requestId: request.requestId,
293
+ ...result
294
+ });
295
+ return result;
296
+ } finally {
297
+ clearTimeout(timer);
298
+ }
299
+ }
300
+ eventMode(request) {
301
+ return new Promise((resolve) => {
302
+ const timer = setTimeout(() => {
303
+ this.pending.delete(request.requestId);
304
+ const decision = {
305
+ approved: false,
306
+ reason: "Approval timed out"
307
+ };
308
+ this.eventBus?.emit("tool.approval.response", {
309
+ requestId: request.requestId,
310
+ ...decision
311
+ });
312
+ resolve(decision);
313
+ }, this.timeout);
314
+ this.pending.set(request.requestId, { resolve, timer });
315
+ });
316
+ }
317
+ };
318
+
319
+ // src/tools/tool-executor.ts
122
320
  var _require = createRequire(import.meta.url);
123
321
  var ToolExecutor = class {
124
322
  tools;
125
323
  concurrency;
126
324
  cache = /* @__PURE__ */ new Map();
127
325
  cachedDefs = null;
128
- constructor(tools, concurrency = 5) {
326
+ agentSandbox;
327
+ approvalManager;
328
+ agentName;
329
+ constructor(tools, configOrConcurrency) {
129
330
  this.tools = new Map(tools.map((t) => [t.name, t]));
130
- this.concurrency = concurrency;
331
+ if (typeof configOrConcurrency === "number" || configOrConcurrency === void 0) {
332
+ this.concurrency = configOrConcurrency ?? 5;
333
+ this.agentName = "";
334
+ } else {
335
+ this.concurrency = configOrConcurrency.concurrency ?? 5;
336
+ this.agentSandbox = configOrConcurrency.sandbox;
337
+ this.agentName = configOrConcurrency.agentName ?? "";
338
+ if (configOrConcurrency.approval && configOrConcurrency.approval.policy !== "none") {
339
+ this.approvalManager = new ApprovalManager(configOrConcurrency.approval);
340
+ }
341
+ }
131
342
  this.cachedDefs = this.buildToolDefinitions();
132
343
  }
344
+ getApprovalManager() {
345
+ return this.approvalManager;
346
+ }
133
347
  clearCache() {
134
348
  this.cache.clear();
135
349
  }
@@ -197,6 +411,35 @@ var ToolExecutor = class {
197
411
  toolName: toolCall.name,
198
412
  args: toolCall.arguments
199
413
  });
414
+ if (this.approvalManager) {
415
+ const needs = this.approvalManager.needsApproval(
416
+ toolCall.name,
417
+ toolCall.arguments,
418
+ tool.requiresApproval
419
+ );
420
+ if (needs) {
421
+ const decision = await this.approvalManager.check(
422
+ toolCall.name,
423
+ toolCall.arguments,
424
+ ctx,
425
+ this.agentName
426
+ );
427
+ if (!decision.approved) {
428
+ const reason = decision.reason ?? "Tool call denied by human reviewer";
429
+ ctx.eventBus.emit("tool.result", {
430
+ runId: ctx.runId,
431
+ toolName: toolCall.name,
432
+ result: reason
433
+ });
434
+ return {
435
+ toolCallId: toolCall.id,
436
+ toolName: toolCall.name,
437
+ result: `[DENIED] ${reason}`,
438
+ error: reason
439
+ };
440
+ }
441
+ }
442
+ }
200
443
  const cachedResult = this.getCached(toolCall.name, toolCall.arguments);
201
444
  if (cachedResult !== void 0) {
202
445
  const resultContent2 = typeof cachedResult === "string" ? cachedResult : cachedResult.content;
@@ -227,7 +470,14 @@ var ToolExecutor = class {
227
470
  });
228
471
  return result;
229
472
  }
230
- const rawResult = await tool.execute(parsed.data, ctx);
473
+ const sandboxConfig = resolveSandboxConfig(tool.sandbox, this.agentSandbox);
474
+ let rawResult;
475
+ if (sandboxConfig) {
476
+ const sandbox = new Sandbox(sandboxConfig);
477
+ rawResult = await sandbox.execute(tool.execute, parsed.data, ctx);
478
+ } else {
479
+ rawResult = await tool.execute(parsed.data, ctx);
480
+ }
231
481
  const resultContent = typeof rawResult === "string" ? rawResult : rawResult.content;
232
482
  this.setCache(toolCall.name, toolCall.arguments, rawResult);
233
483
  ctx.eventBus.emit("tool.result", {
@@ -724,7 +974,7 @@ var LLMLoop = class {
724
974
  };
725
975
 
726
976
  // src/agent/run-context.ts
727
- import { v4 as uuidv4 } from "uuid";
977
+ import { v4 as uuidv42 } from "uuid";
728
978
  var RunContext = class {
729
979
  runId;
730
980
  sessionId;
@@ -733,7 +983,7 @@ var RunContext = class {
733
983
  eventBus;
734
984
  sessionState;
735
985
  constructor(opts) {
736
- this.runId = opts.runId ?? uuidv4();
986
+ this.runId = opts.runId ?? uuidv42();
737
987
  this.sessionId = opts.sessionId;
738
988
  this.userId = opts.userId;
739
989
  this.metadata = opts.metadata ?? {};
@@ -758,6 +1008,7 @@ var Agent = class {
758
1008
  llmLoop;
759
1009
  logger;
760
1010
  storageInitPromise = null;
1011
+ _toolExecutor = null;
761
1012
  get tools() {
762
1013
  return this.config.tools ?? [];
763
1014
  }
@@ -770,6 +1021,9 @@ var Agent = class {
770
1021
  get hasStructuredOutput() {
771
1022
  return !!this.config.structuredOutput;
772
1023
  }
1024
+ get approvalManager() {
1025
+ return this._toolExecutor?.getApprovalManager() ?? null;
1026
+ }
773
1027
  get structuredOutputSchema() {
774
1028
  return this.config.structuredOutput;
775
1029
  }
@@ -782,13 +1036,19 @@ var Agent = class {
782
1036
  if (typeof storage.initialize === "function") {
783
1037
  this.storageInitPromise = storage.initialize();
784
1038
  }
785
- this.sessionManager = new SessionManager(storage);
1039
+ this.sessionManager = new SessionManager(storage, {
1040
+ maxMessages: config.numHistoryRuns ? config.numHistoryRuns * 2 : void 0
1041
+ });
786
1042
  this.logger = new Logger({
787
1043
  level: config.logLevel ?? "silent",
788
1044
  prefix: config.name
789
1045
  });
790
- const toolExecutor = config.tools && config.tools.length > 0 ? new ToolExecutor(config.tools) : null;
791
- this.llmLoop = new LLMLoop(config.model, toolExecutor, {
1046
+ this._toolExecutor = config.tools && config.tools.length > 0 ? new ToolExecutor(config.tools, {
1047
+ sandbox: config.sandbox,
1048
+ approval: config.approval ? { ...config.approval, eventBus: this.eventBus } : void 0,
1049
+ agentName: config.name
1050
+ }) : null;
1051
+ this.llmLoop = new LLMLoop(config.model, this._toolExecutor, {
792
1052
  maxToolRoundtrips: config.maxToolRoundtrips ?? 10,
793
1053
  temperature: config.temperature,
794
1054
  structuredOutput: config.structuredOutput,
@@ -799,7 +1059,7 @@ var Agent = class {
799
1059
  }
800
1060
  async run(input, opts) {
801
1061
  const startTime = Date.now();
802
- const sessionId = opts?.sessionId ?? this.config.sessionId ?? uuidv42();
1062
+ const sessionId = opts?.sessionId ?? this.config.sessionId ?? uuidv43();
803
1063
  const userId = opts?.userId ?? this.config.userId;
804
1064
  const inputText = typeof input === "string" ? input : getTextContent(input);
805
1065
  if (this.storageInitPromise) await this.storageInitPromise;
@@ -844,26 +1104,24 @@ var Agent = class {
844
1104
  }
845
1105
  }
846
1106
  }
847
- await this.sessionManager.appendMessages(sessionId, [
1107
+ const newMessages = [
848
1108
  { role: "user", content: inputText },
849
1109
  { role: "assistant", content: output.text }
850
- ]);
1110
+ ];
1111
+ const { overflow } = await this.sessionManager.appendMessages(
1112
+ sessionId,
1113
+ newMessages
1114
+ );
851
1115
  await this.sessionManager.updateState(sessionId, ctx.sessionState);
852
- if (this.config.memory) {
853
- await this.config.memory.addMessages(sessionId, [
854
- { role: "user", content: inputText },
855
- { role: "assistant", content: output.text }
856
- ]);
1116
+ if (this.config.memory && overflow.length > 0) {
1117
+ this.config.memory.summarize(sessionId, overflow, this.config.model).catch(
1118
+ (e) => this.logger.warn(`Memory summarization failed: ${e}`)
1119
+ );
857
1120
  }
858
1121
  if (this.config.userMemory && userId) {
859
- this.config.userMemory.extractAndStore(
860
- userId,
861
- [
862
- { role: "user", content: inputText },
863
- { role: "assistant", content: output.text }
864
- ],
865
- this.config.model
866
- ).catch((e) => this.logger.warn(`UserMemory extraction failed: ${e}`));
1122
+ this.config.userMemory.extractAndStore(userId, newMessages, this.config.model).catch(
1123
+ (e) => this.logger.warn(`UserMemory extraction failed: ${e}`)
1124
+ );
867
1125
  }
868
1126
  if (this.config.hooks?.afterRun) {
869
1127
  await this.config.hooks.afterRun(ctx, output);
@@ -891,7 +1149,7 @@ var Agent = class {
891
1149
  }
892
1150
  }
893
1151
  async *stream(input, opts) {
894
- const sessionId = opts?.sessionId ?? this.config.sessionId ?? uuidv42();
1152
+ const sessionId = opts?.sessionId ?? this.config.sessionId ?? uuidv43();
895
1153
  const userId = opts?.userId ?? this.config.userId;
896
1154
  const inputText = typeof input === "string" ? input : getTextContent(input);
897
1155
  if (this.storageInitPromise) await this.storageInitPromise;
@@ -956,26 +1214,24 @@ var Agent = class {
956
1214
  throw err;
957
1215
  } finally {
958
1216
  if (streamOk) {
959
- await this.sessionManager.appendMessages(sessionId, [
1217
+ const newMessages = [
960
1218
  { role: "user", content: inputText },
961
1219
  { role: "assistant", content: fullText }
962
- ]);
1220
+ ];
1221
+ const { overflow } = await this.sessionManager.appendMessages(
1222
+ sessionId,
1223
+ newMessages
1224
+ );
963
1225
  await this.sessionManager.updateState(sessionId, ctx.sessionState);
964
- if (this.config.memory) {
965
- await this.config.memory.addMessages(sessionId, [
966
- { role: "user", content: inputText },
967
- { role: "assistant", content: fullText }
968
- ]);
1226
+ if (this.config.memory && overflow.length > 0) {
1227
+ this.config.memory.summarize(sessionId, overflow, this.config.model).catch(
1228
+ (e) => this.logger.warn(`Memory summarization failed: ${e}`)
1229
+ );
969
1230
  }
970
1231
  if (this.config.userMemory && userId) {
971
- this.config.userMemory.extractAndStore(
972
- userId,
973
- [
974
- { role: "user", content: inputText },
975
- { role: "assistant", content: fullText }
976
- ],
977
- this.config.model
978
- ).catch((e) => this.logger.warn(`UserMemory extraction failed: ${e}`));
1232
+ this.config.userMemory.extractAndStore(userId, newMessages, this.config.model).catch(
1233
+ (e) => this.logger.warn(`UserMemory extraction failed: ${e}`)
1234
+ );
979
1235
  }
980
1236
  this.eventBus.emit("run.complete", {
981
1237
  runId: ctx.runId,
@@ -1062,7 +1318,7 @@ ${userContext}` : userContext;
1062
1318
  };
1063
1319
 
1064
1320
  // src/team/team.ts
1065
- import { v4 as uuidv43 } from "uuid";
1321
+ import { v4 as uuidv44 } from "uuid";
1066
1322
 
1067
1323
  // src/team/types.ts
1068
1324
  var TeamMode = /* @__PURE__ */ ((TeamMode2) => {
@@ -1085,7 +1341,7 @@ var Team = class {
1085
1341
  }
1086
1342
  async run(input, opts) {
1087
1343
  const ctx = new RunContext({
1088
- sessionId: opts?.sessionId ?? uuidv43(),
1344
+ sessionId: opts?.sessionId ?? uuidv44(),
1089
1345
  userId: opts?.userId,
1090
1346
  metadata: opts?.metadata ?? {},
1091
1347
  eventBus: this.eventBus,
@@ -1325,7 +1581,7 @@ Synthesize these responses into a single coherent answer.`;
1325
1581
  };
1326
1582
 
1327
1583
  // src/workflow/workflow.ts
1328
- import { v4 as uuidv44 } from "uuid";
1584
+ import { v4 as uuidv45 } from "uuid";
1329
1585
 
1330
1586
  // src/workflow/step-runner.ts
1331
1587
  function isAgentStep(step) {
@@ -1557,7 +1813,7 @@ var Workflow = class {
1557
1813
  }
1558
1814
  async run(opts) {
1559
1815
  const ctx = new RunContext({
1560
- sessionId: opts?.sessionId ?? uuidv44(),
1816
+ sessionId: opts?.sessionId ?? uuidv45(),
1561
1817
  userId: opts?.userId,
1562
1818
  eventBus: this.eventBus,
1563
1819
  sessionState: {}
@@ -2951,182 +3207,665 @@ var VertexAIProvider = class {
2951
3207
  }
2952
3208
  };
2953
3209
 
2954
- // src/models/registry.ts
2955
- var ModelRegistry = class {
2956
- factories = /* @__PURE__ */ new Map();
2957
- register(providerId, factory) {
2958
- this.factories.set(providerId, factory);
2959
- }
2960
- resolve(providerId, modelId, config) {
2961
- const factory = this.factories.get(providerId);
2962
- if (!factory) {
2963
- throw new Error(
2964
- `Unknown provider "${providerId}". Register it first with registry.register().`
2965
- );
2966
- }
2967
- return factory(modelId, config);
2968
- }
2969
- has(providerId) {
2970
- return this.factories.has(providerId);
2971
- }
2972
- };
2973
- var registry = new ModelRegistry();
2974
- registry.register(
2975
- "openai",
2976
- (modelId, config) => new OpenAIProvider(modelId, config)
2977
- );
2978
- registry.register(
2979
- "anthropic",
2980
- (modelId, config) => new AnthropicProvider(modelId, config)
2981
- );
2982
- registry.register(
2983
- "google",
2984
- (modelId, config) => new GoogleProvider(modelId, config)
2985
- );
2986
- registry.register(
2987
- "ollama",
2988
- (modelId, config) => new OllamaProvider(modelId, config)
2989
- );
2990
- function openai(modelId, config) {
2991
- return registry.resolve("openai", modelId, config);
2992
- }
2993
- function anthropic(modelId, config) {
2994
- return registry.resolve("anthropic", modelId, config);
2995
- }
2996
- function google(modelId, config) {
2997
- return registry.resolve("google", modelId, config);
2998
- }
2999
- function ollama(modelId, config) {
3000
- return registry.resolve("ollama", modelId, config);
3001
- }
3002
- registry.register(
3003
- "vertex",
3004
- (modelId, config) => new VertexAIProvider(
3005
- modelId,
3006
- config
3007
- )
3008
- );
3009
- function vertex(modelId, config) {
3010
- return registry.resolve("vertex", modelId, config);
3011
- }
3012
-
3013
- // src/tools/define-tool.ts
3014
- function defineTool(config) {
3015
- return {
3016
- name: config.name,
3017
- description: config.description,
3018
- parameters: config.parameters,
3019
- execute: config.execute,
3020
- cache: config.cache
3021
- };
3022
- }
3023
-
3024
- // src/storage/sqlite.ts
3210
+ // src/voice/providers/openai-realtime.ts
3211
+ import { EventEmitter as EventEmitter2 } from "events";
3025
3212
  import { createRequire as createRequire8 } from "module";
3026
3213
  var _require8 = createRequire8(import.meta.url);
3027
- var SqliteStorage = class {
3028
- db;
3029
- constructor(dbPath) {
3030
- try {
3031
- const Database = _require8("better-sqlite3");
3032
- this.db = new Database(dbPath);
3033
- this.db.pragma("journal_mode = WAL");
3034
- this.db.exec(`
3035
- CREATE TABLE IF NOT EXISTS kv_store (
3036
- namespace TEXT NOT NULL,
3037
- key TEXT NOT NULL,
3038
- value TEXT NOT NULL,
3039
- updated_at TEXT DEFAULT (datetime('now')),
3040
- PRIMARY KEY (namespace, key)
3041
- )
3042
- `);
3043
- } catch {
3044
- throw new Error(
3045
- "better-sqlite3 is required for SqliteStorage. Install it: npm install better-sqlite3"
3046
- );
3047
- }
3214
+ function toOpenAIAudioFormat(fmt) {
3215
+ if (!fmt) return "pcm16";
3216
+ switch (fmt) {
3217
+ case "pcm16":
3218
+ return "pcm16";
3219
+ case "g711_ulaw":
3220
+ return "g711_ulaw";
3221
+ case "g711_alaw":
3222
+ return "g711_alaw";
3223
+ default:
3224
+ return "pcm16";
3048
3225
  }
3049
- async get(namespace, key) {
3050
- const row = this.db.prepare("SELECT value FROM kv_store WHERE namespace = ? AND key = ?").get(namespace, key);
3051
- if (!row) return null;
3052
- return JSON.parse(row.value);
3226
+ }
3227
+ var OpenAIRealtimeConnection = class extends EventEmitter2 {
3228
+ ws;
3229
+ closed = false;
3230
+ constructor(ws) {
3231
+ super();
3232
+ this.ws = ws;
3053
3233
  }
3054
- async set(namespace, key, value) {
3055
- this.db.prepare(
3056
- `INSERT INTO kv_store (namespace, key, value, updated_at)
3057
- VALUES (?, ?, ?, datetime('now'))
3058
- ON CONFLICT(namespace, key)
3059
- DO UPDATE SET value = excluded.value, updated_at = datetime('now')`
3060
- ).run(namespace, key, JSON.stringify(value));
3234
+ sendAudio(data) {
3235
+ if (this.closed) return;
3236
+ this.send({
3237
+ type: "input_audio_buffer.append",
3238
+ audio: data.toString("base64")
3239
+ });
3061
3240
  }
3062
- async delete(namespace, key) {
3063
- this.db.prepare("DELETE FROM kv_store WHERE namespace = ? AND key = ?").run(namespace, key);
3241
+ sendText(text) {
3242
+ if (this.closed) return;
3243
+ this.send({
3244
+ type: "conversation.item.create",
3245
+ item: {
3246
+ type: "message",
3247
+ role: "user",
3248
+ content: [{ type: "input_text", text }]
3249
+ }
3250
+ });
3251
+ this.send({ type: "response.create" });
3252
+ }
3253
+ sendToolResult(callId, result) {
3254
+ if (this.closed) return;
3255
+ this.send({
3256
+ type: "conversation.item.create",
3257
+ item: {
3258
+ type: "function_call_output",
3259
+ call_id: callId,
3260
+ output: result
3261
+ }
3262
+ });
3263
+ this.send({ type: "response.create" });
3064
3264
  }
3065
- async list(namespace, prefix) {
3066
- const rows = prefix ? this.db.prepare(
3067
- "SELECT key, value FROM kv_store WHERE namespace = ? AND key LIKE ?"
3068
- ).all(namespace, `${prefix}%`) : this.db.prepare("SELECT key, value FROM kv_store WHERE namespace = ?").all(namespace);
3069
- return rows.map((row) => ({
3070
- key: row.key,
3071
- value: JSON.parse(row.value)
3072
- }));
3265
+ interrupt() {
3266
+ if (this.closed) return;
3267
+ this.send({ type: "response.cancel" });
3073
3268
  }
3074
3269
  async close() {
3075
- this.db.close();
3076
- }
3077
- };
3078
-
3079
- // src/storage/postgres.ts
3080
- import { createRequire as createRequire9 } from "module";
3081
- var _require9 = createRequire9(import.meta.url);
3082
- var PostgresStorage = class {
3083
- pool;
3084
- constructor(connectionString) {
3270
+ if (this.closed) return;
3271
+ this.closed = true;
3085
3272
  try {
3086
- const { Pool } = _require9("pg");
3087
- this.pool = new Pool({ connectionString });
3273
+ this.ws.close();
3088
3274
  } catch {
3089
- throw new Error(
3090
- "pg is required for PostgresStorage. Install it: npm install pg"
3091
- );
3092
3275
  }
3276
+ this.emit("disconnected", {});
3093
3277
  }
3094
- async initialize() {
3095
- await this.pool.query(`
3096
- CREATE TABLE IF NOT EXISTS kv_store (
3097
- namespace TEXT NOT NULL,
3098
- key TEXT NOT NULL,
3099
- value JSONB NOT NULL,
3100
- updated_at TIMESTAMPTZ DEFAULT NOW(),
3101
- PRIMARY KEY (namespace, key)
3102
- )
3103
- `);
3104
- }
3105
- async get(namespace, key) {
3106
- const result = await this.pool.query(
3107
- "SELECT value FROM kv_store WHERE namespace = $1 AND key = $2",
3108
- [namespace, key]
3109
- );
3110
- if (result.rows.length === 0) return null;
3111
- return result.rows[0].value;
3278
+ on(event, handler) {
3279
+ return super.on(event, handler);
3112
3280
  }
3113
- async set(namespace, key, value) {
3114
- await this.pool.query(
3115
- `INSERT INTO kv_store (namespace, key, value, updated_at)
3116
- VALUES ($1, $2, $3, NOW())
3117
- ON CONFLICT (namespace, key)
3118
- DO UPDATE SET value = EXCLUDED.value, updated_at = NOW()`,
3119
- [namespace, key, JSON.stringify(value)]
3120
- );
3281
+ off(event, handler) {
3282
+ return super.off(event, handler);
3121
3283
  }
3122
- async delete(namespace, key) {
3123
- await this.pool.query(
3124
- "DELETE FROM kv_store WHERE namespace = $1 AND key = $2",
3125
- [namespace, key]
3126
- );
3284
+ send(event) {
3285
+ if (this.ws.readyState === 1) {
3286
+ this.ws.send(JSON.stringify(event));
3287
+ }
3127
3288
  }
3128
- async list(namespace, prefix) {
3129
- const result = prefix ? await this.pool.query(
3289
+ /** Called by the provider to set up server event handling. */
3290
+ _bindServerEvents() {
3291
+ this.ws.on("message", (raw) => {
3292
+ try {
3293
+ const data = JSON.parse(typeof raw === "string" ? raw : raw.toString());
3294
+ this.handleServerEvent(data);
3295
+ } catch {
3296
+ }
3297
+ });
3298
+ this.ws.on("error", (err) => {
3299
+ this.emit("error", { error: err });
3300
+ });
3301
+ this.ws.on("close", () => {
3302
+ if (!this.closed) {
3303
+ this.closed = true;
3304
+ this.emit("disconnected", {});
3305
+ }
3306
+ });
3307
+ }
3308
+ pendingFunctionCalls = /* @__PURE__ */ new Map();
3309
+ handleServerEvent(event) {
3310
+ switch (event.type) {
3311
+ case "session.created":
3312
+ this.emit("connected", {});
3313
+ break;
3314
+ case "response.audio.delta":
3315
+ if (event.delta) {
3316
+ this.emit("audio", {
3317
+ data: Buffer.from(event.delta, "base64"),
3318
+ mimeType: "audio/pcm"
3319
+ });
3320
+ }
3321
+ break;
3322
+ case "response.audio_transcript.delta":
3323
+ if (event.delta) {
3324
+ this.emit("transcript", { text: event.delta, role: "assistant" });
3325
+ }
3326
+ break;
3327
+ case "response.text.delta":
3328
+ if (event.delta) {
3329
+ this.emit("text", { text: event.delta });
3330
+ }
3331
+ break;
3332
+ case "input_audio_buffer.speech_started":
3333
+ this.emit("interrupted", {});
3334
+ break;
3335
+ case "conversation.item.input_audio_transcription.completed":
3336
+ if (event.transcript) {
3337
+ this.emit("transcript", { text: event.transcript, role: "user" });
3338
+ }
3339
+ break;
3340
+ case "response.function_call_arguments.delta":
3341
+ if (event.item_id) {
3342
+ const pending = this.pendingFunctionCalls.get(event.item_id);
3343
+ if (pending) {
3344
+ pending.args += event.delta ?? "";
3345
+ }
3346
+ }
3347
+ break;
3348
+ case "response.output_item.added":
3349
+ if (event.item?.type === "function_call") {
3350
+ this.pendingFunctionCalls.set(event.item.id, {
3351
+ name: event.item.name ?? "",
3352
+ args: ""
3353
+ });
3354
+ }
3355
+ break;
3356
+ case "response.output_item.done":
3357
+ if (event.item?.type === "function_call") {
3358
+ const pending = this.pendingFunctionCalls.get(event.item.id);
3359
+ const toolCall = {
3360
+ id: event.item.call_id ?? event.item.id,
3361
+ name: pending?.name ?? event.item.name ?? "",
3362
+ arguments: pending?.args ?? event.item.arguments ?? "{}"
3363
+ };
3364
+ this.pendingFunctionCalls.delete(event.item.id);
3365
+ this.emit("tool_call", toolCall);
3366
+ }
3367
+ break;
3368
+ case "error":
3369
+ this.emit("error", {
3370
+ error: new Error(event.error?.message ?? "Realtime API error")
3371
+ });
3372
+ break;
3373
+ }
3374
+ }
3375
+ };
3376
+ var OpenAIRealtimeProvider = class {
3377
+ providerId = "openai-realtime";
3378
+ modelId;
3379
+ apiKey;
3380
+ baseURL;
3381
+ constructor(modelId, config) {
3382
+ this.modelId = modelId ?? "gpt-4o-realtime-preview";
3383
+ this.apiKey = config?.apiKey;
3384
+ this.baseURL = config?.baseURL;
3385
+ }
3386
+ async connect(config) {
3387
+ let WebSocket;
3388
+ try {
3389
+ WebSocket = _require8("ws");
3390
+ } catch {
3391
+ throw new Error("ws package is required for OpenAIRealtimeProvider. Install it: npm install ws");
3392
+ }
3393
+ const key = config.apiKey ?? this.apiKey ?? process.env.OPENAI_API_KEY;
3394
+ if (!key) {
3395
+ throw new Error(
3396
+ "No OpenAI API key provided for realtime connection. Set OPENAI_API_KEY env var or pass apiKey."
3397
+ );
3398
+ }
3399
+ const base = this.baseURL ?? "wss://api.openai.com";
3400
+ const url = `${base}/v1/realtime?model=${encodeURIComponent(this.modelId)}`;
3401
+ const ws = new WebSocket(url, {
3402
+ headers: {
3403
+ Authorization: `Bearer ${key}`,
3404
+ "OpenAI-Beta": "realtime=v1"
3405
+ }
3406
+ });
3407
+ const connection = new OpenAIRealtimeConnection(ws);
3408
+ return new Promise((resolve, reject) => {
3409
+ const TIMEOUT_MS = 3e4;
3410
+ const timeout = setTimeout(() => {
3411
+ reject(new Error(`OpenAI Realtime connection timed out after ${TIMEOUT_MS / 1e3}s`));
3412
+ try {
3413
+ ws.close();
3414
+ } catch {
3415
+ }
3416
+ }, TIMEOUT_MS);
3417
+ ws.on("open", () => {
3418
+ clearTimeout(timeout);
3419
+ connection._bindServerEvents();
3420
+ const sessionUpdate = {
3421
+ type: "session.update",
3422
+ session: this.buildSessionPayload(config)
3423
+ };
3424
+ ws.send(JSON.stringify(sessionUpdate));
3425
+ resolve(connection);
3426
+ });
3427
+ ws.on("error", (err) => {
3428
+ clearTimeout(timeout);
3429
+ reject(new Error(`OpenAI Realtime WebSocket error: ${err.message}`));
3430
+ });
3431
+ ws.on("unexpected-response", (_req, res) => {
3432
+ clearTimeout(timeout);
3433
+ let body = "";
3434
+ res.on("data", (chunk) => {
3435
+ body += chunk.toString();
3436
+ });
3437
+ res.on("end", () => {
3438
+ reject(new Error(`OpenAI Realtime rejected (HTTP ${res.statusCode}): ${body}`));
3439
+ });
3440
+ });
3441
+ });
3442
+ }
3443
+ buildSessionPayload(config) {
3444
+ const session = {
3445
+ modalities: ["text", "audio"],
3446
+ model: this.modelId
3447
+ };
3448
+ if (config.instructions) {
3449
+ session.instructions = config.instructions;
3450
+ }
3451
+ if (config.voice) {
3452
+ session.voice = config.voice;
3453
+ }
3454
+ if (config.inputAudioFormat) {
3455
+ session.input_audio_format = toOpenAIAudioFormat(config.inputAudioFormat);
3456
+ }
3457
+ if (config.outputAudioFormat) {
3458
+ session.output_audio_format = toOpenAIAudioFormat(config.outputAudioFormat);
3459
+ }
3460
+ if (config.turnDetection !== void 0) {
3461
+ if (config.turnDetection === null) {
3462
+ session.turn_detection = null;
3463
+ } else {
3464
+ session.turn_detection = {
3465
+ type: config.turnDetection.type,
3466
+ ...config.turnDetection.threshold !== void 0 && {
3467
+ threshold: config.turnDetection.threshold
3468
+ },
3469
+ ...config.turnDetection.prefixPaddingMs !== void 0 && {
3470
+ prefix_padding_ms: config.turnDetection.prefixPaddingMs
3471
+ },
3472
+ ...config.turnDetection.silenceDurationMs !== void 0 && {
3473
+ silence_duration_ms: config.turnDetection.silenceDurationMs
3474
+ }
3475
+ };
3476
+ }
3477
+ }
3478
+ if (config.temperature !== void 0) {
3479
+ session.temperature = config.temperature;
3480
+ }
3481
+ if (config.maxResponseOutputTokens !== void 0) {
3482
+ session.max_response_output_tokens = config.maxResponseOutputTokens;
3483
+ }
3484
+ session.input_audio_transcription = { model: "whisper-1" };
3485
+ if (config.tools && config.tools.length > 0) {
3486
+ session.tools = config.tools.map((t) => ({
3487
+ type: "function",
3488
+ name: t.name,
3489
+ description: t.description,
3490
+ parameters: t.parameters
3491
+ }));
3492
+ }
3493
+ return session;
3494
+ }
3495
+ };
3496
+
3497
+ // src/voice/providers/google-live.ts
3498
+ import { EventEmitter as EventEmitter3 } from "events";
3499
+ import { createRequire as createRequire9 } from "module";
3500
+ var _require9 = createRequire9(import.meta.url);
3501
+ var GoogleLiveConnection = class extends EventEmitter3 {
3502
+ session;
3503
+ closed = false;
3504
+ constructor(session) {
3505
+ super();
3506
+ this.session = session;
3507
+ }
3508
+ sendAudio(data) {
3509
+ if (this.closed) return;
3510
+ this.session.sendRealtimeInput({
3511
+ audio: {
3512
+ data: data.toString("base64"),
3513
+ mimeType: "audio/pcm;rate=16000"
3514
+ }
3515
+ });
3516
+ }
3517
+ sendText(text) {
3518
+ if (this.closed) return;
3519
+ this.session.sendClientContent({
3520
+ turns: text,
3521
+ turnComplete: true
3522
+ });
3523
+ }
3524
+ sendToolResult(callId, result) {
3525
+ if (this.closed) return;
3526
+ let responseObj;
3527
+ try {
3528
+ responseObj = JSON.parse(result);
3529
+ } catch {
3530
+ responseObj = { result };
3531
+ }
3532
+ this.session.sendToolResponse({
3533
+ functionResponses: [
3534
+ {
3535
+ id: callId,
3536
+ name: callId,
3537
+ response: responseObj
3538
+ }
3539
+ ]
3540
+ });
3541
+ }
3542
+ interrupt() {
3543
+ }
3544
+ async close() {
3545
+ if (this.closed) return;
3546
+ this.closed = true;
3547
+ try {
3548
+ this.session.close();
3549
+ } catch {
3550
+ }
3551
+ this.emit("disconnected", {});
3552
+ }
3553
+ on(event, handler) {
3554
+ return super.on(event, handler);
3555
+ }
3556
+ off(event, handler) {
3557
+ return super.off(event, handler);
3558
+ }
3559
+ /** Internal: handle server messages from the Live API. */
3560
+ _handleMessage(message) {
3561
+ if (message.serverContent?.interrupted) {
3562
+ this.emit("interrupted", {});
3563
+ return;
3564
+ }
3565
+ if (message.toolCall?.functionCalls) {
3566
+ for (const fc of message.toolCall.functionCalls) {
3567
+ const toolCall = {
3568
+ id: fc.id ?? fc.name,
3569
+ name: fc.name,
3570
+ arguments: JSON.stringify(fc.args ?? {})
3571
+ };
3572
+ this.emit("tool_call", toolCall);
3573
+ }
3574
+ return;
3575
+ }
3576
+ if (message.serverContent?.modelTurn?.parts) {
3577
+ for (const part of message.serverContent.modelTurn.parts) {
3578
+ if (part.inlineData && part.inlineData.data) {
3579
+ const mimeType = part.inlineData.mimeType ?? "audio/pcm";
3580
+ const buf = typeof part.inlineData.data === "string" ? Buffer.from(part.inlineData.data, "base64") : Buffer.from(part.inlineData.data);
3581
+ this.emit("audio", { data: buf, mimeType });
3582
+ }
3583
+ if (part.text) {
3584
+ this.emit("text", { text: part.text });
3585
+ this.emit("transcript", { text: part.text, role: "assistant" });
3586
+ }
3587
+ if (part.functionCall) {
3588
+ const toolCall = {
3589
+ id: part.functionCall.id ?? part.functionCall.name,
3590
+ name: part.functionCall.name,
3591
+ arguments: JSON.stringify(part.functionCall.args ?? {})
3592
+ };
3593
+ this.emit("tool_call", toolCall);
3594
+ }
3595
+ }
3596
+ }
3597
+ }
3598
+ };
3599
+ var GoogleLiveProvider = class {
3600
+ providerId = "google-live";
3601
+ modelId;
3602
+ apiKey;
3603
+ constructor(modelId, config) {
3604
+ this.modelId = modelId ?? "gemini-2.5-flash-native-audio-preview-12-2025";
3605
+ this.apiKey = config?.apiKey;
3606
+ }
3607
+ async connect(config) {
3608
+ let GoogleGenAI;
3609
+ let Modality;
3610
+ try {
3611
+ const mod = _require9("@google/genai");
3612
+ GoogleGenAI = mod.GoogleGenAI;
3613
+ Modality = mod.Modality;
3614
+ } catch {
3615
+ throw new Error(
3616
+ "@google/genai package is required for GoogleLiveProvider. Install it: npm install @google/genai"
3617
+ );
3618
+ }
3619
+ const key = config.apiKey ?? this.apiKey ?? process.env.GOOGLE_API_KEY;
3620
+ if (!key) {
3621
+ throw new Error(
3622
+ "No Google API key provided for live connection. Set GOOGLE_API_KEY env var or pass apiKey."
3623
+ );
3624
+ }
3625
+ const ai = new GoogleGenAI({ apiKey: key });
3626
+ const liveConfig = {
3627
+ responseModalities: [Modality.AUDIO]
3628
+ };
3629
+ if (config.instructions) {
3630
+ liveConfig.systemInstruction = config.instructions;
3631
+ }
3632
+ if (config.voice) {
3633
+ liveConfig.speechConfig = { voiceConfig: { prebuiltVoiceConfig: { voiceName: config.voice } } };
3634
+ }
3635
+ if (config.temperature !== void 0) {
3636
+ liveConfig.temperature = config.temperature;
3637
+ }
3638
+ if (config.tools && config.tools.length > 0) {
3639
+ liveConfig.tools = [
3640
+ {
3641
+ functionDeclarations: config.tools.map((t) => ({
3642
+ name: t.name,
3643
+ description: t.description,
3644
+ parameters: t.parameters
3645
+ }))
3646
+ }
3647
+ ];
3648
+ }
3649
+ const connection = new GoogleLiveConnection(null);
3650
+ return new Promise((resolve, reject) => {
3651
+ const timeout = setTimeout(() => {
3652
+ reject(new Error("Google Live connection timed out after 15s"));
3653
+ }, 15e3);
3654
+ ai.live.connect({
3655
+ model: this.modelId,
3656
+ config: liveConfig,
3657
+ callbacks: {
3658
+ onopen: () => {
3659
+ clearTimeout(timeout);
3660
+ connection.emit("connected", {});
3661
+ },
3662
+ onmessage: (message) => {
3663
+ connection._handleMessage(message);
3664
+ },
3665
+ onerror: (e) => {
3666
+ const err = e?.error ?? e?.message ?? e;
3667
+ const error = err instanceof Error ? err : new Error(String(err));
3668
+ connection.emit("error", { error });
3669
+ },
3670
+ onclose: () => {
3671
+ connection.emit("disconnected", {});
3672
+ }
3673
+ }
3674
+ }).then((session) => {
3675
+ connection.session = session;
3676
+ resolve(connection);
3677
+ }).catch((err) => {
3678
+ clearTimeout(timeout);
3679
+ reject(err);
3680
+ });
3681
+ });
3682
+ }
3683
+ };
3684
+
3685
+ // src/models/registry.ts
3686
+ var ModelRegistry = class {
3687
+ factories = /* @__PURE__ */ new Map();
3688
+ register(providerId, factory) {
3689
+ this.factories.set(providerId, factory);
3690
+ }
3691
+ resolve(providerId, modelId, config) {
3692
+ const factory = this.factories.get(providerId);
3693
+ if (!factory) {
3694
+ throw new Error(
3695
+ `Unknown provider "${providerId}". Register it first with registry.register().`
3696
+ );
3697
+ }
3698
+ return factory(modelId, config);
3699
+ }
3700
+ has(providerId) {
3701
+ return this.factories.has(providerId);
3702
+ }
3703
+ };
3704
+ var registry = new ModelRegistry();
3705
+ registry.register(
3706
+ "openai",
3707
+ (modelId, config) => new OpenAIProvider(modelId, config)
3708
+ );
3709
+ registry.register(
3710
+ "anthropic",
3711
+ (modelId, config) => new AnthropicProvider(modelId, config)
3712
+ );
3713
+ registry.register(
3714
+ "google",
3715
+ (modelId, config) => new GoogleProvider(modelId, config)
3716
+ );
3717
+ registry.register(
3718
+ "ollama",
3719
+ (modelId, config) => new OllamaProvider(modelId, config)
3720
+ );
3721
+ function openai(modelId, config) {
3722
+ return registry.resolve("openai", modelId, config);
3723
+ }
3724
+ function anthropic(modelId, config) {
3725
+ return registry.resolve("anthropic", modelId, config);
3726
+ }
3727
+ function google(modelId, config) {
3728
+ return registry.resolve("google", modelId, config);
3729
+ }
3730
+ function ollama(modelId, config) {
3731
+ return registry.resolve("ollama", modelId, config);
3732
+ }
3733
+ registry.register(
3734
+ "vertex",
3735
+ (modelId, config) => new VertexAIProvider(
3736
+ modelId,
3737
+ config
3738
+ )
3739
+ );
3740
+ function vertex(modelId, config) {
3741
+ return registry.resolve("vertex", modelId, config);
3742
+ }
3743
+ function openaiRealtime(modelId, config) {
3744
+ return new OpenAIRealtimeProvider(modelId, config);
3745
+ }
3746
+ function googleLive(modelId, config) {
3747
+ return new GoogleLiveProvider(modelId, config);
3748
+ }
3749
+
3750
+ // src/tools/define-tool.ts
3751
+ function defineTool(config) {
3752
+ return {
3753
+ name: config.name,
3754
+ description: config.description,
3755
+ parameters: config.parameters,
3756
+ execute: config.execute,
3757
+ cache: config.cache,
3758
+ sandbox: config.sandbox,
3759
+ requiresApproval: config.requiresApproval
3760
+ };
3761
+ }
3762
+
3763
+ // src/storage/sqlite.ts
3764
+ import { createRequire as createRequire10 } from "module";
3765
+ var _require10 = createRequire10(import.meta.url);
3766
+ var SqliteStorage = class {
3767
+ db;
3768
+ constructor(dbPath) {
3769
+ try {
3770
+ const Database = _require10("better-sqlite3");
3771
+ this.db = new Database(dbPath);
3772
+ this.db.pragma("journal_mode = WAL");
3773
+ this.db.exec(`
3774
+ CREATE TABLE IF NOT EXISTS kv_store (
3775
+ namespace TEXT NOT NULL,
3776
+ key TEXT NOT NULL,
3777
+ value TEXT NOT NULL,
3778
+ updated_at TEXT DEFAULT (datetime('now')),
3779
+ PRIMARY KEY (namespace, key)
3780
+ )
3781
+ `);
3782
+ } catch {
3783
+ throw new Error(
3784
+ "better-sqlite3 is required for SqliteStorage. Install it: npm install better-sqlite3"
3785
+ );
3786
+ }
3787
+ }
3788
+ async get(namespace, key) {
3789
+ const row = this.db.prepare("SELECT value FROM kv_store WHERE namespace = ? AND key = ?").get(namespace, key);
3790
+ if (!row) return null;
3791
+ return JSON.parse(row.value);
3792
+ }
3793
+ async set(namespace, key, value) {
3794
+ this.db.prepare(
3795
+ `INSERT INTO kv_store (namespace, key, value, updated_at)
3796
+ VALUES (?, ?, ?, datetime('now'))
3797
+ ON CONFLICT(namespace, key)
3798
+ DO UPDATE SET value = excluded.value, updated_at = datetime('now')`
3799
+ ).run(namespace, key, JSON.stringify(value));
3800
+ }
3801
+ async delete(namespace, key) {
3802
+ this.db.prepare("DELETE FROM kv_store WHERE namespace = ? AND key = ?").run(namespace, key);
3803
+ }
3804
+ async list(namespace, prefix) {
3805
+ const rows = prefix ? this.db.prepare(
3806
+ "SELECT key, value FROM kv_store WHERE namespace = ? AND key LIKE ?"
3807
+ ).all(namespace, `${prefix}%`) : this.db.prepare("SELECT key, value FROM kv_store WHERE namespace = ?").all(namespace);
3808
+ return rows.map((row) => ({
3809
+ key: row.key,
3810
+ value: JSON.parse(row.value)
3811
+ }));
3812
+ }
3813
+ async close() {
3814
+ this.db.close();
3815
+ }
3816
+ };
3817
+
3818
+ // src/storage/postgres.ts
3819
+ import { createRequire as createRequire11 } from "module";
3820
+ var _require11 = createRequire11(import.meta.url);
3821
+ var PostgresStorage = class {
3822
+ pool;
3823
+ constructor(connectionString) {
3824
+ try {
3825
+ const { Pool } = _require11("pg");
3826
+ this.pool = new Pool({ connectionString });
3827
+ } catch {
3828
+ throw new Error(
3829
+ "pg is required for PostgresStorage. Install it: npm install pg"
3830
+ );
3831
+ }
3832
+ }
3833
+ async initialize() {
3834
+ await this.pool.query(`
3835
+ CREATE TABLE IF NOT EXISTS kv_store (
3836
+ namespace TEXT NOT NULL,
3837
+ key TEXT NOT NULL,
3838
+ value JSONB NOT NULL,
3839
+ updated_at TIMESTAMPTZ DEFAULT NOW(),
3840
+ PRIMARY KEY (namespace, key)
3841
+ )
3842
+ `);
3843
+ }
3844
+ async get(namespace, key) {
3845
+ const result = await this.pool.query(
3846
+ "SELECT value FROM kv_store WHERE namespace = $1 AND key = $2",
3847
+ [namespace, key]
3848
+ );
3849
+ if (result.rows.length === 0) return null;
3850
+ return result.rows[0].value;
3851
+ }
3852
+ async set(namespace, key, value) {
3853
+ await this.pool.query(
3854
+ `INSERT INTO kv_store (namespace, key, value, updated_at)
3855
+ VALUES ($1, $2, $3, NOW())
3856
+ ON CONFLICT (namespace, key)
3857
+ DO UPDATE SET value = EXCLUDED.value, updated_at = NOW()`,
3858
+ [namespace, key, JSON.stringify(value)]
3859
+ );
3860
+ }
3861
+ async delete(namespace, key) {
3862
+ await this.pool.query(
3863
+ "DELETE FROM kv_store WHERE namespace = $1 AND key = $2",
3864
+ [namespace, key]
3865
+ );
3866
+ }
3867
+ async list(namespace, prefix) {
3868
+ const result = prefix ? await this.pool.query(
3130
3869
  "SELECT key, value FROM kv_store WHERE namespace = $1 AND key LIKE $2",
3131
3870
  [namespace, `${prefix}%`]
3132
3871
  ) : await this.pool.query(
@@ -3144,15 +3883,15 @@ var PostgresStorage = class {
3144
3883
  };
3145
3884
 
3146
3885
  // src/storage/mongodb.ts
3147
- import { createRequire as createRequire10 } from "module";
3148
- var _require10 = createRequire10(import.meta.url);
3886
+ import { createRequire as createRequire12 } from "module";
3887
+ var _require12 = createRequire12(import.meta.url);
3149
3888
  var MongoDBStorage = class {
3150
3889
  constructor(uri, dbName = "radaros", collectionName = "kv_store") {
3151
3890
  this.uri = uri;
3152
3891
  this.dbName = dbName;
3153
3892
  this.collectionName = collectionName;
3154
3893
  try {
3155
- const { MongoClient } = _require10("mongodb");
3894
+ const { MongoClient } = _require12("mongodb");
3156
3895
  this.client = new MongoClient(uri);
3157
3896
  } catch {
3158
3897
  throw new Error(
@@ -3312,8 +4051,8 @@ var InMemoryVectorStore = class extends BaseVectorStore {
3312
4051
  };
3313
4052
 
3314
4053
  // src/vector/pgvector.ts
3315
- import { createRequire as createRequire11 } from "module";
3316
- var _require11 = createRequire11(import.meta.url);
4054
+ import { createRequire as createRequire13 } from "module";
4055
+ var _require13 = createRequire13(import.meta.url);
3317
4056
  var PgVectorStore = class extends BaseVectorStore {
3318
4057
  pool;
3319
4058
  dimensions;
@@ -3322,7 +4061,7 @@ var PgVectorStore = class extends BaseVectorStore {
3322
4061
  super(embedder);
3323
4062
  this.dimensions = config.dimensions ?? embedder?.dimensions ?? 1536;
3324
4063
  try {
3325
- const { Pool } = _require11("pg");
4064
+ const { Pool } = _require13("pg");
3326
4065
  this.pool = new Pool({ connectionString: config.connectionString });
3327
4066
  } catch {
3328
4067
  throw new Error(
@@ -3441,9 +4180,9 @@ var PgVectorStore = class extends BaseVectorStore {
3441
4180
  };
3442
4181
 
3443
4182
  // src/vector/qdrant.ts
3444
- import { createRequire as createRequire12 } from "module";
4183
+ import { createRequire as createRequire14 } from "module";
3445
4184
  import { createHash } from "crypto";
3446
- var _require12 = createRequire12(import.meta.url);
4185
+ var _require14 = createRequire14(import.meta.url);
3447
4186
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
3448
4187
  function stringToUUID(str) {
3449
4188
  const hex = createHash("md5").update(str).digest("hex");
@@ -3468,7 +4207,7 @@ var QdrantVectorStore = class extends BaseVectorStore {
3468
4207
  super(embedder);
3469
4208
  this.dimensions = config.dimensions ?? embedder?.dimensions ?? 1536;
3470
4209
  try {
3471
- const { QdrantClient } = _require12("@qdrant/js-client-rest");
4210
+ const { QdrantClient } = _require14("@qdrant/js-client-rest");
3472
4211
  this.client = new QdrantClient({
3473
4212
  url: config.url ?? "http://localhost:6333",
3474
4213
  apiKey: config.apiKey,
@@ -3601,8 +4340,8 @@ var QdrantVectorStore = class extends BaseVectorStore {
3601
4340
  };
3602
4341
 
3603
4342
  // src/vector/mongodb.ts
3604
- import { createRequire as createRequire13 } from "module";
3605
- var _require13 = createRequire13(import.meta.url);
4343
+ import { createRequire as createRequire15 } from "module";
4344
+ var _require15 = createRequire15(import.meta.url);
3606
4345
  var MongoDBVectorStore = class extends BaseVectorStore {
3607
4346
  client;
3608
4347
  db;
@@ -3614,7 +4353,7 @@ var MongoDBVectorStore = class extends BaseVectorStore {
3614
4353
  this.indexName = config.indexName ?? "vector_index";
3615
4354
  this.dbName = config.dbName ?? "radaros_vectors";
3616
4355
  try {
3617
- const { MongoClient } = _require13("mongodb");
4356
+ const { MongoClient } = _require15("mongodb");
3618
4357
  this.client = new MongoClient(config.uri);
3619
4358
  } catch {
3620
4359
  throw new Error(
@@ -3785,8 +4524,8 @@ function cosine(a, b) {
3785
4524
  }
3786
4525
 
3787
4526
  // src/vector/embeddings/openai.ts
3788
- import { createRequire as createRequire14 } from "module";
3789
- var _require14 = createRequire14(import.meta.url);
4527
+ import { createRequire as createRequire16 } from "module";
4528
+ var _require16 = createRequire16(import.meta.url);
3790
4529
  var MODEL_DIMENSIONS = {
3791
4530
  "text-embedding-3-small": 1536,
3792
4531
  "text-embedding-3-large": 3072,
@@ -3800,7 +4539,7 @@ var OpenAIEmbedding = class {
3800
4539
  this.model = config.model ?? "text-embedding-3-small";
3801
4540
  this.dimensions = config.dimensions ?? MODEL_DIMENSIONS[this.model] ?? 1536;
3802
4541
  try {
3803
- const mod = _require14("openai");
4542
+ const mod = _require16("openai");
3804
4543
  const OpenAI = mod.default ?? mod;
3805
4544
  this.client = new OpenAI({
3806
4545
  apiKey: config.apiKey ?? process.env.OPENAI_API_KEY,
@@ -3831,8 +4570,8 @@ var OpenAIEmbedding = class {
3831
4570
  };
3832
4571
 
3833
4572
  // src/vector/embeddings/google.ts
3834
- import { createRequire as createRequire15 } from "module";
3835
- var _require15 = createRequire15(import.meta.url);
4573
+ import { createRequire as createRequire17 } from "module";
4574
+ var _require17 = createRequire17(import.meta.url);
3836
4575
  var MODEL_DIMENSIONS2 = {
3837
4576
  "text-embedding-004": 768,
3838
4577
  "embedding-001": 768
@@ -3845,7 +4584,7 @@ var GoogleEmbedding = class {
3845
4584
  this.model = config.model ?? "text-embedding-004";
3846
4585
  this.dimensions = config.dimensions ?? MODEL_DIMENSIONS2[this.model] ?? 768;
3847
4586
  try {
3848
- const { GoogleGenAI } = _require15("@google/genai");
4587
+ const { GoogleGenAI } = _require17("@google/genai");
3849
4588
  this.ai = new GoogleGenAI({
3850
4589
  apiKey: config.apiKey ?? process.env.GOOGLE_API_KEY
3851
4590
  });
@@ -3879,15 +4618,235 @@ var GoogleEmbedding = class {
3879
4618
 
3880
4619
  // src/knowledge/knowledge-base.ts
3881
4620
  import { z } from "zod";
4621
+
4622
+ // src/vector/bm25.ts
4623
+ var STOP_WORDS = /* @__PURE__ */ new Set([
4624
+ "a",
4625
+ "an",
4626
+ "and",
4627
+ "are",
4628
+ "as",
4629
+ "at",
4630
+ "be",
4631
+ "but",
4632
+ "by",
4633
+ "for",
4634
+ "from",
4635
+ "had",
4636
+ "has",
4637
+ "have",
4638
+ "he",
4639
+ "her",
4640
+ "his",
4641
+ "how",
4642
+ "i",
4643
+ "in",
4644
+ "is",
4645
+ "it",
4646
+ "its",
4647
+ "my",
4648
+ "no",
4649
+ "not",
4650
+ "of",
4651
+ "on",
4652
+ "or",
4653
+ "our",
4654
+ "she",
4655
+ "so",
4656
+ "than",
4657
+ "that",
4658
+ "the",
4659
+ "their",
4660
+ "them",
4661
+ "then",
4662
+ "there",
4663
+ "these",
4664
+ "they",
4665
+ "this",
4666
+ "to",
4667
+ "up",
4668
+ "was",
4669
+ "we",
4670
+ "were",
4671
+ "what",
4672
+ "when",
4673
+ "which",
4674
+ "who",
4675
+ "will",
4676
+ "with",
4677
+ "you",
4678
+ "your"
4679
+ ]);
4680
+ function tokenize(text) {
4681
+ return text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length > 1 && !STOP_WORDS.has(t));
4682
+ }
4683
+ var BM25Index = class {
4684
+ docs = /* @__PURE__ */ new Map();
4685
+ docFreqs = /* @__PURE__ */ new Map();
4686
+ avgDocLength = 0;
4687
+ /** BM25 free parameter: term frequency saturation. */
4688
+ k1;
4689
+ /** BM25 free parameter: document length normalization. */
4690
+ b;
4691
+ constructor(opts) {
4692
+ this.k1 = opts?.k1 ?? 1.5;
4693
+ this.b = opts?.b ?? 0.75;
4694
+ }
4695
+ get size() {
4696
+ return this.docs.size;
4697
+ }
4698
+ add(doc) {
4699
+ this.remove(doc.id);
4700
+ const tokens = tokenize(doc.content);
4701
+ const termFreqs = /* @__PURE__ */ new Map();
4702
+ for (const token of tokens) {
4703
+ termFreqs.set(token, (termFreqs.get(token) ?? 0) + 1);
4704
+ }
4705
+ const entry = {
4706
+ id: doc.id,
4707
+ content: doc.content,
4708
+ metadata: doc.metadata,
4709
+ termFreqs,
4710
+ length: tokens.length
4711
+ };
4712
+ this.docs.set(doc.id, entry);
4713
+ for (const term of termFreqs.keys()) {
4714
+ this.docFreqs.set(term, (this.docFreqs.get(term) ?? 0) + 1);
4715
+ }
4716
+ this.recomputeAvgLength();
4717
+ }
4718
+ addBatch(docs) {
4719
+ for (const doc of docs) {
4720
+ this.add(doc);
4721
+ }
4722
+ }
4723
+ remove(id) {
4724
+ const existing = this.docs.get(id);
4725
+ if (!existing) return;
4726
+ for (const term of existing.termFreqs.keys()) {
4727
+ const count = this.docFreqs.get(term) ?? 1;
4728
+ if (count <= 1) {
4729
+ this.docFreqs.delete(term);
4730
+ } else {
4731
+ this.docFreqs.set(term, count - 1);
4732
+ }
4733
+ }
4734
+ this.docs.delete(id);
4735
+ this.recomputeAvgLength();
4736
+ }
4737
+ clear() {
4738
+ this.docs.clear();
4739
+ this.docFreqs.clear();
4740
+ this.avgDocLength = 0;
4741
+ }
4742
+ search(query, opts) {
4743
+ const queryTokens = tokenize(query);
4744
+ if (queryTokens.length === 0) return [];
4745
+ const N = this.docs.size;
4746
+ if (N === 0) return [];
4747
+ const topK = opts?.topK ?? 10;
4748
+ const results = [];
4749
+ for (const doc of this.docs.values()) {
4750
+ if (opts?.filter) {
4751
+ let match = true;
4752
+ for (const [k, v] of Object.entries(opts.filter)) {
4753
+ if (doc.metadata?.[k] !== v) {
4754
+ match = false;
4755
+ break;
4756
+ }
4757
+ }
4758
+ if (!match) continue;
4759
+ }
4760
+ let score = 0;
4761
+ for (const term of queryTokens) {
4762
+ const tf = doc.termFreqs.get(term) ?? 0;
4763
+ if (tf === 0) continue;
4764
+ const df = this.docFreqs.get(term) ?? 0;
4765
+ const idf = Math.log(1 + (N - df + 0.5) / (df + 0.5));
4766
+ const numerator = tf * (this.k1 + 1);
4767
+ const denominator = tf + this.k1 * (1 - this.b + this.b * (doc.length / this.avgDocLength));
4768
+ score += idf * (numerator / denominator);
4769
+ }
4770
+ if (score > 0 && (opts?.minScore == null || score >= opts.minScore)) {
4771
+ results.push({
4772
+ id: doc.id,
4773
+ content: doc.content,
4774
+ score,
4775
+ metadata: doc.metadata
4776
+ });
4777
+ }
4778
+ }
4779
+ results.sort((a, b) => b.score - a.score);
4780
+ return results.slice(0, topK);
4781
+ }
4782
+ recomputeAvgLength() {
4783
+ if (this.docs.size === 0) {
4784
+ this.avgDocLength = 0;
4785
+ return;
4786
+ }
4787
+ let total = 0;
4788
+ for (const doc of this.docs.values()) {
4789
+ total += doc.length;
4790
+ }
4791
+ this.avgDocLength = total / this.docs.size;
4792
+ }
4793
+ };
4794
+
4795
+ // src/vector/rrf.ts
4796
+ function reciprocalRankFusion(rankedLists, options) {
4797
+ const k = options?.k ?? 60;
4798
+ const topK = options?.topK ?? 10;
4799
+ const weights = options?.weights ?? rankedLists.map(() => 1);
4800
+ const fusedScores = /* @__PURE__ */ new Map();
4801
+ for (let listIdx = 0; listIdx < rankedLists.length; listIdx++) {
4802
+ const list = rankedLists[listIdx];
4803
+ const weight = weights[listIdx] ?? 1;
4804
+ for (let rank = 0; rank < list.length; rank++) {
4805
+ const item = list[rank];
4806
+ const rrfScore = weight / (k + rank + 1);
4807
+ const existing = fusedScores.get(item.id);
4808
+ if (existing) {
4809
+ existing.score += rrfScore;
4810
+ } else {
4811
+ fusedScores.set(item.id, {
4812
+ score: rrfScore,
4813
+ item: { ...item }
4814
+ });
4815
+ }
4816
+ }
4817
+ }
4818
+ const results = Array.from(fusedScores.values()).map(({ score, item }) => ({
4819
+ ...item,
4820
+ score
4821
+ }));
4822
+ results.sort((a, b) => b.score - a.score);
4823
+ if (options?.minScore != null) {
4824
+ const min = options.minScore;
4825
+ return results.filter((r) => r.score >= min).slice(0, topK);
4826
+ }
4827
+ return results.slice(0, topK);
4828
+ }
4829
+
4830
+ // src/knowledge/knowledge-base.ts
3882
4831
  var KnowledgeBase = class {
3883
4832
  name;
3884
4833
  collection;
3885
4834
  store;
3886
4835
  initialized = false;
4836
+ bm25;
4837
+ defaultSearchMode;
4838
+ hybridConfig;
3887
4839
  constructor(config) {
3888
4840
  this.name = config.name;
3889
4841
  this.store = config.vectorStore;
3890
4842
  this.collection = config.collection ?? config.name.toLowerCase().replace(/\s+/g, "_");
4843
+ this.defaultSearchMode = config.searchMode ?? "vector";
4844
+ this.hybridConfig = {
4845
+ vectorWeight: config.hybridConfig?.vectorWeight ?? 1,
4846
+ keywordWeight: config.hybridConfig?.keywordWeight ?? 1,
4847
+ rrfK: config.hybridConfig?.rrfK ?? 60
4848
+ };
4849
+ this.bm25 = new BM25Index();
3891
4850
  }
3892
4851
  async initialize() {
3893
4852
  if (this.initialized) return;
@@ -3897,14 +4856,27 @@ var KnowledgeBase = class {
3897
4856
  async add(doc) {
3898
4857
  await this.ensureInit();
3899
4858
  await this.store.upsert(this.collection, doc);
4859
+ this.bm25.add({ id: doc.id, content: doc.content, metadata: doc.metadata });
3900
4860
  }
3901
4861
  async addDocuments(docs) {
3902
4862
  await this.ensureInit();
3903
4863
  await this.store.upsertBatch(this.collection, docs);
4864
+ this.bm25.addBatch(
4865
+ docs.map((d) => ({ id: d.id, content: d.content, metadata: d.metadata }))
4866
+ );
3904
4867
  }
3905
4868
  async search(query, options) {
3906
4869
  await this.ensureInit();
3907
- return this.store.search(this.collection, query, options);
4870
+ const mode = options?.searchMode ?? this.defaultSearchMode;
4871
+ switch (mode) {
4872
+ case "keyword":
4873
+ return this.keywordSearch(query, options);
4874
+ case "hybrid":
4875
+ return this.hybridSearch(query, options);
4876
+ case "vector":
4877
+ default:
4878
+ return this.store.search(this.collection, query, options);
4879
+ }
3908
4880
  }
3909
4881
  async get(id) {
3910
4882
  await this.ensureInit();
@@ -3913,9 +4885,11 @@ var KnowledgeBase = class {
3913
4885
  async delete(id) {
3914
4886
  await this.ensureInit();
3915
4887
  await this.store.delete(this.collection, id);
4888
+ this.bm25.remove(id);
3916
4889
  }
3917
4890
  async clear() {
3918
4891
  await this.store.dropCollection(this.collection);
4892
+ this.bm25.clear();
3919
4893
  }
3920
4894
  async close() {
3921
4895
  await this.store.close();
@@ -3928,6 +4902,7 @@ var KnowledgeBase = class {
3928
4902
  const topK = config.topK ?? 5;
3929
4903
  const minScore = config.minScore;
3930
4904
  const filter = config.filter;
4905
+ const searchMode = config.searchMode;
3931
4906
  const toolName = config.toolName ?? `search_${this.collection}`;
3932
4907
  const description = config.description ?? `Search the "${this.name}" knowledge base for relevant information. Use this before answering questions related to ${this.name}.`;
3933
4908
  const formatResults = config.formatResults ?? defaultFormatResults;
@@ -3942,7 +4917,8 @@ var KnowledgeBase = class {
3942
4917
  const results = await kb.search(args.query, {
3943
4918
  topK,
3944
4919
  minScore,
3945
- filter
4920
+ filter,
4921
+ searchMode
3946
4922
  });
3947
4923
  if (results.length === 0) {
3948
4924
  return "No relevant documents found in the knowledge base.";
@@ -3951,6 +4927,60 @@ var KnowledgeBase = class {
3951
4927
  }
3952
4928
  };
3953
4929
  }
4930
+ keywordSearch(query, options) {
4931
+ const results = this.bm25.search(query, {
4932
+ topK: options?.topK ?? 10,
4933
+ filter: options?.filter
4934
+ });
4935
+ return results.map((r) => ({
4936
+ id: r.id,
4937
+ content: r.content,
4938
+ score: r.score,
4939
+ metadata: r.metadata
4940
+ }));
4941
+ }
4942
+ async hybridSearch(query, options) {
4943
+ const topK = options?.topK ?? 10;
4944
+ const fetchK = topK * 2;
4945
+ const [vectorResults, keywordResults] = await Promise.all([
4946
+ this.store.search(this.collection, query, {
4947
+ ...options,
4948
+ topK: fetchK
4949
+ }),
4950
+ Promise.resolve(
4951
+ this.bm25.search(query, {
4952
+ topK: fetchK,
4953
+ filter: options?.filter
4954
+ })
4955
+ )
4956
+ ]);
4957
+ const vectorRanked = vectorResults.map((r) => ({
4958
+ id: r.id,
4959
+ content: r.content,
4960
+ score: r.score,
4961
+ metadata: r.metadata
4962
+ }));
4963
+ const keywordRanked = keywordResults.map((r) => ({
4964
+ id: r.id,
4965
+ content: r.content,
4966
+ score: r.score,
4967
+ metadata: r.metadata
4968
+ }));
4969
+ const fused = reciprocalRankFusion(
4970
+ [vectorRanked, keywordRanked],
4971
+ {
4972
+ k: this.hybridConfig.rrfK,
4973
+ topK,
4974
+ weights: [this.hybridConfig.vectorWeight, this.hybridConfig.keywordWeight]
4975
+ }
4976
+ );
4977
+ return fused.map((r) => ({
4978
+ id: r.id,
4979
+ content: r.content,
4980
+ score: r.score,
4981
+ metadata: r.metadata
4982
+ }));
4983
+ }
3954
4984
  async ensureInit() {
3955
4985
  if (!this.initialized) await this.initialize();
3956
4986
  }
@@ -3968,66 +4998,110 @@ ${lines.join("\n\n")}`;
3968
4998
  }
3969
4999
 
3970
5000
  // src/memory/memory.ts
3971
- var SHORT_TERM_NS = "memory:short";
3972
5001
  var LONG_TERM_NS = "memory:long";
5002
+ var SUMMARIZE_PROMPT = `Summarize the following conversation chunk in 2-3 concise sentences. Focus on key topics discussed, decisions made, and important information shared. Do not include greetings or filler.
5003
+
5004
+ Conversation:
5005
+ {conversation}
5006
+
5007
+ Summary:`;
3973
5008
  var Memory = class {
3974
5009
  storage;
3975
- maxShortTermMessages;
3976
- enableLongTerm;
5010
+ model;
5011
+ maxSummaries;
5012
+ initPromise = null;
3977
5013
  constructor(config) {
3978
5014
  this.storage = config?.storage ?? new InMemoryStorage();
3979
- this.maxShortTermMessages = config?.maxShortTermMessages ?? 50;
3980
- this.enableLongTerm = config?.enableLongTerm ?? false;
3981
- }
3982
- async addMessages(sessionId, messages) {
3983
- const existing = await this.storage.get(SHORT_TERM_NS, sessionId) ?? [];
3984
- const updated = [...existing, ...messages];
3985
- if (updated.length > this.maxShortTermMessages) {
3986
- const overflow = updated.splice(
3987
- 0,
3988
- updated.length - this.maxShortTermMessages
5015
+ this.model = config?.model;
5016
+ this.maxSummaries = config?.maxSummaries ?? 20;
5017
+ }
5018
+ ensureInitialized() {
5019
+ if (!this.initPromise) {
5020
+ this.initPromise = (async () => {
5021
+ if (typeof this.storage.initialize === "function") {
5022
+ await this.storage.initialize();
5023
+ }
5024
+ })();
5025
+ }
5026
+ return this.initPromise;
5027
+ }
5028
+ /**
5029
+ * Summarize overflow messages and store the summary.
5030
+ * Uses the configured LLM model; falls back to basic concatenation if no model.
5031
+ */
5032
+ async summarize(sessionId, messages, fallbackModel) {
5033
+ await this.ensureInitialized();
5034
+ const textParts = messages.filter((m) => m.content).map((m) => `${m.role}: ${getTextContent(m.content)}`);
5035
+ if (textParts.length === 0) return;
5036
+ const model = this.model ?? fallbackModel;
5037
+ let summary;
5038
+ if (model) {
5039
+ try {
5040
+ const prompt = SUMMARIZE_PROMPT.replace(
5041
+ "{conversation}",
5042
+ textParts.join("\n")
5043
+ );
5044
+ const response = await model.generate(
5045
+ [{ role: "user", content: prompt }],
5046
+ { temperature: 0, maxTokens: 300 }
5047
+ );
5048
+ summary = typeof response.message.content === "string" ? response.message.content.trim() : textParts.join(" | ").slice(0, 500);
5049
+ } catch {
5050
+ summary = textParts.join(" | ").slice(0, 500);
5051
+ }
5052
+ } else {
5053
+ summary = textParts.join(" | ").slice(0, 500);
5054
+ }
5055
+ const entry = {
5056
+ key: `${sessionId}:${Date.now()}`,
5057
+ summary,
5058
+ createdAt: /* @__PURE__ */ new Date()
5059
+ };
5060
+ await this.storage.set(LONG_TERM_NS, entry.key, entry);
5061
+ const all = await this.storage.list(LONG_TERM_NS, sessionId);
5062
+ if (all.length > this.maxSummaries) {
5063
+ const sorted = all.sort(
5064
+ (a, b) => new Date(a.value.createdAt).getTime() - new Date(b.value.createdAt).getTime()
3989
5065
  );
3990
- if (this.enableLongTerm && overflow.length > 0) {
3991
- await this.summarizeAndStore(sessionId, overflow);
5066
+ const toDelete = sorted.slice(0, all.length - this.maxSummaries);
5067
+ for (const item of toDelete) {
5068
+ await this.storage.delete(LONG_TERM_NS, item.key);
3992
5069
  }
3993
5070
  }
3994
- await this.storage.set(SHORT_TERM_NS, sessionId, updated);
3995
- }
3996
- async getMessages(sessionId) {
3997
- return await this.storage.get(SHORT_TERM_NS, sessionId) ?? [];
3998
5071
  }
5072
+ /** Get all stored summaries for a session. */
3999
5073
  async getSummaries(sessionId) {
4000
- if (!this.enableLongTerm) return [];
5074
+ await this.ensureInitialized();
4001
5075
  const entries = await this.storage.list(
4002
5076
  LONG_TERM_NS,
4003
5077
  sessionId
4004
5078
  );
4005
- return entries.map((e) => e.value.summary);
5079
+ return entries.sort(
5080
+ (a, b) => new Date(a.value.createdAt).getTime() - new Date(b.value.createdAt).getTime()
5081
+ ).map((e) => e.value.summary);
4006
5082
  }
5083
+ /** Get summaries formatted as a context string for injection into the system prompt. */
4007
5084
  async getContextString(sessionId) {
4008
5085
  const summaries = await this.getSummaries(sessionId);
4009
5086
  if (summaries.length === 0) return "";
4010
- return `Previous context:
5087
+ return `Previous conversation context:
4011
5088
  ${summaries.join("\n")}`;
4012
5089
  }
4013
- async summarizeAndStore(sessionId, messages) {
4014
- const textParts = messages.filter((m) => m.content).map((m) => `${m.role}: ${getTextContent(m.content)}`);
4015
- if (textParts.length === 0) return;
4016
- const summary = textParts.join(" | ").slice(0, 500);
4017
- const entry = {
4018
- key: `${sessionId}:${Date.now()}`,
4019
- summary,
4020
- createdAt: /* @__PURE__ */ new Date()
4021
- };
4022
- await this.storage.set(LONG_TERM_NS, entry.key, entry);
4023
- }
5090
+ /** Clear all summaries for a session. */
4024
5091
  async clear(sessionId) {
4025
- await this.storage.delete(SHORT_TERM_NS, sessionId);
5092
+ await this.ensureInitialized();
5093
+ const entries = await this.storage.list(
5094
+ LONG_TERM_NS,
5095
+ sessionId
5096
+ );
5097
+ for (const entry of entries) {
5098
+ await this.storage.delete(LONG_TERM_NS, entry.key);
5099
+ }
4026
5100
  }
4027
5101
  };
4028
5102
 
4029
5103
  // src/memory/user-memory.ts
4030
- import { v4 as uuidv45 } from "uuid";
5104
+ import { v4 as uuidv46 } from "uuid";
4031
5105
  import { z as z2 } from "zod";
4032
5106
  var USER_MEMORY_NS = "memory:user";
4033
5107
  var EXTRACTION_PROMPT = `You are a memory extraction assistant. Analyze the conversation below and extract important facts about the user that would be useful for future personalization.
@@ -4082,7 +5156,7 @@ var UserMemory = class {
4082
5156
  const normalized = fact.trim();
4083
5157
  if (!normalized || existingSet.has(normalized.toLowerCase())) continue;
4084
5158
  newFacts.push({
4085
- id: uuidv45(),
5159
+ id: uuidv46(),
4086
5160
  fact: normalized,
4087
5161
  createdAt: /* @__PURE__ */ new Date(),
4088
5162
  source
@@ -4556,6 +5630,258 @@ var A2ARemoteAgent = class {
4556
5630
  }
4557
5631
  };
4558
5632
 
5633
+ // src/voice/voice-agent.ts
5634
+ import { EventEmitter as EventEmitter4 } from "events";
5635
+ import { v4 as uuidv47 } from "uuid";
5636
+ var VoiceSessionImpl = class extends EventEmitter4 {
5637
+ connection;
5638
+ constructor(connection) {
5639
+ super();
5640
+ this.connection = connection;
5641
+ }
5642
+ sendAudio(data) {
5643
+ this.connection.sendAudio(data);
5644
+ }
5645
+ sendText(text) {
5646
+ this.connection.sendText(text);
5647
+ }
5648
+ interrupt() {
5649
+ this.connection.interrupt();
5650
+ }
5651
+ async close() {
5652
+ await this.connection.close();
5653
+ }
5654
+ on(event, handler) {
5655
+ return super.on(event, handler);
5656
+ }
5657
+ off(event, handler) {
5658
+ return super.off(event, handler);
5659
+ }
5660
+ };
5661
+ var VoiceAgent = class {
5662
+ name;
5663
+ config;
5664
+ eventBus;
5665
+ logger;
5666
+ toolExecutor;
5667
+ constructor(config) {
5668
+ this.name = config.name;
5669
+ this.config = config;
5670
+ this.eventBus = config.eventBus ?? new EventBus();
5671
+ this.logger = new Logger({
5672
+ level: config.logLevel ?? "silent",
5673
+ prefix: config.name
5674
+ });
5675
+ this.toolExecutor = config.tools && config.tools.length > 0 ? new ToolExecutor(config.tools) : null;
5676
+ }
5677
+ async connect(opts) {
5678
+ const toolDefs = this.toolExecutor?.getToolDefinitions() ?? [];
5679
+ const sessionId = opts?.sessionId ?? this.config.sessionId ?? `voice_${uuidv47()}`;
5680
+ const userId = opts?.userId ?? this.config.userId;
5681
+ let instructions = this.config.instructions ?? "";
5682
+ if (this.config.userMemory && userId) {
5683
+ const userContext = await this.config.userMemory.getContextString(userId);
5684
+ if (userContext) {
5685
+ instructions = instructions ? `${instructions}
5686
+
5687
+ ${userContext}` : userContext;
5688
+ const facts = await this.config.userMemory.getFacts(userId);
5689
+ this.logger.info(
5690
+ `Loaded ${facts.length} user facts for "${userId}"`
5691
+ );
5692
+ }
5693
+ }
5694
+ const sessionConfig = {
5695
+ instructions,
5696
+ voice: this.config.voice,
5697
+ tools: toolDefs,
5698
+ inputAudioFormat: this.config.inputAudioFormat,
5699
+ outputAudioFormat: this.config.outputAudioFormat,
5700
+ turnDetection: this.config.turnDetection,
5701
+ temperature: this.config.temperature,
5702
+ maxResponseOutputTokens: this.config.maxResponseOutputTokens,
5703
+ apiKey: opts?.apiKey
5704
+ };
5705
+ this.logger.info("Connecting to realtime provider...");
5706
+ const connection = await this.config.provider.connect(sessionConfig);
5707
+ const session = new VoiceSessionImpl(connection);
5708
+ const ctx = new RunContext({
5709
+ sessionId,
5710
+ userId,
5711
+ eventBus: this.eventBus
5712
+ });
5713
+ const transcripts = [];
5714
+ let persisted = false;
5715
+ this.wireEvents(connection, session, ctx, transcripts);
5716
+ const onSessionEnd = async () => {
5717
+ if (persisted) return;
5718
+ persisted = true;
5719
+ await this.persistSession(sessionId, userId, transcripts);
5720
+ };
5721
+ session.on("disconnected", () => {
5722
+ onSessionEnd().catch(
5723
+ (e) => this.logger.warn(`Session persist failed: ${e}`)
5724
+ );
5725
+ });
5726
+ const originalClose = session.close.bind(session);
5727
+ session.close = async () => {
5728
+ await originalClose();
5729
+ await onSessionEnd();
5730
+ };
5731
+ this.eventBus.emit("voice.connected", { agentName: this.name });
5732
+ this.logger.info(
5733
+ `Voice session connected (session=${sessionId}, user=${userId ?? "anonymous"})`
5734
+ );
5735
+ return session;
5736
+ }
5737
+ /**
5738
+ * Consolidate transcript deltas into full messages.
5739
+ * Voice transcripts arrive as many small chunks (one per word/syllable).
5740
+ * Consecutive entries with the same role are merged into a single message.
5741
+ */
5742
+ consolidateTranscripts(transcripts) {
5743
+ const consolidated = [];
5744
+ let current = null;
5745
+ for (const t of transcripts) {
5746
+ if (current && current.role === t.role) {
5747
+ current.content += t.text;
5748
+ } else {
5749
+ if (current && current.content.trim()) {
5750
+ consolidated.push(current);
5751
+ }
5752
+ current = { role: t.role, content: t.text };
5753
+ }
5754
+ }
5755
+ if (current && current.content.trim()) {
5756
+ consolidated.push(current);
5757
+ }
5758
+ return consolidated;
5759
+ }
5760
+ async persistSession(_sessionId, userId, transcripts) {
5761
+ if (transcripts.length === 0) return;
5762
+ const messages = this.consolidateTranscripts(transcripts);
5763
+ this.logger.info(
5764
+ `Consolidated ${transcripts.length} transcript deltas into ${messages.length} messages`
5765
+ );
5766
+ for (const m of messages) {
5767
+ this.logger.info(` [${m.role}] ${m.content.substring(0, 100)}`);
5768
+ }
5769
+ if (this.config.userMemory && userId) {
5770
+ try {
5771
+ await this.config.userMemory.extractAndStore(
5772
+ userId,
5773
+ messages,
5774
+ this.config.model
5775
+ );
5776
+ const facts = await this.config.userMemory.getFacts(userId);
5777
+ this.logger.info(
5778
+ `Extracted user facts for "${userId}" \u2014 total: ${facts.length}`
5779
+ );
5780
+ } catch (e) {
5781
+ this.logger.warn(`UserMemory extraction failed: ${e.message ?? e}`);
5782
+ }
5783
+ }
5784
+ }
5785
+ wireEvents(connection, session, ctx, transcripts) {
5786
+ connection.on("audio", (data) => {
5787
+ session.emit("audio", data);
5788
+ this.eventBus.emit("voice.audio", {
5789
+ agentName: this.name,
5790
+ data: data.data
5791
+ });
5792
+ });
5793
+ connection.on("text", (data) => {
5794
+ session.emit("text", data);
5795
+ });
5796
+ connection.on("transcript", (data) => {
5797
+ session.emit("transcript", data);
5798
+ transcripts.push({ role: data.role, text: data.text });
5799
+ this.eventBus.emit("voice.transcript", {
5800
+ agentName: this.name,
5801
+ text: data.text,
5802
+ role: data.role
5803
+ });
5804
+ this.logger.info(`[${data.role}] ${data.text}`);
5805
+ });
5806
+ connection.on("tool_call", (toolCall) => {
5807
+ this.handleToolCall(connection, session, ctx, toolCall);
5808
+ });
5809
+ connection.on("interrupted", () => {
5810
+ session.emit("interrupted", {});
5811
+ this.logger.debug("Response interrupted by user speech");
5812
+ });
5813
+ connection.on("error", (data) => {
5814
+ session.emit("error", data);
5815
+ this.eventBus.emit("voice.error", {
5816
+ agentName: this.name,
5817
+ error: data.error
5818
+ });
5819
+ this.logger.error(`Error: ${data.error.message}`);
5820
+ });
5821
+ connection.on("disconnected", () => {
5822
+ session.emit("disconnected", {});
5823
+ this.eventBus.emit("voice.disconnected", { agentName: this.name });
5824
+ this.logger.info("Voice session disconnected");
5825
+ });
5826
+ }
5827
+ async handleToolCall(connection, session, ctx, toolCall) {
5828
+ if (!this.toolExecutor) {
5829
+ this.logger.warn(
5830
+ `Tool call "${toolCall.name}" received but no tools registered`
5831
+ );
5832
+ connection.sendToolResult(
5833
+ toolCall.id,
5834
+ JSON.stringify({ error: "No tools available" })
5835
+ );
5836
+ return;
5837
+ }
5838
+ let parsedArgs = {};
5839
+ try {
5840
+ parsedArgs = JSON.parse(toolCall.arguments || "{}");
5841
+ } catch {
5842
+ parsedArgs = {};
5843
+ }
5844
+ session.emit("tool_call_start", {
5845
+ name: toolCall.name,
5846
+ args: parsedArgs
5847
+ });
5848
+ this.eventBus.emit("voice.tool.call", {
5849
+ agentName: this.name,
5850
+ toolName: toolCall.name,
5851
+ args: parsedArgs
5852
+ });
5853
+ this.logger.info(`Tool call: ${toolCall.name}`);
5854
+ try {
5855
+ const results = await this.toolExecutor.executeAll(
5856
+ [{ id: toolCall.id, name: toolCall.name, arguments: parsedArgs }],
5857
+ ctx
5858
+ );
5859
+ const result = results[0];
5860
+ const resultContent = typeof result.result === "string" ? result.result : result.result.content;
5861
+ connection.sendToolResult(toolCall.id, resultContent);
5862
+ session.emit("tool_result", {
5863
+ name: toolCall.name,
5864
+ result: resultContent
5865
+ });
5866
+ this.eventBus.emit("voice.tool.result", {
5867
+ agentName: this.name,
5868
+ toolName: toolCall.name,
5869
+ result: resultContent
5870
+ });
5871
+ this.logger.info(
5872
+ `Tool result: ${toolCall.name} -> ${resultContent.substring(0, 100)}`
5873
+ );
5874
+ } catch (error) {
5875
+ const errMsg = error?.message ?? "Tool execution failed";
5876
+ connection.sendToolResult(
5877
+ toolCall.id,
5878
+ JSON.stringify({ error: errMsg })
5879
+ );
5880
+ this.logger.error(`Tool error: ${toolCall.name} -> ${errMsg}`);
5881
+ }
5882
+ }
5883
+ };
5884
+
4559
5885
  // src/toolkits/base.ts
4560
5886
  var Toolkit = class {
4561
5887
  };
@@ -4666,8 +5992,8 @@ Snippet: ${r.snippet ?? ""}
4666
5992
 
4667
5993
  // src/toolkits/gmail.ts
4668
5994
  import { z as z5 } from "zod";
4669
- import { createRequire as createRequire16 } from "module";
4670
- var _require16 = createRequire16(import.meta.url);
5995
+ import { createRequire as createRequire18 } from "module";
5996
+ var _require18 = createRequire18(import.meta.url);
4671
5997
  var GmailToolkit = class extends Toolkit {
4672
5998
  name = "gmail";
4673
5999
  config;
@@ -4679,7 +6005,7 @@ var GmailToolkit = class extends Toolkit {
4679
6005
  async getGmailClient() {
4680
6006
  if (this.gmail) return this.gmail;
4681
6007
  if (this.config.authClient) {
4682
- const { google: google3 } = _require16("googleapis");
6008
+ const { google: google3 } = _require18("googleapis");
4683
6009
  this.gmail = google3.gmail({ version: "v1", auth: this.config.authClient });
4684
6010
  return this.gmail;
4685
6011
  }
@@ -4690,7 +6016,7 @@ var GmailToolkit = class extends Toolkit {
4690
6016
  "GmailToolkit: Provide credentialsPath + tokenPath, or an authClient. Set GMAIL_CREDENTIALS_PATH and GMAIL_TOKEN_PATH env vars, or pass them in config."
4691
6017
  );
4692
6018
  }
4693
- const { google: google2 } = _require16("googleapis");
6019
+ const { google: google2 } = _require18("googleapis");
4694
6020
  const fs = await import("fs");
4695
6021
  const creds = JSON.parse(fs.readFileSync(credPath, "utf-8"));
4696
6022
  const token = JSON.parse(fs.readFileSync(tokenPath, "utf-8"));
@@ -5221,11 +6547,14 @@ export {
5221
6547
  A2ARemoteAgent,
5222
6548
  Agent,
5223
6549
  AnthropicProvider,
6550
+ ApprovalManager,
6551
+ BM25Index,
5224
6552
  BaseVectorStore,
5225
6553
  DuckDuckGoToolkit,
5226
6554
  EventBus,
5227
6555
  GmailToolkit,
5228
6556
  GoogleEmbedding,
6557
+ GoogleLiveProvider,
5229
6558
  GoogleProvider,
5230
6559
  HackerNewsToolkit,
5231
6560
  InMemoryStorage,
@@ -5241,10 +6570,12 @@ export {
5241
6570
  OllamaProvider,
5242
6571
  OpenAIEmbedding,
5243
6572
  OpenAIProvider,
6573
+ OpenAIRealtimeProvider,
5244
6574
  PgVectorStore,
5245
6575
  PostgresStorage,
5246
6576
  QdrantVectorStore,
5247
6577
  RunContext,
6578
+ Sandbox,
5248
6579
  SessionManager,
5249
6580
  SqliteStorage,
5250
6581
  Team,
@@ -5253,6 +6584,7 @@ export {
5253
6584
  Toolkit,
5254
6585
  UserMemory,
5255
6586
  VertexAIProvider,
6587
+ VoiceAgent,
5256
6588
  WebSearchToolkit,
5257
6589
  WhatsAppToolkit,
5258
6590
  Workflow,
@@ -5260,10 +6592,14 @@ export {
5260
6592
  defineTool,
5261
6593
  getTextContent,
5262
6594
  google,
6595
+ googleLive,
5263
6596
  isMultiModal,
5264
6597
  ollama,
5265
6598
  openai,
6599
+ openaiRealtime,
6600
+ reciprocalRankFusion,
5266
6601
  registry,
6602
+ resolveSandboxConfig,
5267
6603
  vertex,
5268
6604
  withRetry
5269
6605
  };