mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-24 04:01:10 -04:00
606de48d8b
Remove the steer promotion cutoff: a turn boundary now promotes every steer pending at promotion time instead of snapshotting the inbox at a captured sequence, so late-arriving steers ride along into the turn. Delete legacy projected-prompt synthesis from projectPrompted. The event-sourced session input migration wiped pre-inbox event history, so every Prompted event follows an admitted inbox row and a missing or divergent row on replay is an invariant violation. Inline the prompt equivalence predicate and update tests that seeded history with bare Prompted events to publish PromptAdmitted first.
111 lines
4.4 KiB
TypeScript
111 lines
4.4 KiB
TypeScript
import { describe, expect } from "bun:test"
|
|
import { LLMClient, LLMEvent, Model, type LLMRequest } from "@opencode-ai/llm"
|
|
import { OpenAIChat } from "@opencode-ai/llm/protocols"
|
|
import { Config } from "@opencode-ai/core/config"
|
|
import { Database } from "@opencode-ai/core/database/database"
|
|
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
|
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
|
import { EventV2 } from "@opencode-ai/core/event"
|
|
import { Job } from "@opencode-ai/core/job"
|
|
import { Location } from "@opencode-ai/core/location"
|
|
import { LocationServiceMap } from "@opencode-ai/core/location-service-map"
|
|
import type { LocationServices } from "@opencode-ai/core/location-services"
|
|
import { ProjectV2 } from "@opencode-ai/core/project"
|
|
import { AbsolutePath } from "@opencode-ai/core/schema"
|
|
import { SessionV2 } from "@opencode-ai/core/session"
|
|
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 { Prompt } from "@opencode-ai/core/session/prompt"
|
|
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
|
import { SessionExecution } from "@opencode-ai/core/session/execution"
|
|
import { SessionRunnerModel } from "@opencode-ai/core/session/runner/model"
|
|
import { SessionStore } from "@opencode-ai/core/session/store"
|
|
import { DateTime, Effect, Layer, LayerMap, Stream } from "effect"
|
|
import { testEffect } from "./lib/effect"
|
|
|
|
const location = Location.Ref.make({ directory: AbsolutePath.make("/project") })
|
|
const model = Model.make({
|
|
id: "summary-model",
|
|
provider: "test",
|
|
route: OpenAIChat.route.with({ limits: { context: 10_000, output: 1_000 } }),
|
|
})
|
|
const projects = Layer.succeed(
|
|
ProjectV2.Service,
|
|
ProjectV2.Service.of({
|
|
resolve: (directory) => Effect.succeed({ id: ProjectV2.ID.global, directory }),
|
|
directories: () => Effect.succeed([]),
|
|
commit: () => Effect.void,
|
|
}),
|
|
)
|
|
let requests: LLMRequest[] = []
|
|
const client = Layer.mock(LLMClient.Service)({
|
|
prepare: () => Effect.die("unused"),
|
|
stream: (request: LLMRequest) => {
|
|
requests.push(request)
|
|
return Stream.make(LLMEvent.textDelta({ id: "summary", text: "manual session summary" }))
|
|
},
|
|
generate: () => Effect.die("unused"),
|
|
})
|
|
const config = Layer.mock(Config.Service)({ entries: () => Effect.succeed([]) })
|
|
const models = SessionRunnerModel.layerWith(() => Effect.succeed(model))
|
|
const locations = Layer.effect(
|
|
LocationServiceMap.Service,
|
|
LayerMap.make(
|
|
() =>
|
|
// The test only needs the compaction location service used by SessionV2.compact.
|
|
// oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion
|
|
SessionCompaction.layer.pipe(
|
|
Layer.provide(client),
|
|
Layer.provide(config),
|
|
Layer.provide(models),
|
|
) as unknown as Layer.Layer<LocationServices>,
|
|
),
|
|
)
|
|
const it = testEffect(
|
|
AppNodeBuilder.build(
|
|
LayerNode.group([Database.node, EventV2.node, SessionProjector.node, SessionStore.node, SessionV2.node]),
|
|
[
|
|
[LocationServiceMap.node, locations],
|
|
[ProjectV2.node, projects],
|
|
[SessionExecution.node, SessionExecution.noopLayer],
|
|
],
|
|
),
|
|
)
|
|
|
|
describe("SessionV2.compact", () => {
|
|
it.effect("manually compacts the active session context", () =>
|
|
Effect.gen(function* () {
|
|
requests = []
|
|
const session = yield* SessionV2.Service
|
|
const events = yield* EventV2.Service
|
|
const created = yield* session.create({ location })
|
|
|
|
const messageID = SessionMessage.ID.create()
|
|
const prompt = Prompt.make({ text: "Please compact this session history." })
|
|
yield* events.publish(SessionEvent.PromptAdmitted, {
|
|
sessionID: created.id,
|
|
messageID,
|
|
timestamp: DateTime.makeUnsafe(0),
|
|
prompt,
|
|
delivery: "steer",
|
|
})
|
|
yield* events.publish(SessionEvent.Prompted, {
|
|
sessionID: created.id,
|
|
messageID,
|
|
timestamp: DateTime.makeUnsafe(0),
|
|
prompt,
|
|
delivery: "steer",
|
|
})
|
|
|
|
yield* session.compact({ sessionID: created.id })
|
|
|
|
expect(requests).toHaveLength(1)
|
|
expect(JSON.stringify(requests[0]?.messages)).toContain("Please compact this session history.")
|
|
expect(yield* session.context(created.id)).toMatchObject([
|
|
{ type: "compaction", reason: "manual", summary: "manual session summary", recent: "" },
|
|
])
|
|
}),
|
|
)
|
|
})
|