From b4450d943f39d4bd2a14cb942a3c870de03449df Mon Sep 17 00:00:00 2001 From: Hweinstock Date: Thu, 1 Oct 2026 17:06:40 +0000 Subject: [PATCH 1/3] fix(templates): name BMA environment-python-bma --- src/core/project/manager.test.ts | 12 ++--- src/core/project/templates/runtime.ts | 5 +-- .../project/add/runtime/index.test.ts | 45 +++++++++++-------- .../add/runtime/runtime.screen.test.tsx | 4 +- src/handlers/project/add/runtime/screen.tsx | 2 +- src/handlers/project/bmaProfile.ts | 2 +- .../project/create/create.screen.test.tsx | 8 ++-- src/handlers/project/create/screen.tsx | 2 +- src/handlers/project/project.test.ts | 4 +- .../project/runtimeTemplateProfile.test.ts | 4 +- src/handlers/project/shortcuts.test.ts | 6 +-- src/handlers/project/shortcuts.ts | 10 ++--- src/handlers/project/types.ts | 3 +- 13 files changed, 59 insertions(+), 48 deletions(-) diff --git a/src/core/project/manager.test.ts b/src/core/project/manager.test.ts index fd7f490115..abda94a7d7 100644 --- a/src/core/project/manager.test.ts +++ b/src/core/project/manager.test.ts @@ -47,7 +47,7 @@ const AGENT_PYTHON_STRANDS_CONTAINER = resolveRuntimeTemplateShortcut( const AGENT_TYPESCRIPT_STRANDS = resolveRuntimeTemplateShortcut("agent-typescript-strands"); const A2A_PYTHON_STRANDS = resolveRuntimeTemplateShortcut("a2a-python-strands"); const AGENT_PYTHON_LANGCHAIN = resolveRuntimeTemplateShortcut("agent-python-langchain"); -const BEDROCK_MANAGED_AGENTS = resolveRuntimeTemplateShortcut("bedrock-managed-agents"); +const BEDROCK_MANAGED_AGENTS = resolveRuntimeTemplateShortcut("environment-python-bma"); function withTemplateProfile( input: ScaffoldRuntimeInput, @@ -301,14 +301,14 @@ describe("FsProjectManager.create", () => { }); const projectRoot = join(directory, "example"); - const appDir = join(projectRoot, "app", "bedrock_managed_agents"); + const appDir = join(projectRoot, "app", "environment_python_bma"); const spec = await Bun.file(join(projectRoot, "agentcore", "agentcore.json")).json(); expect(spec.runtimes).toEqual([ { - name: "bedrock_managed_agents", + name: "environment_python_bma", build: "Container", entrypoint: "lifecycle/server.py", - codeLocation: "app/bedrock_managed_agents", + codeLocation: "app/environment_python_bma", dockerfile: "Dockerfile", additionalPolicies: ["bma-acr-policy.json"], protocol: "HTTP", @@ -328,7 +328,7 @@ describe("FsProjectManager.create", () => { ).exists(), ).toBe(true); expect(await Bun.file(join(appDir, "pyproject.toml")).text()).toContain( - 'name = "bedrock_managed_agents"', + 'name = "environment_python_bma"', ); expect(setup.commands).toEqual([ { @@ -950,7 +950,7 @@ describe("FsProjectManager.addResource", () => { const { error } = await deployOutcome(subject, project); expect(error).toBeInstanceOf(RegionUnsupportedFeatureError); - expect(String(error)).toContain("runtime 'bedrock_managed_agents'"); + expect(String(error)).toContain("runtime 'environment_python_bma'"); expect(String(error)).toContain(BMA_CN_MESSAGE); expect(deployCalls).toEqual([]); }); diff --git a/src/core/project/templates/runtime.ts b/src/core/project/templates/runtime.ts index ea2004fce4..4f0218961b 100644 --- a/src/core/project/templates/runtime.ts +++ b/src/core/project/templates/runtime.ts @@ -15,6 +15,7 @@ import { InputValidationError } from "../../../errors"; import { toPythonPackageName } from "../fsUtils"; import { templateUsesModel } from "../../../handlers/project/templateProfile"; import { resolveRuntimeTemplateProfile } from "../../../handlers/project/runtimeTemplateProfile"; +import { BMA_TEMPLATE_NAME } from "../../../handlers/project/bmaProfile"; /** A model provider's render context, spec entries, and .env.local secrets for a scaffolded runtime. */ type ModelProviderTemplateConfig = { @@ -331,9 +332,7 @@ const getTemplateResolvers = (assetSource: AssetSource, templateRenderer: Templa spec: { runtimes: [{ ...buildRuntimeSpec(input), protocol: "HTTP" as const }] }, }; }, - [buildResolverKey("bedrock-managed-agents", "Python", "HTTP")]: async ( - input: RuntimeResourceConfig, - ) => { + [buildResolverKey(BMA_TEMPLATE_NAME, "Python", "HTTP")]: async (input: RuntimeResourceConfig) => { const tree = await FsTreeNode.fromAssetSource( { assetSource }, { assetDir: "templates/bedrock-managed-agents" }, diff --git a/src/handlers/project/add/runtime/index.test.ts b/src/handlers/project/add/runtime/index.test.ts index fdfce409a9..892d388064 100644 --- a/src/handlers/project/add/runtime/index.test.ts +++ b/src/handlers/project/add/runtime/index.test.ts @@ -104,7 +104,7 @@ describe("project add runtime", () => { build: "CodeZip", protocol: "AGUI", }, - "bedrock-managed-agents template preset": { + "environment-python-bma template preset": { build: "Container", entrypoint: "lifecycle/server.py", dockerfile: "Dockerfile", @@ -176,8 +176,8 @@ describe("project add runtime", () => { ["--name", "my_agui", "--template", "agui-python-strands"], ], [ - "bedrock-managed-agents template preset", - ["--name", "my_bma", "--template", "bedrock-managed-agents"], + "environment-python-bma template preset", + ["--name", "my_bma", "--template", "environment-python-bma"], ], [ "agent-python-strands with session, EFS, and S3 mounts", @@ -286,14 +286,14 @@ describe("project add runtime", () => { const spec = await Bun.file(join(projectRoot, "agentcore", "agentcore.json")).json(); const runtime = spec.runtimes.find((candidate: { name: string }) => candidate.name === name); expect(runtime).toMatchObject({ entrypoint: "main.py", ...expectedSpecByLabel[label] }); - const isBma = flags.includes("bedrock-managed-agents"); + const isBma = flags.includes("environment-python-bma"); expect( await Bun.file( join(projectRoot, "app", name, isBma ? "lifecycle/server.py" : "main.py"), ).exists(), ).toBe(true); const isContainer = flags.some( - (flag) => flag.endsWith("-container") || flag === "bedrock-managed-agents", + (flag) => flag.endsWith("-container") || flag === "environment-python-bma", ); expect(runtime.runtimeVersion).toBe(isContainer ? undefined : "PYTHON_3_14"); expect(await Bun.file(join(projectRoot, "app", name, "Dockerfile")).exists()).toBe(isContainer); @@ -311,7 +311,7 @@ describe("project add runtime", () => { "--name", "my_bma", "--template", - "bedrock-managed-agents", + "environment-python-bma", ]); expect(core.projectCommands).toEqual([]); @@ -329,7 +329,7 @@ describe("project add runtime", () => { "--name", "my_bma", "--template", - "bedrock-managed-agents", + "environment-python-bma", "--lifecycle-configuration", '{"idleRuntimeSessionTimeout":300,"maxLifetime":3600}', "--additional-policies", @@ -363,7 +363,7 @@ describe("project add runtime", () => { "--name", "my_bma", "--template", - "bedrock-managed-agents", + "environment-python-bma", "--role-arn", roleArn, "--json", @@ -391,7 +391,7 @@ describe("project add runtime", () => { ["agent-typescript-vercel", []], ["mcp-python-fastmcp", []], ["agui-python-strands", ["SEMANTIC", "USER_PREFERENCE", "SUMMARIZATION", "EPISODIC"]], - ["bedrock-managed-agents", []], + ["environment-python-bma", []], ])("%s ships with its pre-configured memory", async (templateName, expectedStrategies) => { const { projectRoot, cleanup } = await initProject(); cleanups.push(cleanup); @@ -526,8 +526,9 @@ describe("project add runtime", () => { ], ], [ - "--model-provider is not valid with the bedrock-managed-agents template", - ["--name", "my_bma", "--template", "bedrock-managed-agents", "--model-provider", "Anthropic"], + "--model-provider is not valid with the environment-python-bma template", + ["--name", "my_bma", "--template", "environment-python-bma", "--model-provider", "Anthropic"], + "--model-provider, --model-id, and --api-key are not valid with the environment-python-bma template", ], [ "--model-provider without a template requires agent-python-strands", @@ -545,13 +546,21 @@ describe("project add runtime", () => { await expectError(promise, requiredMessage ?? /./, InputValidationError); }); - test("rejects an unknown --template value", async () => { - const { cleanup } = await initProject(); - cleanups.push(cleanup); - await expect( - run(["add", "runtime", "--name", "my_agent", "--template", "nonsense"]), - ).rejects.toThrow(); - }); + test.each(["nonsense", "bedrock-managed-agents"])( + "rejects the unknown --template value %s", + async (template) => { + const { projectRoot, cleanup } = await initProject(); + cleanups.push(cleanup); + await expect( + run(["add", "runtime", "--name", "my_agent", "--template", template]), + ).rejects.toThrow(); + const spec = await Bun.file(join(projectRoot, "agentcore", "agentcore.json")).json(); + expect(spec.runtimes).toEqual([]); + expect( + await Bun.file(join(projectRoot, "app", "my_agent", "lifecycle", "server.py")).exists(), + ).toBe(false); + }, + ); }); describe("project add runtime --type import", () => { diff --git a/src/handlers/project/add/runtime/runtime.screen.test.tsx b/src/handlers/project/add/runtime/runtime.screen.test.tsx index 023c671910..09937ba97a 100644 --- a/src/handlers/project/add/runtime/runtime.screen.test.tsx +++ b/src/handlers/project/add/runtime/runtime.screen.test.tsx @@ -142,10 +142,11 @@ describe("project add runtime wizard", () => { await r.press("return"); await waitForText(r.lastFrame, "choose a template"); - await selectTemplate(r, "bedrock-managed-agents"); + await selectTemplate(r, "environment-python-bma"); await r.press("return"); await waitForFlatText(r.lastFrame, "build Container"); + expect(flatFrame(r.lastFrame)).toContain("template environment-python-bma"); await r.press("return"); await waitForText(r.lastFrame, "added runtime 'bma_environment' to 'TestProject'"); @@ -153,6 +154,7 @@ describe("project add runtime wizard", () => { build: "Container", entrypoint: "lifecycle/server.py", additionalPolicies: ["bma-acr-policy.json"], + tags: { "agentcore:template": "BedrockManagedAgents" }, }); expect( await Bun.file( diff --git a/src/handlers/project/add/runtime/screen.tsx b/src/handlers/project/add/runtime/screen.tsx index 62bfeca0cc..dafb22a77e 100644 --- a/src/handlers/project/add/runtime/screen.tsx +++ b/src/handlers/project/add/runtime/screen.tsx @@ -119,7 +119,7 @@ function AddRuntimeWizard({ project, core }: { project: Project; core: ScreenPro - help="the agent code scaffolded into app/" choices={TEMPLATE_CHOICES} value={values.template} diff --git a/src/handlers/project/bmaProfile.ts b/src/handlers/project/bmaProfile.ts index 5a7881d28b..0b6e259a37 100644 --- a/src/handlers/project/bmaProfile.ts +++ b/src/handlers/project/bmaProfile.ts @@ -1,6 +1,6 @@ import type { RuntimeTemplateProfile } from "./templateProfile"; -export const BMA_TEMPLATE_NAME = "bedrock-managed-agents"; +export const BMA_TEMPLATE_NAME = "environment-python-bma"; export const BMA_POLICY_FILE = "bma-acr-policy.json"; export const BMA_TEMPLATE_TAG_KEY = "agentcore:template"; export const BMA_TEMPLATE_TAG_VALUE = "BedrockManagedAgents"; diff --git a/src/handlers/project/create/create.screen.test.tsx b/src/handlers/project/create/create.screen.test.tsx index afdb5ce6de..94f267b1f6 100644 --- a/src/handlers/project/create/create.screen.test.tsx +++ b/src/handlers/project/create/create.screen.test.tsx @@ -389,7 +389,7 @@ describe("project create wizard", () => { expect(r.lastFrame()).toContain("● agent-python-strands"); expect(r.lastFrame()).not.toContain("agent-python-strands (recommended)"); expect(r.lastFrame()).toContain("agent-python-strands-container"); - expect(r.lastFrame()).toContain("bedrock-managed-agents"); + expect(r.lastFrame()).toContain("environment-python-bma"); await r.press("return"); // No memory step: memory is no longer a choice, so review follows directly. @@ -438,8 +438,8 @@ describe("project create wizard", () => { await waitForText(r.lastFrame, "choose a template"); await r.press("down"); // agent-python-strands-container await r.press("down"); // agent-python-langchain - await r.press("down"); // bedrock-managed-agents - await waitForText(r.lastFrame, "● bedrock-managed-agents"); + await r.press("down"); // environment-python-bma + await waitForText(r.lastFrame, "● environment-python-bma"); await r.press("return"); await waitForText(r.lastFrame, "this project will be created"); await r.press("return"); @@ -465,7 +465,7 @@ describe("project create wizard", () => { await waitForText(r.lastFrame, "choose a template"); await r.press("down"); // agent-python-strands-container await r.press("down"); // agent-python-langchain - await r.press("down"); // bedrock-managed-agents + await r.press("down"); // environment-python-bma await r.press("down"); // agent-python-minimal await waitForText(r.lastFrame, "● agent-python-minimal "); await r.press("return"); diff --git a/src/handlers/project/create/screen.tsx b/src/handlers/project/create/screen.tsx index 8330cfd24d..25e502fb69 100644 --- a/src/handlers/project/create/screen.tsx +++ b/src/handlers/project/create/screen.tsx @@ -202,7 +202,7 @@ export function ProjectCreateScreen({ ctx, core }: ScreenProps) { {values.kind === "agent" && ( - choices={TEMPLATE_CHOICES} value={values.template} onChange={(template) => patch({ template })} diff --git a/src/handlers/project/project.test.ts b/src/handlers/project/project.test.ts index 99ea931654..226f03281a 100644 --- a/src/handlers/project/project.test.ts +++ b/src/handlers/project/project.test.ts @@ -359,7 +359,7 @@ describe("project create", () => { "--name", "BmaProject", "--template", - "bedrock-managed-agents", + "environment-python-bma", ]); const projectRoot = join(directory, "BmaProject"); @@ -1206,7 +1206,7 @@ describe("create in China regions", () => { "--name", "CnBma", "--template", - "bedrock-managed-agents", + "environment-python-bma", ...skips, "--region", "cn-north-1", diff --git a/src/handlers/project/runtimeTemplateProfile.test.ts b/src/handlers/project/runtimeTemplateProfile.test.ts index 579423382e..a0ab51570e 100644 --- a/src/handlers/project/runtimeTemplateProfile.test.ts +++ b/src/handlers/project/runtimeTemplateProfile.test.ts @@ -6,13 +6,13 @@ describe("resolveRuntimeTemplateProfile", () => { test("derives the canonical BMA profile from the framework", () => { expect( resolveRuntimeTemplateProfile({ - framework: "bedrock-managed-agents", + framework: "environment-python-bma", }), ).toBe(BMA_TEMPLATE_PROFILE); expect( resolveRuntimeTemplateProfile({ - framework: "bedrock-managed-agents", + framework: "environment-python-bma", templateProfile: { usesModel: true, dependencySetup: "managed", diff --git a/src/handlers/project/shortcuts.test.ts b/src/handlers/project/shortcuts.test.ts index 299f4cc8b7..19badd660a 100644 --- a/src/handlers/project/shortcuts.test.ts +++ b/src/handlers/project/shortcuts.test.ts @@ -14,7 +14,7 @@ describe("template order", () => { "agent-python-strands", "agent-python-strands-container", "agent-python-langchain", - "bedrock-managed-agents", + "environment-python-bma", "agent-python-minimal", "agent-typescript-strands", "agent-typescript-vercel", @@ -27,10 +27,10 @@ describe("template order", () => { }); test("the Bedrock Managed Agents shortcut selects its environment profile", () => { - expect(RUNTIME_TEMPLATE_SHORTCUTS["bedrock-managed-agents"]).toMatchObject({ + expect(RUNTIME_TEMPLATE_SHORTCUTS["environment-python-bma"]).toMatchObject({ build: "Container", language: "Python", - framework: "bedrock-managed-agents", + framework: "environment-python-bma", protocol: "HTTP", includesMemory: false, supportsModelProviderOverride: false, diff --git a/src/handlers/project/shortcuts.ts b/src/handlers/project/shortcuts.ts index 06e942011b..1877ccb95b 100644 --- a/src/handlers/project/shortcuts.ts +++ b/src/handlers/project/shortcuts.ts @@ -8,7 +8,7 @@ import { import { InputValidationError } from "../../errors"; import { ScaffoldRuntimeInputSchema, type ModelProvider, type ScaffoldRuntimeInput } from "./types"; import type { RuntimeTemplateProfile } from "./templateProfile"; -import { BMA_TEMPLATE_PROFILE } from "./bmaProfile"; +import { BMA_TEMPLATE_NAME, BMA_TEMPLATE_PROFILE } from "./bmaProfile"; /** The default memory that templates ship with. */ export function getDefaultMemorySpec(runtimeName: string): Memory { @@ -117,12 +117,12 @@ export const RUNTIME_TEMPLATE_SHORTCUTS = { supportsModelProviderOverride: false, runtimeVersion: "NODE_22", }, - "bedrock-managed-agents": { - runtimeName: "bedrock_managed_agents", + [BMA_TEMPLATE_NAME]: { + runtimeName: "environment_python_bma", description: "Execution environment for Bedrock Managed Agents", build: "Container", language: "Python", - framework: "bedrock-managed-agents", + framework: BMA_TEMPLATE_NAME, protocol: "HTTP", includesMemory: false, supportsModelProviderOverride: false, @@ -181,7 +181,7 @@ const FRAMEWORK_ORDER: Record = { strands: 0, langchain: 1, vercelai: 2, - "bedrock-managed-agents": 3, + [BMA_TEMPLATE_NAME]: 3, none: 4, }; const BUILD_ORDER: Record = { CodeZip: 0, Container: 1 }; diff --git a/src/handlers/project/types.ts b/src/handlers/project/types.ts index 40322a7fb6..141f62cb38 100644 --- a/src/handlers/project/types.ts +++ b/src/handlers/project/types.ts @@ -17,6 +17,7 @@ import type { AwsDeploymentTarget } from "../../projectSchemas/aws-targets"; import type { ProgressEvent } from "../../tui/progress"; import type { AwsCredentialProvider } from "../../core/types"; import { RuntimeTemplateProfileSchema } from "./templateProfile"; +import { BMA_TEMPLATE_NAME } from "./bmaProfile"; type CreateProjectInputBase = { /** The name of the project; also the directory it is scaffolded into. */ @@ -48,7 +49,7 @@ export const ScaffoldRuntimeInputSchema = z runtimeName: AgentNameSchema, build: BuildTypeSchema, language: z.enum(["Python", "TypeScript"]), - framework: z.enum(["strands", "langchain", "vercelai", "bedrock-managed-agents", "none"]), + framework: z.enum(["strands", "langchain", "vercelai", BMA_TEMPLATE_NAME, "none"]), protocol: ProtocolModeSchema.optional(), modelProvider: ModelProviderSchema.optional(), modelId: z.string().min(1).optional(), From a91aef8971f106fe1551709cccd4d549c5337fb7 Mon Sep 17 00:00:00 2001 From: Hweinstock Date: Thu, 1 Oct 2026 17:06:41 +0000 Subject: [PATCH 2/3] fix(dev): skip unsupported BMA environments --- src/core/project/manager.tsx | 15 +--- src/core/project/templates/runtime.ts | 3 +- src/errors/errors.tsx | 4 +- src/handlers/project/add/runtime/index.ts | 4 +- .../project/{bmaProfile.ts => bma.ts} | 10 +++ src/handlers/project/create/region.ts | 3 +- src/handlers/project/dev/index.test.ts | 75 +++++++++++++++++-- src/handlers/project/dev/index.ts | 46 +++++++++--- .../project/runtimeTemplateProfile.test.ts | 6 +- .../project/runtimeTemplateProfile.ts | 4 +- src/handlers/project/shortcuts.test.ts | 2 +- src/handlers/project/shortcuts.ts | 6 +- src/handlers/project/types.ts | 3 +- 13 files changed, 133 insertions(+), 48 deletions(-) rename src/handlers/project/{bmaProfile.ts => bma.ts} (75%) diff --git a/src/core/project/manager.tsx b/src/core/project/manager.tsx index 7543f6e9eb..371217896b 100644 --- a/src/core/project/manager.tsx +++ b/src/core/project/manager.tsx @@ -94,12 +94,7 @@ import type { CreateCloudFormationClient } from "../types"; import type { CoreIdentityClient } from "../../handlers/identity/types"; import { templateManagesDependencies } from "../../handlers/project/templateProfile"; import { resolveRuntimeTemplateProfile } from "../../handlers/project/runtimeTemplateProfile"; -import { - BMA_POLICY_FILE, - BMA_TEMPLATE_NAME, - BMA_TEMPLATE_TAG_KEY, - BMA_TEMPLATE_TAG_VALUE, -} from "../../handlers/project/bmaProfile"; +import { isBmaRuntime } from "../../handlers/project/bma"; const TARGETS_EXAMPLE = '[{ "name": "default", "account": "111122223333", "region": "us-east-1" }]'; @@ -469,7 +464,7 @@ export class FsProjectManager implements ProjectManager { if (input.resourceType === "runtime") { const { framework, modelProvider, modelId, memory } = input.resourceConfig.scaffoldRuntimeInput; - if (framework === BMA_TEMPLATE_NAME) { + if (framework === "bma") { throw new RegionUnsupportedFeatureError(BMA_CN_MESSAGE); } if (framework !== "none") { @@ -1167,11 +1162,7 @@ export class FsProjectManager implements ProjectManager { // modelProvider (BYO, provider-free, hand-edited, or scaffolded by an // older CLI) cannot be classified and only get an informational note. if (isChinaRegion(target.region)) { - const bmaRuntimes = project.spec.runtimes.filter( - (runtime) => - runtime.tags?.[BMA_TEMPLATE_TAG_KEY] === BMA_TEMPLATE_TAG_VALUE || - runtime.additionalPolicies?.includes(BMA_POLICY_FILE), - ); + const bmaRuntimes = project.spec.runtimes.filter(isBmaRuntime); if (bmaRuntimes.length > 0) { throw new RegionUnsupportedFeatureError( `Cannot deploy to China region ${target.region}: ` + diff --git a/src/core/project/templates/runtime.ts b/src/core/project/templates/runtime.ts index 4f0218961b..a7b33f117d 100644 --- a/src/core/project/templates/runtime.ts +++ b/src/core/project/templates/runtime.ts @@ -15,7 +15,6 @@ import { InputValidationError } from "../../../errors"; import { toPythonPackageName } from "../fsUtils"; import { templateUsesModel } from "../../../handlers/project/templateProfile"; import { resolveRuntimeTemplateProfile } from "../../../handlers/project/runtimeTemplateProfile"; -import { BMA_TEMPLATE_NAME } from "../../../handlers/project/bmaProfile"; /** A model provider's render context, spec entries, and .env.local secrets for a scaffolded runtime. */ type ModelProviderTemplateConfig = { @@ -332,7 +331,7 @@ const getTemplateResolvers = (assetSource: AssetSource, templateRenderer: Templa spec: { runtimes: [{ ...buildRuntimeSpec(input), protocol: "HTTP" as const }] }, }; }, - [buildResolverKey(BMA_TEMPLATE_NAME, "Python", "HTTP")]: async (input: RuntimeResourceConfig) => { + [buildResolverKey("bma", "Python", "HTTP")]: async (input: RuntimeResourceConfig) => { const tree = await FsTreeNode.fromAssetSource( { assetSource }, { assetDir: "templates/bedrock-managed-agents" }, diff --git a/src/errors/errors.tsx b/src/errors/errors.tsx index 06f1c12cc3..25552c3a0d 100644 --- a/src/errors/errors.tsx +++ b/src/errors/errors.tsx @@ -108,8 +108,8 @@ export class ResourceNotFoundError extends AgentCoreCLIError { /** Error raised when a command or operation has not been implemented yet. */ export class NotImplementedError extends AgentCoreCLIError { - constructor(message?: string, options?: Omit) { - super(message ?? "not implemented yet", { ...options, source: ERROR_SOURCE.INTERNAL }); + constructor(message?: string, options?: AgentCoreCLIErrorOptions) { + super(message ?? "not implemented yet", { source: ERROR_SOURCE.INTERNAL, ...options }); } } diff --git a/src/handlers/project/add/runtime/index.ts b/src/handlers/project/add/runtime/index.ts index a7114677be..efc4e5fd10 100644 --- a/src/handlers/project/add/runtime/index.ts +++ b/src/handlers/project/add/runtime/index.ts @@ -23,7 +23,7 @@ import { } from "../../importBedrockAgent"; import { RegionKey } from "../../../keys"; import { addProjectResource, requireDeployedNameFits } from "../shared"; -import { BMA_CUSTOM_EXECUTION_ROLE_WARNING, BMA_TEMPLATE_NAME } from "../../bmaProfile"; +import { BMA_CUSTOM_EXECUTION_ROLE_WARNING } from "../../bma"; const CONFIGURATION = "Configuration:"; const ENVIRONMENT = "Environment:"; @@ -275,7 +275,7 @@ export const createAddRuntimeHandler = (config: AddProjectResourceConfig) => }) : resolveRuntimeTemplateShortcut("agent-python-minimal", { runtimeName: flags.name }); - if (scaffoldRuntimeInput.framework === BMA_TEMPLATE_NAME && flags["role-arn"] !== undefined) { + if (scaffoldRuntimeInput.framework === "bma" && flags["role-arn"] !== undefined) { notes.push(BMA_CUSTOM_EXECUTION_ROLE_WARNING); } diff --git a/src/handlers/project/bmaProfile.ts b/src/handlers/project/bma.ts similarity index 75% rename from src/handlers/project/bmaProfile.ts rename to src/handlers/project/bma.ts index 0b6e259a37..c229b47018 100644 --- a/src/handlers/project/bmaProfile.ts +++ b/src/handlers/project/bma.ts @@ -1,10 +1,20 @@ import type { RuntimeTemplateProfile } from "./templateProfile"; +import type { ProjectRuntime } from "../../projectSchemas/runtime"; export const BMA_TEMPLATE_NAME = "environment-python-bma"; export const BMA_POLICY_FILE = "bma-acr-policy.json"; export const BMA_TEMPLATE_TAG_KEY = "agentcore:template"; export const BMA_TEMPLATE_TAG_VALUE = "BedrockManagedAgents"; +export function isBmaRuntime( + runtime: Pick, +): boolean { + return ( + runtime.tags?.[BMA_TEMPLATE_TAG_KEY] === BMA_TEMPLATE_TAG_VALUE || + runtime.additionalPolicies?.includes(BMA_POLICY_FILE) === true + ); +} + export const BMA_CUSTOM_EXECUTION_ROLE_WARNING = `Warning: --role-arn uses an existing execution role, so AgentCore CDK cannot attach ` + `${BMA_POLICY_FILE}. Ensure the role grants bedrock-mantle:RegisterEnvironment and ` + diff --git a/src/handlers/project/create/region.ts b/src/handlers/project/create/region.ts index ef695af01b..266e64da5b 100644 --- a/src/handlers/project/create/region.ts +++ b/src/handlers/project/create/region.ts @@ -7,7 +7,6 @@ import { MODEL_PROVIDER_RUNTIMES_CN_MESSAGE, } from "../../../core/project/manager"; import { RegionUnsupportedFeatureError } from "../../../errors"; -import { BMA_TEMPLATE_NAME } from "../bmaProfile"; import type { CreateProjectInput } from "../types"; /** Applies the hard create-time restrictions shared by the CLI and interactive wizard. */ @@ -20,7 +19,7 @@ export function validateCreateRegionSupport(input: CreateProjectInput, region: s const runtime = input.scaffoldRuntimeInput; if (runtime === undefined || runtime.framework === "none") return; - if (runtime.framework === BMA_TEMPLATE_NAME) { + if (runtime.framework === "bma") { throw new RegionUnsupportedFeatureError(BMA_CN_MESSAGE); } if ((runtime.modelProvider ?? "Bedrock") !== "LiteLLM") { diff --git a/src/handlers/project/dev/index.test.ts b/src/handlers/project/dev/index.test.ts index f217590104..8af08b8c63 100644 --- a/src/handlers/project/dev/index.test.ts +++ b/src/handlers/project/dev/index.test.ts @@ -3,6 +3,7 @@ import { join } from "node:path"; import type { ProjectRuntime } from "../../../projectSchemas/runtime"; import { InputValidationError, + NotImplementedError, ResourceNotFoundError, SilentCLIError, UserCancellationError, @@ -13,6 +14,12 @@ import { testIO } from "../../../testing"; import { JsonRendererKey } from "../../../tui"; import { JsonKey, RegionKey } from "../../keys"; import type { Project } from "../types"; +import { + BMA_POLICY_FILE, + BMA_TEMPLATE_NAME, + BMA_TEMPLATE_TAG_KEY, + BMA_TEMPLATE_TAG_VALUE, +} from "../bma"; import { createDevProjectHandler, type DevProjectHandlerConfig } from "."; import type { DevEnvironmentInput } from "./environment"; import type { DevEvent, DevRunner, DevServerInput, DevTraceCollector } from "./types"; @@ -35,6 +42,15 @@ function project(...runtimes: ProjectRuntime[]): Project { }; } +function bmaRuntime(overrides: Partial = {}): ProjectRuntime { + return { + ...runtime("environment", "Container"), + tags: { [BMA_TEMPLATE_TAG_KEY]: BMA_TEMPLATE_TAG_VALUE }, + additionalPolicies: [BMA_POLICY_FILE], + ...overrides, + }; +} + function captureRunner(events: DevEvent[] = []) { const inputs: DevServerInput[] = []; const runner: DevRunner = { @@ -187,6 +203,32 @@ async function inspectorStatus(subject: ReturnType): Promise<{ n } describe("project dev selection and dispatch", () => { + test.each([ + ["headless", undefined, project(bmaRuntime({ additionalPolicies: undefined }))], + ["browser", undefined, project(bmaRuntime({ tags: undefined }))], + [ + "headless", + "environment", + project(bmaRuntime({ tags: { [BMA_TEMPLATE_TAG_KEY]: "Custom" } }), runtime()), + ], + ["browser", "environment", project(bmaRuntime(), runtime())], + ] as const)( + "rejects unsupported BMA selection (%s, %s)", + async (mode, agent, configuredProject) => { + const subject = harness({ project: configuredProject }); + const pending = subject.run({ mode, agent }); + await expect(pending).rejects.toBeInstanceOf(NotImplementedError); + await expect(pending).rejects.toMatchObject({ source: "user", exitCode: 1 }); + await expect(pending).rejects.toThrow( + `Local dev is not supported for runtime 'environment' (${BMA_TEMPLATE_NAME})`, + ); + expect(subject.codeZip.inputs).toHaveLength(0); + expect(subject.container.inputs).toHaveLength(0); + expect(subject.collector.starts).toHaveLength(0); + expect(subject.ui.starts).toHaveLength(0); + }, + ); + test.each([ [project(), {}, "This project has no runtimes", InputValidationError], [ @@ -212,7 +254,7 @@ describe("project dev selection and dispatch", () => { test("loads the environment and dispatches the selected runtime", async () => { const subject = harness({ - project: project(runtime("orders"), runtime("support", "Container")), + project: project(bmaRuntime(), runtime("orders"), runtime("support", "Container")), }); await subject.run({ agent: "support", port: 4567 }); @@ -235,6 +277,7 @@ describe("project dev selection and dispatch", () => { }, runtime: { name: "support", build: "Container" }, }); + expect(subject.io.stderr()).not.toContain("Skipping runtime"); }); test("announces an automatically selected port", async () => { @@ -264,12 +307,21 @@ describe("project dev headless multi-agent", () => { return { pending }; } - test("supervises every runtime with attributed output and per-runtime env", async () => { + test("supervises supported runtimes with attributed output and per-runtime env", async () => { const codeZip = stayingRunner([{ type: "stdout", line: "orders says hi" }]); const container = stayingRunner(); - const subject = harness({ project: twoRuntimes(), codeZip, container }); + const subject = harness({ + project: project( + bmaRuntime({ tags: { [BMA_TEMPLATE_TAG_KEY]: "Custom" } }), + runtime("orders"), + runtime("support", "Container"), + ), + codeZip, + container, + }); const { pending } = await supervised(subject); + expect(subject.io.stderr()).toContain("Skipping runtime 'environment'"); expect(codeZip.inputs).toHaveLength(1); expect(container.inputs).toHaveLength(1); expect(codeZip.inputs[0]!.env).toMatchObject({ @@ -429,9 +481,9 @@ describe("project dev Inspector UI mode", () => { }, ); - test("serves the Inspector API: status lists every runtime, none started", async () => { + test("serves the Inspector API: status lists supported runtimes, none started", async () => { const subject = harness({ - project: project(runtime("orders"), runtime("support", "Container")), + project: project(bmaRuntime(), runtime("orders"), runtime("support", "Container")), }); const { pending } = await runUi(subject); @@ -446,7 +498,9 @@ describe("project dev Inspector UI mode", () => { }); test("agentcore.json edits reload the supervised agents", async () => { - const subject = harness({ reloadedRuntimes: [runtime("orders"), runtime("payments")] }); + const subject = harness({ + reloadedRuntimes: [bmaRuntime({ tags: undefined }), runtime("orders"), runtime("payments")], + }); const { pending } = await runUi(subject); expect(subject.watchers[0]?.path).toBe( @@ -467,11 +521,12 @@ describe("project dev Inspector UI mode", () => { test("--agent narrows the supervised set", async () => { const subject = harness({ - project: project(runtime("orders"), runtime("support", "Container")), + project: project(bmaRuntime(), runtime("orders"), runtime("support", "Container")), }); const { pending } = await runUi(subject, { agent: "support" }); expect((await inspectorStatus(subject)).map((agent) => agent.name)).toEqual(["support"]); + expect(subject.io.stderr()).not.toContain("Skipping runtime"); process.emit("SIGINT", "SIGINT"); await pending.catch(() => undefined); @@ -493,7 +548,11 @@ test("project dev renders attributed human and NDJSON output", async () => { ]; for (const json of [false, true]) { - const subject = harness({ codeZip: captureRunner(events), json }); + const subject = harness({ + project: project(bmaRuntime(), runtime()), + codeZip: captureRunner(events), + json, + }); await subject.run({ agent: "orders", traces: false }); expect(subject.io.stdout()).toBe( json diff --git a/src/handlers/project/dev/index.ts b/src/handlers/project/dev/index.ts index 7a389e34e8..de3603cdd1 100644 --- a/src/handlers/project/dev/index.ts +++ b/src/handlers/project/dev/index.ts @@ -9,7 +9,9 @@ import { DevSupervisor, type SupervisorConfig } from "../../../core/dev/supervis import type { ProjectRuntime } from "../../../projectSchemas/runtime"; import { AgentCoreCLIError, + ERROR_SOURCE, InputValidationError, + NotImplementedError, ResourceNotFoundError, SilentCLIError, UserCancellationError, @@ -19,6 +21,7 @@ import { createHandler, flag, ProjectKey, type Middleware } from "../../../route import { JsonRendererKey, type JsonRenderer } from "../../../tui"; import { JsonKey, RegionKey } from "../../keys"; import type { Project, ProjectManager } from "../types"; +import { BMA_TEMPLATE_NAME, isBmaRuntime } from "../bma"; import type { DevEnvironmentLoader } from "./environment"; import type { DevEvent, DevRunner, DevTraceCollector, DevTraceCollectorStarter } from "./types"; @@ -54,20 +57,34 @@ function otelEnvForRuntime( return runtime.build === "Container" ? rewriteOtelEndpointForContainer(env) : env; } +function supportsLocalDev(runtime: ProjectRuntime): boolean { + return !isBmaRuntime(runtime); +} + function selectRuntimes(project: Project, name?: string): ProjectRuntime[] { if (project.spec.runtimes.length === 0) { throw new InputValidationError( "This project has no runtimes. Add a runtime to agentcore/agentcore.json and retry.", ); } - if (!name) return project.spec.runtimes; - - const runtime = project.spec.runtimes.find((candidate) => candidate.name === name); - if (runtime) return [runtime]; - const available = project.spec.runtimes.map((candidate) => candidate.name).join(", "); - throw new ResourceNotFoundError( - `Runtime '${name}' was not found. Available runtimes: ${available}.`, - ); + const selectedRuntimes = name + ? project.spec.runtimes.filter((runtime) => runtime.name === name) + : project.spec.runtimes; + if (selectedRuntimes.length === 0) { + const available = project.spec.runtimes.map((candidate) => candidate.name).join(", "); + throw new ResourceNotFoundError( + `Runtime '${name}' was not found. Available runtimes: ${available}.`, + ); + } + const supportedRuntimes = selectedRuntimes.filter(supportsLocalDev); + if (supportedRuntimes.length === 0) { + throw new NotImplementedError( + `Local dev is not supported for runtime '${selectedRuntimes[0]!.name}' (${BMA_TEMPLATE_NAME}). ` + + "Run agentcore deploy, then use client.py to connect through Bedrock Managed Agents.", + { source: ERROR_SOURCE.USER }, + ); + } + return supportedRuntimes; } /** An agent's own output, always tagged with the agent that produced it. */ @@ -136,6 +153,17 @@ export const createDevProjectHandler = (config: DevProjectHandlerConfig) => "--port applies to a single runtime. Use --agent to select one.", ); } + if (!flags.agent) { + for (const runtime of project.spec.runtimes.filter( + (runtime) => !supportsLocalDev(runtime), + )) { + renderStatus( + config.io, + `Skipping runtime '${runtime.name}': local dev is not supported for ${BMA_TEMPLATE_NAME}.`, + json, + ); + } + } if ( flags.traces && @@ -264,7 +292,7 @@ export const createDevProjectHandler = (config: DevProjectHandlerConfig) => try { const reloaded = await config.projectManager.resolve({ filePath: project.rootPath }); if (!reloaded) return; - const runtimes = reloaded.spec.runtimes; + const runtimes = reloaded.spec.runtimes.filter(supportsLocalDev); supervisor.setRuntimes( flags.agent ? runtimes.filter((runtime) => runtime.name === flags.agent) : runtimes, ); diff --git a/src/handlers/project/runtimeTemplateProfile.test.ts b/src/handlers/project/runtimeTemplateProfile.test.ts index a0ab51570e..853603b269 100644 --- a/src/handlers/project/runtimeTemplateProfile.test.ts +++ b/src/handlers/project/runtimeTemplateProfile.test.ts @@ -1,18 +1,18 @@ import { describe, expect, test } from "bun:test"; -import { BMA_TEMPLATE_PROFILE } from "./bmaProfile"; +import { BMA_TEMPLATE_PROFILE } from "./bma"; import { resolveRuntimeTemplateProfile } from "./runtimeTemplateProfile"; describe("resolveRuntimeTemplateProfile", () => { test("derives the canonical BMA profile from the framework", () => { expect( resolveRuntimeTemplateProfile({ - framework: "environment-python-bma", + framework: "bma", }), ).toBe(BMA_TEMPLATE_PROFILE); expect( resolveRuntimeTemplateProfile({ - framework: "environment-python-bma", + framework: "bma", templateProfile: { usesModel: true, dependencySetup: "managed", diff --git a/src/handlers/project/runtimeTemplateProfile.ts b/src/handlers/project/runtimeTemplateProfile.ts index de23fe4001..d6b5ca0171 100644 --- a/src/handlers/project/runtimeTemplateProfile.ts +++ b/src/handlers/project/runtimeTemplateProfile.ts @@ -1,4 +1,4 @@ -import { BMA_TEMPLATE_NAME, BMA_TEMPLATE_PROFILE } from "./bmaProfile"; +import { BMA_TEMPLATE_PROFILE } from "./bma"; import type { RuntimeTemplateProfile } from "./templateProfile"; import type { ScaffoldRuntimeInput } from "./types"; @@ -11,7 +11,7 @@ import type { ScaffoldRuntimeInput } from "./types"; export function resolveRuntimeTemplateProfile( input: Pick, ): RuntimeTemplateProfile | undefined { - if (input.framework === BMA_TEMPLATE_NAME) { + if (input.framework === "bma") { return BMA_TEMPLATE_PROFILE; } return input.templateProfile; diff --git a/src/handlers/project/shortcuts.test.ts b/src/handlers/project/shortcuts.test.ts index 19badd660a..28b7ce7d83 100644 --- a/src/handlers/project/shortcuts.test.ts +++ b/src/handlers/project/shortcuts.test.ts @@ -30,7 +30,7 @@ test("the Bedrock Managed Agents shortcut selects its environment profile", () = expect(RUNTIME_TEMPLATE_SHORTCUTS["environment-python-bma"]).toMatchObject({ build: "Container", language: "Python", - framework: "environment-python-bma", + framework: "bma", protocol: "HTTP", includesMemory: false, supportsModelProviderOverride: false, diff --git a/src/handlers/project/shortcuts.ts b/src/handlers/project/shortcuts.ts index 1877ccb95b..06dad9166f 100644 --- a/src/handlers/project/shortcuts.ts +++ b/src/handlers/project/shortcuts.ts @@ -8,7 +8,7 @@ import { import { InputValidationError } from "../../errors"; import { ScaffoldRuntimeInputSchema, type ModelProvider, type ScaffoldRuntimeInput } from "./types"; import type { RuntimeTemplateProfile } from "./templateProfile"; -import { BMA_TEMPLATE_NAME, BMA_TEMPLATE_PROFILE } from "./bmaProfile"; +import { BMA_TEMPLATE_NAME, BMA_TEMPLATE_PROFILE } from "./bma"; /** The default memory that templates ship with. */ export function getDefaultMemorySpec(runtimeName: string): Memory { @@ -122,7 +122,7 @@ export const RUNTIME_TEMPLATE_SHORTCUTS = { description: "Execution environment for Bedrock Managed Agents", build: "Container", language: "Python", - framework: BMA_TEMPLATE_NAME, + framework: "bma", protocol: "HTTP", includesMemory: false, supportsModelProviderOverride: false, @@ -181,7 +181,7 @@ const FRAMEWORK_ORDER: Record = { strands: 0, langchain: 1, vercelai: 2, - [BMA_TEMPLATE_NAME]: 3, + bma: 3, none: 4, }; const BUILD_ORDER: Record = { CodeZip: 0, Container: 1 }; diff --git a/src/handlers/project/types.ts b/src/handlers/project/types.ts index 141f62cb38..26ad94657d 100644 --- a/src/handlers/project/types.ts +++ b/src/handlers/project/types.ts @@ -17,7 +17,6 @@ import type { AwsDeploymentTarget } from "../../projectSchemas/aws-targets"; import type { ProgressEvent } from "../../tui/progress"; import type { AwsCredentialProvider } from "../../core/types"; import { RuntimeTemplateProfileSchema } from "./templateProfile"; -import { BMA_TEMPLATE_NAME } from "./bmaProfile"; type CreateProjectInputBase = { /** The name of the project; also the directory it is scaffolded into. */ @@ -49,7 +48,7 @@ export const ScaffoldRuntimeInputSchema = z runtimeName: AgentNameSchema, build: BuildTypeSchema, language: z.enum(["Python", "TypeScript"]), - framework: z.enum(["strands", "langchain", "vercelai", BMA_TEMPLATE_NAME, "none"]), + framework: z.enum(["strands", "langchain", "vercelai", "bma", "none"]), protocol: ProtocolModeSchema.optional(), modelProvider: ModelProviderSchema.optional(), modelId: z.string().min(1).optional(), From 8e12d14264ba4bbbffa347cac3a3877f36541a1e Mon Sep 17 00:00:00 2001 From: Hweinstock Date: Thu, 1 Oct 2026 17:06:42 +0000 Subject: [PATCH 3/3] fix(deps): patch Fastify audit findings --- bun.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bun.lock b/bun.lock index 8cc361b531..c0a6157ed2 100644 --- a/bun.lock +++ b/bun.lock @@ -835,7 +835,7 @@ "fast-uri": ["fast-uri@4.1.5", "", {}, "sha512-vZeoMRB4epNr7QfdHxel7te/RcX16CxyXI07JCCTFWZA2s4v1azGNESRj+2EoaHSaWFL/Z3GmKT2jF6A202jLg=="], - "fastify": ["fastify@5.12.1", "", { "dependencies": { "@fastify/ajv-compiler": "^4.0.5", "@fastify/error": "^4.0.0", "@fastify/fast-json-stringify-compiler": "^5.0.0", "@fastify/proxy-addr": "^5.0.0", "abstract-logging": "^2.0.1", "avvio": "^9.0.0", "fast-json-stringify": "^7.0.0", "find-my-way": "^9.6.0", "light-my-request": "^6.0.0", "pino": "^9.14.0 || ^10.1.0", "process-warning": "^5.1.0", "rfdc": "^1.3.1", "secure-json-parse": "^4.0.0", "semver": "^7.6.0", "toad-cache": "^3.7.0" } }, "sha512-FWi+tQvwxR/PeRX7Z2mhfEF5ozJ3jn9asiiclzKXNSzJRHAYcU924aIOKAdHFJ+YIKieh3cqr1IwCOvTr41B3Q=="], + "fastify": ["fastify@5.12.5", "", { "dependencies": { "@fastify/ajv-compiler": "^4.0.5", "@fastify/error": "^4.0.0", "@fastify/fast-json-stringify-compiler": "^5.0.0", "@fastify/proxy-addr": "^5.0.0", "abstract-logging": "^2.0.1", "avvio": "^9.0.0", "fast-json-stringify": "^7.0.0", "find-my-way": "^9.6.0", "light-my-request": "^6.0.0", "pino": "^9.14.0 || ^10.1.0", "process-warning": "^5.1.0", "rfdc": "^1.3.1", "secure-json-parse": "^4.0.0", "semver": "^7.6.0", "toad-cache": "^3.7.0" } }, "sha512-OB2k1dlxs5/NAABqeKV2FUHkSD2BbENsCak8yULVcymn3fHIPDVa9TI3SDnJSWYSllZmSYuZXy2gTnsT+Sut1A=="], "fastify-plugin": ["fastify-plugin@5.1.0", "", {}, "sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw=="],