From dcb56a07d9a9f0d61423e57e44ef340b8b54ff8e Mon Sep 17 00:00:00 2001 From: Minwook Shin <163576506+minwookshin@users.noreply.github.com> Date: Wed, 16 Sep 2026 22:26:16 -0400 Subject: [PATCH] fix(tool): handle undefined input while streaming --- .../content/components/(chatbot)/tool.mdx | 2 +- packages/elements/__tests__/tool.test.tsx | 68 +++++++++++++++++++ packages/elements/src/tool.tsx | 24 ++++--- packages/examples/src/tool.tsx | 2 +- 4 files changed, 85 insertions(+), 11 deletions(-) diff --git a/apps/docs/content/components/(chatbot)/tool.mdx b/apps/docs/content/components/(chatbot)/tool.mdx index a56f31c4..27fca941 100644 --- a/apps/docs/content/components/(chatbot)/tool.mdx +++ b/apps/docs/content/components/(chatbot)/tool.mdx @@ -277,7 +277,7 @@ Shows a tool that encountered an error during execution. Opens by default to dis type={{ input: { description: - "The input parameters passed to the tool, displayed as formatted JSON.", + "The input parameters passed to the tool, displayed as formatted JSON. Renders nothing when input is undefined, such as before parameters arrive during input-streaming.", type: 'ToolUIPart["input"]', }, "...props": { diff --git a/packages/elements/__tests__/tool.test.tsx b/packages/elements/__tests__/tool.test.tsx index a7225762..e47a6dbf 100644 --- a/packages/elements/__tests__/tool.test.tsx +++ b/packages/elements/__tests__/tool.test.tsx @@ -10,6 +10,19 @@ import { const QUERY_REGEX = /"query"/; +const renderTool = ( + input: unknown, + state: "input-streaming" | "input-available" +) => ( + + + + + + + +); + describe("tool", () => { it("renders children", () => { render(Content); @@ -182,6 +195,61 @@ describe("toolContent", () => { }); describe("toolInput", () => { + it("renders nothing when input is undefined", () => { + const { container } = render(); + + expect(container).toBeEmptyDOMElement(); + }); + + it("keeps the tool visible as input starts streaming and becomes available", () => { + const { container, rerender } = render( + renderTool(undefined, "input-streaming") + ); + + expect( + screen.getByRole("button", { name: "searchPending" }) + ).toBeInTheDocument(); + expect(screen.queryByText("Parameters")).not.toBeInTheDocument(); + + rerender(renderTool({ query: "test" }, "input-streaming")); + + expect(container.querySelector("code")).toHaveTextContent('"test"'); + + rerender(renderTool({ query: "test search" }, "input-available")); + + expect( + screen.getByRole("button", { name: "searchRunning" }) + ).toBeInTheDocument(); + expect(container.querySelector("code")).toHaveTextContent('"test search"'); + }); + + it("removes previous parameters when a new input starts streaming", () => { + const { container, rerender } = render( + renderTool({ query: "test search" }, "input-available") + ); + + rerender(renderTool(undefined, "input-streaming")); + + expect( + screen.getByRole("button", { name: "searchPending" }) + ).toBeInTheDocument(); + expect(screen.queryByText("Parameters")).not.toBeInTheDocument(); + expect(container.querySelector("code")).not.toBeInTheDocument(); + }); + + it.each([ + { input: null, json: "null" }, + { input: false, json: "false" }, + { input: 0, json: "0" }, + { input: "", json: '""' }, + { input: {}, json: "{}" }, + ])("renders defined input $json", ({ input, json }) => { + const { container } = render(); + + expect(screen.getByText("Parameters")).toBeInTheDocument(); + expect(container.querySelector("code")).toHaveTextContent(json); + }); + it("renders input parameters", async () => { const input = { query: "test search" }; render( diff --git a/packages/elements/src/tool.tsx b/packages/elements/src/tool.tsx index 9a22010e..3474453c 100644 --- a/packages/elements/src/tool.tsx +++ b/packages/elements/src/tool.tsx @@ -116,16 +116,22 @@ export type ToolInputProps = ComponentProps<"div"> & { input: ToolPart["input"]; }; -export const ToolInput = ({ className, input, ...props }: ToolInputProps) => ( -
-

- Parameters -

-
- +export const ToolInput = ({ className, input, ...props }: ToolInputProps) => { + if (input === undefined) { + return null; + } + + return ( +
+

+ Parameters +

+
+ +
-
-); + ); +}; export type ToolOutputProps = ComponentProps<"div"> & { output: ToolPart["output"]; diff --git a/packages/examples/src/tool.tsx b/packages/examples/src/tool.tsx index d24d71aa..21323db1 100644 --- a/packages/examples/src/tool.tsx +++ b/packages/examples/src/tool.tsx @@ -57,7 +57,7 @@ const Example = () => ( type="tool-database_query" /> - +