1
0
mirror of synced 2026-05-22 22:53:35 +00:00

adding typescript (#1408)

This commit is contained in:
Evandro Franco
2026-04-28 14:22:35 -04:00
committed by GitHub
parent 639165a94d
commit 2c0fdfc523
7 changed files with 650 additions and 0 deletions
@@ -0,0 +1,20 @@
__pycache__
*.pyc
.venv/
.env
.ipynb_checkpoints/
.kiro/
.vscode/
.DS_Store
tmp/
**/chat_app/
**/travel_chat/
dist/
node_modules
my-agent.zip
# to-remove
models/
config.py
travel_chat
@@ -0,0 +1,224 @@
# Getting Started — TypeScript Agent on Amazon Bedrock AgentCore
Deploy a TypeScript agent to AgentCore Runtime using Direct Code Deploy with Node.js 22.
## Prerequisites
| Requirement | Version | Install |
|---|---|---|
| Node.js | 22.x | [nodejs.org](https://nodejs.org/) |
| AWS CLI | 2.x | [AWS CLI install guide](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) |
| jq | latest | `brew install jq` / `apt install jq` |
### Configure AWS credentials
```bash
aws configure
# Or set environment variables:
# export AWS_ACCESS_KEY_ID=<your-key>
# export AWS_SECRET_ACCESS_KEY=<your-secret>
# export AWS_DEFAULT_REGION=us-west-2
```
Verify your credentials:
```bash
aws sts get-caller-identity
```
---
## Project Structure
```
typescript/
├── app.ts # Agent entry point (Express server)
├── package.json # Node.js dependencies
├── tsconfig.json # TypeScript config
├── iam.sh # IAM role creation/deletion (bash + AWS CLI)
├── runtime.sh # Create, get, list, wait, invoke, and delete runtimes (bash + AWS CLI)
└── README.md
```
---
## Step 1: Create the IAM Execution Role
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).
```bash
./iam.sh create
```
This is idempotent — if the role already exists, it returns the existing ARN. Capture the output and export it:
```bash
export ROLE_ARN=$(./iam.sh create)
echo $ROLE_ARN
```
---
## Step 2: Build the Agent Package
Install dependencies, bundle TypeScript + all dependencies into a single file, and create the deployment zip:
```bash
npm install
npm run build
cd dist
zip deployment_package.zip app.js
cd ..
```
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.
---
## Step 3: Upload to S3
Set your account ID and region, then upload:
```bash
export BUCKET=$(your-bucket)
aws s3 cp dist/deployment_package.zip \
s3://$BUCKET/typescript_deploy/deployment_package.zip
```
---
## Step 4: Deploy with Direct Code Deploy
Make sure `ROLE_ARN` is exported from Step 1, then create the runtime:
```bash
export AWS_REGION="us-east-1"
# ROLE_ARN already exported from Step 1
./runtime.sh create
```
Example output:
```json
{
"agentRuntimeArn": "arn:aws:bedrock-agentcore:us-west-2:123456789012:runtime/my_typescript_agent-XXXXXXX",
"agentRuntimeId": "my_typescript_agent-XXXXXXX",
"status": "CREATING"
}
```
Export your agent ARN to be referenced in next examples:
```bash
export AGENT_ARN="arn:aws:bedrock-agentcore:us-west-2:123456789012:runtime/my_typescript_agent-XXXXXXX"
export AGENT_ID="my_typescript_agent-XXXXXXX"
```
Wait for the runtime to become `READY` (polls every 10s):
```bash
./runtime.sh wait $AGENT_ID
```
---
## Step 5: Verify and Invoke
### List runtimes
```bash
./runtime.sh list
```
### Get runtime details
```bash
./runtime.sh get $AGENT_ID
```
### Invoke the agent
Once the runtime status is `READY`:
```bash
./runtime.sh invoke $AGENT_ARN
# With a custom prompt:
./runtime.sh invoke $AGENT_ARN "what is your status?"
```
---
## Step 6: Clean Up
Delete the runtime, IAM role, and S3 artifact when you're done:
```bash
# Delete the runtime
./runtime.sh delete <agentRuntimeId>
# Delete the IAM role
./iam.sh delete
# Remove the S3 artifact
aws s3 rm s3://bedrock-agentcore-code-${ACCOUNT_ID}-${REGION}/typescript_deploy/deployment_package.zip
```
---
## How It Works
### Agent Code (`app.ts`)
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.
| Endpoint | Method | Purpose |
|---|---|---|
| `/ping` | GET | Health check — AgentCore uses this to verify the agent is running |
| `/invocations` | POST | Receives a prompt, runs the Strands agent (with tool use), and returns the response |
AgentCore Runtime expects the server to listen on port **8080**.
#### Calculator Tool
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.
```bash
./runtime.sh invoke $AGENT_ARN "What is 25 * 4 + 10?"
```
### Direct Code Deploy
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:
```json
{
"agentRuntimeArtifact": {
"codeConfiguration": {
"code": {
"s3": {
"bucket": "bedrock-agentcore-code-<ACCOUNT_ID>-<REGION>",
"prefix": "typescript_deploy/deployment_package.zip"
}
},
"runtime": "NODE_22",
"entryPoint": ["app.js"]
}
}
}
```
### IAM Role
The execution role grants the agent permissions to:
- Invoke Bedrock models (`bedrock:InvokeModel`)
- Pull container images from ECR
- Write logs to CloudWatch
- Send traces to X-Ray
- Access AgentCore services (Memory, Browser, Gateway, CodeInterpreter)
@@ -0,0 +1,69 @@
import http from "node:http";
import zlib from "node:zlib";
import { Agent, tool } from "@strands-agents/sdk";
import { BedrockModel } from "@strands-agents/sdk/models/bedrock";
import z from "zod";
const model = new BedrockModel({
region: process.env.AWS_REGION || "us-east-1",
modelId: "global.anthropic.claude-haiku-4-5-20251001-v1:0",
});
const calculator = tool({
name: "calculator",
description: "Perform basic arithmetic: add, subtract, multiply, divide.",
inputSchema: z.object({
operation: z.enum(["add", "subtract", "multiply", "divide"]),
a: z.number(),
b: z.number(),
}),
callback: ({ operation, a, b }) => {
if (operation === "add") return `${a + b}`;
if (operation === "subtract") return `${a - b}`;
if (operation === "multiply") return `${a * b}`;
if (operation === "divide") return b === 0 ? "Error: division by zero" : `${a / b}`;
return "Unknown operation";
},
});
const agent = new Agent({ model, tools: [calculator] });
function readBody(req: http.IncomingMessage): Promise<string> {
return new Promise((resolve, reject) => {
const chunks: Buffer[] = [];
const stream = req.headers["content-encoding"] === "gzip" ? req.pipe(zlib.createGunzip()) : req;
stream.on("data", (c: Buffer) => chunks.push(c));
stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
stream.on("error", reject);
});
}
http.createServer(async (req, res) => {
if (req.url === "/ping") {
res.writeHead(200, { "Content-Type": "application/json" });
return res.end('{"status":"Healthy"}');
}
if (req.method === "POST" && req.url === "/invocations") {
let prompt = "Hello";
try {
const raw = await readBody(req);
try {
const body = JSON.parse(raw);
if (body.prompt) prompt = body.prompt;
} catch {
const text = raw.trim();
if (text) prompt = text;
}
} catch {
// use default prompt
}
const result = await agent.invoke(prompt);
res.writeHead(200, { "Content-Type": "application/json" });
return res.end(JSON.stringify({ response: result.lastMessage }));
}
res.writeHead(404);
res.end();
}).listen(8080, "0.0.0.0");
@@ -0,0 +1,137 @@
#!/usr/bin/env bash
set -euo pipefail
ROLE_NAME="TypescriptExecutionRole"
POLICY_NAME="TypescriptExecutionPolicy"
TRUST_POLICY='{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "bedrock-agentcore.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}'
PERMISSIONS_POLICY='{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "BedrockInvokeModel",
"Effect": "Allow",
"Action": ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"],
"Resource": "*"
},
{
"Sid": "ECRPull",
"Effect": "Allow",
"Action": [
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:BatchCheckLayerAvailability",
"ecr:GetAuthorizationToken"
],
"Resource": "*"
},
{
"Sid": "EcrPublicPull",
"Effect": "Allow",
"Action": ["ecr-public:GetAuthorizationToken"],
"Resource": "*"
},
{
"Sid": "StsForEcrPublicPull",
"Effect": "Allow",
"Action": ["sts:GetServiceBearerToken"],
"Resource": "*"
},
{
"Sid": "XRay",
"Effect": "Allow",
"Action": ["xray:PutTraceSegments", "xray:PutTelemetryRecords"],
"Resource": "*"
},
{
"Sid": "CloudWatchLogs",
"Effect": "Allow",
"Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
"Resource": "*"
},
{
"Sid": "AgentCore",
"Effect": "Allow",
"Action": [
"bedrock-agentcore:*Memory*",
"bedrock-agentcore:*Browser*",
"bedrock-agentcore:*Gateway*",
"bedrock-agentcore:*CodeInterpreter*",
"bedrock-agentcore:RetrieveMemoryRecords",
"bedrock-agentcore:CreateEvent",
"bedrock-agentcore:ListEvents",
"bedrock-agentcore:GetEvent"
],
"Resource": "*"
},
{
"Sid": "GetAgentCoreApiKeys",
"Effect": "Allow",
"Action": ["bedrock-agentcore:GetResourceApiKey"],
"Resource": "*"
}
]
}'
create_role() {
local role_arn
role_arn=$(aws iam get-role \
--role-name "$ROLE_NAME" \
--query 'Role.Arn' \
--output text 2>/dev/null || true)
if [ -n "$role_arn" ] && [ "$role_arn" != "None" ]; then
echo "Role $ROLE_NAME already exists: $role_arn" >&2
echo "$role_arn"
return 0
fi
role_arn=$(aws iam create-role \
--role-name "$ROLE_NAME" \
--assume-role-policy-document "$TRUST_POLICY" \
--description "Execution role for Amazon Bedrock AgentCore Runtime with TypeScript" \
--query 'Role.Arn' \
--output text)
echo "Created role: $role_arn" >&2
aws iam put-role-policy \
--role-name "$ROLE_NAME" \
--policy-name "$POLICY_NAME" \
--policy-document "$PERMISSIONS_POLICY"
echo "Attached policy: $POLICY_NAME" >&2
echo "$role_arn"
}
delete_role() {
aws iam delete-role-policy \
--role-name "$ROLE_NAME" \
--policy-name "$POLICY_NAME" 2>/dev/null && \
echo "Deleted inline policy: $POLICY_NAME" || true
aws iam delete-role \
--role-name "$ROLE_NAME" 2>/dev/null && \
echo "Deleted role: $ROLE_NAME" || \
echo "Role $ROLE_NAME not found"
}
case "${1:-}" in
create) create_role ;;
delete) delete_role ;;
*)
echo "Usage: ./iam.sh <create|delete>"
exit 1
;;
esac
@@ -0,0 +1,21 @@
{
"name": "00-getting-started",
"version": "1.0.0",
"type": "module",
"scripts": {
"build": "esbuild app.ts --bundle --platform=node --target=node22 --outfile=dist/app.js",
"start": "node dist/app.js",
"dev": "tsx app.ts"
},
"dependencies": {
"@strands-agents/sdk": "^1.0.0-rc.5",
"express": "^5.2.1",
"zod": "^4.3.6"
},
"devDependencies": {
"@types/express": "^5.0.1",
"esbuild": "^0.28.0",
"tsx": "^4.19.4",
"typescript": "^5.8.3"
}
}
@@ -0,0 +1,167 @@
#!/usr/bin/env bash
set -euo pipefail
REGION="${AWS_REGION:-us-west-2}"
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
BUCKET="${BUCKET:-bedrock-agentcore-code-${ACCOUNT_ID}-${REGION}}"
PREFIX="typescript_deploy/deployment_package.zip"
AGENT_NAME="my_typescript_agent"
create_runtime() {
local role_arn="${ROLE_ARN:?Set ROLE_ARN before calling create}"
aws bedrock-agentcore-control create-agent-runtime \
--region "$REGION" \
--agent-runtime-name "$AGENT_NAME" \
--role-arn "$role_arn" \
--agent-runtime-artifact '{
"codeConfiguration": {
"code": {
"s3": {
"bucket": "'"$BUCKET"'",
"prefix": "'"$PREFIX"'"
}
},
"runtime": "NODE_22",
"entryPoint": ["app.js"]
}
}' \
--network-configuration '{"networkMode": "PUBLIC"}' \
--protocol-configuration '{"serverProtocol": "HTTP"}' \
--query '{agentRuntimeArn: agentRuntimeArn, agentRuntimeId: agentRuntimeId, status: status}' \
--output json
}
get_runtime() {
local runtime_id="${1:?Usage: runtime.sh get <agentRuntimeId>}"
aws bedrock-agentcore-control get-agent-runtime \
--region "$REGION" \
--agent-runtime-id "$runtime_id" \
--query '{agentRuntimeArn: agentRuntimeArn, agentRuntimeId: agentRuntimeId, agentRuntimeName: agentRuntimeName, status: status, createdAt: createdAt, lastUpdatedAt: lastUpdatedAt}' \
--output json
}
wait_ready() {
local runtime_id="${1:?Usage: runtime.sh wait <agentRuntimeId>}"
local timeout="${2:-300}"
local elapsed=0
echo "Waiting for $runtime_id to reach READY..."
while [ "$elapsed" -lt "$timeout" ]; do
local status
status=$(aws bedrock-agentcore-control get-agent-runtime \
--region "$REGION" \
--agent-runtime-id "$runtime_id" \
--query 'status' \
--output text)
echo " status: $status"
if [ "$status" = "READY" ]; then
echo "Done."
return 0
fi
case "$status" in
*_FAILED)
echo "Failed!"
aws bedrock-agentcore-control get-agent-runtime \
--region "$REGION" \
--agent-runtime-id "$runtime_id" \
--query 'failureReason' \
--output text
exit 1
;;
esac
sleep 10
elapsed=$((elapsed + 10))
done
echo "Timed out after ${timeout}s"
exit 1
}
list_runtimes() {
aws bedrock-agentcore-control list-agent-runtimes \
--region "$REGION" \
--query 'agentRuntimes[].{Id: agentRuntimeId, Status: status, Name: agentRuntimeName}' \
--output table
}
invoke_runtime() {
local runtime_id="${1:?Usage: runtime.sh invoke <agentRuntimeId> [prompt]}"
local prompt="${2:-hello, what can you do?}"
local tmpfile
tmpfile=$(mktemp)
aws bedrock-agentcore invoke-agent-runtime \
--region "$REGION" \
--agent-runtime-arn "$runtime_id" \
--content-type "application/json" \
--accept "application/json" \
--payload "$(echo -n "{\"prompt\": \"$prompt\"}" | base64)" \
"$tmpfile"
cat "$tmpfile"
echo ""
rm -f "$tmpfile"
}
update_runtime() {
local runtime_id="${1:?Usage: runtime.sh update <agentRuntimeId>}"
local role_arn="${ROLE_ARN:?Set ROLE_ARN before calling update}"
aws bedrock-agentcore-control update-agent-runtime \
--region "$REGION" \
--agent-runtime-id "$runtime_id" \
--role-arn "$role_arn" \
--agent-runtime-artifact '{
"codeConfiguration": {
"code": {
"s3": {
"bucket": "'"$BUCKET"'",
"prefix": "'"$PREFIX"'"
}
},
"runtime": "NODE_22",
"entryPoint": ["app.js"]
}
}' \
--network-configuration '{"networkMode": "PUBLIC"}' \
--protocol-configuration '{"serverProtocol": "HTTP"}' \
--query '{agentRuntimeArn: agentRuntimeArn, agentRuntimeId: agentRuntimeId, status: status}' \
--output json
}
delete_runtime() {
local runtime_id="${1:?Usage: runtime.sh delete <agentRuntimeId>}"
aws bedrock-agentcore-control delete-agent-runtime \
--region "$REGION" \
--agent-runtime-id "$runtime_id" \
--query '{agentRuntimeId: agentRuntimeId, status: status}' \
--output json
}
USAGE="Usage: ./runtime.sh <command> [args]
Commands:
create Create the agent runtime
get <agentRuntimeId> Get runtime details
wait <agentRuntimeId> [timeout] Wait until runtime is READY (default 300s)
list List all runtimes
update <agentRuntimeId> Update the runtime (redeploy from S3)
invoke <agentRuntimeId> [prompt] Invoke the agent
delete <agentRuntimeId> Delete the runtime"
case "${1:-}" in
create) create_runtime ;;
get) get_runtime "${2:-}" ;;
wait) wait_ready "${2:-}" "${3:-300}" ;;
list) list_runtimes ;;
update) update_runtime "${2:-}" ;;
invoke) invoke_runtime "${2:-}" "${3:-hello, what can you do?}" ;;
delete) delete_runtime "${2:-}" ;;
*) echo "$USAGE"; exit 1 ;;
esac
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"outDir": "./dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
},
"include": ["app.ts"]
}