@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/python.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
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 { ImageContent } from "@oh-my-pi/pi-ai";
|
|
4
4
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
5
5
|
import { Text, truncateToWidth } from "@oh-my-pi/pi-tui";
|
|
6
|
-
import { Type } from "@sinclair/typebox";
|
|
6
|
+
import { type Static, Type } from "@sinclair/typebox";
|
|
7
7
|
import { truncateToVisualLines } from "../../modes/interactive/components/visual-truncate";
|
|
8
8
|
import type { Theme } from "../../modes/interactive/theme/theme";
|
|
9
9
|
import pythonDescription from "../../prompts/tools/python.md" with { type: "text" };
|
|
@@ -116,160 +116,163 @@ export function getPythonToolDescription(): string {
|
|
|
116
116
|
return renderPromptTemplate(pythonDescription, { categories });
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
interface
|
|
119
|
+
export interface PythonToolOptions {
|
|
120
120
|
proxyExecutor?: PythonProxyExecutor;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
export
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
return {
|
|
130
|
-
name: "python",
|
|
131
|
-
label: "Python",
|
|
132
|
-
description: getPythonToolDescription(),
|
|
133
|
-
parameters: pythonSchema,
|
|
134
|
-
execute: async (
|
|
135
|
-
_toolCallId: string,
|
|
136
|
-
params: PythonToolParams,
|
|
137
|
-
signal?: AbortSignal,
|
|
138
|
-
onUpdate?,
|
|
139
|
-
_ctx?: AgentToolContext,
|
|
140
|
-
) => {
|
|
141
|
-
if (proxyExecutor) {
|
|
142
|
-
return proxyExecutor(params, signal);
|
|
143
|
-
}
|
|
123
|
+
export class PythonTool implements AgentTool<typeof pythonSchema> {
|
|
124
|
+
public readonly name = "python";
|
|
125
|
+
public readonly label = "Python";
|
|
126
|
+
public readonly description: string;
|
|
127
|
+
public readonly parameters = pythonSchema;
|
|
144
128
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
129
|
+
private readonly session: ToolSession | null;
|
|
130
|
+
private readonly proxyExecutor?: PythonProxyExecutor;
|
|
148
131
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
132
|
+
constructor(session: ToolSession | null, options?: PythonToolOptions) {
|
|
133
|
+
this.session = session;
|
|
134
|
+
this.proxyExecutor = options?.proxyExecutor;
|
|
135
|
+
this.description = getPythonToolDescription();
|
|
136
|
+
}
|
|
153
137
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
138
|
+
public async execute(
|
|
139
|
+
_toolCallId: string,
|
|
140
|
+
params: Static<typeof pythonSchema>,
|
|
141
|
+
signal?: AbortSignal,
|
|
142
|
+
onUpdate?: AgentToolUpdateCallback,
|
|
143
|
+
_ctx?: AgentToolContext,
|
|
144
|
+
): Promise<AgentToolResult<PythonToolDetails | undefined>> {
|
|
145
|
+
if (this.proxyExecutor) {
|
|
146
|
+
return this.proxyExecutor(params, signal);
|
|
147
|
+
}
|
|
158
148
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
cwdStat = await Bun.file(commandCwd).stat();
|
|
163
|
-
} catch {
|
|
164
|
-
throw new Error(`Working directory does not exist: ${commandCwd}`);
|
|
165
|
-
}
|
|
166
|
-
if (!cwdStat.isDirectory()) {
|
|
167
|
-
throw new Error(`Working directory is not a directory: ${commandCwd}`);
|
|
168
|
-
}
|
|
149
|
+
if (!this.session) {
|
|
150
|
+
throw new Error("Python tool requires a session when not using proxy executor");
|
|
151
|
+
}
|
|
169
152
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const images: ImageContent[] = [];
|
|
175
|
-
|
|
176
|
-
const sessionFile = session.getSessionFile?.() ?? undefined;
|
|
177
|
-
const sessionId = sessionFile ? `session:${sessionFile}:workdir:${commandCwd}` : `cwd:${commandCwd}`;
|
|
178
|
-
const executorOptions: PythonExecutorOptions = {
|
|
179
|
-
cwd: commandCwd,
|
|
180
|
-
timeout: timeout ? timeout * 1000 : undefined,
|
|
181
|
-
signal: controller.signal,
|
|
182
|
-
sessionId,
|
|
183
|
-
kernelMode: session.settings?.getPythonKernelMode?.() ?? "session",
|
|
184
|
-
useSharedGateway: session.settings?.getPythonSharedGateway?.() ?? true,
|
|
185
|
-
reset,
|
|
186
|
-
onChunk: (chunk) => {
|
|
187
|
-
const chunkBytes = Buffer.byteLength(chunk, "utf-8");
|
|
188
|
-
tailChunks.push({ text: chunk, bytes: chunkBytes });
|
|
189
|
-
tailBytes += chunkBytes;
|
|
190
|
-
while (tailBytes > maxTailBytes && tailChunks.length > 1) {
|
|
191
|
-
const removed = tailChunks.shift();
|
|
192
|
-
if (removed) {
|
|
193
|
-
tailBytes -= removed.bytes;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
if (onUpdate) {
|
|
197
|
-
const tailText = tailChunks.map((entry) => entry.text).join("");
|
|
198
|
-
const truncation = truncateTail(tailText);
|
|
199
|
-
onUpdate({
|
|
200
|
-
content: [{ type: "text", text: truncation.content || "" }],
|
|
201
|
-
details: truncation.truncated ? { truncation } : undefined,
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
},
|
|
205
|
-
};
|
|
153
|
+
const { code, timeout, workdir, reset } = params;
|
|
154
|
+
const controller = new AbortController();
|
|
155
|
+
const onAbort = () => controller.abort();
|
|
156
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
206
157
|
|
|
207
|
-
|
|
158
|
+
try {
|
|
159
|
+
if (signal?.aborted) {
|
|
160
|
+
throw new Error("Aborted");
|
|
161
|
+
}
|
|
208
162
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
163
|
+
const commandCwd = workdir ? resolveToCwd(workdir, this.session.cwd) : this.session.cwd;
|
|
164
|
+
let cwdStat: Awaited<ReturnType<Bun.BunFile["stat"]>>;
|
|
165
|
+
try {
|
|
166
|
+
cwdStat = await Bun.file(commandCwd).stat();
|
|
167
|
+
} catch {
|
|
168
|
+
throw new Error(`Working directory does not exist: ${commandCwd}`);
|
|
169
|
+
}
|
|
170
|
+
if (!cwdStat.isDirectory()) {
|
|
171
|
+
throw new Error(`Working directory is not a directory: ${commandCwd}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const maxTailBytes = DEFAULT_MAX_BYTES * 2;
|
|
175
|
+
const tailChunks: Array<{ text: string; bytes: number }> = [];
|
|
176
|
+
let tailBytes = 0;
|
|
177
|
+
const jsonOutputs: unknown[] = [];
|
|
178
|
+
const images: ImageContent[] = [];
|
|
179
|
+
|
|
180
|
+
const sessionFile = this.session.getSessionFile?.() ?? undefined;
|
|
181
|
+
const sessionId = sessionFile ? `session:${sessionFile}:workdir:${commandCwd}` : `cwd:${commandCwd}`;
|
|
182
|
+
const executorOptions: PythonExecutorOptions = {
|
|
183
|
+
cwd: commandCwd,
|
|
184
|
+
timeout: timeout ? timeout * 1000 : undefined,
|
|
185
|
+
signal: controller.signal,
|
|
186
|
+
sessionId,
|
|
187
|
+
kernelMode: this.session.settings?.getPythonKernelMode?.() ?? "session",
|
|
188
|
+
useSharedGateway: this.session.settings?.getPythonSharedGateway?.() ?? true,
|
|
189
|
+
reset,
|
|
190
|
+
onChunk: (chunk) => {
|
|
191
|
+
const chunkBytes = Buffer.byteLength(chunk, "utf-8");
|
|
192
|
+
tailChunks.push({ text: chunk, bytes: chunkBytes });
|
|
193
|
+
tailBytes += chunkBytes;
|
|
194
|
+
while (tailBytes > maxTailBytes && tailChunks.length > 1) {
|
|
195
|
+
const removed = tailChunks.shift();
|
|
196
|
+
if (removed) {
|
|
197
|
+
tailBytes -= removed.bytes;
|
|
198
|
+
}
|
|
216
199
|
}
|
|
217
|
-
if (
|
|
218
|
-
|
|
200
|
+
if (onUpdate) {
|
|
201
|
+
const tailText = tailChunks.map((entry) => entry.text).join("");
|
|
202
|
+
const truncation = truncateTail(tailText);
|
|
203
|
+
onUpdate({
|
|
204
|
+
content: [{ type: "text", text: truncation.content || "" }],
|
|
205
|
+
details: truncation.truncated ? { truncation } : undefined,
|
|
206
|
+
});
|
|
219
207
|
}
|
|
220
|
-
}
|
|
208
|
+
},
|
|
209
|
+
};
|
|
221
210
|
|
|
222
|
-
|
|
223
|
-
throw new Error(result.output || "Command aborted");
|
|
224
|
-
}
|
|
211
|
+
const result = await executePython(code, executorOptions);
|
|
225
212
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if (truncation.truncated) {
|
|
232
|
-
const fullOutputSuffix = result.fullOutputPath ? ` Full output: ${result.fullOutputPath}` : "";
|
|
233
|
-
details = {
|
|
234
|
-
truncation,
|
|
235
|
-
fullOutputPath: result.fullOutputPath,
|
|
236
|
-
jsonOutputs: jsonOutputs,
|
|
237
|
-
images,
|
|
238
|
-
statusEvents: statusEvents.length > 0 ? statusEvents : undefined,
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
const startLine = truncation.totalLines - truncation.outputLines + 1;
|
|
242
|
-
const endLine = truncation.totalLines;
|
|
243
|
-
|
|
244
|
-
if (truncation.lastLinePartial) {
|
|
245
|
-
const lastLineSize = formatSize(Buffer.byteLength(result.output.split("\n").pop() || "", "utf-8"));
|
|
246
|
-
outputText += `\n\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize})${fullOutputSuffix}]`;
|
|
247
|
-
} else if (truncation.truncatedBy === "lines") {
|
|
248
|
-
outputText += `\n\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}${fullOutputSuffix}]`;
|
|
249
|
-
} else {
|
|
250
|
-
outputText += `\n\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit)${fullOutputSuffix}]`;
|
|
251
|
-
}
|
|
213
|
+
const statusEvents: PythonStatusEvent[] = [];
|
|
214
|
+
for (const output of result.displayOutputs) {
|
|
215
|
+
if (output.type === "json") {
|
|
216
|
+
jsonOutputs.push(output.data);
|
|
252
217
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
details = {
|
|
256
|
-
jsonOutputs: jsonOutputs.length > 0 ? jsonOutputs : undefined,
|
|
257
|
-
images: images.length > 0 ? images : undefined,
|
|
258
|
-
statusEvents: statusEvents.length > 0 ? statusEvents : undefined,
|
|
259
|
-
};
|
|
218
|
+
if (output.type === "image") {
|
|
219
|
+
images.push({ type: "image", data: output.data, mimeType: output.mimeType });
|
|
260
220
|
}
|
|
221
|
+
if (output.type === "status") {
|
|
222
|
+
statusEvents.push(output.event);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (result.cancelled) {
|
|
227
|
+
throw new Error(result.output || "Command aborted");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const truncation = truncateTail(result.output);
|
|
231
|
+
let outputText =
|
|
232
|
+
truncation.content || (jsonOutputs.length > 0 || images.length > 0 ? "(no text output)" : "(no output)");
|
|
233
|
+
let details: PythonToolDetails | undefined;
|
|
234
|
+
|
|
235
|
+
if (truncation.truncated) {
|
|
236
|
+
const fullOutputSuffix = result.fullOutputPath ? ` Full output: ${result.fullOutputPath}` : "";
|
|
237
|
+
details = {
|
|
238
|
+
truncation,
|
|
239
|
+
fullOutputPath: result.fullOutputPath,
|
|
240
|
+
jsonOutputs: jsonOutputs,
|
|
241
|
+
images,
|
|
242
|
+
statusEvents: statusEvents.length > 0 ? statusEvents : undefined,
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const startLine = truncation.totalLines - truncation.outputLines + 1;
|
|
246
|
+
const endLine = truncation.totalLines;
|
|
261
247
|
|
|
262
|
-
if (
|
|
263
|
-
|
|
264
|
-
|
|
248
|
+
if (truncation.lastLinePartial) {
|
|
249
|
+
const lastLineSize = formatSize(Buffer.byteLength(result.output.split("\n").pop() || "", "utf-8"));
|
|
250
|
+
outputText += `\n\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize})${fullOutputSuffix}]`;
|
|
251
|
+
} else if (truncation.truncatedBy === "lines") {
|
|
252
|
+
outputText += `\n\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}${fullOutputSuffix}]`;
|
|
253
|
+
} else {
|
|
254
|
+
outputText += `\n\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit)${fullOutputSuffix}]`;
|
|
265
255
|
}
|
|
256
|
+
}
|
|
266
257
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
258
|
+
if (!details && (jsonOutputs.length > 0 || images.length > 0 || statusEvents.length > 0)) {
|
|
259
|
+
details = {
|
|
260
|
+
jsonOutputs: jsonOutputs.length > 0 ? jsonOutputs : undefined,
|
|
261
|
+
images: images.length > 0 ? images : undefined,
|
|
262
|
+
statusEvents: statusEvents.length > 0 ? statusEvents : undefined,
|
|
263
|
+
};
|
|
270
264
|
}
|
|
271
|
-
|
|
272
|
-
|
|
265
|
+
|
|
266
|
+
if (result.exitCode !== 0 && result.exitCode !== undefined) {
|
|
267
|
+
outputText += `\n\nCommand exited with code ${result.exitCode}`;
|
|
268
|
+
throw new Error(outputText);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return { content: [{ type: "text", text: outputText }], details };
|
|
272
|
+
} finally {
|
|
273
|
+
signal?.removeEventListener("abort", onAbort);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
273
276
|
}
|
|
274
277
|
|
|
275
278
|
interface PythonRenderArgs {
|