Skip to content

Commit 177c6a5

Browse files
committed
fix(executor): guard against missing content in provider responses
When an LLM provider returns a 200 response without a content field, the router and evaluator handlers crash with a TypeError trying to call .trim() on undefined. This happens when a model is unavailable, rate-limited mid-stream, or returns an unexpected response structure. Add an explicit check for result.content before accessing it, matching the existing pattern in shared/response-format.ts (line 91) which already uses `result.content ?? ''` for this case. Affected handlers: - RouterBlockHandler (legacy path, line 127) - RouterBlockHandler V2 (line 287 fallback after JSON parse failure) - EvaluatorBlockHandler (line 148) Added regression tests for all three paths.
1 parent a54dcbe commit 177c6a5

File tree

4 files changed

+106
-0
lines changed

4 files changed

+106
-0
lines changed

apps/sim/executor/handlers/evaluator/evaluator-handler.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,29 @@ describe('EvaluatorBlockHandler', () => {
287287
expect((result as any).fluency).toBe(0)
288288
})
289289

290+
it('should throw a clear error when provider returns no content', async () => {
291+
const inputs = {
292+
content: 'Test content to evaluate',
293+
metrics: [{ name: 'quality', description: 'Quality', range: { min: 0, max: 10 } }],
294+
apiKey: 'test-api-key',
295+
}
296+
297+
mockFetch.mockImplementationOnce(() => {
298+
return Promise.resolve({
299+
ok: true,
300+
json: () =>
301+
Promise.resolve({
302+
model: 'gpt-4o',
303+
tokens: { input: 50, output: 0, total: 50 },
304+
}),
305+
})
306+
})
307+
308+
await expect(handler.execute(mockContext, mockBlock, inputs)).rejects.toThrow(
309+
'Provider returned an empty response'
310+
)
311+
})
312+
290313
it('should extract metric scores ignoring case', async () => {
291314
const inputs = {
292315
content: 'Test',

apps/sim/executor/handlers/evaluator/evaluator-handler.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,12 @@ export class EvaluatorBlockHandler implements BlockHandler {
145145

146146
const result = await response.json()
147147

148+
if (!result.content) {
149+
throw new Error(
150+
'Provider returned an empty response. The model may be unavailable or the request was malformed.'
151+
)
152+
}
153+
148154
const parsedContent = this.extractJSONFromResponse(result.content)
149155

150156
const metricScores = this.extractMetricScores(parsedContent, inputs.metrics)

apps/sim/executor/handlers/router/router-handler.test.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,45 @@ describe('RouterBlockHandler', () => {
244244
await expect(handler.execute(mockContext, mockBlock, inputs)).rejects.toThrow('Server error')
245245
})
246246

247+
it('should throw a clear error when provider returns no content', async () => {
248+
const inputs = { prompt: 'Choose the best option.', apiKey: 'test-api-key' }
249+
250+
mockFetch.mockImplementationOnce(() => {
251+
return Promise.resolve({
252+
ok: true,
253+
json: () =>
254+
Promise.resolve({
255+
model: 'gpt-4o',
256+
tokens: { input: 100, output: 0, total: 100 },
257+
}),
258+
})
259+
})
260+
261+
await expect(handler.execute(mockContext, mockBlock, inputs)).rejects.toThrow(
262+
'Provider returned an empty response'
263+
)
264+
})
265+
266+
it('should throw a clear error when provider content is empty string', async () => {
267+
const inputs = { prompt: 'Choose the best option.', apiKey: 'test-api-key' }
268+
269+
mockFetch.mockImplementationOnce(() => {
270+
return Promise.resolve({
271+
ok: true,
272+
json: () =>
273+
Promise.resolve({
274+
content: '',
275+
model: 'gpt-4o',
276+
tokens: { input: 100, output: 0, total: 100 },
277+
}),
278+
})
279+
})
280+
281+
await expect(handler.execute(mockContext, mockBlock, inputs)).rejects.toThrow(
282+
'Provider returned an empty response'
283+
)
284+
})
285+
247286
it('should handle Azure OpenAI models with endpoint and API version', async () => {
248287
const inputs = {
249288
prompt: 'Choose the best option.',
@@ -587,4 +626,30 @@ describe('RouterBlockHandler V2', () => {
587626
expect(result.selectedRoute).toBe('route-1')
588627
expect(result.reasoning).toBe('')
589628
})
629+
630+
it('should throw a clear error when provider returns no content (V2)', async () => {
631+
const inputs = {
632+
context: 'I need help with billing',
633+
model: 'gpt-4o',
634+
apiKey: 'test-api-key',
635+
routes: JSON.stringify([
636+
{ id: 'route-support', title: 'Support', value: 'Customer support inquiries' },
637+
]),
638+
}
639+
640+
mockFetch.mockImplementationOnce(() => {
641+
return Promise.resolve({
642+
ok: true,
643+
json: () =>
644+
Promise.resolve({
645+
model: 'gpt-4o',
646+
tokens: { input: 150, output: 0, total: 150 },
647+
}),
648+
})
649+
})
650+
651+
await expect(handler.execute(mockContext, mockRouterV2Block, inputs)).rejects.toThrow(
652+
'Provider returned an empty response'
653+
)
654+
})
590655
})

apps/sim/executor/handlers/router/router-handler.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,12 @@ export class RouterBlockHandler implements BlockHandler {
124124

125125
const result = await response.json()
126126

127+
if (!result.content) {
128+
throw new Error(
129+
'Provider returned an empty response. The model may be unavailable or the request was malformed.'
130+
)
131+
}
132+
127133
const chosenBlockId = result.content.trim().toLowerCase()
128134
const chosenBlock = targetBlocks?.find((b) => b.id === chosenBlockId)
129135

@@ -273,6 +279,12 @@ export class RouterBlockHandler implements BlockHandler {
273279

274280
const result = await response.json()
275281

282+
if (!result.content) {
283+
throw new Error(
284+
'Provider returned an empty response. The model may be unavailable or the request was malformed.'
285+
)
286+
}
287+
276288
let chosenRouteId: string
277289
let reasoning = ''
278290

0 commit comments

Comments
 (0)