mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-10 04:18:30 +00:00
fix(sdk): wake embedded session execution (#33992)
This commit is contained in:
parent
a1f093a748
commit
40ecfceec2
9 changed files with 53 additions and 19 deletions
|
|
@ -6,9 +6,9 @@ import { Git } from "../git"
|
|||
import { Location } from "../location"
|
||||
import { ProjectV2 } from "../project"
|
||||
import { SessionV2 } from "../session"
|
||||
import { SessionExecution } from "../session/execution"
|
||||
import { SessionEvent } from "../session/event"
|
||||
import { SessionSchema } from "../session/schema"
|
||||
import { SessionStore } from "../session/store"
|
||||
import { AbsolutePath, RelativePath } from "../schema"
|
||||
import path from "path"
|
||||
|
||||
|
|
@ -71,10 +71,11 @@ export const layer = Layer.effect(
|
|||
const git = yield* Git.Service
|
||||
const events = yield* EventV2.Service
|
||||
const project = yield* ProjectV2.Service
|
||||
const session = yield* SessionV2.Service
|
||||
const sessions = yield* SessionStore.Service
|
||||
|
||||
const moveSession = Effect.fn("MoveSession.moveSession")(function* (input: Input) {
|
||||
const current = yield* session.get(input.sessionID)
|
||||
const current = yield* sessions.get(input.sessionID)
|
||||
if (!current) return yield* new SessionV2.NotFoundError({ sessionID: input.sessionID })
|
||||
const directory = AbsolutePath.make(input.destination.directory)
|
||||
if (current.location.directory === directory) return
|
||||
|
||||
|
|
@ -143,6 +144,5 @@ export const defaultLayer = layer.pipe(
|
|||
Layer.provide(Git.defaultLayer),
|
||||
Layer.provide(EventV2.defaultLayer),
|
||||
Layer.provide(ProjectV2.defaultLayer),
|
||||
Layer.provide(SessionExecution.noopLayer),
|
||||
Layer.provide(SessionV2.defaultLayer),
|
||||
Layer.provide(SessionStore.defaultLayer),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -443,7 +443,6 @@ export const defaultLayer = layer.pipe(
|
|||
Layer.provide(
|
||||
Layer.unwrap(Effect.promise(() => import("./location-layer")).pipe(Effect.map((m) => m.LocationServiceMap.layer))),
|
||||
),
|
||||
Layer.provide(SessionExecution.noopLayer),
|
||||
Layer.provide(SessionStore.defaultLayer),
|
||||
Layer.provide(SessionProjector.defaultLayer),
|
||||
Layer.provide(EventV2.defaultLayer),
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ const layer = MoveSession.layer.pipe(
|
|||
Layer.provide(Git.defaultLayer),
|
||||
Layer.provide(EventV2.defaultLayer),
|
||||
Layer.provide(project),
|
||||
Layer.provide(sessions),
|
||||
Layer.provide(SessionStore.defaultLayer),
|
||||
)
|
||||
const it = testEffect(
|
||||
Layer.mergeAll(
|
||||
|
|
@ -53,7 +53,6 @@ const it = testEffect(
|
|||
project,
|
||||
SessionProjector.defaultLayer,
|
||||
SessionStore.defaultLayer,
|
||||
SessionExecution.noopLayer,
|
||||
sessions,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ import { Database } from "@opencode-ai/core/database/database"
|
|||
import { makeRuntime } from "@opencode-ai/core/effect/runtime"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { SessionV2 } from "@opencode-ai/core/session"
|
||||
import { SessionExecution } from "@opencode-ai/core/session/execution"
|
||||
import * as SessionExecutionLocal from "@opencode-ai/core/session/execution/local"
|
||||
import { LocationServiceMap } from "@opencode-ai/core/location-layer"
|
||||
|
||||
import { NotFoundError } from "@/storage/storage"
|
||||
import { eq } from "drizzle-orm"
|
||||
|
|
@ -943,8 +944,12 @@ export const defaultLayer = layer.pipe(
|
|||
Layer.provide(BackgroundJob.defaultLayer),
|
||||
Layer.provide(Database.defaultLayer),
|
||||
Layer.provide(EventV2Bridge.defaultLayer),
|
||||
Layer.provide(SessionExecution.noopLayer),
|
||||
Layer.provide(SessionV2.defaultLayer),
|
||||
Layer.provide(
|
||||
SessionV2.defaultLayer.pipe(
|
||||
Layer.provide(SessionExecutionLocal.defaultLayer),
|
||||
Layer.provide(LocationServiceMap.layer),
|
||||
),
|
||||
),
|
||||
Layer.provide(RuntimeFlags.defaultLayer),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import { resetDatabase } from "../fixture/db"
|
|||
import { disposeAllInstances, provideInstanceEffect, TestInstance, tmpdirScoped } from "../fixture/fixture"
|
||||
import { TestLLMServer } from "../lib/llm-server"
|
||||
import { testProviderConfig } from "../lib/test-provider"
|
||||
import { testEffect } from "../lib/effect"
|
||||
import { pollWithTimeout, testEffect } from "../lib/effect"
|
||||
|
||||
const originalWorkspaces = Flag.OPENCODE_EXPERIMENTAL_WORKSPACES
|
||||
const workspaceLayer = Workspace.defaultLayer.pipe(
|
||||
|
|
@ -581,7 +581,7 @@ describe("session HttpApi", () => {
|
|||
request(`/api/session/${session.id}/prompt`, {
|
||||
method: "POST",
|
||||
headers: { ...headers, "content-type": "application/json" },
|
||||
body: JSON.stringify({ id: "msg_http_prompt", prompt: { text: "hello" } }),
|
||||
body: JSON.stringify({ id: "msg_http_prompt", prompt: { text: "hello" }, resume: false }),
|
||||
})
|
||||
const first = yield* recordPrompt()
|
||||
const retried = yield* recordPrompt()
|
||||
|
|
@ -624,6 +624,22 @@ describe("session HttpApi", () => {
|
|||
message: "Prompt message ID conflicts with an existing durable record: msg_http_prompt",
|
||||
resource: "msg_http_prompt",
|
||||
})
|
||||
|
||||
const wakeID = SessionMessage.ID.make("msg_http_wake")
|
||||
const wake = yield* request(`/api/session/${session.id}/prompt`, {
|
||||
method: "POST",
|
||||
headers: { ...headers, "content-type": "application/json" },
|
||||
body: JSON.stringify({ id: wakeID, prompt: { text: "hello again" } }),
|
||||
})
|
||||
expect(wake.status).toBe(200)
|
||||
const message = yield* pollWithTimeout(
|
||||
requestJson<{ data: SessionMessage.Message[] }>(`/api/session/${session.id}/message`, { headers }).pipe(
|
||||
Effect.map(({ data }) => data.find((message) => message.id === wakeID)),
|
||||
),
|
||||
"V2 prompt was not promoted after wake",
|
||||
"10 seconds",
|
||||
)
|
||||
expect(message).toMatchObject({ id: wakeID, type: "user" })
|
||||
}),
|
||||
{ git: true, config: { formatter: false, lsp: false } },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -613,8 +613,7 @@ describe("session.compaction.create", () => {
|
|||
})
|
||||
|
||||
const v2 = yield* SessionV2.Service.use((svc) => svc.messages({ sessionID: info.id })).pipe(
|
||||
Effect.provide(SessionExecution.noopLayer),
|
||||
Effect.provide(SessionV2.defaultLayer),
|
||||
Effect.provide(SessionV2.defaultLayer.pipe(Layer.provide(SessionExecution.noopLayer))),
|
||||
)
|
||||
expect(v2.at(-1)).toMatchObject({
|
||||
type: "compaction",
|
||||
|
|
|
|||
|
|
@ -697,8 +697,7 @@ noLLMServer.instance.skip(
|
|||
})
|
||||
|
||||
const messages = yield* SessionV2.Service.use((session) => session.messages({ sessionID: chat.id })).pipe(
|
||||
Effect.provide(SessionExecution.noopLayer),
|
||||
Effect.provide(SessionV2.defaultLayer),
|
||||
Effect.provide(SessionV2.defaultLayer.pipe(Layer.provide(SessionExecution.noopLayer))),
|
||||
)
|
||||
const { db } = yield* Database.Service
|
||||
const row = yield* db
|
||||
|
|
|
|||
|
|
@ -39,6 +39,17 @@ test("embedded client uses the real router and handlers", async () => {
|
|||
resume: false,
|
||||
})
|
||||
const context = yield* opencode.sessions.context({ sessionID })
|
||||
const wake = yield* opencode.sessions.prompt({
|
||||
sessionID,
|
||||
prompt: Prompt.make({ text: "Promote this input" }),
|
||||
})
|
||||
const prompted = yield* opencode.sessions.events({ sessionID }).pipe(
|
||||
Stream.filter((event) => event.type === "session.next.prompted" && event.data.messageID === wake.id),
|
||||
Stream.runHead,
|
||||
Effect.timeout("10 seconds"),
|
||||
Effect.map(Option.getOrThrow),
|
||||
)
|
||||
const wakeContext = yield* opencode.sessions.context({ sessionID })
|
||||
const event = yield* opencode.sessions
|
||||
.events({ sessionID })
|
||||
.pipe(Stream.take(1), Stream.runHead, Effect.map(Option.getOrUndefined))
|
||||
|
|
@ -71,6 +82,8 @@ test("embedded client uses the real router and handlers", async () => {
|
|||
expect(selected.model?.providerID).toBe(model.providerID)
|
||||
expect(page.data.some((session) => session.id === sessionID)).toBe(true)
|
||||
expect(admitted.sessionID).toBe(sessionID)
|
||||
expect(prompted.type).toBe("session.next.prompted")
|
||||
expect(wakeContext).toContainEqual(expect.objectContaining({ id: wake.id, type: "user" }))
|
||||
expect(context.some((message) => message.type === "model-switched")).toBe(true)
|
||||
expect(event).toMatchObject({ type: "session.next.model.switched", durable: { seq: 1 } })
|
||||
expect(message).toEqual(modelMessage)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,11 @@ import { CredentialHandler } from "./handlers/credential"
|
|||
import { Credential } from "@opencode-ai/core/credential"
|
||||
import { ProjectCopyHandler } from "./handlers/project-copy"
|
||||
|
||||
const sessionLayer = SessionV2.defaultLayer.pipe(
|
||||
Layer.provide(SessionExecutionLocal.defaultLayer),
|
||||
Layer.provide(LocationServiceMap.layer),
|
||||
)
|
||||
|
||||
export const handlers = Layer.mergeAll(
|
||||
HealthHandler,
|
||||
LocationHandler,
|
||||
|
|
@ -48,8 +53,7 @@ export const handlers = Layer.mergeAll(
|
|||
).pipe(
|
||||
Layer.provide(sessionLocationLayer),
|
||||
Layer.provide(locationLayer),
|
||||
Layer.provide(SessionV2.defaultLayer),
|
||||
Layer.provide(SessionExecutionLocal.defaultLayer),
|
||||
Layer.provide(sessionLayer),
|
||||
Layer.provide(PermissionSaved.defaultLayer),
|
||||
Layer.provide(PtyTicket.defaultLayer),
|
||||
Layer.provide(LocationServiceMap.layer),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue