feat: add dynamic tool registration for plugins and external services (#2420)

This commit is contained in:
Zack Jackson
2025-09-09 04:25:04 +08:00
committed by GitHub
parent f0f6e9cad7
commit ab3c22b77a
7 changed files with 727 additions and 12 deletions

View File

@@ -23,6 +23,8 @@ import { Auth } from "../auth"
import { Command } from "../command"
import { Global } from "../global"
import { ProjectRoute } from "./project"
import { ToolRegistry } from "../tool/registry"
import { zodToJsonSchema } from "zod-to-json-schema"
const ERRORS = {
400: {
@@ -46,6 +48,29 @@ const ERRORS = {
export namespace Server {
const log = Log.create({ service: "server" })
// Schemas for HTTP tool registration
const HttpParamSpec = z
.object({
type: z.enum(["string", "number", "boolean", "array"]),
description: z.string().optional(),
optional: z.boolean().optional(),
items: z.enum(["string", "number", "boolean"]).optional(),
})
.openapi({ ref: "HttpParamSpec" })
const HttpToolRegistration = z
.object({
id: z.string(),
description: z.string(),
parameters: z.object({
type: z.literal("object"),
properties: z.record(HttpParamSpec),
}),
callbackUrl: z.string(),
headers: z.record(z.string(), z.string()).optional(),
})
.openapi({ ref: "HttpToolRegistration" })
export const Event = {
Connected: Bus.event("server.connected", z.object({})),
}
@@ -166,6 +191,99 @@ export namespace Server {
return c.json(await Config.get())
},
)
.post(
"/experimental/tool/register",
describeRoute({
description: "Register a new HTTP callback tool",
operationId: "tool.register",
responses: {
200: {
description: "Tool registered successfully",
content: {
"application/json": {
schema: resolver(z.boolean()),
},
},
},
...ERRORS,
},
}),
zValidator("json", HttpToolRegistration),
async (c) => {
ToolRegistry.registerHTTP(c.req.valid("json"))
return c.json(true)
},
)
.get(
"/experimental/tool/ids",
describeRoute({
description: "List all tool IDs (including built-in and dynamically registered)",
operationId: "tool.ids",
responses: {
200: {
description: "Tool IDs",
content: {
"application/json": {
schema: resolver(z.array(z.string()).openapi({ ref: "ToolIDs" })),
},
},
},
...ERRORS,
},
}),
async (c) => {
return c.json(ToolRegistry.ids())
},
)
.get(
"/experimental/tool",
describeRoute({
description: "List tools with JSON schema parameters for a provider/model",
operationId: "tool.list",
responses: {
200: {
description: "Tools",
content: {
"application/json": {
schema: resolver(
z
.array(
z
.object({
id: z.string(),
description: z.string(),
parameters: z.any(),
})
.openapi({ ref: "ToolListItem" }),
)
.openapi({ ref: "ToolList" }),
),
},
},
},
...ERRORS,
},
}),
zValidator(
"query",
z.object({
provider: z.string(),
model: z.string(),
}),
),
async (c) => {
const { provider, model } = c.req.valid("query")
const tools = await ToolRegistry.tools(provider, model)
return c.json(
tools.map((t) => ({
id: t.id,
description: t.description,
// Handle both Zod schemas and plain JSON schemas
parameters: (t.parameters as any)?._def ? zodToJsonSchema(t.parameters as any) : t.parameters,
})),
)
},
)
.get(
"/path",
describeRoute({