refactor(ai): align multimodal naming (#40073)

This commit is contained in:
Aiden Cline 2026-08-01 21:57:05 -05:00 committed by GitHub
commit 634b9b21dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
91 changed files with 563 additions and 500 deletions

View file

@ -2,7 +2,7 @@ import type { LanguageModelV3, LanguageModelV3StreamPart } from "@ai-sdk/provide
import { AISDK } from "@opencode-ai/core/aisdk"
import { Model } from "@opencode-ai/core/model"
import { Provider } from "@opencode-ai/core/provider"
import { LLM, LLMError, LLMEvent, Message } from "@opencode-ai/ai"
import { LLM, AIError, LLMEvent, Message } from "@opencode-ai/ai"
import { LLMClient, RequestExecutor } from "@opencode-ai/ai/route"
import { compileRequest } from "@opencode-ai/ai/route/client"
import { expect } from "bun:test"
@ -316,7 +316,7 @@ it.effect("keeps malformed provider-executed AI SDK input terminal", () =>
Effect.flip,
)
expect(error).toBeInstanceOf(LLMError)
expect(error).toBeInstanceOf(AIError)
expect(error.message).toContain("Invalid JSON input for aisdk tool call web_search")
}),
)

View file

@ -1,5 +1,5 @@
import { expect } from "bun:test"
import { Model } from "@opencode-ai/ai"
import { LanguageModel } from "@opencode-ai/ai"
import { OpenAIChat } from "@opencode-ai/ai/protocols"
import { TestLLM } from "@opencode-ai/ai/testing"
import { AISDK } from "@opencode-ai/core/aisdk"
@ -17,7 +17,7 @@ const selected = Info.make({
...Info.default(Provider.ID.make("test-provider"), ID.make("gemini")),
package: Provider.aisdk("@ai-sdk/mistral"),
})
const runtime = Model.make({ id: "gemini", provider: "test-provider", route: OpenAIChat.route })
const runtime = LanguageModel.make({ id: "gemini", provider: "test-provider", route: OpenAIChat.route })
const catalog = Layer.mock(Catalog.Service, {
provider: {

View file

@ -1,5 +1,5 @@
import { describe, expect } from "bun:test"
import { LLM, Model } from "@opencode-ai/ai"
import { LLM, LanguageModel } from "@opencode-ai/ai"
import { OpenAIChat } from "@opencode-ai/ai/protocols"
import { compileRequest } from "@opencode-ai/ai/route/client"
import { Effect } from "effect"
@ -439,7 +439,7 @@ describe("ModelResolver", () => {
body: { custom: true },
limits: { context: 100, output: 20 },
})
return Model.make({ id: modelID, provider: "package-provider", route: native.route })
return LanguageModel.make({ id: modelID, provider: "package-provider", route: native.route })
},
})
},
@ -481,7 +481,7 @@ describe("ModelResolver", () => {
model: (modelID, settings) => {
expect(settings).toMatchObject({ [key]: "oauth-token" })
expect(settings).not.toHaveProperty("apiKey")
return Model.make({ id: modelID, provider: "package-provider", route: native.route })
return LanguageModel.make({ id: modelID, provider: "package-provider", route: native.route })
},
}),
}),
@ -536,7 +536,7 @@ describe("ModelResolver", () => {
limits: { context: 100, output: 20 },
providerOptions,
})
return Model.make({ id: modelID, provider: "native-provider", route: native.route })
return LanguageModel.make({ id: modelID, provider: "native-provider", route: native.route })
},
})
},
@ -571,7 +571,7 @@ describe("ModelResolver", () => {
transforms: ["middle-out"],
provider: { sort: "price", only: ["anthropic"] },
})
return Model.make({ id: modelID, provider: "openrouter", route: OpenAIChat.route })
return LanguageModel.make({ id: modelID, provider: "openrouter", route: OpenAIChat.route })
},
}),
},
@ -632,7 +632,7 @@ describe("ModelResolver", () => {
headers: { "x-aisdk": "header" },
body: { custom: true },
})
return Model.make({
return LanguageModel.make({
id: runtime.modelID ?? runtime.id,
provider: runtime.providerID,
route: native.route,

View file

@ -1,9 +1,9 @@
import { describe, expect, test } from "bun:test"
import { GenerationOptions, LLM, LLMRequest, Message, Model, ToolDefinition } from "@opencode-ai/ai"
import { GenerationOptions, LLM, LLMRequest, Message, LanguageModel, ToolDefinition } from "@opencode-ai/ai"
import { OpenAIChat } from "@opencode-ai/ai/protocols"
import { PromptCacheDiagnostics } from "@opencode-ai/core/session/prompt-cache-diagnostics"
const model = Model.make({ id: "test", provider: "test", route: OpenAIChat.route })
const model = LanguageModel.make({ id: "test", provider: "test", route: OpenAIChat.route })
const tool = ToolDefinition.make({
name: "read",
description: "Read a file",

View file

@ -1,5 +1,5 @@
import { describe, expect } from "bun:test"
import { LLMClient, LLMEvent, Model, type LLMRequest } from "@opencode-ai/ai"
import { LLMClient, LLMEvent, LanguageModel, type LLMRequest } from "@opencode-ai/ai"
import { OpenAIChat } from "@opencode-ai/ai/protocols"
import { Config } from "@opencode-ai/core/config"
import { Database } from "@opencode-ai/core/database/database"
@ -25,7 +25,7 @@ import { DateTime, Effect, Layer, LayerMap, Stream } from "effect"
import { testEffect } from "./lib/effect"
const location = Location.Ref.make({ directory: AbsolutePath.make("/project") })
const model = Model.make({
const model = LanguageModel.make({
id: "summary-model",
provider: "test",
route: OpenAIChat.route.with({ limits: { context: 10_000, output: 1_000 } }),

View file

@ -1,5 +1,5 @@
import { expect, test } from "bun:test"
import { LLMClient, LLMEvent, Model, type LLMRequest } from "@opencode-ai/ai"
import { LLMClient, LLMEvent, LanguageModel, type LLMRequest } from "@opencode-ai/ai"
import { OpenAIChat } from "@opencode-ai/ai/protocols"
import { Config } from "@opencode-ai/core/config"
import { Database } from "@opencode-ai/core/database/database"
@ -28,7 +28,7 @@ import { asc, eq } from "drizzle-orm"
import { testEffect } from "./lib/effect"
let requests: LLMRequest[] = []
const model = Model.make({
const model = LanguageModel.make({
id: "summary-model",
provider: "test",
route: OpenAIChat.route.with({ limits: { context: 10_000, output: 1_000 } }),
@ -145,7 +145,7 @@ it.effect("auto compaction reserves a buffer below the prompt ceiling", () =>
})
const input = (tokens: number, limits: { context: number; input?: number; output: number }) => ({
session,
model: Model.make({
model: LanguageModel.make({
id: "test-model",
provider: "test-provider",
route: OpenAIChat.route.with({ limits }),

View file

@ -4,7 +4,7 @@ import {
ContentPolicyReason,
InvalidProviderOutputReason,
InvalidRequestReason,
LLMError,
AIError,
NoRouteReason,
ModelID,
ProviderID,
@ -23,10 +23,10 @@ import { Tool } from "@opencode-ai/schema/tool"
import { toSessionError } from "@opencode-ai/core/session/to-session-error"
import { SessionRunnerRetry } from "@opencode-ai/core/session/runner/retry"
const llm = (reason: LLMError["reason"]) => new LLMError({ module: "test", method: "stream", reason })
const llm = (reason: AIError["reason"]) => new AIError({ module: "test", method: "stream", reason })
describe("toSessionError", () => {
test("maps every LLM reason to the open wire type", () => {
test("maps every AI error reason to the open wire type", () => {
expect(toSessionError(llm(new RateLimitReason({ message: "rate", retryAfterMs: 123 })))).toEqual({
type: "provider.rate-limit",
message: "rate",

View file

@ -1,5 +1,5 @@
import { describe, expect, test } from "bun:test"
import { LLMError, TransportReason } from "@opencode-ai/ai"
import { AIError, TransportReason } from "@opencode-ai/ai"
import { Database } from "@opencode-ai/core/database/database"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { LayerNode } from "@opencode-ai/util/effect/layer-node"
@ -27,7 +27,7 @@ describe("SessionExecution lifecycle", () => {
expect(
SessionExecution.terminal(
Exit.fail(
new LLMError({
new AIError({
module: "test",
method: "stream",
reason: new TransportReason({ message: "Disconnected" }),

View file

@ -1,5 +1,13 @@
import { expect } from "bun:test"
import { LLMClient, LLMEvent, LLMResponse, Model, SystemPart, ToolDefinition, type LLMRequest } from "@opencode-ai/ai"
import {
LLMClient,
LLMEvent,
LLMResponse,
LanguageModel,
SystemPart,
ToolDefinition,
type LLMRequest,
} from "@opencode-ai/ai"
import { OpenAIChat } from "@opencode-ai/ai/protocols"
import { Agent } from "@opencode-ai/core/agent"
import { Database } from "@opencode-ai/core/database/database"
@ -46,7 +54,7 @@ const requests: LLMRequest[] = []
let instruction: string | Instructions.Unavailable = "Initial context"
const sessionID = SessionSchema.ID.make("ses_generate_test")
const model = Model.make({ id: "generate-model", provider: "test", route: OpenAIChat.route })
const model = LanguageModel.make({ id: "generate-model", provider: "test", route: OpenAIChat.route })
const client = Layer.mock(LLMClient.Service)({
stream: () => Stream.die(new Error("unused")),
generate: (request) =>

View file

@ -1,10 +1,10 @@
import { describe, expect, test } from "bun:test"
import {
LLMError,
AIError,
LLMEvent,
LLMRequest,
Message,
Model,
LanguageModel,
SystemPart,
ToolFailure,
TransportReason,
@ -130,25 +130,25 @@ const testLLM = TestLLM.layer({
}),
})
const client = TestLLM.clientLayer
const model = Model.make({ id: "fake-model", provider: "fake", route: OpenAIChat.route })
const model = LanguageModel.make({ id: "fake-model", provider: "fake", route: OpenAIChat.route })
const defaultSystem = PROMPT_DEFAULT
const replacementModel = Model.make({ id: "replacement", provider: "fake", route: OpenAIChat.route })
const compactModel = Model.make({
const replacementModel = LanguageModel.make({ id: "replacement", provider: "fake", route: OpenAIChat.route })
const compactModel = LanguageModel.make({
id: "compact",
provider: "fake",
route: OpenAIChat.route.with({ limits: { context: 4_000, output: 50 } }),
})
const fullOutputModel = Model.make({
const fullOutputModel = LanguageModel.make({
id: "full-output",
provider: "fake",
route: OpenAIChat.route.with({ limits: { context: 262_144, output: 262_144 } }),
})
const undersizedContextModel = Model.make({
const undersizedContextModel = LanguageModel.make({
id: "undersized-context",
provider: "fake",
route: OpenAIChat.route.with({ limits: { context: 1, output: 1_000 } }),
})
const recoveryModel = Model.make({
const recoveryModel = LanguageModel.make({
id: "recovery",
provider: "fake",
route: OpenAIChat.route.with({ limits: { context: 20_000, output: 1_000 } }),
@ -230,9 +230,7 @@ const permission = Layer.succeed(
)
const transformTools = (registry: Tool.Interface, tools: Readonly<Record<string, Info>>, options?: Tool.Options) =>
registry.transform((draft) =>
Object.entries(tools).forEach(([name, tool]) =>
draft.add({ ...tool, name, options: options ?? tool.options }),
),
Object.entries(tools).forEach(([name, tool]) => draft.add({ ...tool, name, options: options ?? tool.options })),
)
const echo = Layer.effectDiscard(
Tool.Service.use((registry) =>
@ -509,21 +507,21 @@ const setup = Effect.gen(function* () {
})
const providerUnavailable = () =>
new LLMError({
new AIError({
module: "test",
method: "stream",
reason: new TransportReason({ message: "Provider unavailable" }),
})
const invalidRequest = () =>
new LLMError({
new AIError({
module: "test",
method: "stream",
reason: new InvalidRequestReason({ message: "Invalid request" }),
})
const rateLimited = (retryAfterMs?: number) =>
new LLMError({
new AIError({
module: "test",
method: "stream",
reason: new RateLimitReason({ message: "Rate limited", retryAfterMs }),
@ -1307,7 +1305,7 @@ describe("SessionRunnerLLM", () => {
it.effect("uses the selected model family prompt when the agent does not override it", () =>
Effect.gen(function* () {
const session = yield* setup
currentModel = Model.make({ id: "gpt-5", provider: "openai", route: OpenAIChat.route })
currentModel = LanguageModel.make({ id: "gpt-5", provider: "openai", route: OpenAIChat.route })
yield* admit(session, "First")
yield* TestLLM.push(TestLLM.text("Done", "text-provider-prompt"))
@ -1323,7 +1321,7 @@ describe("SessionRunnerLLM", () => {
it.effect("uses the selected model family prompt when the agent system override is empty", () =>
Effect.gen(function* () {
const session = yield* setup
currentModel = Model.make({ id: "gpt-5", provider: "openai", route: OpenAIChat.route })
currentModel = LanguageModel.make({ id: "gpt-5", provider: "openai", route: OpenAIChat.route })
const agent = yield* Agent.Service
yield* agent.transform((editor) =>
editor.update(Agent.ID.make("build"), (agent) => {
@ -2149,7 +2147,7 @@ describe("SessionRunnerLLM", () => {
const session = yield* setupOverflowRecovery
yield* TestLLM.push(
Stream.fail(
new LLMError({
new AIError({
module: "test",
method: "stream",
reason: new InvalidRequestReason({
@ -4076,7 +4074,7 @@ describe("SessionRunnerLLM", () => {
it.effect("settles malformed streamed tool input before the provider failure", () =>
Effect.gen(function* () {
const session = yield* setup
const failure = new LLMError({
const failure = new AIError({
module: "test",
method: "stream",
reason: new InvalidProviderOutputReason({ message: "Invalid JSON input for tool call echo" }),
@ -4290,7 +4288,7 @@ describe("SessionRunnerLLM", () => {
it.effect("records malformed provider-executed input as executed", () =>
Effect.gen(function* () {
const session = yield* setup
const failure = new LLMError({
const failure = new AIError({
module: "test",
method: "stream",
reason: new InvalidProviderOutputReason({ message: "Invalid hosted tool input" }),
@ -4322,7 +4320,7 @@ describe("SessionRunnerLLM", () => {
it.effect("records a provider failure after malformed input", () =>
Effect.gen(function* () {
const session = yield* setup
const failure = new LLMError({
const failure = new AIError({
module: "test",
method: "stream",
reason: new InvalidProviderOutputReason({ message: "Provider failed after malformed input" }),

View file

@ -1,5 +1,5 @@
import { expect } from "bun:test"
import { LLMClient, LLMEvent, Model, type LLMRequest } from "@opencode-ai/ai"
import { LLMClient, LLMEvent, LanguageModel, type LLMRequest } from "@opencode-ai/ai"
import { OpenAIChat } from "@opencode-ai/ai/protocols"
import { Agent } from "@opencode-ai/core/agent"
import { Database } from "@opencode-ai/core/database/database"
@ -24,7 +24,7 @@ import { Deferred, Effect, Fiber, Layer, Stream } from "effect"
import { testEffect } from "./lib/effect"
let requests: LLMRequest[] = []
const model = Model.make({
const model = LanguageModel.make({
id: "title-model",
provider: "test",
route: OpenAIChat.route.with({ limits: { context: 10_000, output: 1_000 } }),
@ -77,14 +77,7 @@ const models = Layer.mock(SessionRunnerModel.Service)({
})
const it = testEffect(
AppNodeBuilder.build(
LayerNode.group([
Database.node,
Bus.node,
SessionProjector.node,
SessionStore.node,
Agent.node,
SessionTitle.node,
]),
LayerNode.group([Database.node, Bus.node, SessionProjector.node, SessionStore.node, Agent.node, SessionTitle.node]),
[
[llmClient, client],
[SessionRunnerModel.node, models],

View file

@ -17,6 +17,7 @@ import { Connection } from "@opencode-ai/schema/connection"
import { Credential } from "@opencode-ai/schema/credential"
import { FileSystem } from "@opencode-ai/schema/filesystem"
import { Integration } from "@opencode-ai/schema/integration"
import { AI } from "@opencode-ai/schema/ai"
import { LLM } from "@opencode-ai/schema/llm"
import { Permission } from "@opencode-ai/schema/permission"
import { Pty } from "@opencode-ai/schema/pty"
@ -34,7 +35,7 @@ test("Core reuses the canonical shared schemas", async () => {
coreFileSystem,
coreIntegration,
coreLocation,
coreLLM,
coreAI,
coreModel,
corePermission,
corePermissionV1,
@ -100,8 +101,8 @@ test("Core reuses the canonical shared schemas", async () => {
[coreIntegration.Inputs, Integration.Inputs],
[coreIntegration.Ref, Integration.Ref],
[coreLocation.Ref, Location.Ref],
[coreLLM.ProviderMetadata, LLM.ProviderMetadata],
[coreLLM.FinishReason, LLM.FinishReason],
[coreAI.ProviderMetadata, AI.ProviderMetadata],
[coreAI.FinishReason, LLM.FinishReason],
[coreModel.ID, Model.ID],
[coreModel.VariantID, Model.VariantID],
[coreModel.Ref, Model.Ref],