fix(opencode): restore stable v1 runtime

This commit is contained in:
2026-08-05 17:59:35 +08:00
parent 764a1f4e82
commit 1407dd3bbe
33 changed files with 1106 additions and 1668 deletions
+9 -170
View File
@@ -4,8 +4,7 @@
"workspaces": { "workspaces": {
"": { "": {
"dependencies": { "dependencies": {
"@opencode-ai/plugin": "0.0.0-next-16741", "@opencode-ai/plugin": "1.18.13",
"zod": "4.1.8",
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^24.7.2", "@types/node": "^24.7.2",
@@ -16,14 +15,6 @@
"packages": { "packages": {
"@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="], "@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="],
"@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="],
"@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="],
"@aws-sdk/types": ["@aws-sdk/types@3.974.2", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA=="],
"@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
"@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ=="], "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ=="],
"@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w=="], "@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w=="],
@@ -36,212 +27,60 @@
"@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ=="], "@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ=="],
"@opencode-ai/ai": ["@opencode-ai/ai@0.0.0-next-16741", "", { "dependencies": { "@opencode-ai/schema": "0.0.0-next-16741", "@smithy/eventstream-codec": "4.2.14", "@smithy/util-utf8": "4.2.2", "aws4fetch": "1.0.20", "effect": "4.0.0-beta.101", "google-auth-library": "10.5.0" } }, "sha512-nmVBHgT/rVA1JWWn1lIYP03JZVd+mbZc1bYPcV1fH4QMWlVmr9aDiU15HbhrXfSqkaRd1DBENqHR6zhW+S2iIA=="], "@opencode-ai/plugin": ["@opencode-ai/plugin@1.18.13", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@opencode-ai/sdk": "1.18.13", "effect": "4.0.0-beta.83", "zod": "4.1.8" }, "peerDependencies": { "@opentui/core": ">=0.4.5", "@opentui/keymap": ">=0.4.5", "@opentui/solid": ">=0.4.5" }, "optionalPeers": ["@opentui/core", "@opentui/keymap", "@opentui/solid"] }, "sha512-2H9YT80M1PYElpG+lmd/9kGqsNouiJIBCUhLblmgFwoSrB4wyahgkCS6NcFQR/AYXNH4I1Yd3lmQcVaEPu1qNg=="],
"@opencode-ai/client": ["@opencode-ai/client@0.0.0-next-16741", "", { "dependencies": { "@opencode-ai/protocol": "0.0.0-next-16741", "@opencode-ai/schema": "0.0.0-next-16741" }, "peerDependencies": { "effect": "4.0.0-beta.101" }, "optionalPeers": ["effect"] }, "sha512-myhJUUk5llhbcngRlxTkykUVtaBvsCTlPPi2PTHbCSWE+LFdByVbuYqGNFkKzM6Q3qJ0bS8Xm4BAlA4oc7wayw=="], "@opencode-ai/sdk": ["@opencode-ai/sdk@1.18.13", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-JY9etiVcu1G/pZjaH2vjK/b8z54ujxaWCD1GziO4ADUhRM6m6zm2332bPGcxEfA6TwweiJfNlK6wVZQ0f/X4KQ=="],
"@opencode-ai/plugin": ["@opencode-ai/plugin@0.0.0-next-16741", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@opencode-ai/ai": "0.0.0-next-16741", "@opencode-ai/client": "0.0.0-next-16741", "@opencode-ai/schema": "0.0.0-next-16741", "@opencode-ai/sdk": "1.18.5", "@standard-schema/spec": "1.1.0", "effect": "4.0.0-beta.101", "zod": "4.1.8" }, "peerDependencies": { "@opencode-ai/theme": "0.0.0-next-16741", "@opentui/core": ">=0.4.5", "@opentui/keymap": ">=0.4.5", "@opentui/solid": ">=0.4.5", "solid-js": ">=1.9.0" }, "optionalPeers": ["@opencode-ai/theme", "@opentui/core", "@opentui/keymap", "@opentui/solid", "solid-js"] }, "sha512-AMCtMGs/XioqsOSvwZFs/5BsKfovYdSpxcI9QTxzm9IHfuCe3gsffT9CgnMqCKNyV0snz9eVA3spd4UpbTeVSA=="],
"@opencode-ai/protocol": ["@opencode-ai/protocol@0.0.0-next-16741", "", { "dependencies": { "@opencode-ai/schema": "0.0.0-next-16741", "effect": "4.0.0-beta.101" } }, "sha512-2UWB5i8uXP9cojJus2A7z1DZhJIbsr81OWuUx1+MsaPMCLmJYkrvOzdSEhfFSuVRgNy1HY5X1n7QHmRPZzrIvg=="],
"@opencode-ai/schema": ["@opencode-ai/schema@0.0.0-next-16741", "", { "dependencies": { "@standard-schema/spec": "1.1.0", "effect": "4.0.0-beta.101" } }, "sha512-PzFe3hAI2gTwKwrbFssHvAiP9D7w/JI5W2VzW+gB0wqU5WT4MOxPGGQRJ72+xXGqhQZCeoyN15as4hY4jeOXXA=="],
"@opencode-ai/sdk": ["@opencode-ai/sdk@1.18.5", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-7KgMvP5/1oxbhHj6kYBtPSTEdFKYpUeEYOzBTKdzSaRpapUpFFdn6Hkus3rr0rljO0kukWZIgRd3DrVBwTULGA=="],
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
"@smithy/core": ["@smithy/core@3.31.1", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-CyogUINxvi7C7LDsh8Syo6hVJOT9ckz4rG8dRZfTJ8r91HkMY59PnNooaj7WcHyxEkxPfBAmbgztZU+xTo76lg=="],
"@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.14", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.14.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw=="],
"@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
"@smithy/types": ["@smithy/types@4.16.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg=="],
"@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.4.16", "", { "dependencies": { "@smithy/core": "^3.31.1", "tslib": "^2.6.2" } }, "sha512-HxG8CH8HXUO6JqH+ftXCJm1XrISkcZNrOLGkMYiz90wT3FPBZZE/syDNYJ/iKQEi91gN2J/SnyEhHjcJPEVSFw=="],
"@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.4.16", "", { "dependencies": { "@smithy/core": "^3.31.1", "tslib": "^2.6.2" } }, "sha512-x9/taZ/r/nEL6SBfdV+M18VpJU16yzehn2q1XHNmXWEp3g5U4NBl2oMBVw7YKdmH2HiKQ/ZqDW5JjBDNzgLKrg=="],
"@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@types/node": ["@types/node@24.12.4", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA=="], "@types/node": ["@types/node@24.12.4", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA=="],
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
"ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
"ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
"aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="],
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
"bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
"brace-expansion": ["brace-expansion@2.1.4", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg=="],
"buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], "effect": ["effect@4.0.0-beta.83", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.8.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.1", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^14.0.0", "yaml": "^2.9.0" } }, "sha512-0wsak8RtgGAr9UWSbVDgJHZcUqMSvicHcvaZv1MbMM7MCGgW4Rn/137J1MHQbwYPcwYGxT/IqehFd+UbYuj78w=="],
"ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], "fast-check": ["fast-check@4.8.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-GOJ158CUMnN6cSahsv4+ExARvIDuzzinFjkp0E9WtiBa5zcVeLozVkWaE4IzFcc+Y48Wp1EDlUZsXRyAztQcSg=="],
"effect": ["effect@4.0.0-beta.101", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.9.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.4", "multipasta": "^0.2.8", "toml": "^4.1.2", "uuid": "^14.0.1", "yaml": "^2.9.0" } }, "sha512-HjowumlIo+orthn4jMlEJPuzIYPBV+uq/XiciHWhiedLsXQpWHdNJHO5d59BVDP5s1LPuvERcktwFqRXnJqnhA=="],
"emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
"fast-check": ["fast-check@4.9.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-7ms6T7SybUev/PQITciI0yLM2pOSFy5zpG8Ty7tQofcVaQUvrMXp6CBwqF6fThLCLOrfBtuHAtwq6Yu4XPCllg=="],
"fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
"find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="], "find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="],
"foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
"formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
"gaxios": ["gaxios@7.3.0", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-RB5vLV+vvQeoFPCX4QMK6/hjVkbIamPp1QSUD0CiZcnj12qbpiL+pLbYtgD+oZkWl0tl9z+o2Utp+MpM3QRhBA=="],
"gcp-metadata": ["gcp-metadata@8.1.4", "", { "dependencies": { "gaxios": "7.1.3", "google-logging-utils": "1.1.3", "json-bigint": "^1.0.0" } }, "sha512-iJ9KMsiu+xKtNRX0PmGLSaIU3bUBAyzWTyqKemKPzNPsmmsBCQYmlNg+brEbES7IHSXtdVwzBPzx1vz3FAaipw=="],
"glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
"google-auth-library": ["google-auth-library@10.5.0", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.0.0", "gcp-metadata": "^8.0.0", "google-logging-utils": "^1.0.0", "gtoken": "^8.0.0", "jws": "^4.0.0" } }, "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w=="],
"google-logging-utils": ["google-logging-utils@1.2.0", "", {}, "sha512-WE9av4wKDZgRjBwgVUabocx8T6/7o3Ca1Fat46FXDhXVAFibzNadedcOXrdgd1Kzmk8tsk/9ZH89Wyf/SqeZ3A=="],
"gtoken": ["gtoken@8.0.0", "", { "dependencies": { "gaxios": "^7.0.0", "jws": "^4.0.0" } }, "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw=="],
"https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
"ini": ["ini@7.0.0", "", {}, "sha512-ifK0CgjALofS5bkrcTy4RaQ9Vx2Knf/eLeIO+NaswQEpH1UblrtTSCIvN71qQDMq0PeQ/SSPojvEJp9vvvfr+w=="], "ini": ["ini@7.0.0", "", {}, "sha512-ifK0CgjALofS5bkrcTy4RaQ9Vx2Knf/eLeIO+NaswQEpH1UblrtTSCIvN71qQDMq0PeQ/SSPojvEJp9vvvfr+w=="],
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
"json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="],
"json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="], "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
"jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
"jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="],
"kubernetes-types": ["kubernetes-types@1.30.0", "", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="], "kubernetes-types": ["kubernetes-types@1.30.0", "", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="],
"lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], "msgpackr": ["msgpackr@2.0.2", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.4" } }, "sha512-c5hYOXFbP79Slh6Dzd2wzk+jnV7mX1UxfMYtilnY1NmalXPqG8DGb5cYCMBrW4AsH3zekBBZd4QrKz9NhtvYLQ=="],
"minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="],
"minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"msgpackr": ["msgpackr@2.0.5", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.4" } }, "sha512-cef05H/dSYpLpqp3sj/qyZh5vhUYCalnaLO7j1yOmpsR0y/XwLVtK7r5gn+U/F7CTEfMowcGhlUQJDLcLf7jcA=="],
"msgpackr-extract": ["msgpackr-extract@3.0.4", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw=="], "msgpackr-extract": ["msgpackr-extract@3.0.4", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw=="],
"multipasta": ["multipasta@0.2.8", "", {}, "sha512-ZPWuMKyv0cSO29f7hozp+k6+crZbQijV8ipMvxNxRf2SwtYGTX1ZX89Kd20VV4H9Znonx+EQn+iy1wGQsJ+b+Q=="], "multipasta": ["multipasta@0.2.7", "", {}, "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA=="],
"node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
"node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
"node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="], "node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="],
"package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
"path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
"pure-rand": ["pure-rand@8.4.0", "", {}, "sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A=="], "pure-rand": ["pure-rand@8.4.0", "", {}, "sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A=="],
"rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="],
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], "toml": ["toml@4.1.1", "", {}, "sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw=="],
"string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
"string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
"strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"toml": ["toml@4.3.0", "", {}, "sha512-lVb8X9BsPVuH0M4BKeS91tXAmJvCjQ5UIyAbQFaxkKGyUFK2RPkhwaFSQH8vbpl1d23eu/IBH+dwVMHWaq9A5A=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"uuid": ["uuid@14.0.1", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew=="], "uuid": ["uuid@14.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg=="],
"web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
"wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
"yaml": ["yaml@2.9.0", "", { "bin": "bin.mjs" }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], "yaml": ["yaml@2.9.0", "", { "bin": "bin.mjs" }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="],
"zod": ["zod@4.1.8", "", {}, ""], "zod": ["zod@4.1.8", "", {}, ""],
"@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=="],
"gcp-metadata/gaxios": ["gaxios@7.1.3", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2", "rimraf": "^5.0.1" } }, "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ=="],
"gcp-metadata/google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="],
"string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
} }
} }
+1 -2
View File
@@ -4,8 +4,7 @@
"typecheck": "tsc --noEmit -p tsconfig.json" "typecheck": "tsc --noEmit -p tsconfig.json"
}, },
"dependencies": { "dependencies": {
"@opencode-ai/plugin": "0.0.0-next-16741", "@opencode-ai/plugin": "1.18.13"
"zod": "4.1.8"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^24.7.2", "@types/node": "^24.7.2",
-44
View File
@@ -1,44 +0,0 @@
import apply_layer_style from "../tools/apply_layer_style.js";
import geocode from "../tools/geocode.js";
import locate_features from "../tools/locate_features.js";
import memory_manager from "../tools/memory_manager.js";
import render_junctions from "../tools/render_junctions.js";
import session_search from "../tools/session_search.js";
import show_chart from "../tools/show_chart.js";
import skill_manager from "../tools/skill_manager.js";
import store_render_ref from "../tools/store_render_ref.js";
import tjwater_cli from "../tools/tjwater_cli.js";
import view_history from "../tools/view_history.js";
import view_scada from "../tools/view_scada.js";
import web_search from "../tools/web_search.js";
import zoom_to_map from "../tools/zoom_to_map.js";
import { Plugin } from "@opencode-ai/plugin";
const definitions = {
apply_layer_style,
geocode,
locate_features,
memory_manager,
render_junctions,
session_search,
show_chart,
skill_manager,
store_render_ref,
tjwater_cli,
view_history,
view_scada,
web_search,
zoom_to_map,
};
export default Plugin.define({
id: "tjwater-tools",
async setup(context) {
const registration = await context.tool.transform((tools) => {
for (const [name, definition] of Object.entries(definitions)) {
tools.add({ name, ...definition });
}
});
return () => registration.dispose();
},
});
+1 -1
View File
@@ -1,4 +1,4 @@
import { tool } from "./defineTool.js"; import { tool } from "@opencode-ai/plugin";
export default tool({ export default tool({
description: description:
-31
View File
@@ -1,31 +0,0 @@
import { z } from "zod";
type ToolContext = {
sessionID: string;
};
type ToolDefinition<Shape extends z.ZodRawShape> = {
description: string;
args: Shape;
execute: (
args: z.infer<z.ZodObject<Shape>>,
context: ToolContext,
) => Promise<string> | string;
};
const defineTool = <Shape extends z.ZodRawShape>(
definition: ToolDefinition<Shape>,
) => {
const inputSchema = z.object(definition.args);
return {
description: definition.description,
input: z.toJSONSchema(inputSchema),
async execute(input: unknown, context: ToolContext) {
return {
content: await definition.execute(inputSchema.parse(input), context),
};
},
};
};
export const tool = Object.assign(defineTool, { schema: z });
+1 -1
View File
@@ -1,4 +1,4 @@
import { tool } from "./defineTool.js"; import { tool } from "@opencode-ai/plugin";
const internalBaseUrl = const internalBaseUrl =
process.env.TJWATER_AGENT_INTERNAL_BASE_URL ?? "http://127.0.0.1:8787"; process.env.TJWATER_AGENT_INTERNAL_BASE_URL ?? "http://127.0.0.1:8787";
+1 -1
View File
@@ -1,4 +1,4 @@
import { tool } from "./defineTool.js"; import { tool } from "@opencode-ai/plugin";
export default tool({ export default tool({
description: "在前端地图上定位并高亮指定的管网要素。", description: "在前端地图上定位并高亮指定的管网要素。",
+1 -1
View File
@@ -1,4 +1,4 @@
import { tool } from "./defineTool.js"; import { tool } from "@opencode-ai/plugin";
import { MemoryStore } from "../../src/memory/store.js"; import { MemoryStore } from "../../src/memory/store.js";
import { import {
getRuntimeSessionContext, getRuntimeSessionContext,
+1 -1
View File
@@ -1,4 +1,4 @@
import { tool } from "./defineTool.js"; import { tool } from "@opencode-ai/plugin";
export default tool({ export default tool({
description: description:
+1 -1
View File
@@ -1,4 +1,4 @@
import { tool } from "./defineTool.js"; import { tool } from "@opencode-ai/plugin";
const internalBaseUrl = const internalBaseUrl =
process.env.TJWATER_AGENT_INTERNAL_BASE_URL ?? "http://127.0.0.1:8787"; process.env.TJWATER_AGENT_INTERNAL_BASE_URL ?? "http://127.0.0.1:8787";
+1 -1
View File
@@ -1,4 +1,4 @@
import { tool } from "./defineTool.js"; import { tool } from "@opencode-ai/plugin";
export default tool({ export default tool({
description: description:
+1 -1
View File
@@ -1,4 +1,4 @@
import { tool } from "./defineTool.js"; import { tool } from "@opencode-ai/plugin";
import { SkillStore } from "../../src/skills/store.js"; import { SkillStore } from "../../src/skills/store.js";
import { import {
+1 -1
View File
@@ -1,4 +1,4 @@
import { tool } from "./defineTool.js"; import { tool } from "@opencode-ai/plugin";
const internalBaseUrl = const internalBaseUrl =
process.env.TJWATER_AGENT_INTERNAL_BASE_URL ?? "http://127.0.0.1:8787"; process.env.TJWATER_AGENT_INTERNAL_BASE_URL ?? "http://127.0.0.1:8787";
+1 -1
View File
@@ -1,4 +1,4 @@
import { tool } from "./defineTool.js"; import { tool } from "@opencode-ai/plugin";
const internalBaseUrl = const internalBaseUrl =
process.env.TJWATER_AGENT_INTERNAL_BASE_URL ?? "http://127.0.0.1:8787"; process.env.TJWATER_AGENT_INTERNAL_BASE_URL ?? "http://127.0.0.1:8787";
+1 -1
View File
@@ -1,4 +1,4 @@
import { tool } from "./defineTool.js"; import { tool } from "@opencode-ai/plugin";
export default tool({ export default tool({
description: "为选定的管网要素打开前端的历史记录或计算结果面板。", description: "为选定的管网要素打开前端的历史记录或计算结果面板。",
+1 -1
View File
@@ -1,4 +1,4 @@
import { tool } from "./defineTool.js"; import { tool } from "@opencode-ai/plugin";
export default tool({ export default tool({
description: "打开前端的 SCADA 监测数据历史面板。", description: "打开前端的 SCADA 监测数据历史面板。",
+1 -1
View File
@@ -1,4 +1,4 @@
import { tool } from "./defineTool.js"; import { tool } from "@opencode-ai/plugin";
const internalBaseUrl = const internalBaseUrl =
process.env.TJWATER_AGENT_INTERNAL_BASE_URL ?? "http://127.0.0.1:8787"; process.env.TJWATER_AGENT_INTERNAL_BASE_URL ?? "http://127.0.0.1:8787";
+1 -1
View File
@@ -1,4 +1,4 @@
import { tool } from "./defineTool.js"; import { tool } from "@opencode-ai/plugin";
export default tool({ export default tool({
description: description:
+8 -15
View File
@@ -1,11 +1,10 @@
FROM ubuntu:24.04 AS base FROM smanx/opencode:1.18.4@sha256:719821683648f5c28431ed28a068cb96f92c15374148783a1e0d34d5e2f16ed3 AS base
USER root USER root
ARG UBUNTU_APT_MIRROR= ARG UBUNTU_APT_MIRROR=
ARG OPENCODE_VERSION=0.0.0-next-16741
ARG PYPI_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple ARG PYPI_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple
ARG PYPI_TRUSTED_HOST=pypi.tuna.tsinghua.edu.cn ARG PYPI_TRUSTED_HOST=pypi.tuna.tsinghua.edu.cn
ENV VIRTUAL_ENV=/opt/venv ENV VIRTUAL_ENV=/opt/venv
ENV PATH="$VIRTUAL_ENV/bin:/root/.local/bin:/root/.bun/bin:$PATH" ENV PATH="$VIRTUAL_ENV/bin:/root/.local/bin:$PATH"
ENV PIP_INDEX_URL=${PYPI_INDEX_URL} ENV PIP_INDEX_URL=${PYPI_INDEX_URL}
ENV PIP_TRUSTED_HOST=${PYPI_TRUSTED_HOST} ENV PIP_TRUSTED_HOST=${PYPI_TRUSTED_HOST}
ENV UV_INDEX_URL=${PYPI_INDEX_URL} ENV UV_INDEX_URL=${PYPI_INDEX_URL}
@@ -17,7 +16,6 @@ RUN if [ -n "${UBUNTU_APT_MIRROR}" ]; then \
sed -i "s|http://archive.ubuntu.com|https://${UBUNTU_APT_MIRROR}|g; s|http://security.ubuntu.com|https://${UBUNTU_APT_MIRROR}|g" /etc/apt/sources.list.d/*.sources 2>/dev/null || true; \ sed -i "s|http://archive.ubuntu.com|https://${UBUNTU_APT_MIRROR}|g; s|http://security.ubuntu.com|https://${UBUNTU_APT_MIRROR}|g" /etc/apt/sources.list.d/*.sources 2>/dev/null || true; \
fi && \ fi && \
apt-get update && apt-get install -y --no-install-recommends \ apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
curl \ curl \
jq \ jq \
unzip \ unzip \
@@ -27,8 +25,6 @@ RUN if [ -n "${UBUNTU_APT_MIRROR}" ]; then \
mv /usr/local/bun-linux-x64 /usr/local/bun && \ mv /usr/local/bun-linux-x64 /usr/local/bun && \
ln -sf /usr/local/bun/bun /usr/local/bin/bun && \ ln -sf /usr/local/bun/bun /usr/local/bin/bun && \
rm -f /tmp/bun.zip && \ rm -f /tmp/bun.zip && \
bun install --global --trust "@opencode-ai/cli@${OPENCODE_VERSION}" && \
opencode2 --version && \
curl -LsSf https://astral.sh/uv/install.sh | sh && \ curl -LsSf https://astral.sh/uv/install.sh | sh && \
ln -s /root/.local/bin/uv /usr/local/bin/uv && \ ln -s /root/.local/bin/uv /usr/local/bin/uv && \
ln -sf /usr/bin/python3 /usr/local/bin/python && \ ln -sf /usr/bin/python3 /usr/local/bin/python && \
@@ -62,32 +58,29 @@ WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules COPY --from=deps /app/node_modules ./node_modules
COPY --from=deps /app/.opencode/node_modules ./.opencode/node_modules COPY --from=deps /app/.opencode/node_modules ./.opencode/node_modules
COPY package.json bun.lock ./
COPY tsconfig.json opencode.json README.md .gitignore ./ COPY tsconfig.json opencode.json README.md .gitignore ./
COPY src ./src COPY src ./src
COPY cli ./cli COPY cli ./cli
COPY .opencode ./.opencode COPY .opencode ./.opencode
RUN bun run check RUN bun run check
FROM base AS runner FROM build AS runner
WORKDIR /app WORKDIR /app
ENV NODE_ENV=production ENV NODE_ENV=production
ENV HOST=0.0.0.0 ENV HOST=0.0.0.0
ENV PORT=8787 ENV PORT=8787
ENV OPENCODE_HOST=127.0.0.1
ENV OPENCODE_HOSTNAME=127.0.0.1
ENV TJWATER_CLI_PATH=./cli/tjwater-cli ENV TJWATER_CLI_PATH=./cli/tjwater-cli
COPY --from=deps /app/node_modules ./node_modules
COPY --from=deps /app/.opencode/node_modules ./.opencode/node_modules
COPY package.json bun.lock ./
COPY tsconfig.json opencode.json .gitignore ./
COPY src ./src
COPY .opencode ./.opencode
COPY cli ./cli
COPY entrypoint.sh /entrypoint.sh COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh ./cli/tjwater-cli RUN chmod +x /entrypoint.sh ./cli/tjwater-cli
ENTRYPOINT ["/entrypoint.sh"] ENTRYPOINT ["/entrypoint.sh"]
EXPOSE 8787 EXPOSE 8787
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
CMD curl --fail --silent --show-error "http://127.0.0.1:${PORT}/health" >/dev/null || exit 1
CMD ["bun", "src/server.ts"] CMD ["bun", "src/server.ts"]
+9 -6
View File
@@ -5,10 +5,10 @@
## 主要能力 ## 主要能力
- 提供 `POST /api/v1/agent/sessions/{session_id}/runs` SSE 聊天接口。 - 提供 `POST /api/v1/agent/sessions/{session_id}/runs` SSE 聊天接口。
- 使用 OpenCode 2.0 beta client支持 embedded `opencode2` service,也可连接外部 V2 service。 - 支持 embedded OpenCode 运行时,也可连接外部 OpenCode server
- 管理前端 `session_id` 与 OpenCode session 的映射。 - 管理前端 `session_id` 与 OpenCode session 的映射。
- 在服务端保存当前会话的用户 token、项目、network 和 trace 上下文。 - 在服务端保存当前会话的用户 token、项目、network 和 trace 上下文。
- 通过 `.opencode/plugins` 注册 `.opencode/tools` 中的工具实现,驱动地图定位、图表、SCADA、历史数据和业务 API 调用。 - 通过 `.opencode/tools` 和 MCP 工具驱动地图定位、图表、SCADA、历史数据和业务 API 调用。
- 通过 `data/` 保存运行时会话元数据、结果引用和本地状态。 - 通过 `data/` 保存运行时会话元数据、结果引用和本地状态。
## 目录结构 ## 目录结构
@@ -21,8 +21,7 @@ src/runtime/ OpenCode 运行时管理
src/session/ 会话映射和运行上下文 src/session/ 会话映射和运行上下文
src/mcp/ MCP 服务与工具桥接 src/mcp/ MCP 服务与工具桥接
.opencode/agents/ Agent prompt 和模型行为配置 .opencode/agents/ Agent prompt 和模型行为配置
.opencode/plugins/ OpenCode V2 插件入口 .opencode/tools/ OpenCode 自定义工具
.opencode/tools/ 插件注册的工具实现
.opencode/skills/ 可复用分析工作流 .opencode/skills/ 可复用分析工作流
node-tests/ Node CLI 测试 node-tests/ Node CLI 测试
data/ 本地运行时数据,禁止提交 data/ 本地运行时数据,禁止提交
@@ -61,14 +60,14 @@ docker build -t tjwater-agent:local .
## 运行模式 ## 运行模式
Embedded 模式由服务进程拉起本机 `opencode2 serve --service`。本地需要安装`@opencode-ai/client``@opencode-ai/plugin` 对齐的 V2 beta CLI;Docker 镜像会安装仓库固定版本 当前运行时使用 OpenCode 稳定版 1.x CLI,并通过稳定版 SDK 的 `@opencode-ai/sdk/v2` HTTP 客户端访问运行时;这`opencode2``@opencode-ai/client` 的 2.0 beta 运行时不同。Embedded 模式由服务进程拉起本机 OpenCode
```bash ```bash
OPENCODE_MODE=embedded OPENCODE_MODE=embedded
TJWATER_API_BASE_URL=http://127.0.0.1:8000 TJWATER_API_BASE_URL=http://127.0.0.1:8000
``` ```
Client 模式连接外部 OpenCode V2 service Client 模式连接外部 OpenCode server
```bash ```bash
OPENCODE_MODE=client OPENCODE_MODE=client
@@ -78,6 +77,10 @@ TJWATER_API_BASE_URL=http://127.0.0.1:8000
本地可使用 `.local.env` 保存开发配置;系统环境变量优先级更高。 本地可使用 `.local.env` 保存开发配置;系统环境变量优先级更高。
服务会在 HTTP 端口开始监听前完成 OpenCode 健康检查、临时会话创建和工具目录加载。`GET /health` 返回 `ready: true``warmed_up: true` 时,表示冷启动预热已经完成。开发环境会输出各预热阶段的耗时。
`opencode.json` 已启用 `experimental.continue_loop_on_deny`。用户拒绝权限请求后,OpenCode V1 会把拒绝结果交还给 Agent,让其尝试无需该权限的替代方案,而不是直接结束本轮执行。
## 配置与安全 ## 配置与安全
不要提交 `.env``.local.env``data/``logs/`、会话记录、模型输出、访问令牌或 `node_modules/`。部署凭据、镜像仓库账号和 webhook 地址应放在 Gitea secrets 或部署环境变量中。 不要提交 `.env``.local.env``data/``logs/`、会话记录、模型输出、访问令牌或 `node_modules/`。部署凭据、镜像仓库账号和 webhook 地址应放在 Gitea secrets 或部署环境变量中。
+14 -46
View File
@@ -5,7 +5,7 @@
"": { "": {
"name": "tjwater-agent", "name": "tjwater-agent",
"dependencies": { "dependencies": {
"@opencode-ai/client": "0.0.0-next-16741", "@opencode-ai/sdk": "1.18.13",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"express": "^4.21.2", "express": "^4.21.2",
@@ -26,28 +26,10 @@
"packages": { "packages": {
"@asteasolutions/zod-to-openapi": ["@asteasolutions/zod-to-openapi@7.3.4", "", { "dependencies": { "openapi3-ts": "^4.1.2" }, "peerDependencies": { "zod": "^3.20.2" } }, "sha512-/2rThQ5zPi9OzVwes6U7lK1+Yvug0iXu25olp7S0XsYmOqnyMfxH7gdSQjn/+DSOHRg7wnotwGJSyL+fBKdnEA=="], "@asteasolutions/zod-to-openapi": ["@asteasolutions/zod-to-openapi@7.3.4", "", { "dependencies": { "openapi3-ts": "^4.1.2" }, "peerDependencies": { "zod": "^3.20.2" } }, "sha512-/2rThQ5zPi9OzVwes6U7lK1+Yvug0iXu25olp7S0XsYmOqnyMfxH7gdSQjn/+DSOHRg7wnotwGJSyL+fBKdnEA=="],
"@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ=="], "@opencode-ai/sdk": ["@opencode-ai/sdk@1.18.13", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-JY9etiVcu1G/pZjaH2vjK/b8z54ujxaWCD1GziO4ADUhRM6m6zm2332bPGcxEfA6TwweiJfNlK6wVZQ0f/X4KQ=="],
"@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-zExlW9zUJKZH/tOtVMttwjKa4Xm/3KcNjnE3dPN92uCktwavMxpgCA3MoJK/DOnTWsQgo224OaST27/mPNAf+w=="],
"@msgpackr-extract/msgpackr-extract-linux-arm": ["@msgpackr-extract/msgpackr-extract-linux-arm@3.0.4", "", { "os": "linux", "cpu": "arm" }, "sha512-Tg3yX65f5GbtXLkrYEHE5oibZG9epyYWas7FogTTEJeDEF9JlXJzKgXaNhT3UXlTOeA+AfZpYZYZ0uPj7Cfquw=="],
"@msgpackr-extract/msgpackr-extract-linux-arm64": ["@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-dgX0P/9wGPJeHFBG+ZmhgE6bmtMt7NP5CRBGyyktpopdk/mW4POnrpQsSLtKI1dwpc+pPLuXHDh6vvskyQE/sw=="],
"@msgpackr-extract/msgpackr-extract-linux-x64": ["@msgpackr-extract/msgpackr-extract-linux-x64@3.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-8TNXMEjJc3QEy7R/x1INhgiU+XakDAFUzBhaz7+Rbrs8NH5UQeHQxxmzsSBJGyV6I1jW79undiQm8tOI+D+8FQ=="],
"@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-CmCXPQrkbwExx3j946/PtHWHbYJiCRBRDl4BlkRQcJB/YOwQxJRTpoo7aTsortjgoJ1x7opzTSxn7C+ASSLVjQ=="],
"@opencode-ai/client": ["@opencode-ai/client@0.0.0-next-16741", "", { "dependencies": { "@opencode-ai/protocol": "0.0.0-next-16741", "@opencode-ai/schema": "0.0.0-next-16741" }, "peerDependencies": { "effect": "4.0.0-beta.101" }, "optionalPeers": ["effect"] }, "sha512-myhJUUk5llhbcngRlxTkykUVtaBvsCTlPPi2PTHbCSWE+LFdByVbuYqGNFkKzM6Q3qJ0bS8Xm4BAlA4oc7wayw=="],
"@opencode-ai/protocol": ["@opencode-ai/protocol@0.0.0-next-16741", "", { "dependencies": { "@opencode-ai/schema": "0.0.0-next-16741", "effect": "4.0.0-beta.101" } }, "sha512-2UWB5i8uXP9cojJus2A7z1DZhJIbsr81OWuUx1+MsaPMCLmJYkrvOzdSEhfFSuVRgNy1HY5X1n7QHmRPZzrIvg=="],
"@opencode-ai/schema": ["@opencode-ai/schema@0.0.0-next-16741", "", { "dependencies": { "@standard-schema/spec": "1.1.0", "effect": "4.0.0-beta.101" } }, "sha512-PzFe3hAI2gTwKwrbFssHvAiP9D7w/JI5W2VzW+gB0wqU5WT4MOxPGGQRJ72+xXGqhQZCeoyN15as4hY4jeOXXA=="],
"@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="], "@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="],
"@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@types/body-parser": ["@types/body-parser@1.19.6", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="], "@types/body-parser": ["@types/body-parser@1.19.6", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="],
"@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="], "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="],
@@ -98,6 +80,8 @@
"cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="], "cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"dateformat": ["dateformat@4.6.3", "", {}, "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA=="], "dateformat": ["dateformat@4.6.3", "", {}, "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA=="],
"debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
@@ -106,16 +90,12 @@
"destroy": ["destroy@1.2.0", "", {}, "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="], "destroy": ["destroy@1.2.0", "", {}, "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="], "dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="],
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
"effect": ["effect@4.0.0-beta.101", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.9.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.4", "multipasta": "^0.2.8", "toml": "^4.1.2", "uuid": "^14.0.1", "yaml": "^2.9.0" } }, "sha512-HjowumlIo+orthn4jMlEJPuzIYPBV+uq/XiciHWhiedLsXQpWHdNJHO5d59BVDP5s1LPuvERcktwFqRXnJqnhA=="],
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
"end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
@@ -132,16 +112,12 @@
"express": ["express@4.22.1", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.3", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g=="], "express": ["express@4.22.1", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.3", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g=="],
"fast-check": ["fast-check@4.9.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-7ms6T7SybUev/PQITciI0yLM2pOSFy5zpG8Ty7tQofcVaQUvrMXp6CBwqF6fThLCLOrfBtuHAtwq6Yu4XPCllg=="],
"fast-copy": ["fast-copy@4.0.3", "", {}, "sha512-58apWr0GUiDFM8+3afrO6eYwJBn9ZAhDOzG3L+/9llab/haCARS2UIfffmOurYLwbgDRs8n0rfr6qAAPEAuAQw=="], "fast-copy": ["fast-copy@4.0.3", "", {}, "sha512-58apWr0GUiDFM8+3afrO6eYwJBn9ZAhDOzG3L+/9llab/haCARS2UIfffmOurYLwbgDRs8n0rfr6qAAPEAuAQw=="],
"fast-safe-stringify": ["fast-safe-stringify@2.1.1", "", {}, "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="], "fast-safe-stringify": ["fast-safe-stringify@2.1.1", "", {}, "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="],
"finalhandler": ["finalhandler@1.3.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "statuses": "~2.0.2", "unpipe": "~1.0.0" } }, "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg=="], "finalhandler": ["finalhandler@1.3.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "statuses": "~2.0.2", "unpipe": "~1.0.0" } }, "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg=="],
"find-my-way-ts": ["find-my-way-ts@0.1.6", "", {}, "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA=="],
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
"fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="], "fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="],
@@ -166,13 +142,11 @@
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"ini": ["ini@7.0.0", "", {}, "sha512-ifK0CgjALofS5bkrcTy4RaQ9Vx2Knf/eLeIO+NaswQEpH1UblrtTSCIvN71qQDMq0PeQ/SSPojvEJp9vvvfr+w=="],
"ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
"joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"kubernetes-types": ["kubernetes-types@1.30.0", "", {}, "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q=="], "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="],
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
@@ -192,16 +166,8 @@
"ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], "ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"msgpackr": ["msgpackr@2.0.5", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.4" } }, "sha512-cef05H/dSYpLpqp3sj/qyZh5vhUYCalnaLO7j1yOmpsR0y/XwLVtK7r5gn+U/F7CTEfMowcGhlUQJDLcLf7jcA=="],
"msgpackr-extract": ["msgpackr-extract@3.0.4", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.4", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.4", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-4kmO/MdyUIkLIvTPr8VHLil4AtoKIoniWPIEk5+CDy0xnWC84azhSFmuJ7PxZdsYtiP5kEeQsORAVIeMgxT+Hw=="],
"multipasta": ["multipasta@0.2.8", "", {}, "sha512-ZPWuMKyv0cSO29f7hozp+k6+crZbQijV8ipMvxNxRf2SwtYGTX1ZX89Kd20VV4H9Znonx+EQn+iy1wGQsJ+b+Q=="],
"negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], "negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
"node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="],
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
@@ -216,6 +182,8 @@
"parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
"path-to-regexp": ["path-to-regexp@0.1.13", "", {}, "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA=="], "path-to-regexp": ["path-to-regexp@0.1.13", "", {}, "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA=="],
"pino": ["pino@9.14.0", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^3.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w=="], "pino": ["pino@9.14.0", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^3.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w=="],
@@ -232,8 +200,6 @@
"pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="],
"pure-rand": ["pure-rand@8.4.2", "", {}, "sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng=="],
"qs": ["qs@6.14.2", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q=="], "qs": ["qs@6.14.2", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q=="],
"quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="], "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="],
@@ -258,6 +224,10 @@
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
"side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
"side-channel-list": ["side-channel-list@1.0.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" } }, "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w=="], "side-channel-list": ["side-channel-list@1.0.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" } }, "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w=="],
@@ -278,8 +248,6 @@
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
"toml": ["toml@4.3.0", "", {}, "sha512-lVb8X9BsPVuH0M4BKeS91tXAmJvCjQ5UIyAbQFaxkKGyUFK2RPkhwaFSQH8vbpl1d23eu/IBH+dwVMHWaq9A5A=="],
"type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], "type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
@@ -290,10 +258,10 @@
"utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="], "utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="],
"uuid": ["uuid@14.0.1", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew=="],
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
"yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="],
+1 -1
View File
@@ -2,6 +2,6 @@
set -e set -e
# 直接启动 TJWaterAgent # 直接启动 TJWaterAgent
# V2 client service helper 会根据 src/runtime/opencode.ts 自动管理 opencode2 service # SDK 会根据 src/runtime/opencode.ts 中的逻辑自动管理 opencode 实例
echo "Starting TJWaterAgent..." echo "Starting TJWaterAgent..."
exec bun run start exec bun run start
+25 -15
View File
@@ -1,25 +1,35 @@
{ {
"$schema": "https://opencode.ai/config.json", "$schema": "https://opencode.ai/config.json",
"providers": { "provider": {
"deepseek": { "deepseek": {
"settings": { "options": {
"apiKey": "{env:DEEPSEEK_API_KEY}" "apiKey": "{env:DEEPSEEK_API_KEY}"
} }
} }
}, },
"model": "deepseek/deepseek-v4-flash", "model": "deepseek/deepseek-v4-flash",
"permissions": [ "server": {
{ "action": "*", "resource": "*", "effect": "allow" }, "hostname": "127.0.0.1",
{ "action": "external_directory", "resource": "*", "effect": "ask" }, "port": 4096
{ "action": "shell", "resource": "rm *", "effect": "ask" }, },
{ "action": "shell", "resource": "rmdir *", "effect": "ask" }, "permission": {
{ "action": "shell", "resource": "mv *", "effect": "ask" }, "*": "allow",
{ "action": "shell", "resource": "chmod *", "effect": "ask" }, "external_directory": "ask",
{ "action": "shell", "resource": "chown *", "effect": "ask" }, "bash": {
{ "action": "shell", "resource": "sudo *", "effect": "ask" }, "*": "allow",
{ "action": "shell", "resource": "curl *", "effect": "ask" }, "rm *": "ask",
{ "action": "shell", "resource": "wget *", "effect": "ask" }, "rmdir *": "ask",
{ "action": "edit", "resource": "*", "effect": "ask" } "mv *": "ask",
], "chmod *": "ask",
"chown *": "ask",
"sudo *": "ask",
"curl *": "ask",
"wget *": "ask"
},
"edit": "ask"
},
"experimental": {
"continue_loop_on_deny": true
},
"default_agent": "instruction" "default_agent": "instruction"
} }
+1 -1
View File
@@ -21,7 +21,7 @@
"start:prod": "bun run check && bun src/server.ts" "start:prod": "bun run check && bun src/server.ts"
}, },
"dependencies": { "dependencies": {
"@opencode-ai/client": "0.0.0-next-16741", "@opencode-ai/sdk": "1.18.13",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"express": "^4.21.2", "express": "^4.21.2",
+14 -4
View File
@@ -41,9 +41,13 @@ const envSchema = z
AGENT_INTERNAL_TOKEN: optionalString(), AGENT_INTERNAL_TOKEN: optionalString(),
// Agent 前置认证调用后端 /api/v1/agent/auth/context 的超时时间(毫秒)。 // Agent 前置认证调用后端 /api/v1/agent/auth/context 的超时时间(毫秒)。
AGENT_AUTH_TIMEOUT_MS: z.coerce.number().int().positive().default(5000), AGENT_AUTH_TIMEOUT_MS: z.coerce.number().int().positive().default(5000),
// OpenCode V2 运行模式:embedded 启动 opencode2 serviceclient 连接 V2 service。 // opencode 运行模式:embedded 启动本地 CLI 子进程client 连接有 server
OPENCODE_MODE: z.enum(["embedded", "client"]).default("embedded"), OPENCODE_MODE: z.enum(["embedded", "client"]).default("embedded"),
// OpenCode V2 client 等待会话状态变化的超时时间(毫秒) // embedded opencode server 的监听地址
OPENCODE_HOSTNAME: z.string().default("127.0.0.1"),
// embedded opencode server 的监听端口。
OPENCODE_PORT: z.coerce.number().int().positive().default(4096),
// opencode SDK 启动或连接运行时时的超时时间(毫秒)。
OPENCODE_TIMEOUT_MS: z.coerce.number().int().positive().default(5000), OPENCODE_TIMEOUT_MS: z.coerce.number().int().positive().default(5000),
// 默认使用的 opencode 模型标识。 // 默认使用的 opencode 模型标识。
OPENCODE_MODEL: z.string().default("deepseek/deepseek-v4-flash"), OPENCODE_MODEL: z.string().default("deepseek/deepseek-v4-flash"),
@@ -51,8 +55,10 @@ const envSchema = z
OPENCODE_MODEL_OPTIONS: z.string().default(defaultAgentModelOptionsJson), OPENCODE_MODEL_OPTIONS: z.string().default(defaultAgentModelOptionsJson),
// opencode skills 树目录;会在运行时解析为绝对路径,避免工具 cwd 偏移。 // opencode skills 树目录;会在运行时解析为绝对路径,避免工具 cwd 偏移。
OPENCODE_SKILLS_ROOT_DIR: z.string().default("./.opencode/skills"), OPENCODE_SKILLS_ROOT_DIR: z.string().default("./.opencode/skills"),
// client 模式下,目标 OpenCode V2 service 的基础地址。 // client 模式下,目标 opencode server 的基础地址。
OPENCODE_CLIENT_BASE_URL: z.string().url().optional(), OPENCODE_CLIENT_BASE_URL: z.string().url().optional(),
// 旧版 client 模式环境变量名,保留兼容,解析时会映射到 OPENCODE_CLIENT_BASE_URL。
OPENCODE_BASE_URL: z.string().url().optional(),
// tjwater-cli 可执行文件路径。 // tjwater-cli 可执行文件路径。
TJWATER_CLI_PATH: z.string().default("./cli/tjwater-cli"), TJWATER_CLI_PATH: z.string().default("./cli/tjwater-cli"),
// TJWater 后端 API 的基础地址。 // TJWater 后端 API 的基础地址。
@@ -152,7 +158,11 @@ const normalizedEnv = {
...process.env, ...process.env,
OPENCODE_MODE: OPENCODE_MODE:
process.env.OPENCODE_MODE ?? process.env.OPENCODE_MODE ??
(process.env.OPENCODE_CLIENT_BASE_URL ? "client" : "embedded"), (process.env.OPENCODE_CLIENT_BASE_URL || process.env.OPENCODE_BASE_URL
? "client"
: "embedded"),
OPENCODE_CLIENT_BASE_URL:
process.env.OPENCODE_CLIENT_BASE_URL ?? process.env.OPENCODE_BASE_URL,
}; };
export const config: AppConfig = envSchema.parse(normalizedEnv); export const config: AppConfig = envSchema.parse(normalizedEnv);
+286 -132
View File
@@ -1,13 +1,15 @@
import type { Event as OpencodeEvent, Part } from "@opencode-ai/sdk/v2";
import { writeLlmRequestAuditLog } from "../audit/llmRequestAudit.js"; import { writeLlmRequestAuditLog } from "../audit/llmRequestAudit.js";
import { type SupportedModel } from "../chat/models.js"; import { type SupportedModel } from "../chat/models.js";
import { logger } from "../logger.js"; import { logger } from "../logger.js";
import { import {
type PermissionReply, type PermissionReply,
type OpencodeRuntimeAdapter, type OpencodeRuntimeAdapter,
type RuntimeEvent as OpencodeEvent,
} from "../runtime/opencode.js"; } from "../runtime/opencode.js";
import { import {
buildPermissionDetail, buildPermissionDetail,
buildPermissionV2Detail,
buildReasoningProgressDetail, buildReasoningProgressDetail,
buildSessionStatusDetail, buildSessionStatusDetail,
buildToolProgressDetail, buildToolProgressDetail,
@@ -20,15 +22,23 @@ import {
hasToolParams, hasToolParams,
isPermissionAskedEvent, isPermissionAskedEvent,
isPermissionRepliedEvent, isPermissionRepliedEvent,
isPermissionV2AskedEvent,
isPermissionV2RepliedEvent,
isQuestionAskedEvent, isQuestionAskedEvent,
isObjectRecord, isObjectRecord,
isQuestionRejectedEvent, isQuestionRejectedEvent,
isQuestionRepliedEvent, isQuestionRepliedEvent,
isQuestionV2AskedEvent,
isQuestionV2RejectedEvent,
isQuestionV2RepliedEvent,
isSessionEvent, isSessionEvent,
isSkillEvent, isSkillEvent,
logDevelopmentDebug, logDevelopmentDebug,
normalizeQuestionAnswers, normalizeQuestionAnswers,
normalizeQuestionPayload, normalizeQuestionPayload,
normalizeQuestionToolPayload,
normalizeTodoPriority,
normalizeTodoStatus,
normalizeToolParams, normalizeToolParams,
normalizeToolStatus, normalizeToolStatus,
type PermissionRequestPayload, type PermissionRequestPayload,
@@ -140,7 +150,10 @@ export const streamPromptResponse = async ({
const progressStartedAtMap = new Map<string, number>(); const progressStartedAtMap = new Map<string, number>();
const finalizedProgressIds = new Set<string>(); const finalizedProgressIds = new Set<string>();
const emittedToolParts = new Set<string>(); const emittedToolParts = new Set<string>();
const emittedQuestionToolParts = new Set<string>();
const emittedQuestionRequestIds = new Set<string>(); const emittedQuestionRequestIds = new Set<string>();
const partTypes = new Map<string, Part["type"]>();
const pendingPartTextDeltas = new Map<string, string[]>();
const reasoningDeltas = new Map<string, string[]>(); const reasoningDeltas = new Map<string, string[]>();
const reasoningStatuses = new Map<string, "running" | "completed">(); const reasoningStatuses = new Map<string, "running" | "completed">();
const toolStatuses = new Map<string, string>(); const toolStatuses = new Map<string, string>();
@@ -158,10 +171,6 @@ export const streamPromptResponse = async ({
let promptSettled = false; let promptSettled = false;
let aborted = signal?.aborted ?? false; let aborted = signal?.aborted ?? false;
let failed = false; let failed = false;
let pendingNextEvent: Promise<{
type: "event";
result: IteratorResult<OpencodeEvent>;
}> | null = null;
const debugContext = { const debugContext = {
sessionId, sessionId,
clientSessionId, clientSessionId,
@@ -270,7 +279,7 @@ export const streamPromptResponse = async ({
break; break;
} }
pendingNextEvent ??= iterator const nextEvent = iterator
.next() .next()
.then((result) => ({ type: "event" as const, result })); .then((result) => ({ type: "event" as const, result }));
const nextPrompt = promptSettled const nextPrompt = promptSettled
@@ -281,7 +290,7 @@ export const streamPromptResponse = async ({
); );
const next = await Promise.race( const next = await Promise.race(
[ [
...(nextPrompt ? [pendingNextEvent, nextPrompt] : [pendingNextEvent]), ...(nextPrompt ? [nextEvent, nextPrompt] : [nextEvent]),
...(abortPromise ? [abortPromise] : []), ...(abortPromise ? [abortPromise] : []),
], ],
); );
@@ -297,7 +306,6 @@ export const streamPromptResponse = async ({
if (next.type === "prompt") { if (next.type === "prompt") {
continue; continue;
} }
pendingNextEvent = null;
if (next.result.done) { if (next.result.done) {
break; break;
} }
@@ -318,10 +326,11 @@ export const streamPromptResponse = async ({
} }
if (event.type === "session.status") { if (event.type === "session.status") {
const nextStatus = event.status.type; const nextStatus = event.properties.status.type;
const nextStatusMessage = const nextStatusMessage =
"message" in event.status && typeof event.status.message === "string" "message" in event.properties.status &&
? event.status.message typeof event.properties.status.message === "string"
? event.properties.status.message
: null; : null;
if ( if (
nextStatus !== lastSessionStatus || nextStatus !== lastSessionStatus ||
@@ -339,14 +348,14 @@ export const streamPromptResponse = async ({
emitProgress({ emitProgress({
id: "session-status", id: "session-status",
phase: "session", phase: "session",
status: event.status.type === "idle" ? "completed" : "running", status: event.properties.status.type === "idle" ? "completed" : "running",
title: title:
event.status.type === "retry" event.properties.status.type === "retry"
? `模型请求重试中:${event.status.message}` ? `模型请求重试中:${event.properties.status.message}`
: event.status.type === "busy" : event.properties.status.type === "busy"
? "Agent 正在处理请求" ? "Agent 正在处理请求"
: "Agent 已空闲", : "Agent 已空闲",
detail: buildSessionStatusDetail(event.status), detail: buildSessionStatusDetail(event.properties.status),
}); });
continue; continue;
} }
@@ -365,13 +374,13 @@ export const streamPromptResponse = async ({
sawResponseActivity = true; sawResponseActivity = true;
logDevelopmentDebug("permission request received", { logDevelopmentDebug("permission request received", {
...debugContext, ...debugContext,
requestId: event.request.id, requestId: event.properties.id,
permission: event.request.action, permission: event.properties.permission,
patterns: event.request.resources, patterns: event.properties.patterns,
elapsedMs: Math.max(0, Date.now() - requestStartedAt), elapsedMs: Math.max(0, Date.now() - requestStartedAt),
}); });
emitProgress({ emitProgress({
id: `permission-${event.request.id}`, id: `permission-${event.properties.id}`,
phase: "permission", phase: "permission",
status: approvalMode === "always" ? "completed" : "running", status: approvalMode === "always" ? "completed" : "running",
title: approvalMode === "always" ? "已自动允许权限请求" : "等待权限确认", title: approvalMode === "always" ? "已自动允许权限请求" : "等待权限确认",
@@ -382,25 +391,70 @@ export const streamPromptResponse = async ({
}); });
if (approvalMode === "always") { if (approvalMode === "always") {
await runtime.replyPermission({ await runtime.replyPermission({
requestId: event.request.id, requestId: event.properties.id,
sessionId, sessionId,
reply: "always", reply: "always",
}); });
write("permission_response", { write("permission_response", {
session_id: clientSessionId, session_id: clientSessionId,
request_id: event.request.id, request_id: event.properties.id,
reply: "always" satisfies PermissionReply, reply: "always" satisfies PermissionReply,
}); });
continue; continue;
} }
write("permission_request", { write("permission_request", {
session_id: clientSessionId, session_id: clientSessionId,
request_id: event.request.id, request_id: event.properties.id,
permission: event.request.action, permission: event.properties.permission,
patterns: event.request.resources, patterns: event.properties.patterns,
target: getPermissionTarget(event.request.metadata), target: getPermissionTarget(event.properties.metadata),
always: event.request.save ?? [], always: event.properties.always,
tool: event.request.tool, tool: event.properties.tool,
created_at: Date.now(),
} satisfies PermissionRequestPayload);
continue;
}
if (isPermissionV2AskedEvent(event)) {
sawResponseActivity = true;
logDevelopmentDebug("permission v2 request received", {
...debugContext,
requestId: event.properties.id,
action: event.properties.action,
resources: event.properties.resources,
elapsedMs: Math.max(0, Date.now() - requestStartedAt),
});
emitProgress({
id: `permission-${event.properties.id}`,
phase: "permission",
status: approvalMode === "always" ? "completed" : "running",
title: approvalMode === "always" ? "已自动允许权限请求" : "等待权限确认",
detail:
approvalMode === "always"
? "当前批准模式为始终允许,已自动允许本次权限请求。"
: buildPermissionV2Detail(event),
});
if (approvalMode === "always") {
await runtime.replyPermission({
requestId: event.properties.id,
sessionId,
reply: "always",
});
write("permission_response", {
session_id: clientSessionId,
request_id: event.properties.id,
reply: "always" satisfies PermissionReply,
});
continue;
}
write("permission_request", {
session_id: clientSessionId,
request_id: event.properties.id,
permission: event.properties.action,
patterns: event.properties.resources,
target: getPermissionTarget(event.properties.metadata),
always: event.properties.save ?? [],
tool: undefined,
created_at: Date.now(), created_at: Date.now(),
} satisfies PermissionRequestPayload); } satisfies PermissionRequestPayload);
continue; continue;
@@ -410,47 +464,78 @@ export const streamPromptResponse = async ({
sawResponseActivity = true; sawResponseActivity = true;
logDevelopmentDebug("permission request replied", { logDevelopmentDebug("permission request replied", {
...debugContext, ...debugContext,
requestId: event.requestId, requestId: event.properties.requestID,
reply: event.reply, reply: event.properties.reply,
elapsedMs: Math.max(0, Date.now() - requestStartedAt), elapsedMs: Math.max(0, Date.now() - requestStartedAt),
}); });
emitProgress({ emitProgress({
id: `permission-${event.requestId}`, id: `permission-${event.properties.requestID}`,
phase: "permission", phase: "permission",
status: event.reply === "reject" ? "error" : "completed", status: event.properties.reply === "reject" ? "error" : "completed",
title: title:
event.reply === "reject" event.properties.reply === "reject"
? "权限请求已拒绝" ? "权限请求已拒绝"
: "权限请求已允许", : "权限请求已允许",
detail: detail:
event.reply === "always" event.properties.reply === "always"
? "已允许本次请求,并记住同类权限。" ? "已允许本次请求,并记住同类权限。"
: event.reply === "once" : event.properties.reply === "once"
? "已允许本次请求。" ? "已允许本次请求。"
: "已拒绝本次请求。", : "已拒绝本次请求。",
}); });
write("permission_response", { write("permission_response", {
session_id: clientSessionId, session_id: clientSessionId,
request_id: event.requestId, request_id: event.properties.requestID,
reply: event.reply satisfies PermissionReply, reply: event.properties.reply satisfies PermissionReply,
}); });
continue; continue;
} }
if (isQuestionAskedEvent(event)) { if (isPermissionV2RepliedEvent(event)) {
sawResponseActivity = true; sawResponseActivity = true;
logDevelopmentDebug("question request received", { logDevelopmentDebug("permission v2 request replied", {
...debugContext, ...debugContext,
requestId: event.request.id, requestId: event.properties.requestID,
questionCount: event.request.questions.length, reply: event.properties.reply,
elapsedMs: Math.max(0, Date.now() - requestStartedAt), elapsedMs: Math.max(0, Date.now() - requestStartedAt),
}); });
emitProgress({ emitProgress({
id: `question-${event.request.id}`, id: `permission-${event.properties.requestID}`,
phase: "permission",
status: event.properties.reply === "reject" ? "error" : "completed",
title:
event.properties.reply === "reject"
? "权限请求已拒绝"
: "权限请求已允许",
detail:
event.properties.reply === "always"
? "已允许本次请求,并记住同类权限。"
: event.properties.reply === "once"
? "已允许本次请求。"
: "已拒绝本次请求。",
});
write("permission_response", {
session_id: clientSessionId,
request_id: event.properties.requestID,
reply: event.properties.reply satisfies PermissionReply,
});
continue;
}
if (isQuestionAskedEvent(event) || isQuestionV2AskedEvent(event)) {
sawResponseActivity = true;
logDevelopmentDebug("question request received", {
...debugContext,
requestId: event.properties.id,
questionCount: event.properties.questions.length,
elapsedMs: Math.max(0, Date.now() - requestStartedAt),
});
emitProgress({
id: `question-${event.properties.id}`,
phase: "question", phase: "question",
status: "running", status: "running",
title: "等待用户补充信息", title: "等待用户补充信息",
detail: event.request.questions detail: event.properties.questions
.map((question) => question.question) .map((question) => question.question)
.join("\n"), .join("\n"),
}); });
@@ -460,40 +545,40 @@ export const streamPromptResponse = async ({
continue; continue;
} }
if (isQuestionRepliedEvent(event)) { if (isQuestionRepliedEvent(event) || isQuestionV2RepliedEvent(event)) {
sawResponseActivity = true; sawResponseActivity = true;
logDevelopmentDebug("question request replied", { logDevelopmentDebug("question request replied", {
...debugContext, ...debugContext,
requestId: event.requestId, requestId: event.properties.requestID,
elapsedMs: Math.max(0, Date.now() - requestStartedAt), elapsedMs: Math.max(0, Date.now() - requestStartedAt),
}); });
emitProgress({ emitProgress({
id: `question-${event.requestId}`, id: `question-${event.properties.requestID}`,
phase: "question", phase: "question",
status: "completed", status: "completed",
title: "已收到补充信息", title: "已收到补充信息",
detail: normalizeQuestionAnswers(event.answers) detail: normalizeQuestionAnswers(event.properties.answers)
.map((answer) => answer.join("、")) .map((answer) => answer.join("、"))
.filter(Boolean) .filter(Boolean)
.join("\n"), .join("\n"),
}); });
write("question_response", { write("question_response", {
session_id: clientSessionId, session_id: clientSessionId,
request_id: event.requestId, request_id: event.properties.requestID,
answers: normalizeQuestionAnswers(event.answers), answers: normalizeQuestionAnswers(event.properties.answers),
}); });
continue; continue;
} }
if (isQuestionRejectedEvent(event)) { if (isQuestionRejectedEvent(event) || isQuestionV2RejectedEvent(event)) {
sawResponseActivity = true; sawResponseActivity = true;
logDevelopmentDebug("question request rejected", { logDevelopmentDebug("question request rejected", {
...debugContext, ...debugContext,
requestId: event.requestId, requestId: event.properties.requestID,
elapsedMs: Math.max(0, Date.now() - requestStartedAt), elapsedMs: Math.max(0, Date.now() - requestStartedAt),
}); });
emitProgress({ emitProgress({
id: `question-${event.requestId}`, id: `question-${event.properties.requestID}`,
phase: "question", phase: "question",
status: "completed", status: "completed",
title: "已跳过补充信息", title: "已跳过补充信息",
@@ -501,7 +586,7 @@ export const streamPromptResponse = async ({
}); });
write("question_response", { write("question_response", {
session_id: clientSessionId, session_id: clientSessionId,
request_id: event.requestId, request_id: event.properties.requestID,
rejected: true, rejected: true,
}); });
continue; continue;
@@ -532,13 +617,22 @@ export const streamPromptResponse = async ({
}); });
} }
if (event.type === "text.delta") { if (event.type === "message.updated") {
if (event.properties.info.role === "assistant") {
sawResponseActivity = true; sawResponseActivity = true;
}
continue;
}
if (event.type === "message.part.delta" && event.properties.field === "text") {
sawResponseActivity = true;
const partType = partTypes.get(event.properties.partID);
if (partType === "text") {
if (!firstTokenLogged) { if (!firstTokenLogged) {
firstTokenLogged = true; firstTokenLogged = true;
logDevelopmentDebug("first response token emitted", { logDevelopmentDebug("first response token emitted", {
...debugContext, ...debugContext,
partId: event.partId, partId: event.properties.partID,
elapsedMs: Math.max(0, Date.now() - requestStartedAt), elapsedMs: Math.max(0, Date.now() - requestStartedAt),
sincePromptDispatchMs: Math.max(0, Date.now() - promptStartedAt), sincePromptDispatchMs: Math.max(0, Date.now() - promptStartedAt),
}); });
@@ -546,54 +640,74 @@ export const streamPromptResponse = async ({
emittedText = true; emittedText = true;
write("token", { write("token", {
session_id: clientSessionId, session_id: clientSessionId,
content: event.delta, content: event.properties.delta,
}); });
continue; } else if (partType === "reasoning") {
}
if (event.type === "reasoning.updated") {
sawResponseActivity = true;
if (!firstReasoningLogged) { if (!firstReasoningLogged) {
firstReasoningLogged = true; firstReasoningLogged = true;
logDevelopmentDebug("first reasoning delta received", { logDevelopmentDebug("first reasoning delta received", {
...debugContext, ...debugContext,
partId: event.partId, partId: event.properties.partID,
elapsedMs: Math.max(0, Date.now() - requestStartedAt), elapsedMs: Math.max(0, Date.now() - requestStartedAt),
sincePromptDispatchMs: Math.max(0, Date.now() - promptStartedAt), sincePromptDispatchMs: Math.max(0, Date.now() - promptStartedAt),
}); });
} }
const pending = reasoningDeltas.get(event.partId) ?? []; const pending = reasoningDeltas.get(event.properties.partID) ?? [];
if (event.delta) { pending.push(event.properties.delta);
pending.push(event.delta); reasoningDeltas.set(event.properties.partID, pending);
reasoningDeltas.set(event.partId, pending); } else if (!partType) {
const pending = pendingPartTextDeltas.get(event.properties.partID) ?? [];
pending.push(event.properties.delta);
pendingPartTextDeltas.set(event.properties.partID, pending);
} }
const reasoningStatus = event.completed ? "completed" : "running";
if (reasoningStatuses.get(event.partId) !== reasoningStatus) {
reasoningStatuses.set(event.partId, reasoningStatus);
logDevelopmentDebug("reasoning part status changed", {
...debugContext,
partId: event.partId,
status: reasoningStatus,
chunkCount: pending.length,
elapsedMs: Math.max(0, Date.now() - requestStartedAt),
});
}
emitProgress({
id: event.partId,
phase: "planning",
status: event.completed ? "completed" : "running",
title: event.completed ? "分析规划完成" : "正在规划分析步骤",
detail: buildReasoningProgressDetail(
reasoningDeltas.get(event.partId) ?? [],
event.completed ? Date.now() : undefined,
),
});
continue; continue;
} }
if (event.type === "tool.updated") { if (event.type === "message.part.updated") {
sawResponseActivity = true; sawResponseActivity = true;
const part = event.part; const part = event.properties.part;
partTypes.set(part.id, part.type);
if (part.type === "text") {
const pending = pendingPartTextDeltas.get(part.id) ?? [];
pendingPartTextDeltas.delete(part.id);
for (const content of pending) {
emittedText = true;
write("token", {
session_id: clientSessionId,
content,
});
}
} else if (part.type === "reasoning") {
const pending = pendingPartTextDeltas.get(part.id) ?? [];
if (pending.length > 0) {
const existing = reasoningDeltas.get(part.id) ?? [];
reasoningDeltas.set(part.id, existing.concat(pending));
}
pendingPartTextDeltas.delete(part.id);
const reasoningStatus = part.time.end ? "completed" : "running";
if (reasoningStatuses.get(part.id) !== reasoningStatus) {
reasoningStatuses.set(part.id, reasoningStatus);
logDevelopmentDebug("reasoning part status changed", {
...debugContext,
partId: part.id,
status: reasoningStatus,
chunkCount: (reasoningDeltas.get(part.id) ?? []).length,
elapsedMs: Math.max(0, Date.now() - requestStartedAt),
});
}
const reasoningDetail = buildReasoningProgressDetail(
reasoningDeltas.get(part.id) ?? [],
part.time.end,
);
emitProgress({
id: part.id,
phase: "planning",
status: part.time.end ? "completed" : "running",
title: part.time.end ? "分析规划完成" : "正在规划分析步骤",
detail: reasoningDetail,
});
}
if (part.type === "tool") {
if (!firstToolEventLogged) { if (!firstToolEventLogged) {
firstToolEventLogged = true; firstToolEventLogged = true;
logDevelopmentDebug("first tool event received", { logDevelopmentDebug("first tool event received", {
@@ -626,9 +740,33 @@ export const streamPromptResponse = async ({
}); });
} }
if (part.tool === "question" || part.tool === "request_user_input") { const questionToolPayload = normalizeQuestionToolPayload(
// V2 emits the actionable form.created/question.asked event separately. part,
// Do not expose the tool callID as a request ID: it cannot settle a form. toolParams,
clientSessionId,
);
if (questionToolPayload) {
if (!emittedQuestionToolParts.has(part.id)) {
emittedQuestionToolParts.add(part.id);
emittedQuestionRequestIds.add(questionToolPayload.request_id);
logDevelopmentDebug("question tool request received", {
...debugContext,
requestId: questionToolPayload.request_id,
tool: part.tool,
questionCount: questionToolPayload.questions.length,
elapsedMs: Math.max(0, Date.now() - requestStartedAt),
});
emitProgress({
id: `question-${questionToolPayload.request_id}`,
phase: "question",
status: "running",
title: "等待用户补充信息",
detail: questionToolPayload.questions
.map((question) => question.question)
.join("\n"),
});
write("question_request", questionToolPayload);
}
continue; continue;
} }
@@ -655,7 +793,7 @@ export const streamPromptResponse = async ({
logger.warn( logger.warn(
{ {
tool: part.tool, tool: part.tool,
sessionId, sessionId: sessionId,
clientSessionId, clientSessionId,
}, },
"llm tool request missing reason", "llm tool request missing reason",
@@ -663,7 +801,7 @@ export const streamPromptResponse = async ({
} }
void writeLlmRequestAuditLog({ void writeLlmRequestAuditLog({
kind: "tool", kind: "tool",
sessionId, sessionId: sessionId,
clientSessionId, clientSessionId,
traceId, traceId,
projectId, projectId,
@@ -681,60 +819,59 @@ export const streamPromptResponse = async ({
reason, reason,
}); });
} }
}
continue; continue;
} }
if (event.type === "session.execution.started") { if (event.type === "todo.updated") {
sawResponseActivity = true;
const todos = event.properties.todos as Array<{
content: string;
status: string;
priority: string;
}>;
const normalizedTodos = todos.map((todo, index) => ({
id: `todo-${index}-${todo.content.slice(0, 24)}`,
content: todo.content,
status: normalizeTodoStatus(todo.status),
priority: normalizeTodoPriority(todo.priority),
updated_at: Date.now(),
}));
const completed = todos.filter(
(todo) => todo.status === "completed",
).length;
emitProgress({ emitProgress({
id: "session-status", id: "todo-progress",
phase: "session", phase: "planning",
status: "running", status: completed === todos.length ? "completed" : "running",
title: "Agent 正在处理", title: `计划进度 ${completed}/${todos.length}`,
detail: "OpenCode 已开始执行当前请求。", detail: todos
.map((todo) => `${todo.status}: ${todo.content}`)
.join("\n"),
}); });
write("todo_update", {
session_id: clientSessionId,
todos: normalizedTodos,
created_at: Date.now(),
} satisfies TodoUpdatePayload);
continue; continue;
} }
if (event.type === "session.execution.succeeded") { if (event.type === "session.error") {
emitProgress({
id: "session-status",
phase: "session",
status: "completed",
title: "Agent 已完成处理",
detail: "当前请求已执行完成,正在整理并返回结果。",
});
done = true;
continue;
}
if (event.type === "session.execution.failed" || event.type === "session.error") {
sawResponseActivity = true; sawResponseActivity = true;
const runtimeError = event.error;
logDevelopmentDebug("session error received", { logDevelopmentDebug("session error received", {
...debugContext, ...debugContext,
elapsedMs: Math.max(0, Date.now() - requestStartedAt), elapsedMs: Math.max(0, Date.now() - requestStartedAt),
error: runtimeError error: event.properties.error
? getErrorMessage(runtimeError) ? getErrorMessage(event.properties.error)
: "opencode session error", : "opencode session error",
}); });
write("error", { write("error", {
session_id: clientSessionId, session_id: clientSessionId,
message: runtimeError message: event.properties.error
? getErrorMessage(runtimeError) ? getErrorMessage(event.properties.error)
: "opencode session error", : "opencode session error",
detail: runtimeError?.name, detail: event.properties.error?.name,
total_duration_ms: Math.max(0, Date.now() - requestStartedAt),
});
failed = true;
done = true;
continue;
}
if (event.type === "session.execution.interrupted") {
write("error", {
session_id: clientSessionId,
message: `OpenCode execution interrupted: ${event.reason}`,
detail: "SessionExecutionInterrupted",
total_duration_ms: Math.max(0, Date.now() - requestStartedAt), total_duration_ms: Math.max(0, Date.now() - requestStartedAt),
}); });
failed = true; failed = true;
@@ -743,10 +880,27 @@ export const streamPromptResponse = async ({
} }
if (event.type === "session.idle") { if (event.type === "session.idle") {
logDevelopmentDebug("ignoring legacy session.idle event", { if (!sawResponseActivity) {
logDevelopmentDebug("ignoring session idle before response activity", {
...debugContext, ...debugContext,
elapsedMs: Math.max(0, Date.now() - requestStartedAt), elapsedMs: Math.max(0, Date.now() - requestStartedAt),
}); });
continue;
}
logDevelopmentDebug("session idle received", {
...debugContext,
emittedText,
toolCallCount,
elapsedMs: Math.max(0, Date.now() - requestStartedAt),
});
emitProgress({
id: "session-status",
phase: "session",
status: "completed",
title: "Agent 已完成处理",
detail: "当前会话已无待执行任务,正在收尾并准备返回最终结果。",
});
done = true;
} }
} }
+138 -38
View File
@@ -1,9 +1,7 @@
import type { Event as OpencodeEvent, Part } from "@opencode-ai/sdk/v2";
import { logger } from "../logger.js"; import { logger } from "../logger.js";
import { import { type QuestionAnswers } from "../runtime/opencode.js";
type QuestionAnswers,
type RuntimeEvent,
type RuntimePart,
} from "../runtime/opencode.js";
export type PermissionRequestPayload = { export type PermissionRequestPayload = {
session_id: string; session_id: string;
@@ -141,16 +139,24 @@ export const extractRequestReason = (params: Record<string, unknown>) => {
return ""; return "";
}; };
export const isSkillEvent = (event: RuntimeEvent) => export const isSkillEvent = (event: OpencodeEvent) =>
event.type === "skill.activated"; event.type.toLowerCase().includes("skill");
export const extractSkillAuditInfo = ( export const extractSkillAuditInfo = (event: OpencodeEvent) => {
event: Extract<RuntimeEvent, { type: "skill.activated" }>, const payload = isObjectRecord(event.properties)
) => { ? (event.properties as Record<string, unknown>)
const payload = event.payload; : {};
const candidateName =
typeof payload.skill === "string"
? payload.skill
: typeof payload.skillName === "string"
? payload.skillName
: typeof payload.name === "string"
? payload.name
: event.type;
const reason = extractRequestReason(payload); const reason = extractRequestReason(payload);
return { return {
name: event.name, name: candidateName,
reason, reason,
payload, payload,
}; };
@@ -159,50 +165,88 @@ export const extractSkillAuditInfo = (
export const hasToolParams = (params: Record<string, unknown>) => export const hasToolParams = (params: Record<string, unknown>) =>
Object.keys(params).length > 0; Object.keys(params).length > 0;
export const isSessionEvent = (event: RuntimeEvent, sessionId: string) => export const isSessionEvent = (event: OpencodeEvent, sessionId: string) =>
event.sessionId === sessionId; "properties" in event &&
typeof event.properties === "object" &&
event.properties !== null &&
"sessionID" in event.properties &&
event.properties.sessionID === sessionId;
export const isPermissionAskedEvent = ( export const isPermissionAskedEvent = (
event: RuntimeEvent, event: OpencodeEvent,
): event is Extract<RuntimeEvent, { type: "permission.asked" }> => ): event is Extract<OpencodeEvent, { type: "permission.asked" }> =>
event.type === "permission.asked"; event.type === "permission.asked";
export const isPermissionV2AskedEvent = (
event: OpencodeEvent,
): event is Extract<OpencodeEvent, { type: "permission.v2.asked" }> =>
event.type === "permission.v2.asked";
export const isPermissionRepliedEvent = ( export const isPermissionRepliedEvent = (
event: RuntimeEvent, event: OpencodeEvent,
): event is Extract<RuntimeEvent, { type: "permission.replied" }> => ): event is Extract<OpencodeEvent, { type: "permission.replied" }> =>
event.type === "permission.replied"; event.type === "permission.replied";
export const isPermissionV2RepliedEvent = (
event: OpencodeEvent,
): event is Extract<OpencodeEvent, { type: "permission.v2.replied" }> =>
event.type === "permission.v2.replied";
export const isQuestionAskedEvent = ( export const isQuestionAskedEvent = (
event: RuntimeEvent, event: OpencodeEvent,
): event is Extract<RuntimeEvent, { type: "question.asked" }> => ): event is Extract<OpencodeEvent, { type: "question.asked" }> =>
event.type === "question.asked"; event.type === "question.asked";
export const isQuestionV2AskedEvent = (
event: OpencodeEvent,
): event is Extract<OpencodeEvent, { type: "question.v2.asked" }> =>
event.type === "question.v2.asked";
export const isQuestionRepliedEvent = ( export const isQuestionRepliedEvent = (
event: RuntimeEvent, event: OpencodeEvent,
): event is Extract<RuntimeEvent, { type: "question.replied" }> => ): event is Extract<OpencodeEvent, { type: "question.replied" }> =>
event.type === "question.replied"; event.type === "question.replied";
export const isQuestionV2RepliedEvent = (
event: OpencodeEvent,
): event is Extract<OpencodeEvent, { type: "question.v2.replied" }> =>
event.type === "question.v2.replied";
export const isQuestionRejectedEvent = ( export const isQuestionRejectedEvent = (
event: RuntimeEvent, event: OpencodeEvent,
): event is Extract<RuntimeEvent, { type: "question.rejected" }> => ): event is Extract<OpencodeEvent, { type: "question.rejected" }> =>
event.type === "question.rejected"; event.type === "question.rejected";
export const isQuestionV2RejectedEvent = (
event: OpencodeEvent,
): event is Extract<OpencodeEvent, { type: "question.v2.rejected" }> =>
event.type === "question.v2.rejected";
export const buildPermissionDetail = ( export const buildPermissionDetail = (
event: Extract<RuntimeEvent, { type: "permission.asked" }>, event: Extract<OpencodeEvent, { type: "permission.asked" }>,
) => { ) => {
const resources = event.request.resources.length const patterns = event.properties.patterns.length
? event.request.resources.join(", ") ? event.properties.patterns.join(", ")
: event.request.action; : event.properties.permission;
return `需要用户确认权限:${event.request.action}资源${resources}`; return `需要用户确认权限:${event.properties.permission}匹配规则${patterns}`;
};
export const buildPermissionV2Detail = (
event: Extract<OpencodeEvent, { type: "permission.v2.asked" }>,
) => {
const resources = event.properties.resources.length
? event.properties.resources.join(", ")
: event.properties.action;
return `需要用户确认权限:${event.properties.action};资源:${resources}`;
}; };
export const normalizeQuestionPayload = ( export const normalizeQuestionPayload = (
event: Extract<RuntimeEvent, { type: "question.asked" }>, event: Extract<OpencodeEvent, { type: "question.asked" | "question.v2.asked" }>,
clientSessionId: string, clientSessionId: string,
): QuestionRequestPayload => ({ ): QuestionRequestPayload => ({
session_id: clientSessionId, session_id: clientSessionId,
request_id: event.request.id, request_id: event.properties.id,
questions: event.request.questions.map((question) => ({ questions: event.properties.questions.map((question) => ({
header: question.header, header: question.header,
question: question.question, question: question.question,
options: question.options.map((option) => ({ options: question.options.map((option) => ({
@@ -212,7 +256,7 @@ export const normalizeQuestionPayload = (
multiple: question.multiple, multiple: question.multiple,
custom: question.custom, custom: question.custom,
})), })),
tool: event.request.tool, tool: event.properties.tool,
created_at: Date.now(), created_at: Date.now(),
}); });
@@ -225,6 +269,65 @@ export const normalizeQuestionAnswers = (answers: QuestionAnswers | undefined) =
) )
: []; : [];
const questionToolNames = new Set(["question", "request_user_input"]);
const normalizeQuestionOptions = (value: unknown): QuestionOptionPayload[] =>
Array.isArray(value)
? value.filter(isObjectRecord).map((option) => ({
label: typeof option.label === "string" ? option.label : "",
description:
typeof option.description === "string" ? option.description : "",
})).filter((option) => option.label.trim().length > 0)
: [];
const normalizeToolQuestionInfo = (value: unknown): QuestionInfoPayload | undefined => {
if (!isObjectRecord(value) || typeof value.question !== "string") {
return undefined;
}
const question = value.question.trim();
if (!question) {
return undefined;
}
return {
header:
typeof value.header === "string" && value.header.trim()
? value.header
: "补充信息",
question,
options: normalizeQuestionOptions(value.options),
multiple: typeof value.multiple === "boolean" ? value.multiple : undefined,
custom: typeof value.custom === "boolean" ? value.custom : undefined,
};
};
export const normalizeQuestionToolPayload = (
part: Extract<Part, { type: "tool" }>,
params: Record<string, unknown>,
clientSessionId: string,
): QuestionRequestPayload | undefined => {
if (!questionToolNames.has(part.tool)) {
return undefined;
}
const questions = Array.isArray(params.questions)
? params.questions
.map(normalizeToolQuestionInfo)
.filter((question): question is QuestionInfoPayload => Boolean(question))
: [];
if (questions.length === 0) {
return undefined;
}
return {
session_id: clientSessionId,
request_id: part.callID || part.id,
questions,
tool: {
messageID: part.messageID,
callID: part.callID,
},
created_at: Date.now(),
};
};
export const normalizeTodoStatus = (status: string): TodoItemPayload["status"] => { export const normalizeTodoStatus = (status: string): TodoItemPayload["status"] => {
if (status === "in_progress" || status === "completed" || status === "cancelled") { if (status === "in_progress" || status === "completed" || status === "cancelled") {
return status; return status;
@@ -241,12 +344,9 @@ export const normalizeTodoPriority = (
return undefined; return undefined;
}; };
export const collectTextContent = (parts: RuntimePart[]) => export const collectTextContent = (parts: Part[]) =>
parts parts
.filter( .filter((part): part is Extract<Part, { type: "text" }> => part.type === "text")
(part): part is Extract<RuntimePart, { type: "text" }> =>
part.type === "text",
)
.map((part) => part.text) .map((part) => part.text)
.join(""); .join("");
+332 -603
View File
File diff suppressed because it is too large Load Diff
+11 -2
View File
@@ -54,6 +54,8 @@ app.get("/health", async (_req, res) => {
const runtime = await opencodeRuntime.health(); const runtime = await opencodeRuntime.health();
res.json({ res.json({
ok: true, ok: true,
ready: true,
warmed_up: true,
runtime, runtime,
sessions: sessionBridge.count(), sessions: sessionBridge.count(),
}); });
@@ -61,6 +63,8 @@ app.get("/health", async (_req, res) => {
const detail = error instanceof Error ? error.message : String(error); const detail = error instanceof Error ? error.message : String(error);
res.status(503).json({ res.status(503).json({
ok: false, ok: false,
ready: false,
warmed_up: true,
message: "opencode runtime unavailable", message: "opencode runtime unavailable",
detail, detail,
sessions: sessionBridge.count(), sessions: sessionBridge.count(),
@@ -520,7 +524,12 @@ resultReferenceStore.startCleanupLoop();
const server = app.listen(config.PORT, config.HOST, () => { const server = app.listen(config.PORT, config.HOST, () => {
logger.info( logger.info(
{ host: config.HOST, port: config.PORT }, {
host: config.HOST,
port: config.PORT,
ready: true,
warmedUp: true,
},
"TJWaterAgent listening", "TJWaterAgent listening",
); );
}); });
@@ -529,7 +538,7 @@ const shutdown = async () => {
logger.info("shutting down TJWaterAgent"); logger.info("shutting down TJWaterAgent");
server.close(); server.close();
resultReferenceStore.stopCleanupLoop(); resultReferenceStore.stopCleanupLoop();
// 同步关闭 embedded OpenCode V2 service,避免本服务退出后留下孤儿进程。 // 同步关闭 embedded opencode server,避免本服务退出后留下孤儿进程。
await opencodeRuntime.dispose(); await opencodeRuntime.dispose();
}; };
+8 -11
View File
@@ -7,9 +7,6 @@ import { createSkillManagerTool } from "../../.opencode/tools/skill_manager.js";
import { type RuntimeSessionContext } from "../../src/runtime/sessionContext.js"; import { type RuntimeSessionContext } from "../../src/runtime/sessionContext.js";
import { SkillStore } from "../../src/skills/store.js"; import { SkillStore } from "../../src/skills/store.js";
const toolContent = async (result: Promise<{ content?: string }>) =>
(await result).content ?? "";
describe("skill_manager tool", () => { describe("skill_manager tool", () => {
let tempDir: string; let tempDir: string;
let skillStore: SkillStore; let skillStore: SkillStore;
@@ -64,7 +61,7 @@ describe("skill_manager tool", () => {
); );
const writeResult = JSON.parse( const writeResult = JSON.parse(
await toolContent(tool.execute( await tool.execute(
{ {
action: "write_skill", action: "write_skill",
content: skillDocument("# Pressure Review"), content: skillDocument("# Pressure Review"),
@@ -72,7 +69,7 @@ describe("skill_manager tool", () => {
skill_path: "workflow/pressure-review", skill_path: "workflow/pressure-review",
}, },
toolContext, toolContext,
)), ) as string,
); );
expect(writeResult.decision).toBe("accepted"); expect(writeResult.decision).toBe("accepted");
await expect(readFile(writeResult.target, "utf8")).resolves.toContain( await expect(readFile(writeResult.target, "utf8")).resolves.toContain(
@@ -80,7 +77,7 @@ describe("skill_manager tool", () => {
); );
const updateResult = JSON.parse( const updateResult = JSON.parse(
await toolContent(tool.execute( await tool.execute(
{ {
action: "write_skill", action: "write_skill",
content: skillDocument("# Updated Pressure Review"), content: skillDocument("# Updated Pressure Review"),
@@ -88,7 +85,7 @@ describe("skill_manager tool", () => {
skill_path: "workflow/pressure-review", skill_path: "workflow/pressure-review",
}, },
toolContext, toolContext,
)), ) as string,
); );
expect(updateResult.decision).toBe("accepted"); expect(updateResult.decision).toBe("accepted");
await expect(readFile(updateResult.target, "utf8")).resolves.toContain( await expect(readFile(updateResult.target, "utf8")).resolves.toContain(
@@ -96,14 +93,14 @@ describe("skill_manager tool", () => {
); );
const removeResult = JSON.parse( const removeResult = JSON.parse(
await toolContent(tool.execute( await tool.execute(
{ {
action: "remove_skill", action: "remove_skill",
reason: "workflow is obsolete", reason: "workflow is obsolete",
skill_path: "workflow/pressure-review", skill_path: "workflow/pressure-review",
}, },
toolContext, toolContext,
)), ) as string,
); );
expect(removeResult.decision).toBe("accepted"); expect(removeResult.decision).toBe("accepted");
await expect(readFile(removeResult.target, "utf8")).rejects.toThrow(); await expect(readFile(removeResult.target, "utf8")).rejects.toThrow();
@@ -117,7 +114,7 @@ describe("skill_manager tool", () => {
); );
const writeResult = JSON.parse( const writeResult = JSON.parse(
await toolContent(tool.execute( await tool.execute(
{ {
action: "write_skill", action: "write_skill",
content: [ content: [
@@ -132,7 +129,7 @@ describe("skill_manager tool", () => {
skill_path: "__root__", skill_path: "__root__",
}, },
toolContext, toolContext,
)), ) as string,
); );
expect(writeResult.decision).toBe("accepted"); expect(writeResult.decision).toBe("accepted");
+84 -146
View File
@@ -21,18 +21,20 @@ describe("streamPromptResponse", () => {
createEventStream([ createEventStream([
{ {
type: "permission.asked", type: "permission.asked",
sessionId: "runtime-session-1", properties: {
request: {
id: "perm-1", id: "perm-1",
action: "shell", sessionID: "runtime-session-1",
resources: ["rm *"], permission: "bash",
patterns: ["rm *"],
metadata: { command: "rm tmp.txt" }, metadata: { command: "rm tmp.txt" },
save: ["rm *"], always: ["rm *"],
}, },
}, },
{ {
type: "session.execution.succeeded", type: "session.idle",
sessionId: "runtime-session-1", properties: {
sessionID: "runtime-session-1",
},
}, },
]), ]),
prompt: async () => undefined, prompt: async () => undefined,
@@ -52,7 +54,7 @@ describe("streamPromptResponse", () => {
expect(permissionEvent?.data).toMatchObject({ expect(permissionEvent?.data).toMatchObject({
session_id: "client-session-1", session_id: "client-session-1",
request_id: "perm-1", request_id: "perm-1",
permission: "shell", permission: "bash",
patterns: ["rm *"], patterns: ["rm *"],
target: "rm tmp.txt", target: "rm tmp.txt",
always: ["rm *"], always: ["rm *"],
@@ -66,18 +68,20 @@ describe("streamPromptResponse", () => {
createEventStream([ createEventStream([
{ {
type: "permission.asked", type: "permission.asked",
sessionId: "runtime-session-1", properties: {
request: {
id: "perm-1", id: "perm-1",
action: "shell", sessionID: "runtime-session-1",
resources: ["npm test"], permission: "bash",
patterns: ["npm test"],
metadata: { command: "npm test" }, metadata: { command: "npm test" },
save: ["npm test"], always: ["npm test"],
}, },
}, },
{ {
type: "session.execution.succeeded", type: "session.idle",
sessionId: "runtime-session-1", properties: {
sessionID: "runtime-session-1",
},
}, },
]), ]),
prompt: async () => undefined, prompt: async () => undefined,
@@ -112,15 +116,15 @@ describe("streamPromptResponse", () => {
}); });
}); });
it("forwards external-directory V2 permission requests as SSE payloads", async () => { it("forwards opencode v2 permission requests as SSE payloads", async () => {
const runtime = { const runtime = {
subscribeEvents: async () => subscribeEvents: async () =>
createEventStream([ createEventStream([
{ {
type: "permission.asked", type: "permission.v2.asked",
sessionId: "runtime-session-1", properties: {
request: {
id: "perm-v2-1", id: "perm-v2-1",
sessionID: "runtime-session-1",
action: "external_directory", action: "external_directory",
resources: ["/tmp"], resources: ["/tmp"],
save: ["/tmp"], save: ["/tmp"],
@@ -128,8 +132,10 @@ describe("streamPromptResponse", () => {
}, },
}, },
{ {
type: "session.execution.succeeded", type: "session.idle",
sessionId: "runtime-session-1", properties: {
sessionID: "runtime-session-1",
},
}, },
]), ]),
prompt: async () => undefined, prompt: async () => undefined,
@@ -162,9 +168,9 @@ describe("streamPromptResponse", () => {
createEventStream([ createEventStream([
{ {
type: "question.asked", type: "question.asked",
sessionId: "runtime-session-1", properties: {
request: {
id: "question-1", id: "question-1",
sessionID: "runtime-session-1",
questions: [ questions: [
{ {
header: "范围", header: "范围",
@@ -178,13 +184,17 @@ describe("streamPromptResponse", () => {
}, },
{ {
type: "question.replied", type: "question.replied",
sessionId: "runtime-session-1", properties: {
requestId: "question-1", sessionID: "runtime-session-1",
requestID: "question-1",
answers: [["城区", "补充说明"]], answers: [["城区", "补充说明"]],
}, },
},
{ {
type: "session.execution.succeeded", type: "session.idle",
sessionId: "runtime-session-1", properties: {
sessionID: "runtime-session-1",
},
}, },
]), ]),
prompt: async () => undefined, prompt: async () => undefined,
@@ -220,16 +230,19 @@ describe("streamPromptResponse", () => {
}); });
}); });
it("waits for the Form event instead of exposing the question tool call ID", async () => { it("converts question tool parts into question request SSE payloads", async () => {
const runtime = { const runtime = {
subscribeEvents: async () => subscribeEvents: async () =>
createEventStream([ createEventStream([
{ {
type: "tool.updated", type: "message.part.updated",
sessionId: "runtime-session-1", properties: {
sessionID: "runtime-session-1",
part: { part: {
id: "tool-part-1", id: "tool-part-1",
sessionID: "runtime-session-1",
messageID: "message-1", messageID: "message-1",
type: "tool",
callID: "call-1", callID: "call-1",
tool: "question", tool: "question",
state: { state: {
@@ -248,31 +261,17 @@ describe("streamPromptResponse", () => {
}, },
], ],
}, },
time: { start: Date.now() },
}, },
}, },
}, time: Date.now(),
{
type: "question.asked",
sessionId: "runtime-session-1",
request: {
id: "frm_1",
questions: [
{
header: "测试问题",
question: "你觉得这个 question 工具好用吗?",
options: [
{
label: "非常好用",
description: "交互清晰,选项方便",
},
],
},
],
}, },
}, },
{ {
type: "session.execution.succeeded", type: "session.idle",
sessionId: "runtime-session-1", properties: {
sessionID: "runtime-session-1",
},
}, },
]), ]),
prompt: async () => undefined, prompt: async () => undefined,
@@ -290,7 +289,7 @@ describe("streamPromptResponse", () => {
expect(events.find((item) => item.event === "question_request")?.data).toMatchObject({ expect(events.find((item) => item.event === "question_request")?.data).toMatchObject({
session_id: "client-session-1", session_id: "client-session-1",
request_id: "frm_1", request_id: "call-1",
questions: [ questions: [
{ {
header: "测试问题", header: "测试问题",
@@ -303,12 +302,11 @@ describe("streamPromptResponse", () => {
], ],
}, },
], ],
tool: {
messageID: "message-1",
callID: "call-1",
},
}); });
expect(
events.some(
(item) => item.event === "question_request" && item.data.request_id === "call-1",
),
).toBe(false);
expect( expect(
events.some( events.some(
(item) => item.event === "tool_call" && item.data.tool === "question", (item) => item.event === "tool_call" && item.data.tool === "question",
@@ -316,32 +314,25 @@ describe("streamPromptResponse", () => {
).toBe(false); ).toBe(false);
}); });
it("forwards normalized V2 text and reasoning deltas", async () => { it("forwards todo updates as structured SSE payloads and progress", async () => {
const runtime = { const runtime = {
subscribeEvents: async () => subscribeEvents: async () =>
createEventStream([ createEventStream([
{ {
type: "reasoning.updated", type: "todo.updated",
sessionId: "runtime-session-1", properties: {
partId: "reasoning-1", sessionID: "runtime-session-1",
delta: "分析中", todos: [
completed: false, { content: "分析水位", status: "completed", priority: "high" },
{ content: "生成建议", status: "in_progress", priority: "medium" },
],
},
}, },
{ {
type: "reasoning.updated", type: "session.idle",
sessionId: "runtime-session-1", properties: {
partId: "reasoning-1", sessionID: "runtime-session-1",
completed: true,
}, },
{
type: "text.delta",
sessionId: "runtime-session-1",
partId: "text-1",
delta: "完成",
},
{
type: "session.execution.succeeded",
sessionId: "runtime-session-1",
}, },
]), ]),
prompt: async () => undefined, prompt: async () => undefined,
@@ -357,83 +348,30 @@ describe("streamPromptResponse", () => {
write: (event, data) => events.push({ event, data }), write: (event, data) => events.push({ event, data }),
}); });
expect(events.find((item) => item.event === "token")?.data).toEqual({
session_id: "client-session-1",
content: "完成",
});
expect( expect(
events.find( events.find(
(item) => (item) => item.event === "progress" && item.data.id === "todo-progress",
item.event === "progress" &&
item.data.id === "reasoning-1" &&
item.data.status === "completed",
)?.data, )?.data,
).toMatchObject({ ).toMatchObject({
id: "todo-progress",
phase: "planning", phase: "planning",
title: "分析规划完成", title: "计划进度 1/2",
}); });
}); expect(events.find((item) => item.event === "todo_update")?.data).toMatchObject({
session_id: "client-session-1",
it("retains the first runtime event when prompt resolves before delivery", async () => { todos: [
const runtime = { expect.objectContaining({
subscribeEvents: async () => ({ content: "分析水位",
async *[Symbol.asyncIterator]() { status: "completed",
await Promise.resolve(); priority: "high",
yield {
type: "text.delta",
sessionId: "runtime-session-1",
partId: "text-1",
delta: "首个事件",
};
yield {
type: "session.execution.succeeded",
sessionId: "runtime-session-1",
};
},
}), }),
prompt: async () => undefined, expect.objectContaining({
messages: async () => [], content: "生成建议",
} as unknown as OpencodeRuntimeAdapter; status: "in_progress",
const events: Array<{ event: string; data: Record<string, unknown> }> = []; priority: "medium",
}),
await streamPromptResponse({ ],
runtime,
sessionId: "runtime-session-1",
clientSessionId: "client-session-1",
message: "race",
write: (event, data) => events.push({ event, data }),
});
expect(events.find((item) => item.event === "token")?.data.content).toBe("首个事件");
expect(events.find((item) => item.event === "done")?.data.session_id).toBe(
"client-session-1",
);
});
it("terminates the stream when V2 reports execution failure", async () => {
const runtime = {
subscribeEvents: async () =>
createEventStream([
{
type: "session.execution.failed",
sessionId: "runtime-session-1",
error: { name: "ExecutionError", data: { message: "执行失败" } },
},
]),
prompt: async () => undefined,
messages: async () => [],
} as unknown as OpencodeRuntimeAdapter;
const events: Array<{ event: string; data: Record<string, unknown> }> = [];
const result = await streamPromptResponse({
runtime,
sessionId: "runtime-session-1",
clientSessionId: "client-session-1",
message: "fail",
write: (event, data) => events.push({ event, data }),
});
expect(events.find((item) => item.event === "error")?.data.message).toBe("执行失败");
expect(result.failed).toBe(true);
}); });
}); });
});
+37 -80
View File
@@ -1,57 +1,34 @@
import { describe, expect, it } from "bun:test"; import { describe, expect, it } from "bun:test";
import { type OpenCodeClient } from "@opencode-ai/client"; import { type OpencodeClient } from "@opencode-ai/sdk/v2";
import { import { config } from "../../src/config.js";
getEmbeddedServicePaths, import { OpencodeRuntimeAdapter } from "../../src/runtime/opencode.js";
OpencodeRuntimeAdapter,
} from "../../src/runtime/opencode.js";
describe("getEmbeddedServicePaths", () => {
it("isolates service registrations by workspace and Agent port", () => {
const internal = getEmbeddedServicePaths("/srv/tjwater-agent", 8787);
const customer = getEmbeddedServicePaths("/srv/tjwater-agent-customer", 8787);
const secondPort = getEmbeddedServicePaths("/srv/tjwater-agent", 8788);
expect(internal.registrationFile).not.toBe(customer.registrationFile);
expect(internal.registrationFile).not.toBe(secondPort.registrationFile);
expect(internal.registrationFile).toEndWith(
"/data/opencode-service/8787/opencode/service.json",
);
});
});
const createRuntimeAdapter = ( const createRuntimeAdapter = (
messages: unknown[], messages: unknown[],
calls: { staged: string[]; committed: string[] } = { calls: {
staged: [], reverted: string[];
committed: [], removed: string[];
}, } = { reverted: [], removed: [] },
) => ) =>
Object.assign(Object.create(OpencodeRuntimeAdapter.prototype), { Object.assign(Object.create(OpencodeRuntimeAdapter.prototype), {
messages: async () => messages, messages: async () => messages,
ensureClient: async () => revertMessage: async (_sessionId: string, messageId: string) => {
({ calls.reverted.push(messageId);
session: {
revert: {
stage: async ({ messageID }: { messageID: string }) => {
calls.staged.push(messageID);
}, },
commit: async ({ sessionID }: { sessionID: string }) => { removeMessage: async (_sessionId: string, messageId: string) => {
calls.committed.push(sessionID); calls.removed.push(messageId);
}, },
},
},
}) as unknown as OpenCodeClient,
}) as OpencodeRuntimeAdapter; }) as OpencodeRuntimeAdapter;
describe("OpencodeRuntimeAdapter.revertToUserMessage", () => { describe("OpencodeRuntimeAdapter.revertToUserMessage", () => {
it("skips reverting the first user message when the runtime session is empty", async () => { it("skips reverting the first user message when the runtime session is empty", async () => {
const calls = { staged: [] as string[], committed: [] as string[] }; const calls = { reverted: [] as string[], removed: [] as string[] };
const runtime = createRuntimeAdapter([], calls); const runtime = createRuntimeAdapter([], calls);
await runtime.revertToUserMessage("session-1", { userOrdinal: 1 }); await runtime.revertToUserMessage("session-1", { userOrdinal: 1 });
expect(calls).toEqual({ staged: [], committed: [] }); expect(calls).toEqual({ reverted: [], removed: [] });
}); });
it("keeps ordinal mismatches visible when runtime messages exist", async () => { it("keeps ordinal mismatches visible when runtime messages exist", async () => {
@@ -65,8 +42,8 @@ describe("OpencodeRuntimeAdapter.revertToUserMessage", () => {
).rejects.toThrow("target user message not found to revert"); ).rejects.toThrow("target user message not found to revert");
}); });
it("stages and commits the V2 revert at the target user message", async () => { it("reverts and removes messages from the target user message onward", async () => {
const calls = { staged: [] as string[], committed: [] as string[] }; const calls = { reverted: [] as string[], removed: [] as string[] };
const runtime = createRuntimeAdapter( const runtime = createRuntimeAdapter(
[ [
{ info: { id: "user-1", role: "user" } }, { info: { id: "user-1", role: "user" } },
@@ -80,8 +57,8 @@ describe("OpencodeRuntimeAdapter.revertToUserMessage", () => {
await runtime.revertToUserMessage("session-1", { userOrdinal: 2 }); await runtime.revertToUserMessage("session-1", { userOrdinal: 2 });
expect(calls).toEqual({ expect(calls).toEqual({
staged: ["user-2"], reverted: ["user-2"],
committed: ["session-1"], removed: ["assistant-2", "user-2"],
}); });
}); });
}); });
@@ -90,10 +67,11 @@ describe("OpencodeRuntimeAdapter.ensureClient", () => {
it("retries bootstrap after a failed startup attempt", async () => { it("retries bootstrap after a failed startup attempt", async () => {
let attempts = 0; let attempts = 0;
const client = { const client = {
health: { get: async () => ({ healthy: true }) }, global: { health: async () => ({ data: { healthy: true } }) },
} as unknown as OpenCodeClient; } as unknown as OpencodeClient;
const runtime = Object.assign(Object.create(OpencodeRuntimeAdapter.prototype), { const runtime = Object.assign(Object.create(OpencodeRuntimeAdapter.prototype), {
clientPromise: null, clientPromise: null,
closeServer: null,
bootstrapClient: async () => { bootstrapClient: async () => {
attempts += 1; attempts += 1;
if (attempts === 1) { if (attempts === 1) {
@@ -110,66 +88,45 @@ describe("OpencodeRuntimeAdapter.ensureClient", () => {
}); });
describe("OpencodeRuntimeAdapter.warmup", () => { describe("OpencodeRuntimeAdapter.warmup", () => {
it("rejects a service that is not the pinned V2 release", async () => { it("initializes the project session and model tools before reporting ready", async () => {
const client = {
health: {
get: async () => ({ healthy: true, version: "1.18.12" }),
},
} as unknown as OpenCodeClient;
const runtime = Object.assign(Object.create(OpencodeRuntimeAdapter.prototype), {
clientPromise: null,
ensureClient: async () => client,
}) as OpencodeRuntimeAdapter;
await expect(runtime.warmup()).rejects.toThrow(
"incompatible OpenCode service version",
);
});
it("checks V2 health, catalogs and plugins, then removes the probe session", async () => {
const calls: string[] = []; const calls: string[] = [];
const client = { const client = {
health: { global: {
get: async () => { health: async () => {
calls.push("health.get"); calls.push("health");
return { healthy: true, version: "0.0.0-next-16741" }; return { data: { healthy: true, version: "test" } };
}, },
}, },
session: { session: {
create: async () => { create: async () => {
calls.push("session.create"); calls.push("session.create");
return { id: "warmup-session" }; return { data: { id: "warmup-session" } };
}, },
remove: async ({ sessionID }: { sessionID: string }) => { delete: async ({ sessionID }: { sessionID: string }) => {
calls.push(`session.remove:${sessionID}`); calls.push(`session.delete:${sessionID}`);
return { data: true };
}, },
}, },
model: { tool: {
list: async () => { list: async (model: { provider: string; model: string }) => {
calls.push("model.list"); calls.push(`tool.list:${model.provider}/${model.model}`);
return { data: [] }; return { data: [] };
}, },
}, },
plugin: { } as unknown as OpencodeClient;
list: async () => {
calls.push("plugin.list");
return [];
},
},
} as unknown as OpenCodeClient;
const runtime = Object.assign(Object.create(OpencodeRuntimeAdapter.prototype), { const runtime = Object.assign(Object.create(OpencodeRuntimeAdapter.prototype), {
clientPromise: null, clientPromise: null,
closeServer: null,
ensureClient: async () => client, ensureClient: async () => client,
}) as OpencodeRuntimeAdapter; }) as OpencodeRuntimeAdapter;
await runtime.warmup(); await runtime.warmup();
expect(calls).toEqual([ expect(calls).toEqual([
"health.get", "health",
"session.create", "session.create",
"model.list", `tool.list:${config.OPENCODE_MODEL}`,
"plugin.list", "session.delete:warmup-session",
"session.remove:warmup-session",
]); ]);
}); });
}); });
-193
View File
@@ -1,193 +0,0 @@
import { describe, expect, it } from "bun:test";
import { type OpenCodeClient } from "@opencode-ai/client";
import { OpencodeRuntimeAdapter } from "../../src/runtime/opencode.js";
const createRuntimeAdapter = (client: unknown) =>
Object.assign(new OpencodeRuntimeAdapter(), {
clientPromise: Promise.resolve(client as OpenCodeClient),
}) as OpencodeRuntimeAdapter;
const createEventStream = (events: unknown[]) => ({
async *[Symbol.asyncIterator]() {
for (const event of events) {
yield event;
}
},
});
describe("OpencodeRuntimeAdapter V2 interaction responses", () => {
it("replies through the top-level V2 permission API", async () => {
const runtime = createRuntimeAdapter({
permission: {
reply: async (parameters: Record<string, unknown>) => {
expect(parameters).toEqual({
sessionID: "session-1",
requestID: "request-1",
reply: "once",
message: "approved",
});
},
},
});
await expect(
runtime.replyPermission({
requestId: "request-1",
sessionId: "session-1",
reply: "once",
message: "approved",
}),
).resolves.toBeUndefined();
});
it("replies through the top-level V2 question API", async () => {
const runtime = createRuntimeAdapter({
question: {
reply: async (parameters: Record<string, unknown>) => {
expect(parameters).toEqual({
sessionID: "session-1",
requestID: "request-1",
answers: [["A"]],
});
},
},
});
await expect(
runtime.replyQuestion({
requestId: "request-1",
sessionId: "session-1",
answers: [["A"]],
}),
).resolves.toBeUndefined();
});
it("rejects through the top-level V2 question API", async () => {
const runtime = createRuntimeAdapter({
question: {
reject: async (parameters: Record<string, unknown>) => {
expect(parameters).toEqual({
sessionID: "session-1",
requestID: "request-1",
});
},
},
});
await expect(
runtime.rejectQuestion({
requestId: "request-1",
sessionId: "session-1",
}),
).resolves.toBeUndefined();
});
it("maps frontend question answers to the V2 Form API", async () => {
const runtime = createRuntimeAdapter({
form: {
get: async (parameters: Record<string, unknown>) => {
expect(parameters).toEqual({ sessionID: "session-1", formID: "frm_1" });
return {
fields: [
{
key: "scope",
type: "string",
title: "范围",
options: [{ value: "urban", label: "城区" }],
},
{ key: "confirmed", type: "boolean", title: "确认" },
],
};
},
reply: async (parameters: Record<string, unknown>) => {
expect(parameters).toEqual({
sessionID: "session-1",
formID: "frm_1",
answer: { scope: "urban", confirmed: true },
});
},
},
});
await expect(
runtime.replyQuestion({
requestId: "frm_1",
sessionId: "session-1",
answers: [["城区"], ["是"]],
}),
).resolves.toBeUndefined();
});
it("cancels V2 Form requests by form ID", async () => {
const runtime = createRuntimeAdapter({
form: {
cancel: async (parameters: Record<string, unknown>) => {
expect(parameters).toEqual({ sessionID: "session-1", formID: "frm_1" });
},
},
});
await expect(
runtime.rejectQuestion({ requestId: "frm_1", sessionId: "session-1" }),
).resolves.toBeUndefined();
});
it("normalizes V2 Form and execution events for the chat stream", async () => {
const runtime = createRuntimeAdapter({
event: {
subscribe: () =>
createEventStream([
{
type: "form.created",
data: {
form: {
id: "frm_1",
sessionID: "session-1",
title: "分析参数",
fields: [
{
key: "scope",
type: "string",
title: "范围",
description: "选择分析范围",
options: [{ value: "urban", label: "城区" }],
},
],
},
},
},
{
type: "session.execution.succeeded",
data: { sessionID: "session-1" },
},
]),
},
});
const events = [];
for await (const event of await runtime.subscribeEvents()) {
events.push(event);
}
expect(events).toEqual([
{
type: "question.asked",
sessionId: "session-1",
request: {
id: "frm_1",
questions: [
{
header: "范围",
question: "选择分析范围",
options: [{ label: "城区", description: "" }],
multiple: false,
custom: false,
},
],
},
},
{ type: "session.execution.succeeded", sessionId: "session-1" },
]);
});
});