Skip to content

Commit 96b1b58

Browse files
committed
Manage display of "Run as Task" checkbox on Tools Tab
* In ToolsTab.tsx - define ExtendedTool interface for augmenting with execution, meta, icons - in hasMeta function, - cast tool as ExtendedTool - add getTaskSupport function - returns forbidden if input is not a tool - returns the setting of execution.taskSupport if set and valid - returns optional otherwise - in useEffect map callback - set runAsTask to true if getTaskSupport returns "required", else false - in IconDisplay instances cast tool as ExtendedTool instead of as WithIcons - hide "Run as Task" checkbox if getTaskSupport returns "forbidden" for the selected tool - disable the "Run as Task" checkbox if getTaskSupport returns "required" * In ToolsTab.test.tsx - added unit test - "should show/hide/disable run-as-task checkbox based on taskSupport" - tests checkbox checked and disabled states for "forbidden", "required", and "optional" states
1 parent ce092a3 commit 96b1b58

2 files changed

Lines changed: 109 additions & 21 deletions

File tree

client/src/components/ToolsTab.tsx

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,40 @@ import {
5050
hasValidMetaName,
5151
hasValidMetaPrefix,
5252
isReservedMetaKey,
53-
} from "@/utils/metaUtils";
53+
} from "../utils/metaUtils";
54+
55+
/**
56+
* Extended Tool type that includes optional fields used by the inspector.
57+
*/
58+
interface ExtendedTool extends Tool, WithIcons {
59+
_meta?: Record<string, unknown>;
60+
execution?: {
61+
taskSupport?: "forbidden" | "required" | "optional";
62+
};
63+
}
5464

5565
// Type guard to safely detect the optional _meta field without using `any`
56-
const hasMeta = (tool: Tool): tool is Tool & { _meta: unknown } =>
57-
typeof (tool as { _meta?: unknown })._meta !== "undefined";
66+
const hasMeta = (
67+
tool: Tool,
68+
): tool is ExtendedTool & { _meta: Record<string, unknown> } =>
69+
typeof (tool as ExtendedTool)._meta !== "undefined";
70+
71+
// Type guard to detect execution.taskSupport
72+
const getTaskSupport = (
73+
tool: Tool | null,
74+
): "forbidden" | "required" | "optional" => {
75+
if (!tool) return "forbidden";
76+
const extendedTool = tool as ExtendedTool;
77+
const taskSupport = extendedTool.execution?.taskSupport;
78+
if (
79+
taskSupport === "forbidden" ||
80+
taskSupport === "required" ||
81+
taskSupport === "optional"
82+
) {
83+
return taskSupport;
84+
}
85+
return "optional";
86+
};
5887

5988
const ToolsTab = ({
6089
tools,
@@ -131,7 +160,8 @@ const ToolsTab = ({
131160
];
132161
});
133162
setParams(Object.fromEntries(params));
134-
setRunAsTask(false);
163+
const taskSupport = getTaskSupport(selectedTool);
164+
setRunAsTask(taskSupport === "required");
135165

136166
// Reset validation errors when switching tools
137167
setHasValidationErrors(false);
@@ -170,7 +200,7 @@ const ToolsTab = ({
170200
renderItem={(tool) => (
171201
<div className="flex items-start w-full gap-2">
172202
<div className="flex-shrink-0 mt-1">
173-
<IconDisplay icons={(tool as WithIcons).icons} size="sm" />
203+
<IconDisplay icons={(tool as ExtendedTool).icons} size="sm" />
174204
</div>
175205
<div className="flex flex-col flex-1 min-w-0">
176206
<span className="truncate">{tool.name}</span>
@@ -191,7 +221,7 @@ const ToolsTab = ({
191221
<div className="flex items-center gap-2">
192222
{selectedTool && (
193223
<IconDisplay
194-
icons={(selectedTool as WithIcons).icons}
224+
icons={(selectedTool as ExtendedTool).icons}
195225
size="md"
196226
/>
197227
)}
@@ -659,21 +689,24 @@ const ToolsTab = ({
659689
</div>
660690
</div>
661691
)}
662-
<div className="flex items-center space-x-2">
663-
<Checkbox
664-
id="run-as-task"
665-
checked={runAsTask}
666-
onCheckedChange={(checked: boolean) =>
667-
setRunAsTask(checked)
668-
}
669-
/>
670-
<Label
671-
htmlFor="run-as-task"
672-
className="text-sm font-medium text-gray-700 dark:text-gray-300 cursor-pointer"
673-
>
674-
Run as task
675-
</Label>
676-
</div>
692+
{getTaskSupport(selectedTool) !== "forbidden" && (
693+
<div className="flex items-center space-x-2">
694+
<Checkbox
695+
id="run-as-task"
696+
checked={runAsTask}
697+
onCheckedChange={(checked: boolean) =>
698+
setRunAsTask(checked)
699+
}
700+
disabled={getTaskSupport(selectedTool) === "required"}
701+
/>
702+
<Label
703+
htmlFor="run-as-task"
704+
className="text-sm font-medium text-gray-700 dark:text-gray-300 cursor-pointer"
705+
>
706+
Run as task
707+
</Label>
708+
</div>
709+
)}
677710
<Button
678711
onClick={async () => {
679712
// Validate JSON inputs before calling tool

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

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ import {
1212
RESERVED_NAMESPACE_MESSAGE,
1313
} from "../../utils/metaUtils";
1414

15+
interface ExtendedTool extends Tool {
16+
_meta?: Record<string, unknown>;
17+
execution?: {
18+
taskSupport?: "forbidden" | "required" | "optional";
19+
};
20+
}
21+
1522
describe("ToolsTab", () => {
1623
beforeEach(() => {
1724
// Clear the output schema cache before each test
@@ -107,6 +114,54 @@ describe("ToolsTab", () => {
107114
expect(newInput.value).toBe("");
108115
});
109116

117+
it("should show/hide/disable run-as-task checkbox based on taskSupport", async () => {
118+
const forbiddenTool: ExtendedTool = {
119+
...mockTools[0],
120+
name: "forbiddenTool",
121+
execution: { taskSupport: "forbidden" },
122+
};
123+
const requiredTool: ExtendedTool = {
124+
...mockTools[0],
125+
name: "requiredTool",
126+
execution: { taskSupport: "required" },
127+
};
128+
const optionalTool: ExtendedTool = {
129+
...mockTools[0],
130+
name: "optionalTool",
131+
execution: { taskSupport: "optional" },
132+
};
133+
134+
const { rerender } = renderToolsTab({
135+
selectedTool: forbiddenTool,
136+
});
137+
138+
expect(screen.queryByLabelText(/run as task/i)).not.toBeInTheDocument();
139+
140+
rerender(
141+
<Tabs defaultValue="tools">
142+
<ToolsTab {...defaultProps} selectedTool={optionalTool} />
143+
</Tabs>,
144+
);
145+
const optionalCheckbox = screen.getByLabelText(
146+
/run as task/i,
147+
) as HTMLInputElement;
148+
expect(optionalCheckbox).toBeInTheDocument();
149+
expect(optionalCheckbox.getAttribute("aria-checked")).toBe("false");
150+
expect(optionalCheckbox).not.toBeDisabled();
151+
152+
rerender(
153+
<Tabs defaultValue="tools">
154+
<ToolsTab {...defaultProps} selectedTool={requiredTool} />
155+
</Tabs>,
156+
);
157+
const requiredCheckbox = screen.getByLabelText(
158+
/run as task/i,
159+
) as HTMLInputElement;
160+
expect(requiredCheckbox).toBeInTheDocument();
161+
expect(requiredCheckbox.getAttribute("aria-checked")).toBe("true");
162+
expect(requiredCheckbox).toBeDisabled();
163+
});
164+
110165
it("should handle integer type inputs", async () => {
111166
renderToolsTab({
112167
selectedTool: mockTools[1], // Use the tool with integer type

0 commit comments

Comments
 (0)