feat: migrate to Letta TS SDK v1 (alpha) (#11)

This commit is contained in:
Charles Packer
2025-10-28 23:50:57 -07:00
committed by GitHub
parent 275fca942d
commit 4ca01d199d
17 changed files with 377 additions and 332 deletions

228
bun.lock
View File

@@ -2,12 +2,12 @@
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "letta-code",
"name": "@letta-ai/letta-code",
"dependencies": {
"@letta-ai/letta-client": "1.0.0-alpha.10",
"ink-link": "^5.0.0",
},
"devDependencies": {
"@letta-ai/letta-client": "1.0.0-alpha.2",
"@types/bun": "latest",
"@types/diff": "^8.0.0",
"diff": "^8.0.2",
@@ -32,133 +32,81 @@
"@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="],
"@letta-ai/letta-client": ["@letta-ai/letta-client@1.0.0-alpha.2", "", { "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" } }, ""],
"@letta-ai/letta-client": ["@letta-ai/letta-client@1.0.0-alpha.10", "", {}, "sha512-UV9b5rbkEPX2+7Dp0gD3Hk8KHbubz6K3GoAF1u813UZCguJcQO2azXNmjyvinQSPhKxlB/WYT/79toB4ElljSw=="],
"@types/bun": ["@types/bun@1.3.0", "", { "dependencies": { "bun-types": "1.3.0" } }, "sha512-+lAGCYjXjip2qY375xX/scJeVRmZ5cY0wyHYyCYxNcdEXrQ4AOe3gACgd4iQ8ksOslJtW4VNxBJ8llUwc3a6AA=="],
"@types/bun": ["@types/bun@1.3.1", "", { "dependencies": { "bun-types": "1.3.1" } }, "sha512-4jNMk2/K9YJtfqwoAa28c8wK+T7nvJFOjxI4h/7sORWcypRNxBpr+TPNaCfVWq70tLCJsqoFwcf0oI0JU/fvMQ=="],
"@types/diff": ["@types/diff@8.0.0", "", { "dependencies": { "diff": "*" } }, ""],
"@types/diff": ["@types/diff@8.0.0", "", { "dependencies": { "diff": "*" } }, "sha512-o7jqJM04gfaYrdCecCVMbZhNdG6T1MHg/oQoRFdERLV+4d+V7FijhiEAbFu0Usww84Yijk9yH58U4Jk4HbtzZw=="],
"@types/node": ["@types/node@24.7.2", "", { "dependencies": { "undici-types": "~7.14.0" } }, ""],
"@types/node": ["@types/node@24.9.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg=="],
"@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, ""],
"@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="],
"@vscode/ripgrep": ["@vscode/ripgrep@1.17.0", "", { "dependencies": { "https-proxy-agent": "^7.0.2", "proxy-from-env": "^1.1.0", "yauzl": "^2.9.2" } }, "sha512-mBRKm+ASPkUcw4o9aAgfbusIu6H4Sdhw09bjeP1YOBFTJEZAnrnk6WZwzv8NEjgC82f7ILvhmb1WIElSugea6g=="],
"abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, ""],
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
"ansi-escapes": ["ansi-escapes@7.1.1", "", { "dependencies": { "environment": "^1.0.0" } }, ""],
"ansi-escapes": ["ansi-escapes@7.1.1", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-Zhl0ErHcSRUaVfGUeUdDuLgpkEo8KIFjB4Y9uAc46ScOpdDiU1Dbyplh7qWJeJ/ZHpbyMSM26+X3BySgnIz40Q=="],
"ansi-regex": ["ansi-regex@6.2.2", "", {}, ""],
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
"ansi-styles": ["ansi-styles@6.2.3", "", {}, ""],
"ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
"asynckit": ["asynckit@0.4.0", "", {}, ""],
"auto-bind": ["auto-bind@5.0.1", "", {}, "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg=="],
"auto-bind": ["auto-bind@5.0.1", "", {}, ""],
"base64-js": ["base64-js@1.5.1", "", {}, ""],
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, ""],
"buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, ""],
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
"buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="],
"bun-types": ["bun-types@1.3.0", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, ""],
"bun-types": ["bun-types@1.3.1", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-NMrcy7smratanWJ2mMXdpatalovtxVggkj11bScuWuiOoXTiKIu2eVS1/7qbyI/4yHedtsn175n4Sm4JcdHLXw=="],
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, ""],
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, ""],
"cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="],
"chalk": ["chalk@5.6.2", "", {}, ""],
"cli-boxes": ["cli-boxes@3.0.0", "", {}, ""],
"cli-cursor": ["cli-cursor@4.0.0", "", { "dependencies": { "restore-cursor": "^4.0.0" } }, ""],
"cli-cursor": ["cli-cursor@4.0.0", "", { "dependencies": { "restore-cursor": "^4.0.0" } }, "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg=="],
"cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="],
"cli-truncate": ["cli-truncate@4.0.0", "", { "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^7.0.0" } }, ""],
"cli-truncate": ["cli-truncate@4.0.0", "", { "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^7.0.0" } }, "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA=="],
"code-excerpt": ["code-excerpt@4.0.0", "", { "dependencies": { "convert-to-spaces": "^2.0.1" } }, ""],
"code-excerpt": ["code-excerpt@4.0.0", "", { "dependencies": { "convert-to-spaces": "^2.0.1" } }, "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA=="],
"colorette": ["colorette@2.0.20", "", {}, ""],
"colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="],
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, ""],
"commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="],
"commander": ["commander@14.0.1", "", {}, ""],
"convert-to-spaces": ["convert-to-spaces@2.0.1", "", {}, "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ=="],
"convert-to-spaces": ["convert-to-spaces@2.0.1", "", {}, ""],
"csstype": ["csstype@3.1.3", "", {}, ""],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"delayed-stream": ["delayed-stream@1.0.0", "", {}, ""],
"diff": ["diff@8.0.2", "", {}, "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg=="],
"diff": ["diff@8.0.2", "", {}, ""],
"emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, ""],
"environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="],
"emoji-regex": ["emoji-regex@10.5.0", "", {}, ""],
"es-toolkit": ["es-toolkit@1.41.0", "", {}, "sha512-bDd3oRmbVgqZCJS6WmeQieOrzpl3URcWBUVDXxOELlUW2FuW+0glPOz1n0KnRie+PdyvUZcXz2sOn00c6pPRIA=="],
"environment": ["environment@1.1.0", "", {}, ""],
"escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="],
"es-define-property": ["es-define-property@1.0.1", "", {}, ""],
"es-errors": ["es-errors@1.3.0", "", {}, ""],
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, ""],
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, ""],
"es-toolkit": ["es-toolkit@1.40.0", "", {}, ""],
"escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, ""],
"event-target-shim": ["event-target-shim@5.0.1", "", {}, ""],
"eventemitter3": ["eventemitter3@5.0.1", "", {}, ""],
"events": ["events@3.3.0", "", {}, ""],
"eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="],
"fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="],
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, ""],
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
"form-data": ["form-data@4.0.4", "", { "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" } }, ""],
"form-data-encoder": ["form-data-encoder@4.1.0", "", {}, ""],
"formdata-node": ["formdata-node@6.0.3", "", {}, ""],
"function-bind": ["function-bind@1.1.2", "", {}, ""],
"get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, ""],
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "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" } }, ""],
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, ""],
"gopd": ["gopd@1.2.0", "", {}, ""],
"get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="],
"has-flag": ["has-flag@5.0.1", "", {}, "sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA=="],
"has-symbols": ["has-symbols@1.1.0", "", {}, ""],
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, ""],
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, ""],
"https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
"husky": ["husky@9.1.7", "", { "bin": "bin.js" }, ""],
"husky": ["husky@9.1.7", "", { "bin": { "husky": "bin.js" } }, "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA=="],
"ieee754": ["ieee754@1.2.1", "", {}, ""],
"indent-string": ["indent-string@5.0.0", "", {}, ""],
"indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="],
"ink": ["ink@5.2.1", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.1.3", "ansi-escapes": "^7.0.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.3.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^4.0.0", "code-excerpt": "^4.0.0", "es-toolkit": "^1.22.0", "indent-string": "^5.0.0", "is-in-ci": "^1.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.29.0", "scheduler": "^0.23.0", "signal-exit": "^3.0.7", "slice-ansi": "^7.1.0", "stack-utils": "^2.0.6", "string-width": "^7.2.0", "type-fest": "^4.27.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=18.0.0", "react": ">=18.0.0", "react-devtools-core": "^4.19.1" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-BqcUyWrG9zq5HIwW6JcfFHsIYebJkWWb4fczNah1goUO0vv5vneIlfwuS85twyJ5hYR/y18FlAYUxrO9ChIWVg=="],
@@ -168,95 +116,67 @@
"ink-text-input": ["ink-text-input@5.0.1", "", { "dependencies": { "chalk": "^5.2.0", "type-fest": "^3.6.1" }, "peerDependencies": { "ink": "^4.0.0", "react": "^18.0.0" } }, "sha512-crnsYJalG4EhneOFnr/q+Kzw1RgmXI2KsBaLFE6mpiIKxAtJLUnvygOF2IUKO8z4nwkSkveGRBMd81RoYdRSag=="],
"is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, ""],
"is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="],
"is-in-ci": ["is-in-ci@1.0.0", "", { "bin": { "is-in-ci": "cli.js" } }, "sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg=="],
"is-number": ["is-number@7.0.0", "", {}, ""],
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"lint-staged": ["lint-staged@16.2.4", "", { "dependencies": { "commander": "^14.0.1", "listr2": "^9.0.4", "micromatch": "^4.0.8", "nano-spawn": "^2.0.0", "pidtree": "^0.6.0", "string-argv": "^0.3.2", "yaml": "^2.8.1" }, "bin": "bin/lint-staged.js" }, ""],
"lint-staged": ["lint-staged@16.2.4", "", { "dependencies": { "commander": "^14.0.1", "listr2": "^9.0.4", "micromatch": "^4.0.8", "nano-spawn": "^2.0.0", "pidtree": "^0.6.0", "string-argv": "^0.3.2", "yaml": "^2.8.1" }, "bin": { "lint-staged": "bin/lint-staged.js" } }, "sha512-Pkyr/wd90oAyXk98i/2KwfkIhoYQUMtss769FIT9hFM5ogYZwrk+GRE46yKXSg2ZGhcJ1p38Gf5gmI5Ohjg2yg=="],
"listr2": ["listr2@9.0.4", "", { "dependencies": { "cli-truncate": "^5.0.0", "colorette": "^2.0.20", "eventemitter3": "^5.0.1", "log-update": "^6.1.0", "rfdc": "^1.4.1", "wrap-ansi": "^9.0.0" } }, ""],
"listr2": ["listr2@9.0.5", "", { "dependencies": { "cli-truncate": "^5.0.0", "colorette": "^2.0.20", "eventemitter3": "^5.0.1", "log-update": "^6.1.0", "rfdc": "^1.4.1", "wrap-ansi": "^9.0.0" } }, "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g=="],
"log-update": ["log-update@6.1.0", "", { "dependencies": { "ansi-escapes": "^7.0.0", "cli-cursor": "^5.0.0", "slice-ansi": "^7.1.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, ""],
"log-update": ["log-update@6.1.0", "", { "dependencies": { "ansi-escapes": "^7.0.0", "cli-cursor": "^5.0.0", "slice-ansi": "^7.1.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w=="],
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, ""],
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, ""],
"mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
"mime-db": ["mime-db@1.52.0", "", {}, ""],
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, ""],
"mimic-fn": ["mimic-fn@2.1.0", "", {}, ""],
"mimic-function": ["mimic-function@5.0.1", "", {}, ""],
"mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="],
"minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"nano-spawn": ["nano-spawn@2.0.0", "", {}, ""],
"nano-spawn": ["nano-spawn@2.0.0", "", {}, "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw=="],
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, ""],
"onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
"object-inspect": ["object-inspect@1.13.4", "", {}, ""],
"onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, ""],
"patch-console": ["patch-console@2.0.0", "", {}, ""],
"patch-console": ["patch-console@2.0.0", "", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="],
"pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="],
"picomatch": ["picomatch@2.3.1", "", {}, ""],
"picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"pidtree": ["pidtree@0.6.0", "", { "bin": "bin/pidtree.js" }, ""],
"process": ["process@0.11.10", "", {}, ""],
"pidtree": ["pidtree@0.6.0", "", { "bin": { "pidtree": "bin/pidtree.js" } }, "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g=="],
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
"qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, ""],
"react": ["react@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="],
"react-reconciler": ["react-reconciler@0.29.2", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-zZQqIiYgDCTP/f1N/mAR10nJGrPD2ZR+jDSEsKWJHYC7Cm2wodlwbR3upZRdC3cjIjSlTLNVyO7Iu0Yy7t2AYg=="],
"readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, ""],
"restore-cursor": ["restore-cursor@4.0.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg=="],
"restore-cursor": ["restore-cursor@4.0.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, ""],
"rfdc": ["rfdc@1.4.1", "", {}, ""],
"safe-buffer": ["safe-buffer@5.2.1", "", {}, ""],
"rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
"scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
"side-channel": ["side-channel@1.1.0", "", { "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" } }, ""],
"signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
"side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, ""],
"slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="],
"side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, ""],
"stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="],
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "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" } }, ""],
"string-argv": ["string-argv@0.3.2", "", {}, "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q=="],
"signal-exit": ["signal-exit@3.0.7", "", {}, ""],
"string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
"slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, ""],
"stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, ""],
"string-argv": ["string-argv@0.3.2", "", {}, ""],
"string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, ""],
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, ""],
"strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, ""],
"strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
"supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
@@ -264,50 +184,42 @@
"terminal-link": ["terminal-link@5.0.0", "", { "dependencies": { "ansi-escapes": "^7.0.0", "supports-hyperlinks": "^4.1.0" } }, "sha512-qFAy10MTMwjzjU8U16YS4YoZD+NQLHzLssFMNqgravjbvIPNiqkGFR4yjhJfmY9R5OFU7+yHxc6y+uGHkKwLRA=="],
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, ""],
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
"tr46": ["tr46@0.0.3", "", {}, ""],
"type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
"type-fest": ["type-fest@4.41.0", "", {}, ""],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, ""],
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"undici-types": ["undici-types@7.14.0", "", {}, ""],
"widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="],
"url-join": ["url-join@4.0.1", "", {}, ""],
"wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, ""],
"ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
"whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, ""],
"widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, ""],
"wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, ""],
"ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, ""],
"yaml": ["yaml@2.8.1", "", { "bin": "bin.mjs" }, ""],
"yaml": ["yaml@2.8.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw=="],
"yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="],
"yoga-layout": ["yoga-layout@3.2.1", "", {}, ""],
"yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="],
"cli-truncate/slice-ansi": ["slice-ansi@5.0.0", "", { "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" } }, ""],
"cli-truncate/slice-ansi": ["slice-ansi@5.0.0", "", { "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ=="],
"ink-text-input/type-fest": ["type-fest@3.13.1", "", {}, "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g=="],
"listr2/cli-truncate": ["cli-truncate@5.1.0", "", { "dependencies": { "slice-ansi": "^7.1.0", "string-width": "^8.0.0" } }, ""],
"listr2/cli-truncate": ["cli-truncate@5.1.1", "", { "dependencies": { "slice-ansi": "^7.1.0", "string-width": "^8.0.0" } }, "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A=="],
"log-update/cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, ""],
"log-update/cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="],
"slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, ""],
"slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="],
"listr2/cli-truncate/string-width": ["string-width@8.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.0", "strip-ansi": "^7.1.0" } }, ""],
"listr2/cli-truncate/string-width": ["string-width@8.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.0", "strip-ansi": "^7.1.0" } }, "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg=="],
"log-update/cli-cursor/restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, ""],
"log-update/cli-cursor/restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="],
"log-update/cli-cursor/restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, ""],
"log-update/cli-cursor/restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],
"log-update/cli-cursor/restore-cursor/signal-exit": ["signal-exit@4.1.0", "", {}, ""],
"log-update/cli-cursor/restore-cursor/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
}
}

View File

@@ -22,6 +22,7 @@
"access": "public"
},
"dependencies": {
"@letta-ai/letta-client": "1.0.0-alpha.10",
"ink-link": "^5.0.0"
},
"optionalDependencies": {
@@ -33,7 +34,6 @@
"typescript": "^5.0.0",
"husky": "9.1.7",
"lint-staged": "16.2.4",
"@letta-ai/letta-client": "1.0.0-alpha.2",
"diff": "^8.0.2",
"ink": "^5.0.0",
"ink-spinner": "^5.0.0",

View File

@@ -1,7 +1,8 @@
// src/agent/check-approval.ts
// Check for pending approvals and retrieve recent message history when resuming an agent
import type { Letta, LettaClient } from "@letta-ai/letta-client";
import type Letta from "@letta-ai/letta-client";
import type { LettaMessageUnion } from "@letta-ai/letta-client/resources/agents/messages";
import type { ApprovalRequest } from "../cli/helpers/stream";
// Number of recent messages to backfill when resuming a session
@@ -9,7 +10,7 @@ const MESSAGE_HISTORY_LIMIT = 15;
export interface ResumeData {
pendingApproval: ApprovalRequest | null;
messageHistory: Letta.LettaMessageUnion[];
messageHistory: LettaMessageUnion[];
}
/**
@@ -21,11 +22,12 @@ export interface ResumeData {
* @returns Pending approval (if any) and recent message history
*/
export async function getResumeData(
client: LettaClient,
client: Letta,
agentId: string,
): Promise<ResumeData> {
try {
const messages = await client.agents.messages.list(agentId);
const messagesPage = await client.agents.messages.list(agentId);
const messages = messagesPage.items;
if (!messages || messages.length === 0) {
return { pendingApproval: null, messageHistory: [] };
}
@@ -33,14 +35,25 @@ export async function getResumeData(
// Check for pending approval (last message)
let pendingApproval: ApprovalRequest | null = null;
const lastMessage = messages[messages.length - 1];
if (lastMessage?.messageType === "approval_request_message") {
const approvalMessage = lastMessage as Letta.ApprovalRequestMessage;
const toolCall = approvalMessage.toolCall;
pendingApproval = {
toolCallId: toolCall.toolCallId || "",
toolName: toolCall.name || "",
toolArgs: toolCall.arguments || "",
};
if (lastMessage?.message_type === "approval_request_message") {
// Use tool_calls array (new) or fallback to tool_call (deprecated)
const toolCalls = Array.isArray(lastMessage.tool_calls)
? lastMessage.tool_calls
: lastMessage.tool_call
? [lastMessage.tool_call]
: [];
if (toolCalls.length > 0) {
const toolCall = toolCalls[0];
// Ensure all required fields are present (type guard for ToolCall vs ToolCallDelta)
if (toolCall?.tool_call_id && toolCall.name && toolCall.arguments) {
pendingApproval = {
toolCallId: toolCall.tool_call_id,
toolName: toolCall.name,
toolArgs: toolCall.arguments,
};
}
}
}
// Get last N messages for backfill
@@ -48,7 +61,7 @@ export async function getResumeData(
let messageHistory = messages.slice(-historyCount);
// Skip if starts with orphaned tool_return (incomplete turn)
if (messageHistory[0]?.messageType === "tool_return_message") {
if (messageHistory[0]?.message_type === "tool_return_message") {
messageHistory = messageHistory.slice(1);
}

View File

@@ -1,11 +1,11 @@
import { LettaClient } from "@letta-ai/letta-client";
import Letta from "@letta-ai/letta-client";
import { loadSettings, updateSettings } from "../settings";
export async function getClient() {
const settings = await loadSettings();
const token = process.env.LETTA_API_KEY || settings.env?.LETTA_API_KEY;
if (!token) {
const apiKey = process.env.LETTA_API_KEY || settings.env?.LETTA_API_KEY;
if (!apiKey) {
console.error("Missing LETTA_API_KEY");
console.error(
"Set it via environment variable or add it to ~/.letta/settings.json:",
@@ -14,7 +14,7 @@ export async function getClient() {
process.exit(1);
}
const baseUrl =
const baseURL =
process.env.LETTA_BASE_URL ||
settings.env?.LETTA_BASE_URL ||
"https://api.letta.com";
@@ -37,5 +37,5 @@ export async function getClient() {
await updateSettings({ env: updatedEnv });
}
return new LettaClient({ token, baseUrl });
return new Letta({ apiKey, baseURL });
}

View File

@@ -2,7 +2,11 @@
* Utilities for creating an agent on the Letta API backend
**/
import { Letta } from "@letta-ai/letta-client";
import type {
AgentType,
Block,
CreateBlock,
} from "@letta-ai/letta-client/resources/agents/agents";
import {
loadProjectSettings,
updateProjectSettings,
@@ -39,7 +43,7 @@ export async function createAgent(
const localSharedBlockIds = projectSettings.localSharedBlockIds;
// Retrieve existing blocks (both global and local) and match them with defaults
const existingBlocks = new Map<string, Letta.Block>();
const existingBlocks = new Map<string, Block>();
// Load global blocks (persona, human)
for (const [label, blockId] of Object.entries(globalSharedBlockIds)) {
@@ -69,7 +73,7 @@ export async function createAgent(
// Separate blocks into existing (reuse) and new (create)
const blockIds: string[] = [];
const blocksToCreate: Array<{ block: Letta.CreateBlock; label: string }> = [];
const blocksToCreate: Array<{ block: CreateBlock; label: string }> = [];
for (const defaultBlock of defaultMemoryBlocks) {
const existingBlock = existingBlocks.get(defaultBlock.label);
@@ -131,17 +135,17 @@ export async function createAgent(
// Create agent with all block IDs (existing + newly created)
const agent = await client.agents.create({
agentType: Letta.AgentType.LettaV1Agent,
agent_type: "letta_v1_agent" as AgentType,
system: SYSTEM_PROMPT,
name,
model,
contextWindowLimit: 200_000,
context_window_limit: 200_000,
tools: toolNames,
blockIds,
block_ids: blockIds,
// should be default off, but just in case
includeBaseTools: false,
includeBaseToolRules: false,
initialMessageSequence: [],
include_base_tools: false,
include_base_tool_rules: false,
initial_message_sequence: [],
});
return agent; // { id, ... }
}

View File

@@ -3,7 +3,7 @@
* Loads memory blocks from .mdx files in src/agent/prompts
*/
import type { Letta } from "@letta-ai/letta-client";
import type { CreateBlock } from "@letta-ai/letta-client/resources/agents/agents";
import { MEMORY_PROMPTS } from "./promptAssets";
/**
@@ -40,8 +40,8 @@ function parseMdxFrontmatter(content: string): {
/**
* Load memory blocks from .mdx files in src/agent/prompts
*/
async function loadMemoryBlocksFromMdx(): Promise<Letta.CreateBlock[]> {
const memoryBlocks: Letta.CreateBlock[] = [];
async function loadMemoryBlocksFromMdx(): Promise<CreateBlock[]> {
const memoryBlocks: CreateBlock[] = [];
const mdxFiles = ["persona.mdx", "human.mdx", "project.mdx"];
// const mdxFiles = ["persona.mdx", "human.mdx", "style.mdx"];
@@ -56,7 +56,7 @@ async function loadMemoryBlocksFromMdx(): Promise<Letta.CreateBlock[]> {
}
const { frontmatter, body } = parseMdxFrontmatter(content);
const block: Letta.CreateBlock = {
const block: CreateBlock = {
label: frontmatter.label || filename.replace(".mdx", ""),
value: body,
};
@@ -75,12 +75,12 @@ async function loadMemoryBlocksFromMdx(): Promise<Letta.CreateBlock[]> {
}
// Cache for loaded memory blocks
let cachedMemoryBlocks: Letta.CreateBlock[] | null = null;
let cachedMemoryBlocks: CreateBlock[] | null = null;
/**
* Get default starter memory blocks for new agents
*/
export async function getDefaultMemoryBlocks(): Promise<Letta.CreateBlock[]> {
export async function getDefaultMemoryBlocks(): Promise<CreateBlock[]> {
if (!cachedMemoryBlocks) {
cachedMemoryBlocks = await loadMemoryBlocksFromMdx();
}

View File

@@ -2,22 +2,27 @@
* Utilities for sending messages to an agent
**/
import type { Letta } from "@letta-ai/letta-client";
import type { Stream } from "@letta-ai/letta-client/core/streaming";
import type { MessageCreate } from "@letta-ai/letta-client/resources/agents/agents";
import type {
ApprovalCreate,
LettaStreamingResponse,
} from "@letta-ai/letta-client/resources/agents/messages";
import { getClient } from "./client";
export async function sendMessageStream(
agentId: string,
messages: Array<Letta.MessageCreate | Letta.ApprovalCreate>,
messages: Array<MessageCreate | ApprovalCreate>,
opts: {
streamTokens?: boolean;
background?: boolean;
// add more later: includePings, request timeouts, etc.
} = { streamTokens: true, background: true },
): Promise<AsyncIterable<Letta.LettaStreamingResponse>> {
): Promise<Stream<LettaStreamingResponse>> {
const client = await getClient();
return client.agents.messages.createStream(agentId, {
return client.agents.messages.stream(agentId, {
messages: messages,
streamTokens: opts.streamTokens ?? true,
stream_tokens: opts.streamTokens ?? true,
background: opts.background ?? true,
});
}

View File

@@ -1,7 +1,7 @@
// src/agent/modify.ts
// Utilities for modifying agent configuration
import type { Letta } from "@letta-ai/letta-client";
import type { LlmConfig } from "@letta-ai/letta-client/resources/models/models";
import { getClient } from "./client";
/**
@@ -19,27 +19,27 @@ export async function updateAgentLLMConfig(
agentId: string,
modelHandle: string,
updateArgs?: Record<string, unknown>,
): Promise<Letta.LlmConfig> {
): Promise<LlmConfig> {
const client = await getClient();
// Step 1: Update model (top-level field)
await client.agents.modify(agentId, { model: modelHandle });
await client.agents.update(agentId, { model: modelHandle });
// Step 2: Get updated agent to retrieve current llmConfig
// Step 2: Get updated agent to retrieve current llm_config
const agent = await client.agents.retrieve(agentId);
let finalConfig = agent.llmConfig;
let finalConfig = agent.llm_config;
// Step 3: If we have updateArgs, merge them into llmConfig and patch again
// Step 3: If we have updateArgs, merge them into llm_config and patch again
if (updateArgs && Object.keys(updateArgs).length > 0) {
const updatedLlmConfig = {
...finalConfig,
...updateArgs,
} as Letta.LlmConfig;
await client.agents.modify(agentId, { llmConfig: updatedLlmConfig });
} as LlmConfig;
await client.agents.update(agentId, { llm_config: updatedLlmConfig });
// Retrieve final state
const finalAgent = await client.agents.retrieve(agentId);
finalConfig = finalAgent.llmConfig;
finalConfig = finalAgent.llm_config;
}
return finalConfig;

View File

@@ -1,6 +1,11 @@
// src/cli/App.tsx
import { Letta } from "@letta-ai/letta-client";
import type { MessageCreate } from "@letta-ai/letta-client/resources/agents/agents";
import type {
ApprovalCreate,
LettaMessageUnion,
} from "@letta-ai/letta-client/resources/agents/messages";
import type { LlmConfig } from "@letta-ai/letta-client/resources/models/models";
import { Box, Static } from "ink";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { getResumeData } from "../agent/check-approval";
@@ -105,7 +110,7 @@ export default function App({
| "ready";
continueSession?: boolean;
startupApproval?: ApprovalRequest | null;
messageHistory?: Letta.LettaMessageUnion[];
messageHistory?: LettaMessageUnion[];
tokenStreaming?: boolean;
}) {
// Whether a stream is in flight (disables input)
@@ -132,7 +137,7 @@ export default function App({
// Model selector state
const [modelSelectorOpen, setModelSelectorOpen] = useState(false);
const [llmConfig, setLlmConfig] = useState<Letta.LlmConfig | null>(null);
const [llmConfig, setLlmConfig] = useState<LlmConfig | null>(null);
// Token streaming preference (can be toggled at runtime)
const [tokenStreamingEnabled, setTokenStreamingEnabled] =
@@ -345,9 +350,9 @@ export default function App({
const { getClient } = await import("../agent/client");
const client = await getClient();
const agent = await client.agents.retrieve(agentId);
setLlmConfig(agent.llmConfig);
setLlmConfig(agent.llm_config);
} catch (error) {
console.error("Error fetching llmConfig:", error);
console.error("Error fetching llm_config:", error);
}
};
fetchConfig();
@@ -372,7 +377,7 @@ export default function App({
// Core streaming function - iterative loop that processes conversation turns
const processConversation = useCallback(
async (
initialInput: Array<Letta.MessageCreate | Letta.ApprovalCreate>,
initialInput: Array<MessageCreate | ApprovalCreate>,
): Promise<void> => {
let currentInput = initialInput;
@@ -398,7 +403,7 @@ export default function App({
refreshDerived();
// Case 1: Turn ended normally
if (stopReason === Letta.StopReasonType.EndTurn) {
if (stopReason === "end_turn") {
setStreaming(false);
return;
}
@@ -411,7 +416,7 @@ export default function App({
}
// Case 2: Requires approval
if (stopReason === Letta.StopReasonType.RequiresApproval) {
if (stopReason === "requires_approval") {
if (!approval) {
appendError(
`Unexpected null approval with stop reason: ${stopReason}`,
@@ -453,7 +458,7 @@ export default function App({
await processConversation([
{
type: "approval",
approvalRequestId: toolCallId,
approval_request_id: toolCallId,
approve: false,
reason: denyReason,
},
@@ -479,11 +484,11 @@ export default function App({
// Update buffers with tool return
onChunk(buffersRef.current, {
messageType: "tool_return_message",
message_type: "tool_return_message",
id: "dummy",
date: new Date(),
toolCallId,
toolReturn: toolResult.toolReturn,
date: new Date().toISOString(),
tool_call_id: toolCallId,
tool_return: toolResult.toolReturn,
status: toolResult.status,
stdout: toolResult.stdout,
stderr: toolResult.stderr,
@@ -497,8 +502,8 @@ export default function App({
approvals: [
{
type: "tool",
toolCallId,
toolReturn: toolResult.toolReturn,
tool_call_id: toolCallId,
tool_return: toolResult.toolReturn,
status: toolResult.status,
stdout: toolResult.stdout,
stderr: toolResult.stderr,
@@ -664,6 +669,61 @@ export default function App({
return { submitted: true };
}
// Special handling for /clear command - reset conversation
if (msg.trim() === "/clear") {
const cmdId = uid("cmd");
buffersRef.current.byId.set(cmdId, {
kind: "command",
id: cmdId,
input: msg,
output: "Clearing conversation...",
phase: "running",
});
buffersRef.current.order.push(cmdId);
refreshDerived();
setCommandRunning(true);
try {
const client = getClient();
await client.agents.messages.reset(agentId, {
add_default_initial_messages: false,
});
// Clear local buffers and static items
// buffersRef.current.byId.clear();
// buffersRef.current.order = [];
// buffersRef.current.tokenCount = 0;
// emittedIdsRef.current.clear();
// setStaticItems([]);
// Update command with success
buffersRef.current.byId.set(cmdId, {
kind: "command",
id: cmdId,
input: msg,
output: "Conversation cleared",
phase: "finished",
success: true,
});
buffersRef.current.order.push(cmdId);
refreshDerived();
} catch (error) {
buffersRef.current.byId.set(cmdId, {
kind: "command",
id: cmdId,
input: msg,
output: `Failed: ${error instanceof Error ? error.message : String(error)}`,
phase: "finished",
success: false,
});
refreshDerived();
} finally {
setCommandRunning(false);
}
return { submitted: true };
}
// Immediately add command to transcript with "running" phase
const cmdId = uid("cmd");
buffersRef.current.byId.set(cmdId, {
@@ -782,8 +842,8 @@ export default function App({
// Start the conversation loop
await processConversation([
{
role: Letta.MessageCreateRole.User,
content: messageContent as unknown as Letta.MessageCreate["content"],
role: "user",
content: messageContent as unknown as MessageCreate["content"],
},
]);
@@ -817,11 +877,11 @@ export default function App({
// Update buffers with tool return
onChunk(buffersRef.current, {
messageType: "tool_return_message",
message_type: "tool_return_message",
id: "dummy",
date: new Date(),
toolCallId,
toolReturn: toolResult.toolReturn,
date: new Date().toISOString(),
tool_call_id: toolCallId,
tool_return: toolResult.toolReturn,
status: toolResult.status,
stdout: toolResult.stdout,
stderr: toolResult.stderr,
@@ -837,8 +897,8 @@ export default function App({
approvals: [
{
type: "tool",
toolCallId,
toolReturn: toolResult.toolReturn,
tool_call_id: toolCallId,
tool_return: toolResult.toolReturn,
status: toolResult.status,
stdout: toolResult.stdout,
stderr: toolResult.stderr,
@@ -897,7 +957,7 @@ export default function App({
await processConversation([
{
type: "approval",
approvalRequestId: toolCallId,
approval_request_id: toolCallId,
approve: false,
reason: reason || "User denied the tool execution",
// TODO the above is legacy?
@@ -1031,11 +1091,11 @@ export default function App({
// Update buffers with tool return
onChunk(buffersRef.current, {
messageType: "tool_return_message",
message_type: "tool_return_message",
id: "dummy",
date: new Date(),
toolCallId,
toolReturn: toolResult.toolReturn,
date: new Date().toISOString(),
tool_call_id: toolCallId,
tool_return: toolResult.toolReturn,
status: toolResult.status,
stdout: toolResult.stdout,
stderr: toolResult.stderr,
@@ -1052,8 +1112,8 @@ export default function App({
approvals: [
{
type: "tool",
toolCallId,
toolReturn: toolResult.toolReturn,
tool_call_id: toolCallId,
tool_return: toolResult.toolReturn,
status: toolResult.status,
stdout: toolResult.stdout,
stderr: toolResult.stderr,
@@ -1085,7 +1145,7 @@ export default function App({
await processConversation([
{
type: "approval",
approvalRequestId: toolCallId,
approval_request_id: toolCallId,
approve: false,
reason:
reason ||

View File

@@ -36,6 +36,13 @@ export const commands: Record<string, Command> = {
return "Exiting...";
},
},
"/clear": {
desc: "Clear conversation history",
handler: () => {
// Handled specially in App.tsx to access client and agent ID
return "Clearing messages...";
},
},
};
/**

View File

@@ -4,7 +4,7 @@
// - Tool calls update in-place (same toolCallId for call+return).
// - Exposes `onChunk` to feed SDK events and `toLines` to render.
import type { Letta } from "@letta-ai/letta-client";
import type { LettaStreamingChunk } from "../../agent/message";
// One line per transcript row. Tool calls evolve in-place.
// For tool call returns, merge into the tool call matching the toolCallId
@@ -194,10 +194,7 @@ function extractTextPart(v: unknown): string {
}
// Feed one SDK chunk; mutate buffers in place.
export function onChunk(
b: Buffers,
chunk: Letta.agents.LettaStreamingResponse,
) {
export function onChunk(b: Buffers, chunk: LettaStreamingChunk) {
// TODO remove once SDK v1 has proper typing for in-stream errors
// Check for streaming error objects (not typed in SDK but emitted by backend)
// These are emitted when LLM errors occur during streaming (rate limits, timeouts, etc.)
@@ -283,20 +280,28 @@ export function onChunk(
if (!id) break;
const toolCallId = chunk.toolCall?.toolCallId;
const name = chunk.toolCall?.name;
const argsText = chunk.toolCall?.arguments;
const toolCall = chunk.tool_call || (Array.isArray(chunk.tool_calls) && chunk.tool_calls.length > 0 ? chunk.tool_calls[0] : null);
const toolCallId = toolCall?.tool_call_id;
const name = toolCall?.name;
const argsText = toolCall?.arguments;
// Record correlation: toolCallId → line id (otid)
if (toolCallId) b.toolCallIdToLineId.set(toolCallId, id);
*/
let id = chunk.otid;
// console.log(`[TOOL_CALL] Received ${chunk.messageType} with otid=${id}, toolCallId=${chunk.toolCall?.toolCallId}, name=${chunk.toolCall?.name}`);
// console.log(`[TOOL_CALL] Received ${chunk.message_type} with otid=${id}, toolCallId=${chunk.tool_call?.tool_call_id}, name=${chunk.tool_call?.name}`);
const toolCallId = chunk.toolCall?.toolCallId;
const name = chunk.toolCall?.name;
const argsText = chunk.toolCall?.arguments;
// Use deprecated tool_call or new tool_calls array
const toolCall =
chunk.tool_call ||
(Array.isArray(chunk.tool_calls) && chunk.tool_calls.length > 0
? chunk.tool_calls[0]
: null);
const toolCallId = toolCall?.tool_call_id;
const name = toolCall?.name;
const argsText = toolCall?.arguments;
// ========== START BACKEND BUG WORKAROUND (Remove after OTID fix) ==========
// Bug: Backend sends same otid for reasoning and tool_call, and multiple otids for same tool_call
@@ -310,7 +315,7 @@ export function onChunk(
}
// Handle otid transition for tracking purposes
handleOtidTransition(b, chunk.otid);
handleOtidTransition(b, chunk.otid ?? undefined);
} else {
// Check if this otid is already used by a reasoning line
if (id && b.byId.has(id)) {
@@ -327,7 +332,7 @@ export function onChunk(
// This part stays after fix:
// Handle otid transition (mark previous line as finished)
// This must happen BEFORE the break, so reasoning gets finished even when tool has no otid
handleOtidTransition(b, id);
handleOtidTransition(b, id ?? undefined);
if (!id) {
// console.log(`[TOOL_CALL] No otid, breaking`);
@@ -338,21 +343,24 @@ export function onChunk(
if (toolCallId) b.toolCallIdToLineId.set(toolCallId, id);
}
// Early exit if no valid id
if (!id) break;
const desiredPhase =
chunk.messageType === "approval_request_message"
chunk.message_type === "approval_request_message"
? "ready"
: "streaming";
const line = ensure<ToolCallLine>(b, id, () => ({
kind: "tool_call",
id,
toolCallId: toolCallId,
name: name,
toolCallId: toolCallId ?? undefined,
name: name ?? undefined,
phase: desiredPhase,
}));
// If this is an approval request and the line already exists, bump phase to ready
if (
chunk.messageType === "approval_request_message" &&
chunk.message_type === "approval_request_message" &&
line.phase !== "finished"
) {
b.byId.set(id, { ...line, phase: "ready" });
@@ -372,8 +380,8 @@ export function onChunk(
case "tool_return_message": {
// Tool return is a special case
// It will have a different otid than the tool call, but we want to merge into the tool call
const toolCallId = chunk.toolCallId;
const resultText = chunk.toolReturn;
const toolCallId = chunk.tool_call_id;
const resultText = chunk.tool_return;
const status = chunk.status;
// Look up the line by toolCallId
@@ -401,17 +409,17 @@ export function onChunk(
case "usage_statistics": {
// Accumulate usage statistics from the stream
// These messages arrive after stop_reason in the stream
if (chunk.promptTokens !== undefined) {
b.usage.promptTokens += chunk.promptTokens;
if (chunk.prompt_tokens !== undefined) {
b.usage.promptTokens += chunk.prompt_tokens;
}
if (chunk.completionTokens !== undefined) {
b.usage.completionTokens += chunk.completionTokens;
if (chunk.completion_tokens !== undefined) {
b.usage.completionTokens += chunk.completion_tokens;
}
if (chunk.totalTokens !== undefined) {
b.usage.totalTokens += chunk.totalTokens;
if (chunk.total_tokens !== undefined) {
b.usage.totalTokens += chunk.total_tokens;
}
if (chunk.stepCount !== undefined) {
b.usage.stepCount += chunk.stepCount;
if (chunk.step_count !== undefined) {
b.usage.stepCount += chunk.step_count;
}
break;
}

View File

@@ -1,4 +1,8 @@
import type { Letta } from "@letta-ai/letta-client";
import type {
LettaAssistantMessageContentUnion,
LettaMessageUnion,
LettaUserMessageContentUnion,
} from "@letta-ai/letta-client/resources/agents/messages";
import type { Buffers } from "./accumulator";
// const PASTE_LINE_THRESHOLD = 5;
@@ -16,7 +20,7 @@ function clip(s: string, limit: number): string {
}
function renderAssistantContentParts(
parts: Letta.AssistantMessageContent,
parts: string | LettaAssistantMessageContentUnion[],
): string {
// AssistantContent can be a string or an array of text parts
if (typeof parts === "string") return parts;
@@ -29,7 +33,9 @@ function renderAssistantContentParts(
return out;
}
function renderUserContentParts(parts: Letta.UserMessageContent): string {
function renderUserContentParts(
parts: string | LettaUserMessageContentUnion[],
): string {
// UserContent can be a string or an array of text OR image parts
// for text parts, we clip them if they're too big (eg copy-pasted chunks)
// for image parts, we just show a placeholder
@@ -49,7 +55,7 @@ function renderUserContentParts(parts: Letta.UserMessageContent): string {
export function backfillBuffers(
buffers: Buffers,
history: Letta.LettaMessageUnion[],
history: LettaMessageUnion[],
): void {
// Clear buffers to ensure idempotency (in case this is called multiple times)
buffers.order = [];
@@ -65,7 +71,7 @@ export function backfillBuffers(
// Use otid as line ID when available (like streaming does), fall back to msg.id
const lineId = "otid" in msg && msg.otid ? msg.otid : msg.id;
switch (msg.messageType) {
switch (msg.message_type) {
// user message - content parts may include text and image parts
case "user_message": {
const exists = buffers.byId.has(lineId);
@@ -107,9 +113,16 @@ export function backfillBuffers(
// tool call message OR approval request (they're the same in history)
case "tool_call_message":
case "approval_request_message": {
if ("toolCall" in msg && msg.toolCall?.toolCallId) {
const toolCall = msg.toolCall;
const toolCallId = toolCall.toolCallId;
// Use tool_calls array (new) or fallback to tool_call (deprecated)
const toolCalls = Array.isArray(msg.tool_calls)
? msg.tool_calls
: msg.tool_call
? [msg.tool_call]
: [];
if (toolCalls.length > 0 && toolCalls[0]?.tool_call_id) {
const toolCall = toolCalls[0];
const toolCallId = toolCall.tool_call_id;
const exists = buffers.byId.has(lineId);
buffers.byId.set(lineId, {
@@ -130,7 +143,7 @@ export function backfillBuffers(
// tool return message - merge into the existing tool call line
case "tool_return_message": {
const toolCallId = msg.toolCallId;
const toolCallId = msg.tool_call_id;
if (!toolCallId) break;
// Look up the line using the mapping (like streaming does)
@@ -143,7 +156,7 @@ export function backfillBuffers(
// Update the existing line with the result
buffers.byId.set(toolCallLineId, {
...existingLine,
resultText: msg.toolReturn,
resultText: msg.tool_return,
resultOk: msg.status === "success",
phase: "finished",
});

View File

@@ -1,4 +1,7 @@
import { Letta } from "@letta-ai/letta-client";
import type { Stream } from "@letta-ai/letta-client/core/streaming";
import type { LettaStreamingResponse } from "@letta-ai/letta-client/resources/agents/messages";
import type { StopReasonType } from "@letta-ai/letta-client/resources/runs/runs";
import {
type createBuffers,
markCurrentLineAsFinished,
@@ -13,7 +16,7 @@ export type ApprovalRequest = {
};
type DrainResult = {
stopReason: Letta.StopReasonType;
stopReason: StopReasonType;
lastRunId?: string | null;
lastSeqId?: number | null;
approval?: ApprovalRequest | null; // present only if we ended due to approval
@@ -21,7 +24,7 @@ type DrainResult = {
};
export async function drainStream(
stream: AsyncIterable<Letta.LettaStreamingResponse>,
stream: Stream<LettaStreamingResponse>,
buffers: ReturnType<typeof createBuffers>,
refresh: () => void,
abortSignal?: AbortSignal,
@@ -33,29 +36,36 @@ export async function drainStream(
let toolName: string | null = null;
let toolArgs: string | null = null;
let stopReason: Letta.StopReasonType | null = null;
let stopReason: StopReasonType | null = null;
let lastRunId: string | null = null;
let lastSeqId: number | null = null;
for await (const chunk of stream) {
// console.log("chunk", chunk);
// Check if stream was aborted
if (abortSignal?.aborted) {
stopReason = "cancelled" as Letta.StopReasonType;
stopReason = "cancelled";
// Mark incomplete tool calls as cancelled to prevent stuck blinking UI
markIncompleteToolsAsCancelled(buffers);
queueMicrotask(refresh);
break;
}
// Store the runId and seqId to re-connect if stream is interrupted
if ("runId" in chunk && "seqId" in chunk && chunk.runId && chunk.seqId) {
lastRunId = chunk.runId;
lastSeqId = chunk.seqId;
// Store the run_id and seq_id to re-connect if stream is interrupted
if (
"run_id" in chunk &&
"seq_id" in chunk &&
chunk.run_id &&
chunk.seq_id
) {
lastRunId = chunk.run_id;
lastSeqId = chunk.seq_id;
}
if (chunk.messageType === "ping") continue;
if (chunk.message_type === "ping") continue;
// Need to store the approval request ID to send an approval in a new run
if (chunk.messageType === "approval_request_message") {
if (chunk.message_type === "approval_request_message") {
approvalRequestId = chunk.id;
}
@@ -63,25 +73,32 @@ export async function drainStream(
// in both the onChunk handler and here, we could refactor to instead pull the tool name
// and JSON args from the mutated lines (eg last mutated line)
if (
chunk.messageType === "tool_call_message" ||
chunk.messageType === "approval_request_message"
chunk.message_type === "tool_call_message" ||
chunk.message_type === "approval_request_message"
) {
if (chunk.toolCall?.toolCallId) {
toolCallId = chunk.toolCall.toolCallId;
// Use deprecated tool_call or new tool_calls array
const toolCall =
chunk.tool_call ||
(Array.isArray(chunk.tool_calls) && chunk.tool_calls.length > 0
? chunk.tool_calls[0]
: null);
if (toolCall?.tool_call_id) {
toolCallId = toolCall.tool_call_id;
}
if (chunk.toolCall?.name) {
if (toolCall?.name) {
if (toolName) {
// TODO would expect that we should allow stacking? I guess not?
// toolName = toolName + chunk.toolCall.name;
// toolName = toolName + toolCall.name;
} else {
toolName = chunk.toolCall.name;
toolName = toolCall.name;
}
}
if (chunk.toolCall?.arguments) {
if (toolCall?.arguments) {
if (toolArgs) {
toolArgs = toolArgs + chunk.toolCall.arguments;
toolArgs = toolArgs + toolCall.arguments;
} else {
toolArgs = chunk.toolCall.arguments;
toolArgs = toolCall.arguments;
}
}
}
@@ -89,15 +106,15 @@ export async function drainStream(
onChunk(buffers, chunk);
queueMicrotask(refresh);
if (chunk.messageType === "stop_reason") {
stopReason = chunk.stopReason;
if (chunk.message_type === "stop_reason") {
stopReason = chunk.stop_reason;
// Continue reading stream to get usage_statistics that may come after
}
}
// Stream has ended, check if we captured a stop reason
if (!stopReason) {
stopReason = Letta.StopReasonType.Error;
stopReason = "error";
}
// Mark the final line as finished now that stream has ended

View File

@@ -1,5 +1,9 @@
import { parseArgs } from "node:util";
import { Letta } from "@letta-ai/letta-client";
import type {
AgentState,
MessageCreate,
} from "@letta-ai/letta-client/resources/agents/agents";
import type { ApprovalCreate } from "@letta-ai/letta-client/resources/agents/messages";
import { getClient } from "./agent/client";
import { createAgent } from "./agent/create";
import { sendMessageStream } from "./agent/message";
@@ -49,7 +53,7 @@ export async function handleHeadlessCommand(argv: string[]) {
const client = await getClient();
// Resolve agent (same logic as interactive mode)
let agent: Letta.AgentState | null = null;
let agent: AgentState | null = null;
const specifiedAgentId = values.agent as string | undefined;
const shouldContinue = values.continue as boolean | undefined;
const forceNew = values.new as boolean | undefined;
@@ -132,9 +136,9 @@ export async function handleHeadlessCommand(argv: string[]) {
}
// Send message and process stream loop
let currentInput: Array<Letta.MessageCreate | Letta.ApprovalCreate> = [
let currentInput: Array<MessageCreate | ApprovalCreate> = [
{
role: Letta.MessageCreateRole.User,
role: "user",
content: [{ type: "text", text: prompt }],
},
];
@@ -218,12 +222,12 @@ export async function handleHeadlessCommand(argv: string[]) {
sessionStats.endTurn(apiDurationMs);
// Case 1: Turn ended normally
if (stopReason === Letta.StopReasonType.EndTurn) {
if (stopReason === "end_turn") {
break;
}
// Case 2: Requires approval
if (stopReason === Letta.StopReasonType.RequiresApproval) {
if (stopReason === "requires_approval") {
if (!approval) {
console.error("Unexpected null approval");
process.exit(1);
@@ -244,7 +248,7 @@ export async function handleHeadlessCommand(argv: string[]) {
currentInput = [
{
type: "approval",
approvalRequestId: toolCallId,
approval_request_id: toolCallId,
approve: false,
reason: denyReason,
},
@@ -257,7 +261,7 @@ export async function handleHeadlessCommand(argv: string[]) {
currentInput = [
{
type: "approval",
approvalRequestId: toolCallId,
approval_request_id: toolCallId,
approve: false,
reason: "Tool requires approval (headless mode)",
},
@@ -274,8 +278,8 @@ export async function handleHeadlessCommand(argv: string[]) {
approvals: [
{
type: "tool",
toolCallId,
toolReturn: toolResult.toolReturn,
tool_call_id: toolCallId,
tool_return: toolResult.toolReturn,
status: toolResult.status,
stdout: toolResult.stdout,
stderr: toolResult.stderr,

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bun
import { parseArgs } from "node:util";
import type { Letta } from "@letta-ai/letta-client";
import type { AgentState } from "@letta-ai/letta-client/resources/agents/agents";
import { getResumeData, type ResumeData } from "./agent/check-approval";
import { getClient } from "./agent/client";
import { permissionMode } from "./permissions/mode";
@@ -216,7 +216,7 @@ async function main() {
const { updateSettings, loadProjectSettings, updateProjectSettings } =
await import("./settings");
let agent: Letta.AgentState | null = null;
let agent: AgentState | null = null;
// Priority 1: Try to use --agent specified ID
if (agentIdArg) {

View File

@@ -18,19 +18,21 @@ async function main() {
console.log(`✅ Agent created: ${agent.id}`);
console.log("💬 Sending test message...");
const stream = await sendMessageStream(
agent.id,
"Hello from Bun smoke test! Try calling a tool.",
);
const stream = await sendMessageStream(agent.id, [
{
role: "user",
content: "Hello from Bun smoke test! Try calling a tool.",
},
]);
// Print every chunk as it arrives
for await (const chunk of stream) {
const type = chunk.messageType ?? "unknown";
const type = chunk.message_type ?? "unknown";
switch (chunk.messageType) {
switch (chunk.message_type) {
case "reasoning_message": {
const run = chunk.runId
? `run=${chunk.runId}:${chunk.seqId ?? "-"} `
const run = chunk.run_id
? `run=${chunk.run_id}:${chunk.seq_id ?? "-"} `
: "";
process.stdout.write(
`[reasoning] ${run}${JSON.stringify(chunk) ?? ""}\n`,
@@ -38,8 +40,8 @@ async function main() {
break;
}
case "assistant_message": {
const run = chunk.runId
? `run=${chunk.runId}:${chunk.seqId ?? "-"} `
const run = chunk.run_id
? `run=${chunk.run_id}:${chunk.seq_id ?? "-"} `
: "";
process.stdout.write(
`[assistant] ${run}${JSON.stringify(chunk) ?? ""}\n`,
@@ -47,8 +49,8 @@ async function main() {
break;
}
case "tool_call_message": {
const run = chunk.runId
? `run=${chunk.runId}:${chunk.seqId ?? "-"} `
const run = chunk.run_id
? `run=${chunk.run_id}:${chunk.seq_id ?? "-"} `
: "";
process.stdout.write(
`[tool_call] ${run}${JSON.stringify(chunk) ?? ""}\n`,
@@ -56,15 +58,15 @@ async function main() {
break;
}
case "tool_return_message": {
const run = chunk.runId
? `run=${chunk.runId}:${chunk.seqId ?? "-"} `
const run = chunk.run_id
? `run=${chunk.run_id}:${chunk.seq_id ?? "-"} `
: "";
process.stdout.write(`[tool_return] ${run}${chunk}\n`);
break;
}
case "approval_request_message": {
const run = chunk.runId
? `run=${chunk.runId}:${chunk.seqId ?? "-"} `
const run = chunk.run_id
? `run=${chunk.run_id}:${chunk.seq_id ?? "-"} `
: "";
process.stdout.write(
`[approval_request] ${run}${JSON.stringify(chunk)}\n`,

View File

@@ -1,4 +1,4 @@
import type { LettaClient } from "@letta-ai/letta-client";
import type Letta from "@letta-ai/letta-client";
import { TOOL_DEFINITIONS, type ToolName } from "./toolDefinitions";
export const TOOL_NAMES = Object.keys(TOOL_DEFINITIONS) as ToolName[];
@@ -228,7 +228,7 @@ export async function loadTools(): Promise<void> {
* @param client - Letta client instance
* @returns Promise that resolves when all tools are registered
*/
export async function upsertToolsToServer(client: LettaClient): Promise<void> {
export async function upsertToolsToServer(client: Letta): Promise<void> {
const upsertPromises = Array.from(toolRegistry.entries()).map(
async ([name, tool]) => {
const pythonStub = generatePythonStub(
@@ -245,9 +245,9 @@ export async function upsertToolsToServer(client: LettaClient): Promise<void> {
};
await client.tools.upsert({
defaultRequiresApproval: true,
sourceCode: pythonStub,
jsonSchema: fullJsonSchema,
default_requires_approval: true,
source_code: pythonStub,
json_schema: fullJsonSchema,
// description: tool.schema.description,
// tags: ['client-side', 'typescript'],
});