@@ -7,7 +7,10 @@ import type {
77} from 'n8n-workflow' ;
88import { NodeApiError , NodeOperationError } from 'n8n-workflow' ;
99
10- const API_BASE_URL = 'https://agent.tinyfish.ai' ;
10+ export const AGENT_API_BASE_URL = 'https://agent.tinyfish.ai' ;
11+ export const BROWSER_API_BASE_URL = 'https://api.browser.tinyfish.ai' ;
12+ export const FETCH_API_BASE_URL = 'https://api.fetch.tinyfish.ai' ;
13+ export const SEARCH_API_BASE_URL = 'https://api.search.tinyfish.ai' ;
1114
1215/**
1316 * Map known TinyFish API error codes to actionable user messages.
@@ -36,7 +39,9 @@ function getActionableMessage(error: unknown): string | undefined {
3639 return `${ message || 'Resource not found' } . Verify the run ID is correct.` ;
3740 case 'INVALID_INPUT' : {
3841 if ( details ) {
39- const detailStr = Object . entries ( details ) . map ( ( [ k , v ] ) => `${ k } : ${ v } ` ) . join ( ', ' ) ;
42+ const detailStr = Object . entries ( details )
43+ . map ( ( [ k , v ] ) => `${ k } : ${ v } ` )
44+ . join ( ', ' ) ;
4045 return `Invalid input (${ detailStr } ). Check your URL and goal parameters.` ;
4146 }
4247 return `Invalid input: ${ message || 'Validation failed' } . Check your URL and goal parameters.` ;
@@ -63,7 +68,7 @@ export async function tinyfishApiRequest(
6368) : Promise < IDataObject > {
6469 const requestOptions : IHttpRequestOptions = {
6570 method,
66- url : `${ API_BASE_URL } ${ path } ` ,
71+ url : path . startsWith ( 'http' ) ? path : `${ AGENT_API_BASE_URL } ${ path } ` ,
6772 qs,
6873 json : true ,
6974 ...options ,
@@ -74,11 +79,13 @@ export async function tinyfishApiRequest(
7479 }
7580
7681 try {
77- return ( await this . helpers . httpRequestWithAuthentication . call (
82+ const response = await this . helpers . httpRequestWithAuthentication . call (
7883 this ,
7984 'tinyfishApi' ,
8085 requestOptions ,
81- ) ) as IDataObject ;
86+ ) ;
87+
88+ return ( response ?? { } ) as IDataObject ;
8289 } catch ( error ) {
8390 const actionableMessage = getActionableMessage ( error ) ;
8491 if ( actionableMessage ) {
@@ -94,10 +101,7 @@ export async function tinyfishApiRequest(
94101 * Build the automation payload from node parameters.
95102 * Mirrors dify/tools/base.py _build_automation_payload().
96103 */
97- export function buildAutomationPayload (
98- this : IExecuteFunctions ,
99- itemIndex : number ,
100- ) : IDataObject {
104+ export function buildAutomationPayload ( this : IExecuteFunctions , itemIndex : number ) : IDataObject {
101105 const url = this . getNodeParameter ( 'url' , itemIndex ) as string ;
102106 const goal = this . getNodeParameter ( 'goal' , itemIndex ) as string ;
103107 const options = this . getNodeParameter ( 'options' , itemIndex , { } ) as IDataObject ;
@@ -120,6 +124,80 @@ export function buildAutomationPayload(
120124 return payload ;
121125}
122126
127+ export function buildSearchQuery ( this : IExecuteFunctions , itemIndex : number ) : IDataObject {
128+ const query = this . getNodeParameter ( 'searchQuery' , itemIndex ) as string ;
129+ const options = this . getNodeParameter ( 'searchOptions' , itemIndex , { } ) as IDataObject ;
130+
131+ const qs : IDataObject = { query } ;
132+
133+ if ( options . location ) {
134+ qs . location = options . location as string ;
135+ }
136+ if ( options . language ) {
137+ qs . language = options . language as string ;
138+ }
139+ if ( options . page !== undefined ) {
140+ qs . page = options . page as number ;
141+ }
142+
143+ return qs ;
144+ }
145+
146+ export function buildFetchPayload ( this : IExecuteFunctions , itemIndex : number ) : IDataObject {
147+ const fetchUrls = this . getNodeParameter ( 'fetchUrls' , itemIndex ) as string ;
148+ const options = this . getNodeParameter ( 'fetchOptions' , itemIndex , { } ) as IDataObject ;
149+ const urls = fetchUrls
150+ . split ( / [ \n , ] / )
151+ . map ( ( url ) => url . trim ( ) )
152+ . filter ( Boolean ) ;
153+
154+ if ( urls . length === 0 ) {
155+ throw new NodeOperationError ( this . getNode ( ) , 'At least one URL is required' , {
156+ itemIndex,
157+ } ) ;
158+ }
159+
160+ if ( urls . length > 10 ) {
161+ throw new NodeOperationError ( this . getNode ( ) , 'Fetch Content accepts a maximum of 10 URLs' , {
162+ itemIndex,
163+ } ) ;
164+ }
165+
166+ const payload : IDataObject = {
167+ urls,
168+ format : ( options . format as string ) || 'markdown' ,
169+ links : Boolean ( options . links ) ,
170+ image_links : Boolean ( options . imageLinks ) ,
171+ } ;
172+
173+ if ( options . proxyCountryCode ) {
174+ payload . proxy_config = {
175+ country_code : options . proxyCountryCode as string ,
176+ } ;
177+ }
178+
179+ return payload ;
180+ }
181+
182+ export function buildBrowserSessionPayload (
183+ this : IExecuteFunctions ,
184+ itemIndex : number ,
185+ ) : IDataObject {
186+ const url = this . getNodeParameter ( 'browserUrl' , itemIndex , '' ) as string ;
187+ const options = this . getNodeParameter ( 'browserOptions' , itemIndex , { } ) as IDataObject ;
188+ const payload : IDataObject = { } ;
189+
190+ if ( url . trim ( ) ) {
191+ payload . url = url . trim ( ) ;
192+ }
193+
194+ if ( options . timeoutSeconds !== undefined ) {
195+ payload . timeout_seconds = options . timeoutSeconds as number ;
196+ }
197+
198+ return payload ;
199+ }
200+
123201/**
124202 * Consume an SSE stream from the TinyFish run-sse endpoint.
125203 * Uses native fetch() for streaming support.
@@ -134,7 +212,7 @@ export async function consumeSseStream(
134212
135213 let lastProgress = '' ;
136214
137- const response = await fetch ( `${ API_BASE_URL } /v1/automation/run-sse` , {
215+ const response = await fetch ( `${ AGENT_API_BASE_URL } /v1/automation/run-sse` , {
138216 method : 'POST' ,
139217 headers : {
140218 'X-API-Key' : apiKey ,
@@ -145,7 +223,10 @@ export async function consumeSseStream(
145223
146224 if ( ! response . ok ) {
147225 const errorText = await response . text ( ) ;
148- throw new NodeOperationError ( this . getNode ( ) , `API request failed with status ${ response . status } : ${ errorText } ` ) ;
226+ throw new NodeOperationError (
227+ this . getNode ( ) ,
228+ `API request failed with status ${ response . status } : ${ errorText } ` ,
229+ ) ;
149230 }
150231
151232 if ( ! response . body ) {
@@ -182,9 +263,10 @@ export async function consumeSseStream(
182263 const eventType = eventData . type as string ;
183264
184265 if ( eventType === 'STARTED' ) {
185- runId = ( eventData . runId as string ) || '' ;
266+ runId = ( eventData . run_id as string ) || ( eventData . runId as string ) || '' ;
186267 } else if ( eventType === 'STREAMING_URL' ) {
187- streamingUrl = ( eventData . streamingUrl as string ) || '' ;
268+ streamingUrl =
269+ ( eventData . streaming_url as string ) || ( eventData . streamingUrl as string ) || '' ;
188270 } else if ( eventType === 'PROGRESS' ) {
189271 lastProgress = ( eventData . purpose as string ) || '' ;
190272 } else if ( eventType === 'COMPLETE' ) {
@@ -195,7 +277,7 @@ export async function consumeSseStream(
195277 runId,
196278 streamingUrl,
197279 lastProgress,
198- resultJson : eventData . resultJson || { } ,
280+ result : eventData . result || eventData . resultJson || { } ,
199281 } ;
200282 } else {
201283 finalResult = {
@@ -212,10 +294,7 @@ export async function consumeSseStream(
212294 }
213295
214296 if ( ! finalResult ) {
215- throw new NodeOperationError (
216- this . getNode ( ) ,
217- 'SSE stream ended without a COMPLETE event' ,
218- ) ;
297+ throw new NodeOperationError ( this . getNode ( ) , 'SSE stream ended without a COMPLETE event' ) ;
219298 }
220299
221300 return finalResult ;
0 commit comments