feat(plugin): add v2 effect host (#33111)
This commit is contained in:
@@ -0,0 +1,317 @@
|
||||
import type { AISDKHooks, PluginHost } from "@opencode-ai/plugin/v2/effect"
|
||||
import { AgentV2 } from "@opencode-ai/core/agent"
|
||||
import { Catalog } from "@opencode-ai/core/catalog"
|
||||
import { Integration } from "@opencode-ai/core/integration"
|
||||
import { ModelV2 } from "@opencode-ai/core/model"
|
||||
import { PluginV2 } from "@opencode-ai/core/plugin"
|
||||
import { ProviderV2 } from "@opencode-ai/core/provider"
|
||||
import type { IntegrationEnvMethod, IntegrationKeyMethod, IntegrationOAuthMethod } from "@opencode-ai/sdk/v2/types"
|
||||
import { Effect, Stream } from "effect"
|
||||
|
||||
export function host(overrides: Partial<PluginHost> = {}): PluginHost {
|
||||
return {
|
||||
aisdk: {
|
||||
hook: () => Effect.die("unused aisdk.hook"),
|
||||
},
|
||||
agent: {
|
||||
get: () => Effect.die("unused agent.get"),
|
||||
default: () => Effect.die("unused agent.default"),
|
||||
list: () => Effect.die("unused agent.list"),
|
||||
rebuild: () => Effect.die("unused agent.rebuild"),
|
||||
transform: () => Effect.die("unused agent.transform"),
|
||||
},
|
||||
catalog: {
|
||||
provider: {
|
||||
get: () => Effect.die("unused catalog.provider.get"),
|
||||
list: () => Effect.die("unused catalog.provider.list"),
|
||||
available: () => Effect.die("unused catalog.provider.available"),
|
||||
},
|
||||
model: {
|
||||
get: () => Effect.die("unused catalog.model.get"),
|
||||
list: () => Effect.die("unused catalog.model.list"),
|
||||
available: () => Effect.die("unused catalog.model.available"),
|
||||
default: () => Effect.die("unused catalog.model.default"),
|
||||
small: () => Effect.die("unused catalog.model.small"),
|
||||
},
|
||||
rebuild: () => Effect.die("unused catalog.rebuild"),
|
||||
transform: () => Effect.die("unused catalog.transform"),
|
||||
},
|
||||
command: {
|
||||
get: () => Effect.die("unused command.get"),
|
||||
list: () => Effect.die("unused command.list"),
|
||||
rebuild: () => Effect.die("unused command.rebuild"),
|
||||
transform: () => Effect.die("unused command.transform"),
|
||||
},
|
||||
event: {
|
||||
subscribe: () => Stream.die("unused event.subscribe"),
|
||||
},
|
||||
filesystem: {
|
||||
read: () => Effect.die("unused filesystem.read"),
|
||||
list: () => Effect.die("unused filesystem.list"),
|
||||
find: () => Effect.die("unused filesystem.find"),
|
||||
glob: () => Effect.die("unused filesystem.glob"),
|
||||
},
|
||||
integration: {
|
||||
get: () => Effect.die("unused integration.get"),
|
||||
list: () => Effect.die("unused integration.list"),
|
||||
rebuild: () => Effect.die("unused integration.rebuild"),
|
||||
transform: () => Effect.die("unused integration.transform"),
|
||||
},
|
||||
location: {
|
||||
directory: "/unused/location",
|
||||
project: { directory: "/unused/project" },
|
||||
},
|
||||
npm: {
|
||||
add: () => Effect.die("unused npm.add"),
|
||||
},
|
||||
path: {
|
||||
home: "/unused/home",
|
||||
data: "/unused/data",
|
||||
cache: "/unused/cache",
|
||||
config: "/unused/config",
|
||||
state: "/unused/state",
|
||||
temp: "/unused/temp",
|
||||
},
|
||||
reference: {
|
||||
list: () => Effect.die("unused reference.list"),
|
||||
rebuild: () => Effect.die("unused reference.rebuild"),
|
||||
transform: () => Effect.die("unused reference.transform"),
|
||||
},
|
||||
skill: {
|
||||
sources: () => Effect.die("unused skill.sources"),
|
||||
list: () => Effect.die("unused skill.list"),
|
||||
rebuild: () => Effect.die("unused skill.rebuild"),
|
||||
transform: () => Effect.die("unused skill.transform"),
|
||||
},
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
export function aisdkHost(plugin: PluginV2.Interface): PluginHost["aisdk"] {
|
||||
return {
|
||||
hook: (name, callback) => {
|
||||
if (name === "sdk") {
|
||||
const run = callback as AISDKHooks["sdk"]
|
||||
return plugin.hook("aisdk.sdk", (event) => {
|
||||
const output = { ...event }
|
||||
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 AISDKHooks["language"]
|
||||
return plugin.hook("aisdk.language", (event) => {
|
||||
const output = { ...event }
|
||||
const result = run(output)
|
||||
return Effect.suspend(() => (Effect.isEffect(result) ? result : Effect.void)).pipe(
|
||||
Effect.tap(() => Effect.sync(() => (event.language = output.language))),
|
||||
)
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function agentHost(agent: AgentV2.Interface): PluginHost["agent"] {
|
||||
return {
|
||||
...host().agent,
|
||||
transform: (callback) =>
|
||||
agent.transform((draft) =>
|
||||
callback({
|
||||
list: () => draft.list().map(agentInfo),
|
||||
get: (id) => {
|
||||
const value = draft.get(AgentV2.ID.make(id))
|
||||
return value && agentInfo(value)
|
||||
},
|
||||
default: (id) => draft.default(id === undefined ? undefined : AgentV2.ID.make(id)),
|
||||
update: (id, update) =>
|
||||
draft.update(AgentV2.ID.make(id), (value) => {
|
||||
const current = agentInfo(value)
|
||||
update(current)
|
||||
Object.assign(value, current, { id: AgentV2.ID.make(current.id) })
|
||||
}),
|
||||
remove: (id) => draft.remove(AgentV2.ID.make(id)),
|
||||
}),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
export function catalogHost(catalog: Catalog.Interface): PluginHost["catalog"] {
|
||||
return {
|
||||
...host().catalog,
|
||||
rebuild: catalog.rebuild,
|
||||
transform: (callback) =>
|
||||
catalog.transform((draft) =>
|
||||
callback({
|
||||
provider: {
|
||||
list: () =>
|
||||
draft.provider.list().map((value) => ({
|
||||
provider: providerInfo(value.provider),
|
||||
models: new Map(Array.from(value.models, ([id, model]) => [id, modelInfo(model)])),
|
||||
})),
|
||||
get: (id) => {
|
||||
const value = draft.provider.get(ProviderV2.ID.make(id))
|
||||
return (
|
||||
value && {
|
||||
provider: providerInfo(value.provider),
|
||||
models: new Map(Array.from(value.models, ([id, model]) => [id, modelInfo(model)])),
|
||||
}
|
||||
)
|
||||
},
|
||||
update: (id, update) =>
|
||||
draft.provider.update(ProviderV2.ID.make(id), (value) => {
|
||||
const current = providerInfo(value)
|
||||
update(current)
|
||||
Object.assign(value, current, { id: ProviderV2.ID.make(current.id) })
|
||||
}),
|
||||
remove: (id) => draft.provider.remove(ProviderV2.ID.make(id)),
|
||||
},
|
||||
model: {
|
||||
get: (providerID, modelID) => {
|
||||
const value = draft.model.get(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID))
|
||||
return value && modelInfo(value)
|
||||
},
|
||||
update: (providerID, modelID, update) =>
|
||||
draft.model.update(ProviderV2.ID.make(providerID), ModelV2.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),
|
||||
variants: current.variants.map((variant) => ({
|
||||
...variant,
|
||||
id: ModelV2.VariantID.make(variant.id),
|
||||
})),
|
||||
})
|
||||
}),
|
||||
remove: (providerID, modelID) =>
|
||||
draft.model.remove(ProviderV2.ID.make(providerID), ModelV2.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)),
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
export function integrationHost(integration: Integration.Interface): PluginHost["integration"] {
|
||||
const info = (value: Integration.Info) => ({
|
||||
id: value.id,
|
||||
name: value.name,
|
||||
methods: value.methods.map(method),
|
||||
connections: value.connections.map((item) => ({ ...item })),
|
||||
})
|
||||
return {
|
||||
get: (id) => integration.get(Integration.ID.make(id)).pipe(Effect.map((value) => value && info(value))),
|
||||
list: () => integration.list().pipe(Effect.map((items) => items.map(info))),
|
||||
rebuild: integration.rebuild,
|
||||
transform: (callback) =>
|
||||
integration.transform((draft) =>
|
||||
callback({
|
||||
list: () => draft.list().map((value) => ({ id: value.id, name: value.name })),
|
||||
get: (id) => {
|
||||
const value = draft.get(Integration.ID.make(id))
|
||||
return value && { id: value.id, name: value.name }
|
||||
},
|
||||
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)).map(method),
|
||||
update: (input) =>
|
||||
input.method.type === "env"
|
||||
? draft.method.update({
|
||||
integrationID: Integration.ID.make(input.integrationID),
|
||||
method: { ...input.method, names: [...input.method.names] },
|
||||
})
|
||||
: draft.method.update({
|
||||
integrationID: Integration.ID.make(input.integrationID),
|
||||
method: input.method,
|
||||
}),
|
||||
remove: (id, item) => draft.method.remove(Integration.ID.make(id), internalMethod(item)),
|
||||
},
|
||||
}),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
function method(value: Integration.Method) {
|
||||
if (value.type === "env") return { type: value.type, names: [...value.names] }
|
||||
if (value.type === "key") return { type: value.type, label: value.label }
|
||||
return {
|
||||
type: value.type,
|
||||
id: value.id,
|
||||
label: value.label,
|
||||
prompts: value.prompts?.map((prompt) => {
|
||||
if (prompt.type === "text") return { ...prompt }
|
||||
return { ...prompt, options: prompt.options.map((option) => ({ ...option })) }
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
function internalMethod(value: IntegrationOAuthMethod | IntegrationKeyMethod | IntegrationEnvMethod): Integration.Method {
|
||||
if (value.type === "env") return value
|
||||
if (value.type === "key") return value
|
||||
return {
|
||||
...value,
|
||||
id: Integration.MethodID.make(value.id),
|
||||
}
|
||||
}
|
||||
|
||||
function agentInfo(value: AgentV2.Info) {
|
||||
return {
|
||||
...value,
|
||||
model: value.model && { ...value.model },
|
||||
request: { headers: { ...value.request.headers }, body: { ...value.request.body } },
|
||||
permissions: value.permissions.map((permission) => ({ ...permission })),
|
||||
}
|
||||
}
|
||||
|
||||
function providerInfo(value: ProviderV2.MutableInfo) {
|
||||
return {
|
||||
...value,
|
||||
api: { ...value.api, settings: value.api.settings && { ...value.api.settings } },
|
||||
request: { headers: { ...value.request.headers }, body: { ...value.request.body } },
|
||||
}
|
||||
}
|
||||
|
||||
function modelInfo(value: ModelV2.Info | ModelV2.MutableInfo) {
|
||||
return {
|
||||
...value,
|
||||
api: { ...value.api, settings: value.api.settings && { ...value.api.settings } },
|
||||
capabilities: {
|
||||
...value.capabilities,
|
||||
input: [...value.capabilities.input],
|
||||
output: [...value.capabilities.output],
|
||||
},
|
||||
request: {
|
||||
...value.request,
|
||||
headers: { ...value.request.headers },
|
||||
body: { ...value.request.body },
|
||||
generation: value.request.generation && {
|
||||
...value.request.generation,
|
||||
stop: value.request.generation.stop && [...value.request.generation.stop],
|
||||
},
|
||||
options: value.request.options && { ...value.request.options },
|
||||
},
|
||||
variants: value.variants.map((variant) => ({
|
||||
...variant,
|
||||
headers: { ...variant.headers },
|
||||
body: { ...variant.body },
|
||||
generation: variant.generation && {
|
||||
...variant.generation,
|
||||
stop: variant.generation.stop && [...variant.generation.stop],
|
||||
},
|
||||
options: variant.options && { ...variant.options },
|
||||
})),
|
||||
time: { ...value.time },
|
||||
cost: value.cost.map((cost) => ({ ...cost, tier: cost.tier && { ...cost.tier }, cache: { ...cost.cache } })),
|
||||
limit: { ...value.limit },
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user