diff --git a/autogpt_platform/autogpt_libs/autogpt_libs/supabase_integration_credentials_store/store.py b/autogpt_platform/autogpt_libs/autogpt_libs/supabase_integration_credentials_store/store.py index 02507009a..79d027819 100644 --- a/autogpt_platform/autogpt_libs/autogpt_libs/supabase_integration_credentials_store/store.py +++ b/autogpt_platform/autogpt_libs/autogpt_libs/supabase_integration_credentials_store/store.py @@ -46,21 +46,21 @@ replicate_credentials = APIKeyCredentials( ) openai_credentials = APIKeyCredentials( id="53c25cb8-e3ee-465c-a4d1-e75a4c899c2a", - provider="llm", + provider="openai", api_key=SecretStr(settings.secrets.openai_api_key), title="Use Credits for OpenAI", expires_at=None, ) anthropic_credentials = APIKeyCredentials( id="24e5d942-d9e3-4798-8151-90143ee55629", - provider="llm", + provider="anthropic", api_key=SecretStr(settings.secrets.anthropic_api_key), title="Use Credits for Anthropic", expires_at=None, ) groq_credentials = APIKeyCredentials( id="4ec22295-8f97-4dd1-b42b-2c6957a02545", - provider="llm", + provider="groq", api_key=SecretStr(settings.secrets.groq_api_key), title="Use Credits for Groq", expires_at=None, diff --git a/autogpt_platform/backend/backend/blocks/llm.py b/autogpt_platform/backend/backend/blocks/llm.py index 766317870..d52739a7c 100644 --- a/autogpt_platform/backend/backend/blocks/llm.py +++ b/autogpt_platform/backend/backend/blocks/llm.py @@ -30,11 +30,12 @@ logger = logging.getLogger(__name__) # "ollama": BlockSecret(value=""), # } -AICredentials = CredentialsMetaInput[Literal["llm"], Literal["api_key"]] +LLMProviderName = Literal["anthropic", "groq", "openai", "ollama"] +AICredentials = CredentialsMetaInput[LLMProviderName, Literal["api_key"]] TEST_CREDENTIALS = APIKeyCredentials( id="ed55ac19-356e-4243-a6cb-bc599e9b716f", - provider="llm", + provider="openai", api_key=SecretStr("mock-openai-api-key"), title="Mock OpenAI API key", expires_at=None, @@ -50,8 +51,12 @@ TEST_CREDENTIALS_INPUT = { def AICredentialsField() -> AICredentials: return CredentialsField( description="API key for the LLM provider.", - provider="llm", + provider=["anthropic", "groq", "openai", "ollama"], supported_credential_types={"api_key"}, + discriminator="model", + discriminator_mapping={ + model.value: model.metadata.provider for model in LlmModel + }, ) diff --git a/autogpt_platform/backend/backend/data/model.py b/autogpt_platform/backend/backend/data/model.py index 0ca9d699c..57705006b 100644 --- a/autogpt_platform/backend/backend/data/model.py +++ b/autogpt_platform/backend/backend/data/model.py @@ -152,10 +152,12 @@ class CredentialsMetaInput(BaseModel, Generic[CP, CT]): def CredentialsField( - provider: CP, + provider: CP | list[CP], supported_credential_types: set[CT], required_scopes: set[str] = set(), *, + discriminator: Optional[str] = None, + discriminator_mapping: Optional[dict[str, Any]] = None, title: Optional[str] = None, description: Optional[str] = None, **kwargs, @@ -167,9 +169,13 @@ def CredentialsField( json_extra = { k: v for k, v in { - "credentials_provider": provider, + "credentials_provider": ( + [provider] if isinstance(provider, str) else provider + ), "credentials_scopes": list(required_scopes) or None, # omit if empty "credentials_types": list(supported_credential_types), + "discriminator": discriminator, + "discriminator_mapping": discriminator_mapping, }.items() if v is not None } diff --git a/autogpt_platform/frontend/src/app/profile/page.tsx b/autogpt_platform/frontend/src/app/profile/page.tsx index 97c1b2b3a..f4d28b999 100644 --- a/autogpt_platform/frontend/src/app/profile/page.tsx +++ b/autogpt_platform/frontend/src/app/profile/page.tsx @@ -4,17 +4,14 @@ import { useSupabase } from "@/components/SupabaseProvider"; import { Button } from "@/components/ui/button"; import useUser from "@/hooks/useUser"; import { useRouter } from "next/navigation"; -import { useCallback, useContext } from "react"; +import { useCallback, useContext, useMemo } from "react"; import { FaSpinner } from "react-icons/fa"; import { Separator } from "@/components/ui/separator"; import { useToast } from "@/components/ui/use-toast"; import { IconKey, IconUser } from "@/components/ui/icons"; import { LogOutIcon, Trash2Icon } from "lucide-react"; import { providerIcons } from "@/components/integrations/credentials-input"; -import { - CredentialsProviderName, - CredentialsProvidersContext, -} from "@/components/integrations/credentials-provider"; +import { CredentialsProvidersContext } from "@/components/integrations/credentials-provider"; import { Table, TableBody, @@ -23,6 +20,7 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; +import { CredentialsProviderName } from "@/lib/autogpt-server-api"; export default function PrivatePage() { const { user, isLoading, error } = useUser(); @@ -62,7 +60,22 @@ export default function PrivatePage() { [providers, toast], ); - if (isLoading || !providers || !providers) { + //TODO: remove when the way system credentials are handled is updated + // This contains ids for built-in "Use Credits for X" credentials + const hiddenCredentials = useMemo( + () => [ + "fdb7f412-f519-48d1-9b5f-d2f73d0e01fe", // Revid + "760f84fc-b270-42de-91f6-08efe1b512d0", // Ideogram + "6b9fc200-4726-4973-86c9-cd526f5ce5db", // Replicate + "53c25cb8-e3ee-465c-a4d1-e75a4c899c2a", // OpenAI + "24e5d942-d9e3-4798-8151-90143ee55629", // Anthropic + "4ec22295-8f97-4dd1-b42b-2c6957a02545", // Groq + "7f7b0654-c36b-4565-8fa7-9a52575dfae2", // D-ID + ], + [], + ); + + if (isLoading || !providers) { return (
@@ -76,15 +89,15 @@ export default function PrivatePage() { } const allCredentials = Object.values(providers).flatMap((provider) => - [...provider.savedOAuthCredentials, ...provider.savedApiKeys].map( - (credentials) => ({ + [...provider.savedOAuthCredentials, ...provider.savedApiKeys] + .filter((cred) => !hiddenCredentials.includes(cred.id)) + .map((credentials) => ({ ...credentials, provider: provider.provider, providerName: provider.providerName, ProviderIcon: providerIcons[provider.provider], TypeIcon: { oauth2: IconUser, api_key: IconKey }[credentials.type], - }), - ), + })), ); return ( diff --git a/autogpt_platform/frontend/src/components/CustomNode.tsx b/autogpt_platform/frontend/src/components/CustomNode.tsx index 902135123..3890ad573 100644 --- a/autogpt_platform/frontend/src/components/CustomNode.tsx +++ b/autogpt_platform/frontend/src/components/CustomNode.tsx @@ -18,7 +18,13 @@ import { BlockUIType, BlockCost, } from "@/lib/autogpt-server-api/types"; -import { beautifyString, cn, setNestedProperty } from "@/lib/utils"; +import { + beautifyString, + cn, + getValue, + parseKeys, + setNestedProperty, +} from "@/lib/utils"; import { Button } from "@/components/ui/button"; import { Switch } from "@/components/ui/switch"; import { history } from "./history"; @@ -36,8 +42,6 @@ import * as Separator from "@radix-ui/react-separator"; import * as ContextMenu from "@radix-ui/react-context-menu"; import { DotsVerticalIcon, TrashIcon, CopyIcon } from "@radix-ui/react-icons"; -type ParsedKey = { key: string; index?: number }; - export type ConnectionData = Array<{ edge_id: string; source: string; @@ -178,7 +182,7 @@ export function CustomNode({ className="" selfKey={noteKey} schema={noteSchema as BlockIOStringSubSchema} - value={getValue(noteKey)} + value={getValue(noteKey, data.hardcodedValues)} handleInputChange={handleInputChange} handleInputClick={handleInputClick} error={data.errors?.[noteKey] ?? ""} @@ -228,7 +232,7 @@ export function CustomNode({ nodeId={id} propKey={getInputPropKey(propKey)} propSchema={propSchema} - currentValue={getValue(getInputPropKey(propKey))} + currentValue={getValue(propKey, data.hardcodedValues)} connections={data.connections} handleInputChange={handleInputChange} handleInputClick={handleInputClick} @@ -283,48 +287,6 @@ export function CustomNode({ setErrors({ ...errors }); }; - // Helper function to parse keys with array indices - //TODO move to utils - const parseKeys = (key: string): ParsedKey[] => { - const splits = key.split(/_@_|_#_|_\$_|\./); - const keys: ParsedKey[] = []; - let currentKey: string | null = null; - - splits.forEach((split) => { - const isInteger = /^\d+$/.test(split); - if (!isInteger) { - if (currentKey !== null) { - keys.push({ key: currentKey }); - } - currentKey = split; - } else { - if (currentKey !== null) { - keys.push({ key: currentKey, index: parseInt(split, 10) }); - currentKey = null; - } else { - throw new Error("Invalid key format: array index without a key"); - } - } - }); - - if (currentKey !== null) { - keys.push({ key: currentKey }); - } - - return keys; - }; - - const getValue = (key: string) => { - const keys = parseKeys(key); - return keys.reduce((acc, k) => { - if (acc === undefined) return undefined; - if (k.index !== undefined) { - return Array.isArray(acc[k.key]) ? acc[k.key][k.index] : undefined; - } - return acc[k.key]; - }, data.hardcodedValues as any); - }; - const isHandleConnected = (key: string) => { return ( data.connections && @@ -347,7 +309,7 @@ export function CustomNode({ const handleInputClick = (key: string) => { console.debug(`Opening modal for key: ${key}`); setActiveKey(key); - const value = getValue(key); + const value = getValue(key, data.hardcodedValues); setInputModalValue( typeof value === "object" ? JSON.stringify(value, null, 2) : value, ); diff --git a/autogpt_platform/frontend/src/components/integrations/credentials-input.tsx b/autogpt_platform/frontend/src/components/integrations/credentials-input.tsx index 8b7e3a36e..3457a1c82 100644 --- a/autogpt_platform/frontend/src/components/integrations/credentials-input.tsx +++ b/autogpt_platform/frontend/src/components/integrations/credentials-input.tsx @@ -46,16 +46,18 @@ export const providerIcons: Record< CredentialsProviderName, React.FC<{ className?: string }> > = { + anthropic: fallbackIcon, github: FaGithub, google: FaGoogle, + groq: fallbackIcon, notion: NotionLogoIcon, discord: FaDiscord, d_id: fallbackIcon, google_maps: FaGoogle, jina: fallbackIcon, ideogram: fallbackIcon, - llm: fallbackIcon, medium: FaMedium, + ollama: fallbackIcon, openai: fallbackIcon, openweathermap: fallbackIcon, pinecone: fallbackIcon, @@ -80,7 +82,7 @@ export type OAuthPopupResultMessage = { message_type: "oauth_popup_result" } & ( export const CredentialsInput: FC<{ className?: string; selectedCredentials?: CredentialsMetaInput; - onSelectCredentials: (newValue: CredentialsMetaInput) => void; + onSelectCredentials: (newValue?: CredentialsMetaInput) => void; }> = ({ className, selectedCredentials, onSelectCredentials }) => { const api = useMemo(() => new AutoGPTServerAPI(), []); const credentials = useCredentials(); @@ -91,14 +93,10 @@ export const CredentialsInput: FC<{ useState(null); const [oAuthError, setOAuthError] = useState(null); - if (!credentials) { + if (!credentials || credentials.isLoading) { return null; } - if (credentials.isLoading) { - return
Loading...
; - } - const { schema, provider, @@ -222,10 +220,21 @@ export const CredentialsInput: FC<{ ); + // Deselect credentials if they do not exist (e.g. provider was changed) + if ( + selectedCredentials && + !savedApiKeys + .concat(savedOAuthCredentials) + .some((c) => c.id === selectedCredentials.id) + ) { + onSelectCredentials(undefined); + } + // No saved credentials yet if (savedApiKeys.length === 0 && savedOAuthCredentials.length === 0) { return ( <> + Credentials
{supportsOAuth2 && (