Something went wrong. Try again.
opencode v2 mirror
Something went wrong. Try again.
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500import { expect, test } from "bun:test"import { LLMClient, LanguageModel, Message, ToolDefinition, Usage } from "@opencode/ai"import { OpenAI } from "@opencode/ai/providers"import { Agent } from "@opencode/core/agent"import { Bus } from "@opencode/core/bus"import { Database } from "@opencode/core/database/database"import { AppNodeBuilder } from "@opencode/core/effect/app-node-builder"import { llmClient } from "@opencode/core/effect/app-node-platform"import { Instructions } from "@opencode/core/instructions/index"import { NativeCompactionPlugin } from "@opencode/core/plugin/compaction"import { PluginHooks } from "@opencode/core/plugin/hooks"import { Project } from "@opencode/core/project"import { ProjectTable } from "@opencode/core/project/sql"import { AbsolutePath } from "@opencode/core/schema"import { SessionCompaction } from "@opencode/core/session/compaction"import { SessionEvent } from "@opencode/core/session/event"import { SessionHistory } from "@opencode/core/session/history"import { SessionInbox } from "@opencode/core/session/inbox"import { InstructionState } from "@opencode/core/session/instruction-state"import { SessionMessage } from "@opencode/core/session/message"import { SessionModelRequest } from "@opencode/core/session/model-request"import { SessionProjector } from "@opencode/core/session/projector"import { SessionProviderContext } from "@opencode/core/session/provider-context"import { SessionRunnerModel } from "@opencode/core/session/runner/model"import { SessionSchema } from "@opencode/core/session/schema"import { SessionStore } from "@opencode/core/session/store"import { LayerNode } from "@opencode/util/effect/layer-node"import { DateTime, Deferred, Effect, Fiber, Schema } from "effect"import { testEffect } from "./lib/effect"import { host } from "./plugin/host"
const it = testEffect( AppNodeBuilder.build( LayerNode.group([ Database.node, Bus.node, SessionProjector.node, SessionInbox.node, SessionStore.node, SessionCompaction.node, SessionModelRequest.node, PluginHooks.node, llmClient, ]), [Bus.node.replace(Bus.configured({ persist: true }))], ),)
const setup = Effect.fnUntraced(function* (options: { endpoint?: boolean; plugin?: boolean } = {}) { const endpoint = options.endpoint ?? false const db = (yield* Database.Service).db const bus = yield* Bus.Service const inbox = yield* SessionInbox.Service const store = yield* SessionStore.Service const compaction = yield* SessionCompaction.Service const requests = yield* SessionModelRequest.Service const hooks = yield* PluginHooks.Service const blocked = Deferred.makeUnsafe<void>() const hanging = Promise.withResolvers<Response>() const state = { failure: false, flaky: false, hang: false, overflow: false, localFailure: false, calls: 0 } const bodies: Record<string, unknown>[] = [] const headers: Headers[] = [] const server = yield* Effect.acquireRelease( Effect.sync(() => Bun.serve({ hostname: "127.0.0.1", port: 0, async fetch(request) { state.calls++ headers.push(request.headers) bodies.push( Schema.decodeUnknownSync(Schema.fromJsonString(Schema.Record(Schema.String, Schema.Unknown)))( await request.text(), ), ) if (state.hang) { Deferred.doneUnsafe(blocked, Effect.void) return hanging.promise } // Persistent failures opt out of retries so the schedule's backoff stays out of these tests. if (state.failure || state.flaky) { const retry = state.flaky state.flaky = false return Response.json( { error: { message: "fixture rate limit", type: "rate_limit_error" } }, { status: 429, headers: retry ? {} : { "x-should-retry": "false" } }, ) } const trigger = JSON.stringify(bodies.at(-1)).includes("compaction_trigger") if (state.overflow && (trigger || state.localFailure)) return Response.json( { error: { message: "Your input exceeds the context window", code: "context_length_exceeded", type: "invalid_request_error", }, }, { status: 400 }, ) const checkpoint = { type: "compaction", id: `cmp_${state.calls}`, encrypted_content: `encrypted_${state.calls}`, } if (new URL(request.url).pathname.endsWith("/compact")) return Response.json({ id: "compact_endpoint", object: "response.compaction", output: [ { type: "message", role: "user", content: [{ type: "input_text", text: "endpoint retained" }] }, checkpoint, ], usage: { input_tokens: 20, output_tokens: 4, total_tokens: 24 }, }) const output = trigger ? [checkpoint] : [] const summary = state.overflow ? [ { type: "response.output_item.added", output_index: 0, item: { type: "message", id: "summary", role: "assistant", content: [] }, }, { type: "response.output_text.delta", item_id: "summary", output_index: 0, content_index: 0, delta: "## Objective\n- Recovered locally", }, ] .map((event) => `data: ${JSON.stringify(event)}\n\n`) .join("") : "" return new Response( `${summary}data: ${JSON.stringify({ type: "response.completed", response: { id: `resp_${state.calls}`, status: "completed", output, usage: { input_tokens: 20, output_tokens: 4, total_tokens: 24 }, }, })}\n\n`, { headers: { "content-type": "text/event-stream" } }, ) }, }), ), (server) => Effect.sync(() => { hanging.resolve(new Response("cancelled")) void server.stop(true) }), ) const native = OpenAI.configure({ apiKey: "fixture", baseURL: server.url.toString() }).responses("gpt-5.4-mini") const model = SessionRunnerModel.resolved( endpoint ? LanguageModel.update(native, { route: native.route.with({ compact: { endpoint: native.route.compact.endpoint } }), }) : native, { capabilities: { tools: true, input: ["text", "image"], output: ["text"] }, cost: [], limit: { context: 200_000, output: 32_000 }, compaction: { type: "native" }, }, ) const sessionID = SessionSchema.ID.create() yield* db .insert(ProjectTable) .values({ id: Project.ID.global, worktree: AbsolutePath.make("/project"), sandboxes: [] }) .run() yield* bus.publish(SessionEvent.Created, { sessionID, projectID: Project.ID.global, location: { directory: AbsolutePath.make("/project") }, slug: "native-compaction", version: "test", }) const session = yield* store.get(sessionID) if (!session) return yield* Effect.die("Missing fixture session") const instructions = Instructions.make({ key: Instructions.Key.make("test/native"), codec: Schema.toCodecJson(Schema.String), read: Effect.succeed("Current instructions"), render: { initial: String, changed: (_previous, value) => value, removed: () => "removed" }, }) yield* InstructionState.prepare(db, bus, instructions, sessionID) if (options.plugin !== false) yield* NativeCompactionPlugin.Plugin.effect(host()) yield* hooks.register("session", "model.request", (event) => Effect.sync(() => { event.headers["x-test-hook"] = event.kind }), ) yield* hooks.register("session", "http.request", (event) => Effect.sync(() => event.request.headers.set("x-http-hook", event.kind)), ) const prompt = Effect.fnUntraced(function* (text: string, synthetic = false) { const id = SessionMessage.ID.create() yield* inbox.admit({ id, sessionID, item: { type: synthetic ? "synthetic" : "user", payload: { text }, delivery: "steer" }, }) yield* bus.publish(SessionEvent.InboxDelivered, { sessionID, inboxID: id }) }) const load = Effect.gen(function* () { const history = yield* SessionHistory.preview( db, sessionID, instructions, SessionProviderContext.provenance(model) ?? "local", ) return { session, model, initial: history.initial, messages: history.messages, instructionUpdate: history.instructionUpdate, agent: { id: Agent.defaultID, info: Agent.Info.default(Agent.defaultID) }, tools: { definitions: [ ToolDefinition.make({ name: "read", description: "Read a file", inputSchema: { type: "object" } }), ], execute: () => Effect.die("Compaction must never dispatch tools"), }, } }) const compact = Effect.gen(function* () { return yield* compaction.compactManual({ session, messages: yield* store.context(sessionID), inputID: SessionMessage.ID.create(), resolveContext: () => load, prepare: requests.compaction, }) }) const checkpoint = Effect.gen(function* () { const messages = (yield* load).messages const last = messages.findLast((message) => message.type === "compaction" && message.status === "completed") if (last?.type !== "compaction" || last.status !== "completed" || !last.providerContext) return yield* Effect.die("Missing native checkpoint") expect(last.summary).toBe("") expect(last.recent).toBe("") // Provider compaction has no summary, so the request usage is the only visible cost of the operation. expect(last.tokens).toMatchObject({ input: 20, output: 4 }) return last.providerContext }) return { compact, automatic: Effect.gen(function* () { return yield* compaction.compact({ context: yield* load, prepare: requests.compaction }) }), checkpoint, prompt, load, requests, bodies, headers, state, blocked, sessionID, store, hooks, model, compaction, }})
it.live( "manual trigger persists and continues, retains earlier users repeatedly, and preserves context on failure/cancellation", () => Effect.gen(function* () { const fixture = yield* setup() yield* fixture.prompt("First real user request") yield* fixture.prompt("Synthetic context, not a user request", true) expect(yield* fixture.compact).toEqual({ status: "completed" }) const first = yield* fixture.checkpoint expect(SessionProviderContext.decode(first).map((message) => message.role)).toEqual(["user", "assistant"]) expect(JSON.stringify(first.messages)).not.toContain("Synthetic context") expect(fixture.bodies[0]).toMatchObject({ input: expect.arrayContaining([{ type: "compaction_trigger" }]), tools: [expect.objectContaining({ name: "read" })], }) expect(fixture.bodies[0]).not.toHaveProperty("context_management") expect(fixture.headers[0]?.get("x-test-hook")).toBe("compaction") expect(fixture.headers[0]?.get("x-http-hook")).toBe("compaction") yield* fixture.prompt("Second real user request") const context = yield* fixture.load const prepared = yield* fixture.requests.primary({ session: context.session, agent: context.agent.id, model: context.model, tools: context.tools, ...SessionModelRequest.baseTranscript({ ...context, agent: context.agent.info }), }) const client = yield* LLMClient.Service yield* client.generate(prepared.request, prepared.options) expect(JSON.stringify(fixture.bodies[1])).toContain("encrypted_1") expect(JSON.stringify(fixture.bodies[1])).toContain("Current instructions") expect(JSON.stringify(fixture.bodies[1])).toContain("Second real user request") expect(yield* fixture.compact).toEqual({ status: "completed" }) const second = yield* fixture.checkpoint expect( SessionProviderContext.decode(second) .filter((message) => message.role === "user") .map((message) => message.content), ).toEqual([[Message.text("First real user request")], [Message.text("Second real user request")]]) expect(JSON.stringify(second.messages)).not.toContain("encrypted_1") expect(yield* fixture.store.get(fixture.sessionID)).toMatchObject({ tokens: { input: 40, output: 8 } }) // Nothing new since the checkpoint is not compactable, exactly like a fresh local summary. expect(yield* fixture.compact).toMatchObject({ status: "failed", error: { type: "compaction.unavailable" } }) yield* fixture.prompt("Third real user request") fixture.state.failure = true expect(yield* fixture.compact).toMatchObject({ status: "failed", error: { type: "provider.rate-limit" } }) expect(fixture.state.calls).toBe(4) expect(yield* fixture.checkpoint).toEqual(second) fixture.state.failure = false fixture.state.hang = true const pending = yield* fixture.compact.pipe(Effect.forkScoped) yield* Deferred.await(fixture.blocked) yield* Fiber.interrupt(pending) expect(fixture.state.calls).toBe(5) expect(yield* fixture.checkpoint).toEqual(second) // A transient provider failure retries under the shared session policy and its plugin hook. fixture.state.hang = false const retries: PluginHooks.Domains["session"]["retry"][] = [] yield* fixture.hooks.register("session", "retry", (event) => Effect.sync(() => { retries.push(event) event.decision = { retry: true, delay: 0 } }), ) fixture.state.flaky = true expect(yield* fixture.compact).toEqual({ status: "completed" }) expect(fixture.state.calls).toBe(7) expect(retries).toMatchObject([ { agent: "build", attempt: 2, error: { type: "provider.rate-limit" }, decision: { retry: true, delay: 0 }, }, ]) expect( SessionProviderContext.decode(yield* fixture.checkpoint).filter((message) => message.role === "user"), ).toHaveLength(3) }), 15000,)
it.live("manual and automatic endpoint compaction keep the provider replacement unchanged", () => Effect.gen(function* () { const fixture = yield* setup({ endpoint: true }) yield* fixture.prompt("Original user") expect(yield* fixture.compact).toEqual({ status: "completed" }) expect(yield* fixture.automatic).toEqual({ status: "completed" }) const replacement = SessionProviderContext.decode(yield* fixture.checkpoint) expect(replacement[0]?.content).toEqual([Message.text("endpoint retained")]) expect(JSON.stringify(replacement)).not.toContain("Original user") expect(fixture.state.calls).toBe(2) expect(fixture.headers[0]?.get("x-http-hook")).toBe("compaction") expect(fixture.bodies[0]).not.toHaveProperty("context_management") }),)
it.live("only known automatic native overflow falls back locally and failed recovery retains the checkpoint", () => Effect.gen(function* () { const fixture = yield* setup() yield* fixture.prompt("Original durable request") expect(yield* fixture.compact).toEqual({ status: "completed" }) const installed = yield* fixture.checkpoint yield* fixture.prompt("Recent request") fixture.state.failure = true expect(yield* fixture.automatic).toMatchObject({ status: "failed", error: { type: "provider.rate-limit" } }) expect(fixture.state.calls).toBe(2) expect(yield* fixture.checkpoint).toEqual(installed) fixture.state.failure = false fixture.state.hang = true const pending = yield* fixture.automatic.pipe(Effect.forkScoped) yield* Deferred.await(fixture.blocked) yield* Fiber.interrupt(pending) expect((yield* fixture.load).messages.at(-1)).toMatchObject({ type: "compaction", status: "failed", error: { type: "compaction.interrupted" }, }) expect(yield* fixture.checkpoint).toEqual(installed) fixture.state.hang = false fixture.state.overflow = true fixture.state.localFailure = true expect(yield* fixture.automatic).toMatchObject({ status: "failed" }) expect(fixture.state.calls).toBe(5) expect(yield* fixture.checkpoint).toEqual(installed) expect(JSON.stringify(fixture.bodies[4])).toContain("Original durable request") expect(JSON.stringify(fixture.bodies[4])).not.toContain("encrypted_1") fixture.state.localFailure = false expect(yield* fixture.automatic).toEqual({ status: "completed", recoveredOverflow: true }) expect(fixture.state.calls).toBe(7) expect((yield* fixture.load).messages).toContainEqual( expect.objectContaining({ type: "compaction", summary: "## Objective\n- Recovered locally" }), ) }),)
it.live("compaction hooks supply the summary instead of provider compaction", () => Effect.gen(function* () { const fixture = yield* setup() yield* fixture.prompt("Original user") yield* fixture.hooks.register("session", "compaction", (event) => Effect.sync(() => { event.result = { summary: "## Objective\n- hooked summary", providerState: { responseId: "plugin" }, metadata: { plugin: "custom" }, tokens: { input: 10, output: 5, reasoning: 0, cache: { read: 0, write: 0 } }, } }), ) expect(yield* fixture.compact).toEqual({ status: "completed" }) expect(fixture.state.calls).toBe(0) expect((yield* fixture.load).messages.at(-1)).toMatchObject({ type: "compaction", status: "completed", summary: "## Objective\n- hooked summary", recent: "", providerState: { responseId: "plugin" }, metadata: { plugin: "custom" }, tokens: { input: 10, output: 5 }, }) }),)
it.live("rejects request-hook route rewrites before provider compaction", () => Effect.gen(function* () { const fixture = yield* setup() yield* fixture.prompt("Original user") yield* fixture.hooks.register("session", "model.request", (event) => Effect.sync(() => { event.baseURL = "https://another.example/v1" }), ) expect(yield* fixture.compact).toMatchObject({ status: "failed", error: { type: "provider.unsupported-operation" }, }) expect(fixture.state.calls).toBe(0) }),)
it.live("provider compaction fails without a native strategy and persists a registered strategy's window", () => Effect.gen(function* () { const fixture = yield* setup({ plugin: false }) yield* fixture.prompt("Original user") expect(yield* fixture.compact).toMatchObject({ status: "failed", error: { type: "provider.unsupported-operation", message: expect.stringContaining("openai/openai-responses") }, }) yield* fixture.compaction.transform((editor) => { editor.native(() => Effect.succeed({ replacement: [Message.assistant("plugin window")], usage: new Usage({ nonCachedInputTokens: 20, outputTokens: 4 }), }), ) }) expect(yield* fixture.compact).toEqual({ status: "completed" }) expect(fixture.state.calls).toBe(0) const installed = yield* fixture.checkpoint expect(installed.provenance).toEqual(SessionProviderContext.provenance(fixture.model)!) expect(SessionProviderContext.decode(installed)).toEqual([Message.assistant("plugin window")]) expect(yield* fixture.store.get(fixture.sessionID)).toMatchObject({ tokens: { input: 20, output: 4 } }) }),)
test("retained user budget counts attachments and drops whole oldest messages", () => { const model = SessionRunnerModel.resolved(OpenAI.responses("gpt-5.4-mini"), { capabilities: { tools: true, input: ["text", "image"], output: ["text"] }, cost: [], limit: { context: 200_000, output: 32_000 }, }) const user = (text: string) => SessionMessage.User.make({ id: SessionMessage.ID.create(), type: "user", text, time: { created: DateTime.makeUnsafe(0) }, }) const newest = { ...user("x".repeat(63_000 * 4)), files: [{ mime: "image/png", data: "aGVsbG8=", source: { type: "inline" as const } }], } expect(SessionCompaction.retainUsers([user("old"), newest], model, 64_000)).toEqual([]) expect( SessionCompaction.retainUsers([user("x".repeat(63_000 * 4)), { ...newest, text: "new" }], model, 64_000), ).toHaveLength(1)})