Merge a7eb6c58ab1d6a65607f57a870c44c601b24953c into 5e417b44e1540f528d2ae63e3e20229a902d1db2
This commit is contained in:
commit
6330af45b1
@ -31,7 +31,42 @@ describe("chat markdown rendering", () => {
|
||||
|
||||
await app.updateComplete;
|
||||
|
||||
const strong = app.querySelector(".sidebar-markdown strong");
|
||||
expect(strong?.textContent).toBe("world");
|
||||
const strongNodes = Array.from(app.querySelectorAll(".sidebar-markdown strong"));
|
||||
expect(strongNodes.map((node) => node.textContent)).toContain("world");
|
||||
});
|
||||
|
||||
it("shows tool call request parameters in the sidebar", async () => {
|
||||
const app = mountApp("/chat");
|
||||
await app.updateComplete;
|
||||
|
||||
const timestamp = Date.now();
|
||||
app.chatMessages = [
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "toolcall",
|
||||
name: "sessions_spawn",
|
||||
arguments: { agentId: "research", prompt: "hello" },
|
||||
},
|
||||
],
|
||||
timestamp,
|
||||
},
|
||||
];
|
||||
|
||||
await app.updateComplete;
|
||||
|
||||
const toolCard = app.querySelector<HTMLElement>(".chat-tool-card");
|
||||
expect(toolCard).not.toBeNull();
|
||||
toolCard?.click();
|
||||
|
||||
await app.updateComplete;
|
||||
|
||||
const sidebar = app.querySelector(".sidebar-markdown");
|
||||
expect(sidebar?.textContent).toContain("Arguments");
|
||||
expect(sidebar?.textContent).toContain("agentId");
|
||||
expect(sidebar?.textContent).toContain("research");
|
||||
expect(sidebar?.textContent).toContain("prompt");
|
||||
expect(sidebar?.textContent).toContain("hello");
|
||||
});
|
||||
});
|
||||
|
||||
@ -5,7 +5,7 @@ import type { ToolCard } from "../types/chat-types.ts";
|
||||
import { TOOL_INLINE_THRESHOLD } from "./constants.ts";
|
||||
import { extractTextCached } from "./message-extract.ts";
|
||||
import { isToolResultMessage } from "./message-normalizer.ts";
|
||||
import { formatToolOutputForSidebar, getTruncatedPreview } from "./tool-helpers.ts";
|
||||
import { buildToolSidebarContent, getTruncatedPreview } from "./tool-helpers.ts";
|
||||
|
||||
export function extractToolCards(message: unknown): ToolCard[] {
|
||||
const m = message as Record<string, unknown>;
|
||||
@ -56,14 +56,14 @@ export function renderToolCardSidebar(card: ToolCard, onOpenSidebar?: (content:
|
||||
const canClick = Boolean(onOpenSidebar);
|
||||
const handleClick = canClick
|
||||
? () => {
|
||||
if (hasText) {
|
||||
onOpenSidebar!(formatToolOutputForSidebar(card.text!));
|
||||
return;
|
||||
}
|
||||
const info = `## ${display.label}\n\n${
|
||||
detail ? `**Command:** \`${detail}\`\n\n` : ""
|
||||
}*No output — tool completed successfully.*`;
|
||||
onOpenSidebar!(info);
|
||||
onOpenSidebar!(
|
||||
buildToolSidebarContent({
|
||||
title: display.label,
|
||||
detail,
|
||||
args: card.args,
|
||||
output: card.text,
|
||||
}),
|
||||
);
|
||||
}
|
||||
: undefined;
|
||||
|
||||
@ -95,11 +95,7 @@ export function renderToolCardSidebar(card: ToolCard, onOpenSidebar?: (content:
|
||||
<span class="chat-tool-card__icon">${icons[display.icon]}</span>
|
||||
<span>${display.label}</span>
|
||||
</div>
|
||||
${
|
||||
canClick
|
||||
? html`<span class="chat-tool-card__action">${hasText ? "View" : ""} ${icons.check}</span>`
|
||||
: nothing
|
||||
}
|
||||
${canClick ? html`<span class="chat-tool-card__action">View ${icons.check}</span>` : nothing}
|
||||
${isEmpty && !canClick ? html`<span class="chat-tool-card__status">${icons.check}</span>` : nothing}
|
||||
</div>
|
||||
${detail ? html`<div class="chat-tool-card__detail">${detail}</div>` : nothing}
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { formatToolOutputForSidebar, getTruncatedPreview } from "./tool-helpers.ts";
|
||||
import {
|
||||
buildToolSidebarContent,
|
||||
formatToolOutputForSidebar,
|
||||
formatToolPayloadForSidebar,
|
||||
getTruncatedPreview,
|
||||
} from "./tool-helpers.ts";
|
||||
|
||||
describe("tool-helpers", () => {
|
||||
describe("formatToolOutputForSidebar", () => {
|
||||
@ -138,4 +143,54 @@ describe("tool-helpers", () => {
|
||||
expect(result.endsWith("…")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatToolPayloadForSidebar", () => {
|
||||
it("formats structured arguments as JSON", () => {
|
||||
const result = formatToolPayloadForSidebar({ agentId: "research", count: 2 });
|
||||
|
||||
expect(result).toBe(`\`\`\`json
|
||||
{
|
||||
"agentId": "research",
|
||||
"count": 2
|
||||
}
|
||||
\`\`\``);
|
||||
});
|
||||
|
||||
it("returns null for undefined arguments", () => {
|
||||
expect(formatToolPayloadForSidebar(undefined)).toBeNull();
|
||||
});
|
||||
|
||||
it("returns null for null arguments", () => {
|
||||
expect(formatToolPayloadForSidebar(null)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildToolSidebarContent", () => {
|
||||
it("includes arguments and output when both are present", () => {
|
||||
const result = buildToolSidebarContent({
|
||||
title: "Session Spawn",
|
||||
detail: "sessions_spawn research",
|
||||
args: { agentId: "research" },
|
||||
output: '{"status":"ok"}',
|
||||
});
|
||||
|
||||
expect(result).toContain("## Session Spawn");
|
||||
expect(result).toContain("**Command:** `sessions_spawn research`");
|
||||
expect(result).toContain("**Arguments**");
|
||||
expect(result).toContain('"agentId": "research"');
|
||||
expect(result).toContain("**Output**");
|
||||
expect(result).toContain('"status": "ok"');
|
||||
});
|
||||
|
||||
it("shows completion text when output is absent", () => {
|
||||
const result = buildToolSidebarContent({
|
||||
title: "Session Spawn",
|
||||
args: { agentId: "research" },
|
||||
});
|
||||
|
||||
expect(result).toContain("**Arguments**");
|
||||
expect(result).toContain('"agentId": "research"');
|
||||
expect(result).toContain("*No output - tool completed successfully.*");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -22,6 +22,46 @@ export function formatToolOutputForSidebar(text: string): string {
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format tool-call input/arguments for display in the sidebar.
|
||||
* Uses JSON code blocks for structured values and preserves plain text input.
|
||||
*/
|
||||
export function formatToolPayloadForSidebar(value: unknown): string | null {
|
||||
if (value === undefined || value === null) {
|
||||
return null;
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return formatToolOutputForSidebar(value);
|
||||
}
|
||||
try {
|
||||
return "```json\n" + JSON.stringify(value, null, 2) + "\n```";
|
||||
} catch {
|
||||
return String(value);
|
||||
}
|
||||
}
|
||||
|
||||
export function buildToolSidebarContent(params: {
|
||||
title: string;
|
||||
detail?: string;
|
||||
args?: unknown;
|
||||
output?: string;
|
||||
}): string {
|
||||
const sections = [`## ${params.title}`];
|
||||
if (params.detail) {
|
||||
sections.push(`**Command:** \`${params.detail}\``);
|
||||
}
|
||||
const args = formatToolPayloadForSidebar(params.args);
|
||||
if (args !== null) {
|
||||
sections.push(`**Arguments**\n${args}`);
|
||||
}
|
||||
if (typeof params.output === "string") {
|
||||
sections.push(`**Output**\n${formatToolOutputForSidebar(params.output)}`);
|
||||
} else {
|
||||
sections.push("*No output - tool completed successfully.*");
|
||||
}
|
||||
return sections.join("\n\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a truncated preview of tool output text.
|
||||
* Truncates to first N lines or first N characters, whichever is shorter.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user