mirror of
https://github.com/sst/opencode.git
synced 2025-07-08 00:25:00 +00:00
Compare commits
42 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a272b58fe9 | ||
![]() |
9948fcf1b6 | ||
![]() |
0d50c867ff | ||
![]() |
27f7e02f12 | ||
![]() |
0f93ecd564 | ||
![]() |
da909d9684 | ||
![]() |
facd851b11 | ||
![]() |
c51de945a5 | ||
![]() |
9253a3ca9e | ||
![]() |
7cfa297a78 | ||
![]() |
661b74def6 | ||
![]() |
b478e5655c | ||
![]() |
f884766445 | ||
![]() |
76b2e4539c | ||
![]() |
d87922c0eb | ||
![]() |
2446483df5 | ||
![]() |
f4c453155d | ||
![]() |
969ad80ed2 | ||
![]() |
af064b41d7 | ||
![]() |
ea6bfef21a | ||
![]() |
107363b1d9 | ||
![]() |
85214d7c59 | ||
![]() |
997cb2d945 | ||
![]() |
45b139390c | ||
![]() |
994368de15 | ||
![]() |
143fd8e076 | ||
![]() |
06dba28bd6 | ||
![]() |
b8d276a049 | ||
![]() |
ee01f01271 | ||
![]() |
32d5db4f0a | ||
![]() |
f6108b7be8 | ||
![]() |
94ef341c9d | ||
![]() |
f9abc7c84f | ||
![]() |
891ed6ebc0 | ||
![]() |
163e23a68b | ||
![]() |
f13b0af491 | ||
![]() |
4a0be45d3d | ||
![]() |
23788674c8 | ||
![]() |
121eb24e73 | ||
![]() |
571d60182a | ||
![]() |
167a9dcaf3 | ||
![]() |
37327259cb |
135 changed files with 5784 additions and 7443 deletions
2
.github/workflows/stats.yml
vendored
2
.github/workflows/stats.yml
vendored
|
@ -28,5 +28,5 @@ jobs:
|
||||||
git config --local user.email "action@github.com"
|
git config --local user.email "action@github.com"
|
||||||
git config --local user.name "GitHub Action"
|
git config --local user.name "GitHub Action"
|
||||||
git add STATS.md
|
git add STATS.md
|
||||||
git diff --staged --quiet || git commit -m "Update download stats $(date -I)"
|
git diff --staged --quiet || git commit -m "ignore: update download stats $(date -I)"
|
||||||
git push
|
git push
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
</p>
|
</p>
|
||||||
<p align="center">AI coding agent, built for the terminal.</p>
|
<p align="center">AI coding agent, built for the terminal.</p>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://opencode.ai/docs"><img alt="View docs" src="https://img.shields.io/badge/view-docs-blue?style=flat-square" /></a>
|
<a href="https://discord.gg/opencode"><img alt="Discord" src="https://img.shields.io/discord/1391832426048651334?style=flat-square&label=discord" /></a>
|
||||||
<a href="https://www.npmjs.com/package/opencode-ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square" /></a>
|
<a href="https://www.npmjs.com/package/opencode-ai"><img alt="npm" src="https://img.shields.io/npm/v/opencode-ai?style=flat-square" /></a>
|
||||||
<a href="https://github.com/sst/opencode/actions/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/sst/opencode/publish.yml?style=flat-square&branch=dev" /></a>
|
<a href="https://github.com/sst/opencode/actions/workflows/publish.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/sst/opencode/publish.yml?style=flat-square&branch=dev" /></a>
|
||||||
</p>
|
</p>
|
||||||
|
@ -76,4 +76,4 @@ The other confusingly named repo has no relation to this one. You can [read the
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Join our community** [YouTube](https://www.youtube.com/c/sst-dev) | [X.com](https://x.com/SST_dev)
|
**Join our community** [Discord](https://discord.gg/opencode) | [YouTube](https://www.youtube.com/c/sst-dev) | [X.com](https://x.com/SST_dev)
|
||||||
|
|
3
STATS.md
3
STATS.md
|
@ -7,3 +7,6 @@
|
||||||
| 2025-07-01 | 22,108 (+1,981) | 43,745 (+2,686) | 65,853 (+4,667) |
|
| 2025-07-01 | 22,108 (+1,981) | 43,745 (+2,686) | 65,853 (+4,667) |
|
||||||
| 2025-07-02 | 24,814 (+2,706) | 46,168 (+2,423) | 70,982 (+5,129) |
|
| 2025-07-02 | 24,814 (+2,706) | 46,168 (+2,423) | 70,982 (+5,129) |
|
||||||
| 2025-07-03 | 27,834 (+3,020) | 49,955 (+3,787) | 77,789 (+6,807) |
|
| 2025-07-03 | 27,834 (+3,020) | 49,955 (+3,787) | 77,789 (+6,807) |
|
||||||
|
| 2025-07-04 | 30,608 (+2,774) | 54,758 (+4,803) | 85,366 (+7,577) |
|
||||||
|
| 2025-07-05 | 32,524 (+1,916) | 58,371 (+3,613) | 90,895 (+5,529) |
|
||||||
|
| 2025-07-06 | 33,766 (+1,242) | 59,694 (+1,323) | 93,460 (+2,565) |
|
||||||
|
|
87
bun.lock
87
bun.lock
|
@ -5,7 +5,7 @@
|
||||||
"name": "opencode",
|
"name": "opencode",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"prettier": "3.5.3",
|
"prettier": "3.5.3",
|
||||||
"sst": "3.17.6",
|
"sst": "3.17.8",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages/function": {
|
"packages/function": {
|
||||||
|
@ -78,6 +78,7 @@
|
||||||
"lang-map": "0.4.0",
|
"lang-map": "0.4.0",
|
||||||
"luxon": "3.6.1",
|
"luxon": "3.6.1",
|
||||||
"marked": "15.0.12",
|
"marked": "15.0.12",
|
||||||
|
"marked-shiki": "1.2.0",
|
||||||
"rehype-autolink-headings": "7.1.0",
|
"rehype-autolink-headings": "7.1.0",
|
||||||
"sharp": "0.32.5",
|
"sharp": "0.32.5",
|
||||||
"shiki": "3.4.2",
|
"shiki": "3.4.2",
|
||||||
|
@ -95,30 +96,22 @@
|
||||||
"sharp",
|
"sharp",
|
||||||
"esbuild",
|
"esbuild",
|
||||||
],
|
],
|
||||||
"patchedDependencies": {
|
|
||||||
"ai@4.3.16": "patches/ai@4.3.16.patch",
|
|
||||||
},
|
|
||||||
"overrides": {
|
|
||||||
"zod": "3.24.2",
|
|
||||||
},
|
|
||||||
"catalog": {
|
"catalog": {
|
||||||
"@types/node": "22.13.9",
|
"@types/node": "22.13.9",
|
||||||
"ai": "4.3.16",
|
"ai": "5.0.0-beta.7",
|
||||||
"typescript": "5.8.2",
|
"typescript": "5.8.2",
|
||||||
"zod": "3.24.2",
|
"zod": "3.25.49",
|
||||||
},
|
},
|
||||||
"packages": {
|
"packages": {
|
||||||
"@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@2.2.10", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-icLGO7Q0NinnHIPgT+y1QjHVwH4HwV+brWbvM+FfCG2Afpa89PyKa3Ret91kGjZpBgM/xnj1B7K5eM+rRlsXQA=="],
|
"@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@2.2.10", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-icLGO7Q0NinnHIPgT+y1QjHVwH4HwV+brWbvM+FfCG2Afpa89PyKa3Ret91kGjZpBgM/xnj1B7K5eM+rRlsXQA=="],
|
||||||
|
|
||||||
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@1.2.12", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-YSzjlko7JvuiyQFmI9RN1tNZdEiZxc+6xld/0tq/VkJaHpEzGAb1yiNxxvmYVcjvfu/PcvCxAAYXmTYQQ63IHQ=="],
|
"@ai-sdk/anthropic": ["@ai-sdk/anthropic@1.2.12", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8" }, "peerDependencies": { "zod": "^3.0.0" } }, "sha512-YSzjlko7JvuiyQFmI9RN1tNZdEiZxc+6xld/0tq/VkJaHpEzGAb1yiNxxvmYVcjvfu/PcvCxAAYXmTYQQ63IHQ=="],
|
||||||
|
|
||||||
"@ai-sdk/provider": ["@ai-sdk/provider@1.1.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg=="],
|
"@ai-sdk/gateway": ["@ai-sdk/gateway@1.0.0-beta.3", "", { "dependencies": { "@ai-sdk/provider": "2.0.0-beta.1", "@ai-sdk/provider-utils": "3.0.0-beta.2" }, "peerDependencies": { "zod": "^3.25.49" } }, "sha512-g49gMSkXy94lYvl5LRh438OR/0JCG6ol0jV+iLot7cy5HLltZlGocEuauETBu4b10mDXOd7XIjTEZoQpYFMYLQ=="],
|
||||||
|
|
||||||
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@2.2.8", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "nanoid": "^3.3.8", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA=="],
|
"@ai-sdk/provider": ["@ai-sdk/provider@2.0.0-beta.1", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Z8SPncMtS3RsoXITmT7NVwrAq6M44dmw0DoUOYJqNNtCu8iMWuxB8Nxsoqpa0uEEy9R1V1ZThJAXTYgjTUxl3w=="],
|
||||||
|
|
||||||
"@ai-sdk/react": ["@ai-sdk/react@1.2.12", "", { "dependencies": { "@ai-sdk/provider-utils": "2.2.8", "@ai-sdk/ui-utils": "1.2.11", "swr": "^2.2.5", "throttleit": "2.1.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "zod": "^3.23.8" }, "optionalPeers": ["zod"] }, "sha512-jK1IZZ22evPZoQW3vlkZ7wvjYGYF+tRBKXtrcolduIkQ/m/sOAVcVeVDUDvh1T91xCnWCdUGCPZg2avZ90mv3g=="],
|
"@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.0-beta.2", "", { "dependencies": { "@ai-sdk/provider": "2.0.0-beta.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.3", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.25.49" } }, "sha512-H4K+4weOVgWqrDDeAbQWoA4U5mN4WrQPHQFdH7ynQYcnhj/pzctU9Q6mGlR5ESMWxaXxazxlOblSITlXo9bahA=="],
|
||||||
|
|
||||||
"@ai-sdk/ui-utils": ["@ai-sdk/ui-utils@1.2.11", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w=="],
|
|
||||||
|
|
||||||
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
|
"@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
|
||||||
|
|
||||||
|
@ -462,12 +455,10 @@
|
||||||
|
|
||||||
"@types/babel__traverse": ["@types/babel__traverse@7.20.7", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng=="],
|
"@types/babel__traverse": ["@types/babel__traverse@7.20.7", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng=="],
|
||||||
|
|
||||||
"@types/bun": ["@types/bun@1.2.17", "", { "dependencies": { "bun-types": "1.2.17" } }, "sha512-l/BYs/JYt+cXA/0+wUhulYJB6a6p//GTPiJ7nV+QHa8iiId4HZmnu/3J/SowP5g0rTiERY2kfGKXEK5Ehltx4Q=="],
|
"@types/bun": ["@types/bun@1.2.18", "", { "dependencies": { "bun-types": "1.2.18" } }, "sha512-Xf6RaWVheyemaThV0kUfaAUvCNokFr+bH8Jxp+tTZfx7dAPA8z9ePnP9S9+Vspzuxxx9JRAXhnyccRj3GyCMdQ=="],
|
||||||
|
|
||||||
"@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
|
"@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
|
||||||
|
|
||||||
"@types/diff-match-patch": ["@types/diff-match-patch@1.0.36", "", {}, "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg=="],
|
|
||||||
|
|
||||||
"@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="],
|
"@types/estree": ["@types/estree@1.0.7", "", {}, "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="],
|
||||||
|
|
||||||
"@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="],
|
"@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="],
|
||||||
|
@ -492,6 +483,8 @@
|
||||||
|
|
||||||
"@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
|
"@types/node": ["@types/node@22.13.9", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw=="],
|
||||||
|
|
||||||
|
"@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="],
|
||||||
|
|
||||||
"@types/sax": ["@types/sax@1.2.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A=="],
|
"@types/sax": ["@types/sax@1.2.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A=="],
|
||||||
|
|
||||||
"@types/turndown": ["@types/turndown@5.0.5", "", {}, "sha512-TL2IgGgc7B5j78rIccBtlYAnkuv8nUQqhQc+DSYV5j9Be9XOcm/SKOVRuA47xAVI3680Tk9B1d8flK2GWT2+4w=="],
|
"@types/turndown": ["@types/turndown@5.0.5", "", {}, "sha512-TL2IgGgc7B5j78rIccBtlYAnkuv8nUQqhQc+DSYV5j9Be9XOcm/SKOVRuA47xAVI3680Tk9B1d8flK2GWT2+4w=="],
|
||||||
|
@ -512,7 +505,7 @@
|
||||||
|
|
||||||
"acorn-walk": ["acorn-walk@8.3.2", "", {}, "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A=="],
|
"acorn-walk": ["acorn-walk@8.3.2", "", {}, "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A=="],
|
||||||
|
|
||||||
"ai": ["ai@4.3.16", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "@ai-sdk/react": "1.2.12", "@ai-sdk/ui-utils": "1.2.11", "@opentelemetry/api": "1.9.0", "jsondiffpatch": "0.6.0" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "zod": "^3.23.8" }, "optionalPeers": ["react"] }, "sha512-KUDwlThJ5tr2Vw0A1ZkbDKNME3wzWhuVfAOwIvFUzl1TPVDFAXDFTXio3p+jaKneB+dKNCvFFlolYmmgHttG1g=="],
|
"ai": ["ai@5.0.0-beta.7", "", { "dependencies": { "@ai-sdk/gateway": "1.0.0-beta.3", "@ai-sdk/provider": "2.0.0-beta.1", "@ai-sdk/provider-utils": "3.0.0-beta.2", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.49" } }, "sha512-oC4KzUJCQPMB7v9rCqL/rVk2ogZvI6lYiXfKjzPYHwa1zIgy329qqRLmAd3mKEDTTG6By1r0zasQu7FKmG+4gw=="],
|
||||||
|
|
||||||
"ansi-align": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="],
|
"ansi-align": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="],
|
||||||
|
|
||||||
|
@ -602,7 +595,7 @@
|
||||||
|
|
||||||
"buffer": ["buffer@4.9.2", "", { "dependencies": { "base64-js": "^1.0.2", "ieee754": "^1.1.4", "isarray": "^1.0.0" } }, "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg=="],
|
"buffer": ["buffer@4.9.2", "", { "dependencies": { "base64-js": "^1.0.2", "ieee754": "^1.1.4", "isarray": "^1.0.0" } }, "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg=="],
|
||||||
|
|
||||||
"bun-types": ["bun-types@1.2.17", "", { "dependencies": { "@types/node": "*" } }, "sha512-ElC7ItwT3SCQwYZDYoAH+q6KT4Fxjl8DtZ6qDulUFBmXA8YB4xo+l54J9ZJN+k2pphfn9vk7kfubeSd5QfTVJQ=="],
|
"bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="],
|
||||||
|
|
||||||
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
|
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
|
||||||
|
|
||||||
|
@ -730,8 +723,6 @@
|
||||||
|
|
||||||
"diff": ["diff@8.0.2", "", {}, "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg=="],
|
"diff": ["diff@8.0.2", "", {}, "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg=="],
|
||||||
|
|
||||||
"diff-match-patch": ["diff-match-patch@1.0.5", "", {}, "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw=="],
|
|
||||||
|
|
||||||
"diff3": ["diff3@0.0.3", "", {}, "sha512-iSq8ngPOt0K53A6eVr4d5Kn6GNrM2nQZtC740pzIriHtn4pOQ2lyzEXQMBeVcWERN0ye7fhBsk9PbLLQOnUx/g=="],
|
"diff3": ["diff3@0.0.3", "", {}, "sha512-iSq8ngPOt0K53A6eVr4d5Kn6GNrM2nQZtC740pzIriHtn4pOQ2lyzEXQMBeVcWERN0ye7fhBsk9PbLLQOnUx/g=="],
|
||||||
|
|
||||||
"direction": ["direction@2.0.1", "", { "bin": { "direction": "cli.js" } }, "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA=="],
|
"direction": ["direction@2.0.1", "", { "bin": { "direction": "cli.js" } }, "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA=="],
|
||||||
|
@ -800,7 +791,7 @@
|
||||||
|
|
||||||
"eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="],
|
"eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="],
|
||||||
|
|
||||||
"eventsource-parser": ["eventsource-parser@3.0.2", "", {}, "sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA=="],
|
"eventsource-parser": ["eventsource-parser@3.0.3", "", {}, "sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA=="],
|
||||||
|
|
||||||
"exit-hook": ["exit-hook@2.2.1", "", {}, "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw=="],
|
"exit-hook": ["exit-hook@2.2.1", "", {}, "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw=="],
|
||||||
|
|
||||||
|
@ -1022,8 +1013,6 @@
|
||||||
|
|
||||||
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||||
|
|
||||||
"jsondiffpatch": ["jsondiffpatch@0.6.0", "", { "dependencies": { "@types/diff-match-patch": "^1.0.36", "chalk": "^5.3.0", "diff-match-patch": "^1.0.5" }, "bin": { "jsondiffpatch": "bin/jsondiffpatch.js" } }, "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ=="],
|
|
||||||
|
|
||||||
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
|
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
|
||||||
|
|
||||||
"klona": ["klona@2.0.6", "", {}, "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA=="],
|
"klona": ["klona@2.0.6", "", {}, "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA=="],
|
||||||
|
@ -1050,6 +1039,8 @@
|
||||||
|
|
||||||
"marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="],
|
"marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="],
|
||||||
|
|
||||||
|
"marked-shiki": ["marked-shiki@1.2.0", "", { "peerDependencies": { "marked": ">=7.0.0", "shiki": ">=1.0.0" } }, "sha512-N924hp8veE6Mc91g5/kCNVoTU7TkeJfB2G2XEWb+k1fVA0Bck2T0rVt93d39BlOYH6ohP4Q9BFlPk+UkblhXbg=="],
|
||||||
|
|
||||||
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||||
|
|
||||||
"mdast-util-definitions": ["mdast-util-definitions@6.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ=="],
|
"mdast-util-definitions": ["mdast-util-definitions@6.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ=="],
|
||||||
|
@ -1338,8 +1329,6 @@
|
||||||
|
|
||||||
"rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="],
|
"rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="],
|
||||||
|
|
||||||
"react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="],
|
|
||||||
|
|
||||||
"readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
|
"readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
|
||||||
|
|
||||||
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
|
||||||
|
@ -1476,23 +1465,23 @@
|
||||||
|
|
||||||
"split2": ["split2@3.2.2", "", { "dependencies": { "readable-stream": "^3.0.0" } }, "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg=="],
|
"split2": ["split2@3.2.2", "", { "dependencies": { "readable-stream": "^3.0.0" } }, "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg=="],
|
||||||
|
|
||||||
"sst": ["sst@3.17.6", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.17.6", "sst-darwin-x64": "3.17.6", "sst-linux-arm64": "3.17.6", "sst-linux-x64": "3.17.6", "sst-linux-x86": "3.17.6", "sst-win32-arm64": "3.17.6", "sst-win32-x64": "3.17.6", "sst-win32-x86": "3.17.6" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-p+AcqwfYQUdkxeRjCikQoTMviPCBiGoU7M0vcV6GDVmVis8hzhVw4EFfHTafZC+aWfy1Ke2UQi66vZlEVWuEqA=="],
|
"sst": ["sst@3.17.8", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.17.8", "sst-darwin-x64": "3.17.8", "sst-linux-arm64": "3.17.8", "sst-linux-x64": "3.17.8", "sst-linux-x86": "3.17.8", "sst-win32-arm64": "3.17.8", "sst-win32-x64": "3.17.8", "sst-win32-x86": "3.17.8" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-P/a9/ZsjtQRrTBerBMO1ODaVa5HVTmNLrQNJiYvu2Bgd0ov+vefQeHv6oima8HLlPwpDIPS2gxJk8BZrTZMfCA=="],
|
||||||
|
|
||||||
"sst-darwin-arm64": ["sst-darwin-arm64@3.17.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-6tb7KlcPR7PTi3ofQv8dX/n6Jf7pNP9VfrnYL4HBWnWrcYaZeJ5MWobILfIJ/y2jHgoqmg9e5C3266Eds0JQyw=="],
|
"sst-darwin-arm64": ["sst-darwin-arm64@3.17.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-50P6YRMnZVItZUfB0+NzqMww2mmm4vB3zhTVtWUtGoXeiw78g1AEnVlmS28gYXPHM1P987jTvR7EON9u9ig/Dg=="],
|
||||||
|
|
||||||
"sst-darwin-x64": ["sst-darwin-x64@3.17.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-lFakq6/EgTuBSjbl8Kry4pfgAPEIyn6o7ZkyRz3hz5331wUaX88yfjs3tL9JQ8Ey6jBUYxwhP/Q1n7fzIG046g=="],
|
"sst-darwin-x64": ["sst-darwin-x64@3.17.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-P0pnMHCmpkpcsxkWpilmeoD79LkbkoIcv6H0aeM9ArT/71/JBhvqH+HjMHSJCzni/9uR6er+nH5F+qol0UO6Bw=="],
|
||||||
|
|
||||||
"sst-linux-arm64": ["sst-linux-arm64@3.17.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-SdTxXMbTEdiwOqp37w31kXv97vHqSx3oK9h/76lKg7V9k5JxPJ6JMefPLhoKWwK0Zh6AndY2zo2oRoEv4SIaDw=="],
|
"sst-linux-arm64": ["sst-linux-arm64@3.17.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-vun54YA/UzprCu9p8BC4rMwFU5Cj9xrHAHYLYUp/yq4H0pfmBIiQM62nsfIKizRThe/TkBFy60EEi9myf6raYA=="],
|
||||||
|
|
||||||
"sst-linux-x64": ["sst-linux-x64@3.17.6", "", { "os": "linux", "cpu": "x64" }, "sha512-qneh7uWDiTUYx8X1Y3h2YVw3SJ0ybBBlRrVybIvCM09JqQ8+qq/XjKXGzA/3/EF0Jr7Ug8cARSn9CwxhdQGN7Q=="],
|
"sst-linux-x64": ["sst-linux-x64@3.17.8", "", { "os": "linux", "cpu": "x64" }, "sha512-HqByCaLE2gEJbM20P1QRd+GqDMAiieuU53FaZA1F+AGxQi+kR82NWjrPqFcMj4dMYg8w/TWXuV+G5+PwoUmpDw=="],
|
||||||
|
|
||||||
"sst-linux-x86": ["sst-linux-x86@3.17.6", "", { "os": "linux", "cpu": "none" }, "sha512-pU3D5OeqnmfxGqN31DxuwWnc1OayxhkErnITHhZ39D0MTiwbIgCapH26FuLW8B08/uxJWG8djUlOboCRhSBvWA=="],
|
"sst-linux-x86": ["sst-linux-x86@3.17.8", "", { "os": "linux", "cpu": "none" }, "sha512-bCd6QM3MejfSmdvg8I/k+aUJQIZEQJg023qmN78fv00vwlAtfECvY7tjT9E2m3LDp33pXrcRYbFOQzPu+tWFfA=="],
|
||||||
|
|
||||||
"sst-win32-arm64": ["sst-win32-arm64@3.17.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-Rr3RTYWAsH9sM9CbM/sAZCk7dB1OsSAljjJuuHMvdSAYW3RDpXEza0PBJGxnBID2eOrpswEchzMPL2d8LtL7oA=="],
|
"sst-win32-arm64": ["sst-win32-arm64@3.17.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-pilx0n8gm4aHJae/vNiqIwZkWF3tdwWzD/ON7hkytw+CVSZ0FXtyFW/yO/+2u3Yw0Kj0lSWPnUqYgm/eHPLwQA=="],
|
||||||
|
|
||||||
"sst-win32-x64": ["sst-win32-x64@3.17.6", "", { "os": "win32", "cpu": "x64" }, "sha512-yZ3roxwI0Wve9PFzdrrF1kfzCmIMFCCoa8qKeXY7LxCJ4QQIqHbCOccLK1Wv/MIU/mcZHWXTQVCLHw77uaa0GQ=="],
|
"sst-win32-x64": ["sst-win32-x64@3.17.8", "", { "os": "win32", "cpu": "x64" }, "sha512-Jb0FVRyiOtESudF1V8ucW65PuHrx/iOHUamIO0JnbujWNHZBTRPB2QHN1dbewgkueYDaCmyS8lvuIImLwYJnzQ=="],
|
||||||
|
|
||||||
"sst-win32-x86": ["sst-win32-x86@3.17.6", "", { "os": "win32", "cpu": "none" }, "sha512-zV7TJWPJN9PmIXr15iXFSs0tbGsa52oBR3+xiKrUj2qj9XsZe7HBFwskRnHyiFq0durZY9kk9ZtoVlpuUuzr1g=="],
|
"sst-win32-x86": ["sst-win32-x86@3.17.8", "", { "os": "win32", "cpu": "none" }, "sha512-oVmFa/PoElQmfnGJlB0w6rPXiYuldiagO6AbrLMT/6oAnWerLQ8Uhv9tJWfMh3xtPLImQLTjxDo1v0AIzEv9QA=="],
|
||||||
|
|
||||||
"stacktracey": ["stacktracey@2.1.8", "", { "dependencies": { "as-table": "^1.0.36", "get-source": "^2.0.12" } }, "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw=="],
|
"stacktracey": ["stacktracey@2.1.8", "", { "dependencies": { "as-table": "^1.0.36", "get-source": "^2.0.12" } }, "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw=="],
|
||||||
|
|
||||||
|
@ -1524,8 +1513,6 @@
|
||||||
|
|
||||||
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||||
|
|
||||||
"swr": ["swr@2.3.3", "", { "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A=="],
|
|
||||||
|
|
||||||
"tar-fs": ["tar-fs@3.0.9", "", { "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-fs": ["tar-fs@3.0.9", "", { "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": ["tar-stream@3.1.7", "", { "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ=="],
|
"tar-stream": ["tar-stream@3.1.7", "", { "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ=="],
|
||||||
|
@ -1534,8 +1521,6 @@
|
||||||
|
|
||||||
"thread-stream": ["thread-stream@0.15.2", "", { "dependencies": { "real-require": "^0.1.0" } }, "sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA=="],
|
"thread-stream": ["thread-stream@0.15.2", "", { "dependencies": { "real-require": "^0.1.0" } }, "sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA=="],
|
||||||
|
|
||||||
"throttleit": ["throttleit@2.1.0", "", {}, "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw=="],
|
|
||||||
|
|
||||||
"tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="],
|
"tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="],
|
||||||
|
|
||||||
"tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
|
"tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="],
|
||||||
|
@ -1620,8 +1605,6 @@
|
||||||
|
|
||||||
"url": ["url@0.10.3", "", { "dependencies": { "punycode": "1.3.2", "querystring": "0.2.0" } }, "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ=="],
|
"url": ["url@0.10.3", "", { "dependencies": { "punycode": "1.3.2", "querystring": "0.2.0" } }, "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ=="],
|
||||||
|
|
||||||
"use-sync-external-store": ["use-sync-external-store@1.5.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A=="],
|
|
||||||
|
|
||||||
"util": ["util@0.12.5", "", { "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", "is-generator-function": "^1.0.7", "is-typed-array": "^1.1.3", "which-typed-array": "^1.1.2" } }, "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA=="],
|
"util": ["util@0.12.5", "", { "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", "is-generator-function": "^1.0.7", "is-typed-array": "^1.1.3", "which-typed-array": "^1.1.2" } }, "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA=="],
|
||||||
|
|
||||||
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
||||||
|
@ -1698,7 +1681,7 @@
|
||||||
|
|
||||||
"youch": ["youch@3.3.4", "", { "dependencies": { "cookie": "^0.7.1", "mustache": "^4.2.0", "stacktracey": "^2.1.8" } }, "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg=="],
|
"youch": ["youch@3.3.4", "", { "dependencies": { "cookie": "^0.7.1", "mustache": "^4.2.0", "stacktracey": "^2.1.8" } }, "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg=="],
|
||||||
|
|
||||||
"zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="],
|
"zod": ["zod@3.25.49", "", {}, "sha512-JMMPMy9ZBk3XFEdbM3iL1brx4NUSejd6xr3ELrrGEfGb355gjhiAWtG3K5o+AViV/3ZfkIrCzXsZn6SbLwTR8Q=="],
|
||||||
|
|
||||||
"zod-openapi": ["zod-openapi@4.2.4", "", { "peerDependencies": { "zod": "^3.21.4" } }, "sha512-tsrQpbpqFCXqVXUzi3TPwFhuMtLN3oNZobOtYnK6/5VkXsNdnIgyNr4r8no4wmYluaxzN3F7iS+8xCW8BmMQ8g=="],
|
"zod-openapi": ["zod-openapi@4.2.4", "", { "peerDependencies": { "zod": "^3.21.4" } }, "sha512-tsrQpbpqFCXqVXUzi3TPwFhuMtLN3oNZobOtYnK6/5VkXsNdnIgyNr4r8no4wmYluaxzN3F7iS+8xCW8BmMQ8g=="],
|
||||||
|
|
||||||
|
@ -1710,12 +1693,22 @@
|
||||||
|
|
||||||
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
||||||
|
|
||||||
|
"@ai-sdk/amazon-bedrock/@ai-sdk/provider": ["@ai-sdk/provider@1.1.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg=="],
|
||||||
|
|
||||||
|
"@ai-sdk/amazon-bedrock/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@2.2.8", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "nanoid": "^3.3.8", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA=="],
|
||||||
|
|
||||||
"@ai-sdk/amazon-bedrock/aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="],
|
"@ai-sdk/amazon-bedrock/aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="],
|
||||||
|
|
||||||
|
"@ai-sdk/anthropic/@ai-sdk/provider": ["@ai-sdk/provider@1.1.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg=="],
|
||||||
|
|
||||||
|
"@ai-sdk/anthropic/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@2.2.8", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "nanoid": "^3.3.8", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA=="],
|
||||||
|
|
||||||
"@ampproject/remapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
|
"@ampproject/remapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
|
||||||
|
|
||||||
"@astrojs/mdx/@astrojs/markdown-remark": ["@astrojs/markdown-remark@6.3.2", "", { "dependencies": { "@astrojs/internal-helpers": "0.6.1", "@astrojs/prism": "3.3.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.1.0", "js-yaml": "^4.1.0", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "shiki": "^3.2.1", "smol-toml": "^1.3.1", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.1", "vfile": "^6.0.3" } }, "sha512-bO35JbWpVvyKRl7cmSJD822e8YA8ThR/YbUsciWNA7yTcqpIAL2hJDToWP5KcZBWxGT6IOdOkHSXARSNZc4l/Q=="],
|
"@astrojs/mdx/@astrojs/markdown-remark": ["@astrojs/markdown-remark@6.3.2", "", { "dependencies": { "@astrojs/internal-helpers": "0.6.1", "@astrojs/prism": "3.3.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.1.0", "js-yaml": "^4.1.0", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "shiki": "^3.2.1", "smol-toml": "^1.3.1", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.1", "vfile": "^6.0.3" } }, "sha512-bO35JbWpVvyKRl7cmSJD822e8YA8ThR/YbUsciWNA7yTcqpIAL2hJDToWP5KcZBWxGT6IOdOkHSXARSNZc4l/Q=="],
|
||||||
|
|
||||||
|
"@astrojs/sitemap/zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="],
|
||||||
|
|
||||||
"@aws-crypto/crc32/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
"@aws-crypto/crc32/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
"@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
"@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
||||||
|
@ -1734,6 +1727,8 @@
|
||||||
|
|
||||||
"@jridgewell/gen-mapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
|
"@jridgewell/gen-mapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
|
||||||
|
|
||||||
|
"@modelcontextprotocol/sdk/zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="],
|
||||||
|
|
||||||
"@openauthjs/openauth/@standard-schema/spec": ["@standard-schema/spec@1.0.0-beta.3", "", {}, "sha512-0ifF3BjA1E8SY9C+nUew8RefNOIq0cDlYALPty4rhUm8Rrl6tCM8hBT4bhGhx7I7iXD0uAgt50lgo8dD73ACMw=="],
|
"@openauthjs/openauth/@standard-schema/spec": ["@standard-schema/spec@1.0.0-beta.3", "", {}, "sha512-0ifF3BjA1E8SY9C+nUew8RefNOIq0cDlYALPty4rhUm8Rrl6tCM8hBT4bhGhx7I7iXD0uAgt50lgo8dD73ACMw=="],
|
||||||
|
|
||||||
"@openauthjs/openauth/aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="],
|
"@openauthjs/openauth/aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="],
|
||||||
|
@ -1770,10 +1765,14 @@
|
||||||
|
|
||||||
"astro/sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="],
|
"astro/sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="],
|
||||||
|
|
||||||
|
"astro/zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="],
|
||||||
|
|
||||||
"babel-plugin-jsx-dom-expressions/@babel/helper-module-imports": ["@babel/helper-module-imports@7.18.6", "", { "dependencies": { "@babel/types": "^7.18.6" } }, "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA=="],
|
"babel-plugin-jsx-dom-expressions/@babel/helper-module-imports": ["@babel/helper-module-imports@7.18.6", "", { "dependencies": { "@babel/types": "^7.18.6" } }, "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA=="],
|
||||||
|
|
||||||
"bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
|
"bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
|
||||||
|
|
||||||
|
"eventsource/eventsource-parser": ["eventsource-parser@3.0.2", "", {}, "sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA=="],
|
||||||
|
|
||||||
"express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
|
"express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
|
||||||
|
|
||||||
"get-source/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
"get-source/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||||
|
@ -1786,8 +1785,12 @@
|
||||||
|
|
||||||
"miniflare/sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="],
|
"miniflare/sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="],
|
||||||
|
|
||||||
|
"miniflare/zod": ["zod@3.22.3", "", {}, "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="],
|
||||||
|
|
||||||
"opencontrol/hono": ["hono@4.7.4", "", {}, "sha512-Pst8FuGqz3L7tFF+u9Pu70eI0xa5S3LPUmrNd5Jm8nTHze9FxLTK9Kaj5g/k4UcwuJSXTP65SyHOPLrffpcAJg=="],
|
"opencontrol/hono": ["hono@4.7.4", "", {}, "sha512-Pst8FuGqz3L7tFF+u9Pu70eI0xa5S3LPUmrNd5Jm8nTHze9FxLTK9Kaj5g/k4UcwuJSXTP65SyHOPLrffpcAJg=="],
|
||||||
|
|
||||||
|
"opencontrol/zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="],
|
||||||
|
|
||||||
"opencontrol/zod-to-json-schema": ["zod-to-json-schema@3.24.3", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-HIAfWdYIt1sssHfYZFCXp4rU1w2r8hVVXYIlmoa0r0gABLs5di3RCqPU5DDROogVz1pAdYBaz7HK5n9pSUNs3A=="],
|
"opencontrol/zod-to-json-schema": ["zod-to-json-schema@3.24.3", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-HIAfWdYIt1sssHfYZFCXp4rU1w2r8hVVXYIlmoa0r0gABLs5di3RCqPU5DDROogVz1pAdYBaz7HK5n9pSUNs3A=="],
|
||||||
|
|
||||||
"openid-client/jose": ["jose@4.15.9", "", {}, "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA=="],
|
"openid-client/jose": ["jose@4.15.9", "", {}, "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA=="],
|
||||||
|
|
|
@ -39,6 +39,8 @@ new sst.cloudflare.x.Astro("Web", {
|
||||||
domain,
|
domain,
|
||||||
path: "packages/web",
|
path: "packages/web",
|
||||||
environment: {
|
environment: {
|
||||||
|
// For astro config
|
||||||
|
SST_STAGE: $app.stage,
|
||||||
VITE_API_URL: api.url,
|
VITE_API_URL: api.url,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://opencode.ai/config.json",
|
"$schema": "https://opencode.ai/config.json",
|
||||||
|
"mcp": {
|
||||||
|
"weather": {
|
||||||
|
"type": "local",
|
||||||
|
"command": ["opencode", "x", "@h1deya/mcp-server-weather"]
|
||||||
|
}
|
||||||
|
},
|
||||||
"experimental": {
|
"experimental": {
|
||||||
"hook": {
|
"hook": {
|
||||||
"file_edited": {
|
"file_edited": {
|
||||||
|
|
12
package.json
12
package.json
|
@ -17,13 +17,13 @@
|
||||||
"catalog": {
|
"catalog": {
|
||||||
"typescript": "5.8.2",
|
"typescript": "5.8.2",
|
||||||
"@types/node": "22.13.9",
|
"@types/node": "22.13.9",
|
||||||
"zod": "3.24.2",
|
"zod": "3.25.49",
|
||||||
"ai": "4.3.16"
|
"ai": "5.0.0-beta.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"prettier": "3.5.3",
|
"prettier": "3.5.3",
|
||||||
"sst": "3.17.6"
|
"sst": "3.17.8"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -31,10 +31,8 @@
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"semi": false
|
"semi": false,
|
||||||
},
|
"printWidth": 120
|
||||||
"overrides": {
|
|
||||||
"zod": "3.24.2"
|
|
||||||
},
|
},
|
||||||
"trustedDependencies": [
|
"trustedDependencies": [
|
||||||
"esbuild",
|
"esbuild",
|
||||||
|
|
|
@ -38,10 +38,7 @@ export class SyncServer extends DurableObject<Env> {
|
||||||
|
|
||||||
async publish(key: string, content: any) {
|
async publish(key: string, content: any) {
|
||||||
const sessionID = await this.getSessionID()
|
const sessionID = await this.getSessionID()
|
||||||
if (
|
if (!key.startsWith(`session/info/${sessionID}`) && !key.startsWith(`session/message/${sessionID}/`))
|
||||||
!key.startsWith(`session/info/${sessionID}`) &&
|
|
||||||
!key.startsWith(`session/message/${sessionID}/`)
|
|
||||||
)
|
|
||||||
return new Response("Error: Invalid key", { status: 400 })
|
return new Response("Error: Invalid key", { status: 400 })
|
||||||
|
|
||||||
// store message
|
// store message
|
||||||
|
@ -184,8 +181,7 @@ export default {
|
||||||
}
|
}
|
||||||
const id = url.searchParams.get("id")
|
const id = url.searchParams.get("id")
|
||||||
console.log("share_poll", id)
|
console.log("share_poll", id)
|
||||||
if (!id)
|
if (!id) return new Response("Error: Share ID is required", { status: 400 })
|
||||||
return new Response("Error: Share ID is required", { status: 400 })
|
|
||||||
const stub = env.SYNC_SERVER.get(env.SYNC_SERVER.idFromName(id))
|
const stub = env.SYNC_SERVER.get(env.SYNC_SERVER.idFromName(id))
|
||||||
return stub.fetch(request)
|
return stub.fetch(request)
|
||||||
}
|
}
|
||||||
|
@ -193,8 +189,7 @@ export default {
|
||||||
if (request.method === "GET" && method === "share_data") {
|
if (request.method === "GET" && method === "share_data") {
|
||||||
const id = url.searchParams.get("id")
|
const id = url.searchParams.get("id")
|
||||||
console.log("share_data", id)
|
console.log("share_data", id)
|
||||||
if (!id)
|
if (!id) return new Response("Error: Share ID is required", { status: 400 })
|
||||||
return new Response("Error: Share ID is required", { status: 400 })
|
|
||||||
const stub = env.SYNC_SERVER.get(env.SYNC_SERVER.idFromName(id))
|
const stub = env.SYNC_SERVER.get(env.SYNC_SERVER.idFromName(id))
|
||||||
const data = await stub.getData()
|
const data = await stub.getData()
|
||||||
|
|
||||||
|
|
1
packages/opencode/.gitignore
vendored
1
packages/opencode/.gitignore
vendored
|
@ -1,4 +1,3 @@
|
||||||
node_modules
|
|
||||||
research
|
research
|
||||||
dist
|
dist
|
||||||
gen
|
gen
|
||||||
|
|
|
@ -57,8 +57,7 @@ for (const [os, arch] of targets) {
|
||||||
2,
|
2,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if (!dry)
|
if (!dry) await $`cd dist/${name} && bun publish --access public --tag ${npmTag}`
|
||||||
await $`cd dist/${name} && bun publish --access public --tag ${npmTag}`
|
|
||||||
optionalDependencies[name] = version
|
optionalDependencies[name] = version
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,8 +81,7 @@ await Bun.file(`./dist/${pkg.name}/package.json`).write(
|
||||||
2,
|
2,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if (!dry)
|
if (!dry) await $`cd ./dist/${pkg.name} && bun publish --access public --tag ${npmTag}`
|
||||||
await $`cd ./dist/${pkg.name} && bun publish --access public --tag ${npmTag}`
|
|
||||||
|
|
||||||
if (!snapshot) {
|
if (!snapshot) {
|
||||||
// Github Release
|
// Github Release
|
||||||
|
@ -91,15 +89,11 @@ if (!snapshot) {
|
||||||
await $`cd dist/${key}/bin && zip -r ../../${key}.zip *`
|
await $`cd dist/${key}/bin && zip -r ../../${key}.zip *`
|
||||||
}
|
}
|
||||||
|
|
||||||
const previous = await fetch(
|
const previous = await fetch("https://api.github.com/repos/sst/opencode/releases/latest")
|
||||||
"https://api.github.com/repos/sst/opencode/releases/latest",
|
|
||||||
)
|
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => data.tag_name)
|
.then((data) => data.tag_name)
|
||||||
|
|
||||||
const commits = await fetch(
|
const commits = await fetch(`https://api.github.com/repos/sst/opencode/compare/${previous}...HEAD`)
|
||||||
`https://api.github.com/repos/sst/opencode/compare/${previous}...HEAD`,
|
|
||||||
)
|
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => data.commits || [])
|
.then((data) => data.commits || [])
|
||||||
|
|
||||||
|
@ -117,26 +111,13 @@ if (!snapshot) {
|
||||||
})
|
})
|
||||||
.join("\n")
|
.join("\n")
|
||||||
|
|
||||||
if (!dry)
|
if (!dry) await $`gh release create v${version} --title "v${version}" --notes ${notes} ./dist/*.zip`
|
||||||
await $`gh release create v${version} --title "v${version}" --notes ${notes} ./dist/*.zip`
|
|
||||||
|
|
||||||
// Calculate SHA values
|
// Calculate SHA values
|
||||||
const arm64Sha =
|
const arm64Sha = await $`sha256sum ./dist/opencode-linux-arm64.zip | cut -d' ' -f1`.text().then((x) => x.trim())
|
||||||
await $`sha256sum ./dist/opencode-linux-arm64.zip | cut -d' ' -f1`
|
const x64Sha = await $`sha256sum ./dist/opencode-linux-x64.zip | cut -d' ' -f1`.text().then((x) => x.trim())
|
||||||
.text()
|
const macX64Sha = await $`sha256sum ./dist/opencode-darwin-x64.zip | cut -d' ' -f1`.text().then((x) => x.trim())
|
||||||
.then((x) => x.trim())
|
const macArm64Sha = await $`sha256sum ./dist/opencode-darwin-arm64.zip | cut -d' ' -f1`.text().then((x) => x.trim())
|
||||||
const x64Sha =
|
|
||||||
await $`sha256sum ./dist/opencode-linux-x64.zip | cut -d' ' -f1`
|
|
||||||
.text()
|
|
||||||
.then((x) => x.trim())
|
|
||||||
const macX64Sha =
|
|
||||||
await $`sha256sum ./dist/opencode-darwin-x64.zip | cut -d' ' -f1`
|
|
||||||
.text()
|
|
||||||
.then((x) => x.trim())
|
|
||||||
const macArm64Sha =
|
|
||||||
await $`sha256sum ./dist/opencode-darwin-arm64.zip | cut -d' ' -f1`
|
|
||||||
.text()
|
|
||||||
.then((x) => x.trim())
|
|
||||||
|
|
||||||
// AUR package
|
// AUR package
|
||||||
const pkgbuild = [
|
const pkgbuild = [
|
||||||
|
@ -170,9 +151,7 @@ if (!snapshot) {
|
||||||
for (const pkg of ["opencode", "opencode-bin"]) {
|
for (const pkg of ["opencode", "opencode-bin"]) {
|
||||||
await $`rm -rf ./dist/aur-${pkg}`
|
await $`rm -rf ./dist/aur-${pkg}`
|
||||||
await $`git clone ssh://aur@aur.archlinux.org/${pkg}.git ./dist/aur-${pkg}`
|
await $`git clone ssh://aur@aur.archlinux.org/${pkg}.git ./dist/aur-${pkg}`
|
||||||
await Bun.file(`./dist/aur-${pkg}/PKGBUILD`).write(
|
await Bun.file(`./dist/aur-${pkg}/PKGBUILD`).write(pkgbuild.replace("${pkg}", pkg))
|
||||||
pkgbuild.replace("${pkg}", pkg),
|
|
||||||
)
|
|
||||||
await $`cd ./dist/aur-${pkg} && makepkg --printsrcinfo > .SRCINFO`
|
await $`cd ./dist/aur-${pkg} && makepkg --printsrcinfo > .SRCINFO`
|
||||||
await $`cd ./dist/aur-${pkg} && git add PKGBUILD .SRCINFO`
|
await $`cd ./dist/aur-${pkg} && git add PKGBUILD .SRCINFO`
|
||||||
await $`cd ./dist/aur-${pkg} && git commit -m "Update to v${version}"`
|
await $`cd ./dist/aur-${pkg} && git commit -m "Update to v${version}"`
|
||||||
|
|
|
@ -45,23 +45,14 @@ export namespace App {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const provideExisting = ctx.provide
|
export const provideExisting = ctx.provide
|
||||||
export async function provide<T>(
|
export async function provide<T>(input: Input, cb: (app: App.Info) => Promise<T>) {
|
||||||
input: Input,
|
|
||||||
cb: (app: App.Info) => Promise<T>,
|
|
||||||
) {
|
|
||||||
log.info("creating", {
|
log.info("creating", {
|
||||||
cwd: input.cwd,
|
cwd: input.cwd,
|
||||||
})
|
})
|
||||||
const git = await Filesystem.findUp(".git", input.cwd).then(([x]) =>
|
const git = await Filesystem.findUp(".git", input.cwd).then(([x]) => (x ? path.dirname(x) : undefined))
|
||||||
x ? path.dirname(x) : undefined,
|
|
||||||
)
|
|
||||||
log.info("git", { git })
|
log.info("git", { git })
|
||||||
|
|
||||||
const data = path.join(
|
const data = path.join(Global.Path.data, "project", git ? directory(git) : "global")
|
||||||
Global.Path.data,
|
|
||||||
"project",
|
|
||||||
git ? directory(git) : "global",
|
|
||||||
)
|
|
||||||
const stateFile = Bun.file(path.join(data, APP_JSON))
|
const stateFile = Bun.file(path.join(data, APP_JSON))
|
||||||
const state = (await stateFile.json().catch(() => ({}))) as {
|
const state = (await stateFile.json().catch(() => ({}))) as {
|
||||||
initialized: number
|
initialized: number
|
||||||
|
|
|
@ -10,14 +10,8 @@ export namespace AuthAnthropic {
|
||||||
url.searchParams.set("code", "true")
|
url.searchParams.set("code", "true")
|
||||||
url.searchParams.set("client_id", CLIENT_ID)
|
url.searchParams.set("client_id", CLIENT_ID)
|
||||||
url.searchParams.set("response_type", "code")
|
url.searchParams.set("response_type", "code")
|
||||||
url.searchParams.set(
|
url.searchParams.set("redirect_uri", "https://console.anthropic.com/oauth/code/callback")
|
||||||
"redirect_uri",
|
url.searchParams.set("scope", "org:create_api_key user:profile user:inference")
|
||||||
"https://console.anthropic.com/oauth/code/callback",
|
|
||||||
)
|
|
||||||
url.searchParams.set(
|
|
||||||
"scope",
|
|
||||||
"org:create_api_key user:profile user:inference",
|
|
||||||
)
|
|
||||||
url.searchParams.set("code_challenge", pkce.challenge)
|
url.searchParams.set("code_challenge", pkce.challenge)
|
||||||
url.searchParams.set("code_challenge_method", "S256")
|
url.searchParams.set("code_challenge_method", "S256")
|
||||||
url.searchParams.set("state", pkce.verifier)
|
url.searchParams.set("state", pkce.verifier)
|
||||||
|
@ -57,20 +51,17 @@ export namespace AuthAnthropic {
|
||||||
const info = await Auth.get("anthropic")
|
const info = await Auth.get("anthropic")
|
||||||
if (!info || info.type !== "oauth") return
|
if (!info || info.type !== "oauth") return
|
||||||
if (info.access && info.expires > Date.now()) return info.access
|
if (info.access && info.expires > Date.now()) return info.access
|
||||||
const response = await fetch(
|
const response = await fetch("https://console.anthropic.com/v1/oauth/token", {
|
||||||
"https://console.anthropic.com/v1/oauth/token",
|
method: "POST",
|
||||||
{
|
headers: {
|
||||||
method: "POST",
|
"Content-Type": "application/json",
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
grant_type: "refresh_token",
|
|
||||||
refresh_token: info.refresh,
|
|
||||||
client_id: CLIENT_ID,
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
)
|
body: JSON.stringify({
|
||||||
|
grant_type: "refresh_token",
|
||||||
|
refresh_token: info.refresh,
|
||||||
|
client_id: CLIENT_ID,
|
||||||
|
}),
|
||||||
|
})
|
||||||
if (!response.ok) return
|
if (!response.ok) return
|
||||||
const json = await response.json()
|
const json = await response.json()
|
||||||
await Auth.set("anthropic", {
|
await Auth.set("anthropic", {
|
||||||
|
|
|
@ -4,9 +4,7 @@ import path from "path"
|
||||||
|
|
||||||
export const AuthCopilot = lazy(async () => {
|
export const AuthCopilot = lazy(async () => {
|
||||||
const file = Bun.file(path.join(Global.Path.state, "plugin", "copilot.ts"))
|
const file = Bun.file(path.join(Global.Path.state, "plugin", "copilot.ts"))
|
||||||
const response = fetch(
|
const response = fetch("https://raw.githubusercontent.com/sst/opencode-github-copilot/refs/heads/main/auth.ts")
|
||||||
"https://raw.githubusercontent.com/sst/opencode-github-copilot/refs/heads/main/auth.ts",
|
|
||||||
)
|
|
||||||
.then((x) => Bun.write(file, x))
|
.then((x) => Bun.write(file, x))
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
|
|
||||||
|
|
|
@ -122,10 +122,7 @@ export namespace AuthGithubCopilot {
|
||||||
return tokenData.token
|
return tokenData.token
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DeviceCodeError = NamedError.create(
|
export const DeviceCodeError = NamedError.create("DeviceCodeError", z.object({}))
|
||||||
"DeviceCodeError",
|
|
||||||
z.object({}),
|
|
||||||
)
|
|
||||||
|
|
||||||
export const TokenExchangeError = NamedError.create(
|
export const TokenExchangeError = NamedError.create(
|
||||||
"TokenExchangeError",
|
"TokenExchangeError",
|
||||||
|
|
|
@ -8,10 +8,7 @@ import { readableStreamToText } from "bun"
|
||||||
export namespace BunProc {
|
export namespace BunProc {
|
||||||
const log = Log.create({ service: "bun" })
|
const log = Log.create({ service: "bun" })
|
||||||
|
|
||||||
export async function run(
|
export async function run(cmd: string[], options?: Bun.SpawnOptions.OptionsObject<any, any, any>) {
|
||||||
cmd: string[],
|
|
||||||
options?: Bun.SpawnOptions.OptionsObject<any, any, any>,
|
|
||||||
) {
|
|
||||||
log.info("running", {
|
log.info("running", {
|
||||||
cmd: [which(), ...cmd],
|
cmd: [which(), ...cmd],
|
||||||
...options,
|
...options,
|
||||||
|
@ -26,9 +23,17 @@ export namespace BunProc {
|
||||||
BUN_BE_BUN: "1",
|
BUN_BE_BUN: "1",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const code = await result.exited;
|
const code = await result.exited
|
||||||
const stdout = result.stdout ? typeof result.stdout === "number" ? result.stdout : await readableStreamToText(result.stdout) : undefined
|
const stdout = result.stdout
|
||||||
const stderr = result.stderr ? typeof result.stderr === "number" ? result.stderr : await readableStreamToText(result.stderr) : undefined
|
? typeof result.stdout === "number"
|
||||||
|
? result.stdout
|
||||||
|
: await readableStreamToText(result.stdout)
|
||||||
|
: undefined
|
||||||
|
const stderr = result.stderr
|
||||||
|
? typeof result.stderr === "number"
|
||||||
|
? result.stderr
|
||||||
|
: await readableStreamToText(result.stderr)
|
||||||
|
: undefined
|
||||||
log.info("done", {
|
log.info("done", {
|
||||||
code,
|
code,
|
||||||
stdout,
|
stdout,
|
||||||
|
@ -61,7 +66,7 @@ export namespace BunProc {
|
||||||
if (parsed.dependencies[pkg] === version) return mod
|
if (parsed.dependencies[pkg] === version) return mod
|
||||||
parsed.dependencies[pkg] = version
|
parsed.dependencies[pkg] = version
|
||||||
await Bun.write(pkgjson, JSON.stringify(parsed, null, 2))
|
await Bun.write(pkgjson, JSON.stringify(parsed, null, 2))
|
||||||
await BunProc.run(["install", "--registry=https://registry.npmjs.org"], {
|
await BunProc.run(["install", "--cwd", Global.Path.cache, "--registry=https://registry.npmjs.org"], {
|
||||||
cwd: Global.Path.cache,
|
cwd: Global.Path.cache,
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
throw new InstallFailedError(
|
throw new InstallFailedError(
|
||||||
|
|
|
@ -18,10 +18,7 @@ export namespace Bus {
|
||||||
|
|
||||||
const registry = new Map<string, EventDefinition>()
|
const registry = new Map<string, EventDefinition>()
|
||||||
|
|
||||||
export function event<Type extends string, Properties extends ZodType>(
|
export function event<Type extends string, Properties extends ZodType>(type: Type, properties: Properties) {
|
||||||
type: Type,
|
|
||||||
properties: Properties,
|
|
||||||
) {
|
|
||||||
const result = {
|
const result = {
|
||||||
type,
|
type,
|
||||||
properties,
|
properties,
|
||||||
|
@ -72,10 +69,7 @@ export namespace Bus {
|
||||||
|
|
||||||
export function subscribe<Definition extends EventDefinition>(
|
export function subscribe<Definition extends EventDefinition>(
|
||||||
def: Definition,
|
def: Definition,
|
||||||
callback: (event: {
|
callback: (event: { type: Definition["type"]; properties: z.infer<Definition["properties"]> }) => void,
|
||||||
type: Definition["type"]
|
|
||||||
properties: z.infer<Definition["properties"]>
|
|
||||||
}) => void,
|
|
||||||
) {
|
) {
|
||||||
return raw(def.type, callback)
|
return raw(def.type, callback)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,7 @@ import { Format } from "../format"
|
||||||
import { LSP } from "../lsp"
|
import { LSP } from "../lsp"
|
||||||
import { Share } from "../share/share"
|
import { Share } from "../share/share"
|
||||||
|
|
||||||
export async function bootstrap<T>(
|
export async function bootstrap<T>(input: App.Input, cb: (app: App.Info) => Promise<T>) {
|
||||||
input: App.Input,
|
|
||||||
cb: (app: App.Info) => Promise<T>,
|
|
||||||
) {
|
|
||||||
return App.provide(input, async (app) => {
|
return App.provide(input, async (app) => {
|
||||||
Share.init()
|
Share.init()
|
||||||
Format.init()
|
Format.init()
|
||||||
|
|
|
@ -15,11 +15,7 @@ export const AuthCommand = cmd({
|
||||||
command: "auth",
|
command: "auth",
|
||||||
describe: "manage credentials",
|
describe: "manage credentials",
|
||||||
builder: (yargs) =>
|
builder: (yargs) =>
|
||||||
yargs
|
yargs.command(AuthLoginCommand).command(AuthLogoutCommand).command(AuthListCommand).demandCommand(),
|
||||||
.command(AuthLoginCommand)
|
|
||||||
.command(AuthLogoutCommand)
|
|
||||||
.command(AuthListCommand)
|
|
||||||
.demandCommand(),
|
|
||||||
async handler() {},
|
async handler() {},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -31,9 +27,7 @@ export const AuthListCommand = cmd({
|
||||||
UI.empty()
|
UI.empty()
|
||||||
const authPath = path.join(Global.Path.data, "auth.json")
|
const authPath = path.join(Global.Path.data, "auth.json")
|
||||||
const homedir = os.homedir()
|
const homedir = os.homedir()
|
||||||
const displayPath = authPath.startsWith(homedir)
|
const displayPath = authPath.startsWith(homedir) ? authPath.replace(homedir, "~") : authPath
|
||||||
? authPath.replace(homedir, "~")
|
|
||||||
: authPath
|
|
||||||
prompts.intro(`Credentials ${UI.Style.TEXT_DIM}${displayPath}`)
|
prompts.intro(`Credentials ${UI.Style.TEXT_DIM}${displayPath}`)
|
||||||
const results = await Auth.all().then((x) => Object.entries(x))
|
const results = await Auth.all().then((x) => Object.entries(x))
|
||||||
const database = await ModelsDev.get()
|
const database = await ModelsDev.get()
|
||||||
|
@ -114,8 +108,7 @@ export const AuthLoginCommand = cmd({
|
||||||
if (provider === "other") {
|
if (provider === "other") {
|
||||||
provider = await prompts.text({
|
provider = await prompts.text({
|
||||||
message: "Enter provider id",
|
message: "Enter provider id",
|
||||||
validate: (x) =>
|
validate: (x) => (x.match(/^[a-z-]+$/) ? undefined : "a-z and hyphens only"),
|
||||||
x.match(/^[a-z-]+$/) ? undefined : "a-z and hyphens only",
|
|
||||||
})
|
})
|
||||||
if (prompts.isCancel(provider)) throw new UI.CancelledError()
|
if (prompts.isCancel(provider)) throw new UI.CancelledError()
|
||||||
provider = provider.replace(/^@ai-sdk\//, "")
|
provider = provider.replace(/^@ai-sdk\//, "")
|
||||||
|
@ -186,17 +179,13 @@ export const AuthLoginCommand = cmd({
|
||||||
await new Promise((resolve) => setTimeout(resolve, 10))
|
await new Promise((resolve) => setTimeout(resolve, 10))
|
||||||
const deviceInfo = await copilot.authorize()
|
const deviceInfo = await copilot.authorize()
|
||||||
|
|
||||||
prompts.note(
|
prompts.note(`Please visit: ${deviceInfo.verification}\nEnter code: ${deviceInfo.user}`)
|
||||||
`Please visit: ${deviceInfo.verification}\nEnter code: ${deviceInfo.user}`,
|
|
||||||
)
|
|
||||||
|
|
||||||
const spinner = prompts.spinner()
|
const spinner = prompts.spinner()
|
||||||
spinner.start("Waiting for authorization...")
|
spinner.start("Waiting for authorization...")
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
await new Promise((resolve) =>
|
await new Promise((resolve) => setTimeout(resolve, deviceInfo.interval * 1000))
|
||||||
setTimeout(resolve, deviceInfo.interval * 1000),
|
|
||||||
)
|
|
||||||
const response = await copilot.poll(deviceInfo.device)
|
const response = await copilot.poll(deviceInfo.device)
|
||||||
if (response.status === "pending") continue
|
if (response.status === "pending") continue
|
||||||
if (response.status === "success") {
|
if (response.status === "success") {
|
||||||
|
@ -248,12 +237,7 @@ export const AuthLogoutCommand = cmd({
|
||||||
const providerID = await prompts.select({
|
const providerID = await prompts.select({
|
||||||
message: "Select provider",
|
message: "Select provider",
|
||||||
options: credentials.map(([key, value]) => ({
|
options: credentials.map(([key, value]) => ({
|
||||||
label:
|
label: (database[key]?.name || key) + UI.Style.TEXT_DIM + " (" + value.type + ")",
|
||||||
(database[key]?.name || key) +
|
|
||||||
UI.Style.TEXT_DIM +
|
|
||||||
" (" +
|
|
||||||
value.type +
|
|
||||||
")",
|
|
||||||
value: key,
|
value: key,
|
||||||
})),
|
})),
|
||||||
})
|
})
|
||||||
|
|
|
@ -31,7 +31,6 @@ const FileStatusCommand = cmd({
|
||||||
|
|
||||||
export const FileCommand = cmd({
|
export const FileCommand = cmd({
|
||||||
command: "file",
|
command: "file",
|
||||||
builder: (yargs) =>
|
builder: (yargs) => yargs.command(FileReadCommand).command(FileStatusCommand).demandCommand(),
|
||||||
yargs.command(FileReadCommand).command(FileStatusCommand).demandCommand(),
|
|
||||||
async handler() {},
|
async handler() {},
|
||||||
})
|
})
|
||||||
|
|
|
@ -17,9 +17,7 @@ export const DebugCommand = cmd({
|
||||||
command: "wait",
|
command: "wait",
|
||||||
async handler() {
|
async handler() {
|
||||||
await bootstrap({ cwd: process.cwd() }, async () => {
|
await bootstrap({ cwd: process.cwd() }, async () => {
|
||||||
await new Promise((resolve) =>
|
await new Promise((resolve) => setTimeout(resolve, 1_000 * 60 * 60 * 24))
|
||||||
setTimeout(resolve, 1_000 * 60 * 60 * 24),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -5,15 +5,13 @@ import { Log } from "../../../util/log"
|
||||||
|
|
||||||
export const LSPCommand = cmd({
|
export const LSPCommand = cmd({
|
||||||
command: "lsp",
|
command: "lsp",
|
||||||
builder: (yargs) =>
|
builder: (yargs) => yargs.command(DiagnosticsCommand).command(SymbolsCommand).demandCommand(),
|
||||||
yargs.command(DiagnosticsCommand).command(SymbolsCommand).demandCommand(),
|
|
||||||
async handler() {},
|
async handler() {},
|
||||||
})
|
})
|
||||||
|
|
||||||
const DiagnosticsCommand = cmd({
|
const DiagnosticsCommand = cmd({
|
||||||
command: "diagnostics <file>",
|
command: "diagnostics <file>",
|
||||||
builder: (yargs) =>
|
builder: (yargs) => yargs.positional("file", { type: "string", demandOption: true }),
|
||||||
yargs.positional("file", { type: "string", demandOption: true }),
|
|
||||||
async handler(args) {
|
async handler(args) {
|
||||||
await bootstrap({ cwd: process.cwd() }, async () => {
|
await bootstrap({ cwd: process.cwd() }, async () => {
|
||||||
await LSP.touchFile(args.file, true)
|
await LSP.touchFile(args.file, true)
|
||||||
|
@ -24,8 +22,7 @@ const DiagnosticsCommand = cmd({
|
||||||
|
|
||||||
export const SymbolsCommand = cmd({
|
export const SymbolsCommand = cmd({
|
||||||
command: "symbols <query>",
|
command: "symbols <query>",
|
||||||
builder: (yargs) =>
|
builder: (yargs) => yargs.positional("query", { type: "string", demandOption: true }),
|
||||||
yargs.positional("query", { type: "string", demandOption: true }),
|
|
||||||
async handler(args) {
|
async handler(args) {
|
||||||
await bootstrap({ cwd: process.cwd() }, async () => {
|
await bootstrap({ cwd: process.cwd() }, async () => {
|
||||||
await LSP.touchFile("./src/index.ts", true)
|
await LSP.touchFile("./src/index.ts", true)
|
||||||
|
|
|
@ -5,12 +5,7 @@ import { cmd } from "../cmd"
|
||||||
|
|
||||||
export const RipgrepCommand = cmd({
|
export const RipgrepCommand = cmd({
|
||||||
command: "rg",
|
command: "rg",
|
||||||
builder: (yargs) =>
|
builder: (yargs) => yargs.command(TreeCommand).command(FilesCommand).command(SearchCommand).demandCommand(),
|
||||||
yargs
|
|
||||||
.command(TreeCommand)
|
|
||||||
.command(FilesCommand)
|
|
||||||
.command(SearchCommand)
|
|
||||||
.demandCommand(),
|
|
||||||
async handler() {},
|
async handler() {},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,7 @@ import { cmd } from "../cmd"
|
||||||
|
|
||||||
export const SnapshotCommand = cmd({
|
export const SnapshotCommand = cmd({
|
||||||
command: "snapshot",
|
command: "snapshot",
|
||||||
builder: (yargs) =>
|
builder: (yargs) => yargs.command(SnapshotCreateCommand).command(SnapshotRestoreCommand).demandCommand(),
|
||||||
yargs
|
|
||||||
.command(SnapshotCreateCommand)
|
|
||||||
.command(SnapshotRestoreCommand)
|
|
||||||
.demandCommand(),
|
|
||||||
async handler() {},
|
async handler() {},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,6 @@ export const GenerateCommand = {
|
||||||
const dir = "gen"
|
const dir = "gen"
|
||||||
await fs.rmdir(dir, { recursive: true }).catch(() => {})
|
await fs.rmdir(dir, { recursive: true }).catch(() => {})
|
||||||
await fs.mkdir(dir, { recursive: true })
|
await fs.mkdir(dir, { recursive: true })
|
||||||
await Bun.write(
|
await Bun.write(path.join(dir, "openapi.json"), JSON.stringify(specs, null, 2))
|
||||||
path.join(dir, "openapi.json"),
|
|
||||||
JSON.stringify(specs, null, 2),
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
} satisfies CommandModule
|
} satisfies CommandModule
|
||||||
|
|
|
@ -2,12 +2,12 @@ import type { Argv } from "yargs"
|
||||||
import { Bus } from "../../bus"
|
import { Bus } from "../../bus"
|
||||||
import { Provider } from "../../provider/provider"
|
import { Provider } from "../../provider/provider"
|
||||||
import { Session } from "../../session"
|
import { Session } from "../../session"
|
||||||
import { Message } from "../../session/message"
|
|
||||||
import { UI } from "../ui"
|
import { UI } from "../ui"
|
||||||
import { cmd } from "./cmd"
|
import { cmd } from "./cmd"
|
||||||
import { Flag } from "../../flag/flag"
|
import { Flag } from "../../flag/flag"
|
||||||
import { Config } from "../../config/config"
|
import { Config } from "../../config/config"
|
||||||
import { bootstrap } from "../bootstrap"
|
import { bootstrap } from "../bootstrap"
|
||||||
|
import { MessageV2 } from "../../session/message-v2"
|
||||||
|
|
||||||
const TOOL: Record<string, [string, string]> = {
|
const TOOL: Record<string, [string, string]> = {
|
||||||
todowrite: ["Todo", UI.Style.TEXT_WARNING_BOLD],
|
todowrite: ["Todo", UI.Style.TEXT_WARNING_BOLD],
|
||||||
|
@ -54,7 +54,10 @@ export const RunCommand = cmd({
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
handler: async (args) => {
|
handler: async (args) => {
|
||||||
const message = args.message.join(" ")
|
let message = args.message.join(" ")
|
||||||
|
|
||||||
|
if (!process.stdin.isTTY) message += "\n" + (await Bun.stdin.text())
|
||||||
|
|
||||||
await bootstrap({ cwd: process.cwd() }, async () => {
|
await bootstrap({ cwd: process.cwd() }, async () => {
|
||||||
const session = await (async () => {
|
const session = await (async () => {
|
||||||
if (args.continue) {
|
if (args.continue) {
|
||||||
|
@ -78,27 +81,19 @@ export const RunCommand = cmd({
|
||||||
UI.empty()
|
UI.empty()
|
||||||
UI.println(UI.logo())
|
UI.println(UI.logo())
|
||||||
UI.empty()
|
UI.empty()
|
||||||
UI.println(UI.Style.TEXT_NORMAL_BOLD + "> ", message)
|
const displayMessage = message.length > 300 ? message.slice(0, 300) + "..." : message
|
||||||
|
UI.println(UI.Style.TEXT_NORMAL_BOLD + "> ", displayMessage)
|
||||||
UI.empty()
|
UI.empty()
|
||||||
|
|
||||||
const cfg = await Config.get()
|
const cfg = await Config.get()
|
||||||
if (cfg.autoshare || Flag.OPENCODE_AUTO_SHARE || args.share) {
|
if (cfg.autoshare || Flag.OPENCODE_AUTO_SHARE || args.share) {
|
||||||
await Session.share(session.id)
|
await Session.share(session.id)
|
||||||
UI.println(
|
UI.println(UI.Style.TEXT_INFO_BOLD + "~ https://opencode.ai/s/" + session.id.slice(-8))
|
||||||
UI.Style.TEXT_INFO_BOLD +
|
|
||||||
"~ https://opencode.ai/s/" +
|
|
||||||
session.id.slice(-8),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
UI.empty()
|
UI.empty()
|
||||||
|
|
||||||
const { providerID, modelID } = args.model
|
const { providerID, modelID } = args.model ? Provider.parseModel(args.model) : await Provider.defaultModel()
|
||||||
? Provider.parseModel(args.model)
|
UI.println(UI.Style.TEXT_NORMAL_BOLD + "@ ", UI.Style.TEXT_NORMAL + `${providerID}/${modelID}`)
|
||||||
: await Provider.defaultModel()
|
|
||||||
UI.println(
|
|
||||||
UI.Style.TEXT_NORMAL_BOLD + "@ ",
|
|
||||||
UI.Style.TEXT_NORMAL + `${providerID}/${modelID}`,
|
|
||||||
)
|
|
||||||
UI.empty()
|
UI.empty()
|
||||||
|
|
||||||
function printEvent(color: string, type: string, title: string) {
|
function printEvent(color: string, type: string, title: string) {
|
||||||
|
@ -110,24 +105,13 @@ export const RunCommand = cmd({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Bus.subscribe(Message.Event.PartUpdated, async (evt) => {
|
Bus.subscribe(MessageV2.Event.PartUpdated, async (evt) => {
|
||||||
if (evt.properties.sessionID !== session.id) return
|
if (evt.properties.sessionID !== session.id) return
|
||||||
const part = evt.properties.part
|
const part = evt.properties.part
|
||||||
const message = await Session.getMessage(
|
|
||||||
evt.properties.sessionID,
|
|
||||||
evt.properties.messageID,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (
|
if (part.type === "tool" && part.state.status === "completed") {
|
||||||
part.type === "tool-invocation" &&
|
const [tool, color] = TOOL[part.tool] ?? [part.tool, UI.Style.TEXT_INFO_BOLD]
|
||||||
part.toolInvocation.state === "result"
|
printEvent(color, tool, part.state.title || "Unknown")
|
||||||
) {
|
|
||||||
const metadata = message.metadata.tool[part.toolInvocation.toolCallId]
|
|
||||||
const [tool, color] = TOOL[part.toolInvocation.toolName] ?? [
|
|
||||||
part.toolInvocation.toolName,
|
|
||||||
UI.Style.TEXT_INFO_BOLD,
|
|
||||||
]
|
|
||||||
printEvent(color, tool, metadata?.title || "Unknown")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (part.type === "text") {
|
if (part.type === "text") {
|
||||||
|
|
|
@ -38,9 +38,7 @@ export const ServeCommand = cmd({
|
||||||
hostname,
|
hostname,
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log(
|
console.log(`opencode server listening on http://${server.hostname}:${server.port}`)
|
||||||
`opencode server listening on http://${server.hostname}:${server.port}`,
|
|
||||||
)
|
|
||||||
|
|
||||||
await new Promise(() => {})
|
await new Promise(() => {})
|
||||||
|
|
||||||
|
|
|
@ -15,10 +15,21 @@ export const TuiCommand = cmd({
|
||||||
command: "$0 [project]",
|
command: "$0 [project]",
|
||||||
describe: "start opencode tui",
|
describe: "start opencode tui",
|
||||||
builder: (yargs) =>
|
builder: (yargs) =>
|
||||||
yargs.positional("project", {
|
yargs
|
||||||
type: "string",
|
.positional("project", {
|
||||||
describe: "path to start opencode in",
|
type: "string",
|
||||||
}),
|
describe: "path to start opencode in",
|
||||||
|
})
|
||||||
|
.option("model", {
|
||||||
|
type: "string",
|
||||||
|
alias: ["m"],
|
||||||
|
describe: "model to use in the format of provider/model",
|
||||||
|
})
|
||||||
|
.option("prompt", {
|
||||||
|
alias: ["p"],
|
||||||
|
type: "string",
|
||||||
|
describe: "prompt to use",
|
||||||
|
}),
|
||||||
handler: async (args) => {
|
handler: async (args) => {
|
||||||
while (true) {
|
while (true) {
|
||||||
const cwd = args.project ? path.resolve(args.project) : process.cwd()
|
const cwd = args.project ? path.resolve(args.project) : process.cwd()
|
||||||
|
@ -40,9 +51,7 @@ export const TuiCommand = cmd({
|
||||||
})
|
})
|
||||||
|
|
||||||
let cmd = ["go", "run", "./main.go"]
|
let cmd = ["go", "run", "./main.go"]
|
||||||
let cwd = Bun.fileURLToPath(
|
let cwd = Bun.fileURLToPath(new URL("../../../../tui/cmd/opencode", import.meta.url))
|
||||||
new URL("../../../../tui/cmd/opencode", import.meta.url),
|
|
||||||
)
|
|
||||||
if (Bun.embeddedFiles.length > 0) {
|
if (Bun.embeddedFiles.length > 0) {
|
||||||
const blob = Bun.embeddedFiles[0] as File
|
const blob = Bun.embeddedFiles[0] as File
|
||||||
let binaryName = blob.name
|
let binaryName = blob.name
|
||||||
|
@ -62,7 +71,11 @@ export const TuiCommand = cmd({
|
||||||
cmd,
|
cmd,
|
||||||
})
|
})
|
||||||
const proc = Bun.spawn({
|
const proc = Bun.spawn({
|
||||||
cmd: [...cmd, ...process.argv.slice(2)],
|
cmd: [
|
||||||
|
...cmd,
|
||||||
|
...(args.model ? ["--model", args.model] : []),
|
||||||
|
...(args.prompt ? ["--prompt", args.prompt] : []),
|
||||||
|
],
|
||||||
cwd,
|
cwd,
|
||||||
stdout: "inherit",
|
stdout: "inherit",
|
||||||
stderr: "inherit",
|
stderr: "inherit",
|
||||||
|
|
|
@ -27,22 +27,26 @@ export const UpgradeCommand = {
|
||||||
const detectedMethod = await Installation.method()
|
const detectedMethod = await Installation.method()
|
||||||
const method = (args.method as Installation.Method) ?? detectedMethod
|
const method = (args.method as Installation.Method) ?? detectedMethod
|
||||||
if (method === "unknown") {
|
if (method === "unknown") {
|
||||||
prompts.log.error(
|
prompts.log.error(`opencode is installed to ${process.execPath} and seems to be managed by a package manager`)
|
||||||
`opencode is installed to ${process.execPath} and seems to be managed by a package manager`,
|
|
||||||
)
|
|
||||||
prompts.outro("Done")
|
prompts.outro("Done")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
prompts.log.info("Using method: " + method)
|
prompts.log.info("Using method: " + method)
|
||||||
const target = args.target ?? (await Installation.latest())
|
const target = args.target ?? (await Installation.latest())
|
||||||
|
|
||||||
|
if (Installation.VERSION === target) {
|
||||||
|
prompts.log.warn(`opencode upgrade skipped: ${target} is already installed`)
|
||||||
|
prompts.outro("Done")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
prompts.log.info(`From ${Installation.VERSION} → ${target}`)
|
prompts.log.info(`From ${Installation.VERSION} → ${target}`)
|
||||||
const spinner = prompts.spinner()
|
const spinner = prompts.spinner()
|
||||||
spinner.start("Upgrading...")
|
spinner.start("Upgrading...")
|
||||||
const err = await Installation.upgrade(method, target).catch((err) => err)
|
const err = await Installation.upgrade(method, target).catch((err) => err)
|
||||||
if (err) {
|
if (err) {
|
||||||
spinner.stop("Upgrade failed")
|
spinner.stop("Upgrade failed")
|
||||||
if (err instanceof Installation.UpgradeFailedError)
|
if (err instanceof Installation.UpgradeFailedError) prompts.log.error(err.data.stderr)
|
||||||
prompts.log.error(err.data.stderr)
|
|
||||||
else if (err instanceof Error) prompts.log.error(err.message)
|
else if (err instanceof Error) prompts.log.error(err.message)
|
||||||
prompts.outro("Done")
|
prompts.outro("Done")
|
||||||
return
|
return
|
||||||
|
|
|
@ -5,14 +5,11 @@ import { UI } from "./ui"
|
||||||
export function FormatError(input: unknown) {
|
export function FormatError(input: unknown) {
|
||||||
if (MCP.Failed.isInstance(input))
|
if (MCP.Failed.isInstance(input))
|
||||||
return `MCP server "${input.data.name}" failed. Note, opencode does not support MCP authentication yet.`
|
return `MCP server "${input.data.name}" failed. Note, opencode does not support MCP authentication yet.`
|
||||||
if (Config.JsonError.isInstance(input))
|
if (Config.JsonError.isInstance(input)) return `Config file at ${input.data.path} is not valid JSON`
|
||||||
return `Config file at ${input.data.path} is not valid JSON`
|
|
||||||
if (Config.InvalidError.isInstance(input))
|
if (Config.InvalidError.isInstance(input))
|
||||||
return [
|
return [
|
||||||
`Config file at ${input.data.path} is invalid`,
|
`Config file at ${input.data.path} is invalid`,
|
||||||
...(input.data.issues?.map(
|
...(input.data.issues?.map((issue) => "↳ " + issue.message + " " + issue.path.join(".")) ?? []),
|
||||||
(issue) => "↳ " + issue.message + " " + issue.path.join("."),
|
|
||||||
) ?? []),
|
|
||||||
].join("\n")
|
].join("\n")
|
||||||
|
|
||||||
if (UI.CancelledError.isInstance(input)) return ""
|
if (UI.CancelledError.isInstance(input)) return ""
|
||||||
|
|
|
@ -29,18 +29,12 @@ export namespace Config {
|
||||||
export const McpLocal = z
|
export const McpLocal = z
|
||||||
.object({
|
.object({
|
||||||
type: z.literal("local").describe("Type of MCP server connection"),
|
type: z.literal("local").describe("Type of MCP server connection"),
|
||||||
command: z
|
command: z.string().array().describe("Command and arguments to run the MCP server"),
|
||||||
.string()
|
|
||||||
.array()
|
|
||||||
.describe("Command and arguments to run the MCP server"),
|
|
||||||
environment: z
|
environment: z
|
||||||
.record(z.string(), z.string())
|
.record(z.string(), z.string())
|
||||||
.optional()
|
.optional()
|
||||||
.describe("Environment variables to set when running the MCP server"),
|
.describe("Environment variables to set when running the MCP server"),
|
||||||
enabled: z
|
enabled: z.boolean().optional().describe("Enable or disable the MCP server on startup"),
|
||||||
.boolean()
|
|
||||||
.optional()
|
|
||||||
.describe("Enable or disable the MCP server on startup"),
|
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
.openapi({
|
.openapi({
|
||||||
|
@ -51,10 +45,7 @@ export namespace Config {
|
||||||
.object({
|
.object({
|
||||||
type: z.literal("remote").describe("Type of MCP server connection"),
|
type: z.literal("remote").describe("Type of MCP server connection"),
|
||||||
url: z.string().describe("URL of the remote MCP server"),
|
url: z.string().describe("URL of the remote MCP server"),
|
||||||
enabled: z
|
enabled: z.boolean().optional().describe("Enable or disable the MCP server on startup"),
|
||||||
.boolean()
|
|
||||||
.optional()
|
|
||||||
.describe("Enable or disable the MCP server on startup"),
|
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
.openapi({
|
.openapi({
|
||||||
|
@ -66,67 +57,31 @@ export namespace Config {
|
||||||
|
|
||||||
export const Keybinds = z
|
export const Keybinds = z
|
||||||
.object({
|
.object({
|
||||||
leader: z
|
leader: z.string().optional().describe("Leader key for keybind combinations"),
|
||||||
.string()
|
|
||||||
.optional()
|
|
||||||
.describe("Leader key for keybind combinations"),
|
|
||||||
help: z.string().optional().describe("Show help dialog"),
|
help: z.string().optional().describe("Show help dialog"),
|
||||||
editor_open: z.string().optional().describe("Open external editor"),
|
editor_open: z.string().optional().describe("Open external editor"),
|
||||||
session_new: z.string().optional().describe("Create a new session"),
|
session_new: z.string().optional().describe("Create a new session"),
|
||||||
session_list: z.string().optional().describe("List all sessions"),
|
session_list: z.string().optional().describe("List all sessions"),
|
||||||
session_share: z.string().optional().describe("Share current session"),
|
session_share: z.string().optional().describe("Share current session"),
|
||||||
session_interrupt: z
|
session_interrupt: z.string().optional().describe("Interrupt current session"),
|
||||||
.string()
|
session_compact: z.string().optional().describe("Toggle compact mode for session"),
|
||||||
.optional()
|
|
||||||
.describe("Interrupt current session"),
|
|
||||||
session_compact: z
|
|
||||||
.string()
|
|
||||||
.optional()
|
|
||||||
.describe("Toggle compact mode for session"),
|
|
||||||
tool_details: z.string().optional().describe("Show tool details"),
|
tool_details: z.string().optional().describe("Show tool details"),
|
||||||
model_list: z.string().optional().describe("List available models"),
|
model_list: z.string().optional().describe("List available models"),
|
||||||
theme_list: z.string().optional().describe("List available themes"),
|
theme_list: z.string().optional().describe("List available themes"),
|
||||||
project_init: z
|
project_init: z.string().optional().describe("Initialize project configuration"),
|
||||||
.string()
|
|
||||||
.optional()
|
|
||||||
.describe("Initialize project configuration"),
|
|
||||||
input_clear: z.string().optional().describe("Clear input field"),
|
input_clear: z.string().optional().describe("Clear input field"),
|
||||||
input_paste: z.string().optional().describe("Paste from clipboard"),
|
input_paste: z.string().optional().describe("Paste from clipboard"),
|
||||||
input_submit: z.string().optional().describe("Submit input"),
|
input_submit: z.string().optional().describe("Submit input"),
|
||||||
input_newline: z.string().optional().describe("Insert newline in input"),
|
input_newline: z.string().optional().describe("Insert newline in input"),
|
||||||
history_previous: z
|
history_previous: z.string().optional().describe("Navigate to previous history item"),
|
||||||
.string()
|
history_next: z.string().optional().describe("Navigate to next history item"),
|
||||||
.optional()
|
messages_page_up: z.string().optional().describe("Scroll messages up by one page"),
|
||||||
.describe("Navigate to previous history item"),
|
messages_page_down: z.string().optional().describe("Scroll messages down by one page"),
|
||||||
history_next: z
|
messages_half_page_up: z.string().optional().describe("Scroll messages up by half page"),
|
||||||
.string()
|
messages_half_page_down: z.string().optional().describe("Scroll messages down by half page"),
|
||||||
.optional()
|
messages_previous: z.string().optional().describe("Navigate to previous message"),
|
||||||
.describe("Navigate to next history item"),
|
|
||||||
messages_page_up: z
|
|
||||||
.string()
|
|
||||||
.optional()
|
|
||||||
.describe("Scroll messages up by one page"),
|
|
||||||
messages_page_down: z
|
|
||||||
.string()
|
|
||||||
.optional()
|
|
||||||
.describe("Scroll messages down by one page"),
|
|
||||||
messages_half_page_up: z
|
|
||||||
.string()
|
|
||||||
.optional()
|
|
||||||
.describe("Scroll messages up by half page"),
|
|
||||||
messages_half_page_down: z
|
|
||||||
.string()
|
|
||||||
.optional()
|
|
||||||
.describe("Scroll messages down by half page"),
|
|
||||||
messages_previous: z
|
|
||||||
.string()
|
|
||||||
.optional()
|
|
||||||
.describe("Navigate to previous message"),
|
|
||||||
messages_next: z.string().optional().describe("Navigate to next message"),
|
messages_next: z.string().optional().describe("Navigate to next message"),
|
||||||
messages_first: z
|
messages_first: z.string().optional().describe("Navigate to first message"),
|
||||||
.string()
|
|
||||||
.optional()
|
|
||||||
.describe("Navigate to first message"),
|
|
||||||
messages_last: z.string().optional().describe("Navigate to last message"),
|
messages_last: z.string().optional().describe("Navigate to last message"),
|
||||||
app_exit: z.string().optional().describe("Exit the application"),
|
app_exit: z.string().optional().describe("Exit the application"),
|
||||||
})
|
})
|
||||||
|
@ -136,33 +91,13 @@ export namespace Config {
|
||||||
})
|
})
|
||||||
export const Info = z
|
export const Info = z
|
||||||
.object({
|
.object({
|
||||||
$schema: z
|
$schema: z.string().optional().describe("JSON schema reference for configuration validation"),
|
||||||
.string()
|
theme: z.string().optional().describe("Theme name to use for the interface"),
|
||||||
.optional()
|
|
||||||
.describe("JSON schema reference for configuration validation"),
|
|
||||||
theme: z
|
|
||||||
.string()
|
|
||||||
.optional()
|
|
||||||
.describe("Theme name to use for the interface"),
|
|
||||||
keybinds: Keybinds.optional().describe("Custom keybind configurations"),
|
keybinds: Keybinds.optional().describe("Custom keybind configurations"),
|
||||||
autoshare: z
|
autoshare: z.boolean().optional().describe("Share newly created sessions automatically"),
|
||||||
.boolean()
|
autoupdate: z.boolean().optional().describe("Automatically update to the latest version"),
|
||||||
.optional()
|
disabled_providers: z.array(z.string()).optional().describe("Disable providers that are loaded automatically"),
|
||||||
.describe("Share newly created sessions automatically"),
|
model: z.string().describe("Model to use in the format of provider/model, eg anthropic/claude-2").optional(),
|
||||||
autoupdate: z
|
|
||||||
.boolean()
|
|
||||||
.optional()
|
|
||||||
.describe("Automatically update to the latest version"),
|
|
||||||
disabled_providers: z
|
|
||||||
.array(z.string())
|
|
||||||
.optional()
|
|
||||||
.describe("Disable providers that are loaded automatically"),
|
|
||||||
model: z
|
|
||||||
.string()
|
|
||||||
.describe(
|
|
||||||
"Model to use in the format of provider/model, eg anthropic/claude-2",
|
|
||||||
)
|
|
||||||
.optional(),
|
|
||||||
provider: z
|
provider: z
|
||||||
.record(
|
.record(
|
||||||
ModelsDev.Provider.partial().extend({
|
ModelsDev.Provider.partial().extend({
|
||||||
|
@ -172,14 +107,8 @@ export namespace Config {
|
||||||
)
|
)
|
||||||
.optional()
|
.optional()
|
||||||
.describe("Custom provider configurations and model overrides"),
|
.describe("Custom provider configurations and model overrides"),
|
||||||
mcp: z
|
mcp: z.record(z.string(), Mcp).optional().describe("MCP (Model Context Protocol) server configurations"),
|
||||||
.record(z.string(), Mcp)
|
instructions: z.array(z.string()).optional().describe("Additional instruction files or patterns to include"),
|
||||||
.optional()
|
|
||||||
.describe("MCP (Model Context Protocol) server configurations"),
|
|
||||||
instructions: z
|
|
||||||
.array(z.string())
|
|
||||||
.optional()
|
|
||||||
.describe("Additional instruction files or patterns to include"),
|
|
||||||
experimental: z
|
experimental: z
|
||||||
.object({
|
.object({
|
||||||
hook: z
|
hook: z
|
||||||
|
@ -227,10 +156,7 @@ export namespace Config {
|
||||||
if (provider && model) result.model = `${provider}/${model}`
|
if (provider && model) result.model = `${provider}/${model}`
|
||||||
result["$schema"] = "https://opencode.ai/config.json"
|
result["$schema"] = "https://opencode.ai/config.json"
|
||||||
result = mergeDeep(result, rest)
|
result = mergeDeep(result, rest)
|
||||||
await Bun.write(
|
await Bun.write(path.join(Global.Path.config, "config.json"), JSON.stringify(result, null, 2))
|
||||||
path.join(Global.Path.config, "config.json"),
|
|
||||||
JSON.stringify(result, null, 2),
|
|
||||||
)
|
|
||||||
await fs.unlink(path.join(Global.Path.config, "config"))
|
await fs.unlink(path.join(Global.Path.config, "config"))
|
||||||
})
|
})
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
|
|
|
@ -22,9 +22,7 @@ export namespace ConfigHooks {
|
||||||
command: item.command,
|
command: item.command,
|
||||||
})
|
})
|
||||||
Bun.spawn({
|
Bun.spawn({
|
||||||
cmd: item.command.map((x) =>
|
cmd: item.command.map((x) => x.replace("$FILE", payload.properties.file)),
|
||||||
x.replace("$FILE", payload.properties.file),
|
|
||||||
),
|
|
||||||
env: item.environment,
|
env: item.environment,
|
||||||
cwd: app.path.cwd,
|
cwd: app.path.cwd,
|
||||||
stdout: "ignore",
|
stdout: "ignore",
|
||||||
|
|
|
@ -45,10 +45,7 @@ export namespace Fzf {
|
||||||
log.info("found", { filepath })
|
log.info("found", { filepath })
|
||||||
return { filepath }
|
return { filepath }
|
||||||
}
|
}
|
||||||
filepath = path.join(
|
filepath = path.join(Global.Path.bin, "fzf" + (process.platform === "win32" ? ".exe" : ""))
|
||||||
Global.Path.bin,
|
|
||||||
"fzf" + (process.platform === "win32" ? ".exe" : ""),
|
|
||||||
)
|
|
||||||
|
|
||||||
const file = Bun.file(filepath)
|
const file = Bun.file(filepath)
|
||||||
if (!(await file.exists())) {
|
if (!(await file.exists())) {
|
||||||
|
@ -56,18 +53,15 @@ export namespace Fzf {
|
||||||
const arch = archMap[process.arch as keyof typeof archMap] ?? "amd64"
|
const arch = archMap[process.arch as keyof typeof archMap] ?? "amd64"
|
||||||
|
|
||||||
const config = PLATFORM[process.platform as keyof typeof PLATFORM]
|
const config = PLATFORM[process.platform as keyof typeof PLATFORM]
|
||||||
if (!config)
|
if (!config) throw new UnsupportedPlatformError({ platform: process.platform })
|
||||||
throw new UnsupportedPlatformError({ platform: process.platform })
|
|
||||||
|
|
||||||
const version = VERSION
|
const version = VERSION
|
||||||
const platformName =
|
const platformName = process.platform === "win32" ? "windows" : process.platform
|
||||||
process.platform === "win32" ? "windows" : process.platform
|
|
||||||
const filename = `fzf-${version}-${platformName}_${arch}.${config.extension}`
|
const filename = `fzf-${version}-${platformName}_${arch}.${config.extension}`
|
||||||
const url = `https://github.com/junegunn/fzf/releases/download/v${version}/${filename}`
|
const url = `https://github.com/junegunn/fzf/releases/download/v${version}/${filename}`
|
||||||
|
|
||||||
const response = await fetch(url)
|
const response = await fetch(url)
|
||||||
if (!response.ok)
|
if (!response.ok) throw new DownloadFailedError({ url, status: response.status })
|
||||||
throw new DownloadFailedError({ url, status: response.status })
|
|
||||||
|
|
||||||
const buffer = await response.arrayBuffer()
|
const buffer = await response.arrayBuffer()
|
||||||
const archivePath = path.join(Global.Path.bin, filename)
|
const archivePath = path.join(Global.Path.bin, filename)
|
||||||
|
@ -86,14 +80,11 @@ export namespace Fzf {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (config.extension === "zip") {
|
if (config.extension === "zip") {
|
||||||
const proc = Bun.spawn(
|
const proc = Bun.spawn(["unzip", "-j", archivePath, "fzf.exe", "-d", Global.Path.bin], {
|
||||||
["unzip", "-j", archivePath, "fzf.exe", "-d", Global.Path.bin],
|
cwd: Global.Path.bin,
|
||||||
{
|
stderr: "pipe",
|
||||||
cwd: Global.Path.bin,
|
stdout: "ignore",
|
||||||
stderr: "pipe",
|
})
|
||||||
stdout: "ignore",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
await proc.exited
|
await proc.exited
|
||||||
if (proc.exitCode !== 0)
|
if (proc.exitCode !== 0)
|
||||||
throw new ExtractionFailedError({
|
throw new ExtractionFailedError({
|
||||||
|
|
|
@ -24,11 +24,7 @@ export namespace File {
|
||||||
const app = App.info()
|
const app = App.info()
|
||||||
if (!app.git) return []
|
if (!app.git) return []
|
||||||
|
|
||||||
const diffOutput = await $`git diff --numstat HEAD`
|
const diffOutput = await $`git diff --numstat HEAD`.cwd(app.path.cwd).quiet().nothrow().text()
|
||||||
.cwd(app.path.cwd)
|
|
||||||
.quiet()
|
|
||||||
.nothrow()
|
|
||||||
.text()
|
|
||||||
|
|
||||||
const changedFiles = []
|
const changedFiles = []
|
||||||
|
|
||||||
|
@ -45,19 +41,13 @@ export namespace File {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const untrackedOutput = await $`git ls-files --others --exclude-standard`
|
const untrackedOutput = await $`git ls-files --others --exclude-standard`.cwd(app.path.cwd).quiet().nothrow().text()
|
||||||
.cwd(app.path.cwd)
|
|
||||||
.quiet()
|
|
||||||
.nothrow()
|
|
||||||
.text()
|
|
||||||
|
|
||||||
if (untrackedOutput.trim()) {
|
if (untrackedOutput.trim()) {
|
||||||
const untrackedFiles = untrackedOutput.trim().split("\n")
|
const untrackedFiles = untrackedOutput.trim().split("\n")
|
||||||
for (const filepath of untrackedFiles) {
|
for (const filepath of untrackedFiles) {
|
||||||
try {
|
try {
|
||||||
const content = await Bun.file(
|
const content = await Bun.file(path.join(app.path.root, filepath)).text()
|
||||||
path.join(app.path.root, filepath),
|
|
||||||
).text()
|
|
||||||
const lines = content.split("\n").length
|
const lines = content.split("\n").length
|
||||||
changedFiles.push({
|
changedFiles.push({
|
||||||
file: filepath,
|
file: filepath,
|
||||||
|
@ -72,11 +62,7 @@ export namespace File {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get deleted files
|
// Get deleted files
|
||||||
const deletedOutput = await $`git diff --name-only --diff-filter=D HEAD`
|
const deletedOutput = await $`git diff --name-only --diff-filter=D HEAD`.cwd(app.path.cwd).quiet().nothrow().text()
|
||||||
.cwd(app.path.cwd)
|
|
||||||
.quiet()
|
|
||||||
.nothrow()
|
|
||||||
.text()
|
|
||||||
|
|
||||||
if (deletedOutput.trim()) {
|
if (deletedOutput.trim()) {
|
||||||
const deletedFiles = deletedOutput.trim().split("\n")
|
const deletedFiles = deletedOutput.trim().split("\n")
|
||||||
|
@ -112,11 +98,7 @@ export namespace File {
|
||||||
filepath: rel,
|
filepath: rel,
|
||||||
})
|
})
|
||||||
if (diff !== "unmodified") {
|
if (diff !== "unmodified") {
|
||||||
const original = await $`git show HEAD:${rel}`
|
const original = await $`git show HEAD:${rel}`.cwd(app.path.root).quiet().nothrow().text()
|
||||||
.cwd(app.path.root)
|
|
||||||
.quiet()
|
|
||||||
.nothrow()
|
|
||||||
.text()
|
|
||||||
const patch = createPatch(file, original, content, "old", "new", {
|
const patch = createPatch(file, original, content, "old", "new", {
|
||||||
context: Infinity,
|
context: Infinity,
|
||||||
})
|
})
|
||||||
|
|
|
@ -122,15 +122,11 @@ export namespace Ripgrep {
|
||||||
const state = lazy(async () => {
|
const state = lazy(async () => {
|
||||||
let filepath = Bun.which("rg")
|
let filepath = Bun.which("rg")
|
||||||
if (filepath) return { filepath }
|
if (filepath) return { filepath }
|
||||||
filepath = path.join(
|
filepath = path.join(Global.Path.bin, "rg" + (process.platform === "win32" ? ".exe" : ""))
|
||||||
Global.Path.bin,
|
|
||||||
"rg" + (process.platform === "win32" ? ".exe" : ""),
|
|
||||||
)
|
|
||||||
|
|
||||||
const file = Bun.file(filepath)
|
const file = Bun.file(filepath)
|
||||||
if (!(await file.exists())) {
|
if (!(await file.exists())) {
|
||||||
const platformKey =
|
const platformKey = `${process.arch}-${process.platform}` as keyof typeof PLATFORM
|
||||||
`${process.arch}-${process.platform}` as keyof typeof PLATFORM
|
|
||||||
const config = PLATFORM[platformKey]
|
const config = PLATFORM[platformKey]
|
||||||
if (!config) throw new UnsupportedPlatformError({ platform: platformKey })
|
if (!config) throw new UnsupportedPlatformError({ platform: platformKey })
|
||||||
|
|
||||||
|
@ -139,8 +135,7 @@ export namespace Ripgrep {
|
||||||
const url = `https://github.com/BurntSushi/ripgrep/releases/download/${version}/${filename}`
|
const url = `https://github.com/BurntSushi/ripgrep/releases/download/${version}/${filename}`
|
||||||
|
|
||||||
const response = await fetch(url)
|
const response = await fetch(url)
|
||||||
if (!response.ok)
|
if (!response.ok) throw new DownloadFailedError({ url, status: response.status })
|
||||||
throw new DownloadFailedError({ url, status: response.status })
|
|
||||||
|
|
||||||
const buffer = await response.arrayBuffer()
|
const buffer = await response.arrayBuffer()
|
||||||
const archivePath = path.join(Global.Path.bin, filename)
|
const archivePath = path.join(Global.Path.bin, filename)
|
||||||
|
@ -164,14 +159,11 @@ export namespace Ripgrep {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (config.extension === "zip") {
|
if (config.extension === "zip") {
|
||||||
const proc = Bun.spawn(
|
const proc = Bun.spawn(["unzip", "-j", archivePath, "*/rg.exe", "-d", Global.Path.bin], {
|
||||||
["unzip", "-j", archivePath, "*/rg.exe", "-d", Global.Path.bin],
|
cwd: Global.Path.bin,
|
||||||
{
|
stderr: "pipe",
|
||||||
cwd: Global.Path.bin,
|
stdout: "ignore",
|
||||||
stderr: "pipe",
|
})
|
||||||
stdout: "ignore",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
await proc.exited
|
await proc.exited
|
||||||
if (proc.exitCode !== 0)
|
if (proc.exitCode !== 0)
|
||||||
throw new ExtractionFailedError({
|
throw new ExtractionFailedError({
|
||||||
|
@ -193,17 +185,11 @@ export namespace Ripgrep {
|
||||||
return filepath
|
return filepath
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function files(input: {
|
export async function files(input: { cwd: string; query?: string; glob?: string; limit?: number }) {
|
||||||
cwd: string
|
|
||||||
query?: string
|
|
||||||
glob?: string
|
|
||||||
limit?: number
|
|
||||||
}) {
|
|
||||||
const commands = [
|
const commands = [
|
||||||
`${await filepath()} --files --hidden --glob='!.git/*' ${input.glob ? `--glob='${input.glob}'` : ``}`,
|
`${await filepath()} --files --hidden --glob='!.git/*' ${input.glob ? `--glob='${input.glob}'` : ``}`,
|
||||||
]
|
]
|
||||||
if (input.query)
|
if (input.query) commands.push(`${await Fzf.filepath()} --filter=${input.query}`)
|
||||||
commands.push(`${await Fzf.filepath()} --filter=${input.query}`)
|
|
||||||
if (input.limit) commands.push(`head -n ${input.limit}`)
|
if (input.limit) commands.push(`head -n ${input.limit}`)
|
||||||
const joined = commands.join(" | ")
|
const joined = commands.join(" | ")
|
||||||
const result = await $`${{ raw: joined }}`.cwd(input.cwd).nothrow().text()
|
const result = await $`${{ raw: joined }}`.cwd(input.cwd).nothrow().text()
|
||||||
|
@ -310,18 +296,8 @@ export namespace Ripgrep {
|
||||||
return lines.join("\n")
|
return lines.join("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function search(input: {
|
export async function search(input: { cwd: string; pattern: string; glob?: string[]; limit?: number }) {
|
||||||
cwd: string
|
const args = [`${await filepath()}`, "--json", "--hidden", "--glob='!.git/*'"]
|
||||||
pattern: string
|
|
||||||
glob?: string[]
|
|
||||||
limit?: number
|
|
||||||
}) {
|
|
||||||
const args = [
|
|
||||||
`${await filepath()}`,
|
|
||||||
"--json",
|
|
||||||
"--hidden",
|
|
||||||
"--glob='!.git/*'",
|
|
||||||
]
|
|
||||||
|
|
||||||
if (input.glob) {
|
if (input.glob) {
|
||||||
for (const g of input.glob) {
|
for (const g of input.glob) {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { App } from "../app/app"
|
import { App } from "../app/app"
|
||||||
|
import { Log } from "../util/log"
|
||||||
|
|
||||||
export namespace FileTime {
|
export namespace FileTime {
|
||||||
|
const log = Log.create({ service: "file.time" })
|
||||||
export const state = App.state("tool.filetimes", () => {
|
export const state = App.state("tool.filetimes", () => {
|
||||||
const read: {
|
const read: {
|
||||||
[sessionID: string]: {
|
[sessionID: string]: {
|
||||||
|
@ -13,6 +15,7 @@ export namespace FileTime {
|
||||||
})
|
})
|
||||||
|
|
||||||
export function read(sessionID: string, file: string) {
|
export function read(sessionID: string, file: string) {
|
||||||
|
log.info("read", { sessionID, file })
|
||||||
const { read } = state()
|
const { read } = state()
|
||||||
read[sessionID] = read[sessionID] || {}
|
read[sessionID] = read[sessionID] || {}
|
||||||
read[sessionID][file] = new Date()
|
read[sessionID][file] = new Date()
|
||||||
|
@ -24,10 +27,7 @@ export namespace FileTime {
|
||||||
|
|
||||||
export async function assert(sessionID: string, filepath: string) {
|
export async function assert(sessionID: string, filepath: string) {
|
||||||
const time = get(sessionID, filepath)
|
const time = get(sessionID, filepath)
|
||||||
if (!time)
|
if (!time) throw new Error(`You must read the file ${filepath} before overwriting it. Use the Read tool first`)
|
||||||
throw new Error(
|
|
||||||
`You must read the file ${filepath} before overwriting it. Use the Read tool first`,
|
|
||||||
)
|
|
||||||
const stats = await Bun.file(filepath).stat()
|
const stats = await Bun.file(filepath).stat()
|
||||||
if (stats.mtime.getTime() > time.getTime()) {
|
if (stats.mtime.getTime() > time.getTime()) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|
|
@ -21,23 +21,20 @@ export namespace FileWatcher {
|
||||||
"file.watcher",
|
"file.watcher",
|
||||||
() => {
|
() => {
|
||||||
const app = App.use()
|
const app = App.use()
|
||||||
|
if (!app.info.git) return {}
|
||||||
try {
|
try {
|
||||||
const watcher = fs.watch(
|
const watcher = fs.watch(app.info.path.cwd, { recursive: true }, (event, file) => {
|
||||||
app.info.path.cwd,
|
log.info("change", { file, event })
|
||||||
{ recursive: true },
|
if (!file) return
|
||||||
(event, file) => {
|
// for some reason async local storage is lost here
|
||||||
log.info("change", { file, event })
|
// https://github.com/oven-sh/bun/issues/20754
|
||||||
if (!file) return
|
App.provideExisting(app, async () => {
|
||||||
// for some reason async local storage is lost here
|
Bus.publish(Event.Updated, {
|
||||||
// https://github.com/oven-sh/bun/issues/20754
|
file,
|
||||||
App.provideExisting(app, async () => {
|
event,
|
||||||
Bus.publish(Event.Updated, {
|
|
||||||
file,
|
|
||||||
event,
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
},
|
})
|
||||||
)
|
})
|
||||||
return { watcher }
|
return { watcher }
|
||||||
} catch {
|
} catch {
|
||||||
return {}
|
return {}
|
||||||
|
|
|
@ -94,21 +94,7 @@ export const zig: Info = {
|
||||||
export const clang: Info = {
|
export const clang: Info = {
|
||||||
name: "clang-format",
|
name: "clang-format",
|
||||||
command: ["clang-format", "-i", "$FILE"],
|
command: ["clang-format", "-i", "$FILE"],
|
||||||
extensions: [
|
extensions: [".c", ".cc", ".cpp", ".cxx", ".c++", ".h", ".hh", ".hpp", ".hxx", ".h++", ".ino", ".C", ".H"],
|
||||||
".c",
|
|
||||||
".cc",
|
|
||||||
".cpp",
|
|
||||||
".cxx",
|
|
||||||
".c++",
|
|
||||||
".h",
|
|
||||||
".hh",
|
|
||||||
".hpp",
|
|
||||||
".hxx",
|
|
||||||
".h++",
|
|
||||||
".ino",
|
|
||||||
".C",
|
|
||||||
".H",
|
|
||||||
],
|
|
||||||
async enabled() {
|
async enabled() {
|
||||||
return Bun.which("clang-format") !== null
|
return Bun.which("clang-format") !== null
|
||||||
},
|
},
|
||||||
|
|
|
@ -26,11 +26,7 @@ export namespace Identifier {
|
||||||
return generateID(prefix, true, given)
|
return generateID(prefix, true, given)
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateID(
|
function generateID(prefix: keyof typeof prefixes, descending: boolean, given?: string): string {
|
||||||
prefix: keyof typeof prefixes,
|
|
||||||
descending: boolean,
|
|
||||||
given?: string,
|
|
||||||
): string {
|
|
||||||
if (!given) {
|
if (!given) {
|
||||||
return generateNewID(prefix, descending)
|
return generateNewID(prefix, descending)
|
||||||
}
|
}
|
||||||
|
@ -42,8 +38,7 @@ export namespace Identifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
function randomBase62(length: number): string {
|
function randomBase62(length: number): string {
|
||||||
const chars =
|
const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||||
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
|
||||||
let result = ""
|
let result = ""
|
||||||
const bytes = randomBytes(length)
|
const bytes = randomBytes(length)
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
|
@ -52,10 +47,7 @@ export namespace Identifier {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateNewID(
|
function generateNewID(prefix: keyof typeof prefixes, descending: boolean): string {
|
||||||
prefix: keyof typeof prefixes,
|
|
||||||
descending: boolean,
|
|
||||||
): string {
|
|
||||||
const currentTimestamp = Date.now()
|
const currentTimestamp = Date.now()
|
||||||
|
|
||||||
if (currentTimestamp !== lastTimestamp) {
|
if (currentTimestamp !== lastTimestamp) {
|
||||||
|
@ -73,11 +65,6 @@ export namespace Identifier {
|
||||||
timeBytes[i] = Number((now >> BigInt(40 - 8 * i)) & BigInt(0xff))
|
timeBytes[i] = Number((now >> BigInt(40 - 8 * i)) & BigInt(0xff))
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return prefixes[prefix] + "_" + timeBytes.toString("hex") + randomBase62(LENGTH - 12)
|
||||||
prefixes[prefix] +
|
|
||||||
"_" +
|
|
||||||
timeBytes.toString("hex") +
|
|
||||||
randomBase62(LENGTH - 12)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,10 +55,7 @@ const cli = yargs(hideBin(process.argv))
|
||||||
.command(ServeCommand)
|
.command(ServeCommand)
|
||||||
.command(ModelsCommand)
|
.command(ModelsCommand)
|
||||||
.fail((msg) => {
|
.fail((msg) => {
|
||||||
if (
|
if (msg.startsWith("Unknown argument") || msg.startsWith("Not enough non-option arguments")) {
|
||||||
msg.startsWith("Unknown argument") ||
|
|
||||||
msg.startsWith("Not enough non-option arguments")
|
|
||||||
) {
|
|
||||||
cli.showHelp("log")
|
cli.showHelp("log")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -97,10 +94,7 @@ try {
|
||||||
Log.Default.error("fatal", data)
|
Log.Default.error("fatal", data)
|
||||||
const formatted = FormatError(e)
|
const formatted = FormatError(e)
|
||||||
if (formatted) UI.error(formatted)
|
if (formatted) UI.error(formatted)
|
||||||
if (formatted === undefined)
|
if (formatted === undefined) UI.error("Unexpected error, check log file at " + Log.file() + " for more details")
|
||||||
UI.error(
|
|
||||||
"Unexpected error, check log file at " + Log.file() + " for more details",
|
|
||||||
)
|
|
||||||
process.exitCode = 1
|
process.exitCode = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -135,8 +135,7 @@ export namespace Installation {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const VERSION =
|
export const VERSION = typeof OPENCODE_VERSION === "string" ? OPENCODE_VERSION : "dev"
|
||||||
typeof OPENCODE_VERSION === "string" ? OPENCODE_VERSION : "dev"
|
|
||||||
|
|
||||||
export async function latest() {
|
export async function latest() {
|
||||||
return fetch("https://api.github.com/repos/sst/opencode/releases/latest")
|
return fetch("https://api.github.com/repos/sst/opencode/releases/latest")
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import {
|
import { createMessageConnection, StreamMessageReader, StreamMessageWriter } from "vscode-jsonrpc/node"
|
||||||
createMessageConnection,
|
|
||||||
StreamMessageReader,
|
|
||||||
StreamMessageWriter,
|
|
||||||
} from "vscode-jsonrpc/node"
|
|
||||||
import type { Diagnostic as VSCodeDiagnostic } from "vscode-languageserver-types"
|
import type { Diagnostic as VSCodeDiagnostic } from "vscode-languageserver-types"
|
||||||
import { App } from "../app/app"
|
import { App } from "../app/app"
|
||||||
import { Log } from "../util/log"
|
import { Log } from "../util/log"
|
||||||
|
@ -66,6 +62,7 @@ export namespace LSPClient {
|
||||||
log.info("sending initialize", { id: serverID })
|
log.info("sending initialize", { id: serverID })
|
||||||
await withTimeout(
|
await withTimeout(
|
||||||
connection.sendRequest("initialize", {
|
connection.sendRequest("initialize", {
|
||||||
|
rootUri: "file://" + app.path.cwd,
|
||||||
processId: server.process.pid,
|
processId: server.process.pid,
|
||||||
workspaceFolders: [
|
workspaceFolders: [
|
||||||
{
|
{
|
||||||
|
@ -120,9 +117,7 @@ export namespace LSPClient {
|
||||||
},
|
},
|
||||||
notify: {
|
notify: {
|
||||||
async open(input: { path: string }) {
|
async open(input: { path: string }) {
|
||||||
input.path = path.isAbsolute(input.path)
|
input.path = path.isAbsolute(input.path) ? input.path : path.resolve(app.path.cwd, input.path)
|
||||||
? input.path
|
|
||||||
: path.resolve(app.path.cwd, input.path)
|
|
||||||
const file = Bun.file(input.path)
|
const file = Bun.file(input.path)
|
||||||
const text = await file.text()
|
const text = await file.text()
|
||||||
const version = files[input.path]
|
const version = files[input.path]
|
||||||
|
@ -154,18 +149,13 @@ export namespace LSPClient {
|
||||||
return diagnostics
|
return diagnostics
|
||||||
},
|
},
|
||||||
async waitForDiagnostics(input: { path: string }) {
|
async waitForDiagnostics(input: { path: string }) {
|
||||||
input.path = path.isAbsolute(input.path)
|
input.path = path.isAbsolute(input.path) ? input.path : path.resolve(app.path.cwd, input.path)
|
||||||
? input.path
|
|
||||||
: path.resolve(app.path.cwd, input.path)
|
|
||||||
log.info("waiting for diagnostics", input)
|
log.info("waiting for diagnostics", input)
|
||||||
let unsub: () => void
|
let unsub: () => void
|
||||||
return await withTimeout(
|
return await withTimeout(
|
||||||
new Promise<void>((resolve) => {
|
new Promise<void>((resolve) => {
|
||||||
unsub = Bus.subscribe(Event.Diagnostics, (event) => {
|
unsub = Bus.subscribe(Event.Diagnostics, (event) => {
|
||||||
if (
|
if (event.properties.path === input.path && event.properties.serverID === result.serverID) {
|
||||||
event.properties.path === input.path &&
|
|
||||||
event.properties.serverID === result.serverID
|
|
||||||
) {
|
|
||||||
log.info("got diagnostics", input)
|
log.info("got diagnostics", input)
|
||||||
unsub?.()
|
unsub?.()
|
||||||
resolve()
|
resolve()
|
||||||
|
|
|
@ -46,9 +46,7 @@ export namespace LSP {
|
||||||
if (!file) continue
|
if (!file) continue
|
||||||
const handle = await server.spawn(App.info())
|
const handle = await server.spawn(App.info())
|
||||||
if (!handle) break
|
if (!handle) break
|
||||||
const client = await LSPClient.create(server.id, handle).catch(
|
const client = await LSPClient.create(server.id, handle).catch((err) => log.error("", { error: err }))
|
||||||
(err) => log.error("", { error: err }),
|
|
||||||
)
|
|
||||||
if (!client) break
|
if (!client) break
|
||||||
clients.set(server.id, client)
|
clients.set(server.id, client)
|
||||||
break
|
break
|
||||||
|
@ -77,9 +75,7 @@ export namespace LSP {
|
||||||
.map((x) => x.id)
|
.map((x) => x.id)
|
||||||
await run(async (client) => {
|
await run(async (client) => {
|
||||||
if (!matches.includes(client.serverID)) return
|
if (!matches.includes(client.serverID)) return
|
||||||
const wait = waitForDiagnostics
|
const wait = waitForDiagnostics ? client.waitForDiagnostics({ path: input }) : Promise.resolve()
|
||||||
? client.waitForDiagnostics({ path: input })
|
|
||||||
: Promise.resolve()
|
|
||||||
await client.notify.open({ path: input })
|
await client.notify.open({ path: input })
|
||||||
return wait
|
return wait
|
||||||
})
|
})
|
||||||
|
@ -97,11 +93,7 @@ export namespace LSP {
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function hover(input: {
|
export async function hover(input: { file: string; line: number; character: number }) {
|
||||||
file: string
|
|
||||||
line: number
|
|
||||||
character: number
|
|
||||||
}) {
|
|
||||||
return run((client) => {
|
return run((client) => {
|
||||||
return client.connection.sendRequest("textDocument/hover", {
|
return client.connection.sendRequest("textDocument/hover", {
|
||||||
textDocument: {
|
textDocument: {
|
||||||
|
@ -123,9 +115,7 @@ export namespace LSP {
|
||||||
).then((result) => result.flat() as LSP.Symbol[])
|
).then((result) => result.flat() as LSP.Symbol[])
|
||||||
}
|
}
|
||||||
|
|
||||||
async function run<T>(
|
async function run<T>(input: (client: LSPClient.Info) => Promise<T>): Promise<T[]> {
|
||||||
input: (client: LSPClient.Info) => Promise<T>,
|
|
||||||
): Promise<T[]> {
|
|
||||||
const clients = await state().then((x) => [...x.clients.values()])
|
const clients = await state().then((x) => [...x.clients.values()])
|
||||||
const tasks = clients.map((x) => input(x))
|
const tasks = clients.map((x) => input(x))
|
||||||
return Promise.all(tasks)
|
return Promise.all(tasks)
|
||||||
|
|
|
@ -25,21 +25,14 @@ export namespace LSPServer {
|
||||||
id: "typescript",
|
id: "typescript",
|
||||||
extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"],
|
extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"],
|
||||||
async spawn(app) {
|
async spawn(app) {
|
||||||
const tsserver = await Bun.resolve(
|
const tsserver = await Bun.resolve("typescript/lib/tsserver.js", app.path.cwd).catch(() => {})
|
||||||
"typescript/lib/tsserver.js",
|
|
||||||
app.path.cwd,
|
|
||||||
).catch(() => {})
|
|
||||||
if (!tsserver) return
|
if (!tsserver) return
|
||||||
const proc = spawn(
|
const proc = spawn(BunProc.which(), ["x", "typescript-language-server", "--stdio"], {
|
||||||
BunProc.which(),
|
env: {
|
||||||
["x", "typescript-language-server", "--stdio"],
|
...process.env,
|
||||||
{
|
BUN_BE_BUN: "1",
|
||||||
env: {
|
|
||||||
...process.env,
|
|
||||||
BUN_BE_BUN: "1",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
)
|
})
|
||||||
return {
|
return {
|
||||||
process: proc,
|
process: proc,
|
||||||
initialization: {
|
initialization: {
|
||||||
|
@ -73,10 +66,7 @@ export namespace LSPServer {
|
||||||
log.error("Failed to install gopls")
|
log.error("Failed to install gopls")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
bin = path.join(
|
bin = path.join(Global.Path.bin, "gopls" + (process.platform === "win32" ? ".exe" : ""))
|
||||||
Global.Path.bin,
|
|
||||||
"gopls" + (process.platform === "win32" ? ".exe" : ""),
|
|
||||||
)
|
|
||||||
log.info(`installed gopls`, {
|
log.info(`installed gopls`, {
|
||||||
bin,
|
bin,
|
||||||
})
|
})
|
||||||
|
@ -113,10 +103,7 @@ export namespace LSPServer {
|
||||||
log.error("Failed to install ruby-lsp")
|
log.error("Failed to install ruby-lsp")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
bin = path.join(
|
bin = path.join(Global.Path.bin, "ruby-lsp" + (process.platform === "win32" ? ".exe" : ""))
|
||||||
Global.Path.bin,
|
|
||||||
"ruby-lsp" + (process.platform === "win32" ? ".exe" : ""),
|
|
||||||
)
|
|
||||||
log.info(`installed ruby-lsp`, {
|
log.info(`installed ruby-lsp`, {
|
||||||
bin,
|
bin,
|
||||||
})
|
})
|
||||||
|
@ -131,16 +118,12 @@ export namespace LSPServer {
|
||||||
id: "pyright",
|
id: "pyright",
|
||||||
extensions: [".py", ".pyi"],
|
extensions: [".py", ".pyi"],
|
||||||
async spawn() {
|
async spawn() {
|
||||||
const proc = spawn(
|
const proc = spawn(BunProc.which(), ["x", "pyright-langserver", "--stdio"], {
|
||||||
BunProc.which(),
|
env: {
|
||||||
["x", "pyright-langserver", "--stdio"],
|
...process.env,
|
||||||
{
|
BUN_BE_BUN: "1",
|
||||||
env: {
|
|
||||||
...process.env,
|
|
||||||
BUN_BE_BUN: "1",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
)
|
})
|
||||||
return {
|
return {
|
||||||
process: proc,
|
process: proc,
|
||||||
}
|
}
|
||||||
|
@ -158,9 +141,7 @@ export namespace LSPServer {
|
||||||
Global.Path.bin,
|
Global.Path.bin,
|
||||||
"elixir-ls-master",
|
"elixir-ls-master",
|
||||||
"release",
|
"release",
|
||||||
process.platform === "win32"
|
process.platform === "win32" ? "language_server.bar" : "language_server.sh",
|
||||||
? "language_server.bar"
|
|
||||||
: "language_server.sh",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!(await Bun.file(binary).exists())) {
|
if (!(await Bun.file(binary).exists())) {
|
||||||
|
@ -172,9 +153,7 @@ export namespace LSPServer {
|
||||||
|
|
||||||
log.info("downloading elixir-ls from GitHub releases")
|
log.info("downloading elixir-ls from GitHub releases")
|
||||||
|
|
||||||
const response = await fetch(
|
const response = await fetch("https://github.com/elixir-lsp/elixir-ls/archive/refs/heads/master.zip")
|
||||||
"https://github.com/elixir-lsp/elixir-ls/archive/refs/heads/master.zip",
|
|
||||||
)
|
|
||||||
if (!response.ok) return
|
if (!response.ok) return
|
||||||
const zipPath = path.join(Global.Path.bin, "elixir-ls.zip")
|
const zipPath = path.join(Global.Path.bin, "elixir-ls.zip")
|
||||||
await Bun.file(zipPath).write(response)
|
await Bun.file(zipPath).write(response)
|
||||||
|
|
|
@ -91,8 +91,7 @@ export namespace Provider {
|
||||||
if (!info || info.type !== "oauth") return
|
if (!info || info.type !== "oauth") return
|
||||||
if (!info.access || info.expires < Date.now()) {
|
if (!info.access || info.expires < Date.now()) {
|
||||||
const tokens = await copilot.access(info.refresh)
|
const tokens = await copilot.access(info.refresh)
|
||||||
if (!tokens)
|
if (!tokens) throw new Error("GitHub Copilot authentication expired")
|
||||||
throw new Error("GitHub Copilot authentication expired")
|
|
||||||
await Auth.set("github-copilot", {
|
await Auth.set("github-copilot", {
|
||||||
type: "oauth",
|
type: "oauth",
|
||||||
...tokens,
|
...tokens,
|
||||||
|
@ -101,15 +100,9 @@ export namespace Provider {
|
||||||
}
|
}
|
||||||
let isAgentCall = false
|
let isAgentCall = false
|
||||||
try {
|
try {
|
||||||
const body =
|
const body = typeof init.body === "string" ? JSON.parse(init.body) : init.body
|
||||||
typeof init.body === "string"
|
|
||||||
? JSON.parse(init.body)
|
|
||||||
: init.body
|
|
||||||
if (body?.messages) {
|
if (body?.messages) {
|
||||||
isAgentCall = body.messages.some(
|
isAgentCall = body.messages.some((msg: any) => msg.role && ["tool", "assistant"].includes(msg.role))
|
||||||
(msg: any) =>
|
|
||||||
msg.role && ["tool", "assistant"].includes(msg.role),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
const headers = {
|
const headers = {
|
||||||
|
@ -138,14 +131,11 @@ export namespace Provider {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"amazon-bedrock": async () => {
|
"amazon-bedrock": async () => {
|
||||||
if (!process.env["AWS_PROFILE"] && !process.env["AWS_ACCESS_KEY_ID"])
|
if (!process.env["AWS_PROFILE"] && !process.env["AWS_ACCESS_KEY_ID"]) return { autoload: false }
|
||||||
return { autoload: false }
|
|
||||||
|
|
||||||
const region = process.env["AWS_REGION"] ?? "us-east-1"
|
const region = process.env["AWS_REGION"] ?? "us-east-1"
|
||||||
|
|
||||||
const { fromNodeProviderChain } = await import(
|
const { fromNodeProviderChain } = await import(await BunProc.install("@aws-sdk/credential-providers"))
|
||||||
await BunProc.install("@aws-sdk/credential-providers")
|
|
||||||
)
|
|
||||||
return {
|
return {
|
||||||
autoload: true,
|
autoload: true,
|
||||||
options: {
|
options: {
|
||||||
|
@ -157,9 +147,7 @@ export namespace Provider {
|
||||||
|
|
||||||
switch (regionPrefix) {
|
switch (regionPrefix) {
|
||||||
case "us": {
|
case "us": {
|
||||||
const modelRequiresPrefix = ["claude", "deepseek"].some((m) =>
|
const modelRequiresPrefix = ["claude", "deepseek"].some((m) => modelID.includes(m))
|
||||||
modelID.includes(m),
|
|
||||||
)
|
|
||||||
if (modelRequiresPrefix) {
|
if (modelRequiresPrefix) {
|
||||||
modelID = `${regionPrefix}.${modelID}`
|
modelID = `${regionPrefix}.${modelID}`
|
||||||
}
|
}
|
||||||
|
@ -174,25 +162,18 @@ export namespace Provider {
|
||||||
"eu-south-1",
|
"eu-south-1",
|
||||||
"eu-south-2",
|
"eu-south-2",
|
||||||
].some((r) => region.includes(r))
|
].some((r) => region.includes(r))
|
||||||
const modelRequiresPrefix = [
|
const modelRequiresPrefix = ["claude", "nova-lite", "nova-micro", "llama3", "pixtral"].some((m) =>
|
||||||
"claude",
|
modelID.includes(m),
|
||||||
"nova-lite",
|
)
|
||||||
"nova-micro",
|
|
||||||
"llama3",
|
|
||||||
"pixtral",
|
|
||||||
].some((m) => modelID.includes(m))
|
|
||||||
if (regionRequiresPrefix && modelRequiresPrefix) {
|
if (regionRequiresPrefix && modelRequiresPrefix) {
|
||||||
modelID = `${regionPrefix}.${modelID}`
|
modelID = `${regionPrefix}.${modelID}`
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case "ap": {
|
case "ap": {
|
||||||
const modelRequiresPrefix = [
|
const modelRequiresPrefix = ["claude", "nova-lite", "nova-micro", "nova-pro"].some((m) =>
|
||||||
"claude",
|
modelID.includes(m),
|
||||||
"nova-lite",
|
)
|
||||||
"nova-micro",
|
|
||||||
"nova-pro",
|
|
||||||
].some((m) => modelID.includes(m))
|
|
||||||
if (modelRequiresPrefix) {
|
if (modelRequiresPrefix) {
|
||||||
regionPrefix = "apac"
|
regionPrefix = "apac"
|
||||||
modelID = `${regionPrefix}.${modelID}`
|
modelID = `${regionPrefix}.${modelID}`
|
||||||
|
@ -230,10 +211,7 @@ export namespace Provider {
|
||||||
options: Record<string, any>
|
options: Record<string, any>
|
||||||
}
|
}
|
||||||
} = {}
|
} = {}
|
||||||
const models = new Map<
|
const models = new Map<string, { info: ModelsDev.Model; language: LanguageModel }>()
|
||||||
string,
|
|
||||||
{ info: ModelsDev.Model; language: LanguageModel }
|
|
||||||
>()
|
|
||||||
const sdk = new Map<string, SDK>()
|
const sdk = new Map<string, SDK>()
|
||||||
|
|
||||||
log.info("init")
|
log.info("init")
|
||||||
|
@ -308,9 +286,7 @@ export namespace Provider {
|
||||||
database[providerID] = parsed
|
database[providerID] = parsed
|
||||||
}
|
}
|
||||||
|
|
||||||
const disabled = await Config.get().then(
|
const disabled = await Config.get().then((cfg) => new Set(cfg.disabled_providers ?? []))
|
||||||
(cfg) => new Set(cfg.disabled_providers ?? []),
|
|
||||||
)
|
|
||||||
// load env
|
// load env
|
||||||
for (const [providerID, provider] of Object.entries(database)) {
|
for (const [providerID, provider] of Object.entries(database)) {
|
||||||
if (disabled.has(providerID)) continue
|
if (disabled.has(providerID)) continue
|
||||||
|
@ -337,12 +313,7 @@ export namespace Provider {
|
||||||
if (disabled.has(providerID)) continue
|
if (disabled.has(providerID)) continue
|
||||||
const result = await fn(database[providerID])
|
const result = await fn(database[providerID])
|
||||||
if (result && (result.autoload || providers[providerID])) {
|
if (result && (result.autoload || providers[providerID])) {
|
||||||
mergeProvider(
|
mergeProvider(providerID, result.options ?? {}, "custom", result.getModel)
|
||||||
providerID,
|
|
||||||
result.options ?? {},
|
|
||||||
"custom",
|
|
||||||
result.getModel,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -379,7 +350,7 @@ export namespace Provider {
|
||||||
const existing = s.sdk.get(provider.id)
|
const existing = s.sdk.get(provider.id)
|
||||||
if (existing) return existing
|
if (existing) return existing
|
||||||
const pkg = provider.npm ?? provider.id
|
const pkg = provider.npm ?? provider.id
|
||||||
const mod = await import(await BunProc.install(pkg, "latest"))
|
const mod = await import(await BunProc.install(pkg, "beta"))
|
||||||
const fn = mod[Object.keys(mod).find((key) => key.startsWith("create"))!]
|
const fn = mod[Object.keys(mod).find((key) => key.startsWith("create"))!]
|
||||||
const loaded = fn(s.providers[provider.id]?.options)
|
const loaded = fn(s.providers[provider.id]?.options)
|
||||||
s.sdk.set(provider.id, loaded)
|
s.sdk.set(provider.id, loaded)
|
||||||
|
@ -406,9 +377,7 @@ export namespace Provider {
|
||||||
const sdk = await getSDK(provider.info)
|
const sdk = await getSDK(provider.info)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const language = provider.getModel
|
const language = provider.getModel ? await provider.getModel(sdk, modelID) : sdk.languageModel(modelID)
|
||||||
? await provider.getModel(sdk, modelID)
|
|
||||||
: sdk.languageModel(modelID)
|
|
||||||
log.info("found", { providerID, modelID })
|
log.info("found", { providerID, modelID })
|
||||||
s.models.set(key, {
|
s.models.set(key, {
|
||||||
info,
|
info,
|
||||||
|
@ -435,10 +404,7 @@ export namespace Provider {
|
||||||
export function sort(models: ModelsDev.Model[]) {
|
export function sort(models: ModelsDev.Model[]) {
|
||||||
return sortBy(
|
return sortBy(
|
||||||
models,
|
models,
|
||||||
[
|
[(model) => priority.findIndex((filter) => model.id.includes(filter)), "desc"],
|
||||||
(model) => priority.findIndex((filter) => model.id.includes(filter)),
|
|
||||||
"desc",
|
|
||||||
],
|
|
||||||
[(model) => (model.id.includes("latest") ? 0 : 1), "asc"],
|
[(model) => (model.id.includes("latest") ? 0 : 1), "asc"],
|
||||||
[(model) => model.id, "desc"],
|
[(model) => model.id, "desc"],
|
||||||
)
|
)
|
||||||
|
@ -449,11 +415,7 @@ export namespace Provider {
|
||||||
if (cfg.model) return parseModel(cfg.model)
|
if (cfg.model) return parseModel(cfg.model)
|
||||||
const provider = await list()
|
const provider = await list()
|
||||||
.then((val) => Object.values(val))
|
.then((val) => Object.values(val))
|
||||||
.then((x) =>
|
.then((x) => x.find((p) => !cfg.provider || Object.keys(cfg.provider).includes(p.info.id)))
|
||||||
x.find(
|
|
||||||
(p) => !cfg.provider || Object.keys(cfg.provider).includes(p.info.id),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if (!provider) throw new Error("no providers found")
|
if (!provider) throw new Error("no providers found")
|
||||||
const [model] = sort(Object.values(provider.info.models))
|
const [model] = sort(Object.values(provider.info.models))
|
||||||
if (!model) throw new Error("no models found")
|
if (!model) throw new Error("no models found")
|
||||||
|
@ -536,9 +498,11 @@ export namespace Provider {
|
||||||
|
|
||||||
if (schema instanceof z.ZodUnion) {
|
if (schema instanceof z.ZodUnion) {
|
||||||
return z.union(
|
return z.union(
|
||||||
schema.options.map((option: z.ZodTypeAny) =>
|
schema.options.map((option: z.ZodTypeAny) => optionalToNullable(option)) as [
|
||||||
optionalToNullable(option),
|
z.ZodTypeAny,
|
||||||
) as [z.ZodTypeAny, z.ZodTypeAny, ...z.ZodTypeAny[]],
|
z.ZodTypeAny,
|
||||||
|
...z.ZodTypeAny[],
|
||||||
|
],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,21 @@
|
||||||
import type { LanguageModelV1Prompt } from "ai"
|
import type { ModelMessage } from "ai"
|
||||||
import { unique } from "remeda"
|
import { unique } from "remeda"
|
||||||
|
|
||||||
export namespace ProviderTransform {
|
export namespace ProviderTransform {
|
||||||
export function message(
|
export function message(msgs: ModelMessage[], providerID: string, modelID: string) {
|
||||||
msgs: LanguageModelV1Prompt,
|
|
||||||
providerID: string,
|
|
||||||
modelID: string,
|
|
||||||
) {
|
|
||||||
if (providerID === "anthropic" || modelID.includes("anthropic")) {
|
if (providerID === "anthropic" || modelID.includes("anthropic")) {
|
||||||
const system = msgs.filter((msg) => msg.role === "system").slice(0, 2)
|
const system = msgs.filter((msg) => msg.role === "system").slice(0, 2)
|
||||||
const final = msgs.filter((msg) => msg.role !== "system").slice(-2)
|
const final = msgs.filter((msg) => msg.role !== "system").slice(-2)
|
||||||
|
|
||||||
for (const msg of unique([...system, ...final])) {
|
for (const msg of unique([...system, ...final])) {
|
||||||
msg.providerMetadata = {
|
msg.providerOptions = {
|
||||||
...msg.providerMetadata,
|
...msg.providerOptions,
|
||||||
anthropic: {
|
anthropic: {
|
||||||
cacheControl: { type: "ephemeral" },
|
cacheControl: { type: "ephemeral" },
|
||||||
},
|
},
|
||||||
|
openaiCompatible: {
|
||||||
|
cache_control: { type: "ephemeral" },
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,8 +24,8 @@ export namespace ProviderTransform {
|
||||||
const final = msgs.filter((msg) => msg.role !== "system").slice(-2)
|
const final = msgs.filter((msg) => msg.role !== "system").slice(-2)
|
||||||
|
|
||||||
for (const msg of unique([...system, ...final])) {
|
for (const msg of unique([...system, ...final])) {
|
||||||
msg.providerMetadata = {
|
msg.providerOptions = {
|
||||||
...msg.providerMetadata,
|
...msg.providerOptions,
|
||||||
bedrock: {
|
bedrock: {
|
||||||
cachePoint: { type: "ephemeral" },
|
cachePoint: { type: "ephemeral" },
|
||||||
},
|
},
|
||||||
|
|
|
@ -6,7 +6,6 @@ import { streamSSE } from "hono/streaming"
|
||||||
import { Session } from "../session"
|
import { Session } from "../session"
|
||||||
import { resolver, validator as zValidator } from "hono-openapi/zod"
|
import { resolver, validator as zValidator } from "hono-openapi/zod"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
import { Message } from "../session/message"
|
|
||||||
import { Provider } from "../provider/provider"
|
import { Provider } from "../provider/provider"
|
||||||
import { App } from "../app/app"
|
import { App } from "../app/app"
|
||||||
import { mapValues } from "remeda"
|
import { mapValues } from "remeda"
|
||||||
|
@ -16,6 +15,7 @@ import { Ripgrep } from "../file/ripgrep"
|
||||||
import { Config } from "../config/config"
|
import { Config } from "../config/config"
|
||||||
import { File } from "../file"
|
import { File } from "../file"
|
||||||
import { LSP } from "../lsp"
|
import { LSP } from "../lsp"
|
||||||
|
import { MessageV2 } from "../session/message-v2"
|
||||||
|
|
||||||
const ERRORS = {
|
const ERRORS = {
|
||||||
400: {
|
400: {
|
||||||
|
@ -51,12 +51,9 @@ export namespace Server {
|
||||||
status: 400,
|
status: 400,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return c.json(
|
return c.json(new NamedError.Unknown({ message: err.toString() }).toObject(), {
|
||||||
new NamedError.Unknown({ message: err.toString() }).toObject(),
|
status: 400,
|
||||||
{
|
})
|
||||||
status: 400,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.use(async (c, next) => {
|
.use(async (c, next) => {
|
||||||
log.info("request", {
|
log.info("request", {
|
||||||
|
@ -407,7 +404,7 @@ export namespace Server {
|
||||||
description: "List of messages",
|
description: "List of messages",
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
schema: resolver(Message.Info.array()),
|
schema: resolver(MessageV2.Info.array()),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -433,7 +430,7 @@ export namespace Server {
|
||||||
description: "Created message",
|
description: "Created message",
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
schema: resolver(Message.Info),
|
schema: resolver(MessageV2.Assistant),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -450,7 +447,7 @@ export namespace Server {
|
||||||
z.object({
|
z.object({
|
||||||
providerID: z.string(),
|
providerID: z.string(),
|
||||||
modelID: z.string(),
|
modelID: z.string(),
|
||||||
parts: Message.MessagePart.array(),
|
parts: MessageV2.UserPart.array(),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
|
@ -481,15 +478,10 @@ export namespace Server {
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
const providers = await Provider.list().then((x) =>
|
const providers = await Provider.list().then((x) => mapValues(x, (item) => item.info))
|
||||||
mapValues(x, (item) => item.info),
|
|
||||||
)
|
|
||||||
return c.json({
|
return c.json({
|
||||||
providers: Object.values(providers),
|
providers: Object.values(providers),
|
||||||
default: mapValues(
|
default: mapValues(providers, (item) => Provider.sort(Object.values(item.models))[0].id),
|
||||||
providers,
|
|
||||||
(item) => Provider.sort(Object.values(item.models))[0].id,
|
|
||||||
),
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
File diff suppressed because it is too large
Load diff
398
packages/opencode/src/session/message-v2.ts
Normal file
398
packages/opencode/src/session/message-v2.ts
Normal file
|
@ -0,0 +1,398 @@
|
||||||
|
import z from "zod"
|
||||||
|
import { Bus } from "../bus"
|
||||||
|
import { Provider } from "../provider/provider"
|
||||||
|
import { NamedError } from "../util/error"
|
||||||
|
import { Message } from "./message"
|
||||||
|
import { convertToModelMessages, type ModelMessage, type UIMessage } from "ai"
|
||||||
|
|
||||||
|
export namespace MessageV2 {
|
||||||
|
export const OutputLengthError = NamedError.create("MessageOutputLengthError", z.object({}))
|
||||||
|
|
||||||
|
export const ToolStatePending = z
|
||||||
|
.object({
|
||||||
|
status: z.literal("pending"),
|
||||||
|
})
|
||||||
|
.openapi({
|
||||||
|
ref: "ToolStatePending",
|
||||||
|
})
|
||||||
|
|
||||||
|
export type ToolStatePending = z.infer<typeof ToolStatePending>
|
||||||
|
|
||||||
|
export const ToolStateRunning = z
|
||||||
|
.object({
|
||||||
|
status: z.literal("running"),
|
||||||
|
input: z.any(),
|
||||||
|
title: z.string().optional(),
|
||||||
|
metadata: z.record(z.any()).optional(),
|
||||||
|
time: z.object({
|
||||||
|
start: z.number(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.openapi({
|
||||||
|
ref: "ToolStateRunning",
|
||||||
|
})
|
||||||
|
export type ToolStateRunning = z.infer<typeof ToolStateRunning>
|
||||||
|
|
||||||
|
export const ToolStateCompleted = z
|
||||||
|
.object({
|
||||||
|
status: z.literal("completed"),
|
||||||
|
input: z.record(z.any()),
|
||||||
|
output: z.string(),
|
||||||
|
title: z.string(),
|
||||||
|
metadata: z.record(z.any()),
|
||||||
|
time: z.object({
|
||||||
|
start: z.number(),
|
||||||
|
end: z.number(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.openapi({
|
||||||
|
ref: "ToolStateCompleted",
|
||||||
|
})
|
||||||
|
export type ToolStateCompleted = z.infer<typeof ToolStateCompleted>
|
||||||
|
|
||||||
|
export const ToolStateError = z
|
||||||
|
.object({
|
||||||
|
status: z.literal("error"),
|
||||||
|
input: z.record(z.any()),
|
||||||
|
error: z.string(),
|
||||||
|
time: z.object({
|
||||||
|
start: z.number(),
|
||||||
|
end: z.number(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.openapi({
|
||||||
|
ref: "ToolStateError",
|
||||||
|
})
|
||||||
|
export type ToolStateError = z.infer<typeof ToolStateError>
|
||||||
|
|
||||||
|
export const ToolState = z
|
||||||
|
.discriminatedUnion("status", [ToolStatePending, ToolStateRunning, ToolStateCompleted, ToolStateError])
|
||||||
|
.openapi({
|
||||||
|
ref: "ToolState",
|
||||||
|
})
|
||||||
|
|
||||||
|
export const TextPart = z
|
||||||
|
.object({
|
||||||
|
type: z.literal("text"),
|
||||||
|
text: z.string(),
|
||||||
|
})
|
||||||
|
.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 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 type FilePart = z.infer<typeof FilePart>
|
||||||
|
|
||||||
|
export const StepStartPart = z
|
||||||
|
.object({
|
||||||
|
type: z.literal("step-start"),
|
||||||
|
})
|
||||||
|
.openapi({
|
||||||
|
ref: "StepStartPart",
|
||||||
|
})
|
||||||
|
export type StepStartPart = z.infer<typeof StepStartPart>
|
||||||
|
|
||||||
|
const Base = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
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(),
|
||||||
|
}),
|
||||||
|
}).openapi({
|
||||||
|
ref: "UserMessage",
|
||||||
|
})
|
||||||
|
export type User = z.infer<typeof User>
|
||||||
|
|
||||||
|
export const AssistantPart = z.discriminatedUnion("type", [TextPart, ToolPart, StepStartPart]).openapi({
|
||||||
|
ref: "AssistantMessagePart",
|
||||||
|
})
|
||||||
|
export type AssistantPart = z.infer<typeof AssistantPart>
|
||||||
|
|
||||||
|
export const Assistant = Base.extend({
|
||||||
|
role: z.literal("assistant"),
|
||||||
|
parts: z.array(AssistantPart),
|
||||||
|
time: z.object({
|
||||||
|
created: z.number(),
|
||||||
|
completed: z.number().optional(),
|
||||||
|
}),
|
||||||
|
error: z
|
||||||
|
.discriminatedUnion("name", [Provider.AuthError.Schema, NamedError.Unknown.Schema, OutputLengthError.Schema])
|
||||||
|
.optional(),
|
||||||
|
system: z.string().array(),
|
||||||
|
modelID: z.string(),
|
||||||
|
providerID: z.string(),
|
||||||
|
path: z.object({
|
||||||
|
cwd: z.string(),
|
||||||
|
root: z.string(),
|
||||||
|
}),
|
||||||
|
cost: z.number(),
|
||||||
|
summary: z.boolean().optional(),
|
||||||
|
tokens: z.object({
|
||||||
|
input: z.number(),
|
||||||
|
output: z.number(),
|
||||||
|
reasoning: z.number(),
|
||||||
|
cache: z.object({
|
||||||
|
read: z.number(),
|
||||||
|
write: z.number(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}).openapi({
|
||||||
|
ref: "AssistantMessage",
|
||||||
|
})
|
||||||
|
export type Assistant = z.infer<typeof Assistant>
|
||||||
|
|
||||||
|
export const Info = z.discriminatedUnion("role", [User, Assistant]).openapi({
|
||||||
|
ref: "Message",
|
||||||
|
})
|
||||||
|
export type Info = z.infer<typeof Info>
|
||||||
|
|
||||||
|
export const Event = {
|
||||||
|
Updated: Bus.event(
|
||||||
|
"message.updated",
|
||||||
|
z.object({
|
||||||
|
info: Info,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
Removed: Bus.event(
|
||||||
|
"message.removed",
|
||||||
|
z.object({
|
||||||
|
sessionID: z.string(),
|
||||||
|
messageID: z.string(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
PartUpdated: Bus.event(
|
||||||
|
"message.part.updated",
|
||||||
|
z.object({
|
||||||
|
part: AssistantPart,
|
||||||
|
sessionID: z.string(),
|
||||||
|
messageID: z.string(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fromV1(v1: Message.Info) {
|
||||||
|
if (v1.role === "assistant") {
|
||||||
|
const result: Assistant = {
|
||||||
|
id: v1.id,
|
||||||
|
sessionID: v1.metadata.sessionID,
|
||||||
|
role: "assistant",
|
||||||
|
time: {
|
||||||
|
created: v1.metadata.time.created,
|
||||||
|
completed: v1.metadata.time.completed,
|
||||||
|
},
|
||||||
|
cost: v1.metadata.assistant!.cost,
|
||||||
|
path: v1.metadata.assistant!.path,
|
||||||
|
summary: v1.metadata.assistant!.summary,
|
||||||
|
tokens: v1.metadata.assistant!.tokens,
|
||||||
|
modelID: v1.metadata.assistant!.modelID,
|
||||||
|
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 { 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error("unknown tool invocation state")
|
||||||
|
})(),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v1.role === "user") {
|
||||||
|
const result: 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toModelMessage(input: Info[]): ModelMessage[] {
|
||||||
|
const result: UIMessage[] = []
|
||||||
|
|
||||||
|
for (const msg of input) {
|
||||||
|
if (msg.parts.length === 0) continue
|
||||||
|
if (msg.role === "user") {
|
||||||
|
result.push({
|
||||||
|
id: msg.id,
|
||||||
|
role: "user",
|
||||||
|
parts: msg.parts.flatMap((part): UIMessage["parts"] => {
|
||||||
|
if (part.type === "text")
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: part.text,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
if (part.type === "file")
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: "file",
|
||||||
|
url: part.url,
|
||||||
|
mediaType: part.mime,
|
||||||
|
filename: part.filename,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
return []
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.role === "assistant") {
|
||||||
|
result.push({
|
||||||
|
id: msg.id,
|
||||||
|
role: "assistant",
|
||||||
|
parts: msg.parts.flatMap((part): UIMessage["parts"] => {
|
||||||
|
if (part.type === "text")
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: part.text,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
if (part.type === "step-start")
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: "step-start",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
if (part.type === "tool") {
|
||||||
|
if (part.state.status === "completed")
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: ("tool-" + part.tool) as `tool-${string}`,
|
||||||
|
state: "output-available",
|
||||||
|
toolCallId: part.id,
|
||||||
|
input: part.state.input,
|
||||||
|
output: part.state.output,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
if (part.state.status === "error")
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: ("tool-" + part.tool) as `tool-${string}`,
|
||||||
|
state: "output-error",
|
||||||
|
toolCallId: part.id,
|
||||||
|
input: part.state.input,
|
||||||
|
errorText: part.state.error,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
return []
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return convertToModelMessages(result)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,9 @@
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { Bus } from "../bus"
|
|
||||||
import { Provider } from "../provider/provider"
|
import { Provider } from "../provider/provider"
|
||||||
import { NamedError } from "../util/error"
|
import { NamedError } from "../util/error"
|
||||||
|
|
||||||
export namespace Message {
|
export namespace Message {
|
||||||
export const OutputLengthError = NamedError.create(
|
export const OutputLengthError = NamedError.create("MessageOutputLengthError", z.object({}))
|
||||||
"MessageOutputLengthError",
|
|
||||||
z.object({}),
|
|
||||||
)
|
|
||||||
|
|
||||||
export const ToolCall = z
|
export const ToolCall = z
|
||||||
.object({
|
.object({
|
||||||
|
@ -49,11 +45,9 @@ export namespace Message {
|
||||||
})
|
})
|
||||||
export type ToolResult = z.infer<typeof ToolResult>
|
export type ToolResult = z.infer<typeof ToolResult>
|
||||||
|
|
||||||
export const ToolInvocation = z
|
export const ToolInvocation = z.discriminatedUnion("state", [ToolCall, ToolPartialCall, ToolResult]).openapi({
|
||||||
.discriminatedUnion("state", [ToolCall, ToolPartialCall, ToolResult])
|
ref: "ToolInvocation",
|
||||||
.openapi({
|
})
|
||||||
ref: "ToolInvocation",
|
|
||||||
})
|
|
||||||
export type ToolInvocation = z.infer<typeof ToolInvocation>
|
export type ToolInvocation = z.infer<typeof ToolInvocation>
|
||||||
|
|
||||||
export const TextPart = z
|
export const TextPart = z
|
||||||
|
@ -122,14 +116,7 @@ export namespace Message {
|
||||||
export type StepStartPart = z.infer<typeof StepStartPart>
|
export type StepStartPart = z.infer<typeof StepStartPart>
|
||||||
|
|
||||||
export const MessagePart = z
|
export const MessagePart = z
|
||||||
.discriminatedUnion("type", [
|
.discriminatedUnion("type", [TextPart, ReasoningPart, ToolInvocationPart, SourceUrlPart, FilePart, StepStartPart])
|
||||||
TextPart,
|
|
||||||
ReasoningPart,
|
|
||||||
ToolInvocationPart,
|
|
||||||
SourceUrlPart,
|
|
||||||
FilePart,
|
|
||||||
StepStartPart,
|
|
||||||
])
|
|
||||||
.openapi({
|
.openapi({
|
||||||
ref: "MessagePart",
|
ref: "MessagePart",
|
||||||
})
|
})
|
||||||
|
@ -197,28 +184,4 @@ export namespace Message {
|
||||||
ref: "Message",
|
ref: "Message",
|
||||||
})
|
})
|
||||||
export type Info = z.infer<typeof Info>
|
export type Info = z.infer<typeof Info>
|
||||||
|
|
||||||
export const Event = {
|
|
||||||
Updated: Bus.event(
|
|
||||||
"message.updated",
|
|
||||||
z.object({
|
|
||||||
info: Info,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
Removed: Bus.event(
|
|
||||||
"message.removed",
|
|
||||||
z.object({
|
|
||||||
sessionID: z.string(),
|
|
||||||
messageID: z.string(),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
PartUpdated: Bus.event(
|
|
||||||
"message.part.updated",
|
|
||||||
z.object({
|
|
||||||
part: MessagePart,
|
|
||||||
sessionID: z.string(),
|
|
||||||
messageID: z.string(),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,9 +53,7 @@ export namespace Share {
|
||||||
|
|
||||||
export const URL =
|
export const URL =
|
||||||
process.env["OPENCODE_API"] ??
|
process.env["OPENCODE_API"] ??
|
||||||
(Installation.isSnapshot() || Installation.isDev()
|
(Installation.isSnapshot() || Installation.isDev() ? "https://api.dev.opencode.ai" : "https://api.opencode.ai")
|
||||||
? "https://api.dev.opencode.ai"
|
|
||||||
: "https://api.opencode.ai")
|
|
||||||
|
|
||||||
export async function create(sessionID: string) {
|
export async function create(sessionID: string) {
|
||||||
return fetch(`${URL}/share_create`, {
|
return fetch(`${URL}/share_create`, {
|
||||||
|
|
|
@ -1,14 +1,7 @@
|
||||||
import { App } from "../app/app"
|
import { App } from "../app/app"
|
||||||
import {
|
import { $ } from "bun"
|
||||||
add,
|
|
||||||
commit,
|
|
||||||
init,
|
|
||||||
checkout,
|
|
||||||
statusMatrix,
|
|
||||||
remove,
|
|
||||||
} from "isomorphic-git"
|
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import fs from "fs"
|
import fs from "fs/promises"
|
||||||
import { Ripgrep } from "../file/ripgrep"
|
import { Ripgrep } from "../file/ripgrep"
|
||||||
import { Log } from "../util/log"
|
import { Log } from "../util/log"
|
||||||
|
|
||||||
|
@ -16,79 +9,53 @@ export namespace Snapshot {
|
||||||
const log = Log.create({ service: "snapshot" })
|
const log = Log.create({ service: "snapshot" })
|
||||||
|
|
||||||
export async function create(sessionID: string) {
|
export async function create(sessionID: string) {
|
||||||
|
return
|
||||||
log.info("creating snapshot")
|
log.info("creating snapshot")
|
||||||
const app = App.info()
|
const app = App.info()
|
||||||
const git = gitdir(sessionID)
|
const git = gitdir(sessionID)
|
||||||
const files = await Ripgrep.files({
|
|
||||||
cwd: app.path.cwd,
|
// not a git repo, check if too big to snapshot
|
||||||
limit: app.git ? undefined : 1000,
|
if (!app.git) {
|
||||||
})
|
const files = await Ripgrep.files({
|
||||||
log.info("found files", { count: files.length })
|
cwd: app.path.cwd,
|
||||||
// not a git repo and too big to snapshot
|
limit: 1000,
|
||||||
if (!app.git && files.length === 1000) return
|
})
|
||||||
await init({
|
log.info("found files", { count: files.length })
|
||||||
dir: app.path.cwd,
|
if (files.length > 1000) return
|
||||||
gitdir: git,
|
|
||||||
fs,
|
|
||||||
})
|
|
||||||
log.info("initialized")
|
|
||||||
const status = await statusMatrix({
|
|
||||||
fs,
|
|
||||||
gitdir: git,
|
|
||||||
dir: app.path.cwd,
|
|
||||||
})
|
|
||||||
log.info("matrix", {
|
|
||||||
count: status.length,
|
|
||||||
})
|
|
||||||
const added = []
|
|
||||||
for (const [file, head, workdir, stage] of status) {
|
|
||||||
if (workdir === 0 && stage === 1) {
|
|
||||||
log.info("remove", { file })
|
|
||||||
await remove({
|
|
||||||
fs,
|
|
||||||
gitdir: git,
|
|
||||||
dir: app.path.cwd,
|
|
||||||
filepath: file,
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (workdir !== head) {
|
|
||||||
added.push(file)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
log.info("removed files")
|
|
||||||
await add({
|
if (await fs.mkdir(git, { recursive: true })) {
|
||||||
fs,
|
await $`git init`
|
||||||
gitdir: git,
|
.env({
|
||||||
parallel: true,
|
...process.env,
|
||||||
dir: app.path.cwd,
|
GIT_DIR: git,
|
||||||
filepath: added,
|
GIT_WORK_TREE: app.path.root,
|
||||||
})
|
})
|
||||||
|
.quiet()
|
||||||
|
.nothrow()
|
||||||
|
log.info("initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
await $`git --git-dir ${git} add .`.quiet().cwd(app.path.cwd).nothrow()
|
||||||
log.info("added files")
|
log.info("added files")
|
||||||
const result = await commit({
|
|
||||||
fs,
|
const result =
|
||||||
gitdir: git,
|
await $`git --git-dir ${git} commit --allow-empty -m "snapshot" --author="opencode <mail@opencode.ai>"`
|
||||||
dir: app.path.cwd,
|
.quiet()
|
||||||
message: "snapshot",
|
.cwd(app.path.cwd)
|
||||||
author: {
|
.nothrow()
|
||||||
name: "opencode",
|
log.info("commit")
|
||||||
email: "mail@opencode.ai",
|
|
||||||
},
|
const match = result.stdout.toString().match(/\[.+ ([a-f0-9]+)\]/)
|
||||||
})
|
if (!match) return
|
||||||
log.info("commit", { result })
|
return match![1]
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function restore(sessionID: string, commit: string) {
|
export async function restore(sessionID: string, commit: string) {
|
||||||
log.info("restore", { commit })
|
log.info("restore", { commit })
|
||||||
const app = App.info()
|
const app = App.info()
|
||||||
await checkout({
|
const git = gitdir(sessionID)
|
||||||
fs,
|
await $`git --git-dir=${git} checkout ${commit} --force`.quiet().cwd(app.path.root)
|
||||||
gitdir: gitdir(sessionID),
|
|
||||||
dir: app.path.cwd,
|
|
||||||
ref: commit,
|
|
||||||
force: true,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function gitdir(sessionID: string) {
|
function gitdir(sessionID: string) {
|
||||||
|
|
|
@ -4,44 +4,79 @@ import { Bus } from "../bus"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import fs from "fs/promises"
|
import fs from "fs/promises"
|
||||||
|
import { MessageV2 } from "../session/message-v2"
|
||||||
|
|
||||||
export namespace Storage {
|
export namespace Storage {
|
||||||
const log = Log.create({ service: "storage" })
|
const log = Log.create({ service: "storage" })
|
||||||
|
|
||||||
export const Event = {
|
export const Event = {
|
||||||
Write: Bus.event(
|
Write: Bus.event("storage.write", z.object({ key: z.string(), content: z.any() })),
|
||||||
"storage.write",
|
|
||||||
z.object({ key: z.string(), content: z.any() }),
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const state = App.state("storage", () => {
|
type Migration = (dir: string) => Promise<void>
|
||||||
|
|
||||||
|
const MIGRATIONS: Migration[] = [
|
||||||
|
async (dir: string) => {
|
||||||
|
try {
|
||||||
|
const files = new Bun.Glob("session/message/*/*.json").scanSync({
|
||||||
|
cwd: dir,
|
||||||
|
absolute: true,
|
||||||
|
})
|
||||||
|
for (const file of files) {
|
||||||
|
const content = await Bun.file(file).json()
|
||||||
|
if (!content.metadata) continue
|
||||||
|
log.info("migrating to v2 message", { file })
|
||||||
|
try {
|
||||||
|
const result = MessageV2.fromV1(content)
|
||||||
|
await Bun.write(file, JSON.stringify(result, null, 2))
|
||||||
|
} catch (e) {
|
||||||
|
await fs.rename(file, file.replace("storage", "broken"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const state = App.state("storage", async () => {
|
||||||
const app = App.info()
|
const app = App.info()
|
||||||
const dir = path.join(app.path.data, "storage")
|
const dir = path.join(app.path.data, "storage")
|
||||||
log.info("init", { path: dir })
|
const migration = await Bun.file(path.join(dir, "migration"))
|
||||||
|
.json()
|
||||||
|
.then((x) => parseInt(x))
|
||||||
|
.catch(() => 0)
|
||||||
|
for (let index = migration; index < MIGRATIONS.length; index++) {
|
||||||
|
log.info("running migration", { index })
|
||||||
|
const migration = MIGRATIONS[index]
|
||||||
|
await migration(dir)
|
||||||
|
await Bun.write(path.join(dir, "migration"), (index + 1).toString())
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
dir,
|
dir,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export async function remove(key: string) {
|
export async function remove(key: string) {
|
||||||
const target = path.join(state().dir, key + ".json")
|
const dir = await state().then((x) => x.dir)
|
||||||
|
const target = path.join(dir, key + ".json")
|
||||||
await fs.unlink(target).catch(() => {})
|
await fs.unlink(target).catch(() => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removeDir(key: string) {
|
export async function removeDir(key: string) {
|
||||||
const target = path.join(state().dir, key)
|
const dir = await state().then((x) => x.dir)
|
||||||
|
const target = path.join(dir, key)
|
||||||
await fs.rm(target, { recursive: true, force: true }).catch(() => {})
|
await fs.rm(target, { recursive: true, force: true }).catch(() => {})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function readJSON<T>(key: string) {
|
export async function readJSON<T>(key: string) {
|
||||||
return Bun.file(path.join(state().dir, key + ".json")).json() as Promise<T>
|
const dir = await state().then((x) => x.dir)
|
||||||
|
return Bun.file(path.join(dir, key + ".json")).json() as Promise<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function writeJSON<T>(key: string, content: T) {
|
export async function writeJSON<T>(key: string, content: T) {
|
||||||
const target = path.join(state().dir, key + ".json")
|
const dir = await state().then((x) => x.dir)
|
||||||
|
const target = path.join(dir, key + ".json")
|
||||||
const tmp = target + Date.now() + ".tmp"
|
const tmp = target + Date.now() + ".tmp"
|
||||||
await Bun.write(tmp, JSON.stringify(content))
|
await Bun.write(tmp, JSON.stringify(content, null, 2))
|
||||||
await fs.rename(tmp, target).catch(() => {})
|
await fs.rename(tmp, target).catch(() => {})
|
||||||
await fs.unlink(tmp).catch(() => {})
|
await fs.unlink(tmp).catch(() => {})
|
||||||
Bus.publish(Event.Write, { key, content })
|
Bus.publish(Event.Write, { key, content })
|
||||||
|
@ -49,9 +84,10 @@ export namespace Storage {
|
||||||
|
|
||||||
const glob = new Bun.Glob("**/*")
|
const glob = new Bun.Glob("**/*")
|
||||||
export async function* list(prefix: string) {
|
export async function* list(prefix: string) {
|
||||||
|
const dir = await state().then((x) => x.dir)
|
||||||
try {
|
try {
|
||||||
for await (const item of glob.scan({
|
for await (const item of glob.scan({
|
||||||
cwd: path.join(state().dir, prefix),
|
cwd: path.join(dir, prefix),
|
||||||
onlyFiles: true,
|
onlyFiles: true,
|
||||||
})) {
|
})) {
|
||||||
const result = path.join(prefix, item.slice(0, -5))
|
const result = path.join(prefix, item.slice(0, -5))
|
||||||
|
|
|
@ -4,25 +4,6 @@ import DESCRIPTION from "./bash.txt"
|
||||||
import { App } from "../app/app"
|
import { App } from "../app/app"
|
||||||
|
|
||||||
const MAX_OUTPUT_LENGTH = 30000
|
const MAX_OUTPUT_LENGTH = 30000
|
||||||
const BANNED_COMMANDS = [
|
|
||||||
"alias",
|
|
||||||
"curl",
|
|
||||||
"curlie",
|
|
||||||
"wget",
|
|
||||||
"axel",
|
|
||||||
"aria2c",
|
|
||||||
"nc",
|
|
||||||
"telnet",
|
|
||||||
"lynx",
|
|
||||||
"w3m",
|
|
||||||
"links",
|
|
||||||
"httpie",
|
|
||||||
"xh",
|
|
||||||
"http-prompt",
|
|
||||||
"chrome",
|
|
||||||
"firefox",
|
|
||||||
"safari",
|
|
||||||
]
|
|
||||||
const DEFAULT_TIMEOUT = 1 * 60 * 1000
|
const DEFAULT_TIMEOUT = 1 * 60 * 1000
|
||||||
const MAX_TIMEOUT = 10 * 60 * 1000
|
const MAX_TIMEOUT = 10 * 60 * 1000
|
||||||
|
|
||||||
|
@ -31,12 +12,7 @@ export const BashTool = Tool.define({
|
||||||
description: DESCRIPTION,
|
description: DESCRIPTION,
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
command: z.string().describe("The command to execute"),
|
command: z.string().describe("The command to execute"),
|
||||||
timeout: z
|
timeout: z.number().min(0).max(MAX_TIMEOUT).describe("Optional timeout in milliseconds").optional(),
|
||||||
.number()
|
|
||||||
.min(0)
|
|
||||||
.max(MAX_TIMEOUT)
|
|
||||||
.describe("Optional timeout in milliseconds")
|
|
||||||
.optional(),
|
|
||||||
description: z
|
description: z
|
||||||
.string()
|
.string()
|
||||||
.describe(
|
.describe(
|
||||||
|
@ -45,8 +21,6 @@ export const BashTool = Tool.define({
|
||||||
}),
|
}),
|
||||||
async execute(params, ctx) {
|
async execute(params, ctx) {
|
||||||
const timeout = Math.min(params.timeout ?? DEFAULT_TIMEOUT, MAX_TIMEOUT)
|
const timeout = Math.min(params.timeout ?? DEFAULT_TIMEOUT, MAX_TIMEOUT)
|
||||||
if (BANNED_COMMANDS.some((item) => params.command.startsWith(item)))
|
|
||||||
throw new Error(`Command '${params.command}' is not allowed`)
|
|
||||||
|
|
||||||
const process = Bun.spawn({
|
const process = Bun.spawn({
|
||||||
cmd: ["bash", "-c", params.command],
|
cmd: ["bash", "-c", params.command],
|
||||||
|
@ -62,21 +36,14 @@ export const BashTool = Tool.define({
|
||||||
const stderr = await new Response(process.stderr).text()
|
const stderr = await new Response(process.stderr).text()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
title: params.command,
|
||||||
metadata: {
|
metadata: {
|
||||||
stderr,
|
stderr,
|
||||||
stdout,
|
stdout,
|
||||||
exit: process.exitCode,
|
exit: process.exitCode,
|
||||||
description: params.description,
|
description: params.description,
|
||||||
title: params.command,
|
|
||||||
},
|
},
|
||||||
output: [
|
output: [`<stdout>`, stdout ?? "", `</stdout>`, `<stderr>`, stderr ?? "", `</stderr>`].join("\n"),
|
||||||
`<stdout>`,
|
|
||||||
stdout ?? "",
|
|
||||||
`</stdout>`,
|
|
||||||
`<stderr>`,
|
|
||||||
stderr ?? "",
|
|
||||||
`</stderr>`,
|
|
||||||
].join("\n"),
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -20,15 +20,8 @@ export const EditTool = Tool.define({
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
filePath: z.string().describe("The absolute path to the file to modify"),
|
filePath: z.string().describe("The absolute path to the file to modify"),
|
||||||
oldString: z.string().describe("The text to replace"),
|
oldString: z.string().describe("The text to replace"),
|
||||||
newString: z
|
newString: z.string().describe("The text to replace it with (must be different from old_string)"),
|
||||||
.string()
|
replaceAll: z.boolean().optional().describe("Replace all occurrences of old_string (default false)"),
|
||||||
.describe(
|
|
||||||
"The text to replace it with (must be different from old_string)",
|
|
||||||
),
|
|
||||||
replaceAll: z
|
|
||||||
.boolean()
|
|
||||||
.optional()
|
|
||||||
.describe("Replace all occurrences of old_string (default false)"),
|
|
||||||
}),
|
}),
|
||||||
async execute(params, ctx) {
|
async execute(params, ctx) {
|
||||||
if (!params.filePath) {
|
if (!params.filePath) {
|
||||||
|
@ -40,9 +33,7 @@ export const EditTool = Tool.define({
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = App.info()
|
const app = App.info()
|
||||||
const filepath = path.isAbsolute(params.filePath)
|
const filepath = path.isAbsolute(params.filePath) ? params.filePath : path.join(app.path.cwd, params.filePath)
|
||||||
? params.filePath
|
|
||||||
: path.join(app.path.cwd, params.filePath)
|
|
||||||
|
|
||||||
await Permission.ask({
|
await Permission.ask({
|
||||||
id: "edit",
|
id: "edit",
|
||||||
|
@ -70,17 +61,11 @@ export const EditTool = Tool.define({
|
||||||
const file = Bun.file(filepath)
|
const file = Bun.file(filepath)
|
||||||
const stats = await file.stat().catch(() => {})
|
const stats = await file.stat().catch(() => {})
|
||||||
if (!stats) throw new Error(`File ${filepath} not found`)
|
if (!stats) throw new Error(`File ${filepath} not found`)
|
||||||
if (stats.isDirectory())
|
if (stats.isDirectory()) throw new Error(`Path is a directory, not a file: ${filepath}`)
|
||||||
throw new Error(`Path is a directory, not a file: ${filepath}`)
|
|
||||||
await FileTime.assert(ctx.sessionID, filepath)
|
await FileTime.assert(ctx.sessionID, filepath)
|
||||||
contentOld = await file.text()
|
contentOld = await file.text()
|
||||||
|
|
||||||
contentNew = replace(
|
contentNew = replace(contentOld, params.oldString, params.newString, params.replaceAll)
|
||||||
contentOld,
|
|
||||||
params.oldString,
|
|
||||||
params.newString,
|
|
||||||
params.replaceAll,
|
|
||||||
)
|
|
||||||
await file.write(contentNew)
|
await file.write(contentNew)
|
||||||
await Bus.publish(File.Event.Edited, {
|
await Bus.publish(File.Event.Edited, {
|
||||||
file: filepath,
|
file: filepath,
|
||||||
|
@ -88,9 +73,7 @@ export const EditTool = Tool.define({
|
||||||
contentNew = await file.text()
|
contentNew = await file.text()
|
||||||
})()
|
})()
|
||||||
|
|
||||||
const diff = trimDiff(
|
const diff = trimDiff(createTwoFilesPatch(filepath, filepath, contentOld, contentNew))
|
||||||
createTwoFilesPatch(filepath, filepath, contentOld, contentNew),
|
|
||||||
)
|
|
||||||
|
|
||||||
FileTime.read(ctx.sessionID, filepath)
|
FileTime.read(ctx.sessionID, filepath)
|
||||||
|
|
||||||
|
@ -110,17 +93,14 @@ export const EditTool = Tool.define({
|
||||||
metadata: {
|
metadata: {
|
||||||
diagnostics,
|
diagnostics,
|
||||||
diff,
|
diff,
|
||||||
title: `${path.relative(app.path.root, filepath)}`,
|
|
||||||
},
|
},
|
||||||
|
title: `${path.relative(app.path.root, filepath)}`,
|
||||||
output,
|
output,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export type Replacer = (
|
export type Replacer = (content: string, find: string) => Generator<string, void, unknown>
|
||||||
content: string,
|
|
||||||
find: string,
|
|
||||||
) => Generator<string, void, unknown>
|
|
||||||
|
|
||||||
export const SimpleReplacer: Replacer = function* (_content, find) {
|
export const SimpleReplacer: Replacer = function* (_content, find) {
|
||||||
yield find
|
yield find
|
||||||
|
@ -208,10 +188,7 @@ export const BlockAnchorReplacer: Replacer = function* (content, find) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WhitespaceNormalizedReplacer: Replacer = function* (
|
export const WhitespaceNormalizedReplacer: Replacer = function* (content, find) {
|
||||||
content,
|
|
||||||
find,
|
|
||||||
) {
|
|
||||||
const normalizeWhitespace = (text: string) => text.replace(/\s+/g, " ").trim()
|
const normalizeWhitespace = (text: string) => text.replace(/\s+/g, " ").trim()
|
||||||
const normalizedFind = normalizeWhitespace(find)
|
const normalizedFind = normalizeWhitespace(find)
|
||||||
|
|
||||||
|
@ -229,9 +206,7 @@ export const WhitespaceNormalizedReplacer: Replacer = function* (
|
||||||
// Find the actual substring in the original line that matches
|
// Find the actual substring in the original line that matches
|
||||||
const words = find.trim().split(/\s+/)
|
const words = find.trim().split(/\s+/)
|
||||||
if (words.length > 0) {
|
if (words.length > 0) {
|
||||||
const pattern = words
|
const pattern = words.map((word) => word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("\\s+")
|
||||||
.map((word) => word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
|
|
||||||
.join("\\s+")
|
|
||||||
try {
|
try {
|
||||||
const regex = new RegExp(pattern)
|
const regex = new RegExp(pattern)
|
||||||
const match = line.match(regex)
|
const match = line.match(regex)
|
||||||
|
@ -270,9 +245,7 @@ export const IndentationFlexibleReplacer: Replacer = function* (content, find) {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
return lines
|
return lines.map((line) => (line.trim().length === 0 ? line : line.slice(minIndent))).join("\n")
|
||||||
.map((line) => (line.trim().length === 0 ? line : line.slice(minIndent)))
|
|
||||||
.join("\n")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalizedFind = removeIndentation(find)
|
const normalizedFind = removeIndentation(find)
|
||||||
|
@ -423,10 +396,7 @@ export const ContextAwareReplacer: Replacer = function* (content, find) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (totalNonEmptyLines === 0 || matchingLines / totalNonEmptyLines >= 0.5) {
|
||||||
totalNonEmptyLines === 0 ||
|
|
||||||
matchingLines / totalNonEmptyLines >= 0.5
|
|
||||||
) {
|
|
||||||
yield block
|
yield block
|
||||||
break // Only match the first occurrence
|
break // Only match the first occurrence
|
||||||
}
|
}
|
||||||
|
@ -473,12 +443,7 @@ function trimDiff(diff: string): string {
|
||||||
return trimmedLines.join("\n")
|
return trimmedLines.join("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
export function replace(
|
export function replace(content: string, oldString: string, newString: string, replaceAll = false): string {
|
||||||
content: string,
|
|
||||||
oldString: string,
|
|
||||||
newString: string,
|
|
||||||
replaceAll = false,
|
|
||||||
): string {
|
|
||||||
if (oldString === newString) {
|
if (oldString === newString) {
|
||||||
throw new Error("oldString and newString must be different")
|
throw new Error("oldString and newString must be different")
|
||||||
}
|
}
|
||||||
|
@ -502,11 +467,7 @@ export function replace(
|
||||||
}
|
}
|
||||||
const lastIndex = content.lastIndexOf(search)
|
const lastIndex = content.lastIndexOf(search)
|
||||||
if (index !== lastIndex) continue
|
if (index !== lastIndex) continue
|
||||||
return (
|
return content.substring(0, index) + newString + content.substring(index + search.length)
|
||||||
content.substring(0, index) +
|
|
||||||
newString +
|
|
||||||
content.substring(index + search.length)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new Error("oldString not found in content or was found multiple times")
|
throw new Error("oldString not found in content or was found multiple times")
|
||||||
|
|
|
@ -20,9 +20,7 @@ export const GlobTool = Tool.define({
|
||||||
async execute(params) {
|
async execute(params) {
|
||||||
const app = App.info()
|
const app = App.info()
|
||||||
let search = params.path ?? app.path.cwd
|
let search = params.path ?? app.path.cwd
|
||||||
search = path.isAbsolute(search)
|
search = path.isAbsolute(search) ? search : path.resolve(app.path.cwd, search)
|
||||||
? search
|
|
||||||
: path.resolve(app.path.cwd, search)
|
|
||||||
|
|
||||||
const limit = 100
|
const limit = 100
|
||||||
const files = []
|
const files = []
|
||||||
|
@ -53,17 +51,15 @@ export const GlobTool = Tool.define({
|
||||||
output.push(...files.map((f) => f.path))
|
output.push(...files.map((f) => f.path))
|
||||||
if (truncated) {
|
if (truncated) {
|
||||||
output.push("")
|
output.push("")
|
||||||
output.push(
|
output.push("(Results are truncated. Consider using a more specific path or pattern.)")
|
||||||
"(Results are truncated. Consider using a more specific path or pattern.)",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
title: path.relative(app.path.root, search),
|
||||||
metadata: {
|
metadata: {
|
||||||
count: files.length,
|
count: files.length,
|
||||||
truncated,
|
truncated,
|
||||||
title: path.relative(app.path.root, search),
|
|
||||||
},
|
},
|
||||||
output: output.join("\n"),
|
output: output.join("\n"),
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,21 +9,9 @@ export const GrepTool = Tool.define({
|
||||||
id: "grep",
|
id: "grep",
|
||||||
description: DESCRIPTION,
|
description: DESCRIPTION,
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
pattern: z
|
pattern: z.string().describe("The regex pattern to search for in file contents"),
|
||||||
.string()
|
path: z.string().optional().describe("The directory to search in. Defaults to the current working directory."),
|
||||||
.describe("The regex pattern to search for in file contents"),
|
include: z.string().optional().describe('File pattern to include in the search (e.g. "*.js", "*.{ts,tsx}")'),
|
||||||
path: z
|
|
||||||
.string()
|
|
||||||
.optional()
|
|
||||||
.describe(
|
|
||||||
"The directory to search in. Defaults to the current working directory.",
|
|
||||||
),
|
|
||||||
include: z
|
|
||||||
.string()
|
|
||||||
.optional()
|
|
||||||
.describe(
|
|
||||||
'File pattern to include in the search (e.g. "*.js", "*.{ts,tsx}")',
|
|
||||||
),
|
|
||||||
}),
|
}),
|
||||||
async execute(params) {
|
async execute(params) {
|
||||||
if (!params.pattern) {
|
if (!params.pattern) {
|
||||||
|
@ -51,7 +39,8 @@ export const GrepTool = Tool.define({
|
||||||
|
|
||||||
if (exitCode === 1) {
|
if (exitCode === 1) {
|
||||||
return {
|
return {
|
||||||
metadata: { matches: 0, truncated: false, title: params.pattern },
|
title: params.pattern,
|
||||||
|
metadata: { matches: 0, truncated: false },
|
||||||
output: "No files found",
|
output: "No files found",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,7 +82,8 @@ export const GrepTool = Tool.define({
|
||||||
|
|
||||||
if (finalMatches.length === 0) {
|
if (finalMatches.length === 0) {
|
||||||
return {
|
return {
|
||||||
metadata: { matches: 0, truncated: false, title: params.pattern },
|
title: params.pattern,
|
||||||
|
metadata: { matches: 0, truncated: false },
|
||||||
output: "No files found",
|
output: "No files found",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,16 +104,14 @@ export const GrepTool = Tool.define({
|
||||||
|
|
||||||
if (truncated) {
|
if (truncated) {
|
||||||
outputLines.push("")
|
outputLines.push("")
|
||||||
outputLines.push(
|
outputLines.push("(Results are truncated. Consider using a more specific path or pattern.)")
|
||||||
"(Results are truncated. Consider using a more specific path or pattern.)",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
title: params.pattern,
|
||||||
metadata: {
|
metadata: {
|
||||||
matches: finalMatches.length,
|
matches: finalMatches.length,
|
||||||
truncated,
|
truncated,
|
||||||
title: params.pattern,
|
|
||||||
},
|
},
|
||||||
output: outputLines.join("\n"),
|
output: outputLines.join("\n"),
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,16 +24,8 @@ export const ListTool = Tool.define({
|
||||||
id: "list",
|
id: "list",
|
||||||
description: DESCRIPTION,
|
description: DESCRIPTION,
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
path: z
|
path: z.string().describe("The absolute path to the directory to list (must be absolute, not relative)").optional(),
|
||||||
.string()
|
ignore: z.array(z.string()).describe("List of glob patterns to ignore").optional(),
|
||||||
.describe(
|
|
||||||
"The absolute path to the directory to list (must be absolute, not relative)",
|
|
||||||
)
|
|
||||||
.optional(),
|
|
||||||
ignore: z
|
|
||||||
.array(z.string())
|
|
||||||
.describe("List of glob patterns to ignore")
|
|
||||||
.optional(),
|
|
||||||
}),
|
}),
|
||||||
async execute(params) {
|
async execute(params) {
|
||||||
const app = App.info()
|
const app = App.info()
|
||||||
|
@ -44,8 +36,7 @@ export const ListTool = Tool.define({
|
||||||
|
|
||||||
for await (const file of glob.scan({ cwd: searchPath, dot: true })) {
|
for await (const file of glob.scan({ cwd: searchPath, dot: true })) {
|
||||||
if (IGNORE_PATTERNS.some((p) => file.includes(p))) continue
|
if (IGNORE_PATTERNS.some((p) => file.includes(p))) continue
|
||||||
if (params.ignore?.some((pattern) => new Bun.Glob(pattern).match(file)))
|
if (params.ignore?.some((pattern) => new Bun.Glob(pattern).match(file))) continue
|
||||||
continue
|
|
||||||
files.push(file)
|
files.push(file)
|
||||||
if (files.length >= LIMIT) break
|
if (files.length >= LIMIT) break
|
||||||
}
|
}
|
||||||
|
@ -99,10 +90,10 @@ export const ListTool = Tool.define({
|
||||||
const output = `${searchPath}/\n` + renderDir(".", 0)
|
const output = `${searchPath}/\n` + renderDir(".", 0)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
title: path.relative(app.path.root, searchPath),
|
||||||
metadata: {
|
metadata: {
|
||||||
count: files.length,
|
count: files.length,
|
||||||
truncated: files.length >= LIMIT,
|
truncated: files.length >= LIMIT,
|
||||||
title: path.relative(app.path.root, searchPath),
|
|
||||||
},
|
},
|
||||||
output,
|
output,
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,20 +13,16 @@ export const LspDiagnosticTool = Tool.define({
|
||||||
}),
|
}),
|
||||||
execute: async (args) => {
|
execute: async (args) => {
|
||||||
const app = App.info()
|
const app = App.info()
|
||||||
const normalized = path.isAbsolute(args.path)
|
const normalized = path.isAbsolute(args.path) ? args.path : path.join(app.path.cwd, args.path)
|
||||||
? args.path
|
|
||||||
: path.join(app.path.cwd, args.path)
|
|
||||||
await LSP.touchFile(normalized, true)
|
await LSP.touchFile(normalized, true)
|
||||||
const diagnostics = await LSP.diagnostics()
|
const diagnostics = await LSP.diagnostics()
|
||||||
const file = diagnostics[normalized]
|
const file = diagnostics[normalized]
|
||||||
return {
|
return {
|
||||||
|
title: path.relative(app.path.root, normalized),
|
||||||
metadata: {
|
metadata: {
|
||||||
diagnostics,
|
diagnostics,
|
||||||
title: path.relative(app.path.root, normalized),
|
|
||||||
},
|
},
|
||||||
output: file?.length
|
output: file?.length ? file.map(LSP.Diagnostic.pretty).join("\n") : "No errors found",
|
||||||
? file.map(LSP.Diagnostic.pretty).join("\n")
|
|
||||||
: "No errors found",
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -15,9 +15,7 @@ export const LspHoverTool = Tool.define({
|
||||||
}),
|
}),
|
||||||
execute: async (args) => {
|
execute: async (args) => {
|
||||||
const app = App.info()
|
const app = App.info()
|
||||||
const file = path.isAbsolute(args.file)
|
const file = path.isAbsolute(args.file) ? args.file : path.join(app.path.cwd, args.file)
|
||||||
? args.file
|
|
||||||
: path.join(app.path.cwd, args.file)
|
|
||||||
await LSP.touchFile(file, true)
|
await LSP.touchFile(file, true)
|
||||||
const result = await LSP.hover({
|
const result = await LSP.hover({
|
||||||
...args,
|
...args,
|
||||||
|
@ -25,14 +23,9 @@ export const LspHoverTool = Tool.define({
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
title: path.relative(app.path.root, file) + ":" + args.line + ":" + args.character,
|
||||||
metadata: {
|
metadata: {
|
||||||
result,
|
result,
|
||||||
title:
|
|
||||||
path.relative(app.path.root, file) +
|
|
||||||
":" +
|
|
||||||
args.line +
|
|
||||||
":" +
|
|
||||||
args.character,
|
|
||||||
},
|
},
|
||||||
output: JSON.stringify(result, null, 2),
|
output: JSON.stringify(result, null, 2),
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,7 @@ export const MultiEditTool = Tool.define({
|
||||||
description: DESCRIPTION,
|
description: DESCRIPTION,
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
filePath: z.string().describe("The absolute path to the file to modify"),
|
filePath: z.string().describe("The absolute path to the file to modify"),
|
||||||
edits: z
|
edits: z.array(EditTool.parameters).describe("Array of edit operations to perform sequentially on the file"),
|
||||||
.array(EditTool.parameters)
|
|
||||||
.describe("Array of edit operations to perform sequentially on the file"),
|
|
||||||
}),
|
}),
|
||||||
async execute(params, ctx) {
|
async execute(params, ctx) {
|
||||||
const results = []
|
const results = []
|
||||||
|
@ -30,9 +28,9 @@ export const MultiEditTool = Tool.define({
|
||||||
}
|
}
|
||||||
const app = App.info()
|
const app = App.info()
|
||||||
return {
|
return {
|
||||||
|
title: path.relative(app.path.root, params.filePath),
|
||||||
metadata: {
|
metadata: {
|
||||||
results: results.map((r) => r.metadata),
|
results: results.map((r) => r.metadata),
|
||||||
title: path.relative(app.path.root, params.filePath),
|
|
||||||
},
|
},
|
||||||
output: results.at(-1)!.output,
|
output: results.at(-1)!.output,
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,7 @@ import { FileTime } from "../file/time"
|
||||||
import DESCRIPTION from "./patch.txt"
|
import DESCRIPTION from "./patch.txt"
|
||||||
|
|
||||||
const PatchParams = z.object({
|
const PatchParams = z.object({
|
||||||
patchText: z
|
patchText: z.string().describe("The full patch text that describes all changes to be made"),
|
||||||
.string()
|
|
||||||
.describe("The full patch text that describes all changes to be made"),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
interface Change {
|
interface Change {
|
||||||
|
@ -42,10 +40,7 @@ function identifyFilesNeeded(patchText: string): string[] {
|
||||||
const files: string[] = []
|
const files: string[] = []
|
||||||
const lines = patchText.split("\n")
|
const lines = patchText.split("\n")
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
if (
|
if (line.startsWith("*** Update File:") || line.startsWith("*** Delete File:")) {
|
||||||
line.startsWith("*** Update File:") ||
|
|
||||||
line.startsWith("*** Delete File:")
|
|
||||||
) {
|
|
||||||
const filePath = line.split(":", 2)[1]?.trim()
|
const filePath = line.split(":", 2)[1]?.trim()
|
||||||
if (filePath) files.push(filePath)
|
if (filePath) files.push(filePath)
|
||||||
}
|
}
|
||||||
|
@ -65,10 +60,7 @@ function identifyFilesAdded(patchText: string): string[] {
|
||||||
return files
|
return files
|
||||||
}
|
}
|
||||||
|
|
||||||
function textToPatch(
|
function textToPatch(patchText: string, _currentFiles: Record<string, string>): [PatchOperation[], number] {
|
||||||
patchText: string,
|
|
||||||
_currentFiles: Record<string, string>,
|
|
||||||
): [PatchOperation[], number] {
|
|
||||||
const operations: PatchOperation[] = []
|
const operations: PatchOperation[] = []
|
||||||
const lines = patchText.split("\n")
|
const lines = patchText.split("\n")
|
||||||
let i = 0
|
let i = 0
|
||||||
|
@ -93,11 +85,7 @@ function textToPatch(
|
||||||
const changes: PatchChange[] = []
|
const changes: PatchChange[] = []
|
||||||
i++
|
i++
|
||||||
|
|
||||||
while (
|
while (i < lines.length && !lines[i].startsWith("@@") && !lines[i].startsWith("***")) {
|
||||||
i < lines.length &&
|
|
||||||
!lines[i].startsWith("@@") &&
|
|
||||||
!lines[i].startsWith("***")
|
|
||||||
) {
|
|
||||||
const changeLine = lines[i]
|
const changeLine = lines[i]
|
||||||
if (changeLine.startsWith(" ")) {
|
if (changeLine.startsWith(" ")) {
|
||||||
changes.push({ type: "keep", content: changeLine.substring(1) })
|
changes.push({ type: "keep", content: changeLine.substring(1) })
|
||||||
|
@ -151,10 +139,7 @@ function textToPatch(
|
||||||
return [operations, fuzz]
|
return [operations, fuzz]
|
||||||
}
|
}
|
||||||
|
|
||||||
function patchToCommit(
|
function patchToCommit(operations: PatchOperation[], currentFiles: Record<string, string>): Commit {
|
||||||
operations: PatchOperation[],
|
|
||||||
currentFiles: Record<string, string>,
|
|
||||||
): Commit {
|
|
||||||
const changes: Record<string, Change> = {}
|
const changes: Record<string, Change> = {}
|
||||||
|
|
||||||
for (const op of operations) {
|
for (const op of operations) {
|
||||||
|
@ -173,9 +158,7 @@ function patchToCommit(
|
||||||
const lines = originalContent.split("\n")
|
const lines = originalContent.split("\n")
|
||||||
|
|
||||||
for (const hunk of op.hunks) {
|
for (const hunk of op.hunks) {
|
||||||
const contextIndex = lines.findIndex((line) =>
|
const contextIndex = lines.findIndex((line) => line.includes(hunk.contextLine))
|
||||||
line.includes(hunk.contextLine),
|
|
||||||
)
|
|
||||||
if (contextIndex === -1) {
|
if (contextIndex === -1) {
|
||||||
throw new Error(`Context line not found: ${hunk.contextLine}`)
|
throw new Error(`Context line not found: ${hunk.contextLine}`)
|
||||||
}
|
}
|
||||||
|
@ -204,11 +187,7 @@ function patchToCommit(
|
||||||
return { changes }
|
return { changes }
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateDiff(
|
function generateDiff(oldContent: string, newContent: string, filePath: string): [string, number, number] {
|
||||||
oldContent: string,
|
|
||||||
newContent: string,
|
|
||||||
filePath: string,
|
|
||||||
): [string, number, number] {
|
|
||||||
// Mock implementation - would need actual diff generation
|
// Mock implementation - would need actual diff generation
|
||||||
const lines1 = oldContent.split("\n")
|
const lines1 = oldContent.split("\n")
|
||||||
const lines2 = newContent.split("\n")
|
const lines2 = newContent.split("\n")
|
||||||
|
@ -296,9 +275,7 @@ export const PatchTool = Tool.define({
|
||||||
// Process the patch
|
// Process the patch
|
||||||
const [patch, fuzz] = textToPatch(params.patchText, currentFiles)
|
const [patch, fuzz] = textToPatch(params.patchText, currentFiles)
|
||||||
if (fuzz > 3) {
|
if (fuzz > 3) {
|
||||||
throw new Error(
|
throw new Error(`patch contains fuzzy matches (fuzz level: ${fuzz}). Please make your context lines more precise`)
|
||||||
`patch contains fuzzy matches (fuzz level: ${fuzz}). Please make your context lines more precise`,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert patch to commit
|
// Convert patch to commit
|
||||||
|
@ -343,11 +320,7 @@ export const PatchTool = Tool.define({
|
||||||
const newContent = change.new_content || ""
|
const newContent = change.new_content || ""
|
||||||
|
|
||||||
// Calculate diff statistics
|
// Calculate diff statistics
|
||||||
const [, additions, removals] = generateDiff(
|
const [, additions, removals] = generateDiff(oldContent, newContent, filePath)
|
||||||
oldContent,
|
|
||||||
newContent,
|
|
||||||
filePath,
|
|
||||||
)
|
|
||||||
totalAdditions += additions
|
totalAdditions += additions
|
||||||
totalRemovals += removals
|
totalRemovals += removals
|
||||||
|
|
||||||
|
@ -358,11 +331,11 @@ export const PatchTool = Tool.define({
|
||||||
const output = result
|
const output = result
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
title: `${filesToRead.length} files`,
|
||||||
metadata: {
|
metadata: {
|
||||||
changed: changedFiles,
|
changed: changedFiles,
|
||||||
additions: totalAdditions,
|
additions: totalAdditions,
|
||||||
removals: totalRemovals,
|
removals: totalRemovals,
|
||||||
title: `${filesToRead.length} files`,
|
|
||||||
},
|
},
|
||||||
output,
|
output,
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,14 +16,8 @@ export const ReadTool = Tool.define({
|
||||||
description: DESCRIPTION,
|
description: DESCRIPTION,
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
filePath: z.string().describe("The path to the file to read"),
|
filePath: z.string().describe("The path to the file to read"),
|
||||||
offset: z
|
offset: z.number().describe("The line number to start reading from (0-based)").optional(),
|
||||||
.number()
|
limit: z.number().describe("The number of lines to read (defaults to 2000)").optional(),
|
||||||
.describe("The line number to start reading from (0-based)")
|
|
||||||
.optional(),
|
|
||||||
limit: z
|
|
||||||
.number()
|
|
||||||
.describe("The number of lines to read (defaults to 2000)")
|
|
||||||
.optional(),
|
|
||||||
}),
|
}),
|
||||||
async execute(params, ctx) {
|
async execute(params, ctx) {
|
||||||
let filePath = params.filePath
|
let filePath = params.filePath
|
||||||
|
@ -40,16 +34,13 @@ export const ReadTool = Tool.define({
|
||||||
const suggestions = dirEntries
|
const suggestions = dirEntries
|
||||||
.filter(
|
.filter(
|
||||||
(entry) =>
|
(entry) =>
|
||||||
entry.toLowerCase().includes(base.toLowerCase()) ||
|
entry.toLowerCase().includes(base.toLowerCase()) || base.toLowerCase().includes(entry.toLowerCase()),
|
||||||
base.toLowerCase().includes(entry.toLowerCase()),
|
|
||||||
)
|
)
|
||||||
.map((entry) => path.join(dir, entry))
|
.map((entry) => path.join(dir, entry))
|
||||||
.slice(0, 3)
|
.slice(0, 3)
|
||||||
|
|
||||||
if (suggestions.length > 0) {
|
if (suggestions.length > 0) {
|
||||||
throw new Error(
|
throw new Error(`File not found: ${filePath}\n\nDid you mean one of these?\n${suggestions.join("\n")}`)
|
||||||
`File not found: ${filePath}\n\nDid you mean one of these?\n${suggestions.join("\n")}`,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`File not found: ${filePath}`)
|
throw new Error(`File not found: ${filePath}`)
|
||||||
|
@ -57,21 +48,14 @@ export const ReadTool = Tool.define({
|
||||||
const stats = await file.stat()
|
const stats = await file.stat()
|
||||||
|
|
||||||
if (stats.size > MAX_READ_SIZE)
|
if (stats.size > MAX_READ_SIZE)
|
||||||
throw new Error(
|
throw new Error(`File is too large (${stats.size} bytes). Maximum size is ${MAX_READ_SIZE} bytes`)
|
||||||
`File is too large (${stats.size} bytes). Maximum size is ${MAX_READ_SIZE} bytes`,
|
|
||||||
)
|
|
||||||
const limit = params.limit ?? DEFAULT_READ_LIMIT
|
const limit = params.limit ?? DEFAULT_READ_LIMIT
|
||||||
const offset = params.offset || 0
|
const offset = params.offset || 0
|
||||||
const isImage = isImageFile(filePath)
|
const isImage = isImageFile(filePath)
|
||||||
if (isImage)
|
if (isImage) throw new Error(`This is an image file of type: ${isImage}\nUse a different tool to process images`)
|
||||||
throw new Error(
|
|
||||||
`This is an image file of type: ${isImage}\nUse a different tool to process images`,
|
|
||||||
)
|
|
||||||
const lines = await file.text().then((text) => text.split("\n"))
|
const lines = await file.text().then((text) => text.split("\n"))
|
||||||
const raw = lines.slice(offset, offset + limit).map((line) => {
|
const raw = lines.slice(offset, offset + limit).map((line) => {
|
||||||
return line.length > MAX_LINE_LENGTH
|
return line.length > MAX_LINE_LENGTH ? line.substring(0, MAX_LINE_LENGTH) + "..." : line
|
||||||
? line.substring(0, MAX_LINE_LENGTH) + "..."
|
|
||||||
: line
|
|
||||||
})
|
})
|
||||||
const content = raw.map((line, index) => {
|
const content = raw.map((line, index) => {
|
||||||
return `${(index + offset + 1).toString().padStart(5, "0")}| ${line}`
|
return `${(index + offset + 1).toString().padStart(5, "0")}| ${line}`
|
||||||
|
@ -82,9 +66,7 @@ export const ReadTool = Tool.define({
|
||||||
output += content.join("\n")
|
output += content.join("\n")
|
||||||
|
|
||||||
if (lines.length > offset + content.length) {
|
if (lines.length > offset + content.length) {
|
||||||
output += `\n\n(File has more lines. Use 'offset' parameter to read beyond line ${
|
output += `\n\n(File has more lines. Use 'offset' parameter to read beyond line ${offset + content.length})`
|
||||||
offset + content.length
|
|
||||||
})`
|
|
||||||
}
|
}
|
||||||
output += "\n</file>"
|
output += "\n</file>"
|
||||||
|
|
||||||
|
@ -93,10 +75,10 @@ export const ReadTool = Tool.define({
|
||||||
FileTime.read(ctx.sessionID, filePath)
|
FileTime.read(ctx.sessionID, filePath)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
title: path.relative(App.info().path.root, filePath),
|
||||||
output,
|
output,
|
||||||
metadata: {
|
metadata: {
|
||||||
preview,
|
preview,
|
||||||
title: path.relative(App.info().path.root, filePath),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,41 +3,36 @@ import DESCRIPTION from "./task.txt"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
import { Session } from "../session"
|
import { Session } from "../session"
|
||||||
import { Bus } from "../bus"
|
import { Bus } from "../bus"
|
||||||
import { Message } from "../session/message"
|
import { MessageV2 } from "../session/message-v2"
|
||||||
|
|
||||||
export const TaskTool = Tool.define({
|
export const TaskTool = Tool.define({
|
||||||
id: "task",
|
id: "task",
|
||||||
description: DESCRIPTION,
|
description: DESCRIPTION,
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
description: z
|
description: z.string().describe("A short (3-5 words) description of the task"),
|
||||||
.string()
|
|
||||||
.describe("A short (3-5 words) description of the task"),
|
|
||||||
prompt: z.string().describe("The task for the agent to perform"),
|
prompt: z.string().describe("The task for the agent to perform"),
|
||||||
}),
|
}),
|
||||||
async execute(params, ctx) {
|
async execute(params, ctx) {
|
||||||
const session = await Session.create(ctx.sessionID)
|
const session = await Session.create(ctx.sessionID)
|
||||||
const msg = await Session.getMessage(ctx.sessionID, ctx.messageID)
|
const msg = (await Session.getMessage(ctx.sessionID, ctx.messageID)) as MessageV2.Assistant
|
||||||
const metadata = msg.metadata.assistant!
|
|
||||||
|
|
||||||
function summary(input: Message.Info) {
|
function summary(input: MessageV2.Info) {
|
||||||
const result = []
|
const result = []
|
||||||
|
|
||||||
for (const part of input.parts) {
|
for (const part of input.parts) {
|
||||||
if (part.type === "tool-invocation") {
|
if (part.type === "tool" && part.state.status === "completed") {
|
||||||
result.push({
|
result.push(part)
|
||||||
toolInvocation: part.toolInvocation,
|
|
||||||
metadata: input.metadata.tool[part.toolInvocation.toolCallId],
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
const unsub = Bus.subscribe(Message.Event.Updated, async (evt) => {
|
const unsub = Bus.subscribe(MessageV2.Event.Updated, async (evt) => {
|
||||||
if (evt.properties.info.metadata.sessionID !== session.id) return
|
if (evt.properties.info.sessionID !== session.id) return
|
||||||
ctx.metadata({
|
ctx.metadata({
|
||||||
title: params.description,
|
title: params.description,
|
||||||
summary: summary(evt.properties.info),
|
metadata: {
|
||||||
|
summary: summary(evt.properties.info),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -46,8 +41,8 @@ export const TaskTool = Tool.define({
|
||||||
})
|
})
|
||||||
const result = await Session.chat({
|
const result = await Session.chat({
|
||||||
sessionID: session.id,
|
sessionID: session.id,
|
||||||
modelID: metadata.modelID,
|
modelID: msg.modelID,
|
||||||
providerID: metadata.providerID,
|
providerID: msg.providerID,
|
||||||
parts: [
|
parts: [
|
||||||
{
|
{
|
||||||
type: "text",
|
type: "text",
|
||||||
|
@ -57,8 +52,8 @@ export const TaskTool = Tool.define({
|
||||||
})
|
})
|
||||||
unsub()
|
unsub()
|
||||||
return {
|
return {
|
||||||
|
title: params.description,
|
||||||
metadata: {
|
metadata: {
|
||||||
title: params.description,
|
|
||||||
summary: summary(result),
|
summary: summary(result),
|
||||||
},
|
},
|
||||||
output: result.parts.findLast((x) => x.type === "text")!.text,
|
output: result.parts.findLast((x) => x.type === "text")!.text,
|
||||||
|
|
|
@ -5,12 +5,8 @@ import { App } from "../app/app"
|
||||||
|
|
||||||
const TodoInfo = z.object({
|
const TodoInfo = z.object({
|
||||||
content: z.string().min(1).describe("Brief description of the task"),
|
content: z.string().min(1).describe("Brief description of the task"),
|
||||||
status: z
|
status: z.enum(["pending", "in_progress", "completed"]).describe("Current status of the task"),
|
||||||
.enum(["pending", "in_progress", "completed"])
|
priority: z.enum(["high", "medium", "low"]).describe("Priority level of the task"),
|
||||||
.describe("Current status of the task"),
|
|
||||||
priority: z
|
|
||||||
.enum(["high", "medium", "low"])
|
|
||||||
.describe("Priority level of the task"),
|
|
||||||
id: z.string().describe("Unique identifier for the todo item"),
|
id: z.string().describe("Unique identifier for the todo item"),
|
||||||
})
|
})
|
||||||
type TodoInfo = z.infer<typeof TodoInfo>
|
type TodoInfo = z.infer<typeof TodoInfo>
|
||||||
|
@ -32,9 +28,9 @@ export const TodoWriteTool = Tool.define({
|
||||||
const todos = state()
|
const todos = state()
|
||||||
todos[opts.sessionID] = params.todos
|
todos[opts.sessionID] = params.todos
|
||||||
return {
|
return {
|
||||||
|
title: `${params.todos.filter((x) => x.status !== "completed").length} todos`,
|
||||||
output: JSON.stringify(params.todos, null, 2),
|
output: JSON.stringify(params.todos, null, 2),
|
||||||
metadata: {
|
metadata: {
|
||||||
title: `${params.todos.filter((x) => x.status !== "completed").length} todos`,
|
|
||||||
todos: params.todos,
|
todos: params.todos,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -48,9 +44,9 @@ export const TodoReadTool = Tool.define({
|
||||||
async execute(_params, opts) {
|
async execute(_params, opts) {
|
||||||
const todos = state()[opts.sessionID] ?? []
|
const todos = state()[opts.sessionID] ?? []
|
||||||
return {
|
return {
|
||||||
|
title: `${todos.filter((x) => x.status !== "completed").length} todos`,
|
||||||
metadata: {
|
metadata: {
|
||||||
todos,
|
todos,
|
||||||
title: `${todos.filter((x) => x.status !== "completed").length} todos`,
|
|
||||||
},
|
},
|
||||||
output: JSON.stringify(todos, null, 2),
|
output: JSON.stringify(todos, null, 2),
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,19 +2,15 @@ import type { StandardSchemaV1 } from "@standard-schema/spec"
|
||||||
|
|
||||||
export namespace Tool {
|
export namespace Tool {
|
||||||
interface Metadata {
|
interface Metadata {
|
||||||
title: string
|
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
}
|
}
|
||||||
export type Context<M extends Metadata = Metadata> = {
|
export type Context<M extends Metadata = Metadata> = {
|
||||||
sessionID: string
|
sessionID: string
|
||||||
messageID: string
|
messageID: string
|
||||||
abort: AbortSignal
|
abort: AbortSignal
|
||||||
metadata(meta: M): void
|
metadata(input: { title?: string; metadata?: M }): void
|
||||||
}
|
}
|
||||||
export interface Info<
|
export interface Info<Parameters extends StandardSchemaV1 = StandardSchemaV1, M extends Metadata = Metadata> {
|
||||||
Parameters extends StandardSchemaV1 = StandardSchemaV1,
|
|
||||||
M extends Metadata = Metadata,
|
|
||||||
> {
|
|
||||||
id: string
|
id: string
|
||||||
description: string
|
description: string
|
||||||
parameters: Parameters
|
parameters: Parameters
|
||||||
|
@ -22,15 +18,15 @@ export namespace Tool {
|
||||||
args: StandardSchemaV1.InferOutput<Parameters>,
|
args: StandardSchemaV1.InferOutput<Parameters>,
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
): Promise<{
|
): Promise<{
|
||||||
|
title: string
|
||||||
metadata: M
|
metadata: M
|
||||||
output: string
|
output: string
|
||||||
}>
|
}>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function define<
|
export function define<Parameters extends StandardSchemaV1, Result extends Metadata>(
|
||||||
Parameters extends StandardSchemaV1,
|
input: Info<Parameters, Result>,
|
||||||
Result extends Metadata,
|
): Info<Parameters, Result> {
|
||||||
>(input: Info<Parameters, Result>): Info<Parameters, Result> {
|
|
||||||
return input
|
return input
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,9 +14,7 @@ export const WebFetchTool = Tool.define({
|
||||||
url: z.string().describe("The URL to fetch content from"),
|
url: z.string().describe("The URL to fetch content from"),
|
||||||
format: z
|
format: z
|
||||||
.enum(["text", "markdown", "html"])
|
.enum(["text", "markdown", "html"])
|
||||||
.describe(
|
.describe("The format to return the content in (text, markdown, or html)"),
|
||||||
"The format to return the content in (text, markdown, or html)",
|
|
||||||
),
|
|
||||||
timeout: z
|
timeout: z
|
||||||
.number()
|
.number()
|
||||||
.min(0)
|
.min(0)
|
||||||
|
@ -26,17 +24,11 @@ export const WebFetchTool = Tool.define({
|
||||||
}),
|
}),
|
||||||
async execute(params, ctx) {
|
async execute(params, ctx) {
|
||||||
// Validate URL
|
// Validate URL
|
||||||
if (
|
if (!params.url.startsWith("http://") && !params.url.startsWith("https://")) {
|
||||||
!params.url.startsWith("http://") &&
|
|
||||||
!params.url.startsWith("https://")
|
|
||||||
) {
|
|
||||||
throw new Error("URL must start with http:// or https://")
|
throw new Error("URL must start with http:// or https://")
|
||||||
}
|
}
|
||||||
|
|
||||||
const timeout = Math.min(
|
const timeout = Math.min((params.timeout ?? DEFAULT_TIMEOUT / 1000) * 1000, MAX_TIMEOUT)
|
||||||
(params.timeout ?? DEFAULT_TIMEOUT / 1000) * 1000,
|
|
||||||
MAX_TIMEOUT,
|
|
||||||
)
|
|
||||||
|
|
||||||
const controller = new AbortController()
|
const controller = new AbortController()
|
||||||
const timeoutId = setTimeout(() => controller.abort(), timeout)
|
const timeoutId = setTimeout(() => controller.abort(), timeout)
|
||||||
|
@ -46,8 +38,7 @@ export const WebFetchTool = Tool.define({
|
||||||
headers: {
|
headers: {
|
||||||
"User-Agent":
|
"User-Agent":
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||||||
Accept:
|
Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
|
||||||
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
|
|
||||||
"Accept-Language": "en-US,en;q=0.9",
|
"Accept-Language": "en-US,en;q=0.9",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -79,16 +70,14 @@ export const WebFetchTool = Tool.define({
|
||||||
const text = await extractTextFromHTML(content)
|
const text = await extractTextFromHTML(content)
|
||||||
return {
|
return {
|
||||||
output: text,
|
output: text,
|
||||||
metadata: {
|
title,
|
||||||
title,
|
metadata: {},
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
output: content,
|
output: content,
|
||||||
metadata: {
|
title,
|
||||||
title,
|
metadata: {},
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case "markdown":
|
case "markdown":
|
||||||
|
@ -96,32 +85,28 @@ export const WebFetchTool = Tool.define({
|
||||||
const markdown = convertHTMLToMarkdown(content)
|
const markdown = convertHTMLToMarkdown(content)
|
||||||
return {
|
return {
|
||||||
output: markdown,
|
output: markdown,
|
||||||
metadata: {
|
title,
|
||||||
title,
|
metadata: {},
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
output: "```\n" + content + "\n```",
|
output: "```\n" + content + "\n```",
|
||||||
metadata: {
|
title,
|
||||||
title,
|
metadata: {},
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case "html":
|
case "html":
|
||||||
return {
|
return {
|
||||||
output: content,
|
output: content,
|
||||||
metadata: {
|
title,
|
||||||
title,
|
metadata: {},
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
output: content,
|
output: content,
|
||||||
metadata: {
|
title,
|
||||||
title,
|
metadata: {},
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -143,16 +128,7 @@ async function extractTextFromHTML(html: string) {
|
||||||
.on("*", {
|
.on("*", {
|
||||||
element(element) {
|
element(element) {
|
||||||
// Reset skip flag when entering other elements
|
// Reset skip flag when entering other elements
|
||||||
if (
|
if (!["script", "style", "noscript", "iframe", "object", "embed"].includes(element.tagName)) {
|
||||||
![
|
|
||||||
"script",
|
|
||||||
"style",
|
|
||||||
"noscript",
|
|
||||||
"iframe",
|
|
||||||
"object",
|
|
||||||
"embed",
|
|
||||||
].includes(element.tagName)
|
|
||||||
) {
|
|
||||||
skipContent = false
|
skipContent = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -13,18 +13,12 @@ export const WriteTool = Tool.define({
|
||||||
id: "write",
|
id: "write",
|
||||||
description: DESCRIPTION,
|
description: DESCRIPTION,
|
||||||
parameters: z.object({
|
parameters: z.object({
|
||||||
filePath: z
|
filePath: z.string().describe("The absolute path to the file to write (must be absolute, not relative)"),
|
||||||
.string()
|
|
||||||
.describe(
|
|
||||||
"The absolute path to the file to write (must be absolute, not relative)",
|
|
||||||
),
|
|
||||||
content: z.string().describe("The content to write to the file"),
|
content: z.string().describe("The content to write to the file"),
|
||||||
}),
|
}),
|
||||||
async execute(params, ctx) {
|
async execute(params, ctx) {
|
||||||
const app = App.info()
|
const app = App.info()
|
||||||
const filepath = path.isAbsolute(params.filePath)
|
const filepath = path.isAbsolute(params.filePath) ? params.filePath : path.join(app.path.cwd, params.filePath)
|
||||||
? params.filePath
|
|
||||||
: path.join(app.path.cwd, params.filePath)
|
|
||||||
|
|
||||||
const file = Bun.file(filepath)
|
const file = Bun.file(filepath)
|
||||||
const exists = await file.exists()
|
const exists = await file.exists()
|
||||||
|
@ -33,9 +27,7 @@ export const WriteTool = Tool.define({
|
||||||
await Permission.ask({
|
await Permission.ask({
|
||||||
id: "write",
|
id: "write",
|
||||||
sessionID: ctx.sessionID,
|
sessionID: ctx.sessionID,
|
||||||
title: exists
|
title: exists ? "Overwrite this file: " + filepath : "Create new file: " + filepath,
|
||||||
? "Overwrite this file: " + filepath
|
|
||||||
: "Create new file: " + filepath,
|
|
||||||
metadata: {
|
metadata: {
|
||||||
filePath: filepath,
|
filePath: filepath,
|
||||||
content: params.content,
|
content: params.content,
|
||||||
|
@ -62,11 +54,11 @@ export const WriteTool = Tool.define({
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
title: path.relative(app.path.root, filepath),
|
||||||
metadata: {
|
metadata: {
|
||||||
diagnostics,
|
diagnostics,
|
||||||
filepath,
|
filepath,
|
||||||
exists: exists,
|
exists: exists,
|
||||||
title: path.relative(app.path.root, filepath),
|
|
||||||
},
|
},
|
||||||
output,
|
output,
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,10 +7,7 @@ export abstract class NamedError extends Error {
|
||||||
abstract schema(): ZodSchema
|
abstract schema(): ZodSchema
|
||||||
abstract toObject(): { name: string; data: any }
|
abstract toObject(): { name: string; data: any }
|
||||||
|
|
||||||
static create<Name extends string, Data extends ZodSchema>(
|
static create<Name extends string, Data extends ZodSchema>(name: Name, data: Data) {
|
||||||
name: Name,
|
|
||||||
data: Data,
|
|
||||||
) {
|
|
||||||
const schema = z
|
const schema = z
|
||||||
.object({
|
.object({
|
||||||
name: z.literal(name),
|
name: z.literal(name),
|
||||||
|
|
|
@ -19,10 +19,7 @@ export namespace Log {
|
||||||
await fs.mkdir(dir, { recursive: true })
|
await fs.mkdir(dir, { recursive: true })
|
||||||
cleanup(dir)
|
cleanup(dir)
|
||||||
if (options.print) return
|
if (options.print) return
|
||||||
logpath = path.join(
|
logpath = path.join(dir, new Date().toISOString().split(".")[0].replace(/:/g, "") + ".log")
|
||||||
dir,
|
|
||||||
new Date().toISOString().split(".")[0].replace(/:/g, "") + ".log",
|
|
||||||
)
|
|
||||||
const logfile = Bun.file(logpath)
|
const logfile = Bun.file(logpath)
|
||||||
await fs.truncate(logpath).catch(() => {})
|
await fs.truncate(logpath).catch(() => {})
|
||||||
const writer = logfile.writer()
|
const writer = logfile.writer()
|
||||||
|
@ -43,9 +40,7 @@ export namespace Log {
|
||||||
|
|
||||||
const filesToDelete = files.slice(0, -10)
|
const filesToDelete = files.slice(0, -10)
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(filesToDelete.map((file) => fs.unlink(file).catch(() => {})))
|
||||||
filesToDelete.map((file) => fs.unlink(file).catch(() => {})),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let last = Date.now()
|
let last = Date.now()
|
||||||
|
@ -63,11 +58,7 @@ export namespace Log {
|
||||||
const next = new Date()
|
const next = new Date()
|
||||||
const diff = next.getTime() - last
|
const diff = next.getTime() - last
|
||||||
last = next.getTime()
|
last = next.getTime()
|
||||||
return (
|
return [next.toISOString().split(".")[0], "+" + diff + "ms", prefix, message].filter(Boolean).join(" ") + "\n"
|
||||||
[next.toISOString().split(".")[0], "+" + diff + "ms", prefix, message]
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(" ") + "\n"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
const result = {
|
const result = {
|
||||||
info(message?: any, extra?: Record<string, any>) {
|
info(message?: any, extra?: Record<string, any>) {
|
||||||
|
|
|
@ -17,12 +17,7 @@ const testCases: TestCase[] = [
|
||||||
replace: 'console.log("universe");',
|
replace: 'console.log("universe");',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: [
|
content: ["if (condition) {", " doSomething();", " doSomethingElse();", "}"].join("\n"),
|
||||||
"if (condition) {",
|
|
||||||
" doSomething();",
|
|
||||||
" doSomethingElse();",
|
|
||||||
"}",
|
|
||||||
].join("\n"),
|
|
||||||
find: [" doSomething();", " doSomethingElse();"].join("\n"),
|
find: [" doSomething();", " doSomethingElse();"].join("\n"),
|
||||||
replace: [" doNewThing();", " doAnotherThing();"].join("\n"),
|
replace: [" doNewThing();", " doAnotherThing();"].join("\n"),
|
||||||
},
|
},
|
||||||
|
@ -53,15 +48,8 @@ const testCases: TestCase[] = [
|
||||||
" return result;",
|
" return result;",
|
||||||
"}",
|
"}",
|
||||||
].join("\n"),
|
].join("\n"),
|
||||||
find: [
|
find: ["function calculate(a, b) {", " // different middle content", " return result;", "}"].join("\n"),
|
||||||
"function calculate(a, b) {",
|
replace: ["function calculate(a, b) {", " return a * b * 2;", "}"].join("\n"),
|
||||||
" // different middle content",
|
|
||||||
" return result;",
|
|
||||||
"}",
|
|
||||||
].join("\n"),
|
|
||||||
replace: ["function calculate(a, b) {", " return a * b * 2;", "}"].join(
|
|
||||||
"\n",
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: [
|
content: [
|
||||||
|
@ -76,13 +64,7 @@ const testCases: TestCase[] = [
|
||||||
"}",
|
"}",
|
||||||
].join("\n"),
|
].join("\n"),
|
||||||
find: ["class MyClass {", " // different implementation", "}"].join("\n"),
|
find: ["class MyClass {", " // different implementation", "}"].join("\n"),
|
||||||
replace: [
|
replace: ["class MyClass {", " constructor() {", " this.value = 42;", " }", "}"].join("\n"),
|
||||||
"class MyClass {",
|
|
||||||
" constructor() {",
|
|
||||||
" this.value = 42;",
|
|
||||||
" }",
|
|
||||||
"}",
|
|
||||||
].join("\n"),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// WhitespaceNormalizedReplacer cases
|
// WhitespaceNormalizedReplacer cases
|
||||||
|
@ -104,48 +86,21 @@ const testCases: TestCase[] = [
|
||||||
|
|
||||||
// IndentationFlexibleReplacer cases
|
// IndentationFlexibleReplacer cases
|
||||||
{
|
{
|
||||||
content: [
|
content: [" function nested() {", ' console.log("deeply nested");', " return true;", " }"].join(
|
||||||
" function nested() {",
|
"\n",
|
||||||
' console.log("deeply nested");',
|
),
|
||||||
" return true;",
|
find: ["function nested() {", ' console.log("deeply nested");', " return true;", "}"].join("\n"),
|
||||||
" }",
|
replace: ["function nested() {", ' console.log("updated");', " return false;", "}"].join("\n"),
|
||||||
].join("\n"),
|
|
||||||
find: [
|
|
||||||
"function nested() {",
|
|
||||||
' console.log("deeply nested");',
|
|
||||||
" return true;",
|
|
||||||
"}",
|
|
||||||
].join("\n"),
|
|
||||||
replace: [
|
|
||||||
"function nested() {",
|
|
||||||
' console.log("updated");',
|
|
||||||
" return false;",
|
|
||||||
"}",
|
|
||||||
].join("\n"),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: [
|
content: [" if (true) {", ' console.log("level 1");', ' console.log("level 2");', " }"].join("\n"),
|
||||||
" if (true) {",
|
find: ["if (true) {", 'console.log("level 1");', ' console.log("level 2");', "}"].join("\n"),
|
||||||
' console.log("level 1");',
|
|
||||||
' console.log("level 2");',
|
|
||||||
" }",
|
|
||||||
].join("\n"),
|
|
||||||
find: [
|
|
||||||
"if (true) {",
|
|
||||||
'console.log("level 1");',
|
|
||||||
' console.log("level 2");',
|
|
||||||
"}",
|
|
||||||
].join("\n"),
|
|
||||||
replace: ["if (true) {", 'console.log("updated");', "}"].join("\n"),
|
replace: ["if (true) {", 'console.log("updated");', "}"].join("\n"),
|
||||||
},
|
},
|
||||||
|
|
||||||
// replaceAll option cases
|
// replaceAll option cases
|
||||||
{
|
{
|
||||||
content: [
|
content: ['console.log("test");', 'console.log("test");', 'console.log("test");'].join("\n"),
|
||||||
'console.log("test");',
|
|
||||||
'console.log("test");',
|
|
||||||
'console.log("test");',
|
|
||||||
].join("\n"),
|
|
||||||
find: 'console.log("test");',
|
find: 'console.log("test");',
|
||||||
replace: 'console.log("updated");',
|
replace: 'console.log("updated");',
|
||||||
all: true,
|
all: true,
|
||||||
|
@ -213,9 +168,7 @@ const testCases: TestCase[] = [
|
||||||
|
|
||||||
// MultiOccurrenceReplacer cases (with replaceAll)
|
// MultiOccurrenceReplacer cases (with replaceAll)
|
||||||
{
|
{
|
||||||
content: ["debug('start');", "debug('middle');", "debug('end');"].join(
|
content: ["debug('start');", "debug('middle');", "debug('end');"].join("\n"),
|
||||||
"\n",
|
|
||||||
),
|
|
||||||
find: "debug",
|
find: "debug",
|
||||||
replace: "log",
|
replace: "log",
|
||||||
all: true,
|
all: true,
|
||||||
|
@ -239,9 +192,7 @@ const testCases: TestCase[] = [
|
||||||
replace: "const value = 24;",
|
replace: "const value = 24;",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: ["", " if (condition) {", " doSomething();", " }", ""].join(
|
content: ["", " if (condition) {", " doSomething();", " }", ""].join("\n"),
|
||||||
"\n",
|
|
||||||
),
|
|
||||||
find: ["if (condition) {", " doSomething();", "}"].join("\n"),
|
find: ["if (condition) {", " doSomething();", "}"].join("\n"),
|
||||||
replace: ["if (condition) {", " doNothing();", "}"].join("\n"),
|
replace: ["if (condition) {", " doNothing();", "}"].join("\n"),
|
||||||
},
|
},
|
||||||
|
@ -262,9 +213,7 @@ const testCases: TestCase[] = [
|
||||||
" return result;",
|
" return result;",
|
||||||
"}",
|
"}",
|
||||||
].join("\n"),
|
].join("\n"),
|
||||||
replace: ["function calculate(a, b) {", " return (a + b) * 2;", "}"].join(
|
replace: ["function calculate(a, b) {", " return (a + b) * 2;", "}"].join("\n"),
|
||||||
"\n",
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: [
|
content: [
|
||||||
|
@ -278,15 +227,8 @@ const testCases: TestCase[] = [
|
||||||
" }",
|
" }",
|
||||||
"}",
|
"}",
|
||||||
].join("\n"),
|
].join("\n"),
|
||||||
find: [
|
find: ["class TestClass {", " // different implementation", " // with multiple lines", "}"].join("\n"),
|
||||||
"class TestClass {",
|
replace: ["class TestClass {", " getValue() { return 42; }", "}"].join("\n"),
|
||||||
" // different implementation",
|
|
||||||
" // with multiple lines",
|
|
||||||
"}",
|
|
||||||
].join("\n"),
|
|
||||||
replace: ["class TestClass {", " getValue() { return 42; }", "}"].join(
|
|
||||||
"\n",
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Combined edge cases for new replacers
|
// Combined edge cases for new replacers
|
||||||
|
@ -296,9 +238,7 @@ const testCases: TestCase[] = [
|
||||||
replace: 'console.log("updated");',
|
replace: 'console.log("updated");',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: [" ", "function test() {", " return 'value';", "}", " "].join(
|
content: [" ", "function test() {", " return 'value';", "}", " "].join("\n"),
|
||||||
"\n",
|
|
||||||
),
|
|
||||||
find: ["function test() {", "return 'value';", "}"].join("\n"),
|
find: ["function test() {", "return 'value';", "}"].join("\n"),
|
||||||
replace: ["function test() {", "return 'new value';", "}"].join("\n"),
|
replace: ["function test() {", "return 'new value';", "}"].join("\n"),
|
||||||
},
|
},
|
||||||
|
@ -346,13 +286,7 @@ const testCases: TestCase[] = [
|
||||||
|
|
||||||
// ContextAwareReplacer - test with trailing newline in find string
|
// ContextAwareReplacer - test with trailing newline in find string
|
||||||
{
|
{
|
||||||
content: [
|
content: ["class Test {", " method1() {", " return 1;", " }", "}"].join("\n"),
|
||||||
"class Test {",
|
|
||||||
" method1() {",
|
|
||||||
" return 1;",
|
|
||||||
" }",
|
|
||||||
"}",
|
|
||||||
].join("\n"),
|
|
||||||
find: [
|
find: [
|
||||||
"class Test {",
|
"class Test {",
|
||||||
" // different content",
|
" // different content",
|
||||||
|
@ -401,12 +335,7 @@ describe("EditTool Replacers", () => {
|
||||||
replace(testCase.content, testCase.find, testCase.replace, testCase.all)
|
replace(testCase.content, testCase.find, testCase.replace, testCase.all)
|
||||||
}).toThrow()
|
}).toThrow()
|
||||||
} else {
|
} else {
|
||||||
const result = replace(
|
const result = replace(testCase.content, testCase.find, testCase.replace, testCase.all)
|
||||||
testCase.content,
|
|
||||||
testCase.find,
|
|
||||||
testCase.replace,
|
|
||||||
testCase.all,
|
|
||||||
)
|
|
||||||
expect(result).toContain(testCase.replace)
|
expect(result).toContain(testCase.replace)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -42,10 +42,7 @@ describe("tool.glob", () => {
|
||||||
describe("tool.ls", () => {
|
describe("tool.ls", () => {
|
||||||
test("basic", async () => {
|
test("basic", async () => {
|
||||||
const result = await App.provide({ cwd: process.cwd() }, async () => {
|
const result = await App.provide({ cwd: process.cwd() }, async () => {
|
||||||
return await ListTool.execute(
|
return await ListTool.execute({ path: "./example", ignore: [".git"] }, ctx)
|
||||||
{ path: "./example", ignore: [".git"] },
|
|
||||||
ctx,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
expect(result.output).toMatchSnapshot()
|
expect(result.output).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
|
@ -10,6 +10,7 @@ require (
|
||||||
github.com/charmbracelet/glamour v0.10.0
|
github.com/charmbracelet/glamour v0.10.0
|
||||||
github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.1
|
github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.1
|
||||||
github.com/charmbracelet/x/ansi v0.8.0
|
github.com/charmbracelet/x/ansi v0.8.0
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
github.com/lithammer/fuzzysearch v1.1.8
|
github.com/lithammer/fuzzysearch v1.1.8
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6
|
||||||
github.com/muesli/reflow v0.3.0
|
github.com/muesli/reflow v0.3.0
|
||||||
|
|
|
@ -92,6 +92,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
|
||||||
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
|
||||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||||
|
|
|
@ -30,7 +30,7 @@ type App struct {
|
||||||
Provider *opencode.Provider
|
Provider *opencode.Provider
|
||||||
Model *opencode.Model
|
Model *opencode.Model
|
||||||
Session *opencode.Session
|
Session *opencode.Session
|
||||||
Messages []opencode.Message
|
Messages []opencode.MessageUnion
|
||||||
Commands commands.CommandRegistry
|
Commands commands.CommandRegistry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,10 +44,10 @@ type SessionClearedMsg struct{}
|
||||||
type CompactSessionMsg struct{}
|
type CompactSessionMsg struct{}
|
||||||
type SendMsg struct {
|
type SendMsg struct {
|
||||||
Text string
|
Text string
|
||||||
Attachments []Attachment
|
Attachments []opencode.FilePartParam
|
||||||
}
|
}
|
||||||
type OptimisticMessageAddedMsg struct {
|
type OptimisticMessageAddedMsg struct {
|
||||||
Message opencode.Message
|
Message opencode.MessageUnion
|
||||||
}
|
}
|
||||||
type FileRenderedMsg struct {
|
type FileRenderedMsg struct {
|
||||||
FilePath string
|
FilePath string
|
||||||
|
@ -116,7 +116,7 @@ func New(
|
||||||
State: appState,
|
State: appState,
|
||||||
Client: httpClient,
|
Client: httpClient,
|
||||||
Session: &opencode.Session{},
|
Session: &opencode.Session{},
|
||||||
Messages: []opencode.Message{},
|
Messages: []opencode.MessageUnion{},
|
||||||
Commands: commands.LoadFromConfig(configInfo),
|
Commands: commands.LoadFromConfig(configInfo),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,20 +217,16 @@ func getDefaultModel(
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Attachment struct {
|
|
||||||
FilePath string
|
|
||||||
FileName string
|
|
||||||
MimeType string
|
|
||||||
Content []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *App) IsBusy() bool {
|
func (a *App) IsBusy() bool {
|
||||||
if len(a.Messages) == 0 {
|
if len(a.Messages) == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
lastMessage := a.Messages[len(a.Messages)-1]
|
lastMessage := a.Messages[len(a.Messages)-1]
|
||||||
return lastMessage.Metadata.Time.Completed == 0
|
if casted, ok := lastMessage.(opencode.AssistantMessage); ok {
|
||||||
|
return casted.Time.Completed == 0
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) SaveState() {
|
func (a *App) SaveState() {
|
||||||
|
@ -296,29 +292,43 @@ func (a *App) CreateSession(ctx context.Context) (*opencode.Session, error) {
|
||||||
return session, nil
|
return session, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) SendChatMessage(ctx context.Context, text string, attachments []Attachment) tea.Cmd {
|
func (a *App) SendChatMessage(
|
||||||
|
ctx context.Context,
|
||||||
|
text string,
|
||||||
|
attachments []opencode.FilePartParam,
|
||||||
|
) (*App, tea.Cmd) {
|
||||||
var cmds []tea.Cmd
|
var cmds []tea.Cmd
|
||||||
if a.Session.ID == "" {
|
if a.Session.ID == "" {
|
||||||
session, err := a.CreateSession(ctx)
|
session, err := a.CreateSession(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return toast.NewErrorToast(err.Error())
|
return a, toast.NewErrorToast(err.Error())
|
||||||
}
|
}
|
||||||
a.Session = session
|
a.Session = session
|
||||||
cmds = append(cmds, util.CmdHandler(SessionSelectedMsg(session)))
|
cmds = append(cmds, util.CmdHandler(SessionSelectedMsg(session)))
|
||||||
}
|
}
|
||||||
|
|
||||||
optimisticMessage := opencode.Message{
|
optimisticParts := []opencode.UserMessagePart{{
|
||||||
ID: fmt.Sprintf("optimistic-%d", time.Now().UnixNano()),
|
Type: opencode.UserMessagePartTypeText,
|
||||||
Role: opencode.MessageRoleUser,
|
Text: text,
|
||||||
Parts: []opencode.MessagePart{{
|
}}
|
||||||
Type: opencode.MessagePartTypeText,
|
if len(attachments) > 0 {
|
||||||
Text: text,
|
for _, attachment := range attachments {
|
||||||
}},
|
optimisticParts = append(optimisticParts, opencode.UserMessagePart{
|
||||||
Metadata: opencode.MessageMetadata{
|
Type: opencode.UserMessagePartTypeFile,
|
||||||
SessionID: a.Session.ID,
|
Filename: attachment.Filename.Value,
|
||||||
Time: opencode.MessageMetadataTime{
|
Mime: attachment.Mime.Value,
|
||||||
Created: float64(time.Now().Unix()),
|
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()),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -326,13 +336,25 @@ func (a *App) SendChatMessage(ctx context.Context, text string, attachments []At
|
||||||
cmds = append(cmds, util.CmdHandler(OptimisticMessageAddedMsg{Message: optimisticMessage}))
|
cmds = append(cmds, util.CmdHandler(OptimisticMessageAddedMsg{Message: optimisticMessage}))
|
||||||
|
|
||||||
cmds = append(cmds, func() tea.Msg {
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_, err := a.Client.Session.Chat(ctx, a.Session.ID, opencode.SessionChatParams{
|
_, err := a.Client.Session.Chat(ctx, a.Session.ID, opencode.SessionChatParams{
|
||||||
Parts: opencode.F([]opencode.MessagePartUnionParam{
|
Parts: opencode.F(parts),
|
||||||
opencode.TextPartParam{
|
|
||||||
Type: opencode.F(opencode.TextPartTypeText),
|
|
||||||
Text: opencode.F(text),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
ProviderID: opencode.F(a.Provider.ID),
|
ProviderID: opencode.F(a.Provider.ID),
|
||||||
ModelID: opencode.F(a.Model.ID),
|
ModelID: opencode.F(a.Model.ID),
|
||||||
})
|
})
|
||||||
|
@ -346,7 +368,7 @@ func (a *App) SendChatMessage(ctx context.Context, text string, attachments []At
|
||||||
|
|
||||||
// The actual response will come through SSE
|
// The actual response will come through SSE
|
||||||
// For now, just return success
|
// For now, just return success
|
||||||
return tea.Batch(cmds...)
|
return a, tea.Batch(cmds...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) Cancel(ctx context.Context, sessionID string) error {
|
func (a *App) Cancel(ctx context.Context, sessionID string) error {
|
||||||
|
|
|
@ -16,12 +16,11 @@ import (
|
||||||
|
|
||||||
type filesAndFoldersContextGroup struct {
|
type filesAndFoldersContextGroup struct {
|
||||||
app *app.App
|
app *app.App
|
||||||
prefix string
|
|
||||||
gitFiles []dialog.CompletionItemI
|
gitFiles []dialog.CompletionItemI
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cg *filesAndFoldersContextGroup) GetId() string {
|
func (cg *filesAndFoldersContextGroup) GetId() string {
|
||||||
return cg.prefix
|
return "files"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cg *filesAndFoldersContextGroup) GetEmptyMessage() string {
|
func (cg *filesAndFoldersContextGroup) GetEmptyMessage() string {
|
||||||
|
@ -107,9 +106,10 @@ func (cg *filesAndFoldersContextGroup) GetChildEntries(
|
||||||
|
|
||||||
func NewFileAndFolderContextGroup(app *app.App) dialog.CompletionProvider {
|
func NewFileAndFolderContextGroup(app *app.App) dialog.CompletionProvider {
|
||||||
cg := &filesAndFoldersContextGroup{
|
cg := &filesAndFoldersContextGroup{
|
||||||
app: app,
|
app: app,
|
||||||
prefix: "file",
|
|
||||||
}
|
}
|
||||||
cg.gitFiles = cg.getGitFiles()
|
go func() {
|
||||||
|
cg.gitFiles = cg.getGitFiles()
|
||||||
|
}()
|
||||||
return cg
|
return cg
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,14 @@ package chat
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/charmbracelet/bubbles/v2/spinner"
|
"github.com/charmbracelet/bubbles/v2/spinner"
|
||||||
tea "github.com/charmbracelet/bubbletea/v2"
|
tea "github.com/charmbracelet/bubbletea/v2"
|
||||||
"github.com/charmbracelet/lipgloss/v2"
|
"github.com/charmbracelet/lipgloss/v2"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/sst/opencode-sdk-go"
|
||||||
"github.com/sst/opencode/internal/app"
|
"github.com/sst/opencode/internal/app"
|
||||||
"github.com/sst/opencode/internal/commands"
|
"github.com/sst/opencode/internal/commands"
|
||||||
"github.com/sst/opencode/internal/components/dialog"
|
"github.com/sst/opencode/internal/components/dialog"
|
||||||
|
@ -37,7 +40,6 @@ type EditorComponent interface {
|
||||||
type editorComponent struct {
|
type editorComponent struct {
|
||||||
app *app.App
|
app *app.App
|
||||||
textarea textarea.Model
|
textarea textarea.Model
|
||||||
attachments []app.Attachment
|
|
||||||
spinner spinner.Model
|
spinner spinner.Model
|
||||||
interruptKeyInDebounce bool
|
interruptKeyInDebounce bool
|
||||||
}
|
}
|
||||||
|
@ -66,17 +68,54 @@ func (m *editorComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
m.spinner = createSpinner()
|
m.spinner = createSpinner()
|
||||||
return m, tea.Batch(m.spinner.Tick, m.textarea.Focus())
|
return m, tea.Batch(m.spinner.Tick, m.textarea.Focus())
|
||||||
case dialog.CompletionSelectedMsg:
|
case dialog.CompletionSelectedMsg:
|
||||||
if msg.IsCommand {
|
switch msg.ProviderID {
|
||||||
|
case "commands":
|
||||||
commandName := strings.TrimPrefix(msg.CompletionValue, "/")
|
commandName := strings.TrimPrefix(msg.CompletionValue, "/")
|
||||||
updated, cmd := m.Clear()
|
updated, cmd := m.Clear()
|
||||||
m = updated.(*editorComponent)
|
m = updated.(*editorComponent)
|
||||||
cmds = append(cmds, cmd)
|
cmds = append(cmds, cmd)
|
||||||
cmds = append(cmds, util.CmdHandler(commands.ExecuteCommandMsg(m.app.Commands[commands.CommandName(commandName)])))
|
cmds = append(cmds, util.CmdHandler(commands.ExecuteCommandMsg(m.app.Commands[commands.CommandName(commandName)])))
|
||||||
return m, tea.Batch(cmds...)
|
return m, tea.Batch(cmds...)
|
||||||
} else {
|
case "files":
|
||||||
existingValue := m.textarea.Value()
|
atIndex := m.textarea.LastRuneIndex('@')
|
||||||
|
if atIndex == -1 {
|
||||||
|
// Should not happen, but as a fallback, just insert.
|
||||||
|
m.textarea.InsertString(msg.CompletionValue + " ")
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Replace the current token (after last space)
|
// The range to replace is from the '@' up to the current cursor position.
|
||||||
|
// Replace the search term (e.g., "@search") with an empty string first.
|
||||||
|
cursorCol := m.textarea.CursorColumn()
|
||||||
|
m.textarea.ReplaceRange(atIndex, cursorCol, "")
|
||||||
|
|
||||||
|
// Now, insert the attachment at the position where the '@' was.
|
||||||
|
// The cursor is now at `atIndex` after the replacement.
|
||||||
|
filePath := msg.CompletionValue
|
||||||
|
extension := filepath.Ext(filePath)
|
||||||
|
mediaType := ""
|
||||||
|
switch extension {
|
||||||
|
case ".jpg":
|
||||||
|
mediaType = "image/jpeg"
|
||||||
|
case ".png", ".jpeg", ".gif", ".webp":
|
||||||
|
mediaType = "image/" + extension[1:]
|
||||||
|
case ".pdf":
|
||||||
|
mediaType = "application/pdf"
|
||||||
|
default:
|
||||||
|
mediaType = "text/plain"
|
||||||
|
}
|
||||||
|
attachment := &textarea.Attachment{
|
||||||
|
ID: uuid.NewString(),
|
||||||
|
Display: "@" + filePath,
|
||||||
|
URL: fmt.Sprintf("file://./%s", filePath),
|
||||||
|
Filename: filePath,
|
||||||
|
MediaType: mediaType,
|
||||||
|
}
|
||||||
|
m.textarea.InsertAttachment(attachment)
|
||||||
|
m.textarea.InsertString(" ")
|
||||||
|
return m, nil
|
||||||
|
default:
|
||||||
|
existingValue := m.textarea.Value()
|
||||||
lastSpaceIndex := strings.LastIndex(existingValue, " ")
|
lastSpaceIndex := strings.LastIndex(existingValue, " ")
|
||||||
if lastSpaceIndex == -1 {
|
if lastSpaceIndex == -1 {
|
||||||
m.textarea.SetValue(msg.CompletionValue + " ")
|
m.textarea.SetValue(msg.CompletionValue + " ")
|
||||||
|
@ -128,7 +167,15 @@ func (m *editorComponent) Content(width int) string {
|
||||||
if m.app.IsBusy() {
|
if m.app.IsBusy() {
|
||||||
keyText := m.getInterruptKeyText()
|
keyText := m.getInterruptKeyText()
|
||||||
if m.interruptKeyInDebounce {
|
if m.interruptKeyInDebounce {
|
||||||
hint = muted("working") + m.spinner.View() + muted(" ") + base(keyText+" again") + muted(" interrupt")
|
hint = muted(
|
||||||
|
"working",
|
||||||
|
) + m.spinner.View() + muted(
|
||||||
|
" ",
|
||||||
|
) + base(
|
||||||
|
keyText+" again",
|
||||||
|
) + muted(
|
||||||
|
" interrupt",
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
hint = muted("working") + m.spinner.View() + muted(" ") + base(keyText) + muted(" interrupt")
|
hint = muted("working") + m.spinner.View() + muted(" ") + base(keyText) + muted(" interrupt")
|
||||||
}
|
}
|
||||||
|
@ -190,19 +237,29 @@ func (m *editorComponent) Submit() (tea.Model, tea.Cmd) {
|
||||||
}
|
}
|
||||||
if len(value) > 0 && value[len(value)-1] == '\\' {
|
if len(value) > 0 && value[len(value)-1] == '\\' {
|
||||||
// If the last character is a backslash, remove it and add a newline
|
// If the last character is a backslash, remove it and add a newline
|
||||||
m.textarea.SetValue(value[:len(value)-1] + "\n")
|
m.textarea.ReplaceRange(len(value)-1, len(value), "")
|
||||||
|
m.textarea.InsertString("\n")
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var cmds []tea.Cmd
|
var cmds []tea.Cmd
|
||||||
|
|
||||||
|
attachments := m.textarea.GetAttachments()
|
||||||
|
fileParts := make([]opencode.FilePartParam, 0)
|
||||||
|
for _, attachment := range attachments {
|
||||||
|
fileParts = append(fileParts, opencode.FilePartParam{
|
||||||
|
Type: opencode.F(opencode.FilePartTypeFile),
|
||||||
|
Mime: opencode.F(attachment.MediaType),
|
||||||
|
URL: opencode.F(attachment.URL),
|
||||||
|
Filename: opencode.F(attachment.Filename),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
updated, cmd := m.Clear()
|
updated, cmd := m.Clear()
|
||||||
m = updated.(*editorComponent)
|
m = updated.(*editorComponent)
|
||||||
cmds = append(cmds, cmd)
|
cmds = append(cmds, cmd)
|
||||||
|
|
||||||
attachments := m.attachments
|
cmds = append(cmds, util.CmdHandler(app.SendMsg{Text: value, Attachments: fileParts}))
|
||||||
m.attachments = nil
|
|
||||||
|
|
||||||
cmds = append(cmds, util.CmdHandler(app.SendMsg{Text: value, Attachments: attachments}))
|
|
||||||
return m, tea.Batch(cmds...)
|
return m, tea.Batch(cmds...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,18 +269,23 @@ func (m *editorComponent) Clear() (tea.Model, tea.Cmd) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *editorComponent) Paste() (tea.Model, tea.Cmd) {
|
func (m *editorComponent) Paste() (tea.Model, tea.Cmd) {
|
||||||
imageBytes, text, err := image.GetImageFromClipboard()
|
_, text, err := image.GetImageFromClipboard()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error(err.Error())
|
slog.Error(err.Error())
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
if len(imageBytes) != 0 {
|
// if len(imageBytes) != 0 {
|
||||||
attachmentName := fmt.Sprintf("clipboard-image-%d", len(m.attachments))
|
// attachmentName := fmt.Sprintf("clipboard-image-%d", len(m.attachments))
|
||||||
attachment := app.Attachment{FilePath: attachmentName, FileName: attachmentName, Content: imageBytes, MimeType: "image/png"}
|
// attachment := app.Attachment{
|
||||||
m.attachments = append(m.attachments, attachment)
|
// FilePath: attachmentName,
|
||||||
} else {
|
// FileName: attachmentName,
|
||||||
m.textarea.SetValue(m.textarea.Value() + text)
|
// Content: imageBytes,
|
||||||
}
|
// MimeType: "image/png",
|
||||||
|
// }
|
||||||
|
// m.attachments = append(m.attachments, attachment)
|
||||||
|
// } else {
|
||||||
|
m.textarea.InsertString(text)
|
||||||
|
// }
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,12 +316,26 @@ func createTextArea(existing *textarea.Model) textarea.Model {
|
||||||
|
|
||||||
ta.Styles.Blurred.Base = styles.NewStyle().Foreground(textColor).Background(bgColor).Lipgloss()
|
ta.Styles.Blurred.Base = styles.NewStyle().Foreground(textColor).Background(bgColor).Lipgloss()
|
||||||
ta.Styles.Blurred.CursorLine = styles.NewStyle().Background(bgColor).Lipgloss()
|
ta.Styles.Blurred.CursorLine = styles.NewStyle().Background(bgColor).Lipgloss()
|
||||||
ta.Styles.Blurred.Placeholder = styles.NewStyle().Foreground(textMutedColor).Background(bgColor).Lipgloss()
|
ta.Styles.Blurred.Placeholder = styles.NewStyle().
|
||||||
|
Foreground(textMutedColor).
|
||||||
|
Background(bgColor).
|
||||||
|
Lipgloss()
|
||||||
ta.Styles.Blurred.Text = styles.NewStyle().Foreground(textColor).Background(bgColor).Lipgloss()
|
ta.Styles.Blurred.Text = styles.NewStyle().Foreground(textColor).Background(bgColor).Lipgloss()
|
||||||
ta.Styles.Focused.Base = styles.NewStyle().Foreground(textColor).Background(bgColor).Lipgloss()
|
ta.Styles.Focused.Base = styles.NewStyle().Foreground(textColor).Background(bgColor).Lipgloss()
|
||||||
ta.Styles.Focused.CursorLine = styles.NewStyle().Background(bgColor).Lipgloss()
|
ta.Styles.Focused.CursorLine = styles.NewStyle().Background(bgColor).Lipgloss()
|
||||||
ta.Styles.Focused.Placeholder = styles.NewStyle().Foreground(textMutedColor).Background(bgColor).Lipgloss()
|
ta.Styles.Focused.Placeholder = styles.NewStyle().
|
||||||
|
Foreground(textMutedColor).
|
||||||
|
Background(bgColor).
|
||||||
|
Lipgloss()
|
||||||
ta.Styles.Focused.Text = styles.NewStyle().Foreground(textColor).Background(bgColor).Lipgloss()
|
ta.Styles.Focused.Text = styles.NewStyle().Foreground(textColor).Background(bgColor).Lipgloss()
|
||||||
|
ta.Styles.Attachment = styles.NewStyle().
|
||||||
|
Foreground(t.Secondary()).
|
||||||
|
Background(bgColor).
|
||||||
|
Lipgloss()
|
||||||
|
ta.Styles.SelectedAttachment = styles.NewStyle().
|
||||||
|
Foreground(t.Text()).
|
||||||
|
Background(t.Secondary()).
|
||||||
|
Lipgloss()
|
||||||
ta.Styles.Cursor.Color = t.Primary()
|
ta.Styles.Cursor.Color = t.Primary()
|
||||||
|
|
||||||
ta.Prompt = " "
|
ta.Prompt = " "
|
||||||
|
|
|
@ -17,7 +17,6 @@ import (
|
||||||
"github.com/sst/opencode/internal/styles"
|
"github.com/sst/opencode/internal/styles"
|
||||||
"github.com/sst/opencode/internal/theme"
|
"github.com/sst/opencode/internal/theme"
|
||||||
"github.com/sst/opencode/internal/util"
|
"github.com/sst/opencode/internal/util"
|
||||||
"github.com/tidwall/gjson"
|
|
||||||
"golang.org/x/text/cases"
|
"golang.org/x/text/cases"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
)
|
)
|
||||||
|
@ -217,17 +216,35 @@ func renderContentBlock(
|
||||||
|
|
||||||
func renderText(
|
func renderText(
|
||||||
app *app.App,
|
app *app.App,
|
||||||
message opencode.Message,
|
message opencode.MessageUnion,
|
||||||
text string,
|
text string,
|
||||||
author string,
|
author string,
|
||||||
showToolDetails bool,
|
showToolDetails bool,
|
||||||
highlight bool,
|
highlight bool,
|
||||||
width int,
|
width int,
|
||||||
toolCalls ...opencode.ToolInvocationPart,
|
extra string,
|
||||||
|
toolCalls ...opencode.ToolPart,
|
||||||
) string {
|
) string {
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
|
|
||||||
timestamp := time.UnixMilli(int64(message.Metadata.Time.Created)).
|
var ts time.Time
|
||||||
|
backgroundColor := t.BackgroundPanel()
|
||||||
|
if highlight {
|
||||||
|
backgroundColor = t.BackgroundElement()
|
||||||
|
}
|
||||||
|
messageStyle := styles.NewStyle().Background(backgroundColor)
|
||||||
|
content := messageStyle.Render(text)
|
||||||
|
|
||||||
|
switch casted := message.(type) {
|
||||||
|
case opencode.AssistantMessage:
|
||||||
|
ts = time.UnixMilli(int64(casted.Time.Created))
|
||||||
|
content = util.ToMarkdown(text, width, backgroundColor)
|
||||||
|
case opencode.UserMessage:
|
||||||
|
ts = time.UnixMilli(int64(casted.Time.Created))
|
||||||
|
messageStyle = messageStyle.Width(width - 6)
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp := ts.
|
||||||
Local().
|
Local().
|
||||||
Format("02 Jan 2006 03:04 PM")
|
Format("02 Jan 2006 03:04 PM")
|
||||||
if time.Now().Format("02 Jan 2006") == timestamp[:11] {
|
if time.Now().Format("02 Jan 2006") == timestamp[:11] {
|
||||||
|
@ -237,30 +254,12 @@ func renderText(
|
||||||
info := fmt.Sprintf("%s (%s)", author, timestamp)
|
info := fmt.Sprintf("%s (%s)", author, timestamp)
|
||||||
info = styles.NewStyle().Foreground(t.TextMuted()).Render(info)
|
info = styles.NewStyle().Foreground(t.TextMuted()).Render(info)
|
||||||
|
|
||||||
backgroundColor := t.BackgroundPanel()
|
|
||||||
if highlight {
|
|
||||||
backgroundColor = t.BackgroundElement()
|
|
||||||
}
|
|
||||||
messageStyle := styles.NewStyle().Background(backgroundColor)
|
|
||||||
if message.Role == opencode.MessageRoleUser {
|
|
||||||
messageStyle = messageStyle.Width(width - 6)
|
|
||||||
}
|
|
||||||
|
|
||||||
content := messageStyle.Render(text)
|
|
||||||
if message.Role == opencode.MessageRoleAssistant {
|
|
||||||
content = util.ToMarkdown(text, width, backgroundColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !showToolDetails && toolCalls != nil && len(toolCalls) > 0 {
|
if !showToolDetails && toolCalls != nil && len(toolCalls) > 0 {
|
||||||
content = content + "\n\n"
|
content = content + "\n\n"
|
||||||
for _, toolCall := range toolCalls {
|
for _, toolCall := range toolCalls {
|
||||||
title := renderToolTitle(toolCall, message.Metadata, width)
|
title := renderToolTitle(toolCall, width)
|
||||||
metadata := opencode.MessageMetadataTool{}
|
|
||||||
if _, ok := message.Metadata.Tool[toolCall.ToolInvocation.ToolCallID]; ok {
|
|
||||||
metadata = message.Metadata.Tool[toolCall.ToolInvocation.ToolCallID]
|
|
||||||
}
|
|
||||||
style := styles.NewStyle()
|
style := styles.NewStyle()
|
||||||
if _, ok := metadata.ExtraFields["error"]; ok {
|
if toolCall.State.Status == opencode.ToolPartStateStatusError {
|
||||||
style = style.Foreground(t.Error())
|
style = style.Foreground(t.Error())
|
||||||
}
|
}
|
||||||
title = style.Render(title)
|
title = style.Render(title)
|
||||||
|
@ -269,10 +268,14 @@ func renderText(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
content = strings.Join([]string{content, info}, "\n")
|
sections := []string{content, info}
|
||||||
|
if extra != "" {
|
||||||
|
sections = append(sections, "\n"+extra)
|
||||||
|
}
|
||||||
|
content = strings.Join(sections, "\n")
|
||||||
|
|
||||||
switch message.Role {
|
switch message.(type) {
|
||||||
case opencode.MessageRoleUser:
|
case opencode.UserMessage:
|
||||||
return renderContentBlock(
|
return renderContentBlock(
|
||||||
app,
|
app,
|
||||||
content,
|
content,
|
||||||
|
@ -281,7 +284,7 @@ func renderText(
|
||||||
WithTextColor(t.Text()),
|
WithTextColor(t.Text()),
|
||||||
WithBorderColorRight(t.Secondary()),
|
WithBorderColorRight(t.Secondary()),
|
||||||
)
|
)
|
||||||
case opencode.MessageRoleAssistant:
|
case opencode.AssistantMessage:
|
||||||
return renderContentBlock(
|
return renderContentBlock(
|
||||||
app,
|
app,
|
||||||
content,
|
content,
|
||||||
|
@ -295,39 +298,32 @@ func renderText(
|
||||||
|
|
||||||
func renderToolDetails(
|
func renderToolDetails(
|
||||||
app *app.App,
|
app *app.App,
|
||||||
toolCall opencode.ToolInvocationPart,
|
toolCall opencode.ToolPart,
|
||||||
messageMetadata opencode.MessageMetadata,
|
|
||||||
highlight bool,
|
highlight bool,
|
||||||
width int,
|
width int,
|
||||||
) string {
|
) string {
|
||||||
ignoredTools := []string{"todoread"}
|
ignoredTools := []string{"todoread"}
|
||||||
if slices.Contains(ignoredTools, toolCall.ToolInvocation.ToolName) {
|
if slices.Contains(ignoredTools, toolCall.Tool) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
toolCallID := toolCall.ToolInvocation.ToolCallID
|
if toolCall.State.Status == opencode.ToolPartStateStatusPending || toolCall.State.Status == opencode.ToolPartStateStatusRunning {
|
||||||
metadata := opencode.MessageMetadataTool{}
|
title := renderToolTitle(toolCall, width)
|
||||||
if _, ok := messageMetadata.Tool[toolCallID]; ok {
|
|
||||||
metadata = messageMetadata.Tool[toolCallID]
|
|
||||||
}
|
|
||||||
|
|
||||||
var result *string
|
|
||||||
if toolCall.ToolInvocation.Result != "" {
|
|
||||||
result = &toolCall.ToolInvocation.Result
|
|
||||||
}
|
|
||||||
|
|
||||||
if toolCall.ToolInvocation.State == "partial-call" {
|
|
||||||
title := renderToolTitle(toolCall, messageMetadata, width)
|
|
||||||
return renderContentBlock(app, title, highlight, width)
|
return renderContentBlock(app, title, highlight, width)
|
||||||
}
|
}
|
||||||
|
|
||||||
toolArgsMap := make(map[string]any)
|
var result *string
|
||||||
if toolCall.ToolInvocation.Args != nil {
|
if toolCall.State.Output != "" {
|
||||||
value := toolCall.ToolInvocation.Args
|
result = &toolCall.State.Output
|
||||||
|
}
|
||||||
|
|
||||||
|
toolInputMap := make(map[string]any)
|
||||||
|
if toolCall.State.Input != nil {
|
||||||
|
value := toolCall.State.Input
|
||||||
if m, ok := value.(map[string]any); ok {
|
if m, ok := value.(map[string]any); ok {
|
||||||
toolArgsMap = m
|
toolInputMap = m
|
||||||
keys := make([]string, 0, len(toolArgsMap))
|
keys := make([]string, 0, len(toolInputMap))
|
||||||
for key := range toolArgsMap {
|
for key := range toolInputMap {
|
||||||
keys = append(keys, key)
|
keys = append(keys, key)
|
||||||
}
|
}
|
||||||
slices.Sort(keys)
|
slices.Sort(keys)
|
||||||
|
@ -335,7 +331,6 @@ func renderToolDetails(
|
||||||
}
|
}
|
||||||
|
|
||||||
body := ""
|
body := ""
|
||||||
finished := result != nil && *result != ""
|
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
backgroundColor := t.BackgroundPanel()
|
backgroundColor := t.BackgroundPanel()
|
||||||
borderColor := t.BackgroundPanel()
|
borderColor := t.BackgroundPanel()
|
||||||
|
@ -344,137 +339,128 @@ func renderToolDetails(
|
||||||
borderColor = t.BorderActive()
|
borderColor = t.BorderActive()
|
||||||
}
|
}
|
||||||
|
|
||||||
switch toolCall.ToolInvocation.ToolName {
|
if toolCall.State.Status == opencode.ToolPartStateStatusCompleted {
|
||||||
case "read":
|
metadata := toolCall.State.Metadata.(map[string]any)
|
||||||
preview := metadata.ExtraFields["preview"]
|
switch toolCall.Tool {
|
||||||
if preview != nil && toolArgsMap["filePath"] != nil {
|
case "read":
|
||||||
filename := toolArgsMap["filePath"].(string)
|
preview := metadata["preview"]
|
||||||
body = preview.(string)
|
if preview != nil && toolInputMap["filePath"] != nil {
|
||||||
body = util.RenderFile(filename, body, width, util.WithTruncate(6))
|
filename := toolInputMap["filePath"].(string)
|
||||||
}
|
body = preview.(string)
|
||||||
case "edit":
|
body = util.RenderFile(filename, body, width, util.WithTruncate(6))
|
||||||
if filename, ok := toolArgsMap["filePath"].(string); ok {
|
|
||||||
diffField := metadata.ExtraFields["diff"]
|
|
||||||
if diffField != nil {
|
|
||||||
patch := diffField.(string)
|
|
||||||
var formattedDiff string
|
|
||||||
formattedDiff, _ = diff.FormatUnifiedDiff(
|
|
||||||
filename,
|
|
||||||
patch,
|
|
||||||
diff.WithWidth(width-2),
|
|
||||||
)
|
|
||||||
body = strings.TrimSpace(formattedDiff)
|
|
||||||
style := styles.NewStyle().
|
|
||||||
Background(backgroundColor).
|
|
||||||
Foreground(t.TextMuted()).
|
|
||||||
Padding(1, 2).
|
|
||||||
Width(width - 4)
|
|
||||||
if highlight {
|
|
||||||
style = style.Foreground(t.Text()).Bold(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
if diagnostics := renderDiagnostics(metadata, filename); diagnostics != "" {
|
|
||||||
diagnostics = style.Render(diagnostics)
|
|
||||||
body += "\n" + diagnostics
|
|
||||||
}
|
|
||||||
|
|
||||||
title := renderToolTitle(toolCall, messageMetadata, width)
|
|
||||||
title = style.Render(title)
|
|
||||||
content := title + "\n" + body
|
|
||||||
content = renderContentBlock(
|
|
||||||
app,
|
|
||||||
content,
|
|
||||||
highlight,
|
|
||||||
width,
|
|
||||||
WithPadding(0),
|
|
||||||
WithBorderColor(borderColor),
|
|
||||||
)
|
|
||||||
return content
|
|
||||||
}
|
}
|
||||||
}
|
case "edit":
|
||||||
case "write":
|
if filename, ok := toolInputMap["filePath"].(string); ok {
|
||||||
if filename, ok := toolArgsMap["filePath"].(string); ok {
|
diffField := metadata["diff"]
|
||||||
if content, ok := toolArgsMap["content"].(string); ok {
|
if diffField != nil {
|
||||||
body = util.RenderFile(filename, content, width)
|
patch := diffField.(string)
|
||||||
if diagnostics := renderDiagnostics(metadata, filename); diagnostics != "" {
|
var formattedDiff string
|
||||||
body += "\n\n" + diagnostics
|
formattedDiff, _ = diff.FormatUnifiedDiff(
|
||||||
|
filename,
|
||||||
|
patch,
|
||||||
|
diff.WithWidth(width-2),
|
||||||
|
)
|
||||||
|
body = strings.TrimSpace(formattedDiff)
|
||||||
|
style := styles.NewStyle().
|
||||||
|
Background(backgroundColor).
|
||||||
|
Foreground(t.TextMuted()).
|
||||||
|
Padding(1, 2).
|
||||||
|
Width(width - 4)
|
||||||
|
if highlight {
|
||||||
|
style = style.Foreground(t.Text()).Bold(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if diagnostics := renderDiagnostics(metadata, filename); diagnostics != "" {
|
||||||
|
diagnostics = style.Render(diagnostics)
|
||||||
|
body += "\n" + diagnostics
|
||||||
|
}
|
||||||
|
|
||||||
|
title := renderToolTitle(toolCall, width)
|
||||||
|
title = style.Render(title)
|
||||||
|
content := title + "\n" + body
|
||||||
|
content = renderContentBlock(
|
||||||
|
app,
|
||||||
|
content,
|
||||||
|
highlight,
|
||||||
|
width,
|
||||||
|
WithPadding(0),
|
||||||
|
WithBorderColor(borderColor),
|
||||||
|
)
|
||||||
|
return content
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
case "write":
|
||||||
case "bash":
|
if filename, ok := toolInputMap["filePath"].(string); ok {
|
||||||
stdout := metadata.ExtraFields["stdout"]
|
if content, ok := toolInputMap["content"].(string); ok {
|
||||||
if stdout != nil {
|
body = util.RenderFile(filename, content, width)
|
||||||
command := toolArgsMap["command"].(string)
|
if diagnostics := renderDiagnostics(metadata, filename); diagnostics != "" {
|
||||||
body = fmt.Sprintf("```console\n> %s\n%s```", command, stdout)
|
body += "\n\n" + diagnostics
|
||||||
body = util.ToMarkdown(body, width, backgroundColor)
|
}
|
||||||
}
|
}
|
||||||
case "webfetch":
|
}
|
||||||
if format, ok := toolArgsMap["format"].(string); ok && result != nil {
|
case "bash":
|
||||||
body = *result
|
stdout := metadata["stdout"]
|
||||||
body = util.TruncateHeight(body, 10)
|
if stdout != nil {
|
||||||
if format == "html" || format == "markdown" {
|
command := toolInputMap["command"].(string)
|
||||||
|
body = fmt.Sprintf("```console\n> %s\n%s```", command, stdout)
|
||||||
body = util.ToMarkdown(body, width, backgroundColor)
|
body = util.ToMarkdown(body, width, backgroundColor)
|
||||||
}
|
}
|
||||||
}
|
case "webfetch":
|
||||||
case "todowrite":
|
if format, ok := toolInputMap["format"].(string); ok && result != nil {
|
||||||
todos := metadata.JSON.ExtraFields["todos"]
|
body = *result
|
||||||
if !todos.IsNull() && finished {
|
body = util.TruncateHeight(body, 10)
|
||||||
strTodos := todos.Raw()
|
if format == "html" || format == "markdown" {
|
||||||
todos := gjson.Parse(strTodos)
|
body = util.ToMarkdown(body, width, backgroundColor)
|
||||||
for _, todo := range todos.Array() {
|
|
||||||
content := todo.Get("content").String()
|
|
||||||
switch todo.Get("status").String() {
|
|
||||||
case "completed":
|
|
||||||
body += fmt.Sprintf("- [x] %s\n", content)
|
|
||||||
// case "in-progress":
|
|
||||||
// body += fmt.Sprintf("- [ ] %s\n", content)
|
|
||||||
default:
|
|
||||||
body += fmt.Sprintf("- [ ] %s\n", content)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
body = util.ToMarkdown(body, width, backgroundColor)
|
case "todowrite":
|
||||||
}
|
todos := metadata["todos"]
|
||||||
case "task":
|
if todos != nil {
|
||||||
summary := metadata.JSON.ExtraFields["summary"]
|
for _, item := range todos.([]any) {
|
||||||
if !summary.IsNull() {
|
todo := item.(map[string]any)
|
||||||
strValue := summary.Raw()
|
content := todo["content"].(string)
|
||||||
toolcalls := gjson.Parse(strValue).Array()
|
switch todo["status"] {
|
||||||
|
case "completed":
|
||||||
steps := []string{}
|
body += fmt.Sprintf("- [x] %s\n", content)
|
||||||
for _, toolcall := range toolcalls {
|
// case "in-progress":
|
||||||
call := toolcall.Value().(map[string]any)
|
// body += fmt.Sprintf("- [ ] %s\n", content)
|
||||||
if toolInvocation, ok := call["toolInvocation"].(map[string]any); ok {
|
default:
|
||||||
data, _ := json.Marshal(toolInvocation)
|
body += fmt.Sprintf("- [ ] %s\n", content)
|
||||||
var toolCall opencode.ToolInvocationPart
|
}
|
||||||
_ = json.Unmarshal(data, &toolCall)
|
}
|
||||||
|
body = util.ToMarkdown(body, width, backgroundColor)
|
||||||
if metadata, ok := call["metadata"].(map[string]any); ok {
|
}
|
||||||
data, _ = json.Marshal(metadata)
|
case "task":
|
||||||
var toolMetadata opencode.MessageMetadataTool
|
summary := metadata["summary"]
|
||||||
_ = json.Unmarshal(data, &toolMetadata)
|
if summary != nil {
|
||||||
|
toolcalls := summary.([]any)
|
||||||
step := renderToolTitle(toolCall, messageMetadata, width)
|
steps := []string{}
|
||||||
|
for _, toolcall := range toolcalls {
|
||||||
|
call := toolcall.(map[string]any)
|
||||||
|
if toolInvocation, ok := call["toolInvocation"].(map[string]any); ok {
|
||||||
|
data, _ := json.Marshal(toolInvocation)
|
||||||
|
var toolCall opencode.ToolPart
|
||||||
|
_ = json.Unmarshal(data, &toolCall)
|
||||||
|
step := renderToolTitle(toolCall, width)
|
||||||
step = "∟ " + step
|
step = "∟ " + step
|
||||||
steps = append(steps, step)
|
steps = append(steps, step)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
body = strings.Join(steps, "\n")
|
||||||
}
|
}
|
||||||
body = strings.Join(steps, "\n")
|
default:
|
||||||
|
if result == nil {
|
||||||
|
empty := ""
|
||||||
|
result = &empty
|
||||||
|
}
|
||||||
|
body = *result
|
||||||
|
body = util.TruncateHeight(body, 10)
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
if result == nil {
|
|
||||||
empty := ""
|
|
||||||
result = &empty
|
|
||||||
}
|
|
||||||
body = *result
|
|
||||||
body = util.TruncateHeight(body, 10)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
error := ""
|
error := ""
|
||||||
if err, ok := metadata.ExtraFields["error"].(bool); ok && err {
|
if toolCall.State.Status == opencode.ToolPartStateStatusError {
|
||||||
if message, ok := metadata.ExtraFields["message"].(string); ok {
|
error = toolCall.State.Error
|
||||||
error = message
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if error != "" {
|
if error != "" {
|
||||||
|
@ -489,7 +475,7 @@ func renderToolDetails(
|
||||||
body = util.TruncateHeight(body, 10)
|
body = util.TruncateHeight(body, 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
title := renderToolTitle(toolCall, messageMetadata, width)
|
title := renderToolTitle(toolCall, width)
|
||||||
content := title + "\n\n" + body
|
content := title + "\n\n" + body
|
||||||
return renderContentBlock(app, content, highlight, width, WithBorderColor(borderColor))
|
return renderContentBlock(app, content, highlight, width, WithBorderColor(borderColor))
|
||||||
}
|
}
|
||||||
|
@ -510,20 +496,19 @@ func renderToolName(name string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderToolTitle(
|
func renderToolTitle(
|
||||||
toolCall opencode.ToolInvocationPart,
|
toolCall opencode.ToolPart,
|
||||||
messageMetadata opencode.MessageMetadata,
|
|
||||||
width int,
|
width int,
|
||||||
) string {
|
) string {
|
||||||
// TODO: handle truncate to width
|
// TODO: handle truncate to width
|
||||||
|
|
||||||
if toolCall.ToolInvocation.State == "partial-call" {
|
if toolCall.State.Status == opencode.ToolPartStateStatusPending {
|
||||||
return renderToolAction(toolCall.ToolInvocation.ToolName)
|
return renderToolAction(toolCall.Tool)
|
||||||
}
|
}
|
||||||
|
|
||||||
toolArgs := ""
|
toolArgs := ""
|
||||||
toolArgsMap := make(map[string]any)
|
toolArgsMap := make(map[string]any)
|
||||||
if toolCall.ToolInvocation.Args != nil {
|
if toolCall.State.Input != nil {
|
||||||
value := toolCall.ToolInvocation.Args
|
value := toolCall.State.Input
|
||||||
if m, ok := value.(map[string]any); ok {
|
if m, ok := value.(map[string]any); ok {
|
||||||
toolArgsMap = m
|
toolArgsMap = m
|
||||||
|
|
||||||
|
@ -541,8 +526,8 @@ func renderToolTitle(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
title := renderToolName(toolCall.ToolInvocation.ToolName)
|
title := renderToolName(toolCall.Tool)
|
||||||
switch toolCall.ToolInvocation.ToolName {
|
switch toolCall.Tool {
|
||||||
case "read":
|
case "read":
|
||||||
toolArgs = renderArgs(&toolArgsMap, "filePath")
|
toolArgs = renderArgs(&toolArgsMap, "filePath")
|
||||||
title = fmt.Sprintf("%s %s", title, toolArgs)
|
title = fmt.Sprintf("%s %s", title, toolArgs)
|
||||||
|
@ -560,7 +545,7 @@ func renderToolTitle(
|
||||||
case "todowrite", "todoread":
|
case "todowrite", "todoread":
|
||||||
// title is just the tool name
|
// title is just the tool name
|
||||||
default:
|
default:
|
||||||
toolName := renderToolName(toolCall.ToolInvocation.ToolName)
|
toolName := renderToolName(toolCall.Tool)
|
||||||
title = fmt.Sprintf("%s %s", toolName, toolArgs)
|
title = fmt.Sprintf("%s %s", toolName, toolArgs)
|
||||||
}
|
}
|
||||||
return title
|
return title
|
||||||
|
@ -640,8 +625,8 @@ type Diagnostic struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderDiagnostics formats LSP diagnostics for display in the TUI
|
// renderDiagnostics formats LSP diagnostics for display in the TUI
|
||||||
func renderDiagnostics(metadata opencode.MessageMetadataTool, filePath string) string {
|
func renderDiagnostics(metadata map[string]any, filePath string) string {
|
||||||
if diagnosticsData, ok := metadata.ExtraFields["diagnostics"].(map[string]any); ok {
|
if diagnosticsData, ok := metadata["diagnostics"].(map[string]any); ok {
|
||||||
if fileDiagnostics, ok := diagnosticsData[filePath].([]any); ok {
|
if fileDiagnostics, ok := diagnosticsData[filePath].([]any); ok {
|
||||||
var errorDiagnostics []string
|
var errorDiagnostics []string
|
||||||
for _, diagInterface := range fileDiagnostics {
|
for _, diagInterface := range fileDiagnostics {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/sst/opencode-sdk-go"
|
"github.com/sst/opencode-sdk-go"
|
||||||
"github.com/sst/opencode/internal/app"
|
"github.com/sst/opencode/internal/app"
|
||||||
"github.com/sst/opencode/internal/components/dialog"
|
"github.com/sst/opencode/internal/components/dialog"
|
||||||
|
"github.com/sst/opencode/internal/layout"
|
||||||
"github.com/sst/opencode/internal/styles"
|
"github.com/sst/opencode/internal/styles"
|
||||||
"github.com/sst/opencode/internal/theme"
|
"github.com/sst/opencode/internal/theme"
|
||||||
"github.com/sst/opencode/internal/util"
|
"github.com/sst/opencode/internal/util"
|
||||||
|
@ -67,11 +68,9 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
m.selectedPart = -1
|
m.selectedPart = -1
|
||||||
return m, nil
|
return m, nil
|
||||||
case app.OptimisticMessageAddedMsg:
|
case app.OptimisticMessageAddedMsg:
|
||||||
m.renderView(m.width)
|
m.tail = true
|
||||||
if m.tail {
|
m.rendering = true
|
||||||
m.viewport.GotoBottom()
|
return m, m.Reload()
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
case dialog.ThemeSelectedMsg:
|
case dialog.ThemeSelectedMsg:
|
||||||
m.cache.Clear()
|
m.cache.Clear()
|
||||||
m.rendering = true
|
m.rendering = true
|
||||||
|
@ -100,7 +99,7 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case opencode.EventListResponseEventMessageUpdated:
|
case opencode.EventListResponseEventMessageUpdated:
|
||||||
if msg.Properties.Info.Metadata.SessionID == m.app.Session.ID {
|
if msg.Properties.Info.SessionID == m.app.Session.ID {
|
||||||
m.renderView(m.width)
|
m.renderView(m.width)
|
||||||
if m.tail {
|
if m.tail {
|
||||||
m.viewport.GotoBottom()
|
m.viewport.GotoBottom()
|
||||||
|
@ -125,18 +124,58 @@ func (m *messagesComponent) renderView(width int) {
|
||||||
m.partCount = 0
|
m.partCount = 0
|
||||||
m.lineCount = 0
|
m.lineCount = 0
|
||||||
|
|
||||||
orphanedToolCalls := make([]opencode.ToolInvocationPart, 0)
|
orphanedToolCalls := make([]opencode.ToolPart, 0)
|
||||||
|
|
||||||
for _, message := range m.app.Messages {
|
for _, message := range m.app.Messages {
|
||||||
var content string
|
var content string
|
||||||
var cached bool
|
var cached bool
|
||||||
|
|
||||||
switch message.Role {
|
switch casted := message.(type) {
|
||||||
case opencode.MessageRoleUser:
|
case opencode.UserMessage:
|
||||||
for _, part := range message.Parts {
|
userLoop:
|
||||||
|
for partIndex, part := range casted.Parts {
|
||||||
switch part := part.AsUnion().(type) {
|
switch part := part.AsUnion().(type) {
|
||||||
case opencode.TextPart:
|
case opencode.TextPart:
|
||||||
key := m.cache.GenerateKey(message.ID, part.Text, width, m.selectedPart == m.partCount)
|
remainingParts := casted.Parts[partIndex+1:]
|
||||||
|
fileParts := make([]opencode.FilePart, 0)
|
||||||
|
for _, part := range remainingParts {
|
||||||
|
switch part := part.AsUnion().(type) {
|
||||||
|
case opencode.FilePart:
|
||||||
|
fileParts = append(fileParts, part)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flexItems := []layout.FlexItem{}
|
||||||
|
if len(fileParts) > 0 {
|
||||||
|
fileStyle := styles.NewStyle().Background(t.BackgroundElement()).Foreground(t.TextMuted()).Padding(0, 1)
|
||||||
|
mediaTypeStyle := styles.NewStyle().Background(t.Secondary()).Foreground(t.BackgroundPanel()).Padding(0, 1)
|
||||||
|
for _, filePart := range fileParts {
|
||||||
|
mediaType := ""
|
||||||
|
switch filePart.Mime {
|
||||||
|
case "text/plain":
|
||||||
|
mediaType = "txt"
|
||||||
|
case "image/png", "image/jpeg", "image/gif", "image/webp":
|
||||||
|
mediaType = "img"
|
||||||
|
mediaTypeStyle = mediaTypeStyle.Background(t.Accent())
|
||||||
|
case "application/pdf":
|
||||||
|
mediaType = "pdf"
|
||||||
|
mediaTypeStyle = mediaTypeStyle.Background(t.Primary())
|
||||||
|
}
|
||||||
|
flexItems = append(flexItems, layout.FlexItem{
|
||||||
|
View: mediaTypeStyle.Render(mediaType) + fileStyle.Render(filePart.Filename),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bgColor := t.BackgroundPanel()
|
||||||
|
files := layout.Render(
|
||||||
|
layout.FlexOptions{
|
||||||
|
Background: &bgColor,
|
||||||
|
Width: width - 6,
|
||||||
|
Direction: layout.Column,
|
||||||
|
},
|
||||||
|
flexItems...,
|
||||||
|
)
|
||||||
|
|
||||||
|
key := m.cache.GenerateKey(casted.ID, part.Text, width, m.selectedPart == m.partCount, files)
|
||||||
content, cached = m.cache.Get(key)
|
content, cached = m.cache.Get(key)
|
||||||
if !cached {
|
if !cached {
|
||||||
content = renderText(
|
content = renderText(
|
||||||
|
@ -147,6 +186,7 @@ func (m *messagesComponent) renderView(width int) {
|
||||||
m.showToolDetails,
|
m.showToolDetails,
|
||||||
m.partCount == m.selectedPart,
|
m.partCount == m.selectedPart,
|
||||||
width,
|
width,
|
||||||
|
files,
|
||||||
)
|
)
|
||||||
m.cache.Set(key, content)
|
m.cache.Set(key, content)
|
||||||
}
|
}
|
||||||
|
@ -154,24 +194,26 @@ func (m *messagesComponent) renderView(width int) {
|
||||||
m = m.updateSelected(content, part.Text)
|
m = m.updateSelected(content, part.Text)
|
||||||
blocks = append(blocks, content)
|
blocks = append(blocks, content)
|
||||||
}
|
}
|
||||||
|
// Only render the first text part
|
||||||
|
break userLoop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case opencode.MessageRoleAssistant:
|
case opencode.AssistantMessage:
|
||||||
hasTextPart := false
|
hasTextPart := false
|
||||||
for partIndex, p := range message.Parts {
|
for partIndex, p := range casted.Parts {
|
||||||
switch part := p.AsUnion().(type) {
|
switch part := p.AsUnion().(type) {
|
||||||
case opencode.TextPart:
|
case opencode.TextPart:
|
||||||
hasTextPart = true
|
hasTextPart = true
|
||||||
finished := message.Metadata.Time.Completed > 0
|
finished := casted.Time.Completed > 0
|
||||||
remainingParts := message.Parts[partIndex+1:]
|
remainingParts := casted.Parts[partIndex+1:]
|
||||||
toolCallParts := make([]opencode.ToolInvocationPart, 0)
|
toolCallParts := make([]opencode.ToolPart, 0)
|
||||||
|
|
||||||
// sometimes tool calls happen without an assistant message
|
// sometimes tool calls happen without an assistant message
|
||||||
// these should be included in this assistant message as well
|
// these should be included in this assistant message as well
|
||||||
if len(orphanedToolCalls) > 0 {
|
if len(orphanedToolCalls) > 0 {
|
||||||
toolCallParts = append(toolCallParts, orphanedToolCalls...)
|
toolCallParts = append(toolCallParts, orphanedToolCalls...)
|
||||||
orphanedToolCalls = make([]opencode.ToolInvocationPart, 0)
|
orphanedToolCalls = make([]opencode.ToolPart, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
remaining := true
|
remaining := true
|
||||||
|
@ -184,9 +226,9 @@ func (m *messagesComponent) renderView(width int) {
|
||||||
// we only want tool calls associated with the current text part.
|
// we only want tool calls associated with the current text part.
|
||||||
// if we hit another text part, we're done.
|
// if we hit another text part, we're done.
|
||||||
remaining = false
|
remaining = false
|
||||||
case opencode.ToolInvocationPart:
|
case opencode.ToolPart:
|
||||||
toolCallParts = append(toolCallParts, part)
|
toolCallParts = append(toolCallParts, part)
|
||||||
if part.ToolInvocation.State != "result" {
|
if part.State.Status != opencode.ToolPartStateStatusCompleted || part.State.Status != opencode.ToolPartStateStatusError {
|
||||||
// i don't think there's a case where a tool call isn't in result state
|
// i don't think there's a case where a tool call isn't in result state
|
||||||
// and the message time is 0, but just in case
|
// and the message time is 0, but just in case
|
||||||
finished = false
|
finished = false
|
||||||
|
@ -195,17 +237,18 @@ func (m *messagesComponent) renderView(width int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if finished {
|
if finished {
|
||||||
key := m.cache.GenerateKey(message.ID, p.Text, width, m.showToolDetails, m.selectedPart == m.partCount)
|
key := m.cache.GenerateKey(casted.ID, p.Text, width, m.showToolDetails, m.selectedPart == m.partCount)
|
||||||
content, cached = m.cache.Get(key)
|
content, cached = m.cache.Get(key)
|
||||||
if !cached {
|
if !cached {
|
||||||
content = renderText(
|
content = renderText(
|
||||||
m.app,
|
m.app,
|
||||||
message,
|
message,
|
||||||
p.Text,
|
p.Text,
|
||||||
message.Metadata.Assistant.ModelID,
|
casted.ModelID,
|
||||||
m.showToolDetails,
|
m.showToolDetails,
|
||||||
m.partCount == m.selectedPart,
|
m.partCount == m.selectedPart,
|
||||||
width,
|
width,
|
||||||
|
"",
|
||||||
toolCallParts...,
|
toolCallParts...,
|
||||||
)
|
)
|
||||||
m.cache.Set(key, content)
|
m.cache.Set(key, content)
|
||||||
|
@ -215,10 +258,11 @@ func (m *messagesComponent) renderView(width int) {
|
||||||
m.app,
|
m.app,
|
||||||
message,
|
message,
|
||||||
p.Text,
|
p.Text,
|
||||||
message.Metadata.Assistant.ModelID,
|
casted.ModelID,
|
||||||
m.showToolDetails,
|
m.showToolDetails,
|
||||||
m.partCount == m.selectedPart,
|
m.partCount == m.selectedPart,
|
||||||
width,
|
width,
|
||||||
|
"",
|
||||||
toolCallParts...,
|
toolCallParts...,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -226,7 +270,7 @@ func (m *messagesComponent) renderView(width int) {
|
||||||
m = m.updateSelected(content, p.Text)
|
m = m.updateSelected(content, p.Text)
|
||||||
blocks = append(blocks, content)
|
blocks = append(blocks, content)
|
||||||
}
|
}
|
||||||
case opencode.ToolInvocationPart:
|
case opencode.ToolPart:
|
||||||
if !m.showToolDetails {
|
if !m.showToolDetails {
|
||||||
if !hasTextPart {
|
if !hasTextPart {
|
||||||
orphanedToolCalls = append(orphanedToolCalls, part)
|
orphanedToolCalls = append(orphanedToolCalls, part)
|
||||||
|
@ -234,9 +278,9 @@ func (m *messagesComponent) renderView(width int) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if part.ToolInvocation.State == "result" {
|
if part.State.Status == opencode.ToolPartStateStatusCompleted || part.State.Status == opencode.ToolPartStateStatusError {
|
||||||
key := m.cache.GenerateKey(message.ID,
|
key := m.cache.GenerateKey(casted.ID,
|
||||||
part.ToolInvocation.ToolCallID,
|
part.ID,
|
||||||
m.showToolDetails,
|
m.showToolDetails,
|
||||||
width,
|
width,
|
||||||
m.partCount == m.selectedPart,
|
m.partCount == m.selectedPart,
|
||||||
|
@ -246,7 +290,6 @@ func (m *messagesComponent) renderView(width int) {
|
||||||
content = renderToolDetails(
|
content = renderToolDetails(
|
||||||
m.app,
|
m.app,
|
||||||
part,
|
part,
|
||||||
message.Metadata,
|
|
||||||
m.partCount == m.selectedPart,
|
m.partCount == m.selectedPart,
|
||||||
width,
|
width,
|
||||||
)
|
)
|
||||||
|
@ -257,7 +300,6 @@ func (m *messagesComponent) renderView(width int) {
|
||||||
content = renderToolDetails(
|
content = renderToolDetails(
|
||||||
m.app,
|
m.app,
|
||||||
part,
|
part,
|
||||||
message.Metadata,
|
|
||||||
m.partCount == m.selectedPart,
|
m.partCount == m.selectedPart,
|
||||||
width,
|
width,
|
||||||
)
|
)
|
||||||
|
@ -271,14 +313,16 @@ func (m *messagesComponent) renderView(width int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
error := ""
|
error := ""
|
||||||
switch err := message.Metadata.Error.AsUnion().(type) {
|
if assistant, ok := message.(opencode.AssistantMessage); ok {
|
||||||
case nil:
|
switch err := assistant.Error.AsUnion().(type) {
|
||||||
case opencode.MessageMetadataErrorMessageOutputLengthError:
|
case nil:
|
||||||
error = "Message output length exceeded"
|
case opencode.AssistantMessageErrorMessageOutputLengthError:
|
||||||
case opencode.ProviderAuthError:
|
error = "Message output length exceeded"
|
||||||
error = err.Data.Message
|
case opencode.ProviderAuthError:
|
||||||
case opencode.UnknownError:
|
error = err.Data.Message
|
||||||
error = err.Data.Message
|
case opencode.UnknownError:
|
||||||
|
error = err.Data.Message
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if error != "" {
|
if error != "" {
|
||||||
|
|
|
@ -64,7 +64,7 @@ type CompletionProvider interface {
|
||||||
type CompletionSelectedMsg struct {
|
type CompletionSelectedMsg struct {
|
||||||
SearchString string
|
SearchString string
|
||||||
CompletionValue string
|
CompletionValue string
|
||||||
IsCommand bool
|
ProviderID string
|
||||||
}
|
}
|
||||||
|
|
||||||
type CompletionDialogCompleteItemMsg struct {
|
type CompletionDialogCompleteItemMsg struct {
|
||||||
|
@ -121,9 +121,6 @@ func (c *completionDialogComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
|
||||||
var query string
|
var query string
|
||||||
query = c.pseudoSearchTextArea.Value()
|
query = c.pseudoSearchTextArea.Value()
|
||||||
if query != "" {
|
|
||||||
query = query[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
if query != c.query {
|
if query != c.query {
|
||||||
c.query = query
|
c.query = query
|
||||||
|
@ -183,8 +180,9 @@ func (c *completionDialogComponent) View() string {
|
||||||
|
|
||||||
for _, cmd := range completions {
|
for _, cmd := range completions {
|
||||||
title := cmd.DisplayValue()
|
title := cmd.DisplayValue()
|
||||||
if len(title) > maxWidth-4 {
|
width := lipgloss.Width(title)
|
||||||
maxWidth = len(title) + 4
|
if width > maxWidth-4 {
|
||||||
|
maxWidth = width + 4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,14 +211,11 @@ func (c *completionDialogComponent) IsEmpty() bool {
|
||||||
func (c *completionDialogComponent) complete(item CompletionItemI) tea.Cmd {
|
func (c *completionDialogComponent) complete(item CompletionItemI) tea.Cmd {
|
||||||
value := c.pseudoSearchTextArea.Value()
|
value := c.pseudoSearchTextArea.Value()
|
||||||
|
|
||||||
// Check if this is a command completion
|
|
||||||
isCommand := c.completionProvider.GetId() == "commands"
|
|
||||||
|
|
||||||
return tea.Batch(
|
return tea.Batch(
|
||||||
util.CmdHandler(CompletionSelectedMsg{
|
util.CmdHandler(CompletionSelectedMsg{
|
||||||
SearchString: value,
|
SearchString: value,
|
||||||
CompletionValue: item.GetValue(),
|
CompletionValue: item.GetValue(),
|
||||||
IsCommand: isCommand,
|
ProviderID: c.completionProvider.GetId(),
|
||||||
}),
|
}),
|
||||||
c.close(),
|
c.close(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -124,7 +124,7 @@ func (f *findDialogComponent) View() string {
|
||||||
f.list.SetMaxWidth(f.width - 4)
|
f.list.SetMaxWidth(f.width - 4)
|
||||||
inputView := f.textInput.View()
|
inputView := f.textInput.View()
|
||||||
inputView = styles.NewStyle().
|
inputView = styles.NewStyle().
|
||||||
Background(t.BackgroundPanel()).
|
Background(t.BackgroundElement()).
|
||||||
Height(1).
|
Height(1).
|
||||||
Width(f.width-4).
|
Width(f.width-4).
|
||||||
Padding(0, 0).
|
Padding(0, 0).
|
||||||
|
@ -171,7 +171,7 @@ func (f *findDialogComponent) Close() tea.Cmd {
|
||||||
|
|
||||||
func createTextInput(existing *textinput.Model) textinput.Model {
|
func createTextInput(existing *textinput.Model) textinput.Model {
|
||||||
t := theme.CurrentTheme()
|
t := theme.CurrentTheme()
|
||||||
bgColor := t.BackgroundPanel()
|
bgColor := t.BackgroundElement()
|
||||||
textColor := t.Text()
|
textColor := t.Text()
|
||||||
textMutedColor := t.TextMuted()
|
textMutedColor := t.TextMuted()
|
||||||
|
|
||||||
|
|
|
@ -56,24 +56,24 @@ func (m ModelItem) Render(selected bool, width int) string {
|
||||||
displayText := fmt.Sprintf("%s (%s)", m.ModelName, m.ProviderName)
|
displayText := fmt.Sprintf("%s (%s)", m.ModelName, m.ProviderName)
|
||||||
return styles.NewStyle().
|
return styles.NewStyle().
|
||||||
Background(t.Primary()).
|
Background(t.Primary()).
|
||||||
Foreground(t.BackgroundElement()).
|
Foreground(t.BackgroundPanel()).
|
||||||
Width(width).
|
Width(width).
|
||||||
PaddingLeft(1).
|
PaddingLeft(1).
|
||||||
Render(displayText)
|
Render(displayText)
|
||||||
} else {
|
} else {
|
||||||
modelStyle := styles.NewStyle().
|
modelStyle := styles.NewStyle().
|
||||||
Foreground(t.Text()).
|
Foreground(t.Text()).
|
||||||
Background(t.BackgroundElement())
|
Background(t.BackgroundPanel())
|
||||||
providerStyle := styles.NewStyle().
|
providerStyle := styles.NewStyle().
|
||||||
Foreground(t.TextMuted()).
|
Foreground(t.TextMuted()).
|
||||||
Background(t.BackgroundElement())
|
Background(t.BackgroundPanel())
|
||||||
|
|
||||||
modelPart := modelStyle.Render(m.ModelName)
|
modelPart := modelStyle.Render(m.ModelName)
|
||||||
providerPart := providerStyle.Render(fmt.Sprintf(" (%s)", m.ProviderName))
|
providerPart := providerStyle.Render(fmt.Sprintf(" (%s)", m.ProviderName))
|
||||||
|
|
||||||
combinedText := modelPart + providerPart
|
combinedText := modelPart + providerPart
|
||||||
return styles.NewStyle().
|
return styles.NewStyle().
|
||||||
Background(t.BackgroundElement()).
|
Background(t.BackgroundPanel()).
|
||||||
PaddingLeft(1).
|
PaddingLeft(1).
|
||||||
Render(combinedText)
|
Render(combinedText)
|
||||||
}
|
}
|
||||||
|
|
|
@ -158,7 +158,12 @@ func (c *listComponent[T]) View() string {
|
||||||
return strings.Join(listItems, "\n")
|
return strings.Join(listItems, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewListComponent[T ListItem](items []T, maxVisibleItems int, fallbackMsg string, useAlphaNumericKeys bool) List[T] {
|
func NewListComponent[T ListItem](
|
||||||
|
items []T,
|
||||||
|
maxVisibleItems int,
|
||||||
|
fallbackMsg string,
|
||||||
|
useAlphaNumericKeys bool,
|
||||||
|
) List[T] {
|
||||||
return &listComponent[T]{
|
return &listComponent[T]{
|
||||||
fallbackMsg: fallbackMsg,
|
fallbackMsg: fallbackMsg,
|
||||||
items: items,
|
items: items,
|
||||||
|
@ -194,7 +199,12 @@ func (s StringItem) Render(selected bool, width int) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStringList creates a new list component with string items
|
// NewStringList creates a new list component with string items
|
||||||
func NewStringList(items []string, maxVisibleItems int, fallbackMsg string, useAlphaNumericKeys bool) List[StringItem] {
|
func NewStringList(
|
||||||
|
items []string,
|
||||||
|
maxVisibleItems int,
|
||||||
|
fallbackMsg string,
|
||||||
|
useAlphaNumericKeys bool,
|
||||||
|
) List[StringItem] {
|
||||||
stringItems := make([]StringItem, len(items))
|
stringItems := make([]StringItem, len(items))
|
||||||
for i, item := range items {
|
for i, item := range items {
|
||||||
stringItems[i] = StringItem(item)
|
stringItems[i] = StringItem(item)
|
||||||
|
|
|
@ -90,7 +90,7 @@ func (m *Modal) Render(contentView string, background string) string {
|
||||||
|
|
||||||
innerWidth := outerWidth - 4
|
innerWidth := outerWidth - 4
|
||||||
|
|
||||||
baseStyle := styles.NewStyle().Foreground(t.TextMuted()).Background(t.BackgroundElement())
|
baseStyle := styles.NewStyle().Foreground(t.TextMuted()).Background(t.BackgroundPanel())
|
||||||
|
|
||||||
var finalContent string
|
var finalContent string
|
||||||
if m.title != "" {
|
if m.title != "" {
|
||||||
|
@ -140,6 +140,6 @@ func (m *Modal) Render(contentView string, background string) string {
|
||||||
modalView,
|
modalView,
|
||||||
background,
|
background,
|
||||||
layout.WithOverlayBorder(),
|
layout.WithOverlayBorder(),
|
||||||
layout.WithOverlayBorderColor(t.BorderActive()),
|
layout.WithOverlayBorderColor(t.Primary()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ func Generate(text string) (string, int, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create lipgloss style for QR code with theme colors
|
// Create lipgloss style for QR code with theme colors
|
||||||
qrStyle := styles.NewStyleWithColors(t.Text(), t.Background())
|
qrStyle := styles.NewStyle().Foreground(t.Text()).Background(t.Background())
|
||||||
|
|
||||||
var result strings.Builder
|
var result strings.Builder
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
|
|
||||||
tea "github.com/charmbracelet/bubbletea/v2"
|
tea "github.com/charmbracelet/bubbletea/v2"
|
||||||
"github.com/charmbracelet/lipgloss/v2"
|
"github.com/charmbracelet/lipgloss/v2"
|
||||||
|
"github.com/sst/opencode-sdk-go"
|
||||||
"github.com/sst/opencode/internal/app"
|
"github.com/sst/opencode/internal/app"
|
||||||
"github.com/sst/opencode/internal/styles"
|
"github.com/sst/opencode/internal/styles"
|
||||||
"github.com/sst/opencode/internal/theme"
|
"github.com/sst/opencode/internal/theme"
|
||||||
|
@ -101,18 +102,20 @@ func (m statusComponent) View() string {
|
||||||
contextWindow := m.app.Model.Limit.Context
|
contextWindow := m.app.Model.Limit.Context
|
||||||
|
|
||||||
for _, message := range m.app.Messages {
|
for _, message := range m.app.Messages {
|
||||||
cost += message.Metadata.Assistant.Cost
|
if assistant, ok := message.(opencode.AssistantMessage); ok {
|
||||||
usage := message.Metadata.Assistant.Tokens
|
cost += assistant.Cost
|
||||||
if usage.Output > 0 {
|
usage := assistant.Tokens
|
||||||
if message.Metadata.Assistant.Summary {
|
if usage.Output > 0 {
|
||||||
tokens = usage.Output
|
if assistant.Summary {
|
||||||
continue
|
tokens = usage.Output
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tokens = (usage.Input +
|
||||||
|
usage.Cache.Write +
|
||||||
|
usage.Cache.Read +
|
||||||
|
usage.Output +
|
||||||
|
usage.Reasoning)
|
||||||
}
|
}
|
||||||
tokens = (usage.Input +
|
|
||||||
usage.Cache.Write +
|
|
||||||
usage.Cache.Read +
|
|
||||||
usage.Output +
|
|
||||||
usage.Reasoning)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,41 +0,0 @@
|
||||||
package layout_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/sst/opencode/internal/layout"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ExampleRender_withGap() {
|
|
||||||
// Create a horizontal layout with 3px gap between items
|
|
||||||
result := layout.Render(
|
|
||||||
layout.FlexOptions{
|
|
||||||
Direction: layout.Row,
|
|
||||||
Width: 30,
|
|
||||||
Height: 1,
|
|
||||||
Gap: 3,
|
|
||||||
},
|
|
||||||
layout.FlexItem{View: "Item1"},
|
|
||||||
layout.FlexItem{View: "Item2"},
|
|
||||||
layout.FlexItem{View: "Item3"},
|
|
||||||
)
|
|
||||||
fmt.Println(result)
|
|
||||||
// Output: Item1 Item2 Item3
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleRender_withGapAndJustify() {
|
|
||||||
// Create a horizontal layout with gap and space-between justification
|
|
||||||
result := layout.Render(
|
|
||||||
layout.FlexOptions{
|
|
||||||
Direction: layout.Row,
|
|
||||||
Width: 30,
|
|
||||||
Height: 1,
|
|
||||||
Gap: 2,
|
|
||||||
Justify: layout.JustifySpaceBetween,
|
|
||||||
},
|
|
||||||
layout.FlexItem{View: "A"},
|
|
||||||
layout.FlexItem{View: "B"},
|
|
||||||
layout.FlexItem{View: "C"},
|
|
||||||
)
|
|
||||||
fmt.Println(result)
|
|
||||||
// Output: A B C
|
|
||||||
}
|
|
|
@ -1,90 +0,0 @@
|
||||||
package layout
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFlexGap(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
opts FlexOptions
|
|
||||||
items []FlexItem
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Row with gap",
|
|
||||||
opts: FlexOptions{
|
|
||||||
Direction: Row,
|
|
||||||
Width: 20,
|
|
||||||
Height: 1,
|
|
||||||
Gap: 2,
|
|
||||||
},
|
|
||||||
items: []FlexItem{
|
|
||||||
{View: "A"},
|
|
||||||
{View: "B"},
|
|
||||||
{View: "C"},
|
|
||||||
},
|
|
||||||
expected: "A B C",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Column with gap",
|
|
||||||
opts: FlexOptions{
|
|
||||||
Direction: Column,
|
|
||||||
Width: 1,
|
|
||||||
Height: 5,
|
|
||||||
Gap: 1,
|
|
||||||
Align: AlignStart,
|
|
||||||
},
|
|
||||||
items: []FlexItem{
|
|
||||||
{View: "A", FixedSize: 1},
|
|
||||||
{View: "B", FixedSize: 1},
|
|
||||||
{View: "C", FixedSize: 1},
|
|
||||||
},
|
|
||||||
expected: "A\n \nB\n \nC",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Row with gap and justify space between",
|
|
||||||
opts: FlexOptions{
|
|
||||||
Direction: Row,
|
|
||||||
Width: 15,
|
|
||||||
Height: 1,
|
|
||||||
Gap: 1,
|
|
||||||
Justify: JustifySpaceBetween,
|
|
||||||
},
|
|
||||||
items: []FlexItem{
|
|
||||||
{View: "A"},
|
|
||||||
{View: "B"},
|
|
||||||
{View: "C"},
|
|
||||||
},
|
|
||||||
expected: "A B C",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "No gap specified",
|
|
||||||
opts: FlexOptions{
|
|
||||||
Direction: Row,
|
|
||||||
Width: 10,
|
|
||||||
Height: 1,
|
|
||||||
},
|
|
||||||
items: []FlexItem{
|
|
||||||
{View: "A"},
|
|
||||||
{View: "B"},
|
|
||||||
{View: "C"},
|
|
||||||
},
|
|
||||||
expected: "ABC",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
result := Render(tt.opts, tt.items...)
|
|
||||||
// Trim any trailing spaces for comparison
|
|
||||||
result = strings.TrimRight(result, " ")
|
|
||||||
expected := strings.TrimRight(tt.expected, " ")
|
|
||||||
|
|
||||||
if result != expected {
|
|
||||||
t.Errorf("Render() = %q, want %q", result, expected)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -52,7 +52,9 @@ type appModel struct {
|
||||||
messages chat.MessagesComponent
|
messages chat.MessagesComponent
|
||||||
completions dialog.CompletionDialog
|
completions dialog.CompletionDialog
|
||||||
commandProvider dialog.CompletionProvider
|
commandProvider dialog.CompletionProvider
|
||||||
|
fileProvider dialog.CompletionProvider
|
||||||
showCompletionDialog bool
|
showCompletionDialog bool
|
||||||
|
fileCompletionActive bool
|
||||||
leaderBinding *key.Binding
|
leaderBinding *key.Binding
|
||||||
isLeaderSequence bool
|
isLeaderSequence bool
|
||||||
toastManager *toast.ToastManager
|
toastManager *toast.ToastManager
|
||||||
|
@ -180,11 +182,33 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
!a.showCompletionDialog &&
|
!a.showCompletionDialog &&
|
||||||
a.editor.Value() == "" {
|
a.editor.Value() == "" {
|
||||||
a.showCompletionDialog = true
|
a.showCompletionDialog = true
|
||||||
|
a.fileCompletionActive = false
|
||||||
|
|
||||||
updated, cmd := a.editor.Update(msg)
|
updated, cmd := a.editor.Update(msg)
|
||||||
a.editor = updated.(chat.EditorComponent)
|
a.editor = updated.(chat.EditorComponent)
|
||||||
cmds = append(cmds, cmd)
|
cmds = append(cmds, cmd)
|
||||||
|
|
||||||
|
// Set command provider for command completion
|
||||||
|
a.completions = dialog.NewCompletionDialogComponent(a.commandProvider)
|
||||||
|
updated, cmd = a.completions.Update(msg)
|
||||||
|
a.completions = updated.(dialog.CompletionDialog)
|
||||||
|
cmds = append(cmds, cmd)
|
||||||
|
|
||||||
|
return a, tea.Sequence(cmds...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle file completions trigger
|
||||||
|
if keyString == "@" &&
|
||||||
|
!a.showCompletionDialog {
|
||||||
|
a.showCompletionDialog = true
|
||||||
|
a.fileCompletionActive = true
|
||||||
|
|
||||||
|
updated, cmd := a.editor.Update(msg)
|
||||||
|
a.editor = updated.(chat.EditorComponent)
|
||||||
|
cmds = append(cmds, cmd)
|
||||||
|
|
||||||
|
// Set file provider for file completion
|
||||||
|
a.completions = dialog.NewCompletionDialogComponent(a.fileProvider)
|
||||||
updated, cmd = a.completions.Update(msg)
|
updated, cmd = a.completions.Update(msg)
|
||||||
a.completions = updated.(dialog.CompletionDialog)
|
a.completions = updated.(dialog.CompletionDialog)
|
||||||
cmds = append(cmds, cmd)
|
cmds = append(cmds, cmd)
|
||||||
|
@ -194,7 +218,7 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
|
||||||
if a.showCompletionDialog {
|
if a.showCompletionDialog {
|
||||||
switch keyString {
|
switch keyString {
|
||||||
case "tab", "enter", "esc", "ctrl+c":
|
case "tab", "enter", "esc", "ctrl+c", "up", "down":
|
||||||
updated, cmd := a.completions.Update(msg)
|
updated, cmd := a.completions.Update(msg)
|
||||||
a.completions = updated.(dialog.CompletionDialog)
|
a.completions = updated.(dialog.CompletionDialog)
|
||||||
cmds = append(cmds, cmd)
|
cmds = append(cmds, cmd)
|
||||||
|
@ -326,10 +350,11 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
return a, toast.NewErrorToast(msg.Error())
|
return a, toast.NewErrorToast(msg.Error())
|
||||||
case app.SendMsg:
|
case app.SendMsg:
|
||||||
a.showCompletionDialog = false
|
a.showCompletionDialog = false
|
||||||
cmd := a.app.SendChatMessage(context.Background(), msg.Text, msg.Attachments)
|
a.app, cmd = a.app.SendChatMessage(context.Background(), msg.Text, msg.Attachments)
|
||||||
cmds = append(cmds, cmd)
|
cmds = append(cmds, cmd)
|
||||||
case dialog.CompletionDialogCloseMsg:
|
case dialog.CompletionDialogCloseMsg:
|
||||||
a.showCompletionDialog = false
|
a.showCompletionDialog = false
|
||||||
|
a.fileCompletionActive = false
|
||||||
case opencode.EventListResponseEventInstallationUpdated:
|
case opencode.EventListResponseEventInstallationUpdated:
|
||||||
return a, toast.NewSuccessToast(
|
return a, toast.NewSuccessToast(
|
||||||
"opencode updated to "+msg.Properties.Version+", restart to apply.",
|
"opencode updated to "+msg.Properties.Version+", restart to apply.",
|
||||||
|
@ -338,7 +363,7 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
case opencode.EventListResponseEventSessionDeleted:
|
case opencode.EventListResponseEventSessionDeleted:
|
||||||
if a.app.Session != nil && msg.Properties.Info.ID == a.app.Session.ID {
|
if a.app.Session != nil && msg.Properties.Info.ID == a.app.Session.ID {
|
||||||
a.app.Session = &opencode.Session{}
|
a.app.Session = &opencode.Session{}
|
||||||
a.app.Messages = []opencode.Message{}
|
a.app.Messages = []opencode.MessageUnion{}
|
||||||
}
|
}
|
||||||
return a, toast.NewSuccessToast("Session deleted successfully")
|
return a, toast.NewSuccessToast("Session deleted successfully")
|
||||||
case opencode.EventListResponseEventSessionUpdated:
|
case opencode.EventListResponseEventSessionUpdated:
|
||||||
|
@ -346,7 +371,7 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
a.app.Session = &msg.Properties.Info
|
a.app.Session = &msg.Properties.Info
|
||||||
}
|
}
|
||||||
case opencode.EventListResponseEventMessageUpdated:
|
case opencode.EventListResponseEventMessageUpdated:
|
||||||
if msg.Properties.Info.Metadata.SessionID == a.app.Session.ID {
|
if msg.Properties.Info.SessionID == a.app.Session.ID {
|
||||||
exists := false
|
exists := false
|
||||||
optimisticReplaced := false
|
optimisticReplaced := false
|
||||||
|
|
||||||
|
@ -354,12 +379,15 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
if msg.Properties.Info.Role == opencode.MessageRoleUser {
|
if msg.Properties.Info.Role == opencode.MessageRoleUser {
|
||||||
// Look for optimistic messages to replace
|
// Look for optimistic messages to replace
|
||||||
for i, m := range a.app.Messages {
|
for i, m := range a.app.Messages {
|
||||||
if strings.HasPrefix(m.ID, "optimistic-") && m.Role == opencode.MessageRoleUser {
|
switch m := m.(type) {
|
||||||
// Replace the optimistic message with the real one
|
case opencode.UserMessage:
|
||||||
a.app.Messages[i] = msg.Properties.Info
|
if strings.HasPrefix(m.ID, "optimistic-") && m.Role == opencode.UserMessageRoleUser {
|
||||||
exists = true
|
// Replace the optimistic message with the real one
|
||||||
optimisticReplaced = true
|
a.app.Messages[i] = msg.Properties.Info.AsUnion()
|
||||||
break
|
exists = true
|
||||||
|
optimisticReplaced = true
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -367,8 +395,15 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
// If not replacing optimistic, check for existing message with same ID
|
// If not replacing optimistic, check for existing message with same ID
|
||||||
if !optimisticReplaced {
|
if !optimisticReplaced {
|
||||||
for i, m := range a.app.Messages {
|
for i, m := range a.app.Messages {
|
||||||
if m.ID == msg.Properties.Info.ID {
|
var id string
|
||||||
a.app.Messages[i] = msg.Properties.Info
|
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
|
exists = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -376,7 +411,7 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
a.app.Messages = append(a.app.Messages, msg.Properties.Info)
|
a.app.Messages = append(a.app.Messages, msg.Properties.Info.AsUnion())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case opencode.EventListResponseEventSessionError:
|
case opencode.EventListResponseEventSessionError:
|
||||||
|
@ -437,7 +472,10 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
return a, toast.NewErrorToast("Failed to open session")
|
return a, toast.NewErrorToast("Failed to open session")
|
||||||
}
|
}
|
||||||
a.app.Session = msg
|
a.app.Session = msg
|
||||||
a.app.Messages = messages
|
a.app.Messages = make([]opencode.MessageUnion, 0)
|
||||||
|
for _, message := range messages {
|
||||||
|
a.app.Messages = append(a.app.Messages, message.AsUnion())
|
||||||
|
}
|
||||||
return a, util.CmdHandler(app.SessionLoadedMsg{})
|
return a, util.CmdHandler(app.SessionLoadedMsg{})
|
||||||
case app.ModelSelectedMsg:
|
case app.ModelSelectedMsg:
|
||||||
a.app.Provider = &msg.Provider
|
a.app.Provider = &msg.Provider
|
||||||
|
@ -778,11 +816,8 @@ func (a appModel) executeCommand(command commands.Command) (tea.Model, tea.Cmd)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
os.Remove(tmpfile.Name())
|
os.Remove(tmpfile.Name())
|
||||||
// attachments := m.attachments
|
|
||||||
// m.attachments = nil
|
|
||||||
return app.SendMsg{
|
return app.SendMsg{
|
||||||
Text: string(content),
|
Text: string(content),
|
||||||
Attachments: []app.Attachment{}, // attachments,
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
cmds = append(cmds, cmd)
|
cmds = append(cmds, cmd)
|
||||||
|
@ -791,7 +826,7 @@ func (a appModel) executeCommand(command commands.Command) (tea.Model, tea.Cmd)
|
||||||
return a, nil
|
return a, nil
|
||||||
}
|
}
|
||||||
a.app.Session = &opencode.Session{}
|
a.app.Session = &opencode.Session{}
|
||||||
a.app.Messages = []opencode.Message{}
|
a.app.Messages = []opencode.MessageUnion{}
|
||||||
cmds = append(cmds, util.CmdHandler(app.SessionClearedMsg{}))
|
cmds = append(cmds, util.CmdHandler(app.SessionClearedMsg{}))
|
||||||
case commands.SessionListCommand:
|
case commands.SessionListCommand:
|
||||||
sessionDialog := dialog.NewSessionDialog(a.app)
|
sessionDialog := dialog.NewSessionDialog(a.app)
|
||||||
|
@ -954,6 +989,7 @@ func (a appModel) executeCommand(command commands.Command) (tea.Model, tea.Cmd)
|
||||||
|
|
||||||
func NewModel(app *app.App) tea.Model {
|
func NewModel(app *app.App) tea.Model {
|
||||||
commandProvider := completions.NewCommandCompletionProvider(app)
|
commandProvider := completions.NewCommandCompletionProvider(app)
|
||||||
|
fileProvider := completions.NewFileAndFolderContextGroup(app)
|
||||||
|
|
||||||
messages := chat.NewMessagesComponent(app)
|
messages := chat.NewMessagesComponent(app)
|
||||||
editor := chat.NewEditorComponent(app)
|
editor := chat.NewEditorComponent(app)
|
||||||
|
@ -972,9 +1008,11 @@ func NewModel(app *app.App) tea.Model {
|
||||||
messages: messages,
|
messages: messages,
|
||||||
completions: completions,
|
completions: completions,
|
||||||
commandProvider: commandProvider,
|
commandProvider: commandProvider,
|
||||||
|
fileProvider: fileProvider,
|
||||||
leaderBinding: leaderBinding,
|
leaderBinding: leaderBinding,
|
||||||
isLeaderSequence: false,
|
isLeaderSequence: false,
|
||||||
showCompletionDialog: false,
|
showCompletionDialog: false,
|
||||||
|
fileCompletionActive: false,
|
||||||
toastManager: toast.NewToastManager(),
|
toastManager: toast.NewToastManager(),
|
||||||
interruptKeyState: InterruptKeyIdle,
|
interruptKeyState: InterruptKeyIdle,
|
||||||
fileViewer: fileviewer.New(app),
|
fileViewer: fileviewer.New(app),
|
||||||
|
|
|
@ -83,7 +83,7 @@ func Extension(path string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToMarkdown(content string, width int, backgroundColor compat.AdaptiveColor) string {
|
func ToMarkdown(content string, width int, backgroundColor compat.AdaptiveColor) string {
|
||||||
r := styles.GetMarkdownRenderer(width-7, backgroundColor)
|
r := styles.GetMarkdownRenderer(width-6, backgroundColor)
|
||||||
content = strings.ReplaceAll(content, RootPath+"/", "")
|
content = strings.ReplaceAll(content, RootPath+"/", "")
|
||||||
rendered, _ := r.Render(content)
|
rendered, _ := r.Render(content)
|
||||||
lines := strings.Split(rendered, "\n")
|
lines := strings.Split(rendered, "\n")
|
||||||
|
|
14
packages/tui/sdk/.github/workflows/ci.yml
vendored
14
packages/tui/sdk/.github/workflows/ci.yml
vendored
|
@ -2,15 +2,15 @@ name: CI
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches-ignore:
|
branches-ignore:
|
||||||
- 'generated'
|
- "generated"
|
||||||
- 'codegen/**'
|
- "codegen/**"
|
||||||
- 'integrated/**'
|
- "integrated/**"
|
||||||
- 'stl-preview-head/**'
|
- "stl-preview-head/**"
|
||||||
- 'stl-preview-base/**'
|
- "stl-preview-base/**"
|
||||||
pull_request:
|
pull_request:
|
||||||
branches-ignore:
|
branches-ignore:
|
||||||
- 'stl-preview-head/**'
|
- "stl-preview-head/**"
|
||||||
- 'stl-preview-base/**'
|
- "stl-preview-base/**"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
{
|
{
|
||||||
".": "0.1.0-alpha.8"
|
".": "0.1.0-alpha.8"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
configured_endpoints: 20
|
configured_endpoints: 20
|
||||||
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-945f9da9e9a4c4008834deef63e4346c0076e020eed3d3c98c249095033c1ac5.yml
|
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-15eeb028f79b9a065b4e54a6ea6a58631e9bd5004f97820f0c79d18e3f8bac84.yml
|
||||||
openapi_spec_hash: 522a44f6cb0677435fe2ac7693848ad7
|
openapi_spec_hash: 38c8bacb6c8e4c46852a3e81e3fb9fda
|
||||||
config_hash: 6c8822d278ba83456e5eed6d774ca230
|
config_hash: e03e9d1aad76081fa1163086e89f201b
|
||||||
|
|
|
@ -6,7 +6,7 @@ Full Changelog: [v0.1.0-alpha.7...v0.1.0-alpha.8](https://github.com/sst/opencod
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
* **api:** update via SDK Studio ([651e937](https://github.com/sst/opencode-sdk-go/commit/651e937c334e1caba3b968e6cac865c219879519))
|
- **api:** update via SDK Studio ([651e937](https://github.com/sst/opencode-sdk-go/commit/651e937c334e1caba3b968e6cac865c219879519))
|
||||||
|
|
||||||
## 0.1.0-alpha.7 (2025-06-30)
|
## 0.1.0-alpha.7 (2025-06-30)
|
||||||
|
|
||||||
|
@ -14,13 +14,12 @@ Full Changelog: [v0.1.0-alpha.6...v0.1.0-alpha.7](https://github.com/sst/opencod
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
* **api:** update via SDK Studio ([13550a5](https://github.com/sst/opencode-sdk-go/commit/13550a5c65d77325e945ed99fe0799cd1107b775))
|
- **api:** update via SDK Studio ([13550a5](https://github.com/sst/opencode-sdk-go/commit/13550a5c65d77325e945ed99fe0799cd1107b775))
|
||||||
* **api:** update via SDK Studio ([7b73730](https://github.com/sst/opencode-sdk-go/commit/7b73730c7fa62ba966dda3541c3e97b49be8d2bf))
|
- **api:** update via SDK Studio ([7b73730](https://github.com/sst/opencode-sdk-go/commit/7b73730c7fa62ba966dda3541c3e97b49be8d2bf))
|
||||||
|
|
||||||
|
|
||||||
### Chores
|
### Chores
|
||||||
|
|
||||||
* **ci:** only run for pushes and fork pull requests ([bea59b8](https://github.com/sst/opencode-sdk-go/commit/bea59b886800ef555f89c47a9256d6392ed2e53d))
|
- **ci:** only run for pushes and fork pull requests ([bea59b8](https://github.com/sst/opencode-sdk-go/commit/bea59b886800ef555f89c47a9256d6392ed2e53d))
|
||||||
|
|
||||||
## 0.1.0-alpha.6 (2025-06-28)
|
## 0.1.0-alpha.6 (2025-06-28)
|
||||||
|
|
||||||
|
@ -28,7 +27,7 @@ Full Changelog: [v0.1.0-alpha.5...v0.1.0-alpha.6](https://github.com/sst/opencod
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* don't try to deserialize as json when ResponseBodyInto is []byte ([5988d04](https://github.com/sst/opencode-sdk-go/commit/5988d04839cb78b6613057280b91b72a60fef33d))
|
- don't try to deserialize as json when ResponseBodyInto is []byte ([5988d04](https://github.com/sst/opencode-sdk-go/commit/5988d04839cb78b6613057280b91b72a60fef33d))
|
||||||
|
|
||||||
## 0.1.0-alpha.5 (2025-06-27)
|
## 0.1.0-alpha.5 (2025-06-27)
|
||||||
|
|
||||||
|
@ -36,7 +35,7 @@ Full Changelog: [v0.1.0-alpha.4...v0.1.0-alpha.5](https://github.com/sst/opencod
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
* **api:** update via SDK Studio ([9e39a59](https://github.com/sst/opencode-sdk-go/commit/9e39a59b3d5d1bd5e64633732521fb28362cc70e))
|
- **api:** update via SDK Studio ([9e39a59](https://github.com/sst/opencode-sdk-go/commit/9e39a59b3d5d1bd5e64633732521fb28362cc70e))
|
||||||
|
|
||||||
## 0.1.0-alpha.4 (2025-06-27)
|
## 0.1.0-alpha.4 (2025-06-27)
|
||||||
|
|
||||||
|
@ -44,7 +43,7 @@ Full Changelog: [v0.1.0-alpha.3...v0.1.0-alpha.4](https://github.com/sst/opencod
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
* **api:** update via SDK Studio ([9609d1b](https://github.com/sst/opencode-sdk-go/commit/9609d1b1db7806d00cb846c9914cb4935cdedf52))
|
- **api:** update via SDK Studio ([9609d1b](https://github.com/sst/opencode-sdk-go/commit/9609d1b1db7806d00cb846c9914cb4935cdedf52))
|
||||||
|
|
||||||
## 0.1.0-alpha.3 (2025-06-27)
|
## 0.1.0-alpha.3 (2025-06-27)
|
||||||
|
|
||||||
|
@ -52,7 +51,7 @@ Full Changelog: [v0.1.0-alpha.2...v0.1.0-alpha.3](https://github.com/sst/opencod
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
* **api:** update via SDK Studio ([57f3230](https://github.com/sst/opencode-sdk-go/commit/57f32309023cc1f0f20c20d02a3907e390a71f61))
|
- **api:** update via SDK Studio ([57f3230](https://github.com/sst/opencode-sdk-go/commit/57f32309023cc1f0f20c20d02a3907e390a71f61))
|
||||||
|
|
||||||
## 0.1.0-alpha.2 (2025-06-27)
|
## 0.1.0-alpha.2 (2025-06-27)
|
||||||
|
|
||||||
|
@ -60,7 +59,7 @@ Full Changelog: [v0.1.0-alpha.1...v0.1.0-alpha.2](https://github.com/sst/opencod
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
* **api:** update via SDK Studio ([a766f1c](https://github.com/sst/opencode-sdk-go/commit/a766f1c54f02bbc1380151b0e22d97cc2c5892e6))
|
- **api:** update via SDK Studio ([a766f1c](https://github.com/sst/opencode-sdk-go/commit/a766f1c54f02bbc1380151b0e22d97cc2c5892e6))
|
||||||
|
|
||||||
## 0.1.0-alpha.1 (2025-06-27)
|
## 0.1.0-alpha.1 (2025-06-27)
|
||||||
|
|
||||||
|
@ -68,6 +67,6 @@ Full Changelog: [v0.0.1-alpha.0...v0.1.0-alpha.1](https://github.com/sst/opencod
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
* **api:** update via SDK Studio ([27b7376](https://github.com/sst/opencode-sdk-go/commit/27b7376310466ee17a63f2104f546b53a2b8361a))
|
- **api:** update via SDK Studio ([27b7376](https://github.com/sst/opencode-sdk-go/commit/27b7376310466ee17a63f2104f546b53a2b8361a))
|
||||||
* **api:** update via SDK Studio ([0a73e04](https://github.com/sst/opencode-sdk-go/commit/0a73e04c23c90b2061611edaa8fd6282dc0ce397))
|
- **api:** update via SDK Studio ([0a73e04](https://github.com/sst/opencode-sdk-go/commit/0a73e04c23c90b2061611edaa8fd6282dc0ce397))
|
||||||
* **api:** update via SDK Studio ([9b7883a](https://github.com/sst/opencode-sdk-go/commit/9b7883a144eeac526d9d04538e0876a9d18bb844))
|
- **api:** update via SDK Studio ([9b7883a](https://github.com/sst/opencode-sdk-go/commit/9b7883a144eeac526d9d04538e0876a9d18bb844))
|
||||||
|
|
|
@ -71,30 +71,25 @@ Methods:
|
||||||
Params Types:
|
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#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#MessagePartUnionParam">MessagePartUnionParam</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#ReasoningPartParam">ReasoningPartParam</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#SourceURLPartParam">SourceURLPartParam</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#StepStartPartParam">StepStartPartParam</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#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#ToolCallParam">ToolCallParam</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>
|
||||||
- <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#ToolInvocationPartParam">ToolInvocationPartParam</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#ToolPartialCallParam">ToolPartialCallParam</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#ToolResultParam">ToolResultParam</a>
|
|
||||||
|
|
||||||
Response Types:
|
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#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#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#MessagePart">MessagePart</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#ReasoningPart">ReasoningPart</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#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#SourceURLPart">SourceURLPart</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#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#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#ToolCall">ToolCall</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>
|
||||||
- <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#ToolInvocationPart">ToolInvocationPart</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#ToolStateCompleted">ToolStateCompleted</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#ToolPartialCall">ToolPartialCall</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#ToolStateError">ToolStateError</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#ToolResult">ToolResult</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#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>
|
||||||
|
|
||||||
Methods:
|
Methods:
|
||||||
|
|
||||||
|
@ -102,7 +97,7 @@ Methods:
|
||||||
- <code title="get /session">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</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="get /session">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</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="delete /session/{id}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Delete">Delete</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="delete /session/{id}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Delete">Delete</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}/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}/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#Message">Message</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="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#Message">Message</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}/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>
|
||||||
|
|
|
@ -51,8 +51,8 @@ type EventListResponse struct {
|
||||||
// [EventListResponseEventLspClientDiagnosticsProperties],
|
// [EventListResponseEventLspClientDiagnosticsProperties],
|
||||||
// [EventListResponseEventPermissionUpdatedProperties],
|
// [EventListResponseEventPermissionUpdatedProperties],
|
||||||
// [EventListResponseEventFileEditedProperties],
|
// [EventListResponseEventFileEditedProperties],
|
||||||
// [EventListResponseEventStorageWriteProperties],
|
|
||||||
// [EventListResponseEventInstallationUpdatedProperties],
|
// [EventListResponseEventInstallationUpdatedProperties],
|
||||||
|
// [EventListResponseEventStorageWriteProperties],
|
||||||
// [EventListResponseEventMessageUpdatedProperties],
|
// [EventListResponseEventMessageUpdatedProperties],
|
||||||
// [EventListResponseEventMessageRemovedProperties],
|
// [EventListResponseEventMessageRemovedProperties],
|
||||||
// [EventListResponseEventMessagePartUpdatedProperties],
|
// [EventListResponseEventMessagePartUpdatedProperties],
|
||||||
|
@ -95,9 +95,9 @@ func (r *EventListResponse) UnmarshalJSON(data []byte) (err error) {
|
||||||
// Possible runtime types of the union are
|
// Possible runtime types of the union are
|
||||||
// [EventListResponseEventLspClientDiagnostics],
|
// [EventListResponseEventLspClientDiagnostics],
|
||||||
// [EventListResponseEventPermissionUpdated], [EventListResponseEventFileEdited],
|
// [EventListResponseEventPermissionUpdated], [EventListResponseEventFileEdited],
|
||||||
// [EventListResponseEventStorageWrite],
|
|
||||||
// [EventListResponseEventInstallationUpdated],
|
// [EventListResponseEventInstallationUpdated],
|
||||||
// [EventListResponseEventMessageUpdated], [EventListResponseEventMessageRemoved],
|
// [EventListResponseEventStorageWrite], [EventListResponseEventMessageUpdated],
|
||||||
|
// [EventListResponseEventMessageRemoved],
|
||||||
// [EventListResponseEventMessagePartUpdated],
|
// [EventListResponseEventMessagePartUpdated],
|
||||||
// [EventListResponseEventSessionUpdated], [EventListResponseEventSessionDeleted],
|
// [EventListResponseEventSessionUpdated], [EventListResponseEventSessionDeleted],
|
||||||
// [EventListResponseEventSessionIdle], [EventListResponseEventSessionError],
|
// [EventListResponseEventSessionIdle], [EventListResponseEventSessionError],
|
||||||
|
@ -108,9 +108,9 @@ func (r EventListResponse) AsUnion() EventListResponseUnion {
|
||||||
|
|
||||||
// Union satisfied by [EventListResponseEventLspClientDiagnostics],
|
// Union satisfied by [EventListResponseEventLspClientDiagnostics],
|
||||||
// [EventListResponseEventPermissionUpdated], [EventListResponseEventFileEdited],
|
// [EventListResponseEventPermissionUpdated], [EventListResponseEventFileEdited],
|
||||||
// [EventListResponseEventStorageWrite],
|
|
||||||
// [EventListResponseEventInstallationUpdated],
|
// [EventListResponseEventInstallationUpdated],
|
||||||
// [EventListResponseEventMessageUpdated], [EventListResponseEventMessageRemoved],
|
// [EventListResponseEventStorageWrite], [EventListResponseEventMessageUpdated],
|
||||||
|
// [EventListResponseEventMessageRemoved],
|
||||||
// [EventListResponseEventMessagePartUpdated],
|
// [EventListResponseEventMessagePartUpdated],
|
||||||
// [EventListResponseEventSessionUpdated], [EventListResponseEventSessionDeleted],
|
// [EventListResponseEventSessionUpdated], [EventListResponseEventSessionDeleted],
|
||||||
// [EventListResponseEventSessionIdle], [EventListResponseEventSessionError] or
|
// [EventListResponseEventSessionIdle], [EventListResponseEventSessionError] or
|
||||||
|
@ -140,13 +140,13 @@ func init() {
|
||||||
},
|
},
|
||||||
apijson.UnionVariant{
|
apijson.UnionVariant{
|
||||||
TypeFilter: gjson.JSON,
|
TypeFilter: gjson.JSON,
|
||||||
Type: reflect.TypeOf(EventListResponseEventStorageWrite{}),
|
Type: reflect.TypeOf(EventListResponseEventInstallationUpdated{}),
|
||||||
DiscriminatorValue: "storage.write",
|
DiscriminatorValue: "installation.updated",
|
||||||
},
|
},
|
||||||
apijson.UnionVariant{
|
apijson.UnionVariant{
|
||||||
TypeFilter: gjson.JSON,
|
TypeFilter: gjson.JSON,
|
||||||
Type: reflect.TypeOf(EventListResponseEventInstallationUpdated{}),
|
Type: reflect.TypeOf(EventListResponseEventStorageWrite{}),
|
||||||
DiscriminatorValue: "installation.updated",
|
DiscriminatorValue: "storage.write",
|
||||||
},
|
},
|
||||||
apijson.UnionVariant{
|
apijson.UnionVariant{
|
||||||
TypeFilter: gjson.JSON,
|
TypeFilter: gjson.JSON,
|
||||||
|
@ -402,6 +402,66 @@ func (r EventListResponseEventFileEditedType) IsKnown() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EventListResponseEventInstallationUpdated struct {
|
||||||
|
Properties EventListResponseEventInstallationUpdatedProperties `json:"properties,required"`
|
||||||
|
Type EventListResponseEventInstallationUpdatedType `json:"type,required"`
|
||||||
|
JSON eventListResponseEventInstallationUpdatedJSON `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// eventListResponseEventInstallationUpdatedJSON contains the JSON metadata for the
|
||||||
|
// struct [EventListResponseEventInstallationUpdated]
|
||||||
|
type eventListResponseEventInstallationUpdatedJSON struct {
|
||||||
|
Properties apijson.Field
|
||||||
|
Type apijson.Field
|
||||||
|
raw string
|
||||||
|
ExtraFields map[string]apijson.Field
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *EventListResponseEventInstallationUpdated) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
return apijson.UnmarshalRoot(data, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r eventListResponseEventInstallationUpdatedJSON) RawJSON() string {
|
||||||
|
return r.raw
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r EventListResponseEventInstallationUpdated) implementsEventListResponse() {}
|
||||||
|
|
||||||
|
type EventListResponseEventInstallationUpdatedProperties struct {
|
||||||
|
Version string `json:"version,required"`
|
||||||
|
JSON eventListResponseEventInstallationUpdatedPropertiesJSON `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// eventListResponseEventInstallationUpdatedPropertiesJSON contains the JSON
|
||||||
|
// metadata for the struct [EventListResponseEventInstallationUpdatedProperties]
|
||||||
|
type eventListResponseEventInstallationUpdatedPropertiesJSON struct {
|
||||||
|
Version apijson.Field
|
||||||
|
raw string
|
||||||
|
ExtraFields map[string]apijson.Field
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *EventListResponseEventInstallationUpdatedProperties) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
return apijson.UnmarshalRoot(data, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r eventListResponseEventInstallationUpdatedPropertiesJSON) RawJSON() string {
|
||||||
|
return r.raw
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventListResponseEventInstallationUpdatedType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
EventListResponseEventInstallationUpdatedTypeInstallationUpdated EventListResponseEventInstallationUpdatedType = "installation.updated"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r EventListResponseEventInstallationUpdatedType) IsKnown() bool {
|
||||||
|
switch r {
|
||||||
|
case EventListResponseEventInstallationUpdatedTypeInstallationUpdated:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type EventListResponseEventStorageWrite struct {
|
type EventListResponseEventStorageWrite struct {
|
||||||
Properties EventListResponseEventStorageWriteProperties `json:"properties,required"`
|
Properties EventListResponseEventStorageWriteProperties `json:"properties,required"`
|
||||||
Type EventListResponseEventStorageWriteType `json:"type,required"`
|
Type EventListResponseEventStorageWriteType `json:"type,required"`
|
||||||
|
@ -464,66 +524,6 @@ func (r EventListResponseEventStorageWriteType) IsKnown() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
type EventListResponseEventInstallationUpdated struct {
|
|
||||||
Properties EventListResponseEventInstallationUpdatedProperties `json:"properties,required"`
|
|
||||||
Type EventListResponseEventInstallationUpdatedType `json:"type,required"`
|
|
||||||
JSON eventListResponseEventInstallationUpdatedJSON `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// eventListResponseEventInstallationUpdatedJSON contains the JSON metadata for the
|
|
||||||
// struct [EventListResponseEventInstallationUpdated]
|
|
||||||
type eventListResponseEventInstallationUpdatedJSON struct {
|
|
||||||
Properties apijson.Field
|
|
||||||
Type apijson.Field
|
|
||||||
raw string
|
|
||||||
ExtraFields map[string]apijson.Field
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *EventListResponseEventInstallationUpdated) UnmarshalJSON(data []byte) (err error) {
|
|
||||||
return apijson.UnmarshalRoot(data, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r eventListResponseEventInstallationUpdatedJSON) RawJSON() string {
|
|
||||||
return r.raw
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r EventListResponseEventInstallationUpdated) implementsEventListResponse() {}
|
|
||||||
|
|
||||||
type EventListResponseEventInstallationUpdatedProperties struct {
|
|
||||||
Version string `json:"version,required"`
|
|
||||||
JSON eventListResponseEventInstallationUpdatedPropertiesJSON `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// eventListResponseEventInstallationUpdatedPropertiesJSON contains the JSON
|
|
||||||
// metadata for the struct [EventListResponseEventInstallationUpdatedProperties]
|
|
||||||
type eventListResponseEventInstallationUpdatedPropertiesJSON struct {
|
|
||||||
Version apijson.Field
|
|
||||||
raw string
|
|
||||||
ExtraFields map[string]apijson.Field
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *EventListResponseEventInstallationUpdatedProperties) UnmarshalJSON(data []byte) (err error) {
|
|
||||||
return apijson.UnmarshalRoot(data, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r eventListResponseEventInstallationUpdatedPropertiesJSON) RawJSON() string {
|
|
||||||
return r.raw
|
|
||||||
}
|
|
||||||
|
|
||||||
type EventListResponseEventInstallationUpdatedType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
EventListResponseEventInstallationUpdatedTypeInstallationUpdated EventListResponseEventInstallationUpdatedType = "installation.updated"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (r EventListResponseEventInstallationUpdatedType) IsKnown() bool {
|
|
||||||
switch r {
|
|
||||||
case EventListResponseEventInstallationUpdatedTypeInstallationUpdated:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
type EventListResponseEventMessageUpdated struct {
|
type EventListResponseEventMessageUpdated struct {
|
||||||
Properties EventListResponseEventMessageUpdatedProperties `json:"properties,required"`
|
Properties EventListResponseEventMessageUpdatedProperties `json:"properties,required"`
|
||||||
Type EventListResponseEventMessageUpdatedType `json:"type,required"`
|
Type EventListResponseEventMessageUpdatedType `json:"type,required"`
|
||||||
|
@ -673,7 +673,7 @@ func (r EventListResponseEventMessagePartUpdated) implementsEventListResponse()
|
||||||
|
|
||||||
type EventListResponseEventMessagePartUpdatedProperties struct {
|
type EventListResponseEventMessagePartUpdatedProperties struct {
|
||||||
MessageID string `json:"messageID,required"`
|
MessageID string `json:"messageID,required"`
|
||||||
Part MessagePart `json:"part,required"`
|
Part AssistantMessagePart `json:"part,required"`
|
||||||
SessionID string `json:"sessionID,required"`
|
SessionID string `json:"sessionID,required"`
|
||||||
JSON eventListResponseEventMessagePartUpdatedPropertiesJSON `json:"-"`
|
JSON eventListResponseEventMessagePartUpdatedPropertiesJSON `json:"-"`
|
||||||
}
|
}
|
||||||
|
@ -1159,8 +1159,8 @@ const (
|
||||||
EventListResponseTypeLspClientDiagnostics EventListResponseType = "lsp.client.diagnostics"
|
EventListResponseTypeLspClientDiagnostics EventListResponseType = "lsp.client.diagnostics"
|
||||||
EventListResponseTypePermissionUpdated EventListResponseType = "permission.updated"
|
EventListResponseTypePermissionUpdated EventListResponseType = "permission.updated"
|
||||||
EventListResponseTypeFileEdited EventListResponseType = "file.edited"
|
EventListResponseTypeFileEdited EventListResponseType = "file.edited"
|
||||||
EventListResponseTypeStorageWrite EventListResponseType = "storage.write"
|
|
||||||
EventListResponseTypeInstallationUpdated EventListResponseType = "installation.updated"
|
EventListResponseTypeInstallationUpdated EventListResponseType = "installation.updated"
|
||||||
|
EventListResponseTypeStorageWrite EventListResponseType = "storage.write"
|
||||||
EventListResponseTypeMessageUpdated EventListResponseType = "message.updated"
|
EventListResponseTypeMessageUpdated EventListResponseType = "message.updated"
|
||||||
EventListResponseTypeMessageRemoved EventListResponseType = "message.removed"
|
EventListResponseTypeMessageRemoved EventListResponseType = "message.removed"
|
||||||
EventListResponseTypeMessagePartUpdated EventListResponseType = "message.part.updated"
|
EventListResponseTypeMessagePartUpdated EventListResponseType = "message.part.updated"
|
||||||
|
@ -1173,7 +1173,7 @@ const (
|
||||||
|
|
||||||
func (r EventListResponseType) IsKnown() bool {
|
func (r EventListResponseType) IsKnown() bool {
|
||||||
switch r {
|
switch r {
|
||||||
case EventListResponseTypeLspClientDiagnostics, EventListResponseTypePermissionUpdated, EventListResponseTypeFileEdited, EventListResponseTypeStorageWrite, EventListResponseTypeInstallationUpdated, EventListResponseTypeMessageUpdated, EventListResponseTypeMessageRemoved, EventListResponseTypeMessagePartUpdated, EventListResponseTypeSessionUpdated, EventListResponseTypeSessionDeleted, EventListResponseTypeSessionIdle, EventListResponseTypeSessionError, EventListResponseTypeFileWatcherUpdated:
|
case EventListResponseTypeLspClientDiagnostics, EventListResponseTypePermissionUpdated, EventListResponseTypeFileEdited, EventListResponseTypeInstallationUpdated, EventListResponseTypeStorageWrite, EventListResponseTypeMessageUpdated, EventListResponseTypeMessageRemoved, EventListResponseTypeMessagePartUpdated, EventListResponseTypeSessionUpdated, EventListResponseTypeSessionDeleted, EventListResponseTypeSessionIdle, EventListResponseTypeSessionError, EventListResponseTypeFileWatcherUpdated:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -60,8 +60,5 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"release-type": "go",
|
"release-type": "go",
|
||||||
"extra-files": [
|
"extra-files": ["internal/version.go", "README.md"]
|
||||||
"internal/version.go",
|
}
|
||||||
"README.md"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -118,7 +118,7 @@ func TestSessionChat(t *testing.T) {
|
||||||
"id",
|
"id",
|
||||||
opencode.SessionChatParams{
|
opencode.SessionChatParams{
|
||||||
ModelID: opencode.F("modelID"),
|
ModelID: opencode.F("modelID"),
|
||||||
Parts: opencode.F([]opencode.MessagePartUnionParam{opencode.TextPartParam{
|
Parts: opencode.F([]opencode.UserMessagePartUnionParam{opencode.TextPartParam{
|
||||||
Text: opencode.F("text"),
|
Text: opencode.F("text"),
|
||||||
Type: opencode.F(opencode.TextPartTypeText),
|
Type: opencode.F(opencode.TextPartTypeText),
|
||||||
}}),
|
}}),
|
||||||
|
|
|
@ -31,7 +31,7 @@ func (r providerAuthErrorJSON) RawJSON() string {
|
||||||
|
|
||||||
func (r ProviderAuthError) ImplementsEventListResponseEventSessionErrorPropertiesError() {}
|
func (r ProviderAuthError) ImplementsEventListResponseEventSessionErrorPropertiesError() {}
|
||||||
|
|
||||||
func (r ProviderAuthError) ImplementsMessageMetadataError() {}
|
func (r ProviderAuthError) ImplementsAssistantMessageError() {}
|
||||||
|
|
||||||
type ProviderAuthErrorData struct {
|
type ProviderAuthErrorData struct {
|
||||||
Message string `json:"message,required"`
|
Message string `json:"message,required"`
|
||||||
|
@ -94,7 +94,7 @@ func (r unknownErrorJSON) RawJSON() string {
|
||||||
|
|
||||||
func (r UnknownError) ImplementsEventListResponseEventSessionErrorPropertiesError() {}
|
func (r UnknownError) ImplementsEventListResponseEventSessionErrorPropertiesError() {}
|
||||||
|
|
||||||
func (r UnknownError) ImplementsMessageMetadataError() {}
|
func (r UnknownError) ImplementsAssistantMessageError() {}
|
||||||
|
|
||||||
type UnknownErrorData struct {
|
type UnknownErrorData struct {
|
||||||
Message string `json:"message,required"`
|
Message string `json:"message,required"`
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue