diff --git a/litellm/proxy/public_endpoints/provider_create_fields.json b/litellm/proxy/public_endpoints/provider_create_fields.json index 6aca902bdf5..10221b79ff1 100644 --- a/litellm/proxy/public_endpoints/provider_create_fields.json +++ b/litellm/proxy/public_endpoints/provider_create_fields.json @@ -577,13 +577,13 @@ "credential_fields": [ { "key": "api_key", - "label": "OAuth credential reference", - "placeholder": "oauth:my-chatgpt", - "tooltip": "Reference a stored ChatGPT OAuth credential by name, prefixed with 'oauth:'. Create the credential first via Credentials \u2192 Add Credential \u2192 ChatGPT (OAuth).", + "label": "OAuth credential", + "placeholder": "Select a stored ChatGPT OAuth credential", + "tooltip": "Pick a credential you signed in with via Credentials \u2192 Add Credential \u2192 ChatGPT (OAuth). The proxy resolves the chosen name to the stored OAuth tokens at request time.", "required": true, - "field_type": "text", + "field_type": "oauth_credential_select", "options": null, - "default_value": "oauth:" + "default_value": null } ], "default_model_placeholder": "gpt-5.3-codex" diff --git a/litellm/types/proxy/public_endpoints/public_endpoints.py b/litellm/types/proxy/public_endpoints/public_endpoints.py index caa9a978530..dfe63b053fa 100644 --- a/litellm/types/proxy/public_endpoints/public_endpoints.py +++ b/litellm/types/proxy/public_endpoints/public_endpoints.py @@ -19,7 +19,14 @@ class ProviderCredentialField(BaseModel): placeholder: Optional[str] = None tooltip: Optional[str] = None required: bool = False - field_type: Literal["text", "password", "select", "upload", "textarea"] = "text" + field_type: Literal[ + "text", + "password", + "select", + "upload", + "textarea", + "oauth_credential_select", + ] = "text" options: Optional[List[str]] = None default_value: Optional[str] = None diff --git a/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.tsx b/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.tsx index 24df0ac21ef..5bd350e00c1 100644 --- a/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.tsx +++ b/ui/litellm-dashboard/src/components/add_model/provider_specific_fields.tsx @@ -1,3 +1,4 @@ +import { useCredentials } from "@/app/(dashboard)/hooks/credentials/useCredentials"; import { useProviderFields } from "@/app/(dashboard)/hooks/providers/useProviderFields"; import { UploadOutlined } from "@ant-design/icons"; import { Text, TextInput } from "@tremor/react"; @@ -5,6 +6,10 @@ import { Button as Button2, Col, Form, Input, Row, Select, Typography, Upload, U import React from "react"; import { CredentialItem, ProviderCredentialFieldMetadata } from "../networking"; import { provider_map, Providers } from "../provider_info_helpers"; + +// credential_info.type values recognised as OAuth-backed credentials. +// Add more as we ship OAuth flows for other providers. +const OAUTH_CREDENTIAL_TYPES = new Set(["chatgpt_oauth", "copilot_oauth"]); const { Link } = Typography; interface ProviderSpecificFieldsProps { @@ -18,7 +23,13 @@ interface ProviderCredentialField { placeholder?: string; tooltip?: string; required?: boolean; - type?: "text" | "password" | "select" | "upload" | "textarea"; + type?: + | "text" + | "password" + | "select" + | "upload" + | "textarea" + | "oauth_credential_select"; options?: string[]; defaultValue?: string; } @@ -38,7 +49,9 @@ const mapFieldMetadataToUiField = (field: ProviderCredentialFieldMetadata): Prov ? "upload" : field.field_type === "textarea" ? "textarea" - : "text"; + : field.field_type === "oauth_credential_select" + ? "oauth_credential_select" + : "text"; return { key: field.key, @@ -98,6 +111,17 @@ const ProviderSpecificFields: React.FC = ({ selecte const { data: providerMetadata, isLoading, error: loadError } = useProviderFields(); + // Fetched lazily; only used by oauth_credential_select fields. The hook + // is a no-op until ``accessToken`` is available. + const { data: credentialsResponse } = useCredentials(); + const oauthCredentials = React.useMemo(() => { + const all = credentialsResponse?.credentials ?? []; + return all.filter((c: CredentialItem) => { + const type = (c.credential_info as Record | undefined)?.type; + return typeof type === "string" && OAUTH_CREDENTIAL_TYPES.has(type); + }); + }, [credentialsResponse]); + // Memoize the expensive cache computation const cacheEntries = React.useMemo(() => { if (!providerMetadata) { @@ -223,7 +247,27 @@ const ProviderSpecificFields: React.FC = ({ selecte tooltip={field.tooltip} className={field.key === "vertex_credentials" ? "mb-0" : undefined} > - {field.type === "select" ? ( + {field.type === "oauth_credential_select" ? ( + + ) : field.type === "select" ? (