refactor(core): consolidate tool architecture

This commit is contained in:
Dax Raad 2026-07-26 20:08:55 -04:00
commit 8db7487c89
466 changed files with 9405 additions and 11071 deletions

View file

@ -1,6 +1,6 @@
import { describe, expect } from "bun:test"
import { Effect, Layer } from "effect"
import { CommandV2 } from "@opencode-ai/core/command"
import { Command } from "@opencode-ai/core/command"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { Location } from "@opencode-ai/core/location"
import { CommandPlugin } from "@opencode-ai/core/plugin/command"
@ -15,12 +15,12 @@ const locationLayer = Layer.succeed(
Location.Service,
Location.Service.of(location({ directory }, { projectDirectory: project })),
)
const it = testEffect(AppNodeBuilder.build(CommandV2.node, [[Location.node, locationLayer]]))
const it = testEffect(AppNodeBuilder.build(Command.node, [[Location.node, locationLayer]]))
describe("CommandPlugin.Plugin", () => {
it.effect("registers built-in init and review commands", () =>
Effect.gen(function* () {
const command = yield* CommandV2.Service
const command = yield* Command.Service
yield* CommandPlugin.Plugin.effect(
host({
command: {

View file

@ -1,26 +1,25 @@
import { AgentV2 } from "@opencode-ai/core/agent"
import { Agent } from "@opencode-ai/core/agent"
import { AISDK } from "@opencode-ai/core/aisdk"
import { Catalog } from "@opencode-ai/core/catalog"
import { CommandV2 } from "@opencode-ai/core/command"
import { Command } from "@opencode-ai/core/command"
import { Config } from "@opencode-ai/core/config"
import { Credential } from "@opencode-ai/core/credential"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { LayerNodePlatform } from "@opencode-ai/util/effect/app-node-platform"
import { LayerNode } from "@opencode-ai/util/effect/layer-node"
import { EventV2 } from "@opencode-ai/core/event"
import { Bus } from "@opencode-ai/core/bus"
import { FileSystem } from "@opencode-ai/core/filesystem"
import { FSUtil } from "@opencode-ai/util/fs-util"
import { Form } from "@opencode-ai/core/form"
import { Integration } from "@opencode-ai/core/integration"
import { Location } from "@opencode-ai/core/location"
import { Npm } from "@opencode-ai/util/npm"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHooks } from "@opencode-ai/core/plugin/hooks"
import { PluginRuntime } from "@opencode-ai/core/plugin/runtime"
import { Reference } from "@opencode-ai/core/reference"
import { SkillV2 } from "@opencode-ai/core/skill"
import { ToolHooks } from "@opencode-ai/core/tool/hooks"
import { ToolRegistry } from "@opencode-ai/core/tool/registry"
import { Skill } from "@opencode-ai/core/skill"
import { Tool } from "@opencode-ai/core/tool"
import { WebSearch } from "@opencode-ai/core/websearch"
import { Effect, Layer } from "effect"
import { tempLocationLayer } from "../fixture/location"
@ -41,21 +40,21 @@ export const PluginTestLayer = AppNodeBuilder.build(
Location.node,
Npm.node,
Credential.node,
EventV2.node,
Bus.node,
Form.node,
LayerNodePlatform.httpClient,
PluginV2.node,
AgentV2.node,
Plugin.node,
Agent.node,
AISDK.node,
Catalog.node,
CommandV2.node,
Command.node,
Integration.node,
PluginRuntime.node,
PluginHooks.node,
Reference.node,
SkillV2.node,
ToolHooks.node,
ToolRegistry.toolsNode,
Skill.node,
PluginHooks.node,
Tool.node,
WebSearch.node,
]),
[

View file

@ -1,4 +1,4 @@
import { Plugin } from "@opencode-ai/plugin/v2/effect"
import { Plugin } from "@opencode-ai/plugin/effect"
import { Effect } from "effect"
export default Plugin.define({

View file

@ -1,4 +1,4 @@
import { Plugin } from "@opencode-ai/plugin/v2"
import { Plugin } from "@opencode-ai/plugin"
export default Plugin.define({
id: "config-promise-plugin",

View file

@ -1,4 +1,4 @@
import { Plugin } from "@opencode-ai/plugin/v2/effect"
import { Plugin } from "@opencode-ai/plugin/effect"
import { Effect } from "effect"
export default Plugin.define({

View file

@ -1,5 +1,6 @@
import { Plugin } from "@opencode-ai/plugin/v2/effect"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Plugin } from "@opencode-ai/plugin/effect"
import { Model } from "@opencode-ai/core/model"
import { Provider } from "@opencode-ai/core/provider"
import { Effect } from "effect"
export default Plugin.define({
@ -8,14 +9,14 @@ export default Plugin.define({
ctx.catalog
.transform((catalog) => {
catalog.provider.update("configured", (provider) => {
provider.package = ProviderV2.aisdk("@ai-sdk/openai-compatible")
provider.package = Provider.aisdk("@ai-sdk/openai-compatible")
})
catalog.model.update("configured", "glm-5.2", (model) => {
model.modelID = "glm-5.2"
model.package = ProviderV2.aisdk("@ai-sdk/openai-compatible")
model.modelID = Model.ID.make("glm-5.2")
model.package = Provider.aisdk("@ai-sdk/openai-compatible")
model.variants = [
{
id: "high",
id: Model.VariantID.make("high"),
settings: {},
headers: { custom: "true" },
body: {},

View file

@ -1,21 +1,15 @@
import { Plugin } from "@opencode-ai/plugin/v2/effect"
import { AgentV2 } from "@opencode-ai/core/agent"
import { Plugin } from "@opencode-ai/plugin/effect"
import type { IntegrationMethodRegistration } from "@opencode-ai/plugin/effect/integration"
import { Agent } from "@opencode-ai/core/agent"
import { Catalog } from "@opencode-ai/core/catalog"
import { Credential } from "@opencode-ai/core/credential"
import { Integration } from "@opencode-ai/core/integration"
import { Location } from "@opencode-ai/core/location"
import { ModelV2 } from "@opencode-ai/core/model"
import { Model } from "@opencode-ai/core/model"
import { Project } from "@opencode-ai/core/project"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Provider } from "@opencode-ai/core/provider"
import { AbsolutePath } from "@opencode-ai/core/schema"
import { WebSearch } from "@opencode-ai/core/websearch"
import type {
CredentialOAuth,
IntegrationCommandMethod,
IntegrationEnvMethod,
IntegrationKeyMethod,
IntegrationOAuthMethod,
} from "@opencode-ai/sdk/v2/types"
import { Effect, Stream } from "effect"
type Overrides = Partial<Omit<Plugin.Context, "options" | "session">> & {
@ -41,7 +35,6 @@ export function host(overrides: Overrides = {}): Plugin.Context {
get: () => Effect.die("unused catalog.provider.get"),
},
model: {
get: () => Effect.die("unused catalog.model.get"),
list: () => Effect.die("unused catalog.model.list"),
default: () => Effect.die("unused catalog.model.default"),
},
@ -116,9 +109,22 @@ export function host(overrides: Overrides = {}): Plugin.Context {
}
}
export function agentHost(agent: AgentV2.Interface): Plugin.Context["agent"] {
export function agentHost(agent: Agent.Interface): Plugin.Context["agent"] {
return {
get: (id) => agent.get(AgentV2.ID.make(id)).pipe(Effect.map((value) => value && agentInfo(value))),
get: (input) =>
agent.get(input.agentID).pipe(
Effect.flatMap((value) =>
value
? Effect.succeed({
location: new Location.Info({
directory: AbsolutePath.make("/"),
project: { id: Project.ID.make("test"), directory: AbsolutePath.make("/") },
}),
data: agentInfo(value),
})
: Effect.fail(new Error(`Agent not found: ${input.agentID}`)),
),
),
list: () => Effect.die("unused agent.list"),
reload: agent.reload,
transform: (callback) =>
@ -126,17 +132,17 @@ export function agentHost(agent: AgentV2.Interface): Plugin.Context["agent"] {
callback({
list: () => draft.list().map(agentInfo),
get: (id) => {
const value = draft.get(AgentV2.ID.make(id))
const value = draft.get(Agent.ID.make(id))
return value && agentInfo(value)
},
default: (id) => draft.default(id === undefined ? undefined : AgentV2.ID.make(id)),
default: (id) => draft.default(id === undefined ? undefined : Agent.ID.make(id)),
update: (id, update) =>
draft.update(AgentV2.ID.make(id), (value) => {
draft.update(Agent.ID.make(id), (value) => {
const current = agentInfo(value)
update(current)
Object.assign(value, current, { id: AgentV2.ID.make(current.id) })
Object.assign(value, current, { id: Agent.ID.make(current.id) })
}),
remove: (id) => draft.remove(AgentV2.ID.make(id)),
remove: (id) => draft.remove(Agent.ID.make(id)),
}),
),
}
@ -149,10 +155,6 @@ export function catalogHost(catalog: Catalog.Interface): Plugin.Context["catalog
get: () => Effect.die("unused catalog.provider.get"),
},
model: {
get: (providerID, modelID) =>
catalog.model
.get(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID))
.pipe(Effect.map((value) => value && modelInfo(value))),
list: () => Effect.die("unused catalog.model.list"),
default: () => Effect.die("unused catalog.model.default"),
},
@ -167,7 +169,7 @@ export function catalogHost(catalog: Catalog.Interface): Plugin.Context["catalog
models: new Map(Array.from(value.models, ([id, model]) => [id, modelInfo(model)])),
})),
get: (id) => {
const value = draft.provider.get(ProviderV2.ID.make(id))
const value = draft.provider.get(Provider.ID.make(id))
return (
value && {
provider: providerInfo(value.provider),
@ -176,41 +178,41 @@ export function catalogHost(catalog: Catalog.Interface): Plugin.Context["catalog
)
},
update: (id, update) =>
draft.provider.update(ProviderV2.ID.make(id), (value) => {
draft.provider.update(Provider.ID.make(id), (value) => {
const current = providerInfo(value)
update(current)
Object.assign(value, current, { id: ProviderV2.ID.make(current.id) })
Object.assign(value, current, { id: Provider.ID.make(current.id) })
}),
remove: (id) => draft.provider.remove(ProviderV2.ID.make(id)),
remove: (id) => draft.provider.remove(Provider.ID.make(id)),
},
model: {
get: (providerID, modelID) => {
const value = draft.model.get(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID))
const value = draft.model.get(Provider.ID.make(providerID), Model.ID.make(modelID))
return value && modelInfo(value)
},
update: (providerID, modelID, update) =>
draft.model.update(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID), (value) => {
draft.model.update(Provider.ID.make(providerID), Model.ID.make(modelID), (value) => {
const current = modelInfo(value)
update(current)
Object.assign(value, current, {
id: ModelV2.ID.make(current.id),
providerID: ProviderV2.ID.make(current.providerID),
family: current.family === undefined ? undefined : ModelV2.Family.make(current.family),
id: Model.ID.make(current.id),
providerID: Provider.ID.make(current.providerID),
family: current.family === undefined ? undefined : Model.Family.make(current.family),
variants: current.variants?.map((variant) => ({
...variant,
id: ModelV2.VariantID.make(variant.id),
id: Model.VariantID.make(variant.id),
})),
})
}),
remove: (providerID, modelID) =>
draft.model.remove(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID)),
draft.model.remove(Provider.ID.make(providerID), Model.ID.make(modelID)),
default: {
get: () => {
const value = draft.model.default.get()
return value && { providerID: value.providerID, modelID: value.modelID }
},
set: (providerID, modelID) =>
draft.model.default.set(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID)),
draft.model.default.set(Provider.ID.make(providerID), Model.ID.make(modelID)),
},
},
}),
@ -370,7 +372,7 @@ export function webSearchHost(websearch: WebSearch.Interface): Plugin.Context["w
}
}
function oauthCredential(value: CredentialOAuth) {
function oauthCredential(value: Credential.OAuth) {
return Credential.OAuth.make({ ...value, methodID: Integration.MethodID.make(value.methodID) })
}
@ -390,7 +392,7 @@ function method(value: Integration.Method) {
}
function internalMethod(
value: IntegrationOAuthMethod | IntegrationCommandMethod | IntegrationKeyMethod | IntegrationEnvMethod,
value: IntegrationMethodRegistration["method"],
): Integration.Method {
if (value.type === "env") return value
if (value.type === "key") return value
@ -407,7 +409,7 @@ function internalMethod(
}
}
function agentInfo(value: AgentV2.Info) {
function agentInfo(value: Agent.Info) {
return {
...value,
model: value.model && { ...value.model },
@ -420,7 +422,7 @@ function agentInfo(value: AgentV2.Info) {
}
}
function providerInfo(value: ProviderV2.MutableInfo) {
function providerInfo(value: Provider.MutableInfo) {
return {
...value,
settings: value.settings && { ...value.settings },
@ -429,7 +431,7 @@ function providerInfo(value: ProviderV2.MutableInfo) {
}
}
function modelInfo(value: ModelV2.Info | ModelV2.MutableInfo) {
function modelInfo(value: Model.Info | Model.MutableInfo) {
return {
...value,
settings: value.settings && { ...value.settings },

View file

@ -6,12 +6,12 @@ import { Catalog } from "@opencode-ai/core/catalog"
import { Integration } from "@opencode-ai/core/integration"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { LayerNode } from "@opencode-ai/util/effect/layer-node"
import { EventV2 } from "@opencode-ai/core/event"
import { Bus } from "@opencode-ai/core/bus"
import { Location } from "@opencode-ai/core/location"
import { ModelV2 } from "@opencode-ai/core/model"
import { Model } from "@opencode-ai/core/model"
import { ModelsDev } from "@opencode-ai/core/models-dev"
import { ModelsDevPlugin } from "@opencode-ai/core/plugin/models-dev"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Provider } from "@opencode-ai/core/provider"
import { AbsolutePath } from "@opencode-ai/core/schema"
import { location } from "../fixture/location"
import { testEffect } from "../lib/effect"
@ -21,7 +21,7 @@ const locationLayer = Layer.succeed(
Location.Service,
Location.Service.of(location({ directory: AbsolutePath.make(import.meta.dir) })),
)
const layer = AppNodeBuilder.build(LayerNode.group([Catalog.node, Integration.node, EventV2.node]), [
const layer = AppNodeBuilder.build(LayerNode.group([Catalog.node, Integration.node, Bus.node]), [
[Location.node, locationLayer],
])
const it = testEffect(layer)
@ -33,8 +33,8 @@ describe("ModelsDevPlugin", () => {
Effect.gen(function* () {
const integrations = yield* Integration.Service
const catalog = yield* Catalog.Service
const providerID = ProviderV2.ID.make("acme")
const modelID = ModelV2.ID.make("gpt-5.4")
const providerID = Provider.ID.make("acme")
const modelID = Model.ID.make("gpt-5.4")
const models = ModelsDev.Service.of({
get: () =>
Effect.succeed([
@ -42,7 +42,7 @@ describe("ModelsDevPlugin", () => {
info: {
id: providerID,
name: "Acme",
package: ProviderV2.aisdk("@ai-sdk/openai-compatible"),
package: Provider.aisdk("@ai-sdk/openai-compatible"),
settings: { baseURL: "https://api.acme.test/v1" },
},
environment: [],
@ -52,7 +52,7 @@ describe("ModelsDevPlugin", () => {
modelID,
providerID,
name: "GPT-5.4",
family: ModelV2.Family.make("gpt"),
family: Model.Family.make("gpt"),
capabilities: { tools: true, input: [], output: [] },
variants: [],
time: { released: Date.parse("2026-01-01") },
@ -89,12 +89,12 @@ describe("ModelsDevPlugin", () => {
limit: { context: 1_050_000, input: 922_000, output: 128_000 },
},
{
id: ModelV2.ID.make("gpt-5.4-fast"),
id: Model.ID.make("gpt-5.4-fast"),
modelID,
providerID,
name: "GPT-5.4 Fast",
family: ModelV2.Family.make("gpt"),
package: ProviderV2.aisdk("@ai-sdk/openai-compatible"),
family: Model.Family.make("gpt"),
package: Provider.aisdk("@ai-sdk/openai-compatible"),
settings: { baseURL: "https://api.acme.test/v1" },
headers: { "x-mode": "fast" },
body: { service_tier: "priority" },
@ -146,8 +146,8 @@ describe("ModelsDevPlugin", () => {
}),
).pipe(Effect.provideService(ModelsDev.Service, models))
const base = yield* catalog.model.get(providerID, ModelV2.ID.make("gpt-5.4"))
const fast = yield* catalog.model.get(providerID, ModelV2.ID.make("gpt-5.4-fast"))
const base = yield* catalog.model.get(providerID, Model.ID.make("gpt-5.4"))
const fast = yield* catalog.model.get(providerID, Model.ID.make("gpt-5.4-fast"))
expect(base?.variants).toEqual([])
expect(base?.body).toBeUndefined()
@ -156,7 +156,7 @@ describe("ModelsDevPlugin", () => {
modelID: "gpt-5.4",
providerID: "acme",
name: "GPT-5.4 Fast",
package: ProviderV2.aisdk("@ai-sdk/openai-compatible"),
package: Provider.aisdk("@ai-sdk/openai-compatible"),
settings: { baseURL: "https://api.acme.test/v1" },
headers: { "x-mode": "fast" },
body: { service_tier: "priority" },
@ -231,13 +231,13 @@ describe("ModelsDevPlugin", () => {
}),
)
const model = yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-reasoning"))
const model = yield* catalog.model.get(Provider.ID.openai, Model.ID.make("gpt-reasoning"))
expect(model?.variants?.map((variant) => variant.id)).toEqual([
ModelV2.VariantID.make("low"),
ModelV2.VariantID.make("high"),
Model.VariantID.make("low"),
Model.VariantID.make("high"),
])
expect(model?.variants).toContainEqual({
id: ModelV2.VariantID.make("low"),
id: Model.VariantID.make("low"),
settings: {
reasoningEffort: "low",
reasoningSummary: "auto",
@ -245,7 +245,7 @@ describe("ModelsDevPlugin", () => {
},
})
expect(model?.variants).toContainEqual({
id: ModelV2.VariantID.make("high"),
id: Model.VariantID.make("high"),
settings: {
reasoningEffort: "high",
reasoningSummary: "auto",
@ -253,7 +253,7 @@ describe("ModelsDevPlugin", () => {
},
})
const mode = yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-reasoning-high"))
const mode = yield* catalog.model.get(Provider.ID.openai, Model.ID.make("gpt-reasoning-high"))
expect(mode).toMatchObject({
id: "gpt-reasoning-high",
name: "GPT Reasoning High",
@ -261,229 +261,229 @@ describe("ModelsDevPlugin", () => {
body: { service_tier: "priority" },
})
expect(mode?.variants?.map((variant) => variant.id)).toEqual([
ModelV2.VariantID.make("low"),
ModelV2.VariantID.make("high"),
Model.VariantID.make("low"),
Model.VariantID.make("high"),
])
const pro = yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-reasoning-pro"))
const pro = yield* catalog.model.get(Provider.ID.openai, Model.ID.make("gpt-reasoning-pro"))
expect(pro).toMatchObject({
id: "gpt-reasoning-pro",
body: { reasoning: { mode: "pro" } },
})
const budgetModel = yield* catalog.model.get(ProviderV2.ID.anthropic, ModelV2.ID.make("claude-budget"))
const budgetModel = yield* catalog.model.get(Provider.ID.anthropic, Model.ID.make("claude-budget"))
expect(budgetModel?.variants).toContainEqual({
id: ModelV2.VariantID.make("high"),
id: Model.VariantID.make("high"),
settings: { thinking: { type: "enabled", budgetTokens: 16000 } },
})
expect(budgetModel?.variants).toContainEqual({
id: ModelV2.VariantID.make("max"),
id: Model.VariantID.make("max"),
settings: { thinking: { type: "enabled", budgetTokens: 31999 } },
})
const anthropicEffortModel = yield* catalog.model.get(ProviderV2.ID.anthropic, ModelV2.ID.make("claude-opus-4.7"))
const anthropicEffortModel = yield* catalog.model.get(Provider.ID.anthropic, Model.ID.make("claude-opus-4.7"))
expect(anthropicEffortModel?.variants).toEqual([
{ id: ModelV2.VariantID.make("none"), settings: { thinking: { type: "disabled" } } },
{ id: Model.VariantID.make("none"), settings: { thinking: { type: "disabled" } } },
{
id: ModelV2.VariantID.make("low"),
id: Model.VariantID.make("low"),
settings: { thinking: { type: "adaptive", display: "summarized" }, effort: "low" },
},
])
const anthropicToggleModel = yield* catalog.model.get(ProviderV2.ID.anthropic, ModelV2.ID.make("claude-toggle"))
const anthropicToggleModel = yield* catalog.model.get(Provider.ID.anthropic, Model.ID.make("claude-toggle"))
expect(anthropicToggleModel?.variants).toEqual([
{ id: ModelV2.VariantID.make("none"), settings: { thinking: { type: "disabled" } } },
{ id: Model.VariantID.make("none"), settings: { thinking: { type: "disabled" } } },
{
id: ModelV2.VariantID.make("thinking"),
id: Model.VariantID.make("thinking"),
settings: { thinking: { type: "adaptive", display: "summarized" } },
},
])
const opus45 = yield* catalog.model.get(ProviderV2.ID.anthropic, ModelV2.ID.make("claude-opus-4-5"))
const opus45 = yield* catalog.model.get(Provider.ID.anthropic, Model.ID.make("claude-opus-4-5"))
expect(opus45?.variants).toEqual([
{ id: ModelV2.VariantID.make("low"), settings: { effort: "low" } },
{ id: ModelV2.VariantID.make("high"), settings: { effort: "high" } },
{ id: Model.VariantID.make("low"), settings: { effort: "low" } },
{ id: Model.VariantID.make("high"), settings: { effort: "high" } },
])
const grok = yield* catalog.model.get(ProviderV2.ID.make("xai"), ModelV2.ID.make("grok-4.5"))
const grok = yield* catalog.model.get(Provider.ID.make("xai"), Model.ID.make("grok-4.5"))
expect(grok?.variants).toEqual(
["low", "medium", "high"].map((id) => ({
id: ModelV2.VariantID.make(id),
id: Model.VariantID.make(id),
settings: { reasoningEffort: id },
})),
)
const minimax = yield* catalog.model.get(ProviderV2.ID.make("opencode-go"), ModelV2.ID.make("minimax-m3"))
const minimax = yield* catalog.model.get(Provider.ID.make("opencode-go"), Model.ID.make("minimax-m3"))
expect(minimax?.variants).toEqual([
{ id: ModelV2.VariantID.make("none"), settings: { thinking: { type: "disabled" } } },
{ id: Model.VariantID.make("none"), settings: { thinking: { type: "disabled" } } },
{
id: ModelV2.VariantID.make("thinking"),
id: Model.VariantID.make("thinking"),
settings: { thinking: { type: "adaptive", display: "summarized" } },
},
])
const toggle = yield* catalog.model.get(ProviderV2.ID.make("alibaba"), ModelV2.ID.make("toggle-only"))
const toggle = yield* catalog.model.get(Provider.ID.make("alibaba"), Model.ID.make("toggle-only"))
expect(toggle?.variants).toEqual([
{ id: ModelV2.VariantID.make("none"), settings: { enableThinking: false } },
{ id: ModelV2.VariantID.make("thinking"), settings: { enableThinking: true } },
{ id: Model.VariantID.make("none"), settings: { enableThinking: false } },
{ id: Model.VariantID.make("thinking"), settings: { enableThinking: true } },
])
const combined = yield* catalog.model.get(ProviderV2.ID.make("alibaba"), ModelV2.ID.make("toggle-budget"))
const combined = yield* catalog.model.get(Provider.ID.make("alibaba"), Model.ID.make("toggle-budget"))
expect(combined?.variants).toEqual([
{ id: ModelV2.VariantID.make("none"), settings: { enableThinking: false } },
{ id: Model.VariantID.make("none"), settings: { enableThinking: false } },
{
id: ModelV2.VariantID.make("high"),
id: Model.VariantID.make("high"),
settings: { enableThinking: true, thinkingBudget: 8000 },
},
{
id: ModelV2.VariantID.make("max"),
id: Model.VariantID.make("max"),
settings: { enableThinking: true, thinkingBudget: 16000 },
},
])
const gateway = yield* catalog.model.get(ProviderV2.ID.make("vercel"), ModelV2.ID.make("alibaba/qwen-toggle"))
const gateway = yield* catalog.model.get(Provider.ID.make("vercel"), Model.ID.make("alibaba/qwen-toggle"))
expect(gateway?.variants).toEqual([
{ id: ModelV2.VariantID.make("none"), settings: { enableThinking: false } },
{ id: Model.VariantID.make("none"), settings: { enableThinking: false } },
{
id: ModelV2.VariantID.make("high"),
id: Model.VariantID.make("high"),
settings: { enableThinking: true, thinkingBudget: 8000 },
},
{
id: ModelV2.VariantID.make("max"),
id: Model.VariantID.make("max"),
settings: { enableThinking: true, thinkingBudget: 16000 },
},
])
const gatewayNova = yield* catalog.model.get(ProviderV2.ID.make("vercel"), ModelV2.ID.make("amazon/nova-2-lite"))
const gatewayNova = yield* catalog.model.get(Provider.ID.make("vercel"), Model.ID.make("amazon/nova-2-lite"))
expect(gatewayNova?.variants).toEqual([
{
id: ModelV2.VariantID.make("none"),
id: Model.VariantID.make("none"),
settings: { additionalModelRequestFields: { reasoningConfig: { type: "disabled" } } },
},
{
id: ModelV2.VariantID.make("low"),
id: Model.VariantID.make("low"),
settings: { reasoningConfig: { type: "enabled", maxReasoningEffort: "low" } },
},
{
id: ModelV2.VariantID.make("high"),
id: Model.VariantID.make("high"),
settings: { reasoningConfig: { type: "enabled", maxReasoningEffort: "high" } },
},
])
const gatewayFallback = yield* catalog.model.get(
ProviderV2.ID.make("vercel"),
ModelV2.ID.make("deepseek/deepseek-toggle"),
Provider.ID.make("vercel"),
Model.ID.make("deepseek/deepseek-toggle"),
)
expect(gatewayFallback?.variants).toEqual([
{
id: ModelV2.VariantID.make("none"),
id: Model.VariantID.make("none"),
settings: { reasoning: { enabled: false } },
},
{
id: ModelV2.VariantID.make("low"),
id: Model.VariantID.make("low"),
settings: { reasoningEffort: "low" },
},
{
id: ModelV2.VariantID.make("high"),
id: Model.VariantID.make("high"),
settings: { reasoningEffort: "high" },
},
])
const openrouter = yield* catalog.model.get(
ProviderV2.ID.make("openrouter"),
ModelV2.ID.make("openrouter-toggle"),
Provider.ID.make("openrouter"),
Model.ID.make("openrouter-toggle"),
)
expect(openrouter?.variants).toEqual([
{ id: ModelV2.VariantID.make("none"), settings: { reasoning: { enabled: false } } },
{ id: ModelV2.VariantID.make("thinking"), settings: { reasoning: { enabled: true } } },
{ id: Model.VariantID.make("none"), settings: { reasoning: { enabled: false } } },
{ id: Model.VariantID.make("thinking"), settings: { reasoning: { enabled: true } } },
])
const google = yield* catalog.model.get(ProviderV2.ID.make("google"), ModelV2.ID.make("gemini-2.5-flash"))
const google = yield* catalog.model.get(Provider.ID.make("google"), Model.ID.make("gemini-2.5-flash"))
expect(google?.variants).toEqual([
{
id: ModelV2.VariantID.make("none"),
id: Model.VariantID.make("none"),
settings: { thinkingConfig: { includeThoughts: false, thinkingBudget: 0 } },
},
{
id: ModelV2.VariantID.make("high"),
id: Model.VariantID.make("high"),
settings: { thinkingConfig: { includeThoughts: true, thinkingBudget: 8000 } },
},
{
id: ModelV2.VariantID.make("max"),
id: Model.VariantID.make("max"),
settings: { thinkingConfig: { includeThoughts: true, thinkingBudget: 16000 } },
},
])
const vertex = yield* catalog.model.get(
ProviderV2.ID.make("google-vertex"),
ModelV2.ID.make("gemini-2.5-flash-lite"),
Provider.ID.make("google-vertex"),
Model.ID.make("gemini-2.5-flash-lite"),
)
expect(vertex?.variants).toEqual([
{
id: ModelV2.VariantID.make("none"),
id: Model.VariantID.make("none"),
settings: { thinkingConfig: { includeThoughts: false, thinkingBudget: 0 } },
},
{
id: ModelV2.VariantID.make("high"),
id: Model.VariantID.make("high"),
settings: { thinkingConfig: { includeThoughts: true, thinkingBudget: 8000 } },
},
{
id: ModelV2.VariantID.make("max"),
id: Model.VariantID.make("max"),
settings: { thinkingConfig: { includeThoughts: true, thinkingBudget: 16000 } },
},
])
const bedrock = yield* catalog.model.get(
ProviderV2.ID.make("amazon-bedrock"),
ModelV2.ID.make("amazon.nova-2-lite-v1:0"),
Provider.ID.make("amazon-bedrock"),
Model.ID.make("amazon.nova-2-lite-v1:0"),
)
expect(bedrock?.variants).toEqual([
{
id: ModelV2.VariantID.make("none"),
id: Model.VariantID.make("none"),
settings: { additionalModelRequestFields: { reasoningConfig: { type: "disabled" } } },
},
{
id: ModelV2.VariantID.make("low"),
id: Model.VariantID.make("low"),
settings: { reasoningConfig: { type: "enabled", maxReasoningEffort: "low" } },
},
{
id: ModelV2.VariantID.make("high"),
id: Model.VariantID.make("high"),
settings: { reasoningConfig: { type: "enabled", maxReasoningEffort: "high" } },
},
])
const sapGemini = yield* catalog.model.get(ProviderV2.ID.make("sap-ai-core"), ModelV2.ID.make("gemini-2.5-flash"))
const sapGemini = yield* catalog.model.get(Provider.ID.make("sap-ai-core"), Model.ID.make("gemini-2.5-flash"))
expect(sapGemini?.variants).toEqual([
{
id: ModelV2.VariantID.make("none"),
id: Model.VariantID.make("none"),
settings: { modelParams: { thinkingConfig: { includeThoughts: false, thinkingBudget: 0 } } },
},
{
id: ModelV2.VariantID.make("high"),
id: Model.VariantID.make("high"),
settings: { modelParams: { thinkingConfig: { includeThoughts: true, thinkingBudget: 8000 } } },
},
{
id: ModelV2.VariantID.make("max"),
id: Model.VariantID.make("max"),
settings: { modelParams: { thinkingConfig: { includeThoughts: true, thinkingBudget: 16000 } } },
},
])
const sapNova = yield* catalog.model.get(ProviderV2.ID.make("sap-ai-core"), ModelV2.ID.make("amazon--nova-lite"))
const sapNova = yield* catalog.model.get(Provider.ID.make("sap-ai-core"), Model.ID.make("amazon--nova-lite"))
expect(sapNova?.variants).toEqual([
{
id: ModelV2.VariantID.make("none"),
id: Model.VariantID.make("none"),
settings: {
modelParams: { additionalModelRequestFields: { thinking: { type: "disabled" } } },
},
},
{
id: ModelV2.VariantID.make("low"),
id: Model.VariantID.make("low"),
settings: {
modelParams: { additionalModelRequestFields: { output_config: { effort: "low" } } },
},
},
{
id: ModelV2.VariantID.make("high"),
id: Model.VariantID.make("high"),
settings: {
modelParams: { additionalModelRequestFields: { output_config: { effort: "high" } } },
},
@ -491,31 +491,31 @@ describe("ModelsDevPlugin", () => {
])
const sapCohere = yield* catalog.model.get(
ProviderV2.ID.make("sap-ai-core"),
ModelV2.ID.make("cohere--command-a-reasoning"),
Provider.ID.make("sap-ai-core"),
Model.ID.make("cohere--command-a-reasoning"),
)
expect(sapCohere?.variants).toEqual([
{
id: ModelV2.VariantID.make("none"),
id: Model.VariantID.make("none"),
settings: { modelParams: { thinking: { type: "disabled" } } },
},
{
id: ModelV2.VariantID.make("low"),
id: Model.VariantID.make("low"),
settings: { modelParams: { reasoning_effort: "low" } },
},
{
id: ModelV2.VariantID.make("high"),
id: Model.VariantID.make("high"),
settings: { modelParams: { reasoning_effort: "high" } },
},
])
const sapAnthropicEffort = yield* catalog.model.get(
ProviderV2.ID.make("sap-ai-core"),
ModelV2.ID.make("anthropic--claude-4.7-opus"),
Provider.ID.make("sap-ai-core"),
Model.ID.make("anthropic--claude-4.7-opus"),
)
expect(sapAnthropicEffort?.variants).toEqual([
{
id: ModelV2.VariantID.make("low"),
id: Model.VariantID.make("low"),
settings: {
modelParams: {
additionalModelRequestFields: {
@ -528,12 +528,12 @@ describe("ModelsDevPlugin", () => {
])
const sapAnthropicBudget = yield* catalog.model.get(
ProviderV2.ID.make("sap-ai-core"),
ModelV2.ID.make("anthropic--claude-4-sonnet"),
Provider.ID.make("sap-ai-core"),
Model.ID.make("anthropic--claude-4-sonnet"),
)
expect(sapAnthropicBudget?.variants).toEqual([
{
id: ModelV2.VariantID.make("high"),
id: Model.VariantID.make("high"),
settings: {
modelParams: {
additionalModelRequestFields: { thinking: { type: "enabled", budget_tokens: 8000 } },
@ -541,7 +541,7 @@ describe("ModelsDevPlugin", () => {
},
},
{
id: ModelV2.VariantID.make("max"),
id: Model.VariantID.make("max"),
settings: {
modelParams: {
additionalModelRequestFields: { thinking: { type: "enabled", budget_tokens: 16000 } },

View file

@ -1,24 +1,21 @@
import { describe, expect } from "bun:test"
import { Message, SystemPart } from "@opencode-ai/ai"
import { DateTime, Effect, Schema } from "effect"
import { AgentV2 } from "@opencode-ai/core/agent"
import { Agent } from "@opencode-ai/core/agent"
import { Catalog } from "@opencode-ai/core/catalog"
import { ModelV2 } from "@opencode-ai/core/model"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { Model } from "@opencode-ai/core/model"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHooks } from "@opencode-ai/core/plugin/hooks"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { PluginPromise } from "@opencode-ai/core/plugin/promise"
import { WebSearch } from "@opencode-ai/core/websearch"
import { SessionV2 } from "@opencode-ai/core/session"
import { Session } from "@opencode-ai/core/session"
import { SessionMessage } from "@opencode-ai/core/session/message"
import { SessionPending } from "@opencode-ai/core/session/pending"
import { ToolRegistry } from "@opencode-ai/core/tool/registry"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Plugin } from "@opencode-ai/plugin/v2"
import { Tool } from "@opencode-ai/plugin/v2/tool"
import type { SessionHooks } from "@opencode-ai/plugin/v2/effect/session"
import { Model } from "@opencode-ai/schema/model"
import { Provider } from "@opencode-ai/schema/provider"
import { Tool } from "@opencode-ai/core/tool"
import { Provider } from "@opencode-ai/core/provider"
import { define } from "@opencode-ai/plugin/promise/plugin"
import type { SessionHooks } from "@opencode-ai/plugin/effect/session"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
import { host as testHost } from "./host"
@ -35,7 +32,7 @@ describe("fromPromise", () => {
})
yield* PluginPromise.fromPromise(
Plugin.define({
define({
id: "promise-session-generate",
setup: async (ctx) => {
expect(await ctx.session.generate({ sessionID: "ses_generate", prompt: "Summarize" })).toEqual({
@ -67,7 +64,7 @@ describe("fromPromise", () => {
SessionPending.Synthetic.make({
admittedSeq: 1,
id: SessionMessage.ID.make(input.id),
sessionID: SessionV2.ID.make(input.sessionID),
sessionID: Session.ID.make(input.sessionID),
timeCreated: DateTime.makeUnsafe(0),
type: "synthetic",
data: {
@ -82,7 +79,7 @@ describe("fromPromise", () => {
})
yield* PluginPromise.fromPromise(
Plugin.define({
define({
id: "promise-session-synthetic",
setup: async (ctx) => {
await ctx.session.synthetic(input)
@ -101,10 +98,10 @@ describe("fromPromise", () => {
it.effect("forwards standard client reads", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const host = yield* PluginHost.make(plugin)
const seen: string[] = []
const promisePlugin = Plugin.define({
const promisePlugin = define({
id: "promise-client-reads",
setup: async (ctx) => {
const results = await Promise.all([
@ -128,31 +125,36 @@ describe("fromPromise", () => {
}),
)
it.effect("forwards direct agent and model reads", () =>
it.effect("forwards direct agent and model list reads", () =>
Effect.gen(function* () {
const agents = yield* AgentV2.Service
const agents = yield* Agent.Service
const catalog = yield* Catalog.Service
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const host = yield* PluginHost.make(plugin)
yield* agents.transform((draft) =>
draft.update(AgentV2.ID.make("reviewer"), (agent) => {
draft.update(Agent.ID.make("reviewer"), (agent) => {
agent.description = "Reviews code"
}),
)
yield* catalog.transform((draft) =>
draft.model.update(ProviderV2.ID.make("test"), ModelV2.ID.make("alias"), (model) => {
model.modelID = ModelV2.ID.make("gpt-5")
draft.model.update(Provider.ID.make("test"), Model.ID.make("alias"), (model) => {
model.modelID = Model.ID.make("gpt-5")
}),
)
yield* PluginPromise.fromPromise(
Plugin.define({
define({
id: "promise-direct-reads",
setup: async (ctx) => {
expect(await ctx.agent.get("reviewer")).toMatchObject({ description: "Reviews code" })
expect(await ctx.agent.get("missing")).toBeUndefined()
expect(await ctx.catalog.model.get("test", "alias")).toMatchObject({ modelID: "gpt-5" })
expect(await ctx.catalog.model.get("test", "missing")).toBeUndefined()
expect((await ctx.agent.get({ agentID: Agent.ID.make("reviewer") })).data).toMatchObject({
description: "Reviews code",
})
await expect(ctx.agent.get({ agentID: Agent.ID.make("missing") })).rejects.toThrow("Agent not found: missing")
const models = (await ctx.catalog.model.list()).data
expect(models.find((model) => model.providerID === "test" && model.id === "alias")).toMatchObject({
modelID: "gpt-5",
})
expect(models.find((model) => model.providerID === "test" && model.id === "missing")).toBeUndefined()
},
}),
).effect(host)
@ -161,11 +163,11 @@ describe("fromPromise", () => {
it.effect("loads a promise plugin and registers a transform hook", () =>
Effect.gen(function* () {
const agents = yield* AgentV2.Service
const plugin = yield* PluginV2.Service
const agents = yield* Agent.Service
const plugin = yield* Plugin.Service
const host = yield* PluginHost.make(plugin)
const promisePlugin = Plugin.define({
const promisePlugin = define({
id: "promise-example",
setup: async (ctx) => {
expect(ctx.options.mode).toBe("strict")
@ -181,7 +183,7 @@ describe("fromPromise", () => {
const adapted = PluginPromise.fromPromise(promisePlugin)
yield* adapted.effect({ ...host, options: { mode: "strict" } })
expect(yield* agents.get(AgentV2.ID.make("reviewer"))).toMatchObject({
expect(yield* agents.get(Agent.ID.make("reviewer"))).toMatchObject({
description: "Reviews code",
mode: "subagent",
})
@ -190,11 +192,11 @@ describe("fromPromise", () => {
it.effect("forwards session context hooks", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const hooks = yield* PluginHooks.Service
const host = yield* PluginHost.make(plugin)
yield* PluginPromise.fromPromise(
Plugin.define({
define({
id: "promise-session-context",
setup: async (ctx) => {
await ctx.session.hook("context", (event) => {
@ -205,8 +207,8 @@ describe("fromPromise", () => {
}),
).effect(host)
const event: SessionHooks["context"] = {
sessionID: SessionV2.ID.make("ses_promise_session_context"),
agent: AgentV2.ID.make("build"),
sessionID: Session.ID.make("ses_promise_session_context"),
agent: Agent.ID.make("build"),
model: Model.Ref.make({ providerID: Provider.ID.make("test"), id: Model.ID.make("model") }),
system: [SystemPart.make("Initial")],
messages: [Message.user("Hello")],
@ -222,11 +224,11 @@ describe("fromPromise", () => {
it.effect("disposes a hook registration on request", () =>
Effect.gen(function* () {
const agents = yield* AgentV2.Service
const plugin = yield* PluginV2.Service
const agents = yield* Agent.Service
const plugin = yield* Plugin.Service
const host = yield* PluginHost.make(plugin)
const promisePlugin = Plugin.define({
const promisePlugin = define({
id: "promise-dispose",
setup: async (ctx) => {
const registration = await ctx.agent.transform((draft) => {
@ -241,16 +243,16 @@ describe("fromPromise", () => {
const adapted = PluginPromise.fromPromise(promisePlugin)
yield* adapted.effect(host)
expect(yield* agents.get(AgentV2.ID.make("temp"))).toBeUndefined()
expect(yield* agents.get(Agent.ID.make("temp"))).toBeUndefined()
}),
)
it.effect("registers a standalone web search provider", () =>
Effect.gen(function* () {
const websearch = yield* WebSearch.Service
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const host = yield* PluginHost.make(plugin)
const promisePlugin = Plugin.define({
const promisePlugin = define({
id: "promise-websearch",
setup: async (ctx) => {
await ctx.websearch.transform((draft) => {
@ -279,10 +281,10 @@ describe("fromPromise", () => {
it.effect("runs the setup cleanup when the plugin scope closes", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const host = yield* PluginHost.make(plugin)
const events: string[] = []
const promisePlugin = Plugin.define({
const promisePlugin = define({
id: "promise-cleanup",
setup: async () => {
events.push("setup")
@ -306,17 +308,18 @@ describe("fromPromise", () => {
it.effect("constructs plain Promise tool definitions in the host", () =>
Effect.gen(function* () {
const plugins = yield* PluginV2.Service
const registry = yield* ToolRegistry.Service
const plugins = yield* Plugin.Service
const registry = yield* Tool.Service
const host = yield* PluginHost.make(plugins)
const progress: ToolRegistry.Progress[] = []
const promisePlugin = Plugin.define({
const progress: Tool.Metadata[] = []
const promisePlugin = define({
id: "promise-tool",
setup: async (ctx) => {
await ctx.tool.transform((tools) => {
tools.add(
"hello",
Tool.make({
{
name: "hello",
options: { codemode: false },
description: "Hello",
input: Schema.Struct({ name: Schema.String }),
output: Schema.String,
@ -324,8 +327,7 @@ describe("fromPromise", () => {
await context.progress({ phase: "greeting" })
return { output: `Hello, ${name}!` }
},
}),
{ codemode: false },
},
)
})
},
@ -337,8 +339,8 @@ describe("fromPromise", () => {
expect(toolSet.definitions).toContainEqual(expect.objectContaining({ name: "hello", description: "Hello" }))
expect(
yield* toolSet.execute({
sessionID: SessionV2.ID.make("ses_promise_tool"),
agent: AgentV2.ID.make("build"),
sessionID: Session.ID.make("ses_promise_tool"),
agent: Agent.ID.make("build"),
messageID: SessionMessage.ID.make("msg_promise_tool"),
progress: (update) => Effect.sync(() => progress.push(update)),
call: { type: "tool-call", id: "call_promise_tool", name: "hello", input: { name: "world" } },

View file

@ -3,18 +3,18 @@ import { describe, expect } from "bun:test"
import type { LanguageModelV3 } from "@ai-sdk/provider"
import { Effect } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { ModelV2 } from "@opencode-ai/core/model"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { Model } from "@opencode-ai/core/model"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { AmazonBedrockPlugin } from "@opencode-ai/core/plugin/provider/amazon-bedrock"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Provider } from "@opencode-ai/core/provider"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const host = yield* PluginHost.make(plugin)
yield* AmazonBedrockPlugin.effect(host)
@ -83,9 +83,9 @@ describe("AmazonBedrockPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
const bedrock = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.amazonBedrock),
package: ProviderV2.aisdk("@ai-sdk/amazon-bedrock"),
const bedrock = Provider.Info.make({
...Provider.Info.empty(Provider.ID.amazonBedrock),
package: Provider.aisdk("@ai-sdk/amazon-bedrock"),
settings: { endpoint: "https://bedrock.example" },
})
catalog.provider.update(bedrock.id, (item) => {
@ -94,8 +94,8 @@ describe("AmazonBedrockPlugin", () => {
})
})
yield* addPlugin()
const result = required(yield* catalog.provider.get(ProviderV2.ID.amazonBedrock))
expect(result.package).toBe(ProviderV2.aisdk("@ai-sdk/amazon-bedrock"))
const result = required(yield* catalog.provider.get(Provider.ID.amazonBedrock))
expect(result.package).toBe(Provider.aisdk("@ai-sdk/amazon-bedrock"))
expect(result.settings).toEqual({ baseURL: "https://bedrock.example" })
}),
)
@ -103,14 +103,14 @@ describe("AmazonBedrockPlugin", () => {
it.effect("prefers endpoint over baseURL for SDK base URL", () =>
withEnv({ AWS_BEARER_TOKEN_BEDROCK: undefined, AWS_PROFILE: undefined, AWS_ACCESS_KEY_ID: undefined }, () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
package: "@ai-sdk/amazon-bedrock",
options: {
@ -129,14 +129,14 @@ describe("AmazonBedrockPlugin", () => {
it.effect("uses baseURL as SDK base URL", () =>
withEnv({ AWS_BEARER_TOKEN_BEDROCK: undefined, AWS_PROFILE: undefined, AWS_ACCESS_KEY_ID: undefined }, () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
package: "@ai-sdk/amazon-bedrock",
options: {
@ -164,14 +164,14 @@ describe("AmazonBedrockPlugin", () => {
},
() =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
package: "@ai-sdk/amazon-bedrock",
options: { name: "amazon-bedrock" },
@ -185,14 +185,14 @@ describe("AmazonBedrockPlugin", () => {
it.effect("uses config region over AWS_REGION for SDK base URL", () =>
withEnv({ AWS_BEARER_TOKEN_BEDROCK: "token", AWS_REGION: "us-east-1" }, () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
package: "@ai-sdk/amazon-bedrock",
options: { name: "amazon-bedrock", region: "eu-west-1" },
@ -205,14 +205,14 @@ describe("AmazonBedrockPlugin", () => {
it.effect("uses AWS_REGION for SDK base URL when config region is absent", () =>
withEnv({ AWS_BEARER_TOKEN_BEDROCK: "token", AWS_REGION: "eu-west-1" }, () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
package: "@ai-sdk/amazon-bedrock",
options: { name: "amazon-bedrock" },
@ -225,14 +225,14 @@ describe("AmazonBedrockPlugin", () => {
it.effect("defaults SDK region to us-east-1", () =>
withEnv({ AWS_BEARER_TOKEN_BEDROCK: "token", AWS_REGION: undefined }, () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
package: "@ai-sdk/amazon-bedrock",
options: { name: "amazon-bedrock" },
@ -245,15 +245,15 @@ describe("AmazonBedrockPlugin", () => {
it.effect("loads bearer token option into env and uses bearer auth", () =>
withEnv({ AWS_ACCESS_KEY_ID: undefined, AWS_BEARER_TOKEN_BEDROCK: undefined, AWS_PROFILE: undefined }, () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const headers: Array<string | null> = []
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
package: "@ai-sdk/amazon-bedrock",
options: {
@ -275,15 +275,15 @@ describe("AmazonBedrockPlugin", () => {
it.effect("prefers bearer token env over bearer token option", () =>
withEnv({ AWS_BEARER_TOKEN_BEDROCK: "env-token" }, () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const headers: Array<string | null> = []
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
package: "@ai-sdk/amazon-bedrock",
options: {
@ -305,14 +305,14 @@ describe("AmazonBedrockPlugin", () => {
it.effect("creates Mantle SDK with GPT-5 OpenAI base path", () =>
withEnv({ AWS_BEARER_TOKEN_BEDROCK: undefined, AWS_PROFILE: undefined, AWS_ACCESS_KEY_ID: undefined }, () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("openai.gpt-5.5")),
modelID: ModelV2.ID.make("openai.gpt-5.5"),
package: ProviderV2.aisdk("@ai-sdk/amazon-bedrock/mantle"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("openai.gpt-5.5")),
modelID: Model.ID.make("openai.gpt-5.5"),
package: Provider.aisdk("@ai-sdk/amazon-bedrock/mantle"),
}),
package: "@ai-sdk/amazon-bedrock/mantle",
options: {
@ -332,24 +332,24 @@ describe("AmazonBedrockPlugin", () => {
it.effect("selects Mantle APIs without Bedrock cross-region prefixes", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("openai.gpt-5.5")),
modelID: ModelV2.ID.make("openai.gpt-5.5"),
package: ProviderV2.aisdk("@ai-sdk/amazon-bedrock/mantle"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("openai.gpt-5.5")),
modelID: Model.ID.make("openai.gpt-5.5"),
package: Provider.aisdk("@ai-sdk/amazon-bedrock/mantle"),
}),
sdk: fakeSelectorSdk(calls),
options: { baseURL: "https://bedrock-mantle.us-east-2.api.aws/openai/v1", region: "us-east-2" },
})
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("openai.gpt-oss-safeguard-120b")),
modelID: ModelV2.ID.make("openai.gpt-oss-safeguard-120b"),
package: ProviderV2.aisdk("@ai-sdk/amazon-bedrock/mantle"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("openai.gpt-oss-safeguard-120b")),
modelID: Model.ID.make("openai.gpt-oss-safeguard-120b"),
package: Provider.aisdk("@ai-sdk/amazon-bedrock/mantle"),
}),
sdk: fakeSelectorSdk(calls),
options: { region: "us-east-1" },
@ -360,14 +360,14 @@ describe("AmazonBedrockPlugin", () => {
it.effect("ignores other Bedrock provider subpaths", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
package: ProviderV2.aisdk("@ai-sdk/amazon-bedrock/anthropic"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("@ai-sdk/amazon-bedrock/anthropic"),
}),
package: "@ai-sdk/amazon-bedrock/anthropic",
options: { name: "amazon-bedrock" },
@ -387,15 +387,15 @@ describe("AmazonBedrockPlugin", () => {
},
() =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const headers: Array<string | null> = []
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
package: "@ai-sdk/amazon-bedrock",
options: {
@ -419,51 +419,51 @@ describe("AmazonBedrockPlugin", () => {
it.effect("applies legacy cross-region inference prefixes", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
sdk: { languageModel: fakeSelectorSdk(calls).languageModel },
options: {},
})
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
sdk: { languageModel: fakeSelectorSdk(calls).languageModel },
options: { region: "eu-west-1" },
})
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("global.anthropic.claude-sonnet-4-5")),
modelID: ModelV2.ID.make("global.anthropic.claude-sonnet-4-5"),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("global.anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("global.anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
sdk: { languageModel: fakeSelectorSdk(calls).languageModel },
options: { region: "eu-west-1" },
})
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
sdk: { languageModel: fakeSelectorSdk(calls).languageModel },
options: { region: "ap-northeast-1" },
})
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
sdk: { languageModel: fakeSelectorSdk(calls).languageModel },
options: { region: "ap-southeast-2" },
@ -481,15 +481,15 @@ describe("AmazonBedrockPlugin", () => {
it.effect("uses AWS_REGION for language prefixes when region option is absent", () =>
withEnv({ AWS_REGION: "eu-west-1" }, () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
sdk: { languageModel: fakeSelectorSdk(calls).languageModel },
options: {},
@ -501,7 +501,7 @@ describe("AmazonBedrockPlugin", () => {
it.effect("applies the full legacy cross-region prefix matrix", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
const cases = [
@ -573,10 +573,10 @@ describe("AmazonBedrockPlugin", () => {
yield* addPlugin()
for (const item of cases) {
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.amazonBedrock, ModelV2.ID.make(item.modelID)),
modelID: ModelV2.ID.make(item.modelID),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.amazonBedrock, Model.ID.make(item.modelID)),
modelID: Model.ID.make(item.modelID),
package: Provider.aisdk("test-provider"),
}),
sdk: { languageModel: fakeSelectorSdk(calls).languageModel },
options: { region: item.region },
@ -588,15 +588,15 @@ describe("AmazonBedrockPlugin", () => {
it.effect("ignores non-Bedrock providers for language selection", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin()
const result = yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.openai, ModelV2.ID.make("anthropic.claude-sonnet-4-5")),
modelID: ModelV2.ID.make("anthropic.claude-sonnet-4-5"),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.openai, Model.ID.make("anthropic.claude-sonnet-4-5")),
modelID: Model.ID.make("anthropic.claude-sonnet-4-5"),
package: Provider.aisdk("test-provider"),
}),
sdk: { languageModel: fakeSelectorSdk(calls).languageModel },
options: { region: "eu-west-1" },

View file

@ -2,18 +2,18 @@ import { AISDK } from "@opencode-ai/core/aisdk"
import { describe, expect } from "bun:test"
import { Effect } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { ModelV2 } from "@opencode-ai/core/model"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { Model } from "@opencode-ai/core/model"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { AnthropicPlugin } from "@opencode-ai/core/plugin/provider/anthropic"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Provider } from "@opencode-ai/core/provider"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const host = yield* PluginHost.make(plugin)
yield* AnthropicPlugin.effect(host)
@ -29,9 +29,9 @@ describe("AnthropicPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
const item = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.anthropic),
package: ProviderV2.aisdk("@ai-sdk/anthropic"),
const item = Provider.Info.make({
...Provider.Info.empty(Provider.ID.anthropic),
package: Provider.aisdk("@ai-sdk/anthropic"),
headers: { Existing: "1" },
})
catalog.provider.update(item.id, (draft) => {
@ -40,32 +40,32 @@ describe("AnthropicPlugin", () => {
})
})
yield* addPlugin()
expect(required(yield* catalog.provider.get(ProviderV2.ID.anthropic)).headers?.["anthropic-beta"]).toBe(
expect(required(yield* catalog.provider.get(Provider.ID.anthropic)).headers?.["anthropic-beta"]).toBe(
"interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14",
)
expect(required(yield* catalog.provider.get(ProviderV2.ID.anthropic)).headers?.Existing).toBe("1")
expect(required(yield* catalog.provider.get(Provider.ID.anthropic)).headers?.Existing).toBe("1")
}),
)
it.effect("ignores non-Anthropic providers", () =>
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => catalog.provider.update(ProviderV2.ID.openai, () => {}))
yield* catalog.transform((catalog) => catalog.provider.update(Provider.ID.openai, () => {}))
yield* addPlugin()
expect(required(yield* catalog.provider.get(ProviderV2.ID.openai)).headers?.["anthropic-beta"]).toBeUndefined()
expect(required(yield* catalog.provider.get(Provider.ID.openai)).headers?.["anthropic-beta"]).toBeUndefined()
}),
)
it.effect("creates Anthropic SDKs with the model provider ID as the SDK name", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("custom-anthropic"), ModelV2.ID.make("claude-sonnet-4-5")),
modelID: ModelV2.ID.make("claude-sonnet-4-5"),
package: ProviderV2.aisdk("@ai-sdk/anthropic"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("custom-anthropic"), Model.ID.make("claude-sonnet-4-5")),
modelID: Model.ID.make("claude-sonnet-4-5"),
package: Provider.aisdk("@ai-sdk/anthropic"),
}),
package: "@ai-sdk/anthropic",
options: { name: "custom-anthropic", apiKey: "test" },
@ -76,14 +76,14 @@ describe("AnthropicPlugin", () => {
it.effect("uses the Anthropic provider ID as the SDK name for the bundled Anthropic provider", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.anthropic, ModelV2.ID.make("claude-sonnet-4-5")),
modelID: ModelV2.ID.make("claude-sonnet-4-5"),
package: ProviderV2.aisdk("@ai-sdk/anthropic"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.anthropic, Model.ID.make("claude-sonnet-4-5")),
modelID: Model.ID.make("claude-sonnet-4-5"),
package: Provider.aisdk("@ai-sdk/anthropic"),
}),
package: "@ai-sdk/anthropic",
options: { name: "anthropic", apiKey: "test" },

View file

@ -3,18 +3,18 @@ import { describe, expect } from "bun:test"
import type { LanguageModelV3 } from "@ai-sdk/provider"
import { Effect } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { ModelV2 } from "@opencode-ai/core/model"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { Model } from "@opencode-ai/core/model"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { AzureCognitiveServicesPlugin } from "@opencode-ai/core/plugin/provider/azure"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Provider } from "@opencode-ai/core/provider"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const host = yield* PluginHost.make(plugin)
yield* AzureCognitiveServicesPlugin.effect(host)
@ -65,12 +65,12 @@ describe("AzureCognitiveServicesPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
catalog.provider.update(ProviderV2.ID.make("azure-cognitive-services"), (item) => {
item.package = ProviderV2.aisdk("@ai-sdk/openai-compatible")
catalog.provider.update(Provider.ID.make("azure-cognitive-services"), (item) => {
item.package = Provider.aisdk("@ai-sdk/openai-compatible")
})
})
yield* addPlugin()
const result = required(yield* catalog.provider.get(ProviderV2.ID.make("azure-cognitive-services")))
const result = required(yield* catalog.provider.get(Provider.ID.make("azure-cognitive-services")))
expect(result).toMatchObject({
package: "aisdk:@ai-sdk/openai-compatible",
settings: { baseURL: "https://cognitive.cognitiveservices.azure.com/openai" },
@ -85,12 +85,12 @@ describe("AzureCognitiveServicesPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
const azure = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.make("azure-cognitive-services")),
const azure = Provider.Info.make({
...Provider.Info.empty(Provider.ID.make("azure-cognitive-services")),
package: "aisdk:@ai-sdk/openai-compatible",
})
const openai = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.openai),
const openai = Provider.Info.make({
...Provider.Info.empty(Provider.ID.openai),
package: "aisdk:test-provider",
})
catalog.provider.update(azure.id, (item) => {
@ -103,8 +103,8 @@ describe("AzureCognitiveServicesPlugin", () => {
})
})
yield* addPlugin()
const azure = required(yield* catalog.provider.get(ProviderV2.ID.make("azure-cognitive-services")))
const openai = required(yield* catalog.provider.get(ProviderV2.ID.openai))
const azure = required(yield* catalog.provider.get(Provider.ID.make("azure-cognitive-services")))
const openai = required(yield* catalog.provider.get(Provider.ID.openai))
expect(azure.settings?.baseURL).toBeUndefined()
expect(azure).toMatchObject({ package: "aisdk:@ai-sdk/openai-compatible" })
expect(openai.settings?.baseURL).toBeUndefined()
@ -115,14 +115,14 @@ describe("AzureCognitiveServicesPlugin", () => {
it.effect("selects chat only for completion URLs", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("azure-cognitive-services"), ModelV2.ID.make("deployment")),
modelID: ModelV2.ID.make("deployment"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("azure-cognitive-services"), Model.ID.make("deployment")),
modelID: Model.ID.make("deployment"),
package: "aisdk:test-provider",
}),
sdk: fakeSelectorSdk(calls),
@ -134,23 +134,23 @@ describe("AzureCognitiveServicesPlugin", () => {
it.effect("uses the legacy Azure selector order and provider guard", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("azure-cognitive-services"), ModelV2.ID.make("deployment")),
modelID: ModelV2.ID.make("deployment"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("azure-cognitive-services"), Model.ID.make("deployment")),
modelID: Model.ID.make("deployment"),
package: "aisdk:test-provider",
}),
sdk: fakeSelectorSdk(calls),
options: {},
})
const ignored = yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.openai, ModelV2.ID.make("deployment")),
modelID: ModelV2.ID.make("deployment"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.openai, Model.ID.make("deployment")),
modelID: Model.ID.make("deployment"),
package: "aisdk:test-provider",
}),
sdk: fakeSelectorSdk(calls),
@ -163,33 +163,33 @@ describe("AzureCognitiveServicesPlugin", () => {
it.effect("falls back from responses to messages, chat, then languageModel", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
const sdk = fakeSelectorSdk(calls)
yield* addPlugin()
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("azure-cognitive-services"), ModelV2.ID.make("messages-deployment")),
modelID: ModelV2.ID.make("messages-deployment"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("azure-cognitive-services"), Model.ID.make("messages-deployment")),
modelID: Model.ID.make("messages-deployment"),
package: "aisdk:test-provider",
}),
sdk: { messages: sdk.messages, chat: sdk.chat, languageModel: sdk.languageModel },
options: {},
})
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("azure-cognitive-services"), ModelV2.ID.make("chat-deployment")),
modelID: ModelV2.ID.make("chat-deployment"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("azure-cognitive-services"), Model.ID.make("chat-deployment")),
modelID: Model.ID.make("chat-deployment"),
package: "aisdk:test-provider",
}),
sdk: { chat: sdk.chat, languageModel: sdk.languageModel },
options: {},
})
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("azure-cognitive-services"), ModelV2.ID.make("language-deployment")),
modelID: ModelV2.ID.make("language-deployment"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("azure-cognitive-services"), Model.ID.make("language-deployment")),
modelID: Model.ID.make("language-deployment"),
package: "aisdk:test-provider",
}),
sdk: { languageModel: sdk.languageModel },

View file

@ -3,18 +3,18 @@ import { describe, expect } from "bun:test"
import type { LanguageModelV3 } from "@ai-sdk/provider"
import { Effect } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { ModelV2 } from "@opencode-ai/core/model"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { Model } from "@opencode-ai/core/model"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { AzurePlugin } from "@opencode-ai/core/plugin/provider/azure"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Provider } from "@opencode-ai/core/provider"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const host = yield* PluginHost.make(plugin)
yield* AzurePlugin.effect(host)
@ -65,12 +65,12 @@ describe("AzurePlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
catalog.provider.update(ProviderV2.ID.azure, (item) => {
item.package = ProviderV2.aisdk("@ai-sdk/azure")
catalog.provider.update(Provider.ID.azure, (item) => {
item.package = Provider.aisdk("@ai-sdk/azure")
})
})
yield* addPlugin()
expect(required(yield* catalog.provider.get(ProviderV2.ID.azure)).settings?.resourceName).toBe("from-env")
expect(required(yield* catalog.provider.get(Provider.ID.azure)).settings?.resourceName).toBe("from-env")
}),
),
)
@ -80,20 +80,20 @@ describe("AzurePlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
const azure = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.azure),
package: ProviderV2.aisdk("@ai-sdk/azure"),
const azure = Provider.Info.make({
...Provider.Info.empty(Provider.ID.azure),
package: Provider.aisdk("@ai-sdk/azure"),
settings: { resourceName: "from-config" },
})
catalog.provider.update(azure.id, (item) => {
item.package = azure.package
item.settings = { resourceName: "from-config" }
})
catalog.provider.update(ProviderV2.ID.openai, () => {})
catalog.provider.update(Provider.ID.openai, () => {})
})
yield* addPlugin()
expect(required(yield* catalog.provider.get(ProviderV2.ID.azure)).settings?.resourceName).toBe("from-config")
expect(required(yield* catalog.provider.get(ProviderV2.ID.openai)).settings?.resourceName).toBeUndefined()
expect(required(yield* catalog.provider.get(Provider.ID.azure)).settings?.resourceName).toBe("from-config")
expect(required(yield* catalog.provider.get(Provider.ID.openai)).settings?.resourceName).toBeUndefined()
}),
),
)
@ -103,9 +103,9 @@ describe("AzurePlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
const azure = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.azure),
package: ProviderV2.aisdk("@ai-sdk/azure"),
const azure = Provider.Info.make({
...Provider.Info.empty(Provider.ID.azure),
package: Provider.aisdk("@ai-sdk/azure"),
settings: { resourceName: "" },
})
catalog.provider.update(azure.id, (item) => {
@ -114,7 +114,7 @@ describe("AzurePlugin", () => {
})
})
yield* addPlugin()
expect(required(yield* catalog.provider.get(ProviderV2.ID.azure)).settings?.resourceName).toBe("from-env")
expect(required(yield* catalog.provider.get(Provider.ID.azure)).settings?.resourceName).toBe("from-env")
}),
),
)
@ -124,9 +124,9 @@ describe("AzurePlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
const azure = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.azure),
package: ProviderV2.aisdk("@ai-sdk/azure"),
const azure = Provider.Info.make({
...Provider.Info.empty(Provider.ID.azure),
package: Provider.aisdk("@ai-sdk/azure"),
settings: { resourceName: " " },
})
catalog.provider.update(azure.id, (item) => {
@ -135,7 +135,7 @@ describe("AzurePlugin", () => {
})
})
yield* addPlugin()
expect(required(yield* catalog.provider.get(ProviderV2.ID.azure)).settings?.resourceName).toBe("from-env")
expect(required(yield* catalog.provider.get(Provider.ID.azure)).settings?.resourceName).toBe("from-env")
}),
),
)
@ -143,14 +143,14 @@ describe("AzurePlugin", () => {
it.effect("allows configured baseURL without resourceName", () =>
withEnv({ AZURE_RESOURCE_NAME: undefined }, () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.azure, ModelV2.ID.make("deployment")),
modelID: ModelV2.ID.make("deployment"),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.azure, Model.ID.make("deployment")),
modelID: Model.ID.make("deployment"),
package: Provider.aisdk("test-provider"),
}),
package: "@ai-sdk/azure",
options: { name: "azure", baseURL: "https://proxy.example.com/openai" },
@ -167,10 +167,10 @@ describe("AzurePlugin", () => {
yield* addPlugin()
const exit = yield* aisdk
.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.azure, ModelV2.ID.make("deployment")),
modelID: ModelV2.ID.make("deployment"),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.azure, Model.ID.make("deployment")),
modelID: Model.ID.make("deployment"),
package: Provider.aisdk("test-provider"),
}),
package: "@ai-sdk/azure",
options: { name: "azure" },
@ -183,15 +183,15 @@ describe("AzurePlugin", () => {
it.effect("selects chat only for completion URLs", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.azure, ModelV2.ID.make("deployment")),
modelID: ModelV2.ID.make("deployment"),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.azure, Model.ID.make("deployment")),
modelID: Model.ID.make("deployment"),
package: Provider.aisdk("test-provider"),
}),
sdk: fakeSelectorSdk(calls),
options: { useCompletionUrls: true },
@ -202,15 +202,15 @@ describe("AzurePlugin", () => {
it.effect("selects chat from per-call useCompletionUrls", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.azure, ModelV2.ID.make("deployment")),
modelID: ModelV2.ID.make("deployment"),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.azure, Model.ID.make("deployment")),
modelID: Model.ID.make("deployment"),
package: Provider.aisdk("test-provider"),
}),
sdk: fakeSelectorSdk(calls),
options: { useCompletionUrls: true },
@ -221,15 +221,15 @@ describe("AzurePlugin", () => {
it.effect("ignores model useCompletionUrls when per-call option is unset", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.azure, ModelV2.ID.make("deployment")),
modelID: ModelV2.ID.make("deployment"),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.azure, Model.ID.make("deployment")),
modelID: Model.ID.make("deployment"),
package: Provider.aisdk("test-provider"),
body: { useCompletionUrls: true },
}),
sdk: fakeSelectorSdk(calls),
@ -241,24 +241,24 @@ describe("AzurePlugin", () => {
it.effect("uses the legacy Azure selector order and provider guard", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.azure, ModelV2.ID.make("deployment")),
modelID: ModelV2.ID.make("deployment"),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.azure, Model.ID.make("deployment")),
modelID: Model.ID.make("deployment"),
package: Provider.aisdk("test-provider"),
}),
sdk: fakeSelectorSdk(calls),
options: {},
})
const ignored = yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.openai, ModelV2.ID.make("deployment")),
modelID: ModelV2.ID.make("deployment"),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.openai, Model.ID.make("deployment")),
modelID: Model.ID.make("deployment"),
package: Provider.aisdk("test-provider"),
}),
sdk: fakeSelectorSdk(calls),
options: {},
@ -270,7 +270,7 @@ describe("AzurePlugin", () => {
it.effect("falls back through the legacy Azure selector order", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
const make = (method: string) => (id: string) => {
@ -279,19 +279,19 @@ describe("AzurePlugin", () => {
}
yield* addPlugin()
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.azure, ModelV2.ID.make("messages-deployment")),
modelID: ModelV2.ID.make("messages-deployment"),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.azure, Model.ID.make("messages-deployment")),
modelID: Model.ID.make("messages-deployment"),
package: Provider.aisdk("test-provider"),
}),
sdk: { messages: make("messages"), chat: make("chat"), languageModel: make("languageModel") },
options: {},
})
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.azure, ModelV2.ID.make("language-deployment")),
modelID: ModelV2.ID.make("language-deployment"),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.azure, Model.ID.make("language-deployment")),
modelID: Model.ID.make("language-deployment"),
package: Provider.aisdk("test-provider"),
}),
sdk: { languageModel: make("languageModel") },
options: {},

View file

@ -2,11 +2,11 @@ import { AISDK } from "@opencode-ai/core/aisdk"
import { describe, expect, mock } from "bun:test"
import { Effect } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { ModelV2 } from "@opencode-ai/core/model"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { Model } from "@opencode-ai/core/model"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { CerebrasPlugin } from "@opencode-ai/core/plugin/provider/cerebras"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Provider } from "@opencode-ai/core/provider"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
@ -14,7 +14,7 @@ const cerebrasOptions: Record<string, unknown>[] = []
const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const host = yield* PluginHost.make(plugin)
yield* CerebrasPlugin.effect(host)
@ -35,13 +35,13 @@ describe("CerebrasPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
catalog.provider.update(ProviderV2.ID.make("cerebras"), (item) => {
item.package = ProviderV2.aisdk("@ai-sdk/cerebras")
catalog.provider.update(Provider.ID.make("cerebras"), (item) => {
item.package = Provider.aisdk("@ai-sdk/cerebras")
item.headers = { ...item.headers, Existing: "1" }
})
})
yield* addPlugin()
expect((yield* catalog.provider.get(ProviderV2.ID.make("cerebras")))?.headers).toEqual({
expect((yield* catalog.provider.get(Provider.ID.make("cerebras")))?.headers).toEqual({
Existing: "1",
"X-Cerebras-3rd-Party-Integration": "opencode",
})
@ -51,25 +51,25 @@ describe("CerebrasPlugin", () => {
it.effect("ignores non-Cerebras providers", () =>
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => catalog.provider.update(ProviderV2.ID.make("groq"), () => {}))
yield* catalog.transform((catalog) => catalog.provider.update(Provider.ID.make("groq"), () => {}))
yield* addPlugin()
expect((yield* catalog.provider.get(ProviderV2.ID.make("groq")))?.headers).toBeUndefined()
expect((yield* catalog.provider.get(Provider.ID.make("groq")))?.headers).toBeUndefined()
}),
)
it.effect("creates a bundled Cerebras SDK with the model provider ID as the SDK name", () =>
Effect.gen(function* () {
cerebrasOptions.length = 0
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(
ProviderV2.ID.make("custom-cerebras"),
ModelV2.ID.make("llama-4-scout-17b-16e-instruct"),
model: Model.Info.make({
...Model.Info.default(
Provider.ID.make("custom-cerebras"),
Model.ID.make("llama-4-scout-17b-16e-instruct"),
),
modelID: ModelV2.ID.make("llama-4-scout-17b-16e-instruct"),
modelID: Model.ID.make("llama-4-scout-17b-16e-instruct"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/cerebras",
@ -83,16 +83,16 @@ describe("CerebrasPlugin", () => {
it.effect("preserves an explicit bundled Cerebras SDK name option", () =>
Effect.gen(function* () {
cerebrasOptions.length = 0
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(
ProviderV2.ID.make("custom-cerebras"),
ModelV2.ID.make("llama-4-scout-17b-16e-instruct"),
model: Model.Info.make({
...Model.Info.default(
Provider.ID.make("custom-cerebras"),
Model.ID.make("llama-4-scout-17b-16e-instruct"),
),
modelID: ModelV2.ID.make("llama-4-scout-17b-16e-instruct"),
modelID: Model.ID.make("llama-4-scout-17b-16e-instruct"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/cerebras",
@ -105,16 +105,16 @@ describe("CerebrasPlugin", () => {
it.effect("ignores non-Cerebras SDK packages", () =>
Effect.gen(function* () {
cerebrasOptions.length = 0
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(
ProviderV2.ID.make("custom-cerebras"),
ModelV2.ID.make("llama-4-scout-17b-16e-instruct"),
model: Model.Info.make({
...Model.Info.default(
Provider.ID.make("custom-cerebras"),
Model.ID.make("llama-4-scout-17b-16e-instruct"),
),
modelID: ModelV2.ID.make("llama-4-scout-17b-16e-instruct"),
modelID: Model.ID.make("llama-4-scout-17b-16e-instruct"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/groq",

View file

@ -1,18 +1,18 @@
import { AISDK } from "@opencode-ai/core/aisdk"
import { describe, expect, mock } from "bun:test"
import { Effect } from "effect"
import { ModelV2 } from "@opencode-ai/core/model"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { Model } from "@opencode-ai/core/model"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { CloudflareAIGatewayPlugin } from "@opencode-ai/core/plugin/provider/cloudflare-ai-gateway"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Provider } from "@opencode-ai/core/provider"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const host = yield* PluginHost.make(plugin)
yield* CloudflareAIGatewayPlugin.effect(host)
@ -112,13 +112,13 @@ describe("CloudflareAIGatewayPlugin", () => {
},
() =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
modelID: ModelV2.ID.make("openai/gpt-5"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("cloudflare-ai-gateway"), Model.ID.make("openai/gpt-5")),
modelID: Model.ID.make("openai/gpt-5"),
package: "aisdk:test-provider",
}),
package: "ai-gateway-provider",
@ -133,14 +133,14 @@ describe("CloudflareAIGatewayPlugin", () => {
withEnv(cloudflareEnv(), () =>
Effect.gen(function* () {
resetCalls()
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
modelID: ModelV2.ID.make("openai/gpt-5"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("cloudflare-ai-gateway"), Model.ID.make("openai/gpt-5")),
modelID: Model.ID.make("openai/gpt-5"),
package: "aisdk:test-provider",
}),
package: "ai-gateway-provider",
@ -178,14 +178,14 @@ describe("CloudflareAIGatewayPlugin", () => {
withEnv(cloudflareEnv(), () =>
Effect.gen(function* () {
resetCalls()
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
modelID: ModelV2.ID.make("openai/gpt-5"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("cloudflare-ai-gateway"), Model.ID.make("openai/gpt-5")),
modelID: Model.ID.make("openai/gpt-5"),
package: "aisdk:test-provider",
}),
package: "ai-gateway-provider",
@ -208,14 +208,14 @@ describe("CloudflareAIGatewayPlugin", () => {
withEnv(cloudflareEnv(), () =>
Effect.gen(function* () {
resetCalls()
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
modelID: ModelV2.ID.make("openai/gpt-5"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("cloudflare-ai-gateway"), Model.ID.make("openai/gpt-5")),
modelID: Model.ID.make("openai/gpt-5"),
package: "aisdk:test-provider",
}),
package: "ai-gateway-provider",
@ -246,14 +246,14 @@ describe("CloudflareAIGatewayPlugin", () => {
() =>
Effect.gen(function* () {
resetCalls()
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
modelID: ModelV2.ID.make("openai/gpt-5"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("cloudflare-ai-gateway"), Model.ID.make("openai/gpt-5")),
modelID: Model.ID.make("openai/gpt-5"),
package: "aisdk:test-provider",
}),
package: "ai-gateway-provider",
@ -278,14 +278,14 @@ describe("CloudflareAIGatewayPlugin", () => {
withEnv(cloudflareEnv({ CLOUDFLARE_API_TOKEN: undefined, CF_AIG_TOKEN: "cf-aig-token" }), () =>
Effect.gen(function* () {
resetCalls()
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
modelID: ModelV2.ID.make("openai/gpt-5"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("cloudflare-ai-gateway"), Model.ID.make("openai/gpt-5")),
modelID: Model.ID.make("openai/gpt-5"),
package: "aisdk:test-provider",
}),
package: "ai-gateway-provider",
@ -301,14 +301,14 @@ describe("CloudflareAIGatewayPlugin", () => {
withEnv(cloudflareEnv({ CLOUDFLARE_ACCOUNT_ID: undefined, CLOUDFLARE_GATEWAY_ID: undefined }), () =>
Effect.gen(function* () {
resetCalls()
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
modelID: ModelV2.ID.make("openai/gpt-5"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("cloudflare-ai-gateway"), Model.ID.make("openai/gpt-5")),
modelID: Model.ID.make("openai/gpt-5"),
package: "aisdk:test-provider",
}),
package: "ai-gateway-provider",
@ -325,14 +325,14 @@ describe("CloudflareAIGatewayPlugin", () => {
withEnv(cloudflareEnv({ CLOUDFLARE_API_TOKEN: undefined, CF_AIG_TOKEN: undefined }), () =>
Effect.gen(function* () {
resetCalls()
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
modelID: ModelV2.ID.make("openai/gpt-5"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("cloudflare-ai-gateway"), Model.ID.make("openai/gpt-5")),
modelID: Model.ID.make("openai/gpt-5"),
package: "aisdk:test-provider",
}),
package: "ai-gateway-provider",
@ -355,14 +355,14 @@ describe("CloudflareAIGatewayPlugin", () => {
() =>
Effect.gen(function* () {
resetCalls()
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
modelID: ModelV2.ID.make("openai/gpt-5"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("cloudflare-ai-gateway"), Model.ID.make("openai/gpt-5")),
modelID: Model.ID.make("openai/gpt-5"),
package: "aisdk:test-provider",
}),
package: "ai-gateway-provider",
@ -379,17 +379,17 @@ describe("CloudflareAIGatewayPlugin", () => {
withEnv(cloudflareEnv(), () =>
Effect.gen(function* () {
resetCalls()
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(
ProviderV2.ID.make("cloudflare-ai-gateway"),
ModelV2.ID.make("anthropic/claude-sonnet-4-5"),
model: Model.Info.make({
...Model.Info.default(
Provider.ID.make("cloudflare-ai-gateway"),
Model.ID.make("anthropic/claude-sonnet-4-5"),
),
modelID: ModelV2.ID.make("anthropic/claude-sonnet-4-5"),
modelID: Model.ID.make("anthropic/claude-sonnet-4-5"),
package: "aisdk:test-provider",
}),
package: "ai-gateway-provider",
@ -411,14 +411,14 @@ describe("CloudflareAIGatewayPlugin", () => {
withEnv(cloudflareEnv(), () =>
Effect.gen(function* () {
resetCalls()
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-ai-gateway"), ModelV2.ID.make("openai/gpt-5")),
modelID: ModelV2.ID.make("openai/gpt-5"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("cloudflare-ai-gateway"), Model.ID.make("openai/gpt-5")),
modelID: Model.ID.make("openai/gpt-5"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/openai-compatible",

View file

@ -2,11 +2,11 @@ import { AISDK } from "@opencode-ai/core/aisdk"
import { describe, expect } from "bun:test"
import { Effect } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { ModelV2 } from "@opencode-ai/core/model"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { Model } from "@opencode-ai/core/model"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { CloudflareWorkersAIPlugin } from "@opencode-ai/core/plugin/provider/cloudflare-workers-ai"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Provider } from "@opencode-ai/core/provider"
import type { LanguageModelV3 } from "@ai-sdk/provider"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
@ -14,7 +14,7 @@ import { PluginTestLayer } from "./fixture"
const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const host = yield* PluginHost.make(plugin)
yield* CloudflareWorkersAIPlugin.effect(host)
@ -82,20 +82,20 @@ describe("CloudflareWorkersAIPlugin", () => {
it.effect("maps account ID to endpoint URL and creates an OpenAI-compatible SDK", () =>
withEnv({ CLOUDFLARE_ACCOUNT_ID: "acct", CLOUDFLARE_API_KEY: "key" }, () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) =>
catalog.provider.update(ProviderV2.ID.make("cloudflare-workers-ai"), (provider) => {
provider.package = ProviderV2.aisdk("test-provider")
catalog.provider.update(Provider.ID.make("cloudflare-workers-ai"), (provider) => {
provider.package = Provider.aisdk("test-provider")
}),
)
yield* addPlugin()
const provider = required(yield* catalog.provider.get(ProviderV2.ID.make("cloudflare-workers-ai")))
const provider = required(yield* catalog.provider.get(Provider.ID.make("cloudflare-workers-ai")))
const sdk = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("@cf/model")),
modelID: ModelV2.ID.make("@cf/model"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("cloudflare-workers-ai"), Model.ID.make("@cf/model")),
modelID: Model.ID.make("@cf/model"),
package: provider.package,
settings: provider.settings,
}),
@ -116,13 +116,13 @@ describe("CloudflareWorkersAIPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) =>
catalog.provider.update(ProviderV2.ID.make("cloudflare-workers-ai"), (provider) => {
provider.package = ProviderV2.aisdk("test-provider")
catalog.provider.update(Provider.ID.make("cloudflare-workers-ai"), (provider) => {
provider.package = Provider.aisdk("test-provider")
provider.settings = { ...provider.settings, baseURL: "https://proxy.example/v1" }
}),
)
yield* addPlugin()
expect(required(yield* catalog.provider.get(ProviderV2.ID.make("cloudflare-workers-ai")))).toMatchObject({
expect(required(yield* catalog.provider.get(Provider.ID.make("cloudflare-workers-ai")))).toMatchObject({
package: "aisdk:test-provider",
settings: { baseURL: "https://proxy.example/v1" },
})
@ -133,13 +133,13 @@ describe("CloudflareWorkersAIPlugin", () => {
it.effect("allows a configured baseURL without account ID", () =>
withEnv({ CLOUDFLARE_ACCOUNT_ID: undefined, CLOUDFLARE_API_KEY: "key" }, () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("@cf/model")),
modelID: ModelV2.ID.make("@cf/model"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("cloudflare-workers-ai"), Model.ID.make("@cf/model")),
modelID: Model.ID.make("@cf/model"),
package: "aisdk:@ai-sdk/openai-compatible",
settings: { baseURL: "https://proxy.example/v1" },
}),
@ -156,13 +156,13 @@ describe("CloudflareWorkersAIPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) =>
catalog.provider.update(ProviderV2.ID.make("cloudflare-workers-ai"), (provider) => {
provider.package = ProviderV2.aisdk("test-provider")
catalog.provider.update(Provider.ID.make("cloudflare-workers-ai"), (provider) => {
provider.package = Provider.aisdk("test-provider")
provider.settings = { ...provider.settings, accountId: "configured-acct" }
}),
)
yield* addPlugin()
expect(required(yield* catalog.provider.get(ProviderV2.ID.make("cloudflare-workers-ai")))).toMatchObject({
expect(required(yield* catalog.provider.get(Provider.ID.make("cloudflare-workers-ai")))).toMatchObject({
package: "aisdk:test-provider",
settings: { baseURL: "https://api.cloudflare.com/client/v4/accounts/env-acct/ai/v1" },
})
@ -173,13 +173,13 @@ describe("CloudflareWorkersAIPlugin", () => {
it.effect("uses env API key over auth or configured API key and keeps the Cloudflare User-Agent", () =>
withEnv({ CLOUDFLARE_ACCOUNT_ID: "acct", CLOUDFLARE_API_KEY: "env-key" }, () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("@cf/model")),
modelID: ModelV2.ID.make("@cf/model"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("cloudflare-workers-ai"), Model.ID.make("@cf/model")),
modelID: Model.ID.make("@cf/model"),
package: "aisdk:@ai-sdk/openai-compatible",
settings: { baseURL: "https://proxy.example/v1" },
}),
@ -202,13 +202,13 @@ describe("CloudflareWorkersAIPlugin", () => {
it.effect("expands account ID vars in endpoint URLs", () =>
withEnv({ CLOUDFLARE_ACCOUNT_ID: "acct", CLOUDFLARE_API_KEY: "key" }, () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("@cf/model")),
modelID: ModelV2.ID.make("@cf/model"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("cloudflare-workers-ai"), Model.ID.make("@cf/model")),
modelID: Model.ID.make("@cf/model"),
package: "aisdk:@ai-sdk/openai-compatible",
settings: { baseURL: "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/ai/v1" },
}),
@ -227,14 +227,14 @@ describe("CloudflareWorkersAIPlugin", () => {
it.effect("selects languageModel with the API model ID", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin()
const result = yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("alias")),
modelID: ModelV2.ID.make("@cf/api-model"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("cloudflare-workers-ai"), Model.ID.make("alias")),
modelID: Model.ID.make("@cf/api-model"),
package: "aisdk:test-provider",
}),
sdk: fakeSelectorSdk(calls),
@ -248,13 +248,13 @@ describe("CloudflareWorkersAIPlugin", () => {
it.effect("does not create an SDK for non OpenAI-compatible packages", () =>
withEnv({ CLOUDFLARE_ACCOUNT_ID: "acct", CLOUDFLARE_API_KEY: "key" }, () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("@cf/model")),
modelID: ModelV2.ID.make("@cf/model"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("cloudflare-workers-ai"), Model.ID.make("@cf/model")),
modelID: Model.ID.make("@cf/model"),
package: "aisdk:@ai-sdk/anthropic",
settings: { baseURL: "https://proxy.example/v1" },
}),

View file

@ -7,11 +7,11 @@ import path from "path"
import { fileURLToPath } from "url"
import { AISDK } from "@opencode-ai/core/aisdk"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { ModelV2 } from "@opencode-ai/core/model"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { Model } from "@opencode-ai/core/model"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { DynamicProviderPlugin } from "@opencode-ai/core/plugin/provider/dynamic"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Provider } from "@opencode-ai/core/provider"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
@ -29,7 +29,7 @@ function npmEntrypoint(entrypoint?: string) {
}
const addPlugin = Effect.fn(function* (npm?: Npm.Interface) {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const host = yield* PluginHost.make(plugin)
yield* DynamicProviderPlugin.effect(host).pipe(Effect.provideService(Npm.Service, npm ?? (yield* Npm.Service)))
})
@ -52,10 +52,10 @@ describe("DynamicProviderPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("custom"), ModelV2.ID.make("test-model")),
modelID: ModelV2.ID.make("test-model"),
package: ProviderV2.aisdk(fixtureProvider),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("custom"), Model.ID.make("test-model")),
modelID: Model.ID.make("test-model"),
package: Provider.aisdk(fixtureProvider),
}),
package: fixtureProvider,
options: { name: "custom", marker: "dynamic" },
@ -71,10 +71,10 @@ describe("DynamicProviderPlugin", () => {
const sdk = { marker: "existing" }
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("custom"), ModelV2.ID.make("test-model")),
modelID: ModelV2.ID.make("test-model"),
package: ProviderV2.aisdk(fixtureProvider),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("custom"), Model.ID.make("test-model")),
modelID: Model.ID.make("test-model"),
package: Provider.aisdk(fixtureProvider),
}),
package: fixtureProvider,
options: { name: "custom", marker: "dynamic" },
@ -89,10 +89,10 @@ describe("DynamicProviderPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("custom-provider"), ModelV2.ID.make("test-model")),
modelID: ModelV2.ID.make("test-model"),
package: ProviderV2.aisdk(fixtureProvider),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("custom-provider"), Model.ID.make("test-model")),
modelID: Model.ID.make("test-model"),
package: Provider.aisdk(fixtureProvider),
}),
package: fixtureProvider,
options: { name: "custom-provider", marker: "dynamic" },
@ -106,9 +106,9 @@ describe("DynamicProviderPlugin", () => {
const aisdk = yield* AISDK.Service
yield* addPlugin(npmEntrypoint(fixtureProviderPath))
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("npm-provider"), ModelV2.ID.make("test-model")),
modelID: ModelV2.ID.make("test-model"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("npm-provider"), Model.ID.make("test-model")),
modelID: Model.ID.make("test-model"),
package: "aisdk:fixture-provider",
}),
package: "fixture-provider",
@ -124,9 +124,9 @@ describe("DynamicProviderPlugin", () => {
yield* addPlugin(npmEntrypoint())
const exit = yield* aisdk
.language(
ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("missing-entrypoint"), ModelV2.ID.make("alias")),
modelID: ModelV2.ID.make("alias"),
Model.Info.make({
...Model.Info.default(Provider.ID.make("missing-entrypoint"), Model.ID.make("alias")),
modelID: Model.ID.make("alias"),
package: "aisdk:fixture-provider",
}),
)
@ -142,9 +142,9 @@ describe("DynamicProviderPlugin", () => {
yield* addPlugin()
const exit = yield* aisdk
.language(
ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("bad-import"), ModelV2.ID.make("alias")),
modelID: ModelV2.ID.make("alias"),
Model.Info.make({
...Model.Info.default(Provider.ID.make("bad-import"), Model.ID.make("alias")),
modelID: Model.ID.make("alias"),
package: "aisdk:file:///missing/provider-factory.js",
}),
)
@ -156,15 +156,15 @@ describe("DynamicProviderPlugin", () => {
itWithAISDK.live("wraps missing provider factory exports as AISDK init errors", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const tmp = yield* tempEntrypoint("export const notAProviderFactory = true\n")
yield* addPlugin(npmEntrypoint(tmp.entrypoint))
const exit = yield* aisdk
.language(
ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("missing-factory"), ModelV2.ID.make("alias")),
modelID: ModelV2.ID.make("alias"),
Model.Info.make({
...Model.Info.default(Provider.ID.make("missing-factory"), Model.ID.make("alias")),
modelID: Model.ID.make("alias"),
package: "aisdk:fixture-provider",
}),
)
@ -176,14 +176,14 @@ describe("DynamicProviderPlugin", () => {
itWithAISDK.effect("uses the model modelID for the default language model", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const language = yield* aisdk.language(
ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("custom"), ModelV2.ID.make("alias")),
modelID: ModelV2.ID.make("test-model-api"),
package: ProviderV2.aisdk(fixtureProvider),
Model.Info.make({
...Model.Info.default(Provider.ID.make("custom"), Model.ID.make("alias")),
modelID: Model.ID.make("test-model-api"),
package: Provider.aisdk(fixtureProvider),
}),
)
expect(language).toMatchObject({ modelID: "test-model-api", options: { name: "custom" } })

View file

@ -1,8 +1,8 @@
import { expect } from "bun:test"
import { Effect } from "effect"
import { AISDK } from "@opencode-ai/core/aisdk"
import { ModelV2 } from "@opencode-ai/core/model"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { Model } from "@opencode-ai/core/model"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { AlibabaPlugin } from "@opencode-ai/core/plugin/provider/alibaba"
import { CoherePlugin } from "@opencode-ai/core/plugin/provider/cohere"
@ -13,11 +13,11 @@ import { MistralPlugin } from "@opencode-ai/core/plugin/provider/mistral"
import { PerplexityPlugin } from "@opencode-ai/core/plugin/provider/perplexity"
import { TogetherAIPlugin } from "@opencode-ai/core/plugin/provider/togetherai"
import { VenicePlugin } from "@opencode-ai/core/plugin/provider/venice"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Provider } from "@opencode-ai/core/provider"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
const modelID = ModelV2.ID.make("test-model")
const modelID = Model.ID.make("test-model")
const options = { name: "custom-provider", apiKey: "test", baseURL: "https://example.test" }
const providers = [
{ id: "alibaba", plugin: AlibabaPlugin, package: "@ai-sdk/alibaba", provider: "alibaba.chat" },
@ -36,14 +36,14 @@ const it = testEffect(PluginTestLayer)
providers.forEach((item) =>
it.effect(`${item.id} loads only its exact package`, () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const host = yield* PluginHost.make(plugin)
yield* item.plugin.effect(host)
const model = ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make(item.id), modelID),
const model = Model.Info.make({
...Model.Info.default(Provider.ID.make(item.id), modelID),
modelID,
package: ProviderV2.aisdk(item.package),
package: Provider.aisdk(item.package),
})
const matched = yield* aisdk.runSDK({ model, package: item.package, options })
const ignored = yield* aisdk.runSDK({ model, package: `${item.package}/unsupported`, options })

View file

@ -3,11 +3,11 @@ import { App } from "@opencode-ai/core/app"
import { describe, expect, test } from "bun:test"
import { Effect } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { ModelV2 } from "@opencode-ai/core/model"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { Model } from "@opencode-ai/core/model"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { copilotBaseURL, copilotFetch, GithubCopilotPlugin } from "@opencode-ai/core/plugin/provider/github-copilot"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Provider } from "@opencode-ai/core/provider"
import { Integration } from "@opencode-ai/core/integration"
import type { LanguageModelV3 } from "@ai-sdk/provider"
import { testEffect } from "../lib/effect"
@ -16,7 +16,7 @@ import { PluginTestLayer } from "./fixture"
const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const host = yield* PluginHost.make(plugin)
yield* GithubCopilotPlugin.effect(host)
@ -94,22 +94,22 @@ describe("GithubCopilotPlugin", () => {
it.effect("creates the bundled Copilot SDK for the GitHub Copilot package", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const ignored = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5")),
modelID: ModelV2.ID.make("gpt-5"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("github-copilot"), Model.ID.make("gpt-5")),
modelID: Model.ID.make("gpt-5"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/openai-compatible",
options: { name: "github-copilot" },
})
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5")),
modelID: ModelV2.ID.make("gpt-5"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("github-copilot"), Model.ID.make("gpt-5")),
modelID: Model.ID.make("gpt-5"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/github-copilot",
@ -122,14 +122,14 @@ describe("GithubCopilotPlugin", () => {
it.effect("selects languageModel when responses and chat are absent", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("claude-sonnet-4")),
modelID: ModelV2.ID.make("claude-sonnet-4"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("github-copilot"), Model.ID.make("claude-sonnet-4")),
modelID: Model.ID.make("claude-sonnet-4"),
package: "aisdk:test-provider",
}),
sdk: { languageModel: fakeSelectorSdk(calls).languageModel },
@ -141,14 +141,14 @@ describe("GithubCopilotPlugin", () => {
it.effect("selects languageModel with the API model ID when responses and chat are absent", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("alias")),
modelID: ModelV2.ID.make("claude-sonnet-4"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("github-copilot"), Model.ID.make("alias")),
modelID: Model.ID.make("claude-sonnet-4"),
package: "aisdk:test-provider",
}),
sdk: { languageModel: fakeSelectorSdk(calls).languageModel },
@ -160,50 +160,50 @@ describe("GithubCopilotPlugin", () => {
it.effect("uses responses for gpt-5 models except gpt-5-mini", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5")),
modelID: ModelV2.ID.make("gpt-5"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("github-copilot"), Model.ID.make("gpt-5")),
modelID: Model.ID.make("gpt-5"),
package: "aisdk:test-provider",
}),
sdk: fakeSelectorSdk(calls),
options: {},
})
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5.1-codex")),
modelID: ModelV2.ID.make("gpt-5.1-codex"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("github-copilot"), Model.ID.make("gpt-5.1-codex")),
modelID: Model.ID.make("gpt-5.1-codex"),
package: "aisdk:test-provider",
}),
sdk: fakeSelectorSdk(calls),
options: {},
})
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-4o")),
modelID: ModelV2.ID.make("gpt-4o"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("github-copilot"), Model.ID.make("gpt-4o")),
modelID: Model.ID.make("gpt-4o"),
package: "aisdk:test-provider",
}),
sdk: fakeSelectorSdk(calls),
options: {},
})
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5-mini")),
modelID: ModelV2.ID.make("gpt-5-mini"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("github-copilot"), Model.ID.make("gpt-5-mini")),
modelID: Model.ID.make("gpt-5-mini"),
package: "aisdk:test-provider",
}),
sdk: fakeSelectorSdk(calls),
options: {},
})
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5-mini-2025-08-07")),
modelID: ModelV2.ID.make("gpt-5-mini-2025-08-07"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("github-copilot"), Model.ID.make("gpt-5-mini-2025-08-07")),
modelID: Model.ID.make("gpt-5-mini-2025-08-07"),
package: "aisdk:test-provider",
}),
sdk: fakeSelectorSdk(calls),
@ -221,14 +221,14 @@ describe("GithubCopilotPlugin", () => {
it.effect("uses advertised Copilot endpoint metadata before model ID fallbacks", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("mai-code-1-flash-picker")),
modelID: ModelV2.ID.make("mai-code-1-flash-picker"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("github-copilot"), Model.ID.make("mai-code-1-flash-picker")),
modelID: Model.ID.make("mai-code-1-flash-picker"),
package: "aisdk:test-provider",
settings: { endpoint: "responses" },
}),
@ -236,9 +236,9 @@ describe("GithubCopilotPlugin", () => {
options: { endpoint: "responses" },
})
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5")),
modelID: ModelV2.ID.make("gpt-5"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("github-copilot"), Model.ID.make("gpt-5")),
modelID: Model.ID.make("gpt-5"),
package: "aisdk:test-provider",
settings: { endpoint: "chat" },
}),
@ -251,32 +251,32 @@ describe("GithubCopilotPlugin", () => {
it.effect("uses the API model ID when selecting responses or chat", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("default")),
modelID: ModelV2.ID.make("gpt-5"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("github-copilot"), Model.ID.make("default")),
modelID: Model.ID.make("gpt-5"),
package: "aisdk:test-provider",
}),
sdk: fakeSelectorSdk(calls),
options: {},
})
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("small")),
modelID: ModelV2.ID.make("gpt-5-mini"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("github-copilot"), Model.ID.make("small")),
modelID: Model.ID.make("gpt-5-mini"),
package: "aisdk:test-provider",
}),
sdk: fakeSelectorSdk(calls),
options: {},
})
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("sonnet")),
modelID: ModelV2.ID.make("claude-sonnet-4"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("github-copilot"), Model.ID.make("sonnet")),
modelID: Model.ID.make("claude-sonnet-4"),
package: "aisdk:test-provider",
}),
sdk: fakeSelectorSdk(calls),
@ -290,12 +290,12 @@ describe("GithubCopilotPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
catalog.provider.update(ProviderV2.ID.make("github-copilot"), () => {})
catalog.model.update(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5-chat-latest"), () => {})
catalog.provider.update(Provider.ID.make("github-copilot"), () => {})
catalog.model.update(Provider.ID.make("github-copilot"), Model.ID.make("gpt-5-chat-latest"), () => {})
})
yield* addPlugin()
expect(
required(yield* catalog.model.get(ProviderV2.ID.make("github-copilot"), ModelV2.ID.make("gpt-5-chat-latest")))
required(yield* catalog.model.get(Provider.ID.make("github-copilot"), Model.ID.make("gpt-5-chat-latest")))
.enabled,
).toBe(false)
}),
@ -305,12 +305,12 @@ describe("GithubCopilotPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
catalog.provider.update(ProviderV2.ID.make("custom-copilot"), () => {})
catalog.model.update(ProviderV2.ID.make("custom-copilot"), ModelV2.ID.make("gpt-5-chat-latest"), () => {})
catalog.provider.update(Provider.ID.make("custom-copilot"), () => {})
catalog.model.update(Provider.ID.make("custom-copilot"), Model.ID.make("gpt-5-chat-latest"), () => {})
})
yield* addPlugin()
expect(
required(yield* catalog.model.get(ProviderV2.ID.make("custom-copilot"), ModelV2.ID.make("gpt-5-chat-latest")))
required(yield* catalog.model.get(Provider.ID.make("custom-copilot"), Model.ID.make("gpt-5-chat-latest")))
.enabled,
).toBe(true)
}),
@ -318,14 +318,14 @@ describe("GithubCopilotPlugin", () => {
it.effect("ignores non-Copilot providers", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin()
const result = yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("openai"), ModelV2.ID.make("gpt-5")),
modelID: ModelV2.ID.make("gpt-5"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("openai"), Model.ID.make("gpt-5")),
modelID: Model.ID.make("gpt-5"),
package: "aisdk:test-provider",
}),
sdk: fakeSelectorSdk(calls),

View file

@ -2,11 +2,11 @@ import { AISDK } from "@opencode-ai/core/aisdk"
import { describe, expect, mock } from "bun:test"
import { Effect } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { ModelV2 } from "@opencode-ai/core/model"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { Model } from "@opencode-ai/core/model"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { GitLabPlugin } from "@opencode-ai/core/plugin/provider/gitlab"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Provider } from "@opencode-ai/core/provider"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
@ -14,7 +14,7 @@ const gitlabSDKOptions: Record<string, unknown>[] = []
const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const host = yield* PluginHost.make(plugin)
yield* GitLabPlugin.effect(host)
@ -64,13 +64,13 @@ describe("GitLabPlugin", () => {
() =>
Effect.gen(function* () {
gitlabSDKOptions.length = 0
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("claude")),
modelID: ModelV2.ID.make("claude"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("gitlab"), Model.ID.make("claude")),
modelID: Model.ID.make("claude"),
package: "aisdk:test-provider",
}),
package: "gitlab-ai-provider",
@ -102,13 +102,13 @@ describe("GitLabPlugin", () => {
() =>
Effect.gen(function* () {
gitlabSDKOptions.length = 0
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("claude")),
modelID: ModelV2.ID.make("claude"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("gitlab"), Model.ID.make("claude")),
modelID: Model.ID.make("claude"),
package: "aisdk:test-provider",
}),
package: "gitlab-ai-provider",
@ -128,13 +128,13 @@ describe("GitLabPlugin", () => {
() =>
Effect.gen(function* () {
gitlabSDKOptions.length = 0
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("claude")),
modelID: ModelV2.ID.make("claude"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("gitlab"), Model.ID.make("claude")),
modelID: Model.ID.make("claude"),
package: "aisdk:test-provider",
}),
package: "gitlab-ai-provider",
@ -170,13 +170,13 @@ describe("GitLabPlugin", () => {
it.effect("ignores non-GitLab SDK packages", () =>
Effect.gen(function* () {
gitlabSDKOptions.length = 0
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("claude")),
modelID: ModelV2.ID.make("claude"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("gitlab"), Model.ID.make("claude")),
modelID: Model.ID.make("claude"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/openai",
@ -189,14 +189,14 @@ describe("GitLabPlugin", () => {
it.effect("uses workflowChat for duo workflow models and preserves selectedModelRef", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: [string, unknown][] = []
yield* addPlugin()
const result = yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("duo-workflow-custom")),
modelID: ModelV2.ID.make("duo-workflow-custom"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("gitlab"), Model.ID.make("duo-workflow-custom")),
modelID: Model.ID.make("duo-workflow-custom"),
package: "aisdk:test-provider",
headers: {},
settings: { workflowRef: "ref", workflowDefinition: "definition" },
@ -223,14 +223,14 @@ describe("GitLabPlugin", () => {
it.effect("uses exact static workflow model ids when the provider recognizes them", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: [string, unknown][] = []
yield* addPlugin()
const result = yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("duo-workflow-exact")),
modelID: ModelV2.ID.make("duo-workflow-exact"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("gitlab"), Model.ID.make("duo-workflow-exact")),
modelID: Model.ID.make("duo-workflow-exact"),
package: "aisdk:test-provider",
}),
sdk: {
@ -251,14 +251,14 @@ describe("GitLabPlugin", () => {
it.effect("uses provider feature flags instead of model settings feature flags", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: [string, unknown][] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("duo-workflow-custom")),
modelID: ModelV2.ID.make("duo-workflow-custom"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("gitlab"), Model.ID.make("duo-workflow-custom")),
modelID: Model.ID.make("duo-workflow-custom"),
package: "aisdk:test-provider",
headers: {},
settings: { featureFlags: { request_flag: true } },
@ -278,14 +278,14 @@ describe("GitLabPlugin", () => {
it.effect("uses agenticChat with provider aiGatewayHeaders and feature flags for normal models", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: [string, unknown][] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("gitlab"), ModelV2.ID.make("claude")),
modelID: ModelV2.ID.make("claude"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("gitlab"), Model.ID.make("claude")),
modelID: Model.ID.make("claude"),
package: "aisdk:test-provider",
headers: { h: "v" },
settings: {},

View file

@ -3,18 +3,18 @@ import type { LanguageModelV3 } from "@ai-sdk/provider"
import { describe, expect } from "bun:test"
import { Effect } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { ModelV2 } from "@opencode-ai/core/model"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { Model } from "@opencode-ai/core/model"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { GoogleVertexAnthropicPlugin, GoogleVertexPlugin } from "@opencode-ai/core/plugin/provider/google-vertex"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Provider } from "@opencode-ai/core/provider"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* (definition: typeof GoogleVertexAnthropicPlugin | typeof GoogleVertexPlugin) {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const host = yield* PluginHost.make(plugin)
yield* definition.effect(host)
@ -63,15 +63,15 @@ describe("GoogleVertexAnthropicPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) =>
catalog.provider.update(ProviderV2.ID.make("google-vertex-anthropic"), (provider) => {
provider.package = ProviderV2.aisdk("@ai-sdk/google-vertex/anthropic")
catalog.provider.update(Provider.ID.make("google-vertex-anthropic"), (provider) => {
provider.package = Provider.aisdk("@ai-sdk/google-vertex/anthropic")
}),
)
yield* addPlugin(GoogleVertexAnthropicPlugin)
expect((yield* catalog.provider.get(ProviderV2.ID.make("google-vertex-anthropic")))?.settings?.project).toBe(
expect((yield* catalog.provider.get(Provider.ID.make("google-vertex-anthropic")))?.settings?.project).toBe(
"cloud-project",
)
expect((yield* catalog.provider.get(ProviderV2.ID.make("google-vertex-anthropic")))?.settings?.location).toBe(
expect((yield* catalog.provider.get(Provider.ID.make("google-vertex-anthropic")))?.settings?.location).toBe(
"cloud-location",
)
}),
@ -83,16 +83,16 @@ describe("GoogleVertexAnthropicPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) =>
catalog.provider.update(ProviderV2.ID.make("google-vertex-anthropic"), (provider) => {
provider.package = ProviderV2.aisdk("@ai-sdk/google-vertex/anthropic")
catalog.provider.update(Provider.ID.make("google-vertex-anthropic"), (provider) => {
provider.package = Provider.aisdk("@ai-sdk/google-vertex/anthropic")
provider.settings = { ...provider.settings, project: "configured-project", location: "configured-location" }
}),
)
yield* addPlugin(GoogleVertexAnthropicPlugin)
expect((yield* catalog.provider.get(ProviderV2.ID.make("google-vertex-anthropic")))?.settings?.project).toBe(
expect((yield* catalog.provider.get(Provider.ID.make("google-vertex-anthropic")))?.settings?.project).toBe(
"configured-project",
)
expect((yield* catalog.provider.get(ProviderV2.ID.make("google-vertex-anthropic")))?.settings?.location).toBe(
expect((yield* catalog.provider.get(Provider.ID.make("google-vertex-anthropic")))?.settings?.location).toBe(
"configured-location",
)
}),
@ -111,16 +111,16 @@ describe("GoogleVertexAnthropicPlugin", () => {
},
() =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin(GoogleVertexAnthropicPlugin)
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(
ProviderV2.ID.make("google-vertex-anthropic"),
ModelV2.ID.make("claude-sonnet-4-5"),
model: Model.Info.make({
...Model.Info.default(
Provider.ID.make("google-vertex-anthropic"),
Model.ID.make("claude-sonnet-4-5"),
),
modelID: ModelV2.ID.make("claude-sonnet-4-5"),
modelID: Model.ID.make("claude-sonnet-4-5"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/google-vertex/anthropic",
@ -138,16 +138,16 @@ describe("GoogleVertexAnthropicPlugin", () => {
{ GOOGLE_CLOUD_PROJECT: "project", GOOGLE_CLOUD_LOCATION: "cloud-location", VERTEX_LOCATION: "vertex-location" },
() =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin(GoogleVertexAnthropicPlugin)
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(
ProviderV2.ID.make("google-vertex-anthropic"),
ModelV2.ID.make("claude-sonnet-4-5"),
model: Model.Info.make({
...Model.Info.default(
Provider.ID.make("google-vertex-anthropic"),
Model.ID.make("claude-sonnet-4-5"),
),
modelID: ModelV2.ID.make("claude-sonnet-4-5"),
modelID: Model.ID.make("claude-sonnet-4-5"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/google-vertex/anthropic",
@ -162,13 +162,13 @@ describe("GoogleVertexAnthropicPlugin", () => {
it.effect("creates SDKs for google-vertex Anthropic models with multi-region endpoints", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin(GoogleVertexAnthropicPlugin)
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make("claude-sonnet-4-5")),
modelID: ModelV2.ID.make("claude-sonnet-4-5"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("google-vertex"), Model.ID.make("claude-sonnet-4-5")),
modelID: Model.ID.make("claude-sonnet-4-5"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/google-vertex/anthropic",
@ -182,13 +182,13 @@ describe("GoogleVertexAnthropicPlugin", () => {
it.effect("keeps configured baseURL for google-vertex Anthropic models", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin(GoogleVertexAnthropicPlugin)
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make("claude-sonnet-4-5")),
modelID: ModelV2.ID.make("claude-sonnet-4-5"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("google-vertex"), Model.ID.make("claude-sonnet-4-5")),
modelID: Model.ID.make("claude-sonnet-4-5"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/google-vertex/anthropic",
@ -198,25 +198,25 @@ describe("GoogleVertexAnthropicPlugin", () => {
}),
)
it.effect("selects google-vertex Anthropic language models through V2 plugins", () =>
it.effect("selects google-vertex Anthropic language models through plugins", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin(GoogleVertexPlugin)
yield* addPlugin(GoogleVertexAnthropicPlugin)
const sdkResult = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make(" claude-sonnet-4-5 ")),
modelID: ModelV2.ID.make(" claude-sonnet-4-5 "),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("google-vertex"), Model.ID.make(" claude-sonnet-4-5 ")),
modelID: Model.ID.make(" claude-sonnet-4-5 "),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/google-vertex/anthropic",
options: { name: "google-vertex", project: "project", location: "us" },
})
const languageResult = yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make(" claude-sonnet-4-5 ")),
modelID: ModelV2.ID.make(" claude-sonnet-4-5 "),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("google-vertex"), Model.ID.make(" claude-sonnet-4-5 ")),
modelID: Model.ID.make(" claude-sonnet-4-5 "),
package: "aisdk:test-provider",
}),
sdk: sdkResult.sdk,
@ -232,14 +232,14 @@ describe("GoogleVertexAnthropicPlugin", () => {
it.effect("trims model IDs before selecting language models", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin(GoogleVertexAnthropicPlugin)
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("google-vertex-anthropic"), ModelV2.ID.make(" claude-sonnet-4-5 ")),
modelID: ModelV2.ID.make(" claude-sonnet-4-5 "),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("google-vertex-anthropic"), Model.ID.make(" claude-sonnet-4-5 ")),
modelID: Model.ID.make(" claude-sonnet-4-5 "),
package: "aisdk:test-provider",
}),
sdk: { languageModel: selector(calls) },
@ -251,14 +251,14 @@ describe("GoogleVertexAnthropicPlugin", () => {
it.effect("ignores non Vertex Anthropic providers for language selection", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin(GoogleVertexAnthropicPlugin)
const result = yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make("claude-sonnet-4-5")),
modelID: ModelV2.ID.make("claude-sonnet-4-5"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("google-vertex"), Model.ID.make("claude-sonnet-4-5")),
modelID: Model.ID.make("claude-sonnet-4-5"),
package: "aisdk:test-provider",
}),
sdk: { languageModel: selector(calls) },

View file

@ -2,11 +2,11 @@ import { AISDK } from "@opencode-ai/core/aisdk"
import { describe, expect, mock } from "bun:test"
import { Effect } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { ModelV2 } from "@opencode-ai/core/model"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { Model } from "@opencode-ai/core/model"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { GoogleVertexPlugin } from "@opencode-ai/core/plugin/provider/google-vertex"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Provider } from "@opencode-ai/core/provider"
import type { LanguageModelV3 } from "@ai-sdk/provider"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
@ -16,7 +16,7 @@ const googleAuthOptions: Record<string, any>[] = []
const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const host = yield* PluginHost.make(plugin)
yield* GoogleVertexPlugin.effect(host)
@ -91,14 +91,14 @@ describe("GoogleVertexPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) =>
catalog.provider.update(ProviderV2.ID.opencode, (provider) => {
provider.package = ProviderV2.aisdk("@ai-sdk/openai-compatible")
catalog.provider.update(Provider.ID.opencode, (provider) => {
provider.package = Provider.aisdk("@ai-sdk/openai-compatible")
provider.settings = { ...provider.settings, baseURL: "https://opencode.ai/zen/v1" }
}),
)
yield* addPlugin()
const provider = required(yield* catalog.provider.get(ProviderV2.ID.opencode))
const provider = required(yield* catalog.provider.get(Provider.ID.opencode))
expect(provider.settings).toEqual({ baseURL: "https://opencode.ai/zen/v1" })
}),
)
@ -117,8 +117,8 @@ describe("GoogleVertexPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) =>
catalog.provider.update(ProviderV2.ID.make("google-vertex"), (provider) => {
provider.package = ProviderV2.aisdk("@ai-sdk/openai-compatible")
catalog.provider.update(Provider.ID.make("google-vertex"), (provider) => {
provider.package = Provider.aisdk("@ai-sdk/openai-compatible")
provider.settings = {
...provider.settings,
baseURL:
@ -127,7 +127,7 @@ describe("GoogleVertexPlugin", () => {
}),
)
yield* addPlugin()
const provider = required(yield* catalog.provider.get(ProviderV2.ID.make("google-vertex")))
const provider = required(yield* catalog.provider.get(Provider.ID.make("google-vertex")))
expect(provider.settings?.project).toBe("google-cloud-project")
expect(provider.settings?.location).toBe("google-vertex-location")
expect(provider).toMatchObject({
@ -155,12 +155,12 @@ describe("GoogleVertexPlugin", () => {
() =>
Effect.gen(function* () {
vertexOptions.length = 0
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) =>
catalog.provider.update(ProviderV2.ID.make("google-vertex"), (provider) => {
provider.package = ProviderV2.aisdk("@ai-sdk/openai-compatible")
catalog.provider.update(Provider.ID.make("google-vertex"), (provider) => {
provider.package = Provider.aisdk("@ai-sdk/openai-compatible")
provider.settings = {
...provider.settings,
baseURL:
@ -169,11 +169,11 @@ describe("GoogleVertexPlugin", () => {
}),
)
yield* addPlugin()
const provider = required(yield* catalog.provider.get(ProviderV2.ID.make("google-vertex")))
const provider = required(yield* catalog.provider.get(Provider.ID.make("google-vertex")))
yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make("gemini")),
modelID: ModelV2.ID.make("gemini"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("google-vertex"), Model.ID.make("gemini")),
modelID: Model.ID.make("gemini"),
package: "aisdk:@ai-sdk/google-vertex",
}),
package: "@ai-sdk/google-vertex",
@ -208,8 +208,8 @@ describe("GoogleVertexPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) =>
catalog.provider.update(ProviderV2.ID.make("google-vertex"), (provider) => {
provider.package = ProviderV2.aisdk("@ai-sdk/openai-compatible")
catalog.provider.update(Provider.ID.make("google-vertex"), (provider) => {
provider.package = Provider.aisdk("@ai-sdk/openai-compatible")
provider.settings = {
...provider.settings,
baseURL:
@ -219,7 +219,7 @@ describe("GoogleVertexPlugin", () => {
}),
)
yield* addPlugin()
const provider = required(yield* catalog.provider.get(ProviderV2.ID.make("google-vertex")))
const provider = required(yield* catalog.provider.get(Provider.ID.make("google-vertex")))
expect(provider.settings?.project).toBe("config-project")
expect(provider.settings?.location).toBe("global")
expect(provider).toMatchObject({
@ -234,8 +234,8 @@ describe("GoogleVertexPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) =>
catalog.provider.update(ProviderV2.ID.make("google-vertex"), (provider) => {
provider.package = ProviderV2.aisdk("@ai-sdk/openai-compatible")
catalog.provider.update(Provider.ID.make("google-vertex"), (provider) => {
provider.package = Provider.aisdk("@ai-sdk/openai-compatible")
provider.settings = {
...provider.settings,
baseURL:
@ -245,7 +245,7 @@ describe("GoogleVertexPlugin", () => {
}),
)
yield* addPlugin()
const provider = required(yield* catalog.provider.get(ProviderV2.ID.make("google-vertex")))
const provider = required(yield* catalog.provider.get(Provider.ID.make("google-vertex")))
expect(provider).toMatchObject({
package: "aisdk:@ai-sdk/openai-compatible",
settings: { baseURL: "https://eu-aiplatform.googleapis.com/v1/projects/config-project/locations/eu" },
@ -267,13 +267,13 @@ describe("GoogleVertexPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) =>
catalog.provider.update(ProviderV2.ID.make("google-vertex"), (provider) => {
provider.package = ProviderV2.aisdk("@ai-sdk/google-vertex")
catalog.provider.update(Provider.ID.make("google-vertex"), (provider) => {
provider.package = Provider.aisdk("@ai-sdk/google-vertex")
provider.settings = { ...provider.settings, project: "config-project" }
}),
)
yield* addPlugin()
const provider = required(yield* catalog.provider.get(ProviderV2.ID.make("google-vertex")))
const provider = required(yield* catalog.provider.get(Provider.ID.make("google-vertex")))
expect(provider.settings?.project).toBe("config-project")
expect(provider.settings?.location).toBe("us-central1")
}),
@ -289,13 +289,13 @@ describe("GoogleVertexPlugin", () => {
() =>
Effect.gen(function* () {
vertexOptions.length = 0
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make("gemini")),
modelID: ModelV2.ID.make("gemini"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("google-vertex"), Model.ID.make("gemini")),
modelID: Model.ID.make("gemini"),
package: "aisdk:@ai-sdk/google-vertex",
}),
package: "@ai-sdk/google-vertex",
@ -313,7 +313,7 @@ describe("GoogleVertexPlugin", () => {
Effect.gen(function* () {
googleAuthOptions.length = 0
const fetchCalls: { input: Parameters<typeof fetch>[0]; init?: RequestInit }[] = []
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
yield* aisdk.hook.sdk((evt) =>
@ -338,9 +338,9 @@ describe("GoogleVertexPlugin", () => {
Effect.void,
() =>
aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make("gemini")),
modelID: ModelV2.ID.make("gemini"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("google-vertex"), Model.ID.make("gemini")),
modelID: Model.ID.make("gemini"),
package: "aisdk:@ai-sdk/openai-compatible",
}),
package: "@ai-sdk/openai-compatible",
@ -361,14 +361,14 @@ describe("GoogleVertexPlugin", () => {
it.effect("trims model IDs before selecting language models", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin()
yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("google-vertex"), ModelV2.ID.make(" gemini-2.5-pro ")),
modelID: ModelV2.ID.make(" gemini-2.5-pro "),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("google-vertex"), Model.ID.make(" gemini-2.5-pro ")),
modelID: Model.ID.make(" gemini-2.5-pro "),
package: "aisdk:test-provider",
}),
sdk: { languageModel: fakeSelectorSdk(calls).languageModel },

View file

@ -1,18 +1,18 @@
import { AISDK } from "@opencode-ai/core/aisdk"
import { describe, expect } from "bun:test"
import { Effect } from "effect"
import { ModelV2 } from "@opencode-ai/core/model"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { Model } from "@opencode-ai/core/model"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { GooglePlugin } from "@opencode-ai/core/plugin/provider/google"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Provider } from "@opencode-ai/core/provider"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const host = yield* PluginHost.make(plugin)
yield* GooglePlugin.effect(host)
@ -21,13 +21,13 @@ const addPlugin = Effect.fn(function* () {
describe("GooglePlugin", () => {
it.effect("creates a Google Generative AI SDK for @ai-sdk/google using the provider ID as SDK name", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("custom-google"), ModelV2.ID.make("gemini")),
modelID: ModelV2.ID.make("gemini"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("custom-google"), Model.ID.make("gemini")),
modelID: Model.ID.make("gemini"),
package: "aisdk:@ai-sdk/google",
}),
package: "@ai-sdk/google",
@ -40,13 +40,13 @@ describe("GooglePlugin", () => {
it.effect("ignores non-Google SDK packages", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("google"), ModelV2.ID.make("gemini")),
modelID: ModelV2.ID.make("gemini"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("google"), Model.ID.make("gemini")),
modelID: Model.ID.make("gemini"),
package: "aisdk:@ai-sdk/google",
}),
package: "@ai-sdk/google-vertex",
@ -58,13 +58,13 @@ describe("GooglePlugin", () => {
it.effect("uses default languageModel loading with provider ID parity", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const sdkEvent = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("custom-google"), ModelV2.ID.make("alias")),
modelID: ModelV2.ID.make("gemini-api"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("custom-google"), Model.ID.make("alias")),
modelID: Model.ID.make("gemini-api"),
package: "aisdk:@ai-sdk/google",
}),
package: "@ai-sdk/google",
@ -87,9 +87,9 @@ describe("GooglePlugin", () => {
yield* addPlugin()
const resolved = yield* aisdk.model(
ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("custom-google"), ModelV2.ID.make("alias")),
modelID: ModelV2.ID.make("gemini-api"),
Model.Info.make({
...Model.Info.default(Provider.ID.make("custom-google"), Model.ID.make("alias")),
modelID: Model.ID.make("gemini-api"),
package: "aisdk:@ai-sdk/google",
settings: { apiKey: "test" },
}),

View file

@ -1,18 +1,18 @@
import { describe, expect } from "bun:test"
import { Effect } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { ProviderPlugins } from "@opencode-ai/core/plugin/provider"
import { KiloPlugin } from "@opencode-ai/core/plugin/provider/kilo"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Provider } from "@opencode-ai/core/provider"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const host = yield* PluginHost.make(plugin)
yield* KiloPlugin.effect(host)
})
@ -26,20 +26,20 @@ describe("KiloPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
catalog.provider.update(ProviderV2.ID.make("kilo"), (provider) => {
provider.package = ProviderV2.aisdk("@ai-sdk/openai-compatible")
catalog.provider.update(Provider.ID.make("kilo"), (provider) => {
provider.package = Provider.aisdk("@ai-sdk/openai-compatible")
provider.settings = { baseURL: "https://api.kilo.ai/api/gateway" }
provider.headers = { Existing: "value" }
})
catalog.provider.update(ProviderV2.ID.openrouter, () => {})
catalog.provider.update(Provider.ID.openrouter, () => {})
})
yield* addPlugin()
expect((yield* catalog.provider.get(ProviderV2.ID.make("kilo")))?.headers).toEqual({
expect((yield* catalog.provider.get(Provider.ID.make("kilo")))?.headers).toEqual({
Existing: "value",
"HTTP-Referer": "https://opencode.ai/",
"X-Title": "opencode",
})
expect((yield* catalog.provider.get(ProviderV2.ID.openrouter))?.headers).toBeUndefined()
expect((yield* catalog.provider.get(Provider.ID.openrouter))?.headers).toBeUndefined()
}),
)
@ -47,20 +47,20 @@ describe("KiloPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
catalog.provider.update(ProviderV2.ID.make("kilo"), (provider) => {
provider.package = ProviderV2.aisdk("@ai-sdk/openai-compatible")
catalog.provider.update(Provider.ID.make("kilo"), (provider) => {
provider.package = Provider.aisdk("@ai-sdk/openai-compatible")
provider.settings = { baseURL: "https://api.kilo.ai/api/gateway" }
})
})
yield* addPlugin()
expect((yield* catalog.provider.get(ProviderV2.ID.make("kilo")))?.headers).toEqual({
expect((yield* catalog.provider.get(Provider.ID.make("kilo")))?.headers).toEqual({
"HTTP-Referer": "https://opencode.ai/",
"X-Title": "opencode",
})
expect((yield* catalog.provider.get(ProviderV2.ID.make("kilo")))?.headers).not.toHaveProperty("http-referer")
expect((yield* catalog.provider.get(ProviderV2.ID.make("kilo")))?.headers).not.toHaveProperty("x-title")
expect((yield* catalog.provider.get(ProviderV2.ID.make("kilo")))?.headers).not.toHaveProperty("X-Source")
expect((yield* catalog.provider.get(Provider.ID.make("kilo")))?.headers).not.toHaveProperty("http-referer")
expect((yield* catalog.provider.get(Provider.ID.make("kilo")))?.headers).not.toHaveProperty("x-title")
expect((yield* catalog.provider.get(Provider.ID.make("kilo")))?.headers).not.toHaveProperty("X-Source")
}),
)
@ -68,18 +68,18 @@ describe("KiloPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
catalog.provider.update(ProviderV2.ID.make("kilo"), (provider) => {
provider.package = ProviderV2.aisdk("kilo")
catalog.provider.update(Provider.ID.make("kilo"), (provider) => {
provider.package = Provider.aisdk("kilo")
})
catalog.provider.update(ProviderV2.ID.make("custom-kilo"), (provider) => {
provider.package = ProviderV2.aisdk("@ai-sdk/openai-compatible")
catalog.provider.update(Provider.ID.make("custom-kilo"), (provider) => {
provider.package = Provider.aisdk("@ai-sdk/openai-compatible")
provider.settings = { baseURL: "https://api.kilo.ai/api/gateway" }
})
})
yield* addPlugin()
expect((yield* catalog.provider.get(ProviderV2.ID.make("kilo")))?.headers).toBeUndefined()
expect((yield* catalog.provider.get(ProviderV2.ID.make("custom-kilo")))?.headers).toEqual({
expect((yield* catalog.provider.get(Provider.ID.make("kilo")))?.headers).toBeUndefined()
expect((yield* catalog.provider.get(Provider.ID.make("custom-kilo")))?.headers).toEqual({
"HTTP-Referer": "https://opencode.ai/",
"X-Title": "opencode",
})

View file

@ -2,18 +2,18 @@ import { describe, expect } from "bun:test"
import { Effect } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { Integration } from "@opencode-ai/core/integration"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { ProviderPlugins } from "@opencode-ai/core/plugin/provider"
import { LLMGatewayPlugin } from "@opencode-ai/core/plugin/provider/llmgateway"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Provider } from "@opencode-ai/core/provider"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const host = yield* PluginHost.make(plugin)
const integration = yield* Integration.Service
yield* LLMGatewayPlugin.effect(host).pipe(Effect.provideService(Integration.Service, integration))
@ -33,21 +33,21 @@ describe("LLMGatewayPlugin", () => {
editor.update(Integration.ID.make("openrouter"), () => {})
})
yield* catalog.transform((catalog) => {
catalog.provider.update(ProviderV2.ID.make("llmgateway"), (provider) => {
provider.package = ProviderV2.aisdk("@ai-sdk/openai-compatible")
catalog.provider.update(Provider.ID.make("llmgateway"), (provider) => {
provider.package = Provider.aisdk("@ai-sdk/openai-compatible")
provider.settings = { baseURL: "https://api.llmgateway.io/v1" }
provider.headers = { Existing: "value" }
})
catalog.provider.update(ProviderV2.ID.openrouter, () => {})
catalog.provider.update(Provider.ID.openrouter, () => {})
})
yield* addPlugin()
expect((yield* catalog.provider.get(ProviderV2.ID.make("llmgateway")))?.headers).toEqual({
expect((yield* catalog.provider.get(Provider.ID.make("llmgateway")))?.headers).toEqual({
Existing: "value",
"HTTP-Referer": "https://opencode.ai/",
"X-Title": "opencode",
"X-Source": "opencode",
})
expect((yield* catalog.provider.get(ProviderV2.ID.openrouter))?.headers).toBeUndefined()
expect((yield* catalog.provider.get(Provider.ID.openrouter))?.headers).toBeUndefined()
}),
)
@ -59,16 +59,16 @@ describe("LLMGatewayPlugin", () => {
editor.update(Integration.ID.make("llmgateway"), () => {})
})
yield* catalog.transform((catalog) => {
catalog.provider.update(ProviderV2.ID.make("llmgateway"), (provider) => {
catalog.provider.update(Provider.ID.make("llmgateway"), (provider) => {
provider.disabled = true
provider.package = ProviderV2.aisdk("@ai-sdk/openai-compatible")
provider.package = Provider.aisdk("@ai-sdk/openai-compatible")
provider.settings = { baseURL: "https://api.llmgateway.io/v1" }
})
})
yield* addPlugin()
expect((yield* catalog.provider.get(ProviderV2.ID.make("llmgateway")))?.disabled).toBe(true)
expect((yield* catalog.provider.get(ProviderV2.ID.make("llmgateway")))?.headers).toBeUndefined()
expect((yield* catalog.provider.get(Provider.ID.make("llmgateway")))?.disabled).toBe(true)
expect((yield* catalog.provider.get(Provider.ID.make("llmgateway")))?.headers).toBeUndefined()
}),
)
})

View file

@ -1,18 +1,18 @@
import { describe, expect } from "bun:test"
import { Effect } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { ProviderPlugins } from "@opencode-ai/core/plugin/provider"
import { NvidiaPlugin } from "@opencode-ai/core/plugin/provider/nvidia"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Provider } from "@opencode-ai/core/provider"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const host = yield* PluginHost.make(plugin)
yield* NvidiaPlugin.effect(host)
})
@ -26,21 +26,21 @@ describe("NvidiaPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
catalog.provider.update(ProviderV2.ID.make("nvidia"), (provider) => {
provider.package = ProviderV2.aisdk("@ai-sdk/openai-compatible")
catalog.provider.update(Provider.ID.make("nvidia"), (provider) => {
provider.package = Provider.aisdk("@ai-sdk/openai-compatible")
provider.settings = { baseURL: "https://integrate.api.nvidia.com/v1" }
provider.headers = { Existing: "value" }
})
catalog.provider.update(ProviderV2.ID.openrouter, () => {})
catalog.provider.update(Provider.ID.openrouter, () => {})
})
yield* addPlugin()
expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia")))?.headers).toEqual({
expect((yield* catalog.provider.get(Provider.ID.make("nvidia")))?.headers).toEqual({
Existing: "value",
"HTTP-Referer": "https://opencode.ai/",
"X-Title": "opencode",
"X-BILLING-INVOKE-ORIGIN": "OpenCode",
})
expect((yield* catalog.provider.get(ProviderV2.ID.openrouter))?.headers).toBeUndefined()
expect((yield* catalog.provider.get(Provider.ID.openrouter))?.headers).toBeUndefined()
}),
)
@ -48,14 +48,14 @@ describe("NvidiaPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
catalog.provider.update(ProviderV2.ID.make("nvidia"), (provider) => {
provider.package = ProviderV2.aisdk("@ai-sdk/openai-compatible")
catalog.provider.update(Provider.ID.make("nvidia"), (provider) => {
provider.package = Provider.aisdk("@ai-sdk/openai-compatible")
provider.settings = { baseURL: "https://integrate.api.nvidia.com/v1" }
})
})
yield* addPlugin()
expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia")))?.headers).toEqual({
expect((yield* catalog.provider.get(Provider.ID.make("nvidia")))?.headers).toEqual({
"HTTP-Referer": "https://opencode.ai/",
"X-Title": "opencode",
"X-BILLING-INVOKE-ORIGIN": "OpenCode",
@ -67,15 +67,15 @@ describe("NvidiaPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
catalog.provider.update(ProviderV2.ID.make("nvidia"), (provider) => {
provider.package = ProviderV2.aisdk("@ai-sdk/openai-compatible")
catalog.provider.update(Provider.ID.make("nvidia"), (provider) => {
provider.package = Provider.aisdk("@ai-sdk/openai-compatible")
provider.settings = { baseURL: "https://integrate.api.nvidia.com/v1" }
provider.headers = { "X-BILLING-INVOKE-ORIGIN": "CustomOrigin" }
})
})
yield* addPlugin()
expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia")))?.headers).toEqual({
expect((yield* catalog.provider.get(Provider.ID.make("nvidia")))?.headers).toEqual({
"HTTP-Referer": "https://opencode.ai/",
"X-Title": "opencode",
"X-BILLING-INVOKE-ORIGIN": "CustomOrigin",

View file

@ -1,18 +1,18 @@
import { AISDK } from "@opencode-ai/core/aisdk"
import { describe, expect } from "bun:test"
import { Effect } from "effect"
import { ModelV2 } from "@opencode-ai/core/model"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { Model } from "@opencode-ai/core/model"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { OpenAICompatiblePlugin } from "@opencode-ai/core/plugin/provider/openai-compatible"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Provider } from "@opencode-ai/core/provider"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const host = yield* PluginHost.make(plugin)
yield* OpenAICompatiblePlugin.effect(host)
@ -21,22 +21,22 @@ const addPlugin = Effect.fn(function* () {
describe("OpenAICompatiblePlugin", () => {
it.effect("preserves explicit includeUsage false and defaults it to true", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const defaulted = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("custom"), ModelV2.ID.make("model")),
modelID: ModelV2.ID.make("model"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("custom"), Model.ID.make("model")),
modelID: Model.ID.make("model"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/openai-compatible",
options: { name: "custom" },
})
const disabled = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("custom"), ModelV2.ID.make("model")),
modelID: ModelV2.ID.make("model"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("custom"), Model.ID.make("model")),
modelID: Model.ID.make("model"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/openai-compatible",
@ -49,13 +49,13 @@ describe("OpenAICompatiblePlugin", () => {
it.effect("defaults includeUsage for OpenAI-compatible package matches", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("custom"), ModelV2.ID.make("model")),
modelID: ModelV2.ID.make("model"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("custom"), Model.ID.make("model")),
modelID: Model.ID.make("model"),
package: "aisdk:test-provider",
}),
package: "file:///tmp/@ai-sdk/openai-compatible-provider.js",
@ -67,7 +67,7 @@ describe("OpenAICompatiblePlugin", () => {
it.effect("uses the provider ID as the OpenAI-compatible provider name", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const observed: string[] = []
yield* addPlugin()
@ -77,9 +77,9 @@ describe("OpenAICompatiblePlugin", () => {
}),
)
yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("custom-provider"), ModelV2.ID.make("model")),
modelID: ModelV2.ID.make("model"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("custom-provider"), Model.ID.make("model")),
modelID: Model.ID.make("model"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/openai-compatible",
@ -98,9 +98,9 @@ describe("OpenAICompatiblePlugin", () => {
})
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("cloudflare-workers-ai"), ModelV2.ID.make("model")),
modelID: ModelV2.ID.make("model"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("cloudflare-workers-ai"), Model.ID.make("model")),
modelID: Model.ID.make("model"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/openai-compatible",

View file

@ -6,18 +6,18 @@ import { Effect } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { Credential } from "@opencode-ai/core/credential"
import { Integration } from "@opencode-ai/core/integration"
import { ModelV2 } from "@opencode-ai/core/model"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { Model } from "@opencode-ai/core/model"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { OpenAIPlugin } from "@opencode-ai/core/plugin/provider/openai"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Provider } from "@opencode-ai/core/provider"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const host = yield* PluginHost.make(plugin)
const integrations = yield* Integration.Service
@ -63,14 +63,14 @@ describe("OpenAIPlugin", () => {
it.effect("creates an OpenAI SDK for @ai-sdk/openai using the provider ID as SDK name", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("custom-openai"), ModelV2.ID.make("gpt-5")),
modelID: ModelV2.ID.make("gpt-5"),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("custom-openai"), Model.ID.make("gpt-5")),
modelID: Model.ID.make("gpt-5"),
package: Provider.aisdk("test-provider"),
}),
package: "@ai-sdk/openai",
options: { name: "custom-openai", apiKey: "test" },
@ -81,14 +81,14 @@ describe("OpenAIPlugin", () => {
it.effect("ignores non-OpenAI SDK packages", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.openai, ModelV2.ID.make("gpt-5")),
modelID: ModelV2.ID.make("gpt-5"),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.openai, Model.ID.make("gpt-5")),
modelID: Model.ID.make("gpt-5"),
package: Provider.aisdk("test-provider"),
}),
package: "@ai-sdk/openai-compatible",
options: { name: "openai" },
@ -99,15 +99,15 @@ describe("OpenAIPlugin", () => {
it.effect("uses the Responses API for language models", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin()
const result = yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.openai, ModelV2.ID.make("alias")),
modelID: ModelV2.ID.make("gpt-5"),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.openai, Model.ID.make("alias")),
modelID: Model.ID.make("gpt-5"),
package: Provider.aisdk("test-provider"),
}),
sdk: fakeSelectorSdk(calls),
options: {},
@ -119,15 +119,15 @@ describe("OpenAIPlugin", () => {
it.effect("ignores non-OpenAI providers", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const calls: string[] = []
yield* addPlugin()
const result = yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.anthropic, ModelV2.ID.make("gpt-5")),
modelID: ModelV2.ID.make("gpt-5"),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.anthropic, Model.ID.make("gpt-5")),
modelID: Model.ID.make("gpt-5"),
package: Provider.aisdk("test-provider"),
}),
sdk: fakeSelectorSdk(calls),
options: {},
@ -141,20 +141,20 @@ describe("OpenAIPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
const item = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.openai),
package: ProviderV2.aisdk("@ai-sdk/openai"),
const item = Provider.Info.make({
...Provider.Info.empty(Provider.ID.openai),
package: Provider.aisdk("@ai-sdk/openai"),
})
catalog.provider.update(item.id, (draft) => {
draft.package = item.package
})
catalog.model.update(item.id, ModelV2.ID.make("gpt-5"), () => {})
catalog.model.update(item.id, ModelV2.ID.make("gpt-5-chat-latest"), () => {})
catalog.model.update(item.id, Model.ID.make("gpt-5"), () => {})
catalog.model.update(item.id, Model.ID.make("gpt-5-chat-latest"), () => {})
})
yield* addPlugin()
expect(required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-5"))).enabled).toBe(true)
expect(required(yield* catalog.model.get(Provider.ID.openai, Model.ID.make("gpt-5"))).enabled).toBe(true)
expect(
required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-5-chat-latest"))).enabled,
required(yield* catalog.model.get(Provider.ID.openai, Model.ID.make("gpt-5-chat-latest"))).enabled,
).toBe(false)
}),
)
@ -164,14 +164,14 @@ describe("OpenAIPlugin", () => {
const catalog = yield* Catalog.Service
const credentials = yield* Credential.Service
yield* catalog.transform((catalog) => {
const item = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.openai),
package: ProviderV2.aisdk("@ai-sdk/openai"),
const item = Provider.Info.make({
...Provider.Info.empty(Provider.ID.openai),
package: Provider.aisdk("@ai-sdk/openai"),
})
catalog.provider.update(item.id, (draft) => {
draft.package = item.package
})
catalog.model.update(item.id, ModelV2.ID.make("gpt-5.5"), (model) => {
catalog.model.update(item.id, Model.ID.make("gpt-5.5"), (model) => {
model.cost = [
{
input: Money.USDPerMillionTokens.make(1),
@ -183,14 +183,14 @@ describe("OpenAIPlugin", () => {
},
]
})
catalog.model.update(item.id, ModelV2.ID.make("gpt-5.5-pro"), () => {})
catalog.model.update(item.id, ModelV2.ID.make("gpt-5.4-pro"), (model) => {
model.modelID = ModelV2.ID.make("gpt-5.4")
catalog.model.update(item.id, Model.ID.make("gpt-5.5-pro"), () => {})
catalog.model.update(item.id, Model.ID.make("gpt-5.4-pro"), (model) => {
model.modelID = Model.ID.make("gpt-5.4")
model.body = { reasoning: { mode: "pro" } }
})
catalog.model.update(item.id, ModelV2.ID.make("gpt-5.6"), () => {})
catalog.model.update(item.id, ModelV2.ID.make("gpt-5.6-sol"), () => {})
catalog.model.update(item.id, ModelV2.ID.make("gpt-4.1"), () => {})
catalog.model.update(item.id, Model.ID.make("gpt-5.6"), () => {})
catalog.model.update(item.id, Model.ID.make("gpt-5.6-sol"), () => {})
catalog.model.update(item.id, Model.ID.make("gpt-4.1"), () => {})
})
yield* credentials.create({
integrationID: Integration.ID.make("openai"),
@ -205,20 +205,20 @@ describe("OpenAIPlugin", () => {
})
yield* addPlugin()
const eligible = required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-5.5")))
const eligible = required(yield* catalog.model.get(Provider.ID.openai, Model.ID.make("gpt-5.5")))
expect(eligible.cost).toEqual([])
expect(eligible.enabled).toBe(true)
expect(required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-5.5-pro"))).enabled).toBe(
expect(required(yield* catalog.model.get(Provider.ID.openai, Model.ID.make("gpt-5.5-pro"))).enabled).toBe(
false,
)
expect(required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-5.4-pro"))).enabled).toBe(
expect(required(yield* catalog.model.get(Provider.ID.openai, Model.ID.make("gpt-5.4-pro"))).enabled).toBe(
false,
)
expect(required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-5.6"))).enabled).toBe(false)
expect(required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-5.6-sol"))).enabled).toBe(
expect(required(yield* catalog.model.get(Provider.ID.openai, Model.ID.make("gpt-5.6"))).enabled).toBe(false)
expect(required(yield* catalog.model.get(Provider.ID.openai, Model.ID.make("gpt-5.6-sol"))).enabled).toBe(
true,
)
expect(required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-4.1"))).enabled).toBe(false)
expect(required(yield* catalog.model.get(Provider.ID.openai, Model.ID.make("gpt-4.1"))).enabled).toBe(false)
}),
)
@ -227,15 +227,15 @@ describe("OpenAIPlugin", () => {
const catalog = yield* Catalog.Service
const credentials = yield* Credential.Service
yield* catalog.transform((catalog) => {
const item = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.openai),
package: ProviderV2.aisdk("@ai-sdk/openai"),
const item = Provider.Info.make({
...Provider.Info.empty(Provider.ID.openai),
package: Provider.aisdk("@ai-sdk/openai"),
})
catalog.provider.update(item.id, (draft) => {
draft.package = item.package
})
catalog.model.update(item.id, ModelV2.ID.make("gpt-5.5"), () => {})
catalog.model.update(item.id, ModelV2.ID.make("gpt-4.1"), () => {})
catalog.model.update(item.id, Model.ID.make("gpt-5.5"), () => {})
catalog.model.update(item.id, Model.ID.make("gpt-4.1"), () => {})
})
yield* credentials.create({
integrationID: Integration.ID.make("openai"),
@ -243,8 +243,8 @@ describe("OpenAIPlugin", () => {
})
yield* addPlugin()
expect(required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-5.5"))).enabled).toBe(true)
expect(required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("gpt-4.1"))).enabled).toBe(true)
expect(required(yield* catalog.model.get(Provider.ID.openai, Model.ID.make("gpt-5.5"))).enabled).toBe(true)
expect(required(yield* catalog.model.get(Provider.ID.openai, Model.ID.make("gpt-4.1"))).enabled).toBe(true)
}),
)
@ -252,18 +252,18 @@ describe("OpenAIPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
const item = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.make("custom-openai")),
package: ProviderV2.aisdk("test-provider"),
const item = Provider.Info.make({
...Provider.Info.empty(Provider.ID.make("custom-openai")),
package: Provider.aisdk("test-provider"),
})
catalog.provider.update(item.id, (draft) => {
draft.package = item.package
})
catalog.model.update(item.id, ModelV2.ID.make("gpt-5-chat-latest"), () => {})
catalog.model.update(item.id, Model.ID.make("gpt-5-chat-latest"), () => {})
})
yield* addPlugin()
expect(
required(yield* catalog.model.get(ProviderV2.ID.make("custom-openai"), ModelV2.ID.make("gpt-5-chat-latest")))
required(yield* catalog.model.get(Provider.ID.make("custom-openai"), Model.ID.make("gpt-5-chat-latest")))
.enabled,
).toBe(true)
}),

View file

@ -3,25 +3,25 @@ import { Money } from "@opencode-ai/schema/money"
import { Effect } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { Credential } from "@opencode-ai/core/credential"
import { EventV2 } from "@opencode-ai/core/event"
import { Bus } from "@opencode-ai/core/bus"
import { Integration } from "@opencode-ai/core/integration"
import { ModelV2 } from "@opencode-ai/core/model"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { Model } from "@opencode-ai/core/model"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { OpencodePlugin } from "@opencode-ai/core/plugin/provider/opencode"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Provider } from "@opencode-ai/core/provider"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const host = yield* PluginHost.make(plugin)
const events = yield* EventV2.Service
const bus = yield* Bus.Service
const integration = yield* Integration.Service
yield* OpencodePlugin.effect(host).pipe(
Effect.provideService(EventV2.Service, events),
Effect.provideService(Bus.Service, bus),
Effect.provideService(Integration.Service, integration),
)
})
@ -215,18 +215,18 @@ describe("OpencodePlugin", () => {
const credentials = yield* Credential.Service
const catalog = yield* Catalog.Service
yield* catalog.transform((draft) => {
draft.provider.update(ProviderV2.ID.make("remote"), () => {})
draft.model.update(ProviderV2.ID.make("remote"), ModelV2.ID.make("model"), (model) => {
draft.provider.update(Provider.ID.make("remote"), () => {})
draft.model.update(Provider.ID.make("remote"), Model.ID.make("model"), (model) => {
model.variants = [
{
id: ModelV2.VariantID.make("custom"),
id: Model.VariantID.make("custom"),
settings: {},
headers: { "x-custom": "true" },
body: { custom: true },
},
]
})
draft.model.update(ProviderV2.ID.make("remote"), ModelV2.ID.make("stale"), () => {})
draft.model.update(Provider.ID.make("remote"), Model.ID.make("stale"), () => {})
})
yield* credentials.create({
integrationID: Integration.ID.make("opencode"),
@ -240,44 +240,44 @@ describe("OpencodePlugin", () => {
yield* addPlugin()
expect(authorization).toEqual(["Bearer secret"])
const provider = required(yield* catalog.provider.get(ProviderV2.ID.make("remote")))
const provider = required(yield* catalog.provider.get(Provider.ID.make("remote")))
expect(provider).toMatchObject({
name: "Remote",
integrationID: "opencode",
package: ProviderV2.aisdk("@ai-sdk/openai-compatible"),
package: Provider.aisdk("@ai-sdk/openai-compatible"),
settings: { baseURL: `${server.url.origin}/v1`, custom: "value" },
headers: { "x-org-id": "org" },
})
expect(yield* (yield* Integration.Service).get(Integration.ID.make("remote"))).toBeUndefined()
const model = required(yield* catalog.model.get(ProviderV2.ID.make("remote"), ModelV2.ID.make("model")))
const model = required(yield* catalog.model.get(Provider.ID.make("remote"), Model.ID.make("model")))
expect(model).toMatchObject({
name: "Remote Model",
family: "remote",
capabilities: { tools: true, input: ["text", "image"], output: ["text"] },
cost: [{ input: 1, output: 2, cache: { read: 0.1, write: 0 } }],
limit: { context: 1000, output: 100 },
package: ProviderV2.aisdk("@ai-sdk/openai-compatible"),
package: Provider.aisdk("@ai-sdk/openai-compatible"),
settings: { baseURL: `${server.url.origin}/v1`, custom: "value", temperature: 0.5 },
headers: { "x-org-id": "org" },
})
expect(model.variants).toEqual([
{
id: ModelV2.VariantID.make("custom"),
id: Model.VariantID.make("custom"),
settings: {},
headers: { "x-custom": "true" },
body: { custom: true },
},
{
id: ModelV2.VariantID.make("high"),
id: Model.VariantID.make("high"),
settings: { temperature: 0.2 },
headers: {},
},
])
expect(
required(yield* catalog.model.get(ProviderV2.ID.make("remote"), ModelV2.ID.make("disabled"))).enabled,
required(yield* catalog.model.get(Provider.ID.make("remote"), Model.ID.make("disabled"))).enabled,
).toBe(false)
expect(yield* catalog.model.get(ProviderV2.ID.make("remote"), ModelV2.ID.make("stale"))).toBeDefined()
expect(yield* catalog.model.get(Provider.ID.make("remote"), Model.ID.make("stale"))).toBeDefined()
}),
({ server }) => Effect.promise(() => server.stop(true)),
),
@ -288,14 +288,14 @@ describe("OpencodePlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
const provider = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.opencode),
package: ProviderV2.aisdk("test-provider"),
const provider = Provider.Info.make({
...Provider.Info.empty(Provider.ID.opencode),
package: Provider.aisdk("test-provider"),
})
const model = ModelV2.Info.make({
...ModelV2.Info.default(provider.id, ModelV2.ID.make("paid")),
modelID: ModelV2.ID.make("paid"),
package: ProviderV2.aisdk("test-provider"),
const model = Model.Info.make({
...Model.Info.default(provider.id, Model.ID.make("paid")),
modelID: Model.ID.make("paid"),
package: Provider.aisdk("test-provider"),
cost: cost(1),
})
catalog.provider.update(provider.id, () => {})
@ -304,8 +304,8 @@ describe("OpencodePlugin", () => {
})
})
yield* addPlugin()
expect(required(yield* catalog.provider.get(ProviderV2.ID.opencode)).settings?.apiKey).toBe("public")
expect(required(yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("paid"))).enabled).toBe(false)
expect(required(yield* catalog.provider.get(Provider.ID.opencode)).settings?.apiKey).toBe("public")
expect(required(yield* catalog.model.get(Provider.ID.opencode, Model.ID.make("paid"))).enabled).toBe(false)
}),
),
)
@ -315,14 +315,14 @@ describe("OpencodePlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
const provider = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.opencode),
package: ProviderV2.aisdk("test-provider"),
const provider = Provider.Info.make({
...Provider.Info.empty(Provider.ID.opencode),
package: Provider.aisdk("test-provider"),
})
const model = ModelV2.Info.make({
...ModelV2.Info.default(provider.id, ModelV2.ID.make("free")),
modelID: ModelV2.ID.make("free"),
package: ProviderV2.aisdk("test-provider"),
const model = Model.Info.make({
...Model.Info.default(provider.id, Model.ID.make("free")),
modelID: Model.ID.make("free"),
package: Provider.aisdk("test-provider"),
cost: cost(0),
})
catalog.provider.update(provider.id, () => {})
@ -331,8 +331,8 @@ describe("OpencodePlugin", () => {
})
})
yield* addPlugin()
expect(required(yield* catalog.provider.get(ProviderV2.ID.opencode)).settings?.apiKey).toBe("public")
expect(required(yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("free"))).enabled).toBe(true)
expect(required(yield* catalog.provider.get(Provider.ID.opencode)).settings?.apiKey).toBe("public")
expect(required(yield* catalog.model.get(Provider.ID.opencode, Model.ID.make("free"))).enabled).toBe(true)
}),
),
)
@ -342,14 +342,14 @@ describe("OpencodePlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
const provider = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.opencode),
package: ProviderV2.aisdk("test-provider"),
const provider = Provider.Info.make({
...Provider.Info.empty(Provider.ID.opencode),
package: Provider.aisdk("test-provider"),
})
const model = ModelV2.Info.make({
...ModelV2.Info.default(provider.id, ModelV2.ID.make("output-only")),
modelID: ModelV2.ID.make("output-only"),
package: ProviderV2.aisdk("test-provider"),
const model = Model.Info.make({
...Model.Info.default(provider.id, Model.ID.make("output-only")),
modelID: Model.ID.make("output-only"),
package: Provider.aisdk("test-provider"),
cost: cost(0, 1),
})
catalog.provider.update(provider.id, () => {})
@ -358,8 +358,8 @@ describe("OpencodePlugin", () => {
})
})
yield* addPlugin()
expect(required(yield* catalog.provider.get(ProviderV2.ID.opencode)).settings?.apiKey).toBe("public")
expect(required(yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("output-only"))).enabled).toBe(
expect(required(yield* catalog.provider.get(Provider.ID.opencode)).settings?.apiKey).toBe("public")
expect(required(yield* catalog.model.get(Provider.ID.opencode, Model.ID.make("output-only"))).enabled).toBe(
true,
)
}),
@ -371,14 +371,14 @@ describe("OpencodePlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
const provider = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.opencode),
package: ProviderV2.aisdk("test-provider"),
const provider = Provider.Info.make({
...Provider.Info.empty(Provider.ID.opencode),
package: Provider.aisdk("test-provider"),
})
const model = ModelV2.Info.make({
...ModelV2.Info.default(provider.id, ModelV2.ID.make("paid")),
modelID: ModelV2.ID.make("paid"),
package: ProviderV2.aisdk("test-provider"),
const model = Model.Info.make({
...Model.Info.default(provider.id, Model.ID.make("paid")),
modelID: Model.ID.make("paid"),
package: Provider.aisdk("test-provider"),
cost: cost(1),
})
catalog.provider.update(provider.id, () => {})
@ -387,8 +387,8 @@ describe("OpencodePlugin", () => {
})
})
yield* addPlugin()
expect(required(yield* catalog.provider.get(ProviderV2.ID.opencode)).settings?.apiKey).toBeUndefined()
expect(required(yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("paid"))).enabled).toBe(true)
expect(required(yield* catalog.provider.get(Provider.ID.opencode)).settings?.apiKey).toBeUndefined()
expect(required(yield* catalog.model.get(Provider.ID.opencode, Model.ID.make("paid"))).enabled).toBe(true)
}),
),
)
@ -405,14 +405,14 @@ describe("OpencodePlugin", () => {
})
})
yield* catalog.transform((catalog) => {
const provider = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.opencode),
package: ProviderV2.aisdk("test-provider"),
const provider = Provider.Info.make({
...Provider.Info.empty(Provider.ID.opencode),
package: Provider.aisdk("test-provider"),
})
const model = ModelV2.Info.make({
...ModelV2.Info.default(provider.id, ModelV2.ID.make("paid")),
modelID: ModelV2.ID.make("paid"),
package: ProviderV2.aisdk("test-provider"),
const model = Model.Info.make({
...Model.Info.default(provider.id, Model.ID.make("paid")),
modelID: Model.ID.make("paid"),
package: Provider.aisdk("test-provider"),
cost: cost(1),
})
catalog.provider.update(provider.id, () => {})
@ -421,8 +421,8 @@ describe("OpencodePlugin", () => {
})
})
yield* addPlugin()
expect(required(yield* catalog.provider.get(ProviderV2.ID.opencode)).settings?.apiKey).toBeUndefined()
expect(required(yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("paid"))).enabled).toBe(true)
expect(required(yield* catalog.provider.get(Provider.ID.opencode)).settings?.apiKey).toBeUndefined()
expect(required(yield* catalog.model.get(Provider.ID.opencode, Model.ID.make("paid"))).enabled).toBe(true)
}),
),
)
@ -432,15 +432,15 @@ describe("OpencodePlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
const provider = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.opencode),
package: ProviderV2.aisdk("test-provider"),
const provider = Provider.Info.make({
...Provider.Info.empty(Provider.ID.opencode),
package: Provider.aisdk("test-provider"),
settings: { apiKey: "configured" },
})
const model = ModelV2.Info.make({
...ModelV2.Info.default(provider.id, ModelV2.ID.make("paid")),
modelID: ModelV2.ID.make("paid"),
package: ProviderV2.aisdk("test-provider"),
const model = Model.Info.make({
...Model.Info.default(provider.id, Model.ID.make("paid")),
modelID: Model.ID.make("paid"),
package: Provider.aisdk("test-provider"),
cost: cost(1),
})
catalog.provider.update(provider.id, (draft) => {
@ -452,8 +452,8 @@ describe("OpencodePlugin", () => {
})
})
yield* addPlugin()
expect(required(yield* catalog.provider.get(ProviderV2.ID.opencode)).settings?.apiKey).toBe("configured")
expect(required(yield* catalog.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("paid"))).enabled).toBe(true)
expect(required(yield* catalog.provider.get(Provider.ID.opencode)).settings?.apiKey).toBe("configured")
expect(required(yield* catalog.model.get(Provider.ID.opencode, Model.ID.make("paid"))).enabled).toBe(true)
}),
),
)
@ -463,14 +463,14 @@ describe("OpencodePlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
const provider = ProviderV2.Info.make({
...ProviderV2.Info.empty(ProviderV2.ID.openai),
package: ProviderV2.aisdk("test-provider"),
const provider = Provider.Info.make({
...Provider.Info.empty(Provider.ID.openai),
package: Provider.aisdk("test-provider"),
})
const model = ModelV2.Info.make({
...ModelV2.Info.default(provider.id, ModelV2.ID.make("paid")),
modelID: ModelV2.ID.make("paid"),
package: ProviderV2.aisdk("test-provider"),
const model = Model.Info.make({
...Model.Info.default(provider.id, Model.ID.make("paid")),
modelID: Model.ID.make("paid"),
package: Provider.aisdk("test-provider"),
cost: cost(1),
})
catalog.provider.update(provider.id, () => {})
@ -479,8 +479,8 @@ describe("OpencodePlugin", () => {
})
})
yield* addPlugin()
expect(required(yield* catalog.provider.get(ProviderV2.ID.openai)).settings?.apiKey).toBeUndefined()
expect(required(yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("paid"))).enabled).toBe(true)
expect(required(yield* catalog.provider.get(Provider.ID.openai)).settings?.apiKey).toBeUndefined()
expect(required(yield* catalog.model.get(Provider.ID.openai, Model.ID.make("paid"))).enabled).toBe(true)
}),
),
)
@ -488,17 +488,17 @@ describe("OpencodePlugin", () => {
it.effect("prefers gpt-5-nano as the opencode small model", () =>
Effect.gen(function* () {
const catalog = yield* Catalog.Service
const providerID = ProviderV2.ID.opencode
const providerID = Provider.ID.opencode
yield* catalog.transform((catalog) => {
catalog.provider.update(providerID, () => {})
catalog.model.update(providerID, ModelV2.ID.make("cheap-mini"), (model) => {
catalog.model.update(providerID, Model.ID.make("cheap-mini"), (model) => {
model.capabilities.input = ["text"]
model.capabilities.output = ["text"]
model.cost = [...cost(1, 1)]
model.time.released = Date.now()
})
catalog.model.update(providerID, ModelV2.ID.make("gpt-5-nano"), (model) => {
catalog.model.update(providerID, Model.ID.make("gpt-5-nano"), (model) => {
model.capabilities.input = ["text"]
model.capabilities.output = ["text"]
model.cost = [...cost(10, 10)]
@ -508,7 +508,7 @@ describe("OpencodePlugin", () => {
const selected = yield* catalog.model.small(providerID)
expect(selected?.id).toBe(ModelV2.ID.make("gpt-5-nano"))
expect(selected?.id).toBe(Model.ID.make("gpt-5-nano"))
}),
)
})

View file

@ -2,19 +2,19 @@ import { AISDK } from "@opencode-ai/core/aisdk"
import { describe, expect } from "bun:test"
import { Effect } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { ModelV2 } from "@opencode-ai/core/model"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { Model } from "@opencode-ai/core/model"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { ProviderPlugins } from "@opencode-ai/core/plugin/provider"
import { OpenRouterPlugin } from "@opencode-ai/core/plugin/provider/openrouter"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Provider } from "@opencode-ai/core/provider"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const host = yield* PluginHost.make(plugin)
yield* OpenRouterPlugin.effect(host)
@ -29,34 +29,34 @@ describe("OpenRouterPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
catalog.provider.update(ProviderV2.ID.openrouter, (provider) => {
provider.package = ProviderV2.aisdk("@openrouter/ai-sdk-provider")
catalog.provider.update(Provider.ID.openrouter, (provider) => {
provider.package = Provider.aisdk("@openrouter/ai-sdk-provider")
provider.headers = { Existing: "value" }
})
catalog.provider.update(ProviderV2.ID.make("nvidia"), () => {})
catalog.provider.update(Provider.ID.make("nvidia"), () => {})
})
yield* addPlugin()
expect((yield* catalog.provider.get(ProviderV2.ID.openrouter))?.headers).toEqual({
expect((yield* catalog.provider.get(Provider.ID.openrouter))?.headers).toEqual({
Existing: "value",
"HTTP-Referer": "https://opencode.ai/",
"X-Title": "opencode",
})
expect((yield* catalog.provider.get(ProviderV2.ID.make("nvidia")))?.headers).toBeUndefined()
expect((yield* catalog.provider.get(Provider.ID.make("nvidia")))?.headers).toBeUndefined()
}),
)
it.effect("creates an SDK only for the OpenRouter package", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const ignored = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.openrouter, ModelV2.ID.make("openai/gpt-5")),
modelID: ModelV2.ID.make("openai/gpt-5"),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.openrouter, Model.ID.make("openai/gpt-5")),
modelID: Model.ID.make("openai/gpt-5"),
package: Provider.aisdk("test-provider"),
}),
package: "@ai-sdk/openai-compatible",
options: { name: "openrouter" },
@ -64,10 +64,10 @@ describe("OpenRouterPlugin", () => {
expect(ignored.sdk).toBeUndefined()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("custom"), ModelV2.ID.make("openai/gpt-5")),
modelID: ModelV2.ID.make("openai/gpt-5"),
package: ProviderV2.aisdk("test-provider"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("custom"), Model.ID.make("openai/gpt-5")),
modelID: Model.ID.make("openai/gpt-5"),
package: Provider.aisdk("test-provider"),
}),
package: "@openrouter/ai-sdk-provider",
options: { name: "custom" },
@ -80,21 +80,21 @@ describe("OpenRouterPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
catalog.provider.update(ProviderV2.ID.openrouter, (provider) => {
provider.package = ProviderV2.aisdk("@openrouter/ai-sdk-provider")
catalog.provider.update(Provider.ID.openrouter, (provider) => {
provider.package = Provider.aisdk("@openrouter/ai-sdk-provider")
})
catalog.provider.update(ProviderV2.ID.openai, () => {})
catalog.model.update(ProviderV2.ID.openrouter, ModelV2.ID.make("openai/gpt-5-chat"), () => {})
catalog.model.update(ProviderV2.ID.openrouter, ModelV2.ID.make("openai/gpt-5"), () => {})
catalog.model.update(ProviderV2.ID.openai, ModelV2.ID.make("openai/gpt-5-chat"), () => {})
catalog.provider.update(Provider.ID.openai, () => {})
catalog.model.update(Provider.ID.openrouter, Model.ID.make("openai/gpt-5-chat"), () => {})
catalog.model.update(Provider.ID.openrouter, Model.ID.make("openai/gpt-5"), () => {})
catalog.model.update(Provider.ID.openai, Model.ID.make("openai/gpt-5-chat"), () => {})
})
yield* addPlugin()
expect((yield* catalog.model.get(ProviderV2.ID.openrouter, ModelV2.ID.make("openai/gpt-5-chat")))?.enabled).toBe(
expect((yield* catalog.model.get(Provider.ID.openrouter, Model.ID.make("openai/gpt-5-chat")))?.enabled).toBe(
false,
)
expect((yield* catalog.model.get(ProviderV2.ID.openrouter, ModelV2.ID.make("openai/gpt-5")))?.enabled).toBe(true)
expect((yield* catalog.model.get(ProviderV2.ID.openai, ModelV2.ID.make("openai/gpt-5-chat")))?.enabled).toBe(true)
expect((yield* catalog.model.get(Provider.ID.openrouter, Model.ID.make("openai/gpt-5")))?.enabled).toBe(true)
expect((yield* catalog.model.get(Provider.ID.openai, Model.ID.make("openai/gpt-5-chat")))?.enabled).toBe(true)
}),
)
@ -102,12 +102,12 @@ describe("OpenRouterPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
catalog.provider.update(ProviderV2.ID.make("custom-openrouter"), () => {})
catalog.model.update(ProviderV2.ID.make("custom-openrouter"), ModelV2.ID.make("gpt-5-chat-latest"), () => {})
catalog.provider.update(Provider.ID.make("custom-openrouter"), () => {})
catalog.model.update(Provider.ID.make("custom-openrouter"), Model.ID.make("gpt-5-chat-latest"), () => {})
})
yield* addPlugin()
expect(
(yield* catalog.model.get(ProviderV2.ID.make("custom-openrouter"), ModelV2.ID.make("gpt-5-chat-latest")))
(yield* catalog.model.get(Provider.ID.make("custom-openrouter"), Model.ID.make("gpt-5-chat-latest")))
?.enabled,
).toBe(true)
}),

View file

@ -1,12 +1,12 @@
import { AISDK } from "@opencode-ai/core/aisdk"
import { describe, expect } from "bun:test"
import { Effect } from "effect"
import { ModelV2 } from "@opencode-ai/core/model"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { Model } from "@opencode-ai/core/model"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { Npm } from "@opencode-ai/util/npm"
import { SapAICorePlugin } from "@opencode-ai/core/plugin/provider/sap-ai-core"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Provider } from "@opencode-ai/core/provider"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
@ -19,7 +19,7 @@ const npm = Npm.Service.of({
})
const addPlugin = Effect.fn(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const host = yield* PluginHost.make(plugin)
yield* SapAICorePlugin.effect(host).pipe(Effect.provideService(Npm.Service, npm))
@ -47,10 +47,10 @@ function withEnv<A, E, R>(vars: Record<string, string | undefined>, effect: () =
}
function model(providerID: string) {
return ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make(providerID), ModelV2.ID.make("sap-model")),
modelID: ModelV2.ID.make("sap-model"),
package: ProviderV2.aisdk(fixtureProvider),
return Model.Info.make({
...Model.Info.default(Provider.ID.make(providerID), Model.ID.make("sap-model")),
modelID: Model.ID.make("sap-model"),
package: Provider.aisdk(fixtureProvider),
})
}
@ -60,7 +60,7 @@ describe("SapAICorePlugin", () => {
{ AICORE_SERVICE_KEY: undefined, AICORE_DEPLOYMENT_ID: "deployment", AICORE_RESOURCE_GROUP: "resource-group" },
() =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const sdk = yield* aisdk.runSDK({
@ -83,7 +83,7 @@ describe("SapAICorePlugin", () => {
},
() =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const sdk = yield* aisdk.runSDK({
@ -102,7 +102,7 @@ describe("SapAICorePlugin", () => {
{ AICORE_SERVICE_KEY: undefined, AICORE_DEPLOYMENT_ID: "deployment", AICORE_RESOURCE_GROUP: "resource-group" },
() =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const sdk = yield* aisdk.runSDK({
@ -118,7 +118,7 @@ describe("SapAICorePlugin", () => {
it.effect("uses the callable SDK for language selection", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const sdk = Object.assign((modelID: string) => ({ modelID, provider: "callable" }), {
@ -136,7 +136,7 @@ describe("SapAICorePlugin", () => {
{ AICORE_SERVICE_KEY: undefined, AICORE_DEPLOYMENT_ID: "deployment", AICORE_RESOURCE_GROUP: "resource-group" },
() =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const sdk = yield* aisdk.runSDK({

View file

@ -1,19 +1,19 @@
import { AISDK } from "@opencode-ai/core/aisdk"
import { describe, expect, it as bun_it } from "bun:test"
import { Effect } from "effect"
import { ModelV2 } from "@opencode-ai/core/model"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { Model } from "@opencode-ai/core/model"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { SnowflakeCortexPlugin, cortexFetch } from "@opencode-ai/core/plugin/provider/snowflake-cortex"
import { ProviderPlugins } from "@opencode-ai/core/plugin/provider"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Provider } from "@opencode-ai/core/provider"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const host = yield* PluginHost.make(plugin)
yield* SnowflakeCortexPlugin.effect(host)
@ -53,13 +53,13 @@ describe("SnowflakeCortexPlugin", () => {
it.effect("ignores non-snowflake-cortex providers", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("openai"), ModelV2.ID.make("gpt-4")),
modelID: ModelV2.ID.make("gpt-4"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("openai"), Model.ID.make("gpt-4")),
modelID: Model.ID.make("gpt-4"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/openai",
@ -72,13 +72,13 @@ describe("SnowflakeCortexPlugin", () => {
it.effect("creates SDK for snowflake-cortex using SNOWFLAKE_CORTEX_PAT env var", () =>
withEnv({ SNOWFLAKE_CORTEX_PAT: "test-pat" }, () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("snowflake-cortex"), ModelV2.ID.make("claude-sonnet-4-6")),
modelID: ModelV2.ID.make("claude-sonnet-4-6"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("snowflake-cortex"), Model.ID.make("claude-sonnet-4-6")),
modelID: Model.ID.make("claude-sonnet-4-6"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/openai-compatible",
@ -92,13 +92,13 @@ describe("SnowflakeCortexPlugin", () => {
it.effect("falls back to options.apiKey when SNOWFLAKE_CORTEX_PAT env var is absent", () =>
withEnv({ SNOWFLAKE_CORTEX_PAT: undefined }, () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("snowflake-cortex"), ModelV2.ID.make("claude-sonnet-4-6")),
modelID: ModelV2.ID.make("claude-sonnet-4-6"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("snowflake-cortex"), Model.ID.make("claude-sonnet-4-6")),
modelID: Model.ID.make("claude-sonnet-4-6"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/openai-compatible",
@ -116,13 +116,13 @@ describe("SnowflakeCortexPlugin", () => {
it.effect("uses SNOWFLAKE_CORTEX_TOKEN env var", () =>
withEnv({ SNOWFLAKE_CORTEX_TOKEN: "oauth-token", SNOWFLAKE_CORTEX_PAT: undefined }, () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("snowflake-cortex"), ModelV2.ID.make("claude-sonnet-4-6")),
modelID: ModelV2.ID.make("claude-sonnet-4-6"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("snowflake-cortex"), Model.ID.make("claude-sonnet-4-6")),
modelID: Model.ID.make("claude-sonnet-4-6"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/openai-compatible",
@ -136,13 +136,13 @@ describe("SnowflakeCortexPlugin", () => {
it.effect("falls back to options.token when no Snowflake env token is set", () =>
withEnv({ SNOWFLAKE_CORTEX_TOKEN: undefined, SNOWFLAKE_CORTEX_PAT: undefined }, () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("snowflake-cortex"), ModelV2.ID.make("claude-sonnet-4-6")),
modelID: ModelV2.ID.make("claude-sonnet-4-6"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("snowflake-cortex"), Model.ID.make("claude-sonnet-4-6")),
modelID: Model.ID.make("claude-sonnet-4-6"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/openai-compatible",
@ -160,13 +160,13 @@ describe("SnowflakeCortexPlugin", () => {
it.effect("sets includeUsage on the SDK options", () =>
withEnv({ SNOWFLAKE_CORTEX_PAT: "test-pat" }, () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("snowflake-cortex"), ModelV2.ID.make("claude-sonnet-4-6")),
modelID: ModelV2.ID.make("claude-sonnet-4-6"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("snowflake-cortex"), Model.ID.make("claude-sonnet-4-6")),
modelID: Model.ID.make("claude-sonnet-4-6"),
package: "aisdk:test-provider",
}),
package: "@ai-sdk/openai-compatible",

View file

@ -2,18 +2,18 @@ import { AISDK } from "@opencode-ai/core/aisdk"
import { describe, expect } from "bun:test"
import { Effect } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { ModelV2 } from "@opencode-ai/core/model"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { Model } from "@opencode-ai/core/model"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { VercelPlugin } from "@opencode-ai/core/plugin/provider/vercel"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Provider } from "@opencode-ai/core/provider"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
const host = yield* PluginHost.make(plugin)
yield* VercelPlugin.effect(host)
@ -24,13 +24,13 @@ describe("VercelPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
catalog.provider.update(ProviderV2.ID.make("vercel"), (provider) => {
provider.package = ProviderV2.aisdk("@ai-sdk/vercel")
catalog.provider.update(Provider.ID.make("vercel"), (provider) => {
provider.package = Provider.aisdk("@ai-sdk/vercel")
provider.headers = { ...provider.headers, Existing: "1" }
})
})
yield* addPlugin()
expect((yield* catalog.provider.get(ProviderV2.ID.make("vercel")))?.headers).toEqual({
expect((yield* catalog.provider.get(Provider.ID.make("vercel")))?.headers).toEqual({
Existing: "1",
"http-referer": "https://opencode.ai/",
"x-title": "opencode",
@ -42,25 +42,25 @@ describe("VercelPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) =>
catalog.provider.update(ProviderV2.ID.make("vercel"), (provider) => {
provider.package = ProviderV2.aisdk("@ai-sdk/vercel")
catalog.provider.update(Provider.ID.make("vercel"), (provider) => {
provider.package = Provider.aisdk("@ai-sdk/vercel")
}),
)
yield* addPlugin()
expect((yield* catalog.provider.get(ProviderV2.ID.make("vercel")))?.headers).not.toHaveProperty("HTTP-Referer")
expect((yield* catalog.provider.get(ProviderV2.ID.make("vercel")))?.headers).not.toHaveProperty("X-Title")
expect((yield* catalog.provider.get(Provider.ID.make("vercel")))?.headers).not.toHaveProperty("HTTP-Referer")
expect((yield* catalog.provider.get(Provider.ID.make("vercel")))?.headers).not.toHaveProperty("X-Title")
}),
)
it.effect("creates @ai-sdk/vercel SDKs for custom provider IDs", () =>
Effect.gen(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const aisdk = yield* AISDK.Service
yield* addPlugin()
const event = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("custom-vercel"), ModelV2.ID.make("v0-1.0-md")),
modelID: ModelV2.ID.make("v0-1.0-md"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("custom-vercel"), Model.ID.make("v0-1.0-md")),
modelID: Model.ID.make("v0-1.0-md"),
package: "aisdk:@ai-sdk/vercel",
}),
package: "@ai-sdk/vercel",
@ -74,9 +74,9 @@ describe("VercelPlugin", () => {
it.effect("ignores non-Vercel providers", () =>
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => catalog.provider.update(ProviderV2.ID.make("gateway"), () => {}))
yield* catalog.transform((catalog) => catalog.provider.update(Provider.ID.make("gateway"), () => {}))
yield* addPlugin()
expect((yield* catalog.provider.get(ProviderV2.ID.make("gateway")))?.headers).toBeUndefined()
expect((yield* catalog.provider.get(Provider.ID.make("gateway")))?.headers).toBeUndefined()
}),
)
})

View file

@ -3,18 +3,18 @@ import type { LanguageModelV3 } from "@ai-sdk/provider"
import { describe, expect } from "bun:test"
import { Effect } from "effect"
import { Integration } from "@opencode-ai/core/integration"
import { ModelV2 } from "@opencode-ai/core/model"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { Model } from "@opencode-ai/core/model"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { XAIPlugin } from "@opencode-ai/core/plugin/provider/xai"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Provider } from "@opencode-ai/core/provider"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const host = yield* PluginHost.make(plugin)
yield* XAIPlugin.effect(host)
})
@ -61,9 +61,9 @@ describe("XAIPlugin", () => {
yield* addPlugin()
const ignored = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("xai"), ModelV2.ID.make("grok-4")),
modelID: ModelV2.ID.make("grok-4"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("xai"), Model.ID.make("grok-4")),
modelID: Model.ID.make("grok-4"),
package: "aisdk:@ai-sdk/xai",
}),
package: "@ai-sdk/openai-compatible",
@ -71,9 +71,9 @@ describe("XAIPlugin", () => {
})
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("xai"), ModelV2.ID.make("grok-4")),
modelID: ModelV2.ID.make("grok-4"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("xai"), Model.ID.make("grok-4")),
modelID: Model.ID.make("grok-4"),
package: "aisdk:@ai-sdk/xai",
}),
package: "@ai-sdk/xai",
@ -91,9 +91,9 @@ describe("XAIPlugin", () => {
yield* addPlugin()
const result = yield* aisdk.runSDK({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("custom-xai"), ModelV2.ID.make("grok-4")),
modelID: ModelV2.ID.make("grok-4"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("custom-xai"), Model.ID.make("grok-4")),
modelID: Model.ID.make("grok-4"),
package: "aisdk:@ai-sdk/xai",
}),
package: "@ai-sdk/xai",
@ -111,9 +111,9 @@ describe("XAIPlugin", () => {
yield* addPlugin()
const result = yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.make("xai"), ModelV2.ID.make("alias")),
modelID: ModelV2.ID.make("grok-4"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.make("xai"), Model.ID.make("alias")),
modelID: Model.ID.make("grok-4"),
package: "aisdk:@ai-sdk/xai",
}),
sdk: fakeSelectorSdk(calls),
@ -132,9 +132,9 @@ describe("XAIPlugin", () => {
yield* addPlugin()
const result = yield* aisdk.runLanguage({
model: ModelV2.Info.make({
...ModelV2.Info.default(ProviderV2.ID.openai, ModelV2.ID.make("grok-4")),
modelID: ModelV2.ID.make("grok-4"),
model: Model.Info.make({
...Model.Info.default(Provider.ID.openai, Model.ID.make("grok-4")),
modelID: Model.ID.make("grok-4"),
package: "aisdk:@ai-sdk/xai",
}),
sdk: fakeSelectorSdk(calls),

View file

@ -1,18 +1,18 @@
import { describe, expect } from "bun:test"
import { Effect } from "effect"
import { Catalog } from "@opencode-ai/core/catalog"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { ProviderPlugins } from "@opencode-ai/core/plugin/provider"
import { ZenmuxPlugin } from "@opencode-ai/core/plugin/provider/zenmux"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Provider } from "@opencode-ai/core/provider"
import { testEffect } from "../lib/effect"
import { PluginTestLayer } from "./fixture"
const it = testEffect(PluginTestLayer)
const addPlugin = Effect.fn(function* () {
const plugin = yield* PluginV2.Service
const plugin = yield* Plugin.Service
const host = yield* PluginHost.make(plugin)
yield* ZenmuxPlugin.effect(host)
})
@ -31,13 +31,13 @@ describe("ZenmuxPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
catalog.provider.update(ProviderV2.ID.make("zenmux"), (provider) => {
provider.package = ProviderV2.aisdk("@ai-sdk/openai-compatible")
catalog.provider.update(Provider.ID.make("zenmux"), (provider) => {
provider.package = Provider.aisdk("@ai-sdk/openai-compatible")
provider.settings = { ...provider.settings, baseURL: "https://zenmux.ai/api/v1" }
})
})
yield* addPlugin()
const result = required(yield* catalog.provider.get(ProviderV2.ID.make("zenmux")))
const result = required(yield* catalog.provider.get(Provider.ID.make("zenmux")))
expect(result.headers).toEqual({ "HTTP-Referer": "https://opencode.ai/", "X-Title": "opencode" })
expect(Object.keys(required(result.headers)).sort()).toEqual(["HTTP-Referer", "X-Title"])
}),
@ -47,15 +47,15 @@ describe("ZenmuxPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
catalog.provider.update(ProviderV2.ID.make("zenmux"), (provider) => {
provider.package = ProviderV2.aisdk("@ai-sdk/openai-compatible")
catalog.provider.update(Provider.ID.make("zenmux"), (provider) => {
provider.package = Provider.aisdk("@ai-sdk/openai-compatible")
provider.settings = { ...provider.settings, baseURL: "https://zenmux.ai/api/v1" }
provider.headers = { ...provider.headers, Existing: "value" }
})
})
yield* addPlugin()
expect(required(yield* catalog.provider.get(ProviderV2.ID.make("zenmux"))).headers).toEqual({
expect(required(yield* catalog.provider.get(Provider.ID.make("zenmux"))).headers).toEqual({
Existing: "value",
"HTTP-Referer": "https://opencode.ai/",
"X-Title": "opencode",
@ -67,15 +67,15 @@ describe("ZenmuxPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
catalog.provider.update(ProviderV2.ID.make("zenmux"), (provider) => {
provider.package = ProviderV2.aisdk("@ai-sdk/openai-compatible")
catalog.provider.update(Provider.ID.make("zenmux"), (provider) => {
provider.package = Provider.aisdk("@ai-sdk/openai-compatible")
provider.settings = { ...provider.settings, baseURL: "https://zenmux.ai/api/v1" }
provider.headers = { "HTTP-Referer": "https://example.com/", "X-Title": "custom-title" }
})
})
yield* addPlugin()
expect(required(yield* catalog.provider.get(ProviderV2.ID.make("zenmux"))).headers).toEqual({
expect(required(yield* catalog.provider.get(Provider.ID.make("zenmux"))).headers).toEqual({
"HTTP-Referer": "https://example.com/",
"X-Title": "custom-title",
})
@ -86,13 +86,13 @@ describe("ZenmuxPlugin", () => {
Effect.gen(function* () {
const catalog = yield* Catalog.Service
yield* catalog.transform((catalog) => {
catalog.provider.update(ProviderV2.ID.openrouter, (provider) => {
catalog.provider.update(Provider.ID.openrouter, (provider) => {
provider.headers = { "HTTP-Referer": "https://example.com/", "X-Title": "custom-title" }
})
})
yield* addPlugin()
expect(required(yield* catalog.provider.get(ProviderV2.ID.openrouter)).headers).toEqual({
expect(required(yield* catalog.provider.get(Provider.ID.openrouter)).headers).toEqual({
"HTTP-Referer": "https://example.com/",
"X-Title": "custom-title",
})

View file

@ -7,17 +7,17 @@ import { Location } from "@opencode-ai/core/location"
import { Effect } from "effect"
import { SkillPlugin } from "@opencode-ai/core/plugin/skill"
import { AbsolutePath } from "@opencode-ai/core/schema"
import { SkillV2 } from "@opencode-ai/core/skill"
import { Skill } from "@opencode-ai/core/skill"
import { location } from "../fixture/location"
import { testEffect } from "../lib/effect"
import { host } from "./host"
const it = testEffect(AppNodeBuilder.build(SkillV2.node))
const it = testEffect(AppNodeBuilder.build(Skill.node))
describe("SkillPlugin.Plugin", () => {
it.effect("registers built-in skills", () =>
Effect.gen(function* () {
const skill = yield* SkillV2.Service
const skill = yield* Skill.Service
yield* SkillPlugin.Plugin.effect(
host({
app: { name: "test", version: "1.2.3", channel: "beta" },

View file

@ -1,13 +1,13 @@
import { describe, expect, test } from "bun:test"
import { SystemPart } from "@opencode-ai/ai"
import { AgentV2 } from "@opencode-ai/core/agent"
import { Agent } from "@opencode-ai/core/agent"
import { Catalog } from "@opencode-ai/core/catalog"
import { PluginV2 } from "@opencode-ai/core/plugin"
import { Plugin } from "@opencode-ai/core/plugin"
import { PluginHooks } from "@opencode-ai/core/plugin/hooks"
import { PluginHost } from "@opencode-ai/core/plugin/host"
import { SystemPromptPlugin } from "@opencode-ai/core/plugin/system-prompt"
import { SessionV2 } from "@opencode-ai/core/session"
import type { SessionHooks } from "@opencode-ai/plugin/v2/effect/session"
import { Session } from "@opencode-ai/core/session"
import type { SessionHooks } from "@opencode-ai/plugin/effect/session"
import { Model } from "@opencode-ai/schema/model"
import { Provider } from "@opencode-ai/schema/provider"
import { Effect } from "effect"
@ -19,13 +19,15 @@ import PROMPT_DEFAULT from "../../src/session/runner/prompt/base.txt"
const it = testEffect(PluginTestLayer)
const fallback = PROMPT_DEFAULT
const makeHost = Effect.gen(function* () {
const plugins = yield* PluginV2.Service
const agents = yield* Agent.Service
const plugins = yield* Plugin.Service
yield* agents.transform((draft) => draft.update(Agent.ID.make("build"), () => {}))
return yield* PluginHost.make(plugins)
})
const context = (id: string, system = fallback): SessionHooks["context"] => ({
sessionID: SessionV2.ID.make("ses_system_prompt"),
agent: AgentV2.ID.make("build"),
sessionID: Session.ID.make("ses_system_prompt"),
agent: Agent.ID.make("build"),
model: Model.Ref.make({ providerID: Provider.ID.make("test"), id: Model.ID.make(id) }),
system: [SystemPart.make(system)],
messages: [],
@ -33,7 +35,7 @@ const context = (id: string, system = fallback): SessionHooks["context"] => ({
})
describe("SystemPromptPlugin", () => {
test("uses V2 vocabulary in the Meta prompt", () => {
test("uses current vocabulary in the Meta prompt", () => {
expect(PROMPT_META).toContain("webfetch tool")
expect(PROMPT_META).toContain("subagent tool")
expect(PROMPT_META).toContain("shell tool")
@ -92,10 +94,10 @@ describe("SystemPromptPlugin", () => {
it.effect("preserves an explicit agent system prompt", () =>
Effect.gen(function* () {
const agents = yield* AgentV2.Service
const agents = yield* Agent.Service
const hooks = yield* PluginHooks.Service
yield* agents.transform((draft) =>
draft.update(AgentV2.ID.make("build"), (agent) => {
draft.update(Agent.ID.make("build"), (agent) => {
agent.system = "Custom agent prompt"
}),
)
@ -111,6 +113,21 @@ describe("SystemPromptPlugin", () => {
}),
)
it.effect("skips the hook when agent lookup fails", () =>
Effect.gen(function* () {
const agents = yield* Agent.Service
const hooks = yield* PluginHooks.Service
const pluginHost = yield* makeHost
yield* SystemPromptPlugin.OpenAIPlugin.effect(pluginHost)
yield* agents.transform((draft) => draft.remove(Agent.ID.make("build")))
const event = context("gpt-5")
yield* hooks.trigger("session", "context", event)
expect(event.system[0]?.text).toBe(fallback)
}),
)
it.effect("allows one model-lab prompt plugin to be enabled independently", () =>
Effect.gen(function* () {
const hooks = yield* PluginHooks.Service

View file

@ -3,9 +3,9 @@ import { Catalog } from "@opencode-ai/core/catalog"
import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { LayerNode } from "@opencode-ai/util/effect/layer-node"
import { Location } from "@opencode-ai/core/location"
import { ModelV2 } from "@opencode-ai/core/model"
import { Model } from "@opencode-ai/core/model"
import { VariantPlugin } from "@opencode-ai/core/plugin/variant"
import { ProviderV2 } from "@opencode-ai/core/provider"
import { Provider } from "@opencode-ai/core/provider"
import { AbsolutePath } from "@opencode-ai/core/schema"
import { Effect, Layer } from "effect"
import { location } from "../fixture/location"
@ -23,17 +23,17 @@ describe("VariantPlugin", () => {
Effect.gen(function* () {
const service = yield* Catalog.Service
yield* service.transform((catalog) => {
catalog.provider.update(ProviderV2.ID.opencode, (provider) => {
provider.package = ProviderV2.aisdk("@ai-sdk/openai-compatible")
catalog.provider.update(Provider.ID.opencode, (provider) => {
provider.package = Provider.aisdk("@ai-sdk/openai-compatible")
})
catalog.model.update(ProviderV2.ID.opencode, ModelV2.ID.make("glm-5.2"), (model) => {
model.modelID = ModelV2.ID.make("glm-5.2")
model.package = ProviderV2.aisdk("@ai-sdk/openai-compatible")
catalog.model.update(Provider.ID.opencode, Model.ID.make("glm-5.2"), (model) => {
model.modelID = Model.ID.make("glm-5.2")
model.package = Provider.aisdk("@ai-sdk/openai-compatible")
})
})
yield* VariantPlugin.Plugin.effect(host({ catalog: catalogHost(service) }))
expect((yield* service.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("glm-5.2")))?.variants).toEqual([
expect((yield* service.model.get(Provider.ID.opencode, Model.ID.make("glm-5.2")))?.variants).toEqual([
expect.objectContaining({ id: "high", settings: { reasoningEffort: "high" } }),
expect.objectContaining({ id: "max", settings: { reasoningEffort: "max" } }),
])
@ -44,15 +44,15 @@ describe("VariantPlugin", () => {
Effect.gen(function* () {
const service = yield* Catalog.Service
yield* service.transform((catalog) => {
catalog.model.update(ProviderV2.ID.opencode, ModelV2.ID.make("glm-5.2"), (model) => {
model.modelID = ModelV2.ID.make("glm-5.2")
model.package = ProviderV2.aisdk("@ai-sdk/openai-compatible")
model.variants = [{ id: ModelV2.VariantID.make("high"), settings: {}, headers: { custom: "true" }, body: {} }]
catalog.model.update(Provider.ID.opencode, Model.ID.make("glm-5.2"), (model) => {
model.modelID = Model.ID.make("glm-5.2")
model.package = Provider.aisdk("@ai-sdk/openai-compatible")
model.variants = [{ id: Model.VariantID.make("high"), settings: {}, headers: { custom: "true" }, body: {} }]
})
})
yield* VariantPlugin.Plugin.effect(host({ catalog: catalogHost(service) }))
expect((yield* service.model.get(ProviderV2.ID.opencode, ModelV2.ID.make("glm-5.2")))?.variants).toEqual([
expect((yield* service.model.get(Provider.ID.opencode, Model.ID.make("glm-5.2")))?.variants).toEqual([
expect.objectContaining({ id: "high", headers: { custom: "true" } }),
expect.objectContaining({ id: "max", settings: { reasoningEffort: "max" } }),
])

View file

@ -4,7 +4,7 @@ import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
import { LayerNode } from "@opencode-ai/util/effect/layer-node"
import { Credential } from "@opencode-ai/core/credential"
import { Config } from "@opencode-ai/core/config"
import { EventV2 } from "@opencode-ai/core/event"
import { Bus } from "@opencode-ai/core/bus"
import { Form } from "@opencode-ai/core/form"
import { Integration } from "@opencode-ai/core/integration"
import { WebSearch } from "@opencode-ai/core/websearch"
@ -42,7 +42,7 @@ const http = Layer.succeed(
export const webSearchIntegrationTest = testEffect(
Layer.merge(
AppNodeBuilder.build(
LayerNode.group([Integration.node, Credential.node, EventV2.node, Form.node, WebSearch.node]),
LayerNode.group([Integration.node, Credential.node, Bus.node, Form.node, WebSearch.node]),
[[Config.node, Layer.succeed(Config.Service, Config.Service.of({ entries: () => Effect.succeed([]) }))]],
),
http,