From 9954761c1e57f63db615fc70498d27cdc4fe5b37 Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Mon, 31 Aug 2026 21:39:45 +0000 Subject: [PATCH 1/3] fix(import): validate Bedrock Agent service metadata --- src/core/project/bedrockAgent.test.ts | 138 ++++++++++++++++++++++++++ src/core/project/bedrockAgent.ts | 138 +++++++++++++++++--------- 2 files changed, 231 insertions(+), 45 deletions(-) create mode 100644 src/core/project/bedrockAgent.test.ts diff --git a/src/core/project/bedrockAgent.test.ts b/src/core/project/bedrockAgent.test.ts new file mode 100644 index 000000000..7447ef414 --- /dev/null +++ b/src/core/project/bedrockAgent.test.ts @@ -0,0 +1,138 @@ +import { describe, expect, test } from "bun:test"; +import type { + GetAgentAliasCommandOutput, + GetAgentCommandOutput, +} from "@aws-sdk/client-bedrock-agent"; +import { InputValidationError, MalformedServiceResponseError } from "../../errors"; +import { createDescribeBedrockAgent, type BedrockAgentControlClient } from "./bedrockAgent"; + +const agent = { + agentId: "A1B2C3D4E5", + agentName: "SupportAgent", + agentArn: "arn:aws:bedrock:us-east-1:111122223333:agent/A1B2C3D4E5", + agentVersion: "DRAFT", + agentStatus: "PREPARED", + idleSessionTTLInSeconds: 600, + agentResourceRoleArn: "arn:aws:iam::111122223333:role/BedrockAgentRole", + createdAt: new Date(0), + updatedAt: new Date(0), + foundationModel: "us.amazon.nova-lite-v1:0", +}; + +const agentAlias = { + agentId: agent.agentId, + agentAliasId: "TSTALIASID", + agentAliasName: "live", + agentAliasArn: `arn:aws:bedrock:us-east-1:111122223333:agent-alias/${agent.agentId}/TSTALIASID`, + routingConfiguration: [{ agentVersion: "1" }], + createdAt: new Date(0), + updatedAt: new Date(0), + agentAliasStatus: "PREPARED", +}; + +class TestBedrockAgentControlClient implements BedrockAgentControlClient { + readonly calls: string[] = []; + agentOutput: GetAgentCommandOutput = { agent } as GetAgentCommandOutput; + aliasOutput: GetAgentAliasCommandOutput = { + agentAlias, + } as GetAgentAliasCommandOutput; + agentError?: Error; + aliasError?: Error; + + async getAgent(): Promise { + this.calls.push("getAgent"); + if (this.agentError) throw this.agentError; + return this.agentOutput; + } + + async getAgentAlias(): Promise { + this.calls.push("getAgentAlias"); + if (this.aliasError) throw this.aliasError; + return this.aliasOutput; + } +} + +function resourceNotFound(): Error { + return Object.assign(new Error("not found"), { name: "ResourceNotFoundException" }); +} + +describe("describeBedrockAgent", () => { + test("returns metadata from the requested agent and alias", async () => { + const client = new TestBedrockAgentControlClient(); + const describeAgent = createDescribeBedrockAgent(() => client); + + await expect( + describeAgent({ + region: "us-east-1", + agentId: agent.agentId, + agentAliasId: agentAlias.agentAliasId, + }), + ).resolves.toEqual({ + agentName: agent.agentName, + agentStatus: agent.agentStatus, + agentAliasArn: agentAlias.agentAliasArn, + agentAliasName: agentAlias.agentAliasName, + agentAliasStatus: agentAlias.agentAliasStatus, + foundationModel: agent.foundationModel, + description: undefined, + }); + expect(client.calls).toEqual(["getAgent", "getAgentAlias"]); + }); + + test("rejects an incomplete agent response before requesting the alias", async () => { + const client = new TestBedrockAgentControlClient(); + client.agentOutput = {} as GetAgentCommandOutput; + const describeAgent = createDescribeBedrockAgent(() => client); + + await expect( + describeAgent({ + region: "us-east-1", + agentId: agent.agentId, + agentAliasId: agentAlias.agentAliasId, + }), + ).rejects.toBeInstanceOf(MalformedServiceResponseError); + expect(client.calls).toEqual(["getAgent"]); + }); + + test("rejects an alias response for a different agent", async () => { + const client = new TestBedrockAgentControlClient(); + client.aliasOutput = { + agentAlias: { ...agentAlias, agentId: "OTHERAGENT" }, + } as GetAgentAliasCommandOutput; + const describeAgent = createDescribeBedrockAgent(() => client); + + await expect( + describeAgent({ + region: "us-east-1", + agentId: agent.agentId, + agentAliasId: agentAlias.agentAliasId, + }), + ).rejects.toBeInstanceOf(MalformedServiceResponseError); + }); + + test("maps agent and alias not-found errors independently", async () => { + const missingAgentClient = new TestBedrockAgentControlClient(); + missingAgentClient.agentError = resourceNotFound(); + const describeMissingAgent = createDescribeBedrockAgent(() => missingAgentClient); + await expect( + describeMissingAgent({ + region: "us-east-1", + agentId: agent.agentId, + agentAliasId: agentAlias.agentAliasId, + }), + ).rejects.toBeInstanceOf(InputValidationError); + expect(missingAgentClient.calls).toEqual(["getAgent"]); + + const missingAliasClient = new TestBedrockAgentControlClient(); + missingAliasClient.aliasError = resourceNotFound(); + const describeMissingAlias = createDescribeBedrockAgent(() => missingAliasClient); + await expect( + describeMissingAlias({ + region: "us-east-1", + agentId: agent.agentId, + agentAliasId: agentAlias.agentAliasId, + }), + ).rejects.toBeInstanceOf(InputValidationError); + expect(missingAliasClient.calls).toEqual(["getAgent", "getAgentAlias"]); + }); +}); diff --git a/src/core/project/bedrockAgent.ts b/src/core/project/bedrockAgent.ts index b4a003b52..e43fb0a63 100644 --- a/src/core/project/bedrockAgent.ts +++ b/src/core/project/bedrockAgent.ts @@ -1,3 +1,10 @@ +import { + BedrockAgentClient, + GetAgentAliasCommand, + GetAgentCommand, + type GetAgentAliasCommandOutput, + type GetAgentCommandOutput, +} from "@aws-sdk/client-bedrock-agent"; import { InputValidationError, MalformedServiceResponseError } from "../../errors"; /** @@ -8,8 +15,12 @@ export const BEDROCK_AGENT_IMPORT_REGIONS = [ "us-east-1", "us-west-2", "eu-west-1", + "eu-west-2", + "eu-west-3", "eu-central-1", + "eu-central-2", "ap-southeast-1", + "ap-southeast-2", "ap-northeast-1", "ap-south-1", "ca-central-1", @@ -40,6 +51,27 @@ export type DescribeBedrockAgent = ( input: DescribeBedrockAgentInput, ) => Promise; +export interface BedrockAgentControlClient { + getAgent(agentId: string): Promise; + getAgentAlias(agentId: string, agentAliasId: string): Promise; +} + +class AwsBedrockAgentControlClient implements BedrockAgentControlClient { + private readonly client: BedrockAgentClient; + + constructor(region: string) { + this.client = new BedrockAgentClient({ region }); + } + + getAgent(agentId: string): Promise { + return this.client.send(new GetAgentCommand({ agentId })); + } + + getAgentAlias(agentId: string, agentAliasId: string): Promise { + return this.client.send(new GetAgentAliasCommand({ agentId, agentAliasId })); + } +} + function isNamedError(error: unknown, name: string): boolean { return error instanceof Error && error.name === name; } @@ -49,55 +81,71 @@ function isNamedError(error: unknown, name: string): boolean { * both to fail fast on a nonexistent agent/alias and to capture the metadata * the scaffolded proxy embeds. */ -export const describeBedrockAgent: DescribeBedrockAgent = async (input) => { - const { BedrockAgentClient, GetAgentCommand, GetAgentAliasCommand } = - await import("@aws-sdk/client-bedrock-agent"); - const client = new BedrockAgentClient({ region: input.region }); - - let agent; - try { - ({ agent } = await client.send(new GetAgentCommand({ agentId: input.agentId }))); - } catch (error) { - if (isNamedError(error, "ResourceNotFoundException")) { - throw new InputValidationError( - `no Bedrock Agent with id '${input.agentId}' exists in ${input.region}; ` + - `check --agent-id and --region`, - { cause: error }, - ); +export function createDescribeBedrockAgent( + createClient: (region: string) => BedrockAgentControlClient = (region) => + new AwsBedrockAgentControlClient(region), +): DescribeBedrockAgent { + return async (input) => { + const client = createClient(input.region); + + let agent; + try { + ({ agent } = await client.getAgent(input.agentId)); + } catch (error) { + if (isNamedError(error, "ResourceNotFoundException")) { + throw new InputValidationError( + `no Bedrock Agent with id '${input.agentId}' exists in ${input.region}; ` + + `check --agent-id and --region`, + { cause: error }, + ); + } + throw error; } - throw error; - } - let agentAlias; - try { - ({ agentAlias } = await client.send( - new GetAgentAliasCommand({ agentId: input.agentId, agentAliasId: input.agentAliasId }), - )); - } catch (error) { - if (isNamedError(error, "ResourceNotFoundException")) { - throw new InputValidationError( - `Bedrock Agent '${input.agentId}' has no alias with id '${input.agentAliasId}' in ` + - `${input.region}; check --agent-alias-id`, - { cause: error }, + if (!agent?.agentId || agent.agentId !== input.agentId || !agent.agentName) { + throw new MalformedServiceResponseError( + `the Bedrock Agent service returned an incomplete description for agent '${input.agentId}'`, ); } - throw error; - } - if (!agent?.agentName || !agentAlias?.agentAliasArn || !agentAlias.agentAliasName) { - throw new MalformedServiceResponseError( - `the Bedrock Agent service returned an incomplete description for agent ` + - `'${input.agentId}' / alias '${input.agentAliasId}'`, - ); - } + let agentAlias; + try { + ({ agentAlias } = await client.getAgentAlias(input.agentId, input.agentAliasId)); + } catch (error) { + if (isNamedError(error, "ResourceNotFoundException")) { + throw new InputValidationError( + `Bedrock Agent '${input.agentId}' has no alias with id '${input.agentAliasId}' in ` + + `${input.region}; check --agent-alias-id`, + { cause: error }, + ); + } + throw error; + } - return { - agentName: agent.agentName, - agentStatus: agent.agentStatus ?? "UNKNOWN", - agentAliasArn: agentAlias.agentAliasArn, - agentAliasName: agentAlias.agentAliasName, - agentAliasStatus: agentAlias.agentAliasStatus ?? "UNKNOWN", - foundationModel: agent.foundationModel, - description: agent.description, + if ( + !agentAlias?.agentId || + agentAlias.agentId !== input.agentId || + !agentAlias.agentAliasId || + agentAlias.agentAliasId !== input.agentAliasId || + !agentAlias.agentAliasArn || + !agentAlias.agentAliasName + ) { + throw new MalformedServiceResponseError( + `the Bedrock Agent service returned an incomplete description for agent ` + + `'${input.agentId}' / alias '${input.agentAliasId}'`, + ); + } + + return { + agentName: agent.agentName, + agentStatus: agent.agentStatus ?? "UNKNOWN", + agentAliasArn: agentAlias.agentAliasArn, + agentAliasName: agentAlias.agentAliasName, + agentAliasStatus: agentAlias.agentAliasStatus ?? "UNKNOWN", + foundationModel: agent.foundationModel, + description: agent.description, + }; }; -}; +} + +export const describeBedrockAgent = createDescribeBedrockAgent(); From c1be0b6e064df6523ad3f9ad11c7f17de71f7e7f Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Mon, 31 Aug 2026 21:39:52 +0000 Subject: [PATCH 2/3] fix(import): use alias readiness and complete regions --- .../project/importBedrockAgent.test.ts | 78 +++++++++++++++++++ src/handlers/project/importBedrockAgent.ts | 6 +- 2 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 src/handlers/project/importBedrockAgent.test.ts diff --git a/src/handlers/project/importBedrockAgent.test.ts b/src/handlers/project/importBedrockAgent.test.ts new file mode 100644 index 000000000..2cb4f618d --- /dev/null +++ b/src/handlers/project/importBedrockAgent.test.ts @@ -0,0 +1,78 @@ +import { describe, expect, test } from "bun:test"; +import type { + BedrockAgentMetadata, + DescribeBedrockAgent, + DescribeBedrockAgentInput, +} from "../../core/project/bedrockAgent"; +import { resolveImportBedrockAgentInput } from "./importBedrockAgent"; + +const metadata: BedrockAgentMetadata = { + agentName: "SupportAgent", + agentStatus: "PREPARED", + agentAliasArn: "arn:aws:bedrock:us-east-1:111122223333:agent-alias/A1B2C3D4E5/TSTALIASID", + agentAliasName: "live", + agentAliasStatus: "PREPARED", +}; + +function describer(result: BedrockAgentMetadata = metadata): { + describeBedrockAgent: DescribeBedrockAgent; + calls: DescribeBedrockAgentInput[]; +} { + const calls: DescribeBedrockAgentInput[] = []; + return { + calls, + describeBedrockAgent: async (input) => { + calls.push(input); + return result; + }, + }; +} + +describe("resolveImportBedrockAgentInput", () => { + test.each(["ap-southeast-2", "eu-central-2", "eu-west-2", "eu-west-3"])( + "accepts predecessor-supported region %s", + async (region) => { + const subject = describer(); + + const result = await resolveImportBedrockAgentInput({ + describeBedrockAgent: subject.describeBedrockAgent, + region, + agentId: "A1B2C3D4E5", + agentAliasId: "TSTALIASID", + }); + + expect(result.imported.region).toBe(region); + expect(subject.calls).toEqual([ + { region, agentId: "A1B2C3D4E5", agentAliasId: "TSTALIASID" }, + ]); + }, + ); + + test("warns when the selected alias is not prepared", async () => { + const subject = describer({ ...metadata, agentAliasStatus: "FAILED" }); + + const result = await resolveImportBedrockAgentInput({ + describeBedrockAgent: subject.describeBedrockAgent, + region: "us-east-1", + agentId: "A1B2C3D4E5", + agentAliasId: "TSTALIASID", + }); + + expect(result.warnings).toEqual([ + "Warning: Bedrock Agent alias 'live' is in status FAILED (not PREPARED); invocations may fail until the alias is prepared.", + ]); + }); + + test("does not warn about the mutable agent draft when the alias is prepared", async () => { + const subject = describer({ ...metadata, agentStatus: "NOT_PREPARED" }); + + const result = await resolveImportBedrockAgentInput({ + describeBedrockAgent: subject.describeBedrockAgent, + region: "us-east-1", + agentId: "A1B2C3D4E5", + agentAliasId: "TSTALIASID", + }); + + expect(result.warnings).toEqual([]); + }); +}); diff --git a/src/handlers/project/importBedrockAgent.ts b/src/handlers/project/importBedrockAgent.ts index 3759e9ef2..09dde0954 100644 --- a/src/handlers/project/importBedrockAgent.ts +++ b/src/handlers/project/importBedrockAgent.ts @@ -57,10 +57,10 @@ export async function resolveImportBedrockAgentInput( }); const warnings: string[] = []; - if (metadata.agentStatus !== "PREPARED") { + if (metadata.agentAliasStatus !== "PREPARED") { warnings.push( - `Warning: Bedrock Agent '${metadata.agentName}' is in status ${metadata.agentStatus} ` + - `(not PREPARED); invocations may fail until it is prepared.`, + `Warning: Bedrock Agent alias '${metadata.agentAliasName}' is in status ` + + `${metadata.agentAliasStatus} (not PREPARED); invocations may fail until the alias is prepared.`, ); } From 3022a49eee2bb540e0497b3d662445b09f611aaa Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Mon, 31 Aug 2026 21:40:08 +0000 Subject: [PATCH 3/3] fix(import): harden Bedrock Agent proxy runtime --- .../bedrock-agent-proxy-python/README.md | 7 ++- .../bedrock-agent-proxy-python/main.py | 43 ++++++++++++++++--- src/core/project/templates/runtime.ts | 5 ++- .../project/add/runtime/index.test.ts | 41 ++++++++++++++++-- src/handlers/project/add/runtime/index.ts | 10 +++++ 5 files changed, 95 insertions(+), 11 deletions(-) diff --git a/src/assets/templates/bedrock-agent-proxy-python/README.md b/src/assets/templates/bedrock-agent-proxy-python/README.md index 3afa6e14e..87aba261f 100644 --- a/src/assets/templates/bedrock-agent-proxy-python/README.md +++ b/src/assets/templates/bedrock-agent-proxy-python/README.md @@ -10,7 +10,10 @@ and invoked through AgentCore without changing it. baked in at import time and can be overridden with the `BEDROCK_AGENT_ID`, `BEDROCK_AGENT_ALIAS_ID`, and `BEDROCK_AGENT_REGION` environment variables. - `bedrock-agent-policy.json` — grants the runtime's execution role - `bedrock:InvokeAgent` on the imported agent's alias. It is wired in through - the runtime's `additionalPolicies` entry in `agentcore/agentcore.json`. + `bedrock:InvokeAgent` on the imported agent's alias. + {{#if usesExistingExecutionRole}}This project uses a caller-owned execution + role, so attach `bedrock-agent-policy.json` to that role before deploying. + AgentCore CDK does not modify existing roles.{{else}}It is wired in through + the runtime's `additionalPolicies` entry in `agentcore/agentcore.json`.{{/if}} Invoke it with a JSON payload like `{"prompt": "hello"}`. diff --git a/src/assets/templates/bedrock-agent-proxy-python/main.py b/src/assets/templates/bedrock-agent-proxy-python/main.py index e61b4958f..4210f7b48 100644 --- a/src/assets/templates/bedrock-agent-proxy-python/main.py +++ b/src/assets/templates/bedrock-agent-proxy-python/main.py @@ -3,7 +3,10 @@ # this runtime are forwarded to the Bedrock Agent, and its reply is streamed # back — edit or replace this file to take ownership of the behavior. +import asyncio +import hashlib import os +import re import uuid import boto3 @@ -12,30 +15,60 @@ AGENT_ID = os.environ.get("BEDROCK_AGENT_ID", "{{agentId}}") AGENT_ALIAS_ID = os.environ.get("BEDROCK_AGENT_ALIAS_ID", "{{agentAliasId}}") AGENT_REGION = os.environ.get("BEDROCK_AGENT_REGION", "{{agentRegion}}") +BEDROCK_SESSION_ID_PATTERN = re.compile(r"^[0-9A-Za-z._:-]{2,100}$") +END_OF_COMPLETION = object() app = BedrockAgentCoreApp() client = boto3.client("bedrock-agent-runtime", region_name=AGENT_REGION) +def normalize_session_id(value): + """Return a stable Bedrock-compatible session id.""" + if isinstance(value, str) and BEDROCK_SESSION_ID_PATTERN.fullmatch(value): + return value + if isinstance(value, str) and value: + return hashlib.sha256(value.encode("utf-8")).hexdigest() + return uuid.uuid4().hex + + +def next_completion_event(completion): + """Read one streaming event without leaking StopIteration into asyncio.""" + try: + return next(completion) + except StopIteration: + return END_OF_COMPLETION + + @app.entrypoint async def invoke(payload, context): """Forward the prompt to the Bedrock Agent and stream its completion.""" + if not isinstance(payload, dict): + yield "Invalid payload; expected a JSON object with a non-empty 'prompt' field." + return + prompt = payload.get("prompt", "") if not isinstance(prompt, str) or not prompt: yield "No query provided; include a 'prompt' field in the payload." return - # Bedrock Agent sessions require ids of 2+ chars; reuse the runtime session - # so multi-turn conversations keep the agent's own memory of the exchange. - session_id = context.session_id or payload.get("sessionId") or uuid.uuid4().hex + # Preserve compatible ids and hash longer/unsupported AgentCore ids so + # multi-turn conversations retain a stable Bedrock Agent session. + session_id = normalize_session_id( + getattr(context, "session_id", None) or payload.get("sessionId") + ) - response = client.invoke_agent( + response = await asyncio.to_thread( + client.invoke_agent, agentId=AGENT_ID, agentAliasId=AGENT_ALIAS_ID, sessionId=session_id, inputText=prompt, ) - for event in response["completion"]: + completion = iter(response["completion"]) + while True: + event = await asyncio.to_thread(next_completion_event, completion) + if event is END_OF_COMPLETION: + break chunk = event.get("chunk") if chunk and "bytes" in chunk: yield chunk["bytes"].decode("utf-8") diff --git a/src/core/project/templates/runtime.ts b/src/core/project/templates/runtime.ts index 10ab9cd7d..058aa2cc1 100644 --- a/src/core/project/templates/runtime.ts +++ b/src/core/project/templates/runtime.ts @@ -90,6 +90,7 @@ const importBedrockAgentResolver = agentRegion: imported.region, agentName: imported.agentName, agentAliasArn: imported.agentAliasArn, + usesExistingExecutionRole: input.executionRoleArn !== undefined, }; const tree = await FsTreeNode.fromAssetSource( { assetSource }, @@ -108,7 +109,9 @@ const importBedrockAgentResolver = { ...base, protocol: "HTTP" as const, - additionalPolicies: [...(base.additionalPolicies ?? []), BEDROCK_AGENT_POLICY_FILE], + ...(base.executionRoleArn === undefined && { + additionalPolicies: [...(base.additionalPolicies ?? []), BEDROCK_AGENT_POLICY_FILE], + }), }, ], }, diff --git a/src/handlers/project/add/runtime/index.test.ts b/src/handlers/project/add/runtime/index.test.ts index 13a41fc4c..887aab86c 100644 --- a/src/handlers/project/add/runtime/index.test.ts +++ b/src/handlers/project/add/runtime/index.test.ts @@ -611,6 +611,9 @@ describe("project add runtime --type import", () => { expect(main).toContain('"TSTALIASID"'); expect(main).toContain('"us-east-1"'); expect(main).toContain("invoke_agent"); + expect(main).toContain("asyncio.to_thread"); + expect(main).toContain("hashlib.sha256"); + expect(main).toContain("isinstance(payload, dict)"); const policy = await Bun.file(join(appDir, "bedrock-agent-policy.json")).json(); expect(policy.Statement[0]).toMatchObject({ @@ -623,16 +626,48 @@ describe("project add runtime --type import", () => { expect(pyproject).toContain("boto3"); }); - test("warns when the agent is not PREPARED", async () => { + test("warns when the alias is not PREPARED", async () => { await inProject(); const core = new TestCoreClient(); core.bedrockAgentDescriptions["A1B2C3D4E5/TSTALIASID"] = { ...metadata, - agentStatus: "NOT_PREPARED", + agentAliasStatus: "FAILED", }; const { io } = await run(importArgs, { core }); - expect(io.stderr()).toContain("not PREPARED"); + expect(io.stderr()).toContain("alias 'live' is in status FAILED"); + }); + + test("rejects a non-HTTP protocol before describing the agent", async () => { + await inProject(); + const core = new TestCoreClient(); + + await expect(run([...importArgs, "--protocol", "MCP"], { core })).rejects.toThrow( + /only supports HTTP/, + ); + expect(core.describedBedrockAgents).toEqual([]); + }); + + test("makes caller-owned role permissions explicit", async () => { + const projectRoot = await inProject(); + const core = new TestCoreClient(); + core.bedrockAgentDescriptions["A1B2C3D4E5/TSTALIASID"] = metadata; + const roleArn = "arn:aws:iam::111122223333:role/ExistingRuntimeRole"; + + const { io } = await run([...importArgs, "--role-arn", roleArn], { core }); + + expect(io.stderr()).toContain( + `execution role '${roleArn}' must already allow bedrock:InvokeAgent on ${metadata.agentAliasArn}`, + ); + const spec = await Bun.file(join(projectRoot, "agentcore", "agentcore.json")).json(); + expect(spec.runtimes[0]).toMatchObject({ executionRoleArn: roleArn }); + expect(spec.runtimes[0].additionalPolicies).toBeUndefined(); + + const appDir = join(projectRoot, "app", "support_proxy"); + expect(await Bun.file(join(appDir, "bedrock-agent-policy.json")).exists()).toBe(true); + expect(await Bun.file(join(appDir, "README.md")).text()).toContain( + "attach `bedrock-agent-policy.json` to that role before deploying", + ); }); test("rejects a nonexistent agent with the describe error", async () => { diff --git a/src/handlers/project/add/runtime/index.ts b/src/handlers/project/add/runtime/index.ts index 892354e52..c1d7181a0 100644 --- a/src/handlers/project/add/runtime/index.ts +++ b/src/handlers/project/add/runtime/index.ts @@ -154,6 +154,9 @@ export const createAddRuntimeHandler = (config: AddProjectResourceConfig) => if (!isImport && (flags["agent-id"] !== undefined || flags["agent-alias-id"] !== undefined)) { throw new InputValidationError("--agent-id and --agent-alias-id require --type import"); } + if (isImport && flags.protocol !== undefined && flags.protocol !== "HTTP") { + throw new InputValidationError("an imported Bedrock Agent proxy only supports HTTP"); + } const isCustom = presentScaffoldingFlags.length > 0; @@ -172,6 +175,13 @@ export const createAddRuntimeHandler = (config: AddProjectResourceConfig) => }); importBedrockAgent = imported; for (const warning of warnings) config.io.stderr.write(`${warning}\n`); + if (flags["role-arn"]) { + config.io.stderr.write( + `Warning: execution role '${flags["role-arn"]}' must already allow ` + + `bedrock:InvokeAgent on ${imported.agentAliasArn}; deployment does not attach ` + + `generated policies to caller-owned roles.\n`, + ); + } } const defaultMemory = flags.framework === "strands" ? "longAndShortTerm" : "none";