family-ai-agent 1.0.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 (132) hide show
  1. package/.env.example +49 -0
  2. package/README.md +161 -0
  3. package/dist/cli/index.d.ts +3 -0
  4. package/dist/cli/index.d.ts.map +1 -0
  5. package/dist/cli/index.js +336 -0
  6. package/dist/cli/index.js.map +1 -0
  7. package/dist/config/index.d.ts +37 -0
  8. package/dist/config/index.d.ts.map +1 -0
  9. package/dist/config/index.js +68 -0
  10. package/dist/config/index.js.map +1 -0
  11. package/dist/config/models.d.ts +17 -0
  12. package/dist/config/models.d.ts.map +1 -0
  13. package/dist/config/models.js +128 -0
  14. package/dist/config/models.js.map +1 -0
  15. package/dist/core/agents/agent-factory.d.ts +31 -0
  16. package/dist/core/agents/agent-factory.d.ts.map +1 -0
  17. package/dist/core/agents/agent-factory.js +151 -0
  18. package/dist/core/agents/agent-factory.js.map +1 -0
  19. package/dist/core/agents/base-agent.d.ts +51 -0
  20. package/dist/core/agents/base-agent.d.ts.map +1 -0
  21. package/dist/core/agents/base-agent.js +245 -0
  22. package/dist/core/agents/base-agent.js.map +1 -0
  23. package/dist/core/agents/index.d.ts +8 -0
  24. package/dist/core/agents/index.d.ts.map +1 -0
  25. package/dist/core/agents/index.js +9 -0
  26. package/dist/core/agents/index.js.map +1 -0
  27. package/dist/core/agents/personalities/automation.d.ts +14 -0
  28. package/dist/core/agents/personalities/automation.d.ts.map +1 -0
  29. package/dist/core/agents/personalities/automation.js +146 -0
  30. package/dist/core/agents/personalities/automation.js.map +1 -0
  31. package/dist/core/agents/personalities/chat.d.ts +10 -0
  32. package/dist/core/agents/personalities/chat.d.ts.map +1 -0
  33. package/dist/core/agents/personalities/chat.js +132 -0
  34. package/dist/core/agents/personalities/chat.js.map +1 -0
  35. package/dist/core/agents/personalities/coding.d.ts +16 -0
  36. package/dist/core/agents/personalities/coding.d.ts.map +1 -0
  37. package/dist/core/agents/personalities/coding.js +166 -0
  38. package/dist/core/agents/personalities/coding.js.map +1 -0
  39. package/dist/core/agents/personalities/research.d.ts +13 -0
  40. package/dist/core/agents/personalities/research.d.ts.map +1 -0
  41. package/dist/core/agents/personalities/research.js +133 -0
  42. package/dist/core/agents/personalities/research.js.map +1 -0
  43. package/dist/core/agents/types.d.ts +102 -0
  44. package/dist/core/agents/types.d.ts.map +1 -0
  45. package/dist/core/agents/types.js +2 -0
  46. package/dist/core/agents/types.js.map +1 -0
  47. package/dist/core/orchestrator/graph.d.ts +118 -0
  48. package/dist/core/orchestrator/graph.d.ts.map +1 -0
  49. package/dist/core/orchestrator/graph.js +233 -0
  50. package/dist/core/orchestrator/graph.js.map +1 -0
  51. package/dist/database/client.d.ts +19 -0
  52. package/dist/database/client.d.ts.map +1 -0
  53. package/dist/database/client.js +95 -0
  54. package/dist/database/client.js.map +1 -0
  55. package/dist/index.d.ts +41 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +67 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/llm/openrouter-client.d.ts +45 -0
  60. package/dist/llm/openrouter-client.d.ts.map +1 -0
  61. package/dist/llm/openrouter-client.js +155 -0
  62. package/dist/llm/openrouter-client.js.map +1 -0
  63. package/dist/memory/conversation/index.d.ts +37 -0
  64. package/dist/memory/conversation/index.d.ts.map +1 -0
  65. package/dist/memory/conversation/index.js +196 -0
  66. package/dist/memory/conversation/index.js.map +1 -0
  67. package/dist/memory/index.d.ts +4 -0
  68. package/dist/memory/index.d.ts.map +1 -0
  69. package/dist/memory/index.js +5 -0
  70. package/dist/memory/index.js.map +1 -0
  71. package/dist/memory/knowledge-base/index.d.ts +51 -0
  72. package/dist/memory/knowledge-base/index.d.ts.map +1 -0
  73. package/dist/memory/knowledge-base/index.js +222 -0
  74. package/dist/memory/knowledge-base/index.js.map +1 -0
  75. package/dist/memory/longterm/vector-store.d.ts +44 -0
  76. package/dist/memory/longterm/vector-store.d.ts.map +1 -0
  77. package/dist/memory/longterm/vector-store.js +229 -0
  78. package/dist/memory/longterm/vector-store.js.map +1 -0
  79. package/dist/safety/audit-logger.d.ts +68 -0
  80. package/dist/safety/audit-logger.d.ts.map +1 -0
  81. package/dist/safety/audit-logger.js +215 -0
  82. package/dist/safety/audit-logger.js.map +1 -0
  83. package/dist/safety/guardrails/input-guardrail.d.ts +21 -0
  84. package/dist/safety/guardrails/input-guardrail.d.ts.map +1 -0
  85. package/dist/safety/guardrails/input-guardrail.js +145 -0
  86. package/dist/safety/guardrails/input-guardrail.js.map +1 -0
  87. package/dist/safety/guardrails/output-guardrail.d.ts +18 -0
  88. package/dist/safety/guardrails/output-guardrail.d.ts.map +1 -0
  89. package/dist/safety/guardrails/output-guardrail.js +125 -0
  90. package/dist/safety/guardrails/output-guardrail.js.map +1 -0
  91. package/dist/safety/index.d.ts +4 -0
  92. package/dist/safety/index.d.ts.map +1 -0
  93. package/dist/safety/index.js +5 -0
  94. package/dist/safety/index.js.map +1 -0
  95. package/dist/utils/errors.d.ts +36 -0
  96. package/dist/utils/errors.d.ts.map +1 -0
  97. package/dist/utils/errors.js +94 -0
  98. package/dist/utils/errors.js.map +1 -0
  99. package/dist/utils/logger.d.ts +8 -0
  100. package/dist/utils/logger.d.ts.map +1 -0
  101. package/dist/utils/logger.js +47 -0
  102. package/dist/utils/logger.js.map +1 -0
  103. package/docker/init-db.sql +149 -0
  104. package/docker/sandbox/Dockerfile.sandbox +29 -0
  105. package/docker-compose.yml +61 -0
  106. package/package.json +80 -0
  107. package/src/cli/index.ts +392 -0
  108. package/src/config/index.ts +85 -0
  109. package/src/config/models.ts +156 -0
  110. package/src/core/agents/agent-factory.ts +192 -0
  111. package/src/core/agents/base-agent.ts +333 -0
  112. package/src/core/agents/index.ts +27 -0
  113. package/src/core/agents/personalities/automation.ts +202 -0
  114. package/src/core/agents/personalities/chat.ts +159 -0
  115. package/src/core/agents/personalities/coding.ts +227 -0
  116. package/src/core/agents/personalities/research.ts +177 -0
  117. package/src/core/agents/types.ts +124 -0
  118. package/src/core/orchestrator/graph.ts +305 -0
  119. package/src/database/client.ts +109 -0
  120. package/src/index.ts +104 -0
  121. package/src/llm/openrouter-client.ts +218 -0
  122. package/src/memory/conversation/index.ts +313 -0
  123. package/src/memory/index.ts +23 -0
  124. package/src/memory/knowledge-base/index.ts +357 -0
  125. package/src/memory/longterm/vector-store.ts +364 -0
  126. package/src/safety/audit-logger.ts +357 -0
  127. package/src/safety/guardrails/input-guardrail.ts +191 -0
  128. package/src/safety/guardrails/output-guardrail.ts +160 -0
  129. package/src/safety/index.ts +21 -0
  130. package/src/utils/errors.ts +120 -0
  131. package/src/utils/logger.ts +74 -0
  132. package/tsconfig.json +37 -0
@@ -0,0 +1,392 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import inquirer from 'inquirer';
5
+ import chalk from 'chalk';
6
+ import ora from 'ora';
7
+ import boxen from 'boxen';
8
+
9
+ import { runOrchestrator, streamOrchestrator } from '../core/orchestrator/graph.js';
10
+ import { getVectorStore } from '../memory/longterm/vector-store.js';
11
+ import { getConversationMemory } from '../memory/conversation/index.js';
12
+ import { getKnowledgeBase } from '../memory/knowledge-base/index.js';
13
+ import { validateInput } from '../safety/guardrails/input-guardrail.js';
14
+ import { validateOutput } from '../safety/guardrails/output-guardrail.js';
15
+ import { initDatabase, closePool } from '../database/client.js';
16
+ import { config, isDevelopment } from '../config/index.js';
17
+ import { createLogger } from '../utils/logger.js';
18
+ import { nanoid } from 'nanoid';
19
+
20
+ const logger = createLogger('CLI');
21
+ const program = new Command();
22
+
23
+ // Display welcome banner
24
+ function showBanner(): void {
25
+ const banner = boxen(
26
+ chalk.bold.cyan('Family AI Agent') +
27
+ '\n\n' +
28
+ chalk.white('Your AI Family - Ready to Help') +
29
+ '\n' +
30
+ chalk.gray('Kakak (Chat) | Researcher | Coder | Automator'),
31
+ {
32
+ padding: 1,
33
+ margin: 1,
34
+ borderStyle: 'round',
35
+ borderColor: 'cyan',
36
+ }
37
+ );
38
+ console.log(banner);
39
+ }
40
+
41
+ // Interactive chat mode
42
+ async function startInteractiveChat(): Promise<void> {
43
+ showBanner();
44
+
45
+ const threadId = `thread-${nanoid(8)}`;
46
+ const conversationMemory = getConversationMemory();
47
+ const vectorStore = getVectorStore();
48
+
49
+ console.log(chalk.gray(`\nSession: ${threadId}`));
50
+ console.log(chalk.gray('Type "exit" or "quit" to end the session'));
51
+ console.log(chalk.gray('Type "clear" to start a new conversation'));
52
+ console.log(chalk.gray('Type "memory" to view recent memories'));
53
+ console.log(chalk.gray('Type "help" for more commands\n'));
54
+
55
+ let conversationId: string | null = null;
56
+
57
+ while (true) {
58
+ try {
59
+ const { input } = await inquirer.prompt<{ input: string }>([
60
+ {
61
+ type: 'input',
62
+ name: 'input',
63
+ message: chalk.green('You:'),
64
+ prefix: '',
65
+ },
66
+ ]);
67
+
68
+ const trimmedInput = input.trim();
69
+
70
+ // Handle special commands
71
+ if (['exit', 'quit', 'bye'].includes(trimmedInput.toLowerCase())) {
72
+ console.log(chalk.cyan('\nGoodbye! See you next time. 👋\n'));
73
+ break;
74
+ }
75
+
76
+ if (trimmedInput.toLowerCase() === 'clear') {
77
+ conversationId = null;
78
+ console.log(chalk.yellow('\nConversation cleared. Starting fresh!\n'));
79
+ continue;
80
+ }
81
+
82
+ if (trimmedInput.toLowerCase() === 'help') {
83
+ showHelp();
84
+ continue;
85
+ }
86
+
87
+ if (trimmedInput.toLowerCase() === 'memory') {
88
+ await showMemories();
89
+ continue;
90
+ }
91
+
92
+ if (!trimmedInput) {
93
+ continue;
94
+ }
95
+
96
+ // Validate input
97
+ const inputValidation = await validateInput(trimmedInput);
98
+ if (!inputValidation.valid) {
99
+ console.log(chalk.red(`\n❌ ${inputValidation.blockedReason}\n`));
100
+ continue;
101
+ }
102
+
103
+ if (inputValidation.warnings.length > 0) {
104
+ for (const warning of inputValidation.warnings) {
105
+ console.log(chalk.yellow(`⚠️ ${warning}`));
106
+ }
107
+ }
108
+
109
+ // Create conversation if needed
110
+ if (!conversationId) {
111
+ conversationId = await conversationMemory.createConversation(threadId);
112
+ }
113
+
114
+ // Save user message
115
+ await conversationMemory.addMessage(conversationId, 'user', trimmedInput);
116
+
117
+ // Retrieve relevant memories
118
+ const memories = await vectorStore.search(trimmedInput, { limit: 3 });
119
+
120
+ // Show thinking indicator
121
+ const spinner = ora({
122
+ text: chalk.gray('Thinking...'),
123
+ spinner: 'dots',
124
+ }).start();
125
+
126
+ try {
127
+ // Run the orchestrator
128
+ const result = await runOrchestrator(
129
+ inputValidation.sanitizedInput,
130
+ { threadId, conversationId },
131
+ memories
132
+ );
133
+
134
+ spinner.stop();
135
+
136
+ // Validate and display output
137
+ const outputValidation = validateOutput(result.response);
138
+ const response = outputValidation.sanitizedOutput;
139
+
140
+ if (outputValidation.warnings.length > 0) {
141
+ for (const warning of outputValidation.warnings) {
142
+ console.log(chalk.yellow(`⚠️ ${warning}`));
143
+ }
144
+ }
145
+
146
+ // Display response with agent info
147
+ const agentNames = result.activeAgents?.map((a) => a.name).join(', ') || 'AI';
148
+ console.log(chalk.cyan(`\n${agentNames}:`));
149
+ console.log(response);
150
+ console.log();
151
+
152
+ // Save assistant message
153
+ await conversationMemory.addMessage(conversationId, 'assistant', response);
154
+
155
+ // Store important information in long-term memory (simple heuristic)
156
+ if (response.length > 200) {
157
+ await vectorStore.store(
158
+ `Q: ${trimmedInput}\nA: ${response.slice(0, 500)}`,
159
+ 'episodic',
160
+ { importance: 0.6, metadata: { threadId } }
161
+ );
162
+ }
163
+ } catch (error) {
164
+ spinner.stop();
165
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
166
+ console.log(chalk.red(`\n❌ Error: ${errorMessage}\n`));
167
+ logger.error('Chat error', { error: errorMessage });
168
+ }
169
+ } catch (error) {
170
+ // Handle Ctrl+C gracefully
171
+ if ((error as { name?: string }).name === 'ExitPromptError') {
172
+ console.log(chalk.cyan('\n\nGoodbye! 👋\n'));
173
+ break;
174
+ }
175
+ throw error;
176
+ }
177
+ }
178
+ }
179
+
180
+ // Show help
181
+ function showHelp(): void {
182
+ console.log(chalk.bold('\n📚 Available Commands:\n'));
183
+ console.log(chalk.white(' exit, quit, bye') + chalk.gray(' - End the session'));
184
+ console.log(chalk.white(' clear') + chalk.gray(' - Start a new conversation'));
185
+ console.log(chalk.white(' memory') + chalk.gray(' - View recent memories'));
186
+ console.log(chalk.white(' help') + chalk.gray(' - Show this help message'));
187
+ console.log();
188
+ console.log(chalk.bold('💡 Tips:\n'));
189
+ console.log(chalk.gray(' • Ask about coding, research, or automation'));
190
+ console.log(chalk.gray(' • The system will route to the best agent'));
191
+ console.log(chalk.gray(' • Memories are saved for future context'));
192
+ console.log();
193
+ }
194
+
195
+ // Show recent memories
196
+ async function showMemories(): Promise<void> {
197
+ console.log(chalk.bold('\n🧠 Recent Memories:\n'));
198
+
199
+ try {
200
+ const vectorStore = getVectorStore();
201
+ const memories = await vectorStore.search('recent conversation', {
202
+ limit: 5,
203
+ minSimilarity: 0.3,
204
+ });
205
+
206
+ if (memories.length === 0) {
207
+ console.log(chalk.gray(' No memories stored yet.'));
208
+ } else {
209
+ for (const memory of memories) {
210
+ console.log(chalk.white(` [${memory.type}] `) + chalk.gray(memory.content.slice(0, 100) + '...'));
211
+ }
212
+ }
213
+ } catch {
214
+ console.log(chalk.gray(' Unable to retrieve memories.'));
215
+ }
216
+
217
+ console.log();
218
+ }
219
+
220
+ // Single query mode
221
+ async function runQuery(query: string): Promise<void> {
222
+ const spinner = ora('Processing...').start();
223
+
224
+ try {
225
+ const inputValidation = await validateInput(query);
226
+ if (!inputValidation.valid) {
227
+ spinner.fail(inputValidation.blockedReason);
228
+ return;
229
+ }
230
+
231
+ const vectorStore = getVectorStore();
232
+ const memories = await vectorStore.search(query, { limit: 3 });
233
+
234
+ const result = await runOrchestrator(
235
+ inputValidation.sanitizedInput,
236
+ {},
237
+ memories
238
+ );
239
+
240
+ spinner.stop();
241
+
242
+ const outputValidation = validateOutput(result.response);
243
+ console.log(outputValidation.sanitizedOutput);
244
+ } catch (error) {
245
+ spinner.fail('Query failed');
246
+ console.error(error instanceof Error ? error.message : 'Unknown error');
247
+ process.exit(1);
248
+ }
249
+ }
250
+
251
+ // Main program setup
252
+ program
253
+ .name('family-ai')
254
+ .description('Family AI Agent - Your AI Family for All Tasks')
255
+ .version('1.0.0');
256
+
257
+ program
258
+ .command('chat')
259
+ .description('Start interactive chat mode')
260
+ .action(async () => {
261
+ try {
262
+ await initDatabase();
263
+ await startInteractiveChat();
264
+ } catch (error) {
265
+ console.error(chalk.red('Failed to start chat:'), error);
266
+ process.exit(1);
267
+ } finally {
268
+ await closePool();
269
+ }
270
+ });
271
+
272
+ program
273
+ .command('ask <query>')
274
+ .description('Ask a single question')
275
+ .action(async (query: string) => {
276
+ try {
277
+ await initDatabase();
278
+ await runQuery(query);
279
+ } catch (error) {
280
+ console.error(chalk.red('Failed:'), error);
281
+ process.exit(1);
282
+ } finally {
283
+ await closePool();
284
+ }
285
+ });
286
+
287
+ program
288
+ .command('upload <filepath>')
289
+ .description('Upload a document to the knowledge base')
290
+ .action(async (filepath: string) => {
291
+ const spinner = ora('Uploading document...').start();
292
+
293
+ try {
294
+ await initDatabase();
295
+
296
+ const fs = await import('fs/promises');
297
+ const path = await import('path');
298
+
299
+ const content = await fs.readFile(filepath, 'utf-8');
300
+ const filename = path.basename(filepath);
301
+ const stats = await fs.stat(filepath);
302
+
303
+ const kb = getKnowledgeBase();
304
+ const docId = await kb.addDocument(content, filename, {
305
+ fileSize: stats.size,
306
+ fileType: path.extname(filepath),
307
+ });
308
+
309
+ spinner.succeed(`Document uploaded: ${docId}`);
310
+ } catch (error) {
311
+ spinner.fail('Upload failed');
312
+ console.error(error instanceof Error ? error.message : 'Unknown error');
313
+ process.exit(1);
314
+ } finally {
315
+ await closePool();
316
+ }
317
+ });
318
+
319
+ program
320
+ .command('search <query>')
321
+ .description('Search the knowledge base')
322
+ .option('-l, --limit <number>', 'Maximum results', '5')
323
+ .action(async (query: string, options: { limit: string }) => {
324
+ const spinner = ora('Searching...').start();
325
+
326
+ try {
327
+ await initDatabase();
328
+
329
+ const kb = getKnowledgeBase();
330
+ const results = await kb.search(query, {
331
+ limit: parseInt(options.limit, 10),
332
+ });
333
+
334
+ spinner.stop();
335
+
336
+ if (results.length === 0) {
337
+ console.log(chalk.yellow('No results found.'));
338
+ } else {
339
+ console.log(chalk.bold(`\nFound ${results.length} results:\n`));
340
+ for (const result of results) {
341
+ console.log(chalk.white(`📄 ${result.filename}`));
342
+ console.log(chalk.gray(` Similarity: ${(result.similarity * 100).toFixed(1)}%`));
343
+ console.log(chalk.gray(` ${result.content.slice(0, 150)}...`));
344
+ console.log();
345
+ }
346
+ }
347
+ } catch (error) {
348
+ spinner.fail('Search failed');
349
+ console.error(error instanceof Error ? error.message : 'Unknown error');
350
+ process.exit(1);
351
+ } finally {
352
+ await closePool();
353
+ }
354
+ });
355
+
356
+ program
357
+ .command('status')
358
+ .description('Show system status')
359
+ .action(async () => {
360
+ try {
361
+ await initDatabase();
362
+
363
+ console.log(chalk.bold('\n🤖 Family AI Agent Status\n'));
364
+ console.log(chalk.white('Database:'), chalk.green('Connected'));
365
+ console.log(chalk.white('Environment:'), chalk.cyan(config.NODE_ENV));
366
+ console.log(chalk.white('Default Model:'), chalk.cyan(config.DEFAULT_MODEL));
367
+ console.log(chalk.white('Safety Filters:'), config.ENABLE_CONTENT_FILTER ? chalk.green('Enabled') : chalk.yellow('Disabled'));
368
+ console.log(chalk.white('Audit Logging:'), config.ENABLE_AUDIT_LOGGING ? chalk.green('Enabled') : chalk.yellow('Disabled'));
369
+ console.log();
370
+ } catch (error) {
371
+ console.log(chalk.white('Database:'), chalk.red('Disconnected'));
372
+ console.error(error instanceof Error ? error.message : 'Unknown error');
373
+ } finally {
374
+ await closePool();
375
+ }
376
+ });
377
+
378
+ // Handle unhandled rejections
379
+ process.on('unhandledRejection', (error) => {
380
+ console.error(chalk.red('Unhandled error:'), error);
381
+ process.exit(1);
382
+ });
383
+
384
+ // Parse command line arguments
385
+ program.parse();
386
+
387
+ // If no command specified, start interactive chat
388
+ if (!process.argv.slice(2).length) {
389
+ initDatabase()
390
+ .then(() => startInteractiveChat())
391
+ .finally(() => closePool());
392
+ }
@@ -0,0 +1,85 @@
1
+ import { config as dotenvConfig } from 'dotenv';
2
+ import { z } from 'zod';
3
+
4
+ dotenvConfig();
5
+
6
+ const envSchema = z.object({
7
+ // OpenRouter
8
+ OPENROUTER_API_KEY: z.string().min(1, 'OpenRouter API key is required'),
9
+ OPENROUTER_BASE_URL: z.string().url().default('https://openrouter.ai/api/v1'),
10
+
11
+ // Models
12
+ DEFAULT_MODEL: z.string().default('anthropic/claude-3.5-sonnet'),
13
+ FAST_MODEL: z.string().default('anthropic/claude-3-haiku'),
14
+ EMBEDDING_MODEL: z.string().default('openai/text-embedding-3-small'),
15
+
16
+ // Database
17
+ DB_HOST: z.string().default('localhost'),
18
+ DB_PORT: z.coerce.number().default(5432),
19
+ DB_USER: z.string().default('familyai'),
20
+ DB_PASSWORD: z.string().default('familyai123'),
21
+ DB_NAME: z.string().default('familyai'),
22
+ DATABASE_URL: z.string().optional(),
23
+
24
+ // Redis
25
+ REDIS_HOST: z.string().default('localhost'),
26
+ REDIS_PORT: z.coerce.number().default(6379),
27
+ REDIS_URL: z.string().optional(),
28
+
29
+ // API
30
+ API_PORT: z.coerce.number().default(3000),
31
+ API_HOST: z.string().default('0.0.0.0'),
32
+
33
+ // Safety
34
+ ENABLE_CONTENT_FILTER: z.coerce.boolean().default(true),
35
+ ENABLE_PII_DETECTION: z.coerce.boolean().default(true),
36
+ ENABLE_AUDIT_LOGGING: z.coerce.boolean().default(true),
37
+ MAX_TOKENS_PER_REQUEST: z.coerce.number().default(4096),
38
+ RATE_LIMIT_MAX: z.coerce.number().default(100),
39
+ RATE_LIMIT_WINDOW_MS: z.coerce.number().default(60000),
40
+
41
+ // Sandbox
42
+ SANDBOX_ENABLED: z.coerce.boolean().default(true),
43
+ SANDBOX_TIMEOUT_MS: z.coerce.number().default(30000),
44
+ SANDBOX_MEMORY_LIMIT_MB: z.coerce.number().default(256),
45
+
46
+ // Logging
47
+ LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
48
+ LOG_FORMAT: z.enum(['json', 'pretty']).default('json'),
49
+
50
+ // Web Search
51
+ TAVILY_API_KEY: z.string().optional(),
52
+ SERP_API_KEY: z.string().optional(),
53
+
54
+ // Environment
55
+ NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
56
+ });
57
+
58
+ type EnvConfig = z.infer<typeof envSchema>;
59
+
60
+ function loadConfig(): EnvConfig {
61
+ const parsed = envSchema.safeParse(process.env);
62
+
63
+ if (!parsed.success) {
64
+ console.error('Configuration validation failed:');
65
+ console.error(parsed.error.format());
66
+ throw new Error('Invalid configuration. Check your .env file.');
67
+ }
68
+
69
+ return parsed.data;
70
+ }
71
+
72
+ export const config = loadConfig();
73
+
74
+ export const getDatabaseUrl = (): string => {
75
+ if (config.DATABASE_URL) return config.DATABASE_URL;
76
+ return `postgresql://${config.DB_USER}:${config.DB_PASSWORD}@${config.DB_HOST}:${config.DB_PORT}/${config.DB_NAME}`;
77
+ };
78
+
79
+ export const getRedisUrl = (): string => {
80
+ if (config.REDIS_URL) return config.REDIS_URL;
81
+ return `redis://${config.REDIS_HOST}:${config.REDIS_PORT}`;
82
+ };
83
+
84
+ export const isProduction = (): boolean => config.NODE_ENV === 'production';
85
+ export const isDevelopment = (): boolean => config.NODE_ENV === 'development';
@@ -0,0 +1,156 @@
1
+ // OpenRouter Model Configurations
2
+ export interface ModelConfig {
3
+ id: string;
4
+ name: string;
5
+ contextWindow: number;
6
+ maxOutput: number;
7
+ costPer1kInput: number;
8
+ costPer1kOutput: number;
9
+ capabilities: ModelCapability[];
10
+ }
11
+
12
+ export type ModelCapability =
13
+ | 'chat'
14
+ | 'code'
15
+ | 'reasoning'
16
+ | 'vision'
17
+ | 'function_calling'
18
+ | 'json_mode';
19
+
20
+ export const AVAILABLE_MODELS: Record<string, ModelConfig> = {
21
+ // Anthropic Models
22
+ 'anthropic/claude-3.5-sonnet': {
23
+ id: 'anthropic/claude-3.5-sonnet',
24
+ name: 'Claude 3.5 Sonnet',
25
+ contextWindow: 200000,
26
+ maxOutput: 8192,
27
+ costPer1kInput: 0.003,
28
+ costPer1kOutput: 0.015,
29
+ capabilities: ['chat', 'code', 'reasoning', 'vision', 'function_calling', 'json_mode'],
30
+ },
31
+ 'anthropic/claude-3-haiku': {
32
+ id: 'anthropic/claude-3-haiku',
33
+ name: 'Claude 3 Haiku',
34
+ contextWindow: 200000,
35
+ maxOutput: 4096,
36
+ costPer1kInput: 0.00025,
37
+ costPer1kOutput: 0.00125,
38
+ capabilities: ['chat', 'code', 'vision', 'function_calling'],
39
+ },
40
+ 'anthropic/claude-3-opus': {
41
+ id: 'anthropic/claude-3-opus',
42
+ name: 'Claude 3 Opus',
43
+ contextWindow: 200000,
44
+ maxOutput: 4096,
45
+ costPer1kInput: 0.015,
46
+ costPer1kOutput: 0.075,
47
+ capabilities: ['chat', 'code', 'reasoning', 'vision', 'function_calling', 'json_mode'],
48
+ },
49
+
50
+ // OpenAI Models
51
+ 'openai/gpt-4-turbo': {
52
+ id: 'openai/gpt-4-turbo',
53
+ name: 'GPT-4 Turbo',
54
+ contextWindow: 128000,
55
+ maxOutput: 4096,
56
+ costPer1kInput: 0.01,
57
+ costPer1kOutput: 0.03,
58
+ capabilities: ['chat', 'code', 'reasoning', 'vision', 'function_calling', 'json_mode'],
59
+ },
60
+ 'openai/gpt-4o': {
61
+ id: 'openai/gpt-4o',
62
+ name: 'GPT-4o',
63
+ contextWindow: 128000,
64
+ maxOutput: 16384,
65
+ costPer1kInput: 0.005,
66
+ costPer1kOutput: 0.015,
67
+ capabilities: ['chat', 'code', 'reasoning', 'vision', 'function_calling', 'json_mode'],
68
+ },
69
+ 'openai/gpt-4o-mini': {
70
+ id: 'openai/gpt-4o-mini',
71
+ name: 'GPT-4o Mini',
72
+ contextWindow: 128000,
73
+ maxOutput: 16384,
74
+ costPer1kInput: 0.00015,
75
+ costPer1kOutput: 0.0006,
76
+ capabilities: ['chat', 'code', 'vision', 'function_calling', 'json_mode'],
77
+ },
78
+
79
+ // Google Models
80
+ 'google/gemini-pro-1.5': {
81
+ id: 'google/gemini-pro-1.5',
82
+ name: 'Gemini Pro 1.5',
83
+ contextWindow: 1000000,
84
+ maxOutput: 8192,
85
+ costPer1kInput: 0.00125,
86
+ costPer1kOutput: 0.005,
87
+ capabilities: ['chat', 'code', 'reasoning', 'vision', 'function_calling'],
88
+ },
89
+
90
+ // Meta Models
91
+ 'meta-llama/llama-3.1-70b-instruct': {
92
+ id: 'meta-llama/llama-3.1-70b-instruct',
93
+ name: 'Llama 3.1 70B',
94
+ contextWindow: 131072,
95
+ maxOutput: 4096,
96
+ costPer1kInput: 0.00059,
97
+ costPer1kOutput: 0.00079,
98
+ capabilities: ['chat', 'code', 'function_calling'],
99
+ },
100
+
101
+ // Embedding Models
102
+ 'openai/text-embedding-3-small': {
103
+ id: 'openai/text-embedding-3-small',
104
+ name: 'Text Embedding 3 Small',
105
+ contextWindow: 8191,
106
+ maxOutput: 1536,
107
+ costPer1kInput: 0.00002,
108
+ costPer1kOutput: 0,
109
+ capabilities: [],
110
+ },
111
+ };
112
+
113
+ export type AgentRole = 'supervisor' | 'research' | 'coding' | 'automation' | 'chat';
114
+
115
+ // Recommended models for each agent role
116
+ export const AGENT_MODEL_RECOMMENDATIONS: Record<AgentRole, string[]> = {
117
+ supervisor: [
118
+ 'anthropic/claude-3.5-sonnet',
119
+ 'openai/gpt-4o',
120
+ 'anthropic/claude-3-opus',
121
+ ],
122
+ research: [
123
+ 'anthropic/claude-3.5-sonnet',
124
+ 'google/gemini-pro-1.5',
125
+ 'openai/gpt-4o',
126
+ ],
127
+ coding: [
128
+ 'anthropic/claude-3.5-sonnet',
129
+ 'openai/gpt-4o',
130
+ 'meta-llama/llama-3.1-70b-instruct',
131
+ ],
132
+ automation: [
133
+ 'anthropic/claude-3-haiku',
134
+ 'openai/gpt-4o-mini',
135
+ 'meta-llama/llama-3.1-70b-instruct',
136
+ ],
137
+ chat: [
138
+ 'anthropic/claude-3.5-sonnet',
139
+ 'openai/gpt-4o',
140
+ 'anthropic/claude-3-haiku',
141
+ ],
142
+ };
143
+
144
+ export function getModelConfig(modelId: string): ModelConfig | undefined {
145
+ return AVAILABLE_MODELS[modelId];
146
+ }
147
+
148
+ export function getRecommendedModel(role: AgentRole): string {
149
+ const recommendations = AGENT_MODEL_RECOMMENDATIONS[role];
150
+ return recommendations[0] ?? 'anthropic/claude-3.5-sonnet';
151
+ }
152
+
153
+ export function hasCapability(modelId: string, capability: ModelCapability): boolean {
154
+ const model = AVAILABLE_MODELS[modelId];
155
+ return model?.capabilities.includes(capability) ?? false;
156
+ }