From b7c58284e376fc559d03c4c635bba392c04ba4a7 Mon Sep 17 00:00:00 2001 From: Michael Ruelas Date: Wed, 7 Jan 2026 09:09:48 -0800 Subject: [PATCH 1/2] feat(examples): add browser-based agent example This commit adds a new example in that demonstrates how to use the SDK in a browser environment. It includes: - A simple HTML UI with a chat interface and a modifiable canvas. - An agent configured with and a custom tool. - Vite configuration for development and building. - README documentation for the example. Updates root README to link to the new example. --- README.md | 4 + examples/browser-agent/README.md | 38 + examples/browser-agent/index.html | 140 +++ examples/browser-agent/package-lock.json | 1079 ++++++++++++++++++++++ examples/browser-agent/package.json | 23 + examples/browser-agent/src/index.ts | 110 +++ examples/browser-agent/src/tools.ts | 59 ++ examples/browser-agent/tsconfig.json | 37 + examples/browser-agent/vite.config.ts | 14 + 9 files changed, 1504 insertions(+) create mode 100644 examples/browser-agent/README.md create mode 100644 examples/browser-agent/index.html create mode 100644 examples/browser-agent/package-lock.json create mode 100644 examples/browser-agent/package.json create mode 100644 examples/browser-agent/src/index.ts create mode 100644 examples/browser-agent/src/tools.ts create mode 100644 examples/browser-agent/tsconfig.json create mode 100644 examples/browser-agent/vite.config.ts diff --git a/README.md b/README.md index e16612c4..ea2755f7 100644 --- a/README.md +++ b/README.md @@ -201,6 +201,10 @@ For detailed guidance, tutorials, and concept overviews, please visit: - **[Official Documentation](https://strandsagents.com/)**: Comprehensive guides and tutorials - **[API Reference](https://strandsagents.com/latest/documentation/docs/api-reference/typescript/)**: Complete API documentation - **[Examples](./examples/)**: Sample applications + - **[First Agent](./examples/first-agent/)**: Basic Node.js agent + - **[MCP](./examples/mcp/)**: MCP integration example + - **[Browser Agent](./examples/browser-agent/)**: Browser-based agent with DOM manipulation + - **[Contributing Guide](CONTRIBUTING.md)**: Development setup and guidelines --- diff --git a/examples/browser-agent/README.md b/examples/browser-agent/README.md new file mode 100644 index 00000000..be4f9e5a --- /dev/null +++ b/examples/browser-agent/README.md @@ -0,0 +1,38 @@ +# Browser Agent Example + +This example demonstrates how to run the Strands Agent directly in a browser environment using Vite. + +The agent uses the OpenAI model provider (via API Key) and has access to a tool (`update_canvas`) that allows it to modify the DOM elements on the page. + +## Prerequisites + +- Node.js 20+ +- An OpenAI API Key (you will be prompted to enter this in the browser) + +## Setup + +1. Install dependencies from the root of the repo: + ```bash + npm install + ``` + +## Running the Example + +1. Navigate to this directory: + ```bash + cd examples/browser-agent + ``` + +2. Start the development server: + ```bash + npm run dev + ``` + +3. Open the URL shown in the terminal (usually `http://localhost:5173`). + +4. Enter your OpenAI API Key when prompted. + +5. Interact with the agent! Try commands like: + - "Change the background color to soft blue" + - "Make the canvas a circle" + - "Change the text to Hello Strands!" diff --git a/examples/browser-agent/index.html b/examples/browser-agent/index.html new file mode 100644 index 00000000..2c09acb4 --- /dev/null +++ b/examples/browser-agent/index.html @@ -0,0 +1,140 @@ + + + + + + Strands Browser Agent Example + + + +

Browser Agent Example

+ +
+ I am a canvas. change me! +
+ +
+
+
Hello! I can modify the canvas above. Try asking me "change background to blue" or "make it a circle".
+
+
+ + +
+
+
+ Note: Requires OpenAI API Key. You will be prompted on load if not saved. +
+ + + + diff --git a/examples/browser-agent/package-lock.json b/examples/browser-agent/package-lock.json new file mode 100644 index 00000000..8cffa3a7 --- /dev/null +++ b/examples/browser-agent/package-lock.json @@ -0,0 +1,1079 @@ +{ + "name": "browser-agent-example", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "browser-agent-example", + "version": "0.0.0", + "workspaces": [ + "../../" + ], + "dependencies": { + "@strands-agents/sdk": "*", + "openai": "^6.10.0", + "zod": "4.1.13" + }, + "devDependencies": { + "typescript": "^5.5.0", + "vite": "^5.0.0" + } + }, + "../..": { + "name": "@strands-agents/sdk", + "version": "0.0.1-development", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-bedrock-runtime": "^3.943.0", + "@modelcontextprotocol/sdk": "^1.24.2", + "zod": "^4.1.12" + }, + "devDependencies": { + "@aws-sdk/client-secrets-manager": "^3.943.0", + "@aws-sdk/credential-providers": "^3.943.0", + "@types/json-schema": "^7.0.15", + "@types/node": "^24.6.0", + "@typescript-eslint/eslint-plugin": "^8.48.1", + "@typescript-eslint/parser": "^8.0.0", + "@vitest/browser": "^4.0.15", + "@vitest/browser-playwright": "^4.0.15", + "@vitest/coverage-v8": "^4.0.15", + "eslint": "^9.0.0", + "eslint-plugin-tsdoc": "^0.5.0", + "husky": "^9.1.7", + "playwright": "^1.56.1", + "prettier": "^3.7.4", + "tsx": "^4.21.0", + "typescript": "^5.5.0", + "vitest": "^4.0.8" + }, + "engines": { + "node": ">=20.0.0" + }, + "optionalDependencies": { + "openai": "^6.7.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", + "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz", + "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz", + "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz", + "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz", + "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz", + "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz", + "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz", + "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", + "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz", + "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz", + "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz", + "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz", + "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz", + "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz", + "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz", + "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz", + "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz", + "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz", + "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz", + "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz", + "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz", + "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz", + "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz", + "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz", + "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@strands-agents/sdk": { + "resolved": "../..", + "link": true + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/openai": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.15.0.tgz", + "integrity": "sha512-F1Lvs5BoVvmZtzkUEVyh8mDQPPFolq4F+xdsx/DO8Hee8YF3IGAlZqUIsF+DVGhqf4aU0a3bTghsxB6OIsRy1g==", + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", + "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.55.1", + "@rollup/rollup-android-arm64": "4.55.1", + "@rollup/rollup-darwin-arm64": "4.55.1", + "@rollup/rollup-darwin-x64": "4.55.1", + "@rollup/rollup-freebsd-arm64": "4.55.1", + "@rollup/rollup-freebsd-x64": "4.55.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", + "@rollup/rollup-linux-arm-musleabihf": "4.55.1", + "@rollup/rollup-linux-arm64-gnu": "4.55.1", + "@rollup/rollup-linux-arm64-musl": "4.55.1", + "@rollup/rollup-linux-loong64-gnu": "4.55.1", + "@rollup/rollup-linux-loong64-musl": "4.55.1", + "@rollup/rollup-linux-ppc64-gnu": "4.55.1", + "@rollup/rollup-linux-ppc64-musl": "4.55.1", + "@rollup/rollup-linux-riscv64-gnu": "4.55.1", + "@rollup/rollup-linux-riscv64-musl": "4.55.1", + "@rollup/rollup-linux-s390x-gnu": "4.55.1", + "@rollup/rollup-linux-x64-gnu": "4.55.1", + "@rollup/rollup-linux-x64-musl": "4.55.1", + "@rollup/rollup-openbsd-x64": "4.55.1", + "@rollup/rollup-openharmony-arm64": "4.55.1", + "@rollup/rollup-win32-arm64-msvc": "4.55.1", + "@rollup/rollup-win32-ia32-msvc": "4.55.1", + "@rollup/rollup-win32-x64-gnu": "4.55.1", + "@rollup/rollup-win32-x64-msvc": "4.55.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/zod": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", + "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/examples/browser-agent/package.json b/examples/browser-agent/package.json new file mode 100644 index 00000000..1eec118c --- /dev/null +++ b/examples/browser-agent/package.json @@ -0,0 +1,23 @@ +{ + "name": "browser-agent-example", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@strands-agents/sdk": "*", + "openai": "^6.10.0", + "zod": "4.1.13" + }, + "devDependencies": { + "typescript": "^5.5.0", + "vite": "^5.0.0" + }, + "workspaces": [ + "../../" + ] +} \ No newline at end of file diff --git a/examples/browser-agent/src/index.ts b/examples/browser-agent/src/index.ts new file mode 100644 index 00000000..e83d1489 --- /dev/null +++ b/examples/browser-agent/src/index.ts @@ -0,0 +1,110 @@ +import { Agent } from '@strands-agents/sdk'; +import { OpenAIModel } from '@strands-agents/sdk/openai'; +import { updateCanvasTool } from './tools'; + +const messagesDiv = document.getElementById('messages')!; +const inputForm = document.getElementById('input-area') as HTMLFormElement; +const userInput = document.getElementById('user-input') as HTMLInputElement; +const sendBtn = document.getElementById('send-btn') as HTMLButtonElement; + +// Helper to add message to UI +function addMessage(role: 'user' | 'agent', text: string) { + const div = document.createElement('div'); + div.className = `message ${role}`; + div.textContent = text; + messagesDiv.appendChild(div); + messagesDiv.scrollTop = messagesDiv.scrollHeight; +} + +// Helper to get API Key +async function getApiKey(): Promise { + const savedKey = localStorage.getItem('openai_api_key'); + if (savedKey) return savedKey; + + const key = prompt('Please enter your OpenAI API Key to use this demo (it will be saved in localStorage):'); + if (!key) { + addMessage('agent', '⚠️ APIs Key is required. Please reload and enter a valid key.'); + throw new Error('No API Key provided'); + } + + localStorage.setItem('openai_api_key', key); + return key; +} + + +async function main() { + try { + const apiKey = await getApiKey(); + + // Initialize model + // Note: In a production app, you should proxy requests to avoid exposing keys, + // or use a provider that supports browser-safe authentication. + const model = new OpenAIModel({ + apiKey: apiKey, + modelId: 'gpt-4o', // or gpt-3.5-turbo + clientConfig: { + dangerouslyAllowBrowser: true // OpenAI SDK requires this for browser usage + } + }); + + const agent = new Agent({ + model, + systemPrompt: `You are a creative and helpful browser assistant. +You can modify the style and content of the "canvas" element on the page using the update_canvas tool. +Always use the tool when the user asks for visual changes. +Be concise in your text responses.`, + tools: [updateCanvasTool], + }); + + // Handle user input + inputForm.addEventListener('submit', async (e) => { + e.preventDefault(); + const text = userInput.value.trim(); + if (!text) return; + + addMessage('user', text); + userInput.value = ''; + userInput.disabled = true; + sendBtn.disabled = true; + + try { + // Stream the response + let fullText = ''; + const messageDiv = document.createElement('div'); + messageDiv.className = 'message agent'; + messagesDiv.appendChild(messageDiv); + + // We will update this div as tokens come in + for await (const event of agent.stream(text)) { + if (event.type === 'modelContentBlockDeltaEvent' && event.delta.type === 'textDelta') { + fullText += event.delta.text; + messageDiv.textContent = fullText; + messagesDiv.scrollTop = messagesDiv.scrollHeight; + } + } + + // If the response was empty (e.g. only tool calls), we might want to say something + if (!fullText) { + // Check if we can get the final response from invoke if stream is purely tool based? + // The stream iterator yields events. Tool use events happen, then tool results, then agent response. + // The textDelta accumulates the final answer. + } + + } catch (err) { + console.error(err); + addMessage('agent', 'Error: ' + (err as Error).message); + } finally { + userInput.disabled = false; + sendBtn.disabled = false; + userInput.focus(); + } + }); + + console.log('Agent initialized'); + + } catch (error) { + console.error('Failed to initialize agent:', error); + } +} + +main(); diff --git a/examples/browser-agent/src/tools.ts b/examples/browser-agent/src/tools.ts new file mode 100644 index 00000000..9c89025c --- /dev/null +++ b/examples/browser-agent/src/tools.ts @@ -0,0 +1,59 @@ +import { tool } from '@strands-agents/sdk'; +import { z } from 'zod'; + +export const updateCanvasTool = tool({ + name: 'update_canvas', + description: 'Update the style and content of the canvas element on the page', + inputSchema: z.object({ + backgroundColor: z.string().optional().describe('CSS color for the background'), + textColor: z.string().optional().describe('CSS color for the text'), + text: z.string().optional().describe('Text content to display'), + borderRadius: z.string().optional().describe('CSS border radius value (e.g., "50%" for circle, "0" for square)'), + borderColor: z.string().optional().describe('CSS color for the border'), + width: z.string().optional().describe('CSS width (e.g., "200px")'), + height: z.string().optional().describe('CSS height (e.g., "200px")'), + }), + callback: async (input: any) => { + const canvas = document.getElementById('canvas'); + if (!canvas) { + throw new Error('Canvas element not found'); + } + + const updates: string[] = []; + + if (input.backgroundColor) { + canvas.style.backgroundColor = input.backgroundColor; + updates.push(`background=${input.backgroundColor}`); + } + if (input.textColor) { + canvas.style.color = input.textColor; + updates.push(`color=${input.textColor}`); + } + if (input.text) { + canvas.innerText = input.text; + updates.push(`text="${input.text}"`); + } + if (input.borderRadius) { + canvas.style.borderRadius = input.borderRadius; + updates.push(`borderRadius=${input.borderRadius}`); + } + if (input.borderColor) { + canvas.style.borderColor = input.borderColor; + updates.push(`borderColor=${input.borderColor}`); + } + if (input.width) { + canvas.style.width = input.width; + updates.push(`width=${input.width}`); + } + if (input.height) { + canvas.style.height = input.height; + updates.push(`height=${input.height}`); + } + + if (updates.length === 0) { + return "No changes made."; + } + + return `Canvas updated with: ${updates.join(', ')}`; + }, +}); diff --git a/examples/browser-agent/tsconfig.json b/examples/browser-agent/tsconfig.json new file mode 100644 index 00000000..cf89b51b --- /dev/null +++ b/examples/browser-agent/tsconfig.json @@ -0,0 +1,37 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": [ + "ES2020", + "DOM", + "DOM.Iterable" + ], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "baseUrl": ".", + "paths": { + "@strands-agents/sdk": [ + "../../dist/src/index.d.ts" + ], + "@strands-agents/sdk/openai": [ + "../../dist/src/models/openai.d.ts" + ], + "@strands-agents/sdk/*": [ + "../../dist/src/*" + ] + }, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": [ + "src" + ] +} \ No newline at end of file diff --git a/examples/browser-agent/vite.config.ts b/examples/browser-agent/vite.config.ts new file mode 100644 index 00000000..2bb27a53 --- /dev/null +++ b/examples/browser-agent/vite.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite' +import path from 'path' + +export default defineConfig({ + resolve: { + alias: { + '@strands-agents/sdk/openai': path.resolve(__dirname, '../../src/models/openai.ts'), + '@strands-agents/sdk': path.resolve(__dirname, '../../src/index.ts'), + }, + }, + define: { + 'process.env': {}, + }, +}) From c6a18c0fcb5d8f2aa7124a6bebc124548c759d4a Mon Sep 17 00:00:00 2001 From: Michael Ruelas Date: Fri, 16 Jan 2026 19:39:55 -0800 Subject: [PATCH 2/2] feat: Add Bedrock model support, enhance canvas tool with generic style updates, improve streaming UI for tool calls, and simplify build configuration. --- examples/browser-agent/package.json | 3 +- examples/browser-agent/src/index.ts | 120 +++++++++++++++++++-------- examples/browser-agent/src/tools.ts | 8 ++ examples/browser-agent/tsconfig.json | 37 --------- 4 files changed, 95 insertions(+), 73 deletions(-) delete mode 100644 examples/browser-agent/tsconfig.json diff --git a/examples/browser-agent/package.json b/examples/browser-agent/package.json index 1eec118c..aac2b2df 100644 --- a/examples/browser-agent/package.json +++ b/examples/browser-agent/package.json @@ -5,12 +5,11 @@ "type": "module", "scripts": { "dev": "vite", - "build": "tsc && vite build", + "build": "vite build", "preview": "vite preview" }, "dependencies": { "@strands-agents/sdk": "*", - "openai": "^6.10.0", "zod": "4.1.13" }, "devDependencies": { diff --git a/examples/browser-agent/src/index.ts b/examples/browser-agent/src/index.ts index e83d1489..35160b2c 100644 --- a/examples/browser-agent/src/index.ts +++ b/examples/browser-agent/src/index.ts @@ -1,4 +1,4 @@ -import { Agent } from '@strands-agents/sdk'; +import { Agent, BedrockModel } from '@strands-agents/sdk'; import { OpenAIModel } from '@strands-agents/sdk/openai'; import { updateCanvasTool } from './tools'; @@ -8,44 +8,94 @@ const userInput = document.getElementById('user-input') as HTMLInputElement; const sendBtn = document.getElementById('send-btn') as HTMLButtonElement; // Helper to add message to UI -function addMessage(role: 'user' | 'agent', text: string) { +function addMessage(role: 'user' | 'agent' | 'tool', text: string): HTMLDivElement { const div = document.createElement('div'); div.className = `message ${role}`; div.textContent = text; messagesDiv.appendChild(div); messagesDiv.scrollTop = messagesDiv.scrollHeight; + return div; } -// Helper to get API Key -async function getApiKey(): Promise { - const savedKey = localStorage.getItem('openai_api_key'); - if (savedKey) return savedKey; +// Helper to initialize model based on user selection +async function getModel() { + const savedProvider = localStorage.getItem('agent_provider'); + // Default to openai if not set, but allow changing via prompt if we want to be fancy. + // For simplicity, we'll check if we have keys for one or the other, or ask. - const key = prompt('Please enter your OpenAI API Key to use this demo (it will be saved in localStorage):'); - if (!key) { - addMessage('agent', '⚠️ APIs Key is required. Please reload and enter a valid key.'); - throw new Error('No API Key provided'); + let provider = savedProvider; + if (!provider) { + provider = prompt('Choose model provider: "openai" or "bedrock"', 'openai') || 'openai'; + localStorage.setItem('agent_provider', provider); } - localStorage.setItem('openai_api_key', key); - return key; -} + if (provider.toLowerCase() === 'bedrock') { + const savedRegion = localStorage.getItem('bedrock_region'); + const savedAccessKey = localStorage.getItem('bedrock_access_key'); + const savedSecretKey = localStorage.getItem('bedrock_secret_key'); + + if (savedRegion && savedAccessKey && savedSecretKey) { + return new BedrockModel({ + region: savedRegion, + clientConfig: { + credentials: { + accessKeyId: savedAccessKey, + secretAccessKey: savedSecretKey + } + } + }); + } + const region = prompt('Enter AWS Region (e.g., us-west-2):', 'us-west-2'); + const accessKey = prompt('Enter AWS Access Key ID:'); + const secretKey = prompt('Enter AWS Secret Access Key:'); -async function main() { - try { - const apiKey = await getApiKey(); - - // Initialize model - // Note: In a production app, you should proxy requests to avoid exposing keys, - // or use a provider that supports browser-safe authentication. - const model = new OpenAIModel({ - apiKey: apiKey, - modelId: 'gpt-4o', // or gpt-3.5-turbo + if (!region || !accessKey || !secretKey) { + addMessage('agent', '⚠️ AWS Credentials are required for Bedrock. Please reload.'); + throw new Error('Missing AWS Credentials'); + } + + localStorage.setItem('bedrock_region', region); + localStorage.setItem('bedrock_access_key', accessKey); + localStorage.setItem('bedrock_secret_key', secretKey); + + return new BedrockModel({ + region, clientConfig: { - dangerouslyAllowBrowser: true // OpenAI SDK requires this for browser usage + credentials: { + accessKeyId: accessKey, + secretAccessKey: secretKey + } } }); + } else { + // OpenAI default + const savedKey = localStorage.getItem('openai_api_key'); + let apiKey = savedKey; + + if (!apiKey) { + apiKey = prompt('Please enter your OpenAI API Key (saved in localStorage):'); + if (!apiKey) { + addMessage('agent', '⚠️ API Key is required. Please reload.'); + throw new Error('No API Key provided'); + } + localStorage.setItem('openai_api_key', apiKey); + } + + return new OpenAIModel({ + apiKey: apiKey!, + modelId: 'gpt-4o', + clientConfig: { + dangerouslyAllowBrowser: true + } + }); + } +} + + +async function main() { + try { + const model = await getModel(); const agent = new Agent({ model, @@ -70,9 +120,7 @@ Be concise in your text responses.`, try { // Stream the response let fullText = ''; - const messageDiv = document.createElement('div'); - messageDiv.className = 'message agent'; - messagesDiv.appendChild(messageDiv); + const messageDiv = addMessage('agent', ''); // We will update this div as tokens come in for await (const event of agent.stream(text)) { @@ -80,16 +128,20 @@ Be concise in your text responses.`, fullText += event.delta.text; messageDiv.textContent = fullText; messagesDiv.scrollTop = messagesDiv.scrollHeight; + } else if (event.type === 'modelContentBlockStartEvent' && event.start.type === 'toolUseStart') { + // Indicate tool use + const toolMsg = document.createElement('div'); + toolMsg.className = 'message tool'; + toolMsg.style.fontSize = '0.8em'; + toolMsg.style.color = '#666'; + toolMsg.textContent = `🛠️ Using tool: ${event.start.name}...`; + // Insert before the current agent message or append? + // If we append, it might appear after partial text. + // Let's just append it to the main container for now. + messagesDiv.insertBefore(toolMsg, messageDiv); } } - // If the response was empty (e.g. only tool calls), we might want to say something - if (!fullText) { - // Check if we can get the final response from invoke if stream is purely tool based? - // The stream iterator yields events. Tool use events happen, then tool results, then agent response. - // The textDelta accumulates the final answer. - } - } catch (err) { console.error(err); addMessage('agent', 'Error: ' + (err as Error).message); diff --git a/examples/browser-agent/src/tools.ts b/examples/browser-agent/src/tools.ts index 9c89025c..bc70c832 100644 --- a/examples/browser-agent/src/tools.ts +++ b/examples/browser-agent/src/tools.ts @@ -12,6 +12,7 @@ export const updateCanvasTool = tool({ borderColor: z.string().optional().describe('CSS color for the border'), width: z.string().optional().describe('CSS width (e.g., "200px")'), height: z.string().optional().describe('CSS height (e.g., "200px")'), + style: z.record(z.string()).optional().describe('JSON object containing CSS properties to apply to the canvas element (e.g. {"backgroundColor": "red", "fontSize": "20px"}). This allows you to set any CSS property.'), }), callback: async (input: any) => { const canvas = document.getElementById('canvas'); @@ -21,6 +22,7 @@ export const updateCanvasTool = tool({ const updates: string[] = []; + // Apply specific properties if provided if (input.backgroundColor) { canvas.style.backgroundColor = input.backgroundColor; updates.push(`background=${input.backgroundColor}`); @@ -50,6 +52,12 @@ export const updateCanvasTool = tool({ updates.push(`height=${input.height}`); } + // Apply raw styles if provided + if (input.style) { + Object.assign(canvas.style, input.style); + updates.push(`styleObject=${JSON.stringify(input.style)}`); + } + if (updates.length === 0) { return "No changes made."; } diff --git a/examples/browser-agent/tsconfig.json b/examples/browser-agent/tsconfig.json deleted file mode 100644 index cf89b51b..00000000 --- a/examples/browser-agent/tsconfig.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "module": "ESNext", - "lib": [ - "ES2020", - "DOM", - "DOM.Iterable" - ], - "skipLibCheck": true, - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "baseUrl": ".", - "paths": { - "@strands-agents/sdk": [ - "../../dist/src/index.d.ts" - ], - "@strands-agents/sdk/openai": [ - "../../dist/src/models/openai.d.ts" - ], - "@strands-agents/sdk/*": [ - "../../dist/src/*" - ] - }, - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": [ - "src" - ] -} \ No newline at end of file