Skip to content

Commit a4f3ced

Browse files
authored
Start effect-style compaction tests
1 parent 1c9a2eb commit a4f3ced

2 files changed

Lines changed: 79 additions & 29 deletions

File tree

packages/opencode/src/session/compaction.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import * as DateTime from "effect/DateTime"
1818
import { InstanceState } from "@/effect/instance-state"
1919
import { isOverflow as overflow, usable } from "./overflow"
2020
import { makeRuntime } from "@/effect/run-service"
21+
import { serviceUse } from "@/effect/service-use"
2122
import { fn } from "@/util/fn"
2223
import { EventV2 } from "@/v2/event"
2324
import { SessionEvent } from "@/v2/session-event"
@@ -208,6 +209,8 @@ export interface Interface {
208209

209210
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionCompaction") {}
210211

212+
export const use = serviceUse(Service)
213+
211214
export const layer: Layer.Layer<
212215
Service,
213216
never,

packages/opencode/test/session/compaction.test.ts

Lines changed: 76 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { WithInstance } from "../../src/project/with-instance"
1515
import * as Log from "@opencode-ai/core/util/log"
1616
import { Permission } from "../../src/permission"
1717
import { Plugin } from "../../src/plugin"
18-
import { provideTmpdirInstance, tmpdir } from "../fixture/fixture"
18+
import { provideTmpdirInstance, TestInstance, tmpdir } from "../fixture/fixture"
1919
import { Session as SessionNs } from "@/session/session"
2020
import { MessageV2 } from "../../src/session/message-v2"
2121
import { MessageID, PartID, SessionID } from "../../src/session/schema"
@@ -147,6 +147,53 @@ async function assistant(sessionID: SessionID, parentID: MessageID, root: string
147147
return msg
148148
}
149149

150+
function createUserMessage(sessionID: SessionID, text: string) {
151+
return Effect.gen(function* () {
152+
const ssn = yield* SessionNs.Service
153+
const msg = yield* ssn.updateMessage({
154+
id: MessageID.ascending(),
155+
role: "user",
156+
sessionID,
157+
agent: "build",
158+
model: ref,
159+
time: { created: Date.now() },
160+
})
161+
yield* ssn.updatePart({
162+
id: PartID.ascending(),
163+
messageID: msg.id,
164+
sessionID,
165+
type: "text",
166+
text,
167+
})
168+
return msg
169+
})
170+
}
171+
172+
function createAssistantMessage(sessionID: SessionID, parentID: MessageID, root: string) {
173+
return SessionNs.Service.use((ssn) =>
174+
ssn.updateMessage({
175+
id: MessageID.ascending(),
176+
role: "assistant",
177+
sessionID,
178+
mode: "build",
179+
agent: "build",
180+
path: { cwd: root, root },
181+
cost: 0,
182+
tokens: {
183+
output: 0,
184+
input: 0,
185+
reasoning: 0,
186+
cache: { read: 0, write: 0 },
187+
},
188+
modelID: ref.modelID,
189+
providerID: ref.providerID,
190+
parentID,
191+
time: { created: Date.now() },
192+
finish: "end_turn",
193+
}),
194+
)
195+
}
196+
150197
async function summaryAssistant(sessionID: SessionID, parentID: MessageID, root: string, text: string) {
151198
const msg: MessageV2.Assistant = {
152199
id: MessageID.ascending(),
@@ -805,35 +852,35 @@ describe("session.compaction.prune", () => {
805852
})
806853

807854
describe("session.compaction.process", () => {
808-
test("throws when parent is not a user message", async () => {
809-
await using tmp = await tmpdir()
810-
await WithInstance.provide({
811-
directory: tmp.path,
812-
fn: async () => {
813-
const session = await svc.create({})
814-
const msg = await user(session.id, "hello")
815-
const reply = await assistant(session.id, msg.id, tmp.path)
816-
const rt = runtime("continue")
817-
try {
818-
const msgs = await svc.messages({ sessionID: session.id })
819-
await expect(
820-
rt.runPromise(
821-
SessionCompaction.Service.use((svc) =>
822-
svc.process({
823-
parentID: reply.id,
824-
messages: msgs,
825-
sessionID: session.id,
826-
auto: false,
827-
}),
828-
),
829-
),
830-
).rejects.toThrow(`Compaction parent must be a user message: ${reply.id}`)
831-
} finally {
832-
await rt.dispose()
855+
it.instance(
856+
"throws when parent is not a user message",
857+
Effect.gen(function* () {
858+
const test = yield* TestInstance
859+
const ssn = yield* SessionNs.Service
860+
const session = yield* ssn.create({})
861+
const msg = yield* createUserMessage(session.id, "hello")
862+
const reply = yield* createAssistantMessage(session.id, msg.id, test.directory)
863+
const msgs = yield* ssn.messages({ sessionID: session.id })
864+
865+
const exit = yield* Effect.exit(
866+
SessionCompaction.use.process({
867+
parentID: reply.id,
868+
messages: msgs,
869+
sessionID: session.id,
870+
auto: false,
871+
}),
872+
)
873+
874+
expect(Exit.isFailure(exit)).toBe(true)
875+
if (Exit.isFailure(exit)) {
876+
const error = Cause.squash(exit.cause)
877+
expect(error).toBeInstanceOf(Error)
878+
if (error instanceof Error) {
879+
expect(error.message).toContain(`Compaction parent must be a user message: ${reply.id}`)
833880
}
834-
},
835-
})
836-
})
881+
}
882+
}),
883+
)
837884

838885
test("publishes compacted event on continue", async () => {
839886
await using tmp = await tmpdir()

0 commit comments

Comments
 (0)