Explorar el Código

Start effect-style compaction tests

Kit Langton hace 3 meses
padre
commit
a4f3cedcdf

+ 3 - 0
packages/opencode/src/session/compaction.ts

@@ -18,6 +18,7 @@ import * as DateTime from "effect/DateTime"
 import { InstanceState } from "@/effect/instance-state"
 import { InstanceState } from "@/effect/instance-state"
 import { isOverflow as overflow, usable } from "./overflow"
 import { isOverflow as overflow, usable } from "./overflow"
 import { makeRuntime } from "@/effect/run-service"
 import { makeRuntime } from "@/effect/run-service"
+import { serviceUse } from "@/effect/service-use"
 import { fn } from "@/util/fn"
 import { fn } from "@/util/fn"
 import { EventV2 } from "@/v2/event"
 import { EventV2 } from "@/v2/event"
 import { SessionEvent } from "@/v2/session-event"
 import { SessionEvent } from "@/v2/session-event"
@@ -208,6 +209,8 @@ export interface Interface {
 
 
 export class Service extends Context.Service<Service, Interface>()("@opencode/SessionCompaction") {}
 export class Service extends Context.Service<Service, Interface>()("@opencode/SessionCompaction") {}
 
 
+export const use = serviceUse(Service)
+
 export const layer: Layer.Layer<
 export const layer: Layer.Layer<
   Service,
   Service,
   never,
   never,

+ 76 - 29
packages/opencode/test/session/compaction.test.ts

@@ -15,7 +15,7 @@ import { WithInstance } from "../../src/project/with-instance"
 import * as Log from "@opencode-ai/core/util/log"
 import * as Log from "@opencode-ai/core/util/log"
 import { Permission } from "../../src/permission"
 import { Permission } from "../../src/permission"
 import { Plugin } from "../../src/plugin"
 import { Plugin } from "../../src/plugin"
-import { provideTmpdirInstance, tmpdir } from "../fixture/fixture"
+import { provideTmpdirInstance, TestInstance, tmpdir } from "../fixture/fixture"
 import { Session as SessionNs } from "@/session/session"
 import { Session as SessionNs } from "@/session/session"
 import { MessageV2 } from "../../src/session/message-v2"
 import { MessageV2 } from "../../src/session/message-v2"
 import { MessageID, PartID, SessionID } from "../../src/session/schema"
 import { MessageID, PartID, SessionID } from "../../src/session/schema"
@@ -147,6 +147,53 @@ async function assistant(sessionID: SessionID, parentID: MessageID, root: string
   return msg
   return msg
 }
 }
 
 
+function createUserMessage(sessionID: SessionID, text: string) {
+  return Effect.gen(function* () {
+    const ssn = yield* SessionNs.Service
+    const msg = yield* ssn.updateMessage({
+      id: MessageID.ascending(),
+      role: "user",
+      sessionID,
+      agent: "build",
+      model: ref,
+      time: { created: Date.now() },
+    })
+    yield* ssn.updatePart({
+      id: PartID.ascending(),
+      messageID: msg.id,
+      sessionID,
+      type: "text",
+      text,
+    })
+    return msg
+  })
+}
+
+function createAssistantMessage(sessionID: SessionID, parentID: MessageID, root: string) {
+  return SessionNs.Service.use((ssn) =>
+    ssn.updateMessage({
+      id: MessageID.ascending(),
+      role: "assistant",
+      sessionID,
+      mode: "build",
+      agent: "build",
+      path: { cwd: root, root },
+      cost: 0,
+      tokens: {
+        output: 0,
+        input: 0,
+        reasoning: 0,
+        cache: { read: 0, write: 0 },
+      },
+      modelID: ref.modelID,
+      providerID: ref.providerID,
+      parentID,
+      time: { created: Date.now() },
+      finish: "end_turn",
+    }),
+  )
+}
+
 async function summaryAssistant(sessionID: SessionID, parentID: MessageID, root: string, text: string) {
 async function summaryAssistant(sessionID: SessionID, parentID: MessageID, root: string, text: string) {
   const msg: MessageV2.Assistant = {
   const msg: MessageV2.Assistant = {
     id: MessageID.ascending(),
     id: MessageID.ascending(),
@@ -805,35 +852,35 @@ describe("session.compaction.prune", () => {
 })
 })
 
 
 describe("session.compaction.process", () => {
 describe("session.compaction.process", () => {
-  test("throws when parent is not a user message", async () => {
-    await using tmp = await tmpdir()
-    await WithInstance.provide({
-      directory: tmp.path,
-      fn: async () => {
-        const session = await svc.create({})
-        const msg = await user(session.id, "hello")
-        const reply = await assistant(session.id, msg.id, tmp.path)
-        const rt = runtime("continue")
-        try {
-          const msgs = await svc.messages({ sessionID: session.id })
-          await expect(
-            rt.runPromise(
-              SessionCompaction.Service.use((svc) =>
-                svc.process({
-                  parentID: reply.id,
-                  messages: msgs,
-                  sessionID: session.id,
-                  auto: false,
-                }),
-              ),
-            ),
-          ).rejects.toThrow(`Compaction parent must be a user message: ${reply.id}`)
-        } finally {
-          await rt.dispose()
+  it.instance(
+    "throws when parent is not a user message",
+    Effect.gen(function* () {
+      const test = yield* TestInstance
+      const ssn = yield* SessionNs.Service
+      const session = yield* ssn.create({})
+      const msg = yield* createUserMessage(session.id, "hello")
+      const reply = yield* createAssistantMessage(session.id, msg.id, test.directory)
+      const msgs = yield* ssn.messages({ sessionID: session.id })
+
+      const exit = yield* Effect.exit(
+        SessionCompaction.use.process({
+          parentID: reply.id,
+          messages: msgs,
+          sessionID: session.id,
+          auto: false,
+        }),
+      )
+
+      expect(Exit.isFailure(exit)).toBe(true)
+      if (Exit.isFailure(exit)) {
+        const error = Cause.squash(exit.cause)
+        expect(error).toBeInstanceOf(Error)
+        if (error instanceof Error) {
+          expect(error.message).toContain(`Compaction parent must be a user message: ${reply.id}`)
         }
         }
-      },
-    })
-  })
+      }
+    }),
+  )
 
 
   test("publishes compacted event on continue", async () => {
   test("publishes compacted event on continue", async () => {
     await using tmp = await tmpdir()
     await using tmp = await tmpdir()