Skip to content

feat(tooling): preserve native tool titles#1133

Merged
tiann merged 1 commit into
tiann:mainfrom
swear01:feat/1130-native-tool-metadata
Jul 23, 2026
Merged

feat(tooling): preserve native tool titles#1133
tiann merged 1 commit into
tiann:mainfrom
swear01:feat/1130-native-tool-metadata

Conversation

@swear01

@swear01 swear01 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

  • preserve ACP title and kind through the generic agent conversion path
  • preserve OpenCode local tool-state titles on emitted tool calls
  • normalize native presentation metadata into web tool-call state
  • prefer native titles when rendering individual and grouped tool rows
  • retain optional fields for backward compatibility with stored events and backends without titles

Closes #1130

Verification

  • bun typecheck
  • targeted CLI/web tests: 102 passed
  • bun run test — CLI 1381, hub 505, web 1298, shared 115 tests passed

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Findings

  • [Major] Native titles still do not render for OpenCode local unknown/lowercase tools — parseToolCall now preserves state.title while keeping tool names such as bash, but the web call sites pass that title through description and getToolPresentation ignores description on its unknown-tool path. For OpenCode local bash, the card still renders bash instead of Run project tests, so the core feature in this PR is lost for that backend. Evidence: cli/src/opencode/utils/opencodeLocalToolParse.test.ts:33, web/src/components/ToolCard/ToolCard.tsx:322, related context web/src/components/ToolCard/knownTools.tsx:551.
    Suggested fix:
    // web/src/components/ToolCard/knownTools.tsx
    type ToolOpts = {
        toolName: string
        input: unknown
        result: unknown
        childrenCount: number
        description: string | null
        nativeTitle?: string | null
        metadata: SessionMetadataSummary | null
    }
    
    // In the unknown-tool fallback:
    let title = opts.nativeTitle ?? opts.description ?? opts.toolName
    if (subtitle && subtitle === title) {
        if (filePath) title = t ? t('tool.semanticTitle.readFile') : 'Read file'
        else if (command) title = t ? t('tool.semanticTitle.runShell') : 'Run shell'
        else if (pattern) title = t ? t('tool.semanticTitle.search') : 'Search'
        else if (url) title = t ? t('tool.semanticTitle.openUrl') : 'Open URL'
        else if (query) title = t ? t('tool.semanticTitle.query') : 'Query'
    }

Questions

  • None.

Summary

  • Review mode: initial
  • One rendering bug found; preserved OpenCode native titles can still be dropped at presentation time for lowercase/unknown tool names.

Testing

  • Not run (automation)

HAPI Bot

result: props.block.tool.result,
childrenCount: props.block.children.length,
description: props.block.tool.description,
description: props.block.tool.nativeTitle ?? props.block.tool.description,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[Major] Native titles are still ignored for lower-case/unknown OpenCode tools.

OpenCode local parsing now preserves state.title while keeping names like bash, but this call site tunnels the title through description. getToolPresentation only reads description for known tools such as Bash/shell_command; the unknown fallback uses opts.toolName, so OpenCode local bash still renders as bash instead of the native title.

Suggested fix:

// web/src/components/ToolCard/knownTools.tsx
type ToolOpts = {
    toolName: string
    input: unknown
    result: unknown
    childrenCount: number
    description: string | null
    nativeTitle?: string | null
    metadata: SessionMetadataSummary | null
}

// In the unknown-tool fallback:
let title = opts.nativeTitle ?? opts.description ?? opts.toolName
if (subtitle && subtitle === title) {
    if (filePath) title = t ? t('tool.semanticTitle.readFile') : 'Read file'
    else if (command) title = t ? t('tool.semanticTitle.runShell') : 'Run shell'
    else if (pattern) title = t ? t('tool.semanticTitle.search') : 'Search'
    else if (url) title = t ? t('tool.semanticTitle.openUrl') : 'Open URL'
    else if (query) title = t ? t('tool.semanticTitle.query') : 'Query'
}

@swear01
swear01 force-pushed the feat/1130-native-tool-metadata branch from 3dea4b6 to ed3641b Compare July 22, 2026 19:11

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Findings

  • [Major] OpenCode execute-hook late calls still drop native titles — tool.execute.before now forwards nativeTitle, but the same branch returns early when before has empty {} input. In that documented fallback, the only tool-call emitted is from tool.execute.after, and that recovery payload still omits nativeTitle, so native titles are lost for tools whose usable args arrive only on after. Evidence: cli/src/opencode/opencodeLocalLauncher.ts:424.
    Suggested fix:
    const nativeTitle = getString(tool.title ?? record.title);
    
    session.sendAgentMessage({
        type: 'tool-call',
        name,
        callId,
        input: toolInput,
        ...(nativeTitle ? { nativeTitle } : {})
    });
  • [Minor] Single tool cards can keep a stale presentation when only nativeTitle changes — the memo reads props.block.tool.nativeTitle, while ensureToolBlock can update nativeTitle on an existing block independently of description; because nativeTitle is not in the dependency list, a live metadata-only update can leave the visible title unchanged until another dependency changes. Evidence: web/src/components/ToolCard/ToolCard.tsx:322.
    Suggested fix:
    ], [
        props.block.tool.name,
        props.block.tool.input,
        props.block.tool.result,
        props.block.children.length,
        props.block.tool.nativeTitle,
        props.block.tool.description,
        props.metadata,
        t
    ])

Questions

  • None.

Summary

  • Review mode: follow-up after new commits
  • Two issues found in the updated title-preservation paths: one OpenCode local fallback still drops the title, and one web memo can stale on live metadata-only updates.

Testing

  • Not run (automation; static review only)

HAPI Bot

callId,
input: toolInput
input: toolInput,
...(getString(tool.title ?? record.title) ? { nativeTitle: getString(tool.title ?? record.title) } : {})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[Major] Native titles are still dropped on the execute-hook late recovery path.

This adds nativeTitle only to the tool.execute.before emission, but that branch returns early when before carries placeholder {} input. In that documented fallback, the only tool-call is emitted from tool.execute.after, and that recovery payload still omits the same title, so tools whose real args arrive only on after lose the native title.

Suggested fix:

const nativeTitle = getString(tool.title ?? record.title);

session.sendAgentMessage({
    type: 'tool-call',
    name,
    callId,
    input: toolInput,
    ...(nativeTitle ? { nativeTitle } : {})
});

result: props.block.tool.result,
childrenCount: props.block.children.length,
description: props.block.tool.description,
description: props.block.tool.nativeTitle ?? props.block.tool.description,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[Minor] Include nativeTitle in the presentation memo dependencies.

The memo now reads props.block.tool.nativeTitle, and ensureToolBlock can update nativeTitle on an existing block without changing description. If a live update only adds presentation metadata, this card can keep rendering the old title until another listed dependency changes.

Suggested fix:

], [
    props.block.tool.name,
    props.block.tool.input,
    props.block.tool.result,
    props.block.children.length,
    props.block.tool.nativeTitle,
    props.block.tool.description,
    props.metadata,
    t
])

@swear01
swear01 force-pushed the feat/1130-native-tool-metadata branch from ed3641b to 528d454 Compare July 22, 2026 19:24

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Findings

  • No issues found.

Questions

  • None.

Summary

  • Review mode: follow-up after new commits
  • Full current diff reviewed. Prior bot findings appear addressed: native titles are forwarded on the OpenCode late execute recovery path, and the ToolCard presentation memo now depends on nativeTitle.
  • Residual risk: static review only; I did not execute PR code.

Testing

  • Not run (security/static review only; PR reports bun typecheck, targeted CLI/web tests, and bun run test passing)

HAPI Bot

@tiann
tiann merged commit 6bedd0d into tiann:main Jul 23, 2026
2 checks passed
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.

feat(tooling): preserve native tool presentation metadata across agent backends

2 participants