diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts index bfc84df1ba..a693f47fd0 100644 --- a/packages/opencode/src/session/llm.ts +++ b/packages/opencode/src/session/llm.ts @@ -207,11 +207,7 @@ const live: Layer.Layer< input.model.providerID.toLowerCase().includes("litellm") || input.model.api.id.toLowerCase().includes("litellm") - const repair = (toolName: string) => { - const next = toolName.toLowerCase() - if (!tools[next]) return - return next - } + const repair = (toolName: string) => repairToolName(toolName, tools) // LiteLLM/Bedrock rejects requests where the message history contains tool // calls but no tools param is present. When there are no active tools (e.g. @@ -449,6 +445,12 @@ export const defaultLayer = Layer.suspend(() => ), ) +export function repairToolName(toolName: string, tools: Record) { + const next = ShellToolID.normalize(toolName.toLowerCase()) + if (!tools[next]) return + return next +} + function resolveTools(input: Pick) { const disabled = Permission.disabled( Object.keys(input.tools), diff --git a/packages/opencode/test/session/llm.test.ts b/packages/opencode/test/session/llm.test.ts index 663bfe3218..0ad0b2b695 100644 --- a/packages/opencode/test/session/llm.test.ts +++ b/packages/opencode/test/session/llm.test.ts @@ -1,6 +1,6 @@ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test" import path from "path" -import { tool, type ModelMessage } from "ai" +import { tool, type ModelMessage, type Tool } from "ai" import { Cause, Effect, Exit, Stream } from "effect" import z from "zod" import { makeRuntime } from "../../src/effect/run-service" @@ -119,6 +119,17 @@ describe("session.llm.hasToolCalls", () => { }) }) +describe("session.llm.repairToolName", () => { + test("normalizes legacy bash alias to shell when available", () => { + expect(LLM.repairToolName("bash", { shell: {} as Tool })).toBe("shell") + expect(LLM.repairToolName("BASH", { shell: {} as Tool })).toBe("shell") + }) + + test("returns undefined when normalized tool is unavailable", () => { + expect(LLM.repairToolName("bash", { read: {} as Tool })).toBeUndefined() + }) +}) + type Capture = { url: URL headers: Headers diff --git a/packages/ui/src/components/tool-error-card.tsx b/packages/ui/src/components/tool-error-card.tsx index 3b0b293319..7d22c9ff74 100644 --- a/packages/ui/src/components/tool-error-card.tsx +++ b/packages/ui/src/components/tool-error-card.tsx @@ -34,6 +34,7 @@ export function ToolErrorCard(props: ToolErrorCardProps) { webfetch: "ui.tool.webfetch", websearch: "ui.tool.websearch", codesearch: "ui.tool.codesearch", + bash: "ui.tool.shell", shell: "ui.tool.shell", apply_patch: "ui.tool.patch", question: "ui.tool.questions",