Skip to content

Commit 8df8044

Browse files
waleedlatif1claude
andauthored
feat(tinyfish): surface run profile diagnostics and a wall-clock cap (#7489)
* feat(tinyfish): surface run profile diagnostics and a wall-clock cap Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ej1gsFPVzN5xZZ3QzvK8bD * fix(tinyfish): reject a malformed numeric cap instead of dropping it Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ej1gsFPVzN5xZZ3QzvK8bD * docs(tinyfish): document the null case on the profile metadata fields Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ej1gsFPVzN5xZZ3QzvK8bD * docs(tinyfish): describe a null profileId as unknown, not as proof Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ej1gsFPVzN5xZZ3QzvK8bD --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 94aa4be commit 8df8044

9 files changed

Lines changed: 309 additions & 21 deletions

File tree

apps/docs/content/docs/integrations/tinyfish.mdx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ Run a TinyFish web agent against a website and wait for it to finish, returning
7878
| `browserProfile` | string | No | Browser engine: "lite" \(standard\) or "stealth" \(anti-detection\). Not a Browser Context Profile — use useProfile for saved logins |
7979
| `agentMode` | string | No | Agent behavior: "default" or "strict" \(fail fast\) |
8080
| `maxSteps` | number | No | Maximum tool-call steps before the agent stops \(1-500, default 150\) |
81+
| `maxDurationSeconds` | number | No | Maximum wall-clock seconds before the agent stops. Unlimited by default, so a run stalled on a slow page is only bounded by the step cap |
8182
| `outputSchema` | json | No | JSON Schema draft-07 contract the run result must satisfy |
8283
| `proxyEnabled` | boolean | No | Route the run through TinyFish’s Tetra proxy |
8384
| `proxyCountryCode` | string | No | Proxy country: US, GB, CA, DE, FR, JP, or AU |
@@ -112,6 +113,10 @@ Run a TinyFish web agent against a website and wait for it to finish, returning
112113
|`retryAfter` | number | Suggested retry delay in seconds, null when not retryable |
113114
|`helpUrl` | string | Troubleshooting documentation URL |
114115
|`helpMessage` | string | Human-readable guidance |
116+
| `profileHint` | object | Present when TinyFish believes a Browser Context Profile would fix this failed run, such as one that stopped at a login wall |
117+
|`message` | string | Why a Browser Context Profile would help this run |
118+
|`setupUrl` | string | Path on the TinyFish dashboard that sets up a profile for the blocked domain |
119+
|`reason` | string | auth_wall \(the run hit a login\) or bot_challenge \(the site blocked automation\) |
115120

116121
### TinyFish Start Agent Run
117122

@@ -126,6 +131,7 @@ Queue a TinyFish web agent run and return its run ID immediately, without waitin
126131
| `browserProfile` | string | No | Browser engine: "lite" \(standard\) or "stealth" \(anti-detection\). Not a Browser Context Profile — use useProfile for saved logins |
127132
| `agentMode` | string | No | Agent behavior: "default" or "strict" \(fail fast\) |
128133
| `maxSteps` | number | No | Maximum tool-call steps before the agent stops \(1-500, default 150\) |
134+
| `maxDurationSeconds` | number | No | Maximum wall-clock seconds before the agent stops. Unlimited by default, so a run stalled on a slow page is only bounded by the step cap |
129135
| `outputSchema` | json | No | JSON Schema draft-07 contract the run result must satisfy |
130136
| `proxyEnabled` | boolean | No | Route the run through TinyFish’s Tetra proxy |
131137
| `proxyCountryCode` | string | No | Proxy country: US, GB, CA, DE, FR, JP, or AU |
@@ -184,6 +190,12 @@ Get the status, extracted result, and step history of a TinyFish automation run
184190
| `browserConfig` | object | Proxy settings the run executed with |
185191
|`proxyEnabled` | boolean | Whether a proxy was used |
186192
|`proxyCountryCode` | string | Proxy country code |
193+
| `profileAttached` | boolean | Whether the run actually started from a Browser Context Profile, null when the API omits it — treat null as unknown rather than as false |
194+
| `profileId` | string | Browser Context Profile the run attached. Null covers both no profile and a payload that omitted the field, so read profileAttached alongside it rather than reading null as proof |
195+
| `profileHint` | object | Present when TinyFish believes a Browser Context Profile would fix this failed run |
196+
|`message` | string | Why a Browser Context Profile would help this run |
197+
|`setupUrl` | string | Path on the TinyFish dashboard that sets up a profile for the blocked domain |
198+
|`reason` | string | auth_wall \(the run hit a login\) or bot_challenge \(the site blocked automation\) |
187199
| `videoUrl` | string | Presigned recording URL, expires 15 minutes after it is issued |
188200
| `steps` | array | Steps the agent took during the run |
189201
|`id` | string | Step identifier |
@@ -261,6 +273,12 @@ List TinyFish automation runs, optionally filtered by status, goal text, or crea
261273
|`browserConfig` | object | Proxy settings the run executed with |
262274
|`proxyEnabled` | boolean | Whether a proxy was used |
263275
|`proxyCountryCode` | string | Proxy country code |
276+
|`profileAttached` | boolean | Whether the run actually started from a Browser Context Profile, null when the API omits it — treat null as unknown rather than as false |
277+
|`profileId` | string | Browser Context Profile the run attached. Null covers both no profile and a payload that omitted the field, so read profileAttached alongside it rather than reading null as proof |
278+
|`profileHint` | object | Present when TinyFish believes a Browser Context Profile would fix this failed run |
279+
|`message` | string | Why a Browser Context Profile would help this run |
280+
|`setupUrl` | string | Path on the TinyFish dashboard that sets up a profile for the blocked domain |
281+
|`reason` | string | auth_wall \(the run hit a login\) or bot_challenge \(the site blocked automation\) |
264282
| `total` | number | Total runs matching the filters |
265283
| `nextCursor` | string | Cursor for the next page, null when there are no more results |
266284
| `hasMore` | boolean | Whether more results follow this page |
@@ -371,7 +389,9 @@ List the Browser Context Profiles saved on the TinyFish account, with the ids an
371389
|`name` | string | Profile name, such as "Salesforce Production" |
372390
|`proxyCountryCode` | string | Country the profile proxies through, null when it has no proxy |
373391
|`fingerprintSeed` | string | Seed for the browser fingerprint the profile replays, null when the API omits it |
392+
|`domainCount` | number | How many domains the profile holds saved state for, null when the API omits it. Zero means it was created but never logged into |
374393
|`createdAt` | string | ISO 8601 timestamp when the profile was created, null when the API omits it |
375-
|`isDefault` | boolean | Whether runs with no Browser Profile ID use this one, null when the API does not report it |
394+
|`updatedAt` | string | ISO 8601 timestamp when the profile was last saved, null when the API omits it |
395+
|`isDefault` | boolean | Whether runs with no Browser Profile ID use this one, null when the API omits it |
376396

377397

apps/sim/blocks/blocks/tinyfish.ts

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,15 @@ Return ONLY the JSON Schema - no explanations, no extra text.`,
181181
condition: { field: 'operation', value: AUTOMATION_OPERATIONS },
182182
mode: 'advanced',
183183
},
184+
{
185+
id: 'maxDurationSeconds',
186+
title: 'Max Duration (seconds)',
187+
type: 'short-input',
188+
placeholder: '300',
189+
description: 'Wall-clock limit before the agent stops. Unlimited when empty',
190+
condition: { field: 'operation', value: AUTOMATION_OPERATIONS },
191+
mode: 'advanced',
192+
},
184193
{
185194
id: 'proxyEnabled',
186195
title: 'Use Proxy',
@@ -492,11 +501,30 @@ Return ONLY the comma-separated URL list - no explanations, no extra text.`,
492501
params: (params) => {
493502
const result: Record<string, unknown> = {}
494503

504+
/**
505+
* A numeric text input that does not parse is rejected rather than
506+
* dropped. Omitting it silently hands the run TinyFish's default — no
507+
* wall-clock limit at all for Max Duration — while the block still
508+
* shows the value the user typed.
509+
*/
510+
const toFiniteNumber = (value: string, field: string): number => {
511+
const parsed = Number(value)
512+
if (!Number.isFinite(parsed)) {
513+
throw new Error(`Invalid numeric value for ${field}: ${value}`)
514+
}
515+
return parsed
516+
}
517+
495518
const maxSteps = String(params.maxSteps ?? '').trim()
496-
if (maxSteps) result.maxSteps = Number(maxSteps)
519+
if (maxSteps) result.maxSteps = toFiniteNumber(maxSteps, 'Max Steps')
520+
521+
const maxDurationSeconds = String(params.maxDurationSeconds ?? '').trim()
522+
if (maxDurationSeconds) {
523+
result.maxDurationSeconds = toFiniteNumber(maxDurationSeconds, 'Max Duration (seconds)')
524+
}
497525

498526
const limit = String(params.limit ?? '').trim()
499-
if (limit) result.limit = Number(limit)
527+
if (limit) result.limit = toFiniteNumber(limit, 'Limit')
500528

501529
/**
502530
* The list filter has its own sub-block id so it does not collide with
@@ -520,6 +548,10 @@ Return ONLY the comma-separated URL list - no explanations, no extra text.`,
520548
browserProfile: { type: 'string', description: 'Browser engine: lite or stealth' },
521549
agentMode: { type: 'string', description: 'Agent behavior: default or strict' },
522550
maxSteps: { type: 'number', description: 'Maximum agent steps' },
551+
maxDurationSeconds: {
552+
type: 'number',
553+
description: 'Maximum wall-clock seconds before the agent stops',
554+
},
523555
proxyEnabled: { type: 'boolean', description: 'Route the run through the Tetra proxy' },
524556
proxyCountryCode: { type: 'string', description: 'Proxy country code' },
525557
useVault: { type: 'boolean', description: 'Allow vault credentials during the run' },
@@ -578,7 +610,7 @@ Return ONLY the comma-separated URL list - no explanations, no extra text.`,
578610
runs: {
579611
type: 'json',
580612
description:
581-
'Runs matching the list filters [{runId, status, goal, createdAt, startedAt, finishedAt, numOfSteps, result, schemaValidation, error, streamingUrl, browserConfig}]',
613+
'Runs matching the list filters [{runId, status, goal, createdAt, startedAt, finishedAt, numOfSteps, result, schemaValidation, error, streamingUrl, browserConfig, profileAttached, profileId, profileHint}]',
582614
},
583615
total: { type: 'number', description: 'Total runs matching the list filters' },
584616
nextCursor: { type: 'string', description: 'Cursor for the next page of runs' },
@@ -596,10 +628,23 @@ Return ONLY the comma-separated URL list - no explanations, no extra text.`,
596628
description:
597629
'Vault credentials available to a run [{itemId, connectionId, label, vaultName, domains, fieldMetadata, hasTotp}]',
598630
},
631+
profileId: {
632+
type: 'string',
633+
description: 'Browser Context Profile the run attached, null when none did',
634+
},
635+
profileAttached: {
636+
type: 'boolean',
637+
description: 'Whether the run actually started from a Browser Context Profile',
638+
},
639+
profileHint: {
640+
type: 'json',
641+
description:
642+
'Set when TinyFish believes a Browser Context Profile would fix a failed run {message, setupUrl, reason} — reason is auth_wall or bot_challenge',
643+
},
599644
profiles: {
600645
type: 'json',
601646
description:
602-
'Browser Context Profiles a run can start from [{profileId, name, proxyCountryCode, fingerprintSeed, createdAt, isDefault}]. Every field but profileId and name can be null when the API omits it',
647+
'Browser Context Profiles a run can start from [{profileId, name, proxyCountryCode, fingerprintSeed, domainCount, createdAt, updatedAt, isDefault}]. Every field but profileId and name can be null when the API omits it',
603648
},
604649
},
605650
}
@@ -718,7 +763,7 @@ export const TinyFishBlockMeta = {
718763
description:
719764
'Read a failed TinyFish run and decide whether to retry, reword the goal, or escalate. Use when an automation returns FAILED or a workflow keeps burning steps without a result.',
720765
content:
721-
'# Diagnose A Failed Run\n\nA failed automation comes back as a normal 200 response with the failure inside the run, so read `status` before trusting `result`.\n\n## Steps\n1. Read `error.category`. `AGENT_FAILURE` means the goal or the page is the problem — reword the goal or start the run closer to the target. `SYSTEM_FAILURE` is TinyFish-side; wait `error.retryAfter` seconds and retry the same input. `BILLING_FAILURE` means the TinyFish wallet is empty and no retry will help. `UNKNOWN` should be treated as retryable once.\n2. Compare `numOfSteps` against the Max Steps you set. Hitting the cap means the agent was still working, so raise the cap or narrow the goal.\n3. If an Output Schema was set, read `schemaValidation.errors` — a run can reach the right page and still fail on one mistyped field, and `rePromptAttempts` shows how hard TinyFish already tried to repair it.\n4. For an async run, call Get Run and read the `steps` list to find the last action before the failure. `videoUrl` gives a recording, but the link expires 15 minutes after it is issued.\n\n## Output\nState the category, the concrete cause, and the single next action. Quote `error.message` rather than paraphrasing it, and do not retry a `BILLING_FAILURE` or an `AGENT_FAILURE` without changing the input first.',
766+
'# Diagnose A Failed Run\n\nA failed automation comes back as a normal 200 response with the failure inside the run, so read `status` before trusting `result`.\n\n## Steps\n1. Read `error.category`. `AGENT_FAILURE` means the goal or the page is the problem — reword the goal or start the run closer to the target. `SYSTEM_FAILURE` is TinyFish-side; wait `error.retryAfter` seconds and retry the same input. `BILLING_FAILURE` means the TinyFish wallet is empty and no retry will help. `UNKNOWN` should be treated as retryable once.\n2. Read `profileHint` whenever it is set — TinyFish attaches it only when a Browser Context Profile would likely fix the run, so it outranks the category above. `reason` is `auth_wall` (the agent hit a login) or `bot_challenge` (the site blocked automation); the fix is to set up a profile for that domain and rerun with Use Browser Profile on, not to reword the goal.\n3. Compare `numOfSteps` against the Max Steps you set. Hitting the cap means the agent was still working, so raise the cap or narrow the goal. If the run instead stopped early on a slow page, set Max Duration.\n4. If an Output Schema was set, read `schemaValidation.errors` — a run can reach the right page and still fail on one mistyped field, and `rePromptAttempts` shows how hard TinyFish already tried to repair it.\n5. For an async run, call Get Run and read the `steps` list to find the last action before the failure. `videoUrl` gives a recording, but the link expires 15 minutes after it is issued.\n\n## Output\nState the category, the concrete cause, and the single next action. Quote `error.message` rather than paraphrasing it, and do not retry a `BILLING_FAILURE` or an `AGENT_FAILURE` without changing the input first.',
722767
},
723768
{
724769
name: 'queue-and-track-long-runs',

apps/sim/tools/generated/tool-metadata.ts

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

apps/sim/tools/generated/tool-outputs.ts

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

apps/sim/tools/tinyfish/list_profiles.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ import type { ToolConfig } from '@/tools/types'
1414
/**
1515
* Reads the profile array out of the list payload.
1616
*
17-
* TinyFish publishes no example response for `GET /v1/profiles`, so all three
18-
* shapes a list endpoint plausibly returns are accepted rather than betting on
19-
* one: a bare array, and the `profiles` and `data` envelopes its other list
20-
* endpoints use.
17+
* The OpenAPI spec documents a `{ profiles: [...] }` envelope, which is what
18+
* this reads first. The bare array and the `data` envelope its other list
19+
* endpoints use stay accepted because the prose docs show no example at all and
20+
* tolerating them costs nothing.
2121
*/
2222
function extractProfiles(data: TinyFishRawProfiles | TinyFishRawProfile[]): TinyFishRawProfile[] {
2323
if (Array.isArray(data)) return data
@@ -72,8 +72,10 @@ export const listProfilesTool: ToolConfig<
7272
name: profile?.name ?? '',
7373
proxyCountryCode: profile?.proxy_country_code ?? null,
7474
fingerprintSeed: profile?.fingerprint_seed ?? null,
75+
domainCount: profile?.domain_count ?? null,
7576
createdAt: profile?.created_at ?? null,
76-
isDefault: profile?.set_as_default ?? profile?.is_default ?? null,
77+
updatedAt: profile?.updated_at ?? null,
78+
isDefault: profile?.is_default ?? null,
7779
})),
7880
},
7981
}
@@ -104,17 +106,31 @@ export const listProfilesTool: ToolConfig<
104106
optional: true,
105107
nullable: true,
106108
},
109+
domainCount: {
110+
type: 'number',
111+
description:
112+
'How many domains the profile holds saved state for, null when the API omits it. Zero means it was created but never logged into',
113+
optional: true,
114+
nullable: true,
115+
},
107116
createdAt: {
108117
type: 'string',
109118
description:
110119
'ISO 8601 timestamp when the profile was created, null when the API omits it',
111120
optional: true,
112121
nullable: true,
113122
},
123+
updatedAt: {
124+
type: 'string',
125+
description:
126+
'ISO 8601 timestamp when the profile was last saved, null when the API omits it',
127+
optional: true,
128+
nullable: true,
129+
},
114130
isDefault: {
115131
type: 'boolean',
116132
description:
117-
'Whether runs with no Browser Profile ID use this one, null when the API does not report it',
133+
'Whether runs with no Browser Profile ID use this one, null when the API omits it',
118134
optional: true,
119135
nullable: true,
120136
},

apps/sim/tools/tinyfish/run.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { tinyfishAgentHosting } from '@/tools/tinyfish/hosting'
22
import {
3+
PROFILE_HINT_OUTPUT_PROPERTIES,
34
RUN_ERROR_OUTPUT_PROPERTIES,
45
SCHEMA_VALIDATION_OUTPUT_PROPERTIES,
56
type TinyFishRawRun,
@@ -9,6 +10,7 @@ import {
910
import {
1011
AUTOMATION_TOOL_PARAMS,
1112
buildAutomationBody,
13+
mapProfileHint,
1214
mapRunError,
1315
mapSchemaValidation,
1416
selectAutomationModelInput,
@@ -62,6 +64,7 @@ export const runTool: ToolConfig<TinyFishRunParams, TinyFishRunResponse> = {
6264
result: data.result ?? null,
6365
schemaValidation: mapSchemaValidation(data.schema_validation),
6466
error: mapRunError(data.error),
67+
profileHint: mapProfileHint(data.profile_hint),
6568
},
6669
error: data.error?.message ?? undefined,
6770
}
@@ -99,5 +102,13 @@ export const runTool: ToolConfig<TinyFishRunParams, TinyFishRunResponse> = {
99102
optional: true,
100103
properties: RUN_ERROR_OUTPUT_PROPERTIES,
101104
},
105+
profileHint: {
106+
type: 'object',
107+
description:
108+
'Present when TinyFish believes a Browser Context Profile would fix this failed run, such as one that stopped at a login wall',
109+
optional: true,
110+
nullable: true,
111+
properties: PROFILE_HINT_OUTPUT_PROPERTIES,
112+
},
102113
},
103114
}

0 commit comments

Comments
 (0)