diff --git a/packages/opencode/src/server/routes/session.ts b/packages/opencode/src/server/routes/session.ts index c33c5e989b37..6fdd65812bb3 100644 --- a/packages/opencode/src/server/routes/session.ts +++ b/packages/opencode/src/server/routes/session.ts @@ -206,9 +206,11 @@ export const SessionRoutes = lazy(() => }, }), validator("json", Session.create.schema.optional()), + validator("query", z.object({ directory: z.string().optional() })), async (c) => { const body = c.req.valid("json") ?? {} - const session = await Session.create(body) + const query = c.req.valid("query") + const session = await Session.create({ ...body, ...query }) return c.json(session) }, ) diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index 65032de96252..5b1eed7d40b5 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -27,6 +27,7 @@ import { Snapshot } from "@/snapshot" import { ProjectID } from "../project/schema" import { WorkspaceID } from "../control-plane/schema" import { SessionID, MessageID, PartID } from "./schema" +import { Filesystem } from "@/util/filesystem" import type { Provider } from "@/provider/provider" import { ModelID, ProviderID } from "@/provider/schema" @@ -314,6 +315,7 @@ export namespace Session { export interface Interface { readonly create: (input?: { + directory?: string parentID?: SessionID title?: string permission?: Permission.Ruleset @@ -493,12 +495,13 @@ export namespace Session { }).pipe(Effect.withSpan("Session.updatePart")) const create = Effect.fn("Session.create")(function* (input?: { + directory?: string parentID?: SessionID title?: string permission?: Permission.Ruleset workspaceID?: WorkspaceID }) { - const directory = yield* InstanceState.directory + const directory = input?.directory ? Filesystem.resolve(input.directory) : yield* InstanceState.directory return yield* createNext({ parentID: input?.parentID, directory, @@ -688,6 +691,7 @@ export namespace Session { export const create = fn( z .object({ + directory: z.string().optional(), parentID: SessionID.zod.optional(), title: z.string().optional(), permission: Info.shape.permission, diff --git a/packages/opencode/test/server/session-actions.test.ts b/packages/opencode/test/server/session-actions.test.ts index e6dba676ce35..596d256a13cd 100644 --- a/packages/opencode/test/server/session-actions.test.ts +++ b/packages/opencode/test/server/session-actions.test.ts @@ -1,3 +1,4 @@ +import { mkdir } from "node:fs/promises" import { afterEach, describe, expect, mock, spyOn, test } from "bun:test" import { Instance } from "../../src/project/instance" import { Server } from "../../src/server/server" @@ -5,6 +6,7 @@ import { Session } from "../../src/session" import { ModelID, ProviderID } from "../../src/provider/schema" import { MessageID, PartID, type SessionID } from "../../src/session/schema" import { SessionPrompt } from "../../src/session/prompt" +import { Filesystem } from "../../src/util/filesystem" import { Log } from "../../src/util/log" import { tmpdir } from "../fixture/fixture" @@ -35,6 +37,30 @@ async function user(sessionID: SessionID, text: string) { } describe("session action routes", () => { + test("create route accepts directory query param", async () => { + await using tmp = await tmpdir({ git: true }) + const dir = `${tmp.path}/custom-worktree` + await mkdir(dir, { recursive: true }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const app = Server.Default() + + const res = await app.request(`/session?directory=${encodeURIComponent(dir)}`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ title: "custom-dir" }), + }) + + expect(res.status).toBe(200) + const session = (await res.json()) as Session.Info + expect(session.directory).toBe(Filesystem.resolve(dir)) + + await Session.remove(session.id) + }, + }) + }) + test("abort route calls SessionPrompt.cancel", async () => { await using tmp = await tmpdir({ git: true }) await Instance.provide({