feat(core): port v2 command invocation (#34849)

This commit is contained in:
Aiden Cline 2026-07-02 11:22:04 -05:00 committed by GitHub
commit c176baee82
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 1445 additions and 179 deletions

View file

@ -1,12 +1,22 @@
import { describe, expect } from "bun:test"
import { Effect } from "effect"
import { CommandV2 } from "@opencode-ai/core/command"
import { Config } from "@opencode-ai/core/config"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { Location } from "@opencode-ai/core/location"
import { MCP } from "@opencode-ai/core/mcp/index"
import { ModelV2 } from "@opencode-ai/core/model"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { emptyConfigLayer, emptyMcpLayer, testLocationLayer } from "./fixture/mcp"
import { testEffect } from "./lib/effect"
const it = testEffect(AppNodeBuilder.build(CommandV2.node))
const it = testEffect(
AppNodeBuilder.build(CommandV2.node, [
[MCP.node, emptyMcpLayer],
[Config.node, emptyConfigLayer],
[Location.node, testLocationLayer],
]),
)
describe("CommandV2", () => {
it.effect("applies command transforms and preserves later overrides", () =>
@ -53,4 +63,18 @@ describe("CommandV2", () => {
])
}),
)
it.effect("evaluates command template shell blocks", () =>
Effect.gen(function* () {
const command = yield* CommandV2.Service
yield* command.transform((editor) => {
editor.update("review", (command) => {
command.template = "Output: !`printf command-output`"
})
})
expect(yield* command.evaluate({ name: "review" })).toEqual({ text: "Output: command-output" })
}),
)
})

View file

@ -8,14 +8,23 @@ import { ConfigCommandPlugin } from "@opencode-ai/core/config/plugin/command"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
import { FSUtil } from "@opencode-ai/core/fs-util"
import { Location } from "@opencode-ai/core/location"
import { MCP } from "@opencode-ai/core/mcp/index"
import { ModelV2 } from "@opencode-ai/core/model"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { AbsolutePath } from "@opencode-ai/core/schema"
import { emptyConfigLayer, emptyMcpLayer, testLocationLayer } from "../fixture/mcp"
import { tmpdir } from "../fixture/tmpdir"
import { testEffect } from "../lib/effect"
import { host } from "../plugin/host"
const it = testEffect(AppNodeBuilder.build(LayerNode.group([CommandV2.node, FSUtil.node])))
const it = testEffect(
AppNodeBuilder.build(LayerNode.group([CommandV2.node, FSUtil.node]), [
[MCP.node, emptyMcpLayer],
[Config.node, emptyConfigLayer],
[Location.node, testLocationLayer],
]),
)
const decode = Schema.decodeUnknownSync(Config.Info)
describe("ConfigCommandPlugin.Plugin", () => {

View file

@ -0,0 +1,30 @@
import { Effect, Layer } from "effect"
import { Config } from "@opencode-ai/core/config"
import { Location } from "@opencode-ai/core/location"
import { MCP } from "@opencode-ai/core/mcp/index"
import { AbsolutePath } from "@opencode-ai/core/schema"
import { location } from "./location"
export const emptyMcpLayer = Layer.succeed(
MCP.Service,
MCP.Service.of({
servers: () => Effect.succeed([]),
tools: () => Effect.succeed([]),
callTool: () => Effect.die("unused mcp.callTool"),
instructions: () => Effect.succeed([]),
prompts: () => Effect.succeed([]),
prompt: () => Effect.succeed(undefined),
resourceCatalog: () => Effect.succeed(new MCP.ResourceCatalog({ resources: [], templates: [] })),
readResource: () => Effect.succeed(undefined),
}),
)
export const emptyConfigLayer = Layer.succeed(
Config.Service,
Config.Service.of({ entries: () => Effect.succeed([]) }),
)
export const testLocationLayer = Layer.succeed(
Location.Service,
Location.Service.of(location({ directory: AbsolutePath.make(process.cwd()) })),
)

View file

@ -40,7 +40,6 @@ describe("CommandPlugin.Plugin", () => {
expect(yield* command.get("review")).toMatchObject({
name: "review",
description: "review changes [commit|branch|pr], defaults to uncommitted",
subtask: true,
})
}),
)

View file

@ -61,6 +61,7 @@ export function host(overrides: Overrides = {}): PluginContext {
create: () => Effect.die("unused session.create"),
get: () => Effect.die("unused session.get"),
prompt: () => Effect.die("unused session.prompt"),
command: () => Effect.die("unused session.command"),
interrupt: () => Effect.die("unused session.interrupt"),
},
}