diff --git a/packages/opencode/src/server/instance/session.ts b/packages/opencode/src/server/instance/session.ts index b28db3a8941d..5ef4b68fc714 100644 --- a/packages/opencode/src/server/instance/session.ts +++ b/packages/opencode/src/server/instance/session.ts @@ -474,10 +474,14 @@ export const SessionRoutes = lazy(() => async (c) => { const query = c.req.valid("query") const params = c.req.valid("param") - const result = await SessionSummary.diff({ - sessionID: params.sessionID, - messageID: query.messageID, - }) + const result = await AppRuntime.runPromise( + SessionSummary.Service.use((summary) => + summary.diff({ + sessionID: params.sessionID, + messageID: query.messageID, + }), + ), + ) return c.json(result) }, ) diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index be0977c1ddd2..a544e4b456cd 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -1,4 +1,4 @@ -import { Cause, Deferred, Effect, Layer, Context } from "effect" +import { Cause, Deferred, Effect, Layer, Context, Scope } from "effect" import * as Stream from "effect/Stream" import { Agent } from "@/agent/agent" import { Bus } from "@/bus" @@ -89,6 +89,7 @@ export namespace SessionProcessor { | LLM.Service | Permission.Service | Plugin.Service + | SessionSummary.Service | SessionStatus.Service > = Layer.effect( Service, @@ -101,6 +102,8 @@ export namespace SessionProcessor { const llm = yield* LLM.Service const permission = yield* Permission.Service const plugin = yield* Plugin.Service + const summary = yield* SessionSummary.Service + const scope = yield* Scope.Scope const status = yield* SessionStatus.Service const create = Effect.fn("SessionProcessor.create")(function* (input: Input) { @@ -385,10 +388,12 @@ export namespace SessionProcessor { } ctx.snapshot = undefined } - SessionSummary.summarize({ - sessionID: ctx.sessionID, - messageID: ctx.assistantMessage.parentID, - }) + yield* summary + .summarize({ + sessionID: ctx.sessionID, + messageID: ctx.assistantMessage.parentID, + }) + .pipe(Effect.ignore, Effect.forkIn(scope)) if ( !ctx.assistantMessage.summary && isOverflow({ cfg: yield* config.get(), tokens: usage.tokens, model: ctx.model }) @@ -603,6 +608,7 @@ export namespace SessionProcessor { Layer.provide(LLM.defaultLayer), Layer.provide(Permission.defaultLayer), Layer.provide(Plugin.defaultLayer), + Layer.provide(SessionSummary.defaultLayer), Layer.provide(SessionStatus.defaultLayer), Layer.provide(Bus.layer), Layer.provide(Config.defaultLayer), diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 97a37865dfa2..1d96392b0ac3 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -102,6 +102,7 @@ export namespace SessionPrompt { const instruction = yield* Instruction.Service const state = yield* SessionRunState.Service const revert = yield* SessionRevert.Service + const summary = yield* SessionSummary.Service const sys = yield* SystemPrompt.Service const llm = yield* LLM.Service @@ -1444,7 +1445,10 @@ NOTE: At any point in time through this workflow you should feel free to ask the }) } - if (step === 1) SessionSummary.summarize({ sessionID, messageID: lastUser.id }) + if (step === 1) + yield* summary + .summarize({ sessionID, messageID: lastUser.id }) + .pipe(Effect.ignore, Effect.forkIn(scope)) if (step > 1 && lastFinished) { for (const m of msgs) { @@ -1692,6 +1696,7 @@ NOTE: At any point in time through this workflow you should feel free to ask the Layer.provide(Plugin.defaultLayer), Layer.provide(Session.defaultLayer), Layer.provide(SessionRevert.defaultLayer), + Layer.provide(SessionSummary.defaultLayer), Layer.provide( Layer.mergeAll( Agent.defaultLayer, diff --git a/packages/opencode/src/session/summary.ts b/packages/opencode/src/session/summary.ts index 498288d61588..2c973c5df75a 100644 --- a/packages/opencode/src/session/summary.ts +++ b/packages/opencode/src/session/summary.ts @@ -1,6 +1,5 @@ import z from "zod" import { Effect, Layer, Context } from "effect" -import { makeRuntime } from "@/effect/run-service" import { Bus } from "@/bus" import { Snapshot } from "@/snapshot" import { Storage } from "@/storage/storage" @@ -159,17 +158,8 @@ export namespace SessionSummary { ), ) - const { runPromise } = makeRuntime(Service, defaultLayer) - - export const summarize = (input: { sessionID: SessionID; messageID: MessageID }) => - void runPromise((svc) => svc.summarize(input)).catch(() => {}) - export const DiffInput = z.object({ sessionID: SessionID.zod, messageID: MessageID.zod.optional(), }) - - export async function diff(input: z.infer) { - return runPromise((svc) => svc.diff(input)) - } } diff --git a/packages/opencode/test/session/compaction.test.ts b/packages/opencode/test/session/compaction.test.ts index 61b47df34aba..2b0908ee9de9 100644 --- a/packages/opencode/test/session/compaction.test.ts +++ b/packages/opencode/test/session/compaction.test.ts @@ -18,6 +18,7 @@ import { Session } from "../../src/session" import { MessageV2 } from "../../src/session/message-v2" import { MessageID, PartID, SessionID } from "../../src/session/schema" import { SessionStatus } from "../../src/session/status" +import { SessionSummary } from "../../src/session/summary" import { ModelID, ProviderID } from "../../src/provider/schema" import type { Provider } from "../../src/provider/provider" import * as SessionProcessorModule from "../../src/session/processor" @@ -26,6 +27,15 @@ import { ProviderTest } from "../fake/provider" Log.init({ print: false }) +const summary = Layer.succeed( + SessionSummary.Service, + SessionSummary.Service.of({ + summarize: () => Effect.void, + diff: () => Effect.succeed([]), + computeDiff: () => Effect.succeed([]), + }), +) + const ref = { providerID: ProviderID.make("test"), modelID: ModelID.make("test-model"), @@ -194,7 +204,7 @@ function llm() { function liveRuntime(layer: Layer.Layer, provider = ProviderTest.fake()) { const bus = Bus.layer const status = SessionStatus.layer.pipe(Layer.provide(bus)) - const processor = SessionProcessorModule.SessionProcessor.layer + const processor = SessionProcessorModule.SessionProcessor.layer.pipe(Layer.provide(summary)) return ManagedRuntime.make( Layer.mergeAll(SessionCompaction.layer.pipe(Layer.provide(processor)), processor, bus, status).pipe( Layer.provide(provider.layer), diff --git a/packages/opencode/test/session/processor-effect.test.ts b/packages/opencode/test/session/processor-effect.test.ts index a3b335b6da8a..d38451308788 100644 --- a/packages/opencode/test/session/processor-effect.test.ts +++ b/packages/opencode/test/session/processor-effect.test.ts @@ -16,6 +16,7 @@ import { MessageV2 } from "../../src/session/message-v2" import { SessionProcessor } from "../../src/session/processor" import { MessageID, PartID, SessionID } from "../../src/session/schema" import { SessionStatus } from "../../src/session/status" +import { SessionSummary } from "../../src/session/summary" import { Snapshot } from "../../src/snapshot" import { Log } from "../../src/util/log" import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner" @@ -25,6 +26,15 @@ import { raw, reply, TestLLMServer } from "../lib/llm-server" Log.init({ print: false }) +const summary = Layer.succeed( + SessionSummary.Service, + SessionSummary.Service.of({ + summarize: () => Effect.void, + diff: () => Effect.succeed([]), + computeDiff: () => Effect.succeed([]), + }), +) + const ref = { providerID: ProviderID.make("test"), modelID: ModelID.make("test-model"), @@ -156,7 +166,10 @@ const deps = Layer.mergeAll( Provider.defaultLayer, status, ).pipe(Layer.provideMerge(infra)) -const env = Layer.mergeAll(TestLLMServer.layer, SessionProcessor.layer.pipe(Layer.provideMerge(deps))) +const env = Layer.mergeAll( + TestLLMServer.layer, + SessionProcessor.layer.pipe(Layer.provide(summary), Layer.provideMerge(deps)), +) const it = testEffect(env) diff --git a/packages/opencode/test/session/prompt-effect.test.ts b/packages/opencode/test/session/prompt-effect.test.ts index eafe68206752..4e5b39942a98 100644 --- a/packages/opencode/test/session/prompt-effect.test.ts +++ b/packages/opencode/test/session/prompt-effect.test.ts @@ -23,6 +23,7 @@ import { LLM } from "../../src/session/llm" import { MessageV2 } from "../../src/session/message-v2" import { AppFileSystem } from "../../src/filesystem" import { SessionCompaction } from "../../src/session/compaction" +import { SessionSummary } from "../../src/session/summary" import { Instruction } from "../../src/session/instruction" import { SessionProcessor } from "../../src/session/processor" import { SessionPrompt } from "../../src/session/prompt" @@ -46,6 +47,15 @@ import { reply, TestLLMServer } from "../lib/llm-server" Log.init({ print: false }) +const summary = Layer.succeed( + SessionSummary.Service, + SessionSummary.Service.of({ + summarize: () => Effect.void, + diff: () => Effect.succeed([]), + computeDiff: () => Effect.succeed([]), + }), +) + const ref = { providerID: ProviderID.make("test"), modelID: ModelID.make("test-model"), @@ -182,12 +192,13 @@ function makeHttp() { Layer.provideMerge(deps), ) const trunc = Truncate.layer.pipe(Layer.provideMerge(deps)) - const proc = SessionProcessor.layer.pipe(Layer.provideMerge(deps)) + const proc = SessionProcessor.layer.pipe(Layer.provide(summary), Layer.provideMerge(deps)) const compact = SessionCompaction.layer.pipe(Layer.provideMerge(proc), Layer.provideMerge(deps)) return Layer.mergeAll( TestLLMServer.layer, SessionPrompt.layer.pipe( Layer.provide(SessionRevert.defaultLayer), + Layer.provide(summary), Layer.provideMerge(run), Layer.provideMerge(compact), Layer.provideMerge(proc), diff --git a/packages/opencode/test/session/snapshot-tool-race.test.ts b/packages/opencode/test/session/snapshot-tool-race.test.ts index 391d9d488c23..2723e362dd22 100644 --- a/packages/opencode/test/session/snapshot-tool-race.test.ts +++ b/packages/opencode/test/session/snapshot-tool-race.test.ts @@ -146,12 +146,14 @@ function makeHttp() { Layer.provideMerge(deps), ) const trunc = Truncate.layer.pipe(Layer.provideMerge(deps)) - const proc = SessionProcessor.layer.pipe(Layer.provideMerge(deps)) + const proc = SessionProcessor.layer.pipe(Layer.provide(SessionSummary.defaultLayer), Layer.provideMerge(deps)) const compact = SessionCompaction.layer.pipe(Layer.provideMerge(proc), Layer.provideMerge(deps)) return Layer.mergeAll( TestLLMServer.layer, + SessionSummary.defaultLayer, SessionPrompt.layer.pipe( Layer.provide(SessionRevert.defaultLayer), + Layer.provide(SessionSummary.defaultLayer), Layer.provideMerge(run), Layer.provideMerge(compact), Layer.provideMerge(proc), @@ -200,6 +202,7 @@ it.live("tool execution produces non-empty session diff (snapshot race)", () => Effect.fnUntraced(function* ({ dir, llm }) { const prompt = yield* SessionPrompt.Service const sessions = yield* Session.Service + const summary = yield* SessionSummary.Service const session = yield* sessions.create({ title: "snapshot race test", @@ -244,9 +247,9 @@ it.live("tool execution produces non-empty session diff (snapshot race)", () => expect(tool?.state.status).toBe("completed") // Poll for diff — summarize() is fire-and-forget - let diff: Awaited> = [] + let diff: Array<{ file: string }> = [] for (let i = 0; i < 50; i++) { - diff = yield* Effect.promise(() => SessionSummary.diff({ sessionID: session.id })) + diff = yield* summary.diff({ sessionID: session.id }) if (diff.length > 0) break yield* Effect.sleep("100 millis") }