summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorEric Guo <[email protected]>2026-01-13 13:10:56 +0800
committerGitHub <[email protected]>2026-01-12 23:10:56 -0600
commitf4f8f2d15134ccd7b5538687cc1e50f6dc652989 (patch)
tree544620cde291b4f4e8df108cc4923e72e22fd377 /packages
parentb4ad5c138ee1a5c808cf742ae828b6903b0f7649 (diff)
downloadopencode-f4f8f2d15134ccd7b5538687cc1e50f6dc652989.tar.gz
opencode-f4f8f2d15134ccd7b5538687cc1e50f6dc652989.zip
feat(cli): Support debug tool calling directly in CLI. (#6564)
Diffstat (limited to 'packages')
-rw-r--r--packages/opencode/src/cli/cmd/debug/agent.ts149
1 files changed, 143 insertions, 6 deletions
diff --git a/packages/opencode/src/cli/cmd/debug/agent.ts b/packages/opencode/src/cli/cmd/debug/agent.ts
index ec5ef0c43..ef6b0c4fc 100644
--- a/packages/opencode/src/cli/cmd/debug/agent.ts
+++ b/packages/opencode/src/cli/cmd/debug/agent.ts
@@ -1,6 +1,14 @@
import { EOL } from "os"
import { basename } from "path"
import { Agent } from "../../../agent/agent"
+import { Provider } from "../../../provider/provider"
+import { Session } from "../../../session"
+import type { MessageV2 } from "../../../session/message-v2"
+import { Identifier } from "../../../id/id"
+import { ToolRegistry } from "../../../tool/registry"
+import { Instance } from "../../../project/instance"
+import { PermissionNext } from "../../../permission/next"
+import { iife } from "../../../util/iife"
import { bootstrap } from "../../bootstrap"
import { cmd } from "../cmd"
@@ -8,11 +16,20 @@ export const AgentCommand = cmd({
command: "agent <name>",
describe: "show agent configuration details",
builder: (yargs) =>
- yargs.positional("name", {
- type: "string",
- demandOption: true,
- description: "Agent name",
- }),
+ yargs
+ .positional("name", {
+ type: "string",
+ demandOption: true,
+ description: "Agent name",
+ })
+ .option("tool", {
+ type: "string",
+ description: "Tool id to execute",
+ })
+ .option("params", {
+ type: "string",
+ description: "Tool params as JSON or a JS object literal",
+ }),
async handler(args) {
await bootstrap(process.cwd(), async () => {
const agentName = args.name as string
@@ -23,7 +40,127 @@ export const AgentCommand = cmd({
)
process.exit(1)
}
- process.stdout.write(JSON.stringify(agent, null, 2) + EOL)
+ const availableTools = await getAvailableTools(agent)
+ const resolvedTools = await resolveTools(agent, availableTools)
+ const toolID = args.tool as string | undefined
+ if (toolID) {
+ const tool = availableTools.find((item) => item.id === toolID)
+ if (!tool) {
+ process.stderr.write(`Tool ${toolID} not found for agent ${agentName}` + EOL)
+ process.exit(1)
+ }
+ if (resolvedTools[toolID] === false) {
+ process.stderr.write(`Tool ${toolID} is disabled for agent ${agentName}` + EOL)
+ process.exit(1)
+ }
+ const params = parseToolParams(args.params as string | undefined)
+ const ctx = await createToolContext(agent)
+ const result = await tool.execute(params, ctx)
+ process.stdout.write(JSON.stringify({ tool: toolID, input: params, result }, null, 2) + EOL)
+ return
+ }
+
+ const output = {
+ ...agent,
+ tools: resolvedTools,
+ }
+ process.stdout.write(JSON.stringify(output, null, 2) + EOL)
})
},
})
+
+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 disabled = PermissionNext.disabled(
+ availableTools.map((tool) => tool.id),
+ agent.permission,
+ )
+ const resolved: Record<string, boolean> = {}
+ for (const tool of availableTools) {
+ resolved[tool.id] = !disabled.has(tool.id)
+ }
+ return resolved
+}
+
+function parseToolParams(input?: string) {
+ if (!input) return {}
+ const trimmed = input.trim()
+ if (trimmed.length === 0) return {}
+
+ const parsed = iife(() => {
+ try {
+ return JSON.parse(trimmed)
+ } catch (jsonError) {
+ try {
+ return 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) {
+ const session = await Session.create({ title: `Debug tool run (${agent.name})` })
+ const messageID = Identifier.ascending("message")
+ const model = agent.model ?? (await Provider.defaultModel())
+ const now = Date.now()
+ const message: MessageV2.Assistant = {
+ id: messageID,
+ sessionID: session.id,
+ role: "assistant",
+ time: {
+ created: now,
+ },
+ parentID: messageID,
+ modelID: model.modelID,
+ providerID: model.providerID,
+ mode: "debug",
+ agent: agent.name,
+ path: {
+ cwd: Instance.directory,
+ root: Instance.worktree,
+ },
+ cost: 0,
+ tokens: {
+ input: 0,
+ output: 0,
+ reasoning: 0,
+ cache: {
+ read: 0,
+ write: 0,
+ },
+ },
+ }
+ await Session.updateMessage(message)
+
+ const ruleset = PermissionNext.merge(agent.permission, session.permission ?? [])
+
+ return {
+ sessionID: session.id,
+ messageID,
+ callID: Identifier.ascending("part"),
+ agent: agent.name,
+ abort: new AbortController().signal,
+ metadata: () => {},
+ async ask(req: Omit<PermissionNext.Request, "id" | "sessionID" | "tool">) {
+ for (const pattern of req.patterns) {
+ const rule = PermissionNext.evaluate(req.permission, pattern, ruleset)
+ if (rule.action === "deny") {
+ throw new PermissionNext.DeniedError(ruleset)
+ }
+ }
+ },
+ }
+}