sam-coder-cli 1.0.46 → 1.0.47

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 +250 -43
  2. package/package.json +1 -1
package/bin/agi-cli.js CHANGED
@@ -211,11 +211,130 @@ INSTRUCTIONS:
211
211
  Always think step by step and explain your reasoning before taking actions that could affect the system.`;
212
212
 
213
213
  // System prompt for the AI Assistant when using legacy function calling (JSON actions)
214
- const FUNCTION_CALLING_PROMPT = `You are a helpful AI assistant with agency capabilities. You can perform actions on the user's system.
214
+ const FUNCTION_CALLING_PROMPT = `You are a helpful AI assistant with agency capabilities. You can perform actions on the user's system using JSON-formatted actions.
215
+
216
+ AVAILABLE ACTIONS:
217
+ 1. read - Read the contents of a file
218
+ 2. write - Write content to a file
219
+ 3. edit - Edit specific parts of a file
220
+ 4. command - Execute a shell command
221
+ 5. search - Search for files using patterns
222
+ 6. execute - Execute code (bash or node.js)
223
+ 7. analyze - Analyze code and answer questions
224
+ 8. stop - Stop taking actions and provide final response
225
+
226
+ ACTION FORMAT:
227
+ You must respond with a JSON object wrapped in markdown code blocks with the 'json' language specifier.
228
+ The JSON should have the following structure:
229
+ {
230
+ "type": "action_name",
231
+ "data": { /* action-specific parameters */ },
232
+ "reasoning": "Brief explanation of why you're taking this action"
233
+ }
234
+
235
+ EXAMPLES:
236
+
237
+ To read a file:
238
+ \`\`\`json
239
+ {
240
+ "type": "read",
241
+ "data": {
242
+ "path": "path/to/file.txt"
243
+ },
244
+ "reasoning": "Reading the file to understand its current contents"
245
+ }
246
+ \`\`\`
247
+
248
+ To write a file:
249
+ \`\`\`json
250
+ {
251
+ "type": "write",
252
+ "data": {
253
+ "path": "path/to/file.txt",
254
+ "content": "File content here"
255
+ },
256
+ "reasoning": "Creating a new file with the specified content"
257
+ }
258
+ \`\`\`
259
+
260
+ To edit a file:
261
+ \`\`\`json
262
+ {
263
+ "type": "edit",
264
+ "data": {
265
+ "path": "path/to/file.txt",
266
+ "edits": {
267
+ "operations": [
268
+ {
269
+ "type": "replace",
270
+ "startLine": 5,
271
+ "endLine": 7,
272
+ "newText": "New content for lines 5-7"
273
+ }
274
+ ]
275
+ }
276
+ },
277
+ "reasoning": "Modifying specific lines in the file"
278
+ }
279
+ \`\`\`
280
+
281
+ To run a command:
282
+ \`\`\`json
283
+ {
284
+ "type": "command",
285
+ "data": {
286
+ "command": "ls -la"
287
+ },
288
+ "reasoning": "Listing directory contents to see what files are available"
289
+ }
290
+ \`\`\`
291
+
292
+ To search for files:
293
+ \`\`\`json
294
+ {
295
+ "type": "search",
296
+ "data": {
297
+ "type": "files",
298
+ "pattern": "*.js"
299
+ },
300
+ "reasoning": "Finding all JavaScript files in the current directory"
301
+ }
302
+ \`\`\`
303
+
304
+ To execute code:
305
+ \`\`\`json
306
+ {
307
+ "type": "execute",
308
+ "data": {
309
+ "language": "bash",
310
+ "code": "echo 'Hello World'"
311
+ },
312
+ "reasoning": "Running a simple bash command"
313
+ }
314
+ \`\`\`
315
+
316
+ To stop and provide final response:
317
+ \`\`\`json
318
+ {
319
+ "type": "stop",
320
+ "data": {},
321
+ "reasoning": "Task completed successfully"
322
+ }
323
+ \`\`\`
215
324
 
216
- By default, you will continue to take actions in a loop until you decide to stop with the 'stop' action type.
217
- Always wrap your JSON in markdown code blocks with the json language specifier.
218
- When executing code or commands that might be potentially harmful, explain what the code does before executing it.`;
325
+ IMPORTANT RULES:
326
+ - Always wrap your JSON in markdown code blocks with 'json' language specifier
327
+ - Include a "reasoning" field explaining why you're taking the action
328
+ - You will continue taking actions in a loop until you use the 'stop' action
329
+ - Be careful with destructive operations - explain what you're doing
330
+ - If an action fails, analyze the error and try a different approach
331
+ - Always think step by step before taking actions
332
+
333
+ ENVIRONMENT:
334
+ - OS: ${process.platform}
335
+ - Current directory: ${process.cwd()}
336
+
337
+ Start by understanding the user's request, then take appropriate actions to fulfill it.`;
219
338
 
220
339
  // Agent utilities
221
340
  const agentUtils = {
@@ -357,26 +476,68 @@ const agentUtils = {
357
476
 
358
477
  // Extract JSON from markdown code blocks
359
478
  function extractJsonFromMarkdown(text) {
360
- // Try to find a markdown code block with JSON content
361
- const codeBlockRegex = /```json\s*([\s\S]*?)\s*```/;
479
+ if (!text || typeof text !== 'string') {
480
+ console.error('Invalid input to extractJsonFromMarkdown:', typeof text);
481
+ return null;
482
+ }
483
+
484
+ // Try to find a markdown code block with JSON content (case insensitive)
485
+ const codeBlockRegex = /```(?:json|JSON)\s*([\s\S]*?)\s*```/i;
362
486
  const match = text.match(codeBlockRegex);
363
487
 
364
- if (match) {
488
+ if (match && match[1]) {
365
489
  try {
366
- return JSON.parse(match[1]);
490
+ const jsonStr = match[1].trim();
491
+ if (!jsonStr) {
492
+ console.error('Empty JSON content in markdown block');
493
+ return null;
494
+ }
495
+ return JSON.parse(jsonStr);
367
496
  } catch (error) {
368
- console.error('Error parsing JSON from markdown:', error);
497
+ console.error('Error parsing JSON from markdown block:', error.message);
498
+ console.error('JSON content was:', match[1]);
369
499
  }
370
500
  }
371
501
 
372
- // If no code block, try to parse the entire text as JSON
502
+ // If no code block found, look for JSON-like patterns in the text
503
+ const jsonPatterns = [
504
+ // Look for objects that start with { and end with }
505
+ /\{[\s\S]*?\}/g,
506
+ // Look for arrays that start with [ and end with ]
507
+ /\[[\s\S]*?\]/g
508
+ ];
509
+
510
+ for (const pattern of jsonPatterns) {
511
+ const matches = text.match(pattern);
512
+ if (matches) {
513
+ for (const match of matches) {
514
+ try {
515
+ const parsed = JSON.parse(match.trim());
516
+ // Validate that it looks like an action object
517
+ if (parsed && typeof parsed === 'object' && parsed.type) {
518
+ return parsed;
519
+ }
520
+ } catch (error) {
521
+ // Continue to next match
522
+ continue;
523
+ }
524
+ }
525
+ }
526
+ }
527
+
528
+ // If still no valid JSON found, try to parse the entire text as JSON
373
529
  try {
374
- return JSON.parse(text);
530
+ const trimmed = text.trim();
531
+ if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
532
+ return JSON.parse(trimmed);
533
+ }
375
534
  } catch (error) {
376
- console.error('Error parsing JSON:', error);
377
- ui.stopThinking();
378
- return null;
535
+ // Last resort failed
379
536
  }
537
+
538
+ console.error('Failed to extract valid JSON from response');
539
+ console.error('Response text was:', text);
540
+ return null;
380
541
  }
381
542
 
382
543
  // Call OpenRouter API with tool calling
@@ -542,10 +703,15 @@ async function executeAction(action) {
542
703
  }
543
704
  throw new Error('Text search is not implemented yet');
544
705
  case 'execute':
545
- const cmd = data.language === 'bash'
546
- ? data.code
547
- : `node -e "${data.code.replace(/"/g, '\"')}"`;
548
- return await agentUtils.runCommand(cmd);
706
+ if (data.language === 'bash' || data.language === 'sh') {
707
+ return await agentUtils.runCommand(data.code);
708
+ } else if (data.language === 'node' || data.language === 'javascript' || data.language === 'js') {
709
+ // For node.js code, escape quotes properly and handle multiline code
710
+ const escapedCode = data.code.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
711
+ return await agentUtils.runCommand(`node -e "${escapedCode}"`);
712
+ } else {
713
+ throw new Error(`Unsupported execution language: ${data.language}`);
714
+ }
549
715
  case 'browse':
550
716
  throw new Error('Web browsing is not implemented yet');
551
717
  case 'analyze':
@@ -560,44 +726,77 @@ async function executeAction(action) {
560
726
  async function processQuery(query, conversation = [], currentModel) {
561
727
  try {
562
728
  const userMessage = { role: 'user', content: query };
729
+
730
+ // Use existing conversation (which should already have system prompt from chat function)
563
731
  let messages = [...conversation, userMessage];
732
+
564
733
  let actionCount = 0;
565
734
  const MAX_ACTIONS = 10;
735
+ let finalResponse = '';
566
736
 
567
737
  ui.startThinking();
568
738
 
569
- const responseText = await callOpenRouter(messages, currentModel, true);
570
- const assistantMessage = responseText.choices[0].message;
571
- messages.push(assistantMessage);
572
-
573
- const actionData = extractJsonFromMarkdown(assistantMessage.content);
574
-
575
- if (!actionData || actionData.type === 'stop') {
576
- ui.stopThinking();
577
- return {
578
- response: assistantMessage.content,
579
- conversation: messages
580
- };
581
- }
582
-
583
- let currentAction = actionData;
584
- while (currentAction && currentAction.type !== 'stop' && actionCount < MAX_ACTIONS) {
585
- actionCount++;
586
- const result = await executeAction(currentAction);
739
+ while (actionCount < MAX_ACTIONS) {
740
+ const responseObj = await callOpenRouter(messages, currentModel, true);
741
+ const assistantMessage = responseObj.choices[0].message;
742
+ messages.push(assistantMessage);
587
743
 
588
- messages.push({ role: 'assistant', content: `Action result: ${result}` });
744
+ const actionData = extractJsonFromMarkdown(assistantMessage.content);
589
745
 
590
- ui.startThinking();
591
- const nextResponse = await callOpenRouter(messages, currentModel, true);
592
- const nextAssistantMessage = nextResponse.choices[0].message;
593
- messages.push(nextAssistantMessage);
746
+ // If no valid action found, treat as final response
747
+ if (!actionData || !actionData.type) {
748
+ finalResponse = assistantMessage.content;
749
+ break;
750
+ }
751
+
752
+ // If stop action, break with the reasoning or content
753
+ if (actionData.type === 'stop') {
754
+ finalResponse = actionData.reasoning || assistantMessage.content;
755
+ break;
756
+ }
594
757
 
595
- currentAction = extractJsonFromMarkdown(nextAssistantMessage.content);
758
+ actionCount++;
759
+ console.log(`🔧 Executing action ${actionCount}: ${actionData.type}`);
760
+ if (actionData.reasoning) {
761
+ console.log(`📝 Reasoning: ${actionData.reasoning}`);
762
+ }
763
+
764
+ try {
765
+ const result = await executeAction(actionData);
766
+ console.log('✅ Action executed successfully');
767
+
768
+ // Add action result to conversation
769
+ const resultMessage = {
770
+ role: 'user',
771
+ content: `Action result (${actionData.type}): ${result}`
772
+ };
773
+ messages.push(resultMessage);
774
+
775
+ } catch (error) {
776
+ console.error('❌ Action execution failed:', error.message);
777
+
778
+ // Add error result to conversation
779
+ const errorMessage = {
780
+ role: 'user',
781
+ content: `Action failed (${actionData.type}): ${error.message}`
782
+ };
783
+ messages.push(errorMessage);
784
+ }
785
+ }
786
+
787
+ // If we hit max actions, get a final response
788
+ if (actionCount >= MAX_ACTIONS && !finalResponse) {
789
+ const finalMsg = { role: 'user', content: 'Please provide a final summary of what was accomplished.' };
790
+ messages.push(finalMsg);
791
+
792
+ const finalResponseObj = await callOpenRouter(messages, currentModel, true);
793
+ finalResponse = finalResponseObj.choices[0].message.content;
794
+ messages.push(finalResponseObj.choices[0].message);
596
795
  }
597
796
 
598
797
  ui.stopThinking();
599
798
  return {
600
- response: messages[messages.length - 1].content,
799
+ response: finalResponse || 'Task completed.',
601
800
  conversation: messages
602
801
  };
603
802
  } catch (error) {
@@ -613,6 +812,14 @@ async function processQuery(query, conversation = [], currentModel) {
613
812
  async function chat(rl, useToolCalling, initialModel) {
614
813
  let currentModel = initialModel;
615
814
  const conversation = [];
815
+
816
+ // Initialize conversation with appropriate system prompt
817
+ if (useToolCalling) {
818
+ conversation.push({ role: 'system', content: TOOL_CALLING_PROMPT });
819
+ } else {
820
+ conversation.push({ role: 'system', content: FUNCTION_CALLING_PROMPT });
821
+ }
822
+
616
823
  console.log('Type your message, or "exit" to quit.');
617
824
 
618
825
  rl.setPrompt('> ');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sam-coder-cli",
3
- "version": "1.0.46",
3
+ "version": "1.0.47",
4
4
  "description": "SAM-CODER: An animated command-line AI assistant with agency capabilities.",
5
5
  "main": "bin/agi-cli.js",
6
6
  "bin": {