From 59e7b32c923684e89329ca46518d0ae1002768fb Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Thu, 27 Aug 2026 19:57:52 -0400 Subject: [PATCH] refactor(core): extract instance module from location services (#45705) --- packages/core/src/instance.ts | 133 ++++++++++++++++++++ packages/core/src/location-service-map.ts | 4 +- packages/core/src/location-services.ts | 147 ++-------------------- 3 files changed, 146 insertions(+), 138 deletions(-) create mode 100644 packages/core/src/instance.ts diff --git a/packages/core/src/instance.ts b/packages/core/src/instance.ts new file mode 100644 index 00000000000..469230db83d --- /dev/null +++ b/packages/core/src/instance.ts @@ -0,0 +1,133 @@ +import { Effect, Layer } from "effect" +import { Agent } from "./agent.js" +import { AISDK } from "./aisdk.js" +import { Catalog } from "./catalog.js" +import { Command } from "./command.js" +import { Config } from "./config.js" +import { LayerNode } from "@opencode-ai/util/effect/layer-node" +import { Node } from "@opencode-ai/util/effect/app-node" +import { FileMutation } from "./file-mutation.js" +import { Environment } from "./environment/index.js" +import { Formatter } from "./formatter.js" +import { FileSystem } from "./filesystem.js" +import { FileSystemSearch } from "./filesystem/search.js" +import { Generate } from "./generate.js" +import { Form } from "./form.js" +import { Image } from "./image.js" +import { LocationWatcher } from "./filesystem/location-watcher.js" +import { Integration } from "./integration.js" +import { Location } from "./location.js" +import { LocationMutation } from "./location-mutation.js" +import { ModelResolver } from "./model-resolver.js" +import { Mcp } from "./mcp/index.js" +import { Permission } from "./permission.js" +import { Plugin } from "./plugin.js" +import { PluginHooks } from "./plugin/hooks.js" +import { PluginSupervisor } from "./plugin/supervisor.js" +import { Worktree } from "./worktree.js" +import { Pty } from "./pty.js" +import { Shell } from "./shell.js" +import { ShellSelect } from "./shell/select.js" +import { Reference } from "./reference.js" +import { WebSearch } from "./websearch.js" +import { ReferenceInstructions } from "./reference/instructions.js" +import { SessionRunnerLLM } from "./session/runner/llm.js" +import { SessionRunnerModel } from "./session/runner/model.js" +import { SessionModelTransport } from "./session/model-transport.js" +import { SessionCompaction } from "./session/compaction.js" +import { SessionTitle } from "./session/title.js" +import { Skill } from "./skill.js" +import { SkillInstructions } from "./skill/instructions.js" +import { Snapshot } from "./snapshot.js" +import { InstructionDiscovery } from "./instruction-discovery.js" +import { InstructionBuiltIns } from "./instructions/builtins.js" +import { InstructionEntry } from "./session/instruction-entry.js" +import { SessionInstructions } from "./session/instructions.js" +import { SessionGenerateNode } from "./session/generate-node.js" +import { McpTool } from "./tool/mcp.js" +import { ReadToolFileSystem } from "./tool/read-filesystem.js" +import { Tool } from "./tool.js" +import { ToolOutput } from "./tool-output.js" +import { Vcs } from "./vcs.js" + +export * as Instance from "./instance.js" + +const nodes = [ + Location.node, + Environment.node, + Config.node, + Agent.node, + Command.node, + Reference.node, + WebSearch.node, + Integration.node, + Catalog.node, + ModelResolver.node, + AISDK.node, + Plugin.node, + PluginHooks.node, + PluginSupervisor.node, + Worktree.refreshNode, + FileSystemSearch.node, + FileSystem.node, + ShellSelect.node, + Pty.node, + Shell.node, + Skill.node, + InstructionBuiltIns.node, + InstructionDiscovery.node, + LocationMutation.node, + FileMutation.node, + Formatter.node, + Mcp.node, + Permission.node, + Tool.node, + ToolOutput.node, + Image.node, + SkillInstructions.node, + ReferenceInstructions.node, + InstructionEntry.node, + Form.node, + Generate.node, + SessionGenerateNode.node, + ReadToolFileSystem.node, + McpTool.node, + SessionInstructions.node, + SessionRunnerModel.node, + SessionModelTransport.node, + SessionCompaction.node, + SessionTitle.node, + Snapshot.node, + SessionRunnerLLM.node, + Vcs.node, + // Start repository watches only after boot-critical filesystem and Git work. + LocationWatcher.node, +] as const satisfies readonly Node.LocationNode[] + +export const graph = LayerNode.group(nodes) + +export type Services = LayerNode.Output +export type Error = LayerNode.Error + +// One instance is one compiled, fresh copy of the graph standing on a directory. +export function layer(ref: Location.Ref, replacements: LayerNode.Replacements = []) { + const startedAt = performance.now() + const allReplacements = replacements.concat([[Location.node, Location.boundNode(ref)]]) + // Apply replacements during hoist, not afterward: replacements can + // introduce new tagged dependencies (Location.boundNode depends on + // Project), and the hoist walk is the only pass that can still slice + // those back out. + const location = LayerNode.hoist(graph, Node.tags.values.global, allReplacements) + + return LayerNode.compile(location.node).pipe( + Layer.fresh, + Layer.tap(() => + Effect.logInfo("location services booted", { + directory: ref.directory, + workspaceID: ref.workspaceID, + durationMs: Math.round(performance.now() - startedAt), + }), + ), + Layer.provide(LayerNode.compile(location.hoisted)), + ) +} diff --git a/packages/core/src/location-service-map.ts b/packages/core/src/location-service-map.ts index 21a33bdfb55..67a49616e0c 100644 --- a/packages/core/src/location-service-map.ts +++ b/packages/core/src/location-service-map.ts @@ -2,11 +2,11 @@ import { Context, Effect, Layer, LayerMap } from "effect" import { LayerNode } from "@opencode-ai/util/effect/layer-node" import { Node } from "@opencode-ai/util/effect/app-node" import { Location } from "./location.js" -import type { LocationError, LocationServices } from "./location-services.js" +import type { Instance } from "./instance.js" export class Service extends Context.Service< Service, - LayerMap.LayerMap + LayerMap.LayerMap >()("@opencode/example/LocationServiceMap") { static get(ref: Location.Ref) { return Layer.unwrap(Effect.map(Service, (locations) => locations.get(ref))) diff --git a/packages/core/src/location-services.ts b/packages/core/src/location-services.ts index 1d905635b4c..7763855508b 100644 --- a/packages/core/src/location-services.ts +++ b/packages/core/src/location-services.ts @@ -1,118 +1,16 @@ import { Duration, Effect, Layer, LayerMap } from "effect" import { existsSync } from "fs" import path from "path" -import { Agent } from "./agent.js" -import { AISDK } from "./aisdk.js" -import { Catalog } from "./catalog.js" -import { Command } from "./command.js" -import { Config } from "./config.js" import { LayerNode } from "@opencode-ai/util/effect/layer-node" -import { Node } from "@opencode-ai/util/effect/app-node" -import { Bus } from "./bus.js" -import { FileMutation } from "./file-mutation.js" -import { Environment } from "./environment/index.js" -import { Formatter } from "./formatter.js" -import { FileSystem } from "./filesystem.js" -import { FileSystemSearch } from "./filesystem/search.js" -import { Generate } from "./generate.js" -import { Form } from "./form.js" -import { Image } from "./image.js" -import { LocationWatcher } from "./filesystem/location-watcher.js" -import { Integration } from "./integration.js" +import { Instance } from "./instance.js" import { Location } from "./location.js" -import { LocationMutation } from "./location-mutation.js" import { LocationServiceMap } from "./location-service-map.js" -import { ModelResolver } from "./model-resolver.js" -import { Mcp } from "./mcp/index.js" -import { Permission } from "./permission.js" -import { Plugin } from "./plugin.js" -import { PluginHooks } from "./plugin/hooks.js" -import { PluginSupervisor } from "./plugin/supervisor.js" -import { Worktree } from "./worktree.js" -import { Pty } from "./pty.js" -import { Shell } from "./shell.js" -import { ShellSelect } from "./shell/select.js" -import { Reference } from "./reference.js" -import { WebSearch } from "./websearch.js" -import { ReferenceInstructions } from "./reference/instructions.js" -import { SessionRunnerLLM } from "./session/runner/llm.js" -import { SessionRunnerModel } from "./session/runner/model.js" -import { SessionModelTransport } from "./session/model-transport.js" -import { SessionCompaction } from "./session/compaction.js" -import { SessionTitle } from "./session/title.js" -import { Skill } from "./skill.js" -import { SkillInstructions } from "./skill/instructions.js" -import { Snapshot } from "./snapshot.js" -import { InstructionDiscovery } from "./instruction-discovery.js" -import { InstructionBuiltIns } from "./instructions/builtins.js" -import { InstructionEntry } from "./session/instruction-entry.js" -import { SessionInstructions } from "./session/instructions.js" -import { SessionGenerateNode } from "./session/generate-node.js" -import { McpTool } from "./tool/mcp.js" -import { ReadToolFileSystem } from "./tool/read-filesystem.js" -import { Tool } from "./tool.js" -import { ToolOutput } from "./tool-output.js" -import { Vcs } from "./vcs.js" import { AbsolutePath } from "./schema.js" export { LocationServiceMap } from "./location-service-map.js" -const locationServiceNodes = [ - Location.node, - Environment.node, - Config.node, - Agent.node, - Command.node, - Reference.node, - WebSearch.node, - Integration.node, - Catalog.node, - ModelResolver.node, - AISDK.node, - Plugin.node, - PluginHooks.node, - PluginSupervisor.node, - Worktree.refreshNode, - FileSystemSearch.node, - FileSystem.node, - ShellSelect.node, - Pty.node, - Shell.node, - Skill.node, - InstructionBuiltIns.node, - InstructionDiscovery.node, - LocationMutation.node, - FileMutation.node, - Formatter.node, - Mcp.node, - Permission.node, - Tool.node, - ToolOutput.node, - Image.node, - SkillInstructions.node, - ReferenceInstructions.node, - InstructionEntry.node, - Form.node, - Generate.node, - SessionGenerateNode.node, - ReadToolFileSystem.node, - McpTool.node, - SessionInstructions.node, - SessionRunnerModel.node, - SessionModelTransport.node, - SessionCompaction.node, - SessionTitle.node, - Snapshot.node, - SessionRunnerLLM.node, - Vcs.node, - // Start repository watches only after boot-critical filesystem and Git work. - LocationWatcher.node, -] as const satisfies readonly Node.LocationNode[] - -export const locationServices = LayerNode.group(locationServiceNodes) - -export type LocationServices = LayerNode.Output -export type LocationError = LayerNode.Error +export type LocationServices = Instance.Services +export type LocationError = Instance.Error export function buildLocationServiceMap( replacements: LayerNode.Replacements = [], @@ -127,37 +25,14 @@ export function buildLocationServiceMap( return Layer.effect( LocationServiceMap.Service, Effect.map( - LayerMap.make( - (ref: Location.Ref) => { - const startedAt = performance.now() - const allReplacements = replacements.concat([[Location.node, Location.boundNode(ref)]]) - // Apply replacements during hoist, not afterward: replacements can - // introduce new tagged dependencies (Location.boundNode depends on - // Project), and the hoist walk is the only pass that can still slice - // those back out. - const location = LayerNode.hoist(locationServices, Node.tags.values.global, allReplacements) - - return LayerNode.compile(location.node).pipe( - Layer.fresh, - Layer.tap(() => - Effect.logInfo("location services booted", { - directory: ref.directory, - workspaceID: ref.workspaceID, - durationMs: Math.round(performance.now() - startedAt), - }), - ), - Layer.provide(LayerNode.compile(location.hoisted)), - ) - }, - { - // Workspace-placed directories exist only inside the workspace, so a - // local stat consults the wrong filesystem. Workspace liveness is - // owned by placement; do not probe the sandbox here, which would - // provision lazily-idle workspaces. - idleTimeToLive: (ref) => - ref.workspaceID !== undefined || existsSync(ref.directory) ? Duration.infinity : Duration.zero, - }, - ), + LayerMap.make((ref: Location.Ref) => Instance.layer(ref, replacements), { + // Workspace-placed directories exist only inside the workspace, so a + // local stat consults the wrong filesystem. Workspace liveness is + // owned by placement; do not probe the sandbox here, which would + // provision lazily-idle workspaces. + idleTimeToLive: (ref) => + ref.workspaceID !== undefined || existsSync(ref.directory) ? Duration.infinity : Duration.zero, + }), (inner) => ({ ...inner, get: (ref: Location.Ref) => inner.get(canonical(ref)),