@koi-language/koi 1.0.5 → 1.1.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.
- package/README.md +4 -125
- package/examples/.build/agent-dialogue.ts +138 -0
- package/examples/.build/agent-dialogue.ts.map +1 -0
- package/examples/.build/chess.ts +77 -0
- package/examples/.build/chess.ts.map +1 -0
- package/examples/.build/delegation-test.ts +140 -0
- package/examples/.build/delegation-test.ts.map +1 -0
- package/examples/.build/dialog-demo.ts +77 -0
- package/examples/.build/dialog-demo.ts.map +1 -0
- package/examples/.build/hello-world.ts +77 -0
- package/examples/.build/hello-world.ts.map +1 -0
- package/examples/.build/lover-dialog-demo.ts +77 -0
- package/examples/.build/lover-dialog-demo.ts.map +1 -0
- package/examples/.build/package.json +3 -0
- package/examples/.build/registry-interactive-demo.ts +202 -0
- package/examples/.build/registry-interactive-demo.ts.map +1 -0
- package/examples/.build/registry-playbook-demo.ts +201 -0
- package/examples/.build/registry-playbook-demo.ts.map +1 -0
- package/examples/.build/tic-tac-toe.ts +77 -0
- package/examples/.build/tic-tac-toe.ts.map +1 -0
- package/examples/actions-demo.koi +8 -9
- package/examples/activists-dialogue.koi +75 -0
- package/examples/agent-dialogue.koi +66 -0
- package/examples/chess.koi +19 -0
- package/examples/counter.koi +20 -69
- package/examples/delegation-test.koi +16 -18
- package/examples/dialog-demo.koi +20 -0
- package/examples/hello-world.koi +7 -43
- package/examples/mcp-stdio-demo.koi +29 -0
- package/examples/memory-test.koi +49 -0
- package/examples/mobile-mcp-demo.koi +32 -0
- package/examples/multi-event-handler-test.koi +16 -18
- package/examples/pipeline.koi +15 -17
- package/examples/prompt-demo.koi +20 -0
- package/examples/{registry-playbook-email-compositor.koi → registry-interactive-demo.koi} +27 -27
- package/examples/registry-playbook-demo.koi +28 -28
- package/examples/skill-import-test.koi +7 -9
- package/examples/skills/.build/math-operations.ts +1656 -0
- package/examples/skills/.build/math-operations.ts.map +1 -0
- package/examples/skills/.build/package.json +3 -0
- package/examples/skills/.build/string-operations.ts +1643 -0
- package/examples/skills/.build/string-operations.ts.map +1 -0
- package/examples/skills/advanced/.build/index.ts +3223 -0
- package/examples/skills/advanced/.build/index.ts.map +1 -0
- package/examples/skills/advanced/.build/package.json +3 -0
- package/examples/skills/advanced/index.koi +3 -5
- package/examples/skills/math-operations.koi +1 -3
- package/examples/skills/string-operations.koi +1 -3
- package/examples/tic-tac-toe.koi +19 -0
- package/examples/utils/echo-mcp-server.js +141 -0
- package/examples/web-delegation-demo.koi +15 -17
- package/package.json +2 -1
- package/src/cli/koi.js +30 -41
- package/src/compiler/build-optimizer.js +204 -289
- package/src/compiler/cache-manager.js +1 -1
- package/src/compiler/import-resolver.js +5 -9
- package/src/compiler/parser.js +6072 -3476
- package/src/compiler/transpiler.js +346 -38
- package/src/grammar/koi.pegjs +302 -62
- package/src/runtime/actions/{format.js → call-llm.js} +37 -44
- package/src/runtime/actions/call-mcp.js +97 -0
- package/src/runtime/actions/if.js +179 -0
- package/src/runtime/actions/print.js +3 -1
- package/src/runtime/actions/prompt-user.js +75 -0
- package/src/runtime/actions/repeat.js +147 -0
- package/src/runtime/actions/shell.js +185 -0
- package/src/runtime/actions/while.js +205 -0
- package/src/runtime/agent.js +592 -178
- package/src/runtime/cli-display.js +26 -0
- package/src/runtime/cli-input.js +421 -0
- package/src/runtime/cli-logger.js +2 -5
- package/src/runtime/cli-markdown.js +61 -0
- package/src/runtime/cli-select.js +106 -0
- package/src/runtime/incremental-json-parser.js +51 -17
- package/src/runtime/index.js +1 -0
- package/src/runtime/llm-provider.js +1083 -572
- package/src/runtime/mcp-registry.js +141 -0
- package/src/runtime/mcp-stdio-client.js +334 -0
- package/src/runtime/planner.js +1 -1
- package/src/runtime/playbook-session.js +259 -0
- package/src/runtime/registry-backends/keyv-sqlite.js +1 -1
- package/src/runtime/registry-backends/local.js +1 -1
- package/src/runtime/router.js +22 -26
- package/src/runtime/runtime.js +7 -1
- package/examples/cache-test.koi +0 -29
- package/examples/calculator.koi +0 -61
- package/examples/clear-registry.js +0 -33
- package/examples/clear-registry.koi +0 -30
- package/examples/code-introspection-test.koi +0 -149
- package/examples/directory-import-test.koi +0 -84
- package/examples/hello-world-claude.koi +0 -52
- package/examples/hello.koi +0 -24
- package/examples/mcp-example.koi +0 -70
- package/examples/new-import-test.koi +0 -89
- package/examples/registry-demo.koi +0 -184
- package/examples/registry-playbook-email-compositor-2.koi +0 -140
- package/examples/sentiment.koi +0 -90
- package/examples/simple.koi +0 -48
- package/examples/task-chaining-demo.koi +0 -244
- package/examples/test-await.koi +0 -22
- package/examples/test-crypto-sha256.koi +0 -196
- package/examples/test-delegation.koi +0 -41
- package/examples/test-multi-team-routing.koi +0 -258
- package/examples/test-no-handler.koi +0 -35
- package/examples/test-npm-import.koi +0 -67
- package/examples/test-parse.koi +0 -10
- package/examples/test-peers-with-team.koi +0 -59
- package/examples/test-permissions-fail.koi +0 -20
- package/examples/test-permissions.koi +0 -36
- package/examples/test-simple-registry.koi +0 -31
- package/examples/test-typescript-import.koi +0 -64
- package/examples/test-uses-team-syntax.koi +0 -25
- package/examples/test-uses-team.koi +0 -31
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shell Action - Execute a shell command with user permission.
|
|
3
|
+
*
|
|
4
|
+
* The LLM must provide a human-readable description of what the command does.
|
|
5
|
+
* Before execution, the user is prompted for permission unless the command
|
|
6
|
+
* has been "Always allow"-ed for this agent during the current session.
|
|
7
|
+
*
|
|
8
|
+
* Permission options:
|
|
9
|
+
* - Yes → execute this time only
|
|
10
|
+
* - Always allow → execute without asking again (this agent, this session)
|
|
11
|
+
* - No → skip this time (can be asked again later)
|
|
12
|
+
*
|
|
13
|
+
* Permissions are per-agent and in-memory only (reset between sessions).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { spawn } from 'child_process';
|
|
17
|
+
import { cliLogger } from '../cli-logger.js';
|
|
18
|
+
import { cliSelect } from '../cli-select.js';
|
|
19
|
+
|
|
20
|
+
async function askPermission(command, description) {
|
|
21
|
+
cliLogger.clearProgress();
|
|
22
|
+
|
|
23
|
+
process.stdout.write(`\n${description}\n`);
|
|
24
|
+
process.stdout.write(`🔧 The agent wants to execute this command:\n`);
|
|
25
|
+
process.stdout.write(` \x1b[33m$ ${command}\x1b[0m\n\n`);
|
|
26
|
+
|
|
27
|
+
const value = await cliSelect('Allow this command?', [
|
|
28
|
+
{ title: 'Yes', value: 'yes', description: 'Execute this time' },
|
|
29
|
+
{ title: 'Always allow', value: 'always', description: 'Always allow for this agent (this session only)' },
|
|
30
|
+
{ title: 'No', value: 'no', description: 'Skip this command' }
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
return value || 'no';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default {
|
|
37
|
+
type: 'shell',
|
|
38
|
+
intent: 'shell',
|
|
39
|
+
description: 'Execute a shell command (requires user permission). Requires: command (the shell command), description (human-friendly explanation of what it does and why). Optional: cwd (working directory)',
|
|
40
|
+
permission: 'execute',
|
|
41
|
+
hidden: false,
|
|
42
|
+
|
|
43
|
+
schema: {
|
|
44
|
+
type: 'object',
|
|
45
|
+
properties: {
|
|
46
|
+
command: { type: 'string', description: 'The shell command to execute' },
|
|
47
|
+
description: { type: 'string', description: 'Human-friendly reason WHY this command is needed (shown to user). Express NEED, not action. Good: "Need to install X because Y". Bad: "Installing X".' },
|
|
48
|
+
cwd: { type: 'string', description: 'Working directory for the command (optional)' }
|
|
49
|
+
},
|
|
50
|
+
required: ['command', 'description']
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
examples: [
|
|
54
|
+
{
|
|
55
|
+
actionType: 'direct',
|
|
56
|
+
intent: 'shell',
|
|
57
|
+
command: 'npm install',
|
|
58
|
+
description: 'Need to install Node.js dependencies required by the project',
|
|
59
|
+
cwd: '/path/to/project'
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
actionType: 'direct',
|
|
63
|
+
intent: 'shell',
|
|
64
|
+
command: 'brew tap facebook/fb && brew install idb-companion && pip install fb-idb',
|
|
65
|
+
description: 'Need to install IDB because the MCP server requires it for iOS Simulator control'
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
|
|
69
|
+
async execute(action, agent) {
|
|
70
|
+
const { command, description, cwd } = action;
|
|
71
|
+
|
|
72
|
+
if (!command) {
|
|
73
|
+
throw new Error('shell: "command" field is required');
|
|
74
|
+
}
|
|
75
|
+
if (!description) {
|
|
76
|
+
throw new Error('shell: "description" field is required');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Reject commands with obvious placeholder values like <your_api_key>, <TOKEN>, etc.
|
|
80
|
+
const placeholderMatch = command.match(/<[a-zA-Z_][a-zA-Z0-9_]*>/);
|
|
81
|
+
if (placeholderMatch) {
|
|
82
|
+
return {
|
|
83
|
+
success: false,
|
|
84
|
+
error: `Command contains a placeholder "${placeholderMatch[0]}" instead of a real value. Do NOT use placeholder values — use actual values or ask the user for them with prompt_user.`
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Per-agent in-memory allowed commands (lazy-init on the agent instance)
|
|
89
|
+
if (!agent._allowedShellCommands) {
|
|
90
|
+
agent._allowedShellCommands = new Set();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let permitted = agent._allowedShellCommands.has(command);
|
|
94
|
+
|
|
95
|
+
if (permitted) {
|
|
96
|
+
process.stdout.write(`\n${description}\n`);
|
|
97
|
+
process.stdout.write(`⚡ Executing: \x1b[33m$ ${command}\x1b[0m\n`);
|
|
98
|
+
} else {
|
|
99
|
+
// Ask user for permission
|
|
100
|
+
const answer = await askPermission(command, description);
|
|
101
|
+
|
|
102
|
+
if (answer === 'always') {
|
|
103
|
+
agent._allowedShellCommands.add(command);
|
|
104
|
+
permitted = true;
|
|
105
|
+
} else if (answer === 'yes') {
|
|
106
|
+
permitted = true;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!permitted) {
|
|
111
|
+
console.log(` ⏭️ Skipped`);
|
|
112
|
+
return {
|
|
113
|
+
success: false,
|
|
114
|
+
denied: true,
|
|
115
|
+
message: `User denied execution: ${description}`
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return new Promise((resolve) => {
|
|
120
|
+
const child = spawn('sh', ['-c', command], {
|
|
121
|
+
cwd: cwd || process.cwd(),
|
|
122
|
+
env: { ...process.env },
|
|
123
|
+
stdio: ['inherit', 'pipe', 'pipe'] // stdin from user (for sudo etc), capture stdout/stderr
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const stdoutChunks = [];
|
|
127
|
+
const stderrChunks = [];
|
|
128
|
+
|
|
129
|
+
child.stdout.on('data', (data) => {
|
|
130
|
+
stdoutChunks.push(data);
|
|
131
|
+
// Stream stdout to console in real-time
|
|
132
|
+
process.stdout.write(data);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
child.stderr.on('data', (data) => {
|
|
136
|
+
stderrChunks.push(data);
|
|
137
|
+
// Stream stderr to console in real-time
|
|
138
|
+
process.stderr.write(data);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Timeout after 5 minutes
|
|
142
|
+
const timeout = setTimeout(() => {
|
|
143
|
+
child.kill('SIGTERM');
|
|
144
|
+
}, 300000);
|
|
145
|
+
|
|
146
|
+
child.on('close', (code) => {
|
|
147
|
+
clearTimeout(timeout);
|
|
148
|
+
|
|
149
|
+
const stdoutStr = Buffer.concat(stdoutChunks).toString().trim();
|
|
150
|
+
const stderrStr = Buffer.concat(stderrChunks).toString().trim();
|
|
151
|
+
|
|
152
|
+
if (code !== 0) {
|
|
153
|
+
console.log(` ❌ Failed (exit code ${code})`);
|
|
154
|
+
resolve({
|
|
155
|
+
success: false,
|
|
156
|
+
exitCode: code || 1,
|
|
157
|
+
stdout: stdoutStr,
|
|
158
|
+
stderr: stderrStr,
|
|
159
|
+
error: stderrStr || `Command exited with code ${code}`
|
|
160
|
+
});
|
|
161
|
+
} else {
|
|
162
|
+
console.log(` ✅ Done`);
|
|
163
|
+
resolve({
|
|
164
|
+
success: true,
|
|
165
|
+
exitCode: 0,
|
|
166
|
+
stdout: stdoutStr,
|
|
167
|
+
stderr: stderrStr
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
child.on('error', (err) => {
|
|
173
|
+
clearTimeout(timeout);
|
|
174
|
+
console.log(` ❌ Failed: ${err.message}`);
|
|
175
|
+
resolve({
|
|
176
|
+
success: false,
|
|
177
|
+
exitCode: 1,
|
|
178
|
+
stdout: '',
|
|
179
|
+
stderr: '',
|
|
180
|
+
error: err.message
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
};
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* While Action - Execute actions at least once, then repeat while condition is true (do-while semantics)
|
|
3
|
+
*/
|
|
4
|
+
import { buildActionDisplay } from '../cli-display.js';
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
type: 'while',
|
|
8
|
+
intent: 'while',
|
|
9
|
+
description: 'Execute actions at least once, then repeat while condition is true. CONDITION: Use string "${a1.output} !== \'stop\'" for exact match, OR object with llm_eval for semantic: { "llm_eval": true, "instruction": "¿Continuar? (false si despide)", "data": "${a1.output.answer}" } → Returns: { iterations: N, results: [array], stopped_reason: "condition_false" | "max_iterations" }',
|
|
10
|
+
permission: 'execute',
|
|
11
|
+
|
|
12
|
+
schema: {
|
|
13
|
+
type: 'object',
|
|
14
|
+
properties: {
|
|
15
|
+
condition: {
|
|
16
|
+
oneOf: [
|
|
17
|
+
{ type: 'string', description: 'Simple condition (e.g., "${a1.output.answer} !== \'hasta luego\'")' },
|
|
18
|
+
{ type: 'object', description: 'LLM-evaluated condition with llm_eval: true, instruction, and data fields' }
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
actions: {
|
|
22
|
+
type: 'array',
|
|
23
|
+
description: 'Actions to execute in each iteration'
|
|
24
|
+
},
|
|
25
|
+
max_iterations: {
|
|
26
|
+
type: 'number',
|
|
27
|
+
description: 'Optional safety limit to prevent infinite loops (default: 100)'
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
required: ['condition', 'actions']
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
examples: [
|
|
34
|
+
{
|
|
35
|
+
intent: 'while',
|
|
36
|
+
condition: "${a1.output.answer} !== 'hasta luego'",
|
|
37
|
+
max_iterations: 10,
|
|
38
|
+
actions: [
|
|
39
|
+
{ id: 'a1', intent: 'prompt_user', question: '¿Qué quieres hablar? (escribe "hasta luego" para salir)' },
|
|
40
|
+
{ intent: 'print', message: 'Dijiste: ${a1.output.answer}' }
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
|
|
45
|
+
// Executor function
|
|
46
|
+
async execute(action, agent) {
|
|
47
|
+
const condition = action.condition || action.data?.condition || '';
|
|
48
|
+
const actions = action.actions || action.data?.actions || [];
|
|
49
|
+
const maxIterations = action.max_iterations || action.data?.max_iterations || 100;
|
|
50
|
+
|
|
51
|
+
if (!condition) {
|
|
52
|
+
throw new Error('while action requires a "condition" field');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!actions || actions.length === 0) {
|
|
56
|
+
throw new Error('while action requires an "actions" array');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Get the current action context from the agent
|
|
60
|
+
const context = agent._currentActionContext || {
|
|
61
|
+
state: agent.state,
|
|
62
|
+
results: []
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Execute actions with inherited context
|
|
66
|
+
const actionRegistry = (await import('../action-registry.js')).actionRegistry;
|
|
67
|
+
const cliLogger = (await import('../cli-logger.js')).cliLogger;
|
|
68
|
+
|
|
69
|
+
const allResults = [];
|
|
70
|
+
let iterations = 0;
|
|
71
|
+
let stoppedReason = 'condition_false';
|
|
72
|
+
|
|
73
|
+
// Do-while loop: execute at least once, then check condition
|
|
74
|
+
while (iterations < maxIterations) {
|
|
75
|
+
iterations++;
|
|
76
|
+
|
|
77
|
+
// Create iteration context with current iteration number
|
|
78
|
+
const iterationContext = {
|
|
79
|
+
...context,
|
|
80
|
+
iteration: iterations,
|
|
81
|
+
iterationIndex: iterations - 1
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Execute actions first
|
|
85
|
+
for (const nestedAction of actions) {
|
|
86
|
+
// Resolve references using the iteration context
|
|
87
|
+
const resolvedAction = agent.resolveActionReferences(nestedAction, iterationContext);
|
|
88
|
+
|
|
89
|
+
// Show progress
|
|
90
|
+
cliLogger.planning(buildActionDisplay(agent.name, resolvedAction));
|
|
91
|
+
|
|
92
|
+
let result;
|
|
93
|
+
|
|
94
|
+
// Check if this is a delegation action
|
|
95
|
+
if (resolvedAction.actionType === 'delegate') {
|
|
96
|
+
// Delegation: route to appropriate team member
|
|
97
|
+
result = await agent.resolveAction(resolvedAction, iterationContext);
|
|
98
|
+
} else {
|
|
99
|
+
// Direct action: Get action definition from registry
|
|
100
|
+
const actionDef = actionRegistry.get(nestedAction.intent || nestedAction.type);
|
|
101
|
+
|
|
102
|
+
if (actionDef && actionDef.execute) {
|
|
103
|
+
// Update agent's current context for nested action
|
|
104
|
+
const previousContext = agent._currentActionContext;
|
|
105
|
+
agent._currentActionContext = iterationContext;
|
|
106
|
+
|
|
107
|
+
// Execute with agent
|
|
108
|
+
result = await actionDef.execute(resolvedAction, agent);
|
|
109
|
+
|
|
110
|
+
// Restore previous context
|
|
111
|
+
agent._currentActionContext = previousContext;
|
|
112
|
+
} else {
|
|
113
|
+
cliLogger.clear();
|
|
114
|
+
throw new Error(`Action ${nestedAction.intent || nestedAction.type} not found`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
cliLogger.clear();
|
|
119
|
+
|
|
120
|
+
// Update iteration context with result (and parent context for next iteration's condition check)
|
|
121
|
+
if (result && typeof result === 'object') {
|
|
122
|
+
// Unwrap double-encoded results (LLM sometimes returns { "result": "{...json...}" })
|
|
123
|
+
if (result.result && typeof result.result === 'string' && Object.keys(result).length === 1) {
|
|
124
|
+
try {
|
|
125
|
+
const parsed = JSON.parse(result.result);
|
|
126
|
+
if (typeof parsed === 'object') {
|
|
127
|
+
result = parsed;
|
|
128
|
+
}
|
|
129
|
+
} catch (e) {
|
|
130
|
+
// Not JSON, keep as-is
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const resultForContext = JSON.parse(JSON.stringify(result));
|
|
135
|
+
iterationContext.results.push(resultForContext);
|
|
136
|
+
context.results.push(resultForContext); // Also update parent for condition evaluation
|
|
137
|
+
|
|
138
|
+
// Store with action ID if provided
|
|
139
|
+
if (nestedAction.id) {
|
|
140
|
+
iterationContext[nestedAction.id] = { output: resultForContext };
|
|
141
|
+
context[nestedAction.id] = { output: resultForContext }; // Also update parent
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Track all results
|
|
145
|
+
allResults.push(resultForContext);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// After executing actions, evaluate condition to decide if we should continue
|
|
150
|
+
let conditionResult = false;
|
|
151
|
+
try {
|
|
152
|
+
// Check if condition is LLM-evaluated (object with llm_eval: true)
|
|
153
|
+
if (typeof condition === 'object' && condition.llm_eval === true) {
|
|
154
|
+
// Show progress while evaluating condition
|
|
155
|
+
const displayText = condition.desc ? condition.desc.replace(/\.\.\.$/, '') : 'Evaluating condition';
|
|
156
|
+
cliLogger.planning(`[🤖 ${agent.name}] ${displayText}`);
|
|
157
|
+
|
|
158
|
+
// Use call_llm action to evaluate condition with LLM
|
|
159
|
+
const callLlmAction = (await import('./call-llm.js')).default;
|
|
160
|
+
|
|
161
|
+
// Resolve data references
|
|
162
|
+
const resolvedData = agent.resolveObjectReferences(condition.data || {}, context);
|
|
163
|
+
|
|
164
|
+
// Create call_llm action to evaluate condition
|
|
165
|
+
const callLlmRequest = {
|
|
166
|
+
data: resolvedData,
|
|
167
|
+
instruction: condition.instruction + "\n\nRESPOND WITH ONLY 'true' or 'false' (lowercase, no quotes, no explanation)."
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const llmResult = await callLlmAction.execute(callLlmRequest, agent);
|
|
171
|
+
const resultText = llmResult.result.trim().toLowerCase();
|
|
172
|
+
|
|
173
|
+
// Clear progress
|
|
174
|
+
cliLogger.clear();
|
|
175
|
+
|
|
176
|
+
// Parse boolean result
|
|
177
|
+
conditionResult = resultText === 'true';
|
|
178
|
+
} else {
|
|
179
|
+
// Simple string condition - evaluate with JavaScript
|
|
180
|
+
conditionResult = agent.evaluateCondition(condition, context);
|
|
181
|
+
}
|
|
182
|
+
} catch (error) {
|
|
183
|
+
throw new Error(`Failed to evaluate while condition: ${error.message}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// If condition is false, stop (don't continue to next iteration)
|
|
187
|
+
if (!conditionResult) {
|
|
188
|
+
stoppedReason = 'condition_false';
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Safety check
|
|
193
|
+
if (iterations >= maxIterations) {
|
|
194
|
+
stoppedReason = 'max_iterations';
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
iterations,
|
|
201
|
+
results: allResults,
|
|
202
|
+
stopped_reason: stoppedReason
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
};
|