mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-24 14:43:37 -04:00
9a1de86d9c
Co-authored-by: rekram1-node <rekram1-node@users.noreply.github.com>
168 lines
6.2 KiB
TypeScript
168 lines
6.2 KiB
TypeScript
import { describe, expect } from "bun:test"
|
|
import { LanguageModel, LLMClient, LLMEvent } from "@opencode-ai/ai"
|
|
import { OpenAIChat } from "@opencode-ai/ai/protocols"
|
|
import { Bus } from "@opencode-ai/core/bus"
|
|
import { Config } from "@opencode-ai/core/config"
|
|
import { ConfigCompactionPlugin } from "@opencode-ai/core/config/plugin/compaction"
|
|
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
|
import { llmClient } from "@opencode-ai/core/effect/app-node-platform"
|
|
import { SessionCompaction } from "@opencode-ai/core/session/compaction"
|
|
import { SessionEvent } from "@opencode-ai/core/session/event"
|
|
import { SessionMessage } from "@opencode-ai/core/session/message"
|
|
import { SessionRunnerModel } from "@opencode-ai/core/session/runner/model"
|
|
import { Session } from "@opencode-ai/core/session"
|
|
import { Agent } from "@opencode-ai/core/agent"
|
|
import { Location } from "@opencode-ai/core/location"
|
|
import { Project } from "@opencode-ai/core/project"
|
|
import { AbsolutePath } from "@opencode-ai/core/schema"
|
|
import { ConfigCompaction } from "@opencode-ai/schema/config/compaction"
|
|
import { Document, Event, Info } from "@opencode-ai/schema/config"
|
|
import { Money } from "@opencode-ai/schema/money"
|
|
import { LayerNode } from "@opencode-ai/util/effect/layer-node"
|
|
import { DateTime, Effect, Fiber, Layer, Option, Schema, Stream } from "effect"
|
|
import { testEffect } from "../lib/effect"
|
|
import { host } from "../plugin/host"
|
|
|
|
const model = LanguageModel.make({
|
|
id: "test-model",
|
|
provider: "test-provider",
|
|
route: OpenAIChat.route,
|
|
})
|
|
const limit = { context: 100_000, output: 1_000 }
|
|
const resolved = SessionRunnerModel.resolved(model, {
|
|
capabilities: { tools: true, input: ["text"], output: ["text"] },
|
|
cost: [],
|
|
limit,
|
|
})
|
|
const config = Config.testLayer()
|
|
const it = testEffect(
|
|
Layer.merge(
|
|
config,
|
|
AppNodeBuilder.build(LayerNode.group([SessionCompaction.node, Config.node, Bus.node]), [
|
|
[
|
|
llmClient,
|
|
Layer.mock(LLMClient.Service)({
|
|
stream: () => Stream.make(LLMEvent.textDelta({ id: "summary", text: "summary" })),
|
|
}),
|
|
],
|
|
[
|
|
SessionRunnerModel.node,
|
|
Layer.mock(SessionRunnerModel.Service)({
|
|
resolve: () => Effect.succeed(resolved),
|
|
}),
|
|
],
|
|
[Config.node, config],
|
|
]),
|
|
),
|
|
)
|
|
describe("ConfigCompactionPlugin.Plugin", () => {
|
|
it.live("merges settings and reloads changed config", () =>
|
|
Effect.gen(function* () {
|
|
const compaction = yield* SessionCompaction.Service
|
|
const config = yield* Config.Test
|
|
const bus = yield* Bus.Service
|
|
yield* config.setEntries([
|
|
new Document({
|
|
type: "document",
|
|
info: new Info({ compaction: new ConfigCompaction.Info({ auto: false, buffer: 20_000 }) }),
|
|
}),
|
|
new Document({
|
|
type: "document",
|
|
info: new Info({
|
|
compaction: new ConfigCompaction.Info({
|
|
buffer: 10_000,
|
|
keep: new ConfigCompaction.Keep({ tokens: 0 }),
|
|
}),
|
|
}),
|
|
}),
|
|
])
|
|
yield* ConfigCompactionPlugin.Plugin.effect(host({ event: { subscribe: () => bus.subscribe(Event.Updated) } }))
|
|
|
|
expect(compaction.required(nearInput)).toBe(false)
|
|
const started = yield* bus
|
|
.subscribe(SessionEvent.Compaction.Started)
|
|
.pipe(Stream.runHead, Effect.forkScoped({ startImmediately: true }))
|
|
expect(
|
|
yield* compaction.compactManual({
|
|
session,
|
|
messages: [
|
|
{
|
|
id: SessionMessage.ID.create(),
|
|
type: "user",
|
|
text: "Older context",
|
|
time: { created: DateTime.makeUnsafe(0) },
|
|
},
|
|
{
|
|
id: SessionMessage.ID.create(),
|
|
type: "user",
|
|
text: "Recent context",
|
|
time: { created: DateTime.makeUnsafe(1) },
|
|
},
|
|
],
|
|
inputID: SessionMessage.ID.make("msg_compaction_manual"),
|
|
}),
|
|
).toEqual({ status: "completed" })
|
|
expect(Option.getOrThrow(yield* Fiber.join(started)).data.recent).toContain("Recent context")
|
|
|
|
yield* config.setEntries([
|
|
new Document({
|
|
type: "document",
|
|
info: new Info({ compaction: new ConfigCompaction.Info({ auto: true, buffer: 20_000 }) }),
|
|
}),
|
|
new Document({
|
|
type: "document",
|
|
info: new Info({ compaction: new ConfigCompaction.Info({ buffer: 10_000 }) }),
|
|
}),
|
|
])
|
|
yield* bus.publish(Event.Updated, {})
|
|
yield* Effect.gen(function* () {
|
|
for (let attempt = 0; attempt < 200; attempt++) {
|
|
if (compaction.required(nearInput)) return
|
|
yield* Effect.sleep("10 millis")
|
|
}
|
|
yield* Effect.die(new Error("Timed out waiting for compaction config reload"))
|
|
})
|
|
expect(compaction.required(bufferedInput)).toBe(false)
|
|
|
|
yield* config.setEntries([
|
|
new Document({
|
|
type: "document",
|
|
info: new Info({ compaction: new ConfigCompaction.Info({ auto: true, buffer: 20_000 }) }),
|
|
}),
|
|
])
|
|
yield* bus.publish(Event.Updated, {})
|
|
for (let attempt = 0; attempt < 200; attempt++) {
|
|
if (compaction.required(bufferedInput)) return
|
|
yield* Effect.sleep("10 millis")
|
|
}
|
|
yield* Effect.die(new Error("Timed out waiting for compaction config reload"))
|
|
}),
|
|
)
|
|
})
|
|
|
|
const session = Session.Info.make({
|
|
id: Session.ID.make("ses_compaction_config"),
|
|
projectID: Project.ID.global,
|
|
cost: Money.USD.zero,
|
|
tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
|
|
time: { created: DateTime.makeUnsafe(0), updated: DateTime.makeUnsafe(0) },
|
|
location: Location.Ref.make({ directory: AbsolutePath.make("/tmp") }),
|
|
})
|
|
const input = (tokens: number) => ({
|
|
session,
|
|
resolved,
|
|
messages: [
|
|
Schema.decodeUnknownSync(SessionMessage.Assistant)({
|
|
id: SessionMessage.ID.make("msg_compaction_config"),
|
|
type: "assistant",
|
|
agent: Agent.defaultID,
|
|
model: { id: "test-model", providerID: "test-provider" },
|
|
content: [],
|
|
tokens: { input: tokens, output: 0, reasoning: 0, cache: { read: 0, write: 0 } },
|
|
time: { created: 0, completed: 0 },
|
|
}),
|
|
],
|
|
})
|
|
const bufferedInput = input(85_000)
|
|
const nearInput = input(95_000)
|