summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--bun.lock185
-rw-r--r--packages/function/src/api.ts17
-rw-r--r--packages/opencode/package.json1
-rw-r--r--packages/opencode/src/cli/cmd/run.ts26
-rw-r--r--packages/opencode/src/cli/cmd/stats.ts84
-rw-r--r--packages/opencode/src/cli/ui.ts16
-rw-r--r--packages/opencode/src/id/id.ts1
-rw-r--r--packages/opencode/src/server/server.ts13
-rw-r--r--packages/opencode/src/session/index.ts444
-rw-r--r--packages/opencode/src/session/message-v2.ts320
-rw-r--r--packages/opencode/src/storage/storage.ts39
-rw-r--r--packages/opencode/src/tool/task.ts20
-rw-r--r--packages/tui/internal/app/app.go109
-rw-r--r--packages/tui/internal/components/chat/messages.go43
-rw-r--r--packages/tui/internal/id/id.go96
-rw-r--r--packages/tui/internal/tui/tui.go103
-rw-r--r--packages/tui/sdk/.stats.yml6
-rw-r--r--packages/tui/sdk/README.md23
-rw-r--r--packages/tui/sdk/api.md8
-rw-r--r--packages/tui/sdk/client_test.go44
-rw-r--r--packages/tui/sdk/event.go8
-rwxr-xr-xpackages/tui/sdk/scripts/lint4
-rw-r--r--packages/tui/sdk/session.go683
-rw-r--r--packages/tui/sdk/session_test.go18
-rw-r--r--packages/tui/sdk/usage_test.go7
-rw-r--r--packages/web/src/components/Share.tsx60
-rw-r--r--stainless.yml2
27 files changed, 1431 insertions, 949 deletions
diff --git a/bun.lock b/bun.lock
index 4327be9cf..6ec2a0050 100644
--- a/bun.lock
+++ b/bun.lock
@@ -37,6 +37,7 @@
"@openauthjs/openauth": "0.4.3",
"@standard-schema/spec": "1.0.0",
"ai": "catalog:",
+ "cli-markdown": "3.5.1",
"decimal.js": "10.5.0",
"diff": "8.0.2",
"env-paths": "3.0.0",
@@ -209,6 +210,8 @@
"@cloudflare/workers-types": ["@cloudflare/[email protected]", "", {}, "sha512-9RIffHobc35JWeddzBguGgPa4wLDr5x5F94+0/qy7LiV6pTBQ/M5qGEN9VA16IDT3EUpYI0WKh6VpcmeVEtVtw=="],
+ "@colors/colors": ["@colors/[email protected]", "", {}, "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="],
+
"@cspotcode/source-map-support": ["@cspotcode/[email protected]", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],
"@ctrl/tinycolor": ["@ctrl/[email protected]", "", {}, "sha512-WyOx8cJQ+FQus4Mm4uPIZA64gbk3Wxh0so5Lcii0aJifqwoVOlfFtorjLE0Hen4OYyHZMXDWqMmaQemBhgxFRQ=="],
@@ -337,6 +340,8 @@
"@jsdevtools/ono": ["@jsdevtools/[email protected]", "", {}, "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg=="],
+ "@mdit/plugin-alert": ["@mdit/[email protected]", "", { "dependencies": { "@types/markdown-it": "^14.1.2" }, "peerDependencies": { "markdown-it": "^14.1.0" }, "optionalPeers": ["markdown-it"] }, "sha512-n2oVSeg3yeZBCjqfAqbnJxeu4PGq+CXwUWsiwrrARj39z23QZ62FbgL5WGNyP/WFnDAeHMedLDYtipC9OgIOgA=="],
+
"@mdx-js/mdx": ["@mdx-js/[email protected]", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw=="],
"@mixmark-io/domino": ["@mixmark-io/[email protected]", "", {}, "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="],
@@ -513,10 +518,18 @@
"@types/json-schema": ["@types/[email protected]", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
+ "@types/linkify-it": ["@types/[email protected]", "", {}, "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q=="],
+
"@types/luxon": ["@types/[email protected]", "", {}, "sha512-R/BdP7OxEMc44l2Ex5lSXHoIXTB2JLNa3y2QISIbr58U/YcsffyQrYW//hZSdrfxrjRZj3GcUoxMPGdO8gSYuw=="],
+ "@types/markdown-it": ["@types/[email protected]", "", { "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" } }, "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog=="],
+
+ "@types/marked": ["@types/[email protected]", "", { "dependencies": { "marked": "*" } }, "sha512-jmjpa4BwUsmhxcfsgUit/7A9KbrC48Q0q8KvnY107ogcjGgTFDlIL3RpihNpx2Mu1hM4mdFQjoVc4O6JoGKHsA=="],
+
"@types/mdast": ["@types/[email protected]", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
+ "@types/mdurl": ["@types/[email protected]", "", {}, "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg=="],
+
"@types/mdx": ["@types/[email protected]", "", {}, "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw=="],
"@types/ms": ["@types/[email protected]", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
@@ -553,10 +566,14 @@
"ansi-align": ["[email protected]", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="],
+ "ansi-escapes": ["[email protected]", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw=="],
+
"ansi-regex": ["[email protected]", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
"ansi-styles": ["[email protected]", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
+ "any-promise": ["[email protected]", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="],
+
"anymatch": ["[email protected]", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
"arctic": ["[email protected]", "", { "dependencies": { "@oslojs/crypto": "1.0.1", "@oslojs/encoding": "1.1.0", "@oslojs/jwt": "0.2.0" } }, "sha512-+p30BOWsctZp+CVYCt7oAean/hWGW42sH5LAcRQX56ttEkFJWbzXBhmSpibbzwSJkRrotmsA+oAoJoVsU0f5xA=="],
@@ -569,6 +586,8 @@
"aria-query": ["[email protected]", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
+ "arity-n": ["[email protected]", "", {}, "sha512-fExL2kFDC1Q2DUOx3whE/9KoN66IzkY4b4zUHUBFM1ojEYjZZYDcUW3bek/ufGionX9giIKDC5redH2IlGqcQQ=="],
+
"array-iterate": ["[email protected]", "", {}, "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg=="],
"as-table": ["[email protected]", "", { "dependencies": { "printable-characters": "^1.0.42" } }, "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ=="],
@@ -641,6 +660,8 @@
"buffer": ["[email protected]", "", { "dependencies": { "base64-js": "^1.0.2", "ieee754": "^1.1.4", "isarray": "^1.0.0" } }, "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg=="],
+ "buffer-from": ["[email protected]", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
+
"bun-types": ["[email protected]", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="],
"bundle-name": ["[email protected]", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
@@ -661,6 +682,10 @@
"chalk": ["[email protected]", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="],
+ "chalk-string": ["[email protected]", "", { "dependencies": { "colors-option": "^6.0.1", "is-plain-obj": "^4.1.0" } }, "sha512-kUA4bEXNsDXRnMBRNex8Vsp9cUF9w9UZeRwBBePSUhU3/hHDMuSQKUPYtmuqIHwiDomYd/IVYu2N8aUGl/t6SQ=="],
+
+ "change-case": ["[email protected]", "", {}, "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w=="],
+
"character-entities": ["[email protected]", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="],
"character-entities-html4": ["[email protected]", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="],
@@ -673,12 +698,22 @@
"chownr": ["[email protected]", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="],
+ "chroma-js": ["[email protected]", "", {}, "sha512-jTwQiT859RTFN/vIf7s+Vl/Z2LcMrvMv3WUFmd/4u76AdlFC0NTNgqEEFPcRiHmAswPsMiQEDZLM8vX8qXpZNQ=="],
+
"ci-info": ["[email protected]", "", {}, "sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg=="],
"clean-git-ref": ["[email protected]", "", {}, "sha512-bLSptAy2P0s6hU4PzuIMKmMJJSE6gLXGH1cntDu7bWJUksvuM+7ReOK61mozULErYvP6a15rnYl0zFDef+pyPw=="],
"cli-boxes": ["[email protected]", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="],
+ "cli-highlight": ["[email protected]", "", { "dependencies": { "chalk": "^4.0.0", "highlight.js": "^10.7.1", "mz": "^2.4.0", "parse5": "^5.1.1", "parse5-htmlparser2-tree-adapter": "^6.0.0", "yargs": "^16.0.0" }, "bin": { "highlight": "bin/highlight" } }, "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg=="],
+
+ "cli-html": ["[email protected]", "", { "dependencies": { "ansi-align": "^3.0.1", "ansi-escapes": "^7.0.0", "boxen": "^8.0.1", "chalk": "^5.4.1", "chalk-string": "^3.0.1", "change-case": "^5.4.4", "cli-highlight": "^2.1.11", "cli-table3": "^0.6.5", "color-namer": "^1.4.0", "compose-function": "^3.0.3", "concat-stream": "^2.0.0", "env-paths": "^3.0.0", "he": "^1.2.0", "inline-style-parser": "^0.2.4", "languages-aliases": "^3.0.0", "longest-line": "0.0.3", "normalize-html-whitespace": "^1.0.0", "number-to-alphabet": "^1.0.0", "parse5": "^7.3.0", "romanize": "^1.1.1", "supports-hyperlinks": "^4.1.0", "terminal-size": "^4.0.0", "wrap-ansi": "^9.0.0", "yaml": "^2.8.0" }, "bin": { "html": "bin/html.js" } }, "sha512-H71WH7iAgLh7RN84qzhFxlqlP66Ek4wdqhm8ziJq23zKzrIFjM7GZo9fvUx/jtjB72GCdsHRcu+hgdLlfExWUg=="],
+
+ "cli-markdown": ["[email protected]", "", { "dependencies": { "@mdit/plugin-alert": "^0.22.1", "@types/marked": "^6.0.0", "cli-html": "^4.4.0", "concat-stream": "^2.0.0", "markdown-it": "^14.1.0", "markdown-it-abbr": "^2.0.0", "markdown-it-container": "^4.0.0", "markdown-it-deflist": "^3.0.0", "markdown-it-footnote": "^4.0.0", "markdown-it-ins": "^4.0.0", "markdown-it-mark": "^4.0.0", "markdown-it-sub": "^2.0.0", "markdown-it-sup": "^2.0.0", "markdown-it-task-lists": "^2.1.1" }, "bin": { "markdown": "bin/markdown.js", "md": "bin/markdown.js" } }, "sha512-4WFct6LuFxibmSvAXJTGTxZaVqT14jQ8x58lNXKk6afPgWZEFIkUVTCcEn2afBOb7q2bpGIvTRM58e7WcOLSyQ=="],
+
+ "cli-table3": ["[email protected]", "", { "dependencies": { "string-width": "^4.2.0" }, "optionalDependencies": { "@colors/colors": "1.5.0" } }, "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ=="],
+
"cliui": ["[email protected]", "", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="],
"clone": ["[email protected]", "", {}, "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="],
@@ -693,12 +728,20 @@
"color-name": ["[email protected]", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
+ "color-namer": ["[email protected]", "", { "dependencies": { "chroma-js": "^1.3.4", "es6-weak-map": "^2.0.3" } }, "sha512-3mQMY9MJyfdV2uhe+xjQWcKHtYnPtl5svGjt89V2WWT2MlaLAd7C02886Wq7H1MTjjIIEa/NJLYPNF/Lhxhq2A=="],
+
"color-string": ["[email protected]", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="],
+ "colors-option": ["[email protected]", "", { "dependencies": { "chalk": "^5.4.1", "is-plain-obj": "^4.1.0" } }, "sha512-FsAlu5KTTN+W6Xc4NpxNAhl8iCKwVBzjL7Y2ZK6G9zMv50AfMDlU7Mi16lzaDK8Iwpoq/GfAXX+WrYx38gfSHA=="],
+
"comma-separated-tokens": ["[email protected]", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="],
"common-ancestor-path": ["[email protected]", "", {}, "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w=="],
+ "compose-function": ["[email protected]", "", { "dependencies": { "arity-n": "^1.0.4" } }, "sha512-xzhzTJ5eC+gmIzvZq+C3kCJHsp9os6tJkrigDRZclyGtOKINbZtE8n1Tzmeh32jW+BUDPbvZpibwvJHBLGMVwg=="],
+
+ "concat-stream": ["[email protected]", "", { "dependencies": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.0.2", "typedarray": "^0.0.6" } }, "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A=="],
+
"content-disposition": ["[email protected]", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg=="],
"content-type": ["[email protected]", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
@@ -729,6 +772,8 @@
"csstype": ["[email protected]", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
+ "d": ["[email protected]", "", { "dependencies": { "es5-ext": "^0.10.64", "type": "^2.7.2" } }, "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw=="],
+
"data-uri-to-buffer": ["[email protected]", "", {}, "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA=="],
"dateformat": ["[email protected]", "", {}, "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA=="],
@@ -793,10 +838,12 @@
"end-of-stream": ["[email protected]", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q=="],
- "entities": ["[email protected]", "", {}, "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw=="],
+ "entities": ["[email protected]", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
"env-paths": ["[email protected]", "", {}, "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="],
+ "environment": ["[email protected]", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="],
+
"es-define-property": ["[email protected]", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
"es-errors": ["[email protected]", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
@@ -805,6 +852,14 @@
"es-object-atoms": ["[email protected]", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
+ "es5-ext": ["[email protected]", "", { "dependencies": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", "esniff": "^2.0.1", "next-tick": "^1.1.0" } }, "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg=="],
+
+ "es6-iterator": ["[email protected]", "", { "dependencies": { "d": "1", "es5-ext": "^0.10.35", "es6-symbol": "^3.1.1" } }, "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g=="],
+
+ "es6-symbol": ["[email protected]", "", { "dependencies": { "d": "^1.0.2", "ext": "^1.7.0" } }, "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg=="],
+
+ "es6-weak-map": ["[email protected]", "", { "dependencies": { "d": "1", "es5-ext": "^0.10.46", "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.1" } }, "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA=="],
+
"esast-util-from-estree": ["[email protected]", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-visit": "^2.0.0", "unist-util-position-from-estree": "^2.0.0" } }, "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ=="],
"esast-util-from-js": ["[email protected]", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "acorn": "^8.0.0", "esast-util-from-estree": "^2.0.0", "vfile-message": "^4.0.0" } }, "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw=="],
@@ -817,6 +872,8 @@
"escape-string-regexp": ["[email protected]", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
+ "esniff": ["[email protected]", "", { "dependencies": { "d": "^1.0.1", "es5-ext": "^0.10.62", "event-emitter": "^0.3.5", "type": "^2.7.2" } }, "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg=="],
+
"estree-util-attach-comments": ["[email protected]", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw=="],
"estree-util-build-jsx": ["[email protected]", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-walker": "^3.0.0" } }, "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ=="],
@@ -833,6 +890,8 @@
"etag": ["[email protected]", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
+ "event-emitter": ["[email protected]", "", { "dependencies": { "d": "1", "es5-ext": "~0.10.14" } }, "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA=="],
+
"eventemitter3": ["[email protected]", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="],
"events": ["[email protected]", "", {}, "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw=="],
@@ -853,6 +912,8 @@
"exsolve": ["[email protected]", "", {}, "sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg=="],
+ "ext": ["[email protected]", "", { "dependencies": { "type": "^2.7.2" } }, "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw=="],
+
"extend": ["[email protected]", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
"fast-content-type-parse": ["[email protected]", "", {}, "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg=="],
@@ -917,7 +978,7 @@
"h3": ["[email protected]", "", { "dependencies": { "cookie-es": "^1.2.2", "crossws": "^0.3.4", "defu": "^6.1.4", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.0", "radix3": "^1.1.2", "ufo": "^1.6.1", "uncrypto": "^0.1.3" } }, "sha512-z6GknHqyX0h9aQaTx22VZDf6QyZn+0Nh+Ym8O/u0SGSkyF5cuTJYKlc8MkzW3Nzf9LE1ivcpmYC3FUGpywhuUQ=="],
- "has-flag": ["[email protected]", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
+ "has-flag": ["[email protected]", "", {}, "sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA=="],
"has-property-descriptors": ["[email protected]", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="],
@@ -969,6 +1030,10 @@
"hastscript": ["[email protected]", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="],
+ "he": ["[email protected]", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="],
+
+ "highlight.js": ["[email protected]", "", {}, "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="],
+
"hono": ["[email protected]", "", {}, "sha512-QkACju9MiN59CKSY5JsGZCYmPZkA6sIW6OFCUp7qDjZu6S6KHtJHhAc9Uy9mV9F8PJ1/HQ3ybZF2yjCa/73fvQ=="],
"hono-openapi": ["[email protected]", "", { "dependencies": { "json-schema-walker": "^2.0.0" }, "peerDependencies": { "@hono/arktype-validator": "^2.0.0", "@hono/effect-validator": "^1.2.0", "@hono/typebox-validator": "^0.2.0 || ^0.3.0", "@hono/valibot-validator": "^0.5.1", "@hono/zod-validator": "^0.4.1", "@sinclair/typebox": "^0.34.9", "@valibot/to-json-schema": "^1.0.0-beta.3", "arktype": "^2.0.0", "effect": "^3.11.3", "hono": "^4.6.13", "openapi-types": "^12.1.3", "valibot": "^1.0.0-beta.9", "zod": "^3.23.8", "zod-openapi": "^4.0.0" }, "optionalPeers": ["@hono/arktype-validator", "@hono/effect-validator", "@hono/typebox-validator", "@hono/valibot-validator", "@hono/zod-validator", "@sinclair/typebox", "@valibot/to-json-schema", "arktype", "effect", "hono", "valibot", "zod", "zod-openapi"] }, "sha512-LYr5xdtD49M7hEAduV1PftOMzuT8ZNvkyWfh1DThkLsIr4RkvDb12UxgIiFbwrJB6FLtFXLoOZL9x4IeDk2+VA=="],
@@ -1077,8 +1142,14 @@
"language-map": ["[email protected]", "", {}, "sha512-n7gFZpe+DwEAX9cXVTw43i3wiudWDDtSn28RmdnS/HCPr284dQI/SztsamWanRr75oSlKSaGbV2nmWCTzGCoVg=="],
+ "languages-aliases": ["[email protected]", "", {}, "sha512-0TeT8ZQXq5y59hzowc2PSHkqDDEApXUpHl35BmwxG+8Uuy30ISpMMx51kdBHD3LoVmGgNxBsKcsV4HJClL4U2g=="],
+
"leven": ["[email protected]", "", {}, "sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA=="],
+ "linkify-it": ["[email protected]", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ=="],
+
+ "longest-line": ["[email protected]", "", { "dependencies": { "strip-ansi": "^3.0.0" } }, "sha512-ZRdPmYYhydc50iw8abrurJaWD+MGLaMOZrNOE2BzQIsumYBIbg07ByILhyiSXIczVdBxipotSrbdgt2JjWPeCg=="],
+
"longest-streak": ["[email protected]", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="],
"lru-cache": ["[email protected]", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
@@ -1091,6 +1162,26 @@
"markdown-extensions": ["[email protected]", "", {}, "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q=="],
+ "markdown-it": ["[email protected]", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg=="],
+
+ "markdown-it-abbr": ["[email protected]", "", {}, "sha512-of7C8pXSjXjDojW4neNP+jD7inUYH/DO0Ca+K/4FUEccg0oHAEX/nfscw0jfz66PJbYWOAT9U8mjO21X5p6aAw=="],
+
+ "markdown-it-container": ["[email protected]", "", {}, "sha512-HaNccxUH0l7BNGYbFbjmGpf5aLHAMTinqRZQAEQbMr2cdD3z91Q6kIo1oUn1CQndkT03jat6ckrdRYuwwqLlQw=="],
+
+ "markdown-it-deflist": ["[email protected]", "", {}, "sha512-OxPmQ/keJZwbubjiQWOvKLHwpV2wZ5I3Smc81OjhwbfJsjdRrvD5aLTQxmZzzePeO0kbGzAo3Krk4QLgA8PWLg=="],
+
+ "markdown-it-footnote": ["[email protected]", "", {}, "sha512-WYJ7urf+khJYl3DqofQpYfEYkZKbmXmwxQV8c8mO/hGIhgZ1wOe7R4HLFNwqx7TjILbnC98fuyeSsin19JdFcQ=="],
+
+ "markdown-it-ins": ["[email protected]", "", {}, "sha512-sWbjK2DprrkINE4oYDhHdCijGT+MIDhEupjSHLXe5UXeVr5qmVxs/nTUVtgi0Oh/qtF+QKV0tNWDhQBEPxiMew=="],
+
+ "markdown-it-mark": ["[email protected]", "", {}, "sha512-YLhzaOsU9THO/cal0lUjfMjrqSMPjjyjChYM7oyj4DnyaXEzA8gnW6cVJeyCrCVeyesrY2PlEdUYJSPFYL4Nkg=="],
+
+ "markdown-it-sub": ["[email protected]", "", {}, "sha512-iCBKgwCkfQBRg2vApy9vx1C1Tu6D8XYo8NvevI3OlwzBRmiMtsJ2sXupBgEA7PPxiDwNni3qIUkhZ6j5wofDUA=="],
+
+ "markdown-it-sup": ["[email protected]", "", {}, "sha512-5VgmdKlkBd8sgXuoDoxMpiU+BiEt3I49GItBzzw7Mxq9CxvnhE/k09HFli09zgfFDRixDQDfDxi0mgBCXtaTvA=="],
+
+ "markdown-it-task-lists": ["[email protected]", "", {}, "sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA=="],
+
"markdown-table": ["[email protected]", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="],
"marked": ["[email protected]", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="],
@@ -1137,6 +1228,8 @@
"mdn-data": ["[email protected]", "", {}, "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA=="],
+ "mdurl": ["[email protected]", "", {}, "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="],
+
"media-typer": ["[email protected]", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
"merge-anything": ["[email protected]", "", { "dependencies": { "is-what": "^4.1.8" } }, "sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ=="],
@@ -1241,6 +1334,8 @@
"mustache": ["[email protected]", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="],
+ "mz": ["[email protected]", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
+
"nanoid": ["[email protected]", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"napi-build-utils": ["[email protected]", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="],
@@ -1249,6 +1344,8 @@
"neotraverse": ["[email protected]", "", {}, "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA=="],
+ "next-tick": ["[email protected]", "", {}, "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="],
+
"nlcst-to-string": ["[email protected]", "", { "dependencies": { "@types/nlcst": "^2.0.0" } }, "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA=="],
"node-abi": ["[email protected]", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg=="],
@@ -1263,10 +1360,14 @@
"node-releases": ["[email protected]", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="],
+ "normalize-html-whitespace": ["[email protected]", "", {}, "sha512-9ui7CGtOOlehQu0t/OhhlmDyc71mKVlv+4vF+me4iZLPrNtRL2xoquEdfZxasC/bdQi/Hr3iTrpyRKIG+ocabA=="],
+
"normalize-path": ["[email protected]", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
"nth-check": ["[email protected]", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
+ "number-to-alphabet": ["[email protected]", "", {}, "sha512-5hahJfMZmQ78ydjHB0d9xrZiUOhEKD1u3BngbhfTs/3CCVbnOlbyAbSw8nIe+dpcRtJ+zOthw2YcG6DkfzlEgA=="],
+
"object-assign": ["[email protected]", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
"object-hash": ["[email protected]", "", {}, "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw=="],
@@ -1317,6 +1418,8 @@
"parse5": ["[email protected]", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
+ "parse5-htmlparser2-tree-adapter": ["[email protected]", "", { "dependencies": { "parse5": "^6.0.1" } }, "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA=="],
+
"parseurl": ["[email protected]", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
"path-browserify": ["[email protected]", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
@@ -1373,6 +1476,8 @@
"punycode": ["[email protected]", "", {}, "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw=="],
+ "punycode.js": ["[email protected]", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="],
+
"qs": ["[email protected]", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="],
"querystring": ["[email protected]", "", {}, "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g=="],
@@ -1439,6 +1544,10 @@
"remeda": ["[email protected]", "", { "dependencies": { "type-fest": "^4.40.1" } }, "sha512-Ka6965m9Zu9OLsysWxVf3jdJKmp6+PKzDv7HWHinEevf0JOJ9y02YpjiC/sKxRpCqGhVyvm1U+0YIj+E6DMgKw=="],
+ "repeat-string": ["[email protected]", "", {}, "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w=="],
+
+ "require-directory": ["[email protected]", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
+
"restructure": ["[email protected]", "", {}, "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw=="],
"retext": ["[email protected]", "", { "dependencies": { "@types/nlcst": "^2.0.0", "retext-latin": "^4.0.0", "retext-stringify": "^4.0.0", "unified": "^11.0.0" } }, "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA=="],
@@ -1453,6 +1562,8 @@
"rollup": ["[email protected]", "", { "dependencies": { "@types/estree": "1.0.7" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.41.1", "@rollup/rollup-android-arm64": "4.41.1", "@rollup/rollup-darwin-arm64": "4.41.1", "@rollup/rollup-darwin-x64": "4.41.1", "@rollup/rollup-freebsd-arm64": "4.41.1", "@rollup/rollup-freebsd-x64": "4.41.1", "@rollup/rollup-linux-arm-gnueabihf": "4.41.1", "@rollup/rollup-linux-arm-musleabihf": "4.41.1", "@rollup/rollup-linux-arm64-gnu": "4.41.1", "@rollup/rollup-linux-arm64-musl": "4.41.1", "@rollup/rollup-linux-loongarch64-gnu": "4.41.1", "@rollup/rollup-linux-powerpc64le-gnu": "4.41.1", "@rollup/rollup-linux-riscv64-gnu": "4.41.1", "@rollup/rollup-linux-riscv64-musl": "4.41.1", "@rollup/rollup-linux-s390x-gnu": "4.41.1", "@rollup/rollup-linux-x64-gnu": "4.41.1", "@rollup/rollup-linux-x64-musl": "4.41.1", "@rollup/rollup-win32-arm64-msvc": "4.41.1", "@rollup/rollup-win32-ia32-msvc": "4.41.1", "@rollup/rollup-win32-x64-msvc": "4.41.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw=="],
+ "romanize": ["[email protected]", "", { "dependencies": { "repeat-string": "^1.6.1" } }, "sha512-DpNfq5KrpHNT60jpyOtPyyZGVGNKoBbaMSkCo3m6Hl+4dLPB4jUR3Gs/Agbdi8dB314jjyQFl2+dRS7rqXy9Lw=="],
+
"router": ["[email protected]", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
"run-applescript": ["[email protected]", "", {}, "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A=="],
@@ -1573,14 +1684,22 @@
"style-to-object": ["[email protected]", "", { "dependencies": { "inline-style-parser": "0.2.4" } }, "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g=="],
- "supports-color": ["[email protected]", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
+ "supports-color": ["[email protected]", "", {}, "sha512-HRVVSbCCMbj7/kdWF9Q+bbckjBHLtHMEoJWlkmYzzdwhYMkjkOwubLM6t7NbWKjgKamGDrWL1++KrjUO1t9oAQ=="],
+
+ "supports-hyperlinks": ["[email protected]", "", { "dependencies": { "has-flag": "^5.0.1", "supports-color": "^10.0.0" } }, "sha512-6lY0rDZ5bbZhAPrwpz/nMR6XmeaFmh2itk7YnIyph2jblPmDcKMCPkSdLFTlaX8snBvg7OJmaOL3WRLqMEqcJQ=="],
"tar-fs": ["[email protected]", "", { "dependencies": { "pump": "^3.0.0", "tar-stream": "^3.1.5" }, "optionalDependencies": { "bare-fs": "^4.0.1", "bare-path": "^3.0.0" } }, "sha512-XF4w9Xp+ZQgifKakjZYmFdkLoSWd34VGKcsTCwlNWM7QG3ZbaxnTsaBwnjFZqHRf/rROxaR8rXnbtwdvaDI+lA=="],
"tar-stream": ["[email protected]", "", { "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ=="],
+ "terminal-size": ["[email protected]", "", {}, "sha512-rcdty1xZ2/BkWa4ANjWRp4JGpda2quksXIHgn5TMjNBPZfwzJIgR68DKfSYiTL+CZWowDX/sbOo5ME/FRURvYQ=="],
+
"text-decoder": ["[email protected]", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA=="],
+ "thenify": ["[email protected]", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="],
+
+ "thenify-all": ["[email protected]", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="],
+
"thread-stream": ["[email protected]", "", { "dependencies": { "real-require": "^0.1.0" } }, "sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA=="],
"tiny-inflate": ["[email protected]", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="],
@@ -1613,12 +1732,18 @@
"turndown": ["[email protected]", "", { "dependencies": { "@mixmark-io/domino": "^2.2.0" } }, "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A=="],
+ "type": ["[email protected]", "", {}, "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ=="],
+
"type-fest": ["[email protected]", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
"type-is": ["[email protected]", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
+ "typedarray": ["[email protected]", "", {}, "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="],
+
"typescript": ["[email protected]", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="],
+ "uc.micro": ["[email protected]", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="],
+
"ufo": ["[email protected]", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="],
"uint8array-extras": ["[email protected]", "", {}, "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ=="],
@@ -1741,6 +1866,8 @@
"yallist": ["[email protected]", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
+ "yaml": ["[email protected]", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ=="],
+
"yargs": ["[email protected]", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^7.2.0", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg=="],
"yargs-parser": ["[email protected]", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
@@ -1841,6 +1968,14 @@
"bl/buffer": ["[email protected]", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
+ "cli-highlight/chalk": ["[email protected]", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
+
+ "cli-highlight/parse5": ["[email protected]", "", {}, "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug=="],
+
+ "cli-highlight/yargs": ["[email protected]", "", { "dependencies": { "cliui": "^7.0.2", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.0", "y18n": "^5.0.5", "yargs-parser": "^20.2.2" } }, "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw=="],
+
+ "cli-table3/string-width": ["[email protected]", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
+
"eventsource/eventsource-parser": ["[email protected]", "", {}, "sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA=="],
"express/cookie": ["[email protected]", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
@@ -1849,6 +1984,8 @@
"hast-util-to-parse5/property-information": ["[email protected]", "", {}, "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig=="],
+ "longest-line/strip-ansi": ["[email protected]", "", { "dependencies": { "ansi-regex": "^2.0.0" } }, "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg=="],
+
"mdast-util-find-and-replace/escape-string-regexp": ["[email protected]", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
"miniflare/acorn": ["[email protected]", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="],
@@ -1869,6 +2006,10 @@
"parse-entities/@types/unist": ["@types/[email protected]", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
+ "parse5/entities": ["[email protected]", "", {}, "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw=="],
+
+ "parse5-htmlparser2-tree-adapter/parse5": ["[email protected]", "", {}, "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="],
+
"pino-abstract-transport/split2": ["[email protected]", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
"pino-pretty/chalk": ["[email protected]", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
@@ -1921,12 +2062,30 @@
"bl/buffer/ieee754": ["[email protected]", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
+ "cli-highlight/chalk/ansi-styles": ["[email protected]", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
+
+ "cli-highlight/chalk/supports-color": ["[email protected]", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
+
+ "cli-highlight/yargs/cliui": ["[email protected]", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" } }, "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ=="],
+
+ "cli-highlight/yargs/string-width": ["[email protected]", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
+
+ "cli-highlight/yargs/yargs-parser": ["[email protected]", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="],
+
+ "cli-table3/string-width/emoji-regex": ["[email protected]", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+
+ "cli-table3/string-width/strip-ansi": ["[email protected]", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
+ "longest-line/strip-ansi/ansi-regex": ["[email protected]", "", {}, "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA=="],
+
"opencontrol/@modelcontextprotocol/sdk/pkce-challenge": ["[email protected]", "", {}, "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ=="],
"opencontrol/@modelcontextprotocol/sdk/zod-to-json-schema": ["[email protected]", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="],
"pino-pretty/chalk/ansi-styles": ["[email protected]", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
+ "pino-pretty/chalk/supports-color": ["[email protected]", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
+
"prebuild-install/tar-fs/tar-stream": ["[email protected]", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="],
"wrangler/esbuild/@esbuild/aix-ppc64": ["@esbuild/[email protected]", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="],
@@ -1987,6 +2146,26 @@
"args/chalk/supports-color/has-flag": ["[email protected]", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
+ "cli-highlight/chalk/supports-color/has-flag": ["[email protected]", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
+
+ "cli-highlight/yargs/cliui/strip-ansi": ["[email protected]", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
+ "cli-highlight/yargs/cliui/wrap-ansi": ["[email protected]", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
+
+ "cli-highlight/yargs/string-width/emoji-regex": ["[email protected]", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+
+ "cli-highlight/yargs/string-width/strip-ansi": ["[email protected]", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
+ "cli-table3/string-width/strip-ansi/ansi-regex": ["[email protected]", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
+ "pino-pretty/chalk/supports-color/has-flag": ["[email protected]", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
+
"args/chalk/ansi-styles/color-convert/color-name": ["[email protected]", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
+
+ "cli-highlight/yargs/cliui/strip-ansi/ansi-regex": ["[email protected]", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
+ "cli-highlight/yargs/cliui/wrap-ansi/ansi-styles": ["[email protected]", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
+
+ "cli-highlight/yargs/string-width/strip-ansi/ansi-regex": ["[email protected]", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
}
}
diff --git a/packages/function/src/api.ts b/packages/function/src/api.ts
index 150afd887..c5ac8b4a4 100644
--- a/packages/function/src/api.ts
+++ b/packages/function/src/api.ts
@@ -42,7 +42,11 @@ export class SyncServer extends DurableObject<Env> {
async publish(key: string, content: any) {
const sessionID = await this.getSessionID()
- if (!key.startsWith(`session/info/${sessionID}`) && !key.startsWith(`session/message/${sessionID}/`))
+ if (
+ !key.startsWith(`session/info/${sessionID}`) &&
+ !key.startsWith(`session/message/${sessionID}/`) &&
+ !key.startsWith(`session/part/${sessionID}/`)
+ )
return new Response("Error: Invalid key", { status: 400 })
// store message
@@ -71,7 +75,7 @@ export class SyncServer extends DurableObject<Env> {
}
public async getData() {
- const data = await this.ctx.storage.list()
+ const data = (await this.ctx.storage.list()) as Map<string, any>
return Array.from(data.entries())
.filter(([key, _]) => key.startsWith("session/"))
.map(([key, content]) => ({ key, content }))
@@ -207,8 +211,13 @@ export default {
return
}
if (type === "message") {
- const [, messageID] = splits
- messages[messageID] = d.content
+ messages[d.content.id] = {
+ parts: [],
+ ...d.content,
+ }
+ }
+ if (type === "part") {
+ messages[d.content.messageID].parts.push(d.content)
}
})
diff --git a/packages/opencode/package.json b/packages/opencode/package.json
index c5b7e4ba9..6e61c1d82 100644
--- a/packages/opencode/package.json
+++ b/packages/opencode/package.json
@@ -33,6 +33,7 @@
"@openauthjs/openauth": "0.4.3",
"@standard-schema/spec": "1.0.0",
"ai": "catalog:",
+ "cli-markdown": "3.5.1",
"decimal.js": "10.5.0",
"diff": "8.0.2",
"env-paths": "3.0.0",
diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts
index 0daa510d1..553bff818 100644
--- a/packages/opencode/src/cli/cmd/run.ts
+++ b/packages/opencode/src/cli/cmd/run.ts
@@ -9,6 +9,7 @@ import { Config } from "../../config/config"
import { bootstrap } from "../bootstrap"
import { MessageV2 } from "../../session/message-v2"
import { Mode } from "../../session/mode"
+import { Identifier } from "../../id/id"
const TOOL: Record<string, [string, string]> = {
todowrite: ["Todo", UI.Style.TEXT_WARNING_BOLD],
@@ -83,14 +84,9 @@ export const RunCommand = cmd({
return
}
- const isPiped = !process.stdout.isTTY
-
UI.empty()
UI.println(UI.logo())
UI.empty()
- const displayMessage = message.length > 300 ? message.slice(0, 300) + "..." : message
- UI.println(UI.Style.TEXT_NORMAL_BOLD + "> ", displayMessage)
- UI.empty()
const cfg = await Config.get()
if (cfg.share === "auto" || Flag.OPENCODE_AUTO_SHARE || args.share) {
@@ -120,8 +116,10 @@ export const RunCommand = cmd({
)
}
+ let text = ""
Bus.subscribe(MessageV2.Event.PartUpdated, async (evt) => {
- if (evt.properties.sessionID !== session.id) return
+ if (evt.properties.part.sessionID !== session.id) return
+ if (evt.properties.part.messageID === messageID) return
const part = evt.properties.part
if (part.type === "tool" && part.state.status === "completed") {
@@ -130,13 +128,15 @@ export const RunCommand = cmd({
}
if (part.type === "text") {
- if (part.text.includes("\n")) {
+ text = part.text
+
+ if (part.time?.end) {
UI.empty()
- UI.println(part.text)
+ UI.println(UI.markdown(text))
UI.empty()
+ text = ""
return
}
- printEvent(UI.Style.TEXT_NORMAL_BOLD, "Text", part.text)
}
})
@@ -156,8 +156,10 @@ export const RunCommand = cmd({
const mode = args.mode ? await Mode.get(args.mode) : await Mode.list().then((x) => x[0])
+ const messageID = Identifier.ascending("message")
const result = await Session.chat({
sessionID: session.id,
+ messageID,
...(mode.model
? mode.model
: {
@@ -167,15 +169,19 @@ export const RunCommand = cmd({
mode: mode.name,
parts: [
{
+ id: Identifier.ascending("part"),
+ sessionID: session.id,
+ messageID: messageID,
type: "text",
text: message,
},
],
})
+ const isPiped = !process.stdout.isTTY
if (isPiped) {
const match = result.parts.findLast((x) => x.type === "text")
- if (match) process.stdout.write(match.text)
+ if (match) process.stdout.write(UI.markdown(match.text))
if (errorMsg) process.stdout.write(errorMsg)
}
UI.empty()
diff --git a/packages/opencode/src/cli/cmd/stats.ts b/packages/opencode/src/cli/cmd/stats.ts
index 6c0b16003..39ae86ba0 100644
--- a/packages/opencode/src/cli/cmd/stats.ts
+++ b/packages/opencode/src/cli/cmd/stats.ts
@@ -1,7 +1,4 @@
-import { Storage } from "../../storage/storage"
-import { MessageV2 } from "../../session/message-v2"
import { cmd } from "./cmd"
-import { bootstrap } from "../bootstrap"
interface SessionStats {
totalSessions: number
@@ -27,87 +24,10 @@ interface SessionStats {
export const StatsCommand = cmd({
command: "stats",
- handler: async () => {
- await bootstrap({ cwd: process.cwd() }, async () => {
- const stats: SessionStats = {
- totalSessions: 0,
- totalMessages: 0,
- totalCost: 0,
- totalTokens: {
- input: 0,
- output: 0,
- reasoning: 0,
- cache: {
- read: 0,
- write: 0,
- },
- },
- toolUsage: {},
- dateRange: {
- earliest: Date.now(),
- latest: 0,
- },
- days: 0,
- costPerDay: 0,
- }
-
- const sessionMap = new Map<string, number>()
-
- try {
- for await (const messagePath of Storage.list("session/message")) {
- try {
- const message = await Storage.readJSON<MessageV2.Info>(messagePath)
- if (!message.parts.find((part) => part.type === "step-finish")) continue
-
- stats.totalMessages++
-
- const sessionId = message.sessionID
- sessionMap.set(sessionId, (sessionMap.get(sessionId) || 0) + 1)
-
- if (message.time.created < stats.dateRange.earliest) {
- stats.dateRange.earliest = message.time.created
- }
- if (message.time.created > stats.dateRange.latest) {
- stats.dateRange.latest = message.time.created
- }
-
- if (message.role === "assistant") {
- stats.totalCost += message.cost
- stats.totalTokens.input += message.tokens.input
- stats.totalTokens.output += message.tokens.output
- stats.totalTokens.reasoning += message.tokens.reasoning
- stats.totalTokens.cache.read += message.tokens.cache.read
- stats.totalTokens.cache.write += message.tokens.cache.write
-
- for (const part of message.parts) {
- if (part.type === "tool") {
- stats.toolUsage[part.tool] = (stats.toolUsage[part.tool] || 0) + 1
- }
- }
- }
- } catch (e) {
- continue
- }
- }
- } catch (e) {
- console.error("Failed to read storage:", e)
- return
- }
-
- stats.totalSessions = sessionMap.size
-
- if (stats.dateRange.latest > 0) {
- const daysDiff = (stats.dateRange.latest - stats.dateRange.earliest) / (1000 * 60 * 60 * 24)
- stats.days = Math.max(1, Math.ceil(daysDiff))
- stats.costPerDay = stats.totalCost / stats.days
- }
-
- displayStats(stats)
- })
- },
+ handler: async () => {},
})
-function displayStats(stats: SessionStats) {
+export function displayStats(stats: SessionStats) {
const width = 56
function renderRow(label: string, value: string): string {
diff --git a/packages/opencode/src/cli/ui.ts b/packages/opencode/src/cli/ui.ts
index 9801b459e..1b6c3cace 100644
--- a/packages/opencode/src/cli/ui.ts
+++ b/packages/opencode/src/cli/ui.ts
@@ -1,6 +1,8 @@
import { z } from "zod"
import { EOL } from "os"
import { NamedError } from "../util/error"
+// @ts-ignore
+import cliMarkdown from "cli-markdown"
export namespace UI {
const LOGO = [
@@ -76,4 +78,18 @@ export namespace UI {
export function error(message: string) {
println(Style.TEXT_DANGER_BOLD + "Error: " + Style.TEXT_NORMAL + message)
}
+
+ export function markdown(text: string): string {
+ const rendered = cliMarkdown(text, {
+ width: process.stdout.columns || 80,
+ firstHeading: false,
+ tab: 0,
+ }).trim()
+
+ // Remove leading space from each line
+ return rendered
+ .split("\n")
+ .map((line: string) => line.replace(/^ /, ""))
+ .join("\n")
+ }
}
diff --git a/packages/opencode/src/id/id.ts b/packages/opencode/src/id/id.ts
index b705ff2ce..6c1edd50d 100644
--- a/packages/opencode/src/id/id.ts
+++ b/packages/opencode/src/id/id.ts
@@ -6,6 +6,7 @@ export namespace Identifier {
session: "ses",
message: "msg",
user: "usr",
+ part: "prt",
} as const
export function schema(prefix: keyof typeof prefixes) {
diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts
index a958f48b6..6469b9bbc 100644
--- a/packages/opencode/src/server/server.ts
+++ b/packages/opencode/src/server/server.ts
@@ -269,6 +269,7 @@ export namespace Server {
zValidator(
"json",
z.object({
+ messageID: z.string(),
providerID: z.string(),
modelID: z.string(),
}),
@@ -405,7 +406,14 @@ export namespace Server {
description: "List of messages",
content: {
"application/json": {
- schema: resolver(MessageV2.Info.array()),
+ schema: resolver(
+ z
+ .object({
+ info: MessageV2.Info,
+ parts: MessageV2.Part.array(),
+ })
+ .array(),
+ ),
},
},
},
@@ -446,10 +454,11 @@ export namespace Server {
zValidator(
"json",
z.object({
+ messageID: z.string(),
providerID: z.string(),
modelID: z.string(),
mode: z.string(),
- parts: MessageV2.UserPart.array(),
+ parts: z.union([MessageV2.FilePart, MessageV2.TextPart]).array(),
}),
),
async (c) => {
diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts
index 16b9de9d3..44879e9b8 100644
--- a/packages/opencode/src/session/index.ts
+++ b/packages/opencode/src/session/index.ts
@@ -12,6 +12,7 @@ import {
type ProviderMetadata,
type ModelMessage,
stepCountIs,
+ type StreamTextResult,
} from "ai"
import PROMPT_INITIALIZE from "../session/prompt/initialize.txt"
@@ -190,7 +191,10 @@ export namespace Session {
await Storage.writeJSON<ShareInfo>("session/share/" + id, share)
await Share.sync("session/info/" + id, session)
for (const msg of await messages(id)) {
- await Share.sync("session/message/" + id + "/" + msg.id, msg)
+ await Share.sync("session/message/" + id + "/" + msg.info.id, msg.info)
+ for (const part of msg.parts) {
+ await Share.sync("session/part/" + id + "/" + msg.info.id + "/" + part.id, part)
+ }
}
return share
}
@@ -220,13 +224,19 @@ export namespace Session {
}
export async function messages(sessionID: string) {
- const result = [] as MessageV2.Info[]
+ const result = [] as {
+ info: MessageV2.Info
+ parts: MessageV2.Part[]
+ }[]
const list = Storage.list("session/message/" + sessionID)
for await (const p of list) {
const read = await Storage.readJSON<MessageV2.Info>(p)
- result.push(read)
+ result.push({
+ info: read,
+ parts: await parts(sessionID, read.id),
+ })
}
- result.sort((a, b) => (a.id > b.id ? 1 : -1))
+ result.sort((a, b) => (a.info.id > b.info.id ? 1 : -1))
return result
}
@@ -234,6 +244,16 @@ export namespace Session {
return Storage.readJSON<MessageV2.Info>("session/message/" + sessionID + "/" + messageID)
}
+ export async function parts(sessionID: string, messageID: string) {
+ const result = [] as MessageV2.Part[]
+ for await (const item of Storage.list("session/part/" + sessionID + "/" + messageID)) {
+ const read = await Storage.readJSON<MessageV2.Part>(item)
+ result.push(read)
+ }
+ result.sort((a, b) => (a.id > b.id ? 1 : -1))
+ return result
+ }
+
export async function* list() {
for await (const item of Storage.list("session/info")) {
const sessionID = path.basename(item, ".json")
@@ -289,12 +309,21 @@ export namespace Session {
})
}
+ async function updatePart(part: MessageV2.Part) {
+ await Storage.writeJSON(["session", "part", part.sessionID, part.messageID, part.id].join("/"), part)
+ Bus.publish(MessageV2.Event.PartUpdated, {
+ part,
+ })
+ return part
+ }
+
export async function chat(input: {
sessionID: string
+ messageID: string
providerID: string
modelID: string
mode?: string
- parts: MessageV2.UserPart[]
+ parts: (MessageV2.TextPart | MessageV2.FilePart)[]
}) {
const l = log.clone().tag("session", input.sessionID)
l.info("chatting")
@@ -306,16 +335,19 @@ export namespace Session {
if (session.revert) {
const trimmed = []
for (const msg of msgs) {
- if (msg.id > session.revert.messageID || (msg.id === session.revert.messageID && session.revert.part === 0)) {
- await Storage.remove("session/message/" + input.sessionID + "/" + msg.id)
+ if (
+ msg.info.id > session.revert.messageID ||
+ (msg.info.id === session.revert.messageID && session.revert.part === 0)
+ ) {
+ await Storage.remove("session/message/" + input.sessionID + "/" + msg.info.id)
await Bus.publish(MessageV2.Event.Removed, {
sessionID: input.sessionID,
- messageID: msg.id,
+ messageID: msg.info.id,
})
continue
}
- if (msg.id === session.revert.messageID) {
+ if (msg.info.id === session.revert.messageID) {
if (session.revert.part === 0) break
msg.parts = msg.parts.slice(0, session.revert.part)
}
@@ -327,7 +359,7 @@ export namespace Session {
})
}
- const previous = msgs.at(-1) as MessageV2.Assistant
+ const previous = msgs.filter((x) => x.info.role === "assistant").at(-1)?.info as MessageV2.Assistant
const outputLimit = Math.min(model.info.limit.output, OUTPUT_TOKEN_MAX) || OUTPUT_TOKEN_MAX
// auto summarize if too long
@@ -346,12 +378,21 @@ export namespace Session {
using abort = lock(input.sessionID)
- const lastSummary = msgs.findLast((msg) => msg.role === "assistant" && msg.summary === true)
- if (lastSummary) msgs = msgs.filter((msg) => msg.id >= lastSummary.id)
+ const lastSummary = msgs.findLast((msg) => msg.info.role === "assistant" && msg.info.summary === true)
+ if (lastSummary) msgs = msgs.filter((msg) => msg.info.id >= lastSummary.info.id)
+
+ const userMsg: MessageV2.Info = {
+ id: input.messageID,
+ role: "user",
+ sessionID: input.sessionID,
+ time: {
+ created: Date.now(),
+ },
+ }
const app = App.info()
- input.parts = await Promise.all(
- input.parts.map(async (part): Promise<MessageV2.UserPart[]> => {
+ const userParts = await Promise.all(
+ input.parts.map(async (part): Promise<MessageV2.Part[]> => {
if (part.type === "file") {
const url = new URL(part.url)
switch (url.protocol) {
@@ -406,11 +447,17 @@ export namespace Session {
})
return [
{
+ id: Identifier.ascending("part"),
+ messageID: userMsg.id,
+ sessionID: input.sessionID,
type: "text",
synthetic: true,
text: `Called the Read tool with the following input: ${JSON.stringify(args)}`,
},
{
+ id: Identifier.ascending("part"),
+ messageID: userMsg.id,
+ sessionID: input.sessionID,
type: "text",
synthetic: true,
text: result.output,
@@ -422,11 +469,17 @@ export namespace Session {
FileTime.read(input.sessionID, filePath)
return [
{
+ id: Identifier.ascending("part"),
+ messageID: userMsg.id,
+ sessionID: input.sessionID,
type: "text",
text: `Called the Read tool with the following input: {\"filePath\":\"${pathname}\"}`,
synthetic: true,
},
{
+ id: Identifier.ascending("part"),
+ messageID: userMsg.id,
+ sessionID: input.sessionID,
type: "file",
url: `data:${part.mime};base64,` + Buffer.from(await file.bytes()).toString("base64"),
mime: part.mime,
@@ -440,7 +493,10 @@ export namespace Session {
).then((x) => x.flat())
if (input.mode === "plan")
- input.parts.push({
+ userParts.push({
+ id: Identifier.ascending("part"),
+ messageID: userMsg.id,
+ sessionID: input.sessionID,
type: "text",
text: PROMPT_PLAN,
synthetic: true,
@@ -459,13 +515,15 @@ export namespace Session {
),
...MessageV2.toModelMessage([
{
- id: Identifier.ascending("message"),
- role: "user",
- sessionID: input.sessionID,
- parts: input.parts,
- time: {
- created: Date.now(),
+ info: {
+ id: Identifier.ascending("message"),
+ role: "user",
+ sessionID: input.sessionID,
+ time: {
+ created: Date.now(),
+ },
},
+ parts: userParts,
},
]),
],
@@ -479,17 +537,11 @@ export namespace Session {
})
.catch(() => {})
}
- const msg: MessageV2.Info = {
- id: Identifier.ascending("message"),
- role: "user",
- sessionID: input.sessionID,
- parts: input.parts,
- time: {
- created: Date.now(),
- },
+ await updateMessage(userMsg)
+ for (const part of userParts) {
+ await updatePart(part)
}
- await updateMessage(msg)
- msgs.push(msg)
+ msgs.push({ info: userMsg, parts: userParts })
const mode = await Mode.get(input.mode ?? "build")
let system = mode.prompt ? [mode.prompt] : SystemPrompt.provider(input.providerID, input.modelID)
@@ -499,10 +551,9 @@ export namespace Session {
const [first, ...rest] = system
system = [first, rest.join("\n")]
- const next: MessageV2.Info = {
+ const assistantMsg: MessageV2.Info = {
id: Identifier.ascending("message"),
role: "assistant",
- parts: [],
system,
path: {
cwd: app.path.cwd,
@@ -522,7 +573,7 @@ export namespace Session {
},
sessionID: input.sessionID,
}
- await updateMessage(next)
+ await updateMessage(assistantMsg)
const tools: Record<string, AITool> = {}
for (const item of await Provider.tools(input.providerID)) {
@@ -531,20 +582,29 @@ export namespace Session {
id: item.id as any,
description: item.description,
inputSchema: item.parameters as ZodSchema,
- async execute(args, opts) {
+ async execute(args) {
const result = await item.execute(args, {
sessionID: input.sessionID,
abort: abort.signal,
- messageID: next.id,
- metadata: async (val) => {
- const match = next.parts.find(
- (p): p is MessageV2.ToolPart => p.type === "tool" && p.id === opts.toolCallId,
- )
+ messageID: assistantMsg.id,
+ metadata: async () => {
+ /*
+ const match = toolCalls[opts.toolCallId]
if (match && match.state.status === "running") {
- match.state.title = val.title
- match.state.metadata = val.metadata
+ await updatePart({
+ ...match,
+ state: {
+ title: val.title,
+ metadata: val.metadata,
+ status: "running",
+ input: args.input,
+ time: {
+ start: Date.now(),
+ },
+ },
+ })
}
- await updateMessage(next)
+ */
},
})
return result
@@ -582,10 +642,6 @@ export namespace Session {
tools[key] = item
}
- let text: MessageV2.TextPart = {
- type: "text",
- text: "",
- }
const result = streamText({
onError() {},
maxRetries: 10,
@@ -619,9 +675,20 @@ export namespace Session {
],
}),
})
+ return processStream(assistantMsg, model.info, result)
+ }
+
+ async function processStream(
+ assistantMsg: MessageV2.Assistant,
+ model: ModelsDev.Model,
+ stream: StreamTextResult<Record<string, AITool>, never>,
+ ) {
try {
- for await (const value of result.fullStream) {
- l.info("part", {
+ let currentText: MessageV2.TextPart | undefined
+ const toolCalls: Record<string, MessageV2.ToolPart> = {}
+
+ for await (const value of stream.fullStream) {
+ log.info("part", {
type: value.type,
})
switch (value.type) {
@@ -629,88 +696,78 @@ export namespace Session {
break
case "tool-input-start":
- next.parts.push({
+ const part = await updatePart({
+ id: Identifier.ascending("part"),
+ messageID: assistantMsg.id,
+ sessionID: assistantMsg.sessionID,
type: "tool",
tool: value.toolName,
- id: value.id,
+ callID: value.id,
state: {
status: "pending",
},
})
- Bus.publish(MessageV2.Event.PartUpdated, {
- part: next.parts[next.parts.length - 1],
- sessionID: next.sessionID,
- messageID: next.id,
- })
+ toolCalls[value.id] = part as MessageV2.ToolPart
break
case "tool-input-delta":
break
case "tool-call": {
- const match = next.parts.find(
- (p): p is MessageV2.ToolPart => p.type === "tool" && p.id === value.toolCallId,
- )
+ const match = toolCalls[value.toolCallId]
if (match) {
- match.state = {
- status: "running",
- input: value.input,
- time: {
- start: Date.now(),
+ const part = await updatePart({
+ ...match,
+ state: {
+ status: "running",
+ input: value.input,
+ time: {
+ start: Date.now(),
+ },
},
- }
- Bus.publish(MessageV2.Event.PartUpdated, {
- part: match,
- sessionID: next.sessionID,
- messageID: next.id,
})
+ toolCalls[value.toolCallId] = part as MessageV2.ToolPart
}
break
}
case "tool-result": {
- const match = next.parts.find(
- (p): p is MessageV2.ToolPart => p.type === "tool" && p.id === value.toolCallId,
- )
+ const match = toolCalls[value.toolCallId]
if (match && match.state.status === "running") {
- match.state = {
- status: "completed",
- input: value.input,
- output: value.output.output,
- metadata: value.output.metadata,
- title: value.output.title,
- time: {
- start: match.state.time.start,
- end: Date.now(),
+ await updatePart({
+ ...match,
+ state: {
+ status: "completed",
+ input: value.input,
+ output: value.output.output,
+ metadata: value.output.metadata,
+ title: value.output.title,
+ time: {
+ start: match.state.time.start,
+ end: Date.now(),
+ },
},
- }
- Bus.publish(MessageV2.Event.PartUpdated, {
- part: match,
- sessionID: next.sessionID,
- messageID: next.id,
})
+ delete toolCalls[value.toolCallId]
}
break
}
case "tool-error": {
- const match = next.parts.find(
- (p): p is MessageV2.ToolPart => p.type === "tool" && p.id === value.toolCallId,
- )
+ const match = toolCalls[value.toolCallId]
if (match && match.state.status === "running") {
- match.state = {
- status: "error",
- input: value.input,
- error: (value.error as any).toString(),
- time: {
- start: match.state.time.start,
- end: Date.now(),
+ await updatePart({
+ ...match,
+ state: {
+ status: "error",
+ input: value.input,
+ error: (value.error as any).toString(),
+ time: {
+ start: match.state.time.start,
+ end: Date.now(),
+ },
},
- }
- Bus.publish(MessageV2.Event.PartUpdated, {
- part: match,
- sessionID: next.sessionID,
- messageID: next.id,
})
+ delete toolCalls[value.toolCallId]
}
break
}
@@ -719,53 +776,71 @@ export namespace Session {
throw value.error
case "start-step":
- next.parts.push({
+ await updatePart({
+ id: Identifier.ascending("part"),
+ messageID: assistantMsg.id,
+ sessionID: assistantMsg.sessionID,
type: "step-start",
})
break
case "finish-step":
- const usage = getUsage(model.info, value.usage, value.providerMetadata)
- next.cost += usage.cost
- next.tokens = usage.tokens
- next.parts.push({
+ const usage = getUsage(model, value.usage, value.providerMetadata)
+ assistantMsg.cost += usage.cost
+ assistantMsg.tokens = usage.tokens
+ await updatePart({
+ id: Identifier.ascending("part"),
+ messageID: assistantMsg.id,
+ sessionID: assistantMsg.sessionID,
type: "step-finish",
tokens: usage.tokens,
cost: usage.cost,
})
+ await updateMessage(assistantMsg)
break
case "text-start":
- text = {
+ currentText = {
+ id: Identifier.ascending("part"),
+ messageID: assistantMsg.id,
+ sessionID: assistantMsg.sessionID,
type: "text",
text: "",
+ time: {
+ start: Date.now(),
+ },
}
break
case "text":
- if (text.text === "") next.parts.push(text)
- text.text += value.text
+ if (currentText) {
+ currentText.text += value.text
+ await updatePart(currentText)
+ }
break
case "text-end":
- Bus.publish(MessageV2.Event.PartUpdated, {
- part: text,
- sessionID: next.sessionID,
- messageID: next.id,
- })
+ if (currentText && currentText.text) {
+ currentText.time = {
+ start: Date.now(),
+ end: Date.now(),
+ }
+ await updatePart(currentText)
+ }
+ currentText = undefined
break
case "finish":
- next.time.completed = Date.now()
+ assistantMsg.time.completed = Date.now()
+ await updateMessage(assistantMsg)
break
default:
- l.info("unhandled", {
+ log.info("unhandled", {
...value,
})
continue
}
- await updateMessage(next)
}
} catch (e) {
log.error("", {
@@ -773,7 +848,7 @@ export namespace Session {
})
switch (true) {
case e instanceof DOMException && e.name === "AbortError":
- next.error = new MessageV2.AbortedError(
+ assistantMsg.error = new MessageV2.AbortedError(
{ message: e.message },
{
cause: e,
@@ -781,44 +856,48 @@ export namespace Session {
).toObject()
break
case MessageV2.OutputLengthError.isInstance(e):
- next.error = e
+ assistantMsg.error = e
break
case LoadAPIKeyError.isInstance(e):
- next.error = new Provider.AuthError(
+ assistantMsg.error = new Provider.AuthError(
{
- providerID: input.providerID,
+ providerID: model.id,
message: e.message,
},
{ cause: e },
).toObject()
break
case e instanceof Error:
- next.error = new NamedError.Unknown({ message: e.toString() }, { cause: e }).toObject()
+ assistantMsg.error = new NamedError.Unknown({ message: e.toString() }, { cause: e }).toObject()
break
default:
- next.error = new NamedError.Unknown({ message: JSON.stringify(e) }, { cause: e })
+ assistantMsg.error = new NamedError.Unknown({ message: JSON.stringify(e) }, { cause: e })
}
Bus.publish(Event.Error, {
- sessionID: next.sessionID,
- error: next.error,
+ sessionID: assistantMsg.sessionID,
+ error: assistantMsg.error,
})
}
- for (const part of next.parts) {
+ const p = await parts(assistantMsg.sessionID, assistantMsg.id)
+ for (const part of p) {
if (part.type === "tool" && part.state.status !== "completed") {
- part.state = {
- status: "error",
- error: "Tool execution aborted",
- time: {
- start: Date.now(),
- end: Date.now(),
+ updatePart({
+ ...part,
+ state: {
+ status: "error",
+ error: "Tool execution aborted",
+ time: {
+ start: Date.now(),
+ end: Date.now(),
+ },
+ input: {},
},
- input: {},
- }
+ })
}
}
- next.time.completed = Date.now()
- await updateMessage(next)
- return next
+ assistantMsg.time.completed = Date.now()
+ await updateMessage(assistantMsg)
+ return { info: assistantMsg, parts: p }
}
export async function revert(_input: { sessionID: string; messageID: string; part: number }) {
@@ -867,8 +946,8 @@ export namespace Session {
export async function summarize(input: { sessionID: string; providerID: string; modelID: string }) {
using abort = lock(input.sessionID)
const msgs = await messages(input.sessionID)
- const lastSummary = msgs.findLast((msg) => msg.role === "assistant" && msg.summary === true)?.id
- const filtered = msgs.filter((msg) => !lastSummary || msg.id >= lastSummary)
+ const lastSummary = msgs.findLast((msg) => msg.info.role === "assistant" && msg.info.summary === true)
+ const filtered = msgs.filter((msg) => !lastSummary || msg.info.id >= lastSummary.info.id)
const model = await Provider.getModel(input.providerID, input.modelID)
const app = App.info()
const system = SystemPrompt.summarize(input.providerID)
@@ -876,7 +955,6 @@ export namespace Session {
const next: MessageV2.Info = {
id: Identifier.ascending("message"),
role: "assistant",
- parts: [],
sessionID: input.sessionID,
system,
path: {
@@ -899,7 +977,6 @@ export namespace Session {
}
await updateMessage(next)
- let text: MessageV2.TextPart | undefined
const result = streamText({
abortSignal: abort.signal,
model: model.language,
@@ -921,81 +998,9 @@ export namespace Session {
],
},
],
- onStepFinish: async (step) => {
- const usage = getUsage(model.info, step.usage, step.providerMetadata)
- next.cost += usage.cost
- next.tokens = usage.tokens
- await updateMessage(next)
- if (text) {
- Bus.publish(MessageV2.Event.PartUpdated, {
- part: text,
- messageID: next.id,
- sessionID: next.sessionID,
- })
- }
- text = undefined
- },
- async onFinish(input) {
- const usage = getUsage(model.info, input.usage, input.providerMetadata)
- next.cost += usage.cost
- next.tokens = usage.tokens
- next.time.completed = Date.now()
- await updateMessage(next)
- },
})
- try {
- for await (const value of result.fullStream) {
- switch (value.type) {
- case "text":
- if (!text) {
- text = {
- type: "text",
- text: value.text,
- }
- next.parts.push(text)
- } else text.text += value.text
- await updateMessage(next)
- break
- }
- }
- } catch (e: any) {
- log.error("summarize stream error", {
- error: e,
- })
- switch (true) {
- case e instanceof DOMException && e.name === "AbortError":
- next.error = new MessageV2.AbortedError(
- { message: e.message },
- {
- cause: e,
- },
- ).toObject()
- break
- case MessageV2.OutputLengthError.isInstance(e):
- next.error = e
- break
- case LoadAPIKeyError.isInstance(e):
- next.error = new Provider.AuthError(
- {
- providerID: input.providerID,
- message: e.message,
- },
- { cause: e },
- ).toObject()
- break
- case e instanceof Error:
- next.error = new NamedError.Unknown({ message: e.toString() }, { cause: e }).toObject()
- break
- default:
- next.error = new NamedError.Unknown({ message: JSON.stringify(e) }, { cause: e }).toObject()
- }
- Bus.publish(Event.Error, {
- error: next.error,
- })
- }
- next.time.completed = Date.now()
- await updateMessage(next)
+ return processStream(next, model.info, result)
}
function lock(sessionID: string) {
@@ -1045,14 +1050,23 @@ export namespace Session {
}
}
- export async function initialize(input: { sessionID: string; modelID: string; providerID: string }) {
+ export async function initialize(input: {
+ sessionID: string
+ modelID: string
+ providerID: string
+ messageID: string
+ }) {
const app = App.info()
await Session.chat({
sessionID: input.sessionID,
+ messageID: input.messageID,
providerID: input.providerID,
modelID: input.modelID,
parts: [
{
+ id: Identifier.ascending("part"),
+ sessionID: input.sessionID,
+ messageID: input.messageID,
type: "text",
text: PROMPT_INITIALIZE.replace("${path}", app.path.root),
},
diff --git a/packages/opencode/src/session/message-v2.ts b/packages/opencode/src/session/message-v2.ts
index aadc1a5e2..353fdcec3 100644
--- a/packages/opencode/src/session/message-v2.ts
+++ b/packages/opencode/src/session/message-v2.ts
@@ -4,6 +4,7 @@ import { Provider } from "../provider/provider"
import { NamedError } from "../util/error"
import { Message } from "./message"
import { convertToModelMessages, type ModelMessage, type UIMessage } from "ai"
+import { Identifier } from "../id/id"
export namespace MessageV2 {
export const OutputLengthError = NamedError.create("MessageOutputLengthError", z.object({}))
@@ -72,67 +73,69 @@ export namespace MessageV2 {
ref: "ToolState",
})
- export const TextPart = z
- .object({
- type: z.literal("text"),
- text: z.string(),
- synthetic: z.boolean().optional(),
- })
- .openapi({
- ref: "TextPart",
- })
+ const PartBase = z.object({
+ id: z.string(),
+ sessionID: z.string(),
+ messageID: z.string(),
+ })
+
+ export const TextPart = PartBase.extend({
+ type: z.literal("text"),
+ text: z.string(),
+ synthetic: z.boolean().optional(),
+ time: z
+ .object({
+ start: z.number(),
+ end: z.number().optional(),
+ })
+ .optional(),
+ }).openapi({
+ ref: "TextPart",
+ })
export type TextPart = z.infer<typeof TextPart>
- export const ToolPart = z
- .object({
- type: z.literal("tool"),
- id: z.string(),
- tool: z.string(),
- state: ToolState,
- })
- .openapi({
- ref: "ToolPart",
- })
+ export const ToolPart = PartBase.extend({
+ type: z.literal("tool"),
+ callID: z.string(),
+ tool: z.string(),
+ state: ToolState,
+ }).openapi({
+ ref: "ToolPart",
+ })
export type ToolPart = z.infer<typeof ToolPart>
- export const FilePart = z
- .object({
- type: z.literal("file"),
- mime: z.string(),
- filename: z.string().optional(),
- url: z.string(),
- })
- .openapi({
- ref: "FilePart",
- })
+ export const FilePart = PartBase.extend({
+ type: z.literal("file"),
+ mime: z.string(),
+ filename: z.string().optional(),
+ url: z.string(),
+ }).openapi({
+ ref: "FilePart",
+ })
export type FilePart = z.infer<typeof FilePart>
- export const StepStartPart = z
- .object({
- type: z.literal("step-start"),
- })
- .openapi({
- ref: "StepStartPart",
- })
+ export const StepStartPart = PartBase.extend({
+ type: z.literal("step-start"),
+ }).openapi({
+ ref: "StepStartPart",
+ })
export type StepStartPart = z.infer<typeof StepStartPart>
- export const StepFinishPart = z
- .object({
- type: z.literal("step-finish"),
- cost: z.number(),
- tokens: z.object({
- input: z.number(),
- output: z.number(),
- reasoning: z.number(),
- cache: z.object({
- read: z.number(),
- write: z.number(),
- }),
+ export const StepFinishPart = PartBase.extend({
+ type: z.literal("step-finish"),
+ cost: z.number(),
+ tokens: z.object({
+ input: z.number(),
+ output: z.number(),
+ reasoning: z.number(),
+ cache: z.object({
+ read: z.number(),
+ write: z.number(),
}),
- })
- .openapi({
- ref: "StepFinishPart",
- })
+ }),
+ }).openapi({
+ ref: "StepFinishPart",
+ })
export type StepFinishPart = z.infer<typeof StepFinishPart>
const Base = z.object({
@@ -140,14 +143,8 @@ export namespace MessageV2 {
sessionID: z.string(),
})
- export const UserPart = z.discriminatedUnion("type", [TextPart, FilePart]).openapi({
- ref: "UserMessagePart",
- })
- export type UserPart = z.infer<typeof UserPart>
-
export const User = Base.extend({
role: z.literal("user"),
- parts: z.array(UserPart),
time: z.object({
created: z.number(),
}),
@@ -156,16 +153,15 @@ export namespace MessageV2 {
})
export type User = z.infer<typeof User>
- export const AssistantPart = z
- .discriminatedUnion("type", [TextPart, ToolPart, StepStartPart, StepFinishPart])
+ export const Part = z
+ .discriminatedUnion("type", [TextPart, FilePart, ToolPart, StepStartPart, StepFinishPart])
.openapi({
- ref: "AssistantMessagePart",
+ ref: "Part",
})
- export type AssistantPart = z.infer<typeof AssistantPart>
+ export type Part = z.infer<typeof Part>
export const Assistant = Base.extend({
role: z.literal("assistant"),
- parts: z.array(AssistantPart),
time: z.object({
created: z.number(),
completed: z.number().optional(),
@@ -223,16 +219,14 @@ export namespace MessageV2 {
PartUpdated: Bus.event(
"message.part.updated",
z.object({
- part: AssistantPart,
- sessionID: z.string(),
- messageID: z.string(),
+ part: Part,
}),
),
}
export function fromV1(v1: Message.Info) {
if (v1.role === "assistant") {
- const result: Assistant = {
+ const info: Assistant = {
id: v1.id,
sessionID: v1.metadata.sessionID,
role: "assistant",
@@ -248,109 +242,135 @@ export namespace MessageV2 {
providerID: v1.metadata.assistant!.providerID,
system: v1.metadata.assistant!.system,
error: v1.metadata.error,
- parts: v1.parts.flatMap((part): AssistantPart[] => {
- if (part.type === "text") {
- return [
- {
- type: "text",
- text: part.text,
- },
- ]
- }
- if (part.type === "step-start") {
- return [
- {
- type: "step-start",
- },
- ]
- }
- if (part.type === "tool-invocation") {
- return [
- {
- type: "tool",
- id: part.toolInvocation.toolCallId,
- tool: part.toolInvocation.toolName,
- state: (() => {
- if (part.toolInvocation.state === "partial-call") {
- return {
- status: "pending",
- }
+ }
+ const parts = v1.parts.flatMap((part): Part[] => {
+ const base = {
+ id: Identifier.ascending("part"),
+ messageID: v1.id,
+ sessionID: v1.metadata.sessionID,
+ }
+ if (part.type === "text") {
+ return [
+ {
+ ...base,
+ type: "text",
+ text: part.text,
+ },
+ ]
+ }
+ if (part.type === "step-start") {
+ return [
+ {
+ ...base,
+ type: "step-start",
+ },
+ ]
+ }
+ if (part.type === "tool-invocation") {
+ return [
+ {
+ ...base,
+ type: "tool",
+ callID: part.toolInvocation.toolCallId,
+ tool: part.toolInvocation.toolName,
+ state: (() => {
+ if (part.toolInvocation.state === "partial-call") {
+ return {
+ status: "pending",
}
+ }
- const { title, time, ...metadata } = v1.metadata.tool[part.toolInvocation.toolCallId] ?? {}
- if (part.toolInvocation.state === "call") {
- return {
- status: "running",
- input: part.toolInvocation.args,
- time: {
- start: time?.start,
- },
- }
+ const { title, time, ...metadata } = v1.metadata.tool[part.toolInvocation.toolCallId] ?? {}
+ if (part.toolInvocation.state === "call") {
+ return {
+ status: "running",
+ input: part.toolInvocation.args,
+ time: {
+ start: time?.start,
+ },
}
+ }
- if (part.toolInvocation.state === "result") {
- return {
- status: "completed",
- input: part.toolInvocation.args,
- output: part.toolInvocation.result,
- title,
- time,
- metadata,
- }
+ if (part.toolInvocation.state === "result") {
+ return {
+ status: "completed",
+ input: part.toolInvocation.args,
+ output: part.toolInvocation.result,
+ title,
+ time,
+ metadata,
}
- throw new Error("unknown tool invocation state")
- })(),
- },
- ]
- }
- return []
- }),
+ }
+ throw new Error("unknown tool invocation state")
+ })(),
+ },
+ ]
+ }
+ return []
+ })
+ return {
+ info,
+ parts,
}
- return result
}
if (v1.role === "user") {
- const result: User = {
+ const info: User = {
id: v1.id,
sessionID: v1.metadata.sessionID,
role: "user",
time: {
created: v1.metadata.time.created,
},
- parts: v1.parts.flatMap((part): UserPart[] => {
- if (part.type === "text") {
- return [
- {
- type: "text",
- text: part.text,
- },
- ]
- }
- if (part.type === "file") {
- return [
- {
- type: "file",
- mime: part.mediaType,
- filename: part.filename,
- url: part.url,
- },
- ]
- }
- return []
- }),
}
- return result
+ const parts = v1.parts.flatMap((part): Part[] => {
+ const base = {
+ id: Identifier.ascending("part"),
+ messageID: v1.id,
+ sessionID: v1.metadata.sessionID,
+ }
+ if (part.type === "text") {
+ return [
+ {
+ ...base,
+ type: "text",
+ text: part.text,
+ },
+ ]
+ }
+ if (part.type === "file") {
+ return [
+ {
+ ...base,
+ type: "file",
+ mime: part.mediaType,
+ filename: part.filename,
+ url: part.url,
+ },
+ ]
+ }
+ return []
+ })
+ return { info, parts }
}
+
+ throw new Error("unknown message type")
}
- export function toModelMessage(input: Info[]): ModelMessage[] {
+ export function toModelMessage(
+ input: {
+ info: Info
+ parts: Part[]
+ }[],
+ ): ModelMessage[] {
const result: UIMessage[] = []
for (const msg of input) {
if (msg.parts.length === 0) continue
- if (msg.role === "user") {
+
+ if (msg.info.role === "user") {
result.push({
- id: msg.id,
+ id: msg.info.id,
role: "user",
parts: msg.parts.flatMap((part): UIMessage["parts"] => {
if (part.type === "text")
@@ -374,9 +394,9 @@ export namespace MessageV2 {
})
}
- if (msg.role === "assistant") {
+ if (msg.info.role === "assistant") {
result.push({
- id: msg.id,
+ id: msg.info.id,
role: "assistant",
parts: msg.parts.flatMap((part): UIMessage["parts"] => {
if (part.type === "text")
@@ -398,7 +418,7 @@ export namespace MessageV2 {
{
type: ("tool-" + part.tool) as `tool-${string}`,
state: "output-available",
- toolCallId: part.id,
+ toolCallId: part.callID,
input: part.state.input,
output: part.state.output,
},
@@ -408,7 +428,7 @@ export namespace MessageV2 {
{
type: ("tool-" + part.tool) as `tool-${string}`,
state: "output-error",
- toolCallId: part.id,
+ toolCallId: part.callID,
input: part.state.input,
errorText: part.state.error,
},
diff --git a/packages/opencode/src/storage/storage.ts b/packages/opencode/src/storage/storage.ts
index 001eee0a1..22876ee40 100644
--- a/packages/opencode/src/storage/storage.ts
+++ b/packages/opencode/src/storage/storage.ts
@@ -5,6 +5,7 @@ import path from "path"
import z from "zod"
import fs from "fs/promises"
import { MessageV2 } from "../session/message-v2"
+import { Identifier } from "../id/id"
export namespace Storage {
const log = Log.create({ service: "storage" })
@@ -28,13 +29,49 @@ export namespace Storage {
log.info("migrating to v2 message", { file })
try {
const result = MessageV2.fromV1(content)
- await Bun.write(file, JSON.stringify(result, null, 2))
+ await Bun.write(
+ file,
+ JSON.stringify(
+ {
+ ...result.info,
+ parts: result.parts,
+ },
+ null,
+ 2,
+ ),
+ )
} catch (e) {
await fs.rename(file, file.replace("storage", "broken"))
}
}
} catch {}
},
+ async (dir: string) => {
+ const files = new Bun.Glob("session/message/*/*.json").scanSync({
+ cwd: dir,
+ absolute: true,
+ })
+ for (const file of files) {
+ try {
+ const { parts, ...info } = await Bun.file(file).json()
+ if (!parts) continue
+ for (const part of parts) {
+ const id = Identifier.ascending("part")
+ await Bun.write(
+ [dir, "session", "part", info.sessionID, info.id, id + ".json"].join("/"),
+ JSON.stringify({
+ ...part,
+ id,
+ sessionID: info.sessionID,
+ messageID: info.id,
+ ...(part.type === "tool" ? { callID: part.id } : {}),
+ }),
+ )
+ }
+ await Bun.write(file, JSON.stringify(info, null, 2))
+ } catch (e) {}
+ }
+ },
]
const state = App.state("storage", async () => {
diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts
index 757398337..0d7808a3a 100644
--- a/packages/opencode/src/tool/task.ts
+++ b/packages/opencode/src/tool/task.ts
@@ -4,6 +4,7 @@ import { z } from "zod"
import { Session } from "../session"
import { Bus } from "../bus"
import { MessageV2 } from "../session/message-v2"
+import { Identifier } from "../id/id"
export const TaskTool = Tool.define({
id: "task",
@@ -16,9 +17,10 @@ export const TaskTool = Tool.define({
const session = await Session.create(ctx.sessionID)
const msg = (await Session.getMessage(ctx.sessionID, ctx.messageID)) as MessageV2.Assistant
- function summary(input: MessageV2.Info) {
+ const parts: Record<string, MessageV2.Part> = {}
+ function summary(input: MessageV2.Part[]) {
const result = []
- for (const part of input.parts) {
+ for (const part of input) {
if (part.type === "tool" && part.state.status === "completed") {
result.push(part)
}
@@ -26,12 +28,13 @@ export const TaskTool = Tool.define({
return result
}
- const unsub = Bus.subscribe(MessageV2.Event.Updated, async (evt) => {
- if (evt.properties.info.sessionID !== session.id) return
+ const unsub = Bus.subscribe(MessageV2.Event.PartUpdated, async (evt) => {
+ if (evt.properties.part.sessionID !== session.id) return
+ parts[evt.properties.part.id] = evt.properties.part
ctx.metadata({
title: params.description,
metadata: {
- summary: summary(evt.properties.info),
+ summary: Object.values(parts).sort((a, b) => a.id?.localeCompare(b.id)),
},
})
})
@@ -39,12 +42,17 @@ export const TaskTool = Tool.define({
ctx.abort.addEventListener("abort", () => {
Session.abort(session.id)
})
+ const messageID = Identifier.ascending("message")
const result = await Session.chat({
+ messageID,
sessionID: session.id,
modelID: msg.modelID,
providerID: msg.providerID,
parts: [
{
+ id: Identifier.ascending("part"),
+ messageID,
+ sessionID: session.id,
type: "text",
text: params.prompt,
},
@@ -54,7 +62,7 @@ export const TaskTool = Tool.define({
return {
title: params.description,
metadata: {
- summary: summary(result),
+ summary: summary(result.parts),
},
output: result.parts.findLast((x) => x.type === "text")!.text,
}
diff --git a/packages/tui/internal/app/app.go b/packages/tui/internal/app/app.go
index 03eb361b7..fb8358d8c 100644
--- a/packages/tui/internal/app/app.go
+++ b/packages/tui/internal/app/app.go
@@ -16,11 +16,17 @@ import (
"github.com/sst/opencode/internal/commands"
"github.com/sst/opencode/internal/components/toast"
"github.com/sst/opencode/internal/config"
+ "github.com/sst/opencode/internal/id"
"github.com/sst/opencode/internal/styles"
"github.com/sst/opencode/internal/theme"
"github.com/sst/opencode/internal/util"
)
+type Message struct {
+ Info opencode.MessageUnion
+ Parts []opencode.PartUnion
+}
+
type App struct {
Info opencode.App
Modes []opencode.Mode
@@ -35,7 +41,7 @@ type App struct {
Provider *opencode.Provider
Model *opencode.Model
Session *opencode.Session
- Messages []opencode.MessageUnion
+ Messages []Message
Commands commands.CommandRegistry
InitialModel *string
InitialPrompt *string
@@ -158,7 +164,7 @@ func New(
ModeIndex: modeIndex,
Mode: mode,
Session: &opencode.Session{},
- Messages: []opencode.MessageUnion{},
+ Messages: []Message{},
Commands: commands.LoadFromConfig(configInfo),
InitialModel: initialModel,
InitialPrompt: initialPrompt,
@@ -351,7 +357,7 @@ func (a *App) IsBusy() bool {
}
lastMessage := a.Messages[len(a.Messages)-1]
- if casted, ok := lastMessage.(opencode.AssistantMessage); ok {
+ if casted, ok := lastMessage.Info.(opencode.AssistantMessage); ok {
return casted.Time.Completed == 0
}
return false
@@ -452,54 +458,67 @@ func (a *App) SendChatMessage(
cmds = append(cmds, util.CmdHandler(SessionSelectedMsg(session)))
}
- optimisticParts := []opencode.UserMessagePart{{
- Type: opencode.UserMessagePartTypeText,
- Text: text,
+ message := opencode.UserMessage{
+ ID: id.Ascending(id.Message),
+ SessionID: a.Session.ID,
+ Role: opencode.UserMessageRoleUser,
+ Time: opencode.UserMessageTime{
+ Created: float64(time.Now().UnixMilli()),
+ },
+ }
+
+ parts := []opencode.PartUnion{opencode.TextPart{
+ ID: id.Ascending(id.Part),
+ MessageID: message.ID,
+ SessionID: a.Session.ID,
+ Type: opencode.TextPartTypeText,
+ Text: text,
}}
if len(attachments) > 0 {
for _, attachment := range attachments {
- optimisticParts = append(optimisticParts, opencode.UserMessagePart{
- Type: opencode.UserMessagePartTypeFile,
- Filename: attachment.Filename.Value,
- Mime: attachment.Mime.Value,
- URL: attachment.URL.Value,
+ parts = append(parts, opencode.FilePart{
+ ID: id.Ascending(id.Part),
+ MessageID: message.ID,
+ SessionID: a.Session.ID,
+ Type: opencode.FilePartTypeFile,
+ Filename: attachment.Filename.Value,
+ Mime: attachment.Mime.Value,
+ URL: attachment.URL.Value,
})
}
}
- optimisticMessage := opencode.UserMessage{
- ID: fmt.Sprintf("optimistic-%d", time.Now().UnixNano()),
- Role: opencode.UserMessageRoleUser,
- Parts: optimisticParts,
- SessionID: a.Session.ID,
- Time: opencode.UserMessageTime{
- Created: float64(time.Now().Unix()),
- },
- }
-
- a.Messages = append(a.Messages, optimisticMessage)
- cmds = append(cmds, util.CmdHandler(OptimisticMessageAddedMsg{Message: optimisticMessage}))
+ a.Messages = append(a.Messages, Message{Info: message, Parts: parts})
+ cmds = append(cmds, util.CmdHandler(OptimisticMessageAddedMsg{Message: message}))
cmds = append(cmds, func() tea.Msg {
- parts := []opencode.UserMessagePartUnionParam{
- opencode.TextPartParam{
- Type: opencode.F(opencode.TextPartTypeText),
- Text: opencode.F(text),
- },
- }
- if len(attachments) > 0 {
- for _, attachment := range attachments {
- parts = append(parts, opencode.FilePartParam{
- Mime: attachment.Mime,
- Type: attachment.Type,
- URL: attachment.URL,
- Filename: attachment.Filename,
+ partsParam := []opencode.SessionChatParamsPartUnion{}
+ for _, part := range parts {
+ switch casted := part.(type) {
+ case opencode.TextPart:
+ partsParam = append(partsParam, opencode.TextPartParam{
+ ID: opencode.F(casted.ID),
+ MessageID: opencode.F(casted.MessageID),
+ SessionID: opencode.F(casted.SessionID),
+ Type: opencode.F(casted.Type),
+ Text: opencode.F(casted.Text),
+ })
+ case opencode.FilePart:
+ partsParam = append(partsParam, opencode.FilePartParam{
+ ID: opencode.F(casted.ID),
+ Mime: opencode.F(casted.Mime),
+ MessageID: opencode.F(casted.MessageID),
+ SessionID: opencode.F(casted.SessionID),
+ Type: opencode.F(casted.Type),
+ URL: opencode.F(casted.URL),
+ Filename: opencode.F(casted.Filename),
})
}
}
_, err := a.Client.Session.Chat(ctx, a.Session.ID, opencode.SessionChatParams{
- Parts: opencode.F(parts),
+ Parts: opencode.F(partsParam),
+ MessageID: opencode.F(message.ID),
ProviderID: opencode.F(a.Provider.ID),
ModelID: opencode.F(a.Model.ID),
Mode: opencode.F(a.Mode.Name),
@@ -557,15 +576,25 @@ func (a *App) DeleteSession(ctx context.Context, sessionID string) error {
return nil
}
-func (a *App) ListMessages(ctx context.Context, sessionId string) ([]opencode.Message, error) {
+func (a *App) ListMessages(ctx context.Context, sessionId string) ([]Message, error) {
response, err := a.Client.Session.Messages(ctx, sessionId)
if err != nil {
return nil, err
}
if response == nil {
- return []opencode.Message{}, nil
+ return []Message{}, nil
+ }
+ messages := []Message{}
+ for _, message := range *response {
+ msg := Message{
+ Info: message.Info.AsUnion(),
+ Parts: []opencode.PartUnion{},
+ }
+ for _, part := range message.Parts {
+ msg.Parts = append(msg.Parts, part.AsUnion())
+ }
+ messages = append(messages, msg)
}
- messages := *response
return messages, nil
}
diff --git a/packages/tui/internal/components/chat/messages.go b/packages/tui/internal/components/chat/messages.go
index 7ecd9b21f..191432cc3 100644
--- a/packages/tui/internal/components/chat/messages.go
+++ b/packages/tui/internal/components/chat/messages.go
@@ -106,6 +106,13 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.viewport.GotoBottom()
}
}
+ case opencode.EventListResponseEventMessagePartUpdated:
+ if msg.Properties.Part.SessionID == m.app.Session.ID {
+ m.renderView(m.width)
+ if m.tail {
+ m.viewport.GotoBottom()
+ }
+ }
}
viewport, cmd := m.viewport.Update(msg)
@@ -131,16 +138,16 @@ func (m *messagesComponent) renderView(width int) {
var content string
var cached bool
- switch casted := message.(type) {
+ switch casted := message.Info.(type) {
case opencode.UserMessage:
userLoop:
- for partIndex, part := range casted.Parts {
- switch part := part.AsUnion().(type) {
+ for partIndex, part := range message.Parts {
+ switch part := part.(type) {
case opencode.TextPart:
- remainingParts := casted.Parts[partIndex+1:]
+ remainingParts := message.Parts[partIndex+1:]
fileParts := make([]opencode.FilePart, 0)
for _, part := range remainingParts {
- switch part := part.AsUnion().(type) {
+ switch part := part.(type) {
case opencode.FilePart:
fileParts = append(fileParts, part)
}
@@ -181,7 +188,7 @@ func (m *messagesComponent) renderView(width int) {
if !cached {
content = renderText(
m.app,
- message,
+ message.Info,
part.Text,
m.app.Info.User,
m.showToolDetails,
@@ -202,12 +209,12 @@ func (m *messagesComponent) renderView(width int) {
case opencode.AssistantMessage:
hasTextPart := false
- for partIndex, p := range casted.Parts {
- switch part := p.AsUnion().(type) {
+ for partIndex, p := range message.Parts {
+ switch part := p.(type) {
case opencode.TextPart:
hasTextPart = true
finished := casted.Time.Completed > 0
- remainingParts := casted.Parts[partIndex+1:]
+ remainingParts := message.Parts[partIndex+1:]
toolCallParts := make([]opencode.ToolPart, 0)
// sometimes tool calls happen without an assistant message
@@ -222,7 +229,7 @@ func (m *messagesComponent) renderView(width int) {
if !remaining {
break
}
- switch part := part.AsUnion().(type) {
+ switch part := part.(type) {
case opencode.TextPart:
// we only want tool calls associated with the current text part.
// if we hit another text part, we're done.
@@ -238,13 +245,13 @@ func (m *messagesComponent) renderView(width int) {
}
if finished {
- key := m.cache.GenerateKey(casted.ID, p.Text, width, m.showToolDetails, m.selectedPart == m.partCount)
+ key := m.cache.GenerateKey(casted.ID, part.Text, width, m.showToolDetails, m.selectedPart == m.partCount)
content, cached = m.cache.Get(key)
if !cached {
content = renderText(
m.app,
- message,
- p.Text,
+ message.Info,
+ part.Text,
casted.ModelID,
m.showToolDetails,
m.partCount == m.selectedPart,
@@ -257,8 +264,8 @@ func (m *messagesComponent) renderView(width int) {
} else {
content = renderText(
m.app,
- message,
- p.Text,
+ message.Info,
+ part.Text,
casted.ModelID,
m.showToolDetails,
m.partCount == m.selectedPart,
@@ -268,7 +275,7 @@ func (m *messagesComponent) renderView(width int) {
)
}
if content != "" {
- m = m.updateSelected(content, p.Text)
+ m = m.updateSelected(content, part.Text)
blocks = append(blocks, content)
}
case opencode.ToolPart:
@@ -314,7 +321,7 @@ func (m *messagesComponent) renderView(width int) {
}
error := ""
- if assistant, ok := message.(opencode.AssistantMessage); ok {
+ if assistant, ok := message.Info.(opencode.AssistantMessage); ok {
switch err := assistant.Error.AsUnion().(type) {
case nil:
case opencode.AssistantMessageErrorMessageOutputLengthError:
@@ -386,7 +393,7 @@ func (m *messagesComponent) header(width int) string {
contextWindow := m.app.Model.Limit.Context
for _, message := range m.app.Messages {
- if assistant, ok := message.(opencode.AssistantMessage); ok {
+ if assistant, ok := message.Info.(opencode.AssistantMessage); ok {
cost += assistant.Cost
usage := assistant.Tokens
if usage.Output > 0 {
diff --git a/packages/tui/internal/id/id.go b/packages/tui/internal/id/id.go
new file mode 100644
index 000000000..0490b8f20
--- /dev/null
+++ b/packages/tui/internal/id/id.go
@@ -0,0 +1,96 @@
+package id
+
+import (
+ "crypto/rand"
+ "encoding/hex"
+ "fmt"
+ "strings"
+ "sync"
+ "time"
+)
+
+const (
+ PrefixSession = "ses"
+ PrefixMessage = "msg"
+ PrefixUser = "usr"
+ PrefixPart = "prt"
+)
+
+const length = 26
+
+var (
+ lastTimestamp int64
+ counter int64
+ mu sync.Mutex
+)
+
+type Prefix string
+
+const (
+ Session Prefix = PrefixSession
+ Message Prefix = PrefixMessage
+ User Prefix = PrefixUser
+ Part Prefix = PrefixPart
+)
+
+func ValidatePrefix(id string, prefix Prefix) bool {
+ return strings.HasPrefix(id, string(prefix))
+}
+
+func Ascending(prefix Prefix, given ...string) string {
+ return generateID(prefix, false, given...)
+}
+
+func Descending(prefix Prefix, given ...string) string {
+ return generateID(prefix, true, given...)
+}
+
+func generateID(prefix Prefix, descending bool, given ...string) string {
+ if len(given) > 0 && given[0] != "" {
+ if !strings.HasPrefix(given[0], string(prefix)) {
+ panic(fmt.Sprintf("ID %s does not start with %s", given[0], string(prefix)))
+ }
+ return given[0]
+ }
+
+ return generateNewID(prefix, descending)
+}
+
+func randomBase62(length int) string {
+ const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+ result := make([]byte, length)
+ bytes := make([]byte, length)
+ rand.Read(bytes)
+
+ for i := 0; i < length; i++ {
+ result[i] = chars[bytes[i]%62]
+ }
+
+ return string(result)
+}
+
+func generateNewID(prefix Prefix, descending bool) string {
+ mu.Lock()
+ defer mu.Unlock()
+
+ currentTimestamp := time.Now().UnixMilli()
+
+ if currentTimestamp != lastTimestamp {
+ lastTimestamp = currentTimestamp
+ counter = 0
+ }
+ counter++
+
+ now := uint64(currentTimestamp)*0x1000 + uint64(counter)
+
+ if descending {
+ now = ^now
+ }
+
+ timeBytes := make([]byte, 6)
+ for i := 0; i < 6; i++ {
+ timeBytes[i] = byte((now >> (40 - 8*i)) & 0xff)
+ }
+
+ return string(prefix) + "_" + hex.EncodeToString(timeBytes) + randomBase62(length-12)
+} \ No newline at end of file
diff --git a/packages/tui/internal/tui/tui.go b/packages/tui/internal/tui/tui.go
index 389dd64f1..0ebdd35ab 100644
--- a/packages/tui/internal/tui/tui.go
+++ b/packages/tui/internal/tui/tui.go
@@ -5,6 +5,7 @@ import (
"log/slog"
"os"
"os/exec"
+ "slices"
"strings"
"time"
@@ -364,55 +365,76 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case opencode.EventListResponseEventSessionDeleted:
if a.app.Session != nil && msg.Properties.Info.ID == a.app.Session.ID {
a.app.Session = &opencode.Session{}
- a.app.Messages = []opencode.MessageUnion{}
+ a.app.Messages = []app.Message{}
}
return a, toast.NewSuccessToast("Session deleted successfully")
case opencode.EventListResponseEventSessionUpdated:
if msg.Properties.Info.ID == a.app.Session.ID {
a.app.Session = &msg.Properties.Info
}
- case opencode.EventListResponseEventMessageUpdated:
- if msg.Properties.Info.SessionID == a.app.Session.ID {
- exists := false
- optimisticReplaced := false
-
- // First check if this is replacing an optimistic message
- if msg.Properties.Info.Role == opencode.MessageRoleUser {
- // Look for optimistic messages to replace
- for i, m := range a.app.Messages {
- switch m := m.(type) {
- case opencode.UserMessage:
- if strings.HasPrefix(m.ID, "optimistic-") && m.Role == opencode.UserMessageRoleUser {
- // Replace the optimistic message with the real one
- a.app.Messages[i] = msg.Properties.Info.AsUnion()
- exists = true
- optimisticReplaced = true
- break
- }
+ case opencode.EventListResponseEventMessagePartUpdated:
+ slog.Info("message part updated", "message", msg.Properties.Part.MessageID, "part", msg.Properties.Part.ID)
+ if msg.Properties.Part.SessionID == a.app.Session.ID {
+ messageIndex := slices.IndexFunc(a.app.Messages, func(m app.Message) bool {
+ switch casted := m.Info.(type) {
+ case opencode.UserMessage:
+ return casted.ID == msg.Properties.Part.MessageID
+ case opencode.AssistantMessage:
+ return casted.ID == msg.Properties.Part.MessageID
+ }
+ return false
+ })
+ if messageIndex > -1 {
+ message := a.app.Messages[messageIndex]
+ partIndex := slices.IndexFunc(message.Parts, func(p opencode.PartUnion) bool {
+ switch casted := p.(type) {
+ case opencode.TextPart:
+ return casted.ID == msg.Properties.Part.ID
+ case opencode.FilePart:
+ return casted.ID == msg.Properties.Part.ID
+ case opencode.ToolPart:
+ return casted.ID == msg.Properties.Part.ID
+ case opencode.StepStartPart:
+ return casted.ID == msg.Properties.Part.ID
+ case opencode.StepFinishPart:
+ return casted.ID == msg.Properties.Part.ID
}
+ return false
+ })
+ if partIndex > -1 {
+ message.Parts[partIndex] = msg.Properties.Part.AsUnion()
}
+ if partIndex == -1 {
+ message.Parts = append(message.Parts, msg.Properties.Part.AsUnion())
+ }
+ a.app.Messages[messageIndex] = message
}
-
- // If not replacing optimistic, check for existing message with same ID
- if !optimisticReplaced {
- for i, m := range a.app.Messages {
- var id string
- switch m := m.(type) {
- case opencode.UserMessage:
- id = m.ID
- case opencode.AssistantMessage:
- id = m.ID
- }
- if id == msg.Properties.Info.ID {
- a.app.Messages[i] = msg.Properties.Info.AsUnion()
- exists = true
- break
- }
+ }
+ case opencode.EventListResponseEventMessageUpdated:
+ if msg.Properties.Info.SessionID == a.app.Session.ID {
+ matchIndex := slices.IndexFunc(a.app.Messages, func(m app.Message) bool {
+ switch casted := m.Info.(type) {
+ case opencode.UserMessage:
+ return casted.ID == msg.Properties.Info.ID
+ case opencode.AssistantMessage:
+ return casted.ID == msg.Properties.Info.ID
+ }
+ return false
+ })
+
+ if matchIndex > -1 {
+ match := a.app.Messages[matchIndex]
+ a.app.Messages[matchIndex] = app.Message{
+ Info: msg.Properties.Info.AsUnion(),
+ Parts: match.Parts,
}
}
- if !exists {
- a.app.Messages = append(a.app.Messages, msg.Properties.Info.AsUnion())
+ if matchIndex == -1 {
+ a.app.Messages = append(a.app.Messages, app.Message{
+ Info: msg.Properties.Info.AsUnion(),
+ Parts: []opencode.PartUnion{},
+ })
}
}
case opencode.EventListResponseEventSessionError:
@@ -473,10 +495,7 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return a, toast.NewErrorToast("Failed to open session")
}
a.app.Session = msg
- a.app.Messages = make([]opencode.MessageUnion, 0)
- for _, message := range messages {
- a.app.Messages = append(a.app.Messages, message.AsUnion())
- }
+ a.app.Messages = messages
return a, util.CmdHandler(app.SessionLoadedMsg{})
case app.ModelSelectedMsg:
a.app.Provider = &msg.Provider
@@ -837,7 +856,7 @@ func (a appModel) executeCommand(command commands.Command) (tea.Model, tea.Cmd)
return a, nil
}
a.app.Session = &opencode.Session{}
- a.app.Messages = []opencode.MessageUnion{}
+ a.app.Messages = []app.Message{}
cmds = append(cmds, util.CmdHandler(app.SessionClearedMsg{}))
case commands.SessionListCommand:
sessionDialog := dialog.NewSessionDialog(a.app)
diff --git a/packages/tui/sdk/.stats.yml b/packages/tui/sdk/.stats.yml
index 4449c4d95..61d2fa3b7 100644
--- a/packages/tui/sdk/.stats.yml
+++ b/packages/tui/sdk/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 22
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-eb25bb3673f94d0e98a7036e2a2b0ed7ad63d1598665f2d5e091ec0835273798.yml
-openapi_spec_hash: 62f6a8a06aaa4f4ae13e85d56652724f
-config_hash: 589ec6a935a43a3c49a325ece86cbda2
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-352994eb17f76d9472b0f0176efacf77a200a6fab2db28d1cfcd29451b211d7a.yml
+openapi_spec_hash: f01cd3de8c7cf0c9fd513896e81986de
+config_hash: 3695cfc829cfaae14490850b4a1ed282
diff --git a/packages/tui/sdk/README.md b/packages/tui/sdk/README.md
index 2b5782347..38840b28a 100644
--- a/packages/tui/sdk/README.md
+++ b/packages/tui/sdk/README.md
@@ -49,11 +49,14 @@ import (
func main() {
client := opencode.NewClient()
- events, err := client.Event.List(context.TODO())
+ stream := client.Event.ListStreaming(context.TODO())
+ for stream.Next() {
+ fmt.Printf("%+v\n", stream.Current())
+ }
+ err := stream.Err()
if err != nil {
panic(err.Error())
}
- fmt.Printf("%+v\n", events)
}
```
@@ -171,14 +174,14 @@ When the API returns a non-success status code, we return an error with type
To handle errors, we recommend that you use the `errors.As` pattern:
```go
-_, err := client.Event.List(context.TODO())
-if err != nil {
+stream := client.Event.ListStreaming(context.TODO())
+if stream.Err() != nil {
var apierr *opencode.Error
- if errors.As(err, &apierr) {
+ if errors.As(stream.Err(), &apierr) {
println(string(apierr.DumpRequest(true))) // Prints the serialized HTTP request
println(string(apierr.DumpResponse(true))) // Prints the serialized HTTP response
}
- panic(err.Error()) // GET "/event": 400 Bad Request { ... }
+ panic(stream.Err().Error()) // GET "/event": 400 Bad Request { ... }
}
```
@@ -196,7 +199,7 @@ To set a per-retry timeout, use `option.WithRequestTimeout()`.
// This sets the timeout for the request, including all the retries.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()
-client.Event.List(
+client.Event.ListStreaming(
ctx,
// This sets the per-retry timeout
option.WithRequestTimeout(20*time.Second),
@@ -231,7 +234,7 @@ client := opencode.NewClient(
)
// Override per-request:
-client.Event.List(context.TODO(), option.WithMaxRetries(5))
+client.Event.ListStreaming(context.TODO(), option.WithMaxRetries(5))
```
### Accessing raw response data (e.g. response headers)
@@ -242,8 +245,8 @@ you need to examine response headers, status codes, or other details.
```go
// Create a variable to store the HTTP response
var response *http.Response
-events, err := client.Event.List(context.TODO(), option.WithResponseInto(&response))
-if err != nil {
+stream := client.Event.ListStreaming(context.TODO(), option.WithResponseInto(&response))
+if stream.Err() != nil {
// handle error
}
fmt.Printf("%+v\n", events)
diff --git a/packages/tui/sdk/api.md b/packages/tui/sdk/api.md
index 15bdcfa93..a48e6d7f1 100644
--- a/packages/tui/sdk/api.md
+++ b/packages/tui/sdk/api.md
@@ -77,15 +77,15 @@ Params Types:
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FilePartParam">FilePartParam</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TextPartParam">TextPartParam</a>
-- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#UserMessagePartUnionParam">UserMessagePartUnionParam</a>
Response Types:
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AssistantMessage">AssistantMessage</a>
-- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AssistantMessagePart">AssistantMessagePart</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FilePart">FilePart</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Message">Message</a>
+- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Part">Part</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>
+- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#StepFinishPart">StepFinishPart</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#StepStartPart">StepStartPart</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#TextPart">TextPart</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ToolPart">ToolPart</a>
@@ -94,7 +94,7 @@ Response Types:
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ToolStatePending">ToolStatePending</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ToolStateRunning">ToolStateRunning</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#UserMessage">UserMessage</a>
-- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#UserMessagePart">UserMessagePart</a>
+- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessagesResponse">SessionMessagesResponse</a>
Methods:
@@ -104,7 +104,7 @@ Methods:
- <code title="post /session/{id}/abort">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Abort">Abort</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="post /session/{id}/message">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Chat">Chat</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionChatParams">SessionChatParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AssistantMessage">AssistantMessage</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="post /session/{id}/init">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Init">Init</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionInitParams">SessionInitParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
-- <code title="get /session/{id}/message">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Messages">Messages</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Message">Message</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="get /session/{id}/message">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Messages">Messages</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessagesResponse">SessionMessagesResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="post /session/{id}/share">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Share">Share</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="post /session/{id}/summarize">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Summarize">Summarize</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionSummarizeParams">SessionSummarizeParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="delete /session/{id}/share">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Unshare">Unshare</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
diff --git a/packages/tui/sdk/client_test.go b/packages/tui/sdk/client_test.go
index e75d64925..62222a465 100644
--- a/packages/tui/sdk/client_test.go
+++ b/packages/tui/sdk/client_test.go
@@ -38,7 +38,7 @@ func TestUserAgentHeader(t *testing.T) {
},
}),
)
- client.Event.List(context.Background())
+ client.Event.ListStreaming(context.Background())
if userAgent != fmt.Sprintf("Opencode/Go %s", internal.PackageVersion) {
t.Errorf("Expected User-Agent to be correct, but got: %#v", userAgent)
}
@@ -61,7 +61,11 @@ func TestRetryAfter(t *testing.T) {
},
}),
)
- _, err := client.Event.List(context.Background())
+ stream := client.Event.ListStreaming(context.Background())
+ for stream.Next() {
+ // ...
+ }
+ err := stream.Err()
if err == nil {
t.Error("Expected there to be a cancel error")
}
@@ -95,7 +99,11 @@ func TestDeleteRetryCountHeader(t *testing.T) {
}),
option.WithHeaderDel("X-Stainless-Retry-Count"),
)
- _, err := client.Event.List(context.Background())
+ stream := client.Event.ListStreaming(context.Background())
+ for stream.Next() {
+ // ...
+ }
+ err := stream.Err()
if err == nil {
t.Error("Expected there to be a cancel error")
}
@@ -124,7 +132,11 @@ func TestOverwriteRetryCountHeader(t *testing.T) {
}),
option.WithHeader("X-Stainless-Retry-Count", "42"),
)
- _, err := client.Event.List(context.Background())
+ stream := client.Event.ListStreaming(context.Background())
+ for stream.Next() {
+ // ...
+ }
+ err := stream.Err()
if err == nil {
t.Error("Expected there to be a cancel error")
}
@@ -152,7 +164,11 @@ func TestRetryAfterMs(t *testing.T) {
},
}),
)
- _, err := client.Event.List(context.Background())
+ stream := client.Event.ListStreaming(context.Background())
+ for stream.Next() {
+ // ...
+ }
+ err := stream.Err()
if err == nil {
t.Error("Expected there to be a cancel error")
}
@@ -174,7 +190,11 @@ func TestContextCancel(t *testing.T) {
)
cancelCtx, cancel := context.WithCancel(context.Background())
cancel()
- _, err := client.Event.List(cancelCtx)
+ stream := client.Event.ListStreaming(cancelCtx)
+ for stream.Next() {
+ // ...
+ }
+ err := stream.Err()
if err == nil {
t.Error("Expected there to be a cancel error")
}
@@ -193,7 +213,11 @@ func TestContextCancelDelay(t *testing.T) {
)
cancelCtx, cancel := context.WithTimeout(context.Background(), 2*time.Millisecond)
defer cancel()
- _, err := client.Event.List(cancelCtx)
+ stream := client.Event.ListStreaming(cancelCtx)
+ for stream.Next() {
+ // ...
+ }
+ err := stream.Err()
if err == nil {
t.Error("expected there to be a cancel error")
}
@@ -218,7 +242,11 @@ func TestContextDeadline(t *testing.T) {
},
}),
)
- _, err := client.Event.List(deadlineCtx)
+ stream := client.Event.ListStreaming(deadlineCtx)
+ for stream.Next() {
+ // ...
+ }
+ err := stream.Err()
if err == nil {
t.Error("expected there to be a deadline error")
}
diff --git a/packages/tui/sdk/event.go b/packages/tui/sdk/event.go
index 8bbf636c3..9002d2aac 100644
--- a/packages/tui/sdk/event.go
+++ b/packages/tui/sdk/event.go
@@ -610,18 +610,14 @@ func (r eventListResponseEventMessagePartUpdatedJSON) RawJSON() string {
func (r EventListResponseEventMessagePartUpdated) implementsEventListResponse() {}
type EventListResponseEventMessagePartUpdatedProperties struct {
- MessageID string `json:"messageID,required"`
- Part AssistantMessagePart `json:"part,required"`
- SessionID string `json:"sessionID,required"`
- JSON eventListResponseEventMessagePartUpdatedPropertiesJSON `json:"-"`
+ Part Part `json:"part,required"`
+ JSON eventListResponseEventMessagePartUpdatedPropertiesJSON `json:"-"`
}
// eventListResponseEventMessagePartUpdatedPropertiesJSON contains the JSON
// metadata for the struct [EventListResponseEventMessagePartUpdatedProperties]
type eventListResponseEventMessagePartUpdatedPropertiesJSON struct {
- MessageID apijson.Field
Part apijson.Field
- SessionID apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
diff --git a/packages/tui/sdk/scripts/lint b/packages/tui/sdk/scripts/lint
index 9f37abf28..7e03a7beb 100755
--- a/packages/tui/sdk/scripts/lint
+++ b/packages/tui/sdk/scripts/lint
@@ -5,7 +5,7 @@ set -e
cd "$(dirname "$0")/.."
echo "==> Running Go build"
-go build .
+go build ./...
echo "==> Checking tests compile"
-go test -run=^$ .
+go test -run=^$ ./...
diff --git a/packages/tui/sdk/session.go b/packages/tui/sdk/session.go
index 6321d1ff6..e76ab7f90 100644
--- a/packages/tui/sdk/session.go
+++ b/packages/tui/sdk/session.go
@@ -101,7 +101,7 @@ func (r *SessionService) Init(ctx context.Context, id string, body SessionInitPa
}
// List messages for a session
-func (r *SessionService) Messages(ctx context.Context, id string, opts ...option.RequestOption) (res *[]Message, err error) {
+func (r *SessionService) Messages(ctx context.Context, id string, opts ...option.RequestOption) (res *[]SessionMessagesResponse, err error) {
opts = append(r.Options[:], opts...)
if id == "" {
err = errors.New("missing required id parameter")
@@ -152,7 +152,6 @@ type AssistantMessage struct {
ID string `json:"id,required"`
Cost float64 `json:"cost,required"`
ModelID string `json:"modelID,required"`
- Parts []AssistantMessagePart `json:"parts,required"`
Path AssistantMessagePath `json:"path,required"`
ProviderID string `json:"providerID,required"`
Role AssistantMessageRole `json:"role,required"`
@@ -171,7 +170,6 @@ type assistantMessageJSON struct {
ID apijson.Field
Cost apijson.Field
ModelID apijson.Field
- Parts apijson.Field
Path apijson.Field
ProviderID apijson.Field
Role apijson.Field
@@ -435,211 +433,23 @@ func (r AssistantMessageErrorName) IsKnown() bool {
return false
}
-type AssistantMessagePart struct {
- Type AssistantMessagePartType `json:"type,required"`
- ID string `json:"id"`
- Cost float64 `json:"cost"`
- // This field can have the runtime type of [ToolPartState].
- State interface{} `json:"state"`
- Synthetic bool `json:"synthetic"`
- Text string `json:"text"`
- // This field can have the runtime type of
- // [AssistantMessagePartStepFinishPartTokens].
- Tokens interface{} `json:"tokens"`
- Tool string `json:"tool"`
- JSON assistantMessagePartJSON `json:"-"`
- union AssistantMessagePartUnion
-}
-
-// assistantMessagePartJSON contains the JSON metadata for the struct
-// [AssistantMessagePart]
-type assistantMessagePartJSON struct {
- Type apijson.Field
- ID apijson.Field
- Cost apijson.Field
- State apijson.Field
- Synthetic apijson.Field
- Text apijson.Field
- Tokens apijson.Field
- Tool apijson.Field
- raw string
- ExtraFields map[string]apijson.Field
-}
-
-func (r assistantMessagePartJSON) RawJSON() string {
- return r.raw
-}
-
-func (r *AssistantMessagePart) UnmarshalJSON(data []byte) (err error) {
- *r = AssistantMessagePart{}
- err = apijson.UnmarshalRoot(data, &r.union)
- if err != nil {
- return err
- }
- return apijson.Port(r.union, &r)
-}
-
-// AsUnion returns a [AssistantMessagePartUnion] interface which you can cast to
-// the specific types for more type safety.
-//
-// Possible runtime types of the union are [TextPart], [ToolPart], [StepStartPart],
-// [AssistantMessagePartStepFinishPart].
-func (r AssistantMessagePart) AsUnion() AssistantMessagePartUnion {
- return r.union
-}
-
-// Union satisfied by [TextPart], [ToolPart], [StepStartPart] or
-// [AssistantMessagePartStepFinishPart].
-type AssistantMessagePartUnion interface {
- implementsAssistantMessagePart()
-}
-
-func init() {
- apijson.RegisterUnion(
- reflect.TypeOf((*AssistantMessagePartUnion)(nil)).Elem(),
- "type",
- apijson.UnionVariant{
- TypeFilter: gjson.JSON,
- Type: reflect.TypeOf(TextPart{}),
- DiscriminatorValue: "text",
- },
- apijson.UnionVariant{
- TypeFilter: gjson.JSON,
- Type: reflect.TypeOf(ToolPart{}),
- DiscriminatorValue: "tool",
- },
- apijson.UnionVariant{
- TypeFilter: gjson.JSON,
- Type: reflect.TypeOf(StepStartPart{}),
- DiscriminatorValue: "step-start",
- },
- apijson.UnionVariant{
- TypeFilter: gjson.JSON,
- Type: reflect.TypeOf(AssistantMessagePartStepFinishPart{}),
- DiscriminatorValue: "step-finish",
- },
- )
-}
-
-type AssistantMessagePartStepFinishPart struct {
- Cost float64 `json:"cost,required"`
- Tokens AssistantMessagePartStepFinishPartTokens `json:"tokens,required"`
- Type AssistantMessagePartStepFinishPartType `json:"type,required"`
- JSON assistantMessagePartStepFinishPartJSON `json:"-"`
-}
-
-// assistantMessagePartStepFinishPartJSON contains the JSON metadata for the struct
-// [AssistantMessagePartStepFinishPart]
-type assistantMessagePartStepFinishPartJSON struct {
- Cost apijson.Field
- Tokens apijson.Field
- Type apijson.Field
- raw string
- ExtraFields map[string]apijson.Field
-}
-
-func (r *AssistantMessagePartStepFinishPart) UnmarshalJSON(data []byte) (err error) {
- return apijson.UnmarshalRoot(data, r)
-}
-
-func (r assistantMessagePartStepFinishPartJSON) RawJSON() string {
- return r.raw
-}
-
-func (r AssistantMessagePartStepFinishPart) implementsAssistantMessagePart() {}
-
-type AssistantMessagePartStepFinishPartTokens struct {
- Cache AssistantMessagePartStepFinishPartTokensCache `json:"cache,required"`
- Input float64 `json:"input,required"`
- Output float64 `json:"output,required"`
- Reasoning float64 `json:"reasoning,required"`
- JSON assistantMessagePartStepFinishPartTokensJSON `json:"-"`
-}
-
-// assistantMessagePartStepFinishPartTokensJSON contains the JSON metadata for the
-// struct [AssistantMessagePartStepFinishPartTokens]
-type assistantMessagePartStepFinishPartTokensJSON struct {
- Cache apijson.Field
- Input apijson.Field
- Output apijson.Field
- Reasoning apijson.Field
- raw string
- ExtraFields map[string]apijson.Field
-}
-
-func (r *AssistantMessagePartStepFinishPartTokens) UnmarshalJSON(data []byte) (err error) {
- return apijson.UnmarshalRoot(data, r)
-}
-
-func (r assistantMessagePartStepFinishPartTokensJSON) RawJSON() string {
- return r.raw
-}
-
-type AssistantMessagePartStepFinishPartTokensCache struct {
- Read float64 `json:"read,required"`
- Write float64 `json:"write,required"`
- JSON assistantMessagePartStepFinishPartTokensCacheJSON `json:"-"`
-}
-
-// assistantMessagePartStepFinishPartTokensCacheJSON contains the JSON metadata for
-// the struct [AssistantMessagePartStepFinishPartTokensCache]
-type assistantMessagePartStepFinishPartTokensCacheJSON struct {
- Read apijson.Field
- Write apijson.Field
- raw string
- ExtraFields map[string]apijson.Field
-}
-
-func (r *AssistantMessagePartStepFinishPartTokensCache) UnmarshalJSON(data []byte) (err error) {
- return apijson.UnmarshalRoot(data, r)
-}
-
-func (r assistantMessagePartStepFinishPartTokensCacheJSON) RawJSON() string {
- return r.raw
-}
-
-type AssistantMessagePartStepFinishPartType string
-
-const (
- AssistantMessagePartStepFinishPartTypeStepFinish AssistantMessagePartStepFinishPartType = "step-finish"
-)
-
-func (r AssistantMessagePartStepFinishPartType) IsKnown() bool {
- switch r {
- case AssistantMessagePartStepFinishPartTypeStepFinish:
- return true
- }
- return false
-}
-
-type AssistantMessagePartType string
-
-const (
- AssistantMessagePartTypeText AssistantMessagePartType = "text"
- AssistantMessagePartTypeTool AssistantMessagePartType = "tool"
- AssistantMessagePartTypeStepStart AssistantMessagePartType = "step-start"
- AssistantMessagePartTypeStepFinish AssistantMessagePartType = "step-finish"
-)
-
-func (r AssistantMessagePartType) IsKnown() bool {
- switch r {
- case AssistantMessagePartTypeText, AssistantMessagePartTypeTool, AssistantMessagePartTypeStepStart, AssistantMessagePartTypeStepFinish:
- return true
- }
- return false
-}
-
type FilePart struct {
- Mime string `json:"mime,required"`
- Type FilePartType `json:"type,required"`
- URL string `json:"url,required"`
- Filename string `json:"filename"`
- JSON filePartJSON `json:"-"`
+ ID string `json:"id,required"`
+ MessageID string `json:"messageID,required"`
+ Mime string `json:"mime,required"`
+ SessionID string `json:"sessionID,required"`
+ Type FilePartType `json:"type,required"`
+ URL string `json:"url,required"`
+ Filename string `json:"filename"`
+ JSON filePartJSON `json:"-"`
}
// filePartJSON contains the JSON metadata for the struct [FilePart]
type filePartJSON struct {
+ ID apijson.Field
+ MessageID apijson.Field
Mime apijson.Field
+ SessionID apijson.Field
Type apijson.Field
URL apijson.Field
Filename apijson.Field
@@ -655,7 +465,7 @@ func (r filePartJSON) RawJSON() string {
return r.raw
}
-func (r FilePart) implementsUserMessagePart() {}
+func (r FilePart) implementsPart() {}
type FilePartType string
@@ -672,23 +482,23 @@ func (r FilePartType) IsKnown() bool {
}
type FilePartParam struct {
- Mime param.Field[string] `json:"mime,required"`
- Type param.Field[FilePartType] `json:"type,required"`
- URL param.Field[string] `json:"url,required"`
- Filename param.Field[string] `json:"filename"`
+ ID param.Field[string] `json:"id,required"`
+ MessageID param.Field[string] `json:"messageID,required"`
+ Mime param.Field[string] `json:"mime,required"`
+ SessionID param.Field[string] `json:"sessionID,required"`
+ Type param.Field[FilePartType] `json:"type,required"`
+ URL param.Field[string] `json:"url,required"`
+ Filename param.Field[string] `json:"filename"`
}
func (r FilePartParam) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
-func (r FilePartParam) implementsUserMessagePartUnionParam() {}
+func (r FilePartParam) implementsSessionChatParamsPartUnion() {}
type Message struct {
- ID string `json:"id,required"`
- // This field can have the runtime type of [[]UserMessagePart],
- // [[]AssistantMessagePart].
- Parts interface{} `json:"parts,required"`
+ ID string `json:"id,required"`
Role MessageRole `json:"role,required"`
SessionID string `json:"sessionID,required"`
// This field can have the runtime type of [UserMessageTime],
@@ -713,7 +523,6 @@ type Message struct {
// messageJSON contains the JSON metadata for the struct [Message]
type messageJSON struct {
ID apijson.Field
- Parts apijson.Field
Role apijson.Field
SessionID apijson.Field
Time apijson.Field
@@ -787,6 +596,128 @@ func (r MessageRole) IsKnown() bool {
return false
}
+type Part struct {
+ ID string `json:"id,required"`
+ MessageID string `json:"messageID,required"`
+ SessionID string `json:"sessionID,required"`
+ Type PartType `json:"type,required"`
+ CallID string `json:"callID"`
+ Cost float64 `json:"cost"`
+ Filename string `json:"filename"`
+ Mime string `json:"mime"`
+ // This field can have the runtime type of [ToolPartState].
+ State interface{} `json:"state"`
+ Synthetic bool `json:"synthetic"`
+ Text string `json:"text"`
+ // This field can have the runtime type of [TextPartTime].
+ Time interface{} `json:"time"`
+ // This field can have the runtime type of [StepFinishPartTokens].
+ Tokens interface{} `json:"tokens"`
+ Tool string `json:"tool"`
+ URL string `json:"url"`
+ JSON partJSON `json:"-"`
+ union PartUnion
+}
+
+// partJSON contains the JSON metadata for the struct [Part]
+type partJSON struct {
+ ID apijson.Field
+ MessageID apijson.Field
+ SessionID apijson.Field
+ Type apijson.Field
+ CallID apijson.Field
+ Cost apijson.Field
+ Filename apijson.Field
+ Mime apijson.Field
+ State apijson.Field
+ Synthetic apijson.Field
+ Text apijson.Field
+ Time apijson.Field
+ Tokens apijson.Field
+ Tool apijson.Field
+ URL apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r partJSON) RawJSON() string {
+ return r.raw
+}
+
+func (r *Part) UnmarshalJSON(data []byte) (err error) {
+ *r = Part{}
+ err = apijson.UnmarshalRoot(data, &r.union)
+ if err != nil {
+ return err
+ }
+ return apijson.Port(r.union, &r)
+}
+
+// AsUnion returns a [PartUnion] interface which you can cast to the specific types
+// for more type safety.
+//
+// Possible runtime types of the union are [TextPart], [FilePart], [ToolPart],
+// [StepStartPart], [StepFinishPart].
+func (r Part) AsUnion() PartUnion {
+ return r.union
+}
+
+// Union satisfied by [TextPart], [FilePart], [ToolPart], [StepStartPart] or
+// [StepFinishPart].
+type PartUnion interface {
+ implementsPart()
+}
+
+func init() {
+ apijson.RegisterUnion(
+ reflect.TypeOf((*PartUnion)(nil)).Elem(),
+ "type",
+ apijson.UnionVariant{
+ TypeFilter: gjson.JSON,
+ Type: reflect.TypeOf(TextPart{}),
+ DiscriminatorValue: "text",
+ },
+ apijson.UnionVariant{
+ TypeFilter: gjson.JSON,
+ Type: reflect.TypeOf(FilePart{}),
+ DiscriminatorValue: "file",
+ },
+ apijson.UnionVariant{
+ TypeFilter: gjson.JSON,
+ Type: reflect.TypeOf(ToolPart{}),
+ DiscriminatorValue: "tool",
+ },
+ apijson.UnionVariant{
+ TypeFilter: gjson.JSON,
+ Type: reflect.TypeOf(StepStartPart{}),
+ DiscriminatorValue: "step-start",
+ },
+ apijson.UnionVariant{
+ TypeFilter: gjson.JSON,
+ Type: reflect.TypeOf(StepFinishPart{}),
+ DiscriminatorValue: "step-finish",
+ },
+ )
+}
+
+type PartType string
+
+const (
+ PartTypeText PartType = "text"
+ PartTypeFile PartType = "file"
+ PartTypeTool PartType = "tool"
+ PartTypeStepStart PartType = "step-start"
+ PartTypeStepFinish PartType = "step-finish"
+)
+
+func (r PartType) IsKnown() bool {
+ switch r {
+ case PartTypeText, PartTypeFile, PartTypeTool, PartTypeStepStart, PartTypeStepFinish:
+ return true
+ }
+ return false
+}
+
type Session struct {
ID string `json:"id,required"`
Time SessionTime `json:"time,required"`
@@ -885,13 +816,115 @@ func (r sessionShareJSON) RawJSON() string {
return r.raw
}
+type StepFinishPart struct {
+ ID string `json:"id,required"`
+ Cost float64 `json:"cost,required"`
+ MessageID string `json:"messageID,required"`
+ SessionID string `json:"sessionID,required"`
+ Tokens StepFinishPartTokens `json:"tokens,required"`
+ Type StepFinishPartType `json:"type,required"`
+ JSON stepFinishPartJSON `json:"-"`
+}
+
+// stepFinishPartJSON contains the JSON metadata for the struct [StepFinishPart]
+type stepFinishPartJSON struct {
+ ID apijson.Field
+ Cost apijson.Field
+ MessageID apijson.Field
+ SessionID apijson.Field
+ Tokens apijson.Field
+ Type apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r *StepFinishPart) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r stepFinishPartJSON) RawJSON() string {
+ return r.raw
+}
+
+func (r StepFinishPart) implementsPart() {}
+
+type StepFinishPartTokens struct {
+ Cache StepFinishPartTokensCache `json:"cache,required"`
+ Input float64 `json:"input,required"`
+ Output float64 `json:"output,required"`
+ Reasoning float64 `json:"reasoning,required"`
+ JSON stepFinishPartTokensJSON `json:"-"`
+}
+
+// stepFinishPartTokensJSON contains the JSON metadata for the struct
+// [StepFinishPartTokens]
+type stepFinishPartTokensJSON struct {
+ Cache apijson.Field
+ Input apijson.Field
+ Output apijson.Field
+ Reasoning apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r *StepFinishPartTokens) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r stepFinishPartTokensJSON) RawJSON() string {
+ return r.raw
+}
+
+type StepFinishPartTokensCache struct {
+ Read float64 `json:"read,required"`
+ Write float64 `json:"write,required"`
+ JSON stepFinishPartTokensCacheJSON `json:"-"`
+}
+
+// stepFinishPartTokensCacheJSON contains the JSON metadata for the struct
+// [StepFinishPartTokensCache]
+type stepFinishPartTokensCacheJSON struct {
+ Read apijson.Field
+ Write apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r *StepFinishPartTokensCache) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r stepFinishPartTokensCacheJSON) RawJSON() string {
+ return r.raw
+}
+
+type StepFinishPartType string
+
+const (
+ StepFinishPartTypeStepFinish StepFinishPartType = "step-finish"
+)
+
+func (r StepFinishPartType) IsKnown() bool {
+ switch r {
+ case StepFinishPartTypeStepFinish:
+ return true
+ }
+ return false
+}
+
type StepStartPart struct {
- Type StepStartPartType `json:"type,required"`
- JSON stepStartPartJSON `json:"-"`
+ ID string `json:"id,required"`
+ MessageID string `json:"messageID,required"`
+ SessionID string `json:"sessionID,required"`
+ Type StepStartPartType `json:"type,required"`
+ JSON stepStartPartJSON `json:"-"`
}
// stepStartPartJSON contains the JSON metadata for the struct [StepStartPart]
type stepStartPartJSON struct {
+ ID apijson.Field
+ MessageID apijson.Field
+ SessionID apijson.Field
Type apijson.Field
raw string
ExtraFields map[string]apijson.Field
@@ -905,7 +938,7 @@ func (r stepStartPartJSON) RawJSON() string {
return r.raw
}
-func (r StepStartPart) implementsAssistantMessagePart() {}
+func (r StepStartPart) implementsPart() {}
type StepStartPartType string
@@ -922,17 +955,25 @@ func (r StepStartPartType) IsKnown() bool {
}
type TextPart struct {
+ ID string `json:"id,required"`
+ MessageID string `json:"messageID,required"`
+ SessionID string `json:"sessionID,required"`
Text string `json:"text,required"`
Type TextPartType `json:"type,required"`
Synthetic bool `json:"synthetic"`
+ Time TextPartTime `json:"time"`
JSON textPartJSON `json:"-"`
}
// textPartJSON contains the JSON metadata for the struct [TextPart]
type textPartJSON struct {
+ ID apijson.Field
+ MessageID apijson.Field
+ SessionID apijson.Field
Text apijson.Field
Type apijson.Field
Synthetic apijson.Field
+ Time apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
@@ -945,9 +986,7 @@ func (r textPartJSON) RawJSON() string {
return r.raw
}
-func (r TextPart) implementsAssistantMessagePart() {}
-
-func (r TextPart) implementsUserMessagePart() {}
+func (r TextPart) implementsPart() {}
type TextPartType string
@@ -963,29 +1002,70 @@ func (r TextPartType) IsKnown() bool {
return false
}
+type TextPartTime struct {
+ Start float64 `json:"start,required"`
+ End float64 `json:"end"`
+ JSON textPartTimeJSON `json:"-"`
+}
+
+// textPartTimeJSON contains the JSON metadata for the struct [TextPartTime]
+type textPartTimeJSON struct {
+ Start apijson.Field
+ End apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r *TextPartTime) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r textPartTimeJSON) RawJSON() string {
+ return r.raw
+}
+
type TextPartParam struct {
- Text param.Field[string] `json:"text,required"`
- Type param.Field[TextPartType] `json:"type,required"`
- Synthetic param.Field[bool] `json:"synthetic"`
+ ID param.Field[string] `json:"id,required"`
+ MessageID param.Field[string] `json:"messageID,required"`
+ SessionID param.Field[string] `json:"sessionID,required"`
+ Text param.Field[string] `json:"text,required"`
+ Type param.Field[TextPartType] `json:"type,required"`
+ Synthetic param.Field[bool] `json:"synthetic"`
+ Time param.Field[TextPartTimeParam] `json:"time"`
}
func (r TextPartParam) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
-func (r TextPartParam) implementsUserMessagePartUnionParam() {}
+func (r TextPartParam) implementsSessionChatParamsPartUnion() {}
+
+type TextPartTimeParam struct {
+ Start param.Field[float64] `json:"start,required"`
+ End param.Field[float64] `json:"end"`
+}
+
+func (r TextPartTimeParam) MarshalJSON() (data []byte, err error) {
+ return apijson.MarshalRoot(r)
+}
type ToolPart struct {
- ID string `json:"id,required"`
- State ToolPartState `json:"state,required"`
- Tool string `json:"tool,required"`
- Type ToolPartType `json:"type,required"`
- JSON toolPartJSON `json:"-"`
+ ID string `json:"id,required"`
+ CallID string `json:"callID,required"`
+ MessageID string `json:"messageID,required"`
+ SessionID string `json:"sessionID,required"`
+ State ToolPartState `json:"state,required"`
+ Tool string `json:"tool,required"`
+ Type ToolPartType `json:"type,required"`
+ JSON toolPartJSON `json:"-"`
}
// toolPartJSON contains the JSON metadata for the struct [ToolPart]
type toolPartJSON struct {
ID apijson.Field
+ CallID apijson.Field
+ MessageID apijson.Field
+ SessionID apijson.Field
State apijson.Field
Tool apijson.Field
Type apijson.Field
@@ -1001,7 +1081,7 @@ func (r toolPartJSON) RawJSON() string {
return r.raw
}
-func (r ToolPart) implementsAssistantMessagePart() {}
+func (r ToolPart) implementsPart() {}
type ToolPartState struct {
Status ToolPartStateStatus `json:"status,required"`
@@ -1357,18 +1437,16 @@ func (r toolStateRunningTimeJSON) RawJSON() string {
}
type UserMessage struct {
- ID string `json:"id,required"`
- Parts []UserMessagePart `json:"parts,required"`
- Role UserMessageRole `json:"role,required"`
- SessionID string `json:"sessionID,required"`
- Time UserMessageTime `json:"time,required"`
- JSON userMessageJSON `json:"-"`
+ ID string `json:"id,required"`
+ Role UserMessageRole `json:"role,required"`
+ SessionID string `json:"sessionID,required"`
+ Time UserMessageTime `json:"time,required"`
+ JSON userMessageJSON `json:"-"`
}
// userMessageJSON contains the JSON metadata for the struct [UserMessage]
type userMessageJSON struct {
ID apijson.Field
- Parts apijson.Field
Role apijson.Field
SessionID apijson.Field
Time apijson.Field
@@ -1420,119 +1498,82 @@ func (r userMessageTimeJSON) RawJSON() string {
return r.raw
}
-type UserMessagePart struct {
- Type UserMessagePartType `json:"type,required"`
- Filename string `json:"filename"`
- Mime string `json:"mime"`
- Synthetic bool `json:"synthetic"`
- Text string `json:"text"`
- URL string `json:"url"`
- JSON userMessagePartJSON `json:"-"`
- union UserMessagePartUnion
+type SessionMessagesResponse struct {
+ Info Message `json:"info,required"`
+ Parts []Part `json:"parts,required"`
+ JSON sessionMessagesResponseJSON `json:"-"`
}
-// userMessagePartJSON contains the JSON metadata for the struct [UserMessagePart]
-type userMessagePartJSON struct {
- Type apijson.Field
- Filename apijson.Field
- Mime apijson.Field
- Synthetic apijson.Field
- Text apijson.Field
- URL apijson.Field
+// sessionMessagesResponseJSON contains the JSON metadata for the struct
+// [SessionMessagesResponse]
+type sessionMessagesResponseJSON struct {
+ Info apijson.Field
+ Parts apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
-func (r userMessagePartJSON) RawJSON() string {
+func (r *SessionMessagesResponse) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r sessionMessagesResponseJSON) RawJSON() string {
return r.raw
}
-func (r *UserMessagePart) UnmarshalJSON(data []byte) (err error) {
- *r = UserMessagePart{}
- err = apijson.UnmarshalRoot(data, &r.union)
- if err != nil {
- return err
- }
- return apijson.Port(r.union, &r)
+type SessionChatParams struct {
+ MessageID param.Field[string] `json:"messageID,required"`
+ Mode param.Field[string] `json:"mode,required"`
+ ModelID param.Field[string] `json:"modelID,required"`
+ Parts param.Field[[]SessionChatParamsPartUnion] `json:"parts,required"`
+ ProviderID param.Field[string] `json:"providerID,required"`
}
-// AsUnion returns a [UserMessagePartUnion] interface which you can cast to the
-// specific types for more type safety.
-//
-// Possible runtime types of the union are [TextPart], [FilePart].
-func (r UserMessagePart) AsUnion() UserMessagePartUnion {
- return r.union
+func (r SessionChatParams) MarshalJSON() (data []byte, err error) {
+ return apijson.MarshalRoot(r)
}
-// Union satisfied by [TextPart] or [FilePart].
-type UserMessagePartUnion interface {
- implementsUserMessagePart()
+type SessionChatParamsPart struct {
+ ID param.Field[string] `json:"id,required"`
+ MessageID param.Field[string] `json:"messageID,required"`
+ SessionID param.Field[string] `json:"sessionID,required"`
+ Type param.Field[SessionChatParamsPartsType] `json:"type,required"`
+ Filename param.Field[string] `json:"filename"`
+ Mime param.Field[string] `json:"mime"`
+ Synthetic param.Field[bool] `json:"synthetic"`
+ Text param.Field[string] `json:"text"`
+ Time param.Field[interface{}] `json:"time"`
+ URL param.Field[string] `json:"url"`
}
-func init() {
- apijson.RegisterUnion(
- reflect.TypeOf((*UserMessagePartUnion)(nil)).Elem(),
- "type",
- apijson.UnionVariant{
- TypeFilter: gjson.JSON,
- Type: reflect.TypeOf(TextPart{}),
- DiscriminatorValue: "text",
- },
- apijson.UnionVariant{
- TypeFilter: gjson.JSON,
- Type: reflect.TypeOf(FilePart{}),
- DiscriminatorValue: "file",
- },
- )
+func (r SessionChatParamsPart) MarshalJSON() (data []byte, err error) {
+ return apijson.MarshalRoot(r)
}
-type UserMessagePartType string
+func (r SessionChatParamsPart) implementsSessionChatParamsPartUnion() {}
+
+// Satisfied by [FilePartParam], [TextPartParam], [SessionChatParamsPart].
+type SessionChatParamsPartUnion interface {
+ implementsSessionChatParamsPartUnion()
+}
+
+type SessionChatParamsPartsType string
const (
- UserMessagePartTypeText UserMessagePartType = "text"
- UserMessagePartTypeFile UserMessagePartType = "file"
+ SessionChatParamsPartsTypeFile SessionChatParamsPartsType = "file"
+ SessionChatParamsPartsTypeText SessionChatParamsPartsType = "text"
)
-func (r UserMessagePartType) IsKnown() bool {
+func (r SessionChatParamsPartsType) IsKnown() bool {
switch r {
- case UserMessagePartTypeText, UserMessagePartTypeFile:
+ case SessionChatParamsPartsTypeFile, SessionChatParamsPartsTypeText:
return true
}
return false
}
-type UserMessagePartParam struct {
- Type param.Field[UserMessagePartType] `json:"type,required"`
- Filename param.Field[string] `json:"filename"`
- Mime param.Field[string] `json:"mime"`
- Synthetic param.Field[bool] `json:"synthetic"`
- Text param.Field[string] `json:"text"`
- URL param.Field[string] `json:"url"`
-}
-
-func (r UserMessagePartParam) MarshalJSON() (data []byte, err error) {
- return apijson.MarshalRoot(r)
-}
-
-func (r UserMessagePartParam) implementsUserMessagePartUnionParam() {}
-
-// Satisfied by [TextPartParam], [FilePartParam], [UserMessagePartParam].
-type UserMessagePartUnionParam interface {
- implementsUserMessagePartUnionParam()
-}
-
-type SessionChatParams struct {
- Mode param.Field[string] `json:"mode,required"`
- ModelID param.Field[string] `json:"modelID,required"`
- Parts param.Field[[]UserMessagePartUnionParam] `json:"parts,required"`
- ProviderID param.Field[string] `json:"providerID,required"`
-}
-
-func (r SessionChatParams) MarshalJSON() (data []byte, err error) {
- return apijson.MarshalRoot(r)
-}
-
type SessionInitParams struct {
+ MessageID param.Field[string] `json:"messageID,required"`
ModelID param.Field[string] `json:"modelID,required"`
ProviderID param.Field[string] `json:"providerID,required"`
}
diff --git a/packages/tui/sdk/session_test.go b/packages/tui/sdk/session_test.go
index 4ff2818c5..c74a4a385 100644
--- a/packages/tui/sdk/session_test.go
+++ b/packages/tui/sdk/session_test.go
@@ -117,12 +117,17 @@ func TestSessionChat(t *testing.T) {
context.TODO(),
"id",
opencode.SessionChatParams{
- Mode: opencode.F("mode"),
- ModelID: opencode.F("modelID"),
- Parts: opencode.F([]opencode.UserMessagePartUnionParam{opencode.TextPartParam{
- Text: opencode.F("text"),
- Type: opencode.F(opencode.TextPartTypeText),
- Synthetic: opencode.F(true),
+ MessageID: opencode.F("messageID"),
+ Mode: opencode.F("mode"),
+ ModelID: opencode.F("modelID"),
+ Parts: opencode.F([]opencode.SessionChatParamsPartUnion{opencode.FilePartParam{
+ ID: opencode.F("id"),
+ MessageID: opencode.F("messageID"),
+ Mime: opencode.F("mime"),
+ SessionID: opencode.F("sessionID"),
+ Type: opencode.F(opencode.FilePartTypeFile),
+ URL: opencode.F("url"),
+ Filename: opencode.F("filename"),
}}),
ProviderID: opencode.F("providerID"),
},
@@ -152,6 +157,7 @@ func TestSessionInit(t *testing.T) {
context.TODO(),
"id",
opencode.SessionInitParams{
+ MessageID: opencode.F("messageID"),
ModelID: opencode.F("modelID"),
ProviderID: opencode.F("providerID"),
},
diff --git a/packages/tui/sdk/usage_test.go b/packages/tui/sdk/usage_test.go
index 0e261a7aa..5e8f44c7c 100644
--- a/packages/tui/sdk/usage_test.go
+++ b/packages/tui/sdk/usage_test.go
@@ -23,10 +23,13 @@ func TestUsage(t *testing.T) {
client := opencode.NewClient(
option.WithBaseURL(baseURL),
)
- events, err := client.Event.List(context.TODO())
+ stream := client.Event.ListStreaming(context.TODO())
+ for stream.Next() {
+ t.Logf("%+v\n", stream.Current())
+ }
+ err := stream.Err()
if err != nil {
t.Error(err)
return
}
- t.Logf("%+v\n", events)
}
diff --git a/packages/web/src/components/Share.tsx b/packages/web/src/components/Share.tsx
index ad23f188b..56f218fd8 100644
--- a/packages/web/src/components/Share.tsx
+++ b/packages/web/src/components/Share.tsx
@@ -1,6 +1,6 @@
-import { For, Show, onMount, Suspense, onCleanup, createMemo, createSignal, SuspenseList } from "solid-js"
+import { For, Show, onMount, Suspense, onCleanup, createMemo, createSignal, SuspenseList, createEffect } from "solid-js"
import { DateTime } from "luxon"
-import { createStore, reconcile } from "solid-js/store"
+import { createStore, reconcile, unwrap } from "solid-js/store"
import { IconArrowDown } from "./icons"
import { IconOpencode } from "./icons/custom"
import styles from "./share.module.css"
@@ -9,6 +9,8 @@ import type { Message } from "opencode/session/message"
import type { Session } from "opencode/session/index"
import { Part, ProviderIcon } from "./share/part"
+type MessageWithParts = MessageV2.Info & { parts: MessageV2.Part[] }
+
type Status = "disconnected" | "connecting" | "connected" | "error" | "reconnecting"
function scrollToAnchor(id: string) {
@@ -39,7 +41,7 @@ export default function Share(props: {
id: string
api: string
info: Session.Info
- messages: Record<string, MessageV2.Info>
+ messages: Record<string, MessageWithParts>
}) {
let lastScrollY = 0
let hasScrolledToAnchor = false
@@ -57,10 +59,13 @@ export default function Share(props: {
const [store, setStore] = createStore<{
info?: Session.Info
- messages: Record<string, MessageV2.Info | Message.Info>
+ messages: Record<string, MessageWithParts>
}>({ info: props.info, messages: props.messages })
const messages = createMemo(() => Object.values(store.messages).toSorted((a, b) => a.id?.localeCompare(b.id)))
const [connectionStatus, setConnectionStatus] = createSignal<[Status, string?]>(["disconnected", "Disconnected"])
+ createEffect(() => {
+ console.log(unwrap(store))
+ })
onMount(() => {
const apiUrl = props.api
@@ -115,8 +120,22 @@ export default function Share(props: {
}
if (type === "message") {
const [, messageID] = splits
+ if ("metadata" in d.content) {
+ d.content = fromV1(d.content)
+ }
+ d.content.parts = d.content.parts ?? store.messages[messageID]?.parts ?? []
setStore("messages", messageID, reconcile(d.content))
}
+ if (type === "part") {
+ setStore("messages", d.content.messageID, "parts", arr => {
+ const index = arr.findIndex((x) => x.id === d.content.id)
+ if (index === -1)
+ arr.push(d.content)
+ if (index > -1)
+ arr[index] = d.content
+ return [...arr]
+ })
+ }
} catch (error) {
console.error("Error parsing WebSocket message:", error)
}
@@ -233,7 +252,7 @@ export default function Share(props: {
rootDir: undefined as string | undefined,
created: undefined as number | undefined,
completed: undefined as number | undefined,
- messages: [] as MessageV2.Info[],
+ messages: [] as MessageWithParts[],
models: {} as Record<string, string[]>,
cost: 0,
tokens: {
@@ -247,7 +266,7 @@ export default function Share(props: {
const msgs = messages()
for (let i = 0; i < msgs.length; i++) {
- const msg = "metadata" in msgs[i] ? fromV1(msgs[i] as Message.Info) : (msgs[i] as MessageV2.Info)
+ const msg = msgs[i]
result.messages.push(msg)
@@ -464,9 +483,9 @@ export default function Share(props: {
)
}
-export function fromV1(v1: Message.Info): MessageV2.Info {
+export function fromV1(v1: Message.Info): MessageWithParts {
if (v1.role === "assistant") {
- const result: MessageV2.Assistant = {
+ return {
id: v1.id,
sessionID: v1.metadata.sessionID,
role: "assistant",
@@ -482,10 +501,16 @@ export function fromV1(v1: Message.Info): MessageV2.Info {
providerID: v1.metadata.assistant!.providerID,
system: v1.metadata.assistant!.system,
error: v1.metadata.error,
- parts: v1.parts.flatMap((part): MessageV2.AssistantPart[] => {
+ parts: v1.parts.flatMap((part, index): MessageV2.Part[] => {
+ const base = {
+ id: index.toString(),
+ messageID: v1.id,
+ sessionID: v1.metadata.sessionID,
+ }
if (part.type === "text") {
return [
{
+ ...base,
type: "text",
text: part.text,
},
@@ -494,6 +519,7 @@ export function fromV1(v1: Message.Info): MessageV2.Info {
if (part.type === "step-start") {
return [
{
+ ...base,
type: "step-start",
},
]
@@ -501,8 +527,9 @@ export function fromV1(v1: Message.Info): MessageV2.Info {
if (part.type === "tool-invocation") {
return [
{
+ ...base,
type: "tool",
- id: part.toolInvocation.toolCallId,
+ callID: part.toolInvocation.toolCallId,
tool: part.toolInvocation.toolName,
state: (() => {
if (part.toolInvocation.state === "partial-call") {
@@ -540,21 +567,26 @@ export function fromV1(v1: Message.Info): MessageV2.Info {
return []
}),
}
- return result
}
if (v1.role === "user") {
- const result: MessageV2.User = {
+ return {
id: v1.id,
sessionID: v1.metadata.sessionID,
role: "user",
time: {
created: v1.metadata.time.created,
},
- parts: v1.parts.flatMap((part): MessageV2.UserPart[] => {
+ parts: v1.parts.flatMap((part, index): MessageV2.Part[] => {
+ const base = {
+ id: index.toString(),
+ messageID: v1.id,
+ sessionID: v1.metadata.sessionID,
+ }
if (part.type === "text") {
return [
{
+ ...base,
type: "text",
text: part.text,
},
@@ -563,6 +595,7 @@ export function fromV1(v1: Message.Info): MessageV2.Info {
if (part.type === "file") {
return [
{
+ ...base,
type: "file",
mime: part.mediaType,
filename: part.filename,
@@ -573,7 +606,6 @@ export function fromV1(v1: Message.Info): MessageV2.Info {
return []
}),
}
- return result
}
throw new Error("unknown message type")
diff --git a/stainless.yml b/stainless.yml
index aec6817f5..3e5a484ed 100644
--- a/stainless.yml
+++ b/stainless.yml
@@ -88,10 +88,12 @@ resources:
models:
session: Session
message: Message
+ part: Part
textPart: TextPart
filePart: FilePart
toolPart: ToolPart
stepStartPart: StepStartPart
+ stepFinishPart: StepFinishPart
assistantMessage: AssistantMessage
assistantMessagePart: AssistantMessagePart
userMessage: UserMessage