Skip to content

Commit ccb68b9

Browse files
authored
Merge pull request #850 from gregsaab/greg/add_null_support_to_tools
Add support for specifying `null` values for tool fields
2 parents f013291 + 0ae2b7e commit ccb68b9

File tree

5 files changed

+250
-167
lines changed

5 files changed

+250
-167
lines changed

client/src/components/ToolsTab.tsx

Lines changed: 189 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -160,184 +160,214 @@ const ToolsTab = ({
160160
const required = isPropertyRequired(key, inputSchema);
161161
return (
162162
<div key={key}>
163-
<Label
164-
htmlFor={key}
165-
className="block text-sm font-medium text-gray-700 dark:text-gray-300"
166-
>
167-
{key}
168-
{required && (
169-
<span className="text-red-500 ml-1">*</span>
170-
)}
171-
</Label>
172-
{prop.type === "boolean" ? (
173-
<div className="flex items-center space-x-2 mt-2">
174-
<Checkbox
175-
id={key}
176-
name={key}
177-
checked={!!params[key]}
178-
onCheckedChange={(checked: boolean) =>
179-
setParams({
180-
...params,
181-
[key]: checked,
182-
})
183-
}
184-
/>
185-
<label
186-
htmlFor={key}
187-
className="text-sm font-medium text-gray-700 dark:text-gray-300"
188-
>
189-
{prop.description || "Toggle this option"}
190-
</label>
191-
</div>
192-
) : prop.type === "string" && prop.enum ? (
193-
<Select
194-
value={
195-
params[key] === undefined
196-
? ""
197-
: String(params[key])
198-
}
199-
onValueChange={(value) => {
200-
if (value === "") {
201-
setParams({
202-
...params,
203-
[key]: undefined,
204-
});
205-
} else {
206-
setParams({
207-
...params,
208-
[key]: value,
209-
});
210-
}
211-
}}
163+
<div className="flex justify-between">
164+
<Label
165+
htmlFor={key}
166+
className="block text-sm font-medium text-gray-700 dark:text-gray-300"
212167
>
213-
<SelectTrigger id={key} className="mt-1">
214-
<SelectValue
215-
placeholder={
216-
prop.description || "Select an option"
168+
{key}
169+
{required && (
170+
<span className="text-red-500 ml-1">*</span>
171+
)}
172+
</Label>
173+
{prop.nullable ? (
174+
<div className="flex items-center space-x-2">
175+
<Checkbox
176+
id={key}
177+
name={key}
178+
checked={params[key] === null}
179+
onCheckedChange={(checked: boolean) =>
180+
setParams({
181+
...params,
182+
[key]: checked ? null : prop.default,
183+
})
184+
}
185+
/>
186+
<label
187+
htmlFor={key}
188+
className="text-sm font-medium text-gray-700 dark:text-gray-300"
189+
>
190+
null
191+
</label>
192+
</div>
193+
) : null}
194+
</div>
195+
196+
<div
197+
role="toolinputwrapper"
198+
className={`${prop.nullable && params[key] === null ? "pointer-events-none opacity-50" : ""}`}
199+
>
200+
{prop.type === "boolean" ? (
201+
<div className="flex items-center space-x-2 mt-2">
202+
<Checkbox
203+
id={key}
204+
name={key}
205+
checked={!!params[key]}
206+
onCheckedChange={(checked: boolean) =>
207+
setParams({
208+
...params,
209+
[key]: checked,
210+
})
217211
}
218212
/>
219-
</SelectTrigger>
220-
<SelectContent>
221-
{prop.enum.map((option) => (
222-
<SelectItem key={option} value={option}>
223-
{option}
224-
</SelectItem>
225-
))}
226-
</SelectContent>
227-
</Select>
228-
) : prop.type === "string" ? (
229-
<Textarea
230-
id={key}
231-
name={key}
232-
placeholder={prop.description}
233-
value={
234-
params[key] === undefined
235-
? ""
236-
: String(params[key])
237-
}
238-
onChange={(e) => {
239-
const value = e.target.value;
240-
if (value === "") {
241-
// Field cleared - set to undefined
242-
setParams({
243-
...params,
244-
[key]: undefined,
245-
});
246-
} else {
247-
// Field has value - keep as string
248-
setParams({
249-
...params,
250-
[key]: value,
251-
});
213+
<label
214+
htmlFor={key}
215+
className="text-sm font-medium text-gray-700 dark:text-gray-300"
216+
>
217+
{prop.description || "Toggle this option"}
218+
</label>
219+
</div>
220+
) : prop.type === "string" && prop.enum ? (
221+
<Select
222+
value={
223+
params[key] === undefined
224+
? ""
225+
: String(params[key])
252226
}
253-
}}
254-
className="mt-1"
255-
/>
256-
) : prop.type === "object" || prop.type === "array" ? (
257-
<div className="mt-1">
258-
<DynamicJsonForm
259-
ref={(ref) => (formRefs.current[key] = ref)}
260-
schema={{
261-
type: prop.type,
262-
properties: prop.properties,
263-
description: prop.description,
264-
items: prop.items,
227+
onValueChange={(value) => {
228+
if (value === "") {
229+
setParams({
230+
...params,
231+
[key]: undefined,
232+
});
233+
} else {
234+
setParams({
235+
...params,
236+
[key]: value,
237+
});
238+
}
265239
}}
240+
>
241+
<SelectTrigger id={key} className="mt-1">
242+
<SelectValue
243+
placeholder={
244+
prop.description || "Select an option"
245+
}
246+
/>
247+
</SelectTrigger>
248+
<SelectContent>
249+
{prop.enum.map((option) => (
250+
<SelectItem key={option} value={option}>
251+
{option}
252+
</SelectItem>
253+
))}
254+
</SelectContent>
255+
</Select>
256+
) : prop.type === "string" ? (
257+
<Textarea
258+
id={key}
259+
name={key}
260+
placeholder={prop.description}
266261
value={
267-
(params[key] as JsonValue) ??
268-
generateDefaultValue(prop)
262+
params[key] === undefined
263+
? ""
264+
: String(params[key])
269265
}
270-
onChange={(newValue: JsonValue) => {
271-
setParams({
272-
...params,
273-
[key]: newValue,
274-
});
275-
// Check validation after a short delay to allow form to update
276-
setTimeout(checkValidationErrors, 100);
277-
}}
278-
/>
279-
</div>
280-
) : prop.type === "number" ||
281-
prop.type === "integer" ? (
282-
<Input
283-
type="number"
284-
id={key}
285-
name={key}
286-
placeholder={prop.description}
287-
value={
288-
params[key] === undefined
289-
? ""
290-
: String(params[key])
291-
}
292-
onChange={(e) => {
293-
const value = e.target.value;
294-
if (value === "") {
295-
// Field cleared - set to undefined
296-
setParams({
297-
...params,
298-
[key]: undefined,
299-
});
300-
} else {
301-
// Field has value - try to convert to number, but store input either way
302-
const num = Number(value);
303-
if (!isNaN(num)) {
266+
onChange={(e) => {
267+
const value = e.target.value;
268+
if (value === "") {
269+
// Field cleared - set to undefined
304270
setParams({
305271
...params,
306-
[key]: num,
272+
[key]: undefined,
307273
});
308274
} else {
309-
// Store invalid input as string - let server validate
275+
// Field has value - keep as string
310276
setParams({
311277
...params,
312278
[key]: value,
313279
});
314280
}
315-
}
316-
}}
317-
className="mt-1"
318-
/>
319-
) : (
320-
<div className="mt-1">
321-
<DynamicJsonForm
322-
ref={(ref) => (formRefs.current[key] = ref)}
323-
schema={{
324-
type: prop.type,
325-
properties: prop.properties,
326-
description: prop.description,
327-
items: prop.items,
328281
}}
329-
value={params[key] as JsonValue}
330-
onChange={(newValue: JsonValue) => {
331-
setParams({
332-
...params,
333-
[key]: newValue,
334-
});
335-
// Check validation after a short delay to allow form to update
336-
setTimeout(checkValidationErrors, 100);
282+
className="mt-1"
283+
/>
284+
) : prop.type === "object" ||
285+
prop.type === "array" ? (
286+
<div className="mt-1">
287+
<DynamicJsonForm
288+
ref={(ref) => (formRefs.current[key] = ref)}
289+
schema={{
290+
type: prop.type,
291+
properties: prop.properties,
292+
description: prop.description,
293+
items: prop.items,
294+
}}
295+
value={
296+
(params[key] as JsonValue) ??
297+
generateDefaultValue(prop)
298+
}
299+
onChange={(newValue: JsonValue) => {
300+
setParams({
301+
...params,
302+
[key]: newValue,
303+
});
304+
// Check validation after a short delay to allow form to update
305+
setTimeout(checkValidationErrors, 100);
306+
}}
307+
/>
308+
</div>
309+
) : prop.type === "number" ||
310+
prop.type === "integer" ? (
311+
<Input
312+
type="number"
313+
id={key}
314+
name={key}
315+
placeholder={prop.description}
316+
value={
317+
params[key] === undefined
318+
? ""
319+
: String(params[key])
320+
}
321+
onChange={(e) => {
322+
const value = e.target.value;
323+
if (value === "") {
324+
// Field cleared - set to undefined
325+
setParams({
326+
...params,
327+
[key]: undefined,
328+
});
329+
} else {
330+
// Field has value - try to convert to number, but store input either way
331+
const num = Number(value);
332+
if (!isNaN(num)) {
333+
setParams({
334+
...params,
335+
[key]: num,
336+
});
337+
} else {
338+
// Store invalid input as string - let server validate
339+
setParams({
340+
...params,
341+
[key]: value,
342+
});
343+
}
344+
}
337345
}}
346+
className="mt-1"
338347
/>
339-
</div>
340-
)}
348+
) : (
349+
<div className="mt-1">
350+
<DynamicJsonForm
351+
ref={(ref) => (formRefs.current[key] = ref)}
352+
schema={{
353+
type: prop.type,
354+
properties: prop.properties,
355+
description: prop.description,
356+
items: prop.items,
357+
}}
358+
value={params[key] as JsonValue}
359+
onChange={(newValue: JsonValue) => {
360+
setParams({
361+
...params,
362+
[key]: newValue,
363+
});
364+
// Check validation after a short delay to allow form to update
365+
setTimeout(checkValidationErrors, 100);
366+
}}
367+
/>
368+
</div>
369+
)}
370+
</div>
341371
</div>
342372
);
343373
},

0 commit comments

Comments
 (0)