Skip to content

Conversation

@Eric-Guo
Copy link
Contributor

@Eric-Guo Eric-Guo commented Jan 1, 2026

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"}'
{
  "tool": "glob",
  "input": {
    "pattern": "export*",
    "include": "*.ts"
  },
  "result": {
    "title": "",
    "metadata": {
      "count": 1,
      "truncated": false
    },
    "output": "/Users/guochunzhong/git/oss/opencode/packages/opencode/src/cli/cmd/export.ts"
  }
}

Copilot AI review requested due to automatic review settings January 1, 2026 08:02
Copy link
Contributor

Copilot AI left a 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 --tool and --params to 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.

Comment on lines 71 to 114
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) {
Copy link

Copilot AI Jan 1, 2026

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.

Copilot uses AI. Check for mistakes.
parsed = JSON.parse(trimmed)
} catch (jsonError) {
try {
parsed = new Function(`return (${trimmed})`)()
Copy link

Copilot AI Jan 1, 2026

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.

Copilot uses AI. Check for mistakes.
time: {
created: now,
},
parentID: messageID,
Copy link

Copilot AI Jan 1, 2026

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.

Suggested change
parentID: messageID,
parentID: undefined,

Copilot uses AI. Check for mistakes.
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}.`,
Copy link

Copilot AI Jan 1, 2026

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.

Suggested change
`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)
}.`,

Copilot uses AI. Check for mistakes.
callID: Identifier.ascending("part"),
agent: agent.name,
abort: new AbortController().signal,
metadata: () => {},
Copy link

Copilot AI Jan 1, 2026

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.

Suggested change
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.
},

Copilot uses AI. Check for mistakes.
@Eric-Guo Eric-Guo force-pushed the debug_tool branch 5 times, most recently from fecd38a to 534f4de Compare January 6, 2026 04:15
@Eric-Guo Eric-Guo force-pushed the debug_tool branch 2 times, most recently from fe56ca1 to 12fdf3d Compare January 11, 2026 13:21
@github-actions
Copy link
Contributor

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

@Eric-Guo
Copy link
Contributor Author

@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?

@rekram1-node
Copy link
Collaborator

/review

@rekram1-node
Copy link
Collaborator

I just dont think I saw this before, this is a fun one tho it's fine

@Eric-Guo Eric-Guo requested a review from rekram1-node January 12, 2026 05:57
…e guide, keeping the same error behavior while avoiding mutable state in packages/opencode/src/cli/cmd/debug/agent.ts.
@rekram1-node
Copy link
Collaborator

/review

@github-actions
Copy link
Contributor

lgtm

@rekram1-node rekram1-node merged commit f4f8f2d into anomalyco:dev Jan 13, 2026
3 checks passed
sauerdaniel pushed a commit to sauerdaniel/opencode that referenced this pull request Jan 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE]: Support debug tool calling directly in CLI.

2 participants