Skip to content

Commit de8eddd

Browse files
fix(tools): carry Sim's own status through the tool-response boundary
1 parent c5c5d3e commit de8eddd

4 files changed

Lines changed: 63 additions & 0 deletions

File tree

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ export class GenericBlockHandler implements BlockHandler {
9494
blockName: block.metadata?.name || 'Unnamed Block',
9595
output: result.output || {},
9696
timestamp: new Date().toISOString(),
97+
// `executeTool` flattens a thrown error into a result, so Sim's own
98+
// status (hosted-key 429/503) would be lost here. Carry it onto the
99+
// error so `getExecutionErrorStatus` can still reach the API caller.
100+
...(typeof result.statusCode === 'number' ? { statusCode: result.statusCode } : {}),
97101
})
98102

99103
throw error

apps/sim/executor/utils/errors.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,50 @@ describe('buildBlockExecutionError', () => {
8383
expect(wrapped.cause).toBeUndefined()
8484
})
8585
})
86+
87+
describe('hosted-key status survives the ToolResponse flattening', () => {
88+
/**
89+
* `executeTool` catches a thrown error and returns a `ToolResponse`, so the
90+
* status has to ride `ToolResponse.statusCode` and be re-attached by
91+
* `generic-handler`. This reproduces that hand-off end to end.
92+
*/
93+
function errorFromFailedToolResponse(response: {
94+
error: string
95+
output: Record<string, unknown>
96+
statusCode?: number
97+
}): Error {
98+
const error = new Error(response.error)
99+
Object.assign(error, {
100+
output: response.output,
101+
...(typeof response.statusCode === 'number' ? { statusCode: response.statusCode } : {}),
102+
})
103+
return buildBlockExecutionError({ block, error })
104+
}
105+
106+
it('forwards a hosted-key 429 to the API caller', () => {
107+
const wrapped = errorFromFailedToolResponse({
108+
error: 'Rate limit exceeded',
109+
output: {},
110+
statusCode: 429,
111+
})
112+
expect(getExecutionErrorStatus(wrapped)).toBe(429)
113+
})
114+
115+
it('forwards a hosted-key 503 to the API caller', () => {
116+
const wrapped = errorFromFailedToolResponse({
117+
error: 'No hosted keys configured',
118+
output: {},
119+
statusCode: 503,
120+
})
121+
expect(getExecutionErrorStatus(wrapped)).toBe(503)
122+
})
123+
124+
it("never adopts an upstream provider's status as our own", () => {
125+
// A provider 404 rides `output`, never `statusCode`, so it must not surface.
126+
const wrapped = errorFromFailedToolResponse({
127+
error: 'HTTP 404: Not Found',
128+
output: { status: 404, statusText: 'Not Found' },
129+
})
130+
expect(getExecutionErrorStatus(wrapped)).toBe(500)
131+
})
132+
})

apps/sim/tools/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
validateUrlWithDNS,
1919
} from '@/lib/core/security/input-validation.server'
2020
import { PlatformEvents } from '@/lib/core/telemetry'
21+
import { HttpError } from '@/lib/core/utils/http-error'
2122
import { generateRequestId } from '@/lib/core/utils/request'
2223
import {
2324
isPayloadSizeLimitError,
@@ -1502,6 +1503,10 @@ export async function executeTool(
15021503
success: false,
15031504
output: errorDetails,
15041505
error: errorMessage,
1506+
// Sim's own status (hosted-key 429/503) survives the flattening from a
1507+
// thrown error into a result object; an upstream provider's status stays
1508+
// on `output` where it cannot be mistaken for ours.
1509+
...(error instanceof HttpError ? { statusCode: error.statusCode } : {}),
15051510
timing: {
15061511
startTime: startTimeISO,
15071512
endTime: endTimeISO,

apps/sim/tools/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,13 @@ export interface ToolResponse {
9393
success: boolean // Whether the tool execution was successful
9494
output: Record<string, any> // The structured output from the tool
9595
error?: string // Error message if success is false
96+
/**
97+
* HTTP status owned by SIM itself (e.g. hosted-key rate limiting or
98+
* exhaustion), carried so it survives the throw → `ToolResponse` flattening
99+
* and can reach the API caller. Deliberately NOT the upstream provider's
100+
* status — a provider's 404 must never become the workflow API's status.
101+
*/
102+
statusCode?: number
96103
resources?: MothershipResource[] // Resources to auto-open/show in UI
97104
largeValueKeys?: string[]
98105
fileKeys?: string[]

0 commit comments

Comments
 (0)