{provider.displayName}
@@ -156,6 +244,58 @@ function ProviderCard({ onNamedLogin, provider }: { onNamedLogin: () => void; pr
);
}
+function DeleteCustomProviderDialog({
+ onDeleted,
+ onOpenChange,
+ provider,
+}: {
+ onDeleted: () => void;
+ onOpenChange: (open: boolean) => void;
+ provider: ProviderView | null;
+}) {
+ const [working, setWorking] = useState(false);
+ const [message, setMessage] = useState("");
+
+ async function deleteProviderDefinition() {
+ if (!provider) return;
+ setWorking(true);
+ setMessage("");
+ try {
+ await deleteCustomProvider(provider.name);
+ onDeleted();
+ onOpenChange(false);
+ } catch (error) {
+ setMessage(error instanceof Error ? error.message : "Provider could not be deleted.");
+ } finally {
+ setWorking(false);
+ }
+ }
+
+ return (
+
+ );
+}
+
export type NamedConnectionProvider = Pick
;
export function NamedConnectionDialog({
diff --git a/ui/src/components/ui/select.tsx b/ui/src/components/ui/select.tsx
new file mode 100644
index 00000000..65313a84
--- /dev/null
+++ b/ui/src/components/ui/select.tsx
@@ -0,0 +1,18 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+function Select({ className, ...props }: React.ComponentProps<"select">) {
+ return (
+
+ )
+}
+
+export { Select }
diff --git a/ui/src/lib/authsome-api.ts b/ui/src/lib/authsome-api.ts
index c01ec252..0ee32a18 100644
--- a/ui/src/lib/authsome-api.ts
+++ b/ui/src/lib/authsome-api.ts
@@ -8,6 +8,7 @@ export type DashboardStats = {
export type ProviderView = {
name: string;
displayName: string;
+ definition: ProviderResponse;
authType: "oauth2" | "api_key" | string;
authTypeLabel: string;
apiUrl: string;
@@ -134,17 +135,73 @@ export type ProviderResponse = {
display_name?: string;
logo?: string | null;
description?: string | null;
+ type?: "app" | "llm" | "mcp" | "browser" | string | null;
auth_type?: string;
+ flow?: string;
api_url?: string | string[] | null;
oauth?: {
+ authorization_url?: string;
+ token_url?: string;
+ revocation_url?: string | null;
+ device_authorization_url?: string | null;
+ device_token_request?: "oauth2_form" | "json";
+ scopes?: string[];
+ authorization_params?: Record;
+ pkce?: boolean;
+ supports_device_code?: boolean;
+ supports_dcr?: boolean;
base_url?: string | null;
+ authorization_method?: "body" | "basic";
} | null;
+ registration?: {
+ registration_endpoint?: string | null;
+ } | null;
+ api_key?: {
+ header_name?: string;
+ header_prefix?: string | null;
+ key_pattern?: string | null;
+ key_pattern_hint?: string | null;
+ } | null;
+ browser?: {
+ entry_url?: string;
+ domains?: string[];
+ auth_cookies?: string[];
+ validate_url?: string | null;
+ extra_headers?: Record;
+ ttl_hours?: number;
+ ttl_from_cookie?: string | null;
+ extract?: Array<{
+ cookie: string;
+ header: string;
+ prefix?: string;
+ }>;
+ } | null;
+ export?: Record | { env?: Record } | null;
metadata?: {
description?: string;
};
docs_url?: string | null;
};
+export type ProviderDefinitionPayload = {
+ schema_version?: number;
+ name: string;
+ display_name: string;
+ logo?: string | null;
+ description?: string | null;
+ type?: "app" | "llm" | "mcp" | "browser" | null;
+ auth_type: "oauth2" | "api_key" | "browser";
+ flow: "pkce" | "device_code" | "dcr_pkce" | "api_key" | "browser";
+ oauth?: NonNullable | null;
+ registration?: NonNullable | null;
+ api_key?: NonNullable | null;
+ browser?: NonNullable | null;
+ export?: ProviderResponse["export"];
+ docs_url?: string | null;
+ api_url?: string | string[] | null;
+ metadata?: Record;
+};
+
export type ProviderClientDetail = {
client_id: string | null;
client_secret: string | null;
@@ -378,6 +435,7 @@ function providerView(
return {
name: provider.name,
displayName,
+ definition: provider,
authType: provider.auth_type || "provider",
authTypeLabel: authTypeLabel(provider.auth_type),
apiUrl: providerApiUrl(provider),
@@ -626,6 +684,32 @@ export async function updateProviderConfiguration(
});
}
+export async function createCustomProvider(
+ definition: ProviderDefinitionPayload,
+): Promise<{ status: "ok"; provider: string }> {
+ return sendJson("/api/providers", {
+ method: "POST",
+ body: JSON.stringify({ definition }),
+ });
+}
+
+export async function updateCustomProvider(
+ provider: string,
+ definition: ProviderDefinitionPayload,
+): Promise<{ status: "ok"; provider: string }> {
+ return sendJson(`/api/providers/${encodeURIComponent(provider)}`, {
+ method: "PUT",
+ body: JSON.stringify({ definition }),
+ });
+}
+
+export async function deleteCustomProvider(provider: string): Promise<{ status: "ok"; provider: string }> {
+ return sendJson(`/api/providers/${encodeURIComponent(provider)}`, {
+ method: "DELETE",
+ body: "{}",
+ });
+}
+
export async function fetchConnectionDetail(
provider: string,
connection: string,