feat(core): add plugin list endpoint
Adds a plugin.list HTTP endpoint that reports currently loaded plugins, wires it through protocol, server, and generated clients, and merges it into sdk-next's plugin API. Also updates the sample plugin to register a primary agent instead of a subagent for local testing.
This commit is contained in:
parent
f8626865b9
commit
6a91a682e4
13 changed files with 998 additions and 883 deletions
|
|
@ -3,9 +3,9 @@ export default {
|
|||
setup: async (ctx) => {
|
||||
await ctx.agent.transform((agents) => {
|
||||
agents.update("sample-plugin-agent", (agent) => {
|
||||
agent.description = "Example subagent registered by .opencode/plugins/sample-agent.ts"
|
||||
agent.mode = "subagent"
|
||||
agent.prompt = [
|
||||
agent.description = "Example primary agent registered by .opencode/plugins/sample-agent.ts"
|
||||
agent.mode = "primary"
|
||||
agent.system = [
|
||||
"You are the sample plugin agent for this repository.",
|
||||
"Use this agent to verify that local plugin auto-discovery can add agents.",
|
||||
"Keep responses concise and explain which plugin registered you when asked.",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -4,6 +4,8 @@ import type {
|
|||
LocationGetOutput,
|
||||
AgentListInput,
|
||||
AgentListOutput,
|
||||
PluginListInput,
|
||||
PluginListOutput,
|
||||
SessionListInput,
|
||||
SessionListOutput,
|
||||
SessionCreateInput,
|
||||
|
|
@ -310,6 +312,20 @@ export function make(options: ClientOptions) {
|
|||
requestOptions,
|
||||
),
|
||||
},
|
||||
plugin: {
|
||||
list: (input?: PluginListInput, requestOptions?: RequestOptions) =>
|
||||
request<PluginListOutput>(
|
||||
{
|
||||
method: "GET",
|
||||
path: `/api/plugin`,
|
||||
query: { location: input?.["location"] },
|
||||
successStatus: 200,
|
||||
declaredStatuses: [401, 400],
|
||||
empty: false,
|
||||
},
|
||||
requestOptions,
|
||||
),
|
||||
},
|
||||
session: {
|
||||
list: (input?: SessionListInput, requestOptions?: RequestOptions) =>
|
||||
request<SessionListOutput>(
|
||||
|
|
|
|||
|
|
@ -168,6 +168,21 @@ export type AgentListOutput = {
|
|||
}>
|
||||
}
|
||||
|
||||
export type PluginListInput = {
|
||||
readonly location?: {
|
||||
readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined
|
||||
}["location"]
|
||||
}
|
||||
|
||||
export type PluginListOutput = {
|
||||
readonly location: {
|
||||
readonly directory: string
|
||||
readonly workspaceID?: string
|
||||
readonly project: { readonly id: string; readonly directory: string }
|
||||
}
|
||||
readonly data: ReadonlyArray<{ readonly id: string }>
|
||||
}
|
||||
|
||||
export type SessionListInput = {
|
||||
readonly workspace?: {
|
||||
readonly workspace?: string | undefined
|
||||
|
|
|
|||
|
|
@ -21,12 +21,15 @@ import { ToolRegistry } from "./tool/registry"
|
|||
|
||||
export const ID = Plugin.ID
|
||||
export type ID = typeof ID.Type
|
||||
export const Info = Plugin.Info
|
||||
export type Info = Plugin.Info
|
||||
export const Event = Plugin.Event
|
||||
|
||||
export interface Interface {
|
||||
readonly add: (id: ID, effect: PluginDefinition["effect"]) => Effect.Effect<void>
|
||||
readonly remove: (id: ID) => Effect.Effect<void>
|
||||
readonly wait: (id: ID) => Effect.Effect<void>
|
||||
readonly list: () => Effect.Effect<Info[]>
|
||||
}
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Plugin") {}
|
||||
|
|
@ -139,6 +142,9 @@ export const layer = Layer.effect(
|
|||
add,
|
||||
remove,
|
||||
wait,
|
||||
list: Effect.fn("Plugin.list")(function* () {
|
||||
return Array.from(active.keys()).map((id) => ({ id }))
|
||||
}),
|
||||
})
|
||||
host = yield* PluginHost.make(service)
|
||||
return service
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -13,6 +13,7 @@ import { SkillGroup } from "./groups/skill"
|
|||
import { EventGroup, makeEventGroup } from "./groups/event"
|
||||
import type { Definition } from "@opencode-ai/schema/event"
|
||||
import { AgentGroup } from "./groups/agent"
|
||||
import { PluginGroup } from "./groups/plugin"
|
||||
import { HealthGroup } from "./groups/health"
|
||||
import { PtyGroup } from "./groups/pty"
|
||||
import { ShellGroup } from "./groups/shell"
|
||||
|
|
@ -42,6 +43,7 @@ const makeApiFromGroup = <
|
|||
.add(HealthGroup)
|
||||
.add(LocationGroup.middleware(locationMiddleware))
|
||||
.add(AgentGroup.middleware(locationMiddleware))
|
||||
.add(PluginGroup.middleware(locationMiddleware))
|
||||
.add(makeSessionGroup(sessionLocationMiddleware))
|
||||
.add(MessageGroup.middleware(sessionLocationMiddleware))
|
||||
.add(ModelGroup.middleware(locationMiddleware))
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ export const groupNames = {
|
|||
"server.health": "health",
|
||||
"server.location": "location",
|
||||
"server.agent": "agent",
|
||||
"server.plugin": "plugin",
|
||||
"server.session": "session",
|
||||
"server.message": "message",
|
||||
"server.model": "model",
|
||||
|
|
|
|||
27
packages/protocol/src/groups/plugin.ts
Normal file
27
packages/protocol/src/groups/plugin.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { Location } from "@opencode-ai/schema/location"
|
||||
import { Plugin } from "@opencode-ai/schema/plugin"
|
||||
import { Schema } from "effect"
|
||||
import { HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { LocationQuery, locationQueryOpenApi } from "./location"
|
||||
|
||||
export const PluginGroup = HttpApiGroup.make("server.plugin")
|
||||
.add(
|
||||
HttpApiEndpoint.get("plugin.list", "/api/plugin", {
|
||||
query: LocationQuery,
|
||||
success: Location.response(Schema.Array(Plugin.Info)),
|
||||
})
|
||||
.annotateMerge(locationQueryOpenApi)
|
||||
.annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "v2.plugin.list",
|
||||
summary: "List plugins",
|
||||
description: "Retrieve currently loaded plugins.",
|
||||
}),
|
||||
),
|
||||
)
|
||||
.annotateMerge(
|
||||
OpenApi.annotations({
|
||||
title: "plugins",
|
||||
description: "Experimental plugin routes.",
|
||||
}),
|
||||
)
|
||||
|
|
@ -6,6 +6,11 @@ import { define, inventory } from "./event"
|
|||
export const ID = Schema.String.pipe(Schema.brand("Plugin.ID"))
|
||||
export type ID = typeof ID.Type
|
||||
|
||||
export interface Info extends Schema.Schema.Type<typeof Info> {}
|
||||
export const Info = Schema.Struct({
|
||||
id: ID,
|
||||
}).annotate({ identifier: "Plugin.Info" })
|
||||
|
||||
const Added = define({
|
||||
type: "plugin.added",
|
||||
schema: { id: ID },
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ export const create = Effect.fn("OpenCode.create")(function* () {
|
|||
// `PluginContext`, so `ctx.agent.transform` and every other hook behave exactly
|
||||
// as they do for a config-discovered plugin. Define agent profiles here at
|
||||
// startup, then select one per Session with `sessions.create({ agent })`.
|
||||
plugin: plugins.register,
|
||||
plugin: Object.assign(plugins.register, client.plugin),
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { CommandHandler } from "./handlers/command"
|
|||
import { SkillHandler } from "./handlers/skill"
|
||||
import { EventHandler } from "./handlers/event"
|
||||
import { AgentHandler } from "./handlers/agent"
|
||||
import { PluginHandler } from "./handlers/plugin"
|
||||
import { HealthHandler } from "./handlers/health"
|
||||
import { PtyHandler } from "./handlers/pty"
|
||||
import { ShellHandler } from "./handlers/shell"
|
||||
|
|
@ -26,6 +27,7 @@ export const handlers = Layer.mergeAll(
|
|||
HealthHandler,
|
||||
LocationHandler,
|
||||
AgentHandler,
|
||||
PluginHandler,
|
||||
SessionHandler,
|
||||
MessageHandler,
|
||||
ModelHandler,
|
||||
|
|
|
|||
13
packages/server/src/handlers/plugin.ts
Normal file
13
packages/server/src/handlers/plugin.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { PluginV2 } from "@opencode-ai/core/plugin"
|
||||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||
import { Api } from "../api"
|
||||
import { response } from "../location"
|
||||
|
||||
export const PluginHandler = HttpApiBuilder.group(Api, "server.plugin", (handlers) =>
|
||||
handlers.handle("plugin.list", () =>
|
||||
Effect.gen(function* () {
|
||||
return yield* response(PluginV2.Service.use((plugin) => plugin.list()))
|
||||
}),
|
||||
),
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue