From ed8cd365ed75b8b4d510c5ee5ec3431029b9acd5 Mon Sep 17 00:00:00 2001 From: Cameron Pfiffer Date: Tue, 16 Sep 2025 10:41:22 -0700 Subject: [PATCH] docs: improve streaming documentation structure and examples (#2930) --- examples/streaming/README.md | 84 ++ examples/streaming/package-lock.json | 1143 ++++++++++++++++++++++++++ examples/streaming/package.json | 19 + examples/streaming/streaming_demo.py | 101 +++ examples/streaming/streaming_demo.ts | 102 +++ fern/pages/agents/streaming.mdx | 408 ++++++--- 6 files changed, 1735 insertions(+), 122 deletions(-) create mode 100644 examples/streaming/README.md create mode 100644 examples/streaming/package-lock.json create mode 100644 examples/streaming/package.json create mode 100644 examples/streaming/streaming_demo.py create mode 100644 examples/streaming/streaming_demo.ts diff --git a/examples/streaming/README.md b/examples/streaming/README.md new file mode 100644 index 00000000..e6cd9215 --- /dev/null +++ b/examples/streaming/README.md @@ -0,0 +1,84 @@ +# Letta Streaming Examples + +Minimal examples demonstrating Letta's streaming API in both Python and TypeScript. + +## Setup + +1. Set your Letta API key: +```bash +export LETTA_API_KEY="your_api_key_here" +``` + +2. Install dependencies: +```bash +# For TypeScript +npm install + +# For Python +pip install letta-client +``` + +## Run Examples + +### Python +```bash +python streaming_demo.py +``` + +### TypeScript +```bash +npm run demo:typescript +# or directly with tsx: +npx tsx streaming_demo.ts +``` + +## What These Examples Show + +Both examples demonstrate: + +1. **Step Streaming** (default) - Complete messages delivered as they're generated +2. **Token Streaming** - Partial chunks for real-time display (ChatGPT-like UX) + +The key difference: +- Step streaming: Each event contains a complete message +- Token streaming: Multiple events per message, requiring reassembly by message ID + +## Key Concepts + +### Python +```python +# Step streaming (default) +stream = client.agents.messages.create_stream( + agent_id=agent.id, + messages=[{"role": "user", "content": "Hello!"}] +) + +# Token streaming +stream = client.agents.messages.create_stream( + agent_id=agent.id, + messages=[{"role": "user", "content": "Hello!"}], + stream_tokens=True # Enable token streaming +) +``` + +### TypeScript +```typescript +// Step streaming (default) +const stream = await client.agents.messages.createStream( + agentId, { + messages: [{role: "user", content: "Hello!"}] + } +); + +// Token streaming +const stream = await client.agents.messages.createStream( + agentId, { + messages: [{role: "user", content: "Hello!"}], + streamTokens: true // Enable token streaming + } +); +``` + +## Learn More + +See the full documentation at [docs.letta.com/guides/agents/streaming](https://docs.letta.com/guides/agents/streaming) \ No newline at end of file diff --git a/examples/streaming/package-lock.json b/examples/streaming/package-lock.json new file mode 100644 index 00000000..fb29828b --- /dev/null +++ b/examples/streaming/package-lock.json @@ -0,0 +1,1143 @@ +{ + "name": "streaming", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "streaming", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@letta-ai/letta-client": "^0.0.68646", + "@types/node": "^24.4.0", + "tsx": "^4.20.5", + "typescript": "^5.9.2" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", + "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", + "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", + "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", + "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", + "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", + "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", + "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", + "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", + "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", + "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", + "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", + "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", + "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", + "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", + "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", + "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", + "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", + "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", + "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", + "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", + "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", + "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", + "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", + "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", + "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", + "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@letta-ai/letta-client": { + "version": "0.0.68646", + "resolved": "https://registry.npmjs.org/@letta-ai/letta-client/-/letta-client-0.0.68646.tgz", + "integrity": "sha512-Mjyjn9+XM27NM25/ArI19MhvJt0uAEox/4htlyz09AaBwCbLUPe4Iwt573AnlGdSe4oLLb9B9kTlxvl3nS6KOQ==", + "dependencies": { + "form-data": "^4.0.0", + "form-data-encoder": "^4.0.2", + "formdata-node": "^6.0.3", + "node-fetch": "^2.7.0", + "qs": "^6.13.1", + "readable-stream": "^4.5.2", + "url-join": "4.0.1" + } + }, + "node_modules/@types/node": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.4.0.tgz", + "integrity": "sha512-gUuVEAK4/u6F9wRLznPUU4WGUacSEBDPoC2TrBkw3GAnOLHBL45QdfHOXp1kJ4ypBGLxTOB+t7NJLpKoC3gznQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.11.0" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", + "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.9", + "@esbuild/android-arm": "0.25.9", + "@esbuild/android-arm64": "0.25.9", + "@esbuild/android-x64": "0.25.9", + "@esbuild/darwin-arm64": "0.25.9", + "@esbuild/darwin-x64": "0.25.9", + "@esbuild/freebsd-arm64": "0.25.9", + "@esbuild/freebsd-x64": "0.25.9", + "@esbuild/linux-arm": "0.25.9", + "@esbuild/linux-arm64": "0.25.9", + "@esbuild/linux-ia32": "0.25.9", + "@esbuild/linux-loong64": "0.25.9", + "@esbuild/linux-mips64el": "0.25.9", + "@esbuild/linux-ppc64": "0.25.9", + "@esbuild/linux-riscv64": "0.25.9", + "@esbuild/linux-s390x": "0.25.9", + "@esbuild/linux-x64": "0.25.9", + "@esbuild/netbsd-arm64": "0.25.9", + "@esbuild/netbsd-x64": "0.25.9", + "@esbuild/openbsd-arm64": "0.25.9", + "@esbuild/openbsd-x64": "0.25.9", + "@esbuild/openharmony-arm64": "0.25.9", + "@esbuild/sunos-x64": "0.25.9", + "@esbuild/win32-arm64": "0.25.9", + "@esbuild/win32-ia32": "0.25.9", + "@esbuild/win32-x64": "0.25.9" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-4.1.0.tgz", + "integrity": "sha512-G6NsmEW15s0Uw9XnCg+33H3ViYRyiM0hMrMhhqQOR8NFc5GhYrI+6I3u7OTw7b91J2g8rtvMBZJDbcGb2YUniw==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/formdata-node": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-6.0.3.tgz", + "integrity": "sha512-8e1++BCiTzUno9v5IZ2J6bv4RU+3UKDmqWUQD0MIMVCd9AdhWkO1gw57oo1mNEX1dMq2EGI+FbWz4B92pscSQg==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/tsx": { + "version": "4.20.5", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.5.tgz", + "integrity": "sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw==", + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.11.0.tgz", + "integrity": "sha512-kt1ZriHTi7MU+Z/r9DOdAI3ONdaR3M3csEaRc6ewa4f4dTvX4cQCbJ4NkEn0ohE4hHtq85+PhPSTY+pO/1PwgA==", + "license": "MIT" + }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "license": "MIT" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } +} diff --git a/examples/streaming/package.json b/examples/streaming/package.json new file mode 100644 index 00000000..a830ffca --- /dev/null +++ b/examples/streaming/package.json @@ -0,0 +1,19 @@ +{ + "name": "letta-streaming-examples", + "version": "1.0.0", + "description": "Examples demonstrating Letta's streaming API in Python and TypeScript", + "scripts": { + "demo:python": "python streaming_demo.py", + "demo:typescript": "tsx streaming_demo.ts", + "demo": "npm run demo:typescript" + }, + "keywords": ["letta", "streaming", "ai", "agents"], + "author": "", + "license": "MIT", + "dependencies": { + "@letta-ai/letta-client": "^0.0.68646", + "@types/node": "^24.4.0", + "tsx": "^4.20.5", + "typescript": "^5.9.2" + } +} diff --git a/examples/streaming/streaming_demo.py b/examples/streaming/streaming_demo.py new file mode 100644 index 00000000..3999bc87 --- /dev/null +++ b/examples/streaming/streaming_demo.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +""" +Minimal examples showing Letta's streaming API. +""" + +import os +from typing import Dict, Any +from letta_client import Letta + + +def step_streaming_example(client: Letta, agent_id: str): + """Step streaming: receive complete messages as they're generated.""" + # Send a message with step streaming (default) + stream = client.agents.messages.create_stream( + agent_id=agent_id, + messages=[{ + "role": "user", + "content": "Hi! My name is Alice. What's 2+2?" + }] + ) + + for chunk in stream: + # Each chunk is a complete message + if hasattr(chunk, 'message_type'): + if chunk.message_type == 'assistant_message': + print(chunk.content) + + +def token_streaming_example(client: Letta, agent_id: str): + """Token streaming: receive partial chunks for real-time display.""" + # Send a message with token streaming enabled + stream = client.agents.messages.create_stream( + agent_id=agent_id, + messages=[{ + "role": "user", + "content": "What's my name? And tell me a short joke." + }], + stream_tokens=True # Enable token streaming + ) + + # Track messages by ID for reassembly + message_accumulators: Dict[str, str] = {} + + for chunk in stream: + if hasattr(chunk, 'id') and chunk.message_type == 'assistant_message': + msg_id = chunk.id + + # Initialize accumulator for new messages + if msg_id not in message_accumulators: + message_accumulators[msg_id] = '' + + # Accumulate and print content + content_chunk = chunk.content or '' + message_accumulators[msg_id] += content_chunk + print(content_chunk, end="", flush=True) + + print() # New line after streaming completes + + +def main(): + # Check for API key + api_key = os.environ.get("LETTA_API_KEY") + if not api_key: + print("Please set LETTA_API_KEY environment variable") + return + + # Initialize client + client = Letta(token=api_key) + + # Create a test agent + agent = client.agents.create( + model="openai/gpt-4o-mini", + embedding="openai/text-embedding-3-small", + memory_blocks=[ + { + "label": "human", + "value": "The user is exploring streaming capabilities." + }, + { + "label": "persona", + "value": "I am a helpful assistant demonstrating streaming responses." + } + ] + ) + + try: + # Example 1: Step Streaming (default) + print("\nStep Streaming (complete messages):") + step_streaming_example(client, agent.id) + + # Example 2: Token Streaming + print("\nToken Streaming (real-time chunks):") + token_streaming_example(client, agent.id) + + finally: + # Clean up + client.agents.delete(agent.id) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/examples/streaming/streaming_demo.ts b/examples/streaming/streaming_demo.ts new file mode 100644 index 00000000..4b60e1be --- /dev/null +++ b/examples/streaming/streaming_demo.ts @@ -0,0 +1,102 @@ +#!/usr/bin/env tsx +/** + * Minimal TypeScript examples showing Letta's streaming API. + * Demonstrates both step streaming (default) and token streaming modes. + */ + +import { LettaClient } from '@letta-ai/letta-client'; +import type { LettaMessage } from '@letta-ai/letta-client/api/types'; + +async function stepStreamingExample(client: LettaClient, agentId: string): Promise { + console.log('\nStep Streaming (complete messages):'); + + // Send a message with step streaming (default) + const stream = await client.agents.messages.createStream( + agentId, { + messages: [{role: "user", content: "Hi! My name is Alice. What's 2+2?"}] + } + ); + + for await (const chunk of stream as AsyncIterable) { + // Each chunk is a complete message + if (chunk.messageType === 'assistant_message') { + console.log((chunk as any).content); + } + } +} + +async function tokenStreamingExample(client: LettaClient, agentId: string): Promise { + console.log('\nToken Streaming (real-time chunks):'); + + // Send a message with token streaming enabled + const stream = await client.agents.messages.createStream( + agentId, { + messages: [{role: "user", content: "What's my name? And tell me a short joke."}], + streamTokens: true // Enable token streaming + } + ); + + // Track messages by ID for reassembly + const messageAccumulators = new Map(); + + for await (const chunk of stream as AsyncIterable) { + if (chunk.id && chunk.messageType === 'assistant_message') { + const msgId = chunk.id; + + // Initialize accumulator for new messages + if (!messageAccumulators.has(msgId)) { + messageAccumulators.set(msgId, ''); + } + + // Accumulate and print content + const contentChunk = (chunk as any).content || ''; + messageAccumulators.set(msgId, messageAccumulators.get(msgId)! + contentChunk); + process.stdout.write(contentChunk); + } + } + + console.log(); // New line after streaming completes +} + +async function main(): Promise { + // Check for API key + const apiKey = process.env.LETTA_API_KEY; + if (!apiKey) { + console.error('Please set LETTA_API_KEY environment variable'); + process.exit(1); + } + + // Initialize client + const client = new LettaClient({ token: apiKey }); + + // Create a test agent + const agent = await client.agents.create({ + model: "openai/gpt-4o-mini", + embedding: "openai/text-embedding-3-small", + memoryBlocks: [ + { + label: "human", + value: "The user is exploring streaming capabilities." + }, + { + label: "persona", + value: "I am a helpful assistant demonstrating streaming responses." + } + ] + }); + + try { + // Example 1: Step Streaming (default) + await stepStreamingExample(client, agent.id); + + // Example 2: Token Streaming + await tokenStreamingExample(client, agent.id); + + } finally { + // Clean up + await client.agents.delete(agent.id); + } +} + +// Run the example +main().catch(console.error); \ No newline at end of file diff --git a/fern/pages/agents/streaming.mdx b/fern/pages/agents/streaming.mdx index 9064c54b..c6959e57 100644 --- a/fern/pages/agents/streaming.mdx +++ b/fern/pages/agents/streaming.mdx @@ -6,162 +6,326 @@ slug: guides/agents/streaming Messages from the **Letta server** can be **streamed** to the client. If you're building a UI on the Letta API, enabling streaming allows your UI to update in real-time as the agent generates a response to an input message. -There are two kinds of streaming you can enable: **streaming agent steps** and **streaming tokens**. -To enable streaming (either mode), you need to use the [`/v1/agent/messages/stream`](/api-reference/agents/messages/stream) API route instead of the [`/v1/agent/messages`](/api-reference/agents/messages) API route. - When working with agents that execute long-running operations (e.g., complex tool calls, extensive searches, or code execution), you may encounter timeouts with the message routes. See our [tips on handling long-running tasks](/guides/agents/long-running) for more info. -## Streaming agent steps +## Quick Start -When you send a message to the Letta server, the agent may run multiple steps while generating a response. -For example, an agent may run a search query, then use the results of that query to generate a response. +Letta supports two streaming modes: **step streaming** (default) and **token streaming**. + +To enable streaming, use the [`/v1/agents/{agent_id}/messages/stream`](/api-reference/agents/messages/stream) endpoint instead of `/messages`: -When you use the `/messages/stream` route, `stream_steps` is enabled by default, and the response to the `POST` request will stream back as server-sent events (read more about SSE format [here](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events)): -```curl curl -curl --request POST \ - --url http://localhost:8283/v1/agents/$AGENT_ID/messages/stream \ - --header 'Content-Type: application/json' \ - --data '{ - "messages": [ - { - "role": "user", - "content": "hows it going????" - } - ] -}' -``` -```python title="python" maxLines=50 -# send a message to the agent (streaming steps) +```python title="python" +# Step streaming (default) - returns complete messages stream = client.agents.messages.create_stream( - agent_id=agent_state.id, - messages=[ - { - "role": "user", - "content": "hows it going????" - } - ], + agent_id=agent.id, + messages=[{"role": "user", "content": "Hello!"}] ) - -# print the chunks coming back for chunk in stream: - print(chunk) + print(chunk) # Complete message objects + +# Token streaming - returns partial chunks for real-time UX +stream = client.agents.messages.create_stream( + agent_id=agent.id, + messages=[{"role": "user", "content": "Hello!"}], + stream_tokens=True # Enable token streaming +) +for chunk in stream: + print(chunk) # Partial content chunks ``` -```typescript maxLines=50 title="node.js" -// send a message to the agent (streaming steps) -const stream = await client.agents.messages.create_stream( - agentState.id, { - messages: [ - { - role: "user", - content: "hows it going????" - } - ] + +```typescript title="typescript" +import { LettaClient } from '@letta-ai/letta-client'; + +const client = new LettaClient({ token: 'YOUR_API_KEY' }); + +// Step streaming (default) - returns complete messages +const stream = await client.agents.messages.createStream( + agent.id, { + messages: [{role: "user", content: "Hello!"}] } ); - -// print the chunks coming back for await (const chunk of stream) { - console.log(chunk); -}; + console.log(chunk); // Complete message objects +} + +// Token streaming - returns partial chunks for real-time UX +const tokenStream = await client.agents.messages.createStream( + agent.id, { + messages: [{role: "user", content: "Hello!"}], + streamTokens: true // Enable token streaming + } +); +for await (const chunk of tokenStream) { + console.log(chunk); // Partial content chunks +} ``` -```json maxLines=50 -data: {"id":"...","date":"...","message_type":"reasoning_message","reasoning":"User keeps asking the same question; maybe it's part of their style or humor. I\u2019ll respond warmly and play along."} +## Streaming Modes Comparison -data: {"id":"...","date":"...","message_type":"assistant_message","assistant_message":"Hey! It\u2019s going well! Still here, ready to chat. How about you? Anything exciting happening?"} +| Aspect | Step Streaming (default) | Token Streaming | +|--------|-------------------------|-----------------| +| **What you get** | Complete messages after each step | Partial chunks as tokens generate | +| **When to use** | Simple implementation | ChatGPT-like real-time UX | +| **Reassembly needed** | No | Yes (by message ID) | +| **Message IDs** | Unique per message | Same ID across chunks | +| **Content format** | Full text in each message | Incremental text pieces | +| **Enable with** | Default behavior | `stream_tokens: true` | -data: {"message_type":"usage_statistics","completion_tokens":65,"prompt_tokens":2329,"total_tokens":2394,"step_count":1} +## Understanding Message Flow -data: [DONE] -``` +### Message Types and Flow Patterns -## Streaming tokens +The messages you receive depend on your agent's configuration: -You can also stream chunks of tokens from the agent as they are generated by the underlying LLM process by setting `stream_tokens` to `true` in your API request: - -```curl curl -curl --request POST \ - --url http://localhost:8283/v1/agents/$AGENT_ID/messages/stream \ - --header 'Content-Type: application/json' \ - --data '{ - "messages": [ - { - "role": "user", - "content": "hows it going????" - } - ], - "stream_tokens": true -}' -``` -```python title="python" maxLines=50 -# send a message to the agent (streaming steps) -stream = client.agents.messages.create_stream( - agent_id=agent_state.id, - messages=[ - { - "role": "user", - "content": "hows it going????" - } - ], - stream_tokens=True, +**With reasoning enabled (default):** +- Simple response: `reasoning_message` → `assistant_message` +- With tool use: `reasoning_message` → `tool_call_message` → `tool_return_message` → `reasoning_message` → `assistant_message` + +**With reasoning disabled (`reasoning=false`):** +- Simple response: `assistant_message` +- With tool use: `tool_call_message` → `tool_return_message` → `assistant_message` + +### Message Type Reference + +- **`reasoning_message`**: Agent's internal thinking process (only when `reasoning=true`) +- **`assistant_message`**: The actual response shown to the user +- **`tool_call_message`**: Request to execute a tool +- **`tool_return_message`**: Result from tool execution +- **`stop_reason`**: Indicates end of response (`end_turn`) +- **`usage_statistics`**: Token usage and step count metrics + +### Controlling Reasoning Messages + +```python +# With reasoning (default) - includes reasoning_message events +agent = client.agents.create( + model="openai/gpt-4o-mini", + # reasoning=True is the default ) -# print the chunks coming back -for chunk in stream: - print(chunk) +# Without reasoning - no reasoning_message events +agent = client.agents.create( + model="openai/gpt-4o-mini", + reasoning=False # Disable reasoning messages +) ``` -```typescript maxLines=50 title="node.js" -// send a message to the agent (streaming steps) -const stream = await client.agents.messages.create_stream( - agentState.id, { - messages: [ - { - role: "user", - content: "hows it going????" - } - ], - streamTokens: true + +## Step Streaming (Default) + +Step streaming delivers **complete messages** after each agent step completes. This is the default behavior when you use the streaming endpoint. + +### How It Works + +1. Agent processes your request through steps (reasoning, tool calls, generating responses) +2. After each step completes, you receive a complete `LettaMessage` via SSE +3. Each message can be processed immediately without reassembly + +### Example + + +```python title="python" +stream = client.agents.messages.create_stream( + agent_id=agent.id, + messages=[{"role": "user", "content": "What's 2+2?"}] +) + +for chunk in stream: + if hasattr(chunk, 'message_type'): + if chunk.message_type == 'reasoning_message': + print(f"Thinking: {chunk.reasoning}") + elif chunk.message_type == 'assistant_message': + print(f"Response: {chunk.content}") +``` + +```typescript title="typescript" +import { LettaClient } from '@letta-ai/letta-client'; +import type { LettaMessage } from '@letta-ai/letta-client/api/types'; + +const client = new LettaClient({ token: 'YOUR_API_KEY' }); + +const stream = await client.agents.messages.createStream( + agent.id, { + messages: [{role: "user", content: "What's 2+2?"}] } ); -// print the chunks coming back -for await (const chunk of stream) { - console.log(chunk); -}; +for await (const chunk of stream as AsyncIterable) { + if (chunk.messageType === 'reasoning_message') { + console.log(`Thinking: ${(chunk as any).reasoning}`); + } else if (chunk.messageType === 'assistant_message') { + console.log(`Response: ${(chunk as any).content}`); + } +} +``` + +```bash title="curl" +curl -N --request POST \ + --url https://api.letta.com/v1/agents/$AGENT_ID/messages/stream \ + --header "Authorization: Bearer $LETTA_API_KEY" \ + --header 'Content-Type: application/json' \ + --data '{"messages": [{"role": "user", "content": "What is 2+2?"}]}' + +# For self-hosted: Replace https://api.letta.com with http://localhost:8283 ``` -With token streaming enabled, the response will look very similar to the prior example (agent steps streaming), but instead of receiving complete messages, the client receives multiple messages with chunks of the response. -The client is responsible for reassembling the response from the chunks. -We've ommited most of the chunks for brevity: -```sh -data: {"id":"...","date":"...","message_type":"reasoning_message","reasoning":"It's"} - -data: {"id":"...","date":"...","message_type":"reasoning_message","reasoning":" interesting"} - -... chunks ommited - -data: {"id":"...","date":"...","message_type":"reasoning_message","reasoning":"!"} - -data: {"id":"...","date":"...","message_type":"assistant_message","assistant_message":"Well"} - -... chunks ommited - -data: {"id":"...","date":"...","message_type":"assistant_message","assistant_message":"."} - -data: {"message_type":"usage_statistics","completion_tokens":50,"prompt_tokens":2771,"total_tokens":2821,"step_count":1} +### Example Output +``` +data: {"id":"msg-123","message_type":"reasoning_message","reasoning":"User is asking a simple math question."} +data: {"id":"msg-456","message_type":"assistant_message","content":"2 + 2 equals 4!"} +data: {"message_type":"stop_reason","stop_reason":"end_turn"} +data: {"message_type":"usage_statistics","completion_tokens":50,"total_tokens":2821} data: [DONE] ``` -## Tips on handling streaming in your client code -The data structure for token streaming is the same as for agent steps streaming (`LettaMessage`) - just instead of returning complete messages, the Letta server will return multiple messages each with a chunk of the response. -Because the format of the data looks the same, if you write your frontend code to handle tokens streaming, it will also work for agent steps streaming. +## Token Streaming -For example, if the Letta server is connected to multiple LLM backend providers and only a subset of them support LLM token streaming, you can use the same frontend code (interacting with the Letta API) to handle both streaming and non-streaming providers. -If you send a message to an agent with streaming enabled (`stream_tokens` are `true`), the server will stream back `LettaMessage` objects with chunks if the selected LLM provider supports token streaming, and `LettaMessage` objects with complete strings if the selected LLM provider does not support token streaming. +Token streaming provides **partial content chunks** as they're generated by the LLM, enabling a ChatGPT-like experience where text appears character by character. + +### How It Works + +1. Set `stream_tokens: true` in your request +2. Receive multiple chunks with the **same message ID** +3. Each chunk contains a piece of the content +4. Client must accumulate chunks by ID to rebuild complete messages + +### Example with Reassembly + + +```python title="python" +# Token streaming with reassembly +message_accumulators = {} + +stream = client.agents.messages.create_stream( + agent_id=agent.id, + messages=[{"role": "user", "content": "Tell me a joke"}], + stream_tokens=True +) + +for chunk in stream: + if hasattr(chunk, 'id') and hasattr(chunk, 'message_type'): + msg_id = chunk.id + msg_type = chunk.message_type + + # Initialize accumulator for new messages + if msg_id not in message_accumulators: + message_accumulators[msg_id] = { + 'type': msg_type, + 'content': '' + } + + # Accumulate content + if msg_type == 'reasoning_message': + message_accumulators[msg_id]['content'] += chunk.reasoning + elif msg_type == 'assistant_message': + message_accumulators[msg_id]['content'] += chunk.content + + # Display accumulated content in real-time + print(message_accumulators[msg_id]['content'], end='', flush=True) +``` + +```typescript title="typescript" +import { LettaClient } from '@letta-ai/letta-client'; +import type { LettaMessage } from '@letta-ai/letta-client/api/types'; + +const client = new LettaClient({ token: 'YOUR_API_KEY' }); + +// Token streaming with reassembly +interface MessageAccumulator { + type: string; + content: string; +} + +const messageAccumulators = new Map(); + +const stream = await client.agents.messages.createStream( + agent.id, { + messages: [{role: "user", content: "Tell me a joke"}], + streamTokens: true // Note: camelCase + } +); + +for await (const chunk of stream as AsyncIterable) { + if (chunk.id && chunk.messageType) { + const msgId = chunk.id; + const msgType = chunk.messageType; + + // Initialize accumulator for new messages + if (!messageAccumulators.has(msgId)) { + messageAccumulators.set(msgId, { + type: msgType, + content: '' + }); + } + + // Accumulate content based on message type + const acc = messageAccumulators.get(msgId)!; + + // Only accumulate if the type matches (in case types share IDs) + if (acc.type === msgType) { + if (msgType === 'reasoning_message') { + acc.content += (chunk as any).reasoning || ''; + } else if (msgType === 'assistant_message') { + acc.content += (chunk as any).content || ''; + } + } + + // Update UI with accumulated content + process.stdout.write(acc.content); + } +} +``` + +```bash title="curl" +curl -N --request POST \ + --url https://api.letta.com/v1/agents/$AGENT_ID/messages/stream \ + --header "Authorization: Bearer $LETTA_API_KEY" \ + --header 'Content-Type: application/json' \ + --data '{ + "messages": [{"role": "user", "content": "Tell me a joke"}], + "stream_tokens": true + }' +``` + + +### Example Output + +``` +# Same ID across chunks of the same message +data: {"id":"msg-abc","message_type":"assistant_message","content":"Why"} +data: {"id":"msg-abc","message_type":"assistant_message","content":" did"} +data: {"id":"msg-abc","message_type":"assistant_message","content":" the"} +data: {"id":"msg-abc","message_type":"assistant_message","content":" scarecrow"} +data: {"id":"msg-abc","message_type":"assistant_message","content":" win"} +# ... more chunks with same ID +data: [DONE] +``` + +## Implementation Tips + +### Universal Handling Pattern + +The accumulator pattern shown above works for **both** streaming modes: +- **Step streaming**: Each message is complete (single chunk per ID) +- **Token streaming**: Multiple chunks per ID need accumulation + +This means you can write your client code once to handle both cases. + +### SSE Format Notes + +All streaming responses follow the Server-Sent Events (SSE) format: +- Each event starts with `data: ` followed by JSON +- Stream ends with `data: [DONE]` +- Empty lines separate events + +Learn more about SSE format [here](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events). + +### Handling Different LLM Providers + +If your Letta server connects to multiple LLM providers, some may not support token streaming. Your client code will still work - the server will fall back to step streaming automatically when token streaming isn't available. \ No newline at end of file