Selaa lähdekoodia

refactor(core): remove orphaned message projector paths (#40705)

Kit Langton 1 viikko sitten
vanhempi
sitoutus
0af6c82563

+ 8 - 120
packages/core/src/session/message-updater.ts

@@ -1,12 +1,8 @@
 import { castDraft, produce, type WritableDraft } from "immer"
-import { DateTime, Effect } from "effect"
+import { DateTime, Effect, Match, pipe } from "effect"
 import { SessionEvent } from "./event"
 import { SessionMessage } from "./message"
 
-export type MemoryState = {
-  messages: SessionMessage.Info[]
-}
-
 export interface Adapter {
   readonly getModel: () => Effect.Effect<SessionMessage.ModelSelected["model"] | undefined, never, never>
   readonly getCurrentAssistant: () => Effect.Effect<SessionMessage.Assistant | undefined, never, never>
@@ -23,89 +19,7 @@ export interface Adapter {
   readonly appendMessage: (message: SessionMessage.Info) => Effect.Effect<void, never, never>
 }
 
-export function memory(state: MemoryState): Adapter {
-  const assistantIndex = (messageID: SessionMessage.ID) =>
-    state.messages.findLastIndex((message) => message.id === messageID)
-  const shellIndex = (messageID: SessionMessage.ID) =>
-    state.messages.findLastIndex((message) => message.id === messageID)
-  const compactionIndex = () =>
-    state.messages.findLastIndex((message) => message.type === "compaction" && message.status === "running")
-  // A newer step supersedes stale incomplete rows; never resume an older assistant projection.
-  const latestAssistantIndex = () => state.messages.findLastIndex((message) => message.type === "assistant")
-
-  return {
-    getModel() {
-      return Effect.sync(
-        () =>
-          state.messages.findLast(
-            (message): message is SessionMessage.ModelSelected | SessionMessage.Assistant =>
-              message.type === "model-switched" || message.type === "assistant",
-          )?.model,
-      )
-    },
-    getCurrentAssistant() {
-      return Effect.sync(() => {
-        const index = latestAssistantIndex()
-        if (index < 0) return
-        const assistant = state.messages[index]
-        return assistant?.type === "assistant" && !assistant.time.completed ? assistant : undefined
-      })
-    },
-    getAssistant(messageID) {
-      return Effect.sync(() => {
-        const index = assistantIndex(messageID)
-        if (index < 0) return
-        const assistant = state.messages[index]
-        return assistant?.type === "assistant" ? assistant : undefined
-      })
-    },
-    getShell(shellID) {
-      return Effect.sync(() => {
-        return state.messages.find((message): message is SessionMessage.Shell => {
-          return message.type === "shell" && message.shellID === shellID
-        })
-      })
-    },
-    getCompaction() {
-      return Effect.sync(() => {
-        const index = compactionIndex()
-        const message = state.messages[index]
-        return message?.type === "compaction" ? message : undefined
-      })
-    },
-    updateAssistant(assistant) {
-      return Effect.sync(() => {
-        const index = assistantIndex(assistant.id)
-        if (index < 0) return
-        const current = state.messages[index]
-        if (current?.type !== "assistant") return
-        state.messages[index] = assistant
-      })
-    },
-    updateShell(shell) {
-      return Effect.sync(() => {
-        const index = shellIndex(shell.id)
-        if (index < 0) return
-        const current = state.messages[index]
-        if (current?.type !== "shell") return
-        state.messages[index] = shell
-      })
-    },
-    updateCompaction(compaction) {
-      return Effect.sync(() => {
-        const index = state.messages.findLastIndex((message) => message.id === compaction.id)
-        if (index >= 0) state.messages[index] = compaction
-      })
-    },
-    appendMessage(message) {
-      return Effect.sync(() => {
-        state.messages.push(message)
-      })
-    },
-  }
-}
-
-export function update(adapter: Adapter, event: SessionEvent.Event) {
+export function update(adapter: Adapter, event: SessionEvent.DurableEvent) {
   type DraftAssistant = WritableDraft<SessionMessage.Assistant>
   type DraftTool = WritableDraft<SessionMessage.AssistantTool>
   type DraftText = WritableDraft<SessionMessage.AssistantText>
@@ -139,9 +53,9 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
     }
   })
 
-  return Effect.gen(function* () {
-    yield* SessionEvent.All.match(event, {
-      "session.usage.updated": () => Effect.void,
+  const project = pipe(
+    Match.type<SessionEvent.DurableEvent>(),
+    Match.discriminatorsExhaustive("type")({
       "session.usage.recorded": () => Effect.void,
       "session.agent.selected": (event) => {
         return adapter.appendMessage(
@@ -321,12 +235,6 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
           draft.content.push(castDraft(SessionMessage.AssistantText.make({ type: "text", text: "" })))
         })
       },
-      "session.text.delta": (event) => {
-        return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
-          const match = latestText(draft)
-          if (match) match.text += event.data.delta
-        })
-      },
       "session.text.ended": (event) => {
         return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
           const match = latestText(draft)
@@ -351,7 +259,6 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
           )
         })
       },
-      "session.tool.input.delta": () => Effect.void,
       "session.tool.input.ended": (event) => {
         return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
           const match = latestTool(draft, event.data.id)
@@ -375,14 +282,6 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
           }
         })
       },
-      "session.tool.progress": (event) => {
-        return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
-          const match = latestTool(draft, event.data.id)
-          if (match && match.state.status === "running") {
-            match.state.metadata = event.data.metadata
-          }
-        })
-      },
       // Terminal tool events are self-contained; projection is a direct copy and
       // never reaches into ephemeral progress history.
       "session.tool.success": (event) => {
@@ -436,12 +335,6 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
           )
         })
       },
-      "session.reasoning.delta": (event) => {
-        return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
-          const match = latestReasoning(draft)
-          if (match) match.text += event.data.delta
-        })
-      },
       "session.reasoning.ended": (event) => {
         return updateOwnedAssistant(event.data.assistantMessageID, (draft) => {
           const match = latestReasoning(draft)
@@ -475,12 +368,6 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
             time: { created: event.created },
           }),
         ),
-      "session.compaction.delta": (event) =>
-        Effect.gen(function* () {
-          const current = yield* adapter.getCompaction()
-          if (current?.status !== "running") return
-          yield* adapter.updateCompaction({ ...current, summary: current.summary + event.data.text })
-        }),
       "session.compaction.ended": (event) => {
         return Effect.gen(function* () {
           const current = yield* adapter.getCompaction()
@@ -526,8 +413,9 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
       "session.revert.staged": () => Effect.void,
       "session.revert.cleared": () => Effect.void,
       "session.revert.committed": () => Effect.void,
-    })
-  })
+    }),
+  )
+  return project(event)
 }
 
 export * as SessionMessageUpdater from "./message-updater"

+ 0 - 54
packages/core/test/session-projector.test.ts

@@ -17,7 +17,6 @@ import { Session } from "@opencode-ai/core/session"
 import { SessionEvent } from "@opencode-ai/core/session/event"
 import { SessionMessage } from "@opencode-ai/core/session/message"
 import { Money } from "@opencode-ai/schema/money"
-import { SessionMessageUpdater } from "@opencode-ai/core/session/message-updater"
 import { SessionProjector } from "@opencode-ai/core/session/projector"
 import { SessionExecution } from "@opencode-ai/core/session/execution"
 import { fromRow } from "@opencode-ai/core/session/info"
@@ -126,34 +125,6 @@ describe("SessionProjector", () => {
     }),
   )
 
-  it.effect("folds live compaction deltas into running memory state", () =>
-    Effect.gen(function* () {
-      const state = {
-        messages: [
-          SessionMessage.CompactionRunning.make({
-            id: SessionMessage.ID.make("msg_compaction"),
-            type: "compaction",
-            status: "running",
-            reason: "manual",
-            summary: "partial ",
-            recent: "recent",
-            time: { created },
-          }),
-        ],
-      }
-      yield* SessionMessageUpdater.update(
-        SessionMessageUpdater.memory(state),
-        SessionEvent.Compaction.Delta.make({
-          id: Event.ID.make("evt_delta"),
-          type: "session.compaction.delta",
-          created,
-          data: { sessionID, text: "summary" },
-        }),
-      )
-      expect(state.messages[0]).toMatchObject({ status: "running", summary: "partial summary", recent: "recent" })
-    }),
-  )
-
   it.effect("projects staged, cleared, and committed reverts", () =>
     Effect.gen(function* () {
       const db = (yield* Database.Service).db
@@ -550,31 +521,6 @@ describe("SessionProjector", () => {
     }),
   )
 
-  it.effect("does not revive a stale incomplete in-memory assistant projection", () =>
-    Effect.gen(function* () {
-      const stale = SessionMessage.Assistant.make({
-        id: SessionMessage.ID.make("msg_assistant_stale"),
-        type: "assistant",
-        agent: build,
-        model,
-        content: [],
-        time: { created },
-      })
-      const completed = SessionMessage.Assistant.make({
-        id: SessionMessage.ID.make("msg_assistant_completed"),
-        type: "assistant",
-        agent: build,
-        model,
-        content: [],
-        time: { created: DateTime.makeUnsafe(1), completed: DateTime.makeUnsafe(2) },
-      })
-
-      expect(
-        yield* SessionMessageUpdater.memory({ messages: [stale, completed] }).getCurrentAssistant(),
-      ).toBeUndefined()
-    }),
-  )
-
   it.effect("projects retry state and clears it at the next step or execution terminal", () =>
     Effect.gen(function* () {
       const { db } = yield* Database.Service