feat(plugin): add v2 effect host (#33111)

This commit is contained in:
Dax 2026-06-21 14:05:49 +02:00 committed by GitHub
commit c780d7cee7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
139 changed files with 3740 additions and 1943 deletions

View file

@ -1,12 +1,11 @@
export * as AgentPlugin from "./agent"
import path from "path"
import { define } from "@opencode-ai/plugin/v2/effect"
import { Effect } from "effect"
import { AgentV2 } from "../agent"
import { Global } from "../global"
import { Location } from "../location"
import { PermissionV2 } from "../permission"
import { PluginV2 } from "../plugin"
const TRUNCATION_GLOB = path.join(Global.Path.data, "tool-output", "*")
const BUILD_SYSTEM =
@ -97,12 +96,10 @@ Rules:
- If the conversation ends with an unanswered question to the user, preserve that exact question
- If the conversation ends with an imperative statement or request to the user (e.g. "Now please run the command and paste the console output"), always include that exact request in the summary`
export const Plugin = PluginV2.define({
id: PluginV2.ID.make("agent"),
effect: Effect.gen(function* () {
const agent = yield* AgentV2.Service
const location = yield* Location.Service
const worktree = location.directory
export const Plugin = define({
id: "agent",
effect: Effect.fn(function* (ctx) {
const worktree = ctx.location.directory
const whitelistedDirs = [TRUNCATION_GLOB, path.join(Global.Path.tmp, "*")]
const readonlyExternalDirectory: PermissionV2.Ruleset = [
{ action: "external_directory", resource: "*", effect: "ask" },
@ -122,8 +119,8 @@ export const Plugin = PluginV2.define({
{ action: "read", resource: "*.env.example", effect: "allow" },
]
yield* agent.update((editor) => {
editor.update(AgentV2.defaultID, (item) => {
yield* ctx.agent.transform((draft) => {
draft.update(AgentV2.defaultID, (item) => {
item.description = "The default agent. Executes tools based on configured permissions."
item.system ??= BUILD_SYSTEM
item.mode = "primary"
@ -135,7 +132,7 @@ export const Plugin = PluginV2.define({
)
})
editor.update(AgentV2.ID.make("plan"), (item) => {
draft.update(AgentV2.ID.make("plan"), (item) => {
item.description = "Plan mode. Disallows all edit tools."
item.mode = "primary"
item.permissions.push(
@ -154,14 +151,14 @@ export const Plugin = PluginV2.define({
)
})
editor.update(AgentV2.ID.make("general"), (item) => {
draft.update(AgentV2.ID.make("general"), (item) => {
item.description =
"General-purpose agent for researching complex questions and executing multi-step tasks. Use this agent to execute multiple units of work in parallel."
item.mode = "subagent"
item.permissions.push(...PermissionV2.merge(defaults, [{ action: "todowrite", resource: "*", effect: "deny" }]))
})
editor.update(AgentV2.ID.make("explore"), (item) => {
draft.update(AgentV2.ID.make("explore"), (item) => {
item.description =
'Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (eg. "src/components/**/*.tsx"), search code for keywords (eg. "API endpoints"), or answer questions about the codebase (eg. "how do API endpoints work?"). When calling this agent, specify the desired thoroughness level: "quick" for basic searches, "medium" for moderate exploration, or "very thorough" for comprehensive analysis across multiple locations and naming conventions.'
item.system = PROMPT_EXPLORE
@ -182,21 +179,21 @@ export const Plugin = PluginV2.define({
)
})
editor.update(AgentV2.ID.make("compaction"), (item) => {
draft.update(AgentV2.ID.make("compaction"), (item) => {
item.mode = "primary"
item.hidden = true
item.system = PROMPT_COMPACTION
item.permissions.push(...PermissionV2.merge(defaults, [{ action: "*", resource: "*", effect: "deny" }]))
})
editor.update(AgentV2.ID.make("title"), (item) => {
draft.update(AgentV2.ID.make("title"), (item) => {
item.mode = "primary"
item.hidden = true
item.system = PROMPT_TITLE
item.permissions.push(...PermissionV2.merge(defaults, [{ action: "*", resource: "*", effect: "deny" }]))
})
editor.update(AgentV2.ID.make("summary"), (item) => {
draft.update(AgentV2.ID.make("summary"), (item) => {
item.mode = "primary"
item.hidden = true
item.system = PROMPT_SUMMARY

View file

@ -1,7 +1,7 @@
export * as PluginBoot from "./boot"
import type { Plugin as PublicPlugin } from "@opencode-ai/plugin/v2/effect"
import { Context, Deferred, Effect, Layer } from "effect"
import { Credential } from "../credential"
import { Integration } from "../integration"
import { AgentV2 } from "../agent"
import { Catalog } from "../catalog"
@ -13,6 +13,7 @@ import { ConfigSkillPlugin } from "../config/plugin/skill"
import { ConfigReferencePlugin } from "../config/plugin/reference"
import { EventV2 } from "../event"
import { FSUtil } from "../fs-util"
import { FileSystem } from "../filesystem"
import { Global } from "../global"
import { Location } from "../location"
import { ModelsDev } from "../models-dev"
@ -26,29 +27,13 @@ import { ModelsDevPlugin } from "./models-dev"
import { ProviderPlugins } from "./provider"
import { SkillV2 } from "../skill"
import { Reference } from "../reference"
import { State } from "../state"
import { PluginHost } from "./host"
type Plugin = {
id: PluginV2.ID
effect: PluginV2.Effect<
| Catalog.Service
| CommandV2.Service
| Credential.Service
| Integration.Service
| AgentV2.Service
| Npm.Service
| EventV2.Service
| FSUtil.Service
| Global.Service
| Location.Service
| PluginV2.Service
| Config.Service
| ModelsDev.Service
| SkillV2.Service
| Reference.Service
>
}
type InternalPlugin = PublicPlugin<any>
export interface Interface {
readonly add: (plugin: PublicPlugin<any>) => Effect.Effect<void>
readonly wait: () => Effect.Effect<void>
}
@ -60,8 +45,7 @@ export const layer = Layer.effect(
const catalog = yield* Catalog.Service
const commands = yield* CommandV2.Service
const plugin = yield* PluginV2.Service
const credentials = yield* Credential.Service
const integrations = yield* Integration.Service
const integration = yield* Integration.Service
const agents = yield* AgentV2.Service
const config = yield* Config.Service
const location = yield* Location.Service
@ -69,47 +53,54 @@ export const layer = Layer.effect(
const npm = yield* Npm.Service
const events = yield* EventV2.Service
const fs = yield* FSUtil.Service
const filesystem = yield* FileSystem.Service
const global = yield* Global.Service
const skill = yield* SkillV2.Service
const references = yield* Reference.Service
const reference = yield* Reference.Service
const host = yield* PluginHost.make()
const done = yield* Deferred.make<void>()
const add = Effect.fn("PluginBoot.add")(function* (input: Plugin) {
const add = Effect.fn("PluginBoot.add")(function* (input: InternalPlugin) {
yield* plugin.add({
id: input.id,
effect: input.effect.pipe(
Effect.provideService(Catalog.Service, catalog),
Effect.provideService(CommandV2.Service, commands),
Effect.provideService(Credential.Service, credentials),
Effect.provideService(Integration.Service, integrations),
Effect.provideService(AgentV2.Service, agents),
Effect.provideService(Config.Service, config),
Effect.provideService(Location.Service, location),
Effect.provideService(ModelsDev.Service, modelsDev),
Effect.provideService(Npm.Service, npm),
Effect.provideService(EventV2.Service, events),
Effect.provideService(FSUtil.Service, fs),
Effect.provideService(Global.Service, global),
Effect.provideService(SkillV2.Service, skill),
Effect.provideService(Reference.Service, references),
Effect.provideService(PluginV2.Service, plugin),
),
effect: input
.effect(host)
.pipe(
Effect.provideService(Catalog.Service, catalog),
Effect.provideService(CommandV2.Service, commands),
Effect.provideService(Integration.Service, integration),
Effect.provideService(AgentV2.Service, agents),
Effect.provideService(Config.Service, config),
Effect.provideService(Location.Service, location),
Effect.provideService(ModelsDev.Service, modelsDev),
Effect.provideService(Npm.Service, npm),
Effect.provideService(EventV2.Service, events),
Effect.provideService(FSUtil.Service, fs),
Effect.provideService(FileSystem.Service, filesystem),
Effect.provideService(Global.Service, global),
Effect.provideService(SkillV2.Service, skill),
Effect.provideService(Reference.Service, reference),
),
})
})
const boot = Effect.gen(function* () {
yield* add(AgentPlugin.Plugin)
yield* add(CommandPlugin.Plugin)
yield* add(SkillPlugin.Plugin)
for (const item of ProviderPlugins) {
yield* add(item)
}
yield* add(ModelsDevPlugin)
yield* add(ConfigProviderPlugin.Plugin)
yield* add(ConfigAgentPlugin.Plugin)
yield* add(ConfigCommandPlugin.Plugin)
yield* add(ConfigSkillPlugin.Plugin)
yield* add(ConfigReferencePlugin.Plugin)
yield* State.batch(
Effect.gen(function* () {
yield* add(AgentPlugin.Plugin)
yield* add(CommandPlugin.Plugin)
yield* add(SkillPlugin.Plugin)
yield* add(ModelsDevPlugin)
yield* add(ConfigProviderPlugin.Plugin)
yield* add(ConfigAgentPlugin.Plugin)
yield* add(ConfigCommandPlugin.Plugin)
yield* add(ConfigSkillPlugin.Plugin)
yield* add(ConfigReferencePlugin.Plugin)
for (const item of ProviderPlugins) {
yield* add(item)
}
}),
)
}).pipe(Effect.withSpan("PluginBoot.boot"))
yield* boot.pipe(
@ -119,12 +110,22 @@ export const layer = Layer.effect(
)
return Service.of({
add: (input) =>
Deferred.await(done).pipe(
Effect.andThen(
plugin.add({
id: input.id,
effect: input.effect(host),
}),
),
),
wait: () => Deferred.await(done),
})
}),
)
export const locationLayer = layer.pipe(
Layer.provideMerge(PluginV2.locationLayer),
Layer.provideMerge(Integration.locationLayer),
Layer.provideMerge(Catalog.locationLayer),
Layer.provideMerge(CommandV2.locationLayer),
@ -132,4 +133,5 @@ export const locationLayer = layer.pipe(
Layer.provideMerge(AgentV2.locationLayer),
Layer.provideMerge(SkillV2.locationLayer),
Layer.provideMerge(Reference.locationLayer),
Layer.provideMerge(FileSystem.locationLayer),
)

View file

@ -1,26 +1,20 @@
export * as CommandPlugin from "./command"
import { define } from "@opencode-ai/plugin/v2/effect"
import { Effect } from "effect"
import { CommandV2 } from "../command"
import { Location } from "../location"
import { PluginV2 } from "../plugin"
import PROMPT_INITIALIZE from "./command/initialize.txt"
import PROMPT_REVIEW from "./command/review.txt"
export const Plugin = PluginV2.define({
id: PluginV2.ID.make("command"),
effect: Effect.gen(function* () {
const command = yield* CommandV2.Service
const location = yield* Location.Service
const transform = yield* command.transform()
yield* transform((editor) => {
editor.update("init", (command) => {
command.template = PROMPT_INITIALIZE.replace("${path}", location.project.directory)
export const Plugin = define({
id: "command",
effect: Effect.fn(function* (ctx) {
yield* ctx.command.transform((draft) => {
draft.update("init", (command) => {
command.template = PROMPT_INITIALIZE.replace("${path}", ctx.location.project.directory)
command.description = "guided AGENTS.md setup"
})
editor.update("review", (command) => {
command.template = PROMPT_REVIEW.replace("${path}", location.project.directory)
draft.update("review", (command) => {
command.template = PROMPT_REVIEW.replace("${path}", ctx.location.project.directory)
command.description = "review changes [commit|branch|pr], defaults to uncommitted"
command.subtask = true
})

View file

@ -0,0 +1,236 @@
export * as PluginHost from "./host"
import type { LanguageModelV3 } from "@ai-sdk/provider"
import type { PluginHost as Interface } from "@opencode-ai/plugin/v2/effect"
import type { Event as SDKEvent, ModelV2Info } from "@opencode-ai/sdk/v2/types"
import { Effect, Schema, Stream } from "effect"
import { AgentV2 } from "../agent"
import { Catalog } from "../catalog"
import { CommandV2 } from "../command"
import { EventV2 } from "../event"
import { FileSystem } from "../filesystem"
import { Global } from "../global"
import { Integration } from "../integration"
import { Location } from "../location"
import { ModelV2 } from "../model"
import { Npm } from "../npm"
import { PluginV2 } from "../plugin"
import { ProviderV2 } from "../provider"
import { Reference } from "../reference"
import { SkillV2 } from "../skill"
type EventMap = { [Item in SDKEvent as Item["type"]]: Item }
type SDKHook = (event: {
readonly model: ModelV2Info
readonly package: string
readonly options: Record<string, any>
sdk?: any
}) => Effect.Effect<void> | void
type LanguageHook = (event: {
readonly model: ModelV2Info
readonly sdk: any
readonly options: Record<string, any>
language?: LanguageModelV3
}) => Effect.Effect<void> | void
export const make = Effect.fn("PluginHost.make")(function* () {
const agents = yield* AgentV2.Service
const catalog = yield* Catalog.Service
const commands = yield* CommandV2.Service
const events = yield* EventV2.Service
const filesystem = yield* FileSystem.Service
const global = yield* Global.Service
const integration = yield* Integration.Service
const location = yield* Location.Service
const npm = yield* Npm.Service
const plugin = yield* PluginV2.Service
const reference = yield* Reference.Service
const skill = yield* SkillV2.Service
return {
agent: {
get: (id) => agents.get(AgentV2.ID.make(id)),
default: agents.default,
list: agents.all,
rebuild: agents.rebuild,
transform: (callback) =>
agents.transform((draft) =>
callback({
list: draft.list,
get: (id) => draft.get(AgentV2.ID.make(id)),
default: (id) => draft.default(id === undefined ? undefined : AgentV2.ID.make(id)),
update: (id, update) => draft.update(AgentV2.ID.make(id), update),
remove: (id) => draft.remove(AgentV2.ID.make(id)),
}),
),
},
aisdk: {
hook: (name, callback) => {
if (name === "sdk") {
const run = callback as SDKHook
return plugin.hook("aisdk.sdk", (event) => {
const output = {
model: event.model,
package: event.package,
options: event.options,
sdk: event.sdk,
}
const result = run(output)
return Effect.suspend(() => (Effect.isEffect(result) ? result : Effect.void)).pipe(
Effect.tap(() => Effect.sync(() => (event.sdk = output.sdk))),
)
})
}
const run = callback as LanguageHook
return plugin.hook("aisdk.language", (event) => {
const output = {
model: event.model,
sdk: event.sdk,
options: event.options,
language: event.language,
}
const result = run(output)
return Effect.suspend(() => (Effect.isEffect(result) ? result : Effect.void)).pipe(
Effect.tap(() => Effect.sync(() => (event.language = output.language))),
)
})
},
},
catalog: {
provider: {
get: (id) => catalog.provider.get(ProviderV2.ID.make(id)),
list: catalog.provider.all,
available: catalog.provider.available,
},
model: {
get: (providerID, modelID) => catalog.model.get(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID)),
list: catalog.model.all,
available: catalog.model.available,
default: catalog.model.default,
small: (providerID) => catalog.model.small(ProviderV2.ID.make(providerID)),
},
rebuild: catalog.rebuild,
transform: (callback) =>
catalog.transform((draft) =>
callback({
provider: {
list: draft.provider.list,
get: (id) => draft.provider.get(ProviderV2.ID.make(id)),
update: (id, update) => draft.provider.update(ProviderV2.ID.make(id), update),
remove: (id) => draft.provider.remove(ProviderV2.ID.make(id)),
},
model: {
get: (providerID, modelID) => draft.model.get(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID)),
update: (providerID, modelID, update) =>
draft.model.update(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID), update),
remove: (providerID, modelID) =>
draft.model.remove(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID)),
default: {
get: draft.model.default.get,
set: (providerID, modelID) =>
draft.model.default.set(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID)),
},
},
}),
),
},
command: {
get: commands.get,
list: commands.list,
rebuild: commands.rebuild,
transform: commands.transform,
},
event: {
subscribe: <Type extends keyof EventMap>(type: Type): Stream.Stream<EventMap[Type]> =>
Stream.unwrap(
Effect.sync(() => {
const definition = EventV2.registry.get(type)
if (!definition) throw new Error(`Unknown event type: ${type}`)
const encode = Schema.encodeUnknownSync(definition.data as Schema.Codec<unknown, unknown, never, never>)
return events.subscribe(definition).pipe(
Stream.map(
(event) =>
({
id: event.id,
type: event.type,
properties: encode(event.data),
}) as unknown as EventMap[Type],
),
)
}),
),
},
filesystem: {
read: (input) => filesystem.read(Schema.decodeUnknownSync(FileSystem.ReadInput)(input)),
list: (input) => filesystem.list(Schema.decodeUnknownSync(FileSystem.ListInput)(input ?? {})),
find: (input) => filesystem.find(Schema.decodeUnknownSync(FileSystem.FindInput)(input)),
glob: (input) => filesystem.glob(Schema.decodeUnknownSync(FileSystem.GlobInput)(input)),
},
integration: {
get: (id) => integration.get(Integration.ID.make(id)),
list: integration.list,
rebuild: integration.rebuild,
transform: (callback) =>
integration.transform((draft) =>
callback({
list: draft.list,
get: (id) => draft.get(Integration.ID.make(id)),
update: (id, update) => draft.update(Integration.ID.make(id), update),
remove: (id) => draft.remove(Integration.ID.make(id)),
method: {
list: (id) => draft.method.list(Integration.ID.make(id)),
update: (input) => {
if (input.method.type === "env") {
draft.method.update({
integrationID: Integration.ID.make(input.integrationID),
method: { type: "env", names: input.method.names },
})
return
}
draft.method.update({
integrationID: Integration.ID.make(input.integrationID),
method: { type: "key", label: input.method.label },
})
},
remove: (id, method) =>
draft.method.remove(Integration.ID.make(id), Schema.decodeUnknownSync(Integration.Method)(method)),
},
}),
),
},
location,
npm,
path: {
home: global.home,
data: global.data,
cache: global.cache,
config: global.config,
state: global.state,
temp: global.tmp,
},
reference: {
list: reference.list,
rebuild: reference.rebuild,
transform: (callback) =>
reference.transform((draft) =>
callback({
add: (name, source) => draft.add(name, Schema.decodeUnknownSync(Reference.Source)(source)),
remove: draft.remove,
list: draft.list,
}),
),
},
skill: {
sources: skill.sources,
list: skill.list,
rebuild: skill.rebuild,
transform: (callback) =>
skill.transform((draft) =>
callback({
source: (source) => draft.source(Schema.decodeUnknownSync(SkillV2.Source)(source)),
list: draft.list,
}),
),
},
} satisfies Interface
})

View file

@ -1,16 +1,13 @@
import { DateTime, Effect, Scope, Stream } from "effect"
import { Catalog } from "../catalog"
import { Integration } from "../integration"
import { EventV2 } from "../event"
import { define } from "@opencode-ai/plugin/v2/effect"
import { Effect, Stream } from "effect"
import { ModelV2 } from "../model"
import { ModelRequest } from "../model-request"
import { ModelsDev } from "../models-dev"
import { PluginV2 } from "../plugin"
import { ProviderV2 } from "../provider"
function released(date: string) {
const time = Date.parse(date)
return DateTime.makeUnsafe(Number.isFinite(time) ? time : 0)
return Number.isFinite(time) ? time : 0
}
function cost(input: ModelsDev.Model["cost"]) {
@ -51,22 +48,16 @@ function variants(model: ModelsDev.Model, packageName?: string) {
})
}
export const ModelsDevPlugin = PluginV2.define({
id: PluginV2.ID.make("models-dev"),
effect: Effect.gen(function* () {
const catalog = yield* Catalog.Service
const integrations = yield* Integration.Service
export const ModelsDevPlugin = define({
id: "models-dev",
effect: Effect.fn(function* (ctx) {
const modelsDev = yield* ModelsDev.Service
const events = yield* EventV2.Service
const scope = yield* Scope.Scope
const transform = yield* catalog.transform()
const integrationTransform = yield* integrations.transform()
const refresh = Effect.fn("ModelsDevPlugin.refresh")(function* () {
const data = yield* modelsDev.get()
yield* integrationTransform((integrations) => {
yield* ctx.integration.transform(
Effect.fn(function* (integrations) {
const data = yield* modelsDev.get()
for (const item of Object.values(data)) {
if (item.env.length === 0) continue
const integrationID = Integration.ID.make(item.id)
const integrationID = item.id
integrations.update(integrationID, (integration) => (integration.name = item.name))
integrations.method.update({
integrationID,
@ -77,8 +68,11 @@ export const ModelsDevPlugin = PluginV2.define({
method: { type: "env", names: [...item.env] },
})
}
})
yield* transform((catalog) => {
}),
)
yield* ctx.catalog.transform(
Effect.fn(function* (catalog) {
const data = yield* modelsDev.get()
for (const item of Object.values(data)) {
const providerID = ProviderV2.ID.make(item.id)
catalog.provider.update(providerID, (provider) => {
@ -132,11 +126,10 @@ export const ModelsDevPlugin = PluginV2.define({
})
}
}
})
})
yield* refresh()
yield* events.subscribe(ModelsDev.Event.Refreshed).pipe(
Stream.runForEach(() => refresh()),
}),
)
yield* ctx.event.subscribe("models-dev.refreshed").pipe(
Stream.runForEach(() => ctx.integration.rebuild().pipe(Effect.andThen(ctx.catalog.rebuild()))),
Effect.forkScoped({ startImmediately: true }),
)
}),

View file

@ -1,15 +1,16 @@
import { Effect } from "effect"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
export const AlibabaPlugin = PluginV2.define({
id: PluginV2.ID.make("alibaba"),
effect: Effect.gen(function* () {
return {
"aisdk.sdk": Effect.fn(function* (evt) {
export const AlibabaPlugin = define({
id: "alibaba",
effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/alibaba") return
const mod = yield* Effect.promise(() => import("@ai-sdk/alibaba"))
evt.sdk = mod.createAlibaba(evt.options)
}),
}
)
}),
})

View file

@ -1,6 +1,6 @@
import { Effect } from "effect"
import type { LanguageModelV3 } from "@ai-sdk/provider"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
import { ProviderV2 } from "../../provider"
type MantleSDK = {
@ -59,11 +59,11 @@ function selectMantleModel(sdk: MantleSDK, modelID: string) {
return sdk.responses(modelID)
}
export const AmazonBedrockPlugin = PluginV2.define({
id: PluginV2.ID.make("amazon-bedrock"),
effect: Effect.gen(function* () {
return {
"catalog.transform": Effect.fn(function* (evt) {
export const AmazonBedrockPlugin = define({
id: "amazon-bedrock",
effect: Effect.fn(function* (ctx) {
yield* ctx.catalog.transform(
Effect.fn(function* (evt) {
for (const item of evt.provider.list()) {
if (item.provider.api.type !== "aisdk") continue
if (item.provider.api.package !== "@ai-sdk/amazon-bedrock") continue
@ -77,7 +77,10 @@ export const AmazonBedrockPlugin = PluginV2.define({
})
}
}),
"aisdk.sdk": Effect.fn(function* (evt) {
)
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (!["@ai-sdk/amazon-bedrock", "@ai-sdk/amazon-bedrock/mantle"].includes(evt.package)) return
const options = { ...evt.options }
const profile = typeof options.profile === "string" ? options.profile : process.env.AWS_PROFILE
@ -108,7 +111,10 @@ export const AmazonBedrockPlugin = PluginV2.define({
const mod = yield* Effect.promise(() => import("@ai-sdk/amazon-bedrock"))
evt.sdk = mod.createAmazonBedrock(options)
}),
"aisdk.language": Effect.fn(function* (evt) {
)
yield* ctx.aisdk.hook(
"language",
Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.amazonBedrock) return
if (evt.model.api.type === "aisdk" && evt.model.api.package === "@ai-sdk/amazon-bedrock/mantle") {
evt.language = selectMantleModel(evt.sdk, evt.model.api.id)
@ -117,6 +123,6 @@ export const AmazonBedrockPlugin = PluginV2.define({
const region = typeof evt.options.region === "string" ? evt.options.region : process.env.AWS_REGION
evt.language = evt.sdk.languageModel(resolveModelID(evt.model.api.id, region))
}),
}
)
}),
})

View file

@ -1,11 +1,11 @@
import { Effect } from "effect"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
export const AnthropicPlugin = PluginV2.define({
id: PluginV2.ID.make("anthropic"),
effect: Effect.gen(function* () {
return {
"catalog.transform": Effect.fn(function* (evt) {
export const AnthropicPlugin = define({
id: "anthropic",
effect: Effect.fn(function* (ctx) {
yield* ctx.catalog.transform(
Effect.fn(function* (evt) {
for (const item of evt.provider.list()) {
if (item.provider.api.type !== "aisdk") continue
if (item.provider.api.package !== "@ai-sdk/anthropic") continue
@ -15,11 +15,14 @@ export const AnthropicPlugin = PluginV2.define({
})
}
}),
"aisdk.sdk": Effect.fn(function* (evt) {
)
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/anthropic") return
const mod = yield* Effect.promise(() => import("@ai-sdk/anthropic"))
evt.sdk = mod.createAnthropic(evt.options)
}),
}
)
}),
})

View file

@ -1,5 +1,5 @@
import { Effect } from "effect"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
import { ProviderV2 } from "../../provider"
function selectLanguage(sdk: any, modelID: string, useChat: boolean) {
@ -10,11 +10,11 @@ function selectLanguage(sdk: any, modelID: string, useChat: boolean) {
return sdk.languageModel(modelID)
}
export const AzurePlugin = PluginV2.define({
id: PluginV2.ID.make("azure"),
effect: Effect.gen(function* () {
return {
"catalog.transform": Effect.fn(function* (evt) {
export const AzurePlugin = define({
id: "azure",
effect: Effect.fn(function* (ctx) {
yield* ctx.catalog.transform(
Effect.fn(function* (evt) {
for (const item of evt.provider.list()) {
if (item.provider.api.type !== "aisdk") continue
if (item.provider.api.package !== "@ai-sdk/azure") continue
@ -27,7 +27,10 @@ export const AzurePlugin = PluginV2.define({
})
}
}),
"aisdk.sdk": Effect.fn(function* (evt) {
)
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/azure") return
if (evt.model.providerID === ProviderV2.ID.azure) {
if (
@ -43,19 +46,22 @@ export const AzurePlugin = PluginV2.define({
const mod = yield* Effect.promise(() => import("@ai-sdk/azure"))
evt.sdk = mod.createAzure(evt.options)
}),
"aisdk.language": Effect.fn(function* (evt) {
)
yield* ctx.aisdk.hook(
"language",
Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.azure) return
evt.language = selectLanguage(evt.sdk, evt.model.api.id, Boolean(evt.options.useCompletionUrls))
}),
}
)
}),
})
export const AzureCognitiveServicesPlugin = PluginV2.define({
id: PluginV2.ID.make("azure-cognitive-services"),
effect: Effect.gen(function* () {
return {
"catalog.transform": Effect.fn(function* (evt) {
export const AzureCognitiveServicesPlugin = define({
id: "azure-cognitive-services",
effect: Effect.fn(function* (ctx) {
yield* ctx.catalog.transform(
Effect.fn(function* (evt) {
const resourceName = process.env.AZURE_COGNITIVE_SERVICES_RESOURCE_NAME
if (!resourceName) return
for (const item of evt.provider.list()) {
@ -67,10 +73,13 @@ export const AzureCognitiveServicesPlugin = PluginV2.define({
})
}
}),
"aisdk.language": Effect.fn(function* (evt) {
)
yield* ctx.aisdk.hook(
"language",
Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.make("azure-cognitive-services")) return
evt.language = selectLanguage(evt.sdk, evt.model.api.id, Boolean(evt.options.useCompletionUrls))
}),
}
)
}),
})

View file

@ -1,24 +1,27 @@
import { Effect } from "effect"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
export const CerebrasPlugin = PluginV2.define({
id: PluginV2.ID.make("cerebras"),
effect: Effect.gen(function* () {
return {
"catalog.transform": Effect.fn(function* (ctx) {
for (const item of ctx.provider.list()) {
export const CerebrasPlugin = define({
id: "cerebras",
effect: Effect.fn(function* (ctx) {
yield* ctx.catalog.transform(
Effect.fn(function* (evt) {
for (const item of evt.provider.list()) {
if (item.provider.api.type !== "aisdk") continue
if (item.provider.api.package !== "@ai-sdk/cerebras") continue
ctx.provider.update(item.provider.id, (provider) => {
evt.provider.update(item.provider.id, (provider) => {
provider.request.headers["X-Cerebras-3rd-Party-Integration"] = "opencode"
})
}
}),
"aisdk.sdk": Effect.fn(function* (evt) {
)
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/cerebras") return
const mod = yield* Effect.promise(() => import("@ai-sdk/cerebras"))
evt.sdk = mod.createCerebras(evt.options)
}),
}
)
}),
})

View file

@ -1,13 +1,14 @@
import os from "os"
import { InstallationVersion } from "../../installation/version"
import { Effect, Option, Schema } from "effect"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
export const CloudflareAIGatewayPlugin = PluginV2.define({
id: PluginV2.ID.make("cloudflare-ai-gateway"),
effect: Effect.gen(function* () {
return {
"aisdk.sdk": Effect.fn(function* (evt) {
export const CloudflareAIGatewayPlugin = define({
id: "cloudflare-ai-gateway",
effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.package !== "ai-gateway-provider") return
if (evt.options.baseURL) return
@ -31,7 +32,7 @@ export const CloudflareAIGatewayPlugin = PluginV2.define({
},
}
}),
}
)
}),
})

View file

@ -1,16 +1,16 @@
import os from "os"
import { InstallationVersion } from "../../installation/version"
import { Effect } from "effect"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
import { ProviderV2 } from "../../provider"
const providerID = ProviderV2.ID.make("cloudflare-workers-ai")
export const CloudflareWorkersAIPlugin = PluginV2.define({
id: PluginV2.ID.make("cloudflare-workers-ai"),
effect: Effect.gen(function* () {
return {
"catalog.transform": Effect.fn(function* (evt) {
export const CloudflareWorkersAIPlugin = define({
id: "cloudflare-workers-ai",
effect: Effect.fn(function* (ctx) {
yield* ctx.catalog.transform(
Effect.fn(function* (evt) {
const item = evt.provider.get(providerID)
if (!item) return
evt.provider.update(item.provider.id, (provider) => {
@ -20,7 +20,10 @@ export const CloudflareWorkersAIPlugin = PluginV2.define({
if (accountId) provider.api.url = workersEndpoint(accountId)
})
}),
"aisdk.sdk": Effect.fn(function* (evt) {
)
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.model.providerID !== providerID) return
if (evt.package !== "@ai-sdk/openai-compatible") return
@ -34,11 +37,14 @@ export const CloudflareWorkersAIPlugin = PluginV2.define({
}) as any,
)
}),
"aisdk.language": Effect.fn(function* (evt) {
)
yield* ctx.aisdk.hook(
"language",
Effect.fn(function* (evt) {
if (evt.model.providerID !== providerID) return
evt.language = evt.sdk.languageModel(evt.model.api.id)
}),
}
)
}),
})

View file

@ -1,15 +1,16 @@
import { Effect } from "effect"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
export const CoherePlugin = PluginV2.define({
id: PluginV2.ID.make("cohere"),
effect: Effect.gen(function* () {
return {
"aisdk.sdk": Effect.fn(function* (evt) {
export const CoherePlugin = define({
id: "cohere",
effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/cohere") return
const mod = yield* Effect.promise(() => import("@ai-sdk/cohere"))
evt.sdk = mod.createCohere(evt.options)
}),
}
)
}),
})

View file

@ -1,15 +1,16 @@
import { Effect } from "effect"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
export const DeepInfraPlugin = PluginV2.define({
id: PluginV2.ID.make("deepinfra"),
effect: Effect.gen(function* () {
return {
"aisdk.sdk": Effect.fn(function* (evt) {
export const DeepInfraPlugin = define({
id: "deepinfra",
effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/deepinfra") return
const mod = yield* Effect.promise(() => import("@ai-sdk/deepinfra"))
evt.sdk = mod.createDeepInfra(evt.options)
}),
}
)
}),
})

View file

@ -1,19 +1,18 @@
import { Npm } from "../../npm"
import { Effect, Option } from "effect"
import { Effect } from "effect"
import { pathToFileURL } from "url"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
export const DynamicProviderPlugin = PluginV2.define({
id: PluginV2.ID.make("dynamic-provider"),
effect: Effect.gen(function* () {
const npm = yield* Npm.Service
return {
"aisdk.sdk": Effect.fn(function* (evt) {
export const DynamicProviderPlugin = define({
id: "dynamic-provider",
effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.sdk) return
const installedPath = evt.package.startsWith("file://")
? evt.package
: Option.getOrUndefined((yield* npm.add(evt.package).pipe(Effect.orDie)).entrypoint)
: (yield* ctx.npm.add(evt.package).pipe(Effect.orDie)).entrypoint
if (!installedPath) throw new Error(`Package ${evt.package} has no import entrypoint`)
const mod = yield* Effect.promise(async () => {
@ -26,6 +25,6 @@ export const DynamicProviderPlugin = PluginV2.define({
evt.sdk = mod[match](evt.options)
}),
}
)
}),
})

View file

@ -1,15 +1,16 @@
import { Effect } from "effect"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
export const GatewayPlugin = PluginV2.define({
id: PluginV2.ID.make("gateway"),
effect: Effect.gen(function* () {
return {
"aisdk.sdk": Effect.fn(function* (evt) {
export const GatewayPlugin = define({
id: "gateway",
effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/gateway") return
const mod = yield* Effect.promise(() => import("@ai-sdk/gateway"))
evt.sdk = mod.createGateway(evt.options)
}),
}
)
}),
})

View file

@ -1,6 +1,6 @@
import { Effect } from "effect"
import { ModelV2 } from "../../model"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
import { ProviderV2 } from "../../provider"
function shouldUseResponses(modelID: string) {
@ -11,16 +11,31 @@ function shouldUseResponses(modelID: string) {
return Number(match[1]) >= 5 && !modelID.startsWith("gpt-5-mini")
}
export const GithubCopilotPlugin = PluginV2.define({
id: PluginV2.ID.make("github-copilot"),
effect: Effect.gen(function* () {
return {
"aisdk.sdk": Effect.fn(function* (evt) {
export const GithubCopilotPlugin = define({
id: "github-copilot",
effect: Effect.fn(function* (ctx) {
yield* ctx.catalog.transform(
Effect.fn(function* (evt) {
const item = evt.provider.get(ProviderV2.ID.githubCopilot)
if (!item || !item.models.has(ModelV2.ID.make("gpt-5-chat-latest"))) return
evt.model.update(item.provider.id, ModelV2.ID.make("gpt-5-chat-latest"), (model) => {
// This chat-only alias conflicts with the Copilot GPT-5 Responses route,
// so hide it only for Copilot rather than for every provider catalog.
model.enabled = false
})
}),
)
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/github-copilot") return
const mod = yield* Effect.promise(() => import("../../github-copilot/copilot-provider"))
evt.sdk = mod.createOpenaiCompatible(evt.options)
}),
"aisdk.language": Effect.fn(function* (evt) {
)
yield* ctx.aisdk.hook(
"language",
Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.githubCopilot) return
if (evt.sdk.responses === undefined && evt.sdk.chat === undefined) {
evt.language = evt.sdk.languageModel(evt.model.api.id)
@ -30,15 +45,6 @@ export const GithubCopilotPlugin = PluginV2.define({
? evt.sdk.responses(evt.model.api.id)
: evt.sdk.chat(evt.model.api.id)
}),
"catalog.transform": Effect.fn(function* (evt) {
const item = evt.provider.get(ProviderV2.ID.githubCopilot)
if (!item || !item.models.has(ModelV2.ID.make("gpt-5-chat-latest"))) return
evt.model.update(item.provider.id, ModelV2.ID.make("gpt-5-chat-latest"), (model) => {
// This chat-only alias conflicts with the Copilot GPT-5 Responses route,
// so hide it only for Copilot rather than for every provider catalog.
model.enabled = false
})
}),
}
)
}),
})

View file

@ -1,14 +1,15 @@
import os from "os"
import { InstallationVersion } from "../../installation/version"
import { Effect } from "effect"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
import { ProviderV2 } from "../../provider"
export const GitLabPlugin = PluginV2.define({
id: PluginV2.ID.make("gitlab"),
effect: Effect.gen(function* () {
return {
"aisdk.sdk": Effect.fn(function* (evt) {
export const GitLabPlugin = define({
id: "gitlab",
effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.package !== "gitlab-ai-provider") return
const mod = yield* Effect.promise(() => import("gitlab-ai-provider"))
evt.sdk = mod.createGitLab({
@ -30,7 +31,10 @@ export const GitLabPlugin = PluginV2.define({
},
})
}),
"aisdk.language": Effect.fn(function* (evt) {
)
yield* ctx.aisdk.hook(
"language",
Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.gitlab) return
const featureFlags =
typeof evt.options.featureFlags === "object" && evt.options.featureFlags ? evt.options.featureFlags : {}
@ -58,6 +62,6 @@ export const GitLabPlugin = PluginV2.define({
featureFlags,
})
}),
}
)
}),
})

View file

@ -1,5 +1,5 @@
import { Effect } from "effect"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
import { ProviderV2 } from "../../provider"
function resolveProject(options: Record<string, any>) {
@ -54,11 +54,11 @@ function authFetch(fetchWithRuntimeOptions?: unknown) {
}
}
export const GoogleVertexPlugin = PluginV2.define({
id: PluginV2.ID.make("google-vertex"),
effect: Effect.gen(function* () {
return {
"catalog.transform": Effect.fn(function* (evt) {
export const GoogleVertexPlugin = define({
id: "google-vertex",
effect: Effect.fn(function* (ctx) {
yield* ctx.catalog.transform(
Effect.fn(function* (evt) {
for (const item of evt.provider.list()) {
if (item.provider.api.type !== "aisdk") continue
if (
@ -83,7 +83,10 @@ export const GoogleVertexPlugin = PluginV2.define({
})
}
}),
"aisdk.sdk": Effect.fn(function* (evt) {
)
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.model.providerID === ProviderV2.ID.googleVertex && evt.package.includes("@ai-sdk/openai-compatible")) {
evt.options.fetch = authFetch(evt.options.fetch)
return
@ -100,19 +103,22 @@ export const GoogleVertexPlugin = PluginV2.define({
location,
})
}),
"aisdk.language": Effect.fn(function* (evt) {
)
yield* ctx.aisdk.hook(
"language",
Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.googleVertex) return
evt.language = evt.sdk.languageModel(String(evt.model.api.id).trim())
}),
}
)
}),
})
export const GoogleVertexAnthropicPlugin = PluginV2.define({
id: PluginV2.ID.make("google-vertex-anthropic"),
effect: Effect.gen(function* () {
return {
"catalog.transform": Effect.fn(function* (evt) {
export const GoogleVertexAnthropicPlugin = define({
id: "google-vertex-anthropic",
effect: Effect.fn(function* (ctx) {
yield* ctx.catalog.transform(
Effect.fn(function* (evt) {
for (const item of evt.provider.list()) {
if (item.provider.api.type !== "aisdk") continue
if (item.provider.api.package !== "@ai-sdk/google-vertex/anthropic") continue
@ -132,7 +138,10 @@ export const GoogleVertexAnthropicPlugin = PluginV2.define({
})
}
}),
"aisdk.sdk": Effect.fn(function* (evt) {
)
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/google-vertex/anthropic") return
const mod = yield* Effect.promise(() => import("@ai-sdk/google-vertex/anthropic"))
const project =
@ -156,10 +165,13 @@ export const GoogleVertexAnthropicPlugin = PluginV2.define({
: {}),
})
}),
"aisdk.language": Effect.fn(function* (evt) {
)
yield* ctx.aisdk.hook(
"language",
Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.make("google-vertex-anthropic")) return
evt.language = evt.sdk.languageModel(String(evt.model.api.id).trim())
}),
}
)
}),
})

View file

@ -1,15 +1,16 @@
import { Effect } from "effect"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
export const GooglePlugin = PluginV2.define({
id: PluginV2.ID.make("google"),
effect: Effect.gen(function* () {
return {
"aisdk.sdk": Effect.fn(function* (evt) {
export const GooglePlugin = define({
id: "google",
effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/google") return
const mod = yield* Effect.promise(() => import("@ai-sdk/google"))
evt.sdk = mod.createGoogleGenerativeAI(evt.options)
}),
}
)
}),
})

View file

@ -1,15 +1,16 @@
import { Effect } from "effect"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
export const GroqPlugin = PluginV2.define({
id: PluginV2.ID.make("groq"),
effect: Effect.gen(function* () {
return {
"aisdk.sdk": Effect.fn(function* (evt) {
export const GroqPlugin = define({
id: "groq",
effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/groq") return
const mod = yield* Effect.promise(() => import("@ai-sdk/groq"))
evt.sdk = mod.createGroq(evt.options)
}),
}
)
}),
})

View file

@ -1,11 +1,11 @@
import { Effect } from "effect"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
export const KiloPlugin = PluginV2.define({
id: PluginV2.ID.make("kilo"),
effect: Effect.gen(function* () {
return {
"catalog.transform": Effect.fn(function* (evt) {
export const KiloPlugin = define({
id: "kilo",
effect: Effect.fn(function* (ctx) {
yield* ctx.catalog.transform(
Effect.fn(function* (evt) {
for (const item of evt.provider.list()) {
if (item.provider.api.type !== "aisdk") continue
if (item.provider.api.package !== "@ai-sdk/openai-compatible") continue
@ -16,6 +16,6 @@ export const KiloPlugin = PluginV2.define({
})
}
}),
}
)
}),
})

View file

@ -1,19 +1,17 @@
import { Effect } from "effect"
import { Integration } from "../../integration"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
export const LLMGatewayPlugin = PluginV2.define({
id: PluginV2.ID.make("llmgateway"),
effect: Effect.gen(function* () {
const integrations = yield* Integration.Service
return {
"catalog.transform": Effect.fn(function* (evt) {
export const LLMGatewayPlugin = define({
id: "llmgateway",
effect: Effect.fn(function* (ctx) {
yield* ctx.catalog.transform(
Effect.fn(function* (evt) {
for (const item of evt.provider.list()) {
if (item.provider.disabled) continue
if (!(yield* integrations.get(Integration.ID.make(item.provider.id)))) continue
if (item.provider.api.type !== "aisdk") continue
if (item.provider.api.package !== "@ai-sdk/openai-compatible") continue
if (item.provider.api.url !== "https://api.llmgateway.io/v1") continue
if (!(yield* ctx.integration.get(item.provider.id))) continue
evt.provider.update(item.provider.id, (provider) => {
provider.request.headers["HTTP-Referer"] = "https://opencode.ai/"
provider.request.headers["X-Title"] = "opencode"
@ -21,6 +19,6 @@ export const LLMGatewayPlugin = PluginV2.define({
})
}
}),
}
)
}),
})

View file

@ -1,15 +1,16 @@
import { Effect } from "effect"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
export const MistralPlugin = PluginV2.define({
id: PluginV2.ID.make("mistral"),
effect: Effect.gen(function* () {
return {
"aisdk.sdk": Effect.fn(function* (evt) {
export const MistralPlugin = define({
id: "mistral",
effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/mistral") return
const mod = yield* Effect.promise(() => import("@ai-sdk/mistral"))
evt.sdk = mod.createMistral(evt.options)
}),
}
)
}),
})

View file

@ -1,11 +1,11 @@
import { Effect } from "effect"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
export const NvidiaPlugin = PluginV2.define({
id: PluginV2.ID.make("nvidia"),
effect: Effect.gen(function* () {
return {
"catalog.transform": Effect.fn(function* (evt) {
export const NvidiaPlugin = define({
id: "nvidia",
effect: Effect.fn(function* (ctx) {
yield* ctx.catalog.transform(
Effect.fn(function* (evt) {
for (const item of evt.provider.list()) {
if (item.provider.api.type !== "aisdk") continue
if (item.provider.api.package !== "@ai-sdk/openai-compatible") continue
@ -17,6 +17,6 @@ export const NvidiaPlugin = PluginV2.define({
})
}
}),
}
)
}),
})

View file

@ -1,17 +1,18 @@
import { Effect } from "effect"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
export const OpenAICompatiblePlugin = PluginV2.define({
id: PluginV2.ID.make("openai-compatible"),
effect: Effect.gen(function* () {
return {
"aisdk.sdk": Effect.fn(function* (evt) {
export const OpenAICompatiblePlugin = define({
id: "openai-compatible",
effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.sdk) return
if (!evt.package.includes("@ai-sdk/openai-compatible")) return
if (evt.options.includeUsage !== false) evt.options.includeUsage = true
const mod = yield* Effect.promise(() => import("@ai-sdk/openai-compatible"))
evt.sdk = mod.createOpenAICompatible(evt.options as any)
}),
}
)
}),
})

View file

@ -1,29 +1,20 @@
import { Effect } from "effect"
import { ModelV2 } from "../../model"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
import { ProviderV2 } from "../../provider"
import { Integration } from "../../integration"
import { browser, headless } from "./openai-auth"
export const OpenAIPlugin = PluginV2.define({
id: PluginV2.ID.make("openai"),
effect: Effect.gen(function* () {
export const OpenAIPlugin = define({
id: "openai",
effect: Effect.fn(function* (ctx) {
const integrations = yield* Integration.Service
yield* integrations.update((editor) => {
editor.method.update(browser)
editor.method.update(headless)
yield* integrations.transform((draft) => {
draft.method.update(browser)
draft.method.update(headless)
})
return {
"aisdk.sdk": Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/openai") return
const mod = yield* Effect.promise(() => import("@ai-sdk/openai"))
evt.sdk = mod.createOpenAI(evt.options)
}),
"aisdk.language": Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.openai) return
evt.language = evt.sdk.responses(evt.model.api.id)
}),
"catalog.transform": Effect.fn(function* (evt) {
yield* ctx.catalog.transform(
Effect.fn(function* (evt) {
for (const item of evt.provider.list()) {
if (item.provider.api.type !== "aisdk") continue
if (item.provider.api.package !== "@ai-sdk/openai") continue
@ -35,6 +26,21 @@ export const OpenAIPlugin = PluginV2.define({
})
}
}),
}
)
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/openai") return
const mod = yield* Effect.promise(() => import("@ai-sdk/openai"))
evt.sdk = mod.createOpenAI(evt.options)
}),
)
yield* ctx.aisdk.hook(
"language",
Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.openai) return
evt.language = evt.sdk.responses(evt.model.api.id)
}),
)
}),
})

View file

@ -1,18 +1,16 @@
import { Effect } from "effect"
import { Integration } from "../../integration"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
import { ProviderV2 } from "../../provider"
export const OpencodePlugin = PluginV2.define({
id: PluginV2.ID.make("opencode"),
effect: Effect.gen(function* () {
const integrations = yield* Integration.Service
export const OpencodePlugin = define({
id: "opencode",
effect: Effect.fn(function* (ctx) {
let hasKey = false
return {
"catalog.transform": Effect.fn(function* (evt) {
yield* ctx.catalog.transform(
Effect.fn(function* (evt) {
const item = evt.provider.get(ProviderV2.ID.opencode)
if (!item) return
const integration = yield* integrations.get(Integration.ID.make(item.provider.id))
const integration = yield* ctx.integration.get(item.provider.id)
hasKey = Boolean(
process.env.OPENCODE_API_KEY || integration?.connections.length || item.provider.request.body.apiKey,
)
@ -27,6 +25,6 @@ export const OpencodePlugin = PluginV2.define({
})
}
}),
}
)
}),
})

View file

@ -1,12 +1,12 @@
import { Effect } from "effect"
import { ModelV2 } from "../../model"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
export const OpenRouterPlugin = PluginV2.define({
id: PluginV2.ID.make("openrouter"),
effect: Effect.gen(function* () {
return {
"catalog.transform": Effect.fn(function* (evt) {
export const OpenRouterPlugin = define({
id: "openrouter",
effect: Effect.fn(function* (ctx) {
yield* ctx.catalog.transform(
Effect.fn(function* (evt) {
for (const item of evt.provider.list()) {
if (item.provider.api.type !== "aisdk") continue
if (item.provider.api.package !== "@openrouter/ai-sdk-provider") continue
@ -24,11 +24,14 @@ export const OpenRouterPlugin = PluginV2.define({
}
}
}),
"aisdk.sdk": Effect.fn(function* (evt) {
)
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.package !== "@openrouter/ai-sdk-provider") return
const mod = yield* Effect.promise(() => import("@openrouter/ai-sdk-provider"))
evt.sdk = mod.createOpenRouter(evt.options)
}),
}
)
}),
})

View file

@ -1,15 +1,16 @@
import { Effect } from "effect"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
export const PerplexityPlugin = PluginV2.define({
id: PluginV2.ID.make("perplexity"),
effect: Effect.gen(function* () {
return {
"aisdk.sdk": Effect.fn(function* (evt) {
export const PerplexityPlugin = define({
id: "perplexity",
effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/perplexity") return
const mod = yield* Effect.promise(() => import("@ai-sdk/perplexity"))
evt.sdk = mod.createPerplexity(evt.options)
}),
}
)
}),
})

View file

@ -1,15 +1,14 @@
import { Npm } from "../../npm"
import { Effect, Option } from "effect"
import { Effect } from "effect"
import { pathToFileURL } from "url"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
import { ProviderV2 } from "../../provider"
export const SapAICorePlugin = PluginV2.define({
id: PluginV2.ID.make("sap-ai-core"),
effect: Effect.gen(function* () {
const npm = yield* Npm.Service
return {
"aisdk.sdk": Effect.fn(function* (evt) {
export const SapAICorePlugin = define({
id: "sap-ai-core",
effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.make("sap-ai-core")) return
const serviceKey =
process.env.AICORE_SERVICE_KEY ??
@ -18,7 +17,7 @@ export const SapAICorePlugin = PluginV2.define({
const installedPath = evt.package.startsWith("file://")
? evt.package
: Option.getOrUndefined((yield* npm.add(evt.package).pipe(Effect.orDie)).entrypoint)
: (yield* ctx.npm.add(evt.package).pipe(Effect.orDie)).entrypoint
if (!installedPath) throw new Error(`Package ${evt.package} has no import entrypoint`)
const mod = yield* Effect.promise(async () => {
@ -35,10 +34,13 @@ export const SapAICorePlugin = PluginV2.define({
: {},
)
}),
"aisdk.language": Effect.fn(function* (evt) {
)
yield* ctx.aisdk.hook(
"language",
Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.make("sap-ai-core")) return
evt.language = evt.sdk(evt.model.api.id)
}),
}
)
}),
})

View file

@ -1,5 +1,5 @@
import { Effect } from "effect"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
import { ProviderV2 } from "../../provider"
type FetchLike = (url: string | URL | Request, init?: RequestInit) => Promise<Response>
@ -64,11 +64,12 @@ export function cortexFetch(upstream: FetchLike = fetch) {
}
}
export const SnowflakeCortexPlugin = PluginV2.define({
id: PluginV2.ID.make("snowflake-cortex"),
effect: Effect.gen(function* () {
return {
"aisdk.sdk": Effect.fn(function* (evt) {
export const SnowflakeCortexPlugin = define({
id: "snowflake-cortex",
effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.make("snowflake-cortex")) return
const token =
process.env.SNOWFLAKE_CORTEX_TOKEN ??
@ -84,6 +85,6 @@ export const SnowflakeCortexPlugin = PluginV2.define({
fetch: cortexFetch(upstream) as typeof fetch,
} as any)
}),
}
)
}),
})

View file

@ -1,15 +1,16 @@
import { Effect } from "effect"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
export const TogetherAIPlugin = PluginV2.define({
id: PluginV2.ID.make("togetherai"),
effect: Effect.gen(function* () {
return {
"aisdk.sdk": Effect.fn(function* (evt) {
export const TogetherAIPlugin = define({
id: "togetherai",
effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/togetherai") return
const mod = yield* Effect.promise(() => import("@ai-sdk/togetherai"))
evt.sdk = mod.createTogetherAI(evt.options)
}),
}
)
}),
})

View file

@ -1,15 +1,16 @@
import { Effect } from "effect"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
export const VenicePlugin = PluginV2.define({
id: PluginV2.ID.make("venice"),
effect: Effect.gen(function* () {
return {
"aisdk.sdk": Effect.fn(function* (evt) {
export const VenicePlugin = define({
id: "venice",
effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.package !== "venice-ai-sdk-provider") return
const mod = yield* Effect.promise(() => import("venice-ai-sdk-provider"))
evt.sdk = mod.createVenice(evt.options)
}),
}
)
}),
})

View file

@ -1,11 +1,11 @@
import { Effect } from "effect"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
export const VercelPlugin = PluginV2.define({
id: PluginV2.ID.make("vercel"),
effect: Effect.gen(function* () {
return {
"catalog.transform": Effect.fn(function* (evt) {
export const VercelPlugin = define({
id: "vercel",
effect: Effect.fn(function* (ctx) {
yield* ctx.catalog.transform(
Effect.fn(function* (evt) {
for (const item of evt.provider.list()) {
if (item.provider.api.type !== "aisdk") continue
if (item.provider.api.package !== "@ai-sdk/vercel") continue
@ -15,11 +15,14 @@ export const VercelPlugin = PluginV2.define({
})
}
}),
"aisdk.sdk": Effect.fn(function* (evt) {
)
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/vercel") return
const mod = yield* Effect.promise(() => import("@ai-sdk/vercel"))
evt.sdk = mod.createVercel(evt.options)
}),
}
)
}),
})

View file

@ -1,20 +1,24 @@
import { Effect } from "effect"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
import { ProviderV2 } from "../../provider"
export const XAIPlugin = PluginV2.define({
id: PluginV2.ID.make("xai"),
effect: Effect.gen(function* () {
return {
"aisdk.sdk": Effect.fn(function* (evt) {
export const XAIPlugin = define({
id: "xai",
effect: Effect.fn(function* (ctx) {
yield* ctx.aisdk.hook(
"sdk",
Effect.fn(function* (evt) {
if (evt.package !== "@ai-sdk/xai") return
const mod = yield* Effect.promise(() => import("@ai-sdk/xai"))
evt.sdk = mod.createXai(evt.options)
}),
"aisdk.language": Effect.fn(function* (evt) {
)
yield* ctx.aisdk.hook(
"language",
Effect.fn(function* (evt) {
if (evt.model.providerID !== ProviderV2.ID.make("xai")) return
evt.language = evt.sdk.responses(evt.model.api.id)
}),
}
)
}),
})

View file

@ -1,11 +1,11 @@
import { Effect } from "effect"
import { PluginV2 } from "../../plugin"
import { define } from "@opencode-ai/plugin/v2/effect"
export const ZenmuxPlugin = PluginV2.define({
id: PluginV2.ID.make("zenmux"),
effect: Effect.gen(function* () {
return {
"catalog.transform": Effect.fn(function* (evt) {
export const ZenmuxPlugin = define({
id: "zenmux",
effect: Effect.fn(function* (ctx) {
yield* ctx.catalog.transform(
Effect.fn(function* (evt) {
for (const item of evt.provider.list()) {
if (item.provider.api.type !== "aisdk") continue
if (item.provider.api.package !== "@ai-sdk/openai-compatible") continue
@ -16,6 +16,6 @@ export const ZenmuxPlugin = PluginV2.define({
})
}
}),
}
)
}),
})

View file

@ -2,22 +2,19 @@
export * as SkillPlugin from "./skill"
import { define } from "@opencode-ai/plugin/v2/effect"
import { Effect } from "effect"
import { PluginV2 } from "../plugin"
import { AbsolutePath } from "../schema"
import { SkillV2 } from "../skill"
import customizeOpencodeContent from "./skill/customize-opencode.md" with { type: "text" }
export const CustomizeOpencodeContent = customizeOpencodeContent
export const Plugin = PluginV2.define({
id: PluginV2.ID.make("skill"),
effect: Effect.gen(function* () {
const skill = yield* SkillV2.Service
const transform = yield* skill.transform()
yield* transform((editor) => {
editor.source(
export const Plugin = define({
id: "skill",
effect: Effect.fn(function* (ctx) {
yield* ctx.skill.transform((draft) => {
draft.source(
new SkillV2.EmbeddedSource({
type: "embedded",
skill: new SkillV2.Info({