Skip to content

Commit 535343b

Browse files
thdxrgreptile-apps[bot]HonaadamdotdevinBrendonovich
authored
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>
1 parent 4394e42 commit 535343b

File tree

18 files changed

+359
-147
lines changed

18 files changed

+359
-147
lines changed

bun.lock

Lines changed: 38 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"dev:console": "ulimit -n 10240 2>/dev/null; bun run --cwd packages/console/app dev",
1313
"dev:storybook": "bun --cwd packages/storybook storybook",
1414
"typecheck": "bun turbo typecheck",
15+
"postinstall": "bun run --cwd packages/opencode fix-node-pty",
1516
"prepare": "husky",
1617
"random": "echo 'Random script'",
1718
"hello": "echo 'Hello World!'",
@@ -103,6 +104,7 @@
103104
},
104105
"trustedDependencies": [
105106
"esbuild",
107+
"node-pty",
106108
"protobufjs",
107109
"tree-sitter",
108110
"tree-sitter-bash",

packages/app/script/e2e-local.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ const runnerEnv = {
8787

8888
let seed: ReturnType<typeof Bun.spawn> | undefined
8989
let runner: ReturnType<typeof Bun.spawn> | undefined
90-
let server: { stop: () => Promise<void> | void } | undefined
90+
let server: { stop: (close?: boolean) => Promise<void> | void } | undefined
9191
let inst: { Instance: { disposeAll: () => Promise<void> | void } } | undefined
9292
let cleaned = false
9393

@@ -100,7 +100,7 @@ const cleanup = async () => {
100100

101101
const jobs = [
102102
inst?.Instance.disposeAll(),
103-
server?.stop(),
103+
typeof server?.stop === "function" ? server.stop() : undefined,
104104
keepSandbox ? undefined : fs.rm(sandbox, { recursive: true, force: true }),
105105
].filter(Boolean)
106106
await Promise.allSettled(jobs)
@@ -158,7 +158,7 @@ try {
158158

159159
const servermod = await import("../../opencode/src/server/server")
160160
inst = await import("../../opencode/src/project/instance")
161-
server = servermod.Server.listen({ port: serverPort, hostname: "127.0.0.1" })
161+
server = await servermod.Server.listen({ port: serverPort, hostname: "127.0.0.1" })
162162
console.log(`opencode server listening on http://127.0.0.1:${serverPort}`)
163163

164164
await waitForHealth(`http://127.0.0.1:${serverPort}/global/health`)

packages/opencode/package.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"test": "bun test --timeout 30000",
1212
"test:ci": "mkdir -p .artifacts/unit && bun test --timeout 30000 --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml",
1313
"build": "bun run script/build.ts",
14+
"fix-node-pty": "bun run script/fix-node-pty.ts",
1415
"upgrade-opentui": "bun run script/upgrade-opentui.ts",
1516
"dev": "bun run --conditions=browser ./src/index.ts",
1617
"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 @@
3334
"bun": "./src/storage/db.bun.ts",
3435
"node": "./src/storage/db.node.ts",
3536
"default": "./src/storage/db.bun.ts"
37+
},
38+
"#pty": {
39+
"bun": "./src/pty/pty.bun.ts",
40+
"node": "./src/pty/pty.node.ts",
41+
"default": "./src/pty/pty.bun.ts"
3642
}
3743
},
3844
"devDependencies": {
@@ -94,8 +100,13 @@
94100
"@aws-sdk/credential-providers": "3.993.0",
95101
"@clack/prompts": "1.0.0-alpha.1",
96102
"@effect/platform-node": "catalog:",
103+
"@gitlab/gitlab-ai-provider": "3.6.0",
104+
"@gitlab/opencode-gitlab-auth": "1.3.3",
105+
"@hono/node-server": "1.19.11",
106+
"@hono/node-ws": "1.3.0",
97107
"@hono/standard-validator": "0.1.5",
98108
"@hono/zod-validator": "catalog:",
109+
"@lydell/node-pty": "1.2.0-beta.10",
99110
"@modelcontextprotocol/sdk": "1.27.1",
100111
"@npmcli/arborist": "9.4.0",
101112
"@octokit/graphql": "9.0.2",

packages/opencode/script/build-node.ts

100644100755
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#!/usr/bin/env bun
22

3+
import { $ } from "bun"
34
import { Script } from "@opencode-ai/script"
45
import fs from "fs"
56
import path from "path"
@@ -8,6 +9,15 @@ import { fileURLToPath } from "url"
89
const __filename = fileURLToPath(import.meta.url)
910
const __dirname = path.dirname(__filename)
1011
const dir = path.resolve(__dirname, "..")
12+
const root = path.resolve(dir, "../..")
13+
14+
function linker(): "hoisted" | "isolated" {
15+
// jsonc-parser is only declared in packages/opencode, so its install location
16+
// tells us whether Bun used a hoisted or isolated workspace layout.
17+
if (fs.existsSync(path.join(dir, "node_modules", "jsonc-parser"))) return "isolated"
18+
if (fs.existsSync(path.join(root, "node_modules", "jsonc-parser"))) return "hoisted"
19+
throw new Error("Could not detect Bun linker from jsonc-parser")
20+
}
1121

1222
process.chdir(dir)
1323

@@ -41,11 +51,16 @@ const migrations = await Promise.all(
4151
)
4252
console.log(`Loaded ${migrations.length} migrations`)
4353

54+
const link = linker()
55+
56+
await $`bun install --linker=${link} --os="*" --cpu="*" @lydell/node-pty@1.2.0-beta.10`
57+
4458
await Bun.build({
4559
target: "node",
4660
entrypoints: ["./src/node.ts"],
4761
outdir: "./dist",
4862
format: "esm",
63+
sourcemap: "linked",
4964
external: ["jsonc-parser"],
5065
define: {
5166
OPENCODE_MIGRATIONS: JSON.stringify(migrations),
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/usr/bin/env bun
2+
3+
import fs from "fs/promises"
4+
import path from "path"
5+
import { fileURLToPath } from "url"
6+
7+
const __filename = fileURLToPath(import.meta.url)
8+
const __dirname = path.dirname(__filename)
9+
const dir = path.resolve(__dirname, "..")
10+
11+
if (process.platform !== "win32") {
12+
const root = path.join(dir, "node_modules", "node-pty", "prebuilds")
13+
const dirs = await fs.readdir(root, { withFileTypes: true }).catch(() => [])
14+
const files = dirs.filter((x) => x.isDirectory()).map((x) => path.join(root, x.name, "spawn-helper"))
15+
const result = await Promise.all(
16+
files.map(async (file) => {
17+
const stat = await fs.stat(file).catch(() => undefined)
18+
if (!stat) return
19+
if ((stat.mode & 0o111) === 0o111) return
20+
await fs.chmod(file, stat.mode | 0o755)
21+
return file
22+
}),
23+
)
24+
const fixed = result.filter(Boolean)
25+
if (fixed.length) {
26+
console.log(`fixed node-pty permissions for ${fixed.length} helper${fixed.length === 1 ? "" : "s"}`)
27+
}
28+
}

packages/opencode/src/cli/cmd/acp.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export const AcpCommand = cmd({
2323
process.env.OPENCODE_CLIENT = "acp"
2424
await bootstrap(process.cwd(), async () => {
2525
const opts = await resolveNetworkOptions(args)
26-
const server = Server.listen(opts)
26+
const server = await Server.listen(opts)
2727

2828
const sdk = createOpencodeClient({
2929
baseUrl: `http://${server.hostname}:${server.port}`,

packages/opencode/src/cli/cmd/serve.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export const ServeCommand = cmd({
1515
console.log("Warning: OPENCODE_SERVER_PASSWORD is not set; server is unsecured.")
1616
}
1717
const opts = await resolveNetworkOptions(args)
18-
const server = Server.listen(opts)
18+
const server = await Server.listen(opts)
1919
console.log(`opencode server listening on http://${server.hostname}:${server.port}`)
2020

2121
await new Promise(() => {})

packages/opencode/src/cli/cmd/web.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export const WebCommand = cmd({
3737
UI.println(UI.Style.TEXT_WARNING_BOLD + "! " + "OPENCODE_SERVER_PASSWORD is not set; server is unsecured.")
3838
}
3939
const opts = await resolveNetworkOptions(args)
40-
const server = Server.listen(opts)
40+
const server = await Server.listen(opts)
4141
UI.empty()
4242
UI.println(UI.logo(" "))
4343
UI.empty()

packages/opencode/src/plugin/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@ export namespace Plugin {
130130
get serverUrl(): URL {
131131
return Server.url ?? new URL("http://localhost:4096")
132132
},
133-
$: Bun.$,
133+
// @ts-expect-error
134+
$: typeof Bun === "undefined" ? undefined : Bun.$,
134135
}
135136

136137
for (const plugin of INTERNAL_PLUGINS) {

0 commit comments

Comments
 (0)