@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.d.ts +454 -13
- package/dist/index.js +1607 -271
- package/dist/tools/sandbox-worker.d.ts +2 -0
- package/dist/tools/sandbox-worker.js +21 -0
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/agent/agent.ts
|
|
2
|
-
import { v4 as
|
|
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
|
-
|
|
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
|
-
|
|
326
|
+
agentSandbox;
|
|
327
|
+
approvalManager;
|
|
328
|
+
agentName;
|
|
329
|
+
constructor(tools, configOrConcurrency) {
|
|
129
330
|
this.tools = new Map(tools.map((t) => [t.name, t]));
|
|
130
|
-
|
|
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
|
|
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
|
|
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 ??
|
|
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
|
-
|
|
791
|
-
|
|
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 ??
|
|
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
|
-
|
|
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
|
-
|
|
854
|
-
|
|
855
|
-
|
|
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
|
-
|
|
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 ??
|
|
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
|
-
|
|
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
|
-
|
|
966
|
-
|
|
967
|
-
|
|
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
|
-
|
|
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
|
|
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 ??
|
|
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
|
|
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 ??
|
|
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/
|
|
2955
|
-
|
|
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
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
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
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3226
|
+
}
|
|
3227
|
+
var OpenAIRealtimeConnection = class extends EventEmitter2 {
|
|
3228
|
+
ws;
|
|
3229
|
+
closed = false;
|
|
3230
|
+
constructor(ws) {
|
|
3231
|
+
super();
|
|
3232
|
+
this.ws = ws;
|
|
3053
3233
|
}
|
|
3054
|
-
|
|
3055
|
-
this.
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
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
|
-
|
|
3063
|
-
this.
|
|
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
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
3095
|
-
|
|
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
|
-
|
|
3114
|
-
|
|
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
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
);
|
|
3284
|
+
send(event) {
|
|
3285
|
+
if (this.ws.readyState === 1) {
|
|
3286
|
+
this.ws.send(JSON.stringify(event));
|
|
3287
|
+
}
|
|
3127
3288
|
}
|
|
3128
|
-
|
|
3129
|
-
|
|
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
|
|
3148
|
-
var
|
|
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 } =
|
|
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
|
|
3316
|
-
var
|
|
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 } =
|
|
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
|
|
4183
|
+
import { createRequire as createRequire14 } from "module";
|
|
3445
4184
|
import { createHash } from "crypto";
|
|
3446
|
-
var
|
|
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 } =
|
|
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
|
|
3605
|
-
var
|
|
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 } =
|
|
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
|
|
3789
|
-
var
|
|
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 =
|
|
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
|
|
3835
|
-
var
|
|
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 } =
|
|
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
|
-
|
|
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
|
-
|
|
3976
|
-
|
|
5010
|
+
model;
|
|
5011
|
+
maxSummaries;
|
|
5012
|
+
initPromise = null;
|
|
3977
5013
|
constructor(config) {
|
|
3978
5014
|
this.storage = config?.storage ?? new InMemoryStorage();
|
|
3979
|
-
this.
|
|
3980
|
-
this.
|
|
3981
|
-
}
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
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
|
-
|
|
3991
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
|
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:
|
|
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
|
|
4670
|
-
var
|
|
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 } =
|
|
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 } =
|
|
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
|
};
|