sam-coder-cli 1.0.8 → 1.0.10

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 (2) hide show
  1. package/bin/agi-cli.js +104 -166
  2. package/package.json +1 -1
package/bin/agi-cli.js CHANGED
@@ -12,7 +12,7 @@ const execAsync = util.promisify(exec);
12
12
  // Configuration
13
13
  const CONFIG_PATH = path.join(os.homedir(), '.sam-coder-config.json');
14
14
  let OPENROUTER_API_KEY;
15
- const MODEL = 'deepseek/deepseek-chat-v3-0324:free';
15
+ let MODEL = 'deepseek/deepseek-chat-v3-0324:free';
16
16
  let API_BASE_URL = 'https://openrouter.ai/api/v1';
17
17
 
18
18
  // Tool/Function definitions for the AI
@@ -380,22 +380,21 @@ function extractJsonFromMarkdown(text) {
380
380
  }
381
381
 
382
382
  // Call OpenRouter API with tool calling
383
- async function callOpenRouterWithTools(messages) {
383
+ async function callOpenRouter(messages, currentModel, useJson = false) {
384
384
  const apiKey = OPENROUTER_API_KEY;
385
385
 
386
386
  try {
387
- const response = await fetch(API_BASE_URL + '/chat/completions', {
387
+ const response = await fetch(`${API_BASE_URL}/chat/completions`, {
388
388
  method: 'POST',
389
389
  headers: {
390
- 'Content-Type': 'application/json',
391
390
  'Authorization': `Bearer ${apiKey}`,
392
- 'HTTP-Referer': 'https://github.com/yourusername/agi-cli'
391
+ 'Content-Type': 'application/json'
393
392
  },
394
393
  body: JSON.stringify({
395
- model: MODEL,
394
+ model: currentModel,
396
395
  messages: messages,
397
- tools: tools,
398
- tool_choice: 'auto'
396
+ tools: useJson ? undefined : tools,
397
+ tool_choice: useJson ? undefined : 'auto'
399
398
  })
400
399
  });
401
400
 
@@ -415,41 +414,49 @@ async function callOpenRouterWithTools(messages) {
415
414
  }
416
415
  }
417
416
 
418
- // Call OpenRouter API with function calling (legacy)
419
- async function callOpenRouterWithFunctions(messages) {
420
- const apiKey = OPENROUTER_API_KEY;
417
+ // Process a query with tool calling
418
+ async function processQueryWithTools(query, conversation = [], currentModel) {
419
+ const userMessage = { role: 'user', content: query };
420
+ const messages = [...conversation, userMessage];
421
+
422
+ ui.startThinking();
421
423
 
422
424
  try {
423
- const response = await fetch(API_BASE_URL + '/chat/completions', {
424
- method: 'POST',
425
- headers: {
426
- 'Content-Type': 'application/json',
427
- 'Authorization': `Bearer ${apiKey}`,
428
- 'HTTP-Referer': 'https://github.com/yourusername/agi-cli'
429
- },
430
- body: JSON.stringify({
431
- model: MODEL,
432
- messages: messages
433
- })
434
- });
425
+ const response = await callOpenRouter(messages, currentModel);
426
+ const assistantMessage = response.choices[0].message;
427
+ messages.push(assistantMessage);
435
428
 
436
- if (!response.ok) {
437
- if (response.status === 401) {
438
- throw new Error('AuthenticationError: Invalid API key. Please run /setup to reconfigure.');
439
- }
440
- const error = await response.json();
441
- throw new Error(`API error: ${error.error?.message || response.statusText}`);
442
- }
429
+ if (assistantMessage.tool_calls) {
430
+ const toolResults = await handleToolCalls(assistantMessage.tool_calls, messages);
431
+ messages.push(...toolResults);
443
432
 
444
- return await response.json();
433
+ ui.startThinking();
434
+ const finalResponseObj = await callOpenRouter(messages, currentModel);
435
+ const finalAssistantMessage = finalResponseObj.choices[0].message;
436
+ messages.push(finalAssistantMessage);
437
+ ui.stopThinking();
438
+
439
+ return {
440
+ response: finalAssistantMessage.content,
441
+ conversation: messages
442
+ };
443
+ } else {
444
+ ui.stopThinking();
445
+ return {
446
+ response: assistantMessage.content,
447
+ conversation: messages
448
+ };
449
+ }
445
450
  } catch (error) {
446
- console.error('API call failed:', error);
447
451
  ui.stopThinking();
448
- throw new Error(`Failed to call OpenRouter API: ${error.message}`);
452
+ ui.showError(`Error processing query: ${error.message}`);
453
+ return {
454
+ response: `Error: ${error.message}`,
455
+ conversation: messages
456
+ };
449
457
  }
450
458
  }
451
459
 
452
- // Process tool calls from the AI response
453
460
  async function handleToolCalls(toolCalls, messages) {
454
461
  const results = [];
455
462
 
@@ -480,7 +487,6 @@ async function handleToolCalls(toolCalls, messages) {
480
487
  const result = await agentUtils[functionName](...Object.values(args));
481
488
  console.log('✅ Tool executed successfully');
482
489
 
483
- // Stringify the result if it's not already a string
484
490
  const resultContent = typeof result === 'string' ? result : JSON.stringify(result);
485
491
 
486
492
  results.push({
@@ -507,183 +513,94 @@ async function handleToolCalls(toolCalls, messages) {
507
513
  return results;
508
514
  }
509
515
 
510
- // Process a query with tool calling
511
- async function processQueryWithTools(query, conversation = []) {
512
- // Add user message to conversation
513
- const userMessage = { role: 'user', content: query };
514
- const messages = [...conversation, userMessage];
515
-
516
- // Add system message if this is the first message
517
- if (conversation.length === 0) {
518
- messages.unshift({
519
- role: 'system',
520
- content: `You are a helpful AI assistant with access to tools. Use the tools when needed.
521
- You can use multiple tools in sequence if needed to complete the task.
522
- When using tools, make sure to provide all required parameters.
523
- If a tool fails, you can try again with different parameters or a different approach.`
524
- });
525
- }
526
-
527
- ui.startThinking();
528
-
529
- try {
530
- const response = await callOpenRouterWithTools(messages);
531
- const assistantMessage = response.choices[0].message;
532
- messages.push(assistantMessage);
533
-
534
- // If there are no tool calls, we're done
535
- if (!assistantMessage.tool_calls || assistantMessage.tool_calls.length === 0) {
536
- ui.stopThinking();
537
- return {
538
- response: assistantMessage.content || 'No content in response',
539
- conversation: messages
540
- };
541
- }
542
-
543
- // Process tool calls
544
- ui.stopThinking(); // Stop thinking while tools execute
545
- console.log(`🛠️ Executing ${assistantMessage.tool_calls.length} tools...`);
546
- const toolResults = await handleToolCalls(assistantMessage.tool_calls, messages);
547
-
548
- // Add tool results to messages
549
- messages.push(...toolResults);
550
-
551
- // Now, get the AI's response to the tool results
552
- ui.startThinking();
553
- const finalResponseObj = await callOpenRouterWithTools(messages);
554
- const finalAssistantMessage = finalResponseObj.choices[0].message;
555
- messages.push(finalAssistantMessage);
556
- ui.stopThinking();
557
-
558
- return {
559
- response: finalAssistantMessage.content || 'Actions executed. What is the next step?',
560
- conversation: messages
561
- };
562
-
563
- } catch (error) {
564
- ui.stopThinking();
565
- console.error('❌ Error during processing:', error);
566
- return {
567
- response: `An error occurred: ${error.message}`,
568
- conversation: messages
569
- };
570
- }
571
- }
572
-
573
- // Execute a single action from the action system
574
516
  async function executeAction(action) {
575
517
  const { type, data } = action;
576
518
 
577
519
  switch (type) {
578
520
  case 'read':
579
521
  return await agentUtils.readFile(data.path);
580
-
581
522
  case 'write':
582
523
  return await agentUtils.writeFile(data.path, data.content);
583
-
584
524
  case 'edit':
585
525
  return await agentUtils.editFile(data.path, data.edits);
586
-
587
526
  case 'command':
588
527
  return await agentUtils.runCommand(data.command);
589
-
590
528
  case 'search':
591
529
  if (data.type === 'files') {
592
530
  return await agentUtils.searchFiles(data.pattern);
593
531
  }
594
532
  throw new Error('Text search is not implemented yet');
595
-
596
533
  case 'execute':
597
- // For execute action, we'll run it as a command
598
534
  const cmd = data.language === 'bash'
599
535
  ? data.code
600
- : `node -e "${data.code.replace(/"/g, '\\"')}"`;
536
+ : `node -e "${data.code.replace(/"/g, '\"')}"`;
601
537
  return await agentUtils.runCommand(cmd);
602
-
603
538
  case 'browse':
604
539
  throw new Error('Web browsing is not implemented yet');
605
-
606
540
  case 'analyze':
607
- // For analyze action, we'll just return the question for now
608
541
  return `Analysis requested for code: ${data.code}\nQuestion: ${data.question}`;
609
-
610
542
  case 'stop':
611
543
  return 'Stopping action execution';
612
-
613
544
  default:
614
545
  throw new Error(`Unknown action type: ${type}`);
615
546
  }
616
547
  }
617
548
 
618
- // Process a query with action handling (legacy function calling)
619
- async function processQuery(query, conversation = []) {
549
+ async function processQuery(query, conversation = [], currentModel) {
620
550
  try {
621
- // Add user message to conversation
622
551
  const userMessage = { role: 'user', content: query };
623
- const messages = [...conversation, userMessage];
624
-
625
- // Add system message if this is the first message
626
- if (conversation.length === 0) {
627
- messages.unshift({
628
- role: 'system',
629
- content: FUNCTION_CALLING_PROMPT
630
- });
631
- }
552
+ let messages = [...conversation, userMessage];
553
+ let actionCount = 0;
554
+ const MAX_ACTIONS = 10;
632
555
 
633
556
  ui.startThinking();
634
557
 
635
- const response = await callOpenRouterWithFunctions(messages);
636
- const assistantMessage = response.choices[0].message;
558
+ const responseText = await callOpenRouter(messages, currentModel, true);
559
+ const assistantMessage = responseText.choices[0].message;
637
560
  messages.push(assistantMessage);
638
561
 
639
- // Try to extract JSON from the response
640
562
  const actionData = extractJsonFromMarkdown(assistantMessage.content);
641
563
 
642
- if (actionData && actionData.actions) {
564
+ if (!actionData || actionData.type === 'stop') {
643
565
  ui.stopThinking();
644
- ui.showAction('Executing actions...');
645
- const results = [];
646
-
647
- for (const action of actionData.actions) {
648
- ui.showAction(` → ${action.type} action`);
649
- try {
650
- const result = await executeAction(action);
651
- results.push({ type: action.type, success: true, result });
652
- } catch (error) {
653
- results.push({ type: action.type, success: false, error: error.message });
654
- }
655
- }
656
-
657
- // Add action results to the conversation
658
- messages.push({
659
- role: 'system',
660
- content: `Action results: ${JSON.stringify(results, null, 2)}`
661
- });
662
-
663
- // Continue the conversation with the results
664
566
  return {
665
- response: `Actions executed. Results: ${JSON.stringify(results, null, 2)}`,
567
+ response: assistantMessage.content,
666
568
  conversation: messages
667
569
  };
668
570
  }
669
-
571
+
572
+ let currentAction = actionData;
573
+ while (currentAction && currentAction.type !== 'stop' && actionCount < MAX_ACTIONS) {
574
+ actionCount++;
575
+ const result = await executeAction(currentAction);
576
+
577
+ messages.push({ role: 'assistant', content: `Action result: ${result}` });
578
+
579
+ ui.startThinking();
580
+ const nextResponse = await callOpenRouter(messages, currentModel, true);
581
+ const nextAssistantMessage = nextResponse.choices[0].message;
582
+ messages.push(nextAssistantMessage);
583
+
584
+ currentAction = extractJsonFromMarkdown(nextAssistantMessage.content);
585
+ }
586
+
670
587
  ui.stopThinking();
671
588
  return {
672
- response: assistantMessage.content || 'No content in response',
589
+ response: messages[messages.length - 1].content,
673
590
  conversation: messages
674
591
  };
675
592
  } catch (error) {
676
593
  ui.stopThinking();
677
- ui.showError(`Error processing query: ${error.message}`);
594
+ console.error('❌ Error during processing:', error);
678
595
  return {
679
- response: `Error: ${error.message}`,
680
- conversation: conversation || []
596
+ response: `An error occurred: ${error.message}`,
597
+ conversation: conversation
681
598
  };
682
599
  }
683
600
  }
684
601
 
685
- // Main chat loop
686
- async function chat(rl, useToolCalling) {
602
+ async function chat(rl, useToolCalling, initialModel) {
603
+ let currentModel = initialModel;
687
604
  const conversation = [];
688
605
  console.log('Type your message, or "exit" to quit.');
689
606
 
@@ -691,6 +608,31 @@ async function chat(rl, useToolCalling) {
691
608
  rl.prompt();
692
609
 
693
610
  rl.on('line', async (input) => {
611
+ if (input.toLowerCase().startsWith('/model')) {
612
+ const newModel = input.split(' ')[1];
613
+ if (newModel) {
614
+ currentModel = newModel;
615
+ let config = await readConfig() || {};
616
+ config.MODEL = currentModel;
617
+ await writeConfig(config);
618
+ console.log(`Model changed to: ${currentModel}`);
619
+ } else {
620
+ console.log('Please specify a model. Usage: /model <model_name>');
621
+ }
622
+ rl.prompt();
623
+ return;
624
+ }
625
+
626
+ if (input.toLowerCase() === '/default-model') {
627
+ currentModel = 'deepseek/deepseek-chat-v3-0324:free';
628
+ let config = await readConfig() || {};
629
+ config.MODEL = currentModel;
630
+ await writeConfig(config);
631
+ console.log(`Model reset to default: ${currentModel}`);
632
+ rl.prompt();
633
+ return;
634
+ }
635
+
694
636
  if (input.toLowerCase() === '/setup') {
695
637
  await runSetup(rl, true);
696
638
  console.log('\nSetup complete. Please restart the application to apply changes.');
@@ -704,13 +646,12 @@ async function chat(rl, useToolCalling) {
704
646
  }
705
647
 
706
648
  const result = useToolCalling
707
- ? await processQueryWithTools(input, conversation)
708
- : await processQuery(input, conversation);
649
+ ? await processQueryWithTools(input, conversation, currentModel)
650
+ : await processQuery(input, conversation, currentModel);
709
651
  ui.stopThinking();
710
652
  ui.showResponse(result.response);
711
653
 
712
- // Update conversation with the full context
713
- conversation.length = 0; // Clear the array
654
+ conversation.length = 0;
714
655
  result.conversation.forEach(msg => conversation.push(msg));
715
656
 
716
657
  rl.prompt();
@@ -720,7 +661,6 @@ async function chat(rl, useToolCalling) {
720
661
  });
721
662
  }
722
663
 
723
- // Ask user for mode selection
724
664
  function askForMode(rl) {
725
665
  return new Promise((resolve) => {
726
666
  rl.question('Select mode (1 for tool calling, 2 for function calling): ', (answer) => {
@@ -729,14 +669,13 @@ function askForMode(rl) {
729
669
  });
730
670
  }
731
671
 
732
- // Start the application
733
672
  async function readConfig() {
734
673
  try {
735
674
  const data = await fs.readFile(CONFIG_PATH, 'utf-8');
736
675
  return JSON.parse(data);
737
676
  } catch (error) {
738
677
  if (error.code === 'ENOENT') {
739
- return null; // Config file doesn't exist
678
+ return null;
740
679
  }
741
680
  throw error;
742
681
  }
@@ -783,7 +722,6 @@ async function runSetup(rl, isReconfig = false) {
783
722
  return config;
784
723
  }
785
724
 
786
- // Start the application
787
725
  async function start() {
788
726
  const rl = readline.createInterface({
789
727
  input: process.stdin,
@@ -796,6 +734,8 @@ async function start() {
796
734
  config = await runSetup(rl);
797
735
  }
798
736
 
737
+ MODEL = config.MODEL || 'deepseek/deepseek-chat-v3-0324:free';
738
+
799
739
  OPENROUTER_API_KEY = config.OPENROUTER_API_KEY;
800
740
  if (config.isPro && config.customApiBase) {
801
741
  API_BASE_URL = config.customApiBase;
@@ -810,8 +750,7 @@ async function start() {
810
750
  const useToolCalling = await askForMode(rl);
811
751
  ui.showResponse(`\nStarting in ${useToolCalling ? 'Tool Calling' : 'Function Calling'} mode...\n`);
812
752
 
813
- // Start the chat with the selected mode
814
- await chat(rl, useToolCalling);
753
+ await chat(rl, useToolCalling, MODEL);
815
754
  } catch (error) {
816
755
  ui.showError(error);
817
756
  rl.close();
@@ -819,5 +758,4 @@ async function start() {
819
758
  }
820
759
  }
821
760
 
822
- // Start the application
823
761
  start().catch(console.error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sam-coder-cli",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "SAM-CODER: An animated command-line AI assistant with agency capabilities.",
5
5
  "main": "bin/agi-cli.js",
6
6
  "bin": {