diff --git a/packages/opencode/test/server/session-list-sdk.test.ts b/packages/opencode/test/server/session-list-sdk.test.ts new file mode 100644 index 000000000000..ec0d31efa951 --- /dev/null +++ b/packages/opencode/test/server/session-list-sdk.test.ts @@ -0,0 +1,83 @@ +import { afterEach, describe, expect, test } from "bun:test" +import { mkdir } from "fs/promises" +import { createOpencodeClient as v1 } from "@opencode-ai/sdk" +import { createOpencodeClient as v2 } from "@opencode-ai/sdk/v2" +import { Instance } from "../../src/project/instance" +import { Server } from "../../src/server/server" +import { Session } from "../../src/session" +import { Log } from "../../src/util/log" +import { tmpdir } from "../fixture/fixture" + +Log.init({ print: false }) + +afterEach(async () => { + await Instance.disposeAll() +}) + +describe("session.list with sdk directory", () => { + test("v2 does not implicitly filter by current directory", async () => { + await using tmp = await tmpdir({ git: true }) + const dir = `${tmp.path}/.dmux/worktrees/a` + await mkdir(dir, { recursive: true }) + + const key = `worktree-v2-${Date.now()}` + const root = await Instance.provide({ + directory: tmp.path, + fn: async () => Session.create({ title: `${key}-root` }), + }) + const child = await Instance.provide({ + directory: dir, + fn: async () => Session.create({ title: `${key}-child` }), + }) + + const app = Server.Default().app + const fetcher = (async (input: RequestInfo | URL, init?: RequestInit) => { + const req = new Request(input, init) + return app.request(req) + }) as typeof globalThis.fetch + + const sdk = v2({ + baseUrl: "http://opencode.internal", + directory: dir, + fetch: fetcher, + }) + const res = await sdk.session.list({ search: key }) + const ids = (res.data ?? []).map((item) => item.id) + + expect(ids).toContain(root.id) + expect(ids).toContain(child.id) + }) + + test("v1 does not implicitly filter by current directory", async () => { + await using tmp = await tmpdir({ git: true }) + const dir = `${tmp.path}/.dmux/worktrees/a` + await mkdir(dir, { recursive: true }) + + const key = `worktree-v1-${Date.now()}` + const root = await Instance.provide({ + directory: tmp.path, + fn: async () => Session.create({ title: `${key}-root` }), + }) + const child = await Instance.provide({ + directory: dir, + fn: async () => Session.create({ title: `${key}-child` }), + }) + + const app = Server.Default().app + const fetcher = (async (input: RequestInfo | URL, init?: RequestInit) => { + const req = new Request(input, init) + return app.request(req) + }) as typeof globalThis.fetch + + const sdk = v1({ + baseUrl: "http://opencode.internal", + directory: dir, + fetch: fetcher, + }) + const res = await sdk.session.list() + const ids = (res.data ?? []).map((item) => item.id) + + expect(ids).toContain(root.id) + expect(ids).toContain(child.id) + }) +}) diff --git a/packages/sdk/js/src/client.ts b/packages/sdk/js/src/client.ts index 05f46382523c..e7573daa9f17 100644 --- a/packages/sdk/js/src/client.ts +++ b/packages/sdk/js/src/client.ts @@ -16,11 +16,17 @@ function pick(value: string | null, fallback?: string) { function rewrite(request: Request, directory?: string) { if (request.method !== "GET" && request.method !== "HEAD") return request + const url = new URL(request.url) + const isSessionRequest = /\/session\/?$/.test(url.pathname) + const hasExplicitSearchParameter = url.searchParams.has("directory") + const value = pick(request.headers.get("x-opencode-directory"), directory) if (!value) return request - const url = new URL(request.url) - if (!url.searchParams.has("directory")) { + // Keep implicit directory context in headers so /session is not accidentally directory-filtered. + if (isSessionRequest && !hasExplicitSearchParameter) return request + + if (!hasExplicitSearchParameter) { url.searchParams.set("directory", value) } diff --git a/packages/sdk/js/src/v2/client.ts b/packages/sdk/js/src/v2/client.ts index 67fe1de32f72..500baa35fd52 100644 --- a/packages/sdk/js/src/v2/client.ts +++ b/packages/sdk/js/src/v2/client.ts @@ -17,19 +17,26 @@ function rewrite(request: Request, values: { directory?: string; workspace?: str if (request.method !== "GET" && request.method !== "HEAD") return request const url = new URL(request.url) + const isSessionRequest = /\/session\/?$/.test(url.pathname) let changed = false for (const [name, key] of [ ["x-opencode-directory", "directory"], ["x-opencode-workspace", "workspace"], ] as const) { + const hasExplicitSearchParameter = url.searchParams.has(key) + if (isSessionRequest) { + // Keep implicit directory context in headers so /session is not accidentally directory-filtered. + if (key === "directory" && !hasExplicitSearchParameter) continue + } + const value = pick( request.headers.get(name), key === "directory" ? values.directory : values.workspace, key === "directory" ? encodeURIComponent : undefined, ) if (!value) continue - if (!url.searchParams.has(key)) { + if (!hasExplicitSearchParameter) { url.searchParams.set(key, value) } changed = true