-
Notifications
You must be signed in to change notification settings - Fork 5.8k
feat(cli): Support debug tool calling directly in CLI. #6564
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds support for testing tools directly in the CLI without invoking the LLM. It introduces new --tool and --params options to the debug agent command, allowing developers to execute individual tools with specified parameters and view the results in JSON format.
Key Changes:
- Added CLI options
--tooland--paramsto specify which tool to execute and its parameters - Implemented parameter parsing that supports both JSON and JavaScript object literal syntax
- Created a tool context generator to enable standalone tool execution in debug mode
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| async function getAvailableTools(agent: Agent.Info) { | ||
| const providerID = agent.model?.providerID ?? (await Provider.defaultModel()).providerID | ||
| return ToolRegistry.tools(providerID, agent) | ||
| } | ||
|
|
||
| async function resolveTools(agent: Agent.Info, availableTools: Awaited<ReturnType<typeof getAvailableTools>>) { | ||
| const toolOverrides = { | ||
| ...agent.tools, | ||
| ...(await ToolRegistry.enabled(agent)), | ||
| } | ||
| const availableTools = await ToolRegistry.tools(providerID, agent) | ||
| const resolved: Record<string, boolean> = {} | ||
| for (const tool of availableTools) { | ||
| resolved[tool.id] = Wildcard.all(tool.id, toolOverrides) !== false | ||
| } | ||
| return resolved | ||
| } | ||
|
|
||
| function parseToolParams(input?: string) { | ||
| if (!input) return {} | ||
| const trimmed = input.trim() | ||
| if (trimmed.length === 0) return {} | ||
|
|
||
| let parsed: unknown | ||
| try { | ||
| parsed = JSON.parse(trimmed) | ||
| } catch (jsonError) { | ||
| try { | ||
| parsed = new Function(`return (${trimmed})`)() | ||
| } catch (evalError) { | ||
| throw new Error( | ||
| `Failed to parse --params. Use JSON or a JS object literal. JSON error: ${jsonError}. Eval error: ${evalError}.`, | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { | ||
| throw new Error("Tool params must be an object.") | ||
| } | ||
| return parsed as Record<string, unknown> | ||
| } | ||
|
|
||
| async function createToolContext(agent: Agent.Info) { |
Copilot
AI
Jan 1, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new helper functions (parseToolParams, createToolContext, and getAvailableTools) lack documentation. Consider adding JSDoc comments to explain their purpose, parameters, return values, and any important behavior, especially for parseToolParams which has complex parsing logic and error handling.
| parsed = JSON.parse(trimmed) | ||
| } catch (jsonError) { | ||
| try { | ||
| parsed = new Function(`return (${trimmed})`)() |
Copilot
AI
Jan 1, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using new Function() to parse user input poses a security risk as it can execute arbitrary JavaScript code. An attacker could potentially execute malicious code by crafting a specially designed params string. Consider using a safer alternative like a dedicated YAML parser or restricting the input format to JSON only.
| time: { | ||
| created: now, | ||
| }, | ||
| parentID: messageID, |
Copilot
AI
Jan 1, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The parentID is set to the same value as the message's own id, which creates a self-referencing parent relationship. This appears to be a logic error. The parentID should typically reference a previous message in the conversation chain, or be set to null/undefined if this is the first message in the chain.
| parentID: messageID, | |
| parentID: undefined, |
| parsed = new Function(`return (${trimmed})`)() | ||
| } catch (evalError) { | ||
| throw new Error( | ||
| `Failed to parse --params. Use JSON or a JS object literal. JSON error: ${jsonError}. Eval error: ${evalError}.`, |
Copilot
AI
Jan 1, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message construction concatenates error objects directly into the string, which will display "[object Object]" instead of the actual error messages. Consider using String(jsonError) or accessing the .message property to get meaningful error information.
| `Failed to parse --params. Use JSON or a JS object literal. JSON error: ${jsonError}. Eval error: ${evalError}.`, | |
| `Failed to parse --params. Use JSON or a JS object literal. JSON error: ${ | |
| jsonError instanceof Error ? jsonError.message : String(jsonError) | |
| }. Eval error: ${ | |
| evalError instanceof Error ? evalError.message : String(evalError) | |
| }.`, |
| callID: Identifier.ascending("part"), | ||
| agent: agent.name, | ||
| abort: new AbortController().signal, | ||
| metadata: () => {}, |
Copilot
AI
Jan 1, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The metadata function is defined as a no-op (empty function). If this function is expected to be called by tools, consider either providing a meaningful implementation or adding a comment explaining why it's intentionally a no-op in the debug context.
| metadata: () => {}, | |
| // Debug tool context: metadata is accepted but intentionally ignored. | |
| metadata: (_metadata: Record<string, unknown>) => { | |
| // Intentionally a no-op in debug mode: metadata is not persisted or used. | |
| }, |
fecd38a to
534f4de
Compare
fe56ca1 to
12fdf3d
Compare
|
Thanks for your contribution! This PR doesn't have a linked issue. All PRs must reference an existing issue. Please:
See CONTRIBUTING.md for details. |
|
@rekram1-node It's a following enhancement for #6529 and I used quit a lot in past week and found it's useful. Anything I need to do to made it accepted? |
|
/review |
|
I just dont think I saw this before, this is a fun one tho it's fine |
…: "export", include: "*.ts"}'`
…omalyco#6319) for tool availability and tool execution
…e guide, keeping the same error behavior while avoiding mutable state in packages/opencode/src/cli/cmd/debug/agent.ts.
|
/review |
|
lgtm |
Close #7802 7802
So now we can testing tools directly in cli without calling LLM.
opencode debug agent plan --tool glob --params '{pattern: "export*", include: "*.ts"}'