diff --git a/packages/core/src/effect/layer-node/layer-node.ts b/packages/core/src/effect/layer-node/layer-node.ts index 52ed1a9faed..e010d1044fe 100644 --- a/packages/core/src/effect/layer-node/layer-node.ts +++ b/packages/core/src/effect/layer-node/layer-node.ts @@ -50,11 +50,7 @@ export function make< const T extends Tag | undefined = undefined, >( input: MakeInput, -): Node< - Layer.Success, - Layer.Error | Error, - T -> { +): Node, Layer.Error | Error, T> { return { kind: "layer", name: input.service !== undefined ? input.service.key : input.name, @@ -65,10 +61,7 @@ export function make< } } -export function unbound( - service: Context.Key, - tag: T, -): Node { +export function unbound(service: Context.Key, tag: T): Node { return { kind: "unbound", name: service.key, @@ -100,16 +93,12 @@ export interface Tags { ) => ( input: DistributiveOmit>, "tag"> & CheckTags>, - ) => Node< - Layer.Success, - Layer.Error | Error, - Tag - > + ) => Node, Layer.Error | Error, Tag> } -export function tags< - const Config extends { readonly [Name in keyof Config]: readonly (keyof Config & string)[] }, ->(config: Config): Tags { +export function tags( + config: Config, +): Tags { const names = Object.keys(config) as TagNames[] const values = Object.fromEntries(names.map((name) => [name, makeTag(name)])) as Tags["values"] return { diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index 35f9a265afd..13df855e105 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -184,271 +184,268 @@ export class Service extends Context.Service()("@opencode/v2 export const layer = Layer.effect( Service, Effect.gen(function* () { - const database = yield* Database.Service - const db = database.db - const events = yield* EventV2.Service - const projects = yield* ProjectV2.Service - const execution = yield* SessionExecution.Service - const store = yield* SessionStore.Service - const locations = yield* LocationServiceMap.Service - const decodeMessage = Schema.decodeUnknownEffect(SessionMessage.Message) - const isDurableSessionEvent = Schema.is(SessionEvent.Durable) - const decode = (row: typeof SessionMessageTable.$inferSelect) => - decodeMessage({ ...row.data, id: row.id, type: row.type }).pipe( - Effect.mapError( - () => - new MessageDecodeError({ - sessionID: SessionSchema.ID.make(row.session_id), - messageID: SessionMessage.ID.make(row.id), - }), + const database = yield* Database.Service + const db = database.db + const events = yield* EventV2.Service + const projects = yield* ProjectV2.Service + const execution = yield* SessionExecution.Service + const store = yield* SessionStore.Service + const locations = yield* LocationServiceMap.Service + const decodeMessage = Schema.decodeUnknownEffect(SessionMessage.Message) + const isDurableSessionEvent = Schema.is(SessionEvent.Durable) + const decode = (row: typeof SessionMessageTable.$inferSelect) => + decodeMessage({ ...row.data, id: row.id, type: row.type }).pipe( + Effect.mapError( + () => + new MessageDecodeError({ + sessionID: SessionSchema.ID.make(row.session_id), + messageID: SessionMessage.ID.make(row.id), + }), + ), + ) + + const result = Service.of({ + create: Effect.fn("V2Session.create")(function* (input) { + const sessionID = input.id ?? SessionSchema.ID.create() + const recorded = yield* store.get(sessionID) + if (recorded) return recorded + const project = yield* projects.resolve(input.location.directory) + yield* db + .insert(ProjectTable) + .values({ id: project.id, worktree: project.directory, vcs: project.vcs?.type, sandboxes: [] }) + .onConflictDoNothing() + .run() + .pipe(Effect.orDie) + const now = Date.now() + const info = SessionV1.SessionInfo.make({ + id: sessionID, + slug: Slug.create(), + version: InstallationVersion, + projectID: project.id, + directory: input.location.directory, + path: path.relative(project.directory, input.location.directory).replaceAll("\\", "/"), + workspaceID: input.location.workspaceID ? WorkspaceV2.ID.make(input.location.workspaceID) : undefined, + title: `New session - ${new Date(now).toISOString()}`, + agent: input.agent, + model: input.model + ? { + id: ModelV2.ID.make(input.model.id), + providerID: input.model.providerID, + variant: input.model.variant, + } + : undefined, + cost: 0, + tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, + time: { created: now, updated: now }, + }) + const projected = yield* events + .publish(SessionV1.Event.Created, { sessionID, info }, { location: input.location }) + .pipe( + Effect.as({ type: "created" } as const), + Effect.catchDefect((defect) => { + if (!(defect instanceof SessionProjector.SessionAlreadyProjected)) { + return Effect.die(defect) + } + // Concurrent creation lost the projection race. The existing Session identity wins. + return store + .get(sessionID) + .pipe( + Effect.flatMap((session) => + session ? Effect.succeed({ type: "existing", session } as const) : Effect.die(defect), + ), + ) + }), + ) + if (projected.type === "existing") return projected.session + // TODO: Restore recorded sessions onto replacement synchronized workspaces in a future API slice. + return yield* result.get(sessionID).pipe(Effect.orDie) + }), + get: Effect.fn("V2Session.get")(function* (sessionID) { + const session = yield* store.get(sessionID) + if (!session) return yield* new NotFoundError({ sessionID }) + return session + }), + list: Effect.fn("V2Session.list")(function* (input = {}) { + const direction = input.anchor?.direction ?? "next" + const requestedOrder = input.order ?? "desc" + const order = direction === "previous" ? (requestedOrder === "asc" ? "desc" : "asc") : requestedOrder + const sortColumn = SessionTable.time_created + const conditions: SQL[] = [] + if ("directory" in input) conditions.push(eq(SessionTable.directory, input.directory)) + if (input.workspaceID) conditions.push(eq(SessionTable.workspace_id, input.workspaceID)) + if ("project" in input) conditions.push(eq(SessionTable.project_id, input.project)) + if (input.search) conditions.push(like(SessionTable.title, `%${input.search}%`)) + if (input.anchor) { + conditions.push( + order === "asc" + ? or( + gt(sortColumn, input.anchor.time), + and(eq(sortColumn, input.anchor.time), gt(SessionTable.id, input.anchor.id)), + )! + : or( + lt(sortColumn, input.anchor.time), + and(eq(sortColumn, input.anchor.time), lt(SessionTable.id, input.anchor.id)), + )!, + ) + } + const query = db + .select() + .from(SessionTable) + .where(conditions.length > 0 ? and(...conditions) : undefined) + .orderBy( + order === "asc" ? asc(sortColumn) : desc(sortColumn), + order === "asc" ? asc(SessionTable.id) : desc(SessionTable.id), + ) + const rows = yield* (input.limit === undefined ? query.all() : query.limit(input.limit).all()).pipe( + Effect.orDie, + ) + return (direction === "previous" ? rows.toReversed() : rows).map((row) => fromRow(row)) + }), + messages: Effect.fn("V2Session.messages")(function* (input) { + yield* result.get(input.sessionID) + const direction = input.cursor?.direction ?? "next" + const requestedOrder = input.order ?? "desc" + const order = direction === "previous" ? (requestedOrder === "asc" ? "desc" : "asc") : requestedOrder + const anchor = input.cursor + ? yield* db + .select({ seq: SessionMessageTable.seq }) + .from(SessionMessageTable) + .where( + and(eq(SessionMessageTable.session_id, input.sessionID), eq(SessionMessageTable.id, input.cursor.id)), + ) + .get() + .pipe(Effect.orDie) + : undefined + if (input.cursor && !anchor) return [] + const boundary = anchor + ? order === "asc" + ? gt(SessionMessageTable.seq, anchor.seq) + : lt(SessionMessageTable.seq, anchor.seq) + : undefined + const where = boundary + ? and(eq(SessionMessageTable.session_id, input.sessionID), boundary) + : eq(SessionMessageTable.session_id, input.sessionID) + const query = db + .select() + .from(SessionMessageTable) + .where(where) + .orderBy(order === "asc" ? asc(SessionMessageTable.seq) : desc(SessionMessageTable.seq)) + const rows = yield* (input.limit === undefined ? query.all() : query.limit(input.limit).all()).pipe( + Effect.orDie, + ) + return yield* Effect.forEach(direction === "previous" ? rows.toReversed() : rows, decode) + }), + message: Effect.fn("V2Session.message")(function* (input) { + const stored = yield* store.message(input.messageID) + return stored?.sessionID === input.sessionID ? stored.message : undefined + }), + context: Effect.fn("V2Session.context")(function* (sessionID) { + yield* result.get(sessionID) + return yield* store.context(sessionID) + }), + events: (input) => + Stream.unwrap( + result + .get(input.sessionID) + .pipe(Effect.as(events.durable({ aggregateID: input.sessionID, after: input.after }))), + ).pipe(Stream.filter((event): event is SessionEvent.DurableEvent => isDurableSessionEvent(event))), + history: Effect.fn("V2Session.history")(function* (input) { + yield* result.get(input.sessionID) + return yield* EventV2.readAggregate(db, { + ...input, + aggregateID: input.sessionID, + manifest: SessionDurable, + }) + }), + prompt: Effect.fn("V2Session.prompt")((input) => + Effect.uninterruptible( + Effect.gen(function* () { + yield* result.get(input.sessionID) + const prompt = resolvePrompt(input.prompt) + const messageID = input.id ?? SessionMessage.ID.create() + const delivery = input.delivery ?? "steer" + const expected = { sessionID: input.sessionID, messageID, prompt, delivery } + const admitted = yield* SessionInput.admit(db, events, { + id: messageID, + sessionID: input.sessionID, + prompt, + delivery, + }).pipe( + Effect.catchDefect((defect) => + defect instanceof SessionInput.LifecycleConflict + ? new PromptConflictError({ sessionID: input.sessionID, messageID }) + : Effect.die(defect), ), ) - - const result = Service.of({ - create: Effect.fn("V2Session.create")(function* (input) { - const sessionID = input.id ?? SessionSchema.ID.create() - const recorded = yield* store.get(sessionID) - if (recorded) return recorded - const project = yield* projects.resolve(input.location.directory) - yield* db - .insert(ProjectTable) - .values({ id: project.id, worktree: project.directory, vcs: project.vcs?.type, sandboxes: [] }) - .onConflictDoNothing() - .run() - .pipe(Effect.orDie) - const now = Date.now() - const info = SessionV1.SessionInfo.make({ - id: sessionID, - slug: Slug.create(), - version: InstallationVersion, - projectID: project.id, - directory: input.location.directory, - path: path.relative(project.directory, input.location.directory).replaceAll("\\", "/"), - workspaceID: input.location.workspaceID ? WorkspaceV2.ID.make(input.location.workspaceID) : undefined, - title: `New session - ${new Date(now).toISOString()}`, - agent: input.agent, - model: input.model - ? { - id: ModelV2.ID.make(input.model.id), - providerID: input.model.providerID, - variant: input.model.variant, - } - : undefined, - cost: 0, - tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, - time: { created: now, updated: now }, - }) - const projected = yield* events - .publish(SessionV1.Event.Created, { sessionID, info }, { location: input.location }) - .pipe( - Effect.as({ type: "created" } as const), - Effect.catchDefect((defect) => { - if (!(defect instanceof SessionProjector.SessionAlreadyProjected)) { - return Effect.die(defect) - } - // Concurrent creation lost the projection race. The existing Session identity wins. - return store - .get(sessionID) - .pipe( - Effect.flatMap((session) => - session ? Effect.succeed({ type: "existing", session } as const) : Effect.die(defect), - ), - ) - }), - ) - if (projected.type === "existing") return projected.session - // TODO: Restore recorded sessions onto replacement synchronized workspaces in a future API slice. - return yield* result.get(sessionID).pipe(Effect.orDie) - }), - get: Effect.fn("V2Session.get")(function* (sessionID) { - const session = yield* store.get(sessionID) - if (!session) return yield* new NotFoundError({ sessionID }) - return session - }), - list: Effect.fn("V2Session.list")(function* (input = {}) { - const direction = input.anchor?.direction ?? "next" - const requestedOrder = input.order ?? "desc" - const order = direction === "previous" ? (requestedOrder === "asc" ? "desc" : "asc") : requestedOrder - const sortColumn = SessionTable.time_created - const conditions: SQL[] = [] - if ("directory" in input) conditions.push(eq(SessionTable.directory, input.directory)) - if (input.workspaceID) conditions.push(eq(SessionTable.workspace_id, input.workspaceID)) - if ("project" in input) conditions.push(eq(SessionTable.project_id, input.project)) - if (input.search) conditions.push(like(SessionTable.title, `%${input.search}%`)) - if (input.anchor) { - conditions.push( - order === "asc" - ? or( - gt(sortColumn, input.anchor.time), - and(eq(sortColumn, input.anchor.time), gt(SessionTable.id, input.anchor.id)), - )! - : or( - lt(sortColumn, input.anchor.time), - and(eq(sortColumn, input.anchor.time), lt(SessionTable.id, input.anchor.id)), - )!, - ) - } - const query = db - .select() - .from(SessionTable) - .where(conditions.length > 0 ? and(...conditions) : undefined) - .orderBy( - order === "asc" ? asc(sortColumn) : desc(sortColumn), - order === "asc" ? asc(SessionTable.id) : desc(SessionTable.id), - ) - const rows = yield* (input.limit === undefined ? query.all() : query.limit(input.limit).all()).pipe( - Effect.orDie, - ) - return (direction === "previous" ? rows.toReversed() : rows).map((row) => fromRow(row)) - }), - messages: Effect.fn("V2Session.messages")(function* (input) { - yield* result.get(input.sessionID) - const direction = input.cursor?.direction ?? "next" - const requestedOrder = input.order ?? "desc" - const order = direction === "previous" ? (requestedOrder === "asc" ? "desc" : "asc") : requestedOrder - const anchor = input.cursor - ? yield* db - .select({ seq: SessionMessageTable.seq }) - .from(SessionMessageTable) - .where( - and( - eq(SessionMessageTable.session_id, input.sessionID), - eq(SessionMessageTable.id, input.cursor.id), - ), - ) - .get() - .pipe(Effect.orDie) - : undefined - if (input.cursor && !anchor) return [] - const boundary = anchor - ? order === "asc" - ? gt(SessionMessageTable.seq, anchor.seq) - : lt(SessionMessageTable.seq, anchor.seq) - : undefined - const where = boundary - ? and(eq(SessionMessageTable.session_id, input.sessionID), boundary) - : eq(SessionMessageTable.session_id, input.sessionID) - const query = db - .select() - .from(SessionMessageTable) - .where(where) - .orderBy(order === "asc" ? asc(SessionMessageTable.seq) : desc(SessionMessageTable.seq)) - const rows = yield* (input.limit === undefined ? query.all() : query.limit(input.limit).all()).pipe( - Effect.orDie, - ) - return yield* Effect.forEach(direction === "previous" ? rows.toReversed() : rows, decode) - }), - message: Effect.fn("V2Session.message")(function* (input) { - const stored = yield* store.message(input.messageID) - return stored?.sessionID === input.sessionID ? stored.message : undefined - }), - context: Effect.fn("V2Session.context")(function* (sessionID) { - yield* result.get(sessionID) - return yield* store.context(sessionID) - }), - events: (input) => - Stream.unwrap( - result - .get(input.sessionID) - .pipe(Effect.as(events.durable({ aggregateID: input.sessionID, after: input.after }))), - ).pipe(Stream.filter((event): event is SessionEvent.DurableEvent => isDurableSessionEvent(event))), - history: Effect.fn("V2Session.history")(function* (input) { - yield* result.get(input.sessionID) - return yield* EventV2.readAggregate(db, { - ...input, - aggregateID: input.sessionID, - manifest: SessionDurable, - }) - }), - prompt: Effect.fn("V2Session.prompt")((input) => - Effect.uninterruptible( - Effect.gen(function* () { - yield* result.get(input.sessionID) - const prompt = resolvePrompt(input.prompt) - const messageID = input.id ?? SessionMessage.ID.create() - const delivery = input.delivery ?? "steer" - const expected = { sessionID: input.sessionID, messageID, prompt, delivery } - const admitted = yield* SessionInput.admit(db, events, { - id: messageID, - sessionID: input.sessionID, - prompt, - delivery, - }).pipe( - Effect.catchDefect((defect) => - defect instanceof SessionInput.LifecycleConflict - ? new PromptConflictError({ sessionID: input.sessionID, messageID }) - : Effect.die(defect), - ), - ) - if (!SessionInput.equivalent(admitted, expected)) - return yield* new PromptConflictError({ sessionID: input.sessionID, messageID }) - if (input.resume !== false) yield* execution.wake(admitted.sessionID) - return admitted - }), - ), - ), - shell: Effect.fn("V2Session.shell")(function* () { - return yield* new OperationUnavailableError({ operation: "shell" }) - }), - skill: Effect.fn("V2Session.skill")(function* () { - return yield* new OperationUnavailableError({ operation: "skill" }) - }), - switchAgent: Effect.fn("V2Session.switchAgent")(function* (input) { - yield* result.get(input.sessionID) - yield* events.publish(SessionEvent.AgentSwitched, { - sessionID: input.sessionID, - messageID: SessionMessage.ID.create(), - timestamp: yield* DateTime.now, - agent: input.agent, - }) - }), - switchModel: Effect.fn("V2Session.switchModel")(function* (input) { - yield* result.get(input.sessionID) - yield* events.publish(SessionEvent.ModelSwitched, { - sessionID: input.sessionID, - messageID: SessionMessage.ID.create(), - timestamp: yield* DateTime.now, - model: input.model, - }) - }), - compact: Effect.fn("V2Session.compact")(function* (input) { - yield* result.get(input.sessionID) - return yield* new OperationUnavailableError({ operation: "compact" }) - }), - wait: Effect.fn("V2Session.wait")(function* (sessionID) { - yield* result.get(sessionID) - return yield* new OperationUnavailableError({ operation: "wait" }) - }), - active: execution.active, - resume: Effect.fn("V2Session.resume")(function* (sessionID) { - yield* result.get(sessionID) - yield* execution.resume(sessionID) - }), - interrupt: Effect.fn("V2Session.interrupt")((sessionID) => - Effect.uninterruptible(execution.interrupt(sessionID)), - ), - revert: { - stage: Effect.fn("V2Session.revert.stage")(function* (input) { - const session = yield* result.get(input.sessionID) - return yield* SessionRevert.stage({ session, messageID: input.messageID, files: input.files }).pipe( - Effect.provideService(Database.Service, database), - Effect.provideService(EventV2.Service, events), - Effect.provide(locations.get(session.location)), - ) - }), - clear: Effect.fn("V2Session.revert.clear")(function* (sessionID) { - const session = yield* result.get(sessionID) - yield* SessionRevert.clear(session).pipe( - Effect.provideService(EventV2.Service, events), - Effect.provide(locations.get(session.location)), - ) - }), - commit: Effect.fn("V2Session.revert.commit")(function* (sessionID) { - const session = yield* result.get(sessionID) - yield* SessionRevert.commit(session).pipe(Effect.provideService(EventV2.Service, events)) - }), - }, - }) + if (!SessionInput.equivalent(admitted, expected)) + return yield* new PromptConflictError({ sessionID: input.sessionID, messageID }) + if (input.resume !== false) yield* execution.wake(admitted.sessionID) + return admitted + }), + ), + ), + shell: Effect.fn("V2Session.shell")(function* () { + return yield* new OperationUnavailableError({ operation: "shell" }) + }), + skill: Effect.fn("V2Session.skill")(function* () { + return yield* new OperationUnavailableError({ operation: "skill" }) + }), + switchAgent: Effect.fn("V2Session.switchAgent")(function* (input) { + yield* result.get(input.sessionID) + yield* events.publish(SessionEvent.AgentSwitched, { + sessionID: input.sessionID, + messageID: SessionMessage.ID.create(), + timestamp: yield* DateTime.now, + agent: input.agent, + }) + }), + switchModel: Effect.fn("V2Session.switchModel")(function* (input) { + yield* result.get(input.sessionID) + yield* events.publish(SessionEvent.ModelSwitched, { + sessionID: input.sessionID, + messageID: SessionMessage.ID.create(), + timestamp: yield* DateTime.now, + model: input.model, + }) + }), + compact: Effect.fn("V2Session.compact")(function* (input) { + yield* result.get(input.sessionID) + return yield* new OperationUnavailableError({ operation: "compact" }) + }), + wait: Effect.fn("V2Session.wait")(function* (sessionID) { + yield* result.get(sessionID) + return yield* new OperationUnavailableError({ operation: "wait" }) + }), + active: execution.active, + resume: Effect.fn("V2Session.resume")(function* (sessionID) { + yield* result.get(sessionID) + yield* execution.resume(sessionID) + }), + interrupt: Effect.fn("V2Session.interrupt")((sessionID) => + Effect.uninterruptible(execution.interrupt(sessionID)), + ), + revert: { + stage: Effect.fn("V2Session.revert.stage")(function* (input) { + const session = yield* result.get(input.sessionID) + return yield* SessionRevert.stage({ session, messageID: input.messageID, files: input.files }).pipe( + Effect.provideService(Database.Service, database), + Effect.provideService(EventV2.Service, events), + Effect.provide(locations.get(session.location)), + ) + }), + clear: Effect.fn("V2Session.revert.clear")(function* (sessionID) { + const session = yield* result.get(sessionID) + yield* SessionRevert.clear(session).pipe( + Effect.provideService(EventV2.Service, events), + Effect.provide(locations.get(session.location)), + ) + }), + commit: Effect.fn("V2Session.revert.commit")(function* (sessionID) { + const session = yield* result.get(sessionID) + yield* SessionRevert.commit(session).pipe(Effect.provideService(EventV2.Service, events)) + }), + }, + }) return result }), diff --git a/packages/core/test/effect/layer-node/layer-node-types.test.ts b/packages/core/test/effect/layer-node/layer-node-types.test.ts index a4dce388696..80194520146 100644 --- a/packages/core/test/effect/layer-node/layer-node-types.test.ts +++ b/packages/core/test/effect/layer-node/layer-node-types.test.ts @@ -16,8 +16,7 @@ class OtherError { const tags = LayerNode.tags({ app: [] }) const make = tags.make("app") -const build = (root: LayerNode.Node) => - LayerNodeTree.compile(root) as Layer.Layer +const build = (root: LayerNode.Node) => LayerNodeTree.compile(root) as Layer.Layer const aLayer = Layer.succeed(A, A.of({})) const bLayer = Layer.effect(B, Effect.as(A, B.of({}))) const cLayer = Layer.effect( diff --git a/packages/core/test/effect/layer-node/layer-node.test.ts b/packages/core/test/effect/layer-node/layer-node.test.ts index 7c0ffe27c15..757a264f226 100644 --- a/packages/core/test/effect/layer-node/layer-node.test.ts +++ b/packages/core/test/effect/layer-node/layer-node.test.ts @@ -14,10 +14,10 @@ class App extends Context.Service } const tags = LayerNode.tags({ app: [] }) const make = tags.make("app") const build = (root: LayerNode.Node, replacements?: readonly LayerNode.Replacement[]) => - LayerNodeTree.compile( - root, - new Map(replacements?.map((item) => [item.source, item.replacement])), - ) as Layer.Layer + LayerNodeTree.compile(root, new Map(replacements?.map((item) => [item.source, item.replacement]))) as Layer.Layer< + A, + E + > const valueLayer = Layer.succeed(Value, Value.of({ value: "production" })) const greetingLayer = Layer.effect( Greeting, diff --git a/packages/core/test/location-layer.test.ts b/packages/core/test/location-layer.test.ts index b48bcb9ecfe..5c4f21ef1ff 100644 --- a/packages/core/test/location-layer.test.ts +++ b/packages/core/test/location-layer.test.ts @@ -111,7 +111,9 @@ describe("LocationServiceMap", () => { } }).pipe( Effect.scoped, - Effect.provide(LocationServiceMap.Service.get(Location.Ref.make({ directory: AbsolutePath.make(directory) }))), + Effect.provide( + LocationServiceMap.Service.get(Location.Ref.make({ directory: AbsolutePath.make(directory) })), + ), ) const blockedState = yield* update(blocked.path) diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index d6b89eb6b1a..1b079a7db35 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -447,7 +447,11 @@ export const defaultLayer = layer.pipe( Layer.provide(locationServiceMapLayer), ) -const locationServiceMapNode = LayerNode.make({ service: LocationServiceMap.Service, layer: locationServiceMapLayer, deps: [] }) +const locationServiceMapNode = LayerNode.make({ + service: LocationServiceMap.Service, + layer: locationServiceMapLayer, + deps: [], +}) export const node = LayerNode.make({ service: Service, diff --git a/packages/opencode/src/session/system.ts b/packages/opencode/src/session/system.ts index 29c66bab800..a4664b9b376 100644 --- a/packages/opencode/src/session/system.ts +++ b/packages/opencode/src/session/system.ts @@ -134,7 +134,11 @@ export const defaultLayer = layer.pipe( Layer.provide(locationServiceMapLayer), ) -const locationServiceMapNode = LayerNode.make({ service: LocationServiceMap.Service, layer: locationServiceMapLayer, deps: [] }) +const locationServiceMapNode = LayerNode.make({ + service: LocationServiceMap.Service, + layer: locationServiceMapLayer, + deps: [], +}) export const node = LayerNode.make({ service: Service, diff --git a/packages/opencode/test/session/processor-effect.test.ts b/packages/opencode/test/session/processor-effect.test.ts index 4263c01bee2..16cd8470b14 100644 --- a/packages/opencode/test/session/processor-effect.test.ts +++ b/packages/opencode/test/session/processor-effect.test.ts @@ -232,10 +232,7 @@ const fragmentFailureLLM = Layer.succeed( const fragmentFailureEnv = LayerNodeTree.compile( root, new Map( - [...replacements, LayerNode.replace(LLM.layer, fragmentFailureLLM)].map((item) => [ - item.source, - item.replacement, - ]), + [...replacements, LayerNode.replace(LLM.layer, fragmentFailureLLM)].map((item) => [item.source, item.replacement]), ), ) const itFragmentFailure = testEffect(fragmentFailureEnv) diff --git a/packages/opencode/test/session/snapshot-tool-race.test.ts b/packages/opencode/test/session/snapshot-tool-race.test.ts index 3ffc6a26368..9dbea18c145 100644 --- a/packages/opencode/test/session/snapshot-tool-race.test.ts +++ b/packages/opencode/test/session/snapshot-tool-race.test.ts @@ -92,9 +92,9 @@ const it = testEffect( root, new Map( [ - LayerNode.replace(MCP.layer, mcp), - LayerNode.replace(LSP.layer, lsp), - LayerNode.replace(RuntimeFlags.defaultLayer, RuntimeFlags.layer({ experimentalEventSystem: true })), + LayerNode.replace(MCP.layer, mcp), + LayerNode.replace(LSP.layer, lsp), + LayerNode.replace(RuntimeFlags.defaultLayer, RuntimeFlags.layer({ experimentalEventSystem: true })), ].map((item) => [item.source, item.replacement]), ), ), diff --git a/packages/opencode/test/share/share-next.test.ts b/packages/opencode/test/share/share-next.test.ts index bce95e993a6..694bedc1cef 100644 --- a/packages/opencode/test/share/share-next.test.ts +++ b/packages/opencode/test/share/share-next.test.ts @@ -45,7 +45,14 @@ function requestLayer(client: HttpClient.HttpClient) { function integrationLayer(client: HttpClient.HttpClient) { const replacement = LayerNode.replace(FetchHttpClient.layer, Layer.succeed(HttpClient.HttpClient, client)) return LayerNodeTree.compile( - LayerNode.group([ShareNext.node, EventV2Bridge.node, Session.node, SessionProjector.node, AccountRepo.node, Database.node]), + LayerNode.group([ + ShareNext.node, + EventV2Bridge.node, + Session.node, + SessionProjector.node, + AccountRepo.node, + Database.node, + ]), new Map([[replacement.source, replacement.replacement]]), ) }