4runr-os 1.0.6 → 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.
- package/dist/index.js +543 -78
- package/dist/index.js.map +1 -1
- package/dist/local-model-executor.d.ts +41 -0
- package/dist/local-model-executor.d.ts.map +1 -0
- package/dist/local-model-executor.js +201 -0
- package/dist/local-model-executor.js.map +1 -0
- package/dist/local-setup.d.ts +52 -0
- package/dist/local-setup.d.ts.map +1 -0
- package/dist/local-setup.js +254 -0
- package/dist/local-setup.js.map +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8,6 +8,8 @@ import * as fs from 'fs';
|
|
|
8
8
|
import * as path from 'path';
|
|
9
9
|
import * as os from 'os';
|
|
10
10
|
import { GatewayClient } from './gateway-client.js';
|
|
11
|
+
import { executeLocalModel, verifyLocalModelServer } from './local-model-executor.js';
|
|
12
|
+
import { checkOllamaInstallation, installOllama, startOllamaServer, downloadModel, verifyLocalExecution } from './local-setup.js';
|
|
11
13
|
// ANSI colors
|
|
12
14
|
const colors = {
|
|
13
15
|
reset: '\x1b[0m',
|
|
@@ -34,7 +36,13 @@ let gatewayUrl = process.env.GATEWAY_URL || 'http://44.222.212.152';
|
|
|
34
36
|
let chatMode = false;
|
|
35
37
|
let chatAgent = null;
|
|
36
38
|
let conversationHistory = [];
|
|
39
|
+
// Multiline input state
|
|
40
|
+
let multilineMode = false;
|
|
41
|
+
let multilineResolver = null;
|
|
42
|
+
let multilineLines = [];
|
|
43
|
+
let multilineEndMarker = 'END';
|
|
37
44
|
const customAgents = new Map();
|
|
45
|
+
const customTools = new Map();
|
|
38
46
|
// Get config directory path
|
|
39
47
|
function getConfigDir() {
|
|
40
48
|
const homeDir = os.homedir();
|
|
@@ -76,6 +84,37 @@ function saveCustomAgents() {
|
|
|
76
84
|
console.error(`Failed to save agents: ${error.message}`);
|
|
77
85
|
}
|
|
78
86
|
}
|
|
87
|
+
// Get tools file path
|
|
88
|
+
function getToolsFilePath() {
|
|
89
|
+
return path.join(getConfigDir(), 'tools.json');
|
|
90
|
+
}
|
|
91
|
+
// Load custom tools from file
|
|
92
|
+
function loadCustomTools() {
|
|
93
|
+
try {
|
|
94
|
+
const filePath = getToolsFilePath();
|
|
95
|
+
if (fs.existsSync(filePath)) {
|
|
96
|
+
const data = fs.readFileSync(filePath, 'utf-8');
|
|
97
|
+
const tools = JSON.parse(data);
|
|
98
|
+
tools.forEach(tool => {
|
|
99
|
+
customTools.set(tool.name.toLowerCase(), tool);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
// Silently fail - file might not exist yet
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Save custom tools to file
|
|
108
|
+
function saveCustomTools() {
|
|
109
|
+
try {
|
|
110
|
+
const filePath = getToolsFilePath();
|
|
111
|
+
const tools = Array.from(customTools.values());
|
|
112
|
+
fs.writeFileSync(filePath, JSON.stringify(tools, null, 2), 'utf-8');
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
console.error(`Failed to save tools: ${error.message}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
79
118
|
const aiConfig = {
|
|
80
119
|
provider: null,
|
|
81
120
|
model: null,
|
|
@@ -173,26 +212,14 @@ function promptMultiline(rl, question, endMarker = 'END') {
|
|
|
173
212
|
return new Promise((resolve) => {
|
|
174
213
|
console.log(question);
|
|
175
214
|
console.log(`${gray}Enter your text. Type '${endMarker}' on a new line when finished:${reset}\n`);
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
// Check for end marker
|
|
183
|
-
if (trimmed === endMarker || trimmed === endMarker.toUpperCase()) {
|
|
184
|
-
collecting = false;
|
|
185
|
-
rl.removeListener('line', lineHandler);
|
|
186
|
-
rl.setPrompt(`${cyan}4runr>${reset} `);
|
|
187
|
-
resolve(lines.join('\n').trim());
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
lines.push(line);
|
|
191
|
-
};
|
|
192
|
-
// Temporarily change prompt
|
|
215
|
+
// Set up multiline mode
|
|
216
|
+
multilineMode = true;
|
|
217
|
+
multilineResolver = resolve;
|
|
218
|
+
multilineLines = [];
|
|
219
|
+
multilineEndMarker = endMarker;
|
|
220
|
+
// Change prompt to indicate multiline mode
|
|
193
221
|
rl.setPrompt(`${gray} >${reset} `);
|
|
194
222
|
rl.prompt();
|
|
195
|
-
rl.on('line', lineHandler);
|
|
196
223
|
});
|
|
197
224
|
}
|
|
198
225
|
// Command handlers
|
|
@@ -472,11 +499,96 @@ const commands = {
|
|
|
472
499
|
console.log(`${red}Invalid choice${reset}\n`);
|
|
473
500
|
return;
|
|
474
501
|
}
|
|
475
|
-
|
|
502
|
+
// Integrated setup - automatically install and configure
|
|
503
|
+
console.log(`\n${bright}Setting up local model environment...${reset}`);
|
|
504
|
+
if (localModelProvider === 'ollama') {
|
|
505
|
+
// Check if Ollama is installed
|
|
506
|
+
const ollamaCheck = await checkOllamaInstallation();
|
|
507
|
+
if (!ollamaCheck.ollamaInstalled) {
|
|
508
|
+
console.log(`${yellow}Ollama not found. Installing automatically...${reset}`);
|
|
509
|
+
const installResult = await installOllama();
|
|
510
|
+
if (!installResult.success) {
|
|
511
|
+
console.log(`${red}✗ ${installResult.message}${reset}\n`);
|
|
512
|
+
const continueChoice = await prompt(rl, `${yellow}Continue anyway? (y/n):${reset} `);
|
|
513
|
+
if (continueChoice?.toLowerCase() !== 'y') {
|
|
514
|
+
console.log(`${gray}Agent creation cancelled${reset}\n`);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
else {
|
|
519
|
+
console.log(`${green}✓ ${installResult.message}${reset}`);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
// Start server if not running
|
|
523
|
+
const serverCheck = await checkOllamaInstallation();
|
|
524
|
+
if (!serverCheck.success) {
|
|
525
|
+
console.log(`${dim}Starting Ollama server...${reset}`);
|
|
526
|
+
const startResult = await startOllamaServer();
|
|
527
|
+
if (startResult.success) {
|
|
528
|
+
console.log(`${green}✓ ${startResult.message}${reset}`);
|
|
529
|
+
}
|
|
530
|
+
else {
|
|
531
|
+
console.log(`${yellow}⚠ ${startResult.message}${reset}`);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
// Get available models
|
|
535
|
+
const finalCheck = await checkOllamaInstallation();
|
|
536
|
+
if (finalCheck.modelsAvailable && finalCheck.modelsAvailable.length > 0) {
|
|
537
|
+
console.log(`${green}✓ Ollama is ready${reset}`);
|
|
538
|
+
console.log(`${dim}Available models: ${finalCheck.modelsAvailable.join(', ')}${reset}`);
|
|
539
|
+
}
|
|
540
|
+
else {
|
|
541
|
+
console.log(`${yellow}⚠ No models installed yet${reset}`);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
else if (localModelProvider === 'lm-studio') {
|
|
545
|
+
// LM Studio - check if running
|
|
546
|
+
const verification = await verifyLocalModelServer('lm-studio', localModelUrl);
|
|
547
|
+
if (!verification.available) {
|
|
548
|
+
console.log(`${yellow}⚠ LM Studio server not running${reset}`);
|
|
549
|
+
console.log(`${dim}Please start LM Studio and click "Start Server" in the Chat tab${reset}`);
|
|
550
|
+
const continueChoice = await prompt(rl, `${yellow}Continue anyway? (y/n):${reset} `);
|
|
551
|
+
if (continueChoice?.toLowerCase() !== 'y') {
|
|
552
|
+
console.log(`${gray}Agent creation cancelled${reset}\n`);
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
console.log(`${green}✓ LM Studio server is running${reset}`);
|
|
558
|
+
if (verification.models && verification.models.length > 0) {
|
|
559
|
+
console.log(`${dim}Available models: ${verification.models.join(', ')}${reset}`);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
// Get model name
|
|
564
|
+
localModelName = await prompt(rl, `\n${cyan}Model name (e.g., llama2, mistral, gpt-3.5-turbo):${reset} `);
|
|
476
565
|
if (!localModelName) {
|
|
477
566
|
console.log(`${red}Model name required${reset}\n`);
|
|
478
567
|
return;
|
|
479
568
|
}
|
|
569
|
+
// Auto-download model if using Ollama and model not found
|
|
570
|
+
if (localModelProvider === 'ollama') {
|
|
571
|
+
const finalCheck = await checkOllamaInstallation();
|
|
572
|
+
if (finalCheck.modelsAvailable && !finalCheck.modelsAvailable.includes(localModelName)) {
|
|
573
|
+
console.log(`\n${yellow}Model '${localModelName}' not found${reset}`);
|
|
574
|
+
const downloadChoice = await prompt(rl, `${cyan}Download it now? (y/n):${reset} `);
|
|
575
|
+
if (downloadChoice?.toLowerCase() === 'y') {
|
|
576
|
+
console.log(`\n${dim}Downloading model (this may take several minutes)...${reset}`);
|
|
577
|
+
const downloadResult = await downloadModel(localModelName);
|
|
578
|
+
if (downloadResult.success) {
|
|
579
|
+
console.log(`${green}✓ ${downloadResult.message}${reset}`);
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
console.log(`${red}✗ ${downloadResult.message}${reset}`);
|
|
583
|
+
const continueChoice = await prompt(rl, `${yellow}Continue anyway? (y/n):${reset} `);
|
|
584
|
+
if (continueChoice?.toLowerCase() !== 'y') {
|
|
585
|
+
console.log(`${gray}Agent creation cancelled${reset}\n`);
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
480
592
|
baseAgent = `local:${localModelProvider}:${localModelName}`;
|
|
481
593
|
}
|
|
482
594
|
else {
|
|
@@ -510,21 +622,55 @@ const commands = {
|
|
|
510
622
|
const presencePenalty = presPenaltyStr ? parseFloat(presPenaltyStr) : undefined;
|
|
511
623
|
// Tools selection
|
|
512
624
|
console.log(`\n${bright}Available Tools:${reset}`);
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
625
|
+
const predefinedTools = ['calculator', 'get_current_time', 'web_search', 'read_file'];
|
|
626
|
+
predefinedTools.forEach((tool, idx) => {
|
|
627
|
+
console.log(` ${idx + 1}. ${cyan}${tool}${reset}`);
|
|
628
|
+
});
|
|
629
|
+
// Show custom tools
|
|
630
|
+
let customToolStartIdx = predefinedTools.length + 1;
|
|
631
|
+
if (customTools.size > 0) {
|
|
632
|
+
customTools.forEach((tool) => {
|
|
633
|
+
console.log(` ${customToolStartIdx}. ${cyan}${tool.name}${reset} ${gray}(custom)${reset}`);
|
|
634
|
+
customToolStartIdx++;
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
const noneIdx = customToolStartIdx;
|
|
638
|
+
const createIdx = noneIdx + 1;
|
|
639
|
+
console.log(` ${noneIdx}. ${cyan}None${reset} - No tools`);
|
|
640
|
+
console.log(` ${createIdx}. ${cyan}Create New${reset} - Create a custom tool`);
|
|
641
|
+
const toolsChoice = await prompt(rl, `${cyan}Select tools (comma-separated, e.g., 1,2 or ${noneIdx} for none):${reset} `);
|
|
519
642
|
const tools = [];
|
|
520
|
-
if (toolsChoice
|
|
521
|
-
const
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
tools.push(
|
|
643
|
+
if (toolsChoice) {
|
|
644
|
+
const choiceNums = toolsChoice.split(',').map(s => parseInt(s.trim()));
|
|
645
|
+
choiceNums.forEach(choiceNum => {
|
|
646
|
+
if (choiceNum >= 1 && choiceNum <= predefinedTools.length) {
|
|
647
|
+
// Predefined tool
|
|
648
|
+
tools.push(predefinedTools[choiceNum - 1]);
|
|
649
|
+
}
|
|
650
|
+
else if (choiceNum > predefinedTools.length && choiceNum < noneIdx) {
|
|
651
|
+
// Custom tool
|
|
652
|
+
const customToolArray = Array.from(customTools.keys());
|
|
653
|
+
const customToolIdx = choiceNum - predefinedTools.length - 1;
|
|
654
|
+
if (customToolIdx >= 0 && customToolIdx < customToolArray.length) {
|
|
655
|
+
tools.push(customToolArray[customToolIdx]);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
else if (choiceNum === createIdx) {
|
|
659
|
+
// Create new tool - handled below
|
|
526
660
|
}
|
|
527
661
|
});
|
|
662
|
+
// If create was selected, handle it
|
|
663
|
+
if (choiceNums.includes(createIdx)) {
|
|
664
|
+
console.log(`\n${yellow}Creating new tool...${reset}`);
|
|
665
|
+
console.log(`${gray}After creating the tool, run ${bright}build${reset}${gray} again to add it to this agent${reset}\n`);
|
|
666
|
+
// Trigger tools create command
|
|
667
|
+
await commands.tools.handler(['create'], rl);
|
|
668
|
+
// After creating, ask if they want to add it
|
|
669
|
+
const newToolName = await prompt(rl, `${cyan}Tool name to add to this agent (or Enter to skip):${reset} `);
|
|
670
|
+
if (newToolName && customTools.has(newToolName.toLowerCase())) {
|
|
671
|
+
tools.push(newToolName);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
528
674
|
}
|
|
529
675
|
// Confirm
|
|
530
676
|
console.log(`\n${dim}┌─ AGENT CONFIGURATION ─────────────────────────┐${reset}`);
|
|
@@ -588,20 +734,41 @@ const commands = {
|
|
|
588
734
|
description: 'List available agents',
|
|
589
735
|
usage: 'agents',
|
|
590
736
|
handler: async () => {
|
|
591
|
-
console.log(`\n${bright}Available
|
|
592
|
-
|
|
593
|
-
console.log(
|
|
594
|
-
console.log(` ${cyan}
|
|
595
|
-
console.log(` ${cyan}
|
|
596
|
-
console.log(` ${cyan}
|
|
597
|
-
|
|
737
|
+
console.log(`\n${bright}Available Agents:${reset}\n`);
|
|
738
|
+
// Show built-in agents
|
|
739
|
+
console.log(`${bright}Built-in Agents:${reset}`);
|
|
740
|
+
console.log(` ${cyan}assistant${reset} GPT-3.5 Assistant (fast, economical)`);
|
|
741
|
+
console.log(` ${cyan}gpt4${reset} GPT-4 Assistant (most capable)`);
|
|
742
|
+
console.log(` ${cyan}test${reset} Test agent (mock, no AI)`);
|
|
743
|
+
// Show custom agents
|
|
744
|
+
if (customAgents.size > 0) {
|
|
745
|
+
console.log(`\n${bright}Custom Agents:${reset}`);
|
|
746
|
+
customAgents.forEach((agent) => {
|
|
747
|
+
const modelInfo = agent.useLocalModel
|
|
748
|
+
? ` (Local: ${agent.localModelProvider}/${agent.localModelName})`
|
|
749
|
+
: ` (${agent.baseAgent})`;
|
|
750
|
+
const toolsInfo = agent.tools && agent.tools.length > 0
|
|
751
|
+
? ` [Tools: ${agent.tools.join(', ')}]`
|
|
752
|
+
: '';
|
|
753
|
+
console.log(` ${cyan}${agent.name}${reset}${modelInfo}${toolsInfo}`);
|
|
754
|
+
if (agent.description) {
|
|
755
|
+
console.log(` ${gray}${agent.description}${reset}`);
|
|
756
|
+
}
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
else {
|
|
760
|
+
console.log(`\n${gray}No custom agents yet.${reset}`);
|
|
761
|
+
}
|
|
762
|
+
console.log(`\n${gray}Use ${bright}build${reset}${gray} to create custom agents${reset}`);
|
|
763
|
+
console.log(`${gray}Use ${bright}tools${reset}${gray} to manage tools${reset}\n`);
|
|
598
764
|
}
|
|
599
765
|
},
|
|
600
766
|
'run-agent': {
|
|
601
767
|
description: 'Start chatting with an AI agent',
|
|
602
768
|
usage: 'run-agent [agent-name]',
|
|
603
769
|
handler: async (args, rl) => {
|
|
604
|
-
|
|
770
|
+
// Join all args to support multi-word agent names
|
|
771
|
+
let agentName = args.join(' ').trim();
|
|
605
772
|
// If no agent specified, prompt for one
|
|
606
773
|
if (!agentName) {
|
|
607
774
|
console.log(`\n${bright}Available Agents:${reset}\n`);
|
|
@@ -638,7 +805,13 @@ const commands = {
|
|
|
638
805
|
let agentConfig = null;
|
|
639
806
|
if (customAgent) {
|
|
640
807
|
// Use custom agent
|
|
641
|
-
|
|
808
|
+
// For local models, use gpt-3.5-turbo as the base executor (gateway handles local models)
|
|
809
|
+
if (customAgent.useLocalModel) {
|
|
810
|
+
actualAgentId = 'gpt-3.5-turbo';
|
|
811
|
+
}
|
|
812
|
+
else {
|
|
813
|
+
actualAgentId = customAgent.baseAgent;
|
|
814
|
+
}
|
|
642
815
|
agentConfig = customAgent;
|
|
643
816
|
}
|
|
644
817
|
else {
|
|
@@ -844,6 +1017,224 @@ const commands = {
|
|
|
844
1017
|
console.log(`${gray}Goodbye!${reset}\n`);
|
|
845
1018
|
process.exit(0);
|
|
846
1019
|
}
|
|
1020
|
+
},
|
|
1021
|
+
tools: {
|
|
1022
|
+
description: 'Manage tools for agents',
|
|
1023
|
+
usage: 'tools [list|create|edit|delete|test] [tool-name]',
|
|
1024
|
+
handler: async (args, rl) => {
|
|
1025
|
+
const action = args[0] || 'list';
|
|
1026
|
+
if (action === 'list') {
|
|
1027
|
+
console.log(`\n${bright}Available Tools:${reset}\n`);
|
|
1028
|
+
// Predefined tools
|
|
1029
|
+
console.log(`${bright}Predefined Tools:${reset}`);
|
|
1030
|
+
console.log(` ${cyan}calculator${reset} - Perform mathematical calculations`);
|
|
1031
|
+
console.log(` ${cyan}get_current_time${reset} - Get current date and time`);
|
|
1032
|
+
console.log(` ${cyan}web_search${reset} - Search the web (requires API key)`);
|
|
1033
|
+
console.log(` ${cyan}read_file${reset} - Read files from filesystem`);
|
|
1034
|
+
// Custom tools
|
|
1035
|
+
if (customTools.size > 0) {
|
|
1036
|
+
console.log(`\n${bright}Custom Tools:${reset}`);
|
|
1037
|
+
customTools.forEach((tool) => {
|
|
1038
|
+
console.log(` ${cyan}${tool.name}${reset}${tool.description ? ` - ${tool.description}` : ''}`);
|
|
1039
|
+
if (tool.version) {
|
|
1040
|
+
console.log(` ${gray}Version: ${tool.version}${reset}`);
|
|
1041
|
+
}
|
|
1042
|
+
if (tool.author) {
|
|
1043
|
+
console.log(` ${gray}Author: ${tool.author}${reset}`);
|
|
1044
|
+
}
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
console.log(`\n${gray}Use ${bright}tools create${reset}${gray} to create a custom tool${reset}`);
|
|
1048
|
+
console.log(`${gray}Use ${bright}tools edit <name>${reset}${gray} to edit a tool${reset}`);
|
|
1049
|
+
console.log(`${gray}Use ${bright}tools delete <name>${reset}${gray} to delete a tool${reset}\n`);
|
|
1050
|
+
}
|
|
1051
|
+
else if (action === 'create') {
|
|
1052
|
+
console.log(`\n${dim}┌─ TOOL BUILDER ───────────────────────────────┐${reset}`);
|
|
1053
|
+
console.log(`${dim}│${reset} ${brightGreen}Create Custom Tool${reset}`);
|
|
1054
|
+
console.log(`${dim}└──────────────────────────────────────────────────┘${reset}\n`);
|
|
1055
|
+
const name = await prompt(rl, `${cyan}Tool name:${reset} `);
|
|
1056
|
+
if (!name) {
|
|
1057
|
+
console.log(`${red}Tool name required${reset}\n`);
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
if (customTools.has(name.toLowerCase())) {
|
|
1061
|
+
console.log(`${red}Tool '${name}' already exists${reset}\n`);
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
const description = await prompt(rl, `${cyan}Tool description:${reset} `);
|
|
1065
|
+
console.log(`\n${gray}Enter the tool code (JavaScript/TypeScript):${reset}`);
|
|
1066
|
+
console.log(`${gray}The tool function should accept (input: any) and return Promise<any>${reset}`);
|
|
1067
|
+
console.log(`${gray}Type 'END' on a new line when finished:${reset}\n`);
|
|
1068
|
+
const code = await promptMultiline(rl, `${cyan}Tool code:${reset}`, 'END');
|
|
1069
|
+
if (!code || code.trim().length === 0) {
|
|
1070
|
+
console.log(`${red}Tool code required${reset}\n`);
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
// Validate code has async function
|
|
1074
|
+
if (!code.includes('async') && !code.includes('Promise')) {
|
|
1075
|
+
console.log(`${yellow}⚠ Warning: Tool should be async or return a Promise${reset}`);
|
|
1076
|
+
}
|
|
1077
|
+
const version = await prompt(rl, `${cyan}Version (optional, default: 1.0.0):${reset} `) || '1.0.0';
|
|
1078
|
+
const author = await prompt(rl, `${cyan}Author (optional):${reset} `);
|
|
1079
|
+
// Ask for parameters
|
|
1080
|
+
const parameters = [];
|
|
1081
|
+
console.log(`\n${bright}Tool Parameters (optional):${reset}`);
|
|
1082
|
+
console.log(`${gray}Press Enter to skip, or enter parameter details${reset}`);
|
|
1083
|
+
let addParam = true;
|
|
1084
|
+
while (addParam) {
|
|
1085
|
+
const paramName = await prompt(rl, `${cyan}Parameter name (or Enter to finish):${reset} `);
|
|
1086
|
+
if (!paramName) {
|
|
1087
|
+
addParam = false;
|
|
1088
|
+
break;
|
|
1089
|
+
}
|
|
1090
|
+
const paramType = await prompt(rl, `${cyan}Parameter type (string|number|boolean|object):${reset} `) || 'string';
|
|
1091
|
+
const paramDesc = await prompt(rl, `${cyan}Parameter description:${reset} `);
|
|
1092
|
+
const paramRequired = await prompt(rl, `${cyan}Required? (yes/no, default: yes):${reset} `);
|
|
1093
|
+
parameters.push({
|
|
1094
|
+
name: paramName,
|
|
1095
|
+
type: paramType,
|
|
1096
|
+
description: paramDesc,
|
|
1097
|
+
required: paramRequired.toLowerCase() !== 'no'
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
const tool = {
|
|
1101
|
+
name,
|
|
1102
|
+
description: description || '',
|
|
1103
|
+
code,
|
|
1104
|
+
parameters: parameters.length > 0 ? parameters : undefined,
|
|
1105
|
+
version,
|
|
1106
|
+
author
|
|
1107
|
+
};
|
|
1108
|
+
customTools.set(name.toLowerCase(), tool);
|
|
1109
|
+
saveCustomTools();
|
|
1110
|
+
console.log(`\n${green}✓${reset} Tool created: ${bright}${name}${reset}`);
|
|
1111
|
+
console.log(` Use ${cyan}tools test ${name}${reset} to test it${reset}`);
|
|
1112
|
+
console.log(` Add it to agents with: ${cyan}build${reset}${gray} (select tools)${reset}\n`);
|
|
1113
|
+
}
|
|
1114
|
+
else if (action === 'edit') {
|
|
1115
|
+
const toolName = args[1];
|
|
1116
|
+
if (!toolName) {
|
|
1117
|
+
console.log(`${red}Tool name required${reset}`);
|
|
1118
|
+
console.log(`${gray}Usage: tools edit <tool-name>${reset}\n`);
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
const tool = customTools.get(toolName.toLowerCase());
|
|
1122
|
+
if (!tool) {
|
|
1123
|
+
console.log(`${red}Tool '${toolName}' not found${reset}\n`);
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
console.log(`\n${bright}Editing tool: ${tool.name}${reset}\n`);
|
|
1127
|
+
const newDescription = await prompt(rl, `${cyan}Description (current: ${tool.description}):${reset} `) || tool.description;
|
|
1128
|
+
console.log(`\n${gray}Current code:${reset}`);
|
|
1129
|
+
console.log(`${gray}${'─'.repeat(50)}${reset}`);
|
|
1130
|
+
console.log(tool.code);
|
|
1131
|
+
console.log(`${gray}${'─'.repeat(50)}${reset}\n`);
|
|
1132
|
+
console.log(`${gray}Enter new code (or press Enter to keep current):${reset}`);
|
|
1133
|
+
const newCode = await promptMultiline(rl, `${cyan}Tool code:${reset}`, 'END');
|
|
1134
|
+
tool.description = newDescription;
|
|
1135
|
+
if (newCode && newCode.trim().length > 0) {
|
|
1136
|
+
tool.code = newCode;
|
|
1137
|
+
}
|
|
1138
|
+
saveCustomTools();
|
|
1139
|
+
console.log(`\n${green}✓${reset} Tool updated: ${bright}${tool.name}${reset}\n`);
|
|
1140
|
+
}
|
|
1141
|
+
else if (action === 'delete') {
|
|
1142
|
+
const toolName = args[1];
|
|
1143
|
+
if (!toolName) {
|
|
1144
|
+
console.log(`${red}Tool name required${reset}`);
|
|
1145
|
+
console.log(`${gray}Usage: tools delete <tool-name>${reset}\n`);
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
const tool = customTools.get(toolName.toLowerCase());
|
|
1149
|
+
if (!tool) {
|
|
1150
|
+
console.log(`${red}Tool '${toolName}' not found${reset}\n`);
|
|
1151
|
+
return;
|
|
1152
|
+
}
|
|
1153
|
+
const confirm = await prompt(rl, `${yellow}Delete tool '${tool.name}'? (yes/no):${reset} `);
|
|
1154
|
+
if (confirm.toLowerCase() === 'yes' || confirm.toLowerCase() === 'y') {
|
|
1155
|
+
customTools.delete(toolName.toLowerCase());
|
|
1156
|
+
saveCustomTools();
|
|
1157
|
+
console.log(`\n${green}✓${reset} Tool deleted: ${bright}${tool.name}${reset}\n`);
|
|
1158
|
+
}
|
|
1159
|
+
else {
|
|
1160
|
+
console.log(`${yellow}Deletion cancelled${reset}\n`);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
else if (action === 'test') {
|
|
1164
|
+
const toolName = args[1];
|
|
1165
|
+
if (!toolName) {
|
|
1166
|
+
console.log(`${red}Tool name required${reset}`);
|
|
1167
|
+
console.log(`${gray}Usage: tools test <tool-name>${reset}\n`);
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
const tool = customTools.get(toolName.toLowerCase());
|
|
1171
|
+
if (!tool) {
|
|
1172
|
+
console.log(`${red}Tool '${toolName}' not found${reset}\n`);
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
console.log(`\n${bright}Testing tool: ${tool.name}${reset}\n`);
|
|
1176
|
+
console.log(`${gray}Tool code:${reset}`);
|
|
1177
|
+
console.log(`${gray}${'─'.repeat(50)}${reset}`);
|
|
1178
|
+
console.log(tool.code);
|
|
1179
|
+
console.log(`${gray}${'─'.repeat(50)}${reset}\n`);
|
|
1180
|
+
// Get test input
|
|
1181
|
+
const testInput = await prompt(rl, `${cyan}Test input (JSON or plain text):${reset} `);
|
|
1182
|
+
try {
|
|
1183
|
+
// Create a safe execution context
|
|
1184
|
+
const vm = await import('vm');
|
|
1185
|
+
const context = {
|
|
1186
|
+
console,
|
|
1187
|
+
setTimeout,
|
|
1188
|
+
setInterval,
|
|
1189
|
+
clearTimeout,
|
|
1190
|
+
clearInterval,
|
|
1191
|
+
Buffer,
|
|
1192
|
+
require: (module) => {
|
|
1193
|
+
// Whitelist safe modules
|
|
1194
|
+
const allowed = ['fs', 'path', 'os', 'crypto', 'url', 'http', 'https', 'axios'];
|
|
1195
|
+
if (allowed.includes(module)) {
|
|
1196
|
+
return require(module);
|
|
1197
|
+
}
|
|
1198
|
+
throw new Error(`Module '${module}' is not allowed`);
|
|
1199
|
+
}
|
|
1200
|
+
};
|
|
1201
|
+
// Parse test input
|
|
1202
|
+
let parsedInput = testInput;
|
|
1203
|
+
try {
|
|
1204
|
+
parsedInput = JSON.parse(testInput);
|
|
1205
|
+
}
|
|
1206
|
+
catch {
|
|
1207
|
+
// Use as string if not valid JSON
|
|
1208
|
+
}
|
|
1209
|
+
// Wrap tool code in async function
|
|
1210
|
+
const wrappedCode = `
|
|
1211
|
+
(async () => {
|
|
1212
|
+
${tool.code}
|
|
1213
|
+
// Tool should export a function or be a function
|
|
1214
|
+
if (typeof tool === 'function') {
|
|
1215
|
+
return await tool(${JSON.stringify(parsedInput)});
|
|
1216
|
+
} else if (typeof execute === 'function') {
|
|
1217
|
+
return await execute(${JSON.stringify(parsedInput)});
|
|
1218
|
+
} else {
|
|
1219
|
+
throw new Error('Tool must export a function named "tool" or "execute"');
|
|
1220
|
+
}
|
|
1221
|
+
})()
|
|
1222
|
+
`;
|
|
1223
|
+
const result = await vm.default.runInNewContext(wrappedCode, context, { timeout: 5000 });
|
|
1224
|
+
console.log(`\n${green}✓${reset} Tool executed successfully:`);
|
|
1225
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1226
|
+
console.log();
|
|
1227
|
+
}
|
|
1228
|
+
catch (error) {
|
|
1229
|
+
console.log(`\n${red}✗${reset} Tool execution failed:`);
|
|
1230
|
+
console.log(`${red}${error.message}${reset}\n`);
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
else {
|
|
1234
|
+
console.log(`${red}Unknown action: ${action}${reset}`);
|
|
1235
|
+
console.log(`${gray}Available actions: list, create, edit, delete, test${reset}\n`);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
847
1238
|
}
|
|
848
1239
|
};
|
|
849
1240
|
function getStatusColor(status) {
|
|
@@ -865,6 +1256,26 @@ async function startREPL() {
|
|
|
865
1256
|
showWelcome();
|
|
866
1257
|
rl.prompt();
|
|
867
1258
|
rl.on('line', async (line) => {
|
|
1259
|
+
// Handle multiline mode FIRST (before any command processing)
|
|
1260
|
+
if (multilineMode && multilineResolver) {
|
|
1261
|
+
const trimmed = line.trim();
|
|
1262
|
+
// Check for end marker (must be exactly END, case-insensitive)
|
|
1263
|
+
if (trimmed.toUpperCase() === multilineEndMarker.toUpperCase()) {
|
|
1264
|
+
multilineMode = false;
|
|
1265
|
+
const result = multilineLines.join('\n').trim();
|
|
1266
|
+
const resolver = multilineResolver;
|
|
1267
|
+
multilineResolver = null;
|
|
1268
|
+
multilineLines = [];
|
|
1269
|
+
// Restore normal prompt
|
|
1270
|
+
rl.setPrompt(`${cyan}4runr>${reset} `);
|
|
1271
|
+
resolver(result);
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
// Collect the line (preserve original line including empty lines)
|
|
1275
|
+
multilineLines.push(line);
|
|
1276
|
+
rl.prompt();
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
868
1279
|
const input = line.trim();
|
|
869
1280
|
if (!input) {
|
|
870
1281
|
rl.prompt();
|
|
@@ -888,50 +1299,104 @@ async function startREPL() {
|
|
|
888
1299
|
try {
|
|
889
1300
|
// Get custom agent config if available
|
|
890
1301
|
const agentConfig = rl.chatAgentConfig;
|
|
891
|
-
//
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
// Include custom agent settings
|
|
899
|
-
...(agentConfig?.systemPrompt && { systemPrompt: agentConfig.systemPrompt }),
|
|
900
|
-
...(agentConfig?.temperature !== undefined && { temperature: agentConfig.temperature }),
|
|
901
|
-
...(agentConfig?.maxTokens !== undefined && { maxTokens: agentConfig.maxTokens }),
|
|
902
|
-
...(conversationHistory.length > 0 && { conversationHistory })
|
|
903
|
-
}
|
|
1302
|
+
// Check if this is a local model agent - execute directly (bypass gateway)
|
|
1303
|
+
if (agentConfig?.useLocalModel && agentConfig.localModelProvider && agentConfig.localModelName) {
|
|
1304
|
+
// Execute locally - this is the REAL local execution
|
|
1305
|
+
const messages = [];
|
|
1306
|
+
// Add system prompt
|
|
1307
|
+
if (agentConfig.systemPrompt) {
|
|
1308
|
+
messages.push({ role: 'system', content: agentConfig.systemPrompt });
|
|
904
1309
|
}
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
1310
|
+
// Add conversation history
|
|
1311
|
+
if (conversationHistory.length > 0) {
|
|
1312
|
+
messages.push(...conversationHistory);
|
|
1313
|
+
}
|
|
1314
|
+
// Add current message
|
|
1315
|
+
messages.push({ role: 'user', content: message });
|
|
1316
|
+
// Execute local model directly
|
|
1317
|
+
const localConfig = {
|
|
1318
|
+
provider: agentConfig.localModelProvider,
|
|
1319
|
+
model: agentConfig.localModelName,
|
|
1320
|
+
baseUrl: agentConfig.localModelUrl,
|
|
1321
|
+
temperature: agentConfig.temperature,
|
|
1322
|
+
maxTokens: agentConfig.maxTokens,
|
|
1323
|
+
topP: agentConfig.topP,
|
|
1324
|
+
frequencyPenalty: agentConfig.frequencyPenalty,
|
|
1325
|
+
presencePenalty: agentConfig.presencePenalty,
|
|
1326
|
+
};
|
|
1327
|
+
// PROOF: Verify execution is truly local before executing
|
|
1328
|
+
const localProof = await verifyLocalExecution();
|
|
1329
|
+
if (!localProof.isLocal) {
|
|
1330
|
+
console.log(`${red}⚠ WARNING: Local execution verification failed!${reset}`);
|
|
1331
|
+
console.log(`${yellow}This may indicate the request is going through a remote server.${reset}\n`);
|
|
1332
|
+
}
|
|
1333
|
+
const result = await executeLocalModel(localConfig, messages);
|
|
1334
|
+
// Clear thinking indicator
|
|
1335
|
+
process.stdout.write(`\r${' '.repeat(20)}\r`);
|
|
918
1336
|
// Update conversation history
|
|
919
1337
|
conversationHistory.push({ role: 'user', content: message });
|
|
920
|
-
conversationHistory.push({ role: 'assistant', content:
|
|
921
|
-
// Show response with
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
console.log(`${green}${friendlyName}:${reset} ${response}\n`);
|
|
928
|
-
// Show usage if available
|
|
929
|
-
if (output.usage) {
|
|
930
|
-
console.log(`${gray}Tokens: ${output.usage.totalTokens || 'N/A'} | Cost: $${(output.cost || 0).toFixed(6)}${reset}\n`);
|
|
1338
|
+
conversationHistory.push({ role: 'assistant', content: result.result });
|
|
1339
|
+
// Show response with proof it's local
|
|
1340
|
+
console.log(`${green}${agentConfig.name}:${reset} ${result.result}\n`);
|
|
1341
|
+
// Show usage and proof
|
|
1342
|
+
if (result.usage) {
|
|
1343
|
+
console.log(`${gray}Tokens: ${result.usage.totalTokens || 'N/A'} | Model: ${result.model} | Cost: $0.000000 (local)${reset}`);
|
|
1344
|
+
console.log(`${dim}✓ Executed locally (${localProof.proof.executionPath}) | Response time: ${localProof.proof.responseTime}ms${reset}\n`);
|
|
931
1345
|
}
|
|
932
1346
|
}
|
|
933
1347
|
else {
|
|
934
|
-
|
|
1348
|
+
// Remote model - execute through gateway
|
|
1349
|
+
const run = await client.runs.create({
|
|
1350
|
+
name: `${chatAgent}: ${message.substring(0, 50)}...`,
|
|
1351
|
+
input: {
|
|
1352
|
+
agent_id: chatAgent,
|
|
1353
|
+
data: {
|
|
1354
|
+
prompt: message,
|
|
1355
|
+
// Include custom agent settings
|
|
1356
|
+
...(agentConfig?.systemPrompt && { systemPrompt: agentConfig.systemPrompt }),
|
|
1357
|
+
...(agentConfig?.temperature !== undefined && { temperature: agentConfig.temperature }),
|
|
1358
|
+
...(agentConfig?.maxTokens !== undefined && { maxTokens: agentConfig.maxTokens }),
|
|
1359
|
+
// Advanced parameters
|
|
1360
|
+
...(agentConfig?.topP !== undefined && { topP: agentConfig.topP }),
|
|
1361
|
+
...(agentConfig?.frequencyPenalty !== undefined && { frequencyPenalty: agentConfig.frequencyPenalty }),
|
|
1362
|
+
...(agentConfig?.presencePenalty !== undefined && { presencePenalty: agentConfig.presencePenalty }),
|
|
1363
|
+
// Tools
|
|
1364
|
+
...(agentConfig?.tools && agentConfig.tools.length > 0 && { tools: agentConfig.tools }),
|
|
1365
|
+
// Always include conversation history for context
|
|
1366
|
+
conversationHistory: conversationHistory.length > 0 ? conversationHistory : []
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
});
|
|
1370
|
+
await client.runs.start(run.id);
|
|
1371
|
+
// Monitor execution
|
|
1372
|
+
let status = await client.runs.get(run.id);
|
|
1373
|
+
while (status.status === 'running' || status.status === 'queued') {
|
|
1374
|
+
await sleep(500);
|
|
1375
|
+
status = await client.runs.get(run.id);
|
|
1376
|
+
}
|
|
1377
|
+
// Clear thinking indicator
|
|
1378
|
+
process.stdout.write(`\r${' '.repeat(20)}\r`);
|
|
1379
|
+
if (status.output) {
|
|
1380
|
+
const output = status.output;
|
|
1381
|
+
const response = output.result || output.message || JSON.stringify(output, null, 2);
|
|
1382
|
+
// Update conversation history
|
|
1383
|
+
conversationHistory.push({ role: 'user', content: message });
|
|
1384
|
+
conversationHistory.push({ role: 'assistant', content: response });
|
|
1385
|
+
// Show response with friendly name
|
|
1386
|
+
const agentConfig = rl.chatAgentConfig;
|
|
1387
|
+
const friendlyName = agentConfig ? agentConfig.name :
|
|
1388
|
+
chatAgent === 'gpt-3.5-turbo' ? 'Assistant' :
|
|
1389
|
+
chatAgent === 'gpt-4' ? 'GPT-4' :
|
|
1390
|
+
chatAgent;
|
|
1391
|
+
console.log(`${green}${friendlyName}:${reset} ${response}\n`);
|
|
1392
|
+
// Show usage if available
|
|
1393
|
+
if (output.usage) {
|
|
1394
|
+
console.log(`${gray}Tokens: ${output.usage.totalTokens || 'N/A'} | Cost: $${(output.cost || 0).toFixed(6)}${reset}\n`);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
else {
|
|
1398
|
+
console.log(`${yellow}No response received${reset}\n`);
|
|
1399
|
+
}
|
|
935
1400
|
}
|
|
936
1401
|
}
|
|
937
1402
|
catch (error) {
|