mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-12 00:18:09 -05:00
Compare commits
40 Commits
v0.2.8
...
feat/aws-l
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f553667242 | ||
|
|
0c753c4394 | ||
|
|
2ac203e233 | ||
|
|
d29692ede4 | ||
|
|
f60232fa5b | ||
|
|
4ceec7ff9a | ||
|
|
0ff86a1413 | ||
|
|
4886e5aae8 | ||
|
|
6274bdcb18 | ||
|
|
7064f69520 | ||
|
|
154d8a674a | ||
|
|
a6e144ad93 | ||
|
|
d0514a39a8 | ||
|
|
ee66cd262b | ||
|
|
5aab24e1ed | ||
|
|
689d88fd7e | ||
|
|
b1047503b9 | ||
|
|
ec1eec4546 | ||
|
|
2b3989edd2 | ||
|
|
cb393c1638 | ||
|
|
c82e5ac3b3 | ||
|
|
67030d9576 | ||
|
|
8c157083bc | ||
|
|
6f07c2958e | ||
|
|
be100e4f86 | ||
|
|
46be9e3558 | ||
|
|
abf1ac06ce | ||
|
|
3e45d793f1 | ||
|
|
5167deb75c | ||
|
|
02b7899861 | ||
|
|
7e4669108f | ||
|
|
ede224a15f | ||
|
|
5cf7d025db | ||
|
|
b4eda8fe6a | ||
|
|
60e2e6c735 | ||
|
|
c635b19548 | ||
|
|
0bf9ce0b9e | ||
|
|
e22f0123a3 | ||
|
|
231bfb9add | ||
|
|
cac9ad250d |
@@ -81,4 +81,4 @@ Sim Studio provides a wide range of features designed to accelerate your develop
|
||||
|
||||
##
|
||||
|
||||
Ready to get started? Check out our [Getting Started](/getting-started) guide or explore our [Blocks](/docs/blocks) and [Tools](/docs/tools) in more detail.
|
||||
Ready to get started? Check out our [Getting Started](/getting-started) guide or explore our [Blocks](/blocks) and [Tools](/tools) in more detail.
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"fumadocs-mdx": "^11.5.6",
|
||||
"fumadocs-ui": "^15.0.16",
|
||||
"lucide-react": "^0.511.0",
|
||||
"next": "^15.2.3",
|
||||
"next": "^15.3.2",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
|
||||
@@ -14,6 +14,8 @@ const logger = createLogger('OAuthTokenAPI')
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
|
||||
logger.info(`[${requestId}] OAuth token API POST request received`)
|
||||
|
||||
try {
|
||||
// Parse request body
|
||||
const body = await request.json()
|
||||
@@ -38,6 +40,7 @@ export async function POST(request: NextRequest) {
|
||||
const credential = await getCredential(requestId, credentialId, userId)
|
||||
|
||||
if (!credential) {
|
||||
logger.error(`[${requestId}] Credential not found: ${credentialId}`)
|
||||
return NextResponse.json({ error: 'Credential not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
@@ -45,7 +48,8 @@ export async function POST(request: NextRequest) {
|
||||
// Refresh the token if needed
|
||||
const { accessToken } = await refreshTokenIfNeeded(requestId, credential, credentialId)
|
||||
return NextResponse.json({ accessToken }, { status: 200 })
|
||||
} catch (_error) {
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Failed to refresh access token:`, error)
|
||||
return NextResponse.json({ error: 'Failed to refresh access token' }, { status: 401 })
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -89,6 +89,7 @@ export async function getOAuthToken(userId: string, providerId: string): Promise
|
||||
// Check if the token is expired and needs refreshing
|
||||
const now = new Date()
|
||||
const tokenExpiry = credential.accessTokenExpiresAt
|
||||
// Only refresh if we have an expiration time AND it's expired AND we have a refresh token
|
||||
const needsRefresh = tokenExpiry && tokenExpiry < now && !!credential.refreshToken
|
||||
|
||||
if (needsRefresh) {
|
||||
@@ -166,7 +167,9 @@ export async function refreshAccessTokenIfNeeded(
|
||||
// Check if we need to refresh the token
|
||||
const expiresAt = credential.accessTokenExpiresAt
|
||||
const now = new Date()
|
||||
const needsRefresh = !expiresAt || expiresAt <= now
|
||||
// Only refresh if we have an expiration time AND it's expired
|
||||
// If no expiration time is set (newly created credentials), assume token is valid
|
||||
const needsRefresh = expiresAt && expiresAt <= now
|
||||
|
||||
const accessToken = credential.accessToken
|
||||
|
||||
@@ -233,7 +236,9 @@ export async function refreshTokenIfNeeded(
|
||||
// Check if we need to refresh the token
|
||||
const expiresAt = credential.accessTokenExpiresAt
|
||||
const now = new Date()
|
||||
const needsRefresh = !expiresAt || expiresAt <= now
|
||||
// Only refresh if we have an expiration time AND it's expired
|
||||
// If no expiration time is set (newly created credentials), assume token is valid
|
||||
const needsRefresh = expiresAt && expiresAt <= now
|
||||
|
||||
// If token is still valid, return it directly
|
||||
if (!needsRefresh || !credential.refreshToken) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { and, eq, isNull } from 'drizzle-orm'
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { db } from '@/db'
|
||||
@@ -40,7 +40,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
|
||||
const memories = await db
|
||||
.select()
|
||||
.from(memory)
|
||||
.where(and(eq(memory.key, id), eq(memory.workflowId, workflowId), isNull(memory.deletedAt)))
|
||||
.where(and(eq(memory.key, id), eq(memory.workflowId, workflowId)))
|
||||
.orderBy(memory.createdAt)
|
||||
.limit(1)
|
||||
|
||||
@@ -112,7 +112,7 @@ export async function DELETE(
|
||||
const existingMemory = await db
|
||||
.select({ id: memory.id })
|
||||
.from(memory)
|
||||
.where(and(eq(memory.key, id), eq(memory.workflowId, workflowId), isNull(memory.deletedAt)))
|
||||
.where(and(eq(memory.key, id), eq(memory.workflowId, workflowId)))
|
||||
.limit(1)
|
||||
|
||||
if (existingMemory.length === 0) {
|
||||
@@ -128,14 +128,8 @@ export async function DELETE(
|
||||
)
|
||||
}
|
||||
|
||||
// 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)))
|
||||
// Hard delete the memory
|
||||
await db.delete(memory).where(and(eq(memory.key, id), eq(memory.workflowId, workflowId)))
|
||||
|
||||
logger.info(`[${requestId}] Memory deleted successfully: ${id} for workflow: ${workflowId}`)
|
||||
return NextResponse.json(
|
||||
@@ -202,7 +196,7 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
|
||||
const existingMemories = await db
|
||||
.select()
|
||||
.from(memory)
|
||||
.where(and(eq(memory.key, id), eq(memory.workflowId, workflowId), isNull(memory.deletedAt)))
|
||||
.where(and(eq(memory.key, id), eq(memory.workflowId, workflowId)))
|
||||
.limit(1)
|
||||
|
||||
if (existingMemories.length === 0) {
|
||||
@@ -250,13 +244,7 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
|
||||
}
|
||||
|
||||
// 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)))
|
||||
await db.delete(memory).where(and(eq(memory.key, id), eq(memory.workflowId, workflowId)))
|
||||
|
||||
// Fetch the updated memory
|
||||
const updatedMemories = await db
|
||||
|
||||
480
apps/sim/app/api/tools/aws-lambda/deploy-endpoint/route.ts
Normal file
480
apps/sim/app/api/tools/aws-lambda/deploy-endpoint/route.ts
Normal file
@@ -0,0 +1,480 @@
|
||||
import {
|
||||
ApiGatewayV2Client,
|
||||
CreateApiCommand,
|
||||
CreateIntegrationCommand,
|
||||
CreateRouteCommand,
|
||||
CreateStageCommand,
|
||||
GetApisCommand,
|
||||
GetIntegrationsCommand,
|
||||
GetRoutesCommand,
|
||||
GetStagesCommand,
|
||||
} from '@aws-sdk/client-apigatewayv2'
|
||||
import { AddPermissionCommand, GetFunctionCommand, LambdaClient } from '@aws-sdk/client-lambda'
|
||||
import type { NextRequest } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
|
||||
|
||||
const logger = createLogger('AWSLambdaDeployEndpointAPI')
|
||||
|
||||
// Validation schema for the request body
|
||||
const DeployEndpointRequestSchema = 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'),
|
||||
endpointName: z.string().min(1, 'Endpoint name is required'),
|
||||
role: z.string().min(1, 'Role ARN is required'),
|
||||
})
|
||||
|
||||
type DeployEndpointRequest = z.infer<typeof DeployEndpointRequestSchema>
|
||||
|
||||
interface DeployEndpointResponse {
|
||||
functionArn: string
|
||||
functionName: string
|
||||
endpointName: string
|
||||
endpointUrl: string
|
||||
region: string
|
||||
status: string
|
||||
lastModified: string
|
||||
apiGatewayId: string
|
||||
stageName: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a Lambda function exists
|
||||
*/
|
||||
async function checkFunctionExists(
|
||||
lambdaClient: LambdaClient,
|
||||
functionName: string
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
await lambdaClient.send(new GetFunctionCommand({ FunctionName: functionName }))
|
||||
return true
|
||||
} catch (error: any) {
|
||||
if (error.name === 'ResourceNotFoundException') {
|
||||
return false
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Lambda function details
|
||||
*/
|
||||
async function getFunctionDetails(lambdaClient: LambdaClient, functionName: string): Promise<any> {
|
||||
return await lambdaClient.send(new GetFunctionCommand({ FunctionName: functionName }))
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if API Gateway HTTP API already exists
|
||||
*/
|
||||
async function checkApiExists(
|
||||
apiGatewayClient: ApiGatewayV2Client,
|
||||
apiName: string
|
||||
): Promise<string | null> {
|
||||
try {
|
||||
const apis = await apiGatewayClient.send(new GetApisCommand({}))
|
||||
const existingApi = apis.Items?.find((api: any) => api.Name === apiName)
|
||||
return existingApi?.ApiId || null
|
||||
} catch (error) {
|
||||
logger.error('Error checking for existing API', { error })
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a route already exists for the API Gateway
|
||||
*/
|
||||
async function checkRouteExists(
|
||||
apiGatewayClient: ApiGatewayV2Client,
|
||||
apiId: string,
|
||||
routeKey: string
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const routes = await apiGatewayClient.send(new GetRoutesCommand({ ApiId: apiId }))
|
||||
return routes.Items?.some((route: any) => route.RouteKey === routeKey) || false
|
||||
} catch (error) {
|
||||
logger.error('Error checking for existing route', { error })
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an integration already exists for the API Gateway
|
||||
*/
|
||||
async function checkIntegrationExists(
|
||||
apiGatewayClient: ApiGatewayV2Client,
|
||||
apiId: string,
|
||||
functionArn: string
|
||||
): Promise<string | null> {
|
||||
try {
|
||||
const integrations = await apiGatewayClient.send(new GetIntegrationsCommand({ ApiId: apiId }))
|
||||
const existingIntegration = integrations.Items?.find(
|
||||
(integration) => integration.IntegrationUri === functionArn
|
||||
)
|
||||
return existingIntegration?.IntegrationId || null
|
||||
} catch (error) {
|
||||
logger.error('Error checking for existing integration', { error })
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new API Gateway HTTP API
|
||||
*/
|
||||
async function createApiGateway(
|
||||
apiGatewayClient: ApiGatewayV2Client,
|
||||
apiName: string
|
||||
): Promise<string> {
|
||||
const createApiResponse = await apiGatewayClient.send(
|
||||
new CreateApiCommand({
|
||||
Name: apiName,
|
||||
ProtocolType: 'HTTP',
|
||||
Description: `HTTP API for Lambda function ${apiName}`,
|
||||
})
|
||||
)
|
||||
|
||||
if (!createApiResponse.ApiId) {
|
||||
throw new Error('Failed to create API Gateway - no ID returned')
|
||||
}
|
||||
|
||||
return createApiResponse.ApiId
|
||||
}
|
||||
|
||||
/**
|
||||
* Create API Gateway integration with Lambda
|
||||
*/
|
||||
async function createApiIntegration(
|
||||
apiGatewayClient: ApiGatewayV2Client,
|
||||
apiId: string,
|
||||
functionArn: string
|
||||
): Promise<string> {
|
||||
const integration = await apiGatewayClient.send(
|
||||
new CreateIntegrationCommand({
|
||||
ApiId: apiId,
|
||||
IntegrationType: 'AWS_PROXY',
|
||||
IntegrationUri: functionArn,
|
||||
IntegrationMethod: 'POST',
|
||||
PayloadFormatVersion: '2.0',
|
||||
})
|
||||
)
|
||||
|
||||
if (!integration.IntegrationId) {
|
||||
throw new Error('Failed to create integration - no ID returned')
|
||||
}
|
||||
|
||||
return integration.IntegrationId
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a route for the API Gateway
|
||||
*/
|
||||
async function createApiRoute(
|
||||
apiGatewayClient: ApiGatewayV2Client,
|
||||
apiId: string,
|
||||
integrationId: string
|
||||
): Promise<void> {
|
||||
await apiGatewayClient.send(
|
||||
new CreateRouteCommand({
|
||||
ApiId: apiId,
|
||||
RouteKey: 'ANY /',
|
||||
Target: `integrations/${integrationId}`,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Lambda permission for API Gateway
|
||||
*/
|
||||
async function addLambdaPermission(
|
||||
lambdaClient: LambdaClient,
|
||||
functionName: string,
|
||||
apiId: string,
|
||||
region: string,
|
||||
accountId: string
|
||||
): Promise<void> {
|
||||
try {
|
||||
await lambdaClient.send(
|
||||
new AddPermissionCommand({
|
||||
FunctionName: functionName,
|
||||
StatementId: `api-gateway-${apiId}`,
|
||||
Action: 'lambda:InvokeFunction',
|
||||
Principal: 'apigateway.amazonaws.com',
|
||||
SourceArn: `arn:aws:execute-api:${region}:${accountId}:${apiId}/*/*`,
|
||||
})
|
||||
)
|
||||
} catch (error: any) {
|
||||
// If permission already exists, that's fine
|
||||
if (error.name !== 'ResourceConflictException') {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a stage exists for the API Gateway
|
||||
*/
|
||||
async function checkStageExists(
|
||||
apiGatewayClient: ApiGatewayV2Client,
|
||||
apiId: string,
|
||||
stageName: string
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const stages = await apiGatewayClient.send(
|
||||
new GetStagesCommand({
|
||||
ApiId: apiId,
|
||||
})
|
||||
)
|
||||
return stages.Items?.some((stage: any) => stage.StageName === stageName) || false
|
||||
} catch (error) {
|
||||
logger.error('Error checking for existing stage', { error })
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a stage for the API Gateway
|
||||
*/
|
||||
async function createApiStage(
|
||||
apiGatewayClient: ApiGatewayV2Client,
|
||||
apiId: string
|
||||
): Promise<string> {
|
||||
const stageName = 'prod'
|
||||
|
||||
// Check if stage already exists
|
||||
const stageExists = await checkStageExists(apiGatewayClient, apiId, stageName)
|
||||
|
||||
if (stageExists) {
|
||||
logger.info(`Stage ${stageName} already exists for API ${apiId}`)
|
||||
return stageName
|
||||
}
|
||||
|
||||
logger.info(`Creating new stage ${stageName} for API ${apiId}`)
|
||||
const stage = await apiGatewayClient.send(
|
||||
new CreateStageCommand({
|
||||
ApiId: apiId,
|
||||
StageName: stageName,
|
||||
AutoDeploy: true,
|
||||
})
|
||||
)
|
||||
|
||||
return stage.StageName || stageName
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure API is deployed by waiting for deployment to complete
|
||||
*/
|
||||
async function ensureApiDeployed(
|
||||
apiGatewayClient: ApiGatewayV2Client,
|
||||
apiId: string,
|
||||
stageName: string
|
||||
): Promise<void> {
|
||||
// In API Gateway v2, AutoDeploy: true should handle deployment automatically
|
||||
// But we can add a small delay to ensure the deployment completes
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000))
|
||||
|
||||
logger.info(`API Gateway deployment completed for API ${apiId}, stage ${stageName}`)
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
|
||||
try {
|
||||
logger.info(`[${requestId}] Processing AWS Lambda deploy endpoint request`)
|
||||
|
||||
// Parse and validate request body
|
||||
let body: any
|
||||
try {
|
||||
body = await request.json()
|
||||
} catch (parseError) {
|
||||
logger.error(`[${requestId}] Failed to parse request body`, {
|
||||
error: parseError instanceof Error ? parseError.message : String(parseError),
|
||||
})
|
||||
return createErrorResponse('Invalid JSON in request body', 400, 'INVALID_JSON')
|
||||
}
|
||||
|
||||
// Log the raw request body for debugging
|
||||
logger.info(`[${requestId}] Raw request body received`, {
|
||||
body: JSON.stringify(body, null, 2),
|
||||
})
|
||||
|
||||
const validationResult = DeployEndpointRequestSchema.safeParse(body)
|
||||
if (!validationResult.success) {
|
||||
logger.warn(`[${requestId}] Invalid request body`, { errors: validationResult.error.errors })
|
||||
return createErrorResponse('Invalid request parameters', 400, 'VALIDATION_ERROR')
|
||||
}
|
||||
|
||||
const params = validationResult.data
|
||||
|
||||
// Log the deployment payload (excluding sensitive credentials)
|
||||
logger.info(`[${requestId}] AWS Lambda deploy endpoint payload received`, {
|
||||
functionName: params.functionName,
|
||||
endpointName: params.endpointName,
|
||||
region: params.region,
|
||||
accessKeyId: params.accessKeyId ? `${params.accessKeyId.substring(0, 4)}...` : undefined,
|
||||
hasSecretAccessKey: !!params.secretAccessKey,
|
||||
hasRole: !!params.role,
|
||||
role: params.role ? `${params.role.substring(0, 20)}...` : undefined,
|
||||
})
|
||||
|
||||
logger.info(`[${requestId}] Deploying Lambda function as endpoint: ${params.functionName}`)
|
||||
|
||||
// Create Lambda client
|
||||
const lambdaClient = new LambdaClient({
|
||||
region: params.region,
|
||||
credentials: {
|
||||
accessKeyId: params.accessKeyId,
|
||||
secretAccessKey: params.secretAccessKey,
|
||||
},
|
||||
})
|
||||
|
||||
// Create API Gateway v2 client
|
||||
const apiGatewayClient = new ApiGatewayV2Client({
|
||||
region: params.region,
|
||||
credentials: {
|
||||
accessKeyId: params.accessKeyId,
|
||||
secretAccessKey: params.secretAccessKey,
|
||||
},
|
||||
})
|
||||
|
||||
// Check if Lambda function exists
|
||||
const functionExists = await checkFunctionExists(lambdaClient, params.functionName)
|
||||
if (!functionExists) {
|
||||
logger.error(`[${requestId}] Lambda function ${params.functionName} does not exist`)
|
||||
return createErrorResponse(
|
||||
`Lambda function ${params.functionName} does not exist. Please deploy the function first.`,
|
||||
404,
|
||||
'FUNCTION_NOT_FOUND'
|
||||
)
|
||||
}
|
||||
|
||||
// Get function details
|
||||
const functionDetails = await getFunctionDetails(lambdaClient, params.functionName)
|
||||
const functionArn = functionDetails.Configuration?.FunctionArn
|
||||
|
||||
if (!functionArn) {
|
||||
logger.error(`[${requestId}] Failed to get function ARN for ${params.functionName}`)
|
||||
return createErrorResponse('Failed to get function ARN', 500, 'FUNCTION_ARN_ERROR')
|
||||
}
|
||||
|
||||
// Extract account ID from function ARN
|
||||
const accountId = functionArn.split(':')[4]
|
||||
if (!accountId) {
|
||||
logger.error(`[${requestId}] Failed to extract account ID from function ARN: ${functionArn}`)
|
||||
return createErrorResponse(
|
||||
'Failed to extract account ID from function ARN',
|
||||
500,
|
||||
'ACCOUNT_ID_ERROR'
|
||||
)
|
||||
}
|
||||
|
||||
// Check if API Gateway already exists
|
||||
let apiId = await checkApiExists(apiGatewayClient, params.endpointName)
|
||||
|
||||
if (!apiId) {
|
||||
logger.info(`[${requestId}] Creating new API Gateway HTTP API: ${params.endpointName}`)
|
||||
apiId = await createApiGateway(apiGatewayClient, params.endpointName)
|
||||
} else {
|
||||
logger.info(
|
||||
`[${requestId}] Using existing API Gateway HTTP API: ${params.endpointName} (${apiId})`
|
||||
)
|
||||
}
|
||||
|
||||
// Check if integration already exists before creating a new one
|
||||
let integrationId = await checkIntegrationExists(apiGatewayClient, apiId, functionArn)
|
||||
|
||||
if (integrationId) {
|
||||
logger.info(
|
||||
`[${requestId}] Integration for function ${params.functionName} already exists for API ${apiId}, using existing integration`
|
||||
)
|
||||
} else {
|
||||
logger.info(`[${requestId}] Creating API Gateway integration`)
|
||||
integrationId = await createApiIntegration(apiGatewayClient, apiId, functionArn)
|
||||
}
|
||||
|
||||
// Check if route already exists before creating a new one
|
||||
const routeKey = 'ANY /'
|
||||
const routeExists = await checkRouteExists(apiGatewayClient, apiId, routeKey)
|
||||
|
||||
if (routeExists) {
|
||||
logger.info(
|
||||
`[${requestId}] Route ${routeKey} already exists for API ${apiId}, skipping route creation`
|
||||
)
|
||||
} else {
|
||||
logger.info(`[${requestId}] Creating API Gateway route`)
|
||||
await createApiRoute(apiGatewayClient, apiId, integrationId)
|
||||
}
|
||||
|
||||
// Add Lambda permission for API Gateway
|
||||
logger.info(`[${requestId}] Adding Lambda permission for API Gateway`)
|
||||
await addLambdaPermission(lambdaClient, params.functionName, apiId, params.region, accountId)
|
||||
|
||||
// Create stage for the API Gateway
|
||||
logger.info(`[${requestId}] Creating API Gateway stage`)
|
||||
const stageName = await createApiStage(apiGatewayClient, apiId)
|
||||
|
||||
if (!stageName) {
|
||||
logger.error(`[${requestId}] Failed to create or get stage for API ${apiId}`)
|
||||
return createErrorResponse('Failed to create API Gateway stage', 500, 'STAGE_CREATION_ERROR')
|
||||
}
|
||||
|
||||
// Ensure API is deployed
|
||||
logger.info(`[${requestId}] Ensuring API Gateway deployment is complete`)
|
||||
await ensureApiDeployed(apiGatewayClient, apiId, stageName)
|
||||
|
||||
// Construct the endpoint URL
|
||||
const endpointUrl = `https://${apiId}.execute-api.${params.region}.amazonaws.com/${stageName}/`
|
||||
|
||||
const response: DeployEndpointResponse = {
|
||||
functionArn,
|
||||
functionName: params.functionName,
|
||||
endpointName: params.endpointName,
|
||||
endpointUrl,
|
||||
region: params.region,
|
||||
status: 'ACTIVE',
|
||||
lastModified: new Date().toISOString(),
|
||||
apiGatewayId: apiId,
|
||||
stageName,
|
||||
}
|
||||
|
||||
logger.info(`[${requestId}] Lambda function endpoint deployment completed successfully`, {
|
||||
functionName: params.functionName,
|
||||
endpointName: params.endpointName,
|
||||
endpointUrl,
|
||||
apiGatewayId: apiId,
|
||||
})
|
||||
|
||||
return createSuccessResponse({
|
||||
success: true,
|
||||
output: response,
|
||||
})
|
||||
} catch (error: any) {
|
||||
logger.error(`[${requestId}] Error deploying Lambda function endpoint`, {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
// Handle specific AWS errors
|
||||
let errorMessage = 'Failed to deploy Lambda function endpoint'
|
||||
let statusCode = 500
|
||||
|
||||
if (error.name === 'AccessDeniedException') {
|
||||
errorMessage = 'Access denied. Please check your AWS credentials and permissions.'
|
||||
statusCode = 403
|
||||
} else if (error.name === 'InvalidParameterValueException') {
|
||||
errorMessage = `Invalid parameter: ${error.message}`
|
||||
statusCode = 400
|
||||
} else if (error.name === 'ResourceConflictException') {
|
||||
errorMessage = 'Resource conflict. The API may be in use or being updated.'
|
||||
statusCode = 409
|
||||
} else if (error.name === 'ServiceException') {
|
||||
errorMessage = 'AWS service error. Please try again later.'
|
||||
statusCode = 503
|
||||
} else if (error instanceof Error) {
|
||||
errorMessage = error.message
|
||||
}
|
||||
|
||||
return createErrorResponse(errorMessage, statusCode, 'DEPLOYMENT_ERROR')
|
||||
}
|
||||
}
|
||||
442
apps/sim/app/api/tools/aws-lambda/deploy/route.ts
Normal file
442
apps/sim/app/api/tools/aws-lambda/deploy/route.ts
Normal file
@@ -0,0 +1,442 @@
|
||||
import { promises as fs } from 'fs'
|
||||
import { tmpdir } from 'os'
|
||||
import { join } from 'path'
|
||||
import { GetFunctionCommand, LambdaClient } from '@aws-sdk/client-lambda'
|
||||
import type { NextRequest } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
|
||||
|
||||
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
|
||||
.record(z.string())
|
||||
.refine((val) => Object.keys(val).length > 0, 'At least one code file is required'),
|
||||
|
||||
timeout: z.coerce.number().min(1).max(900).optional().default(3),
|
||||
memorySize: z.coerce.number().min(128).max(10240).optional().default(128),
|
||||
environmentVariables: z.record(z.string()).default({}),
|
||||
tags: z.record(z.string()).default({}),
|
||||
role: z.string().min(1, 'Role ARN is required'),
|
||||
})
|
||||
|
||||
type DeployRequest = z.infer<typeof DeployRequestSchema>
|
||||
|
||||
interface LambdaFunctionDetails {
|
||||
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>
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate file extension for the given runtime
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize function name for SAM/CloudFormation resource naming
|
||||
* SAM resource names must be alphanumeric only (letters and numbers)
|
||||
*/
|
||||
function sanitizeResourceName(functionName: string): string {
|
||||
return (
|
||||
functionName
|
||||
.replace(/[^a-zA-Z0-9]/g, '') // Remove all non-alphanumeric characters
|
||||
.replace(/^(\d)/, 'Func$1') // Ensure it starts with a letter if it starts with a number
|
||||
.substring(0, 64) || // Ensure reasonable length limit
|
||||
'LambdaFunction'
|
||||
) // Fallback if name becomes empty
|
||||
}
|
||||
|
||||
/**
|
||||
* Create SAM template for the Lambda function
|
||||
*/
|
||||
function createSamTemplate(params: DeployRequest): string {
|
||||
// Sanitize the function name for CloudFormation resource naming
|
||||
const resourceName = sanitizeResourceName(params.functionName)
|
||||
|
||||
const template = {
|
||||
AWSTemplateFormatVersion: '2010-09-09',
|
||||
Transform: 'AWS::Serverless-2016-10-31',
|
||||
Resources: {
|
||||
[resourceName]: {
|
||||
Type: 'AWS::Serverless::Function',
|
||||
Properties: {
|
||||
FunctionName: params.functionName, // Use original function name for actual Lambda function
|
||||
CodeUri: './src',
|
||||
Handler: params.handler,
|
||||
Runtime: params.runtime,
|
||||
Role: params.role,
|
||||
Timeout: params.timeout,
|
||||
MemorySize: params.memorySize,
|
||||
Environment: {
|
||||
Variables: params.environmentVariables,
|
||||
},
|
||||
Tags: params.tags,
|
||||
},
|
||||
},
|
||||
},
|
||||
Outputs: {
|
||||
FunctionArn: {
|
||||
Value: { 'Fn::GetAtt': [resourceName, 'Arn'] },
|
||||
Export: { Name: `${params.functionName}-Arn` },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return JSON.stringify(template, null, 2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a shell command and return the result
|
||||
*/
|
||||
async function execCommand(
|
||||
command: string,
|
||||
cwd: string,
|
||||
env?: Record<string, string>
|
||||
): Promise<{ stdout: string; stderr: string }> {
|
||||
const { exec } = await import('child_process')
|
||||
const { promisify } = await import('util')
|
||||
const execAsync = promisify(exec)
|
||||
|
||||
return await execAsync(command, {
|
||||
cwd,
|
||||
env: env ? { ...process.env, ...env } : process.env,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Deploy Lambda function using SAM CLI
|
||||
*/
|
||||
async function deployWithSam(
|
||||
params: DeployRequest,
|
||||
requestId: string
|
||||
): Promise<LambdaFunctionDetails> {
|
||||
const tempDir = join(tmpdir(), `lambda-deploy-${requestId}`)
|
||||
const srcDir = join(tempDir, 'src')
|
||||
|
||||
try {
|
||||
// Create temporary directory structure
|
||||
await fs.mkdir(tempDir, { recursive: true })
|
||||
await fs.mkdir(srcDir, { recursive: true })
|
||||
|
||||
logger.info(`[${requestId}] Created temporary directory: ${tempDir}`)
|
||||
|
||||
// Write SAM template
|
||||
const samTemplate = createSamTemplate(params)
|
||||
await fs.writeFile(join(tempDir, 'template.yaml'), samTemplate)
|
||||
|
||||
logger.info(`[${requestId}] Created SAM template`)
|
||||
|
||||
// Write source code files
|
||||
for (const [filePath, codeContent] of Object.entries(params.code)) {
|
||||
const fullPath = join(srcDir, filePath)
|
||||
const fileDir = join(fullPath, '..')
|
||||
|
||||
// Ensure directory exists
|
||||
await fs.mkdir(fileDir, { recursive: true })
|
||||
await fs.writeFile(fullPath, codeContent)
|
||||
|
||||
logger.info(`[${requestId}] Created source file: ${filePath}`)
|
||||
}
|
||||
|
||||
// Set AWS credentials in environment
|
||||
const env = {
|
||||
AWS_ACCESS_KEY_ID: params.accessKeyId,
|
||||
AWS_SECRET_ACCESS_KEY: params.secretAccessKey,
|
||||
AWS_DEFAULT_REGION: params.region,
|
||||
}
|
||||
|
||||
// Build the SAM application
|
||||
logger.info(`[${requestId}] Building SAM application...`)
|
||||
const buildCommand = 'sam build --no-cached'
|
||||
const buildResult = await execCommand(buildCommand, tempDir, env)
|
||||
|
||||
logger.info(`[${requestId}] SAM build output:`, {
|
||||
stdout: buildResult.stdout,
|
||||
stderr: buildResult.stderr,
|
||||
})
|
||||
|
||||
if (buildResult.stderr && !buildResult.stderr.includes('Successfully built')) {
|
||||
logger.warn(`[${requestId}] SAM build warnings:`, { stderr: buildResult.stderr })
|
||||
}
|
||||
|
||||
logger.info(`[${requestId}] SAM build completed`)
|
||||
|
||||
// Deploy the SAM application
|
||||
logger.info(`[${requestId}] Deploying SAM application...`)
|
||||
const stackName = `${sanitizeResourceName(params.functionName)}Stack`
|
||||
const deployCommand = [
|
||||
'sam deploy',
|
||||
'--no-confirm-changeset',
|
||||
'--no-fail-on-empty-changeset',
|
||||
`--stack-name ${stackName}`,
|
||||
`--region ${params.region}`,
|
||||
'--resolve-s3',
|
||||
'--capabilities CAPABILITY_IAM',
|
||||
'--no-progressbar',
|
||||
].join(' ')
|
||||
|
||||
const deployResult = await execCommand(deployCommand, tempDir, env)
|
||||
|
||||
logger.info(`[${requestId}] SAM deploy output:`, {
|
||||
stdout: deployResult.stdout,
|
||||
stderr: deployResult.stderr,
|
||||
})
|
||||
|
||||
if (
|
||||
deployResult.stderr &&
|
||||
!deployResult.stderr.includes('Successfully created/updated stack')
|
||||
) {
|
||||
logger.warn(`[${requestId}] SAM deploy warnings:`, { stderr: deployResult.stderr })
|
||||
}
|
||||
|
||||
logger.info(`[${requestId}] SAM deploy completed`)
|
||||
|
||||
// Get function details using AWS SDK
|
||||
const lambdaClient = new LambdaClient({
|
||||
region: params.region,
|
||||
credentials: {
|
||||
accessKeyId: params.accessKeyId,
|
||||
secretAccessKey: params.secretAccessKey,
|
||||
},
|
||||
})
|
||||
|
||||
const functionDetails = await getFunctionDetails(
|
||||
lambdaClient,
|
||||
params.functionName,
|
||||
params.region
|
||||
)
|
||||
|
||||
return functionDetails
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error during SAM deployment`, {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
throw error
|
||||
} finally {
|
||||
// Clean up temporary directory
|
||||
try {
|
||||
await fs.rm(tempDir, { recursive: true, force: true })
|
||||
logger.info(`[${requestId}] Cleaned up temporary directory: ${tempDir}`)
|
||||
} catch (cleanupError) {
|
||||
logger.warn(`[${requestId}] Failed to clean up temporary directory`, {
|
||||
error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get detailed information about a Lambda function
|
||||
*/
|
||||
async function getFunctionDetails(
|
||||
lambdaClient: LambdaClient,
|
||||
functionName: string,
|
||||
region: string
|
||||
): Promise<LambdaFunctionDetails> {
|
||||
const functionDetails = await lambdaClient.send(
|
||||
new GetFunctionCommand({ FunctionName: functionName })
|
||||
)
|
||||
|
||||
return {
|
||||
functionArn: functionDetails.Configuration?.FunctionArn || '',
|
||||
functionName: functionDetails.Configuration?.FunctionName || '',
|
||||
runtime: functionDetails.Configuration?.Runtime || '',
|
||||
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 || {},
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
let body: any
|
||||
try {
|
||||
body = await request.json()
|
||||
} catch (parseError) {
|
||||
logger.error(`[${requestId}] Failed to parse request body`, {
|
||||
error: parseError instanceof Error ? parseError.message : String(parseError),
|
||||
})
|
||||
return createErrorResponse('Invalid JSON in request body', 400, 'INVALID_JSON')
|
||||
}
|
||||
|
||||
logger.info(`[${requestId}] Request body received:`, {
|
||||
body,
|
||||
codeType: typeof body.code,
|
||||
codeValue: body.code,
|
||||
})
|
||||
|
||||
// Parse the code field if it's a JSON string
|
||||
if (typeof body.code === 'string') {
|
||||
try {
|
||||
body.code = JSON.parse(body.code)
|
||||
logger.info(`[${requestId}] Parsed code field:`, { parsedCode: body.code })
|
||||
} catch (parseError) {
|
||||
logger.error(`[${requestId}] Failed to parse code field as JSON`, {
|
||||
error: parseError instanceof Error ? parseError.message : String(parseError),
|
||||
codeString: body.code,
|
||||
})
|
||||
return createErrorResponse('Invalid JSON in code field', 400, 'INVALID_CODE_JSON')
|
||||
}
|
||||
}
|
||||
|
||||
// Runtime field should be a string, no JSON parsing needed
|
||||
if (typeof body.runtime !== 'string') {
|
||||
logger.error(`[${requestId}] Runtime field must be a string`, {
|
||||
runtimeType: typeof body.runtime,
|
||||
runtimeValue: body.runtime,
|
||||
})
|
||||
return createErrorResponse('Runtime field must be a string', 400, 'INVALID_RUNTIME_TYPE')
|
||||
}
|
||||
|
||||
// Parse the timeout field if it's a JSON string
|
||||
if (typeof body.timeout === 'string') {
|
||||
try {
|
||||
body.timeout = JSON.parse(body.timeout)
|
||||
logger.info(`[${requestId}] Parsed timeout field:`, { parsedTimeout: body.timeout })
|
||||
} catch (parseError) {
|
||||
logger.error(`[${requestId}] Failed to parse timeout field as JSON`, {
|
||||
error: parseError instanceof Error ? parseError.message : String(parseError),
|
||||
timeoutString: body.timeout,
|
||||
})
|
||||
return createErrorResponse('Invalid JSON in timeout field', 400, 'INVALID_TIMEOUT_JSON')
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the memorySize field if it's a JSON string
|
||||
if (typeof body.memorySize === 'string') {
|
||||
try {
|
||||
body.memorySize = JSON.parse(body.memorySize)
|
||||
logger.info(`[${requestId}] Parsed memorySize field:`, {
|
||||
parsedMemorySize: body.memorySize,
|
||||
})
|
||||
} catch (parseError) {
|
||||
logger.error(`[${requestId}] Failed to parse memorySize field as JSON`, {
|
||||
error: parseError instanceof Error ? parseError.message : String(parseError),
|
||||
memorySizeString: body.memorySize,
|
||||
})
|
||||
return createErrorResponse(
|
||||
'Invalid JSON in memorySize field',
|
||||
400,
|
||||
'INVALID_MEMORYSIZE_JSON'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const validationResult = DeployRequestSchema.safeParse(body)
|
||||
if (!validationResult.success) {
|
||||
logger.warn(`[${requestId}] Invalid request body`, {
|
||||
errors: validationResult.error.errors,
|
||||
codeField: body.code,
|
||||
codeType: typeof body.code,
|
||||
hasCode: 'code' in body,
|
||||
bodyKeys: Object.keys(body),
|
||||
})
|
||||
return createErrorResponse('Invalid request parameters', 400, 'VALIDATION_ERROR')
|
||||
}
|
||||
|
||||
const params = validationResult.data
|
||||
|
||||
// Log the deployment payload (excluding sensitive credentials)
|
||||
logger.info(`[${requestId}] AWS Lambda deployment payload received`, {
|
||||
functionName: params.functionName,
|
||||
region: params.region,
|
||||
runtime: params.runtime,
|
||||
handler: params.handler,
|
||||
timeout: params.timeout,
|
||||
memorySize: params.memorySize,
|
||||
accessKeyId: params.accessKeyId ? `${params.accessKeyId.substring(0, 4)}...` : undefined,
|
||||
hasSecretAccessKey: !!params.secretAccessKey,
|
||||
hasRole: !!params.role,
|
||||
role: params.role ? `${params.role.substring(0, 20)}...` : undefined,
|
||||
codeFiles: Object.keys(params.code),
|
||||
codeFilesCount: Object.keys(params.code).length,
|
||||
environmentVariables: params.environmentVariables,
|
||||
environmentVariablesCount: Object.keys(params.environmentVariables || {}).length,
|
||||
tags: params.tags,
|
||||
tagsCount: Object.keys(params.tags || {}).length,
|
||||
})
|
||||
|
||||
logger.info(`[${requestId}] Deploying Lambda function with SAM: ${params.functionName}`)
|
||||
|
||||
// Deploy using SAM CLI
|
||||
const functionDetails = await deployWithSam(params, requestId)
|
||||
|
||||
logger.info(`[${requestId}] Lambda function deployment completed successfully`, {
|
||||
functionName: params.functionName,
|
||||
functionArn: functionDetails.functionArn,
|
||||
})
|
||||
|
||||
return createSuccessResponse({
|
||||
success: true,
|
||||
output: functionDetails,
|
||||
})
|
||||
} catch (error: any) {
|
||||
logger.error(`[${requestId}] Error deploying Lambda function`, {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
// Handle specific AWS errors
|
||||
let errorMessage = 'Failed to deploy Lambda function'
|
||||
let statusCode = 500
|
||||
|
||||
if (error.message?.includes('sam: command not found')) {
|
||||
errorMessage = 'SAM CLI is not installed or not available in PATH'
|
||||
statusCode = 500
|
||||
} else if (error.name === 'AccessDeniedException') {
|
||||
errorMessage = 'Access denied. Please check your AWS credentials and permissions.'
|
||||
statusCode = 403
|
||||
} else if (error.name === 'InvalidParameterValueException') {
|
||||
errorMessage = `Invalid parameter: ${error.message}`
|
||||
statusCode = 400
|
||||
} else if (error.name === 'ResourceConflictException') {
|
||||
errorMessage = 'Resource conflict. The function may be in use or being updated.'
|
||||
statusCode = 409
|
||||
} else if (error.name === 'ServiceException') {
|
||||
errorMessage = 'AWS Lambda service error. Please try again later.'
|
||||
statusCode = 503
|
||||
} else if (error instanceof Error) {
|
||||
errorMessage = error.message
|
||||
}
|
||||
|
||||
return createErrorResponse(errorMessage, statusCode, 'DEPLOYMENT_ERROR')
|
||||
}
|
||||
}
|
||||
322
apps/sim/app/api/tools/aws-lambda/fetch/route.ts
Normal file
322
apps/sim/app/api/tools/aws-lambda/fetch/route.ts
Normal file
@@ -0,0 +1,322 @@
|
||||
import {
|
||||
GetFunctionCommand,
|
||||
GetFunctionConfigurationCommand,
|
||||
LambdaClient,
|
||||
} from '@aws-sdk/client-lambda'
|
||||
import JSZip from 'jszip'
|
||||
import type { NextRequest } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
const logger = createLogger('AWSLambdaFetchAPI')
|
||||
|
||||
// Validation schema for the request body
|
||||
const FetchRequestSchema = 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'),
|
||||
role: z.string().min(1, 'IAM Role ARN is required'),
|
||||
})
|
||||
|
||||
type FetchRequest = z.infer<typeof FetchRequestSchema>
|
||||
|
||||
interface LambdaFunctionDetails {
|
||||
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>
|
||||
codeFiles: Record<string, string>
|
||||
handler: string
|
||||
role: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract code from Lambda function ZIP file
|
||||
*/
|
||||
async function extractCodeFromZip(
|
||||
zipBuffer: Buffer,
|
||||
runtime: string
|
||||
): Promise<{ mainCode: string; allFiles: Record<string, string> }> {
|
||||
try {
|
||||
const zip = await JSZip.loadAsync(zipBuffer)
|
||||
const allFiles = Object.keys(zip.files)
|
||||
logger.info('Files in ZIP:', allFiles)
|
||||
|
||||
// Extract all text files
|
||||
const allFilesContent: Record<string, string> = {}
|
||||
let mainCode = ''
|
||||
|
||||
// Determine the main file based on runtime
|
||||
let mainFile = 'index.js' // default
|
||||
if (runtime.startsWith('python')) {
|
||||
mainFile = 'index.py'
|
||||
} else if (runtime.startsWith('java')) {
|
||||
mainFile = 'index.java'
|
||||
} else if (runtime.startsWith('dotnet')) {
|
||||
mainFile = 'index.cs'
|
||||
} else if (runtime.startsWith('go')) {
|
||||
mainFile = 'index.go'
|
||||
} else if (runtime.startsWith('ruby')) {
|
||||
mainFile = 'index.rb'
|
||||
}
|
||||
|
||||
logger.info('Looking for main file:', mainFile)
|
||||
|
||||
// Extract all non-directory files
|
||||
for (const fileName of allFiles) {
|
||||
if (!fileName.endsWith('/')) {
|
||||
try {
|
||||
const fileContent = await zip.file(fileName)?.async('string')
|
||||
if (fileContent !== undefined) {
|
||||
allFilesContent[fileName] = fileContent
|
||||
|
||||
// Set main code if this is the main file
|
||||
if (fileName === mainFile) {
|
||||
mainCode = fileContent
|
||||
logger.info('Found main file content, length:', mainCode.length)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(`Failed to extract file ${fileName}:`, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If main file not found, try to find any code file
|
||||
if (!mainCode) {
|
||||
const codeFiles = Object.keys(allFilesContent).filter(
|
||||
(file) =>
|
||||
file.endsWith('.js') ||
|
||||
file.endsWith('.py') ||
|
||||
file.endsWith('.java') ||
|
||||
file.endsWith('.cs') ||
|
||||
file.endsWith('.go') ||
|
||||
file.endsWith('.rb')
|
||||
)
|
||||
|
||||
logger.info('Found code files:', codeFiles)
|
||||
|
||||
if (codeFiles.length > 0) {
|
||||
const firstCodeFile = codeFiles[0]
|
||||
mainCode = allFilesContent[firstCodeFile]
|
||||
logger.info('Using first code file as main, length:', mainCode.length)
|
||||
}
|
||||
}
|
||||
|
||||
// If still no main code, use the first file
|
||||
if (!mainCode && Object.keys(allFilesContent).length > 0) {
|
||||
const firstFile = Object.keys(allFilesContent)[0]
|
||||
mainCode = allFilesContent[firstFile]
|
||||
logger.info('Using first file as main, length:', mainCode.length)
|
||||
}
|
||||
|
||||
logger.info(`Extracted ${Object.keys(allFilesContent).length} files`)
|
||||
return { mainCode, allFiles: allFilesContent }
|
||||
} catch (error) {
|
||||
logger.error('Failed to extract code from ZIP', { error })
|
||||
return { mainCode: '', allFiles: {} }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get detailed information about a Lambda function including code
|
||||
*/
|
||||
async function getFunctionDetailsWithCode(
|
||||
lambdaClient: LambdaClient,
|
||||
functionName: string,
|
||||
region: string,
|
||||
accessKeyId: string,
|
||||
secretAccessKey: string
|
||||
): Promise<LambdaFunctionDetails> {
|
||||
// Get function configuration
|
||||
const functionConfig = await lambdaClient.send(
|
||||
new GetFunctionConfigurationCommand({ FunctionName: functionName })
|
||||
)
|
||||
|
||||
// Get function code
|
||||
const functionCode = await lambdaClient.send(
|
||||
new GetFunctionCommand({ FunctionName: functionName })
|
||||
)
|
||||
|
||||
let codeFiles: Record<string, string> = {}
|
||||
if (functionCode.Code?.Location) {
|
||||
try {
|
||||
logger.info('Downloading code from:', functionCode.Code.Location)
|
||||
|
||||
const response = await fetch(functionCode.Code.Location)
|
||||
logger.info('Fetch response status:', response.status)
|
||||
|
||||
if (response.ok) {
|
||||
const zipBuffer = Buffer.from(await response.arrayBuffer())
|
||||
logger.info('ZIP buffer size:', zipBuffer.length)
|
||||
const extractedCode = await extractCodeFromZip(zipBuffer, functionConfig.Runtime || '')
|
||||
codeFiles = extractedCode.allFiles
|
||||
logger.info('Extracted files count:', Object.keys(codeFiles).length)
|
||||
} else {
|
||||
logger.warn('Fetch failed with status:', response.status)
|
||||
const errorText = await response.text()
|
||||
logger.warn('Error response:', errorText)
|
||||
}
|
||||
} catch (fetchError) {
|
||||
logger.error('Failed to download function code using fetch', { fetchError })
|
||||
}
|
||||
} else {
|
||||
logger.info('No code location found in function response')
|
||||
}
|
||||
|
||||
return {
|
||||
functionArn: functionConfig.FunctionArn || '',
|
||||
functionName: functionConfig.FunctionName || '',
|
||||
runtime: functionConfig.Runtime || '',
|
||||
region,
|
||||
status: functionConfig.State || '',
|
||||
lastModified: functionConfig.LastModified || '',
|
||||
codeSize: functionConfig.CodeSize || 0,
|
||||
description: functionConfig.Description || '',
|
||||
timeout: functionConfig.Timeout || 0,
|
||||
memorySize: functionConfig.MemorySize || 0,
|
||||
environment: functionConfig.Environment?.Variables || {},
|
||||
tags: {}, // Tags need to be fetched separately if needed
|
||||
codeFiles,
|
||||
handler: functionConfig.Handler || '',
|
||||
role: functionConfig.Role || '',
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
|
||||
try {
|
||||
logger.info(`[${requestId}] Processing AWS Lambda fetch request`)
|
||||
|
||||
// Parse and validate request body
|
||||
let body: any
|
||||
try {
|
||||
body = await request.json()
|
||||
} catch (parseError) {
|
||||
logger.error(`[${requestId}] Failed to parse request body`, {
|
||||
error: parseError instanceof Error ? parseError.message : String(parseError),
|
||||
})
|
||||
return createErrorResponse('Invalid JSON in request body', 400, 'INVALID_JSON')
|
||||
}
|
||||
|
||||
const validationResult = FetchRequestSchema.safeParse(body)
|
||||
if (!validationResult.success) {
|
||||
logger.warn(`[${requestId}] Invalid request body`, { errors: validationResult.error.errors })
|
||||
return createErrorResponse('Invalid request parameters', 400, 'VALIDATION_ERROR')
|
||||
}
|
||||
|
||||
const params = validationResult.data
|
||||
|
||||
// Log the payload (excluding sensitive credentials)
|
||||
logger.info(`[${requestId}] AWS Lambda fetch payload received`, {
|
||||
functionName: params.functionName,
|
||||
region: params.region,
|
||||
accessKeyId: params.accessKeyId ? `${params.accessKeyId.substring(0, 4)}...` : undefined,
|
||||
hasSecretAccessKey: !!params.secretAccessKey,
|
||||
hasFunctionName: !!params.functionName,
|
||||
hasRole: !!params.role,
|
||||
role: params.role ? `${params.role.substring(0, 20)}...` : undefined,
|
||||
})
|
||||
|
||||
logger.info(`[${requestId}] Fetching Lambda function: ${params.functionName}`)
|
||||
|
||||
// Create Lambda client
|
||||
const lambdaClient = new LambdaClient({
|
||||
region: params.region,
|
||||
credentials: {
|
||||
accessKeyId: params.accessKeyId,
|
||||
secretAccessKey: params.secretAccessKey,
|
||||
},
|
||||
})
|
||||
|
||||
// Fetch function details and code
|
||||
try {
|
||||
const functionDetails = await getFunctionDetailsWithCode(
|
||||
lambdaClient,
|
||||
params.functionName,
|
||||
params.region,
|
||||
params.accessKeyId,
|
||||
params.secretAccessKey
|
||||
)
|
||||
|
||||
logger.info(`[${requestId}] Successfully fetched Lambda function: ${params.functionName}`, {
|
||||
functionName: functionDetails.functionName,
|
||||
filesCount: Object.keys(functionDetails.codeFiles).length,
|
||||
hasFiles: Object.keys(functionDetails.codeFiles).length > 0,
|
||||
})
|
||||
|
||||
return createSuccessResponse({
|
||||
success: true,
|
||||
output: functionDetails,
|
||||
})
|
||||
} catch (fetchError: any) {
|
||||
// Handle ResourceNotFoundException gracefully - return empty function details
|
||||
if (fetchError.name === 'ResourceNotFoundException') {
|
||||
logger.info(
|
||||
`[${requestId}] Lambda function '${params.functionName}' not found, returning empty response`
|
||||
)
|
||||
|
||||
const emptyFunctionDetails: LambdaFunctionDetails = {
|
||||
functionArn: '',
|
||||
functionName: params.functionName,
|
||||
runtime: '',
|
||||
region: params.region,
|
||||
status: '',
|
||||
lastModified: '',
|
||||
codeSize: 0,
|
||||
description: '',
|
||||
timeout: 0,
|
||||
memorySize: 0,
|
||||
environment: {},
|
||||
tags: {},
|
||||
codeFiles: {},
|
||||
handler: '',
|
||||
role: '',
|
||||
}
|
||||
|
||||
return createSuccessResponse({
|
||||
success: true,
|
||||
output: emptyFunctionDetails,
|
||||
})
|
||||
}
|
||||
|
||||
// Re-throw other errors to be handled by the outer catch block
|
||||
throw fetchError
|
||||
}
|
||||
} catch (error: any) {
|
||||
logger.error(`[${requestId}] Failed to fetch Lambda function`, {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
})
|
||||
|
||||
// Handle specific AWS errors
|
||||
// Note: ResourceNotFoundException is now handled gracefully in the inner try-catch
|
||||
|
||||
if (error.name === 'AccessDeniedException') {
|
||||
return createErrorResponse(
|
||||
'Access denied. Please check your AWS credentials and permissions.',
|
||||
403,
|
||||
'ACCESS_DENIED'
|
||||
)
|
||||
}
|
||||
|
||||
if (error.name === 'InvalidParameterValueException') {
|
||||
return createErrorResponse('Invalid parameter value provided', 400, 'INVALID_PARAMETER')
|
||||
}
|
||||
|
||||
return createErrorResponse('Failed to fetch Lambda function', 500, 'FETCH_ERROR')
|
||||
}
|
||||
}
|
||||
91
apps/sim/app/api/tools/aws-lambda/get-prompts/route.ts
Normal file
91
apps/sim/app/api/tools/aws-lambda/get-prompts/route.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import type { NextRequest } from 'next/server'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
|
||||
|
||||
const logger = createLogger('AWSLambdaGetPromptsAPI')
|
||||
|
||||
// Constants for getPrompts operation
|
||||
const system_prompt = `You are an expert in writing aws lambda functions. The user will provide an input which may contain the the existing lambda code, or they may not. If the initial code is provided, make the changes to the initial code to reflect what the user wants. If no code is provided, your job is to write the lambda function, choosing a runtime and handler.
|
||||
|
||||
Your output should be a valid JSON object, with the following structure:
|
||||
|
||||
[
|
||||
"runtime": runtime string,
|
||||
"handler": handler,
|
||||
"timeout": timeout,
|
||||
"memory": memory,
|
||||
"files":
|
||||
{
|
||||
"file_path_1": "code string for first file",
|
||||
"file_path_2": "code string for second file"
|
||||
}
|
||||
]`
|
||||
|
||||
const schema = {
|
||||
name: 'aws_lambda_function',
|
||||
description: 'Defines the structure for an AWS Lambda function configuration.',
|
||||
strict: true,
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
runtime: {
|
||||
type: 'string',
|
||||
description: 'The runtime environment for the Lambda function.',
|
||||
},
|
||||
handler: {
|
||||
type: 'string',
|
||||
description: 'The function handler that Lambda calls to start execution.',
|
||||
},
|
||||
memory: {
|
||||
type: 'integer',
|
||||
description: 'The amount of memory allocated to the Lambda function in MB (128-10240).',
|
||||
minimum: 128,
|
||||
maximum: 10240,
|
||||
},
|
||||
timeout: {
|
||||
type: 'integer',
|
||||
description: 'The maximum execution time for the Lambda function in seconds (1-900).',
|
||||
minimum: 1,
|
||||
maximum: 900,
|
||||
},
|
||||
files: {
|
||||
type: 'object',
|
||||
description: 'A mapping of file paths to their respective code strings.',
|
||||
additionalProperties: {
|
||||
type: 'string',
|
||||
description: 'The code string for a specific file.',
|
||||
},
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
required: ['runtime', 'handler', 'files', 'memory', 'timeout'],
|
||||
},
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
|
||||
try {
|
||||
logger.info(`[${requestId}] Processing AWS Lambda get prompts request`)
|
||||
|
||||
// No validation needed since this endpoint doesn't require any parameters
|
||||
// Just return the hardcoded system prompt and schema
|
||||
|
||||
logger.info(`[${requestId}] Returning system prompt and schema`)
|
||||
|
||||
return createSuccessResponse({
|
||||
success: true,
|
||||
output: {
|
||||
systemPrompt: system_prompt,
|
||||
schema: schema,
|
||||
},
|
||||
})
|
||||
} catch (error: any) {
|
||||
logger.error(`[${requestId}] Error in get prompts operation`, {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
})
|
||||
|
||||
return createErrorResponse('Failed to get prompts and schema', 500, 'GET_PROMPTS_ERROR')
|
||||
}
|
||||
}
|
||||
@@ -197,18 +197,42 @@ async function executeWorkflow(workflow: any, requestId: string, input?: any) {
|
||||
(acc, [blockId, blockState]) => {
|
||||
// Check if this block has a responseFormat that needs to be parsed
|
||||
if (blockState.responseFormat && typeof blockState.responseFormat === 'string') {
|
||||
try {
|
||||
logger.debug(`[${requestId}] Parsing responseFormat for block ${blockId}`)
|
||||
// Attempt to parse the responseFormat if it's a string
|
||||
const parsedResponseFormat = JSON.parse(blockState.responseFormat)
|
||||
const responseFormatValue = blockState.responseFormat.trim()
|
||||
|
||||
// Check for variable references like <start.input>
|
||||
if (responseFormatValue.startsWith('<') && responseFormatValue.includes('>')) {
|
||||
logger.debug(
|
||||
`[${requestId}] Response format contains variable reference for block ${blockId}`
|
||||
)
|
||||
// Keep variable references as-is - they will be resolved during execution
|
||||
acc[blockId] = blockState
|
||||
} else if (responseFormatValue === '') {
|
||||
// Empty string - remove response format
|
||||
acc[blockId] = {
|
||||
...blockState,
|
||||
responseFormat: parsedResponseFormat,
|
||||
responseFormat: undefined,
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
logger.debug(`[${requestId}] Parsing responseFormat for block ${blockId}`)
|
||||
// Attempt to parse the responseFormat if it's a string
|
||||
const parsedResponseFormat = JSON.parse(responseFormatValue)
|
||||
|
||||
acc[blockId] = {
|
||||
...blockState,
|
||||
responseFormat: parsedResponseFormat,
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
`[${requestId}] Failed to parse responseFormat for block ${blockId}, using undefined`,
|
||||
error
|
||||
)
|
||||
// Set to undefined instead of keeping malformed JSON - this allows execution to continue
|
||||
acc[blockId] = {
|
||||
...blockState,
|
||||
responseFormat: undefined,
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(`[${requestId}] Failed to parse responseFormat for block ${blockId}`, error)
|
||||
acc[blockId] = blockState
|
||||
}
|
||||
} else {
|
||||
acc[blockId] = blockState
|
||||
|
||||
121
apps/sim/app/api/workflows/[id]/revert-to-deployed/route.ts
Normal file
121
apps/sim/app/api/workflows/[id]/revert-to-deployed/route.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import crypto from 'crypto'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import type { NextRequest } from 'next/server'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { saveWorkflowToNormalizedTables } from '@/lib/workflows/db-helpers'
|
||||
import { db } from '@/db'
|
||||
import { workflow } from '@/db/schema'
|
||||
import type { WorkflowState } from '@/stores/workflows/workflow/types'
|
||||
import { validateWorkflowAccess } from '../../middleware'
|
||||
import { createErrorResponse, createSuccessResponse } from '../../utils'
|
||||
|
||||
const logger = createLogger('RevertToDeployedAPI')
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
export const runtime = 'nodejs'
|
||||
|
||||
/**
|
||||
* POST /api/workflows/[id]/revert-to-deployed
|
||||
* Revert workflow to its deployed state by saving deployed state to normalized tables
|
||||
*/
|
||||
export async function POST(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||
const requestId = crypto.randomUUID().slice(0, 8)
|
||||
const { id } = await params
|
||||
|
||||
try {
|
||||
logger.debug(`[${requestId}] Reverting workflow to deployed state: ${id}`)
|
||||
const validation = await validateWorkflowAccess(request, id, false)
|
||||
|
||||
if (validation.error) {
|
||||
logger.warn(`[${requestId}] Workflow revert failed: ${validation.error.message}`)
|
||||
return createErrorResponse(validation.error.message, validation.error.status)
|
||||
}
|
||||
|
||||
const workflowData = validation.workflow
|
||||
|
||||
// Check if workflow is deployed and has deployed state
|
||||
if (!workflowData.isDeployed || !workflowData.deployedState) {
|
||||
logger.warn(`[${requestId}] Cannot revert: workflow is not deployed or has no deployed state`)
|
||||
return createErrorResponse('Workflow is not deployed or has no deployed state', 400)
|
||||
}
|
||||
|
||||
// Validate deployed state structure
|
||||
const deployedState = workflowData.deployedState as WorkflowState
|
||||
if (!deployedState.blocks || !deployedState.edges) {
|
||||
logger.error(`[${requestId}] Invalid deployed state structure`, { deployedState })
|
||||
return createErrorResponse('Invalid deployed state structure', 500)
|
||||
}
|
||||
|
||||
logger.debug(`[${requestId}] Saving deployed state to normalized tables`, {
|
||||
blocksCount: Object.keys(deployedState.blocks).length,
|
||||
edgesCount: deployedState.edges.length,
|
||||
loopsCount: Object.keys(deployedState.loops || {}).length,
|
||||
parallelsCount: Object.keys(deployedState.parallels || {}).length,
|
||||
})
|
||||
|
||||
// Save deployed state to normalized tables
|
||||
const saveResult = await saveWorkflowToNormalizedTables(id, {
|
||||
blocks: deployedState.blocks,
|
||||
edges: deployedState.edges,
|
||||
loops: deployedState.loops || {},
|
||||
parallels: deployedState.parallels || {},
|
||||
lastSaved: Date.now(),
|
||||
isDeployed: workflowData.isDeployed,
|
||||
deployedAt: workflowData.deployedAt,
|
||||
deploymentStatuses: deployedState.deploymentStatuses || {},
|
||||
hasActiveSchedule: deployedState.hasActiveSchedule || false,
|
||||
hasActiveWebhook: deployedState.hasActiveWebhook || false,
|
||||
})
|
||||
|
||||
if (!saveResult.success) {
|
||||
logger.error(`[${requestId}] Failed to save deployed state to normalized tables`, {
|
||||
error: saveResult.error,
|
||||
})
|
||||
return createErrorResponse(
|
||||
saveResult.error || 'Failed to save deployed state to normalized tables',
|
||||
500
|
||||
)
|
||||
}
|
||||
|
||||
// Update workflow's last_synced timestamp to indicate changes
|
||||
await db
|
||||
.update(workflow)
|
||||
.set({
|
||||
lastSynced: new Date(),
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(workflow.id, id))
|
||||
|
||||
// Notify socket server about the revert operation for real-time sync
|
||||
try {
|
||||
const socketServerUrl = process.env.SOCKET_SERVER_URL || 'http://localhost:3002'
|
||||
await fetch(`${socketServerUrl}/api/workflow-reverted`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
workflowId: id,
|
||||
timestamp: Date.now(),
|
||||
}),
|
||||
})
|
||||
logger.debug(`[${requestId}] Notified socket server about workflow revert: ${id}`)
|
||||
} catch (socketError) {
|
||||
// Don't fail the request if socket notification fails
|
||||
logger.warn(`[${requestId}] Failed to notify socket server about revert:`, socketError)
|
||||
}
|
||||
|
||||
logger.info(`[${requestId}] Successfully reverted workflow to deployed state: ${id}`)
|
||||
|
||||
return createSuccessResponse({
|
||||
message: 'Workflow successfully reverted to deployed state',
|
||||
lastSaved: Date.now(),
|
||||
})
|
||||
} catch (error: any) {
|
||||
logger.error(`[${requestId}] Error reverting workflow to deployed state: ${id}`, {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
})
|
||||
return createErrorResponse(error.message || 'Failed to revert workflow to deployed state', 500)
|
||||
}
|
||||
}
|
||||
@@ -305,15 +305,18 @@ export default function Logs() {
|
||||
<div className='flex flex-1 flex-col overflow-hidden'>
|
||||
{/* Table container */}
|
||||
<div className='flex flex-1 flex-col overflow-hidden'>
|
||||
{/* Simple header */}
|
||||
<div className='border-border/50 border-b px-4 py-3'>
|
||||
<div className='flex items-center gap-4 font-medium text-muted-foreground text-xs'>
|
||||
<div className='w-32'>Time</div>
|
||||
<div className='w-20'>Status</div>
|
||||
<div className='flex-1'>Workflow</div>
|
||||
<div className='hidden w-24 lg:block'>Trigger</div>
|
||||
<div className='hidden w-20 xl:block'>Cost</div>
|
||||
<div className='w-20'>Duration</div>
|
||||
{/* Table with fixed layout */}
|
||||
<div className='w-full min-w-[800px]'>
|
||||
{/* Header */}
|
||||
<div className='border-border/50 border-b'>
|
||||
<div className='grid grid-cols-[160px_100px_1fr_120px_100px_100px] gap-4 px-4 py-3 font-medium text-muted-foreground text-xs'>
|
||||
<div>Time</div>
|
||||
<div>Status</div>
|
||||
<div>Workflow</div>
|
||||
<div className='hidden lg:block'>Trigger</div>
|
||||
<div className='hidden xl:block'>Cost</div>
|
||||
<div>Duration</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -357,9 +360,9 @@ export default function Logs() {
|
||||
}`}
|
||||
onClick={() => handleLogClick(log)}
|
||||
>
|
||||
<div className='flex items-center gap-4 p-4'>
|
||||
<div className='grid grid-cols-[160px_100px_1fr_120px_100px_100px] gap-4 p-4'>
|
||||
{/* Time */}
|
||||
<div className='w-32 flex-shrink-0'>
|
||||
<div>
|
||||
<div className='font-medium text-sm'>{formattedDate.formatted}</div>
|
||||
<div className='text-muted-foreground text-xs'>
|
||||
{formattedDate.relative}
|
||||
@@ -367,7 +370,7 @@ export default function Logs() {
|
||||
</div>
|
||||
|
||||
{/* Status */}
|
||||
<div className='w-20 flex-shrink-0'>
|
||||
<div>
|
||||
<div
|
||||
className={`inline-flex items-center justify-center rounded-md px-2 py-1 text-xs ${
|
||||
log.level === 'error'
|
||||
@@ -382,7 +385,7 @@ export default function Logs() {
|
||||
</div>
|
||||
|
||||
{/* Workflow */}
|
||||
<div className='min-w-0 flex-1'>
|
||||
<div className='min-w-0'>
|
||||
<div className='truncate font-medium text-sm'>
|
||||
{log.workflow?.name || 'Unknown Workflow'}
|
||||
</div>
|
||||
@@ -392,14 +395,14 @@ export default function Logs() {
|
||||
</div>
|
||||
|
||||
{/* Trigger */}
|
||||
<div className='hidden w-24 flex-shrink-0 lg:block'>
|
||||
<div className='hidden lg:block'>
|
||||
<div className='text-muted-foreground text-xs'>
|
||||
{log.trigger || '—'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Cost */}
|
||||
<div className='hidden w-20 flex-shrink-0 xl:block'>
|
||||
<div className='hidden xl:block'>
|
||||
<div className='text-xs'>
|
||||
{log.metadata?.enhanced && log.metadata?.cost?.total ? (
|
||||
<span className='text-muted-foreground'>
|
||||
@@ -412,7 +415,7 @@ export default function Logs() {
|
||||
</div>
|
||||
|
||||
{/* Duration */}
|
||||
<div className='w-20 flex-shrink-0'>
|
||||
<div>
|
||||
<div className='text-muted-foreground text-xs'>
|
||||
{log.duration || '—'}
|
||||
</div>
|
||||
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
type OAuthProvider,
|
||||
parseProvider,
|
||||
} from '@/lib/oauth'
|
||||
import { saveToStorage } from '@/stores/workflows/persistence'
|
||||
|
||||
const logger = createLogger('OAuthRequiredModal')
|
||||
|
||||
@@ -157,42 +156,11 @@ export function OAuthRequiredModal({
|
||||
(scope) => !scope.includes('userinfo.email') && !scope.includes('userinfo.profile')
|
||||
)
|
||||
|
||||
const handleRedirectToSettings = () => {
|
||||
try {
|
||||
// Determine the appropriate serviceId and providerId
|
||||
const providerId = getProviderIdFromServiceId(effectiveServiceId)
|
||||
|
||||
// Store information about the required connection
|
||||
saveToStorage<string>('pending_service_id', effectiveServiceId)
|
||||
saveToStorage<string[]>('pending_oauth_scopes', requiredScopes)
|
||||
saveToStorage<string>('pending_oauth_return_url', window.location.href)
|
||||
saveToStorage<string>('pending_oauth_provider_id', providerId)
|
||||
saveToStorage<boolean>('from_oauth_modal', true)
|
||||
|
||||
// Close the modal
|
||||
onClose()
|
||||
|
||||
// Open the settings modal with the credentials tab
|
||||
const event = new CustomEvent('open-settings', {
|
||||
detail: { tab: 'credentials' },
|
||||
})
|
||||
window.dispatchEvent(event)
|
||||
} catch (error) {
|
||||
logger.error('Error redirecting to settings:', { error })
|
||||
}
|
||||
}
|
||||
|
||||
const handleConnectDirectly = async () => {
|
||||
try {
|
||||
// Determine the appropriate serviceId and providerId
|
||||
const providerId = getProviderIdFromServiceId(effectiveServiceId)
|
||||
|
||||
// Store information about the required connection
|
||||
saveToStorage<string>('pending_service_id', effectiveServiceId)
|
||||
saveToStorage<string[]>('pending_oauth_scopes', requiredScopes)
|
||||
saveToStorage<string>('pending_oauth_return_url', window.location.href)
|
||||
saveToStorage<string>('pending_oauth_provider_id', providerId)
|
||||
|
||||
// Close the modal
|
||||
onClose()
|
||||
|
||||
@@ -258,14 +226,6 @@ export function OAuthRequiredModal({
|
||||
<Button type='button' onClick={handleConnectDirectly} className='sm:order-3'>
|
||||
Connect Now
|
||||
</Button>
|
||||
<Button
|
||||
type='button'
|
||||
variant='secondary'
|
||||
onClick={handleRedirectToSettings}
|
||||
className='sm:order-2'
|
||||
>
|
||||
Go to Settings
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@@ -21,31 +21,24 @@ import {
|
||||
type OAuthProvider,
|
||||
parseProvider,
|
||||
} from '@/lib/oauth'
|
||||
import { saveToStorage } from '@/stores/workflows/persistence'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import { useSubBlockValue } from '../../hooks/use-sub-block-value'
|
||||
import { OAuthRequiredModal } from './components/oauth-required-modal'
|
||||
|
||||
const logger = createLogger('CredentialSelector')
|
||||
|
||||
interface CredentialSelectorProps {
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
provider: OAuthProvider
|
||||
requiredScopes?: string[]
|
||||
label?: string
|
||||
blockId: string
|
||||
subBlock: SubBlockConfig
|
||||
disabled?: boolean
|
||||
serviceId?: string
|
||||
isPreview?: boolean
|
||||
previewValue?: any | null
|
||||
}
|
||||
|
||||
export function CredentialSelector({
|
||||
value,
|
||||
onChange,
|
||||
provider,
|
||||
requiredScopes = [],
|
||||
label = 'Select credential',
|
||||
blockId,
|
||||
subBlock,
|
||||
disabled = false,
|
||||
serviceId,
|
||||
isPreview = false,
|
||||
previewValue,
|
||||
}: CredentialSelectorProps) {
|
||||
@@ -55,14 +48,22 @@ export function CredentialSelector({
|
||||
const [showOAuthModal, setShowOAuthModal] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState('')
|
||||
|
||||
// Use collaborative state management via useSubBlockValue hook
|
||||
const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlock.id)
|
||||
|
||||
// Extract values from subBlock config
|
||||
const provider = subBlock.provider as OAuthProvider
|
||||
const requiredScopes = subBlock.requiredScopes || []
|
||||
const label = subBlock.placeholder || 'Select credential'
|
||||
const serviceId = subBlock.serviceId
|
||||
|
||||
// Get the effective value (preview or store value)
|
||||
const effectiveValue = isPreview && previewValue !== undefined ? previewValue : storeValue
|
||||
|
||||
// Initialize selectedId with the effective value
|
||||
useEffect(() => {
|
||||
if (isPreview && previewValue !== undefined) {
|
||||
setSelectedId(previewValue || '')
|
||||
} else {
|
||||
setSelectedId(value)
|
||||
}
|
||||
}, [value, isPreview, previewValue])
|
||||
setSelectedId(effectiveValue || '')
|
||||
}, [effectiveValue])
|
||||
|
||||
// Derive service and provider IDs using useMemo
|
||||
const effectiveServiceId = useMemo(() => {
|
||||
@@ -85,7 +86,9 @@ export function CredentialSelector({
|
||||
// If we have a value but it's not in the credentials, reset it
|
||||
if (selectedId && !data.credentials.some((cred: Credential) => cred.id === selectedId)) {
|
||||
setSelectedId('')
|
||||
onChange('')
|
||||
if (!isPreview) {
|
||||
setStoreValue('')
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-select logic:
|
||||
@@ -99,11 +102,15 @@ export function CredentialSelector({
|
||||
const defaultCred = data.credentials.find((cred: Credential) => cred.isDefault)
|
||||
if (defaultCred) {
|
||||
setSelectedId(defaultCred.id)
|
||||
onChange(defaultCred.id)
|
||||
if (!isPreview) {
|
||||
setStoreValue(defaultCred.id)
|
||||
}
|
||||
} else if (data.credentials.length === 1) {
|
||||
// If only one credential, select it
|
||||
setSelectedId(data.credentials[0].id)
|
||||
onChange(data.credentials[0].id)
|
||||
if (!isPreview) {
|
||||
setStoreValue(data.credentials[0].id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,7 +119,7 @@ export function CredentialSelector({
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}, [effectiveProviderId, onChange, selectedId])
|
||||
}, [effectiveProviderId, selectedId, isPreview, setStoreValue])
|
||||
|
||||
// Fetch credentials on initial mount
|
||||
useEffect(() => {
|
||||
@@ -121,11 +128,7 @@ export function CredentialSelector({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
// Update local state when external value changes
|
||||
useEffect(() => {
|
||||
const currentValue = isPreview ? previewValue : value
|
||||
setSelectedId(currentValue || '')
|
||||
}, [value, isPreview, previewValue])
|
||||
// This effect is no longer needed since we're using effectiveValue directly
|
||||
|
||||
// Listen for visibility changes to update credentials when user returns from settings
|
||||
useEffect(() => {
|
||||
@@ -158,19 +161,13 @@ export function CredentialSelector({
|
||||
const handleSelect = (credentialId: string) => {
|
||||
setSelectedId(credentialId)
|
||||
if (!isPreview) {
|
||||
onChange(credentialId)
|
||||
setStoreValue(credentialId)
|
||||
}
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
// Handle adding a new credential
|
||||
const handleAddCredential = () => {
|
||||
// Store information about the required connection
|
||||
saveToStorage<string>('pending_service_id', effectiveServiceId)
|
||||
saveToStorage<string[]>('pending_oauth_scopes', requiredScopes)
|
||||
saveToStorage<string>('pending_oauth_return_url', window.location.href)
|
||||
saveToStorage<string>('pending_oauth_provider_id', effectiveProviderId)
|
||||
|
||||
// Show the OAuth modal
|
||||
setShowOAuthModal(true)
|
||||
setOpen(false)
|
||||
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
getServiceIdFromScopes,
|
||||
type OAuthProvider,
|
||||
} from '@/lib/oauth'
|
||||
import { saveToStorage } from '@/stores/workflows/persistence'
|
||||
import { OAuthRequiredModal } from '../../credential-selector/components/oauth-required-modal'
|
||||
|
||||
export interface ConfluenceFileInfo {
|
||||
@@ -355,15 +354,6 @@ export function ConfluenceFileSelector({
|
||||
|
||||
// Handle adding a new credential
|
||||
const handleAddCredential = () => {
|
||||
const effectiveServiceId = getServiceId()
|
||||
const providerId = getProviderId()
|
||||
|
||||
// Store information about the required connection
|
||||
saveToStorage<string>('pending_service_id', effectiveServiceId)
|
||||
saveToStorage<string[]>('pending_oauth_scopes', requiredScopes)
|
||||
saveToStorage<string>('pending_oauth_return_url', window.location.href)
|
||||
saveToStorage<string>('pending_oauth_provider_id', providerId)
|
||||
|
||||
// Show the OAuth modal
|
||||
setShowOAuthModal(true)
|
||||
setOpen(false)
|
||||
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
type OAuthProvider,
|
||||
parseProvider,
|
||||
} from '@/lib/oauth'
|
||||
import { saveToStorage } from '@/stores/workflows/persistence'
|
||||
import { OAuthRequiredModal } from '../../credential-selector/components/oauth-required-modal'
|
||||
|
||||
const logger = createLogger('GoogleDrivePicker')
|
||||
@@ -79,6 +78,7 @@ export function GoogleDrivePicker({
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [isLoadingSelectedFile, setIsLoadingSelectedFile] = useState(false)
|
||||
const [showOAuthModal, setShowOAuthModal] = useState(false)
|
||||
const [credentialsLoaded, setCredentialsLoaded] = useState(false)
|
||||
const initialFetchRef = useRef(false)
|
||||
const [openPicker, _authResponse] = useDrivePicker()
|
||||
|
||||
@@ -97,6 +97,7 @@ export function GoogleDrivePicker({
|
||||
// Fetch available credentials for this provider
|
||||
const fetchCredentials = useCallback(async () => {
|
||||
setIsLoading(true)
|
||||
setCredentialsLoaded(false)
|
||||
try {
|
||||
const providerId = getProviderId()
|
||||
const response = await fetch(`/api/auth/oauth/credentials?provider=${providerId}`)
|
||||
@@ -128,6 +129,7 @@ export function GoogleDrivePicker({
|
||||
logger.error('Error fetching credentials:', { error })
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
setCredentialsLoaded(true)
|
||||
}
|
||||
}, [provider, getProviderId, selectedCredentialId])
|
||||
|
||||
@@ -154,9 +156,16 @@ export function GoogleDrivePicker({
|
||||
return data.file
|
||||
}
|
||||
} else {
|
||||
logger.error('Error fetching file by ID:', {
|
||||
error: await response.text(),
|
||||
})
|
||||
const errorText = await response.text()
|
||||
logger.error('Error fetching file by ID:', { error: errorText })
|
||||
|
||||
// If file not found or access denied, clear the selection
|
||||
if (response.status === 404 || response.status === 403) {
|
||||
logger.info('File not accessible, clearing selection')
|
||||
setSelectedFileId('')
|
||||
onChange('')
|
||||
onFileInfoChange?.(null)
|
||||
}
|
||||
}
|
||||
return null
|
||||
} catch (error) {
|
||||
@@ -166,7 +175,7 @@ export function GoogleDrivePicker({
|
||||
setIsLoadingSelectedFile(false)
|
||||
}
|
||||
},
|
||||
[selectedCredentialId, onFileInfoChange]
|
||||
[selectedCredentialId, onChange, onFileInfoChange]
|
||||
)
|
||||
|
||||
// Fetch credentials on initial mount
|
||||
@@ -177,20 +186,61 @@ export function GoogleDrivePicker({
|
||||
}
|
||||
}, [fetchCredentials])
|
||||
|
||||
// Fetch the selected file metadata once credentials are loaded or changed
|
||||
useEffect(() => {
|
||||
// If we have a file ID selected and credentials are ready but we still don't have the file info, fetch it
|
||||
if (value && selectedCredentialId && !selectedFile) {
|
||||
fetchFileById(value)
|
||||
}
|
||||
}, [value, selectedCredentialId, selectedFile, fetchFileById])
|
||||
|
||||
// Keep internal selectedFileId in sync with the value prop
|
||||
useEffect(() => {
|
||||
if (value !== selectedFileId) {
|
||||
const previousFileId = selectedFileId
|
||||
setSelectedFileId(value)
|
||||
// Only clear selected file info if we had a different file before (not initial load)
|
||||
if (previousFileId && previousFileId !== value && selectedFile) {
|
||||
setSelectedFile(null)
|
||||
}
|
||||
}
|
||||
}, [value])
|
||||
}, [value, selectedFileId, selectedFile])
|
||||
|
||||
// Track previous credential ID to detect changes
|
||||
const prevCredentialIdRef = useRef<string>('')
|
||||
|
||||
// Clear selected file when credentials are removed or changed
|
||||
useEffect(() => {
|
||||
const prevCredentialId = prevCredentialIdRef.current
|
||||
prevCredentialIdRef.current = selectedCredentialId
|
||||
|
||||
if (!selectedCredentialId) {
|
||||
// No credentials - clear everything
|
||||
if (selectedFile) {
|
||||
setSelectedFile(null)
|
||||
setSelectedFileId('')
|
||||
onChange('')
|
||||
}
|
||||
} else if (prevCredentialId && prevCredentialId !== selectedCredentialId) {
|
||||
// Credentials changed (not initial load) - clear file info to force refetch
|
||||
if (selectedFile) {
|
||||
setSelectedFile(null)
|
||||
}
|
||||
}
|
||||
}, [selectedCredentialId, selectedFile, onChange])
|
||||
|
||||
// Fetch the selected file metadata once credentials are loaded or changed
|
||||
useEffect(() => {
|
||||
// Only fetch if we have both a file ID and credentials, credentials are loaded, but no file info yet
|
||||
if (
|
||||
value &&
|
||||
selectedCredentialId &&
|
||||
credentialsLoaded &&
|
||||
!selectedFile &&
|
||||
!isLoadingSelectedFile
|
||||
) {
|
||||
fetchFileById(value)
|
||||
}
|
||||
}, [
|
||||
value,
|
||||
selectedCredentialId,
|
||||
credentialsLoaded,
|
||||
selectedFile,
|
||||
isLoadingSelectedFile,
|
||||
fetchFileById,
|
||||
])
|
||||
|
||||
// Fetch the access token for the selected credential
|
||||
const fetchAccessToken = async (): Promise<string | null> => {
|
||||
@@ -286,15 +336,6 @@ export function GoogleDrivePicker({
|
||||
|
||||
// Handle adding a new credential
|
||||
const handleAddCredential = () => {
|
||||
const effectiveServiceId = getServiceId()
|
||||
const providerId = getProviderId()
|
||||
|
||||
// Store information about the required connection
|
||||
saveToStorage<string>('pending_service_id', effectiveServiceId)
|
||||
saveToStorage<string[]>('pending_oauth_scopes', requiredScopes)
|
||||
saveToStorage<string>('pending_oauth_return_url', window.location.href)
|
||||
saveToStorage<string>('pending_oauth_provider_id', providerId)
|
||||
|
||||
// Show the OAuth modal
|
||||
setShowOAuthModal(true)
|
||||
setOpen(false)
|
||||
@@ -399,7 +440,7 @@ export function GoogleDrivePicker({
|
||||
{getFileIcon(selectedFile, 'sm')}
|
||||
<span className='truncate font-normal'>{selectedFile.name}</span>
|
||||
</div>
|
||||
) : selectedFileId && (isLoadingSelectedFile || !selectedCredentialId) ? (
|
||||
) : selectedFileId && isLoadingSelectedFile && selectedCredentialId ? (
|
||||
<div className='flex items-center gap-2'>
|
||||
<RefreshCw className='h-4 w-4 animate-spin' />
|
||||
<span className='text-muted-foreground'>Loading document...</span>
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
getServiceIdFromScopes,
|
||||
type OAuthProvider,
|
||||
} from '@/lib/oauth'
|
||||
import { saveToStorage } from '@/stores/workflows/persistence'
|
||||
import { OAuthRequiredModal } from '../../credential-selector/components/oauth-required-modal'
|
||||
|
||||
const logger = new Logger('jira_issue_selector')
|
||||
@@ -420,15 +419,6 @@ export function JiraIssueSelector({
|
||||
|
||||
// Handle adding a new credential
|
||||
const handleAddCredential = () => {
|
||||
const effectiveServiceId = getServiceId()
|
||||
const providerId = getProviderId()
|
||||
|
||||
// Store information about the required connection
|
||||
saveToStorage<string>('pending_service_id', effectiveServiceId)
|
||||
saveToStorage<string[]>('pending_oauth_scopes', requiredScopes)
|
||||
saveToStorage<string>('pending_oauth_return_url', window.location.href)
|
||||
saveToStorage<string>('pending_oauth_provider_id', providerId)
|
||||
|
||||
// Show the OAuth modal
|
||||
setShowOAuthModal(true)
|
||||
setOpen(false)
|
||||
|
||||
@@ -23,7 +23,6 @@ import {
|
||||
type OAuthProvider,
|
||||
parseProvider,
|
||||
} from '@/lib/oauth'
|
||||
import { saveToStorage } from '@/stores/workflows/persistence'
|
||||
import { OAuthRequiredModal } from '../../credential-selector/components/oauth-required-modal'
|
||||
|
||||
const logger = createLogger('MicrosoftFileSelector')
|
||||
@@ -75,6 +74,7 @@ export function MicrosoftFileSelector({
|
||||
const [availableFiles, setAvailableFiles] = useState<MicrosoftFileInfo[]>([])
|
||||
const [searchQuery, setSearchQuery] = useState<string>('')
|
||||
const [showOAuthModal, setShowOAuthModal] = useState(false)
|
||||
const [credentialsLoaded, setCredentialsLoaded] = useState(false)
|
||||
const initialFetchRef = useRef(false)
|
||||
|
||||
// Determine the appropriate service ID based on provider and scopes
|
||||
@@ -92,6 +92,7 @@ export function MicrosoftFileSelector({
|
||||
// Fetch available credentials for this provider
|
||||
const fetchCredentials = useCallback(async () => {
|
||||
setIsLoading(true)
|
||||
setCredentialsLoaded(false)
|
||||
try {
|
||||
const providerId = getProviderId()
|
||||
const response = await fetch(`/api/auth/oauth/credentials?provider=${providerId}`)
|
||||
@@ -123,6 +124,7 @@ export function MicrosoftFileSelector({
|
||||
logger.error('Error fetching credentials:', { error })
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
setCredentialsLoaded(true)
|
||||
}
|
||||
}, [provider, getProviderId, selectedCredentialId])
|
||||
|
||||
@@ -183,9 +185,16 @@ export function MicrosoftFileSelector({
|
||||
return data.file
|
||||
}
|
||||
} else {
|
||||
logger.error('Error fetching file by ID:', {
|
||||
error: await response.text(),
|
||||
})
|
||||
const errorText = await response.text()
|
||||
logger.error('Error fetching file by ID:', { error: errorText })
|
||||
|
||||
// If file not found or access denied, clear the selection
|
||||
if (response.status === 404 || response.status === 403) {
|
||||
logger.info('File not accessible, clearing selection')
|
||||
setSelectedFileId('')
|
||||
onChange('')
|
||||
onFileInfoChange?.(null)
|
||||
}
|
||||
}
|
||||
return null
|
||||
} catch (error) {
|
||||
@@ -224,20 +233,61 @@ export function MicrosoftFileSelector({
|
||||
}
|
||||
}, [searchQuery, selectedCredentialId, fetchAvailableFiles])
|
||||
|
||||
// Fetch the selected file metadata once credentials are loaded or changed
|
||||
useEffect(() => {
|
||||
// If we have a file ID selected and credentials are ready but we still don't have the file info, fetch it
|
||||
if (value && selectedCredentialId && !selectedFile) {
|
||||
fetchFileById(value)
|
||||
}
|
||||
}, [value, selectedCredentialId, selectedFile, fetchFileById])
|
||||
|
||||
// Keep internal selectedFileId in sync with the value prop
|
||||
useEffect(() => {
|
||||
if (value !== selectedFileId) {
|
||||
const previousFileId = selectedFileId
|
||||
setSelectedFileId(value)
|
||||
// Only clear selected file info if we had a different file before (not initial load)
|
||||
if (previousFileId && previousFileId !== value && selectedFile) {
|
||||
setSelectedFile(null)
|
||||
}
|
||||
}
|
||||
}, [value])
|
||||
}, [value, selectedFileId, selectedFile])
|
||||
|
||||
// Track previous credential ID to detect changes
|
||||
const prevCredentialIdRef = useRef<string>('')
|
||||
|
||||
// Clear selected file when credentials are removed or changed
|
||||
useEffect(() => {
|
||||
const prevCredentialId = prevCredentialIdRef.current
|
||||
prevCredentialIdRef.current = selectedCredentialId
|
||||
|
||||
if (!selectedCredentialId) {
|
||||
// No credentials - clear everything
|
||||
if (selectedFile) {
|
||||
setSelectedFile(null)
|
||||
setSelectedFileId('')
|
||||
onChange('')
|
||||
}
|
||||
} else if (prevCredentialId && prevCredentialId !== selectedCredentialId) {
|
||||
// Credentials changed (not initial load) - clear file info to force refetch
|
||||
if (selectedFile) {
|
||||
setSelectedFile(null)
|
||||
}
|
||||
}
|
||||
}, [selectedCredentialId, selectedFile, onChange])
|
||||
|
||||
// Fetch the selected file metadata once credentials are loaded or changed
|
||||
useEffect(() => {
|
||||
// Only fetch if we have both a file ID and credentials, credentials are loaded, but no file info yet
|
||||
if (
|
||||
value &&
|
||||
selectedCredentialId &&
|
||||
credentialsLoaded &&
|
||||
!selectedFile &&
|
||||
!isLoadingSelectedFile
|
||||
) {
|
||||
fetchFileById(value)
|
||||
}
|
||||
}, [
|
||||
value,
|
||||
selectedCredentialId,
|
||||
credentialsLoaded,
|
||||
selectedFile,
|
||||
isLoadingSelectedFile,
|
||||
fetchFileById,
|
||||
])
|
||||
|
||||
// Handle selecting a file from the available files
|
||||
const handleFileSelect = (file: MicrosoftFileInfo) => {
|
||||
@@ -251,15 +301,6 @@ export function MicrosoftFileSelector({
|
||||
|
||||
// Handle adding a new credential
|
||||
const handleAddCredential = () => {
|
||||
const effectiveServiceId = getServiceId()
|
||||
const providerId = getProviderId()
|
||||
|
||||
// Store information about the required connection
|
||||
saveToStorage<string>('pending_service_id', effectiveServiceId)
|
||||
saveToStorage<string[]>('pending_oauth_scopes', requiredScopes)
|
||||
saveToStorage<string>('pending_oauth_return_url', window.location.href)
|
||||
saveToStorage<string>('pending_oauth_provider_id', providerId)
|
||||
|
||||
// Show the OAuth modal
|
||||
setShowOAuthModal(true)
|
||||
setOpen(false)
|
||||
@@ -381,7 +422,7 @@ export function MicrosoftFileSelector({
|
||||
{getFileIcon(selectedFile, 'sm')}
|
||||
<span className='truncate font-normal'>{selectedFile.name}</span>
|
||||
</div>
|
||||
) : selectedFileId && (isLoadingSelectedFile || !selectedCredentialId) ? (
|
||||
) : selectedFileId && isLoadingSelectedFile && selectedCredentialId ? (
|
||||
<div className='flex items-center gap-2'>
|
||||
<RefreshCw className='h-4 w-4 animate-spin' />
|
||||
<span className='text-muted-foreground'>Loading document...</span>
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
getServiceIdFromScopes,
|
||||
type OAuthProvider,
|
||||
} from '@/lib/oauth'
|
||||
import { saveToStorage } from '@/stores/workflows/persistence'
|
||||
import { OAuthRequiredModal } from '../../credential-selector/components/oauth-required-modal'
|
||||
|
||||
const logger = new Logger('TeamsMessageSelector')
|
||||
@@ -399,15 +398,6 @@ export function TeamsMessageSelector({
|
||||
|
||||
// Handle adding a new credential
|
||||
const handleAddCredential = () => {
|
||||
const effectiveServiceId = getServiceId()
|
||||
const providerId = getProviderId()
|
||||
|
||||
// Store information about the required connection
|
||||
saveToStorage<string>('pending_service_id', effectiveServiceId)
|
||||
saveToStorage<string[]>('pending_oauth_scopes', requiredScopes)
|
||||
saveToStorage<string>('pending_oauth_return_url', window.location.href)
|
||||
saveToStorage<string>('pending_oauth_provider_id', providerId)
|
||||
|
||||
// Show the OAuth modal
|
||||
setShowOAuthModal(true)
|
||||
setOpen(false)
|
||||
|
||||
@@ -16,7 +16,6 @@ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { type Credential, getProviderIdFromServiceId, getServiceIdFromScopes } from '@/lib/oauth'
|
||||
import { OAuthRequiredModal } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/components/sub-block/components/credential-selector/components/oauth-required-modal'
|
||||
import { saveToStorage } from '@/stores/workflows/persistence'
|
||||
|
||||
const logger = createLogger('FolderSelector')
|
||||
|
||||
@@ -274,15 +273,6 @@ export function FolderSelector({
|
||||
|
||||
// Handle adding a new credential
|
||||
const handleAddCredential = () => {
|
||||
const effectiveServiceId = getServiceId()
|
||||
const providerId = getProviderId()
|
||||
|
||||
// Store information about the required connection
|
||||
saveToStorage<string>('pending_service_id', effectiveServiceId)
|
||||
saveToStorage<string[]>('pending_oauth_scopes', requiredScopes)
|
||||
saveToStorage<string>('pending_oauth_return_url', window.location.href)
|
||||
saveToStorage<string>('pending_oauth_provider_id', providerId)
|
||||
|
||||
// Show the OAuth modal
|
||||
setShowOAuthModal(true)
|
||||
setOpen(false)
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
getServiceIdFromScopes,
|
||||
type OAuthProvider,
|
||||
} from '@/lib/oauth'
|
||||
import { saveToStorage } from '@/stores/workflows/persistence'
|
||||
import { OAuthRequiredModal } from '../../credential-selector/components/oauth-required-modal'
|
||||
|
||||
const logger = new Logger('jira_project_selector')
|
||||
@@ -371,15 +370,6 @@ export function JiraProjectSelector({
|
||||
|
||||
// Handle adding a new credential
|
||||
const handleAddCredential = () => {
|
||||
const effectiveServiceId = getServiceId()
|
||||
const providerId = getProviderId()
|
||||
|
||||
// Store information about the required connection
|
||||
saveToStorage<string>('pending_service_id', effectiveServiceId)
|
||||
saveToStorage<string[]>('pending_oauth_scopes', requiredScopes)
|
||||
saveToStorage<string>('pending_oauth_return_url', window.location.href)
|
||||
saveToStorage<string>('pending_oauth_provider_id', providerId)
|
||||
|
||||
// Show the OAuth modal
|
||||
setShowOAuthModal(true)
|
||||
setOpen(false)
|
||||
|
||||
@@ -290,7 +290,13 @@ export function ResponseFormat({
|
||||
{showPreview && (
|
||||
<div className='rounded border bg-muted/30 p-2'>
|
||||
<pre className='max-h-32 overflow-auto text-xs'>
|
||||
{JSON.stringify(generateJSON(properties), null, 2)}
|
||||
{(() => {
|
||||
try {
|
||||
return JSON.stringify(generateJSON(properties), null, 2)
|
||||
} catch (error) {
|
||||
return `Error generating preview: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
}
|
||||
})()}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { Check, ChevronDown, ExternalLink, Plus, RefreshCw } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
} from '@/components/ui/command'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import {
|
||||
type Credential,
|
||||
OAUTH_PROVIDERS,
|
||||
type OAuthProvider,
|
||||
type OAuthService,
|
||||
parseProvider,
|
||||
} from '@/lib/oauth'
|
||||
import { OAuthRequiredModal } from '../../credential-selector/components/oauth-required-modal'
|
||||
|
||||
const logger = createLogger('ToolCredentialSelector')
|
||||
|
||||
// Helper functions for provider icons and names
|
||||
const getProviderIcon = (providerName: OAuthProvider) => {
|
||||
const { baseProvider } = parseProvider(providerName)
|
||||
const baseProviderConfig = OAUTH_PROVIDERS[baseProvider]
|
||||
|
||||
if (!baseProviderConfig) {
|
||||
return <ExternalLink className='h-4 w-4' />
|
||||
}
|
||||
// Always use the base provider icon for a more consistent UI
|
||||
return baseProviderConfig.icon({ className: 'h-4 w-4' })
|
||||
}
|
||||
|
||||
const getProviderName = (providerName: OAuthProvider) => {
|
||||
const { baseProvider } = parseProvider(providerName)
|
||||
const baseProviderConfig = OAUTH_PROVIDERS[baseProvider]
|
||||
|
||||
if (baseProviderConfig) {
|
||||
return baseProviderConfig.name
|
||||
}
|
||||
|
||||
// Fallback: capitalize the provider name
|
||||
return providerName
|
||||
.split('-')
|
||||
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
||||
.join(' ')
|
||||
}
|
||||
|
||||
interface ToolCredentialSelectorProps {
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
provider: OAuthProvider
|
||||
requiredScopes?: string[]
|
||||
label?: string
|
||||
serviceId?: OAuthService
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export function ToolCredentialSelector({
|
||||
value,
|
||||
onChange,
|
||||
provider,
|
||||
requiredScopes = [],
|
||||
label = 'Select account',
|
||||
serviceId,
|
||||
disabled = false,
|
||||
}: ToolCredentialSelectorProps) {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [credentials, setCredentials] = useState<Credential[]>([])
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [showOAuthModal, setShowOAuthModal] = useState(false)
|
||||
const [selectedId, setSelectedId] = useState('')
|
||||
|
||||
// Update selected ID when value changes
|
||||
useEffect(() => {
|
||||
setSelectedId(value)
|
||||
}, [value])
|
||||
|
||||
const fetchCredentials = useCallback(async () => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
const response = await fetch(`/api/auth/oauth/credentials?provider=${provider}`)
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
setCredentials(data.credentials || [])
|
||||
|
||||
// If we have a selected value but it's not in the credentials list, clear it
|
||||
if (value && !data.credentials?.some((cred: Credential) => cred.id === value)) {
|
||||
onChange('')
|
||||
}
|
||||
} else {
|
||||
logger.error('Error fetching credentials:', { error: await response.text() })
|
||||
setCredentials([])
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error fetching credentials:', { error })
|
||||
setCredentials([])
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}, [provider, value, onChange])
|
||||
|
||||
// Fetch credentials on mount and when provider changes
|
||||
useEffect(() => {
|
||||
fetchCredentials()
|
||||
}, [fetchCredentials])
|
||||
|
||||
const handleSelect = (credentialId: string) => {
|
||||
setSelectedId(credentialId)
|
||||
onChange(credentialId)
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const handleOAuthClose = () => {
|
||||
setShowOAuthModal(false)
|
||||
// Refetch credentials to include any new ones
|
||||
fetchCredentials()
|
||||
}
|
||||
|
||||
const selectedCredential = credentials.find((cred) => cred.id === selectedId)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant='outline'
|
||||
role='combobox'
|
||||
aria-expanded={open}
|
||||
className='w-full justify-between'
|
||||
disabled={disabled}
|
||||
>
|
||||
{selectedCredential ? (
|
||||
<div className='flex items-center gap-2 overflow-hidden'>
|
||||
{getProviderIcon(provider)}
|
||||
<span className='truncate font-normal'>{selectedCredential.name}</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className='flex items-center gap-2'>
|
||||
{getProviderIcon(provider)}
|
||||
<span className='text-muted-foreground'>{label}</span>
|
||||
</div>
|
||||
)}
|
||||
<ChevronDown className='ml-2 h-4 w-4 shrink-0 opacity-50' />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className='w-[300px] p-0' align='start'>
|
||||
<Command>
|
||||
<CommandList>
|
||||
<CommandEmpty>
|
||||
{isLoading ? (
|
||||
<div className='flex items-center justify-center p-4'>
|
||||
<RefreshCw className='h-4 w-4 animate-spin' />
|
||||
<span className='ml-2'>Loading...</span>
|
||||
</div>
|
||||
) : credentials.length === 0 ? (
|
||||
<div className='p-4 text-center'>
|
||||
<p className='font-medium text-sm'>No accounts connected.</p>
|
||||
<p className='text-muted-foreground text-xs'>
|
||||
Connect a {getProviderName(provider)} account to continue.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className='p-4 text-center'>
|
||||
<p className='font-medium text-sm'>No accounts found.</p>
|
||||
</div>
|
||||
)}
|
||||
</CommandEmpty>
|
||||
|
||||
{credentials.length > 0 && (
|
||||
<CommandGroup>
|
||||
{credentials.map((credential) => (
|
||||
<CommandItem
|
||||
key={credential.id}
|
||||
value={credential.id}
|
||||
onSelect={() => handleSelect(credential.id)}
|
||||
>
|
||||
<div className='flex items-center gap-2'>
|
||||
{getProviderIcon(credential.provider)}
|
||||
<span className='font-normal'>{credential.name}</span>
|
||||
</div>
|
||||
{credential.id === selectedId && <Check className='ml-auto h-4 w-4' />}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
)}
|
||||
|
||||
<CommandGroup>
|
||||
<CommandItem onSelect={() => setShowOAuthModal(true)}>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Plus className='h-4 w-4' />
|
||||
<span className='font-normal'>Connect {getProviderName(provider)} account</span>
|
||||
</div>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
<OAuthRequiredModal
|
||||
isOpen={showOAuthModal}
|
||||
onClose={handleOAuthClose}
|
||||
provider={provider}
|
||||
toolName={label}
|
||||
requiredScopes={requiredScopes}
|
||||
serviceId={serviceId}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -22,10 +22,10 @@ import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
import { getTool } from '@/tools/utils'
|
||||
import { useSubBlockValue } from '../../hooks/use-sub-block-value'
|
||||
import { ChannelSelectorInput } from '../channel-selector/channel-selector-input'
|
||||
import { CredentialSelector } from '../credential-selector/credential-selector'
|
||||
import { ShortInput } from '../short-input'
|
||||
import { type CustomTool, CustomToolModal } from './components/custom-tool-modal/custom-tool-modal'
|
||||
import { ToolCommand } from './components/tool-command/tool-command'
|
||||
import { ToolCredentialSelector } from './components/tool-credential-selector'
|
||||
|
||||
interface ToolInputProps {
|
||||
blockId: string
|
||||
@@ -347,6 +347,8 @@ export function ToolInput({
|
||||
const [customToolModalOpen, setCustomToolModalOpen] = useState(false)
|
||||
const [editingToolIndex, setEditingToolIndex] = useState<number | null>(null)
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
const [draggedIndex, setDraggedIndex] = useState<number | null>(null)
|
||||
const [dragOverIndex, setDragOverIndex] = useState<number | null>(null)
|
||||
const isWide = useWorkflowStore((state) => state.blocks[blockId]?.isWide)
|
||||
const customTools = useCustomToolsStore((state) => state.getAllTools())
|
||||
const subBlockStore = useSubBlockStore()
|
||||
@@ -668,6 +670,46 @@ export function ToolInput({
|
||||
)
|
||||
}
|
||||
|
||||
const handleDragStart = (e: React.DragEvent, index: number) => {
|
||||
if (isPreview || disabled) return
|
||||
setDraggedIndex(index)
|
||||
e.dataTransfer.effectAllowed = 'move'
|
||||
e.dataTransfer.setData('text/html', '')
|
||||
}
|
||||
|
||||
const handleDragOver = (e: React.DragEvent, index: number) => {
|
||||
if (isPreview || disabled || draggedIndex === null) return
|
||||
e.preventDefault()
|
||||
e.dataTransfer.dropEffect = 'move'
|
||||
setDragOverIndex(index)
|
||||
}
|
||||
|
||||
const handleDragEnd = () => {
|
||||
setDraggedIndex(null)
|
||||
setDragOverIndex(null)
|
||||
}
|
||||
|
||||
const handleDrop = (e: React.DragEvent, dropIndex: number) => {
|
||||
if (isPreview || disabled || draggedIndex === null || draggedIndex === dropIndex) return
|
||||
e.preventDefault()
|
||||
|
||||
const newTools = [...selectedTools]
|
||||
const draggedTool = newTools[draggedIndex]
|
||||
|
||||
newTools.splice(draggedIndex, 1)
|
||||
|
||||
if (dropIndex === selectedTools.length) {
|
||||
newTools.push(draggedTool)
|
||||
} else {
|
||||
const adjustedDropIndex = draggedIndex < dropIndex ? dropIndex - 1 : dropIndex
|
||||
newTools.splice(adjustedDropIndex, 0, draggedTool)
|
||||
}
|
||||
|
||||
setStoreValue(newTools)
|
||||
setDraggedIndex(null)
|
||||
setDragOverIndex(null)
|
||||
}
|
||||
|
||||
const IconComponent = ({ icon: Icon, className }: { icon: any; className?: string }) => {
|
||||
if (!Icon) return null
|
||||
return <Icon className={className} />
|
||||
@@ -827,9 +869,34 @@ export function ToolInput({
|
||||
return (
|
||||
<div
|
||||
key={`${tool.type}-${toolIndex}`}
|
||||
className={cn('group flex flex-col', isWide ? 'w-[calc(50%-0.25rem)]' : 'w-full')}
|
||||
className={cn(
|
||||
'group relative flex flex-col transition-all duration-200 ease-in-out',
|
||||
isWide ? 'w-[calc(50%-0.25rem)]' : 'w-full',
|
||||
draggedIndex === toolIndex ? 'scale-95 opacity-40' : '',
|
||||
dragOverIndex === toolIndex && draggedIndex !== toolIndex && draggedIndex !== null
|
||||
? 'translate-y-1 transform'
|
||||
: '',
|
||||
selectedTools.length > 1 && !isPreview && !disabled
|
||||
? 'cursor-grab active:cursor-grabbing'
|
||||
: ''
|
||||
)}
|
||||
draggable={!isPreview && !disabled}
|
||||
onDragStart={(e) => handleDragStart(e, toolIndex)}
|
||||
onDragOver={(e) => handleDragOver(e, toolIndex)}
|
||||
onDragEnd={handleDragEnd}
|
||||
onDrop={(e) => handleDrop(e, toolIndex)}
|
||||
>
|
||||
<div className='flex flex-col overflow-visible rounded-md border bg-card'>
|
||||
{/* Subtle drop indicator - use border highlight instead of separate line */}
|
||||
<div
|
||||
className={cn(
|
||||
'flex flex-col overflow-visible rounded-md border bg-card',
|
||||
dragOverIndex === toolIndex &&
|
||||
draggedIndex !== toolIndex &&
|
||||
draggedIndex !== null
|
||||
? 'border-t-2 border-t-muted-foreground/40'
|
||||
: ''
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center justify-between bg-accent/50 p-2',
|
||||
@@ -993,13 +1060,14 @@ export function ToolInput({
|
||||
<div className='font-medium text-muted-foreground text-xs'>
|
||||
Account
|
||||
</div>
|
||||
<CredentialSelector
|
||||
<ToolCredentialSelector
|
||||
value={tool.params.credential || ''}
|
||||
onChange={(value) => handleCredentialChange(toolIndex, value)}
|
||||
provider={oauthConfig.provider as OAuthProvider}
|
||||
requiredScopes={oauthConfig.additionalScopes || []}
|
||||
label={`Select ${oauthConfig.provider} account`}
|
||||
serviceId={oauthConfig.provider}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
@@ -1091,6 +1159,20 @@ export function ToolInput({
|
||||
)
|
||||
})}
|
||||
|
||||
{/* Drop zone for the end of the list */}
|
||||
{selectedTools.length > 0 && draggedIndex !== null && (
|
||||
<div
|
||||
className={cn(
|
||||
'h-2 w-full rounded transition-all duration-200 ease-in-out',
|
||||
dragOverIndex === selectedTools.length
|
||||
? 'border-b-2 border-b-muted-foreground/40'
|
||||
: ''
|
||||
)}
|
||||
onDragOver={(e) => handleDragOver(e, selectedTools.length)}
|
||||
onDrop={(e) => handleDrop(e, selectedTools.length)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
|
||||
@@ -16,7 +16,7 @@ import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
|
||||
import { useWorkflowStore } from '@/stores/workflows/workflow/store'
|
||||
import { useSubBlockValue } from '../../hooks/use-sub-block-value'
|
||||
import { CredentialSelector } from '../credential-selector/credential-selector'
|
||||
import { ToolCredentialSelector } from '../tool-input/components/tool-credential-selector'
|
||||
import { WebhookModal } from './components/webhook-modal'
|
||||
|
||||
const logger = createLogger('WebhookConfig')
|
||||
@@ -564,7 +564,7 @@ export function WebhookConfig({
|
||||
{error && <div className='mb-2 text-red-500 text-sm dark:text-red-400'>{error}</div>}
|
||||
|
||||
<div className='mb-3'>
|
||||
<CredentialSelector
|
||||
<ToolCredentialSelector
|
||||
value={gmailCredentialId}
|
||||
onChange={handleCredentialChange}
|
||||
provider='google-email'
|
||||
|
||||
@@ -297,27 +297,11 @@ export function SubBlock({
|
||||
case 'oauth-input':
|
||||
return (
|
||||
<CredentialSelector
|
||||
value={
|
||||
isPreview ? previewValue || '' : typeof config.value === 'string' ? config.value : ''
|
||||
}
|
||||
onChange={(value) => {
|
||||
// Only allow changes in non-preview mode and when not disabled
|
||||
if (!isPreview && !disabled) {
|
||||
const event = new CustomEvent('update-subblock-value', {
|
||||
detail: {
|
||||
blockId,
|
||||
subBlockId: config.id,
|
||||
value,
|
||||
},
|
||||
})
|
||||
window.dispatchEvent(event)
|
||||
}
|
||||
}}
|
||||
provider={config.provider as any}
|
||||
requiredScopes={config.requiredScopes || []}
|
||||
label={config.placeholder || 'Select a credential'}
|
||||
serviceId={config.serviceId}
|
||||
blockId={blockId}
|
||||
subBlock={config}
|
||||
disabled={isDisabled}
|
||||
isPreview={isPreview}
|
||||
previewValue={previewValue}
|
||||
/>
|
||||
)
|
||||
case 'file-selector':
|
||||
|
||||
@@ -29,6 +29,35 @@ export interface ConnectedBlock {
|
||||
}
|
||||
}
|
||||
|
||||
function parseResponseFormatSafely(responseFormatValue: any, blockId: string): any {
|
||||
if (!responseFormatValue) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (typeof responseFormatValue === 'object' && responseFormatValue !== null) {
|
||||
return responseFormatValue
|
||||
}
|
||||
|
||||
if (typeof responseFormatValue === 'string') {
|
||||
const trimmedValue = responseFormatValue.trim()
|
||||
|
||||
if (trimmedValue.startsWith('<') && trimmedValue.includes('>')) {
|
||||
return trimmedValue
|
||||
}
|
||||
|
||||
if (trimmedValue === '') {
|
||||
return undefined
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(trimmedValue)
|
||||
} catch (error) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Helper function to extract fields from JSON Schema
|
||||
function extractFieldsFromSchema(schema: any): Field[] {
|
||||
if (!schema || typeof schema !== 'object') {
|
||||
@@ -77,15 +106,8 @@ export function useBlockConnections(blockId: string) {
|
||||
|
||||
let responseFormat
|
||||
|
||||
try {
|
||||
responseFormat =
|
||||
typeof responseFormatValue === 'string' && responseFormatValue
|
||||
? JSON.parse(responseFormatValue)
|
||||
: responseFormatValue // Handle case where it's already an object
|
||||
} catch (e) {
|
||||
logger.error('Failed to parse response format:', { e })
|
||||
responseFormat = undefined
|
||||
}
|
||||
// Safely parse response format with proper error handling
|
||||
responseFormat = parseResponseFormatSafely(responseFormatValue, sourceId)
|
||||
|
||||
// Get the default output type from the block's outputs
|
||||
const defaultOutputs: Field[] = Object.entries(sourceBlock.outputs || {}).map(([key]) => ({
|
||||
@@ -120,15 +142,8 @@ export function useBlockConnections(blockId: string) {
|
||||
|
||||
let responseFormat
|
||||
|
||||
try {
|
||||
responseFormat =
|
||||
typeof responseFormatValue === 'string' && responseFormatValue
|
||||
? JSON.parse(responseFormatValue)
|
||||
: responseFormatValue // Handle case where it's already an object
|
||||
} catch (e) {
|
||||
logger.error('Failed to parse response format:', { e })
|
||||
responseFormat = undefined
|
||||
}
|
||||
// Safely parse response format with proper error handling
|
||||
responseFormat = parseResponseFormatSafely(responseFormatValue, edge.source)
|
||||
|
||||
// Get the default output type from the block's outputs
|
||||
const defaultOutputs: Field[] = Object.entries(sourceBlock.outputs || {}).map(([key]) => ({
|
||||
|
||||
@@ -7,7 +7,6 @@ import { useParams, usePathname, useRouter } from 'next/navigation'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { useSession } from '@/lib/auth-client'
|
||||
import { env } from '@/lib/env'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import {
|
||||
getKeyboardShortcutText,
|
||||
@@ -28,7 +27,7 @@ import { WorkspaceHeader } from './components/workspace-header/workspace-header'
|
||||
|
||||
const logger = createLogger('Sidebar')
|
||||
|
||||
const IS_DEV = env.NODE_ENV === 'development'
|
||||
const IS_DEV = process.env.NODE_ENV === 'development'
|
||||
|
||||
export function Sidebar() {
|
||||
useGlobalShortcuts()
|
||||
|
||||
@@ -58,6 +58,22 @@ export function WorkflowPreview({
|
||||
defaultZoom,
|
||||
onNodeClick,
|
||||
}: WorkflowPreviewProps) {
|
||||
// Handle migrated logs that don't have complete workflow state
|
||||
if (!workflowState || !workflowState.blocks || !workflowState.edges) {
|
||||
return (
|
||||
<div
|
||||
style={{ height, width }}
|
||||
className='flex items-center justify-center rounded-lg border border-gray-200 bg-gray-50 dark:border-gray-700 dark:bg-gray-900'
|
||||
>
|
||||
<div className='text-center text-gray-500 dark:text-gray-400'>
|
||||
<div className='mb-2 font-medium text-lg'>⚠️ Logged State Not Found</div>
|
||||
<div className='text-sm'>
|
||||
This log was migrated from the old system and doesn't contain workflow state data.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const blocksStructure = useMemo(
|
||||
() => ({
|
||||
count: Object.keys(workflowState.blocks || {}).length,
|
||||
@@ -84,8 +100,8 @@ export function WorkflowPreview({
|
||||
|
||||
const edgesStructure = useMemo(
|
||||
() => ({
|
||||
count: workflowState.edges.length,
|
||||
ids: workflowState.edges.map((e) => e.id).join(','),
|
||||
count: workflowState.edges?.length || 0,
|
||||
ids: workflowState.edges?.map((e) => e.id).join(',') || '',
|
||||
}),
|
||||
[workflowState.edges]
|
||||
)
|
||||
@@ -115,7 +131,7 @@ export function WorkflowPreview({
|
||||
const nodes: Node[] = useMemo(() => {
|
||||
const nodeArray: Node[] = []
|
||||
|
||||
Object.entries(workflowState.blocks).forEach(([blockId, block]) => {
|
||||
Object.entries(workflowState.blocks || {}).forEach(([blockId, block]) => {
|
||||
if (!block || !block.type) {
|
||||
logger.warn(`Skipping invalid block: ${blockId}`)
|
||||
return
|
||||
@@ -186,7 +202,7 @@ export function WorkflowPreview({
|
||||
})
|
||||
|
||||
if (block.type === 'loop') {
|
||||
const childBlocks = Object.entries(workflowState.blocks).filter(
|
||||
const childBlocks = Object.entries(workflowState.blocks || {}).filter(
|
||||
([_, childBlock]) => childBlock.data?.parentId === blockId
|
||||
)
|
||||
|
||||
@@ -223,7 +239,7 @@ export function WorkflowPreview({
|
||||
}, [blocksStructure, loopsStructure, parallelsStructure, showSubBlocks, workflowState.blocks])
|
||||
|
||||
const edges: Edge[] = useMemo(() => {
|
||||
return workflowState.edges.map((edge) => ({
|
||||
return (workflowState.edges || []).map((edge) => ({
|
||||
id: edge.id,
|
||||
source: edge.source,
|
||||
target: edge.target,
|
||||
|
||||
316
apps/sim/blocks/blocks/aws_lambda.ts
Normal file
316
apps/sim/blocks/blocks/aws_lambda.ts
Normal file
@@ -0,0 +1,316 @@
|
||||
import { S3Icon } from '@/components/icons'
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
import type { BlockConfig } from '../types'
|
||||
|
||||
// Define the expected response type for AWS Lambda operations
|
||||
interface AWSLambdaResponse extends ToolResponse {
|
||||
output: {
|
||||
functionArn: string
|
||||
functionName: string
|
||||
endpointName?: string
|
||||
endpointUrl?: 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>
|
||||
codeFiles: Record<string, string>
|
||||
handler: string
|
||||
apiGatewayId?: string
|
||||
stageName?: string
|
||||
}
|
||||
}
|
||||
|
||||
export const AWSLambdaBlock: BlockConfig<AWSLambdaResponse> = {
|
||||
type: 'aws_lambda',
|
||||
name: 'AWS Lambda',
|
||||
description: 'Deploy and manage AWS Lambda functions',
|
||||
longDescription:
|
||||
'Create, update, and manage AWS Lambda functions with automatic deployment. Configure runtime environments, memory allocation, timeout settings, and environment variables for serverless function execution. Use fetch to retrieve existing function details and code files to understand the current state, then deploy with any desired changes to the function configuration and code.',
|
||||
docsLink: 'https://docs.simstudio.ai/tools/aws-lambda',
|
||||
category: 'tools',
|
||||
bgColor: '#FF9900',
|
||||
icon: S3Icon,
|
||||
subBlocks: [
|
||||
{
|
||||
id: 'operation',
|
||||
title: 'Operation',
|
||||
type: 'dropdown',
|
||||
layout: 'full',
|
||||
options: [
|
||||
{ label: 'Fetch', id: 'fetch' },
|
||||
{ label: 'Create/Update', id: 'create/update' },
|
||||
{ label: 'Deploy Endpoint', id: 'deploy_endpoint' },
|
||||
{ label: 'Get Prompts', id: 'getPrompts' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'accessKeyId',
|
||||
title: 'AWS Access Key ID',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Enter AWS Access Key ID',
|
||||
password: true,
|
||||
description: 'AWS Access Key ID for authentication. Required for all operations.',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['fetch', 'create/update', 'deploy_endpoint'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'secretAccessKey',
|
||||
title: 'AWS Secret Access Key',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Enter AWS Secret Access Key',
|
||||
password: true,
|
||||
description: 'AWS Secret Access Key for authentication. Required for all operations.',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['fetch', 'create/update', 'deploy_endpoint'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'role',
|
||||
title: 'Role ARN',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Enter the IAM Role ARN for Lambda execution',
|
||||
password: false,
|
||||
description:
|
||||
'IAM Role ARN that the Lambda function will assume during execution. Must have appropriate permissions.',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['fetch', 'create/update', 'deploy_endpoint'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'region',
|
||||
title: 'AWS Region',
|
||||
type: 'dropdown',
|
||||
layout: 'full',
|
||||
options: [
|
||||
'us-east-1',
|
||||
'us-east-2',
|
||||
'us-west-1',
|
||||
'us-west-2',
|
||||
'af-south-1',
|
||||
'ap-east-1',
|
||||
'ap-south-1',
|
||||
'ap-northeast-1',
|
||||
'ap-northeast-2',
|
||||
'ap-northeast-3',
|
||||
'ap-southeast-1',
|
||||
'ap-southeast-2',
|
||||
'ca-central-1',
|
||||
'eu-central-1',
|
||||
'eu-west-1',
|
||||
'eu-west-2',
|
||||
'eu-west-3',
|
||||
'eu-north-1',
|
||||
'eu-south-1',
|
||||
'me-south-1',
|
||||
'sa-east-1',
|
||||
],
|
||||
description: 'AWS region where the Lambda function will be deployed or is located.',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['fetch', 'create/update', 'deploy_endpoint'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'functionName',
|
||||
title: 'Function Name',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Enter Lambda function name',
|
||||
description:
|
||||
'Name of the Lambda function. For fetch operations, this must be an existing function to understand its current state. For create/update, this will be the name of the new function or the existing function to update with any desired changes.',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['fetch', 'create/update', 'deploy_endpoint'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'endpointName',
|
||||
title: 'Endpoint Name',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'Enter API Gateway endpoint name',
|
||||
description:
|
||||
'Name for the API Gateway HTTP API endpoint. This will be used to create the API Gateway and will appear in the endpoint URL.',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['deploy_endpoint'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'runtime',
|
||||
title: 'Runtime',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'e.g., nodejs18.x, python3.11, java11',
|
||||
description:
|
||||
'Lambda runtime environment. Common values: nodejs18.x, python3.11, java11, go1.x, dotnet6, ruby2.7',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['create/update'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'handler',
|
||||
title: 'Handler',
|
||||
type: 'short-input',
|
||||
layout: 'full',
|
||||
placeholder: 'e.g., index.handler',
|
||||
description:
|
||||
'Function handler that Lambda calls to start execution. Format varies by runtime: index.handler (Node.js), lambda_function.lambda_handler (Python), etc.',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['create/update'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'timeout',
|
||||
title: 'Timeout (seconds)',
|
||||
type: 'short-input',
|
||||
layout: 'half',
|
||||
placeholder: 'Enter timeout in seconds (1-900)',
|
||||
description: 'Function timeout in seconds. Must be between 1 and 900 seconds (15 minutes).',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['create/update'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'memorySize',
|
||||
title: 'Memory (MB)',
|
||||
type: 'short-input',
|
||||
layout: 'half',
|
||||
placeholder: 'Enter memory in MB (128-10240)',
|
||||
description:
|
||||
'Amount of memory allocated to the function in MB. Must be between 128 and 10240 MB.',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['create/update'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'code',
|
||||
title: 'Function Code',
|
||||
type: 'code',
|
||||
layout: 'full',
|
||||
language: 'json',
|
||||
placeholder: '{\n "index.js": "exports.handler = async (event) => {...};"\n}',
|
||||
description:
|
||||
'Function code files as JSON object. Keys are file paths, values are file contents. For Node.js, typically include index.js with the handler function.',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['create/update'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'environmentVariables',
|
||||
title: 'Environment Variables',
|
||||
type: 'table',
|
||||
layout: 'full',
|
||||
columns: ['Key', 'Value'],
|
||||
placeholder: 'Add environment variables as key-value pairs',
|
||||
description:
|
||||
'Environment variables that will be available to the Lambda function during execution.',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['create/update'],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'tags',
|
||||
title: 'Tags',
|
||||
type: 'table',
|
||||
layout: 'full',
|
||||
columns: ['Key', 'Value'],
|
||||
placeholder: 'Add tags as key-value pairs',
|
||||
description: 'Tags to associate with the Lambda function for organization and cost tracking.',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['create/update'],
|
||||
},
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
access: [
|
||||
'aws_lambda_deploy',
|
||||
'aws_lambda_deploy_endpoint',
|
||||
'aws_lambda_fetch',
|
||||
'aws_lambda_get_prompts',
|
||||
],
|
||||
config: {
|
||||
tool: (params: Record<string, any>) => {
|
||||
const operation = String(params.operation || '').trim()
|
||||
// Only map user-facing names; pass through tool IDs as-is
|
||||
const operationMap: Record<string, string> = {
|
||||
fetch: 'aws_lambda_fetch',
|
||||
'create/update': 'aws_lambda_deploy',
|
||||
deploy_endpoint: 'aws_lambda_deploy_endpoint',
|
||||
getPrompts: 'aws_lambda_get_prompts',
|
||||
}
|
||||
if (operationMap[operation]) {
|
||||
return operationMap[operation]
|
||||
}
|
||||
// If already a tool ID, return as-is
|
||||
if (
|
||||
operation === 'aws_lambda_fetch' ||
|
||||
operation === 'aws_lambda_deploy' ||
|
||||
operation === 'aws_lambda_deploy_endpoint' ||
|
||||
operation === 'aws_lambda_get_prompts'
|
||||
) {
|
||||
return operation
|
||||
}
|
||||
// Default fallback
|
||||
console.warn(`Unknown operation: "${operation}", defaulting to aws_lambda_fetch`)
|
||||
return 'aws_lambda_fetch'
|
||||
},
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
accessKeyId: { type: 'string', required: true },
|
||||
secretAccessKey: { type: 'string', required: true },
|
||||
region: { type: 'string', required: true },
|
||||
role: { type: 'string', required: true },
|
||||
operation: { type: 'string', required: true },
|
||||
functionName: { type: 'string', required: true },
|
||||
endpointName: { type: 'string', required: false },
|
||||
handler: { type: 'string', required: false },
|
||||
runtime: { type: 'string', required: false },
|
||||
code: { type: 'json', required: false },
|
||||
timeout: { type: 'number', required: false },
|
||||
memorySize: { type: 'number', required: false },
|
||||
environmentVariables: { type: 'json', required: false },
|
||||
tags: { type: 'json', required: false },
|
||||
},
|
||||
outputs: {
|
||||
functionArn: 'string',
|
||||
functionName: 'string',
|
||||
endpointName: 'any',
|
||||
endpointUrl: 'any',
|
||||
runtime: 'string',
|
||||
region: 'string',
|
||||
status: 'string',
|
||||
lastModified: 'string',
|
||||
codeSize: 'number',
|
||||
description: 'string',
|
||||
timeout: 'number',
|
||||
memorySize: 'number',
|
||||
environment: 'json',
|
||||
tags: 'json',
|
||||
codeFiles: 'json',
|
||||
handler: 'string',
|
||||
apiGatewayId: 'any',
|
||||
stageName: 'any',
|
||||
},
|
||||
}
|
||||
@@ -31,6 +31,18 @@ export const RedditBlock: BlockConfig<
|
||||
],
|
||||
},
|
||||
|
||||
// Reddit OAuth Authentication
|
||||
{
|
||||
id: 'credential',
|
||||
title: 'Reddit Account',
|
||||
type: 'oauth-input',
|
||||
layout: 'full',
|
||||
provider: 'reddit',
|
||||
serviceId: 'reddit',
|
||||
requiredScopes: ['identity', 'read'],
|
||||
placeholder: 'Select Reddit account',
|
||||
},
|
||||
|
||||
// Common fields - appear for all actions
|
||||
{
|
||||
id: 'subreddit',
|
||||
@@ -151,27 +163,31 @@ export const RedditBlock: BlockConfig<
|
||||
},
|
||||
params: (inputs) => {
|
||||
const action = inputs.action || 'get_posts'
|
||||
const { credential, ...rest } = inputs
|
||||
|
||||
if (action === 'get_comments') {
|
||||
return {
|
||||
postId: inputs.postId,
|
||||
subreddit: inputs.subreddit,
|
||||
sort: inputs.commentSort,
|
||||
limit: inputs.commentLimit ? Number.parseInt(inputs.commentLimit) : undefined,
|
||||
postId: rest.postId,
|
||||
subreddit: rest.subreddit,
|
||||
sort: rest.commentSort,
|
||||
limit: rest.commentLimit ? Number.parseInt(rest.commentLimit) : undefined,
|
||||
credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
subreddit: inputs.subreddit,
|
||||
sort: inputs.sort,
|
||||
limit: inputs.limit ? Number.parseInt(inputs.limit) : undefined,
|
||||
time: inputs.sort === 'top' ? inputs.time : undefined,
|
||||
subreddit: rest.subreddit,
|
||||
sort: rest.sort,
|
||||
limit: rest.limit ? Number.parseInt(rest.limit) : undefined,
|
||||
time: rest.sort === 'top' ? rest.time : undefined,
|
||||
credential: credential,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
inputs: {
|
||||
action: { type: 'string', required: true },
|
||||
credential: { type: 'string', required: true },
|
||||
subreddit: { type: 'string', required: true },
|
||||
sort: { type: 'string', required: true },
|
||||
time: { type: 'string', required: false },
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { AgentBlock } from './blocks/agent'
|
||||
import { AirtableBlock } from './blocks/airtable'
|
||||
import { ApiBlock } from './blocks/api'
|
||||
import { AWSLambdaBlock } from './blocks/aws_lambda'
|
||||
// import { AutoblocksBlock } from './blocks/autoblocks'
|
||||
import { BrowserUseBlock } from './blocks/browser_use'
|
||||
import { ClayBlock } from './blocks/clay'
|
||||
@@ -72,6 +73,7 @@ export const registry: Record<string, BlockConfig> = {
|
||||
agent: AgentBlock,
|
||||
airtable: AirtableBlock,
|
||||
api: ApiBlock,
|
||||
aws_lambda: AWSLambdaBlock,
|
||||
// autoblocks: AutoblocksBlock,
|
||||
browser_use: BrowserUseBlock,
|
||||
clay: ClayBlock,
|
||||
|
||||
@@ -50,6 +50,7 @@ interface SocketContextType {
|
||||
onUserJoined: (handler: (data: any) => void) => void
|
||||
onUserLeft: (handler: (data: any) => void) => void
|
||||
onWorkflowDeleted: (handler: (data: any) => void) => void
|
||||
onWorkflowReverted: (handler: (data: any) => void) => void
|
||||
}
|
||||
|
||||
const SocketContext = createContext<SocketContextType>({
|
||||
@@ -71,6 +72,7 @@ const SocketContext = createContext<SocketContextType>({
|
||||
onUserJoined: () => {},
|
||||
onUserLeft: () => {},
|
||||
onWorkflowDeleted: () => {},
|
||||
onWorkflowReverted: () => {},
|
||||
})
|
||||
|
||||
export const useSocket = () => useContext(SocketContext)
|
||||
@@ -100,6 +102,7 @@ export function SocketProvider({ children, user }: SocketProviderProps) {
|
||||
userJoined?: (data: any) => void
|
||||
userLeft?: (data: any) => void
|
||||
workflowDeleted?: (data: any) => void
|
||||
workflowReverted?: (data: any) => void
|
||||
}>({})
|
||||
|
||||
// Helper function to generate a fresh socket token
|
||||
@@ -281,6 +284,12 @@ export function SocketProvider({ children, user }: SocketProviderProps) {
|
||||
eventHandlers.current.workflowDeleted?.(data)
|
||||
})
|
||||
|
||||
// Workflow revert events
|
||||
socketInstance.on('workflow-reverted', (data) => {
|
||||
logger.info(`Workflow ${data.workflowId} has been reverted to deployed state`)
|
||||
eventHandlers.current.workflowReverted?.(data)
|
||||
})
|
||||
|
||||
// Cursor update events
|
||||
socketInstance.on('cursor-update', (data) => {
|
||||
setPresenceUsers((prev) =>
|
||||
@@ -557,6 +566,10 @@ export function SocketProvider({ children, user }: SocketProviderProps) {
|
||||
eventHandlers.current.workflowDeleted = handler
|
||||
}, [])
|
||||
|
||||
const onWorkflowReverted = useCallback((handler: (data: any) => void) => {
|
||||
eventHandlers.current.workflowReverted = handler
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<SocketContext.Provider
|
||||
value={{
|
||||
@@ -578,6 +591,7 @@ export function SocketProvider({ children, user }: SocketProviderProps) {
|
||||
onUserJoined,
|
||||
onUserLeft,
|
||||
onWorkflowDeleted,
|
||||
onWorkflowReverted,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -736,7 +736,29 @@ describe('AgentBlockHandler', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should throw an error for invalid JSON in responseFormat', async () => {
|
||||
it('should handle invalid JSON in responseFormat gracefully', async () => {
|
||||
mockFetch.mockImplementationOnce(() => {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
headers: {
|
||||
get: (name: string) => {
|
||||
if (name === 'Content-Type') return 'application/json'
|
||||
if (name === 'X-Execution-Data') return null
|
||||
return null
|
||||
},
|
||||
},
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
content: 'Regular text response',
|
||||
model: 'mock-model',
|
||||
tokens: { prompt: 10, completion: 20, total: 30 },
|
||||
timing: { total: 100 },
|
||||
toolCalls: [],
|
||||
cost: undefined,
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
const inputs = {
|
||||
model: 'gpt-4o',
|
||||
userPrompt: 'Format this output.',
|
||||
@@ -744,9 +766,60 @@ describe('AgentBlockHandler', () => {
|
||||
responseFormat: '{invalid-json',
|
||||
}
|
||||
|
||||
await expect(handler.execute(mockBlock, inputs, mockContext)).rejects.toThrow(
|
||||
'Invalid response'
|
||||
)
|
||||
// Should not throw an error, but continue with default behavior
|
||||
const result = await handler.execute(mockBlock, inputs, mockContext)
|
||||
|
||||
expect(result).toEqual({
|
||||
content: 'Regular text response',
|
||||
model: 'mock-model',
|
||||
tokens: { prompt: 10, completion: 20, total: 30 },
|
||||
toolCalls: { list: [], count: 0 },
|
||||
providerTiming: { total: 100 },
|
||||
cost: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle variable references in responseFormat gracefully', async () => {
|
||||
mockFetch.mockImplementationOnce(() => {
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
headers: {
|
||||
get: (name: string) => {
|
||||
if (name === 'Content-Type') return 'application/json'
|
||||
if (name === 'X-Execution-Data') return null
|
||||
return null
|
||||
},
|
||||
},
|
||||
json: () =>
|
||||
Promise.resolve({
|
||||
content: 'Regular text response',
|
||||
model: 'mock-model',
|
||||
tokens: { prompt: 10, completion: 20, total: 30 },
|
||||
timing: { total: 100 },
|
||||
toolCalls: [],
|
||||
cost: undefined,
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
const inputs = {
|
||||
model: 'gpt-4o',
|
||||
userPrompt: 'Format this output.',
|
||||
apiKey: 'test-api-key',
|
||||
responseFormat: '<start.input>',
|
||||
}
|
||||
|
||||
// Should not throw an error, but continue with default behavior
|
||||
const result = await handler.execute(mockBlock, inputs, mockContext)
|
||||
|
||||
expect(result).toEqual({
|
||||
content: 'Regular text response',
|
||||
model: 'mock-model',
|
||||
tokens: { prompt: 10, completion: 20, total: 30 },
|
||||
toolCalls: { list: [], count: 0 },
|
||||
providerTiming: { total: 100 },
|
||||
cost: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle errors from the provider request', async () => {
|
||||
|
||||
@@ -58,22 +58,63 @@ export class AgentBlockHandler implements BlockHandler {
|
||||
private parseResponseFormat(responseFormat?: string | object): any {
|
||||
if (!responseFormat || responseFormat === '') return undefined
|
||||
|
||||
try {
|
||||
const parsed =
|
||||
typeof responseFormat === 'string' ? JSON.parse(responseFormat) : responseFormat
|
||||
|
||||
if (parsed && typeof parsed === 'object' && !parsed.schema && !parsed.name) {
|
||||
// If already an object, process it directly
|
||||
if (typeof responseFormat === 'object' && responseFormat !== null) {
|
||||
const formatObj = responseFormat as any
|
||||
if (!formatObj.schema && !formatObj.name) {
|
||||
return {
|
||||
name: 'response_schema',
|
||||
schema: parsed,
|
||||
schema: responseFormat,
|
||||
strict: true,
|
||||
}
|
||||
}
|
||||
return parsed
|
||||
} catch (error: any) {
|
||||
logger.error('Failed to parse response format:', { error })
|
||||
throw new Error(`Invalid response format: ${error.message}`)
|
||||
return responseFormat
|
||||
}
|
||||
|
||||
// Handle string values
|
||||
if (typeof responseFormat === 'string') {
|
||||
const trimmedValue = responseFormat.trim()
|
||||
|
||||
// Check for variable references like <start.input>
|
||||
if (trimmedValue.startsWith('<') && trimmedValue.includes('>')) {
|
||||
logger.info('Response format contains variable reference:', {
|
||||
value: trimmedValue,
|
||||
})
|
||||
// Variable references should have been resolved by the resolver before reaching here
|
||||
// If we still have a variable reference, it means it couldn't be resolved
|
||||
// Return undefined to use default behavior (no structured response)
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Try to parse as JSON
|
||||
try {
|
||||
const parsed = JSON.parse(trimmedValue)
|
||||
|
||||
if (parsed && typeof parsed === 'object' && !parsed.schema && !parsed.name) {
|
||||
return {
|
||||
name: 'response_schema',
|
||||
schema: parsed,
|
||||
strict: true,
|
||||
}
|
||||
}
|
||||
return parsed
|
||||
} catch (error: any) {
|
||||
logger.warn('Failed to parse response format as JSON, using default behavior:', {
|
||||
error: error.message,
|
||||
value: trimmedValue,
|
||||
})
|
||||
// Return undefined instead of throwing - this allows execution to continue
|
||||
// without structured response format
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
// For any other type, return undefined
|
||||
logger.warn('Unexpected response format type, using default behavior:', {
|
||||
type: typeof responseFormat,
|
||||
value: responseFormat,
|
||||
})
|
||||
return undefined
|
||||
}
|
||||
|
||||
private async formatTools(inputTools: ToolInput[], context: ExecutionContext): Promise<any[]> {
|
||||
|
||||
@@ -25,6 +25,7 @@ export function useCollaborativeWorkflow() {
|
||||
onUserJoined,
|
||||
onUserLeft,
|
||||
onWorkflowDeleted,
|
||||
onWorkflowReverted,
|
||||
} = useSocket()
|
||||
|
||||
const { activeWorkflowId } = useWorkflowRegistry()
|
||||
@@ -262,12 +263,80 @@ export function useCollaborativeWorkflow() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleWorkflowReverted = async (data: any) => {
|
||||
const { workflowId } = data
|
||||
logger.info(`Workflow ${workflowId} has been reverted to deployed state`)
|
||||
|
||||
// If the reverted workflow is the currently active one, reload the workflow state
|
||||
if (activeWorkflowId === workflowId) {
|
||||
logger.info(`Currently active workflow ${workflowId} was reverted, reloading state`)
|
||||
|
||||
try {
|
||||
// Fetch the updated workflow state from the server (which loads from normalized tables)
|
||||
const response = await fetch(`/api/workflows/${workflowId}`)
|
||||
if (response.ok) {
|
||||
const responseData = await response.json()
|
||||
const workflowData = responseData.data
|
||||
|
||||
if (workflowData?.state) {
|
||||
// Update the workflow store with the reverted state
|
||||
isApplyingRemoteChange.current = true
|
||||
try {
|
||||
// Update the main workflow state using the API response
|
||||
useWorkflowStore.setState({
|
||||
blocks: workflowData.state.blocks || {},
|
||||
edges: workflowData.state.edges || [],
|
||||
loops: workflowData.state.loops || {},
|
||||
parallels: workflowData.state.parallels || {},
|
||||
isDeployed: workflowData.state.isDeployed || false,
|
||||
deployedAt: workflowData.state.deployedAt,
|
||||
lastSaved: workflowData.state.lastSaved || Date.now(),
|
||||
hasActiveSchedule: workflowData.state.hasActiveSchedule || false,
|
||||
hasActiveWebhook: workflowData.state.hasActiveWebhook || false,
|
||||
deploymentStatuses: workflowData.state.deploymentStatuses || {},
|
||||
})
|
||||
|
||||
// Update subblock store with reverted values
|
||||
const subblockValues: Record<string, Record<string, any>> = {}
|
||||
Object.entries(workflowData.state.blocks || {}).forEach(([blockId, block]) => {
|
||||
const blockState = block as any
|
||||
subblockValues[blockId] = {}
|
||||
Object.entries(blockState.subBlocks || {}).forEach(([subblockId, subblock]) => {
|
||||
subblockValues[blockId][subblockId] = (subblock as any).value
|
||||
})
|
||||
})
|
||||
|
||||
// Update subblock store for this workflow
|
||||
useSubBlockStore.setState((state: any) => ({
|
||||
workflowValues: {
|
||||
...state.workflowValues,
|
||||
[workflowId]: subblockValues,
|
||||
},
|
||||
}))
|
||||
|
||||
logger.info(`Successfully loaded reverted workflow state for ${workflowId}`)
|
||||
} finally {
|
||||
isApplyingRemoteChange.current = false
|
||||
}
|
||||
} else {
|
||||
logger.error('No state found in workflow data after revert', { workflowData })
|
||||
}
|
||||
} else {
|
||||
logger.error(`Failed to fetch workflow data after revert: ${response.statusText}`)
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error reloading workflow state after revert:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register event handlers
|
||||
onWorkflowOperation(handleWorkflowOperation)
|
||||
onSubblockUpdate(handleSubblockUpdate)
|
||||
onUserJoined(handleUserJoined)
|
||||
onUserLeft(handleUserLeft)
|
||||
onWorkflowDeleted(handleWorkflowDeleted)
|
||||
onWorkflowReverted(handleWorkflowReverted)
|
||||
|
||||
return () => {
|
||||
// Cleanup handled by socket context
|
||||
@@ -278,6 +347,7 @@ export function useCollaborativeWorkflow() {
|
||||
onUserJoined,
|
||||
onUserLeft,
|
||||
onWorkflowDeleted,
|
||||
onWorkflowReverted,
|
||||
workflowStore,
|
||||
subBlockStore,
|
||||
activeWorkflowId,
|
||||
|
||||
@@ -135,6 +135,7 @@ export const auth = betterAuth({
|
||||
'notion',
|
||||
'microsoft',
|
||||
'slack',
|
||||
'reddit',
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -825,6 +826,57 @@ export const auth = betterAuth({
|
||||
},
|
||||
},
|
||||
|
||||
// Reddit provider
|
||||
{
|
||||
providerId: 'reddit',
|
||||
clientId: env.REDDIT_CLIENT_ID as string,
|
||||
clientSecret: env.REDDIT_CLIENT_SECRET as string,
|
||||
authorizationUrl: 'https://www.reddit.com/api/v1/authorize',
|
||||
tokenUrl: 'https://www.reddit.com/api/v1/access_token',
|
||||
userInfoUrl: 'https://oauth.reddit.com/api/v1/me',
|
||||
scopes: ['identity', 'read'],
|
||||
responseType: 'code',
|
||||
pkce: false,
|
||||
accessType: 'offline',
|
||||
authentication: 'basic',
|
||||
prompt: 'consent',
|
||||
redirectURI: `${env.NEXT_PUBLIC_APP_URL}/api/auth/oauth2/callback/reddit`,
|
||||
getUserInfo: async (tokens) => {
|
||||
try {
|
||||
const response = await fetch('https://oauth.reddit.com/api/v1/me', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${tokens.accessToken}`,
|
||||
'User-Agent': 'sim-studio/1.0',
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Error fetching Reddit user info:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const now = new Date()
|
||||
|
||||
return {
|
||||
id: data.id,
|
||||
name: data.name || 'Reddit User',
|
||||
email: `${data.name}@reddit.user`, // Reddit doesn't provide email in identity scope
|
||||
image: data.icon_img || null,
|
||||
emailVerified: false,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error in Reddit getUserInfo:', { error })
|
||||
return null
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
providerId: 'linear',
|
||||
clientId: env.LINEAR_CLIENT_ID as string,
|
||||
|
||||
@@ -103,6 +103,8 @@ export const env = createEnv({
|
||||
LINEAR_CLIENT_SECRET: z.string().optional(),
|
||||
SLACK_CLIENT_ID: z.string().optional(),
|
||||
SLACK_CLIENT_SECRET: z.string().optional(),
|
||||
REDDIT_CLIENT_ID: z.string().optional(),
|
||||
REDDIT_CLIENT_SECRET: z.string().optional(),
|
||||
SOCKET_SERVER_URL: z.string().url().optional(),
|
||||
SOCKET_PORT: z.number().optional(),
|
||||
PORT: z.number().optional(),
|
||||
|
||||
@@ -26,6 +26,8 @@ vi.mock('../env', () => ({
|
||||
LINEAR_CLIENT_SECRET: 'linear_client_secret',
|
||||
SLACK_CLIENT_ID: 'slack_client_id',
|
||||
SLACK_CLIENT_SECRET: 'slack_client_secret',
|
||||
REDDIT_CLIENT_ID: 'reddit_client_id',
|
||||
REDDIT_CLIENT_SECRET: 'reddit_client_secret',
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -80,6 +82,11 @@ describe('OAuth Token Refresh', () => {
|
||||
endpoint: 'https://discord.com/api/v10/oauth2/token',
|
||||
},
|
||||
{ name: 'Linear', providerId: 'linear', endpoint: 'https://api.linear.app/oauth/token' },
|
||||
{
|
||||
name: 'Reddit',
|
||||
providerId: 'reddit',
|
||||
endpoint: 'https://www.reddit.com/api/v1/access_token',
|
||||
},
|
||||
]
|
||||
|
||||
basicAuthProviders.forEach(({ name, providerId, endpoint }) => {
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
MicrosoftTeamsIcon,
|
||||
NotionIcon,
|
||||
OutlookIcon,
|
||||
RedditIcon,
|
||||
SlackIcon,
|
||||
SupabaseIcon,
|
||||
xIcon,
|
||||
@@ -39,6 +40,7 @@ export type OAuthProvider =
|
||||
| 'microsoft'
|
||||
| 'linear'
|
||||
| 'slack'
|
||||
| 'reddit'
|
||||
| string
|
||||
|
||||
export type OAuthService =
|
||||
@@ -61,6 +63,7 @@ export type OAuthService =
|
||||
| 'outlook'
|
||||
| 'linear'
|
||||
| 'slack'
|
||||
| 'reddit'
|
||||
|
||||
export interface OAuthProviderConfig {
|
||||
id: OAuthProvider
|
||||
@@ -387,6 +390,23 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
|
||||
},
|
||||
defaultService: 'slack',
|
||||
},
|
||||
reddit: {
|
||||
id: 'reddit',
|
||||
name: 'Reddit',
|
||||
icon: (props) => RedditIcon(props),
|
||||
services: {
|
||||
reddit: {
|
||||
id: 'reddit',
|
||||
name: 'Reddit',
|
||||
description: 'Access Reddit data and content from subreddits.',
|
||||
providerId: 'reddit',
|
||||
icon: (props) => RedditIcon(props),
|
||||
baseProviderIcon: (props) => RedditIcon(props),
|
||||
scopes: ['identity', 'read'],
|
||||
},
|
||||
},
|
||||
defaultService: 'reddit',
|
||||
},
|
||||
}
|
||||
|
||||
// Helper function to get a service by provider and service ID
|
||||
@@ -695,6 +715,18 @@ function getProviderAuthConfig(provider: string): ProviderAuthConfig {
|
||||
useBasicAuth: false,
|
||||
}
|
||||
}
|
||||
case 'reddit': {
|
||||
const { clientId, clientSecret } = getCredentials(
|
||||
env.REDDIT_CLIENT_ID,
|
||||
env.REDDIT_CLIENT_SECRET
|
||||
)
|
||||
return {
|
||||
tokenEndpoint: 'https://www.reddit.com/api/v1/access_token',
|
||||
clientId,
|
||||
clientSecret,
|
||||
useBasicAuth: true,
|
||||
}
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unsupported provider: ${provider}`)
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ const nextConfig: NextConfig = {
|
||||
},
|
||||
experimental: {
|
||||
optimizeCss: true,
|
||||
turbopackSourceMaps: false,
|
||||
},
|
||||
...(env.NODE_ENV === 'development' && {
|
||||
allowedDevOrigins: [
|
||||
@@ -41,36 +42,6 @@ const nextConfig: NextConfig = {
|
||||
],
|
||||
outputFileTracingRoot: path.join(__dirname, '../../'),
|
||||
}),
|
||||
webpack: (config, { isServer, dev }) => {
|
||||
// Skip webpack configuration in development when using Turbopack
|
||||
if (dev && env.NEXT_RUNTIME === 'turbopack') {
|
||||
return config
|
||||
}
|
||||
|
||||
// Configure webpack to use filesystem cache for faster incremental builds
|
||||
if (config.cache) {
|
||||
config.cache = {
|
||||
type: 'filesystem',
|
||||
buildDependencies: {
|
||||
config: [__filename],
|
||||
},
|
||||
cacheDirectory: path.resolve(process.cwd(), '.next/cache/webpack'),
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid aliasing React on the server/edge runtime builds because it bypasses
|
||||
// the "react-server" export condition, which Next.js relies on when
|
||||
// bundling React Server Components and API route handlers.
|
||||
if (!isServer) {
|
||||
config.resolve.alias = {
|
||||
...config.resolve.alias,
|
||||
react: path.join(__dirname, '../../node_modules/react'),
|
||||
'react-dom': path.join(__dirname, '../../node_modules/react-dom'),
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
},
|
||||
transpilePackages: ['prettier', '@react-email/components', '@react-email/render'],
|
||||
async headers() {
|
||||
return [
|
||||
@@ -144,6 +115,16 @@ const nextConfig: NextConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
// Block access to sourcemap files (defense in depth)
|
||||
{
|
||||
source: '/(.*)\\.map$',
|
||||
headers: [
|
||||
{
|
||||
key: 'x-robots-tag',
|
||||
value: 'noindex',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Apply security headers to all routes
|
||||
{
|
||||
source: '/:path*',
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"dev:classic": "next dev",
|
||||
"dev:sockets": "bun run socket-server/index.ts",
|
||||
"dev:full": "concurrently -n \"NextJS,Realtime\" -c \"cyan,magenta\" \"bun run dev\" \"bun run dev:sockets\"",
|
||||
"build": "next build",
|
||||
"build": "next build --turbopack",
|
||||
"start": "next start",
|
||||
"prepare": "cd ../.. && bun husky",
|
||||
"db:push": "bunx drizzle-kit push",
|
||||
@@ -27,8 +27,10 @@
|
||||
},
|
||||
"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-apigatewayv2": "3.840.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 +71,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 +89,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",
|
||||
|
||||
@@ -121,7 +121,7 @@ export class Serializer {
|
||||
// Include response format fields if available
|
||||
...(params.responseFormat
|
||||
? {
|
||||
responseFormat: JSON.parse(params.responseFormat),
|
||||
responseFormat: this.parseResponseFormatSafely(params.responseFormat),
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
@@ -136,6 +136,48 @@ export class Serializer {
|
||||
}
|
||||
}
|
||||
|
||||
private parseResponseFormatSafely(responseFormat: any): any {
|
||||
if (!responseFormat) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
// If already an object, return as-is
|
||||
if (typeof responseFormat === 'object' && responseFormat !== null) {
|
||||
return responseFormat
|
||||
}
|
||||
|
||||
// Handle string values
|
||||
if (typeof responseFormat === 'string') {
|
||||
const trimmedValue = responseFormat.trim()
|
||||
|
||||
// Check for variable references like <start.input>
|
||||
if (trimmedValue.startsWith('<') && trimmedValue.includes('>')) {
|
||||
// Keep variable references as-is
|
||||
return trimmedValue
|
||||
}
|
||||
|
||||
if (trimmedValue === '') {
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Try to parse as JSON
|
||||
try {
|
||||
return JSON.parse(trimmedValue)
|
||||
} catch (error) {
|
||||
// If parsing fails, return undefined to avoid crashes
|
||||
// This allows the workflow to continue without structured response format
|
||||
logger.warn('Failed to parse response format as JSON in serializer, using undefined:', {
|
||||
value: trimmedValue,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
})
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
// For any other type, return undefined
|
||||
return undefined
|
||||
}
|
||||
|
||||
private extractParams(block: BlockState): Record<string, any> {
|
||||
// Special handling for subflow blocks (loops, parallels, etc.)
|
||||
if (block.type === 'loop' || block.type === 'parallel') {
|
||||
|
||||
@@ -115,6 +115,26 @@ export class RoomManager {
|
||||
)
|
||||
}
|
||||
|
||||
handleWorkflowRevert(workflowId: string, timestamp: number) {
|
||||
logger.info(`Handling workflow revert notification for ${workflowId}`)
|
||||
|
||||
const room = this.workflowRooms.get(workflowId)
|
||||
if (!room) {
|
||||
logger.debug(`No active room found for reverted workflow ${workflowId}`)
|
||||
return
|
||||
}
|
||||
|
||||
this.io.to(workflowId).emit('workflow-reverted', {
|
||||
workflowId,
|
||||
message: 'Workflow has been reverted to deployed state',
|
||||
timestamp,
|
||||
})
|
||||
|
||||
room.lastModified = timestamp
|
||||
|
||||
logger.info(`Notified ${room.users.size} users about workflow revert: ${workflowId}`)
|
||||
}
|
||||
|
||||
async validateWorkflowConsistency(
|
||||
workflowId: string
|
||||
): Promise<{ valid: boolean; issues: string[] }> {
|
||||
|
||||
@@ -50,6 +50,27 @@ export function createHttpHandler(roomManager: RoomManager, logger: Logger) {
|
||||
return
|
||||
}
|
||||
|
||||
// Handle workflow revert notifications from the main API
|
||||
if (req.method === 'POST' && req.url === '/api/workflow-reverted') {
|
||||
let body = ''
|
||||
req.on('data', (chunk) => {
|
||||
body += chunk.toString()
|
||||
})
|
||||
req.on('end', () => {
|
||||
try {
|
||||
const { workflowId, timestamp } = JSON.parse(body)
|
||||
roomManager.handleWorkflowRevert(workflowId, timestamp)
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' })
|
||||
res.end(JSON.stringify({ success: true }))
|
||||
} catch (error) {
|
||||
logger.error('Error handling workflow revert notification:', error)
|
||||
res.writeHead(500, { 'Content-Type': 'application/json' })
|
||||
res.end(JSON.stringify({ error: 'Failed to process revert notification' }))
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
res.writeHead(404, { 'Content-Type': 'application/json' })
|
||||
res.end(JSON.stringify({ error: 'Not found' }))
|
||||
}
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
/**
|
||||
* OAuth state persistence for secure OAuth redirects
|
||||
* This is the ONLY localStorage usage in the app - for temporary OAuth state during redirects
|
||||
*/
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
|
||||
const logger = createLogger('OAuthPersistence')
|
||||
|
||||
interface OAuthState {
|
||||
providerId: string
|
||||
serviceId: string
|
||||
requiredScopes: string[]
|
||||
returnUrl: string
|
||||
context: string
|
||||
timestamp: number
|
||||
data?: Record<string, any>
|
||||
}
|
||||
|
||||
const OAUTH_STATE_KEY = 'pending_oauth_state'
|
||||
const OAUTH_STATE_EXPIRY = 10 * 60 * 1000 // 10 minutes
|
||||
|
||||
/**
|
||||
* Generic function to save data to localStorage (used by main branch OAuth flow)
|
||||
*/
|
||||
export function saveToStorage<T>(key: string, data: T): boolean {
|
||||
try {
|
||||
localStorage.setItem(key, JSON.stringify(data))
|
||||
return true
|
||||
} catch (error) {
|
||||
logger.error(`Failed to save data to ${key}:`, { error })
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic function to load data from localStorage
|
||||
*/
|
||||
export function loadFromStorage<T>(key: string): T | null {
|
||||
try {
|
||||
const stored = localStorage.getItem(key)
|
||||
if (!stored) return null
|
||||
return JSON.parse(stored) as T
|
||||
} catch (error) {
|
||||
logger.error(`Failed to load data from ${key}:`, { error })
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save OAuth state to localStorage before redirect
|
||||
*/
|
||||
export function saveOAuthState(state: OAuthState): boolean {
|
||||
try {
|
||||
const stateWithTimestamp = {
|
||||
...state,
|
||||
timestamp: Date.now(),
|
||||
}
|
||||
localStorage.setItem(OAUTH_STATE_KEY, JSON.stringify(stateWithTimestamp))
|
||||
return true
|
||||
} catch (error) {
|
||||
logger.error('Failed to save OAuth state to localStorage:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and remove OAuth state from localStorage after redirect
|
||||
*/
|
||||
export function loadOAuthState(): OAuthState | null {
|
||||
try {
|
||||
const stored = localStorage.getItem(OAUTH_STATE_KEY)
|
||||
if (!stored) return null
|
||||
|
||||
const state = JSON.parse(stored) as OAuthState
|
||||
|
||||
// Check if state has expired
|
||||
if (Date.now() - state.timestamp > OAUTH_STATE_EXPIRY) {
|
||||
localStorage.removeItem(OAUTH_STATE_KEY)
|
||||
logger.warn('OAuth state expired, removing from localStorage')
|
||||
return null
|
||||
}
|
||||
|
||||
// Remove state after loading (one-time use)
|
||||
localStorage.removeItem(OAUTH_STATE_KEY)
|
||||
|
||||
return state
|
||||
} catch (error) {
|
||||
logger.error('Failed to load OAuth state from localStorage:', error)
|
||||
// Clean up corrupted state
|
||||
localStorage.removeItem(OAUTH_STATE_KEY)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove OAuth state from localStorage (cleanup)
|
||||
*/
|
||||
export function clearOAuthState(): void {
|
||||
try {
|
||||
localStorage.removeItem(OAUTH_STATE_KEY)
|
||||
} catch (error) {
|
||||
logger.error('Failed to clear OAuth state from localStorage:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there's pending OAuth state
|
||||
*/
|
||||
export function hasPendingOAuthState(): boolean {
|
||||
try {
|
||||
const stored = localStorage.getItem(OAUTH_STATE_KEY)
|
||||
if (!stored) return false
|
||||
|
||||
const state = JSON.parse(stored) as OAuthState
|
||||
|
||||
// Check if expired
|
||||
if (Date.now() - state.timestamp > OAUTH_STATE_EXPIRY) {
|
||||
localStorage.removeItem(OAUTH_STATE_KEY)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
logger.error('Failed to check pending OAuth state:', error)
|
||||
localStorage.removeItem(OAUTH_STATE_KEY)
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -822,13 +822,18 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
|
||||
}
|
||||
},
|
||||
|
||||
revertToDeployedState: (deployedState: WorkflowState) => {
|
||||
revertToDeployedState: async (deployedState: WorkflowState) => {
|
||||
const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId
|
||||
|
||||
if (!activeWorkflowId) {
|
||||
console.error('Cannot revert: no active workflow ID')
|
||||
return
|
||||
}
|
||||
|
||||
// Preserving the workflow-specific deployment status if it exists
|
||||
const deploymentStatus = activeWorkflowId
|
||||
? useWorkflowRegistry.getState().getWorkflowDeploymentStatus(activeWorkflowId)
|
||||
: null
|
||||
const deploymentStatus = useWorkflowRegistry
|
||||
.getState()
|
||||
.getWorkflowDeploymentStatus(activeWorkflowId)
|
||||
|
||||
const newState = {
|
||||
blocks: deployedState.blocks,
|
||||
@@ -841,7 +846,7 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
|
||||
// Keep existing deployment statuses and update for the active workflow if needed
|
||||
deploymentStatuses: {
|
||||
...get().deploymentStatuses,
|
||||
...(activeWorkflowId && deploymentStatus
|
||||
...(deploymentStatus
|
||||
? {
|
||||
[activeWorkflowId]: deploymentStatus,
|
||||
}
|
||||
@@ -852,9 +857,6 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
|
||||
// Update the main workflow state
|
||||
set(newState)
|
||||
|
||||
// Get the active workflow ID
|
||||
if (!activeWorkflowId) return
|
||||
|
||||
// Initialize subblock store with values from deployed state
|
||||
const subBlockStore = useSubBlockStore.getState()
|
||||
const values: Record<string, Record<string, any>> = {}
|
||||
@@ -885,7 +887,27 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
|
||||
|
||||
pushHistory(set, get, newState, 'Reverted to deployed state')
|
||||
get().updateLastSaved()
|
||||
// Note: Socket.IO handles real-time sync automatically
|
||||
|
||||
// Call API to persist the revert to normalized tables
|
||||
try {
|
||||
const response = await fetch(`/api/workflows/${activeWorkflowId}/revert-to-deployed`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json()
|
||||
console.error('Failed to persist revert to deployed state:', errorData.error)
|
||||
// Don't throw error to avoid breaking the UI, but log it
|
||||
} else {
|
||||
console.log('Successfully persisted revert to deployed state')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error calling revert to deployed API:', error)
|
||||
// Don't throw error to avoid breaking the UI
|
||||
}
|
||||
},
|
||||
|
||||
toggleBlockAdvancedMode: (id: string) => {
|
||||
|
||||
159
apps/sim/tools/aws_lambda/deploy.ts
Normal file
159
apps/sim/tools/aws_lambda/deploy.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
import type { ToolConfig } from '../types'
|
||||
|
||||
interface AWSLambdaDeployInput {
|
||||
accessKeyId: string
|
||||
secretAccessKey: string
|
||||
region: string
|
||||
role: string
|
||||
functionName: string
|
||||
handler?: string
|
||||
runtime: string
|
||||
code: Record<string, string>
|
||||
timeout?: number
|
||||
memorySize?: number
|
||||
environmentVariables: Record<string, string>
|
||||
tags: Record<string, 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:
|
||||
'Make sure to satisfy the user request.Deploy or update an AWS Lambda function with the specified configuration. This tool can create a new Lambda function or update an existing one with any changes you specify. It accepts function code as a JSON object where keys are file paths and values are file contents. For Node.js functions, typically include an index.js file with the handler function. The tool will package and deploy the code to AWS Lambda with the specified runtime, memory, timeout, and environment variables. When updating an existing function, this tool can make whatever changes you want to the function configuration and code.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
// Common AWS parameters (always at the top)
|
||||
accessKeyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
requiredForToolCall: true,
|
||||
description: 'AWS Access Key ID for authentication. This is required to access AWS services.',
|
||||
},
|
||||
secretAccessKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
requiredForToolCall: true,
|
||||
description:
|
||||
'AWS Secret Access Key for authentication. This is required to access AWS services.',
|
||||
},
|
||||
region: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
requiredForToolCall: true,
|
||||
description:
|
||||
'AWS region where the Lambda function will be deployed. Examples: us-east-1, eu-west-1, ap-southeast-2',
|
||||
},
|
||||
role: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
requiredForToolCall: true,
|
||||
description:
|
||||
'IAM Role ARN that the Lambda function will assume during execution. This role must have appropriate permissions for the function to operate correctly.',
|
||||
},
|
||||
// Operation-specific parameters
|
||||
functionName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
optionalToolInput: true,
|
||||
description:
|
||||
'Name of the Lambda function to create or update. If the function already exists, it will be updated with any changes you specify to the configuration and code.',
|
||||
},
|
||||
handler: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
optionalToolInput: true,
|
||||
description:
|
||||
'Function handler that Lambda calls to start execution. Format varies by runtime: index.handler (Node.js), lambda_function.lambda_handler (Python), etc. If not provided, a default will be used based on the runtime.',
|
||||
},
|
||||
runtime: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
optionalToolInput: true,
|
||||
description:
|
||||
'Lambda runtime environment. Common values: nodejs18.x, python3.11, java11, go1.x, dotnet6, ruby2.7. This determines the execution environment for your function.',
|
||||
},
|
||||
code: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
description:
|
||||
'Function code files as JSON object with file paths as keys and code content as values. For Node.js, typically include {"index.js": "exports.handler = async (event) => { return { statusCode: 200, body: JSON.stringify({ message: \"Hello World\" }) }; };"}. For Python, include {"lambda_function.py": "def lambda_handler(event, context): return { \"statusCode\": 200, \"body\": \"Hello World\" }"}. The code object must contain at least one file with non-empty string content.',
|
||||
},
|
||||
|
||||
timeout: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
optionalToolInput: true,
|
||||
description:
|
||||
'Function timeout in seconds. Must be between 1 and 900 seconds (15 minutes). Default is 3 seconds.',
|
||||
default: 3,
|
||||
},
|
||||
memorySize: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
optionalToolInput: true,
|
||||
description:
|
||||
'Function memory size in MB. Must be between 128 and 10240 MB. More memory also means more CPU power. Default is 128 MB.',
|
||||
default: 128,
|
||||
},
|
||||
environmentVariables: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
description:
|
||||
'Environment variables for the function. These will be available to your function during execution. Example: {"API_KEY": "your-api-key", "ENVIRONMENT": "production"}.',
|
||||
default: {},
|
||||
},
|
||||
tags: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
description:
|
||||
'Tags for the function. Useful for organization and cost tracking. Example: {"Environment": "production", "Project": "my-app"}.',
|
||||
default: {},
|
||||
},
|
||||
endpointName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
optionalToolInput: true,
|
||||
description:
|
||||
'Name of the API Gateway endpoint to create or update. This will be used to create the API Gateway and will appear in the endpoint URL.',
|
||||
},
|
||||
},
|
||||
|
||||
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,
|
||||
role: params.role,
|
||||
functionName: params.functionName,
|
||||
handler: params.handler,
|
||||
runtime: params.runtime,
|
||||
code: typeof params.code === 'string' ? params.code : JSON.stringify(params.code),
|
||||
|
||||
timeout: params.timeout || 30,
|
||||
memorySize: params.memorySize || 128,
|
||||
environmentVariables: params.environmentVariables || {},
|
||||
tags: params.tags || {},
|
||||
}),
|
||||
},
|
||||
}
|
||||
95
apps/sim/tools/aws_lambda/deploy_endpoint.ts
Normal file
95
apps/sim/tools/aws_lambda/deploy_endpoint.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import type { ToolConfig } from '../types'
|
||||
|
||||
interface AWSLambdaDeployEndpointParams {
|
||||
accessKeyId: string
|
||||
secretAccessKey: string
|
||||
region: string
|
||||
role: string
|
||||
functionName: string
|
||||
endpointName: string
|
||||
}
|
||||
|
||||
interface AWSLambdaDeployEndpointResponse {
|
||||
functionArn: string
|
||||
functionName: string
|
||||
endpointName: string
|
||||
endpointUrl: string
|
||||
region: string
|
||||
status: string
|
||||
lastModified: string
|
||||
apiGatewayId: string
|
||||
stageName: string
|
||||
}
|
||||
|
||||
export const awsLambdaDeployEndpointTool: ToolConfig<
|
||||
AWSLambdaDeployEndpointParams,
|
||||
AWSLambdaDeployEndpointResponse
|
||||
> = {
|
||||
id: 'aws_lambda_deploy_endpoint',
|
||||
name: 'AWS Lambda Deploy Endpoint',
|
||||
description:
|
||||
'Deploy an AWS Lambda function as an HTTP endpoint using API Gateway. This tool creates or updates an API Gateway REST API and connects it to the specified Lambda function, making it accessible via HTTP requests. The endpoint will be publicly accessible and can handle GET, POST, PUT, DELETE, and other HTTP methods. This is useful for creating web APIs, webhooks, or any HTTP-based service using Lambda functions.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
// Common AWS parameters (always at the top)
|
||||
accessKeyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
requiredForToolCall: true,
|
||||
description: 'AWS Access Key ID for authentication. This is required to access AWS services.',
|
||||
},
|
||||
secretAccessKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
requiredForToolCall: true,
|
||||
description:
|
||||
'AWS Secret Access Key for authentication. This is required to access AWS services.',
|
||||
},
|
||||
region: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
requiredForToolCall: true,
|
||||
description:
|
||||
'AWS region where the Lambda function and API Gateway will be deployed. Examples: us-east-1, eu-west-1, ap-southeast-2',
|
||||
},
|
||||
role: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
requiredForToolCall: true,
|
||||
description:
|
||||
'IAM Role ARN that the Lambda function will assume during execution. This role must have appropriate permissions for the function to operate correctly and be invoked by API Gateway.',
|
||||
},
|
||||
// Operation-specific parameters
|
||||
functionName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
requiredForToolCall: true,
|
||||
description:
|
||||
'Name of the existing Lambda function to deploy as an endpoint. This function must already exist in the specified region and be properly configured to handle HTTP requests.',
|
||||
},
|
||||
endpointName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
requiredForToolCall: true,
|
||||
description:
|
||||
'Name for the API Gateway endpoint. This will be used to create the API Gateway REST API and will appear in the endpoint URL. Should be descriptive and unique within your AWS account.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/aws-lambda/deploy-endpoint',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params: AWSLambdaDeployEndpointParams) => ({
|
||||
accessKeyId: params.accessKeyId,
|
||||
secretAccessKey: params.secretAccessKey,
|
||||
region: params.region,
|
||||
role: params.role,
|
||||
functionName: params.functionName,
|
||||
endpointName: params.endpointName,
|
||||
}),
|
||||
},
|
||||
}
|
||||
89
apps/sim/tools/aws_lambda/fetch.ts
Normal file
89
apps/sim/tools/aws_lambda/fetch.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import type { ToolConfig } from '../types'
|
||||
|
||||
interface AWSLambdaFetchParams {
|
||||
accessKeyId: string
|
||||
secretAccessKey: string
|
||||
region: string
|
||||
functionName: string
|
||||
role: string
|
||||
}
|
||||
|
||||
interface AWSLambdaFetchResponse {
|
||||
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>
|
||||
codeFiles: Record<string, string>
|
||||
handler: string
|
||||
role: string
|
||||
}
|
||||
|
||||
export const awsLambdaFetchTool: ToolConfig<AWSLambdaFetchParams, AWSLambdaFetchResponse> = {
|
||||
id: 'aws_lambda_fetch',
|
||||
name: 'AWS Lambda Fetch',
|
||||
description:
|
||||
'Fetch AWS Lambda function details, configuration, and code files. Use this to retrieve information about an existing Lambda function including its runtime, handler, timeout, memory settings, environment variables, tags, and actual code files. This is used to understand the current state of a function before making changes. The fetch operation is read-only and does not modify the function.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
// Common AWS parameters (always at the top)
|
||||
accessKeyId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
requiredForToolCall: true,
|
||||
description: 'AWS Access Key ID for authentication. This is required to access AWS services.',
|
||||
},
|
||||
secretAccessKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
requiredForToolCall: true,
|
||||
description:
|
||||
'AWS Secret Access Key for authentication. This is required to access AWS services.',
|
||||
},
|
||||
region: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
requiredForToolCall: true,
|
||||
description:
|
||||
'AWS region where the Lambda function is located. Examples: us-east-1, eu-west-1, ap-southeast-2',
|
||||
},
|
||||
role: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
requiredForToolCall: true,
|
||||
description:
|
||||
'IAM Role ARN that the Lambda function will assume during execution. This role must have appropriate permissions for the function to operate correctly.',
|
||||
},
|
||||
// Operation-specific parameters
|
||||
functionName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
optionalToolInput: true,
|
||||
description:
|
||||
'Name of the existing Lambda function to fetch and understand. This must be the exact name of a function that already exists in the specified region. Use this to retrieve the current state before making changes.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/aws-lambda/fetch',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params: AWSLambdaFetchParams) => ({
|
||||
accessKeyId: params.accessKeyId,
|
||||
secretAccessKey: params.secretAccessKey,
|
||||
region: params.region,
|
||||
functionName: params.functionName,
|
||||
role: params.role,
|
||||
}),
|
||||
},
|
||||
}
|
||||
32
apps/sim/tools/aws_lambda/get_prompts.ts
Normal file
32
apps/sim/tools/aws_lambda/get_prompts.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { ToolConfig } from '../types'
|
||||
|
||||
type AWSLambdaGetPromptsParams = {}
|
||||
|
||||
interface AWSLambdaGetPromptsResponse {
|
||||
systemPrompt: string
|
||||
schema: Record<string, any>
|
||||
}
|
||||
|
||||
export const awsLambdaGetPromptsTool: ToolConfig<
|
||||
AWSLambdaGetPromptsParams,
|
||||
AWSLambdaGetPromptsResponse
|
||||
> = {
|
||||
id: 'aws_lambda_get_prompts',
|
||||
name: 'AWS Lambda Get Prompts',
|
||||
description:
|
||||
'Get system prompt and schema for AWS Lambda operations. This tool provides AI assistance prompts and schemas to help with Lambda function development, including best practices, common patterns, and code examples.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
// No parameters needed for this operation
|
||||
},
|
||||
|
||||
request: {
|
||||
url: '/api/tools/aws-lambda/get-prompts',
|
||||
method: 'POST',
|
||||
headers: () => ({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: () => ({}), // No body needed
|
||||
},
|
||||
}
|
||||
4
apps/sim/tools/aws_lambda/index.ts
Normal file
4
apps/sim/tools/aws_lambda/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { awsLambdaDeployTool } from './deploy'
|
||||
export { awsLambdaDeployEndpointTool } from './deploy_endpoint'
|
||||
export { awsLambdaFetchTool } from './fetch'
|
||||
export { awsLambdaGetPromptsTool } from './get_prompts'
|
||||
@@ -48,6 +48,9 @@ export async function executeTool(
|
||||
|
||||
// If we have a credential parameter, fetch the access token
|
||||
if (contextParams.credential) {
|
||||
logger.info(
|
||||
`[${requestId}] Tool ${toolId} needs access token for credential: ${contextParams.credential}`
|
||||
)
|
||||
try {
|
||||
const baseUrl = env.NEXT_PUBLIC_APP_URL
|
||||
if (!baseUrl) {
|
||||
@@ -69,6 +72,8 @@ export async function executeTool(
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`[${requestId}] Fetching access token from ${baseUrl}/api/auth/oauth/token`)
|
||||
|
||||
const tokenUrl = new URL('/api/auth/oauth/token', baseUrl).toString()
|
||||
const response = await fetch(tokenUrl, {
|
||||
method: 'POST',
|
||||
@@ -88,6 +93,10 @@ export async function executeTool(
|
||||
const data = await response.json()
|
||||
contextParams.accessToken = data.accessToken
|
||||
|
||||
logger.info(
|
||||
`[${requestId}] Successfully got access token for ${toolId}, length: ${data.accessToken?.length || 0}`
|
||||
)
|
||||
|
||||
// Clean up params we don't need to pass to the actual tool
|
||||
contextParams.credential = undefined
|
||||
if (contextParams.workflowId) contextParams.workflowId = undefined
|
||||
|
||||
@@ -7,6 +7,12 @@ export const getCommentsTool: ToolConfig<RedditCommentsParams, RedditCommentsRes
|
||||
description: 'Fetch comments from a specific Reddit post',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'reddit',
|
||||
additionalScopes: ['read'],
|
||||
},
|
||||
|
||||
params: {
|
||||
postId: {
|
||||
type: 'string',
|
||||
@@ -38,15 +44,21 @@ export const getCommentsTool: ToolConfig<RedditCommentsParams, RedditCommentsRes
|
||||
const sort = params.sort || 'confidence'
|
||||
const limit = Math.min(Math.max(1, params.limit || 50), 100)
|
||||
|
||||
// Build URL
|
||||
return `https://www.reddit.com/r/${subreddit}/comments/${params.postId}.json?sort=${sort}&limit=${limit}&raw_json=1`
|
||||
// Build URL using OAuth endpoint
|
||||
return `https://oauth.reddit.com/r/${subreddit}/comments/${params.postId}?sort=${sort}&limit=${limit}&raw_json=1`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36',
|
||||
Accept: 'application/json',
|
||||
}),
|
||||
headers: (params: RedditCommentsParams) => {
|
||||
if (!params.accessToken?.trim()) {
|
||||
throw new Error('Access token is required for Reddit API')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'User-Agent': 'sim-studio/1.0 (https://github.com/simstudioai/sim)',
|
||||
Accept: 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, requestParams?: RedditCommentsParams) => {
|
||||
|
||||
@@ -7,6 +7,12 @@ export const getPostsTool: ToolConfig<RedditPostsParams, RedditPostsResponse> =
|
||||
description: 'Fetch posts from a subreddit with different sorting options',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'reddit',
|
||||
additionalScopes: ['read'],
|
||||
},
|
||||
|
||||
params: {
|
||||
subreddit: {
|
||||
type: 'string',
|
||||
@@ -38,8 +44,8 @@ export const getPostsTool: ToolConfig<RedditPostsParams, RedditPostsResponse> =
|
||||
const sort = params.sort || 'hot'
|
||||
const limit = Math.min(Math.max(1, params.limit || 10), 100)
|
||||
|
||||
// Build URL with appropriate parameters
|
||||
let url = `https://www.reddit.com/r/${subreddit}/${sort}.json?limit=${limit}&raw_json=1`
|
||||
// Build URL with appropriate parameters using OAuth endpoint
|
||||
let url = `https://oauth.reddit.com/r/${subreddit}/${sort}?limit=${limit}&raw_json=1`
|
||||
|
||||
// Add time parameter only for 'top' sorting
|
||||
if (sort === 'top' && params.time) {
|
||||
@@ -49,29 +55,54 @@ export const getPostsTool: ToolConfig<RedditPostsParams, RedditPostsResponse> =
|
||||
return url
|
||||
},
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36',
|
||||
Accept: 'application/json',
|
||||
}),
|
||||
headers: (params: RedditPostsParams) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required for Reddit API')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'User-Agent': 'sim-studio/1.0 (https://github.com/simstudioai/sim)',
|
||||
Accept: 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, requestParams?: RedditPostsParams) => {
|
||||
try {
|
||||
// Check if response is OK
|
||||
if (!response.ok) {
|
||||
// Get response text for better error details
|
||||
const errorText = await response.text()
|
||||
console.error('Reddit API Error:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
body: errorText,
|
||||
url: response.url,
|
||||
})
|
||||
|
||||
if (response.status === 403 || response.status === 429) {
|
||||
throw new Error('Reddit API access blocked or rate limited. Please try again later.')
|
||||
}
|
||||
throw new Error(`Reddit API returned ${response.status}: ${response.statusText}`)
|
||||
throw new Error(
|
||||
`Reddit API returned ${response.status}: ${response.statusText}. Body: ${errorText}`
|
||||
)
|
||||
}
|
||||
|
||||
// Attempt to parse JSON
|
||||
let data
|
||||
try {
|
||||
data = await response.json()
|
||||
} catch (_error) {
|
||||
throw new Error('Failed to parse Reddit API response: Response was not valid JSON')
|
||||
} catch (error) {
|
||||
const responseText = await response.text()
|
||||
console.error('Failed to parse Reddit API response as JSON:', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
responseText,
|
||||
contentType: response.headers.get('content-type'),
|
||||
})
|
||||
throw new Error(
|
||||
`Failed to parse Reddit API response: Response was not valid JSON. Content: ${responseText}`
|
||||
)
|
||||
}
|
||||
|
||||
// Check if response contains error
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { RedditHotPostsResponse, RedditPost } from './types'
|
||||
interface HotPostsParams {
|
||||
subreddit: string
|
||||
limit?: number
|
||||
accessToken: string
|
||||
}
|
||||
|
||||
export const hotPostsTool: ToolConfig<HotPostsParams, RedditHotPostsResponse> = {
|
||||
@@ -12,6 +13,12 @@ export const hotPostsTool: ToolConfig<HotPostsParams, RedditHotPostsResponse> =
|
||||
description: 'Fetch the most popular (hot) posts from a specified subreddit.',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'reddit',
|
||||
additionalScopes: ['read'],
|
||||
},
|
||||
|
||||
params: {
|
||||
subreddit: {
|
||||
type: 'string',
|
||||
@@ -31,14 +38,20 @@ export const hotPostsTool: ToolConfig<HotPostsParams, RedditHotPostsResponse> =
|
||||
const subreddit = params.subreddit.trim().replace(/^r\//, '')
|
||||
const limit = Math.min(Math.max(1, params.limit || 10), 100)
|
||||
|
||||
return `https://www.reddit.com/r/${subreddit}/hot.json?limit=${limit}&raw_json=1`
|
||||
return `https://oauth.reddit.com/r/${subreddit}/hot?limit=${limit}&raw_json=1`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: () => ({
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36',
|
||||
Accept: 'application/json',
|
||||
}),
|
||||
headers: (params: HotPostsParams) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required for Reddit API')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'User-Agent': 'sim-studio/1.0 (https://github.com/simstudioai/sim)',
|
||||
Accept: 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, requestParams?: HotPostsParams) => {
|
||||
|
||||
@@ -39,6 +39,7 @@ export interface RedditPostsParams {
|
||||
sort?: 'hot' | 'new' | 'top' | 'rising'
|
||||
limit?: number
|
||||
time?: 'day' | 'week' | 'month' | 'year' | 'all'
|
||||
accessToken?: string
|
||||
}
|
||||
|
||||
// Response for the generalized get_posts tool
|
||||
@@ -55,6 +56,7 @@ export interface RedditCommentsParams {
|
||||
subreddit: string
|
||||
sort?: 'confidence' | 'top' | 'new' | 'controversial' | 'old' | 'random' | 'qa'
|
||||
limit?: number
|
||||
accessToken?: string
|
||||
}
|
||||
|
||||
// Response for the get_comments tool
|
||||
|
||||
@@ -5,6 +5,12 @@ import {
|
||||
airtableUpdateRecordTool,
|
||||
} from './airtable'
|
||||
import { autoblocksPromptManagerTool } from './autoblocks'
|
||||
import {
|
||||
awsLambdaDeployEndpointTool,
|
||||
awsLambdaDeployTool,
|
||||
awsLambdaFetchTool,
|
||||
awsLambdaGetPromptsTool,
|
||||
} from './aws_lambda'
|
||||
import { browserUseRunTaskTool } from './browser_use'
|
||||
import { clayPopulateTool } from './clay'
|
||||
import { confluenceRetrieveTool, confluenceUpdateTool } from './confluence'
|
||||
@@ -223,4 +229,8 @@ export const tools: Record<string, ToolConfig> = {
|
||||
google_calendar_quick_add: googleCalendarQuickAddTool,
|
||||
google_calendar_invite: googleCalendarInviteTool,
|
||||
workflow_executor: workflowExecutorTool,
|
||||
aws_lambda_deploy: awsLambdaDeployTool,
|
||||
aws_lambda_deploy_endpoint: awsLambdaDeployEndpointTool,
|
||||
aws_lambda_fetch: awsLambdaFetchTool,
|
||||
aws_lambda_get_prompts: awsLambdaGetPromptsTool,
|
||||
}
|
||||
|
||||
202
bun.lock
202
bun.lock
@@ -34,7 +34,7 @@
|
||||
"fumadocs-mdx": "^11.5.6",
|
||||
"fumadocs-ui": "^15.0.16",
|
||||
"lucide-react": "^0.511.0",
|
||||
"next": "^15.2.3",
|
||||
"next": "^15.3.2",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
@@ -57,8 +57,10 @@
|
||||
"version": "0.1.0",
|
||||
"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-apigatewayv2": "3.840.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",
|
||||
@@ -99,6 +101,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",
|
||||
@@ -116,6 +119,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",
|
||||
@@ -274,69 +278,73 @@
|
||||
|
||||
"@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="],
|
||||
|
||||
"@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.832.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.826.0", "@aws-sdk/credential-provider-node": "3.830.0", "@aws-sdk/middleware-bucket-endpoint": "3.830.0", "@aws-sdk/middleware-expect-continue": "3.821.0", "@aws-sdk/middleware-flexible-checksums": "3.826.0", "@aws-sdk/middleware-host-header": "3.821.0", "@aws-sdk/middleware-location-constraint": "3.821.0", "@aws-sdk/middleware-logger": "3.821.0", "@aws-sdk/middleware-recursion-detection": "3.821.0", "@aws-sdk/middleware-sdk-s3": "3.826.0", "@aws-sdk/middleware-ssec": "3.821.0", "@aws-sdk/middleware-user-agent": "3.828.0", "@aws-sdk/region-config-resolver": "3.821.0", "@aws-sdk/signature-v4-multi-region": "3.826.0", "@aws-sdk/types": "3.821.0", "@aws-sdk/util-endpoints": "3.828.0", "@aws-sdk/util-user-agent-browser": "3.821.0", "@aws-sdk/util-user-agent-node": "3.828.0", "@aws-sdk/xml-builder": "3.821.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.5.3", "@smithy/eventstream-serde-browser": "^4.0.4", "@smithy/eventstream-serde-config-resolver": "^4.1.2", "@smithy/eventstream-serde-node": "^4.0.4", "@smithy/fetch-http-handler": "^5.0.4", "@smithy/hash-blob-browser": "^4.0.4", "@smithy/hash-node": "^4.0.4", "@smithy/hash-stream-node": "^4.0.4", "@smithy/invalid-dependency": "^4.0.4", "@smithy/md5-js": "^4.0.4", "@smithy/middleware-content-length": "^4.0.4", "@smithy/middleware-endpoint": "^4.1.11", "@smithy/middleware-retry": "^4.1.12", "@smithy/middleware-serde": "^4.0.8", "@smithy/middleware-stack": "^4.0.4", "@smithy/node-config-provider": "^4.1.3", "@smithy/node-http-handler": "^4.0.6", "@smithy/protocol-http": "^5.1.2", "@smithy/smithy-client": "^4.4.3", "@smithy/types": "^4.3.1", "@smithy/url-parser": "^4.0.4", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.19", "@smithy/util-defaults-mode-node": "^4.0.19", "@smithy/util-endpoints": "^3.0.6", "@smithy/util-middleware": "^4.0.4", "@smithy/util-retry": "^4.0.5", "@smithy/util-stream": "^4.2.2", "@smithy/util-utf8": "^4.0.0", "@smithy/util-waiter": "^4.0.5", "@types/uuid": "^9.0.1", "tslib": "^2.6.2", "uuid": "^9.0.1" } }, "sha512-S+md1zCe71SEuaRDuLHq4mzhYYkVxR1ENa8NwrgInfYoC4xo8/pESoR6i0ZZpcLs0Jw4EyVInWYs4GgDHW70qQ=="],
|
||||
"@aws-sdk/client-apigatewayv2": ["@aws-sdk/client-apigatewayv2@3.840.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.840.0", "@aws-sdk/credential-provider-node": "3.840.0", "@aws-sdk/middleware-host-header": "3.840.0", "@aws-sdk/middleware-logger": "3.840.0", "@aws-sdk/middleware-recursion-detection": "3.840.0", "@aws-sdk/middleware-user-agent": "3.840.0", "@aws-sdk/region-config-resolver": "3.840.0", "@aws-sdk/types": "3.840.0", "@aws-sdk/util-endpoints": "3.840.0", "@aws-sdk/util-user-agent-browser": "3.840.0", "@aws-sdk/util-user-agent-node": "3.840.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.6.0", "@smithy/fetch-http-handler": "^5.0.4", "@smithy/hash-node": "^4.0.4", "@smithy/invalid-dependency": "^4.0.4", "@smithy/middleware-content-length": "^4.0.4", "@smithy/middleware-endpoint": "^4.1.13", "@smithy/middleware-retry": "^4.1.14", "@smithy/middleware-serde": "^4.0.8", "@smithy/middleware-stack": "^4.0.4", "@smithy/node-config-provider": "^4.1.3", "@smithy/node-http-handler": "^4.0.6", "@smithy/protocol-http": "^5.1.2", "@smithy/smithy-client": "^4.4.5", "@smithy/types": "^4.3.1", "@smithy/url-parser": "^4.0.4", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.21", "@smithy/util-defaults-mode-node": "^4.0.21", "@smithy/util-endpoints": "^3.0.6", "@smithy/util-middleware": "^4.0.4", "@smithy/util-retry": "^4.0.6", "@smithy/util-stream": "^4.2.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-r9lIhflnQrhLRX1Z1U5lb2a/fhyG/qs8KZQ7kBVYsXEJAV36pIQTjRlyigDHF0gbV/je9NJpfSA2MdgeRgOyuA=="],
|
||||
|
||||
"@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.830.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.826.0", "@aws-sdk/middleware-host-header": "3.821.0", "@aws-sdk/middleware-logger": "3.821.0", "@aws-sdk/middleware-recursion-detection": "3.821.0", "@aws-sdk/middleware-user-agent": "3.828.0", "@aws-sdk/region-config-resolver": "3.821.0", "@aws-sdk/types": "3.821.0", "@aws-sdk/util-endpoints": "3.828.0", "@aws-sdk/util-user-agent-browser": "3.821.0", "@aws-sdk/util-user-agent-node": "3.828.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.5.3", "@smithy/fetch-http-handler": "^5.0.4", "@smithy/hash-node": "^4.0.4", "@smithy/invalid-dependency": "^4.0.4", "@smithy/middleware-content-length": "^4.0.4", "@smithy/middleware-endpoint": "^4.1.11", "@smithy/middleware-retry": "^4.1.12", "@smithy/middleware-serde": "^4.0.8", "@smithy/middleware-stack": "^4.0.4", "@smithy/node-config-provider": "^4.1.3", "@smithy/node-http-handler": "^4.0.6", "@smithy/protocol-http": "^5.1.2", "@smithy/smithy-client": "^4.4.3", "@smithy/types": "^4.3.1", "@smithy/url-parser": "^4.0.4", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.19", "@smithy/util-defaults-mode-node": "^4.0.19", "@smithy/util-endpoints": "^3.0.6", "@smithy/util-middleware": "^4.0.4", "@smithy/util-retry": "^4.0.5", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-5zCEpfI+zwX2SIa258L+TItNbBoAvQQ6w74qdFM6YJufQ1F9tvwjTX8T+eSTT9nsFIvfYnUaGalWwJVfmJUgVQ=="],
|
||||
"@aws-sdk/client-lambda": ["@aws-sdk/client-lambda@3.840.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.840.0", "@aws-sdk/credential-provider-node": "3.840.0", "@aws-sdk/middleware-host-header": "3.840.0", "@aws-sdk/middleware-logger": "3.840.0", "@aws-sdk/middleware-recursion-detection": "3.840.0", "@aws-sdk/middleware-user-agent": "3.840.0", "@aws-sdk/region-config-resolver": "3.840.0", "@aws-sdk/types": "3.840.0", "@aws-sdk/util-endpoints": "3.840.0", "@aws-sdk/util-user-agent-browser": "3.840.0", "@aws-sdk/util-user-agent-node": "3.840.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.6.0", "@smithy/eventstream-serde-browser": "^4.0.4", "@smithy/eventstream-serde-config-resolver": "^4.1.2", "@smithy/eventstream-serde-node": "^4.0.4", "@smithy/fetch-http-handler": "^5.0.4", "@smithy/hash-node": "^4.0.4", "@smithy/invalid-dependency": "^4.0.4", "@smithy/middleware-content-length": "^4.0.4", "@smithy/middleware-endpoint": "^4.1.13", "@smithy/middleware-retry": "^4.1.14", "@smithy/middleware-serde": "^4.0.8", "@smithy/middleware-stack": "^4.0.4", "@smithy/node-config-provider": "^4.1.3", "@smithy/node-http-handler": "^4.0.6", "@smithy/protocol-http": "^5.1.2", "@smithy/smithy-client": "^4.4.5", "@smithy/types": "^4.3.1", "@smithy/url-parser": "^4.0.4", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.21", "@smithy/util-defaults-mode-node": "^4.0.21", "@smithy/util-endpoints": "^3.0.6", "@smithy/util-middleware": "^4.0.4", "@smithy/util-retry": "^4.0.6", "@smithy/util-stream": "^4.2.2", "@smithy/util-utf8": "^4.0.0", "@smithy/util-waiter": "^4.0.6", "tslib": "^2.6.2" } }, "sha512-aUKHKWW4Z1nxQ0q/shHkSA278oyv+lRJSvpin1GJXQumDdMKcOuXktmufOCZzjbl6UVw/Pqaw6V1Vo2gda6RdQ=="],
|
||||
|
||||
"@aws-sdk/core": ["@aws-sdk/core@3.826.0", "", { "dependencies": { "@aws-sdk/types": "3.821.0", "@aws-sdk/xml-builder": "3.821.0", "@smithy/core": "^3.5.3", "@smithy/node-config-provider": "^4.1.3", "@smithy/property-provider": "^4.0.4", "@smithy/protocol-http": "^5.1.2", "@smithy/signature-v4": "^5.1.2", "@smithy/smithy-client": "^4.4.3", "@smithy/types": "^4.3.1", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-middleware": "^4.0.4", "@smithy/util-utf8": "^4.0.0", "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" } }, "sha512-BGbQYzWj3ps+dblq33FY5tz/SsgJCcXX0zjQlSC07tYvU1jHTUvsefphyig+fY38xZ4wdKjbTop+KUmXUYrOXw=="],
|
||||
"@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.842.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.840.0", "@aws-sdk/credential-provider-node": "3.840.0", "@aws-sdk/middleware-bucket-endpoint": "3.840.0", "@aws-sdk/middleware-expect-continue": "3.840.0", "@aws-sdk/middleware-flexible-checksums": "3.840.0", "@aws-sdk/middleware-host-header": "3.840.0", "@aws-sdk/middleware-location-constraint": "3.840.0", "@aws-sdk/middleware-logger": "3.840.0", "@aws-sdk/middleware-recursion-detection": "3.840.0", "@aws-sdk/middleware-sdk-s3": "3.840.0", "@aws-sdk/middleware-ssec": "3.840.0", "@aws-sdk/middleware-user-agent": "3.840.0", "@aws-sdk/region-config-resolver": "3.840.0", "@aws-sdk/signature-v4-multi-region": "3.840.0", "@aws-sdk/types": "3.840.0", "@aws-sdk/util-endpoints": "3.840.0", "@aws-sdk/util-user-agent-browser": "3.840.0", "@aws-sdk/util-user-agent-node": "3.840.0", "@aws-sdk/xml-builder": "3.821.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.6.0", "@smithy/eventstream-serde-browser": "^4.0.4", "@smithy/eventstream-serde-config-resolver": "^4.1.2", "@smithy/eventstream-serde-node": "^4.0.4", "@smithy/fetch-http-handler": "^5.0.4", "@smithy/hash-blob-browser": "^4.0.4", "@smithy/hash-node": "^4.0.4", "@smithy/hash-stream-node": "^4.0.4", "@smithy/invalid-dependency": "^4.0.4", "@smithy/md5-js": "^4.0.4", "@smithy/middleware-content-length": "^4.0.4", "@smithy/middleware-endpoint": "^4.1.13", "@smithy/middleware-retry": "^4.1.14", "@smithy/middleware-serde": "^4.0.8", "@smithy/middleware-stack": "^4.0.4", "@smithy/node-config-provider": "^4.1.3", "@smithy/node-http-handler": "^4.0.6", "@smithy/protocol-http": "^5.1.2", "@smithy/smithy-client": "^4.4.5", "@smithy/types": "^4.3.1", "@smithy/url-parser": "^4.0.4", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.21", "@smithy/util-defaults-mode-node": "^4.0.21", "@smithy/util-endpoints": "^3.0.6", "@smithy/util-middleware": "^4.0.4", "@smithy/util-retry": "^4.0.6", "@smithy/util-stream": "^4.2.2", "@smithy/util-utf8": "^4.0.0", "@smithy/util-waiter": "^4.0.6", "@types/uuid": "^9.0.1", "tslib": "^2.6.2", "uuid": "^9.0.1" } }, "sha512-T5Rh72Rcq1xIaM8KkTr1Wpr7/WPCYO++KrM+/Em0rq2jxpjMMhj77ITpgH7eEmNxWmwIndTwqpgfmbpNfk7Gbw=="],
|
||||
|
||||
"@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.826.0", "", { "dependencies": { "@aws-sdk/core": "3.826.0", "@aws-sdk/types": "3.821.0", "@smithy/property-provider": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-DK3pQY8+iKK3MGDdC3uOZQ2psU01obaKlTYhEwNu4VWzgwQL4Vi3sWj4xSWGEK41vqZxiRLq6fOq7ysRI+qEZA=="],
|
||||
"@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.840.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.840.0", "@aws-sdk/middleware-host-header": "3.840.0", "@aws-sdk/middleware-logger": "3.840.0", "@aws-sdk/middleware-recursion-detection": "3.840.0", "@aws-sdk/middleware-user-agent": "3.840.0", "@aws-sdk/region-config-resolver": "3.840.0", "@aws-sdk/types": "3.840.0", "@aws-sdk/util-endpoints": "3.840.0", "@aws-sdk/util-user-agent-browser": "3.840.0", "@aws-sdk/util-user-agent-node": "3.840.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.6.0", "@smithy/fetch-http-handler": "^5.0.4", "@smithy/hash-node": "^4.0.4", "@smithy/invalid-dependency": "^4.0.4", "@smithy/middleware-content-length": "^4.0.4", "@smithy/middleware-endpoint": "^4.1.13", "@smithy/middleware-retry": "^4.1.14", "@smithy/middleware-serde": "^4.0.8", "@smithy/middleware-stack": "^4.0.4", "@smithy/node-config-provider": "^4.1.3", "@smithy/node-http-handler": "^4.0.6", "@smithy/protocol-http": "^5.1.2", "@smithy/smithy-client": "^4.4.5", "@smithy/types": "^4.3.1", "@smithy/url-parser": "^4.0.4", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.21", "@smithy/util-defaults-mode-node": "^4.0.21", "@smithy/util-endpoints": "^3.0.6", "@smithy/util-middleware": "^4.0.4", "@smithy/util-retry": "^4.0.6", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-3Zp+FWN2hhmKdpS0Ragi5V2ZPsZNScE3jlbgoJjzjI/roHZqO+e3/+XFN4TlM0DsPKYJNp+1TAjmhxN6rOnfYA=="],
|
||||
|
||||
"@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.826.0", "", { "dependencies": { "@aws-sdk/core": "3.826.0", "@aws-sdk/types": "3.821.0", "@smithy/fetch-http-handler": "^5.0.4", "@smithy/node-http-handler": "^4.0.6", "@smithy/property-provider": "^4.0.4", "@smithy/protocol-http": "^5.1.2", "@smithy/smithy-client": "^4.4.3", "@smithy/types": "^4.3.1", "@smithy/util-stream": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-N+IVZBh+yx/9GbMZTKO/gErBi/FYZQtcFRItoLbY+6WU+0cSWyZYfkoeOxHmQV3iX9k65oljERIWUmL9x6OSQg=="],
|
||||
"@aws-sdk/core": ["@aws-sdk/core@3.840.0", "", { "dependencies": { "@aws-sdk/types": "3.840.0", "@aws-sdk/xml-builder": "3.821.0", "@smithy/core": "^3.6.0", "@smithy/node-config-provider": "^4.1.3", "@smithy/property-provider": "^4.0.4", "@smithy/protocol-http": "^5.1.2", "@smithy/signature-v4": "^5.1.2", "@smithy/smithy-client": "^4.4.5", "@smithy/types": "^4.3.1", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-middleware": "^4.0.4", "@smithy/util-utf8": "^4.0.0", "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" } }, "sha512-x3Zgb39tF1h2XpU+yA4OAAQlW6LVEfXNlSedSYJ7HGKXqA/E9h3rWQVpYfhXXVVsLdYXdNw5KBUkoAoruoZSZA=="],
|
||||
|
||||
"@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.830.0", "", { "dependencies": { "@aws-sdk/core": "3.826.0", "@aws-sdk/credential-provider-env": "3.826.0", "@aws-sdk/credential-provider-http": "3.826.0", "@aws-sdk/credential-provider-process": "3.826.0", "@aws-sdk/credential-provider-sso": "3.830.0", "@aws-sdk/credential-provider-web-identity": "3.830.0", "@aws-sdk/nested-clients": "3.830.0", "@aws-sdk/types": "3.821.0", "@smithy/credential-provider-imds": "^4.0.6", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-zeQenzvh8JRY5nULd8izdjVGoCM1tgsVVsrLSwDkHxZTTW0hW/bmOmXfvdaE0wDdomXW7m2CkQDSmP7XdvNXZg=="],
|
||||
"@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.840.0", "", { "dependencies": { "@aws-sdk/core": "3.840.0", "@aws-sdk/types": "3.840.0", "@smithy/property-provider": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-EzF6VcJK7XvQ/G15AVEfJzN2mNXU8fcVpXo4bRyr1S6t2q5zx6UPH/XjDbn18xyUmOq01t+r8gG+TmHEVo18fA=="],
|
||||
|
||||
"@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.830.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.826.0", "@aws-sdk/credential-provider-http": "3.826.0", "@aws-sdk/credential-provider-ini": "3.830.0", "@aws-sdk/credential-provider-process": "3.826.0", "@aws-sdk/credential-provider-sso": "3.830.0", "@aws-sdk/credential-provider-web-identity": "3.830.0", "@aws-sdk/types": "3.821.0", "@smithy/credential-provider-imds": "^4.0.6", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-X/2LrTgwtK1pkWrvofxQBI8VTi6QVLtSMpsKKPPnJQ0vgqC0e4czSIs3ZxiEsOkCBaQ2usXSiKyh0ccsQ6k2OA=="],
|
||||
"@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.840.0", "", { "dependencies": { "@aws-sdk/core": "3.840.0", "@aws-sdk/types": "3.840.0", "@smithy/fetch-http-handler": "^5.0.4", "@smithy/node-http-handler": "^4.0.6", "@smithy/property-provider": "^4.0.4", "@smithy/protocol-http": "^5.1.2", "@smithy/smithy-client": "^4.4.5", "@smithy/types": "^4.3.1", "@smithy/util-stream": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-wbnUiPGLVea6mXbUh04fu+VJmGkQvmToPeTYdHE8eRZq3NRDi3t3WltT+jArLBKD/4NppRpMjf2ju4coMCz91g=="],
|
||||
|
||||
"@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.826.0", "", { "dependencies": { "@aws-sdk/core": "3.826.0", "@aws-sdk/types": "3.821.0", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-kURrc4amu3NLtw1yZw7EoLNEVhmOMRUTs+chaNcmS+ERm3yK0nKjaJzmKahmwlTQTSl3wJ8jjK7x962VPo+zWw=="],
|
||||
"@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.840.0", "", { "dependencies": { "@aws-sdk/core": "3.840.0", "@aws-sdk/credential-provider-env": "3.840.0", "@aws-sdk/credential-provider-http": "3.840.0", "@aws-sdk/credential-provider-process": "3.840.0", "@aws-sdk/credential-provider-sso": "3.840.0", "@aws-sdk/credential-provider-web-identity": "3.840.0", "@aws-sdk/nested-clients": "3.840.0", "@aws-sdk/types": "3.840.0", "@smithy/credential-provider-imds": "^4.0.6", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-7F290BsWydShHb+7InXd+IjJc3mlEIm9I0R57F/Pjl1xZB69MdkhVGCnuETWoBt4g53ktJd6NEjzm/iAhFXFmw=="],
|
||||
|
||||
"@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.830.0", "", { "dependencies": { "@aws-sdk/client-sso": "3.830.0", "@aws-sdk/core": "3.826.0", "@aws-sdk/token-providers": "3.830.0", "@aws-sdk/types": "3.821.0", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-+VdRpZmfekzpySqZikAKx6l5ndnLGluioIgUG4ZznrButgFD/iogzFtGmBDFB3ZLViX1l4pMXru0zFwJEZT21Q=="],
|
||||
"@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.840.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.840.0", "@aws-sdk/credential-provider-http": "3.840.0", "@aws-sdk/credential-provider-ini": "3.840.0", "@aws-sdk/credential-provider-process": "3.840.0", "@aws-sdk/credential-provider-sso": "3.840.0", "@aws-sdk/credential-provider-web-identity": "3.840.0", "@aws-sdk/types": "3.840.0", "@smithy/credential-provider-imds": "^4.0.6", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-KufP8JnxA31wxklLm63evUPSFApGcH8X86z3mv9SRbpCm5ycgWIGVCTXpTOdgq6rPZrwT9pftzv2/b4mV/9clg=="],
|
||||
|
||||
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.830.0", "", { "dependencies": { "@aws-sdk/core": "3.826.0", "@aws-sdk/nested-clients": "3.830.0", "@aws-sdk/types": "3.821.0", "@smithy/property-provider": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-hPYrKsZeeOdLROJ59T6Y8yZ0iwC/60L3qhZXjapBFjbqBtMaQiMTI645K6xVXBioA6vxXq7B4aLOhYqk6Fy/Ww=="],
|
||||
"@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.840.0", "", { "dependencies": { "@aws-sdk/core": "3.840.0", "@aws-sdk/types": "3.840.0", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-HkDQWHy8tCI4A0Ps2NVtuVYMv9cB4y/IuD/TdOsqeRIAT12h8jDb98BwQPNLAImAOwOWzZJ8Cu0xtSpX7CQhMw=="],
|
||||
|
||||
"@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.830.0", "", { "dependencies": { "@aws-sdk/types": "3.821.0", "@aws-sdk/util-arn-parser": "3.804.0", "@smithy/node-config-provider": "^4.1.3", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "@smithy/util-config-provider": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-ElVeCReZSH5Ds+/pkL5ebneJjuo8f49e9JXV1cYizuH0OAOQfYaBU9+M+7+rn61pTttOFE8W//qKzrXBBJhfMg=="],
|
||||
"@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.840.0", "", { "dependencies": { "@aws-sdk/client-sso": "3.840.0", "@aws-sdk/core": "3.840.0", "@aws-sdk/token-providers": "3.840.0", "@aws-sdk/types": "3.840.0", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-2qgdtdd6R0Z1y0KL8gzzwFUGmhBHSUx4zy85L2XV1CXhpRNwV71SVWJqLDVV5RVWVf9mg50Pm3AWrUC0xb0pcA=="],
|
||||
|
||||
"@aws-sdk/middleware-expect-continue": ["@aws-sdk/middleware-expect-continue@3.821.0", "", { "dependencies": { "@aws-sdk/types": "3.821.0", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-zAOoSZKe1njOrtynvK6ZORU57YGv5I7KP4+rwOvUN3ZhJbQ7QPf8gKtFUCYAPRMegaXCKF/ADPtDZBAmM+zZ9g=="],
|
||||
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.840.0", "", { "dependencies": { "@aws-sdk/core": "3.840.0", "@aws-sdk/nested-clients": "3.840.0", "@aws-sdk/types": "3.840.0", "@smithy/property-provider": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-dpEeVXG8uNZSmVXReE4WP0lwoioX2gstk4RnUgrdUE3YaPq8A+hJiVAyc3h+cjDeIqfbsQbZm9qFetKC2LF9dQ=="],
|
||||
|
||||
"@aws-sdk/middleware-flexible-checksums": ["@aws-sdk/middleware-flexible-checksums@3.826.0", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", "@aws-sdk/core": "3.826.0", "@aws-sdk/types": "3.821.0", "@smithy/is-array-buffer": "^4.0.0", "@smithy/node-config-provider": "^4.1.3", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "@smithy/util-middleware": "^4.0.4", "@smithy/util-stream": "^4.2.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-Fz9w8CFYPfSlHEB6feSsi06hdS+s+FB8k5pO4L7IV0tUa78mlhxF/VNlAJaVWYyOkZXl4HPH2K48aapACSQOXw=="],
|
||||
"@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.840.0", "", { "dependencies": { "@aws-sdk/types": "3.840.0", "@aws-sdk/util-arn-parser": "3.804.0", "@smithy/node-config-provider": "^4.1.3", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "@smithy/util-config-provider": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-+gkQNtPwcSMmlwBHFd4saVVS11In6ID1HczNzpM3MXKXRBfSlbZJbCt6wN//AZ8HMklZEik4tcEOG0qa9UY8SQ=="],
|
||||
|
||||
"@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.821.0", "", { "dependencies": { "@aws-sdk/types": "3.821.0", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-xSMR+sopSeWGx5/4pAGhhfMvGBHioVBbqGvDs6pG64xfNwM5vq5s5v6D04e2i+uSTj4qGa71dLUs5I0UzAK3sw=="],
|
||||
"@aws-sdk/middleware-expect-continue": ["@aws-sdk/middleware-expect-continue@3.840.0", "", { "dependencies": { "@aws-sdk/types": "3.840.0", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-iJg2r6FKsKKvdiU4oCOuCf7Ro/YE0Q2BT/QyEZN3/Rt8Nr4SAZiQOlcBXOCpGvuIKOEAhvDOUnW3aDHL01PdVw=="],
|
||||
|
||||
"@aws-sdk/middleware-location-constraint": ["@aws-sdk/middleware-location-constraint@3.821.0", "", { "dependencies": { "@aws-sdk/types": "3.821.0", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-sKrm80k0t3R0on8aA/WhWFoMaAl4yvdk+riotmMElLUpcMcRXAd1+600uFVrxJqZdbrKQ0mjX0PjT68DlkYXLg=="],
|
||||
"@aws-sdk/middleware-flexible-checksums": ["@aws-sdk/middleware-flexible-checksums@3.840.0", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", "@aws-sdk/core": "3.840.0", "@aws-sdk/types": "3.840.0", "@smithy/is-array-buffer": "^4.0.0", "@smithy/node-config-provider": "^4.1.3", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "@smithy/util-middleware": "^4.0.4", "@smithy/util-stream": "^4.2.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-Kg/o2G6o72sdoRH0J+avdcf668gM1bp6O4VeEXpXwUj/urQnV5qiB2q1EYT110INHUKWOLXPND3sQAqh6sTqHw=="],
|
||||
|
||||
"@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.821.0", "", { "dependencies": { "@aws-sdk/types": "3.821.0", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-0cvI0ipf2tGx7fXYEEN5fBeZDz2RnHyb9xftSgUsEq7NBxjV0yTZfLJw6Za5rjE6snC80dRN8+bTNR1tuG89zA=="],
|
||||
"@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.840.0", "", { "dependencies": { "@aws-sdk/types": "3.840.0", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-ub+hXJAbAje94+Ya6c6eL7sYujoE8D4Bumu1NUI8TXjUhVVn0HzVWQjpRLshdLsUp1AW7XyeJaxyajRaJQ8+Xg=="],
|
||||
|
||||
"@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.821.0", "", { "dependencies": { "@aws-sdk/types": "3.821.0", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-efmaifbhBoqKG3bAoEfDdcM8hn1psF+4qa7ykWuYmfmah59JBeqHLfz5W9m9JoTwoKPkFcVLWZxnyZzAnVBOIg=="],
|
||||
"@aws-sdk/middleware-location-constraint": ["@aws-sdk/middleware-location-constraint@3.840.0", "", { "dependencies": { "@aws-sdk/types": "3.840.0", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-KVLD0u0YMF3aQkVF8bdyHAGWSUY6N1Du89htTLgqCcIhSxxAJ9qifrosVZ9jkAzqRW99hcufyt2LylcVU2yoKQ=="],
|
||||
|
||||
"@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.826.0", "", { "dependencies": { "@aws-sdk/core": "3.826.0", "@aws-sdk/types": "3.821.0", "@aws-sdk/util-arn-parser": "3.804.0", "@smithy/core": "^3.5.3", "@smithy/node-config-provider": "^4.1.3", "@smithy/protocol-http": "^5.1.2", "@smithy/signature-v4": "^5.1.2", "@smithy/smithy-client": "^4.4.3", "@smithy/types": "^4.3.1", "@smithy/util-config-provider": "^4.0.0", "@smithy/util-middleware": "^4.0.4", "@smithy/util-stream": "^4.2.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-8F0qWaYKfvD/de1AKccXuigM+gb/IZSncCqxdnFWqd+TFzo9qI9Hh+TpUhWOMYSgxsMsYQ8ipmLzlD/lDhjrmA=="],
|
||||
"@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.840.0", "", { "dependencies": { "@aws-sdk/types": "3.840.0", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-lSV8FvjpdllpGaRspywss4CtXV8M7NNNH+2/j86vMH+YCOZ6fu2T/TyFd/tHwZ92vDfHctWkRbQxg0bagqwovA=="],
|
||||
|
||||
"@aws-sdk/middleware-ssec": ["@aws-sdk/middleware-ssec@3.821.0", "", { "dependencies": { "@aws-sdk/types": "3.821.0", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-YYi1Hhr2AYiU/24cQc8HIB+SWbQo6FBkMYojVuz/zgrtkFmALxENGF/21OPg7f/QWd+eadZJRxCjmRwh5F2Cxg=="],
|
||||
"@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.840.0", "", { "dependencies": { "@aws-sdk/types": "3.840.0", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-Gu7lGDyfddyhIkj1Z1JtrY5NHb5+x/CRiB87GjaSrKxkDaydtX2CU977JIABtt69l9wLbcGDIQ+W0uJ5xPof7g=="],
|
||||
|
||||
"@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.828.0", "", { "dependencies": { "@aws-sdk/core": "3.826.0", "@aws-sdk/types": "3.821.0", "@aws-sdk/util-endpoints": "3.828.0", "@smithy/core": "^3.5.3", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-nixvI/SETXRdmrVab4D9LvXT3lrXkwAWGWk2GVvQvzlqN1/M/RfClj+o37Sn4FqRkGH9o9g7Fqb1YqZ4mqDAtA=="],
|
||||
"@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.840.0", "", { "dependencies": { "@aws-sdk/core": "3.840.0", "@aws-sdk/types": "3.840.0", "@aws-sdk/util-arn-parser": "3.804.0", "@smithy/core": "^3.6.0", "@smithy/node-config-provider": "^4.1.3", "@smithy/protocol-http": "^5.1.2", "@smithy/signature-v4": "^5.1.2", "@smithy/smithy-client": "^4.4.5", "@smithy/types": "^4.3.1", "@smithy/util-config-provider": "^4.0.0", "@smithy/util-middleware": "^4.0.4", "@smithy/util-stream": "^4.2.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-rOUji7CayWN3O09zvvgLzDVQe0HiJdZkxoTS6vzOS3WbbdT7joGdVtAJHtn+x776QT3hHzbKU5gnfhel0o6gQA=="],
|
||||
|
||||
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.830.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.826.0", "@aws-sdk/middleware-host-header": "3.821.0", "@aws-sdk/middleware-logger": "3.821.0", "@aws-sdk/middleware-recursion-detection": "3.821.0", "@aws-sdk/middleware-user-agent": "3.828.0", "@aws-sdk/region-config-resolver": "3.821.0", "@aws-sdk/types": "3.821.0", "@aws-sdk/util-endpoints": "3.828.0", "@aws-sdk/util-user-agent-browser": "3.821.0", "@aws-sdk/util-user-agent-node": "3.828.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.5.3", "@smithy/fetch-http-handler": "^5.0.4", "@smithy/hash-node": "^4.0.4", "@smithy/invalid-dependency": "^4.0.4", "@smithy/middleware-content-length": "^4.0.4", "@smithy/middleware-endpoint": "^4.1.11", "@smithy/middleware-retry": "^4.1.12", "@smithy/middleware-serde": "^4.0.8", "@smithy/middleware-stack": "^4.0.4", "@smithy/node-config-provider": "^4.1.3", "@smithy/node-http-handler": "^4.0.6", "@smithy/protocol-http": "^5.1.2", "@smithy/smithy-client": "^4.4.3", "@smithy/types": "^4.3.1", "@smithy/url-parser": "^4.0.4", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.19", "@smithy/util-defaults-mode-node": "^4.0.19", "@smithy/util-endpoints": "^3.0.6", "@smithy/util-middleware": "^4.0.4", "@smithy/util-retry": "^4.0.5", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-5N5YTlBr1vtxf7+t+UaIQ625KEAmm7fY9o1e3MgGOi/paBoI0+axr3ud24qLIy0NSzFlAHEaxUSWxcERNjIoZw=="],
|
||||
"@aws-sdk/middleware-ssec": ["@aws-sdk/middleware-ssec@3.840.0", "", { "dependencies": { "@aws-sdk/types": "3.840.0", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-CBZP9t1QbjDFGOrtnUEHL1oAvmnCUUm7p0aPNbIdSzNtH42TNKjPRN3TuEIJDGjkrqpL3MXyDSmNayDcw/XW7Q=="],
|
||||
|
||||
"@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.821.0", "", { "dependencies": { "@aws-sdk/types": "3.821.0", "@smithy/node-config-provider": "^4.1.3", "@smithy/types": "^4.3.1", "@smithy/util-config-provider": "^4.0.0", "@smithy/util-middleware": "^4.0.4", "tslib": "^2.6.2" } }, "sha512-t8og+lRCIIy5nlId0bScNpCkif8sc0LhmtaKsbm0ZPm3sCa/WhCbSZibjbZ28FNjVCV+p0D9RYZx0VDDbtWyjw=="],
|
||||
"@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.840.0", "", { "dependencies": { "@aws-sdk/core": "3.840.0", "@aws-sdk/types": "3.840.0", "@aws-sdk/util-endpoints": "3.840.0", "@smithy/core": "^3.6.0", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-hiiMf7BP5ZkAFAvWRcK67Mw/g55ar7OCrvrynC92hunx/xhMkrgSLM0EXIZ1oTn3uql9kH/qqGF0nqsK6K555A=="],
|
||||
|
||||
"@aws-sdk/s3-request-presigner": ["@aws-sdk/s3-request-presigner@3.832.0", "", { "dependencies": { "@aws-sdk/signature-v4-multi-region": "3.826.0", "@aws-sdk/types": "3.821.0", "@aws-sdk/util-format-url": "3.821.0", "@smithy/middleware-endpoint": "^4.1.11", "@smithy/protocol-http": "^5.1.2", "@smithy/smithy-client": "^4.4.3", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-zXuwfaAYu99LUF7/6iBr3UlKCMaMImBwfmLXJQlvtE3ebrERXQuISME9Vjd2oG+hJ6XcX6RJqkeIvZBytMzvRw=="],
|
||||
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.840.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.840.0", "@aws-sdk/middleware-host-header": "3.840.0", "@aws-sdk/middleware-logger": "3.840.0", "@aws-sdk/middleware-recursion-detection": "3.840.0", "@aws-sdk/middleware-user-agent": "3.840.0", "@aws-sdk/region-config-resolver": "3.840.0", "@aws-sdk/types": "3.840.0", "@aws-sdk/util-endpoints": "3.840.0", "@aws-sdk/util-user-agent-browser": "3.840.0", "@aws-sdk/util-user-agent-node": "3.840.0", "@smithy/config-resolver": "^4.1.4", "@smithy/core": "^3.6.0", "@smithy/fetch-http-handler": "^5.0.4", "@smithy/hash-node": "^4.0.4", "@smithy/invalid-dependency": "^4.0.4", "@smithy/middleware-content-length": "^4.0.4", "@smithy/middleware-endpoint": "^4.1.13", "@smithy/middleware-retry": "^4.1.14", "@smithy/middleware-serde": "^4.0.8", "@smithy/middleware-stack": "^4.0.4", "@smithy/node-config-provider": "^4.1.3", "@smithy/node-http-handler": "^4.0.6", "@smithy/protocol-http": "^5.1.2", "@smithy/smithy-client": "^4.4.5", "@smithy/types": "^4.3.1", "@smithy/url-parser": "^4.0.4", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", "@smithy/util-defaults-mode-browser": "^4.0.21", "@smithy/util-defaults-mode-node": "^4.0.21", "@smithy/util-endpoints": "^3.0.6", "@smithy/util-middleware": "^4.0.4", "@smithy/util-retry": "^4.0.6", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-LXYYo9+n4hRqnRSIMXLBb+BLz+cEmjMtTudwK1BF6Bn2RfdDv29KuyeDRrPCS3TwKl7ZKmXUmE9n5UuHAPfBpA=="],
|
||||
|
||||
"@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.826.0", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "3.826.0", "@aws-sdk/types": "3.821.0", "@smithy/protocol-http": "^5.1.2", "@smithy/signature-v4": "^5.1.2", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-3fEi/zy6tpMzomYosksGtu7jZqGFcdBXoL7YRsG7OEeQzBbOW9B+fVaQZ4jnsViSjzA/yKydLahMrfPnt+iaxg=="],
|
||||
"@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.840.0", "", { "dependencies": { "@aws-sdk/types": "3.840.0", "@smithy/node-config-provider": "^4.1.3", "@smithy/types": "^4.3.1", "@smithy/util-config-provider": "^4.0.0", "@smithy/util-middleware": "^4.0.4", "tslib": "^2.6.2" } }, "sha512-Qjnxd/yDv9KpIMWr90ZDPtRj0v75AqGC92Lm9+oHXZ8p1MjG5JE2CW0HL8JRgK9iKzgKBL7pPQRXI8FkvEVfrA=="],
|
||||
|
||||
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.830.0", "", { "dependencies": { "@aws-sdk/core": "3.826.0", "@aws-sdk/nested-clients": "3.830.0", "@aws-sdk/types": "3.821.0", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-aJ4guFwj92nV9D+EgJPaCFKK0I3y2uMchiDfh69Zqnmwfxxxfxat6F79VA7PS0BdbjRfhLbn+Ghjftnomu2c1g=="],
|
||||
"@aws-sdk/s3-request-presigner": ["@aws-sdk/s3-request-presigner@3.842.0", "", { "dependencies": { "@aws-sdk/signature-v4-multi-region": "3.840.0", "@aws-sdk/types": "3.840.0", "@aws-sdk/util-format-url": "3.840.0", "@smithy/middleware-endpoint": "^4.1.13", "@smithy/protocol-http": "^5.1.2", "@smithy/smithy-client": "^4.4.5", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-daS69IJ20X+BzsiEtj3XuyyM765iFOdZ648lrptHncQHRWdpzahk67/nP/SKYhWvnNrQ4pw2vYlVxpOs9vl1yg=="],
|
||||
|
||||
"@aws-sdk/types": ["@aws-sdk/types@3.821.0", "", { "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-Znroqdai1a90TlxGaJ+FK1lwC0fHpo97Xjsp5UKGR5JODYm7f9+/fF17ebO1KdoBr/Rm0UIFiF5VmI8ts9F1eA=="],
|
||||
"@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.840.0", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "3.840.0", "@aws-sdk/types": "3.840.0", "@smithy/protocol-http": "^5.1.2", "@smithy/signature-v4": "^5.1.2", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-8AoVgHrkSfhvGPtwx23hIUO4MmMnux2pjnso1lrLZGqxfElM6jm2w4jTNLlNXk8uKHGyX89HaAIuT0lL6dJj9g=="],
|
||||
|
||||
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.840.0", "", { "dependencies": { "@aws-sdk/core": "3.840.0", "@aws-sdk/nested-clients": "3.840.0", "@aws-sdk/types": "3.840.0", "@smithy/property-provider": "^4.0.4", "@smithy/shared-ini-file-loader": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-6BuTOLTXvmgwjK7ve7aTg9JaWFdM5UoMolLVPMyh3wTv9Ufalh8oklxYHUBIxsKkBGO2WiHXytveuxH6tAgTYg=="],
|
||||
|
||||
"@aws-sdk/types": ["@aws-sdk/types@3.840.0", "", { "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-xliuHaUFZxEx1NSXeLLZ9Dyu6+EJVQKEoD+yM+zqUo3YDZ7medKJWY6fIOKiPX/N7XbLdBYwajb15Q7IL8KkeA=="],
|
||||
|
||||
"@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.804.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-wmBJqn1DRXnZu3b4EkE6CWnoWMo1ZMvlfkqU5zPz67xx1GMaXlDCchFvKAXMjk4jn/L1O3tKnoFDNsoLV1kgNQ=="],
|
||||
|
||||
"@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.828.0", "", { "dependencies": { "@aws-sdk/types": "3.821.0", "@smithy/types": "^4.3.1", "@smithy/util-endpoints": "^3.0.6", "tslib": "^2.6.2" } }, "sha512-RvKch111SblqdkPzg3oCIdlGxlQs+k+P7Etory9FmxPHyPDvsP1j1c74PmgYqtzzMWmoXTjd+c9naUHh9xG8xg=="],
|
||||
"@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.840.0", "", { "dependencies": { "@aws-sdk/types": "3.840.0", "@smithy/types": "^4.3.1", "@smithy/util-endpoints": "^3.0.6", "tslib": "^2.6.2" } }, "sha512-eqE9ROdg/Kk0rj3poutyRCFauPDXIf/WSvCqFiRDDVi6QOnCv/M0g2XW8/jSvkJlOyaXkNCptapIp6BeeFFGYw=="],
|
||||
|
||||
"@aws-sdk/util-format-url": ["@aws-sdk/util-format-url@3.821.0", "", { "dependencies": { "@aws-sdk/types": "3.821.0", "@smithy/querystring-builder": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-h+xqmPToxDrZ0a7rxE1a8Oh4zpWfZe9oiQUphGtfiGFA6j75UiURH5J3MmGHa/G4t15I3iLLbYtUXxvb1i7evg=="],
|
||||
"@aws-sdk/util-format-url": ["@aws-sdk/util-format-url@3.840.0", "", { "dependencies": { "@aws-sdk/types": "3.840.0", "@smithy/querystring-builder": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-VB1PWyI1TQPiPvg4w7tgUGGQER1xxXPNUqfh3baxUSFi1Oh8wHrDnFywkxLm3NMmgDmnLnSZ5Q326qAoyqKLSg=="],
|
||||
|
||||
"@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.804.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-zVoRfpmBVPodYlnMjgVjfGoEZagyRF5IPn3Uo6ZvOZp24chnW/FRstH7ESDHDDRga4z3V+ElUQHKpFDXWyBW5A=="],
|
||||
|
||||
"@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.821.0", "", { "dependencies": { "@aws-sdk/types": "3.821.0", "@smithy/types": "^4.3.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-irWZHyM0Jr1xhC+38OuZ7JB6OXMLPZlj48thElpsO1ZSLRkLZx5+I7VV6k3sp2yZ7BYbKz/G2ojSv4wdm7XTLw=="],
|
||||
"@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.840.0", "", { "dependencies": { "@aws-sdk/types": "3.840.0", "@smithy/types": "^4.3.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-JdyZM3EhhL4PqwFpttZu1afDpPJCCc3eyZOLi+srpX11LsGj6sThf47TYQN75HT1CarZ7cCdQHGzP2uy3/xHfQ=="],
|
||||
|
||||
"@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.828.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.828.0", "@aws-sdk/types": "3.821.0", "@smithy/node-config-provider": "^4.1.3", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-LdN6fTBzTlQmc8O8f1wiZN0qF3yBWVGis7NwpWK7FUEzP9bEZRxYfIkV9oV9zpt6iNRze1SedK3JQVB/udxBoA=="],
|
||||
"@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.840.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.840.0", "@aws-sdk/types": "3.840.0", "@smithy/node-config-provider": "^4.1.3", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-Fy5JUEDQU1tPm2Yw/YqRYYc27W5+QD/J4mYvQvdWjUGZLB5q3eLFMGD35Uc28ZFoGMufPr4OCxK/bRfWROBRHQ=="],
|
||||
|
||||
"@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.821.0", "", { "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-DIIotRnefVL6DiaHtO6/21DhJ4JZnnIwdNbpwiAhdt/AVbttcE4yw925gsjur0OGv5BTYXQXU3YnANBYnZjuQA=="],
|
||||
|
||||
@@ -1066,7 +1074,7 @@
|
||||
|
||||
"@smithy/config-resolver": ["@smithy/config-resolver@4.1.4", "", { "dependencies": { "@smithy/node-config-provider": "^4.1.3", "@smithy/types": "^4.3.1", "@smithy/util-config-provider": "^4.0.0", "@smithy/util-middleware": "^4.0.4", "tslib": "^2.6.2" } }, "sha512-prmU+rDddxHOH0oNcwemL+SwnzcG65sBF2yXRO7aeXIn/xTlq2pX7JLVbkBnVLowHLg4/OL4+jBmv9hVrVGS+w=="],
|
||||
|
||||
"@smithy/core": ["@smithy/core@3.5.3", "", { "dependencies": { "@smithy/middleware-serde": "^4.0.8", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-middleware": "^4.0.4", "@smithy/util-stream": "^4.2.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-xa5byV9fEguZNofCclv6v9ra0FYh5FATQW/da7FQUVTic94DfrN/NvmKZjrMyzbpqfot9ZjBaO8U1UeTbmSLuA=="],
|
||||
"@smithy/core": ["@smithy/core@3.6.0", "", { "dependencies": { "@smithy/middleware-serde": "^4.0.8", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-middleware": "^4.0.4", "@smithy/util-stream": "^4.2.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-Pgvfb+TQ4wUNLyHzvgCP4aYZMh16y7GcfF59oirRHcgGgkH1e/s9C0nv/v3WP+Quymyr5je71HeFQCwh+44XLg=="],
|
||||
|
||||
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.0.6", "", { "dependencies": { "@smithy/node-config-provider": "^4.1.3", "@smithy/property-provider": "^4.0.4", "@smithy/types": "^4.3.1", "@smithy/url-parser": "^4.0.4", "tslib": "^2.6.2" } }, "sha512-hKMWcANhUiNbCJouYkZ9V3+/Qf9pteR1dnwgdyzR09R4ODEYx8BbUysHwRSyex4rZ9zapddZhLFTnT4ZijR4pw=="],
|
||||
|
||||
@@ -1096,9 +1104,9 @@
|
||||
|
||||
"@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.0.4", "", { "dependencies": { "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-F7gDyfI2BB1Kc+4M6rpuOLne5LOcEknH1n6UQB69qv+HucXBR1rkzXBnQTB2q46sFy1PM/zuSJOB532yc8bg3w=="],
|
||||
|
||||
"@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.1.11", "", { "dependencies": { "@smithy/core": "^3.5.3", "@smithy/middleware-serde": "^4.0.8", "@smithy/node-config-provider": "^4.1.3", "@smithy/shared-ini-file-loader": "^4.0.4", "@smithy/types": "^4.3.1", "@smithy/url-parser": "^4.0.4", "@smithy/util-middleware": "^4.0.4", "tslib": "^2.6.2" } }, "sha512-zDogwtRLzKl58lVS8wPcARevFZNBOOqnmzWWxVe9XiaXU2CADFjvJ9XfNibgkOWs08sxLuSr81NrpY4mgp9OwQ=="],
|
||||
"@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.1.13", "", { "dependencies": { "@smithy/core": "^3.6.0", "@smithy/middleware-serde": "^4.0.8", "@smithy/node-config-provider": "^4.1.3", "@smithy/shared-ini-file-loader": "^4.0.4", "@smithy/types": "^4.3.1", "@smithy/url-parser": "^4.0.4", "@smithy/util-middleware": "^4.0.4", "tslib": "^2.6.2" } }, "sha512-xg3EHV/Q5ZdAO5b0UiIMj3RIOCobuS40pBBODguUDVdko6YK6QIzCVRrHTogVuEKglBWqWenRnZ71iZnLL3ZAQ=="],
|
||||
|
||||
"@smithy/middleware-retry": ["@smithy/middleware-retry@4.1.12", "", { "dependencies": { "@smithy/node-config-provider": "^4.1.3", "@smithy/protocol-http": "^5.1.2", "@smithy/service-error-classification": "^4.0.5", "@smithy/smithy-client": "^4.4.3", "@smithy/types": "^4.3.1", "@smithy/util-middleware": "^4.0.4", "@smithy/util-retry": "^4.0.5", "tslib": "^2.6.2", "uuid": "^9.0.1" } }, "sha512-wvIH70c4e91NtRxdaLZF+mbLZ/HcC6yg7ySKUiufL6ESp6zJUSnJucZ309AvG9nqCFHSRB5I6T3Ez1Q9wCh0Ww=="],
|
||||
"@smithy/middleware-retry": ["@smithy/middleware-retry@4.1.14", "", { "dependencies": { "@smithy/node-config-provider": "^4.1.3", "@smithy/protocol-http": "^5.1.2", "@smithy/service-error-classification": "^4.0.6", "@smithy/smithy-client": "^4.4.5", "@smithy/types": "^4.3.1", "@smithy/util-middleware": "^4.0.4", "@smithy/util-retry": "^4.0.6", "tslib": "^2.6.2", "uuid": "^9.0.1" } }, "sha512-eoXaLlDGpKvdmvt+YBfRXE7HmIEtFF+DJCbTPwuLunP0YUnrydl+C4tS+vEM0+nyxXrX3PSUFqC+lP1+EHB1Tw=="],
|
||||
|
||||
"@smithy/middleware-serde": ["@smithy/middleware-serde@4.0.8", "", { "dependencies": { "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-iSSl7HJoJaGyMIoNn2B7czghOVwJ9nD7TMvLhMWeSB5vt0TnEYyRRqPJu/TqW76WScaNvYYB8nRoiBHR9S1Ddw=="],
|
||||
|
||||
@@ -1116,13 +1124,13 @@
|
||||
|
||||
"@smithy/querystring-parser": ["@smithy/querystring-parser@4.0.4", "", { "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w=="],
|
||||
|
||||
"@smithy/service-error-classification": ["@smithy/service-error-classification@4.0.5", "", { "dependencies": { "@smithy/types": "^4.3.1" } }, "sha512-LvcfhrnCBvCmTee81pRlh1F39yTS/+kYleVeLCwNtkY8wtGg8V/ca9rbZZvYIl8OjlMtL6KIjaiL/lgVqHD2nA=="],
|
||||
"@smithy/service-error-classification": ["@smithy/service-error-classification@4.0.6", "", { "dependencies": { "@smithy/types": "^4.3.1" } }, "sha512-RRoTDL//7xi4tn5FrN2NzH17jbgmnKidUqd4KvquT0954/i6CXXkh1884jBiunq24g9cGtPBEXlU40W6EpNOOg=="],
|
||||
|
||||
"@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.0.4", "", { "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw=="],
|
||||
|
||||
"@smithy/signature-v4": ["@smithy/signature-v4@5.1.2", "", { "dependencies": { "@smithy/is-array-buffer": "^4.0.0", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "@smithy/util-hex-encoding": "^4.0.0", "@smithy/util-middleware": "^4.0.4", "@smithy/util-uri-escape": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-d3+U/VpX7a60seHziWnVZOHuEgJlclufjkS6zhXvxcJgkJq4UWdH5eOBLzHRMx6gXjsdT9h6lfpmLzbrdupHgQ=="],
|
||||
|
||||
"@smithy/smithy-client": ["@smithy/smithy-client@4.4.3", "", { "dependencies": { "@smithy/core": "^3.5.3", "@smithy/middleware-endpoint": "^4.1.11", "@smithy/middleware-stack": "^4.0.4", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "@smithy/util-stream": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-xxzNYgA0HD6ETCe5QJubsxP0hQH3QK3kbpJz3QrosBCuIWyEXLR/CO5hFb2OeawEKUxMNhz3a1nuJNN2np2RMA=="],
|
||||
"@smithy/smithy-client": ["@smithy/smithy-client@4.4.5", "", { "dependencies": { "@smithy/core": "^3.6.0", "@smithy/middleware-endpoint": "^4.1.13", "@smithy/middleware-stack": "^4.0.4", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", "@smithy/util-stream": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-+lynZjGuUFJaMdDYSTMnP/uPBBXXukVfrJlP+1U/Dp5SFTEI++w6NMga8DjOENxecOF71V9Z2DllaVDYRnGlkg=="],
|
||||
|
||||
"@smithy/types": ["@smithy/types@4.3.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA=="],
|
||||
|
||||
@@ -1138,9 +1146,9 @@
|
||||
|
||||
"@smithy/util-config-provider": ["@smithy/util-config-provider@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w=="],
|
||||
|
||||
"@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.0.19", "", { "dependencies": { "@smithy/property-provider": "^4.0.4", "@smithy/smithy-client": "^4.4.3", "@smithy/types": "^4.3.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-mvLMh87xSmQrV5XqnUYEPoiFFeEGYeAKIDDKdhE2ahqitm8OHM3aSvhqL6rrK6wm1brIk90JhxDf5lf2hbrLbQ=="],
|
||||
"@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.0.21", "", { "dependencies": { "@smithy/property-provider": "^4.0.4", "@smithy/smithy-client": "^4.4.5", "@smithy/types": "^4.3.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-wM0jhTytgXu3wzJoIqpbBAG5U6BwiubZ6QKzSbP7/VbmF1v96xlAbX2Am/mz0Zep0NLvLh84JT0tuZnk3wmYQA=="],
|
||||
|
||||
"@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.0.19", "", { "dependencies": { "@smithy/config-resolver": "^4.1.4", "@smithy/credential-provider-imds": "^4.0.6", "@smithy/node-config-provider": "^4.1.3", "@smithy/property-provider": "^4.0.4", "@smithy/smithy-client": "^4.4.3", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-8tYnx+LUfj6m+zkUUIrIQJxPM1xVxfRBvoGHua7R/i6qAxOMjqR6CpEpDwKoIs1o0+hOjGvkKE23CafKL0vJ9w=="],
|
||||
"@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.0.21", "", { "dependencies": { "@smithy/config-resolver": "^4.1.4", "@smithy/credential-provider-imds": "^4.0.6", "@smithy/node-config-provider": "^4.1.3", "@smithy/property-provider": "^4.0.4", "@smithy/smithy-client": "^4.4.5", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-/F34zkoU0GzpUgLJydHY8Rxu9lBn8xQC/s/0M0U9lLBkYbA1htaAFjWYJzpzsbXPuri5D1H8gjp2jBum05qBrA=="],
|
||||
|
||||
"@smithy/util-endpoints": ["@smithy/util-endpoints@3.0.6", "", { "dependencies": { "@smithy/node-config-provider": "^4.1.3", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-YARl3tFL3WgPuLzljRUnrS2ngLiUtkwhQtj8PAL13XZSyUiNLQxwG3fBBq3QXFqGFUXepIN73pINp3y8c2nBmA=="],
|
||||
|
||||
@@ -1148,7 +1156,7 @@
|
||||
|
||||
"@smithy/util-middleware": ["@smithy/util-middleware@4.0.4", "", { "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ=="],
|
||||
|
||||
"@smithy/util-retry": ["@smithy/util-retry@4.0.5", "", { "dependencies": { "@smithy/service-error-classification": "^4.0.5", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-V7MSjVDTlEt/plmOFBn1762Dyu5uqMrV2Pl2X0dYk4XvWfdWJNe9Bs5Bzb56wkCuiWjSfClVMGcsuKrGj7S/yg=="],
|
||||
"@smithy/util-retry": ["@smithy/util-retry@4.0.6", "", { "dependencies": { "@smithy/service-error-classification": "^4.0.6", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-+YekoF2CaSMv6zKrA6iI/N9yva3Gzn4L6n35Luydweu5MMPYpiGZlWqehPHDHyNbnyaYlz/WJyYAZnC+loBDZg=="],
|
||||
|
||||
"@smithy/util-stream": ["@smithy/util-stream@4.2.2", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.0.4", "@smithy/node-http-handler": "^4.0.6", "@smithy/types": "^4.3.1", "@smithy/util-base64": "^4.0.0", "@smithy/util-buffer-from": "^4.0.0", "@smithy/util-hex-encoding": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-aI+GLi7MJoVxg24/3J1ipwLoYzgkB4kUfogZfnslcYlynj3xsQ0e7vk4TnTro9hhsS5PvX1mwmkRqqHQjwcU7w=="],
|
||||
|
||||
@@ -1156,7 +1164,7 @@
|
||||
|
||||
"@smithy/util-utf8": ["@smithy/util-utf8@4.0.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow=="],
|
||||
|
||||
"@smithy/util-waiter": ["@smithy/util-waiter@4.0.5", "", { "dependencies": { "@smithy/abort-controller": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-4QvC49HTteI1gfemu0I1syWovJgPvGn7CVUoN9ZFkdvr/cCFkrEL7qNCdx/2eICqDWEGnnr68oMdSIPCLAriSQ=="],
|
||||
"@smithy/util-waiter": ["@smithy/util-waiter@4.0.6", "", { "dependencies": { "@smithy/abort-controller": "^4.0.4", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-slcr1wdRbX7NFphXZOxtxRNA7hXAAtJAXJDE/wdoMAos27SIquVCKiSqfB6/28YzQ8FCsB5NKkhdM5gMADbqxg=="],
|
||||
|
||||
"@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="],
|
||||
|
||||
@@ -1176,35 +1184,35 @@
|
||||
|
||||
"@tabler/icons-react": ["@tabler/icons-react@3.34.0", "", { "dependencies": { "@tabler/icons": "3.34.0" }, "peerDependencies": { "react": ">= 16" } }, "sha512-OpEIR2iZsIXECtAIMbn1zfKfQ3zKJjXyIZlkgOGUL9UkMCFycEiF2Y8AVfEQsyre/3FnBdlWJvGr0NU47n2TbQ=="],
|
||||
|
||||
"@tailwindcss/node": ["@tailwindcss/node@4.1.10", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.10" } }, "sha512-2ACf1znY5fpRBwRhMgj9ZXvb2XZW8qs+oTfotJ2C5xR0/WNL7UHZ7zXl6s+rUqedL1mNi+0O+WQr5awGowS3PQ=="],
|
||||
"@tailwindcss/node": ["@tailwindcss/node@4.1.11", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "lightningcss": "1.30.1", "magic-string": "^0.30.17", "source-map-js": "^1.2.1", "tailwindcss": "4.1.11" } }, "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q=="],
|
||||
|
||||
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.10", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.4.3" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.10", "@tailwindcss/oxide-darwin-arm64": "4.1.10", "@tailwindcss/oxide-darwin-x64": "4.1.10", "@tailwindcss/oxide-freebsd-x64": "4.1.10", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.10", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.10", "@tailwindcss/oxide-linux-arm64-musl": "4.1.10", "@tailwindcss/oxide-linux-x64-gnu": "4.1.10", "@tailwindcss/oxide-linux-x64-musl": "4.1.10", "@tailwindcss/oxide-wasm32-wasi": "4.1.10", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.10", "@tailwindcss/oxide-win32-x64-msvc": "4.1.10" } }, "sha512-v0C43s7Pjw+B9w21htrQwuFObSkio2aV/qPx/mhrRldbqxbWJK6KizM+q7BF1/1CmuLqZqX3CeYF7s7P9fbA8Q=="],
|
||||
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.11", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.4.3" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.11", "@tailwindcss/oxide-darwin-arm64": "4.1.11", "@tailwindcss/oxide-darwin-x64": "4.1.11", "@tailwindcss/oxide-freebsd-x64": "4.1.11", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.11", "@tailwindcss/oxide-linux-arm64-musl": "4.1.11", "@tailwindcss/oxide-linux-x64-gnu": "4.1.11", "@tailwindcss/oxide-linux-x64-musl": "4.1.11", "@tailwindcss/oxide-wasm32-wasi": "4.1.11", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.11", "@tailwindcss/oxide-win32-x64-msvc": "4.1.11" } }, "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg=="],
|
||||
|
||||
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.10", "", { "os": "android", "cpu": "arm64" }, "sha512-VGLazCoRQ7rtsCzThaI1UyDu/XRYVyH4/EWiaSX6tFglE+xZB5cvtC5Omt0OQ+FfiIVP98su16jDVHDEIuH4iQ=="],
|
||||
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.11", "", { "os": "android", "cpu": "arm64" }, "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg=="],
|
||||
|
||||
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ZIFqvR1irX2yNjWJzKCqTCcHZbgkSkSkZKbRM3BPzhDL/18idA8uWCoopYA2CSDdSGFlDAxYdU2yBHwAwx8euQ=="],
|
||||
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ=="],
|
||||
|
||||
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-eCA4zbIhWUFDXoamNztmS0MjXHSEJYlvATzWnRiTqJkcUteSjO94PoRHJy1Xbwp9bptjeIxxBHh+zBWFhttbrQ=="],
|
||||
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw=="],
|
||||
|
||||
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.10", "", { "os": "freebsd", "cpu": "x64" }, "sha512-8/392Xu12R0cc93DpiJvNpJ4wYVSiciUlkiOHOSOQNH3adq9Gi/dtySK7dVQjXIOzlpSHjeCL89RUUI8/GTI6g=="],
|
||||
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.11", "", { "os": "freebsd", "cpu": "x64" }, "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.10", "", { "os": "linux", "cpu": "arm" }, "sha512-t9rhmLT6EqeuPT+MXhWhlRYIMSfh5LZ6kBrC4FS6/+M1yXwfCtp24UumgCWOAJVyjQwG+lYva6wWZxrfvB+NhQ=="],
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11", "", { "os": "linux", "cpu": "arm" }, "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-3oWrlNlxLRxXejQ8zImzrVLuZ/9Z2SeKoLhtCu0hpo38hTO2iL86eFOu4sVR8cZc6n3z7eRXXqtHJECa6mFOvA=="],
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-saScU0cmWvg/Ez4gUmQWr9pvY9Kssxt+Xenfx1LG7LmqjcrvBnw4r9VjkFcqmbBb7GCBwYNcZi9X3/oMda9sqQ=="],
|
||||
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.10", "", { "os": "linux", "cpu": "x64" }, "sha512-/G3ao/ybV9YEEgAXeEg28dyH6gs1QG8tvdN9c2MNZdUXYBaIY/Gx0N6RlJzfLy/7Nkdok4kaxKPHKJUlAaoTdA=="],
|
||||
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.11", "", { "os": "linux", "cpu": "x64" }, "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg=="],
|
||||
|
||||
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.10", "", { "os": "linux", "cpu": "x64" }, "sha512-LNr7X8fTiKGRtQGOerSayc2pWJp/9ptRYAa4G+U+cjw9kJZvkopav1AQc5HHD+U364f71tZv6XamaHKgrIoVzA=="],
|
||||
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.11", "", { "os": "linux", "cpu": "x64" }, "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.10", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@emnapi/wasi-threads": "^1.0.2", "@napi-rs/wasm-runtime": "^0.2.10", "@tybys/wasm-util": "^0.9.0", "tslib": "^2.8.0" }, "cpu": "none" }, "sha512-d6ekQpopFQJAcIK2i7ZzWOYGZ+A6NzzvQ3ozBvWFdeyqfOZdYHU66g5yr+/HC4ipP1ZgWsqa80+ISNILk+ae/Q=="],
|
||||
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.11", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@emnapi/wasi-threads": "^1.0.2", "@napi-rs/wasm-runtime": "^0.2.11", "@tybys/wasm-util": "^0.9.0", "tslib": "^2.8.0" }, "cpu": "none" }, "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g=="],
|
||||
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-i1Iwg9gRbwNVOCYmnigWCCgow8nDWSFmeTUU5nbNx3rqbe4p0kRbEqLwLJbYZKmSSp23g4N6rCDmm7OuPBXhDA=="],
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w=="],
|
||||
|
||||
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.10", "", { "os": "win32", "cpu": "x64" }, "sha512-sGiJTjcBSfGq2DVRtaSljq5ZgZS2SDHSIfhOylkBvHVjwOsodBhnb3HdmiKkVuUGKD0I7G63abMOVaskj1KpOA=="],
|
||||
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.11", "", { "os": "win32", "cpu": "x64" }, "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg=="],
|
||||
|
||||
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.10", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.10", "@tailwindcss/oxide": "4.1.10", "postcss": "^8.4.41", "tailwindcss": "4.1.10" } }, "sha512-B+7r7ABZbkXJwpvt2VMnS6ujcDoR2OOcFaqrLIo1xbcdxje4Vf+VgJdBzNNbrAjBj/rLZ66/tlQ1knIGNLKOBQ=="],
|
||||
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.11", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.11", "@tailwindcss/oxide": "4.1.11", "postcss": "^8.4.41", "tailwindcss": "4.1.11" } }, "sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA=="],
|
||||
|
||||
"@testing-library/dom": ["@testing-library/dom@10.4.0", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "chalk": "^4.1.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "pretty-format": "^27.0.2" } }, "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ=="],
|
||||
|
||||
@@ -1480,6 +1488,10 @@
|
||||
|
||||
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
|
||||
|
||||
"archiver": ["archiver@7.0.1", "", { "dependencies": { "archiver-utils": "^5.0.2", "async": "^3.2.4", "buffer-crc32": "^1.0.0", "readable-stream": "^4.0.0", "readdir-glob": "^1.1.2", "tar-stream": "^3.0.0", "zip-stream": "^6.0.1" } }, "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ=="],
|
||||
|
||||
"archiver-utils": ["archiver-utils@5.0.2", "", { "dependencies": { "glob": "^10.0.0", "graceful-fs": "^4.2.0", "is-stream": "^2.0.1", "lazystream": "^1.0.0", "lodash": "^4.17.15", "normalize-path": "^3.0.0", "readable-stream": "^4.0.0" } }, "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA=="],
|
||||
|
||||
"arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],
|
||||
|
||||
"argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
|
||||
@@ -1498,14 +1510,20 @@
|
||||
|
||||
"astring": ["astring@1.9.0", "", { "bin": { "astring": "bin/astring" } }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="],
|
||||
|
||||
"async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="],
|
||||
|
||||
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
|
||||
|
||||
"atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="],
|
||||
|
||||
"b4a": ["b4a@1.6.7", "", {}, "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg=="],
|
||||
|
||||
"bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
|
||||
|
||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"bare-events": ["bare-events@2.5.4", "", {}, "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA=="],
|
||||
|
||||
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
||||
|
||||
"base64id": ["base64id@2.0.0", "", {}, "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="],
|
||||
@@ -1534,7 +1552,9 @@
|
||||
|
||||
"browserslist": ["browserslist@4.25.0", "", { "dependencies": { "caniuse-lite": "^1.0.30001718", "electron-to-chromium": "^1.5.160", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA=="],
|
||||
|
||||
"buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
|
||||
"buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
|
||||
|
||||
"buffer-crc32": ["buffer-crc32@1.0.0", "", {}, "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w=="],
|
||||
|
||||
"buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
|
||||
|
||||
@@ -1638,6 +1658,8 @@
|
||||
|
||||
"commondir": ["commondir@1.0.1", "", {}, "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="],
|
||||
|
||||
"compress-commons": ["compress-commons@6.0.2", "", { "dependencies": { "crc-32": "^1.2.0", "crc32-stream": "^6.0.0", "is-stream": "^2.0.1", "normalize-path": "^3.0.0", "readable-stream": "^4.0.0" } }, "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg=="],
|
||||
|
||||
"compute-scroll-into-view": ["compute-scroll-into-view@3.1.1", "", {}, "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw=="],
|
||||
|
||||
"concurrently": ["concurrently@9.1.2", "", { "dependencies": { "chalk": "^4.1.2", "lodash": "^4.17.21", "rxjs": "^7.8.1", "shell-quote": "^1.8.1", "supports-color": "^8.1.1", "tree-kill": "^1.2.2", "yargs": "^17.7.2" }, "bin": { "conc": "dist/bin/concurrently.js", "concurrently": "dist/bin/concurrently.js" } }, "sha512-H9MWcoPsYddwbOGM6difjVwVZHl63nwMEwDJG/L7VGtuaJhb12h2caPG2tVPWs7emuYix252iGfqOyrz1GczTQ=="],
|
||||
@@ -1656,6 +1678,8 @@
|
||||
|
||||
"crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="],
|
||||
|
||||
"crc32-stream": ["crc32-stream@6.0.0", "", { "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^4.0.0" } }, "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g=="],
|
||||
|
||||
"critters": ["critters@0.0.23", "", { "dependencies": { "chalk": "^4.1.0", "css-select": "^5.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.2", "htmlparser2": "^8.0.2", "postcss": "^8.4.23", "postcss-media-query-parser": "^0.2.3" } }, "sha512-/MCsQbuzTPA/ZTOjjyr2Na5o3lRpr8vd0MZE8tMP0OBNg/VrLxWHteVKalQ8KR+fBmUadbJLdoyEz9sT+q84qg=="],
|
||||
|
||||
"croner": ["croner@9.1.0", "", {}, "sha512-p9nwwR4qyT5W996vBZhdvBCnMhicY5ytZkR4D1Xj0wuTDEiMnjwR57Q3RXYY/s0EpX6Ay3vgIcfaR+ewGHsi+g=="],
|
||||
@@ -1894,6 +1918,8 @@
|
||||
|
||||
"fast-equals": ["fast-equals@5.2.2", "", {}, "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw=="],
|
||||
|
||||
"fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="],
|
||||
|
||||
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
|
||||
|
||||
"fast-redact": ["fast-redact@3.5.0", "", {}, "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A=="],
|
||||
@@ -2166,6 +2192,8 @@
|
||||
|
||||
"kysely": ["kysely@0.28.2", "", {}, "sha512-4YAVLoF0Sf0UTqlhgQMFU9iQECdah7n+13ANkiuVfRvlK+uI0Etbgd7bVP36dKlG+NXWbhGua8vnGt+sdhvT7A=="],
|
||||
|
||||
"lazystream": ["lazystream@1.0.1", "", { "dependencies": { "readable-stream": "^2.0.5" } }, "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw=="],
|
||||
|
||||
"leac": ["leac@0.6.0", "", {}, "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg=="],
|
||||
|
||||
"lenis": ["lenis@1.3.4", "", { "peerDependencies": { "@nuxt/kit": ">=3.0.0", "react": ">=17.0.0", "vue": ">=3.0.0" }, "optionalPeers": ["@nuxt/kit", "react", "vue"] }, "sha512-WIGk8wiV2ABm/T7M+NC+tAV8fjzNJD1J4z11aZ3mTtx7WAZX/4QdCNhBO0g/TqXISA+/3hTbzrPC4FW1nhoNMQ=="],
|
||||
@@ -2574,7 +2602,7 @@
|
||||
|
||||
"prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="],
|
||||
|
||||
"process": ["process@0.10.1", "", {}, "sha512-dyIett8dgGIZ/TXKUzeYExt7WA6ldDzys9vTDU/cCA9L17Ypme+KzS+NjQCjpn9xsvi/shbMC+yP/BcFMBz0NA=="],
|
||||
"process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="],
|
||||
|
||||
"process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
|
||||
|
||||
@@ -2646,7 +2674,9 @@
|
||||
|
||||
"read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="],
|
||||
|
||||
"readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
|
||||
"readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="],
|
||||
|
||||
"readdir-glob": ["readdir-glob@1.1.3", "", { "dependencies": { "minimatch": "^5.1.0" } }, "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA=="],
|
||||
|
||||
"readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
|
||||
|
||||
@@ -2826,6 +2856,8 @@
|
||||
|
||||
"streamsearch": ["streamsearch@1.1.0", "", {}, "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="],
|
||||
|
||||
"streamx": ["streamx@2.22.1", "", { "dependencies": { "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" }, "optionalDependencies": { "bare-events": "^2.2.0" } }, "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA=="],
|
||||
|
||||
"string-argv": ["string-argv@0.3.2", "", {}, "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q=="],
|
||||
|
||||
"string-template": ["string-template@0.2.1", "", {}, "sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw=="],
|
||||
@@ -2836,7 +2868,7 @@
|
||||
|
||||
"string.prototype.codepointat": ["string.prototype.codepointat@0.2.1", "", {}, "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg=="],
|
||||
|
||||
"string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
||||
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
|
||||
|
||||
"stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
|
||||
|
||||
@@ -2880,12 +2912,16 @@
|
||||
|
||||
"tar": ["tar@7.4.3", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.0.1", "mkdirp": "^3.0.1", "yallist": "^5.0.0" } }, "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw=="],
|
||||
|
||||
"tar-stream": ["tar-stream@3.1.7", "", { "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ=="],
|
||||
|
||||
"terser": ["terser@5.43.1", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.14.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg=="],
|
||||
|
||||
"terser-webpack-plugin": ["terser-webpack-plugin@5.3.14", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", "serialize-javascript": "^6.0.2", "terser": "^5.31.1" }, "peerDependencies": { "webpack": "^5.1.0" } }, "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw=="],
|
||||
|
||||
"test-exclude": ["test-exclude@7.0.1", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^10.4.1", "minimatch": "^9.0.4" } }, "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg=="],
|
||||
|
||||
"text-decoder": ["text-decoder@1.2.3", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA=="],
|
||||
|
||||
"thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="],
|
||||
|
||||
"thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="],
|
||||
@@ -3088,6 +3124,8 @@
|
||||
|
||||
"yoga-wasm-web": ["yoga-wasm-web@0.3.3", "", {}, "sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA=="],
|
||||
|
||||
"zip-stream": ["zip-stream@6.0.1", "", { "dependencies": { "archiver-utils": "^5.0.0", "compress-commons": "^6.0.2", "readable-stream": "^4.0.0" } }, "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA=="],
|
||||
|
||||
"zod": ["zod@3.25.67", "", {}, "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw=="],
|
||||
|
||||
"zod-to-json-schema": ["zod-to-json-schema@3.24.5", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="],
|
||||
@@ -3104,10 +3142,22 @@
|
||||
|
||||
"@asamuzakjp/css-color/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"@aws-crypto/crc32/@aws-sdk/types": ["@aws-sdk/types@3.821.0", "", { "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-Znroqdai1a90TlxGaJ+FK1lwC0fHpo97Xjsp5UKGR5JODYm7f9+/fF17ebO1KdoBr/Rm0UIFiF5VmI8ts9F1eA=="],
|
||||
|
||||
"@aws-crypto/crc32c/@aws-sdk/types": ["@aws-sdk/types@3.821.0", "", { "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-Znroqdai1a90TlxGaJ+FK1lwC0fHpo97Xjsp5UKGR5JODYm7f9+/fF17ebO1KdoBr/Rm0UIFiF5VmI8ts9F1eA=="],
|
||||
|
||||
"@aws-crypto/sha1-browser/@aws-sdk/types": ["@aws-sdk/types@3.821.0", "", { "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-Znroqdai1a90TlxGaJ+FK1lwC0fHpo97Xjsp5UKGR5JODYm7f9+/fF17ebO1KdoBr/Rm0UIFiF5VmI8ts9F1eA=="],
|
||||
|
||||
"@aws-crypto/sha1-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
||||
|
||||
"@aws-crypto/sha256-browser/@aws-sdk/types": ["@aws-sdk/types@3.821.0", "", { "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-Znroqdai1a90TlxGaJ+FK1lwC0fHpo97Xjsp5UKGR5JODYm7f9+/fF17ebO1KdoBr/Rm0UIFiF5VmI8ts9F1eA=="],
|
||||
|
||||
"@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
||||
|
||||
"@aws-crypto/sha256-js/@aws-sdk/types": ["@aws-sdk/types@3.821.0", "", { "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-Znroqdai1a90TlxGaJ+FK1lwC0fHpo97Xjsp5UKGR5JODYm7f9+/fF17ebO1KdoBr/Rm0UIFiF5VmI8ts9F1eA=="],
|
||||
|
||||
"@aws-crypto/util/@aws-sdk/types": ["@aws-sdk/types@3.821.0", "", { "dependencies": { "@smithy/types": "^4.3.1", "tslib": "^2.6.2" } }, "sha512-Znroqdai1a90TlxGaJ+FK1lwC0fHpo97Xjsp5UKGR5JODYm7f9+/fF17ebO1KdoBr/Rm0UIFiF5VmI8ts9F1eA=="],
|
||||
|
||||
"@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
||||
|
||||
"@aws-sdk/client-s3/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
|
||||
@@ -3374,11 +3424,11 @@
|
||||
|
||||
"@tailwindcss/node/jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="],
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.4", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.3", "tslib": "^2.4.0" }, "bundled": true }, "sha512-A9CnAbC6ARNMKcIcrQwq6HeHCjpcBZ5wSx4U01WXCqEKlrzB9F9315WDNHkrs2xbx7YjjSxbUYxuN6EQzpcY2g=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA=="],
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.0.3", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-8K5IFFsQqF9wQNJptGbS6FNKgUTsSRYnTqNCG1vPP8jFdjSv18n2mQfJpkt2Oibo9iBEzcDnDxNwKTzC7svlJw=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.11", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.9.0" }, "bundled": true }, "sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA=="],
|
||||
|
||||
@@ -3406,8 +3456,12 @@
|
||||
|
||||
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"archiver-utils/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
|
||||
|
||||
"better-auth/jose": ["jose@5.10.0", "", {}, "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg=="],
|
||||
|
||||
"bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
|
||||
|
||||
"bl/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
|
||||
|
||||
"chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
@@ -3458,6 +3512,8 @@
|
||||
|
||||
"groq-sdk/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
|
||||
|
||||
"hexer/process": ["process@0.10.1", "", {}, "sha512-dyIett8dgGIZ/TXKUzeYExt7WA6ldDzys9vTDU/cCA9L17Ypme+KzS+NjQCjpn9xsvi/shbMC+yP/BcFMBz0NA=="],
|
||||
|
||||
"hoist-non-react-statics/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
|
||||
|
||||
"htmlparser2/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
@@ -3476,6 +3532,10 @@
|
||||
|
||||
"jsondiffpatch/chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
|
||||
|
||||
"jszip/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
|
||||
|
||||
"lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
|
||||
|
||||
"linebreak/base64-js": ["base64-js@0.0.8", "", {}, "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw=="],
|
||||
|
||||
"lint-staged/chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
|
||||
@@ -3548,6 +3608,8 @@
|
||||
|
||||
"react-email/commander": ["commander@13.1.0", "", {}, "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw=="],
|
||||
|
||||
"readdir-glob/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
|
||||
|
||||
"readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
|
||||
"resend/@react-email/render": ["@react-email/render@1.1.2", "", { "dependencies": { "html-to-text": "^9.0.5", "prettier": "^3.5.3", "react-promise-suspense": "^0.3.4" }, "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-RnRehYN3v9gVlNMehHPHhyp2RQo7+pSkHDtXPvg3s0GbzM9SQMW4Qrf8GRNvtpLC4gsI+Wt0VatNRUFqjvevbw=="],
|
||||
@@ -3582,6 +3644,8 @@
|
||||
|
||||
"string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"string_decoder/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||
|
||||
"sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
|
||||
|
||||
"sucrase/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
|
||||
@@ -3768,6 +3832,12 @@
|
||||
|
||||
"accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||
|
||||
"archiver-utils/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
|
||||
|
||||
"archiver-utils/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
|
||||
|
||||
"bl/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
||||
|
||||
"cli-truncate/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||
|
||||
"cli-truncate/string-width/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
|
||||
@@ -3792,6 +3862,10 @@
|
||||
|
||||
"jest-diff/pretty-format/react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="],
|
||||
|
||||
"jszip/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
||||
|
||||
"lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
||||
|
||||
"lint-staged/listr2/cli-truncate": ["cli-truncate@4.0.0", "", { "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^7.0.0" } }, "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA=="],
|
||||
|
||||
"lint-staged/listr2/eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="],
|
||||
@@ -3890,6 +3964,8 @@
|
||||
|
||||
"@sentry/cli/node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
|
||||
|
||||
"archiver-utils/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"cli-truncate/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
|
||||
|
||||
"gaxios/node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
|
||||
|
||||
20938
package-lock.json
generated
Normal file
20938
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user