320 lines
12 KiB
TypeScript
320 lines
12 KiB
TypeScript
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 },
|
|
}
|
|
}
|