Compare commits
2 commits
dev
...
jlongster/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fdfe599cb5 | ||
|
|
6df566161e |
3 changed files with 145 additions and 20 deletions
|
|
@ -12,22 +12,7 @@ export function useEvent() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special hack for truly global events
|
handler(event.payload)
|
||||||
if (event.directory === "global") {
|
|
||||||
handler(event.payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (project.workspace.current()) {
|
|
||||||
if (event.workspace === project.workspace.current()) {
|
|
||||||
handler(event.payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.directory === project.instance.directory()) {
|
|
||||||
handler(event.payload)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,14 +48,14 @@ export function workspaceProxyURL(target: string | URL, requestURL: URL) {
|
||||||
return proxyURL
|
return proxyURL
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getSessionWorkspace(url: URL) {
|
async function getSession(url: URL) {
|
||||||
const id = getWorkspaceRouteSessionID(url)
|
const id = getWorkspaceRouteSessionID(url)
|
||||||
if (!id) return null
|
if (!id) return null
|
||||||
|
|
||||||
const session = await AppRuntime.runPromise(
|
const session = await AppRuntime.runPromise(
|
||||||
Session.Service.use((svc) => svc.get(id)).pipe(Effect.withSpan("WorkspaceRouter.lookup")),
|
Session.Service.use((svc) => svc.get(id)).pipe(Effect.withSpan("WorkspaceRouter.lookup")),
|
||||||
).catch(() => undefined)
|
).catch(() => undefined)
|
||||||
return session?.workspaceID
|
return session
|
||||||
}
|
}
|
||||||
|
|
||||||
export function WorkspaceRouterMiddleware(upgrade: UpgradeWebSocket): MiddlewareHandler {
|
export function WorkspaceRouterMiddleware(upgrade: UpgradeWebSocket): MiddlewareHandler {
|
||||||
|
|
@ -64,10 +64,20 @@ export function WorkspaceRouterMiddleware(upgrade: UpgradeWebSocket): Middleware
|
||||||
return async (c, next) => {
|
return async (c, next) => {
|
||||||
const url = new URL(c.req.url)
|
const url = new URL(c.req.url)
|
||||||
|
|
||||||
const sessionWorkspaceID = await getSessionWorkspace(url)
|
const session = await getSession(url)
|
||||||
const workspaceID = sessionWorkspaceID || url.searchParams.get("workspace")
|
const workspaceID = session?.workspaceID || url.searchParams.get("workspace")
|
||||||
|
|
||||||
if (!workspaceID || url.pathname.startsWith("/console") || Flag.OPENCODE_WORKSPACE_ID) {
|
if (!workspaceID || url.pathname.startsWith("/console") || Flag.OPENCODE_WORKSPACE_ID) {
|
||||||
|
if (session) {
|
||||||
|
return Instance.provide({
|
||||||
|
directory: session.directory,
|
||||||
|
init: () => AppRuntime.runPromise(InstanceBootstrap),
|
||||||
|
async fn() {
|
||||||
|
return next()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
130
packages/opencode/test/server/session-routes-directory.test.ts
Normal file
130
packages/opencode/test/server/session-routes-directory.test.ts
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
import { afterEach, describe, expect, test } from "bun:test"
|
||||||
|
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||||
|
import path from "path"
|
||||||
|
import { GlobalBus, type GlobalEvent } from "../../src/bus/global"
|
||||||
|
import { Instance } from "../../src/project/instance"
|
||||||
|
import { Server } from "../../src/server/server"
|
||||||
|
import * as Log from "@opencode-ai/core/util/log"
|
||||||
|
import { resetDatabase } from "../fixture/db"
|
||||||
|
import { tmpdir } from "../fixture/fixture"
|
||||||
|
|
||||||
|
void Log.init({ print: false })
|
||||||
|
|
||||||
|
const originalHttpApi = Flag.OPENCODE_EXPERIMENTAL_HTTPAPI
|
||||||
|
type SyncTrace = { type: string; directory?: string }
|
||||||
|
|
||||||
|
function app() {
|
||||||
|
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = false
|
||||||
|
return Server.Default().app
|
||||||
|
}
|
||||||
|
|
||||||
|
function route(pathname: string, directory: string, query?: Record<string, string>) {
|
||||||
|
const url = new URL(pathname, "http://localhost")
|
||||||
|
url.searchParams.set("directory", directory)
|
||||||
|
for (const [key, value] of Object.entries(query ?? {})) {
|
||||||
|
url.searchParams.set(key, value)
|
||||||
|
}
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchJson<T>(
|
||||||
|
pathname: string,
|
||||||
|
directory: string,
|
||||||
|
init?: RequestInit,
|
||||||
|
query?: Record<string, string>,
|
||||||
|
) {
|
||||||
|
const response = await app().fetch(new Request(route(pathname, directory, query), init))
|
||||||
|
if (response.status !== 200) throw new Error(await response.text())
|
||||||
|
return (await response.json()) as T
|
||||||
|
}
|
||||||
|
|
||||||
|
function pathFor(pathname: string, params: Record<string, string>) {
|
||||||
|
return Object.entries(params).reduce((result, [key, value]) => result.replace(`:${key}`, value), pathname)
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
Flag.OPENCODE_EXPERIMENTAL_HTTPAPI = originalHttpApi
|
||||||
|
await Instance.disposeAll()
|
||||||
|
await resetDatabase()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("Hono session routes", () => {
|
||||||
|
test("use request directory for non-session routes and saved session directory for session routes", async () => {
|
||||||
|
await using sessionDir = await tmpdir({
|
||||||
|
git: true,
|
||||||
|
config: { formatter: false, lsp: false },
|
||||||
|
init: (dir) => Bun.write(path.join(dir, "marker.txt"), "session-directory"),
|
||||||
|
})
|
||||||
|
await using requestDir = await tmpdir({
|
||||||
|
git: true,
|
||||||
|
config: { formatter: false, lsp: false },
|
||||||
|
init: (dir) => Bun.write(path.join(dir, "marker.txt"), "request-directory"),
|
||||||
|
})
|
||||||
|
|
||||||
|
const json = { "content-type": "application/json" }
|
||||||
|
const trace: SyncTrace[] = []
|
||||||
|
const onEvent = (event: GlobalEvent) => {
|
||||||
|
if (event.payload.type !== "sync") return
|
||||||
|
if (!["session.created.1", "message.updated.1", "message.part.updated.1"].includes(event.payload.syncEvent.type)) return
|
||||||
|
trace.push({ type: event.payload.syncEvent.type, directory: event.directory })
|
||||||
|
}
|
||||||
|
GlobalBus.on("event", onEvent)
|
||||||
|
|
||||||
|
const session = await fetchJson<{ id: string }>("/session", sessionDir.path, {
|
||||||
|
method: "POST",
|
||||||
|
headers: json,
|
||||||
|
body: JSON.stringify({ title: "session-dir" }),
|
||||||
|
})
|
||||||
|
|
||||||
|
const currentPath = await fetchJson<{ directory: string }>("/path", requestDir.path)
|
||||||
|
expect(currentPath.directory).toBe(requestDir.path)
|
||||||
|
|
||||||
|
const marker = await fetchJson<{ type: string; content: string }>(
|
||||||
|
"/file/content",
|
||||||
|
requestDir.path,
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
path: "marker.txt",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
expect(marker).toMatchObject({ type: "text", content: "request-directory" })
|
||||||
|
|
||||||
|
await fetchJson<unknown>(pathFor("/session/:sessionID", { sessionID: session.id }), requestDir.path)
|
||||||
|
|
||||||
|
await fetchJson<unknown>(
|
||||||
|
pathFor("/session/:sessionID/fork", { sessionID: session.id }),
|
||||||
|
requestDir.path,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: json,
|
||||||
|
body: JSON.stringify({}),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
await fetchJson<{ info: { path: { cwd: string; root: string } }; parts: unknown[] }>(
|
||||||
|
pathFor("/session/:sessionID/shell", { sessionID: session.id }),
|
||||||
|
requestDir.path,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: json,
|
||||||
|
body: JSON.stringify({
|
||||||
|
agent: "build",
|
||||||
|
model: { providerID: "test", modelID: "test" },
|
||||||
|
command: "pwd",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
GlobalBus.off("event", onEvent)
|
||||||
|
|
||||||
|
expect(trace).toContainEqual({ type: "session.created.1", directory: sessionDir.path })
|
||||||
|
expect(trace.filter((event) => event.type === "session.created.1")).toEqual([
|
||||||
|
{ type: "session.created.1", directory: sessionDir.path },
|
||||||
|
{ type: "session.created.1", directory: sessionDir.path },
|
||||||
|
])
|
||||||
|
expect(trace.filter((event) => event.type === "message.updated.1").map((event) => event.directory)).toEqual(
|
||||||
|
expect.arrayContaining([sessionDir.path]),
|
||||||
|
)
|
||||||
|
expect(trace.filter((event) => event.type === "message.updated.1").every((event) => event.directory === sessionDir.path))
|
||||||
|
.toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
Add table
Add a link
Reference in a new issue