diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 12aeb36697b..793cbde8472 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -3655,6 +3655,17 @@ export function AshbyIcon(props: SVGProps) { ) } +export function AppConfigIcon(props: SVGProps) { + return ( + + + + ) +} + export function ArxivIcon(props: SVGProps) { return ( diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index 37682dbf1f4..2ad53ceaf7f 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -14,6 +14,7 @@ import { AmplitudeIcon, ApifyIcon, ApolloIcon, + AppConfigIcon, ArxivIcon, AsanaIcon, AshbyIcon, @@ -222,6 +223,7 @@ export const blockTypeToIconMap: Record = { amplitude: AmplitudeIcon, apify: ApifyIcon, apollo: ApolloIcon, + appconfig: AppConfigIcon, arxiv: ArxivIcon, asana: AsanaIcon, ashby: AshbyIcon, diff --git a/apps/docs/content/docs/en/tools/appconfig.mdx b/apps/docs/content/docs/en/tools/appconfig.mdx new file mode 100644 index 00000000000..27b13ccc207 --- /dev/null +++ b/apps/docs/content/docs/en/tools/appconfig.mdx @@ -0,0 +1,351 @@ +--- +title: AWS AppConfig +description: Manage configuration versions and deployments in AWS AppConfig +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +## Usage Instructions + +Integrate AWS AppConfig into workflows. Create hosted configuration versions, start, stop, and inspect deployments, and discover applications, environments, configuration profiles, and deployment strategies. + + + +## Tools + +### `appconfig_create_hosted_configuration_version` + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `message` | string | Operation status message | +| `applicationId` | string | The application ID | +| `configurationProfileId` | string | The configuration profile ID | +| `environmentId` | string | The environment ID | +| `versionNumber` | number | The hosted configuration version number | +| `content` | string | The configuration content | +| `contentType` | string | The content MIME type | +| `versionLabel` | string | The version label | +| `deploymentNumber` | number | The deployment sequence number | +| `deploymentStrategyId` | string | The deployment strategy ID | +| `configurationVersion` | string | The deployed configuration version | +| `description` | string | Description of the version or deployment | +| `state` | string | The deployment state | +| `percentageComplete` | number | Percentage of targets deployed | +| `startedAt` | string | When the deployment started \(ISO\) | +| `completedAt` | string | When the deployment completed \(ISO\) | +| `items` | array | List of results for list operations | +| `nextToken` | string | Pagination token for the next page | + +### `appconfig_get_hosted_configuration_version` + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `message` | string | Operation status message | +| `applicationId` | string | The application ID | +| `configurationProfileId` | string | The configuration profile ID | +| `environmentId` | string | The environment ID | +| `versionNumber` | number | The hosted configuration version number | +| `content` | string | The configuration content | +| `contentType` | string | The content MIME type | +| `versionLabel` | string | The version label | +| `deploymentNumber` | number | The deployment sequence number | +| `deploymentStrategyId` | string | The deployment strategy ID | +| `configurationVersion` | string | The deployed configuration version | +| `description` | string | Description of the version or deployment | +| `state` | string | The deployment state | +| `percentageComplete` | number | Percentage of targets deployed | +| `startedAt` | string | When the deployment started \(ISO\) | +| `completedAt` | string | When the deployment completed \(ISO\) | +| `items` | array | List of results for list operations | +| `nextToken` | string | Pagination token for the next page | + +### `appconfig_list_hosted_configuration_versions` + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `message` | string | Operation status message | +| `applicationId` | string | The application ID | +| `configurationProfileId` | string | The configuration profile ID | +| `environmentId` | string | The environment ID | +| `versionNumber` | number | The hosted configuration version number | +| `content` | string | The configuration content | +| `contentType` | string | The content MIME type | +| `versionLabel` | string | The version label | +| `deploymentNumber` | number | The deployment sequence number | +| `deploymentStrategyId` | string | The deployment strategy ID | +| `configurationVersion` | string | The deployed configuration version | +| `description` | string | Description of the version or deployment | +| `state` | string | The deployment state | +| `percentageComplete` | number | Percentage of targets deployed | +| `startedAt` | string | When the deployment started \(ISO\) | +| `completedAt` | string | When the deployment completed \(ISO\) | +| `items` | array | List of results for list operations | +| `nextToken` | string | Pagination token for the next page | + +### `appconfig_start_deployment` + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `message` | string | Operation status message | +| `applicationId` | string | The application ID | +| `configurationProfileId` | string | The configuration profile ID | +| `environmentId` | string | The environment ID | +| `versionNumber` | number | The hosted configuration version number | +| `content` | string | The configuration content | +| `contentType` | string | The content MIME type | +| `versionLabel` | string | The version label | +| `deploymentNumber` | number | The deployment sequence number | +| `deploymentStrategyId` | string | The deployment strategy ID | +| `configurationVersion` | string | The deployed configuration version | +| `description` | string | Description of the version or deployment | +| `state` | string | The deployment state | +| `percentageComplete` | number | Percentage of targets deployed | +| `startedAt` | string | When the deployment started \(ISO\) | +| `completedAt` | string | When the deployment completed \(ISO\) | +| `items` | array | List of results for list operations | +| `nextToken` | string | Pagination token for the next page | + +### `appconfig_get_deployment` + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `message` | string | Operation status message | +| `applicationId` | string | The application ID | +| `configurationProfileId` | string | The configuration profile ID | +| `environmentId` | string | The environment ID | +| `versionNumber` | number | The hosted configuration version number | +| `content` | string | The configuration content | +| `contentType` | string | The content MIME type | +| `versionLabel` | string | The version label | +| `deploymentNumber` | number | The deployment sequence number | +| `deploymentStrategyId` | string | The deployment strategy ID | +| `configurationVersion` | string | The deployed configuration version | +| `description` | string | Description of the version or deployment | +| `state` | string | The deployment state | +| `percentageComplete` | number | Percentage of targets deployed | +| `startedAt` | string | When the deployment started \(ISO\) | +| `completedAt` | string | When the deployment completed \(ISO\) | +| `items` | array | List of results for list operations | +| `nextToken` | string | Pagination token for the next page | + +### `appconfig_stop_deployment` + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `message` | string | Operation status message | +| `applicationId` | string | The application ID | +| `configurationProfileId` | string | The configuration profile ID | +| `environmentId` | string | The environment ID | +| `versionNumber` | number | The hosted configuration version number | +| `content` | string | The configuration content | +| `contentType` | string | The content MIME type | +| `versionLabel` | string | The version label | +| `deploymentNumber` | number | The deployment sequence number | +| `deploymentStrategyId` | string | The deployment strategy ID | +| `configurationVersion` | string | The deployed configuration version | +| `description` | string | Description of the version or deployment | +| `state` | string | The deployment state | +| `percentageComplete` | number | Percentage of targets deployed | +| `startedAt` | string | When the deployment started \(ISO\) | +| `completedAt` | string | When the deployment completed \(ISO\) | +| `items` | array | List of results for list operations | +| `nextToken` | string | Pagination token for the next page | + +### `appconfig_list_deployments` + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `message` | string | Operation status message | +| `applicationId` | string | The application ID | +| `configurationProfileId` | string | The configuration profile ID | +| `environmentId` | string | The environment ID | +| `versionNumber` | number | The hosted configuration version number | +| `content` | string | The configuration content | +| `contentType` | string | The content MIME type | +| `versionLabel` | string | The version label | +| `deploymentNumber` | number | The deployment sequence number | +| `deploymentStrategyId` | string | The deployment strategy ID | +| `configurationVersion` | string | The deployed configuration version | +| `description` | string | Description of the version or deployment | +| `state` | string | The deployment state | +| `percentageComplete` | number | Percentage of targets deployed | +| `startedAt` | string | When the deployment started \(ISO\) | +| `completedAt` | string | When the deployment completed \(ISO\) | +| `items` | array | List of results for list operations | +| `nextToken` | string | Pagination token for the next page | + +### `appconfig_list_applications` + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `message` | string | Operation status message | +| `applicationId` | string | The application ID | +| `configurationProfileId` | string | The configuration profile ID | +| `environmentId` | string | The environment ID | +| `versionNumber` | number | The hosted configuration version number | +| `content` | string | The configuration content | +| `contentType` | string | The content MIME type | +| `versionLabel` | string | The version label | +| `deploymentNumber` | number | The deployment sequence number | +| `deploymentStrategyId` | string | The deployment strategy ID | +| `configurationVersion` | string | The deployed configuration version | +| `description` | string | Description of the version or deployment | +| `state` | string | The deployment state | +| `percentageComplete` | number | Percentage of targets deployed | +| `startedAt` | string | When the deployment started \(ISO\) | +| `completedAt` | string | When the deployment completed \(ISO\) | +| `items` | array | List of results for list operations | +| `nextToken` | string | Pagination token for the next page | + +### `appconfig_list_environments` + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `message` | string | Operation status message | +| `applicationId` | string | The application ID | +| `configurationProfileId` | string | The configuration profile ID | +| `environmentId` | string | The environment ID | +| `versionNumber` | number | The hosted configuration version number | +| `content` | string | The configuration content | +| `contentType` | string | The content MIME type | +| `versionLabel` | string | The version label | +| `deploymentNumber` | number | The deployment sequence number | +| `deploymentStrategyId` | string | The deployment strategy ID | +| `configurationVersion` | string | The deployed configuration version | +| `description` | string | Description of the version or deployment | +| `state` | string | The deployment state | +| `percentageComplete` | number | Percentage of targets deployed | +| `startedAt` | string | When the deployment started \(ISO\) | +| `completedAt` | string | When the deployment completed \(ISO\) | +| `items` | array | List of results for list operations | +| `nextToken` | string | Pagination token for the next page | + +### `appconfig_list_configuration_profiles` + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `message` | string | Operation status message | +| `applicationId` | string | The application ID | +| `configurationProfileId` | string | The configuration profile ID | +| `environmentId` | string | The environment ID | +| `versionNumber` | number | The hosted configuration version number | +| `content` | string | The configuration content | +| `contentType` | string | The content MIME type | +| `versionLabel` | string | The version label | +| `deploymentNumber` | number | The deployment sequence number | +| `deploymentStrategyId` | string | The deployment strategy ID | +| `configurationVersion` | string | The deployed configuration version | +| `description` | string | Description of the version or deployment | +| `state` | string | The deployment state | +| `percentageComplete` | number | Percentage of targets deployed | +| `startedAt` | string | When the deployment started \(ISO\) | +| `completedAt` | string | When the deployment completed \(ISO\) | +| `items` | array | List of results for list operations | +| `nextToken` | string | Pagination token for the next page | + +### `appconfig_list_deployment_strategies` + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `message` | string | Operation status message | +| `applicationId` | string | The application ID | +| `configurationProfileId` | string | The configuration profile ID | +| `environmentId` | string | The environment ID | +| `versionNumber` | number | The hosted configuration version number | +| `content` | string | The configuration content | +| `contentType` | string | The content MIME type | +| `versionLabel` | string | The version label | +| `deploymentNumber` | number | The deployment sequence number | +| `deploymentStrategyId` | string | The deployment strategy ID | +| `configurationVersion` | string | The deployed configuration version | +| `description` | string | Description of the version or deployment | +| `state` | string | The deployment state | +| `percentageComplete` | number | Percentage of targets deployed | +| `startedAt` | string | When the deployment started \(ISO\) | +| `completedAt` | string | When the deployment completed \(ISO\) | +| `items` | array | List of results for list operations | +| `nextToken` | string | Pagination token for the next page | + + diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index c1e0a1d2081..c2edcceb166 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -11,6 +11,7 @@ "amplitude", "apify", "apollo", + "appconfig", "arxiv", "asana", "ashby", diff --git a/apps/sim/app/api/tools/appconfig/create-hosted-configuration-version/route.ts b/apps/sim/app/api/tools/appconfig/create-hosted-configuration-version/route.ts new file mode 100644 index 00000000000..b7bc30a4090 --- /dev/null +++ b/apps/sim/app/api/tools/appconfig/create-hosted-configuration-version/route.ts @@ -0,0 +1,55 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { awsAppConfigCreateHostedConfigurationVersionContract } from '@/lib/api/contracts/tools/aws/appconfig-create-hosted-configuration-version' +import { parseToolRequest } from '@/lib/api/server' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { + createAppConfigClient, + createHostedConfigurationVersion, +} from '@/app/api/tools/appconfig/utils' + +const logger = createLogger('AppConfigCreateHostedConfigurationVersionAPI') + +export const POST = withRouteHandler(async (request: NextRequest) => { + try { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + const parsed = await parseToolRequest( + awsAppConfigCreateHostedConfigurationVersionContract, + request, + { errorFormat: 'details', logger } + ) + if (!parsed.success) return parsed.response + const data = parsed.data.body + + logger.info( + `Creating hosted configuration version for profile '${data.configurationProfileId}'` + ) + + const client = createAppConfigClient(data) + try { + const result = await createHostedConfigurationVersion(client, { + applicationId: data.applicationId, + configurationProfileId: data.configurationProfileId, + content: data.content, + contentType: data.contentType, + description: data.description ?? undefined, + versionLabel: data.versionLabel ?? undefined, + latestVersionNumber: data.latestVersionNumber ?? undefined, + }) + return NextResponse.json(result) + } finally { + client.destroy() + } + } catch (error) { + const errorMessage = + toError(error).message || 'AppConfig create hosted configuration version failed' + logger.error('AppConfig create hosted configuration version failed:', error) + return NextResponse.json({ error: errorMessage }, { status: 500 }) + } +}) diff --git a/apps/sim/app/api/tools/appconfig/get-deployment/route.ts b/apps/sim/app/api/tools/appconfig/get-deployment/route.ts new file mode 100644 index 00000000000..b33c957c517 --- /dev/null +++ b/apps/sim/app/api/tools/appconfig/get-deployment/route.ts @@ -0,0 +1,46 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { awsAppConfigGetDeploymentContract } from '@/lib/api/contracts/tools/aws/appconfig-get-deployment' +import { parseToolRequest } from '@/lib/api/server' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { createAppConfigClient, getDeployment } from '@/app/api/tools/appconfig/utils' + +const logger = createLogger('AppConfigGetDeploymentAPI') + +export const POST = withRouteHandler(async (request: NextRequest) => { + try { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + const parsed = await parseToolRequest(awsAppConfigGetDeploymentContract, request, { + errorFormat: 'details', + logger, + }) + if (!parsed.success) return parsed.response + const data = parsed.data.body + + logger.info( + `Getting deployment ${data.deploymentNumber} for environment '${data.environmentId}'` + ) + + const client = createAppConfigClient(data) + try { + const result = await getDeployment(client, { + applicationId: data.applicationId, + environmentId: data.environmentId, + deploymentNumber: data.deploymentNumber, + }) + return NextResponse.json(result) + } finally { + client.destroy() + } + } catch (error) { + const errorMessage = toError(error).message || 'AppConfig get deployment failed' + logger.error('AppConfig get deployment failed:', error) + return NextResponse.json({ error: errorMessage }, { status: 500 }) + } +}) diff --git a/apps/sim/app/api/tools/appconfig/get-hosted-configuration-version/route.ts b/apps/sim/app/api/tools/appconfig/get-hosted-configuration-version/route.ts new file mode 100644 index 00000000000..c67019783e2 --- /dev/null +++ b/apps/sim/app/api/tools/appconfig/get-hosted-configuration-version/route.ts @@ -0,0 +1,51 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { awsAppConfigGetHostedConfigurationVersionContract } from '@/lib/api/contracts/tools/aws/appconfig-get-hosted-configuration-version' +import { parseToolRequest } from '@/lib/api/server' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { + createAppConfigClient, + getHostedConfigurationVersion, +} from '@/app/api/tools/appconfig/utils' + +const logger = createLogger('AppConfigGetHostedConfigurationVersionAPI') + +export const POST = withRouteHandler(async (request: NextRequest) => { + try { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + const parsed = await parseToolRequest( + awsAppConfigGetHostedConfigurationVersionContract, + request, + { errorFormat: 'details', logger } + ) + if (!parsed.success) return parsed.response + const data = parsed.data.body + + logger.info( + `Getting hosted configuration version ${data.versionNumber} for profile '${data.configurationProfileId}'` + ) + + const client = createAppConfigClient(data) + try { + const result = await getHostedConfigurationVersion(client, { + applicationId: data.applicationId, + configurationProfileId: data.configurationProfileId, + versionNumber: data.versionNumber, + }) + return NextResponse.json(result) + } finally { + client.destroy() + } + } catch (error) { + const errorMessage = + toError(error).message || 'AppConfig get hosted configuration version failed' + logger.error('AppConfig get hosted configuration version failed:', error) + return NextResponse.json({ error: errorMessage }, { status: 500 }) + } +}) diff --git a/apps/sim/app/api/tools/appconfig/list-applications/route.ts b/apps/sim/app/api/tools/appconfig/list-applications/route.ts new file mode 100644 index 00000000000..542a47931d7 --- /dev/null +++ b/apps/sim/app/api/tools/appconfig/list-applications/route.ts @@ -0,0 +1,43 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { awsAppConfigListApplicationsContract } from '@/lib/api/contracts/tools/aws/appconfig-list-applications' +import { parseToolRequest } from '@/lib/api/server' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { createAppConfigClient, listApplications } from '@/app/api/tools/appconfig/utils' + +const logger = createLogger('AppConfigListApplicationsAPI') + +export const POST = withRouteHandler(async (request: NextRequest) => { + try { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + const parsed = await parseToolRequest(awsAppConfigListApplicationsContract, request, { + errorFormat: 'details', + logger, + }) + if (!parsed.success) return parsed.response + const data = parsed.data.body + + logger.info('Listing AppConfig applications') + + const client = createAppConfigClient(data) + try { + const result = await listApplications(client, { + maxResults: data.maxResults ?? undefined, + nextToken: data.nextToken ?? undefined, + }) + return NextResponse.json(result) + } finally { + client.destroy() + } + } catch (error) { + const errorMessage = toError(error).message || 'AppConfig list applications failed' + logger.error('AppConfig list applications failed:', error) + return NextResponse.json({ error: errorMessage }, { status: 500 }) + } +}) diff --git a/apps/sim/app/api/tools/appconfig/list-configuration-profiles/route.ts b/apps/sim/app/api/tools/appconfig/list-configuration-profiles/route.ts new file mode 100644 index 00000000000..2e7f0c24a82 --- /dev/null +++ b/apps/sim/app/api/tools/appconfig/list-configuration-profiles/route.ts @@ -0,0 +1,44 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { awsAppConfigListConfigurationProfilesContract } from '@/lib/api/contracts/tools/aws/appconfig-list-configuration-profiles' +import { parseToolRequest } from '@/lib/api/server' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { createAppConfigClient, listConfigurationProfiles } from '@/app/api/tools/appconfig/utils' + +const logger = createLogger('AppConfigListConfigurationProfilesAPI') + +export const POST = withRouteHandler(async (request: NextRequest) => { + try { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + const parsed = await parseToolRequest(awsAppConfigListConfigurationProfilesContract, request, { + errorFormat: 'details', + logger, + }) + if (!parsed.success) return parsed.response + const data = parsed.data.body + + logger.info(`Listing configuration profiles for application '${data.applicationId}'`) + + const client = createAppConfigClient(data) + try { + const result = await listConfigurationProfiles(client, { + applicationId: data.applicationId, + maxResults: data.maxResults ?? undefined, + nextToken: data.nextToken ?? undefined, + }) + return NextResponse.json(result) + } finally { + client.destroy() + } + } catch (error) { + const errorMessage = toError(error).message || 'AppConfig list configuration profiles failed' + logger.error('AppConfig list configuration profiles failed:', error) + return NextResponse.json({ error: errorMessage }, { status: 500 }) + } +}) diff --git a/apps/sim/app/api/tools/appconfig/list-deployment-strategies/route.ts b/apps/sim/app/api/tools/appconfig/list-deployment-strategies/route.ts new file mode 100644 index 00000000000..b1b9cd50778 --- /dev/null +++ b/apps/sim/app/api/tools/appconfig/list-deployment-strategies/route.ts @@ -0,0 +1,43 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { awsAppConfigListDeploymentStrategiesContract } from '@/lib/api/contracts/tools/aws/appconfig-list-deployment-strategies' +import { parseToolRequest } from '@/lib/api/server' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { createAppConfigClient, listDeploymentStrategies } from '@/app/api/tools/appconfig/utils' + +const logger = createLogger('AppConfigListDeploymentStrategiesAPI') + +export const POST = withRouteHandler(async (request: NextRequest) => { + try { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + const parsed = await parseToolRequest(awsAppConfigListDeploymentStrategiesContract, request, { + errorFormat: 'details', + logger, + }) + if (!parsed.success) return parsed.response + const data = parsed.data.body + + logger.info('Listing AppConfig deployment strategies') + + const client = createAppConfigClient(data) + try { + const result = await listDeploymentStrategies(client, { + maxResults: data.maxResults ?? undefined, + nextToken: data.nextToken ?? undefined, + }) + return NextResponse.json(result) + } finally { + client.destroy() + } + } catch (error) { + const errorMessage = toError(error).message || 'AppConfig list deployment strategies failed' + logger.error('AppConfig list deployment strategies failed:', error) + return NextResponse.json({ error: errorMessage }, { status: 500 }) + } +}) diff --git a/apps/sim/app/api/tools/appconfig/list-deployments/route.ts b/apps/sim/app/api/tools/appconfig/list-deployments/route.ts new file mode 100644 index 00000000000..3ea5c53df63 --- /dev/null +++ b/apps/sim/app/api/tools/appconfig/list-deployments/route.ts @@ -0,0 +1,45 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { awsAppConfigListDeploymentsContract } from '@/lib/api/contracts/tools/aws/appconfig-list-deployments' +import { parseToolRequest } from '@/lib/api/server' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { createAppConfigClient, listDeployments } from '@/app/api/tools/appconfig/utils' + +const logger = createLogger('AppConfigListDeploymentsAPI') + +export const POST = withRouteHandler(async (request: NextRequest) => { + try { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + const parsed = await parseToolRequest(awsAppConfigListDeploymentsContract, request, { + errorFormat: 'details', + logger, + }) + if (!parsed.success) return parsed.response + const data = parsed.data.body + + logger.info(`Listing deployments for environment '${data.environmentId}'`) + + const client = createAppConfigClient(data) + try { + const result = await listDeployments(client, { + applicationId: data.applicationId, + environmentId: data.environmentId, + maxResults: data.maxResults ?? undefined, + nextToken: data.nextToken ?? undefined, + }) + return NextResponse.json(result) + } finally { + client.destroy() + } + } catch (error) { + const errorMessage = toError(error).message || 'AppConfig list deployments failed' + logger.error('AppConfig list deployments failed:', error) + return NextResponse.json({ error: errorMessage }, { status: 500 }) + } +}) diff --git a/apps/sim/app/api/tools/appconfig/list-environments/route.ts b/apps/sim/app/api/tools/appconfig/list-environments/route.ts new file mode 100644 index 00000000000..ac92e912c6d --- /dev/null +++ b/apps/sim/app/api/tools/appconfig/list-environments/route.ts @@ -0,0 +1,44 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { awsAppConfigListEnvironmentsContract } from '@/lib/api/contracts/tools/aws/appconfig-list-environments' +import { parseToolRequest } from '@/lib/api/server' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { createAppConfigClient, listEnvironments } from '@/app/api/tools/appconfig/utils' + +const logger = createLogger('AppConfigListEnvironmentsAPI') + +export const POST = withRouteHandler(async (request: NextRequest) => { + try { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + const parsed = await parseToolRequest(awsAppConfigListEnvironmentsContract, request, { + errorFormat: 'details', + logger, + }) + if (!parsed.success) return parsed.response + const data = parsed.data.body + + logger.info(`Listing environments for application '${data.applicationId}'`) + + const client = createAppConfigClient(data) + try { + const result = await listEnvironments(client, { + applicationId: data.applicationId, + maxResults: data.maxResults ?? undefined, + nextToken: data.nextToken ?? undefined, + }) + return NextResponse.json(result) + } finally { + client.destroy() + } + } catch (error) { + const errorMessage = toError(error).message || 'AppConfig list environments failed' + logger.error('AppConfig list environments failed:', error) + return NextResponse.json({ error: errorMessage }, { status: 500 }) + } +}) diff --git a/apps/sim/app/api/tools/appconfig/list-hosted-configuration-versions/route.ts b/apps/sim/app/api/tools/appconfig/list-hosted-configuration-versions/route.ts new file mode 100644 index 00000000000..8f7a7841446 --- /dev/null +++ b/apps/sim/app/api/tools/appconfig/list-hosted-configuration-versions/route.ts @@ -0,0 +1,52 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { awsAppConfigListHostedConfigurationVersionsContract } from '@/lib/api/contracts/tools/aws/appconfig-list-hosted-configuration-versions' +import { parseToolRequest } from '@/lib/api/server' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { + createAppConfigClient, + listHostedConfigurationVersions, +} from '@/app/api/tools/appconfig/utils' + +const logger = createLogger('AppConfigListHostedConfigurationVersionsAPI') + +export const POST = withRouteHandler(async (request: NextRequest) => { + try { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + const parsed = await parseToolRequest( + awsAppConfigListHostedConfigurationVersionsContract, + request, + { errorFormat: 'details', logger } + ) + if (!parsed.success) return parsed.response + const data = parsed.data.body + + logger.info( + `Listing hosted configuration versions for profile '${data.configurationProfileId}'` + ) + + const client = createAppConfigClient(data) + try { + const result = await listHostedConfigurationVersions(client, { + applicationId: data.applicationId, + configurationProfileId: data.configurationProfileId, + maxResults: data.maxResults ?? undefined, + nextToken: data.nextToken ?? undefined, + }) + return NextResponse.json(result) + } finally { + client.destroy() + } + } catch (error) { + const errorMessage = + toError(error).message || 'AppConfig list hosted configuration versions failed' + logger.error('AppConfig list hosted configuration versions failed:', error) + return NextResponse.json({ error: errorMessage }, { status: 500 }) + } +}) diff --git a/apps/sim/app/api/tools/appconfig/start-deployment/route.ts b/apps/sim/app/api/tools/appconfig/start-deployment/route.ts new file mode 100644 index 00000000000..aeca649bdc1 --- /dev/null +++ b/apps/sim/app/api/tools/appconfig/start-deployment/route.ts @@ -0,0 +1,49 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { awsAppConfigStartDeploymentContract } from '@/lib/api/contracts/tools/aws/appconfig-start-deployment' +import { parseToolRequest } from '@/lib/api/server' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { createAppConfigClient, startDeployment } from '@/app/api/tools/appconfig/utils' + +const logger = createLogger('AppConfigStartDeploymentAPI') + +export const POST = withRouteHandler(async (request: NextRequest) => { + try { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + const parsed = await parseToolRequest(awsAppConfigStartDeploymentContract, request, { + errorFormat: 'details', + logger, + }) + if (!parsed.success) return parsed.response + const data = parsed.data.body + + logger.info( + `Starting deployment to environment '${data.environmentId}' (version ${data.configurationVersion})` + ) + + const client = createAppConfigClient(data) + try { + const result = await startDeployment(client, { + applicationId: data.applicationId, + environmentId: data.environmentId, + deploymentStrategyId: data.deploymentStrategyId, + configurationProfileId: data.configurationProfileId, + configurationVersion: data.configurationVersion, + description: data.description ?? undefined, + }) + return NextResponse.json(result) + } finally { + client.destroy() + } + } catch (error) { + const errorMessage = toError(error).message || 'AppConfig start deployment failed' + logger.error('AppConfig start deployment failed:', error) + return NextResponse.json({ error: errorMessage }, { status: 500 }) + } +}) diff --git a/apps/sim/app/api/tools/appconfig/stop-deployment/route.ts b/apps/sim/app/api/tools/appconfig/stop-deployment/route.ts new file mode 100644 index 00000000000..6391997cd70 --- /dev/null +++ b/apps/sim/app/api/tools/appconfig/stop-deployment/route.ts @@ -0,0 +1,46 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { awsAppConfigStopDeploymentContract } from '@/lib/api/contracts/tools/aws/appconfig-stop-deployment' +import { parseToolRequest } from '@/lib/api/server' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { createAppConfigClient, stopDeployment } from '@/app/api/tools/appconfig/utils' + +const logger = createLogger('AppConfigStopDeploymentAPI') + +export const POST = withRouteHandler(async (request: NextRequest) => { + try { + const auth = await checkInternalAuth(request) + if (!auth.success || !auth.userId) { + return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 }) + } + + const parsed = await parseToolRequest(awsAppConfigStopDeploymentContract, request, { + errorFormat: 'details', + logger, + }) + if (!parsed.success) return parsed.response + const data = parsed.data.body + + logger.info( + `Stopping deployment ${data.deploymentNumber} for environment '${data.environmentId}'` + ) + + const client = createAppConfigClient(data) + try { + const result = await stopDeployment(client, { + applicationId: data.applicationId, + environmentId: data.environmentId, + deploymentNumber: data.deploymentNumber, + }) + return NextResponse.json(result) + } finally { + client.destroy() + } + } catch (error) { + const errorMessage = toError(error).message || 'AppConfig stop deployment failed' + logger.error('AppConfig stop deployment failed:', error) + return NextResponse.json({ error: errorMessage }, { status: 500 }) + } +}) diff --git a/apps/sim/app/api/tools/appconfig/utils.ts b/apps/sim/app/api/tools/appconfig/utils.ts new file mode 100644 index 00000000000..c952c251b12 --- /dev/null +++ b/apps/sim/app/api/tools/appconfig/utils.ts @@ -0,0 +1,340 @@ +import { + AppConfigClient, + CreateHostedConfigurationVersionCommand, + GetDeploymentCommand, + GetHostedConfigurationVersionCommand, + ListApplicationsCommand, + ListConfigurationProfilesCommand, + ListDeploymentStrategiesCommand, + ListDeploymentsCommand, + ListEnvironmentsCommand, + ListHostedConfigurationVersionsCommand, + StartDeploymentCommand, + StopDeploymentCommand, +} from '@aws-sdk/client-appconfig' +import type { + AppConfigApplicationSummary, + AppConfigConnectionConfig, + AppConfigDeploymentSummary, + AppConfigEnvironmentSummary, + AppConfigProfileSummary, + AppConfigStrategySummary, + AppConfigVersionSummary, +} from '@/tools/appconfig/types' + +export function createAppConfigClient(config: AppConfigConnectionConfig): AppConfigClient { + return new AppConfigClient({ + region: config.region, + credentials: { + accessKeyId: config.accessKeyId, + secretAccessKey: config.secretAccessKey, + }, + }) +} + +const toIso = (date?: Date): string | null => (date ? date.toISOString() : null) +const decodeContent = (content?: Uint8Array): string | null => + content ? new TextDecoder().decode(content) : null + +export async function createHostedConfigurationVersion( + client: AppConfigClient, + params: { + applicationId: string + configurationProfileId: string + content: string + contentType: string + description?: string + versionLabel?: string + latestVersionNumber?: number + } +) { + const response = await client.send( + new CreateHostedConfigurationVersionCommand({ + ApplicationId: params.applicationId, + ConfigurationProfileId: params.configurationProfileId, + Content: new TextEncoder().encode(params.content), + ContentType: params.contentType, + ...(params.description && { Description: params.description }), + ...(params.versionLabel && { VersionLabel: params.versionLabel }), + ...(params.latestVersionNumber !== undefined && { + LatestVersionNumber: params.latestVersionNumber, + }), + }) + ) + + return { + message: `Created hosted configuration version ${response.VersionNumber ?? ''}`.trim(), + applicationId: response.ApplicationId ?? null, + configurationProfileId: response.ConfigurationProfileId ?? null, + versionNumber: response.VersionNumber ?? null, + contentType: response.ContentType ?? null, + description: response.Description ?? null, + versionLabel: response.VersionLabel ?? null, + } +} + +export async function getHostedConfigurationVersion( + client: AppConfigClient, + params: { applicationId: string; configurationProfileId: string; versionNumber: number } +) { + const response = await client.send( + new GetHostedConfigurationVersionCommand({ + ApplicationId: params.applicationId, + ConfigurationProfileId: params.configurationProfileId, + VersionNumber: params.versionNumber, + }) + ) + + return { + applicationId: response.ApplicationId ?? null, + configurationProfileId: response.ConfigurationProfileId ?? null, + versionNumber: response.VersionNumber ?? null, + content: decodeContent(response.Content), + contentType: response.ContentType ?? null, + description: response.Description ?? null, + versionLabel: response.VersionLabel ?? null, + } +} + +export async function listHostedConfigurationVersions( + client: AppConfigClient, + params: { + applicationId: string + configurationProfileId: string + maxResults?: number + nextToken?: string + } +) { + const response = await client.send( + new ListHostedConfigurationVersionsCommand({ + ApplicationId: params.applicationId, + ConfigurationProfileId: params.configurationProfileId, + ...(params.maxResults !== undefined && { MaxResults: params.maxResults }), + ...(params.nextToken && { NextToken: params.nextToken }), + }) + ) + + const items: AppConfigVersionSummary[] = (response.Items ?? []).map((item) => ({ + applicationId: item.ApplicationId ?? null, + configurationProfileId: item.ConfigurationProfileId ?? null, + versionNumber: item.VersionNumber ?? null, + description: item.Description ?? null, + contentType: item.ContentType ?? null, + versionLabel: item.VersionLabel ?? null, + })) + + return { items, nextToken: response.NextToken ?? null } +} + +export async function startDeployment( + client: AppConfigClient, + params: { + applicationId: string + environmentId: string + deploymentStrategyId: string + configurationProfileId: string + configurationVersion: string + description?: string + } +) { + const response = await client.send( + new StartDeploymentCommand({ + ApplicationId: params.applicationId, + EnvironmentId: params.environmentId, + DeploymentStrategyId: params.deploymentStrategyId, + ConfigurationProfileId: params.configurationProfileId, + ConfigurationVersion: params.configurationVersion, + ...(params.description && { Description: params.description }), + }) + ) + + return { + message: `Started deployment ${response.DeploymentNumber ?? ''}`.trim(), + applicationId: response.ApplicationId ?? null, + environmentId: response.EnvironmentId ?? null, + deploymentNumber: response.DeploymentNumber ?? null, + deploymentStrategyId: response.DeploymentStrategyId ?? null, + configurationProfileId: response.ConfigurationProfileId ?? null, + configurationVersion: response.ConfigurationVersion ?? null, + description: response.Description ?? null, + state: response.State ?? null, + percentageComplete: response.PercentageComplete ?? null, + startedAt: toIso(response.StartedAt), + completedAt: toIso(response.CompletedAt), + } +} + +export async function getDeployment( + client: AppConfigClient, + params: { applicationId: string; environmentId: string; deploymentNumber: number } +) { + const response = await client.send( + new GetDeploymentCommand({ + ApplicationId: params.applicationId, + EnvironmentId: params.environmentId, + DeploymentNumber: params.deploymentNumber, + }) + ) + + return { + message: + `Deployment ${response.DeploymentNumber ?? ''} is ${response.State ?? 'UNKNOWN'}`.trim(), + applicationId: response.ApplicationId ?? null, + environmentId: response.EnvironmentId ?? null, + deploymentNumber: response.DeploymentNumber ?? null, + deploymentStrategyId: response.DeploymentStrategyId ?? null, + configurationProfileId: response.ConfigurationProfileId ?? null, + configurationVersion: response.ConfigurationVersion ?? null, + description: response.Description ?? null, + state: response.State ?? null, + percentageComplete: response.PercentageComplete ?? null, + startedAt: toIso(response.StartedAt), + completedAt: toIso(response.CompletedAt), + } +} + +export async function stopDeployment( + client: AppConfigClient, + params: { applicationId: string; environmentId: string; deploymentNumber: number } +) { + const response = await client.send( + new StopDeploymentCommand({ + ApplicationId: params.applicationId, + EnvironmentId: params.environmentId, + DeploymentNumber: params.deploymentNumber, + }) + ) + + return { + message: `Stopped deployment ${response.DeploymentNumber ?? ''}`.trim(), + applicationId: response.ApplicationId ?? null, + environmentId: response.EnvironmentId ?? null, + deploymentNumber: response.DeploymentNumber ?? null, + deploymentStrategyId: response.DeploymentStrategyId ?? null, + configurationProfileId: response.ConfigurationProfileId ?? null, + configurationVersion: response.ConfigurationVersion ?? null, + description: response.Description ?? null, + state: response.State ?? null, + percentageComplete: response.PercentageComplete ?? null, + startedAt: toIso(response.StartedAt), + completedAt: toIso(response.CompletedAt), + } +} + +export async function listDeployments( + client: AppConfigClient, + params: { applicationId: string; environmentId: string; maxResults?: number; nextToken?: string } +) { + const response = await client.send( + new ListDeploymentsCommand({ + ApplicationId: params.applicationId, + EnvironmentId: params.environmentId, + ...(params.maxResults !== undefined && { MaxResults: params.maxResults }), + ...(params.nextToken && { NextToken: params.nextToken }), + }) + ) + + const items: AppConfigDeploymentSummary[] = (response.Items ?? []).map((item) => ({ + deploymentNumber: item.DeploymentNumber ?? null, + configurationName: item.ConfigurationName ?? null, + configurationVersion: item.ConfigurationVersion ?? null, + state: item.State ?? null, + percentageComplete: item.PercentageComplete ?? null, + startedAt: toIso(item.StartedAt), + completedAt: toIso(item.CompletedAt), + versionLabel: item.VersionLabel ?? null, + })) + + return { items, nextToken: response.NextToken ?? null } +} + +export async function listApplications( + client: AppConfigClient, + params: { maxResults?: number; nextToken?: string } +) { + const response = await client.send( + new ListApplicationsCommand({ + ...(params.maxResults !== undefined && { MaxResults: params.maxResults }), + ...(params.nextToken && { NextToken: params.nextToken }), + }) + ) + + const items: AppConfigApplicationSummary[] = (response.Items ?? []).map((item) => ({ + id: item.Id ?? null, + name: item.Name ?? null, + description: item.Description ?? null, + })) + + return { items, nextToken: response.NextToken ?? null } +} + +export async function listEnvironments( + client: AppConfigClient, + params: { applicationId: string; maxResults?: number; nextToken?: string } +) { + const response = await client.send( + new ListEnvironmentsCommand({ + ApplicationId: params.applicationId, + ...(params.maxResults !== undefined && { MaxResults: params.maxResults }), + ...(params.nextToken && { NextToken: params.nextToken }), + }) + ) + + const items: AppConfigEnvironmentSummary[] = (response.Items ?? []).map((item) => ({ + applicationId: item.ApplicationId ?? null, + id: item.Id ?? null, + name: item.Name ?? null, + state: item.State ?? null, + description: item.Description ?? null, + })) + + return { items, nextToken: response.NextToken ?? null } +} + +export async function listConfigurationProfiles( + client: AppConfigClient, + params: { applicationId: string; maxResults?: number; nextToken?: string } +) { + const response = await client.send( + new ListConfigurationProfilesCommand({ + ApplicationId: params.applicationId, + ...(params.maxResults !== undefined && { MaxResults: params.maxResults }), + ...(params.nextToken && { NextToken: params.nextToken }), + }) + ) + + const items: AppConfigProfileSummary[] = (response.Items ?? []).map((item) => ({ + applicationId: item.ApplicationId ?? null, + id: item.Id ?? null, + name: item.Name ?? null, + locationUri: item.LocationUri ?? null, + type: item.Type ?? null, + })) + + return { items, nextToken: response.NextToken ?? null } +} + +export async function listDeploymentStrategies( + client: AppConfigClient, + params: { maxResults?: number; nextToken?: string } +) { + const response = await client.send( + new ListDeploymentStrategiesCommand({ + ...(params.maxResults !== undefined && { MaxResults: params.maxResults }), + ...(params.nextToken && { NextToken: params.nextToken }), + }) + ) + + const items: AppConfigStrategySummary[] = (response.Items ?? []).map((item) => ({ + id: item.Id ?? null, + name: item.Name ?? null, + description: item.Description ?? null, + deploymentDurationInMinutes: item.DeploymentDurationInMinutes ?? null, + growthType: item.GrowthType ?? null, + growthFactor: item.GrowthFactor ?? null, + finalBakeTimeInMinutes: item.FinalBakeTimeInMinutes ?? null, + replicateTo: item.ReplicateTo ?? null, + })) + + return { items, nextToken: response.NextToken ?? null } +} diff --git a/apps/sim/blocks/blocks/appconfig.ts b/apps/sim/blocks/blocks/appconfig.ts new file mode 100644 index 00000000000..44ba25fe6f8 --- /dev/null +++ b/apps/sim/blocks/blocks/appconfig.ts @@ -0,0 +1,436 @@ +import { AppConfigIcon } from '@/components/icons' +import type { BlockConfig, BlockMeta } from '@/blocks/types' +import { AuthMode, IntegrationType } from '@/blocks/types' +import type { AppConfigResponse } from '@/tools/appconfig/types' + +const APP_ID_OPERATIONS = [ + 'create_version', + 'get_version', + 'list_versions', + 'start_deployment', + 'get_deployment', + 'stop_deployment', + 'list_deployments', + 'list_environments', + 'list_profiles', +] +const PROFILE_ID_OPERATIONS = ['create_version', 'get_version', 'list_versions', 'start_deployment'] +const ENVIRONMENT_ID_OPERATIONS = [ + 'start_deployment', + 'get_deployment', + 'stop_deployment', + 'list_deployments', +] +const DEPLOYMENT_NUMBER_OPERATIONS = ['get_deployment', 'stop_deployment'] +const PAGINATED_OPERATIONS = [ + 'list_versions', + 'list_deployments', + 'list_applications', + 'list_environments', + 'list_profiles', + 'list_strategies', +] + +/** Strict integer parse — rejects partial strings like "12abc" (no parseInt truncation). */ +const toInteger = (value: unknown): number | undefined => { + if (value === undefined || value === null || value === '') return undefined + const parsed = Number(value) + return Number.isInteger(parsed) ? parsed : undefined +} + +export const AppConfigBlock: BlockConfig = { + type: 'appconfig', + name: 'AWS AppConfig', + description: 'Manage configuration versions and deployments in AWS AppConfig', + authMode: AuthMode.ApiKey, + longDescription: + 'Integrate AWS AppConfig into workflows. Create hosted configuration versions, start, stop, and inspect deployments, and discover applications, environments, configuration profiles, and deployment strategies.', + docsLink: 'https://docs.sim.ai/tools/appconfig', + category: 'tools', + integrationType: IntegrationType.DevOps, + bgColor: '#E7157B', + iconColor: '#E7157B', + icon: AppConfigIcon, + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'Create Configuration Version', id: 'create_version' }, + { label: 'Get Configuration Version', id: 'get_version' }, + { label: 'List Configuration Versions', id: 'list_versions' }, + { label: 'Start Deployment', id: 'start_deployment' }, + { label: 'Get Deployment', id: 'get_deployment' }, + { label: 'Stop Deployment', id: 'stop_deployment' }, + { label: 'List Deployments', id: 'list_deployments' }, + { label: 'List Applications', id: 'list_applications' }, + { label: 'List Environments', id: 'list_environments' }, + { label: 'List Configuration Profiles', id: 'list_profiles' }, + { label: 'List Deployment Strategies', id: 'list_strategies' }, + ], + value: () => 'list_applications', + }, + { + id: 'region', + title: 'AWS Region', + type: 'short-input', + placeholder: 'us-east-1', + required: true, + }, + { + id: 'accessKeyId', + title: 'AWS Access Key ID', + type: 'short-input', + placeholder: 'AKIA...', + password: true, + required: true, + }, + { + id: 'secretAccessKey', + title: 'AWS Secret Access Key', + type: 'short-input', + placeholder: 'Your secret access key', + password: true, + required: true, + }, + { + id: 'applicationId', + title: 'Application ID', + type: 'short-input', + placeholder: 'The AppConfig application ID', + condition: { field: 'operation', value: APP_ID_OPERATIONS }, + required: { field: 'operation', value: APP_ID_OPERATIONS }, + }, + { + id: 'configurationProfileId', + title: 'Configuration Profile ID', + type: 'short-input', + placeholder: 'The configuration profile ID', + condition: { field: 'operation', value: PROFILE_ID_OPERATIONS }, + required: { field: 'operation', value: PROFILE_ID_OPERATIONS }, + }, + { + id: 'environmentId', + title: 'Environment ID', + type: 'short-input', + placeholder: 'The environment ID', + condition: { field: 'operation', value: ENVIRONMENT_ID_OPERATIONS }, + required: { field: 'operation', value: ENVIRONMENT_ID_OPERATIONS }, + }, + { + id: 'content', + title: 'Configuration Content', + type: 'code', + placeholder: '{\n "featureEnabled": true\n}', + condition: { field: 'operation', value: 'create_version' }, + required: { field: 'operation', value: 'create_version' }, + wandConfig: { + enabled: true, + prompt: `Generate AppConfig configuration content based on the user's description. +Match the format selected in the Content Type field — JSON for application/json, YAML for application/x-yaml, or plain text for text/plain. + +Return ONLY the configuration content - no explanations, no markdown code blocks.`, + placeholder: 'Describe the configuration...', + }, + }, + { + id: 'contentType', + title: 'Content Type', + type: 'dropdown', + options: [ + { label: 'application/json', id: 'application/json' }, + { label: 'application/x-yaml', id: 'application/x-yaml' }, + { label: 'text/plain', id: 'text/plain' }, + ], + value: () => 'application/json', + condition: { field: 'operation', value: 'create_version' }, + required: { field: 'operation', value: 'create_version' }, + }, + { + id: 'versionNumber', + title: 'Version Number', + type: 'short-input', + placeholder: '1', + condition: { field: 'operation', value: 'get_version' }, + required: { field: 'operation', value: 'get_version' }, + }, + { + id: 'deploymentStrategyId', + title: 'Deployment Strategy ID', + type: 'short-input', + placeholder: 'e.g., AppConfig.AllAtOnce', + condition: { field: 'operation', value: 'start_deployment' }, + required: { field: 'operation', value: 'start_deployment' }, + }, + { + id: 'configurationVersion', + title: 'Configuration Version', + type: 'short-input', + placeholder: 'Version number or label to deploy', + condition: { field: 'operation', value: 'start_deployment' }, + required: { field: 'operation', value: 'start_deployment' }, + }, + { + id: 'deploymentNumber', + title: 'Deployment Number', + type: 'short-input', + placeholder: '1', + condition: { field: 'operation', value: DEPLOYMENT_NUMBER_OPERATIONS }, + required: { field: 'operation', value: DEPLOYMENT_NUMBER_OPERATIONS }, + }, + { + id: 'description', + title: 'Description', + type: 'short-input', + placeholder: 'Optional description', + condition: { field: 'operation', value: ['create_version', 'start_deployment'] }, + mode: 'advanced', + }, + { + id: 'versionLabel', + title: 'Version Label', + type: 'short-input', + placeholder: 'e.g., v2.2.0', + condition: { field: 'operation', value: 'create_version' }, + mode: 'advanced', + }, + { + id: 'latestVersionNumber', + title: 'Latest Version Number (Lock)', + type: 'short-input', + placeholder: 'Prevents overwrites if set', + condition: { field: 'operation', value: 'create_version' }, + mode: 'advanced', + }, + { + id: 'maxResults', + title: 'Max Results', + type: 'short-input', + placeholder: '50', + condition: { field: 'operation', value: PAGINATED_OPERATIONS }, + mode: 'advanced', + }, + { + id: 'nextToken', + title: 'Next Token', + type: 'short-input', + placeholder: 'Pagination token from a previous response', + condition: { field: 'operation', value: PAGINATED_OPERATIONS }, + mode: 'advanced', + }, + ], + tools: { + access: [ + 'appconfig_create_hosted_configuration_version', + 'appconfig_get_hosted_configuration_version', + 'appconfig_list_hosted_configuration_versions', + 'appconfig_start_deployment', + 'appconfig_get_deployment', + 'appconfig_stop_deployment', + 'appconfig_list_deployments', + 'appconfig_list_applications', + 'appconfig_list_environments', + 'appconfig_list_configuration_profiles', + 'appconfig_list_deployment_strategies', + ], + config: { + tool: (params) => { + switch (params.operation) { + case 'create_version': + return 'appconfig_create_hosted_configuration_version' + case 'get_version': + return 'appconfig_get_hosted_configuration_version' + case 'list_versions': + return 'appconfig_list_hosted_configuration_versions' + case 'start_deployment': + return 'appconfig_start_deployment' + case 'get_deployment': + return 'appconfig_get_deployment' + case 'stop_deployment': + return 'appconfig_stop_deployment' + case 'list_deployments': + return 'appconfig_list_deployments' + case 'list_applications': + return 'appconfig_list_applications' + case 'list_environments': + return 'appconfig_list_environments' + case 'list_profiles': + return 'appconfig_list_configuration_profiles' + case 'list_strategies': + return 'appconfig_list_deployment_strategies' + default: + throw new Error(`Invalid AppConfig operation: ${params.operation}`) + } + }, + params: (params) => { + const op = params.operation as string + const result: Record = { + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + } + + if (APP_ID_OPERATIONS.includes(op)) result.applicationId = params.applicationId + if (PROFILE_ID_OPERATIONS.includes(op)) + result.configurationProfileId = params.configurationProfileId + if (ENVIRONMENT_ID_OPERATIONS.includes(op)) result.environmentId = params.environmentId + + if (op === 'create_version') { + result.content = params.content + result.contentType = params.contentType + if (params.description) result.description = params.description + if (params.versionLabel) result.versionLabel = params.versionLabel + const latest = toInteger(params.latestVersionNumber) + if (latest !== undefined) result.latestVersionNumber = latest + } + + if (op === 'get_version') { + // Required: pass the raw value through when not a clean integer so the contract + // surfaces a clear typed error instead of silently dropping the field. + result.versionNumber = toInteger(params.versionNumber) ?? params.versionNumber + } + + if (op === 'start_deployment') { + result.deploymentStrategyId = params.deploymentStrategyId + // Stringify: a versionNumber piped from a create/list step resolves to a JSON + // number, but AppConfig's ConfigurationVersion (number or label) is a string. + if (params.configurationVersion !== undefined && params.configurationVersion !== null) { + result.configurationVersion = String(params.configurationVersion) + } + if (params.description) result.description = params.description + } + + if (DEPLOYMENT_NUMBER_OPERATIONS.includes(op)) { + // Required: pass the raw value through when not a clean integer so the contract + // surfaces a clear typed error instead of silently dropping the field. + result.deploymentNumber = toInteger(params.deploymentNumber) ?? params.deploymentNumber + } + + if (PAGINATED_OPERATIONS.includes(op)) { + const max = toInteger(params.maxResults) + if (max !== undefined) result.maxResults = max + if (params.nextToken) result.nextToken = params.nextToken + } + + return result + }, + }, + }, + inputs: { + operation: { type: 'string', description: 'AppConfig operation to perform' }, + region: { type: 'string', description: 'AWS region' }, + accessKeyId: { type: 'string', description: 'AWS access key ID' }, + secretAccessKey: { type: 'string', description: 'AWS secret access key' }, + applicationId: { type: 'string', description: 'The AppConfig application ID' }, + configurationProfileId: { type: 'string', description: 'The configuration profile ID' }, + environmentId: { type: 'string', description: 'The environment ID' }, + content: { type: 'string', description: 'Configuration content for a new version' }, + contentType: { type: 'string', description: 'MIME type of the configuration content' }, + versionNumber: { type: 'number', description: 'Hosted configuration version number' }, + deploymentStrategyId: { type: 'string', description: 'The deployment strategy ID' }, + configurationVersion: { type: 'string', description: 'Configuration version to deploy' }, + deploymentNumber: { type: 'number', description: 'The deployment sequence number' }, + description: { type: 'string', description: 'Optional description' }, + versionLabel: { type: 'string', description: 'Optional version label' }, + latestVersionNumber: { type: 'number', description: 'Locking token for create version' }, + maxResults: { type: 'number', description: 'Maximum number of results to return' }, + nextToken: { type: 'string', description: 'Pagination token' }, + }, + outputs: { + message: { type: 'string', description: 'Operation status message' }, + applicationId: { type: 'string', description: 'The application ID' }, + configurationProfileId: { type: 'string', description: 'The configuration profile ID' }, + environmentId: { type: 'string', description: 'The environment ID' }, + versionNumber: { type: 'number', description: 'The hosted configuration version number' }, + content: { type: 'string', description: 'The configuration content' }, + contentType: { type: 'string', description: 'The content MIME type' }, + versionLabel: { type: 'string', description: 'The version label' }, + deploymentNumber: { type: 'number', description: 'The deployment sequence number' }, + deploymentStrategyId: { type: 'string', description: 'The deployment strategy ID' }, + configurationVersion: { type: 'string', description: 'The deployed configuration version' }, + description: { type: 'string', description: 'Description of the version or deployment' }, + state: { type: 'string', description: 'The deployment state' }, + percentageComplete: { type: 'number', description: 'Percentage of targets deployed' }, + startedAt: { type: 'string', description: 'When the deployment started (ISO)' }, + completedAt: { type: 'string', description: 'When the deployment completed (ISO)' }, + items: { + type: 'array', + description: + 'List results — versions, deployments, applications, environments, profiles, or strategies depending on the operation', + }, + nextToken: { type: 'string', description: 'Pagination token for the next page' }, + }, +} + +export const AppConfigBlockMeta = { + tags: ['cloud', 'feature-flags'], + templates: [ + { + icon: AppConfigIcon, + title: 'AppConfig version + deploy', + prompt: + 'Build a workflow that creates a new AppConfig hosted configuration version from provided JSON, then starts a deployment to the production environment and reports the resulting deployment number.', + modules: ['agent', 'workflows'], + category: 'engineering', + tags: ['devops', 'automation'], + }, + { + icon: AppConfigIcon, + title: 'AppConfig deployment watcher', + prompt: + 'Create a scheduled workflow that lists in-progress AppConfig deployments for an environment, checks each deployment status, and posts a summary to Slack when any deployment completes or rolls back.', + modules: ['scheduled', 'agent', 'workflows'], + category: 'operations', + tags: ['devops', 'monitoring'], + alsoIntegrations: ['slack'], + }, + { + icon: AppConfigIcon, + title: 'AppConfig auto-rollback on alarm', + prompt: + 'Build a workflow that watches a CloudWatch alarm and, when it fires, stops the active AppConfig deployment for the affected environment and notifies the on-call engineer.', + modules: ['agent', 'workflows'], + category: 'engineering', + tags: ['devops', 'incident'], + alsoIntegrations: ['cloudwatch', 'slack'], + }, + { + icon: AppConfigIcon, + title: 'AppConfig config audit', + prompt: + 'Create a scheduled workflow that lists every AppConfig application, environment, and configuration profile, fetches the latest hosted version content, and writes a configuration inventory to a file.', + modules: ['scheduled', 'agent', 'files', 'workflows'], + category: 'operations', + tags: ['devops', 'audit'], + }, + { + icon: AppConfigIcon, + title: 'AppConfig feature flag rollout', + prompt: + 'Build a workflow that creates a new feature-flag configuration version enabling a flag, then deploys it to staging using a gradual deployment strategy and confirms the deployment reached 100 percent.', + modules: ['agent', 'workflows'], + category: 'engineering', + tags: ['feature-flags', 'devops'], + }, + { + icon: AppConfigIcon, + title: 'AppConfig promote staging to prod', + prompt: + 'Create a workflow that reads the configuration version currently deployed to staging in AppConfig and starts a deployment of that same version to the production environment after approval.', + modules: ['agent', 'workflows'], + category: 'operations', + tags: ['devops', 'promotion'], + }, + { + icon: AppConfigIcon, + title: 'AppConfig deployment digest', + prompt: + 'Build a scheduled weekly workflow that lists AppConfig deployments across environments, summarizes successes, rollbacks, and durations, and emails the digest to the platform team.', + modules: ['scheduled', 'agent', 'workflows'], + category: 'operations', + tags: ['devops', 'reporting'], + alsoIntegrations: ['gmail'], + }, + ], +} as const satisfies BlockMeta diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index f9ba05533b9..1ef98d0d32a 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -13,6 +13,7 @@ import { ApiBlock } from '@/blocks/blocks/api' import { ApiTriggerBlock } from '@/blocks/blocks/api_trigger' import { ApifyBlock, ApifyBlockMeta } from '@/blocks/blocks/apify' import { ApolloBlock, ApolloBlockMeta } from '@/blocks/blocks/apollo' +import { AppConfigBlock, AppConfigBlockMeta } from '@/blocks/blocks/appconfig' import { ArxivBlock, ArxivBlockMeta } from '@/blocks/blocks/arxiv' import { AsanaBlock, AsanaBlockMeta } from '@/blocks/blocks/asana' import { AshbyBlock, AshbyBlockMeta } from '@/blocks/blocks/ashby' @@ -329,6 +330,7 @@ const BLOCK_REGISTRY: Record = { api_trigger: ApiTriggerBlock, apify: ApifyBlock, apollo: ApolloBlock, + appconfig: AppConfigBlock, arxiv: ArxivBlock, asana: AsanaBlock, ashby: AshbyBlock, @@ -616,6 +618,7 @@ const BLOCK_META_REGISTRY: Record = { amplitude: AmplitudeBlockMeta, apify: ApifyBlockMeta, apollo: ApolloBlockMeta, + appconfig: AppConfigBlockMeta, arxiv: ArxivBlockMeta, asana: AsanaBlockMeta, ashby: AshbyBlockMeta, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 12aeb36697b..368127949b8 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -5258,6 +5258,17 @@ export function RDSIcon(props: SVGProps) { ) } +export function AppConfigIcon(props: SVGProps) { + return ( + + + + ) +} + export function DynamoDBIcon(props: SVGProps) { return ( validateAwsRegion(v).isValid, { + message: 'Invalid AWS region format (e.g., us-east-1, eu-west-2)', + }), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + applicationId: z.string().min(1, 'Application ID is required'), + configurationProfileId: z.string().min(1, 'Configuration profile ID is required'), + content: z.string().min(1, 'Configuration content is required'), + contentType: z.string().min(1, 'Content type is required'), + description: z.string().optional().nullable(), + versionLabel: z.string().optional().nullable(), + latestVersionNumber: z.number().int().optional().nullable(), +}) + +const ResponseSchema = z.object({ + message: z.string(), + applicationId: z.string().nullable(), + configurationProfileId: z.string().nullable(), + versionNumber: z.number().nullable(), + contentType: z.string().nullable(), + description: z.string().nullable(), + versionLabel: z.string().nullable(), +}) + +export const awsAppConfigCreateHostedConfigurationVersionContract = defineRouteContract({ + method: 'POST', + path: '/api/tools/appconfig/create-hosted-configuration-version', + body: Schema, + response: { mode: 'json', schema: ResponseSchema }, +}) +export type AwsAppConfigCreateHostedConfigurationVersionRequest = ContractBodyInput< + typeof awsAppConfigCreateHostedConfigurationVersionContract +> +export type AwsAppConfigCreateHostedConfigurationVersionBody = ContractBody< + typeof awsAppConfigCreateHostedConfigurationVersionContract +> +export type AwsAppConfigCreateHostedConfigurationVersionResponse = ContractJsonResponse< + typeof awsAppConfigCreateHostedConfigurationVersionContract +> diff --git a/apps/sim/lib/api/contracts/tools/aws/appconfig-get-deployment.ts b/apps/sim/lib/api/contracts/tools/aws/appconfig-get-deployment.ts new file mode 100644 index 00000000000..0099da9ef19 --- /dev/null +++ b/apps/sim/lib/api/contracts/tools/aws/appconfig-get-deployment.ts @@ -0,0 +1,51 @@ +import { z } from 'zod' +import type { + ContractBody, + ContractBodyInput, + ContractJsonResponse, +} from '@/lib/api/contracts/types' +import { defineRouteContract } from '@/lib/api/contracts/types' +import { validateAwsRegion } from '@/lib/core/security/input-validation' + +const Schema = z.object({ + region: z + .string() + .min(1, 'AWS region is required') + .refine((v) => validateAwsRegion(v).isValid, { + message: 'Invalid AWS region format (e.g., us-east-1, eu-west-2)', + }), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + applicationId: z.string().min(1, 'Application ID is required'), + environmentId: z.string().min(1, 'Environment ID is required'), + deploymentNumber: z.number().int().min(1, 'Deployment number is required'), +}) + +const ResponseSchema = z.object({ + message: z.string(), + applicationId: z.string().nullable(), + environmentId: z.string().nullable(), + deploymentNumber: z.number().nullable(), + deploymentStrategyId: z.string().nullable(), + configurationProfileId: z.string().nullable(), + configurationVersion: z.string().nullable(), + description: z.string().nullable(), + state: z.string().nullable(), + percentageComplete: z.number().nullable(), + startedAt: z.string().nullable(), + completedAt: z.string().nullable(), +}) + +export const awsAppConfigGetDeploymentContract = defineRouteContract({ + method: 'POST', + path: '/api/tools/appconfig/get-deployment', + body: Schema, + response: { mode: 'json', schema: ResponseSchema }, +}) +export type AwsAppConfigGetDeploymentRequest = ContractBodyInput< + typeof awsAppConfigGetDeploymentContract +> +export type AwsAppConfigGetDeploymentBody = ContractBody +export type AwsAppConfigGetDeploymentResponse = ContractJsonResponse< + typeof awsAppConfigGetDeploymentContract +> diff --git a/apps/sim/lib/api/contracts/tools/aws/appconfig-get-hosted-configuration-version.ts b/apps/sim/lib/api/contracts/tools/aws/appconfig-get-hosted-configuration-version.ts new file mode 100644 index 00000000000..9b8854df6fd --- /dev/null +++ b/apps/sim/lib/api/contracts/tools/aws/appconfig-get-hosted-configuration-version.ts @@ -0,0 +1,48 @@ +import { z } from 'zod' +import type { + ContractBody, + ContractBodyInput, + ContractJsonResponse, +} from '@/lib/api/contracts/types' +import { defineRouteContract } from '@/lib/api/contracts/types' +import { validateAwsRegion } from '@/lib/core/security/input-validation' + +const Schema = z.object({ + region: z + .string() + .min(1, 'AWS region is required') + .refine((v) => validateAwsRegion(v).isValid, { + message: 'Invalid AWS region format (e.g., us-east-1, eu-west-2)', + }), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + applicationId: z.string().min(1, 'Application ID is required'), + configurationProfileId: z.string().min(1, 'Configuration profile ID is required'), + versionNumber: z.number().int().min(1, 'Version number is required'), +}) + +const ResponseSchema = z.object({ + applicationId: z.string().nullable(), + configurationProfileId: z.string().nullable(), + versionNumber: z.number().nullable(), + content: z.string().nullable(), + contentType: z.string().nullable(), + description: z.string().nullable(), + versionLabel: z.string().nullable(), +}) + +export const awsAppConfigGetHostedConfigurationVersionContract = defineRouteContract({ + method: 'POST', + path: '/api/tools/appconfig/get-hosted-configuration-version', + body: Schema, + response: { mode: 'json', schema: ResponseSchema }, +}) +export type AwsAppConfigGetHostedConfigurationVersionRequest = ContractBodyInput< + typeof awsAppConfigGetHostedConfigurationVersionContract +> +export type AwsAppConfigGetHostedConfigurationVersionBody = ContractBody< + typeof awsAppConfigGetHostedConfigurationVersionContract +> +export type AwsAppConfigGetHostedConfigurationVersionResponse = ContractJsonResponse< + typeof awsAppConfigGetHostedConfigurationVersionContract +> diff --git a/apps/sim/lib/api/contracts/tools/aws/appconfig-list-applications.ts b/apps/sim/lib/api/contracts/tools/aws/appconfig-list-applications.ts new file mode 100644 index 00000000000..7a1c053f550 --- /dev/null +++ b/apps/sim/lib/api/contracts/tools/aws/appconfig-list-applications.ts @@ -0,0 +1,48 @@ +import { z } from 'zod' +import type { + ContractBody, + ContractBodyInput, + ContractJsonResponse, +} from '@/lib/api/contracts/types' +import { defineRouteContract } from '@/lib/api/contracts/types' +import { validateAwsRegion } from '@/lib/core/security/input-validation' + +const Schema = z.object({ + region: z + .string() + .min(1, 'AWS region is required') + .refine((v) => validateAwsRegion(v).isValid, { + message: 'Invalid AWS region format (e.g., us-east-1, eu-west-2)', + }), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + maxResults: z.number().int().min(1).max(50).optional().nullable(), + nextToken: z.string().optional().nullable(), +}) + +const ResponseSchema = z.object({ + items: z.array( + z.object({ + id: z.string().nullable(), + name: z.string().nullable(), + description: z.string().nullable(), + }) + ), + nextToken: z.string().nullable(), +}) + +export const awsAppConfigListApplicationsContract = defineRouteContract({ + method: 'POST', + path: '/api/tools/appconfig/list-applications', + body: Schema, + response: { mode: 'json', schema: ResponseSchema }, +}) +export type AwsAppConfigListApplicationsRequest = ContractBodyInput< + typeof awsAppConfigListApplicationsContract +> +export type AwsAppConfigListApplicationsBody = ContractBody< + typeof awsAppConfigListApplicationsContract +> +export type AwsAppConfigListApplicationsResponse = ContractJsonResponse< + typeof awsAppConfigListApplicationsContract +> diff --git a/apps/sim/lib/api/contracts/tools/aws/appconfig-list-configuration-profiles.ts b/apps/sim/lib/api/contracts/tools/aws/appconfig-list-configuration-profiles.ts new file mode 100644 index 00000000000..8c165bce827 --- /dev/null +++ b/apps/sim/lib/api/contracts/tools/aws/appconfig-list-configuration-profiles.ts @@ -0,0 +1,51 @@ +import { z } from 'zod' +import type { + ContractBody, + ContractBodyInput, + ContractJsonResponse, +} from '@/lib/api/contracts/types' +import { defineRouteContract } from '@/lib/api/contracts/types' +import { validateAwsRegion } from '@/lib/core/security/input-validation' + +const Schema = z.object({ + region: z + .string() + .min(1, 'AWS region is required') + .refine((v) => validateAwsRegion(v).isValid, { + message: 'Invalid AWS region format (e.g., us-east-1, eu-west-2)', + }), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + applicationId: z.string().min(1, 'Application ID is required'), + maxResults: z.number().int().min(1).max(50).optional().nullable(), + nextToken: z.string().optional().nullable(), +}) + +const ResponseSchema = z.object({ + items: z.array( + z.object({ + applicationId: z.string().nullable(), + id: z.string().nullable(), + name: z.string().nullable(), + locationUri: z.string().nullable(), + type: z.string().nullable(), + }) + ), + nextToken: z.string().nullable(), +}) + +export const awsAppConfigListConfigurationProfilesContract = defineRouteContract({ + method: 'POST', + path: '/api/tools/appconfig/list-configuration-profiles', + body: Schema, + response: { mode: 'json', schema: ResponseSchema }, +}) +export type AwsAppConfigListConfigurationProfilesRequest = ContractBodyInput< + typeof awsAppConfigListConfigurationProfilesContract +> +export type AwsAppConfigListConfigurationProfilesBody = ContractBody< + typeof awsAppConfigListConfigurationProfilesContract +> +export type AwsAppConfigListConfigurationProfilesResponse = ContractJsonResponse< + typeof awsAppConfigListConfigurationProfilesContract +> diff --git a/apps/sim/lib/api/contracts/tools/aws/appconfig-list-deployment-strategies.ts b/apps/sim/lib/api/contracts/tools/aws/appconfig-list-deployment-strategies.ts new file mode 100644 index 00000000000..fd4f73ef77a --- /dev/null +++ b/apps/sim/lib/api/contracts/tools/aws/appconfig-list-deployment-strategies.ts @@ -0,0 +1,53 @@ +import { z } from 'zod' +import type { + ContractBody, + ContractBodyInput, + ContractJsonResponse, +} from '@/lib/api/contracts/types' +import { defineRouteContract } from '@/lib/api/contracts/types' +import { validateAwsRegion } from '@/lib/core/security/input-validation' + +const Schema = z.object({ + region: z + .string() + .min(1, 'AWS region is required') + .refine((v) => validateAwsRegion(v).isValid, { + message: 'Invalid AWS region format (e.g., us-east-1, eu-west-2)', + }), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + maxResults: z.number().int().min(1).max(50).optional().nullable(), + nextToken: z.string().optional().nullable(), +}) + +const ResponseSchema = z.object({ + items: z.array( + z.object({ + id: z.string().nullable(), + name: z.string().nullable(), + description: z.string().nullable(), + deploymentDurationInMinutes: z.number().nullable(), + growthType: z.string().nullable(), + growthFactor: z.number().nullable(), + finalBakeTimeInMinutes: z.number().nullable(), + replicateTo: z.string().nullable(), + }) + ), + nextToken: z.string().nullable(), +}) + +export const awsAppConfigListDeploymentStrategiesContract = defineRouteContract({ + method: 'POST', + path: '/api/tools/appconfig/list-deployment-strategies', + body: Schema, + response: { mode: 'json', schema: ResponseSchema }, +}) +export type AwsAppConfigListDeploymentStrategiesRequest = ContractBodyInput< + typeof awsAppConfigListDeploymentStrategiesContract +> +export type AwsAppConfigListDeploymentStrategiesBody = ContractBody< + typeof awsAppConfigListDeploymentStrategiesContract +> +export type AwsAppConfigListDeploymentStrategiesResponse = ContractJsonResponse< + typeof awsAppConfigListDeploymentStrategiesContract +> diff --git a/apps/sim/lib/api/contracts/tools/aws/appconfig-list-deployments.ts b/apps/sim/lib/api/contracts/tools/aws/appconfig-list-deployments.ts new file mode 100644 index 00000000000..2e17bc9689f --- /dev/null +++ b/apps/sim/lib/api/contracts/tools/aws/appconfig-list-deployments.ts @@ -0,0 +1,55 @@ +import { z } from 'zod' +import type { + ContractBody, + ContractBodyInput, + ContractJsonResponse, +} from '@/lib/api/contracts/types' +import { defineRouteContract } from '@/lib/api/contracts/types' +import { validateAwsRegion } from '@/lib/core/security/input-validation' + +const Schema = z.object({ + region: z + .string() + .min(1, 'AWS region is required') + .refine((v) => validateAwsRegion(v).isValid, { + message: 'Invalid AWS region format (e.g., us-east-1, eu-west-2)', + }), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + applicationId: z.string().min(1, 'Application ID is required'), + environmentId: z.string().min(1, 'Environment ID is required'), + maxResults: z.number().int().min(1).max(50).optional().nullable(), + nextToken: z.string().optional().nullable(), +}) + +const ResponseSchema = z.object({ + items: z.array( + z.object({ + deploymentNumber: z.number().nullable(), + configurationName: z.string().nullable(), + configurationVersion: z.string().nullable(), + state: z.string().nullable(), + percentageComplete: z.number().nullable(), + startedAt: z.string().nullable(), + completedAt: z.string().nullable(), + versionLabel: z.string().nullable(), + }) + ), + nextToken: z.string().nullable(), +}) + +export const awsAppConfigListDeploymentsContract = defineRouteContract({ + method: 'POST', + path: '/api/tools/appconfig/list-deployments', + body: Schema, + response: { mode: 'json', schema: ResponseSchema }, +}) +export type AwsAppConfigListDeploymentsRequest = ContractBodyInput< + typeof awsAppConfigListDeploymentsContract +> +export type AwsAppConfigListDeploymentsBody = ContractBody< + typeof awsAppConfigListDeploymentsContract +> +export type AwsAppConfigListDeploymentsResponse = ContractJsonResponse< + typeof awsAppConfigListDeploymentsContract +> diff --git a/apps/sim/lib/api/contracts/tools/aws/appconfig-list-environments.ts b/apps/sim/lib/api/contracts/tools/aws/appconfig-list-environments.ts new file mode 100644 index 00000000000..f5777b4a7ca --- /dev/null +++ b/apps/sim/lib/api/contracts/tools/aws/appconfig-list-environments.ts @@ -0,0 +1,51 @@ +import { z } from 'zod' +import type { + ContractBody, + ContractBodyInput, + ContractJsonResponse, +} from '@/lib/api/contracts/types' +import { defineRouteContract } from '@/lib/api/contracts/types' +import { validateAwsRegion } from '@/lib/core/security/input-validation' + +const Schema = z.object({ + region: z + .string() + .min(1, 'AWS region is required') + .refine((v) => validateAwsRegion(v).isValid, { + message: 'Invalid AWS region format (e.g., us-east-1, eu-west-2)', + }), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + applicationId: z.string().min(1, 'Application ID is required'), + maxResults: z.number().int().min(1).max(50).optional().nullable(), + nextToken: z.string().optional().nullable(), +}) + +const ResponseSchema = z.object({ + items: z.array( + z.object({ + applicationId: z.string().nullable(), + id: z.string().nullable(), + name: z.string().nullable(), + state: z.string().nullable(), + description: z.string().nullable(), + }) + ), + nextToken: z.string().nullable(), +}) + +export const awsAppConfigListEnvironmentsContract = defineRouteContract({ + method: 'POST', + path: '/api/tools/appconfig/list-environments', + body: Schema, + response: { mode: 'json', schema: ResponseSchema }, +}) +export type AwsAppConfigListEnvironmentsRequest = ContractBodyInput< + typeof awsAppConfigListEnvironmentsContract +> +export type AwsAppConfigListEnvironmentsBody = ContractBody< + typeof awsAppConfigListEnvironmentsContract +> +export type AwsAppConfigListEnvironmentsResponse = ContractJsonResponse< + typeof awsAppConfigListEnvironmentsContract +> diff --git a/apps/sim/lib/api/contracts/tools/aws/appconfig-list-hosted-configuration-versions.ts b/apps/sim/lib/api/contracts/tools/aws/appconfig-list-hosted-configuration-versions.ts new file mode 100644 index 00000000000..b6dbce49db0 --- /dev/null +++ b/apps/sim/lib/api/contracts/tools/aws/appconfig-list-hosted-configuration-versions.ts @@ -0,0 +1,53 @@ +import { z } from 'zod' +import type { + ContractBody, + ContractBodyInput, + ContractJsonResponse, +} from '@/lib/api/contracts/types' +import { defineRouteContract } from '@/lib/api/contracts/types' +import { validateAwsRegion } from '@/lib/core/security/input-validation' + +const Schema = z.object({ + region: z + .string() + .min(1, 'AWS region is required') + .refine((v) => validateAwsRegion(v).isValid, { + message: 'Invalid AWS region format (e.g., us-east-1, eu-west-2)', + }), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + applicationId: z.string().min(1, 'Application ID is required'), + configurationProfileId: z.string().min(1, 'Configuration profile ID is required'), + maxResults: z.number().int().min(1).max(50).optional().nullable(), + nextToken: z.string().optional().nullable(), +}) + +const ResponseSchema = z.object({ + items: z.array( + z.object({ + applicationId: z.string().nullable(), + configurationProfileId: z.string().nullable(), + versionNumber: z.number().nullable(), + description: z.string().nullable(), + contentType: z.string().nullable(), + versionLabel: z.string().nullable(), + }) + ), + nextToken: z.string().nullable(), +}) + +export const awsAppConfigListHostedConfigurationVersionsContract = defineRouteContract({ + method: 'POST', + path: '/api/tools/appconfig/list-hosted-configuration-versions', + body: Schema, + response: { mode: 'json', schema: ResponseSchema }, +}) +export type AwsAppConfigListHostedConfigurationVersionsRequest = ContractBodyInput< + typeof awsAppConfigListHostedConfigurationVersionsContract +> +export type AwsAppConfigListHostedConfigurationVersionsBody = ContractBody< + typeof awsAppConfigListHostedConfigurationVersionsContract +> +export type AwsAppConfigListHostedConfigurationVersionsResponse = ContractJsonResponse< + typeof awsAppConfigListHostedConfigurationVersionsContract +> diff --git a/apps/sim/lib/api/contracts/tools/aws/appconfig-start-deployment.ts b/apps/sim/lib/api/contracts/tools/aws/appconfig-start-deployment.ts new file mode 100644 index 00000000000..98a1c37d3e5 --- /dev/null +++ b/apps/sim/lib/api/contracts/tools/aws/appconfig-start-deployment.ts @@ -0,0 +1,56 @@ +import { z } from 'zod' +import type { + ContractBody, + ContractBodyInput, + ContractJsonResponse, +} from '@/lib/api/contracts/types' +import { defineRouteContract } from '@/lib/api/contracts/types' +import { validateAwsRegion } from '@/lib/core/security/input-validation' + +const Schema = z.object({ + region: z + .string() + .min(1, 'AWS region is required') + .refine((v) => validateAwsRegion(v).isValid, { + message: 'Invalid AWS region format (e.g., us-east-1, eu-west-2)', + }), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + applicationId: z.string().min(1, 'Application ID is required'), + environmentId: z.string().min(1, 'Environment ID is required'), + deploymentStrategyId: z.string().min(1, 'Deployment strategy ID is required'), + configurationProfileId: z.string().min(1, 'Configuration profile ID is required'), + configurationVersion: z.string().min(1, 'Configuration version is required'), + description: z.string().optional().nullable(), +}) + +const ResponseSchema = z.object({ + message: z.string(), + applicationId: z.string().nullable(), + environmentId: z.string().nullable(), + deploymentNumber: z.number().nullable(), + deploymentStrategyId: z.string().nullable(), + configurationProfileId: z.string().nullable(), + configurationVersion: z.string().nullable(), + description: z.string().nullable(), + state: z.string().nullable(), + percentageComplete: z.number().nullable(), + startedAt: z.string().nullable(), + completedAt: z.string().nullable(), +}) + +export const awsAppConfigStartDeploymentContract = defineRouteContract({ + method: 'POST', + path: '/api/tools/appconfig/start-deployment', + body: Schema, + response: { mode: 'json', schema: ResponseSchema }, +}) +export type AwsAppConfigStartDeploymentRequest = ContractBodyInput< + typeof awsAppConfigStartDeploymentContract +> +export type AwsAppConfigStartDeploymentBody = ContractBody< + typeof awsAppConfigStartDeploymentContract +> +export type AwsAppConfigStartDeploymentResponse = ContractJsonResponse< + typeof awsAppConfigStartDeploymentContract +> diff --git a/apps/sim/lib/api/contracts/tools/aws/appconfig-stop-deployment.ts b/apps/sim/lib/api/contracts/tools/aws/appconfig-stop-deployment.ts new file mode 100644 index 00000000000..59b854ec2fc --- /dev/null +++ b/apps/sim/lib/api/contracts/tools/aws/appconfig-stop-deployment.ts @@ -0,0 +1,51 @@ +import { z } from 'zod' +import type { + ContractBody, + ContractBodyInput, + ContractJsonResponse, +} from '@/lib/api/contracts/types' +import { defineRouteContract } from '@/lib/api/contracts/types' +import { validateAwsRegion } from '@/lib/core/security/input-validation' + +const Schema = z.object({ + region: z + .string() + .min(1, 'AWS region is required') + .refine((v) => validateAwsRegion(v).isValid, { + message: 'Invalid AWS region format (e.g., us-east-1, eu-west-2)', + }), + accessKeyId: z.string().min(1, 'AWS access key ID is required'), + secretAccessKey: z.string().min(1, 'AWS secret access key is required'), + applicationId: z.string().min(1, 'Application ID is required'), + environmentId: z.string().min(1, 'Environment ID is required'), + deploymentNumber: z.number().int().min(1, 'Deployment number is required'), +}) + +const ResponseSchema = z.object({ + message: z.string(), + applicationId: z.string().nullable(), + environmentId: z.string().nullable(), + deploymentNumber: z.number().nullable(), + deploymentStrategyId: z.string().nullable(), + configurationProfileId: z.string().nullable(), + configurationVersion: z.string().nullable(), + description: z.string().nullable(), + state: z.string().nullable(), + percentageComplete: z.number().nullable(), + startedAt: z.string().nullable(), + completedAt: z.string().nullable(), +}) + +export const awsAppConfigStopDeploymentContract = defineRouteContract({ + method: 'POST', + path: '/api/tools/appconfig/stop-deployment', + body: Schema, + response: { mode: 'json', schema: ResponseSchema }, +}) +export type AwsAppConfigStopDeploymentRequest = ContractBodyInput< + typeof awsAppConfigStopDeploymentContract +> +export type AwsAppConfigStopDeploymentBody = ContractBody +export type AwsAppConfigStopDeploymentResponse = ContractJsonResponse< + typeof awsAppConfigStopDeploymentContract +> diff --git a/apps/sim/lib/integrations/icon-mapping.ts b/apps/sim/lib/integrations/icon-mapping.ts index c4163e094b9..a0a17f222c3 100644 --- a/apps/sim/lib/integrations/icon-mapping.ts +++ b/apps/sim/lib/integrations/icon-mapping.ts @@ -14,6 +14,7 @@ import { AmplitudeIcon, ApifyIcon, ApolloIcon, + AppConfigIcon, ArxivIcon, AsanaIcon, AshbyIcon, @@ -220,6 +221,7 @@ export const blockTypeToIconMap: Record = { amplitude: AmplitudeIcon, apify: ApifyIcon, apollo: ApolloIcon, + appconfig: AppConfigIcon, arxiv: ArxivIcon, asana: AsanaIcon, ashby: AshbyIcon, diff --git a/apps/sim/lib/integrations/integrations.json b/apps/sim/lib/integrations/integrations.json index 313c8091f15..3c3b5fe46c7 100644 --- a/apps/sim/lib/integrations/integrations.json +++ b/apps/sim/lib/integrations/integrations.json @@ -874,6 +874,69 @@ "integrationType": "sales", "tags": ["enrichment", "sales-engagement"] }, + { + "type": "appconfig", + "slug": "aws-appconfig", + "name": "AWS AppConfig", + "description": "Manage configuration versions and deployments in AWS AppConfig", + "longDescription": "Integrate AWS AppConfig into workflows. Create hosted configuration versions, start, stop, and inspect deployments, and discover applications, environments, configuration profiles, and deployment strategies.", + "bgColor": "#E7157B", + "iconName": "AppConfigIcon", + "docsUrl": "https://docs.sim.ai/tools/appconfig", + "operations": [ + { + "name": "Create Configuration Version", + "description": "Create a new immutable hosted configuration version for an AppConfig profile" + }, + { + "name": "Get Configuration Version", + "description": "Retrieve the content of a specific hosted configuration version" + }, + { + "name": "List Configuration Versions", + "description": "List hosted configuration versions for an AppConfig configuration profile" + }, + { + "name": "Start Deployment", + "description": "Deploy a configuration version to an AppConfig environment" + }, + { + "name": "Get Deployment", + "description": "Get the status and details of an AppConfig deployment" + }, + { + "name": "Stop Deployment", + "description": "Stop a running AppConfig deployment" + }, + { + "name": "List Deployments", + "description": "List deployments for an AppConfig environment" + }, + { + "name": "List Applications", + "description": "List AppConfig applications in the account" + }, + { + "name": "List Environments", + "description": "List environments for an AppConfig application" + }, + { + "name": "List Configuration Profiles", + "description": "List configuration profiles for an AppConfig application" + }, + { + "name": "List Deployment Strategies", + "description": "List AppConfig deployment strategies in the account" + } + ], + "operationCount": 11, + "triggers": [], + "triggerCount": 0, + "authType": "none", + "category": "tools", + "integrationType": "devops", + "tags": ["cloud", "feature-flags"] + }, { "type": "arxiv", "slug": "arxiv", diff --git a/apps/sim/package.json b/apps/sim/package.json index 259f9dca72a..87153a03cbb 100644 --- a/apps/sim/package.json +++ b/apps/sim/package.json @@ -34,6 +34,7 @@ "@1password/sdk": "0.3.1", "@a2a-js/sdk": "0.3.7", "@anthropic-ai/sdk": "0.71.2", + "@aws-sdk/client-appconfig": "3.1032.0", "@aws-sdk/client-appconfigdata": "3.1032.0", "@aws-sdk/client-athena": "3.1032.0", "@aws-sdk/client-bedrock-runtime": "3.1032.0", diff --git a/apps/sim/tools/appconfig/create-hosted-configuration-version.ts b/apps/sim/tools/appconfig/create-hosted-configuration-version.ts new file mode 100644 index 00000000000..e3e9fe9d473 --- /dev/null +++ b/apps/sim/tools/appconfig/create-hosted-configuration-version.ts @@ -0,0 +1,124 @@ +import type { + AppConfigCreateVersionParams, + AppConfigCreateVersionResponse, +} from '@/tools/appconfig/types' +import type { ToolConfig } from '@/tools/types' + +export const createHostedConfigurationVersionTool: ToolConfig< + AppConfigCreateVersionParams, + AppConfigCreateVersionResponse +> = { + id: 'appconfig_create_hosted_configuration_version', + name: 'AppConfig Create Hosted Configuration Version', + description: 'Create a new immutable hosted configuration version for an AppConfig profile', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + applicationId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The AppConfig application ID', + }, + configurationProfileId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The configuration profile ID', + }, + content: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The configuration content (e.g., JSON or YAML text)', + }, + contentType: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'A standard MIME type for the content (e.g., application/json)', + }, + description: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'A description of the configuration version', + }, + versionLabel: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'An optional user-defined label (e.g., "v2.2.0")', + }, + latestVersionNumber: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Optional locking token — the latest version number to prevent overwrites', + }, + }, + + request: { + url: '/api/tools/appconfig/create-hosted-configuration-version', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + applicationId: params.applicationId, + configurationProfileId: params.configurationProfileId, + content: params.content, + contentType: params.contentType, + ...(params.description !== undefined && { description: params.description }), + ...(params.versionLabel !== undefined && { versionLabel: params.versionLabel }), + ...(params.latestVersionNumber !== undefined && { + latestVersionNumber: params.latestVersionNumber, + }), + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + throw new Error(data.error || 'AppConfig create hosted configuration version failed') + } + return { success: true, output: data } + }, + + outputs: { + message: { type: 'string', description: 'Operation status message' }, + applicationId: { type: 'string', description: 'The application ID', optional: true }, + configurationProfileId: { + type: 'string', + description: 'The configuration profile ID', + optional: true, + }, + versionNumber: { + type: 'number', + description: 'The created version number', + optional: true, + }, + contentType: { type: 'string', description: 'The content MIME type', optional: true }, + description: { type: 'string', description: 'The version description', optional: true }, + versionLabel: { type: 'string', description: 'The version label', optional: true }, + }, +} diff --git a/apps/sim/tools/appconfig/get-deployment.ts b/apps/sim/tools/appconfig/get-deployment.ts new file mode 100644 index 00000000000..a1f3e9f3a86 --- /dev/null +++ b/apps/sim/tools/appconfig/get-deployment.ts @@ -0,0 +1,115 @@ +import type { + AppConfigDeploymentRefParams, + AppConfigDeploymentResponse, +} from '@/tools/appconfig/types' +import type { ToolConfig } from '@/tools/types' + +export const getDeploymentTool: ToolConfig< + AppConfigDeploymentRefParams, + AppConfigDeploymentResponse +> = { + id: 'appconfig_get_deployment', + name: 'AppConfig Get Deployment', + description: 'Get the status and details of an AppConfig deployment', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + applicationId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The AppConfig application ID', + }, + environmentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The environment ID', + }, + deploymentNumber: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'The deployment sequence number', + }, + }, + + request: { + url: '/api/tools/appconfig/get-deployment', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + applicationId: params.applicationId, + environmentId: params.environmentId, + deploymentNumber: params.deploymentNumber, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + throw new Error(data.error || 'AppConfig get deployment failed') + } + return { success: true, output: data } + }, + + outputs: { + message: { type: 'string', description: 'Operation status message' }, + applicationId: { type: 'string', description: 'The application ID', optional: true }, + environmentId: { type: 'string', description: 'The environment ID', optional: true }, + deploymentNumber: { + type: 'number', + description: 'The deployment sequence number', + optional: true, + }, + deploymentStrategyId: { + type: 'string', + description: 'The deployment strategy ID', + optional: true, + }, + configurationProfileId: { + type: 'string', + description: 'The configuration profile ID', + optional: true, + }, + configurationVersion: { + type: 'string', + description: 'The deployed configuration version', + optional: true, + }, + description: { type: 'string', description: 'The deployment description', optional: true }, + state: { type: 'string', description: 'The deployment state', optional: true }, + percentageComplete: { + type: 'number', + description: 'Percentage of targets deployed', + optional: true, + }, + startedAt: { type: 'string', description: 'When the deployment started (ISO)', optional: true }, + completedAt: { + type: 'string', + description: 'When the deployment completed (ISO)', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/appconfig/get-hosted-configuration-version.ts b/apps/sim/tools/appconfig/get-hosted-configuration-version.ts new file mode 100644 index 00000000000..b518c61a4cd --- /dev/null +++ b/apps/sim/tools/appconfig/get-hosted-configuration-version.ts @@ -0,0 +1,90 @@ +import type { + AppConfigGetVersionParams, + AppConfigGetVersionResponse, +} from '@/tools/appconfig/types' +import type { ToolConfig } from '@/tools/types' + +export const getHostedConfigurationVersionTool: ToolConfig< + AppConfigGetVersionParams, + AppConfigGetVersionResponse +> = { + id: 'appconfig_get_hosted_configuration_version', + name: 'AppConfig Get Hosted Configuration Version', + description: 'Retrieve the content of a specific hosted configuration version', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + applicationId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The AppConfig application ID', + }, + configurationProfileId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The configuration profile ID', + }, + versionNumber: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'The version number to retrieve', + }, + }, + + request: { + url: '/api/tools/appconfig/get-hosted-configuration-version', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + applicationId: params.applicationId, + configurationProfileId: params.configurationProfileId, + versionNumber: params.versionNumber, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + throw new Error(data.error || 'AppConfig get hosted configuration version failed') + } + return { success: true, output: data } + }, + + outputs: { + applicationId: { type: 'string', description: 'The application ID', optional: true }, + configurationProfileId: { + type: 'string', + description: 'The configuration profile ID', + optional: true, + }, + versionNumber: { type: 'number', description: 'The version number', optional: true }, + content: { type: 'string', description: 'The configuration content', optional: true }, + contentType: { type: 'string', description: 'The content MIME type', optional: true }, + description: { type: 'string', description: 'The version description', optional: true }, + versionLabel: { type: 'string', description: 'The version label', optional: true }, + }, +} diff --git a/apps/sim/tools/appconfig/index.ts b/apps/sim/tools/appconfig/index.ts new file mode 100644 index 00000000000..acad3d7ce68 --- /dev/null +++ b/apps/sim/tools/appconfig/index.ts @@ -0,0 +1,25 @@ +import { createHostedConfigurationVersionTool } from '@/tools/appconfig/create-hosted-configuration-version' +import { getDeploymentTool } from '@/tools/appconfig/get-deployment' +import { getHostedConfigurationVersionTool } from '@/tools/appconfig/get-hosted-configuration-version' +import { listApplicationsTool } from '@/tools/appconfig/list-applications' +import { listConfigurationProfilesTool } from '@/tools/appconfig/list-configuration-profiles' +import { listDeploymentStrategiesTool } from '@/tools/appconfig/list-deployment-strategies' +import { listDeploymentsTool } from '@/tools/appconfig/list-deployments' +import { listEnvironmentsTool } from '@/tools/appconfig/list-environments' +import { listHostedConfigurationVersionsTool } from '@/tools/appconfig/list-hosted-configuration-versions' +import { startDeploymentTool } from '@/tools/appconfig/start-deployment' +import { stopDeploymentTool } from '@/tools/appconfig/stop-deployment' + +export const appconfigCreateHostedConfigurationVersionTool = createHostedConfigurationVersionTool +export const appconfigGetHostedConfigurationVersionTool = getHostedConfigurationVersionTool +export const appconfigListHostedConfigurationVersionsTool = listHostedConfigurationVersionsTool +export const appconfigStartDeploymentTool = startDeploymentTool +export const appconfigGetDeploymentTool = getDeploymentTool +export const appconfigStopDeploymentTool = stopDeploymentTool +export const appconfigListDeploymentsTool = listDeploymentsTool +export const appconfigListApplicationsTool = listApplicationsTool +export const appconfigListEnvironmentsTool = listEnvironmentsTool +export const appconfigListConfigurationProfilesTool = listConfigurationProfilesTool +export const appconfigListDeploymentStrategiesTool = listDeploymentStrategiesTool + +export * from './types' diff --git a/apps/sim/tools/appconfig/list-applications.ts b/apps/sim/tools/appconfig/list-applications.ts new file mode 100644 index 00000000000..ecc04b491de --- /dev/null +++ b/apps/sim/tools/appconfig/list-applications.ts @@ -0,0 +1,93 @@ +import type { + AppConfigListApplicationsResponse, + AppConfigListPaginatedParams, +} from '@/tools/appconfig/types' +import type { ToolConfig } from '@/tools/types' + +export const listApplicationsTool: ToolConfig< + AppConfigListPaginatedParams, + AppConfigListApplicationsResponse +> = { + id: 'appconfig_list_applications', + name: 'AppConfig List Applications', + description: 'List AppConfig applications in the account', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + maxResults: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of applications to return (1-50)', + }, + nextToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination token from a previous response', + }, + }, + + request: { + url: '/api/tools/appconfig/list-applications', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + ...(params.maxResults !== undefined && { maxResults: params.maxResults }), + ...(params.nextToken !== undefined && { nextToken: params.nextToken }), + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + throw new Error(data.error || 'AppConfig list applications failed') + } + return { success: true, output: data } + }, + + outputs: { + items: { + type: 'array', + description: 'List of application summaries', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'The application ID', optional: true }, + name: { type: 'string', description: 'The application name', optional: true }, + description: { + type: 'string', + description: 'The application description', + optional: true, + }, + }, + }, + }, + nextToken: { + type: 'string', + description: 'Pagination token for the next page', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/appconfig/list-configuration-profiles.ts b/apps/sim/tools/appconfig/list-configuration-profiles.ts new file mode 100644 index 00000000000..807cbe6153f --- /dev/null +++ b/apps/sim/tools/appconfig/list-configuration-profiles.ts @@ -0,0 +1,106 @@ +import type { + AppConfigListProfilesParams, + AppConfigListProfilesResponse, +} from '@/tools/appconfig/types' +import type { ToolConfig } from '@/tools/types' + +export const listConfigurationProfilesTool: ToolConfig< + AppConfigListProfilesParams, + AppConfigListProfilesResponse +> = { + id: 'appconfig_list_configuration_profiles', + name: 'AppConfig List Configuration Profiles', + description: 'List configuration profiles for an AppConfig application', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + applicationId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The AppConfig application ID', + }, + maxResults: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of profiles to return (1-50)', + }, + nextToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination token from a previous response', + }, + }, + + request: { + url: '/api/tools/appconfig/list-configuration-profiles', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + applicationId: params.applicationId, + ...(params.maxResults !== undefined && { maxResults: params.maxResults }), + ...(params.nextToken !== undefined && { nextToken: params.nextToken }), + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + throw new Error(data.error || 'AppConfig list configuration profiles failed') + } + return { success: true, output: data } + }, + + outputs: { + items: { + type: 'array', + description: 'List of configuration profile summaries', + items: { + type: 'object', + properties: { + applicationId: { type: 'string', description: 'The application ID', optional: true }, + id: { type: 'string', description: 'The configuration profile ID', optional: true }, + name: { type: 'string', description: 'The configuration profile name', optional: true }, + locationUri: { + type: 'string', + description: 'The configuration source location URI', + optional: true, + }, + type: { + type: 'string', + description: 'The configuration profile type', + optional: true, + }, + }, + }, + }, + nextToken: { + type: 'string', + description: 'Pagination token for the next page', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/appconfig/list-deployment-strategies.ts b/apps/sim/tools/appconfig/list-deployment-strategies.ts new file mode 100644 index 00000000000..9cab81b7c68 --- /dev/null +++ b/apps/sim/tools/appconfig/list-deployment-strategies.ts @@ -0,0 +1,118 @@ +import type { + AppConfigListPaginatedParams, + AppConfigListStrategiesResponse, +} from '@/tools/appconfig/types' +import type { ToolConfig } from '@/tools/types' + +export const listDeploymentStrategiesTool: ToolConfig< + AppConfigListPaginatedParams, + AppConfigListStrategiesResponse +> = { + id: 'appconfig_list_deployment_strategies', + name: 'AppConfig List Deployment Strategies', + description: 'List AppConfig deployment strategies in the account', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + maxResults: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of strategies to return (1-50)', + }, + nextToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination token from a previous response', + }, + }, + + request: { + url: '/api/tools/appconfig/list-deployment-strategies', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + ...(params.maxResults !== undefined && { maxResults: params.maxResults }), + ...(params.nextToken !== undefined && { nextToken: params.nextToken }), + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + throw new Error(data.error || 'AppConfig list deployment strategies failed') + } + return { success: true, output: data } + }, + + outputs: { + items: { + type: 'array', + description: 'List of deployment strategy summaries', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'The deployment strategy ID', optional: true }, + name: { type: 'string', description: 'The deployment strategy name', optional: true }, + description: { + type: 'string', + description: 'The deployment strategy description', + optional: true, + }, + deploymentDurationInMinutes: { + type: 'number', + description: 'Total deployment duration in minutes', + optional: true, + }, + growthType: { + type: 'string', + description: 'How percentage grows over time (LINEAR or EXPONENTIAL)', + optional: true, + }, + growthFactor: { + type: 'number', + description: 'Percentage of targets per interval', + optional: true, + }, + finalBakeTimeInMinutes: { + type: 'number', + description: 'Bake time in minutes before completion', + optional: true, + }, + replicateTo: { + type: 'string', + description: 'Where the strategy is replicated (NONE or SSM_DOCUMENT)', + optional: true, + }, + }, + }, + }, + nextToken: { + type: 'string', + description: 'Pagination token for the next page', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/appconfig/list-deployments.ts b/apps/sim/tools/appconfig/list-deployments.ts new file mode 100644 index 00000000000..cbaff741b54 --- /dev/null +++ b/apps/sim/tools/appconfig/list-deployments.ts @@ -0,0 +1,132 @@ +import type { + AppConfigListDeploymentsParams, + AppConfigListDeploymentsResponse, +} from '@/tools/appconfig/types' +import type { ToolConfig } from '@/tools/types' + +export const listDeploymentsTool: ToolConfig< + AppConfigListDeploymentsParams, + AppConfigListDeploymentsResponse +> = { + id: 'appconfig_list_deployments', + name: 'AppConfig List Deployments', + description: 'List deployments for an AppConfig environment', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + applicationId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The AppConfig application ID', + }, + environmentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The environment ID', + }, + maxResults: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of deployments to return (1-50)', + }, + nextToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination token from a previous response', + }, + }, + + request: { + url: '/api/tools/appconfig/list-deployments', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + applicationId: params.applicationId, + environmentId: params.environmentId, + ...(params.maxResults !== undefined && { maxResults: params.maxResults }), + ...(params.nextToken !== undefined && { nextToken: params.nextToken }), + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + throw new Error(data.error || 'AppConfig list deployments failed') + } + return { success: true, output: data } + }, + + outputs: { + items: { + type: 'array', + description: 'List of deployment summaries', + items: { + type: 'object', + properties: { + deploymentNumber: { + type: 'number', + description: 'The deployment sequence number', + optional: true, + }, + configurationName: { + type: 'string', + description: 'The configuration name', + optional: true, + }, + configurationVersion: { + type: 'string', + description: 'The deployed configuration version', + optional: true, + }, + state: { type: 'string', description: 'The deployment state', optional: true }, + percentageComplete: { + type: 'number', + description: 'Percentage of targets deployed', + optional: true, + }, + startedAt: { + type: 'string', + description: 'When the deployment started (ISO)', + optional: true, + }, + completedAt: { + type: 'string', + description: 'When the deployment completed (ISO)', + optional: true, + }, + versionLabel: { type: 'string', description: 'The version label', optional: true }, + }, + }, + }, + nextToken: { + type: 'string', + description: 'Pagination token for the next page', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/appconfig/list-environments.ts b/apps/sim/tools/appconfig/list-environments.ts new file mode 100644 index 00000000000..3d5055b691e --- /dev/null +++ b/apps/sim/tools/appconfig/list-environments.ts @@ -0,0 +1,102 @@ +import type { + AppConfigListEnvironmentsParams, + AppConfigListEnvironmentsResponse, +} from '@/tools/appconfig/types' +import type { ToolConfig } from '@/tools/types' + +export const listEnvironmentsTool: ToolConfig< + AppConfigListEnvironmentsParams, + AppConfigListEnvironmentsResponse +> = { + id: 'appconfig_list_environments', + name: 'AppConfig List Environments', + description: 'List environments for an AppConfig application', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + applicationId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The AppConfig application ID', + }, + maxResults: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of environments to return (1-50)', + }, + nextToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination token from a previous response', + }, + }, + + request: { + url: '/api/tools/appconfig/list-environments', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + applicationId: params.applicationId, + ...(params.maxResults !== undefined && { maxResults: params.maxResults }), + ...(params.nextToken !== undefined && { nextToken: params.nextToken }), + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + throw new Error(data.error || 'AppConfig list environments failed') + } + return { success: true, output: data } + }, + + outputs: { + items: { + type: 'array', + description: 'List of environment summaries', + items: { + type: 'object', + properties: { + applicationId: { type: 'string', description: 'The application ID', optional: true }, + id: { type: 'string', description: 'The environment ID', optional: true }, + name: { type: 'string', description: 'The environment name', optional: true }, + state: { type: 'string', description: 'The environment state', optional: true }, + description: { + type: 'string', + description: 'The environment description', + optional: true, + }, + }, + }, + }, + nextToken: { + type: 'string', + description: 'Pagination token for the next page', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/appconfig/list-hosted-configuration-versions.ts b/apps/sim/tools/appconfig/list-hosted-configuration-versions.ts new file mode 100644 index 00000000000..d62b68019ad --- /dev/null +++ b/apps/sim/tools/appconfig/list-hosted-configuration-versions.ts @@ -0,0 +1,110 @@ +import type { + AppConfigListVersionsParams, + AppConfigListVersionsResponse, +} from '@/tools/appconfig/types' +import type { ToolConfig } from '@/tools/types' + +export const listHostedConfigurationVersionsTool: ToolConfig< + AppConfigListVersionsParams, + AppConfigListVersionsResponse +> = { + id: 'appconfig_list_hosted_configuration_versions', + name: 'AppConfig List Hosted Configuration Versions', + description: 'List hosted configuration versions for an AppConfig configuration profile', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + applicationId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The AppConfig application ID', + }, + configurationProfileId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The configuration profile ID', + }, + maxResults: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of versions to return (1-50)', + }, + nextToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination token from a previous response', + }, + }, + + request: { + url: '/api/tools/appconfig/list-hosted-configuration-versions', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + applicationId: params.applicationId, + configurationProfileId: params.configurationProfileId, + ...(params.maxResults !== undefined && { maxResults: params.maxResults }), + ...(params.nextToken !== undefined && { nextToken: params.nextToken }), + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + throw new Error(data.error || 'AppConfig list hosted configuration versions failed') + } + return { success: true, output: data } + }, + + outputs: { + items: { + type: 'array', + description: 'List of hosted configuration version summaries', + items: { + type: 'object', + properties: { + applicationId: { type: 'string', description: 'The application ID', optional: true }, + configurationProfileId: { + type: 'string', + description: 'The configuration profile ID', + optional: true, + }, + versionNumber: { type: 'number', description: 'The version number', optional: true }, + description: { type: 'string', description: 'The version description', optional: true }, + contentType: { type: 'string', description: 'The content MIME type', optional: true }, + versionLabel: { type: 'string', description: 'The version label', optional: true }, + }, + }, + }, + nextToken: { + type: 'string', + description: 'Pagination token for the next page', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/appconfig/start-deployment.ts b/apps/sim/tools/appconfig/start-deployment.ts new file mode 100644 index 00000000000..596a231367c --- /dev/null +++ b/apps/sim/tools/appconfig/start-deployment.ts @@ -0,0 +1,136 @@ +import type { + AppConfigDeploymentResponse, + AppConfigStartDeploymentParams, +} from '@/tools/appconfig/types' +import type { ToolConfig } from '@/tools/types' + +export const startDeploymentTool: ToolConfig< + AppConfigStartDeploymentParams, + AppConfigDeploymentResponse +> = { + id: 'appconfig_start_deployment', + name: 'AppConfig Start Deployment', + description: 'Deploy a configuration version to an AppConfig environment', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + applicationId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The AppConfig application ID', + }, + environmentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The environment ID to deploy to', + }, + deploymentStrategyId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The deployment strategy ID', + }, + configurationProfileId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The configuration profile ID', + }, + configurationVersion: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The configuration version number or label to deploy', + }, + description: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'A description of the deployment', + }, + }, + + request: { + url: '/api/tools/appconfig/start-deployment', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + applicationId: params.applicationId, + environmentId: params.environmentId, + deploymentStrategyId: params.deploymentStrategyId, + configurationProfileId: params.configurationProfileId, + configurationVersion: params.configurationVersion, + ...(params.description !== undefined && { description: params.description }), + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + throw new Error(data.error || 'AppConfig start deployment failed') + } + return { success: true, output: data } + }, + + outputs: { + message: { type: 'string', description: 'Operation status message' }, + applicationId: { type: 'string', description: 'The application ID', optional: true }, + environmentId: { type: 'string', description: 'The environment ID', optional: true }, + deploymentNumber: { + type: 'number', + description: 'The deployment sequence number', + optional: true, + }, + deploymentStrategyId: { + type: 'string', + description: 'The deployment strategy ID', + optional: true, + }, + configurationProfileId: { + type: 'string', + description: 'The configuration profile ID', + optional: true, + }, + configurationVersion: { + type: 'string', + description: 'The deployed configuration version', + optional: true, + }, + description: { type: 'string', description: 'The deployment description', optional: true }, + state: { type: 'string', description: 'The deployment state', optional: true }, + percentageComplete: { + type: 'number', + description: 'Percentage of targets deployed', + optional: true, + }, + startedAt: { type: 'string', description: 'When the deployment started (ISO)', optional: true }, + completedAt: { + type: 'string', + description: 'When the deployment completed (ISO)', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/appconfig/stop-deployment.ts b/apps/sim/tools/appconfig/stop-deployment.ts new file mode 100644 index 00000000000..c23d7c316d1 --- /dev/null +++ b/apps/sim/tools/appconfig/stop-deployment.ts @@ -0,0 +1,115 @@ +import type { + AppConfigDeploymentRefParams, + AppConfigDeploymentResponse, +} from '@/tools/appconfig/types' +import type { ToolConfig } from '@/tools/types' + +export const stopDeploymentTool: ToolConfig< + AppConfigDeploymentRefParams, + AppConfigDeploymentResponse +> = { + id: 'appconfig_stop_deployment', + name: 'AppConfig Stop Deployment', + description: 'Stop a running AppConfig deployment', + version: '1.0.0', + + params: { + region: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS region (e.g., us-east-1)', + }, + accessKeyId: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS access key ID', + }, + secretAccessKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'AWS secret access key', + }, + applicationId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The AppConfig application ID', + }, + environmentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The environment ID', + }, + deploymentNumber: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'The deployment sequence number', + }, + }, + + request: { + url: '/api/tools/appconfig/stop-deployment', + method: 'POST', + headers: () => ({ 'Content-Type': 'application/json' }), + body: (params) => ({ + region: params.region, + accessKeyId: params.accessKeyId, + secretAccessKey: params.secretAccessKey, + applicationId: params.applicationId, + environmentId: params.environmentId, + deploymentNumber: params.deploymentNumber, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + if (!response.ok) { + throw new Error(data.error || 'AppConfig stop deployment failed') + } + return { success: true, output: data } + }, + + outputs: { + message: { type: 'string', description: 'Operation status message' }, + applicationId: { type: 'string', description: 'The application ID', optional: true }, + environmentId: { type: 'string', description: 'The environment ID', optional: true }, + deploymentNumber: { + type: 'number', + description: 'The deployment sequence number', + optional: true, + }, + deploymentStrategyId: { + type: 'string', + description: 'The deployment strategy ID', + optional: true, + }, + configurationProfileId: { + type: 'string', + description: 'The configuration profile ID', + optional: true, + }, + configurationVersion: { + type: 'string', + description: 'The deployed configuration version', + optional: true, + }, + description: { type: 'string', description: 'The deployment description', optional: true }, + state: { type: 'string', description: 'The deployment state', optional: true }, + percentageComplete: { + type: 'number', + description: 'Percentage of targets deployed', + optional: true, + }, + startedAt: { type: 'string', description: 'When the deployment started (ISO)', optional: true }, + completedAt: { + type: 'string', + description: 'When the deployment completed (ISO)', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/appconfig/types.ts b/apps/sim/tools/appconfig/types.ts new file mode 100644 index 00000000000..b06ac613d01 --- /dev/null +++ b/apps/sim/tools/appconfig/types.ts @@ -0,0 +1,216 @@ +import type { ToolResponse } from '@/tools/types' + +export interface AppConfigConnectionConfig { + region: string + accessKeyId: string + secretAccessKey: string +} + +export interface AppConfigCreateVersionParams extends AppConfigConnectionConfig { + applicationId: string + configurationProfileId: string + content: string + contentType: string + description?: string + versionLabel?: string + latestVersionNumber?: number +} + +export interface AppConfigGetVersionParams extends AppConfigConnectionConfig { + applicationId: string + configurationProfileId: string + versionNumber: number +} + +export interface AppConfigListVersionsParams extends AppConfigConnectionConfig { + applicationId: string + configurationProfileId: string + maxResults?: number + nextToken?: string +} + +export interface AppConfigStartDeploymentParams extends AppConfigConnectionConfig { + applicationId: string + environmentId: string + deploymentStrategyId: string + configurationProfileId: string + configurationVersion: string + description?: string +} + +export interface AppConfigDeploymentRefParams extends AppConfigConnectionConfig { + applicationId: string + environmentId: string + deploymentNumber: number +} + +export interface AppConfigListDeploymentsParams extends AppConfigConnectionConfig { + applicationId: string + environmentId: string + maxResults?: number + nextToken?: string +} + +export interface AppConfigListEnvironmentsParams extends AppConfigConnectionConfig { + applicationId: string + maxResults?: number + nextToken?: string +} + +export interface AppConfigListProfilesParams extends AppConfigConnectionConfig { + applicationId: string + maxResults?: number + nextToken?: string +} + +export interface AppConfigListPaginatedParams extends AppConfigConnectionConfig { + maxResults?: number + nextToken?: string +} + +export interface AppConfigVersionSummary { + applicationId: string | null + configurationProfileId: string | null + versionNumber: number | null + description: string | null + contentType: string | null + versionLabel: string | null +} + +export interface AppConfigDeploymentSummary { + deploymentNumber: number | null + configurationName: string | null + configurationVersion: string | null + state: string | null + percentageComplete: number | null + startedAt: string | null + completedAt: string | null + versionLabel: string | null +} + +export interface AppConfigApplicationSummary { + id: string | null + name: string | null + description: string | null +} + +export interface AppConfigEnvironmentSummary { + applicationId: string | null + id: string | null + name: string | null + state: string | null + description: string | null +} + +export interface AppConfigProfileSummary { + applicationId: string | null + id: string | null + name: string | null + locationUri: string | null + type: string | null +} + +export interface AppConfigStrategySummary { + id: string | null + name: string | null + description: string | null + deploymentDurationInMinutes: number | null + growthType: string | null + growthFactor: number | null + finalBakeTimeInMinutes: number | null + replicateTo: string | null +} + +export interface AppConfigCreateVersionResponse extends ToolResponse { + output: { + message: string + applicationId: string | null + configurationProfileId: string | null + versionNumber: number | null + contentType: string | null + description: string | null + versionLabel: string | null + } +} + +export interface AppConfigGetVersionResponse extends ToolResponse { + output: { + applicationId: string | null + configurationProfileId: string | null + versionNumber: number | null + content: string | null + contentType: string | null + description: string | null + versionLabel: string | null + } +} + +export interface AppConfigListVersionsResponse extends ToolResponse { + output: { + items: AppConfigVersionSummary[] + nextToken: string | null + } +} + +export interface AppConfigDeploymentResponse extends ToolResponse { + output: { + message: string + applicationId: string | null + environmentId: string | null + deploymentNumber: number | null + deploymentStrategyId: string | null + configurationProfileId: string | null + configurationVersion: string | null + description: string | null + state: string | null + percentageComplete: number | null + startedAt: string | null + completedAt: string | null + } +} + +export interface AppConfigListDeploymentsResponse extends ToolResponse { + output: { + items: AppConfigDeploymentSummary[] + nextToken: string | null + } +} + +export interface AppConfigListApplicationsResponse extends ToolResponse { + output: { + items: AppConfigApplicationSummary[] + nextToken: string | null + } +} + +export interface AppConfigListEnvironmentsResponse extends ToolResponse { + output: { + items: AppConfigEnvironmentSummary[] + nextToken: string | null + } +} + +export interface AppConfigListProfilesResponse extends ToolResponse { + output: { + items: AppConfigProfileSummary[] + nextToken: string | null + } +} + +export interface AppConfigListStrategiesResponse extends ToolResponse { + output: { + items: AppConfigStrategySummary[] + nextToken: string | null + } +} + +export type AppConfigResponse = + | AppConfigCreateVersionResponse + | AppConfigGetVersionResponse + | AppConfigListVersionsResponse + | AppConfigDeploymentResponse + | AppConfigListDeploymentsResponse + | AppConfigListApplicationsResponse + | AppConfigListEnvironmentsResponse + | AppConfigListProfilesResponse + | AppConfigListStrategiesResponse diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 6b1c404ef2b..1b6a675d4f4 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -155,6 +155,19 @@ import { apolloTaskCreateTool, apolloTaskSearchTool, } from '@/tools/apollo' +import { + appconfigCreateHostedConfigurationVersionTool, + appconfigGetDeploymentTool, + appconfigGetHostedConfigurationVersionTool, + appconfigListApplicationsTool, + appconfigListConfigurationProfilesTool, + appconfigListDeploymentStrategiesTool, + appconfigListDeploymentsTool, + appconfigListEnvironmentsTool, + appconfigListHostedConfigurationVersionsTool, + appconfigStartDeploymentTool, + appconfigStopDeploymentTool, +} from '@/tools/appconfig' import { arxivGetAuthorPapersTool, arxivGetPaperTool, arxivSearchTool } from '@/tools/arxiv' import { asanaAddCommentTool, @@ -3451,6 +3464,17 @@ export const tools: Record = { amplitude_realtime_active_users: amplitudeRealtimeActiveUsersTool, amplitude_list_events: amplitudeListEventsTool, amplitude_get_revenue: amplitudeGetRevenueTool, + appconfig_create_hosted_configuration_version: appconfigCreateHostedConfigurationVersionTool, + appconfig_get_hosted_configuration_version: appconfigGetHostedConfigurationVersionTool, + appconfig_list_hosted_configuration_versions: appconfigListHostedConfigurationVersionsTool, + appconfig_start_deployment: appconfigStartDeploymentTool, + appconfig_get_deployment: appconfigGetDeploymentTool, + appconfig_stop_deployment: appconfigStopDeploymentTool, + appconfig_list_deployments: appconfigListDeploymentsTool, + appconfig_list_applications: appconfigListApplicationsTool, + appconfig_list_environments: appconfigListEnvironmentsTool, + appconfig_list_configuration_profiles: appconfigListConfigurationProfilesTool, + appconfig_list_deployment_strategies: appconfigListDeploymentStrategiesTool, arxiv_get_author_papers: arxivGetAuthorPapersTool, arxiv_get_paper: arxivGetPaperTool, arxiv_search: arxivSearchTool, diff --git a/bun.lock b/bun.lock index d5496a63094..72170ab510b 100644 --- a/bun.lock +++ b/bun.lock @@ -88,6 +88,7 @@ "@1password/sdk": "0.3.1", "@a2a-js/sdk": "0.3.7", "@anthropic-ai/sdk": "0.71.2", + "@aws-sdk/client-appconfig": "3.1032.0", "@aws-sdk/client-appconfigdata": "3.1032.0", "@aws-sdk/client-athena": "3.1032.0", "@aws-sdk/client-bedrock-runtime": "3.1032.0", @@ -561,6 +562,8 @@ "@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-appconfig": ["@aws-sdk/client-appconfig@3.1032.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.1", "@aws-sdk/credential-provider-node": "^3.972.32", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", "@aws-sdk/middleware-user-agent": "^3.972.31", "@aws-sdk/region-config-resolver": "^3.972.12", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.7", "@aws-sdk/util-user-agent-browser": "^3.972.10", "@aws-sdk/util-user-agent-node": "^3.973.17", "@smithy/config-resolver": "^4.4.16", "@smithy/core": "^3.23.15", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/hash-node": "^4.2.14", "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.30", "@smithy/middleware-retry": "^4.5.3", "@smithy/middleware-serde": "^4.2.18", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/node-http-handler": "^4.5.3", "@smithy/protocol-http": "^5.3.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.47", "@smithy/util-defaults-mode-node": "^4.2.52", "@smithy/util-endpoints": "^3.4.1", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.2", "@smithy/util-stream": "^4.5.23", "@smithy/util-utf8": "^4.2.2", "@smithy/util-waiter": "^4.2.16", "tslib": "^2.6.2" } }, "sha512-WcS820syhSamz1PcZUvdUXf7FUm3cpze+hfMvDKzPojrh/zFO5eVopzhBGEkDFXiHFD0qel1ZgE5s5AkmH9fyg=="], + "@aws-sdk/client-appconfigdata": ["@aws-sdk/client-appconfigdata@3.1032.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.1", "@aws-sdk/credential-provider-node": "^3.972.32", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", "@aws-sdk/middleware-user-agent": "^3.972.31", "@aws-sdk/region-config-resolver": "^3.972.12", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.7", "@aws-sdk/util-user-agent-browser": "^3.972.10", "@aws-sdk/util-user-agent-node": "^3.973.17", "@smithy/config-resolver": "^4.4.16", "@smithy/core": "^3.23.15", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/hash-node": "^4.2.14", "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.30", "@smithy/middleware-retry": "^4.5.3", "@smithy/middleware-serde": "^4.2.18", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/node-http-handler": "^4.5.3", "@smithy/protocol-http": "^5.3.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.47", "@smithy/util-defaults-mode-node": "^4.2.52", "@smithy/util-endpoints": "^3.4.1", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.2", "@smithy/util-stream": "^4.5.23", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-gh/cmEFDN97XJBWRLT0usnWnTDEm+cqgOIffTJGP68xCgj28EkSHnN5vtdy2QaZjj7/n/sKOlqIKONZUeonRpA=="], "@aws-sdk/client-athena": ["@aws-sdk/client-athena@3.1032.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.1", "@aws-sdk/credential-provider-node": "^3.972.32", "@aws-sdk/middleware-host-header": "^3.972.10", "@aws-sdk/middleware-logger": "^3.972.10", "@aws-sdk/middleware-recursion-detection": "^3.972.11", "@aws-sdk/middleware-user-agent": "^3.972.31", "@aws-sdk/region-config-resolver": "^3.972.12", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.7", "@aws-sdk/util-user-agent-browser": "^3.972.10", "@aws-sdk/util-user-agent-node": "^3.973.17", "@smithy/config-resolver": "^4.4.16", "@smithy/core": "^3.23.15", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/hash-node": "^4.2.14", "@smithy/invalid-dependency": "^4.2.14", "@smithy/middleware-content-length": "^4.2.14", "@smithy/middleware-endpoint": "^4.4.30", "@smithy/middleware-retry": "^4.5.3", "@smithy/middleware-serde": "^4.2.18", "@smithy/middleware-stack": "^4.2.14", "@smithy/node-config-provider": "^4.3.14", "@smithy/node-http-handler": "^4.5.3", "@smithy/protocol-http": "^5.3.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.47", "@smithy/util-defaults-mode-node": "^4.2.52", "@smithy/util-endpoints": "^3.4.1", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-/3RrC4J644U1ZlqcGyGCRf2cyCH/xWs2B6PewlKWeyTq2uWSRtY+v5CkEQ51fRm2Y5wfhuxoU9FO1jKIKm9fSA=="],