@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
package/src/core/tools/ask.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* and add "(Recommended)" at the end of the label
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import type { AgentTool, AgentToolContext, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
18
|
+
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
19
19
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
20
20
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
21
21
|
import { Type } from "@sinclair/typebox";
|
|
@@ -188,7 +188,7 @@ function formatQuestionResult(result: QuestionResult): string {
|
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
// =============================================================================
|
|
191
|
-
// Tool
|
|
191
|
+
// Tool Class
|
|
192
192
|
// =============================================================================
|
|
193
193
|
|
|
194
194
|
interface AskParams {
|
|
@@ -203,110 +203,109 @@ interface AskParams {
|
|
|
203
203
|
}>;
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
206
|
+
/**
|
|
207
|
+
* Ask tool for interactive user prompting during execution.
|
|
208
|
+
*
|
|
209
|
+
* Allows gathering user preferences, clarifying instructions, and getting decisions
|
|
210
|
+
* on implementation choices as the agent works.
|
|
211
|
+
*/
|
|
212
|
+
export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
|
|
213
|
+
public readonly name = "ask";
|
|
214
|
+
public readonly label = "Ask";
|
|
215
|
+
public readonly description: string;
|
|
216
|
+
public readonly parameters = askSchema;
|
|
217
|
+
|
|
218
|
+
constructor(_session: ToolSession) {
|
|
219
|
+
this.description = renderPromptTemplate(askDescription);
|
|
209
220
|
}
|
|
210
|
-
return {
|
|
211
|
-
name: "ask",
|
|
212
|
-
label: "Ask",
|
|
213
|
-
description: renderPromptTemplate(askDescription),
|
|
214
|
-
parameters: askSchema,
|
|
215
|
-
|
|
216
|
-
async execute(
|
|
217
|
-
_toolCallId: string,
|
|
218
|
-
params: AskParams,
|
|
219
|
-
_signal?: AbortSignal,
|
|
220
|
-
_onUpdate?: AgentToolUpdateCallback<AskToolDetails>,
|
|
221
|
-
context?: AgentToolContext,
|
|
222
|
-
) {
|
|
223
|
-
// Headless fallback
|
|
224
|
-
if (!context?.hasUI || !context.ui) {
|
|
225
|
-
return {
|
|
226
|
-
content: [{ type: "text" as const, text: "Error: User prompt requires interactive mode" }],
|
|
227
|
-
details: {},
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const { ui } = context;
|
|
232
|
-
|
|
233
|
-
// Multi-part questions mode
|
|
234
|
-
if (params.questions && params.questions.length > 0) {
|
|
235
|
-
const results: QuestionResult[] = [];
|
|
236
221
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
ui,
|
|
241
|
-
q.question,
|
|
242
|
-
optionLabels,
|
|
243
|
-
q.multi ?? false,
|
|
244
|
-
);
|
|
245
|
-
|
|
246
|
-
results.push({
|
|
247
|
-
id: q.id,
|
|
248
|
-
question: q.question,
|
|
249
|
-
options: optionLabels,
|
|
250
|
-
multi: q.multi ?? false,
|
|
251
|
-
selectedOptions,
|
|
252
|
-
customInput,
|
|
253
|
-
});
|
|
254
|
-
}
|
|
222
|
+
static createIf(session: ToolSession): AskTool | null {
|
|
223
|
+
return session.hasUI ? new AskTool(session) : null;
|
|
224
|
+
}
|
|
255
225
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
226
|
+
public async execute(
|
|
227
|
+
_toolCallId: string,
|
|
228
|
+
params: AskParams,
|
|
229
|
+
_signal?: AbortSignal,
|
|
230
|
+
_onUpdate?: AgentToolUpdateCallback<AskToolDetails>,
|
|
231
|
+
context?: AgentToolContext,
|
|
232
|
+
): Promise<AgentToolResult<AskToolDetails>> {
|
|
233
|
+
// Headless fallback
|
|
234
|
+
if (!context?.hasUI || !context.ui) {
|
|
235
|
+
return {
|
|
236
|
+
content: [{ type: "text" as const, text: "Error: User prompt requires interactive mode" }],
|
|
237
|
+
details: {},
|
|
238
|
+
};
|
|
239
|
+
}
|
|
259
240
|
|
|
260
|
-
|
|
241
|
+
const { ui } = context;
|
|
242
|
+
|
|
243
|
+
// Multi-part questions mode
|
|
244
|
+
if (params.questions && params.questions.length > 0) {
|
|
245
|
+
const results: QuestionResult[] = [];
|
|
246
|
+
|
|
247
|
+
for (const q of params.questions) {
|
|
248
|
+
const optionLabels = q.options.map((o) => o.label);
|
|
249
|
+
const { selectedOptions, customInput } = await askSingleQuestion(
|
|
250
|
+
ui,
|
|
251
|
+
q.question,
|
|
252
|
+
optionLabels,
|
|
253
|
+
q.multi ?? false,
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
results.push({
|
|
257
|
+
id: q.id,
|
|
258
|
+
question: q.question,
|
|
259
|
+
options: optionLabels,
|
|
260
|
+
multi: q.multi ?? false,
|
|
261
|
+
selectedOptions,
|
|
262
|
+
customInput,
|
|
263
|
+
});
|
|
261
264
|
}
|
|
262
265
|
|
|
263
|
-
|
|
264
|
-
const
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
if (!question || optionLabels.length === 0) {
|
|
270
|
-
return {
|
|
271
|
-
content: [{ type: "text" as const, text: "Error: question and options are required" }],
|
|
272
|
-
details: {},
|
|
273
|
-
};
|
|
274
|
-
}
|
|
266
|
+
const details: AskToolDetails = { results };
|
|
267
|
+
const responseLines = results.map(formatQuestionResult);
|
|
268
|
+
const responseText = `User answers:\n${responseLines.join("\n")}`;
|
|
269
|
+
|
|
270
|
+
return { content: [{ type: "text" as const, text: responseText }], details };
|
|
271
|
+
}
|
|
275
272
|
|
|
276
|
-
|
|
273
|
+
// Single question mode (backwards compatible)
|
|
274
|
+
const question = params.question ?? "";
|
|
275
|
+
const options = params.options ?? [];
|
|
276
|
+
const multi = params.multi ?? false;
|
|
277
|
+
const optionLabels = options.map((o) => o.label);
|
|
277
278
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
selectedOptions,
|
|
283
|
-
customInput,
|
|
279
|
+
if (!question || optionLabels.length === 0) {
|
|
280
|
+
return {
|
|
281
|
+
content: [{ type: "text" as const, text: "Error: question and options are required" }],
|
|
282
|
+
details: {},
|
|
284
283
|
};
|
|
284
|
+
}
|
|
285
285
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
286
|
+
const { selectedOptions, customInput } = await askSingleQuestion(ui, question, optionLabels, multi);
|
|
287
|
+
|
|
288
|
+
const details: AskToolDetails = {
|
|
289
|
+
question,
|
|
290
|
+
options: optionLabels,
|
|
291
|
+
multi,
|
|
292
|
+
selectedOptions,
|
|
293
|
+
customInput,
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
let responseText: string;
|
|
297
|
+
if (customInput) {
|
|
298
|
+
responseText = `User provided custom input: ${customInput}`;
|
|
299
|
+
} else if (selectedOptions.length > 0) {
|
|
300
|
+
responseText = multi ? `User selected: ${selectedOptions.join(", ")}` : `User selected: ${selectedOptions[0]}`;
|
|
301
|
+
} else {
|
|
302
|
+
responseText = "User cancelled the selection";
|
|
303
|
+
}
|
|
296
304
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
};
|
|
305
|
+
return { content: [{ type: "text" as const, text: responseText }], details };
|
|
306
|
+
}
|
|
300
307
|
}
|
|
301
308
|
|
|
302
|
-
/** Default ask tool - returns null when no UI */
|
|
303
|
-
export const askTool = createAskTool({
|
|
304
|
-
cwd: process.cwd(),
|
|
305
|
-
hasUI: false,
|
|
306
|
-
getSessionFile: () => null,
|
|
307
|
-
getSessionSpawns: () => "*",
|
|
308
|
-
});
|
|
309
|
-
|
|
310
309
|
// =============================================================================
|
|
311
310
|
// TUI Renderer
|
|
312
311
|
// =============================================================================
|
package/src/core/tools/bash.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { relative, resolve, sep } from "node:path";
|
|
2
|
-
import type { AgentTool, AgentToolContext } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
3
3
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
4
4
|
import { Text, truncateToWidth } from "@oh-my-pi/pi-tui";
|
|
5
5
|
import { Type } from "@sinclair/typebox";
|
|
@@ -52,113 +52,122 @@ export interface BashToolOptions {
|
|
|
52
52
|
operations?: BashOperations;
|
|
53
53
|
}
|
|
54
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
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Bash tool implementation.
|
|
57
|
+
*
|
|
58
|
+
* Executes bash commands with optional timeout and working directory.
|
|
59
|
+
* Supports custom operations for remote execution.
|
|
60
|
+
*/
|
|
61
|
+
export class BashTool implements AgentTool<typeof bashSchema, BashToolDetails> {
|
|
62
|
+
public readonly name = "bash";
|
|
63
|
+
public readonly label = "Bash";
|
|
64
|
+
public readonly description: string;
|
|
65
|
+
public readonly parameters = bashSchema;
|
|
66
|
+
|
|
67
|
+
private readonly session: ToolSession;
|
|
68
|
+
private readonly options?: BashToolOptions;
|
|
69
|
+
|
|
70
|
+
constructor(session: ToolSession, options?: BashToolOptions) {
|
|
71
|
+
this.session = session;
|
|
72
|
+
this.options = options;
|
|
73
|
+
this.description = renderPromptTemplate(bashDescription);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public async execute(
|
|
77
|
+
_toolCallId: string,
|
|
78
|
+
{ command, timeout, workdir }: { command: string; timeout?: number; workdir?: string },
|
|
79
|
+
signal?: AbortSignal,
|
|
80
|
+
onUpdate?: AgentToolUpdateCallback<BashToolDetails>,
|
|
81
|
+
ctx?: AgentToolContext,
|
|
82
|
+
): Promise<AgentToolResult<BashToolDetails>> {
|
|
83
|
+
// Check interception if enabled and available tools are known
|
|
84
|
+
if (this.session.settings?.getBashInterceptorEnabled()) {
|
|
85
|
+
const rules = this.session.settings?.getBashInterceptorRules?.();
|
|
86
|
+
const interception = checkBashInterception(command, ctx?.toolNames ?? [], rules);
|
|
87
|
+
if (interception.block) {
|
|
88
|
+
throw new Error(interception.message);
|
|
89
|
+
}
|
|
90
|
+
if (this.session.settings?.getBashInterceptorSimpleLsEnabled?.() !== false) {
|
|
91
|
+
const lsInterception = checkSimpleLsInterception(command, ctx?.toolNames ?? []);
|
|
92
|
+
if (lsInterception.block) {
|
|
93
|
+
throw new Error(lsInterception.message);
|
|
80
94
|
}
|
|
81
95
|
}
|
|
96
|
+
}
|
|
82
97
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
98
|
+
const commandCwd = workdir ? resolveToCwd(workdir, this.session.cwd) : this.session.cwd;
|
|
99
|
+
let cwdStat: Awaited<ReturnType<Bun.BunFile["stat"]>>;
|
|
100
|
+
try {
|
|
101
|
+
cwdStat = await Bun.file(commandCwd).stat();
|
|
102
|
+
} catch {
|
|
103
|
+
throw new Error(`Working directory does not exist: ${commandCwd}`);
|
|
104
|
+
}
|
|
105
|
+
if (!cwdStat.isDirectory()) {
|
|
106
|
+
throw new Error(`Working directory is not a directory: ${commandCwd}`);
|
|
107
|
+
}
|
|
93
108
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
: undefined,
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
},
|
|
116
|
-
};
|
|
109
|
+
// Track output for streaming updates
|
|
110
|
+
let currentOutput = "";
|
|
111
|
+
|
|
112
|
+
const executorOptions: BashExecutorOptions = {
|
|
113
|
+
cwd: commandCwd,
|
|
114
|
+
timeout: timeout ? timeout * 1000 : undefined, // Convert to milliseconds
|
|
115
|
+
signal,
|
|
116
|
+
onChunk: (chunk) => {
|
|
117
|
+
currentOutput += chunk;
|
|
118
|
+
if (onUpdate) {
|
|
119
|
+
const truncation = truncateTail(currentOutput);
|
|
120
|
+
onUpdate({
|
|
121
|
+
content: [{ type: "text", text: truncation.content || "" }],
|
|
122
|
+
details: truncation.truncated ? { truncation, fullOutput: currentOutput } : {},
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
};
|
|
117
127
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
128
|
+
// Use custom operations if provided, otherwise use default local executor
|
|
129
|
+
const result = this.options?.operations
|
|
130
|
+
? await executeBashWithOperations(command, commandCwd, this.options.operations, executorOptions)
|
|
131
|
+
: await executeBash(command, executorOptions);
|
|
122
132
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
133
|
+
// Handle errors
|
|
134
|
+
if (result.cancelled) {
|
|
135
|
+
throw new Error(result.output || "Command aborted");
|
|
136
|
+
}
|
|
127
137
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
138
|
+
// Apply tail truncation for final output
|
|
139
|
+
const truncation = truncateTail(result.output);
|
|
140
|
+
let outputText = truncation.content || "(no output)";
|
|
131
141
|
|
|
132
|
-
|
|
142
|
+
let details: BashToolDetails | undefined;
|
|
133
143
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
144
|
+
if (truncation.truncated) {
|
|
145
|
+
details = {
|
|
146
|
+
truncation,
|
|
147
|
+
fullOutputPath: result.fullOutputPath,
|
|
148
|
+
fullOutput: currentOutput,
|
|
149
|
+
};
|
|
140
150
|
|
|
141
|
-
|
|
142
|
-
|
|
151
|
+
const startLine = truncation.totalLines - truncation.outputLines + 1;
|
|
152
|
+
const endLine = truncation.totalLines;
|
|
143
153
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
154
|
+
if (truncation.lastLinePartial) {
|
|
155
|
+
const lastLineSize = formatSize(Buffer.byteLength(result.output.split("\n").pop() || "", "utf-8"));
|
|
156
|
+
outputText += `\n\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${result.fullOutputPath}]`;
|
|
157
|
+
} else if (truncation.truncatedBy === "lines") {
|
|
158
|
+
outputText += `\n\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${result.fullOutputPath}]`;
|
|
159
|
+
} else {
|
|
160
|
+
outputText += `\n\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${result.fullOutputPath}]`;
|
|
152
161
|
}
|
|
162
|
+
}
|
|
153
163
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
164
|
+
if (result.exitCode !== 0 && result.exitCode !== undefined) {
|
|
165
|
+
outputText += `\n\nCommand exited with code ${result.exitCode}`;
|
|
166
|
+
throw new Error(outputText);
|
|
167
|
+
}
|
|
158
168
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
};
|
|
169
|
+
return { content: [{ type: "text", text: outputText }], details };
|
|
170
|
+
}
|
|
162
171
|
}
|
|
163
172
|
|
|
164
173
|
// =============================================================================
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
1
|
+
import type { AgentTool, AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
3
3
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
4
4
|
import { Type } from "@sinclair/typebox";
|
|
@@ -390,32 +390,47 @@ function formatResult(value: number): string {
|
|
|
390
390
|
return String(value);
|
|
391
391
|
}
|
|
392
392
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
393
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
394
|
+
// Tool Class
|
|
395
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
396
|
+
|
|
397
|
+
type CalculatorParams = { calculations: Array<{ expression: string; prefix: string; suffix: string }> };
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Calculator tool for evaluating mathematical expressions.
|
|
401
|
+
*
|
|
402
|
+
* Supports decimal, hex (0x), binary (0b), octal (0o) literals,
|
|
403
|
+
* standard arithmetic operators, and parentheses.
|
|
404
|
+
*/
|
|
405
|
+
export class CalculatorTool implements AgentTool<typeof calculatorSchema, CalculatorToolDetails> {
|
|
406
|
+
public readonly name = "calc";
|
|
407
|
+
public readonly label = "Calc";
|
|
408
|
+
public readonly description: string;
|
|
409
|
+
public readonly parameters = calculatorSchema;
|
|
410
|
+
|
|
411
|
+
constructor(_session: ToolSession) {
|
|
412
|
+
this.description = renderPromptTemplate(calculatorDescription);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
public async execute(
|
|
416
|
+
_toolCallId: string,
|
|
417
|
+
{ calculations }: CalculatorParams,
|
|
418
|
+
signal?: AbortSignal,
|
|
419
|
+
): Promise<AgentToolResult<CalculatorToolDetails>> {
|
|
420
|
+
return untilAborted(signal, async () => {
|
|
421
|
+
const results = calculations.map((calc) => {
|
|
422
|
+
const value = evaluateExpression(calc.expression);
|
|
423
|
+
const output = `${calc.prefix}${formatResult(value)}${calc.suffix}`;
|
|
424
|
+
return { expression: calc.expression, value, output };
|
|
416
425
|
});
|
|
417
|
-
|
|
418
|
-
|
|
426
|
+
|
|
427
|
+
const outputText = results.map((result) => result.output).join("\n");
|
|
428
|
+
return {
|
|
429
|
+
content: [{ type: "text", text: outputText }],
|
|
430
|
+
details: { results },
|
|
431
|
+
};
|
|
432
|
+
});
|
|
433
|
+
}
|
|
419
434
|
}
|
|
420
435
|
|
|
421
436
|
// =============================================================================
|