From 7d5a230e26bb2e4d770d5cbee10cde3671bafeb3 Mon Sep 17 00:00:00 2001 From: can1357 Date: Sat, 7 Mar 2026 23:52:42 +0100 Subject: [PATCH] fix: lowercased tool names in argument parsing Fixes #324. --- crates/pi-natives/src/keys.rs | 3 +-- packages/coding-agent/src/cli/args.ts | 5 ++++- packages/coding-agent/src/discovery/helpers.ts | 2 +- packages/coding-agent/src/sdk.ts | 4 ++-- packages/coding-agent/src/tools/index.ts | 3 ++- packages/coding-agent/test/args.test.ts | 5 +++++ .../coding-agent/test/discovery/agent-fields.test.ts | 10 ++++++++++ packages/coding-agent/test/tools/index.test.ts | 8 ++++++++ packages/tui/test/input.test.ts | 1 - packages/tui/test/keys.test.ts | 1 - 10 files changed, 33 insertions(+), 9 deletions(-) diff --git a/crates/pi-natives/src/keys.rs b/crates/pi-natives/src/keys.rs index 568d7e6d7..64cf4447c 100644 --- a/crates/pi-natives/src/keys.rs +++ b/crates/pi-natives/src/keys.rs @@ -1302,7 +1302,6 @@ fn parse_optional_digits(bytes: &[u8], idx: usize, end: usize) -> (Option, parse_digits(bytes, idx, end).map_or((None, idx), |(v, i)| (Some(v), i)) } - #[cfg(test)] mod tests { use super::*; @@ -1320,4 +1319,4 @@ mod tests { assert_eq!(parse_key_inner(b"\x1b[127;1:2u", true).as_deref(), Some("backspace")); assert_eq!(parse_key_inner(b"\x1b[127;1:3u", true).as_deref(), None); } -} \ No newline at end of file +} diff --git a/packages/coding-agent/src/cli/args.ts b/packages/coding-agent/src/cli/args.ts index b146cfb4a..b7ef8cf4e 100644 --- a/packages/coding-agent/src/cli/args.ts +++ b/packages/coding-agent/src/cli/args.ts @@ -108,7 +108,10 @@ export function parseArgs(args: string[], extensionFlags?: Map s.trim()); + const toolNames = args[++i] + .split(",") + .map(s => s.trim().toLowerCase()) + .filter(Boolean); const validTools: string[] = []; for (const name of toolNames) { if (name in BUILTIN_TOOLS) { diff --git a/packages/coding-agent/src/discovery/helpers.ts b/packages/coding-agent/src/discovery/helpers.ts index 2d0fdc429..ba752fe0a 100644 --- a/packages/coding-agent/src/discovery/helpers.ts +++ b/packages/coding-agent/src/discovery/helpers.ts @@ -206,7 +206,7 @@ export function parseAgentFields(frontmatter: Record): ParsedAg return null; } - let tools = parseArrayOrCSV(frontmatter.tools); + let tools = parseArrayOrCSV(frontmatter.tools)?.map(tool => tool.toLowerCase()); // Subagents with explicit tool lists always need submit_result if (tools && !tools.includes("submit_result")) { diff --git a/packages/coding-agent/src/sdk.ts b/packages/coding-agent/src/sdk.ts index 38c4539ae..9e490b995 100644 --- a/packages/coding-agent/src/sdk.ts +++ b/packages/coding-agent/src/sdk.ts @@ -1251,9 +1251,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} }; const toolNamesFromRegistry = Array.from(toolRegistry.keys()); - const requestedToolNames = options.toolNames ?? toolNamesFromRegistry; + const requestedToolNames = options.toolNames?.map(name => name.toLowerCase()) ?? toolNamesFromRegistry; const normalizedRequested = requestedToolNames.filter(name => toolRegistry.has(name)); - const includeExitPlanMode = options.toolNames?.includes("exit_plan_mode") ?? false; + const includeExitPlanMode = requestedToolNames.includes("exit_plan_mode"); const initialToolNames = includeExitPlanMode ? normalizedRequested : normalizedRequested.filter(name => name !== "exit_plan_mode"); diff --git a/packages/coding-agent/src/tools/index.ts b/packages/coding-agent/src/tools/index.ts index edffdcadc..cb6c1c79e 100644 --- a/packages/coding-agent/src/tools/index.ts +++ b/packages/coding-agent/src/tools/index.ts @@ -227,7 +227,8 @@ function getPythonModeFromEnv(): PythonToolMode | null { export async function createTools(session: ToolSession, toolNames?: string[]): Promise { const includeSubmitResult = session.requireSubmitResultTool === true; const enableLsp = session.enableLsp ?? true; - const requestedTools = toolNames && toolNames.length > 0 ? [...new Set(toolNames)] : undefined; + const requestedTools = + toolNames && toolNames.length > 0 ? [...new Set(toolNames.map(name => name.toLowerCase()))] : undefined; if (requestedTools && !requestedTools.includes("exit_plan_mode")) { requestedTools.push("exit_plan_mode"); } diff --git a/packages/coding-agent/test/args.test.ts b/packages/coding-agent/test/args.test.ts index f2ad92d8a..8f439396a 100644 --- a/packages/coding-agent/test/args.test.ts +++ b/packages/coding-agent/test/args.test.ts @@ -200,6 +200,11 @@ describe("parseArgs", () => { expect(result.noTools).toBe(true); expect(result.tools).toEqual(["read", "bash"]); }); + + test("lowercases tool names passed to --tools", () => { + const result = parseArgs(["--tools", "Read,Grep"]); + expect(result.tools).toEqual(["read", "grep"]); + }); }); describe("--no-lsp flag", () => { diff --git a/packages/coding-agent/test/discovery/agent-fields.test.ts b/packages/coding-agent/test/discovery/agent-fields.test.ts index 92e61139c..4491400a9 100644 --- a/packages/coding-agent/test/discovery/agent-fields.test.ts +++ b/packages/coding-agent/test/discovery/agent-fields.test.ts @@ -56,4 +56,14 @@ describe("parseAgentFields", () => { expect(fields?.thinkingLevel).toBe(Effort.High); }); + + test("lowercases tool names", () => { + const fields = parseAgentFields({ + name: "reviewer", + description: "desc", + tools: ["Read", "Grep"], + }); + + expect(fields?.tools).toEqual(["read", "grep", "submit_result"]); + }); }); diff --git a/packages/coding-agent/test/tools/index.test.ts b/packages/coding-agent/test/tools/index.test.ts index 53d008bd7..bf4a3fb87 100644 --- a/packages/coding-agent/test/tools/index.test.ts +++ b/packages/coding-agent/test/tools/index.test.ts @@ -98,6 +98,14 @@ describe("createTools", () => { expect(names).toEqual(["read", "write", "exit_plan_mode"]); }); + it("lowercases requested tool subset", async () => { + const session = createTestSession(); + const tools = await createTools(session, ["Read", "Write"]); + const names = tools.map(t => t.name); + + expect(names).toEqual(["read", "write", "exit_plan_mode"]); + }); + it("includes hidden tools when explicitly requested", async () => { const session = createTestSession(); const tools = await createTools(session, ["report_finding"]); diff --git a/packages/tui/test/input.test.ts b/packages/tui/test/input.test.ts index 7b32605c8..692317581 100644 --- a/packages/tui/test/input.test.ts +++ b/packages/tui/test/input.test.ts @@ -132,7 +132,6 @@ describe("Input component", () => { setKittyProtocolActive(false); }); - it("never renders a line wider than the terminal width (wide chars)", () => { const input = new Input(); input.focused = true; diff --git a/packages/tui/test/keys.test.ts b/packages/tui/test/keys.test.ts index 0d4e065d2..feff9007d 100644 --- a/packages/tui/test/keys.test.ts +++ b/packages/tui/test/keys.test.ts @@ -60,7 +60,6 @@ describe("parseKey", () => { setKittyProtocolActive(false); }); - it("should prefer codepoint for symbol keys when base layout differs", () => { setKittyProtocolActive(true); const dvorakCtrlSlash = "\x1b[47::91;5u";