feat(core): add command registry (#30624)

This commit is contained in:
Dax
2026-06-04 02:57:43 -04:00
committed by GitHub
parent 70bb710715
commit 1ff19103a2
150 changed files with 4753 additions and 2657 deletions

View File

@@ -33,6 +33,7 @@ import { TestConfig } from "../fixture/config"
import { RuntimeFlags } from "@/effect/runtime-flags"
import { LLMEvent, Usage } from "@opencode-ai/llm"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { ModelV2 } from "@opencode-ai/core/model"
void Log.init({ print: false })
@@ -47,7 +48,7 @@ const summary = Layer.succeed(
const ref = {
providerID: ProviderV2.ID.make("test"),
modelID: ProviderV2.ModelID.make("test-model"),
modelID: ModelV2.ID.make("test-model"),
}
const usage = (input: ConstructorParameters<typeof Usage>[0]) => new Usage(input)

View File

@@ -16,6 +16,7 @@ import { provideInstance, provideTmpdirInstance, testInstanceStoreLayer, tmpdirS
import { testEffect } from "../lib/effect"
import { TestConfig } from "../fixture/config"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { ModelV2 } from "@opencode-ai/core/model"
const it = testEffect(Layer.mergeAll(CrossSpawnSpawner.defaultLayer, NodeFileSystem.layer, testInstanceStoreLayer))
@@ -77,7 +78,7 @@ function loaded(filepath: string): SessionV1.WithParts[] {
agent: "build",
model: {
providerID: ProviderV2.ID.make("anthropic"),
modelID: ProviderV2.ModelID.make("claude-sonnet-4-20250514"),
modelID: ModelV2.ID.make("claude-sonnet-4-20250514"),
},
},
parts: [

View File

@@ -25,6 +25,7 @@ import { MessageID, SessionID } from "../../src/session/schema"
import { TestInstance } from "../fixture/fixture"
import { testEffect } from "../lib/effect"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { ModelV2 } from "@opencode-ai/core/model"
const FIXTURES_DIR = path.join(import.meta.dir, "../fixtures/recordings")
@@ -368,7 +369,7 @@ const driveToolLoop = (scenario: RecordedScenario) =>
const stableID = scenario.stableID ?? scenario.providerID
const sessionID = SessionID.make(`session-recorded-${stableID}-loop`)
const modelID = ProviderV2.ModelID.make(model.id)
const modelID = ModelV2.ID.make(model.id)
const agent = {
name: "test",
mode: "primary",

View File

@@ -10,9 +10,10 @@ import type { Provider } from "@/provider/provider"
import { OAUTH_DUMMY_KEY } from "@/auth"
import { testEffect } from "../lib/effect"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { ModelV2 } from "@opencode-ai/core/model"
const baseModel: Provider.Model = {
id: ProviderV2.ModelID.make("gpt-5-mini"),
id: ModelV2.ID.make("gpt-5-mini"),
providerID: ProviderV2.ID.make("openai"),
api: {
id: "gpt-5-mini",

View File

@@ -26,6 +26,7 @@ import { Permission } from "@/permission"
import { LLMAISDK } from "@/session/llm/ai-sdk"
import { Session as SessionNs } from "@/session/session"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { ModelV2 } from "@opencode-ai/core/model"
type ConfigModel = NonNullable<NonNullable<ConfigV1.Info["provider"]>[string]["models"]>[string]
@@ -768,7 +769,7 @@ describe("session.llm.stream", () => {
const resolved = yield* Provider.use.getModel(
ProviderV2.ID.make(vivgridFixture.providerID),
ProviderV2.ModelID.make(fixture.model.id),
ModelV2.ID.make(fixture.model.id),
)
const sessionID = SessionID.make("session-test-1")
const agent = {
@@ -842,7 +843,7 @@ describe("session.llm.stream", () => {
const resolved = yield* Provider.use.getModel(
ProviderV2.ID.make(alibabaQwenFixture.providerID),
ProviderV2.ModelID.make(fixture.model.id),
ModelV2.ID.make(fixture.model.id),
)
const sessionID = SessionID.make("session-test-service-abort")
const agent = {
@@ -910,7 +911,7 @@ describe("session.llm.stream", () => {
const resolved = yield* Provider.use.getModel(
ProviderV2.ID.make(alibabaQwenFixture.providerID),
ProviderV2.ModelID.make(fixture.model.id),
ModelV2.ID.make(fixture.model.id),
)
const sessionID = SessionID.make("session-test-tools")
const agent = {
@@ -1013,7 +1014,7 @@ describe("session.llm.stream", () => {
]
const request = waitRequest("/responses", createEventResponse(responseChunks, true))
const resolved = yield* Provider.use.getModel(ProviderV2.ID.openai, ProviderV2.ModelID.make(model.id))
const resolved = yield* Provider.use.getModel(ProviderV2.ID.openai, ModelV2.ID.make(model.id))
const sessionID = SessionID.make("session-test-2")
const agent = {
name: "test",
@@ -1118,7 +1119,7 @@ describe("session.llm.stream", () => {
}),
)
const resolved = yield* Provider.use.getModel(ProviderV2.ID.openai, ProviderV2.ModelID.make(model.id))
const resolved = yield* Provider.use.getModel(ProviderV2.ID.openai, ModelV2.ID.make(model.id))
const sessionID = SessionID.make("session-test-native-flag-off")
const agent = {
name: "test",
@@ -1188,7 +1189,7 @@ describe("session.llm.stream", () => {
]
const request = waitRequest("/responses", createEventResponse(chunks, true))
const resolved = yield* Provider.use.getModel(ProviderV2.ID.openai, ProviderV2.ModelID.make(model.id))
const resolved = yield* Provider.use.getModel(ProviderV2.ID.openai, ModelV2.ID.make(model.id))
const sessionID = SessionID.make("session-test-native")
const agent = {
name: "test",
@@ -1272,7 +1273,7 @@ describe("session.llm.stream", () => {
}),
)
const resolved = yield* Provider.use.getModel(ProviderV2.ID.openai, ProviderV2.ModelID.make(model.id))
const resolved = yield* Provider.use.getModel(ProviderV2.ID.openai, ModelV2.ID.make(model.id))
const sessionID = SessionID.make("session-test-native-injected-tool")
const agent = {
name: "test",
@@ -1360,7 +1361,7 @@ describe("session.llm.stream", () => {
const request = waitRequest("/responses", createEventResponse(chunks, true))
let executed: unknown
const resolved = yield* Provider.use.getModel(ProviderV2.ID.openai, ProviderV2.ModelID.make(model.id))
const resolved = yield* Provider.use.getModel(ProviderV2.ID.openai, ModelV2.ID.make(model.id))
const sessionID = SessionID.make("session-test-native-tool")
const agent = {
name: "test",
@@ -1486,7 +1487,7 @@ describe("session.llm.stream", () => {
),
).toString("base64")}`
const resolved = yield* Provider.use.getModel(ProviderV2.ID.openai, ProviderV2.ModelID.make(model.id))
const resolved = yield* Provider.use.getModel(ProviderV2.ID.openai, ModelV2.ID.make(model.id))
const sessionID = SessionID.make("session-test-data-url")
const agent = {
name: "test",
@@ -1575,7 +1576,7 @@ describe("session.llm.stream", () => {
const resolved = yield* Provider.use.getModel(
ProviderV2.ID.make(minimaxFixture.providerID),
ProviderV2.ModelID.make(model.id),
ModelV2.ID.make(model.id),
)
const sessionID = SessionID.make("session-test-3")
const agent = {
@@ -1593,7 +1594,7 @@ describe("session.llm.stream", () => {
role: "user",
time: { created: Date.now() },
agent: agent.name,
model: { providerID: ProviderV2.ID.make("minimax"), modelID: ProviderV2.ModelID.make("MiniMax-M2.5") },
model: { providerID: ProviderV2.ID.make("minimax"), modelID: ModelV2.ID.make("MiniMax-M2.5") },
} satisfies SessionV1.User
yield* drain({
@@ -1672,7 +1673,7 @@ describe("session.llm.stream", () => {
const resolved = yield* Provider.use.getModel(
ProviderV2.ID.make("anthropic"),
ProviderV2.ModelID.make(model.id),
ModelV2.ID.make(model.id),
)
const sessionID = SessionID.make("session-test-anthropic-tools")
const agent = {
@@ -1874,7 +1875,7 @@ describe("session.llm.stream", () => {
const resolved = yield* Provider.use.getModel(
ProviderV2.ID.make(geminiFixture.providerID),
ProviderV2.ModelID.make(model.id),
ModelV2.ID.make(model.id),
)
const sessionID = SessionID.make("session-test-4")
const agent = {

View File

@@ -8,11 +8,12 @@ import type { Provider } from "@/provider/provider"
import { SessionID, MessageID, PartID } from "../../src/session/schema"
import { Question } from "../../src/question"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { ModelV2 } from "@opencode-ai/core/model"
const sessionID = SessionID.make("session")
const providerID = ProviderV2.ID.make("test")
const model: Provider.Model = {
id: ProviderV2.ModelID.make("test-model"),
id: ModelV2.ID.make("test-model"),
providerID,
api: {
id: "test-model",
@@ -67,7 +68,7 @@ function userInfo(id: string): SessionV1.User {
role: "user",
time: { created: 0 },
agent: "user",
model: { providerID, modelID: ProviderV2.ModelID.make("test") },
model: { providerID, modelID: ModelV2.ID.make("test") },
tools: {},
mode: "",
} as unknown as SessionV1.User
@@ -413,7 +414,7 @@ describe("session.message-v2.toModelMessage", () => {
test("preserves jpeg tool-result media for anthropic models", async () => {
const anthropicModel: Provider.Model = {
...model,
id: ProviderV2.ModelID.make("anthropic/claude-opus-4-7"),
id: ModelV2.ID.make("anthropic/claude-opus-4-7"),
providerID: ProviderV2.ID.make("anthropic"),
api: {
id: "claude-opus-4-7-20250805",
@@ -496,7 +497,7 @@ describe("session.message-v2.toModelMessage", () => {
test("moves bedrock pdf tool-result media into a separate user message", async () => {
const bedrockModel: Provider.Model = {
...model,
id: ProviderV2.ModelID.make("amazon-bedrock/anthropic.claude-sonnet-4-6"),
id: ModelV2.ID.make("amazon-bedrock/anthropic.claude-sonnet-4-6"),
providerID: ProviderV2.ID.make("amazon-bedrock"),
api: {
id: "anthropic.claude-sonnet-4-6",
@@ -1044,7 +1045,7 @@ describe("session.message-v2.toModelMessage", () => {
const assistantID = "m-assistant"
const openrouterModel: Provider.Model = {
...model,
id: ProviderV2.ModelID.make("deepseek/deepseek-v4-pro"),
id: ModelV2.ID.make("deepseek/deepseek-v4-pro"),
providerID: ProviderV2.ID.make("openrouter"),
api: {
id: "deepseek/deepseek-v4-pro",

View File

@@ -10,6 +10,7 @@ import { NotFoundError } from "@/storage/storage"
import * as Log from "@opencode-ai/core/util/log"
import { testEffect } from "../lib/effect"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { ModelV2 } from "@opencode-ai/core/model"
void Log.init({ print: false })
@@ -98,7 +99,7 @@ const addAssistant = Effect.fn("Test.addAssistant")(function* (
role: "assistant",
time: { created: Date.now() },
parentID,
modelID: ProviderV2.ModelID.make("test"),
modelID: ModelV2.ID.make("test"),
providerID: ProviderV2.ID.make("test"),
mode: "",
agent: "default",

View File

@@ -30,6 +30,7 @@ import { testEffect } from "../lib/effect"
import { raw, reply, TestLLMServer } from "../lib/llm-server"
import { RuntimeFlags } from "@/effect/runtime-flags"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { ModelV2 } from "@opencode-ai/core/model"
import { SessionEvent } from "@opencode-ai/core/session/event"
import { LLMEvent } from "@opencode-ai/llm"
@@ -46,7 +47,7 @@ const summary = Layer.succeed(
const ref = {
providerID: ProviderV2.ID.make("test"),
modelID: ProviderV2.ModelID.make("test-model"),
modelID: ModelV2.ID.make("test-model"),
}
const cfg = {

View File

@@ -57,6 +57,7 @@ import { awaitWithTimeout, pollWithTimeout, testEffect } from "../lib/effect"
import { reply, TestLLMServer } from "../lib/llm-server"
import { RuntimeFlags } from "@/effect/runtime-flags"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { ModelV2 } from "@opencode-ai/core/model"
void Log.init({ print: false })
@@ -71,7 +72,7 @@ const summary = Layer.succeed(
const ref = {
providerID: ProviderV2.ID.make("test"),
modelID: ProviderV2.ModelID.make("test-model"),
modelID: ModelV2.ID.make("test-model"),
}
function withSh<A, E, R>(fx: () => Effect.Effect<A, E, R>) {
@@ -759,7 +760,7 @@ it.instance("failed subtask preserves metadata on error tool state", () =>
expect(tool.state.metadata?.sessionId).toBeDefined()
expect(tool.state.metadata?.model).toEqual({
providerID: ProviderV2.ID.make("test"),
modelID: ProviderV2.ModelID.make("missing-model"),
modelID: ModelV2.ID.make("missing-model"),
})
}),
)
@@ -2213,7 +2214,7 @@ noLLMServer.instance(
const other = yield* prompt.prompt({
sessionID: session.id,
agent: "build",
model: { providerID: ProviderV2.ID.make("opencode"), modelID: ProviderV2.ModelID.make("kimi-k2.5-free") },
model: { providerID: ProviderV2.ID.make("opencode"), modelID: ModelV2.ID.make("kimi-k2.5-free") },
noReply: true,
parts: [{ type: "text", text: "hello" }],
})
@@ -2229,7 +2230,7 @@ noLLMServer.instance(
if (match.info.role !== "user") throw new Error("expected user message")
expect(match.info.model).toEqual({
providerID: ProviderV2.ID.make("test"),
modelID: ProviderV2.ModelID.make("test-model"),
modelID: ModelV2.ID.make("test-model"),
variant: "xhigh",
})
expect(match.info.model.variant).toBe("xhigh")

View File

@@ -14,6 +14,7 @@ import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
import { provideTmpdirInstance } from "../fixture/fixture"
import { testEffect } from "../lib/effect"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { ModelV2 } from "@opencode-ai/core/model"
void Log.init({ print: false })
@@ -33,7 +34,7 @@ const user = Effect.fn("test.user")(function* (sessionID: SessionID, agent = "de
role: "user" as const,
sessionID,
agent,
model: { providerID: ProviderV2.ID.make("openai"), modelID: ProviderV2.ModelID.make("gpt-4") },
model: { providerID: ProviderV2.ID.make("openai"), modelID: ModelV2.ID.make("gpt-4") },
time: { created: Date.now() },
})
})
@@ -49,7 +50,7 @@ const assistant = Effect.fn("test.assistant")(function* (sessionID: SessionID, p
path: { cwd: dir, root: dir },
cost: 0,
tokens: { output: 0, input: 0, reasoning: 0, cache: { read: 0, write: 0 } },
modelID: ProviderV2.ModelID.make("gpt-4"),
modelID: ModelV2.ID.make("gpt-4"),
providerID: ProviderV2.ID.make("openai"),
parentID,
time: { created: Date.now() },
@@ -117,7 +118,7 @@ describe("revert + compact workflow", () => {
agent: "default",
model: {
providerID: ProviderV2.ID.make("openai"),
modelID: ProviderV2.ModelID.make("gpt-4"),
modelID: ModelV2.ID.make("gpt-4"),
},
time: {
created: Date.now(),
@@ -149,7 +150,7 @@ describe("revert + compact workflow", () => {
reasoning: 0,
cache: { read: 0, write: 0 },
},
modelID: ProviderV2.ModelID.make("gpt-4"),
modelID: ModelV2.ID.make("gpt-4"),
providerID: ProviderV2.ID.make("openai"),
parentID: userMsg1.id,
time: {
@@ -174,7 +175,7 @@ describe("revert + compact workflow", () => {
agent: "default",
model: {
providerID: ProviderV2.ID.make("openai"),
modelID: ProviderV2.ModelID.make("gpt-4"),
modelID: ModelV2.ID.make("gpt-4"),
},
time: {
created: Date.now(),
@@ -206,7 +207,7 @@ describe("revert + compact workflow", () => {
reasoning: 0,
cache: { read: 0, write: 0 },
},
modelID: ProviderV2.ModelID.make("gpt-4"),
modelID: ModelV2.ID.make("gpt-4"),
providerID: ProviderV2.ID.make("openai"),
parentID: userMsg2.id,
time: {
@@ -279,7 +280,7 @@ describe("revert + compact workflow", () => {
agent: "default",
model: {
providerID: ProviderV2.ID.make("openai"),
modelID: ProviderV2.ModelID.make("gpt-4"),
modelID: ModelV2.ID.make("gpt-4"),
},
time: {
created: Date.now(),
@@ -311,7 +312,7 @@ describe("revert + compact workflow", () => {
reasoning: 0,
cache: { read: 0, write: 0 },
},
modelID: ProviderV2.ModelID.make("gpt-4"),
modelID: ModelV2.ID.make("gpt-4"),
providerID: ProviderV2.ID.make("openai"),
parentID: userMsg.id,
time: {

View File

@@ -1,6 +1,7 @@
import { describe, expect } from "bun:test"
import { SessionV1 } from "@opencode-ai/core/v1/session"
import { Database } from "@opencode-ai/core/database/database"
import { EventV2 } from "@opencode-ai/core/event"
import { SessionProjector } from "@opencode-ai/core/session/projector"
import { Deferred, Effect, Exit, Layer } from "effect"
import { Session as SessionNs } from "@/session/session"
@@ -14,6 +15,7 @@ import { Storage } from "@/storage/storage"
import { RuntimeFlags } from "@/effect/runtime-flags"
import { BackgroundJob } from "@/background/job"
import { EventV2Bridge } from "@/event-v2-bridge"
import { GlobalBus } from "@/bus/global"
void Log.init({ print: false })
@@ -101,6 +103,31 @@ describe("session.created event", () => {
yield* session.remove(info.id)
}),
)
it.instance("emits legacy global sync payload", () =>
Effect.gen(function* () {
const session = yield* SessionNs.Service
const received = yield* Deferred.make<{ syncEvent: EventV2.SerializedEvent }>()
const listener = (event: { payload: { type?: string; syncEvent?: EventV2.SerializedEvent } }) => {
if (event.payload.type === "sync" && event.payload.syncEvent)
Deferred.doneUnsafe(received, Effect.succeed({ syncEvent: event.payload.syncEvent }))
}
GlobalBus.on("event", listener)
yield* Effect.addFinalizer(() => Effect.sync(() => GlobalBus.off("event", listener)))
const info = yield* session.create({})
const event = yield* awaitDeferred(received, "timed out waiting for legacy global sync event")
expect(event.syncEvent).toMatchObject({
type: EventV2.versionedType(SessionNs.Event.Created.type, 1),
seq: 0,
aggregateID: info.id,
data: { sessionID: info.id },
})
yield* session.remove(info.id)
}),
)
})
describe("step-finish token propagation via event", () => {