@oh-my-pi/pi-coding-agent 6.2.0 → 6.7.67
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/CHANGELOG.md +60 -0
- package/docs/sdk.md +1 -1
- package/package.json +5 -5
- package/scripts/generate-template.ts +6 -6
- package/src/cli/args.ts +3 -0
- package/src/core/agent-session.ts +39 -0
- package/src/core/bash-executor.ts +3 -3
- package/src/core/cursor/exec-bridge.ts +95 -88
- package/src/core/custom-commands/bundled/review/index.ts +142 -145
- package/src/core/custom-commands/bundled/wt/index.ts +68 -66
- package/src/core/custom-commands/loader.ts +4 -6
- package/src/core/custom-tools/index.ts +2 -2
- package/src/core/custom-tools/loader.ts +66 -61
- package/src/core/custom-tools/types.ts +4 -4
- package/src/core/custom-tools/wrapper.ts +61 -25
- package/src/core/event-bus.ts +19 -47
- package/src/core/extensions/index.ts +8 -4
- package/src/core/extensions/loader.ts +160 -120
- package/src/core/extensions/types.ts +4 -4
- package/src/core/extensions/wrapper.ts +149 -100
- package/src/core/hooks/index.ts +1 -1
- package/src/core/hooks/tool-wrapper.ts +96 -70
- package/src/core/hooks/types.ts +1 -2
- package/src/core/index.ts +1 -0
- package/src/core/mcp/index.ts +6 -2
- package/src/core/mcp/json-rpc.ts +88 -0
- package/src/core/mcp/loader.ts +22 -4
- package/src/core/mcp/manager.ts +202 -48
- package/src/core/mcp/tool-bridge.ts +143 -55
- package/src/core/mcp/tool-cache.ts +122 -0
- package/src/core/python-executor.ts +3 -9
- package/src/core/sdk.ts +33 -32
- package/src/core/session-manager.ts +30 -0
- package/src/core/settings-manager.ts +54 -1
- package/src/core/ssh/ssh-executor.ts +6 -84
- package/src/core/streaming-output.ts +107 -53
- package/src/core/tools/ask.ts +92 -93
- package/src/core/tools/bash.ts +103 -94
- package/src/core/tools/calculator.ts +41 -26
- package/src/core/tools/complete.ts +76 -66
- package/src/core/tools/context.ts +22 -24
- package/src/core/tools/exa/index.ts +1 -1
- package/src/core/tools/exa/mcp-client.ts +56 -101
- package/src/core/tools/find.ts +250 -253
- package/src/core/tools/git.ts +39 -33
- package/src/core/tools/grep.ts +440 -427
- package/src/core/tools/index.ts +63 -61
- package/src/core/tools/ls.ts +119 -114
- package/src/core/tools/lsp/clients/biome-client.ts +5 -7
- package/src/core/tools/lsp/clients/index.ts +4 -4
- package/src/core/tools/lsp/clients/lsp-linter-client.ts +5 -7
- package/src/core/tools/lsp/config.ts +2 -2
- package/src/core/tools/lsp/index.ts +604 -578
- package/src/core/tools/notebook.ts +121 -119
- package/src/core/tools/output.ts +163 -147
- package/src/core/tools/patch/applicator.ts +1100 -0
- package/src/core/tools/patch/diff.ts +362 -0
- package/src/core/tools/patch/fuzzy.ts +647 -0
- package/src/core/tools/patch/index.ts +430 -0
- package/src/core/tools/patch/normalize.ts +220 -0
- package/src/core/tools/patch/normative.ts +73 -0
- package/src/core/tools/patch/parser.ts +528 -0
- package/src/core/tools/patch/shared.ts +257 -0
- package/src/core/tools/patch/types.ts +244 -0
- package/src/core/tools/python.ts +139 -136
- package/src/core/tools/read.ts +239 -216
- package/src/core/tools/render-utils.ts +196 -77
- package/src/core/tools/renderers.ts +6 -2
- package/src/core/tools/ssh.ts +99 -80
- package/src/core/tools/task/executor.ts +11 -7
- package/src/core/tools/task/index.ts +352 -343
- package/src/core/tools/task/worker.ts +13 -23
- package/src/core/tools/todo-write.ts +74 -59
- package/src/core/tools/web-fetch.ts +54 -47
- package/src/core/tools/web-search/index.ts +27 -16
- package/src/core/tools/write.ts +108 -47
- package/src/core/ttsr.ts +106 -152
- package/src/core/voice.ts +49 -39
- package/src/index.ts +16 -12
- package/src/lib/worktree/index.ts +1 -9
- package/src/modes/interactive/components/diff.ts +15 -8
- package/src/modes/interactive/components/settings-defs.ts +42 -0
- package/src/modes/interactive/components/tool-execution.ts +46 -8
- package/src/modes/interactive/controllers/event-controller.ts +6 -19
- package/src/modes/interactive/controllers/input-controller.ts +1 -1
- package/src/modes/interactive/utils/ui-helpers.ts +5 -1
- package/src/modes/rpc/rpc-mode.ts +99 -81
- package/src/prompts/tools/patch.md +76 -0
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/{edit.md → replace.md} +1 -0
- package/src/utils/shell.ts +0 -40
- package/src/core/tools/edit-diff.ts +0 -574
- package/src/core/tools/edit.ts +0 -345
|
@@ -4,22 +4,26 @@
|
|
|
4
4
|
|
|
5
5
|
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
6
6
|
import * as path from "node:path";
|
|
7
|
+
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
8
|
+
import type { ImageContent, Model, TextContent } from "@oh-my-pi/pi-ai";
|
|
7
9
|
import type { KeyId } from "@oh-my-pi/pi-tui";
|
|
10
|
+
import type { TSchema } from "@sinclair/typebox";
|
|
8
11
|
import * as TypeBox from "@sinclair/typebox";
|
|
9
12
|
import { type ExtensionModule, extensionModuleCapability } from "../../capability/extension-module";
|
|
10
13
|
import { loadCapability } from "../../discovery";
|
|
11
14
|
import { expandPath, getExtensionNameFromPath } from "../../discovery/helpers";
|
|
12
15
|
import * as piCodingAgent from "../../index";
|
|
13
|
-
import {
|
|
16
|
+
import { EventBus } from "../event-bus";
|
|
14
17
|
import type { ExecOptions } from "../exec";
|
|
15
18
|
import { execCommand } from "../exec";
|
|
16
19
|
import { logger } from "../logger";
|
|
20
|
+
import type { CustomMessage } from "../messages";
|
|
17
21
|
import type {
|
|
18
22
|
Extension,
|
|
19
23
|
ExtensionAPI,
|
|
20
24
|
ExtensionContext,
|
|
21
25
|
ExtensionFactory,
|
|
22
|
-
ExtensionRuntime,
|
|
26
|
+
ExtensionRuntime as IExtensionRuntime,
|
|
23
27
|
LoadExtensionsResult,
|
|
24
28
|
MessageRenderer,
|
|
25
29
|
RegisteredCommand,
|
|
@@ -36,147 +40,183 @@ function resolvePath(extPath: string, cwd: string): string {
|
|
|
36
40
|
|
|
37
41
|
type HandlerFn = (...args: unknown[]) => Promise<unknown>;
|
|
38
42
|
|
|
43
|
+
export class ExtensionRuntimeNotInitializedError extends Error {
|
|
44
|
+
constructor() {
|
|
45
|
+
super("Extension runtime not initialized. Action methods cannot be called during extension loading.");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
39
49
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
50
|
+
* Extension runtime with throwing stubs for action methods.
|
|
51
|
+
* These are replaced with real implementations during initialization.
|
|
42
52
|
*/
|
|
43
|
-
export
|
|
44
|
-
|
|
45
|
-
throw new Error("Extension runtime not initialized. Action methods cannot be called during extension loading.");
|
|
46
|
-
};
|
|
53
|
+
export class ExtensionRuntime implements IExtensionRuntime {
|
|
54
|
+
flagValues = new Map<string, boolean | string>();
|
|
47
55
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
sendMessage(): void {
|
|
57
|
+
throw new ExtensionRuntimeNotInitializedError();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
sendUserMessage(): void {
|
|
61
|
+
throw new ExtensionRuntimeNotInitializedError();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
appendEntry(): void {
|
|
65
|
+
throw new ExtensionRuntimeNotInitializedError();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
setLabel(): void {
|
|
69
|
+
throw new ExtensionRuntimeNotInitializedError();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
getActiveTools(): string[] {
|
|
73
|
+
throw new ExtensionRuntimeNotInitializedError();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
getAllTools(): string[] {
|
|
77
|
+
throw new ExtensionRuntimeNotInitializedError();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
setActiveTools(): Promise<void> {
|
|
81
|
+
throw new ExtensionRuntimeNotInitializedError();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
setModel(): Promise<boolean> {
|
|
85
|
+
throw new ExtensionRuntimeNotInitializedError();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
getThinkingLevel(): ThinkingLevel {
|
|
89
|
+
throw new ExtensionRuntimeNotInitializedError();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
setThinkingLevel(): void {
|
|
93
|
+
throw new ExtensionRuntimeNotInitializedError();
|
|
94
|
+
}
|
|
61
95
|
}
|
|
62
96
|
|
|
63
97
|
/**
|
|
64
|
-
*
|
|
98
|
+
* ExtensionAPI implementation for an extension.
|
|
65
99
|
* Registration methods write to the extension object.
|
|
66
100
|
* Action methods delegate to the shared runtime.
|
|
67
101
|
*/
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
},
|
|
84
|
-
|
|
85
|
-
registerTool(tool: ToolDefinition): void {
|
|
86
|
-
extension.tools.set(tool.name, {
|
|
87
|
-
definition: tool,
|
|
88
|
-
extensionPath: extension.path,
|
|
89
|
-
});
|
|
90
|
-
},
|
|
102
|
+
class ConcreteExtensionAPI implements ExtensionAPI, IExtensionRuntime {
|
|
103
|
+
readonly logger = logger;
|
|
104
|
+
readonly typebox = TypeBox;
|
|
105
|
+
readonly pi = piCodingAgent;
|
|
106
|
+
readonly events: EventBus;
|
|
107
|
+
readonly flagValues = new Map<string, boolean | string>();
|
|
108
|
+
|
|
109
|
+
constructor(
|
|
110
|
+
private extension: Extension,
|
|
111
|
+
private runtime: IExtensionRuntime,
|
|
112
|
+
private cwd: string,
|
|
113
|
+
eventBus: EventBus,
|
|
114
|
+
) {
|
|
115
|
+
this.events = eventBus;
|
|
116
|
+
}
|
|
91
117
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
handler: RegisteredCommand["handler"];
|
|
98
|
-
},
|
|
99
|
-
): void {
|
|
100
|
-
extension.commands.set(name, { name, ...options });
|
|
101
|
-
},
|
|
118
|
+
on<F extends HandlerFn>(event: string, handler: F): void {
|
|
119
|
+
const list = this.extension.handlers.get(event) ?? [];
|
|
120
|
+
list.push(handler);
|
|
121
|
+
this.extension.handlers.set(event, list);
|
|
122
|
+
}
|
|
102
123
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
124
|
+
registerTool<TParams extends TSchema = TSchema, TDetails = unknown>(tool: ToolDefinition<TParams, TDetails>): void {
|
|
125
|
+
this.extension.tools.set(tool.name, {
|
|
126
|
+
definition: tool,
|
|
127
|
+
extensionPath: this.extension.path,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
106
130
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
): void {
|
|
114
|
-
extension.shortcuts.set(shortcut, { shortcut, extensionPath: extension.path, ...options });
|
|
131
|
+
registerCommand(
|
|
132
|
+
name: string,
|
|
133
|
+
options: {
|
|
134
|
+
description?: string;
|
|
135
|
+
getArgumentCompletions?: RegisteredCommand["getArgumentCompletions"];
|
|
136
|
+
handler: RegisteredCommand["handler"];
|
|
115
137
|
},
|
|
138
|
+
): void {
|
|
139
|
+
this.extension.commands.set(name, { name, ...options });
|
|
140
|
+
}
|
|
116
141
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
): void {
|
|
121
|
-
extension.flags.set(name, { name, extensionPath: extension.path, ...options });
|
|
122
|
-
if (options.default !== undefined) {
|
|
123
|
-
runtime.flagValues.set(name, options.default);
|
|
124
|
-
}
|
|
125
|
-
},
|
|
142
|
+
setLabel(label: string): void {
|
|
143
|
+
this.extension.label = label;
|
|
144
|
+
}
|
|
126
145
|
|
|
127
|
-
|
|
128
|
-
|
|
146
|
+
registerShortcut(
|
|
147
|
+
shortcut: KeyId,
|
|
148
|
+
options: {
|
|
149
|
+
description?: string;
|
|
150
|
+
handler: (ctx: ExtensionContext) => Promise<void> | void;
|
|
129
151
|
},
|
|
152
|
+
): void {
|
|
153
|
+
this.extension.shortcuts.set(shortcut, { shortcut, extensionPath: this.extension.path, ...options });
|
|
154
|
+
}
|
|
130
155
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
156
|
+
registerFlag(
|
|
157
|
+
name: string,
|
|
158
|
+
options: { description?: string; type: "boolean" | "string"; default?: boolean | string },
|
|
159
|
+
): void {
|
|
160
|
+
this.extension.flags.set(name, { name, extensionPath: this.extension.path, ...options });
|
|
161
|
+
if (options.default !== undefined) {
|
|
162
|
+
this.runtime.flagValues.set(name, options.default);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
135
165
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
166
|
+
registerMessageRenderer<T>(customType: string, renderer: MessageRenderer<T>): void {
|
|
167
|
+
this.extension.messageRenderers.set(customType, renderer as MessageRenderer);
|
|
168
|
+
}
|
|
139
169
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
170
|
+
getFlag(name: string): boolean | string | undefined {
|
|
171
|
+
if (!this.extension.flags.has(name)) return undefined;
|
|
172
|
+
return this.runtime.flagValues.get(name);
|
|
173
|
+
}
|
|
143
174
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
},
|
|
175
|
+
sendMessage<T = unknown>(
|
|
176
|
+
message: Pick<CustomMessage<T>, "customType" | "content" | "display" | "details">,
|
|
177
|
+
options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn" },
|
|
178
|
+
): void {
|
|
179
|
+
this.runtime.sendMessage(message, options);
|
|
180
|
+
}
|
|
147
181
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
},
|
|
182
|
+
sendUserMessage(
|
|
183
|
+
content: string | (TextContent | ImageContent)[],
|
|
184
|
+
options?: { deliverAs?: "steer" | "followUp" },
|
|
185
|
+
): void {
|
|
186
|
+
this.runtime.sendUserMessage(content, options);
|
|
187
|
+
}
|
|
151
188
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
189
|
+
appendEntry(customType: string, data?: unknown): void {
|
|
190
|
+
this.runtime.appendEntry(customType, data);
|
|
191
|
+
}
|
|
155
192
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
193
|
+
exec(command: string, args: string[], options?: ExecOptions) {
|
|
194
|
+
return execCommand(command, args, options?.cwd ?? this.cwd, options);
|
|
195
|
+
}
|
|
159
196
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
197
|
+
getActiveTools(): string[] {
|
|
198
|
+
return this.runtime.getActiveTools();
|
|
199
|
+
}
|
|
163
200
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
201
|
+
getAllTools(): string[] {
|
|
202
|
+
return this.runtime.getAllTools();
|
|
203
|
+
}
|
|
167
204
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
205
|
+
setActiveTools(toolNames: string[]): Promise<void> {
|
|
206
|
+
return this.runtime.setActiveTools(toolNames);
|
|
207
|
+
}
|
|
171
208
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
209
|
+
setModel(model: Model<any>): Promise<boolean> {
|
|
210
|
+
return this.runtime.setModel(model);
|
|
211
|
+
}
|
|
175
212
|
|
|
176
|
-
|
|
177
|
-
|
|
213
|
+
getThinkingLevel(): ThinkingLevel {
|
|
214
|
+
return this.runtime.getThinkingLevel();
|
|
215
|
+
}
|
|
178
216
|
|
|
179
|
-
|
|
217
|
+
setThinkingLevel(level: ThinkingLevel): void {
|
|
218
|
+
this.runtime.setThinkingLevel(level);
|
|
219
|
+
}
|
|
180
220
|
}
|
|
181
221
|
|
|
182
222
|
/**
|
|
@@ -199,7 +239,7 @@ async function loadExtension(
|
|
|
199
239
|
extensionPath: string,
|
|
200
240
|
cwd: string,
|
|
201
241
|
eventBus: EventBus,
|
|
202
|
-
runtime:
|
|
242
|
+
runtime: IExtensionRuntime,
|
|
203
243
|
): Promise<{ extension: Extension | null; error: string | null }> {
|
|
204
244
|
const resolvedPath = resolvePath(extensionPath, cwd);
|
|
205
245
|
|
|
@@ -215,7 +255,7 @@ async function loadExtension(
|
|
|
215
255
|
}
|
|
216
256
|
|
|
217
257
|
const extension = createExtension(extensionPath, resolvedPath);
|
|
218
|
-
const api =
|
|
258
|
+
const api = new ConcreteExtensionAPI(extension, runtime, cwd, eventBus);
|
|
219
259
|
await factory(api);
|
|
220
260
|
|
|
221
261
|
return { extension, error: null };
|
|
@@ -232,11 +272,11 @@ export async function loadExtensionFromFactory(
|
|
|
232
272
|
factory: ExtensionFactory,
|
|
233
273
|
cwd: string,
|
|
234
274
|
eventBus: EventBus,
|
|
235
|
-
runtime:
|
|
275
|
+
runtime: IExtensionRuntime,
|
|
236
276
|
name = "<inline>",
|
|
237
277
|
): Promise<Extension> {
|
|
238
278
|
const extension = createExtension(name, name);
|
|
239
|
-
const api =
|
|
279
|
+
const api = new ConcreteExtensionAPI(extension, runtime, cwd, eventBus);
|
|
240
280
|
await factory(api);
|
|
241
281
|
return extension;
|
|
242
282
|
}
|
|
@@ -247,8 +287,8 @@ export async function loadExtensionFromFactory(
|
|
|
247
287
|
export async function loadExtensions(paths: string[], cwd: string, eventBus?: EventBus): Promise<LoadExtensionsResult> {
|
|
248
288
|
const extensions: Extension[] = [];
|
|
249
289
|
const errors: Array<{ path: string; error: string }> = [];
|
|
250
|
-
const resolvedEventBus = eventBus ??
|
|
251
|
-
const runtime =
|
|
290
|
+
const resolvedEventBus = eventBus ?? new EventBus();
|
|
291
|
+
const runtime = new ExtensionRuntime();
|
|
252
292
|
|
|
253
293
|
for (const extPath of paths) {
|
|
254
294
|
const { extension, error } = await loadExtension(extPath, cwd, resolvedEventBus, runtime);
|
|
@@ -30,7 +30,7 @@ import type {
|
|
|
30
30
|
} from "../session-manager";
|
|
31
31
|
import type { BashToolDetails, FindToolDetails, GrepToolDetails, LsToolDetails, ReadToolDetails } from "../tools";
|
|
32
32
|
import type { BashOperations } from "../tools/bash";
|
|
33
|
-
import type { EditToolDetails } from "../tools/
|
|
33
|
+
import type { EditToolDetails } from "../tools/patch";
|
|
34
34
|
|
|
35
35
|
export type { ExecOptions, ExecResult } from "../exec";
|
|
36
36
|
export type { AgentToolResult, AgentToolUpdateCallback };
|
|
@@ -779,8 +779,8 @@ export type ExtensionFactory = (pi: ExtensionAPI) => void | Promise<void>;
|
|
|
779
779
|
// Loaded Extension Types
|
|
780
780
|
// ============================================================================
|
|
781
781
|
|
|
782
|
-
export interface RegisteredTool {
|
|
783
|
-
definition: ToolDefinition
|
|
782
|
+
export interface RegisteredTool<TParams extends TSchema = TSchema, TDetails = unknown> {
|
|
783
|
+
definition: ToolDefinition<TParams, TDetails>;
|
|
784
784
|
extensionPath: string;
|
|
785
785
|
}
|
|
786
786
|
|
|
@@ -877,7 +877,7 @@ export interface Extension {
|
|
|
877
877
|
resolvedPath: string;
|
|
878
878
|
label?: string;
|
|
879
879
|
handlers: Map<string, HandlerFn[]>;
|
|
880
|
-
tools: Map<string, RegisteredTool
|
|
880
|
+
tools: Map<string, RegisteredTool<any, any>>;
|
|
881
881
|
messageRenderers: Map<string, MessageRenderer>;
|
|
882
882
|
commands: Map<string, RegisteredCommand>;
|
|
883
883
|
flags: Map<string, ExtensionFlag>;
|
|
@@ -9,27 +9,53 @@ import type { ExtensionRunner } from "./runner";
|
|
|
9
9
|
import type { RegisteredTool, ToolCallEventResult, ToolResultEventResult } from "./types";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* Adapts a RegisteredTool into an AgentTool.
|
|
13
|
+
*/
|
|
14
|
+
export class RegisteredToolAdapter implements AgentTool<any, any, any> {
|
|
15
|
+
readonly name: string;
|
|
16
|
+
readonly label: string;
|
|
17
|
+
readonly description: string;
|
|
18
|
+
readonly parameters: any;
|
|
19
|
+
|
|
20
|
+
constructor(
|
|
21
|
+
private registeredTool: RegisteredTool,
|
|
22
|
+
private runner: ExtensionRunner,
|
|
23
|
+
) {
|
|
24
|
+
const { definition } = registeredTool;
|
|
25
|
+
this.name = definition.name;
|
|
26
|
+
this.label = definition.label || "";
|
|
27
|
+
this.description = definition.description;
|
|
28
|
+
this.parameters = definition.parameters;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async execute(
|
|
32
|
+
toolCallId: string,
|
|
33
|
+
params: any,
|
|
34
|
+
signal?: AbortSignal,
|
|
35
|
+
onUpdate?: AgentToolUpdateCallback<any>,
|
|
36
|
+
_context?: AgentToolContext,
|
|
37
|
+
) {
|
|
38
|
+
return this.registeredTool.definition.execute(toolCallId, params, onUpdate, this.runner.createContext(), signal);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
renderCall?(args: any, theme: any) {
|
|
42
|
+
return this.registeredTool.definition.renderCall?.(args, theme as Theme);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
renderResult?(result: any, options: any, theme: any) {
|
|
46
|
+
return this.registeredTool.definition.renderResult?.(
|
|
47
|
+
result,
|
|
48
|
+
{ expanded: options.expanded, isPartial: options.isPartial, spinnerFrame: options.spinnerFrame },
|
|
49
|
+
theme as Theme,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Backward-compatible factory function wrapper.
|
|
13
56
|
*/
|
|
14
57
|
export function wrapRegisteredTool(registeredTool: RegisteredTool, runner: ExtensionRunner): AgentTool {
|
|
15
|
-
|
|
16
|
-
return {
|
|
17
|
-
name: definition.name,
|
|
18
|
-
label: definition.label,
|
|
19
|
-
description: definition.description,
|
|
20
|
-
parameters: definition.parameters,
|
|
21
|
-
execute: (toolCallId, params, signal, onUpdate) =>
|
|
22
|
-
definition.execute(toolCallId, params, onUpdate, runner.createContext(), signal),
|
|
23
|
-
renderCall: definition.renderCall ? (args, theme) => definition.renderCall?.(args, theme as Theme) : undefined,
|
|
24
|
-
renderResult: definition.renderResult
|
|
25
|
-
? (result, options, theme) =>
|
|
26
|
-
definition.renderResult?.(
|
|
27
|
-
result,
|
|
28
|
-
{ expanded: options.expanded, isPartial: options.isPartial, spinnerFrame: options.spinnerFrame },
|
|
29
|
-
theme as Theme,
|
|
30
|
-
)
|
|
31
|
-
: undefined,
|
|
32
|
-
};
|
|
58
|
+
return new RegisteredToolAdapter(registeredTool, runner);
|
|
33
59
|
}
|
|
34
60
|
|
|
35
61
|
/**
|
|
@@ -40,98 +66,121 @@ export function wrapRegisteredTools(registeredTools: RegisteredTool[], runner: E
|
|
|
40
66
|
}
|
|
41
67
|
|
|
42
68
|
/**
|
|
43
|
-
*
|
|
69
|
+
* Wraps a tool with extension callbacks for interception.
|
|
44
70
|
* - Emits tool_call event before execution (can block)
|
|
45
71
|
* - Emits tool_result event after execution (can modify result)
|
|
46
72
|
*/
|
|
47
|
-
export
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
onUpdate?: AgentToolUpdateCallback<T>,
|
|
55
|
-
context?: AgentToolContext,
|
|
56
|
-
) => {
|
|
57
|
-
// Emit tool_call event - extensions can block execution
|
|
58
|
-
if (runner.hasHandlers("tool_call")) {
|
|
59
|
-
try {
|
|
60
|
-
const callResult = (await runner.emitToolCall({
|
|
61
|
-
type: "tool_call",
|
|
62
|
-
toolName: tool.name,
|
|
63
|
-
toolCallId,
|
|
64
|
-
input: params,
|
|
65
|
-
})) as ToolCallEventResult | undefined;
|
|
66
|
-
|
|
67
|
-
if (callResult?.block) {
|
|
68
|
-
const reason = callResult.reason || "Tool execution was blocked by an extension";
|
|
69
|
-
throw new Error(reason);
|
|
70
|
-
}
|
|
71
|
-
} catch (err) {
|
|
72
|
-
if (err instanceof Error) {
|
|
73
|
-
throw err;
|
|
74
|
-
}
|
|
75
|
-
throw new Error(`Extension failed, blocking execution: ${String(err)}`);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
73
|
+
export class ExtensionToolWrapper<T> implements AgentTool<any, T> {
|
|
74
|
+
name: string;
|
|
75
|
+
label: string;
|
|
76
|
+
description: string;
|
|
77
|
+
parameters: unknown;
|
|
78
|
+
renderCall?: AgentTool["renderCall"];
|
|
79
|
+
renderResult?: AgentTool["renderResult"];
|
|
78
80
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
constructor(
|
|
82
|
+
private tool: AgentTool<any, T>,
|
|
83
|
+
private runner: ExtensionRunner,
|
|
84
|
+
) {
|
|
85
|
+
this.name = tool.name;
|
|
86
|
+
this.label = tool.label ?? "";
|
|
87
|
+
this.description = tool.description;
|
|
88
|
+
this.parameters = tool.parameters;
|
|
89
|
+
this.renderCall = tool.renderCall;
|
|
90
|
+
this.renderResult = tool.renderResult;
|
|
91
|
+
}
|
|
82
92
|
|
|
93
|
+
async execute(
|
|
94
|
+
toolCallId: string,
|
|
95
|
+
params: Record<string, unknown>,
|
|
96
|
+
signal?: AbortSignal,
|
|
97
|
+
onUpdate?: AgentToolUpdateCallback<T>,
|
|
98
|
+
context?: AgentToolContext,
|
|
99
|
+
) {
|
|
100
|
+
// Emit tool_call event - extensions can block execution
|
|
101
|
+
if (this.runner.hasHandlers("tool_call")) {
|
|
83
102
|
try {
|
|
84
|
-
|
|
103
|
+
const callResult = (await this.runner.emitToolCall({
|
|
104
|
+
type: "tool_call",
|
|
105
|
+
toolName: this.tool.name,
|
|
106
|
+
toolCallId,
|
|
107
|
+
input: params,
|
|
108
|
+
})) as ToolCallEventResult | undefined;
|
|
109
|
+
|
|
110
|
+
if (callResult?.block) {
|
|
111
|
+
const reason = callResult.reason || "Tool execution was blocked by an extension";
|
|
112
|
+
throw new Error(reason);
|
|
113
|
+
}
|
|
85
114
|
} catch (err) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
};
|
|
115
|
+
if (err instanceof Error) {
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
118
|
+
throw new Error(`Extension failed, blocking execution: ${String(err)}`);
|
|
91
119
|
}
|
|
120
|
+
}
|
|
92
121
|
|
|
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
|
-
|
|
122
|
+
// Execute the actual tool
|
|
123
|
+
let result: { content: any; details?: T };
|
|
124
|
+
let executionError: Error | undefined;
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
result = await this.tool.execute(toolCallId, params, signal, onUpdate, context);
|
|
128
|
+
} catch (err) {
|
|
129
|
+
executionError = err instanceof Error ? err : new Error(String(err));
|
|
130
|
+
result = {
|
|
131
|
+
content: [{ type: "text", text: executionError.message }],
|
|
132
|
+
details: undefined as T,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Emit tool_result event - extensions can modify the result and error status
|
|
137
|
+
if (this.runner.hasHandlers("tool_result")) {
|
|
138
|
+
const resultResult = (await this.runner.emit({
|
|
139
|
+
type: "tool_result",
|
|
140
|
+
toolName: this.tool.name,
|
|
141
|
+
toolCallId,
|
|
142
|
+
input: params,
|
|
143
|
+
content: result.content,
|
|
144
|
+
details: result.details,
|
|
145
|
+
isError: !!executionError,
|
|
146
|
+
})) as ToolResultEventResult | undefined;
|
|
147
|
+
|
|
148
|
+
if (resultResult) {
|
|
149
|
+
const modifiedContent: (TextContent | ImageContent)[] = resultResult.content ?? result.content;
|
|
150
|
+
const modifiedDetails = (resultResult.details ?? result.details) as T;
|
|
151
|
+
|
|
152
|
+
// Extension can override error status
|
|
153
|
+
if (resultResult.isError === true && !executionError) {
|
|
154
|
+
// Extension marks a successful result as error
|
|
155
|
+
const textBlocks = (modifiedContent ?? []).filter((c): c is TextContent => c.type === "text");
|
|
156
|
+
const errorText = textBlocks.map((t) => t.text).join("\n") || "Tool result marked as error by extension";
|
|
157
|
+
throw new Error(errorText);
|
|
158
|
+
}
|
|
159
|
+
if (resultResult.isError === false && executionError) {
|
|
160
|
+
// Extension clears the error - return success
|
|
126
161
|
return { content: modifiedContent, details: modifiedDetails };
|
|
127
162
|
}
|
|
128
|
-
}
|
|
129
163
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
164
|
+
// Error status unchanged, but content/details may be modified
|
|
165
|
+
if (executionError) {
|
|
166
|
+
throw executionError;
|
|
167
|
+
}
|
|
168
|
+
return { content: modifiedContent, details: modifiedDetails };
|
|
133
169
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// No extension modification
|
|
173
|
+
if (executionError) {
|
|
174
|
+
throw executionError;
|
|
175
|
+
}
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Wrap a tool with extension callbacks for interception.
|
|
182
|
+
* @deprecated Use `new ExtensionToolWrapper()` directly
|
|
183
|
+
*/
|
|
184
|
+
export function wrapToolWithExtensions<T>(tool: AgentTool<any, T>, runner: ExtensionRunner): AgentTool<any, T> {
|
|
185
|
+
return new ExtensionToolWrapper(tool, runner);
|
|
137
186
|
}
|