mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
Initial version of lambda creation works
This commit is contained in:
183
apps/sim/app/api/tools/aws-lambda/deploy/route.ts
Normal file
183
apps/sim/app/api/tools/aws-lambda/deploy/route.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { LambdaClient, CreateFunctionCommand, UpdateFunctionCodeCommand, GetFunctionCommand, Runtime } from '@aws-sdk/client-lambda'
|
||||
import JSZip from 'jszip'
|
||||
import { z } from 'zod'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
|
||||
const logger = createLogger('AWSLambdaDeployAPI')
|
||||
|
||||
// Validation schema for the request body
|
||||
const DeployRequestSchema = z.object({
|
||||
accessKeyId: z.string().min(1, 'AWS Access Key ID is required'),
|
||||
secretAccessKey: z.string().min(1, 'AWS Secret Access Key is required'),
|
||||
region: z.string().min(1, 'AWS Region is required'),
|
||||
functionName: z.string().min(1, 'Function name is required'),
|
||||
handler: z.string().optional(),
|
||||
runtime: z.string().min(1, 'Runtime is required'),
|
||||
code: z.string().min(1, 'Function code is required'),
|
||||
requirements: z.string().nullable().optional(),
|
||||
packageJson: z.string().nullable().optional(),
|
||||
timeout: z.number().min(1).max(900),
|
||||
memorySize: z.number().min(128).max(10240),
|
||||
environmentVariables: z.record(z.string()).default({}),
|
||||
tags: z.record(z.string()).default({}),
|
||||
role: z.string().min(1, 'Role ARN is required'),
|
||||
})
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
|
||||
try {
|
||||
logger.info(`[${requestId}] Processing AWS Lambda deployment request`)
|
||||
|
||||
// Parse and validate request body
|
||||
const body = await request.json()
|
||||
|
||||
const validationResult = DeployRequestSchema.safeParse(body)
|
||||
if (!validationResult.success) {
|
||||
logger.warn(`[${requestId}] Invalid request body`, { errors: validationResult.error.errors })
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid request parameters', details: validationResult.error.errors },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const params = validationResult.data
|
||||
console.log(`[${requestId}] Received params:`, JSON.stringify(params, null, 2))
|
||||
logger.info(`[${requestId}] Deploying Lambda function: ${params.functionName}`)
|
||||
|
||||
// Create Lambda client
|
||||
const lambdaClient = new LambdaClient({
|
||||
region: params.region,
|
||||
credentials: {
|
||||
accessKeyId: params.accessKeyId,
|
||||
secretAccessKey: params.secretAccessKey,
|
||||
},
|
||||
})
|
||||
|
||||
// Create ZIP file with the Lambda code and dependencies
|
||||
const zip = new JSZip()
|
||||
|
||||
// Add the main function code
|
||||
const fileExtension = getFileExtension(params.runtime)
|
||||
const fileName = `index.${fileExtension}`
|
||||
zip.file(fileName, params.code)
|
||||
|
||||
// Add dependencies based on runtime
|
||||
if (params.runtime.startsWith('python') && params.requirements && params.requirements.trim() !== '') {
|
||||
zip.file('requirements.txt', params.requirements)
|
||||
} else if (params.runtime.startsWith('nodejs') && params.packageJson && params.packageJson.trim() !== '') {
|
||||
zip.file('package.json', params.packageJson)
|
||||
}
|
||||
|
||||
const zipBuffer = await zip.generateAsync({ type: 'nodebuffer' })
|
||||
|
||||
// Check if function already exists
|
||||
let functionExists = false
|
||||
try {
|
||||
await lambdaClient.send(new GetFunctionCommand({ FunctionName: params.functionName }))
|
||||
functionExists = true
|
||||
logger.info(`[${requestId}] Function ${params.functionName} already exists, updating code`)
|
||||
} catch (error: any) {
|
||||
if (error.name === 'ResourceNotFoundException') {
|
||||
functionExists = false
|
||||
logger.info(`[${requestId}] Function ${params.functionName} does not exist, creating new function`)
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
let result: any
|
||||
|
||||
if (functionExists) {
|
||||
// Update existing function code
|
||||
const updateParams = {
|
||||
FunctionName: params.functionName,
|
||||
ZipFile: zipBuffer,
|
||||
}
|
||||
|
||||
result = await lambdaClient.send(new UpdateFunctionCodeCommand(updateParams))
|
||||
logger.info(`[${requestId}] Lambda function code updated: ${result.FunctionArn}`)
|
||||
} else {
|
||||
// Create new function
|
||||
if (!params.role) {
|
||||
throw new Error('Role ARN is required for creating new Lambda functions. Please provide a valid IAM Role ARN.')
|
||||
}
|
||||
|
||||
const createParams = {
|
||||
FunctionName: params.functionName,
|
||||
Runtime: params.runtime as Runtime,
|
||||
Role: params.role,
|
||||
Handler: params.handler || getDefaultHandler(params.runtime),
|
||||
Code: {
|
||||
ZipFile: zipBuffer,
|
||||
},
|
||||
Timeout: params.timeout,
|
||||
MemorySize: params.memorySize,
|
||||
Environment: {
|
||||
Variables: params.environmentVariables,
|
||||
},
|
||||
Tags: params.tags,
|
||||
}
|
||||
|
||||
result = await lambdaClient.send(new CreateFunctionCommand(createParams))
|
||||
logger.info(`[${requestId}] Lambda function created: ${result.FunctionArn}`)
|
||||
}
|
||||
|
||||
// Get function details for response
|
||||
const functionDetails = await lambdaClient.send(
|
||||
new GetFunctionCommand({ FunctionName: params.functionName })
|
||||
)
|
||||
|
||||
const response = {
|
||||
functionArn: functionDetails.Configuration?.FunctionArn || '',
|
||||
functionName: functionDetails.Configuration?.FunctionName || '',
|
||||
runtime: functionDetails.Configuration?.Runtime || '',
|
||||
region: params.region,
|
||||
status: functionDetails.Configuration?.State || '',
|
||||
lastModified: functionDetails.Configuration?.LastModified || '',
|
||||
codeSize: functionDetails.Configuration?.CodeSize || 0,
|
||||
description: functionDetails.Configuration?.Description || '',
|
||||
timeout: functionDetails.Configuration?.Timeout || 0,
|
||||
memorySize: functionDetails.Configuration?.MemorySize || 0,
|
||||
environment: functionDetails.Configuration?.Environment?.Variables || {},
|
||||
tags: functionDetails.Tags || {},
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
output: response,
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error deploying Lambda function`, error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to deploy Lambda function'
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
function getFileExtension(runtime: string): string {
|
||||
if (runtime.startsWith('nodejs')) return 'js'
|
||||
if (runtime.startsWith('python')) return 'py'
|
||||
if (runtime.startsWith('java')) return 'java'
|
||||
if (runtime.startsWith('dotnet')) return 'cs'
|
||||
if (runtime.startsWith('go')) return 'go'
|
||||
if (runtime.startsWith('ruby')) return 'rb'
|
||||
return 'js' // default
|
||||
}
|
||||
|
||||
function getDefaultHandler(runtime: string): string {
|
||||
if (runtime.startsWith('nodejs')) return 'index.handler'
|
||||
if (runtime.startsWith('python')) return 'index.lambda_handler'
|
||||
if (runtime.startsWith('java')) return 'com.example.LambdaFunction::handleRequest'
|
||||
if (runtime.startsWith('dotnet')) return 'LambdaFunction::LambdaFunction.Function::FunctionHandler'
|
||||
if (runtime.startsWith('go')) return 'main'
|
||||
if (runtime.startsWith('ruby')) return 'index.lambda_handler'
|
||||
return 'index.handler' // default
|
||||
}
|
||||
@@ -77,6 +77,14 @@ export const AWSLambdaBlock: BlockConfig<AWSLambdaResponse> = {
|
||||
'sa-east-1',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'role',
|
||||
title: 'Role ARN',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Enter the IAM Role ARN for Lambda execution',
|
||||
password: false,
|
||||
},
|
||||
{
|
||||
id: 'functionName',
|
||||
title: 'Function Name',
|
||||
@@ -195,6 +203,7 @@ export const AWSLambdaBlock: BlockConfig<AWSLambdaResponse> = {
|
||||
accessKeyId: { type: 'string', required: true },
|
||||
secretAccessKey: { type: 'string', required: true },
|
||||
region: { type: 'string', required: true },
|
||||
role: { type: 'string', required: true },
|
||||
functionName: { type: 'string', required: true },
|
||||
handler: { type: 'string', required: false },
|
||||
runtime: { type: 'string', required: true },
|
||||
|
||||
@@ -27,8 +27,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.39.0",
|
||||
"@aws-sdk/client-s3": "^3.779.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.779.0",
|
||||
"@aws-sdk/client-lambda": "3.840.0",
|
||||
"@aws-sdk/client-s3": "3.842.0",
|
||||
"@aws-sdk/s3-request-presigner": "3.842.0",
|
||||
"@azure/storage-blob": "12.27.0",
|
||||
"@better-auth/stripe": "^1.2.9",
|
||||
"@browserbasehq/stagehand": "^2.0.0",
|
||||
@@ -69,6 +70,7 @@
|
||||
"@vercel/og": "^0.6.5",
|
||||
"@vercel/speed-insights": "^1.2.0",
|
||||
"ai": "^4.3.2",
|
||||
"archiver": "7.0.1",
|
||||
"better-auth": "^1.2.9",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
@@ -86,6 +88,7 @@
|
||||
"input-otp": "^1.4.2",
|
||||
"ioredis": "^5.6.0",
|
||||
"jose": "6.0.11",
|
||||
"jszip": "^3.10.1",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"lenis": "^1.2.3",
|
||||
"lucide-react": "^0.479.0",
|
||||
|
||||
139
apps/sim/tools/aws_lambda/deploy.ts
Normal file
139
apps/sim/tools/aws_lambda/deploy.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
import type { ToolConfig } from '../types'
|
||||
|
||||
interface AWSLambdaDeployInput {
|
||||
accessKeyId: string
|
||||
secretAccessKey: string
|
||||
region: string
|
||||
functionName: string
|
||||
handler?: string
|
||||
runtime: string
|
||||
code: string
|
||||
requirements?: string
|
||||
packageJson?: string
|
||||
timeout: number
|
||||
memorySize: number
|
||||
environmentVariables: Record<string, string>
|
||||
tags: Record<string, string>
|
||||
role: string
|
||||
}
|
||||
|
||||
interface AWSLambdaDeployOutput {
|
||||
functionArn: string
|
||||
functionName: string
|
||||
runtime: string
|
||||
region: string
|
||||
status: string
|
||||
lastModified: string
|
||||
codeSize: number
|
||||
description: string
|
||||
timeout: number
|
||||
memorySize: number
|
||||
environment: Record<string, string>
|
||||
tags: Record<string, string>
|
||||
}
|
||||
|
||||
export const awsLambdaDeployTool: ToolConfig<AWSLambdaDeployInput, AWSLambdaDeployOutput> = {
|
||||
id: 'aws_lambda_deploy',
|
||||
name: 'AWS Lambda Deploy',
|
||||
description: 'Deploy or update an AWS Lambda function with the specified configuration',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
accessKeyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'AWS Access Key ID for authentication',
|
||||
},
|
||||
secretAccessKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'AWS Secret Access Key for authentication',
|
||||
},
|
||||
region: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'AWS region where the Lambda function will be deployed',
|
||||
},
|
||||
functionName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Name of the Lambda function to create or update',
|
||||
},
|
||||
handler: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'Function handler (e.g., index.handler)',
|
||||
},
|
||||
runtime: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Lambda runtime (e.g., nodejs18.x, python3.11)',
|
||||
},
|
||||
code: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Function code to deploy',
|
||||
},
|
||||
requirements: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'Python requirements.txt content',
|
||||
},
|
||||
packageJson: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'Node.js package.json content',
|
||||
},
|
||||
timeout: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
description: 'Function timeout in seconds (1-900)',
|
||||
},
|
||||
memorySize: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
description: 'Function memory size in MB (128-10240)',
|
||||
},
|
||||
environmentVariables: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
description: 'Environment variables for the function',
|
||||
default: {},
|
||||
},
|
||||
tags: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
description: 'Tags for the function',
|
||||
default: {},
|
||||
},
|
||||
role: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'IAM Role ARN for Lambda execution',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/aws-lambda/deploy',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params: AWSLambdaDeployInput) => ({
|
||||
accessKeyId: params.accessKeyId,
|
||||
secretAccessKey: params.secretAccessKey,
|
||||
region: params.region,
|
||||
functionName: params.functionName,
|
||||
handler: params.handler,
|
||||
runtime: params.runtime,
|
||||
code: params.code,
|
||||
requirements: params.requirements,
|
||||
packageJson: params.packageJson,
|
||||
timeout: params.timeout,
|
||||
memorySize: params.memorySize,
|
||||
environmentVariables: params.environmentVariables || {},
|
||||
tags: params.tags || {},
|
||||
role: params.role,
|
||||
}),
|
||||
},
|
||||
}
|
||||
1
apps/sim/tools/aws_lambda/index.ts
Normal file
1
apps/sim/tools/aws_lambda/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { awsLambdaDeployTool } from './deploy'
|
||||
@@ -103,6 +103,7 @@ import { whatsappSendMessageTool } from './whatsapp'
|
||||
import { workflowExecutorTool } from './workflow'
|
||||
import { xReadTool, xSearchTool, xUserTool, xWriteTool } from './x'
|
||||
import { youtubeSearchTool } from './youtube'
|
||||
import { awsLambdaDeployTool } from './aws_lambda'
|
||||
|
||||
// Registry of all available tools
|
||||
export const tools: Record<string, ToolConfig> = {
|
||||
@@ -223,4 +224,5 @@ export const tools: Record<string, ToolConfig> = {
|
||||
google_calendar_quick_add: googleCalendarQuickAddTool,
|
||||
google_calendar_invite: googleCalendarInviteTool,
|
||||
workflow_executor: workflowExecutorTool,
|
||||
aws_lambda_deploy: awsLambdaDeployTool,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user