refactor(opencode): roll out serviceUse proxy across 14 services + tests (#28576)

This commit is contained in:
Kit Langton
2026-05-20 23:36:27 -04:00
committed by GitHub
parent 69eee26f30
commit 39ea816a72
48 changed files with 640 additions and 622 deletions

View File

@@ -1,4 +1,5 @@
import { Cache, Clock, Duration, Effect, Layer, Option, Schema, SchemaGetter, Context } from "effect"
import { serviceUse } from "@/effect/service-use"
import {
FetchHttpClient,
HttpClient,
@@ -181,6 +182,8 @@ export interface Interface {
export class Service extends Context.Service<Service, Interface>()("@opencode/Account") {}
export const use = serviceUse(Service)
export const layer: Layer.Layer<Service, never, AccountRepo.Service | HttpClient.HttpClient> = Layer.effect(
Service,
Effect.gen(function* () {

View File

@@ -1,4 +1,5 @@
import { eq } from "drizzle-orm"
import { serviceUse } from "@/effect/service-use"
import { Effect, Layer, Option, Schema, Context } from "effect"
import { Database } from "@/storage/db"
@@ -38,6 +39,8 @@ export interface Interface {
export class Service extends Context.Service<Service, Interface>()("@opencode/AccountRepo") {}
export const use = serviceUse(Service)
export const layer: Layer.Layer<Service> = Layer.effect(
Service,
Effect.gen(function* () {

View File

@@ -1,4 +1,5 @@
import { Config } from "@/config/config"
import { serviceUse } from "@/effect/service-use"
import { Provider } from "@/provider/provider"
import { ModelID, ProviderID } from "../provider/schema"
import { generateObject, streamObject, type ModelMessage } from "ai"
@@ -76,6 +77,8 @@ type State = Omit<Interface, "generate">
export class Service extends Context.Service<Service, Interface>()("@opencode/Agent") {}
export const use = serviceUse(Service)
export const layer = Layer.effect(
Service,
Effect.gen(function* () {

View File

@@ -1,4 +1,5 @@
import * as Log from "@opencode-ai/core/util/log"
import { serviceUse } from "@/effect/service-use"
import path from "path"
import { pathToFileURL } from "url"
import os from "os"
@@ -319,6 +320,8 @@ export interface Interface {
export class Service extends Context.Service<Service, Interface>()("@opencode/Config") {}
export const use = serviceUse(Service)
function globalConfigFile() {
const candidates = ["opencode.jsonc", "opencode.json", "config.json"].map((file) =>
path.join(Global.Path.config, file),

View File

@@ -1,4 +1,5 @@
import { Context, Effect, Layer } from "effect"
import { serviceUse } from "@/effect/service-use"
import { InstanceState } from "@/effect/instance-state"
type State = Record<string, string | undefined>
@@ -12,6 +13,8 @@ export interface Interface {
export class Service extends Context.Service<Service, Interface>()("@opencode/Env") {}
export const use = serviceUse(Service)
export const layer = Layer.effect(
Service,
Effect.gen(function* () {

View File

@@ -1,4 +1,5 @@
import { BusEvent } from "@/bus/bus-event"
import { serviceUse } from "@/effect/service-use"
import { InstanceState } from "@/effect/instance-state"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
@@ -326,6 +327,8 @@ export interface Interface {
export class Service extends Context.Service<Service, Interface>()("@opencode/File") {}
export const use = serviceUse(Service)
export const layer = Layer.effect(
Service,
Effect.gen(function* () {

View File

@@ -1,4 +1,5 @@
import path from "path"
import { serviceUse } from "@/effect/service-use"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
import { Cause, Context, Effect, Fiber, Layer, Queue, Schema, Stream } from "effect"
import type { PlatformError } from "effect/PlatformError"
@@ -141,6 +142,8 @@ export interface Interface {
export class Service extends Context.Service<Service, Interface>()("@opencode/Ripgrep") {}
export const use = serviceUse(Service)
function env() {
const env = sanitizedProcessEnv()
delete env.RIPGREP_CONFIG_PATH

View File

@@ -1,4 +1,5 @@
import { Effect, Layer, Context, Schema } from "effect"
import { serviceUse } from "@/effect/service-use"
import { ChildProcess } from "effect/unstable/process"
import { AppProcess } from "@opencode-ai/core/process"
import { InstanceState } from "@/effect/instance-state"
@@ -27,6 +28,8 @@ export interface Interface {
export class Service extends Context.Service<Service, Interface>()("@opencode/Format") {}
export const use = serviceUse(Service)
export const layer = Layer.effect(
Service,
Effect.gen(function* () {

View File

@@ -1,4 +1,5 @@
import { Effect, Layer, Schema, Context, Stream } from "effect"
import { serviceUse } from "@/effect/service-use"
import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
import { withTransientReadRetry } from "@/util/effect-http-client"
import { errorMessage } from "@/util/error"
@@ -89,6 +90,8 @@ export interface Interface {
export class Service extends Context.Service<Service, Interface>()("@opencode/Installation") {}
export const use = serviceUse(Service)
export const layer: Layer.Layer<Service, never, HttpClient.HttpClient | AppProcess.Service> = Layer.effect(
Service,
Effect.gen(function* () {

View File

@@ -1,4 +1,5 @@
import path from "path"
import { serviceUse } from "@/effect/service-use"
import { Global } from "@opencode-ai/core/global"
import { Effect, Layer, Context, Option, Schema } from "effect"
import { AppFileSystem } from "@opencode-ai/core/filesystem"
@@ -51,6 +52,8 @@ export interface Interface {
export class Service extends Context.Service<Service, Interface>()("@opencode/McpAuth") {}
export const use = serviceUse(Service)
export const layer = Layer.effect(
Service,
Effect.gen(function* () {

View File

@@ -1,4 +1,5 @@
import { dynamicTool, type Tool, jsonSchema, type JSONSchema7 } from "ai"
import { serviceUse } from "@/effect/service-use"
import { Client } from "@modelcontextprotocol/sdk/client/index.js"
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"
@@ -264,6 +265,8 @@ export interface Interface {
export class Service extends Context.Service<Service, Interface>()("@opencode/MCP") {}
export const use = serviceUse(Service)
export const layer = Layer.effect(
Service,
Effect.gen(function* () {

View File

@@ -1,4 +1,5 @@
import { GlobalBus } from "@/bus/global"
import { serviceUse } from "@/effect/service-use"
import { WorkspaceContext } from "@/control-plane/workspace-context"
import { InstanceRef } from "@/effect/instance-ref"
import { disposeInstance as runDisposers } from "@/effect/instance-registry"
@@ -24,6 +25,8 @@ export interface Interface {
export class Service extends Context.Service<Service, Interface>()("@opencode/InstanceStore") {}
export const use = serviceUse(Service)
interface Entry {
readonly deferred: Deferred.Deferred<InstanceContext>
}

View File

@@ -1,4 +1,5 @@
import type { AuthOAuthResult, Hooks } from "@opencode-ai/plugin"
import { serviceUse } from "@/effect/service-use"
import { Auth } from "@/auth"
import { InstanceState } from "@/effect/instance-state"
import { optionalOmitUndefined } from "@opencode-ai/core/schema"
@@ -102,6 +103,8 @@ interface State {
export class Service extends Context.Service<Service, Interface>()("@opencode/ProviderAuth") {}
export const use = serviceUse(Service)
export const layer: Layer.Layer<Service, never, Auth.Service | Plugin.Service> = Layer.effect(
Service,
Effect.gen(function* () {

View File

@@ -1,4 +1,5 @@
import type * as SDK from "@opencode-ai/sdk/v2"
import { serviceUse } from "@/effect/service-use"
import { Effect, Exit, Layer, Option, Schema, Scope, Context, Stream } from "effect"
import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
import { Account } from "@/account/account"
@@ -76,6 +77,8 @@ export interface Interface {
export class Service extends Context.Service<Service, Interface>()("@opencode/ShareNext") {}
export const use = serviceUse(Service)
const db = <T>(fn: (d: Parameters<typeof Database.use>[0] extends (trx: infer D) => any ? D : never) => T) =>
Effect.sync(() => Database.use(fn))

View File

@@ -18,14 +18,14 @@ const it = testEffect(Layer.merge(AccountRepo.layer, truncate))
it.live("list returns empty when no accounts exist", () =>
Effect.gen(function* () {
const accounts = yield* AccountRepo.Service.use((r) => r.list())
const accounts = yield* AccountRepo.use.list()
expect(accounts).toEqual([])
}),
)
it.live("active returns none when no accounts exist", () =>
Effect.gen(function* () {
const active = yield* AccountRepo.Service.use((r) => r.active())
const active = yield* AccountRepo.use.active()
expect(Option.isNone(active)).toBe(true)
}),
)
@@ -45,13 +45,13 @@ it.live("persistAccount inserts and getRow retrieves", () =>
}),
)
const row = yield* AccountRepo.Service.use((r) => r.getRow(id))
const row = yield* AccountRepo.use.getRow(id)
expect(Option.isSome(row)).toBe(true)
const value = Option.getOrThrow(row)
expect(value.id).toBe(AccountID.make("user-1"))
expect(value.email).toBe("test@example.com")
const active = yield* AccountRepo.Service.use((r) => r.active())
const active = yield* AccountRepo.use.active()
expect(Option.getOrThrow(active).active_org_id).toBe(OrgID.make("org-1"))
}),
)
@@ -72,9 +72,9 @@ it.live("persistAccount normalizes trailing slashes in stored server URLs", () =
}),
)
const row = yield* AccountRepo.Service.use((r) => r.getRow(id))
const active = yield* AccountRepo.Service.use((r) => r.active())
const list = yield* AccountRepo.Service.use((r) => r.list())
const row = yield* AccountRepo.use.getRow(id)
const active = yield* AccountRepo.use.active()
const list = yield* AccountRepo.use.list()
expect(Option.getOrThrow(row).url).toBe("https://control.example.com")
expect(Option.getOrThrow(active).url).toBe("https://control.example.com")
@@ -112,7 +112,7 @@ it.live("persistAccount sets the active account and org", () =>
)
// Last persisted account is active with its org
const active = yield* AccountRepo.Service.use((r) => r.active())
const active = yield* AccountRepo.use.active()
expect(Option.isSome(active)).toBe(true)
expect(Option.getOrThrow(active).id).toBe(AccountID.make("user-2"))
expect(Option.getOrThrow(active).active_org_id).toBe(OrgID.make("org-2"))
@@ -148,7 +148,7 @@ it.live("list returns all accounts", () =>
}),
)
const accounts = yield* AccountRepo.Service.use((r) => r.list())
const accounts = yield* AccountRepo.use.list()
expect(accounts.length).toBe(2)
expect(accounts.map((a) => a.email).sort()).toEqual(["a@example.com", "b@example.com"])
}),
@@ -170,9 +170,9 @@ it.live("remove deletes an account", () =>
}),
)
yield* AccountRepo.Service.use((r) => r.remove(id))
yield* AccountRepo.use.remove(id)
const row = yield* AccountRepo.Service.use((r) => r.getRow(id))
const row = yield* AccountRepo.use.getRow(id)
expect(Option.isNone(row)).toBe(true)
}),
)
@@ -207,12 +207,12 @@ it.live("use stores the selected org and marks the account active", () =>
)
yield* AccountRepo.Service.use((r) => r.use(id1, Option.some(OrgID.make("org-99"))))
const active1 = yield* AccountRepo.Service.use((r) => r.active())
const active1 = yield* AccountRepo.use.active()
expect(Option.getOrThrow(active1).id).toBe(id1)
expect(Option.getOrThrow(active1).active_org_id).toBe(OrgID.make("org-99"))
yield* AccountRepo.Service.use((r) => r.use(id1, Option.none()))
const active2 = yield* AccountRepo.Service.use((r) => r.active())
const active2 = yield* AccountRepo.use.active()
expect(Option.getOrThrow(active2).active_org_id).toBeNull()
}),
)
@@ -243,7 +243,7 @@ it.live("persistToken updates token fields", () =>
}),
)
const row = yield* AccountRepo.Service.use((r) => r.getRow(id))
const row = yield* AccountRepo.use.getRow(id)
const value = Option.getOrThrow(row)
expect(value.access_token).toBe(AccessToken.make("new_token"))
expect(value.refresh_token).toBe(RefreshToken.make("new_refresh"))
@@ -276,7 +276,7 @@ it.live("persistToken with no expiry sets token_expiry to null", () =>
}),
)
const row = yield* AccountRepo.Service.use((r) => r.getRow(id))
const row = yield* AccountRepo.use.getRow(id)
expect(Option.getOrThrow(row).token_expiry).toBeNull()
}),
)
@@ -309,14 +309,14 @@ it.live("persistAccount upserts on conflict", () =>
}),
)
const accounts = yield* AccountRepo.Service.use((r) => r.list())
const accounts = yield* AccountRepo.use.list()
expect(accounts.length).toBe(1)
const row = yield* AccountRepo.Service.use((r) => r.getRow(id))
const row = yield* AccountRepo.use.getRow(id)
const value = Option.getOrThrow(row)
expect(value.access_token).toBe(AccessToken.make("at_v2"))
const active = yield* AccountRepo.Service.use((r) => r.active())
const active = yield* AccountRepo.use.active()
expect(Option.getOrThrow(active).active_org_id).toBe(OrgID.make("org-2"))
}),
)
@@ -337,9 +337,9 @@ it.live("remove clears active state when deleting the active account", () =>
}),
)
yield* AccountRepo.Service.use((r) => r.remove(id))
yield* AccountRepo.use.remove(id)
const active = yield* AccountRepo.Service.use((r) => r.active())
const active = yield* AccountRepo.use.active()
expect(Option.isNone(active)).toBe(true)
}),
)

View File

@@ -88,7 +88,7 @@ it.live("login normalizes trailing slashes in the provided server URL", () =>
}),
)
const result = yield* Account.Service.use((s) => s.login("https://one.example.com/")).pipe(
const result = yield* Account.use.login("https://one.example.com/").pipe(
Effect.provide(live(client)),
)
@@ -109,7 +109,7 @@ it.live("login maps transport failures to account transport errors", () =>
)
const error = yield* Effect.flip(
Account.Service.use((s) => s.login("https://one.example.com")).pipe(Effect.provide(live(client))),
Account.use.login("https://one.example.com").pipe(Effect.provide(live(client))),
)
expect(error).toBeInstanceOf(AccountTransportError)
@@ -163,7 +163,7 @@ it.live("orgsByAccount groups orgs per account", () =>
}),
)
const rows = yield* Account.Service.use((s) => s.orgsByAccount()).pipe(Effect.provide(live(client)))
const rows = yield* Account.use.orgsByAccount().pipe(Effect.provide(live(client)))
expect(rows.map((row) => [row.account.id, row.orgs.map((org) => org.id)]).map(([id, orgs]) => [id, orgs])).toEqual([
[AccountID.make("user-1"), [OrgID.make("org-1")]],
@@ -201,12 +201,12 @@ it.live("token refresh persists the new token", () =>
),
)
const token = yield* Account.Service.use((s) => s.token(id)).pipe(Effect.provide(live(client)))
const token = yield* Account.use.token(id).pipe(Effect.provide(live(client)))
expect(Option.getOrThrow(token)).toBeDefined()
expect(String(Option.getOrThrow(token))).toBe("at_new")
const row = yield* AccountRepo.Service.use((r) => r.getRow(id))
const row = yield* AccountRepo.use.getRow(id)
const value = Option.getOrThrow(row)
expect(value.access_token).toBe(AccessToken.make("at_new"))
expect(value.refresh_token).toBe(RefreshToken.make("rt_new"))
@@ -246,12 +246,12 @@ it.live("token refreshes before expiry when inside the eager refresh window", ()
}),
)
const token = yield* Account.Service.use((s) => s.token(id)).pipe(Effect.provide(live(client)))
const token = yield* Account.use.token(id).pipe(Effect.provide(live(client)))
expect(String(Option.getOrThrow(token))).toBe("at_new")
expect(refreshCalls).toBe(1)
const row = yield* AccountRepo.Service.use((r) => r.getRow(id))
const row = yield* AccountRepo.use.getRow(id)
const value = Option.getOrThrow(row)
expect(value.access_token).toBe(AccessToken.make("at_new"))
expect(value.refresh_token).toBe(RefreshToken.make("rt_new"))
@@ -315,7 +315,7 @@ it.live("concurrent config and token requests coalesce token refresh", () =>
expect(String(Option.getOrThrow(token))).toBe("at_new")
expect(refreshCalls).toBe(1)
const row = yield* AccountRepo.Service.use((r) => r.getRow(id))
const row = yield* AccountRepo.use.getRow(id)
const value = Option.getOrThrow(row)
expect(value.access_token).toBe(AccessToken.make("at_new"))
expect(value.refresh_token).toBe(RefreshToken.make("rt_new"))
@@ -388,7 +388,7 @@ it.live("poll stores the account and first org on success", () =>
expect(res.email).toBe("user@example.com")
}
const active = yield* AccountRepo.Service.use((r) => r.active())
const active = yield* AccountRepo.use.active()
expect(Option.getOrThrow(active)).toEqual(
expect.objectContaining({
id: "user-1",

View File

@@ -46,8 +46,8 @@ function testAgent(input: {
it.instance("[#26514] subagent spawned from plan mode inherits read-only restriction (edit denied)", () =>
Effect.gen(function* () {
const planAgent = yield* Agent.Service.use((svc) => svc.get("plan"))
const generalAgent = yield* Agent.Service.use((svc) => svc.get("general"))
const planAgent = yield* Agent.use.get("plan")
const generalAgent = yield* Agent.use.get("general")
expect(planAgent).toBeDefined()
expect(generalAgent).toBeDefined()
@@ -83,8 +83,8 @@ it.instance("[#26514] explore subagent launched from plan mode also stays read-o
// should propagate the parent **agent** permissions, not just deny edit
// when the subagent happens to already deny it.
Effect.gen(function* () {
const planAgent = yield* Agent.Service.use((svc) => svc.get("plan"))
const explore = yield* Agent.Service.use((svc) => svc.get("explore"))
const planAgent = yield* Agent.use.get("plan")
const explore = yield* Agent.use.get("explore")
expect(planAgent).toBeDefined()
expect(explore).toBeDefined()
@@ -108,8 +108,8 @@ it.instance(
// be able to edit when the parent agent is `plan`.
() =>
Effect.gen(function* () {
const planAgent = yield* Agent.Service.use((svc) => svc.get("plan"))
const my = yield* Agent.Service.use((svc) => svc.get("my_subagent"))
const planAgent = yield* Agent.use.get("plan")
const my = yield* Agent.use.get("my_subagent")
expect(planAgent).toBeDefined()
expect(my).toBeDefined()

View File

@@ -51,7 +51,7 @@ it.instance(
() =>
Effect.gen(function* () {
yield* Plugin.Service.use((p) => p.init())
const agents = yield* Agent.Service.use((svc) => svc.list())
const agents = yield* Agent.use.list()
const added = agents.find((agent) => agent.name === PLUGIN_AGENT.name)
expect(added?.description).toBe(PLUGIN_AGENT.description)
expect(added?.mode).toBe(PLUGIN_AGENT.mode)

View File

@@ -11,7 +11,7 @@ it.instance(
"agent color parsed from project config",
() =>
Effect.gen(function* () {
const cfg = yield* Config.Service.use((svc) => svc.get())
const cfg = yield* Config.use.get()
expect(cfg.agent?.["build"]?.color).toBe("#FFA500")
expect(cfg.agent?.["plan"]?.color).toBe("primary")
}),
@@ -30,9 +30,9 @@ it.instance(
"Agent.get includes color from config",
() =>
Effect.gen(function* () {
const plan = yield* AgentSvc.Service.use((svc) => svc.get("plan"))
const plan = yield* AgentSvc.use.get("plan")
expect(plan?.color).toBe("#A855F7")
const build = yield* AgentSvc.Service.use((svc) => svc.get("build"))
const build = yield* AgentSvc.use.get("build")
expect(build?.color).toBe("accent")
}),
{

View File

@@ -70,14 +70,14 @@ const load = (ctx: InstanceContext) =>
)
const saveGlobal = (config: Config.Info) =>
Effect.runPromise(
Config.Service.use((svc) => svc.updateGlobal(config)).pipe(
Config.use.updateGlobal(config).pipe(
Effect.map((result) => result.info),
Effect.scoped,
Effect.provide(layer),
),
)
const clear = async (wait = false) => {
await Effect.runPromise(Config.Service.use((svc) => svc.invalidate()).pipe(Effect.scoped, Effect.provide(layer)))
await Effect.runPromise(Config.use.invalidate().pipe(Effect.scoped, Effect.provide(layer)))
if (wait) await InstanceRuntime.disposeAllInstances()
}
const listDirs = (ctx: InstanceContext) =>
@@ -162,7 +162,7 @@ async function check(map: (dir: string) => string) {
it.instance("loads config with defaults when no files exist", () =>
Effect.gen(function* () {
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.username).toBeDefined()
}),
)
@@ -218,7 +218,7 @@ test("does not create global config when OPENCODE_CONFIG_DIR is set", async () =
it.instance(
"loads JSON config file",
Effect.gen(function* () {
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.model).toBe("test/model")
expect(config.username).toBe("testuser")
}),
@@ -228,7 +228,7 @@ it.instance(
it.instance(
"loads shell config field",
Effect.gen(function* () {
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.shell).toBe("bash")
}),
{ config: { shell: "bash" } },
@@ -313,7 +313,7 @@ test("updates global config and omits empty shell key in jsonc", async () => {
it.instance(
"loads formatter boolean config",
Effect.gen(function* () {
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.formatter).toBe(true)
}),
{ config: { formatter: true } },
@@ -322,7 +322,7 @@ it.instance(
it.instance(
"loads lsp boolean config",
Effect.gen(function* () {
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.lsp).toBe(true)
}),
{ config: { lsp: true } },
@@ -355,7 +355,7 @@ it.instance("ignores legacy tui keys in opencode config", () =>
tui: { scroll_speed: 4 },
})
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.model).toBe("test/model")
expect((config as Record<string, unknown>).theme).toBeUndefined()
expect((config as Record<string, unknown>).tui).toBeUndefined()
@@ -376,7 +376,7 @@ it.instance("loads JSONC config file", () =>
}`,
),
)
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.model).toBe("test/model")
expect(config.username).toBe("testuser")
}),
@@ -398,7 +398,7 @@ it.instance("jsonc overrides json in the same directory", () =>
$schema: "https://opencode.ai/config.json",
model: "override",
})
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.model).toBe("base")
expect(config.username).toBe("base")
}),
@@ -414,7 +414,7 @@ it.instance("handles environment variable substitution", () =>
$schema: "https://opencode.ai/config.json",
username: "{env:TEST_VAR}",
})
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.username).toBe("test-user")
}),
),
@@ -435,7 +435,7 @@ it.instance("preserves env variables when adding $schema to config", () =>
}),
),
)
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.username).toBe("secret_value")
// Read the file to verify the env variable was preserved
@@ -455,7 +455,7 @@ it.instance("handles file inclusion substitution", () =>
$schema: "https://opencode.ai/config.json",
username: "{file:included.txt}",
})
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.username).toBe("test-user")
}),
)
@@ -470,7 +470,7 @@ it.instance("handles file inclusion with replacement tokens", () =>
$schema: "https://opencode.ai/config.json",
username: "{file:included.md}",
})
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.username).toBe("const out = await Bun.$`echo hi`")
}),
)
@@ -547,7 +547,7 @@ it.instance("validates config schema and throws on invalid fields", () =>
$schema: "https://opencode.ai/config.json",
invalid_field: "should cause error",
})
const exit = yield* Config.Service.use((svc) => svc.get()).pipe(Effect.exit)
const exit = yield* Config.use.get().pipe(Effect.exit)
expect(Exit.isFailure(exit)).toBe(true)
}),
)
@@ -556,7 +556,7 @@ it.instance("throws error for invalid JSON", () =>
Effect.gen(function* () {
const test = yield* TestInstance
yield* Effect.promise(() => Filesystem.write(path.join(test.directory, "opencode.json"), "{ invalid json }"))
const exit = yield* Config.Service.use((svc) => svc.get()).pipe(Effect.exit)
const exit = yield* Config.use.get().pipe(Effect.exit)
expect(Exit.isFailure(exit)).toBe(true)
}),
)
@@ -574,7 +574,7 @@ it.instance("handles agent configuration", () =>
},
},
})
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.agent?.["test_agent"]).toEqual(
expect.objectContaining({
model: "test/model",
@@ -598,7 +598,7 @@ it.instance("treats agent variant as model-scoped setting (not provider option)"
},
},
})
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
const agent = config.agent?.["test_agent"]
expect(agent?.variant).toBe("xhigh")
@@ -622,7 +622,7 @@ it.instance("handles command configuration", () =>
},
},
})
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.command?.["test_command"]).toEqual({
template: "test template",
description: "test command",
@@ -638,7 +638,7 @@ it.instance("migrates autoshare to share field", () =>
$schema: "https://opencode.ai/config.json",
autoshare: true,
})
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.share).toBe("auto")
expect(config.autoshare).toBe(true)
}),
@@ -656,7 +656,7 @@ it.instance("migrates mode field to agent field", () =>
},
},
})
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.agent?.["test_mode"]).toEqual({
model: "test/model",
temperature: 0.5,
@@ -679,7 +679,7 @@ model: test/model
Test agent prompt`,
)
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.agent?.["test"]).toEqual(
expect.objectContaining({
name: "test",
@@ -705,7 +705,7 @@ permission:
Ordered permissions`,
)
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(Object.keys(config.agent?.ordered?.permission ?? {})).toEqual(["bash", "*", "edit"])
}),
)
@@ -732,7 +732,7 @@ mode: subagent
Nested agent prompt`,
)
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.agent?.["helper"]).toMatchObject({
name: "helper",
@@ -770,7 +770,7 @@ description: Nested command
Nested command template`,
)
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.command?.["hello"]).toEqual({
description: "Test command",
@@ -804,7 +804,7 @@ description: Nested command
Nested command template`,
)
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.command?.["hello"]).toEqual({
description: "Test command",
@@ -834,7 +834,7 @@ it.instance("updates config and writes to file", () =>
it.instance("gets config directories", () =>
Effect.gen(function* () {
const dirs = yield* Config.Service.use((svc) => svc.directories())
const dirs = yield* Config.use.directories()
expect(dirs.length).toBeGreaterThanOrEqual(1)
}),
)
@@ -950,7 +950,7 @@ it.instance("resolves scoped npm plugins in config", () =>
yield* writeTextEffect(path.join(pluginDir, "index.js"), "export default {}\n")
yield* writeConfigEffect(test.directory, { plugin: ["@scope/plugin"] })
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.plugin ?? []).toContain("@scope/plugin")
}),
)
@@ -1014,7 +1014,7 @@ mode: subagent
Helper subagent prompt`,
)
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.agent?.["helper"]).toMatchObject({
name: "helper",
model: "test/model",
@@ -1212,7 +1212,7 @@ it.instance("migrates legacy tools config to permissions - allow", () =>
agent: { test: { tools: { bash: true, read: true } } },
})
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.agent?.["test"]?.permission).toEqual({
bash: "allow",
read: "allow",
@@ -1228,7 +1228,7 @@ it.instance("migrates legacy tools config to permissions - deny", () =>
agent: { test: { tools: { bash: false, webfetch: false } } },
})
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.agent?.["test"]?.permission).toEqual({
bash: "deny",
webfetch: "deny",
@@ -1244,7 +1244,7 @@ it.instance("migrates legacy write tool to edit permission", () =>
agent: { test: { tools: { write: true } } },
})
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.agent?.["test"]?.permission).toEqual({ edit: "allow" })
}),
)
@@ -1261,7 +1261,7 @@ it.instance(
share: "disabled",
})
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.model).toBe("managed/model")
expect(config.share).toBe("disabled")
expect(config.username).toBe("testuser")
@@ -1278,7 +1278,7 @@ it.instance(
disabled_providers: ["openai"],
})
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.autoupdate).toBe(false)
expect(config.disabled_providers).toEqual(["openai"])
}),
@@ -1288,7 +1288,7 @@ it.instance(
it.instance(
"missing managed settings file is not an error",
Effect.gen(function* () {
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.model).toBe("user/model")
}),
{ config: { model: "user/model" } },
@@ -1302,7 +1302,7 @@ it.instance("migrates legacy edit tool to edit permission", () =>
agent: { test: { tools: { edit: false } } },
})
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.agent?.["test"]?.permission).toEqual({ edit: "deny" })
}),
)
@@ -1315,7 +1315,7 @@ it.instance("migrates legacy patch tool to edit permission", () =>
agent: { test: { tools: { patch: true } } },
})
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.agent?.["test"]?.permission).toEqual({ edit: "allow" })
}),
)
@@ -1328,7 +1328,7 @@ it.instance("migrates mixed legacy tools config", () =>
agent: { test: { tools: { bash: true, write: true, read: false, webfetch: true } } },
})
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.agent?.["test"]?.permission).toEqual({
bash: "allow",
edit: "allow",
@@ -1346,7 +1346,7 @@ it.instance("merges legacy tools with existing permission config", () =>
agent: { test: { permission: { glob: "allow" }, tools: { bash: true } } },
})
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.agent?.["test"]?.permission).toEqual({
glob: "allow",
bash: "allow",
@@ -1375,7 +1375,7 @@ it.instance("permission config preserves user key order", () =>
},
})
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(Object.keys(config.permission!)).toEqual([
"*",
"edit",
@@ -1451,7 +1451,7 @@ it.instance("project config can override MCP server enabled status", () =>
"opencode.jsonc",
)
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.mcp?.jira).toEqual({
type: "remote",
url: "https://jira.example.com/mcp",
@@ -1496,7 +1496,7 @@ it.instance("MCP config deep merges preserving base config properties", () =>
"opencode.jsonc",
)
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.mcp?.myserver).toEqual({
type: "remote",
url: "https://myserver.example.com/mcp",
@@ -1537,7 +1537,7 @@ it.instance("local .opencode config can override MCP from project config", () =>
"opencode.json",
)
const config = yield* Config.Service.use((svc) => svc.get())
const config = yield* Config.use.get()
expect(config.mcp?.docs?.enabled).toBe(true)
}),
)

View File

@@ -93,7 +93,7 @@ it.instance("keeps server and tui plugin merge semantics aligned", () =>
plugin: [["shared-plugin@2.0.0", { source: "local" }], "local-only@1.0.0"],
})
const server = yield* Config.Service.use((svc) => svc.get())
const server = yield* Config.use.get()
const tui = yield* getTuiConfig(test.directory)
const serverPlugins = (server.plugin ?? []).map((item) => ConfigPlugin.pluginSpecifier(item))
const tuiPlugins = (tui.plugin ?? []).map((item) => ConfigPlugin.pluginSpecifier(item))

File diff suppressed because it is too large Load Diff

View File

@@ -36,7 +36,7 @@ describe("file fsmonitor", () => {
const before = yield* Effect.promise(() => $`git fsmonitor--daemon status`.cwd(directory).quiet().nothrow())
expect(before.exitCode).not.toBe(0)
yield* File.Service.use((svc) => svc.status())
yield* File.use.status()
const after = yield* Effect.promise(() => $`git fsmonitor--daemon status`.cwd(directory).quiet().nothrow())
expect(after.exitCode).not.toBe(0)
@@ -63,7 +63,7 @@ describe("file fsmonitor", () => {
const before = yield* Effect.promise(() => $`git fsmonitor--daemon status`.cwd(directory).quiet().nothrow())
expect(before.exitCode).not.toBe(0)
yield* File.Service.use((svc) => svc.read("tracked.txt"))
yield* File.use.read("tracked.txt")
const after = yield* Effect.promise(() => $`git fsmonitor--daemon status`.cwd(directory).quiet().nothrow())
expect(after.exitCode).not.toBe(0)

View File

@@ -10,8 +10,8 @@ import { TestInstance } from "../fixture/fixture"
import { testEffect } from "../lib/effect"
const it = testEffect(File.defaultLayer)
const read = (file: string) => File.Service.use((svc) => svc.read(file))
const list = (dir?: string) => File.Service.use((svc) => svc.list(dir))
const read = (file: string) => File.use.read(file)
const list = (dir?: string) => File.use.list(dir)
const expectAccessDenied = <A, E, R>(effect: Effect.Effect<A, E, R>) =>
Effect.gen(function* () {
const exit = yield* effect.pipe(Effect.exit)

View File

@@ -85,7 +85,7 @@ describe("file.ripgrep", () => {
Effect.gen(function* () {
const dir = yield* tmpdir((dir) => write(path.join(dir, "match.ts"), "const value = 'other'\n"))
const result = yield* Ripgrep.Service.use((rg) => rg.search({ cwd: dir, pattern: "needle" }))
const result = yield* Ripgrep.use.search({ cwd: dir, pattern: "needle" })
expect(result.partial).toBe(false)
expect(result.items).toEqual([])
}),
@@ -100,7 +100,7 @@ describe("file.ripgrep", () => {
}),
)
const result = yield* Ripgrep.Service.use((rg) => rg.search({ cwd: dir, pattern: "needle" }))
const result = yield* Ripgrep.use.search({ cwd: dir, pattern: "needle" })
expect(result.partial).toBe(false)
expect(result.items).toHaveLength(1)
expect(result.items[0]?.path.text).toBe(path.join("src", "match.ts"))
@@ -118,7 +118,7 @@ describe("file.ripgrep", () => {
}),
)
const result = yield* Ripgrep.Service.use((rg) => rg.search({ cwd: dir, pattern: "needle", glob: ["*.ts"] }))
const result = yield* Ripgrep.use.search({ cwd: dir, pattern: "needle", glob: ["*.ts"] })
expect(result.partial).toBe(false)
expect(result.items).toHaveLength(1)
expect(result.items[0]?.path.text).toContain("match.ts")
@@ -136,7 +136,7 @@ describe("file.ripgrep", () => {
)
const file = path.join(dir, "match.ts")
const result = yield* Ripgrep.Service.use((rg) => rg.search({ cwd: dir, pattern: "needle", file: [file] }))
const result = yield* Ripgrep.use.search({ cwd: dir, pattern: "needle", file: [file] })
expect(result.partial).toBe(false)
expect(result.items).toHaveLength(1)
expect(result.items[0]?.path.text).toBe(file)
@@ -200,7 +200,7 @@ describe("file.ripgrep", () => {
const result = yield* withRipgrepConfig(
path.join(dir, "missing-ripgreprc"),
Ripgrep.Service.use((rg) => rg.search({ cwd: dir, pattern: "needle" })),
Ripgrep.use.search({ cwd: dir, pattern: "needle" }),
)
expect(result.items).toHaveLength(1)
}),
@@ -212,7 +212,7 @@ describe("file.ripgrep", () => {
const result = yield* withRipgrepConfig(
path.join(dir, "missing-ripgreprc"),
Ripgrep.Service.use((rg) => rg.search({ cwd: dir, pattern: "needle" })),
Ripgrep.use.search({ cwd: dir, pattern: "needle" }),
)
expect(result.items).toHaveLength(1)
}),

View File

@@ -133,7 +133,7 @@ describe("Format", () => {
const file = `${dir}/test.txt`
yield* Effect.promise(() => Bun.write(file, "x"))
const formatted = yield* Format.Service.use((fmt) => fmt.file(file))
const formatted = yield* Format.use.file(file)
expect(formatted).toBe(false)
}),
{
@@ -146,10 +146,10 @@ describe("Format", () => {
it.live("status() initializes formatter state per directory", () =>
Effect.gen(function* () {
const a = yield* provideTmpdirInstance(() => Format.Service.use((fmt) => fmt.status()), {
const a = yield* provideTmpdirInstance(() => Format.use.status(), {
config: { formatter: false },
})
const b = yield* provideTmpdirInstance(() => Format.Service.use((fmt) => fmt.status()), {
const b = yield* provideTmpdirInstance(() => Format.use.status(), {
config: {
formatter: true,
},

View File

@@ -58,7 +58,7 @@ describe("installation", () => {
"reads release version from GitHub releases",
() =>
Effect.gen(function* () {
const result = yield* Installation.Service.use((svc) => svc.latest("unknown"))
const result = yield* Installation.use.latest("unknown")
expect(result).toBe("1.2.3")
}),
)
@@ -67,7 +67,7 @@ describe("installation", () => {
"strips v prefix from GitHub release tag",
() =>
Effect.gen(function* () {
const result = yield* Installation.Service.use((svc) => svc.latest("curl"))
const result = yield* Installation.use.latest("curl")
expect(result).toBe("4.0.0-beta.1")
}),
)
@@ -80,7 +80,7 @@ describe("installation", () => {
}),
).effect("reads npm versions via registry", () =>
Effect.gen(function* () {
const result = yield* Installation.Service.use((svc) => svc.latest("npm"))
const result = yield* Installation.use.latest("npm")
expect(result).toBe("1.5.0")
expect(npmCalls).toContain(`https://registry.npmjs.org/opencode-ai/${InstallationChannel}`)
}),
@@ -94,7 +94,7 @@ describe("installation", () => {
}),
).effect("reads bun versions via registry", () =>
Effect.gen(function* () {
const result = yield* Installation.Service.use((svc) => svc.latest("bun"))
const result = yield* Installation.use.latest("bun")
expect(result).toBe("1.6.0")
expect(bunCalls).toContain(`https://registry.npmjs.org/opencode-ai/${InstallationChannel}`)
}),
@@ -108,7 +108,7 @@ describe("installation", () => {
}),
).effect("reads pnpm versions via registry", () =>
Effect.gen(function* () {
const result = yield* Installation.Service.use((svc) => svc.latest("pnpm"))
const result = yield* Installation.use.latest("pnpm")
expect(result).toBe("1.7.0")
expect(pnpmCalls).toContain(`https://registry.npmjs.org/opencode-ai/${InstallationChannel}`)
}),
@@ -116,7 +116,7 @@ describe("installation", () => {
testEffect(testLayer(() => jsonResponse({ version: "2.3.4" }))).effect("reads scoop manifest versions", () =>
Effect.gen(function* () {
const result = yield* Installation.Service.use((svc) => svc.latest("scoop"))
const result = yield* Installation.use.latest("scoop")
expect(result).toBe("2.3.4")
}),
)
@@ -125,7 +125,7 @@ describe("installation", () => {
"reads chocolatey feed versions",
() =>
Effect.gen(function* () {
const result = yield* Installation.Service.use((svc) => svc.latest("choco"))
const result = yield* Installation.use.latest("choco")
expect(result).toBe("3.4.5")
}),
)
@@ -142,7 +142,7 @@ describe("installation", () => {
),
).effect("reads brew formulae API versions", () =>
Effect.gen(function* () {
const result = yield* Installation.Service.use((svc) => svc.latest("brew"))
const result = yield* Installation.use.latest("brew")
expect(result).toBe("2.0.0")
}),
)
@@ -161,7 +161,7 @@ describe("installation", () => {
),
).effect("reads brew tap info JSON via CLI", () =>
Effect.gen(function* () {
const result = yield* Installation.Service.use((svc) => svc.latest("brew"))
const result = yield* Installation.use.latest("brew")
expect(result).toBe("2.1.0")
}),
)

View File

@@ -175,7 +175,7 @@ mcpTest.instance("state() generates a new state when none is saved", () =>
auth,
)
const entryBefore = yield* McpAuth.Service.use((auth) => auth.get("test-state-gen"))
const entryBefore = yield* McpAuth.use.get("test-state-gen")
expect(entryBefore?.oauthState).toBeUndefined()
// state() should generate and return a new state, not throw
@@ -184,7 +184,7 @@ mcpTest.instance("state() generates a new state when none is saved", () =>
expect(state.length).toBe(64) // 32 bytes as hex
// The generated state should be persisted
const entryAfter = yield* McpAuth.Service.use((auth) => auth.get("test-state-gen"))
const entryAfter = yield* McpAuth.use.get("test-state-gen")
expect(entryAfter?.oauthState).toBe(state)
}),
)
@@ -202,7 +202,7 @@ mcpTest.instance("state() returns existing state when one is saved", () =>
// Pre-save a state
const existingState = "pre-saved-state-value"
yield* McpAuth.Service.use((auth) => auth.updateOAuthState("test-state-existing", existingState))
yield* McpAuth.use.updateOAuthState("test-state-existing", existingState)
// state() should return the existing state
const state = yield* Effect.promise(() => provider.state())

View File

@@ -6,7 +6,7 @@ import { testEffect } from "./lib/effect"
const it = testEffect(Config.defaultLayer)
const load = Config.Service.use((svc) => svc.get())
const load = Config.use.get()
describe("Permission.evaluate for permission.task", () => {
const createRuleset = (rules: Record<string, "allow" | "deny" | "ask">): Permission.Ruleset =>

View File

@@ -72,10 +72,10 @@ describe("plugin.auth-override", () => {
const plain = yield* tmpdirScoped({ git: true })
const plugin = pathToFileURL(path.join(pluginDir, "custom-copilot-auth.ts")).href
const methods = yield* ProviderAuth.Service.use((svc) => svc.methods()).pipe(
const methods = yield* ProviderAuth.use.methods().pipe(
Effect.provide(layer(tmp.directory, [plugin])),
)
const plainMethods = yield* ProviderAuth.Service.use((svc) => svc.methods()).pipe(
const plainMethods = yield* ProviderAuth.use.methods().pipe(
Effect.provide(layer(plain, [])),
provideInstance(plain),
)

View File

@@ -18,7 +18,7 @@ const set = (k: string, v: string) =>
Effect.gen(function* () {
if (!originalEnv.has(k)) originalEnv.set(k, process.env[k])
process.env[k] = v
yield* Env.Service.use((svc) => svc.set(k, v))
yield* Env.use.set(k, v)
})
afterEach(async () => {
@@ -30,7 +30,7 @@ afterEach(async () => {
await disposeAllInstances()
})
const list = Provider.Service.use((svc) => svc.list())
const list = Provider.use.list()
const withAuthJson = (contents: string) =>
Effect.acquireRelease(

View File

@@ -35,14 +35,14 @@ const set = (k: string, v: string) =>
Effect.gen(function* () {
rememberEnv(k)
process.env[k] = v
yield* Env.Service.use((svc) => svc.set(k, v))
yield* Env.use.set(k, v)
})
const remove = (k: string) =>
Effect.gen(function* () {
rememberEnv(k)
delete process.env[k]
yield* Env.Service.use((svc) => svc.remove(k))
yield* Env.use.remove(k)
})
afterEach(async () => {
@@ -332,7 +332,7 @@ test("parseModel handles model IDs with slashes", () => {
it.instance("defaultModel returns first available model when no config set", () =>
Effect.gen(function* () {
yield* setProcessEnv("ANTHROPIC_API_KEY", "test-api-key")
const model = yield* Provider.Service.use((provider) => provider.defaultModel())
const model = yield* Provider.use.defaultModel()
expect(model.providerID).toBeDefined()
expect(model.modelID).toBeDefined()
}),
@@ -342,7 +342,7 @@ it.instance(
"defaultModel respects config model setting",
Effect.gen(function* () {
yield* setProcessEnv("ANTHROPIC_API_KEY", "test-api-key")
const model = yield* Provider.Service.use((provider) => provider.defaultModel())
const model = yield* Provider.use.defaultModel()
expect(String(model.providerID)).toBe("anthropic")
expect(String(model.modelID)).toBe("claude-sonnet-4-20250514")
}),
@@ -1032,7 +1032,7 @@ it.instance("getProvider returns undefined for nonexistent provider", () =>
it.instance("getProvider returns provider info", () =>
Effect.gen(function* () {
yield* set("ANTHROPIC_API_KEY", "test-api-key")
const provider = yield* Provider.Service.use((svc) => svc.getProvider(ProviderID.anthropic))
const provider = yield* Provider.use.getProvider(ProviderID.anthropic)
expect(provider).toBeDefined()
expect(String(provider?.id)).toBe("anthropic")
}),
@@ -1680,7 +1680,7 @@ it.effect("opencode loader keeps paid models when config apiKey is present", ()
})
const listIn = (directory: string) =>
Provider.Service.use((svc) => svc.list())
Provider.use.list()
.pipe(provideInstanceEffect(directory))
.pipe(Effect.provide(InstanceLayer.layer), Effect.provide(CrossSpawnSpawner.defaultLayer))
@@ -1698,7 +1698,7 @@ it.effect("opencode loader keeps paid models when auth exists", () =>
const keyedDir = yield* tmpdirScoped()
const listIn = (directory: string) =>
Provider.Service.use((svc) => svc.list())
Provider.use.list()
.pipe(provideInstanceEffect(directory))
.pipe(Effect.provide(InstanceLayer.layer), Effect.provide(CrossSpawnSpawner.defaultLayer))

View File

@@ -13,7 +13,7 @@ const it = testEffect(Layer.mergeAll(SessionNs.defaultLayer, Project.defaultLaye
const withSession = (input?: Parameters<SessionNs.Interface["create"]>[0]) =>
Effect.acquireRelease(
SessionNs.Service.use((session) => session.create(input)),
SessionNs.use.create(input),
(created) => SessionNs.Service.use((session) => session.remove(created.id).pipe(Effect.ignore)),
)
@@ -34,8 +34,8 @@ describe("session.listGlobal", () => {
expect(ids).toContain(firstSession.id)
expect(ids).toContain(secondSession.id)
const firstProject = yield* Project.Service.use((project) => project.get(firstSession.projectID))
const secondProject = yield* Project.Service.use((project) => project.get(secondSession.projectID))
const firstProject = yield* Project.use.get(firstSession.projectID)
const secondProject = yield* Project.use.get(secondSession.projectID)
const firstItem = sessions.find((session) => session.id === firstSession.id)
const secondItem = sessions.find((session) => session.id === secondSession.id)

View File

@@ -31,7 +31,7 @@ function request(path: string, directory: string, init: RequestInit = {}) {
}
function createSession(input?: Session.CreateInput) {
return Session.Service.use((svc) => svc.create(input))
return Session.use.create(input)
}
function json<T>(response: Response) {

View File

@@ -74,7 +74,7 @@ const createLocalWorkspace = (input: { projectID: Project.Info["id"]; type: stri
projectID: input.projectID,
})
}),
(info) => Workspace.Service.use((workspace) => workspace.remove(info.id)).pipe(Effect.ignore),
(info) => Workspace.use.remove(info.id).pipe(Effect.ignore),
)
const probeInstanceContext = Effect.gen(function* () {

View File

@@ -52,7 +52,7 @@ function pathFor(path: string, params: Record<string, string>) {
}
function createSession(input?: Session.CreateInput) {
return Session.Service.use((svc) => svc.create(input))
return Session.use.create(input)
}
function createTextMessage(sessionID: SessionIDType, text: string) {
@@ -101,7 +101,7 @@ const createLocalWorkspace = (input: { projectID: Project.Info["id"]; type: stri
}),
)
}),
(info) => Workspace.Service.use((svc) => svc.remove(info.id)).pipe(Effect.ignore),
(info) => Workspace.use.remove(info.id).pipe(Effect.ignore),
)
const insertLegacyAssistantMessage = (sessionID: SessionIDType, time = 1) =>

View File

@@ -36,7 +36,7 @@ describe("sync HttpApi", () => {
const tmp = yield* TestInstance
const headers = { "x-opencode-directory": tmp.directory, "content-type": "application/json" }
const info = spyOn(Log.create({ service: "server.sync" }), "info")
const session = yield* Session.Service.use((svc) => svc.create({ title: "sync" }))
const session = yield* Session.use.create({ title: "sync" })
const started = yield* Effect.promise(() =>
Promise.resolve(app().request(SyncPaths.start, { method: "POST", headers })),

View File

@@ -128,7 +128,7 @@ const createWorkspace = (input: { projectID: Project.Info["id"]; type: string; a
projectID: input.projectID,
})
}),
(info) => Workspace.Service.use((workspace) => workspace.remove(info.id)).pipe(Effect.ignore),
(info) => Workspace.use.remove(info.id).pipe(Effect.ignore),
)
const createRemoteWorkspace = (input: {

View File

@@ -208,7 +208,7 @@ describe("workspace HttpApi", () => {
const workspace = (yield* Effect.promise(() => created.json())) as Workspace.Info
expect(workspace).toMatchObject({ type: "local-test", name: "local-test" })
const session = yield* Session.Service.use((svc) => svc.create({})).pipe(provideInstance(dir))
const session = yield* Session.use.create({}).pipe(provideInstance(dir))
const warped = yield* request(WorkspacePaths.warp, dir, {
method: "POST",
headers: { "content-type": "application/json" },
@@ -424,7 +424,7 @@ describe("workspace HttpApi", () => {
body: JSON.stringify({ type: "remote-session-target", branch: null }),
})
const workspace = (yield* Effect.promise(() => created.json())) as Workspace.Info
const session = yield* Session.Service.use((svc) => svc.create()).pipe(
const session = yield* Session.use.create().pipe(
Effect.provideService(WorkspaceRef, workspace.id),
provideInstance(dir),
)

View File

@@ -83,7 +83,7 @@ describe("project.initGit endpoint", () => {
worktree: tmp.directory,
})
const ctx = yield* InstanceStore.Service.use((store) => store.reload({ directory: tmp.directory }))
const ctx = yield* InstanceStore.use.reload({ directory: tmp.directory })
const tracked = yield* Snapshot.Service.use((snapshot) => snapshot.track()).pipe(
Effect.provideService(InstanceRef, ctx),
)

View File

@@ -22,8 +22,8 @@ describe("session action routes", () => {
Effect.gen(function* () {
const test = yield* TestInstance
const session = yield* Effect.acquireRelease(
SessionNs.Service.use((svc) => svc.create({})),
(created) => SessionNs.Service.use((svc) => svc.remove(created.id)).pipe(Effect.ignore),
SessionNs.use.create({}),
(created) => SessionNs.use.remove(created.id).pipe(Effect.ignore),
)
const res = yield* Effect.promise(() =>

View File

@@ -35,8 +35,8 @@ function pathFor(template: string, params: Record<string, string>) {
const withSession = (input?: Parameters<Session.Interface["create"]>[0]) =>
Effect.acquireRelease(
Session.Service.use((session) => session.create(input)),
(created) => Session.Service.use((session) => session.remove(created.id)).pipe(Effect.ignore),
Session.use.create(input),
(created) => Session.use.remove(created.id).pipe(Effect.ignore),
)
describe("session diff with missing patch (#26574)", () => {

View File

@@ -28,7 +28,7 @@ const it = testEffect(
const withSession = (input?: Parameters<SessionNs.Interface["create"]>[0]) =>
Effect.acquireRelease(
SessionNs.Service.use((session) => session.create(input)),
SessionNs.use.create(input),
(created) => SessionNs.Service.use((session) => session.remove(created.id).pipe(Effect.ignore)),
)
@@ -56,7 +56,7 @@ describe("session.list", () => {
provideInstance(path.join(test.directory, "packages", "app")),
)
const ids = (yield* SessionNs.Service.use((session) => session.list())).map((session) => session.id)
const ids = (yield* SessionNs.use.list()).map((session) => session.id)
expect(ids).toContain(root.id)
expect(ids).toContain(parent.id)
expect(ids).toContain(current.id)
@@ -179,7 +179,7 @@ describe("session.list", () => {
const root = yield* withSession({ title: "root-session" })
const child = yield* withSession({ title: "child-session", parentID: root.id })
const sessions = yield* SessionNs.Service.use((session) => session.list({ roots: true }))
const sessions = yield* SessionNs.use.list({ roots: true })
const ids = sessions.map((session) => session.id)
expect(ids).toContain(root.id)
@@ -206,7 +206,7 @@ describe("session.list", () => {
yield* withSession({ title: "unique-search-term-abc" })
yield* withSession({ title: "other-session-xyz" })
const sessions = yield* SessionNs.Service.use((session) => session.list({ search: "unique-search" }))
const sessions = yield* SessionNs.use.list({ search: "unique-search" })
const titles = sessions.map((session) => session.title)
expect(titles).toContain("unique-search-term-abc")
@@ -223,7 +223,7 @@ describe("session.list", () => {
yield* withSession({ title: "session-2" })
yield* withSession({ title: "session-3" })
const sessions = yield* SessionNs.Service.use((session) => session.list({ limit: 2 }))
const sessions = yield* SessionNs.use.list({ limit: 2 })
expect(sessions.length).toBe(2)
}),
{ git: true },

View File

@@ -40,8 +40,8 @@ const withoutWatcher = <A, E, R>(effect: Effect.Effect<A, E, R>) => {
}
const sessionScoped = Effect.acquireRelease(
SessionNs.Service.use((svc) => svc.create({})),
(session) => SessionNs.Service.use((svc) => svc.remove(session.id)).pipe(Effect.ignore),
SessionNs.use.create({}),
(session) => SessionNs.use.remove(session.id).pipe(Effect.ignore),
)
const fill = Effect.fn("SessionMessagesTest.fill")(function* (

View File

@@ -16,7 +16,7 @@ describe("tui.selectSession endpoint", () => {
() =>
Effect.gen(function* () {
const tmp = yield* TestInstance
const session = yield* Session.Service.use((svc) => svc.create({}))
const session = yield* Session.use.create({})
const app = Server.Default().app
const response = yield* Effect.promise(() =>

View File

@@ -292,7 +292,7 @@ function createSummaryCompaction(sessionID: SessionID) {
}
function readCompactionPart(sessionID: SessionID) {
return SessionNs.Service.use((ssn) => ssn.messages({ sessionID })).pipe(
return SessionNs.use.messages({ sessionID }).pipe(
Effect.map((messages) =>
messages.at(-2)?.parts.find((item): item is MessageV2.CompactionPart => item.type === "compaction"),
),

View File

@@ -35,7 +35,7 @@ const awaitDeferred = <T>(deferred: Deferred.Deferred<T>, message: string) =>
Effect.sleep("2 seconds").pipe(Effect.flatMap(() => Effect.fail(new Error(message)))),
)
const remove = (id: SessionID) => SessionNs.Service.use((svc) => svc.remove(id))
const remove = (id: SessionID) => SessionNs.use.remove(id)
const subscribeGlobal = (type: string, callback: (event: NonNullable<GlobalEvent["payload"]>) => void) => {
const listener = (event: GlobalEvent) => {

View File

@@ -128,7 +128,7 @@ describe("ShareNext", () => {
Effect.gen(function* () {
yield* seed("https://control.example.com", "org-1")
const req = yield* ShareNext.Service.use((svc) => svc.request()).pipe(Effect.provide(live(none)))
const req = yield* ShareNext.use.request().pipe(Effect.provide(live(none)))
expect(req.api.create).toBe("/api/shares")
expect(req.api.sync("shr_123")).toBe("/api/shares/shr_123/sync")
@@ -147,7 +147,7 @@ describe("ShareNext", () => {
provideTmpdirInstance(
() =>
Effect.gen(function* () {
const session = yield* Session.Service.use((svc) => svc.create({ title: "test" }))
const session = yield* Session.use.create({ title: "test" })
const seen: HttpClientRequest.HttpClientRequest[] = []
const client = HttpClient.make((req) => {
seen.push(req)
@@ -163,7 +163,7 @@ describe("ShareNext", () => {
return Effect.succeed(json(req, { ok: true }))
})
const result = yield* ShareNext.Service.use((svc) => svc.create(session.id)).pipe(
const result = yield* ShareNext.use.create(session.id).pipe(
Effect.provide(live(client)),
)
@@ -188,7 +188,7 @@ describe("ShareNext", () => {
provideTmpdirInstance(
() =>
Effect.gen(function* () {
const session = yield* Session.Service.use((svc) => svc.create({ title: "test" }))
const session = yield* Session.use.create({ title: "test" })
const seen: HttpClientRequest.HttpClientRequest[] = []
const client = HttpClient.make((req) => {
seen.push(req)
@@ -205,8 +205,8 @@ describe("ShareNext", () => {
})
yield* Effect.gen(function* () {
yield* ShareNext.Service.use((svc) => svc.create(session.id))
yield* ShareNext.Service.use((svc) => svc.remove(session.id))
yield* ShareNext.use.create(session.id)
yield* ShareNext.use.remove(session.id)
}).pipe(Effect.provide(live(client)))
expect(share(session.id)).toBeUndefined()
@@ -222,7 +222,7 @@ describe("ShareNext", () => {
it.live("create fails on a non-ok response and does not persist a share", () =>
provideTmpdirInstance(() =>
Effect.gen(function* () {
const session = yield* Session.Service.use((svc) => svc.create({ title: "test" }))
const session = yield* Session.use.create({ title: "test" })
const client = HttpClient.make((req) => Effect.succeed(json(req, { error: "bad" }, 500)))
const exit = yield* ShareNext.Service.use((svc) => Effect.exit(svc.create(session.id))).pipe(