GitHub Action 1 месяц назад
Родитель
Сommit
6e028ec2dc

+ 124 - 123
packages/opencode/src/server/routes/experimental.ts

@@ -10,147 +10,148 @@ import { zodToJsonSchema } from "zod-to-json-schema"
 import { errors } from "../error"
 import { lazy } from "../../util/lazy"
 
-export const ExperimentalRoutes = lazy(() => new Hono()
-  .get(
-    "/tool/ids",
-    describeRoute({
-      summary: "List tool IDs",
-      description:
-        "Get a list of all available tool IDs, including both built-in tools and dynamically registered tools.",
-      operationId: "tool.ids",
-      responses: {
-        200: {
-          description: "Tool IDs",
-          content: {
-            "application/json": {
-              schema: resolver(z.array(z.string()).meta({ ref: "ToolIDs" })),
+export const ExperimentalRoutes = lazy(() =>
+  new Hono()
+    .get(
+      "/tool/ids",
+      describeRoute({
+        summary: "List tool IDs",
+        description:
+          "Get a list of all available tool IDs, including both built-in tools and dynamically registered tools.",
+        operationId: "tool.ids",
+        responses: {
+          200: {
+            description: "Tool IDs",
+            content: {
+              "application/json": {
+                schema: resolver(z.array(z.string()).meta({ ref: "ToolIDs" })),
+              },
             },
           },
+          ...errors(400),
         },
-        ...errors(400),
+      }),
+      async (c) => {
+        return c.json(await ToolRegistry.ids())
       },
-    }),
-    async (c) => {
-      return c.json(await ToolRegistry.ids())
-    },
-  )
-  .get(
-    "/tool",
-    describeRoute({
-      summary: "List tools",
-      description:
-        "Get a list of available tools with their JSON schema parameters for a specific provider and model combination.",
-      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(),
-                      })
-                      .meta({ ref: "ToolListItem" }),
-                  )
-                  .meta({ ref: "ToolList" }),
-              ),
+    )
+    .get(
+      "/tool",
+      describeRoute({
+        summary: "List tools",
+        description:
+          "Get a list of available tools with their JSON schema parameters for a specific provider and model combination.",
+        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(),
+                        })
+                        .meta({ ref: "ToolListItem" }),
+                    )
+                    .meta({ ref: "ToolList" }),
+                ),
+              },
             },
           },
+          ...errors(400),
         },
-        ...errors(400),
-      },
-    }),
-    validator(
-      "query",
-      z.object({
-        provider: z.string(),
-        model: z.string(),
       }),
-    ),
-    async (c) => {
-      const { provider } = c.req.valid("query")
-      const tools = await ToolRegistry.tools(provider)
-      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,
-        })),
-      )
-    },
-  )
-  .post(
-    "/worktree",
-    describeRoute({
-      summary: "Create worktree",
-      description: "Create a new git worktree for the current project.",
-      operationId: "worktree.create",
-      responses: {
-        200: {
-          description: "Worktree created",
-          content: {
-            "application/json": {
-              schema: resolver(Worktree.Info),
+      validator(
+        "query",
+        z.object({
+          provider: z.string(),
+          model: z.string(),
+        }),
+      ),
+      async (c) => {
+        const { provider } = c.req.valid("query")
+        const tools = await ToolRegistry.tools(provider)
+        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,
+          })),
+        )
+      },
+    )
+    .post(
+      "/worktree",
+      describeRoute({
+        summary: "Create worktree",
+        description: "Create a new git worktree for the current project.",
+        operationId: "worktree.create",
+        responses: {
+          200: {
+            description: "Worktree created",
+            content: {
+              "application/json": {
+                schema: resolver(Worktree.Info),
+              },
             },
           },
+          ...errors(400),
         },
-        ...errors(400),
+      }),
+      validator("json", Worktree.create.schema),
+      async (c) => {
+        const body = c.req.valid("json")
+        const worktree = await Worktree.create(body)
+        return c.json(worktree)
       },
-    }),
-    validator("json", Worktree.create.schema),
-    async (c) => {
-      const body = c.req.valid("json")
-      const worktree = await Worktree.create(body)
-      return c.json(worktree)
-    },
-  )
-  .get(
-    "/worktree",
-    describeRoute({
-      summary: "List worktrees",
-      description: "List all sandbox worktrees for the current project.",
-      operationId: "worktree.list",
-      responses: {
-        200: {
-          description: "List of worktree directories",
-          content: {
-            "application/json": {
-              schema: resolver(z.array(z.string())),
+    )
+    .get(
+      "/worktree",
+      describeRoute({
+        summary: "List worktrees",
+        description: "List all sandbox worktrees for the current project.",
+        operationId: "worktree.list",
+        responses: {
+          200: {
+            description: "List of worktree directories",
+            content: {
+              "application/json": {
+                schema: resolver(z.array(z.string())),
+              },
             },
           },
         },
+      }),
+      async (c) => {
+        const sandboxes = await Project.sandboxes(Instance.project.id)
+        return c.json(sandboxes)
       },
-    }),
-    async (c) => {
-      const sandboxes = await Project.sandboxes(Instance.project.id)
-      return c.json(sandboxes)
-    },
-  )
-  .get(
-    "/resource",
-    describeRoute({
-      summary: "Get MCP resources",
-      description: "Get all available MCP resources from connected servers. Optionally filter by name.",
-      operationId: "experimental.resource.list",
-      responses: {
-        200: {
-          description: "MCP resources",
-          content: {
-            "application/json": {
-              schema: resolver(z.record(z.string(), MCP.Resource)),
+    )
+    .get(
+      "/resource",
+      describeRoute({
+        summary: "Get MCP resources",
+        description: "Get all available MCP resources from connected servers. Optionally filter by name.",
+        operationId: "experimental.resource.list",
+        responses: {
+          200: {
+            description: "MCP resources",
+            content: {
+              "application/json": {
+                schema: resolver(z.record(z.string(), MCP.Resource)),
+              },
             },
           },
         },
+      }),
+      async (c) => {
+        return c.json(await MCP.resources())
       },
-    }),
-    async (c) => {
-      return c.json(await MCP.resources())
-    },
-  )
+    ),
 )

+ 159 - 158
packages/opencode/src/server/routes/file.ts

@@ -7,190 +7,191 @@ import { LSP } from "../../lsp"
 import { Instance } from "../../project/instance"
 import { lazy } from "../../util/lazy"
 
-export const FileRoutes = lazy(() => new Hono()
-  .get(
-    "/find",
-    describeRoute({
-      summary: "Find text",
-      description: "Search for text patterns across files in the project using ripgrep.",
-      operationId: "find.text",
-      responses: {
-        200: {
-          description: "Matches",
-          content: {
-            "application/json": {
-              schema: resolver(Ripgrep.Match.shape.data.array()),
+export const FileRoutes = lazy(() =>
+  new Hono()
+    .get(
+      "/find",
+      describeRoute({
+        summary: "Find text",
+        description: "Search for text patterns across files in the project using ripgrep.",
+        operationId: "find.text",
+        responses: {
+          200: {
+            description: "Matches",
+            content: {
+              "application/json": {
+                schema: resolver(Ripgrep.Match.shape.data.array()),
+              },
             },
           },
         },
-      },
-    }),
-    validator(
-      "query",
-      z.object({
-        pattern: z.string(),
       }),
-    ),
-    async (c) => {
-      const pattern = c.req.valid("query").pattern
-      const result = await Ripgrep.search({
-        cwd: Instance.directory,
-        pattern,
-        limit: 10,
-      })
-      return c.json(result)
-    },
-  )
-  .get(
-    "/find/file",
-    describeRoute({
-      summary: "Find files",
-      description: "Search for files or directories by name or pattern in the project directory.",
-      operationId: "find.files",
-      responses: {
-        200: {
-          description: "File paths",
-          content: {
-            "application/json": {
-              schema: resolver(z.string().array()),
+      validator(
+        "query",
+        z.object({
+          pattern: z.string(),
+        }),
+      ),
+      async (c) => {
+        const pattern = c.req.valid("query").pattern
+        const result = await Ripgrep.search({
+          cwd: Instance.directory,
+          pattern,
+          limit: 10,
+        })
+        return c.json(result)
+      },
+    )
+    .get(
+      "/find/file",
+      describeRoute({
+        summary: "Find files",
+        description: "Search for files or directories by name or pattern in the project directory.",
+        operationId: "find.files",
+        responses: {
+          200: {
+            description: "File paths",
+            content: {
+              "application/json": {
+                schema: resolver(z.string().array()),
+              },
             },
           },
         },
-      },
-    }),
-    validator(
-      "query",
-      z.object({
-        query: z.string(),
-        dirs: z.enum(["true", "false"]).optional(),
-        type: z.enum(["file", "directory"]).optional(),
-        limit: z.coerce.number().int().min(1).max(200).optional(),
       }),
-    ),
-    async (c) => {
-      const query = c.req.valid("query").query
-      const dirs = c.req.valid("query").dirs
-      const type = c.req.valid("query").type
-      const limit = c.req.valid("query").limit
-      const results = await File.search({
-        query,
-        limit: limit ?? 10,
-        dirs: dirs !== "false",
-        type,
-      })
-      return c.json(results)
-    },
-  )
-  .get(
-    "/find/symbol",
-    describeRoute({
-      summary: "Find symbols",
-      description: "Search for workspace symbols like functions, classes, and variables using LSP.",
-      operationId: "find.symbols",
-      responses: {
-        200: {
-          description: "Symbols",
-          content: {
-            "application/json": {
-              schema: resolver(LSP.Symbol.array()),
+      validator(
+        "query",
+        z.object({
+          query: z.string(),
+          dirs: z.enum(["true", "false"]).optional(),
+          type: z.enum(["file", "directory"]).optional(),
+          limit: z.coerce.number().int().min(1).max(200).optional(),
+        }),
+      ),
+      async (c) => {
+        const query = c.req.valid("query").query
+        const dirs = c.req.valid("query").dirs
+        const type = c.req.valid("query").type
+        const limit = c.req.valid("query").limit
+        const results = await File.search({
+          query,
+          limit: limit ?? 10,
+          dirs: dirs !== "false",
+          type,
+        })
+        return c.json(results)
+      },
+    )
+    .get(
+      "/find/symbol",
+      describeRoute({
+        summary: "Find symbols",
+        description: "Search for workspace symbols like functions, classes, and variables using LSP.",
+        operationId: "find.symbols",
+        responses: {
+          200: {
+            description: "Symbols",
+            content: {
+              "application/json": {
+                schema: resolver(LSP.Symbol.array()),
+              },
             },
           },
         },
-      },
-    }),
-    validator(
-      "query",
-      z.object({
-        query: z.string(),
       }),
-    ),
-    async (c) => {
-      /*
+      validator(
+        "query",
+        z.object({
+          query: z.string(),
+        }),
+      ),
+      async (c) => {
+        /*
       const query = c.req.valid("query").query
       const result = await LSP.workspaceSymbol(query)
       return c.json(result)
       */
-      return c.json([])
-    },
-  )
-  .get(
-    "/file",
-    describeRoute({
-      summary: "List files",
-      description: "List files and directories in a specified path.",
-      operationId: "file.list",
-      responses: {
-        200: {
-          description: "Files and directories",
-          content: {
-            "application/json": {
-              schema: resolver(File.Node.array()),
+        return c.json([])
+      },
+    )
+    .get(
+      "/file",
+      describeRoute({
+        summary: "List files",
+        description: "List files and directories in a specified path.",
+        operationId: "file.list",
+        responses: {
+          200: {
+            description: "Files and directories",
+            content: {
+              "application/json": {
+                schema: resolver(File.Node.array()),
+              },
             },
           },
         },
-      },
-    }),
-    validator(
-      "query",
-      z.object({
-        path: z.string(),
       }),
-    ),
-    async (c) => {
-      const path = c.req.valid("query").path
-      const content = await File.list(path)
-      return c.json(content)
-    },
-  )
-  .get(
-    "/file/content",
-    describeRoute({
-      summary: "Read file",
-      description: "Read the content of a specified file.",
-      operationId: "file.read",
-      responses: {
-        200: {
-          description: "File content",
-          content: {
-            "application/json": {
-              schema: resolver(File.Content),
+      validator(
+        "query",
+        z.object({
+          path: z.string(),
+        }),
+      ),
+      async (c) => {
+        const path = c.req.valid("query").path
+        const content = await File.list(path)
+        return c.json(content)
+      },
+    )
+    .get(
+      "/file/content",
+      describeRoute({
+        summary: "Read file",
+        description: "Read the content of a specified file.",
+        operationId: "file.read",
+        responses: {
+          200: {
+            description: "File content",
+            content: {
+              "application/json": {
+                schema: resolver(File.Content),
+              },
             },
           },
         },
-      },
-    }),
-    validator(
-      "query",
-      z.object({
-        path: z.string(),
       }),
-    ),
-    async (c) => {
-      const path = c.req.valid("query").path
-      const content = await File.read(path)
-      return c.json(content)
-    },
-  )
-  .get(
-    "/file/status",
-    describeRoute({
-      summary: "Get file status",
-      description: "Get the git status of all files in the project.",
-      operationId: "file.status",
-      responses: {
-        200: {
-          description: "File status",
-          content: {
-            "application/json": {
-              schema: resolver(File.Info.array()),
+      validator(
+        "query",
+        z.object({
+          path: z.string(),
+        }),
+      ),
+      async (c) => {
+        const path = c.req.valid("query").path
+        const content = await File.read(path)
+        return c.json(content)
+      },
+    )
+    .get(
+      "/file/status",
+      describeRoute({
+        summary: "Get file status",
+        description: "Get the git status of all files in the project.",
+        operationId: "file.status",
+        responses: {
+          200: {
+            description: "File status",
+            content: {
+              "application/json": {
+                schema: resolver(File.Info.array()),
+              },
             },
           },
         },
+      }),
+      async (c) => {
+        const content = await File.status()
+        return c.json(content)
       },
-    }),
-    async (c) => {
-      const content = await File.status()
-      return c.json(content)
-    },
-  )
+    ),
 )

+ 98 - 97
packages/opencode/src/server/routes/global.ts

@@ -13,122 +13,123 @@ const log = Log.create({ service: "server" })
 
 export const GlobalDisposedEvent = BusEvent.define("global.disposed", z.object({}))
 
-export const GlobalRoutes = lazy(() => new Hono()
-  .get(
-    "/health",
-    describeRoute({
-      summary: "Get health",
-      description: "Get health information about the OpenCode server.",
-      operationId: "global.health",
-      responses: {
-        200: {
-          description: "Health information",
-          content: {
-            "application/json": {
-              schema: resolver(z.object({ healthy: z.literal(true), version: z.string() })),
+export const GlobalRoutes = lazy(() =>
+  new Hono()
+    .get(
+      "/health",
+      describeRoute({
+        summary: "Get health",
+        description: "Get health information about the OpenCode server.",
+        operationId: "global.health",
+        responses: {
+          200: {
+            description: "Health information",
+            content: {
+              "application/json": {
+                schema: resolver(z.object({ healthy: z.literal(true), version: z.string() })),
+              },
             },
           },
         },
+      }),
+      async (c) => {
+        return c.json({ healthy: true, version: Installation.VERSION })
       },
-    }),
-    async (c) => {
-      return c.json({ healthy: true, version: Installation.VERSION })
-    },
-  )
-  .get(
-    "/event",
-    describeRoute({
-      summary: "Get global events",
-      description: "Subscribe to global events from the OpenCode system using server-sent events.",
-      operationId: "global.event",
-      responses: {
-        200: {
-          description: "Event stream",
-          content: {
-            "text/event-stream": {
-              schema: resolver(
-                z
-                  .object({
-                    directory: z.string(),
-                    payload: BusEvent.payloads(),
-                  })
-                  .meta({
-                    ref: "GlobalEvent",
-                  }),
-              ),
+    )
+    .get(
+      "/event",
+      describeRoute({
+        summary: "Get global events",
+        description: "Subscribe to global events from the OpenCode system using server-sent events.",
+        operationId: "global.event",
+        responses: {
+          200: {
+            description: "Event stream",
+            content: {
+              "text/event-stream": {
+                schema: resolver(
+                  z
+                    .object({
+                      directory: z.string(),
+                      payload: BusEvent.payloads(),
+                    })
+                    .meta({
+                      ref: "GlobalEvent",
+                    }),
+                ),
+              },
             },
           },
         },
-      },
-    }),
-    async (c) => {
-      log.info("global event connected")
-      return streamSSE(c, async (stream) => {
-        stream.writeSSE({
-          data: JSON.stringify({
-            payload: {
-              type: "server.connected",
-              properties: {},
-            },
-          }),
-        })
-        async function handler(event: any) {
-          await stream.writeSSE({
-            data: JSON.stringify(event),
-          })
-        }
-        GlobalBus.on("event", handler)
-
-        // Send heartbeat every 30s to prevent WKWebView timeout (60s default)
-        const heartbeat = setInterval(() => {
+      }),
+      async (c) => {
+        log.info("global event connected")
+        return streamSSE(c, async (stream) => {
           stream.writeSSE({
             data: JSON.stringify({
               payload: {
-                type: "server.heartbeat",
+                type: "server.connected",
                 properties: {},
               },
             }),
           })
-        }, 30000)
+          async function handler(event: any) {
+            await stream.writeSSE({
+              data: JSON.stringify(event),
+            })
+          }
+          GlobalBus.on("event", handler)
 
-        await new Promise<void>((resolve) => {
-          stream.onAbort(() => {
-            clearInterval(heartbeat)
-            GlobalBus.off("event", handler)
-            resolve()
-            log.info("global event disconnected")
+          // Send heartbeat every 30s to prevent WKWebView timeout (60s default)
+          const heartbeat = setInterval(() => {
+            stream.writeSSE({
+              data: JSON.stringify({
+                payload: {
+                  type: "server.heartbeat",
+                  properties: {},
+                },
+              }),
+            })
+          }, 30000)
+
+          await new Promise<void>((resolve) => {
+            stream.onAbort(() => {
+              clearInterval(heartbeat)
+              GlobalBus.off("event", handler)
+              resolve()
+              log.info("global event disconnected")
+            })
           })
         })
-      })
-    },
-  )
-  .post(
-    "/dispose",
-    describeRoute({
-      summary: "Dispose instance",
-      description: "Clean up and dispose all OpenCode instances, releasing all resources.",
-      operationId: "global.dispose",
-      responses: {
-        200: {
-          description: "Global disposed",
-          content: {
-            "application/json": {
-              schema: resolver(z.boolean()),
+      },
+    )
+    .post(
+      "/dispose",
+      describeRoute({
+        summary: "Dispose instance",
+        description: "Clean up and dispose all OpenCode instances, releasing all resources.",
+        operationId: "global.dispose",
+        responses: {
+          200: {
+            description: "Global disposed",
+            content: {
+              "application/json": {
+                schema: resolver(z.boolean()),
+              },
             },
           },
         },
+      }),
+      async (c) => {
+        await Instance.disposeAll()
+        GlobalBus.emit("event", {
+          directory: "global",
+          payload: {
+            type: GlobalDisposedEvent.type,
+            properties: {},
+          },
+        })
+        return c.json(true)
       },
-    }),
-    async (c) => {
-      await Instance.disposeAll()
-      GlobalBus.emit("event", {
-        directory: "global",
-        payload: {
-          type: GlobalDisposedEvent.type,
-          properties: {},
-        },
-      })
-      return c.json(true)
-    },
-  )
+    ),
 )

+ 184 - 183
packages/opencode/src/server/routes/mcp.ts

@@ -6,219 +6,220 @@ import { Config } from "../../config/config"
 import { errors } from "../error"
 import { lazy } from "../../util/lazy"
 
-export const McpRoutes = lazy(() => new Hono()
-  .get(
-    "/",
-    describeRoute({
-      summary: "Get MCP status",
-      description: "Get the status of all Model Context Protocol (MCP) servers.",
-      operationId: "mcp.status",
-      responses: {
-        200: {
-          description: "MCP server status",
-          content: {
-            "application/json": {
-              schema: resolver(z.record(z.string(), MCP.Status)),
+export const McpRoutes = lazy(() =>
+  new Hono()
+    .get(
+      "/",
+      describeRoute({
+        summary: "Get MCP status",
+        description: "Get the status of all Model Context Protocol (MCP) servers.",
+        operationId: "mcp.status",
+        responses: {
+          200: {
+            description: "MCP server status",
+            content: {
+              "application/json": {
+                schema: resolver(z.record(z.string(), MCP.Status)),
+              },
             },
           },
         },
+      }),
+      async (c) => {
+        return c.json(await MCP.status())
       },
-    }),
-    async (c) => {
-      return c.json(await MCP.status())
-    },
-  )
-  .post(
-    "/",
-    describeRoute({
-      summary: "Add MCP server",
-      description: "Dynamically add a new Model Context Protocol (MCP) server to the system.",
-      operationId: "mcp.add",
-      responses: {
-        200: {
-          description: "MCP server added successfully",
-          content: {
-            "application/json": {
-              schema: resolver(z.record(z.string(), MCP.Status)),
+    )
+    .post(
+      "/",
+      describeRoute({
+        summary: "Add MCP server",
+        description: "Dynamically add a new Model Context Protocol (MCP) server to the system.",
+        operationId: "mcp.add",
+        responses: {
+          200: {
+            description: "MCP server added successfully",
+            content: {
+              "application/json": {
+                schema: resolver(z.record(z.string(), MCP.Status)),
+              },
             },
           },
+          ...errors(400),
         },
-        ...errors(400),
-      },
-    }),
-    validator(
-      "json",
-      z.object({
-        name: z.string(),
-        config: Config.Mcp,
       }),
-    ),
-    async (c) => {
-      const { name, config } = c.req.valid("json")
-      const result = await MCP.add(name, config)
-      return c.json(result.status)
-    },
-  )
-  .post(
-    "/:name/auth",
-    describeRoute({
-      summary: "Start MCP OAuth",
-      description: "Start OAuth authentication flow for a Model Context Protocol (MCP) server.",
-      operationId: "mcp.auth.start",
-      responses: {
-        200: {
-          description: "OAuth flow started",
-          content: {
-            "application/json": {
-              schema: resolver(
-                z.object({
-                  authorizationUrl: z.string().describe("URL to open in browser for authorization"),
-                }),
-              ),
+      validator(
+        "json",
+        z.object({
+          name: z.string(),
+          config: Config.Mcp,
+        }),
+      ),
+      async (c) => {
+        const { name, config } = c.req.valid("json")
+        const result = await MCP.add(name, config)
+        return c.json(result.status)
+      },
+    )
+    .post(
+      "/:name/auth",
+      describeRoute({
+        summary: "Start MCP OAuth",
+        description: "Start OAuth authentication flow for a Model Context Protocol (MCP) server.",
+        operationId: "mcp.auth.start",
+        responses: {
+          200: {
+            description: "OAuth flow started",
+            content: {
+              "application/json": {
+                schema: resolver(
+                  z.object({
+                    authorizationUrl: z.string().describe("URL to open in browser for authorization"),
+                  }),
+                ),
+              },
             },
           },
+          ...errors(400, 404),
         },
-        ...errors(400, 404),
+      }),
+      async (c) => {
+        const name = c.req.param("name")
+        const supportsOAuth = await MCP.supportsOAuth(name)
+        if (!supportsOAuth) {
+          return c.json({ error: `MCP server ${name} does not support OAuth` }, 400)
+        }
+        const result = await MCP.startAuth(name)
+        return c.json(result)
       },
-    }),
-    async (c) => {
-      const name = c.req.param("name")
-      const supportsOAuth = await MCP.supportsOAuth(name)
-      if (!supportsOAuth) {
-        return c.json({ error: `MCP server ${name} does not support OAuth` }, 400)
-      }
-      const result = await MCP.startAuth(name)
-      return c.json(result)
-    },
-  )
-  .post(
-    "/:name/auth/callback",
-    describeRoute({
-      summary: "Complete MCP OAuth",
-      description:
-        "Complete OAuth authentication for a Model Context Protocol (MCP) server using the authorization code.",
-      operationId: "mcp.auth.callback",
-      responses: {
-        200: {
-          description: "OAuth authentication completed",
-          content: {
-            "application/json": {
-              schema: resolver(MCP.Status),
+    )
+    .post(
+      "/:name/auth/callback",
+      describeRoute({
+        summary: "Complete MCP OAuth",
+        description:
+          "Complete OAuth authentication for a Model Context Protocol (MCP) server using the authorization code.",
+        operationId: "mcp.auth.callback",
+        responses: {
+          200: {
+            description: "OAuth authentication completed",
+            content: {
+              "application/json": {
+                schema: resolver(MCP.Status),
+              },
             },
           },
+          ...errors(400, 404),
         },
-        ...errors(400, 404),
-      },
-    }),
-    validator(
-      "json",
-      z.object({
-        code: z.string().describe("Authorization code from OAuth callback"),
       }),
-    ),
-    async (c) => {
-      const name = c.req.param("name")
-      const { code } = c.req.valid("json")
-      const status = await MCP.finishAuth(name, code)
-      return c.json(status)
-    },
-  )
-  .post(
-    "/:name/auth/authenticate",
-    describeRoute({
-      summary: "Authenticate MCP OAuth",
-      description: "Start OAuth flow and wait for callback (opens browser)",
-      operationId: "mcp.auth.authenticate",
-      responses: {
-        200: {
-          description: "OAuth authentication completed",
-          content: {
-            "application/json": {
-              schema: resolver(MCP.Status),
+      validator(
+        "json",
+        z.object({
+          code: z.string().describe("Authorization code from OAuth callback"),
+        }),
+      ),
+      async (c) => {
+        const name = c.req.param("name")
+        const { code } = c.req.valid("json")
+        const status = await MCP.finishAuth(name, code)
+        return c.json(status)
+      },
+    )
+    .post(
+      "/:name/auth/authenticate",
+      describeRoute({
+        summary: "Authenticate MCP OAuth",
+        description: "Start OAuth flow and wait for callback (opens browser)",
+        operationId: "mcp.auth.authenticate",
+        responses: {
+          200: {
+            description: "OAuth authentication completed",
+            content: {
+              "application/json": {
+                schema: resolver(MCP.Status),
+              },
             },
           },
+          ...errors(400, 404),
         },
-        ...errors(400, 404),
+      }),
+      async (c) => {
+        const name = c.req.param("name")
+        const supportsOAuth = await MCP.supportsOAuth(name)
+        if (!supportsOAuth) {
+          return c.json({ error: `MCP server ${name} does not support OAuth` }, 400)
+        }
+        const status = await MCP.authenticate(name)
+        return c.json(status)
       },
-    }),
-    async (c) => {
-      const name = c.req.param("name")
-      const supportsOAuth = await MCP.supportsOAuth(name)
-      if (!supportsOAuth) {
-        return c.json({ error: `MCP server ${name} does not support OAuth` }, 400)
-      }
-      const status = await MCP.authenticate(name)
-      return c.json(status)
-    },
-  )
-  .delete(
-    "/:name/auth",
-    describeRoute({
-      summary: "Remove MCP OAuth",
-      description: "Remove OAuth credentials for an MCP server",
-      operationId: "mcp.auth.remove",
-      responses: {
-        200: {
-          description: "OAuth credentials removed",
-          content: {
-            "application/json": {
-              schema: resolver(z.object({ success: z.literal(true) })),
+    )
+    .delete(
+      "/:name/auth",
+      describeRoute({
+        summary: "Remove MCP OAuth",
+        description: "Remove OAuth credentials for an MCP server",
+        operationId: "mcp.auth.remove",
+        responses: {
+          200: {
+            description: "OAuth credentials removed",
+            content: {
+              "application/json": {
+                schema: resolver(z.object({ success: z.literal(true) })),
+              },
             },
           },
+          ...errors(404),
         },
-        ...errors(404),
+      }),
+      async (c) => {
+        const name = c.req.param("name")
+        await MCP.removeAuth(name)
+        return c.json({ success: true as const })
       },
-    }),
-    async (c) => {
-      const name = c.req.param("name")
-      await MCP.removeAuth(name)
-      return c.json({ success: true as const })
-    },
-  )
-  .post(
-    "/:name/connect",
-    describeRoute({
-      description: "Connect an MCP server",
-      operationId: "mcp.connect",
-      responses: {
-        200: {
-          description: "MCP server connected successfully",
-          content: {
-            "application/json": {
-              schema: resolver(z.boolean()),
+    )
+    .post(
+      "/:name/connect",
+      describeRoute({
+        description: "Connect an MCP server",
+        operationId: "mcp.connect",
+        responses: {
+          200: {
+            description: "MCP server connected successfully",
+            content: {
+              "application/json": {
+                schema: resolver(z.boolean()),
+              },
             },
           },
         },
+      }),
+      validator("param", z.object({ name: z.string() })),
+      async (c) => {
+        const { name } = c.req.valid("param")
+        await MCP.connect(name)
+        return c.json(true)
       },
-    }),
-    validator("param", z.object({ name: z.string() })),
-    async (c) => {
-      const { name } = c.req.valid("param")
-      await MCP.connect(name)
-      return c.json(true)
-    },
-  )
-  .post(
-    "/:name/disconnect",
-    describeRoute({
-      description: "Disconnect an MCP server",
-      operationId: "mcp.disconnect",
-      responses: {
-        200: {
-          description: "MCP server disconnected successfully",
-          content: {
-            "application/json": {
-              schema: resolver(z.boolean()),
+    )
+    .post(
+      "/:name/disconnect",
+      describeRoute({
+        description: "Disconnect an MCP server",
+        operationId: "mcp.disconnect",
+        responses: {
+          200: {
+            description: "MCP server disconnected successfully",
+            content: {
+              "application/json": {
+                schema: resolver(z.boolean()),
+              },
             },
           },
         },
+      }),
+      validator("param", z.object({ name: z.string() })),
+      async (c) => {
+        const { name } = c.req.valid("param")
+        await MCP.disconnect(name)
+        return c.json(true)
       },
-    }),
-    validator("param", z.object({ name: z.string() })),
-    async (c) => {
-      const { name } = c.req.valid("param")
-      await MCP.disconnect(name)
-      return c.json(true)
-    },
-  )
+    ),
 )

+ 52 - 51
packages/opencode/src/server/routes/permission.ts

@@ -5,63 +5,64 @@ import { PermissionNext } from "@/permission/next"
 import { errors } from "../error"
 import { lazy } from "../../util/lazy"
 
-export const PermissionRoutes = lazy(() => new Hono()
-  .post(
-    "/:requestID/reply",
-    describeRoute({
-      summary: "Respond to permission request",
-      description: "Approve or deny a permission request from the AI assistant.",
-      operationId: "permission.reply",
-      responses: {
-        200: {
-          description: "Permission processed successfully",
-          content: {
-            "application/json": {
-              schema: resolver(z.boolean()),
+export const PermissionRoutes = lazy(() =>
+  new Hono()
+    .post(
+      "/:requestID/reply",
+      describeRoute({
+        summary: "Respond to permission request",
+        description: "Approve or deny a permission request from the AI assistant.",
+        operationId: "permission.reply",
+        responses: {
+          200: {
+            description: "Permission processed successfully",
+            content: {
+              "application/json": {
+                schema: resolver(z.boolean()),
+              },
             },
           },
+          ...errors(400, 404),
         },
-        ...errors(400, 404),
-      },
-    }),
-    validator(
-      "param",
-      z.object({
-        requestID: z.string(),
       }),
-    ),
-    validator("json", z.object({ reply: PermissionNext.Reply, message: z.string().optional() })),
-    async (c) => {
-      const params = c.req.valid("param")
-      const json = c.req.valid("json")
-      await PermissionNext.reply({
-        requestID: params.requestID,
-        reply: json.reply,
-        message: json.message,
-      })
-      return c.json(true)
-    },
-  )
-  .get(
-    "/",
-    describeRoute({
-      summary: "List pending permissions",
-      description: "Get all pending permission requests across all sessions.",
-      operationId: "permission.list",
-      responses: {
-        200: {
-          description: "List of pending permissions",
-          content: {
-            "application/json": {
-              schema: resolver(PermissionNext.Request.array()),
+      validator(
+        "param",
+        z.object({
+          requestID: z.string(),
+        }),
+      ),
+      validator("json", z.object({ reply: PermissionNext.Reply, message: z.string().optional() })),
+      async (c) => {
+        const params = c.req.valid("param")
+        const json = c.req.valid("json")
+        await PermissionNext.reply({
+          requestID: params.requestID,
+          reply: json.reply,
+          message: json.message,
+        })
+        return c.json(true)
+      },
+    )
+    .get(
+      "/",
+      describeRoute({
+        summary: "List pending permissions",
+        description: "Get all pending permission requests across all sessions.",
+        operationId: "permission.list",
+        responses: {
+          200: {
+            description: "List of pending permissions",
+            content: {
+              "application/json": {
+                schema: resolver(PermissionNext.Request.array()),
+              },
             },
           },
         },
+      }),
+      async (c) => {
+        const permissions = await PermissionNext.list()
+        return c.json(permissions)
       },
-    }),
-    async (c) => {
-      const permissions = await PermissionNext.list()
-      return c.json(permissions)
-    },
-  )
+    ),
 )

+ 60 - 59
packages/opencode/src/server/routes/project.ts

@@ -7,75 +7,76 @@ import z from "zod"
 import { errors } from "../error"
 import { lazy } from "../../util/lazy"
 
-export const ProjectRoutes = lazy(() => new Hono()
-  .get(
-    "/",
-    describeRoute({
-      summary: "List all projects",
-      description: "Get a list of projects that have been opened with OpenCode.",
-      operationId: "project.list",
-      responses: {
-        200: {
-          description: "List of projects",
-          content: {
-            "application/json": {
-              schema: resolver(Project.Info.array()),
+export const ProjectRoutes = lazy(() =>
+  new Hono()
+    .get(
+      "/",
+      describeRoute({
+        summary: "List all projects",
+        description: "Get a list of projects that have been opened with OpenCode.",
+        operationId: "project.list",
+        responses: {
+          200: {
+            description: "List of projects",
+            content: {
+              "application/json": {
+                schema: resolver(Project.Info.array()),
+              },
             },
           },
         },
+      }),
+      async (c) => {
+        const projects = await Project.list()
+        return c.json(projects)
       },
-    }),
-    async (c) => {
-      const projects = await Project.list()
-      return c.json(projects)
-    },
-  )
-  .get(
-    "/current",
-    describeRoute({
-      summary: "Get current project",
-      description: "Retrieve the currently active project that OpenCode is working with.",
-      operationId: "project.current",
-      responses: {
-        200: {
-          description: "Current project information",
-          content: {
-            "application/json": {
-              schema: resolver(Project.Info),
+    )
+    .get(
+      "/current",
+      describeRoute({
+        summary: "Get current project",
+        description: "Retrieve the currently active project that OpenCode is working with.",
+        operationId: "project.current",
+        responses: {
+          200: {
+            description: "Current project information",
+            content: {
+              "application/json": {
+                schema: resolver(Project.Info),
+              },
             },
           },
         },
+      }),
+      async (c) => {
+        return c.json(Instance.project)
       },
-    }),
-    async (c) => {
-      return c.json(Instance.project)
-    },
-  )
-  .patch(
-    "/:projectID",
-    describeRoute({
-      summary: "Update project",
-      description: "Update project properties such as name, icon and color.",
-      operationId: "project.update",
-      responses: {
-        200: {
-          description: "Updated project information",
-          content: {
-            "application/json": {
-              schema: resolver(Project.Info),
+    )
+    .patch(
+      "/:projectID",
+      describeRoute({
+        summary: "Update project",
+        description: "Update project properties such as name, icon and color.",
+        operationId: "project.update",
+        responses: {
+          200: {
+            description: "Updated project information",
+            content: {
+              "application/json": {
+                schema: resolver(Project.Info),
+              },
             },
           },
+          ...errors(400, 404),
         },
-        ...errors(400, 404),
+      }),
+      validator("param", z.object({ projectID: z.string() })),
+      validator("json", Project.update.schema.omit({ projectID: true })),
+      async (c) => {
+        const projectID = c.req.valid("param").projectID
+        const body = c.req.valid("json")
+        const project = await Project.update({ ...body, projectID })
+        return c.json(project)
       },
-    }),
-    validator("param", z.object({ projectID: z.string() })),
-    validator("json", Project.update.schema.omit({ projectID: true })),
-    async (c) => {
-      const projectID = c.req.valid("param").projectID
-      const body = c.req.valid("json")
-      const project = await Project.update({ ...body, projectID })
-      return c.json(project)
-    },
-  )
+    ),
 )

+ 134 - 133
packages/opencode/src/server/routes/provider.ts

@@ -9,156 +9,157 @@ import { mapValues } from "remeda"
 import { errors } from "../error"
 import { lazy } from "../../util/lazy"
 
-export const ProviderRoutes = lazy(() => new Hono()
-  .get(
-    "/",
-    describeRoute({
-      summary: "List providers",
-      description: "Get a list of all available AI providers, including both available and connected ones.",
-      operationId: "provider.list",
-      responses: {
-        200: {
-          description: "List of providers",
-          content: {
-            "application/json": {
-              schema: resolver(
-                z.object({
-                  all: ModelsDev.Provider.array(),
-                  default: z.record(z.string(), z.string()),
-                  connected: z.array(z.string()),
-                }),
-              ),
+export const ProviderRoutes = lazy(() =>
+  new Hono()
+    .get(
+      "/",
+      describeRoute({
+        summary: "List providers",
+        description: "Get a list of all available AI providers, including both available and connected ones.",
+        operationId: "provider.list",
+        responses: {
+          200: {
+            description: "List of providers",
+            content: {
+              "application/json": {
+                schema: resolver(
+                  z.object({
+                    all: ModelsDev.Provider.array(),
+                    default: z.record(z.string(), z.string()),
+                    connected: z.array(z.string()),
+                  }),
+                ),
+              },
             },
           },
         },
-      },
-    }),
-    async (c) => {
-      const config = await Config.get()
-      const disabled = new Set(config.disabled_providers ?? [])
-      const enabled = config.enabled_providers ? new Set(config.enabled_providers) : undefined
+      }),
+      async (c) => {
+        const config = await Config.get()
+        const disabled = new Set(config.disabled_providers ?? [])
+        const enabled = config.enabled_providers ? new Set(config.enabled_providers) : undefined
 
-      const allProviders = await ModelsDev.get()
-      const filteredProviders: Record<string, (typeof allProviders)[string]> = {}
-      for (const [key, value] of Object.entries(allProviders)) {
-        if ((enabled ? enabled.has(key) : true) && !disabled.has(key)) {
-          filteredProviders[key] = value
+        const allProviders = await ModelsDev.get()
+        const filteredProviders: Record<string, (typeof allProviders)[string]> = {}
+        for (const [key, value] of Object.entries(allProviders)) {
+          if ((enabled ? enabled.has(key) : true) && !disabled.has(key)) {
+            filteredProviders[key] = value
+          }
         }
-      }
 
-      const connected = await Provider.list()
-      const providers = Object.assign(
-        mapValues(filteredProviders, (x) => Provider.fromModelsDevProvider(x)),
-        connected,
-      )
-      return c.json({
-        all: Object.values(providers),
-        default: mapValues(providers, (item) => Provider.sort(Object.values(item.models))[0].id),
-        connected: Object.keys(connected),
-      })
-    },
-  )
-  .get(
-    "/auth",
-    describeRoute({
-      summary: "Get provider auth methods",
-      description: "Retrieve available authentication methods for all AI providers.",
-      operationId: "provider.auth",
-      responses: {
-        200: {
-          description: "Provider auth methods",
-          content: {
-            "application/json": {
-              schema: resolver(z.record(z.string(), z.array(ProviderAuth.Method))),
+        const connected = await Provider.list()
+        const providers = Object.assign(
+          mapValues(filteredProviders, (x) => Provider.fromModelsDevProvider(x)),
+          connected,
+        )
+        return c.json({
+          all: Object.values(providers),
+          default: mapValues(providers, (item) => Provider.sort(Object.values(item.models))[0].id),
+          connected: Object.keys(connected),
+        })
+      },
+    )
+    .get(
+      "/auth",
+      describeRoute({
+        summary: "Get provider auth methods",
+        description: "Retrieve available authentication methods for all AI providers.",
+        operationId: "provider.auth",
+        responses: {
+          200: {
+            description: "Provider auth methods",
+            content: {
+              "application/json": {
+                schema: resolver(z.record(z.string(), z.array(ProviderAuth.Method))),
+              },
             },
           },
         },
+      }),
+      async (c) => {
+        return c.json(await ProviderAuth.methods())
       },
-    }),
-    async (c) => {
-      return c.json(await ProviderAuth.methods())
-    },
-  )
-  .post(
-    "/:providerID/oauth/authorize",
-    describeRoute({
-      summary: "OAuth authorize",
-      description: "Initiate OAuth authorization for a specific AI provider to get an authorization URL.",
-      operationId: "provider.oauth.authorize",
-      responses: {
-        200: {
-          description: "Authorization URL and method",
-          content: {
-            "application/json": {
-              schema: resolver(ProviderAuth.Authorization.optional()),
+    )
+    .post(
+      "/:providerID/oauth/authorize",
+      describeRoute({
+        summary: "OAuth authorize",
+        description: "Initiate OAuth authorization for a specific AI provider to get an authorization URL.",
+        operationId: "provider.oauth.authorize",
+        responses: {
+          200: {
+            description: "Authorization URL and method",
+            content: {
+              "application/json": {
+                schema: resolver(ProviderAuth.Authorization.optional()),
+              },
             },
           },
+          ...errors(400),
         },
-        ...errors(400),
-      },
-    }),
-    validator(
-      "param",
-      z.object({
-        providerID: z.string().meta({ description: "Provider ID" }),
-      }),
-    ),
-    validator(
-      "json",
-      z.object({
-        method: z.number().meta({ description: "Auth method index" }),
       }),
-    ),
-    async (c) => {
-      const providerID = c.req.valid("param").providerID
-      const { method } = c.req.valid("json")
-      const result = await ProviderAuth.authorize({
-        providerID,
-        method,
-      })
-      return c.json(result)
-    },
-  )
-  .post(
-    "/:providerID/oauth/callback",
-    describeRoute({
-      summary: "OAuth callback",
-      description: "Handle the OAuth callback from a provider after user authorization.",
-      operationId: "provider.oauth.callback",
-      responses: {
-        200: {
-          description: "OAuth callback processed successfully",
-          content: {
-            "application/json": {
-              schema: resolver(z.boolean()),
+      validator(
+        "param",
+        z.object({
+          providerID: z.string().meta({ description: "Provider ID" }),
+        }),
+      ),
+      validator(
+        "json",
+        z.object({
+          method: z.number().meta({ description: "Auth method index" }),
+        }),
+      ),
+      async (c) => {
+        const providerID = c.req.valid("param").providerID
+        const { method } = c.req.valid("json")
+        const result = await ProviderAuth.authorize({
+          providerID,
+          method,
+        })
+        return c.json(result)
+      },
+    )
+    .post(
+      "/:providerID/oauth/callback",
+      describeRoute({
+        summary: "OAuth callback",
+        description: "Handle the OAuth callback from a provider after user authorization.",
+        operationId: "provider.oauth.callback",
+        responses: {
+          200: {
+            description: "OAuth callback processed successfully",
+            content: {
+              "application/json": {
+                schema: resolver(z.boolean()),
+              },
             },
           },
+          ...errors(400),
         },
-        ...errors(400),
-      },
-    }),
-    validator(
-      "param",
-      z.object({
-        providerID: z.string().meta({ description: "Provider ID" }),
-      }),
-    ),
-    validator(
-      "json",
-      z.object({
-        method: z.number().meta({ description: "Auth method index" }),
-        code: z.string().optional().meta({ description: "OAuth authorization code" }),
       }),
+      validator(
+        "param",
+        z.object({
+          providerID: z.string().meta({ description: "Provider ID" }),
+        }),
+      ),
+      validator(
+        "json",
+        z.object({
+          method: z.number().meta({ description: "Auth method index" }),
+          code: z.string().optional().meta({ description: "OAuth authorization code" }),
+        }),
+      ),
+      async (c) => {
+        const providerID = c.req.valid("param").providerID
+        const { method, code } = c.req.valid("json")
+        await ProviderAuth.callback({
+          providerID,
+          method,
+          code,
+        })
+        return c.json(true)
+      },
     ),
-    async (c) => {
-      const providerID = c.req.valid("param").providerID
-      const { method, code } = c.req.valid("json")
-      await ProviderAuth.callback({
-        providerID,
-        method,
-        code,
-      })
-      return c.json(true)
-    },
-  )
 )

+ 136 - 135
packages/opencode/src/server/routes/pty.ts

@@ -7,162 +7,163 @@ import { Storage } from "../../storage/storage"
 import { errors } from "../error"
 import { lazy } from "../../util/lazy"
 
-export const PtyRoutes = lazy(() => new Hono()
-  .get(
-    "/",
-    describeRoute({
-      summary: "List PTY sessions",
-      description: "Get a list of all active pseudo-terminal (PTY) sessions managed by OpenCode.",
-      operationId: "pty.list",
-      responses: {
-        200: {
-          description: "List of sessions",
-          content: {
-            "application/json": {
-              schema: resolver(Pty.Info.array()),
+export const PtyRoutes = lazy(() =>
+  new Hono()
+    .get(
+      "/",
+      describeRoute({
+        summary: "List PTY sessions",
+        description: "Get a list of all active pseudo-terminal (PTY) sessions managed by OpenCode.",
+        operationId: "pty.list",
+        responses: {
+          200: {
+            description: "List of sessions",
+            content: {
+              "application/json": {
+                schema: resolver(Pty.Info.array()),
+              },
             },
           },
         },
+      }),
+      async (c) => {
+        return c.json(Pty.list())
       },
-    }),
-    async (c) => {
-      return c.json(Pty.list())
-    },
-  )
-  .post(
-    "/",
-    describeRoute({
-      summary: "Create PTY session",
-      description: "Create a new pseudo-terminal (PTY) session for running shell commands and processes.",
-      operationId: "pty.create",
-      responses: {
-        200: {
-          description: "Created session",
-          content: {
-            "application/json": {
-              schema: resolver(Pty.Info),
+    )
+    .post(
+      "/",
+      describeRoute({
+        summary: "Create PTY session",
+        description: "Create a new pseudo-terminal (PTY) session for running shell commands and processes.",
+        operationId: "pty.create",
+        responses: {
+          200: {
+            description: "Created session",
+            content: {
+              "application/json": {
+                schema: resolver(Pty.Info),
+              },
             },
           },
+          ...errors(400),
         },
-        ...errors(400),
+      }),
+      validator("json", Pty.CreateInput),
+      async (c) => {
+        const info = await Pty.create(c.req.valid("json"))
+        return c.json(info)
       },
-    }),
-    validator("json", Pty.CreateInput),
-    async (c) => {
-      const info = await Pty.create(c.req.valid("json"))
-      return c.json(info)
-    },
-  )
-  .get(
-    "/:ptyID",
-    describeRoute({
-      summary: "Get PTY session",
-      description: "Retrieve detailed information about a specific pseudo-terminal (PTY) session.",
-      operationId: "pty.get",
-      responses: {
-        200: {
-          description: "Session info",
-          content: {
-            "application/json": {
-              schema: resolver(Pty.Info),
+    )
+    .get(
+      "/:ptyID",
+      describeRoute({
+        summary: "Get PTY session",
+        description: "Retrieve detailed information about a specific pseudo-terminal (PTY) session.",
+        operationId: "pty.get",
+        responses: {
+          200: {
+            description: "Session info",
+            content: {
+              "application/json": {
+                schema: resolver(Pty.Info),
+              },
             },
           },
+          ...errors(404),
         },
-        ...errors(404),
+      }),
+      validator("param", z.object({ ptyID: z.string() })),
+      async (c) => {
+        const info = Pty.get(c.req.valid("param").ptyID)
+        if (!info) {
+          throw new Storage.NotFoundError({ message: "Session not found" })
+        }
+        return c.json(info)
       },
-    }),
-    validator("param", z.object({ ptyID: z.string() })),
-    async (c) => {
-      const info = Pty.get(c.req.valid("param").ptyID)
-      if (!info) {
-        throw new Storage.NotFoundError({ message: "Session not found" })
-      }
-      return c.json(info)
-    },
-  )
-  .put(
-    "/:ptyID",
-    describeRoute({
-      summary: "Update PTY session",
-      description: "Update properties of an existing pseudo-terminal (PTY) session.",
-      operationId: "pty.update",
-      responses: {
-        200: {
-          description: "Updated session",
-          content: {
-            "application/json": {
-              schema: resolver(Pty.Info),
+    )
+    .put(
+      "/:ptyID",
+      describeRoute({
+        summary: "Update PTY session",
+        description: "Update properties of an existing pseudo-terminal (PTY) session.",
+        operationId: "pty.update",
+        responses: {
+          200: {
+            description: "Updated session",
+            content: {
+              "application/json": {
+                schema: resolver(Pty.Info),
+              },
             },
           },
+          ...errors(400),
         },
-        ...errors(400),
+      }),
+      validator("param", z.object({ ptyID: z.string() })),
+      validator("json", Pty.UpdateInput),
+      async (c) => {
+        const info = await Pty.update(c.req.valid("param").ptyID, c.req.valid("json"))
+        return c.json(info)
       },
-    }),
-    validator("param", z.object({ ptyID: z.string() })),
-    validator("json", Pty.UpdateInput),
-    async (c) => {
-      const info = await Pty.update(c.req.valid("param").ptyID, c.req.valid("json"))
-      return c.json(info)
-    },
-  )
-  .delete(
-    "/:ptyID",
-    describeRoute({
-      summary: "Remove PTY session",
-      description: "Remove and terminate a specific pseudo-terminal (PTY) session.",
-      operationId: "pty.remove",
-      responses: {
-        200: {
-          description: "Session removed",
-          content: {
-            "application/json": {
-              schema: resolver(z.boolean()),
+    )
+    .delete(
+      "/:ptyID",
+      describeRoute({
+        summary: "Remove PTY session",
+        description: "Remove and terminate a specific pseudo-terminal (PTY) session.",
+        operationId: "pty.remove",
+        responses: {
+          200: {
+            description: "Session removed",
+            content: {
+              "application/json": {
+                schema: resolver(z.boolean()),
+              },
             },
           },
+          ...errors(404),
         },
-        ...errors(404),
+      }),
+      validator("param", z.object({ ptyID: z.string() })),
+      async (c) => {
+        await Pty.remove(c.req.valid("param").ptyID)
+        return c.json(true)
       },
-    }),
-    validator("param", z.object({ ptyID: z.string() })),
-    async (c) => {
-      await Pty.remove(c.req.valid("param").ptyID)
-      return c.json(true)
-    },
-  )
-  .get(
-    "/:ptyID/connect",
-    describeRoute({
-      summary: "Connect to PTY session",
-      description: "Establish a WebSocket connection to interact with a pseudo-terminal (PTY) session in real-time.",
-      operationId: "pty.connect",
-      responses: {
-        200: {
-          description: "Connected session",
-          content: {
-            "application/json": {
-              schema: resolver(z.boolean()),
+    )
+    .get(
+      "/:ptyID/connect",
+      describeRoute({
+        summary: "Connect to PTY session",
+        description: "Establish a WebSocket connection to interact with a pseudo-terminal (PTY) session in real-time.",
+        operationId: "pty.connect",
+        responses: {
+          200: {
+            description: "Connected session",
+            content: {
+              "application/json": {
+                schema: resolver(z.boolean()),
+              },
             },
           },
+          ...errors(404),
         },
-        ...errors(404),
-      },
-    }),
-    validator("param", z.object({ ptyID: z.string() })),
-    upgradeWebSocket((c) => {
-      const id = c.req.param("ptyID")
-      let handler: ReturnType<typeof Pty.connect>
-      if (!Pty.get(id)) throw new Error("Session not found")
-      return {
-        onOpen(_event, ws) {
-          handler = Pty.connect(id, ws)
-        },
-        onMessage(event) {
-          handler?.onMessage(String(event.data))
-        },
-        onClose() {
-          handler?.onClose()
-        },
-      }
-    }),
-  )
+      }),
+      validator("param", z.object({ ptyID: z.string() })),
+      upgradeWebSocket((c) => {
+        const id = c.req.param("ptyID")
+        let handler: ReturnType<typeof Pty.connect>
+        if (!Pty.get(id)) throw new Error("Session not found")
+        return {
+          onOpen(_event, ws) {
+            handler = Pty.connect(id, ws)
+          },
+          onMessage(event) {
+            handler?.onMessage(String(event.data))
+          },
+          onClose() {
+            handler?.onClose()
+          },
+        }
+      }),
+    ),
 )

+ 76 - 75
packages/opencode/src/server/routes/question.ts

@@ -6,92 +6,93 @@ import z from "zod"
 import { errors } from "../error"
 import { lazy } from "../../util/lazy"
 
-export const QuestionRoutes = lazy(() => new Hono()
-  .get(
-    "/",
-    describeRoute({
-      summary: "List pending questions",
-      description: "Get all pending question requests across all sessions.",
-      operationId: "question.list",
-      responses: {
-        200: {
-          description: "List of pending questions",
-          content: {
-            "application/json": {
-              schema: resolver(Question.Request.array()),
+export const QuestionRoutes = lazy(() =>
+  new Hono()
+    .get(
+      "/",
+      describeRoute({
+        summary: "List pending questions",
+        description: "Get all pending question requests across all sessions.",
+        operationId: "question.list",
+        responses: {
+          200: {
+            description: "List of pending questions",
+            content: {
+              "application/json": {
+                schema: resolver(Question.Request.array()),
+              },
             },
           },
         },
+      }),
+      async (c) => {
+        const questions = await Question.list()
+        return c.json(questions)
       },
-    }),
-    async (c) => {
-      const questions = await Question.list()
-      return c.json(questions)
-    },
-  )
-  .post(
-    "/:requestID/reply",
-    describeRoute({
-      summary: "Reply to question request",
-      description: "Provide answers to a question request from the AI assistant.",
-      operationId: "question.reply",
-      responses: {
-        200: {
-          description: "Question answered successfully",
-          content: {
-            "application/json": {
-              schema: resolver(z.boolean()),
+    )
+    .post(
+      "/:requestID/reply",
+      describeRoute({
+        summary: "Reply to question request",
+        description: "Provide answers to a question request from the AI assistant.",
+        operationId: "question.reply",
+        responses: {
+          200: {
+            description: "Question answered successfully",
+            content: {
+              "application/json": {
+                schema: resolver(z.boolean()),
+              },
             },
           },
+          ...errors(400, 404),
         },
-        ...errors(400, 404),
-      },
-    }),
-    validator(
-      "param",
-      z.object({
-        requestID: z.string(),
       }),
-    ),
-    validator("json", Question.Reply),
-    async (c) => {
-      const params = c.req.valid("param")
-      const json = c.req.valid("json")
-      await Question.reply({
-        requestID: params.requestID,
-        answers: json.answers,
-      })
-      return c.json(true)
-    },
-  )
-  .post(
-    "/:requestID/reject",
-    describeRoute({
-      summary: "Reject question request",
-      description: "Reject a question request from the AI assistant.",
-      operationId: "question.reject",
-      responses: {
-        200: {
-          description: "Question rejected successfully",
-          content: {
-            "application/json": {
-              schema: resolver(z.boolean()),
+      validator(
+        "param",
+        z.object({
+          requestID: z.string(),
+        }),
+      ),
+      validator("json", Question.Reply),
+      async (c) => {
+        const params = c.req.valid("param")
+        const json = c.req.valid("json")
+        await Question.reply({
+          requestID: params.requestID,
+          answers: json.answers,
+        })
+        return c.json(true)
+      },
+    )
+    .post(
+      "/:requestID/reject",
+      describeRoute({
+        summary: "Reject question request",
+        description: "Reject a question request from the AI assistant.",
+        operationId: "question.reject",
+        responses: {
+          200: {
+            description: "Question rejected successfully",
+            content: {
+              "application/json": {
+                schema: resolver(z.boolean()),
+              },
             },
           },
+          ...errors(400, 404),
         },
-        ...errors(400, 404),
-      },
-    }),
-    validator(
-      "param",
-      z.object({
-        requestID: z.string(),
       }),
+      validator(
+        "param",
+        z.object({
+          requestID: z.string(),
+        }),
+      ),
+      async (c) => {
+        const params = c.req.valid("param")
+        await Question.reject(params.requestID)
+        return c.json(true)
+      },
     ),
-    async (c) => {
-      const params = c.req.valid("param")
-      await Question.reject(params.requestID)
-      return c.json(true)
-    },
-  )
 )

+ 257 - 238
packages/sdk/js/src/v2/gen/sdk.gen.ts

@@ -7,6 +7,7 @@ import type {
   AppAgentsResponses,
   AppLogErrors,
   AppLogResponses,
+  AppSkillsResponses,
   Auth as Auth3,
   AuthSetErrors,
   AuthSetResponses,
@@ -100,7 +101,6 @@ import type {
   SessionCreateResponses,
   SessionDeleteErrors,
   SessionDeleteResponses,
-  SessionDiffErrors,
   SessionDiffResponses,
   SessionForkResponses,
   SessionGetErrors,
@@ -653,48 +653,6 @@ export class Tool extends HeyApiClient {
   }
 }
 
-export class Instance extends HeyApiClient {
-  /**
-   * Dispose instance
-   *
-   * Clean up and dispose the current OpenCode instance, releasing all resources.
-   */
-  public dispose<ThrowOnError extends boolean = false>(
-    parameters?: {
-      directory?: string
-    },
-    options?: Options<never, ThrowOnError>,
-  ) {
-    const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }])
-    return (options?.client ?? this.client).post<InstanceDisposeResponses, unknown, ThrowOnError>({
-      url: "/instance/dispose",
-      ...options,
-      ...params,
-    })
-  }
-}
-
-export class Path extends HeyApiClient {
-  /**
-   * Get paths
-   *
-   * Retrieve the current working directory and related path information for the OpenCode instance.
-   */
-  public get<ThrowOnError extends boolean = false>(
-    parameters?: {
-      directory?: string
-    },
-    options?: Options<never, ThrowOnError>,
-  ) {
-    const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }])
-    return (options?.client ?? this.client).get<PathGetResponses, unknown, ThrowOnError>({
-      url: "/path",
-      ...options,
-      ...params,
-    })
-  }
-}
-
 export class Worktree extends HeyApiClient {
   /**
    * List worktrees
@@ -751,27 +709,34 @@ export class Worktree extends HeyApiClient {
   }
 }
 
-export class Vcs extends HeyApiClient {
+export class Resource extends HeyApiClient {
   /**
-   * Get VCS info
+   * Get MCP resources
    *
-   * Retrieve version control system (VCS) information for the current project, such as git branch.
+   * Get all available MCP resources from connected servers. Optionally filter by name.
    */
-  public get<ThrowOnError extends boolean = false>(
+  public list<ThrowOnError extends boolean = false>(
     parameters?: {
       directory?: string
     },
     options?: Options<never, ThrowOnError>,
   ) {
     const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }])
-    return (options?.client ?? this.client).get<VcsGetResponses, unknown, ThrowOnError>({
-      url: "/vcs",
+    return (options?.client ?? this.client).get<ExperimentalResourceListResponses, unknown, ThrowOnError>({
+      url: "/experimental/resource",
       ...options,
       ...params,
     })
   }
 }
 
+export class Experimental extends HeyApiClient {
+  private _resource?: Resource
+  get resource(): Resource {
+    return (this._resource ??= new Resource({ client: this.client }))
+  }
+}
+
 export class Session extends HeyApiClient {
   /**
    * List sessions
@@ -1197,9 +1162,9 @@ export class Session extends HeyApiClient {
   }
 
   /**
-   * Get session diff
+   * Get message diff
    *
-   * Get all file changes (diffs) made during this session.
+   * Get the file changes (diff) that resulted from a specific user message in the session.
    */
   public diff<ThrowOnError extends boolean = false>(
     parameters: {
@@ -1221,7 +1186,7 @@ export class Session extends HeyApiClient {
         },
       ],
     )
-    return (options?.client ?? this.client).get<SessionDiffResponses, SessionDiffErrors, ThrowOnError>({
+    return (options?.client ?? this.client).get<SessionDiffResponses, unknown, ThrowOnError>({
       url: "/session/{sessionID}/diff",
       ...options,
       ...params,
@@ -1877,27 +1842,6 @@ export class Question extends HeyApiClient {
   }
 }
 
-export class Command extends HeyApiClient {
-  /**
-   * List commands
-   *
-   * Get a list of all available commands in the OpenCode system.
-   */
-  public list<ThrowOnError extends boolean = false>(
-    parameters?: {
-      directory?: string
-    },
-    options?: Options<never, ThrowOnError>,
-  ) {
-    const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }])
-    return (options?.client ?? this.client).get<CommandListResponses, unknown, ThrowOnError>({
-      url: "/command",
-      ...options,
-      ...params,
-    })
-  }
-}
-
 export class Oauth extends HeyApiClient {
   /**
    * OAuth authorize
@@ -2208,70 +2152,6 @@ export class File extends HeyApiClient {
   }
 }
 
-export class App extends HeyApiClient {
-  /**
-   * Write log
-   *
-   * Write a log entry to the server logs with specified level and metadata.
-   */
-  public log<ThrowOnError extends boolean = false>(
-    parameters?: {
-      directory?: string
-      service?: string
-      level?: "debug" | "info" | "error" | "warn"
-      message?: string
-      extra?: {
-        [key: string]: unknown
-      }
-    },
-    options?: Options<never, ThrowOnError>,
-  ) {
-    const params = buildClientParams(
-      [parameters],
-      [
-        {
-          args: [
-            { in: "query", key: "directory" },
-            { in: "body", key: "service" },
-            { in: "body", key: "level" },
-            { in: "body", key: "message" },
-            { in: "body", key: "extra" },
-          ],
-        },
-      ],
-    )
-    return (options?.client ?? this.client).post<AppLogResponses, AppLogErrors, ThrowOnError>({
-      url: "/log",
-      ...options,
-      ...params,
-      headers: {
-        "Content-Type": "application/json",
-        ...options?.headers,
-        ...params.headers,
-      },
-    })
-  }
-
-  /**
-   * List agents
-   *
-   * Get a list of all available AI agents in the OpenCode system.
-   */
-  public agents<ThrowOnError extends boolean = false>(
-    parameters?: {
-      directory?: string
-    },
-    options?: Options<never, ThrowOnError>,
-  ) {
-    const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }])
-    return (options?.client ?? this.client).get<AppAgentsResponses, unknown, ThrowOnError>({
-      url: "/agent",
-      ...options,
-      ...params,
-    })
-  }
-}
-
 export class Auth extends HeyApiClient {
   /**
    * Remove MCP OAuth
@@ -2522,76 +2402,6 @@ export class Mcp extends HeyApiClient {
   }
 }
 
-export class Resource extends HeyApiClient {
-  /**
-   * Get MCP resources
-   *
-   * Get all available MCP resources from connected servers. Optionally filter by name.
-   */
-  public list<ThrowOnError extends boolean = false>(
-    parameters?: {
-      directory?: string
-    },
-    options?: Options<never, ThrowOnError>,
-  ) {
-    const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }])
-    return (options?.client ?? this.client).get<ExperimentalResourceListResponses, unknown, ThrowOnError>({
-      url: "/experimental/resource",
-      ...options,
-      ...params,
-    })
-  }
-}
-
-export class Experimental extends HeyApiClient {
-  private _resource?: Resource
-  get resource(): Resource {
-    return (this._resource ??= new Resource({ client: this.client }))
-  }
-}
-
-export class Lsp extends HeyApiClient {
-  /**
-   * Get LSP status
-   *
-   * Get LSP server status
-   */
-  public status<ThrowOnError extends boolean = false>(
-    parameters?: {
-      directory?: string
-    },
-    options?: Options<never, ThrowOnError>,
-  ) {
-    const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }])
-    return (options?.client ?? this.client).get<LspStatusResponses, unknown, ThrowOnError>({
-      url: "/lsp",
-      ...options,
-      ...params,
-    })
-  }
-}
-
-export class Formatter extends HeyApiClient {
-  /**
-   * Get formatter status
-   *
-   * Get formatter status
-   */
-  public status<ThrowOnError extends boolean = false>(
-    parameters?: {
-      directory?: string
-    },
-    options?: Options<never, ThrowOnError>,
-  ) {
-    const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }])
-    return (options?.client ?? this.client).get<FormatterStatusResponses, unknown, ThrowOnError>({
-      url: "/formatter",
-      ...options,
-      ...params,
-    })
-  }
-}
-
 export class Control extends HeyApiClient {
   /**
    * Get next TUI request
@@ -2930,6 +2740,215 @@ export class Tui extends HeyApiClient {
   }
 }
 
+export class Instance extends HeyApiClient {
+  /**
+   * Dispose instance
+   *
+   * Clean up and dispose the current OpenCode instance, releasing all resources.
+   */
+  public dispose<ThrowOnError extends boolean = false>(
+    parameters?: {
+      directory?: string
+    },
+    options?: Options<never, ThrowOnError>,
+  ) {
+    const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }])
+    return (options?.client ?? this.client).post<InstanceDisposeResponses, unknown, ThrowOnError>({
+      url: "/instance/dispose",
+      ...options,
+      ...params,
+    })
+  }
+}
+
+export class Path extends HeyApiClient {
+  /**
+   * Get paths
+   *
+   * Retrieve the current working directory and related path information for the OpenCode instance.
+   */
+  public get<ThrowOnError extends boolean = false>(
+    parameters?: {
+      directory?: string
+    },
+    options?: Options<never, ThrowOnError>,
+  ) {
+    const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }])
+    return (options?.client ?? this.client).get<PathGetResponses, unknown, ThrowOnError>({
+      url: "/path",
+      ...options,
+      ...params,
+    })
+  }
+}
+
+export class Vcs extends HeyApiClient {
+  /**
+   * Get VCS info
+   *
+   * Retrieve version control system (VCS) information for the current project, such as git branch.
+   */
+  public get<ThrowOnError extends boolean = false>(
+    parameters?: {
+      directory?: string
+    },
+    options?: Options<never, ThrowOnError>,
+  ) {
+    const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }])
+    return (options?.client ?? this.client).get<VcsGetResponses, unknown, ThrowOnError>({
+      url: "/vcs",
+      ...options,
+      ...params,
+    })
+  }
+}
+
+export class Command extends HeyApiClient {
+  /**
+   * List commands
+   *
+   * Get a list of all available commands in the OpenCode system.
+   */
+  public list<ThrowOnError extends boolean = false>(
+    parameters?: {
+      directory?: string
+    },
+    options?: Options<never, ThrowOnError>,
+  ) {
+    const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }])
+    return (options?.client ?? this.client).get<CommandListResponses, unknown, ThrowOnError>({
+      url: "/command",
+      ...options,
+      ...params,
+    })
+  }
+}
+
+export class App extends HeyApiClient {
+  /**
+   * Write log
+   *
+   * Write a log entry to the server logs with specified level and metadata.
+   */
+  public log<ThrowOnError extends boolean = false>(
+    parameters?: {
+      directory?: string
+      service?: string
+      level?: "debug" | "info" | "error" | "warn"
+      message?: string
+      extra?: {
+        [key: string]: unknown
+      }
+    },
+    options?: Options<never, ThrowOnError>,
+  ) {
+    const params = buildClientParams(
+      [parameters],
+      [
+        {
+          args: [
+            { in: "query", key: "directory" },
+            { in: "body", key: "service" },
+            { in: "body", key: "level" },
+            { in: "body", key: "message" },
+            { in: "body", key: "extra" },
+          ],
+        },
+      ],
+    )
+    return (options?.client ?? this.client).post<AppLogResponses, AppLogErrors, ThrowOnError>({
+      url: "/log",
+      ...options,
+      ...params,
+      headers: {
+        "Content-Type": "application/json",
+        ...options?.headers,
+        ...params.headers,
+      },
+    })
+  }
+
+  /**
+   * List agents
+   *
+   * Get a list of all available AI agents in the OpenCode system.
+   */
+  public agents<ThrowOnError extends boolean = false>(
+    parameters?: {
+      directory?: string
+    },
+    options?: Options<never, ThrowOnError>,
+  ) {
+    const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }])
+    return (options?.client ?? this.client).get<AppAgentsResponses, unknown, ThrowOnError>({
+      url: "/agent",
+      ...options,
+      ...params,
+    })
+  }
+
+  /**
+   * List skills
+   *
+   * Get a list of all available skills in the OpenCode system.
+   */
+  public skills<ThrowOnError extends boolean = false>(
+    parameters?: {
+      directory?: string
+    },
+    options?: Options<never, ThrowOnError>,
+  ) {
+    const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }])
+    return (options?.client ?? this.client).get<AppSkillsResponses, unknown, ThrowOnError>({
+      url: "/skill",
+      ...options,
+      ...params,
+    })
+  }
+}
+
+export class Lsp extends HeyApiClient {
+  /**
+   * Get LSP status
+   *
+   * Get LSP server status
+   */
+  public status<ThrowOnError extends boolean = false>(
+    parameters?: {
+      directory?: string
+    },
+    options?: Options<never, ThrowOnError>,
+  ) {
+    const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }])
+    return (options?.client ?? this.client).get<LspStatusResponses, unknown, ThrowOnError>({
+      url: "/lsp",
+      ...options,
+      ...params,
+    })
+  }
+}
+
+export class Formatter extends HeyApiClient {
+  /**
+   * Get formatter status
+   *
+   * Get formatter status
+   */
+  public status<ThrowOnError extends boolean = false>(
+    parameters?: {
+      directory?: string
+    },
+    options?: Options<never, ThrowOnError>,
+  ) {
+    const params = buildClientParams([parameters], [{ args: [{ in: "query", key: "directory" }] }])
+    return (options?.client ?? this.client).get<FormatterStatusResponses, unknown, ThrowOnError>({
+      url: "/formatter",
+      ...options,
+      ...params,
+    })
+  }
+}
+
 export class Auth2 extends HeyApiClient {
   /**
    * Set auth credentials
@@ -3023,24 +3042,14 @@ export class OpencodeClient extends HeyApiClient {
     return (this._tool ??= new Tool({ client: this.client }))
   }
 
-  private _instance?: Instance
-  get instance(): Instance {
-    return (this._instance ??= new Instance({ client: this.client }))
-  }
-
-  private _path?: Path
-  get path(): Path {
-    return (this._path ??= new Path({ client: this.client }))
-  }
-
   private _worktree?: Worktree
   get worktree(): Worktree {
     return (this._worktree ??= new Worktree({ client: this.client }))
   }
 
-  private _vcs?: Vcs
-  get vcs(): Vcs {
-    return (this._vcs ??= new Vcs({ client: this.client }))
+  private _experimental?: Experimental
+  get experimental(): Experimental {
+    return (this._experimental ??= new Experimental({ client: this.client }))
   }
 
   private _session?: Session
@@ -3063,11 +3072,6 @@ export class OpencodeClient extends HeyApiClient {
     return (this._question ??= new Question({ client: this.client }))
   }
 
-  private _command?: Command
-  get command(): Command {
-    return (this._command ??= new Command({ client: this.client }))
-  }
-
   private _provider?: Provider
   get provider(): Provider {
     return (this._provider ??= new Provider({ client: this.client }))
@@ -3083,19 +3087,39 @@ export class OpencodeClient extends HeyApiClient {
     return (this._file ??= new File({ client: this.client }))
   }
 
-  private _app?: App
-  get app(): App {
-    return (this._app ??= new App({ client: this.client }))
-  }
-
   private _mcp?: Mcp
   get mcp(): Mcp {
     return (this._mcp ??= new Mcp({ client: this.client }))
   }
 
-  private _experimental?: Experimental
-  get experimental(): Experimental {
-    return (this._experimental ??= new Experimental({ client: this.client }))
+  private _tui?: Tui
+  get tui(): Tui {
+    return (this._tui ??= new Tui({ client: this.client }))
+  }
+
+  private _instance?: Instance
+  get instance(): Instance {
+    return (this._instance ??= new Instance({ client: this.client }))
+  }
+
+  private _path?: Path
+  get path(): Path {
+    return (this._path ??= new Path({ client: this.client }))
+  }
+
+  private _vcs?: Vcs
+  get vcs(): Vcs {
+    return (this._vcs ??= new Vcs({ client: this.client }))
+  }
+
+  private _command?: Command
+  get command(): Command {
+    return (this._command ??= new Command({ client: this.client }))
+  }
+
+  private _app?: App
+  get app(): App {
+    return (this._app ??= new App({ client: this.client }))
   }
 
   private _lsp?: Lsp
@@ -3108,11 +3132,6 @@ export class OpencodeClient extends HeyApiClient {
     return (this._formatter ??= new Formatter({ client: this.client }))
   }
 
-  private _tui?: Tui
-  get tui(): Tui {
-    return (this._tui ??= new Tui({ client: this.client }))
-  }
-
   private _auth?: Auth2
   get auth(): Auth2 {
     return (this._auth ??= new Auth2({ client: this.client }))

+ 377 - 371
packages/sdk/js/src/v2/gen/types.gen.ts

@@ -62,6 +62,13 @@ export type EventLspUpdated = {
   }
 }
 
+export type EventFileEdited = {
+  type: "file.edited"
+  properties: {
+    file: string
+  }
+}
+
 export type FileDiff = {
   file: string
   before: string
@@ -599,13 +606,6 @@ export type EventSessionCompacted = {
   }
 }
 
-export type EventFileEdited = {
-  type: "file.edited"
-  properties: {
-    file: string
-  }
-}
-
 export type Todo = {
   /**
    * Brief description of the task
@@ -843,15 +843,15 @@ export type EventPtyDeleted = {
   }
 }
 
-export type EventServerConnected = {
-  type: "server.connected"
+export type EventGlobalDisposed = {
+  type: "global.disposed"
   properties: {
     [key: string]: unknown
   }
 }
 
-export type EventGlobalDisposed = {
-  type: "global.disposed"
+export type EventServerConnected = {
+  type: "server.connected"
   properties: {
     [key: string]: unknown
   }
@@ -864,6 +864,7 @@ export type Event =
   | EventServerInstanceDisposed
   | EventLspClientDiagnostics
   | EventLspUpdated
+  | EventFileEdited
   | EventMessageUpdated
   | EventMessageRemoved
   | EventMessagePartUpdated
@@ -876,7 +877,6 @@ export type Event =
   | EventQuestionReplied
   | EventQuestionRejected
   | EventSessionCompacted
-  | EventFileEdited
   | EventTodoUpdated
   | EventTuiPromptAppend
   | EventTuiCommandExecute
@@ -896,8 +896,8 @@ export type Event =
   | EventPtyUpdated
   | EventPtyExited
   | EventPtyDeleted
-  | EventServerConnected
   | EventGlobalDisposed
+  | EventServerConnected
 
 export type GlobalEvent = {
   directory: string
@@ -1796,98 +1796,6 @@ export type Config = {
   }
 }
 
-export type ToolIds = Array<string>
-
-export type ToolListItem = {
-  id: string
-  description: string
-  parameters: unknown
-}
-
-export type ToolList = Array<ToolListItem>
-
-export type Path = {
-  home: string
-  state: string
-  config: string
-  worktree: string
-  directory: string
-}
-
-export type Worktree = {
-  name: string
-  branch: string
-  directory: string
-}
-
-export type WorktreeCreateInput = {
-  name?: string
-  startCommand?: string
-}
-
-export type VcsInfo = {
-  branch: string
-}
-
-export type TextPartInput = {
-  id?: string
-  type: "text"
-  text: string
-  synthetic?: boolean
-  ignored?: boolean
-  time?: {
-    start: number
-    end?: number
-  }
-  metadata?: {
-    [key: string]: unknown
-  }
-}
-
-export type FilePartInput = {
-  id?: string
-  type: "file"
-  mime: string
-  filename?: string
-  url: string
-  source?: FilePartSource
-}
-
-export type AgentPartInput = {
-  id?: string
-  type: "agent"
-  name: string
-  source?: {
-    value: string
-    start: number
-    end: number
-  }
-}
-
-export type SubtaskPartInput = {
-  id?: string
-  type: "subtask"
-  prompt: string
-  description: string
-  agent: string
-  model?: {
-    providerID: string
-    modelID: string
-  }
-  command?: string
-}
-
-export type Command = {
-  name: string
-  description?: string
-  agent?: string
-  model?: string
-  mcp?: boolean
-  template: string
-  subtask?: boolean
-  hints: Array<string>
-}
-
 export type Model = {
   id: string
   providerID: string
@@ -1973,6 +1881,83 @@ export type Provider = {
   }
 }
 
+export type ToolIds = Array<string>
+
+export type ToolListItem = {
+  id: string
+  description: string
+  parameters: unknown
+}
+
+export type ToolList = Array<ToolListItem>
+
+export type Worktree = {
+  name: string
+  branch: string
+  directory: string
+}
+
+export type WorktreeCreateInput = {
+  name?: string
+  startCommand?: string
+}
+
+export type McpResource = {
+  name: string
+  uri: string
+  description?: string
+  mimeType?: string
+  client: string
+}
+
+export type TextPartInput = {
+  id?: string
+  type: "text"
+  text: string
+  synthetic?: boolean
+  ignored?: boolean
+  time?: {
+    start: number
+    end?: number
+  }
+  metadata?: {
+    [key: string]: unknown
+  }
+}
+
+export type FilePartInput = {
+  id?: string
+  type: "file"
+  mime: string
+  filename?: string
+  url: string
+  source?: FilePartSource
+}
+
+export type AgentPartInput = {
+  id?: string
+  type: "agent"
+  name: string
+  source?: {
+    value: string
+    start: number
+    end: number
+  }
+}
+
+export type SubtaskPartInput = {
+  id?: string
+  type: "subtask"
+  prompt: string
+  description: string
+  agent: string
+  model?: {
+    providerID: string
+    modelID: string
+  }
+  command?: string
+}
+
 export type ProviderAuthMethod = {
   type: "oauth" | "api"
   label: string
@@ -2030,27 +2015,6 @@ export type File = {
   status: "added" | "deleted" | "modified"
 }
 
-export type Agent = {
-  name: string
-  description?: string
-  mode: "subagent" | "primary" | "all"
-  native?: boolean
-  hidden?: boolean
-  topP?: number
-  temperature?: number
-  color?: string
-  permission: PermissionRuleset
-  model?: {
-    modelID: string
-    providerID: string
-  }
-  prompt?: string
-  options: {
-    [key: string]: unknown
-  }
-  steps?: number
-}
-
 export type McpStatusConnected = {
   status: "connected"
 }
@@ -2080,37 +2044,73 @@ export type McpStatus =
   | McpStatusNeedsAuth
   | McpStatusNeedsClientRegistration
 
-export type McpResource = {
-  name: string
-  uri: string
-  description?: string
-  mimeType?: string
-  client: string
+export type Path = {
+  home: string
+  state: string
+  config: string
+  worktree: string
+  directory: string
 }
 
-export type LspStatus = {
-  id: string
-  name: string
-  root: string
-  status: "connected" | "error"
+export type VcsInfo = {
+  branch: string
 }
 
-export type FormatterStatus = {
+export type Command = {
   name: string
-  extensions: Array<string>
-  enabled: boolean
-}
-
-export type OAuth = {
-  type: "oauth"
-  refresh: string
-  access: string
-  expires: number
-  accountId?: string
-  enterpriseUrl?: string
+  description?: string
+  agent?: string
+  model?: string
+  mcp?: boolean
+  template: string
+  subtask?: boolean
+  hints: Array<string>
 }
 
-export type ApiAuth = {
+export type Agent = {
+  name: string
+  description?: string
+  mode: "subagent" | "primary" | "all"
+  native?: boolean
+  hidden?: boolean
+  topP?: number
+  temperature?: number
+  color?: string
+  permission: PermissionRuleset
+  model?: {
+    modelID: string
+    providerID: string
+  }
+  prompt?: string
+  options: {
+    [key: string]: unknown
+  }
+  steps?: number
+}
+
+export type LspStatus = {
+  id: string
+  name: string
+  root: string
+  status: "connected" | "error"
+}
+
+export type FormatterStatus = {
+  name: string
+  extensions: Array<string>
+  enabled: boolean
+}
+
+export type OAuth = {
+  type: "oauth"
+  refresh: string
+  access: string
+  expires: number
+  accountId?: string
+  enterpriseUrl?: string
+}
+
+export type ApiAuth = {
   type: "api"
   key: string
 }
@@ -2469,6 +2469,29 @@ export type ConfigUpdateResponses = {
 
 export type ConfigUpdateResponse = ConfigUpdateResponses[keyof ConfigUpdateResponses]
 
+export type ConfigProvidersData = {
+  body?: never
+  path?: never
+  query?: {
+    directory?: string
+  }
+  url: "/config/providers"
+}
+
+export type ConfigProvidersResponses = {
+  /**
+   * List of providers
+   */
+  200: {
+    providers: Array<Provider>
+    default: {
+      [key: string]: string
+    }
+  }
+}
+
+export type ConfigProvidersResponse = ConfigProvidersResponses[keyof ConfigProvidersResponses]
+
 export type ToolIdsData = {
   body?: never
   path?: never
@@ -2525,42 +2548,6 @@ export type ToolListResponses = {
 
 export type ToolListResponse = ToolListResponses[keyof ToolListResponses]
 
-export type InstanceDisposeData = {
-  body?: never
-  path?: never
-  query?: {
-    directory?: string
-  }
-  url: "/instance/dispose"
-}
-
-export type InstanceDisposeResponses = {
-  /**
-   * Instance disposed
-   */
-  200: boolean
-}
-
-export type InstanceDisposeResponse = InstanceDisposeResponses[keyof InstanceDisposeResponses]
-
-export type PathGetData = {
-  body?: never
-  path?: never
-  query?: {
-    directory?: string
-  }
-  url: "/path"
-}
-
-export type PathGetResponses = {
-  /**
-   * Path
-   */
-  200: Path
-}
-
-export type PathGetResponse = PathGetResponses[keyof PathGetResponses]
-
 export type WorktreeListData = {
   body?: never
   path?: never
@@ -2606,23 +2593,26 @@ export type WorktreeCreateResponses = {
 
 export type WorktreeCreateResponse = WorktreeCreateResponses[keyof WorktreeCreateResponses]
 
-export type VcsGetData = {
+export type ExperimentalResourceListData = {
   body?: never
   path?: never
   query?: {
     directory?: string
   }
-  url: "/vcs"
+  url: "/experimental/resource"
 }
 
-export type VcsGetResponses = {
+export type ExperimentalResourceListResponses = {
   /**
-   * VCS info
+   * MCP resources
    */
-  200: VcsInfo
+  200: {
+    [key: string]: McpResource
+  }
 }
 
-export type VcsGetResponse = VcsGetResponses[keyof VcsGetResponses]
+export type ExperimentalResourceListResponse =
+  ExperimentalResourceListResponses[keyof ExperimentalResourceListResponses]
 
 export type SessionListData = {
   body?: never
@@ -3058,9 +3048,6 @@ export type SessionShareResponse = SessionShareResponses[keyof SessionShareRespo
 export type SessionDiffData = {
   body?: never
   path: {
-    /**
-     * Session ID
-     */
     sessionID: string
   }
   query?: {
@@ -3070,22 +3057,9 @@ export type SessionDiffData = {
   url: "/session/{sessionID}/diff"
 }
 
-export type SessionDiffErrors = {
-  /**
-   * Bad request
-   */
-  400: BadRequestError
-  /**
-   * Not found
-   */
-  404: NotFoundError
-}
-
-export type SessionDiffError = SessionDiffErrors[keyof SessionDiffErrors]
-
 export type SessionDiffResponses = {
   /**
-   * List of diffs
+   * Successfully retrieved diff
    */
   200: Array<FileDiff>
 }
@@ -3757,47 +3731,6 @@ export type QuestionRejectResponses = {
 
 export type QuestionRejectResponse = QuestionRejectResponses[keyof QuestionRejectResponses]
 
-export type CommandListData = {
-  body?: never
-  path?: never
-  query?: {
-    directory?: string
-  }
-  url: "/command"
-}
-
-export type CommandListResponses = {
-  /**
-   * List of commands
-   */
-  200: Array<Command>
-}
-
-export type CommandListResponse = CommandListResponses[keyof CommandListResponses]
-
-export type ConfigProvidersData = {
-  body?: never
-  path?: never
-  query?: {
-    directory?: string
-  }
-  url: "/config/providers"
-}
-
-export type ConfigProvidersResponses = {
-  /**
-   * List of providers
-   */
-  200: {
-    providers: Array<Provider>
-    default: {
-      [key: string]: string
-    }
-  }
-}
-
-export type ConfigProvidersResponse = ConfigProvidersResponses[keyof ConfigProvidersResponses]
-
 export type ProviderListData = {
   body?: never
   path?: never
@@ -4112,70 +4045,6 @@ export type FileStatusResponses = {
 
 export type FileStatusResponse = FileStatusResponses[keyof FileStatusResponses]
 
-export type AppLogData = {
-  body?: {
-    /**
-     * Service name for the log entry
-     */
-    service: string
-    /**
-     * Log level
-     */
-    level: "debug" | "info" | "error" | "warn"
-    /**
-     * Log message
-     */
-    message: string
-    /**
-     * Additional metadata for the log entry
-     */
-    extra?: {
-      [key: string]: unknown
-    }
-  }
-  path?: never
-  query?: {
-    directory?: string
-  }
-  url: "/log"
-}
-
-export type AppLogErrors = {
-  /**
-   * Bad request
-   */
-  400: BadRequestError
-}
-
-export type AppLogError = AppLogErrors[keyof AppLogErrors]
-
-export type AppLogResponses = {
-  /**
-   * Log entry written successfully
-   */
-  200: boolean
-}
-
-export type AppLogResponse = AppLogResponses[keyof AppLogResponses]
-
-export type AppAgentsData = {
-  body?: never
-  path?: never
-  query?: {
-    directory?: string
-  }
-  url: "/agent"
-}
-
-export type AppAgentsResponses = {
-  /**
-   * List of agents
-   */
-  200: Array<Agent>
-}
-
-export type AppAgentsResponse = AppAgentsResponses[keyof AppAgentsResponses]
-
 export type McpStatusData = {
   body?: never
   path?: never
@@ -4408,63 +4277,6 @@ export type McpDisconnectResponses = {
 
 export type McpDisconnectResponse = McpDisconnectResponses[keyof McpDisconnectResponses]
 
-export type ExperimentalResourceListData = {
-  body?: never
-  path?: never
-  query?: {
-    directory?: string
-  }
-  url: "/experimental/resource"
-}
-
-export type ExperimentalResourceListResponses = {
-  /**
-   * MCP resources
-   */
-  200: {
-    [key: string]: McpResource
-  }
-}
-
-export type ExperimentalResourceListResponse =
-  ExperimentalResourceListResponses[keyof ExperimentalResourceListResponses]
-
-export type LspStatusData = {
-  body?: never
-  path?: never
-  query?: {
-    directory?: string
-  }
-  url: "/lsp"
-}
-
-export type LspStatusResponses = {
-  /**
-   * LSP server status
-   */
-  200: Array<LspStatus>
-}
-
-export type LspStatusResponse = LspStatusResponses[keyof LspStatusResponses]
-
-export type FormatterStatusData = {
-  body?: never
-  path?: never
-  query?: {
-    directory?: string
-  }
-  url: "/formatter"
-}
-
-export type FormatterStatusResponses = {
-  /**
-   * Formatter status
-   */
-  200: Array<FormatterStatus>
-}
-
-export type FormatterStatusResponse = FormatterStatusResponses[keyof FormatterStatusResponses]
-
 export type TuiAppendPromptData = {
   body?: {
     text: string
@@ -4759,6 +4571,200 @@ export type TuiControlResponseResponses = {
 
 export type TuiControlResponseResponse = TuiControlResponseResponses[keyof TuiControlResponseResponses]
 
+export type InstanceDisposeData = {
+  body?: never
+  path?: never
+  query?: {
+    directory?: string
+  }
+  url: "/instance/dispose"
+}
+
+export type InstanceDisposeResponses = {
+  /**
+   * Instance disposed
+   */
+  200: boolean
+}
+
+export type InstanceDisposeResponse = InstanceDisposeResponses[keyof InstanceDisposeResponses]
+
+export type PathGetData = {
+  body?: never
+  path?: never
+  query?: {
+    directory?: string
+  }
+  url: "/path"
+}
+
+export type PathGetResponses = {
+  /**
+   * Path
+   */
+  200: Path
+}
+
+export type PathGetResponse = PathGetResponses[keyof PathGetResponses]
+
+export type VcsGetData = {
+  body?: never
+  path?: never
+  query?: {
+    directory?: string
+  }
+  url: "/vcs"
+}
+
+export type VcsGetResponses = {
+  /**
+   * VCS info
+   */
+  200: VcsInfo
+}
+
+export type VcsGetResponse = VcsGetResponses[keyof VcsGetResponses]
+
+export type CommandListData = {
+  body?: never
+  path?: never
+  query?: {
+    directory?: string
+  }
+  url: "/command"
+}
+
+export type CommandListResponses = {
+  /**
+   * List of commands
+   */
+  200: Array<Command>
+}
+
+export type CommandListResponse = CommandListResponses[keyof CommandListResponses]
+
+export type AppLogData = {
+  body?: {
+    /**
+     * Service name for the log entry
+     */
+    service: string
+    /**
+     * Log level
+     */
+    level: "debug" | "info" | "error" | "warn"
+    /**
+     * Log message
+     */
+    message: string
+    /**
+     * Additional metadata for the log entry
+     */
+    extra?: {
+      [key: string]: unknown
+    }
+  }
+  path?: never
+  query?: {
+    directory?: string
+  }
+  url: "/log"
+}
+
+export type AppLogErrors = {
+  /**
+   * Bad request
+   */
+  400: BadRequestError
+}
+
+export type AppLogError = AppLogErrors[keyof AppLogErrors]
+
+export type AppLogResponses = {
+  /**
+   * Log entry written successfully
+   */
+  200: boolean
+}
+
+export type AppLogResponse = AppLogResponses[keyof AppLogResponses]
+
+export type AppAgentsData = {
+  body?: never
+  path?: never
+  query?: {
+    directory?: string
+  }
+  url: "/agent"
+}
+
+export type AppAgentsResponses = {
+  /**
+   * List of agents
+   */
+  200: Array<Agent>
+}
+
+export type AppAgentsResponse = AppAgentsResponses[keyof AppAgentsResponses]
+
+export type AppSkillsData = {
+  body?: never
+  path?: never
+  query?: {
+    directory?: string
+  }
+  url: "/skill"
+}
+
+export type AppSkillsResponses = {
+  /**
+   * List of skills
+   */
+  200: Array<{
+    name: string
+    description: string
+    location: string
+  }>
+}
+
+export type AppSkillsResponse = AppSkillsResponses[keyof AppSkillsResponses]
+
+export type LspStatusData = {
+  body?: never
+  path?: never
+  query?: {
+    directory?: string
+  }
+  url: "/lsp"
+}
+
+export type LspStatusResponses = {
+  /**
+   * LSP server status
+   */
+  200: Array<LspStatus>
+}
+
+export type LspStatusResponse = LspStatusResponses[keyof LspStatusResponses]
+
+export type FormatterStatusData = {
+  body?: never
+  path?: never
+  query?: {
+    directory?: string
+  }
+  url: "/formatter"
+}
+
+export type FormatterStatusResponses = {
+  /**
+   * Formatter status
+   */
+  200: Array<FormatterStatus>
+}
+
+export type FormatterStatusResponse = FormatterStatusResponses[keyof FormatterStatusResponses]
+
 export type AuthSetData = {
   body?: Auth
   path: {

Разница между файлами не показана из-за своего большого размера
+ 470 - 441
packages/sdk/openapi.json


Некоторые файлы не были показаны из-за большого количества измененных файлов