Skip to content

Commit 28882e7

Browse files
committed
Add TinyFish n8n search, fetch, and browser endpoints
Adds n8n operations for Search Web, Fetch Content, Create Browser Session, and Terminate Browser Session alongside the existing agent run operations. Updates TinyFish API routing, operation descriptions, docs links, package metadata, and the n8n publish workflow for trusted publishing.
1 parent 521d646 commit 28882e7

8 files changed

Lines changed: 391 additions & 83 deletions

File tree

.github/workflows/n8n-publish.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@ jobs:
2020

2121
- uses: actions/setup-node@v4
2222
with:
23-
node-version: '22'
23+
node-version: '22.14.0'
2424
registry-url: 'https://registry.npmjs.org'
2525

26+
- run: npm install -g npm@latest
27+
2628
- run: npm install
2729

2830
- run: npm run lint
2931

3032
- run: npm run build
3133

32-
- run: npm publish --provenance --access public
33-
env:
34-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
34+
- run: npm publish --access public

n8n/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Overview
44

5-
[TinyFish Web Agent](https://docs.mino.ai/) enables enterprises, builders, and developers to deploy AI agents that navigate real sites, complete real workflows across authenticated systems and dynamic interfaces, and return structured operational intelligence - through our visual platform or API. At scale. Reliably.
5+
[TinyFish Web Agent](https://docs.tinyfish.ai/) enables enterprises, builders, and developers to deploy AI agents that navigate real sites, complete real workflows across authenticated systems and dynamic interfaces, and return structured operational intelligence - through our visual platform or API. At scale. Reliably.
66

77
## Configuration
88

@@ -158,7 +158,7 @@ After confirming that the node works properly, submit a pull request to the `mai
158158

159159
## Resources
160160

161-
- [Documentation](https://docs.mino.ai/)
161+
- [Documentation](https://docs.tinyfish.ai/)
162162
- [Cookbook](https://github.com/tinyfish-io/tinyfish-cookbook)
163163
- [Templates](https://agent.tinyfish.ai/examples)
164164

n8n/credentials/TinyfishApi.credentials.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export class TinyfishApi implements ICredentialType {
1212

1313
icon = 'file:tinyfish.svg' as const;
1414

15-
documentationUrl = 'https://docs.mino.ai';
15+
documentationUrl = 'https://docs.tinyfish.ai';
1616

1717
properties: INodeProperties[] = [
1818
{

n8n/nodes/Tinyfish/GenericFunctions.ts

Lines changed: 97 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import type {
77
} from 'n8n-workflow';
88
import { 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;

n8n/nodes/Tinyfish/Tinyfish.node.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,17 @@
66
"resources": {
77
"primaryDocumentation": [
88
{
9-
"url": "https://docs.mino.ai"
9+
"url": "https://docs.tinyfish.ai"
1010
}
1111
],
1212
"credentialDocumentation": [
1313
{
14-
"url": "https://docs.mino.ai/authentication"
14+
"url": "https://docs.tinyfish.ai/authentication"
1515
}
1616
]
1717
},
1818
"alias": [
1919
"tinyfish",
20-
"mino",
2120
"web automation",
2221
"browser automation",
2322
"web scraping",

0 commit comments

Comments
 (0)