diff --git a/client/src/components/ToolResults.tsx b/client/src/components/ToolResults.tsx index d036298b9..106312852 100644 --- a/client/src/components/ToolResults.tsx +++ b/client/src/components/ToolResults.tsx @@ -13,8 +13,29 @@ interface ToolResultsProps { resourceContent: Record; onReadResource?: (uri: string) => void; isPollingTask?: boolean; + toolRunDurationMs?: number | null; } +const formatToolRunDuration = (durationMs: number): string => { + const roundedMs = Math.max(0, Math.round(durationMs)); + if (roundedMs < 1000) { + return `${roundedMs}ms`; + } + return `${(roundedMs / 1000).toFixed(2)}s`; +}; + +const ToolRunDuration = ({ durationMs }: { durationMs?: number | null }) => { + if (typeof durationMs !== "number" || !Number.isFinite(durationMs)) { + return null; + } + + return ( + + Took {formatToolRunDuration(durationMs)} + + ); +}; + const checkContentCompatibility = ( structuredContent: unknown, unstructuredContent: Array<{ @@ -65,6 +86,7 @@ const ToolResults = ({ resourceContent, onReadResource, isPollingTask, + toolRunDurationMs, }: ToolResultsProps) => { if (!toolResult) return null; @@ -73,7 +95,10 @@ const ToolResults = ({ if (!parsedResult.success) { return ( <> -

Invalid Tool Result:

+

+ Invalid Tool Result: + +

Errors:

{parsedResult.error.issues.map((issue, idx) => ( @@ -141,6 +166,7 @@ const ToolResults = ({ ) : ( Success )} + {structuredResult.structuredContent && (
@@ -240,7 +266,10 @@ const ToolResults = ({ } else if ("toolResult" in toolResult) { return ( <> -

Tool Result (Legacy):

+

+ Tool Result (Legacy): + +

); diff --git a/client/src/components/ToolsTab.tsx b/client/src/components/ToolsTab.tsx index febea1d8f..02327cd1e 100644 --- a/client/src/components/ToolsTab.tsx +++ b/client/src/components/ToolsTab.tsx @@ -201,6 +201,9 @@ const ToolsTab = ({ const [params, setParams] = useState>({}); const [runAsTask, setRunAsTask] = useState(false); const [isToolRunning, setIsToolRunning] = useState(false); + const [toolRunDurationMs, setToolRunDurationMs] = useState( + null, + ); const [isOutputSchemaExpanded, setIsOutputSchemaExpanded] = useState(false); const [isMetadataExpanded, setIsMetadataExpanded] = useState(false); const [metadataEntries, setMetadataEntries] = useState< @@ -248,6 +251,7 @@ const ToolsTab = ({ // Reset validation errors when switching tools setHasValidationErrors(false); + setToolRunDurationMs(null); // Clear form refs for the previous tool formRefs.current = {}; @@ -808,8 +812,10 @@ const ToolsTab = ({ // Validate JSON inputs before calling tool if (checkValidationErrors(true)) return; + const runStartedAt = Date.now(); try { setIsToolRunning(true); + setToolRunDurationMs(null); const metadata = metadataEntries.reduce< Record >((acc, { key, value }) => { @@ -831,6 +837,7 @@ const ToolsTab = ({ runAsTask, ); } finally { + setToolRunDurationMs(Date.now() - runStartedAt); setIsToolRunning(false); } }} @@ -886,6 +893,7 @@ const ToolsTab = ({ resourceContent={resourceContent} onReadResource={onReadResource} isPollingTask={isPollingTask} + toolRunDurationMs={toolRunDurationMs} />
) : ( diff --git a/client/src/components/__tests__/ToolsTab.test.tsx b/client/src/components/__tests__/ToolsTab.test.tsx index 5678914d6..fc6d66c49 100644 --- a/client/src/components/__tests__/ToolsTab.test.tsx +++ b/client/src/components/__tests__/ToolsTab.test.tsx @@ -423,6 +423,36 @@ describe("ToolsTab", () => { expect(submitButton.getAttribute("disabled")).toBeNull(); }); + it("should show how long the last tool run took", async () => { + let currentTime = 1000; + const nowSpy = jest + .spyOn(Date, "now") + .mockImplementation(() => currentTime); + const mockCallTool = jest.fn(async () => { + currentTime = 6240; + return { content: [] }; + }); + + try { + renderToolsTab({ + selectedTool: mockTools[0], + callTool: mockCallTool, + toolResult: { content: [] }, + }); + + expect(screen.queryByText(/Took /)).not.toBeInTheDocument(); + + const submitButton = screen.getByRole("button", { name: /run tool/i }); + await act(async () => { + fireEvent.click(submitButton); + }); + + expect(screen.getByText("Took 5.24s")).toBeInTheDocument(); + } finally { + nowSpy.mockRestore(); + } + }); + describe("Output Schema Display", () => { const toolWithOutputSchema: Tool = { name: "weatherTool",