Kaynağa Gözat

refactor(session): eliminate Effect.promise roundtrips for sync MessageV2.stream (#21973)

Kit Langton 1 hafta önce
ebeveyn
işleme
cd004cf0b2

+ 18 - 1
packages/opencode/src/session/index.ts

@@ -29,7 +29,7 @@ import type { Provider } from "@/provider/provider"
 import { Permission } from "@/permission"
 import { Permission } from "@/permission"
 import { Global } from "@/global"
 import { Global } from "@/global"
 import type { LanguageModelV2Usage } from "@ai-sdk/provider"
 import type { LanguageModelV2Usage } from "@ai-sdk/provider"
-import { Effect, Layer, Context } from "effect"
+import { Effect, Layer, Option, Context } from "effect"
 import { makeRuntime } from "@/effect/run-service"
 import { makeRuntime } from "@/effect/run-service"
 
 
 export namespace Session {
 export namespace Session {
@@ -352,6 +352,11 @@ export namespace Session {
       field: string
       field: string
       delta: string
       delta: string
     }) => Effect.Effect<void>
     }) => Effect.Effect<void>
+    /** Finds the first message matching the predicate, searching newest-first. */
+    readonly findMessage: (
+      sessionID: SessionID,
+      predicate: (msg: MessageV2.WithParts) => boolean,
+    ) => Effect.Effect<Option.Option<MessageV2.WithParts>>
   }
   }
 
 
   export class Service extends Context.Service<Service, Interface>()("@opencode/Session") {}
   export class Service extends Context.Service<Service, Interface>()("@opencode/Session") {}
@@ -636,6 +641,17 @@ export namespace Session {
         yield* bus.publish(MessageV2.Event.PartDelta, input)
         yield* bus.publish(MessageV2.Event.PartDelta, input)
       })
       })
 
 
+      /** Finds the first message matching the predicate, searching newest-first. */
+      const findMessage = Effect.fn("Session.findMessage")(function* (
+        sessionID: SessionID,
+        predicate: (msg: MessageV2.WithParts) => boolean,
+      ) {
+        for (const item of MessageV2.stream(sessionID)) {
+          if (predicate(item)) return Option.some(item)
+        }
+        return Option.none<MessageV2.WithParts>()
+      })
+
       return Service.of({
       return Service.of({
         create,
         create,
         fork,
         fork,
@@ -657,6 +673,7 @@ export namespace Session {
         updatePart,
         updatePart,
         getPart,
         getPart,
         updatePartDelta,
         updatePartDelta,
+        findMessage,
       })
       })
     }),
     }),
   )
   )

+ 9 - 16
packages/opencode/src/session/prompt.ts

@@ -902,12 +902,8 @@ NOTE: At any point in time through this workflow you should feel free to ask the
       })
       })
 
 
       const lastModel = Effect.fnUntraced(function* (sessionID: SessionID) {
       const lastModel = Effect.fnUntraced(function* (sessionID: SessionID) {
-        const model = yield* Effect.promise(async () => {
-          for await (const item of MessageV2.stream(sessionID)) {
-            if (item.info.role === "user" && item.info.model) return item.info.model
-          }
-        })
-        if (model) return model
+        const match = yield* sessions.findMessage(sessionID, (m) => m.info.role === "user" && !!m.info.model)
+        if (Option.isSome(match) && match.value.info.role === "user") return match.value.info.model
         return yield* provider.defaultModel()
         return yield* provider.defaultModel()
       })
       })
 
 
@@ -1290,16 +1286,13 @@ NOTE: At any point in time through this workflow you should feel free to ask the
         },
         },
       )
       )
 
 
-      const lastAssistant = (sessionID: SessionID) =>
-        Effect.promise(async () => {
-          let latest: MessageV2.WithParts | undefined
-          for await (const item of MessageV2.stream(sessionID)) {
-            latest ??= item
-            if (item.info.role !== "user") return item
-          }
-          if (latest) return latest
-          throw new Error("Impossible")
-        })
+      const lastAssistant = Effect.fnUntraced(function* (sessionID: SessionID) {
+        const match = yield* sessions.findMessage(sessionID, (m) => m.info.role !== "user")
+        if (Option.isSome(match)) return match.value
+        const msgs = yield* sessions.messages({ sessionID, limit: 1 })
+        if (msgs.length > 0) return msgs[0]
+        throw new Error("Impossible")
+      })
 
 
       const runLoop: (sessionID: SessionID) => Effect.Effect<MessageV2.WithParts> = Effect.fn("SessionPrompt.run")(
       const runLoop: (sessionID: SessionID) => Effect.Effect<MessageV2.WithParts> = Effect.fn("SessionPrompt.run")(
         function* (sessionID: SessionID) {
         function* (sessionID: SessionID) {