Skip to content

Commit bac3672

Browse files
committed
Improve mship fexecute
1 parent bdf9ffc commit bac3672

5 files changed

Lines changed: 116 additions & 6 deletions

File tree

apps/sim/lib/copilot/generated/tool-catalog-v1.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,6 +1040,11 @@ export const FunctionExecute: ToolCatalogEntry = {
10401040
description:
10411041
'Table ID to overwrite with the code\'s return value. Code MUST return an array of objects where keys match column names. All existing rows are replaced. Example: "tbl_abc123"',
10421042
},
1043+
timeout: {
1044+
type: 'number',
1045+
description:
1046+
'Optional maximum execution time in seconds. If omitted, Copilot sends 10 seconds by default. Override when needed; capped at the default execution limit.',
1047+
},
10431048
},
10441049
required: ['code'],
10451050
},
@@ -2964,8 +2969,7 @@ export const UserTable: ToolCatalogEntry = {
29642969
},
29652970
rowIds: {
29662971
type: 'array',
2967-
description:
2968-
'Array of row IDs. Used by batch_delete_rows (rows to delete) and run_column (optional row scope — when omitted, runs across the whole table; when provided, only these rows are candidates and the server eligibility predicate still applies).',
2972+
description: 'Array of row IDs to delete (for batch_delete_rows)',
29692973
items: { type: 'string' },
29702974
},
29712975
rows: {

apps/sim/lib/copilot/generated/tool-schemas-v1.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
export type JsonSchema = unknown
66

7-
interface ToolRuntimeSchemaEntry {
7+
export interface ToolRuntimeSchemaEntry {
88
parameters?: JsonSchema
99
resultSchema?: JsonSchema
1010
}
@@ -863,6 +863,11 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
863863
description:
864864
'Table ID to overwrite with the code\'s return value. Code MUST return an array of objects where keys match column names. All existing rows are replaced. Example: "tbl_abc123"',
865865
},
866+
timeout: {
867+
type: 'number',
868+
description:
869+
'Optional maximum execution time in seconds. If omitted, Copilot sends 10 seconds by default. Override when needed; capped at the default execution limit.',
870+
},
866871
},
867872
required: ['code'],
868873
},
@@ -2794,8 +2799,7 @@ export const TOOL_RUNTIME_SCHEMAS: Record<string, ToolRuntimeSchemaEntry> = {
27942799
},
27952800
rowIds: {
27962801
type: 'array',
2797-
description:
2798-
'Array of row IDs. Used by batch_delete_rows (rows to delete) and run_column (optional row scope — when omitted, runs across the whole table; when provided, only these rows are candidates and the server eligibility predicate still applies).',
2802+
description: 'Array of row IDs to delete (for batch_delete_rows)',
27992803
items: {
28002804
type: 'string',
28012805
},

apps/sim/lib/copilot/tool-executor/executor.test.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*/
44

55
import { beforeEach, describe, expect, it, vi } from 'vitest'
6+
import { DEFAULT_EXECUTION_TIMEOUT_MS } from '@/lib/execution/constants'
67

78
const { isKnownTool, isSimExecuted } = vi.hoisted(() => ({
89
isKnownTool: vi.fn(),
@@ -58,4 +59,82 @@ describe('copilot tool executor fallback', () => {
5859
)
5960
expect(result).toEqual({ success: true, output: { emails: [] } })
6061
})
62+
63+
it('converts function_execute timeout from seconds to milliseconds for copilot calls', async () => {
64+
isKnownTool.mockReturnValue(false)
65+
isSimExecuted.mockReturnValue(false)
66+
executeAppTool.mockResolvedValue({ success: true, output: { result: 'ok' } })
67+
68+
await executeTool(
69+
'function_execute',
70+
{ code: 'return 1', timeout: 7 },
71+
{
72+
userId: 'user-1',
73+
workflowId: 'workflow-1',
74+
workspaceId: 'ws-1',
75+
copilotToolExecution: true,
76+
}
77+
)
78+
79+
expect(executeAppTool).toHaveBeenCalledWith(
80+
'function_execute',
81+
expect.objectContaining({
82+
timeout: 7000,
83+
_context: expect.objectContaining({
84+
copilotToolExecution: true,
85+
}),
86+
}),
87+
false
88+
)
89+
})
90+
91+
it('defaults copilot function_execute timeout to 10 seconds when omitted', async () => {
92+
isKnownTool.mockReturnValue(false)
93+
isSimExecuted.mockReturnValue(false)
94+
executeAppTool.mockResolvedValue({ success: true, output: { result: 'ok' } })
95+
96+
await executeTool(
97+
'function_execute',
98+
{ code: 'return 1' },
99+
{
100+
userId: 'user-1',
101+
workflowId: 'workflow-1',
102+
workspaceId: 'ws-1',
103+
copilotToolExecution: true,
104+
}
105+
)
106+
107+
expect(executeAppTool).toHaveBeenCalledWith(
108+
'function_execute',
109+
expect.objectContaining({
110+
timeout: 10_000,
111+
}),
112+
false
113+
)
114+
})
115+
116+
it('does not let copilot function_execute timeout exceed the default execution limit', async () => {
117+
isKnownTool.mockReturnValue(false)
118+
isSimExecuted.mockReturnValue(false)
119+
executeAppTool.mockResolvedValue({ success: true, output: { result: 'ok' } })
120+
121+
await executeTool(
122+
'function_execute',
123+
{ code: 'return 1', timeout: 10_000 },
124+
{
125+
userId: 'user-1',
126+
workflowId: 'workflow-1',
127+
workspaceId: 'ws-1',
128+
copilotToolExecution: true,
129+
}
130+
)
131+
132+
expect(executeAppTool).toHaveBeenCalledWith(
133+
'function_execute',
134+
expect.objectContaining({
135+
timeout: DEFAULT_EXECUTION_TIMEOUT_MS,
136+
}),
137+
false
138+
)
139+
})
61140
})

apps/sim/lib/copilot/tool-executor/executor.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createLogger } from '@sim/logger'
22
import { toError } from '@sim/utils/errors'
3+
import { DEFAULT_EXECUTION_TIMEOUT_MS } from '@/lib/execution/constants'
34
import { executeTool as executeAppTool } from '@/tools'
45
import { isKnownTool, isSimExecuted } from './router'
56
import type {
@@ -10,6 +11,9 @@ import type {
1011
} from './types'
1112

1213
const logger = createLogger('ToolExecutor')
14+
const FUNCTION_EXECUTE_TOOL_ID = 'function_execute'
15+
const DEFAULT_FUNCTION_EXECUTE_TIMEOUT_SECONDS = 10
16+
const MILLISECONDS_PER_SECOND = 1000
1317

1418
const handlerRegistry = new Map<string, ToolHandler>()
1519

@@ -38,7 +42,7 @@ export async function executeTool(
3842
): Promise<ToolExecutionResult> {
3943
const canUseRegisteredHandler = isKnownTool(toolId) && isSimExecuted(toolId)
4044
if (!canUseRegisteredHandler) {
41-
const appParams = buildAppToolParams(params, context)
45+
const appParams = buildAppToolParams(toolId, params, context)
4246
return executeAppTool(toolId, appParams, false)
4347
}
4448

@@ -95,11 +99,25 @@ async function executeToolBatch(
9599
}
96100

97101
function buildAppToolParams(
102+
toolId: string,
98103
params: Record<string, unknown>,
99104
context: ToolExecutionContext
100105
): Record<string, unknown> {
101106
const result = { ...params }
102107

108+
if (toolId === FUNCTION_EXECUTE_TOOL_ID && context.copilotToolExecution) {
109+
const timeoutSeconds =
110+
result.timeout === undefined || result.timeout === null
111+
? DEFAULT_FUNCTION_EXECUTE_TIMEOUT_SECONDS
112+
: Number(result.timeout)
113+
if (Number.isFinite(timeoutSeconds) && timeoutSeconds > 0) {
114+
result.timeout = Math.min(
115+
Math.ceil(timeoutSeconds * MILLISECONDS_PER_SECOND),
116+
DEFAULT_EXECUTION_TIMEOUT_MS
117+
)
118+
}
119+
}
120+
103121
if (result.credentialId && !result.credential && !result.oauthCredential) {
104122
result.credential = result.credentialId
105123
}

apps/sim/tools/function/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ export interface CodeExecutionInput {
77
sourceCode?: string
88
language?: CodeLanguage
99
useLocalVM?: boolean
10+
/**
11+
* Workflow Function blocks pass milliseconds. Copilot/Mothership tool calls pass seconds
12+
* and are converted at the request boundary.
13+
*/
1014
timeout?: number
1115
memoryLimit?: number
1216
outputPath?: string
@@ -28,6 +32,7 @@ export interface CodeExecutionInput {
2832
allowLargeValueWorkflowScope?: boolean
2933
userId?: string
3034
workspaceId?: string
35+
copilotToolExecution?: boolean
3136
}
3237
isCustomTool?: boolean
3338
_sandboxFiles?: Array<{ path: string; content: string }>

0 commit comments

Comments
 (0)