Skip to content

Commit 914c9b2

Browse files
committed
fix(exa): keep legacy research model, add subblock migrations, sharpen outputs
The subblock ID stability check caught the removed subblocks — that gate exists precisely to stop removals from breaking deployed workflows. - Restore the research model sub-block, scoped to the legacy exa_research operation so it never shows for new workflows but still serializes for saved ones, and restore the model to effort mapping. Removing it lost the configured research depth, silently falling back to effort auto. - Register useAutoprompt and livecrawl in SUBBLOCK_ID_MIGRATIONS as intentional removals. Neither has a value-compatible replacement: livecrawl is a mode string and maxAgeHours a number, so mapping one to the other would send NaN. - Replace vague json output descriptions with their inner field lists. - Drop the separate compat test file; the coverage that guards real regressions now lives in exa.test.ts.
1 parent 95122fd commit 914c9b2

4 files changed

Lines changed: 106 additions & 107 deletions

File tree

apps/sim/blocks/blocks/exa.ts

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,16 @@ const CATEGORY_OPTIONS = [
2525
const LEGACY_RESEARCH_OPERATION = 'exa_research'
2626
const AGENT_OPERATIONS = ['exa_agent', LEGACY_RESEARCH_OPERATION]
2727

28+
/**
29+
* Maps the retired research models onto the Agent API's effort levels, so a
30+
* workflow that asked for a deeper (or cheaper) run still gets one.
31+
*/
32+
const RESEARCH_MODEL_TO_EFFORT: Record<string, string> = {
33+
'exa-research-fast': 'low',
34+
'exa-research': 'medium',
35+
'exa-research-pro': 'high',
36+
}
37+
2838
export const ExaBlock: BlockConfig<ExaResponse> = {
2939
type: 'exa',
3040
name: 'Exa',
@@ -411,6 +421,18 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
411421
condition: { field: 'operation', value: AGENT_OPERATIONS },
412422
mode: 'advanced',
413423
},
424+
{
425+
id: 'model',
426+
title: 'Research Model (Legacy)',
427+
type: 'dropdown',
428+
options: [
429+
{ label: 'Standard', id: 'exa-research' },
430+
{ label: 'Fast', id: 'exa-research-fast' },
431+
{ label: 'Pro', id: 'exa-research-pro' },
432+
],
433+
description: 'Retired Exa research model, carried over as an Agent effort level',
434+
condition: { field: 'operation', value: LEGACY_RESEARCH_OPERATION },
435+
},
414436
{
415437
id: 'previousRunId',
416438
title: 'Previous Run ID',
@@ -556,6 +578,10 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
556578
if (params.livecrawlTimeout) {
557579
result.livecrawlTimeout = Number(params.livecrawlTimeout)
558580
}
581+
/** Carry a retired research model over to the Agent API's effort scale. */
582+
if (params.operation === LEGACY_RESEARCH_OPERATION && params.model) {
583+
result.effort = RESEARCH_MODEL_TO_EFFORT[params.model as string] ?? 'medium'
584+
}
559585
return result
560586
},
561587
},
@@ -595,20 +621,43 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
595621
excludeSourceDomain: { type: 'boolean', description: 'Exclude source domain' },
596622
// Agent operation
597623
effort: { type: 'string', description: 'Agent effort level' },
624+
model: { type: 'string', description: 'Retired research model, mapped to an effort level' },
598625
previousRunId: { type: 'string', description: 'Agent run to continue from' },
599626
},
600627
outputs: {
601628
// Search and Get Contents output
602-
results: { type: 'json', description: 'Search or content results' },
603-
statuses: { type: 'json', description: 'Per-URL crawl outcome for Get Contents' },
604-
structuredOutput: { type: 'json', description: 'Synthesized output matching the schema' },
605-
grounding: { type: 'json', description: 'Field-level citations for generated output' },
606-
requestId: { type: 'string', description: 'Exa request identifier' },
629+
results: {
630+
type: 'json',
631+
description:
632+
'[{id, title, url, publishedDate, author, summary, favicon, image, text, highlights, highlightScores, subpages, entities, extras}]',
633+
},
634+
statuses: {
635+
type: 'json',
636+
description: 'Get Contents only. [{id, status, source, error}] — per-URL crawl outcome',
637+
},
638+
structuredOutput: {
639+
type: 'json',
640+
description: 'Search only. Synthesized output matching the supplied output schema',
641+
},
642+
grounding: {
643+
type: 'json',
644+
description: '[{field, citations, confidence}] — field-level citations for generated output',
645+
},
646+
requestId: { type: 'string', description: 'Exa request identifier, useful for support' },
607647
// Find Similar Links output
608-
similarLinks: { type: 'json', description: 'Similar links found' },
648+
similarLinks: {
649+
type: 'json',
650+
description: '[{id, title, url, text, summary, highlights, score}]',
651+
},
609652
// Answer output
610-
answer: { type: 'json', description: 'Generated answer' },
611-
citations: { type: 'json', description: 'Answer citations' },
653+
answer: {
654+
type: 'json',
655+
description: 'Generated answer — a string, or an object when an output schema is supplied',
656+
},
657+
citations: {
658+
type: 'json',
659+
description: '[{id, title, url, text, author, publishedDate}]',
660+
},
612661
// Agent output
613662
runId: { type: 'string', description: 'Agent run identifier' },
614663
status: { type: 'string', description: 'Agent run status' },
@@ -617,7 +666,8 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
617666
structured: { type: 'json', description: 'Agent structured result' },
618667
research: {
619668
type: 'json',
620-
description: 'Agent answer in the retired Research operation output shape',
669+
description:
670+
'[{title, url, summary, text, score}] — the agent answer in the retired Research operation shape, so saved workflows keep resolving',
621671
},
622672
},
623673
}

apps/sim/lib/workflows/migrations/subblock-migrations.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,18 @@ export const SUBBLOCK_ID_MIGRATIONS: Record<string, Record<string, string>> = {
6565
stage_ids: '_removed_stage_ids',
6666
owner_ids: '_removed_owner_ids',
6767
},
68+
exa: {
69+
/**
70+
* Exa deprecated both fields. `useAutoprompt` is gone from the API, and
71+
* `livecrawl` is superseded by `maxAgeHours` — but their values are not
72+
* interchangeable (`livecrawl` is a mode string, `maxAgeHours` a number),
73+
* so mapping one onto the other would send `NaN`. Dropping `livecrawl` is
74+
* also the fix for the block having defaulted it to `never`, which pinned
75+
* every saved search to cached results.
76+
*/
77+
useAutoprompt: '_removed_useAutoprompt',
78+
livecrawl: '_removed_livecrawl',
79+
},
6880
rippling: {
6981
action: '_removed_action',
7082
candidateDepartment: '_removed_candidateDepartment',

apps/sim/tools/exa/exa-compat.test.ts

Lines changed: 0 additions & 98 deletions
This file was deleted.

apps/sim/tools/exa/exa.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,27 @@ describe('exa block', () => {
170170
expect(ExaBlock.tools.config?.tool?.({ operation: 'exa_research' })).toBe('exa_agent')
171171
})
172172

173+
it('carries a saved research model over to an agent effort level', () => {
174+
const params = ExaBlock.tools.config?.params?.({
175+
operation: 'exa_research',
176+
model: 'exa-research-pro',
177+
}) as Record<string, unknown>
178+
expect(params.effort).toBe('high')
179+
})
180+
181+
it('keeps the legacy model sub-block so the serializer preserves its value', () => {
182+
const model = ExaBlock.subBlocks.find((block) => block.id === 'model')
183+
expect(model?.condition?.value).toBe('exa_research')
184+
})
185+
186+
it('does not map a model value on non-research operations', () => {
187+
const params = ExaBlock.tools.config?.params?.({
188+
operation: 'exa_agent',
189+
model: 'exa-research-pro',
190+
}) as Record<string, unknown>
191+
expect(params.effort).toBeUndefined()
192+
})
193+
173194
it('coerces maxAgeHours of 0 rather than dropping it as falsy', () => {
174195
const params = ExaBlock.tools.config?.params?.({
175196
operation: 'exa_search',
@@ -227,6 +248,20 @@ describe('exa_agent terminal statuses', () => {
227248
expect(result?.error).toMatch(/cancelled/)
228249
})
229250

251+
it('emits the retired research output shape so saved references still resolve', async () => {
252+
const result = await agentTool.postProcess?.(
253+
{
254+
success: true,
255+
output: { runId: 'agent_run_1', status: 'completed', text: 'the answer' },
256+
} as never,
257+
{ apiKey: API_KEY, query: 'q' } as never,
258+
{} as never
259+
)
260+
expect(result?.output.research).toEqual([
261+
{ title: 'Research Complete', url: '', summary: 'the answer', text: 'the answer', score: 1 },
262+
])
263+
})
264+
230265
it('falls back to the structured payload when a completed run has no text', async () => {
231266
const result = await settle('completed')
232267
expect(result?.success).toBe(true)

0 commit comments

Comments
 (0)