Start effect-style compaction tests

This commit is contained in:
Kit Langton
2026-05-10 12:12:00 -04:00
committed by GitHub
parent 1c9a2eb239
commit a4f3cedcdf
2 changed files with 79 additions and 29 deletions

View File

@@ -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,

View File

@@ -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 () => { it.instance(
await using tmp = await tmpdir() "throws when parent is not a user message",
await WithInstance.provide({ Effect.gen(function* () {
directory: tmp.path, const test = yield* TestInstance
fn: async () => { const ssn = yield* SessionNs.Service
const session = await svc.create({}) const session = yield* ssn.create({})
const msg = await user(session.id, "hello") const msg = yield* createUserMessage(session.id, "hello")
const reply = await assistant(session.id, msg.id, tmp.path) const reply = yield* createAssistantMessage(session.id, msg.id, test.directory)
const rt = runtime("continue") const msgs = yield* ssn.messages({ sessionID: session.id })
try {
const msgs = await svc.messages({ sessionID: session.id }) const exit = yield* Effect.exit(
await expect( SessionCompaction.use.process({
rt.runPromise( parentID: reply.id,
SessionCompaction.Service.use((svc) => messages: msgs,
svc.process({ sessionID: session.id,
parentID: reply.id, auto: false,
messages: msgs, }),
sessionID: session.id, )
auto: false,
}), expect(Exit.isFailure(exit)).toBe(true)
), if (Exit.isFailure(exit)) {
), const error = Cause.squash(exit.cause)
).rejects.toThrow(`Compaction parent must be a user message: ${reply.id}`) expect(error).toBeInstanceOf(Error)
} finally { if (error instanceof Error) {
await rt.dispose() 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()