@radaros/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/index.d.ts +887 -0
  2. package/dist/index.js +3462 -0
  3. package/package.json +64 -0
  4. package/src/agent/agent.ts +314 -0
  5. package/src/agent/llm-loop.ts +263 -0
  6. package/src/agent/run-context.ts +35 -0
  7. package/src/agent/types.ts +77 -0
  8. package/src/events/event-bus.ts +45 -0
  9. package/src/events/types.ts +16 -0
  10. package/src/guardrails/types.ts +5 -0
  11. package/src/hooks/types.ts +6 -0
  12. package/src/index.ts +111 -0
  13. package/src/knowledge/knowledge-base.ts +146 -0
  14. package/src/logger/logger.ts +232 -0
  15. package/src/memory/memory.ts +87 -0
  16. package/src/memory/types.ts +13 -0
  17. package/src/models/provider.ts +22 -0
  18. package/src/models/providers/anthropic.ts +330 -0
  19. package/src/models/providers/google.ts +361 -0
  20. package/src/models/providers/ollama.ts +211 -0
  21. package/src/models/providers/openai.ts +323 -0
  22. package/src/models/registry.ts +90 -0
  23. package/src/models/types.ts +112 -0
  24. package/src/session/session-manager.ts +75 -0
  25. package/src/session/types.ts +10 -0
  26. package/src/storage/driver.ts +10 -0
  27. package/src/storage/in-memory.ts +44 -0
  28. package/src/storage/mongodb.ts +70 -0
  29. package/src/storage/postgres.ts +81 -0
  30. package/src/storage/sqlite.ts +81 -0
  31. package/src/team/modes.ts +1 -0
  32. package/src/team/team.ts +323 -0
  33. package/src/team/types.ts +26 -0
  34. package/src/tools/define-tool.ts +20 -0
  35. package/src/tools/tool-executor.ts +131 -0
  36. package/src/tools/types.ts +27 -0
  37. package/src/vector/base.ts +44 -0
  38. package/src/vector/embeddings/google.ts +64 -0
  39. package/src/vector/embeddings/openai.ts +66 -0
  40. package/src/vector/in-memory.ts +115 -0
  41. package/src/vector/mongodb.ts +241 -0
  42. package/src/vector/pgvector.ts +169 -0
  43. package/src/vector/qdrant.ts +203 -0
  44. package/src/vector/types.ts +55 -0
  45. package/src/workflow/step-runner.ts +303 -0
  46. package/src/workflow/types.ts +55 -0
  47. package/src/workflow/workflow.ts +68 -0
  48. package/tsconfig.json +8 -0
@@ -0,0 +1,70 @@
1
+ import { createRequire } from "node:module";
2
+ import type { StorageDriver } from "./driver.js";
3
+
4
+ const _require = createRequire(import.meta.url);
5
+
6
+ export class MongoDBStorage implements StorageDriver {
7
+ private client: any;
8
+ private db: any;
9
+ private collection: any;
10
+
11
+ constructor(
12
+ private uri: string,
13
+ private dbName: string = "radaros",
14
+ private collectionName: string = "kv_store"
15
+ ) {
16
+ try {
17
+ const { MongoClient } = _require("mongodb");
18
+ this.client = new MongoClient(uri);
19
+ } catch {
20
+ throw new Error(
21
+ "mongodb is required for MongoDBStorage. Install it: npm install mongodb"
22
+ );
23
+ }
24
+ }
25
+
26
+ async initialize(): Promise<void> {
27
+ await this.client.connect();
28
+ this.db = this.client.db(this.dbName);
29
+ this.collection = this.db.collection(this.collectionName);
30
+ await this.collection.createIndex(
31
+ { namespace: 1, key: 1 },
32
+ { unique: true }
33
+ );
34
+ }
35
+
36
+ async get<T>(namespace: string, key: string): Promise<T | null> {
37
+ const doc = await this.collection.findOne({ namespace, key });
38
+ if (!doc) return null;
39
+ return doc.value as T;
40
+ }
41
+
42
+ async set<T>(namespace: string, key: string, value: T): Promise<void> {
43
+ await this.collection.updateOne(
44
+ { namespace, key },
45
+ { $set: { value, updatedAt: new Date() } },
46
+ { upsert: true }
47
+ );
48
+ }
49
+
50
+ async delete(namespace: string, key: string): Promise<void> {
51
+ await this.collection.deleteOne({ namespace, key });
52
+ }
53
+
54
+ async list<T>(
55
+ namespace: string,
56
+ prefix?: string
57
+ ): Promise<Array<{ key: string; value: T }>> {
58
+ const filter: Record<string, unknown> = { namespace };
59
+ if (prefix) {
60
+ filter.key = { $regex: `^${prefix}` };
61
+ }
62
+
63
+ const docs = await this.collection.find(filter).toArray();
64
+ return docs.map((doc: any) => ({ key: doc.key as string, value: doc.value as T }));
65
+ }
66
+
67
+ async close(): Promise<void> {
68
+ await this.client.close();
69
+ }
70
+ }
@@ -0,0 +1,81 @@
1
+ import { createRequire } from "node:module";
2
+ import type { StorageDriver } from "./driver.js";
3
+
4
+ const _require = createRequire(import.meta.url);
5
+
6
+ export class PostgresStorage implements StorageDriver {
7
+ private pool: any;
8
+
9
+ constructor(connectionString: string) {
10
+ try {
11
+ const { Pool } = _require("pg");
12
+ this.pool = new Pool({ connectionString });
13
+ } catch {
14
+ throw new Error(
15
+ "pg is required for PostgresStorage. Install it: npm install pg"
16
+ );
17
+ }
18
+ }
19
+
20
+ async initialize(): Promise<void> {
21
+ await this.pool.query(`
22
+ CREATE TABLE IF NOT EXISTS kv_store (
23
+ namespace TEXT NOT NULL,
24
+ key TEXT NOT NULL,
25
+ value JSONB NOT NULL,
26
+ updated_at TIMESTAMPTZ DEFAULT NOW(),
27
+ PRIMARY KEY (namespace, key)
28
+ )
29
+ `);
30
+ }
31
+
32
+ async get<T>(namespace: string, key: string): Promise<T | null> {
33
+ const result = await this.pool.query(
34
+ "SELECT value FROM kv_store WHERE namespace = $1 AND key = $2",
35
+ [namespace, key]
36
+ );
37
+ if (result.rows.length === 0) return null;
38
+ return result.rows[0].value as T;
39
+ }
40
+
41
+ async set<T>(namespace: string, key: string, value: T): Promise<void> {
42
+ await this.pool.query(
43
+ `INSERT INTO kv_store (namespace, key, value, updated_at)
44
+ VALUES ($1, $2, $3, NOW())
45
+ ON CONFLICT (namespace, key)
46
+ DO UPDATE SET value = EXCLUDED.value, updated_at = NOW()`,
47
+ [namespace, key, JSON.stringify(value)]
48
+ );
49
+ }
50
+
51
+ async delete(namespace: string, key: string): Promise<void> {
52
+ await this.pool.query(
53
+ "DELETE FROM kv_store WHERE namespace = $1 AND key = $2",
54
+ [namespace, key]
55
+ );
56
+ }
57
+
58
+ async list<T>(
59
+ namespace: string,
60
+ prefix?: string
61
+ ): Promise<Array<{ key: string; value: T }>> {
62
+ const result = prefix
63
+ ? await this.pool.query(
64
+ "SELECT key, value FROM kv_store WHERE namespace = $1 AND key LIKE $2",
65
+ [namespace, `${prefix}%`]
66
+ )
67
+ : await this.pool.query(
68
+ "SELECT key, value FROM kv_store WHERE namespace = $1",
69
+ [namespace]
70
+ );
71
+
72
+ return result.rows.map((row: { key: string; value: T }) => ({
73
+ key: row.key,
74
+ value: row.value,
75
+ }));
76
+ }
77
+
78
+ async close(): Promise<void> {
79
+ await this.pool.end();
80
+ }
81
+ }
@@ -0,0 +1,81 @@
1
+ import { createRequire } from "node:module";
2
+ import type { StorageDriver } from "./driver.js";
3
+
4
+ const _require = createRequire(import.meta.url);
5
+
6
+ export class SqliteStorage implements StorageDriver {
7
+ private db: any;
8
+
9
+ constructor(dbPath: string) {
10
+ try {
11
+ const Database = _require("better-sqlite3");
12
+ this.db = new Database(dbPath);
13
+ this.db.pragma("journal_mode = WAL");
14
+ this.db.exec(`
15
+ CREATE TABLE IF NOT EXISTS kv_store (
16
+ namespace TEXT NOT NULL,
17
+ key TEXT NOT NULL,
18
+ value TEXT NOT NULL,
19
+ updated_at TEXT DEFAULT (datetime('now')),
20
+ PRIMARY KEY (namespace, key)
21
+ )
22
+ `);
23
+ } catch {
24
+ throw new Error(
25
+ "better-sqlite3 is required for SqliteStorage. Install it: npm install better-sqlite3"
26
+ );
27
+ }
28
+ }
29
+
30
+ async get<T>(namespace: string, key: string): Promise<T | null> {
31
+ const row = this.db
32
+ .prepare("SELECT value FROM kv_store WHERE namespace = ? AND key = ?")
33
+ .get(namespace, key) as { value: string } | undefined;
34
+ if (!row) return null;
35
+ return JSON.parse(row.value) as T;
36
+ }
37
+
38
+ async set<T>(namespace: string, key: string, value: T): Promise<void> {
39
+ this.db
40
+ .prepare(
41
+ `INSERT INTO kv_store (namespace, key, value, updated_at)
42
+ VALUES (?, ?, ?, datetime('now'))
43
+ ON CONFLICT(namespace, key)
44
+ DO UPDATE SET value = excluded.value, updated_at = datetime('now')`
45
+ )
46
+ .run(namespace, key, JSON.stringify(value));
47
+ }
48
+
49
+ async delete(namespace: string, key: string): Promise<void> {
50
+ this.db
51
+ .prepare("DELETE FROM kv_store WHERE namespace = ? AND key = ?")
52
+ .run(namespace, key);
53
+ }
54
+
55
+ async list<T>(
56
+ namespace: string,
57
+ prefix?: string
58
+ ): Promise<Array<{ key: string; value: T }>> {
59
+ const rows = prefix
60
+ ? (this.db
61
+ .prepare(
62
+ "SELECT key, value FROM kv_store WHERE namespace = ? AND key LIKE ?"
63
+ )
64
+ .all(namespace, `${prefix}%`) as Array<{
65
+ key: string;
66
+ value: string;
67
+ }>)
68
+ : (this.db
69
+ .prepare("SELECT key, value FROM kv_store WHERE namespace = ?")
70
+ .all(namespace) as Array<{ key: string; value: string }>);
71
+
72
+ return rows.map((row) => ({
73
+ key: row.key,
74
+ value: JSON.parse(row.value) as T,
75
+ }));
76
+ }
77
+
78
+ async close(): Promise<void> {
79
+ this.db.close();
80
+ }
81
+ }
@@ -0,0 +1 @@
1
+ export { TeamMode } from "./types.js";
@@ -0,0 +1,323 @@
1
+ import { v4 as uuidv4 } from "uuid";
2
+ import { EventBus } from "../events/event-bus.js";
3
+ import { RunContext } from "../agent/run-context.js";
4
+ import { getTextContent, type StreamChunk } from "../models/types.js";
5
+ import type { RunOpts, RunOutput } from "../agent/types.js";
6
+ import type { Agent } from "../agent/agent.js";
7
+ import { TeamMode, type TeamConfig } from "./types.js";
8
+
9
+ interface DelegationPlan {
10
+ memberId: string;
11
+ task: string;
12
+ }
13
+
14
+ export class Team {
15
+ readonly name: string;
16
+ readonly eventBus: EventBus;
17
+
18
+ private config: TeamConfig;
19
+
20
+ constructor(config: TeamConfig) {
21
+ this.config = config;
22
+ this.name = config.name;
23
+ this.eventBus = config.eventBus ?? new EventBus();
24
+ }
25
+
26
+ async run(input: string, opts?: RunOpts): Promise<RunOutput> {
27
+ const ctx = new RunContext({
28
+ sessionId: opts?.sessionId ?? uuidv4(),
29
+ userId: opts?.userId,
30
+ metadata: opts?.metadata ?? {},
31
+ eventBus: this.eventBus,
32
+ sessionState: { ...(this.config.sessionState ?? {}) },
33
+ });
34
+
35
+ this.eventBus.emit("run.start", {
36
+ runId: ctx.runId,
37
+ agentName: this.name,
38
+ input,
39
+ });
40
+
41
+ try {
42
+ let output: RunOutput;
43
+
44
+ switch (this.config.mode) {
45
+ case TeamMode.Route:
46
+ output = await this.runRouteMode(input, ctx);
47
+ break;
48
+ case TeamMode.Broadcast:
49
+ output = await this.runBroadcastMode(input, ctx);
50
+ break;
51
+ case TeamMode.Collaborate:
52
+ output = await this.runCollaborateMode(input, ctx);
53
+ break;
54
+ case TeamMode.Coordinate:
55
+ default:
56
+ output = await this.runCoordinateMode(input, ctx);
57
+ break;
58
+ }
59
+
60
+ this.eventBus.emit("run.complete", { runId: ctx.runId, output });
61
+ return output;
62
+ } catch (error) {
63
+ const err = error instanceof Error ? error : new Error(String(error));
64
+ this.eventBus.emit("run.error", { runId: ctx.runId, error: err });
65
+ throw err;
66
+ }
67
+ }
68
+
69
+ async *stream(
70
+ input: string,
71
+ opts?: RunOpts
72
+ ): AsyncGenerator<StreamChunk> {
73
+ const result = await this.run(input, opts);
74
+ yield { type: "text", text: result.text };
75
+ yield { type: "finish", finishReason: "stop", usage: result.usage };
76
+ }
77
+
78
+ private async runCoordinateMode(
79
+ input: string,
80
+ ctx: RunContext
81
+ ): Promise<RunOutput> {
82
+ const memberDescriptions = this.buildMemberDescriptions();
83
+ const planPrompt = this.buildCoordinatorPrompt(
84
+ input,
85
+ memberDescriptions,
86
+ "coordinate"
87
+ );
88
+
89
+ const planResponse = await this.config.model.generate([
90
+ { role: "system", content: planPrompt },
91
+ { role: "user", content: input },
92
+ ]);
93
+
94
+ const delegations = this.parseDelegationPlan(
95
+ getTextContent(planResponse.message.content)
96
+ );
97
+
98
+ const memberOutputs: Array<{ memberId: string; output: RunOutput }> = [];
99
+
100
+ for (const delegation of delegations) {
101
+ const member = this.findMember(delegation.memberId);
102
+ if (!member) continue;
103
+
104
+ this.eventBus.emit("team.delegate", {
105
+ runId: ctx.runId,
106
+ memberId: delegation.memberId,
107
+ task: delegation.task,
108
+ });
109
+
110
+ const output = await member.run(delegation.task, {
111
+ sessionId: ctx.sessionId,
112
+ });
113
+ memberOutputs.push({ memberId: delegation.memberId, output });
114
+ }
115
+
116
+ const synthesisPrompt = this.buildSynthesisPrompt(
117
+ input,
118
+ memberOutputs
119
+ );
120
+ const synthesisResponse = await this.config.model.generate([
121
+ { role: "user", content: synthesisPrompt },
122
+ ]);
123
+
124
+ return {
125
+ text: getTextContent(synthesisResponse.message.content),
126
+ toolCalls: memberOutputs.flatMap((o) => o.output.toolCalls),
127
+ usage: synthesisResponse.usage,
128
+ };
129
+ }
130
+
131
+ private async runRouteMode(
132
+ input: string,
133
+ ctx: RunContext
134
+ ): Promise<RunOutput> {
135
+ const memberDescriptions = this.buildMemberDescriptions();
136
+ const routePrompt = this.buildCoordinatorPrompt(
137
+ input,
138
+ memberDescriptions,
139
+ "route"
140
+ );
141
+
142
+ const routeResponse = await this.config.model.generate([
143
+ { role: "system", content: routePrompt },
144
+ { role: "user", content: input },
145
+ ]);
146
+
147
+ const selectedName = getTextContent(routeResponse.message.content).trim();
148
+ const member = this.findMember(selectedName);
149
+
150
+ if (!member) {
151
+ return {
152
+ text: `Could not route to member "${selectedName}". Available: ${this.config.members.map((m) => m.name).join(", ")}`,
153
+ toolCalls: [],
154
+ usage: routeResponse.usage,
155
+ };
156
+ }
157
+
158
+ this.eventBus.emit("team.delegate", {
159
+ runId: ctx.runId,
160
+ memberId: member.name,
161
+ task: input,
162
+ });
163
+
164
+ return member.run(input, { sessionId: ctx.sessionId });
165
+ }
166
+
167
+ private async runBroadcastMode(
168
+ input: string,
169
+ ctx: RunContext
170
+ ): Promise<RunOutput> {
171
+ for (const member of this.config.members) {
172
+ this.eventBus.emit("team.delegate", {
173
+ runId: ctx.runId,
174
+ memberId: member.name,
175
+ task: input,
176
+ });
177
+ }
178
+
179
+ const outputs = await Promise.all(
180
+ this.config.members.map((member) =>
181
+ member.run(input, { sessionId: ctx.sessionId })
182
+ )
183
+ );
184
+
185
+ const memberOutputs = this.config.members.map((member, i) => ({
186
+ memberId: member.name,
187
+ output: outputs[i],
188
+ }));
189
+
190
+ const synthesisPrompt = this.buildSynthesisPrompt(input, memberOutputs);
191
+ const synthesisResponse = await this.config.model.generate([
192
+ { role: "user", content: synthesisPrompt },
193
+ ]);
194
+
195
+ return {
196
+ text: getTextContent(synthesisResponse.message.content),
197
+ toolCalls: outputs.flatMap((o) => o.toolCalls),
198
+ usage: synthesisResponse.usage,
199
+ };
200
+ }
201
+
202
+ private async runCollaborateMode(
203
+ input: string,
204
+ ctx: RunContext
205
+ ): Promise<RunOutput> {
206
+ const maxRounds = this.config.maxRounds ?? 3;
207
+ let currentInput = input;
208
+ let finalOutput: RunOutput | null = null;
209
+
210
+ for (let round = 0; round < maxRounds; round++) {
211
+ for (const member of this.config.members) {
212
+ this.eventBus.emit("team.delegate", {
213
+ runId: ctx.runId,
214
+ memberId: member.name,
215
+ task: currentInput,
216
+ });
217
+ }
218
+
219
+ const outputs = await Promise.all(
220
+ this.config.members.map((member) =>
221
+ member.run(currentInput, { sessionId: ctx.sessionId })
222
+ )
223
+ );
224
+
225
+ const memberOutputs = this.config.members.map((member, i) => ({
226
+ memberId: member.name,
227
+ output: outputs[i],
228
+ }));
229
+
230
+ 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.\n\n${memberOutputs.map((o) => `${o.memberId}: ${o.output.text}`).join("\n\n")}\n\nRespond with either "CONSENSUS: <final answer>" or "FOLLOW_UP: <question>"`;
231
+
232
+ const consensusResponse = await this.config.model.generate([
233
+ { role: "user", content: consensusPrompt },
234
+ ]);
235
+
236
+ const responseText = getTextContent(consensusResponse.message.content);
237
+
238
+ if (responseText.startsWith("CONSENSUS:")) {
239
+ finalOutput = {
240
+ text: responseText.slice("CONSENSUS:".length).trim(),
241
+ toolCalls: outputs.flatMap((o) => o.toolCalls),
242
+ usage: consensusResponse.usage,
243
+ };
244
+ break;
245
+ }
246
+
247
+ currentInput = responseText.startsWith("FOLLOW_UP:")
248
+ ? responseText.slice("FOLLOW_UP:".length).trim()
249
+ : responseText;
250
+ }
251
+
252
+ if (!finalOutput) {
253
+ const lastSynthesis = this.buildSynthesisPrompt(input, []);
254
+ const response = await this.config.model.generate([
255
+ { role: "user", content: lastSynthesis },
256
+ ]);
257
+ finalOutput = {
258
+ text: getTextContent(response.message.content),
259
+ toolCalls: [],
260
+ usage: response.usage,
261
+ };
262
+ }
263
+
264
+ return finalOutput!;
265
+ }
266
+
267
+ private buildMemberDescriptions(): string {
268
+ return this.config.members
269
+ .map((member) => {
270
+ const desc =
271
+ typeof member.instructions === "function"
272
+ ? "(dynamic instructions)"
273
+ : (member.instructions ?? "General-purpose agent");
274
+ return `- ${member.name}: ${desc}`;
275
+ })
276
+ .join("\n");
277
+ }
278
+
279
+ private buildCoordinatorPrompt(
280
+ input: string,
281
+ memberDescriptions: string,
282
+ mode: "coordinate" | "route"
283
+ ): string {
284
+ if (mode === "route") {
285
+ return `You are a team coordinator. Based on the user's request, select the single most appropriate team member to handle it. Available members:\n${memberDescriptions}\n\nRespond with ONLY the member name, nothing else.`;
286
+ }
287
+
288
+ return `You are a team coordinator. Break down the user's request into subtasks and delegate to appropriate team members. Available members:\n${memberDescriptions}\n\nRespond with a JSON array of delegations: [{"memberId": "name", "task": "specific task"}]\n${this.config.instructions ? `\nAdditional instructions: ${this.config.instructions}` : ""}`;
289
+ }
290
+
291
+ private buildSynthesisPrompt(
292
+ originalInput: string,
293
+ memberOutputs: Array<{ memberId: string; output: RunOutput }>
294
+ ): string {
295
+ const outputsText = memberOutputs
296
+ .map((o) => `### ${o.memberId}\n${o.output.text}`)
297
+ .join("\n\n");
298
+
299
+ return `Original request: ${originalInput}\n\nTeam member responses:\n${outputsText}\n\nSynthesize these responses into a single coherent answer.`;
300
+ }
301
+
302
+ private parseDelegationPlan(content: string): DelegationPlan[] {
303
+ try {
304
+ const jsonMatch = content.match(/\[[\s\S]*\]/);
305
+ if (jsonMatch) {
306
+ return JSON.parse(jsonMatch[0]);
307
+ }
308
+ } catch {
309
+ // fall through
310
+ }
311
+
312
+ return this.config.members.map((m) => ({
313
+ memberId: m.name,
314
+ task: content,
315
+ }));
316
+ }
317
+
318
+ private findMember(name: string): Agent | undefined {
319
+ return this.config.members.find(
320
+ (m) => m.name.toLowerCase() === name.toLowerCase()
321
+ );
322
+ }
323
+ }
@@ -0,0 +1,26 @@
1
+ import type { ModelProvider } from "../models/provider.js";
2
+ import type { StorageDriver } from "../storage/driver.js";
3
+ import type { EventBus } from "../events/event-bus.js";
4
+ import type { Agent } from "../agent/agent.js";
5
+ import type { RunOpts, RunOutput, StreamChunk } from "../agent/types.js";
6
+
7
+ export enum TeamMode {
8
+ Coordinate = "coordinate",
9
+ Route = "route",
10
+ Broadcast = "broadcast",
11
+ Collaborate = "collaborate",
12
+ }
13
+
14
+ export interface TeamConfig {
15
+ name: string;
16
+ mode: TeamMode;
17
+ model: ModelProvider;
18
+ members: Agent[];
19
+ instructions?: string;
20
+ sessionState?: Record<string, unknown>;
21
+ storage?: StorageDriver;
22
+ maxRounds?: number;
23
+ eventBus?: EventBus;
24
+ }
25
+
26
+ export type { RunOpts, RunOutput, StreamChunk };
@@ -0,0 +1,20 @@
1
+ import type { z } from "zod";
2
+ import type { RunContext } from "../agent/run-context.js";
3
+ import type { ToolDef, ToolResult } from "./types.js";
4
+
5
+ export function defineTool<T extends z.ZodObject<any>>(config: {
6
+ name: string;
7
+ description: string;
8
+ parameters: T;
9
+ execute: (
10
+ args: z.infer<T>,
11
+ ctx: RunContext
12
+ ) => Promise<string | ToolResult>;
13
+ }): ToolDef {
14
+ return {
15
+ name: config.name,
16
+ description: config.description,
17
+ parameters: config.parameters,
18
+ execute: config.execute as ToolDef["execute"],
19
+ };
20
+ }