refactor(core): move v1 schemas into core (#30473)

This commit is contained in:
Dax 2026-06-02 22:42:13 -04:00 committed by GitHub
parent 0543fd29c8
commit 83452558f7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
129 changed files with 1578 additions and 1227 deletions

12
packages/cli/src/api.ts Normal file
View file

@ -0,0 +1,12 @@
import { CliApi } from "./cli-api"
export const Api = CliApi.make("opencode", {
description: "OpenCode command line interface",
commands: [
CliApi.make("debug", {
description: "Debugging and troubleshooting tools",
commands: [CliApi.make("agents", { description: "List all agents" })],
}),
CliApi.make("migrate", { description: "Migrate v1 data to v2" }),
],
})

View file

@ -0,0 +1,40 @@
import * as Command from "effect/unstable/cli/Command"
type Options<Config extends Command.Command.Config, Commands extends ReadonlyArray<Any>> = {
readonly description?: string
readonly params?: Config
readonly commands?: Commands
}
export interface Node<
Name extends string,
Spec extends Command.Command<Name, any, any, any, any>,
Commands extends Children,
> {
readonly name: Name
readonly spec: Spec
readonly commands: Commands
}
export type Any = Node<string, Command.Command<any, any, any, any, any>, Children>
export type Children = Readonly<Record<string, Any>>
export function make<
const Name extends string,
const Config extends Command.Command.Config = {},
const Commands extends ReadonlyArray<Any> = [],
>(name: Name, options: Options<Config, Commands> = {}) {
const command = Command.make(name, options.params ?? ({} as Config))
const spec = options.description ? command.pipe(Command.withDescription(options.description)) : command
return {
name,
spec,
commands: Object.fromEntries((options.commands ?? []).map((command) => [command.name, command])) as ChildrenOf<Commands>,
}
}
type ChildrenOf<Commands extends ReadonlyArray<Any>> = {
readonly [Node in Commands[number] as Node["name"]]: Node
}
export * as CliApi from "./cli-api"

View file

@ -0,0 +1,66 @@
import * as Effect from "effect/Effect"
import * as Command from "effect/unstable/cli/Command"
import { CliApi } from "./cli-api"
export type Input<Value> = Value extends CliApi.Node<infer _Name, infer Spec, infer _Commands>
? Input<Spec>
: Value extends Command.Command<infer _Name, infer Input, infer _Context, infer _Error, infer _Requirements>
? Input
: never
type RuntimeHandler = (input: unknown) => Effect.Effect<void, unknown>
type Loader<Node extends CliApi.Any> = () => Promise<{ default: (input: Input<Node>) => Effect.Effect<void, any> }>
type ProvidedCommand = Command.Command<string, unknown, unknown, unknown, never>
export type Handlers<Node extends CliApi.Any> = keyof Node["commands"] extends never
? Loader<Node>
: { readonly $?: Loader<Node> } & { readonly [Key in keyof Node["commands"]]: Handlers<Node["commands"][Key]> }
interface LazyHandler {
readonly spec: Command.Command.Any
readonly load: () => Promise<{ default: RuntimeHandler }>
}
type RuntimeHandlers = (() => Promise<{ default: RuntimeHandler }>) | { readonly $?: () => Promise<{ default: RuntimeHandler }>; readonly [key: string]: RuntimeHandlers | (() => Promise<{ default: RuntimeHandler }>) | undefined }
export function handler<const Node extends CliApi.Any, Error>(
_node: Node,
run: (input: Input<Node>) => Effect.Effect<void, Error>,
) {
return run
}
export function handlers<const Root extends CliApi.Any>(root: Root, handlers: Handlers<Root>) {
const result: LazyHandler[] = []
function add(node: CliApi.Any, value: RuntimeHandlers) {
if (typeof value === "function") {
result.push({ spec: node.spec, load: value as () => Promise<{ default: RuntimeHandler }> })
return
}
if (value.$) result.push({ spec: node.spec, load: value.$ as () => Promise<{ default: RuntimeHandler }> })
for (const [name, child] of Object.entries(node.commands)) add(child, value[name] as RuntimeHandlers)
}
add(root, handlers as RuntimeHandlers)
return result
}
export function run(api: CliApi.Any, handlers: ReadonlyArray<LazyHandler>, options: { readonly version: string }) {
return Command.run(provide(api, handlers), options) as Effect.Effect<void, unknown, Command.Environment>
}
function provide(node: CliApi.Any, handlers: ReadonlyArray<LazyHandler>): ProvidedCommand {
const spec: Command.Command.Any = Object.keys(node.commands).length
? (node.spec as Command.Command<string, unknown>).pipe(
Command.withSubcommands(Object.values(node.commands).map((child) => provide(child, handlers))),
)
: node.spec
const handler = handlers.find((handler) => handler.spec === node.spec)
if (!handler) return spec as ProvidedCommand
return spec.pipe(
Command.withHandler((input) => Effect.flatMap(Effect.promise(handler.load), (module) => module.default(input))),
) as ProvidedCommand
}
export * as CliBuilder from "./cli-builder"

View file

@ -1,31 +0,0 @@
import { EOL } from "os"
import { AgentV2 } from "@opencode-ai/core/agent"
import { PluginBoot } from "@opencode-ai/core/plugin/boot"
import * as Effect from "effect/Effect"
import * as Command from "effect/unstable/cli/Command"
import { LocationServiceMap } from "@opencode-ai/core/location-layer"
import { AbsolutePath } from "@opencode-ai/core/schema"
export const AgentsCommand = Command.make("agents", {}, () =>
Effect.gen(function* () {
const svc = {
plugin: yield* PluginBoot.Service,
agent: yield* AgentV2.Service,
}
yield* svc.plugin.wait()
const agents = yield* svc.agent.all()
process.stdout.write(
JSON.stringify(
agents.sort((a, b) => a.id.localeCompare(b.id)),
null,
2,
) + EOL,
)
}).pipe(
Effect.provide(
LocationServiceMap.get({
directory: AbsolutePath.make(process.cwd()),
}),
),
),
).pipe(Command.withDescription("List all agents"))

View file

@ -1,7 +0,0 @@
import * as Command from "effect/unstable/cli/Command"
import { AgentsCommand } from "./agents"
export const DebugCommand = Command.make("debug").pipe(
Command.withDescription("Debugging and troubleshooting tools"),
Command.withSubcommands([AgentsCommand]),
)

View file

@ -0,0 +1,17 @@
import { EOL } from "os"
import { AgentV2 } from "@opencode-ai/core/agent"
import { LocationServiceMap } from "@opencode-ai/core/location-layer"
import { PluginBoot } from "@opencode-ai/core/plugin/boot"
import { AbsolutePath } from "@opencode-ai/core/schema"
import * as Effect from "effect/Effect"
import { Api } from "../../api"
import { CliBuilder } from "../../cli-builder"
export default CliBuilder.handler(Api.commands.debug.commands.agents, Effect.fn("cli.debug.agents")(function* () {
const svc = {
plugin: yield* PluginBoot.Service,
agent: yield* AgentV2.Service,
}
yield* svc.plugin.wait()
process.stdout.write(JSON.stringify((yield* svc.agent.all()).sort((a, b) => a.id.localeCompare(b.id)), null, 2) + EOL)
}, Effect.provide(LocationServiceMap.get({ directory: AbsolutePath.make(process.cwd()) })), Effect.provide(LocationServiceMap.layer)))

View file

@ -0,0 +1,5 @@
import * as Effect from "effect/Effect"
import { Api } from "../api"
import { CliBuilder } from "../cli-builder"
export default CliBuilder.handler(Api.commands.migrate, (_input) => Effect.log("No migrations to run."))

View file

@ -3,16 +3,18 @@
import * as NodeRuntime from "@effect/platform-node/NodeRuntime"
import * as NodeServices from "@effect/platform-node/NodeServices"
import * as Effect from "effect/Effect"
import * as Layer from "effect/Layer"
import * as Command from "effect/unstable/cli/Command"
import { DebugCommand } from "./debug"
import { LocationServiceMap } from "@opencode-ai/core/location-layer"
import { Api } from "./api"
import { CliBuilder } from "./cli-builder"
const cli = Command.make("opencode", {}, () => Effect.void).pipe(
Command.withDescription("OpenCode command line interface"),
Command.withSubcommands([DebugCommand]),
const Handlers = CliBuilder.handlers(Api, {
debug: {
agents: () => import("./handlers/debug/agents"),
},
migrate: () => import("./handlers/migrate"),
})
CliBuilder.run(Api, Handlers, { version: "local" }).pipe(
Effect.provide(NodeServices.layer),
Effect.scoped,
NodeRuntime.runMain,
)
const layer = Layer.mergeAll(LocationServiceMap.layer, NodeServices.layer)
Command.run(cli, { version: "local" }).pipe(Effect.provide(layer), Effect.scoped, NodeRuntime.runMain)