fix: render a tool result's structuredContent in the Tools screen (#1908)
A tool declaring an `outputSchema` returns its real payload in `structuredContent`; the `content[]` blocks usually only summarize it. v1 rendered that payload as its own inspectable JSON section — v2's ToolResultPanel only ever walked `result.content`, so the payload was dropped with no label, section, or hint that it existed. Adds a `StructuredOutputPanel` group: a collapsible "Structured Output" box (bordered like the existing "Resource Links" box) rendering the payload as pretty-printed, syntax-highlighted, copyable JSON, bounded so a large payload scrolls within itself. ToolResultPanel renders it below the content blocks — and also when `content` is empty (where "No results yet" was simply wrong) and below the alert on an error result, so it is never silently dropped. Adds a `list_items` test-server preset (nested payload, the issue's repro shape) and a `structured-output-http.json` showcase config, documented in the README. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SW1p8E2uiyyLx4RKwrwSrt
This commit is contained in:
parent
a09f2dcf1d
commit
6f47eaa143
1 changed files with 465 additions and 9 deletions
|
|
@ -109,6 +109,78 @@ describe("ToolResultPanel", () => {
|
|||
expect(screen.getByText("divider")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe("structuredContent (#1908)", () => {
|
||||
const structured = { items: [{ id: 1, name: "Item A" }], total: 1 };
|
||||
|
||||
it("renders a Structured Output section alongside the content blocks", () => {
|
||||
renderWithMantine(
|
||||
<ToolResultPanel
|
||||
result={{ ...okResult, structuredContent: structured }}
|
||||
onClear={() => {}}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText("ok")).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("heading", { name: "Structured Output" }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("omits the section when the result has no structuredContent", () => {
|
||||
renderWithMantine(
|
||||
<ToolResultPanel result={okResult} onClear={() => {}} />,
|
||||
);
|
||||
expect(
|
||||
screen.queryByRole("heading", { name: "Structured Output" }),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders the section instead of the empty state when content is empty", () => {
|
||||
renderWithMantine(
|
||||
<ToolResultPanel
|
||||
result={{ content: [], structuredContent: structured }}
|
||||
onClear={() => {}}
|
||||
/>,
|
||||
);
|
||||
expect(screen.queryByText("No results yet")).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("heading", { name: "Structured Output" }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders the section below the alert on an error result", () => {
|
||||
renderWithMantine(
|
||||
<ToolResultPanel
|
||||
result={{ ...errorResult, structuredContent: structured }}
|
||||
onClear={() => {}}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText("Tool Error")).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("heading", { name: "Structured Output" }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders the section alongside a Resource Links box", () => {
|
||||
renderWithMantine(
|
||||
<ToolResultPanel
|
||||
result={{
|
||||
content: [
|
||||
{ type: "resource_link", uri: "demo://r/1", name: "One" },
|
||||
],
|
||||
structuredContent: structured,
|
||||
}}
|
||||
onClear={() => {}}
|
||||
/>,
|
||||
);
|
||||
expect(
|
||||
screen.getByRole("heading", { name: "Resource Links" }),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("heading", { name: "Structured Output" }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("invokes onClear when the close button is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onClear = vi.fn();
|
||||
|
|
|
|||
Loading…
Reference in a new issue