11import { db } from '@sim/db'
2- import { workflow } from '@sim/db/schema'
2+ import { customTools , workflow } from '@sim/db/schema'
33import { createLogger } from '@sim/logger'
4- import { eq } from 'drizzle-orm'
4+ import { and , desc , eq , isNull , or } from 'drizzle-orm'
55import { SIM_AGENT_API_URL } from '@/lib/copilot/constants'
66import type {
77 ExecutionContext ,
@@ -12,6 +12,7 @@ import { routeExecution } from '@/lib/copilot/tools/server/router'
1212import { env } from '@/lib/core/config/env'
1313import { getBaseUrl } from '@/lib/core/utils/urls'
1414import { getEffectiveDecryptedEnv } from '@/lib/environment/utils'
15+ import { upsertCustomTools } from '@/lib/workflows/custom-tools/operations'
1516import { getTool , resolveToolId } from '@/tools/utils'
1617import {
1718 executeCheckDeploymentStatus ,
@@ -76,6 +77,247 @@ import {
7677
7778const logger = createLogger ( 'CopilotToolExecutor' )
7879
80+ type ManageCustomToolOperation = 'add' | 'edit' | 'delete' | 'list'
81+
82+ interface ManageCustomToolSchema {
83+ type : 'function'
84+ function : {
85+ name : string
86+ description ?: string
87+ parameters : Record < string , unknown >
88+ }
89+ }
90+
91+ interface ManageCustomToolParams {
92+ operation ?: string
93+ toolId ?: string
94+ schema ?: ManageCustomToolSchema
95+ code ?: string
96+ title ?: string
97+ workspaceId ?: string
98+ }
99+
100+ async function executeManageCustomTool (
101+ rawParams : Record < string , unknown > ,
102+ context : ExecutionContext
103+ ) : Promise < ToolCallResult > {
104+ const params = rawParams as ManageCustomToolParams
105+ const operation = String ( params . operation || '' ) . toLowerCase ( ) as ManageCustomToolOperation
106+ const workspaceId = params . workspaceId || context . workspaceId
107+
108+ if ( ! operation ) {
109+ return { success : false , error : "Missing required 'operation' argument" }
110+ }
111+
112+ try {
113+ if ( operation === 'list' ) {
114+ const toolsForUser = workspaceId
115+ ? await db
116+ . select ( )
117+ . from ( customTools )
118+ . where (
119+ or (
120+ eq ( customTools . workspaceId , workspaceId ) ,
121+ and ( isNull ( customTools . workspaceId ) , eq ( customTools . userId , context . userId ) )
122+ )
123+ )
124+ . orderBy ( desc ( customTools . createdAt ) )
125+ : await db
126+ . select ( )
127+ . from ( customTools )
128+ . where ( and ( isNull ( customTools . workspaceId ) , eq ( customTools . userId , context . userId ) ) )
129+ . orderBy ( desc ( customTools . createdAt ) )
130+
131+ return {
132+ success : true ,
133+ output : {
134+ success : true ,
135+ operation,
136+ tools : toolsForUser ,
137+ count : toolsForUser . length ,
138+ } ,
139+ }
140+ }
141+
142+ if ( operation === 'add' ) {
143+ if ( ! workspaceId ) {
144+ return {
145+ success : false ,
146+ error : "workspaceId is required for operation 'add'" ,
147+ }
148+ }
149+ if ( ! params . schema || ! params . code ) {
150+ return {
151+ success : false ,
152+ error : "Both 'schema' and 'code' are required for operation 'add'" ,
153+ }
154+ }
155+
156+ const title = params . title || params . schema . function ?. name
157+ if ( ! title ) {
158+ return { success : false , error : "Missing tool title or schema.function.name for 'add'" }
159+ }
160+
161+ const resultTools = await upsertCustomTools ( {
162+ tools : [
163+ {
164+ title,
165+ schema : params . schema ,
166+ code : params . code ,
167+ } ,
168+ ] ,
169+ workspaceId,
170+ userId : context . userId ,
171+ } )
172+ const created = resultTools . find ( ( tool ) => tool . title === title )
173+
174+ return {
175+ success : true ,
176+ output : {
177+ success : true ,
178+ operation,
179+ toolId : created ?. id ,
180+ title,
181+ message : `Created custom tool "${ title } "` ,
182+ } ,
183+ }
184+ }
185+
186+ if ( operation === 'edit' ) {
187+ if ( ! workspaceId ) {
188+ return {
189+ success : false ,
190+ error : "workspaceId is required for operation 'edit'" ,
191+ }
192+ }
193+ if ( ! params . toolId ) {
194+ return { success : false , error : "'toolId' is required for operation 'edit'" }
195+ }
196+ if ( ! params . schema && ! params . code ) {
197+ return {
198+ success : false ,
199+ error : "At least one of 'schema' or 'code' is required for operation 'edit'" ,
200+ }
201+ }
202+
203+ const workspaceTool = await db
204+ . select ( )
205+ . from ( customTools )
206+ . where ( and ( eq ( customTools . id , params . toolId ) , eq ( customTools . workspaceId , workspaceId ) ) )
207+ . limit ( 1 )
208+
209+ const legacyTool =
210+ workspaceTool . length === 0
211+ ? await db
212+ . select ( )
213+ . from ( customTools )
214+ . where (
215+ and (
216+ eq ( customTools . id , params . toolId ) ,
217+ isNull ( customTools . workspaceId ) ,
218+ eq ( customTools . userId , context . userId )
219+ )
220+ )
221+ . limit ( 1 )
222+ : [ ]
223+
224+ const existing = workspaceTool [ 0 ] || legacyTool [ 0 ]
225+ if ( ! existing ) {
226+ return { success : false , error : `Custom tool not found: ${ params . toolId } ` }
227+ }
228+
229+ const mergedSchema = params . schema || ( existing . schema as ManageCustomToolSchema )
230+ const mergedCode = params . code || existing . code
231+ const title = params . title || mergedSchema . function ?. name || existing . title
232+
233+ await upsertCustomTools ( {
234+ tools : [
235+ {
236+ id : params . toolId ,
237+ title,
238+ schema : mergedSchema ,
239+ code : mergedCode ,
240+ } ,
241+ ] ,
242+ workspaceId,
243+ userId : context . userId ,
244+ } )
245+
246+ return {
247+ success : true ,
248+ output : {
249+ success : true ,
250+ operation,
251+ toolId : params . toolId ,
252+ title,
253+ message : `Updated custom tool "${ title } "` ,
254+ } ,
255+ }
256+ }
257+
258+ if ( operation === 'delete' ) {
259+ if ( ! params . toolId ) {
260+ return { success : false , error : "'toolId' is required for operation 'delete'" }
261+ }
262+
263+ const workspaceDelete =
264+ workspaceId != null
265+ ? await db
266+ . delete ( customTools )
267+ . where (
268+ and ( eq ( customTools . id , params . toolId ) , eq ( customTools . workspaceId , workspaceId ) )
269+ )
270+ . returning ( { id : customTools . id } )
271+ : [ ]
272+
273+ const legacyDelete =
274+ workspaceDelete . length === 0
275+ ? await db
276+ . delete ( customTools )
277+ . where (
278+ and (
279+ eq ( customTools . id , params . toolId ) ,
280+ isNull ( customTools . workspaceId ) ,
281+ eq ( customTools . userId , context . userId )
282+ )
283+ )
284+ . returning ( { id : customTools . id } )
285+ : [ ]
286+
287+ const deleted = workspaceDelete [ 0 ] || legacyDelete [ 0 ]
288+ if ( ! deleted ) {
289+ return { success : false , error : `Custom tool not found: ${ params . toolId } ` }
290+ }
291+
292+ return {
293+ success : true ,
294+ output : {
295+ success : true ,
296+ operation,
297+ toolId : params . toolId ,
298+ message : 'Deleted custom tool' ,
299+ } ,
300+ }
301+ }
302+
303+ return {
304+ success : false ,
305+ error : `Unsupported operation for manage_custom_tool: ${ operation } ` ,
306+ }
307+ } catch ( error ) {
308+ logger . error ( 'manage_custom_tool execution failed' , {
309+ operation,
310+ workspaceId,
311+ userId : context . userId ,
312+ error : error instanceof Error ? error . message : String ( error ) ,
313+ } )
314+ return {
315+ success : false ,
316+ error : error instanceof Error ? error . message : 'Failed to manage custom tool' ,
317+ }
318+ }
319+ }
320+
79321const SERVER_TOOLS = new Set < string > ( [
80322 'get_blocks_and_tools' ,
81323 'get_blocks_metadata' ,
@@ -161,6 +403,19 @@ const SIM_WORKFLOW_TOOL_HANDLERS: Record<
161403 }
162404 }
163405 } ,
406+ oauth_request_access : async ( p , _c ) => {
407+ const providerName = ( p . providerName || p . provider_name || 'the provider' ) as string
408+ return {
409+ success : true ,
410+ output : {
411+ success : true ,
412+ status : 'requested' ,
413+ providerName,
414+ message : `Requested ${ providerName } OAuth connection. The user should complete the OAuth modal in the UI, then retry credential-dependent actions.` ,
415+ } ,
416+ }
417+ } ,
418+ manage_custom_tool : ( p , c ) => executeManageCustomTool ( p , c ) ,
164419}
165420
166421/**
0 commit comments