diff --git a/apps/sim/app/api/memory/[id]/route.ts b/apps/sim/app/api/memory/[id]/route.ts new file mode 100644 index 0000000000..0ca17acb4f --- /dev/null +++ b/apps/sim/app/api/memory/[id]/route.ts @@ -0,0 +1,329 @@ +import { NextRequest, NextResponse } from 'next/server' +import { and, eq, isNull } from 'drizzle-orm' +import { createLogger } from '@/lib/logs/console-logger' +import { db } from '@/db' +import { memory } from '@/db/schema' + +const logger = createLogger('MemoryByIdAPI') + +export const dynamic = 'force-dynamic' +export const runtime = 'nodejs' + +/** + * GET handler for retrieving a specific memory by ID + */ +export async function GET( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + const requestId = crypto.randomUUID().slice(0, 8) + const { id } = await params + + try { + logger.info(`[${requestId}] Processing memory get request for ID: ${id}`) + + // Get workflowId from query parameter (required) + const url = new URL(request.url) + const workflowId = url.searchParams.get('workflowId') + + if (!workflowId) { + logger.warn(`[${requestId}] Missing required parameter: workflowId`) + return NextResponse.json( + { + success: false, + error: { + message: 'workflowId parameter is required', + }, + }, + { status: 400 } + ) + } + + // Query the database for the memory + const memories = await db + .select() + .from(memory) + .where( + and( + eq(memory.key, id), + eq(memory.workflowId, workflowId), + isNull(memory.deletedAt) + ) + ) + .orderBy(memory.createdAt) + .limit(1) + + if (memories.length === 0) { + logger.warn(`[${requestId}] Memory not found: ${id} for workflow: ${workflowId}`) + return NextResponse.json( + { + success: false, + error: { + message: 'Memory not found', + }, + }, + { status: 404 } + ) + } + + logger.info(`[${requestId}] Memory retrieved successfully: ${id} for workflow: ${workflowId}`) + return NextResponse.json( + { + success: true, + data: memories[0], + }, + { status: 200 } + ) + + } catch (error: any) { + return NextResponse.json( + { + success: false, + error: { + message: error.message || 'Failed to retrieve memory', + }, + }, + { status: 500 } + ) + } +} + +/** + * DELETE handler for removing a specific memory + */ +export async function DELETE( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + const requestId = crypto.randomUUID().slice(0, 8) + const { id } = await params + + try { + logger.info(`[${requestId}] Processing memory delete request for ID: ${id}`) + + // Get workflowId from query parameter (required) + const url = new URL(request.url) + const workflowId = url.searchParams.get('workflowId') + + if (!workflowId) { + logger.warn(`[${requestId}] Missing required parameter: workflowId`) + return NextResponse.json( + { + success: false, + error: { + message: 'workflowId parameter is required', + }, + }, + { status: 400 } + ) + } + + // Verify memory exists before attempting to delete + const existingMemory = await db + .select({ id: memory.id }) + .from(memory) + .where( + and( + eq(memory.key, id), + eq(memory.workflowId, workflowId), + isNull(memory.deletedAt) + ) + ) + .limit(1) + + if (existingMemory.length === 0) { + logger.warn(`[${requestId}] Memory not found: ${id} for workflow: ${workflowId}`) + return NextResponse.json( + { + success: false, + error: { + message: 'Memory not found', + }, + }, + { status: 404 } + ) + } + + // Soft delete by setting deletedAt timestamp + await db + .update(memory) + .set({ + deletedAt: new Date(), + updatedAt: new Date() + }) + .where( + and( + eq(memory.key, id), + eq(memory.workflowId, workflowId) + ) + ) + + logger.info(`[${requestId}] Memory deleted successfully: ${id} for workflow: ${workflowId}`) + return NextResponse.json( + { + success: true, + data: { message: 'Memory deleted successfully' }, + }, + { status: 200 } + ) + + } catch (error: any) { + return NextResponse.json( + { + success: false, + error: { + message: error.message || 'Failed to delete memory', + }, + }, + { status: 500 } + ) + } +} + +/** + * PUT handler for updating a specific memory + */ +export async function PUT( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + const requestId = crypto.randomUUID().slice(0, 8) + const { id } = await params + + try { + logger.info(`[${requestId}] Processing memory update request for ID: ${id}`) + + // Parse request body + const body = await request.json() + const { data, workflowId } = body + + if (!data) { + logger.warn(`[${requestId}] Missing required field: data`) + return NextResponse.json( + { + success: false, + error: { + message: 'Memory data is required', + }, + }, + { status: 400 } + ) + } + + if (!workflowId) { + logger.warn(`[${requestId}] Missing required field: workflowId`) + return NextResponse.json( + { + success: false, + error: { + message: 'workflowId is required', + }, + }, + { status: 400 } + ) + } + + // Verify memory exists before attempting to update + const existingMemories = await db + .select() + .from(memory) + .where( + and( + eq(memory.key, id), + eq(memory.workflowId, workflowId), + isNull(memory.deletedAt) + ) + ) + .limit(1) + + if (existingMemories.length === 0) { + logger.warn(`[${requestId}] Memory not found: ${id} for workflow: ${workflowId}`) + return NextResponse.json( + { + success: false, + error: { + message: 'Memory not found', + }, + }, + { status: 404 } + ) + } + + const existingMemory = existingMemories[0] + + // Validate memory data based on the existing memory type + if (existingMemory.type === 'agent') { + if (!data.role || !data.content) { + logger.warn(`[${requestId}] Missing agent memory fields`) + return NextResponse.json( + { + success: false, + error: { + message: 'Agent memory requires role and content', + }, + }, + { status: 400 } + ) + } + + if (!['user', 'assistant', 'system'].includes(data.role)) { + logger.warn(`[${requestId}] Invalid agent role: ${data.role}`) + return NextResponse.json( + { + success: false, + error: { + message: 'Agent role must be user, assistant, or system', + }, + }, + { status: 400 } + ) + } + } + + // Update the memory with new data + await db + .update(memory) + .set({ + data, + updatedAt: new Date() + }) + .where( + and( + eq(memory.key, id), + eq(memory.workflowId, workflowId) + ) + ) + + // Fetch the updated memory + const updatedMemories = await db + .select() + .from(memory) + .where( + and( + eq(memory.key, id), + eq(memory.workflowId, workflowId) + ) + ) + .limit(1) + + logger.info(`[${requestId}] Memory updated successfully: ${id} for workflow: ${workflowId}`) + return NextResponse.json( + { + success: true, + data: updatedMemories[0], + }, + { status: 200 } + ) + + } catch (error: any) { + return NextResponse.json( + { + success: false, + error: { + message: error.message || 'Failed to update memory', + }, + }, + { status: 500 } + ) + } +} \ No newline at end of file diff --git a/apps/sim/app/api/memory/route.ts b/apps/sim/app/api/memory/route.ts new file mode 100644 index 0000000000..7a69e925ae --- /dev/null +++ b/apps/sim/app/api/memory/route.ts @@ -0,0 +1,335 @@ +import { NextRequest, NextResponse } from 'next/server' +import { and, eq, like, isNull } from 'drizzle-orm' +import { db } from '@/db' +import { memory } from '@/db/schema' +import { createLogger } from '@/lib/logs/console-logger' + +const logger = createLogger('MemoryAPI') + +export const dynamic = 'force-dynamic' +export const runtime = 'nodejs' + +/** + * GET handler for searching and retrieving memories + * Supports query parameters: + * - query: Search string for memory keys + * - type: Filter by memory type + * - limit: Maximum number of results (default: 50) + * - workflowId: Filter by workflow ID (required) + */ +export async function GET(request: NextRequest) { + const requestId = crypto.randomUUID().slice(0, 8) + + try { + logger.info(`[${requestId}] Processing memory search request`) + + // Extract workflowId from query parameters + const url = new URL(request.url) + const workflowId = url.searchParams.get('workflowId') + const searchQuery = url.searchParams.get('query') + const type = url.searchParams.get('type') + const limit = parseInt(url.searchParams.get('limit') || '50') + + // Require workflowId for security + if (!workflowId) { + logger.warn(`[${requestId}] Missing required parameter: workflowId`) + return NextResponse.json( + { + success: false, + error: { + message: 'workflowId parameter is required', + }, + }, + { status: 400 } + ) + } + + // Build query conditions + const conditions = [] + + // Only include non-deleted memories + conditions.push(isNull(memory.deletedAt)) + + // Filter by workflow ID (required) + conditions.push(eq(memory.workflowId, workflowId)) + + // Add type filter if provided + if (type) { + conditions.push(eq(memory.type, type)) + } + + // Add search query if provided (leverages index on key field) + if (searchQuery) { + conditions.push(like(memory.key, `%${searchQuery}%`)) + } + + // Execute the query + const memories = await db + .select() + .from(memory) + .where(and(...conditions)) + .orderBy(memory.createdAt) + .limit(limit) + + logger.info(`[${requestId}] Found ${memories.length} memories for workflow: ${workflowId}`) + return NextResponse.json( + { + success: true, + data: { memories } + }, + { status: 200 } + ) + + } catch (error: any) { + return NextResponse.json( + { + success: false, + error: { + message: error.message || 'Failed to search memories', + }, + }, + { status: 500 } + ) + } +} + +/** + * POST handler for creating new memories + * Requires: + * - key: Unique identifier for the memory (within workflow scope) + * - type: Memory type ('agent' or 'raw') + * - data: Memory content (varies by type) + * - workflowId: ID of the workflow this memory belongs to + */ +export async function POST(request: NextRequest) { + const requestId = crypto.randomUUID().slice(0, 8) + + try { + logger.info(`[${requestId}] Processing memory creation request`) + + // Parse request body + const body = await request.json() + const { key, type, data, workflowId } = body + + // Validate required fields + if (!key) { + logger.warn(`[${requestId}] Missing required field: key`) + return NextResponse.json( + { + success: false, + error: { + message: 'Memory key is required', + }, + }, + { status: 400 } + ) + } + + if (!type || !['agent', 'raw'].includes(type)) { + logger.warn(`[${requestId}] Invalid memory type: ${type}`) + return NextResponse.json( + { + success: false, + error: { + message: 'Valid memory type (agent or raw) is required', + }, + }, + { status: 400 } + ) + } + + if (!data) { + logger.warn(`[${requestId}] Missing required field: data`) + return NextResponse.json( + { + success: false, + error: { + message: 'Memory data is required', + }, + }, + { status: 400 } + ) + } + + if (!workflowId) { + logger.warn(`[${requestId}] Missing required field: workflowId`) + return NextResponse.json( + { + success: false, + error: { + message: 'workflowId is required', + }, + }, + { status: 400 } + ) + } + + // Additional validation for agent type + if (type === 'agent') { + if (!data.role || !data.content) { + logger.warn(`[${requestId}] Missing agent memory fields`) + return NextResponse.json( + { + success: false, + error: { + message: 'Agent memory requires role and content', + }, + }, + { status: 400 } + ) + } + + if (!['user', 'assistant', 'system'].includes(data.role)) { + logger.warn(`[${requestId}] Invalid agent role: ${data.role}`) + return NextResponse.json( + { + success: false, + error: { + message: 'Agent role must be user, assistant, or system', + }, + }, + { status: 400 } + ) + } + } + + // Check if memory with the same key already exists for this workflow + const existingMemory = await db + .select() + .from(memory) + .where( + and( + eq(memory.key, key), + eq(memory.workflowId, workflowId), + isNull(memory.deletedAt) + ) + ) + .limit(1) + + if (existingMemory.length > 0) { + logger.info(`[${requestId}] Memory with key ${key} exists, checking if we can append`) + + // Check if types match + if (existingMemory[0].type !== type) { + logger.warn(`[${requestId}] Memory type mismatch: existing=${existingMemory[0].type}, new=${type}`) + return NextResponse.json( + { + success: false, + error: { + message: `Cannot append memory of type '${type}' to existing memory of type '${existingMemory[0].type}'`, + }, + }, + { status: 400 } + ) + } + + // Handle appending based on memory type + let updatedData; + + if (type === 'agent') { + // For agent type + const newMessage = data; + const existingData = existingMemory[0].data; + + // If existing data is an array, append to it + if (Array.isArray(existingData)) { + updatedData = [...existingData, newMessage]; + } + // If existing data is a single message object, convert to array + else { + updatedData = [existingData, newMessage]; + } + } else { + // For raw type + // Merge objects if they're objects, otherwise use the new data + if (typeof existingMemory[0].data === 'object' && typeof data === 'object') { + updatedData = { ...existingMemory[0].data, ...data }; + } else { + updatedData = data; + } + } + + // Update the existing memory with appended data + await db + .update(memory) + .set({ + data: updatedData, + updatedAt: new Date() + }) + .where( + and( + eq(memory.key, key), + eq(memory.workflowId, workflowId) + ) + ) + + // Fetch the updated memory + const updatedMemory = await db + .select() + .from(memory) + .where( + and( + eq(memory.key, key), + eq(memory.workflowId, workflowId) + ) + ) + .limit(1) + + logger.info(`[${requestId}] Memory appended successfully: ${key} for workflow: ${workflowId}`) + return NextResponse.json( + { + success: true, + data: updatedMemory[0] + }, + { status: 200 } + ) + } + + // Insert the new memory + const newMemory = { + id: `mem_${crypto.randomUUID().replace(/-/g, '')}`, + workflowId, + key, + type, + data: type === 'agent' ? Array.isArray(data) ? data : [data] : data, + createdAt: new Date(), + updatedAt: new Date() + } + + await db.insert(memory).values(newMemory) + + logger.info(`[${requestId}] Memory created successfully: ${key} for workflow: ${workflowId}`) + return NextResponse.json( + { + success: true, + data: newMemory + }, + { status: 201 } + ) + + } catch (error: any) { + // Handle unique constraint violation + if (error.code === '23505') { + logger.warn(`[${requestId}] Duplicate key violation`) + return NextResponse.json( + { + success: false, + error: { + message: 'Memory with this key already exists', + }, + }, + { status: 409 } + ) + } + + return NextResponse.json( + { + success: false, + error: { + message: error.message || 'Failed to create memory', + }, + }, + { status: 500 } + ) + } +} \ No newline at end of file diff --git a/apps/sim/app/api/providers/route.ts b/apps/sim/app/api/providers/route.ts index ab02a77fcc..2c30287386 100644 --- a/apps/sim/app/api/providers/route.ts +++ b/apps/sim/app/api/providers/route.ts @@ -26,6 +26,7 @@ export async function POST(request: NextRequest) { responseFormat, workflowId, stream, + messages, } = body let finalApiKey: string @@ -51,6 +52,7 @@ export async function POST(request: NextRequest) { responseFormat, workflowId, stream, + messages, }) // Check if the response is a StreamingExecution diff --git a/apps/sim/app/w/[id]/components/workflow-block/workflow-block.tsx b/apps/sim/app/w/[id]/components/workflow-block/workflow-block.tsx index db1a36f49a..fbabb5843a 100644 --- a/apps/sim/app/w/[id]/components/workflow-block/workflow-block.tsx +++ b/apps/sim/app/w/[id]/components/workflow-block/workflow-block.tsx @@ -1,5 +1,5 @@ import { useEffect, useRef, useState } from 'react' -import { BookOpen, Info, RectangleHorizontal, RectangleVertical } from 'lucide-react' +import { BookOpen, Code, Info, RectangleHorizontal, RectangleVertical } from 'lucide-react' import { Handle, NodeProps, Position, useUpdateNodeInternals } from 'reactflow' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' @@ -61,6 +61,8 @@ export function WorkflowBlock({ id, data }: NodeProps) { const isWide = useWorkflowStore((state) => state.blocks[id]?.isWide ?? false) const blockHeight = useWorkflowStore((state) => state.blocks[id]?.height ?? 0) const hasActiveWebhook = useWorkflowStore((state) => state.hasActiveWebhook ?? false) + const blockAdvancedMode = useWorkflowStore((state) => state.blocks[id]?.advancedMode ?? false) + const toggleBlockAdvancedMode = useWorkflowStore((state) => state.toggleBlockAdvancedMode) // Workflow store actions const updateBlockName = useWorkflowStore((state) => state.updateBlockName) @@ -257,11 +259,18 @@ export function WorkflowBlock({ id, data }: NodeProps) { const blocks = useWorkflowStore.getState().blocks const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId || undefined const mergedState = mergeSubblockState(blocks, activeWorkflowId, blockId)[blockId] + const isAdvancedMode = useWorkflowStore.getState().blocks[blockId]?.advancedMode ?? false // Filter visible blocks and those that meet their conditions const visibleSubBlocks = subBlocks.filter((block) => { if (block.hidden) return false + // Filter by mode if specified + if (block.mode) { + if (block.mode === 'basic' && isAdvancedMode) return false + if (block.mode === 'advanced' && !isAdvancedMode) return false + } + // If there's no condition, the block should be shown if (!block.condition) return true @@ -552,76 +561,86 @@ export function WorkflowBlock({ id, data }: NodeProps) { )} - {config.longDescription && ( + {config.subBlocks.some((block) => block.mode) && ( - {config.docsLink ? ( - - ) : ( + + + + {blockAdvancedMode ? 'Switch to Basic Mode' : 'Switch to Advanced Mode'} + + + )} + {config.docsLink ? ( + + + + + See Docs + + ) : ( + config.longDescription && ( + + - )} - - -
-
-

Description

-

{config.longDescription}

- {config.docsLink && ( -

- { - e.stopPropagation() - }} - > - View Documentation - -

+ + +
+
+

Description

+

{config.longDescription}

+
+ {config.outputs && ( +
+

Output

+
+ {Object.entries(config.outputs).map(([key, value]) => ( +
+ {key}{' '} + {typeof value.type === 'object' ? ( +
+ {Object.entries(value.type).map(([typeKey, typeValue]) => ( +
+ + {typeKey}: + + + {typeValue as string} + +
+ ))} +
+ ) : ( + {value.type as string} + )} +
+ ))} +
+
)}
- {config.outputs && ( -
-

Output

-
- {Object.entries(config.outputs).map(([key, value]) => ( -
- {key}{' '} - {typeof value.type === 'object' ? ( -
- {Object.entries(value.type).map(([typeKey, typeValue]) => ( -
- {typeKey}: - - {typeValue as string} - -
- ))} -
- ) : ( - {value.type as string} - )} -
- ))} -
-
- )} -
- - + + + ) )} diff --git a/apps/sim/blocks/blocks/agent.ts b/apps/sim/blocks/blocks/agent.ts index 07fc3a6087..8d4bced8c5 100644 --- a/apps/sim/blocks/blocks/agent.ts +++ b/apps/sim/blocks/blocks/agent.ts @@ -61,6 +61,7 @@ export const AgentBlock: BlockConfig = { layout: 'full', placeholder: 'Enter system prompt...', rows: 5, + mode: 'basic', }, { id: 'context', @@ -69,6 +70,16 @@ export const AgentBlock: BlockConfig = { layout: 'full', placeholder: 'Enter context or user message...', rows: 3, + mode: 'basic', + }, + { + id: 'messages', + title: 'Messages', + type: 'code', + layout: 'full', + mode: 'advanced', + language: 'javascript', + placeholder: '[{"role": "user", "content": "Hello, can you help me with a question?"}]', }, { id: 'model', @@ -226,6 +237,11 @@ export const AgentBlock: BlockConfig = { context: { type: 'string', required: false }, model: { type: 'string', required: true }, apiKey: { type: 'string', required: true }, + messages: { + type: 'json', + required: false, + description: 'Array of message objects with role and content fields for advanced chat history control.' + }, responseFormat: { type: 'json', required: false, diff --git a/apps/sim/blocks/blocks/condition.ts b/apps/sim/blocks/blocks/condition.ts index 238ebbfc27..aaa4d621c4 100644 --- a/apps/sim/blocks/blocks/condition.ts +++ b/apps/sim/blocks/blocks/condition.ts @@ -22,7 +22,7 @@ export const ConditionBlock: BlockConfig = { longDescription: 'Add a condition to the workflow to branch the execution path based on a boolean expression.', docsLink: 'https://docs.simstudio.ai/blocks/condition', - bgColor: '#FF972F', + bgColor: '#FF752F', icon: ConditionalIcon, category: 'blocks', subBlocks: [ diff --git a/apps/sim/blocks/blocks/memory.ts b/apps/sim/blocks/blocks/memory.ts index 329809dd04..f8712537be 100644 --- a/apps/sim/blocks/blocks/memory.ts +++ b/apps/sim/blocks/blocks/memory.ts @@ -7,32 +7,235 @@ export const MemoryBlock: BlockConfig = { description: 'Add memory store', longDescription: 'Create persistent storage for data that needs to be accessed across multiple workflow steps. Store and retrieve information throughout your workflow execution to maintain context and state.', - bgColor: '#FF65BF', + bgColor: '#F64F9E', icon: BrainIcon, category: 'blocks', docsLink: 'https://docs.simstudio.ai/tools/memory', tools: { - access: [], + access: ['memory_add', 'memory_get', 'memory_get_all', 'memory_delete'], + config: { + tool: (params: Record) => { + const operation = params.operation || 'add' + switch (operation) { + case 'add': + return 'memory_add' + case 'get': + return 'memory_get' + case 'getAll': + return 'memory_get_all' + case 'delete': + return 'memory_delete' + default: + return 'memory_add' + } + }, + params: (params: Record) => { + // Create detailed error information for any missing required fields + const errors: string[] = [] + + if (!params.operation) { + errors.push('Operation is required') + } + + if (params.operation === 'add' || params.operation === 'get' || params.operation === 'delete') { + if (!params.id) { + errors.push(`Memory ID is required for ${params.operation} operation`) + } + } + + if (params.operation === 'add') { + if (!params.type) { + errors.push('Memory type is required for add operation') + } else if (params.type === 'agent') { + if (!params.role) { + errors.push('Role is required for agent memory') + } + if (!params.content) { + errors.push('Content is required for agent memory') + } + } else if (params.type === 'raw') { + if (!params.rawData) { + errors.push('Raw data is required for raw memory') + } + } + } + + // Throw error if any required fields are missing + if (errors.length > 0) { + throw new Error(`Memory Block Error: ${errors.join(', ')}`) + } + + // Base result object + const baseResult: Record = {} + + // For add operation + if (params.operation === 'add') { + const result: Record = { + ...baseResult, + id: params.id, + type: params.type, + } + + if (params.type === 'agent') { + result.role = params.role + result.content = params.content + } else if (params.type === 'raw') { + result.rawData = params.rawData + } + + return result + } + + // For get operation + if (params.operation === 'get') { + return { + ...baseResult, + id: params.id, + } + } + + // For delete operation + if (params.operation === 'delete') { + return { + ...baseResult, + id: params.id, + } + } + + // For getAll operation + return baseResult + }, + }, }, inputs: { - code: { type: 'string', required: true }, - timeout: { type: 'number', required: false }, - memoryLimit: { type: 'number', required: false }, + operation: { type: 'string', required: true }, + id: { type: 'string', required: true }, + type: { type: 'string', required: false }, + role: { type: 'string', required: false }, + content: { type: 'string', required: false }, + rawData: { type: 'json', required: false }, }, outputs: { response: { type: { - result: 'any', - stdout: 'string', - executionTime: 'number', + memory: 'any', + memories: 'any', + id: 'string', }, }, }, subBlocks: [ { - id: 'code', + id: 'operation', + title: 'Operation', + type: 'dropdown', + layout: 'full', + options: [ + { label: 'Add Memory', id: 'add' }, + { label: 'Get All Memories', id: 'getAll' }, + { label: 'Get Memory', id: 'get' }, + { label: 'Delete Memory', id: 'delete' }, + ], + placeholder: 'Select operation', + }, + { + id: 'id', + title: 'ID', + type: 'short-input', + layout: 'full', + placeholder: 'Enter memory identifier', + condition: { + field: 'operation', + value: 'add', + }, + }, + { + id: 'id', + title: 'ID', + type: 'short-input', + layout: 'full', + placeholder: 'Enter memory identifier to retrieve', + condition: { + field: 'operation', + value: 'get', + }, + }, + { + id: 'id', + title: 'ID', + type: 'short-input', + layout: 'full', + placeholder: 'Enter memory identifier to delete', + condition: { + field: 'operation', + value: 'delete', + }, + }, + { + id: 'type', + title: 'Type', + type: 'dropdown', + layout: 'full', + options: [ + { label: 'Agent', id: 'agent' }, + { label: 'Raw', id: 'raw' }, + ], + placeholder: 'Select memory type', + condition: { + field: 'operation', + value: 'add', + }, + }, + { + id: 'role', + title: 'Role', + type: 'dropdown', + layout: 'full', + options: [ + { label: 'User', id: 'user' }, + { label: 'Assistant', id: 'assistant' }, + { label: 'System', id: 'system' }, + ], + placeholder: 'Select agent role', + condition: { + field: 'type', + value: 'agent', + and: { + field: 'operation', + value: 'add', + }, + }, + }, + { + id: 'content', + title: 'Content', + type: 'short-input', + layout: 'full', + placeholder: 'Enter message content', + condition: { + field: 'type', + value: 'agent', + and: { + field: 'operation', + value: 'add', + }, + }, + }, + { + id: 'rawData', + title: 'Raw Data', type: 'code', layout: 'full', - }, + language: 'json', + placeholder: '{"key": "value"}', + condition: { + field: 'type', + value: 'raw', + and: { + field: 'operation', + value: 'add', + }, + }, + } ], } diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index 4a44fdaa4a..4381a7fb92 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -25,6 +25,7 @@ import { GoogleDocsBlock } from './blocks/google_docs' import { GoogleDriveBlock } from './blocks/google_drive' import { GoogleSheetsBlock } from './blocks/google_sheets' // import { GuestyBlock } from './blocks/guesty' +import { MemoryBlock } from './blocks/memory' import { ImageGeneratorBlock } from './blocks/image_generator' import { JinaBlock } from './blocks/jina' import { JiraBlock } from './blocks/jira' @@ -92,6 +93,7 @@ export const registry: Record = { pinecone: PineconeBlock, reddit: RedditBlock, router: RouterBlock, + memory: MemoryBlock, s3: S3Block, serper: SerperBlock, stagehand: StagehandBlock, diff --git a/apps/sim/blocks/types.ts b/apps/sim/blocks/types.ts index a195f6d0ec..6f98e998cf 100644 --- a/apps/sim/blocks/types.ts +++ b/apps/sim/blocks/types.ts @@ -87,6 +87,7 @@ export interface SubBlockConfig { title?: string type: SubBlockType layout?: SubBlockLayout + mode?: 'basic' | 'advanced' | 'both' // Default is 'both' if not specified options?: | string[] | { label: string; id: string }[] diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 942107e350..d4f784f687 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -1863,16 +1863,27 @@ export function MistralIcon(props: SVGProps) { export function BrainIcon(props: SVGProps) { return ( - - Brain - - - - - - - - + + + + + + + + + + ) } diff --git a/apps/sim/db/migrations/0038_shocking_thor.sql b/apps/sim/db/migrations/0038_shocking_thor.sql new file mode 100644 index 0000000000..3853a2d95e --- /dev/null +++ b/apps/sim/db/migrations/0038_shocking_thor.sql @@ -0,0 +1,15 @@ +CREATE TABLE "memory" ( + "id" text PRIMARY KEY NOT NULL, + "workflow_id" text, + "key" text NOT NULL, + "type" text NOT NULL, + "data" json NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL, + "deleted_at" timestamp +); +--> statement-breakpoint +ALTER TABLE "memory" ADD CONSTRAINT "memory_workflow_id_workflow_id_fk" FOREIGN KEY ("workflow_id") REFERENCES "public"."workflow"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "memory_key_idx" ON "memory" USING btree ("key");--> statement-breakpoint +CREATE INDEX "memory_workflow_idx" ON "memory" USING btree ("workflow_id");--> statement-breakpoint +CREATE UNIQUE INDEX "memory_workflow_key_idx" ON "memory" USING btree ("workflow_id","key"); \ No newline at end of file diff --git a/apps/sim/db/migrations/meta/0038_snapshot.json b/apps/sim/db/migrations/meta/0038_snapshot.json new file mode 100644 index 0000000000..f4085554f5 --- /dev/null +++ b/apps/sim/db/migrations/meta/0038_snapshot.json @@ -0,0 +1,2233 @@ +{ + "id": "b991219f-3836-4a95-ab88-42b3ed7c69c0", + "prevId": "28c7e39e-2dc5-4bbc-9730-0c4ee54995d4", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.api_key": { + "name": "api_key", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_used": { + "name": "last_used", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "api_key_user_id_user_id_fk": { + "name": "api_key_user_id_user_id_fk", + "tableFrom": "api_key", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_key_key_unique": { + "name": "api_key_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat": { + "name": "chat", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subdomain": { + "name": "subdomain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "customizations": { + "name": "customizations", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_emails": { + "name": "allowed_emails", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "output_configs": { + "name": "output_configs", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "subdomain_idx": { + "name": "subdomain_idx", + "columns": [ + { + "expression": "subdomain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_workflow_id_workflow_id_fk": { + "name": "chat_workflow_id_workflow_id_fk", + "tableFrom": "chat", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_user_id_user_id_fk": { + "name": "chat_user_id_user_id_fk", + "tableFrom": "chat", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_tools": { + "name": "custom_tools", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema": { + "name": "schema", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "custom_tools_user_id_user_id_fk": { + "name": "custom_tools_user_id_user_id_fk", + "tableFrom": "custom_tools", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environment": { + "name": "environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "environment_user_id_user_id_fk": { + "name": "environment_user_id_user_id_fk", + "tableFrom": "environment", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "environment_user_id_unique": { + "name": "environment_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "tableTo": "user", + "columnsFrom": [ + "inviter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.marketplace": { + "name": "marketplace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_name": { + "name": "author_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "marketplace_workflow_id_workflow_id_fk": { + "name": "marketplace_workflow_id_workflow_id_fk", + "tableFrom": "marketplace", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "marketplace_author_id_user_id_fk": { + "name": "marketplace_author_id_user_id_fk", + "tableFrom": "marketplace", + "tableTo": "user", + "columnsFrom": [ + "author_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_organization_id_organization_id_fk": { + "name": "member_organization_id_organization_id_fk", + "tableFrom": "member", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.memory": { + "name": "memory", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "memory_key_idx": { + "name": "memory_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workflow_idx": { + "name": "memory_workflow_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workflow_key_idx": { + "name": "memory_workflow_key_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "memory_workflow_id_workflow_id_fk": { + "name": "memory_workflow_id_workflow_id_fk", + "tableFrom": "memory", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization": { + "name": "organization", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_active_organization_id_organization_id_fk": { + "name": "session_active_organization_id_organization_id_fk", + "tableFrom": "session", + "tableTo": "organization", + "columnsFrom": [ + "active_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.settings": { + "name": "settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "debug_mode": { + "name": "debug_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "auto_connect": { + "name": "auto_connect", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "auto_fill_env_vars": { + "name": "auto_fill_env_vars", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "telemetry_enabled": { + "name": "telemetry_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "telemetry_notified_user": { + "name": "telemetry_notified_user", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "general": { + "name": "general", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "settings_user_id_user_id_fk": { + "name": "settings_user_id_user_id_fk", + "tableFrom": "settings", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "settings_user_id_unique": { + "name": "settings_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.subscription": { + "name": "subscription", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_subscription_id": { + "name": "stripe_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "period_start": { + "name": "period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "period_end": { + "name": "period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancel_at_period_end": { + "name": "cancel_at_period_end", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "seats": { + "name": "seats", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "trial_start": { + "name": "trial_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trial_end": { + "name": "trial_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_stats": { + "name": "user_stats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "total_manual_executions": { + "name": "total_manual_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_api_calls": { + "name": "total_api_calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_webhook_triggers": { + "name": "total_webhook_triggers", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_scheduled_executions": { + "name": "total_scheduled_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_chat_executions": { + "name": "total_chat_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_tokens_used": { + "name": "total_tokens_used", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cost": { + "name": "total_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "last_active": { + "name": "last_active", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_stats_user_id_user_id_fk": { + "name": "user_stats_user_id_user_id_fk", + "tableFrom": "user_stats", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_stats_user_id_unique": { + "name": "user_stats_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.waitlist": { + "name": "waitlist", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "waitlist_email_unique": { + "name": "waitlist_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook": { + "name": "webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_config": { + "name": "provider_config", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "path_idx": { + "name": "path_idx", + "columns": [ + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "webhook_workflow_id_workflow_id_fk": { + "name": "webhook_workflow_id_workflow_id_fk", + "tableFrom": "webhook", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow": { + "name": "workflow", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#3972F6'" + }, + "last_synced": { + "name": "last_synced", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "is_deployed": { + "name": "is_deployed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deployed_state": { + "name": "deployed_state", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "deployed_at": { + "name": "deployed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "collaborators": { + "name": "collaborators", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "marketplace_data": { + "name": "marketplace_data", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "is_published": { + "name": "is_published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "workflow_user_id_user_id_fk": { + "name": "workflow_user_id_user_id_fk", + "tableFrom": "workflow", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_workspace_id_workspace_id_fk": { + "name": "workflow_workspace_id_workspace_id_fk", + "tableFrom": "workflow", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_logs": { + "name": "workflow_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "duration": { + "name": "duration", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "workflow_logs_workflow_id_workflow_id_fk": { + "name": "workflow_logs_workflow_id_workflow_id_fk", + "tableFrom": "workflow_logs", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_schedule": { + "name": "workflow_schedule", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_ran_at": { + "name": "last_ran_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trigger_type": { + "name": "trigger_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_failed_at": { + "name": "last_failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workflow_schedule_workflow_id_workflow_id_fk": { + "name": "workflow_schedule_workflow_id_workflow_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workflow_schedule_workflow_id_unique": { + "name": "workflow_schedule_workflow_id_unique", + "nullsNotDistinct": false, + "columns": [ + "workflow_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace": { + "name": "workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_owner_id_user_id_fk": { + "name": "workspace_owner_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_invitation": { + "name": "workspace_invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_invitation_workspace_id_workspace_id_fk": { + "name": "workspace_invitation_workspace_id_workspace_id_fk", + "tableFrom": "workspace_invitation", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_invitation_inviter_id_user_id_fk": { + "name": "workspace_invitation_inviter_id_user_id_fk", + "tableFrom": "workspace_invitation", + "tableTo": "user", + "columnsFrom": [ + "inviter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_invitation_token_unique": { + "name": "workspace_invitation_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_member": { + "name": "workspace_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_workspace_idx": { + "name": "user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_member_workspace_id_workspace_id_fk": { + "name": "workspace_member_workspace_id_workspace_id_fk", + "tableFrom": "workspace_member", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_member_user_id_user_id_fk": { + "name": "workspace_member_user_id_user_id_fk", + "tableFrom": "workspace_member", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/sim/db/migrations/meta/_journal.json b/apps/sim/db/migrations/meta/_journal.json index efb8e3b3ea..20e384f71f 100644 --- a/apps/sim/db/migrations/meta/_journal.json +++ b/apps/sim/db/migrations/meta/_journal.json @@ -261,6 +261,13 @@ "when": 1747460441992, "tag": "0037_outgoing_madame_hydra", "breakpoints": true + }, + { + "idx": 38, + "version": "7", + "when": 1747559012564, + "tag": "0038_shocking_thor", + "breakpoints": true } ] -} +} \ No newline at end of file diff --git a/apps/sim/db/schema.ts b/apps/sim/db/schema.ts index 507468831d..d9b4d7b499 100644 --- a/apps/sim/db/schema.ts +++ b/apps/sim/db/schema.ts @@ -7,6 +7,7 @@ import { text, timestamp, uniqueIndex, + index, } from 'drizzle-orm/pg-core' export const user = pgTable('user', { @@ -380,3 +381,29 @@ export const workspaceInvitation = pgTable('workspace_invitation', { createdAt: timestamp('created_at').notNull().defaultNow(), updatedAt: timestamp('updated_at').notNull().defaultNow(), }) + +export const memory = pgTable( + 'memory', + { + id: text('id').primaryKey(), + workflowId: text('workflow_id').references(() => workflow.id, { onDelete: 'cascade' }), + key: text('key').notNull(), // Identifier for the memory within its context + type: text('type').notNull(), // 'agent' or 'raw' + data: json('data').notNull(), // Stores either agent message data or raw data + createdAt: timestamp('created_at').notNull().defaultNow(), + updatedAt: timestamp('updated_at').notNull().defaultNow(), + deletedAt: timestamp('deleted_at'), + }, + (table) => { + return { + // Add index on key for faster lookups + keyIdx: index('memory_key_idx').on(table.key), + + // Add index on workflowId for faster filtering + workflowIdx: index('memory_workflow_idx').on(table.workflowId), + + // Compound unique index to ensure keys are unique per workflow + uniqueKeyPerWorkflowIdx: uniqueIndex('memory_workflow_key_idx').on(table.workflowId, table.key), + } + } +) diff --git a/apps/sim/executor/handlers/agent/agent-handler.test.ts b/apps/sim/executor/handlers/agent/agent-handler.test.ts index 5abe8d4a0f..b6af322a2d 100644 --- a/apps/sim/executor/handlers/agent/agent-handler.test.ts +++ b/apps/sim/executor/handlers/agent/agent-handler.test.ts @@ -761,6 +761,142 @@ describe('AgentBlockHandler', () => { ) }) + // Tests for raw messages parameter + it('should execute with raw JSON messages array', async () => { + const inputs = { + model: 'gpt-4o', + messages: [ + { role: 'system', content: 'You are a helpful assistant.' }, + { role: 'user', content: 'Hello, how are you?' } + ], + apiKey: 'test-api-key', + } + + mockGetProviderFromModel.mockReturnValue('openai') + + await handler.execute(mockBlock, inputs, mockContext) + + const fetchCall = mockFetch.mock.calls[0] + const requestBody = JSON.parse(fetchCall[1].body) + + // Verify messages were sent to the provider + expect(requestBody.messages).toBeDefined() + expect(requestBody.messages.length).toBe(2) + expect(requestBody.messages[0].role).toBe('system') + expect(requestBody.messages[1].role).toBe('user') + + // Verify system prompt and context are not included + expect(requestBody.systemPrompt).toBeUndefined() + expect(requestBody.context).toBeUndefined() + }) + + it('should parse and use messages with single quotes', async () => { + const inputs = { + model: 'gpt-4o', + // Single-quoted JSON format + messages: `[{'role': 'system', 'content': 'You are a helpful assistant.'}, {'role': 'user', 'content': 'Hello, how are you?'}]`, + apiKey: 'test-api-key', + } + + mockGetProviderFromModel.mockReturnValue('openai') + + await handler.execute(mockBlock, inputs, mockContext) + + const fetchCall = mockFetch.mock.calls[0] + const requestBody = JSON.parse(fetchCall[1].body) + + // Verify messages were parsed and sent to the provider + expect(requestBody.messages).toBeDefined() + expect(requestBody.messages.length).toBe(2) + expect(requestBody.messages[0].role).toBe('system') + expect(requestBody.messages[0].content).toBe('You are a helpful assistant.') + expect(requestBody.messages[1].role).toBe('user') + expect(requestBody.messages[1].content).toBe('Hello, how are you?') + }) + + it('should prioritize messages over systemPrompt and context when both are provided', async () => { + const inputs = { + model: 'gpt-4o', + // Valid messages array should take priority + messages: [ + { role: 'system', content: 'You are an AI assistant.' }, + { role: 'user', content: 'What is the capital of France?' } + ], + // These should be ignored since messages are valid + systemPrompt: 'You are a helpful assistant.', + context: 'Tell me about the weather.', + apiKey: 'test-api-key', + } + + mockGetProviderFromModel.mockReturnValue('openai') + + await handler.execute(mockBlock, inputs, mockContext) + + const fetchCall = mockFetch.mock.calls[0] + const requestBody = JSON.parse(fetchCall[1].body) + + // Verify messages were sent to the provider + expect(requestBody.messages).toBeDefined() + expect(requestBody.messages.length).toBe(2) + expect(requestBody.messages[0].content).toBe('You are an AI assistant.') + expect(requestBody.messages[1].content).toBe('What is the capital of France?') + + // Verify system prompt and context are not included + expect(requestBody.systemPrompt).toBeUndefined() + expect(requestBody.context).toBeUndefined() + }) + + it('should fall back to systemPrompt and context if messages array is invalid', async () => { + const inputs = { + model: 'gpt-4o', + // Invalid messages array (missing required 'role' field) + messages: [ + { content: 'This message is missing the role field' }, + { role: 'user', content: 'Hello' } + ], + // These should be used as fallback + systemPrompt: 'You are a helpful assistant.', + context: 'Help the user with their query.', + apiKey: 'test-api-key', + } + + mockGetProviderFromModel.mockReturnValue('openai') + + await handler.execute(mockBlock, inputs, mockContext) + + const fetchCall = mockFetch.mock.calls[0] + const requestBody = JSON.parse(fetchCall[1].body) + + // Verify fallback to systemPrompt and context + expect(requestBody.messages).toBeUndefined() + expect(requestBody.systemPrompt).toBe('You are a helpful assistant.') + expect(requestBody.context).toBe('Help the user with their query.') + }) + + it('should handle messages with mixed quote styles', async () => { + const inputs = { + model: 'gpt-4o', + // Mixed quote styles as shown in the user's example + messages: `[{'role': 'system', "content": "Only answer questions about the United States. If someone asks about something else, just say you can't help with that."}, {"role": "user", "content": "What's the capital of Bosnia and Herzegovina?"}]`, + apiKey: 'test-api-key', + } + + mockGetProviderFromModel.mockReturnValue('openai') + + await handler.execute(mockBlock, inputs, mockContext) + + const fetchCall = mockFetch.mock.calls[0] + const requestBody = JSON.parse(fetchCall[1].body) + + // Verify messages were parsed and sent to the provider + expect(requestBody.messages).toBeDefined() + expect(requestBody.messages.length).toBe(2) + expect(requestBody.messages[0].role).toBe('system') + expect(requestBody.messages[0].content).toBe("Only answer questions about the United States. If someone asks about something else, just say you can't help with that.") + expect(requestBody.messages[1].role).toBe('user') + expect(requestBody.messages[1].content).toBe("What's the capital of Bosnia and Herzegovina?") + }) + it('should handle streaming responses with text/event-stream content type', async () => { const mockStreamBody = { getReader: vi.fn().mockReturnValue({ diff --git a/apps/sim/executor/handlers/agent/agent-handler.ts b/apps/sim/executor/handlers/agent/agent-handler.ts index 07e6902618..b540ae50e4 100644 --- a/apps/sim/executor/handlers/agent/agent-handler.ts +++ b/apps/sim/executor/handlers/agent/agent-handler.ts @@ -188,16 +188,86 @@ export class AgentBlockHandler implements BlockHandler { ) } + // Parse messages if they're in string format + let parsedMessages = inputs.messages; + if (typeof inputs.messages === 'string' && inputs.messages.trim()) { + try { + // Fast path: try standard JSON.parse first + try { + parsedMessages = JSON.parse(inputs.messages); + logger.info('Successfully parsed messages from JSON format'); + } catch (jsonError) { + // Fast direct approach for single-quoted JSON + // Replace single quotes with double quotes, but keep single quotes inside double quotes + // This optimized approach handles the most common cases in one pass + const preprocessed = inputs.messages + // Ensure we have valid JSON by replacing all single quotes with double quotes, + // except those inside existing double quotes + .replace(/(['"])(.*?)\1/g, (match, quote, content) => { + if (quote === '"') return match; // Keep existing double quotes intact + return `"${content}"`; // Replace single quotes with double quotes + }); + + try { + parsedMessages = JSON.parse(preprocessed); + logger.info('Successfully parsed messages after single-quote preprocessing'); + } catch (preprocessError) { + // Ultimate fallback: simply replace all single quotes + try { + parsedMessages = JSON.parse(inputs.messages.replace(/'/g, '"')); + logger.info('Successfully parsed messages using direct quote replacement'); + } catch (finalError) { + logger.error('All parsing attempts failed', { + original: inputs.messages, + error: finalError + }); + // Keep original value + } + } + } + } catch (error) { + logger.error('Failed to parse messages from string:', { error }); + // Keep original value if all parsing fails + } + } + + // Fast validation of parsed messages + const validMessages = Array.isArray(parsedMessages) && + parsedMessages.length > 0 && + parsedMessages.every(msg => + typeof msg === 'object' && + msg !== null && + 'role' in msg && + typeof msg.role === 'string' && + ( + 'content' in msg || + (msg.role === 'assistant' && ('function_call' in msg || 'tool_calls' in msg)) + ) + ); + + if (Array.isArray(parsedMessages) && parsedMessages.length > 0 && !validMessages) { + logger.warn('Messages array has invalid format:', { + messageCount: parsedMessages.length + }); + } else if (validMessages) { + logger.info('Messages validated successfully'); + } + // Debug request before sending to provider const providerRequest = { provider: providerId, model, - systemPrompt: inputs.systemPrompt, - context: Array.isArray(inputs.context) - ? JSON.stringify(inputs.context, null, 2) - : typeof inputs.context === 'string' - ? inputs.context - : JSON.stringify(inputs.context, null, 2), + // If messages are provided (advanced mode), use them exclusively and skip systemPrompt/context + ...(validMessages + ? { messages: parsedMessages } + : { + systemPrompt: inputs.systemPrompt, + context: Array.isArray(inputs.context) + ? JSON.stringify(inputs.context, null, 2) + : typeof inputs.context === 'string' + ? inputs.context + : JSON.stringify(inputs.context, null, 2), + }), tools: formattedTools.length > 0 ? formattedTools : undefined, temperature: inputs.temperature, maxTokens: inputs.maxTokens, @@ -209,14 +279,18 @@ export class AgentBlockHandler implements BlockHandler { logger.info(`Provider request prepared`, { model: providerRequest.model, - hasSystemPrompt: !!providerRequest.systemPrompt, - hasContext: !!providerRequest.context, + hasMessages: Array.isArray(parsedMessages) && parsedMessages.length > 0, + hasSystemPrompt: !(Array.isArray(parsedMessages) && parsedMessages.length > 0) && !!inputs.systemPrompt, + hasContext: !(Array.isArray(parsedMessages) && parsedMessages.length > 0) && !!inputs.context, hasTools: !!providerRequest.tools, hasApiKey: !!providerRequest.apiKey, workflowId: providerRequest.workflowId, stream: shouldUseStreaming, isBlockSelectedForOutput, hasOutgoingConnections, + // Debug info about messages to help diagnose issues + messagesProvided: 'messages' in providerRequest, + messagesCount: 'messages' in providerRequest && Array.isArray(providerRequest.messages) ? providerRequest.messages.length : 0 }) const baseUrl = env.NEXT_PUBLIC_APP_URL || '' diff --git a/apps/sim/stores/workflows/workflow/store.ts b/apps/sim/stores/workflows/workflow/store.ts index d4ece4ef70..8aebeccaf4 100644 --- a/apps/sim/stores/workflows/workflow/store.ts +++ b/apps/sim/stores/workflows/workflow/store.ts @@ -770,6 +770,57 @@ export const useWorkflowStore = create()( get().sync.markDirty() get().sync.forceSync() }, + + toggleBlockAdvancedMode: (id: string) => { + const block = get().blocks[id] + if (!block) return + + const newState = { + blocks: { + ...get().blocks, + [id]: { + ...block, + advancedMode: !block.advancedMode, + }, + }, + edges: [...get().edges], + loops: { ...get().loops }, + } + + set(newState) + + // Clear the appropriate subblock values based on the new mode + const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId + if (activeWorkflowId) { + const subBlockStore = useSubBlockStore.getState() + const blockValues = subBlockStore.workflowValues[activeWorkflowId]?.[id] || {} + const updatedValues = { ...blockValues } + + if (!block.advancedMode) { + // Switching TO advanced mode, clear system prompt and context (basic mode fields) + updatedValues.systemPrompt = null + updatedValues.context = null + } else { + // Switching TO basic mode, clear messages (advanced mode field) + updatedValues.messages = null + } + + // Update subblock store with the cleared values + useSubBlockStore.setState({ + workflowValues: { + ...subBlockStore.workflowValues, + [activeWorkflowId]: { + ...subBlockStore.workflowValues[activeWorkflowId], + [id]: updatedValues + } + } + }) + } + + get().triggerUpdate() + get().sync.markDirty() + get().sync.forceSync() + }, })), { name: 'workflow-store' } ) diff --git a/apps/sim/stores/workflows/workflow/types.ts b/apps/sim/stores/workflows/workflow/types.ts index e409bbabc4..e46c3c09c1 100644 --- a/apps/sim/stores/workflows/workflow/types.ts +++ b/apps/sim/stores/workflows/workflow/types.ts @@ -17,6 +17,7 @@ export interface BlockState { horizontalHandles?: boolean isWide?: boolean height?: number + advancedMode?: boolean } export interface SubBlockState { @@ -78,6 +79,7 @@ export interface WorkflowActions { setDeploymentStatus: (isDeployed: boolean, deployedAt?: Date) => void setScheduleStatus: (hasActiveSchedule: boolean) => void setWebhookStatus: (hasActiveWebhook: boolean) => void + toggleBlockAdvancedMode: (id: string) => void // Add the sync control methods to the WorkflowActions interface sync: SyncControl diff --git a/apps/sim/tools/memory/add_memory.ts b/apps/sim/tools/memory/add_memory.ts new file mode 100644 index 0000000000..7a85660d30 --- /dev/null +++ b/apps/sim/tools/memory/add_memory.ts @@ -0,0 +1,170 @@ +import { ToolConfig } from '../types' +import { MemoryResponse } from './types' + +// Add Memory Tool +export const memoryAddTool: ToolConfig = { + id: 'memory_add', + name: 'Add Memory', + description: 'Add a new memory to the database or append to existing memory with the same ID. When appending to existing memory, the memory types must match.', + version: '1.0.0', + params: { + id: { + type: 'string', + required: true, + description: 'Identifier for the memory. If a memory with this ID already exists, the new data will be appended to it.', + }, + type: { + type: 'string', + required: true, + description: 'Type of memory (agent or raw)', + }, + role: { + type: 'string', + required: false, + description: 'Role for agent memory (user, assistant, or system)', + }, + content: { + type: 'string', + required: false, + description: 'Content for agent memory', + }, + rawData: { + type: 'json', + required: false, + description: 'Raw data to store (JSON format)', + } + }, + request: { + url: '/api/memory', + method: 'POST', + headers: () => ({ + 'Content-Type': 'application/json', + }), + body: (params) => { + // Get workflowId from context (set by workflow execution) + const workflowId = params._context?.workflowId + + // Prepare error response instead of throwing error + if (!workflowId) { + return { + _errorResponse: { + status: 400, + data: { + success: false, + error: { + message: 'workflowId is required and must be provided in execution context' + } + } + } + } + } + + const body: Record = { + key: params.id, + type: params.type, + workflowId + } + + // Set data based on type + if (params.type === 'agent') { + if (!params.role || !params.content) { + return { + _errorResponse: { + status: 400, + data: { + success: false, + error: { + message: 'Role and content are required for agent memory' + } + } + } + } + } + body.data = { + role: params.role, + content: params.content, + } + } else if (params.type === 'raw') { + if (!params.rawData) { + return { + _errorResponse: { + status: 400, + data: { + success: false, + error: { + message: 'Raw data is required for raw memory' + } + } + } + } + } + + let parsedRawData + if (typeof params.rawData === 'string') { + try { + parsedRawData = JSON.parse(params.rawData) + } catch (e) { + return { + _errorResponse: { + status: 400, + data: { + success: false, + error: { + message: 'Invalid JSON for raw data' + } + } + } + } + } + } else { + parsedRawData = params.rawData + } + + body.data = parsedRawData + } + + return body + }, + isInternalRoute: true, + }, + transformResponse: async (response): Promise => { + try { + const result = await response.json() + + if (!response.ok) { + const errorMessage = result.error?.message || 'Failed to add memory' + throw new Error(errorMessage) + } + + const data = result.data || result + const isNewMemory = response.status === 201 + + return { + success: true, + output: { + memory: data.data, + message: isNewMemory ? 'Memory created successfully' : 'Memory appended successfully' + }, + } + } catch (error: any) { + return { + success: false, + output: { + memory: undefined, + message: `Failed to add memory: ${error.message || 'Unknown error occurred'}` + }, + } + } + }, + transformError: async (error): Promise => { + const errorMessage = `Memory operation failed: ${error.message || 'Unknown error occurred'}`; + return { + success: false, + output: { + memory: undefined, + message: `Memory operation failed: ${error.message || 'Unknown error occurred'}` + }, + error: errorMessage + } + }, +} \ No newline at end of file diff --git a/apps/sim/tools/memory/delete_memory.ts b/apps/sim/tools/memory/delete_memory.ts new file mode 100644 index 0000000000..7b7bb77bc3 --- /dev/null +++ b/apps/sim/tools/memory/delete_memory.ts @@ -0,0 +1,83 @@ +import { ToolConfig } from '../types' +import { MemoryResponse } from './types' + +// Delete Memory Tool +export const memoryDeleteTool: ToolConfig = { + id: 'memory_delete', + name: 'Delete Memory', + description: 'Delete a specific memory by its ID', + version: '1.0.0', + params: { + id: { + type: 'string', + required: true, + description: 'Identifier for the memory to delete', + } + }, + request: { + url: (params): any => { + // Get workflowId from context (set by workflow execution) + const workflowId = params._context?.workflowId + + if (!workflowId) { + return { + _errorResponse: { + status: 400, + data: { + success: false, + error: { + message: 'workflowId is required and must be provided in execution context' + } + } + } + } + } + + // Append workflowId as query parameter + return `/api/memory/${encodeURIComponent(params.id)}?workflowId=${encodeURIComponent(workflowId)}` + }, + method: 'DELETE', + headers: () => ({ + 'Content-Type': 'application/json', + }), + isInternalRoute: true, + }, + transformResponse: async (response): Promise => { + try { + const result = await response.json() + + if (!response.ok) { + const errorMessage = result.error?.message || 'Failed to delete memory' + throw new Error(errorMessage) + } + + return { + success: true, + output: { + memory: undefined, + message: `Deleted memory.` + }, + } + } catch (error: any) { + return { + success: false, + output: { + memory: undefined, + message: `Failed to delete memory: ${error.message || 'Unknown error'}` + }, + error: `Failed to delete memory: ${error.message || 'Unknown error'}` + } + } + }, + transformError: async (error): Promise => { + const errorMessage = `Memory deletion failed: ${error.message || 'Unknown error'}` + return { + success: false, + output: { + memory: undefined, + message: errorMessage + }, + error: errorMessage + } + }, +} \ No newline at end of file diff --git a/apps/sim/tools/memory/get_all_memories.ts b/apps/sim/tools/memory/get_all_memories.ts new file mode 100644 index 0000000000..cd4de28f4d --- /dev/null +++ b/apps/sim/tools/memory/get_all_memories.ts @@ -0,0 +1,88 @@ +import { ToolConfig } from '../types' +import { MemoryResponse } from './types' + +// Get All Memories Tool +export const memoryGetAllTool: ToolConfig = { + id: 'memory_get_all', + name: 'Get All Memories', + description: 'Retrieve all memories from the database', + version: '1.0.0', + params: {}, + request: { + url: (params): any => { + // Get workflowId from context (set by workflow execution) + const workflowId = params._context?.workflowId + + if (!workflowId) { + return { + _errorResponse: { + status: 400, + data: { + success: false, + error: { + message: 'workflowId is required and must be provided in execution context' + } + } + } + } + } + + // Append workflowId as query parameter + return `/api/memory?workflowId=${encodeURIComponent(workflowId)}` + }, + method: 'GET', + headers: () => ({ + 'Content-Type': 'application/json', + }), + isInternalRoute: true, + }, + transformResponse: async (response): Promise => { + try { + const result = await response.json() + + if (!response.ok) { + const errorMessage = result.error?.message || 'Failed to retrieve memories' + throw new Error(errorMessage) + } + + // Extract memories from the response + const data = result.data || result + let rawMemories = data.memories || data || []; + + // Transform memories to return them with their keys and types for better context + const memories = rawMemories.map((memory: any) => ({ + key: memory.key, + type: memory.type, + data: memory.data + })); + + return { + success: true, + output: { + memories, + message: 'Memories retrieved successfully' + }, + } + } catch (error: any) { + return { + success: false, + output: { + memories: [], + message: `Failed to retrieve memories: ${error.message || 'Unknown error'}` + }, + error: `Failed to retrieve memories: ${error.message || 'Unknown error'}` + } + } + }, + transformError: async (error): Promise => { + const errorMessage = `Memory retrieval failed: ${error.message || 'Unknown error'}` + return { + success: false, + output: { + memories: [], + message: errorMessage + }, + error: errorMessage + } + }, +} \ No newline at end of file diff --git a/apps/sim/tools/memory/get_memory.ts b/apps/sim/tools/memory/get_memory.ts new file mode 100644 index 0000000000..e97aad358d --- /dev/null +++ b/apps/sim/tools/memory/get_memory.ts @@ -0,0 +1,85 @@ +import { ToolConfig } from '../types' +import { MemoryResponse } from './types' + +// Get Memory Tool +export const memoryGetTool: ToolConfig = { + id: 'memory_get', + name: 'Get Memory', + description: 'Retrieve a specific memory by its ID', + version: '1.0.0', + params: { + id: { + type: 'string', + required: true, + description: 'Identifier for the memory to retrieve', + } + }, + request: { + url: (params): any => { + // Get workflowId from context (set by workflow execution) + const workflowId = params._context?.workflowId + + if (!workflowId) { + return { + _errorResponse: { + status: 400, + data: { + success: false, + error: { + message: 'workflowId is required and must be provided in execution context' + } + } + } + } + } + + // Append workflowId as query parameter + return `/api/memory/${encodeURIComponent(params.id)}?workflowId=${encodeURIComponent(workflowId)}` + }, + method: 'GET', + headers: () => ({ + 'Content-Type': 'application/json', + }), + isInternalRoute: true, + }, + transformResponse: async (response): Promise => { + try { + const result = await response.json() + + if (!response.ok) { + const errorMessage = result.error?.message || 'Failed to retrieve memory' + throw new Error(errorMessage) + } + + const data = result.data || result + + return { + success: true, + output: { + memory: data.data, + message: 'Memory retrieved successfully' + }, + } + } catch (error: any) { + return { + success: false, + output: { + memory: undefined, + message: `Failed to retrieve memory: ${error.message || 'Unknown error'}` + }, + error: `Failed to retrieve memory: ${error.message || 'Unknown error'}` + } + } + }, + transformError: async (error): Promise => { + const errorMessage = `Memory retrieval failed: ${error.message || 'Unknown error'}` + return { + success: false, + output: { + memory: undefined, + message: errorMessage + }, + error: errorMessage + } + }, +} \ No newline at end of file diff --git a/apps/sim/tools/memory/index.ts b/apps/sim/tools/memory/index.ts new file mode 100644 index 0000000000..909d75afa1 --- /dev/null +++ b/apps/sim/tools/memory/index.ts @@ -0,0 +1,6 @@ +import { memoryAddTool } from './add_memory' +import { memoryGetTool } from './get_memory' +import { memoryGetAllTool } from './get_all_memories' +import { memoryDeleteTool } from './delete_memory' + +export { memoryAddTool, memoryGetTool, memoryGetAllTool, memoryDeleteTool } \ No newline at end of file diff --git a/apps/sim/tools/memory/types.ts b/apps/sim/tools/memory/types.ts new file mode 100644 index 0000000000..bfc6687f4b --- /dev/null +++ b/apps/sim/tools/memory/types.ts @@ -0,0 +1,35 @@ +import { ToolResponse } from '../types' + +export interface MemoryResponse extends ToolResponse { + output: { + memory?: any + memories?: any[] + message: string + } +} + +export interface AgentMemoryData { + role: 'user' | 'assistant' | 'system' + content: string +} + +export interface RawMemoryData { + [key: string]: any +} + +export interface MemoryRecord { + id: string + key: string + type: 'agent' | 'raw' + data: AgentMemoryData[] | RawMemoryData + createdAt: string + updatedAt: string + workflowId?: string + workspaceId?: string +} + +export interface MemoryError { + code: string + message: string + details?: Record +} \ No newline at end of file diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 88c1f552a4..5ae72556c4 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -42,6 +42,7 @@ import { readUrlTool } from './jina' import { jiraBulkRetrieveTool, jiraRetrieveTool, jiraUpdateTool, jiraWriteTool } from './jira' import { linkupSearchTool } from './linkup' import { mem0AddMemoriesTool, mem0GetMemoriesTool, mem0SearchMemoriesTool } from './mem0' +import { memoryAddTool, memoryGetTool, memoryGetAllTool, memoryDeleteTool } from './memory' import { mistralParserTool } from './mistral' import { notionReadTool, notionWriteTool } from './notion' import { dalleTool, embeddingsTool as openAIEmbeddings } from './openai' @@ -154,6 +155,10 @@ export const tools: Record = { mem0_add_memories: mem0AddMemoriesTool, mem0_search_memories: mem0SearchMemoriesTool, mem0_get_memories: mem0GetMemoriesTool, + memory_add: memoryAddTool, + memory_get: memoryGetTool, + memory_get_all: memoryGetAllTool, + memory_delete: memoryDeleteTool, elevenlabs_tts: elevenLabsTtsTool, s3_get_object: s3GetObjectTool, telegram_message: telegramMessageTool,