feat(o2ai): render CLI tool_result summary and command (#11449)

## Summary
- Render `summary.return_code`, `stdout_lines`, `stderr_lines`, and
`truncated` in the chat tool-call details panel so CLI tool results show
parsed info like MCP tools do.
- Whitelist `command` in `getToolCallDisplayData` and add a "Command"
row with a copy button so the actual CLI command the agent ran is
visible.

## Context
The o2-ai server already emits a uniform `tool_result` shape for CLI
tools (see openobserve/o2-ai#96 — wait for that to merge):
```json
{
  "type": "tool_result",
  "tool": "cli_kubectl",
  "success": true,
  "message": "Exited 0 (12 lines stdout)",
  "summary": { "return_code": 0, "stdout_lines": 12, "stderr_lines": 0 },
  "call_args": { "command": "get pods -n default" },
  "call_id": "call-uuid-v7"
}
```
`ContentBlock.summary` was already being captured from the event — the
template just had no conditionals for the CLI fields. This PR adds them.

## What this does NOT do
- Does not show raw `stdout`/`stderr` text (server currently only sends
line counts). If we want inline output, that's a separate backend
change.
- Does not add a custom header format for CLI — the default
`block.message` ("Exited 0 (12 lines stdout)") renders fine as plain
text.

## Test plan
- [x] `npm run type-check` — clean
- [ ] Manually verify in dev chat that a CLI tool call (`kubectl get
pods`) shows the Exit code / Stdout / Stderr rows and the Command row
with copy button
- [ ] MCP tools (SearchSQL, StreamList) still render the existing
Results / Duration rows unchanged

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hengfei Yang
2026-04-24 15:32:07 +00:00
committed by GitHub
co-authored by Claude Opus 4.7
parent abac6d95db
commit f897aa2cb2
+36
View File
@@ -402,6 +402,23 @@
<span class="detail-label">Duration</span>
<span class="detail-value">{{ block.summary.took }}ms</span>
</div>
<!-- CLI tool summary (return_code / stdout_lines / stderr_lines / truncated) -->
<div v-if="block.summary.return_code !== undefined" class="detail-item">
<span class="detail-label">Exit code</span>
<code class="detail-value">{{ block.summary.return_code }}</code>
</div>
<div v-if="block.summary.stdout_lines !== undefined" class="detail-item">
<span class="detail-label">Stdout</span>
<span class="detail-value">{{ block.summary.stdout_lines }} lines</span>
</div>
<div v-if="block.summary.stderr_lines" class="detail-item">
<span class="detail-label">Stderr</span>
<span class="detail-value">{{ block.summary.stderr_lines }} lines</span>
</div>
<div v-if="block.summary.truncated" class="detail-item">
<span class="detail-label">Output</span>
<span class="detail-value">truncated</span>
</div>
</template>
<!-- Existing context details -->
<div v-if="getToolCallDisplayData(block.context)?.query" class="detail-item">
@@ -464,6 +481,22 @@
</div>
<code class="detail-value query-value">{{ getToolCallDisplayData(block.context)?.vrl }}</code>
</div>
<div v-if="getToolCallDisplayData(block.context)?.command" class="detail-item">
<div class="detail-header">
<span class="detail-label">Command</span>
<q-btn
flat
dense
size="xs"
icon="content_copy"
class="copy-btn"
@click.stop="copyToClipboard(getToolCallDisplayData(block.context)?.command)"
>
<q-tooltip>Copy command</q-tooltip>
</q-btn>
</div>
<code class="detail-value query-value">{{ getToolCallDisplayData(block.context)?.command }}</code>
</div>
<!-- Tool response: SearchSQL hits -->
<template v-if="block.response && block.response.hits">
<div class="detail-item">
@@ -4532,6 +4565,9 @@ export default defineComponent({
if (context.stream_name) data.stream = context.stream_name;
if (context.type) data.type = context.type;
// CLI tools: surface the command string
if (context.command) data.command = context.command;
return Object.keys(data).length > 0 ? data : null;
};