chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { toError } from '@sim/utils/errors'
|
||||
import { upsertCustomTools } from '@/lib/workflows/custom-tools/operations'
|
||||
|
||||
const logger = createLogger('CustomToolsPersistence')
|
||||
|
||||
interface CustomTool {
|
||||
id?: string
|
||||
type: 'custom-tool'
|
||||
title: string
|
||||
toolId?: string
|
||||
schema: {
|
||||
function: {
|
||||
name?: string
|
||||
description: string
|
||||
parameters: Record<string, any>
|
||||
}
|
||||
}
|
||||
code: string
|
||||
usageControl?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Stored tool format that may contain either reference or inline definition
|
||||
*/
|
||||
interface StoredCustomTool {
|
||||
type: string
|
||||
title?: string
|
||||
toolId?: string
|
||||
customToolId?: string
|
||||
schema?: any
|
||||
code?: string
|
||||
usageControl?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a stored tool is a reference-only custom tool (no inline definition)
|
||||
*/
|
||||
function isCustomToolReference(tool: StoredCustomTool): boolean {
|
||||
return tool.type === 'custom-tool' && !!tool.customToolId && !tool.code
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract all custom tools from agent blocks in the workflow state
|
||||
*
|
||||
* @remarks
|
||||
* Only extracts tools with inline definitions (legacy format).
|
||||
* Reference-only tools (new format with customToolId) are skipped since they're already in the database.
|
||||
*/
|
||||
export function extractCustomToolsFromWorkflowState(workflowState: any): CustomTool[] {
|
||||
const customToolsMap = new Map<string, CustomTool>()
|
||||
|
||||
if (!workflowState?.blocks) {
|
||||
return []
|
||||
}
|
||||
|
||||
for (const [blockId, block] of Object.entries(workflowState.blocks)) {
|
||||
try {
|
||||
const blockData = block as any
|
||||
|
||||
if (!blockData || blockData.type !== 'agent') {
|
||||
continue
|
||||
}
|
||||
|
||||
const subBlocks = blockData.subBlocks || {}
|
||||
const toolsSubBlock = subBlocks.tools
|
||||
|
||||
if (!toolsSubBlock?.value) {
|
||||
continue
|
||||
}
|
||||
|
||||
let tools = toolsSubBlock.value
|
||||
|
||||
if (typeof tools === 'string') {
|
||||
try {
|
||||
tools = JSON.parse(tools)
|
||||
} catch (error) {
|
||||
logger.warn(`Failed to parse tools in block ${blockId}`, { error })
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (!Array.isArray(tools)) {
|
||||
continue
|
||||
}
|
||||
|
||||
for (const tool of tools) {
|
||||
if (!tool || typeof tool !== 'object' || tool.type !== 'custom-tool') {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip reference-only tools - they're already in the database
|
||||
if (isCustomToolReference(tool)) {
|
||||
logger.debug(`Skipping reference-only custom tool: ${tool.title || tool.customToolId}`)
|
||||
continue
|
||||
}
|
||||
|
||||
// Only persist tools with inline definitions (legacy format)
|
||||
if (tool.title && tool.schema?.function && tool.code) {
|
||||
const toolKey = tool.toolId || tool.title
|
||||
|
||||
if (!customToolsMap.has(toolKey)) {
|
||||
customToolsMap.set(toolKey, tool as CustomTool)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Error extracting custom tools from block ${blockId}`, { error })
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(customToolsMap.values())
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist custom tools to the database using the upsert function
|
||||
* Creates new tools or updates existing ones
|
||||
*/
|
||||
export async function persistCustomToolsToDatabase(
|
||||
customToolsList: CustomTool[],
|
||||
workspaceId: string | null,
|
||||
userId: string
|
||||
): Promise<{ saved: number; errors: string[] }> {
|
||||
if (!customToolsList || customToolsList.length === 0) {
|
||||
return { saved: 0, errors: [] }
|
||||
}
|
||||
|
||||
if (!workspaceId) {
|
||||
logger.debug('Skipping custom tools persistence - no workspaceId provided (user-scoped tools)')
|
||||
return { saved: 0, errors: [] }
|
||||
}
|
||||
|
||||
const errors: string[] = []
|
||||
let saved = 0
|
||||
|
||||
const validTools = customToolsList.filter((tool) => {
|
||||
if (!tool.schema?.function?.name) {
|
||||
logger.warn(`Skipping custom tool without function name: ${tool.title}`)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if (validTools.length === 0) {
|
||||
return { saved: 0, errors: [] }
|
||||
}
|
||||
|
||||
try {
|
||||
await upsertCustomTools({
|
||||
tools: validTools.map((tool) => ({
|
||||
id: tool.toolId,
|
||||
title: tool.title,
|
||||
schema: tool.schema,
|
||||
code: tool.code,
|
||||
})),
|
||||
workspaceId,
|
||||
userId,
|
||||
})
|
||||
|
||||
saved = validTools.length
|
||||
logger.info(`Persisted ${saved} custom tool(s)`, { workspaceId })
|
||||
} catch (error) {
|
||||
const errorMsg = `Failed to persist custom tools: ${toError(error).message}`
|
||||
logger.error(errorMsg, { error })
|
||||
errors.push(errorMsg)
|
||||
}
|
||||
|
||||
return { saved, errors }
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract and persist custom tools from workflow state
|
||||
*/
|
||||
export async function extractAndPersistCustomTools(
|
||||
workflowState: any,
|
||||
workspaceId: string | null,
|
||||
userId: string
|
||||
): Promise<{ saved: number; errors: string[] }> {
|
||||
const customToolsList = extractCustomToolsFromWorkflowState(workflowState)
|
||||
|
||||
if (customToolsList.length === 0) {
|
||||
logger.debug('No custom tools found in workflow state')
|
||||
return { saved: 0, errors: [] }
|
||||
}
|
||||
|
||||
logger.info(`Found ${customToolsList.length} custom tool(s) to persist`, {
|
||||
tools: customToolsList.map((t) => t.title),
|
||||
workspaceId,
|
||||
})
|
||||
|
||||
return await persistCustomToolsToDatabase(customToolsList, workspaceId, userId)
|
||||
}
|
||||
Reference in New Issue
Block a user