Bladeren bron

effectify SessionStatus service (#18565)

Kit Langton 3 weken geleden
bovenliggende
commit
10a3d6c54e

+ 1 - 0
packages/opencode/specs/effect-migration.md

@@ -123,6 +123,7 @@ Fully migrated (single namespace, InstanceState where needed, flattened facade):
 - [x] `Truncate` — `tool/truncate.ts`
 - [x] `Vcs` — `project/vcs.ts`
 - [x] `Discovery` — `skill/discovery.ts`
+- [x] `SessionStatus`
 
 Still open and likely worth migrating:
 

+ 2 - 2
packages/opencode/src/server/routes/session.ts

@@ -88,8 +88,8 @@ export const SessionRoutes = lazy(() =>
         },
       }),
       async (c) => {
-        const result = SessionStatus.list()
-        return c.json(result)
+        const result = await SessionStatus.list()
+        return c.json(Object.fromEntries(result))
       },
     )
     .get(

+ 3 - 3
packages/opencode/src/session/processor.ts

@@ -57,7 +57,7 @@ export namespace SessionProcessor {
               input.abort.throwIfAborted()
               switch (value.type) {
                 case "start":
-                  SessionStatus.set(input.sessionID, { type: "busy" })
+                  await SessionStatus.set(input.sessionID, { type: "busy" })
                   break
 
                 case "reasoning-start":
@@ -368,7 +368,7 @@ export namespace SessionProcessor {
               if (retry !== undefined) {
                 attempt++
                 const delay = SessionRetry.delay(attempt, error.name === "APIError" ? error : undefined)
-                SessionStatus.set(input.sessionID, {
+                await SessionStatus.set(input.sessionID, {
                   type: "retry",
                   attempt,
                   message: retry,
@@ -382,7 +382,7 @@ export namespace SessionProcessor {
                 sessionID: input.assistantMessage.sessionID,
                 error: input.assistantMessage.error,
               })
-              SessionStatus.set(input.sessionID, { type: "idle" })
+              await SessionStatus.set(input.sessionID, { type: "idle" })
             }
           }
           if (snapshot) {

+ 5 - 5
packages/opencode/src/session/prompt.ts

@@ -257,17 +257,17 @@ export namespace SessionPrompt {
     return s[sessionID].abort.signal
   }
 
-  export function cancel(sessionID: SessionID) {
+  export async function cancel(sessionID: SessionID) {
     log.info("cancel", { sessionID })
     const s = state()
     const match = s[sessionID]
     if (!match) {
-      SessionStatus.set(sessionID, { type: "idle" })
+      await SessionStatus.set(sessionID, { type: "idle" })
       return
     }
     match.abort.abort()
     delete s[sessionID]
-    SessionStatus.set(sessionID, { type: "idle" })
+    await SessionStatus.set(sessionID, { type: "idle" })
     return
   }
 
@@ -286,7 +286,7 @@ export namespace SessionPrompt {
       })
     }
 
-    using _ = defer(() => cancel(sessionID))
+    await using _ = defer(() => cancel(sessionID))
 
     // Structured output state
     // Note: On session resumption, state is reset but outputFormat is preserved
@@ -296,7 +296,7 @@ export namespace SessionPrompt {
     let step = 0
     const session = await Session.get(sessionID)
     while (true) {
-      SessionStatus.set(sessionID, { type: "busy" })
+      await SessionStatus.set(sessionID, { type: "busy" })
       log.info("loop", { step, sessionID })
       if (abort.aborted) break
       let msgs = await MessageV2.filterCompacted(MessageV2.stream(sessionID))

+ 49 - 27
packages/opencode/src/session/status.ts

@@ -1,7 +1,9 @@
 import { BusEvent } from "@/bus/bus-event"
 import { Bus } from "@/bus"
-import { Instance } from "@/project/instance"
+import { InstanceState } from "@/effect/instance-state"
+import { makeRunPromise } from "@/effect/run-service"
 import { SessionID } from "./schema"
+import { Effect, Layer, ServiceMap } from "effect"
 import z from "zod"
 
 export namespace SessionStatus {
@@ -42,36 +44,56 @@ export namespace SessionStatus {
     ),
   }
 
-  const state = Instance.state(() => {
-    const data: Record<string, Info> = {}
-    return data
-  })
+  export interface Interface {
+    readonly get: (sessionID: SessionID) => Effect.Effect<Info>
+    readonly list: () => Effect.Effect<Map<SessionID, Info>>
+    readonly set: (sessionID: SessionID, status: Info) => Effect.Effect<void>
+  }
+
+  export class Service extends ServiceMap.Service<Service, Interface>()("@opencode/SessionStatus") {}
+
+  export const layer = Layer.effect(
+    Service,
+    Effect.gen(function* () {
+      const state = yield* InstanceState.make(
+        Effect.fn("SessionStatus.state")(() => Effect.succeed(new Map<SessionID, Info>())),
+      )
+
+      const get = Effect.fn("SessionStatus.get")(function* (sessionID: SessionID) {
+        const data = yield* InstanceState.get(state)
+        return data.get(sessionID) ?? { type: "idle" as const }
+      })
+
+      const list = Effect.fn("SessionStatus.list")(function* () {
+        return new Map(yield* InstanceState.get(state))
+      })
 
-  export function get(sessionID: SessionID) {
-    return (
-      state()[sessionID] ?? {
-        type: "idle",
-      }
-    )
+      const set = Effect.fn("SessionStatus.set")(function* (sessionID: SessionID, status: Info) {
+        const data = yield* InstanceState.get(state)
+        yield* Effect.promise(() => Bus.publish(Event.Status, { sessionID, status }))
+        if (status.type === "idle") {
+          yield* Effect.promise(() => Bus.publish(Event.Idle, { sessionID }))
+          data.delete(sessionID)
+          return
+        }
+        data.set(sessionID, status)
+      })
+
+      return Service.of({ get, list, set })
+    }),
+  )
+
+  const runPromise = makeRunPromise(Service, layer)
+
+  export async function get(sessionID: SessionID) {
+    return runPromise((svc) => svc.get(sessionID))
   }
 
-  export function list() {
-    return state()
+  export async function list() {
+    return runPromise((svc) => svc.list())
   }
 
-  export function set(sessionID: SessionID, status: Info) {
-    Bus.publish(Event.Status, {
-      sessionID,
-      status,
-    })
-    if (status.type === "idle") {
-      // deprecated
-      Bus.publish(Event.Idle, {
-        sessionID,
-      })
-      delete state()[sessionID]
-      return
-    }
-    state()[sessionID] = status
+  export async function set(sessionID: SessionID, status: Info) {
+    return runPromise((svc) => svc.set(sessionID, status))
   }
 }