Something went wrong. Try again.
opencode v2 mirror
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117export * as SessionRevert from "./revert.js"
import { and, asc, eq, gt } from "drizzle-orm"import { Effect, Schema } from "effect"import { Database } from "../database/database.js"import { Bus } from "../bus.js"import { Instance } from "../instance/service.js"import { RelativePath } from "../schema.js"import { Snapshot } from "../snapshot.js"import { SessionEvent } from "./event.js"import { MessageNotFoundError } from "./error.js"import { SessionMessage } from "./message.js"import { SessionSchema } from "./schema.js"import { SessionMessageTable } from "./sql.js"
export { MessageNotFoundError }
interface BoundaryInput { readonly sessionID: SessionSchema.ID readonly messageID: SessionMessage.ID}
export const stage = Effect.fn("SessionRevert.stage")(function* (input: { session: SessionSchema.Info messageID: SessionMessage.ID files?: boolean}) { const instances = yield* Instance.Service const database = yield* Database.Service const bus = yield* Bus.Service
return yield* Effect.gen(function* () { const snapshot = yield* Snapshot.Service const original = input.session.revert?.snapshot ? Snapshot.ID.make(input.session.revert.snapshot) : yield* snapshot.capture() const next = yield* plan(database.db, { sessionID: input.session.id, messageID: input.messageID }) const restore = new Map<RelativePath, Snapshot.ID>() if (original) { for (const file of input.session.revert?.files ?? []) restore.set(RelativePath.make(file.file), original) } if (input.files !== false) for (const [file, tree] of next) restore.set(file, tree) if (restore.size) yield* snapshot.restore({ files: restore }) const paths = input.files === false ? [] : Array.from(next.keys()) const files = original ? yield* snapshot.diff({ from: original, to: (yield* snapshot.capture()) ?? original, paths }) : [] const revert = { messageID: input.messageID, snapshot: original, files, } satisfies SessionSchema.Info["revert"] yield* bus.publish(SessionEvent.RevertEvent.Staged, { sessionID: input.session.id, revert, }) return revert }).pipe(instances.provide(input.session))})
export const clear = Effect.fn("SessionRevert.clear")(function* (session: SessionSchema.Info) { const instances = yield* Instance.Service const bus = yield* Bus.Service yield* Effect.gen(function* () { const snapshot = yield* Snapshot.Service if (!session.revert) return const original = session.revert.snapshot ? Snapshot.ID.make(session.revert.snapshot) : undefined if (original) yield* snapshot.restore({ files: new Map((session.revert.files ?? []).map((file) => [RelativePath.make(file.file), original])), }) yield* bus.publish(SessionEvent.RevertEvent.Cleared, { sessionID: session.id, }) }).pipe(instances.provide(session))})
export const commit = Effect.fn("SessionRevert.commit")(function* (bus: Bus.Interface, session: SessionSchema.Info) { if (!session.revert) return yield* bus.publish(SessionEvent.RevertEvent.Committed, { sessionID: session.id, to: session.revert.messageID, })})
const plan = Effect.fn("SessionRevert.plan")(function* (db: Database.Interface["db"], input: BoundaryInput) { const boundary = yield* db .select({ seq: SessionMessageTable.seq }) .from(SessionMessageTable) .where(and(eq(SessionMessageTable.session_id, input.sessionID), eq(SessionMessageTable.id, input.messageID))) .get() .pipe(Effect.orDie) if (!boundary) return yield* new MessageNotFoundError(input) const rows = yield* db .select() .from(SessionMessageTable) .where( and( eq(SessionMessageTable.session_id, input.sessionID), eq(SessionMessageTable.type, "assistant"), gt(SessionMessageTable.seq, boundary.seq), ), ) .orderBy(asc(SessionMessageTable.seq)) .all() .pipe(Effect.orDie) const decode = Schema.decodeUnknownEffect(SessionMessage.Info) const files = new Map<RelativePath, Snapshot.ID>() for (const row of rows) { const message = yield* decode({ ...row.data, id: row.id, type: row.type }).pipe(Effect.orDie) if (message.type !== "assistant" || !message.snapshot?.start) continue for (const file of message.snapshot.files ?? []) if (!files.has(file)) files.set(file, Snapshot.ID.make(message.snapshot.start)) } return files})