refactor(server): replace Bun serve with Hono node adapters (#18335)
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com> Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com> Co-authored-by: Brendan Allan <git@brendonovich.dev>pull/21221/head
parent
4394e42615
commit
535343bf56
39
bun.lock
39
bun.lock
|
|
@ -329,8 +329,13 @@
|
||||||
"@aws-sdk/credential-providers": "3.993.0",
|
"@aws-sdk/credential-providers": "3.993.0",
|
||||||
"@clack/prompts": "1.0.0-alpha.1",
|
"@clack/prompts": "1.0.0-alpha.1",
|
||||||
"@effect/platform-node": "catalog:",
|
"@effect/platform-node": "catalog:",
|
||||||
|
"@gitlab/gitlab-ai-provider": "3.6.0",
|
||||||
|
"@gitlab/opencode-gitlab-auth": "1.3.3",
|
||||||
|
"@hono/node-server": "1.19.11",
|
||||||
|
"@hono/node-ws": "1.3.0",
|
||||||
"@hono/standard-validator": "0.1.5",
|
"@hono/standard-validator": "0.1.5",
|
||||||
"@hono/zod-validator": "catalog:",
|
"@hono/zod-validator": "catalog:",
|
||||||
|
"@lydell/node-pty": "1.2.0-beta.10",
|
||||||
"@modelcontextprotocol/sdk": "1.27.1",
|
"@modelcontextprotocol/sdk": "1.27.1",
|
||||||
"@npmcli/arborist": "9.4.0",
|
"@npmcli/arborist": "9.4.0",
|
||||||
"@octokit/graphql": "9.0.2",
|
"@octokit/graphql": "9.0.2",
|
||||||
|
|
@ -1144,6 +1149,10 @@
|
||||||
|
|
||||||
"@gar/promise-retry": ["@gar/promise-retry@1.0.3", "", {}, "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA=="],
|
"@gar/promise-retry": ["@gar/promise-retry@1.0.3", "", {}, "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA=="],
|
||||||
|
|
||||||
|
"@gitlab/gitlab-ai-provider": ["@gitlab/gitlab-ai-provider@3.6.0", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.0", "@anycable/core": "^0.9.2", "graphql-request": "^6.1.0", "isomorphic-ws": "^5.0.0", "openai": "^6.16.0", "socket.io-client": "^4.8.1", "vscode-jsonrpc": "^8.2.1", "zod": "^3.25.76" }, "peerDependencies": { "@ai-sdk/provider": ">=2.0.0", "@ai-sdk/provider-utils": ">=3.0.0" } }, "sha512-8LmcIQ86xkMtC7L4P1/QYVEC+yKMTRerfPeniaaQGalnzXKtX6iMHLjLPOL9Rxp55lOXi6ed0WrFuJzZx+fNRg=="],
|
||||||
|
|
||||||
|
"@gitlab/opencode-gitlab-auth": ["@gitlab/opencode-gitlab-auth@1.3.3", "", { "dependencies": { "@fastify/rate-limit": "^10.2.0", "@opencode-ai/plugin": "*", "fastify": "^5.2.0", "open": "^10.0.0" } }, "sha512-FT+KsCmAJjtqWr1YAq0MywGgL9kaLQ4apmsoowAXrPqHtoYf2i/nY10/A+L06kNj22EATeEDRpbB1NWXMto/SA=="],
|
||||||
|
|
||||||
"@graphql-typed-document-node/core": ["@graphql-typed-document-node/core@3.2.0", "", { "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ=="],
|
"@graphql-typed-document-node/core": ["@graphql-typed-document-node/core@3.2.0", "", { "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ=="],
|
||||||
|
|
||||||
"@happy-dom/global-registrator": ["@happy-dom/global-registrator@20.0.11", "", { "dependencies": { "@types/node": "^20.0.0", "happy-dom": "^20.0.11" } }, "sha512-GqNqiShBT/lzkHTMC/slKBrvN0DsD4Di8ssBk4aDaVgEn+2WMzE6DXxq701ndSXj7/0cJ8mNT71pM7Bnrr6JRw=="],
|
"@happy-dom/global-registrator": ["@happy-dom/global-registrator@20.0.11", "", { "dependencies": { "@types/node": "^20.0.0", "happy-dom": "^20.0.11" } }, "sha512-GqNqiShBT/lzkHTMC/slKBrvN0DsD4Di8ssBk4aDaVgEn+2WMzE6DXxq701ndSXj7/0cJ8mNT71pM7Bnrr6JRw=="],
|
||||||
|
|
@ -1156,7 +1165,9 @@
|
||||||
|
|
||||||
"@hey-api/types": ["@hey-api/types@0.1.2", "", {}, "sha512-uNNtiVAWL7XNrV/tFXx7GLY9lwaaDazx1173cGW3+UEaw4RUPsHEmiB4DSpcjNxMIcrctfz2sGKLnVx5PBG2RA=="],
|
"@hey-api/types": ["@hey-api/types@0.1.2", "", {}, "sha512-uNNtiVAWL7XNrV/tFXx7GLY9lwaaDazx1173cGW3+UEaw4RUPsHEmiB4DSpcjNxMIcrctfz2sGKLnVx5PBG2RA=="],
|
||||||
|
|
||||||
"@hono/node-server": ["@hono/node-server@1.19.12", "", { "peerDependencies": { "hono": "^4" } }, "sha512-txsUW4SQ1iilgE0l9/e9VQWmELXifEFvmdA1j6WFh/aFPj99hIntrSsq/if0UWyGVkmrRPKA1wCeP+UCr1B9Uw=="],
|
"@hono/node-server": ["@hono/node-server@1.19.11", "", { "peerDependencies": { "hono": "^4" } }, "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g=="],
|
||||||
|
|
||||||
|
"@hono/node-ws": ["@hono/node-ws@1.3.0", "", { "dependencies": { "ws": "^8.17.0" }, "peerDependencies": { "@hono/node-server": "^1.19.2", "hono": "^4.6.0" } }, "sha512-ju25YbbvLuXdqBCmLZLqnNYu1nbHIQjoyUqA8ApZOeL1k4skuiTcw5SW77/5SUYo2Xi2NVBJoVlfQurnKEp03Q=="],
|
||||||
|
|
||||||
"@hono/standard-validator": ["@hono/standard-validator@0.1.5", "", { "peerDependencies": { "@standard-schema/spec": "1.0.0", "hono": ">=3.9.0" } }, "sha512-EIyZPPwkyLn6XKwFj5NBEWHXhXbgmnVh2ceIFo5GO7gKI9WmzTjPDKnppQB0KrqKeAkq3kpoW4SIbu5X1dgx3w=="],
|
"@hono/standard-validator": ["@hono/standard-validator@0.1.5", "", { "peerDependencies": { "@standard-schema/spec": "1.0.0", "hono": ">=3.9.0" } }, "sha512-EIyZPPwkyLn6XKwFj5NBEWHXhXbgmnVh2ceIFo5GO7gKI9WmzTjPDKnppQB0KrqKeAkq3kpoW4SIbu5X1dgx3w=="],
|
||||||
|
|
||||||
|
|
@ -1348,6 +1359,20 @@
|
||||||
|
|
||||||
"@lukeed/ms": ["@lukeed/ms@2.0.2", "", {}, "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA=="],
|
"@lukeed/ms": ["@lukeed/ms@2.0.2", "", {}, "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA=="],
|
||||||
|
|
||||||
|
"@lydell/node-pty": ["@lydell/node-pty@1.2.0-beta.10", "", { "optionalDependencies": { "@lydell/node-pty-darwin-arm64": "1.2.0-beta.10", "@lydell/node-pty-darwin-x64": "1.2.0-beta.10", "@lydell/node-pty-linux-arm64": "1.2.0-beta.10", "@lydell/node-pty-linux-x64": "1.2.0-beta.10", "@lydell/node-pty-win32-arm64": "1.2.0-beta.10", "@lydell/node-pty-win32-x64": "1.2.0-beta.10" } }, "sha512-Fv+A3+MZVA8qhkBIZsM1E6dCdHNMyXXz22mAYiMWd03LlyK///F3OH6CKPX9mj4id7LUlxpr45yPzyBVy9aDPw=="],
|
||||||
|
|
||||||
|
"@lydell/node-pty-darwin-arm64": ["@lydell/node-pty-darwin-arm64@1.2.0-beta.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-C+eqDyRNHRYvx7RaHj6VVCx6nCpRBPuuxhTcc3JH3GuBMoxTsYeY4GkWH2XOktrgbAq1BG8e/Y8bu/wNQreCEw=="],
|
||||||
|
|
||||||
|
"@lydell/node-pty-darwin-x64": ["@lydell/node-pty-darwin-x64@1.2.0-beta.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-aZoIK6HtJO5BiT4ELm683U4dyHtt8b7wNgq3NJqYAQwSXrcPv576Z8vY3BIulVxfcFkht/SPLKou9TtdFXdNpg=="],
|
||||||
|
|
||||||
|
"@lydell/node-pty-linux-arm64": ["@lydell/node-pty-linux-arm64@1.2.0-beta.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-0cKX2iMyXFNBE4fGtGK6B7IkdXcDMZajyEDoGMOgQQs/DDtoI5tSPcBcqNY9VitVrsRQA8+gFt6eKYU9Ye/lUA=="],
|
||||||
|
|
||||||
|
"@lydell/node-pty-linux-x64": ["@lydell/node-pty-linux-x64@1.2.0-beta.10", "", { "os": "linux", "cpu": "x64" }, "sha512-J9HnxvSzEeMH748+Ul1VrmCLWMo7iCVJy9EGijRR62+YO/Yk5GaCydUTZ+KzlH0/X5aTrgt5cfiof4vx45tRRg=="],
|
||||||
|
|
||||||
|
"@lydell/node-pty-win32-arm64": ["@lydell/node-pty-win32-arm64@1.2.0-beta.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-PlDJpJX/pnKyy6OmADKzhf+INZDDnzTBGaI0LT4laVNc6NblZNqUSkCMjLFWbeakeuQp0VG37M49WQSN9FDfeA=="],
|
||||||
|
|
||||||
|
"@lydell/node-pty-win32-x64": ["@lydell/node-pty-win32-x64@1.2.0-beta.10", "", { "os": "win32", "cpu": "x64" }, "sha512-ExFgWrzyldNAMi45U9PLIOu+g/RatP+f0c/dZxaooifME6yLW32BoHveH26/TtoAjZyJrc2iL0u48pgnR1fzmg=="],
|
||||||
|
|
||||||
"@malept/cross-spawn-promise": ["@malept/cross-spawn-promise@2.0.0", "", { "dependencies": { "cross-spawn": "^7.0.1" } }, "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg=="],
|
"@malept/cross-spawn-promise": ["@malept/cross-spawn-promise@2.0.0", "", { "dependencies": { "cross-spawn": "^7.0.1" } }, "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg=="],
|
||||||
|
|
||||||
"@malept/flatpak-bundler": ["@malept/flatpak-bundler@0.4.0", "", { "dependencies": { "debug": "^4.1.1", "fs-extra": "^9.0.0", "lodash": "^4.17.15", "tmp-promise": "^3.0.2" } }, "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q=="],
|
"@malept/flatpak-bundler": ["@malept/flatpak-bundler@0.4.0", "", { "dependencies": { "debug": "^4.1.1", "fs-extra": "^9.0.0", "lodash": "^4.17.15", "tmp-promise": "^3.0.2" } }, "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q=="],
|
||||||
|
|
@ -5194,10 +5219,18 @@
|
||||||
|
|
||||||
"@fastify/proxy-addr/ipaddr.js": ["ipaddr.js@2.3.0", "", {}, "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg=="],
|
"@fastify/proxy-addr/ipaddr.js": ["ipaddr.js@2.3.0", "", {}, "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg=="],
|
||||||
|
|
||||||
|
"@gitlab/gitlab-ai-provider/openai": ["openai@6.33.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-xAYN1W3YsDXJWA5F277135YfkEk6H7D3D6vWwRhJ3OEkzRgcyK8z/P5P9Gyi/wB4N8kK9kM5ZjprfvyHagKmpw=="],
|
||||||
|
|
||||||
|
"@gitlab/gitlab-ai-provider/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||||
|
|
||||||
|
"@gitlab/opencode-gitlab-auth/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="],
|
||||||
|
|
||||||
"@hey-api/openapi-ts/open": ["open@11.0.0", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="],
|
"@hey-api/openapi-ts/open": ["open@11.0.0", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="],
|
||||||
|
|
||||||
"@hey-api/openapi-ts/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
"@hey-api/openapi-ts/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
||||||
|
|
||||||
|
"@hono/node-ws/ws": ["ws@8.20.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="],
|
||||||
|
|
||||||
"@hono/zod-validator/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
"@hono/zod-validator/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||||
|
|
||||||
"@jimp/core/mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
|
"@jimp/core/mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
|
||||||
|
|
@ -5250,6 +5283,8 @@
|
||||||
|
|
||||||
"@mdx-js/mdx/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
|
"@mdx-js/mdx/source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
|
||||||
|
|
||||||
|
"@modelcontextprotocol/sdk/@hono/node-server": ["@hono/node-server@1.19.12", "", { "peerDependencies": { "hono": "^4" } }, "sha512-txsUW4SQ1iilgE0l9/e9VQWmELXifEFvmdA1j6WFh/aFPj99hIntrSsq/if0UWyGVkmrRPKA1wCeP+UCr1B9Uw=="],
|
||||||
|
|
||||||
"@modelcontextprotocol/sdk/express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
|
"@modelcontextprotocol/sdk/express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
|
||||||
|
|
||||||
"@modelcontextprotocol/sdk/hono": ["hono@4.12.9", "", {}, "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA=="],
|
"@modelcontextprotocol/sdk/hono": ["hono@4.12.9", "", {}, "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA=="],
|
||||||
|
|
@ -6084,6 +6119,8 @@
|
||||||
|
|
||||||
"@expressive-code/plugin-shiki/shiki/@shikijs/types": ["@shikijs/types@3.23.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ=="],
|
"@expressive-code/plugin-shiki/shiki/@shikijs/types": ["@shikijs/types@3.23.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ=="],
|
||||||
|
|
||||||
|
"@gitlab/opencode-gitlab-auth/open/wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
|
||||||
|
|
||||||
"@jsx-email/cli/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.19.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA=="],
|
"@jsx-email/cli/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.19.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA=="],
|
||||||
|
|
||||||
"@jsx-email/cli/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.19.12", "", { "os": "android", "cpu": "arm" }, "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w=="],
|
"@jsx-email/cli/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.19.12", "", { "os": "android", "cpu": "arm" }, "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w=="],
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
"dev:console": "ulimit -n 10240 2>/dev/null; bun run --cwd packages/console/app dev",
|
"dev:console": "ulimit -n 10240 2>/dev/null; bun run --cwd packages/console/app dev",
|
||||||
"dev:storybook": "bun --cwd packages/storybook storybook",
|
"dev:storybook": "bun --cwd packages/storybook storybook",
|
||||||
"typecheck": "bun turbo typecheck",
|
"typecheck": "bun turbo typecheck",
|
||||||
|
"postinstall": "bun run --cwd packages/opencode fix-node-pty",
|
||||||
"prepare": "husky",
|
"prepare": "husky",
|
||||||
"random": "echo 'Random script'",
|
"random": "echo 'Random script'",
|
||||||
"hello": "echo 'Hello World!'",
|
"hello": "echo 'Hello World!'",
|
||||||
|
|
@ -103,6 +104,7 @@
|
||||||
},
|
},
|
||||||
"trustedDependencies": [
|
"trustedDependencies": [
|
||||||
"esbuild",
|
"esbuild",
|
||||||
|
"node-pty",
|
||||||
"protobufjs",
|
"protobufjs",
|
||||||
"tree-sitter",
|
"tree-sitter",
|
||||||
"tree-sitter-bash",
|
"tree-sitter-bash",
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ const runnerEnv = {
|
||||||
|
|
||||||
let seed: ReturnType<typeof Bun.spawn> | undefined
|
let seed: ReturnType<typeof Bun.spawn> | undefined
|
||||||
let runner: ReturnType<typeof Bun.spawn> | undefined
|
let runner: ReturnType<typeof Bun.spawn> | undefined
|
||||||
let server: { stop: () => Promise<void> | void } | undefined
|
let server: { stop: (close?: boolean) => Promise<void> | void } | undefined
|
||||||
let inst: { Instance: { disposeAll: () => Promise<void> | void } } | undefined
|
let inst: { Instance: { disposeAll: () => Promise<void> | void } } | undefined
|
||||||
let cleaned = false
|
let cleaned = false
|
||||||
|
|
||||||
|
|
@ -100,7 +100,7 @@ const cleanup = async () => {
|
||||||
|
|
||||||
const jobs = [
|
const jobs = [
|
||||||
inst?.Instance.disposeAll(),
|
inst?.Instance.disposeAll(),
|
||||||
server?.stop(),
|
typeof server?.stop === "function" ? server.stop() : undefined,
|
||||||
keepSandbox ? undefined : fs.rm(sandbox, { recursive: true, force: true }),
|
keepSandbox ? undefined : fs.rm(sandbox, { recursive: true, force: true }),
|
||||||
].filter(Boolean)
|
].filter(Boolean)
|
||||||
await Promise.allSettled(jobs)
|
await Promise.allSettled(jobs)
|
||||||
|
|
@ -158,7 +158,7 @@ try {
|
||||||
|
|
||||||
const servermod = await import("../../opencode/src/server/server")
|
const servermod = await import("../../opencode/src/server/server")
|
||||||
inst = await import("../../opencode/src/project/instance")
|
inst = await import("../../opencode/src/project/instance")
|
||||||
server = servermod.Server.listen({ port: serverPort, hostname: "127.0.0.1" })
|
server = await servermod.Server.listen({ port: serverPort, hostname: "127.0.0.1" })
|
||||||
console.log(`opencode server listening on http://127.0.0.1:${serverPort}`)
|
console.log(`opencode server listening on http://127.0.0.1:${serverPort}`)
|
||||||
|
|
||||||
await waitForHealth(`http://127.0.0.1:${serverPort}/global/health`)
|
await waitForHealth(`http://127.0.0.1:${serverPort}/global/health`)
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
"test": "bun test --timeout 30000",
|
"test": "bun test --timeout 30000",
|
||||||
"test:ci": "mkdir -p .artifacts/unit && bun test --timeout 30000 --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml",
|
"test:ci": "mkdir -p .artifacts/unit && bun test --timeout 30000 --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml",
|
||||||
"build": "bun run script/build.ts",
|
"build": "bun run script/build.ts",
|
||||||
|
"fix-node-pty": "bun run script/fix-node-pty.ts",
|
||||||
"upgrade-opentui": "bun run script/upgrade-opentui.ts",
|
"upgrade-opentui": "bun run script/upgrade-opentui.ts",
|
||||||
"dev": "bun run --conditions=browser ./src/index.ts",
|
"dev": "bun run --conditions=browser ./src/index.ts",
|
||||||
"random": "echo 'Random script updated at $(date)' && echo 'Change queued successfully' && echo 'Another change made' && echo 'Yet another change' && echo 'One more change' && echo 'Final change' && echo 'Another final change' && echo 'Yet another final change'",
|
"random": "echo 'Random script updated at $(date)' && echo 'Change queued successfully' && echo 'Another change made' && echo 'Yet another change' && echo 'One more change' && echo 'Final change' && echo 'Another final change' && echo 'Yet another final change'",
|
||||||
|
|
@ -33,6 +34,11 @@
|
||||||
"bun": "./src/storage/db.bun.ts",
|
"bun": "./src/storage/db.bun.ts",
|
||||||
"node": "./src/storage/db.node.ts",
|
"node": "./src/storage/db.node.ts",
|
||||||
"default": "./src/storage/db.bun.ts"
|
"default": "./src/storage/db.bun.ts"
|
||||||
|
},
|
||||||
|
"#pty": {
|
||||||
|
"bun": "./src/pty/pty.bun.ts",
|
||||||
|
"node": "./src/pty/pty.node.ts",
|
||||||
|
"default": "./src/pty/pty.bun.ts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
@ -94,8 +100,13 @@
|
||||||
"@aws-sdk/credential-providers": "3.993.0",
|
"@aws-sdk/credential-providers": "3.993.0",
|
||||||
"@clack/prompts": "1.0.0-alpha.1",
|
"@clack/prompts": "1.0.0-alpha.1",
|
||||||
"@effect/platform-node": "catalog:",
|
"@effect/platform-node": "catalog:",
|
||||||
|
"@gitlab/gitlab-ai-provider": "3.6.0",
|
||||||
|
"@gitlab/opencode-gitlab-auth": "1.3.3",
|
||||||
|
"@hono/node-server": "1.19.11",
|
||||||
|
"@hono/node-ws": "1.3.0",
|
||||||
"@hono/standard-validator": "0.1.5",
|
"@hono/standard-validator": "0.1.5",
|
||||||
"@hono/zod-validator": "catalog:",
|
"@hono/zod-validator": "catalog:",
|
||||||
|
"@lydell/node-pty": "1.2.0-beta.10",
|
||||||
"@modelcontextprotocol/sdk": "1.27.1",
|
"@modelcontextprotocol/sdk": "1.27.1",
|
||||||
"@npmcli/arborist": "9.4.0",
|
"@npmcli/arborist": "9.4.0",
|
||||||
"@octokit/graphql": "9.0.2",
|
"@octokit/graphql": "9.0.2",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/env bun
|
#!/usr/bin/env bun
|
||||||
|
|
||||||
|
import { $ } from "bun"
|
||||||
import { Script } from "@opencode-ai/script"
|
import { Script } from "@opencode-ai/script"
|
||||||
import fs from "fs"
|
import fs from "fs"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
|
|
@ -8,6 +9,15 @@ import { fileURLToPath } from "url"
|
||||||
const __filename = fileURLToPath(import.meta.url)
|
const __filename = fileURLToPath(import.meta.url)
|
||||||
const __dirname = path.dirname(__filename)
|
const __dirname = path.dirname(__filename)
|
||||||
const dir = path.resolve(__dirname, "..")
|
const dir = path.resolve(__dirname, "..")
|
||||||
|
const root = path.resolve(dir, "../..")
|
||||||
|
|
||||||
|
function linker(): "hoisted" | "isolated" {
|
||||||
|
// jsonc-parser is only declared in packages/opencode, so its install location
|
||||||
|
// tells us whether Bun used a hoisted or isolated workspace layout.
|
||||||
|
if (fs.existsSync(path.join(dir, "node_modules", "jsonc-parser"))) return "isolated"
|
||||||
|
if (fs.existsSync(path.join(root, "node_modules", "jsonc-parser"))) return "hoisted"
|
||||||
|
throw new Error("Could not detect Bun linker from jsonc-parser")
|
||||||
|
}
|
||||||
|
|
||||||
process.chdir(dir)
|
process.chdir(dir)
|
||||||
|
|
||||||
|
|
@ -41,11 +51,16 @@ const migrations = await Promise.all(
|
||||||
)
|
)
|
||||||
console.log(`Loaded ${migrations.length} migrations`)
|
console.log(`Loaded ${migrations.length} migrations`)
|
||||||
|
|
||||||
|
const link = linker()
|
||||||
|
|
||||||
|
await $`bun install --linker=${link} --os="*" --cpu="*" @lydell/node-pty@1.2.0-beta.10`
|
||||||
|
|
||||||
await Bun.build({
|
await Bun.build({
|
||||||
target: "node",
|
target: "node",
|
||||||
entrypoints: ["./src/node.ts"],
|
entrypoints: ["./src/node.ts"],
|
||||||
outdir: "./dist",
|
outdir: "./dist",
|
||||||
format: "esm",
|
format: "esm",
|
||||||
|
sourcemap: "linked",
|
||||||
external: ["jsonc-parser"],
|
external: ["jsonc-parser"],
|
||||||
define: {
|
define: {
|
||||||
OPENCODE_MIGRATIONS: JSON.stringify(migrations),
|
OPENCODE_MIGRATIONS: JSON.stringify(migrations),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
#!/usr/bin/env bun
|
||||||
|
|
||||||
|
import fs from "fs/promises"
|
||||||
|
import path from "path"
|
||||||
|
import { fileURLToPath } from "url"
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url)
|
||||||
|
const __dirname = path.dirname(__filename)
|
||||||
|
const dir = path.resolve(__dirname, "..")
|
||||||
|
|
||||||
|
if (process.platform !== "win32") {
|
||||||
|
const root = path.join(dir, "node_modules", "node-pty", "prebuilds")
|
||||||
|
const dirs = await fs.readdir(root, { withFileTypes: true }).catch(() => [])
|
||||||
|
const files = dirs.filter((x) => x.isDirectory()).map((x) => path.join(root, x.name, "spawn-helper"))
|
||||||
|
const result = await Promise.all(
|
||||||
|
files.map(async (file) => {
|
||||||
|
const stat = await fs.stat(file).catch(() => undefined)
|
||||||
|
if (!stat) return
|
||||||
|
if ((stat.mode & 0o111) === 0o111) return
|
||||||
|
await fs.chmod(file, stat.mode | 0o755)
|
||||||
|
return file
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
const fixed = result.filter(Boolean)
|
||||||
|
if (fixed.length) {
|
||||||
|
console.log(`fixed node-pty permissions for ${fixed.length} helper${fixed.length === 1 ? "" : "s"}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -23,7 +23,7 @@ export const AcpCommand = cmd({
|
||||||
process.env.OPENCODE_CLIENT = "acp"
|
process.env.OPENCODE_CLIENT = "acp"
|
||||||
await bootstrap(process.cwd(), async () => {
|
await bootstrap(process.cwd(), async () => {
|
||||||
const opts = await resolveNetworkOptions(args)
|
const opts = await resolveNetworkOptions(args)
|
||||||
const server = Server.listen(opts)
|
const server = await Server.listen(opts)
|
||||||
|
|
||||||
const sdk = createOpencodeClient({
|
const sdk = createOpencodeClient({
|
||||||
baseUrl: `http://${server.hostname}:${server.port}`,
|
baseUrl: `http://${server.hostname}:${server.port}`,
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ export const ServeCommand = cmd({
|
||||||
console.log("Warning: OPENCODE_SERVER_PASSWORD is not set; server is unsecured.")
|
console.log("Warning: OPENCODE_SERVER_PASSWORD is not set; server is unsecured.")
|
||||||
}
|
}
|
||||||
const opts = await resolveNetworkOptions(args)
|
const opts = await resolveNetworkOptions(args)
|
||||||
const server = Server.listen(opts)
|
const server = await Server.listen(opts)
|
||||||
console.log(`opencode server listening on http://${server.hostname}:${server.port}`)
|
console.log(`opencode server listening on http://${server.hostname}:${server.port}`)
|
||||||
|
|
||||||
await new Promise(() => {})
|
await new Promise(() => {})
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ export const WebCommand = cmd({
|
||||||
UI.println(UI.Style.TEXT_WARNING_BOLD + "! " + "OPENCODE_SERVER_PASSWORD is not set; server is unsecured.")
|
UI.println(UI.Style.TEXT_WARNING_BOLD + "! " + "OPENCODE_SERVER_PASSWORD is not set; server is unsecured.")
|
||||||
}
|
}
|
||||||
const opts = await resolveNetworkOptions(args)
|
const opts = await resolveNetworkOptions(args)
|
||||||
const server = Server.listen(opts)
|
const server = await Server.listen(opts)
|
||||||
UI.empty()
|
UI.empty()
|
||||||
UI.println(UI.logo(" "))
|
UI.println(UI.logo(" "))
|
||||||
UI.empty()
|
UI.empty()
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,8 @@ export namespace Plugin {
|
||||||
get serverUrl(): URL {
|
get serverUrl(): URL {
|
||||||
return Server.url ?? new URL("http://localhost:4096")
|
return Server.url ?? new URL("http://localhost:4096")
|
||||||
},
|
},
|
||||||
$: Bun.$,
|
// @ts-expect-error
|
||||||
|
$: typeof Bun === "undefined" ? undefined : Bun.$,
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const plugin of INTERNAL_PLUGINS) {
|
for (const plugin of INTERNAL_PLUGINS) {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { Bus } from "@/bus"
|
||||||
import { InstanceState } from "@/effect/instance-state"
|
import { InstanceState } from "@/effect/instance-state"
|
||||||
import { makeRuntime } from "@/effect/run-service"
|
import { makeRuntime } from "@/effect/run-service"
|
||||||
import { Instance } from "@/project/instance"
|
import { Instance } from "@/project/instance"
|
||||||
import { type IPty } from "bun-pty"
|
import type { Proc } from "#pty"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { Log } from "../util/log"
|
import { Log } from "../util/log"
|
||||||
import { lazy } from "@opencode-ai/util/lazy"
|
import { lazy } from "@opencode-ai/util/lazy"
|
||||||
|
|
@ -26,9 +26,11 @@ export namespace Pty {
|
||||||
close: (code?: number, reason?: string) => void
|
close: (code?: number, reason?: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sock = (ws: Socket) => (ws.data && typeof ws.data === "object" ? ws.data : ws)
|
||||||
|
|
||||||
type Active = {
|
type Active = {
|
||||||
info: Info
|
info: Info
|
||||||
process: IPty
|
process: Proc
|
||||||
buffer: string
|
buffer: string
|
||||||
bufferCursor: number
|
bufferCursor: number
|
||||||
cursor: number
|
cursor: number
|
||||||
|
|
@ -50,10 +52,7 @@ export namespace Pty {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
const pty = lazy(async () => {
|
const pty = lazy(() => import("#pty"))
|
||||||
const { spawn } = await import("bun-pty")
|
|
||||||
return spawn
|
|
||||||
})
|
|
||||||
|
|
||||||
export const Info = z
|
export const Info = z
|
||||||
.object({
|
.object({
|
||||||
|
|
@ -124,9 +123,9 @@ export namespace Pty {
|
||||||
try {
|
try {
|
||||||
session.process.kill()
|
session.process.kill()
|
||||||
} catch {}
|
} catch {}
|
||||||
for (const [key, ws] of session.subscribers.entries()) {
|
for (const [sub, ws] of session.subscribers.entries()) {
|
||||||
try {
|
try {
|
||||||
if (ws.data === key) ws.close()
|
if (sock(ws) === sub) ws.close()
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
session.subscribers.clear()
|
session.subscribers.clear()
|
||||||
|
|
@ -198,7 +197,7 @@ export namespace Pty {
|
||||||
}
|
}
|
||||||
log.info("creating session", { id, cmd: command, args, cwd })
|
log.info("creating session", { id, cmd: command, args, cwd })
|
||||||
|
|
||||||
const spawn = yield* Effect.promise(() => pty())
|
const { spawn } = yield* Effect.promise(() => pty())
|
||||||
const proc = yield* Effect.sync(() =>
|
const proc = yield* Effect.sync(() =>
|
||||||
spawn(command, args, {
|
spawn(command, args, {
|
||||||
name: "xterm-256color",
|
name: "xterm-256color",
|
||||||
|
|
@ -234,7 +233,7 @@ export namespace Pty {
|
||||||
session.subscribers.delete(key)
|
session.subscribers.delete(key)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (ws.data !== key) {
|
if (sock(ws) !== key) {
|
||||||
session.subscribers.delete(key)
|
session.subscribers.delete(key)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -304,15 +303,12 @@ export namespace Pty {
|
||||||
}
|
}
|
||||||
log.info("client connected to session", { id })
|
log.info("client connected to session", { id })
|
||||||
|
|
||||||
// Use ws.data as the unique key for this connection lifecycle.
|
const sub = sock(ws)
|
||||||
// If ws.data is undefined, fallback to ws object.
|
session.subscribers.delete(sub)
|
||||||
const key = ws.data && typeof ws.data === "object" ? ws.data : ws
|
session.subscribers.set(sub, ws)
|
||||||
// Optionally cleanup if the key somehow exists
|
|
||||||
session.subscribers.delete(key)
|
|
||||||
session.subscribers.set(key, ws)
|
|
||||||
|
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
session.subscribers.delete(key)
|
session.subscribers.delete(sub)
|
||||||
}
|
}
|
||||||
|
|
||||||
const start = session.bufferCursor
|
const start = session.bufferCursor
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { spawn as create } from "bun-pty"
|
||||||
|
import type { Opts, Proc } from "./pty"
|
||||||
|
|
||||||
|
export type { Disp, Exit, Opts, Proc } from "./pty"
|
||||||
|
|
||||||
|
export function spawn(file: string, args: string[], opts: Opts): Proc {
|
||||||
|
const pty = create(file, args, opts)
|
||||||
|
return {
|
||||||
|
pid: pty.pid,
|
||||||
|
onData(listener) {
|
||||||
|
return pty.onData(listener)
|
||||||
|
},
|
||||||
|
onExit(listener) {
|
||||||
|
return pty.onExit(listener)
|
||||||
|
},
|
||||||
|
write(data) {
|
||||||
|
pty.write(data)
|
||||||
|
},
|
||||||
|
resize(cols, rows) {
|
||||||
|
pty.resize(cols, rows)
|
||||||
|
},
|
||||||
|
kill(signal) {
|
||||||
|
pty.kill(signal)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
/** @ts-expect-error */
|
||||||
|
import * as pty from "@lydell/node-pty"
|
||||||
|
import type { Opts, Proc } from "./pty"
|
||||||
|
|
||||||
|
export type { Disp, Exit, Opts, Proc } from "./pty"
|
||||||
|
|
||||||
|
export function spawn(file: string, args: string[], opts: Opts): Proc {
|
||||||
|
const proc = pty.spawn(file, args, opts)
|
||||||
|
return {
|
||||||
|
pid: proc.pid,
|
||||||
|
onData(listener) {
|
||||||
|
return proc.onData(listener)
|
||||||
|
},
|
||||||
|
onExit(listener) {
|
||||||
|
return proc.onExit(listener)
|
||||||
|
},
|
||||||
|
write(data) {
|
||||||
|
proc.write(data)
|
||||||
|
},
|
||||||
|
resize(cols, rows) {
|
||||||
|
proc.resize(cols, rows)
|
||||||
|
},
|
||||||
|
kill(signal) {
|
||||||
|
proc.kill(signal)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
export type Disp = {
|
||||||
|
dispose(): void
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Exit = {
|
||||||
|
exitCode: number
|
||||||
|
signal?: number | string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Opts = {
|
||||||
|
name: string
|
||||||
|
cols?: number
|
||||||
|
rows?: number
|
||||||
|
cwd?: string
|
||||||
|
env?: Record<string, string>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Proc = {
|
||||||
|
pid: number
|
||||||
|
onData(listener: (data: string) => void): Disp
|
||||||
|
onExit(listener: (event: Exit) => void): Disp
|
||||||
|
write(data: string): void
|
||||||
|
resize(cols: number, rows: number): void
|
||||||
|
kill(signal?: string): void
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { describeRoute, resolver, validator } from "hono-openapi"
|
import { describeRoute, resolver, validator } from "hono-openapi"
|
||||||
import { Hono } from "hono"
|
import { Hono } from "hono"
|
||||||
import { proxy } from "hono/proxy"
|
import { proxy } from "hono/proxy"
|
||||||
|
import type { UpgradeWebSocket } from "hono/ws"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { createHash } from "node:crypto"
|
import { createHash } from "node:crypto"
|
||||||
import { Log } from "../util/log"
|
import { Log } from "../util/log"
|
||||||
|
|
@ -41,11 +42,11 @@ const DEFAULT_CSP =
|
||||||
const csp = (hash = "") =>
|
const csp = (hash = "") =>
|
||||||
`default-src 'self'; script-src 'self' 'wasm-unsafe-eval'${hash ? ` 'sha256-${hash}'` : ""}; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; media-src 'self' data:; connect-src 'self' data:`
|
`default-src 'self'; script-src 'self' 'wasm-unsafe-eval'${hash ? ` 'sha256-${hash}'` : ""}; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; media-src 'self' data:; connect-src 'self' data:`
|
||||||
|
|
||||||
export const InstanceRoutes = (app?: Hono) =>
|
export const InstanceRoutes = (upgrade: UpgradeWebSocket, app: Hono = new Hono()) =>
|
||||||
(app ?? new Hono())
|
app
|
||||||
.onError(errorHandler(log))
|
.onError(errorHandler(log))
|
||||||
.route("/project", ProjectRoutes())
|
.route("/project", ProjectRoutes())
|
||||||
.route("/pty", PtyRoutes())
|
.route("/pty", PtyRoutes(upgrade))
|
||||||
.route("/config", ConfigRoutes())
|
.route("/config", ConfigRoutes())
|
||||||
.route("/experimental", ExperimentalRoutes())
|
.route("/experimental", ExperimentalRoutes())
|
||||||
.route("/session", SessionRoutes())
|
.route("/session", SessionRoutes())
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import type { MiddlewareHandler } from "hono"
|
import type { MiddlewareHandler } from "hono"
|
||||||
|
import type { UpgradeWebSocket } from "hono/ws"
|
||||||
import { getAdaptor } from "@/control-plane/adaptors"
|
import { getAdaptor } from "@/control-plane/adaptors"
|
||||||
import { WorkspaceID } from "@/control-plane/schema"
|
import { WorkspaceID } from "@/control-plane/schema"
|
||||||
import { Workspace } from "@/control-plane/workspace"
|
import { Workspace } from "@/control-plane/workspace"
|
||||||
|
|
@ -24,76 +25,78 @@ function local(method: string, path: string) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const routes = lazy(() => InstanceRoutes())
|
export function WorkspaceRouterMiddleware(upgrade: UpgradeWebSocket): MiddlewareHandler {
|
||||||
|
const routes = lazy(() => InstanceRoutes(upgrade))
|
||||||
|
|
||||||
export const WorkspaceRouterMiddleware: MiddlewareHandler = async (c) => {
|
return async (c) => {
|
||||||
const raw = c.req.query("directory") || c.req.header("x-opencode-directory") || process.cwd()
|
const raw = c.req.query("directory") || c.req.header("x-opencode-directory") || process.cwd()
|
||||||
const directory = Filesystem.resolve(
|
const directory = Filesystem.resolve(
|
||||||
(() => {
|
(() => {
|
||||||
try {
|
try {
|
||||||
return decodeURIComponent(raw)
|
return decodeURIComponent(raw)
|
||||||
} catch {
|
} catch {
|
||||||
return raw
|
return raw
|
||||||
}
|
}
|
||||||
})(),
|
})(),
|
||||||
)
|
)
|
||||||
|
|
||||||
const url = new URL(c.req.url)
|
const url = new URL(c.req.url)
|
||||||
const workspaceParam = url.searchParams.get("workspace")
|
const workspaceParam = url.searchParams.get("workspace")
|
||||||
|
|
||||||
// TODO: If session is being routed, force it to lookup the
|
// TODO: If session is being routed, force it to lookup the
|
||||||
// project/workspace
|
// project/workspace
|
||||||
|
|
||||||
// If no workspace is provided we use the "project" workspace
|
// If no workspace is provided we use the "project" workspace
|
||||||
if (!workspaceParam) {
|
if (!workspaceParam) {
|
||||||
return Instance.provide({
|
return Instance.provide({
|
||||||
directory,
|
directory,
|
||||||
init: InstanceBootstrap,
|
init: InstanceBootstrap,
|
||||||
async fn() {
|
async fn() {
|
||||||
return routes().fetch(c.req.raw, c.env)
|
return routes().fetch(c.req.raw, c.env)
|
||||||
},
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const workspaceID = WorkspaceID.make(workspaceParam)
|
||||||
|
const workspace = await Workspace.get(workspaceID)
|
||||||
|
if (!workspace) {
|
||||||
|
return new Response(`Workspace not found: ${workspaceID}`, {
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"content-type": "text/plain; charset=utf-8",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle local workspaces directly so we can pass env to `fetch`,
|
||||||
|
// necessary for websocket upgrades
|
||||||
|
if (workspace.type === "worktree") {
|
||||||
|
return Instance.provide({
|
||||||
|
directory: workspace.directory!,
|
||||||
|
init: InstanceBootstrap,
|
||||||
|
async fn() {
|
||||||
|
return routes().fetch(c.req.raw, c.env)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remote workspaces
|
||||||
|
|
||||||
|
if (local(c.req.method, url.pathname)) {
|
||||||
|
// No instance provided because we are serving cached data; there
|
||||||
|
// is no instance to work with
|
||||||
|
return routes().fetch(c.req.raw, c.env)
|
||||||
|
}
|
||||||
|
|
||||||
|
const adaptor = await getAdaptor(workspace.type)
|
||||||
|
const headers = new Headers(c.req.raw.headers)
|
||||||
|
headers.delete("x-opencode-workspace")
|
||||||
|
|
||||||
|
return adaptor.fetch(workspace, `${url.pathname}${url.search}`, {
|
||||||
|
method: c.req.method,
|
||||||
|
body: c.req.method === "GET" || c.req.method === "HEAD" ? undefined : await c.req.raw.arrayBuffer(),
|
||||||
|
signal: c.req.raw.signal,
|
||||||
|
headers,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const workspaceID = WorkspaceID.make(workspaceParam)
|
|
||||||
const workspace = await Workspace.get(workspaceID)
|
|
||||||
if (!workspace) {
|
|
||||||
return new Response(`Workspace not found: ${workspaceID}`, {
|
|
||||||
status: 500,
|
|
||||||
headers: {
|
|
||||||
"content-type": "text/plain; charset=utf-8",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle local workspaces directly so we can pass env to `fetch`,
|
|
||||||
// necessary for websocket upgrades
|
|
||||||
if (workspace.type === "worktree") {
|
|
||||||
return Instance.provide({
|
|
||||||
directory: workspace.directory!,
|
|
||||||
init: InstanceBootstrap,
|
|
||||||
async fn() {
|
|
||||||
return routes().fetch(c.req.raw, c.env)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remote workspaces
|
|
||||||
|
|
||||||
if (local(c.req.method, url.pathname)) {
|
|
||||||
// No instance provided because we are serving cached data; there
|
|
||||||
// is no instance to work with
|
|
||||||
return routes().fetch(c.req.raw, c.env)
|
|
||||||
}
|
|
||||||
|
|
||||||
const adaptor = await getAdaptor(workspace.type)
|
|
||||||
const headers = new Headers(c.req.raw.headers)
|
|
||||||
headers.delete("x-opencode-workspace")
|
|
||||||
|
|
||||||
return adaptor.fetch(workspace, `${url.pathname}${url.search}`, {
|
|
||||||
method: c.req.method,
|
|
||||||
body: c.req.method === "GET" || c.req.method === "HEAD" ? undefined : await c.req.raw.arrayBuffer(),
|
|
||||||
signal: c.req.raw.signal,
|
|
||||||
headers,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
import { Hono } from "hono"
|
import { Hono, type MiddlewareHandler } from "hono"
|
||||||
import { describeRoute, validator, resolver } from "hono-openapi"
|
import { describeRoute, validator, resolver } from "hono-openapi"
|
||||||
import { upgradeWebSocket } from "hono/bun"
|
import type { UpgradeWebSocket } from "hono/ws"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { Pty } from "@/pty"
|
import { Pty } from "@/pty"
|
||||||
import { PtyID } from "@/pty/schema"
|
import { PtyID } from "@/pty/schema"
|
||||||
import { NotFoundError } from "../../storage/db"
|
import { NotFoundError } from "../../storage/db"
|
||||||
import { errors } from "../error"
|
import { errors } from "../error"
|
||||||
import { lazy } from "../../util/lazy"
|
|
||||||
|
|
||||||
export const PtyRoutes = lazy(() =>
|
export function PtyRoutes(upgradeWebSocket: UpgradeWebSocket) {
|
||||||
new Hono()
|
return new Hono()
|
||||||
.get(
|
.get(
|
||||||
"/",
|
"/",
|
||||||
describeRoute({
|
describeRoute({
|
||||||
|
|
@ -207,5 +206,5 @@ export const PtyRoutes = lazy(() =>
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
),
|
)
|
||||||
)
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,14 @@ import { Hono } from "hono"
|
||||||
import { compress } from "hono/compress"
|
import { compress } from "hono/compress"
|
||||||
import { cors } from "hono/cors"
|
import { cors } from "hono/cors"
|
||||||
import { basicAuth } from "hono/basic-auth"
|
import { basicAuth } from "hono/basic-auth"
|
||||||
|
import type { UpgradeWebSocket } from "hono/ws"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { Auth } from "../auth"
|
import { Auth } from "../auth"
|
||||||
import { Flag } from "../flag/flag"
|
import { Flag } from "../flag/flag"
|
||||||
import { ProviderID } from "../provider/schema"
|
import { ProviderID } from "../provider/schema"
|
||||||
|
import { createAdaptorServer, type ServerType } from "@hono/node-server"
|
||||||
|
import { createNodeWebSocket } from "@hono/node-ws"
|
||||||
import { WorkspaceRouterMiddleware } from "./router"
|
import { WorkspaceRouterMiddleware } from "./router"
|
||||||
import { websocket } from "hono/bun"
|
|
||||||
import { errors } from "./error"
|
import { errors } from "./error"
|
||||||
import { GlobalRoutes } from "./routes/global"
|
import { GlobalRoutes } from "./routes/global"
|
||||||
import { MDNS } from "./mdns"
|
import { MDNS } from "./mdns"
|
||||||
|
|
@ -24,8 +26,14 @@ globalThis.AI_SDK_LOG_WARNINGS = false
|
||||||
initProjectors()
|
initProjectors()
|
||||||
|
|
||||||
export namespace Server {
|
export namespace Server {
|
||||||
const log = Log.create({ service: "server" })
|
export type Listener = {
|
||||||
|
hostname: string
|
||||||
|
port: number
|
||||||
|
url: URL
|
||||||
|
stop: (close?: boolean) => Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
const log = Log.create({ service: "server" })
|
||||||
const zipped = compress()
|
const zipped = compress()
|
||||||
|
|
||||||
const skipCompress = (path: string, method: string) => {
|
const skipCompress = (path: string, method: string) => {
|
||||||
|
|
@ -34,10 +42,9 @@ export namespace Server {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Default = lazy(() => ControlPlaneRoutes())
|
export const Default = lazy(() => create({}).app)
|
||||||
|
|
||||||
export const ControlPlaneRoutes = (opts?: { cors?: string[] }): Hono => {
|
export function ControlPlaneRoutes(upgrade: UpgradeWebSocket, app = new Hono(), opts?: { cors?: string[] }): Hono {
|
||||||
const app = new Hono()
|
|
||||||
return app
|
return app
|
||||||
.onError(errorHandler(log))
|
.onError(errorHandler(log))
|
||||||
.use((c, next) => {
|
.use((c, next) => {
|
||||||
|
|
@ -62,9 +69,7 @@ export namespace Server {
|
||||||
path: c.req.path,
|
path: c.req.path,
|
||||||
})
|
})
|
||||||
await next()
|
await next()
|
||||||
if (!skip) {
|
if (!skip) timer.stop()
|
||||||
timer.stop()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.use(
|
.use(
|
||||||
cors({
|
cors({
|
||||||
|
|
@ -81,15 +86,8 @@ export namespace Server {
|
||||||
)
|
)
|
||||||
return input
|
return input
|
||||||
|
|
||||||
// *.opencode.ai (https only, adjust if needed)
|
if (/^https:\/\/([a-z0-9-]+\.)*opencode\.ai$/.test(input)) return input
|
||||||
if (/^https:\/\/([a-z0-9-]+\.)*opencode\.ai$/.test(input)) {
|
if (opts?.cors?.includes(input)) return input
|
||||||
return input
|
|
||||||
}
|
|
||||||
if (opts?.cors?.includes(input)) {
|
|
||||||
return input
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
@ -234,11 +232,20 @@ export namespace Server {
|
||||||
return c.json(true)
|
return c.json(true)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.use(WorkspaceRouterMiddleware)
|
.use(WorkspaceRouterMiddleware(upgrade))
|
||||||
|
}
|
||||||
|
|
||||||
|
function create(opts: { cors?: string[] }) {
|
||||||
|
const app = new Hono()
|
||||||
|
const ws = createNodeWebSocket({ app })
|
||||||
|
return {
|
||||||
|
app: ControlPlaneRoutes(ws.upgradeWebSocket, app, opts),
|
||||||
|
ws,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createApp(opts: { cors?: string[] }) {
|
export function createApp(opts: { cors?: string[] }) {
|
||||||
return ControlPlaneRoutes(opts)
|
return create(opts).app
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function openapi() {
|
export async function openapi() {
|
||||||
|
|
@ -246,8 +253,8 @@ export namespace Server {
|
||||||
// hono-openapi can see describeRoute metadata (`.route()` wraps
|
// hono-openapi can see describeRoute metadata (`.route()` wraps
|
||||||
// handlers when the sub-app has a custom errorHandler, which
|
// handlers when the sub-app has a custom errorHandler, which
|
||||||
// strips the metadata symbol).
|
// strips the metadata symbol).
|
||||||
const app = ControlPlaneRoutes()
|
const { app, ws } = create({})
|
||||||
InstanceRoutes(app)
|
InstanceRoutes(ws.upgradeWebSocket, app)
|
||||||
const result = await generateSpecs(app, {
|
const result = await generateSpecs(app, {
|
||||||
documentation: {
|
documentation: {
|
||||||
info: {
|
info: {
|
||||||
|
|
@ -261,52 +268,86 @@ export namespace Server {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated do not use this dumb shit */
|
|
||||||
export let url: URL
|
export let url: URL
|
||||||
|
|
||||||
export function listen(opts: {
|
export async function listen(opts: {
|
||||||
port: number
|
port: number
|
||||||
hostname: string
|
hostname: string
|
||||||
mdns?: boolean
|
mdns?: boolean
|
||||||
mdnsDomain?: string
|
mdnsDomain?: string
|
||||||
cors?: string[]
|
cors?: string[]
|
||||||
}) {
|
}): Promise<Listener> {
|
||||||
url = new URL(`http://${opts.hostname}:${opts.port}`)
|
const built = create(opts)
|
||||||
const app = ControlPlaneRoutes({ cors: opts.cors })
|
const start = (port: number) =>
|
||||||
const args = {
|
new Promise<ServerType>((resolve, reject) => {
|
||||||
hostname: opts.hostname,
|
const server = createAdaptorServer({ fetch: built.app.fetch })
|
||||||
idleTimeout: 0,
|
built.ws.injectWebSocket(server)
|
||||||
fetch: app.fetch,
|
const fail = (err: Error) => {
|
||||||
websocket: websocket,
|
cleanup()
|
||||||
} as const
|
reject(err)
|
||||||
const tryServe = (port: number) => {
|
}
|
||||||
try {
|
const ready = () => {
|
||||||
return Bun.serve({ ...args, port })
|
cleanup()
|
||||||
} catch {
|
resolve(server)
|
||||||
return undefined
|
}
|
||||||
}
|
const cleanup = () => {
|
||||||
}
|
server.off("error", fail)
|
||||||
const server = opts.port === 0 ? (tryServe(4096) ?? tryServe(0)) : tryServe(opts.port)
|
server.off("listening", ready)
|
||||||
if (!server) throw new Error(`Failed to start server on port ${opts.port}`)
|
}
|
||||||
|
server.once("error", fail)
|
||||||
|
server.once("listening", ready)
|
||||||
|
server.listen(port, opts.hostname)
|
||||||
|
})
|
||||||
|
|
||||||
const shouldPublishMDNS =
|
const server = opts.port === 0 ? await start(4096).catch(() => start(0)) : await start(opts.port)
|
||||||
|
const addr = server.address()
|
||||||
|
if (!addr || typeof addr === "string") {
|
||||||
|
throw new Error(`Failed to resolve server address for port ${opts.port}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const next = new URL("http://localhost")
|
||||||
|
next.hostname = opts.hostname
|
||||||
|
next.port = String(addr.port)
|
||||||
|
url = next
|
||||||
|
|
||||||
|
const mdns =
|
||||||
opts.mdns &&
|
opts.mdns &&
|
||||||
server.port &&
|
addr.port &&
|
||||||
opts.hostname !== "127.0.0.1" &&
|
opts.hostname !== "127.0.0.1" &&
|
||||||
opts.hostname !== "localhost" &&
|
opts.hostname !== "localhost" &&
|
||||||
opts.hostname !== "::1"
|
opts.hostname !== "::1"
|
||||||
if (shouldPublishMDNS) {
|
if (mdns) {
|
||||||
MDNS.publish(server.port!, opts.mdnsDomain)
|
MDNS.publish(addr.port, opts.mdnsDomain)
|
||||||
} else if (opts.mdns) {
|
} else if (opts.mdns) {
|
||||||
log.warn("mDNS enabled but hostname is loopback; skipping mDNS publish")
|
log.warn("mDNS enabled but hostname is loopback; skipping mDNS publish")
|
||||||
}
|
}
|
||||||
|
|
||||||
const originalStop = server.stop.bind(server)
|
let closing: Promise<void> | undefined
|
||||||
server.stop = async (closeActiveConnections?: boolean) => {
|
return {
|
||||||
if (shouldPublishMDNS) MDNS.unpublish()
|
hostname: opts.hostname,
|
||||||
return originalStop(closeActiveConnections)
|
port: addr.port,
|
||||||
|
url: next,
|
||||||
|
stop(close?: boolean) {
|
||||||
|
closing ??= new Promise((resolve, reject) => {
|
||||||
|
if (mdns) MDNS.unpublish()
|
||||||
|
server.close((err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
if (close) {
|
||||||
|
if ("closeAllConnections" in server && typeof server.closeAllConnections === "function") {
|
||||||
|
server.closeAllConnections()
|
||||||
|
}
|
||||||
|
if ("closeIdleConnections" in server && typeof server.closeIdleConnections === "function") {
|
||||||
|
server.closeIdleConnections()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return closing
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return server
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue