refactor(core): consolidate tool architecture
This commit is contained in:
parent
0fd73a2976
commit
8db7487c89
466 changed files with 9405 additions and 11071 deletions
|
|
@ -1,12 +1,12 @@
|
|||
export * as AgentPlugin from "./agent"
|
||||
|
||||
import path from "path"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
import { Effect } from "effect"
|
||||
import { AgentV2 } from "../agent"
|
||||
import { Agent } from "../agent"
|
||||
import { Global } from "@opencode-ai/util/global"
|
||||
import { Location } from "../location"
|
||||
import { PermissionV2 } from "../permission"
|
||||
import { Permission } from "../permission"
|
||||
|
||||
// Combined output files written by the Shell service, e.g. `<data>/shell/<projectID>/<shellID>.out`.
|
||||
// Whitelisted so agents can read a command's full captured output without an external-directory prompt.
|
||||
|
|
@ -105,13 +105,13 @@ export const Plugin = define({
|
|||
const location = yield* Location.Service
|
||||
const worktree = location.directory
|
||||
const whitelistedDirs = [SHELL_OUTPUT_GLOB, path.join(Global.Path.tmp, "*")]
|
||||
const readonlyExternalDirectory: PermissionV2.Ruleset = [
|
||||
const readonlyExternalDirectory: Permission.Ruleset = [
|
||||
{ action: "external_directory", resource: "*", effect: "ask" },
|
||||
...whitelistedDirs.map(
|
||||
(resource): PermissionV2.Rule => ({ action: "external_directory", resource, effect: "allow" }),
|
||||
(resource): Permission.Rule => ({ action: "external_directory", resource, effect: "allow" }),
|
||||
),
|
||||
]
|
||||
const defaults: PermissionV2.Ruleset = [
|
||||
const defaults: Permission.Ruleset = [
|
||||
{ action: "*", resource: "*", effect: "allow" },
|
||||
...readonlyExternalDirectory,
|
||||
{ action: "question", resource: "*", effect: "deny" },
|
||||
|
|
@ -124,24 +124,24 @@ export const Plugin = define({
|
|||
]
|
||||
|
||||
yield* ctx.agent.transform((draft) => {
|
||||
draft.update(AgentV2.defaultID, (item) => {
|
||||
item.name = AgentV2.Name.make("Build")
|
||||
draft.update(Agent.defaultID, (item) => {
|
||||
item.name = Agent.Name.make("Build")
|
||||
item.description = "The default agent. Executes tools based on configured permissions."
|
||||
item.mode = "primary"
|
||||
item.permissions.push(
|
||||
...PermissionV2.merge(defaults, [
|
||||
...Permission.merge(defaults, [
|
||||
{ action: "question", resource: "*", effect: "allow" },
|
||||
{ action: "plan_enter", resource: "*", effect: "allow" },
|
||||
]),
|
||||
)
|
||||
})
|
||||
|
||||
draft.update(AgentV2.ID.make("plan"), (item) => {
|
||||
item.name = AgentV2.Name.make("Plan")
|
||||
draft.update(Agent.ID.make("plan"), (item) => {
|
||||
item.name = Agent.Name.make("Plan")
|
||||
item.description = "Plan mode. Disallows all edit tools."
|
||||
item.mode = "primary"
|
||||
item.permissions.push(
|
||||
...PermissionV2.merge(defaults, [
|
||||
...Permission.merge(defaults, [
|
||||
{ action: "question", resource: "*", effect: "allow" },
|
||||
{ action: "plan_exit", resource: "*", effect: "allow" },
|
||||
{ action: "external_directory", resource: path.join(Global.Path.data, "plans", "*"), effect: "allow" },
|
||||
|
|
@ -156,22 +156,22 @@ export const Plugin = define({
|
|||
)
|
||||
})
|
||||
|
||||
draft.update(AgentV2.ID.make("general"), (item) => {
|
||||
item.name = AgentV2.Name.make("General")
|
||||
draft.update(Agent.ID.make("general"), (item) => {
|
||||
item.name = Agent.Name.make("General")
|
||||
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: "subagent", resource: "*", effect: "deny" }]))
|
||||
item.permissions.push(...Permission.merge(defaults, [{ action: "subagent", resource: "*", effect: "deny" }]))
|
||||
})
|
||||
|
||||
draft.update(AgentV2.ID.make("explore"), (item) => {
|
||||
item.name = AgentV2.Name.make("Explore")
|
||||
draft.update(Agent.ID.make("explore"), (item) => {
|
||||
item.name = Agent.Name.make("Explore")
|
||||
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
|
||||
item.mode = "subagent"
|
||||
item.permissions.push(
|
||||
...PermissionV2.merge(
|
||||
...Permission.merge(
|
||||
defaults,
|
||||
[
|
||||
{ action: "*", resource: "*", effect: "deny" },
|
||||
|
|
@ -187,28 +187,28 @@ export const Plugin = define({
|
|||
)
|
||||
})
|
||||
|
||||
draft.update(AgentV2.ID.make("compaction"), (item) => {
|
||||
item.name = AgentV2.Name.make("Compaction")
|
||||
draft.update(Agent.ID.make("compaction"), (item) => {
|
||||
item.name = Agent.Name.make("Compaction")
|
||||
item.mode = "primary"
|
||||
item.hidden = true
|
||||
item.system = PROMPT_COMPACTION
|
||||
item.permissions.push(...PermissionV2.merge(defaults, [{ action: "*", resource: "*", effect: "deny" }]))
|
||||
item.permissions.push(...Permission.merge(defaults, [{ action: "*", resource: "*", effect: "deny" }]))
|
||||
})
|
||||
|
||||
draft.update(AgentV2.ID.make("title"), (item) => {
|
||||
item.name = AgentV2.Name.make("Title")
|
||||
draft.update(Agent.ID.make("title"), (item) => {
|
||||
item.name = Agent.Name.make("Title")
|
||||
item.mode = "primary"
|
||||
item.hidden = true
|
||||
item.system = PROMPT_TITLE
|
||||
item.permissions.push(...PermissionV2.merge(defaults, [{ action: "*", resource: "*", effect: "deny" }]))
|
||||
item.permissions.push(...Permission.merge(defaults, [{ action: "*", resource: "*", effect: "deny" }]))
|
||||
})
|
||||
|
||||
draft.update(AgentV2.ID.make("summary"), (item) => {
|
||||
item.name = AgentV2.Name.make("Summary")
|
||||
draft.update(Agent.ID.make("summary"), (item) => {
|
||||
item.name = Agent.Name.make("Summary")
|
||||
item.mode = "primary"
|
||||
item.hidden = true
|
||||
item.system = PROMPT_SUMMARY
|
||||
item.permissions.push(...PermissionV2.merge(defaults, [{ action: "*", resource: "*", effect: "deny" }]))
|
||||
item.permissions.push(...Permission.merge(defaults, [{ action: "*", resource: "*", effect: "deny" }]))
|
||||
})
|
||||
})
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
export * as CommandPlugin from "./command"
|
||||
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
import { Effect } from "effect"
|
||||
import { Location } from "../location"
|
||||
import PROMPT_INITIALIZE from "./command/initialize.txt"
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
export * as PluginHooks from "./hooks"
|
||||
|
||||
import type { AISDKHooks } from "@opencode-ai/plugin/v2/effect/aisdk"
|
||||
import type { SessionHooks } from "@opencode-ai/plugin/v2/effect/session"
|
||||
import type { ToolHooks } from "@opencode-ai/plugin/v2/effect/tool"
|
||||
import type { AISDKHooks } from "@opencode-ai/plugin/effect/aisdk"
|
||||
import type { SessionHooks } from "@opencode-ai/plugin/effect/session"
|
||||
import type { ToolHooks } from "@opencode-ai/plugin/effect/tool"
|
||||
import { Context, Effect, Layer, Scope } from "effect"
|
||||
import { makeLocationNode } from "@opencode-ai/util/effect/app-node"
|
||||
import { State } from "../state"
|
||||
|
|
@ -28,7 +28,7 @@ export interface Interface {
|
|||
) => Effect.Effect<Domains[Domain][Name]>
|
||||
}
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/PluginHooks") {}
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/PluginHooks") {}
|
||||
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
|
|
|
|||
|
|
@ -1,48 +1,44 @@
|
|||
export * as PluginHost from "./host"
|
||||
|
||||
import { Plugin } from "@opencode-ai/plugin/v2/effect"
|
||||
import type { IntegrationMethodRegistration } from "@opencode-ai/plugin/v2/effect/integration"
|
||||
import { Plugin } from "@opencode-ai/plugin/effect"
|
||||
import type { IntegrationMethodRegistration } from "@opencode-ai/plugin/effect/integration"
|
||||
import type { CredentialOAuth } from "@opencode-ai/sdk/v2/types"
|
||||
import { EventManifest } from "@opencode-ai/schema/event-manifest"
|
||||
import { App } from "../app"
|
||||
import { Effect, Schema, Stream } from "effect"
|
||||
import { AgentV2 } from "../agent"
|
||||
import { Agent } from "../agent"
|
||||
import { AISDK } from "../aisdk"
|
||||
import { Catalog } from "../catalog"
|
||||
import { CommandV2 } from "../command"
|
||||
import { Command } from "../command"
|
||||
import { Credential } from "../credential"
|
||||
import { EventV2 } from "../event"
|
||||
import { Bus } from "../bus"
|
||||
import { Integration } from "../integration"
|
||||
import { Location } from "../location"
|
||||
import { ModelV2 } from "../model"
|
||||
import type { PluginV2 } from "../plugin"
|
||||
import { Model } from "../model"
|
||||
import { PluginRuntime } from "./runtime"
|
||||
import { ProviderV2 } from "../provider"
|
||||
import { Provider } from "../provider"
|
||||
import { Reference } from "../reference"
|
||||
import { AbsolutePath, type DeepMutable } from "../schema"
|
||||
import { SkillV2 } from "../skill"
|
||||
import { Tool } from "../tool/tool"
|
||||
import { Tools } from "../tool/tools"
|
||||
import { ToolHooks } from "../tool/hooks"
|
||||
import { WorkspaceV2 } from "../workspace"
|
||||
import { Skill } from "../skill"
|
||||
import { Tool } from "../tool"
|
||||
import { Workspace } from "../workspace"
|
||||
import { WebSearch } from "../websearch"
|
||||
import { PluginHooks } from "./hooks"
|
||||
|
||||
const mutable = <T>(value: T) => value as DeepMutable<T>
|
||||
export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Interface) {
|
||||
export const make = Effect.fn("PluginHost.make")(function* (plugin: import("../plugin").Interface) {
|
||||
const app = yield* App.Metadata
|
||||
const agents = yield* AgentV2.Service
|
||||
const agents = yield* Agent.Service
|
||||
const aisdk = yield* AISDK.Service
|
||||
const catalog = yield* Catalog.Service
|
||||
const commands = yield* CommandV2.Service
|
||||
const events = yield* EventV2.Service
|
||||
const commands = yield* Command.Service
|
||||
const bus = yield* Bus.Service
|
||||
const integration = yield* Integration.Service
|
||||
const location = yield* Location.Service
|
||||
const reference = yield* Reference.Service
|
||||
const skill = yield* SkillV2.Service
|
||||
const tools = yield* Tools.Service
|
||||
const skill = yield* Skill.Service
|
||||
const tools = yield* Tool.Service
|
||||
const websearch = yield* WebSearch.Service
|
||||
const toolHooks = yield* ToolHooks.Service
|
||||
const hooks = yield* PluginHooks.Service
|
||||
const runtime = yield* PluginRuntime.Service
|
||||
const locationInfo = () =>
|
||||
|
|
@ -51,7 +47,9 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
|
|||
workspaceID: location.workspaceID,
|
||||
project: location.project,
|
||||
})
|
||||
const locationRef = (input?: Parameters<Plugin.Context["agent"]["list"]>[0]) =>
|
||||
const locationRef = (input?: {
|
||||
readonly location?: { readonly directory?: string; readonly workspace?: string }
|
||||
}) =>
|
||||
input?.location === undefined
|
||||
? undefined
|
||||
: Location.Ref.make({
|
||||
|
|
@ -59,7 +57,7 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
|
|||
workspaceID:
|
||||
input.location.workspace === undefined
|
||||
? location.workspaceID
|
||||
: WorkspaceV2.ID.make(input.location.workspace),
|
||||
: Workspace.ID.make(input.location.workspace),
|
||||
})
|
||||
const isCurrentLocation = (ref: Location.Ref) =>
|
||||
ref.directory === location.directory && ref.workspaceID === location.workspaceID
|
||||
|
|
@ -70,7 +68,22 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
|
|||
app,
|
||||
options: {},
|
||||
agent: {
|
||||
get: (id) => agents.get(AgentV2.ID.make(id)),
|
||||
get: (input) => {
|
||||
const ref = locationRef(input)
|
||||
const output =
|
||||
ref && !isCurrentLocation(ref)
|
||||
? runtime.location.agent
|
||||
.list(ref)
|
||||
.pipe(Effect.map((result) => ({ ...result, data: result.data.find((agent) => agent.id === input.agentID) })))
|
||||
: response(agents.get(input.agentID))
|
||||
return output.pipe(
|
||||
Effect.flatMap((result) =>
|
||||
result.data
|
||||
? Effect.succeed({ ...result, data: result.data })
|
||||
: Effect.fail(new Error(`Agent not found: ${input.agentID}`)),
|
||||
),
|
||||
)
|
||||
},
|
||||
list: (input) => {
|
||||
const ref = locationRef(input)
|
||||
if (ref && !isCurrentLocation(ref)) return runtime.location.agent.list(ref)
|
||||
|
|
@ -81,10 +94,10 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
|
|||
agents.transform((draft) => {
|
||||
callback({
|
||||
list: () => mutable(draft.list()),
|
||||
get: (id) => mutable(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)),
|
||||
get: (id) => mutable(draft.get(Agent.ID.make(id))),
|
||||
default: (id) => draft.default(id === undefined ? undefined : Agent.ID.make(id)),
|
||||
update: (id, update) => draft.update(Agent.ID.make(id), update),
|
||||
remove: (id) => draft.remove(Agent.ID.make(id)),
|
||||
})
|
||||
}),
|
||||
},
|
||||
|
|
@ -121,7 +134,7 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
|
|||
list: () => response(catalog.provider.available()),
|
||||
get: (input) =>
|
||||
catalog.provider
|
||||
.get(ProviderV2.ID.make(input.providerID))
|
||||
.get(Provider.ID.make(input.providerID))
|
||||
.pipe(
|
||||
Effect.flatMap((provider) =>
|
||||
provider === undefined
|
||||
|
|
@ -131,7 +144,6 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
|
|||
),
|
||||
},
|
||||
model: {
|
||||
get: (providerID, modelID) => catalog.model.get(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID)),
|
||||
list: () => response(catalog.model.available()),
|
||||
default: () => response(catalog.model.default()),
|
||||
},
|
||||
|
|
@ -141,21 +153,21 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
|
|||
callback({
|
||||
provider: {
|
||||
list: () => mutable(draft.provider.list()),
|
||||
get: (id) => mutable(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)),
|
||||
get: (id) => mutable(draft.provider.get(Provider.ID.make(id))),
|
||||
update: (id, update) => draft.provider.update(Provider.ID.make(id), update),
|
||||
remove: (id) => draft.provider.remove(Provider.ID.make(id)),
|
||||
},
|
||||
model: {
|
||||
get: (providerID, modelID) =>
|
||||
mutable(draft.model.get(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID))),
|
||||
mutable(draft.model.get(Provider.ID.make(providerID), Model.ID.make(modelID))),
|
||||
update: (providerID, modelID, update) =>
|
||||
draft.model.update(ProviderV2.ID.make(providerID), ModelV2.ID.make(modelID), update),
|
||||
draft.model.update(Provider.ID.make(providerID), Model.ID.make(modelID), update),
|
||||
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: draft.model.default.get,
|
||||
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)),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
@ -170,7 +182,7 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
|
|||
}),
|
||||
},
|
||||
event: {
|
||||
subscribe: () => events.subscribe().pipe(Stream.filter(EventManifest.isServer)),
|
||||
subscribe: () => bus.subscribe().pipe(Stream.filter(EventManifest.isServer)),
|
||||
},
|
||||
integration: {
|
||||
list: () => response(integration.list()),
|
||||
|
|
@ -279,88 +291,21 @@ export const make = Effect.fn("PluginHost.make")(function* (plugin: PluginV2.Int
|
|||
transform: (callback) =>
|
||||
skill.transform((draft) => {
|
||||
callback({
|
||||
source: (source) => draft.source(Schema.decodeUnknownSync(SkillV2.Source)(source)),
|
||||
source: (source) => draft.source(Schema.decodeUnknownSync(Skill.Source)(source)),
|
||||
list: draft.list,
|
||||
})
|
||||
}),
|
||||
},
|
||||
tool: {
|
||||
transform: (callback) =>
|
||||
Effect.gen(function* () {
|
||||
const registrations: Array<{
|
||||
readonly name: string
|
||||
readonly tool: Tool.Any
|
||||
readonly options?: Tool.RegisterOptions
|
||||
}> = []
|
||||
yield* Effect.sync(() =>
|
||||
tools
|
||||
.transform((draft) =>
|
||||
callback({
|
||||
add: (name, tool, options) => {
|
||||
registrations.push({ name, tool, ...(options ? { options } : {}) })
|
||||
},
|
||||
add: (tool) => draft.add(tool),
|
||||
}),
|
||||
)
|
||||
yield* tools
|
||||
.registerBatch(
|
||||
registrations.map((registration) => ({
|
||||
tools: { [registration.name]: registration.tool },
|
||||
...(registration.options === undefined ? {} : { options: registration.options }),
|
||||
})),
|
||||
)
|
||||
.pipe(Effect.orDie)
|
||||
return { dispose: Effect.void }
|
||||
}),
|
||||
hook: (name, callback) => {
|
||||
if (name === "execute.before") {
|
||||
return toolHooks.hook.before((event) => {
|
||||
const output = {
|
||||
tool: event.tool,
|
||||
sessionID: event.sessionID,
|
||||
agent: event.agent,
|
||||
messageID: event.messageID,
|
||||
callID: event.callID,
|
||||
input: event.input,
|
||||
}
|
||||
return Reflect.apply(callback, undefined, [output]).pipe(
|
||||
Effect.tap(() => Effect.sync(() => (event.input = output.input))),
|
||||
)
|
||||
})
|
||||
}
|
||||
return toolHooks.hook.after((event) => {
|
||||
// Decode first so plugin mutations cannot alias the canonical outcome.
|
||||
const output = {
|
||||
tool: event.tool,
|
||||
sessionID: event.sessionID,
|
||||
agent: event.agent,
|
||||
messageID: event.messageID,
|
||||
callID: event.callID,
|
||||
input: event.input,
|
||||
...Schema.decodeUnknownSync(Tool.ExecuteAfterOutcome)(event),
|
||||
}
|
||||
return Reflect.apply(callback, undefined, [output]).pipe(
|
||||
Effect.tap(() => {
|
||||
const decoded = Schema.decodeUnknownOption(Tool.ExecuteAfterOutcome)(output)
|
||||
if (decoded._tag === "None")
|
||||
return Effect.logWarning("ignoring invalid execute.after tool outcome", { tool: event.tool })
|
||||
if (decoded.value.status !== event.status)
|
||||
return Effect.logWarning("ignoring execute.after tool status change", { tool: event.tool })
|
||||
return Effect.sync(() => {
|
||||
if (event.status === "completed" && decoded.value.status === "completed") {
|
||||
event.content = decoded.value.content
|
||||
event.metadata = decoded.value.metadata
|
||||
event.outputPaths = decoded.value.outputPaths
|
||||
return
|
||||
}
|
||||
if (event.status === "error" && decoded.value.status === "error") {
|
||||
event.error = decoded.value.error
|
||||
event.content = decoded.value.content
|
||||
event.metadata = decoded.value.metadata
|
||||
event.outputPaths = decoded.value.outputPaths
|
||||
}
|
||||
})
|
||||
}),
|
||||
)
|
||||
})
|
||||
},
|
||||
.pipe(Effect.orDie, Effect.as({ dispose: Effect.void })),
|
||||
hook: (name, callback) => hooks.register("tool", name, callback),
|
||||
},
|
||||
websearch: {
|
||||
providers: () => response(websearch.providers()),
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
export * as PluginInternal from "./internal"
|
||||
|
||||
import type { Plugin } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import type { Plugin } from "@opencode-ai/plugin/effect/plugin"
|
||||
import { Context, Effect, Scope } from "effect"
|
||||
import { HttpClient } from "effect/unstable/http"
|
||||
import { AgentV2 } from "../agent"
|
||||
import { Agent } from "../agent"
|
||||
import { Catalog } from "../catalog"
|
||||
import { CommandV2 } from "../command"
|
||||
import { Command } from "../command"
|
||||
import { Config } from "../config"
|
||||
import { ConfigAgentPlugin } from "../config/plugin/agent"
|
||||
import { ConfigCommandPlugin } from "../config/plugin/command"
|
||||
|
|
@ -14,7 +14,7 @@ import { ConfigPolicyPlugin } from "../config/plugin/policy"
|
|||
import { ConfigReferencePlugin } from "../config/plugin/reference"
|
||||
import { ConfigSkillPlugin } from "../config/plugin/skill"
|
||||
import { ConfigWebSearchPlugin } from "../config/plugin/websearch"
|
||||
import { EventV2 } from "../event"
|
||||
import { Bus } from "../bus"
|
||||
import { FileMutation } from "../file-mutation"
|
||||
import { Form } from "../form"
|
||||
import { FileSystem } from "../filesystem"
|
||||
|
|
@ -27,28 +27,28 @@ import { Location } from "../location"
|
|||
import { LocationMutation } from "../location-mutation"
|
||||
import { ModelsDev } from "../models-dev"
|
||||
import { Npm } from "@opencode-ai/util/npm"
|
||||
import { PermissionV2 } from "../permission"
|
||||
import { Permission } from "../permission"
|
||||
import { Reference } from "../reference"
|
||||
import { WebSearch } from "../websearch"
|
||||
import { Ripgrep } from "../ripgrep"
|
||||
import { SessionInstructions } from "../session/instructions"
|
||||
import { Shell } from "../shell"
|
||||
import { SkillV2 } from "../skill"
|
||||
import { PatchTool } from "../tool/patch"
|
||||
import { EditTool } from "../tool/edit"
|
||||
import { GlobTool } from "../tool/glob"
|
||||
import { GrepTool } from "../tool/grep"
|
||||
import { QuestionTool } from "../tool/question"
|
||||
import { Skill } from "../skill"
|
||||
import { PatchTool } from "../tool/plugin/patch"
|
||||
import { EditTool } from "../tool/plugin/edit"
|
||||
import { GlobTool } from "../tool/plugin/glob"
|
||||
import { GrepTool } from "../tool/plugin/grep"
|
||||
import { QuestionTool } from "../tool/plugin/question"
|
||||
import { ReadToolFileSystem } from "../tool/read-filesystem"
|
||||
import { ReadTool } from "../tool/read"
|
||||
import { ShellTool } from "../tool/shell"
|
||||
import { SkillTool } from "../tool/skill"
|
||||
import { SubagentTool } from "../tool/subagent"
|
||||
import { Tools } from "../tool/tools"
|
||||
import { WebFetchTool } from "../tool/webfetch"
|
||||
import { WebSearchTool } from "../tool/websearch"
|
||||
import { ReadTool } from "../tool/plugin/read"
|
||||
import { ShellTool } from "../tool/plugin/shell"
|
||||
import { SkillTool } from "../tool/plugin/skill"
|
||||
import { SubagentTool } from "../tool/plugin/subagent"
|
||||
import { Tool } from "../tool"
|
||||
import { WebFetchTool } from "../tool/plugin/webfetch"
|
||||
import { WebSearchTool } from "../tool/plugin/websearch"
|
||||
import { WellKnown } from "../wellknown"
|
||||
import { WriteTool } from "../tool/write"
|
||||
import { WriteTool } from "../tool/plugin/write"
|
||||
import { AgentPlugin } from "./agent"
|
||||
import { CommandPlugin } from "./command"
|
||||
import { ModelsDevPlugin } from "./models-dev"
|
||||
|
|
@ -62,11 +62,11 @@ import { WarmingPlugin } from "./warming"
|
|||
import { WellKnownPlugin } from "../wellknown/plugin"
|
||||
|
||||
const services = Effect.fn("PluginInternal.services")(function* () {
|
||||
const agent = yield* AgentV2.Service
|
||||
const agent = yield* Agent.Service
|
||||
const catalog = yield* Catalog.Service
|
||||
const command = yield* CommandV2.Service
|
||||
const command = yield* Command.Service
|
||||
const config = yield* Config.Service
|
||||
const events = yield* EventV2.Service
|
||||
const bus = yield* Bus.Service
|
||||
const mutation = yield* FileMutation.Service
|
||||
const filesystem = yield* FileSystem.Service
|
||||
const fs = yield* FSUtil.Service
|
||||
|
|
@ -79,7 +79,7 @@ const services = Effect.fn("PluginInternal.services")(function* () {
|
|||
const locationMutation = yield* LocationMutation.Service
|
||||
const models = yield* ModelsDev.Service
|
||||
const npm = yield* Npm.Service
|
||||
const permission = yield* PermissionV2.Service
|
||||
const permission = yield* Permission.Service
|
||||
const runtime = yield* PluginRuntime.Service
|
||||
const form = yield* Form.Service
|
||||
const read = yield* ReadToolFileSystem.Service
|
||||
|
|
@ -88,15 +88,15 @@ const services = Effect.fn("PluginInternal.services")(function* () {
|
|||
const ripgrep = yield* Ripgrep.Service
|
||||
const instructions = yield* SessionInstructions.Service
|
||||
const shell = yield* Shell.Service
|
||||
const skill = yield* SkillV2.Service
|
||||
const tools = yield* Tools.Service
|
||||
const skill = yield* Skill.Service
|
||||
const tools = yield* Tool.Service
|
||||
const wellknown = yield* WellKnown.Service
|
||||
return Context.mergeAll(
|
||||
Context.make(AgentV2.Service, agent),
|
||||
Context.make(Agent.Service, agent),
|
||||
Context.make(Catalog.Service, catalog),
|
||||
Context.make(CommandV2.Service, command),
|
||||
Context.make(Command.Service, command),
|
||||
Context.make(Config.Service, config),
|
||||
Context.make(EventV2.Service, events),
|
||||
Context.make(Bus.Service, bus),
|
||||
Context.make(FileMutation.Service, mutation),
|
||||
Context.make(FileSystem.Service, filesystem),
|
||||
Context.make(FSUtil.Service, fs),
|
||||
|
|
@ -109,7 +109,7 @@ const services = Effect.fn("PluginInternal.services")(function* () {
|
|||
Context.make(LocationMutation.Service, locationMutation),
|
||||
Context.make(ModelsDev.Service, models),
|
||||
Context.make(Npm.Service, npm),
|
||||
Context.make(PermissionV2.Service, permission),
|
||||
Context.make(Permission.Service, permission),
|
||||
Context.make(PluginRuntime.Service, runtime),
|
||||
Context.make(Form.Service, form),
|
||||
Context.make(ReadToolFileSystem.Service, read),
|
||||
|
|
@ -118,8 +118,8 @@ const services = Effect.fn("PluginInternal.services")(function* () {
|
|||
Context.make(Ripgrep.Service, ripgrep),
|
||||
Context.make(SessionInstructions.Service, instructions),
|
||||
Context.make(Shell.Service, shell),
|
||||
Context.make(SkillV2.Service, skill),
|
||||
Context.make(Tools.Service, tools),
|
||||
Context.make(Skill.Service, skill),
|
||||
Context.make(Tool.Service, tools),
|
||||
Context.make(WellKnown.Service, wellknown),
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
import { Integration } from "@opencode-ai/schema/integration"
|
||||
import { Effect, Stream } from "effect"
|
||||
import { EventV2 } from "../event"
|
||||
import { Bus } from "../bus"
|
||||
import { ModelsDev } from "../models-dev"
|
||||
|
||||
export const ModelsDevPlugin = define({
|
||||
id: "opencode.models-dev",
|
||||
effect: Effect.fn(function* (ctx) {
|
||||
const modelsDev = yield* ModelsDev.Service
|
||||
const events = yield* EventV2.Service
|
||||
const bus = yield* Bus.Service
|
||||
const loaded = { data: structuredClone(yield* modelsDev.get()) }
|
||||
yield* ctx.integration.transform((integrations) => {
|
||||
for (const provider of loaded.data) {
|
||||
|
|
@ -28,14 +29,14 @@ export const ModelsDevPlugin = define({
|
|||
for (const provider of loaded.data) {
|
||||
catalog.provider.update(provider.info.id, (draft) => {
|
||||
Object.assign(draft, provider.info)
|
||||
draft.integrationID = provider.info.id
|
||||
draft.integrationID = Integration.ID.make(provider.info.id)
|
||||
})
|
||||
for (const model of provider.models) {
|
||||
catalog.model.update(provider.info.id, model.id, (draft) => Object.assign(draft, model))
|
||||
}
|
||||
}
|
||||
})
|
||||
yield* events.subscribe(ModelsDev.Event.Refreshed).pipe(
|
||||
yield* bus.subscribe(ModelsDev.Event.Refreshed).pipe(
|
||||
Stream.runForEach(() =>
|
||||
modelsDev.get().pipe(
|
||||
Effect.tap((data) => Effect.sync(() => (loaded.data = structuredClone(data)))),
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
export * as PluginPromise from "./promise"
|
||||
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import type { Context, Plugin } from "@opencode-ai/plugin/v2/plugin"
|
||||
import type { Any, RegisterOptions } from "@opencode-ai/plugin/v2/tool"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
import type { Context, Plugin } from "@opencode-ai/plugin/promise/plugin"
|
||||
import type { Info } from "@opencode-ai/plugin/promise/tool"
|
||||
import { Agent } from "@opencode-ai/schema/agent"
|
||||
import { Integration } from "@opencode-ai/schema/integration"
|
||||
import { Location } from "@opencode-ai/schema/location"
|
||||
|
|
@ -14,7 +14,7 @@ import { SessionMessage } from "@opencode-ai/schema/session-message"
|
|||
import { Workspace } from "@opencode-ai/schema/workspace"
|
||||
import { WebSearch } from "@opencode-ai/schema/websearch"
|
||||
import { DateTime, Effect, Scope, Stream } from "effect"
|
||||
import { Tool } from "../tool/tool"
|
||||
import { Tool } from "../tool"
|
||||
|
||||
type HostRegistration = { readonly dispose: Effect.Effect<void> }
|
||||
type Registration = { readonly dispose: () => Promise<void> }
|
||||
|
|
@ -23,7 +23,7 @@ type JsonValue = null | boolean | number | string | Array<JsonValue> | { [key: s
|
|||
|
||||
/**
|
||||
* Adapts a Promise plugin into an Effect plugin so the existing Effect-only
|
||||
* loader (`PluginV2` / `PluginSupervisor`) can run it unchanged.
|
||||
* loader (`Plugin` / `PluginSupervisor`) can run it unchanged.
|
||||
*
|
||||
* Hook registrations created during the async `setup` attach to the plugin's
|
||||
* scope, so unloading the plugin disposes them. The captured fiber context
|
||||
|
|
@ -61,7 +61,7 @@ export function fromPromise(plugin: Plugin) {
|
|||
app: host.app,
|
||||
options: host.options,
|
||||
agent: {
|
||||
get: (id) => run(host.agent.get(id)),
|
||||
get: (input) => run(host.agent.get({ ...input, agentID: Agent.ID.make(input.agentID) })),
|
||||
list: (input) => run(host.agent.list(input)),
|
||||
transform: transform(host.agent),
|
||||
reload: () => run(host.agent.reload()),
|
||||
|
|
@ -77,7 +77,6 @@ export function fromPromise(plugin: Plugin) {
|
|||
run(host.catalog.provider.get({ ...input, providerID: Provider.ID.make(input.providerID) })),
|
||||
},
|
||||
model: {
|
||||
get: (providerID, modelID) => run(host.catalog.model.get(providerID, modelID)),
|
||||
list: (input) => run(host.catalog.model.list(input)),
|
||||
default: (input) =>
|
||||
run(host.catalog.model.default(input)).then((result) => ({ ...result, data: result.data ?? null })),
|
||||
|
|
@ -165,7 +164,44 @@ export function fromPromise(plugin: Plugin) {
|
|||
}),
|
||||
),
|
||||
},
|
||||
transform: transform(host.integration),
|
||||
transform: (callback) =>
|
||||
register(
|
||||
host.integration.transform((draft) =>
|
||||
callback({
|
||||
list: draft.list,
|
||||
get: draft.get,
|
||||
update: draft.update,
|
||||
remove: draft.remove,
|
||||
method: {
|
||||
list: draft.method.list,
|
||||
update: (input) => {
|
||||
if (!("authorize" in input)) return draft.method.update(input)
|
||||
const refresh = input.refresh
|
||||
draft.method.update({
|
||||
...input,
|
||||
authorize: (inputs) =>
|
||||
Effect.promise(() => input.authorize(inputs)).pipe(
|
||||
Effect.map((authorization) =>
|
||||
authorization.mode === "auto"
|
||||
? {
|
||||
...authorization,
|
||||
callback: Effect.promise(() => authorization.callback),
|
||||
}
|
||||
: {
|
||||
...authorization,
|
||||
callback: (code) => Effect.promise(() => authorization.callback(code)),
|
||||
},
|
||||
),
|
||||
),
|
||||
refresh:
|
||||
refresh === undefined ? undefined : (credential) => Effect.promise(() => refresh(credential)),
|
||||
})
|
||||
},
|
||||
remove: draft.method.remove,
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
reload: () => run(host.integration.reload()),
|
||||
connection: {
|
||||
active: (id) => Effect.runPromiseWith(context)(host.integration.connection.active(id)),
|
||||
|
|
@ -190,8 +226,11 @@ export function fromPromise(plugin: Plugin) {
|
|||
register(
|
||||
host.tool.transform((draft) =>
|
||||
callback({
|
||||
add: (name: string, tool: Any, options?: RegisterOptions) =>
|
||||
draft.add(name, fromPromiseTool(tool), options),
|
||||
add: (tool: Info) =>
|
||||
draft.add({
|
||||
...tool,
|
||||
execute: (input, context) => executePromiseTool(tool, input, context),
|
||||
}),
|
||||
}),
|
||||
),
|
||||
),
|
||||
|
|
@ -333,15 +372,10 @@ function wireEvent(value: unknown): unknown {
|
|||
return wire(value)
|
||||
}
|
||||
|
||||
function fromPromiseTool(tool: Any): Tool.Any {
|
||||
return {
|
||||
...tool,
|
||||
execute: (input, context) =>
|
||||
Effect.promise(() =>
|
||||
tool.execute(input, {
|
||||
...context,
|
||||
progress: (update) => Effect.runPromise(context.progress(update)),
|
||||
}),
|
||||
),
|
||||
}
|
||||
}
|
||||
const executePromiseTool = (tool: Info, input: any, context: Tool.Context) =>
|
||||
Effect.promise(() =>
|
||||
tool.execute(input, {
|
||||
...context,
|
||||
progress: (update) => Effect.runPromise(context.progress(update)),
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
|
||||
export const AlibabaPlugin = define({
|
||||
id: "opencode.provider.alibaba",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Effect } from "effect"
|
||||
import type { LanguageModelV3 } from "@ai-sdk/provider"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
import { Provider } from "../../provider"
|
||||
|
||||
type MantleSDK = {
|
||||
languageModel: (modelID: string) => LanguageModelV3
|
||||
|
|
@ -64,8 +64,8 @@ export const AmazonBedrockPlugin = define({
|
|||
effect: Effect.fn(function* (ctx) {
|
||||
yield* ctx.catalog.transform((evt) => {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (!ProviderV2.isAISDK(item.provider.package)) continue
|
||||
if (ProviderV2.packageName(item.provider.package) !== "@ai-sdk/amazon-bedrock") continue
|
||||
if (!Provider.isAISDK(item.provider.package)) continue
|
||||
if (Provider.packageName(item.provider.package) !== "@ai-sdk/amazon-bedrock") continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
if (typeof provider.settings?.endpoint !== "string") return
|
||||
// The AI SDK expects a base URL, but users configure Bedrock private/VPC
|
||||
|
|
@ -112,10 +112,10 @@ export const AmazonBedrockPlugin = define({
|
|||
yield* ctx.aisdk.hook(
|
||||
"language",
|
||||
Effect.fn(function* (evt) {
|
||||
if (evt.model.providerID !== ProviderV2.ID.amazonBedrock) return
|
||||
if (evt.model.providerID !== Provider.ID.amazonBedrock) return
|
||||
if (
|
||||
ProviderV2.isAISDK(evt.model.package) &&
|
||||
ProviderV2.packageName(evt.model.package) === "@ai-sdk/amazon-bedrock/mantle"
|
||||
Provider.isAISDK(evt.model.package) &&
|
||||
Provider.packageName(evt.model.package) === "@ai-sdk/amazon-bedrock/mantle"
|
||||
) {
|
||||
evt.language = selectMantleModel(evt.sdk, evt.model.modelID ?? evt.model.id)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
import { Provider } from "../../provider"
|
||||
|
||||
export const AnthropicPlugin = define({
|
||||
id: "opencode.provider.anthropic",
|
||||
effect: Effect.fn(function* (ctx) {
|
||||
yield* ctx.catalog.transform((evt) => {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (!ProviderV2.isAISDK(item.provider.package)) continue
|
||||
if (ProviderV2.packageName(item.provider.package) !== "@ai-sdk/anthropic") continue
|
||||
if (!Provider.isAISDK(item.provider.package)) continue
|
||||
if (Provider.packageName(item.provider.package) !== "@ai-sdk/anthropic") continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.headers = {
|
||||
...provider.headers,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
import { Provider } from "../../provider"
|
||||
|
||||
function selectLanguage(sdk: any, modelID: string, useChat: boolean) {
|
||||
if (useChat && sdk.chat) return sdk.chat(modelID)
|
||||
|
|
@ -15,8 +15,8 @@ export const AzurePlugin = define({
|
|||
effect: Effect.fn(function* (ctx) {
|
||||
yield* ctx.catalog.transform((evt) => {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (!ProviderV2.isAISDK(item.provider.package)) continue
|
||||
if (ProviderV2.packageName(item.provider.package) !== "@ai-sdk/azure") continue
|
||||
if (!Provider.isAISDK(item.provider.package)) continue
|
||||
if (Provider.packageName(item.provider.package) !== "@ai-sdk/azure") continue
|
||||
const configured = item.provider.settings?.resourceName
|
||||
const resourceName =
|
||||
typeof configured === "string" && configured.trim() !== "" ? configured : process.env.AZURE_RESOURCE_NAME
|
||||
|
|
@ -30,11 +30,11 @@ export const AzurePlugin = define({
|
|||
"sdk",
|
||||
Effect.fn(function* (evt) {
|
||||
if (evt.package !== "@ai-sdk/azure") return
|
||||
if (evt.model.providerID === ProviderV2.ID.azure) {
|
||||
if (evt.model.providerID === Provider.ID.azure) {
|
||||
if (
|
||||
!evt.options.resourceName &&
|
||||
!evt.options.baseURL &&
|
||||
(!ProviderV2.isAISDK(evt.model.package) || typeof evt.model.settings?.baseURL !== "string")
|
||||
(!Provider.isAISDK(evt.model.package) || typeof evt.model.settings?.baseURL !== "string")
|
||||
) {
|
||||
throw new Error(
|
||||
"AZURE_RESOURCE_NAME is missing, set it using env var or reconnecting the azure provider and setting it",
|
||||
|
|
@ -48,7 +48,7 @@ export const AzurePlugin = define({
|
|||
yield* ctx.aisdk.hook(
|
||||
"language",
|
||||
Effect.fn(function* (evt) {
|
||||
if (evt.model.providerID !== ProviderV2.ID.azure) return
|
||||
if (evt.model.providerID !== Provider.ID.azure) return
|
||||
evt.language = selectLanguage(
|
||||
evt.sdk,
|
||||
evt.model.modelID ?? evt.model.id,
|
||||
|
|
@ -66,8 +66,8 @@ export const AzureCognitiveServicesPlugin = define({
|
|||
const resourceName = process.env.AZURE_COGNITIVE_SERVICES_RESOURCE_NAME
|
||||
if (!resourceName) return
|
||||
for (const item of evt.provider.list()) {
|
||||
if (!ProviderV2.isAISDK(item.provider.package)) continue
|
||||
if (ProviderV2.packageName(item.provider.package) !== "@ai-sdk/openai-compatible") continue
|
||||
if (!Provider.isAISDK(item.provider.package)) continue
|
||||
if (Provider.packageName(item.provider.package) !== "@ai-sdk/openai-compatible") continue
|
||||
if (!item.provider.id.includes("azure-cognitive-services")) continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.settings = {
|
||||
|
|
@ -80,7 +80,7 @@ export const AzureCognitiveServicesPlugin = define({
|
|||
yield* ctx.aisdk.hook(
|
||||
"language",
|
||||
Effect.fn(function* (evt) {
|
||||
if (evt.model.providerID !== ProviderV2.ID.make("azure-cognitive-services")) return
|
||||
if (evt.model.providerID !== Provider.ID.make("azure-cognitive-services")) return
|
||||
evt.language = selectLanguage(
|
||||
evt.sdk,
|
||||
evt.model.modelID ?? evt.model.id,
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
import { Provider } from "../../provider"
|
||||
|
||||
export const CerebrasPlugin = define({
|
||||
id: "opencode.provider.cerebras",
|
||||
effect: Effect.fn(function* (ctx) {
|
||||
yield* ctx.catalog.transform((evt) => {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (!ProviderV2.isAISDK(item.provider.package)) continue
|
||||
if (ProviderV2.packageName(item.provider.package) !== "@ai-sdk/cerebras") continue
|
||||
if (!Provider.isAISDK(item.provider.package)) continue
|
||||
if (Provider.packageName(item.provider.package) !== "@ai-sdk/cerebras") continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.headers = { ...provider.headers, "X-Cerebras-3rd-Party-Integration": "opencode" }
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import os from "os"
|
||||
import { App } from "../../app"
|
||||
import { Effect, Option, Schema } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
|
||||
export const CloudflareAIGatewayPlugin = define({
|
||||
id: "opencode.provider.cloudflare-ai-gateway",
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import os from "os"
|
||||
import { App } from "../../app"
|
||||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
import { Provider } from "../../provider"
|
||||
|
||||
const providerID = ProviderV2.ID.make("cloudflare-workers-ai")
|
||||
const providerID = Provider.ID.make("cloudflare-workers-ai")
|
||||
|
||||
export const CloudflareWorkersAIPlugin = define({
|
||||
id: "opencode.provider.cloudflare-workers-ai",
|
||||
|
|
@ -13,7 +13,7 @@ export const CloudflareWorkersAIPlugin = define({
|
|||
const item = evt.provider.get(providerID)
|
||||
if (!item) return
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
if (!ProviderV2.isAISDK(provider.package)) return
|
||||
if (!Provider.isAISDK(provider.package)) return
|
||||
if (typeof provider.settings?.baseURL === "string") return
|
||||
const accountId = resolveAccountId(provider.settings ?? {})
|
||||
if (accountId) provider.settings = { ...provider.settings, baseURL: workersEndpoint(accountId) }
|
||||
|
|
@ -61,7 +61,7 @@ function hasWorkersEndpoint(model: {
|
|||
readonly package?: string
|
||||
readonly settings?: Readonly<Record<string, unknown>>
|
||||
}) {
|
||||
return ProviderV2.isAISDK(model.package) && typeof model.settings?.baseURL === "string"
|
||||
return Provider.isAISDK(model.package) && typeof model.settings?.baseURL === "string"
|
||||
}
|
||||
|
||||
function sdkOptions(options: Record<string, any>, app: App.Info) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
|
||||
export const CoherePlugin = define({
|
||||
id: "opencode.provider.cohere",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
|
||||
export const DeepInfraPlugin = define({
|
||||
id: "opencode.provider.deepinfra",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Effect } from "effect"
|
||||
import { pathToFileURL } from "url"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
import { Npm } from "@opencode-ai/util/npm"
|
||||
import { importModule } from "@opencode-ai/util/runtime-import"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
|
||||
export const GatewayPlugin = define({
|
||||
id: "opencode.provider.gateway",
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import type { IntegrationOAuthMethodRegistration } from "@opencode-ai/plugin/v2/effect/integration"
|
||||
import type { IntegrationOAuthMethodRegistration } from "@opencode-ai/plugin/effect/integration"
|
||||
import { Effect, Option, Schema, Semaphore, Stream } from "effect"
|
||||
import { Catalog } from "../../catalog"
|
||||
import { Credential } from "../../credential"
|
||||
import { EventV2 } from "../../event"
|
||||
import { Bus } from "../../bus"
|
||||
import { CopilotModels } from "../../github-copilot/models"
|
||||
import { App } from "../../app"
|
||||
import { Integration } from "../../integration"
|
||||
import { ModelV2 } from "../../model"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
import { Model } from "../../model"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
import { Provider } from "../../provider"
|
||||
import type { PluginInternal } from "../internal"
|
||||
|
||||
const clientID = "Ov23li8tweQw6odWQebz"
|
||||
|
|
@ -152,11 +152,11 @@ export const GithubCopilotPlugin = define({
|
|||
id: "opencode.provider.github-copilot",
|
||||
effect: Effect.fn(function* (ctx) {
|
||||
const catalog = yield* Catalog.Service
|
||||
const events = yield* EventV2.Service
|
||||
const bus = yield* Bus.Service
|
||||
const loading = Semaphore.makeUnsafe(1)
|
||||
const loaded: {
|
||||
baseURL?: string
|
||||
models?: Map<ModelV2.ID, ModelV2.Info>
|
||||
models?: Map<Model.ID, Model.Info>
|
||||
} = {}
|
||||
|
||||
const load = Effect.fn("GithubCopilotPlugin.load")(function* () {
|
||||
|
|
@ -171,8 +171,8 @@ export const GithubCopilotPlugin = define({
|
|||
}
|
||||
|
||||
loaded.baseURL = copilotBaseURL(credential.metadata)
|
||||
const provider = yield* catalog.provider.get(ProviderV2.ID.githubCopilot)
|
||||
const existing = (yield* catalog.model.all()).filter((model) => model.providerID === ProviderV2.ID.githubCopilot)
|
||||
const provider = yield* catalog.provider.get(Provider.ID.githubCopilot)
|
||||
const existing = (yield* catalog.model.all()).filter((model) => model.providerID === Provider.ID.githubCopilot)
|
||||
loaded.models = yield* Effect.tryPromise({
|
||||
try: () =>
|
||||
CopilotModels.get(
|
||||
|
|
@ -197,11 +197,11 @@ export const GithubCopilotPlugin = define({
|
|||
draft.method.update(oauth(ctx.app))
|
||||
})
|
||||
yield* ctx.catalog.transform((evt) => {
|
||||
const item = evt.provider.get(ProviderV2.ID.githubCopilot)
|
||||
const item = evt.provider.get(Provider.ID.githubCopilot)
|
||||
if (!item) return
|
||||
if (loaded.models) {
|
||||
for (const id of item.models.keys()) {
|
||||
if (!loaded.models.has(ModelV2.ID.make(id))) evt.model.remove(item.provider.id, id)
|
||||
if (!loaded.models.has(Model.ID.make(id))) evt.model.remove(item.provider.id, id)
|
||||
}
|
||||
for (const [id, model] of loaded.models) {
|
||||
evt.model.update(item.provider.id, id, (draft) => Object.assign(draft, structuredClone(model)))
|
||||
|
|
@ -209,12 +209,12 @@ export const GithubCopilotPlugin = define({
|
|||
} else if (loaded.baseURL) {
|
||||
for (const id of item.models.keys()) {
|
||||
evt.model.update(item.provider.id, id, (model) => {
|
||||
model.settings = ProviderV2.mergeOverlay(model.settings, { baseURL: loaded.baseURL })
|
||||
model.settings = Provider.mergeOverlay(model.settings, { baseURL: loaded.baseURL })
|
||||
})
|
||||
}
|
||||
}
|
||||
if (item.models.has(ModelV2.ID.make("gpt-5-chat-latest"))) {
|
||||
evt.model.update(item.provider.id, ModelV2.ID.make("gpt-5-chat-latest"), (model) => {
|
||||
if (item.models.has(Model.ID.make("gpt-5-chat-latest"))) {
|
||||
evt.model.update(item.provider.id, Model.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
|
||||
|
|
@ -222,7 +222,7 @@ export const GithubCopilotPlugin = define({
|
|||
}
|
||||
})
|
||||
const refresh = () => loading.withPermit(load().pipe(Effect.andThen(ctx.catalog.reload())))
|
||||
yield* events.subscribe(Integration.Event.ConnectionUpdated).pipe(
|
||||
yield* bus.subscribe(Integration.Event.ConnectionUpdated).pipe(
|
||||
Stream.filter((event) => event.data.integrationID === Integration.ID.make("github-copilot")),
|
||||
Stream.runForEach(refresh),
|
||||
Effect.forkScoped({ startImmediately: true }),
|
||||
|
|
@ -231,7 +231,7 @@ export const GithubCopilotPlugin = define({
|
|||
yield* ctx.aisdk.hook(
|
||||
"sdk",
|
||||
Effect.fn(function* (evt) {
|
||||
if (evt.model.providerID !== ProviderV2.ID.githubCopilot) return
|
||||
if (evt.model.providerID !== Provider.ID.githubCopilot) return
|
||||
if (evt.package !== "@ai-sdk/github-copilot" && evt.package !== "@ai-sdk/anthropic") return
|
||||
evt.options.fetch = copilotFetch(
|
||||
typeof evt.options.apiKey === "string" ? evt.options.apiKey : undefined,
|
||||
|
|
@ -255,7 +255,7 @@ export const GithubCopilotPlugin = define({
|
|||
yield* ctx.aisdk.hook(
|
||||
"language",
|
||||
Effect.fn(function* (evt) {
|
||||
if (evt.model.providerID !== ProviderV2.ID.githubCopilot) return
|
||||
if (evt.model.providerID !== Provider.ID.githubCopilot) return
|
||||
if (evt.sdk.responses === undefined && evt.sdk.chat === undefined) {
|
||||
evt.language = evt.sdk.languageModel(evt.model.modelID ?? evt.model.id)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import os from "os"
|
||||
import { App } from "../../app"
|
||||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
import { Provider } from "../../provider"
|
||||
|
||||
export const GitLabPlugin = define({
|
||||
id: "opencode.provider.gitlab",
|
||||
|
|
@ -35,7 +35,7 @@ export const GitLabPlugin = define({
|
|||
yield* ctx.aisdk.hook(
|
||||
"language",
|
||||
Effect.fn(function* (evt) {
|
||||
if (evt.model.providerID !== ProviderV2.ID.gitlab) return
|
||||
if (evt.model.providerID !== Provider.ID.gitlab) return
|
||||
const featureFlags =
|
||||
typeof evt.options.featureFlags === "object" && evt.options.featureFlags ? evt.options.featureFlags : {}
|
||||
const id = evt.model.modelID ?? evt.model.id
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
import { Provider } from "../../provider"
|
||||
|
||||
function resolveProject(options: Record<string, any>) {
|
||||
// models.dev advertises GOOGLE_VERTEX_PROJECT for Vertex, while Google SDKs
|
||||
|
|
@ -59,12 +59,12 @@ export const GoogleVertexPlugin = define({
|
|||
effect: Effect.fn(function* (ctx) {
|
||||
yield* ctx.catalog.transform((evt) => {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (!ProviderV2.isAISDK(item.provider.package)) continue
|
||||
if (!Provider.isAISDK(item.provider.package)) continue
|
||||
if (
|
||||
ProviderV2.packageName(item.provider.package) !== "@ai-sdk/google-vertex" &&
|
||||
Provider.packageName(item.provider.package) !== "@ai-sdk/google-vertex" &&
|
||||
!(
|
||||
item.provider.id === ProviderV2.ID.googleVertex &&
|
||||
ProviderV2.packageName(item.provider.package)?.includes("@ai-sdk/openai-compatible")
|
||||
item.provider.id === Provider.ID.googleVertex &&
|
||||
Provider.packageName(item.provider.package)?.includes("@ai-sdk/openai-compatible")
|
||||
)
|
||||
)
|
||||
continue
|
||||
|
|
@ -78,7 +78,7 @@ export const GoogleVertexPlugin = define({
|
|||
...(typeof provider.settings?.baseURL === "string"
|
||||
? { baseURL: replaceVertexVars(provider.settings.baseURL, project, location) }
|
||||
: {}),
|
||||
...(ProviderV2.packageName(provider.package)?.includes("@ai-sdk/openai-compatible")
|
||||
...(Provider.packageName(provider.package)?.includes("@ai-sdk/openai-compatible")
|
||||
? { fetch: authFetch(provider.settings?.fetch) }
|
||||
: {}),
|
||||
}
|
||||
|
|
@ -88,7 +88,7 @@ export const GoogleVertexPlugin = define({
|
|||
yield* ctx.aisdk.hook(
|
||||
"sdk",
|
||||
Effect.fn(function* (evt) {
|
||||
if (evt.model.providerID === ProviderV2.ID.googleVertex && evt.package.includes("@ai-sdk/openai-compatible")) {
|
||||
if (evt.model.providerID === Provider.ID.googleVertex && evt.package.includes("@ai-sdk/openai-compatible")) {
|
||||
evt.options.fetch = authFetch(evt.options.fetch)
|
||||
return
|
||||
}
|
||||
|
|
@ -108,7 +108,7 @@ export const GoogleVertexPlugin = define({
|
|||
yield* ctx.aisdk.hook(
|
||||
"language",
|
||||
Effect.fn(function* (evt) {
|
||||
if (evt.model.providerID !== ProviderV2.ID.googleVertex) return
|
||||
if (evt.model.providerID !== Provider.ID.googleVertex) return
|
||||
evt.language = evt.sdk.languageModel(String(evt.model.modelID ?? evt.model.id).trim())
|
||||
}),
|
||||
)
|
||||
|
|
@ -120,8 +120,8 @@ export const GoogleVertexAnthropicPlugin = define({
|
|||
effect: Effect.fn(function* (ctx) {
|
||||
yield* ctx.catalog.transform((evt) => {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (!ProviderV2.isAISDK(item.provider.package)) continue
|
||||
if (ProviderV2.packageName(item.provider.package) !== "@ai-sdk/google-vertex/anthropic") continue
|
||||
if (!Provider.isAISDK(item.provider.package)) continue
|
||||
if (Provider.packageName(item.provider.package) !== "@ai-sdk/google-vertex/anthropic") continue
|
||||
const project =
|
||||
item.provider.settings?.project ??
|
||||
process.env.GOOGLE_CLOUD_PROJECT ??
|
||||
|
|
@ -167,7 +167,7 @@ export const GoogleVertexAnthropicPlugin = define({
|
|||
yield* ctx.aisdk.hook(
|
||||
"language",
|
||||
Effect.fn(function* (evt) {
|
||||
if (evt.model.providerID !== ProviderV2.ID.make("google-vertex-anthropic")) return
|
||||
if (evt.model.providerID !== Provider.ID.make("google-vertex-anthropic")) return
|
||||
evt.language = evt.sdk.languageModel(String(evt.model.modelID ?? evt.model.id).trim())
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
|
||||
export const GooglePlugin = define({
|
||||
id: "opencode.provider.google",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
|
||||
export const GroqPlugin = define({
|
||||
id: "opencode.provider.groq",
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
import { Provider } from "../../provider"
|
||||
|
||||
export const KiloPlugin = define({
|
||||
id: "opencode.provider.kilo",
|
||||
effect: Effect.fn(function* (ctx) {
|
||||
yield* ctx.catalog.transform((evt) => {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (!ProviderV2.isAISDK(item.provider.package)) continue
|
||||
if (ProviderV2.packageName(item.provider.package) !== "@ai-sdk/openai-compatible") continue
|
||||
if (!Provider.isAISDK(item.provider.package)) continue
|
||||
if (Provider.packageName(item.provider.package) !== "@ai-sdk/openai-compatible") continue
|
||||
if (item.provider.settings?.baseURL !== "https://api.kilo.ai/api/gateway") continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.headers = { ...provider.headers, "HTTP-Referer": "https://opencode.ai/", "X-Title": "opencode" }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
import { Integration } from "../../integration"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
import { Provider } from "../../provider"
|
||||
|
||||
export const LLMGatewayPlugin = define({
|
||||
id: "opencode.provider.llmgateway",
|
||||
|
|
@ -11,8 +11,8 @@ export const LLMGatewayPlugin = define({
|
|||
yield* ctx.catalog.transform((evt) => {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (item.provider.disabled) continue
|
||||
if (!ProviderV2.isAISDK(item.provider.package)) continue
|
||||
if (ProviderV2.packageName(item.provider.package) !== "@ai-sdk/openai-compatible") continue
|
||||
if (!Provider.isAISDK(item.provider.package)) continue
|
||||
if (Provider.packageName(item.provider.package) !== "@ai-sdk/openai-compatible") continue
|
||||
if (item.provider.settings?.baseURL !== "https://api.llmgateway.io/v1") continue
|
||||
if (!configured.has(Integration.ID.make(item.provider.id))) continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
|
||||
export const MistralPlugin = define({
|
||||
id: "opencode.provider.mistral",
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
import { Provider } from "../../provider"
|
||||
|
||||
export const NvidiaPlugin = define({
|
||||
id: "opencode.provider.nvidia",
|
||||
effect: Effect.fn(function* (ctx) {
|
||||
yield* ctx.catalog.transform((evt) => {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (!ProviderV2.isAISDK(item.provider.package)) continue
|
||||
if (ProviderV2.packageName(item.provider.package) !== "@ai-sdk/openai-compatible") continue
|
||||
if (!Provider.isAISDK(item.provider.package)) continue
|
||||
if (Provider.packageName(item.provider.package) !== "@ai-sdk/openai-compatible") continue
|
||||
if (item.provider.settings?.baseURL !== "https://integrate.api.nvidia.com/v1") continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.headers = {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
|
||||
export const OpenAICompatiblePlugin = define({
|
||||
id: "opencode.provider.openai-compatible",
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import { createServer } from "node:http"
|
||||
import type { IntegrationOAuthMethodRegistration } from "@opencode-ai/plugin/v2/effect/integration"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import type { IntegrationOAuthMethodRegistration } from "@opencode-ai/plugin/effect/integration"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
import { Deferred, Effect, Option, Schema, Semaphore, Stream } from "effect"
|
||||
import { App } from "../../app"
|
||||
import { Credential } from "../../credential"
|
||||
import { EventV2 } from "../../event"
|
||||
import { Bus } from "../../bus"
|
||||
import { Integration } from "../../integration"
|
||||
import { ModelV2 } from "../../model"
|
||||
import { Model } from "../../model"
|
||||
import { OauthCallbackPage } from "../../oauth/page"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
import { Provider } from "../../provider"
|
||||
import type { PluginInternal } from "../internal"
|
||||
import { OpenAICodex } from "./openai-codex"
|
||||
|
||||
|
|
@ -162,7 +162,7 @@ const headless = (app: App.Info) => ({
|
|||
export const OpenAIPlugin = define({
|
||||
id: "opencode.provider.openai",
|
||||
effect: Effect.fn(function* (ctx) {
|
||||
const events = yield* EventV2.Service
|
||||
const bus = yield* Bus.Service
|
||||
const loading = Semaphore.makeUnsafe(1)
|
||||
let chatgpt = false
|
||||
|
||||
|
|
@ -181,17 +181,17 @@ export const OpenAIPlugin = define({
|
|||
yield* load()
|
||||
yield* ctx.catalog.transform((evt) => {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (!ProviderV2.isAISDK(item.provider.package)) continue
|
||||
if (ProviderV2.packageName(item.provider.package) !== "@ai-sdk/openai") continue
|
||||
if (!item.models.has(ModelV2.ID.make("gpt-5-chat-latest"))) continue
|
||||
evt.model.update(item.provider.id, ModelV2.ID.make("gpt-5-chat-latest"), (model) => {
|
||||
if (!Provider.isAISDK(item.provider.package)) continue
|
||||
if (Provider.packageName(item.provider.package) !== "@ai-sdk/openai") continue
|
||||
if (!item.models.has(Model.ID.make("gpt-5-chat-latest"))) continue
|
||||
evt.model.update(item.provider.id, Model.ID.make("gpt-5-chat-latest"), (model) => {
|
||||
// OpenAIPlugin sends OpenAI models through Responses; this alias is a
|
||||
// chat-completions-only model, so hide it only from OpenAI's catalog.
|
||||
model.enabled = false
|
||||
})
|
||||
}
|
||||
if (!chatgpt) return
|
||||
const item = evt.provider.get(ProviderV2.ID.openai)
|
||||
const item = evt.provider.get(Provider.ID.openai)
|
||||
if (!item) return
|
||||
for (const model of item.models.values()) {
|
||||
// ChatGPT-plan tokens only authorize codex-eligible models, and the
|
||||
|
|
@ -211,7 +211,7 @@ export const OpenAIPlugin = define({
|
|||
})
|
||||
|
||||
const refresh = () => loading.withPermit(load().pipe(Effect.andThen(ctx.catalog.reload())))
|
||||
yield* events.subscribe(Integration.Event.ConnectionUpdated).pipe(
|
||||
yield* bus.subscribe(Integration.Event.ConnectionUpdated).pipe(
|
||||
Stream.filter((event) => event.data.integrationID === Integration.ID.make("openai")),
|
||||
Stream.runForEach(refresh),
|
||||
Effect.forkScoped({ startImmediately: true }),
|
||||
|
|
@ -227,7 +227,7 @@ export const OpenAIPlugin = define({
|
|||
yield* ctx.aisdk.hook(
|
||||
"language",
|
||||
Effect.fn(function* (evt) {
|
||||
if (evt.model.providerID !== ProviderV2.ID.openai) return
|
||||
if (evt.model.providerID !== Provider.ID.openai) return
|
||||
evt.language = evt.sdk.responses(evt.model.modelID ?? evt.model.id)
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import { Duration, Effect, Schema, Semaphore, Stream } from "effect"
|
||||
import type { Scope } from "effect"
|
||||
import type { IntegrationOAuthMethodRegistration } from "@opencode-ai/plugin/v2/effect/integration"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import type { IntegrationOAuthMethodRegistration } from "@opencode-ai/plugin/effect/integration"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
import type { CredentialValue } from "@opencode-ai/sdk/v2/types"
|
||||
import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
|
||||
import { EventV2 } from "../../event"
|
||||
import { Bus } from "../../bus"
|
||||
import { Credential } from "../../credential"
|
||||
import { Integration } from "../../integration"
|
||||
import { ModelV2 } from "../../model"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
import { Model } from "../../model"
|
||||
import { Provider } from "../../provider"
|
||||
import { ConfigProviderV1 } from "../../v1/config/provider"
|
||||
import { Money } from "@opencode-ai/schema/money"
|
||||
import { ConfigProviderOptionsV1 } from "../../v1/config/provider-options"
|
||||
|
|
@ -82,10 +82,10 @@ function oauth(http: HttpClient.HttpClient) {
|
|||
} satisfies IntegrationOAuthMethodRegistration
|
||||
}
|
||||
|
||||
export const OpencodePlugin = define<HttpClient.HttpClient | EventV2.Service | Scope.Scope>({
|
||||
export const OpencodePlugin = define<HttpClient.HttpClient | Bus.Service | Scope.Scope>({
|
||||
id: "opencode.provider.opencode",
|
||||
effect: Effect.fn(function* (ctx) {
|
||||
const events = yield* EventV2.Service
|
||||
const bus = yield* Bus.Service
|
||||
const http = yield* HttpClient.HttpClient
|
||||
const loading = Semaphore.makeUnsafe(1)
|
||||
let connected = false
|
||||
|
|
@ -120,7 +120,7 @@ export const OpencodePlugin = define<HttpClient.HttpClient | EventV2.Service | S
|
|||
catalog.provider.update(providerID, (provider) => {
|
||||
provider.integrationID = Integration.ID.make("opencode")
|
||||
if (item.name !== undefined) provider.name = item.name
|
||||
provider.package = item.npm ? ProviderV2.aisdk(item.npm) : ""
|
||||
provider.package = item.npm ? Provider.aisdk(item.npm) : ""
|
||||
provider.settings = {
|
||||
...provider.settings,
|
||||
...withoutCredentials(item.options),
|
||||
|
|
@ -131,12 +131,12 @@ export const OpencodePlugin = define<HttpClient.HttpClient | EventV2.Service | S
|
|||
|
||||
for (const [modelID, config] of Object.entries(item.models ?? {})) {
|
||||
catalog.model.update(providerID, modelID, (model) => {
|
||||
if (config.family !== undefined) model.family = config.family
|
||||
if (config.family !== undefined) model.family = Model.Family.make(config.family)
|
||||
if (config.name !== undefined) model.name = config.name
|
||||
if (config.id !== undefined) model.modelID = config.id
|
||||
model.compatibility = ModelV2.compatibility(config.interleaved) ?? model.compatibility
|
||||
if (config.id !== undefined) model.modelID = Model.ID.make(config.id)
|
||||
model.compatibility = Model.compatibility(config.interleaved) ?? model.compatibility
|
||||
if (config.provider !== undefined) {
|
||||
model.package = config.provider.npm ? ProviderV2.aisdk(config.provider.npm) : undefined
|
||||
model.package = config.provider.npm ? Provider.aisdk(config.provider.npm) : undefined
|
||||
if (config.provider.api) model.settings = { ...model.settings, baseURL: config.provider.api }
|
||||
}
|
||||
if (config.tool_call !== undefined) model.capabilities.tools = config.tool_call
|
||||
|
|
@ -147,7 +147,7 @@ export const OpencodePlugin = define<HttpClient.HttpClient | EventV2.Service | S
|
|||
if (config.variants !== undefined) {
|
||||
model.variants ??= []
|
||||
for (const [id, options] of Object.entries(config.variants)) {
|
||||
const variantID = ModelV2.VariantID.make(id)
|
||||
const variantID = Model.VariantID.make(id)
|
||||
let existing = model.variants.find((item) => item.id === variantID)
|
||||
if (!existing) {
|
||||
existing = { id: variantID }
|
||||
|
|
@ -174,7 +174,7 @@ export const OpencodePlugin = define<HttpClient.HttpClient | EventV2.Service | S
|
|||
}
|
||||
}
|
||||
|
||||
const item = catalog.provider.get(ProviderV2.ID.opencode)
|
||||
const item = catalog.provider.get(Provider.ID.opencode)
|
||||
if (!item) return
|
||||
const hasKey = Boolean(process.env.OPENCODE_API_KEY || connected || item.provider.settings?.apiKey)
|
||||
catalog.provider.update(item.provider.id, (provider) => {
|
||||
|
|
@ -190,7 +190,7 @@ export const OpencodePlugin = define<HttpClient.HttpClient | EventV2.Service | S
|
|||
})
|
||||
|
||||
const refresh = () => loading.withPermit(load().pipe(Effect.andThen(ctx.catalog.reload())))
|
||||
yield* events.subscribe(Integration.Event.ConnectionUpdated).pipe(
|
||||
yield* bus.subscribe(Integration.Event.ConnectionUpdated).pipe(
|
||||
Stream.filter((event) => event.data.integrationID === Integration.ID.make("opencode")),
|
||||
Stream.runForEach(refresh),
|
||||
Effect.forkScoped({ startImmediately: true }),
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
import { Effect } from "effect"
|
||||
import { ModelV2 } from "../../model"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { Model } from "../../model"
|
||||
import { Provider } from "../../provider"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
|
||||
export const OpenRouterPlugin = define({
|
||||
id: "opencode.provider.openrouter",
|
||||
effect: Effect.fn(function* (ctx) {
|
||||
yield* ctx.catalog.transform((evt) => {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (!ProviderV2.isAISDK(item.provider.package)) continue
|
||||
if (ProviderV2.packageName(item.provider.package) !== "@openrouter/ai-sdk-provider") continue
|
||||
if (!Provider.isAISDK(item.provider.package)) continue
|
||||
if (Provider.packageName(item.provider.package) !== "@openrouter/ai-sdk-provider") continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.headers = { ...provider.headers, "HTTP-Referer": "https://opencode.ai/", "X-Title": "opencode" }
|
||||
})
|
||||
for (const modelID of [ModelV2.ID.make("gpt-5-chat-latest"), ModelV2.ID.make("openai/gpt-5-chat")]) {
|
||||
for (const modelID of [Model.ID.make("gpt-5-chat-latest"), Model.ID.make("openai/gpt-5-chat")]) {
|
||||
if (!item.models.has(modelID)) continue
|
||||
evt.model.update(item.provider.id, modelID, (model) => {
|
||||
// These are OpenRouter-specific OpenAI chat aliases that do not work
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
|
||||
export const PerplexityPlugin = define({
|
||||
id: "opencode.provider.perplexity",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { Effect } from "effect"
|
||||
import { pathToFileURL } from "url"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
import { Npm } from "@opencode-ai/util/npm"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
import { Provider } from "../../provider"
|
||||
import { importModule } from "@opencode-ai/util/runtime-import"
|
||||
|
||||
export const SapAICorePlugin = define({
|
||||
|
|
@ -12,7 +12,7 @@ export const SapAICorePlugin = define({
|
|||
yield* ctx.aisdk.hook(
|
||||
"sdk",
|
||||
Effect.fn(function* (evt) {
|
||||
if (evt.model.providerID !== ProviderV2.ID.make("sap-ai-core")) return
|
||||
if (evt.model.providerID !== Provider.ID.make("sap-ai-core")) return
|
||||
const serviceKey =
|
||||
process.env.AICORE_SERVICE_KEY ??
|
||||
(typeof evt.options.serviceKey === "string" ? evt.options.serviceKey : undefined)
|
||||
|
|
@ -42,7 +42,7 @@ export const SapAICorePlugin = define({
|
|||
yield* ctx.aisdk.hook(
|
||||
"language",
|
||||
Effect.fn(function* (evt) {
|
||||
if (evt.model.providerID !== ProviderV2.ID.make("sap-ai-core")) return
|
||||
if (evt.model.providerID !== Provider.ID.make("sap-ai-core")) return
|
||||
evt.language = evt.sdk(evt.model.modelID ?? evt.model.id)
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
import { Provider } from "../../provider"
|
||||
|
||||
type FetchLike = (url: string | URL | Request, init?: RequestInit) => Promise<Response>
|
||||
|
||||
|
|
@ -70,7 +70,7 @@ export const SnowflakeCortexPlugin = define({
|
|||
yield* ctx.aisdk.hook(
|
||||
"sdk",
|
||||
Effect.fn(function* (evt) {
|
||||
if (evt.model.providerID !== ProviderV2.ID.make("snowflake-cortex")) return
|
||||
if (evt.model.providerID !== Provider.ID.make("snowflake-cortex")) return
|
||||
const token =
|
||||
process.env.SNOWFLAKE_CORTEX_TOKEN ??
|
||||
process.env.SNOWFLAKE_CORTEX_PAT ??
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
|
||||
export const TogetherAIPlugin = define({
|
||||
id: "opencode.provider.togetherai",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
|
||||
export const VenicePlugin = define({
|
||||
id: "opencode.provider.venice",
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
import { Provider } from "../../provider"
|
||||
|
||||
export const VercelPlugin = define({
|
||||
id: "opencode.provider.vercel",
|
||||
effect: Effect.fn(function* (ctx) {
|
||||
yield* ctx.catalog.transform((evt) => {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (!ProviderV2.isAISDK(item.provider.package)) continue
|
||||
if (ProviderV2.packageName(item.provider.package) !== "@ai-sdk/vercel") continue
|
||||
if (!Provider.isAISDK(item.provider.package)) continue
|
||||
if (Provider.packageName(item.provider.package) !== "@ai-sdk/vercel") continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.headers = { ...provider.headers, "http-referer": "https://opencode.ai/", "x-title": "opencode" }
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { createServer } from "node:http"
|
||||
import type { IntegrationOAuthMethodRegistration } from "@opencode-ai/plugin/v2/effect/integration"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import type { IntegrationOAuthMethodRegistration } from "@opencode-ai/plugin/effect/integration"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
import { Clock, Deferred, Effect, Option, Schema } from "effect"
|
||||
import { App } from "../../app"
|
||||
import { Credential } from "../../credential"
|
||||
import { Integration } from "../../integration"
|
||||
import { OauthCallbackPage } from "../../oauth/page"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
import { Provider } from "../../provider"
|
||||
|
||||
const clientID = "b1a00492-073a-47ea-816f-4c329264a828"
|
||||
const issuer = "https://auth.x.ai/oauth2"
|
||||
|
|
@ -173,7 +173,7 @@ export const XAIPlugin = define({
|
|||
yield* ctx.aisdk.hook(
|
||||
"language",
|
||||
Effect.fn(function* (evt) {
|
||||
if (evt.model.providerID !== ProviderV2.ID.make("xai")) return
|
||||
if (evt.model.providerID !== Provider.ID.make("xai")) return
|
||||
evt.language = evt.sdk.responses(evt.model.modelID ?? evt.model.id)
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { ProviderV2 } from "../../provider"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
import { Provider } from "../../provider"
|
||||
|
||||
export const ZenmuxPlugin = define({
|
||||
id: "opencode.provider.zenmux",
|
||||
effect: Effect.fn(function* (ctx) {
|
||||
yield* ctx.catalog.transform((evt) => {
|
||||
for (const item of evt.provider.list()) {
|
||||
if (!ProviderV2.isAISDK(item.provider.package)) continue
|
||||
if (ProviderV2.packageName(item.provider.package) !== "@ai-sdk/openai-compatible") continue
|
||||
if (!Provider.isAISDK(item.provider.package)) continue
|
||||
if (Provider.packageName(item.provider.package) !== "@ai-sdk/openai-compatible") continue
|
||||
if (item.provider.settings?.baseURL !== "https://zenmux.ai/api/v1") continue
|
||||
evt.provider.update(item.provider.id, (provider) => {
|
||||
provider.headers = {
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
export * as PluginRuntime from "./runtime"
|
||||
|
||||
import { Context, Effect, Layer } from "effect"
|
||||
import { AgentV2 } from "../agent"
|
||||
import { Agent } from "../agent"
|
||||
import { makeGlobalNode } from "@opencode-ai/util/effect/app-node"
|
||||
import { Job } from "../job"
|
||||
import { Location } from "../location"
|
||||
import { LocationServiceMap } from "../location-service-map"
|
||||
import { SessionV2 } from "../session"
|
||||
import { Session } from "../session"
|
||||
|
||||
export interface Interface {
|
||||
readonly session: Pick<
|
||||
SessionV2.Interface,
|
||||
Session.Interface,
|
||||
"get" | "create" | "messages" | "prompt" | "generate" | "command" | "resume" | "interrupt" | "synthetic"
|
||||
>
|
||||
readonly job: Pick<Job.Interface, "start" | "wait" | "block" | "background" | "cancel">
|
||||
|
|
@ -18,7 +18,7 @@ export interface Interface {
|
|||
readonly agent: {
|
||||
readonly list: (
|
||||
ref: Location.Ref,
|
||||
) => Effect.Effect<{ readonly location: Location.Info; readonly data: AgentV2.Info[] }>
|
||||
) => Effect.Effect<{ readonly location: Location.Info; readonly data: Agent.Info[] }>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -74,7 +74,7 @@ export const layerWithCell = (cell: Cell) =>
|
|||
export const providerLayerWithCell = (cell: Cell) =>
|
||||
Layer.effectDiscard(
|
||||
Effect.gen(function* () {
|
||||
const sessions = yield* SessionV2.Service
|
||||
const sessions = yield* Session.Service
|
||||
const jobs = yield* Job.Service
|
||||
const locations = yield* LocationServiceMap.Service
|
||||
const runtime: Interface = {
|
||||
|
|
@ -85,7 +85,7 @@ export const providerLayerWithCell = (cell: Cell) =>
|
|||
list: (ref) =>
|
||||
Effect.gen(function* () {
|
||||
const location = yield* Location.Service
|
||||
const agents = yield* AgentV2.Service
|
||||
const agents = yield* Agent.Service
|
||||
return {
|
||||
location: new Location.Info({
|
||||
directory: location.directory,
|
||||
|
|
@ -118,7 +118,7 @@ export const providerNodeWithCell = (cell: Cell) =>
|
|||
makeGlobalNode({
|
||||
name: "plugin-runtime-provider",
|
||||
layer: providerLayerWithCell(cell),
|
||||
deps: [node, SessionV2.node, Job.node, LocationServiceMap.node],
|
||||
deps: [node, Session.node, Job.node, LocationServiceMap.node],
|
||||
})
|
||||
|
||||
export const providerNode = providerNodeWithCell(defaultCell)
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
export * as SdkPlugins from "./sdk"
|
||||
|
||||
import type { Plugin } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import type { Plugin } from "@opencode-ai/plugin/effect/plugin"
|
||||
import { Context, Effect, Layer } from "effect"
|
||||
import { makeGlobalNode } from "@opencode-ai/util/effect/app-node"
|
||||
import { EventV2 } from "../event"
|
||||
import type { PluginV2 } from "../plugin"
|
||||
import { Bus } from "../bus"
|
||||
import type { Versioned } from "../plugin"
|
||||
|
||||
export const Updated = EventV2.ephemeral({ type: "sdk.plugin.updated", schema: {} })
|
||||
export const Updated = Bus.ephemeral({ type: "sdk.plugin.updated", schema: {} })
|
||||
|
||||
/**
|
||||
* Holds the plugins an embedder (the `@opencode-ai/sdk-next` host) contributes,
|
||||
|
|
@ -21,7 +21,7 @@ export const Updated = EventV2.ephemeral({ type: "sdk.plugin.updated", schema: {
|
|||
*/
|
||||
export interface Interface {
|
||||
readonly register: (plugin: Plugin) => Effect.Effect<void>
|
||||
readonly all: () => readonly PluginV2.Versioned[]
|
||||
readonly all: () => readonly Versioned[]
|
||||
}
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/SdkPlugins") {}
|
||||
|
|
@ -29,17 +29,17 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/Sd
|
|||
export const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const events = yield* EventV2.Service
|
||||
const plugins = new Map<string, PluginV2.Versioned>()
|
||||
const bus = yield* Bus.Service
|
||||
const plugins = new Map<string, Versioned>()
|
||||
let revision = 0
|
||||
return Service.of({
|
||||
register: (plugin) =>
|
||||
Effect.sync(() => {
|
||||
plugins.set(plugin.id, { ...plugin, version: String(++revision) })
|
||||
}).pipe(Effect.andThen(events.publish(Updated, {})), Effect.asVoid),
|
||||
}).pipe(Effect.andThen(bus.publish(Updated, {})), Effect.asVoid),
|
||||
all: () => [...plugins.values()],
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
export const node = makeGlobalNode({ service: Service, layer, deps: [EventV2.node] })
|
||||
export const node = makeGlobalNode({ service: Service, layer, deps: [Bus.node] })
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
export * as SkillPlugin from "./skill"
|
||||
|
||||
import { define, type Context } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { define, type Context } from "@opencode-ai/plugin/effect/plugin"
|
||||
import { Effect } from "effect"
|
||||
import { AbsolutePath } from "../schema"
|
||||
import { SkillV2 } from "../skill"
|
||||
import { Skill } from "../skill"
|
||||
import { Config } from "../config"
|
||||
import { Location } from "../location"
|
||||
import { FSUtil } from "@opencode-ai/util/fs-util"
|
||||
|
|
@ -29,11 +29,11 @@ export const Plugin = define({
|
|||
const reportContent = yield* reportContentWithDiagnostics(ctx.app)
|
||||
yield* ctx.skill.transform((draft) => {
|
||||
draft.source(
|
||||
SkillV2.EmbeddedSource.make({
|
||||
Skill.EmbeddedSource.make({
|
||||
type: "embedded",
|
||||
skill: SkillV2.Info.make({
|
||||
id: SkillV2.ID.make("opencode"),
|
||||
name: SkillV2.Name.make("OpenCode"),
|
||||
skill: Skill.Info.make({
|
||||
id: Skill.ID.make("opencode"),
|
||||
name: Skill.Name.make("OpenCode"),
|
||||
description: OpencodeDescription,
|
||||
location: AbsolutePath.make("/builtin/opencode.md"),
|
||||
content: OpencodeContent,
|
||||
|
|
@ -41,11 +41,11 @@ export const Plugin = define({
|
|||
}),
|
||||
)
|
||||
draft.source(
|
||||
SkillV2.EmbeddedSource.make({
|
||||
Skill.EmbeddedSource.make({
|
||||
type: "embedded",
|
||||
skill: SkillV2.Info.make({
|
||||
id: SkillV2.ID.make("report"),
|
||||
name: SkillV2.Name.make("Report"),
|
||||
skill: Skill.Info.make({
|
||||
id: Skill.ID.make("report"),
|
||||
name: Skill.Name.make("Report"),
|
||||
description: REPORT_DESCRIPTION,
|
||||
slash: true,
|
||||
location: AbsolutePath.make("/builtin/report.md"),
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ Use this guide as the starting point for work involving OpenCode itself. It
|
|||
covers the core concepts needed to configure and customize OpenCode, extend it
|
||||
with plugins, and build integrations with the OpenCode SDK, clients, and API.
|
||||
|
||||
Full documentation is available at <https://v2.opencode.ai/docs>. This overview is
|
||||
Full documentation is available at <https://opencode.ai/v2/docs/>. This overview is
|
||||
only an index of core concepts. Before answering a question about a topic below,
|
||||
fetch the URL named in that section and use the full page as the source of
|
||||
truth. Follow links from that page when the question needs more detail. Fetch
|
||||
<https://v2.opencode.ai/llms.txt> first when you need to discover the relevant
|
||||
<https://opencode.ai/v2/docs/> first when you need to discover the relevant
|
||||
documentation page.
|
||||
|
||||
## Version policy
|
||||
|
|
@ -16,10 +16,10 @@ documentation page.
|
|||
Always answer for OpenCode V2 unless the user explicitly asks about V1,
|
||||
legacy OpenCode, or migrating from V1.
|
||||
|
||||
Use only <https://v2.opencode.ai/docs> documentation as the source of truth for V2.
|
||||
Use only <https://opencode.ai/v2/docs/> documentation as the source of truth for V2.
|
||||
Do not use <https://opencode.ai/docs/>, which documents V1, and do not use
|
||||
general web search to resolve a V2 documentation question when the V2 docs or
|
||||
their `llms.txt` index cover it. The schema served from
|
||||
linked pages cover it. The schema served from
|
||||
<https://opencode.ai/config.json> may describe V1 even though V2 configuration
|
||||
files include that URL for editor integration. Never use it to infer V2 field
|
||||
names or shapes. If V2 documentation is missing or contradictory, state the
|
||||
|
|
@ -29,7 +29,7 @@ V1 documentation and syntax may be consulted only when the user explicitly
|
|||
asks about V1 or when needed as migration input. Outputs and recommendations
|
||||
must still use V2 unless the user specifically requests a V1 result.
|
||||
|
||||
## [Configuration](https://v2.opencode.ai/docs/config)
|
||||
## [Configuration](https://opencode.ai/v2/docs/config)
|
||||
|
||||
OpenCode configuration uses JSON or JSONC. Include the published schema so the
|
||||
user's editor can validate fields and provide autocomplete:
|
||||
|
|
@ -60,14 +60,14 @@ linked topic guide as the source of truth, and preserve unrelated settings when
|
|||
editing an existing file. Keep the published `$schema` URL in configuration
|
||||
examples, but do not fetch it to determine the V2 configuration shape.
|
||||
|
||||
See the [full configuration guide](https://v2.opencode.ai/docs/config) for
|
||||
See the [full configuration guide](https://opencode.ai/v2/docs/config) for
|
||||
every field, examples, config locations, and links to dedicated feature guides.
|
||||
|
||||
## [V1 to V2 migration](https://v2.opencode.ai/docs/migrate-v1)
|
||||
## [V1 to V2 migration](https://opencode.ai/v2/docs/migrate-v1)
|
||||
|
||||
For any request to migrate OpenCode configuration, agents, commands, skills,
|
||||
plugins, integrations, or other behavior from V1 to V2, read the full
|
||||
[migration guide](https://v2.opencode.ai/docs/migrate-v1) before acting. In
|
||||
[migration guide](https://opencode.ai/v2/docs/migrate-v1) before acting. In
|
||||
the repository, its source is `packages/www/content/docs/(Get started)/migrate-v1.mdx`.
|
||||
|
||||
V1 config files and `.opencode/` definitions are intended to remain compatible.
|
||||
|
|
@ -76,18 +76,18 @@ V2 config uses more ergonomic shapes, but conversion is optional. When the user
|
|||
requests conversion, inspect the complete configuration, preserve behavior and
|
||||
unrelated settings, and apply only the relevant migrations from the guide. For
|
||||
plugin migrations, fetch and follow both the migration guide and the full
|
||||
[plugins guide](https://v2.opencode.ai/docs/build/plugins). If non-API V1
|
||||
[plugins guide](https://opencode.ai/v2/docs/build/plugins). If non-API V1
|
||||
functionality fails in V2, use the `report` skill to file it as a compatibility
|
||||
bug.
|
||||
|
||||
## [Plugins](https://v2.opencode.ai/docs/build/plugins)
|
||||
## [Plugins](https://opencode.ai/v2/docs/build/plugins)
|
||||
|
||||
For questions about creating, configuring, loading, publishing, or migrating
|
||||
plugins, fetch the full [plugins guide](https://v2.opencode.ai/docs/build/plugins)
|
||||
plugins, fetch the full [plugins guide](https://opencode.ai/v2/docs/build/plugins)
|
||||
before answering. This includes questions about the Effect plugin API, hooks,
|
||||
transforms, tools, plugin context capabilities, and package entrypoints.
|
||||
|
||||
## [Service](https://v2.opencode.ai/docs/troubleshooting#check-the-background-service)
|
||||
## [Service](https://opencode.ai/v2/docs/troubleshooting#check-the-background-service)
|
||||
|
||||
OpenCode uses a client-server architecture. Interfaces such as the TUI connect
|
||||
to a background OpenCode service, which owns sessions, configuration, plugins,
|
||||
|
|
@ -106,7 +106,7 @@ Check its status after restarting:
|
|||
opencode2 service status
|
||||
```
|
||||
|
||||
## [API](https://v2.opencode.ai/docs/api)
|
||||
## [API](https://opencode.ai/v2/docs/api)
|
||||
|
||||
OpenCode exposes an HTTP API from its server. The API is described by an
|
||||
OpenAPI document available from the running server at `/openapi.json`.
|
||||
|
|
@ -135,15 +135,15 @@ connected to an explicit server instead of its managed background service, use
|
|||
the same configured server and authentication context rather than constructing
|
||||
an unauthenticated request separately.
|
||||
|
||||
See the [full API reference](https://v2.opencode.ai/docs/api) for available
|
||||
See the [full API reference](https://opencode.ai/v2/docs/api) for available
|
||||
endpoints, parameters, request bodies, and response schemas. The
|
||||
raw [OpenAPI specification](https://v2.opencode.ai/openapi.json) is also
|
||||
raw [OpenAPI specification](https://opencode.ai/v2/openapi.json) is also
|
||||
available for code generation and other tooling.
|
||||
|
||||
## [Client](https://v2.opencode.ai/docs/build/client)
|
||||
## [Client](https://opencode.ai/v2/docs/build/client)
|
||||
|
||||
For questions about connecting an application to OpenCode over the network,
|
||||
fetch the full [client guide](https://v2.opencode.ai/docs/build/client) before
|
||||
fetch the full [client guide](https://opencode.ai/v2/docs/build/client) before
|
||||
answering.
|
||||
|
||||
`@opencode-ai/client` is the generated TypeScript client for the OpenCode HTTP
|
||||
|
|
@ -154,7 +154,7 @@ exposes typed Effects, Streams, and decoded OpenCode schema values. Its
|
|||
`Service` API can discover, start, stop, and authenticate with the local
|
||||
background service from a Node application.
|
||||
|
||||
## [Troubleshooting](https://v2.opencode.ai/docs/troubleshooting)
|
||||
## [Troubleshooting](https://opencode.ai/v2/docs/troubleshooting)
|
||||
|
||||
OpenCode runs a client and a background server. Start by determining whether a
|
||||
problem belongs to the client, the shared server, or one project.
|
||||
|
|
@ -174,6 +174,6 @@ problem belongs to the client, the shared server, or one project.
|
|||
- Redact API keys, authorization headers, prompts, file contents, and other
|
||||
sensitive data before sharing diagnostics.
|
||||
|
||||
See the [full troubleshooting guide](https://v2.opencode.ai/docs/troubleshooting)
|
||||
See the [full troubleshooting guide](https://opencode.ai/v2/docs/troubleshooting)
|
||||
for service lifecycle commands, API inspection, log locations, explicit server
|
||||
connections, issue-reporting details, and local development paths.
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
export * as PluginSupervisor from "./supervisor"
|
||||
|
||||
import type { Plugin } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { Event } from "@opencode-ai/schema/config"
|
||||
import { Context, Deferred, Effect, Layer, Option, Schema, Semaphore, Stream } from "effect"
|
||||
import path from "path"
|
||||
import { fileURLToPath, pathToFileURL } from "url"
|
||||
import { AgentV2 } from "../agent"
|
||||
import { Agent } from "../agent"
|
||||
import { Catalog } from "../catalog"
|
||||
import { CommandV2 } from "../command"
|
||||
import { Command } from "../command"
|
||||
import { Config } from "../config"
|
||||
import { ConfigPlugin } from "../config/plugin"
|
||||
import { makeLocationNode } from "@opencode-ai/util/effect/app-node"
|
||||
import { httpClient } from "@opencode-ai/util/effect/app-node-platform"
|
||||
import { EventV2 } from "../event"
|
||||
import { Bus } from "../bus"
|
||||
import { FileMutation } from "../file-mutation"
|
||||
import { FileSystem } from "../filesystem"
|
||||
import { Form } from "../form"
|
||||
|
|
@ -25,16 +24,16 @@ import { Location } from "../location"
|
|||
import { LocationMutation } from "../location-mutation"
|
||||
import { ModelsDev } from "../models-dev"
|
||||
import { Npm } from "@opencode-ai/util/npm"
|
||||
import { PermissionV2 } from "../permission"
|
||||
import { PluginV2 } from "../plugin"
|
||||
import { Permission } from "../permission"
|
||||
import { Plugin } from "../plugin"
|
||||
import { PluginPromise } from "../plugin/promise"
|
||||
import { Reference } from "../reference"
|
||||
import { Ripgrep } from "../ripgrep"
|
||||
import { SessionInstructions } from "../session/instructions"
|
||||
import { Shell } from "../shell"
|
||||
import { SkillV2 } from "../skill"
|
||||
import { Skill } from "../skill"
|
||||
import { ReadToolFileSystem } from "../tool/read-filesystem"
|
||||
import { ToolRegistry } from "../tool/registry"
|
||||
import { Tool } from "../tool"
|
||||
import { WebSearch } from "../websearch"
|
||||
import { WellKnown } from "../wellknown"
|
||||
import { PluginInternal } from "./internal"
|
||||
|
|
@ -46,7 +45,9 @@ const PluginModule = Schema.Struct({
|
|||
default: Schema.Union([
|
||||
Schema.Struct({
|
||||
id: Schema.String,
|
||||
effect: Schema.declare<Plugin["effect"]>((input): input is Plugin["effect"] => typeof input === "function"),
|
||||
effect: Schema.declare<import("@opencode-ai/plugin/effect/plugin").Plugin["effect"]>(
|
||||
(input): input is import("@opencode-ai/plugin/effect/plugin").Plugin["effect"] => typeof input === "function",
|
||||
),
|
||||
}),
|
||||
Schema.Struct({
|
||||
id: Schema.String,
|
||||
|
|
@ -113,15 +114,15 @@ const scan = Effect.fn("PluginSupervisor.scan")(function* (entries: readonly Con
|
|||
})
|
||||
|
||||
const resolve = Effect.fn("PluginSupervisor.resolve")(function* (
|
||||
pre: readonly PluginV2.Versioned[],
|
||||
post: readonly PluginV2.Versioned[],
|
||||
pre: readonly Plugin.Versioned[],
|
||||
post: readonly Plugin.Versioned[],
|
||||
operations: readonly Operation[],
|
||||
) {
|
||||
const matches = (selector: string, target: string) =>
|
||||
selector === "*" || (selector.endsWith(".*") ? target.startsWith(selector.slice(0, -1)) : selector === target)
|
||||
const definitions = [...pre, ...post]
|
||||
const enabled = new Set(definitions.map((plugin) => plugin.id))
|
||||
const packages = new Map<string, PluginV2.Versioned>()
|
||||
const packages = new Map<string, Plugin.Versioned>()
|
||||
const plugins = () => [...definitions, ...packages.values()]
|
||||
|
||||
for (const operation of operations) {
|
||||
|
|
@ -183,7 +184,7 @@ const load = Effect.fn("PluginSupervisor.load")(function* (operation: Extract<Op
|
|||
id: plugin.id,
|
||||
version: JSON.stringify(operation),
|
||||
effect: (host) => plugin.effect({ ...host, options: operation.options }),
|
||||
} satisfies PluginV2.Versioned
|
||||
} satisfies Plugin.Versioned
|
||||
})
|
||||
|
||||
function discoverDirectory(fs: FSUtil.Interface, directory: string) {
|
||||
|
|
@ -211,10 +212,10 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/Pl
|
|||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const registry = yield* PluginV2.Service
|
||||
const registry = yield* Plugin.Service
|
||||
const sdk = yield* SdkPlugins.Service
|
||||
const config = yield* Config.Service
|
||||
const events = yield* EventV2.Service
|
||||
const bus = yield* Bus.Service
|
||||
const lock = Semaphore.makeUnsafe(1)
|
||||
const ready = yield* Deferred.make<void>()
|
||||
let observed = 0
|
||||
|
|
@ -238,7 +239,7 @@ const layer = Layer.effect(
|
|||
}),
|
||||
)
|
||||
})
|
||||
const updates = yield* events
|
||||
const updates = yield* bus
|
||||
.subscribe([Event.Updated, SdkPlugins.Updated])
|
||||
.pipe(Stream.toQueue({ capacity: 1, strategy: "sliding" }))
|
||||
const signals = yield* Stream.concat(
|
||||
|
|
@ -276,13 +277,13 @@ export const node = makeLocationNode({
|
|||
service: Service,
|
||||
layer: nodeLayer,
|
||||
deps: [
|
||||
PluginV2.node,
|
||||
Plugin.node,
|
||||
SdkPlugins.node,
|
||||
AgentV2.node,
|
||||
Agent.node,
|
||||
Catalog.node,
|
||||
CommandV2.node,
|
||||
Command.node,
|
||||
Config.node,
|
||||
EventV2.node,
|
||||
Bus.node,
|
||||
FileMutation.node,
|
||||
FileSystem.node,
|
||||
FSUtil.node,
|
||||
|
|
@ -295,7 +296,7 @@ export const node = makeLocationNode({
|
|||
LocationMutation.node,
|
||||
ModelsDev.node,
|
||||
Npm.node,
|
||||
PermissionV2.node,
|
||||
Permission.node,
|
||||
PluginRuntime.node,
|
||||
Form.node,
|
||||
ReadToolFileSystem.node,
|
||||
|
|
@ -303,8 +304,8 @@ export const node = makeLocationNode({
|
|||
Ripgrep.node,
|
||||
SessionInstructions.node,
|
||||
Shell.node,
|
||||
SkillV2.node,
|
||||
ToolRegistry.toolsNode,
|
||||
Skill.node,
|
||||
Tool.node,
|
||||
WebSearch.node,
|
||||
WellKnown.node,
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
export * as SystemPromptPlugin from "./system-prompt"
|
||||
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
import { Effect } from "effect"
|
||||
|
||||
import PROMPT_ANTHROPIC from "./system-prompt/anthropic.txt"
|
||||
|
|
@ -33,14 +33,16 @@ function make(id: string, select: (modelID: string) => string | undefined) {
|
|||
effect: Effect.fn(`SystemPromptPlugin.${id}`)(function* (ctx) {
|
||||
yield* ctx.session.hook("context", (event) =>
|
||||
Effect.gen(function* () {
|
||||
if ((yield* ctx.agent.get(event.agent))?.system) return
|
||||
if ((yield* ctx.agent.get({ agentID: event.agent })).data.system) return
|
||||
const system = event.system[0]
|
||||
if (!system) return
|
||||
const model = yield* ctx.catalog.model.get(event.model.providerID, event.model.id)
|
||||
const model = (yield* ctx.catalog.model.list()).data.find(
|
||||
(model) => model.providerID === event.model.providerID && model.id === event.model.id,
|
||||
)
|
||||
const prompt = select(`${model?.modelID ?? event.model.id} ${model?.family ?? ""}`.toLowerCase())
|
||||
if (!prompt) return
|
||||
event.system[0] = { ...system, text: prompt }
|
||||
}),
|
||||
}).pipe(Effect.catch(() => Effect.void)),
|
||||
)
|
||||
}),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
export * as VariantPlugin from "./variant"
|
||||
|
||||
import { Effect } from "effect"
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { ModelV2 } from "../model"
|
||||
import { ProviderV2 } from "../provider"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
import { Model } from "../model"
|
||||
import { Provider } from "../provider"
|
||||
|
||||
export const Plugin = define({
|
||||
id: "opencode.variant",
|
||||
|
|
@ -32,13 +32,13 @@ export const Plugin = define({
|
|||
export function generate(
|
||||
model: { readonly id: string; readonly modelID?: string; readonly package?: string },
|
||||
provider?: { readonly package: string },
|
||||
): NonNullable<ModelV2.Info["variants"]> {
|
||||
): NonNullable<Model.Info["variants"]> {
|
||||
const packageName = model.package ?? provider?.package
|
||||
if (!ProviderV2.isAISDK(packageName) || ProviderV2.packageName(packageName) !== "@ai-sdk/openai-compatible") return []
|
||||
if (!Provider.isAISDK(packageName) || Provider.packageName(packageName) !== "@ai-sdk/openai-compatible") return []
|
||||
const ids = `${model.id} ${model.modelID ?? ""}`.toLowerCase()
|
||||
if (!["glm-5.2", "glm-5-2", "glm-5p2"].some((name) => ids.includes(name))) return []
|
||||
return ["high", "max"].map((id) => ({
|
||||
id: ModelV2.VariantID.make(id),
|
||||
id: Model.VariantID.make(id),
|
||||
settings: { reasoningEffort: id },
|
||||
}))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
export * as WarmingPlugin from "./warming"
|
||||
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
import { Clock, Duration, Effect, Scope } from "effect"
|
||||
import { Config } from "../config"
|
||||
import { SessionSchema } from "../session/schema"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
export * as WebSearchExa from "./exa"
|
||||
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
import { Effect, Schema, Scope } from "effect"
|
||||
import { HttpClient } from "effect/unstable/http"
|
||||
import { WebSearchMcp } from "./mcp"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
export * as WebSearchParallel from "./parallel"
|
||||
|
||||
import { define } from "@opencode-ai/plugin/v2/effect/plugin"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
import { Effect, Schema, Scope } from "effect"
|
||||
import { HttpClient } from "effect/unstable/http"
|
||||
import { App } from "../../app"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue