Skip to content

Commit d64c66e

Browse files
committed
refactor: move Claude Code built-in tools filtering to CLI level
As suggested by @juliettefournier-econ, this change moves the filtering of Claude Code built-in tools (ExitPlanMode, BashOutput, KillBash) from the handler level to the CLI configuration level by adding them to the claudeCodeTools disallowed list in run.ts. This is a cleaner approach as it prevents these tools from being used at the source rather than filtering them after the fact.
1 parent 9f1a815 commit d64c66e

File tree

3 files changed

+4
-140
lines changed

3 files changed

+4
-140
lines changed

src/api/providers/__tests__/claude-code.spec.ts

Lines changed: 0 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -562,137 +562,4 @@ describe("ClaudeCodeHandler", () => {
562562

563563
consoleSpy.mockRestore()
564564
})
565-
566-
test("should silently ignore Claude Code built-in tools", async () => {
567-
const systemPrompt = "You are a helpful assistant"
568-
const messages = [{ role: "user" as const, content: "Hello" }]
569-
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {})
570-
571-
// Mock async generator that yields Claude Code built-in tool_use content
572-
const mockGenerator = async function* (): AsyncGenerator<ClaudeCodeMessage | string> {
573-
yield {
574-
type: "assistant" as const,
575-
message: {
576-
id: "msg_123",
577-
type: "message",
578-
role: "assistant",
579-
model: "claude-3-5-sonnet-20241022",
580-
content: [
581-
{
582-
type: "tool_use",
583-
id: "tool_123",
584-
name: "ExitPlanMode",
585-
input: {},
586-
},
587-
{
588-
type: "tool_use",
589-
id: "tool_124",
590-
name: "BashOutput",
591-
input: { command: "ls" },
592-
},
593-
{
594-
type: "tool_use",
595-
id: "tool_125",
596-
name: "KillBash",
597-
input: { pid: 1234 },
598-
},
599-
{
600-
type: "text",
601-
text: "Here's the response",
602-
},
603-
],
604-
stop_reason: null,
605-
stop_sequence: null,
606-
usage: {
607-
input_tokens: 10,
608-
output_tokens: 20,
609-
},
610-
} as any,
611-
session_id: "session_123",
612-
}
613-
}
614-
615-
mockRunClaudeCode.mockReturnValue(mockGenerator())
616-
617-
const stream = handler.createMessage(systemPrompt, messages)
618-
const results = []
619-
620-
for await (const chunk of stream) {
621-
results.push(chunk)
622-
}
623-
624-
// Should NOT log errors for Claude Code built-in tools
625-
expect(consoleSpy).not.toHaveBeenCalled()
626-
627-
// Should only have the text content in results
628-
expect(results).toHaveLength(1)
629-
expect(results[0]).toEqual({
630-
type: "text",
631-
text: "Here's the response",
632-
})
633-
634-
consoleSpy.mockRestore()
635-
})
636-
637-
test("should log warning for non-Claude Code tools but not for built-in ones", async () => {
638-
const systemPrompt = "You are a helpful assistant"
639-
const messages = [{ role: "user" as const, content: "Hello" }]
640-
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {})
641-
642-
// Mock async generator that yields mixed tool_use content
643-
const mockGenerator = async function* (): AsyncGenerator<ClaudeCodeMessage | string> {
644-
yield {
645-
type: "assistant" as const,
646-
message: {
647-
id: "msg_123",
648-
type: "message",
649-
role: "assistant",
650-
model: "claude-3-5-sonnet-20241022",
651-
content: [
652-
{
653-
type: "tool_use",
654-
id: "tool_123",
655-
name: "ExitPlanMode", // Built-in tool - should be ignored silently
656-
input: {},
657-
},
658-
{
659-
type: "tool_use",
660-
id: "tool_124",
661-
name: "custom_tool", // Non-built-in tool - should log error
662-
input: { data: "test" },
663-
},
664-
{
665-
type: "tool_use",
666-
id: "tool_125",
667-
name: "BashOutput", // Built-in tool - should be ignored silently
668-
input: { command: "pwd" },
669-
},
670-
],
671-
stop_reason: null,
672-
stop_sequence: null,
673-
usage: {
674-
input_tokens: 10,
675-
output_tokens: 20,
676-
},
677-
} as any,
678-
session_id: "session_123",
679-
}
680-
}
681-
682-
mockRunClaudeCode.mockReturnValue(mockGenerator())
683-
684-
const stream = handler.createMessage(systemPrompt, messages)
685-
const results = []
686-
687-
for await (const chunk of stream) {
688-
results.push(chunk)
689-
}
690-
691-
// Should only log error for the non-built-in tool
692-
expect(consoleSpy).toHaveBeenCalledTimes(1)
693-
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("tool_use is not supported yet"))
694-
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("custom_tool"))
695-
696-
consoleSpy.mockRestore()
697-
})
698565
})

src/api/providers/claude-code.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ import { ApiHandlerOptions } from "../../shared/api"
1616

1717
export class ClaudeCodeHandler extends BaseProvider implements ApiHandler {
1818
private options: ApiHandlerOptions
19-
// Claude Code built-in tools that should be filtered out
20-
private static readonly CLAUDE_CODE_BUILTIN_TOOLS = new Set(["ExitPlanMode", "BashOutput", "KillBash"])
2119

2220
constructor(options: ApiHandlerOptions) {
2321
super()
@@ -118,11 +116,7 @@ export class ClaudeCodeHandler extends BaseProvider implements ApiHandler {
118116
}
119117
break
120118
case "tool_use":
121-
// Filter out Claude Code built-in tools
122-
if (!ClaudeCodeHandler.CLAUDE_CODE_BUILTIN_TOOLS.has(content.name)) {
123-
console.error(`tool_use is not supported yet. Received: ${JSON.stringify(content)}`)
124-
}
125-
// Silently ignore Claude Code built-in tools
119+
console.error(`tool_use is not supported yet. Received: ${JSON.stringify(content)}`)
126120
break
127121
}
128122
}

src/integrations/claude-code/run.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ const claudeCodeTools = [
139139
"TodoRead",
140140
"TodoWrite",
141141
"WebSearch",
142+
"ExitPlanMode",
143+
"BashOutput",
144+
"KillBash",
142145
].join(",")
143146

144147
const CLAUDE_CODE_TIMEOUT = 600000 // 10 minutes

0 commit comments

Comments
 (0)