mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-24 00:52:24 +00:00
feat(ui): oauth_credential_select field type for Add Model form
Previously the ChatGPT (OAuth) provider in the Add Model form rendered
a plain text input for ``api_key`` and expected the admin to type the
``oauth:<credential_name>`` marker by hand — easy to typo, and
discoverability depended on reading the tooltip.
Add a new ``field_type: "oauth_credential_select"`` that
``provider_specific_fields.tsx`` renders as an antd Select populated
from ``useCredentials()``, filtered to OAuth-backed credentials
(``credential_info.type`` in ``{chatgpt_oauth, copilot_oauth}``).
Picking a row stores ``oauth:<credential_name>`` as the form value, so
the downstream request path is unchanged.
Wire-up:
- ``ProviderCredentialField`` Literal (Python) and
``ProviderCredentialFieldMetadata`` union (TypeScript) both extended
with the new type.
- ``provider_create_fields.json`` for ``ChatGPT`` updated to use it.
- Empty-state: if no OAuth credentials exist yet, the dropdown shows a
"No OAuth credentials found" hint pointing at Credentials → Add
Credential.
No change to Copilot's entry — Copilot's existing ``api_key``/``api_base``
text fields stay, since that provider still supports plain PAT auth
alongside OAuth. Admins who want the OAuth flow for Copilot can still
type ``oauth:<name>`` into the ``api_key`` field manually; the dropdown
will cover that too in a follow-up if you want symmetry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a5225003a3
commit
9d0466ba6d
4 changed files with 67 additions and 10 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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<ProviderSpecificFieldsProps> = ({ 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<string, unknown> | 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<ProviderSpecificFieldsProps> = ({ selecte
|
|||
tooltip={field.tooltip}
|
||||
className={field.key === "vertex_credentials" ? "mb-0" : undefined}
|
||||
>
|
||||
{field.type === "select" ? (
|
||||
{field.type === "oauth_credential_select" ? (
|
||||
<Select
|
||||
placeholder={field.placeholder ?? "Select a stored OAuth credential"}
|
||||
showSearch
|
||||
notFoundContent={
|
||||
<Text className="text-sm text-gray-500">
|
||||
No OAuth credentials found. Create one via Credentials →
|
||||
Add Credential first.
|
||||
</Text>
|
||||
}
|
||||
>
|
||||
{oauthCredentials.map((c) => (
|
||||
<Select.Option
|
||||
key={c.credential_name}
|
||||
value={`oauth:${c.credential_name}`}
|
||||
>
|
||||
{c.credential_name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
) : field.type === "select" ? (
|
||||
<Select placeholder={field.placeholder} defaultValue={field.defaultValue}>
|
||||
{field.options?.map((option) => (
|
||||
<Select.Option key={option} value={option}>
|
||||
|
|
|
|||
|
|
@ -272,7 +272,13 @@ export interface ProviderCredentialFieldMetadata {
|
|||
placeholder?: string | null;
|
||||
tooltip?: string | null;
|
||||
required?: boolean;
|
||||
field_type?: "text" | "password" | "select" | "upload" | "textarea";
|
||||
field_type?:
|
||||
| "text"
|
||||
| "password"
|
||||
| "select"
|
||||
| "upload"
|
||||
| "textarea"
|
||||
| "oauth_credential_select";
|
||||
options?: string[] | null;
|
||||
default_value?: string | null;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue