mirror of
https://gitee.com/zhangsken/opencode.git
synced 2026-10-16 10:30:01 +08:00
102 lines
4.7 KiB
TypeScript
102 lines
4.7 KiB
TypeScript
import { expect, test } from "bun:test"
|
|
import { Effect, Schema } from "effect"
|
|
import { CompactionPart, CompactionResponse, LLMEvent, LLMResponse, Message, ProviderID } from "../src/schema/index.js"
|
|
import { LLM, LLMClient, LLMRequest, LanguageModel } from "../src/index.js"
|
|
import { OpenAI, Anthropic } from "../src/providers.js"
|
|
import { testEffect } from "./lib/effect.js"
|
|
import { fixedResponse } from "./lib/http.js"
|
|
|
|
test("runtime capability checks follow model and route updates", () => {
|
|
const supported = OpenAI.configure({ apiKey: "test" }).responses("fixture")
|
|
const unsupported = Anthropic.configure({ apiKey: "test" }).model("fixture")
|
|
const request = LLM.request({ model: supported, prompt: "hello" })
|
|
expect(LLMClient.canCompact(request)).toBe(true)
|
|
expect(LLMClient.canCompact(LLMRequest.update(request, { messages: [] }))).toBe(true)
|
|
expect(LLMClient.canCompact(LLMRequest.update(request, { model: unsupported }))).toBe(false)
|
|
expect(
|
|
LLMClient.canCompact(LLM.request({ model: LanguageModel.update(supported, { route: unsupported.route }) })),
|
|
).toBe(false)
|
|
expect(LLMClient.canCompact(LLM.request({ model: LanguageModel.update(supported, { route: undefined }) }))).toBe(true)
|
|
})
|
|
|
|
test("explicit compaction serializes a replacement window without a messages alias", () => {
|
|
const response = new CompactionResponse({
|
|
replacement: [
|
|
Message.user("retained input"),
|
|
Message.assistant(CompactionPart.make({ provider: ProviderID.make("openai"), encrypted: "checkpoint" })),
|
|
],
|
|
})
|
|
const codec = Schema.fromJsonString(CompactionResponse)
|
|
const decoded = Schema.decodeSync(codec)(Schema.encodeSync(codec)(response))
|
|
expect(decoded.replacement).toEqual(response.replacement)
|
|
expect("messages" in decoded).toBe(false)
|
|
})
|
|
|
|
test("compaction survives event assembly and message serialization without becoming text", () => {
|
|
const part = CompactionPart.make({
|
|
provider: ProviderID.make("openai"),
|
|
id: "cmp_1",
|
|
encrypted: "opaque",
|
|
})
|
|
const response = LLMResponse.fromEvents([
|
|
LLMEvent.textStart({ id: "before" }),
|
|
LLMEvent.textDelta({ id: "before", text: "Before" }),
|
|
LLMEvent.textEnd({ id: "before" }),
|
|
part,
|
|
LLMEvent.textStart({ id: "after" }),
|
|
LLMEvent.textDelta({ id: "after", text: "After" }),
|
|
LLMEvent.textEnd({ id: "after" }),
|
|
LLMEvent.finish({ reason: { normalized: "stop" } }),
|
|
])!
|
|
expect(response.message.content.map((part) => part.type)).toEqual(["text", "compaction", "text"])
|
|
expect(response.text).toBe("BeforeAfter")
|
|
expect(response.reasoning).toBe("")
|
|
expect(response.events.filter(LLMEvent.is.compaction)).toEqual([part])
|
|
const codec = Schema.fromJsonString(Message)
|
|
expect(Schema.decodeSync(codec)(Schema.encodeSync(codec)(response.message))).toEqual(response.message)
|
|
})
|
|
|
|
test("compaction requires exactly one typed representation", () => {
|
|
const provider = ProviderID.make("anthropic")
|
|
expect(CompactionPart.make({ provider, text: null })).toEqual({ type: "compaction", provider, text: null })
|
|
const decode = Schema.decodeUnknownSync(CompactionPart)
|
|
expect(() => decode({ type: "compaction", provider })).toThrow()
|
|
expect(() => decode({ type: "compaction", provider, text: "summary", encrypted: "opaque" })).toThrow()
|
|
})
|
|
|
|
test("tagged content and event guards accept both checkpoint representations", () => {
|
|
for (const part of [
|
|
CompactionPart.make({ provider: ProviderID.make("openai"), encrypted: "opaque" }),
|
|
CompactionPart.make({ provider: ProviderID.make("anthropic"), text: "summary" }),
|
|
CompactionPart.make({ provider: ProviderID.make("anthropic"), text: null }),
|
|
]) {
|
|
expect(LLMEvent.is.compaction(part)).toBe(true)
|
|
expect(LLMEvent.guards.compaction(part)).toBe(true)
|
|
const codec = Schema.fromJsonString(Message)
|
|
const message = Message.assistant(part)
|
|
expect(Schema.decodeSync(codec)(Schema.encodeSync(codec)(message))).toEqual(message)
|
|
}
|
|
})
|
|
|
|
testEffect(fixedResponse("")).effect(
|
|
"explicit compaction on a route without a compact endpoint fails with UnsupportedOperation",
|
|
() =>
|
|
Effect.gen(function* () {
|
|
const request = LLM.request({
|
|
model: Anthropic.configure({ apiKey: "test" }).model("fixture"),
|
|
prompt: "hello",
|
|
})
|
|
expect(LLMClient.canCompact(request)).toBe(false)
|
|
const error = yield* LLMClient.compact(request as unknown as Parameters<typeof LLMClient.compact>[0]).pipe(
|
|
Effect.flip,
|
|
)
|
|
expect(error.reason._tag).toBe("UnsupportedOperation")
|
|
expect(error.message).toContain("does not support explicit compaction")
|
|
if (error.reason._tag === "UnsupportedOperation") {
|
|
expect(error.reason.operation).toBe("compact")
|
|
expect(error.reason.provider).toBe("anthropic")
|
|
expect(error.reason.route).toBe("anthropic-messages")
|
|
}
|
|
}),
|
|
)
|