-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtool-dispatch.mjs
More file actions
146 lines (128 loc) · 5.63 KB
/
Copy pathtool-dispatch.mjs
File metadata and controls
146 lines (128 loc) · 5.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import { runShellCommands } from './tool-shell.mjs';
import { runParallelWorkerFunction } from './parallel-workers.mjs';
function stableValue(value) {
if (Array.isArray(value)) return value.map(stableValue);
if (value && typeof value === 'object') {
return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stableValue(value[key])]));
}
return value;
}
export function commandPermission(call) {
if (call?.type !== 'shell_call') return 'execute';
const commands = normalizeCommandList(call?.action?.commands).join(' && ').toLowerCase().trim();
if (!commands) return 'read';
if (/[>]|\b(sed|perl|ruby|python|python3)\s+[^;&|]*\s-i(?:\s|$)/.test(commands)) return 'write';
if (/(^|[;&|\s])(rm|mv|cp|mkdir|rmdir|touch|tee|chmod|chown|truncate|dd|install|shutdown|reboot|poweroff|systemctl|apt|dnf|yum|npm\s+(install|uninstall|update|ci)|git\s+(commit|push|reset)|terraform|kubectl|ssh)(\s|$)/.test(commands)) return 'write';
if (!/(^|[;&|\s])(cat|cut|diff|du|env|file|find|git\s+(branch|diff|log|show|status)|grep|head|less|ls|printf|pwd|rg|sed|sort|stat|tail|tree|uniq| wc)(\s|$)/.test(commands)) return 'execute';
if (/(^|[;&|\s])(node|python|python3|perl|ruby|bash|sh|zsh|make|cargo|gradle|mvn|pytest|npx)(\s|$)/.test(commands)) return 'execute';
return 'read';
}
export function permissionAllows(permission, call) {
const level = permission === 'read' || permission === 'write' || permission === 'execute' ? permission : 'execute';
const required = commandPermission(call);
return level === 'execute' || (level === 'write' && required !== 'execute') || (level === 'read' && required === 'read');
}
export function requiresToolConfirmation(call) {
if (call?.type !== 'shell_call') return false;
const commands = normalizeCommandList(call?.action?.commands).join(' && ').toLowerCase();
return /(^|[;&|\s])(rm|mv|cp|mkdir|rmdir|shutdown|reboot|poweroff|systemctl|apt|dnf|yum|npm\s+(install|uninstall|update| ci)|git\s+(commit|push|reset)|terraform|kubectl|xe\s+vm-(create|destroy|shutdown)|snapshot|ssh)(\s|$)/.test(commands);
}
export function toolCallIdentity(call, cwd = '') {
const callId = call?.call_id || call?.id;
if (callId) return `id:${callId}`;
return `hash:${JSON.stringify(stableValue({
type: call?.type || '',
name: call?.name || '',
cwd: cwd || '',
action: call?.action || {},
arguments: call?.arguments ?? call?.input ?? '',
}))}`;
}
export function dedupeToolCalls(calls, cwd = '') {
const seen = new Set();
return calls.filter((call) => {
const identity = toolCallIdentity(call, cwd);
if (seen.has(identity)) return false;
seen.add(identity);
return true;
});
}
export function dedupeToolOutputs(outputs) {
const seen = new Set();
return outputs.filter((output) => {
const callId = String(output?.call_id ?? '').trim();
const identity = callId || JSON.stringify(stableValue(output));
if (seen.has(identity)) return false;
seen.add(identity);
return true;
});
}
function normalizeCommandList(commands) {
if (Array.isArray(commands)) return commands.map((command) => String(command ?? ''));
if (typeof commands === 'string') return [commands];
return [];
}
function summarizeShellCommands(commands) {
return normalizeCommandList(commands).filter((command) => command !== '').join(' && ');
}
function normalizeShellOutput(call, output) {
if (output && typeof output === 'object' && output.type === 'shell_call_output') {
return { ...output, call_id: output.call_id || call?.call_id || call?.id || '' };
}
throw new TypeError('shell_call must return shell_call_output');
}
function normalizeFunctionOutput(call, output) {
const callId = call?.call_id || call?.id || '';
const text = typeof output === 'string' ? output : (output == null ? '' : JSON.stringify(output));
return {
type: 'function_call_output',
call_id: callId,
output: text,
};
}
function parseShellActionCommands(call) {
const commands = call?.action?.commands;
return Array.isArray(commands) || typeof commands === 'string' ? commands : [];
}
export async function runToolCall(call, cwd, options = {}) {
if (call?.type === 'function_call' && ['spawn_agent', 'agent_status', 'cancel_agent'].includes(call?.name)) {
return await runParallelWorkerFunction(call, cwd);
}
if (call?.type === 'shell_call') {
const permission = options?.permission || process.env.AGENTX_PERMISSION || 'execute';
if (!permissionAllows(permission, call)) {
return {
type: 'shell_call_output',
call_id: call?.call_id || call?.id || '',
status: 'incomplete',
output: [{
stdout: '',
stderr: `Command blocked: worker permission '${permission}' does not allow ${commandPermission(call)} operations.`,
outcome: { type: 'exit', exit_code: 126 },
}],
};
}
return await runShellCommands(parseShellActionCommands(call), cwd, {
timeoutMs: call?.action?.timeout_ms,
maxOutputLength: call?.action?.max_output_length,
callId: call?.call_id || call?.id || '',
signal: options?.signal,
});
}
return `ERROR: unsupported tool ${call?.name || call?.type}`;
}
export function toolCallSummary(call, _output) {
if (call?.type === 'shell_call') {
return summarizeShellCommands(call?.action?.commands);
}
return `${call?.name || call?.type || 'tool'}... OK!`;
}
export function toolOutputForCall(call, output) {
if (call?.type === 'shell_call') return normalizeShellOutput(call, output);
if (call?.type === 'function_call') return normalizeFunctionOutput(call, output);
return {
type: 'function_call_output',
call_id: call?.call_id || '',
output: String(output ?? ''),
};
}