diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index 01df6da7..63c13549 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -13,6 +13,7 @@ import { type ModelMessage, stepCountIs, type StreamTextResult, + InvalidToolInputError, } from "ai" import PROMPT_INITIALIZE from "../session/prompt/initialize.txt" @@ -869,7 +870,21 @@ export namespace Session { messages, } }, + async experimental_repairToolCall(input) { + if (InvalidToolInputError.isInstance(input.error)) { + return { + ...input.toolCall, + input: JSON.stringify({ + tool: input.toolCall.toolName, + error: input.error.message, + }), + toolName: "invalid", + } + } + return null + }, maxRetries: 3, + activeTools: Object.keys(tools).filter((x) => x !== "invalid"), maxOutputTokens: outputLimit, abortSignal: abort.signal, stopWhen: stepCountIs(1000), @@ -962,6 +977,7 @@ export namespace Session { if (match) { const part = await updatePart({ ...match, + tool: value.toolName, state: { status: "running", input: value.input, diff --git a/packages/opencode/src/tool/invalid.ts b/packages/opencode/src/tool/invalid.ts new file mode 100644 index 00000000..4695f1b7 --- /dev/null +++ b/packages/opencode/src/tool/invalid.ts @@ -0,0 +1,17 @@ +import { z } from "zod" +import { Tool } from "./tool" + +export const InvalidTool = Tool.define("invalid", { + description: "Do not use", + parameters: z.object({ + tool: z.string(), + error: z.string(), + }), + async execute(params) { + return { + title: "Invalid Tool", + output: `The arguments provided to the tool are invalid: ${params.error}`, + metadata: {}, + } + }, +}) diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 497d1078..d3396554 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -10,9 +10,11 @@ import { TaskTool } from "./task" import { TodoWriteTool, TodoReadTool } from "./todo" import { WebFetchTool } from "./webfetch" import { WriteTool } from "./write" +import { InvalidTool } from "./invalid" export namespace ToolRegistry { const ALL = [ + InvalidTool, BashTool, EditTool, WebFetchTool, diff --git a/packages/tui/internal/components/chat/message.go b/packages/tui/internal/components/chat/message.go index 29920efd..f320a3fa 100644 --- a/packages/tui/internal/components/chat/message.go +++ b/packages/tui/internal/components/chat/message.go @@ -555,6 +555,8 @@ func renderToolName(name string) string { switch name { case "webfetch": return "Fetch" + case "invalid": + return "Invalid" default: normalizedName := name if after, ok := strings.CutPrefix(name, "opencode_"); ok { @@ -657,6 +659,10 @@ func renderToolTitle( title = getTodoTitle(toolCall) case "todoread": return "Plan" + case "invalid": + if actualTool, ok := toolArgsMap["tool"].(string); ok { + title = renderToolName(actualTool) + } default: toolName := renderToolName(toolCall.Tool) title = fmt.Sprintf("%s %s", toolName, toolArgs)