{"_id":"@ag-ui/aws-strands","_rev":"9-3b2d11c63b69736966abf2e7d07c50a7","name":"@ag-ui/aws-strands","dist-tags":{"latest":"0.3.0","canary":"0.2.4-canary.1789125038.0"},"versions":{"0.1.0":{"name":"@ag-ui/aws-strands","version":"0.1.0","author":{"name":"AG-UI Contributors"},"_id":"@ag-ui/aws-strands@0.1.0","maintainers":[{"name":"_mme","email":"markus.ecker@gmail.com"},{"name":"copilotkit","email":"devops@copilotkit.ai"}],"dist":{"shasum":"ff1035025f334ea73f7823a5e8ad31846cf6fe3c","tarball":"https://registry.npmjs.org/@ag-ui/aws-strands/-/aws-strands-0.1.0.tgz","fileCount":23,"integrity":"sha512-OQKEuhcvaN9b8K875c89zCrK8mjqOfMllAkr0UYmW3k6ff6EvkQQtAUz/aRCvb8xKE/xdC+XDb2WMCZPRdDP4g==","signatures":[{"sig":"MEUCIQD+Jtd/MomIFocD/gJJsYbFA9BRavAQ7PlI+pnd/L9kcwIgCMc/lv8TBKqp3qOXCB1KsFzpFXzxKCQci+7LlZWd2Tk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":515719},"main":"./dist/index.js","_from":"file:ag-ui-aws-strands-0.1.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.mjs","exports":{".":{"import":"./dist/index.mjs","require":"./dist/index.js"},"./server":{"import":"./dist/server.mjs","require":"./dist/server.js"},"./package.json":"./package.json"},"scripts":{"dev":"tsdown --watch","test":"vitest run","build":"tsdown","clean":"git clean -fdX --exclude=\"!.env\"","typecheck":"tsc --noEmit","test:watch":"vitest","link:global":"pnpm link --global","test:exports":"publint --strict && attw --pack","test:coverage":"vitest run --coverage","unlink:global":"pnpm unlink --global"},"_npmUser":{"name":"copilotkit","email":"devops@copilotkit.ai"},"_resolved":"/private/var/folders/hy/p91nn66967g2s_vdv76_3j9c0000gn/T/6ba243682e3c0d61a8d0ccb15b55103d/ag-ui-aws-strands-0.1.0.tgz","_integrity":"sha512-OQKEuhcvaN9b8K875c89zCrK8mjqOfMllAkr0UYmW3k6ff6EvkQQtAUz/aRCvb8xKE/xdC+XDb2WMCZPRdDP4g==","_npmVersion":"10.9.3","description":"AWS Strands Agents integration for the AG-UI protocol","directories":{},"sideEffects":false,"_nodeVersion":"22.18.0","publishConfig":{"access":"public"},"typesVersions":{"*":{"server":["dist/server.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"zod":"^4.4.3","cors":"^2.8.5","tsdown":"^0.20.1","vitest":"^4.0.18","express":"^5.0.0","publint":"^0.3.12","typescript":"^5.3.3","@ag-ui/core":"0.0.53","@types/cors":"^2.8.17","@types/node":"^20.11.19","@ag-ui/client":"0.0.53","@ag-ui/encoder":"0.0.53","@types/express":"^5.0.0","@strands-agents/sdk":"^1.1.0","@arethetypeswrong/cli":"^0.17.4","@modelcontextprotocol/sdk":"^1.29.0","@vitest/coverage-istanbul":"^4.0.18"},"peerDependencies":{"cors":"^2.8.5","express":"^4.18.0 || ^5.0.0","@ag-ui/core":">=0.0.37","@ag-ui/client":">=0.0.37","@ag-ui/encoder":">=0.0.37","@strands-agents/sdk":">=1.1.0","@modelcontextprotocol/sdk":">=1.0.0"},"peerDependenciesMeta":{"cors":{"optional":true},"express":{"optional":true},"@modelcontextprotocol/sdk":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/aws-strands_0.1.0_1779809416705_0.12069512311638597","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@ag-ui/aws-strands","version":"0.2.0","author":{"name":"AG-UI Contributors"},"_id":"@ag-ui/aws-strands@0.2.0","maintainers":[{"name":"_mme","email":"markus.ecker@gmail.com"},{"name":"copilotkit","email":"devops@copilotkit.ai"}],"homepage":"https://github.com/ag-ui-protocol/ag-ui#readme","bugs":{"url":"https://github.com/ag-ui-protocol/ag-ui/issues"},"dist":{"shasum":"5aac20191d64bb396d4d05df839c46a9eb688568","tarball":"https://registry.npmjs.org/@ag-ui/aws-strands/-/aws-strands-0.2.0.tgz","fileCount":23,"integrity":"sha512-/McoYhCxl+tslUq5l1AF86kJR8YjpaU3Ro7uLz/u6Uk8/M5AglYDN4zdW1xh1o7vZrJ0gYPQ63MMsU9FbJRCFw==","signatures":[{"sig":"MEQCIHNzTT13EKP1cAw1FHbqUMqp0E2/E2k5ZXD6RpvBnfQtAiA+QjE8Jn8UwFmm9RkzriPS7ipFYQTZE/lPH/0hwvvc+w==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@ag-ui%2faws-strands@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":642180},"main":"./dist/index.js","_from":"file:ag-ui-aws-strands-0.2.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.mjs","exports":{".":{"import":"./dist/index.mjs","require":"./dist/index.js"},"./server":{"import":"./dist/server.mjs","require":"./dist/server.js"},"./package.json":"./package.json"},"scripts":{"dev":"tsdown --watch","test":"vitest run","build":"tsdown","clean":"git clean -fdX --exclude=\"!.env\"","typecheck":"tsc --noEmit","test:watch":"vitest","link:global":"pnpm link --global","test:exports":"publint --strict && attw --pack","test:coverage":"vitest run --coverage","unlink:global":"pnpm unlink --global"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:868a4e3e-c050-4174-873e-2aae9576d5c0"}},"_resolved":"/home/runner/work/ag-ui/ag-ui/integrations/aws-strands/typescript/ag-ui-aws-strands-0.2.0.tgz","_integrity":"sha512-/McoYhCxl+tslUq5l1AF86kJR8YjpaU3Ro7uLz/u6Uk8/M5AglYDN4zdW1xh1o7vZrJ0gYPQ63MMsU9FbJRCFw==","repository":{"url":"git+https://github.com/ag-ui-protocol/ag-ui.git","type":"git"},"_npmVersion":"11.15.0","description":"AWS Strands Agents integration for the AG-UI protocol","directories":{},"sideEffects":false,"_nodeVersion":"22.22.3","publishConfig":{"access":"public"},"typesVersions":{"*":{"server":["dist/server.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"zod":"^4.4.3","cors":"^2.8.5","tsdown":"^0.20.1","vitest":"^4.0.18","express":"^5.0.0","publint":"^0.3.12","typescript":"^5.3.3","@ag-ui/core":"0.0.57","@types/cors":"^2.8.17","@types/node":"^20.11.19","@ag-ui/client":"0.0.57","@ag-ui/encoder":"0.0.57","@types/express":"^5.0.0","@ag-ui/a2ui-toolkit":"0.0.3","@strands-agents/sdk":"^1.1.0","@arethetypeswrong/cli":"^0.17.4","@modelcontextprotocol/sdk":"^1.29.0","@vitest/coverage-istanbul":"^4.0.18"},"peerDependencies":{"cors":"^2.8.5","express":"^4.18.0 || ^5.0.0","@ag-ui/core":">=0.0.37","@ag-ui/client":">=0.0.37","@ag-ui/encoder":">=0.0.37","@ag-ui/a2ui-toolkit":">=0.0.3","@strands-agents/sdk":">=1.1.0","@modelcontextprotocol/sdk":">=1.0.0"},"peerDependenciesMeta":{"cors":{"optional":true},"express":{"optional":true},"@modelcontextprotocol/sdk":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/aws-strands_0.2.0_1781515833669_0.41303805720295195","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@ag-ui/aws-strands","version":"0.2.1","author":{"name":"AG-UI Contributors"},"license":"MIT","_id":"@ag-ui/aws-strands@0.2.1","maintainers":[{"name":"_mme","email":"markus.ecker@gmail.com"},{"name":"copilotkit","email":"devops@copilotkit.ai"}],"homepage":"https://github.com/ag-ui-protocol/ag-ui#readme","bugs":{"url":"https://github.com/ag-ui-protocol/ag-ui/issues"},"dist":{"shasum":"942cb3c5edde00aa315307a7b77ac19f275628c8","tarball":"https://registry.npmjs.org/@ag-ui/aws-strands/-/aws-strands-0.2.1.tgz","fileCount":23,"integrity":"sha512-+FivefphEIvjgLRfFH1OrBrKVe74SZIyGPWEz7Tih4h8bETLyv5RYnp2n4IT2YTTbai+dI6R05/B6dKkcIV+BQ==","signatures":[{"sig":"MEQCIEMcb2vaxroQ/ID4VtZ36zQ5c1bku7tulfthPBZGs8sWAiAHBR2LG5aqmDejaGuty5K+ttniqQZ39HhIxrzqSzfHZA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@ag-ui%2faws-strands@0.2.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":644556},"main":"./dist/index.js","_from":"file:ag-ui-aws-strands-0.2.1.tgz","types":"./dist/index.d.ts","module":"./dist/index.mjs","exports":{".":{"import":"./dist/index.mjs","require":"./dist/index.js"},"./server":{"import":"./dist/server.mjs","require":"./dist/server.js"},"./package.json":"./package.json"},"scripts":{"dev":"tsdown --watch","test":"vitest run","build":"tsdown","clean":"git clean -fdX --exclude=\"!.env\"","typecheck":"tsc --noEmit","test:watch":"vitest","link:global":"pnpm link --global","test:exports":"publint --strict && attw --pack","test:coverage":"vitest run --coverage","unlink:global":"pnpm unlink --global"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:868a4e3e-c050-4174-873e-2aae9576d5c0"}},"_resolved":"/home/runner/work/ag-ui/ag-ui/integrations/aws-strands/typescript/ag-ui-aws-strands-0.2.1.tgz","_integrity":"sha512-+FivefphEIvjgLRfFH1OrBrKVe74SZIyGPWEz7Tih4h8bETLyv5RYnp2n4IT2YTTbai+dI6R05/B6dKkcIV+BQ==","repository":{"url":"git+https://github.com/ag-ui-protocol/ag-ui.git","type":"git"},"_npmVersion":"11.15.0","description":"AWS Strands Agents integration for the AG-UI protocol","directories":{},"sideEffects":false,"_nodeVersion":"22.22.3","publishConfig":{"access":"public"},"typesVersions":{"*":{"server":["dist/server.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"zod":"^4.4.3","cors":"^2.8.5","tsdown":"^0.20.1","vitest":"^4.0.18","express":"^5.0.0","publint":"^0.3.12","typescript":"^5.3.3","@ag-ui/core":"0.0.57","@types/cors":"^2.8.17","@types/node":"^20.11.19","@ag-ui/client":"0.0.57","@ag-ui/encoder":"0.0.57","@types/express":"^5.0.0","@ag-ui/a2ui-toolkit":"0.0.4","@strands-agents/sdk":"^1.1.0","@arethetypeswrong/cli":"^0.17.4","@modelcontextprotocol/sdk":"^1.29.0","@vitest/coverage-istanbul":"^4.0.18"},"peerDependencies":{"cors":"^2.8.5","express":"^4.18.0 || ^5.0.0","@ag-ui/core":">=0.0.37","@ag-ui/client":">=0.0.37","@ag-ui/encoder":">=0.0.37","@ag-ui/a2ui-toolkit":">=0.0.3","@strands-agents/sdk":">=1.1.0","@modelcontextprotocol/sdk":">=1.0.0"},"peerDependenciesMeta":{"cors":{"optional":true},"express":{"optional":true},"@modelcontextprotocol/sdk":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/aws-strands_0.2.1_1781881252722_0.25677819916712763","host":"s3://npm-registry-packages-npm-production"}},"0.2.2":{"name":"@ag-ui/aws-strands","version":"0.2.2","author":{"name":"AG-UI Contributors"},"license":"MIT","_id":"@ag-ui/aws-strands@0.2.2","maintainers":[{"name":"_mme","email":"markus.ecker@gmail.com"},{"name":"copilotkit","email":"devops@copilotkit.ai"}],"homepage":"https://github.com/ag-ui-protocol/ag-ui#readme","bugs":{"url":"https://github.com/ag-ui-protocol/ag-ui/issues"},"dist":{"shasum":"c8b48adc456f4ce8cd87159f49c497fee3399c94","tarball":"https://registry.npmjs.org/@ag-ui/aws-strands/-/aws-strands-0.2.2.tgz","fileCount":23,"integrity":"sha512-LXoQz/XO8Ixt5VZKwZ7LJm/zMR+6z9TWHFF+7nWsbjrWDifA3BQwGvuvAhW3Za7EHUihec29vArqbQabtVE20g==","signatures":[{"sig":"MEUCIEspO+KdclSMnBrkxIfcjMqQVVIvxghVm04a2VZBu451AiEAhFw5JilZZGOWZurxIDE6t/AZelZBe+SwUzvmZGf6Ym4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@ag-ui%2faws-strands@0.2.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":651148},"main":"./dist/index.js","_from":"file:ag-ui-aws-strands-0.2.2.tgz","types":"./dist/index.d.ts","module":"./dist/index.mjs","exports":{".":{"import":"./dist/index.mjs","require":"./dist/index.js"},"./server":{"import":"./dist/server.mjs","require":"./dist/server.js"},"./package.json":"./package.json"},"scripts":{"dev":"tsdown --watch","test":"vitest run","build":"tsdown","clean":"git clean -fdX --exclude=\"!.env\"","typecheck":"tsc --noEmit","test:watch":"vitest","link:global":"pnpm link --global","test:exports":"publint --strict && attw --pack","test:coverage":"vitest run --coverage","unlink:global":"pnpm unlink --global"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:868a4e3e-c050-4174-873e-2aae9576d5c0"}},"_resolved":"/home/runner/work/ag-ui/ag-ui/integrations/aws-strands/typescript/ag-ui-aws-strands-0.2.2.tgz","_integrity":"sha512-LXoQz/XO8Ixt5VZKwZ7LJm/zMR+6z9TWHFF+7nWsbjrWDifA3BQwGvuvAhW3Za7EHUihec29vArqbQabtVE20g==","repository":{"url":"git+https://github.com/ag-ui-protocol/ag-ui.git","type":"git"},"_npmVersion":"11.15.0","description":"AWS Strands Agents integration for the AG-UI protocol","directories":{},"sideEffects":false,"_nodeVersion":"22.22.3","publishConfig":{"access":"public"},"typesVersions":{"*":{"server":["dist/server.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"zod":"^4.4.3","cors":"^2.8.5","rxjs":"7.8.1","tsdown":"^0.20.1","vitest":"^4.0.18","express":"^5.0.0","publint":"^0.3.12","typescript":"^5.3.3","@ag-ui/core":"0.0.57","@types/cors":"^2.8.17","@types/node":"^20.11.19","@ag-ui/client":"0.0.57","@ag-ui/encoder":"0.0.57","@types/express":"^5.0.0","@ag-ui/a2ui-toolkit":"0.0.4","@strands-agents/sdk":"^1.1.0","@arethetypeswrong/cli":"^0.17.4","@modelcontextprotocol/sdk":"^1.29.0","@vitest/coverage-istanbul":"^4.0.18"},"peerDependencies":{"cors":"^2.8.5","express":"^4.18.0 || ^5.0.0","@ag-ui/core":">=0.0.37","@ag-ui/client":">=0.0.37","@ag-ui/encoder":">=0.0.37","@ag-ui/a2ui-toolkit":">=0.0.3","@strands-agents/sdk":">=1.1.0","@modelcontextprotocol/sdk":">=1.0.0"},"peerDependenciesMeta":{"cors":{"optional":true},"express":{"optional":true},"@modelcontextprotocol/sdk":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/aws-strands_0.2.2_1782223452748_0.7428428130617166","host":"s3://npm-registry-packages-npm-production"}},"0.2.3":{"name":"@ag-ui/aws-strands","version":"0.2.3","author":{"name":"AG-UI Contributors"},"license":"MIT","_id":"@ag-ui/aws-strands@0.2.3","maintainers":[{"name":"_mme","email":"markus.ecker@gmail.com"},{"name":"copilotkit","email":"devops@copilotkit.ai"}],"homepage":"https://github.com/ag-ui-protocol/ag-ui#readme","bugs":{"url":"https://github.com/ag-ui-protocol/ag-ui/issues"},"dist":{"shasum":"9a3943df2203f474afe567f021c17b65e203802c","tarball":"https://registry.npmjs.org/@ag-ui/aws-strands/-/aws-strands-0.2.3.tgz","fileCount":23,"integrity":"sha512-1POb3rah4e2uA6aSZGinQPoYA2XLgWebLsypAYkAzPOLrOPJEir8TcOUdgby2H1Z7loSBHBLqDZjQ5WUBv2BxA==","signatures":[{"sig":"MEUCIQCKr3hbT57GrAmpqELvKtty6SG4IOceH1KSSdgx8bpF1AIgMtUkjoHWJajOsaL4F8TOwVZVS2PYLvedBNRxr2zweog=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@ag-ui%2faws-strands@0.2.3","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":650152},"main":"./dist/index.js","_from":"file:ag-ui-aws-strands-0.2.3.tgz","types":"./dist/index.d.ts","module":"./dist/index.mjs","exports":{".":{"import":"./dist/index.mjs","require":"./dist/index.js"},"./server":{"import":"./dist/server.mjs","require":"./dist/server.js"},"./package.json":"./package.json"},"scripts":{"dev":"tsdown --watch","test":"vitest run","build":"tsdown","clean":"git clean -fdX --exclude=\"!.env\"","typecheck":"tsc --noEmit","test:watch":"vitest","link:global":"pnpm link --global","test:exports":"publint --strict && attw --pack","test:coverage":"vitest run --coverage","unlink:global":"pnpm unlink --global"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:868a4e3e-c050-4174-873e-2aae9576d5c0"}},"_resolved":"/home/runner/work/ag-ui/ag-ui/integrations/aws-strands/typescript/ag-ui-aws-strands-0.2.3.tgz","_integrity":"sha512-1POb3rah4e2uA6aSZGinQPoYA2XLgWebLsypAYkAzPOLrOPJEir8TcOUdgby2H1Z7loSBHBLqDZjQ5WUBv2BxA==","repository":{"url":"git+https://github.com/ag-ui-protocol/ag-ui.git","type":"git"},"_npmVersion":"11.15.0","description":"AWS Strands Agents integration for the AG-UI protocol","directories":{},"sideEffects":false,"_nodeVersion":"22.23.0","publishConfig":{"access":"public"},"typesVersions":{"*":{"server":["dist/server.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"zod":"^4.4.3","cors":"^2.8.5","rxjs":"7.8.1","tsdown":"^0.20.1","vitest":"^4.0.18","express":"^5.0.0","publint":"^0.3.12","typescript":"^5.3.3","@ag-ui/core":"0.0.57","@types/cors":"^2.8.17","@types/node":"^20.11.19","@ag-ui/client":"0.0.57","@ag-ui/encoder":"0.0.57","@types/express":"^5.0.0","@ag-ui/a2ui-toolkit":"0.0.4","@strands-agents/sdk":"^1.1.0","@arethetypeswrong/cli":"^0.17.4","@modelcontextprotocol/sdk":"^1.29.0","@vitest/coverage-istanbul":"^4.0.18"},"peerDependencies":{"cors":"^2.8.5","express":"^4.18.0 || ^5.0.0","@ag-ui/core":">=0.0.37","@ag-ui/client":">=0.0.37","@ag-ui/encoder":">=0.0.37","@ag-ui/a2ui-toolkit":">=0.0.3","@strands-agents/sdk":">=1.1.0","@modelcontextprotocol/sdk":">=1.0.0"},"peerDependenciesMeta":{"cors":{"optional":true},"express":{"optional":true},"@modelcontextprotocol/sdk":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/aws-strands_0.2.3_1782313655809_0.14690923733407169","host":"s3://npm-registry-packages-npm-production"}},"0.2.4-canary.1788531199.0":{"name":"@ag-ui/aws-strands","version":"0.2.4-canary.1788531199.0","author":{"name":"AG-UI Contributors"},"license":"MIT","_id":"@ag-ui/aws-strands@0.2.4-canary.1788531199.0","maintainers":[{"name":"_mme","email":"markus.ecker@gmail.com"},{"name":"copilotkit","email":"devops@copilotkit.ai"}],"homepage":"https://github.com/ag-ui-protocol/ag-ui#readme","bugs":{"url":"https://github.com/ag-ui-protocol/ag-ui/issues"},"dist":{"shasum":"9761739ca6d7be29c9b96120906a93ba3085d1d9","tarball":"https://registry.npmjs.org/@ag-ui/aws-strands/-/aws-strands-0.2.4-canary.1788531199.0.tgz","fileCount":27,"integrity":"sha512-HSClRrvwkYOEI2Cd0SHkVH8LG6UFWNBz3zPyK+Fd6TJ7nXqgd/Wwucuuj2JjK2NcSXfnRcFv8uO9yW8xbJzzPw==","signatures":[{"sig":"MEUCICwAGvnnpJ7Rpayw4WUpGqSxcSBX7tLANL7luSySFEJyAiEA0nDWSe+wOup9Jjuzu1AleNhCLye99w3Q7UHwe/xiJIA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@ag-ui%2faws-strands@0.2.4-canary.1788531199.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1902927},"main":"./dist/index.js","_from":"file:ag-ui-aws-strands-0.2.4-canary.1788531199.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.mjs","exports":{".":{"import":"./dist/index.mjs","require":"./dist/index.js"},"./server":{"import":"./dist/server.mjs","require":"./dist/server.js"},"./package.json":"./package.json"},"scripts":{"dev":"tsdown --watch","test":"vitest run","build":"tsdown","clean":"git clean -fdX --exclude=\"!.env\"","typecheck":"tsc --noEmit","test:watch":"vitest","link:global":"pnpm link --global","test:exports":"publint --strict && attw --pack","test:coverage":"vitest run --coverage","unlink:global":"pnpm unlink --global"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:868a4e3e-c050-4174-873e-2aae9576d5c0"}},"_resolved":"/home/runner/work/ag-ui/ag-ui/integrations/aws-strands/typescript/ag-ui-aws-strands-0.2.4-canary.1788531199.0.tgz","_integrity":"sha512-HSClRrvwkYOEI2Cd0SHkVH8LG6UFWNBz3zPyK+Fd6TJ7nXqgd/Wwucuuj2JjK2NcSXfnRcFv8uO9yW8xbJzzPw==","repository":{"url":"git+https://github.com/ag-ui-protocol/ag-ui.git","type":"git"},"_npmVersion":"11.15.0","description":"AWS Strands Agents integration for the AG-UI protocol","directories":{},"sideEffects":false,"_nodeVersion":"22.23.2","publishConfig":{"access":"public"},"typesVersions":{"*":{"server":["dist/server.d.ts"]}},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"zod":"^4.4.3","cors":"^2.8.5","rxjs":"7.8.1","tsdown":"^0.20.1","vitest":"^4.0.18","express":"^5.0.0","publint":"^0.3.12","typescript":"^5.3.3","@ag-ui/core":"0.0.59","@types/cors":"^2.8.17","@types/node":"^20.11.19","@ag-ui/client":"0.0.59","@ag-ui/encoder":"0.0.59","@types/express":"^5.0.0","@opentelemetry/api":"^1.9.0","@ag-ui/a2ui-toolkit":"0.0.4","@strands-agents/sdk":"^1.1.0","@arethetypeswrong/cli":"^0.17.4","@modelcontextprotocol/sdk":"^1.29.0","@vitest/coverage-istanbul":"^4.0.18"},"peerDependencies":{"cors":"^2.8.5","express":"^4.18.0 || ^5.0.0","@ag-ui/core":">=0.0.59","@ag-ui/client":">=0.0.59","@ag-ui/encoder":">=0.0.59","@ag-ui/a2ui-toolkit":">=0.0.3","@strands-agents/sdk":">=1.1.0","@modelcontextprotocol/sdk":">=1.0.0"},"peerDependenciesMeta":{"cors":{"optional":true},"express":{"optional":true},"@modelcontextprotocol/sdk":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/aws-strands_0.2.4-canary.1788531199.0_1788531343556_0.9965711647274587","host":"s3://npm-registry-packages-npm-production"}},"0.2.4-canary.1789051242.0":{"name":"@ag-ui/aws-strands","version":"0.2.4-canary.1789051242.0","author":{"name":"AG-UI Contributors"},"license":"MIT","_id":"@ag-ui/aws-strands@0.2.4-canary.1789051242.0","maintainers":[{"name":"_mme","email":"markus.ecker@gmail.com"},{"name":"copilotkit","email":"devops@copilotkit.ai"}],"homepage":"https://github.com/ag-ui-protocol/ag-ui#readme","bugs":{"url":"https://github.com/ag-ui-protocol/ag-ui/issues"},"dist":{"shasum":"6f34584a29ff7d12a864b6b218807374b4c26d33","tarball":"https://registry.npmjs.org/@ag-ui/aws-strands/-/aws-strands-0.2.4-canary.1789051242.0.tgz","fileCount":27,"integrity":"sha512-AfWiPyliLmJimz7wZK1nmj20eAYJHpz8J1kw9duvY4uET1UTTkaXD2qUIFvylBRE7SuRoEVU1PwTg92F9sI3bg==","signatures":[{"sig":"MEUCIFGQuUeeIgKDX5xpFbsfaeiq9BD9HBX1LlOz8dlrPQ0PAiEAj9AADv27YoHWxrAbdyLUMJpcRwawZXYpu7EaPyhUwu4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@ag-ui%2faws-strands@0.2.4-canary.1789051242.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1918773},"main":"./dist/index.js","_from":"file:ag-ui-aws-strands-0.2.4-canary.1789051242.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.mjs","exports":{".":{"import":"./dist/index.mjs","require":"./dist/index.js"},"./server":{"import":"./dist/server.mjs","require":"./dist/server.js"},"./package.json":"./package.json"},"scripts":{"dev":"tsdown --watch","test":"vitest run","build":"tsdown","clean":"git clean -fdX --exclude=\"!.env\"","typecheck":"tsc --noEmit","test:watch":"vitest","link:global":"pnpm link --global","test:exports":"publint --strict && attw --pack","test:coverage":"vitest run --coverage","unlink:global":"pnpm unlink --global"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:868a4e3e-c050-4174-873e-2aae9576d5c0"}},"_resolved":"/home/runner/work/ag-ui/ag-ui/integrations/aws-strands/typescript/ag-ui-aws-strands-0.2.4-canary.1789051242.0.tgz","_integrity":"sha512-AfWiPyliLmJimz7wZK1nmj20eAYJHpz8J1kw9duvY4uET1UTTkaXD2qUIFvylBRE7SuRoEVU1PwTg92F9sI3bg==","repository":{"url":"git+https://github.com/ag-ui-protocol/ag-ui.git","type":"git"},"_npmVersion":"11.15.0","description":"AWS Strands Agents integration for the AG-UI protocol","directories":{},"sideEffects":false,"_nodeVersion":"22.23.2","publishConfig":{"access":"public"},"typesVersions":{"*":{"server":["dist/server.d.ts"]}},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"zod":"^4.4.3","cors":"^2.8.5","rxjs":"7.8.1","openai":"^6.10.0","tsdown":"^0.20.1","vitest":"^4.0.18","express":"^5.0.0","publint":"^0.3.12","typescript":"^5.3.3","@ag-ui/core":"0.0.59","@types/cors":"^2.8.17","@types/node":"^20.11.19","@ag-ui/client":"0.0.59","@ag-ui/encoder":"0.0.59","@types/express":"^5.0.0","@opentelemetry/api":"^1.9.0","@ag-ui/a2ui-toolkit":"0.0.4","@strands-agents/sdk":"^1.1.0","@arethetypeswrong/cli":"^0.17.4","@modelcontextprotocol/sdk":"^1.29.0","@vitest/coverage-istanbul":"^4.0.18"},"peerDependencies":{"cors":"^2.8.5","express":"^4.18.0 || ^5.0.0","@ag-ui/core":">=0.0.59","@ag-ui/client":">=0.0.59","@ag-ui/encoder":">=0.0.59","@ag-ui/a2ui-toolkit":">=0.0.3","@strands-agents/sdk":">=1.1.0","@modelcontextprotocol/sdk":">=1.0.0"},"peerDependenciesMeta":{"cors":{"optional":true},"express":{"optional":true},"@modelcontextprotocol/sdk":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/aws-strands_0.2.4-canary.1789051242.0_1789051386521_0.5322270340275077","host":"s3://npm-registry-packages-npm-production"}},"0.2.4-canary.1789125038.0":{"name":"@ag-ui/aws-strands","version":"0.2.4-canary.1789125038.0","author":{"name":"AG-UI Contributors"},"license":"MIT","_id":"@ag-ui/aws-strands@0.2.4-canary.1789125038.0","maintainers":[{"name":"_mme","email":"markus.ecker@gmail.com"},{"name":"copilotkit","email":"devops@copilotkit.ai"}],"homepage":"https://github.com/ag-ui-protocol/ag-ui#readme","bugs":{"url":"https://github.com/ag-ui-protocol/ag-ui/issues"},"dist":{"shasum":"ed1911efd1f7e7b81869a8503934217d7d78aefc","tarball":"https://registry.npmjs.org/@ag-ui/aws-strands/-/aws-strands-0.2.4-canary.1789125038.0.tgz","fileCount":27,"integrity":"sha512-uZZ26qNuAZhjna6PdYHLIswBb3Kj/i4y1/i+9tSA2XchZwlKO2mI7vaxy7/vlhQMc8IzJiOfYbIkOf9edhtTDA==","signatures":[{"sig":"MEQCIAykaxPYtAH6DRKXox3cH6nkt+eOdqiAw8ieD3/XZkPGAiBY+eB1GgnkHmFZXgvoEpFAewRYw+qvMg/xeyjA0nOLag==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEYCIQDDtMkuFUoyf2V7/+VCF3Z3Eb5jk+wQ1O+Sdv0dXiktXgIhAPVwzW6aaLmj8GUlDNX45lQ3GVMIZST3OCLfTNSWdovC","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@ag-ui%2faws-strands@0.2.4-canary.1789125038.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1919045},"main":"./dist/index.js","_from":"file:ag-ui-aws-strands-0.2.4-canary.1789125038.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.mjs","exports":{".":{"import":"./dist/index.mjs","require":"./dist/index.js"},"./server":{"import":"./dist/server.mjs","require":"./dist/server.js"},"./package.json":"./package.json"},"scripts":{"dev":"tsdown --watch","test":"vitest run","build":"tsdown","clean":"git clean -fdX --exclude=\"!.env\"","typecheck":"tsc --noEmit","test:watch":"vitest","link:global":"pnpm link --global","test:exports":"publint --strict && attw --pack","test:coverage":"vitest run --coverage","unlink:global":"pnpm unlink --global"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:868a4e3e-c050-4174-873e-2aae9576d5c0"}},"_resolved":"/home/runner/work/ag-ui/ag-ui/integrations/aws-strands/typescript/ag-ui-aws-strands-0.2.4-canary.1789125038.0.tgz","_integrity":"sha512-uZZ26qNuAZhjna6PdYHLIswBb3Kj/i4y1/i+9tSA2XchZwlKO2mI7vaxy7/vlhQMc8IzJiOfYbIkOf9edhtTDA==","repository":{"url":"git+https://github.com/ag-ui-protocol/ag-ui.git","type":"git"},"_npmVersion":"11.15.0","description":"AWS Strands Agents integration for the AG-UI protocol","directories":{},"sideEffects":false,"_nodeVersion":"22.23.2","publishConfig":{"access":"public"},"typesVersions":{"*":{"server":["dist/server.d.ts"]}},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"zod":"^4.4.3","cors":"^2.8.5","rxjs":"7.8.1","openai":"^6.10.0","tsdown":"^0.20.1","vitest":"^4.0.18","express":"^5.0.0","publint":"^0.3.12","typescript":"^5.3.3","@ag-ui/core":"0.0.59","@types/cors":"^2.8.17","@types/node":"^20.11.19","@ag-ui/client":"0.0.59","@ag-ui/encoder":"0.0.59","@types/express":"^5.0.0","@opentelemetry/api":"^1.9.0","@ag-ui/a2ui-toolkit":"0.0.4","@strands-agents/sdk":"^1.1.0","@arethetypeswrong/cli":"^0.17.4","@modelcontextprotocol/sdk":"^1.29.0","@vitest/coverage-istanbul":"^4.0.18"},"peerDependencies":{"cors":"^2.8.5","express":"^4.18.0 || ^5.0.0","@ag-ui/core":">=0.0.59","@ag-ui/client":">=0.0.59","@ag-ui/encoder":">=0.0.59","@ag-ui/a2ui-toolkit":">=0.0.3","@strands-agents/sdk":">=1.1.0","@modelcontextprotocol/sdk":">=1.0.0"},"peerDependenciesMeta":{"cors":{"optional":true},"express":{"optional":true},"@modelcontextprotocol/sdk":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/aws-strands_0.2.4-canary.1789125038.0_1789125383107_0.9768650179686638","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"_id":"@ag-ui/aws-strands@0.3.0","bugs":{"url":"https://github.com/ag-ui-protocol/ag-ui/issues"},"dist":{"shasum":"e228b217ef127c11038ef0931c930af9320c5375","tarball":"https://registry.npmjs.org/@ag-ui/aws-strands/-/aws-strands-0.3.0.tgz","fileCount":27,"integrity":"sha512-4LRdZbFur5zFv9IYrPsC8M72uY20lZZoiMslVCRnp5aYoe62rA7Nja0lX8+2d7ck4Ne+mb0NshJIvjf+3M6nJQ==","signatures":[{"sig":"MEUCIQDYlfIASY5J6r5+Vl5zwBoZYeCIHgU3MSw6z++0PThq4wIgNVJPm91Lcnkhzj5/ebUyrnvkhbuoU/bRJJ44HlHN40g=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDWw9/BzcvGqNS27giRYs1JXX+A0bTcgN/Ph0i5PGHC/QIgauFAQgWA5SnCNui5BZ6nPKfTc/LHwOiYwFfjch70CgU="}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@ag-ui%2faws-strands@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":1919025},"main":"./dist/index.js","name":"@ag-ui/aws-strands","_from":"file:ag-ui-aws-strands-0.3.0.tgz","types":"./dist/index.d.ts","author":{"name":"AG-UI Contributors"},"module":"./dist/index.mjs","exports":{".":{"import":"./dist/index.mjs","require":"./dist/index.js"},"./server":{"import":"./dist/server.mjs","require":"./dist/server.js"},"./package.json":"./package.json"},"license":"MIT","scripts":{"dev":"tsdown --watch","test":"vitest run","build":"tsdown","clean":"git clean -fdX --exclude=\"!.env\"","typecheck":"tsc --noEmit","test:watch":"vitest","link:global":"pnpm link --global","test:exports":"publint --strict && attw --pack","test:coverage":"vitest run --coverage","unlink:global":"pnpm unlink --global"},"version":"0.3.0","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:868a4e3e-c050-4174-873e-2aae9576d5c0"}},"homepage":"https://github.com/ag-ui-protocol/ag-ui#readme","_resolved":"/home/runner/work/ag-ui/ag-ui/integrations/aws-strands/typescript/ag-ui-aws-strands-0.3.0.tgz","_integrity":"sha512-4LRdZbFur5zFv9IYrPsC8M72uY20lZZoiMslVCRnp5aYoe62rA7Nja0lX8+2d7ck4Ne+mb0NshJIvjf+3M6nJQ==","repository":{"url":"git+https://github.com/ag-ui-protocol/ag-ui.git","type":"git"},"_npmVersion":"11.15.0","description":"AWS Strands Agents integration for the AG-UI protocol","directories":{},"maintainers":[{"name":"_mme","email":"markus.ecker@gmail.com"},{"name":"copilotkit","email":"devops@copilotkit.ai"}],"sideEffects":false,"_nodeVersion":"22.23.2","publishConfig":{"access":"public"},"typesVersions":{"*":{"server":["dist/server.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"zod":"^4.4.3","cors":"^2.8.5","rxjs":"7.8.1","openai":"^6.10.0","tsdown":"^0.20.1","vitest":"^4.0.18","express":"^5.0.0","publint":"^0.3.12","typescript":"^5.3.3","@ag-ui/core":"0.0.59","@types/cors":"^2.8.17","@types/node":"^20.11.19","@ag-ui/client":"0.0.59","@ag-ui/encoder":"0.0.59","@types/express":"^5.0.0","@opentelemetry/api":"^1.9.0","@ag-ui/a2ui-toolkit":"0.0.4","@strands-agents/sdk":"^1.1.0","@arethetypeswrong/cli":"^0.17.4","@modelcontextprotocol/sdk":"^1.29.0","@vitest/coverage-istanbul":"^4.0.18"},"peerDependencies":{"cors":"^2.8.5","express":"^4.18.0 || ^5.0.0","@ag-ui/core":">=0.0.59","@ag-ui/client":">=0.0.59","@ag-ui/encoder":">=0.0.59","@ag-ui/a2ui-toolkit":">=0.0.3","@strands-agents/sdk":">=1.1.0","@modelcontextprotocol/sdk":">=1.0.0"},"peerDependenciesMeta":{"cors":{"optional":true},"express":{"optional":true},"@modelcontextprotocol/sdk":{"optional":true}},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/aws-strands_0.3.0_1789135332995_0.5563166607084422"}}},"time":{"created":"2026-05-26T15:30:16.542Z","modified":"2026-09-11T14:02:13.489Z","0.1.0":"2026-05-26T15:30:16.885Z","0.2.0":"2026-06-15T09:30:33.853Z","0.2.1":"2026-06-19T15:00:52.943Z","0.2.2":"2026-06-23T14:04:12.962Z","0.2.3":"2026-06-24T15:07:35.979Z","0.2.4-canary.1788531199.0":"2026-09-04T14:15:43.717Z","0.2.4-canary.1789051242.0":"2026-09-10T14:43:06.703Z","0.2.4-canary.1789125038.0":"2026-09-11T11:16:23.215Z","0.3.0":"2026-09-11T14:02:13.102Z"},"bugs":{"url":"https://github.com/ag-ui-protocol/ag-ui/issues"},"author":{"name":"AG-UI Contributors"},"license":"MIT","homepage":"https://github.com/ag-ui-protocol/ag-ui#readme","repository":{"url":"git+https://github.com/ag-ui-protocol/ag-ui.git","type":"git"},"description":"AWS Strands Agents integration for the AG-UI protocol","maintainers":[{"name":"_mme","email":"markus.ecker@gmail.com"},{"name":"copilotkit","email":"devops@copilotkit.ai"}],"readme":"# AWS Strands Integration for AG-UI (TypeScript)\n\nThis package exposes a lightweight wrapper that lets any `@strands-agents/sdk` `Agent` speak the AG-UI protocol. It mirrors the developer experience of the other integrations: give us a Strands agent instance, plug it into `StrandsAgent`, and wire it to Express via `createStrandsApp` (or `addStrandsExpressEndpoint`).\n\n## Prerequisites\n\n- Node.js 20+ if you import this package as ESM. That is\n  `@strands-agents/sdk`'s own floor (`engines.node: \">=20.0.0\"`). This package\n  declares no `engines` of its own, so nothing warns you below it and the failure\n  surfaces later, wherever the SDK first needs something the runtime lacks.\n- **Node.js 20.19+ or 22.12+ if you `require()` it from CommonJS.**\n  `@strands-agents/sdk` is ESM-only (`\"type\": \"module\"`, and its `exports` map\n  offers no `require` condition), while this package also ships a CommonJS build\n  whose entry does a top-level `require` of it. That only works on a runtime with\n  `require(esm)`, which arrived in 22.12.0 and was backported to 20.19.0. On 20.0\n  through 20.18, or on 21.x, a CommonJS consumer fails at load with\n  `ERR_REQUIRE_ESM`. Importing as ESM is unaffected on any Node 20.\n- `pnpm` (recommended) or `npm`\n- A Strands-compatible model key (e.g., AWS credentials for Bedrock, `OPENAI_API_KEY` for OpenAI)\n- Node.js 20.12+ to run the demos under `examples/`. Every demo script there\n  passes `--env-file-if-exists`, which is a Node 20.12 flag, and that package\n  declares no `engines` either. Its `test` and `typecheck` scripts do not use the\n  flag and are unaffected.\n\n## Quick Start\n\nThe `examples/` package ships a \"dojo\" server that mounts every demo on a\nsingle port, plus a standalone server for each of the ten demos that ship a\nrun script, which you can start on its own.\n\n```bash\n# from the repo root\npnpm install\npnpm --filter @ag-ui/aws-strands build\n\ncd integrations/aws-strands/typescript/examples\npnpm dojo                       # all examples at http://localhost:8022\n```\n\nOr run any single example on its own port (default `8000`):\n\n```bash\npnpm agentic-chat\npnpm agentic-chat-reasoning\npnpm agentic-chat-multimodal\npnpm backend-tool-rendering\npnpm shared-state\npnpm agentic-generative-ui\npnpm human-in-the-loop\npnpm interrupt\npnpm predictive-state-updates\npnpm tool-based-generative-ui\n```\n\nThe dojo exposes:\n\n| Route                       | Description                                                              |\n| --------------------------- | ------------------------------------------------------------------------ |\n| `/agentic-chat`             | Baseline chat; frontend tools auto-registered from `RunAgentInput.tools` |\n| `/agentic-chat-reasoning`   | Reasoning / thinking event streaming                                     |\n| `/agentic-chat-citations`   | Answers carrying the sources they came from                              |\n| `/agentic-chat-multimodal`  | Multimodal image / document analysis                                     |\n| `/backend-tool-rendering`   | Backend-executed tools (`get_weather`, `render_chart`)                   |\n| `/shared-state`             | Shared recipe state (`stateFromArgs`)                                    |\n| `/agentic-generative-ui`    | Async-generator tool streams `STATE_SNAPSHOT`s + `PredictState`          |\n| `/human-in-the-loop`        | Frontend proxy tool with halt-after-call                                 |\n| `/interrupt`                | Backend tool pauses itself to ask the user for a meeting time            |\n| `/predictive-state-updates` | Frontend write tool whose streaming args paint `state.document`          |\n| `/tool-based-generative-ui` | Frontend-rendered tool (`generate_haiku`)                                |\n| `/multi-agent`              | Graph orchestrator; the adapter drives `.stream()` rather than cloning   |\n| `/a2ui-dynamic-schema`      | A2UI surfaces composed on the fly (auto-injected tool)                   |\n| `/a2ui-fixed-schema`        | A2UI from fixed-layout backend tools                                     |\n| `/a2ui-recovery`            | A2UI validate-and-retry recovery loop                                    |\n\nEvery file under `examples/server/api/*.ts` follows the same pattern: build the thing the demo drives, wrap it in a `StrandsAgent`, and export that as a factory. Usually that is a single Strands `Agent`; `multi-agent.ts` wraps a graph orchestrator instead. Each file is the single definition of its demo, so the dojo server mounts the same agent you get by running the demo on its own. The ten with a `pnpm run <demo>` script also hand the agent to `createStrandsApp` and listen, guarded so importing the file starts no server. The multi-agent and three a2ui files export the factory only. `agentic-chat-citations.ts` sits between the two: it carries the same standalone runner, but no `pnpm` script points at it, so run it with `tsx` directly.\n\n## Architecture Overview\n\nThe integration has three main layers:\n\n- **StrandsAgent** – wraps `Agent.stream()` from `@strands-agents/sdk`. It translates Strands streaming events into AG-UI events (text chunks, tool calls, PredictState, snapshots, reasoning/thinking, multi-agent steps, etc.).\n- **Configuration** – `StrandsAgentConfig` + `ToolBehavior` + `PredictStateMapping` let you describe tool-specific quirks declaratively (skip message snapshots, emit state, stream args, etc.).\n- **Transport helpers** – `createStrandsApp` and `addStrandsExpressEndpoint` expose the agent via SSE. They are thin shells over the shared `@ag-ui/encoder` `EventEncoder`. Imported from `@ag-ui/aws-strands/server` — kept off the main entry so client-side bundlers (Next.js, Vite) don't pull Express into the browser graph.\n\nSee [../ARCHITECTURE.md](../ARCHITECTURE.md) for diagrams and a deeper dive.\n\n## Key Files\n\n| File                       | Description                                                                     |\n| -------------------------- | ------------------------------------------------------------------------------- |\n| `src/agent.ts`             | Core wrapper translating Strands streams into AG-UI events                      |\n| `src/config.ts`            | Config primitives (`StrandsAgentConfig`, `ToolBehavior`, `PredictStateMapping`) |\n| `src/template-tools.ts`    | Per-request filter over the template agent's tools                              |\n| `src/server.ts`            | `createStrandsApp` + Express transport (subpath: `@ag-ui/aws-strands/server`)   |\n| `src/endpoint.ts`          | Express endpoint helpers (used by `server.ts`)                                  |\n| `src/utils.ts`             | Multimodal content conversion and the `UrlFetchPolicy` that guards it           |\n| `src/client-proxy-tool.ts` | Dynamic frontend tool registration/deregistration                               |\n| `src/citations.ts`         | Provider citations normalised onto the message they annotate                    |\n| `src/a2ui-tool.ts`         | A2UI tool injection and the validate-and-retry recovery loop                    |\n| `src/session-reconcile.ts` | Frontend-result reconciliation against a persisted session                      |\n| `examples/server/api/*.ts` | One factory per demo; eleven carry a standalone runner, ten of those scripted   |\n\n## Amazon Bedrock AgentCore Considerations\n\nIf you are planning to deploy your agent into Amazon Bedrock AgentCore (AC), please note that AC expects the following:\n\n- The server is running on port 8080.\n- The path `/invocations - POST` is implemented and can be used for interacting with the agent.\n- The path `/ping - GET` is implemented and can be used for verifying that the agent is operational and ready to handle requests.\n\nTo implement the paths mentioned above, you can use the helper function `createStrandsApp` and pass the agent interaction path and the ping path as shown below:\n\n```ts\nconst app = await createStrandsApp(aguiAgent, {\n  path: \"/invocations\",\n  pingPath: \"/ping\",\n});\napp.listen(8080);\n```\n\nYou can also use the helper functions `addStrandsExpressEndpoint` and `addPing` for adding the mentioned paths to an Express app that you are creating separately:\n\n```ts\nimport express from \"express\";\nimport { addStrandsExpressEndpoint, addPing } from \"@ag-ui/aws-strands/server\";\n\nconst app = express();\n// No CORS middleware, so no page on a different origin can read this\n// endpoint's responses. Same-origin pages are unaffected: CORS governs\n// cross-origin requests only.\n// Add `cors` yourself only if a browser on another origin has to reach it.\naddStrandsExpressEndpoint(app, aguiAgent, {\n  path: \"/invocations\",\n  bodyParser: express.json({ limit: \"50mb\" }),\n});\naddPing(app, \"/ping\");\napp.listen(8080);\n```\n\nRequests to the AC endpoint must be authenticated. You can configure your agent runtime to accept JWT bearer tokens (via Amazon Cognito) or use SigV4. See [Set up authentication](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-agui.html) in the AgentCore documentation.\n\nFor details on how AgentCore handles AG-UI requests, event streaming, and error formatting, see the [AG-UI protocol contract](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-agui-protocol-contract.html).\n\nTo deploy, use the [AgentCore Starter Toolkit](https://github.com/aws/bedrock-agentcore-starter-toolkit).\nThese are the commands AWS's own AG-UI deployment guide gives for a TypeScript\nentrypoint, and `--protocol AGUI` is what tells the runtime to treat port 8080\nand `/invocations` as AG-UI rather than plain HTTP:\n\n```bash\npip install bedrock-agentcore-starter-toolkit\nagentcore configure -e my-agui-server.ts --protocol AGUI\nagentcore deploy\n```\n\nThe starter toolkit's repository says its CLI is superseded by `@aws/agentcore`,\nwhich carries the same `--protocol AGUI` value under its own command names,\nwhile the AG-UI deployment guide linked above still gives the starter-toolkit\ncommands. Where the two disagree, that guide is the one to follow: it is AWS's\nown instructions for this protocol.\n\nFor the complete deployment walkthrough, see [Deploy AG-UI servers in AgentCore Runtime](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-agui.html).\n\n## Supported AG-UI Events\n\nThe integration supports the following AG-UI event families:\n\n- **Lifecycle**: `RUN_STARTED`, `RUN_FINISHED`, `RUN_ERROR`\n- **Text streaming**: `TEXT_MESSAGE_START`, `TEXT_MESSAGE_CONTENT`, `TEXT_MESSAGE_END` (optionally collapsed into `TEXT_MESSAGE_CHUNK` via `StrandsAgentConfig.emitChunkEvents`)\n- **Reasoning**: `REASONING_*` events for models with extended thinking (`REASONING_MESSAGE_CHUNK` when `emitChunkEvents` is on)\n- **Tool calls**: `TOOL_CALL_START`, `TOOL_CALL_ARGS`, `TOOL_CALL_END`, `TOOL_CALL_RESULT` (or `TOOL_CALL_CHUNK` with `emitChunkEvents`)\n- **State management**: `STATE_SNAPSHOT`, and `STATE_DELTA` where a\n  `customResultHandler` emits one; the adapter produces no delta of its own\n- **Multi-agent**: `STEP_STARTED`, `STEP_FINISHED`, and `MultiAgentHandoff` custom events\n- **Generative UI**: `PredictState` custom events for optimistic UI updates\n- **Message history**: `MESSAGES_SNAPSHOT` after the opening state snapshot and\n  after each `TOOL_CALL_END`, `TOOL_CALL_RESULT` and terminal `TEXT_MESSAGE_END`,\n  each carrying the complete thread as known so far. On by default; turn it off\n  globally with `StrandsAgentConfig.emitMessagesSnapshot`, or per tool with\n  `ToolBehavior.skipMessagesSnapshot`. The multi-agent orchestrator path emits\n  none whatever those say.\n- **Multimodal**: Image, document, and video content in user messages (converted to Strands ContentBlock format)\n- **Citations**: source passages attached to the assistant message's `metadata` (see below)\n- **Custom**: `PredictState`, `MultiAgentHandoff`, `AgentStopped` (an abnormal\n  model stop reason) and `hook_error` (a developer callback that threw), all as\n  `CUSTOM` events keyed by `name`\n- **Interrupts**: `RUN_FINISHED` carries an interrupt outcome when a backend\n  tool or hook paused the run (see below)\n- **Raw passthrough**: `RAW` for Strands events this adapter does not map (see\n  below)\n\nThe adapter advertises an event / feature matrix at GET `/capabilities`\n(enabled by default; override via\n`createStrandsApp({ capabilitiesPath, capabilities })` or mount manually with\n`addCapabilities(app, path, overrides)`, or\n`addCapabilities(app, path, { agent, overrides })` to derive the chunk flags from\na live agent's `emitChunkEvents` rather than pinning them).\n\nOne flag in that matrix needs reading with care. `events.STATE_DELTA: false` and\n`features.stateDelta: false` are not a mistake, and mean what they say about the\nadapter, which emits no delta of its own; a `customResultHandler` that emits one\nis your own addition. Fold an override in if you serve the matrix to something\nthat reads it.\n\n## Unmapped Strands events reach the client as `RAW`\n\nA Strands stream event with no AG-UI translation is forwarded rather than\ndropped, as `{ type: \"RAW\", event, source: \"strands\" }`. Bedrock's per-turn\n`metadata` (token usage, latency, trace ids) arrives this way, and so does\nanything a future SDK release starts emitting before this adapter learns to map\nit.\n\n> **`event` is a framework-shaped payload, not an AG-UI one.** Its contents are\n> whatever `@strands-agents/sdk` put on the wire for that event, and the SDK is\n> free to change that shape in any release without it being a break in this\n> package. Read it defensively, and do not build a required UI path on a field\n> you found in it. Anything this adapter promises to keep stable is a mapped\n> event with a name, not a `RAW` one.\n\nForwarding is filtered rather than coerced. Keys belonging to the per-run\ninvocation state are stripped, since Strands merges them into otherwise public\nmodel events, and a payload that will not survive a strict JSON round trip is\ndropped with a warning rather than stringified. Coercing it would ship the\nserialized live `Agent`, system prompt and conversation history included, to\nevery connected client.\n\n## Multi-agent orchestration\n\nPass a Strands `Graph` or `Swarm` where `StrandsAgentOptions.agent` would\nnormally take an `Agent`. The adapter detects the orchestrator structurally (it\nhas no `model`) and drives its `.stream()` directly instead of cloning a\nper-thread agent, so per-thread caching, session managers and proxy-tool sync do\nnot apply: the orchestrator owns its own nodes. Both bridges do this; see\nStrands' [Graph](https://strandsagents.com/docs/user-guide/concepts/multi-agent/graph/)\nand [Swarm](https://strandsagents.com/docs/user-guide/concepts/multi-agent/swarm/)\nguides for what each pattern is for.\n\nEach node opens a `STEP_STARTED` named `{nodeType}:{nodeId}` and closes it with\n`STEP_FINISHED`, a handoff becomes `CUSTOM` `MultiAgentHandoff` carrying\n`from_nodes` / `to_nodes` / `message`, and each node's text and tool calls stream\ninside its own step. `/multi-agent` in the dojo is a live example.\n\nTwo limits are worth knowing before you rely on this path, and both are\ndescribed in full in [../ARCHITECTURE.md](../ARCHITECTURE.md):\n\n- **A failed Graph node does not fail the run here.** The TypeScript SDK's\n  `Node.stream()` turns a node throw into a FAILED `NodeResult` and returns\n  normally, so the adapter never sees it: the run emits its steps and then\n  `RUN_FINISHED`. Only orchestration budgets (`maxSteps`, `timeout`,\n  `nodeTimeout`) escape as a throw, and those report `STRANDS_ERROR`. The Python\n  bridge differs, because a Python `Graph` fails fast and re-raises.\n- **A step the SDK abandoned stays open.** Step envelopes are paired from the\n  SDK's own node brackets and this adapter closes none of its own.\n\n## One run at a time per thread\n\nA second run starting on a thread that already has one in flight is refused\nbefore the body is entered, with\n`RUN_ERROR { code: \"THREAD_BUSY\" }` and the message `Another run is already in\nprogress on thread \"<id>\". Wait for RUN_FINISHED before starting another.` One\nStrands `Agent` is cached per thread and cannot be multiplexed, and an\nunguarded overlap corrupts the cached history rather than merely racing.\n\nThe slot is released in the run generator's `finally`. A caller driving\n`agent.run(...)` directly rather than through the transport owes that generator\na `.return()`: breaking out of the loop, or pulling one event and dropping it,\nleaves the slot held until the runtime finalizes the abandoned generator, and\nthe thread refuses runs for as long as that takes. The Express transport closes\nit explicitly, including on client disconnect.\n\nThe guard is per adapter instance. Two instances sharing one `agentsByThread`\nmap, which is exactly what request-scoped serverless wrappers do, each start\nwith an empty busy set and can both accept a run on the same thread. Python has\nthe same limit.\n\n## Abnormal model stop reasons\n\nA terminal result whose stop reason is a guardrail intervention or a content\nfilter emits `CUSTOM` `AgentStopped` with `value: { stop_reason: <reason> }`\nahead of an ordinary `RUN_FINISHED`, so a UI can explain a short, empty or\nfiltered answer instead of reading it as success. A normal end of turn or a tool\nuse emits nothing.\n\nMind the two spellings. This SDK canonicalises provider stop reasons to\ncamelCase, so what arrives here is `guardrailIntervened` or `contentFiltered`,\nand `ABNORMAL_STOP_REASONS` accepts both spellings because `StopReason` widens to\n`string`. What goes out in `stop_reason` is Python's snake_case,\n`guardrail_intervened` or `content_filtered`, from both bridges, so a client\nmatches one string rather than one per language.\n\nTruncation is the exception: the SDK throws `MaxTokensError` as soon as the\naggregated stop reason is `maxTokens`, so no terminal result is produced and the\nrun reports `STRANDS_ERROR` with no hint. Python behaves identically.\n\nWhether a hint can arrive at all is the provider's choice, because the hint is\nonly as good as the provider's own stop-reason mapping, and the TypeScript\nproviders do not map the way the Python ones do. Bedrock produces both hints;\nOpenAI's chat-completions adapter and the Vercel provider produce the filtered\none only; OpenAI's Responses adapter and Gemini produce none. The full\nper-provider survey, and where it disagrees with Python, is in\n[../ARCHITECTURE.md](../ARCHITECTURE.md).\n\n## Fetching URL content sources\n\nA user message may carry an image, document or video as a URL rather than inline\ndata. The adapter fetches those server-side, so every fetch runs under a\n`UrlFetchPolicy`. `DEFAULT_URL_FETCH_POLICY` is the one in force:\n`allowedSchemes` of `http` and `https` only, `allowPrivateNetworks: false` so\nany host resolving outside the public internet is refused (loopback, private and\nlink-local, the cloud metadata endpoints among them), `maxBytes` of 25 MiB,\n`timeoutMs` of 30000 and `maxRedirects` of 10. The connection is pinned to the\naddress the policy validated, so a second DNS answer cannot redirect it; every\nredirect hop is re-checked, and one that drops TLS is refused. `nat64Prefixes`\nnames the deployment-specific NAT64 prefixes to unwrap, over and above the\nwell-known `64:ff9b::/96` and `64:ff9b:1::/48`. A run whose media all fail\nconversion with no text fallback ends with\n`RUN_ERROR { code: \"MEDIA_RESOLUTION_FAILED\" }`.\n\nA deployment whose attachments live on a private CDN or behind split DNS opts\nin through `StrandsAgentConfig.urlFetchPolicy`, the counterpart to Python's\n`url_fetch_policy`. `UrlFetchPolicy` is an interface rather than a class, so an\noverride is a spread over the exported default rather than a constructor call:\n\n```ts\nimport {\n  DEFAULT_URL_FETCH_POLICY,\n  StrandsAgent,\n  type UrlFetchPolicy,\n} from \"@ag-ui/aws-strands\";\n\nconst policy: UrlFetchPolicy = {\n  ...DEFAULT_URL_FETCH_POLICY,\n  allowPrivateNetworks: true,\n  maxBytes: 100 * 1024 * 1024,\n  // Narrowing is allowed; widening is not (see below).\n  allowedSchemes: new Set([\"https\"]),\n};\n\nconst agent = new StrandsAgent({\n  agent: strandsAgent,\n  name: \"my-agent\",\n  config: { urlFetchPolicy: policy },\n});\n```\n\nLeaving `urlFetchPolicy` unset is the same as `DEFAULT_URL_FETCH_POLICY`, and\nthe opt-in is always the host's, never anything a client can put in a\n`RunAgentInput`. Link-local addresses and the cloud metadata endpoints stay\nblocked under `allowPrivateNetworks`, and `allowedSchemes` can only be\nnarrowed, never widened: an `http`/`https` request goes out over a transport\npinned to the addresses that passed validation, while any other scheme would\nresolve the host again at connection time. `DEFAULT_URL_FETCH_POLICY` and\n`UrlFetchPolicyError` are exported from the root entry as values, with\n`UrlFetchPolicy` and `SchemeAllowlist` as types, so an override can be both\nwritten and typed; `UrlFetchUnavailableError` stays internal, as it does in\nPython.\n\nAn unusable policy ends the run with\n`RUN_ERROR { code: \"URL_FETCH_POLICY_INVALID\" }` before any attachment is\nfetched, rather than reverting to the default. That covers a limit below one, a\nfractional redirect cap, a non-boolean `allowPrivateNetworks`, and a scheme\noutside `http`/`https`.\n\nThe two policies are not the same shape either. Python bounds a whole run as\nwell as a single attachment, through `max_attachments`, `max_total_bytes` and\n`max_total_seconds`; this bridge has no per-run budget, so a message carrying\nmany URLs is bounded only one attachment at a time.\n\n## Terminal error codes\n\nEvery `RUN_ERROR` code either bridge can emit, and the message text that goes\nwith each one, is enumerated in\n[`../error-codes.json`](../error-codes.json). That file is a wire contract\nrather than documentation: clients and mock harnesses match both the code and\nthe message literally, and both test suites drive their bridge to each terminal\npath and assert the emitted frame against it, so a reworded message fails a test\ninstead of reaching a client.\n\nTwo codes are TypeScript-only. `SEED_BUILD_ERROR` comes from this bridge's\nhistory-seed preflight, which Python has no equivalent of because it seeds\ninside the run. `THREAD_AGENT_CONFIG_ERROR` reports a throwing\n`threadAgentConfig` callback, where Python reports the same class of failure as\n`THREAD_AGENT_KWARGS_ERROR`, so a client matching on the code sees two values\nrather than one. Everything else this bridge emits is shared with Python, and\n`error-codes.json` records the reason against every one-sided code and every\none-sided sentence.\n\n## Passing tools to the Agent\n\nThe adapter clones the template `Agent`'s resolved `agent.tools` onto every\nper-thread clone, and it does that at construction time. Whatever is in that\nlist is what the model sees.\n\nAn `McpClient` handed straight to `tools` is not in that list. The SDK's\n`tools` option does accept one (`ToolList` is\n`(Tool | McpClient | Agent | ToolList)[]`), but it routes a client to an\ninternal client list rather than to the tool registry, and only registers its\ntools inside `Agent.initialize()`, which runs on the first invocation. Measured\nagainst `@strands-agents/sdk` 1.1.0: `new Agent({ tools: [client] })` leaves\n`agent.tools` empty. Connecting the client first does not change that, so the\ndistinction to keep in mind is resolved-versus-unresolved, not\nconnected-versus-unconnected.\n\nResolve the tools yourself and spread them in, which puts real tools in the\nregistry at construction and so in every per-thread clone:\n\n```ts\nimport { Agent, McpClient } from \"@strands-agents/sdk\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\n\n// `transport` is required: `McpClientConfig` has no default for it.\nconst spellbook = new McpClient({\n  transport: new StreamableHTTPClientTransport(\n    new URL(\"https://mcp.example.com/mcp\"),\n  ),\n});\nawait spellbook.connect();\nconst mcpTools = await spellbook.listTools();\n\nconst agent = new Agent({\n  model: \"anthropic.claude-sonnet-4-5-20250929-v1:0\",\n  tools: [...mcpTools, myLocalTool],\n});\n\nconst aguiAgent = new StrandsAgent({ agent, name: \"MyAgent\" });\n```\n\nThe adapter checks for this at construction time: a template `Agent` still\nholding `McpClient` entries gets a warning naming how many, because their tools\ncannot reach a per-thread clone. Spreading the resolved tools in silences it,\nonce the client itself is out of `tools`.\n\n`McpClient` comes from the package root. `@strands-agents/sdk` publishes an\n`exports` map with no `./mcp` entry, so a subpath import of it does not\nresolve. Any `Transport` from `@modelcontextprotocol/sdk` works in its place;\nthe streamable-HTTP one above is just the common case. See Strands' own\n[MCP tools guide](https://strandsagents.com/docs/user-guide/concepts/tools/mcp-tools/)\nfor the transports and the elicitation callback.\n\n## Per-request tool filtering\n\n`StrandsAgentConfig.templateToolsProvider` decides which of the template\nagent's tools one request may see. It is called once per request with that\nrequest's `RunAgentInput`, so the answer can vary turn by turn on a single\nthread:\n\n```ts\nconst READ_ONLY = [\"search_docs\", \"get_order\"];\n\nconst aguiAgent = new StrandsAgent({\n  agent,\n  name: \"assistant\",\n  config: {\n    templateToolsProvider: (input) =>\n      // Derive the role from authenticated request context in production;\n      // forwardedProps is client-controlled.\n      (input.forwardedProps as { role?: string })?.role === \"admin\"\n        ? null // no filtering: every template tool stays available\n        : READ_ONLY,\n  },\n});\n```\n\nReturn the tools themselves or their names. `null` or `undefined` declines to\nfilter; an empty array is a real answer and withholds all of them. A name the\ntemplate does not contribute is dropped with a warning, because the hook\nnarrows the wrapped agent's tools and cannot add one. The provider may be\nasync.\n\nTwo boundary rules follow from that:\n\n- **The return value is checked, not merely iterated.** A string and a mapping\n  are both iterable and both mean something other than what iterating them\n  produces: a bare name would come apart into characters, and a permission map\n  would have its keys read as an allow-list while its values went unread, so a\n  name mapped to `false` would still be allowed. Both are refused with\n  `TEMPLATE_TOOLS_PROVIDER_ERROR`. Arrays, sets and generators are all\n  accepted, and a generator that raises partway through iteration reports the\n  same code, because the answer is read inside the same guarded step that calls\n  the provider.\n- **The filter reaches the registry, not only the advertised tool specs.** A\n  model that calls a withheld name anyway, primed by a stale turn or by the\n  visible history, is refused by the dispatcher rather than served.\n\nThe filter is applied to the tool registry the thread's live Strands `Agent`\nalready owns, the same way client-declared tools are synchronised, and never by\nrebuilding that agent. The per-thread instance holds the thread's\n`SessionManager`, its native interrupt checkpoint and its history, so replacing\nit to change a tool list would discard a conversation and any approval waiting\ninside it.\n\nThree consequences follow from that:\n\n- **A parked call is never orphaned.** A tool in the batch a live interrupt\n  checkpoint would resume stays registered whatever the provider returns: the\n  human's answer is about to be routed back into that batch, and an absent tool\n  turns it into a \"tool not found\" the model re-fires. Filtering resumes once\n  the pause closes. This is the rule `syncProxyTools` already applies to a proxy\n  parked in a frontend-tool interrupt.\n- **History is never rewritten.** A filtered-out tool's earlier calls and\n  results stay in the thread's messages, so the model can still read what it\n  did with a tool it can no longer call.\n- **A failure is terminal.** If the provider throws, the run yields `RUN_ERROR`\n  with code `TEMPLATE_TOOLS_PROVIDER_ERROR` and stops, matching\n  `threadAgentConfig`. A filter that failed open would hand the model exactly\n  the tools the caller meant to withhold.\n\nThe narrowing is also re-applied inside the run, once a tool batch has been\ndispatched. The exemption above keeps a denied tool registered so a human's\nanswer can reach it, and Strands then carries on in the same run: it\nre-dispatches the batch and makes its next model call from the same registry,\nwhich would otherwise still be advertising what the request denied. The two\nbridges hook different SDK events for this, because the SDKs read the tool\nspecs at different points relative to the events they dispatch; the effect is\nthe same on both.\n\nScope is the template's own tools. Client-declared tools on\n`RunAgentInput.tools` are re-synchronised from the request every turn already,\nso a caller that wants fewer of those sends fewer. The hook is not applied on\nthe multi-agent orchestrator path, which has no template registry to filter.\n\nOne deployment note. With an `agentsByThread` map, a request-scoped wrapper is\nrebuilt per request while the cached thread agent keeps the registry it already\nhad. If the template's tools are built per request too, the adapter is handed\nequivalent but not identical objects, so which registry entry belongs to the\ntemplate is decided by name plus \"not one of the adapter's other producers\"\nrather than by object identity alone. Stable tool objects are still the simpler\nthing to hand it.\n\n## Human-in-the-loop interrupts\n\nTwo complementary patterns are supported:\n\n- **Frontend tools.** The `/human-in-the-loop` example declares\n  `generate_task_steps` on the frontend via `useHumanInTheLoop` — the adapter\n  auto-registers it as a proxy tool, halts the run after the proxy resolves,\n  and hands control back to the UI for approval. That round trip now survives a\n  restart; see below.\n- **Native Strands interrupts (SDK 1.1.0+).** Backend hooks and tools can call\n  `event.interrupt(...)` / `context.interrupt(...)` to raise a\n  `stopReason: 'interrupt'`. The adapter forwards the outstanding interrupts\n  on `RUN_FINISHED`:\n\n  ```json\n  {\n    \"type\": \"RUN_FINISHED\",\n    \"outcome\": {\n      \"type\": \"interrupt\",\n      \"interrupts\": [\n        { \"id\": \"...\", \"reason\": \"...\", \"metadata\": { \"reason\": {} } }\n      ]\n    }\n  }\n  ```\n\n  A generic interrupt keeps the Strands name as its AG-UI `reason` (defaulting\n  to `\"interrupt\"` when the interrupt carries no name) and the free-form Strands reason\n  under `metadata.reason`. A tool configured with `interruptOnCall` instead\n  publishes a `tool_call` approval, which always carries a `message`, an\n  `approved` `responseSchema`, and `tool_name` / `tool_input` / `strandsName` in\n  `metadata`. Two keys are conditional: `toolCallId`, which an approval raised\n  without a native tool use has none of, and `reason`, which is published only\n  when the native reason carried nothing the other keys could hold. Published `tool_input` is a detached copy,\n  so inspecting it cannot reach into the SDK's live checkpoint.\n\n  The `ag_ui:tool_call:` name prefix is **reserved** for this adapter's approval\n  hook. An interrupt raised anywhere else under that prefix is classified,\n  schema-checked and answered as an approval.\n\n### The resume contract\n\nThe next `RunAgentInput` carries `resume[]` entries keyed by those `id`s. The\nadapter converts each entry into a Strands `InterruptResponseContent` and hands\nthem straight to `agent.stream(...)`. Unknown `interruptId`s still short-circuit\nwith `RUN_ERROR { code: \"UNKNOWN_INTERRUPT_ID\" }` per\n[interrupts.mdx rule 2](https://docs.ag-ui.com/concepts/interrupts).\n\n`interrupt()` does **not** return `payload` directly. The adapter always hands\nStrands an envelope instead. Two reasons: an answer the SDK reads as absent\nre-raises the same interrupt and re-runs the tool body forever, and the Python\nadapter supports older Strands releases that read a recorded answer by\ntruthiness rather than by presence. The envelope is always present and always\ntruthy, which satisfies both, and it is what makes one tool body portable\nacross the two bridges:\n\n| `resume[]` entry                    | what the paused `interrupt()` returns                              |\n| ----------------------------------- | ------------------------------------------------------------------ |\n| `status: \"resolved\"`, any `payload` | `{ response: payload }`                                            |\n| `status: \"resolved\"`, no `payload`  | `{ response: null }`                                               |\n| `status: \"cancelled\"`               | `{ cancelled: true }`, matching the exported `INTERRUPT_CANCELLED` |\n\nDestructure it with `.response` / `.cancelled`, and do not truthiness-check the\nenvelope itself, since it is always truthy on resolve. Compare a cancellation by\nvalue rather than by identity: `INTERRUPT_CANCELLED` is exported so you can match\nits shape, and every answer is a fresh copy of it rather than the export itself.\nTreat what you receive as read-only. It is the same object the framework records\nas the answer, so mutating it changes what a later replay is compared against.\nThis is the same contract the Python package applies, so a tool body ports\nbetween the two unchanged.\n\nAdapter-managed `interruptOnCall` approvals are an exception in both\nlanguages, and on Python a parked frontend tool is a second one that its own\nREADME documents: their `{ approved: boolean }` payload is passed through raw, because\nthe approval hook reads `approved` off it directly. A cancelled approval is\nanswered `{ approved: false }` rather than with the sentinel.\n\nResuming a paused tool re-runs its body from the top, so any code before the\n`interrupt()` call executes again. Keep side effects after the pause resolves.\n\n### Frontend tool results survive a restart\n\nA frontend tool call is a round trip: the adapter halts the run, the browser\nexecutes the tool, and the answer arrives on the next request. Nothing\nguarantees the next request reaches the same process, and a redeploy between\nthe two is ordinary. This bridge now recovers that answer from a persisted\nsession rather than losing it, which is what the Python bridge already did.\n\nWire it up by giving the adapter somewhere durable to persist to:\n\n```ts\nconst agent = new StrandsAgent({\n  agent: strandsAgent,\n  name: \"MyAgent\",\n  config: {\n    sessionManagerProvider: async (input) => yourSessionManager(input.threadId),\n  },\n});\n```\n\nWith a session manager active, the halted turn leaves a reinvokable assistant\ntool use and a proxy placeholder result in the persisted history, and the\nadapter records the id of the frontend call it emitted on the agent's own state\nstore. A later run, on a new process and a new adapter sharing only that\nstorage, overwrites the placeholder with the client's real answer and continues\nfrom the corrected native history. Without a session manager the round trip\nstill works in-process, exactly as before, but a restart between the two halves\nloses the answer.\n\n> **Compatibility note.** A frontend call now carries Strands' native\n> `toolUseId` as its AG-UI `tool_call_id`, where it previously carried a\n> freshly minted UUID. That is what makes a persisted placeholder findable by\n> the id the client answers under. A client that only echoes the\n> `tool_call_id` back is unaffected. One that derived or stored its own meaning\n> from the old value will see a different string.\n>\n> The native id has to be non-blank and unique across the transcript for this\n> to work, so a missing, in-turn duplicate or reused id fails the run with\n> `FRONTEND_TOOL_IDENTITY_ERROR` rather than putting a wire id on the stream\n> that names nothing durable. Providers that do not supply stable ids should be\n> upgraded, or kept away from parallel frontend calls.\n\nPython's version of this path additionally reports a duplicate or conflicting\nanswer under codes of its own, and can park a frontend call in a native Strands\ninterrupt rather than halting. Neither has a counterpart here; see\n[`../error-codes.json`](../error-codes.json) and the Python README.\n\n> **Breaking change for tool bodies.** A resolved generic interrupt used to\n> reach the tool as the raw `payload`, and a cancellation as\n> `{ status: \"cancelled\" }`. Any tool reading the raw value must now read it off\n> `.response`, and a cancellation off `.cancelled`. A resolved tool approval is\n> unaffected, still receiving its payload raw; a cancelled one now receives\n> `{ approved: false }` where it previously received `{ status: \"cancelled\" }`. It follows `@ag-ui/aws-strands` 0.2.3; the release that carries it\n> is versioned in a separate bump commit, and this is not patch-compatible.\n\n## Reasoning / extended thinking\n\n`REASONING_*` events arrive only when the underlying Strands model has been\nasked for thinking or reasoning content. A model constructed with none returns\nplain text and the adapter has nothing to stream.\n\nThe `/agentic-chat-reasoning` demo asks for it explicitly rather than relying on\na default, through the shared factory:\n\n```ts\nmodel: await createModel({ openaiApi: \"responses\", reasoning: true });\n```\n\n`model-factory.ts` turns that into whatever the selected `MODEL_PROVIDER` needs:\nreasoning summaries on OpenAI's Responses API, extended thinking on Anthropic,\nthe same thinking block on Bedrock. It wires nothing for Gemini, so that provider\nemits no `REASONING_*` events whatever the flag says.\n\nWiring a model yourself, the Bedrock form is:\n\n```ts\nimport { BedrockModel } from \"@strands-agents/sdk/models/bedrock\";\n\nconst model = new BedrockModel({\n  modelId: \"global.anthropic.claude-sonnet-4-6\",\n  // Anthropic-on-Bedrock requires temperature 1 while thinking is enabled.\n  temperature: 1,\n  additionalRequestFields: {\n    thinking: { type: \"enabled\", budget_tokens: 5000 },\n  },\n});\n```\n\n## Citations\n\nWhen you give a model documents and turn citations on, its answer comes back\nwith the passages it drew from: which document, where in that document, and the\ntext of the passage itself. That is what lets an interface show \"according to\nquarterly-report.pdf\" next to a claim instead of asking the reader to take the\nanswer on trust. Bedrock calls these citations. Strands documents them only as\nan API reference, at\n[`CitationsBlock`](https://strandsagents.com/docs/api/typescript/CitationsBlock/).\n\nThe model emits them between the text deltas of the answer, so a citation\narrives in the middle of the message it belongs to. This adapter attaches them\nto that message rather than emitting them separately, which is what keeps a\ncitation joined to the thing it annotates.\n\n### Where they arrive\n\nUnder the `citations` key of the assistant message's `metadata`, as a list. The\nkey and the entry type are exported as `CITATIONS_METADATA_KEY` and\n`AguiCitation`:\n\n```ts\nimport { CITATIONS_METADATA_KEY, type AguiCitation } from \"@ag-ui/aws-strands\";\n\nconst cited = message.metadata?.[CITATIONS_METADATA_KEY] as\n  | AguiCitation[]\n  | undefined;\n```\n\n```json\n{\n  \"citations\": [\n    {\n      \"title\": \"quarterly-report.pdf\",\n      \"sourceContent\": [{ \"text\": \"revenue grew 12%\" }],\n      \"location\": {\n        \"type\": \"documentChar\",\n        \"documentIndex\": 0,\n        \"start\": 10,\n        \"end\": 26\n      },\n      \"textOffset\": 17\n    }\n  ]\n}\n```\n\n| Field           | Meaning                                                                 |\n| --------------- | ----------------------------------------------------------------------- |\n| `title`         | Title of the cited source, when the provider supplies one               |\n| `source`        | Source identifier, typically a URL for web citations                    |\n| `sourceContent` | The passage in the source document that supports the answer             |\n| `location`      | Where that passage sits in the source, discriminated by `type`          |\n| `content`       | The generated text the citation supports, where the provider reports it |\n| `textOffset`    | UTF-16 code units of this message's text streamed when it arrived       |\n\nWhich of these a response actually carries is the provider's choice. Bedrock\nsends `title`, `sourceContent` and `location`; it sends no `source` and no\ngenerated `content`, so both are absent on that path, in this adapter and in the\nPython one alike. The SDK's OpenAI Responses adapter sends `source` and\n`content` and a web location instead.\n\n`location` is `{ type: \"documentChar\" | \"documentPage\" | \"documentChunk\",\ndocumentIndex, start, end }` for document sources, `{ type: \"searchResult\",\nsearchResultIndex, start, end }` for search results, and `{ type: \"web\", url,\ndomain? }` for web ones. The union is exported as `AguiCitationLocation`.\n\nA location must arrive in one of two tagged forms: Bedrock's single-key wrapper\n(`{ documentChar: { ... } }`) or a discriminated object carrying a string\n`type`. Anything else cannot be placed, so the location is omitted and a warning\nnames what was dropped; the citation itself is kept, since a provider that sent\nan unreadable location still named a source.\n\nBedrock names the search-result kind `searchResultLocation` and this SDK renames\nit to `searchResult`; the Python adapter applies the same rename so both bridges\nagree on it. A kind neither SDK names yet never arrives here at all: the SDK's\nBedrock mapper logs an unknown location and drops the citation with it, where\nthe Python adapter passes it through. The asymmetry is upstream.\n\nA field the provider did not supply is absent rather than empty, and a citation\nthat names no source at all is dropped rather than emitted as a bare\n`textOffset`. A generated `content` span does not count as naming one, which\nmatters here rather than on the Python side, since this is the bridge where\n`content` arrives: it is the text being annotated, not the thing annotating it. One that will not survive JSON encoding is dropped too, with a\nwarning: metadata rides an event that is encoded for the stream, and a value\nthat fails to encode would end the run early.\n\nThe key is a plain metadata key, not AG-UI's reserved `ag-ui` one. Metadata is\nopen by key and user space is yours, so an application already storing something\nunder `citations` should rename it.\n\n### Where the two adapters agree, and where they do not\n\nFor a Bedrock response this adapter and the Python one emit equal citation\nobjects. That is what the normalisation is for: this SDK coalesces a\nmissing `source` or `title` to `\"\"` and those empties are dropped here, Python\nreceives Bedrock's key-wrapped `location` and unwraps it to the same\ndiscriminated form, and both omit absent fields.\n\nThey do not agree for every provider. Strands reports the generated span on the\ndelta rather than on the citation, and the Python SDK's stream shape has no\nequivalent field, so a provider that supplies one (the OpenAI Responses adapter)\nreaches a TypeScript client with `content` and `source`. A Python client is\nwithout the `content`, since that SDK's stream shape has no field for the\ngenerated span, but not necessarily without the `source`: `citations.py` reads\n`source` whenever the value is there, so what a Python client gets depends on\nthe provider path rather than on the adapter.\n\n### How precisely they can be placed\n\n**Message level is the ceiling, and it bounds what a frontend can render.** A\ncitation locates a span in the _source_ document. It carries no offset into the\nanswer, and AG-UI has no anchor for a span inside a message, so nothing here can\npromise \"these words came from that passage\".\n\n`textOffset` is the adapter's best effort at closing that gap: it records how\nmuch of the message had been streamed when the citation arrived. Bedrock emits a\ncitation after the text it supports, which makes the offset the end of the\nannotated span in practice, but that is the provider's ordering rather than a\nguarantee, so treat a marker placed with it as approximate. Where the provider\nreports `content`, that is the generated span itself and is exact.\n\n`textOffset` is counted in **UTF-16 code units**, not characters. The number is\nan index into the message text a client holds, and the clients that will slice\nwith it are browsers, where string indices are UTF-16 units. Both adapters count\nthe same units, so an answer containing an emoji does not shift the marker on\none side and not the other.\n\n### What a client sees while streaming\n\nThe list is republished as it grows, so a client holds a whole prefix at every\npoint rather than a fragment:\n\n1. A citation arrives and is attached to the next `TEXT_MESSAGE_CONTENT`, so it\n   is visible while the answer is still being written.\n2. Each publish carries every citation seen so far for that message. Metadata\n   merging replaces a key's value rather than appending to it, so the complete\n   list is the only correct thing to send.\n3. `TEXT_MESSAGE_END` carries the final list, which is how a citation with no\n   text after it reaches the client at all.\n4. The assistant message inside the following `MESSAGES_SNAPSHOT` carries the\n   same list. A snapshot replaces the message a client assembled, so without it\n   the citations would vanish the moment one arrived. That also applies to the\n   snapshot seeded from `RunAgentInput.messages` at the start of a later turn,\n   which is why the rebuild preserves a message's existing metadata.\n\nCitations belong to the message that was open when they arrived. A tool call\ncloses that message and rotates its id, and the next message starts with none. A\ncitation that arrives when no message is open has nothing to annotate, so it is\ndropped with a warning rather than carried onto whatever message comes next.\n\nOn the multi-agent orchestrator path there is no `MESSAGES_SNAPSHOT` at all, so\npoint 4 does not apply there and the node's `TEXT_MESSAGE_END` is the final\ncarrier. That path keeps one accumulator for the run rather than one per node,\nwhere the Python adapter keys both per node; in practice a node's turn is closed\nwhen it finishes, so its citations still land on its own message, and the\ndifference shows only for nodes whose output genuinely interleaves.\n\n### Chunk mode\n\n`emitChunkEvents` replaces the message triple with `TEXT_MESSAGE_CHUNK` and has\nno equivalent of `TEXT_MESSAGE_END`. That event is where a citation arriving\nafter the last text delta travels, so its metadata is re-emitted as a final\ncontinuation chunk carrying nothing else. The client transform turns a\nmetadata-only chunk into a zero-delta content event, which is how the value\nstill reaches the reducer without re-opening the message.\n\nThis matters most where there is no fallback: the multi-agent orchestrator path\nemits no `MESSAGES_SNAPSHOT` at all, whatever `emitMessagesSnapshot` says, so\nthe chunk is the only carrier a trailing citation has there.\n\n`features.citations` is therefore `true` in every configuration.\n\n## Install\n\n```bash\npnpm add @ag-ui/aws-strands @strands-agents/sdk \\\n  @ag-ui/core @ag-ui/client @ag-ui/encoder @ag-ui/a2ui-toolkit\n# All four @ag-ui peers are non-optional: the package root imports `@ag-ui/client`\n# for the AWSStrandsAgent shim and `@ag-ui/a2ui-toolkit` for the A2UI tool.\n# @strands-agents/sdk carries three non-optional peers of its own,\n# @modelcontextprotocol/sdk, @opentelemetry/api and zod, so your package manager\n# will ask for those too.\n# Server-side helpers (createStrandsApp / addStrandsExpressEndpoint) require express:\npnpm add express\npnpm add -D @types/express\n# `cors` is loaded only when `createStrandsApp` installs the middleware, which\n# needs a truthy `corsOrigin` that `corsEnabled: false` has not vetoed.\n# Skip the next two lines unless you opt into cross-origin access:\npnpm add cors\npnpm add -D @types/cors\n# @modelcontextprotocol/sdk is one of the three SDK peers noted above, and is\n# reachable from its entry whether or not your agent uses MCP. Listed separately\n# only because this package's own manifest marks it optional:\npnpm add @modelcontextprotocol/sdk\n```\n\n## Server: Expose a Strands Agent via AG-UI\n\n```ts\nimport { Agent } from \"@strands-agents/sdk\";\nimport { StrandsAgent } from \"@ag-ui/aws-strands\";\nimport { createStrandsApp } from \"@ag-ui/aws-strands/server\";\n\n// `model` accepts either a Bedrock model ID string or a constructed\n// Model instance (e.g. BedrockModel / AnthropicModel / OpenAIResponsesModel).\n// Omitting it uses Strands' current Bedrock default.\nconst strandsAgent = new Agent({\n  systemPrompt: \"You are a helpful assistant.\",\n  tools: [],\n});\n\nconst aguiAgent = new StrandsAgent({\n  agent: strandsAgent,\n  name: \"MyAgent\",\n  description: \"A Strands agent exposed via AG-UI\",\n});\n\nconst app = await createStrandsApp(aguiAgent, { path: \"/invocations\" });\napp.listen(8000);\n```\n\n## Cross-Origin Access\n\n`createStrandsApp` does not allow cross-origin access unless you ask for it.\nOmit `corsOrigin` and no CORS middleware is installed: responses carry no\n`Access-Control-Allow-Origin` header, so a browser refuses to hand any response\nfrom this app to a page on a different origin. A page served from the same\norigin as the app reads it as usual, since CORS governs cross-origin requests\nonly.\n\nThat default matters because the agent route is unauthenticated unless you pass\n[`auth`](#authenticating-the-agent-route). An allowed origin can invoke the\nagent, trigger whatever side effects its tools have, and read the streamed\nresponse, so cross-origin access is a deliberate choice rather than a starting\nposition.\n\n```ts\n// Default: no cross-origin access.\nconst app = await createStrandsApp(aguiAgent, { path: \"/invocations\" });\n\n// Local development: literal `*`, emitted verbatim, never reflected.\nconst dev = await createStrandsApp(aguiAgent, { corsOrigin: \"*\" });\n\n// Production: an exact-match allowlist.\nconst prod = await createStrandsApp(aguiAgent, {\n  corsOrigin: [\"https://app.example.com\", \"https://admin.example.com\"],\n});\n```\n\n`corsOrigin` accepts:\n\n| Value                    | Effect                                                                           |\n| ------------------------ | -------------------------------------------------------------------------------- |\n| omitted                  | No CORS middleware; no CORS header on any response                               |\n| `\"*\"`                    | Literal `Access-Control-Allow-Origin: *`, emitted verbatim, never reflected      |\n| `[\"*\"]`                  | Collapsed to the bare `\"*\"` before `cors` sees it, so allow-all                  |\n| `[\"*\", \"https://a.tld\"]` | Any array containing `\"*\"` collapses the same way; the named origins are dropped |\n| `\"https://app.tld\"`      | That one origin, emitted verbatim whichever origin asked                         |\n| `[\"https://a.tld\"]`      | Exact-match allowlist; a miss withholds `Access-Control-Allow-Origin`            |\n| `[]`                     | The allowlist path with nothing on the list, so every origin misses              |\n| `true`                   | Reflects the calling origin back per request; see the warning below              |\n| `false`                  | No CORS middleware; identical to omitting `corsOrigin`                           |\n| `\"\"`                     | Same as `false`                                                                  |\n\nA `\"*\"` anywhere in an array collapses the whole array to the bare string\n`\"*\"` before `cors` is constructed, so `[\"*\"]` and `[\"*\", \"https://a.tld\"]` are\nboth allow-all and are measured byte-identical to passing `\"*\"` on its own. The\nconcrete entries alongside a `\"*\"` are dropped rather than honoured, which is\nworth knowing before writing an allowlist that quietly is not one. `cors` itself\nonly ever sees the collapsed value, so nothing downstream can tell an array was\npassed.\n\nAn allowlist miss, `[]` included, is not a silent no-op. Measured against\n`cors` 2.8.5 on Express 5, a preflight from a disallowed origin comes back\n`204` carrying `Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE`;\nthe only header withheld is `Access-Control-Allow-Origin`, and that omission is\nwhat makes the browser block the response. A miss against a named allowlist\nalso carries `Access-Control-Allow-Credentials: true`, since the policy names\nspecific origins even on the call that matched none of them; `[]` names none at\nall and so carries no credentials header on any response. `false` and `\"\"`\nbehave differently again: the factory reads them as falsy and installs no\nmiddleware, so the preflight falls through to Express's own `OPTIONS` responder\n(`200`, `Allow: POST`) and no CORS header is emitted at all. The optional `cors`\ndependency is not even loaded for them.\n\nWhen it installs the middleware, `createStrandsApp` derives `credentials`\nrather than passing a fixed value, and `CreateStrandsAppOptions` offers no way\nto override the derivation. Two conditions, and both have to hold:\n\n1. **The policy has to name a site.** A non-empty origin string other than\n   `\"*\"` and `\"null\"`, an array with an entry other than those, or `true`. So\n   `\"*\"`, `\"null\"`, `[]`, `[\"*\"]`, `[\"*\", \"https://a.tld\"]` and `[\"null\"]` emit\n   no credentials header on any response, whoever calls.\n2. **The caller's own `Origin` has to name a site.** Any policy in the first\n   group can end up answering a request whose `Origin` is the literal `null`:\n   `true` reflects it, an allowlist can list it, and a fixed origin string is\n   echoed at it. Such a request never carries the credentials header, whatever\n   the policy allows for everyone else.\n\n`cors` is handed those options per request rather than once at construction,\nwhich is what keeps the second condition from costing anyone else their\ncredentials.\n\n`\"null\"` is the `Origin` a browser sends from a sandboxed iframe, a `file://`\npage and some redirect chains. It belongs to no site, so nothing tells one such\ncaller from another, and credentials granted against it are granted to whatever\ncan present it. That is the same objection as for `\"*\"`, with one difference:\nbrowsers reject a literal wildcard paired with credentials outright, while they\nhonour the `null` pairing, so this is the one of the two where the grant would\nhave reached the caller.\n\nListing `\"null\"` still admits those callers, and the rest of the list is\nuntouched: `[\"null\", \"https://a.tld\"]` is a working allowlist whose named site\nkeeps its credentials, while a caller outside the list still misses and the\nnull caller is admitted without credentials.\n\nBoth spellings are compared exactly. `cors` matches allowlist entries against\nthe request's `Origin` with `===`, and a browser compares the\n`Access-Control-Allow-Origin` it receives against its own origin serialization\nbyte for byte, so `\"NULL\"` or a trailing slash matches nothing on either side\nand grants nothing: a mis-spelled entry fails closed rather than slipping past\nthe check.\n\n> **`corsOrigin: true` is the value to be careful with, not `\"*\"`.** `true`\n> reflects whatever `Origin` the request carried straight back in\n> `Access-Control-Allow-Origin`, per request, and because a reflected origin is\n> a specific origin the derivation above keeps credentials on, so that origin\n> arrives paired with `Access-Control-Allow-Credentials: true`. Browsers honour\n> that pair for a credentialed request (`credentials: \"include\"`), so `true`\n> lets a page on any site make a credentialed cross-origin call to the agent\n> route and read the streamed response. On a route with no `auth` guard, that is\n> every site the browser visits. Prefer an exact-match array. The one caller it\n> does not credential is the one whose `Origin` is `null`, which belongs to no\n> site.\n>\n> `\"*\"` fails in the safer direction, and now does so twice over. The\n> derivation withholds the credentials header from a wildcard policy in the\n> first place, so it is never sent; and the CORS protocol tells browsers to\n> reject a literal wildcard combined with credentials anyway, so a wildcard\n> only ever serves requests that send none. Either way the `corsOrigin: \"*\"`\n> suggested above for local development cannot carry cookies. Name the origins\n> explicitly when the browser has to send them.\n\nBoth adapters refuse the same two origin values credentials. Python's\n`create_strands_app` computes\n`allow_credentials=bool(origins) and not {\"*\", \"null\"}.intersection(cors_origins)`,\nwhere `cors_origins` is `origins or [\"*\"]`,\nwhich is the first of the two conditions above. It has no equivalent of the\nsecond: Starlette takes one `allow_credentials` for the whole policy, so\n`origins=[\"null\", \"https://a.tld\"]` withholds credentials from the named site\ntoo, where the TypeScript adapter withholds them only from the null caller.\nNothing reflects an arbitrary origin on the Python side, so there is no\n`corsOrigin: true` there to credential a null caller through. What else differs\nis the\ndefault: Python adds `CORSMiddleware` to every app and falls back to\n`allow_origins=[\"*\"]` whenever `origins` is omitted or empty, emitting a\n`FutureWarning` for that implicit wildcard rather than refusing it, while\nTypeScript installs nothing until you pass `corsOrigin`. So Python is open to\nevery origin until you name one, and TypeScript grants no cross-origin access\nuntil you ask for it.\n\n> **Compatibility break.** Before this change the factory installed CORS\n> middleware unconditionally and defaulted to `corsOrigin: \"*\"`, so every\n> browser origin was allowed. Deployments that relied on that implicit default\n> now have to pass `corsOrigin` explicitly. Explicit values are unaffected.\n>\n> **Compatibility break.** A request whose `Origin` is the literal `null` no\n> longer receives `Access-Control-Allow-Credentials: true`, and neither does a\n> policy that names nothing but `\"null\"`. Such requests previously got the\n> header, and a browser honoured it, so a sandboxed iframe or `file://` page\n> could make a credentialed call: through a list naming `\"null\"`, through a\n> reflection under `corsOrigin: true`, or through a fixed origin string echoed\n> at it. Credentials now have to belong to a named site on both sides. Nothing\n> else changes: those callers are still admitted exactly as before, and no\n> other caller loses anything, including the named entries of an allowlist that\n> also lists `\"null\"`.\n\nCross-origin policy is only one of two defenses here. Requests without a JSON\n`Content-Type` are refused with HTTP 415 before the agent runs, which blocks the\nsimple, non-preflighted variant of the same attack. Neither one is a substitute\nfor authentication: pass [`auth`](#authenticating-the-agent-route) if the\nendpoint is reachable from an untrusted network.\n\n### Narrowing methods and headers\n\n`allowMethods` and `allowHeaders` are passed straight to `cors` as `methods` and\n`allowedHeaders`. Omit them and the `cors` defaults apply, measured against\n`cors` 2.8.5 on Express 5:\n\n- `Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE`.\n- `Access-Control-Allow-Headers` reflects the preflight's own\n  `Access-Control-Request-Headers` verbatim, and is absent entirely when the\n  preflight sends none.\n\nNarrowing either one replaces the corresponding default. Narrowing\n`allowHeaders` also pins the list regardless of what the preflight asked for, so\n`Access-Control-Allow-Headers: Content-Type` comes back even for a preflight\nthat sent no `Access-Control-Request-Headers` at all:\n\n```ts\nconst app = await createStrandsApp(aguiAgent, {\n  path: \"/invocations\",\n  corsOrigin: [\"https://app.example.com\"],\n  allowMethods: [\"POST\"],\n  allowHeaders: [\"Content-Type\"],\n});\n```\n\nThree details worth knowing:\n\n- Narrowing the method list does not make `cors` reject a preflight for a\n  method outside it. A `DELETE` preflight against `allowMethods: [\"POST\"]`\n  still answers `204` carrying `Access-Control-Allow-Methods: POST`, and the\n  browser is what enforces the narrowing.\n- **A narrowed `allowHeaders` has to include `Content-Type`.** The agent route\n  answers `415` to any request without a JSON `Content-Type` (measured: both an\n  absent `Content-Type` and `text/plain` come back\n  `415 {\"error\":\"Unsupported Media Type: expected application/json\"}`), and\n  `application/json` is not a CORS-safelisted request header value, so a browser\n  only sends it once a preflight has permitted `Content-Type`. Leave it off the\n  list and every cross-origin agent call is blocked, while the preflight still\n  answers `204` carrying the narrowed list: a healthy-looking response for a\n  route nothing can reach. Server-side callers (curl, another service) are\n  unaffected, since CORS never applies to them.\n- **`[]` is a deny-all, not a request for the default.** An empty array is\n  truthy, so it reaches `cors`, which withholds the corresponding header\n  entirely rather than sending it empty. Measured: `allowMethods: []` answers a\n  preflight `204` with no `Access-Control-Allow-Methods`, `allowHeaders: []`\n  with no `Access-Control-Allow-Headers`, and both leave\n  `Access-Control-Allow-Origin` intact. That mirrors how `corsOrigin: []` denies\n  every origin and is deliberate, but the only symptom is in the caller's\n  browser console, so `createStrandsApp` warns at startup when it installs a\n  policy carrying either empty list.\n\nThose defaults deliberately do not match the Python side, where\n`create_strands_app` passes `allow_methods=[\"*\"]` and `allow_headers=[\"*\"]`.\nThe `cors` defaults are already narrower, neither option existed in the\nTypeScript adapter before, so there is no back-compatibility to preserve, and\nwidening them to match would be a security regression rather than parity.\n\nBoth options only mean something once the middleware is installed. Passing\neither with no `corsOrigin` policy throws at construction, naming the options\npassed and the fix, rather than silently doing nothing.\n\n#### What ends up in `Vary`\n\n`Vary` on the preflight is assembled from two halves with independent causes,\nwhich is why there is no single default to quote. Measured across every origin\nposture and every combination of narrowed, empty and omitted `allowMethods` /\n`allowHeaders`:\n\n| Half of `Vary`                   | Present when                                                |\n| -------------------------------- | ----------------------------------------------------------- |\n| `Origin`                         | The origin policy does not resolve to the bare string `\"*\"` |\n| `Access-Control-Request-Headers` | `allowHeaders` is omitted, whatever `allowMethods` says     |\n\nThe `Origin` half is the cache-safety one: it is what stops a shared cache\nserving one origin's response to another, and it turns on and off with the\norigin form rather than with the narrowing options. A `\"*\"` sends the same\n`Access-Control-Allow-Origin` to every caller, so the response does not depend\non who asked and `cors` correctly leaves `Origin` out. That covers the arrays\nthat collapse to `\"*\"` too, since the collapse happens before `cors` is\nconstructed. A single origin string, an array with no `\"*\"` in it (matching or\nnot), `[]` and `true` all emit it.\n\nThe `Access-Control-Request-Headers` half is not about the caller's origin at\nall. It is present only while the answer depends on what the preflight asked\nfor, which stops being true the moment `allowHeaders` fixes the set. Narrowing\n`allowMethods` moves neither half.\n\nThe four combinations that follow, on a preflight:\n\n| Origin policy     | `allowHeaders`   | `Vary`                                   |\n| ----------------- | ---------------- | ---------------------------------------- |\n| resolves to `\"*\"` | omitted          | `Access-Control-Request-Headers`         |\n| resolves to `\"*\"` | narrowed or `[]` | absent entirely                          |\n| anything else     | omitted          | `Origin, Access-Control-Request-Headers` |\n| anything else     | narrowed or `[]` | `Origin`                                 |\n\nNon-preflight responses never carry the `Access-Control-Request-Headers` half.\nThey carry `Vary: Origin` on every posture except the ones resolving to `\"*\"`,\nwhich carry no `Vary` at all, and neither narrowing option changes that.\n\n### One switch for turning CORS off\n\n`corsEnabled` is a veto over `corsOrigin`, for callers that compute the origin\npolicy somewhere else (an env var, shared config) and want one independent\nswitch:\n\n| `corsEnabled`         | `corsOrigin`        | Result                                                                                                      |\n| --------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------- |\n| `false`               | anything, or absent | No middleware. Also silences `allowMethods` / `allowHeaders` with no complaint; `cors` is never even loaded |\n| `undefined` (default) | truthy              | Middleware installed, exactly as without the option                                                         |\n| `undefined` (default) | falsy, or absent    | No middleware                                                                                               |\n| `true`                | truthy              | Middleware installed; redundant but accepted                                                                |\n| `true`                | falsy, or absent    | Throws at construction                                                                                      |\n\n`corsEnabled: false` is byte-identical on the wire to `corsOrigin: false`, so as\na disable switch it is a second spelling. Its value is compositional: one place\nto turn cross-origin access off without reaching into wherever `corsOrigin` is\ncomputed.\n\n`corsEnabled: true` with no origin policy throws rather than installing\nanything, because the two alternatives are both wro","readmeFilename":"README.md"}