Skip to content

Commit 2c0fdfc

Browse files
adding typescript (#1408)
1 parent 639165a commit 2c0fdfc

7 files changed

Lines changed: 650 additions & 0 deletions

File tree

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
__pycache__
2+
*.pyc
3+
.venv/
4+
.env
5+
.ipynb_checkpoints/
6+
.kiro/
7+
.vscode/
8+
.DS_Store
9+
tmp/
10+
**/chat_app/
11+
**/travel_chat/
12+
dist/
13+
node_modules
14+
my-agent.zip
15+
16+
17+
# to-remove
18+
models/
19+
config.py
20+
travel_chat
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
# Getting Started — TypeScript Agent on Amazon Bedrock AgentCore
2+
3+
Deploy a TypeScript agent to AgentCore Runtime using Direct Code Deploy with Node.js 22.
4+
5+
## Prerequisites
6+
7+
| Requirement | Version | Install |
8+
|---|---|---|
9+
| Node.js | 22.x | [nodejs.org](https://nodejs.org/) |
10+
| AWS CLI | 2.x | [AWS CLI install guide](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) |
11+
| jq | latest | `brew install jq` / `apt install jq` |
12+
13+
### Configure AWS credentials
14+
15+
```bash
16+
aws configure
17+
# Or set environment variables:
18+
# export AWS_ACCESS_KEY_ID=<your-key>
19+
# export AWS_SECRET_ACCESS_KEY=<your-secret>
20+
# export AWS_DEFAULT_REGION=us-west-2
21+
```
22+
23+
Verify your credentials:
24+
25+
```bash
26+
aws sts get-caller-identity
27+
```
28+
29+
---
30+
31+
## Project Structure
32+
33+
```
34+
typescript/
35+
├── app.ts # Agent entry point (Express server)
36+
├── package.json # Node.js dependencies
37+
├── tsconfig.json # TypeScript config
38+
├── iam.sh # IAM role creation/deletion (bash + AWS CLI)
39+
├── runtime.sh # Create, get, list, wait, invoke, and delete runtimes (bash + AWS CLI)
40+
└── README.md
41+
```
42+
43+
---
44+
45+
## Step 1: Create the IAM Execution Role
46+
47+
AgentCore Runtime needs an IAM role to run your agent. The `iam.sh` script creates a role named `TypescriptExecutionRole` with the necessary permissions (Bedrock model invocation, ECR pull, CloudWatch Logs, X-Ray, and AgentCore services).
48+
49+
```bash
50+
./iam.sh create
51+
```
52+
53+
This is idempotent — if the role already exists, it returns the existing ARN. Capture the output and export it:
54+
55+
```bash
56+
export ROLE_ARN=$(./iam.sh create)
57+
echo $ROLE_ARN
58+
```
59+
60+
---
61+
62+
## Step 2: Build the Agent Package
63+
64+
Install dependencies, bundle TypeScript + all dependencies into a single file, and create the deployment zip:
65+
66+
```bash
67+
npm install
68+
69+
npm run build
70+
71+
cd dist
72+
zip deployment_package.zip app.js
73+
cd ..
74+
75+
```
76+
77+
This uses `esbuild` to bundle `app.ts` + all dependencies (Express, Strands Agents SDK, Zod, AWS SDK) into a single `dist/app.js`. No `node_modules` needed in the zip.
78+
79+
---
80+
81+
## Step 3: Upload to S3
82+
83+
Set your account ID and region, then upload:
84+
85+
```bash
86+
export BUCKET=$(your-bucket)
87+
88+
aws s3 cp dist/deployment_package.zip \
89+
s3://$BUCKET/typescript_deploy/deployment_package.zip
90+
```
91+
92+
---
93+
94+
## Step 4: Deploy with Direct Code Deploy
95+
96+
Make sure `ROLE_ARN` is exported from Step 1, then create the runtime:
97+
98+
```bash
99+
export AWS_REGION="us-east-1"
100+
# ROLE_ARN already exported from Step 1
101+
102+
./runtime.sh create
103+
```
104+
105+
Example output:
106+
107+
```json
108+
{
109+
"agentRuntimeArn": "arn:aws:bedrock-agentcore:us-west-2:123456789012:runtime/my_typescript_agent-XXXXXXX",
110+
"agentRuntimeId": "my_typescript_agent-XXXXXXX",
111+
"status": "CREATING"
112+
}
113+
```
114+
115+
Export your agent ARN to be referenced in next examples:
116+
117+
```bash
118+
export AGENT_ARN="arn:aws:bedrock-agentcore:us-west-2:123456789012:runtime/my_typescript_agent-XXXXXXX"
119+
export AGENT_ID="my_typescript_agent-XXXXXXX"
120+
```
121+
122+
123+
Wait for the runtime to become `READY` (polls every 10s):
124+
125+
```bash
126+
./runtime.sh wait $AGENT_ID
127+
```
128+
129+
---
130+
131+
## Step 5: Verify and Invoke
132+
133+
### List runtimes
134+
135+
```bash
136+
./runtime.sh list
137+
```
138+
139+
### Get runtime details
140+
141+
```bash
142+
./runtime.sh get $AGENT_ID
143+
```
144+
145+
### Invoke the agent
146+
147+
Once the runtime status is `READY`:
148+
149+
```bash
150+
./runtime.sh invoke $AGENT_ARN
151+
152+
# With a custom prompt:
153+
./runtime.sh invoke $AGENT_ARN "what is your status?"
154+
```
155+
156+
---
157+
158+
## Step 6: Clean Up
159+
160+
Delete the runtime, IAM role, and S3 artifact when you're done:
161+
162+
```bash
163+
# Delete the runtime
164+
./runtime.sh delete <agentRuntimeId>
165+
166+
# Delete the IAM role
167+
./iam.sh delete
168+
169+
# Remove the S3 artifact
170+
aws s3 rm s3://bedrock-agentcore-code-${ACCOUNT_ID}-${REGION}/typescript_deploy/deployment_package.zip
171+
```
172+
173+
---
174+
175+
## How It Works
176+
177+
### Agent Code (`app.ts`)
178+
179+
The agent is an Express server powered by [Strands Agents SDK](https://strandsagents.com/) with a **calculator tool**. The Strands agent uses Amazon Bedrock (Claude Haiku 4.5) as the LLM and can call tools via native tool-calling.
180+
181+
| Endpoint | Method | Purpose |
182+
|---|---|---|
183+
| `/ping` | GET | Health check — AgentCore uses this to verify the agent is running |
184+
| `/invocations` | POST | Receives a prompt, runs the Strands agent (with tool use), and returns the response |
185+
186+
AgentCore Runtime expects the server to listen on port **8080**.
187+
188+
#### Calculator Tool
189+
190+
The agent has a `calculator` tool that supports `add`, `subtract`, `multiply`, and `divide`. When you send a math question, the LLM decides to call the tool and returns the result.
191+
192+
```bash
193+
./runtime.sh invoke $AGENT_ARN "What is 25 * 4 + 10?"
194+
```
195+
196+
### Direct Code Deploy
197+
198+
Instead of building a container image, Direct Code Deploy lets you upload your source code as a zip to S3. AgentCore handles the build and runtime environment. The deploy payload specifies:
199+
200+
```json
201+
{
202+
"agentRuntimeArtifact": {
203+
"codeConfiguration": {
204+
"code": {
205+
"s3": {
206+
"bucket": "bedrock-agentcore-code-<ACCOUNT_ID>-<REGION>",
207+
"prefix": "typescript_deploy/deployment_package.zip"
208+
}
209+
},
210+
"runtime": "NODE_22",
211+
"entryPoint": ["app.js"]
212+
}
213+
}
214+
}
215+
```
216+
217+
### IAM Role
218+
219+
The execution role grants the agent permissions to:
220+
- Invoke Bedrock models (`bedrock:InvokeModel`)
221+
- Pull container images from ECR
222+
- Write logs to CloudWatch
223+
- Send traces to X-Ray
224+
- Access AgentCore services (Memory, Browser, Gateway, CodeInterpreter)
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import http from "node:http";
2+
import zlib from "node:zlib";
3+
import { Agent, tool } from "@strands-agents/sdk";
4+
import { BedrockModel } from "@strands-agents/sdk/models/bedrock";
5+
import z from "zod";
6+
7+
const model = new BedrockModel({
8+
region: process.env.AWS_REGION || "us-east-1",
9+
modelId: "global.anthropic.claude-haiku-4-5-20251001-v1:0",
10+
});
11+
12+
const calculator = tool({
13+
name: "calculator",
14+
description: "Perform basic arithmetic: add, subtract, multiply, divide.",
15+
inputSchema: z.object({
16+
operation: z.enum(["add", "subtract", "multiply", "divide"]),
17+
a: z.number(),
18+
b: z.number(),
19+
}),
20+
callback: ({ operation, a, b }) => {
21+
if (operation === "add") return `${a + b}`;
22+
if (operation === "subtract") return `${a - b}`;
23+
if (operation === "multiply") return `${a * b}`;
24+
if (operation === "divide") return b === 0 ? "Error: division by zero" : `${a / b}`;
25+
return "Unknown operation";
26+
},
27+
});
28+
29+
const agent = new Agent({ model, tools: [calculator] });
30+
31+
function readBody(req: http.IncomingMessage): Promise<string> {
32+
return new Promise((resolve, reject) => {
33+
const chunks: Buffer[] = [];
34+
const stream = req.headers["content-encoding"] === "gzip" ? req.pipe(zlib.createGunzip()) : req;
35+
stream.on("data", (c: Buffer) => chunks.push(c));
36+
stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
37+
stream.on("error", reject);
38+
});
39+
}
40+
41+
http.createServer(async (req, res) => {
42+
if (req.url === "/ping") {
43+
res.writeHead(200, { "Content-Type": "application/json" });
44+
return res.end('{"status":"Healthy"}');
45+
}
46+
47+
if (req.method === "POST" && req.url === "/invocations") {
48+
let prompt = "Hello";
49+
try {
50+
const raw = await readBody(req);
51+
try {
52+
const body = JSON.parse(raw);
53+
if (body.prompt) prompt = body.prompt;
54+
} catch {
55+
const text = raw.trim();
56+
if (text) prompt = text;
57+
}
58+
} catch {
59+
// use default prompt
60+
}
61+
62+
const result = await agent.invoke(prompt);
63+
res.writeHead(200, { "Content-Type": "application/json" });
64+
return res.end(JSON.stringify({ response: result.lastMessage }));
65+
}
66+
67+
res.writeHead(404);
68+
res.end();
69+
}).listen(8080, "0.0.0.0");

0 commit comments

Comments
 (0)