Skip to content

Commit 01c3cc6

Browse files
waleedlatif1claude
andcommitted
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
1 parent 94aa4be commit 01c3cc6

9 files changed

Lines changed: 262 additions & 17 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 |
194+
| `profileId` | string | Browser Context Profile the run attached, null when none did |
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 |
277+
|`profileId` | string | Browser Context Profile the run attached, null when none did |
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. 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: 30 additions & 1 deletion
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',
@@ -495,6 +504,9 @@ Return ONLY the comma-separated URL list - no explanations, no extra text.`,
495504
const maxSteps = String(params.maxSteps ?? '').trim()
496505
if (maxSteps) result.maxSteps = Number(maxSteps)
497506

507+
const maxDurationSeconds = String(params.maxDurationSeconds ?? '').trim()
508+
if (maxDurationSeconds) result.maxDurationSeconds = Number(maxDurationSeconds)
509+
498510
const limit = String(params.limit ?? '').trim()
499511
if (limit) result.limit = Number(limit)
500512

@@ -520,6 +532,10 @@ Return ONLY the comma-separated URL list - no explanations, no extra text.`,
520532
browserProfile: { type: 'string', description: 'Browser engine: lite or stealth' },
521533
agentMode: { type: 'string', description: 'Agent behavior: default or strict' },
522534
maxSteps: { type: 'number', description: 'Maximum agent steps' },
535+
maxDurationSeconds: {
536+
type: 'number',
537+
description: 'Maximum wall-clock seconds before the agent stops',
538+
},
523539
proxyEnabled: { type: 'boolean', description: 'Route the run through the Tetra proxy' },
524540
proxyCountryCode: { type: 'string', description: 'Proxy country code' },
525541
useVault: { type: 'boolean', description: 'Allow vault credentials during the run' },
@@ -596,6 +612,19 @@ Return ONLY the comma-separated URL list - no explanations, no extra text.`,
596612
description:
597613
'Vault credentials available to a run [{itemId, connectionId, label, vaultName, domains, fieldMetadata, hasTotp}]',
598614
},
615+
profileId: {
616+
type: 'string',
617+
description: 'Browser Context Profile the run attached, null when none did',
618+
},
619+
profileAttached: {
620+
type: 'boolean',
621+
description: 'Whether the run actually started from a Browser Context Profile',
622+
},
623+
profileHint: {
624+
type: 'json',
625+
description:
626+
'Set when TinyFish believes a Browser Context Profile would fix a failed run {message, setupUrl, reason} — reason is auth_wall or bot_challenge',
627+
},
599628
profiles: {
600629
type: 'json',
601630
description:
@@ -718,7 +747,7 @@ export const TinyFishBlockMeta = {
718747
description:
719748
'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.',
720749
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.',
750+
'# 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.',
722751
},
723752
{
724753
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. 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
}

apps/sim/tools/tinyfish/tinyfish.test.ts

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,16 @@ describe('buildAutomationBody', () => {
4444
expect(body.agent_config).toEqual({ mode: 'strict', max_steps: 50 })
4545
})
4646

47+
it('sends a wall-clock cap alongside the step cap', () => {
48+
const body = buildAutomationBody({
49+
url: 'https://example.com',
50+
goal: 'Find pricing',
51+
maxSteps: 50,
52+
maxDurationSeconds: 300,
53+
})
54+
expect(body.agent_config).toEqual({ max_steps: 50, max_duration_seconds: 300 })
55+
})
56+
4757
it('omits agent_config entirely when neither field is set', () => {
4858
const body = buildAutomationBody({ url: 'https://example.com', goal: 'Find pricing' })
4959
expect(body).not.toHaveProperty('agent_config')
@@ -227,6 +237,7 @@ describe('tinyfish_run', () => {
227237
result: { price: 799 },
228238
schemaValidation: { valid: true, rePromptAttempts: 0, errors: [] },
229239
error: null,
240+
profileHint: null,
230241
})
231242
})
232243

@@ -653,6 +664,64 @@ describe('tinyfish_list_vault_items', () => {
653664
})
654665
})
655666

667+
describe('tinyfish profile diagnostics', () => {
668+
it('surfaces the profile nudge on a failed synchronous run', async () => {
669+
const result = await runTool.transformResponse!(
670+
jsonResponse({
671+
run_id: 'run_1',
672+
status: 'FAILED',
673+
num_of_steps: 4,
674+
error: { message: 'Blocked', category: 'AGENT_FAILURE' },
675+
profile_hint: {
676+
message: 'Set up a Browser Context Profile to sign in first',
677+
setup_url: '/profiles/prof_abc123/setup?domain=example.com',
678+
reason: 'auth_wall',
679+
},
680+
})
681+
)
682+
683+
expect(result.success).toBe(false)
684+
expect(result.output.profileHint).toEqual({
685+
message: 'Set up a Browser Context Profile to sign in first',
686+
setupUrl: '/profiles/prof_abc123/setup?domain=example.com',
687+
reason: 'auth_wall',
688+
})
689+
})
690+
691+
it('reports no hint rather than a half-filled one when a field is missing', async () => {
692+
const result = await runTool.transformResponse!(
693+
jsonResponse({
694+
run_id: 'run_1',
695+
status: 'FAILED',
696+
profile_hint: { message: 'Try a profile', reason: 'bot_challenge' },
697+
})
698+
)
699+
expect(result.output.profileHint).toBeNull()
700+
})
701+
702+
it('reports which profile a run actually attached', async () => {
703+
const result = await getRunTool.transformResponse!(
704+
jsonResponse({
705+
run_id: 'run_1',
706+
status: 'COMPLETED',
707+
profile_attached: true,
708+
profile_id: 'prof_abc123',
709+
})
710+
)
711+
expect(result.output.profileAttached).toBe(true)
712+
expect(result.output.profileId).toBe('prof_abc123')
713+
})
714+
715+
it('leaves the profile fields null on a run that attached none', async () => {
716+
const result = await getRunTool.transformResponse!(
717+
jsonResponse({ run_id: 'run_1', status: 'COMPLETED' })
718+
)
719+
expect(result.output.profileAttached).toBeNull()
720+
expect(result.output.profileId).toBeNull()
721+
expect(result.output.profileHint).toBeNull()
722+
})
723+
})
724+
656725
describe('tinyfish profile visibility', () => {
657726
it('keeps the profile fields out of the schema the agent model writes', () => {
658727
for (const tool of [runTool, runAsyncTool]) {
@@ -672,8 +741,10 @@ describe('tinyfish_list_profiles', () => {
672741
name: 'Salesforce Production',
673742
proxy_country_code: 'US',
674743
fingerprint_seed: '12345678',
744+
domain_count: 3,
675745
created_at: '2026-06-04T18:00:00.000Z',
676-
set_as_default: true,
746+
updated_at: '2026-06-05T09:30:00.000Z',
747+
is_default: true,
677748
},
678749
],
679750
})
@@ -684,7 +755,9 @@ describe('tinyfish_list_profiles', () => {
684755
name: 'Salesforce Production',
685756
proxyCountryCode: 'US',
686757
fingerprintSeed: '12345678',
758+
domainCount: 3,
687759
createdAt: '2026-06-04T18:00:00.000Z',
760+
updatedAt: '2026-06-05T09:30:00.000Z',
688761
isDefault: true,
689762
})
690763
})

0 commit comments

Comments
 (0)