chore: merge dev into v2 (#34788)
Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Co-authored-by: Kit Langton <kit.langton@gmail.com> Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com> Co-authored-by: Affan Ali <93028901+affanali2k3@users.noreply.github.com> Co-authored-by: affanali2k3 <affanalikhanxx@gmail.com> Co-authored-by: Frank <frank@anoma.ly> Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com> Co-authored-by: 𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴 <little-frank@opencord.local> Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Co-authored-by: Jay V <air@live.ca> Co-authored-by: Dax Raad <d@ironbay.co> Co-authored-by: Aarav Sareen <96787824+arvsrn@users.noreply.github.com> Co-authored-by: OpeOginni <107570612+OpeOginni@users.noreply.github.com> Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com> Co-authored-by: Ben Guthrie <benjee.012@gmail.com> Co-authored-by: Dax <mail@thdxr.com> Co-authored-by: Filip <34747899+neriousy@users.noreply.github.com> Co-authored-by: Max Anderson <max.a.anderson95@gmail.com> Co-authored-by: Brendan Allan <git@brendonovich.dev> Co-authored-by: Jack <jack@anoma.ly> Co-authored-by: Shoubhit Dash <shoubhit2005@gmail.com> Co-authored-by: Dustin Deus <deusdustin@gmail.com> Co-authored-by: starptech <starptech@starptechs-MBP.fritz.box> Co-authored-by: Aiden Cline <aidenpcline@gmail.com> Co-authored-by: usrnk1 <7547651+usrnk1@users.noreply.github.com> Co-authored-by: Jay <53023+jayair@users.noreply.github.com> Co-authored-by: runvip <164729189+runvip@users.noreply.github.com> Co-authored-by: opencode <opencode@sst.dev> Co-authored-by: Julian Coy <julian@ex-machina.co> Co-authored-by: Vladimir Glafirov <vglafirov@gitlab.com>
This commit is contained in:
parent
932a40cfd9
commit
8c94e9005f
590 changed files with 15772 additions and 5530 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.17.11",
|
||||
"version": "1.17.13",
|
||||
"name": "opencode",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
|
@ -120,7 +120,7 @@
|
|||
"drizzle-orm": "catalog:",
|
||||
"effect": "catalog:",
|
||||
"fuzzysort": "3.1.0",
|
||||
"gitlab-ai-provider": "6.9.3",
|
||||
"gitlab-ai-provider": "6.10.0",
|
||||
"glob": "13.0.5",
|
||||
"google-auth-library": "10.5.0",
|
||||
"gray-matter": "4.0.3",
|
||||
|
|
@ -144,7 +144,7 @@
|
|||
"tree-sitter-powershell": "0.25.10",
|
||||
"turndown": "7.2.0",
|
||||
"ulid": "catalog:",
|
||||
"venice-ai-sdk-provider": "2.0.2",
|
||||
"venice-ai-sdk-provider": "2.1.1",
|
||||
"vscode-jsonrpc": "8.2.1",
|
||||
"web-tree-sitter": "0.25.10",
|
||||
"ws": "8.21.0",
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/Ac
|
|||
|
||||
export const use = serviceUse(Service)
|
||||
|
||||
export const layer: Layer.Layer<Service, never, AccountRepo.Service | HttpClient.HttpClient> = Layer.effect(
|
||||
const layer: Layer.Layer<Service, never, AccountRepo.Service | HttpClient.HttpClient> = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const repo = yield* AccountRepo.Service
|
||||
|
|
@ -456,8 +456,6 @@ export const layer: Layer.Layer<Service, never, AccountRepo.Service | HttpClient
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(AccountRepo.defaultLayer), Layer.provide(FetchHttpClient.layer))
|
||||
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [AccountRepo.node, httpClient] })
|
||||
|
||||
export * as Account from "./account"
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/Ac
|
|||
|
||||
export const use = serviceUse(Service)
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const { db } = yield* Database.Service
|
||||
|
|
@ -166,8 +166,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(Database.defaultLayer))
|
||||
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [Database.node] })
|
||||
|
||||
export * as AccountRepo from "./repo"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { Agent } from "@/agent/agent"
|
||||
import { Command } from "@/command"
|
||||
import { InstanceRef } from "@/effect/instance-ref"
|
||||
import { InstanceBootstrap } from "@/project/bootstrap"
|
||||
import { InstanceStore } from "@/project/instance-store"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
import { Provider } from "@/provider/provider"
|
||||
|
|
@ -139,7 +141,7 @@ export const loaderLayer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const loader = yield* Loader
|
||||
|
|
@ -199,12 +201,12 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(
|
||||
Layer.provide(loaderLayer),
|
||||
Layer.provide(Provider.defaultLayer),
|
||||
Layer.provide(Agent.defaultLayer),
|
||||
Layer.provide(Command.defaultLayer),
|
||||
Layer.provide(InstanceStore.defaultLayer),
|
||||
)
|
||||
export const loaderNode = LayerNode.make({
|
||||
service: Loader,
|
||||
layer: loaderLayer,
|
||||
deps: [Provider.node, Agent.node, Command.node, InstanceStore.node],
|
||||
})
|
||||
|
||||
export const node = LayerNode.make({ service: Service, layer, deps: [loaderNode] })
|
||||
|
||||
export * as Directory from "./directory"
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import {
|
|||
type SetSessionModeResponse,
|
||||
} from "@agentclientprotocol/sdk"
|
||||
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import type { AssistantMessage, Message, OpencodeClient, SessionMessageResponse } from "@opencode-ai/sdk/v2"
|
||||
import { Context, Effect, Layer, ManagedRuntime } from "effect"
|
||||
import * as ACPError from "./error"
|
||||
|
|
@ -569,23 +570,24 @@ export function make(input: {
|
|||
}
|
||||
|
||||
function makeSessionService() {
|
||||
return ManagedRuntime.make(ACPSession.defaultLayer).runSync(
|
||||
return ManagedRuntime.make(AppNodeBuilder.build(ACPSession.node)).runSync(
|
||||
ACPSession.Service.use((service) => Effect.succeed(service)),
|
||||
)
|
||||
}
|
||||
|
||||
function makeDirectoryService(sdk: OpencodeClient) {
|
||||
return ManagedRuntime.make(
|
||||
Directory.layer.pipe(
|
||||
Layer.provide(
|
||||
AppNodeBuilder.build(Directory.node, [
|
||||
[
|
||||
Directory.loaderNode,
|
||||
Layer.succeed(
|
||||
Directory.Loader,
|
||||
Directory.Loader.of({
|
||||
load: (directory) => request(() => loadDirectorySnapshot(sdk, directory), "directory"),
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
]),
|
||||
).runSync(Directory.Service.use((service) => Effect.succeed(service)))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { McpServer } from "@agentclientprotocol/sdk"
|
||||
import type { Message, Part } from "@opencode-ai/sdk/v2"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
import { Context, Effect, Layer, Ref } from "effect"
|
||||
|
|
@ -93,7 +94,7 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/AC
|
|||
|
||||
type State = Map<string, Info>
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const sessions = yield* Ref.make<State>(new Map())
|
||||
|
|
@ -200,7 +201,7 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer
|
||||
export const node = LayerNode.make({ service: Service, layer, deps: [] })
|
||||
|
||||
function makeSession(input: StoreInput): Info {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import type { AgentSideConnection, Usage } from "@agentclientprotocol/sdk"
|
||||
import type { AssistantMessage as OpenCodeAssistantMessage, Message } from "@opencode-ai/sdk/v2"
|
||||
import { InstanceRef } from "@/effect/instance-ref"
|
||||
import { InstanceBootstrap } from "@/project/bootstrap"
|
||||
import { InstanceStore } from "@/project/instance-store"
|
||||
import { makeGlobalNode, Node } from "@opencode-ai/core/effect/app-node"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
import { Provider } from "@/provider/provider"
|
||||
|
|
@ -132,7 +135,7 @@ export const contextLimitLoaderLayer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const messageLoader = yield* MessageLoader
|
||||
|
|
@ -223,10 +226,14 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(
|
||||
Layer.provide(contextLimitLoaderLayer),
|
||||
Layer.provide(Provider.defaultLayer),
|
||||
Layer.provide(InstanceStore.defaultLayer),
|
||||
)
|
||||
export const messageLoaderNode = LayerNode.unbound(MessageLoader, Node.tags.values.global)
|
||||
|
||||
export const contextLimitLoaderNode = makeGlobalNode({
|
||||
service: ContextLimitLoader,
|
||||
layer: contextLimitLoaderLayer,
|
||||
deps: [Provider.node, InstanceStore.node],
|
||||
})
|
||||
|
||||
export const node = makeGlobalNode({ service: Service, layer, deps: [messageLoaderNode, contextLimitLoaderNode] })
|
||||
|
||||
export * as UsageService from "./usage"
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/Ag
|
|||
|
||||
export const use = serviceUse(Service)
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const config = yield* Config.Service
|
||||
|
|
@ -438,15 +438,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(
|
||||
Layer.provide(Plugin.defaultLayer),
|
||||
Layer.provide(Provider.defaultLayer),
|
||||
Layer.provide(Auth.defaultLayer),
|
||||
Layer.provide(Config.defaultLayer),
|
||||
Layer.provide(Skill.defaultLayer),
|
||||
Layer.provide(locationServiceMapLayer),
|
||||
)
|
||||
|
||||
const locationServiceMapNode = LayerNode.make({
|
||||
service: LocationServiceMap.Service,
|
||||
layer: locationServiceMapLayer,
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export interface Interface {
|
|||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/Auth") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const fsys = yield* FSUtil.Service
|
||||
|
|
@ -92,8 +92,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(FSUtil.defaultLayer))
|
||||
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [FSUtil.node] })
|
||||
|
||||
export * as Auth from "."
|
||||
|
|
|
|||
|
|
@ -7,8 +7,9 @@ export const ScrapCommand = cmd({
|
|||
builder: (yargs) => yargs,
|
||||
async handler() {
|
||||
const { Project } = await import("@/project/project")
|
||||
const { AppNodeBuilder } = await import("@opencode-ai/core/effect/app-node-builder")
|
||||
const { makeRuntime } = await import("@opencode-ai/core/effect/runtime")
|
||||
const runtime = makeRuntime(Project.Service, Project.defaultLayer)
|
||||
const runtime = makeRuntime(Project.Service, AppNodeBuilder.build(Project.node))
|
||||
const list = await runtime.runPromise((project) => project.list())
|
||||
process.stdout.write(JSON.stringify(list, null, 2) + EOL)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -761,7 +761,7 @@ export const McpDebugCommand = effectCmd({
|
|||
}
|
||||
|
||||
if (response.status === 401) {
|
||||
prompts.log.warn("Server returned 401 Unauthorized")
|
||||
prompts.log.info("Initial unauthenticated check returned 401, so this server requires OAuth")
|
||||
|
||||
// Try to discover OAuth metadata
|
||||
const oauthConfig = typeof serverConfig.oauth === "object" ? serverConfig.oauth : undefined
|
||||
|
|
|
|||
|
|
@ -233,11 +233,27 @@ export const RunCommand = effectCmd({
|
|||
hidden: true,
|
||||
describe: "cap visible interactive replay to the newest N messages",
|
||||
})
|
||||
.option("dangerously-skip-permissions", {
|
||||
.option("interactive", {
|
||||
alias: ["i"],
|
||||
type: "boolean",
|
||||
describe: "run in direct interactive split-footer mode",
|
||||
default: false,
|
||||
})
|
||||
.option("auto", {
|
||||
type: "boolean",
|
||||
describe: "auto-approve permissions that are not explicitly denied (dangerous!)",
|
||||
default: false,
|
||||
})
|
||||
.option("yolo", {
|
||||
type: "boolean",
|
||||
hidden: true,
|
||||
default: false,
|
||||
})
|
||||
.option("dangerously-skip-permissions", {
|
||||
type: "boolean",
|
||||
hidden: true,
|
||||
default: false,
|
||||
})
|
||||
.option("demo", {
|
||||
type: "boolean",
|
||||
default: false,
|
||||
|
|
@ -255,6 +271,7 @@ export const RunCommand = effectCmd({
|
|||
yield* Effect.promise(async () => {
|
||||
const rawMessage = [...args.message, ...(args["--"] || [])].join(" ")
|
||||
const interactive = args.mini
|
||||
const auto = args.auto || args.yolo || args["dangerously-skip-permissions"]
|
||||
const thinking = interactive ? (args.thinking ?? true) : (args.thinking ?? false)
|
||||
const die = (message: string): never => {
|
||||
UI.error(message)
|
||||
|
|
@ -780,7 +797,7 @@ export const RunCommand = effectCmd({
|
|||
const permission = event.properties
|
||||
if (permission.sessionID !== sessionID) continue
|
||||
|
||||
if (args["dangerously-skip-permissions"]) {
|
||||
if (auto) {
|
||||
await client.permission.reply({
|
||||
requestID: permission.id,
|
||||
reply: "once",
|
||||
|
|
@ -981,9 +998,12 @@ export async function runMini(input: MiniCommandInput) {
|
|||
variant: undefined,
|
||||
thinking: undefined,
|
||||
mini: true,
|
||||
interactive: false,
|
||||
replay: input.replay ?? true,
|
||||
"replay-limit": input.replayLimit,
|
||||
replayLimit: input.replayLimit,
|
||||
auto: false,
|
||||
yolo: false,
|
||||
"dangerously-skip-permissions": false,
|
||||
dangerouslySkipPermissions: false,
|
||||
demo: input.demo ?? false,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
// variant and the persisted file.
|
||||
import path from "path"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { Context, Effect, Layer } from "effect"
|
||||
import { makeRuntime } from "@/effect/run-service"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
|
|
@ -135,7 +136,7 @@ function state(value: unknown): ModelState {
|
|||
}
|
||||
}
|
||||
|
||||
function createLayer(fs = FSUtil.defaultLayer) {
|
||||
function createLayer(fs = AppNodeBuilder.build(FSUtil.node)) {
|
||||
return Layer.fresh(
|
||||
Layer.effect(
|
||||
Service,
|
||||
|
|
@ -196,7 +197,7 @@ function createLayer(fs = FSUtil.defaultLayer) {
|
|||
}
|
||||
|
||||
/** @internal Exported for testing. */
|
||||
export function createVariantRuntime(fs = FSUtil.defaultLayer): VariantRuntime {
|
||||
export function createVariantRuntime(fs = AppNodeBuilder.build(FSUtil.node)): VariantRuntime {
|
||||
const runtime = makeRuntime(Service, createLayer(fs))
|
||||
return {
|
||||
resolveSavedVariant: (model) => runtime.runPromise((svc) => svc.resolveSavedVariant(model)).catch(() => undefined),
|
||||
|
|
|
|||
|
|
@ -6,11 +6,12 @@ import { fileURLToPath } from "url"
|
|||
import { UI } from "@/cli/ui"
|
||||
import { errorMessage } from "@opencode-ai/tui/util/error"
|
||||
import { withTimeout } from "@/util/timeout"
|
||||
import { withNetworkOptions, resolveNetworkOptionsNoConfig } from "@/cli/network"
|
||||
import { withNetworkOptions, resolveNetworkOptionsNoConfig, hasArg } from "@/cli/network"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { OpenCode } from "@opencode-ai/client"
|
||||
import { createOpencodeClient } from "@opencode-ai/sdk/v2"
|
||||
import { writeHeapSnapshot } from "v8"
|
||||
import { ServerAuth } from "@/server/auth"
|
||||
import { validateSession } from "../tui/validate-session"
|
||||
import { win32InstallCtrlCGuard } from "@opencode-ai/tui/terminal-win32"
|
||||
|
||||
|
|
@ -74,6 +75,21 @@ export const TuiThreadCommand = cmd({
|
|||
type: "string",
|
||||
describe: "agent to use",
|
||||
})
|
||||
.option("auto", {
|
||||
type: "boolean",
|
||||
describe: "auto-approve permissions that are not explicitly denied (dangerous!)",
|
||||
default: false,
|
||||
})
|
||||
.option("yolo", {
|
||||
type: "boolean",
|
||||
hidden: true,
|
||||
default: false,
|
||||
})
|
||||
.option("dangerously-skip-permissions", {
|
||||
type: "boolean",
|
||||
hidden: true,
|
||||
default: false,
|
||||
})
|
||||
.option("mini", {
|
||||
type: "boolean",
|
||||
describe: "start the minimal interactive interface",
|
||||
|
|
@ -221,6 +237,7 @@ export const TuiThreadCommand = cmd({
|
|||
model: args.model,
|
||||
prompt,
|
||||
fork: args.fork,
|
||||
auto: args.auto || args.yolo || args["dangerously-skip-permissions"],
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -37,6 +37,22 @@ export type NetworkOptions = InferredOptionTypes<typeof options>
|
|||
export function withNetworkOptions<T>(yargs: Argv<T>) {
|
||||
return yargs.options(options)
|
||||
}
|
||||
|
||||
export function hasArg(name: string) {
|
||||
return networkArgs().some((arg) => arg === name || arg.startsWith(name + "="))
|
||||
}
|
||||
|
||||
function hasBooleanArg(name: string) {
|
||||
return networkArgs().some(
|
||||
(arg) => arg === name || arg === name + "=true" || arg === name + "=false" || arg === "--no-" + name.slice(2),
|
||||
)
|
||||
}
|
||||
|
||||
function networkArgs() {
|
||||
const separator = process.argv.indexOf("--")
|
||||
return process.argv.slice(2, separator === -1 ? undefined : separator)
|
||||
}
|
||||
|
||||
export const resolveNetworkOptions = Effect.fn("Cli.resolveNetworkOptions")(function* (args: NetworkOptions) {
|
||||
const { Config } = yield* Effect.promise(() => import("@/config/config"))
|
||||
const config = yield* Config.Service.use((cfg) => cfg.getGlobal())
|
||||
|
|
@ -44,10 +60,10 @@ export const resolveNetworkOptions = Effect.fn("Cli.resolveNetworkOptions")(func
|
|||
})
|
||||
|
||||
export function resolveNetworkOptionsNoConfig(args: NetworkOptions, config?: ConfigV1.Info) {
|
||||
const portExplicitlySet = process.argv.includes("--port")
|
||||
const hostnameExplicitlySet = process.argv.includes("--hostname")
|
||||
const mdnsExplicitlySet = process.argv.includes("--mdns")
|
||||
const mdnsDomainExplicitlySet = process.argv.includes("--mdns-domain")
|
||||
const portExplicitlySet = hasArg("--port")
|
||||
const hostnameExplicitlySet = hasArg("--hostname")
|
||||
const mdnsExplicitlySet = hasBooleanArg("--mdns")
|
||||
const mdnsDomainExplicitlySet = hasArg("--mdns-domain")
|
||||
const mdns = mdnsExplicitlySet ? args.mdns : (config?.server?.mdns ?? args.mdns)
|
||||
const mdnsDomain = mdnsDomainExplicitlySet ? args["mdns-domain"] : (config?.server?.mdnsDomain ?? args["mdns-domain"])
|
||||
const port = portExplicitlySet ? args.port : (config?.server?.port ?? args.port)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { run as runTui, type TuiInput } from "@opencode-ai/tui"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { Effect } from "effect"
|
||||
|
||||
export function run(input: TuiInput) {
|
||||
return runTui(input).pipe(Effect.provide(Global.defaultLayer))
|
||||
return runTui(input).pipe(Effect.provide(AppNodeBuilder.build(Global.node)))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ export interface Interface {
|
|||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/Command") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const config = yield* Config.Service
|
||||
|
|
@ -172,12 +172,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(
|
||||
Layer.provide(Config.defaultLayer),
|
||||
Layer.provide(MCP.defaultLayer),
|
||||
Layer.provide(Skill.defaultLayer),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [Config.node, MCP.node, Skill.node] })
|
||||
|
||||
export * as Command from "."
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ function writableGlobal(info: Info) {
|
|||
return next
|
||||
}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* FSUtil.Service
|
||||
|
|
@ -671,16 +671,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(
|
||||
Layer.provide(EffectFlock.defaultLayer),
|
||||
Layer.provide(FSUtil.defaultLayer),
|
||||
Layer.provide(Env.defaultLayer),
|
||||
Layer.provide(Auth.defaultLayer),
|
||||
Layer.provide(Account.defaultLayer),
|
||||
Layer.provide(Npm.defaultLayer),
|
||||
Layer.provide(FetchHttpClient.layer),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ export * as TuiConfig from "./tui"
|
|||
|
||||
import path from "path"
|
||||
import { mergeDeep, unique } from "remeda"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { Cause, Context, Effect, Fiber, Layer } from "effect"
|
||||
import { ConfigParse } from "@/config/parse"
|
||||
import * as ConfigPaths from "@/config/paths"
|
||||
|
|
@ -223,7 +225,7 @@ const loadState = Effect.fn("TuiConfig.loadState")(function* (ctx: { directory:
|
|||
}
|
||||
})
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const directory = yield* CurrentWorkingDirectory
|
||||
|
|
@ -257,9 +259,9 @@ export const layer = Layer.effect(
|
|||
}).pipe(Effect.withSpan("TuiConfig.layer")),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(Npm.defaultLayer), Layer.provide(FSUtil.defaultLayer))
|
||||
export const node = LayerNode.make({ service: Service, layer, deps: [Npm.node, FSUtil.node] })
|
||||
|
||||
const { runPromise } = makeRuntime(Service, defaultLayer)
|
||||
const { runPromise } = makeRuntime(Service, AppNodeBuilder.build(node))
|
||||
|
||||
export async function waitForDependencies() {
|
||||
await runPromise((svc) => svc.waitForDependencies())
|
||||
|
|
|
|||
|
|
@ -31,8 +31,8 @@ import { waitEvent } from "./util"
|
|||
import { WorkspaceRef } from "@/effect/instance-ref"
|
||||
import { Vcs } from "@/project/vcs"
|
||||
import { InstanceStore } from "@/project/instance-store"
|
||||
import { InstanceBootstrap } from "@/project/bootstrap"
|
||||
import { WorkspaceAdapterRuntime } from "./workspace-adapter-runtime"
|
||||
import { AppNodeBuilderV1 } from "@/effect/app-node-builder-v1"
|
||||
import { WorkspaceEvent } from "@opencode-ai/schema/workspace-event"
|
||||
|
||||
export const Info = Schema.Struct({
|
||||
|
|
@ -150,7 +150,7 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/Wo
|
|||
|
||||
export const use = serviceUse(Service)
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const auth = yield* Auth.Service
|
||||
|
|
@ -601,7 +601,7 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
fallback: "",
|
||||
response: "text",
|
||||
}).pipe(Effect.provide(InstanceStore.defaultLayer.pipe(Layer.provide(InstanceBootstrap.defaultLayer))))
|
||||
}).pipe(Effect.provide(AppNodeBuilderV1.build(InstanceStore.node)))
|
||||
: ""
|
||||
|
||||
if (sourcePatch) {
|
||||
|
|
@ -617,7 +617,7 @@ export const layer = Layer.effect(
|
|||
body: HttpBody.jsonUnsafe({ patch: sourcePatch }),
|
||||
}),
|
||||
fallback: { applied: false },
|
||||
}).pipe(Effect.provide(InstanceStore.defaultLayer.pipe(Layer.provide(InstanceBootstrap.defaultLayer))))
|
||||
}).pipe(Effect.provide(AppNodeBuilderV1.build(InstanceStore.node)))
|
||||
}
|
||||
|
||||
if (input.workspaceID === null) {
|
||||
|
|
@ -885,19 +885,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(
|
||||
Layer.provide(Auth.defaultLayer),
|
||||
Layer.provide(Session.defaultLayer),
|
||||
Layer.provide(SessionPrompt.defaultLayer),
|
||||
Layer.provide(Project.defaultLayer),
|
||||
Layer.provide(Vcs.defaultLayer),
|
||||
Layer.provide(FSUtil.defaultLayer),
|
||||
Layer.provide(Database.defaultLayer),
|
||||
Layer.provide(EventV2Bridge.defaultLayer),
|
||||
Layer.provide(FetchHttpClient.layer),
|
||||
Layer.provide(RuntimeFlags.defaultLayer),
|
||||
)
|
||||
|
||||
const TIMEOUT = 5000
|
||||
|
||||
type HistoryEvent = {
|
||||
|
|
|
|||
12
packages/opencode/src/effect/app-node-builder-v1.ts
Normal file
12
packages/opencode/src/effect/app-node-builder-v1.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { InstanceBootstrap } from "@/project/bootstrap"
|
||||
import { InstanceStore } from "@/project/instance-store"
|
||||
|
||||
const bootstrapReplacement = [InstanceStore.bootstrapNode, InstanceBootstrap.node] as const
|
||||
|
||||
export function build<A, E>(root: LayerNode.Node<A, E, any>, replacements: LayerNode.Replacements = []) {
|
||||
return AppNodeBuilder.build(root, replacements.concat([bootstrapReplacement]))
|
||||
}
|
||||
|
||||
export * as AppNodeBuilderV1 from "./app-node-builder-v1"
|
||||
|
|
@ -38,7 +38,7 @@ import { Command } from "@/command"
|
|||
import { Truncate } from "@/tool/truncate"
|
||||
import { ToolRegistry } from "@/tool/registry"
|
||||
import { Format } from "@/format"
|
||||
import { InstanceLayer } from "@/project/instance-layer"
|
||||
import { InstanceStore } from "@/project/instance-store"
|
||||
import { Project } from "@/project/project"
|
||||
import { Vcs } from "@/project/vcs"
|
||||
import { Workspace } from "@/control-plane/workspace"
|
||||
|
|
@ -51,59 +51,62 @@ import { memoMap } from "@opencode-ai/core/effect/memo-map"
|
|||
import { Job } from "@/job"
|
||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { AppNodeBuilderV1 } from "./app-node-builder-v1"
|
||||
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
||||
|
||||
export const AppLayer = Layer.mergeAll(
|
||||
Npm.defaultLayer,
|
||||
FSUtil.defaultLayer,
|
||||
Database.defaultLayer,
|
||||
Auth.defaultLayer,
|
||||
Account.defaultLayer,
|
||||
Config.defaultLayer,
|
||||
Git.defaultLayer,
|
||||
Storage.defaultLayer,
|
||||
Snapshot.defaultLayer,
|
||||
Plugin.defaultLayer,
|
||||
ModelsDev.defaultLayer,
|
||||
Provider.defaultLayer,
|
||||
ProviderAuth.defaultLayer,
|
||||
Agent.defaultLayer,
|
||||
Skill.defaultLayer,
|
||||
Discovery.defaultLayer,
|
||||
Question.defaultLayer,
|
||||
Permission.defaultLayer,
|
||||
Todo.defaultLayer,
|
||||
Session.defaultLayer,
|
||||
SessionStatus.defaultLayer,
|
||||
Job.defaultLayer,
|
||||
RuntimeFlags.defaultLayer,
|
||||
EventV2Bridge.defaultLayer,
|
||||
SessionRunState.defaultLayer,
|
||||
SessionProcessor.defaultLayer,
|
||||
SessionCompaction.defaultLayer,
|
||||
SessionRevert.defaultLayer,
|
||||
SessionSummary.defaultLayer,
|
||||
SessionPrompt.defaultLayer,
|
||||
Instruction.defaultLayer,
|
||||
LLM.defaultLayer,
|
||||
LSP.defaultLayer,
|
||||
MCP.defaultLayer,
|
||||
McpAuth.defaultLayer,
|
||||
Command.defaultLayer,
|
||||
Truncate.defaultLayer,
|
||||
ToolRegistry.defaultLayer,
|
||||
Format.defaultLayer,
|
||||
Project.defaultLayer,
|
||||
Vcs.defaultLayer,
|
||||
Workspace.defaultLayer,
|
||||
Worktree.appLayer,
|
||||
Installation.defaultLayer,
|
||||
ShareNext.defaultLayer,
|
||||
SessionShare.defaultLayer,
|
||||
).pipe(
|
||||
Layer.provideMerge(Ripgrep.defaultLayer),
|
||||
Layer.provideMerge(InstanceLayer.layer),
|
||||
Layer.provideMerge(Observability.layer),
|
||||
)
|
||||
export const AppLayer = AppNodeBuilderV1.build(
|
||||
LayerNode.group([
|
||||
Npm.node,
|
||||
FSUtil.node,
|
||||
Database.node,
|
||||
Auth.node,
|
||||
Account.node,
|
||||
Config.node,
|
||||
Git.node,
|
||||
Storage.node,
|
||||
Snapshot.node,
|
||||
Plugin.node,
|
||||
ModelsDev.node,
|
||||
Provider.node,
|
||||
ProviderAuth.node,
|
||||
Agent.node,
|
||||
Skill.node,
|
||||
Discovery.node,
|
||||
Question.node,
|
||||
Permission.node,
|
||||
Todo.node,
|
||||
Session.node,
|
||||
SessionProjector.node,
|
||||
SessionStatus.node,
|
||||
Job.node,
|
||||
RuntimeFlags.node,
|
||||
EventV2Bridge.node,
|
||||
SessionRunState.node,
|
||||
SessionProcessor.node,
|
||||
SessionCompaction.node,
|
||||
SessionRevert.node,
|
||||
SessionSummary.node,
|
||||
SessionPrompt.node,
|
||||
Instruction.node,
|
||||
LLM.node,
|
||||
LSP.node,
|
||||
MCP.node,
|
||||
McpAuth.node,
|
||||
Command.node,
|
||||
Truncate.node,
|
||||
ToolRegistry.node,
|
||||
Format.node,
|
||||
InstanceStore.node,
|
||||
Project.node,
|
||||
Vcs.node,
|
||||
Workspace.node,
|
||||
Worktree.node,
|
||||
Installation.node,
|
||||
ShareNext.node,
|
||||
SessionShare.node,
|
||||
]),
|
||||
).pipe(Layer.provideMerge(AppNodeBuilderV1.build(Ripgrep.node)), Layer.provideMerge(Observability.layer))
|
||||
|
||||
const rt = ManagedRuntime.make(AppLayer, { memoMap })
|
||||
type Runtime = Pick<typeof rt, "runSync" | "runPromise" | "runPromiseExit" | "runFork" | "runCallback" | "dispose">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import { Layer, ManagedRuntime } from "effect"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
|
||||
import { Plugin } from "@/plugin"
|
||||
import { LSP } from "@/lsp/lsp"
|
||||
|
|
@ -10,14 +12,8 @@ import { Config } from "@/config/config"
|
|||
import * as Observability from "@opencode-ai/core/observability"
|
||||
import { memoMap } from "@opencode-ai/core/effect/memo-map"
|
||||
|
||||
export const BootstrapLayer = Layer.mergeAll(
|
||||
Config.defaultLayer,
|
||||
Plugin.defaultLayer,
|
||||
ShareNext.defaultLayer,
|
||||
Format.defaultLayer,
|
||||
LSP.defaultLayer,
|
||||
Vcs.defaultLayer,
|
||||
Snapshot.defaultLayer,
|
||||
export const BootstrapLayer = AppNodeBuilder.build(
|
||||
LayerNode.group([Config.node, Plugin.node, ShareNext.node, Format.node, LSP.node, Vcs.node, Snapshot.node]),
|
||||
).pipe(Layer.provide(Observability.layer))
|
||||
|
||||
export const BootstrapRuntime = ManagedRuntime.make(BootstrapLayer, { memoMap })
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ export type Shape<Fields extends ConfigMap> = {
|
|||
*/
|
||||
export type ServiceClass<Self, Id extends string, Service> = Context.ServiceClass<Self, Id, Service> & {
|
||||
/** Provide already-parsed config, useful in tests. */
|
||||
readonly layer: (input: Service) => Layer.Layer<Self>
|
||||
readonly configLayer: (input: Service) => Layer.Layer<Self>
|
||||
/** Parse config once from the active Effect ConfigProvider and provide the service. */
|
||||
readonly defaultLayer: Layer.Layer<Self, Config.ConfigError>
|
||||
readonly layer: Layer.Layer<Self, Config.ConfigError>
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -35,19 +35,19 @@ export type ServiceClass<Self, Id extends string, Service> = Context.ServiceClas
|
|||
* },
|
||||
* ) {}
|
||||
*
|
||||
* const live = ServerAuthConfig.defaultLayer
|
||||
* const test = ServerAuthConfig.layer({ password: Option.some("secret"), username: "kit" })
|
||||
* const live = ServerAuthConfig.layer
|
||||
* const test = ServerAuthConfig.configLayer({ password: Option.some("secret"), username: "kit" })
|
||||
* ```
|
||||
*/
|
||||
export const Service =
|
||||
<Self>() =>
|
||||
<const Id extends string, const Fields extends ConfigMap>(id: Id, fields: Fields) => {
|
||||
class ConfigTag extends Context.Service<Self, Shape<Fields>>()(id) {
|
||||
static layer(input: Shape<Fields>) {
|
||||
static configLayer(input: Shape<Fields>) {
|
||||
return Layer.succeed(this, this.of(input))
|
||||
}
|
||||
|
||||
static get defaultLayer() {
|
||||
static get layer() {
|
||||
const tag = this
|
||||
return Layer.effect(
|
||||
tag,
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ export class Service extends ConfigService.Service<Service>()("@opencode/Runtime
|
|||
|
||||
export type Info = Context.Service.Shape<typeof Service>
|
||||
|
||||
const emptyConfigLayer = Service.defaultLayer.pipe(
|
||||
const emptyConfigLayer = Service.layer.pipe(
|
||||
Layer.provide(ConfigProvider.layer(ConfigProvider.fromUnknown({}))),
|
||||
Layer.orDie,
|
||||
)
|
||||
|
|
@ -71,9 +71,7 @@ export const layer = (overrides: Partial<Info> = {}) =>
|
|||
}),
|
||||
).pipe(Layer.provide(emptyConfigLayer))
|
||||
|
||||
export const defaultLayer = Service.defaultLayer.pipe(Layer.orDie)
|
||||
|
||||
export const node = LayerNode.make({ service: Service, layer: defaultLayer, deps: [] })
|
||||
export const node = LayerNode.make({ service: Service, layer: Service.layer.pipe(Layer.orDie), deps: [] })
|
||||
|
||||
export * as RuntimeFlags from "./runtime-flags"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
|
|
|
|||
4
packages/opencode/src/env/index.ts
vendored
4
packages/opencode/src/env/index.ts
vendored
|
|
@ -16,7 +16,7 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/En
|
|||
|
||||
export const use = serviceUse(Service)
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const state = yield* InstanceState.make<State>(Effect.fn("Env.state")(() => Effect.succeed({ ...process.env })))
|
||||
|
|
@ -36,8 +36,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer
|
||||
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [] })
|
||||
|
||||
export * as Env from "."
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import { Context, Effect, Layer } from "effect"
|
|||
|
||||
export class Service extends Context.Service<Service, EventV2.Interface>()("@opencode/EventV2Bridge") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const events = yield* EventV2.Service
|
||||
|
|
@ -66,8 +66,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(EventV2.defaultLayer))
|
||||
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [EventV2.node] })
|
||||
|
||||
export * as EventV2Bridge from "./event-v2-bridge"
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/Fo
|
|||
|
||||
export const use = serviceUse(Service)
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const config = yield* Config.Service
|
||||
|
|
@ -194,12 +194,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(
|
||||
Layer.provide(Config.defaultLayer),
|
||||
Layer.provide(AppProcess.defaultLayer),
|
||||
Layer.provide(RuntimeFlags.defaultLayer),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ const kind = (code: string): Kind => {
|
|||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/Git") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const appProcess = yield* AppProcess.Service
|
||||
|
|
@ -343,8 +343,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(AppProcess.defaultLayer))
|
||||
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [AppProcess.node] })
|
||||
|
||||
export * as Git from "."
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ export interface Interface {
|
|||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/Image") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const config = yield* Config.Service
|
||||
|
|
@ -167,8 +167,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(Config.defaultLayer))
|
||||
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [Config.node] })
|
||||
|
||||
export * as Image from "./image"
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { httpClient } from "@opencode-ai/core/effect/app-node-platform"
|
||||
import { Effect, Layer, Schema, Context, Stream } from "effect"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
|
||||
import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
|
||||
import { withTransientReadRetry } from "@/util/effect-http-client"
|
||||
import { errorMessage } from "@/util/error"
|
||||
import { ChildProcess } from "effect/unstable/process"
|
||||
|
|
@ -82,7 +83,7 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/In
|
|||
|
||||
export const use = serviceUse(Service)
|
||||
|
||||
export const layer: Layer.Layer<Service, never, HttpClient.HttpClient | AppProcess.Service> = Layer.effect(
|
||||
const layer: Layer.Layer<Service, never, HttpClient.HttpClient | AppProcess.Service> = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const http = yield* HttpClient.HttpClient
|
||||
|
|
@ -324,14 +325,12 @@ export const layer: Layer.Layer<Service, never, HttpClient.HttpClient | AppProce
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(FetchHttpClient.layer), Layer.provide(AppProcess.defaultLayer))
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [httpClient, AppProcess.node] })
|
||||
|
||||
const { runPromise } = makeRuntime(Service, defaultLayer)
|
||||
const { runPromise } = makeRuntime(Service, AppNodeBuilder.build(node))
|
||||
|
||||
export const latest = (...args: Parameters<Interface["latest"]>) => runPromise((s) => s.latest(...args))
|
||||
export const method = () => runPromise((s) => s.method())
|
||||
export const upgrade = (...args: Parameters<Interface["upgrade"]>) => runPromise((s) => s.upgrade(...args))
|
||||
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [httpClient, AppProcess.node] })
|
||||
|
||||
export * as Installation from "."
|
||||
|
|
|
|||
|
|
@ -34,8 +34,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer
|
||||
|
||||
export const node = LayerNode.make({ service: Service, layer, deps: [] })
|
||||
|
||||
export * as Job from "./job"
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ export interface Interface {
|
|||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/LSP") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const config = yield* Config.Service
|
||||
|
|
@ -496,12 +496,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(
|
||||
Layer.provide(Config.defaultLayer),
|
||||
Layer.provide(RuntimeFlags.defaultLayer),
|
||||
Layer.provide(EventV2Bridge.defaultLayer),
|
||||
)
|
||||
|
||||
export * as Diagnostic from "./diagnostic"
|
||||
|
||||
export const node = LayerNode.make({
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/Mc
|
|||
|
||||
export const use = serviceUse(Service)
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* FSUtil.Service
|
||||
|
|
@ -158,8 +158,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(EffectFlock.defaultLayer), Layer.provide(FSUtil.defaultLayer))
|
||||
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [FSUtil.node, EffectFlock.node] })
|
||||
|
||||
export * as McpAuth from "./auth"
|
||||
|
|
|
|||
|
|
@ -72,7 +72,8 @@ export function convertTool(mcpTool: MCPToolDef, client: Client, timeout?: numbe
|
|||
.filter((text) => text.trim())
|
||||
.join("\n\n") || "MCP tool returned an error",
|
||||
)
|
||||
if (result.structuredContent === undefined || result.structuredContent === null) return result
|
||||
if (result.content.length > 0 || result.structuredContent === undefined || result.structuredContent === null)
|
||||
return result
|
||||
return {
|
||||
...result,
|
||||
content: [{ type: "text" as const, text: JSON.stringify(result.structuredContent) }],
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/MC
|
|||
|
||||
export const use = serviceUse(Service)
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
|
||||
|
|
@ -946,7 +946,7 @@ export const layer = Layer.effect(
|
|||
|
||||
const mcpConfig = yield* requireMcpConfig(mcpName)
|
||||
|
||||
return yield* createAndStore(mcpName, mcpConfig)
|
||||
return yield* createAndStore(mcpName, { ...mcpConfig, enabled: true })
|
||||
})
|
||||
|
||||
const removeAuth = Effect.fn("MCP.removeAuth")(function* (mcpName: string) {
|
||||
|
|
@ -1003,16 +1003,6 @@ export const layer = Layer.effect(
|
|||
|
||||
export type AuthStatus = "authenticated" | "expired" | "not_authenticated"
|
||||
|
||||
// --- Per-service runtime ---
|
||||
|
||||
export const defaultLayer = layer.pipe(
|
||||
Layer.provide(McpAuth.defaultLayer),
|
||||
Layer.provide(EventV2Bridge.defaultLayer),
|
||||
Layer.provide(Config.defaultLayer),
|
||||
Layer.provide(CrossSpawnSpawner.defaultLayer),
|
||||
Layer.provide(FSUtil.defaultLayer),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export function evaluate(permission: string, pattern: string, ...rulesets: Permi
|
|||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/Permission") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const events = yield* EventV2Bridge.Service
|
||||
|
|
@ -213,8 +213,6 @@ export function disabled(tools: string[], ruleset: PermissionV1.Ruleset): Set<st
|
|||
)
|
||||
}
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(EventV2Bridge.defaultLayer))
|
||||
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [EventV2Bridge.node] })
|
||||
|
||||
export * as Permission from "."
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ async function applyPlugin(load: PluginLoader.Loaded, input: PluginInput, hooks:
|
|||
}
|
||||
}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const events = yield* EventV2Bridge.Service
|
||||
|
|
@ -305,12 +305,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(
|
||||
Layer.provide(EventV2Bridge.defaultLayer),
|
||||
Layer.provide(Config.defaultLayer),
|
||||
Layer.provide(RuntimeFlags.defaultLayer),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import {
|
|||
import path from "path"
|
||||
import { fileURLToPath } from "url"
|
||||
import { TuiConfig } from "@/config/tui"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { errorData, errorMessage } from "@opencode-ai/tui/util/error"
|
||||
import { isRecord } from "@opencode-ai/tui/util/record"
|
||||
import { resolveHostAttentionSoundPaths } from "@/config/tui-host-attention"
|
||||
|
|
@ -1082,7 +1083,7 @@ async function load(input: {
|
|||
const flags = await Effect.runPromise(
|
||||
Effect.gen(function* () {
|
||||
return yield* RuntimeFlags.Service
|
||||
}).pipe(Effect.provide(RuntimeFlags.defaultLayer)),
|
||||
}).pipe(Effect.provide(AppNodeBuilder.build(RuntimeFlags.node))),
|
||||
)
|
||||
const pluginOrigins = config.plugin_origins ?? (await TuiConfig.pluginOrigins())
|
||||
const records = Flag.OPENCODE_PURE ? [] : pluginOrigins
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { makeGlobalNode } from "@opencode-ai/core/effect/app-node"
|
||||
import { Plugin } from "../plugin"
|
||||
import { Format } from "../format"
|
||||
import { LSP } from "@/lsp/lsp"
|
||||
|
|
@ -14,7 +14,7 @@ import { Service } from "./bootstrap-service"
|
|||
export { Service } from "./bootstrap-service"
|
||||
export type { Interface } from "./bootstrap-service"
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
// Yield each bootstrap dep at layer init so `run` itself has R = never.
|
||||
|
|
@ -49,20 +49,7 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer: Layer.Layer<Service> = layer.pipe(
|
||||
Layer.provide([
|
||||
Config.defaultLayer,
|
||||
Format.defaultLayer,
|
||||
LSP.defaultLayer,
|
||||
Plugin.defaultLayer,
|
||||
Project.defaultLayer,
|
||||
ShareNext.defaultLayer,
|
||||
Snapshot.defaultLayer,
|
||||
Vcs.defaultLayer,
|
||||
]),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make({
|
||||
export const node = makeGlobalNode({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
deps: [Config.node, Format.node, LSP.node, Plugin.node, Project.node, ShareNext.node, Snapshot.node, Vcs.node],
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
import { Effect, Layer } from "effect"
|
||||
import { InstanceStore } from "./instance-store"
|
||||
|
||||
export const layer = Layer.unwrap(
|
||||
Effect.promise(async () => {
|
||||
const { InstanceBootstrap } = await import("./bootstrap")
|
||||
return InstanceStore.defaultLayer.pipe(Layer.provide(InstanceBootstrap.defaultLayer))
|
||||
}),
|
||||
)
|
||||
|
||||
export * as InstanceLayer from "./instance-layer"
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { makeGlobalNode, Node } from "@opencode-ai/core/effect/app-node"
|
||||
import { GlobalBus } from "@/bus/global"
|
||||
import { serviceUse } from "@opencode-ai/core/effect/service-use"
|
||||
import { WorkspaceContext } from "@/control-plane/workspace-context"
|
||||
|
|
@ -8,7 +9,6 @@ import { FSUtil } from "@opencode-ai/core/fs-util"
|
|||
import { Context, Deferred, Duration, Effect, Exit, Layer, Scope } from "effect"
|
||||
import { type InstanceContext } from "./instance-context"
|
||||
import { InstanceBootstrap } from "./bootstrap-service"
|
||||
import { InstanceBootstrap as InstanceBootstrapGraph } from "./bootstrap"
|
||||
import * as Project from "./project"
|
||||
|
||||
export interface LoadInput {
|
||||
|
|
@ -34,7 +34,7 @@ interface Entry {
|
|||
readonly deferred: Deferred.Deferred<InstanceContext>
|
||||
}
|
||||
|
||||
export const layer: Layer.Layer<Service, never, Project.Service | InstanceBootstrap.Service> = Layer.effect(
|
||||
const layer: Layer.Layer<Service, never, Project.Service | InstanceBootstrap.Service> = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const project = yield* Project.Service
|
||||
|
|
@ -202,12 +202,12 @@ export const layer: Layer.Layer<Service, never, Project.Service | InstanceBootst
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(Project.defaultLayer))
|
||||
export const bootstrapNode = LayerNode.unbound(InstanceBootstrap.Service, Node.tags.values.global)
|
||||
|
||||
export const node = LayerNode.make({
|
||||
export const node = makeGlobalNode({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
deps: [Project.node, InstanceBootstrapGraph.node],
|
||||
deps: [Project.node, bootstrapNode],
|
||||
})
|
||||
|
||||
export * as InstanceStore from "./instance-store"
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/Pr
|
|||
|
||||
type GitResult = { code: number; text: string; stderr: string }
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* FSUtil.Service
|
||||
|
|
@ -463,17 +463,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(
|
||||
Layer.provide(EventV2Bridge.defaultLayer),
|
||||
Layer.provide(ProjectV2.defaultLayer),
|
||||
Layer.provide(ProjectDirectories.defaultLayer),
|
||||
Layer.provide(AppProcess.defaultLayer),
|
||||
Layer.provide(CrossSpawnSpawner.defaultLayer),
|
||||
Layer.provide(FSUtil.defaultLayer),
|
||||
Layer.provide(Database.defaultLayer),
|
||||
Layer.provide(RuntimeFlags.defaultLayer),
|
||||
)
|
||||
|
||||
export const use = serviceUse(Service)
|
||||
|
||||
export const node = LayerNode.make({
|
||||
|
|
|
|||
|
|
@ -295,7 +295,7 @@ interface State {
|
|||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/Vcs") {}
|
||||
|
||||
export const layer: Layer.Layer<Service, never, Git.Service | EventV2Bridge.Service> = Layer.effect(
|
||||
const layer: Layer.Layer<Service, never, Git.Service | EventV2Bridge.Service> = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const git = yield* Git.Service
|
||||
|
|
@ -418,8 +418,6 @@ export const layer: Layer.Layer<Service, never, Git.Service | EventV2Bridge.Serv
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(Git.defaultLayer), Layer.provide(EventV2Bridge.defaultLayer))
|
||||
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [Git.node, EventV2Bridge.node] })
|
||||
|
||||
export * as Vcs from "./vcs"
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/Pr
|
|||
|
||||
export const use = serviceUse(Service)
|
||||
|
||||
export const layer: Layer.Layer<Service, never, Auth.Service | Plugin.Service> = Layer.effect(
|
||||
const layer: Layer.Layer<Service, never, Auth.Service | Plugin.Service> = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const auth = yield* Auth.Service
|
||||
|
|
@ -224,10 +224,6 @@ export const layer: Layer.Layer<Service, never, Auth.Service | Plugin.Service> =
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = Layer.suspend(() =>
|
||||
layer.pipe(Layer.provide(Auth.defaultLayer), Layer.provide(Plugin.defaultLayer)),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [Auth.node, Plugin.node] })
|
||||
|
||||
export * as ProviderAuth from "./auth"
|
||||
|
|
|
|||
|
|
@ -1299,7 +1299,7 @@ function modelSuggestions(provider: Info | undefined, modelID: ModelV2.ID, enabl
|
|||
.map((item) => item.id)
|
||||
}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* FSUtil.Service
|
||||
|
|
@ -1949,18 +1949,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = Layer.suspend(() =>
|
||||
layer.pipe(
|
||||
Layer.provide(FSUtil.defaultLayer),
|
||||
Layer.provide(Env.defaultLayer),
|
||||
Layer.provide(Config.defaultLayer),
|
||||
Layer.provide(Auth.defaultLayer),
|
||||
Layer.provide(Plugin.defaultLayer),
|
||||
Layer.provide(ModelsDev.defaultLayer),
|
||||
Layer.provide(RuntimeFlags.defaultLayer),
|
||||
),
|
||||
)
|
||||
|
||||
const priority = ["gpt-5", "claude-sonnet-4", "big-pickle", "gemini-3-pro"]
|
||||
const smallModelFamilyPriority = ["gemini-flash", "gpt-nano", "claude-haiku"]
|
||||
export function sort<T extends { id: string }>(models: T[]) {
|
||||
|
|
|
|||
|
|
@ -463,7 +463,9 @@ export function message(msgs: ModelMessage[], model: Provider.Model, options: Re
|
|||
if (
|
||||
options.store !== true &&
|
||||
key &&
|
||||
["@ai-sdk/openai", "@ai-sdk/azure", "@ai-sdk/amazon-bedrock/mantle"].includes(model.api.npm)
|
||||
["@ai-sdk/openai", "@ai-sdk/azure", "@ai-sdk/amazon-bedrock/mantle", "@ai-sdk/github-copilot"].includes(
|
||||
model.api.npm,
|
||||
)
|
||||
) {
|
||||
msgs = mapProviderOptions(msgs, (options) => {
|
||||
if (!options?.[key] || !("itemId" in options[key])) return options
|
||||
|
|
@ -605,8 +607,14 @@ function anthropicOpus47OrLater(apiId: string) {
|
|||
return major > 4 || (major === 4 && minor >= 7)
|
||||
}
|
||||
|
||||
function anthropicSonnet5OrLater(apiId: string) {
|
||||
const version = /sonnet-(\d+)(?:[.@-]|$)|claude-(\d+)-sonnet(?:[.@-]|$)/i.exec(apiId)
|
||||
if (!version) return false
|
||||
return Number(version[1] ?? version[2]) >= 5
|
||||
}
|
||||
|
||||
function anthropicAdaptiveEfforts(apiId: string): string[] | null {
|
||||
if (anthropicOpus47OrLater(apiId) || apiId.includes("fable-5")) {
|
||||
if (anthropicOpus47OrLater(apiId) || anthropicSonnet5OrLater(apiId) || apiId.includes("fable-5")) {
|
||||
return ["low", "medium", "high", "xhigh", "max"]
|
||||
}
|
||||
if (
|
||||
|
|
@ -620,7 +628,7 @@ function anthropicAdaptiveEfforts(apiId: string): string[] | null {
|
|||
}
|
||||
|
||||
function anthropicOmitsThinking(apiId: string) {
|
||||
return anthropicOpus47OrLater(apiId) || apiId.includes("fable-5")
|
||||
return anthropicOpus47OrLater(apiId) || anthropicSonnet5OrLater(apiId) || apiId.includes("fable-5")
|
||||
}
|
||||
|
||||
function googleThinkingLevelEfforts(apiId: string) {
|
||||
|
|
@ -1255,6 +1263,16 @@ const SLUG_OVERRIDES: Record<string, string> = {
|
|||
}
|
||||
|
||||
export function providerOptions(model: Provider.Model, options: { [x: string]: any }) {
|
||||
const usesOpenAIReasoningGate =
|
||||
model.api.npm === "@ai-sdk/openai" ||
|
||||
model.api.npm === "@ai-sdk/azure" ||
|
||||
model.api.npm === "@ai-sdk/amazon-bedrock/mantle"
|
||||
const normalized =
|
||||
usesOpenAIReasoningGate &&
|
||||
(model.capabilities.reasoning || options.reasoningEffort !== undefined || options.reasoningSummary !== undefined)
|
||||
? { ...options, forceReasoning: true }
|
||||
: options
|
||||
|
||||
if (model.api.npm === "@ai-sdk/gateway") {
|
||||
// Gateway providerOptions are split across two namespaces:
|
||||
// - `gateway`: gateway-native routing/caching controls (order, only, byok, etc.)
|
||||
|
|
@ -1264,8 +1282,8 @@ export function providerOptions(model: Provider.Model, options: { [x: string]: a
|
|||
const i = model.api.id.indexOf("/")
|
||||
const rawSlug = i > 0 ? model.api.id.slice(0, i) : undefined
|
||||
const slug = rawSlug ? (SLUG_OVERRIDES[rawSlug] ?? rawSlug) : undefined
|
||||
const gateway = options.gateway
|
||||
const rest = Object.fromEntries(Object.entries(options).filter(([k]) => k !== "gateway"))
|
||||
const gateway = normalized.gateway
|
||||
const rest = Object.fromEntries(Object.entries(normalized).filter(([k]) => k !== "gateway"))
|
||||
const has = Object.keys(rest).length > 0
|
||||
|
||||
const result: Record<string, any> = {}
|
||||
|
|
@ -1299,9 +1317,9 @@ export function providerOptions(model: Provider.Model, options: { [x: string]: a
|
|||
// providerOptions["openai"], but OpenAIResponsesLanguageModel checks
|
||||
// "azure" first. Pass both so model options work on either code path.
|
||||
if (model.api.npm === "@ai-sdk/azure") {
|
||||
return { openai: options, azure: options }
|
||||
return { openai: normalized, azure: normalized }
|
||||
}
|
||||
return { [key]: options }
|
||||
return { [key]: normalized }
|
||||
}
|
||||
|
||||
export function maxOutputTokens(model: Provider.Model, outputTokenMax = OUTPUT_TOKEN_MAX): number {
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ export interface Interface {
|
|||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/Question") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const events = yield* EventV2Bridge.Service
|
||||
|
|
@ -156,8 +156,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(EventV2Bridge.defaultLayer))
|
||||
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [EventV2Bridge.node] })
|
||||
|
||||
export * as Question from "."
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ import { Worktree } from "@/worktree"
|
|||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
import { MoveSession } from "@opencode-ai/core/control-plane/move-session"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
import { AppNodeBuilderV1 } from "@/effect/app-node-builder-v1"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { httpClient } from "@opencode-ai/core/effect/app-node-platform"
|
||||
import { EventV2 } from "@opencode-ai/core/event"
|
||||
|
|
@ -63,6 +64,7 @@ import { PtyTicket } from "@opencode-ai/core/pty/ticket"
|
|||
import { Ripgrep } from "@opencode-ai/core/ripgrep"
|
||||
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
||||
import { SessionV2 } from "@opencode-ai/core/session"
|
||||
import { SessionExecution } from "@opencode-ai/core/session/execution"
|
||||
import * as SessionExecutionLocal from "@opencode-ai/core/session/execution/local"
|
||||
import { lazy } from "@/util/lazy"
|
||||
import { CorsConfig, isAllowedCorsOrigin, type CorsOptions } from "@opencode-ai/server/cors"
|
||||
|
|
@ -98,7 +100,7 @@ import { sessionHandlers } from "./handlers/session"
|
|||
import { syncHandlers } from "./handlers/sync"
|
||||
import { tuiHandlers } from "./handlers/tui"
|
||||
import { handlers } from "@opencode-ai/server/handlers"
|
||||
import { locationServiceMapLayer } from "@opencode-ai/core/location-services"
|
||||
import { buildLocationServiceMap, LocationServiceMap } from "@opencode-ai/core/location-services"
|
||||
import { layer as locationLayer } from "@opencode-ai/server/location"
|
||||
import { sessionLocationLayer } from "@opencode-ai/server/middleware/session-location"
|
||||
import { PtyEnvironment } from "@opencode-ai/server/pty-environment"
|
||||
|
|
@ -131,10 +133,10 @@ const cors = (corsOptions?: CorsOptions) =>
|
|||
// - ptyConnectApiRoutes: typed WebSocket upgrade route with ticket-aware auth.
|
||||
// - instanceApiRoutes: remaining typed instance routes.
|
||||
// - uiRoute: raw catch-all fallback; auth is router middleware so public static assets can bypass it.
|
||||
const authOnlyRouterLayer = authorizationRouterMiddleware.layer.pipe(Layer.provide(ServerAuth.Config.defaultLayer))
|
||||
const httpApiAuthLayer = authorizationLayer.pipe(Layer.provide(ServerAuth.Config.defaultLayer))
|
||||
const ptyConnectHttpApiAuthLayer = ptyConnectAuthorizationLayer.pipe(Layer.provide(ServerAuth.Config.defaultLayer))
|
||||
const serverHttpApiAuthLayer = serverAuthorizationLayer.pipe(Layer.provide(ServerAuth.Config.defaultLayer))
|
||||
const authOnlyRouterLayer = authorizationRouterMiddleware.layer.pipe(Layer.provide(ServerAuth.Config.layer))
|
||||
const httpApiAuthLayer = authorizationLayer.pipe(Layer.provide(ServerAuth.Config.layer))
|
||||
const ptyConnectHttpApiAuthLayer = ptyConnectAuthorizationLayer.pipe(Layer.provide(ServerAuth.Config.layer))
|
||||
const serverHttpApiAuthLayer = serverAuthorizationLayer.pipe(Layer.provide(ServerAuth.Config.layer))
|
||||
const workspaceRoutingLive = workspaceRoutingLayer.pipe(Layer.provide(Socket.layerWebSocketConstructorGlobal))
|
||||
const rootApiRoutes = HttpApiBuilder.layer(RootHttpApi).pipe(
|
||||
Layer.provide([controlHandlers, controlPlaneHandlers, globalHandlers]),
|
||||
|
|
@ -269,6 +271,8 @@ const app = LayerNode.group([
|
|||
export function createRoutes(
|
||||
corsOptions?: CorsOptions,
|
||||
): Layer.Layer<never, EffectConfig.ConfigError, RouteRequirements> {
|
||||
const locationServiceMapV2 = buildLocationServiceMap()
|
||||
|
||||
return Layer.mergeAll(
|
||||
rootApiRoutes,
|
||||
eventApiRoutes,
|
||||
|
|
@ -284,7 +288,7 @@ export function createRoutes(
|
|||
corsVaryFix,
|
||||
fenceLayer,
|
||||
cors(corsOptions),
|
||||
MoveSession.defaultLayer,
|
||||
AppNodeBuilderV1.build(MoveSession.node, [[LocationServiceMap.node, locationServiceMapV2]]),
|
||||
HttpServer.layerServices,
|
||||
]),
|
||||
Layer.provide(Layer.succeed(CorsConfig)(corsOptions)),
|
||||
|
|
@ -294,14 +298,14 @@ export function createRoutes(
|
|||
Layer.provide(locationLayer),
|
||||
Layer.provide(PtyEnvironment.layer),
|
||||
Layer.provide(
|
||||
SessionV2.defaultLayer.pipe(
|
||||
Layer.provide(SessionExecutionLocal.defaultLayer),
|
||||
Layer.provide(locationServiceMapLayer),
|
||||
),
|
||||
AppNodeBuilderV1.build(SessionV2.node, [
|
||||
[LocationServiceMap.node, locationServiceMapV2],
|
||||
[SessionExecution.node, SessionExecutionLocal.node],
|
||||
]),
|
||||
),
|
||||
Layer.provide(locationServiceMapLayer),
|
||||
Layer.provide(locationServiceMapV2),
|
||||
|
||||
Layer.provide(LayerNode.compile(app)),
|
||||
Layer.provide(AppNodeBuilderV1.build(app)),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Context, Effect, Layer, Option } from "effect"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import * as Socket from "effect/unstable/socket/Socket"
|
||||
|
||||
export const SERVER_CLOSING_EVENT = () => new Socket.CloseEvent(1001, "server closing")
|
||||
|
|
@ -13,7 +14,7 @@ export interface Interface {
|
|||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/HttpApiWebSocketTracker") {}
|
||||
|
||||
export const layer = Layer.sync(Service)(() => {
|
||||
const layer = Layer.sync(Service)(() => {
|
||||
const sockets = new Set<Close>()
|
||||
let closing = false
|
||||
return Service.of({
|
||||
|
|
@ -44,6 +45,8 @@ export const layer = Layer.sync(Service)(() => {
|
|||
})
|
||||
})
|
||||
|
||||
export const node = LayerNode.make({ service: Service, layer, deps: [] })
|
||||
|
||||
export const register = (close: Close) =>
|
||||
Effect.gen(function* () {
|
||||
const tracker = yield* Effect.serviceOption(Service)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import "./init-projectors"
|
||||
|
||||
import { NodeHttpServer } from "@effect/platform-node"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { ConfigProvider, Context, Effect, Exit, Layer, Scope } from "effect"
|
||||
import { HttpRouter, HttpServer } from "effect/unstable/http"
|
||||
import { OpenApi } from "effect/unstable/httpapi"
|
||||
|
|
@ -102,7 +103,7 @@ function listenerLayer(opts: ListenOptions, port: number) {
|
|||
disableLogger: true,
|
||||
disableListenLog: true,
|
||||
}).pipe(
|
||||
Layer.provideMerge(WebSocketTracker.layer),
|
||||
Layer.provideMerge(AppNodeBuilder.build(WebSocketTracker.node)),
|
||||
Layer.provideMerge(serverLayer({ port, hostname: opts.hostname })),
|
||||
// Install a fresh `ConfigProvider` per listener so `Config.string(...)`
|
||||
// reads reflect the current `process.env`. Effect's default
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/Se
|
|||
|
||||
export const use = serviceUse(Service)
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const config = yield* Config.Service
|
||||
|
|
@ -544,19 +544,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = Layer.suspend(() =>
|
||||
layer.pipe(
|
||||
Layer.provide(Provider.defaultLayer),
|
||||
Layer.provide(Session.defaultLayer),
|
||||
Layer.provide(SessionProcessor.defaultLayer),
|
||||
Layer.provide(Agent.defaultLayer),
|
||||
Layer.provide(Plugin.defaultLayer),
|
||||
Layer.provide(Config.defaultLayer),
|
||||
Layer.provide(RuntimeFlags.defaultLayer),
|
||||
Layer.provide(EventV2Bridge.defaultLayer),
|
||||
),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ export interface Interface {
|
|||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/Instruction") {}
|
||||
|
||||
export const layer: Layer.Layer<
|
||||
const layer: Layer.Layer<
|
||||
Service,
|
||||
never,
|
||||
FSUtil.Service | Config.Service | Global.Service | HttpClient.HttpClient | RuntimeFlags.Service
|
||||
|
|
@ -224,14 +224,6 @@ export const layer: Layer.Layer<
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(
|
||||
Layer.provide(Config.defaultLayer),
|
||||
Layer.provide(Global.layer),
|
||||
Layer.provide(FSUtil.defaultLayer),
|
||||
Layer.provide(FetchHttpClient.layer),
|
||||
Layer.provide(RuntimeFlags.defaultLayer),
|
||||
)
|
||||
|
||||
export function loaded(messages: SessionV1.WithParts[]) {
|
||||
return extract(messages)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { Context, Effect, Layer } from "effect"
|
|||
import * as Stream from "effect/Stream"
|
||||
import { streamText, wrapLanguageModel, type ModelMessage, type Tool } from "ai"
|
||||
import type { LLMEvent } from "@opencode-ai/llm"
|
||||
import { LLMClient, RequestExecutor, WebSocketExecutor } from "@opencode-ai/llm/route"
|
||||
import { LLMClient } from "@opencode-ai/llm/route"
|
||||
import type { LLMClientService } from "@opencode-ai/llm/route"
|
||||
import { GitLabWorkflowLanguageModel } from "gitlab-ai-provider"
|
||||
import { ProviderTransform } from "@/provider/transform"
|
||||
|
|
@ -384,26 +384,11 @@ const live: Layer.Layer<
|
|||
}),
|
||||
)
|
||||
|
||||
export const layer = live.pipe(Layer.provide(Permission.defaultLayer), Layer.provide(EventV2Bridge.defaultLayer))
|
||||
|
||||
export const defaultLayer = Layer.suspend(() =>
|
||||
layer.pipe(
|
||||
Layer.provide(Auth.defaultLayer),
|
||||
Layer.provide(Config.defaultLayer),
|
||||
Layer.provide(Provider.defaultLayer),
|
||||
Layer.provide(Plugin.defaultLayer),
|
||||
Layer.provide(
|
||||
LLMClient.layer.pipe(Layer.provide(Layer.mergeAll(RequestExecutor.defaultLayer, WebSocketExecutor.layer))),
|
||||
),
|
||||
Layer.provide(RuntimeFlags.defaultLayer),
|
||||
),
|
||||
)
|
||||
|
||||
export const hasToolCalls = LLMRequestPrep.hasToolCalls
|
||||
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
layer: live,
|
||||
deps: [
|
||||
Auth.node,
|
||||
Config.node,
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ type StreamEvent = LLMEvent
|
|||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionProcessor") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const session = yield* Session.Service
|
||||
|
|
@ -694,23 +694,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = Layer.suspend(() =>
|
||||
layer.pipe(
|
||||
Layer.provide(Session.defaultLayer),
|
||||
Layer.provide(Snapshot.defaultLayer),
|
||||
Layer.provide(Agent.defaultLayer),
|
||||
Layer.provide(LLM.defaultLayer),
|
||||
Layer.provide(Permission.defaultLayer),
|
||||
Layer.provide(Plugin.defaultLayer),
|
||||
Layer.provide(SessionSummary.defaultLayer),
|
||||
Layer.provide(SessionStatus.defaultLayer),
|
||||
Layer.provide(Image.defaultLayer),
|
||||
Layer.provide(Config.defaultLayer),
|
||||
Layer.provide(Database.defaultLayer),
|
||||
Layer.provide(EventV2Bridge.defaultLayer),
|
||||
),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ export interface Interface {
|
|||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionPrompt") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const status = yield* SessionStatus.Service
|
||||
|
|
@ -1490,40 +1490,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = Layer.suspend(() =>
|
||||
layer.pipe(
|
||||
Layer.provide(SessionRunState.defaultLayer),
|
||||
Layer.provide(SessionStatus.defaultLayer),
|
||||
Layer.provide(SessionCompaction.defaultLayer),
|
||||
Layer.provide(SessionProcessor.defaultLayer),
|
||||
Layer.provide(Command.defaultLayer),
|
||||
Layer.provide(Permission.defaultLayer),
|
||||
Layer.provide(MCP.defaultLayer),
|
||||
Layer.provide(LSP.defaultLayer),
|
||||
Layer.provide(ToolRegistry.defaultLayer),
|
||||
Layer.provide(Truncate.defaultLayer),
|
||||
Layer.provide(Provider.defaultLayer),
|
||||
Layer.provide(Config.defaultLayer),
|
||||
Layer.provide(Instruction.defaultLayer),
|
||||
Layer.provide(FSUtil.defaultLayer),
|
||||
Layer.provide(Plugin.defaultLayer),
|
||||
Layer.provide(Session.defaultLayer),
|
||||
Layer.provide(SessionRevert.defaultLayer),
|
||||
Layer.provide(SessionSummary.defaultLayer),
|
||||
Layer.provide(Image.defaultLayer),
|
||||
Layer.provide(
|
||||
Layer.mergeAll(
|
||||
Agent.defaultLayer,
|
||||
Database.defaultLayer,
|
||||
SystemPrompt.defaultLayer,
|
||||
LLM.defaultLayer,
|
||||
CrossSpawnSpawner.defaultLayer,
|
||||
RuntimeFlags.defaultLayer,
|
||||
EventV2Bridge.defaultLayer,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
const ModelRef = Schema.Struct({
|
||||
providerID: ProviderV2.ID,
|
||||
modelID: ModelV2.ID,
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export interface Interface {
|
|||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionRevert") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const sessions = yield* Session.Service
|
||||
|
|
@ -137,17 +137,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = Layer.suspend(() =>
|
||||
layer.pipe(
|
||||
Layer.provide(SessionRunState.defaultLayer),
|
||||
Layer.provide(Session.defaultLayer),
|
||||
Layer.provide(Snapshot.defaultLayer),
|
||||
Layer.provide(Storage.defaultLayer),
|
||||
Layer.provide(EventV2Bridge.defaultLayer),
|
||||
Layer.provide(SessionSummary.defaultLayer),
|
||||
),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ export interface Interface {
|
|||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionRunState") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const jobs = yield* Job.Service
|
||||
|
|
@ -108,8 +108,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(Job.defaultLayer), Layer.provide(SessionStatus.defaultLayer))
|
||||
|
||||
const cancelJobs = Effect.fn("SessionRunState.cancelJobs")(function* (jobs: Job.Interface, sessionID: SessionID) {
|
||||
const running = yield* jobs.list()
|
||||
const pending = new Set<string>([sessionID])
|
||||
|
|
|
|||
|
|
@ -9,11 +9,8 @@ import { Decimal } from "decimal.js"
|
|||
import type { ProviderMetadata, Usage } from "@opencode-ai/llm"
|
||||
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
import { makeRuntime } from "@opencode-ai/core/effect/runtime"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { SessionV2 } from "@opencode-ai/core/session"
|
||||
import * as SessionExecutionLocal from "@opencode-ai/core/session/execution/local"
|
||||
import { locationServiceMapLayer } from "@opencode-ai/core/location-services"
|
||||
|
||||
import { NotFoundError } from "@/storage/storage"
|
||||
import { eq } from "drizzle-orm"
|
||||
|
|
@ -46,8 +43,6 @@ import { ProviderV2 } from "@opencode-ai/core/provider"
|
|||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
import { SessionMessage } from "@opencode-ai/schema/session-message"
|
||||
|
||||
const runtime = makeRuntime(Database.Service, Database.defaultLayer)
|
||||
|
||||
const parentTitlePrefix = "New session - "
|
||||
const childTitlePrefix = "Child session - "
|
||||
|
||||
|
|
@ -488,7 +483,7 @@ export type Patch = Omit<Partial<Info>, "time" | "share" | "summary" | "revert"
|
|||
permission?: Info["permission"] | null
|
||||
}
|
||||
|
||||
export const layer: Layer.Layer<
|
||||
const layer: Layer.Layer<
|
||||
Service,
|
||||
never,
|
||||
Job.Service | RuntimeFlags.Service | Database.Service | EventV2Bridge.Service
|
||||
|
|
@ -940,19 +935,6 @@ export const layer: Layer.Layer<
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(
|
||||
Layer.provide(Job.defaultLayer),
|
||||
Layer.provide(Database.defaultLayer),
|
||||
Layer.provide(EventV2Bridge.defaultLayer),
|
||||
Layer.provide(
|
||||
SessionV2.defaultLayer.pipe(
|
||||
Layer.provide(SessionExecutionLocal.defaultLayer),
|
||||
Layer.provide(locationServiceMapLayer),
|
||||
),
|
||||
),
|
||||
Layer.provide(RuntimeFlags.defaultLayer),
|
||||
)
|
||||
|
||||
const cancelJobs = Effect.fn("Session.cancelJobs")(function* (jobs: Job.Interface, sessionID: SessionID) {
|
||||
const running = yield* jobs.list()
|
||||
yield* Effect.forEach(
|
||||
|
|
@ -1022,76 +1004,6 @@ function listByProject(
|
|||
)
|
||||
}
|
||||
|
||||
export function* listGlobal(input?: {
|
||||
directory?: string
|
||||
roots?: boolean
|
||||
start?: number
|
||||
cursor?: number
|
||||
search?: string
|
||||
limit?: number
|
||||
archived?: boolean
|
||||
}) {
|
||||
const conditions: SQL[] = []
|
||||
|
||||
if (input?.directory) {
|
||||
conditions.push(eq(SessionTable.directory, input.directory))
|
||||
}
|
||||
if (input?.roots) {
|
||||
conditions.push(isNull(SessionTable.parent_id))
|
||||
}
|
||||
if (input?.start) {
|
||||
conditions.push(gte(SessionTable.time_updated, input.start))
|
||||
}
|
||||
if (input?.cursor) {
|
||||
conditions.push(lt(SessionTable.time_updated, input.cursor))
|
||||
}
|
||||
if (input?.search) {
|
||||
conditions.push(like(SessionTable.title, `%${input.search}%`))
|
||||
}
|
||||
if (!input?.archived) {
|
||||
conditions.push(isNull(SessionTable.time_archived))
|
||||
}
|
||||
|
||||
const limit = input?.limit ?? 100
|
||||
|
||||
const rows = runtime.runSync(({ db }) => {
|
||||
const query =
|
||||
conditions.length > 0
|
||||
? db
|
||||
.select()
|
||||
.from(SessionTable)
|
||||
.where(and(...conditions))
|
||||
: db.select().from(SessionTable)
|
||||
return query.orderBy(desc(SessionTable.time_updated), desc(SessionTable.id)).limit(limit).all().pipe(Effect.orDie)
|
||||
})
|
||||
|
||||
const ids = [...new Set(rows.map((row) => row.project_id))]
|
||||
const projects = new Map<string, ProjectInfo>()
|
||||
|
||||
if (ids.length > 0) {
|
||||
const items = runtime.runSync(({ db }) =>
|
||||
db
|
||||
.select({ id: ProjectTable.id, name: ProjectTable.name, worktree: ProjectTable.worktree })
|
||||
.from(ProjectTable)
|
||||
.where(inArray(ProjectTable.id, ids))
|
||||
.all()
|
||||
.pipe(Effect.orDie),
|
||||
)
|
||||
for (const item of items) {
|
||||
projects.set(item.id, {
|
||||
id: item.id,
|
||||
name: item.name ?? undefined,
|
||||
worktree: item.worktree,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for (const row of rows) {
|
||||
const project = projects.get(row.project_id) ?? null
|
||||
yield { ...fromRow(row), project }
|
||||
}
|
||||
}
|
||||
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export interface Interface {
|
|||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionStatus") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const events = yield* EventV2Bridge.Service
|
||||
|
|
@ -51,8 +51,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(EventV2Bridge.defaultLayer))
|
||||
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [EventV2Bridge.node] })
|
||||
|
||||
export * as SessionStatus from "./status"
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ export interface Interface {
|
|||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionSummary") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const sessions = yield* Session.Service
|
||||
|
|
@ -145,15 +145,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = Layer.suspend(() =>
|
||||
layer.pipe(
|
||||
Layer.provide(Session.defaultLayer),
|
||||
Layer.provide(Snapshot.defaultLayer),
|
||||
Layer.provide(EventV2Bridge.defaultLayer),
|
||||
Layer.provide(Config.defaultLayer),
|
||||
),
|
||||
)
|
||||
|
||||
export const DiffInput = Schema.Struct({
|
||||
sessionID: SessionID,
|
||||
messageID: Schema.optional(MessageID),
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ export interface Interface {
|
|||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/SystemPrompt") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const skill = yield* Skill.Service
|
||||
|
|
@ -128,12 +128,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(
|
||||
Layer.provide(Skill.defaultLayer),
|
||||
Layer.provide(MCP.defaultLayer),
|
||||
Layer.provide(locationServiceMapLayer),
|
||||
)
|
||||
|
||||
const locationServiceMapNode = LayerNode.make({
|
||||
service: LocationServiceMap.Service,
|
||||
layer: locationServiceMapLayer,
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export interface Interface {
|
|||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionTodo") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const events = yield* EventV2Bridge.Service
|
||||
|
|
@ -69,8 +69,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(EventV2Bridge.defaultLayer), Layer.provide(Database.defaultLayer))
|
||||
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [EventV2Bridge.node, Database.node] })
|
||||
|
||||
export * as Todo from "./todo"
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export interface Interface {
|
|||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/SessionShare") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const cfg = yield* Config.Service
|
||||
|
|
@ -49,13 +49,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(
|
||||
Layer.provide(ShareNext.defaultLayer),
|
||||
Layer.provide(Session.defaultLayer),
|
||||
Layer.provide(Config.defaultLayer),
|
||||
Layer.provide(RuntimeFlags.defaultLayer),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ function key(item: Data) {
|
|||
}
|
||||
}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const account = yield* Account.Service
|
||||
|
|
@ -362,16 +362,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(
|
||||
Layer.provide(EventV2Bridge.defaultLayer),
|
||||
Layer.provide(Account.defaultLayer),
|
||||
Layer.provide(Config.defaultLayer),
|
||||
Layer.provide(Database.defaultLayer),
|
||||
Layer.provide(FetchHttpClient.layer),
|
||||
Layer.provide(Provider.defaultLayer),
|
||||
Layer.provide(Session.defaultLayer),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ export interface Interface {
|
|||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/SkillDiscovery") {}
|
||||
|
||||
export const layer: Layer.Layer<Service, never, FSUtil.Service | Path.Path | HttpClient.HttpClient> = Layer.effect(
|
||||
const layer: Layer.Layer<Service, never, FSUtil.Service | Path.Path | HttpClient.HttpClient> = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* FSUtil.Service
|
||||
|
|
@ -135,12 +135,6 @@ export const layer: Layer.Layer<Service, never, FSUtil.Service | Path.Path | Htt
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer: Layer.Layer<Service> = layer.pipe(
|
||||
Layer.provide(FetchHttpClient.layer),
|
||||
Layer.provide(FSUtil.defaultLayer),
|
||||
Layer.provide(NodePath.layer),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [FSUtil.node, path, httpClient] })
|
||||
|
||||
export * as Discovery from "./discovery"
|
||||
|
|
|
|||
|
|
@ -247,7 +247,7 @@ const loadSkills = Effect.fnUntraced(function* (
|
|||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/Skill") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const discovery = yield* Discovery.Service
|
||||
|
|
@ -318,15 +318,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(
|
||||
Layer.provide(Discovery.defaultLayer),
|
||||
Layer.provide(Config.defaultLayer),
|
||||
Layer.provide(EventV2Bridge.defaultLayer),
|
||||
Layer.provide(FSUtil.defaultLayer),
|
||||
Layer.provide(Global.layer),
|
||||
Layer.provide(RuntimeFlags.defaultLayer),
|
||||
)
|
||||
|
||||
export function fmt(list: Info[], opts: { verbose: boolean }) {
|
||||
const described = list.filter((skill) => skill.description !== undefined)
|
||||
if (described.length === 0) return "No skills are currently available."
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ export interface Interface {
|
|||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/Snapshot") {}
|
||||
|
||||
export const layer: Layer.Layer<Service, never, FSUtil.Service | AppProcess.Service | Config.Service> = Layer.effect(
|
||||
const layer: Layer.Layer<Service, never, FSUtil.Service | AppProcess.Service | Config.Service> = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* FSUtil.Service
|
||||
|
|
@ -798,12 +798,6 @@ export const layer: Layer.Layer<Service, never, FSUtil.Service | AppProcess.Serv
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(
|
||||
Layer.provide(AppProcess.defaultLayer),
|
||||
Layer.provide(FSUtil.defaultLayer),
|
||||
Layer.provide(Config.defaultLayer),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
|
|
|
|||
|
|
@ -210,7 +210,7 @@ const MIGRATIONS: Migration[] = [
|
|||
}),
|
||||
]
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* FSUtil.Service
|
||||
|
|
@ -322,8 +322,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(FSUtil.defaultLayer), Layer.provide(Git.defaultLayer))
|
||||
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [FSUtil.node, Git.node] })
|
||||
|
||||
export * as Storage from "./storage"
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ import { Glob } from "@opencode-ai/core/util/glob"
|
|||
import path from "path"
|
||||
import { pathToFileURL } from "url"
|
||||
import { Effect, Layer, Context } from "effect"
|
||||
import { FetchHttpClient, HttpClient } from "effect/unstable/http"
|
||||
import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner"
|
||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { Format } from "../format"
|
||||
|
|
@ -80,7 +79,7 @@ export interface Interface {
|
|||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/ToolRegistry") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const config = yield* Config.Service
|
||||
|
|
@ -315,30 +314,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = Layer.suspend(() =>
|
||||
layer
|
||||
.pipe(
|
||||
Layer.provide(Config.defaultLayer),
|
||||
Layer.provide(Plugin.defaultLayer),
|
||||
Layer.provide(Question.defaultLayer),
|
||||
Layer.provide(Todo.defaultLayer),
|
||||
Layer.provide(Skill.defaultLayer),
|
||||
Layer.provide(Agent.defaultLayer),
|
||||
Layer.provide(Session.defaultLayer),
|
||||
Layer.provide(Job.defaultLayer),
|
||||
Layer.provide(Provider.defaultLayer),
|
||||
Layer.provide(LSP.defaultLayer),
|
||||
Layer.provide(Instruction.defaultLayer),
|
||||
Layer.provide(FSUtil.defaultLayer),
|
||||
Layer.provide(EventV2Bridge.defaultLayer),
|
||||
Layer.provide(FetchHttpClient.layer),
|
||||
Layer.provide(Format.defaultLayer),
|
||||
Layer.provide(CrossSpawnSpawner.defaultLayer),
|
||||
Layer.provide(Truncate.defaultLayer),
|
||||
)
|
||||
.pipe(Layer.provide(Database.defaultLayer), Layer.provide(RuntimeFlags.defaultLayer)),
|
||||
)
|
||||
|
||||
function isZodType(value: unknown): value is z.ZodType {
|
||||
return typeof value === "object" && value !== null && "_zod" in value
|
||||
}
|
||||
|
|
@ -417,7 +392,7 @@ function isJsonSchemaObject(value: unknown): value is Record<string, unknown> {
|
|||
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer.pipe(Layer.provide(Ripgrep.defaultLayer)),
|
||||
layer,
|
||||
deps: [
|
||||
Config.node,
|
||||
Plugin.node,
|
||||
|
|
@ -438,6 +413,7 @@ export const node = LayerNode.make({
|
|||
Truncate.node,
|
||||
RuntimeFlags.node,
|
||||
Database.node,
|
||||
Ripgrep.node,
|
||||
],
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ export interface Interface {
|
|||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/Truncate") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const fs = yield* FSUtil.Service
|
||||
|
|
@ -151,8 +151,6 @@ export const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(FSUtil.defaultLayer), Layer.provide(NodePath.layer))
|
||||
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [FSUtil.node] })
|
||||
|
||||
export * as Truncate from "./truncate"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { path } from "@opencode-ai/core/effect/app-node-platform"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { InstanceLayer } from "@/project/instance-layer"
|
||||
import { InstanceStore } from "@/project/instance-store"
|
||||
import { Project } from "@/project/project"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
|
|
@ -14,7 +13,6 @@ import { GlobalBus } from "@/bus/global"
|
|||
import { Git } from "@/git"
|
||||
import { Effect, Layer, Path, Schema, Scope, Context } from "effect"
|
||||
import { ChildProcess } from "effect/unstable/process"
|
||||
import { NodePath } from "@effect/platform-node"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { AppProcess } from "@opencode-ai/core/process"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
|
|
@ -131,7 +129,7 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/Wo
|
|||
|
||||
type GitResult = { code: number; text: string; stderr: string }
|
||||
|
||||
export const layer: Layer.Layer<
|
||||
const layer: Layer.Layer<
|
||||
Service,
|
||||
never,
|
||||
| FSUtil.Service
|
||||
|
|
@ -616,17 +614,6 @@ export const layer: Layer.Layer<
|
|||
}),
|
||||
)
|
||||
|
||||
export const appLayer = layer.pipe(
|
||||
Layer.provide(Git.defaultLayer),
|
||||
Layer.provide(AppProcess.defaultLayer),
|
||||
Layer.provide(Project.defaultLayer),
|
||||
Layer.provide(Database.defaultLayer),
|
||||
Layer.provide(FSUtil.defaultLayer),
|
||||
Layer.provide(NodePath.layer),
|
||||
)
|
||||
|
||||
export const defaultLayer = appLayer.pipe(Layer.provide(InstanceLayer.layer))
|
||||
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { expect } from "bun:test"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { Effect, Layer, Option } from "effect"
|
||||
import { sql } from "drizzle-orm"
|
||||
|
||||
|
|
@ -13,9 +14,10 @@ const truncate = Layer.effectDiscard(
|
|||
yield* db.run(sql`DELETE FROM account_state`)
|
||||
yield* db.run(sql`DELETE FROM account`)
|
||||
}),
|
||||
).pipe(Layer.provide(Database.defaultLayer))
|
||||
)
|
||||
const truncateNode = LayerNode.make({ name: "truncate-account", layer: truncate, deps: [Database.node] })
|
||||
|
||||
const it = testEffect(Layer.merge(AccountRepo.defaultLayer, truncate))
|
||||
const it = testEffect(LayerNode.compile(LayerNode.group([AccountRepo.node, truncateNode])))
|
||||
|
||||
it.live("list returns empty when no accounts exist", () =>
|
||||
Effect.gen(function* () {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import { expect } from "bun:test"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { httpClient } from "@opencode-ai/core/effect/app-node-platform"
|
||||
import { Duration, Effect, Layer, Option, Schema } from "effect"
|
||||
import { sql } from "drizzle-orm"
|
||||
import { HttpClient, HttpClientError, HttpClientResponse } from "effect/unstable/http"
|
||||
|
|
@ -25,15 +27,16 @@ const truncate = Layer.effectDiscard(
|
|||
yield* db.run(sql`DELETE FROM account_state`)
|
||||
yield* db.run(sql`DELETE FROM account`)
|
||||
}),
|
||||
).pipe(Layer.provide(Database.defaultLayer))
|
||||
)
|
||||
const truncateNode = LayerNode.make({ name: "truncate-account", layer: truncate, deps: [Database.node] })
|
||||
|
||||
const it = testEffect(Layer.merge(AccountRepo.defaultLayer, truncate))
|
||||
const it = testEffect(LayerNode.compile(LayerNode.group([AccountRepo.node, truncateNode])))
|
||||
|
||||
const insideEagerRefreshWindow = Duration.toMillis(Duration.minutes(1))
|
||||
const outsideEagerRefreshWindow = Duration.toMillis(Duration.minutes(10))
|
||||
|
||||
const live = (client: HttpClient.HttpClient) =>
|
||||
Account.layer.pipe(Layer.provide(Layer.succeed(HttpClient.HttpClient, client)))
|
||||
LayerNode.compile(Account.node, [[httpClient, Layer.succeed(HttpClient.HttpClient, client)]])
|
||||
|
||||
const json = (req: Parameters<typeof HttpClientResponse.fromWeb>[0], body: unknown, status = 200) =>
|
||||
HttpClientResponse.fromWeb(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { describe, expect } from "bun:test"
|
||||
import { Directory } from "@/acp/directory"
|
||||
import { Command } from "@/command"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
import { Provider } from "@/provider/provider"
|
||||
|
|
@ -83,8 +84,9 @@ const snapshot = (directory: string) => {
|
|||
}
|
||||
|
||||
const fakeLayer = (calls: string[]) =>
|
||||
Directory.layer.pipe(
|
||||
Layer.provide(
|
||||
LayerNode.compile(Directory.node, [
|
||||
[
|
||||
Directory.loaderNode,
|
||||
Layer.succeed(
|
||||
Directory.Loader,
|
||||
Directory.Loader.of({
|
||||
|
|
@ -95,8 +97,8 @@ const fakeLayer = (calls: string[]) =>
|
|||
}),
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
])
|
||||
|
||||
describe("ACP directory snapshot", () => {
|
||||
it.effect("two concurrent callers share one load", () => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { describe, expect, it } from "bun:test"
|
||||
import type { AgentSideConnection } from "@agentclientprotocol/sdk"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import type { Event, Message, OpencodeClient, Part, SessionMessageResponse, ToolPart } from "@opencode-ai/sdk/v2"
|
||||
import { Effect, ManagedRuntime } from "effect"
|
||||
import { ACPEvent } from "@/acp/event"
|
||||
|
|
@ -30,7 +31,7 @@ const pollUntil = async (
|
|||
}
|
||||
|
||||
function makeSessionService() {
|
||||
return ManagedRuntime.make(ACPSession.defaultLayer).runSync(
|
||||
return ManagedRuntime.make(LayerNode.compile(ACPSession.node)).runSync(
|
||||
ACPSession.Service.use((service) => Effect.succeed(service)),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import type {
|
|||
SessionUpdate,
|
||||
} from "@agentclientprotocol/sdk"
|
||||
import type { Event, OpencodeClient } from "@opencode-ai/sdk/v2"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { createTwoFilesPatch } from "diff"
|
||||
import { Effect, ManagedRuntime } from "effect"
|
||||
import { mkdtemp, rm } from "node:fs/promises"
|
||||
|
|
@ -37,7 +38,7 @@ const pollUntil = async (
|
|||
}
|
||||
|
||||
function makeSessionService() {
|
||||
return ManagedRuntime.make(ACPSession.defaultLayer).runSync(
|
||||
return ManagedRuntime.make(LayerNode.compile(ACPSession.node)).runSync(
|
||||
ACPSession.Service.use((service) => Effect.succeed(service)),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { describe, expect } from "bun:test"
|
||||
import type { McpServer } from "@agentclientprotocol/sdk"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { Effect } from "effect"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
|
|
@ -7,7 +8,7 @@ import * as ACPError from "@/acp/error"
|
|||
import * as ACPSession from "@/acp/session"
|
||||
import { testEffect } from "../lib/effect"
|
||||
|
||||
const sessionTest = testEffect(ACPSession.defaultLayer)
|
||||
const sessionTest = testEffect(LayerNode.compile(ACPSession.node))
|
||||
|
||||
const model = (providerID: string, modelID: string): ACPSession.SelectedModel => ({
|
||||
providerID: ProviderV2.ID.make(providerID),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { describe, expect, test } from "bun:test"
|
||||
import type { SessionNotification } from "@agentclientprotocol/sdk"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
import { UsageService } from "@/acp/usage"
|
||||
|
|
@ -97,24 +98,26 @@ const fakeLayer = (input: {
|
|||
readonly messages?: Effect.Effect<readonly UsageService.SessionMessage[], unknown>
|
||||
readonly providers?: (directory: string) => Effect.Effect<Record<ProviderV2.ID, Provider.Info>, unknown>
|
||||
}) =>
|
||||
UsageService.layer.pipe(
|
||||
Layer.provide(
|
||||
Layer.mergeAll(
|
||||
Layer.succeed(
|
||||
UsageService.MessageLoader,
|
||||
UsageService.MessageLoader.of({
|
||||
messages: () => input.messages ?? Effect.succeed([]),
|
||||
}),
|
||||
),
|
||||
Layer.succeed(
|
||||
UsageService.ContextLimitLoader,
|
||||
UsageService.ContextLimitLoader.of({
|
||||
providers: input.providers ?? (() => Effect.succeed(providers())),
|
||||
}),
|
||||
),
|
||||
LayerNode.compile(UsageService.node, [
|
||||
[
|
||||
UsageService.messageLoaderNode,
|
||||
Layer.succeed(
|
||||
UsageService.MessageLoader,
|
||||
UsageService.MessageLoader.of({
|
||||
messages: () => input.messages ?? Effect.succeed([]),
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
[
|
||||
UsageService.contextLimitLoaderNode,
|
||||
Layer.succeed(
|
||||
UsageService.ContextLimitLoader,
|
||||
UsageService.ContextLimitLoader.of({
|
||||
providers: input.providers ?? (() => Effect.succeed(providers())),
|
||||
}),
|
||||
),
|
||||
],
|
||||
])
|
||||
|
||||
const connection = (updates: SessionNotification[]) => ({
|
||||
sessionUpdate(params: SessionNotification) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { afterEach, expect } from "bun:test"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { Cause, Effect, Exit, Layer } from "effect"
|
||||
import path from "path"
|
||||
import { disposeAllInstances, TestInstance } from "../fixture/fixture"
|
||||
|
|
@ -14,17 +15,11 @@ import { Plugin } from "../../src/plugin"
|
|||
import { Provider } from "../../src/provider/provider"
|
||||
import { Skill } from "../../src/skill"
|
||||
import { Truncate } from "../../src/tool/truncate"
|
||||
import { LocationServiceMap, locationServiceMapLayer } from "@opencode-ai/core/location-services"
|
||||
|
||||
const agentLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
|
||||
Agent.layer.pipe(
|
||||
Layer.provide(Plugin.defaultLayer),
|
||||
Layer.provide(Provider.defaultLayer),
|
||||
Layer.provide(Auth.defaultLayer),
|
||||
Layer.provide(Config.defaultLayer),
|
||||
Layer.provide(Skill.defaultLayer),
|
||||
Layer.provide(locationServiceMapLayer),
|
||||
Layer.provide(RuntimeFlags.layer(flags)),
|
||||
LayerNode.compile(
|
||||
LayerNode.group([Agent.node, Plugin.node, Provider.node, Auth.node, Config.node, Skill.node, RuntimeFlags.node]),
|
||||
[[RuntimeFlags.node, RuntimeFlags.layer(flags)]],
|
||||
)
|
||||
|
||||
const it = testEffect(agentLayer())
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { PermissionV1 } from "@opencode-ai/core/v1/permission"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { expect } from "bun:test"
|
||||
import { Effect } from "effect"
|
||||
import { Agent } from "../../src/agent/agent"
|
||||
|
|
@ -6,7 +7,7 @@ import { deriveSubagentSessionPermission } from "../../src/agent/subagent-permis
|
|||
import { Permission } from "../../src/permission"
|
||||
import { testEffect } from "../lib/effect"
|
||||
|
||||
const it = testEffect(Agent.defaultLayer)
|
||||
const it = testEffect(LayerNode.compile(Agent.node))
|
||||
|
||||
function testAgent(input: {
|
||||
name: string
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
import { expect } from "bun:test"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { LocationServiceMap, locationServiceMapLayer } from "@opencode-ai/core/location-services"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { FetchHttpClient } from "effect/unstable/http"
|
||||
import { Npm } from "@opencode-ai/core/npm"
|
||||
import { Effect } from "effect"
|
||||
import path from "path"
|
||||
import { pathToFileURL } from "url"
|
||||
import { Agent } from "../../src/agent/agent"
|
||||
import { EventV2Bridge } from "../../src/event-v2-bridge"
|
||||
import { Config } from "../../src/config/config"
|
||||
import { Env } from "../../src/env"
|
||||
import { Account } from "../../src/account/account"
|
||||
import { Auth } from "../../src/auth"
|
||||
import { RuntimeFlags } from "../../src/effect/runtime-flags"
|
||||
import { Plugin } from "../../src/plugin"
|
||||
import { Provider } from "../../src/provider/provider"
|
||||
import { Skill } from "../../src/skill"
|
||||
import { AccountTest } from "../fake/account"
|
||||
import { AuthTest } from "../fake/auth"
|
||||
import { NpmTest } from "../fake/npm"
|
||||
|
|
@ -18,6 +17,8 @@ import { ProviderTest } from "../fake/provider"
|
|||
import { SkillTest } from "../fake/skill"
|
||||
import { testEffect } from "../lib/effect"
|
||||
import { PLUGIN_AGENT } from "../fixture/agent-plugin.constants"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
|
||||
// `it.instance` skips InstanceBootstrap so LSP / MCP don't spin up — those
|
||||
// services hang during scope teardown on Windows and aren't needed
|
||||
|
|
@ -25,30 +26,16 @@ import { PLUGIN_AGENT } from "../fixture/agent-plugin.constants"
|
|||
const pluginUrl = pathToFileURL(path.join(import.meta.dir, "..", "fixture", "agent-plugin.ts")).href
|
||||
|
||||
const provider = ProviderTest.fake()
|
||||
const configLayer = Config.layer.pipe(
|
||||
Layer.provide(FSUtil.defaultLayer),
|
||||
Layer.provide(Env.defaultLayer),
|
||||
Layer.provide(AuthTest.empty),
|
||||
Layer.provide(AccountTest.empty),
|
||||
Layer.provide(NpmTest.noop),
|
||||
Layer.provide(FetchHttpClient.layer),
|
||||
const it = testEffect(
|
||||
AppNodeBuilder.build(LayerNode.group([Agent.node, Plugin.node]), [
|
||||
[Auth.node, AuthTest.empty],
|
||||
[Account.node, AccountTest.empty],
|
||||
[Npm.node, NpmTest.noop],
|
||||
[Provider.node, provider.layer],
|
||||
[Skill.node, SkillTest.empty],
|
||||
[RuntimeFlags.node, RuntimeFlags.layer({ disableDefaultPlugins: true })],
|
||||
]),
|
||||
)
|
||||
const pluginLayer = Plugin.layer.pipe(
|
||||
Layer.provide(EventV2Bridge.defaultLayer),
|
||||
Layer.provide(configLayer),
|
||||
Layer.provide(RuntimeFlags.layer({ disableDefaultPlugins: true })),
|
||||
)
|
||||
const agentLayer = Agent.layer.pipe(
|
||||
Layer.provide(configLayer),
|
||||
Layer.provide(AuthTest.empty),
|
||||
Layer.provide(SkillTest.empty),
|
||||
Layer.provide(provider.layer),
|
||||
Layer.provide(pluginLayer),
|
||||
Layer.provide(locationServiceMapLayer),
|
||||
Layer.provide(RuntimeFlags.layer({ disableDefaultPlugins: true })),
|
||||
)
|
||||
|
||||
const it = testEffect(Layer.mergeAll(agentLayer, pluginLayer))
|
||||
|
||||
it.instance(
|
||||
"plugin-registered agents appear in Agent.list",
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
import { describe, expect } from "bun:test"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { Effect } from "effect"
|
||||
import { Auth } from "../../src/auth"
|
||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { testEffect } from "../lib/effect"
|
||||
|
||||
const node = CrossSpawnSpawner.defaultLayer
|
||||
|
||||
const it = testEffect(Layer.mergeAll(Auth.defaultLayer, node))
|
||||
const it = testEffect(LayerNode.compile(Auth.node))
|
||||
|
||||
describe("Auth", () => {
|
||||
it.instance("set normalizes trailing slashes in keys", () =>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { afterEach, expect } from "bun:test"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { Effect } from "effect"
|
||||
import { fileURLToPath } from "url"
|
||||
|
|
@ -6,7 +7,7 @@ import { InstanceRef } from "../../src/effect/instance-ref"
|
|||
import { disposeAllInstances, TestInstance } from "../fixture/fixture"
|
||||
import { testEffect } from "../lib/effect"
|
||||
|
||||
const it = testEffect(FSUtil.defaultLayer)
|
||||
const it = testEffect(LayerNode.compile(FSUtil.node))
|
||||
|
||||
afterEach(async () => {
|
||||
await disposeAllInstances()
|
||||
|
|
|
|||
|
|
@ -76,39 +76,35 @@ Positionals:
|
|||
message message to send [array] [default: []]
|
||||
|
||||
Options:
|
||||
-h, --help show help [boolean]
|
||||
-v, --version show version number [boolean]
|
||||
--print-logs print logs to stderr [boolean]
|
||||
--log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"]
|
||||
--pure run without external plugins [boolean]
|
||||
--command the command to run, use message for args [string]
|
||||
-c, --continue continue the last session [boolean]
|
||||
-s, --session session id to continue [string]
|
||||
--fork fork the session before continuing (requires --continue or
|
||||
--session) [boolean]
|
||||
--share share the session [boolean]
|
||||
-m, --model model to use in the format of provider/model [string]
|
||||
--agent agent to use [string]
|
||||
--format format: default (formatted) or json (raw JSON events)
|
||||
-h, --help show help [boolean]
|
||||
-v, --version show version number [boolean]
|
||||
--print-logs print logs to stderr [boolean]
|
||||
--log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"]
|
||||
--pure run without external plugins [boolean]
|
||||
--command the command to run, use message for args [string]
|
||||
-c, --continue continue the last session [boolean]
|
||||
-s, --session session id to continue [string]
|
||||
--fork fork the session before continuing (requires --continue or --session) [boolean]
|
||||
--share share the session [boolean]
|
||||
-m, --model model to use in the format of provider/model [string]
|
||||
--agent agent to use [string]
|
||||
--format format: default (formatted) or json (raw JSON events)
|
||||
[string] [choices: "default", "json"] [default: "default"]
|
||||
-f, --file file(s) to attach to message [array]
|
||||
--title title for the session (uses truncated prompt if no value
|
||||
provided) [string]
|
||||
--attach attach to a running opencode server (e.g.,
|
||||
http://localhost:4096) [string]
|
||||
-p, --password basic auth password (defaults to OPENCODE_SERVER_PASSWORD)
|
||||
-f, --file file(s) to attach to message [array]
|
||||
--title title for the session (uses truncated prompt if no value provided) [string]
|
||||
--attach attach to a running opencode server (e.g., http://localhost:4096) [string]
|
||||
-p, --password basic auth password (defaults to OPENCODE_SERVER_PASSWORD) [string]
|
||||
-u, --username basic auth username (defaults to OPENCODE_SERVER_USERNAME or 'opencode')
|
||||
[string]
|
||||
-u, --username basic auth username (defaults to OPENCODE_SERVER_USERNAME or
|
||||
'opencode') [string]
|
||||
--dir directory to run in, path on remote server if attaching
|
||||
--dir directory to run in, path on remote server if attaching [string]
|
||||
--port port for the local server (defaults to random port if no value provided)
|
||||
[number]
|
||||
--variant model variant (provider-specific reasoning effort, e.g., high, max, minimal)
|
||||
[string]
|
||||
--port port for the local server (defaults to random port if no value
|
||||
provided) [number]
|
||||
--variant model variant (provider-specific reasoning effort, e.g., high,
|
||||
max, minimal) [string]
|
||||
--thinking show thinking blocks [boolean]
|
||||
--dangerously-skip-permissions auto-approve permissions that are not explicitly denied
|
||||
(dangerous!) [boolean] [default: false]"
|
||||
--thinking show thinking blocks [boolean]
|
||||
-i, --interactive run in direct interactive split-footer mode [boolean] [default: false]
|
||||
--auto auto-approve permissions that are not explicitly denied (dangerous!)
|
||||
[boolean] [default: false]"
|
||||
`;
|
||||
|
||||
exports[`opencode CLI help-text snapshots every documented command emits stable help text: opencode debug --help 1`] = `
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import path from "path"
|
||||
import { NodeFileSystem } from "@effect/platform-node"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { Effect, FileSystem, Layer } from "effect"
|
||||
|
|
@ -98,7 +99,7 @@ function userMessage(
|
|||
}
|
||||
}
|
||||
|
||||
const it = testEffect(Layer.mergeAll(FSUtil.defaultLayer, NodeFileSystem.layer))
|
||||
const it = testEffect(Layer.mergeAll(LayerNode.compile(FSUtil.node), NodeFileSystem.layer))
|
||||
|
||||
function remap(root: string, file: string) {
|
||||
if (file === Global.Path.state) {
|
||||
|
|
@ -123,7 +124,7 @@ function remappedFs(root: string) {
|
|||
writeJson: (file, data, mode) => fs.writeJson(remap(root, file), data, mode),
|
||||
})
|
||||
}),
|
||||
).pipe(Layer.provide(FSUtil.defaultLayer))
|
||||
).pipe(Layer.provide(LayerNode.compile(FSUtil.node)))
|
||||
}
|
||||
|
||||
describe("run variant shared", () => {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { expect } from "bun:test"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { Effect } from "effect"
|
||||
import { Config } from "@/config/config"
|
||||
import { Agent as AgentSvc } from "../../src/agent/agent"
|
||||
import { testEffect } from "../lib/effect"
|
||||
|
||||
const it = testEffect(Layer.mergeAll(Config.defaultLayer, AgentSvc.defaultLayer, CrossSpawnSpawner.defaultLayer))
|
||||
const it = testEffect(LayerNode.compile(LayerNode.group([Config.node, AgentSvc.node])))
|
||||
|
||||
it.instance(
|
||||
"agent color parsed from project config",
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
import { test, expect, describe, afterEach, beforeEach, spyOn } from "bun:test"
|
||||
import { ConfigV1 } from "@opencode-ai/core/v1/config/config"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { httpClient } from "@opencode-ai/core/effect/app-node-platform"
|
||||
import { Cause, Effect, Exit, Layer, Option } from "effect"
|
||||
import { NamedError } from "@opencode-ai/core/util/error"
|
||||
import { FetchHttpClient, HttpClient, HttpClientResponse } from "effect/unstable/http"
|
||||
import { NodeFileSystem, NodePath } from "@effect/platform-node"
|
||||
import { Config } from "@/config/config"
|
||||
import { ConfigManaged } from "@/config/managed"
|
||||
import { ConfigParse } from "../../src/config/parse"
|
||||
import { EffectFlock } from "@opencode-ai/core/util/effect-flock"
|
||||
import { Npm } from "@opencode-ai/core/npm"
|
||||
|
||||
import { InstanceRef } from "../../src/effect/instance-ref"
|
||||
import type { InstanceContext } from "../../src/project/instance-context"
|
||||
|
|
@ -41,13 +42,6 @@ import { AccountTest } from "../fake/account"
|
|||
import { AuthTest } from "../fake/auth"
|
||||
import { NpmTest } from "../fake/npm"
|
||||
|
||||
/** Infra layer that provides FileSystem, Path, ChildProcessSpawner for test fixtures */
|
||||
const infra = CrossSpawnSpawner.defaultLayer.pipe(
|
||||
Layer.provideMerge(Layer.mergeAll(NodeFileSystem.layer, NodePath.layer)),
|
||||
)
|
||||
|
||||
const testFlock = EffectFlock.defaultLayer
|
||||
|
||||
const unexpectedHttp = HttpClient.make((request) =>
|
||||
Effect.die(`unexpected http request: ${request.method} ${request.url}`),
|
||||
)
|
||||
|
|
@ -104,16 +98,12 @@ const configLayer = (
|
|||
client?: HttpClient.HttpClient
|
||||
} = {},
|
||||
) =>
|
||||
Config.layer.pipe(
|
||||
Layer.provide(testFlock),
|
||||
Layer.provide(Env.defaultLayer),
|
||||
Layer.provide(options.auth ?? AuthTest.empty),
|
||||
Layer.provide(options.account ?? AccountTest.empty),
|
||||
Layer.provideMerge(infra),
|
||||
Layer.provide(NpmTest.noop),
|
||||
Layer.provide(Layer.succeed(HttpClient.HttpClient, options.client ?? unexpectedHttp)),
|
||||
Layer.provideMerge(FSUtil.defaultLayer),
|
||||
)
|
||||
LayerNode.compile(LayerNode.group([Config.node, FSUtil.node, Env.node, CrossSpawnSpawner.node]), [
|
||||
[Auth.node, options.auth ?? AuthTest.empty],
|
||||
[Account.node, options.account ?? AccountTest.empty],
|
||||
[Npm.node, NpmTest.noop],
|
||||
[httpClient, Layer.succeed(HttpClient.HttpClient, options.client ?? unexpectedHttp)],
|
||||
])
|
||||
|
||||
const layer = configLayer()
|
||||
|
||||
|
|
@ -171,7 +161,7 @@ const withInstanceDir = <A, E, R>(dir: string, effect: Effect.Effect<A, E, R>) =
|
|||
Effect.provideService(TestInstance, { directory: dir }),
|
||||
provideInstanceEffect(dir),
|
||||
Effect.provide(testInstanceStoreLayer),
|
||||
Effect.provide(CrossSpawnSpawner.defaultLayer),
|
||||
Effect.provide(LayerNode.compile(CrossSpawnSpawner.node)),
|
||||
)
|
||||
|
||||
const withGlobalConfigDir = <A, E, R>(dir: string, effect: Effect.Effect<A, E, R>) =>
|
||||
|
|
@ -325,7 +315,7 @@ it.effect("creates global jsonc config with schema when no global configs exist"
|
|||
|
||||
const content = yield* FSUtil.use.readFileString(path.join(dir, "opencode.jsonc"))
|
||||
expect(content).toContain('"$schema": "https://opencode.ai/config.json"')
|
||||
}).pipe(Effect.provide(testInstanceStoreLayer), Effect.provide(CrossSpawnSpawner.defaultLayer)),
|
||||
}).pipe(Effect.provide(testInstanceStoreLayer), Effect.provide(LayerNode.compile(CrossSpawnSpawner.node))),
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -340,7 +330,7 @@ it.effect("does not create global config when OPENCODE_CONFIG_DIR is set", () =>
|
|||
yield* Config.use.get().pipe(provideInstanceEffect(dir))
|
||||
|
||||
expect(yield* FSUtil.use.existsSafe(path.join(dir, "opencode.jsonc"))).toBe(false)
|
||||
}).pipe(Effect.provide(testInstanceStoreLayer), Effect.provide(CrossSpawnSpawner.defaultLayer)),
|
||||
}).pipe(Effect.provide(testInstanceStoreLayer), Effect.provide(LayerNode.compile(CrossSpawnSpawner.node))),
|
||||
),
|
||||
)
|
||||
}),
|
||||
|
|
@ -930,7 +920,7 @@ it.effect("does not try to install dependencies in read-only OPENCODE_CONFIG_DIR
|
|||
yield* Effect.addFinalizer(() => FSUtil.use.chmod(readonly, 0o755).pipe(Effect.ignore))
|
||||
|
||||
yield* withProcessEnv("OPENCODE_CONFIG_DIR", readonly, Config.use.get().pipe(provideInstanceEffect(dir)))
|
||||
}).pipe(Effect.provide(testInstanceStoreLayer), Effect.provide(CrossSpawnSpawner.defaultLayer)),
|
||||
}).pipe(Effect.provide(testInstanceStoreLayer), Effect.provide(LayerNode.compile(CrossSpawnSpawner.node))),
|
||||
)
|
||||
|
||||
it.effect("installs dependencies in writable OPENCODE_CONFIG_DIR", () =>
|
||||
|
|
@ -948,7 +938,7 @@ it.effect("installs dependencies in writable OPENCODE_CONFIG_DIR", () =>
|
|||
)
|
||||
|
||||
expect(yield* FSUtil.use.readFileString(path.join(configDir, ".gitignore"))).toContain("package-lock.json")
|
||||
}).pipe(Effect.provide(testInstanceStoreLayer), Effect.provide(CrossSpawnSpawner.defaultLayer)),
|
||||
}).pipe(Effect.provide(testInstanceStoreLayer), Effect.provide(LayerNode.compile(CrossSpawnSpawner.node))),
|
||||
)
|
||||
|
||||
// Note: deduplication and serialization of npm installs is now handled by the
|
||||
|
|
@ -1533,16 +1523,12 @@ test("remote well-known config can use FetchHttpClient layer", async () => {
|
|||
Effect.scoped,
|
||||
Effect.provide(
|
||||
Layer.mergeAll(
|
||||
Config.layer.pipe(
|
||||
Layer.provide(testFlock),
|
||||
Layer.provide(FSUtil.defaultLayer),
|
||||
Layer.provide(Env.defaultLayer),
|
||||
Layer.provide(wellKnownAuth(server.url.origin)),
|
||||
Layer.provide(AccountTest.empty),
|
||||
Layer.provideMerge(infra),
|
||||
Layer.provide(NpmTest.noop),
|
||||
Layer.provide(FetchHttpClient.layer),
|
||||
),
|
||||
LayerNode.compile(LayerNode.group([Config.node, FSUtil.node, Env.node, CrossSpawnSpawner.node]), [
|
||||
[Auth.node, wellKnownAuth(server.url.origin)],
|
||||
[Account.node, AccountTest.empty],
|
||||
[Npm.node, NpmTest.noop],
|
||||
[httpClient, FetchHttpClient.layer],
|
||||
]),
|
||||
testInstanceStoreLayer,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import { expect } from "bun:test"
|
||||
import path from "path"
|
||||
import { pathToFileURL } from "url"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
|
|
@ -11,7 +13,7 @@ import { TuiConfig } from "../../src/config/tui"
|
|||
import { TestInstance } from "../fixture/fixture"
|
||||
import { testEffect } from "../lib/effect"
|
||||
|
||||
const it = testEffect(Layer.mergeAll(Config.defaultLayer, FSUtil.defaultLayer))
|
||||
const it = testEffect(LayerNode.compile(LayerNode.group([Config.node, FSUtil.node])))
|
||||
const winIt = process.platform === "win32" ? it.instance : it.instance.skip
|
||||
|
||||
const globalConfigFiles = ["opencode.json", "opencode.jsonc", "tui.json", "tui.jsonc"].map((file) =>
|
||||
|
|
@ -69,12 +71,16 @@ const withPlatform = <A, E, R>(platform: typeof process.platform, self: Effect.E
|
|||
|
||||
const getTuiConfig = (directory: string) =>
|
||||
TuiConfig.Service.use((svc) => svc.get()).pipe(
|
||||
Effect.provide(TuiConfig.defaultLayer.pipe(Layer.provide(Layer.succeed(CurrentWorkingDirectory, directory)))),
|
||||
Effect.provide(
|
||||
AppNodeBuilder.build(TuiConfig.node).pipe(Layer.provide(Layer.succeed(CurrentWorkingDirectory, directory))),
|
||||
),
|
||||
)
|
||||
|
||||
const getTuiPluginOrigins = (directory: string) =>
|
||||
TuiConfig.Service.use((svc) => svc.pluginOrigins()).pipe(
|
||||
Effect.provide(TuiConfig.defaultLayer.pipe(Layer.provide(Layer.succeed(CurrentWorkingDirectory, directory)))),
|
||||
Effect.provide(
|
||||
AppNodeBuilder.build(TuiConfig.node).pipe(Layer.provide(Layer.succeed(CurrentWorkingDirectory, directory))),
|
||||
),
|
||||
)
|
||||
|
||||
it.instance("keeps server and tui plugin merge semantics aligned", () =>
|
||||
|
|
|
|||
|
|
@ -5,9 +5,8 @@ import Http from "node:http"
|
|||
import path from "node:path"
|
||||
import { NodeHttpServer } from "@effect/platform-node"
|
||||
import { Effect, Exit, Fiber, Layer, Schema } from "effect"
|
||||
import { FetchHttpClient, HttpServer, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
|
||||
import { HttpServer, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
|
||||
import { eq } from "drizzle-orm"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { GlobalBus, type GlobalEvent } from "@/bus/global"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
import { ProjectV2 } from "@opencode-ai/core/project"
|
||||
|
|
@ -16,6 +15,7 @@ import { AbsolutePath } from "@opencode-ai/core/schema"
|
|||
import { Session as SessionNs } from "@/session/session"
|
||||
import { SessionID } from "@/session/schema"
|
||||
import { SessionTable } from "@opencode-ai/core/session/sql"
|
||||
import { SessionProjector } from "@opencode-ai/core/session/projector"
|
||||
import { EventSequenceTable } from "@opencode-ai/core/event/sql"
|
||||
import { resetDatabase } from "../fixture/db"
|
||||
import { disposeAllInstances, provideTmpdirInstance, requireInstance, TestInstance } from "../fixture/fixture"
|
||||
|
|
@ -27,13 +27,10 @@ import type { Target, WorkspaceAdapter, WorkspaceInfo } from "../../src/control-
|
|||
import * as Workspace from "../../src/control-plane/workspace"
|
||||
import { InstanceStore } from "@/project/instance-store"
|
||||
import { InstanceBootstrap } from "@/project/bootstrap"
|
||||
import { Auth } from "@/auth"
|
||||
import { SessionPrompt } from "@/session/prompt"
|
||||
import { Project } from "@/project/project"
|
||||
import { Vcs } from "@/project/vcs"
|
||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
import { EventV2Bridge } from "@/event-v2-bridge"
|
||||
import { Ripgrep } from "@opencode-ai/core/ripgrep"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
|
||||
const originalEnv = {
|
||||
OPENCODE_AUTH_CONTENT: process.env.OPENCODE_AUTH_CONTENT,
|
||||
|
|
@ -44,26 +41,27 @@ const originalEnv = {
|
|||
}
|
||||
|
||||
const workspaceLayer = (experimentalWorkspaces: boolean) =>
|
||||
Workspace.layer.pipe(
|
||||
Layer.provide(Auth.defaultLayer),
|
||||
Layer.provide(SessionNs.defaultLayer),
|
||||
Layer.provide(SessionPrompt.defaultLayer),
|
||||
Layer.provide(Project.defaultLayer),
|
||||
Layer.provide(Vcs.defaultLayer),
|
||||
Layer.provide(Database.defaultLayer),
|
||||
Layer.provide(EventV2Bridge.defaultLayer),
|
||||
Layer.provide(FetchHttpClient.layer),
|
||||
Layer.provide(FSUtil.defaultLayer),
|
||||
Layer.provide(RuntimeFlags.layer({ experimentalWorkspaces })),
|
||||
Layer.provide(Ripgrep.defaultLayer),
|
||||
Layer.provide(InstanceStore.defaultLayer.pipe(Layer.provide(InstanceBootstrap.defaultLayer))),
|
||||
AppNodeBuilder.build(
|
||||
LayerNode.group([
|
||||
Workspace.node,
|
||||
SessionNs.node,
|
||||
SessionProjector.node,
|
||||
Database.node,
|
||||
InstanceStore.node,
|
||||
Ripgrep.node,
|
||||
]),
|
||||
[
|
||||
[RuntimeFlags.node, RuntimeFlags.layer({ experimentalWorkspaces })],
|
||||
[
|
||||
InstanceStore.bootstrapNode,
|
||||
Layer.succeed(InstanceBootstrap.Service, InstanceBootstrap.Service.of({ run: Effect.void })),
|
||||
],
|
||||
],
|
||||
)
|
||||
|
||||
const testServerLayer = Layer.mergeAll(
|
||||
NodeHttpServer.layer(Http.createServer, { host: "127.0.0.1", port: 0 }),
|
||||
workspaceLayer(true),
|
||||
SessionNs.defaultLayer,
|
||||
Database.defaultLayer,
|
||||
)
|
||||
const it = testEffect(testServerLayer)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { expect } from "bun:test"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { Context, Deferred, Effect, Fiber, Layer, Logger } from "effect"
|
||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { AppLayer } from "../../src/effect/app-runtime"
|
||||
|
|
@ -9,7 +10,8 @@ import { attach } from "../../src/effect/run-service"
|
|||
import { TestInstance } from "../fixture/fixture"
|
||||
import { testEffect } from "../lib/effect"
|
||||
|
||||
const it = testEffect(CrossSpawnSpawner.defaultLayer)
|
||||
const observabilityLayer = LayerNode.compile(Observability.node)
|
||||
const it = testEffect(LayerNode.compile(CrossSpawnSpawner.node))
|
||||
|
||||
function check(loggers: ReadonlySet<Logger.Logger<unknown, any>>) {
|
||||
return {
|
||||
|
|
@ -34,7 +36,7 @@ it.live("makeRuntime installs the observability logger", () =>
|
|||
)
|
||||
|
||||
const current = yield* Dummy.use((svc) => svc.current()).pipe(
|
||||
Effect.provide(Layer.provideMerge(layer, Observability.layer)),
|
||||
Effect.provide(Layer.provideMerge(layer, observabilityLayer)),
|
||||
)
|
||||
|
||||
expect(current.size).toBeGreaterThan(0)
|
||||
|
|
@ -94,6 +96,6 @@ it.instance(
|
|||
|
||||
expect(result.directory).toBe(test.directory)
|
||||
expect(result.size).toBeGreaterThan(0)
|
||||
}).pipe(Effect.provide(Observability.layer)),
|
||||
}).pipe(Effect.provide(observabilityLayer)),
|
||||
{ git: true },
|
||||
)
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@ class TestConfig extends ConfigService.Service<TestConfig>()("@test/ConfigServic
|
|||
}) {}
|
||||
|
||||
const fromConfig = (input: Record<string, unknown>) =>
|
||||
TestConfig.defaultLayer.pipe(Layer.provide(ConfigProvider.layer(ConfigProvider.fromUnknown(input))))
|
||||
TestConfig.layer.pipe(Layer.provide(ConfigProvider.layer(ConfigProvider.fromUnknown(input))))
|
||||
|
||||
const readConfig = TestConfig.useSync((config) => config)
|
||||
|
||||
describe("ConfigService", () => {
|
||||
it.effect("defaultLayer parses values from the active ConfigProvider", () =>
|
||||
it.effect("layer parses values from the active ConfigProvider", () =>
|
||||
Effect.gen(function* () {
|
||||
const config = yield* readConfig.pipe(
|
||||
Effect.provide(
|
||||
|
|
@ -33,7 +33,7 @@ describe("ConfigService", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
it.effect("defaultLayer applies Effect Config defaults", () =>
|
||||
it.effect("layer applies Effect Config defaults", () =>
|
||||
Effect.gen(function* () {
|
||||
const config = yield* readConfig.pipe(Effect.provide(fromConfig({ NAME: "kit" })))
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ describe("ConfigService", () => {
|
|||
Effect.gen(function* () {
|
||||
const config = yield* readConfig.pipe(
|
||||
Effect.provide(
|
||||
TestConfig.layer({
|
||||
TestConfig.configLayer({
|
||||
name: "direct",
|
||||
token: Option.some("parsed"),
|
||||
port: 9000,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { expect } from "bun:test"
|
||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { $ } from "bun"
|
||||
import { Context, Deferred, Duration, Effect, Exit, Fiber, Layer } from "effect"
|
||||
import { InstanceState } from "@/effect/instance-state"
|
||||
|
|
@ -12,7 +13,7 @@ import {
|
|||
} from "../fixture/fixture"
|
||||
import { testEffect } from "../lib/effect"
|
||||
|
||||
const it = testEffect(Layer.mergeAll(CrossSpawnSpawner.defaultLayer, testInstanceStoreLayer))
|
||||
const it = testEffect(Layer.mergeAll(LayerNode.compile(CrossSpawnSpawner.node), testInstanceStoreLayer))
|
||||
|
||||
const access = <A, E>(state: InstanceState.InstanceState<A, E>, dir: string) =>
|
||||
InstanceState.get(state).pipe(provideInstanceEffect(dir))
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
import { describe, expect } from "bun:test"
|
||||
import { ConfigProvider, Effect, Layer } from "effect"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { RuntimeFlags } from "../../src/effect/runtime-flags"
|
||||
import { it } from "../lib/effect"
|
||||
|
||||
const fromConfig = (input: Record<string, unknown>) =>
|
||||
RuntimeFlags.defaultLayer.pipe(Layer.provide(ConfigProvider.layer(ConfigProvider.fromUnknown(input))))
|
||||
AppNodeBuilder.build(RuntimeFlags.node).pipe(Layer.provide(ConfigProvider.layer(ConfigProvider.fromUnknown(input))))
|
||||
|
||||
const readFlags = RuntimeFlags.Service.useSync((flags) => flags)
|
||||
|
||||
describe("RuntimeFlags", () => {
|
||||
it.effect("defaultLayer defaults autoShare to false", () =>
|
||||
it.effect("layer defaults autoShare to false", () =>
|
||||
Effect.gen(function* () {
|
||||
const flags = yield* readFlags.pipe(Effect.provide(fromConfig({})))
|
||||
|
||||
|
|
@ -17,7 +18,7 @@ describe("RuntimeFlags", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
it.effect("defaultLayer parses plugin flags from the active ConfigProvider", () =>
|
||||
it.effect("layer parses plugin flags from the active ConfigProvider", () =>
|
||||
Effect.gen(function* () {
|
||||
const flags = yield* readFlags.pipe(
|
||||
Effect.provide(
|
||||
|
|
@ -64,7 +65,7 @@ describe("RuntimeFlags", () => {
|
|||
}),
|
||||
)
|
||||
|
||||
it.effect("defaultLayer parses OPENCODE_EXPERIMENTAL_LSP_TY", () =>
|
||||
it.effect("layer parses OPENCODE_EXPERIMENTAL_LSP_TY", () =>
|
||||
Effect.gen(function* () {
|
||||
const flags = yield* readFlags.pipe(
|
||||
Effect.provide(
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
import { describe, test, expect } from "bun:test"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { NodeFileSystem } from "@effect/platform-node"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { Effect } from "effect"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { testEffect } from "../lib/effect"
|
||||
import path from "path"
|
||||
|
||||
const live = FSUtil.layer.pipe(Layer.provide(NodeFileSystem.layer))
|
||||
const { effect: it } = testEffect(live)
|
||||
const { effect: it } = testEffect(LayerNode.compile(FSUtil.node))
|
||||
|
||||
describe("FSUtil", () => {
|
||||
describe("isDir", () => {
|
||||
|
|
|
|||
|
|
@ -7,8 +7,10 @@ import { Effect, Context, Layer } from "effect"
|
|||
import type * as PlatformError from "effect/PlatformError"
|
||||
import type * as Scope from "effect/Scope"
|
||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
|
||||
import type { Config } from "@/config/config"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { InstanceRef } from "../../src/effect/instance-ref"
|
||||
import { InstanceBootstrap } from "../../src/project/bootstrap-service"
|
||||
import type { InstanceContext } from "../../src/project/instance-context"
|
||||
|
|
@ -17,7 +19,9 @@ import { InstanceStore } from "../../src/project/instance-store"
|
|||
import { TestLLMServer } from "../lib/llm-server"
|
||||
|
||||
const noopBootstrap = Layer.succeed(InstanceBootstrap.Service, InstanceBootstrap.Service.of({ run: Effect.void }))
|
||||
export const testInstanceStoreLayer = InstanceStore.defaultLayer.pipe(Layer.provide(noopBootstrap))
|
||||
export const testInstanceStoreLayer = LayerNode.compile(InstanceStore.node, [
|
||||
[InstanceStore.bootstrapNode, noopBootstrap],
|
||||
])
|
||||
|
||||
export async function provideTestInstance<R>(input: {
|
||||
directory: string
|
||||
|
|
@ -204,7 +208,7 @@ export const withTmpdirInstance =
|
|||
Effect.gen(function* () {
|
||||
const directory = yield* tmpdirScoped(options)
|
||||
return yield* self.pipe(Effect.provideService(TestInstance, { directory }), provideInstanceEffect(directory))
|
||||
}).pipe(Effect.provide(testInstanceStoreLayer), Effect.provide(CrossSpawnSpawner.defaultLayer))
|
||||
}).pipe(Effect.provide(testInstanceStoreLayer), Effect.provide(AppNodeBuilder.build(CrossSpawnSpawner.node)))
|
||||
|
||||
export function provideTmpdirServer<A, E, R>(
|
||||
self: (input: { dir: string; llm: TestLLMServer["Service"] }) => Effect.Effect<A, E, R>,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { FetchHttpClient } from "effect/unstable/http"
|
||||
import { Layer } from "effect"
|
||||
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { Database } from "@opencode-ai/core/database/database"
|
||||
import { FSUtil } from "@opencode-ai/core/fs-util"
|
||||
import { Auth } from "../../src/auth"
|
||||
|
|
@ -14,17 +14,21 @@ import { SessionPrompt } from "../../src/session/prompt"
|
|||
import { EventV2Bridge } from "../../src/event-v2-bridge"
|
||||
|
||||
export const workspaceLayerWithRuntimeFlags = (overrides: Partial<RuntimeFlags.Info>) =>
|
||||
Workspace.layer.pipe(
|
||||
Layer.provide(Auth.defaultLayer),
|
||||
Layer.provide(Session.defaultLayer),
|
||||
Layer.provide(SessionPrompt.defaultLayer),
|
||||
Layer.provide(Project.defaultLayer),
|
||||
Layer.provide(Vcs.defaultLayer),
|
||||
Layer.provide(Database.defaultLayer),
|
||||
Layer.provide(EventV2Bridge.defaultLayer),
|
||||
Layer.provide(FetchHttpClient.layer),
|
||||
Layer.provide(FSUtil.defaultLayer),
|
||||
Layer.provide(RuntimeFlags.layer(overrides)),
|
||||
Layer.provide(InstanceStore.defaultLayer),
|
||||
Layer.provide(InstanceBootstrap.defaultLayer),
|
||||
AppNodeBuilder.build(
|
||||
LayerNode.group([
|
||||
Workspace.node,
|
||||
Auth.node,
|
||||
Session.node,
|
||||
SessionPrompt.node,
|
||||
Project.node,
|
||||
Vcs.node,
|
||||
Database.node,
|
||||
EventV2Bridge.node,
|
||||
FSUtil.node,
|
||||
InstanceStore.node,
|
||||
]),
|
||||
[
|
||||
[InstanceStore.bootstrapNode, InstanceBootstrap.node],
|
||||
[RuntimeFlags.node, RuntimeFlags.layer(overrides)],
|
||||
],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { NodeFileSystem } from "@effect/platform-node"
|
||||
import { describe, expect } from "bun:test"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { provideTmpdirInstance, testInstanceStoreLayer, TestInstance } from "../fixture/fixture"
|
||||
import { testEffect } from "../lib/effect"
|
||||
|
|
@ -7,7 +8,9 @@ import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
|||
import { Format } from "../../src/format"
|
||||
import * as Formatter from "../../src/format/formatter"
|
||||
|
||||
const it = testEffect(Layer.mergeAll(Format.defaultLayer, CrossSpawnSpawner.defaultLayer, NodeFileSystem.layer))
|
||||
const it = testEffect(
|
||||
Layer.mergeAll(LayerNode.compile(LayerNode.group([Format.node, CrossSpawnSpawner.node])), NodeFileSystem.layer),
|
||||
)
|
||||
|
||||
describe("Format", () => {
|
||||
it.instance("status() returns empty list when no formatters are configured", () =>
|
||||
|
|
@ -106,7 +109,11 @@ describe("Format", () => {
|
|||
)
|
||||
|
||||
testEffect(
|
||||
Layer.mergeAll(Format.defaultLayer, CrossSpawnSpawner.defaultLayer, NodeFileSystem.layer, testInstanceStoreLayer),
|
||||
Layer.mergeAll(
|
||||
LayerNode.compile(LayerNode.group([Format.node, CrossSpawnSpawner.node])),
|
||||
NodeFileSystem.layer,
|
||||
testInstanceStoreLayer,
|
||||
),
|
||||
).live("status() initializes formatter state per directory", () =>
|
||||
Effect.gen(function* () {
|
||||
const a = yield* provideTmpdirInstance(() => Format.use.status(), {
|
||||
|
|
|
|||
|
|
@ -1,20 +1,18 @@
|
|||
import { describe, expect } from "bun:test"
|
||||
import { Cause, Effect, Exit, Layer } from "effect"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { Cause, Effect, Exit } from "effect"
|
||||
import { Image } from "@/image/image"
|
||||
import { Config } from "@/config/config"
|
||||
import { MessageID, PartID, SessionID } from "@/session/schema"
|
||||
import path from "node:path"
|
||||
import { TestConfig } from "../fixture/config"
|
||||
import { testEffect } from "../lib/effect"
|
||||
|
||||
const it = testEffect(Layer.mergeAll(Image.layer.pipe(Layer.provide(TestConfig.layer()))))
|
||||
const it = testEffect(LayerNode.compile(Image.node, [[Config.node, TestConfig.layer()]]))
|
||||
const tiny = testEffect(
|
||||
Layer.mergeAll(
|
||||
Image.layer.pipe(
|
||||
Layer.provide(
|
||||
TestConfig.layer({ get: () => Effect.succeed({ attachment: { image: { max_base64_bytes: 1 } } }) }),
|
||||
),
|
||||
),
|
||||
),
|
||||
LayerNode.compile(Image.node, [
|
||||
[Config.node, TestConfig.layer({ get: () => Effect.succeed({ attachment: { image: { max_base64_bytes: 1 } } }) })],
|
||||
]),
|
||||
)
|
||||
|
||||
function part(mime: string, data: string) {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
import { describe, expect } from "bun:test"
|
||||
import { makeGlobalNode } from "@opencode-ai/core/effect/app-node"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { httpClient } from "@opencode-ai/core/effect/app-node-platform"
|
||||
import { Effect, Layer, Stream } from "effect"
|
||||
import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
|
||||
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
|
||||
import { Installation } from "../../src/installation"
|
||||
import { InstallationChannel } from "@opencode-ai/core/installation/version"
|
||||
import { AppProcess } from "@opencode-ai/core/process"
|
||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { testEffect } from "../lib/effect"
|
||||
|
||||
const encoder = new TextEncoder()
|
||||
|
|
@ -52,8 +55,15 @@ function testLayer(
|
|||
httpHandler: (request: HttpClientRequest.HttpClientRequest) => Response,
|
||||
spawnHandler?: (cmd: string, args: readonly string[]) => string | { code: number; stdout?: string; stderr?: string },
|
||||
) {
|
||||
const appProcess = AppProcess.layer.pipe(Layer.provide(mockSpawner(spawnHandler)))
|
||||
return Installation.layer.pipe(Layer.provide(mockHttpClient(httpHandler)), Layer.provide(appProcess))
|
||||
const spawnerNode = makeGlobalNode({
|
||||
service: ChildProcessSpawner.ChildProcessSpawner,
|
||||
layer: mockSpawner(spawnHandler),
|
||||
deps: [],
|
||||
})
|
||||
return LayerNode.compile(Installation.node, [
|
||||
[httpClient, mockHttpClient(httpHandler)],
|
||||
[CrossSpawnSpawner.node, spawnerNode],
|
||||
])
|
||||
}
|
||||
|
||||
describe("installation", () => {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue