diff --git a/credentials/BrowserbaseApi.credentials.ts b/credentials/BrowserbaseApi.credentials.ts index 952a538..75a3c44 100644 --- a/credentials/BrowserbaseApi.credentials.ts +++ b/credentials/BrowserbaseApi.credentials.ts @@ -37,8 +37,8 @@ export class BrowserbaseApi implements ICredentialType { type: 'string', typeOptions: { password: true }, default: '', - required: true, - description: 'API key for the AI model (e.g., Gemini API key)', + required: false, + description: 'Optional. Provide your own model API key, or leave blank to use the Browserbase Model Gateway.', }, ]; @@ -47,7 +47,6 @@ export class BrowserbaseApi implements ICredentialType { properties: { headers: { 'x-bb-api-key': '={{$credentials.browserbaseApiKey}}', - 'x-model-api-key': '={{$credentials.modelApiKey}}', }, }, }; diff --git a/nodes/Browserbase/Browserbase.node.ts b/nodes/Browserbase/Browserbase.node.ts index 0170dfb..ea04597 100644 --- a/nodes/Browserbase/Browserbase.node.ts +++ b/nodes/Browserbase/Browserbase.node.ts @@ -122,20 +122,61 @@ export class Browserbase implements INodeType { }, }, }, - // Notice about models + { + displayName: 'Model Source', + name: 'modelSource', + type: 'options', + noDataExpression: true, + displayOptions: { + show: { + resource: ['agent'], + operation: ['execute'], + }, + }, + options: [ + { + name: 'Model Gateway (Browserbase)', + value: 'gateway', + description: 'Use Browserbase-managed model routing. Mix any providers freely.', + }, + { + name: 'User-Provided API Key', + value: 'userProvidedKey', + description: 'Use your own model API key from credentials. Same provider required for both models.', + }, + ], + default: 'gateway', + description: 'Choose how model calls are routed. Model Gateway lets you mix providers; User-provided API key requires both models from the same provider.', + }, + { + displayName: 'Model Info', + name: 'modelNoticeGateway', + type: 'notice', + default: '', + displayOptions: { + show: { + resource: ['agent'], + operation: ['execute'], + modelSource: ['gateway'], + }, + }, + description: + 'Using the Browserbase Model Gateway. You can freely mix models from different providers for Driver and Agent.', + }, { displayName: 'Model Info', - name: 'modelNotice', + name: 'modelNoticeBYOK', type: 'notice', default: '', displayOptions: { show: { resource: ['agent'], operation: ['execute'], + modelSource: ['userProvidedKey'], }, }, description: - 'Driver Model is used for the actual primitive operations. Agent Model is used for orchestration. For now pick both models from the same provider.', + 'Using your own API key from credentials. Both Driver and Agent models MUST be from the same provider.', }, // Driver Model for session start { @@ -150,28 +191,24 @@ export class Browserbase implements INodeType { }, options: [ { - name: 'Claude Opus 4.5 (Anthropic)', - value: 'anthropic/claude-opus-4-5', - }, - { - name: 'Claude Sonnet 4.5 (Anthropic)', - value: 'anthropic/claude-sonnet-4-5-20250929', + name: 'Claude Haiku 4.5 (Anthropic)', + value: 'anthropic/claude-haiku-4-5', }, { - name: 'Gemini 2.5 Flash (Google)', - value: 'google/gemini-2.5-flash', + name: 'Claude Opus 4.6 (Anthropic)', + value: 'anthropic/claude-opus-4-6', }, { - name: 'Gemini 2.5 Pro (Google)', - value: 'google/gemini-2.5-pro', + name: 'Claude Sonnet 4.6 (Anthropic)', + value: 'anthropic/claude-sonnet-4-6', }, { name: 'Gemini 3 Flash (Google)', - value: 'google/gemini-3-flash-preview', + value: 'google/gemini-3-flash', }, { name: 'Gemini 3 Pro (Google)', - value: 'google/gemini-3-pro-preview', + value: 'google/gemini-3-pro', }, { name: 'GPT-4o (OpenAI)', @@ -182,7 +219,7 @@ export class Browserbase implements INodeType { value: 'openai/gpt-4o-mini', }, ], - default: 'google/gemini-2.5-flash', + default: 'anthropic/claude-sonnet-4-6', description: 'Model for browser session (DOM-based, used for navigation)', }, // Mode selection @@ -233,20 +270,12 @@ export class Browserbase implements INodeType { options: [ { name: 'Claude Haiku 4.5 (Anthropic)', - value: 'anthropic/claude-haiku-4-5-20251001', - }, - { - name: 'Claude Opus 4.5 (Anthropic)', - value: 'anthropic/claude-opus-4-5-20251101', + value: 'anthropic/claude-haiku-4-5', }, { name: 'Claude Opus 4.6 (Anthropic)', value: 'anthropic/claude-opus-4-6', }, - { - name: 'Claude Sonnet 4.5 (Anthropic)', - value: 'anthropic/claude-sonnet-4-5-20250929', - }, { name: 'Claude Sonnet 4.6 (Anthropic)', value: 'anthropic/claude-sonnet-4-6', @@ -272,7 +301,7 @@ export class Browserbase implements INodeType { value: 'google/gemini-3-pro-preview', }, ], - default: 'google/gemini-2.5-computer-use-preview-10-2025', + default: 'anthropic/claude-sonnet-4-6', description: 'CUA model for vision-based browser control', }, // DOM Models @@ -289,20 +318,8 @@ export class Browserbase implements INodeType { }, options: [ { - name: 'Claude Opus 4.5 (Anthropic)', - value: 'anthropic/claude-opus-4-5', - }, - { - name: 'Claude Sonnet 4.5 (Anthropic)', - value: 'anthropic/claude-sonnet-4-5-20250929', - }, - { - name: 'Gemini 2.5 Flash (Google)', - value: 'google/gemini-2.5-flash', - }, - { - name: 'Gemini 2.5 Pro (Google)', - value: 'google/gemini-2.5-pro', + name: 'Claude Sonnet 4.6 (Anthropic)', + value: 'anthropic/claude-sonnet-4-6', }, { name: 'Gemini 3 Flash (Google)', @@ -325,7 +342,7 @@ export class Browserbase implements INodeType { value: 'openai/gpt-4o-mini', }, ], - default: 'google/gemini-2.5-flash', + default: 'anthropic/claude-sonnet-4-6', description: 'LLM for DOM-based browser control', }, // Hybrid Models @@ -346,19 +363,15 @@ export class Browserbase implements INodeType { value: 'google/gemini-3-flash-preview', }, { - name: 'Claude Sonnet 4.5 (Anthropic)', - value: 'anthropic/claude-sonnet-4-5-20250929', - }, - { - name: 'Claude Sonnet 4 (Anthropic)', - value: 'anthropic/claude-sonnet-4-20250514', + name: 'Claude Sonnet 4.6 (Anthropic)', + value: 'anthropic/claude-sonnet-4-6', }, { name: 'Claude Haiku 4.5 (Anthropic)', value: 'anthropic/claude-haiku-4-5-20251001', }, ], - default: 'google/gemini-3-flash-preview', + default: 'anthropic/claude-sonnet-4-6', description: 'Model for hybrid mode (must support coordinate actions)', }, // Options collection (combines agent options) @@ -490,21 +503,6 @@ export class Browserbase implements INodeType { default: true, description: 'Whether to enable session logging', }, - { - displayName: 'OS', - name: 'os', - type: 'options', - options: [ - { name: 'Default', value: '' }, - { name: 'Linux', value: 'linux' }, - { name: 'Mac', value: 'mac' }, - { name: 'Mobile', value: 'mobile' }, - { name: 'Tablet', value: 'tablet' }, - { name: 'Windows', value: 'windows' }, - ], - default: '', - description: 'OS for advanced stealth mode. Controls user agent and browser environment signals.', - }, { displayName: 'Record Session', name: 'recordSession', @@ -631,10 +629,13 @@ export class Browserbase implements INodeType { Accept: 'application/json', 'Content-Type': 'application/json', 'x-bb-api-key': browserbaseApiKey as string, - 'x-model-api-key': modelApiKey as string, }; - if (browserbaseProjectId) { - headers['x-bb-project-id'] = browserbaseProjectId as string; + if (modelApiKey) { + headers['x-model-api-key'] = modelApiKey as string; + } + const projectId = (browserbaseProjectId as string)?.trim(); + if (projectId) { + headers['x-bb-project-id'] = projectId; } let sessionId: string | undefined; @@ -704,10 +705,10 @@ export class Browserbase implements INodeType { url = `https://${url}`; } const instruction = this.getNodeParameter('instruction', i) as string; + const modelSource = this.getNodeParameter('modelSource', i) as string; const driverModel = this.getNodeParameter('driverModel', i) as string; const mode = this.getNodeParameter('mode', i) as string; - // Get agent model based on mode let agentModel: string; if (mode === 'cua') { agentModel = this.getNodeParameter('modelCua', i) as string; @@ -717,6 +718,17 @@ export class Browserbase implements INodeType { agentModel = this.getNodeParameter('modelHybrid', i) as string; } + if (modelSource === 'userProvidedKey') { + const driverProvider = driverModel.split('/')[0]; + const agentProvider = agentModel.split('/')[0]; + if (driverProvider !== agentProvider) { + throw new NodeOperationError( + this.getNode(), + `When using your own model API key, both Driver and Agent models must be from the same provider. Driver is "${driverProvider}", Agent is "${agentProvider}".`, + ); + } + } + const options = this.getNodeParameter('options', i, {}) as { maxSteps?: number; systemPrompt?: string; @@ -750,17 +762,25 @@ export class Browserbase implements INodeType { userMetadata?: string; }; - // Get credentials const credentials = await this.getCredentials('browserbaseApi'); const headers: Record = { Accept: 'application/json', 'Content-Type': 'application/json', 'x-bb-api-key': credentials.browserbaseApiKey as string, - 'x-model-api-key': credentials.modelApiKey as string, }; - // We no longer need to set the Project ID, but keeping this here for backwards compatibility - if (credentials.browserbaseProjectId) { - headers['x-bb-project-id'] = credentials.browserbaseProjectId as string; + if (modelSource === 'userProvidedKey') { + const modelApiKey = credentials.modelApiKey as string; + if (!modelApiKey) { + throw new NodeOperationError( + this.getNode(), + 'Model Source is set to "User-provided API key" but no Model API Key is configured in the Browserbase credentials.', + ); + } + headers['x-model-api-key'] = modelApiKey; + } + const projectId = (credentials.browserbaseProjectId as string)?.trim(); + if (projectId) { + headers['x-bb-project-id'] = projectId; } // Helper function to make API calls @@ -832,15 +852,16 @@ export class Browserbase implements INodeType { if (sessionOptions.userMetadata) { try { - sessionCreateParams.userMetadata = JSON.parse(sessionOptions.userMetadata); + sessionCreateParams.userMetadata = { n8n: 'true', ...JSON.parse(sessionOptions.userMetadata) }; } catch { - sessionCreateParams.userMetadata = { note: sessionOptions.userMetadata }; + sessionCreateParams.userMetadata = { n8n: 'true', note: sessionOptions.userMetadata }; } + } else { + sessionCreateParams.userMetadata = { n8n: 'true' }; } const startBody: Record = { modelName: driverModel, - apiKey: credentials.modelApiKey as string, browserbaseSessionCreateParams: sessionCreateParams, }; @@ -870,23 +891,26 @@ export class Browserbase implements INodeType { }); // 3. Execute agent - const provider = agentModel.split('/')[0]; - - const executeBody: Record = { - agentConfig: { - provider, - model: { - modelName: agentModel, - apiKey: credentials.modelApiKey as string, - }, - cua: mode === 'cua' || mode === 'hybrid', - }, - executeOptions: { - instruction, - maxSteps: options.maxSteps ?? 20, - }, + const agentConfigBody: Record = { + model: agentModel, + }; + + if (options.systemPrompt) { + agentConfigBody.systemPrompt = options.systemPrompt; + } + + const executeOpts: Record = { + instruction, + maxSteps: options.maxSteps ?? 20, }; + if ( + (mode === 'cua' || mode === 'hybrid') && + options.highlightCursor !== false + ) { + executeOpts.highlightCursor = options.highlightCursor ?? true; + } + if (mode === 'dom' || mode === 'hybrid') { const variablesParam = this.getNodeParameter('variables', i, {}) as { variableValues?: Array<{ @@ -906,29 +930,18 @@ export class Browserbase implements INodeType { } } if (Object.keys(variables).length > 0) { - (executeBody.executeOptions as Record).variables = - variables; + executeOpts.variables = variables; } } } - if (options.systemPrompt) { - (executeBody.agentConfig as Record).systemPrompt = - options.systemPrompt; - } - - if ( - (mode === 'cua' || mode === 'hybrid') && - options.highlightCursor !== false - ) { - (executeBody.executeOptions as Record).highlightCursor = - options.highlightCursor ?? true; - } - const executeResponse = await apiCall( 'POST', `/v1/sessions/${sessionId}/agentExecute`, - executeBody, + { + agentConfig: agentConfigBody, + executeOptions: executeOpts, + }, ); // 4. End session diff --git a/package-lock.json b/package-lock.json index d9e4ca3..050cdf9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "n8n-nodes-browserbase", - "version": "1.1.3", + "version": "1.1.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "n8n-nodes-browserbase", - "version": "1.1.3", + "version": "1.1.4", "license": "MIT", "devDependencies": { "@n8n/node-cli": "*", diff --git a/package.json b/package.json index 05e0003..24b9cca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-browserbase", - "version": "1.1.3", + "version": "1.1.4", "description": "n8n community node for Browserbase", "license": "MIT", "homepage": "",