refactor(core): split out instance and route through workspaces (#19335)
This commit is contained in:
parent
e528ed5d86
commit
a76be695c7
8 changed files with 622 additions and 598 deletions
|
|
@ -3,6 +3,8 @@ import { Flag } from "../flag/flag"
|
||||||
import { getAdaptor } from "./adaptors"
|
import { getAdaptor } from "./adaptors"
|
||||||
import { WorkspaceID } from "./schema"
|
import { WorkspaceID } from "./schema"
|
||||||
import { Workspace } from "./workspace"
|
import { Workspace } from "./workspace"
|
||||||
|
import { InstanceRoutes } from "../server/instance"
|
||||||
|
import { lazy } from "../util/lazy"
|
||||||
|
|
||||||
type Rule = { method?: string; path: string; exact?: boolean; action: "local" | "forward" }
|
type Rule = { method?: string; path: string; exact?: boolean; action: "local" | "forward" }
|
||||||
|
|
||||||
|
|
@ -20,16 +22,25 @@ function local(method: string, path: string) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
async function routeRequest(req: Request) {
|
const routes = lazy(() => InstanceRoutes())
|
||||||
const url = new URL(req.url)
|
|
||||||
const raw = url.searchParams.get("workspace") || req.headers.get("x-opencode-workspace")
|
|
||||||
|
|
||||||
if (!raw) return
|
export const WorkspaceRouterMiddleware: MiddlewareHandler = async (c) => {
|
||||||
|
if (!Flag.OPENCODE_EXPERIMENTAL_WORKSPACES) {
|
||||||
|
return routes().fetch(c.req.raw, c.env)
|
||||||
|
}
|
||||||
|
|
||||||
if (local(req.method, url.pathname)) return
|
const url = new URL(c.req.url)
|
||||||
|
const raw = url.searchParams.get("workspace")
|
||||||
|
|
||||||
|
if (!raw) {
|
||||||
|
return routes().fetch(c.req.raw, c.env)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (local(c.req.method, url.pathname)) {
|
||||||
|
return routes().fetch(c.req.raw, c.env)
|
||||||
|
}
|
||||||
|
|
||||||
const workspaceID = WorkspaceID.make(raw)
|
const workspaceID = WorkspaceID.make(raw)
|
||||||
|
|
||||||
const workspace = await Workspace.get(workspaceID)
|
const workspace = await Workspace.get(workspaceID)
|
||||||
if (!workspace) {
|
if (!workspace) {
|
||||||
return new Response(`Workspace not found: ${workspaceID}`, {
|
return new Response(`Workspace not found: ${workspaceID}`, {
|
||||||
|
|
@ -41,27 +52,13 @@ async function routeRequest(req: Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const adaptor = await getAdaptor(workspace.type)
|
const adaptor = await getAdaptor(workspace.type)
|
||||||
|
const headers = new Headers(c.req.raw.headers)
|
||||||
const headers = new Headers(req.headers)
|
|
||||||
headers.delete("x-opencode-workspace")
|
headers.delete("x-opencode-workspace")
|
||||||
|
|
||||||
return adaptor.fetch(workspace, `${url.pathname}${url.search}`, {
|
return adaptor.fetch(workspace, `${url.pathname}${url.search}`, {
|
||||||
method: req.method,
|
method: c.req.method,
|
||||||
body: req.method === "GET" || req.method === "HEAD" ? undefined : await req.arrayBuffer(),
|
body: c.req.method === "GET" || c.req.method === "HEAD" ? undefined : await c.req.raw.arrayBuffer(),
|
||||||
signal: req.signal,
|
signal: c.req.raw.signal,
|
||||||
headers,
|
headers,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WorkspaceRouterMiddleware: MiddlewareHandler = async (c, next) => {
|
|
||||||
// Only available in development for now
|
|
||||||
if (!Flag.OPENCODE_EXPERIMENTAL_WORKSPACES) {
|
|
||||||
return next()
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await routeRequest(c.req.raw)
|
|
||||||
if (response) {
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
return next()
|
|
||||||
}
|
|
||||||
|
|
|
||||||
307
packages/opencode/src/server/instance.ts
Normal file
307
packages/opencode/src/server/instance.ts
Normal file
|
|
@ -0,0 +1,307 @@
|
||||||
|
import { describeRoute, resolver } from "hono-openapi"
|
||||||
|
import { Hono } from "hono"
|
||||||
|
import { proxy } from "hono/proxy"
|
||||||
|
import z from "zod"
|
||||||
|
import { createHash } from "node:crypto"
|
||||||
|
import { Log } from "../util/log"
|
||||||
|
import { Format } from "../format"
|
||||||
|
import { TuiRoutes } from "./routes/tui"
|
||||||
|
import { Instance } from "../project/instance"
|
||||||
|
import { Vcs } from "../project/vcs"
|
||||||
|
import { Agent } from "../agent/agent"
|
||||||
|
import { Skill } from "../skill"
|
||||||
|
import { Global } from "../global"
|
||||||
|
import { LSP } from "../lsp"
|
||||||
|
import { Command } from "../command"
|
||||||
|
import { Flag } from "../flag/flag"
|
||||||
|
import { Filesystem } from "@/util/filesystem"
|
||||||
|
import { QuestionRoutes } from "./routes/question"
|
||||||
|
import { PermissionRoutes } from "./routes/permission"
|
||||||
|
import { ProjectRoutes } from "./routes/project"
|
||||||
|
import { SessionRoutes } from "./routes/session"
|
||||||
|
import { PtyRoutes } from "./routes/pty"
|
||||||
|
import { McpRoutes } from "./routes/mcp"
|
||||||
|
import { FileRoutes } from "./routes/file"
|
||||||
|
import { ConfigRoutes } from "./routes/config"
|
||||||
|
import { ExperimentalRoutes } from "./routes/experimental"
|
||||||
|
import { ProviderRoutes } from "./routes/provider"
|
||||||
|
import { EventRoutes } from "./routes/event"
|
||||||
|
import { InstanceBootstrap } from "../project/bootstrap"
|
||||||
|
import { errorHandler } from "./middleware"
|
||||||
|
|
||||||
|
const log = Log.create({ service: "server" })
|
||||||
|
|
||||||
|
const embeddedUIPromise = Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI
|
||||||
|
? Promise.resolve(null)
|
||||||
|
: // @ts-expect-error - generated file at build time
|
||||||
|
import("opencode-web-ui.gen.ts").then((module) => module.default as Record<string, string>).catch(() => null)
|
||||||
|
|
||||||
|
const DEFAULT_CSP =
|
||||||
|
"default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; media-src 'self' data:; connect-src 'self' data:"
|
||||||
|
|
||||||
|
const csp = (hash = "") =>
|
||||||
|
`default-src 'self'; script-src 'self' 'wasm-unsafe-eval'${hash ? ` 'sha256-${hash}'` : ""}; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; media-src 'self' data:; connect-src 'self' data:`
|
||||||
|
|
||||||
|
export const InstanceRoutes = (app?: Hono) =>
|
||||||
|
(app ?? new Hono())
|
||||||
|
.onError(errorHandler(log))
|
||||||
|
.use(async (c, next) => {
|
||||||
|
const raw = c.req.query("directory") || c.req.header("x-opencode-directory") || process.cwd()
|
||||||
|
const directory = Filesystem.resolve(
|
||||||
|
(() => {
|
||||||
|
try {
|
||||||
|
return decodeURIComponent(raw)
|
||||||
|
} catch {
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
)
|
||||||
|
|
||||||
|
return Instance.provide({
|
||||||
|
directory,
|
||||||
|
init: InstanceBootstrap,
|
||||||
|
async fn() {
|
||||||
|
return next()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.route("/project", ProjectRoutes())
|
||||||
|
.route("/pty", PtyRoutes())
|
||||||
|
.route("/config", ConfigRoutes())
|
||||||
|
.route("/experimental", ExperimentalRoutes())
|
||||||
|
.route("/session", SessionRoutes())
|
||||||
|
.route("/permission", PermissionRoutes())
|
||||||
|
.route("/question", QuestionRoutes())
|
||||||
|
.route("/provider", ProviderRoutes())
|
||||||
|
.route("/", FileRoutes())
|
||||||
|
.route("/", EventRoutes())
|
||||||
|
.route("/mcp", McpRoutes())
|
||||||
|
.route("/tui", TuiRoutes())
|
||||||
|
.post(
|
||||||
|
"/instance/dispose",
|
||||||
|
describeRoute({
|
||||||
|
summary: "Dispose instance",
|
||||||
|
description: "Clean up and dispose the current OpenCode instance, releasing all resources.",
|
||||||
|
operationId: "instance.dispose",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Instance disposed",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: resolver(z.boolean()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (c) => {
|
||||||
|
await Instance.dispose()
|
||||||
|
return c.json(true)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.get(
|
||||||
|
"/path",
|
||||||
|
describeRoute({
|
||||||
|
summary: "Get paths",
|
||||||
|
description: "Retrieve the current working directory and related path information for the OpenCode instance.",
|
||||||
|
operationId: "path.get",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Path",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: resolver(
|
||||||
|
z
|
||||||
|
.object({
|
||||||
|
home: z.string(),
|
||||||
|
state: z.string(),
|
||||||
|
config: z.string(),
|
||||||
|
worktree: z.string(),
|
||||||
|
directory: z.string(),
|
||||||
|
})
|
||||||
|
.meta({
|
||||||
|
ref: "Path",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (c) => {
|
||||||
|
return c.json({
|
||||||
|
home: Global.Path.home,
|
||||||
|
state: Global.Path.state,
|
||||||
|
config: Global.Path.config,
|
||||||
|
worktree: Instance.worktree,
|
||||||
|
directory: Instance.directory,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.get(
|
||||||
|
"/vcs",
|
||||||
|
describeRoute({
|
||||||
|
summary: "Get VCS info",
|
||||||
|
description: "Retrieve version control system (VCS) information for the current project, such as git branch.",
|
||||||
|
operationId: "vcs.get",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "VCS info",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: resolver(Vcs.Info),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (c) => {
|
||||||
|
const branch = await Vcs.branch()
|
||||||
|
return c.json({
|
||||||
|
branch,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.get(
|
||||||
|
"/command",
|
||||||
|
describeRoute({
|
||||||
|
summary: "List commands",
|
||||||
|
description: "Get a list of all available commands in the OpenCode system.",
|
||||||
|
operationId: "command.list",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "List of commands",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: resolver(Command.Info.array()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (c) => {
|
||||||
|
const commands = await Command.list()
|
||||||
|
return c.json(commands)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.get(
|
||||||
|
"/agent",
|
||||||
|
describeRoute({
|
||||||
|
summary: "List agents",
|
||||||
|
description: "Get a list of all available AI agents in the OpenCode system.",
|
||||||
|
operationId: "app.agents",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "List of agents",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: resolver(Agent.Info.array()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (c) => {
|
||||||
|
const modes = await Agent.list()
|
||||||
|
return c.json(modes)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.get(
|
||||||
|
"/skill",
|
||||||
|
describeRoute({
|
||||||
|
summary: "List skills",
|
||||||
|
description: "Get a list of all available skills in the OpenCode system.",
|
||||||
|
operationId: "app.skills",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "List of skills",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: resolver(Skill.Info.array()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (c) => {
|
||||||
|
const skills = await Skill.all()
|
||||||
|
return c.json(skills)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.get(
|
||||||
|
"/lsp",
|
||||||
|
describeRoute({
|
||||||
|
summary: "Get LSP status",
|
||||||
|
description: "Get LSP server status",
|
||||||
|
operationId: "lsp.status",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "LSP server status",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: resolver(LSP.Status.array()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (c) => {
|
||||||
|
return c.json(await LSP.status())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.get(
|
||||||
|
"/formatter",
|
||||||
|
describeRoute({
|
||||||
|
summary: "Get formatter status",
|
||||||
|
description: "Get formatter status",
|
||||||
|
operationId: "formatter.status",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Formatter status",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: resolver(Format.Status.array()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (c) => {
|
||||||
|
return c.json(await Format.status())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.all("/*", async (c) => {
|
||||||
|
const embeddedWebUI = await embeddedUIPromise
|
||||||
|
const path = c.req.path
|
||||||
|
|
||||||
|
if (embeddedWebUI) {
|
||||||
|
const match = embeddedWebUI[path.replace(/^\//, "")] ?? embeddedWebUI["index.html"] ?? null
|
||||||
|
if (!match) return c.json({ error: "Not Found" }, 404)
|
||||||
|
const file = Bun.file(match)
|
||||||
|
if (await file.exists()) {
|
||||||
|
c.header("Content-Type", file.type)
|
||||||
|
if (file.type.startsWith("text/html")) {
|
||||||
|
c.header("Content-Security-Policy", DEFAULT_CSP)
|
||||||
|
}
|
||||||
|
return c.body(await file.arrayBuffer())
|
||||||
|
} else {
|
||||||
|
return c.json({ error: "Not Found" }, 404)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const response = await proxy(`https://app.opencode.ai${path}`, {
|
||||||
|
...c.req,
|
||||||
|
headers: {
|
||||||
|
...c.req.raw.headers,
|
||||||
|
host: "app.opencode.ai",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const match = response.headers.get("content-type")?.includes("text/html")
|
||||||
|
? (await response.clone().text()).match(
|
||||||
|
/<script\b(?![^>]*\bsrc\s*=)[^>]*\bid=(['"])oc-theme-preload-script\1[^>]*>([\s\S]*?)<\/script>/i,
|
||||||
|
)
|
||||||
|
: undefined
|
||||||
|
const hash = match ? createHash("sha256").update(match[2]).digest("base64") : ""
|
||||||
|
response.headers.set("Content-Security-Policy", csp(hash))
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
})
|
||||||
29
packages/opencode/src/server/middleware.ts
Normal file
29
packages/opencode/src/server/middleware.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { Provider } from "../provider/provider"
|
||||||
|
import { NamedError } from "@opencode-ai/util/error"
|
||||||
|
import { NotFoundError } from "../storage/db"
|
||||||
|
import type { ContentfulStatusCode } from "hono/utils/http-status"
|
||||||
|
import type { ErrorHandler } from "hono"
|
||||||
|
import { HTTPException } from "hono/http-exception"
|
||||||
|
import type { Log } from "../util/log"
|
||||||
|
|
||||||
|
export function errorHandler(log: Log.Logger): ErrorHandler {
|
||||||
|
return (err, c) => {
|
||||||
|
log.error("failed", {
|
||||||
|
error: err,
|
||||||
|
})
|
||||||
|
if (err instanceof NamedError) {
|
||||||
|
let status: ContentfulStatusCode
|
||||||
|
if (err instanceof NotFoundError) status = 404
|
||||||
|
else if (err instanceof Provider.ModelNotFoundError) status = 400
|
||||||
|
else if (err.name === "ProviderAuthValidationFailed") status = 400
|
||||||
|
else if (err.name.startsWith("Worktree")) status = 400
|
||||||
|
else status = 500
|
||||||
|
return c.json(err.toObject(), { status })
|
||||||
|
}
|
||||||
|
if (err instanceof HTTPException) return err.getResponse()
|
||||||
|
const message = err instanceof Error && err.stack ? err.stack : err.toString()
|
||||||
|
return c.json(new NamedError.Unknown({ message }).toObject(), {
|
||||||
|
status: 500,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,12 +4,11 @@ import { streamSSE } from "hono/streaming"
|
||||||
import { Log } from "@/util/log"
|
import { Log } from "@/util/log"
|
||||||
import { BusEvent } from "@/bus/bus-event"
|
import { BusEvent } from "@/bus/bus-event"
|
||||||
import { Bus } from "@/bus"
|
import { Bus } from "@/bus"
|
||||||
import { lazy } from "../../util/lazy"
|
|
||||||
import { AsyncQueue } from "../../util/queue"
|
import { AsyncQueue } from "../../util/queue"
|
||||||
|
|
||||||
const log = Log.create({ service: "server" })
|
const log = Log.create({ service: "server" })
|
||||||
|
|
||||||
export const EventRoutes = lazy(() =>
|
export const EventRoutes = () =>
|
||||||
new Hono().get(
|
new Hono().get(
|
||||||
"/event",
|
"/event",
|
||||||
describeRoute({
|
describeRoute({
|
||||||
|
|
@ -81,5 +80,4 @@ export const EventRoutes = lazy(() =>
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
),
|
)
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -1,67 +1,30 @@
|
||||||
import { createHash } from "node:crypto"
|
|
||||||
import { Log } from "../util/log"
|
import { Log } from "../util/log"
|
||||||
import { describeRoute, generateSpecs, validator, resolver, openAPIRouteHandler } from "hono-openapi"
|
import { describeRoute, generateSpecs, validator, resolver, openAPIRouteHandler } from "hono-openapi"
|
||||||
import { Hono } from "hono"
|
import { Hono } from "hono"
|
||||||
import { compress } from "hono/compress"
|
import { compress } from "hono/compress"
|
||||||
import { cors } from "hono/cors"
|
import { cors } from "hono/cors"
|
||||||
import { proxy } from "hono/proxy"
|
|
||||||
import { basicAuth } from "hono/basic-auth"
|
import { basicAuth } from "hono/basic-auth"
|
||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { Provider } from "../provider/provider"
|
|
||||||
import { NamedError } from "@opencode-ai/util/error"
|
|
||||||
import { LSP } from "../lsp"
|
|
||||||
import { Format } from "../format"
|
|
||||||
import { TuiRoutes } from "./routes/tui"
|
|
||||||
import { Instance } from "../project/instance"
|
|
||||||
import { Vcs } from "../project/vcs"
|
|
||||||
import { Agent } from "../agent/agent"
|
|
||||||
import { Skill } from "../skill"
|
|
||||||
import { Auth } from "../auth"
|
import { Auth } from "../auth"
|
||||||
import { Flag } from "../flag/flag"
|
import { Flag } from "../flag/flag"
|
||||||
import { Command } from "../command"
|
|
||||||
import { Global } from "../global"
|
|
||||||
import { WorkspaceID } from "../control-plane/schema"
|
|
||||||
import { ProviderID } from "../provider/schema"
|
import { ProviderID } from "../provider/schema"
|
||||||
import { WorkspaceRouterMiddleware } from "../control-plane/workspace-router-middleware"
|
import { WorkspaceRouterMiddleware } from "../control-plane/workspace-router-middleware"
|
||||||
import { ProjectRoutes } from "./routes/project"
|
|
||||||
import { SessionRoutes } from "./routes/session"
|
|
||||||
import { PtyRoutes } from "./routes/pty"
|
|
||||||
import { McpRoutes } from "./routes/mcp"
|
|
||||||
import { FileRoutes } from "./routes/file"
|
|
||||||
import { ConfigRoutes } from "./routes/config"
|
|
||||||
import { ExperimentalRoutes } from "./routes/experimental"
|
|
||||||
import { ProviderRoutes } from "./routes/provider"
|
|
||||||
import { EventRoutes } from "./routes/event"
|
|
||||||
import { InstanceBootstrap } from "../project/bootstrap"
|
|
||||||
import { NotFoundError } from "../storage/db"
|
|
||||||
import type { ContentfulStatusCode } from "hono/utils/http-status"
|
|
||||||
import { websocket } from "hono/bun"
|
import { websocket } from "hono/bun"
|
||||||
import { HTTPException } from "hono/http-exception"
|
|
||||||
import { errors } from "./error"
|
import { errors } from "./error"
|
||||||
import { Filesystem } from "@/util/filesystem"
|
|
||||||
import { QuestionRoutes } from "./routes/question"
|
|
||||||
import { PermissionRoutes } from "./routes/permission"
|
|
||||||
import { GlobalRoutes } from "./routes/global"
|
import { GlobalRoutes } from "./routes/global"
|
||||||
import { MDNS } from "./mdns"
|
import { MDNS } from "./mdns"
|
||||||
import { lazy } from "@/util/lazy"
|
import { lazy } from "@/util/lazy"
|
||||||
|
import { errorHandler } from "./middleware"
|
||||||
|
import { InstanceRoutes } from "./instance"
|
||||||
import { initProjectors } from "./projectors"
|
import { initProjectors } from "./projectors"
|
||||||
|
|
||||||
// @ts-ignore This global is needed to prevent ai-sdk from logging warnings to stdout https://github.com/vercel/ai/blob/2dc67e0ef538307f21368db32d5a12345d98831b/packages/ai/src/logger/log-warnings.ts#L85
|
// @ts-ignore This global is needed to prevent ai-sdk from logging warnings to stdout https://github.com/vercel/ai/blob/2dc67e0ef538307f21368db32d5a12345d98831b/packages/ai/src/logger/log-warnings.ts#L85
|
||||||
globalThis.AI_SDK_LOG_WARNINGS = false
|
globalThis.AI_SDK_LOG_WARNINGS = false
|
||||||
|
|
||||||
const csp = (hash = "") =>
|
|
||||||
`default-src 'self'; script-src 'self' 'wasm-unsafe-eval'${hash ? ` 'sha256-${hash}'` : ""}; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; media-src 'self' data:; connect-src 'self' data:`
|
|
||||||
|
|
||||||
initProjectors()
|
initProjectors()
|
||||||
|
|
||||||
export namespace Server {
|
export namespace Server {
|
||||||
const log = Log.create({ service: "server" })
|
const log = Log.create({ service: "server" })
|
||||||
const DEFAULT_CSP =
|
|
||||||
"default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; media-src 'self' data:; connect-src 'self' data:"
|
|
||||||
const embeddedUIPromise = Flag.OPENCODE_DISABLE_EMBEDDED_WEB_UI
|
|
||||||
? Promise.resolve(null)
|
|
||||||
: // @ts-expect-error - generated file at build time
|
|
||||||
import("opencode-web-ui.gen.ts").then((module) => module.default as Record<string, string>).catch(() => null)
|
|
||||||
|
|
||||||
const zipped = compress()
|
const zipped = compress()
|
||||||
|
|
||||||
|
|
@ -71,30 +34,12 @@ export namespace Server {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Default = lazy(() => createApp({}))
|
export const Default = lazy(() => ControlPlaneRoutes())
|
||||||
|
|
||||||
export const createApp = (opts: { cors?: string[] }): Hono => {
|
export const ControlPlaneRoutes = (opts?: { cors?: string[] }): Hono => {
|
||||||
const app = new Hono()
|
const app = new Hono()
|
||||||
return app
|
return app
|
||||||
.onError((err, c) => {
|
.onError(errorHandler(log))
|
||||||
log.error("failed", {
|
|
||||||
error: err,
|
|
||||||
})
|
|
||||||
if (err instanceof NamedError) {
|
|
||||||
let status: ContentfulStatusCode
|
|
||||||
if (err instanceof NotFoundError) status = 404
|
|
||||||
else if (err instanceof Provider.ModelNotFoundError) status = 400
|
|
||||||
else if (err.name === "ProviderAuthValidationFailed") status = 400
|
|
||||||
else if (err.name.startsWith("Worktree")) status = 400
|
|
||||||
else status = 500
|
|
||||||
return c.json(err.toObject(), { status })
|
|
||||||
}
|
|
||||||
if (err instanceof HTTPException) return err.getResponse()
|
|
||||||
const message = err instanceof Error && err.stack ? err.stack : err.toString()
|
|
||||||
return c.json(new NamedError.Unknown({ message }).toObject(), {
|
|
||||||
status: 500,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.use((c, next) => {
|
.use((c, next) => {
|
||||||
// Allow CORS preflight requests to succeed without auth.
|
// Allow CORS preflight requests to succeed without auth.
|
||||||
// Browser clients sending Authorization headers will preflight with OPTIONS.
|
// Browser clients sending Authorization headers will preflight with OPTIONS.
|
||||||
|
|
@ -105,8 +50,8 @@ export namespace Server {
|
||||||
return basicAuth({ username, password })(c, next)
|
return basicAuth({ username, password })(c, next)
|
||||||
})
|
})
|
||||||
.use(async (c, next) => {
|
.use(async (c, next) => {
|
||||||
const skipLogging = c.req.path === "/log"
|
const skip = c.req.path === "/log"
|
||||||
if (!skipLogging) {
|
if (!skip) {
|
||||||
log.info("request", {
|
log.info("request", {
|
||||||
method: c.req.method,
|
method: c.req.method,
|
||||||
path: c.req.path,
|
path: c.req.path,
|
||||||
|
|
@ -117,7 +62,7 @@ export namespace Server {
|
||||||
path: c.req.path,
|
path: c.req.path,
|
||||||
})
|
})
|
||||||
await next()
|
await next()
|
||||||
if (!skipLogging) {
|
if (!skip) {
|
||||||
timer.stop()
|
timer.stop()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -215,27 +160,6 @@ export namespace Server {
|
||||||
return c.json(true)
|
return c.json(true)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.use(async (c, next) => {
|
|
||||||
if (c.req.path === "/log") return next()
|
|
||||||
const raw = c.req.query("directory") || c.req.header("x-opencode-directory") || process.cwd()
|
|
||||||
const directory = Filesystem.resolve(
|
|
||||||
(() => {
|
|
||||||
try {
|
|
||||||
return decodeURIComponent(raw)
|
|
||||||
} catch {
|
|
||||||
return raw
|
|
||||||
}
|
|
||||||
})(),
|
|
||||||
)
|
|
||||||
|
|
||||||
return Instance.provide({
|
|
||||||
directory,
|
|
||||||
init: InstanceBootstrap,
|
|
||||||
async fn() {
|
|
||||||
return next()
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.get(
|
.get(
|
||||||
"/doc",
|
"/doc",
|
||||||
openAPIRouteHandler(app, {
|
openAPIRouteHandler(app, {
|
||||||
|
|
@ -258,126 +182,6 @@ export namespace Server {
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.use(WorkspaceRouterMiddleware)
|
|
||||||
.route("/project", ProjectRoutes())
|
|
||||||
.route("/pty", PtyRoutes())
|
|
||||||
.route("/config", ConfigRoutes())
|
|
||||||
.route("/experimental", ExperimentalRoutes())
|
|
||||||
.route("/session", SessionRoutes())
|
|
||||||
.route("/permission", PermissionRoutes())
|
|
||||||
.route("/question", QuestionRoutes())
|
|
||||||
.route("/provider", ProviderRoutes())
|
|
||||||
.route("/", FileRoutes())
|
|
||||||
.route("/", EventRoutes())
|
|
||||||
.route("/mcp", McpRoutes())
|
|
||||||
.route("/tui", TuiRoutes())
|
|
||||||
.post(
|
|
||||||
"/instance/dispose",
|
|
||||||
describeRoute({
|
|
||||||
summary: "Dispose instance",
|
|
||||||
description: "Clean up and dispose the current OpenCode instance, releasing all resources.",
|
|
||||||
operationId: "instance.dispose",
|
|
||||||
responses: {
|
|
||||||
200: {
|
|
||||||
description: "Instance disposed",
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: resolver(z.boolean()),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
async (c) => {
|
|
||||||
await Instance.dispose()
|
|
||||||
return c.json(true)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.get(
|
|
||||||
"/path",
|
|
||||||
describeRoute({
|
|
||||||
summary: "Get paths",
|
|
||||||
description: "Retrieve the current working directory and related path information for the OpenCode instance.",
|
|
||||||
operationId: "path.get",
|
|
||||||
responses: {
|
|
||||||
200: {
|
|
||||||
description: "Path",
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: resolver(
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
home: z.string(),
|
|
||||||
state: z.string(),
|
|
||||||
config: z.string(),
|
|
||||||
worktree: z.string(),
|
|
||||||
directory: z.string(),
|
|
||||||
})
|
|
||||||
.meta({
|
|
||||||
ref: "Path",
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
async (c) => {
|
|
||||||
return c.json({
|
|
||||||
home: Global.Path.home,
|
|
||||||
state: Global.Path.state,
|
|
||||||
config: Global.Path.config,
|
|
||||||
worktree: Instance.worktree,
|
|
||||||
directory: Instance.directory,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.get(
|
|
||||||
"/vcs",
|
|
||||||
describeRoute({
|
|
||||||
summary: "Get VCS info",
|
|
||||||
description: "Retrieve version control system (VCS) information for the current project, such as git branch.",
|
|
||||||
operationId: "vcs.get",
|
|
||||||
responses: {
|
|
||||||
200: {
|
|
||||||
description: "VCS info",
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: resolver(Vcs.Info),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
async (c) => {
|
|
||||||
const branch = await Vcs.branch()
|
|
||||||
return c.json({
|
|
||||||
branch,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.get(
|
|
||||||
"/command",
|
|
||||||
describeRoute({
|
|
||||||
summary: "List commands",
|
|
||||||
description: "Get a list of all available commands in the OpenCode system.",
|
|
||||||
operationId: "command.list",
|
|
||||||
responses: {
|
|
||||||
200: {
|
|
||||||
description: "List of commands",
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: resolver(Command.Info.array()),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
async (c) => {
|
|
||||||
const commands = await Command.list()
|
|
||||||
return c.json(commands)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.post(
|
.post(
|
||||||
"/log",
|
"/log",
|
||||||
describeRoute({
|
describeRoute({
|
||||||
|
|
@ -430,132 +234,21 @@ export namespace Server {
|
||||||
return c.json(true)
|
return c.json(true)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.get(
|
.use(WorkspaceRouterMiddleware)
|
||||||
"/agent",
|
}
|
||||||
describeRoute({
|
|
||||||
summary: "List agents",
|
|
||||||
description: "Get a list of all available AI agents in the OpenCode system.",
|
|
||||||
operationId: "app.agents",
|
|
||||||
responses: {
|
|
||||||
200: {
|
|
||||||
description: "List of agents",
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: resolver(Agent.Info.array()),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
async (c) => {
|
|
||||||
const modes = await Agent.list()
|
|
||||||
return c.json(modes)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.get(
|
|
||||||
"/skill",
|
|
||||||
describeRoute({
|
|
||||||
summary: "List skills",
|
|
||||||
description: "Get a list of all available skills in the OpenCode system.",
|
|
||||||
operationId: "app.skills",
|
|
||||||
responses: {
|
|
||||||
200: {
|
|
||||||
description: "List of skills",
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: resolver(Skill.Info.array()),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
async (c) => {
|
|
||||||
const skills = await Skill.all()
|
|
||||||
return c.json(skills)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.get(
|
|
||||||
"/lsp",
|
|
||||||
describeRoute({
|
|
||||||
summary: "Get LSP status",
|
|
||||||
description: "Get LSP server status",
|
|
||||||
operationId: "lsp.status",
|
|
||||||
responses: {
|
|
||||||
200: {
|
|
||||||
description: "LSP server status",
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: resolver(LSP.Status.array()),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
async (c) => {
|
|
||||||
return c.json(await LSP.status())
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.get(
|
|
||||||
"/formatter",
|
|
||||||
describeRoute({
|
|
||||||
summary: "Get formatter status",
|
|
||||||
description: "Get formatter status",
|
|
||||||
operationId: "formatter.status",
|
|
||||||
responses: {
|
|
||||||
200: {
|
|
||||||
description: "Formatter status",
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: resolver(Format.Status.array()),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
async (c) => {
|
|
||||||
return c.json(await Format.status())
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.all("/*", async (c) => {
|
|
||||||
const embeddedWebUI = await embeddedUIPromise
|
|
||||||
const path = c.req.path
|
|
||||||
|
|
||||||
if (embeddedWebUI) {
|
export function createApp(opts: { cors?: string[] }) {
|
||||||
const match = embeddedWebUI[path.replace(/^\//, "")] ?? embeddedWebUI["index.html"] ?? null
|
return ControlPlaneRoutes(opts)
|
||||||
if (!match) return c.json({ error: "Not Found" }, 404)
|
|
||||||
const file = Bun.file(match)
|
|
||||||
if (await file.exists()) {
|
|
||||||
c.header("Content-Type", file.type)
|
|
||||||
if (file.type.startsWith("text/html")) {
|
|
||||||
c.header("Content-Security-Policy", DEFAULT_CSP)
|
|
||||||
}
|
|
||||||
return c.body(await file.arrayBuffer())
|
|
||||||
} else {
|
|
||||||
return c.json({ error: "Not Found" }, 404)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const response = await proxy(`https://app.opencode.ai${path}`, {
|
|
||||||
...c.req,
|
|
||||||
headers: {
|
|
||||||
...c.req.raw.headers,
|
|
||||||
host: "app.opencode.ai",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const match = response.headers.get("content-type")?.includes("text/html")
|
|
||||||
? (await response.clone().text()).match(
|
|
||||||
/<script\b(?![^>]*\bsrc\s*=)[^>]*\bid=(['"])oc-theme-preload-script\1[^>]*>([\s\S]*?)<\/script>/i,
|
|
||||||
)
|
|
||||||
: undefined
|
|
||||||
const hash = match ? createHash("sha256").update(match[2]).digest("base64") : ""
|
|
||||||
response.headers.set("Content-Security-Policy", csp(hash))
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
}) as unknown as Hono
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function openapi() {
|
export async function openapi() {
|
||||||
// Cast to break excessive type recursion from long route chains
|
// Build a fresh app with all routes registered directly so
|
||||||
const result = await generateSpecs(Default(), {
|
// hono-openapi can see describeRoute metadata (`.route()` wraps
|
||||||
|
// handlers when the sub-app has a custom errorHandler, which
|
||||||
|
// strips the metadata symbol).
|
||||||
|
const app = ControlPlaneRoutes()
|
||||||
|
InstanceRoutes(app)
|
||||||
|
const result = await generateSpecs(app, {
|
||||||
documentation: {
|
documentation: {
|
||||||
info: {
|
info: {
|
||||||
title: "opencode",
|
title: "opencode",
|
||||||
|
|
@ -579,7 +272,7 @@ export namespace Server {
|
||||||
cors?: string[]
|
cors?: string[]
|
||||||
}) {
|
}) {
|
||||||
url = new URL(`http://${opts.hostname}:${opts.port}`)
|
url = new URL(`http://${opts.hostname}:${opts.port}`)
|
||||||
const app = createApp(opts)
|
const app = ControlPlaneRoutes({ cors: opts.cors })
|
||||||
const args = {
|
const args = {
|
||||||
hostname: opts.hostname,
|
hostname: opts.hostname,
|
||||||
idleTimeout: 0,
|
idleTimeout: 0,
|
||||||
|
|
|
||||||
|
|
@ -411,6 +411,113 @@ export class Auth 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
|
||||||
|
workspace?: 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: "query", key: "workspace" },
|
||||||
|
{ 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
|
||||||
|
workspace?: string
|
||||||
|
},
|
||||||
|
options?: Options<never, ThrowOnError>,
|
||||||
|
) {
|
||||||
|
const params = buildClientParams(
|
||||||
|
[parameters],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
args: [
|
||||||
|
{ in: "query", key: "directory" },
|
||||||
|
{ in: "query", key: "workspace" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
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
|
||||||
|
workspace?: string
|
||||||
|
},
|
||||||
|
options?: Options<never, ThrowOnError>,
|
||||||
|
) {
|
||||||
|
const params = buildClientParams(
|
||||||
|
[parameters],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
args: [
|
||||||
|
{ in: "query", key: "directory" },
|
||||||
|
{ in: "query", key: "workspace" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
return (options?.client ?? this.client).get<AppSkillsResponses, unknown, ThrowOnError>({
|
||||||
|
url: "/skill",
|
||||||
|
...options,
|
||||||
|
...params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class Project extends HeyApiClient {
|
export class Project extends HeyApiClient {
|
||||||
/**
|
/**
|
||||||
* List all projects
|
* List all projects
|
||||||
|
|
@ -3773,113 +3880,6 @@ export class Command 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
|
|
||||||
workspace?: 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: "query", key: "workspace" },
|
|
||||||
{ 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
|
|
||||||
workspace?: string
|
|
||||||
},
|
|
||||||
options?: Options<never, ThrowOnError>,
|
|
||||||
) {
|
|
||||||
const params = buildClientParams(
|
|
||||||
[parameters],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
args: [
|
|
||||||
{ in: "query", key: "directory" },
|
|
||||||
{ in: "query", key: "workspace" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
)
|
|
||||||
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
|
|
||||||
workspace?: string
|
|
||||||
},
|
|
||||||
options?: Options<never, ThrowOnError>,
|
|
||||||
) {
|
|
||||||
const params = buildClientParams(
|
|
||||||
[parameters],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
args: [
|
|
||||||
{ in: "query", key: "directory" },
|
|
||||||
{ in: "query", key: "workspace" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
)
|
|
||||||
return (options?.client ?? this.client).get<AppSkillsResponses, unknown, ThrowOnError>({
|
|
||||||
url: "/skill",
|
|
||||||
...options,
|
|
||||||
...params,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Lsp extends HeyApiClient {
|
export class Lsp extends HeyApiClient {
|
||||||
/**
|
/**
|
||||||
* Get LSP status
|
* Get LSP status
|
||||||
|
|
@ -3962,6 +3962,11 @@ export class OpencodeClient extends HeyApiClient {
|
||||||
return (this._auth ??= new Auth({ client: this.client }))
|
return (this._auth ??= new Auth({ client: this.client }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _app?: App
|
||||||
|
get app(): App {
|
||||||
|
return (this._app ??= new App({ client: this.client }))
|
||||||
|
}
|
||||||
|
|
||||||
private _project?: Project
|
private _project?: Project
|
||||||
get project(): Project {
|
get project(): Project {
|
||||||
return (this._project ??= new Project({ client: this.client }))
|
return (this._project ??= new Project({ client: this.client }))
|
||||||
|
|
@ -4062,11 +4067,6 @@ export class OpencodeClient extends HeyApiClient {
|
||||||
return (this._command ??= new Command({ client: this.client }))
|
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
|
private _lsp?: Lsp
|
||||||
get lsp(): Lsp {
|
get lsp(): Lsp {
|
||||||
return (this._lsp ??= new Lsp({ client: this.client }))
|
return (this._lsp ??= new Lsp({ client: this.client }))
|
||||||
|
|
|
||||||
|
|
@ -2249,6 +2249,53 @@ export type AuthSetResponses = {
|
||||||
|
|
||||||
export type AuthSetResponse = AuthSetResponses[keyof AuthSetResponses]
|
export type AuthSetResponse = AuthSetResponses[keyof AuthSetResponses]
|
||||||
|
|
||||||
|
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
|
||||||
|
workspace?: 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 ProjectListData = {
|
export type ProjectListData = {
|
||||||
body?: never
|
body?: never
|
||||||
path?: never
|
path?: never
|
||||||
|
|
@ -5036,53 +5083,6 @@ export type CommandListResponses = {
|
||||||
|
|
||||||
export type CommandListResponse = CommandListResponses[keyof CommandListResponses]
|
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
|
|
||||||
workspace?: 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 = {
|
export type AppAgentsData = {
|
||||||
body?: never
|
body?: never
|
||||||
path?: never
|
path?: never
|
||||||
|
|
|
||||||
|
|
@ -356,6 +356,90 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/log": {
|
||||||
|
"post": {
|
||||||
|
"operationId": "app.log",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "query",
|
||||||
|
"name": "directory",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": "query",
|
||||||
|
"name": "workspace",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"summary": "Write log",
|
||||||
|
"description": "Write a log entry to the server logs with specified level and metadata.",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Log entry written successfully",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad request",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/BadRequestError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"service": {
|
||||||
|
"description": "Service name for the log entry",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"level": {
|
||||||
|
"description": "Log level",
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["debug", "info", "error", "warn"]
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"description": "Log message",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"description": "Additional metadata for the log entry",
|
||||||
|
"type": "object",
|
||||||
|
"propertyNames": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"additionalProperties": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["service", "level", "message"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-codeSamples": [
|
||||||
|
{
|
||||||
|
"lang": "js",
|
||||||
|
"source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.app.log({\n ...\n})"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/project": {
|
"/project": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "project.list",
|
"operationId": "project.list",
|
||||||
|
|
@ -6762,90 +6846,6 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/log": {
|
|
||||||
"post": {
|
|
||||||
"operationId": "app.log",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"in": "query",
|
|
||||||
"name": "directory",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"in": "query",
|
|
||||||
"name": "workspace",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"summary": "Write log",
|
|
||||||
"description": "Write a log entry to the server logs with specified level and metadata.",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Log entry written successfully",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Bad request",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/BadRequestError"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"requestBody": {
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"service": {
|
|
||||||
"description": "Service name for the log entry",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"level": {
|
|
||||||
"description": "Log level",
|
|
||||||
"type": "string",
|
|
||||||
"enum": ["debug", "info", "error", "warn"]
|
|
||||||
},
|
|
||||||
"message": {
|
|
||||||
"description": "Log message",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"extra": {
|
|
||||||
"description": "Additional metadata for the log entry",
|
|
||||||
"type": "object",
|
|
||||||
"propertyNames": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"additionalProperties": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": ["service", "level", "message"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"x-codeSamples": [
|
|
||||||
{
|
|
||||||
"lang": "js",
|
|
||||||
"source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.app.log({\n ...\n})"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/agent": {
|
"/agent": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "app.agents",
|
"operationId": "app.agents",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue