From ab3c22b77add395c4b63608c956394162d1ae0d6 Mon Sep 17 00:00:00 2001 From: Zack Jackson <25274700+ScriptedAlchemy@users.noreply.github.com> Date: Tue, 9 Sep 2025 04:25:04 +0800 Subject: [PATCH] feat: add dynamic tool registration for plugins and external services (#2420) --- packages/opencode/src/plugin/index.ts | 8 + packages/opencode/src/server/server.ts | 118 ++++++++ packages/opencode/src/tool/registry.ts | 104 ++++++- packages/opencode/test/tool/register.test.ts | 299 +++++++++++++++++++ packages/plugin/src/index.ts | 37 +++ packages/sdk/js/src/gen/sdk.gen.ts | 46 +++ packages/sdk/js/src/gen/types.gen.ts | 127 +++++++- 7 files changed, 727 insertions(+), 12 deletions(-) create mode 100644 packages/opencode/test/tool/register.test.ts diff --git a/packages/opencode/src/plugin/index.ts b/packages/opencode/src/plugin/index.ts index a9e9803f..0f0689b5 100644 --- a/packages/opencode/src/plugin/index.ts +++ b/packages/opencode/src/plugin/index.ts @@ -7,6 +7,7 @@ import { Server } from "../server/server" import { BunProc } from "../bun" import { Instance } from "../project/instance" import { Flag } from "../flag/flag" +import { ToolRegistry } from "../tool/registry" export namespace Plugin { const log = Log.create({ service: "plugin" }) @@ -24,6 +25,8 @@ export namespace Plugin { worktree: Instance.worktree, directory: Instance.directory, $: Bun.$, + Tool: await import("../tool/tool").then(m => m.Tool), + z: await import("zod").then(m => m.z), } const plugins = [...(config.plugin ?? [])] if (!Flag.OPENCODE_DISABLE_DEFAULT_PLUGINS) { @@ -75,6 +78,11 @@ export namespace Plugin { const config = await Config.get() for (const hook of hooks) { await hook.config?.(config) + // Let plugins register tools at startup + await hook["tool.register"]?.({}, { + registerHTTP: ToolRegistry.registerHTTP, + register: ToolRegistry.register + }) } Bus.subscribeAll(async (input) => { const hooks = await state().then((x) => x.hooks) diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 9091f050..108a67ec 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -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({ diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index e7938057..f1211bf9 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -12,9 +12,11 @@ import { WebFetchTool } from "./webfetch" import { WriteTool } from "./write" import { InvalidTool } from "./invalid" import type { Agent } from "../agent/agent" +import { Tool } from "./tool" export namespace ToolRegistry { - const ALL = [ + // Built-in tools that ship with opencode + const BUILTIN = [ InvalidTool, BashTool, EditTool, @@ -30,13 +32,103 @@ export namespace ToolRegistry { TaskTool, ] + // Extra tools registered at runtime (via plugins) + const EXTRA: Tool.Info[] = [] + + // Tools registered via HTTP callback (via SDK/API) + const HTTP: Tool.Info[] = [] + + export type HttpParamSpec = { + type: "string" | "number" | "boolean" | "array" + description?: string + optional?: boolean + items?: "string" | "number" | "boolean" + } + export type HttpToolRegistration = { + id: string + description: string + parameters: { + type: "object" + properties: Record + } + callbackUrl: string + headers?: Record + } + + function buildZodFromHttpSpec(spec: HttpToolRegistration["parameters"]) { + const shape: Record = {} + for (const [key, val] of Object.entries(spec.properties)) { + let base: z.ZodTypeAny + switch (val.type) { + case "string": + base = z.string() + break + case "number": + base = z.number() + break + case "boolean": + base = z.boolean() + break + case "array": + if (!val.items) throw new Error(`array spec for ${key} requires 'items'`) + base = z.array( + val.items === "string" ? z.string() : val.items === "number" ? z.number() : z.boolean(), + ) + break + default: + base = z.any() + } + if (val.description) base = base.describe(val.description) + shape[key] = val.optional ? base.optional() : base + } + return z.object(shape) + } + + export function register(tool: Tool.Info) { + // Prevent duplicates by id (replace existing) + const idx = EXTRA.findIndex((t) => t.id === tool.id) + if (idx >= 0) EXTRA.splice(idx, 1, tool) + else EXTRA.push(tool) + } + + export function registerHTTP(input: HttpToolRegistration) { + const parameters = buildZodFromHttpSpec(input.parameters) + const info = Tool.define(input.id, { + description: input.description, + parameters, + async execute(args) { + const res = await fetch(input.callbackUrl, { + method: "POST", + headers: { "content-type": "application/json", ...(input.headers ?? {}) }, + body: JSON.stringify({ args }), + }) + if (!res.ok) { + throw new Error(`HTTP tool callback failed: ${res.status} ${await res.text()}`) + } + const json = (await res.json()) as { title?: string; output: string; metadata?: Record } + return { + title: json.title ?? input.id, + output: json.output ?? "", + metadata: (json.metadata ?? {}) as any, + } + }, + }) + const idx = HTTP.findIndex((t) => t.id === info.id) + if (idx >= 0) HTTP.splice(idx, 1, info) + else HTTP.push(info) + } + + function allTools(): Tool.Info[] { + return [...BUILTIN, ...EXTRA, ...HTTP] + } + export function ids() { - return ALL.map((t) => t.id) + return allTools().map((t) => t.id) } export async function tools(providerID: string, _modelID: string) { const result = await Promise.all( - ALL.map(async (t) => ({ + allTools().map(async (t) => ({ id: t.id, ...(await t.init()), })), @@ -45,21 +137,21 @@ export namespace ToolRegistry { if (providerID === "openai") { return result.map((t) => ({ ...t, - parameters: optionalToNullable(t.parameters), + parameters: optionalToNullable(t.parameters as unknown as z.ZodTypeAny), })) } if (providerID === "azure") { return result.map((t) => ({ ...t, - parameters: optionalToNullable(t.parameters), + parameters: optionalToNullable(t.parameters as unknown as z.ZodTypeAny), })) } if (providerID === "google") { return result.map((t) => ({ ...t, - parameters: sanitizeGeminiParameters(t.parameters), + parameters: sanitizeGeminiParameters(t.parameters as unknown as z.ZodTypeAny), })) } diff --git a/packages/opencode/test/tool/register.test.ts b/packages/opencode/test/tool/register.test.ts new file mode 100644 index 00000000..2a72c119 --- /dev/null +++ b/packages/opencode/test/tool/register.test.ts @@ -0,0 +1,299 @@ +import "zod-openapi/extend" +import { describe, expect, test } from "bun:test" +import path from "path" +import os from "os" +import { Instance } from "../../src/project/instance" + +// Helper to create a Request targeting the in-memory Hono app +function makeRequest(method: string, url: string, body?: any) { + const headers: Record = { "content-type": "application/json" } + const init: RequestInit = { method, headers } + if (body !== undefined) init.body = JSON.stringify(body) + return new Request(url, init) +} + +describe("HTTP tool registration API", () => { + test("POST /tool/register then list via /tool/ids and /tool", async () => { + const projectRoot = path.join(__dirname, "../..") + await Instance.provide(projectRoot, async () => { + const { Server } = await import("../../src/server/server") + + const toolSpec = { + id: "http-echo", + description: "Simple echo tool (test-only)", + parameters: { + type: "object" as const, + properties: { + foo: { type: "string" as const, optional: true }, + bar: { type: "number" as const }, + }, + }, + callbackUrl: "http://localhost:9999/echo", + } + + // Register + const registerRes = await Server.App.fetch( + makeRequest("POST", "http://localhost:4096/experimental/tool/register", toolSpec), + ) + expect(registerRes.status).toBe(200) + const ok = await registerRes.json() + expect(ok).toBe(true) + + // IDs should include the new tool + const idsRes = await Server.App.fetch(makeRequest("GET", "http://localhost:4096/experimental/tool/ids")) + expect(idsRes.status).toBe(200) + const ids = (await idsRes.json()) as string[] + expect(ids).toContain("http-echo") + + // List tools for a provider/model and check JSON Schema shape + const listRes = await Server.App.fetch( + makeRequest("GET", "http://localhost:4096/experimental/tool?provider=openai&model=gpt-4o"), + ) + expect(listRes.status).toBe(200) + const list = (await listRes.json()) as Array<{ id: string; description: string; parameters: any }> + const found = list.find((t) => t.id === "http-echo") + expect(found).toBeTruthy() + expect(found!.description).toBe("Simple echo tool (test-only)") + + // Basic JSON Schema checks + expect(found!.parameters?.type).toBe("object") + expect(found!.parameters?.properties?.bar?.type).toBe("number") + + const foo = found!.parameters?.properties?.foo + // optional -> nullable for OpenAI/Azure providers; accept either type array including null or nullable: true + const fooIsNullable = Array.isArray(foo?.type) ? foo.type.includes("null") : foo?.nullable === true + expect(fooIsNullable).toBe(true) + }) + }) +}) + +describe("Plugin tool.register hook", () => { + test("Plugin registers tool during Plugin.init()", async () => { + // Create a temporary project directory with opencode.json that points to our plugin + const tmpDir = path.join(os.tmpdir(), `opencode-test-project-${Date.now()}`) + await Bun.$`mkdir -p ${tmpDir}` + + const tmpPluginPath = path.join(tmpDir, `test-plugin-${Date.now()}.ts`) + const pluginCode = ` + export async function TestPlugin() { + return { + async ["tool.register"](_input, { registerHTTP }) { + registerHTTP({ + id: "from-plugin", + description: "Registered from test plugin", + parameters: { type: "object", properties: { name: { type: "string", optional: true } } }, + callbackUrl: "http://localhost:9999/echo" + }) + } + } + } + ` + await Bun.write(tmpPluginPath, pluginCode) + + const configPath = path.join(tmpDir, "opencode.json") + await Bun.write(configPath, JSON.stringify({ plugin: ["file://" + tmpPluginPath] }, null, 2)) + + await Instance.provide(tmpDir, async () => { + const { Plugin } = await import("../../src/plugin") + const { ToolRegistry } = await import("../../src/tool/registry") + const { Server } = await import("../../src/server/server") + + // Initialize plugins (will invoke our tool.register hook) + await Plugin.init() + + // Confirm the tool is registered + const allIDs = ToolRegistry.ids() + expect(allIDs).toContain("from-plugin") + + // Also verify via the HTTP surface + const idsRes = await Server.App.fetch(makeRequest("GET", "http://localhost:4096/experimental/tool/ids")) + expect(idsRes.status).toBe(200) + const ids = (await idsRes.json()) as string[] + expect(ids).toContain("from-plugin") + }) + }) +}) + +test("Multiple plugins can each register tools", async () => { + const tmpDir = path.join(os.tmpdir(), `opencode-test-project-multi-${Date.now()}`) + await Bun.$`mkdir -p ${tmpDir}` + + // Create two plugin files + const pluginAPath = path.join(tmpDir, `plugin-a-${Date.now()}.ts`) + const pluginBPath = path.join(tmpDir, `plugin-b-${Date.now()}.ts`) + const pluginA = ` + export async function PluginA() { + return { + async ["tool.register"](_input, { registerHTTP }) { + registerHTTP({ + id: "alpha-tool", + description: "Alpha tool", + parameters: { type: "object", properties: { a: { type: "string", optional: true } } }, + callbackUrl: "http://localhost:9999/echo" + }) + } + } + } + ` + const pluginB = ` + export async function PluginB() { + return { + async ["tool.register"](_input, { registerHTTP }) { + registerHTTP({ + id: "beta-tool", + description: "Beta tool", + parameters: { type: "object", properties: { b: { type: "number", optional: true } } }, + callbackUrl: "http://localhost:9999/echo" + }) + } + } + } + ` + await Bun.write(pluginAPath, pluginA) + await Bun.write(pluginBPath, pluginB) + + // Config with both plugins + await Bun.write( + path.join(tmpDir, "opencode.json"), + JSON.stringify({ plugin: ["file://" + pluginAPath, "file://" + pluginBPath] }, null, 2), + ) + + await Instance.provide(tmpDir, async () => { + const { Plugin } = await import("../../src/plugin") + const { ToolRegistry } = await import("../../src/tool/registry") + const { Server } = await import("../../src/server/server") + + await Plugin.init() + + const ids = ToolRegistry.ids() + expect(ids).toContain("alpha-tool") + expect(ids).toContain("beta-tool") + + const res = await Server.App.fetch(new Request("http://localhost:4096/experimental/tool/ids")) + expect(res.status).toBe(200) + const httpIds = (await res.json()) as string[] + expect(httpIds).toContain("alpha-tool") + expect(httpIds).toContain("beta-tool") + }) +}) + +test("Plugin registers native/local tool with function execution", async () => { + const tmpDir = path.join(os.tmpdir(), `opencode-test-project-native-${Date.now()}`) + await Bun.$`mkdir -p ${tmpDir}` + + const pluginPath = path.join(tmpDir, `plugin-native-${Date.now()}.ts`) + const pluginCode = ` + export async function NativeToolPlugin({ $, Tool, z }) { + // Use z (zod) provided by the plugin system + + // Define a native tool using Tool.define from plugin input + const MyNativeTool = Tool.define("my-native-tool", { + description: "A native tool that runs local code", + parameters: z.object({ + message: z.string().describe("Message to process"), + count: z.number().optional().describe("Repeat count").default(1) + }), + async execute(args, ctx) { + // This runs locally in the plugin process, not via HTTP! + const result = args.message.repeat(args.count) + const output = \`Processed: \${result}\` + + // Can also run shell commands directly + const hostname = await $\`hostname\`.text() + + return { + title: "Native Tool Result", + output: output + " on " + hostname.trim(), + metadata: { processedAt: new Date().toISOString() } + } + } + }) + + return { + async ["tool.register"](_input, { register, registerHTTP }) { + // Register our native tool + register(MyNativeTool) + + // Can also register HTTP tools in the same plugin + registerHTTP({ + id: "http-tool-from-same-plugin", + description: "HTTP tool alongside native tool", + parameters: { type: "object", properties: {} }, + callbackUrl: "http://localhost:9999/echo" + }) + } + } + } + ` + await Bun.write(pluginPath, pluginCode) + + await Bun.write(path.join(tmpDir, "opencode.json"), JSON.stringify({ plugin: ["file://" + pluginPath] }, null, 2)) + + await Instance.provide(tmpDir, async () => { + const { Plugin } = await import("../../src/plugin") + const { ToolRegistry } = await import("../../src/tool/registry") + const { Server } = await import("../../src/server/server") + + await Plugin.init() + + // Both tools should be registered + const ids = ToolRegistry.ids() + expect(ids).toContain("my-native-tool") + expect(ids).toContain("http-tool-from-same-plugin") + + // Verify via HTTP endpoint + const res = await Server.App.fetch(new Request("http://localhost:4096/experimental/tool/ids")) + expect(res.status).toBe(200) + const httpIds = (await res.json()) as string[] + expect(httpIds).toContain("my-native-tool") + expect(httpIds).toContain("http-tool-from-same-plugin") + + // Get tool details to verify native tool has proper structure + const toolsRes = await Server.App.fetch( + new Request("http://localhost:4096/experimental/tool?provider=anthropic&model=claude"), + ) + expect(toolsRes.status).toBe(200) + const tools = (await toolsRes.json()) as any[] + const nativeTool = tools.find((t) => t.id === "my-native-tool") + expect(nativeTool).toBeTruthy() + expect(nativeTool.description).toBe("A native tool that runs local code") + expect(nativeTool.parameters.properties.message).toBeTruthy() + expect(nativeTool.parameters.properties.count).toBeTruthy() + }) +}) + +// Malformed plugin (no tool.register) should not throw and should not register anything +test("Plugin without tool.register is handled gracefully", async () => { + const tmpDir = path.join(os.tmpdir(), `opencode-test-project-noreg-${Date.now()}`) + await Bun.$`mkdir -p ${tmpDir}` + + const pluginPath = path.join(tmpDir, `plugin-noreg-${Date.now()}.ts`) + const pluginSrc = ` + export async function NoRegisterPlugin() { + return { + // no tool.register hook provided + async config(_cfg) { /* noop */ } + } + } + ` + await Bun.write(pluginPath, pluginSrc) + + await Bun.write(path.join(tmpDir, "opencode.json"), JSON.stringify({ plugin: ["file://" + pluginPath] }, null, 2)) + + await Instance.provide(tmpDir, async () => { + const { Plugin } = await import("../../src/plugin") + const { ToolRegistry } = await import("../../src/tool/registry") + const { Server } = await import("../../src/server/server") + + await Plugin.init() + + // Ensure our specific id isn't present + const ids = ToolRegistry.ids() + expect(ids).not.toContain("malformed-tool") + + const res = await Server.App.fetch(new Request("http://localhost:4096/experimental/tool/ids")) + expect(res.status).toBe(200) + const httpIds = (await res.json()) as string[] + expect(httpIds).not.toContain("malformed-tool") + }) +}) diff --git a/packages/plugin/src/index.ts b/packages/plugin/src/index.ts index a00b48d1..fca176f5 100644 --- a/packages/plugin/src/index.ts +++ b/packages/plugin/src/index.ts @@ -18,9 +18,34 @@ export type PluginInput = { directory: string worktree: string $: BunShell + Tool: { + define( + id: string, + init: any | (() => Promise) + ): any + } + z: any // Zod instance for creating schemas } export type Plugin = (input: PluginInput) => Promise +// Lightweight schema spec for HTTP-registered tools +export type HttpParamSpec = { + type: "string" | "number" | "boolean" | "array" + description?: string + optional?: boolean + items?: "string" | "number" | "boolean" +} +export type HttpToolRegistration = { + id: string + description: string + parameters: { + type: "object" + properties: Record + } + callbackUrl: string + headers?: Record +} + export interface Hooks { event?: (input: { event: Event }) => Promise config?: (input: Config) => Promise @@ -99,4 +124,16 @@ export interface Hooks { metadata: any }, ) => Promise + /** + * Allow plugins to register additional tools with the server. + * Use registerHTTP to add a tool that calls back to your plugin/service. + * Use register to add a native/local tool with direct function execution. + */ + "tool.register"?: ( + input: {}, + output: { + registerHTTP: (tool: HttpToolRegistration) => void | Promise + register: (tool: any) => void | Promise // Tool.Info type from opencode + }, + ) => Promise } diff --git a/packages/sdk/js/src/gen/sdk.gen.ts b/packages/sdk/js/src/gen/sdk.gen.ts index ac6be13d..ea9cfe9a 100644 --- a/packages/sdk/js/src/gen/sdk.gen.ts +++ b/packages/sdk/js/src/gen/sdk.gen.ts @@ -10,6 +10,15 @@ import type { EventSubscribeResponses, ConfigGetData, ConfigGetResponses, + ToolRegisterData, + ToolRegisterResponses, + ToolRegisterErrors, + ToolIdsData, + ToolIdsResponses, + ToolIdsErrors, + ToolListData, + ToolListResponses, + ToolListErrors, PathGetData, PathGetResponses, SessionListData, @@ -178,6 +187,42 @@ class Config extends _HeyApiClient { } } +class Tool extends _HeyApiClient { + /** + * Register a new HTTP callback tool + */ + public register(options?: Options) { + return (options?.client ?? this._client).post({ + url: "/experimental/tool/register", + ...options, + headers: { + "Content-Type": "application/json", + ...options?.headers, + }, + }) + } + + /** + * List all tool IDs (including built-in and dynamically registered) + */ + public ids(options?: Options) { + return (options?.client ?? this._client).get({ + url: "/experimental/tool/ids", + ...options, + }) + } + + /** + * List tools with JSON schema parameters for a provider/model + */ + public list(options: Options) { + return (options.client ?? this._client).get({ + url: "/experimental/tool", + ...options, + }) + } +} + class Path extends _HeyApiClient { /** * Get the current path @@ -649,6 +694,7 @@ export class OpencodeClient extends _HeyApiClient { project = new Project({ client: this._client }) event = new Event({ client: this._client }) config = new Config({ client: this._client }) + tool = new Tool({ client: this._client }) path = new Path({ client: this._client }) session = new Session({ client: this._client }) command = new Command({ client: this._client }) diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts index 8befce1c..2c0376ef 100644 --- a/packages/sdk/js/src/gen/types.gen.ts +++ b/packages/sdk/js/src/gen/types.gen.ts @@ -1053,6 +1053,44 @@ export type McpRemoteConfig = { export type LayoutConfig = "auto" | "stretch" +export type _Error = { + data: { + [key: string]: unknown + } +} + +export type HttpToolRegistration = { + id: string + description: string + parameters: { + type: "object" + properties: { + [key: string]: HttpParamSpec + } + } + callbackUrl: string + headers?: { + [key: string]: string + } +} + +export type HttpParamSpec = { + type: "string" | "number" | "boolean" | "array" + description?: string + optional?: boolean + items?: "string" | "number" | "boolean" +} + +export type ToolIds = Array + +export type ToolList = Array + +export type ToolListItem = { + id: string + description: string + parameters?: unknown +} + export type Path = { state: string config: string @@ -1060,12 +1098,6 @@ export type Path = { directory: string } -export type _Error = { - data: { - [key: string]: unknown - } -} - export type TextPartInput = { id?: string type: "text" @@ -1276,6 +1308,89 @@ export type ConfigGetResponses = { export type ConfigGetResponse = ConfigGetResponses[keyof ConfigGetResponses] +export type ToolRegisterData = { + body?: HttpToolRegistration + path?: never + query?: { + directory?: string + } + url: "/experimental/tool/register" +} + +export type ToolRegisterErrors = { + /** + * Bad request + */ + 400: _Error +} + +export type ToolRegisterError = ToolRegisterErrors[keyof ToolRegisterErrors] + +export type ToolRegisterResponses = { + /** + * Tool registered successfully + */ + 200: boolean +} + +export type ToolRegisterResponse = ToolRegisterResponses[keyof ToolRegisterResponses] + +export type ToolIdsData = { + body?: never + path?: never + query?: { + directory?: string + } + url: "/experimental/tool/ids" +} + +export type ToolIdsErrors = { + /** + * Bad request + */ + 400: _Error +} + +export type ToolIdsError = ToolIdsErrors[keyof ToolIdsErrors] + +export type ToolIdsResponses = { + /** + * Tool IDs + */ + 200: ToolIds +} + +export type ToolIdsResponse = ToolIdsResponses[keyof ToolIdsResponses] + +export type ToolListData = { + body?: never + path?: never + query: { + directory?: string + provider: string + model: string + } + url: "/experimental/tool" +} + +export type ToolListErrors = { + /** + * Bad request + */ + 400: _Error +} + +export type ToolListError = ToolListErrors[keyof ToolListErrors] + +export type ToolListResponses = { + /** + * Tools + */ + 200: ToolList +} + +export type ToolListResponse = ToolListResponses[keyof ToolListResponses] + export type PathGetData = { body?: never path?: never