Skip to content

Commit 52490c6

Browse files
committed
Show tool run duration
1 parent f18775a commit 52490c6

3 files changed

Lines changed: 69 additions & 2 deletions

File tree

client/src/components/ToolResults.tsx

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,29 @@ interface ToolResultsProps {
1313
resourceContent: Record<string, string>;
1414
onReadResource?: (uri: string) => void;
1515
isPollingTask?: boolean;
16+
toolRunDurationMs?: number | null;
1617
}
1718

19+
const formatToolRunDuration = (durationMs: number): string => {
20+
const roundedMs = Math.max(0, Math.round(durationMs));
21+
if (roundedMs < 1000) {
22+
return `${roundedMs}ms`;
23+
}
24+
return `${(roundedMs / 1000).toFixed(2)}s`;
25+
};
26+
27+
const ToolRunDuration = ({ durationMs }: { durationMs?: number | null }) => {
28+
if (typeof durationMs !== "number" || !Number.isFinite(durationMs)) {
29+
return null;
30+
}
31+
32+
return (
33+
<span className="ml-2 text-xs font-medium text-muted-foreground">
34+
Took {formatToolRunDuration(durationMs)}
35+
</span>
36+
);
37+
};
38+
1839
const checkContentCompatibility = (
1940
structuredContent: unknown,
2041
unstructuredContent: Array<{
@@ -65,6 +86,7 @@ const ToolResults = ({
6586
resourceContent,
6687
onReadResource,
6788
isPollingTask,
89+
toolRunDurationMs,
6890
}: ToolResultsProps) => {
6991
if (!toolResult) return null;
7092

@@ -73,7 +95,10 @@ const ToolResults = ({
7395
if (!parsedResult.success) {
7496
return (
7597
<>
76-
<h4 className="font-semibold mb-2">Invalid Tool Result:</h4>
98+
<h4 className="font-semibold mb-2">
99+
Invalid Tool Result:
100+
<ToolRunDuration durationMs={toolRunDurationMs} />
101+
</h4>
77102
<JsonView data={toolResult} />
78103
<h4 className="font-semibold mb-2">Errors:</h4>
79104
{parsedResult.error.issues.map((issue, idx) => (
@@ -141,6 +166,7 @@ const ToolResults = ({
141166
) : (
142167
<span className="text-green-600 font-semibold">Success</span>
143168
)}
169+
<ToolRunDuration durationMs={toolRunDurationMs} />
144170
</h4>
145171
{structuredResult.structuredContent && (
146172
<div className="mb-4">
@@ -240,7 +266,10 @@ const ToolResults = ({
240266
} else if ("toolResult" in toolResult) {
241267
return (
242268
<>
243-
<h4 className="font-semibold mb-2">Tool Result (Legacy):</h4>
269+
<h4 className="font-semibold mb-2">
270+
Tool Result (Legacy):
271+
<ToolRunDuration durationMs={toolRunDurationMs} />
272+
</h4>
244273
<JsonView data={toolResult.toolResult} />
245274
</>
246275
);

client/src/components/ToolsTab.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,9 @@ const ToolsTab = ({
201201
const [params, setParams] = useState<Record<string, unknown>>({});
202202
const [runAsTask, setRunAsTask] = useState(false);
203203
const [isToolRunning, setIsToolRunning] = useState(false);
204+
const [toolRunDurationMs, setToolRunDurationMs] = useState<number | null>(
205+
null,
206+
);
204207
const [isOutputSchemaExpanded, setIsOutputSchemaExpanded] = useState(false);
205208
const [isMetadataExpanded, setIsMetadataExpanded] = useState(false);
206209
const [metadataEntries, setMetadataEntries] = useState<
@@ -248,6 +251,7 @@ const ToolsTab = ({
248251

249252
// Reset validation errors when switching tools
250253
setHasValidationErrors(false);
254+
setToolRunDurationMs(null);
251255

252256
// Clear form refs for the previous tool
253257
formRefs.current = {};
@@ -808,8 +812,10 @@ const ToolsTab = ({
808812
// Validate JSON inputs before calling tool
809813
if (checkValidationErrors(true)) return;
810814

815+
const runStartedAt = Date.now();
811816
try {
812817
setIsToolRunning(true);
818+
setToolRunDurationMs(null);
813819
const metadata = metadataEntries.reduce<
814820
Record<string, unknown>
815821
>((acc, { key, value }) => {
@@ -831,6 +837,7 @@ const ToolsTab = ({
831837
runAsTask,
832838
);
833839
} finally {
840+
setToolRunDurationMs(Date.now() - runStartedAt);
834841
setIsToolRunning(false);
835842
}
836843
}}
@@ -886,6 +893,7 @@ const ToolsTab = ({
886893
resourceContent={resourceContent}
887894
onReadResource={onReadResource}
888895
isPollingTask={isPollingTask}
896+
toolRunDurationMs={toolRunDurationMs}
889897
/>
890898
</div>
891899
) : (

client/src/components/__tests__/ToolsTab.test.tsx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,36 @@ describe("ToolsTab", () => {
423423
expect(submitButton.getAttribute("disabled")).toBeNull();
424424
});
425425

426+
it("should show how long the last tool run took", async () => {
427+
let currentTime = 1000;
428+
const nowSpy = jest
429+
.spyOn(Date, "now")
430+
.mockImplementation(() => currentTime);
431+
const mockCallTool = jest.fn(async () => {
432+
currentTime = 6240;
433+
return { content: [] };
434+
});
435+
436+
try {
437+
renderToolsTab({
438+
selectedTool: mockTools[0],
439+
callTool: mockCallTool,
440+
toolResult: { content: [] },
441+
});
442+
443+
expect(screen.queryByText(/Took /)).not.toBeInTheDocument();
444+
445+
const submitButton = screen.getByRole("button", { name: /run tool/i });
446+
await act(async () => {
447+
fireEvent.click(submitButton);
448+
});
449+
450+
expect(screen.getByText("Took 5.24s")).toBeInTheDocument();
451+
} finally {
452+
nowSpy.mockRestore();
453+
}
454+
});
455+
426456
describe("Output Schema Display", () => {
427457
const toolWithOutputSchema: Tool = {
428458
name: "weatherTool",

0 commit comments

Comments
 (0)