Skip to content
Open
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
83 changes: 83 additions & 0 deletions packages/opencode/test/server/session-list-sdk.test.ts
Original file line number Diff line number Diff line change
@@ -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)
})
})
10 changes: 8 additions & 2 deletions packages/sdk/js/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
9 changes: 8 additions & 1 deletion packages/sdk/js/src/v2/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading