diff --git a/packages/opencode/src/account/index.ts b/packages/opencode/src/account/index.ts index 753b80c5f1..22341f481a 100644 --- a/packages/opencode/src/account/index.ts +++ b/packages/opencode/src/account/index.ts @@ -1,34 +1,26 @@ -import { Effect, Option } from "effect" - -import { Account as S, type AccountError, type AccessToken, AccountID, Info as Model, OrgID } from "./effect" +import { Option } from "effect" +import { run } from "@/effect/run" +import { type AccessToken, AccountID, Info as Model, OrgID } from "./effect" export { AccessToken, AccountID, OrgID } from "./effect" -import { runtime } from "@/effect/runtime" - -function runSync(f: (service: S.Interface) => Effect.Effect) { - return runtime.runSync(S.Service.use(f)) -} - -function runPromise(f: (service: S.Interface) => Effect.Effect) { - return runtime.runPromise(S.Service.use(f)) -} +const svc = () => import("./effect").then((m) => m.Account.Service) export namespace Account { export const Info = Model export type Info = Model - export function active(): Info | undefined { - return Option.getOrUndefined(runSync((service) => service.active())) + export async function active(): Promise { + return Option.getOrUndefined(await run((await svc()).use((s) => s.active()))) } export async function config(accountID: AccountID, orgID: OrgID): Promise | undefined> { - const config = await runPromise((service) => service.config(accountID, orgID)) + const config = await run((await svc()).use((s) => s.config(accountID, orgID))) return Option.getOrUndefined(config) } export async function token(accountID: AccountID): Promise { - const token = await runPromise((service) => service.token(accountID)) + const token = await run((await svc()).use((s) => s.token(accountID))) return Option.getOrUndefined(token) } } diff --git a/packages/opencode/src/auth/index.ts b/packages/opencode/src/auth/index.ts index 411d9dccc0..b8fb00243a 100644 --- a/packages/opencode/src/auth/index.ts +++ b/packages/opencode/src/auth/index.ts @@ -1,13 +1,9 @@ -import { Effect } from "effect" import z from "zod" -import { runtime } from "@/effect/runtime" -import * as S from "./effect" +import { run } from "@/effect/run" export { OAUTH_DUMMY_KEY } from "./effect" -function runPromise(f: (service: S.Auth.Interface) => Effect.Effect) { - return runtime.runPromise(S.Auth.Service.use(f)) -} +const svc = () => import("./effect").then((m) => m.Auth.Service) export namespace Auth { export const Oauth = z @@ -40,18 +36,18 @@ export namespace Auth { export type Info = z.infer export async function get(providerID: string) { - return runPromise((service) => service.get(providerID)) + return run((await svc()).use((s) => s.get(providerID))) } export async function all(): Promise> { - return runPromise((service) => service.all()) + return run((await svc()).use((s) => s.all())) } export async function set(key: string, info: Info) { - return runPromise((service) => service.set(key, info)) + return run((await svc()).use((s) => s.set(key, info))) } export async function remove(key: string) { - return runPromise((service) => service.remove(key)) + return run((await svc()).use((s) => s.remove(key))) } } diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 47afdfd7d0..09fbcfe19a 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -11,7 +11,6 @@ import fs from "fs/promises" import { lazy } from "../util/lazy" import { NamedError } from "@opencode-ai/util/error" import { Flag } from "../flag/flag" -import { Auth } from "../auth" import { Env } from "../env" import { type ParseError as JsoncParseError, @@ -33,12 +32,14 @@ import { Glob } from "../util/glob" import { PackageRegistry } from "@/bun/registry" import { proxied } from "@/util/proxied" import { iife } from "@/util/iife" -import { Account } from "@/account" import { ConfigPaths } from "./paths" import { Filesystem } from "@/util/filesystem" import { Process } from "@/util/process" import { Lock } from "@/util/lock" +const auth = lazy(() => import("../auth").then((x) => x.Auth)) +const account = lazy(() => import("@/account").then((x) => x.Account)) + export namespace Config { const ModelId = z.string().meta({ $ref: "https://models.dev/model-schema.json#/$defs/Model" }) @@ -76,7 +77,7 @@ export namespace Config { } export const state = Instance.state(async () => { - const auth = await Auth.all() + const entries = await (await auth()).all() // Config loading order (low -> high precedence): https://opencode.ai/docs/config#precedence-order // 1) Remote .well-known/opencode (org defaults) @@ -87,7 +88,7 @@ export namespace Config { // 6) Inline config (OPENCODE_CONFIG_CONTENT) // Managed config directory is enterprise-only and always overrides everything above. let result: Info = {} - for (const [key, value] of Object.entries(auth)) { + for (const [key, value] of Object.entries(entries)) { if (value.type === "wellknown") { const url = key.replace(/\/+$/, "") process.env[value.key] = value.token @@ -177,13 +178,11 @@ export namespace Config { log.debug("loaded custom config from OPENCODE_CONFIG_CONTENT") } - const active = Account.active() + const acct = await account() + const active = await acct.active() if (active?.active_org_id) { try { - const [config, token] = await Promise.all([ - Account.config(active.id, active.active_org_id), - Account.token(active.id), - ]) + const [config, token] = await Promise.all([acct.config(active.id, active.active_org_id), acct.token(active.id)]) if (token) { process.env["OPENCODE_CONSOLE_TOKEN"] = token Env.set("OPENCODE_CONSOLE_TOKEN", token) diff --git a/packages/opencode/src/effect/run.ts b/packages/opencode/src/effect/run.ts new file mode 100644 index 0000000000..3f843a1856 --- /dev/null +++ b/packages/opencode/src/effect/run.ts @@ -0,0 +1,21 @@ +import type { Effect } from "effect" + +/** + * Lazy wrappers that defer the import of @/effect/runtime to call time. + * + * Adapter modules must not eagerly import @/effect/runtime — or even + * their own service modules — because bun's bundler can evaluate them + * before their dependencies have finished initializing. + */ + +/** For global services (Auth, Account, etc.) */ +export async function run(effect: Effect.Effect): Promise { + const { runtime } = await import("@/effect/runtime") + return runtime.runPromise(effect) +} + +/** For instance-scoped services (Skill, Snapshot, Question, etc.) */ +export async function runInstance(effect: Effect.Effect): Promise { + const { runPromiseInstance } = await import("@/effect/runtime") + return runPromiseInstance(effect) +} diff --git a/packages/opencode/src/effect/runtime.ts b/packages/opencode/src/effect/runtime.ts index e6f1f32626..c1d60d48d0 100644 --- a/packages/opencode/src/effect/runtime.ts +++ b/packages/opencode/src/effect/runtime.ts @@ -9,7 +9,7 @@ import { Instance } from "@/project/instance" export const runtime = ManagedRuntime.make( Layer.mergeAll( - Account.defaultLayer, // + Account.defaultLayer, Installation.defaultLayer, Truncate.defaultLayer, Instances.layer, diff --git a/packages/opencode/src/file/index.ts b/packages/opencode/src/file/index.ts index 35a5b5e204..f97fef5f3c 100644 --- a/packages/opencode/src/file/index.ts +++ b/packages/opencode/src/file/index.ts @@ -1,40 +1,37 @@ -import { runPromiseInstance } from "@/effect/runtime" +import { runInstance } from "@/effect/run" import { File as S } from "./service" +const svc = () => import("./service").then((m) => m.File.Service) + export namespace File { export const Info = S.Info export type Info = S.Info - export const Node = S.Node export type Node = S.Node - export const Content = S.Content export type Content = S.Content - export const Event = S.Event - export type Interface = S.Interface - export const Service = S.Service export const layer = S.layer - export function init() { - return runPromiseInstance(S.Service.use((svc) => svc.init())) + export async function init() { + return runInstance((await svc()).use((s) => s.init())) } export async function status() { - return runPromiseInstance(S.Service.use((svc) => svc.status())) + return runInstance((await svc()).use((s) => s.status())) } export async function read(file: string): Promise { - return runPromiseInstance(S.Service.use((svc) => svc.read(file))) + return runInstance((await svc()).use((s) => s.read(file))) } export async function list(dir?: string) { - return runPromiseInstance(S.Service.use((svc) => svc.list(dir))) + return runInstance((await svc()).use((s) => s.list(dir))) } export async function search(input: { query: string; limit?: number; dirs?: boolean; type?: "file" | "directory" }) { - return runPromiseInstance(S.Service.use((svc) => svc.search(input))) + return runInstance((await svc()).use((s) => s.search(input))) } } diff --git a/packages/opencode/src/file/time.ts b/packages/opencode/src/file/time.ts index b6d572fe8b..45fc9dd22f 100644 --- a/packages/opencode/src/file/time.ts +++ b/packages/opencode/src/file/time.ts @@ -1,28 +1,28 @@ -import { runPromiseInstance } from "@/effect/runtime" +import { runInstance } from "@/effect/run" import type { SessionID } from "@/session/schema" import { FileTime as S } from "./time-service" +const svc = () => import("./time-service").then((m) => m.FileTime.Service) + export namespace FileTime { export type Stamp = S.Stamp - export type Interface = S.Interface - export const Service = S.Service export const layer = S.layer - export function read(sessionID: SessionID, file: string) { - return runPromiseInstance(S.Service.use((s) => s.read(sessionID, file))) + export async function read(sessionID: SessionID, file: string) { + return runInstance((await svc()).use((s) => s.read(sessionID, file))) } - export function get(sessionID: SessionID, file: string) { - return runPromiseInstance(S.Service.use((s) => s.get(sessionID, file))) + export async function get(sessionID: SessionID, file: string) { + return runInstance((await svc()).use((s) => s.get(sessionID, file))) } export async function assert(sessionID: SessionID, filepath: string) { - return runPromiseInstance(S.Service.use((s) => s.assert(sessionID, filepath))) + return runInstance((await svc()).use((s) => s.assert(sessionID, filepath))) } export async function withLock(filepath: string, fn: () => Promise): Promise { - return runPromiseInstance(S.Service.use((s) => s.withLock(filepath, fn))) + return runInstance((await svc()).use((s) => s.withLock(filepath, fn))) } } diff --git a/packages/opencode/src/format/index.ts b/packages/opencode/src/format/index.ts index e4381c69b2..33e3e53082 100644 --- a/packages/opencode/src/format/index.ts +++ b/packages/opencode/src/format/index.ts @@ -1,16 +1,9 @@ -import { runPromiseInstance } from "@/effect/runtime" -import { Format as S } from "./service" +import { runInstance } from "@/effect/run" + +const svc = () => import("./service").then((m) => m.Format.Service) export namespace Format { - export const Status = S.Status - export type Status = S.Status - - export type Interface = S.Interface - - export const Service = S.Service - export const layer = S.layer - export async function status() { - return runPromiseInstance(S.Service.use((s) => s.status())) + return runInstance((await svc()).use((s) => s.status())) } } diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts index 01ac768971..8229be5e13 100644 --- a/packages/opencode/src/permission/index.ts +++ b/packages/opencode/src/permission/index.ts @@ -1,52 +1,43 @@ -import { runPromiseInstance } from "@/effect/runtime" -import { fn } from "@/util/fn" -import z from "zod" +import { runInstance } from "@/effect/run" import { Permission as S } from "./service" +const svc = () => import("./service").then((m) => m.Permission.Service) + export namespace PermissionNext { export const Action = S.Action export type Action = S.Action - export const Rule = S.Rule export type Rule = S.Rule - - export const Ruleset = S.Ruleset export type Ruleset = S.Ruleset - export const Request = S.Request export type Request = S.Request - export const Reply = S.Reply export type Reply = S.Reply - export const Approval = S.Approval - export type Approval = z.infer - export const Event = S.Event - export const RejectedError = S.RejectedError export const CorrectedError = S.CorrectedError export const DeniedError = S.DeniedError export type Error = S.Error - export const AskInput = S.AskInput export const ReplyInput = S.ReplyInput - export type Interface = S.Interface - export const Service = S.Service export const layer = S.layer - export const evaluate = S.evaluate export const fromConfig = S.fromConfig export const merge = S.merge export const disabled = S.disabled - export const ask = fn(S.AskInput, async (input) => runPromiseInstance(S.Service.use((s) => s.ask(input)))) + export async function ask(input: S.AskInput) { + return runInstance((await svc()).use((s) => s.ask(input))) + } - export const reply = fn(S.ReplyInput, async (input) => runPromiseInstance(S.Service.use((s) => s.reply(input)))) + export async function reply(input: S.ReplyInput) { + return runInstance((await svc()).use((s) => s.reply(input))) + } export async function list() { - return runPromiseInstance(S.Service.use((s) => s.list())) + return runInstance((await svc()).use((s) => s.list())) } } diff --git a/packages/opencode/src/permission/service.ts b/packages/opencode/src/permission/service.ts index 08475520b2..0bbacf1932 100644 --- a/packages/opencode/src/permission/service.ts +++ b/packages/opencode/src/permission/service.ts @@ -105,16 +105,18 @@ export namespace Permission { export const AskInput = Request.partial({ id: true }).extend({ ruleset: Ruleset, }) + export type AskInput = z.infer export const ReplyInput = z.object({ requestID: PermissionID.zod, reply: Reply, message: z.string().optional(), }) + export type ReplyInput = z.infer export interface Interface { - readonly ask: (input: z.infer) => Effect.Effect - readonly reply: (input: z.infer) => Effect.Effect + readonly ask: (input: AskInput) => Effect.Effect + readonly reply: (input: ReplyInput) => Effect.Effect readonly list: () => Effect.Effect } @@ -140,7 +142,7 @@ export namespace Permission { const pending = new Map() const approved: Ruleset = row?.data ?? [] - const ask = Effect.fn("Permission.ask")(function* (input: z.infer) { + const ask = Effect.fn("Permission.ask")(function* (input: AskInput) { const { ruleset, ...request } = input let needsAsk = false @@ -176,7 +178,7 @@ export namespace Permission { ) }) - const reply = Effect.fn("Permission.reply")(function* (input: z.infer) { + const reply = Effect.fn("Permission.reply")(function* (input: ReplyInput) { const existing = pending.get(input.requestID) if (!existing) return diff --git a/packages/opencode/src/provider/auth.ts b/packages/opencode/src/provider/auth.ts index 8ede977a59..4e026a7dc0 100644 --- a/packages/opencode/src/provider/auth.ts +++ b/packages/opencode/src/provider/auth.ts @@ -1,30 +1,28 @@ -import { runPromiseInstance } from "@/effect/runtime" +import { runInstance } from "@/effect/run" import { fn } from "@/util/fn" import { ProviderID } from "./schema" import z from "zod" import { ProviderAuth as S } from "./auth-service" +const svc = () => import("./auth-service").then((m) => m.ProviderAuth.Service) + export namespace ProviderAuth { export const Method = S.Method export type Method = S.Method - export const Authorization = S.Authorization export type Authorization = S.Authorization - export const OauthMissing = S.OauthMissing export const OauthCodeMissing = S.OauthCodeMissing export const OauthCallbackFailed = S.OauthCallbackFailed export const ValidationFailed = S.ValidationFailed export type Error = S.Error - export type Interface = S.Interface - export const Service = S.Service export const layer = S.layer export const defaultLayer = S.defaultLayer export async function methods() { - return runPromiseInstance(S.Service.use((svc) => svc.methods())) + return runInstance((await svc()).use((s) => s.methods())) } export const authorize = fn( @@ -34,7 +32,7 @@ export namespace ProviderAuth { inputs: z.record(z.string(), z.string()).optional(), }), async (input): Promise => - runPromiseInstance(S.Service.use((svc) => svc.authorize(input))), + runInstance((await svc()).use((s) => s.authorize(input))), ) export const callback = fn( @@ -43,6 +41,6 @@ export namespace ProviderAuth { method: z.number(), code: z.string().optional(), }), - async (input) => runPromiseInstance(S.Service.use((svc) => svc.callback(input))), + async (input) => runInstance((await svc()).use((s) => s.callback(input))), ) } diff --git a/packages/opencode/src/question/index.ts b/packages/opencode/src/question/index.ts index de00951908..57b6494240 100644 --- a/packages/opencode/src/question/index.ts +++ b/packages/opencode/src/question/index.ts @@ -1,29 +1,24 @@ -import { runPromiseInstance } from "@/effect/runtime" +import { runInstance } from "@/effect/run" import type { MessageID, SessionID } from "@/session/schema" import type { QuestionID } from "./schema" import { Question as S } from "./service" -export namespace Question { - export const Option = S.Option - export type Option = S.Option +const svc = () => import("./service").then((m) => m.Question.Service) +export namespace Question { export const Info = S.Info export type Info = S.Info - export const Request = S.Request export type Request = S.Request - export const Answer = S.Answer export type Answer = S.Answer - export const Reply = S.Reply export type Reply = S.Reply - + export const Option = S.Option + export type Option = S.Option export const Event = S.Event export const RejectedError = S.RejectedError - export type Interface = S.Interface - export const Service = S.Service export const layer = S.layer @@ -32,18 +27,18 @@ export namespace Question { questions: Info[] tool?: { messageID: MessageID; callID: string } }): Promise { - return runPromiseInstance(S.Service.use((s) => s.ask(input))) + return runInstance((await svc()).use((s) => s.ask(input))) } export async function reply(input: { requestID: QuestionID; answers: Answer[] }) { - return runPromiseInstance(S.Service.use((s) => s.reply(input))) + return runInstance((await svc()).use((s) => s.reply(input))) } export async function reject(requestID: QuestionID) { - return runPromiseInstance(S.Service.use((s) => s.reject(requestID))) + return runInstance((await svc()).use((s) => s.reject(requestID))) } export async function list() { - return runPromiseInstance(S.Service.use((s) => s.list())) + return runInstance((await svc()).use((s) => s.list())) } } diff --git a/packages/opencode/src/server/routes/file.ts b/packages/opencode/src/server/routes/file.ts index 60789ef4b7..66d67e61ef 100644 --- a/packages/opencode/src/server/routes/file.ts +++ b/packages/opencode/src/server/routes/file.ts @@ -1,7 +1,8 @@ import { Hono } from "hono" import { describeRoute, validator, resolver } from "hono-openapi" import z from "zod" -import { File } from "../../file" +import { File } from "../../file/service" +import { File as FileApi } from "../../file" import { Ripgrep } from "../../file/ripgrep" import { LSP } from "../../lsp" import { Instance } from "../../project/instance" @@ -73,7 +74,7 @@ export const FileRoutes = lazy(() => const dirs = c.req.valid("query").dirs const type = c.req.valid("query").type const limit = c.req.valid("query").limit - const results = await File.search({ + const results = await FileApi.search({ query, limit: limit ?? 10, dirs: dirs !== "false", @@ -139,7 +140,7 @@ export const FileRoutes = lazy(() => ), async (c) => { const path = c.req.valid("query").path - const content = await File.list(path) + const content = await FileApi.list(path) return c.json(content) }, ) @@ -168,7 +169,7 @@ export const FileRoutes = lazy(() => ), async (c) => { const path = c.req.valid("query").path - const content = await File.read(path) + const content = await FileApi.read(path) return c.json(content) }, ) @@ -190,7 +191,7 @@ export const FileRoutes = lazy(() => }, }), async (c) => { - const content = await File.status() + const content = await FileApi.status() return c.json(content) }, ), diff --git a/packages/opencode/src/server/routes/permission.ts b/packages/opencode/src/server/routes/permission.ts index cc6c26d435..bf340042bd 100644 --- a/packages/opencode/src/server/routes/permission.ts +++ b/packages/opencode/src/server/routes/permission.ts @@ -1,6 +1,7 @@ import { Hono } from "hono" import { describeRoute, validator, resolver } from "hono-openapi" import z from "zod" +import { Permission } from "@/permission/service" import { PermissionNext } from "@/permission" import { PermissionID } from "@/permission/schema" import { errors } from "../error" @@ -32,7 +33,7 @@ export const PermissionRoutes = lazy(() => requestID: PermissionID.zod, }), ), - validator("json", z.object({ reply: PermissionNext.Reply, message: z.string().optional() })), + validator("json", z.object({ reply: Permission.Reply, message: z.string().optional() })), async (c) => { const params = c.req.valid("param") const json = c.req.valid("json") @@ -55,7 +56,7 @@ export const PermissionRoutes = lazy(() => description: "List of pending permissions", content: { "application/json": { - schema: resolver(PermissionNext.Request.array()), + schema: resolver(Permission.Request.array()), }, }, }, diff --git a/packages/opencode/src/server/routes/provider.ts b/packages/opencode/src/server/routes/provider.ts index 64fe34f450..4495451d28 100644 --- a/packages/opencode/src/server/routes/provider.ts +++ b/packages/opencode/src/server/routes/provider.ts @@ -4,7 +4,8 @@ import z from "zod" import { Config } from "../../config/config" import { Provider } from "../../provider/provider" import { ModelsDev } from "../../provider/models" -import { ProviderAuth } from "../../provider/auth" +import { ProviderAuth } from "../../provider/auth-service" +import { ProviderAuth as ProviderAuthApi } from "../../provider/auth" import { ProviderID } from "../../provider/schema" import { mapValues } from "remeda" import { errors } from "../error" @@ -81,7 +82,7 @@ export const ProviderRoutes = lazy(() => }, }), async (c) => { - return c.json(await ProviderAuth.methods()) + return c.json(await ProviderAuthApi.methods()) }, ) .post( @@ -118,7 +119,7 @@ export const ProviderRoutes = lazy(() => async (c) => { const providerID = c.req.valid("param").providerID const { method, inputs } = c.req.valid("json") - const result = await ProviderAuth.authorize({ + const result = await ProviderAuthApi.authorize({ providerID, method, inputs, @@ -160,7 +161,7 @@ export const ProviderRoutes = lazy(() => async (c) => { const providerID = c.req.valid("param").providerID const { method, code } = c.req.valid("json") - await ProviderAuth.callback({ + await ProviderAuthApi.callback({ providerID, method, code, diff --git a/packages/opencode/src/server/routes/question.ts b/packages/opencode/src/server/routes/question.ts index 3fff895fa6..357e51a396 100644 --- a/packages/opencode/src/server/routes/question.ts +++ b/packages/opencode/src/server/routes/question.ts @@ -2,7 +2,8 @@ import { Hono } from "hono" import { describeRoute, validator } from "hono-openapi" import { resolver } from "hono-openapi" import { QuestionID } from "@/question/schema" -import { Question } from "../../question" +import { Question } from "../../question/service" +import { Question as QuestionApi } from "../../question" import z from "zod" import { errors } from "../error" import { lazy } from "../../util/lazy" @@ -27,7 +28,7 @@ export const QuestionRoutes = lazy(() => }, }), async (c) => { - const questions = await Question.list() + const questions = await QuestionApi.list() return c.json(questions) }, ) @@ -59,7 +60,7 @@ export const QuestionRoutes = lazy(() => async (c) => { const params = c.req.valid("param") const json = c.req.valid("json") - await Question.reply({ + await QuestionApi.reply({ requestID: params.requestID, answers: json.answers, }) @@ -92,7 +93,7 @@ export const QuestionRoutes = lazy(() => ), async (c) => { const params = c.req.valid("param") - await Question.reject(params.requestID) + await QuestionApi.reject(params.requestID) return c.json(true) }, ), diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index a68becb1fb..07425c7762 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -8,13 +8,15 @@ import z from "zod" import { Provider } from "../provider/provider" import { NamedError } from "@opencode-ai/util/error" import { LSP } from "../lsp" +import { Format as FormatService } from "../format/service" import { Format } from "../format" import { TuiRoutes } from "./routes/tui" import { Instance } from "../project/instance" import { Vcs } from "../project/vcs" import { runPromiseInstance } from "@/effect/runtime" import { Agent } from "../agent/agent" -import { Skill } from "../skill/skill" +import { Skill as SkillService } from "../skill/service" +import { Skill } from "../skill" import { Auth } from "../auth" import { Flag } from "../flag/flag" import { Command } from "../command" @@ -444,7 +446,7 @@ export namespace Server { description: "List of skills", content: { "application/json": { - schema: resolver(Skill.Info.array()), + schema: resolver(SkillService.Info.array()), }, }, }, @@ -487,7 +489,7 @@ export namespace Server { description: "Formatter status", content: { "application/json": { - schema: resolver(Format.Status.array()), + schema: resolver(FormatService.Status.array()), }, }, }, diff --git a/packages/opencode/src/share/share-next.ts b/packages/opencode/src/share/share-next.ts index e911656c90..e331e8fc6a 100644 --- a/packages/opencode/src/share/share-next.ts +++ b/packages/opencode/src/share/share-next.ts @@ -45,7 +45,7 @@ export namespace ShareNext { }> { const headers: Record = {} - const active = Account.active() + const active = await Account.active() if (!active?.active_org_id) { const baseUrl = await Config.get().then((x) => x.enterprise?.url ?? "https://opncd.ai") return { headers, api: legacyApi, baseUrl } diff --git a/packages/opencode/src/skill/index.ts b/packages/opencode/src/skill/index.ts index 67bef3bd38..8dcf5d8044 100644 --- a/packages/opencode/src/skill/index.ts +++ b/packages/opencode/src/skill/index.ts @@ -1 +1,30 @@ -export * from "./skill" +import type { Agent } from "@/agent/agent" +import { runInstance } from "@/effect/run" + +const svc = () => import("./service").then((m) => m.Skill.Service) +const mod = () => import("./service").then((m) => m.Skill) + +export namespace Skill { + export type Info = import("./service").Skill.Info + export type Interface = import("./service").Skill.Interface + + export async function get(name: string) { + return runInstance((await svc()).use((s) => s.get(name))) + } + + export async function all() { + return runInstance((await svc()).use((s) => s.all())) + } + + export async function dirs() { + return runInstance((await svc()).use((s) => s.dirs())) + } + + export async function available(agent?: Agent.Info) { + return runInstance((await svc()).use((s) => s.available(agent))) + } + + export async function fmt(list: Info[], opts: { verbose: boolean }) { + return (await mod()).fmt(list, opts) + } +} diff --git a/packages/opencode/src/skill/skill.ts b/packages/opencode/src/skill/skill.ts deleted file mode 100644 index ed3e0a4b75..0000000000 --- a/packages/opencode/src/skill/skill.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { runPromiseInstance } from "@/effect/runtime" -import type { Agent } from "@/agent/agent" -import { Skill as S } from "./service" - -export namespace Skill { - export const Info = S.Info - export type Info = S.Info - - export const InvalidError = S.InvalidError - export const NameMismatchError = S.NameMismatchError - - export type Interface = S.Interface - - export const Service = S.Service - export const layer = S.layer - export const defaultLayer = S.defaultLayer - - export const fmt = S.fmt - - export async function get(name: string) { - return runPromiseInstance(S.Service.use((skill) => skill.get(name))) - } - - export async function all() { - return runPromiseInstance(S.Service.use((skill) => skill.all())) - } - - export async function dirs() { - return runPromiseInstance(S.Service.use((skill) => skill.dirs())) - } - - export async function available(agent?: Agent.Info) { - return runPromiseInstance(S.Service.use((skill) => skill.available(agent))) - } -} diff --git a/packages/opencode/src/snapshot/index.ts b/packages/opencode/src/snapshot/index.ts index 4f845ca2de..b955c0d8d0 100644 --- a/packages/opencode/src/snapshot/index.ts +++ b/packages/opencode/src/snapshot/index.ts @@ -1,44 +1,43 @@ -import { runPromiseInstance } from "@/effect/runtime" +import { runInstance } from "@/effect/run" import { Snapshot as S } from "./service" +const svc = () => import("./service").then((m) => m.Snapshot.Service) + export namespace Snapshot { export const Patch = S.Patch export type Patch = S.Patch - export const FileDiff = S.FileDiff export type FileDiff = S.FileDiff - export type Interface = S.Interface - export const Service = S.Service export const layer = S.layer export const defaultLayer = S.defaultLayer export async function cleanup() { - return runPromiseInstance(S.Service.use((svc) => svc.cleanup())) + return runInstance((await svc()).use((s) => s.cleanup())) } export async function track() { - return runPromiseInstance(S.Service.use((svc) => svc.track())) + return runInstance((await svc()).use((s) => s.track())) } export async function patch(hash: string) { - return runPromiseInstance(S.Service.use((svc) => svc.patch(hash))) + return runInstance((await svc()).use((s) => s.patch(hash))) } export async function restore(snapshot: string) { - return runPromiseInstance(S.Service.use((svc) => svc.restore(snapshot))) + return runInstance((await svc()).use((s) => s.restore(snapshot))) } export async function revert(patches: Patch[]) { - return runPromiseInstance(S.Service.use((svc) => svc.revert(patches))) + return runInstance((await svc()).use((s) => s.revert(patches))) } export async function diff(hash: string) { - return runPromiseInstance(S.Service.use((svc) => svc.diff(hash))) + return runInstance((await svc()).use((s) => s.diff(hash))) } export async function diffFull(from: string, to: string) { - return runPromiseInstance(S.Service.use((svc) => svc.diffFull(from, to))) + return runInstance((await svc()).use((s) => s.diffFull(from, to))) } } diff --git a/packages/opencode/test/share/share-next.test.ts b/packages/opencode/test/share/share-next.test.ts index 5be5d02450..fc8d511509 100644 --- a/packages/opencode/test/share/share-next.test.ts +++ b/packages/opencode/test/share/share-next.test.ts @@ -7,7 +7,7 @@ test("ShareNext.request uses legacy share API without active org account", async const originalActive = Account.active const originalConfigGet = Config.get - Account.active = mock(() => undefined) + Account.active = mock(async () => undefined) Config.get = mock(async () => ({ enterprise: { url: "https://legacy-share.example.com" } })) try { @@ -29,7 +29,7 @@ test("ShareNext.request uses org share API with auth headers when account is act const originalActive = Account.active const originalToken = Account.token - Account.active = mock(() => ({ + Account.active = mock(async () => ({ id: AccountID.make("account-1"), email: "user@example.com", url: "https://control.example.com", @@ -59,7 +59,7 @@ test("ShareNext.request fails when org account has no token", async () => { const originalActive = Account.active const originalToken = Account.token - Account.active = mock(() => ({ + Account.active = mock(async () => ({ id: AccountID.make("account-1"), email: "user@example.com", url: "https://control.example.com",