From 9dd51f5754847e7dec56a43b4eb7bd207bca4cea Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Wed, 22 Apr 2026 11:01:02 -0400 Subject: [PATCH] feat: add GovCloud multi-partition support Add partition-aware ARN construction, endpoint URL generation, and console URL generation to support aws-us-gov (and future aws-cn) partitions. - Create src/cli/aws/partition.ts with getPartition, arnPrefix, dnsSuffix, serviceEndpoint, and consoleDomain utilities - Replace all hardcoded arn:aws: in ARN template literals with arnPrefix(region) - Update ARN regex patterns to accept any partition (arn:[^:]+:) - Replace hardcoded amazonaws.com in endpoint URLs with serviceEndpoint() - Replace hardcoded console.aws.amazon.com with consoleDomain() - Add us-gov-west-1 to AgentCoreRegionSchema, BEDROCK_REGIONS, and LLM compacted types - Add aws-us-gov to cdk.json target-partitions - Fix execution-role-policy.json to use partition wildcard (arn:*) - Add 15 unit tests for partition utilities - Document multi-partition rules and checklists in AGENTS.md --- AGENTS.md | 38 ++++++++++ eslint.config.mjs | 62 +++++++++++++++ .../assets.snapshot.test.ts.snap | 2 +- src/assets/cdk/cdk.json | 2 +- .../python-lambda/execution-role-policy.json | 2 +- src/cli/aws/__tests__/partition.test.ts | 76 +++++++++++++++++++ src/cli/aws/agentcore.ts | 4 +- src/cli/aws/bedrock-import.ts | 2 +- src/cli/aws/cloudwatch.ts | 3 +- src/cli/aws/index.ts | 1 + src/cli/aws/partition.ts | 28 +++++++ src/cli/aws/transaction-search.ts | 7 +- src/cli/commands/import/actions.ts | 5 +- src/cli/commands/import/import-online-eval.ts | 3 +- src/cli/commands/import/import-utils.ts | 10 +-- src/cli/commands/status/constants.ts | 3 +- .../agent/import/base-translator.ts | 5 +- src/cli/operations/agent/import/constants.ts | 1 + src/cli/operations/traces/trace-url.ts | 5 +- src/cli/tui/screens/import/ArnInputScreen.tsx | 6 +- src/schema/llm-compacted/aws-targets.ts | 3 +- .../schemas/__tests__/aws-targets.test.ts | 1 + src/schema/schemas/aws-targets.ts | 1 + 23 files changed, 244 insertions(+), 26 deletions(-) create mode 100644 src/cli/aws/__tests__/partition.test.ts create mode 100644 src/cli/aws/partition.ts diff --git a/AGENTS.md b/AGENTS.md index 892ee0e02..104b902bf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -143,6 +143,44 @@ See `docs/TESTING.md` for details. - Always look for existing types before creating a new type inline. - Re-usable constants must be defined in a constants file in the closest sensible subdirectory. +## Multi-Partition Support (GovCloud, China) + +The CLI supports multiple AWS partitions (commercial, GovCloud, China) through a central utility at +`src/cli/aws/partition.ts`. This module maps region prefixes to partition-specific values. + +### Rules + +- **Never hardcode `arn:aws:`** in ARN construction. Use `arnPrefix(region)` from `src/cli/aws/partition.ts`. +- **Never hardcode `amazonaws.com`** in endpoint URLs. Use `serviceEndpoint(service, region)` or `dnsSuffix(region)`. +- **Never hardcode `console.aws.amazon.com`** in console URLs. Use `consoleDomain(region)`. +- **ARN regex patterns** must use `arn:[^:]+:` (not `arn:aws:`) to match any partition. +- **Static JSON assets** (e.g., IAM policies in `src/assets/`) cannot use TypeScript utilities — use `arn:*:` as the + partition wildcard since IAM evaluates it across all partitions. + +### Adding a New Region + +Update these files in the CLI repo: + +1. `src/schema/schemas/aws-targets.ts` — add to `AgentCoreRegionSchema` enum +2. `src/schema/llm-compacted/aws-targets.ts` — add to `AgentCoreRegion` type union +3. `src/schema/schemas/__tests__/aws-targets.test.ts` — add to `validRegions` array +4. `src/cli/operations/agent/import/constants.ts` — add to `BEDROCK_REGIONS` (if applicable to Bedrock Agent import) + +Update these files in the CDK repo (`@aws/agentcore-cdk`): + +5. `src/schema/schemas/aws-targets.ts` — add to `AgentCoreRegionSchema` enum +6. `src/schema/llm-compacted/aws-targets.ts` — add to `AgentCoreRegion` type union + +Then run `npm run test:update-snapshots` in the CLI repo if any asset files changed. + +### Adding a New Partition + +1. Add a new entry to `PARTITION_CONFIGS` in `src/cli/aws/partition.ts` with the region prefix, partition name, DNS + suffix, and console domain. +2. Add tests for the new partition in `src/cli/aws/__tests__/partition.test.ts`. +3. Update `src/assets/cdk/cdk.json` — add the partition to `@aws-cdk/core:target-partitions`. +4. Run `npm run test:update-snapshots` to update asset snapshots. + ## TUI Harness See `docs/tui-harness.md` for the full TUI harness usage guide (MCP tools, screen markers, examples, and error diff --git a/eslint.config.mjs b/eslint.config.mjs index 27d45a4f3..216c4caf2 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -7,6 +7,63 @@ import reactRefresh from 'eslint-plugin-react-refresh'; import security from 'eslint-plugin-security'; import tseslint from 'typescript-eslint'; +/** @type {import('eslint').ESLint.Plugin} */ +const partitionPlugin = { + rules: { + 'no-hardcoded-arn-partition': { + meta: { + type: 'problem', + docs: { description: 'Disallow hardcoded arn:aws: partition in ARN construction. Use arnPrefix(region) instead.' }, + schema: [], + }, + create(context) { + function checkForHardcodedArn(node, value) { + if (/arn:aws:/.test(value)) { + context.report({ node, message: 'Hardcoded "arn:aws:" detected. Use arnPrefix(region) from src/cli/aws/partition.ts for multi-partition support.' }); + } + } + return { + TemplateLiteral(node) { + for (const quasi of node.quasis) { + checkForHardcodedArn(node, quasi.value.raw); + } + }, + }; + }, + }, + 'no-hardcoded-endpoint-tld': { + meta: { + type: 'problem', + docs: { description: 'Disallow hardcoded amazonaws.com in endpoint URL construction. Use serviceEndpoint() or dnsSuffix() instead.' }, + schema: [], + }, + create(context) { + const REGION_PATTERN = /[a-z]{2}(-[a-z]+-\d+)/; + function hasHardcodedEndpoint(value) { + return /\.amazonaws\.com/.test(value); + } + function hasHardcodedEndpointWithRegion(value) { + return hasHardcodedEndpoint(value) && REGION_PATTERN.test(value); + } + return { + TemplateLiteral(node) { + for (const quasi of node.quasis) { + if (hasHardcodedEndpoint(quasi.value.raw)) { + context.report({ node, message: 'Hardcoded ".amazonaws.com" in template literal. Use serviceEndpoint() or dnsSuffix() from src/cli/aws/partition.ts for multi-partition support.' }); + } + } + }, + Literal(node) { + if (typeof node.value === 'string' && hasHardcodedEndpointWithRegion(node.value)) { + context.report({ node, message: 'Hardcoded endpoint with region detected. Use serviceEndpoint() or dnsSuffix() from src/cli/aws/partition.ts for multi-partition support.' }); + } + }, + }; + }, + }, + }, +}; + export default tseslint.config( eslint.configs.recommended, ...tseslint.configs.recommendedTypeChecked, @@ -30,8 +87,11 @@ export default tseslint.config( 'react-hooks': reactHooks, 'react-refresh': reactRefresh, security, + partition: partitionPlugin, }, rules: { + 'partition/no-hardcoded-arn-partition': 'error', + 'partition/no-hardcoded-endpoint-tld': 'error', ...importPlugin.configs.recommended.rules, ...react.configs.recommended.rules, ...security.configs.recommended.rules, @@ -68,6 +128,8 @@ export default tseslint.config( { files: ['**/*.test.ts', '**/*.test.tsx', '**/test-utils/**', 'integ-tests/**'], rules: { + 'partition/no-hardcoded-arn-partition': 'off', + 'partition/no-hardcoded-endpoint-tld': 'off', '@typescript-eslint/no-unsafe-assignment': 'off', '@typescript-eslint/no-unsafe-member-access': 'off', '@typescript-eslint/no-unsafe-call': 'off', diff --git a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap index 85a3ad5a4..a1274e294 100644 --- a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap +++ b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap @@ -149,7 +149,7 @@ exports[`Assets Directory Snapshots > CDK assets > cdk/cdk/cdk.json should match "@aws-cdk/aws-ecs-patterns:secGroupsDisablesImplicitOpenListener": true, "@aws-cdk/aws-lambda:recognizeLayerVersion": true, "@aws-cdk/core:checkSecretUsage": true, - "@aws-cdk/core:target-partitions": ["aws", "aws-cn"], + "@aws-cdk/core:target-partitions": ["aws", "aws-cn", "aws-us-gov"], "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, diff --git a/src/assets/cdk/cdk.json b/src/assets/cdk/cdk.json index 40f5c4544..19e6983ab 100644 --- a/src/assets/cdk/cdk.json +++ b/src/assets/cdk/cdk.json @@ -9,7 +9,7 @@ "@aws-cdk/aws-ecs-patterns:secGroupsDisablesImplicitOpenListener": true, "@aws-cdk/aws-lambda:recognizeLayerVersion": true, "@aws-cdk/core:checkSecretUsage": true, - "@aws-cdk/core:target-partitions": ["aws", "aws-cn"], + "@aws-cdk/core:target-partitions": ["aws", "aws-cn", "aws-us-gov"], "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, diff --git a/src/assets/evaluators/python-lambda/execution-role-policy.json b/src/assets/evaluators/python-lambda/execution-role-policy.json index 6a49ca7d9..b3b98be42 100644 --- a/src/assets/evaluators/python-lambda/execution-role-policy.json +++ b/src/assets/evaluators/python-lambda/execution-role-policy.json @@ -4,7 +4,7 @@ { "Effect": "Allow", "Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"], - "Resource": "arn:aws:logs:*:*:log-group:/aws/lambda/*" + "Resource": "arn:*:logs:*:*:log-group:/aws/lambda/*" } ] } diff --git a/src/cli/aws/__tests__/partition.test.ts b/src/cli/aws/__tests__/partition.test.ts new file mode 100644 index 000000000..0c5ed97ba --- /dev/null +++ b/src/cli/aws/__tests__/partition.test.ts @@ -0,0 +1,76 @@ +import { arnPrefix, consoleDomain, dnsSuffix, getPartition, serviceEndpoint } from '../partition'; +import { describe, expect, it } from 'vitest'; + +describe('getPartition', () => { + it('returns aws for standard commercial regions', () => { + expect(getPartition('us-east-1')).toBe('aws'); + expect(getPartition('eu-west-1')).toBe('aws'); + expect(getPartition('ap-southeast-1')).toBe('aws'); + }); + + it('returns aws-us-gov for GovCloud regions', () => { + expect(getPartition('us-gov-west-1')).toBe('aws-us-gov'); + expect(getPartition('us-gov-east-1')).toBe('aws-us-gov'); + }); + + it('returns aws-cn for China regions', () => { + expect(getPartition('cn-north-1')).toBe('aws-cn'); + expect(getPartition('cn-northwest-1')).toBe('aws-cn'); + }); +}); + +describe('arnPrefix', () => { + it('returns arn:aws for commercial regions', () => { + expect(arnPrefix('us-east-1')).toBe('arn:aws'); + }); + + it('returns arn:aws-us-gov for GovCloud regions', () => { + expect(arnPrefix('us-gov-west-1')).toBe('arn:aws-us-gov'); + }); + + it('returns arn:aws-cn for China regions', () => { + expect(arnPrefix('cn-north-1')).toBe('arn:aws-cn'); + }); +}); + +describe('dnsSuffix', () => { + it('returns amazonaws.com for commercial regions', () => { + expect(dnsSuffix('us-east-1')).toBe('amazonaws.com'); + }); + + it('returns amazonaws.com for GovCloud regions', () => { + expect(dnsSuffix('us-gov-west-1')).toBe('amazonaws.com'); + }); + + it('returns amazonaws.com.cn for China regions', () => { + expect(dnsSuffix('cn-north-1')).toBe('amazonaws.com.cn'); + }); +}); + +describe('serviceEndpoint', () => { + it('builds correct endpoint for commercial regions', () => { + expect(serviceEndpoint('bedrock-agentcore', 'us-east-1')).toBe('bedrock-agentcore.us-east-1.amazonaws.com'); + }); + + it('builds correct endpoint for GovCloud regions', () => { + expect(serviceEndpoint('bedrock-agentcore', 'us-gov-west-1')).toBe('bedrock-agentcore.us-gov-west-1.amazonaws.com'); + }); + + it('builds correct endpoint for China regions', () => { + expect(serviceEndpoint('bedrock-agentcore', 'cn-north-1')).toBe('bedrock-agentcore.cn-north-1.amazonaws.com.cn'); + }); +}); + +describe('consoleDomain', () => { + it('returns console.aws.amazon.com for commercial regions', () => { + expect(consoleDomain('us-east-1')).toBe('console.aws.amazon.com'); + }); + + it('returns console.amazonaws-us-gov.com for GovCloud regions', () => { + expect(consoleDomain('us-gov-west-1')).toBe('console.amazonaws-us-gov.com'); + }); + + it('returns console.amazonaws.cn for China regions', () => { + expect(consoleDomain('cn-north-1')).toBe('console.amazonaws.cn'); + }); +}); diff --git a/src/cli/aws/agentcore.ts b/src/cli/aws/agentcore.ts index 43e4fdc8c..55c19d2d0 100644 --- a/src/cli/aws/agentcore.ts +++ b/src/cli/aws/agentcore.ts @@ -1,6 +1,7 @@ import { parseJsonRpcResponse } from '../../lib/utils/json-rpc'; import { getCredentialProvider } from './account'; import { parseAguiSSEStream } from './agui-parser'; +import { serviceEndpoint } from './partition'; import { BedrockAgentCoreClient, EvaluateCommand, @@ -143,11 +144,10 @@ export function extractResult(text: string): string { /** * Build the invoke URL for a runtime ARN. - * Format: https://bedrock-agentcore.{REGION}.amazonaws.com/runtimes/{ESCAPED_ARN}/invocations?qualifier=DEFAULT */ function buildInvokeUrl(region: string, runtimeArn: string): string { const escapedArn = encodeURIComponent(runtimeArn); - return `https://bedrock-agentcore.${region}.amazonaws.com/runtimes/${escapedArn}/invocations?qualifier=DEFAULT`; + return `https://${serviceEndpoint('bedrock-agentcore', region)}/runtimes/${escapedArn}/invocations?qualifier=DEFAULT`; } /** diff --git a/src/cli/aws/bedrock-import.ts b/src/cli/aws/bedrock-import.ts index c5c404df7..c4ba849fd 100644 --- a/src/cli/aws/bedrock-import.ts +++ b/src/cli/aws/bedrock-import.ts @@ -323,7 +323,7 @@ async function fetchCollaborators( const aliasArn = (summary as { agentDescriptor?: { aliasArn?: string } }).agentDescriptor?.aliasArn; if (!aliasArn) continue; - const arnMatch = /^arn:aws:bedrock:[^:]+:[^:]+:agent-alias\/([^/]+)\/([^/]+)$/.exec(aliasArn); + const arnMatch = /^arn:[^:]+:bedrock:[^:]+:[^:]+:agent-alias\/([^/]+)\/([^/]+)$/.exec(aliasArn); if (!arnMatch) continue; const [, collabAgentId, collabAliasId] = arnMatch; if (!collabAgentId || !collabAliasId) continue; diff --git a/src/cli/aws/cloudwatch.ts b/src/cli/aws/cloudwatch.ts index 5c5658a46..c67b77fcd 100644 --- a/src/cli/aws/cloudwatch.ts +++ b/src/cli/aws/cloudwatch.ts @@ -1,4 +1,5 @@ import { getCredentialProvider } from './account'; +import { arnPrefix } from './partition'; import { CloudWatchLogsClient, FilterLogEventsCommand, StartLiveTailCommand } from '@aws-sdk/client-cloudwatch-logs'; export interface LogEvent { @@ -31,7 +32,7 @@ export async function* streamLogs(options: StreamLogsOptions): AsyncGenerator = { + 'aws-us-gov': 'console.amazonaws-us-gov.com', + 'aws-cn': 'console.amazonaws.cn', +}; + +const DEFAULT_CONSOLE_DOMAIN = 'console.aws.amazon.com'; + +export function getPartition(region: string): string { + return partition(region).name; +} + +export function arnPrefix(region: string): string { + return `arn:${getPartition(region)}`; +} + +export function dnsSuffix(region: string): string { + return partition(region).dnsSuffix; +} + +export function serviceEndpoint(service: string, region: string): string { + return `${service}.${region}.${dnsSuffix(region)}`; +} + +export function consoleDomain(region: string): string { + return CONSOLE_DOMAINS[getPartition(region)] ?? DEFAULT_CONSOLE_DOMAIN; +} diff --git a/src/cli/aws/transaction-search.ts b/src/cli/aws/transaction-search.ts index 3dc9e5896..3a3b53fa6 100644 --- a/src/cli/aws/transaction-search.ts +++ b/src/cli/aws/transaction-search.ts @@ -1,5 +1,6 @@ import { getErrorMessage, isAccessDeniedError } from '../errors'; import { getCredentialProvider } from './account'; +import { arnPrefix } from './partition'; import { ApplicationSignalsClient, StartDiscoveryCommand } from '@aws-sdk/client-application-signals'; import { CloudWatchLogsClient, @@ -64,11 +65,11 @@ export async function enableTransactionSearch( Principal: { Service: 'xray.amazonaws.com' }, Action: 'logs:PutLogEvents', Resource: [ - `arn:aws:logs:${region}:${accountId}:log-group:aws/spans:*`, - `arn:aws:logs:${region}:${accountId}:log-group:/aws/application-signals/data:*`, + `${arnPrefix(region)}:logs:${region}:${accountId}:log-group:aws/spans:*`, + `${arnPrefix(region)}:logs:${region}:${accountId}:log-group:/aws/application-signals/data:*`, ], Condition: { - ArnLike: { 'aws:SourceArn': `arn:aws:xray:${region}:${accountId}:*` }, + ArnLike: { 'aws:SourceArn': `${arnPrefix(region)}:xray:${region}:${accountId}:*` }, StringEquals: { 'aws:SourceAccount': accountId }, }, }, diff --git a/src/cli/commands/import/actions.ts b/src/cli/commands/import/actions.ts index ede955939..248216572 100644 --- a/src/cli/commands/import/actions.ts +++ b/src/cli/commands/import/actions.ts @@ -8,6 +8,7 @@ import type { Memory, } from '../../../schema'; import { validateAwsCredentials } from '../../aws/account'; +import { arnPrefix } from '../../aws/partition'; import { ExecLogger } from '../../logging'; import { setupPythonProject } from '../../operations/python/setup'; import { executeCdkImportPipeline } from './import-pipeline'; @@ -521,7 +522,7 @@ export async function handleImport(options: ImportOptions): Promise m.physicalMemoryId) @@ -531,7 +532,7 @@ export async function handleImport(options: ImportOptions): Promise `arn:aws:bedrock-agentcore:${region}:${account}:evaluator/${id}`); + return evaluatorIds.map(id => `${arnPrefix(region)}:bedrock-agentcore:${region}:${account}:evaluator/${id}`); } /** diff --git a/src/cli/commands/import/import-utils.ts b/src/cli/commands/import/import-utils.ts index d224870ec..2b825812c 100644 --- a/src/cli/commands/import/import-utils.ts +++ b/src/cli/commands/import/import-utils.ts @@ -130,10 +130,10 @@ export async function resolveImportTarget(options: ResolveTargetOptions): Promis // Validate ARN format early if provided if ( arn && - !/^arn:aws:bedrock-agentcore:([^:]+):([^:]+):(runtime|memory|evaluator|online-evaluation-config)\/(.+)$/.test(arn) + !/^arn:[^:]+:bedrock-agentcore:([^:]+):([^:]+):(runtime|memory|evaluator|online-evaluation-config)\/(.+)$/.test(arn) ) { throw new Error( - `Not a valid ARN: "${arn}".\nExpected format: arn:aws:bedrock-agentcore:::/` + `Not a valid ARN: "${arn}".\nExpected format: arn::bedrock-agentcore:::/` ); } @@ -146,7 +146,7 @@ export async function resolveImportTarget(options: ResolveTargetOptions): Promis ); } - const arnMatch = /^arn:aws:bedrock-agentcore:([^:]+):([^:]+):/.exec(arn); + const arnMatch = /^arn:[^:]+:bedrock-agentcore:([^:]+):([^:]+):/.exec(arn); if (!arnMatch) { throw new Error( 'No deployment targets found in project and could not parse region/account from ARN.\nRun `agentcore deploy` first to set up a target, then re-run import.' @@ -210,7 +210,7 @@ export interface ParsedArn { } const ARN_PATTERN = - /^arn:aws:bedrock-agentcore:([^:]+):([^:]+):(runtime|memory|evaluator|online-evaluation-config)\/(.+)$/; + /^arn:[^:]+:bedrock-agentcore:([^:]+):([^:]+):(runtime|memory|evaluator|online-evaluation-config)\/(.+)$/; /** Unified config for each importable resource type — ARN mapping, deployed state keys. */ const RESOURCE_TYPE_CONFIG: Record< @@ -244,7 +244,7 @@ export function parseAndValidateArn( const expectedArnType = RESOURCE_TYPE_CONFIG[expectedResourceType].arnType; if (!match) { throw new Error( - `Invalid ARN format: "${arn}". Expected format: arn:aws:bedrock-agentcore:::${expectedArnType}/` + `Invalid ARN format: "${arn}". Expected format: arn::bedrock-agentcore:::${expectedArnType}/` ); } diff --git a/src/cli/commands/status/constants.ts b/src/cli/commands/status/constants.ts index e9b047d5d..f43522b43 100644 --- a/src/cli/commands/status/constants.ts +++ b/src/cli/commands/status/constants.ts @@ -1,3 +1,4 @@ +import { serviceEndpoint } from '../../aws/partition'; import { STATUS_COLORS } from '../../tui/theme'; export type ResourceDeploymentState = 'deployed' | 'local-only' | 'pending-removal'; @@ -16,5 +17,5 @@ export const DEPLOYMENT_STATE_LABELS: Record = export function buildRuntimeInvocationUrl(region: string, runtimeArn: string): string { const encodedArn = encodeURIComponent(runtimeArn); - return `https://bedrock-agentcore.${region}.amazonaws.com/runtimes/${encodedArn}/invocations`; + return `https://${serviceEndpoint('bedrock-agentcore', region)}/runtimes/${encodedArn}/invocations`; } diff --git a/src/cli/operations/agent/import/base-translator.ts b/src/cli/operations/agent/import/base-translator.ts index 5056c6df9..e92277054 100644 --- a/src/cli/operations/agent/import/base-translator.ts +++ b/src/cli/operations/agent/import/base-translator.ts @@ -14,6 +14,7 @@ import type { KnowledgeBaseInfo, PromptConfiguration, } from '../../../aws/bedrock-import-types'; +import { arnPrefix } from '../../../aws/partition'; import type { MemoryOption } from '../../../tui/screens/generate/types'; export interface TranslatorOptions { @@ -373,7 +374,9 @@ memory_id = os.environ.get("MEMORY_ID", "") if (kb.knowledgeBaseArn) { kbArns.push(kb.knowledgeBaseArn); } else if (kb.knowledgeBaseId) { - kbArns.push(`arn:aws:bedrock:${this.agentRegion}:*:knowledge-base/${kb.knowledgeBaseId}`); + kbArns.push( + `${arnPrefix(this.agentRegion)}:bedrock:${this.agentRegion}:*:knowledge-base/${kb.knowledgeBaseId}` + ); } } diff --git a/src/cli/operations/agent/import/constants.ts b/src/cli/operations/agent/import/constants.ts index ce79e3814..ae9c21def 100644 --- a/src/cli/operations/agent/import/constants.ts +++ b/src/cli/operations/agent/import/constants.ts @@ -12,6 +12,7 @@ export const BEDROCK_REGIONS = [ { id: 'ap-south-1', title: 'Asia Pacific (Mumbai)' }, { id: 'ca-central-1', title: 'Canada (Central)' }, { id: 'sa-east-1', title: 'South America (Sao Paulo)' }, + { id: 'us-gov-west-1', title: 'GovCloud (US West)' }, ] as const; export const IMPORT_FRAMEWORK_OPTIONS = [ diff --git a/src/cli/operations/traces/trace-url.ts b/src/cli/operations/traces/trace-url.ts index ceb0512c6..a28791e3c 100644 --- a/src/cli/operations/traces/trace-url.ts +++ b/src/cli/operations/traces/trace-url.ts @@ -1,3 +1,4 @@ +import { arnPrefix, consoleDomain } from '../../aws/partition'; import { DEFAULT_ENDPOINT_NAME } from '../../constants'; /** @@ -11,7 +12,7 @@ export function buildTraceConsoleUrl(params: { }): string { const { region, accountId, runtimeId, agentName } = params; const resourceId = encodeURIComponent( - `arn:aws:bedrock-agentcore:${region}:${accountId}:runtime/${runtimeId}/runtime-endpoint/${DEFAULT_ENDPOINT_NAME}:${DEFAULT_ENDPOINT_NAME}` + `${arnPrefix(region)}:bedrock-agentcore:${region}:${accountId}:runtime/${runtimeId}/runtime-endpoint/${DEFAULT_ENDPOINT_NAME}:${DEFAULT_ENDPOINT_NAME}` ); - return `https://${region}.console.aws.amazon.com/cloudwatch/home?region=${region}#/gen-ai-observability/agent-core/agent-alias/${runtimeId}/endpoint/${DEFAULT_ENDPOINT_NAME}/agent/${agentName}?start=-43200000&resourceId=${resourceId}&serviceName=${agentName}.${DEFAULT_ENDPOINT_NAME}&tabId=traces`; + return `https://${region}.${consoleDomain(region)}/cloudwatch/home?region=${region}#/gen-ai-observability/agent-core/agent-alias/${runtimeId}/endpoint/${DEFAULT_ENDPOINT_NAME}/agent/${agentName}?start=-43200000&resourceId=${resourceId}&serviceName=${agentName}.${DEFAULT_ENDPOINT_NAME}&tabId=traces`; } diff --git a/src/cli/tui/screens/import/ArnInputScreen.tsx b/src/cli/tui/screens/import/ArnInputScreen.tsx index 188f9a694..e56957508 100644 --- a/src/cli/tui/screens/import/ArnInputScreen.tsx +++ b/src/cli/tui/screens/import/ArnInputScreen.tsx @@ -4,11 +4,11 @@ import { Screen } from '../../components/Screen'; import { TextInput } from '../../components/TextInput'; import { HELP_TEXT } from '../../constants'; -const ARN_PATTERN = /^arn:aws:bedrock-agentcore:[^:]+:[^:]+:(runtime|memory|evaluator|online-evaluation-config)\/.+$/; +const ARN_PATTERN = /^arn:[^:]+:bedrock-agentcore:[^:]+:[^:]+:(runtime|memory|evaluator|online-evaluation-config)\/.+$/; function validateArn(value: string): true | string { if (!ARN_PATTERN.test(value)) { - return 'Invalid ARN format. Expected: arn:aws:bedrock-agentcore:::/'; + return 'Invalid ARN format. Expected: arn::bedrock-agentcore:::/'; } return true; } @@ -29,7 +29,7 @@ const RESOURCE_TYPE_LABELS: Record = { export function ArnInputScreen({ resourceType, onSubmit, onExit }: ArnInputScreenProps) { const title = RESOURCE_TYPE_LABELS[resourceType] ?? `Import ${resourceType}`; const arnResourceType = resourceType === 'online-eval' ? 'online-evaluation-config' : resourceType; - const placeholder = `arn:aws:bedrock-agentcore:::${arnResourceType}/`; + const placeholder = `arn::bedrock-agentcore:::${arnResourceType}/`; return ( diff --git a/src/schema/llm-compacted/aws-targets.ts b/src/schema/llm-compacted/aws-targets.ts index d4f9262f3..526a1a695 100644 --- a/src/schema/llm-compacted/aws-targets.ts +++ b/src/schema/llm-compacted/aws-targets.ts @@ -41,4 +41,5 @@ type AgentCoreRegion = | 'sa-east-1' | 'us-east-1' | 'us-east-2' - | 'us-west-2'; + | 'us-west-2' + | 'us-gov-west-1'; diff --git a/src/schema/schemas/__tests__/aws-targets.test.ts b/src/schema/schemas/__tests__/aws-targets.test.ts index 6fbf970d1..cd84f9cb3 100644 --- a/src/schema/schemas/__tests__/aws-targets.test.ts +++ b/src/schema/schemas/__tests__/aws-targets.test.ts @@ -24,6 +24,7 @@ describe('AgentCoreRegionSchema', () => { 'us-east-1', 'us-east-2', 'us-west-2', + 'us-gov-west-1', ]; it.each(validRegions)('accepts valid region "%s"', region => { diff --git a/src/schema/schemas/aws-targets.ts b/src/schema/schemas/aws-targets.ts index 22c7cb19f..711e208dc 100644 --- a/src/schema/schemas/aws-targets.ts +++ b/src/schema/schemas/aws-targets.ts @@ -22,6 +22,7 @@ export const AgentCoreRegionSchema = z.enum([ 'us-east-1', 'us-east-2', 'us-west-2', + 'us-gov-west-1', ]); export type AgentCoreRegion = z.infer;