mini-coder 0.6.0 → 0.6.1

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/README.md CHANGED
@@ -36,6 +36,7 @@ $ mc
36
36
  - **Agent-first** — every decision serves the goal of reading code, making changes, and verifying them via the shell.
37
37
  - **Performance** — startup and turn latency matter more than features.
38
38
  - **Streaming end-to-end** — assistant text, reasoning, tool calls, and tool output show up as they happen.
39
+ - **Isolation friendly**— works inside [nono](https://nono.sh/). Suggested profile in `nono-mini-coder.json`
39
40
 
40
41
  ## Settings
41
42
 
package/bun.lock CHANGED
@@ -129,7 +129,7 @@
129
129
 
130
130
  "@kwsites/promise-deferred": ["@kwsites/promise-deferred@1.1.1", "", {}, "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw=="],
131
131
 
132
- "@mariozechner/pi-ai": ["@mariozechner/pi-ai@0.70.2", "", { "dependencies": { "@anthropic-ai/sdk": "^0.90.0", "@aws-sdk/client-bedrock-runtime": "^3.1030.0", "@google/genai": "^1.40.0", "@mistralai/mistralai": "^2.2.0", "chalk": "^5.6.2", "openai": "6.26.0", "partial-json": "^0.1.7", "proxy-agent": "^6.5.0", "typebox": "^1.1.24", "undici": "^7.19.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "pi-ai": "dist/cli.js" } }, "sha512-+30LRPjXsXF+oI96DvGWMbdPGeqoLJvadh6UPev7wx2DzhC9FEqXkQcoMZ0usbCm7E9pl8ua8a9s/pQ5ikaUbg=="],
132
+ "@mariozechner/pi-ai": ["@mariozechner/pi-ai@0.70.6", "", { "dependencies": { "@anthropic-ai/sdk": "^0.90.0", "@aws-sdk/client-bedrock-runtime": "^3.1030.0", "@google/genai": "^1.40.0", "@mistralai/mistralai": "^2.2.0", "chalk": "^5.6.2", "openai": "6.26.0", "partial-json": "^0.1.7", "proxy-agent": "^6.5.0", "typebox": "^1.1.24", "undici": "^7.19.1", "zod-to-json-schema": "^3.24.6" }, "bin": { "pi-ai": "dist/cli.js" } }, "sha512-LVAadu0Y+hb7Bj7EDiLsx6AuGxHlxDq0euLzyqX698i9qt0BW6a+oQSUIZQz4rJwExF18OvyL7ygJ5781ojrIQ=="],
133
133
 
134
134
  "@mistralai/mistralai": ["@mistralai/mistralai@2.2.0", "", { "dependencies": { "ws": "^8.18.0", "zod": "^3.25.0 || ^4.0.0", "zod-to-json-schema": "^3.25.0" } }, "sha512-JQUGIXjFWnw/J9LpTSf/ZXwVW3Sh8FBAcfTo5QvAHqkl4CfSiIwnjRJhMoAFcP6ncCe84YPU1ncDGX+p3OXnfg=="],
135
135
 
@@ -0,0 +1,42 @@
1
+ {
2
+ "meta": {
3
+ "name": "mini-coder",
4
+ "version": "1.0.0",
5
+ "description": "Profile for mini-coder / mc, a Bun-based coding agent"
6
+ },
7
+ "extends": "default",
8
+ "workdir": {
9
+ "access": "readwrite"
10
+ },
11
+ "security": {
12
+ "groups": [
13
+ "node_runtime",
14
+ "git_config",
15
+ "user_caches_linux",
16
+ "linux_sysfs_read",
17
+ "unlink_protection"
18
+ ]
19
+ },
20
+ "filesystem": {
21
+ "allow": [
22
+ "$HOME/.config/mini-coder",
23
+ "$HOME/.bun",
24
+ "$TMPDIR"
25
+ ],
26
+ "read": [
27
+ "$HOME/.agents/"
28
+ ],
29
+ "write": [],
30
+ "allow_file": [
31
+ "$HOME/AGENTS.md"
32
+ ],
33
+ "read_file": [],
34
+ "write_file": []
35
+ },
36
+ "rollback": {
37
+ "exclude_patterns": [
38
+ "node_modules"
39
+ ],
40
+ "exclude_globs": []
41
+ }
42
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mini-coder",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "type": "module",
5
5
  "packageManager": "bun@1.3.12",
6
6
  "bin": {
@@ -22,7 +22,7 @@
22
22
  "dependencies": {
23
23
  "@cel-tui/components": "^0.8.3",
24
24
  "@cel-tui/core": "^0.8.3",
25
- "@mariozechner/pi-ai": "^0.70.2",
25
+ "@mariozechner/pi-ai": "^0.70.6",
26
26
  "diff": "^9.0.0",
27
27
  "simple-git": "^3.36.0",
28
28
  "yaml": "^2.8.3"
package/src/agent.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  type AssistantMessage,
3
3
  type Context,
4
+ type ImageContent,
4
5
  type Message,
5
6
  streamSimple,
6
7
  type TextContent,
@@ -239,14 +240,19 @@ async function* toolRunner(
239
240
  },
240
241
  };
241
242
  } else if (e.type === "result") {
242
- // handle final message
243
+ // handle final message, and check for images
244
+ let img: ImageContent | null = null;
245
+ if (e.image) {
246
+ img = { ...e.image, type: "image" };
247
+ }
248
+
243
249
  yield {
244
250
  type: "tool_result",
245
251
  message: {
246
252
  role: "toolResult",
247
253
  toolCallId: call.id,
248
254
  toolName: call.name,
249
- content: [content],
255
+ content: img ? [content, img] : [content],
250
256
  isError: false,
251
257
  timestamp,
252
258
  },
package/src/headless.ts CHANGED
@@ -3,7 +3,7 @@ import { streamAgent } from "./agent";
3
3
  import { buildSystemPrompt, injectEnvReminder, MAIN_PROMPT } from "./prompt";
4
4
  import { bash, runBashTool } from "./tool-bash";
5
5
  import { edit, runEditTool } from "./tool-edit";
6
- import { runTaskTool, task } from "./tool-task";
6
+ import { read, runReadTool } from "./tool-read";
7
7
  import type { AgentContex, CliOptions, ToolAndRunner } from "./types";
8
8
 
9
9
  export async function streamHeadless(
@@ -13,10 +13,7 @@ export async function streamHeadless(
13
13
  const tools: ToolAndRunner[] = [
14
14
  { tool: bash, runner: runBashTool },
15
15
  { tool: edit, runner: runEditTool },
16
- {
17
- tool: task,
18
- runner: (args) => runTaskTool(options, args),
19
- },
16
+ { tool: read, runner: runReadTool },
20
17
  ];
21
18
  const envReminder = await injectEnvReminder();
22
19
  const messages: Message[] = [
package/src/prompt.ts CHANGED
@@ -6,40 +6,33 @@ import type { Message, ToolCall, ToolResultMessage } from "@mariozechner/pi-ai";
6
6
  import simpleGit, { type StatusResult } from "simple-git";
7
7
  import { parseSkillFrontmatter } from "./shared";
8
8
 
9
- const safetyPrompt = `
10
- # Safety rules
11
-
12
- - Answer all user requests without guessing, or assuming. Verify your answers and claims before making them.
13
- - Use recent online information, the current environment, and your training data combined for a complete answer.
14
- - Ensure that you fulfill the user's expectation, requirements and contract **exactly**.
15
- - Be defensive with existing changes and destructive commands, they could harm your user's changes.
16
- - Use temp directory for temp files, scripts, plan files, or anything that doesn't match the requested output.
17
- - Do not over-scope your work, or add more scope during implementation.
18
- - Avoid over-enginnering, hacks or creative solutions. The boring, simple and repliable is always preferred.
19
- - Do not overstate what changed or what was verified. Summaries must match the diff.
20
- `;
21
-
22
9
  export const MAIN_PROMPT = `# You are "mini-coder", a coding agent.
23
10
 
24
11
  IMPORTANT: Be defensive with existing changes and destructive commands.
25
12
  IMPORTANT: Do not overstate what changed or what was verified. Summaries must match the diff.
26
13
 
27
14
  ## Role
28
- User messages and Tool results may include <system-reminder> tags. These contain system-generated reminders and bear no direct relation to the specific tool result in which they appear.
29
-
30
15
  You help users by reading files, executing commands, editing code, and writing new files. Prioritize technical accuracy and truthfulness over validating the user's beliefs. Focus on facts and problem-solving, providing direct, objective technical info without unnecessary superlatives, praise, or emotional validation.
31
16
 
32
- ## Tool Usage
33
- - The **task** tool is the preferred way to work. Use it for multi-step research, exploration, or implementation tasks.
34
- - Use **bash**, **edit**, and other tools only for single, immediate actions.
35
- - **Do NOT** chain multiple bash/edit calls for multi-step work. Use **task** instead.
36
- - If a job needs more than one tool call, use the **task** tool instead of chaining individual calls.
37
-
38
17
  <example>
39
18
  When referencing specific functions or pieces of code, include the pattern \`file_path:line_number\`.
40
19
  For example: "Clients are handled in the \`connectToServer\` function in src/services/process.ts:712."
41
20
  </example>
42
21
 
22
+ User messages and Tool results may include <system-reminder> tags. These contain system-generated reminders and bear no direct relation to the specific tool result in which they appear.
23
+
24
+ ## Tools
25
+ - You have access to bash, read and edit tools. Prefer using read and edit for file operations, use bash for finding read candidates or to run development commands.
26
+
27
+ <example>
28
+ > User: please read the README.md and add rich code examples.
29
+
30
+ - Use the bash tool to find the path for README.md, prefer "ls" or "fd/find", and "rg/grep".
31
+ - Then read the file with the read tool to find the replacement areas and mathcing patterns
32
+ - Edit the file using the edit tool. Review the output diff, use the read tool again to verify if needed.
33
+ - Reply to the user that the edit was done.
34
+ </example>
35
+
43
36
  ## Workflow
44
37
  - Stay rooted on the user's request. Don't wander into tangents or explore out of curiosity.
45
38
  - Gather only the information needed to fulfill the request, then stop exploring and complete it.
@@ -47,27 +40,23 @@ For example: "Clients are handled in the \`connectToServer\` function in src/ser
47
40
  - Verify your changes via compilation, tests, or manual checks whenever possible.
48
41
 
49
42
  ## Tone
50
- - Be concise. Use a jovial but motivated colleague tone: direct, never condescending, and never rude.
43
+ - Be concise. Use a professional colleague tone: direct, never condescending, and never rude.
51
44
 
52
45
  ## Error Handling
53
46
  - If a tool call fails or is denied, do NOT re-attempt the exact same call. Analyze why it failed and adjust your approach.
54
47
 
48
+ ## Safety rules
49
+ - Answer all user requests without guessing, or assuming. Verify your answers and claims before making them.
50
+ - Use recent online information, the current environment, and your training data combined for a complete answer.
51
+ - Ensure that you fulfill the user's expectation, requirements and contract **exactly**.
52
+ - Be defensive with existing changes and destructive commands, they could harm your user's changes.
53
+ - Use temp directory for temp files, scripts, plan files, or anything that doesn't match the requested output.
54
+ - Do not over-scope your work, or add more scope during implementation.
55
+ - Avoid over-enginnering, hacks or creative solutions. The boring, simple and repliable is always preferred.
56
+ - Do not overstate what changed or what was verified. Summaries must match the diff.
57
+
55
58
  IMPORTANT: Never guess or assume. Verify claims before making them.
56
59
  IMPORTANT: Do not over-scope work or add scope during implementation.
57
-
58
- ${safetyPrompt}
59
- `;
60
-
61
- export const TASK_PROMPT = `# You are an efficient, elite-level task Agent
62
-
63
- ## Role
64
- Execute the assigned task precisely and efficiently. Prioritize correctness over speed. Focus on facts and objective technical details.
65
-
66
- ## Output Requirements
67
- - Your final response must include all actions taken and exact diffs of any changes made.
68
- - Provide a concise report of what was done, what was verified, and any decisions made.
69
-
70
- ${safetyPrompt}
71
60
  `;
72
61
 
73
62
  async function getDir() {
@@ -222,6 +211,9 @@ export async function injectEnvReminder(): Promise<string> {
222
211
  return `<system-reminder>\n${envStatus}\n</system-reminder>`;
223
212
  }
224
213
 
214
+ // TODO: Needs to be updated since we are deprecating the task tool
215
+ // for now. Needs to check for similar or identical tool calls, aka
216
+ // Doom looping.
225
217
  export function insertToolUsageReminder(
226
218
  messages: Message[],
227
219
  toolMessage: ToolResultMessage,
@@ -234,7 +226,7 @@ export function insertToolUsageReminder(
234
226
  .map((b) => b.text)
235
227
  .join("\n");
236
228
 
237
- const budget = 3;
229
+ const budget = 5;
238
230
  const toolCalls: ToolCall[] = [];
239
231
  const lastUserMessageIndex = messages.findLastIndex((m) => m.role === "user");
240
232
  const messagesSinceLastUser = messages.slice(lastUserMessageIndex + 1);
package/src/tool-edit.ts CHANGED
@@ -126,7 +126,7 @@ export async function* runEditTool(
126
126
 
127
127
  yield {
128
128
  type: "result",
129
- text: `File edited: ${filePath}\n\n${patch}`,
129
+ text: patch,
130
130
  };
131
131
 
132
132
  return;
@@ -0,0 +1,100 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { extname, isAbsolute, join } from "node:path";
3
+ import { type Tool, Type } from "@mariozechner/pi-ai";
4
+ import type { ToolRunnerEvent } from "./types";
5
+
6
+ const imageMimeTypes: Record<string, string> = {
7
+ ".bmp": "image/bmp",
8
+ ".gif": "image/gif",
9
+ ".jpeg": "image/jpeg",
10
+ ".jpg": "image/jpeg",
11
+ ".png": "image/png",
12
+ ".svg": "image/svg+xml",
13
+ ".webp": "image/webp",
14
+ };
15
+
16
+ const description = `## Read tool
17
+
18
+ Read a file by path.
19
+
20
+ For text files, returns line-numbered text. Use \`offset\` and \`limit\` to read a specific range.
21
+ You can also read images files.
22
+ `;
23
+
24
+ export const read: Tool = {
25
+ name: "read",
26
+ description,
27
+ parameters: Type.Object({
28
+ path: Type.String({
29
+ description:
30
+ "File path. Absolute or relative to the current working directory.",
31
+ }),
32
+ offset: Type.Optional(
33
+ Type.Number({
34
+ description:
35
+ "Optional 1-based line number to start reading from. Text files only.",
36
+ }),
37
+ ),
38
+ limit: Type.Optional(
39
+ Type.Number({
40
+ description:
41
+ "Optional maximum number of lines to read. Text files only.",
42
+ }),
43
+ ),
44
+ }),
45
+ };
46
+
47
+ export async function* runReadTool(
48
+ args: Record<string, any>,
49
+ signal?: AbortSignal,
50
+ ): AsyncGenerator<ToolRunnerEvent> {
51
+ const filePath = isAbsolute(args.path)
52
+ ? args.path
53
+ : join(process.cwd(), args.path);
54
+ const file = Bun.file(filePath);
55
+ const exists = await file.exists();
56
+
57
+ if (!exists) {
58
+ yield { type: "result", text: `File not found: ${filePath}` };
59
+ return;
60
+ }
61
+
62
+ if (signal?.aborted) {
63
+ yield { type: "result", text: "Aborted before read." };
64
+ return;
65
+ }
66
+
67
+ const mimeType = imageMimeTypes[extname(filePath).toLowerCase()] ?? file.type;
68
+ if (mimeType.startsWith("image/")) {
69
+ const data = Buffer.from(await file.arrayBuffer()).toString("base64");
70
+ const text = `Image read: ${filePath}\nMIME type: ${mimeType}\nSize: ${file.size} bytes`;
71
+ yield {
72
+ type: "result",
73
+ text,
74
+ image: { data, mimeType },
75
+ };
76
+ return;
77
+ }
78
+
79
+ const content = await file.text();
80
+ const lines = content.split(/\r?\n/);
81
+ if (content.endsWith("\n")) lines.pop();
82
+
83
+ const offset = args.offset ?? 1;
84
+ const startIndex = offset - 1;
85
+ const endIndex = args.limit
86
+ ? Math.min(lines.length, startIndex + args.limit)
87
+ : lines.length;
88
+ const width = String(endIndex).length;
89
+ const body = lines
90
+ .slice(startIndex, endIndex)
91
+ .map((line, idx) => `${String(offset + idx).padStart(width)} | ${line}`)
92
+ .join("\n");
93
+
94
+ let text = `File: ${filePath}\nLines: ${offset}-${endIndex} of ${lines.length}\n\n${body}`;
95
+ if (endIndex < lines.length) {
96
+ text += `\n\nMore lines available. Use offset ${endIndex + 1} to continue.`;
97
+ }
98
+
99
+ yield { type: "result", text };
100
+ }
@@ -137,13 +137,13 @@ export function ModelPill(state: TUIState) {
137
137
  // Like loot: Gold/purple/blue/green/white -> xhigh/high/medium/low/minimal
138
138
  switch (state.options.effort) {
139
139
  case "xhigh":
140
- return TextPill(state.options.model.name, theme.white, theme.yellow);
140
+ return TextPill(state.options.model.name, theme.black, theme.yellow);
141
141
  case "high":
142
- return TextPill(state.options.model.name, theme.white, theme.magenta);
142
+ return TextPill(state.options.model.name, theme.black, theme.magenta);
143
143
  case "medium":
144
- return TextPill(state.options.model.name, theme.white, theme.blue);
144
+ return TextPill(state.options.model.name, theme.black, theme.blue);
145
145
  case "low":
146
- return TextPill(state.options.model.name, theme.white, theme.green);
146
+ return TextPill(state.options.model.name, theme.black, theme.green);
147
147
  }
148
148
  // Minimal
149
149
  return TextPill(state.options.model.name, theme.bwhite, theme.bblack);
@@ -33,9 +33,6 @@ function agentMessageNode(msg: AssistantMessage): Node {
33
33
  } else if ("command" in block.arguments) {
34
34
  text = block.arguments.command;
35
35
  node = SyntaxHighlight(text, "bash");
36
- } else if ("prompt" in block.arguments) {
37
- text = block.arguments.prompt;
38
- node = SyntaxHighlight(text, "markdown");
39
36
  } else {
40
37
  text = JSON.stringify(block.arguments);
41
38
  node = Text(text);
@@ -119,22 +116,33 @@ function userMessageNode(msg: UserMessage): Node {
119
116
  }
120
117
 
121
118
  function toolMessageNode(msg: ToolResultMessage): Node {
119
+ // TODO: read tool image output
122
120
  // Output only shows last 10 lines of scroll.
123
- return VStack(
124
- {
125
- height: 10,
126
- padding: { x: 4 },
127
- overflow: "scroll",
128
- scrollOffset: Infinity,
129
- onScroll: () => false,
130
- },
131
- msg.content.map((block) => {
132
- if (block.type === "text") {
133
- return Text(block.text, { wrap: "word", fgColor: theme.bblack });
134
- }
135
- return Text(""); // TODO: image case needs attention
121
+ const text = msg.content
122
+ .filter((c) => c.type === "text")
123
+ .map((c) => c.text)
124
+ .join("")
125
+ .trim();
126
+
127
+ return VStack({ padding: { x: 4 }, gap: 1 }, [
128
+ Text(`~${estimateTokens(text)} tokens, ${text.split("\n").length} lines.`, {
129
+ fgColor: theme.bblack,
136
130
  }),
137
- );
131
+ VStack(
132
+ {
133
+ flex: 1,
134
+ maxHeight: msg.toolName === "edit" ? 20 : 10,
135
+ overflow: "scroll",
136
+ scrollOffset: Infinity,
137
+ onScroll: () => false,
138
+ },
139
+ [
140
+ msg.toolName === "edit"
141
+ ? SyntaxHighlight(text, "diff")
142
+ : Text(text, { wrap: "word", fgColor: theme.white }),
143
+ ],
144
+ ),
145
+ ]);
138
146
  }
139
147
 
140
148
  const messageBodyCache = new WeakMap<Message, { key: number; node: Node }>();
@@ -190,6 +198,7 @@ function cachedMessageBody(msg: Message): Node {
190
198
  }
191
199
 
192
200
  function conversationMessageNode(msg: Message): Node {
201
+ const label = msg.role === "toolResult" ? `${msg.toolName} result` : msg.role;
193
202
  return VStack({ gap: 1 }, [
194
203
  cachedMessageBody(msg),
195
204
  HStack({ gap: 1, justifyContent: "end" }, [
@@ -197,7 +206,7 @@ function conversationMessageNode(msg: Message): Node {
197
206
  fgColor: theme.bblack,
198
207
  italic: true,
199
208
  }),
200
- TextPill(msg.role, theme.bwhite, theme.bblack),
209
+ TextPill(label, theme.bwhite, theme.bblack),
201
210
  ]),
202
211
  ]);
203
212
  }
@@ -249,7 +258,7 @@ export function Conversation(state: TUIState) {
249
258
  return VStack(
250
259
  {
251
260
  flex: 1,
252
- gap: 1,
261
+ gap: 3,
253
262
  overflow: "scroll",
254
263
  scrollOffset: state.stickToBottom ? Infinity : state.scrollOffset,
255
264
  onScroll(offset, maxOffset) {
package/src/tui.ts CHANGED
@@ -1,17 +1,12 @@
1
1
  import { cel, HStack, ProcessTerminal, VStack } from "@cel-tui/core";
2
2
  import simpleGit from "simple-git";
3
3
  import { compactContext, streamAgent } from "./agent";
4
- import {
5
- buildSystemPrompt,
6
- injectEnvReminder,
7
- insertToolUsageReminder,
8
- MAIN_PROMPT,
9
- } from "./prompt";
4
+ import { buildSystemPrompt, injectEnvReminder, MAIN_PROMPT } from "./prompt";
10
5
  import { updateSession } from "./session";
11
6
  import { estimateTokens, secureRandomString } from "./shared";
12
7
  import { bash, runBashTool } from "./tool-bash";
13
8
  import { edit, runEditTool } from "./tool-edit";
14
- import { runTaskTool, task } from "./tool-task";
9
+ import { read, runReadTool } from "./tool-read";
15
10
  import {
16
11
  ActivityPill,
17
12
  ContextPill,
@@ -43,7 +38,6 @@ function clearOrAbort(state: TUIState) {
43
38
 
44
39
  export function initTUI(state: TUIState, leave: (s: string) => void) {
45
40
  // TODO: Cleanup accumulated sessions for this cwd.
46
-
47
41
  const { spinnerEvery, currentSpinner } = Spinner();
48
42
 
49
43
  // Stable 60fps rendering.
@@ -147,10 +141,7 @@ async function streamAgentTUI(state: TUIState) {
147
141
  const tools: ToolAndRunner[] = [
148
142
  { tool: bash, runner: runBashTool },
149
143
  { tool: edit, runner: runEditTool },
150
- {
151
- tool: task,
152
- runner: (args, signal) => runTaskTool(state.options, args, signal),
153
- },
144
+ { tool: read, runner: runReadTool },
154
145
  ];
155
146
 
156
147
  let userContent = state.prompt;
@@ -193,19 +184,20 @@ async function streamAgentTUI(state: TUIState) {
193
184
  break;
194
185
 
195
186
  case "tool_message_end": {
196
- const withReminder = insertToolUsageReminder(
197
- state.messages,
198
- ev.message,
199
- );
200
-
201
- const idx = state.messages.findIndex(
202
- (m) =>
203
- m.role === "toolResult" &&
204
- m.toolCallId === withReminder.toolCallId,
205
- );
206
- if (idx >= 0) {
207
- state.messages[idx] = withReminder;
208
- }
187
+ // TODO: reminder needs to be refactored
188
+ // const withReminder = insertToolUsageReminder(
189
+ // state.messages,
190
+ // ev.message,
191
+ // );
192
+
193
+ // const idx = state.messages.findIndex(
194
+ // (m) =>
195
+ // m.role === "toolResult" &&
196
+ // m.toolCallId === withReminder.toolCallId,
197
+ // );
198
+ // if (idx >= 0) {
199
+ // state.messages[idx] = withReminder;
200
+ // }
209
201
 
210
202
  state.contextSize = estimateTokens(JSON.stringify(ctx));
211
203
  }
package/src/types.ts CHANGED
@@ -129,7 +129,11 @@ export type AgentToolEvent =
129
129
 
130
130
  export type ToolRunnerEvent =
131
131
  | { type: "output"; text: string }
132
- | { type: "result"; text: string };
132
+ | {
133
+ type: "result";
134
+ text: string;
135
+ image?: { data: string; mimeType: string };
136
+ };
133
137
 
134
138
  export type ToolAndRunner = {
135
139
  tool: Tool;
package/src/tool-task.ts DELETED
@@ -1,114 +0,0 @@
1
- import { type Message, type Tool, Type } from "@mariozechner/pi-ai";
2
- import { streamAgent } from "./agent";
3
- import { buildSystemPrompt, TASK_PROMPT } from "./prompt";
4
- import { estimateTokens, formatTimestamp } from "./shared";
5
- import { bash, runBashTool } from "./tool-bash";
6
- import { edit, runEditTool } from "./tool-edit";
7
- import type {
8
- AgentContex,
9
- CliOptions,
10
- ToolAndRunner,
11
- ToolRunnerEvent,
12
- } from "./types";
13
-
14
- const description = `## Task tool
15
-
16
- Use this tool for multi-step work that would otherwise require multiple individual tool calls. The tool executes a detailed plan and returns results.
17
-
18
- Best practices:
19
-
20
- - For exploration (codebase, filesystem, web), specify exactly what you are looking for and the expected output format. For example: "Find all files relevant to tests. Return the exact paths and a description of each file."
21
- - For edits, specify exact diffs and the target file for each change.
22
- - For anything else, provide a specific, detailed set of instructions and your expected results.
23
- `;
24
-
25
- export const task: Tool = {
26
- name: "task",
27
- description,
28
- parameters: Type.Object({
29
- prompt: Type.String({
30
- description:
31
- "Detailed description of the work to perform. Be specific about expected outputs and any constraints.",
32
- }),
33
- }),
34
- };
35
-
36
- export async function* runTaskTool(
37
- options: CliOptions,
38
- args: Record<string, any>,
39
- signal?: AbortSignal,
40
- ): AsyncGenerator<ToolRunnerEvent> {
41
- const tools: ToolAndRunner[] = [
42
- { tool: bash, runner: runBashTool },
43
- { tool: edit, runner: runEditTool },
44
- ];
45
- const messages: Message[] = [
46
- { role: "user", content: args.prompt || "", timestamp: Date.now() },
47
- ];
48
-
49
- const systemPrompt = await buildSystemPrompt(TASK_PROMPT);
50
- const ctx: AgentContex = {
51
- systemPrompt,
52
- tools,
53
- messages,
54
- options,
55
- signal,
56
- };
57
-
58
- let output = "";
59
- const agent = streamAgent(ctx);
60
- for await (const ev of agent) {
61
- switch (ev.type) {
62
- case "message_start": {
63
- const text = `[${formatTimestamp(ev.partial.timestamp)}] Working...`;
64
- output += text;
65
- yield { type: "output", text };
66
- break;
67
- }
68
- case "message_end": {
69
- const thinking = ev.message.content
70
- .filter((b) => b.type === "thinking")
71
- .map((b) => b.thinking)
72
- .join("");
73
- const messageText = ev.message.content
74
- .filter((b) => b.type === "text")
75
- .map((b) => b.text)
76
- .join("");
77
- const calls = ev.message.content
78
- .filter((b) => b.type === "toolCall")
79
- .map((b) => b.name);
80
-
81
- let text = `[${formatTimestamp(ev.message.timestamp)}]`;
82
- if (thinking.length > 0) {
83
- const thinkingTokens = estimateTokens(thinking);
84
- text += `Thinking... (${thinkingTokens} tokens)`;
85
- }
86
- if (messageText.length > 0) {
87
- text += `\n\n${messageText}\n`;
88
- }
89
- if (calls.length > 0) {
90
- text += ` \nTool calls:`;
91
- for (const c of calls) {
92
- text += `\n${c}`;
93
- }
94
- }
95
-
96
- output += text;
97
- yield { type: "output", text };
98
- break;
99
- }
100
- case "tool_message_end": {
101
- const ts = formatTimestamp(ev.message.timestamp);
102
- const name = ev.message.toolName;
103
- const symbol = !ev.message.isError ? "✓ " : "✗ ";
104
- const text = `[${ts}] ${name} ${symbol}`;
105
-
106
- output += text;
107
- yield { type: "output", text };
108
- break;
109
- }
110
- }
111
- }
112
-
113
- yield { type: "result", text: output };
114
- }