Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 79 additions & 22 deletions packages/opencode/src/server/instance/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { describeRoute, resolver, validator } from "hono-openapi"
import { Hono } from "hono"
import type { UpgradeWebSocket } from "hono/ws"
import fs from "fs/promises"
import { Effect } from "effect"
import z from "zod"
import { Format } from "../../format"
import { Instance } from "../../project/instance"
Expand Down Expand Up @@ -89,7 +90,11 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono =>
},
}),
async (c) => {
await Instance.dispose()
await AppRuntime.runPromise(
Effect.gen(function* () {
yield* Effect.promise(() => Instance.dispose())
}),
)
Comment thread
Astro-Han marked this conversation as resolved.
return c.json(true)
},
)
Expand Down Expand Up @@ -135,19 +140,26 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono =>
}),
async (c) => {
const ensureConfig = c.req.query("ensureConfig") === "true"
const config = Runtime.isPawWork()
? ensureConfig
? await PawWorkHome.ensurePrimary()
: PawWorkHome.primary()
: Global.Path.config
if (ensureConfig && !Runtime.isPawWork()) await fs.mkdir(config, { recursive: true })
return c.json({
home: Global.Path.home,
state: Global.Path.state,
config,
worktree: Instance.worktree,
directory: Instance.directory,
})
const paths = await AppRuntime.runPromise(
Effect.gen(function* () {
const config = Runtime.isPawWork()
? ensureConfig
? yield* Effect.promise(() => PawWorkHome.ensurePrimary())
: PawWorkHome.primary()
: Global.Path.config
if (ensureConfig && !Runtime.isPawWork()) {
yield* Effect.promise(() => fs.mkdir(config, { recursive: true }))
}
return {
home: Global.Path.home,
state: Global.Path.state,
config,
worktree: Instance.worktree,
directory: Instance.directory,
}
}),
)
Comment thread
Astro-Han marked this conversation as resolved.
return c.json(paths)
},
)
.get(
Expand All @@ -168,7 +180,12 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono =>
},
}),
async (c) => {
const [branch, default_branch] = await Promise.all([Vcs.branch(), Vcs.defaultBranch()])
const [branch, default_branch] = await AppRuntime.runPromise(
Effect.gen(function* () {
const vcs = yield* Vcs.Service
return yield* Effect.all([vcs.branch(), vcs.defaultBranch()], { concurrency: 2 })
}),
)
return c.json({
branch,
default_branch,
Expand Down Expand Up @@ -199,7 +216,14 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono =>
}),
),
async (c) => {
return c.json(await Vcs.diff(c.req.valid("query").mode))
const mode = c.req.valid("query").mode
const diff = await AppRuntime.runPromise(
Effect.gen(function* () {
const vcs = yield* Vcs.Service
return yield* vcs.diff(mode)
}),
)
return c.json(diff)
},
)
.get(
Expand All @@ -220,7 +244,13 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono =>
},
}),
async (c) => {
return c.json(await Vcs.status())
const status = await AppRuntime.runPromise(
Effect.gen(function* () {
const vcs = yield* Vcs.Service
return yield* vcs.status()
}),
)
return c.json(status)
},
)
.get(
Expand Down Expand Up @@ -339,7 +369,12 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono =>
},
}),
async (c) => {
const commands = await AppRuntime.runPromise(Command.Service.use((svc) => svc.list()))
const commands = await AppRuntime.runPromise(
Effect.gen(function* () {
const command = yield* Command.Service
return yield* command.list()
}),
)
return c.json(commands)
},
)
Expand All @@ -361,7 +396,12 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono =>
},
}),
async (c) => {
const modes = await Agent.list()
const modes = await AppRuntime.runPromise(
Effect.gen(function* () {
const agent = yield* Agent.Service
return yield* agent.list()
}),
)
return c.json(modes)
},
)
Expand All @@ -383,7 +423,12 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono =>
},
}),
async (c) => {
const skills = await Skill.all()
const skills = await AppRuntime.runPromise(
Effect.gen(function* () {
const skill = yield* Skill.Service
return yield* skill.all()
}),
)
return c.json(skills)
},
)
Expand All @@ -405,7 +450,13 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono =>
},
}),
async (c) => {
return c.json(await LSP.status())
const status = await AppRuntime.runPromise(
Effect.gen(function* () {
const lsp = yield* LSP.Service
return yield* lsp.status()
}),
)
return c.json(status)
},
)
.get(
Expand All @@ -426,6 +477,12 @@ export const InstanceRoutes = (upgrade: UpgradeWebSocket): Hono =>
},
}),
async (c) => {
return c.json(await AppRuntime.runPromise(Format.Service.use((svc) => svc.status())))
const status = await AppRuntime.runPromise(
Effect.gen(function* () {
const format = yield* Format.Service
return yield* format.status()
}),
)
return c.json(status)
},
)
67 changes: 67 additions & 0 deletions packages/opencode/test/server/instance-root-routes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { $ } from "bun"
import { afterEach, describe, expect, test } from "bun:test"
import fs from "fs/promises"
import path from "path"
import { Instance } from "../../src/project/instance"
import { Server } from "../../src/server/server"
import { resetDatabase } from "../fixture/db"
import { tmpdir } from "../fixture/fixture"

afterEach(async () => {
await Instance.disposeAll()
await resetDatabase()
})

describe("instance root routes", () => {
test("returns path and metadata JSON through the route runtime", async () => {
await using tmp = await tmpdir({ git: true })
const app = Server.Default().app
const headers = { "x-opencode-directory": tmp.path }

const pathResponse = await app.request("/path", { headers })
expect(pathResponse.status).toBe(200)
expect(await pathResponse.json()).toMatchObject({ directory: tmp.path, worktree: tmp.path })

for (const route of ["/agent", "/skill", "/command", "/lsp", "/formatter"]) {
const response = await app.request(route, { headers })
expect(response.status, route).toBe(200)
expect(await response.json(), route).toBeArray()
}
})

test("returns VCS JSON through the route runtime", async () => {
await using tmp = await tmpdir({ git: true })
const app = Server.Default().app
const headers = { "x-opencode-directory": tmp.path }

await fs.writeFile(path.join(tmp.path, "tracked.txt"), "original\n", "utf-8")
await $`git add tracked.txt`.cwd(tmp.path).quiet()
await $`git commit --no-gpg-sign -m "add file"`.cwd(tmp.path).quiet()
await fs.writeFile(path.join(tmp.path, "tracked.txt"), "changed\n", "utf-8")

const info = await app.request("/vcs", { headers })
expect(info.status).toBe(200)
expect(await info.json()).toMatchObject({ branch: expect.any(String) })

const diff = await app.request("/vcs/diff?mode=git", { headers })
expect(diff.status).toBe(200)
expect(await diff.json()).toEqual([
expect.objectContaining({ file: "tracked.txt", additions: 1, deletions: 1, status: "modified" }),
])

const status = await app.request("/vcs/status", { headers })
expect(status.status).toBe(200)
expect(await status.json()).toEqual([{ file: "tracked.txt", additions: 1, deletions: 1, status: "modified" }])
})

test("disposes the current instance through the route runtime", async () => {
await using tmp = await tmpdir({ git: true })
const response = await Server.Default().app.request("/instance/dispose", {
method: "POST",
headers: { "x-opencode-directory": tmp.path },
})

expect(response.status).toBe(200)
expect(await response.json()).toBe(true)
})
})
Loading