sam-coder-cli 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 (43) hide show
  1. package/README.md +59 -0
  2. package/ai-assistant-0.0.1.vsix +0 -0
  3. package/bin/agi-cli.js +815 -0
  4. package/bin/agi-cli.js.bak +352 -0
  5. package/bin/agi-cli.js.new +328 -0
  6. package/bin/config.json +3 -0
  7. package/bin/ui.js +42 -0
  8. package/dist/agentUtils.js +539 -0
  9. package/dist/agentUtils.js.map +1 -0
  10. package/dist/aiAssistantViewProvider.js +2098 -0
  11. package/dist/aiAssistantViewProvider.js.map +1 -0
  12. package/dist/extension.js +117 -0
  13. package/dist/extension.js.map +1 -0
  14. package/dist/fetch-polyfill.js +9 -0
  15. package/dist/fetch-polyfill.js.map +1 -0
  16. package/foldersnake/snake_game.py +125 -0
  17. package/media/ai-icon.png +0 -0
  18. package/media/ai-icon.svg +5 -0
  19. package/media/infinity-icon.svg +4 -0
  20. package/out/agentUtils.d.ts +28 -0
  21. package/out/agentUtils.d.ts.map +1 -0
  22. package/out/agentUtils.js +539 -0
  23. package/out/agentUtils.js.map +1 -0
  24. package/out/aiAssistantViewProvider.d.ts +58 -0
  25. package/out/aiAssistantViewProvider.d.ts.map +1 -0
  26. package/out/aiAssistantViewProvider.js +2098 -0
  27. package/out/aiAssistantViewProvider.js.map +1 -0
  28. package/out/extension.d.ts +4 -0
  29. package/out/extension.d.ts.map +1 -0
  30. package/out/extension.js +117 -0
  31. package/out/extension.js.map +1 -0
  32. package/out/fetch-polyfill.d.ts +11 -0
  33. package/out/fetch-polyfill.d.ts.map +1 -0
  34. package/out/fetch-polyfill.js +9 -0
  35. package/out/fetch-polyfill.js.map +1 -0
  36. package/package.json +31 -0
  37. package/src/agentUtils.ts +583 -0
  38. package/src/aiAssistantViewProvider.ts +2264 -0
  39. package/src/cliAgentUtils.js +73 -0
  40. package/src/extension.ts +112 -0
  41. package/src/fetch-polyfill.ts +11 -0
  42. package/tsconfig.json +24 -0
  43. package/webpack.config.js +45 -0
package/bin/agi-cli.js ADDED
@@ -0,0 +1,815 @@
1
+ #!/usr/bin/env node
2
+
3
+ const ui = require('./ui.js');
4
+ const readline = require('readline');
5
+ const path = require('path');
6
+ const fs = require('fs').promises;
7
+ const { exec } = require('child_process');
8
+ const util = require('util');
9
+ const execAsync = util.promisify(exec);
10
+
11
+ // Configuration
12
+ const CONFIG_PATH = path.join(__dirname, 'config.json');
13
+ let OPENROUTER_API_KEY;
14
+ const MODEL = 'deepseek/deepseek-chat-v3-0324:free';
15
+ const API_BASE_URL = 'https://openrouter.ai/api/v1';
16
+
17
+ // Tool/Function definitions for the AI
18
+ const tools = [
19
+ {
20
+ type: 'function',
21
+ function: {
22
+ name: 'readFile',
23
+ description: 'Read the contents of a file',
24
+ parameters: {
25
+ type: 'object',
26
+ properties: {
27
+ path: { type: 'string', description: 'Path to the file to read' }
28
+ },
29
+ required: ['path']
30
+ }
31
+ }
32
+ },
33
+ {
34
+ type: 'function',
35
+ function: {
36
+ name: 'writeFile',
37
+ description: 'Write content to a file',
38
+ parameters: {
39
+ type: 'object',
40
+ properties: {
41
+ path: { type: 'string', description: 'Path to the file to write' },
42
+ content: { type: 'string', description: 'Content to write to the file' }
43
+ },
44
+ required: ['path', 'content']
45
+ }
46
+ }
47
+ },
48
+ {
49
+ type: 'function',
50
+ function: {
51
+ name: 'editFile',
52
+ description: 'Edit specific parts of a file',
53
+ parameters: {
54
+ type: 'object',
55
+ properties: {
56
+ path: { type: 'string', description: 'Path to the file to edit' },
57
+ edits: {
58
+ type: 'object',
59
+ properties: {
60
+ operations: {
61
+ type: 'array',
62
+ items: {
63
+ type: 'object',
64
+ properties: {
65
+ type: {
66
+ type: 'string',
67
+ enum: ['replace', 'insert', 'delete'],
68
+ description: 'Type of edit operation'
69
+ },
70
+ startLine: {
71
+ type: 'number',
72
+ description: 'Starting line number (1-based)'
73
+ },
74
+ endLine: {
75
+ type: 'number',
76
+ description: 'Ending line number (1-based, inclusive)'
77
+ },
78
+ newText: {
79
+ type: 'string',
80
+ description: 'New text to insert or replace with'
81
+ },
82
+ pattern: {
83
+ type: 'string',
84
+ description: 'Pattern to search for (for replace operations)'
85
+ },
86
+ replacement: {
87
+ type: 'string',
88
+ description: 'Replacement text (for pattern-based replace)'
89
+ },
90
+ flags: {
91
+ type: 'string',
92
+ description: 'Regex flags (e.g., "g" for global)'
93
+ },
94
+ position: {
95
+ type: 'string',
96
+ enum: ['start', 'end'],
97
+ description: 'Where to insert (only for insert operations)'
98
+ },
99
+ line: {
100
+ type: 'number',
101
+ description: 'Line number to insert at (for line-based insert)'
102
+ }
103
+ },
104
+ required: ['type'],
105
+ oneOf: [
106
+ {
107
+ properties: {
108
+ type: { const: 'replace' },
109
+ startLine: { type: 'number' },
110
+ endLine: { type: 'number' },
111
+ newText: { type: 'string' }
112
+ },
113
+ required: ['startLine', 'endLine', 'newText']
114
+ },
115
+ {
116
+ properties: {
117
+ type: { const: 'replace' },
118
+ pattern: { type: 'string' },
119
+ replacement: { type: 'string' },
120
+ flags: { type: 'string' }
121
+ },
122
+ required: ['pattern', 'replacement']
123
+ },
124
+ {
125
+ properties: {
126
+ type: { const: 'insert' },
127
+ position: { type: 'string', enum: ['start', 'end'] },
128
+ text: { type: 'string' }
129
+ },
130
+ required: ['position', 'text']
131
+ },
132
+ {
133
+ properties: {
134
+ type: { const: 'insert' },
135
+ line: { type: 'number' },
136
+ text: { type: 'string' }
137
+ },
138
+ required: ['line', 'text']
139
+ },
140
+ {
141
+ properties: {
142
+ type: { const: 'delete' },
143
+ startLine: { type: 'number' },
144
+ endLine: { type: 'number' }
145
+ },
146
+ required: ['startLine', 'endLine']
147
+ }
148
+ ]
149
+ }
150
+ }
151
+ },
152
+ required: ['operations']
153
+ }
154
+ },
155
+ required: ['path', 'edits']
156
+ }
157
+ }
158
+ },
159
+ {
160
+ type: 'function',
161
+ function: {
162
+ name: 'runCommand',
163
+ description: 'Execute a shell command',
164
+ parameters: {
165
+ type: 'object',
166
+ properties: {
167
+ command: { type: 'string', description: 'Command to execute' }
168
+ },
169
+ required: ['command']
170
+ }
171
+ }
172
+ },
173
+ {
174
+ type: 'function',
175
+ function: {
176
+ name: 'searchFiles',
177
+ description: 'Search for files using a glob pattern',
178
+ parameters: {
179
+ type: 'object',
180
+ properties: {
181
+ pattern: { type: 'string', description: 'Glob pattern to search for' }
182
+ },
183
+ required: ['pattern']
184
+ }
185
+ }
186
+ }
187
+ ];
188
+
189
+ // System prompt for the AI Assistant when using tool calling
190
+ const TOOL_CALLING_PROMPT = `You are a helpful AI assistant with agency capabilities. You can perform actions on the user's system using the provided tools.
191
+
192
+ TOOLS AVAILABLE:
193
+ 1. readFile - Read the contents of a file
194
+ 2. writeFile - Write content to a file
195
+ 3. editFile - Edit specific parts of a file
196
+ 4. runCommand - Execute a shell command
197
+ 5. searchFiles - Search for files using a glob pattern
198
+
199
+ ENVIRONMENT:
200
+ - OS: ${process.platform}
201
+ - Current directory: ${process.cwd()}
202
+
203
+ INSTRUCTIONS:
204
+ - Use the provided tools to accomplish the user's request
205
+ - Be concise but thorough in your responses
206
+ - When executing commands or making changes, explain what you're doing
207
+ - If you're unsure about a command or action, ask for clarification
208
+ - Be careful with destructive operations - warn before making changes
209
+
210
+ Always think step by step and explain your reasoning before taking actions that could affect the system.`;
211
+
212
+ // System prompt for the AI Assistant when using legacy function calling (JSON actions)
213
+ const FUNCTION_CALLING_PROMPT = `You are a helpful AI assistant with agency capabilities. You can perform actions on the user's system.
214
+
215
+ By default, you will continue to take actions in a loop until you decide to stop with the 'stop' action type.
216
+ Always wrap your JSON in markdown code blocks with the json language specifier.
217
+ When executing code or commands that might be potentially harmful, explain what the code does before executing it.`;
218
+
219
+ // Agent utilities
220
+ const agentUtils = {
221
+ async readFile(filePath) {
222
+ try {
223
+ const content = await fs.readFile(filePath, 'utf-8');
224
+ return content;
225
+ } catch (error) {
226
+ throw new Error(`Failed to read file ${filePath}: ${error.message}`);
227
+ }
228
+ },
229
+
230
+ async writeFile(filePath, content) {
231
+ try {
232
+ await fs.writeFile(filePath, content, 'utf-8');
233
+ return `Successfully wrote to ${filePath}`;
234
+ } catch (error) {
235
+ throw new Error(`Failed to write to file ${filePath}: ${error.message}`);
236
+ }
237
+ },
238
+
239
+ async editFile(path, edits) {
240
+ try {
241
+ // Read the current file content
242
+ let content = await fs.readFile(path, 'utf-8');
243
+ const lines = content.split('\n');
244
+
245
+ // Process each edit operation
246
+ for (const op of edits.operations) {
247
+ switch (op.type) {
248
+ case 'replace':
249
+ if (op.startLine !== undefined && op.endLine !== undefined) {
250
+ // Line-based replacement
251
+ if (op.startLine < 1 || op.endLine > lines.length) {
252
+ throw new Error(`Line numbers out of range (1-${lines.length})`);
253
+ }
254
+ const before = lines.slice(0, op.startLine - 1);
255
+ const after = lines.slice(op.endLine);
256
+ const newLines = op.newText.split('\n');
257
+ lines.splice(0, lines.length, ...before, ...newLines, ...after);
258
+ } else if (op.pattern) {
259
+ // Pattern-based replacement
260
+ const regex = new RegExp(op.pattern, op.flags || '');
261
+ content = content.replace(regex, op.replacement);
262
+ // Update lines array for subsequent operations
263
+ lines.length = 0;
264
+ lines.push(...content.split('\n'));
265
+ }
266
+ break;
267
+
268
+ case 'insert':
269
+ if (op.position === 'start') {
270
+ lines.unshift(...op.text.split('\n'));
271
+ } else if (op.position === 'end') {
272
+ lines.push(...op.text.split('\n'));
273
+ } else if (op.line !== undefined) {
274
+ if (op.line < 1 || op.line > lines.length + 1) {
275
+ throw new Error(`Line number out of range (1-${lines.length + 1})`);
276
+ }
277
+ const insertLines = op.text.split('\n');
278
+ lines.splice(op.line - 1, 0, ...insertLines);
279
+ }
280
+ break;
281
+
282
+ case 'delete':
283
+ if (op.startLine < 1 || op.endLine > lines.length) {
284
+ throw new Error(`Line numbers out of range (1-${lines.length})`);
285
+ }
286
+ lines.splice(op.startLine - 1, op.endLine - op.startLine + 1);
287
+ break;
288
+
289
+ default:
290
+ throw new Error(`Unknown operation type: ${op.type}`);
291
+ }
292
+ }
293
+
294
+ // Write the modified content back to the file
295
+ await fs.writeFile(path, lines.join('\n'), 'utf-8');
296
+ return `Successfully edited ${path}`;
297
+
298
+ } catch (error) {
299
+ throw new Error(`Failed to edit file ${path}: ${error.message}`);
300
+ }
301
+ },
302
+
303
+ async runCommand(command) {
304
+ try {
305
+ const { stdout, stderr } = await execAsync(command, { cwd: process.cwd() });
306
+ if (stderr) {
307
+ console.error('Command stderr:', stderr);
308
+ }
309
+ return stdout || 'Command executed successfully (no output)';
310
+ } catch (error) {
311
+ throw new Error(`Command failed: ${error.message}`);
312
+ }
313
+ },
314
+
315
+ async searchFiles(pattern) {
316
+ try {
317
+ // Convert glob pattern to regex for Windows compatibility
318
+ const globToRegex = (pattern) => {
319
+ const escaped = pattern.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&');
320
+ return new RegExp('^' + escaped.replace(/\*/g, '.*') + '$');
321
+ };
322
+
323
+ const regex = globToRegex(pattern);
324
+ const searchDir = process.cwd();
325
+ const results = [];
326
+
327
+ // Recursive directory search
328
+ const search = async (dir) => {
329
+ const entries = await fs.readdir(dir, { withFileTypes: true });
330
+
331
+ for (const entry of entries) {
332
+ const fullPath = path.join(dir, entry.name);
333
+
334
+ try {
335
+ if (entry.isDirectory()) {
336
+ await search(fullPath);
337
+ } else if (regex.test(entry.name)) {
338
+ results.push(fullPath);
339
+ }
340
+ } catch (error) {
341
+ // Skip files/directories we can't access
342
+ continue;
343
+ }
344
+ }
345
+ };
346
+
347
+ await search(searchDir);
348
+ return results.length > 0
349
+ ? `Found ${results.length} files:\n${results.join('\n')}`
350
+ : 'No files found';
351
+ } catch (error) {
352
+ throw new Error(`Search failed: ${error.message}`);
353
+ }
354
+ }
355
+ };
356
+
357
+ // Extract JSON from markdown code blocks
358
+ function extractJsonFromMarkdown(text) {
359
+ // Try to find a markdown code block with JSON content
360
+ const codeBlockRegex = /```json\s*([\s\S]*?)\s*```/;
361
+ const match = text.match(codeBlockRegex);
362
+
363
+ if (match) {
364
+ try {
365
+ return JSON.parse(match[1]);
366
+ } catch (error) {
367
+ console.error('Error parsing JSON from markdown:', error);
368
+ }
369
+ }
370
+
371
+ // If no code block, try to parse the entire text as JSON
372
+ try {
373
+ return JSON.parse(text);
374
+ } catch (error) {
375
+ console.error('Error parsing JSON:', error);
376
+ ui.stopThinking();
377
+ return null;
378
+ }
379
+ }
380
+
381
+ // Call OpenRouter API with tool calling
382
+ async function callOpenRouterWithTools(messages) {
383
+ const apiKey = OPENROUTER_API_KEY;
384
+
385
+ try {
386
+ const response = await fetch(API_BASE_URL + '/chat/completions', {
387
+ method: 'POST',
388
+ headers: {
389
+ 'Content-Type': 'application/json',
390
+ 'Authorization': `Bearer ${apiKey}`,
391
+ 'HTTP-Referer': 'https://github.com/yourusername/agi-cli'
392
+ },
393
+ body: JSON.stringify({
394
+ model: MODEL,
395
+ messages: messages,
396
+ tools: tools,
397
+ tool_choice: 'auto'
398
+ })
399
+ });
400
+
401
+ if (!response.ok) {
402
+ const error = await response.json();
403
+ throw new Error(`API error: ${error.error?.message || response.statusText}`);
404
+ }
405
+
406
+ return await response.json();
407
+ } catch (error) {
408
+ console.error('API call failed:', error);
409
+ ui.stopThinking();
410
+ throw new Error(`Failed to call OpenRouter API: ${error.message}`);
411
+ }
412
+ }
413
+
414
+ // Call OpenRouter API with function calling (legacy)
415
+ async function callOpenRouterWithFunctions(messages) {
416
+ const apiKey = OPENROUTER_API_KEY;
417
+
418
+ try {
419
+ const response = await fetch(API_BASE_URL + '/chat/completions', {
420
+ method: 'POST',
421
+ headers: {
422
+ 'Content-Type': 'application/json',
423
+ 'Authorization': `Bearer ${apiKey}`,
424
+ 'HTTP-Referer': 'https://github.com/yourusername/agi-cli'
425
+ },
426
+ body: JSON.stringify({
427
+ model: MODEL,
428
+ messages: messages
429
+ })
430
+ });
431
+
432
+ if (!response.ok) {
433
+ const error = await response.json();
434
+ throw new Error(`API error: ${error.error?.message || response.statusText}`);
435
+ }
436
+
437
+ return await response.json();
438
+ } catch (error) {
439
+ console.error('API call failed:', error);
440
+ ui.stopThinking();
441
+ throw new Error(`Failed to call OpenRouter API: ${error.message}`);
442
+ }
443
+ }
444
+
445
+ // Process tool calls from the AI response
446
+ async function handleToolCalls(toolCalls, messages) {
447
+ const results = [];
448
+
449
+ for (const toolCall of toolCalls) {
450
+ const functionName = toolCall.function.name;
451
+ let args;
452
+
453
+ try {
454
+ args = JSON.parse(toolCall.function.arguments);
455
+ } catch (error) {
456
+ console.error('❌ Failed to parse tool arguments:', error);
457
+ results.push({
458
+ tool_call_id: toolCall.id,
459
+ role: 'tool',
460
+ name: functionName,
461
+ content: JSON.stringify({ error: `Invalid arguments format: ${error.message}` })
462
+ });
463
+ continue;
464
+ }
465
+
466
+ console.log(`🔧 Executing ${functionName} with args:`, args);
467
+
468
+ try {
469
+ if (!agentUtils[functionName]) {
470
+ throw new Error(`Tool '${functionName}' not found`);
471
+ }
472
+
473
+ const result = await agentUtils[functionName](...Object.values(args));
474
+ console.log('✅ Tool executed successfully');
475
+
476
+ // Stringify the result if it's not already a string
477
+ const resultContent = typeof result === 'string' ? result : JSON.stringify(result);
478
+
479
+ results.push({
480
+ tool_call_id: toolCall.id,
481
+ role: 'tool',
482
+ name: functionName,
483
+ content: resultContent
484
+ });
485
+ } catch (error) {
486
+ console.error('❌ Tool execution failed:', error);
487
+
488
+ results.push({
489
+ tool_call_id: toolCall.id,
490
+ role: 'tool',
491
+ name: functionName,
492
+ content: JSON.stringify({
493
+ error: error.message,
494
+ stack: process.env.DEBUG ? error.stack : undefined
495
+ })
496
+ });
497
+ }
498
+ }
499
+
500
+ return results;
501
+ }
502
+
503
+ // Process a query with tool calling
504
+ async function processQueryWithTools(query, conversation = [], maxIterations = 5) {
505
+ // Add user message to conversation
506
+ const userMessage = { role: 'user', content: query };
507
+ let messages = [...conversation, userMessage];
508
+ let iteration = 0;
509
+ let finalResponse = null;
510
+
511
+ // Add system message if this is the first message
512
+ if (conversation.length === 0) {
513
+ messages.unshift({
514
+ role: 'system',
515
+ content: `You are a helpful AI assistant with access to tools. Use the tools when needed.
516
+ You can use multiple tools in sequence if needed to complete the task.
517
+ When using tools, make sure to provide all required parameters.
518
+ If a tool fails, you can try again with different parameters or a different approach.`
519
+ });
520
+ }
521
+
522
+ // Process in a loop to handle multiple tool calls
523
+ while (iteration < maxIterations) {
524
+ ui.startThinking();
525
+
526
+ try {
527
+ const response = await callOpenRouterWithTools(messages);
528
+ const assistantMessage = response.choices[0].message;
529
+ messages.push(assistantMessage);
530
+
531
+ // If there are no tool calls, we're done
532
+ if (!assistantMessage.tool_calls || assistantMessage.tool_calls.length === 0) {
533
+ ui.stopThinking();
534
+ finalResponse = assistantMessage.content || 'No content in response';
535
+ break;
536
+ }
537
+
538
+ // Process tool calls
539
+ console.log(`🛠️ Executing ${assistantMessage.tool_calls.length} tools...`);
540
+ const toolResults = await handleToolCalls(assistantMessage.tool_calls, messages);
541
+
542
+ // Add tool results to messages
543
+ messages = [...messages, ...toolResults];
544
+
545
+ // If we've reached max iterations, get a final response
546
+ if (iteration === maxIterations - 1) {
547
+ console.log('ℹ️ Reached maximum number of iterations. Getting final response...');
548
+ const finalResponseObj = await callOpenRouterWithTools(messages);
549
+ finalResponse = finalResponseObj.choices[0].message.content || 'No content in final response';
550
+ }
551
+
552
+ iteration++;
553
+ } catch (error) {
554
+ ui.stopThinking();
555
+ console.error('❌ Error during processing:', error);
556
+ finalResponse = `An error occurred: ${error.message}`;
557
+ break;
558
+ }
559
+ }
560
+
561
+ // If we don't have a final response yet (shouldn't happen, but just in case)
562
+ if (!finalResponse) {
563
+ finalResponse = 'No response generated. The operation may have timed out or encountered an error.';
564
+ }
565
+
566
+ return {
567
+ response: finalResponse,
568
+ conversation: messages
569
+ };
570
+ }
571
+
572
+ // Execute a single action from the action system
573
+ async function executeAction(action) {
574
+ const { type, data } = action;
575
+
576
+ switch (type) {
577
+ case 'read':
578
+ return await agentUtils.readFile(data.path);
579
+
580
+ case 'write':
581
+ return await agentUtils.writeFile(data.path, data.content);
582
+
583
+ case 'edit':
584
+ return await agentUtils.editFile(data.path, data.edits);
585
+
586
+ case 'command':
587
+ return await agentUtils.runCommand(data.command);
588
+
589
+ case 'search':
590
+ if (data.type === 'files') {
591
+ return await agentUtils.searchFiles(data.pattern);
592
+ }
593
+ throw new Error('Text search is not implemented yet');
594
+
595
+ case 'execute':
596
+ // For execute action, we'll run it as a command
597
+ const cmd = data.language === 'bash'
598
+ ? data.code
599
+ : `node -e "${data.code.replace(/"/g, '\\"')}"`;
600
+ return await agentUtils.runCommand(cmd);
601
+
602
+ case 'browse':
603
+ throw new Error('Web browsing is not implemented yet');
604
+
605
+ case 'analyze':
606
+ // For analyze action, we'll just return the question for now
607
+ return `Analysis requested for code: ${data.code}\nQuestion: ${data.question}`;
608
+
609
+ case 'stop':
610
+ return 'Stopping action execution';
611
+
612
+ default:
613
+ throw new Error(`Unknown action type: ${type}`);
614
+ }
615
+ }
616
+
617
+ // Process a query with action handling (legacy function calling)
618
+ async function processQuery(query, conversation = []) {
619
+ try {
620
+ // Add user message to conversation
621
+ const userMessage = { role: 'user', content: query };
622
+ const messages = [...conversation, userMessage];
623
+
624
+ // Add system message if this is the first message
625
+ if (conversation.length === 0) {
626
+ messages.unshift({
627
+ role: 'system',
628
+ content: FUNCTION_CALLING_PROMPT
629
+ });
630
+ }
631
+
632
+ ui.startThinking();
633
+
634
+ const response = await callOpenRouterWithFunctions(messages);
635
+ const assistantMessage = response.choices[0].message;
636
+ messages.push(assistantMessage);
637
+
638
+ // Try to extract JSON from the response
639
+ const actionData = extractJsonFromMarkdown(assistantMessage.content);
640
+
641
+ if (actionData && actionData.actions) {
642
+ ui.stopThinking();
643
+ ui.showAction('Executing actions...');
644
+ const results = [];
645
+
646
+ for (const action of actionData.actions) {
647
+ ui.showAction(` → ${action.type} action`);
648
+ try {
649
+ const result = await executeAction(action);
650
+ results.push({ type: action.type, success: true, result });
651
+ } catch (error) {
652
+ results.push({ type: action.type, success: false, error: error.message });
653
+ }
654
+ }
655
+
656
+ // Add action results to the conversation
657
+ messages.push({
658
+ role: 'system',
659
+ content: `Action results: ${JSON.stringify(results, null, 2)}`
660
+ });
661
+
662
+ // Continue the conversation with the results
663
+ return {
664
+ response: `Actions executed. Results: ${JSON.stringify(results, null, 2)}`,
665
+ conversation: messages
666
+ };
667
+ }
668
+
669
+ ui.stopThinking();
670
+ return {
671
+ response: assistantMessage.content || 'No content in response',
672
+ conversation: messages
673
+ };
674
+ } catch (error) {
675
+ ui.stopThinking();
676
+ ui.showError(`Error processing query: ${error.message}`);
677
+ return {
678
+ response: `Error: ${error.message}`,
679
+ conversation: conversation || []
680
+ };
681
+ }
682
+ }
683
+
684
+ // Main chat loop
685
+ async function chat(useToolCalling) {
686
+ const conversation = [];
687
+ ui.showHeader();
688
+ console.log('Type your message, or "exit" to quit.');
689
+
690
+ const rl = readline.createInterface({
691
+ input: process.stdin,
692
+ output: process.stdout,
693
+ prompt: '> '
694
+ });
695
+
696
+ rl.prompt();
697
+
698
+ rl.on('line', async (input) => {
699
+ if (input.toLowerCase() === '/setup') {
700
+ await runSetup(true);
701
+ console.log('\nSetup complete. Please restart the application to apply changes.');
702
+ rl.close();
703
+ return;
704
+ }
705
+
706
+ if (input.toLowerCase() === 'exit') {
707
+ rl.close();
708
+ return;
709
+ }
710
+
711
+ const result = useToolCalling
712
+ ? await processQueryWithTools(input, conversation)
713
+ : await processQuery(input, conversation);
714
+ ui.stopThinking();
715
+ ui.showResponse(result.response);
716
+
717
+ // Update conversation with the full context
718
+ conversation.length = 0; // Clear the array
719
+ result.conversation.forEach(msg => conversation.push(msg));
720
+
721
+ rl.prompt();
722
+ }).on('close', () => {
723
+ ui.showResponse('Goodbye!');
724
+ process.exit(0);
725
+ });
726
+ }
727
+
728
+ // Ask user for mode selection
729
+ function askForMode() {
730
+ const rl = readline.createInterface({
731
+ input: process.stdin,
732
+ output: process.stdout
733
+ });
734
+
735
+ return new Promise((resolve) => {
736
+ rl.question('Select mode (1 for tool calling, 2 for function calling): ', (answer) => {
737
+ rl.close();
738
+ resolve(answer.trim() === '1');
739
+ });
740
+ });
741
+ }
742
+
743
+ // Start the application
744
+ async function readConfig() {
745
+ try {
746
+ const data = await fs.readFile(CONFIG_PATH, 'utf-8');
747
+ return JSON.parse(data);
748
+ } catch (error) {
749
+ if (error.code === 'ENOENT') {
750
+ return null; // Config file doesn't exist
751
+ }
752
+ throw error;
753
+ }
754
+ }
755
+
756
+ async function writeConfig(config) {
757
+ await fs.writeFile(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf-8');
758
+ }
759
+
760
+ async function runSetup(isReconfig = false) {
761
+ const rl = readline.createInterface({
762
+ input: process.stdin,
763
+ output: process.stdout
764
+ });
765
+
766
+ const askQuestion = (query) => new Promise(resolve => rl.question(query, resolve));
767
+
768
+ if (!isReconfig) {
769
+ ui.showHeader();
770
+ console.log('Welcome to SAM-CODER Setup!');
771
+ console.log('Please provide your OpenRouter API key to get started.');
772
+ } else {
773
+ console.log('\n--- Re-running Setup ---');
774
+ }
775
+
776
+ const apiKey = await askQuestion('Enter OpenRouter API Key: ');
777
+
778
+ const config = { OPENROUTER_API_KEY: apiKey };
779
+ await writeConfig(config);
780
+
781
+ console.log('✅ Configuration saved successfully!');
782
+ rl.close();
783
+ return config;
784
+ }
785
+
786
+ // Start the application
787
+ async function start() {
788
+ try {
789
+ let config = await readConfig();
790
+ if (!config || !config.OPENROUTER_API_KEY) {
791
+ config = await runSetup();
792
+ console.log('\nSetup complete. Please start the application again.');
793
+ process.exit(0);
794
+ }
795
+
796
+ OPENROUTER_API_KEY = config.OPENROUTER_API_KEY;
797
+
798
+ ui.showHeader();
799
+ console.log('Select Mode:');
800
+ console.log('1. Tool Calling (for models that support it)');
801
+ console.log('2. Function Calling (legacy)');
802
+
803
+ const useToolCalling = await askForMode();
804
+ ui.showResponse(`\nStarting in ${useToolCalling ? 'Tool Calling' : 'Function Calling'} mode...\n`);
805
+
806
+ // Start the chat with the selected mode
807
+ await chat(useToolCalling);
808
+ } catch (error) {
809
+ ui.showError(error);
810
+ process.exit(1);
811
+ }
812
+ }
813
+
814
+ // Start the application
815
+ start().catch(console.error);