diff --git a/src/assets/templates/agent-typescript-vercel/README.md b/src/assets/templates/agent-typescript-vercel/README.md new file mode 100644 index 000000000..b5592fe26 --- /dev/null +++ b/src/assets/templates/agent-typescript-vercel/README.md @@ -0,0 +1,22 @@ +This is a project generated by the AgentCore CLI! + +# Layout + +The generated application code lives at the agent root directory. At the root, there is a `.gitignore` file, an +`agentcore/` folder which represents the configurations and state associated with this project. Other `agentcore` +commands like `deploy`, `dev`, and `invoke` rely on the configuration stored here. + +## Agent Root + +The main entrypoint to your app is defined in `main.ts`. Using the AgentCore SDK `BedrockAgentCoreApp`, this file +defines an HTTP server that streams tokens from Amazon Bedrock via the Vercel AI SDK's `streamText` API. + +# Developing locally + +If installation was successful, `node_modules/` is already populated with dependencies. + +`agentcore project dev` will start a local server using `tsx watch main.ts` for hot reload on 0.0.0.0:8080. + +# Deployment + +After providing credentials, `agentcore project deploy` will deploy your project into Amazon Bedrock AgentCore. diff --git a/src/assets/templates/agent-typescript-vercel/gitignore.template b/src/assets/templates/agent-typescript-vercel/gitignore.template new file mode 100644 index 000000000..feb4f544d --- /dev/null +++ b/src/assets/templates/agent-typescript-vercel/gitignore.template @@ -0,0 +1,22 @@ +# Environment variables +.env +.env.* + +# Node +node_modules/ +dist/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db diff --git a/src/assets/templates/agent-typescript-vercel/main.ts b/src/assets/templates/agent-typescript-vercel/main.ts new file mode 100644 index 000000000..2d27f2e5e --- /dev/null +++ b/src/assets/templates/agent-typescript-vercel/main.ts @@ -0,0 +1,35 @@ +import { BedrockAgentCoreApp } from 'bedrock-agentcore/runtime'; +import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock'; +import { fromNodeProviderChain } from '@aws-sdk/credential-providers'; +import { streamText } from 'ai'; +import { z } from 'zod'; + +const SYSTEM_PROMPT = `You are a helpful assistant.`; + +const bedrock = createAmazonBedrock({ + region: process.env.AWS_REGION ?? 'us-east-1', + credentialProvider: fromNodeProviderChain(), +}); + +const requestSchema = z.object({ + prompt: z.string().default(''), +}); + +const app = new BedrockAgentCoreApp({ + invocationHandler: { + requestSchema, + async *process(payload) { + const result = streamText({ + model: bedrock('global.anthropic.claude-sonnet-4-5-20250929-v1:0'), + system: SYSTEM_PROMPT, + prompt: payload.prompt, + }); + + for await (const chunk of result.textStream) { + yield { data: chunk }; + } + }, + }, +}); + +app.run({ port: parseInt(process.env.PORT ?? '8080') }); diff --git a/src/assets/templates/agent-typescript-vercel/package.json.template b/src/assets/templates/agent-typescript-vercel/package.json.template new file mode 100644 index 000000000..a5328e6fc --- /dev/null +++ b/src/assets/templates/agent-typescript-vercel/package.json.template @@ -0,0 +1,24 @@ +{ + "name": "{{name}}", + "version": "0.1.0", + "description": "AgentCore Runtime Application using the Vercel AI SDK", + "private": true, + "type": "module", + "scripts": { + "build": "tsc", + "start": "node dist/main.js", + "dev": "tsx watch main.ts" + }, + "dependencies": { + "@ai-sdk/amazon-bedrock": "~4.0.0", + "@aws-sdk/credential-providers": "~3.1126.0", + "ai": "~6.0.0", + "bedrock-agentcore": "~0.3.0", + "tsx": "~4.19.0", + "zod": "~4.4.3" + }, + "devDependencies": { + "@types/node": "~22.0.0", + "typescript": "~5.6.0" + } +} diff --git a/src/assets/templates/agent-typescript-vercel/tsconfig.json b/src/assets/templates/agent-typescript-vercel/tsconfig.json new file mode 100644 index 000000000..c199ae076 --- /dev/null +++ b/src/assets/templates/agent-typescript-vercel/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": false, + "sourceMap": true, + "outDir": "dist", + "rootDir": ".", + "types": ["node"] + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/src/core/project/templates/runtime.ts b/src/core/project/templates/runtime.ts index 33f37bfa2..5d6f6ffb0 100644 --- a/src/core/project/templates/runtime.ts +++ b/src/core/project/templates/runtime.ts @@ -199,6 +199,23 @@ const getTemplateResolvers = (assetSource: AssetSource, templateRenderer: Templa ...(modelScaffold.envEntries.length > 0 && { envEntries: modelScaffold.envEntries }), }; }, + [buildResolverKey("vercelai", "TypeScript", "HTTP")]: async (input: RuntimeResourceConfig) => { + if (input.protocol !== undefined && input.protocol !== "HTTP") + throw new InputValidationError("the agent-typescript-vercel template only supports HTTP"); + const context = { name: toNpmPackageName(input.name) }; + const tree = await FsTreeNode.fromAssetSource( + { assetSource }, + { assetDir: "templates/agent-typescript-vercel" }, + { + rootDirName: input.name, + transformContent: (raw) => templateRenderer.render(raw, context), + }, + ); + return { + tree, + spec: { runtimes: [{ ...buildRuntimeSpec(input), protocol: "HTTP" as const }] }, + }; + }, [buildResolverKey("none", "Python", "MCP")]: async (input: RuntimeResourceConfig) => { if (input.scaffoldRuntimeInput.modelProvider !== undefined) throw new InputValidationError("an MCP runtime does not use a model provider"); diff --git a/src/handlers/project/add/runtime/index.test.ts b/src/handlers/project/add/runtime/index.test.ts index 56397610f..ac549445f 100644 --- a/src/handlers/project/add/runtime/index.test.ts +++ b/src/handlers/project/add/runtime/index.test.ts @@ -303,6 +303,7 @@ describe("project add runtime", () => { ["agent-python-strands", ["SEMANTIC", "USER_PREFERENCE", "SUMMARIZATION", "EPISODIC"]], ["a2a-python-strands", ["SEMANTIC", "USER_PREFERENCE", "SUMMARIZATION", "EPISODIC"]], ["agent-python-minimal", []], + ["agent-typescript-vercel", []], ["mcp-python-fastmcp", []], ["agui-python-strands", ["SEMANTIC", "USER_PREFERENCE", "SUMMARIZATION", "EPISODIC"]], ])("%s ships with its pre-configured memory", async (templateName, expectedStrategies) => { @@ -349,6 +350,23 @@ describe("project add runtime", () => { ]); }); + test("agent-typescript-vercel scaffolds a memory-free TypeScript runtime", async () => { + const projectRoot = await inProject(); + await run(["add", "runtime", "--name", "my_agent", "--template", "agent-typescript-vercel"]); + + const spec = await Bun.file(join(projectRoot, "agentcore", "agentcore.json")).json(); + expect(spec.runtimes).toContainEqual( + expect.objectContaining({ + name: "my_agent", + entrypoint: "main.js", + build: "CodeZip", + runtimeVersion: "NODE_22", + protocol: "HTTP", + }), + ); + expect(spec.memories ?? []).toEqual([]); + }); + test.each<[string, string]>([ ["anthropic", "Anthropic"], ["OpenAI", "OpenAI"], @@ -397,6 +415,17 @@ describe("project add runtime", () => { "--api-key is not valid with the agent-python-minimal template", ["--name", "my_agent", "--template", "agent-python-minimal", "--api-key", "secret-key"], ], + [ + "--model-provider is not valid with the agent-typescript-vercel template", + [ + "--name", + "my_agent", + "--template", + "agent-typescript-vercel", + "--model-provider", + "Anthropic", + ], + ], [ "--model-provider without a template requires agent-python-strands", ["--name", "my_agent", "--model-provider", "Anthropic"], diff --git a/src/handlers/project/create/screen.tsx b/src/handlers/project/create/screen.tsx index 9ad03cd5f..b47ddb498 100644 --- a/src/handlers/project/create/screen.tsx +++ b/src/handlers/project/create/screen.tsx @@ -140,6 +140,11 @@ const TEMPLATE_OPTIONS: { label: "agent-typescript-strands", description: "Strands agent on Bedrock with memory, in TypeScript (CodeZip build)", }, + { + template: "agent-typescript-vercel", + label: "agent-typescript-vercel", + description: "minimal Vercel AI SDK agent on Bedrock, in TypeScript (CodeZip build)", + }, { template: "mcp-python-fastmcp", label: "mcp-python-fastmcp", diff --git a/src/handlers/project/shortcuts.ts b/src/handlers/project/shortcuts.ts index d263ce69e..86c9ff574 100644 --- a/src/handlers/project/shortcuts.ts +++ b/src/handlers/project/shortcuts.ts @@ -83,6 +83,16 @@ export const RUNTIME_TEMPLATE_SHORTCUTS = { supportsModelProviderOverride: false, runtimeVersion: "NODE_22", }, + "agent-typescript-vercel": { + runtimeName: "agent_typescript_vercel", + build: "CodeZip", + language: "TypeScript", + framework: "vercelai", + modelProvider: "Bedrock", + includesMemory: false, + supportsModelProviderOverride: false, + runtimeVersion: "NODE_22", + }, "mcp-python-fastmcp": { runtimeName: "mcp_python_fastmcp", build: "CodeZip", diff --git a/src/handlers/project/types.ts b/src/handlers/project/types.ts index b08179aa2..88922634d 100644 --- a/src/handlers/project/types.ts +++ b/src/handlers/project/types.ts @@ -67,7 +67,7 @@ export const ScaffoldRuntimeInputSchema = z runtimeName: AgentNameSchema, build: BuildTypeSchema, language: z.enum(["Python", "TypeScript"]), - framework: z.enum(["strands", "none"]), + framework: z.enum(["strands", "vercelai", "none"]), protocol: ProtocolModeSchema.optional(), modelProvider: ModelProviderSchema.optional(), apiKey: z.string().min(1).optional(),