fix: lowercased tool names in argument parsing

Fixes #324.
This commit is contained in:
can1357
2026-03-07 23:53:16 +01:00
parent 95df9f6f3e
commit 7d5a230e26
10 changed files with 33 additions and 9 deletions
+1 -2
View File
@@ -1302,7 +1302,6 @@ fn parse_optional_digits(bytes: &[u8], idx: usize, end: usize) -> (Option<u32>,
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);
}
}
}
+4 -1
View File
@@ -108,7 +108,10 @@ export function parseArgs(args: string[], extensionFlags?: Map<string, { type: "
} else if (arg === "--no-pty") {
result.noPty = true;
} else if (arg === "--tools" && i + 1 < args.length) {
const toolNames = args[++i].split(",").map(s => 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) {
@@ -206,7 +206,7 @@ export function parseAgentFields(frontmatter: Record<string, unknown>): 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")) {
+2 -2
View File
@@ -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");
+2 -1
View File
@@ -227,7 +227,8 @@ function getPythonModeFromEnv(): PythonToolMode | null {
export async function createTools(session: ToolSession, toolNames?: string[]): Promise<Tool[]> {
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");
}
+5
View File
@@ -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", () => {
@@ -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"]);
});
});
@@ -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"]);
-1
View File
@@ -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;
-1
View File
@@ -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";