Skip to content

Commit adf8c22

Browse files
committed
Fix custom tool save
1 parent ebfdb9c commit adf8c22

File tree

1 file changed

+107
-4
lines changed
  • apps/sim/app/api/workflows/[id]/yaml

1 file changed

+107
-4
lines changed

apps/sim/app/api/workflows/[id]/yaml/route.ts

Lines changed: 107 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { z } from 'zod'
55
import { env } from '@/lib/env'
66
import { createLogger } from '@/lib/logs/console/logger'
77
import { db } from '@/db'
8-
import { workflow as workflowTable, workflowCheckpoints } from '@/db/schema'
8+
import { workflow as workflowTable, workflowCheckpoints, customTools } from '@/db/schema'
99
import { getAllBlocks, getBlock } from '@/blocks'
1010
import type { BlockConfig } from '@/blocks/types'
1111
import { generateLoopBlocks, generateParallelBlocks } from '@/stores/workflows/workflow/utils'
@@ -137,6 +137,105 @@ async function createWorkflowCheckpoint(
137137
}
138138
}
139139

140+
async function upsertCustomToolsFromBlocks(
141+
userId: string,
142+
blocks: Record<string, any>,
143+
requestId: string
144+
): Promise<{ created: number; updated: number }> {
145+
try {
146+
// Collect custom tools from all agent blocks
147+
const collected: Array<{ title: string; schema: any; code: string }> = []
148+
149+
for (const block of Object.values(blocks)) {
150+
if (!block || block.type !== 'agent') continue
151+
const toolsSub = block.subBlocks?.tools
152+
if (!toolsSub) continue
153+
154+
let value = toolsSub.value
155+
if (!value) continue
156+
if (typeof value === 'string') {
157+
try {
158+
value = JSON.parse(value)
159+
} catch {
160+
continue
161+
}
162+
}
163+
if (!Array.isArray(value)) continue
164+
165+
for (const tool of value) {
166+
if (
167+
tool &&
168+
tool.type === 'custom-tool' &&
169+
tool.schema &&
170+
tool.schema.function &&
171+
tool.schema.function.name &&
172+
typeof tool.code === 'string'
173+
) {
174+
collected.push({ title: tool.title || tool.schema.function.name, schema: tool.schema, code: tool.code })
175+
}
176+
}
177+
}
178+
179+
if (collected.length === 0) return { created: 0, updated: 0 }
180+
181+
// Ensure unique by function name
182+
const byName = new Map<string, { title: string; schema: any; code: string }>()
183+
for (const t of collected) {
184+
const name = t.schema.function.name
185+
if (!byName.has(name)) byName.set(name, t)
186+
}
187+
188+
// Load existing user's tools
189+
const existing = await db
190+
.select()
191+
.from(customTools)
192+
.where(eq(customTools.userId, userId))
193+
194+
const existingByName = new Map<string, (typeof existing)[number]>()
195+
for (const row of existing) {
196+
try {
197+
const fnName = (row.schema as any)?.function?.name
198+
if (fnName) existingByName.set(fnName, row as any)
199+
} catch {}
200+
}
201+
202+
let created = 0
203+
let updated = 0
204+
const now = new Date()
205+
206+
// Upsert by function name
207+
for (const [name, tool] of byName.entries()) {
208+
const match = existingByName.get(name)
209+
if (!match) {
210+
await db.insert(customTools).values({
211+
id: crypto.randomUUID(),
212+
userId,
213+
title: tool.title,
214+
schema: tool.schema,
215+
code: tool.code,
216+
createdAt: now,
217+
updatedAt: now,
218+
})
219+
created++
220+
} else {
221+
await db
222+
.update(customTools)
223+
.set({ title: tool.title, schema: tool.schema, code: tool.code, updatedAt: now })
224+
.where(eq(customTools.id, match.id))
225+
updated++
226+
}
227+
}
228+
229+
logger.info(`[${requestId}] Upserted custom tools from YAML`, { created, updated })
230+
return { created, updated }
231+
} catch (err) {
232+
logger.warn(`[${requestId}] Failed to upsert custom tools from YAML`, {
233+
error: err instanceof Error ? err.message : String(err),
234+
})
235+
return { created: 0, updated: 0 }
236+
}
237+
}
238+
140239
/**
141240
* PUT /api/workflows/[id]/yaml
142241
* Consolidated YAML workflow saving endpoint
@@ -226,7 +325,10 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
226325

227326
const conversionResult = await conversionResponse.json()
228327

229-
if (!conversionResult.success || !conversionResult.workflowState) {
328+
const workflowState =
329+
conversionResult.workflowState || (conversionResult.diff && conversionResult.diff.proposedState)
330+
331+
if (!conversionResult.success || !workflowState) {
230332
logger.error(`[${requestId}] YAML conversion failed`, {
231333
errors: conversionResult.errors,
232334
warnings: conversionResult.warnings,
@@ -239,8 +341,6 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
239341
})
240342
}
241343

242-
const { workflowState } = conversionResult
243-
244344
// Ensure all blocks have required fields
245345
Object.values(workflowState.blocks).forEach((block: any) => {
246346
if (block.enabled === undefined) {
@@ -545,6 +645,9 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
545645
}
546646
newWorkflowState.blocks = sanitizedBlocks
547647

648+
// Upsert custom tools from blocks
649+
await upsertCustomToolsFromBlocks(userId, newWorkflowState.blocks, requestId)
650+
548651
// Save to database
549652
const saveResult = await saveWorkflowToNormalizedTables(workflowId, newWorkflowState)
550653

0 commit comments

Comments
 (0)