@link-assistant/agent 0.0.8 → 0.0.11
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/EXAMPLES.md +80 -1
- package/MODELS.md +72 -24
- package/README.md +95 -2
- package/TOOLS.md +20 -0
- package/package.json +36 -2
- package/src/agent/agent.ts +68 -54
- package/src/auth/claude-oauth.ts +426 -0
- package/src/auth/index.ts +28 -26
- package/src/auth/plugins.ts +876 -0
- package/src/bun/index.ts +53 -43
- package/src/bus/global.ts +5 -5
- package/src/bus/index.ts +59 -53
- package/src/cli/bootstrap.js +12 -12
- package/src/cli/bootstrap.ts +6 -6
- package/src/cli/cmd/agent.ts +97 -92
- package/src/cli/cmd/auth.ts +468 -0
- package/src/cli/cmd/cmd.ts +2 -2
- package/src/cli/cmd/export.ts +41 -41
- package/src/cli/cmd/mcp.ts +210 -53
- package/src/cli/cmd/models.ts +30 -29
- package/src/cli/cmd/run.ts +269 -213
- package/src/cli/cmd/stats.ts +185 -146
- package/src/cli/error.ts +17 -13
- package/src/cli/ui.ts +78 -0
- package/src/command/index.ts +26 -26
- package/src/config/config.ts +528 -288
- package/src/config/markdown.ts +15 -15
- package/src/file/ripgrep.ts +201 -169
- package/src/file/time.ts +21 -18
- package/src/file/watcher.ts +51 -42
- package/src/file.ts +1 -1
- package/src/flag/flag.ts +26 -11
- package/src/format/formatter.ts +206 -162
- package/src/format/index.ts +61 -61
- package/src/global/index.ts +21 -21
- package/src/id/id.ts +47 -33
- package/src/index.js +554 -332
- package/src/json-standard/index.ts +173 -0
- package/src/mcp/index.ts +135 -128
- package/src/patch/index.ts +336 -267
- package/src/project/bootstrap.ts +15 -15
- package/src/project/instance.ts +43 -36
- package/src/project/project.ts +47 -47
- package/src/project/state.ts +37 -33
- package/src/provider/models-macro.ts +5 -5
- package/src/provider/models.ts +32 -32
- package/src/provider/opencode.js +19 -19
- package/src/provider/provider.ts +518 -277
- package/src/provider/transform.ts +143 -102
- package/src/server/project.ts +21 -21
- package/src/server/server.ts +111 -105
- package/src/session/agent.js +66 -60
- package/src/session/compaction.ts +136 -111
- package/src/session/index.ts +189 -156
- package/src/session/message-v2.ts +312 -268
- package/src/session/message.ts +73 -57
- package/src/session/processor.ts +180 -166
- package/src/session/prompt.ts +678 -533
- package/src/session/retry.ts +26 -23
- package/src/session/revert.ts +76 -62
- package/src/session/status.ts +26 -26
- package/src/session/summary.ts +97 -76
- package/src/session/system.ts +77 -63
- package/src/session/todo.ts +22 -16
- package/src/snapshot/index.ts +92 -76
- package/src/storage/storage.ts +157 -120
- package/src/tool/bash.ts +116 -106
- package/src/tool/batch.ts +73 -59
- package/src/tool/codesearch.ts +60 -53
- package/src/tool/edit.ts +319 -263
- package/src/tool/glob.ts +32 -28
- package/src/tool/grep.ts +72 -53
- package/src/tool/invalid.ts +7 -7
- package/src/tool/ls.ts +77 -64
- package/src/tool/multiedit.ts +30 -21
- package/src/tool/patch.ts +121 -94
- package/src/tool/read.ts +140 -122
- package/src/tool/registry.ts +38 -38
- package/src/tool/task.ts +93 -60
- package/src/tool/todo.ts +16 -16
- package/src/tool/tool.ts +45 -36
- package/src/tool/webfetch.ts +97 -74
- package/src/tool/websearch.ts +78 -64
- package/src/tool/write.ts +21 -15
- package/src/util/binary.ts +27 -19
- package/src/util/context.ts +8 -8
- package/src/util/defer.ts +7 -5
- package/src/util/error.ts +24 -19
- package/src/util/eventloop.ts +16 -10
- package/src/util/filesystem.ts +37 -33
- package/src/util/fn.ts +11 -8
- package/src/util/iife.ts +1 -1
- package/src/util/keybind.ts +44 -44
- package/src/util/lazy.ts +7 -7
- package/src/util/locale.ts +20 -16
- package/src/util/lock.ts +43 -38
- package/src/util/log.ts +95 -85
- package/src/util/queue.ts +8 -8
- package/src/util/rpc.ts +35 -23
- package/src/util/scrap.ts +4 -4
- package/src/util/signal.ts +5 -5
- package/src/util/timeout.ts +6 -6
- package/src/util/token.ts +2 -2
- package/src/util/wildcard.ts +38 -27
package/src/tool/bash.ts
CHANGED
|
@@ -1,67 +1,75 @@
|
|
|
1
|
-
import z from
|
|
2
|
-
import { spawn } from
|
|
3
|
-
import { Tool } from
|
|
4
|
-
import DESCRIPTION from
|
|
5
|
-
import { Log } from
|
|
6
|
-
import { Instance } from
|
|
7
|
-
import { lazy } from
|
|
8
|
-
import { Language } from
|
|
9
|
-
import { $ } from
|
|
10
|
-
import { Filesystem } from
|
|
11
|
-
import { fileURLToPath } from
|
|
12
|
-
|
|
13
|
-
const MAX_OUTPUT_LENGTH = 30_000
|
|
14
|
-
const DEFAULT_TIMEOUT = 1 * 60 * 1000
|
|
15
|
-
const MAX_TIMEOUT = 10 * 60 * 1000
|
|
16
|
-
const SIGKILL_TIMEOUT_MS = 200
|
|
17
|
-
|
|
18
|
-
export const log = Log.create({ service:
|
|
1
|
+
import z from 'zod';
|
|
2
|
+
import { spawn } from 'child_process';
|
|
3
|
+
import { Tool } from './tool';
|
|
4
|
+
import DESCRIPTION from './bash.txt';
|
|
5
|
+
import { Log } from '../util/log';
|
|
6
|
+
import { Instance } from '../project/instance';
|
|
7
|
+
import { lazy } from '../util/lazy';
|
|
8
|
+
import { Language } from 'web-tree-sitter';
|
|
9
|
+
import { $ } from 'bun';
|
|
10
|
+
import { Filesystem } from '../util/filesystem';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
|
|
13
|
+
const MAX_OUTPUT_LENGTH = 30_000;
|
|
14
|
+
const DEFAULT_TIMEOUT = 1 * 60 * 1000;
|
|
15
|
+
const MAX_TIMEOUT = 10 * 60 * 1000;
|
|
16
|
+
const SIGKILL_TIMEOUT_MS = 200;
|
|
17
|
+
|
|
18
|
+
export const log = Log.create({ service: 'bash-tool' });
|
|
19
19
|
|
|
20
20
|
const resolveWasm = (asset: string) => {
|
|
21
|
-
if (asset.startsWith(
|
|
22
|
-
if (asset.startsWith(
|
|
23
|
-
const url = new URL(asset, import.meta.url)
|
|
24
|
-
return fileURLToPath(url)
|
|
25
|
-
}
|
|
21
|
+
if (asset.startsWith('file://')) return fileURLToPath(asset);
|
|
22
|
+
if (asset.startsWith('/') || /^[a-z]:/i.test(asset)) return asset;
|
|
23
|
+
const url = new URL(asset, import.meta.url);
|
|
24
|
+
return fileURLToPath(url);
|
|
25
|
+
};
|
|
26
26
|
|
|
27
27
|
const parser = lazy(async () => {
|
|
28
|
-
const { Parser } = await import(
|
|
29
|
-
const { default: treeWasm } = await import(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
const { Parser } = await import('web-tree-sitter');
|
|
29
|
+
const { default: treeWasm } = await import(
|
|
30
|
+
'web-tree-sitter/tree-sitter.wasm' as string,
|
|
31
|
+
{
|
|
32
|
+
with: { type: 'wasm' },
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
const treePath = resolveWasm(treeWasm);
|
|
33
36
|
await Parser.init({
|
|
34
37
|
locateFile() {
|
|
35
|
-
return treePath
|
|
38
|
+
return treePath;
|
|
36
39
|
},
|
|
37
|
-
})
|
|
38
|
-
const { default: bashWasm } = await import(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
});
|
|
41
|
+
const { default: bashWasm } = await import(
|
|
42
|
+
'tree-sitter-bash/tree-sitter-bash.wasm' as string,
|
|
43
|
+
{
|
|
44
|
+
with: { type: 'wasm' },
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
const bashPath = resolveWasm(bashWasm);
|
|
48
|
+
const bashLanguage = await Language.load(bashPath);
|
|
49
|
+
const p = new Parser();
|
|
50
|
+
p.setLanguage(bashLanguage);
|
|
51
|
+
return p;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export const BashTool = Tool.define('bash', {
|
|
49
55
|
description: DESCRIPTION,
|
|
50
56
|
parameters: z.object({
|
|
51
|
-
command: z.string().describe(
|
|
52
|
-
timeout: z.number().describe(
|
|
57
|
+
command: z.string().describe('The command to execute'),
|
|
58
|
+
timeout: z.number().describe('Optional timeout in milliseconds').optional(),
|
|
53
59
|
description: z
|
|
54
60
|
.string()
|
|
55
61
|
.describe(
|
|
56
|
-
"Clear, concise description of what this command does in 5-10 words. Examples:\nInput: ls\nOutput: Lists files in current directory\n\nInput: git status\nOutput: Shows working tree status\n\nInput: npm install\nOutput: Installs package dependencies\n\nInput: mkdir foo\nOutput: Creates directory 'foo'"
|
|
62
|
+
"Clear, concise description of what this command does in 5-10 words. Examples:\nInput: ls\nOutput: Lists files in current directory\n\nInput: git status\nOutput: Shows working tree status\n\nInput: npm install\nOutput: Installs package dependencies\n\nInput: mkdir foo\nOutput: Creates directory 'foo'"
|
|
57
63
|
)
|
|
58
64
|
.optional(),
|
|
59
65
|
}),
|
|
60
66
|
async execute(params, ctx) {
|
|
61
67
|
if (params.timeout !== undefined && params.timeout < 0) {
|
|
62
|
-
throw new Error(
|
|
68
|
+
throw new Error(
|
|
69
|
+
`Invalid timeout value: ${params.timeout}. Timeout must be a positive number.`
|
|
70
|
+
);
|
|
63
71
|
}
|
|
64
|
-
const timeout = Math.min(params.timeout ?? DEFAULT_TIMEOUT, MAX_TIMEOUT)
|
|
72
|
+
const timeout = Math.min(params.timeout ?? DEFAULT_TIMEOUT, MAX_TIMEOUT);
|
|
65
73
|
|
|
66
74
|
// No restrictions - unrestricted command execution
|
|
67
75
|
const proc = spawn(params.command, {
|
|
@@ -70,114 +78,116 @@ export const BashTool = Tool.define("bash", {
|
|
|
70
78
|
env: {
|
|
71
79
|
...process.env,
|
|
72
80
|
},
|
|
73
|
-
stdio: [
|
|
74
|
-
detached: process.platform !==
|
|
75
|
-
})
|
|
81
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
82
|
+
detached: process.platform !== 'win32',
|
|
83
|
+
});
|
|
76
84
|
|
|
77
|
-
let output =
|
|
85
|
+
let output = '';
|
|
78
86
|
|
|
79
87
|
// Initialize metadata with empty output
|
|
80
88
|
ctx.metadata({
|
|
81
89
|
metadata: {
|
|
82
|
-
output:
|
|
90
|
+
output: '',
|
|
83
91
|
description: params.description,
|
|
84
92
|
},
|
|
85
|
-
})
|
|
93
|
+
});
|
|
86
94
|
|
|
87
95
|
const append = (chunk: Buffer) => {
|
|
88
|
-
output += chunk.toString()
|
|
96
|
+
output += chunk.toString();
|
|
89
97
|
ctx.metadata({
|
|
90
98
|
metadata: {
|
|
91
99
|
output,
|
|
92
100
|
description: params.description,
|
|
93
101
|
},
|
|
94
|
-
})
|
|
95
|
-
}
|
|
102
|
+
});
|
|
103
|
+
};
|
|
96
104
|
|
|
97
|
-
proc.stdout?.on(
|
|
98
|
-
proc.stderr?.on(
|
|
105
|
+
proc.stdout?.on('data', append);
|
|
106
|
+
proc.stderr?.on('data', append);
|
|
99
107
|
|
|
100
|
-
let timedOut = false
|
|
101
|
-
let aborted = false
|
|
102
|
-
let exited = false
|
|
108
|
+
let timedOut = false;
|
|
109
|
+
let aborted = false;
|
|
110
|
+
let exited = false;
|
|
103
111
|
|
|
104
112
|
const killTree = async () => {
|
|
105
|
-
const pid = proc.pid
|
|
113
|
+
const pid = proc.pid;
|
|
106
114
|
if (!pid || exited) {
|
|
107
|
-
return
|
|
115
|
+
return;
|
|
108
116
|
}
|
|
109
117
|
|
|
110
|
-
if (process.platform ===
|
|
118
|
+
if (process.platform === 'win32') {
|
|
111
119
|
await new Promise<void>((resolve) => {
|
|
112
|
-
const killer = spawn(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
120
|
+
const killer = spawn('taskkill', ['/pid', String(pid), '/f', '/t'], {
|
|
121
|
+
stdio: 'ignore',
|
|
122
|
+
});
|
|
123
|
+
killer.once('exit', resolve);
|
|
124
|
+
killer.once('error', resolve);
|
|
125
|
+
});
|
|
126
|
+
return;
|
|
117
127
|
}
|
|
118
128
|
|
|
119
129
|
try {
|
|
120
|
-
process.kill(-pid,
|
|
121
|
-
await Bun.sleep(SIGKILL_TIMEOUT_MS)
|
|
130
|
+
process.kill(-pid, 'SIGTERM');
|
|
131
|
+
await Bun.sleep(SIGKILL_TIMEOUT_MS);
|
|
122
132
|
if (!exited) {
|
|
123
|
-
process.kill(-pid,
|
|
133
|
+
process.kill(-pid, 'SIGKILL');
|
|
124
134
|
}
|
|
125
135
|
} catch (_e) {
|
|
126
|
-
proc.kill(
|
|
127
|
-
await Bun.sleep(SIGKILL_TIMEOUT_MS)
|
|
136
|
+
proc.kill('SIGTERM');
|
|
137
|
+
await Bun.sleep(SIGKILL_TIMEOUT_MS);
|
|
128
138
|
if (!exited) {
|
|
129
|
-
proc.kill(
|
|
139
|
+
proc.kill('SIGKILL');
|
|
130
140
|
}
|
|
131
141
|
}
|
|
132
|
-
}
|
|
142
|
+
};
|
|
133
143
|
|
|
134
144
|
if (ctx.abort.aborted) {
|
|
135
|
-
aborted = true
|
|
136
|
-
await killTree()
|
|
145
|
+
aborted = true;
|
|
146
|
+
await killTree();
|
|
137
147
|
}
|
|
138
148
|
|
|
139
149
|
const abortHandler = () => {
|
|
140
|
-
aborted = true
|
|
141
|
-
void killTree()
|
|
142
|
-
}
|
|
150
|
+
aborted = true;
|
|
151
|
+
void killTree();
|
|
152
|
+
};
|
|
143
153
|
|
|
144
|
-
ctx.abort.addEventListener(
|
|
154
|
+
ctx.abort.addEventListener('abort', abortHandler, { once: true });
|
|
145
155
|
|
|
146
156
|
const timeoutTimer = setTimeout(() => {
|
|
147
|
-
timedOut = true
|
|
148
|
-
void killTree()
|
|
149
|
-
}, timeout)
|
|
157
|
+
timedOut = true;
|
|
158
|
+
void killTree();
|
|
159
|
+
}, timeout);
|
|
150
160
|
|
|
151
161
|
await new Promise<void>((resolve, reject) => {
|
|
152
162
|
const cleanup = () => {
|
|
153
|
-
clearTimeout(timeoutTimer)
|
|
154
|
-
ctx.abort.removeEventListener(
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
proc.once(
|
|
158
|
-
exited = true
|
|
159
|
-
cleanup()
|
|
160
|
-
resolve()
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
proc.once(
|
|
164
|
-
exited = true
|
|
165
|
-
cleanup()
|
|
166
|
-
reject(error)
|
|
167
|
-
})
|
|
168
|
-
})
|
|
163
|
+
clearTimeout(timeoutTimer);
|
|
164
|
+
ctx.abort.removeEventListener('abort', abortHandler);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
proc.once('exit', () => {
|
|
168
|
+
exited = true;
|
|
169
|
+
cleanup();
|
|
170
|
+
resolve();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
proc.once('error', (error) => {
|
|
174
|
+
exited = true;
|
|
175
|
+
cleanup();
|
|
176
|
+
reject(error);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
169
179
|
|
|
170
180
|
if (output.length > MAX_OUTPUT_LENGTH) {
|
|
171
|
-
output = output.slice(0, MAX_OUTPUT_LENGTH)
|
|
172
|
-
output +=
|
|
181
|
+
output = output.slice(0, MAX_OUTPUT_LENGTH);
|
|
182
|
+
output += '\n\n(Output was truncated due to length limit)';
|
|
173
183
|
}
|
|
174
184
|
|
|
175
185
|
if (timedOut) {
|
|
176
|
-
output += `\n\n(Command timed out after ${timeout} ms)
|
|
186
|
+
output += `\n\n(Command timed out after ${timeout} ms)`;
|
|
177
187
|
}
|
|
178
188
|
|
|
179
189
|
if (aborted) {
|
|
180
|
-
output +=
|
|
190
|
+
output += '\n\n(Command was aborted)';
|
|
181
191
|
}
|
|
182
192
|
|
|
183
193
|
return {
|
|
@@ -188,6 +198,6 @@ export const BashTool = Tool.define("bash", {
|
|
|
188
198
|
description: params.description,
|
|
189
199
|
},
|
|
190
200
|
output,
|
|
191
|
-
}
|
|
201
|
+
};
|
|
192
202
|
},
|
|
193
|
-
})
|
|
203
|
+
});
|
package/src/tool/batch.ts
CHANGED
|
@@ -1,90 +1,100 @@
|
|
|
1
|
-
import z from
|
|
2
|
-
import { Tool } from
|
|
3
|
-
import DESCRIPTION from
|
|
1
|
+
import z from 'zod';
|
|
2
|
+
import { Tool } from './tool';
|
|
3
|
+
import DESCRIPTION from './batch.txt';
|
|
4
4
|
|
|
5
|
-
const DISALLOWED = new Set([
|
|
6
|
-
const FILTERED_FROM_SUGGESTIONS = new Set([
|
|
5
|
+
const DISALLOWED = new Set(['batch', 'edit', 'todoread']);
|
|
6
|
+
const FILTERED_FROM_SUGGESTIONS = new Set(['invalid', 'patch', ...DISALLOWED]);
|
|
7
7
|
|
|
8
|
-
export const BatchTool = Tool.define(
|
|
8
|
+
export const BatchTool = Tool.define('batch', async () => {
|
|
9
9
|
return {
|
|
10
10
|
description: DESCRIPTION,
|
|
11
11
|
parameters: z.object({
|
|
12
12
|
tool_calls: z
|
|
13
13
|
.array(
|
|
14
14
|
z.object({
|
|
15
|
-
tool: z.string().describe(
|
|
16
|
-
parameters: z
|
|
17
|
-
|
|
15
|
+
tool: z.string().describe('The name of the tool to execute'),
|
|
16
|
+
parameters: z
|
|
17
|
+
.object({})
|
|
18
|
+
.loose()
|
|
19
|
+
.describe('Parameters for the tool'),
|
|
20
|
+
})
|
|
18
21
|
)
|
|
19
|
-
.min(1,
|
|
20
|
-
.describe(
|
|
22
|
+
.min(1, 'Provide at least one tool call')
|
|
23
|
+
.describe('Array of tool calls to execute in parallel'),
|
|
21
24
|
}),
|
|
22
25
|
formatValidationError(error) {
|
|
23
26
|
const formattedErrors = error.issues
|
|
24
27
|
.map((issue) => {
|
|
25
|
-
const path = issue.path.length > 0 ? issue.path.join(
|
|
26
|
-
return ` - ${path}: ${issue.message}
|
|
28
|
+
const path = issue.path.length > 0 ? issue.path.join('.') : 'root';
|
|
29
|
+
return ` - ${path}: ${issue.message}`;
|
|
27
30
|
})
|
|
28
|
-
.join(
|
|
31
|
+
.join('\n');
|
|
29
32
|
|
|
30
|
-
return `Invalid parameters for tool 'batch':\n${formattedErrors}\n\nExpected payload format:\n [{"tool": "tool_name", "parameters": {...}}, {...}]
|
|
33
|
+
return `Invalid parameters for tool 'batch':\n${formattedErrors}\n\nExpected payload format:\n [{"tool": "tool_name", "parameters": {...}}, {...}]`;
|
|
31
34
|
},
|
|
32
35
|
async execute(params, ctx) {
|
|
33
|
-
const { Session } = await import(
|
|
34
|
-
const { Identifier } = await import(
|
|
36
|
+
const { Session } = await import('../session');
|
|
37
|
+
const { Identifier } = await import('../id/id');
|
|
35
38
|
|
|
36
|
-
const toolCalls = params.tool_calls.slice(0, 10)
|
|
37
|
-
const discardedCalls = params.tool_calls.slice(10)
|
|
39
|
+
const toolCalls = params.tool_calls.slice(0, 10);
|
|
40
|
+
const discardedCalls = params.tool_calls.slice(10);
|
|
38
41
|
|
|
39
|
-
const { ToolRegistry } = await import(
|
|
40
|
-
const availableTools = await ToolRegistry.tools(
|
|
41
|
-
const toolMap = new Map(availableTools.map((t) => [t.id, t]))
|
|
42
|
+
const { ToolRegistry } = await import('./registry');
|
|
43
|
+
const availableTools = await ToolRegistry.tools('', '');
|
|
44
|
+
const toolMap = new Map(availableTools.map((t) => [t.id, t]));
|
|
42
45
|
|
|
43
46
|
const executeCall = async (call: (typeof toolCalls)[0]) => {
|
|
44
|
-
const callStartTime = Date.now()
|
|
45
|
-
const partID = Identifier.ascending(
|
|
47
|
+
const callStartTime = Date.now();
|
|
48
|
+
const partID = Identifier.ascending('part');
|
|
46
49
|
|
|
47
50
|
try {
|
|
48
51
|
if (DISALLOWED.has(call.tool)) {
|
|
49
52
|
throw new Error(
|
|
50
|
-
`Tool '${call.tool}' is not allowed in batch. Disallowed tools: ${Array.from(DISALLOWED).join(
|
|
51
|
-
)
|
|
53
|
+
`Tool '${call.tool}' is not allowed in batch. Disallowed tools: ${Array.from(DISALLOWED).join(', ')}`
|
|
54
|
+
);
|
|
52
55
|
}
|
|
53
56
|
|
|
54
|
-
const tool = toolMap.get(call.tool)
|
|
57
|
+
const tool = toolMap.get(call.tool);
|
|
55
58
|
if (!tool) {
|
|
56
|
-
const availableToolsList = Array.from(toolMap.keys()).filter(
|
|
57
|
-
|
|
59
|
+
const availableToolsList = Array.from(toolMap.keys()).filter(
|
|
60
|
+
(name) => !FILTERED_FROM_SUGGESTIONS.has(name)
|
|
61
|
+
);
|
|
62
|
+
throw new Error(
|
|
63
|
+
`Tool '${call.tool}' not found. Available tools: ${availableToolsList.join(', ')}`
|
|
64
|
+
);
|
|
58
65
|
}
|
|
59
|
-
const validatedParams = tool.parameters.parse(call.parameters)
|
|
66
|
+
const validatedParams = tool.parameters.parse(call.parameters);
|
|
60
67
|
|
|
61
68
|
await Session.updatePart({
|
|
62
69
|
id: partID,
|
|
63
70
|
messageID: ctx.messageID,
|
|
64
71
|
sessionID: ctx.sessionID,
|
|
65
|
-
type:
|
|
72
|
+
type: 'tool',
|
|
66
73
|
tool: call.tool,
|
|
67
74
|
callID: partID,
|
|
68
75
|
state: {
|
|
69
|
-
status:
|
|
76
|
+
status: 'running',
|
|
70
77
|
input: call.parameters,
|
|
71
78
|
time: {
|
|
72
79
|
start: callStartTime,
|
|
73
80
|
},
|
|
74
81
|
},
|
|
75
|
-
})
|
|
82
|
+
});
|
|
76
83
|
|
|
77
|
-
const result = await tool.execute(validatedParams, {
|
|
84
|
+
const result = await tool.execute(validatedParams, {
|
|
85
|
+
...ctx,
|
|
86
|
+
callID: partID,
|
|
87
|
+
});
|
|
78
88
|
|
|
79
89
|
await Session.updatePart({
|
|
80
90
|
id: partID,
|
|
81
91
|
messageID: ctx.messageID,
|
|
82
92
|
sessionID: ctx.sessionID,
|
|
83
|
-
type:
|
|
93
|
+
type: 'tool',
|
|
84
94
|
tool: call.tool,
|
|
85
95
|
callID: partID,
|
|
86
96
|
state: {
|
|
87
|
-
status:
|
|
97
|
+
status: 'completed',
|
|
88
98
|
input: call.parameters,
|
|
89
99
|
output: result.output,
|
|
90
100
|
title: result.title,
|
|
@@ -95,19 +105,19 @@ export const BatchTool = Tool.define("batch", async () => {
|
|
|
95
105
|
end: Date.now(),
|
|
96
106
|
},
|
|
97
107
|
},
|
|
98
|
-
})
|
|
108
|
+
});
|
|
99
109
|
|
|
100
|
-
return { success: true as const, tool: call.tool, result }
|
|
110
|
+
return { success: true as const, tool: call.tool, result };
|
|
101
111
|
} catch (error) {
|
|
102
112
|
await Session.updatePart({
|
|
103
113
|
id: partID,
|
|
104
114
|
messageID: ctx.messageID,
|
|
105
115
|
sessionID: ctx.sessionID,
|
|
106
|
-
type:
|
|
116
|
+
type: 'tool',
|
|
107
117
|
tool: call.tool,
|
|
108
118
|
callID: partID,
|
|
109
119
|
state: {
|
|
110
|
-
status:
|
|
120
|
+
status: 'error',
|
|
111
121
|
input: call.parameters,
|
|
112
122
|
error: error instanceof Error ? error.message : String(error),
|
|
113
123
|
time: {
|
|
@@ -115,51 +125,55 @@ export const BatchTool = Tool.define("batch", async () => {
|
|
|
115
125
|
end: Date.now(),
|
|
116
126
|
},
|
|
117
127
|
},
|
|
118
|
-
})
|
|
128
|
+
});
|
|
119
129
|
|
|
120
|
-
return { success: false as const, tool: call.tool, error }
|
|
130
|
+
return { success: false as const, tool: call.tool, error };
|
|
121
131
|
}
|
|
122
|
-
}
|
|
132
|
+
};
|
|
123
133
|
|
|
124
|
-
const results = await Promise.all(
|
|
134
|
+
const results = await Promise.all(
|
|
135
|
+
toolCalls.map((call) => executeCall(call))
|
|
136
|
+
);
|
|
125
137
|
|
|
126
138
|
// Add discarded calls as errors
|
|
127
|
-
const now = Date.now()
|
|
139
|
+
const now = Date.now();
|
|
128
140
|
for (const call of discardedCalls) {
|
|
129
|
-
const partID = Identifier.ascending(
|
|
141
|
+
const partID = Identifier.ascending('part');
|
|
130
142
|
await Session.updatePart({
|
|
131
143
|
id: partID,
|
|
132
144
|
messageID: ctx.messageID,
|
|
133
145
|
sessionID: ctx.sessionID,
|
|
134
|
-
type:
|
|
146
|
+
type: 'tool',
|
|
135
147
|
tool: call.tool,
|
|
136
148
|
callID: partID,
|
|
137
149
|
state: {
|
|
138
|
-
status:
|
|
150
|
+
status: 'error',
|
|
139
151
|
input: call.parameters,
|
|
140
|
-
error:
|
|
152
|
+
error: 'Maximum of 10 tools allowed in batch',
|
|
141
153
|
time: { start: now, end: now },
|
|
142
154
|
},
|
|
143
|
-
})
|
|
155
|
+
});
|
|
144
156
|
results.push({
|
|
145
157
|
success: false as const,
|
|
146
158
|
tool: call.tool,
|
|
147
|
-
error: new Error(
|
|
148
|
-
})
|
|
159
|
+
error: new Error('Maximum of 10 tools allowed in batch'),
|
|
160
|
+
});
|
|
149
161
|
}
|
|
150
162
|
|
|
151
|
-
const successfulCalls = results.filter((r) => r.success).length
|
|
152
|
-
const failedCalls = results.length - successfulCalls
|
|
163
|
+
const successfulCalls = results.filter((r) => r.success).length;
|
|
164
|
+
const failedCalls = results.length - successfulCalls;
|
|
153
165
|
|
|
154
166
|
const outputMessage =
|
|
155
167
|
failedCalls > 0
|
|
156
168
|
? `Executed ${successfulCalls}/${results.length} tools successfully. ${failedCalls} failed.`
|
|
157
|
-
: `All ${successfulCalls} tools executed successfully.\n\nKeep using the batch tool for optimal performance in your next response
|
|
169
|
+
: `All ${successfulCalls} tools executed successfully.\n\nKeep using the batch tool for optimal performance in your next response!`;
|
|
158
170
|
|
|
159
171
|
return {
|
|
160
172
|
title: `Batch execution (${successfulCalls}/${results.length} successful)`,
|
|
161
173
|
output: outputMessage,
|
|
162
|
-
attachments: results
|
|
174
|
+
attachments: results
|
|
175
|
+
.filter((result) => result.success)
|
|
176
|
+
.flatMap((r) => r.result.attachments ?? []),
|
|
163
177
|
metadata: {
|
|
164
178
|
totalCalls: results.length,
|
|
165
179
|
successful: successfulCalls,
|
|
@@ -167,7 +181,7 @@ export const BatchTool = Tool.define("batch", async () => {
|
|
|
167
181
|
tools: params.tool_calls.map((c) => c.tool),
|
|
168
182
|
details: results.map((r) => ({ tool: r.tool, success: r.success })),
|
|
169
183
|
},
|
|
170
|
-
}
|
|
184
|
+
};
|
|
171
185
|
},
|
|
172
|
-
}
|
|
173
|
-
})
|
|
186
|
+
};
|
|
187
|
+
});
|