diff --git a/packages/bubble-core/src/bubbles/service-bubble/ai-agent.ts b/packages/bubble-core/src/bubbles/service-bubble/ai-agent.ts index ef040b94..dfbb4537 100644 --- a/packages/bubble-core/src/bubbles/service-bubble/ai-agent.ts +++ b/packages/bubble-core/src/bubbles/service-bubble/ai-agent.ts @@ -5,6 +5,7 @@ import { CredentialType, BUBBLE_CREDENTIAL_OPTIONS, RECOMMENDED_MODELS, + decodeCredentialPayload, } from '@bubblelab/shared-schemas'; import { StateGraph, MessagesAnnotation } from '@langchain/langgraph'; import { ChatOpenAI } from '@langchain/openai'; @@ -1129,6 +1130,8 @@ export class AIAgentBubble extends ServiceBubble< return CredentialType.ANTHROPIC_CRED; case 'openrouter': return CredentialType.OPENROUTER_CRED; + case 'custom': + return CredentialType.CUSTOM_LLM_CRED; default: throw new Error(`Unsupported model provider: ${provider}`); } @@ -1156,6 +1159,8 @@ export class AIAgentBubble extends ServiceBubble< return credentials[CredentialType.ANTHROPIC_CRED]; case 'openrouter': return credentials[CredentialType.OPENROUTER_CRED]; + case 'custom': + return credentials[CredentialType.CUSTOM_LLM_CRED]; default: throw new Error(`Unsupported model provider: ${provider}`); } @@ -1362,6 +1367,9 @@ export class AIAgentBubble extends ServiceBubble< case 'openrouter': apiKey = credentials[CredentialType.OPENROUTER_CRED]; break; + case 'custom': + apiKey = credentials[CredentialType.CUSTOM_LLM_CRED]; + break; default: throw new Error(`Unsupported model provider: ${provider}`); } @@ -1392,6 +1400,24 @@ export class AIAgentBubble extends ServiceBubble< streaming: enableStreaming, maxRetries: retries, }); + case 'custom': { + const payload = decodeCredentialPayload<{ + baseUrl: string; + apiKey: string; + modelName: string; + }>(apiKey); + return new ChatOpenAI({ + model: payload.modelName, + temperature, + maxTokens, + apiKey: payload.apiKey, + configuration: { + baseURL: payload.baseUrl, + }, + streaming: enableStreaming, + maxRetries: retries, + }); + } case 'google': { const thinkingConfig = reasoningEffort ? { diff --git a/packages/bubble-shared-schemas/src/ai-models.ts b/packages/bubble-shared-schemas/src/ai-models.ts index d80d30d6..aa2c03e3 100644 --- a/packages/bubble-shared-schemas/src/ai-models.ts +++ b/packages/bubble-shared-schemas/src/ai-models.ts @@ -36,6 +36,8 @@ export const AvailableModels = z.enum([ 'openrouter/openai/gpt-oss-120b', 'openrouter/openai/o3-deep-research', 'openrouter/openai/o4-mini-deep-research', + // Custom provider + 'custom/custom', ]); export type AvailableModel = z.infer; diff --git a/packages/bubble-shared-schemas/src/credential-schema.ts b/packages/bubble-shared-schemas/src/credential-schema.ts index fab528f1..402a5bd8 100644 --- a/packages/bubble-shared-schemas/src/credential-schema.ts +++ b/packages/bubble-shared-schemas/src/credential-schema.ts @@ -174,6 +174,33 @@ export const CREDENTIAL_TYPE_CONFIG: Record = ignoreSSL: false, }, }, + [CredentialType.CUSTOM_LLM_CRED]: { + label: 'Custom LLM Provider', + description: 'Custom OpenAI-Compatible Provider', + placeholder: '', + namePlaceholder: 'My Custom LLM Provider', + credentialConfigurations: {}, + fields: [ + { + key: 'baseUrl', + label: 'Base URL', + placeholder: 'https://api.groq.com/openai/v1', + type: 'text', + }, + { + key: 'apiKey', + label: 'API Key', + placeholder: 'Your API key', + type: 'password', + }, + { + key: 'modelName', + label: 'Model Name', + placeholder: 'llama3-70b-8192', + type: 'text', + }, + ], + }, [CredentialType.CLOUDFLARE_R2_ACCESS_KEY]: { label: 'Cloudflare R2 Access Key', description: 'Access key for Cloudflare R2 storage', @@ -732,6 +759,7 @@ export const CREDENTIAL_ENV_MAP: Record = { [CredentialType.TELEGRAM_BOT_TOKEN]: 'TELEGRAM_BOT_TOKEN', [CredentialType.RESEND_CRED]: 'RESEND_API_KEY', [CredentialType.OPENROUTER_CRED]: 'OPENROUTER_API_KEY', + [CredentialType.CUSTOM_LLM_CRED]: '', // Multi-field credential [CredentialType.CLOUDFLARE_R2_ACCESS_KEY]: 'CLOUDFLARE_R2_ACCESS_KEY', [CredentialType.CLOUDFLARE_R2_SECRET_KEY]: 'CLOUDFLARE_R2_SECRET_KEY', [CredentialType.CLOUDFLARE_R2_ACCOUNT_ID]: 'CLOUDFLARE_R2_ACCOUNT_ID', @@ -792,6 +820,7 @@ export const SYSTEM_CREDENTIALS = new Set([ CredentialType.ANTHROPIC_CRED, CredentialType.RESEND_CRED, CredentialType.OPENROUTER_CRED, + CredentialType.CUSTOM_LLM_CRED, // Cloudflare R2 Storage credentials CredentialType.CLOUDFLARE_R2_ACCESS_KEY, CredentialType.CLOUDFLARE_R2_SECRET_KEY, diff --git a/packages/bubble-shared-schemas/src/types.ts b/packages/bubble-shared-schemas/src/types.ts index ffff2f2c..6c0027e6 100644 --- a/packages/bubble-shared-schemas/src/types.ts +++ b/packages/bubble-shared-schemas/src/types.ts @@ -12,6 +12,8 @@ export enum CredentialType { GOOGLE_GEMINI_CRED = 'GOOGLE_GEMINI_CRED', ANTHROPIC_CRED = 'ANTHROPIC_CRED', OPENROUTER_CRED = 'OPENROUTER_CRED', + // Custom LLM Provider + CUSTOM_LLM_CRED = 'CUSTOM_LLM_CRED', // Search Credentials FIRECRAWL_API_KEY = 'FIRECRAWL_API_KEY', // Database Credentials