fix: support object and array parameters in custom HTTP tools (#373)

* fix: support object and array parameters in custom HTTP tools

* feat(ui): expose object and array types in the custom tool parameter editor

* fix: error handling and schema generation

---------

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Abhishek Kumar <abhishek@a6k.me>
This commit is contained in:
Matt Van Horn 2026-06-01 23:05:38 -07:00 committed by GitHub
parent 98d2b24cba
commit dd85c4a1b4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 308 additions and 67 deletions

View file

@ -2,6 +2,7 @@
import { PlusIcon, Trash2Icon } from "lucide-react";
import type { ToolParameter as ApiToolParameter } from "@/client/types.gen";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
@ -14,7 +15,7 @@ import {
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
export type ParameterType = "string" | "number" | "boolean";
export type ParameterType = ApiToolParameter["type"];
export interface ToolParameter {
name: string;
@ -124,6 +125,8 @@ export function ParameterEditor({
<SelectItem value="string">String</SelectItem>
<SelectItem value="number">Number</SelectItem>
<SelectItem value="boolean">Boolean</SelectItem>
<SelectItem value="object">Object</SelectItem>
<SelectItem value="array">Array</SelectItem>
</SelectContent>
</Select>
</div>
@ -267,6 +270,8 @@ export function PresetParameterEditor({
<SelectItem value="string">String</SelectItem>
<SelectItem value="number">Number</SelectItem>
<SelectItem value="boolean">Boolean</SelectItem>
<SelectItem value="object">Object</SelectItem>
<SelectItem value="array">Array</SelectItem>
</SelectContent>
</Select>
</div>

View file

@ -36,6 +36,7 @@ import {
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { Textarea } from "@/components/ui/textarea";
import { detailFromError } from "@/lib/apiError";
import { useAuth } from "@/lib/auth";
interface ConfigFormDialogProps {
@ -132,7 +133,7 @@ export function ConfigFormDialog({
body: { name: name || undefined, config: configPayload },
},
);
if (res.error) throw new Error(detailFromError(res.error));
if (res.error) throw new Error(detailFromError(res.error, "Failed to save configuration"));
toast.success("Configuration updated");
} else {
const res = await createTelephonyConfigurationApiV1OrganizationsTelephonyConfigsPost(
@ -145,7 +146,7 @@ export function ConfigFormDialog({
},
},
);
if (res.error) throw new Error(detailFromError(res.error));
if (res.error) throw new Error(detailFromError(res.error, "Failed to save configuration"));
toast.success("Configuration created");
}
onOpenChange(false);
@ -345,16 +346,3 @@ function FieldInput({ field, value, onChange, isEdit }: FieldInputProps) {
/>
);
}
// FastAPI error responses come back as { detail: string } or
// { detail: [{loc, msg, ...}] }. Surface a useful message either way.
function detailFromError(err: unknown): string {
if (typeof err === "string") return err;
const e = err as { detail?: unknown };
if (typeof e?.detail === "string") return e.detail;
if (Array.isArray(e?.detail) && e.detail.length > 0) {
const first = e.detail[0] as { msg?: string };
if (first?.msg) return first.msg;
}
return "Failed to save configuration";
}

View file

@ -28,6 +28,7 @@ import {
SelectValue,
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { detailFromError } from "@/lib/apiError";
import { useAuth } from "@/lib/auth";
interface PhoneNumberDialogProps {
@ -146,7 +147,7 @@ export function PhoneNumberDialog({
},
},
);
if (res.error) throw new Error(detailFromError(res.error));
if (res.error) throw new Error(detailFromError(res.error, "Failed to save phone number"));
providerSync = res.data?.provider_sync;
toast.success("Phone number updated");
} else {
@ -164,7 +165,7 @@ export function PhoneNumberDialog({
},
},
);
if (res.error) throw new Error(detailFromError(res.error));
if (res.error) throw new Error(detailFromError(res.error, "Failed to save phone number"));
providerSync = res.data?.provider_sync;
toast.success("Phone number added");
}
@ -302,14 +303,3 @@ export function PhoneNumberDialog({
</Dialog>
);
}
function detailFromError(err: unknown): string {
if (typeof err === "string") return err;
const e = err as { detail?: unknown };
if (typeof e?.detail === "string") return e.detail;
if (Array.isArray(e?.detail) && e.detail.length > 0) {
const first = e.detail[0] as { msg?: string };
if (first?.msg) return first.msg;
}
return "Failed to save phone number";
}