diff --git a/credentials/BrowserbaseApi.credentials.ts b/credentials/BrowserbaseApi.credentials.ts index 75a3c44..e11025e 100644 --- a/credentials/BrowserbaseApi.credentials.ts +++ b/credentials/BrowserbaseApi.credentials.ts @@ -23,6 +23,18 @@ export class BrowserbaseApi implements ICredentialType { required: true, description: 'Your Browserbase API key', }, + { + displayName: 'Base URL', + name: 'baseUrl', + type: 'hidden', + default: 'https://api.browserbase.com', + }, + { + displayName: 'Stagehand Base URL', + name: 'stagehandBaseUrl', + type: 'hidden', + default: 'https://api.stagehand.browserbase.com', + }, { displayName: 'Browserbase Project ID (Deprecated)', name: 'browserbaseProjectId', diff --git a/nodes/Browserbase/Browserbase.node.ts b/nodes/Browserbase/Browserbase.node.ts index 38dc4f6..a21926f 100644 --- a/nodes/Browserbase/Browserbase.node.ts +++ b/nodes/Browserbase/Browserbase.node.ts @@ -51,6 +51,10 @@ function normalizeUrl(url: string): string { return `https://${url}`; } +function normalizeBaseUrl(url: string): string { + return url.trim().replace(/\/+$/, ''); +} + function getSessionId(response: Record): string | undefined { const data = response.data as Record | undefined; return (data?.sessionId ?? response.sessionId ?? response.id) as string | undefined; @@ -790,7 +794,7 @@ export class Browserbase implements INodeType { name: 'browserbase', icon: 'file:../../icons/browserbase.svg', group: ['transform'], - version: 2, + version: [2, 2.1], subtitle: '={{$parameter["resource"] === "agent" ? $parameter["operation"] + ": " + $parameter["mode"] : $parameter["operation"]}}', description: 'Browser automation, web search, and page fetches with Browserbase.', @@ -818,13 +822,14 @@ export class Browserbase implements INodeType { ): Promise { try { const headers = getHeaders(credential.data!); + const baseUrl = normalizeBaseUrl((credential.data?.baseUrl as string) ?? API_BASE_URL); const httpRequest = this.helpers['request' as keyof typeof this.helpers] as ( opts: object, ) => Promise>; await httpRequest({ method: 'POST', - uri: `${API_BASE_URL}/v1/fetch`, + uri: `${baseUrl}/v1/fetch`, headers, body: { url: 'https://browserbase.com/' }, json: true, @@ -874,6 +879,7 @@ export class Browserbase implements INodeType { executeFunctions: IExecuteFunctions, itemIndex: number, headers: BrowserbaseHeaders, + baseUrl: string, ): Promise { const query = executeFunctions.getNodeParameter('query', itemIndex) as string; const numResults = executeFunctions.getNodeParameter('numResults', itemIndex) as number; @@ -881,7 +887,7 @@ export class Browserbase implements INodeType { const response = await this.apiCall( executeFunctions, 'POST', - API_BASE_URL, + baseUrl, '/v1/search', headers, { @@ -907,6 +913,7 @@ export class Browserbase implements INodeType { executeFunctions: IExecuteFunctions, itemIndex: number, headers: BrowserbaseHeaders, + baseUrl: string, ): Promise { const url = normalizeUrl(executeFunctions.getNodeParameter('fetchUrl', itemIndex) as string); const fetchOptions = executeFunctions.getNodeParameter('fetchOptions', itemIndex, {}) as { @@ -918,7 +925,7 @@ export class Browserbase implements INodeType { const response = await this.apiCall( executeFunctions, 'POST', - API_BASE_URL, + baseUrl, '/v1/fetch', headers, { @@ -946,6 +953,7 @@ export class Browserbase implements INodeType { executeFunctions: IExecuteFunctions, itemIndex: number, headers: BrowserbaseHeaders, + baseUrl: string, ): Promise { let url = executeFunctions.getNodeParameter('url', itemIndex) as string; url = normalizeUrl(url); @@ -1048,7 +1056,7 @@ export class Browserbase implements INodeType { const startResponse = await this.apiCall( executeFunctions, 'POST', - STAGEHAND_BASE_URL, + baseUrl, '/v1/sessions/start', headers, { @@ -1068,7 +1076,7 @@ export class Browserbase implements INodeType { await this.apiCall( executeFunctions, 'POST', - STAGEHAND_BASE_URL, + baseUrl, `/v1/sessions/${sessionId}/navigate`, headers, { @@ -1123,7 +1131,7 @@ export class Browserbase implements INodeType { const executeResponse = await this.apiCall( executeFunctions, 'POST', - STAGEHAND_BASE_URL, + baseUrl, `/v1/sessions/${sessionId}/agentExecute`, headers, { @@ -1135,7 +1143,7 @@ export class Browserbase implements INodeType { await this.apiCall( executeFunctions, 'POST', - STAGEHAND_BASE_URL, + baseUrl, `/v1/sessions/${sessionId}/end`, headers, {}, @@ -1163,7 +1171,7 @@ export class Browserbase implements INodeType { await this.apiCall( executeFunctions, 'POST', - STAGEHAND_BASE_URL, + baseUrl, `/v1/sessions/${sessionId}/end`, headers, {}, @@ -1199,12 +1207,20 @@ export class Browserbase implements INodeType { includeModelApiKey: resource === 'agent' && modelSource === 'userProvidedKey', }); + const useCredentialBaseUrls = this.getNode().typeVersion >= 2.1; + const apiBaseUrl = useCredentialBaseUrls + ? normalizeBaseUrl((credentials.baseUrl as string) || API_BASE_URL) + : API_BASE_URL; + const stagehandBaseUrl = useCredentialBaseUrls + ? normalizeBaseUrl((credentials.stagehandBaseUrl as string) || STAGEHAND_BASE_URL) + : STAGEHAND_BASE_URL; + if (resource === 'search') { - returnData.push(await node.executeSearch(this, i, headers)); + returnData.push(await node.executeSearch(this, i, headers, apiBaseUrl)); } else if (resource === 'fetch') { - returnData.push(await node.executeFetch(this, i, headers)); + returnData.push(await node.executeFetch(this, i, headers, apiBaseUrl)); } else { - returnData.push(await node.executeAgent(this, i, headers)); + returnData.push(await node.executeAgent(this, i, headers, stagehandBaseUrl)); } } catch (error) { if (this.continueOnFail()) {