Compare commits

...

2 Commits

Author SHA1 Message Date
Theodore Li
498504c35b Fix import ordering 2026-04-04 16:49:09 -07:00
Theodore Li
c367da81fb feat(block): Add cloudwatch block (#3911)
* feat(block): add cloudwatch integration

* Fix bun lock

* Add logger, use execution timeout

* Switch metric dimensions to map style input

* Fix attribute names for dimension map

* Fix import styling

---------

Co-authored-by: Theodore Li <theo@sim.ai>
2026-04-04 16:35:36 -07:00
26 changed files with 2247 additions and 0 deletions

View File

@@ -0,0 +1,96 @@
import {
type AlarmType,
CloudWatchClient,
DescribeAlarmsCommand,
type StateValue,
} from '@aws-sdk/client-cloudwatch'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
const logger = createLogger('CloudWatchDescribeAlarms')
const DescribeAlarmsSchema = z.object({
region: z.string().min(1, 'AWS region is required'),
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
secretAccessKey: z.string().min(1, 'AWS secret access key is required'),
alarmNamePrefix: z.string().optional(),
stateValue: z.preprocess(
(v) => (v === '' ? undefined : v),
z.enum(['OK', 'ALARM', 'INSUFFICIENT_DATA']).optional()
),
alarmType: z.preprocess(
(v) => (v === '' ? undefined : v),
z.enum(['MetricAlarm', 'CompositeAlarm']).optional()
),
limit: z.preprocess(
(v) => (v === '' || v === undefined || v === null ? undefined : v),
z.number({ coerce: true }).int().positive().optional()
),
})
export async function POST(request: NextRequest) {
try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await request.json()
const validatedData = DescribeAlarmsSchema.parse(body)
const client = new CloudWatchClient({
region: validatedData.region,
credentials: {
accessKeyId: validatedData.accessKeyId,
secretAccessKey: validatedData.secretAccessKey,
},
})
const command = new DescribeAlarmsCommand({
...(validatedData.alarmNamePrefix && { AlarmNamePrefix: validatedData.alarmNamePrefix }),
...(validatedData.stateValue && { StateValue: validatedData.stateValue as StateValue }),
...(validatedData.alarmType && { AlarmTypes: [validatedData.alarmType as AlarmType] }),
...(validatedData.limit !== undefined && { MaxRecords: validatedData.limit }),
})
const response = await client.send(command)
const metricAlarms = (response.MetricAlarms ?? []).map((a) => ({
alarmName: a.AlarmName ?? '',
alarmArn: a.AlarmArn ?? '',
stateValue: a.StateValue ?? 'UNKNOWN',
stateReason: a.StateReason ?? '',
metricName: a.MetricName,
namespace: a.Namespace,
comparisonOperator: a.ComparisonOperator,
threshold: a.Threshold,
evaluationPeriods: a.EvaluationPeriods,
stateUpdatedTimestamp: a.StateUpdatedTimestamp?.getTime(),
}))
const compositeAlarms = (response.CompositeAlarms ?? []).map((a) => ({
alarmName: a.AlarmName ?? '',
alarmArn: a.AlarmArn ?? '',
stateValue: a.StateValue ?? 'UNKNOWN',
stateReason: a.StateReason ?? '',
metricName: undefined,
namespace: undefined,
comparisonOperator: undefined,
threshold: undefined,
evaluationPeriods: undefined,
stateUpdatedTimestamp: a.StateUpdatedTimestamp?.getTime(),
}))
return NextResponse.json({
success: true,
output: { alarms: [...metricAlarms, ...compositeAlarms] },
})
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : 'Failed to describe CloudWatch alarms'
logger.error('DescribeAlarms failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })
}
}

View File

@@ -0,0 +1,62 @@
import { DescribeLogGroupsCommand } from '@aws-sdk/client-cloudwatch-logs'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import { createCloudWatchLogsClient } from '@/app/api/tools/cloudwatch/utils'
const logger = createLogger('CloudWatchDescribeLogGroups')
const DescribeLogGroupsSchema = z.object({
region: z.string().min(1, 'AWS region is required'),
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
secretAccessKey: z.string().min(1, 'AWS secret access key is required'),
prefix: z.string().optional(),
limit: z.preprocess(
(v) => (v === '' || v === undefined || v === null ? undefined : v),
z.number({ coerce: true }).int().positive().optional()
),
})
export async function POST(request: NextRequest) {
try {
const auth = await checkSessionOrInternalAuth(request)
if (!auth.success || !auth.userId) {
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await request.json()
const validatedData = DescribeLogGroupsSchema.parse(body)
const client = createCloudWatchLogsClient({
region: validatedData.region,
accessKeyId: validatedData.accessKeyId,
secretAccessKey: validatedData.secretAccessKey,
})
const command = new DescribeLogGroupsCommand({
...(validatedData.prefix && { logGroupNamePrefix: validatedData.prefix }),
...(validatedData.limit !== undefined && { limit: validatedData.limit }),
})
const response = await client.send(command)
const logGroups = (response.logGroups ?? []).map((lg) => ({
logGroupName: lg.logGroupName ?? '',
arn: lg.arn ?? '',
storedBytes: lg.storedBytes ?? 0,
retentionInDays: lg.retentionInDays,
creationTime: lg.creationTime,
}))
return NextResponse.json({
success: true,
output: { logGroups },
})
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : 'Failed to describe CloudWatch log groups'
logger.error('DescribeLogGroups failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })
}
}

View File

@@ -0,0 +1,52 @@
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import { createCloudWatchLogsClient, describeLogStreams } from '@/app/api/tools/cloudwatch/utils'
const logger = createLogger('CloudWatchDescribeLogStreams')
const DescribeLogStreamsSchema = z.object({
region: z.string().min(1, 'AWS region is required'),
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
secretAccessKey: z.string().min(1, 'AWS secret access key is required'),
logGroupName: z.string().min(1, 'Log group name is required'),
prefix: z.string().optional(),
limit: z.preprocess(
(v) => (v === '' || v === undefined || v === null ? undefined : v),
z.number({ coerce: true }).int().positive().optional()
),
})
export async function POST(request: NextRequest) {
try {
const auth = await checkSessionOrInternalAuth(request)
if (!auth.success || !auth.userId) {
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await request.json()
const validatedData = DescribeLogStreamsSchema.parse(body)
const client = createCloudWatchLogsClient({
region: validatedData.region,
accessKeyId: validatedData.accessKeyId,
secretAccessKey: validatedData.secretAccessKey,
})
const result = await describeLogStreams(client, validatedData.logGroupName, {
prefix: validatedData.prefix,
limit: validatedData.limit,
})
return NextResponse.json({
success: true,
output: { logStreams: result.logStreams },
})
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : 'Failed to describe CloudWatch log streams'
logger.error('DescribeLogStreams failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })
}
}

View File

@@ -0,0 +1,60 @@
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { createCloudWatchLogsClient, getLogEvents } from '@/app/api/tools/cloudwatch/utils'
const logger = createLogger('CloudWatchGetLogEvents')
const GetLogEventsSchema = z.object({
region: z.string().min(1, 'AWS region is required'),
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
secretAccessKey: z.string().min(1, 'AWS secret access key is required'),
logGroupName: z.string().min(1, 'Log group name is required'),
logStreamName: z.string().min(1, 'Log stream name is required'),
startTime: z.number({ coerce: true }).int().optional(),
endTime: z.number({ coerce: true }).int().optional(),
limit: z.preprocess(
(v) => (v === '' || v === undefined || v === null ? undefined : v),
z.number({ coerce: true }).int().positive().optional()
),
})
export async function POST(request: NextRequest) {
try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await request.json()
const validatedData = GetLogEventsSchema.parse(body)
const client = createCloudWatchLogsClient({
region: validatedData.region,
accessKeyId: validatedData.accessKeyId,
secretAccessKey: validatedData.secretAccessKey,
})
const result = await getLogEvents(
client,
validatedData.logGroupName,
validatedData.logStreamName,
{
startTime: validatedData.startTime,
endTime: validatedData.endTime,
limit: validatedData.limit,
}
)
return NextResponse.json({
success: true,
output: { events: result.events },
})
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : 'Failed to get CloudWatch log events'
logger.error('GetLogEvents failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })
}
}

View File

@@ -0,0 +1,97 @@
import { CloudWatchClient, GetMetricStatisticsCommand } from '@aws-sdk/client-cloudwatch'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
const logger = createLogger('CloudWatchGetMetricStatistics')
const GetMetricStatisticsSchema = z.object({
region: z.string().min(1, 'AWS region is required'),
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
secretAccessKey: z.string().min(1, 'AWS secret access key is required'),
namespace: z.string().min(1, 'Namespace is required'),
metricName: z.string().min(1, 'Metric name is required'),
startTime: z.number({ coerce: true }).int(),
endTime: z.number({ coerce: true }).int(),
period: z.number({ coerce: true }).int().min(1),
statistics: z.array(z.enum(['Average', 'Sum', 'Minimum', 'Maximum', 'SampleCount'])).min(1),
dimensions: z.string().optional(),
})
export async function POST(request: NextRequest) {
try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await request.json()
const validatedData = GetMetricStatisticsSchema.parse(body)
const client = new CloudWatchClient({
region: validatedData.region,
credentials: {
accessKeyId: validatedData.accessKeyId,
secretAccessKey: validatedData.secretAccessKey,
},
})
let parsedDimensions: { Name: string; Value: string }[] | undefined
if (validatedData.dimensions) {
try {
const dims = JSON.parse(validatedData.dimensions)
if (Array.isArray(dims)) {
parsedDimensions = dims.map((d: Record<string, string>) => ({
Name: d.name,
Value: d.value,
}))
} else if (typeof dims === 'object') {
parsedDimensions = Object.entries(dims).map(([name, value]) => ({
Name: name,
Value: String(value),
}))
}
} catch {
throw new Error('Invalid dimensions JSON')
}
}
const command = new GetMetricStatisticsCommand({
Namespace: validatedData.namespace,
MetricName: validatedData.metricName,
StartTime: new Date(validatedData.startTime * 1000),
EndTime: new Date(validatedData.endTime * 1000),
Period: validatedData.period,
Statistics: validatedData.statistics,
...(parsedDimensions && { Dimensions: parsedDimensions }),
})
const response = await client.send(command)
const datapoints = (response.Datapoints ?? [])
.sort((a, b) => (a.Timestamp?.getTime() ?? 0) - (b.Timestamp?.getTime() ?? 0))
.map((dp) => ({
timestamp: dp.Timestamp ? Math.floor(dp.Timestamp.getTime() / 1000) : 0,
average: dp.Average,
sum: dp.Sum,
minimum: dp.Minimum,
maximum: dp.Maximum,
sampleCount: dp.SampleCount,
unit: dp.Unit,
}))
return NextResponse.json({
success: true,
output: {
label: response.Label ?? validatedData.metricName,
datapoints,
},
})
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : 'Failed to get CloudWatch metric statistics'
logger.error('GetMetricStatistics failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })
}
}

View File

@@ -0,0 +1,67 @@
import { CloudWatchClient, ListMetricsCommand } from '@aws-sdk/client-cloudwatch'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
const logger = createLogger('CloudWatchListMetrics')
const ListMetricsSchema = z.object({
region: z.string().min(1, 'AWS region is required'),
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
secretAccessKey: z.string().min(1, 'AWS secret access key is required'),
namespace: z.string().optional(),
metricName: z.string().optional(),
recentlyActive: z.boolean().optional(),
limit: z.preprocess(
(v) => (v === '' || v === undefined || v === null ? undefined : v),
z.number({ coerce: true }).int().positive().optional()
),
})
export async function POST(request: NextRequest) {
try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await request.json()
const validatedData = ListMetricsSchema.parse(body)
const client = new CloudWatchClient({
region: validatedData.region,
credentials: {
accessKeyId: validatedData.accessKeyId,
secretAccessKey: validatedData.secretAccessKey,
},
})
const command = new ListMetricsCommand({
...(validatedData.namespace && { Namespace: validatedData.namespace }),
...(validatedData.metricName && { MetricName: validatedData.metricName }),
...(validatedData.recentlyActive && { RecentlyActive: 'PT3H' }),
})
const response = await client.send(command)
const metrics = (response.Metrics ?? []).slice(0, validatedData.limit ?? 500).map((m) => ({
namespace: m.Namespace ?? '',
metricName: m.MetricName ?? '',
dimensions: (m.Dimensions ?? []).map((d) => ({
name: d.Name ?? '',
value: d.Value ?? '',
})),
}))
return NextResponse.json({
success: true,
output: { metrics },
})
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : 'Failed to list CloudWatch metrics'
logger.error('ListMetrics failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })
}
}

View File

@@ -0,0 +1,71 @@
import { StartQueryCommand } from '@aws-sdk/client-cloudwatch-logs'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { createCloudWatchLogsClient, pollQueryResults } from '@/app/api/tools/cloudwatch/utils'
const logger = createLogger('CloudWatchQueryLogs')
const QueryLogsSchema = z.object({
region: z.string().min(1, 'AWS region is required'),
accessKeyId: z.string().min(1, 'AWS access key ID is required'),
secretAccessKey: z.string().min(1, 'AWS secret access key is required'),
logGroupNames: z.array(z.string().min(1)).min(1, 'At least one log group name is required'),
queryString: z.string().min(1, 'Query string is required'),
startTime: z.number({ coerce: true }).int(),
endTime: z.number({ coerce: true }).int(),
limit: z.preprocess(
(v) => (v === '' || v === undefined || v === null ? undefined : v),
z.number({ coerce: true }).int().positive().optional()
),
})
export async function POST(request: NextRequest) {
try {
const auth = await checkInternalAuth(request)
if (!auth.success || !auth.userId) {
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
}
const body = await request.json()
const validatedData = QueryLogsSchema.parse(body)
const client = createCloudWatchLogsClient({
region: validatedData.region,
accessKeyId: validatedData.accessKeyId,
secretAccessKey: validatedData.secretAccessKey,
})
const startQueryCommand = new StartQueryCommand({
logGroupNames: validatedData.logGroupNames,
queryString: validatedData.queryString,
startTime: validatedData.startTime,
endTime: validatedData.endTime,
...(validatedData.limit !== undefined && { limit: validatedData.limit }),
})
const startQueryResponse = await client.send(startQueryCommand)
const queryId = startQueryResponse.queryId
if (!queryId) {
throw new Error('Failed to start CloudWatch Log Insights query: no queryId returned')
}
const result = await pollQueryResults(client, queryId)
return NextResponse.json({
success: true,
output: {
results: result.results,
statistics: result.statistics,
status: result.status,
},
})
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : 'CloudWatch Log Insights query failed'
logger.error('QueryLogs failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })
}
}

View File

@@ -0,0 +1,161 @@
import {
CloudWatchLogsClient,
DescribeLogStreamsCommand,
GetLogEventsCommand,
GetQueryResultsCommand,
type ResultField,
} from '@aws-sdk/client-cloudwatch-logs'
import { DEFAULT_EXECUTION_TIMEOUT_MS } from '@/lib/core/execution-limits'
interface AwsCredentials {
region: string
accessKeyId: string
secretAccessKey: string
}
export function createCloudWatchLogsClient(config: AwsCredentials): CloudWatchLogsClient {
return new CloudWatchLogsClient({
region: config.region,
credentials: {
accessKeyId: config.accessKeyId,
secretAccessKey: config.secretAccessKey,
},
})
}
interface PollOptions {
maxWaitMs?: number
pollIntervalMs?: number
}
interface PollResult {
results: Record<string, string>[]
statistics: {
bytesScanned: number
recordsMatched: number
recordsScanned: number
}
status: string
}
function parseResultFields(fields: ResultField[] | undefined): Record<string, string> {
const record: Record<string, string> = {}
if (!fields) return record
for (const field of fields) {
if (field.field && field.value !== undefined) {
record[field.field] = field.value ?? ''
}
}
return record
}
export async function pollQueryResults(
client: CloudWatchLogsClient,
queryId: string,
options: PollOptions = {}
): Promise<PollResult> {
const { maxWaitMs = DEFAULT_EXECUTION_TIMEOUT_MS, pollIntervalMs = 1_000 } = options
const startTime = Date.now()
while (Date.now() - startTime < maxWaitMs) {
const command = new GetQueryResultsCommand({ queryId })
const response = await client.send(command)
const status = response.status ?? 'Unknown'
if (status === 'Complete') {
return {
results: (response.results ?? []).map(parseResultFields),
statistics: {
bytesScanned: response.statistics?.bytesScanned ?? 0,
recordsMatched: response.statistics?.recordsMatched ?? 0,
recordsScanned: response.statistics?.recordsScanned ?? 0,
},
status,
}
}
if (status === 'Failed' || status === 'Cancelled') {
throw new Error(`CloudWatch Log Insights query ${status.toLowerCase()}`)
}
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs))
}
// Timeout -- fetch one last time for partial results
const finalResponse = await client.send(new GetQueryResultsCommand({ queryId }))
return {
results: (finalResponse.results ?? []).map(parseResultFields),
statistics: {
bytesScanned: finalResponse.statistics?.bytesScanned ?? 0,
recordsMatched: finalResponse.statistics?.recordsMatched ?? 0,
recordsScanned: finalResponse.statistics?.recordsScanned ?? 0,
},
status: `Timeout (last status: ${finalResponse.status ?? 'Unknown'})`,
}
}
export async function describeLogStreams(
client: CloudWatchLogsClient,
logGroupName: string,
options?: { prefix?: string; limit?: number }
): Promise<{
logStreams: {
logStreamName: string
lastEventTimestamp: number | undefined
firstEventTimestamp: number | undefined
creationTime: number | undefined
storedBytes: number
}[]
}> {
const hasPrefix = Boolean(options?.prefix)
const command = new DescribeLogStreamsCommand({
logGroupName,
...(hasPrefix
? { orderBy: 'LogStreamName', logStreamNamePrefix: options!.prefix }
: { orderBy: 'LastEventTime', descending: true }),
...(options?.limit !== undefined && { limit: options.limit }),
})
const response = await client.send(command)
return {
logStreams: (response.logStreams ?? []).map((ls) => ({
logStreamName: ls.logStreamName ?? '',
lastEventTimestamp: ls.lastEventTimestamp,
firstEventTimestamp: ls.firstEventTimestamp,
creationTime: ls.creationTime,
storedBytes: ls.storedBytes ?? 0,
})),
}
}
export async function getLogEvents(
client: CloudWatchLogsClient,
logGroupName: string,
logStreamName: string,
options?: { startTime?: number; endTime?: number; limit?: number }
): Promise<{
events: {
timestamp: number | undefined
message: string | undefined
ingestionTime: number | undefined
}[]
}> {
const command = new GetLogEventsCommand({
logGroupIdentifier: logGroupName,
logStreamName,
...(options?.startTime !== undefined && { startTime: options.startTime * 1000 }),
...(options?.endTime !== undefined && { endTime: options.endTime * 1000 }),
...(options?.limit !== undefined && { limit: options.limit }),
startFromHead: true,
})
const response = await client.send(command)
return {
events: (response.events ?? []).map((e) => ({
timestamp: e.timestamp,
message: e.message,
ingestionTime: e.ingestionTime,
})),
}
}

View File

@@ -0,0 +1,571 @@
import { CloudWatchIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { IntegrationType } from '@/blocks/types'
import type {
CloudWatchDescribeAlarmsResponse,
CloudWatchDescribeLogGroupsResponse,
CloudWatchDescribeLogStreamsResponse,
CloudWatchGetLogEventsResponse,
CloudWatchGetMetricStatisticsResponse,
CloudWatchListMetricsResponse,
CloudWatchQueryLogsResponse,
} from '@/tools/cloudwatch/types'
export const CloudWatchBlock: BlockConfig<
| CloudWatchQueryLogsResponse
| CloudWatchDescribeLogGroupsResponse
| CloudWatchDescribeLogStreamsResponse
| CloudWatchGetLogEventsResponse
| CloudWatchDescribeAlarmsResponse
| CloudWatchListMetricsResponse
| CloudWatchGetMetricStatisticsResponse
> = {
type: 'cloudwatch',
name: 'CloudWatch',
description: 'Query and monitor AWS CloudWatch logs, metrics, and alarms',
longDescription:
'Integrate AWS CloudWatch into workflows. Run Log Insights queries, list log groups, retrieve log events, list and get metrics, and monitor alarms. Requires AWS access key and secret access key.',
category: 'tools',
integrationType: IntegrationType.Analytics,
tags: ['cloud', 'monitoring'],
bgColor: 'linear-gradient(45deg, #B0084D 0%, #FF4F8B 100%)',
icon: CloudWatchIcon,
subBlocks: [
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
{ label: 'Query Logs (Insights)', id: 'query_logs' },
{ label: 'Describe Log Groups', id: 'describe_log_groups' },
{ label: 'Get Log Events', id: 'get_log_events' },
{ label: 'Describe Log Streams', id: 'describe_log_streams' },
{ label: 'List Metrics', id: 'list_metrics' },
{ label: 'Get Metric Statistics', id: 'get_metric_statistics' },
{ label: 'Describe Alarms', id: 'describe_alarms' },
],
value: () => 'query_logs',
},
{
id: 'awsRegion',
title: 'AWS Region',
type: 'short-input',
placeholder: 'us-east-1',
required: true,
},
{
id: 'awsAccessKeyId',
title: 'AWS Access Key ID',
type: 'short-input',
placeholder: 'AKIA...',
password: true,
required: true,
},
{
id: 'awsSecretAccessKey',
title: 'AWS Secret Access Key',
type: 'short-input',
placeholder: 'Your secret access key',
password: true,
required: true,
},
// Query Logs fields
{
id: 'logGroupSelector',
title: 'Log Group',
type: 'file-selector',
canonicalParamId: 'logGroupNames',
selectorKey: 'cloudwatch.logGroups',
dependsOn: ['awsAccessKeyId', 'awsSecretAccessKey', 'awsRegion'],
placeholder: 'Select a log group',
condition: { field: 'operation', value: 'query_logs' },
required: { field: 'operation', value: 'query_logs' },
mode: 'basic',
},
{
id: 'logGroupNamesInput',
title: 'Log Group Names',
type: 'short-input',
canonicalParamId: 'logGroupNames',
placeholder: '/aws/lambda/my-func, /aws/ecs/my-service',
condition: { field: 'operation', value: 'query_logs' },
required: { field: 'operation', value: 'query_logs' },
mode: 'advanced',
},
{
id: 'queryString',
title: 'Query',
type: 'code',
placeholder: 'fields @timestamp, @message\n| sort @timestamp desc\n| limit 20',
condition: { field: 'operation', value: 'query_logs' },
required: { field: 'operation', value: 'query_logs' },
wandConfig: {
enabled: true,
prompt: `Generate a CloudWatch Log Insights query based on the user's description.
The query language supports: fields, filter, stats, sort, limit, parse, display.
Common patterns:
- fields @timestamp, @message | sort @timestamp desc | limit 20
- filter @message like /ERROR/ | stats count(*) by bin(1h)
- stats avg(duration) as avgDuration by functionName | sort avgDuration desc
- filter @message like /Exception/ | parse @message "* Exception: *" as prefix, errorMsg
- stats count(*) as requestCount by status | sort requestCount desc
Return ONLY the query — no explanations, no markdown code blocks.`,
placeholder: 'Describe what you want to find in the logs...',
},
},
{
id: 'startTime',
title: 'Start Time (Unix epoch seconds)',
type: 'short-input',
placeholder: 'e.g., 1711900800',
condition: {
field: 'operation',
value: ['query_logs', 'get_log_events', 'get_metric_statistics'],
},
required: { field: 'operation', value: ['query_logs', 'get_metric_statistics'] },
},
{
id: 'endTime',
title: 'End Time (Unix epoch seconds)',
type: 'short-input',
placeholder: 'e.g., 1711987200',
condition: {
field: 'operation',
value: ['query_logs', 'get_log_events', 'get_metric_statistics'],
},
required: { field: 'operation', value: ['query_logs', 'get_metric_statistics'] },
},
// Describe Log Groups fields
{
id: 'prefix',
title: 'Log Group Name Prefix',
type: 'short-input',
placeholder: '/aws/lambda/',
condition: { field: 'operation', value: 'describe_log_groups' },
},
// Get Log Events / Describe Log Streams — shared log group selector
{
id: 'logGroupNameSelector',
title: 'Log Group',
type: 'file-selector',
canonicalParamId: 'logGroupName',
selectorKey: 'cloudwatch.logGroups',
dependsOn: ['awsAccessKeyId', 'awsSecretAccessKey', 'awsRegion'],
placeholder: 'Select a log group',
condition: { field: 'operation', value: ['get_log_events', 'describe_log_streams'] },
required: { field: 'operation', value: ['get_log_events', 'describe_log_streams'] },
mode: 'basic',
},
{
id: 'logGroupNameInput',
title: 'Log Group Name',
type: 'short-input',
canonicalParamId: 'logGroupName',
placeholder: '/aws/lambda/my-func',
condition: { field: 'operation', value: ['get_log_events', 'describe_log_streams'] },
required: { field: 'operation', value: ['get_log_events', 'describe_log_streams'] },
mode: 'advanced',
},
// Describe Log Streams — stream prefix filter
{
id: 'streamPrefix',
title: 'Stream Name Prefix',
type: 'short-input',
placeholder: '2024/03/31/',
condition: { field: 'operation', value: 'describe_log_streams' },
},
// Get Log Events — log stream selector (cascading: depends on log group)
{
id: 'logStreamNameSelector',
title: 'Log Stream',
type: 'file-selector',
canonicalParamId: 'logStreamName',
selectorKey: 'cloudwatch.logStreams',
dependsOn: ['awsAccessKeyId', 'awsSecretAccessKey', 'awsRegion', 'logGroupNameSelector'],
placeholder: 'Select a log stream',
condition: { field: 'operation', value: 'get_log_events' },
required: { field: 'operation', value: 'get_log_events' },
mode: 'basic',
},
{
id: 'logStreamNameInput',
title: 'Log Stream Name',
type: 'short-input',
canonicalParamId: 'logStreamName',
placeholder: '2024/03/31/[$LATEST]abc123',
condition: { field: 'operation', value: 'get_log_events' },
required: { field: 'operation', value: 'get_log_events' },
mode: 'advanced',
},
// List Metrics fields
{
id: 'metricNamespace',
title: 'Namespace',
type: 'short-input',
placeholder: 'e.g., AWS/EC2, AWS/Lambda, AWS/RDS',
condition: { field: 'operation', value: ['list_metrics', 'get_metric_statistics'] },
required: { field: 'operation', value: 'get_metric_statistics' },
},
{
id: 'metricName',
title: 'Metric Name',
type: 'short-input',
placeholder: 'e.g., CPUUtilization, Invocations',
condition: { field: 'operation', value: ['list_metrics', 'get_metric_statistics'] },
required: { field: 'operation', value: 'get_metric_statistics' },
},
{
id: 'recentlyActive',
title: 'Recently Active Only',
type: 'switch',
condition: { field: 'operation', value: 'list_metrics' },
},
// Get Metric Statistics fields
{
id: 'metricPeriod',
title: 'Period (seconds)',
type: 'short-input',
placeholder: 'e.g., 60, 300, 3600',
condition: { field: 'operation', value: 'get_metric_statistics' },
required: { field: 'operation', value: 'get_metric_statistics' },
},
{
id: 'metricStatistics',
title: 'Statistics',
type: 'dropdown',
options: [
{ label: 'Average', id: 'Average' },
{ label: 'Sum', id: 'Sum' },
{ label: 'Minimum', id: 'Minimum' },
{ label: 'Maximum', id: 'Maximum' },
{ label: 'Sample Count', id: 'SampleCount' },
],
condition: { field: 'operation', value: 'get_metric_statistics' },
required: { field: 'operation', value: 'get_metric_statistics' },
},
{
id: 'metricDimensions',
title: 'Dimensions',
type: 'table',
columns: ['name', 'value'],
condition: { field: 'operation', value: 'get_metric_statistics' },
},
// Describe Alarms fields
{
id: 'alarmNamePrefix',
title: 'Alarm Name Prefix',
type: 'short-input',
placeholder: 'my-service-',
condition: { field: 'operation', value: 'describe_alarms' },
},
{
id: 'stateValue',
title: 'State',
type: 'dropdown',
options: [
{ label: 'All States', id: '' },
{ label: 'OK', id: 'OK' },
{ label: 'ALARM', id: 'ALARM' },
{ label: 'INSUFFICIENT_DATA', id: 'INSUFFICIENT_DATA' },
],
condition: { field: 'operation', value: 'describe_alarms' },
},
{
id: 'alarmType',
title: 'Alarm Type',
type: 'dropdown',
options: [
{ label: 'All Types', id: '' },
{ label: 'Metric Alarm', id: 'MetricAlarm' },
{ label: 'Composite Alarm', id: 'CompositeAlarm' },
],
condition: { field: 'operation', value: 'describe_alarms' },
},
// Shared limit field
{
id: 'limit',
title: 'Limit',
type: 'short-input',
placeholder: '100',
condition: {
field: 'operation',
value: [
'query_logs',
'describe_log_groups',
'get_log_events',
'describe_log_streams',
'list_metrics',
'describe_alarms',
],
},
},
],
tools: {
access: [
'cloudwatch_query_logs',
'cloudwatch_describe_log_groups',
'cloudwatch_get_log_events',
'cloudwatch_describe_log_streams',
'cloudwatch_list_metrics',
'cloudwatch_get_metric_statistics',
'cloudwatch_describe_alarms',
],
config: {
tool: (params) => {
switch (params.operation) {
case 'query_logs':
return 'cloudwatch_query_logs'
case 'describe_log_groups':
return 'cloudwatch_describe_log_groups'
case 'get_log_events':
return 'cloudwatch_get_log_events'
case 'describe_log_streams':
return 'cloudwatch_describe_log_streams'
case 'list_metrics':
return 'cloudwatch_list_metrics'
case 'get_metric_statistics':
return 'cloudwatch_get_metric_statistics'
case 'describe_alarms':
return 'cloudwatch_describe_alarms'
default:
throw new Error(`Invalid CloudWatch operation: ${params.operation}`)
}
},
params: (params) => {
const { operation, startTime, endTime, limit, ...rest } = params
const awsRegion = rest.awsRegion
const awsAccessKeyId = rest.awsAccessKeyId
const awsSecretAccessKey = rest.awsSecretAccessKey
const parsedLimit = limit ? Number.parseInt(String(limit), 10) : undefined
switch (operation) {
case 'query_logs': {
const logGroupNames = rest.logGroupNames
if (!logGroupNames) {
throw new Error('Log group names are required')
}
if (!startTime) {
throw new Error('Start time is required')
}
if (!endTime) {
throw new Error('End time is required')
}
const groupNames =
typeof logGroupNames === 'string'
? logGroupNames
.split(',')
.map((n: string) => n.trim())
.filter(Boolean)
: logGroupNames
return {
awsRegion,
awsAccessKeyId,
awsSecretAccessKey,
logGroupNames: groupNames,
queryString: rest.queryString,
startTime: Number(startTime),
endTime: Number(endTime),
...(parsedLimit !== undefined && { limit: parsedLimit }),
}
}
case 'describe_log_groups':
return {
awsRegion,
awsAccessKeyId,
awsSecretAccessKey,
...(rest.prefix && { prefix: rest.prefix }),
...(parsedLimit !== undefined && { limit: parsedLimit }),
}
case 'get_log_events': {
if (!rest.logGroupName) {
throw new Error('Log group name is required')
}
if (!rest.logStreamName) {
throw new Error('Log stream name is required')
}
return {
awsRegion,
awsAccessKeyId,
awsSecretAccessKey,
logGroupName: rest.logGroupName,
logStreamName: rest.logStreamName,
...(startTime && { startTime: Number(startTime) }),
...(endTime && { endTime: Number(endTime) }),
...(parsedLimit !== undefined && { limit: parsedLimit }),
}
}
case 'describe_log_streams': {
if (!rest.logGroupName) {
throw new Error('Log group name is required')
}
return {
awsRegion,
awsAccessKeyId,
awsSecretAccessKey,
logGroupName: rest.logGroupName,
...(rest.streamPrefix && { prefix: rest.streamPrefix }),
...(parsedLimit !== undefined && { limit: parsedLimit }),
}
}
case 'list_metrics':
return {
awsRegion,
awsAccessKeyId,
awsSecretAccessKey,
...(rest.metricNamespace && { namespace: rest.metricNamespace }),
...(rest.metricName && { metricName: rest.metricName }),
...(rest.recentlyActive && { recentlyActive: true }),
...(parsedLimit !== undefined && { limit: parsedLimit }),
}
case 'get_metric_statistics': {
if (!rest.metricNamespace) {
throw new Error('Namespace is required')
}
if (!rest.metricName) {
throw new Error('Metric name is required')
}
if (!startTime) {
throw new Error('Start time is required')
}
if (!endTime) {
throw new Error('End time is required')
}
if (!rest.metricPeriod) {
throw new Error('Period is required')
}
const stat = rest.metricStatistics
if (!stat) {
throw new Error('Statistics is required')
}
return {
awsRegion,
awsAccessKeyId,
awsSecretAccessKey,
namespace: rest.metricNamespace,
metricName: rest.metricName,
startTime: Number(startTime),
endTime: Number(endTime),
period: Number(rest.metricPeriod),
statistics: Array.isArray(stat) ? stat : [stat],
...(rest.metricDimensions && {
dimensions: (() => {
const dims = rest.metricDimensions
if (typeof dims === 'string') return dims
if (Array.isArray(dims)) {
const obj: Record<string, string> = {}
for (const row of dims) {
const name = row.cells?.name
const value = row.cells?.value
if (name && value !== undefined) obj[name] = String(value)
}
return JSON.stringify(obj)
}
return JSON.stringify(dims)
})(),
}),
}
}
case 'describe_alarms':
return {
awsRegion,
awsAccessKeyId,
awsSecretAccessKey,
...(rest.alarmNamePrefix && { alarmNamePrefix: rest.alarmNamePrefix }),
...(rest.stateValue && { stateValue: rest.stateValue }),
...(rest.alarmType && { alarmType: rest.alarmType }),
...(parsedLimit !== undefined && { limit: parsedLimit }),
}
default:
throw new Error(`Invalid CloudWatch operation: ${operation}`)
}
},
},
},
inputs: {
operation: { type: 'string', description: 'CloudWatch operation to perform' },
awsRegion: { type: 'string', description: 'AWS region' },
awsAccessKeyId: { type: 'string', description: 'AWS access key ID' },
awsSecretAccessKey: { type: 'string', description: 'AWS secret access key' },
logGroupNames: { type: 'string', description: 'Log group name(s) for query' },
queryString: { type: 'string', description: 'CloudWatch Log Insights query string' },
startTime: { type: 'string', description: 'Start time as Unix epoch seconds' },
endTime: { type: 'string', description: 'End time as Unix epoch seconds' },
prefix: { type: 'string', description: 'Log group name prefix filter' },
logGroupName: {
type: 'string',
description: 'Log group name for get events / describe streams',
},
logStreamName: { type: 'string', description: 'Log stream name for get events' },
streamPrefix: { type: 'string', description: 'Log stream name prefix filter' },
metricNamespace: { type: 'string', description: 'Metric namespace (e.g., AWS/EC2)' },
metricName: { type: 'string', description: 'Metric name (e.g., CPUUtilization)' },
recentlyActive: { type: 'boolean', description: 'Only show recently active metrics' },
metricPeriod: { type: 'number', description: 'Granularity in seconds' },
metricStatistics: { type: 'string', description: 'Statistic type (Average, Sum, etc.)' },
metricDimensions: { type: 'json', description: 'Metric dimensions (Name/Value pairs)' },
alarmNamePrefix: { type: 'string', description: 'Alarm name prefix filter' },
stateValue: {
type: 'string',
description: 'Alarm state filter (OK, ALARM, INSUFFICIENT_DATA)',
},
alarmType: { type: 'string', description: 'Alarm type filter (MetricAlarm, CompositeAlarm)' },
limit: { type: 'number', description: 'Maximum number of results' },
},
outputs: {
results: {
type: 'array',
description: 'Log Insights query result rows',
},
statistics: {
type: 'json',
description: 'Query statistics (bytesScanned, recordsMatched, recordsScanned)',
},
status: {
type: 'string',
description: 'Query completion status',
},
logGroups: {
type: 'array',
description: 'List of CloudWatch log groups',
},
events: {
type: 'array',
description: 'Log events with timestamp and message',
},
logStreams: {
type: 'array',
description: 'Log streams with metadata',
},
metrics: {
type: 'array',
description: 'List of available metrics',
},
label: {
type: 'string',
description: 'Metric label',
},
datapoints: {
type: 'array',
description: 'Metric datapoints with timestamps and values',
},
alarms: {
type: 'array',
description: 'CloudWatch alarms with state and configuration',
},
},
}

View File

@@ -24,6 +24,7 @@ import { CirclebackBlock } from '@/blocks/blocks/circleback'
import { ClayBlock } from '@/blocks/blocks/clay'
import { ClerkBlock } from '@/blocks/blocks/clerk'
import { CloudflareBlock } from '@/blocks/blocks/cloudflare'
import { CloudWatchBlock } from '@/blocks/blocks/cloudwatch'
import { ConditionBlock } from '@/blocks/blocks/condition'
import { ConfluenceBlock, ConfluenceV2Block } from '@/blocks/blocks/confluence'
import { CredentialBlock } from '@/blocks/blocks/credential'
@@ -241,6 +242,7 @@ export const registry: Record<string, BlockConfig> = {
chat_trigger: ChatTriggerBlock,
circleback: CirclebackBlock,
cloudflare: CloudflareBlock,
cloudwatch: CloudWatchBlock,
clay: ClayBlock,
clerk: ClerkBlock,
condition: ConditionBlock,

View File

@@ -4653,6 +4653,33 @@ export function SQSIcon(props: SVGProps<SVGSVGElement>) {
)
}
export function CloudWatchIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
viewBox='0 0 80 80'
version='1.1'
xmlns='http://www.w3.org/2000/svg'
xmlnsXlink='http://www.w3.org/1999/xlink'
>
<g
id='Icon-Architecture/64/Arch_Amazon-CloudWatch_64'
stroke='none'
strokeWidth='1'
fill='none'
fillRule='evenodd'
transform='translate(40, 40) scale(1.25) translate(-40, -40)'
>
<path
d='M53,42 L41,42 L41,24 L43,24 L43,40 L53,40 L53,42 Z M40,66 C24.561,66 12,53.439 12,38 C12,22.561 24.561,10 40,10 C55.439,10 68,22.561 68,38 C68,53.439 55.439,66 40,66 M40,8 C23.458,8 10,21.458 10,38 C10,54.542 23.458,68 40,68 C56.542,68 70,54.542 70,38 C70,21.458 56.542,8 40,8'
id='Amazon-CloudWatch_Icon_64_Squid'
fill='currentColor'
/>
</g>
</svg>
)
}
export function TextractIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg

View File

@@ -1716,6 +1716,81 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
}))
},
},
'cloudwatch.logGroups': {
key: 'cloudwatch.logGroups',
staleTime: SELECTOR_STALE,
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'cloudwatch.logGroups',
context.awsAccessKeyId ?? 'none',
context.awsRegion ?? 'none',
],
enabled: ({ context }) =>
Boolean(context.awsAccessKeyId && context.awsSecretAccessKey && context.awsRegion),
fetchList: async ({ context, search }: SelectorQueryArgs) => {
const body = JSON.stringify({
accessKeyId: context.awsAccessKeyId,
secretAccessKey: context.awsSecretAccessKey,
region: context.awsRegion,
...(search && { prefix: search }),
})
const data = await fetchJson<{
output: { logGroups: { logGroupName: string }[] }
}>('/api/tools/cloudwatch/describe-log-groups', {
method: 'POST',
body,
})
return (data.output?.logGroups || []).map((lg) => ({
id: lg.logGroupName,
label: lg.logGroupName,
}))
},
fetchById: async ({ detailId }: SelectorQueryArgs) => {
if (!detailId) return null
return { id: detailId, label: detailId }
},
},
'cloudwatch.logStreams': {
key: 'cloudwatch.logStreams',
staleTime: SELECTOR_STALE,
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'cloudwatch.logStreams',
context.awsAccessKeyId ?? 'none',
context.awsRegion ?? 'none',
context.logGroupName ?? 'none',
],
enabled: ({ context }) =>
Boolean(
context.awsAccessKeyId &&
context.awsSecretAccessKey &&
context.awsRegion &&
context.logGroupName
),
fetchList: async ({ context, search }: SelectorQueryArgs) => {
const body = JSON.stringify({
accessKeyId: context.awsAccessKeyId,
secretAccessKey: context.awsSecretAccessKey,
region: context.awsRegion,
logGroupName: context.logGroupName,
...(search && { prefix: search }),
})
const data = await fetchJson<{
output: { logStreams: { logStreamName: string; lastEventTimestamp?: number }[] }
}>('/api/tools/cloudwatch/describe-log-streams', {
method: 'POST',
body,
})
return (data.output?.logStreams || []).map((ls) => ({
id: ls.logStreamName,
label: ls.logStreamName,
}))
},
fetchById: async ({ detailId }: SelectorQueryArgs) => {
if (!detailId) return null
return { id: detailId, label: detailId }
},
},
'sim.workflows': {
key: 'sim.workflows',
staleTime: SELECTOR_STALE,

View File

@@ -49,6 +49,8 @@ export type SelectorKey =
| 'webflow.sites'
| 'webflow.collections'
| 'webflow.items'
| 'cloudwatch.logGroups'
| 'cloudwatch.logStreams'
| 'sim.workflows'
export interface SelectorOption {
@@ -78,6 +80,10 @@ export interface SelectorContext {
datasetId?: string
serviceDeskId?: string
impersonateUserEmail?: string
awsAccessKeyId?: string
awsSecretAccessKey?: string
awsRegion?: string
logGroupName?: string
}
export interface SelectorQueryArgs {

View File

@@ -22,6 +22,10 @@ export const SELECTOR_CONTEXT_FIELDS = new Set<keyof SelectorContext>([
'datasetId',
'serviceDeskId',
'impersonateUserEmail',
'awsAccessKeyId',
'awsSecretAccessKey',
'awsRegion',
'logGroupName',
])
/**

View File

@@ -37,6 +37,8 @@
"@a2a-js/sdk": "0.3.7",
"@anthropic-ai/sdk": "0.71.2",
"@aws-sdk/client-bedrock-runtime": "3.940.0",
"@aws-sdk/client-cloudwatch": "3.940.0",
"@aws-sdk/client-cloudwatch-logs": "3.940.0",
"@aws-sdk/client-dynamodb": "3.940.0",
"@aws-sdk/client-rds-data": "3.940.0",
"@aws-sdk/client-s3": "^3.779.0",

View File

@@ -0,0 +1,99 @@
import type {
CloudWatchDescribeAlarmsParams,
CloudWatchDescribeAlarmsResponse,
} from '@/tools/cloudwatch/types'
import type { ToolConfig } from '@/tools/types'
export const describeAlarmsTool: ToolConfig<
CloudWatchDescribeAlarmsParams,
CloudWatchDescribeAlarmsResponse
> = {
id: 'cloudwatch_describe_alarms',
name: 'CloudWatch Describe Alarms',
description: 'List and filter CloudWatch alarms',
version: '1.0',
params: {
awsRegion: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AWS region (e.g., us-east-1)',
},
awsAccessKeyId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AWS access key ID',
},
awsSecretAccessKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AWS secret access key',
},
alarmNamePrefix: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter alarms by name prefix',
},
stateValue: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by alarm state (OK, ALARM, INSUFFICIENT_DATA)',
},
alarmType: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by alarm type (MetricAlarm, CompositeAlarm)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of alarms to return',
},
},
request: {
url: '/api/tools/cloudwatch/describe-alarms',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
region: params.awsRegion,
accessKeyId: params.awsAccessKeyId,
secretAccessKey: params.awsSecretAccessKey,
...(params.alarmNamePrefix && { alarmNamePrefix: params.alarmNamePrefix }),
...(params.stateValue && { stateValue: params.stateValue }),
...(params.alarmType && { alarmType: params.alarmType }),
...(params.limit !== undefined && { limit: params.limit }),
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || 'Failed to describe CloudWatch alarms')
}
return {
success: true,
output: {
alarms: data.output.alarms,
},
}
},
outputs: {
alarms: {
type: 'array',
description: 'List of CloudWatch alarms with state and configuration',
},
},
}

View File

@@ -0,0 +1,82 @@
import type {
CloudWatchDescribeLogGroupsParams,
CloudWatchDescribeLogGroupsResponse,
} from '@/tools/cloudwatch/types'
import type { ToolConfig } from '@/tools/types'
export const describeLogGroupsTool: ToolConfig<
CloudWatchDescribeLogGroupsParams,
CloudWatchDescribeLogGroupsResponse
> = {
id: 'cloudwatch_describe_log_groups',
name: 'CloudWatch Describe Log Groups',
description: 'List available CloudWatch log groups',
version: '1.0',
params: {
awsRegion: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AWS region (e.g., us-east-1)',
},
awsAccessKeyId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AWS access key ID',
},
awsSecretAccessKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AWS secret access key',
},
prefix: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter log groups by name prefix',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of log groups to return',
},
},
request: {
url: '/api/tools/cloudwatch/describe-log-groups',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
region: params.awsRegion,
accessKeyId: params.awsAccessKeyId,
secretAccessKey: params.awsSecretAccessKey,
...(params.prefix && { prefix: params.prefix }),
...(params.limit !== undefined && { limit: params.limit }),
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || 'Failed to describe CloudWatch log groups')
}
return {
success: true,
output: {
logGroups: data.output.logGroups,
},
}
},
outputs: {
logGroups: { type: 'array', description: 'List of CloudWatch log groups with metadata' },
},
}

View File

@@ -0,0 +1,92 @@
import type {
CloudWatchDescribeLogStreamsParams,
CloudWatchDescribeLogStreamsResponse,
} from '@/tools/cloudwatch/types'
import type { ToolConfig } from '@/tools/types'
export const describeLogStreamsTool: ToolConfig<
CloudWatchDescribeLogStreamsParams,
CloudWatchDescribeLogStreamsResponse
> = {
id: 'cloudwatch_describe_log_streams',
name: 'CloudWatch Describe Log Streams',
description: 'List log streams within a CloudWatch log group',
version: '1.0',
params: {
awsRegion: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AWS region (e.g., us-east-1)',
},
awsAccessKeyId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AWS access key ID',
},
awsSecretAccessKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AWS secret access key',
},
logGroupName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'CloudWatch log group name',
},
prefix: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter log streams by name prefix',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of log streams to return',
},
},
request: {
url: '/api/tools/cloudwatch/describe-log-streams',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
region: params.awsRegion,
accessKeyId: params.awsAccessKeyId,
secretAccessKey: params.awsSecretAccessKey,
logGroupName: params.logGroupName,
...(params.prefix && { prefix: params.prefix }),
...(params.limit !== undefined && { limit: params.limit }),
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || 'Failed to describe CloudWatch log streams')
}
return {
success: true,
output: {
logStreams: data.output.logStreams,
},
}
},
outputs: {
logStreams: {
type: 'array',
description: 'List of log streams with metadata',
},
},
}

View File

@@ -0,0 +1,106 @@
import type {
CloudWatchGetLogEventsParams,
CloudWatchGetLogEventsResponse,
} from '@/tools/cloudwatch/types'
import type { ToolConfig } from '@/tools/types'
export const getLogEventsTool: ToolConfig<
CloudWatchGetLogEventsParams,
CloudWatchGetLogEventsResponse
> = {
id: 'cloudwatch_get_log_events',
name: 'CloudWatch Get Log Events',
description: 'Retrieve log events from a specific CloudWatch log stream',
version: '1.0',
params: {
awsRegion: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AWS region (e.g., us-east-1)',
},
awsAccessKeyId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AWS access key ID',
},
awsSecretAccessKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AWS secret access key',
},
logGroupName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'CloudWatch log group name',
},
logStreamName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'CloudWatch log stream name',
},
startTime: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Start time as Unix epoch seconds',
},
endTime: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'End time as Unix epoch seconds',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of events to return',
},
},
request: {
url: '/api/tools/cloudwatch/get-log-events',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
region: params.awsRegion,
accessKeyId: params.awsAccessKeyId,
secretAccessKey: params.awsSecretAccessKey,
logGroupName: params.logGroupName,
logStreamName: params.logStreamName,
...(params.startTime !== undefined && { startTime: params.startTime }),
...(params.endTime !== undefined && { endTime: params.endTime }),
...(params.limit !== undefined && { limit: params.limit }),
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || 'Failed to get CloudWatch log events')
}
return {
success: true,
output: {
events: data.output.events,
},
}
},
outputs: {
events: {
type: 'array',
description: 'Log events with timestamp, message, and ingestion time',
},
},
}

View File

@@ -0,0 +1,119 @@
import type {
CloudWatchGetMetricStatisticsParams,
CloudWatchGetMetricStatisticsResponse,
} from '@/tools/cloudwatch/types'
import type { ToolConfig } from '@/tools/types'
export const getMetricStatisticsTool: ToolConfig<
CloudWatchGetMetricStatisticsParams,
CloudWatchGetMetricStatisticsResponse
> = {
id: 'cloudwatch_get_metric_statistics',
name: 'CloudWatch Get Metric Statistics',
description: 'Get statistics for a CloudWatch metric over a time range',
version: '1.0',
params: {
awsRegion: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AWS region (e.g., us-east-1)',
},
awsAccessKeyId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AWS access key ID',
},
awsSecretAccessKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AWS secret access key',
},
namespace: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Metric namespace (e.g., AWS/EC2, AWS/Lambda)',
},
metricName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Metric name (e.g., CPUUtilization, Invocations)',
},
startTime: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Start time as Unix epoch seconds',
},
endTime: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'End time as Unix epoch seconds',
},
period: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Granularity in seconds (e.g., 60, 300, 3600)',
},
statistics: {
type: 'array',
required: true,
visibility: 'user-or-llm',
description: 'Statistics to retrieve (Average, Sum, Minimum, Maximum, SampleCount)',
},
dimensions: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Dimensions as JSON (e.g., {"InstanceId": "i-1234"})',
},
},
request: {
url: '/api/tools/cloudwatch/get-metric-statistics',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
region: params.awsRegion,
accessKeyId: params.awsAccessKeyId,
secretAccessKey: params.awsSecretAccessKey,
namespace: params.namespace,
metricName: params.metricName,
startTime: params.startTime,
endTime: params.endTime,
period: params.period,
statistics: params.statistics,
...(params.dimensions && { dimensions: params.dimensions }),
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || 'Failed to get CloudWatch metric statistics')
}
return {
success: true,
output: {
label: data.output.label,
datapoints: data.output.datapoints,
},
}
},
outputs: {
label: { type: 'string', description: 'Metric label' },
datapoints: { type: 'array', description: 'Datapoints with timestamp and statistics values' },
},
}

View File

@@ -0,0 +1,15 @@
import { describeAlarmsTool } from '@/tools/cloudwatch/describe_alarms'
import { describeLogGroupsTool } from '@/tools/cloudwatch/describe_log_groups'
import { describeLogStreamsTool } from '@/tools/cloudwatch/describe_log_streams'
import { getLogEventsTool } from '@/tools/cloudwatch/get_log_events'
import { getMetricStatisticsTool } from '@/tools/cloudwatch/get_metric_statistics'
import { listMetricsTool } from '@/tools/cloudwatch/list_metrics'
import { queryLogsTool } from '@/tools/cloudwatch/query_logs'
export const cloudwatchDescribeAlarmsTool = describeAlarmsTool
export const cloudwatchDescribeLogGroupsTool = describeLogGroupsTool
export const cloudwatchDescribeLogStreamsTool = describeLogStreamsTool
export const cloudwatchGetLogEventsTool = getLogEventsTool
export const cloudwatchGetMetricStatisticsTool = getMetricStatisticsTool
export const cloudwatchListMetricsTool = listMetricsTool
export const cloudwatchQueryLogsTool = queryLogsTool

View File

@@ -0,0 +1,96 @@
import type {
CloudWatchListMetricsParams,
CloudWatchListMetricsResponse,
} from '@/tools/cloudwatch/types'
import type { ToolConfig } from '@/tools/types'
export const listMetricsTool: ToolConfig<
CloudWatchListMetricsParams,
CloudWatchListMetricsResponse
> = {
id: 'cloudwatch_list_metrics',
name: 'CloudWatch List Metrics',
description: 'List available CloudWatch metrics',
version: '1.0',
params: {
awsRegion: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AWS region (e.g., us-east-1)',
},
awsAccessKeyId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AWS access key ID',
},
awsSecretAccessKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AWS secret access key',
},
namespace: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by namespace (e.g., AWS/EC2, AWS/Lambda)',
},
metricName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by metric name',
},
recentlyActive: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Only show metrics active in the last 3 hours',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of metrics to return',
},
},
request: {
url: '/api/tools/cloudwatch/list-metrics',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
region: params.awsRegion,
accessKeyId: params.awsAccessKeyId,
secretAccessKey: params.awsSecretAccessKey,
...(params.namespace && { namespace: params.namespace }),
...(params.metricName && { metricName: params.metricName }),
...(params.recentlyActive && { recentlyActive: params.recentlyActive }),
...(params.limit !== undefined && { limit: params.limit }),
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || 'Failed to list CloudWatch metrics')
}
return {
success: true,
output: {
metrics: data.output.metrics,
},
}
},
outputs: {
metrics: { type: 'array', description: 'List of metrics with namespace, name, and dimensions' },
},
}

View File

@@ -0,0 +1,107 @@
import type {
CloudWatchQueryLogsParams,
CloudWatchQueryLogsResponse,
} from '@/tools/cloudwatch/types'
import type { ToolConfig } from '@/tools/types'
export const queryLogsTool: ToolConfig<CloudWatchQueryLogsParams, CloudWatchQueryLogsResponse> = {
id: 'cloudwatch_query_logs',
name: 'CloudWatch Query Logs',
description: 'Run a CloudWatch Log Insights query against one or more log groups',
version: '1.0',
params: {
awsRegion: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AWS region (e.g., us-east-1)',
},
awsAccessKeyId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AWS access key ID',
},
awsSecretAccessKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'AWS secret access key',
},
logGroupNames: {
type: 'array',
required: true,
visibility: 'user-or-llm',
description: 'Log group names to query',
},
queryString: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'CloudWatch Log Insights query string',
},
startTime: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Start time as Unix epoch seconds',
},
endTime: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'End time as Unix epoch seconds',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results to return',
},
},
request: {
url: '/api/tools/cloudwatch/query-logs',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
region: params.awsRegion,
accessKeyId: params.awsAccessKeyId,
secretAccessKey: params.awsSecretAccessKey,
logGroupNames: params.logGroupNames,
queryString: params.queryString,
startTime: params.startTime,
endTime: params.endTime,
...(params.limit !== undefined && { limit: params.limit }),
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || 'CloudWatch Log Insights query failed')
}
return {
success: true,
output: {
results: data.output.results,
statistics: data.output.statistics,
status: data.output.status,
},
}
},
outputs: {
results: { type: 'array', description: 'Query result rows' },
statistics: {
type: 'object',
description: 'Query statistics (bytesScanned, recordsMatched, recordsScanned)',
},
status: { type: 'string', description: 'Query completion status' },
},
}

View File

@@ -0,0 +1,146 @@
import type { ToolResponse } from '@/tools/types'
export interface CloudWatchConnectionConfig {
awsRegion: string
awsAccessKeyId: string
awsSecretAccessKey: string
}
export interface CloudWatchQueryLogsParams extends CloudWatchConnectionConfig {
logGroupNames: string[]
queryString: string
startTime: number
endTime: number
limit?: number
}
export interface CloudWatchDescribeLogGroupsParams extends CloudWatchConnectionConfig {
prefix?: string
limit?: number
}
export interface CloudWatchGetLogEventsParams extends CloudWatchConnectionConfig {
logGroupName: string
logStreamName: string
startTime?: number
endTime?: number
limit?: number
}
export interface CloudWatchQueryLogsResponse extends ToolResponse {
output: {
results: Record<string, string>[]
statistics: {
bytesScanned: number
recordsMatched: number
recordsScanned: number
}
status: string
}
}
export interface CloudWatchDescribeLogGroupsResponse extends ToolResponse {
output: {
logGroups: {
logGroupName: string
arn: string
storedBytes: number
retentionInDays: number | undefined
creationTime: number | undefined
}[]
}
}
export interface CloudWatchGetLogEventsResponse extends ToolResponse {
output: {
events: {
timestamp: number | undefined
message: string | undefined
ingestionTime: number | undefined
}[]
}
}
export interface CloudWatchDescribeLogStreamsParams extends CloudWatchConnectionConfig {
logGroupName: string
prefix?: string
limit?: number
}
export interface CloudWatchDescribeLogStreamsResponse extends ToolResponse {
output: {
logStreams: {
logStreamName: string
lastEventTimestamp: number | undefined
firstEventTimestamp: number | undefined
creationTime: number | undefined
storedBytes: number
}[]
}
}
export interface CloudWatchListMetricsParams extends CloudWatchConnectionConfig {
namespace?: string
metricName?: string
recentlyActive?: boolean
limit?: number
}
export interface CloudWatchListMetricsResponse extends ToolResponse {
output: {
metrics: {
namespace: string
metricName: string
dimensions: { name: string; value: string }[]
}[]
}
}
export interface CloudWatchGetMetricStatisticsParams extends CloudWatchConnectionConfig {
namespace: string
metricName: string
startTime: number
endTime: number
period: number
statistics: string[]
dimensions?: string
}
export interface CloudWatchGetMetricStatisticsResponse extends ToolResponse {
output: {
label: string
datapoints: {
timestamp: number
average?: number
sum?: number
minimum?: number
maximum?: number
sampleCount?: number
unit?: string
}[]
}
}
export interface CloudWatchDescribeAlarmsParams extends CloudWatchConnectionConfig {
alarmNamePrefix?: string
stateValue?: string
alarmType?: string
limit?: number
}
export interface CloudWatchDescribeAlarmsResponse extends ToolResponse {
output: {
alarms: {
alarmName: string
alarmArn: string
stateValue: string
stateReason: string
metricName: string | undefined
namespace: string | undefined
comparisonOperator: string | undefined
threshold: number | undefined
evaluationPeriods: number | undefined
stateUpdatedTimestamp: number | undefined
}[]
}
}

View File

@@ -275,6 +275,15 @@ import {
cloudflareUpdateDnsRecordTool,
cloudflareUpdateZoneSettingTool,
} from '@/tools/cloudflare'
import {
cloudwatchDescribeAlarmsTool,
cloudwatchDescribeLogGroupsTool,
cloudwatchDescribeLogStreamsTool,
cloudwatchGetLogEventsTool,
cloudwatchGetMetricStatisticsTool,
cloudwatchListMetricsTool,
cloudwatchQueryLogsTool,
} from '@/tools/cloudwatch'
import {
confluenceAddLabelTool,
confluenceCreateBlogPostTool,
@@ -3376,6 +3385,13 @@ export const tools: Record<string, ToolConfig> = {
rds_delete: rdsDeleteTool,
rds_execute: rdsExecuteTool,
rds_introspect: rdsIntrospectTool,
cloudwatch_query_logs: cloudwatchQueryLogsTool,
cloudwatch_describe_log_groups: cloudwatchDescribeLogGroupsTool,
cloudwatch_describe_alarms: cloudwatchDescribeAlarmsTool,
cloudwatch_describe_log_streams: cloudwatchDescribeLogStreamsTool,
cloudwatch_get_log_events: cloudwatchGetLogEventsTool,
cloudwatch_list_metrics: cloudwatchListMetricsTool,
cloudwatch_get_metric_statistics: cloudwatchGetMetricStatisticsTool,
dynamodb_get: dynamodbGetTool,
dynamodb_put: dynamodbPutTool,
dynamodb_query: dynamodbQueryTool,

View File

@@ -57,6 +57,8 @@
"@a2a-js/sdk": "0.3.7",
"@anthropic-ai/sdk": "0.71.2",
"@aws-sdk/client-bedrock-runtime": "3.940.0",
"@aws-sdk/client-cloudwatch": "3.940.0",
"@aws-sdk/client-cloudwatch-logs": "3.940.0",
"@aws-sdk/client-dynamodb": "3.940.0",
"@aws-sdk/client-rds-data": "3.940.0",
"@aws-sdk/client-s3": "^3.779.0",
@@ -414,6 +416,10 @@
"@aws-sdk/client-bedrock-runtime": ["@aws-sdk/client-bedrock-runtime@3.940.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.940.0", "@aws-sdk/credential-provider-node": "3.940.0", "@aws-sdk/eventstream-handler-node": "3.936.0", "@aws-sdk/middleware-eventstream": "3.936.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.936.0", "@aws-sdk/middleware-user-agent": "3.940.0", "@aws-sdk/middleware-websocket": "3.936.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/token-providers": "3.940.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.940.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.5", "@smithy/eventstream-serde-browser": "^4.2.5", "@smithy/eventstream-serde-config-resolver": "^4.3.5", "@smithy/eventstream-serde-node": "^4.2.5", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.12", "@smithy/middleware-retry": "^4.4.12", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.11", "@smithy/util-defaults-mode-node": "^4.2.14", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Gs6UUQP1zt8vahOxJ3BADcb3B+2KldUNA3bKa+KdK58de7N7tLJFJfZuXhFGGtwyNPh1aw6phtdP6dauq3OLWA=="],
"@aws-sdk/client-cloudwatch": ["@aws-sdk/client-cloudwatch@3.940.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.940.0", "@aws-sdk/credential-provider-node": "3.940.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.936.0", "@aws-sdk/middleware-user-agent": "3.940.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.940.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.5", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-compression": "^4.3.12", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.12", "@smithy/middleware-retry": "^4.4.12", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.11", "@smithy/util-defaults-mode-node": "^4.2.14", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "@smithy/util-waiter": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-C35xpPntRAGdEg3X5iKpSUCBaP3yxYNo1U95qipN/X1e0/TYIDWHwGt8Z1ntRafK19jp5oVzhRQ+PD1JAPSEzA=="],
"@aws-sdk/client-cloudwatch-logs": ["@aws-sdk/client-cloudwatch-logs@3.940.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.940.0", "@aws-sdk/credential-provider-node": "3.940.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.936.0", "@aws-sdk/middleware-user-agent": "3.940.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.940.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.5", "@smithy/eventstream-serde-browser": "^4.2.5", "@smithy/eventstream-serde-config-resolver": "^4.3.5", "@smithy/eventstream-serde-node": "^4.2.5", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.12", "@smithy/middleware-retry": "^4.4.12", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.11", "@smithy/util-defaults-mode-node": "^4.2.14", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-7dEIO3D98IxA9IhqixPJbzQsBkk4TchHHpFdd0JOhlSlihWhiwbf3ijUePJVXYJxcpRRtMmAMtDRLDzCSO+ZHg=="],
"@aws-sdk/client-dynamodb": ["@aws-sdk/client-dynamodb@3.940.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.940.0", "@aws-sdk/credential-provider-node": "3.940.0", "@aws-sdk/middleware-endpoint-discovery": "3.936.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.936.0", "@aws-sdk/middleware-user-agent": "3.940.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.940.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.5", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.12", "@smithy/middleware-retry": "^4.4.12", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.11", "@smithy/util-defaults-mode-node": "^4.2.14", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "@smithy/util-waiter": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-u2sXsNJazJbuHeWICvsj6RvNyJh3isedEfPvB21jK/kxcriK+dE/izlKC2cyxUjERCmku0zTFNzY9FhrLbYHjQ=="],
"@aws-sdk/client-rds-data": ["@aws-sdk/client-rds-data@3.940.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.940.0", "@aws-sdk/credential-provider-node": "3.940.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.936.0", "@aws-sdk/middleware-user-agent": "3.940.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.940.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.5", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.12", "@smithy/middleware-retry": "^4.4.12", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.11", "@smithy/util-defaults-mode-node": "^4.2.14", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-68NH61MvS48CVPfzBNCPdCG4KnNjM+Uj/3DSw7rT9PJvdML9ARS4M2Uqco9POPw+Aj20KBumsEUd6FMVcYBXAA=="],
@@ -1348,6 +1354,8 @@
"@smithy/md5-js": ["@smithy/md5-js@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-W/oIpHCpWU2+iAkfZYyGWE+qkpuf3vEXHLxQQDx9FPNZTTdnul0dZ2d/gUFrtQ5je1G2kp4cjG0/24YueG2LbQ=="],
"@smithy/middleware-compression": ["@smithy/middleware-compression@4.3.42", "", { "dependencies": { "@smithy/core": "^3.23.13", "@smithy/is-array-buffer": "^4.2.2", "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "fflate": "0.8.1", "tslib": "^2.6.2" } }, "sha512-Ys2R8N7oZ3b6p063lhk7paRbX1F9Ju8a8Bsrw2nFfsG8iHYpgfW6ijd7hJKqRe+Wq9ABfcmX3luBlEd+B5/jVA=="],
"@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA=="],
"@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.27", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/middleware-serde": "^4.2.15", "@smithy/node-config-provider": "^4.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-T3TFfUgXQlpcg+UdzcAISdZpj4Z+XECZ/cefgA6wLBd6V4lRi0svN2hBouN/be9dXQ31X4sLWz3fAQDf+nt6BA=="],
@@ -4122,6 +4130,10 @@
"@shuding/opentype.js/fflate": ["fflate@0.7.4", "", {}, "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw=="],
"@smithy/middleware-compression/@smithy/core": ["@smithy/core@3.23.13", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.21", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-J+2TT9D6oGsUVXVEMvz8h2EmdVnkBiy2auCie4aSJMvKlzUtO5hqjEzXhoCUkIMo7gAYjbQcN0g/MMSXEhDs1Q=="],
"@smithy/middleware-compression/fflate": ["fflate@0.8.1", "", {}, "sha512-/exOvEuc+/iaUm105QIiOt4LpBdMTWsXxqR0HDF35vx3fmaKzw7354gTilCh5rkzEt8WYyG//ku3h3nRmd7CHQ=="],
"@socket.io/redis-adapter/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
"@tailwindcss/node/jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
@@ -4660,6 +4672,8 @@
"@shikijs/rehype/shiki/@shikijs/themes": ["@shikijs/themes@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0" } }, "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA=="],
"@smithy/middleware-compression/@smithy/core/@smithy/util-stream": ["@smithy/util-stream@4.5.21", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.15", "@smithy/node-http-handler": "^4.5.1", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-KzSg+7KKywLnkoKejRtIBXDmwBfjGvg1U1i/etkC7XSWUyFCoLno1IohV2c74IzQqdhX5y3uE44r/8/wuK+A7Q=="],
"@trigger.dev/core/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="],
"@trigger.dev/core/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.203.0", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/otlp-transformer": "0.203.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Wbxf7k+87KyvxFr5D7uOiSq/vHXWommvdnNE7vECO3tAhsA2GfOlpWINCMWUEPdHZ7tCXxw6Epp3vgx3jU7llQ=="],
@@ -5128,6 +5142,8 @@
"@cerebras/cerebras_cloud_sdk/node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
"@smithy/middleware-compression/@smithy/core/@smithy/util-stream/@smithy/node-http-handler": ["@smithy/node-http-handler@4.5.1", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-ejjxdAXjkPIs9lyYyVutOGNOraqUE9v/NjGMKwwFrfOM354wfSD8lmlj8hVwUzQmlLLF4+udhfCX9Exnbmvfzw=="],
"@trigger.dev/core/socket.io-client/engine.io-client/ws": ["ws@8.17.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ=="],
"@trigger.dev/core/socket.io-client/engine.io-client/xmlhttprequest-ssl": ["xmlhttprequest-ssl@2.0.0", "", {}, "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A=="],