@link-assistant/agent 0.26.9 → 0.26.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/package.json +1 -1
- package/src/session/message-v2.ts +8 -1
- package/src/session/processor.ts +3 -7
- package/src/session/prompt.ts +32 -26
- package/src/tool/bash.ts +98 -90
- package/src/tool/batch.ts +6 -8
- package/src/tool/grep.ts +1 -1
- package/src/tool/process-result.ts +18 -0
- package/src/tool/tool.ts +46 -9
package/package.json
CHANGED
|
@@ -338,6 +338,8 @@ export namespace MessageV2 {
|
|
|
338
338
|
status: z.literal('error'),
|
|
339
339
|
input: z.record(z.string(), z.any()),
|
|
340
340
|
error: z.string(),
|
|
341
|
+
title: z.string().optional(),
|
|
342
|
+
attachments: FilePart.array().optional(),
|
|
341
343
|
metadata: z.record(z.string(), z.any()).optional(),
|
|
342
344
|
time: z.object({
|
|
343
345
|
start: z.number(),
|
|
@@ -734,7 +736,10 @@ export namespace MessageV2 {
|
|
|
734
736
|
type: 'step-start',
|
|
735
737
|
});
|
|
736
738
|
if (part.type === 'tool') {
|
|
737
|
-
if (
|
|
739
|
+
if (
|
|
740
|
+
part.state.status === 'completed' ||
|
|
741
|
+
part.state.status === 'error'
|
|
742
|
+
) {
|
|
738
743
|
if (part.state.attachments?.length) {
|
|
739
744
|
result.push({
|
|
740
745
|
id: Identifier.ascending('message'),
|
|
@@ -753,6 +758,8 @@ export namespace MessageV2 {
|
|
|
753
758
|
],
|
|
754
759
|
});
|
|
755
760
|
}
|
|
761
|
+
}
|
|
762
|
+
if (part.state.status === 'completed') {
|
|
756
763
|
assistantMessage.parts.push({
|
|
757
764
|
type: ('tool-' + part.tool) as `tool-${string}`,
|
|
758
765
|
state: 'output-available',
|
package/src/session/processor.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { SessionStatus } from './status';
|
|
|
19
19
|
import { config, isVerbose } from '../config/config';
|
|
20
20
|
import { SessionCompaction } from './compaction';
|
|
21
21
|
import { SSEUsageExtractor } from '../util/sse-usage-extractor';
|
|
22
|
+
import { Tool } from '../tool/tool';
|
|
22
23
|
|
|
23
24
|
export namespace SessionProcessor {
|
|
24
25
|
const DOOM_LOOP_THRESHOLD = 3;
|
|
@@ -233,18 +234,13 @@ export namespace SessionProcessor {
|
|
|
233
234
|
if (match && match.state.status === 'running') {
|
|
234
235
|
await Session.updatePart({
|
|
235
236
|
...match,
|
|
236
|
-
state: {
|
|
237
|
-
status: 'completed',
|
|
237
|
+
state: Tool.toState(value.output, {
|
|
238
238
|
input: value.input,
|
|
239
|
-
output: value.output.output,
|
|
240
|
-
metadata: value.output.metadata,
|
|
241
|
-
title: value.output.title,
|
|
242
239
|
time: {
|
|
243
240
|
start: match.state.time.start,
|
|
244
241
|
end: Date.now(),
|
|
245
242
|
},
|
|
246
|
-
|
|
247
|
-
},
|
|
243
|
+
}),
|
|
248
244
|
});
|
|
249
245
|
|
|
250
246
|
delete toolcalls[value.toolCallId];
|
package/src/session/prompt.ts
CHANGED
|
@@ -48,6 +48,8 @@ import { fn } from '../util/fn';
|
|
|
48
48
|
import { SessionProcessor } from './processor';
|
|
49
49
|
import { TaskTool } from '../tool/task';
|
|
50
50
|
import { SessionStatus } from './status';
|
|
51
|
+
import { Tool } from '../tool/tool';
|
|
52
|
+
import { processResult } from '../tool/process-result';
|
|
51
53
|
|
|
52
54
|
export namespace SessionPrompt {
|
|
53
55
|
const log = Log.create({ service: 'session.prompt' });
|
|
@@ -1198,12 +1200,7 @@ export namespace SessionPrompt {
|
|
|
1198
1200
|
});
|
|
1199
1201
|
return result;
|
|
1200
1202
|
},
|
|
1201
|
-
toModelOutput
|
|
1202
|
-
return {
|
|
1203
|
-
type: 'text',
|
|
1204
|
-
value: result.output,
|
|
1205
|
-
};
|
|
1206
|
-
},
|
|
1203
|
+
toModelOutput: Tool.toModelOutput,
|
|
1207
1204
|
});
|
|
1208
1205
|
}
|
|
1209
1206
|
|
|
@@ -1260,14 +1257,10 @@ export namespace SessionPrompt {
|
|
|
1260
1257
|
output: textParts.join('\n\n'),
|
|
1261
1258
|
attachments,
|
|
1262
1259
|
content: result.content, // directly return content to preserve ordering when outputting to model
|
|
1260
|
+
isError: result.isError,
|
|
1263
1261
|
};
|
|
1264
1262
|
};
|
|
1265
|
-
item.toModelOutput =
|
|
1266
|
-
return {
|
|
1267
|
-
type: 'text',
|
|
1268
|
-
value: result.output,
|
|
1269
|
-
};
|
|
1270
|
-
};
|
|
1263
|
+
item.toModelOutput = Tool.toModelOutput;
|
|
1271
1264
|
tools[key] = item;
|
|
1272
1265
|
}
|
|
1273
1266
|
return tools;
|
|
@@ -1723,20 +1716,29 @@ export namespace SessionPrompt {
|
|
|
1723
1716
|
msg.time.completed = Date.now();
|
|
1724
1717
|
await Session.updateMessage(msg);
|
|
1725
1718
|
if (part.state.status === 'running') {
|
|
1726
|
-
part.state =
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
...
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1719
|
+
part.state = Tool.toState(
|
|
1720
|
+
{
|
|
1721
|
+
title: '',
|
|
1722
|
+
...processResult({
|
|
1723
|
+
output,
|
|
1724
|
+
exit: proc.exitCode,
|
|
1725
|
+
signal: proc.signalCode,
|
|
1726
|
+
}),
|
|
1727
|
+
metadata: {
|
|
1728
|
+
output,
|
|
1729
|
+
exit: proc.exitCode,
|
|
1730
|
+
signal: proc.signalCode,
|
|
1731
|
+
description: '',
|
|
1732
|
+
},
|
|
1737
1733
|
},
|
|
1738
|
-
|
|
1739
|
-
|
|
1734
|
+
{
|
|
1735
|
+
time: {
|
|
1736
|
+
...part.state.time,
|
|
1737
|
+
end: Date.now(),
|
|
1738
|
+
},
|
|
1739
|
+
input: part.state.input,
|
|
1740
|
+
}
|
|
1741
|
+
);
|
|
1740
1742
|
await Session.updatePart(part);
|
|
1741
1743
|
}
|
|
1742
1744
|
return { info: msg, parts: [part] };
|
|
@@ -1794,7 +1796,11 @@ export namespace SessionPrompt {
|
|
|
1794
1796
|
const results = await Promise.all(
|
|
1795
1797
|
shell.map(async ([, cmd]) => {
|
|
1796
1798
|
try {
|
|
1797
|
-
|
|
1799
|
+
const result = await $`${{ raw: cmd }}`.nothrow();
|
|
1800
|
+
return processResult({
|
|
1801
|
+
output: result.text(),
|
|
1802
|
+
exit: result.exitCode,
|
|
1803
|
+
}).output;
|
|
1798
1804
|
} catch (error) {
|
|
1799
1805
|
return `Error executing command: ${error instanceof Error ? error.message : String(error)}`;
|
|
1800
1806
|
}
|
package/src/tool/bash.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import z from 'zod';
|
|
2
|
-
import {
|
|
2
|
+
import { ProcessRunner } from 'command-stream/process-runner';
|
|
3
3
|
import { Tool } from './tool';
|
|
4
4
|
import DESCRIPTION from './bash.txt';
|
|
5
5
|
import { Branding } from '../branding';
|
|
@@ -7,18 +7,29 @@ import { Log } from '../util/log';
|
|
|
7
7
|
import { Instance } from '../project/instance';
|
|
8
8
|
import { lazy } from '../util/lazy';
|
|
9
9
|
import { Language } from 'web-tree-sitter';
|
|
10
|
-
import { $ } from 'bun';
|
|
11
|
-
import { Filesystem } from '../util/filesystem';
|
|
12
10
|
import { Permission } from '../permission';
|
|
13
11
|
import { fileURLToPath } from 'url';
|
|
12
|
+
import { processResult } from './process-result';
|
|
14
13
|
|
|
15
14
|
const MAX_OUTPUT_LENGTH = 30_000;
|
|
16
15
|
const DEFAULT_TIMEOUT = 1 * 60 * 1000;
|
|
17
16
|
const MAX_TIMEOUT = 10 * 60 * 1000;
|
|
18
|
-
const
|
|
19
|
-
|
|
17
|
+
const KILL_SIGNAL = 'SIGTERM';
|
|
20
18
|
export const log = Log.create({ service: 'bash-tool' });
|
|
21
19
|
|
|
20
|
+
// command-stream 1.3.0 signals only the shell on Windows, which has no process
|
|
21
|
+
// groups, so the shell's descendants keep running
|
|
22
|
+
// (link-foundation/command-stream#205). taskkill /t walks the tree from the
|
|
23
|
+
// shell, so it must run before the shell itself is stopped.
|
|
24
|
+
const killWindowsTree = async (pid: number | undefined) => {
|
|
25
|
+
if (!pid) return;
|
|
26
|
+
// A failed taskkill resolves with its exit code; the runner is stopped next.
|
|
27
|
+
await new ProcessRunner(
|
|
28
|
+
{ mode: 'exec', file: 'taskkill', args: ['/pid', String(pid), '/f', '/t'] },
|
|
29
|
+
{ mirror: false, capture: false, stdin: 'ignore' }
|
|
30
|
+
).catch(() => {});
|
|
31
|
+
};
|
|
32
|
+
|
|
22
33
|
const resolveWasm = (asset: string) => {
|
|
23
34
|
if (asset.startsWith('file://')) return fileURLToPath(asset);
|
|
24
35
|
if (asset.startsWith('/') || /^[a-z]:/i.test(asset)) return asset;
|
|
@@ -118,15 +129,8 @@ export const BashTool = Tool.define('bash', {
|
|
|
118
129
|
});
|
|
119
130
|
}
|
|
120
131
|
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
cwd: Instance.directory,
|
|
124
|
-
env: {
|
|
125
|
-
...process.env,
|
|
126
|
-
},
|
|
127
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
128
|
-
detached: process.platform !== 'win32',
|
|
129
|
-
});
|
|
132
|
+
const timeoutSignal = AbortSignal.timeout(timeout);
|
|
133
|
+
const cancel = AbortSignal.any([ctx.abort, timeoutSignal]);
|
|
130
134
|
|
|
131
135
|
let output = '';
|
|
132
136
|
|
|
@@ -138,90 +142,74 @@ export const BashTool = Tool.define('bash', {
|
|
|
138
142
|
},
|
|
139
143
|
});
|
|
140
144
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
ctx.metadata({
|
|
144
|
-
metadata: {
|
|
145
|
-
output,
|
|
146
|
-
description: params.description,
|
|
147
|
-
},
|
|
148
|
-
});
|
|
149
|
-
};
|
|
145
|
+
let exit: number | null = null;
|
|
146
|
+
let signal: string | null = null;
|
|
150
147
|
|
|
151
|
-
|
|
152
|
-
|
|
148
|
+
// command-stream 1.3.0 finishes a runner whose signal is already aborted
|
|
149
|
+
// before stream() subscribes to its end event, so the iterator never ends
|
|
150
|
+
// (link-foundation/command-stream#207).
|
|
151
|
+
// A cancelled command does not need to start at all.
|
|
152
|
+
if (!cancel.aborted) {
|
|
153
|
+
// On Windows the runner is stopped only after taskkill has stopped the
|
|
154
|
+
// shell's process tree.
|
|
155
|
+
const stop =
|
|
156
|
+
process.platform === 'win32' ? new AbortController() : undefined;
|
|
153
157
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
158
|
+
// The file/args shell form spawns the command string through Node's
|
|
159
|
+
// platform shell, as `spawn(command, { shell: true })` does. A command
|
|
160
|
+
// string spec would route simple commands and pipelines through
|
|
161
|
+
// command-stream's JavaScript builtins (`ls`, `exit`, `cd`, ...) instead.
|
|
162
|
+
const command = new ProcessRunner(
|
|
163
|
+
{ mode: 'shell', file: params.command, args: [] },
|
|
164
|
+
{
|
|
165
|
+
cwd: Instance.directory,
|
|
166
|
+
env: { ...process.env },
|
|
167
|
+
signal: stop?.signal ?? cancel,
|
|
168
|
+
killSignal: KILL_SIGNAL,
|
|
169
|
+
killGrace: 200,
|
|
170
|
+
mirror: false,
|
|
171
|
+
capture: true,
|
|
172
|
+
stdin: 'ignore',
|
|
173
|
+
}
|
|
174
|
+
);
|
|
157
175
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
176
|
+
const stopTree = () => {
|
|
177
|
+
void killWindowsTree(command.child?.pid).finally(() => stop?.abort());
|
|
178
|
+
};
|
|
179
|
+
if (stop) cancel.addEventListener('abort', stopTree, { once: true });
|
|
163
180
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
181
|
+
for await (const chunk of command.stream()) {
|
|
182
|
+
if (chunk.type === 'exit') {
|
|
183
|
+
exit = chunk.code;
|
|
184
|
+
} else {
|
|
185
|
+
output += chunk.data.toString();
|
|
186
|
+
ctx.metadata({
|
|
187
|
+
metadata: {
|
|
188
|
+
output,
|
|
189
|
+
description: params.description,
|
|
190
|
+
},
|
|
168
191
|
});
|
|
169
|
-
killer.once('exit', resolve);
|
|
170
|
-
killer.once('error', resolve);
|
|
171
|
-
});
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
try {
|
|
176
|
-
process.kill(-pid, 'SIGTERM');
|
|
177
|
-
await Bun.sleep(SIGKILL_TIMEOUT_MS);
|
|
178
|
-
if (!exited) {
|
|
179
|
-
process.kill(-pid, 'SIGKILL');
|
|
180
|
-
}
|
|
181
|
-
} catch (_e) {
|
|
182
|
-
proc.kill('SIGTERM');
|
|
183
|
-
await Bun.sleep(SIGKILL_TIMEOUT_MS);
|
|
184
|
-
if (!exited) {
|
|
185
|
-
proc.kill('SIGKILL');
|
|
186
192
|
}
|
|
187
193
|
}
|
|
188
|
-
};
|
|
189
194
|
|
|
190
|
-
|
|
191
|
-
aborted = true;
|
|
192
|
-
await killTree();
|
|
193
|
-
}
|
|
195
|
+
cancel.removeEventListener('abort', stopTree);
|
|
194
196
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
timedOut = true;
|
|
204
|
-
void killTree();
|
|
205
|
-
}, timeout);
|
|
206
|
-
|
|
207
|
-
await new Promise<void>((resolve, reject) => {
|
|
208
|
-
const cleanup = () => {
|
|
209
|
-
clearTimeout(timeoutTimer);
|
|
210
|
-
ctx.abort.removeEventListener('abort', abortHandler);
|
|
211
|
-
};
|
|
197
|
+
// command-stream reports a shell that died from a signal as exit 0
|
|
198
|
+
// (link-foundation/command-stream#208).
|
|
199
|
+
// Its result keeps the native child, which has the signal name.
|
|
200
|
+
const finished = command.result as {
|
|
201
|
+
child?: { signalCode?: string | null };
|
|
202
|
+
} | null;
|
|
203
|
+
signal = finished?.child?.signalCode ?? null;
|
|
204
|
+
}
|
|
212
205
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
exited = true;
|
|
221
|
-
cleanup();
|
|
222
|
-
reject(error);
|
|
223
|
-
});
|
|
224
|
-
});
|
|
206
|
+
const timedOut = timeoutSignal.aborted;
|
|
207
|
+
const aborted = ctx.abort.aborted;
|
|
208
|
+
// command-stream settles a cancelled command as soon as it sends the kill
|
|
209
|
+
// signal and reports a synthesized 128 + signal code. The real exit status
|
|
210
|
+
// is unavailable, as it is when the shell itself dies from a signal.
|
|
211
|
+
if (timedOut || aborted) signal = KILL_SIGNAL;
|
|
212
|
+
if (signal) exit = null;
|
|
225
213
|
|
|
226
214
|
if (output.length > MAX_OUTPUT_LENGTH) {
|
|
227
215
|
output = output.slice(0, MAX_OUTPUT_LENGTH);
|
|
@@ -236,14 +224,34 @@ export const BashTool = Tool.define('bash', {
|
|
|
236
224
|
output += '\n\n(Command was aborted)';
|
|
237
225
|
}
|
|
238
226
|
|
|
227
|
+
const result = processResult({
|
|
228
|
+
output,
|
|
229
|
+
exit,
|
|
230
|
+
signal,
|
|
231
|
+
timedOut,
|
|
232
|
+
aborted,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
log.debug(() => ({
|
|
236
|
+
message: 'command finished',
|
|
237
|
+
exit,
|
|
238
|
+
signal,
|
|
239
|
+
timedOut,
|
|
240
|
+
aborted,
|
|
241
|
+
isError: result.isError,
|
|
242
|
+
}));
|
|
243
|
+
|
|
239
244
|
return {
|
|
240
245
|
title: params.command,
|
|
241
246
|
metadata: {
|
|
242
247
|
output,
|
|
243
|
-
exit
|
|
248
|
+
exit,
|
|
249
|
+
signal,
|
|
250
|
+
timedOut,
|
|
251
|
+
aborted,
|
|
244
252
|
description: params.description,
|
|
245
253
|
},
|
|
246
|
-
|
|
254
|
+
...result,
|
|
247
255
|
};
|
|
248
256
|
},
|
|
249
257
|
});
|
package/src/tool/batch.ts
CHANGED
|
@@ -101,21 +101,18 @@ export const BatchTool = Tool.define('batch', async () => {
|
|
|
101
101
|
type: 'tool',
|
|
102
102
|
tool: call.tool,
|
|
103
103
|
callID: partID,
|
|
104
|
-
state: {
|
|
105
|
-
status: 'completed',
|
|
104
|
+
state: Tool.toState(result, {
|
|
106
105
|
input: call.parameters,
|
|
107
|
-
output: result.output,
|
|
108
|
-
title: result.title,
|
|
109
|
-
metadata: result.metadata,
|
|
110
|
-
attachments: result.attachments,
|
|
111
106
|
time: {
|
|
112
107
|
start: callStartTime,
|
|
113
108
|
end: Date.now(),
|
|
114
109
|
},
|
|
115
|
-
},
|
|
110
|
+
}),
|
|
116
111
|
});
|
|
117
112
|
|
|
118
|
-
return
|
|
113
|
+
return result.isError
|
|
114
|
+
? { success: false as const, tool: call.tool, error: result.output }
|
|
115
|
+
: { success: true as const, tool: call.tool, result };
|
|
119
116
|
} catch (error) {
|
|
120
117
|
await Session.updatePart({
|
|
121
118
|
id: partID,
|
|
@@ -179,6 +176,7 @@ export const BatchTool = Tool.define('batch', async () => {
|
|
|
179
176
|
return {
|
|
180
177
|
title: `Batch execution (${successfulCalls}/${results.length} successful)`,
|
|
181
178
|
output: outputMessage,
|
|
179
|
+
isError: failedCalls > 0,
|
|
182
180
|
attachments: results
|
|
183
181
|
.filter((result) => result.success)
|
|
184
182
|
.flatMap((r) => r.result.attachments ?? []),
|
package/src/tool/grep.ts
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Keep process status in the text sent to every provider, including those
|
|
2
|
+
* whose tool protocol has no separate error flag. */
|
|
3
|
+
export function processResult(input: {
|
|
4
|
+
output: string;
|
|
5
|
+
exit: number | null;
|
|
6
|
+
signal?: string | null;
|
|
7
|
+
timedOut?: boolean;
|
|
8
|
+
aborted?: boolean;
|
|
9
|
+
}) {
|
|
10
|
+
const isError = input.exit !== 0 || !!input.timedOut || !!input.aborted;
|
|
11
|
+
const status = input.signal ? ` (signal ${input.signal})` : '';
|
|
12
|
+
return {
|
|
13
|
+
output: isError
|
|
14
|
+
? `Exit code ${input.exit ?? 'unavailable'}${status}\n${input.output}`
|
|
15
|
+
: input.output,
|
|
16
|
+
isError,
|
|
17
|
+
};
|
|
18
|
+
}
|
package/src/tool/tool.ts
CHANGED
|
@@ -1,11 +1,56 @@
|
|
|
1
1
|
import z from 'zod';
|
|
2
2
|
import type { MessageV2 } from '../session/message-v2';
|
|
3
|
+
import type { ToolResultOutput } from 'ai';
|
|
3
4
|
|
|
4
5
|
export namespace Tool {
|
|
5
6
|
interface Metadata {
|
|
6
7
|
[key: string]: any;
|
|
7
8
|
}
|
|
8
9
|
|
|
10
|
+
export interface Result<M extends Metadata = Metadata> {
|
|
11
|
+
title: string;
|
|
12
|
+
metadata: M;
|
|
13
|
+
output: string;
|
|
14
|
+
isError?: boolean;
|
|
15
|
+
attachments?: MessageV2.FilePart[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// AI SDK 7 passes an options object containing the execution output.
|
|
19
|
+
export function toModelOutput({
|
|
20
|
+
output: result,
|
|
21
|
+
}: {
|
|
22
|
+
output: Pick<Result, 'output' | 'isError'>;
|
|
23
|
+
}): ToolResultOutput {
|
|
24
|
+
return {
|
|
25
|
+
type: result.isError ? 'error-text' : 'text',
|
|
26
|
+
value: result.output,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function toState(
|
|
31
|
+
result: Result,
|
|
32
|
+
input: { input: Record<string, any>; time: { start: number; end: number } }
|
|
33
|
+
): MessageV2.ToolStateCompleted | MessageV2.ToolStateError {
|
|
34
|
+
if (result.isError) {
|
|
35
|
+
return {
|
|
36
|
+
...input,
|
|
37
|
+
status: 'error',
|
|
38
|
+
error: result.output,
|
|
39
|
+
metadata: result.metadata,
|
|
40
|
+
title: result.title,
|
|
41
|
+
attachments: result.attachments,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
...input,
|
|
46
|
+
status: 'completed',
|
|
47
|
+
output: result.output,
|
|
48
|
+
title: result.title,
|
|
49
|
+
metadata: result.metadata,
|
|
50
|
+
attachments: result.attachments,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
9
54
|
export type Context<M extends Metadata = Metadata> = {
|
|
10
55
|
sessionID: string;
|
|
11
56
|
messageID: string;
|
|
@@ -23,15 +68,7 @@ export namespace Tool {
|
|
|
23
68
|
init: () => Promise<{
|
|
24
69
|
description: string;
|
|
25
70
|
parameters: Parameters;
|
|
26
|
-
execute(
|
|
27
|
-
args: z.infer<Parameters>,
|
|
28
|
-
ctx: Context
|
|
29
|
-
): Promise<{
|
|
30
|
-
title: string;
|
|
31
|
-
metadata: M;
|
|
32
|
-
output: string;
|
|
33
|
-
attachments?: MessageV2.FilePart[];
|
|
34
|
-
}>;
|
|
71
|
+
execute(args: z.infer<Parameters>, ctx: Context): Promise<Result<M>>;
|
|
35
72
|
formatValidationError?(error: z.ZodError): string;
|
|
36
73
|
}>;
|
|
37
74
|
}
|