Skip to content

Conversation

@Alb-O
Copy link
Contributor

@Alb-O Alb-O commented Dec 10, 2025

This keeps tool metadata callbacks in sync with the in-memory toolcall state so display inputs can be passed through to completion. processor.ts now prefers a metadata-updated input when finishing a tool call, allowing modified displays of commands without changing the actual execution.

  • packages/opencode/src/session/prompt.ts: metadata callback updates the live toolcall entry (input/metadata/title/time/displayInput) before persisting, keeping the processor’s map aligned.
  • packages/opencode/src/session/processor.ts: completion/error handling uses match.state.displayInput (from metadata), so the UI shows the plugin-provided display args.

Use case: nix develop bash wrapper plugin

A plugin can wrap the bash tool and modify the final displayed command via displayInput. This is shown to the user through the UI and also modifies the agent history:

"tool.execute.before": async ({ tool }, state) => {
  if (tool.toLowerCase() !== "bash") return
  const command = state.args?.command
  if (!command) return

  // Show the unwrapped command in the UI
  state.args.displayInput = { ...state.args }

  // Run the wrapped command for execution
  state.args.command = `nix develop --quiet -c bash -c "${command.replace(/"/g, '\\"')}"`
}

Before:

$ nix develop --quiet -c bash -c "echo \"hello\""

After:

$ echo "hello"

This has many use cases other than just nix, e.g. sandbox-esque wrappers where the full wrapped command is not relevant enough to be constantly showing to the user or agent, and would otherwise clutter history.

@rekram1-node
Copy link
Collaborator

/review

@rekram1-node
Copy link
Collaborator

/review

@Alb-O
Copy link
Contributor Author

Alb-O commented Dec 13, 2025

idk if that was worth it
edit: yeah that broke it

@rekram1-node
Copy link
Collaborator

should be good to go tmr if i forget just @ me or dm

@rekram1-node
Copy link
Collaborator

/review

@github-actions
Copy link
Contributor

lgtm

@Alb-O Alb-O reopened this Dec 18, 2025
@rekram1-node
Copy link
Collaborator

/review


case "tool-call": {
const match = toolcalls[value.toolCallId]
if (match) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: This line uses as any which the style guide recommends avoiding. Consider adding a proper type for the tool input that includes the optional displayInput property.

Suggested change
if (match) {
const { displayInput, ...execArgs } = value.input as Record<string, unknown> & { displayInput?: unknown }

toolCallId: part.callID,
input: part.state.input,
output: part.state.time.compacted ? "[Old tool result content cleared]" : part.state.output,
toolCallId: part.callID,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: There appears to be inconsistent whitespace here - the new lines have an extra leading space compared to the surrounding code. This looks like a formatting issue.

Suggested change
toolCallId: part.callID,
toolCallId: part.callID,
input: part.state.displayInput ?? part.state.input,
output: part.state.time.compacted ? "[Old tool result content cleared]" : part.state.output,

toolCallId: part.callID,
input: part.state.input,
errorText: part.state.error,
toolCallId: part.callID,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Same inconsistent whitespace here - extra leading space on the new lines.

Suggested change
toolCallId: part.callID,
toolCallId: part.callID,
input: part.state.displayInput ?? part.state.input,
errorText: part.state.error,

},
)
const result = await item.execute(args, {
const displayArgs = args.displayInput ?? args
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Using delete on args mutates the original object passed in, which could lead to unexpected side effects. Consider destructuring instead to avoid mutation:

Suggested change
const displayArgs = args.displayInput ?? args
const { displayInput, ...execArgs } = args
const displayArgs = displayInput ?? args
const result = await item.execute(execArgs, {

if (!execute) continue

// Wrap execute to add plugin hooks and format output
const schema = (item.inputSchema as any).jsonSchema
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: This uses as any which the style guide recommends avoiding. Consider using a more specific type or type guard.

Suggested change
const schema = (item.inputSchema as any).jsonSchema
const schema = (item.inputSchema as { jsonSchema?: { type?: string; properties?: Record<string, unknown> } }).jsonSchema

},
{
args,
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Similar to above, using delete mutates the original args object. Since the MCP execute wrapper is invoked with caller-provided args, mutating them could cause issues. Consider using destructuring instead:

Suggested change
},
const { displayInput, ...execArgs } = args
const result = await execute(execArgs, opts)

And update the Plugin.trigger call above to use the original args if needed for the hook.

- Avoid mutating tool arguments by using destructuring instead of delete
- Replace 'any' type assertions with more specific types for tool
schemas and inputs
- Fix inconsistent leading whitespace in MessageV2 tool part mapping
@Alb-O Alb-O changed the title Preserve display args from tool metadata Add 'displayInput' to bash tool schema Dec 20, 2025
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.

2 participants