@radaros/core 0.3.1 → 0.3.3
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/package.json +1 -1
- package/src/agent/agent.ts +53 -0
- package/src/agent/llm-loop.ts +18 -0
- package/src/agent/types.ts +8 -1
- package/src/index.ts +6 -1
- package/src/logger/logger.ts +19 -2
- package/src/memory/user-memory.ts +191 -0
- package/src/models/providers/anthropic.ts +34 -4
- package/src/models/providers/google.ts +29 -4
- package/src/models/providers/openai.ts +28 -6
- package/src/models/providers/vertex.ts +31 -4
- package/src/models/types.ts +12 -0
- package/src/toolkits/duckduckgo.ts +256 -0
- package/src/tools/define-tool.ts +3 -1
- package/src/tools/tool-executor.ts +63 -1
- package/src/tools/types.ts +7 -0
- package/dist/index.d.ts +0 -1283
- package/dist/index.js +0 -4640
package/dist/index.js
DELETED
|
@@ -1,4640 +0,0 @@
|
|
|
1
|
-
// src/agent/agent.ts
|
|
2
|
-
import { v4 as uuidv42 } from "uuid";
|
|
3
|
-
|
|
4
|
-
// src/events/event-bus.ts
|
|
5
|
-
import { EventEmitter } from "events";
|
|
6
|
-
var EventBus = class {
|
|
7
|
-
emitter = new EventEmitter();
|
|
8
|
-
constructor() {
|
|
9
|
-
this.emitter.setMaxListeners(100);
|
|
10
|
-
}
|
|
11
|
-
on(event, handler) {
|
|
12
|
-
this.emitter.on(event, handler);
|
|
13
|
-
return this;
|
|
14
|
-
}
|
|
15
|
-
once(event, handler) {
|
|
16
|
-
this.emitter.once(event, handler);
|
|
17
|
-
return this;
|
|
18
|
-
}
|
|
19
|
-
off(event, handler) {
|
|
20
|
-
this.emitter.off(event, handler);
|
|
21
|
-
return this;
|
|
22
|
-
}
|
|
23
|
-
emit(event, data) {
|
|
24
|
-
return this.emitter.emit(event, data);
|
|
25
|
-
}
|
|
26
|
-
removeAllListeners(event) {
|
|
27
|
-
this.emitter.removeAllListeners(event);
|
|
28
|
-
return this;
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
// src/storage/in-memory.ts
|
|
33
|
-
var InMemoryStorage = class {
|
|
34
|
-
store = /* @__PURE__ */ new Map();
|
|
35
|
-
makeKey(namespace, key) {
|
|
36
|
-
return `${namespace}:${key}`;
|
|
37
|
-
}
|
|
38
|
-
async get(namespace, key) {
|
|
39
|
-
const raw = this.store.get(this.makeKey(namespace, key));
|
|
40
|
-
if (raw === void 0) return null;
|
|
41
|
-
return JSON.parse(raw);
|
|
42
|
-
}
|
|
43
|
-
async set(namespace, key, value) {
|
|
44
|
-
this.store.set(this.makeKey(namespace, key), JSON.stringify(value));
|
|
45
|
-
}
|
|
46
|
-
async delete(namespace, key) {
|
|
47
|
-
this.store.delete(this.makeKey(namespace, key));
|
|
48
|
-
}
|
|
49
|
-
async list(namespace, prefix) {
|
|
50
|
-
const nsPrefix = prefix ? `${namespace}:${prefix}` : `${namespace}:`;
|
|
51
|
-
const results = [];
|
|
52
|
-
for (const [fullKey, raw] of this.store.entries()) {
|
|
53
|
-
if (fullKey.startsWith(nsPrefix)) {
|
|
54
|
-
const key = fullKey.slice(namespace.length + 1);
|
|
55
|
-
results.push({ key, value: JSON.parse(raw) });
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return results;
|
|
59
|
-
}
|
|
60
|
-
async close() {
|
|
61
|
-
this.store.clear();
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
// src/session/session-manager.ts
|
|
66
|
-
var NAMESPACE = "sessions";
|
|
67
|
-
var SessionManager = class {
|
|
68
|
-
constructor(storage) {
|
|
69
|
-
this.storage = storage;
|
|
70
|
-
}
|
|
71
|
-
async getOrCreate(sessionId, userId) {
|
|
72
|
-
const existing = await this.storage.get(NAMESPACE, sessionId);
|
|
73
|
-
if (existing) return existing;
|
|
74
|
-
const session = {
|
|
75
|
-
sessionId,
|
|
76
|
-
userId,
|
|
77
|
-
messages: [],
|
|
78
|
-
state: {},
|
|
79
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
80
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
81
|
-
};
|
|
82
|
-
await this.storage.set(NAMESPACE, sessionId, session);
|
|
83
|
-
return session;
|
|
84
|
-
}
|
|
85
|
-
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);
|
|
90
|
-
}
|
|
91
|
-
async appendMessages(sessionId, msgs) {
|
|
92
|
-
const session = await this.getOrCreate(sessionId);
|
|
93
|
-
session.messages.push(...msgs);
|
|
94
|
-
session.updatedAt = /* @__PURE__ */ new Date();
|
|
95
|
-
await this.storage.set(NAMESPACE, sessionId, session);
|
|
96
|
-
}
|
|
97
|
-
async getHistory(sessionId, limit) {
|
|
98
|
-
const session = await this.storage.get(NAMESPACE, sessionId);
|
|
99
|
-
if (!session) return [];
|
|
100
|
-
if (limit && limit > 0) {
|
|
101
|
-
return session.messages.slice(-limit);
|
|
102
|
-
}
|
|
103
|
-
return session.messages;
|
|
104
|
-
}
|
|
105
|
-
async updateState(sessionId, patch) {
|
|
106
|
-
const session = await this.getOrCreate(sessionId);
|
|
107
|
-
Object.assign(session.state, patch);
|
|
108
|
-
session.updatedAt = /* @__PURE__ */ new Date();
|
|
109
|
-
await this.storage.set(NAMESPACE, sessionId, session);
|
|
110
|
-
}
|
|
111
|
-
async getState(sessionId) {
|
|
112
|
-
const session = await this.storage.get(NAMESPACE, sessionId);
|
|
113
|
-
return session?.state ?? {};
|
|
114
|
-
}
|
|
115
|
-
async deleteSession(sessionId) {
|
|
116
|
-
await this.storage.delete(NAMESPACE, sessionId);
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
// src/tools/tool-executor.ts
|
|
121
|
-
import { createRequire } from "module";
|
|
122
|
-
var _require = createRequire(import.meta.url);
|
|
123
|
-
var ToolExecutor = class {
|
|
124
|
-
tools;
|
|
125
|
-
concurrency;
|
|
126
|
-
constructor(tools, concurrency = 5) {
|
|
127
|
-
this.tools = new Map(tools.map((t) => [t.name, t]));
|
|
128
|
-
this.concurrency = concurrency;
|
|
129
|
-
}
|
|
130
|
-
async executeAll(toolCalls, ctx) {
|
|
131
|
-
const results = [];
|
|
132
|
-
for (let i = 0; i < toolCalls.length; i += this.concurrency) {
|
|
133
|
-
const batch = toolCalls.slice(i, i + this.concurrency);
|
|
134
|
-
const batchResults = await Promise.allSettled(
|
|
135
|
-
batch.map((tc) => this.executeSingle(tc, ctx))
|
|
136
|
-
);
|
|
137
|
-
for (let j = 0; j < batchResults.length; j++) {
|
|
138
|
-
const settled = batchResults[j];
|
|
139
|
-
const tc = batch[j];
|
|
140
|
-
if (settled.status === "fulfilled") {
|
|
141
|
-
results.push(settled.value);
|
|
142
|
-
} else {
|
|
143
|
-
results.push({
|
|
144
|
-
toolCallId: tc.id,
|
|
145
|
-
toolName: tc.name,
|
|
146
|
-
result: `Error: ${settled.reason?.message ?? "Unknown error"}`,
|
|
147
|
-
error: settled.reason?.message ?? "Unknown error"
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
return results;
|
|
153
|
-
}
|
|
154
|
-
async executeSingle(toolCall, ctx) {
|
|
155
|
-
const tool = this.tools.get(toolCall.name);
|
|
156
|
-
if (!tool) {
|
|
157
|
-
return {
|
|
158
|
-
toolCallId: toolCall.id,
|
|
159
|
-
toolName: toolCall.name,
|
|
160
|
-
result: `Error: Tool "${toolCall.name}" not found`,
|
|
161
|
-
error: `Tool "${toolCall.name}" not found`
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
ctx.eventBus.emit("tool.call", {
|
|
165
|
-
runId: ctx.runId,
|
|
166
|
-
toolName: toolCall.name,
|
|
167
|
-
args: toolCall.arguments
|
|
168
|
-
});
|
|
169
|
-
const parsed = tool.parameters.safeParse(toolCall.arguments);
|
|
170
|
-
if (!parsed.success) {
|
|
171
|
-
const errMsg = `Invalid arguments: ${parsed.error.message}`;
|
|
172
|
-
const result = {
|
|
173
|
-
toolCallId: toolCall.id,
|
|
174
|
-
toolName: toolCall.name,
|
|
175
|
-
result: errMsg,
|
|
176
|
-
error: errMsg
|
|
177
|
-
};
|
|
178
|
-
ctx.eventBus.emit("tool.result", {
|
|
179
|
-
runId: ctx.runId,
|
|
180
|
-
toolName: toolCall.name,
|
|
181
|
-
result: errMsg
|
|
182
|
-
});
|
|
183
|
-
return result;
|
|
184
|
-
}
|
|
185
|
-
const rawResult = await tool.execute(parsed.data, ctx);
|
|
186
|
-
const resultContent = typeof rawResult === "string" ? rawResult : rawResult.content;
|
|
187
|
-
ctx.eventBus.emit("tool.result", {
|
|
188
|
-
runId: ctx.runId,
|
|
189
|
-
toolName: toolCall.name,
|
|
190
|
-
result: resultContent
|
|
191
|
-
});
|
|
192
|
-
return {
|
|
193
|
-
toolCallId: toolCall.id,
|
|
194
|
-
toolName: toolCall.name,
|
|
195
|
-
result: rawResult
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
getToolDefinitions() {
|
|
199
|
-
const { zodToJsonSchema } = _require("zod-to-json-schema");
|
|
200
|
-
const defs = [];
|
|
201
|
-
for (const tool of this.tools.values()) {
|
|
202
|
-
if (tool.rawJsonSchema) {
|
|
203
|
-
defs.push({
|
|
204
|
-
name: tool.name,
|
|
205
|
-
description: tool.description,
|
|
206
|
-
parameters: tool.rawJsonSchema
|
|
207
|
-
});
|
|
208
|
-
} else {
|
|
209
|
-
const jsonSchema = zodToJsonSchema(tool.parameters, {
|
|
210
|
-
target: "openApi3"
|
|
211
|
-
});
|
|
212
|
-
defs.push({
|
|
213
|
-
name: tool.name,
|
|
214
|
-
description: tool.description,
|
|
215
|
-
parameters: jsonSchema
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
return defs;
|
|
220
|
-
}
|
|
221
|
-
};
|
|
222
|
-
|
|
223
|
-
// src/logger/logger.ts
|
|
224
|
-
var LEVEL_ORDER = {
|
|
225
|
-
debug: 0,
|
|
226
|
-
info: 1,
|
|
227
|
-
warn: 2,
|
|
228
|
-
error: 3,
|
|
229
|
-
silent: 4
|
|
230
|
-
};
|
|
231
|
-
var C = {
|
|
232
|
-
reset: "\x1B[0m",
|
|
233
|
-
bold: "\x1B[1m",
|
|
234
|
-
dim: "\x1B[2m",
|
|
235
|
-
italic: "\x1B[3m",
|
|
236
|
-
black: "\x1B[30m",
|
|
237
|
-
red: "\x1B[31m",
|
|
238
|
-
green: "\x1B[32m",
|
|
239
|
-
yellow: "\x1B[33m",
|
|
240
|
-
blue: "\x1B[34m",
|
|
241
|
-
magenta: "\x1B[35m",
|
|
242
|
-
cyan: "\x1B[36m",
|
|
243
|
-
white: "\x1B[37m",
|
|
244
|
-
bgBlack: "\x1B[40m",
|
|
245
|
-
bgRed: "\x1B[41m",
|
|
246
|
-
bgGreen: "\x1B[42m",
|
|
247
|
-
bgYellow: "\x1B[43m",
|
|
248
|
-
bgBlue: "\x1B[44m",
|
|
249
|
-
bgMagenta: "\x1B[45m",
|
|
250
|
-
bgCyan: "\x1B[46m",
|
|
251
|
-
gray: "\x1B[90m",
|
|
252
|
-
brightGreen: "\x1B[92m",
|
|
253
|
-
brightYellow: "\x1B[93m",
|
|
254
|
-
brightBlue: "\x1B[94m",
|
|
255
|
-
brightMagenta: "\x1B[95m",
|
|
256
|
-
brightCyan: "\x1B[96m"
|
|
257
|
-
};
|
|
258
|
-
function noColor(str) {
|
|
259
|
-
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
260
|
-
}
|
|
261
|
-
var Logger = class {
|
|
262
|
-
level;
|
|
263
|
-
color;
|
|
264
|
-
prefix;
|
|
265
|
-
constructor(config = {}) {
|
|
266
|
-
this.level = config.level ?? "info";
|
|
267
|
-
this.color = config.color ?? process.stdout.isTTY !== false;
|
|
268
|
-
this.prefix = config.prefix ?? "radaros";
|
|
269
|
-
}
|
|
270
|
-
c(code, text) {
|
|
271
|
-
return this.color ? `${code}${text}${C.reset}` : text;
|
|
272
|
-
}
|
|
273
|
-
shouldLog(level) {
|
|
274
|
-
return LEVEL_ORDER[level] >= LEVEL_ORDER[this.level];
|
|
275
|
-
}
|
|
276
|
-
tag(level) {
|
|
277
|
-
switch (level) {
|
|
278
|
-
case "debug":
|
|
279
|
-
return this.c(C.gray, "DBG");
|
|
280
|
-
case "info":
|
|
281
|
-
return this.c(C.brightCyan, "INF");
|
|
282
|
-
case "warn":
|
|
283
|
-
return this.c(C.brightYellow, "WRN");
|
|
284
|
-
case "error":
|
|
285
|
-
return this.c(C.red, "ERR");
|
|
286
|
-
default:
|
|
287
|
-
return "";
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
timestamp() {
|
|
291
|
-
const now = /* @__PURE__ */ new Date();
|
|
292
|
-
const ts = now.toISOString().slice(11, 23);
|
|
293
|
-
return this.c(C.dim, ts);
|
|
294
|
-
}
|
|
295
|
-
log(level, msg, data) {
|
|
296
|
-
if (!this.shouldLog(level)) return;
|
|
297
|
-
const parts = [
|
|
298
|
-
this.timestamp(),
|
|
299
|
-
this.tag(level),
|
|
300
|
-
this.c(C.dim, `[${this.prefix}]`),
|
|
301
|
-
msg
|
|
302
|
-
];
|
|
303
|
-
if (data && Object.keys(data).length > 0) {
|
|
304
|
-
const formatted = Object.entries(data).map(([k, v]) => `${this.c(C.dim, k + "=")}${this.formatValue(v)}`).join(" ");
|
|
305
|
-
parts.push(formatted);
|
|
306
|
-
}
|
|
307
|
-
console.log(parts.join(" "));
|
|
308
|
-
}
|
|
309
|
-
formatValue(v) {
|
|
310
|
-
if (typeof v === "number") return this.c(C.brightGreen, String(v));
|
|
311
|
-
if (typeof v === "string") return this.c(C.yellow, `"${v}"`);
|
|
312
|
-
if (typeof v === "boolean") return this.c(C.magenta, String(v));
|
|
313
|
-
return String(v);
|
|
314
|
-
}
|
|
315
|
-
debug(msg, data) {
|
|
316
|
-
this.log("debug", msg, data);
|
|
317
|
-
}
|
|
318
|
-
info(msg, data) {
|
|
319
|
-
this.log("info", msg, data);
|
|
320
|
-
}
|
|
321
|
-
warn(msg, data) {
|
|
322
|
-
this.log("warn", msg, data);
|
|
323
|
-
}
|
|
324
|
-
error(msg, data) {
|
|
325
|
-
this.log("error", msg, data);
|
|
326
|
-
}
|
|
327
|
-
// ── Formatted agent output helpers ────────────────────────────────────
|
|
328
|
-
boxWidth = 80;
|
|
329
|
-
pipe() {
|
|
330
|
-
return this.c(C.brightCyan, "\u2502");
|
|
331
|
-
}
|
|
332
|
-
printBoxLine(label, value, labelColor = C.dim, valueColor = C.white) {
|
|
333
|
-
const lines = value.split("\n");
|
|
334
|
-
const prefix = `${this.pipe()} ${this.c(labelColor, label)}`;
|
|
335
|
-
console.log(`${prefix}${this.c(valueColor, lines[0])}`);
|
|
336
|
-
const pad = " ".repeat(noColor(label).length);
|
|
337
|
-
for (let i = 1; i < lines.length; i++) {
|
|
338
|
-
console.log(`${this.pipe()} ${pad}${this.c(valueColor, lines[i])}`);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
agentStart(agentName, input) {
|
|
342
|
-
if (!this.shouldLog("info")) return;
|
|
343
|
-
const title = ` Agent: ${agentName} `;
|
|
344
|
-
const lineLen = Math.max(0, this.boxWidth - title.length - 2);
|
|
345
|
-
console.log("");
|
|
346
|
-
console.log(
|
|
347
|
-
this.c(C.bold + C.brightCyan, "\u250C\u2500") + this.c(C.bold + C.brightCyan, title) + this.c(C.dim, "\u2500".repeat(lineLen))
|
|
348
|
-
);
|
|
349
|
-
this.printBoxLine("Input: ", input);
|
|
350
|
-
console.log(this.pipe());
|
|
351
|
-
}
|
|
352
|
-
toolCall(toolName, args) {
|
|
353
|
-
if (!this.shouldLog("debug")) return;
|
|
354
|
-
const argsStr = JSON.stringify(args, null, 2);
|
|
355
|
-
console.log(
|
|
356
|
-
`${this.pipe()} ${this.c(C.brightMagenta, "\u26A1")} ${this.c(C.magenta, toolName)}`
|
|
357
|
-
);
|
|
358
|
-
if (argsStr !== "{}" && argsStr !== "[]") {
|
|
359
|
-
const truncated = argsStr.length > 200 ? argsStr.slice(0, 200) + "\u2026" : argsStr;
|
|
360
|
-
for (const line of truncated.split("\n")) {
|
|
361
|
-
console.log(`${this.pipe()} ${this.c(C.dim, line)}`);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
toolResult(toolName, result) {
|
|
366
|
-
if (!this.shouldLog("debug")) return;
|
|
367
|
-
const truncated = result.length > 300 ? result.slice(0, 300) + "\u2026" : result;
|
|
368
|
-
console.log(
|
|
369
|
-
`${this.pipe()} ${this.c(C.green, "\u2713")} ${this.c(C.dim, toolName + " \u2192")}`
|
|
370
|
-
);
|
|
371
|
-
for (const line of truncated.split("\n")) {
|
|
372
|
-
console.log(`${this.pipe()} ${this.c(C.gray, line)}`);
|
|
373
|
-
}
|
|
374
|
-
console.log(this.pipe());
|
|
375
|
-
}
|
|
376
|
-
agentEnd(agentName, output, usage, durationMs) {
|
|
377
|
-
if (!this.shouldLog("info")) return;
|
|
378
|
-
console.log(this.pipe());
|
|
379
|
-
this.printBoxLine("Output: ", output);
|
|
380
|
-
console.log(this.pipe());
|
|
381
|
-
const tokensLine = this.c(C.dim, "Tokens: ") + this.c(C.brightGreen, `\u2191 ${usage.promptTokens}`) + this.c(C.dim, " ") + this.c(C.brightGreen, `\u2193 ${usage.completionTokens}`) + this.c(C.dim, " ") + this.c(C.bold + C.brightGreen, `\u03A3 ${usage.totalTokens}`);
|
|
382
|
-
const duration = this.c(C.dim, "Duration: ") + this.c(C.yellow, this.formatDuration(durationMs));
|
|
383
|
-
console.log(`${this.pipe()} ${tokensLine}`);
|
|
384
|
-
console.log(`${this.pipe()} ${duration}`);
|
|
385
|
-
console.log(
|
|
386
|
-
this.c(C.bold + C.brightCyan, "\u2514") + this.c(C.dim, "\u2500".repeat(this.boxWidth - 1))
|
|
387
|
-
);
|
|
388
|
-
}
|
|
389
|
-
separator() {
|
|
390
|
-
if (!this.shouldLog("info")) return;
|
|
391
|
-
console.log(this.c(C.dim, "\u2500".repeat(this.boxWidth)));
|
|
392
|
-
}
|
|
393
|
-
formatDuration(ms) {
|
|
394
|
-
if (ms < 1e3) return `${ms}ms`;
|
|
395
|
-
const secs = (ms / 1e3).toFixed(1);
|
|
396
|
-
return `${secs}s`;
|
|
397
|
-
}
|
|
398
|
-
};
|
|
399
|
-
|
|
400
|
-
// src/agent/llm-loop.ts
|
|
401
|
-
import { createRequire as createRequire2 } from "module";
|
|
402
|
-
|
|
403
|
-
// src/models/types.ts
|
|
404
|
-
function getTextContent(content) {
|
|
405
|
-
if (content === null) return "";
|
|
406
|
-
if (typeof content === "string") return content;
|
|
407
|
-
return content.filter((p) => p.type === "text").map((p) => p.text).join("");
|
|
408
|
-
}
|
|
409
|
-
function isMultiModal(content) {
|
|
410
|
-
return Array.isArray(content);
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// src/agent/llm-loop.ts
|
|
414
|
-
var _require2 = createRequire2(import.meta.url);
|
|
415
|
-
var LLMLoop = class {
|
|
416
|
-
provider;
|
|
417
|
-
toolExecutor;
|
|
418
|
-
maxToolRoundtrips;
|
|
419
|
-
temperature;
|
|
420
|
-
maxTokens;
|
|
421
|
-
structuredOutput;
|
|
422
|
-
logger;
|
|
423
|
-
constructor(provider, toolExecutor, options) {
|
|
424
|
-
this.provider = provider;
|
|
425
|
-
this.toolExecutor = toolExecutor;
|
|
426
|
-
this.maxToolRoundtrips = options.maxToolRoundtrips;
|
|
427
|
-
this.temperature = options.temperature;
|
|
428
|
-
this.maxTokens = options.maxTokens;
|
|
429
|
-
this.structuredOutput = options.structuredOutput;
|
|
430
|
-
this.logger = options.logger;
|
|
431
|
-
}
|
|
432
|
-
async run(messages, ctx, apiKey) {
|
|
433
|
-
const allToolCalls = [];
|
|
434
|
-
let totalPromptTokens = 0;
|
|
435
|
-
let totalCompletionTokens = 0;
|
|
436
|
-
const currentMessages = [...messages];
|
|
437
|
-
const toolDefs = this.toolExecutor?.getToolDefinitions() ?? [];
|
|
438
|
-
for (let roundtrip = 0; roundtrip <= this.maxToolRoundtrips; roundtrip++) {
|
|
439
|
-
const modelConfig = {};
|
|
440
|
-
if (apiKey) modelConfig.apiKey = apiKey;
|
|
441
|
-
if (this.temperature !== void 0)
|
|
442
|
-
modelConfig.temperature = this.temperature;
|
|
443
|
-
if (this.maxTokens !== void 0) modelConfig.maxTokens = this.maxTokens;
|
|
444
|
-
if (toolDefs.length > 0) modelConfig.tools = toolDefs;
|
|
445
|
-
if (this.structuredOutput) {
|
|
446
|
-
modelConfig.responseFormat = {
|
|
447
|
-
type: "json_schema",
|
|
448
|
-
schema: this.zodToJsonSchema(this.structuredOutput),
|
|
449
|
-
name: "structured_response"
|
|
450
|
-
};
|
|
451
|
-
}
|
|
452
|
-
const response = await this.provider.generate(
|
|
453
|
-
currentMessages,
|
|
454
|
-
modelConfig
|
|
455
|
-
);
|
|
456
|
-
totalPromptTokens += response.usage.promptTokens;
|
|
457
|
-
totalCompletionTokens += response.usage.completionTokens;
|
|
458
|
-
currentMessages.push(response.message);
|
|
459
|
-
if (response.finishReason !== "tool_calls" || !response.message.toolCalls?.length || !this.toolExecutor) {
|
|
460
|
-
const text2 = getTextContent(response.message.content);
|
|
461
|
-
const output = {
|
|
462
|
-
text: text2,
|
|
463
|
-
toolCalls: allToolCalls,
|
|
464
|
-
usage: {
|
|
465
|
-
promptTokens: totalPromptTokens,
|
|
466
|
-
completionTokens: totalCompletionTokens,
|
|
467
|
-
totalTokens: totalPromptTokens + totalCompletionTokens
|
|
468
|
-
}
|
|
469
|
-
};
|
|
470
|
-
if (this.structuredOutput && text2) {
|
|
471
|
-
try {
|
|
472
|
-
const jsonStr = this.extractJson(text2);
|
|
473
|
-
const parsed = JSON.parse(jsonStr);
|
|
474
|
-
output.structured = this.structuredOutput.parse(parsed);
|
|
475
|
-
} catch {
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
return output;
|
|
479
|
-
}
|
|
480
|
-
const toolResults = await this.toolExecutor.executeAll(
|
|
481
|
-
response.message.toolCalls,
|
|
482
|
-
ctx
|
|
483
|
-
);
|
|
484
|
-
allToolCalls.push(...toolResults);
|
|
485
|
-
for (const result of toolResults) {
|
|
486
|
-
const content = typeof result.result === "string" ? result.result : result.result.content;
|
|
487
|
-
this.logger?.toolCall(result.toolName, {});
|
|
488
|
-
this.logger?.toolResult(result.toolName, typeof content === "string" ? content : JSON.stringify(content));
|
|
489
|
-
currentMessages.push({
|
|
490
|
-
role: "tool",
|
|
491
|
-
content,
|
|
492
|
-
toolCallId: result.toolCallId,
|
|
493
|
-
name: result.toolName
|
|
494
|
-
});
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
const lastAssistantMsg = currentMessages.reverse().find((m) => m.role === "assistant");
|
|
498
|
-
const text = getTextContent(lastAssistantMsg?.content ?? null);
|
|
499
|
-
return {
|
|
500
|
-
text,
|
|
501
|
-
toolCalls: allToolCalls,
|
|
502
|
-
usage: {
|
|
503
|
-
promptTokens: totalPromptTokens,
|
|
504
|
-
completionTokens: totalCompletionTokens,
|
|
505
|
-
totalTokens: totalPromptTokens + totalCompletionTokens
|
|
506
|
-
}
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
async *stream(messages, ctx, apiKey) {
|
|
510
|
-
const currentMessages = [...messages];
|
|
511
|
-
const toolDefs = this.toolExecutor?.getToolDefinitions() ?? [];
|
|
512
|
-
for (let roundtrip = 0; roundtrip <= this.maxToolRoundtrips; roundtrip++) {
|
|
513
|
-
const modelConfig = {};
|
|
514
|
-
if (apiKey) modelConfig.apiKey = apiKey;
|
|
515
|
-
if (this.temperature !== void 0)
|
|
516
|
-
modelConfig.temperature = this.temperature;
|
|
517
|
-
if (this.maxTokens !== void 0) modelConfig.maxTokens = this.maxTokens;
|
|
518
|
-
if (toolDefs.length > 0) modelConfig.tools = toolDefs;
|
|
519
|
-
let fullText = "";
|
|
520
|
-
const pendingToolCalls = [];
|
|
521
|
-
let finishReason = "stop";
|
|
522
|
-
const streamGen = this.provider.stream(currentMessages, modelConfig);
|
|
523
|
-
for await (const chunk of streamGen) {
|
|
524
|
-
yield chunk;
|
|
525
|
-
if (chunk.type === "text") {
|
|
526
|
-
fullText += chunk.text;
|
|
527
|
-
ctx.eventBus.emit("run.stream.chunk", {
|
|
528
|
-
runId: ctx.runId,
|
|
529
|
-
chunk: chunk.text
|
|
530
|
-
});
|
|
531
|
-
} else if (chunk.type === "tool_call_start") {
|
|
532
|
-
pendingToolCalls.push({
|
|
533
|
-
id: chunk.toolCall.id,
|
|
534
|
-
name: chunk.toolCall.name,
|
|
535
|
-
args: ""
|
|
536
|
-
});
|
|
537
|
-
} else if (chunk.type === "tool_call_delta") {
|
|
538
|
-
const tc = pendingToolCalls.find(
|
|
539
|
-
(t) => t.id === chunk.toolCallId
|
|
540
|
-
);
|
|
541
|
-
if (tc) {
|
|
542
|
-
tc.args += chunk.argumentsDelta;
|
|
543
|
-
}
|
|
544
|
-
} else if (chunk.type === "finish") {
|
|
545
|
-
finishReason = chunk.finishReason;
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
if (finishReason !== "tool_calls" || pendingToolCalls.length === 0 || !this.toolExecutor) {
|
|
549
|
-
return;
|
|
550
|
-
}
|
|
551
|
-
const assistantMsg = {
|
|
552
|
-
role: "assistant",
|
|
553
|
-
content: fullText || null,
|
|
554
|
-
toolCalls: pendingToolCalls.map((tc) => ({
|
|
555
|
-
id: tc.id,
|
|
556
|
-
name: tc.name,
|
|
557
|
-
arguments: JSON.parse(tc.args || "{}")
|
|
558
|
-
}))
|
|
559
|
-
};
|
|
560
|
-
currentMessages.push(assistantMsg);
|
|
561
|
-
const toolResults = await this.toolExecutor.executeAll(
|
|
562
|
-
assistantMsg.toolCalls,
|
|
563
|
-
ctx
|
|
564
|
-
);
|
|
565
|
-
for (const result of toolResults) {
|
|
566
|
-
const content = typeof result.result === "string" ? result.result : result.result.content;
|
|
567
|
-
currentMessages.push({
|
|
568
|
-
role: "tool",
|
|
569
|
-
content,
|
|
570
|
-
toolCallId: result.toolCallId,
|
|
571
|
-
name: result.toolName
|
|
572
|
-
});
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
extractJson(text) {
|
|
577
|
-
const fenceMatch = text.match(/```(?:json)?\s*\n?([\s\S]*?)```/);
|
|
578
|
-
if (fenceMatch) return fenceMatch[1].trim();
|
|
579
|
-
const braceStart = text.indexOf("{");
|
|
580
|
-
const braceEnd = text.lastIndexOf("}");
|
|
581
|
-
if (braceStart !== -1 && braceEnd > braceStart) {
|
|
582
|
-
return text.slice(braceStart, braceEnd + 1);
|
|
583
|
-
}
|
|
584
|
-
return text.trim();
|
|
585
|
-
}
|
|
586
|
-
zodToJsonSchema(schema) {
|
|
587
|
-
try {
|
|
588
|
-
const { zodToJsonSchema } = _require2("zod-to-json-schema");
|
|
589
|
-
return zodToJsonSchema(schema, { target: "openApi3" });
|
|
590
|
-
} catch {
|
|
591
|
-
return {};
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
};
|
|
595
|
-
|
|
596
|
-
// src/agent/run-context.ts
|
|
597
|
-
import { v4 as uuidv4 } from "uuid";
|
|
598
|
-
var RunContext = class {
|
|
599
|
-
runId;
|
|
600
|
-
sessionId;
|
|
601
|
-
userId;
|
|
602
|
-
metadata;
|
|
603
|
-
eventBus;
|
|
604
|
-
sessionState;
|
|
605
|
-
constructor(opts) {
|
|
606
|
-
this.runId = opts.runId ?? uuidv4();
|
|
607
|
-
this.sessionId = opts.sessionId;
|
|
608
|
-
this.userId = opts.userId;
|
|
609
|
-
this.metadata = opts.metadata ?? {};
|
|
610
|
-
this.eventBus = opts.eventBus;
|
|
611
|
-
this.sessionState = opts.sessionState ?? {};
|
|
612
|
-
}
|
|
613
|
-
getState(key) {
|
|
614
|
-
return this.sessionState[key];
|
|
615
|
-
}
|
|
616
|
-
setState(key, value) {
|
|
617
|
-
this.sessionState[key] = value;
|
|
618
|
-
}
|
|
619
|
-
};
|
|
620
|
-
|
|
621
|
-
// src/agent/agent.ts
|
|
622
|
-
var Agent = class {
|
|
623
|
-
name;
|
|
624
|
-
eventBus;
|
|
625
|
-
instructions;
|
|
626
|
-
config;
|
|
627
|
-
sessionManager;
|
|
628
|
-
llmLoop;
|
|
629
|
-
logger;
|
|
630
|
-
get tools() {
|
|
631
|
-
return this.config.tools ?? [];
|
|
632
|
-
}
|
|
633
|
-
get modelId() {
|
|
634
|
-
return this.config.model.modelId;
|
|
635
|
-
}
|
|
636
|
-
get providerId() {
|
|
637
|
-
return this.config.model.providerId;
|
|
638
|
-
}
|
|
639
|
-
get hasStructuredOutput() {
|
|
640
|
-
return !!this.config.structuredOutput;
|
|
641
|
-
}
|
|
642
|
-
constructor(config) {
|
|
643
|
-
this.config = config;
|
|
644
|
-
this.name = config.name;
|
|
645
|
-
this.instructions = config.instructions;
|
|
646
|
-
this.eventBus = config.eventBus ?? new EventBus();
|
|
647
|
-
const storage = config.storage ?? new InMemoryStorage();
|
|
648
|
-
this.sessionManager = new SessionManager(storage);
|
|
649
|
-
this.logger = new Logger({
|
|
650
|
-
level: config.logLevel ?? "silent",
|
|
651
|
-
prefix: config.name
|
|
652
|
-
});
|
|
653
|
-
const toolExecutor = config.tools && config.tools.length > 0 ? new ToolExecutor(config.tools) : null;
|
|
654
|
-
this.llmLoop = new LLMLoop(config.model, toolExecutor, {
|
|
655
|
-
maxToolRoundtrips: config.maxToolRoundtrips ?? 10,
|
|
656
|
-
temperature: config.temperature,
|
|
657
|
-
structuredOutput: config.structuredOutput,
|
|
658
|
-
logger: this.logger
|
|
659
|
-
});
|
|
660
|
-
}
|
|
661
|
-
async run(input, opts) {
|
|
662
|
-
const startTime = Date.now();
|
|
663
|
-
const sessionId = opts?.sessionId ?? this.config.sessionId ?? uuidv42();
|
|
664
|
-
const userId = opts?.userId ?? this.config.userId;
|
|
665
|
-
const inputText = typeof input === "string" ? input : getTextContent(input);
|
|
666
|
-
const session = await this.sessionManager.getOrCreate(sessionId, userId);
|
|
667
|
-
const ctx = new RunContext({
|
|
668
|
-
sessionId,
|
|
669
|
-
userId,
|
|
670
|
-
metadata: opts?.metadata ?? {},
|
|
671
|
-
eventBus: this.eventBus,
|
|
672
|
-
sessionState: { ...session.state }
|
|
673
|
-
});
|
|
674
|
-
this.logger.agentStart(this.name, inputText);
|
|
675
|
-
this.eventBus.emit("run.start", {
|
|
676
|
-
runId: ctx.runId,
|
|
677
|
-
agentName: this.name,
|
|
678
|
-
input: inputText
|
|
679
|
-
});
|
|
680
|
-
try {
|
|
681
|
-
if (this.config.hooks?.beforeRun) {
|
|
682
|
-
await this.config.hooks.beforeRun(ctx);
|
|
683
|
-
}
|
|
684
|
-
if (this.config.guardrails?.input) {
|
|
685
|
-
for (const guardrail of this.config.guardrails.input) {
|
|
686
|
-
const result = await guardrail.validate(input, ctx);
|
|
687
|
-
if (!result.pass) {
|
|
688
|
-
throw new Error(
|
|
689
|
-
`Input guardrail "${guardrail.name}" blocked: ${result.reason}`
|
|
690
|
-
);
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
const messages = await this.buildMessages(input, sessionId, ctx);
|
|
695
|
-
const output = await this.llmLoop.run(messages, ctx, opts?.apiKey);
|
|
696
|
-
output.durationMs = Date.now() - startTime;
|
|
697
|
-
if (this.config.guardrails?.output) {
|
|
698
|
-
for (const guardrail of this.config.guardrails.output) {
|
|
699
|
-
const result = await guardrail.validate(output, ctx);
|
|
700
|
-
if (!result.pass) {
|
|
701
|
-
throw new Error(
|
|
702
|
-
`Output guardrail "${guardrail.name}" blocked: ${result.reason}`
|
|
703
|
-
);
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
await this.sessionManager.appendMessages(sessionId, [
|
|
708
|
-
{ role: "user", content: inputText },
|
|
709
|
-
{ role: "assistant", content: output.text }
|
|
710
|
-
]);
|
|
711
|
-
await this.sessionManager.updateState(sessionId, ctx.sessionState);
|
|
712
|
-
if (this.config.memory) {
|
|
713
|
-
await this.config.memory.addMessages(sessionId, [
|
|
714
|
-
{ role: "user", content: inputText },
|
|
715
|
-
{ role: "assistant", content: output.text }
|
|
716
|
-
]);
|
|
717
|
-
}
|
|
718
|
-
if (this.config.hooks?.afterRun) {
|
|
719
|
-
await this.config.hooks.afterRun(ctx, output);
|
|
720
|
-
}
|
|
721
|
-
this.logger.agentEnd(this.name, output.text, output.usage, output.durationMs);
|
|
722
|
-
this.eventBus.emit("run.complete", {
|
|
723
|
-
runId: ctx.runId,
|
|
724
|
-
output
|
|
725
|
-
});
|
|
726
|
-
return output;
|
|
727
|
-
} catch (error) {
|
|
728
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
729
|
-
this.logger.error(`Run failed: ${err.message}`);
|
|
730
|
-
if (this.config.hooks?.onError) {
|
|
731
|
-
await this.config.hooks.onError(ctx, err);
|
|
732
|
-
}
|
|
733
|
-
this.eventBus.emit("run.error", {
|
|
734
|
-
runId: ctx.runId,
|
|
735
|
-
error: err
|
|
736
|
-
});
|
|
737
|
-
throw err;
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
async *stream(input, opts) {
|
|
741
|
-
const sessionId = opts?.sessionId ?? this.config.sessionId ?? uuidv42();
|
|
742
|
-
const userId = opts?.userId ?? this.config.userId;
|
|
743
|
-
const inputText = typeof input === "string" ? input : getTextContent(input);
|
|
744
|
-
const session = await this.sessionManager.getOrCreate(sessionId, userId);
|
|
745
|
-
const ctx = new RunContext({
|
|
746
|
-
sessionId,
|
|
747
|
-
userId,
|
|
748
|
-
metadata: opts?.metadata ?? {},
|
|
749
|
-
eventBus: this.eventBus,
|
|
750
|
-
sessionState: { ...session.state }
|
|
751
|
-
});
|
|
752
|
-
this.eventBus.emit("run.start", {
|
|
753
|
-
runId: ctx.runId,
|
|
754
|
-
agentName: this.name,
|
|
755
|
-
input: inputText
|
|
756
|
-
});
|
|
757
|
-
let fullText = "";
|
|
758
|
-
let streamOk = false;
|
|
759
|
-
try {
|
|
760
|
-
if (this.config.hooks?.beforeRun) {
|
|
761
|
-
await this.config.hooks.beforeRun(ctx);
|
|
762
|
-
}
|
|
763
|
-
if (this.config.guardrails?.input) {
|
|
764
|
-
for (const guardrail of this.config.guardrails.input) {
|
|
765
|
-
const result = await guardrail.validate(input, ctx);
|
|
766
|
-
if (!result.pass) {
|
|
767
|
-
throw new Error(
|
|
768
|
-
`Input guardrail "${guardrail.name}" blocked: ${result.reason}`
|
|
769
|
-
);
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
const messages = await this.buildMessages(input, sessionId, ctx);
|
|
774
|
-
for await (const chunk of this.llmLoop.stream(messages, ctx, opts?.apiKey)) {
|
|
775
|
-
if (chunk.type === "text") {
|
|
776
|
-
fullText += chunk.text;
|
|
777
|
-
}
|
|
778
|
-
yield chunk;
|
|
779
|
-
}
|
|
780
|
-
streamOk = true;
|
|
781
|
-
} catch (error) {
|
|
782
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
783
|
-
if (this.config.hooks?.onError) {
|
|
784
|
-
await this.config.hooks.onError(ctx, err);
|
|
785
|
-
}
|
|
786
|
-
this.eventBus.emit("run.error", {
|
|
787
|
-
runId: ctx.runId,
|
|
788
|
-
error: err
|
|
789
|
-
});
|
|
790
|
-
throw err;
|
|
791
|
-
} finally {
|
|
792
|
-
if (streamOk) {
|
|
793
|
-
await this.sessionManager.appendMessages(sessionId, [
|
|
794
|
-
{ role: "user", content: inputText },
|
|
795
|
-
{ role: "assistant", content: fullText }
|
|
796
|
-
]);
|
|
797
|
-
await this.sessionManager.updateState(sessionId, ctx.sessionState);
|
|
798
|
-
if (this.config.memory) {
|
|
799
|
-
await this.config.memory.addMessages(sessionId, [
|
|
800
|
-
{ role: "user", content: inputText },
|
|
801
|
-
{ role: "assistant", content: fullText }
|
|
802
|
-
]);
|
|
803
|
-
}
|
|
804
|
-
this.eventBus.emit("run.complete", {
|
|
805
|
-
runId: ctx.runId,
|
|
806
|
-
output: {
|
|
807
|
-
text: fullText,
|
|
808
|
-
toolCalls: [],
|
|
809
|
-
usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 }
|
|
810
|
-
}
|
|
811
|
-
});
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
async buildMessages(input, sessionId, ctx) {
|
|
816
|
-
const messages = [];
|
|
817
|
-
let systemContent = "";
|
|
818
|
-
if (this.config.instructions) {
|
|
819
|
-
systemContent = typeof this.config.instructions === "function" ? this.config.instructions(ctx) : this.config.instructions;
|
|
820
|
-
}
|
|
821
|
-
if (this.config.memory) {
|
|
822
|
-
const memoryContext = await this.config.memory.getContextString(
|
|
823
|
-
sessionId
|
|
824
|
-
);
|
|
825
|
-
if (memoryContext) {
|
|
826
|
-
systemContent = systemContent ? `${systemContent}
|
|
827
|
-
|
|
828
|
-
${memoryContext}` : memoryContext;
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
if (systemContent) {
|
|
832
|
-
messages.push({ role: "system", content: systemContent });
|
|
833
|
-
}
|
|
834
|
-
if (this.config.addHistoryToMessages !== false) {
|
|
835
|
-
const limit = this.config.numHistoryRuns ? this.config.numHistoryRuns * 2 : 20;
|
|
836
|
-
const history = await this.sessionManager.getHistory(sessionId, limit);
|
|
837
|
-
if (history.length > 0) {
|
|
838
|
-
this.logger.info(`Loaded ${history.length} history messages for session ${sessionId}`);
|
|
839
|
-
if (messages.length > 0 && messages[0].role === "system") {
|
|
840
|
-
messages[0] = {
|
|
841
|
-
...messages[0],
|
|
842
|
-
content: `${getTextContent(messages[0].content)}
|
|
843
|
-
|
|
844
|
-
This is a multi-turn conversation. The previous messages in this session are included below. Use them to maintain context and answer questions about prior exchanges.`
|
|
845
|
-
};
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
messages.push(...history);
|
|
849
|
-
}
|
|
850
|
-
messages.push({ role: "user", content: input });
|
|
851
|
-
this.logger.info(`Sending ${messages.length} messages to LLM: ${messages.map((m) => `[${m.role}: ${typeof m.content === "string" ? m.content.slice(0, 40) : "(multimodal)"}]`).join(", ")}`);
|
|
852
|
-
return messages;
|
|
853
|
-
}
|
|
854
|
-
};
|
|
855
|
-
|
|
856
|
-
// src/team/team.ts
|
|
857
|
-
import { v4 as uuidv43 } from "uuid";
|
|
858
|
-
|
|
859
|
-
// src/team/types.ts
|
|
860
|
-
var TeamMode = /* @__PURE__ */ ((TeamMode2) => {
|
|
861
|
-
TeamMode2["Coordinate"] = "coordinate";
|
|
862
|
-
TeamMode2["Route"] = "route";
|
|
863
|
-
TeamMode2["Broadcast"] = "broadcast";
|
|
864
|
-
TeamMode2["Collaborate"] = "collaborate";
|
|
865
|
-
return TeamMode2;
|
|
866
|
-
})(TeamMode || {});
|
|
867
|
-
|
|
868
|
-
// src/team/team.ts
|
|
869
|
-
var Team = class {
|
|
870
|
-
name;
|
|
871
|
-
eventBus;
|
|
872
|
-
config;
|
|
873
|
-
constructor(config) {
|
|
874
|
-
this.config = config;
|
|
875
|
-
this.name = config.name;
|
|
876
|
-
this.eventBus = config.eventBus ?? new EventBus();
|
|
877
|
-
}
|
|
878
|
-
async run(input, opts) {
|
|
879
|
-
const ctx = new RunContext({
|
|
880
|
-
sessionId: opts?.sessionId ?? uuidv43(),
|
|
881
|
-
userId: opts?.userId,
|
|
882
|
-
metadata: opts?.metadata ?? {},
|
|
883
|
-
eventBus: this.eventBus,
|
|
884
|
-
sessionState: { ...this.config.sessionState ?? {} }
|
|
885
|
-
});
|
|
886
|
-
this.eventBus.emit("run.start", {
|
|
887
|
-
runId: ctx.runId,
|
|
888
|
-
agentName: this.name,
|
|
889
|
-
input
|
|
890
|
-
});
|
|
891
|
-
try {
|
|
892
|
-
let output;
|
|
893
|
-
switch (this.config.mode) {
|
|
894
|
-
case "route" /* Route */:
|
|
895
|
-
output = await this.runRouteMode(input, ctx);
|
|
896
|
-
break;
|
|
897
|
-
case "broadcast" /* Broadcast */:
|
|
898
|
-
output = await this.runBroadcastMode(input, ctx);
|
|
899
|
-
break;
|
|
900
|
-
case "collaborate" /* Collaborate */:
|
|
901
|
-
output = await this.runCollaborateMode(input, ctx);
|
|
902
|
-
break;
|
|
903
|
-
case "coordinate" /* Coordinate */:
|
|
904
|
-
default:
|
|
905
|
-
output = await this.runCoordinateMode(input, ctx);
|
|
906
|
-
break;
|
|
907
|
-
}
|
|
908
|
-
this.eventBus.emit("run.complete", { runId: ctx.runId, output });
|
|
909
|
-
return output;
|
|
910
|
-
} catch (error) {
|
|
911
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
912
|
-
this.eventBus.emit("run.error", { runId: ctx.runId, error: err });
|
|
913
|
-
throw err;
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
async *stream(input, opts) {
|
|
917
|
-
const result = await this.run(input, opts);
|
|
918
|
-
yield { type: "text", text: result.text };
|
|
919
|
-
yield { type: "finish", finishReason: "stop", usage: result.usage };
|
|
920
|
-
}
|
|
921
|
-
async runCoordinateMode(input, ctx) {
|
|
922
|
-
const memberDescriptions = this.buildMemberDescriptions();
|
|
923
|
-
const planPrompt = this.buildCoordinatorPrompt(
|
|
924
|
-
input,
|
|
925
|
-
memberDescriptions,
|
|
926
|
-
"coordinate"
|
|
927
|
-
);
|
|
928
|
-
const planResponse = await this.config.model.generate([
|
|
929
|
-
{ role: "system", content: planPrompt },
|
|
930
|
-
{ role: "user", content: input }
|
|
931
|
-
]);
|
|
932
|
-
const delegations = this.parseDelegationPlan(
|
|
933
|
-
getTextContent(planResponse.message.content)
|
|
934
|
-
);
|
|
935
|
-
const memberOutputs = [];
|
|
936
|
-
for (const delegation of delegations) {
|
|
937
|
-
const member = this.findMember(delegation.memberId);
|
|
938
|
-
if (!member) continue;
|
|
939
|
-
this.eventBus.emit("team.delegate", {
|
|
940
|
-
runId: ctx.runId,
|
|
941
|
-
memberId: delegation.memberId,
|
|
942
|
-
task: delegation.task
|
|
943
|
-
});
|
|
944
|
-
const output = await member.run(delegation.task, {
|
|
945
|
-
sessionId: ctx.sessionId
|
|
946
|
-
});
|
|
947
|
-
memberOutputs.push({ memberId: delegation.memberId, output });
|
|
948
|
-
}
|
|
949
|
-
const synthesisPrompt = this.buildSynthesisPrompt(
|
|
950
|
-
input,
|
|
951
|
-
memberOutputs
|
|
952
|
-
);
|
|
953
|
-
const synthesisResponse = await this.config.model.generate([
|
|
954
|
-
{ role: "user", content: synthesisPrompt }
|
|
955
|
-
]);
|
|
956
|
-
return {
|
|
957
|
-
text: getTextContent(synthesisResponse.message.content),
|
|
958
|
-
toolCalls: memberOutputs.flatMap((o) => o.output.toolCalls),
|
|
959
|
-
usage: synthesisResponse.usage
|
|
960
|
-
};
|
|
961
|
-
}
|
|
962
|
-
async runRouteMode(input, ctx) {
|
|
963
|
-
const memberDescriptions = this.buildMemberDescriptions();
|
|
964
|
-
const routePrompt = this.buildCoordinatorPrompt(
|
|
965
|
-
input,
|
|
966
|
-
memberDescriptions,
|
|
967
|
-
"route"
|
|
968
|
-
);
|
|
969
|
-
const routeResponse = await this.config.model.generate([
|
|
970
|
-
{ role: "system", content: routePrompt },
|
|
971
|
-
{ role: "user", content: input }
|
|
972
|
-
]);
|
|
973
|
-
const selectedName = getTextContent(routeResponse.message.content).trim();
|
|
974
|
-
const member = this.findMember(selectedName);
|
|
975
|
-
if (!member) {
|
|
976
|
-
return {
|
|
977
|
-
text: `Could not route to member "${selectedName}". Available: ${this.config.members.map((m) => m.name).join(", ")}`,
|
|
978
|
-
toolCalls: [],
|
|
979
|
-
usage: routeResponse.usage
|
|
980
|
-
};
|
|
981
|
-
}
|
|
982
|
-
this.eventBus.emit("team.delegate", {
|
|
983
|
-
runId: ctx.runId,
|
|
984
|
-
memberId: member.name,
|
|
985
|
-
task: input
|
|
986
|
-
});
|
|
987
|
-
return member.run(input, { sessionId: ctx.sessionId });
|
|
988
|
-
}
|
|
989
|
-
async runBroadcastMode(input, ctx) {
|
|
990
|
-
for (const member of this.config.members) {
|
|
991
|
-
this.eventBus.emit("team.delegate", {
|
|
992
|
-
runId: ctx.runId,
|
|
993
|
-
memberId: member.name,
|
|
994
|
-
task: input
|
|
995
|
-
});
|
|
996
|
-
}
|
|
997
|
-
const outputs = await Promise.all(
|
|
998
|
-
this.config.members.map(
|
|
999
|
-
(member) => member.run(input, { sessionId: ctx.sessionId })
|
|
1000
|
-
)
|
|
1001
|
-
);
|
|
1002
|
-
const memberOutputs = this.config.members.map((member, i) => ({
|
|
1003
|
-
memberId: member.name,
|
|
1004
|
-
output: outputs[i]
|
|
1005
|
-
}));
|
|
1006
|
-
const synthesisPrompt = this.buildSynthesisPrompt(input, memberOutputs);
|
|
1007
|
-
const synthesisResponse = await this.config.model.generate([
|
|
1008
|
-
{ role: "user", content: synthesisPrompt }
|
|
1009
|
-
]);
|
|
1010
|
-
return {
|
|
1011
|
-
text: getTextContent(synthesisResponse.message.content),
|
|
1012
|
-
toolCalls: outputs.flatMap((o) => o.toolCalls),
|
|
1013
|
-
usage: synthesisResponse.usage
|
|
1014
|
-
};
|
|
1015
|
-
}
|
|
1016
|
-
async runCollaborateMode(input, ctx) {
|
|
1017
|
-
const maxRounds = this.config.maxRounds ?? 3;
|
|
1018
|
-
let currentInput = input;
|
|
1019
|
-
let finalOutput = null;
|
|
1020
|
-
for (let round = 0; round < maxRounds; round++) {
|
|
1021
|
-
for (const member of this.config.members) {
|
|
1022
|
-
this.eventBus.emit("team.delegate", {
|
|
1023
|
-
runId: ctx.runId,
|
|
1024
|
-
memberId: member.name,
|
|
1025
|
-
task: currentInput
|
|
1026
|
-
});
|
|
1027
|
-
}
|
|
1028
|
-
const outputs = await Promise.all(
|
|
1029
|
-
this.config.members.map(
|
|
1030
|
-
(member) => member.run(currentInput, { sessionId: ctx.sessionId })
|
|
1031
|
-
)
|
|
1032
|
-
);
|
|
1033
|
-
const memberOutputs = this.config.members.map((member, i) => ({
|
|
1034
|
-
memberId: member.name,
|
|
1035
|
-
output: outputs[i]
|
|
1036
|
-
}));
|
|
1037
|
-
const consensusPrompt = `Given the following responses to "${input}", determine if there is consensus. If yes, synthesize a final answer. If not, provide a follow-up question.
|
|
1038
|
-
|
|
1039
|
-
${memberOutputs.map((o) => `${o.memberId}: ${o.output.text}`).join("\n\n")}
|
|
1040
|
-
|
|
1041
|
-
Respond with either "CONSENSUS: <final answer>" or "FOLLOW_UP: <question>"`;
|
|
1042
|
-
const consensusResponse = await this.config.model.generate([
|
|
1043
|
-
{ role: "user", content: consensusPrompt }
|
|
1044
|
-
]);
|
|
1045
|
-
const responseText = getTextContent(consensusResponse.message.content);
|
|
1046
|
-
if (responseText.startsWith("CONSENSUS:")) {
|
|
1047
|
-
finalOutput = {
|
|
1048
|
-
text: responseText.slice("CONSENSUS:".length).trim(),
|
|
1049
|
-
toolCalls: outputs.flatMap((o) => o.toolCalls),
|
|
1050
|
-
usage: consensusResponse.usage
|
|
1051
|
-
};
|
|
1052
|
-
break;
|
|
1053
|
-
}
|
|
1054
|
-
currentInput = responseText.startsWith("FOLLOW_UP:") ? responseText.slice("FOLLOW_UP:".length).trim() : responseText;
|
|
1055
|
-
}
|
|
1056
|
-
if (!finalOutput) {
|
|
1057
|
-
const lastSynthesis = this.buildSynthesisPrompt(input, []);
|
|
1058
|
-
const response = await this.config.model.generate([
|
|
1059
|
-
{ role: "user", content: lastSynthesis }
|
|
1060
|
-
]);
|
|
1061
|
-
finalOutput = {
|
|
1062
|
-
text: getTextContent(response.message.content),
|
|
1063
|
-
toolCalls: [],
|
|
1064
|
-
usage: response.usage
|
|
1065
|
-
};
|
|
1066
|
-
}
|
|
1067
|
-
return finalOutput;
|
|
1068
|
-
}
|
|
1069
|
-
buildMemberDescriptions() {
|
|
1070
|
-
return this.config.members.map((member) => {
|
|
1071
|
-
const desc = typeof member.instructions === "function" ? "(dynamic instructions)" : member.instructions ?? "General-purpose agent";
|
|
1072
|
-
return `- ${member.name}: ${desc}`;
|
|
1073
|
-
}).join("\n");
|
|
1074
|
-
}
|
|
1075
|
-
buildCoordinatorPrompt(input, memberDescriptions, mode) {
|
|
1076
|
-
if (mode === "route") {
|
|
1077
|
-
return `You are a team coordinator. Based on the user's request, select the single most appropriate team member to handle it. Available members:
|
|
1078
|
-
${memberDescriptions}
|
|
1079
|
-
|
|
1080
|
-
Respond with ONLY the member name, nothing else.`;
|
|
1081
|
-
}
|
|
1082
|
-
return `You are a team coordinator. Break down the user's request into subtasks and delegate to appropriate team members. Available members:
|
|
1083
|
-
${memberDescriptions}
|
|
1084
|
-
|
|
1085
|
-
Respond with a JSON array of delegations: [{"memberId": "name", "task": "specific task"}]
|
|
1086
|
-
${this.config.instructions ? `
|
|
1087
|
-
Additional instructions: ${this.config.instructions}` : ""}`;
|
|
1088
|
-
}
|
|
1089
|
-
buildSynthesisPrompt(originalInput, memberOutputs) {
|
|
1090
|
-
const outputsText = memberOutputs.map((o) => `### ${o.memberId}
|
|
1091
|
-
${o.output.text}`).join("\n\n");
|
|
1092
|
-
return `Original request: ${originalInput}
|
|
1093
|
-
|
|
1094
|
-
Team member responses:
|
|
1095
|
-
${outputsText}
|
|
1096
|
-
|
|
1097
|
-
Synthesize these responses into a single coherent answer.`;
|
|
1098
|
-
}
|
|
1099
|
-
parseDelegationPlan(content) {
|
|
1100
|
-
try {
|
|
1101
|
-
const jsonMatch = content.match(/\[[\s\S]*\]/);
|
|
1102
|
-
if (jsonMatch) {
|
|
1103
|
-
return JSON.parse(jsonMatch[0]);
|
|
1104
|
-
}
|
|
1105
|
-
} catch {
|
|
1106
|
-
}
|
|
1107
|
-
return this.config.members.map((m) => ({
|
|
1108
|
-
memberId: m.name,
|
|
1109
|
-
task: content
|
|
1110
|
-
}));
|
|
1111
|
-
}
|
|
1112
|
-
findMember(name) {
|
|
1113
|
-
return this.config.members.find(
|
|
1114
|
-
(m) => m.name.toLowerCase() === name.toLowerCase()
|
|
1115
|
-
);
|
|
1116
|
-
}
|
|
1117
|
-
};
|
|
1118
|
-
|
|
1119
|
-
// src/workflow/workflow.ts
|
|
1120
|
-
import { v4 as uuidv44 } from "uuid";
|
|
1121
|
-
|
|
1122
|
-
// src/workflow/step-runner.ts
|
|
1123
|
-
function isAgentStep(step) {
|
|
1124
|
-
return "agent" in step;
|
|
1125
|
-
}
|
|
1126
|
-
function isFunctionStep(step) {
|
|
1127
|
-
return "run" in step;
|
|
1128
|
-
}
|
|
1129
|
-
function isConditionStep(step) {
|
|
1130
|
-
return "condition" in step;
|
|
1131
|
-
}
|
|
1132
|
-
function isParallelStep(step) {
|
|
1133
|
-
return "parallel" in step;
|
|
1134
|
-
}
|
|
1135
|
-
var StepRunner = class {
|
|
1136
|
-
retryPolicy;
|
|
1137
|
-
constructor(retryPolicy) {
|
|
1138
|
-
this.retryPolicy = retryPolicy;
|
|
1139
|
-
}
|
|
1140
|
-
async executeSteps(steps, state, ctx) {
|
|
1141
|
-
let currentState = { ...state };
|
|
1142
|
-
const allResults = [];
|
|
1143
|
-
for (const step of steps) {
|
|
1144
|
-
const { state: newState, results } = await this.executeStep(
|
|
1145
|
-
step,
|
|
1146
|
-
currentState,
|
|
1147
|
-
ctx
|
|
1148
|
-
);
|
|
1149
|
-
currentState = newState;
|
|
1150
|
-
allResults.push(...results);
|
|
1151
|
-
}
|
|
1152
|
-
return { state: currentState, results: allResults };
|
|
1153
|
-
}
|
|
1154
|
-
async executeStep(step, state, ctx) {
|
|
1155
|
-
if (isConditionStep(step)) {
|
|
1156
|
-
return this.executeConditionStep(step, state, ctx);
|
|
1157
|
-
}
|
|
1158
|
-
if (isParallelStep(step)) {
|
|
1159
|
-
return this.executeParallelStep(step, state, ctx);
|
|
1160
|
-
}
|
|
1161
|
-
if (isAgentStep(step)) {
|
|
1162
|
-
return this.executeAgentStep(step, state, ctx);
|
|
1163
|
-
}
|
|
1164
|
-
if (isFunctionStep(step)) {
|
|
1165
|
-
return this.executeFunctionStep(step, state, ctx);
|
|
1166
|
-
}
|
|
1167
|
-
return { state, results: [] };
|
|
1168
|
-
}
|
|
1169
|
-
async executeAgentStep(step, state, ctx) {
|
|
1170
|
-
const startTime = Date.now();
|
|
1171
|
-
ctx.eventBus.emit("workflow.step", {
|
|
1172
|
-
runId: ctx.runId,
|
|
1173
|
-
stepName: step.name,
|
|
1174
|
-
status: "start"
|
|
1175
|
-
});
|
|
1176
|
-
const execute = async () => {
|
|
1177
|
-
const input = step.inputFrom ? step.inputFrom(state) : JSON.stringify(state);
|
|
1178
|
-
const output = await step.agent.run(input, {
|
|
1179
|
-
sessionId: ctx.sessionId
|
|
1180
|
-
});
|
|
1181
|
-
const newState = {
|
|
1182
|
-
...state,
|
|
1183
|
-
[`${step.name}_output`]: output.text
|
|
1184
|
-
};
|
|
1185
|
-
Object.assign(state, newState);
|
|
1186
|
-
return {
|
|
1187
|
-
stepName: step.name,
|
|
1188
|
-
status: "done",
|
|
1189
|
-
durationMs: Date.now() - startTime
|
|
1190
|
-
};
|
|
1191
|
-
};
|
|
1192
|
-
const result = await this.withRetry(step.name, execute, ctx);
|
|
1193
|
-
return { state, results: [result] };
|
|
1194
|
-
}
|
|
1195
|
-
async executeFunctionStep(step, state, ctx) {
|
|
1196
|
-
const startTime = Date.now();
|
|
1197
|
-
ctx.eventBus.emit("workflow.step", {
|
|
1198
|
-
runId: ctx.runId,
|
|
1199
|
-
stepName: step.name,
|
|
1200
|
-
status: "start"
|
|
1201
|
-
});
|
|
1202
|
-
const execute = async () => {
|
|
1203
|
-
const patch = await step.run(state, ctx);
|
|
1204
|
-
Object.assign(state, patch);
|
|
1205
|
-
return {
|
|
1206
|
-
stepName: step.name,
|
|
1207
|
-
status: "done",
|
|
1208
|
-
durationMs: Date.now() - startTime
|
|
1209
|
-
};
|
|
1210
|
-
};
|
|
1211
|
-
const result = await this.withRetry(step.name, execute, ctx);
|
|
1212
|
-
return { state, results: [result] };
|
|
1213
|
-
}
|
|
1214
|
-
async executeConditionStep(step, state, ctx) {
|
|
1215
|
-
const startTime = Date.now();
|
|
1216
|
-
ctx.eventBus.emit("workflow.step", {
|
|
1217
|
-
runId: ctx.runId,
|
|
1218
|
-
stepName: step.name,
|
|
1219
|
-
status: "start"
|
|
1220
|
-
});
|
|
1221
|
-
if (step.condition(state)) {
|
|
1222
|
-
const { state: newState, results } = await this.executeSteps(
|
|
1223
|
-
step.steps,
|
|
1224
|
-
state,
|
|
1225
|
-
ctx
|
|
1226
|
-
);
|
|
1227
|
-
ctx.eventBus.emit("workflow.step", {
|
|
1228
|
-
runId: ctx.runId,
|
|
1229
|
-
stepName: step.name,
|
|
1230
|
-
status: "done"
|
|
1231
|
-
});
|
|
1232
|
-
return {
|
|
1233
|
-
state: newState,
|
|
1234
|
-
results: [
|
|
1235
|
-
{
|
|
1236
|
-
stepName: step.name,
|
|
1237
|
-
status: "done",
|
|
1238
|
-
durationMs: Date.now() - startTime
|
|
1239
|
-
},
|
|
1240
|
-
...results
|
|
1241
|
-
]
|
|
1242
|
-
};
|
|
1243
|
-
}
|
|
1244
|
-
ctx.eventBus.emit("workflow.step", {
|
|
1245
|
-
runId: ctx.runId,
|
|
1246
|
-
stepName: step.name,
|
|
1247
|
-
status: "done"
|
|
1248
|
-
});
|
|
1249
|
-
return {
|
|
1250
|
-
state,
|
|
1251
|
-
results: [
|
|
1252
|
-
{
|
|
1253
|
-
stepName: step.name,
|
|
1254
|
-
status: "skipped",
|
|
1255
|
-
durationMs: Date.now() - startTime
|
|
1256
|
-
}
|
|
1257
|
-
]
|
|
1258
|
-
};
|
|
1259
|
-
}
|
|
1260
|
-
async executeParallelStep(step, state, ctx) {
|
|
1261
|
-
const startTime = Date.now();
|
|
1262
|
-
ctx.eventBus.emit("workflow.step", {
|
|
1263
|
-
runId: ctx.runId,
|
|
1264
|
-
stepName: step.name,
|
|
1265
|
-
status: "start"
|
|
1266
|
-
});
|
|
1267
|
-
const settled = await Promise.allSettled(
|
|
1268
|
-
step.parallel.map((s) => this.executeStep(s, { ...state }, ctx))
|
|
1269
|
-
);
|
|
1270
|
-
const allResults = [];
|
|
1271
|
-
let mergedState = { ...state };
|
|
1272
|
-
for (const result of settled) {
|
|
1273
|
-
if (result.status === "fulfilled") {
|
|
1274
|
-
Object.assign(mergedState, result.value.state);
|
|
1275
|
-
allResults.push(...result.value.results);
|
|
1276
|
-
} else {
|
|
1277
|
-
allResults.push({
|
|
1278
|
-
stepName: step.name,
|
|
1279
|
-
status: "error",
|
|
1280
|
-
error: result.reason?.message ?? "Unknown error",
|
|
1281
|
-
durationMs: Date.now() - startTime
|
|
1282
|
-
});
|
|
1283
|
-
}
|
|
1284
|
-
}
|
|
1285
|
-
ctx.eventBus.emit("workflow.step", {
|
|
1286
|
-
runId: ctx.runId,
|
|
1287
|
-
stepName: step.name,
|
|
1288
|
-
status: "done"
|
|
1289
|
-
});
|
|
1290
|
-
return {
|
|
1291
|
-
state: mergedState,
|
|
1292
|
-
results: [
|
|
1293
|
-
{
|
|
1294
|
-
stepName: step.name,
|
|
1295
|
-
status: "done",
|
|
1296
|
-
durationMs: Date.now() - startTime
|
|
1297
|
-
},
|
|
1298
|
-
...allResults
|
|
1299
|
-
]
|
|
1300
|
-
};
|
|
1301
|
-
}
|
|
1302
|
-
async withRetry(stepName, fn, ctx) {
|
|
1303
|
-
const maxRetries = this.retryPolicy?.maxRetries ?? 0;
|
|
1304
|
-
const backoffMs = this.retryPolicy?.backoffMs ?? 1e3;
|
|
1305
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
1306
|
-
try {
|
|
1307
|
-
const result = await fn();
|
|
1308
|
-
ctx.eventBus.emit("workflow.step", {
|
|
1309
|
-
runId: ctx.runId,
|
|
1310
|
-
stepName,
|
|
1311
|
-
status: "done"
|
|
1312
|
-
});
|
|
1313
|
-
return result;
|
|
1314
|
-
} catch (error) {
|
|
1315
|
-
if (attempt === maxRetries) {
|
|
1316
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
1317
|
-
ctx.eventBus.emit("workflow.step", {
|
|
1318
|
-
runId: ctx.runId,
|
|
1319
|
-
stepName,
|
|
1320
|
-
status: "error"
|
|
1321
|
-
});
|
|
1322
|
-
return {
|
|
1323
|
-
stepName,
|
|
1324
|
-
status: "error",
|
|
1325
|
-
error: err.message,
|
|
1326
|
-
durationMs: 0
|
|
1327
|
-
};
|
|
1328
|
-
}
|
|
1329
|
-
await new Promise(
|
|
1330
|
-
(resolve) => setTimeout(resolve, backoffMs * Math.pow(2, attempt))
|
|
1331
|
-
);
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1334
|
-
return { stepName, status: "error", error: "Exhausted retries", durationMs: 0 };
|
|
1335
|
-
}
|
|
1336
|
-
};
|
|
1337
|
-
|
|
1338
|
-
// src/workflow/workflow.ts
|
|
1339
|
-
var Workflow = class {
|
|
1340
|
-
name;
|
|
1341
|
-
eventBus;
|
|
1342
|
-
config;
|
|
1343
|
-
stepRunner;
|
|
1344
|
-
constructor(config) {
|
|
1345
|
-
this.config = config;
|
|
1346
|
-
this.name = config.name;
|
|
1347
|
-
this.eventBus = config.eventBus ?? new EventBus();
|
|
1348
|
-
this.stepRunner = new StepRunner(config.retryPolicy);
|
|
1349
|
-
}
|
|
1350
|
-
async run(opts) {
|
|
1351
|
-
const ctx = new RunContext({
|
|
1352
|
-
sessionId: opts?.sessionId ?? uuidv44(),
|
|
1353
|
-
userId: opts?.userId,
|
|
1354
|
-
eventBus: this.eventBus,
|
|
1355
|
-
sessionState: {}
|
|
1356
|
-
});
|
|
1357
|
-
this.eventBus.emit("run.start", {
|
|
1358
|
-
runId: ctx.runId,
|
|
1359
|
-
agentName: `workflow:${this.name}`,
|
|
1360
|
-
input: JSON.stringify(this.config.initialState)
|
|
1361
|
-
});
|
|
1362
|
-
try {
|
|
1363
|
-
const { state, results } = await this.stepRunner.executeSteps(
|
|
1364
|
-
this.config.steps,
|
|
1365
|
-
{ ...this.config.initialState },
|
|
1366
|
-
ctx
|
|
1367
|
-
);
|
|
1368
|
-
const workflowResult = {
|
|
1369
|
-
state,
|
|
1370
|
-
stepResults: results
|
|
1371
|
-
};
|
|
1372
|
-
this.eventBus.emit("run.complete", {
|
|
1373
|
-
runId: ctx.runId,
|
|
1374
|
-
output: {
|
|
1375
|
-
text: JSON.stringify(state),
|
|
1376
|
-
toolCalls: [],
|
|
1377
|
-
usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 }
|
|
1378
|
-
}
|
|
1379
|
-
});
|
|
1380
|
-
return workflowResult;
|
|
1381
|
-
} catch (error) {
|
|
1382
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
1383
|
-
this.eventBus.emit("run.error", { runId: ctx.runId, error: err });
|
|
1384
|
-
throw err;
|
|
1385
|
-
}
|
|
1386
|
-
}
|
|
1387
|
-
};
|
|
1388
|
-
|
|
1389
|
-
// src/models/providers/openai.ts
|
|
1390
|
-
import { createRequire as createRequire3 } from "module";
|
|
1391
|
-
var _require3 = createRequire3(import.meta.url);
|
|
1392
|
-
var OpenAIProvider = class {
|
|
1393
|
-
providerId = "openai";
|
|
1394
|
-
modelId;
|
|
1395
|
-
client;
|
|
1396
|
-
OpenAICtor;
|
|
1397
|
-
baseURL;
|
|
1398
|
-
clientCache = /* @__PURE__ */ new Map();
|
|
1399
|
-
constructor(modelId, config) {
|
|
1400
|
-
this.modelId = modelId;
|
|
1401
|
-
this.baseURL = config?.baseURL;
|
|
1402
|
-
try {
|
|
1403
|
-
const mod = _require3("openai");
|
|
1404
|
-
this.OpenAICtor = mod.default ?? mod;
|
|
1405
|
-
const key = config?.apiKey ?? process.env.OPENAI_API_KEY;
|
|
1406
|
-
if (key) {
|
|
1407
|
-
this.client = new this.OpenAICtor({ apiKey: key, baseURL: config?.baseURL });
|
|
1408
|
-
}
|
|
1409
|
-
} catch {
|
|
1410
|
-
throw new Error(
|
|
1411
|
-
"openai package is required for OpenAIProvider. Install it: npm install openai"
|
|
1412
|
-
);
|
|
1413
|
-
}
|
|
1414
|
-
}
|
|
1415
|
-
getClient(apiKey) {
|
|
1416
|
-
if (apiKey) {
|
|
1417
|
-
let cached = this.clientCache.get(apiKey);
|
|
1418
|
-
if (!cached) {
|
|
1419
|
-
cached = new this.OpenAICtor({ apiKey, baseURL: this.baseURL });
|
|
1420
|
-
this.clientCache.set(apiKey, cached);
|
|
1421
|
-
}
|
|
1422
|
-
return cached;
|
|
1423
|
-
}
|
|
1424
|
-
if (this.client) return this.client;
|
|
1425
|
-
const envKey = process.env.OPENAI_API_KEY;
|
|
1426
|
-
if (envKey) {
|
|
1427
|
-
this.client = new this.OpenAICtor({ apiKey: envKey, baseURL: this.baseURL });
|
|
1428
|
-
return this.client;
|
|
1429
|
-
}
|
|
1430
|
-
throw new Error("No OpenAI API key provided. Pass it via the x-openai-api-key header, apiKey in request body, or set OPENAI_API_KEY env var.");
|
|
1431
|
-
}
|
|
1432
|
-
async generate(messages, options) {
|
|
1433
|
-
const params = {
|
|
1434
|
-
model: this.modelId,
|
|
1435
|
-
messages: this.toOpenAIMessages(messages)
|
|
1436
|
-
};
|
|
1437
|
-
if (options?.temperature !== void 0)
|
|
1438
|
-
params.temperature = options.temperature;
|
|
1439
|
-
if (options?.maxTokens !== void 0)
|
|
1440
|
-
params.max_tokens = options.maxTokens;
|
|
1441
|
-
if (options?.topP !== void 0) params.top_p = options.topP;
|
|
1442
|
-
if (options?.stop) params.stop = options.stop;
|
|
1443
|
-
this.applyResponseFormat(params, options);
|
|
1444
|
-
if (options?.tools?.length) {
|
|
1445
|
-
params.tools = this.toOpenAITools(options.tools);
|
|
1446
|
-
}
|
|
1447
|
-
const client = this.getClient(options?.apiKey);
|
|
1448
|
-
const response = await client.chat.completions.create(params);
|
|
1449
|
-
return this.normalizeResponse(response);
|
|
1450
|
-
}
|
|
1451
|
-
async *stream(messages, options) {
|
|
1452
|
-
const params = {
|
|
1453
|
-
model: this.modelId,
|
|
1454
|
-
messages: this.toOpenAIMessages(messages),
|
|
1455
|
-
stream: true
|
|
1456
|
-
};
|
|
1457
|
-
if (options?.temperature !== void 0)
|
|
1458
|
-
params.temperature = options.temperature;
|
|
1459
|
-
if (options?.maxTokens !== void 0)
|
|
1460
|
-
params.max_tokens = options.maxTokens;
|
|
1461
|
-
if (options?.topP !== void 0) params.top_p = options.topP;
|
|
1462
|
-
if (options?.stop) params.stop = options.stop;
|
|
1463
|
-
this.applyResponseFormat(params, options);
|
|
1464
|
-
if (options?.tools?.length) {
|
|
1465
|
-
params.tools = this.toOpenAITools(options.tools);
|
|
1466
|
-
}
|
|
1467
|
-
const client = this.getClient(options?.apiKey);
|
|
1468
|
-
const stream = await client.chat.completions.create(params);
|
|
1469
|
-
const activeToolCalls = /* @__PURE__ */ new Map();
|
|
1470
|
-
for await (const chunk of stream) {
|
|
1471
|
-
const choice = chunk.choices?.[0];
|
|
1472
|
-
if (!choice) continue;
|
|
1473
|
-
const delta = choice.delta;
|
|
1474
|
-
if (delta?.content) {
|
|
1475
|
-
yield { type: "text", text: delta.content };
|
|
1476
|
-
}
|
|
1477
|
-
if (delta?.tool_calls) {
|
|
1478
|
-
for (const tc of delta.tool_calls) {
|
|
1479
|
-
const idx = tc.index ?? 0;
|
|
1480
|
-
if (tc.id) {
|
|
1481
|
-
activeToolCalls.set(idx, {
|
|
1482
|
-
id: tc.id,
|
|
1483
|
-
name: tc.function?.name ?? "",
|
|
1484
|
-
args: tc.function?.arguments ?? ""
|
|
1485
|
-
});
|
|
1486
|
-
yield {
|
|
1487
|
-
type: "tool_call_start",
|
|
1488
|
-
toolCall: {
|
|
1489
|
-
id: tc.id,
|
|
1490
|
-
name: tc.function?.name ?? ""
|
|
1491
|
-
}
|
|
1492
|
-
};
|
|
1493
|
-
} else if (tc.function?.arguments) {
|
|
1494
|
-
const existing = activeToolCalls.get(idx);
|
|
1495
|
-
if (existing) {
|
|
1496
|
-
existing.args += tc.function.arguments;
|
|
1497
|
-
yield {
|
|
1498
|
-
type: "tool_call_delta",
|
|
1499
|
-
toolCallId: existing.id,
|
|
1500
|
-
argumentsDelta: tc.function.arguments
|
|
1501
|
-
};
|
|
1502
|
-
}
|
|
1503
|
-
}
|
|
1504
|
-
if (tc.function?.name && !tc.id) {
|
|
1505
|
-
const existing = activeToolCalls.get(idx);
|
|
1506
|
-
if (existing) {
|
|
1507
|
-
existing.name = tc.function.name;
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
|
-
}
|
|
1511
|
-
}
|
|
1512
|
-
if (choice.finish_reason) {
|
|
1513
|
-
for (const [, tc] of activeToolCalls) {
|
|
1514
|
-
yield { type: "tool_call_end", toolCallId: tc.id };
|
|
1515
|
-
}
|
|
1516
|
-
yield {
|
|
1517
|
-
type: "finish",
|
|
1518
|
-
finishReason: choice.finish_reason === "tool_calls" ? "tool_calls" : choice.finish_reason,
|
|
1519
|
-
usage: chunk.usage ? {
|
|
1520
|
-
promptTokens: chunk.usage.prompt_tokens ?? 0,
|
|
1521
|
-
completionTokens: chunk.usage.completion_tokens ?? 0,
|
|
1522
|
-
totalTokens: chunk.usage.total_tokens ?? 0
|
|
1523
|
-
} : void 0
|
|
1524
|
-
};
|
|
1525
|
-
}
|
|
1526
|
-
}
|
|
1527
|
-
}
|
|
1528
|
-
applyResponseFormat(params, options) {
|
|
1529
|
-
if (!options?.responseFormat) return;
|
|
1530
|
-
if (options.responseFormat === "json") {
|
|
1531
|
-
params.response_format = { type: "json_object" };
|
|
1532
|
-
} else if (options.responseFormat === "text") {
|
|
1533
|
-
} else if (typeof options.responseFormat === "object") {
|
|
1534
|
-
params.response_format = {
|
|
1535
|
-
type: "json_schema",
|
|
1536
|
-
json_schema: {
|
|
1537
|
-
name: options.responseFormat.name ?? "response",
|
|
1538
|
-
schema: options.responseFormat.schema,
|
|
1539
|
-
strict: true
|
|
1540
|
-
}
|
|
1541
|
-
};
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
toOpenAIMessages(messages) {
|
|
1545
|
-
return messages.map((msg) => {
|
|
1546
|
-
if (msg.role === "assistant" && msg.toolCalls?.length) {
|
|
1547
|
-
return {
|
|
1548
|
-
role: "assistant",
|
|
1549
|
-
content: getTextContent(msg.content),
|
|
1550
|
-
tool_calls: msg.toolCalls.map((tc) => ({
|
|
1551
|
-
id: tc.id,
|
|
1552
|
-
type: "function",
|
|
1553
|
-
function: {
|
|
1554
|
-
name: tc.name,
|
|
1555
|
-
arguments: JSON.stringify(tc.arguments)
|
|
1556
|
-
}
|
|
1557
|
-
}))
|
|
1558
|
-
};
|
|
1559
|
-
}
|
|
1560
|
-
if (msg.role === "tool") {
|
|
1561
|
-
return {
|
|
1562
|
-
role: "tool",
|
|
1563
|
-
tool_call_id: msg.toolCallId,
|
|
1564
|
-
content: getTextContent(msg.content)
|
|
1565
|
-
};
|
|
1566
|
-
}
|
|
1567
|
-
if (isMultiModal(msg.content)) {
|
|
1568
|
-
return {
|
|
1569
|
-
role: msg.role,
|
|
1570
|
-
content: msg.content.map((part) => this.partToOpenAI(part))
|
|
1571
|
-
};
|
|
1572
|
-
}
|
|
1573
|
-
return {
|
|
1574
|
-
role: msg.role,
|
|
1575
|
-
content: msg.content ?? ""
|
|
1576
|
-
};
|
|
1577
|
-
});
|
|
1578
|
-
}
|
|
1579
|
-
partToOpenAI(part) {
|
|
1580
|
-
switch (part.type) {
|
|
1581
|
-
case "text":
|
|
1582
|
-
return { type: "text", text: part.text };
|
|
1583
|
-
case "image": {
|
|
1584
|
-
const isUrl = part.data.startsWith("http://") || part.data.startsWith("https://");
|
|
1585
|
-
return {
|
|
1586
|
-
type: "image_url",
|
|
1587
|
-
image_url: {
|
|
1588
|
-
url: isUrl ? part.data : `data:${part.mimeType ?? "image/png"};base64,${part.data}`
|
|
1589
|
-
}
|
|
1590
|
-
};
|
|
1591
|
-
}
|
|
1592
|
-
case "audio":
|
|
1593
|
-
return {
|
|
1594
|
-
type: "input_audio",
|
|
1595
|
-
input_audio: {
|
|
1596
|
-
data: part.data,
|
|
1597
|
-
format: part.mimeType?.split("/")[1] ?? "mp3"
|
|
1598
|
-
}
|
|
1599
|
-
};
|
|
1600
|
-
case "file":
|
|
1601
|
-
return {
|
|
1602
|
-
type: "text",
|
|
1603
|
-
text: `[File: ${part.filename ?? "attachment"} (${part.mimeType})]`
|
|
1604
|
-
};
|
|
1605
|
-
}
|
|
1606
|
-
}
|
|
1607
|
-
toOpenAITools(tools) {
|
|
1608
|
-
return tools.map((t) => ({
|
|
1609
|
-
type: "function",
|
|
1610
|
-
function: {
|
|
1611
|
-
name: t.name,
|
|
1612
|
-
description: t.description,
|
|
1613
|
-
parameters: t.parameters
|
|
1614
|
-
}
|
|
1615
|
-
}));
|
|
1616
|
-
}
|
|
1617
|
-
normalizeResponse(response) {
|
|
1618
|
-
const choice = response.choices[0];
|
|
1619
|
-
const msg = choice.message;
|
|
1620
|
-
const toolCalls = (msg.tool_calls ?? []).map((tc) => ({
|
|
1621
|
-
id: tc.id,
|
|
1622
|
-
name: tc.function.name,
|
|
1623
|
-
arguments: JSON.parse(tc.function.arguments || "{}")
|
|
1624
|
-
}));
|
|
1625
|
-
const usage = {
|
|
1626
|
-
promptTokens: response.usage?.prompt_tokens ?? 0,
|
|
1627
|
-
completionTokens: response.usage?.completion_tokens ?? 0,
|
|
1628
|
-
totalTokens: response.usage?.total_tokens ?? 0
|
|
1629
|
-
};
|
|
1630
|
-
let finishReason = "stop";
|
|
1631
|
-
if (choice.finish_reason === "tool_calls") finishReason = "tool_calls";
|
|
1632
|
-
else if (choice.finish_reason === "length") finishReason = "length";
|
|
1633
|
-
else if (choice.finish_reason === "content_filter")
|
|
1634
|
-
finishReason = "content_filter";
|
|
1635
|
-
return {
|
|
1636
|
-
message: {
|
|
1637
|
-
role: "assistant",
|
|
1638
|
-
content: msg.content ?? null,
|
|
1639
|
-
toolCalls: toolCalls.length > 0 ? toolCalls : void 0
|
|
1640
|
-
},
|
|
1641
|
-
usage,
|
|
1642
|
-
finishReason,
|
|
1643
|
-
raw: response
|
|
1644
|
-
};
|
|
1645
|
-
}
|
|
1646
|
-
};
|
|
1647
|
-
|
|
1648
|
-
// src/models/providers/anthropic.ts
|
|
1649
|
-
import { createRequire as createRequire4 } from "module";
|
|
1650
|
-
var _require4 = createRequire4(import.meta.url);
|
|
1651
|
-
var AnthropicProvider = class {
|
|
1652
|
-
providerId = "anthropic";
|
|
1653
|
-
modelId;
|
|
1654
|
-
client;
|
|
1655
|
-
AnthropicCtor;
|
|
1656
|
-
clientCache = /* @__PURE__ */ new Map();
|
|
1657
|
-
constructor(modelId, config) {
|
|
1658
|
-
this.modelId = modelId;
|
|
1659
|
-
try {
|
|
1660
|
-
const mod = _require4("@anthropic-ai/sdk");
|
|
1661
|
-
this.AnthropicCtor = mod.default ?? mod;
|
|
1662
|
-
const key = config?.apiKey ?? process.env.ANTHROPIC_API_KEY;
|
|
1663
|
-
if (key) {
|
|
1664
|
-
this.client = new this.AnthropicCtor({ apiKey: key });
|
|
1665
|
-
}
|
|
1666
|
-
} catch {
|
|
1667
|
-
throw new Error(
|
|
1668
|
-
"@anthropic-ai/sdk is required for AnthropicProvider. Install it: npm install @anthropic-ai/sdk"
|
|
1669
|
-
);
|
|
1670
|
-
}
|
|
1671
|
-
}
|
|
1672
|
-
getClient(apiKey) {
|
|
1673
|
-
if (apiKey) {
|
|
1674
|
-
let cached = this.clientCache.get(apiKey);
|
|
1675
|
-
if (!cached) {
|
|
1676
|
-
cached = new this.AnthropicCtor({ apiKey });
|
|
1677
|
-
this.clientCache.set(apiKey, cached);
|
|
1678
|
-
}
|
|
1679
|
-
return cached;
|
|
1680
|
-
}
|
|
1681
|
-
if (this.client) return this.client;
|
|
1682
|
-
const envKey = process.env.ANTHROPIC_API_KEY;
|
|
1683
|
-
if (envKey) {
|
|
1684
|
-
this.client = new this.AnthropicCtor({ apiKey: envKey });
|
|
1685
|
-
return this.client;
|
|
1686
|
-
}
|
|
1687
|
-
throw new Error("No Anthropic API key provided. Pass it via the x-anthropic-api-key header, apiKey in request body, or set ANTHROPIC_API_KEY env var.");
|
|
1688
|
-
}
|
|
1689
|
-
async generate(messages, options) {
|
|
1690
|
-
const { systemMsg, anthropicMessages } = this.toAnthropicMessages(messages);
|
|
1691
|
-
const params = {
|
|
1692
|
-
model: this.modelId,
|
|
1693
|
-
messages: anthropicMessages,
|
|
1694
|
-
max_tokens: options?.maxTokens ?? 4096
|
|
1695
|
-
};
|
|
1696
|
-
if (systemMsg) params.system = systemMsg;
|
|
1697
|
-
if (options?.temperature !== void 0)
|
|
1698
|
-
params.temperature = options.temperature;
|
|
1699
|
-
if (options?.topP !== void 0) params.top_p = options.topP;
|
|
1700
|
-
if (options?.stop) params.stop_sequences = options.stop;
|
|
1701
|
-
if (options?.tools?.length) {
|
|
1702
|
-
params.tools = this.toAnthropicTools(options.tools);
|
|
1703
|
-
}
|
|
1704
|
-
const client = this.getClient(options?.apiKey);
|
|
1705
|
-
const response = await client.messages.create(params);
|
|
1706
|
-
return this.normalizeResponse(response);
|
|
1707
|
-
}
|
|
1708
|
-
async *stream(messages, options) {
|
|
1709
|
-
const { systemMsg, anthropicMessages } = this.toAnthropicMessages(messages);
|
|
1710
|
-
const params = {
|
|
1711
|
-
model: this.modelId,
|
|
1712
|
-
messages: anthropicMessages,
|
|
1713
|
-
max_tokens: options?.maxTokens ?? 4096,
|
|
1714
|
-
stream: true
|
|
1715
|
-
};
|
|
1716
|
-
if (systemMsg) params.system = systemMsg;
|
|
1717
|
-
if (options?.temperature !== void 0)
|
|
1718
|
-
params.temperature = options.temperature;
|
|
1719
|
-
if (options?.topP !== void 0) params.top_p = options.topP;
|
|
1720
|
-
if (options?.stop) params.stop_sequences = options.stop;
|
|
1721
|
-
if (options?.tools?.length) {
|
|
1722
|
-
params.tools = this.toAnthropicTools(options.tools);
|
|
1723
|
-
}
|
|
1724
|
-
const client = this.getClient(options?.apiKey);
|
|
1725
|
-
const stream = await client.messages.create(params);
|
|
1726
|
-
let currentToolId = "";
|
|
1727
|
-
for await (const event of stream) {
|
|
1728
|
-
switch (event.type) {
|
|
1729
|
-
case "content_block_start": {
|
|
1730
|
-
if (event.content_block?.type === "tool_use") {
|
|
1731
|
-
currentToolId = event.content_block.id;
|
|
1732
|
-
yield {
|
|
1733
|
-
type: "tool_call_start",
|
|
1734
|
-
toolCall: {
|
|
1735
|
-
id: event.content_block.id,
|
|
1736
|
-
name: event.content_block.name
|
|
1737
|
-
}
|
|
1738
|
-
};
|
|
1739
|
-
}
|
|
1740
|
-
break;
|
|
1741
|
-
}
|
|
1742
|
-
case "content_block_delta": {
|
|
1743
|
-
if (event.delta?.type === "text_delta") {
|
|
1744
|
-
yield { type: "text", text: event.delta.text };
|
|
1745
|
-
} else if (event.delta?.type === "input_json_delta") {
|
|
1746
|
-
yield {
|
|
1747
|
-
type: "tool_call_delta",
|
|
1748
|
-
toolCallId: currentToolId,
|
|
1749
|
-
argumentsDelta: event.delta.partial_json
|
|
1750
|
-
};
|
|
1751
|
-
}
|
|
1752
|
-
break;
|
|
1753
|
-
}
|
|
1754
|
-
case "content_block_stop": {
|
|
1755
|
-
if (currentToolId) {
|
|
1756
|
-
yield { type: "tool_call_end", toolCallId: currentToolId };
|
|
1757
|
-
currentToolId = "";
|
|
1758
|
-
}
|
|
1759
|
-
break;
|
|
1760
|
-
}
|
|
1761
|
-
case "message_delta": {
|
|
1762
|
-
const usage = event.usage ? {
|
|
1763
|
-
promptTokens: 0,
|
|
1764
|
-
completionTokens: event.usage.output_tokens ?? 0,
|
|
1765
|
-
totalTokens: event.usage.output_tokens ?? 0
|
|
1766
|
-
} : void 0;
|
|
1767
|
-
let finishReason = event.delta?.stop_reason ?? "stop";
|
|
1768
|
-
if (finishReason === "tool_use") finishReason = "tool_calls";
|
|
1769
|
-
if (finishReason === "end_turn") finishReason = "stop";
|
|
1770
|
-
yield { type: "finish", finishReason, usage };
|
|
1771
|
-
break;
|
|
1772
|
-
}
|
|
1773
|
-
case "message_start": {
|
|
1774
|
-
if (event.message?.usage) {
|
|
1775
|
-
}
|
|
1776
|
-
break;
|
|
1777
|
-
}
|
|
1778
|
-
}
|
|
1779
|
-
}
|
|
1780
|
-
}
|
|
1781
|
-
toAnthropicMessages(messages) {
|
|
1782
|
-
let systemMsg;
|
|
1783
|
-
const anthropicMessages = [];
|
|
1784
|
-
for (const msg of messages) {
|
|
1785
|
-
if (msg.role === "system") {
|
|
1786
|
-
systemMsg = getTextContent(msg.content) || void 0;
|
|
1787
|
-
continue;
|
|
1788
|
-
}
|
|
1789
|
-
if (msg.role === "user") {
|
|
1790
|
-
if (isMultiModal(msg.content)) {
|
|
1791
|
-
anthropicMessages.push({
|
|
1792
|
-
role: "user",
|
|
1793
|
-
content: msg.content.map((p) => this.partToAnthropic(p))
|
|
1794
|
-
});
|
|
1795
|
-
} else {
|
|
1796
|
-
anthropicMessages.push({
|
|
1797
|
-
role: "user",
|
|
1798
|
-
content: [{ type: "text", text: msg.content ?? "" }]
|
|
1799
|
-
});
|
|
1800
|
-
}
|
|
1801
|
-
continue;
|
|
1802
|
-
}
|
|
1803
|
-
if (msg.role === "assistant") {
|
|
1804
|
-
const content = [];
|
|
1805
|
-
if (msg.content) {
|
|
1806
|
-
content.push({ type: "text", text: msg.content });
|
|
1807
|
-
}
|
|
1808
|
-
if (msg.toolCalls) {
|
|
1809
|
-
for (const tc of msg.toolCalls) {
|
|
1810
|
-
content.push({
|
|
1811
|
-
type: "tool_use",
|
|
1812
|
-
id: tc.id,
|
|
1813
|
-
name: tc.name,
|
|
1814
|
-
input: tc.arguments
|
|
1815
|
-
});
|
|
1816
|
-
}
|
|
1817
|
-
}
|
|
1818
|
-
anthropicMessages.push({
|
|
1819
|
-
role: "assistant",
|
|
1820
|
-
content: content.length > 0 ? content : [{ type: "text", text: "" }]
|
|
1821
|
-
});
|
|
1822
|
-
continue;
|
|
1823
|
-
}
|
|
1824
|
-
if (msg.role === "tool") {
|
|
1825
|
-
anthropicMessages.push({
|
|
1826
|
-
role: "user",
|
|
1827
|
-
content: [
|
|
1828
|
-
{
|
|
1829
|
-
type: "tool_result",
|
|
1830
|
-
tool_use_id: msg.toolCallId,
|
|
1831
|
-
content: msg.content ?? ""
|
|
1832
|
-
}
|
|
1833
|
-
]
|
|
1834
|
-
});
|
|
1835
|
-
continue;
|
|
1836
|
-
}
|
|
1837
|
-
}
|
|
1838
|
-
return { systemMsg, anthropicMessages };
|
|
1839
|
-
}
|
|
1840
|
-
partToAnthropic(part) {
|
|
1841
|
-
switch (part.type) {
|
|
1842
|
-
case "text":
|
|
1843
|
-
return { type: "text", text: part.text };
|
|
1844
|
-
case "image": {
|
|
1845
|
-
const isUrl = part.data.startsWith("http://") || part.data.startsWith("https://");
|
|
1846
|
-
if (isUrl) {
|
|
1847
|
-
return { type: "image", source: { type: "url", url: part.data } };
|
|
1848
|
-
}
|
|
1849
|
-
return {
|
|
1850
|
-
type: "image",
|
|
1851
|
-
source: {
|
|
1852
|
-
type: "base64",
|
|
1853
|
-
media_type: part.mimeType ?? "image/png",
|
|
1854
|
-
data: part.data
|
|
1855
|
-
}
|
|
1856
|
-
};
|
|
1857
|
-
}
|
|
1858
|
-
case "audio":
|
|
1859
|
-
return {
|
|
1860
|
-
type: "text",
|
|
1861
|
-
text: `[Audio content: ${part.mimeType ?? "audio"}]`
|
|
1862
|
-
};
|
|
1863
|
-
case "file":
|
|
1864
|
-
if (part.mimeType === "application/pdf") {
|
|
1865
|
-
return {
|
|
1866
|
-
type: "document",
|
|
1867
|
-
source: { type: "base64", media_type: "application/pdf", data: part.data }
|
|
1868
|
-
};
|
|
1869
|
-
}
|
|
1870
|
-
return {
|
|
1871
|
-
type: "text",
|
|
1872
|
-
text: `[File: ${part.filename ?? "attachment"} (${part.mimeType})]`
|
|
1873
|
-
};
|
|
1874
|
-
}
|
|
1875
|
-
}
|
|
1876
|
-
toAnthropicTools(tools) {
|
|
1877
|
-
return tools.map((t) => ({
|
|
1878
|
-
name: t.name,
|
|
1879
|
-
description: t.description,
|
|
1880
|
-
input_schema: t.parameters
|
|
1881
|
-
}));
|
|
1882
|
-
}
|
|
1883
|
-
normalizeResponse(response) {
|
|
1884
|
-
const toolCalls = [];
|
|
1885
|
-
let textContent = "";
|
|
1886
|
-
for (const block of response.content ?? []) {
|
|
1887
|
-
if (block.type === "text") {
|
|
1888
|
-
textContent += block.text;
|
|
1889
|
-
} else if (block.type === "tool_use") {
|
|
1890
|
-
toolCalls.push({
|
|
1891
|
-
id: block.id,
|
|
1892
|
-
name: block.name,
|
|
1893
|
-
arguments: block.input ?? {}
|
|
1894
|
-
});
|
|
1895
|
-
}
|
|
1896
|
-
}
|
|
1897
|
-
const usage = {
|
|
1898
|
-
promptTokens: response.usage?.input_tokens ?? 0,
|
|
1899
|
-
completionTokens: response.usage?.output_tokens ?? 0,
|
|
1900
|
-
totalTokens: (response.usage?.input_tokens ?? 0) + (response.usage?.output_tokens ?? 0)
|
|
1901
|
-
};
|
|
1902
|
-
let finishReason = "stop";
|
|
1903
|
-
if (response.stop_reason === "tool_use") finishReason = "tool_calls";
|
|
1904
|
-
else if (response.stop_reason === "max_tokens") finishReason = "length";
|
|
1905
|
-
return {
|
|
1906
|
-
message: {
|
|
1907
|
-
role: "assistant",
|
|
1908
|
-
content: textContent || null,
|
|
1909
|
-
toolCalls: toolCalls.length > 0 ? toolCalls : void 0
|
|
1910
|
-
},
|
|
1911
|
-
usage,
|
|
1912
|
-
finishReason,
|
|
1913
|
-
raw: response
|
|
1914
|
-
};
|
|
1915
|
-
}
|
|
1916
|
-
};
|
|
1917
|
-
|
|
1918
|
-
// src/models/providers/google.ts
|
|
1919
|
-
import { createRequire as createRequire5 } from "module";
|
|
1920
|
-
var _require5 = createRequire5(import.meta.url);
|
|
1921
|
-
var GoogleProvider = class {
|
|
1922
|
-
providerId = "google";
|
|
1923
|
-
modelId;
|
|
1924
|
-
ai;
|
|
1925
|
-
GoogleGenAICtor;
|
|
1926
|
-
clientCache = /* @__PURE__ */ new Map();
|
|
1927
|
-
constructor(modelId, config) {
|
|
1928
|
-
this.modelId = modelId;
|
|
1929
|
-
try {
|
|
1930
|
-
const { GoogleGenAI } = _require5("@google/genai");
|
|
1931
|
-
this.GoogleGenAICtor = GoogleGenAI;
|
|
1932
|
-
const key = config?.apiKey ?? process.env.GOOGLE_API_KEY;
|
|
1933
|
-
if (key) {
|
|
1934
|
-
this.ai = new GoogleGenAI({ apiKey: key });
|
|
1935
|
-
}
|
|
1936
|
-
} catch {
|
|
1937
|
-
throw new Error(
|
|
1938
|
-
"@google/genai is required for GoogleProvider. Install it: npm install @google/genai"
|
|
1939
|
-
);
|
|
1940
|
-
}
|
|
1941
|
-
}
|
|
1942
|
-
getClient(apiKey) {
|
|
1943
|
-
if (apiKey) {
|
|
1944
|
-
let cached = this.clientCache.get(apiKey);
|
|
1945
|
-
if (!cached) {
|
|
1946
|
-
cached = new this.GoogleGenAICtor({ apiKey });
|
|
1947
|
-
this.clientCache.set(apiKey, cached);
|
|
1948
|
-
}
|
|
1949
|
-
return cached;
|
|
1950
|
-
}
|
|
1951
|
-
if (this.ai) return this.ai;
|
|
1952
|
-
const envKey = process.env.GOOGLE_API_KEY;
|
|
1953
|
-
if (envKey) {
|
|
1954
|
-
this.ai = new this.GoogleGenAICtor({ apiKey: envKey });
|
|
1955
|
-
return this.ai;
|
|
1956
|
-
}
|
|
1957
|
-
throw new Error("No Google API key provided. Pass it via the x-google-api-key header, apiKey in request body, or set GOOGLE_API_KEY env var.");
|
|
1958
|
-
}
|
|
1959
|
-
async generate(messages, options) {
|
|
1960
|
-
const { systemInstruction, contents } = this.toGoogleMessages(messages);
|
|
1961
|
-
const config = {};
|
|
1962
|
-
if (options?.temperature !== void 0)
|
|
1963
|
-
config.temperature = options.temperature;
|
|
1964
|
-
if (options?.maxTokens !== void 0)
|
|
1965
|
-
config.maxOutputTokens = options.maxTokens;
|
|
1966
|
-
if (options?.topP !== void 0) config.topP = options.topP;
|
|
1967
|
-
if (options?.stop) config.stopSequences = options.stop;
|
|
1968
|
-
if (options?.responseFormat) {
|
|
1969
|
-
config.responseMimeType = "application/json";
|
|
1970
|
-
const rf = options.responseFormat;
|
|
1971
|
-
if (typeof rf === "object" && rf !== null && "type" in rf && rf.type === "json_schema" && "schema" in rf && rf.schema) {
|
|
1972
|
-
config.responseSchema = this.cleanJsonSchema(rf.schema);
|
|
1973
|
-
}
|
|
1974
|
-
}
|
|
1975
|
-
const params = {
|
|
1976
|
-
model: this.modelId,
|
|
1977
|
-
contents,
|
|
1978
|
-
config
|
|
1979
|
-
};
|
|
1980
|
-
if (systemInstruction) params.systemInstruction = systemInstruction;
|
|
1981
|
-
if (options?.tools?.length) {
|
|
1982
|
-
params.tools = [
|
|
1983
|
-
{
|
|
1984
|
-
functionDeclarations: this.toGoogleTools(options.tools)
|
|
1985
|
-
}
|
|
1986
|
-
];
|
|
1987
|
-
}
|
|
1988
|
-
const client = this.getClient(options?.apiKey);
|
|
1989
|
-
const response = await client.models.generateContent(params);
|
|
1990
|
-
return this.normalizeResponse(response);
|
|
1991
|
-
}
|
|
1992
|
-
async *stream(messages, options) {
|
|
1993
|
-
const { systemInstruction, contents } = this.toGoogleMessages(messages);
|
|
1994
|
-
const config = {};
|
|
1995
|
-
if (options?.temperature !== void 0)
|
|
1996
|
-
config.temperature = options.temperature;
|
|
1997
|
-
if (options?.maxTokens !== void 0)
|
|
1998
|
-
config.maxOutputTokens = options.maxTokens;
|
|
1999
|
-
if (options?.topP !== void 0) config.topP = options.topP;
|
|
2000
|
-
if (options?.stop) config.stopSequences = options.stop;
|
|
2001
|
-
const params = {
|
|
2002
|
-
model: this.modelId,
|
|
2003
|
-
contents,
|
|
2004
|
-
config
|
|
2005
|
-
};
|
|
2006
|
-
if (systemInstruction) params.systemInstruction = systemInstruction;
|
|
2007
|
-
if (options?.tools?.length) {
|
|
2008
|
-
params.tools = [
|
|
2009
|
-
{
|
|
2010
|
-
functionDeclarations: this.toGoogleTools(options.tools)
|
|
2011
|
-
}
|
|
2012
|
-
];
|
|
2013
|
-
}
|
|
2014
|
-
const client = this.getClient(options?.apiKey);
|
|
2015
|
-
const streamResult = await client.models.generateContentStream(params);
|
|
2016
|
-
let toolCallCounter = 0;
|
|
2017
|
-
for await (const chunk of streamResult) {
|
|
2018
|
-
const candidate = chunk.candidates?.[0];
|
|
2019
|
-
if (!candidate?.content?.parts) continue;
|
|
2020
|
-
for (const part of candidate.content.parts) {
|
|
2021
|
-
if (part.text) {
|
|
2022
|
-
yield { type: "text", text: part.text };
|
|
2023
|
-
}
|
|
2024
|
-
if (part.functionCall) {
|
|
2025
|
-
const id = `google_tc_${toolCallCounter++}`;
|
|
2026
|
-
yield {
|
|
2027
|
-
type: "tool_call_start",
|
|
2028
|
-
toolCall: { id, name: part.functionCall.name }
|
|
2029
|
-
};
|
|
2030
|
-
yield {
|
|
2031
|
-
type: "tool_call_delta",
|
|
2032
|
-
toolCallId: id,
|
|
2033
|
-
argumentsDelta: JSON.stringify(part.functionCall.args ?? {})
|
|
2034
|
-
};
|
|
2035
|
-
yield { type: "tool_call_end", toolCallId: id };
|
|
2036
|
-
}
|
|
2037
|
-
}
|
|
2038
|
-
if (candidate.finishReason) {
|
|
2039
|
-
let finishReason = "stop";
|
|
2040
|
-
if (candidate.finishReason === "STOP" || candidate.finishReason === "END_TURN") {
|
|
2041
|
-
finishReason = "stop";
|
|
2042
|
-
} else if (candidate.finishReason === "MAX_TOKENS") {
|
|
2043
|
-
finishReason = "length";
|
|
2044
|
-
} else if (candidate.finishReason === "SAFETY") {
|
|
2045
|
-
finishReason = "content_filter";
|
|
2046
|
-
}
|
|
2047
|
-
const hasToolCalls = candidate.content?.parts?.some(
|
|
2048
|
-
(p) => p.functionCall
|
|
2049
|
-
);
|
|
2050
|
-
if (hasToolCalls) finishReason = "tool_calls";
|
|
2051
|
-
yield {
|
|
2052
|
-
type: "finish",
|
|
2053
|
-
finishReason,
|
|
2054
|
-
usage: chunk.usageMetadata ? {
|
|
2055
|
-
promptTokens: chunk.usageMetadata.promptTokenCount ?? 0,
|
|
2056
|
-
completionTokens: chunk.usageMetadata.candidatesTokenCount ?? 0,
|
|
2057
|
-
totalTokens: chunk.usageMetadata.totalTokenCount ?? 0
|
|
2058
|
-
} : void 0
|
|
2059
|
-
};
|
|
2060
|
-
}
|
|
2061
|
-
}
|
|
2062
|
-
}
|
|
2063
|
-
toGoogleMessages(messages) {
|
|
2064
|
-
let systemInstruction;
|
|
2065
|
-
const contents = [];
|
|
2066
|
-
for (const msg of messages) {
|
|
2067
|
-
if (msg.role === "system") {
|
|
2068
|
-
systemInstruction = getTextContent(msg.content) || void 0;
|
|
2069
|
-
continue;
|
|
2070
|
-
}
|
|
2071
|
-
if (msg.role === "user") {
|
|
2072
|
-
if (isMultiModal(msg.content)) {
|
|
2073
|
-
contents.push({
|
|
2074
|
-
role: "user",
|
|
2075
|
-
parts: msg.content.map((p) => this.partToGoogle(p))
|
|
2076
|
-
});
|
|
2077
|
-
} else {
|
|
2078
|
-
contents.push({
|
|
2079
|
-
role: "user",
|
|
2080
|
-
parts: [{ text: msg.content ?? "" }]
|
|
2081
|
-
});
|
|
2082
|
-
}
|
|
2083
|
-
continue;
|
|
2084
|
-
}
|
|
2085
|
-
if (msg.role === "assistant") {
|
|
2086
|
-
const parts = [];
|
|
2087
|
-
if (msg.content) {
|
|
2088
|
-
parts.push({ text: msg.content });
|
|
2089
|
-
}
|
|
2090
|
-
if (msg.toolCalls) {
|
|
2091
|
-
for (const tc of msg.toolCalls) {
|
|
2092
|
-
parts.push({
|
|
2093
|
-
functionCall: { name: tc.name, args: tc.arguments }
|
|
2094
|
-
});
|
|
2095
|
-
}
|
|
2096
|
-
}
|
|
2097
|
-
if (parts.length === 0) {
|
|
2098
|
-
parts.push({ text: "" });
|
|
2099
|
-
}
|
|
2100
|
-
contents.push({ role: "model", parts });
|
|
2101
|
-
continue;
|
|
2102
|
-
}
|
|
2103
|
-
if (msg.role === "tool") {
|
|
2104
|
-
contents.push({
|
|
2105
|
-
role: "function",
|
|
2106
|
-
parts: [
|
|
2107
|
-
{
|
|
2108
|
-
functionResponse: {
|
|
2109
|
-
name: msg.name ?? "unknown",
|
|
2110
|
-
response: { result: msg.content ?? "" }
|
|
2111
|
-
}
|
|
2112
|
-
}
|
|
2113
|
-
]
|
|
2114
|
-
});
|
|
2115
|
-
continue;
|
|
2116
|
-
}
|
|
2117
|
-
}
|
|
2118
|
-
return { systemInstruction, contents };
|
|
2119
|
-
}
|
|
2120
|
-
partToGoogle(part) {
|
|
2121
|
-
switch (part.type) {
|
|
2122
|
-
case "text":
|
|
2123
|
-
return { text: part.text };
|
|
2124
|
-
case "image":
|
|
2125
|
-
case "audio":
|
|
2126
|
-
case "file": {
|
|
2127
|
-
const isUrl = part.data.startsWith("http://") || part.data.startsWith("https://");
|
|
2128
|
-
if (isUrl) {
|
|
2129
|
-
return { fileData: { fileUri: part.data, mimeType: part.mimeType ?? (part.type === "image" ? "image/png" : "application/octet-stream") } };
|
|
2130
|
-
}
|
|
2131
|
-
return {
|
|
2132
|
-
inlineData: {
|
|
2133
|
-
data: part.data,
|
|
2134
|
-
mimeType: part.mimeType ?? (part.type === "image" ? "image/png" : part.type === "audio" ? "audio/mp3" : "application/octet-stream")
|
|
2135
|
-
}
|
|
2136
|
-
};
|
|
2137
|
-
}
|
|
2138
|
-
}
|
|
2139
|
-
}
|
|
2140
|
-
toGoogleTools(tools) {
|
|
2141
|
-
return tools.map((t) => ({
|
|
2142
|
-
name: t.name,
|
|
2143
|
-
description: t.description,
|
|
2144
|
-
parameters: t.parameters
|
|
2145
|
-
}));
|
|
2146
|
-
}
|
|
2147
|
-
cleanJsonSchema(schema) {
|
|
2148
|
-
const cleaned = { ...schema };
|
|
2149
|
-
delete cleaned["$schema"];
|
|
2150
|
-
delete cleaned["$ref"];
|
|
2151
|
-
delete cleaned["additionalProperties"];
|
|
2152
|
-
if (cleaned.properties && typeof cleaned.properties === "object") {
|
|
2153
|
-
const props = {};
|
|
2154
|
-
for (const [key, val] of Object.entries(cleaned.properties)) {
|
|
2155
|
-
props[key] = typeof val === "object" && val ? this.cleanJsonSchema(val) : val;
|
|
2156
|
-
}
|
|
2157
|
-
cleaned.properties = props;
|
|
2158
|
-
}
|
|
2159
|
-
if (cleaned.items && typeof cleaned.items === "object") {
|
|
2160
|
-
cleaned.items = this.cleanJsonSchema(cleaned.items);
|
|
2161
|
-
}
|
|
2162
|
-
return cleaned;
|
|
2163
|
-
}
|
|
2164
|
-
normalizeResponse(response) {
|
|
2165
|
-
const candidate = response.candidates?.[0];
|
|
2166
|
-
const parts = candidate?.content?.parts ?? [];
|
|
2167
|
-
let textContent = "";
|
|
2168
|
-
const toolCalls = [];
|
|
2169
|
-
let toolCallCounter = 0;
|
|
2170
|
-
for (const part of parts) {
|
|
2171
|
-
if (part.text) {
|
|
2172
|
-
textContent += part.text;
|
|
2173
|
-
}
|
|
2174
|
-
if (part.functionCall) {
|
|
2175
|
-
toolCalls.push({
|
|
2176
|
-
id: `google_tc_${toolCallCounter++}`,
|
|
2177
|
-
name: part.functionCall.name,
|
|
2178
|
-
arguments: part.functionCall.args ?? {}
|
|
2179
|
-
});
|
|
2180
|
-
}
|
|
2181
|
-
}
|
|
2182
|
-
const usage = {
|
|
2183
|
-
promptTokens: response.usageMetadata?.promptTokenCount ?? 0,
|
|
2184
|
-
completionTokens: response.usageMetadata?.candidatesTokenCount ?? 0,
|
|
2185
|
-
totalTokens: response.usageMetadata?.totalTokenCount ?? 0
|
|
2186
|
-
};
|
|
2187
|
-
let finishReason = "stop";
|
|
2188
|
-
if (toolCalls.length > 0) finishReason = "tool_calls";
|
|
2189
|
-
else if (candidate?.finishReason === "MAX_TOKENS")
|
|
2190
|
-
finishReason = "length";
|
|
2191
|
-
else if (candidate?.finishReason === "SAFETY")
|
|
2192
|
-
finishReason = "content_filter";
|
|
2193
|
-
return {
|
|
2194
|
-
message: {
|
|
2195
|
-
role: "assistant",
|
|
2196
|
-
content: textContent || null,
|
|
2197
|
-
toolCalls: toolCalls.length > 0 ? toolCalls : void 0
|
|
2198
|
-
},
|
|
2199
|
-
usage,
|
|
2200
|
-
finishReason,
|
|
2201
|
-
raw: response
|
|
2202
|
-
};
|
|
2203
|
-
}
|
|
2204
|
-
};
|
|
2205
|
-
|
|
2206
|
-
// src/models/providers/ollama.ts
|
|
2207
|
-
import { createRequire as createRequire6 } from "module";
|
|
2208
|
-
var _require6 = createRequire6(import.meta.url);
|
|
2209
|
-
var OllamaProvider = class {
|
|
2210
|
-
providerId = "ollama";
|
|
2211
|
-
modelId;
|
|
2212
|
-
client;
|
|
2213
|
-
constructor(modelId, config) {
|
|
2214
|
-
this.modelId = modelId;
|
|
2215
|
-
try {
|
|
2216
|
-
const { Ollama } = _require6("ollama");
|
|
2217
|
-
this.client = new Ollama({
|
|
2218
|
-
host: config?.host ?? "http://localhost:11434"
|
|
2219
|
-
});
|
|
2220
|
-
} catch {
|
|
2221
|
-
throw new Error(
|
|
2222
|
-
"ollama package is required for OllamaProvider. Install it: npm install ollama"
|
|
2223
|
-
);
|
|
2224
|
-
}
|
|
2225
|
-
}
|
|
2226
|
-
async generate(messages, options) {
|
|
2227
|
-
const params = {
|
|
2228
|
-
model: this.modelId,
|
|
2229
|
-
messages: this.toOllamaMessages(messages),
|
|
2230
|
-
stream: false
|
|
2231
|
-
};
|
|
2232
|
-
const ollamaOptions = {};
|
|
2233
|
-
if (options?.temperature !== void 0)
|
|
2234
|
-
ollamaOptions.temperature = options.temperature;
|
|
2235
|
-
if (options?.topP !== void 0) ollamaOptions.top_p = options.topP;
|
|
2236
|
-
if (options?.stop) ollamaOptions.stop = options.stop;
|
|
2237
|
-
if (options?.maxTokens !== void 0)
|
|
2238
|
-
ollamaOptions.num_predict = options.maxTokens;
|
|
2239
|
-
if (Object.keys(ollamaOptions).length > 0) params.options = ollamaOptions;
|
|
2240
|
-
if (options?.tools?.length) {
|
|
2241
|
-
params.tools = this.toOllamaTools(options.tools);
|
|
2242
|
-
}
|
|
2243
|
-
if (options?.responseFormat === "json") {
|
|
2244
|
-
params.format = "json";
|
|
2245
|
-
}
|
|
2246
|
-
const response = await this.client.chat(params);
|
|
2247
|
-
return this.normalizeResponse(response);
|
|
2248
|
-
}
|
|
2249
|
-
async *stream(messages, options) {
|
|
2250
|
-
const params = {
|
|
2251
|
-
model: this.modelId,
|
|
2252
|
-
messages: this.toOllamaMessages(messages),
|
|
2253
|
-
stream: true
|
|
2254
|
-
};
|
|
2255
|
-
const ollamaOptions = {};
|
|
2256
|
-
if (options?.temperature !== void 0)
|
|
2257
|
-
ollamaOptions.temperature = options.temperature;
|
|
2258
|
-
if (options?.topP !== void 0) ollamaOptions.top_p = options.topP;
|
|
2259
|
-
if (options?.stop) ollamaOptions.stop = options.stop;
|
|
2260
|
-
if (options?.maxTokens !== void 0)
|
|
2261
|
-
ollamaOptions.num_predict = options.maxTokens;
|
|
2262
|
-
if (Object.keys(ollamaOptions).length > 0) params.options = ollamaOptions;
|
|
2263
|
-
if (options?.tools?.length) {
|
|
2264
|
-
params.tools = this.toOllamaTools(options.tools);
|
|
2265
|
-
}
|
|
2266
|
-
if (options?.responseFormat === "json") {
|
|
2267
|
-
params.format = "json";
|
|
2268
|
-
}
|
|
2269
|
-
const stream = await this.client.chat(params);
|
|
2270
|
-
let toolCallCounter = 0;
|
|
2271
|
-
for await (const chunk of stream) {
|
|
2272
|
-
if (chunk.message?.content) {
|
|
2273
|
-
yield { type: "text", text: chunk.message.content };
|
|
2274
|
-
}
|
|
2275
|
-
if (chunk.message?.tool_calls) {
|
|
2276
|
-
for (const tc of chunk.message.tool_calls) {
|
|
2277
|
-
const id = `ollama_tc_${toolCallCounter++}`;
|
|
2278
|
-
yield {
|
|
2279
|
-
type: "tool_call_start",
|
|
2280
|
-
toolCall: {
|
|
2281
|
-
id,
|
|
2282
|
-
name: tc.function?.name ?? ""
|
|
2283
|
-
}
|
|
2284
|
-
};
|
|
2285
|
-
yield {
|
|
2286
|
-
type: "tool_call_delta",
|
|
2287
|
-
toolCallId: id,
|
|
2288
|
-
argumentsDelta: JSON.stringify(tc.function?.arguments ?? {})
|
|
2289
|
-
};
|
|
2290
|
-
yield { type: "tool_call_end", toolCallId: id };
|
|
2291
|
-
}
|
|
2292
|
-
}
|
|
2293
|
-
if (chunk.done) {
|
|
2294
|
-
const hasToolCalls = chunk.message?.tool_calls?.length > 0;
|
|
2295
|
-
yield {
|
|
2296
|
-
type: "finish",
|
|
2297
|
-
finishReason: hasToolCalls ? "tool_calls" : "stop",
|
|
2298
|
-
usage: {
|
|
2299
|
-
promptTokens: chunk.prompt_eval_count ?? 0,
|
|
2300
|
-
completionTokens: chunk.eval_count ?? 0,
|
|
2301
|
-
totalTokens: (chunk.prompt_eval_count ?? 0) + (chunk.eval_count ?? 0)
|
|
2302
|
-
}
|
|
2303
|
-
};
|
|
2304
|
-
}
|
|
2305
|
-
}
|
|
2306
|
-
}
|
|
2307
|
-
toOllamaMessages(messages) {
|
|
2308
|
-
return messages.map((msg) => {
|
|
2309
|
-
if (msg.role === "assistant" && msg.toolCalls?.length) {
|
|
2310
|
-
return {
|
|
2311
|
-
role: "assistant",
|
|
2312
|
-
content: msg.content ?? "",
|
|
2313
|
-
tool_calls: msg.toolCalls.map((tc) => ({
|
|
2314
|
-
function: {
|
|
2315
|
-
name: tc.name,
|
|
2316
|
-
arguments: tc.arguments
|
|
2317
|
-
}
|
|
2318
|
-
}))
|
|
2319
|
-
};
|
|
2320
|
-
}
|
|
2321
|
-
if (msg.role === "tool") {
|
|
2322
|
-
return {
|
|
2323
|
-
role: "tool",
|
|
2324
|
-
content: msg.content ?? ""
|
|
2325
|
-
};
|
|
2326
|
-
}
|
|
2327
|
-
return {
|
|
2328
|
-
role: msg.role,
|
|
2329
|
-
content: msg.content ?? ""
|
|
2330
|
-
};
|
|
2331
|
-
});
|
|
2332
|
-
}
|
|
2333
|
-
toOllamaTools(tools) {
|
|
2334
|
-
return tools.map((t) => ({
|
|
2335
|
-
type: "function",
|
|
2336
|
-
function: {
|
|
2337
|
-
name: t.name,
|
|
2338
|
-
description: t.description,
|
|
2339
|
-
parameters: t.parameters
|
|
2340
|
-
}
|
|
2341
|
-
}));
|
|
2342
|
-
}
|
|
2343
|
-
normalizeResponse(response) {
|
|
2344
|
-
const toolCalls = (response.message?.tool_calls ?? []).map(
|
|
2345
|
-
(tc, i) => ({
|
|
2346
|
-
id: `ollama_tc_${i}`,
|
|
2347
|
-
name: tc.function?.name ?? "",
|
|
2348
|
-
arguments: tc.function?.arguments ?? {}
|
|
2349
|
-
})
|
|
2350
|
-
);
|
|
2351
|
-
const usage = {
|
|
2352
|
-
promptTokens: response.prompt_eval_count ?? 0,
|
|
2353
|
-
completionTokens: response.eval_count ?? 0,
|
|
2354
|
-
totalTokens: (response.prompt_eval_count ?? 0) + (response.eval_count ?? 0)
|
|
2355
|
-
};
|
|
2356
|
-
const hasToolCalls = toolCalls.length > 0;
|
|
2357
|
-
return {
|
|
2358
|
-
message: {
|
|
2359
|
-
role: "assistant",
|
|
2360
|
-
content: response.message?.content ?? null,
|
|
2361
|
-
toolCalls: hasToolCalls ? toolCalls : void 0
|
|
2362
|
-
},
|
|
2363
|
-
usage,
|
|
2364
|
-
finishReason: hasToolCalls ? "tool_calls" : "stop",
|
|
2365
|
-
raw: response
|
|
2366
|
-
};
|
|
2367
|
-
}
|
|
2368
|
-
};
|
|
2369
|
-
|
|
2370
|
-
// src/models/providers/vertex.ts
|
|
2371
|
-
import { createRequire as createRequire7 } from "module";
|
|
2372
|
-
var _require7 = createRequire7(import.meta.url);
|
|
2373
|
-
var VertexAIProvider = class {
|
|
2374
|
-
providerId = "vertex";
|
|
2375
|
-
modelId;
|
|
2376
|
-
ai = null;
|
|
2377
|
-
GoogleGenAICtor;
|
|
2378
|
-
project;
|
|
2379
|
-
location;
|
|
2380
|
-
constructor(modelId, config) {
|
|
2381
|
-
this.modelId = modelId;
|
|
2382
|
-
this.project = config?.project ?? process.env.GOOGLE_CLOUD_PROJECT ?? process.env.GCLOUD_PROJECT ?? "";
|
|
2383
|
-
this.location = config?.location ?? process.env.GOOGLE_CLOUD_LOCATION ?? process.env.GOOGLE_CLOUD_REGION ?? "us-central1";
|
|
2384
|
-
if (!this.project) {
|
|
2385
|
-
throw new Error(
|
|
2386
|
-
"VertexAIProvider: 'project' is required. Pass it in config or set GOOGLE_CLOUD_PROJECT env var."
|
|
2387
|
-
);
|
|
2388
|
-
}
|
|
2389
|
-
try {
|
|
2390
|
-
const { GoogleGenAI } = _require7("@google/genai");
|
|
2391
|
-
this.GoogleGenAICtor = GoogleGenAI;
|
|
2392
|
-
} catch {
|
|
2393
|
-
throw new Error(
|
|
2394
|
-
"@google/genai is required for VertexAIProvider. Install it: npm install @google/genai"
|
|
2395
|
-
);
|
|
2396
|
-
}
|
|
2397
|
-
}
|
|
2398
|
-
getClient() {
|
|
2399
|
-
if (this.ai) return this.ai;
|
|
2400
|
-
this.ai = new this.GoogleGenAICtor({
|
|
2401
|
-
vertexai: true,
|
|
2402
|
-
project: this.project,
|
|
2403
|
-
location: this.location
|
|
2404
|
-
});
|
|
2405
|
-
return this.ai;
|
|
2406
|
-
}
|
|
2407
|
-
async generate(messages, options) {
|
|
2408
|
-
const { systemInstruction, contents } = this.toGoogleMessages(messages);
|
|
2409
|
-
const config = {};
|
|
2410
|
-
if (options?.temperature !== void 0)
|
|
2411
|
-
config.temperature = options.temperature;
|
|
2412
|
-
if (options?.maxTokens !== void 0)
|
|
2413
|
-
config.maxOutputTokens = options.maxTokens;
|
|
2414
|
-
if (options?.topP !== void 0) config.topP = options.topP;
|
|
2415
|
-
if (options?.stop) config.stopSequences = options.stop;
|
|
2416
|
-
if (options?.responseFormat) {
|
|
2417
|
-
config.responseMimeType = "application/json";
|
|
2418
|
-
const rf = options.responseFormat;
|
|
2419
|
-
if (typeof rf === "object" && rf !== null && "type" in rf && rf.type === "json_schema" && "schema" in rf && rf.schema) {
|
|
2420
|
-
config.responseSchema = this.cleanJsonSchema(
|
|
2421
|
-
rf.schema
|
|
2422
|
-
);
|
|
2423
|
-
}
|
|
2424
|
-
}
|
|
2425
|
-
const params = {
|
|
2426
|
-
model: this.modelId,
|
|
2427
|
-
contents,
|
|
2428
|
-
config
|
|
2429
|
-
};
|
|
2430
|
-
if (systemInstruction) params.systemInstruction = systemInstruction;
|
|
2431
|
-
if (options?.tools?.length) {
|
|
2432
|
-
params.tools = [
|
|
2433
|
-
{ functionDeclarations: this.toGoogleTools(options.tools) }
|
|
2434
|
-
];
|
|
2435
|
-
}
|
|
2436
|
-
const client = this.getClient();
|
|
2437
|
-
const response = await client.models.generateContent(params);
|
|
2438
|
-
return this.normalizeResponse(response);
|
|
2439
|
-
}
|
|
2440
|
-
async *stream(messages, options) {
|
|
2441
|
-
const { systemInstruction, contents } = this.toGoogleMessages(messages);
|
|
2442
|
-
const config = {};
|
|
2443
|
-
if (options?.temperature !== void 0)
|
|
2444
|
-
config.temperature = options.temperature;
|
|
2445
|
-
if (options?.maxTokens !== void 0)
|
|
2446
|
-
config.maxOutputTokens = options.maxTokens;
|
|
2447
|
-
if (options?.topP !== void 0) config.topP = options.topP;
|
|
2448
|
-
if (options?.stop) config.stopSequences = options.stop;
|
|
2449
|
-
const params = {
|
|
2450
|
-
model: this.modelId,
|
|
2451
|
-
contents,
|
|
2452
|
-
config
|
|
2453
|
-
};
|
|
2454
|
-
if (systemInstruction) params.systemInstruction = systemInstruction;
|
|
2455
|
-
if (options?.tools?.length) {
|
|
2456
|
-
params.tools = [
|
|
2457
|
-
{ functionDeclarations: this.toGoogleTools(options.tools) }
|
|
2458
|
-
];
|
|
2459
|
-
}
|
|
2460
|
-
const client = this.getClient();
|
|
2461
|
-
const streamResult = await client.models.generateContentStream(params);
|
|
2462
|
-
let toolCallCounter = 0;
|
|
2463
|
-
for await (const chunk of streamResult) {
|
|
2464
|
-
const candidate = chunk.candidates?.[0];
|
|
2465
|
-
if (!candidate?.content?.parts) continue;
|
|
2466
|
-
for (const part of candidate.content.parts) {
|
|
2467
|
-
if (part.text) {
|
|
2468
|
-
yield { type: "text", text: part.text };
|
|
2469
|
-
}
|
|
2470
|
-
if (part.functionCall) {
|
|
2471
|
-
const id = `vertex_tc_${toolCallCounter++}`;
|
|
2472
|
-
yield {
|
|
2473
|
-
type: "tool_call_start",
|
|
2474
|
-
toolCall: { id, name: part.functionCall.name }
|
|
2475
|
-
};
|
|
2476
|
-
yield {
|
|
2477
|
-
type: "tool_call_delta",
|
|
2478
|
-
toolCallId: id,
|
|
2479
|
-
argumentsDelta: JSON.stringify(part.functionCall.args ?? {})
|
|
2480
|
-
};
|
|
2481
|
-
yield { type: "tool_call_end", toolCallId: id };
|
|
2482
|
-
}
|
|
2483
|
-
}
|
|
2484
|
-
if (candidate.finishReason) {
|
|
2485
|
-
let finishReason = "stop";
|
|
2486
|
-
if (candidate.finishReason === "STOP" || candidate.finishReason === "END_TURN")
|
|
2487
|
-
finishReason = "stop";
|
|
2488
|
-
else if (candidate.finishReason === "MAX_TOKENS")
|
|
2489
|
-
finishReason = "length";
|
|
2490
|
-
else if (candidate.finishReason === "SAFETY")
|
|
2491
|
-
finishReason = "content_filter";
|
|
2492
|
-
const hasToolCalls = candidate.content?.parts?.some(
|
|
2493
|
-
(p) => p.functionCall
|
|
2494
|
-
);
|
|
2495
|
-
if (hasToolCalls) finishReason = "tool_calls";
|
|
2496
|
-
yield {
|
|
2497
|
-
type: "finish",
|
|
2498
|
-
finishReason,
|
|
2499
|
-
usage: chunk.usageMetadata ? {
|
|
2500
|
-
promptTokens: chunk.usageMetadata.promptTokenCount ?? 0,
|
|
2501
|
-
completionTokens: chunk.usageMetadata.candidatesTokenCount ?? 0,
|
|
2502
|
-
totalTokens: chunk.usageMetadata.totalTokenCount ?? 0
|
|
2503
|
-
} : void 0
|
|
2504
|
-
};
|
|
2505
|
-
}
|
|
2506
|
-
}
|
|
2507
|
-
}
|
|
2508
|
-
// ── Message conversion (identical to GoogleProvider) ─────────────────────
|
|
2509
|
-
toGoogleMessages(messages) {
|
|
2510
|
-
let systemInstruction;
|
|
2511
|
-
const contents = [];
|
|
2512
|
-
for (const msg of messages) {
|
|
2513
|
-
if (msg.role === "system") {
|
|
2514
|
-
systemInstruction = getTextContent(msg.content) || void 0;
|
|
2515
|
-
continue;
|
|
2516
|
-
}
|
|
2517
|
-
if (msg.role === "user") {
|
|
2518
|
-
if (isMultiModal(msg.content)) {
|
|
2519
|
-
contents.push({
|
|
2520
|
-
role: "user",
|
|
2521
|
-
parts: msg.content.map((p) => this.partToGoogle(p))
|
|
2522
|
-
});
|
|
2523
|
-
} else {
|
|
2524
|
-
contents.push({
|
|
2525
|
-
role: "user",
|
|
2526
|
-
parts: [{ text: msg.content ?? "" }]
|
|
2527
|
-
});
|
|
2528
|
-
}
|
|
2529
|
-
continue;
|
|
2530
|
-
}
|
|
2531
|
-
if (msg.role === "assistant") {
|
|
2532
|
-
const parts = [];
|
|
2533
|
-
if (msg.content) parts.push({ text: msg.content });
|
|
2534
|
-
if (msg.toolCalls) {
|
|
2535
|
-
for (const tc of msg.toolCalls) {
|
|
2536
|
-
parts.push({
|
|
2537
|
-
functionCall: { name: tc.name, args: tc.arguments }
|
|
2538
|
-
});
|
|
2539
|
-
}
|
|
2540
|
-
}
|
|
2541
|
-
if (parts.length === 0) parts.push({ text: "" });
|
|
2542
|
-
contents.push({ role: "model", parts });
|
|
2543
|
-
continue;
|
|
2544
|
-
}
|
|
2545
|
-
if (msg.role === "tool") {
|
|
2546
|
-
contents.push({
|
|
2547
|
-
role: "function",
|
|
2548
|
-
parts: [
|
|
2549
|
-
{
|
|
2550
|
-
functionResponse: {
|
|
2551
|
-
name: msg.name ?? "unknown",
|
|
2552
|
-
response: { result: msg.content ?? "" }
|
|
2553
|
-
}
|
|
2554
|
-
}
|
|
2555
|
-
]
|
|
2556
|
-
});
|
|
2557
|
-
continue;
|
|
2558
|
-
}
|
|
2559
|
-
}
|
|
2560
|
-
return { systemInstruction, contents };
|
|
2561
|
-
}
|
|
2562
|
-
partToGoogle(part) {
|
|
2563
|
-
switch (part.type) {
|
|
2564
|
-
case "text":
|
|
2565
|
-
return { text: part.text };
|
|
2566
|
-
case "image":
|
|
2567
|
-
case "audio":
|
|
2568
|
-
case "file": {
|
|
2569
|
-
const isUrl = part.data.startsWith("http://") || part.data.startsWith("https://");
|
|
2570
|
-
if (isUrl) {
|
|
2571
|
-
return {
|
|
2572
|
-
fileData: {
|
|
2573
|
-
fileUri: part.data,
|
|
2574
|
-
mimeType: part.mimeType ?? (part.type === "image" ? "image/png" : "application/octet-stream")
|
|
2575
|
-
}
|
|
2576
|
-
};
|
|
2577
|
-
}
|
|
2578
|
-
return {
|
|
2579
|
-
inlineData: {
|
|
2580
|
-
data: part.data,
|
|
2581
|
-
mimeType: part.mimeType ?? (part.type === "image" ? "image/png" : part.type === "audio" ? "audio/mp3" : "application/octet-stream")
|
|
2582
|
-
}
|
|
2583
|
-
};
|
|
2584
|
-
}
|
|
2585
|
-
}
|
|
2586
|
-
}
|
|
2587
|
-
toGoogleTools(tools) {
|
|
2588
|
-
return tools.map((t) => ({
|
|
2589
|
-
name: t.name,
|
|
2590
|
-
description: t.description,
|
|
2591
|
-
parameters: t.parameters
|
|
2592
|
-
}));
|
|
2593
|
-
}
|
|
2594
|
-
cleanJsonSchema(schema) {
|
|
2595
|
-
const cleaned = { ...schema };
|
|
2596
|
-
delete cleaned["$schema"];
|
|
2597
|
-
delete cleaned["$ref"];
|
|
2598
|
-
delete cleaned["additionalProperties"];
|
|
2599
|
-
if (cleaned.properties && typeof cleaned.properties === "object") {
|
|
2600
|
-
const props = {};
|
|
2601
|
-
for (const [key, val] of Object.entries(
|
|
2602
|
-
cleaned.properties
|
|
2603
|
-
)) {
|
|
2604
|
-
props[key] = typeof val === "object" && val ? this.cleanJsonSchema(val) : val;
|
|
2605
|
-
}
|
|
2606
|
-
cleaned.properties = props;
|
|
2607
|
-
}
|
|
2608
|
-
if (cleaned.items && typeof cleaned.items === "object") {
|
|
2609
|
-
cleaned.items = this.cleanJsonSchema(
|
|
2610
|
-
cleaned.items
|
|
2611
|
-
);
|
|
2612
|
-
}
|
|
2613
|
-
return cleaned;
|
|
2614
|
-
}
|
|
2615
|
-
normalizeResponse(response) {
|
|
2616
|
-
const candidate = response.candidates?.[0];
|
|
2617
|
-
const parts = candidate?.content?.parts ?? [];
|
|
2618
|
-
let textContent = "";
|
|
2619
|
-
const toolCalls = [];
|
|
2620
|
-
let toolCallCounter = 0;
|
|
2621
|
-
for (const part of parts) {
|
|
2622
|
-
if (part.text) textContent += part.text;
|
|
2623
|
-
if (part.functionCall) {
|
|
2624
|
-
toolCalls.push({
|
|
2625
|
-
id: `vertex_tc_${toolCallCounter++}`,
|
|
2626
|
-
name: part.functionCall.name,
|
|
2627
|
-
arguments: part.functionCall.args ?? {}
|
|
2628
|
-
});
|
|
2629
|
-
}
|
|
2630
|
-
}
|
|
2631
|
-
const usage = {
|
|
2632
|
-
promptTokens: response.usageMetadata?.promptTokenCount ?? 0,
|
|
2633
|
-
completionTokens: response.usageMetadata?.candidatesTokenCount ?? 0,
|
|
2634
|
-
totalTokens: response.usageMetadata?.totalTokenCount ?? 0
|
|
2635
|
-
};
|
|
2636
|
-
let finishReason = "stop";
|
|
2637
|
-
if (toolCalls.length > 0) finishReason = "tool_calls";
|
|
2638
|
-
else if (candidate?.finishReason === "MAX_TOKENS")
|
|
2639
|
-
finishReason = "length";
|
|
2640
|
-
else if (candidate?.finishReason === "SAFETY")
|
|
2641
|
-
finishReason = "content_filter";
|
|
2642
|
-
return {
|
|
2643
|
-
message: {
|
|
2644
|
-
role: "assistant",
|
|
2645
|
-
content: textContent || null,
|
|
2646
|
-
toolCalls: toolCalls.length > 0 ? toolCalls : void 0
|
|
2647
|
-
},
|
|
2648
|
-
usage,
|
|
2649
|
-
finishReason,
|
|
2650
|
-
raw: response
|
|
2651
|
-
};
|
|
2652
|
-
}
|
|
2653
|
-
};
|
|
2654
|
-
|
|
2655
|
-
// src/models/registry.ts
|
|
2656
|
-
var ModelRegistry = class {
|
|
2657
|
-
factories = /* @__PURE__ */ new Map();
|
|
2658
|
-
register(providerId, factory) {
|
|
2659
|
-
this.factories.set(providerId, factory);
|
|
2660
|
-
}
|
|
2661
|
-
resolve(providerId, modelId, config) {
|
|
2662
|
-
const factory = this.factories.get(providerId);
|
|
2663
|
-
if (!factory) {
|
|
2664
|
-
throw new Error(
|
|
2665
|
-
`Unknown provider "${providerId}". Register it first with registry.register().`
|
|
2666
|
-
);
|
|
2667
|
-
}
|
|
2668
|
-
return factory(modelId, config);
|
|
2669
|
-
}
|
|
2670
|
-
has(providerId) {
|
|
2671
|
-
return this.factories.has(providerId);
|
|
2672
|
-
}
|
|
2673
|
-
};
|
|
2674
|
-
var registry = new ModelRegistry();
|
|
2675
|
-
registry.register(
|
|
2676
|
-
"openai",
|
|
2677
|
-
(modelId, config) => new OpenAIProvider(modelId, config)
|
|
2678
|
-
);
|
|
2679
|
-
registry.register(
|
|
2680
|
-
"anthropic",
|
|
2681
|
-
(modelId, config) => new AnthropicProvider(modelId, config)
|
|
2682
|
-
);
|
|
2683
|
-
registry.register(
|
|
2684
|
-
"google",
|
|
2685
|
-
(modelId, config) => new GoogleProvider(modelId, config)
|
|
2686
|
-
);
|
|
2687
|
-
registry.register(
|
|
2688
|
-
"ollama",
|
|
2689
|
-
(modelId, config) => new OllamaProvider(modelId, config)
|
|
2690
|
-
);
|
|
2691
|
-
function openai(modelId, config) {
|
|
2692
|
-
return registry.resolve("openai", modelId, config);
|
|
2693
|
-
}
|
|
2694
|
-
function anthropic(modelId, config) {
|
|
2695
|
-
return registry.resolve("anthropic", modelId, config);
|
|
2696
|
-
}
|
|
2697
|
-
function google(modelId, config) {
|
|
2698
|
-
return registry.resolve("google", modelId, config);
|
|
2699
|
-
}
|
|
2700
|
-
function ollama(modelId, config) {
|
|
2701
|
-
return registry.resolve("ollama", modelId, config);
|
|
2702
|
-
}
|
|
2703
|
-
registry.register(
|
|
2704
|
-
"vertex",
|
|
2705
|
-
(modelId, config) => new VertexAIProvider(
|
|
2706
|
-
modelId,
|
|
2707
|
-
config
|
|
2708
|
-
)
|
|
2709
|
-
);
|
|
2710
|
-
function vertex(modelId, config) {
|
|
2711
|
-
return registry.resolve("vertex", modelId, config);
|
|
2712
|
-
}
|
|
2713
|
-
|
|
2714
|
-
// src/tools/define-tool.ts
|
|
2715
|
-
function defineTool(config) {
|
|
2716
|
-
return {
|
|
2717
|
-
name: config.name,
|
|
2718
|
-
description: config.description,
|
|
2719
|
-
parameters: config.parameters,
|
|
2720
|
-
execute: config.execute
|
|
2721
|
-
};
|
|
2722
|
-
}
|
|
2723
|
-
|
|
2724
|
-
// src/storage/sqlite.ts
|
|
2725
|
-
import { createRequire as createRequire8 } from "module";
|
|
2726
|
-
var _require8 = createRequire8(import.meta.url);
|
|
2727
|
-
var SqliteStorage = class {
|
|
2728
|
-
db;
|
|
2729
|
-
constructor(dbPath) {
|
|
2730
|
-
try {
|
|
2731
|
-
const Database = _require8("better-sqlite3");
|
|
2732
|
-
this.db = new Database(dbPath);
|
|
2733
|
-
this.db.pragma("journal_mode = WAL");
|
|
2734
|
-
this.db.exec(`
|
|
2735
|
-
CREATE TABLE IF NOT EXISTS kv_store (
|
|
2736
|
-
namespace TEXT NOT NULL,
|
|
2737
|
-
key TEXT NOT NULL,
|
|
2738
|
-
value TEXT NOT NULL,
|
|
2739
|
-
updated_at TEXT DEFAULT (datetime('now')),
|
|
2740
|
-
PRIMARY KEY (namespace, key)
|
|
2741
|
-
)
|
|
2742
|
-
`);
|
|
2743
|
-
} catch {
|
|
2744
|
-
throw new Error(
|
|
2745
|
-
"better-sqlite3 is required for SqliteStorage. Install it: npm install better-sqlite3"
|
|
2746
|
-
);
|
|
2747
|
-
}
|
|
2748
|
-
}
|
|
2749
|
-
async get(namespace, key) {
|
|
2750
|
-
const row = this.db.prepare("SELECT value FROM kv_store WHERE namespace = ? AND key = ?").get(namespace, key);
|
|
2751
|
-
if (!row) return null;
|
|
2752
|
-
return JSON.parse(row.value);
|
|
2753
|
-
}
|
|
2754
|
-
async set(namespace, key, value) {
|
|
2755
|
-
this.db.prepare(
|
|
2756
|
-
`INSERT INTO kv_store (namespace, key, value, updated_at)
|
|
2757
|
-
VALUES (?, ?, ?, datetime('now'))
|
|
2758
|
-
ON CONFLICT(namespace, key)
|
|
2759
|
-
DO UPDATE SET value = excluded.value, updated_at = datetime('now')`
|
|
2760
|
-
).run(namespace, key, JSON.stringify(value));
|
|
2761
|
-
}
|
|
2762
|
-
async delete(namespace, key) {
|
|
2763
|
-
this.db.prepare("DELETE FROM kv_store WHERE namespace = ? AND key = ?").run(namespace, key);
|
|
2764
|
-
}
|
|
2765
|
-
async list(namespace, prefix) {
|
|
2766
|
-
const rows = prefix ? this.db.prepare(
|
|
2767
|
-
"SELECT key, value FROM kv_store WHERE namespace = ? AND key LIKE ?"
|
|
2768
|
-
).all(namespace, `${prefix}%`) : this.db.prepare("SELECT key, value FROM kv_store WHERE namespace = ?").all(namespace);
|
|
2769
|
-
return rows.map((row) => ({
|
|
2770
|
-
key: row.key,
|
|
2771
|
-
value: JSON.parse(row.value)
|
|
2772
|
-
}));
|
|
2773
|
-
}
|
|
2774
|
-
async close() {
|
|
2775
|
-
this.db.close();
|
|
2776
|
-
}
|
|
2777
|
-
};
|
|
2778
|
-
|
|
2779
|
-
// src/storage/postgres.ts
|
|
2780
|
-
import { createRequire as createRequire9 } from "module";
|
|
2781
|
-
var _require9 = createRequire9(import.meta.url);
|
|
2782
|
-
var PostgresStorage = class {
|
|
2783
|
-
pool;
|
|
2784
|
-
constructor(connectionString) {
|
|
2785
|
-
try {
|
|
2786
|
-
const { Pool } = _require9("pg");
|
|
2787
|
-
this.pool = new Pool({ connectionString });
|
|
2788
|
-
} catch {
|
|
2789
|
-
throw new Error(
|
|
2790
|
-
"pg is required for PostgresStorage. Install it: npm install pg"
|
|
2791
|
-
);
|
|
2792
|
-
}
|
|
2793
|
-
}
|
|
2794
|
-
async initialize() {
|
|
2795
|
-
await this.pool.query(`
|
|
2796
|
-
CREATE TABLE IF NOT EXISTS kv_store (
|
|
2797
|
-
namespace TEXT NOT NULL,
|
|
2798
|
-
key TEXT NOT NULL,
|
|
2799
|
-
value JSONB NOT NULL,
|
|
2800
|
-
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
2801
|
-
PRIMARY KEY (namespace, key)
|
|
2802
|
-
)
|
|
2803
|
-
`);
|
|
2804
|
-
}
|
|
2805
|
-
async get(namespace, key) {
|
|
2806
|
-
const result = await this.pool.query(
|
|
2807
|
-
"SELECT value FROM kv_store WHERE namespace = $1 AND key = $2",
|
|
2808
|
-
[namespace, key]
|
|
2809
|
-
);
|
|
2810
|
-
if (result.rows.length === 0) return null;
|
|
2811
|
-
return result.rows[0].value;
|
|
2812
|
-
}
|
|
2813
|
-
async set(namespace, key, value) {
|
|
2814
|
-
await this.pool.query(
|
|
2815
|
-
`INSERT INTO kv_store (namespace, key, value, updated_at)
|
|
2816
|
-
VALUES ($1, $2, $3, NOW())
|
|
2817
|
-
ON CONFLICT (namespace, key)
|
|
2818
|
-
DO UPDATE SET value = EXCLUDED.value, updated_at = NOW()`,
|
|
2819
|
-
[namespace, key, JSON.stringify(value)]
|
|
2820
|
-
);
|
|
2821
|
-
}
|
|
2822
|
-
async delete(namespace, key) {
|
|
2823
|
-
await this.pool.query(
|
|
2824
|
-
"DELETE FROM kv_store WHERE namespace = $1 AND key = $2",
|
|
2825
|
-
[namespace, key]
|
|
2826
|
-
);
|
|
2827
|
-
}
|
|
2828
|
-
async list(namespace, prefix) {
|
|
2829
|
-
const result = prefix ? await this.pool.query(
|
|
2830
|
-
"SELECT key, value FROM kv_store WHERE namespace = $1 AND key LIKE $2",
|
|
2831
|
-
[namespace, `${prefix}%`]
|
|
2832
|
-
) : await this.pool.query(
|
|
2833
|
-
"SELECT key, value FROM kv_store WHERE namespace = $1",
|
|
2834
|
-
[namespace]
|
|
2835
|
-
);
|
|
2836
|
-
return result.rows.map((row) => ({
|
|
2837
|
-
key: row.key,
|
|
2838
|
-
value: row.value
|
|
2839
|
-
}));
|
|
2840
|
-
}
|
|
2841
|
-
async close() {
|
|
2842
|
-
await this.pool.end();
|
|
2843
|
-
}
|
|
2844
|
-
};
|
|
2845
|
-
|
|
2846
|
-
// src/storage/mongodb.ts
|
|
2847
|
-
import { createRequire as createRequire10 } from "module";
|
|
2848
|
-
var _require10 = createRequire10(import.meta.url);
|
|
2849
|
-
var MongoDBStorage = class {
|
|
2850
|
-
constructor(uri, dbName = "radaros", collectionName = "kv_store") {
|
|
2851
|
-
this.uri = uri;
|
|
2852
|
-
this.dbName = dbName;
|
|
2853
|
-
this.collectionName = collectionName;
|
|
2854
|
-
try {
|
|
2855
|
-
const { MongoClient } = _require10("mongodb");
|
|
2856
|
-
this.client = new MongoClient(uri);
|
|
2857
|
-
} catch {
|
|
2858
|
-
throw new Error(
|
|
2859
|
-
"mongodb is required for MongoDBStorage. Install it: npm install mongodb"
|
|
2860
|
-
);
|
|
2861
|
-
}
|
|
2862
|
-
}
|
|
2863
|
-
client;
|
|
2864
|
-
db;
|
|
2865
|
-
collection;
|
|
2866
|
-
async initialize() {
|
|
2867
|
-
await this.client.connect();
|
|
2868
|
-
this.db = this.client.db(this.dbName);
|
|
2869
|
-
this.collection = this.db.collection(this.collectionName);
|
|
2870
|
-
await this.collection.createIndex(
|
|
2871
|
-
{ namespace: 1, key: 1 },
|
|
2872
|
-
{ unique: true }
|
|
2873
|
-
);
|
|
2874
|
-
}
|
|
2875
|
-
async get(namespace, key) {
|
|
2876
|
-
const doc = await this.collection.findOne({ namespace, key });
|
|
2877
|
-
if (!doc) return null;
|
|
2878
|
-
return doc.value;
|
|
2879
|
-
}
|
|
2880
|
-
async set(namespace, key, value) {
|
|
2881
|
-
await this.collection.updateOne(
|
|
2882
|
-
{ namespace, key },
|
|
2883
|
-
{ $set: { value, updatedAt: /* @__PURE__ */ new Date() } },
|
|
2884
|
-
{ upsert: true }
|
|
2885
|
-
);
|
|
2886
|
-
}
|
|
2887
|
-
async delete(namespace, key) {
|
|
2888
|
-
await this.collection.deleteOne({ namespace, key });
|
|
2889
|
-
}
|
|
2890
|
-
async list(namespace, prefix) {
|
|
2891
|
-
const filter = { namespace };
|
|
2892
|
-
if (prefix) {
|
|
2893
|
-
filter.key = { $regex: `^${prefix}` };
|
|
2894
|
-
}
|
|
2895
|
-
const docs = await this.collection.find(filter).toArray();
|
|
2896
|
-
return docs.map((doc) => ({ key: doc.key, value: doc.value }));
|
|
2897
|
-
}
|
|
2898
|
-
async close() {
|
|
2899
|
-
await this.client.close();
|
|
2900
|
-
}
|
|
2901
|
-
};
|
|
2902
|
-
|
|
2903
|
-
// src/vector/base.ts
|
|
2904
|
-
var BaseVectorStore = class {
|
|
2905
|
-
constructor(embedder) {
|
|
2906
|
-
this.embedder = embedder;
|
|
2907
|
-
}
|
|
2908
|
-
async ensureEmbedding(doc) {
|
|
2909
|
-
if (doc.embedding) return doc.embedding;
|
|
2910
|
-
if (!this.embedder) {
|
|
2911
|
-
throw new Error(
|
|
2912
|
-
"No embedding provided on document and no EmbeddingProvider configured"
|
|
2913
|
-
);
|
|
2914
|
-
}
|
|
2915
|
-
return this.embedder.embed(doc.content);
|
|
2916
|
-
}
|
|
2917
|
-
async ensureQueryVector(query) {
|
|
2918
|
-
if (Array.isArray(query)) return query;
|
|
2919
|
-
if (!this.embedder) {
|
|
2920
|
-
throw new Error(
|
|
2921
|
-
"String query requires an EmbeddingProvider to be configured"
|
|
2922
|
-
);
|
|
2923
|
-
}
|
|
2924
|
-
return this.embedder.embed(query);
|
|
2925
|
-
}
|
|
2926
|
-
};
|
|
2927
|
-
|
|
2928
|
-
// src/vector/in-memory.ts
|
|
2929
|
-
var InMemoryVectorStore = class extends BaseVectorStore {
|
|
2930
|
-
collections = /* @__PURE__ */ new Map();
|
|
2931
|
-
constructor(embedder) {
|
|
2932
|
-
super(embedder);
|
|
2933
|
-
}
|
|
2934
|
-
async initialize() {
|
|
2935
|
-
}
|
|
2936
|
-
getCol(collection) {
|
|
2937
|
-
let col = this.collections.get(collection);
|
|
2938
|
-
if (!col) {
|
|
2939
|
-
col = /* @__PURE__ */ new Map();
|
|
2940
|
-
this.collections.set(collection, col);
|
|
2941
|
-
}
|
|
2942
|
-
return col;
|
|
2943
|
-
}
|
|
2944
|
-
async upsert(collection, doc) {
|
|
2945
|
-
const embedding = await this.ensureEmbedding(doc);
|
|
2946
|
-
this.getCol(collection).set(doc.id, {
|
|
2947
|
-
id: doc.id,
|
|
2948
|
-
content: doc.content,
|
|
2949
|
-
embedding,
|
|
2950
|
-
metadata: doc.metadata ?? {}
|
|
2951
|
-
});
|
|
2952
|
-
}
|
|
2953
|
-
async upsertBatch(collection, docs) {
|
|
2954
|
-
for (const doc of docs) {
|
|
2955
|
-
await this.upsert(collection, doc);
|
|
2956
|
-
}
|
|
2957
|
-
}
|
|
2958
|
-
async search(collection, query, options) {
|
|
2959
|
-
const vec = await this.ensureQueryVector(query);
|
|
2960
|
-
const topK = options?.topK ?? 10;
|
|
2961
|
-
const col = this.getCol(collection);
|
|
2962
|
-
const scored = [];
|
|
2963
|
-
for (const doc of col.values()) {
|
|
2964
|
-
const score = this.cosineSimilarity(vec, doc.embedding);
|
|
2965
|
-
if (options?.minScore != null && score < options.minScore) continue;
|
|
2966
|
-
if (options?.filter) {
|
|
2967
|
-
let match = true;
|
|
2968
|
-
for (const [k, v] of Object.entries(options.filter)) {
|
|
2969
|
-
if (doc.metadata[k] !== v) {
|
|
2970
|
-
match = false;
|
|
2971
|
-
break;
|
|
2972
|
-
}
|
|
2973
|
-
}
|
|
2974
|
-
if (!match) continue;
|
|
2975
|
-
}
|
|
2976
|
-
scored.push({
|
|
2977
|
-
id: doc.id,
|
|
2978
|
-
content: doc.content,
|
|
2979
|
-
score,
|
|
2980
|
-
metadata: doc.metadata
|
|
2981
|
-
});
|
|
2982
|
-
}
|
|
2983
|
-
scored.sort((a, b) => b.score - a.score);
|
|
2984
|
-
return scored.slice(0, topK);
|
|
2985
|
-
}
|
|
2986
|
-
cosineSimilarity(a, b) {
|
|
2987
|
-
let dot = 0;
|
|
2988
|
-
let normA = 0;
|
|
2989
|
-
let normB = 0;
|
|
2990
|
-
for (let i = 0; i < a.length; i++) {
|
|
2991
|
-
dot += a[i] * b[i];
|
|
2992
|
-
normA += a[i] * a[i];
|
|
2993
|
-
normB += b[i] * b[i];
|
|
2994
|
-
}
|
|
2995
|
-
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
2996
|
-
return denom === 0 ? 0 : dot / denom;
|
|
2997
|
-
}
|
|
2998
|
-
async delete(collection, id) {
|
|
2999
|
-
this.getCol(collection).delete(id);
|
|
3000
|
-
}
|
|
3001
|
-
async get(collection, id) {
|
|
3002
|
-
const doc = this.getCol(collection).get(id);
|
|
3003
|
-
if (!doc) return null;
|
|
3004
|
-
return { id: doc.id, content: doc.content, metadata: doc.metadata };
|
|
3005
|
-
}
|
|
3006
|
-
async dropCollection(collection) {
|
|
3007
|
-
this.collections.delete(collection);
|
|
3008
|
-
}
|
|
3009
|
-
async close() {
|
|
3010
|
-
this.collections.clear();
|
|
3011
|
-
}
|
|
3012
|
-
};
|
|
3013
|
-
|
|
3014
|
-
// src/vector/pgvector.ts
|
|
3015
|
-
import { createRequire as createRequire11 } from "module";
|
|
3016
|
-
var _require11 = createRequire11(import.meta.url);
|
|
3017
|
-
var PgVectorStore = class extends BaseVectorStore {
|
|
3018
|
-
pool;
|
|
3019
|
-
dimensions;
|
|
3020
|
-
initializedCollections = /* @__PURE__ */ new Set();
|
|
3021
|
-
constructor(config, embedder) {
|
|
3022
|
-
super(embedder);
|
|
3023
|
-
this.dimensions = config.dimensions ?? embedder?.dimensions ?? 1536;
|
|
3024
|
-
try {
|
|
3025
|
-
const { Pool } = _require11("pg");
|
|
3026
|
-
this.pool = new Pool({ connectionString: config.connectionString });
|
|
3027
|
-
} catch {
|
|
3028
|
-
throw new Error(
|
|
3029
|
-
"pg is required for PgVectorStore. Install it: npm install pg"
|
|
3030
|
-
);
|
|
3031
|
-
}
|
|
3032
|
-
}
|
|
3033
|
-
async initialize() {
|
|
3034
|
-
await this.pool.query("CREATE EXTENSION IF NOT EXISTS vector");
|
|
3035
|
-
}
|
|
3036
|
-
async ensureCollection(collection) {
|
|
3037
|
-
if (this.initializedCollections.has(collection)) return;
|
|
3038
|
-
const table = this.sanitize(collection);
|
|
3039
|
-
await this.pool.query(`
|
|
3040
|
-
CREATE TABLE IF NOT EXISTS ${table} (
|
|
3041
|
-
id TEXT PRIMARY KEY,
|
|
3042
|
-
content TEXT NOT NULL,
|
|
3043
|
-
embedding vector(${this.dimensions}),
|
|
3044
|
-
metadata JSONB DEFAULT '{}'::jsonb,
|
|
3045
|
-
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
3046
|
-
)
|
|
3047
|
-
`);
|
|
3048
|
-
await this.pool.query(`
|
|
3049
|
-
CREATE INDEX IF NOT EXISTS ${table}_embedding_idx
|
|
3050
|
-
ON ${table} USING ivfflat (embedding vector_cosine_ops)
|
|
3051
|
-
WITH (lists = 100)
|
|
3052
|
-
`).catch(() => {
|
|
3053
|
-
});
|
|
3054
|
-
this.initializedCollections.add(collection);
|
|
3055
|
-
}
|
|
3056
|
-
sanitize(name) {
|
|
3057
|
-
return name.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
3058
|
-
}
|
|
3059
|
-
toSql(vec) {
|
|
3060
|
-
return `[${vec.join(",")}]`;
|
|
3061
|
-
}
|
|
3062
|
-
async upsert(collection, doc) {
|
|
3063
|
-
await this.ensureCollection(collection);
|
|
3064
|
-
const embedding = await this.ensureEmbedding(doc);
|
|
3065
|
-
const table = this.sanitize(collection);
|
|
3066
|
-
await this.pool.query(
|
|
3067
|
-
`INSERT INTO ${table} (id, content, embedding, metadata)
|
|
3068
|
-
VALUES ($1, $2, $3::vector, $4::jsonb)
|
|
3069
|
-
ON CONFLICT (id)
|
|
3070
|
-
DO UPDATE SET content = EXCLUDED.content,
|
|
3071
|
-
embedding = EXCLUDED.embedding,
|
|
3072
|
-
metadata = EXCLUDED.metadata`,
|
|
3073
|
-
[doc.id, doc.content, this.toSql(embedding), JSON.stringify(doc.metadata ?? {})]
|
|
3074
|
-
);
|
|
3075
|
-
}
|
|
3076
|
-
async upsertBatch(collection, docs) {
|
|
3077
|
-
for (const doc of docs) {
|
|
3078
|
-
await this.upsert(collection, doc);
|
|
3079
|
-
}
|
|
3080
|
-
}
|
|
3081
|
-
async search(collection, query, options) {
|
|
3082
|
-
await this.ensureCollection(collection);
|
|
3083
|
-
const vec = await this.ensureQueryVector(query);
|
|
3084
|
-
const topK = options?.topK ?? 10;
|
|
3085
|
-
const table = this.sanitize(collection);
|
|
3086
|
-
let filterClause = "";
|
|
3087
|
-
const params = [this.toSql(vec), topK];
|
|
3088
|
-
if (options?.filter) {
|
|
3089
|
-
const conditions = Object.entries(options.filter).map(([k, v], i) => {
|
|
3090
|
-
params.push(JSON.stringify(v));
|
|
3091
|
-
return `metadata->>'${k.replace(/'/g, "''")}' = $${i + 3}`;
|
|
3092
|
-
});
|
|
3093
|
-
if (conditions.length > 0) {
|
|
3094
|
-
filterClause = `WHERE ${conditions.join(" AND ")}`;
|
|
3095
|
-
}
|
|
3096
|
-
}
|
|
3097
|
-
const result = await this.pool.query(
|
|
3098
|
-
`SELECT id, content, metadata,
|
|
3099
|
-
1 - (embedding <=> $1::vector) AS score
|
|
3100
|
-
FROM ${table}
|
|
3101
|
-
${filterClause}
|
|
3102
|
-
ORDER BY embedding <=> $1::vector
|
|
3103
|
-
LIMIT $2`,
|
|
3104
|
-
params
|
|
3105
|
-
);
|
|
3106
|
-
let rows = result.rows;
|
|
3107
|
-
if (options?.minScore != null) {
|
|
3108
|
-
rows = rows.filter((r) => r.score >= options.minScore);
|
|
3109
|
-
}
|
|
3110
|
-
return rows.map((r) => ({
|
|
3111
|
-
id: r.id,
|
|
3112
|
-
content: r.content,
|
|
3113
|
-
score: r.score,
|
|
3114
|
-
metadata: r.metadata
|
|
3115
|
-
}));
|
|
3116
|
-
}
|
|
3117
|
-
async delete(collection, id) {
|
|
3118
|
-
await this.ensureCollection(collection);
|
|
3119
|
-
const table = this.sanitize(collection);
|
|
3120
|
-
await this.pool.query(`DELETE FROM ${table} WHERE id = $1`, [id]);
|
|
3121
|
-
}
|
|
3122
|
-
async get(collection, id) {
|
|
3123
|
-
await this.ensureCollection(collection);
|
|
3124
|
-
const table = this.sanitize(collection);
|
|
3125
|
-
const result = await this.pool.query(
|
|
3126
|
-
`SELECT id, content, metadata FROM ${table} WHERE id = $1`,
|
|
3127
|
-
[id]
|
|
3128
|
-
);
|
|
3129
|
-
if (result.rows.length === 0) return null;
|
|
3130
|
-
const row = result.rows[0];
|
|
3131
|
-
return { id: row.id, content: row.content, metadata: row.metadata };
|
|
3132
|
-
}
|
|
3133
|
-
async dropCollection(collection) {
|
|
3134
|
-
const table = this.sanitize(collection);
|
|
3135
|
-
await this.pool.query(`DROP TABLE IF EXISTS ${table}`);
|
|
3136
|
-
this.initializedCollections.delete(collection);
|
|
3137
|
-
}
|
|
3138
|
-
async close() {
|
|
3139
|
-
await this.pool.end();
|
|
3140
|
-
}
|
|
3141
|
-
};
|
|
3142
|
-
|
|
3143
|
-
// src/vector/qdrant.ts
|
|
3144
|
-
import { createRequire as createRequire12 } from "module";
|
|
3145
|
-
import { createHash } from "crypto";
|
|
3146
|
-
var _require12 = createRequire12(import.meta.url);
|
|
3147
|
-
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
3148
|
-
function stringToUUID(str) {
|
|
3149
|
-
const hex = createHash("md5").update(str).digest("hex");
|
|
3150
|
-
return [
|
|
3151
|
-
hex.slice(0, 8),
|
|
3152
|
-
hex.slice(8, 12),
|
|
3153
|
-
"4" + hex.slice(13, 16),
|
|
3154
|
-
"8" + hex.slice(17, 20),
|
|
3155
|
-
hex.slice(20, 32)
|
|
3156
|
-
].join("-");
|
|
3157
|
-
}
|
|
3158
|
-
function toQdrantId(id) {
|
|
3159
|
-
if (/^\d+$/.test(id)) return Number(id);
|
|
3160
|
-
if (UUID_RE.test(id)) return id;
|
|
3161
|
-
return stringToUUID(id);
|
|
3162
|
-
}
|
|
3163
|
-
var QdrantVectorStore = class extends BaseVectorStore {
|
|
3164
|
-
client;
|
|
3165
|
-
dimensions;
|
|
3166
|
-
initializedCollections = /* @__PURE__ */ new Set();
|
|
3167
|
-
constructor(config = {}, embedder) {
|
|
3168
|
-
super(embedder);
|
|
3169
|
-
this.dimensions = config.dimensions ?? embedder?.dimensions ?? 1536;
|
|
3170
|
-
try {
|
|
3171
|
-
const { QdrantClient } = _require12("@qdrant/js-client-rest");
|
|
3172
|
-
this.client = new QdrantClient({
|
|
3173
|
-
url: config.url ?? "http://localhost:6333",
|
|
3174
|
-
apiKey: config.apiKey,
|
|
3175
|
-
checkCompatibility: config.checkCompatibility ?? false
|
|
3176
|
-
});
|
|
3177
|
-
} catch {
|
|
3178
|
-
throw new Error(
|
|
3179
|
-
"@qdrant/js-client-rest is required for QdrantVectorStore. Install it: npm install @qdrant/js-client-rest"
|
|
3180
|
-
);
|
|
3181
|
-
}
|
|
3182
|
-
}
|
|
3183
|
-
async initialize() {
|
|
3184
|
-
}
|
|
3185
|
-
async ensureCollection(collection) {
|
|
3186
|
-
if (this.initializedCollections.has(collection)) return;
|
|
3187
|
-
try {
|
|
3188
|
-
await this.client.getCollection(collection);
|
|
3189
|
-
} catch {
|
|
3190
|
-
await this.client.createCollection(collection, {
|
|
3191
|
-
vectors: {
|
|
3192
|
-
size: this.dimensions,
|
|
3193
|
-
distance: "Cosine"
|
|
3194
|
-
}
|
|
3195
|
-
});
|
|
3196
|
-
}
|
|
3197
|
-
this.initializedCollections.add(collection);
|
|
3198
|
-
}
|
|
3199
|
-
async upsert(collection, doc) {
|
|
3200
|
-
await this.ensureCollection(collection);
|
|
3201
|
-
const embedding = await this.ensureEmbedding(doc);
|
|
3202
|
-
await this.client.upsert(collection, {
|
|
3203
|
-
wait: true,
|
|
3204
|
-
points: [
|
|
3205
|
-
{
|
|
3206
|
-
id: toQdrantId(doc.id),
|
|
3207
|
-
vector: embedding,
|
|
3208
|
-
payload: {
|
|
3209
|
-
_originalId: doc.id,
|
|
3210
|
-
content: doc.content,
|
|
3211
|
-
...doc.metadata ?? {}
|
|
3212
|
-
}
|
|
3213
|
-
}
|
|
3214
|
-
]
|
|
3215
|
-
});
|
|
3216
|
-
}
|
|
3217
|
-
async upsertBatch(collection, docs) {
|
|
3218
|
-
await this.ensureCollection(collection);
|
|
3219
|
-
const points = await Promise.all(
|
|
3220
|
-
docs.map(async (doc) => {
|
|
3221
|
-
const embedding = await this.ensureEmbedding(doc);
|
|
3222
|
-
return {
|
|
3223
|
-
id: toQdrantId(doc.id),
|
|
3224
|
-
vector: embedding,
|
|
3225
|
-
payload: {
|
|
3226
|
-
_originalId: doc.id,
|
|
3227
|
-
content: doc.content,
|
|
3228
|
-
...doc.metadata ?? {}
|
|
3229
|
-
}
|
|
3230
|
-
};
|
|
3231
|
-
})
|
|
3232
|
-
);
|
|
3233
|
-
await this.client.upsert(collection, { wait: true, points });
|
|
3234
|
-
}
|
|
3235
|
-
async search(collection, query, options) {
|
|
3236
|
-
await this.ensureCollection(collection);
|
|
3237
|
-
const vec = await this.ensureQueryVector(query);
|
|
3238
|
-
const topK = options?.topK ?? 10;
|
|
3239
|
-
const searchParams = {
|
|
3240
|
-
vector: vec,
|
|
3241
|
-
limit: topK,
|
|
3242
|
-
with_payload: true
|
|
3243
|
-
};
|
|
3244
|
-
if (options?.filter) {
|
|
3245
|
-
searchParams.filter = {
|
|
3246
|
-
must: Object.entries(options.filter).map(([key, value]) => ({
|
|
3247
|
-
key,
|
|
3248
|
-
match: { value }
|
|
3249
|
-
}))
|
|
3250
|
-
};
|
|
3251
|
-
}
|
|
3252
|
-
if (options?.minScore != null) {
|
|
3253
|
-
searchParams.score_threshold = options.minScore;
|
|
3254
|
-
}
|
|
3255
|
-
const results = await this.client.search(collection, searchParams);
|
|
3256
|
-
return results.map((r) => {
|
|
3257
|
-
const { _originalId, content, ...rest } = r.payload ?? {};
|
|
3258
|
-
return {
|
|
3259
|
-
id: _originalId ?? String(r.id),
|
|
3260
|
-
content: content ?? "",
|
|
3261
|
-
score: r.score,
|
|
3262
|
-
metadata: rest
|
|
3263
|
-
};
|
|
3264
|
-
});
|
|
3265
|
-
}
|
|
3266
|
-
async delete(collection, id) {
|
|
3267
|
-
await this.ensureCollection(collection);
|
|
3268
|
-
await this.client.delete(collection, {
|
|
3269
|
-
wait: true,
|
|
3270
|
-
points: [toQdrantId(id)]
|
|
3271
|
-
});
|
|
3272
|
-
}
|
|
3273
|
-
async get(collection, id) {
|
|
3274
|
-
await this.ensureCollection(collection);
|
|
3275
|
-
try {
|
|
3276
|
-
const results = await this.client.retrieve(collection, {
|
|
3277
|
-
ids: [toQdrantId(id)],
|
|
3278
|
-
with_payload: true
|
|
3279
|
-
});
|
|
3280
|
-
if (!results || results.length === 0) return null;
|
|
3281
|
-
const point = results[0];
|
|
3282
|
-
const { _originalId, content, ...rest } = point.payload ?? {};
|
|
3283
|
-
return {
|
|
3284
|
-
id: _originalId ?? String(point.id),
|
|
3285
|
-
content: content ?? "",
|
|
3286
|
-
metadata: rest
|
|
3287
|
-
};
|
|
3288
|
-
} catch {
|
|
3289
|
-
return null;
|
|
3290
|
-
}
|
|
3291
|
-
}
|
|
3292
|
-
async dropCollection(collection) {
|
|
3293
|
-
try {
|
|
3294
|
-
await this.client.deleteCollection(collection);
|
|
3295
|
-
} catch {
|
|
3296
|
-
}
|
|
3297
|
-
this.initializedCollections.delete(collection);
|
|
3298
|
-
}
|
|
3299
|
-
async close() {
|
|
3300
|
-
}
|
|
3301
|
-
};
|
|
3302
|
-
|
|
3303
|
-
// src/vector/mongodb.ts
|
|
3304
|
-
import { createRequire as createRequire13 } from "module";
|
|
3305
|
-
var _require13 = createRequire13(import.meta.url);
|
|
3306
|
-
var MongoDBVectorStore = class extends BaseVectorStore {
|
|
3307
|
-
client;
|
|
3308
|
-
db;
|
|
3309
|
-
indexName;
|
|
3310
|
-
dbName;
|
|
3311
|
-
useAtlas = null;
|
|
3312
|
-
constructor(config, embedder) {
|
|
3313
|
-
super(embedder);
|
|
3314
|
-
this.indexName = config.indexName ?? "vector_index";
|
|
3315
|
-
this.dbName = config.dbName ?? "radaros_vectors";
|
|
3316
|
-
try {
|
|
3317
|
-
const { MongoClient } = _require13("mongodb");
|
|
3318
|
-
this.client = new MongoClient(config.uri);
|
|
3319
|
-
} catch {
|
|
3320
|
-
throw new Error(
|
|
3321
|
-
"mongodb is required for MongoDBVectorStore. Install it: npm install mongodb"
|
|
3322
|
-
);
|
|
3323
|
-
}
|
|
3324
|
-
}
|
|
3325
|
-
async initialize() {
|
|
3326
|
-
await this.client.connect();
|
|
3327
|
-
this.db = this.client.db(this.dbName);
|
|
3328
|
-
}
|
|
3329
|
-
col(collection) {
|
|
3330
|
-
return this.db.collection(collection);
|
|
3331
|
-
}
|
|
3332
|
-
async upsert(collection, doc) {
|
|
3333
|
-
const embedding = await this.ensureEmbedding(doc);
|
|
3334
|
-
await this.col(collection).updateOne(
|
|
3335
|
-
{ _id: doc.id },
|
|
3336
|
-
{
|
|
3337
|
-
$set: {
|
|
3338
|
-
content: doc.content,
|
|
3339
|
-
embedding,
|
|
3340
|
-
metadata: doc.metadata ?? {},
|
|
3341
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
3342
|
-
}
|
|
3343
|
-
},
|
|
3344
|
-
{ upsert: true }
|
|
3345
|
-
);
|
|
3346
|
-
}
|
|
3347
|
-
async upsertBatch(collection, docs) {
|
|
3348
|
-
const ops = await Promise.all(
|
|
3349
|
-
docs.map(async (doc) => {
|
|
3350
|
-
const embedding = await this.ensureEmbedding(doc);
|
|
3351
|
-
return {
|
|
3352
|
-
updateOne: {
|
|
3353
|
-
filter: { _id: doc.id },
|
|
3354
|
-
update: {
|
|
3355
|
-
$set: {
|
|
3356
|
-
content: doc.content,
|
|
3357
|
-
embedding,
|
|
3358
|
-
metadata: doc.metadata ?? {},
|
|
3359
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
3360
|
-
}
|
|
3361
|
-
},
|
|
3362
|
-
upsert: true
|
|
3363
|
-
}
|
|
3364
|
-
};
|
|
3365
|
-
})
|
|
3366
|
-
);
|
|
3367
|
-
if (ops.length > 0) {
|
|
3368
|
-
await this.col(collection).bulkWrite(ops);
|
|
3369
|
-
}
|
|
3370
|
-
}
|
|
3371
|
-
async search(collection, query, options) {
|
|
3372
|
-
const vec = await this.ensureQueryVector(query);
|
|
3373
|
-
if (this.useAtlas === true) {
|
|
3374
|
-
return this.atlasSearch(collection, vec, options);
|
|
3375
|
-
}
|
|
3376
|
-
if (this.useAtlas === false) {
|
|
3377
|
-
return this.localSearch(collection, vec, options);
|
|
3378
|
-
}
|
|
3379
|
-
try {
|
|
3380
|
-
const results = await this.atlasSearch(collection, vec, options);
|
|
3381
|
-
this.useAtlas = true;
|
|
3382
|
-
return results;
|
|
3383
|
-
} catch {
|
|
3384
|
-
this.useAtlas = false;
|
|
3385
|
-
return this.localSearch(collection, vec, options);
|
|
3386
|
-
}
|
|
3387
|
-
}
|
|
3388
|
-
async atlasSearch(collection, vec, options) {
|
|
3389
|
-
const topK = options?.topK ?? 10;
|
|
3390
|
-
const pipeline = [
|
|
3391
|
-
{
|
|
3392
|
-
$vectorSearch: {
|
|
3393
|
-
index: this.indexName,
|
|
3394
|
-
path: "embedding",
|
|
3395
|
-
queryVector: vec,
|
|
3396
|
-
numCandidates: topK * 10,
|
|
3397
|
-
limit: topK,
|
|
3398
|
-
...options?.filter ? { filter: this.buildFilter(options.filter) } : {}
|
|
3399
|
-
}
|
|
3400
|
-
},
|
|
3401
|
-
{
|
|
3402
|
-
$addFields: {
|
|
3403
|
-
score: { $meta: "vectorSearchScore" }
|
|
3404
|
-
}
|
|
3405
|
-
}
|
|
3406
|
-
];
|
|
3407
|
-
if (options?.minScore != null) {
|
|
3408
|
-
pipeline.push({ $match: { score: { $gte: options.minScore } } });
|
|
3409
|
-
}
|
|
3410
|
-
pipeline.push({
|
|
3411
|
-
$project: { _id: 1, content: 1, score: 1, metadata: 1 }
|
|
3412
|
-
});
|
|
3413
|
-
const results = await this.col(collection).aggregate(pipeline).toArray();
|
|
3414
|
-
return results.map((r) => ({
|
|
3415
|
-
id: String(r._id),
|
|
3416
|
-
content: r.content ?? "",
|
|
3417
|
-
score: r.score,
|
|
3418
|
-
metadata: r.metadata
|
|
3419
|
-
}));
|
|
3420
|
-
}
|
|
3421
|
-
async localSearch(collection, vec, options) {
|
|
3422
|
-
const topK = options?.topK ?? 10;
|
|
3423
|
-
const filter = {};
|
|
3424
|
-
if (options?.filter) {
|
|
3425
|
-
for (const [k, v] of Object.entries(options.filter)) {
|
|
3426
|
-
filter[`metadata.${k}`] = v;
|
|
3427
|
-
}
|
|
3428
|
-
}
|
|
3429
|
-
const docs = await this.col(collection).find(filter, { projection: { _id: 1, content: 1, embedding: 1, metadata: 1 } }).toArray();
|
|
3430
|
-
const scored = [];
|
|
3431
|
-
for (const doc of docs) {
|
|
3432
|
-
if (!doc.embedding) continue;
|
|
3433
|
-
const score = cosine(vec, doc.embedding);
|
|
3434
|
-
if (options?.minScore != null && score < options.minScore) continue;
|
|
3435
|
-
scored.push({
|
|
3436
|
-
id: String(doc._id),
|
|
3437
|
-
content: doc.content ?? "",
|
|
3438
|
-
score,
|
|
3439
|
-
metadata: doc.metadata
|
|
3440
|
-
});
|
|
3441
|
-
}
|
|
3442
|
-
scored.sort((a, b) => b.score - a.score);
|
|
3443
|
-
return scored.slice(0, topK);
|
|
3444
|
-
}
|
|
3445
|
-
buildFilter(filter) {
|
|
3446
|
-
const conditions = {};
|
|
3447
|
-
for (const [key, value] of Object.entries(filter)) {
|
|
3448
|
-
conditions[`metadata.${key}`] = value;
|
|
3449
|
-
}
|
|
3450
|
-
return conditions;
|
|
3451
|
-
}
|
|
3452
|
-
async delete(collection, id) {
|
|
3453
|
-
await this.col(collection).deleteOne({ _id: id });
|
|
3454
|
-
}
|
|
3455
|
-
async get(collection, id) {
|
|
3456
|
-
const doc = await this.col(collection).findOne({ _id: id });
|
|
3457
|
-
if (!doc) return null;
|
|
3458
|
-
return {
|
|
3459
|
-
id: String(doc._id),
|
|
3460
|
-
content: doc.content,
|
|
3461
|
-
metadata: doc.metadata
|
|
3462
|
-
};
|
|
3463
|
-
}
|
|
3464
|
-
async dropCollection(collection) {
|
|
3465
|
-
try {
|
|
3466
|
-
await this.col(collection).drop();
|
|
3467
|
-
} catch {
|
|
3468
|
-
}
|
|
3469
|
-
}
|
|
3470
|
-
async close() {
|
|
3471
|
-
await this.client.close();
|
|
3472
|
-
}
|
|
3473
|
-
};
|
|
3474
|
-
function cosine(a, b) {
|
|
3475
|
-
let dot = 0;
|
|
3476
|
-
let normA = 0;
|
|
3477
|
-
let normB = 0;
|
|
3478
|
-
for (let i = 0; i < a.length; i++) {
|
|
3479
|
-
dot += a[i] * b[i];
|
|
3480
|
-
normA += a[i] * a[i];
|
|
3481
|
-
normB += b[i] * b[i];
|
|
3482
|
-
}
|
|
3483
|
-
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
3484
|
-
return denom === 0 ? 0 : dot / denom;
|
|
3485
|
-
}
|
|
3486
|
-
|
|
3487
|
-
// src/vector/embeddings/openai.ts
|
|
3488
|
-
import { createRequire as createRequire14 } from "module";
|
|
3489
|
-
var _require14 = createRequire14(import.meta.url);
|
|
3490
|
-
var MODEL_DIMENSIONS = {
|
|
3491
|
-
"text-embedding-3-small": 1536,
|
|
3492
|
-
"text-embedding-3-large": 3072,
|
|
3493
|
-
"text-embedding-ada-002": 1536
|
|
3494
|
-
};
|
|
3495
|
-
var OpenAIEmbedding = class {
|
|
3496
|
-
dimensions;
|
|
3497
|
-
client;
|
|
3498
|
-
model;
|
|
3499
|
-
constructor(config = {}) {
|
|
3500
|
-
this.model = config.model ?? "text-embedding-3-small";
|
|
3501
|
-
this.dimensions = config.dimensions ?? MODEL_DIMENSIONS[this.model] ?? 1536;
|
|
3502
|
-
try {
|
|
3503
|
-
const mod = _require14("openai");
|
|
3504
|
-
const OpenAI = mod.default ?? mod;
|
|
3505
|
-
this.client = new OpenAI({
|
|
3506
|
-
apiKey: config.apiKey ?? process.env.OPENAI_API_KEY,
|
|
3507
|
-
baseURL: config.baseURL
|
|
3508
|
-
});
|
|
3509
|
-
} catch {
|
|
3510
|
-
throw new Error(
|
|
3511
|
-
"openai package is required for OpenAIEmbedding. Install it: npm install openai"
|
|
3512
|
-
);
|
|
3513
|
-
}
|
|
3514
|
-
}
|
|
3515
|
-
async embed(text) {
|
|
3516
|
-
const response = await this.client.embeddings.create({
|
|
3517
|
-
model: this.model,
|
|
3518
|
-
input: text,
|
|
3519
|
-
...this.dimensions !== MODEL_DIMENSIONS[this.model] ? { dimensions: this.dimensions } : {}
|
|
3520
|
-
});
|
|
3521
|
-
return response.data[0].embedding;
|
|
3522
|
-
}
|
|
3523
|
-
async embedBatch(texts) {
|
|
3524
|
-
const response = await this.client.embeddings.create({
|
|
3525
|
-
model: this.model,
|
|
3526
|
-
input: texts,
|
|
3527
|
-
...this.dimensions !== MODEL_DIMENSIONS[this.model] ? { dimensions: this.dimensions } : {}
|
|
3528
|
-
});
|
|
3529
|
-
return response.data.sort((a, b) => a.index - b.index).map((d) => d.embedding);
|
|
3530
|
-
}
|
|
3531
|
-
};
|
|
3532
|
-
|
|
3533
|
-
// src/vector/embeddings/google.ts
|
|
3534
|
-
import { createRequire as createRequire15 } from "module";
|
|
3535
|
-
var _require15 = createRequire15(import.meta.url);
|
|
3536
|
-
var MODEL_DIMENSIONS2 = {
|
|
3537
|
-
"text-embedding-004": 768,
|
|
3538
|
-
"embedding-001": 768
|
|
3539
|
-
};
|
|
3540
|
-
var GoogleEmbedding = class {
|
|
3541
|
-
dimensions;
|
|
3542
|
-
ai;
|
|
3543
|
-
model;
|
|
3544
|
-
constructor(config = {}) {
|
|
3545
|
-
this.model = config.model ?? "text-embedding-004";
|
|
3546
|
-
this.dimensions = config.dimensions ?? MODEL_DIMENSIONS2[this.model] ?? 768;
|
|
3547
|
-
try {
|
|
3548
|
-
const { GoogleGenAI } = _require15("@google/genai");
|
|
3549
|
-
this.ai = new GoogleGenAI({
|
|
3550
|
-
apiKey: config.apiKey ?? process.env.GOOGLE_API_KEY
|
|
3551
|
-
});
|
|
3552
|
-
} catch {
|
|
3553
|
-
throw new Error(
|
|
3554
|
-
"@google/genai is required for GoogleEmbedding. Install it: npm install @google/genai"
|
|
3555
|
-
);
|
|
3556
|
-
}
|
|
3557
|
-
}
|
|
3558
|
-
async embed(text) {
|
|
3559
|
-
const result = await this.ai.models.embedContent({
|
|
3560
|
-
model: this.model,
|
|
3561
|
-
contents: text,
|
|
3562
|
-
...this.dimensions !== MODEL_DIMENSIONS2[this.model] ? { config: { outputDimensionality: this.dimensions } } : {}
|
|
3563
|
-
});
|
|
3564
|
-
return result.embeddings[0].values;
|
|
3565
|
-
}
|
|
3566
|
-
async embedBatch(texts) {
|
|
3567
|
-
const results = await Promise.all(
|
|
3568
|
-
texts.map(
|
|
3569
|
-
(text) => this.ai.models.embedContent({
|
|
3570
|
-
model: this.model,
|
|
3571
|
-
contents: text,
|
|
3572
|
-
...this.dimensions !== MODEL_DIMENSIONS2[this.model] ? { config: { outputDimensionality: this.dimensions } } : {}
|
|
3573
|
-
})
|
|
3574
|
-
)
|
|
3575
|
-
);
|
|
3576
|
-
return results.map((r) => r.embeddings[0].values);
|
|
3577
|
-
}
|
|
3578
|
-
};
|
|
3579
|
-
|
|
3580
|
-
// src/knowledge/knowledge-base.ts
|
|
3581
|
-
import { z } from "zod";
|
|
3582
|
-
var KnowledgeBase = class {
|
|
3583
|
-
name;
|
|
3584
|
-
collection;
|
|
3585
|
-
store;
|
|
3586
|
-
initialized = false;
|
|
3587
|
-
constructor(config) {
|
|
3588
|
-
this.name = config.name;
|
|
3589
|
-
this.store = config.vectorStore;
|
|
3590
|
-
this.collection = config.collection ?? config.name.toLowerCase().replace(/\s+/g, "_");
|
|
3591
|
-
}
|
|
3592
|
-
async initialize() {
|
|
3593
|
-
if (this.initialized) return;
|
|
3594
|
-
await this.store.initialize();
|
|
3595
|
-
this.initialized = true;
|
|
3596
|
-
}
|
|
3597
|
-
async add(doc) {
|
|
3598
|
-
await this.ensureInit();
|
|
3599
|
-
await this.store.upsert(this.collection, doc);
|
|
3600
|
-
}
|
|
3601
|
-
async addDocuments(docs) {
|
|
3602
|
-
await this.ensureInit();
|
|
3603
|
-
await this.store.upsertBatch(this.collection, docs);
|
|
3604
|
-
}
|
|
3605
|
-
async search(query, options) {
|
|
3606
|
-
await this.ensureInit();
|
|
3607
|
-
return this.store.search(this.collection, query, options);
|
|
3608
|
-
}
|
|
3609
|
-
async get(id) {
|
|
3610
|
-
await this.ensureInit();
|
|
3611
|
-
return this.store.get(this.collection, id);
|
|
3612
|
-
}
|
|
3613
|
-
async delete(id) {
|
|
3614
|
-
await this.ensureInit();
|
|
3615
|
-
await this.store.delete(this.collection, id);
|
|
3616
|
-
}
|
|
3617
|
-
async clear() {
|
|
3618
|
-
await this.store.dropCollection(this.collection);
|
|
3619
|
-
}
|
|
3620
|
-
async close() {
|
|
3621
|
-
await this.store.close();
|
|
3622
|
-
}
|
|
3623
|
-
/**
|
|
3624
|
-
* Returns a ToolDef that an Agent can use to search this knowledge base.
|
|
3625
|
-
* Plug the result directly into `Agent({ tools: [kb.asTool()] })`.
|
|
3626
|
-
*/
|
|
3627
|
-
asTool(config = {}) {
|
|
3628
|
-
const topK = config.topK ?? 5;
|
|
3629
|
-
const minScore = config.minScore;
|
|
3630
|
-
const filter = config.filter;
|
|
3631
|
-
const toolName = config.toolName ?? `search_${this.collection}`;
|
|
3632
|
-
const description = config.description ?? `Search the "${this.name}" knowledge base for relevant information. Use this before answering questions related to ${this.name}.`;
|
|
3633
|
-
const formatResults = config.formatResults ?? defaultFormatResults;
|
|
3634
|
-
const kb = this;
|
|
3635
|
-
return {
|
|
3636
|
-
name: toolName,
|
|
3637
|
-
description,
|
|
3638
|
-
parameters: z.object({
|
|
3639
|
-
query: z.string().describe("Search query to find relevant documents")
|
|
3640
|
-
}),
|
|
3641
|
-
execute: async (args) => {
|
|
3642
|
-
const results = await kb.search(args.query, {
|
|
3643
|
-
topK,
|
|
3644
|
-
minScore,
|
|
3645
|
-
filter
|
|
3646
|
-
});
|
|
3647
|
-
if (results.length === 0) {
|
|
3648
|
-
return "No relevant documents found in the knowledge base.";
|
|
3649
|
-
}
|
|
3650
|
-
return formatResults(results);
|
|
3651
|
-
}
|
|
3652
|
-
};
|
|
3653
|
-
}
|
|
3654
|
-
async ensureInit() {
|
|
3655
|
-
if (!this.initialized) await this.initialize();
|
|
3656
|
-
}
|
|
3657
|
-
};
|
|
3658
|
-
function defaultFormatResults(results) {
|
|
3659
|
-
const lines = results.map((r, i) => {
|
|
3660
|
-
const meta = r.metadata ? Object.entries(r.metadata).filter(([, v]) => v !== void 0).map(([k, v]) => `${k}: ${v}`).join(", ") : "";
|
|
3661
|
-
const metaStr = meta ? ` | ${meta}` : "";
|
|
3662
|
-
return `[${i + 1}] (score: ${r.score.toFixed(3)}${metaStr})
|
|
3663
|
-
${r.content}`;
|
|
3664
|
-
});
|
|
3665
|
-
return `Found ${results.length} relevant document(s):
|
|
3666
|
-
|
|
3667
|
-
${lines.join("\n\n")}`;
|
|
3668
|
-
}
|
|
3669
|
-
|
|
3670
|
-
// src/memory/memory.ts
|
|
3671
|
-
var SHORT_TERM_NS = "memory:short";
|
|
3672
|
-
var LONG_TERM_NS = "memory:long";
|
|
3673
|
-
var Memory = class {
|
|
3674
|
-
storage;
|
|
3675
|
-
maxShortTermMessages;
|
|
3676
|
-
enableLongTerm;
|
|
3677
|
-
constructor(config) {
|
|
3678
|
-
this.storage = config?.storage ?? new InMemoryStorage();
|
|
3679
|
-
this.maxShortTermMessages = config?.maxShortTermMessages ?? 50;
|
|
3680
|
-
this.enableLongTerm = config?.enableLongTerm ?? false;
|
|
3681
|
-
}
|
|
3682
|
-
async addMessages(sessionId, messages) {
|
|
3683
|
-
const existing = await this.storage.get(SHORT_TERM_NS, sessionId) ?? [];
|
|
3684
|
-
const updated = [...existing, ...messages];
|
|
3685
|
-
if (updated.length > this.maxShortTermMessages) {
|
|
3686
|
-
const overflow = updated.splice(
|
|
3687
|
-
0,
|
|
3688
|
-
updated.length - this.maxShortTermMessages
|
|
3689
|
-
);
|
|
3690
|
-
if (this.enableLongTerm && overflow.length > 0) {
|
|
3691
|
-
await this.summarizeAndStore(sessionId, overflow);
|
|
3692
|
-
}
|
|
3693
|
-
}
|
|
3694
|
-
await this.storage.set(SHORT_TERM_NS, sessionId, updated);
|
|
3695
|
-
}
|
|
3696
|
-
async getMessages(sessionId) {
|
|
3697
|
-
return await this.storage.get(SHORT_TERM_NS, sessionId) ?? [];
|
|
3698
|
-
}
|
|
3699
|
-
async getSummaries(sessionId) {
|
|
3700
|
-
if (!this.enableLongTerm) return [];
|
|
3701
|
-
const entries = await this.storage.list(
|
|
3702
|
-
LONG_TERM_NS,
|
|
3703
|
-
sessionId
|
|
3704
|
-
);
|
|
3705
|
-
return entries.map((e) => e.value.summary);
|
|
3706
|
-
}
|
|
3707
|
-
async getContextString(sessionId) {
|
|
3708
|
-
const summaries = await this.getSummaries(sessionId);
|
|
3709
|
-
if (summaries.length === 0) return "";
|
|
3710
|
-
return `Previous context:
|
|
3711
|
-
${summaries.join("\n")}`;
|
|
3712
|
-
}
|
|
3713
|
-
async summarizeAndStore(sessionId, messages) {
|
|
3714
|
-
const textParts = messages.filter((m) => m.content).map((m) => `${m.role}: ${getTextContent(m.content)}`);
|
|
3715
|
-
if (textParts.length === 0) return;
|
|
3716
|
-
const summary = textParts.join(" | ").slice(0, 500);
|
|
3717
|
-
const entry = {
|
|
3718
|
-
key: `${sessionId}:${Date.now()}`,
|
|
3719
|
-
summary,
|
|
3720
|
-
createdAt: /* @__PURE__ */ new Date()
|
|
3721
|
-
};
|
|
3722
|
-
await this.storage.set(LONG_TERM_NS, entry.key, entry);
|
|
3723
|
-
}
|
|
3724
|
-
async clear(sessionId) {
|
|
3725
|
-
await this.storage.delete(SHORT_TERM_NS, sessionId);
|
|
3726
|
-
}
|
|
3727
|
-
};
|
|
3728
|
-
|
|
3729
|
-
// src/mcp/mcp-client.ts
|
|
3730
|
-
var MCPToolProvider = class {
|
|
3731
|
-
name;
|
|
3732
|
-
config;
|
|
3733
|
-
client = null;
|
|
3734
|
-
transportInstance = null;
|
|
3735
|
-
tools = [];
|
|
3736
|
-
connected = false;
|
|
3737
|
-
constructor(config) {
|
|
3738
|
-
this.name = config.name;
|
|
3739
|
-
this.config = config;
|
|
3740
|
-
}
|
|
3741
|
-
async connect() {
|
|
3742
|
-
if (this.connected) return;
|
|
3743
|
-
let ClientClass;
|
|
3744
|
-
try {
|
|
3745
|
-
const mod = await import("@modelcontextprotocol/sdk/client/index.js");
|
|
3746
|
-
ClientClass = mod.Client;
|
|
3747
|
-
} catch {
|
|
3748
|
-
throw new Error(
|
|
3749
|
-
"@modelcontextprotocol/sdk is required for MCPToolProvider. Install it: npm install @modelcontextprotocol/sdk"
|
|
3750
|
-
);
|
|
3751
|
-
}
|
|
3752
|
-
this.client = new ClientClass(
|
|
3753
|
-
{ name: `radaros-${this.name}`, version: "1.0.0" },
|
|
3754
|
-
{ capabilities: {} }
|
|
3755
|
-
);
|
|
3756
|
-
if (this.config.transport === "stdio") {
|
|
3757
|
-
if (!this.config.command) {
|
|
3758
|
-
throw new Error("MCPToolProvider: 'command' is required for stdio transport");
|
|
3759
|
-
}
|
|
3760
|
-
const { StdioClientTransport } = await import("@modelcontextprotocol/sdk/client/stdio.js");
|
|
3761
|
-
this.transportInstance = new StdioClientTransport({
|
|
3762
|
-
command: this.config.command,
|
|
3763
|
-
args: this.config.args ?? [],
|
|
3764
|
-
env: { ...process.env, ...this.config.env ?? {} }
|
|
3765
|
-
});
|
|
3766
|
-
} else if (this.config.transport === "http") {
|
|
3767
|
-
if (!this.config.url) {
|
|
3768
|
-
throw new Error("MCPToolProvider: 'url' is required for http transport");
|
|
3769
|
-
}
|
|
3770
|
-
let TransportClass;
|
|
3771
|
-
try {
|
|
3772
|
-
const mod = await import("@modelcontextprotocol/sdk/client/streamableHttp.js");
|
|
3773
|
-
TransportClass = mod.StreamableHTTPClientTransport;
|
|
3774
|
-
} catch {
|
|
3775
|
-
const mod = await import("@modelcontextprotocol/sdk/client/sse.js");
|
|
3776
|
-
TransportClass = mod.SSEClientTransport;
|
|
3777
|
-
}
|
|
3778
|
-
this.transportInstance = new TransportClass(
|
|
3779
|
-
new URL(this.config.url),
|
|
3780
|
-
{ requestInit: { headers: this.config.headers ?? {} } }
|
|
3781
|
-
);
|
|
3782
|
-
} else {
|
|
3783
|
-
throw new Error(`MCPToolProvider: unsupported transport '${this.config.transport}'`);
|
|
3784
|
-
}
|
|
3785
|
-
await this.client.connect(this.transportInstance);
|
|
3786
|
-
this.connected = true;
|
|
3787
|
-
await this.discoverTools();
|
|
3788
|
-
}
|
|
3789
|
-
async discoverTools() {
|
|
3790
|
-
const { z: z7 } = await import("zod");
|
|
3791
|
-
const result = await this.client.listTools();
|
|
3792
|
-
const mcpTools = result.tools ?? [];
|
|
3793
|
-
this.tools = mcpTools.map((mcpTool) => {
|
|
3794
|
-
const toolName = mcpTool.name;
|
|
3795
|
-
const description = mcpTool.description ?? "";
|
|
3796
|
-
const inputSchema = mcpTool.inputSchema ?? { type: "object", properties: {} };
|
|
3797
|
-
const parameters = this.jsonSchemaToZod(inputSchema, z7);
|
|
3798
|
-
const execute = async (args, _ctx) => {
|
|
3799
|
-
const callResult = await this.client.callTool({
|
|
3800
|
-
name: toolName,
|
|
3801
|
-
arguments: args
|
|
3802
|
-
});
|
|
3803
|
-
const contents = callResult.content ?? [];
|
|
3804
|
-
const textParts = contents.filter((c) => c.type === "text").map((c) => c.text);
|
|
3805
|
-
const text = textParts.join("\n") || JSON.stringify(callResult);
|
|
3806
|
-
const artifacts = contents.filter((c) => c.type !== "text").map((c) => ({
|
|
3807
|
-
type: c.type,
|
|
3808
|
-
data: c.data ?? c.blob ?? c.text,
|
|
3809
|
-
mimeType: c.mimeType
|
|
3810
|
-
}));
|
|
3811
|
-
if (artifacts.length > 0) {
|
|
3812
|
-
return { content: text, artifacts };
|
|
3813
|
-
}
|
|
3814
|
-
return text;
|
|
3815
|
-
};
|
|
3816
|
-
return {
|
|
3817
|
-
name: `${this.name}__${toolName}`,
|
|
3818
|
-
description: `[${this.name}] ${description}`,
|
|
3819
|
-
parameters,
|
|
3820
|
-
execute,
|
|
3821
|
-
rawJsonSchema: inputSchema
|
|
3822
|
-
};
|
|
3823
|
-
});
|
|
3824
|
-
}
|
|
3825
|
-
jsonSchemaToZod(schema, z7) {
|
|
3826
|
-
if (!schema || !schema.properties) {
|
|
3827
|
-
return z7.object({}).passthrough();
|
|
3828
|
-
}
|
|
3829
|
-
const shape = {};
|
|
3830
|
-
const required = schema.required ?? [];
|
|
3831
|
-
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
3832
|
-
let field;
|
|
3833
|
-
switch (prop.type) {
|
|
3834
|
-
case "string":
|
|
3835
|
-
field = z7.string();
|
|
3836
|
-
if (prop.enum) field = z7.enum(prop.enum);
|
|
3837
|
-
break;
|
|
3838
|
-
case "number":
|
|
3839
|
-
case "integer":
|
|
3840
|
-
field = z7.number();
|
|
3841
|
-
break;
|
|
3842
|
-
case "boolean":
|
|
3843
|
-
field = z7.boolean();
|
|
3844
|
-
break;
|
|
3845
|
-
case "array":
|
|
3846
|
-
field = z7.array(z7.any());
|
|
3847
|
-
break;
|
|
3848
|
-
case "object":
|
|
3849
|
-
field = z7.record(z7.any());
|
|
3850
|
-
break;
|
|
3851
|
-
default:
|
|
3852
|
-
field = z7.any();
|
|
3853
|
-
}
|
|
3854
|
-
if (prop.description) {
|
|
3855
|
-
field = field.describe(prop.description);
|
|
3856
|
-
}
|
|
3857
|
-
if (!required.includes(key)) {
|
|
3858
|
-
field = field.optional();
|
|
3859
|
-
}
|
|
3860
|
-
shape[key] = field;
|
|
3861
|
-
}
|
|
3862
|
-
return z7.object(shape);
|
|
3863
|
-
}
|
|
3864
|
-
/**
|
|
3865
|
-
* Returns tools from this MCP server as RadarOS ToolDef[].
|
|
3866
|
-
* Optionally filter by tool names to reduce token usage.
|
|
3867
|
-
*
|
|
3868
|
-
* @param filter - Tool names to include (without the server name prefix).
|
|
3869
|
-
* If omitted, returns all tools.
|
|
3870
|
-
*
|
|
3871
|
-
* @example
|
|
3872
|
-
* // All tools
|
|
3873
|
-
* await mcp.getTools()
|
|
3874
|
-
*
|
|
3875
|
-
* // Only specific tools (pass the original MCP tool names, not prefixed)
|
|
3876
|
-
* await mcp.getTools({ include: ["get_latest_release", "search_repositories"] })
|
|
3877
|
-
*
|
|
3878
|
-
* // Exclude specific tools
|
|
3879
|
-
* await mcp.getTools({ exclude: ["push_files", "create_repository"] })
|
|
3880
|
-
*/
|
|
3881
|
-
async getTools(filter) {
|
|
3882
|
-
if (!this.connected) {
|
|
3883
|
-
await this.connect();
|
|
3884
|
-
}
|
|
3885
|
-
if (!filter) {
|
|
3886
|
-
return [...this.tools];
|
|
3887
|
-
}
|
|
3888
|
-
const prefix = `${this.name}__`;
|
|
3889
|
-
return this.tools.filter((tool) => {
|
|
3890
|
-
const shortName = tool.name.startsWith(prefix) ? tool.name.slice(prefix.length) : tool.name;
|
|
3891
|
-
if (filter.include) {
|
|
3892
|
-
return filter.include.includes(shortName);
|
|
3893
|
-
}
|
|
3894
|
-
if (filter.exclude) {
|
|
3895
|
-
return !filter.exclude.includes(shortName);
|
|
3896
|
-
}
|
|
3897
|
-
return true;
|
|
3898
|
-
});
|
|
3899
|
-
}
|
|
3900
|
-
/** Refresh the tool list from the MCP server. */
|
|
3901
|
-
async refresh() {
|
|
3902
|
-
if (!this.connected) {
|
|
3903
|
-
throw new Error("MCPToolProvider: not connected. Call connect() first.");
|
|
3904
|
-
}
|
|
3905
|
-
await this.discoverTools();
|
|
3906
|
-
}
|
|
3907
|
-
/** Disconnect from the MCP server. */
|
|
3908
|
-
async close() {
|
|
3909
|
-
if (this.client && this.connected) {
|
|
3910
|
-
try {
|
|
3911
|
-
await this.client.close();
|
|
3912
|
-
} catch {
|
|
3913
|
-
}
|
|
3914
|
-
this.connected = false;
|
|
3915
|
-
this.tools = [];
|
|
3916
|
-
}
|
|
3917
|
-
}
|
|
3918
|
-
};
|
|
3919
|
-
|
|
3920
|
-
// src/a2a/a2a-remote-agent.ts
|
|
3921
|
-
import { z as z2 } from "zod";
|
|
3922
|
-
var A2ARemoteAgent = class {
|
|
3923
|
-
url;
|
|
3924
|
-
name;
|
|
3925
|
-
instructions;
|
|
3926
|
-
skills = [];
|
|
3927
|
-
headers;
|
|
3928
|
-
timeoutMs;
|
|
3929
|
-
card = null;
|
|
3930
|
-
rpcId = 0;
|
|
3931
|
-
get tools() {
|
|
3932
|
-
return [];
|
|
3933
|
-
}
|
|
3934
|
-
get modelId() {
|
|
3935
|
-
return "a2a-remote";
|
|
3936
|
-
}
|
|
3937
|
-
get providerId() {
|
|
3938
|
-
return "a2a";
|
|
3939
|
-
}
|
|
3940
|
-
get hasStructuredOutput() {
|
|
3941
|
-
return false;
|
|
3942
|
-
}
|
|
3943
|
-
constructor(config) {
|
|
3944
|
-
this.url = config.url.replace(/\/$/, "");
|
|
3945
|
-
this.name = config.name ?? "remote-agent";
|
|
3946
|
-
this.instructions = "";
|
|
3947
|
-
this.headers = config.headers ?? {};
|
|
3948
|
-
this.timeoutMs = config.timeoutMs ?? 6e4;
|
|
3949
|
-
}
|
|
3950
|
-
/**
|
|
3951
|
-
* Fetch the Agent Card from /.well-known/agent.json and populate metadata.
|
|
3952
|
-
*/
|
|
3953
|
-
async discover() {
|
|
3954
|
-
const res = await fetch(`${this.url}/.well-known/agent.json`, {
|
|
3955
|
-
headers: this.headers,
|
|
3956
|
-
signal: AbortSignal.timeout(this.timeoutMs)
|
|
3957
|
-
});
|
|
3958
|
-
if (!res.ok) {
|
|
3959
|
-
throw new Error(
|
|
3960
|
-
`A2A discover failed: ${res.status} ${res.statusText} from ${this.url}`
|
|
3961
|
-
);
|
|
3962
|
-
}
|
|
3963
|
-
this.card = await res.json();
|
|
3964
|
-
this.name = this.card.name;
|
|
3965
|
-
this.instructions = this.card.description ?? "";
|
|
3966
|
-
this.skills = this.card.skills?.map((s) => ({
|
|
3967
|
-
id: s.id,
|
|
3968
|
-
name: s.name,
|
|
3969
|
-
description: s.description
|
|
3970
|
-
})) ?? [];
|
|
3971
|
-
return this.card;
|
|
3972
|
-
}
|
|
3973
|
-
/**
|
|
3974
|
-
* Synchronous run: sends message/send and returns RunOutput.
|
|
3975
|
-
*/
|
|
3976
|
-
async run(input, opts) {
|
|
3977
|
-
const message = {
|
|
3978
|
-
role: "user",
|
|
3979
|
-
parts: [{ kind: "text", text: input }]
|
|
3980
|
-
};
|
|
3981
|
-
const rpcReq = {
|
|
3982
|
-
jsonrpc: "2.0",
|
|
3983
|
-
id: ++this.rpcId,
|
|
3984
|
-
method: "message/send",
|
|
3985
|
-
params: {
|
|
3986
|
-
message,
|
|
3987
|
-
...opts?.sessionId ? { sessionId: opts.sessionId } : {}
|
|
3988
|
-
}
|
|
3989
|
-
};
|
|
3990
|
-
const startMs = Date.now();
|
|
3991
|
-
const res = await fetch(this.url, {
|
|
3992
|
-
method: "POST",
|
|
3993
|
-
headers: {
|
|
3994
|
-
"Content-Type": "application/json",
|
|
3995
|
-
...this.headers
|
|
3996
|
-
},
|
|
3997
|
-
body: JSON.stringify(rpcReq),
|
|
3998
|
-
signal: AbortSignal.timeout(this.timeoutMs)
|
|
3999
|
-
});
|
|
4000
|
-
if (!res.ok) {
|
|
4001
|
-
throw new Error(`A2A message/send failed: ${res.status} ${res.statusText}`);
|
|
4002
|
-
}
|
|
4003
|
-
const rpcRes = await res.json();
|
|
4004
|
-
if (rpcRes.error) {
|
|
4005
|
-
throw new Error(`A2A error: ${rpcRes.error.message}`);
|
|
4006
|
-
}
|
|
4007
|
-
const task = rpcRes.result;
|
|
4008
|
-
const agentMsg = task.status?.message;
|
|
4009
|
-
const text = agentMsg ? this.partsToText(agentMsg.parts) : "";
|
|
4010
|
-
return {
|
|
4011
|
-
text,
|
|
4012
|
-
toolCalls: [],
|
|
4013
|
-
usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 },
|
|
4014
|
-
durationMs: Date.now() - startMs
|
|
4015
|
-
};
|
|
4016
|
-
}
|
|
4017
|
-
/**
|
|
4018
|
-
* Streaming run: sends message/stream and yields StreamChunks from SSE.
|
|
4019
|
-
*/
|
|
4020
|
-
async *stream(input, opts) {
|
|
4021
|
-
const message = {
|
|
4022
|
-
role: "user",
|
|
4023
|
-
parts: [{ kind: "text", text: input }]
|
|
4024
|
-
};
|
|
4025
|
-
const rpcReq = {
|
|
4026
|
-
jsonrpc: "2.0",
|
|
4027
|
-
id: ++this.rpcId,
|
|
4028
|
-
method: "message/stream",
|
|
4029
|
-
params: {
|
|
4030
|
-
message,
|
|
4031
|
-
...opts?.sessionId ? { sessionId: opts.sessionId } : {}
|
|
4032
|
-
}
|
|
4033
|
-
};
|
|
4034
|
-
const res = await fetch(this.url, {
|
|
4035
|
-
method: "POST",
|
|
4036
|
-
headers: {
|
|
4037
|
-
"Content-Type": "application/json",
|
|
4038
|
-
...this.headers
|
|
4039
|
-
},
|
|
4040
|
-
body: JSON.stringify(rpcReq),
|
|
4041
|
-
signal: AbortSignal.timeout(this.timeoutMs)
|
|
4042
|
-
});
|
|
4043
|
-
if (!res.ok) {
|
|
4044
|
-
throw new Error(`A2A message/stream failed: ${res.status} ${res.statusText}`);
|
|
4045
|
-
}
|
|
4046
|
-
if (!res.body) {
|
|
4047
|
-
throw new Error("A2A message/stream: no response body for SSE");
|
|
4048
|
-
}
|
|
4049
|
-
const reader = res.body.getReader();
|
|
4050
|
-
const decoder = new TextDecoder();
|
|
4051
|
-
let buffer = "";
|
|
4052
|
-
while (true) {
|
|
4053
|
-
const { done, value } = await reader.read();
|
|
4054
|
-
if (done) break;
|
|
4055
|
-
buffer += decoder.decode(value, { stream: true });
|
|
4056
|
-
const lines = buffer.split("\n");
|
|
4057
|
-
buffer = lines.pop();
|
|
4058
|
-
for (const line of lines) {
|
|
4059
|
-
if (!line.startsWith("data: ")) continue;
|
|
4060
|
-
const jsonStr = line.slice(6).trim();
|
|
4061
|
-
if (!jsonStr) continue;
|
|
4062
|
-
try {
|
|
4063
|
-
const event = JSON.parse(jsonStr);
|
|
4064
|
-
const task = event.result;
|
|
4065
|
-
if (!task) continue;
|
|
4066
|
-
if (task.status?.state === "working" && task.status.message?.parts?.length) {
|
|
4067
|
-
for (const part of task.status.message.parts) {
|
|
4068
|
-
if (part.kind === "text") {
|
|
4069
|
-
yield { type: "text", text: part.text };
|
|
4070
|
-
}
|
|
4071
|
-
}
|
|
4072
|
-
}
|
|
4073
|
-
if (task.status?.state === "completed") {
|
|
4074
|
-
yield {
|
|
4075
|
-
type: "finish",
|
|
4076
|
-
finishReason: "stop",
|
|
4077
|
-
usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 }
|
|
4078
|
-
};
|
|
4079
|
-
}
|
|
4080
|
-
} catch {
|
|
4081
|
-
}
|
|
4082
|
-
}
|
|
4083
|
-
}
|
|
4084
|
-
}
|
|
4085
|
-
/**
|
|
4086
|
-
* Wrap this remote agent as a ToolDef so it can be used by an orchestrator agent.
|
|
4087
|
-
*/
|
|
4088
|
-
asTool() {
|
|
4089
|
-
const self = this;
|
|
4090
|
-
return {
|
|
4091
|
-
name: `a2a_${this.name.replace(/[^a-zA-Z0-9_]/g, "_")}`,
|
|
4092
|
-
description: this.instructions || `Remote A2A agent: ${this.name}`,
|
|
4093
|
-
parameters: z2.object({
|
|
4094
|
-
message: z2.string().describe("The message to send to the remote agent")
|
|
4095
|
-
}),
|
|
4096
|
-
async execute(args, _ctx) {
|
|
4097
|
-
const result = await self.run(args.message);
|
|
4098
|
-
return result.text;
|
|
4099
|
-
}
|
|
4100
|
-
};
|
|
4101
|
-
}
|
|
4102
|
-
getAgentCard() {
|
|
4103
|
-
return this.card;
|
|
4104
|
-
}
|
|
4105
|
-
partsToText(parts) {
|
|
4106
|
-
return parts.filter(
|
|
4107
|
-
(p) => p.kind === "text"
|
|
4108
|
-
).map((p) => p.text).join("\n");
|
|
4109
|
-
}
|
|
4110
|
-
};
|
|
4111
|
-
|
|
4112
|
-
// src/toolkits/base.ts
|
|
4113
|
-
var Toolkit = class {
|
|
4114
|
-
};
|
|
4115
|
-
|
|
4116
|
-
// src/toolkits/websearch.ts
|
|
4117
|
-
import { z as z3 } from "zod";
|
|
4118
|
-
var WebSearchToolkit = class extends Toolkit {
|
|
4119
|
-
name = "websearch";
|
|
4120
|
-
config;
|
|
4121
|
-
constructor(config) {
|
|
4122
|
-
super();
|
|
4123
|
-
this.config = config;
|
|
4124
|
-
}
|
|
4125
|
-
getApiKey() {
|
|
4126
|
-
if (this.config.apiKey) return this.config.apiKey;
|
|
4127
|
-
const envKey = this.config.provider === "tavily" ? process.env.TAVILY_API_KEY : process.env.SERPAPI_API_KEY;
|
|
4128
|
-
if (!envKey) {
|
|
4129
|
-
const envName = this.config.provider === "tavily" ? "TAVILY_API_KEY" : "SERPAPI_API_KEY";
|
|
4130
|
-
throw new Error(
|
|
4131
|
-
`WebSearchToolkit: No API key provided. Set ${envName} env var or pass apiKey in config.`
|
|
4132
|
-
);
|
|
4133
|
-
}
|
|
4134
|
-
return envKey;
|
|
4135
|
-
}
|
|
4136
|
-
getTools() {
|
|
4137
|
-
const self = this;
|
|
4138
|
-
return [
|
|
4139
|
-
{
|
|
4140
|
-
name: "web_search",
|
|
4141
|
-
description: "Search the web for current information. Returns titles, URLs, and snippets from search results.",
|
|
4142
|
-
parameters: z3.object({
|
|
4143
|
-
query: z3.string().describe("The search query"),
|
|
4144
|
-
maxResults: z3.number().optional().describe("Maximum number of results (default 5)")
|
|
4145
|
-
}),
|
|
4146
|
-
execute: async (args, _ctx) => {
|
|
4147
|
-
const query = args.query;
|
|
4148
|
-
const max = args.maxResults ?? self.config.maxResults ?? 5;
|
|
4149
|
-
if (self.config.provider === "tavily") {
|
|
4150
|
-
return self.searchTavily(query, max);
|
|
4151
|
-
}
|
|
4152
|
-
return self.searchSerpApi(query, max);
|
|
4153
|
-
}
|
|
4154
|
-
}
|
|
4155
|
-
];
|
|
4156
|
-
}
|
|
4157
|
-
async searchTavily(query, maxResults) {
|
|
4158
|
-
const apiKey = this.getApiKey();
|
|
4159
|
-
const res = await fetch("https://api.tavily.com/search", {
|
|
4160
|
-
method: "POST",
|
|
4161
|
-
headers: { "Content-Type": "application/json" },
|
|
4162
|
-
body: JSON.stringify({
|
|
4163
|
-
api_key: apiKey,
|
|
4164
|
-
query,
|
|
4165
|
-
max_results: maxResults,
|
|
4166
|
-
include_answer: true
|
|
4167
|
-
})
|
|
4168
|
-
});
|
|
4169
|
-
if (!res.ok) {
|
|
4170
|
-
throw new Error(`Tavily search failed: ${res.status} ${res.statusText}`);
|
|
4171
|
-
}
|
|
4172
|
-
const data = await res.json();
|
|
4173
|
-
const results = [];
|
|
4174
|
-
if (data.answer) {
|
|
4175
|
-
results.push(`Answer: ${data.answer}
|
|
4176
|
-
`);
|
|
4177
|
-
}
|
|
4178
|
-
for (const r of data.results ?? []) {
|
|
4179
|
-
results.push(`Title: ${r.title}
|
|
4180
|
-
URL: ${r.url}
|
|
4181
|
-
Snippet: ${r.content}
|
|
4182
|
-
`);
|
|
4183
|
-
}
|
|
4184
|
-
return results.join("\n---\n") || "No results found.";
|
|
4185
|
-
}
|
|
4186
|
-
async searchSerpApi(query, maxResults) {
|
|
4187
|
-
const apiKey = this.getApiKey();
|
|
4188
|
-
const params = new URLSearchParams({
|
|
4189
|
-
q: query,
|
|
4190
|
-
api_key: apiKey,
|
|
4191
|
-
engine: "google",
|
|
4192
|
-
num: String(maxResults)
|
|
4193
|
-
});
|
|
4194
|
-
const res = await fetch(
|
|
4195
|
-
`https://serpapi.com/search.json?${params.toString()}`
|
|
4196
|
-
);
|
|
4197
|
-
if (!res.ok) {
|
|
4198
|
-
throw new Error(
|
|
4199
|
-
`SerpAPI search failed: ${res.status} ${res.statusText}`
|
|
4200
|
-
);
|
|
4201
|
-
}
|
|
4202
|
-
const data = await res.json();
|
|
4203
|
-
const results = [];
|
|
4204
|
-
if (data.answer_box?.answer) {
|
|
4205
|
-
results.push(`Answer: ${data.answer_box.answer}
|
|
4206
|
-
`);
|
|
4207
|
-
}
|
|
4208
|
-
for (const r of (data.organic_results ?? []).slice(0, maxResults)) {
|
|
4209
|
-
results.push(
|
|
4210
|
-
`Title: ${r.title}
|
|
4211
|
-
URL: ${r.link}
|
|
4212
|
-
Snippet: ${r.snippet ?? ""}
|
|
4213
|
-
`
|
|
4214
|
-
);
|
|
4215
|
-
}
|
|
4216
|
-
return results.join("\n---\n") || "No results found.";
|
|
4217
|
-
}
|
|
4218
|
-
};
|
|
4219
|
-
|
|
4220
|
-
// src/toolkits/gmail.ts
|
|
4221
|
-
import { z as z4 } from "zod";
|
|
4222
|
-
import { createRequire as createRequire16 } from "module";
|
|
4223
|
-
var _require16 = createRequire16(import.meta.url);
|
|
4224
|
-
var GmailToolkit = class extends Toolkit {
|
|
4225
|
-
name = "gmail";
|
|
4226
|
-
config;
|
|
4227
|
-
gmail = null;
|
|
4228
|
-
constructor(config = {}) {
|
|
4229
|
-
super();
|
|
4230
|
-
this.config = config;
|
|
4231
|
-
}
|
|
4232
|
-
async getGmailClient() {
|
|
4233
|
-
if (this.gmail) return this.gmail;
|
|
4234
|
-
if (this.config.authClient) {
|
|
4235
|
-
const { google: google3 } = _require16("googleapis");
|
|
4236
|
-
this.gmail = google3.gmail({ version: "v1", auth: this.config.authClient });
|
|
4237
|
-
return this.gmail;
|
|
4238
|
-
}
|
|
4239
|
-
const credPath = this.config.credentialsPath ?? process.env.GMAIL_CREDENTIALS_PATH;
|
|
4240
|
-
const tokenPath = this.config.tokenPath ?? process.env.GMAIL_TOKEN_PATH;
|
|
4241
|
-
if (!credPath || !tokenPath) {
|
|
4242
|
-
throw new Error(
|
|
4243
|
-
"GmailToolkit: Provide credentialsPath + tokenPath, or an authClient. Set GMAIL_CREDENTIALS_PATH and GMAIL_TOKEN_PATH env vars, or pass them in config."
|
|
4244
|
-
);
|
|
4245
|
-
}
|
|
4246
|
-
const { google: google2 } = _require16("googleapis");
|
|
4247
|
-
const fs = await import("fs");
|
|
4248
|
-
const creds = JSON.parse(fs.readFileSync(credPath, "utf-8"));
|
|
4249
|
-
const token = JSON.parse(fs.readFileSync(tokenPath, "utf-8"));
|
|
4250
|
-
const { client_id, client_secret, redirect_uris } = creds.installed || creds.web;
|
|
4251
|
-
const oAuth2 = new google2.auth.OAuth2(
|
|
4252
|
-
client_id,
|
|
4253
|
-
client_secret,
|
|
4254
|
-
redirect_uris?.[0]
|
|
4255
|
-
);
|
|
4256
|
-
oAuth2.setCredentials(token);
|
|
4257
|
-
this.gmail = google2.gmail({ version: "v1", auth: oAuth2 });
|
|
4258
|
-
return this.gmail;
|
|
4259
|
-
}
|
|
4260
|
-
getTools() {
|
|
4261
|
-
const self = this;
|
|
4262
|
-
return [
|
|
4263
|
-
{
|
|
4264
|
-
name: "gmail_send",
|
|
4265
|
-
description: "Send an email via Gmail. Provide recipient, subject, and body.",
|
|
4266
|
-
parameters: z4.object({
|
|
4267
|
-
to: z4.string().describe("Recipient email address"),
|
|
4268
|
-
subject: z4.string().describe("Email subject line"),
|
|
4269
|
-
body: z4.string().describe("Email body (plain text)")
|
|
4270
|
-
}),
|
|
4271
|
-
execute: async (args, _ctx) => {
|
|
4272
|
-
const gmail = await self.getGmailClient();
|
|
4273
|
-
const rawMessage = [
|
|
4274
|
-
`To: ${args.to}`,
|
|
4275
|
-
`Subject: ${args.subject}`,
|
|
4276
|
-
"Content-Type: text/plain; charset=utf-8",
|
|
4277
|
-
"",
|
|
4278
|
-
args.body
|
|
4279
|
-
].join("\n");
|
|
4280
|
-
const encoded = Buffer.from(rawMessage).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
4281
|
-
const res = await gmail.users.messages.send({
|
|
4282
|
-
userId: "me",
|
|
4283
|
-
requestBody: { raw: encoded }
|
|
4284
|
-
});
|
|
4285
|
-
return `Email sent successfully. Message ID: ${res.data.id}`;
|
|
4286
|
-
}
|
|
4287
|
-
},
|
|
4288
|
-
{
|
|
4289
|
-
name: "gmail_search",
|
|
4290
|
-
description: "Search emails in Gmail. Returns subject, from, date, and snippet for matching messages.",
|
|
4291
|
-
parameters: z4.object({
|
|
4292
|
-
query: z4.string().describe(
|
|
4293
|
-
'Gmail search query (e.g. "from:john subject:meeting is:unread")'
|
|
4294
|
-
),
|
|
4295
|
-
maxResults: z4.number().optional().describe("Maximum number of results (default 10)")
|
|
4296
|
-
}),
|
|
4297
|
-
execute: async (args, _ctx) => {
|
|
4298
|
-
const gmail = await self.getGmailClient();
|
|
4299
|
-
const max = args.maxResults ?? 10;
|
|
4300
|
-
const list = await gmail.users.messages.list({
|
|
4301
|
-
userId: "me",
|
|
4302
|
-
q: args.query,
|
|
4303
|
-
maxResults: max
|
|
4304
|
-
});
|
|
4305
|
-
const messages = list.data.messages ?? [];
|
|
4306
|
-
if (messages.length === 0) return "No emails found.";
|
|
4307
|
-
const results = [];
|
|
4308
|
-
for (const msg of messages) {
|
|
4309
|
-
const detail = await gmail.users.messages.get({
|
|
4310
|
-
userId: "me",
|
|
4311
|
-
id: msg.id,
|
|
4312
|
-
format: "metadata",
|
|
4313
|
-
metadataHeaders: ["From", "Subject", "Date"]
|
|
4314
|
-
});
|
|
4315
|
-
const headers = detail.data.payload?.headers ?? [];
|
|
4316
|
-
const getHeader = (name) => headers.find(
|
|
4317
|
-
(h) => h.name.toLowerCase() === name.toLowerCase()
|
|
4318
|
-
)?.value ?? "";
|
|
4319
|
-
results.push(
|
|
4320
|
-
`ID: ${msg.id}
|
|
4321
|
-
From: ${getHeader("From")}
|
|
4322
|
-
Subject: ${getHeader("Subject")}
|
|
4323
|
-
Date: ${getHeader("Date")}
|
|
4324
|
-
Snippet: ${detail.data.snippet ?? ""}`
|
|
4325
|
-
);
|
|
4326
|
-
}
|
|
4327
|
-
return results.join("\n\n---\n\n");
|
|
4328
|
-
}
|
|
4329
|
-
},
|
|
4330
|
-
{
|
|
4331
|
-
name: "gmail_read",
|
|
4332
|
-
description: "Read the full content of an email by its message ID.",
|
|
4333
|
-
parameters: z4.object({
|
|
4334
|
-
messageId: z4.string().describe("The Gmail message ID to read")
|
|
4335
|
-
}),
|
|
4336
|
-
execute: async (args, _ctx) => {
|
|
4337
|
-
const gmail = await self.getGmailClient();
|
|
4338
|
-
const detail = await gmail.users.messages.get({
|
|
4339
|
-
userId: "me",
|
|
4340
|
-
id: args.messageId,
|
|
4341
|
-
format: "full"
|
|
4342
|
-
});
|
|
4343
|
-
const headers = detail.data.payload?.headers ?? [];
|
|
4344
|
-
const getHeader = (name) => headers.find(
|
|
4345
|
-
(h) => h.name.toLowerCase() === name.toLowerCase()
|
|
4346
|
-
)?.value ?? "";
|
|
4347
|
-
let body = "";
|
|
4348
|
-
const payload = detail.data.payload;
|
|
4349
|
-
if (payload?.body?.data) {
|
|
4350
|
-
body = Buffer.from(payload.body.data, "base64").toString("utf-8");
|
|
4351
|
-
} else if (payload?.parts) {
|
|
4352
|
-
const textPart = payload.parts.find(
|
|
4353
|
-
(p) => p.mimeType === "text/plain"
|
|
4354
|
-
);
|
|
4355
|
-
if (textPart?.body?.data) {
|
|
4356
|
-
body = Buffer.from(textPart.body.data, "base64").toString(
|
|
4357
|
-
"utf-8"
|
|
4358
|
-
);
|
|
4359
|
-
} else {
|
|
4360
|
-
const htmlPart = payload.parts.find(
|
|
4361
|
-
(p) => p.mimeType === "text/html"
|
|
4362
|
-
);
|
|
4363
|
-
if (htmlPart?.body?.data) {
|
|
4364
|
-
body = Buffer.from(htmlPart.body.data, "base64").toString(
|
|
4365
|
-
"utf-8"
|
|
4366
|
-
);
|
|
4367
|
-
}
|
|
4368
|
-
}
|
|
4369
|
-
}
|
|
4370
|
-
return `From: ${getHeader("From")}
|
|
4371
|
-
To: ${getHeader("To")}
|
|
4372
|
-
Subject: ${getHeader("Subject")}
|
|
4373
|
-
Date: ${getHeader("Date")}
|
|
4374
|
-
|
|
4375
|
-
${body || "(no body)"}`;
|
|
4376
|
-
}
|
|
4377
|
-
}
|
|
4378
|
-
];
|
|
4379
|
-
}
|
|
4380
|
-
};
|
|
4381
|
-
|
|
4382
|
-
// src/toolkits/whatsapp.ts
|
|
4383
|
-
import { z as z5 } from "zod";
|
|
4384
|
-
var WhatsAppToolkit = class extends Toolkit {
|
|
4385
|
-
name = "whatsapp";
|
|
4386
|
-
accessToken;
|
|
4387
|
-
phoneNumberId;
|
|
4388
|
-
version;
|
|
4389
|
-
recipientWaid;
|
|
4390
|
-
constructor(config = {}) {
|
|
4391
|
-
super();
|
|
4392
|
-
this.accessToken = config.accessToken ?? process.env.WHATSAPP_ACCESS_TOKEN ?? "";
|
|
4393
|
-
this.phoneNumberId = config.phoneNumberId ?? process.env.WHATSAPP_PHONE_NUMBER_ID ?? "";
|
|
4394
|
-
this.version = config.version ?? process.env.WHATSAPP_VERSION ?? "v22.0";
|
|
4395
|
-
this.recipientWaid = config.recipientWaid ?? process.env.WHATSAPP_RECIPIENT_WAID;
|
|
4396
|
-
}
|
|
4397
|
-
getBaseUrl() {
|
|
4398
|
-
return `https://graph.facebook.com/${this.version}/${this.phoneNumberId}/messages`;
|
|
4399
|
-
}
|
|
4400
|
-
validate() {
|
|
4401
|
-
if (!this.accessToken) {
|
|
4402
|
-
throw new Error(
|
|
4403
|
-
"WhatsAppToolkit: accessToken is required. Set WHATSAPP_ACCESS_TOKEN env var or pass in config."
|
|
4404
|
-
);
|
|
4405
|
-
}
|
|
4406
|
-
if (!this.phoneNumberId) {
|
|
4407
|
-
throw new Error(
|
|
4408
|
-
"WhatsAppToolkit: phoneNumberId is required. Set WHATSAPP_PHONE_NUMBER_ID env var or pass in config."
|
|
4409
|
-
);
|
|
4410
|
-
}
|
|
4411
|
-
}
|
|
4412
|
-
resolveRecipient(recipient) {
|
|
4413
|
-
const r = recipient ?? this.recipientWaid;
|
|
4414
|
-
if (!r) {
|
|
4415
|
-
throw new Error(
|
|
4416
|
-
"WhatsAppToolkit: recipient is required. Provide it in the tool call or set WHATSAPP_RECIPIENT_WAID env var."
|
|
4417
|
-
);
|
|
4418
|
-
}
|
|
4419
|
-
return r.replace(/[^0-9]/g, "");
|
|
4420
|
-
}
|
|
4421
|
-
getTools() {
|
|
4422
|
-
const self = this;
|
|
4423
|
-
return [
|
|
4424
|
-
{
|
|
4425
|
-
name: "whatsapp_send_text",
|
|
4426
|
-
description: "Send a text message to a WhatsApp user via WhatsApp Business Cloud API.",
|
|
4427
|
-
parameters: z5.object({
|
|
4428
|
-
text: z5.string().describe("The text message to send"),
|
|
4429
|
-
recipient: z5.string().optional().describe(
|
|
4430
|
-
"Recipient WhatsApp number with country code (e.g. 919876543210). Uses default if omitted."
|
|
4431
|
-
),
|
|
4432
|
-
previewUrl: z5.boolean().optional().describe("Enable URL previews in the message (default false)")
|
|
4433
|
-
}),
|
|
4434
|
-
execute: async (args, _ctx) => {
|
|
4435
|
-
self.validate();
|
|
4436
|
-
const recipient = self.resolveRecipient(args.recipient);
|
|
4437
|
-
const res = await fetch(self.getBaseUrl(), {
|
|
4438
|
-
method: "POST",
|
|
4439
|
-
headers: {
|
|
4440
|
-
Authorization: `Bearer ${self.accessToken}`,
|
|
4441
|
-
"Content-Type": "application/json"
|
|
4442
|
-
},
|
|
4443
|
-
body: JSON.stringify({
|
|
4444
|
-
messaging_product: "whatsapp",
|
|
4445
|
-
recipient_type: "individual",
|
|
4446
|
-
to: recipient,
|
|
4447
|
-
type: "text",
|
|
4448
|
-
text: {
|
|
4449
|
-
preview_url: args.previewUrl ?? false,
|
|
4450
|
-
body: args.text
|
|
4451
|
-
}
|
|
4452
|
-
})
|
|
4453
|
-
});
|
|
4454
|
-
if (!res.ok) {
|
|
4455
|
-
const err = await res.text();
|
|
4456
|
-
throw new Error(
|
|
4457
|
-
`WhatsApp send failed: ${res.status} ${err}`
|
|
4458
|
-
);
|
|
4459
|
-
}
|
|
4460
|
-
const data = await res.json();
|
|
4461
|
-
const msgId = data.messages?.[0]?.id ?? "unknown";
|
|
4462
|
-
return `Message sent successfully to ${recipient}. Message ID: ${msgId}`;
|
|
4463
|
-
}
|
|
4464
|
-
},
|
|
4465
|
-
{
|
|
4466
|
-
name: "whatsapp_send_template",
|
|
4467
|
-
description: "Send a template message to a WhatsApp user. Required for first-time outreach (24-hour messaging window).",
|
|
4468
|
-
parameters: z5.object({
|
|
4469
|
-
templateName: z5.string().describe(
|
|
4470
|
-
'The pre-approved template name (e.g. "hello_world")'
|
|
4471
|
-
),
|
|
4472
|
-
recipient: z5.string().optional().describe(
|
|
4473
|
-
"Recipient WhatsApp number with country code. Uses default if omitted."
|
|
4474
|
-
),
|
|
4475
|
-
languageCode: z5.string().optional().describe('Template language code (default "en_US")'),
|
|
4476
|
-
components: z5.array(z5.record(z5.any())).optional().describe(
|
|
4477
|
-
"Template components for dynamic content (header, body, button parameters)"
|
|
4478
|
-
)
|
|
4479
|
-
}),
|
|
4480
|
-
execute: async (args, _ctx) => {
|
|
4481
|
-
self.validate();
|
|
4482
|
-
const recipient = self.resolveRecipient(args.recipient);
|
|
4483
|
-
const langCode = args.languageCode ?? "en_US";
|
|
4484
|
-
const template = {
|
|
4485
|
-
name: args.templateName,
|
|
4486
|
-
language: { code: langCode }
|
|
4487
|
-
};
|
|
4488
|
-
if (args.components) {
|
|
4489
|
-
template.components = args.components;
|
|
4490
|
-
}
|
|
4491
|
-
const res = await fetch(self.getBaseUrl(), {
|
|
4492
|
-
method: "POST",
|
|
4493
|
-
headers: {
|
|
4494
|
-
Authorization: `Bearer ${self.accessToken}`,
|
|
4495
|
-
"Content-Type": "application/json"
|
|
4496
|
-
},
|
|
4497
|
-
body: JSON.stringify({
|
|
4498
|
-
messaging_product: "whatsapp",
|
|
4499
|
-
recipient_type: "individual",
|
|
4500
|
-
to: recipient,
|
|
4501
|
-
type: "template",
|
|
4502
|
-
template
|
|
4503
|
-
})
|
|
4504
|
-
});
|
|
4505
|
-
if (!res.ok) {
|
|
4506
|
-
const err = await res.text();
|
|
4507
|
-
throw new Error(
|
|
4508
|
-
`WhatsApp template send failed: ${res.status} ${err}`
|
|
4509
|
-
);
|
|
4510
|
-
}
|
|
4511
|
-
const data = await res.json();
|
|
4512
|
-
const msgId = data.messages?.[0]?.id ?? "unknown";
|
|
4513
|
-
return `Template message "${args.templateName}" sent to ${recipient}. Message ID: ${msgId}`;
|
|
4514
|
-
}
|
|
4515
|
-
}
|
|
4516
|
-
];
|
|
4517
|
-
}
|
|
4518
|
-
};
|
|
4519
|
-
|
|
4520
|
-
// src/toolkits/hackernews.ts
|
|
4521
|
-
import { z as z6 } from "zod";
|
|
4522
|
-
var HackerNewsToolkit = class extends Toolkit {
|
|
4523
|
-
name = "hackernews";
|
|
4524
|
-
config;
|
|
4525
|
-
constructor(config = {}) {
|
|
4526
|
-
super();
|
|
4527
|
-
this.config = {
|
|
4528
|
-
enableGetTopStories: config.enableGetTopStories ?? true,
|
|
4529
|
-
enableGetUserDetails: config.enableGetUserDetails ?? true
|
|
4530
|
-
};
|
|
4531
|
-
}
|
|
4532
|
-
getTools() {
|
|
4533
|
-
const tools = [];
|
|
4534
|
-
if (this.config.enableGetTopStories) {
|
|
4535
|
-
tools.push({
|
|
4536
|
-
name: "hackernews_top_stories",
|
|
4537
|
-
description: "Get the top stories from Hacker News. Returns title, URL, score, author, and comment count.",
|
|
4538
|
-
parameters: z6.object({
|
|
4539
|
-
numStories: z6.number().optional().describe("Number of top stories to fetch (default 10, max 30)")
|
|
4540
|
-
}),
|
|
4541
|
-
execute: async (args, _ctx) => {
|
|
4542
|
-
const num = Math.min(args.numStories ?? 10, 30);
|
|
4543
|
-
const idsRes = await fetch(
|
|
4544
|
-
"https://hacker-news.firebaseio.com/v0/topstories.json"
|
|
4545
|
-
);
|
|
4546
|
-
if (!idsRes.ok) throw new Error(`HN API failed: ${idsRes.status}`);
|
|
4547
|
-
const ids = await idsRes.json();
|
|
4548
|
-
const stories = await Promise.all(
|
|
4549
|
-
ids.slice(0, num).map(async (id) => {
|
|
4550
|
-
const res = await fetch(
|
|
4551
|
-
`https://hacker-news.firebaseio.com/v0/item/${id}.json`
|
|
4552
|
-
);
|
|
4553
|
-
return res.json();
|
|
4554
|
-
})
|
|
4555
|
-
);
|
|
4556
|
-
return stories.map(
|
|
4557
|
-
(s, i) => `${i + 1}. ${s.title}
|
|
4558
|
-
URL: ${s.url ?? `https://news.ycombinator.com/item?id=${s.id}`}
|
|
4559
|
-
Score: ${s.score} | By: ${s.by} | Comments: ${s.descendants ?? 0}`
|
|
4560
|
-
).join("\n\n");
|
|
4561
|
-
}
|
|
4562
|
-
});
|
|
4563
|
-
}
|
|
4564
|
-
if (this.config.enableGetUserDetails) {
|
|
4565
|
-
tools.push({
|
|
4566
|
-
name: "hackernews_user",
|
|
4567
|
-
description: "Get details about a Hacker News user by username. Returns karma, about, and account creation date.",
|
|
4568
|
-
parameters: z6.object({
|
|
4569
|
-
username: z6.string().describe("The HN username to look up")
|
|
4570
|
-
}),
|
|
4571
|
-
execute: async (args, _ctx) => {
|
|
4572
|
-
const username = args.username;
|
|
4573
|
-
const res = await fetch(
|
|
4574
|
-
`https://hacker-news.firebaseio.com/v0/user/${username}.json`
|
|
4575
|
-
);
|
|
4576
|
-
if (!res.ok)
|
|
4577
|
-
throw new Error(`HN user API failed: ${res.status}`);
|
|
4578
|
-
const user = await res.json();
|
|
4579
|
-
if (!user) return `User "${username}" not found.`;
|
|
4580
|
-
const created = new Date(user.created * 1e3).toISOString().split("T")[0];
|
|
4581
|
-
return [
|
|
4582
|
-
`Username: ${user.id}`,
|
|
4583
|
-
`Karma: ${user.karma}`,
|
|
4584
|
-
`Created: ${created}`,
|
|
4585
|
-
user.about ? `About: ${user.about}` : null,
|
|
4586
|
-
`Submitted: ${user.submitted?.length ?? 0} items`
|
|
4587
|
-
].filter(Boolean).join("\n");
|
|
4588
|
-
}
|
|
4589
|
-
});
|
|
4590
|
-
}
|
|
4591
|
-
return tools;
|
|
4592
|
-
}
|
|
4593
|
-
};
|
|
4594
|
-
export {
|
|
4595
|
-
A2ARemoteAgent,
|
|
4596
|
-
Agent,
|
|
4597
|
-
AnthropicProvider,
|
|
4598
|
-
BaseVectorStore,
|
|
4599
|
-
EventBus,
|
|
4600
|
-
GmailToolkit,
|
|
4601
|
-
GoogleEmbedding,
|
|
4602
|
-
GoogleProvider,
|
|
4603
|
-
HackerNewsToolkit,
|
|
4604
|
-
InMemoryStorage,
|
|
4605
|
-
InMemoryVectorStore,
|
|
4606
|
-
KnowledgeBase,
|
|
4607
|
-
LLMLoop,
|
|
4608
|
-
Logger,
|
|
4609
|
-
MCPToolProvider,
|
|
4610
|
-
Memory,
|
|
4611
|
-
ModelRegistry,
|
|
4612
|
-
MongoDBStorage,
|
|
4613
|
-
MongoDBVectorStore,
|
|
4614
|
-
OllamaProvider,
|
|
4615
|
-
OpenAIEmbedding,
|
|
4616
|
-
OpenAIProvider,
|
|
4617
|
-
PgVectorStore,
|
|
4618
|
-
PostgresStorage,
|
|
4619
|
-
QdrantVectorStore,
|
|
4620
|
-
RunContext,
|
|
4621
|
-
SessionManager,
|
|
4622
|
-
SqliteStorage,
|
|
4623
|
-
Team,
|
|
4624
|
-
TeamMode,
|
|
4625
|
-
ToolExecutor,
|
|
4626
|
-
Toolkit,
|
|
4627
|
-
VertexAIProvider,
|
|
4628
|
-
WebSearchToolkit,
|
|
4629
|
-
WhatsAppToolkit,
|
|
4630
|
-
Workflow,
|
|
4631
|
-
anthropic,
|
|
4632
|
-
defineTool,
|
|
4633
|
-
getTextContent,
|
|
4634
|
-
google,
|
|
4635
|
-
isMultiModal,
|
|
4636
|
-
ollama,
|
|
4637
|
-
openai,
|
|
4638
|
-
registry,
|
|
4639
|
-
vertex
|
|
4640
|
-
};
|