ignore: fix event type gen
This commit is contained in:
@@ -33,16 +33,16 @@ export namespace Bus {
|
||||
"type",
|
||||
registry
|
||||
.entries()
|
||||
.map(([type, def]) =>
|
||||
z
|
||||
.map(([type, def]) => {
|
||||
return z
|
||||
.object({
|
||||
type: z.literal(type),
|
||||
properties: def.properties,
|
||||
})
|
||||
.meta({
|
||||
ref: "Event" + "." + def.type,
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
.toArray() as any,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@ export namespace Plugin {
|
||||
const state = Instance.state(async () => {
|
||||
const client = createOpencodeClient({
|
||||
baseUrl: "http://localhost:4096",
|
||||
fetch: async (...args) => Server.App.fetch(...args),
|
||||
// @ts-expect-error
|
||||
fetch: async (...args) => Server.App().fetch(...args),
|
||||
})
|
||||
const config = await Config.get()
|
||||
const hooks = []
|
||||
|
||||
+1316
-1313
@@ -28,6 +28,7 @@ import { zodToJsonSchema } from "zod-to-json-schema"
|
||||
import { SessionPrompt } from "../session/prompt"
|
||||
import { SessionCompaction } from "../session/compaction"
|
||||
import { SessionRevert } from "../session/revert"
|
||||
import { lazy } from "../util/lazy"
|
||||
|
||||
const ERRORS = {
|
||||
400: {
|
||||
@@ -79,1343 +80,1345 @@ export namespace Server {
|
||||
}
|
||||
|
||||
const app = new Hono()
|
||||
export const App = app
|
||||
.onError((err, c) => {
|
||||
log.error("failed", {
|
||||
error: err,
|
||||
})
|
||||
if (err instanceof NamedError) {
|
||||
return c.json(err.toObject(), {
|
||||
export const App = lazy(() =>
|
||||
app
|
||||
.onError((err, c) => {
|
||||
log.error("failed", {
|
||||
error: err,
|
||||
})
|
||||
if (err instanceof NamedError) {
|
||||
return c.json(err.toObject(), {
|
||||
status: 400,
|
||||
})
|
||||
}
|
||||
return c.json(new NamedError.Unknown({ message: err.toString() }).toObject(), {
|
||||
status: 400,
|
||||
})
|
||||
}
|
||||
return c.json(new NamedError.Unknown({ message: err.toString() }).toObject(), {
|
||||
status: 400,
|
||||
})
|
||||
})
|
||||
.use(async (c, next) => {
|
||||
const skipLogging = c.req.path === "/log"
|
||||
if (!skipLogging) {
|
||||
log.info("request", {
|
||||
method: c.req.method,
|
||||
path: c.req.path,
|
||||
})
|
||||
}
|
||||
const start = Date.now()
|
||||
await next()
|
||||
if (!skipLogging) {
|
||||
log.info("response", {
|
||||
duration: Date.now() - start,
|
||||
})
|
||||
}
|
||||
})
|
||||
.use(async (c, next) => {
|
||||
const directory = c.req.query("directory") ?? process.cwd()
|
||||
return Instance.provide(directory, async () => {
|
||||
return next()
|
||||
})
|
||||
})
|
||||
.use(cors())
|
||||
.get(
|
||||
"/doc",
|
||||
openAPIRouteHandler(app, {
|
||||
documentation: {
|
||||
info: {
|
||||
title: "opencode",
|
||||
version: "0.0.3",
|
||||
description: "opencode api",
|
||||
},
|
||||
openapi: "3.1.1",
|
||||
},
|
||||
}),
|
||||
)
|
||||
.use(validator("query", z.object({ directory: z.string().optional() })))
|
||||
.route("/project", ProjectRoute)
|
||||
.get(
|
||||
"/event",
|
||||
describeRoute({
|
||||
description: "Get events",
|
||||
operationId: "event.subscribe",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Event stream",
|
||||
content: {
|
||||
"text/event-stream": {
|
||||
schema: resolver(
|
||||
Bus.payloads().meta({
|
||||
ref: "Event",
|
||||
}),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
log.info("event connected")
|
||||
return streamSSE(c, async (stream) => {
|
||||
stream.writeSSE({
|
||||
data: JSON.stringify({
|
||||
type: "server.connected",
|
||||
properties: {},
|
||||
}),
|
||||
.use(async (c, next) => {
|
||||
const skipLogging = c.req.path === "/log"
|
||||
if (!skipLogging) {
|
||||
log.info("request", {
|
||||
method: c.req.method,
|
||||
path: c.req.path,
|
||||
})
|
||||
const unsub = Bus.subscribeAll(async (event) => {
|
||||
await stream.writeSSE({
|
||||
data: JSON.stringify(event),
|
||||
})
|
||||
})
|
||||
await new Promise<void>((resolve) => {
|
||||
stream.onAbort(() => {
|
||||
unsub()
|
||||
resolve()
|
||||
log.info("event disconnected")
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/config",
|
||||
describeRoute({
|
||||
description: "Get config info",
|
||||
operationId: "config.get",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Get config info",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Config.Info),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
return c.json(await Config.get())
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/experimental/tool/register",
|
||||
describeRoute({
|
||||
description: "Register a new HTTP callback tool",
|
||||
operationId: "tool.register",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Tool registered successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
...ERRORS,
|
||||
},
|
||||
}),
|
||||
validator("json", HttpToolRegistration),
|
||||
async (c) => {
|
||||
ToolRegistry.registerHTTP(c.req.valid("json"))
|
||||
return c.json(true)
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/experimental/tool/ids",
|
||||
describeRoute({
|
||||
description: "List all tool IDs (including built-in and dynamically registered)",
|
||||
operationId: "tool.ids",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Tool IDs",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.array(z.string()).meta({ ref: "ToolIDs" })),
|
||||
},
|
||||
},
|
||||
},
|
||||
...ERRORS,
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
return c.json(ToolRegistry.ids())
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/experimental/tool",
|
||||
describeRoute({
|
||||
description: "List tools with JSON schema parameters for a provider/model",
|
||||
operationId: "tool.list",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Tools",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(
|
||||
z
|
||||
.array(
|
||||
z
|
||||
.object({
|
||||
id: z.string(),
|
||||
description: z.string(),
|
||||
parameters: z.any(),
|
||||
})
|
||||
.meta({ ref: "ToolListItem" }),
|
||||
)
|
||||
.meta({ ref: "ToolList" }),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
...ERRORS,
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"query",
|
||||
z.object({
|
||||
provider: z.string(),
|
||||
model: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const { provider, model } = c.req.valid("query")
|
||||
const tools = await ToolRegistry.tools(provider, model)
|
||||
return c.json(
|
||||
tools.map((t) => ({
|
||||
id: t.id,
|
||||
description: t.description,
|
||||
// Handle both Zod schemas and plain JSON schemas
|
||||
parameters: (t.parameters as any)?._def ? zodToJsonSchema(t.parameters as any) : t.parameters,
|
||||
})),
|
||||
)
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/path",
|
||||
describeRoute({
|
||||
description: "Get the current path",
|
||||
operationId: "path.get",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Path",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(
|
||||
z
|
||||
.object({
|
||||
state: z.string(),
|
||||
config: z.string(),
|
||||
worktree: z.string(),
|
||||
directory: z.string(),
|
||||
})
|
||||
.meta({
|
||||
ref: "Path",
|
||||
}),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
return c.json({
|
||||
state: Global.Path.state,
|
||||
config: Global.Path.config,
|
||||
worktree: Instance.worktree,
|
||||
directory: Instance.directory,
|
||||
})
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/session",
|
||||
describeRoute({
|
||||
description: "List all sessions",
|
||||
operationId: "session.list",
|
||||
responses: {
|
||||
200: {
|
||||
description: "List of sessions",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Session.Info.array()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
const sessions = await Array.fromAsync(Session.list())
|
||||
sessions.sort((a, b) => b.time.updated - a.time.updated)
|
||||
return c.json(sessions)
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/session/:id",
|
||||
describeRoute({
|
||||
description: "Get session",
|
||||
operationId: "session.get",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Get session",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Session.Info),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const sessionID = c.req.valid("param").id
|
||||
const session = await Session.get(sessionID)
|
||||
return c.json(session)
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/session/:id/children",
|
||||
describeRoute({
|
||||
description: "Get a session's children",
|
||||
operationId: "session.children",
|
||||
responses: {
|
||||
200: {
|
||||
description: "List of children",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Session.Info.array()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const sessionID = c.req.valid("param").id
|
||||
const session = await Session.children(sessionID)
|
||||
return c.json(session)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/session",
|
||||
describeRoute({
|
||||
description: "Create a new session",
|
||||
operationId: "session.create",
|
||||
responses: {
|
||||
...ERRORS,
|
||||
200: {
|
||||
description: "Successfully created session",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Session.Info),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"json",
|
||||
z
|
||||
.object({
|
||||
parentID: z.string().optional(),
|
||||
title: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
),
|
||||
async (c) => {
|
||||
const body = c.req.valid("json") ?? {}
|
||||
const session = await Session.create(body.parentID, body.title)
|
||||
return c.json(session)
|
||||
},
|
||||
)
|
||||
.delete(
|
||||
"/session/:id",
|
||||
describeRoute({
|
||||
description: "Delete a session and all its data",
|
||||
operationId: "session.delete",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successfully deleted session",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
await Session.remove(c.req.valid("param").id)
|
||||
return c.json(true)
|
||||
},
|
||||
)
|
||||
.patch(
|
||||
"/session/:id",
|
||||
describeRoute({
|
||||
description: "Update session properties",
|
||||
operationId: "session.update",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successfully updated session",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Session.Info),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
),
|
||||
validator(
|
||||
"json",
|
||||
z.object({
|
||||
title: z.string().optional(),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const sessionID = c.req.valid("param").id
|
||||
const updates = c.req.valid("json")
|
||||
|
||||
const updatedSession = await Session.update(sessionID, (session) => {
|
||||
if (updates.title !== undefined) {
|
||||
session.title = updates.title
|
||||
}
|
||||
})
|
||||
|
||||
return c.json(updatedSession)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/session/:id/init",
|
||||
describeRoute({
|
||||
description: "Analyze the app and create an AGENTS.md file",
|
||||
operationId: "session.init",
|
||||
responses: {
|
||||
200: {
|
||||
description: "200",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string().meta({ description: "Session ID" }),
|
||||
}),
|
||||
),
|
||||
validator(
|
||||
"json",
|
||||
z.object({
|
||||
messageID: z.string(),
|
||||
providerID: z.string(),
|
||||
modelID: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const sessionID = c.req.valid("param").id
|
||||
const body = c.req.valid("json")
|
||||
await Session.initialize({ ...body, sessionID })
|
||||
return c.json(true)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/session/:id/abort",
|
||||
describeRoute({
|
||||
description: "Abort a session",
|
||||
operationId: "session.abort",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Aborted session",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
return c.json(SessionPrompt.abort(c.req.valid("param").id))
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/session/:id/share",
|
||||
describeRoute({
|
||||
description: "Share a session",
|
||||
operationId: "session.share",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successfully shared session",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Session.Info),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const id = c.req.valid("param").id
|
||||
await Session.share(id)
|
||||
const session = await Session.get(id)
|
||||
return c.json(session)
|
||||
},
|
||||
)
|
||||
.delete(
|
||||
"/session/:id/share",
|
||||
describeRoute({
|
||||
description: "Unshare the session",
|
||||
operationId: "session.unshare",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successfully unshared session",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Session.Info),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const id = c.req.valid("param").id
|
||||
await Session.unshare(id)
|
||||
const session = await Session.get(id)
|
||||
return c.json(session)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/session/:id/summarize",
|
||||
describeRoute({
|
||||
description: "Summarize the session",
|
||||
operationId: "session.summarize",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Summarized session",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string().meta({ description: "Session ID" }),
|
||||
}),
|
||||
),
|
||||
validator(
|
||||
"json",
|
||||
z.object({
|
||||
providerID: z.string(),
|
||||
modelID: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const id = c.req.valid("param").id
|
||||
const body = c.req.valid("json")
|
||||
await SessionCompaction.run({ ...body, sessionID: id })
|
||||
return c.json(true)
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/session/:id/message",
|
||||
describeRoute({
|
||||
description: "List messages for a session",
|
||||
operationId: "session.messages",
|
||||
responses: {
|
||||
200: {
|
||||
description: "List of messages",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(MessageV2.WithParts.array()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string().meta({ description: "Session ID" }),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const messages = await Session.messages(c.req.valid("param").id)
|
||||
return c.json(messages)
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/session/:id/message/:messageID",
|
||||
describeRoute({
|
||||
description: "Get a message from a session",
|
||||
operationId: "session.message",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Message",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(
|
||||
z.object({
|
||||
info: MessageV2.Info,
|
||||
parts: MessageV2.Part.array(),
|
||||
}),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string().meta({ description: "Session ID" }),
|
||||
messageID: z.string().meta({ description: "Message ID" }),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const params = c.req.valid("param")
|
||||
const message = await Session.getMessage(params.id, params.messageID)
|
||||
return c.json(message)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/session/:id/message",
|
||||
describeRoute({
|
||||
description: "Create and send a new message to a session",
|
||||
operationId: "session.prompt",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Created message",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(
|
||||
z.object({
|
||||
info: MessageV2.Assistant,
|
||||
parts: MessageV2.Part.array(),
|
||||
}),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string().meta({ description: "Session ID" }),
|
||||
}),
|
||||
),
|
||||
validator("json", SessionPrompt.PromptInput.omit({ sessionID: true })),
|
||||
async (c) => {
|
||||
const sessionID = c.req.valid("param").id
|
||||
const body = c.req.valid("json")
|
||||
const msg = await SessionPrompt.prompt({ ...body, sessionID })
|
||||
return c.json(msg)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/session/:id/command",
|
||||
describeRoute({
|
||||
description: "Send a new command to a session",
|
||||
operationId: "session.command",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Created message",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(
|
||||
z.object({
|
||||
info: MessageV2.Assistant,
|
||||
parts: MessageV2.Part.array(),
|
||||
}),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string().meta({ description: "Session ID" }),
|
||||
}),
|
||||
),
|
||||
validator("json", SessionPrompt.CommandInput.omit({ sessionID: true })),
|
||||
async (c) => {
|
||||
const sessionID = c.req.valid("param").id
|
||||
const body = c.req.valid("json")
|
||||
const msg = await SessionPrompt.command({ ...body, sessionID })
|
||||
return c.json(msg)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/session/:id/shell",
|
||||
describeRoute({
|
||||
description: "Run a shell command",
|
||||
operationId: "session.shell",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Created message",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(MessageV2.Assistant),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string().meta({ description: "Session ID" }),
|
||||
}),
|
||||
),
|
||||
validator("json", SessionPrompt.ShellInput.omit({ sessionID: true })),
|
||||
async (c) => {
|
||||
const sessionID = c.req.valid("param").id
|
||||
const body = c.req.valid("json")
|
||||
const msg = await SessionPrompt.shell({ ...body, sessionID })
|
||||
return c.json(msg)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/session/:id/revert",
|
||||
describeRoute({
|
||||
description: "Revert a message",
|
||||
operationId: "session.revert",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Updated session",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Session.Info),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
),
|
||||
validator("json", SessionRevert.RevertInput.omit({ sessionID: true })),
|
||||
async (c) => {
|
||||
const id = c.req.valid("param").id
|
||||
log.info("revert", c.req.valid("json"))
|
||||
const session = await SessionRevert.revert({ sessionID: id, ...c.req.valid("json") })
|
||||
return c.json(session)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/session/:id/unrevert",
|
||||
describeRoute({
|
||||
description: "Restore all reverted messages",
|
||||
operationId: "session.unrevert",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Updated session",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Session.Info),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const id = c.req.valid("param").id
|
||||
const session = await SessionRevert.unrevert({ sessionID: id })
|
||||
return c.json(session)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/session/:id/permissions/:permissionID",
|
||||
describeRoute({
|
||||
description: "Respond to a permission request",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Permission processed successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string(),
|
||||
permissionID: z.string(),
|
||||
}),
|
||||
),
|
||||
validator("json", z.object({ response: Permission.Response })),
|
||||
async (c) => {
|
||||
const params = c.req.valid("param")
|
||||
const id = params.id
|
||||
const permissionID = params.permissionID
|
||||
Permission.respond({ sessionID: id, permissionID, response: c.req.valid("json").response })
|
||||
return c.json(true)
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/command",
|
||||
describeRoute({
|
||||
description: "List all commands",
|
||||
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(
|
||||
"/config/providers",
|
||||
describeRoute({
|
||||
description: "List all providers",
|
||||
operationId: "config.providers",
|
||||
responses: {
|
||||
200: {
|
||||
description: "List of providers",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(
|
||||
z.object({
|
||||
providers: ModelsDev.Provider.array(),
|
||||
default: z.record(z.string(), z.string()),
|
||||
}),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
const providers = await Provider.list().then((x) => mapValues(x, (item) => item.info))
|
||||
return c.json({
|
||||
providers: Object.values(providers),
|
||||
default: mapValues(providers, (item) => Provider.sort(Object.values(item.models))[0].id),
|
||||
})
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/find",
|
||||
describeRoute({
|
||||
description: "Find text in files",
|
||||
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({
|
||||
description: "Find files",
|
||||
operationId: "find.files",
|
||||
responses: {
|
||||
200: {
|
||||
description: "File paths",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.string().array()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"query",
|
||||
z.object({
|
||||
query: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const query = c.req.valid("query").query
|
||||
const result = await Ripgrep.files({
|
||||
cwd: Instance.directory,
|
||||
query,
|
||||
limit: 10,
|
||||
})
|
||||
return c.json(result)
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/find/symbol",
|
||||
describeRoute({
|
||||
description: "Find workspace symbols",
|
||||
operationId: "find.symbols",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Symbols",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(LSP.Symbol.array()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
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)
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/file",
|
||||
describeRoute({
|
||||
description: "List files and directories",
|
||||
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({
|
||||
description: "Read a 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({
|
||||
description: "Get file status",
|
||||
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)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/log",
|
||||
describeRoute({
|
||||
description: "Write a log entry to the server logs",
|
||||
operationId: "app.log",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Log entry written successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"json",
|
||||
z.object({
|
||||
service: z.string().meta({ description: "Service name for the log entry" }),
|
||||
level: z.enum(["debug", "info", "error", "warn"]).meta({ description: "Log level" }),
|
||||
message: z.string().meta({ description: "Log message" }),
|
||||
extra: z
|
||||
.record(z.string(), z.any())
|
||||
.optional()
|
||||
.meta({ description: "Additional metadata for the log entry" }),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const { service, level, message, extra } = c.req.valid("json")
|
||||
const logger = Log.create({ service })
|
||||
|
||||
switch (level) {
|
||||
case "debug":
|
||||
logger.debug(message, extra)
|
||||
break
|
||||
case "info":
|
||||
logger.info(message, extra)
|
||||
break
|
||||
case "error":
|
||||
logger.error(message, extra)
|
||||
break
|
||||
case "warn":
|
||||
logger.warn(message, extra)
|
||||
break
|
||||
}
|
||||
const start = Date.now()
|
||||
await next()
|
||||
if (!skipLogging) {
|
||||
log.info("response", {
|
||||
duration: Date.now() - start,
|
||||
})
|
||||
}
|
||||
})
|
||||
.use(async (c, next) => {
|
||||
const directory = c.req.query("directory") ?? process.cwd()
|
||||
return Instance.provide(directory, async () => {
|
||||
return next()
|
||||
})
|
||||
})
|
||||
.use(cors())
|
||||
.get(
|
||||
"/doc",
|
||||
openAPIRouteHandler(app, {
|
||||
documentation: {
|
||||
info: {
|
||||
title: "opencode",
|
||||
version: "0.0.3",
|
||||
description: "opencode api",
|
||||
},
|
||||
openapi: "3.1.1",
|
||||
},
|
||||
}),
|
||||
)
|
||||
.use(validator("query", z.object({ directory: z.string().optional() })))
|
||||
.route("/project", ProjectRoute)
|
||||
.get(
|
||||
"/config",
|
||||
describeRoute({
|
||||
description: "Get config info",
|
||||
operationId: "config.get",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Get config info",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Config.Info),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
return c.json(await Config.get())
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/experimental/tool/register",
|
||||
describeRoute({
|
||||
description: "Register a new HTTP callback tool",
|
||||
operationId: "tool.register",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Tool registered successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
...ERRORS,
|
||||
},
|
||||
}),
|
||||
validator("json", HttpToolRegistration),
|
||||
async (c) => {
|
||||
ToolRegistry.registerHTTP(c.req.valid("json"))
|
||||
return c.json(true)
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/experimental/tool/ids",
|
||||
describeRoute({
|
||||
description: "List all tool IDs (including built-in and dynamically registered)",
|
||||
operationId: "tool.ids",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Tool IDs",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.array(z.string()).meta({ ref: "ToolIDs" })),
|
||||
},
|
||||
},
|
||||
},
|
||||
...ERRORS,
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
return c.json(ToolRegistry.ids())
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/experimental/tool",
|
||||
describeRoute({
|
||||
description: "List tools with JSON schema parameters for a provider/model",
|
||||
operationId: "tool.list",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Tools",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(
|
||||
z
|
||||
.array(
|
||||
z
|
||||
.object({
|
||||
id: z.string(),
|
||||
description: z.string(),
|
||||
parameters: z.any(),
|
||||
})
|
||||
.meta({ ref: "ToolListItem" }),
|
||||
)
|
||||
.meta({ ref: "ToolList" }),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
...ERRORS,
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"query",
|
||||
z.object({
|
||||
provider: z.string(),
|
||||
model: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const { provider, model } = c.req.valid("query")
|
||||
const tools = await ToolRegistry.tools(provider, model)
|
||||
return c.json(
|
||||
tools.map((t) => ({
|
||||
id: t.id,
|
||||
description: t.description,
|
||||
// Handle both Zod schemas and plain JSON schemas
|
||||
parameters: (t.parameters as any)?._def ? zodToJsonSchema(t.parameters as any) : t.parameters,
|
||||
})),
|
||||
)
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/path",
|
||||
describeRoute({
|
||||
description: "Get the current path",
|
||||
operationId: "path.get",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Path",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(
|
||||
z
|
||||
.object({
|
||||
state: z.string(),
|
||||
config: z.string(),
|
||||
worktree: z.string(),
|
||||
directory: z.string(),
|
||||
})
|
||||
.meta({
|
||||
ref: "Path",
|
||||
}),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
return c.json({
|
||||
state: Global.Path.state,
|
||||
config: Global.Path.config,
|
||||
worktree: Instance.worktree,
|
||||
directory: Instance.directory,
|
||||
})
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/session",
|
||||
describeRoute({
|
||||
description: "List all sessions",
|
||||
operationId: "session.list",
|
||||
responses: {
|
||||
200: {
|
||||
description: "List of sessions",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Session.Info.array()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
const sessions = await Array.fromAsync(Session.list())
|
||||
sessions.sort((a, b) => b.time.updated - a.time.updated)
|
||||
return c.json(sessions)
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/session/:id",
|
||||
describeRoute({
|
||||
description: "Get session",
|
||||
operationId: "session.get",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Get session",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Session.Info),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const sessionID = c.req.valid("param").id
|
||||
const session = await Session.get(sessionID)
|
||||
return c.json(session)
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/session/:id/children",
|
||||
describeRoute({
|
||||
description: "Get a session's children",
|
||||
operationId: "session.children",
|
||||
responses: {
|
||||
200: {
|
||||
description: "List of children",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Session.Info.array()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const sessionID = c.req.valid("param").id
|
||||
const session = await Session.children(sessionID)
|
||||
return c.json(session)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/session",
|
||||
describeRoute({
|
||||
description: "Create a new session",
|
||||
operationId: "session.create",
|
||||
responses: {
|
||||
...ERRORS,
|
||||
200: {
|
||||
description: "Successfully created session",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Session.Info),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"json",
|
||||
z
|
||||
.object({
|
||||
parentID: z.string().optional(),
|
||||
title: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
),
|
||||
async (c) => {
|
||||
const body = c.req.valid("json") ?? {}
|
||||
const session = await Session.create(body.parentID, body.title)
|
||||
return c.json(session)
|
||||
},
|
||||
)
|
||||
.delete(
|
||||
"/session/:id",
|
||||
describeRoute({
|
||||
description: "Delete a session and all its data",
|
||||
operationId: "session.delete",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successfully deleted session",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
await Session.remove(c.req.valid("param").id)
|
||||
return c.json(true)
|
||||
},
|
||||
)
|
||||
.patch(
|
||||
"/session/:id",
|
||||
describeRoute({
|
||||
description: "Update session properties",
|
||||
operationId: "session.update",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successfully updated session",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Session.Info),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
),
|
||||
validator(
|
||||
"json",
|
||||
z.object({
|
||||
title: z.string().optional(),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const sessionID = c.req.valid("param").id
|
||||
const updates = c.req.valid("json")
|
||||
|
||||
return c.json(true)
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/agent",
|
||||
describeRoute({
|
||||
description: "List all agents",
|
||||
operationId: "app.agents",
|
||||
responses: {
|
||||
200: {
|
||||
description: "List of agents",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Agent.Info.array()),
|
||||
const updatedSession = await Session.update(sessionID, (session) => {
|
||||
if (updates.title !== undefined) {
|
||||
session.title = updates.title
|
||||
}
|
||||
})
|
||||
|
||||
return c.json(updatedSession)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/session/:id/init",
|
||||
describeRoute({
|
||||
description: "Analyze the app and create an AGENTS.md file",
|
||||
operationId: "session.init",
|
||||
responses: {
|
||||
200: {
|
||||
description: "200",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
const modes = await Agent.list()
|
||||
return c.json(modes)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/tui/append-prompt",
|
||||
describeRoute({
|
||||
description: "Append prompt to the TUI",
|
||||
operationId: "tui.appendPrompt",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Prompt processed successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"json",
|
||||
z.object({
|
||||
text: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => c.json(await callTui(c)),
|
||||
)
|
||||
.post(
|
||||
"/tui/open-help",
|
||||
describeRoute({
|
||||
description: "Open the help dialog",
|
||||
operationId: "tui.openHelp",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Help dialog opened successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string().meta({ description: "Session ID" }),
|
||||
}),
|
||||
),
|
||||
validator(
|
||||
"json",
|
||||
z.object({
|
||||
messageID: z.string(),
|
||||
providerID: z.string(),
|
||||
modelID: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const sessionID = c.req.valid("param").id
|
||||
const body = c.req.valid("json")
|
||||
await Session.initialize({ ...body, sessionID })
|
||||
return c.json(true)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/session/:id/abort",
|
||||
describeRoute({
|
||||
description: "Abort a session",
|
||||
operationId: "session.abort",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Aborted session",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => c.json(await callTui(c)),
|
||||
)
|
||||
.post(
|
||||
"/tui/open-sessions",
|
||||
describeRoute({
|
||||
description: "Open the session dialog",
|
||||
operationId: "tui.openSessions",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Session dialog opened successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => c.json(await callTui(c)),
|
||||
)
|
||||
.post(
|
||||
"/tui/open-themes",
|
||||
describeRoute({
|
||||
description: "Open the theme dialog",
|
||||
operationId: "tui.openThemes",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Theme dialog opened successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => c.json(await callTui(c)),
|
||||
)
|
||||
.post(
|
||||
"/tui/open-models",
|
||||
describeRoute({
|
||||
description: "Open the model dialog",
|
||||
operationId: "tui.openModels",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Model dialog opened successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => c.json(await callTui(c)),
|
||||
)
|
||||
.post(
|
||||
"/tui/submit-prompt",
|
||||
describeRoute({
|
||||
description: "Submit the prompt",
|
||||
operationId: "tui.submitPrompt",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Prompt submitted successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => c.json(await callTui(c)),
|
||||
)
|
||||
.post(
|
||||
"/tui/clear-prompt",
|
||||
describeRoute({
|
||||
description: "Clear the prompt",
|
||||
operationId: "tui.clearPrompt",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Prompt cleared successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => c.json(await callTui(c)),
|
||||
)
|
||||
.post(
|
||||
"/tui/execute-command",
|
||||
describeRoute({
|
||||
description: "Execute a TUI command (e.g. agent_cycle)",
|
||||
operationId: "tui.executeCommand",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Command executed successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"json",
|
||||
z.object({
|
||||
command: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => c.json(await callTui(c)),
|
||||
)
|
||||
.post(
|
||||
"/tui/show-toast",
|
||||
describeRoute({
|
||||
description: "Show a toast notification in the TUI",
|
||||
operationId: "tui.showToast",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Toast notification shown successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
return c.json(SessionPrompt.abort(c.req.valid("param").id))
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/session/:id/share",
|
||||
describeRoute({
|
||||
description: "Share a session",
|
||||
operationId: "session.share",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successfully shared session",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Session.Info),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"json",
|
||||
z.object({
|
||||
title: z.string().optional(),
|
||||
message: z.string(),
|
||||
variant: z.enum(["info", "success", "warning", "error"]),
|
||||
}),
|
||||
),
|
||||
async (c) => c.json(await callTui(c)),
|
||||
)
|
||||
.route("/tui/control", TuiRoute)
|
||||
.put(
|
||||
"/auth/:id",
|
||||
describeRoute({
|
||||
description: "Set authentication credentials",
|
||||
operationId: "auth.set",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successfully set authentication credentials",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const id = c.req.valid("param").id
|
||||
await Session.share(id)
|
||||
const session = await Session.get(id)
|
||||
return c.json(session)
|
||||
},
|
||||
)
|
||||
.delete(
|
||||
"/session/:id/share",
|
||||
describeRoute({
|
||||
description: "Unshare the session",
|
||||
operationId: "session.unshare",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successfully unshared session",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Session.Info),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
...ERRORS,
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const id = c.req.valid("param").id
|
||||
await Session.unshare(id)
|
||||
const session = await Session.get(id)
|
||||
return c.json(session)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/session/:id/summarize",
|
||||
describeRoute({
|
||||
description: "Summarize the session",
|
||||
operationId: "session.summarize",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Summarized session",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string().meta({ description: "Session ID" }),
|
||||
}),
|
||||
),
|
||||
validator(
|
||||
"json",
|
||||
z.object({
|
||||
providerID: z.string(),
|
||||
modelID: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const id = c.req.valid("param").id
|
||||
const body = c.req.valid("json")
|
||||
await SessionCompaction.run({ ...body, sessionID: id })
|
||||
return c.json(true)
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/session/:id/message",
|
||||
describeRoute({
|
||||
description: "List messages for a session",
|
||||
operationId: "session.messages",
|
||||
responses: {
|
||||
200: {
|
||||
description: "List of messages",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(MessageV2.WithParts.array()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string().meta({ description: "Session ID" }),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const messages = await Session.messages(c.req.valid("param").id)
|
||||
return c.json(messages)
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/session/:id/message/:messageID",
|
||||
describeRoute({
|
||||
description: "Get a message from a session",
|
||||
operationId: "session.message",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Message",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(
|
||||
z.object({
|
||||
info: MessageV2.Info,
|
||||
parts: MessageV2.Part.array(),
|
||||
}),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string().meta({ description: "Session ID" }),
|
||||
messageID: z.string().meta({ description: "Message ID" }),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const params = c.req.valid("param")
|
||||
const message = await Session.getMessage(params.id, params.messageID)
|
||||
return c.json(message)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/session/:id/message",
|
||||
describeRoute({
|
||||
description: "Create and send a new message to a session",
|
||||
operationId: "session.prompt",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Created message",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(
|
||||
z.object({
|
||||
info: MessageV2.Assistant,
|
||||
parts: MessageV2.Part.array(),
|
||||
}),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string().meta({ description: "Session ID" }),
|
||||
}),
|
||||
),
|
||||
validator("json", SessionPrompt.PromptInput.omit({ sessionID: true })),
|
||||
async (c) => {
|
||||
const sessionID = c.req.valid("param").id
|
||||
const body = c.req.valid("json")
|
||||
const msg = await SessionPrompt.prompt({ ...body, sessionID })
|
||||
return c.json(msg)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/session/:id/command",
|
||||
describeRoute({
|
||||
description: "Send a new command to a session",
|
||||
operationId: "session.command",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Created message",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(
|
||||
z.object({
|
||||
info: MessageV2.Assistant,
|
||||
parts: MessageV2.Part.array(),
|
||||
}),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string().meta({ description: "Session ID" }),
|
||||
}),
|
||||
),
|
||||
validator("json", SessionPrompt.CommandInput.omit({ sessionID: true })),
|
||||
async (c) => {
|
||||
const sessionID = c.req.valid("param").id
|
||||
const body = c.req.valid("json")
|
||||
const msg = await SessionPrompt.command({ ...body, sessionID })
|
||||
return c.json(msg)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/session/:id/shell",
|
||||
describeRoute({
|
||||
description: "Run a shell command",
|
||||
operationId: "session.shell",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Created message",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(MessageV2.Assistant),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string().meta({ description: "Session ID" }),
|
||||
}),
|
||||
),
|
||||
validator("json", SessionPrompt.ShellInput.omit({ sessionID: true })),
|
||||
async (c) => {
|
||||
const sessionID = c.req.valid("param").id
|
||||
const body = c.req.valid("json")
|
||||
const msg = await SessionPrompt.shell({ ...body, sessionID })
|
||||
return c.json(msg)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/session/:id/revert",
|
||||
describeRoute({
|
||||
description: "Revert a message",
|
||||
operationId: "session.revert",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Updated session",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Session.Info),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
),
|
||||
validator("json", SessionRevert.RevertInput.omit({ sessionID: true })),
|
||||
async (c) => {
|
||||
const id = c.req.valid("param").id
|
||||
log.info("revert", c.req.valid("json"))
|
||||
const session = await SessionRevert.revert({ sessionID: id, ...c.req.valid("json") })
|
||||
return c.json(session)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/session/:id/unrevert",
|
||||
describeRoute({
|
||||
description: "Restore all reverted messages",
|
||||
operationId: "session.unrevert",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Updated session",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Session.Info),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const id = c.req.valid("param").id
|
||||
const session = await SessionRevert.unrevert({ sessionID: id })
|
||||
return c.json(session)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/session/:id/permissions/:permissionID",
|
||||
describeRoute({
|
||||
description: "Respond to a permission request",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Permission processed successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string(),
|
||||
permissionID: z.string(),
|
||||
}),
|
||||
),
|
||||
validator("json", z.object({ response: Permission.Response })),
|
||||
async (c) => {
|
||||
const params = c.req.valid("param")
|
||||
const id = params.id
|
||||
const permissionID = params.permissionID
|
||||
Permission.respond({ sessionID: id, permissionID, response: c.req.valid("json").response })
|
||||
return c.json(true)
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/command",
|
||||
describeRoute({
|
||||
description: "List all commands",
|
||||
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(
|
||||
"/config/providers",
|
||||
describeRoute({
|
||||
description: "List all providers",
|
||||
operationId: "config.providers",
|
||||
responses: {
|
||||
200: {
|
||||
description: "List of providers",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(
|
||||
z.object({
|
||||
providers: ModelsDev.Provider.array(),
|
||||
default: z.record(z.string(), z.string()),
|
||||
}),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
const providers = await Provider.list().then((x) => mapValues(x, (item) => item.info))
|
||||
return c.json({
|
||||
providers: Object.values(providers),
|
||||
default: mapValues(providers, (item) => Provider.sort(Object.values(item.models))[0].id),
|
||||
})
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/find",
|
||||
describeRoute({
|
||||
description: "Find text in files",
|
||||
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({
|
||||
description: "Find files",
|
||||
operationId: "find.files",
|
||||
responses: {
|
||||
200: {
|
||||
description: "File paths",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.string().array()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"query",
|
||||
z.object({
|
||||
query: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const query = c.req.valid("query").query
|
||||
const result = await Ripgrep.files({
|
||||
cwd: Instance.directory,
|
||||
query,
|
||||
limit: 10,
|
||||
})
|
||||
return c.json(result)
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/find/symbol",
|
||||
describeRoute({
|
||||
description: "Find workspace symbols",
|
||||
operationId: "find.symbols",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Symbols",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(LSP.Symbol.array()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
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)
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/file",
|
||||
describeRoute({
|
||||
description: "List files and directories",
|
||||
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({
|
||||
description: "Read a 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({
|
||||
description: "Get file status",
|
||||
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)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/log",
|
||||
describeRoute({
|
||||
description: "Write a log entry to the server logs",
|
||||
operationId: "app.log",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Log entry written successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"json",
|
||||
z.object({
|
||||
service: z.string().meta({ description: "Service name for the log entry" }),
|
||||
level: z.enum(["debug", "info", "error", "warn"]).meta({ description: "Log level" }),
|
||||
message: z.string().meta({ description: "Log message" }),
|
||||
extra: z
|
||||
.record(z.string(), z.any())
|
||||
.optional()
|
||||
.meta({ description: "Additional metadata for the log entry" }),
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const { service, level, message, extra } = c.req.valid("json")
|
||||
const logger = Log.create({ service })
|
||||
|
||||
switch (level) {
|
||||
case "debug":
|
||||
logger.debug(message, extra)
|
||||
break
|
||||
case "info":
|
||||
logger.info(message, extra)
|
||||
break
|
||||
case "error":
|
||||
logger.error(message, extra)
|
||||
break
|
||||
case "warn":
|
||||
logger.warn(message, extra)
|
||||
break
|
||||
}
|
||||
|
||||
return c.json(true)
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/agent",
|
||||
describeRoute({
|
||||
description: "List all agents",
|
||||
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)
|
||||
},
|
||||
)
|
||||
.post(
|
||||
"/tui/append-prompt",
|
||||
describeRoute({
|
||||
description: "Append prompt to the TUI",
|
||||
operationId: "tui.appendPrompt",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Prompt processed successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"json",
|
||||
z.object({
|
||||
text: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => c.json(await callTui(c)),
|
||||
)
|
||||
.post(
|
||||
"/tui/open-help",
|
||||
describeRoute({
|
||||
description: "Open the help dialog",
|
||||
operationId: "tui.openHelp",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Help dialog opened successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => c.json(await callTui(c)),
|
||||
)
|
||||
.post(
|
||||
"/tui/open-sessions",
|
||||
describeRoute({
|
||||
description: "Open the session dialog",
|
||||
operationId: "tui.openSessions",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Session dialog opened successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => c.json(await callTui(c)),
|
||||
)
|
||||
.post(
|
||||
"/tui/open-themes",
|
||||
describeRoute({
|
||||
description: "Open the theme dialog",
|
||||
operationId: "tui.openThemes",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Theme dialog opened successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => c.json(await callTui(c)),
|
||||
)
|
||||
.post(
|
||||
"/tui/open-models",
|
||||
describeRoute({
|
||||
description: "Open the model dialog",
|
||||
operationId: "tui.openModels",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Model dialog opened successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => c.json(await callTui(c)),
|
||||
)
|
||||
.post(
|
||||
"/tui/submit-prompt",
|
||||
describeRoute({
|
||||
description: "Submit the prompt",
|
||||
operationId: "tui.submitPrompt",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Prompt submitted successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => c.json(await callTui(c)),
|
||||
)
|
||||
.post(
|
||||
"/tui/clear-prompt",
|
||||
describeRoute({
|
||||
description: "Clear the prompt",
|
||||
operationId: "tui.clearPrompt",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Prompt cleared successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => c.json(await callTui(c)),
|
||||
)
|
||||
.post(
|
||||
"/tui/execute-command",
|
||||
describeRoute({
|
||||
description: "Execute a TUI command (e.g. agent_cycle)",
|
||||
operationId: "tui.executeCommand",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Command executed successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"json",
|
||||
z.object({
|
||||
command: z.string(),
|
||||
}),
|
||||
),
|
||||
async (c) => c.json(await callTui(c)),
|
||||
)
|
||||
.post(
|
||||
"/tui/show-toast",
|
||||
describeRoute({
|
||||
description: "Show a toast notification in the TUI",
|
||||
operationId: "tui.showToast",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Toast notification shown successfully",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"json",
|
||||
z.object({
|
||||
title: z.string().optional(),
|
||||
message: z.string(),
|
||||
variant: z.enum(["info", "success", "warning", "error"]),
|
||||
}),
|
||||
),
|
||||
async (c) => c.json(await callTui(c)),
|
||||
)
|
||||
.route("/tui/control", TuiRoute)
|
||||
.put(
|
||||
"/auth/:id",
|
||||
describeRoute({
|
||||
description: "Set authentication credentials",
|
||||
operationId: "auth.set",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successfully set authentication credentials",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(z.boolean()),
|
||||
},
|
||||
},
|
||||
},
|
||||
...ERRORS,
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"param",
|
||||
z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
),
|
||||
validator("json", Auth.Info),
|
||||
async (c) => {
|
||||
const id = c.req.valid("param").id
|
||||
const info = c.req.valid("json")
|
||||
await Auth.set(id, info)
|
||||
return c.json(true)
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/event",
|
||||
describeRoute({
|
||||
description: "Get events",
|
||||
operationId: "event.subscribe",
|
||||
responses: {
|
||||
200: {
|
||||
description: "Event stream",
|
||||
content: {
|
||||
"text/event-stream": {
|
||||
schema: resolver(
|
||||
Bus.payloads().meta({
|
||||
ref: "Event",
|
||||
}),
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
log.info("event connected")
|
||||
return streamSSE(c, async (stream) => {
|
||||
stream.writeSSE({
|
||||
data: JSON.stringify({
|
||||
type: "server.connected",
|
||||
properties: {},
|
||||
}),
|
||||
})
|
||||
const unsub = Bus.subscribeAll(async (event) => {
|
||||
await stream.writeSSE({
|
||||
data: JSON.stringify(event),
|
||||
})
|
||||
})
|
||||
await new Promise<void>((resolve) => {
|
||||
stream.onAbort(() => {
|
||||
unsub()
|
||||
resolve()
|
||||
log.info("event disconnected")
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
),
|
||||
validator("json", Auth.Info),
|
||||
async (c) => {
|
||||
const id = c.req.valid("param").id
|
||||
const info = c.req.valid("json")
|
||||
await Auth.set(id, info)
|
||||
return c.json(true)
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
export async function openapi() {
|
||||
const result = await generateSpecs(App, {
|
||||
const result = await generateSpecs(App(), {
|
||||
documentation: {
|
||||
info: {
|
||||
title: "opencode",
|
||||
@@ -1433,7 +1436,7 @@ export namespace Server {
|
||||
port: opts.port,
|
||||
hostname: opts.hostname,
|
||||
idleTimeout: 0,
|
||||
fetch: App.fetch,
|
||||
fetch: App().fetch,
|
||||
})
|
||||
return server
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ describe("HTTP tool registration API", () => {
|
||||
}
|
||||
|
||||
// Register
|
||||
const registerRes = await Server.App.fetch(
|
||||
const registerRes = await Server.App().fetch(
|
||||
makeRequest("POST", "http://localhost:4096/experimental/tool/register", toolSpec),
|
||||
)
|
||||
expect(registerRes.status).toBe(200)
|
||||
@@ -39,13 +39,13 @@ describe("HTTP tool registration API", () => {
|
||||
expect(ok).toBe(true)
|
||||
|
||||
// IDs should include the new tool
|
||||
const idsRes = await Server.App.fetch(makeRequest("GET", "http://localhost:4096/experimental/tool/ids"))
|
||||
const idsRes = await Server.App().fetch(makeRequest("GET", "http://localhost:4096/experimental/tool/ids"))
|
||||
expect(idsRes.status).toBe(200)
|
||||
const ids = (await idsRes.json()) as string[]
|
||||
expect(ids).toContain("http-echo")
|
||||
|
||||
// List tools for a provider/model and check JSON Schema shape
|
||||
const listRes = await Server.App.fetch(
|
||||
const listRes = await Server.App().fetch(
|
||||
makeRequest("GET", "http://localhost:4096/experimental/tool?provider=openai&model=gpt-4o"),
|
||||
)
|
||||
expect(listRes.status).toBe(200)
|
||||
@@ -105,7 +105,7 @@ describe("Plugin tool.register hook", () => {
|
||||
expect(allIDs).toContain("from-plugin")
|
||||
|
||||
// Also verify via the HTTP surface
|
||||
const idsRes = await Server.App.fetch(makeRequest("GET", "http://localhost:4096/experimental/tool/ids"))
|
||||
const idsRes = await Server.App().fetch(makeRequest("GET", "http://localhost:4096/experimental/tool/ids"))
|
||||
expect(idsRes.status).toBe(200)
|
||||
const ids = (await idsRes.json()) as string[]
|
||||
expect(ids).toContain("from-plugin")
|
||||
@@ -168,7 +168,7 @@ test("Multiple plugins can each register tools", async () => {
|
||||
expect(ids).toContain("alpha-tool")
|
||||
expect(ids).toContain("beta-tool")
|
||||
|
||||
const res = await Server.App.fetch(new Request("http://localhost:4096/experimental/tool/ids"))
|
||||
const res = await Server.App().fetch(new Request("http://localhost:4096/experimental/tool/ids"))
|
||||
expect(res.status).toBe(200)
|
||||
const httpIds = (await res.json()) as string[]
|
||||
expect(httpIds).toContain("alpha-tool")
|
||||
@@ -241,14 +241,14 @@ test("Plugin registers native/local tool with function execution", async () => {
|
||||
expect(ids).toContain("http-tool-from-same-plugin")
|
||||
|
||||
// Verify via HTTP endpoint
|
||||
const res = await Server.App.fetch(new Request("http://localhost:4096/experimental/tool/ids"))
|
||||
const res = await Server.App().fetch(new Request("http://localhost:4096/experimental/tool/ids"))
|
||||
expect(res.status).toBe(200)
|
||||
const httpIds = (await res.json()) as string[]
|
||||
expect(httpIds).toContain("my-native-tool")
|
||||
expect(httpIds).toContain("http-tool-from-same-plugin")
|
||||
|
||||
// Get tool details to verify native tool has proper structure
|
||||
const toolsRes = await Server.App.fetch(
|
||||
const toolsRes = await Server.App().fetch(
|
||||
new Request("http://localhost:4096/experimental/tool?provider=anthropic&model=claude"),
|
||||
)
|
||||
expect(toolsRes.status).toBe(200)
|
||||
@@ -290,7 +290,7 @@ test("Plugin without tool.register is handled gracefully", async () => {
|
||||
const ids = ToolRegistry.ids()
|
||||
expect(ids).not.toContain("malformed-tool")
|
||||
|
||||
const res = await Server.App.fetch(new Request("http://localhost:4096/experimental/tool/ids"))
|
||||
const res = await Server.App().fetch(new Request("http://localhost:4096/experimental/tool/ids"))
|
||||
expect(res.status).toBe(200)
|
||||
const httpIds = (await res.json()) as string[]
|
||||
expect(httpIds).not.toContain("malformed-tool")
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "0.14.0"
|
||||
".": "0.15.0"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
configured_endpoints: 43
|
||||
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-0a4165f1eabf826d3092ea6b789aa527048278dcd4bd891f9e5ac890b9bcbb35.yml
|
||||
openapi_spec_hash: da60e4fc813eb0f9ac3ab5f112e26bf6
|
||||
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-ad911ed0bdbeca62807509f364f25fcafd7a83e0b43e027ec0a85f72b7a4d963.yml
|
||||
openapi_spec_hash: 15152513b4246bf4b5f8546fa6f1603f
|
||||
config_hash: 026ef000d34bf2f930e7b41e77d2d3ff
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## 0.15.0 (2025-09-16)
|
||||
|
||||
Full Changelog: [v0.14.0...v0.15.0](https://github.com/sst/opencode-sdk-go/compare/v0.14.0...v0.15.0)
|
||||
|
||||
### Features
|
||||
|
||||
- **api:** api update ([397048f](https://github.com/sst/opencode-sdk-go/commit/397048faca7a1de7a028edd2254a0ad7797b151f))
|
||||
|
||||
## 0.14.0 (2025-09-14)
|
||||
|
||||
Full Changelog: [v0.13.0...v0.14.0](https://github.com/sst/opencode-sdk-go/compare/v0.13.0...v0.14.0)
|
||||
|
||||
@@ -24,7 +24,7 @@ Or to pin the version:
|
||||
<!-- x-release-please-start-version -->
|
||||
|
||||
```sh
|
||||
go get -u 'github.com/sst/opencode-sdk-go@v0.14.0'
|
||||
go get -u 'github.com/sst/opencode-sdk-go@v0.15.0'
|
||||
```
|
||||
|
||||
<!-- x-release-please-end -->
|
||||
|
||||
@@ -90,8 +90,9 @@ type Config struct {
|
||||
// TUI specific settings
|
||||
Tui ConfigTui `json:"tui"`
|
||||
// Custom username to display in conversations instead of system username
|
||||
Username string `json:"username"`
|
||||
JSON configJSON `json:"-"`
|
||||
Username string `json:"username"`
|
||||
Watcher ConfigWatcher `json:"watcher"`
|
||||
JSON configJSON `json:"-"`
|
||||
}
|
||||
|
||||
// configJSON contains the JSON metadata for the struct [Config]
|
||||
@@ -121,6 +122,7 @@ type configJSON struct {
|
||||
Tools apijson.Field
|
||||
Tui apijson.Field
|
||||
Username apijson.Field
|
||||
Watcher apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
@@ -1772,6 +1774,26 @@ func (r configTuiJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type ConfigWatcher struct {
|
||||
Ignore []string `json:"ignore"`
|
||||
JSON configWatcherJSON `json:"-"`
|
||||
}
|
||||
|
||||
// configWatcherJSON contains the JSON metadata for the struct [ConfigWatcher]
|
||||
type configWatcherJSON struct {
|
||||
Ignore apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *ConfigWatcher) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r configWatcherJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
// Custom keybind configurations
|
||||
type KeybindsConfig struct {
|
||||
// Next agent
|
||||
|
||||
+159
-5
@@ -64,7 +64,9 @@ type EventListResponse struct {
|
||||
// [EventListResponseEventSessionIdleProperties],
|
||||
// [EventListResponseEventSessionUpdatedProperties],
|
||||
// [EventListResponseEventSessionDeletedProperties],
|
||||
// [EventListResponseEventSessionErrorProperties], [interface{}].
|
||||
// [EventListResponseEventSessionErrorProperties], [interface{}],
|
||||
// [EventListResponseEventFileWatcherUpdatedProperties],
|
||||
// [EventListResponseEventIdeInstalledProperties].
|
||||
Properties interface{} `json:"properties,required"`
|
||||
Type EventListResponseType `json:"type,required"`
|
||||
JSON eventListResponseJSON `json:"-"`
|
||||
@@ -107,7 +109,9 @@ func (r *EventListResponse) UnmarshalJSON(data []byte) (err error) {
|
||||
// [EventListResponseEventPermissionReplied], [EventListResponseEventFileEdited],
|
||||
// [EventListResponseEventSessionIdle], [EventListResponseEventSessionUpdated],
|
||||
// [EventListResponseEventSessionDeleted], [EventListResponseEventSessionError],
|
||||
// [EventListResponseEventServerConnected].
|
||||
// [EventListResponseEventServerConnected],
|
||||
// [EventListResponseEventFileWatcherUpdated],
|
||||
// [EventListResponseEventIdeInstalled].
|
||||
func (r EventListResponse) AsUnion() EventListResponseUnion {
|
||||
return r.union
|
||||
}
|
||||
@@ -121,8 +125,10 @@ func (r EventListResponse) AsUnion() EventListResponseUnion {
|
||||
// [EventListResponseEventPermissionUpdated],
|
||||
// [EventListResponseEventPermissionReplied], [EventListResponseEventFileEdited],
|
||||
// [EventListResponseEventSessionIdle], [EventListResponseEventSessionUpdated],
|
||||
// [EventListResponseEventSessionDeleted], [EventListResponseEventSessionError] or
|
||||
// [EventListResponseEventServerConnected].
|
||||
// [EventListResponseEventSessionDeleted], [EventListResponseEventSessionError],
|
||||
// [EventListResponseEventServerConnected],
|
||||
// [EventListResponseEventFileWatcherUpdated] or
|
||||
// [EventListResponseEventIdeInstalled].
|
||||
type EventListResponseUnion interface {
|
||||
implementsEventListResponse()
|
||||
}
|
||||
@@ -191,6 +197,14 @@ func init() {
|
||||
TypeFilter: gjson.JSON,
|
||||
Type: reflect.TypeOf(EventListResponseEventServerConnected{}),
|
||||
},
|
||||
apijson.UnionVariant{
|
||||
TypeFilter: gjson.JSON,
|
||||
Type: reflect.TypeOf(EventListResponseEventFileWatcherUpdated{}),
|
||||
},
|
||||
apijson.UnionVariant{
|
||||
TypeFilter: gjson.JSON,
|
||||
Type: reflect.TypeOf(EventListResponseEventIdeInstalled{}),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1196,6 +1210,144 @@ func (r EventListResponseEventServerConnectedType) IsKnown() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type EventListResponseEventFileWatcherUpdated struct {
|
||||
Properties EventListResponseEventFileWatcherUpdatedProperties `json:"properties,required"`
|
||||
Type EventListResponseEventFileWatcherUpdatedType `json:"type,required"`
|
||||
JSON eventListResponseEventFileWatcherUpdatedJSON `json:"-"`
|
||||
}
|
||||
|
||||
// eventListResponseEventFileWatcherUpdatedJSON contains the JSON metadata for the
|
||||
// struct [EventListResponseEventFileWatcherUpdated]
|
||||
type eventListResponseEventFileWatcherUpdatedJSON struct {
|
||||
Properties apijson.Field
|
||||
Type apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *EventListResponseEventFileWatcherUpdated) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r eventListResponseEventFileWatcherUpdatedJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
func (r EventListResponseEventFileWatcherUpdated) implementsEventListResponse() {}
|
||||
|
||||
type EventListResponseEventFileWatcherUpdatedProperties struct {
|
||||
Event EventListResponseEventFileWatcherUpdatedPropertiesEvent `json:"event,required"`
|
||||
File string `json:"file,required"`
|
||||
JSON eventListResponseEventFileWatcherUpdatedPropertiesJSON `json:"-"`
|
||||
}
|
||||
|
||||
// eventListResponseEventFileWatcherUpdatedPropertiesJSON contains the JSON
|
||||
// metadata for the struct [EventListResponseEventFileWatcherUpdatedProperties]
|
||||
type eventListResponseEventFileWatcherUpdatedPropertiesJSON struct {
|
||||
Event apijson.Field
|
||||
File apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *EventListResponseEventFileWatcherUpdatedProperties) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r eventListResponseEventFileWatcherUpdatedPropertiesJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type EventListResponseEventFileWatcherUpdatedPropertiesEvent string
|
||||
|
||||
const (
|
||||
EventListResponseEventFileWatcherUpdatedPropertiesEventAdd EventListResponseEventFileWatcherUpdatedPropertiesEvent = "add"
|
||||
EventListResponseEventFileWatcherUpdatedPropertiesEventChange EventListResponseEventFileWatcherUpdatedPropertiesEvent = "change"
|
||||
EventListResponseEventFileWatcherUpdatedPropertiesEventUnlink EventListResponseEventFileWatcherUpdatedPropertiesEvent = "unlink"
|
||||
)
|
||||
|
||||
func (r EventListResponseEventFileWatcherUpdatedPropertiesEvent) IsKnown() bool {
|
||||
switch r {
|
||||
case EventListResponseEventFileWatcherUpdatedPropertiesEventAdd, EventListResponseEventFileWatcherUpdatedPropertiesEventChange, EventListResponseEventFileWatcherUpdatedPropertiesEventUnlink:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type EventListResponseEventFileWatcherUpdatedType string
|
||||
|
||||
const (
|
||||
EventListResponseEventFileWatcherUpdatedTypeFileWatcherUpdated EventListResponseEventFileWatcherUpdatedType = "file.watcher.updated"
|
||||
)
|
||||
|
||||
func (r EventListResponseEventFileWatcherUpdatedType) IsKnown() bool {
|
||||
switch r {
|
||||
case EventListResponseEventFileWatcherUpdatedTypeFileWatcherUpdated:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type EventListResponseEventIdeInstalled struct {
|
||||
Properties EventListResponseEventIdeInstalledProperties `json:"properties,required"`
|
||||
Type EventListResponseEventIdeInstalledType `json:"type,required"`
|
||||
JSON eventListResponseEventIdeInstalledJSON `json:"-"`
|
||||
}
|
||||
|
||||
// eventListResponseEventIdeInstalledJSON contains the JSON metadata for the struct
|
||||
// [EventListResponseEventIdeInstalled]
|
||||
type eventListResponseEventIdeInstalledJSON struct {
|
||||
Properties apijson.Field
|
||||
Type apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *EventListResponseEventIdeInstalled) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r eventListResponseEventIdeInstalledJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
func (r EventListResponseEventIdeInstalled) implementsEventListResponse() {}
|
||||
|
||||
type EventListResponseEventIdeInstalledProperties struct {
|
||||
Ide string `json:"ide,required"`
|
||||
JSON eventListResponseEventIdeInstalledPropertiesJSON `json:"-"`
|
||||
}
|
||||
|
||||
// eventListResponseEventIdeInstalledPropertiesJSON contains the JSON metadata for
|
||||
// the struct [EventListResponseEventIdeInstalledProperties]
|
||||
type eventListResponseEventIdeInstalledPropertiesJSON struct {
|
||||
Ide apijson.Field
|
||||
raw string
|
||||
ExtraFields map[string]apijson.Field
|
||||
}
|
||||
|
||||
func (r *EventListResponseEventIdeInstalledProperties) UnmarshalJSON(data []byte) (err error) {
|
||||
return apijson.UnmarshalRoot(data, r)
|
||||
}
|
||||
|
||||
func (r eventListResponseEventIdeInstalledPropertiesJSON) RawJSON() string {
|
||||
return r.raw
|
||||
}
|
||||
|
||||
type EventListResponseEventIdeInstalledType string
|
||||
|
||||
const (
|
||||
EventListResponseEventIdeInstalledTypeIdeInstalled EventListResponseEventIdeInstalledType = "ide.installed"
|
||||
)
|
||||
|
||||
func (r EventListResponseEventIdeInstalledType) IsKnown() bool {
|
||||
switch r {
|
||||
case EventListResponseEventIdeInstalledTypeIdeInstalled:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type EventListResponseType string
|
||||
|
||||
const (
|
||||
@@ -1214,11 +1366,13 @@ const (
|
||||
EventListResponseTypeSessionDeleted EventListResponseType = "session.deleted"
|
||||
EventListResponseTypeSessionError EventListResponseType = "session.error"
|
||||
EventListResponseTypeServerConnected EventListResponseType = "server.connected"
|
||||
EventListResponseTypeFileWatcherUpdated EventListResponseType = "file.watcher.updated"
|
||||
EventListResponseTypeIdeInstalled EventListResponseType = "ide.installed"
|
||||
)
|
||||
|
||||
func (r EventListResponseType) IsKnown() bool {
|
||||
switch r {
|
||||
case EventListResponseTypeInstallationUpdated, EventListResponseTypeLspClientDiagnostics, EventListResponseTypeMessageUpdated, EventListResponseTypeMessageRemoved, EventListResponseTypeMessagePartUpdated, EventListResponseTypeMessagePartRemoved, EventListResponseTypeSessionCompacted, EventListResponseTypePermissionUpdated, EventListResponseTypePermissionReplied, EventListResponseTypeFileEdited, EventListResponseTypeSessionIdle, EventListResponseTypeSessionUpdated, EventListResponseTypeSessionDeleted, EventListResponseTypeSessionError, EventListResponseTypeServerConnected:
|
||||
case EventListResponseTypeInstallationUpdated, EventListResponseTypeLspClientDiagnostics, EventListResponseTypeMessageUpdated, EventListResponseTypeMessageRemoved, EventListResponseTypeMessagePartUpdated, EventListResponseTypeMessagePartRemoved, EventListResponseTypeSessionCompacted, EventListResponseTypePermissionUpdated, EventListResponseTypePermissionReplied, EventListResponseTypeFileEdited, EventListResponseTypeSessionIdle, EventListResponseTypeSessionUpdated, EventListResponseTypeSessionDeleted, EventListResponseTypeSessionError, EventListResponseTypeServerConnected, EventListResponseTypeFileWatcherUpdated, EventListResponseTypeIdeInstalled:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
package internal
|
||||
|
||||
const PackageVersion = "0.14.0" // x-release-please-version
|
||||
const PackageVersion = "0.15.0" // x-release-please-version
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
import { createSseClient } from "../core/serverSentEvents.gen.js"
|
||||
import type { HttpMethod } from "../core/types.gen.js"
|
||||
import { getValidRequestBody } from "../core/utils.gen.js"
|
||||
import type { Client, Config, RequestOptions, ResolvedRequestOptions } from "./types.gen.js"
|
||||
import {
|
||||
buildUrl,
|
||||
@@ -49,12 +51,12 @@ export const createClient = (config: Config = {}): Client => {
|
||||
await opts.requestValidator(opts)
|
||||
}
|
||||
|
||||
if (opts.body && opts.bodySerializer) {
|
||||
if (opts.body !== undefined && opts.bodySerializer) {
|
||||
opts.serializedBody = opts.bodySerializer(opts.body)
|
||||
}
|
||||
|
||||
// remove Content-Type header if body is empty to avoid sending invalid requests
|
||||
if (opts.serializedBody === undefined || opts.serializedBody === "") {
|
||||
if (opts.body === undefined || opts.serializedBody === "") {
|
||||
opts.headers.delete("Content-Type")
|
||||
}
|
||||
|
||||
@@ -69,7 +71,7 @@ export const createClient = (config: Config = {}): Client => {
|
||||
const requestInit: ReqInit = {
|
||||
redirect: "follow",
|
||||
...opts,
|
||||
body: opts.serializedBody,
|
||||
body: getValidRequestBody(opts),
|
||||
}
|
||||
|
||||
let request = new Request(url, requestInit)
|
||||
@@ -97,18 +99,36 @@ export const createClient = (config: Config = {}): Client => {
|
||||
}
|
||||
|
||||
if (response.ok) {
|
||||
const parseAs =
|
||||
(opts.parseAs === "auto" ? getParseAs(response.headers.get("Content-Type")) : opts.parseAs) ?? "json"
|
||||
|
||||
if (response.status === 204 || response.headers.get("Content-Length") === "0") {
|
||||
let emptyData: any
|
||||
switch (parseAs) {
|
||||
case "arrayBuffer":
|
||||
case "blob":
|
||||
case "text":
|
||||
emptyData = await response[parseAs]()
|
||||
break
|
||||
case "formData":
|
||||
emptyData = new FormData()
|
||||
break
|
||||
case "stream":
|
||||
emptyData = response.body
|
||||
break
|
||||
case "json":
|
||||
default:
|
||||
emptyData = {}
|
||||
break
|
||||
}
|
||||
return opts.responseStyle === "data"
|
||||
? {}
|
||||
? emptyData
|
||||
: {
|
||||
data: {},
|
||||
data: emptyData,
|
||||
...result,
|
||||
}
|
||||
}
|
||||
|
||||
const parseAs =
|
||||
(opts.parseAs === "auto" ? getParseAs(response.headers.get("Content-Type")) : opts.parseAs) ?? "json"
|
||||
|
||||
let data: any
|
||||
switch (parseAs) {
|
||||
case "arrayBuffer":
|
||||
@@ -178,35 +198,53 @@ export const createClient = (config: Config = {}): Client => {
|
||||
}
|
||||
}
|
||||
|
||||
const makeMethod = (method: Required<Config>["method"]) => {
|
||||
const fn = (options: RequestOptions) => request({ ...options, method })
|
||||
fn.sse = async (options: RequestOptions) => {
|
||||
const { opts, url } = await beforeRequest(options)
|
||||
return createSseClient({
|
||||
...opts,
|
||||
body: opts.body as BodyInit | null | undefined,
|
||||
headers: opts.headers as unknown as Record<string, string>,
|
||||
method,
|
||||
url,
|
||||
})
|
||||
}
|
||||
return fn
|
||||
const makeMethodFn = (method: Uppercase<HttpMethod>) => (options: RequestOptions) => request({ ...options, method })
|
||||
|
||||
const makeSseFn = (method: Uppercase<HttpMethod>) => async (options: RequestOptions) => {
|
||||
const { opts, url } = await beforeRequest(options)
|
||||
return createSseClient({
|
||||
...opts,
|
||||
body: opts.body as BodyInit | null | undefined,
|
||||
headers: opts.headers as unknown as Record<string, string>,
|
||||
method,
|
||||
onRequest: async (url, init) => {
|
||||
let request = new Request(url, init)
|
||||
for (const fn of interceptors.request._fns) {
|
||||
if (fn) {
|
||||
request = await fn(request, opts)
|
||||
}
|
||||
}
|
||||
return request
|
||||
},
|
||||
url,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
buildUrl,
|
||||
connect: makeMethod("CONNECT"),
|
||||
delete: makeMethod("DELETE"),
|
||||
get: makeMethod("GET"),
|
||||
connect: makeMethodFn("CONNECT"),
|
||||
delete: makeMethodFn("DELETE"),
|
||||
get: makeMethodFn("GET"),
|
||||
getConfig,
|
||||
head: makeMethod("HEAD"),
|
||||
head: makeMethodFn("HEAD"),
|
||||
interceptors,
|
||||
options: makeMethod("OPTIONS"),
|
||||
patch: makeMethod("PATCH"),
|
||||
post: makeMethod("POST"),
|
||||
put: makeMethod("PUT"),
|
||||
options: makeMethodFn("OPTIONS"),
|
||||
patch: makeMethodFn("PATCH"),
|
||||
post: makeMethodFn("POST"),
|
||||
put: makeMethodFn("PUT"),
|
||||
request,
|
||||
setConfig,
|
||||
trace: makeMethod("TRACE"),
|
||||
sse: {
|
||||
connect: makeSseFn("CONNECT"),
|
||||
delete: makeSseFn("DELETE"),
|
||||
get: makeSseFn("GET"),
|
||||
head: makeSseFn("HEAD"),
|
||||
options: makeSseFn("OPTIONS"),
|
||||
patch: makeSseFn("PATCH"),
|
||||
post: makeSseFn("POST"),
|
||||
put: makeSseFn("PUT"),
|
||||
trace: makeSseFn("TRACE"),
|
||||
},
|
||||
trace: makeMethodFn("TRACE"),
|
||||
} as Client
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export interface Config<T extends ClientOptions = ClientOptions>
|
||||
*
|
||||
* @default globalThis.fetch
|
||||
*/
|
||||
fetch?: (request: Request) => ReturnType<typeof fetch>
|
||||
fetch?: typeof fetch
|
||||
/**
|
||||
* Please don't use the Fetch client for Next.js applications. The `next`
|
||||
* options won't have any effect.
|
||||
@@ -128,7 +128,7 @@ export interface ClientOptions {
|
||||
throwOnError?: boolean
|
||||
}
|
||||
|
||||
type MethodFnBase = <
|
||||
type MethodFn = <
|
||||
TData = unknown,
|
||||
TError = unknown,
|
||||
ThrowOnError extends boolean = false,
|
||||
@@ -137,7 +137,7 @@ type MethodFnBase = <
|
||||
options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, "method">,
|
||||
) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>
|
||||
|
||||
type MethodFnServerSentEvents = <
|
||||
type SseFn = <
|
||||
TData = unknown,
|
||||
TError = unknown,
|
||||
ThrowOnError extends boolean = false,
|
||||
@@ -146,10 +146,6 @@ type MethodFnServerSentEvents = <
|
||||
options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, "method">,
|
||||
) => Promise<ServerSentEventsResult<TData, TError>>
|
||||
|
||||
type MethodFn = MethodFnBase & {
|
||||
sse: MethodFnServerSentEvents
|
||||
}
|
||||
|
||||
type RequestFn = <
|
||||
TData = unknown,
|
||||
TError = unknown,
|
||||
@@ -171,7 +167,7 @@ type BuildUrlFn = <
|
||||
options: Pick<TData, "url"> & Options<TData>,
|
||||
) => string
|
||||
|
||||
export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn> & {
|
||||
export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn, SseFn> & {
|
||||
interceptors: Middleware<Request, Response, unknown, ResolvedRequestOptions>
|
||||
}
|
||||
|
||||
|
||||
@@ -162,14 +162,22 @@ export const mergeConfigs = (a: Config, b: Config): Config => {
|
||||
return config
|
||||
}
|
||||
|
||||
const headersEntries = (headers: Headers): Array<[string, string]> => {
|
||||
const entries: Array<[string, string]> = []
|
||||
headers.forEach((value, key) => {
|
||||
entries.push([key, value])
|
||||
})
|
||||
return entries
|
||||
}
|
||||
|
||||
export const mergeHeaders = (...headers: Array<Required<Config>["headers"] | undefined>): Headers => {
|
||||
const mergedHeaders = new Headers()
|
||||
for (const header of headers) {
|
||||
if (!header || typeof header !== "object") {
|
||||
if (!header) {
|
||||
continue
|
||||
}
|
||||
|
||||
const iterator = header instanceof Headers ? header.entries() : Object.entries(header)
|
||||
const iterator = header instanceof Headers ? headersEntries(header) : Object.entries(header)
|
||||
|
||||
for (const [key, value] of iterator) {
|
||||
if (value === null) {
|
||||
|
||||
@@ -4,6 +4,17 @@ import type { Config } from "./types.gen.js"
|
||||
|
||||
export type ServerSentEventsOptions<TData = unknown> = Omit<RequestInit, "method"> &
|
||||
Pick<Config, "method" | "responseTransformer" | "responseValidator"> & {
|
||||
/**
|
||||
* Fetch API implementation. You can use this option to provide a custom
|
||||
* fetch instance.
|
||||
*
|
||||
* @default globalThis.fetch
|
||||
*/
|
||||
fetch?: typeof fetch
|
||||
/**
|
||||
* Implementing clients can call request interceptors inside this hook.
|
||||
*/
|
||||
onRequest?: (url: string, init: RequestInit) => Promise<Request>
|
||||
/**
|
||||
* Callback invoked when a network or parsing error occurs during streaming.
|
||||
*
|
||||
@@ -21,6 +32,7 @@ export type ServerSentEventsOptions<TData = unknown> = Omit<RequestInit, "method
|
||||
* @returns Nothing (void).
|
||||
*/
|
||||
onSseEvent?: (event: StreamEvent<TData>) => void
|
||||
serializedBody?: RequestInit["body"]
|
||||
/**
|
||||
* Default retry delay in milliseconds.
|
||||
*
|
||||
@@ -64,6 +76,7 @@ export type ServerSentEventsResult<TData = unknown, TReturn = void, TNext = unkn
|
||||
}
|
||||
|
||||
export const createSseClient = <TData = unknown>({
|
||||
onRequest,
|
||||
onSseError,
|
||||
onSseEvent,
|
||||
responseTransformer,
|
||||
@@ -99,7 +112,21 @@ export const createSseClient = <TData = unknown>({
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url, { ...options, headers, signal })
|
||||
const requestInit: RequestInit = {
|
||||
redirect: "follow",
|
||||
...options,
|
||||
body: options.serializedBody,
|
||||
headers,
|
||||
signal,
|
||||
}
|
||||
let request = new Request(url, requestInit)
|
||||
if (onRequest) {
|
||||
request = await onRequest(url, requestInit)
|
||||
}
|
||||
// fetch must be assigned here, otherwise it would throw the error:
|
||||
// TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation
|
||||
const _fetch = options.fetch ?? globalThis.fetch
|
||||
const response = await _fetch(request)
|
||||
|
||||
if (!response.ok) throw new Error(`SSE failed: ${response.status} ${response.statusText}`)
|
||||
|
||||
|
||||
@@ -3,24 +3,19 @@
|
||||
import type { Auth, AuthToken } from "./auth.gen.js"
|
||||
import type { BodySerializer, QuerySerializer, QuerySerializerOptions } from "./bodySerializer.gen.js"
|
||||
|
||||
export interface Client<RequestFn = never, Config = unknown, MethodFn = never, BuildUrlFn = never> {
|
||||
export type HttpMethod = "connect" | "delete" | "get" | "head" | "options" | "patch" | "post" | "put" | "trace"
|
||||
|
||||
export type Client<RequestFn = never, Config = unknown, MethodFn = never, BuildUrlFn = never, SseFn = never> = {
|
||||
/**
|
||||
* Returns the final request URL.
|
||||
*/
|
||||
buildUrl: BuildUrlFn
|
||||
connect: MethodFn
|
||||
delete: MethodFn
|
||||
get: MethodFn
|
||||
getConfig: () => Config
|
||||
head: MethodFn
|
||||
options: MethodFn
|
||||
patch: MethodFn
|
||||
post: MethodFn
|
||||
put: MethodFn
|
||||
request: RequestFn
|
||||
setConfig: (config: Config) => Config
|
||||
trace: MethodFn
|
||||
}
|
||||
} & {
|
||||
[K in HttpMethod]: MethodFn
|
||||
} & ([SseFn] extends [never] ? { sse?: never } : { sse: { [K in HttpMethod]: SseFn } })
|
||||
|
||||
export interface Config {
|
||||
/**
|
||||
@@ -47,7 +42,7 @@ export interface Config {
|
||||
*
|
||||
* {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more}
|
||||
*/
|
||||
method?: "CONNECT" | "DELETE" | "GET" | "HEAD" | "OPTIONS" | "PATCH" | "POST" | "PUT" | "TRACE"
|
||||
method?: Uppercase<HttpMethod>
|
||||
/**
|
||||
* A function for serializing request query parameters. By default, arrays
|
||||
* will be exploded in form style, objects will be exploded in deepObject
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
import type { QuerySerializer } from "./bodySerializer.gen.js"
|
||||
import type { BodySerializer, QuerySerializer } from "./bodySerializer.gen.js"
|
||||
import {
|
||||
type ArraySeparatorStyle,
|
||||
serializeArrayParam,
|
||||
@@ -107,3 +107,31 @@ export const getUrl = ({
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
export function getValidRequestBody(options: {
|
||||
body?: unknown
|
||||
bodySerializer?: BodySerializer | null
|
||||
serializedBody?: unknown
|
||||
}) {
|
||||
const hasBody = options.body !== undefined
|
||||
const isSerializedBody = hasBody && options.bodySerializer
|
||||
|
||||
if (isSerializedBody) {
|
||||
if ("serializedBody" in options) {
|
||||
const hasSerializedBody = options.serializedBody !== undefined && options.serializedBody !== ""
|
||||
|
||||
return hasSerializedBody ? options.serializedBody : null
|
||||
}
|
||||
|
||||
// not all clients implement a serializedBody property (i.e. client-axios)
|
||||
return options.body !== "" ? options.body : null
|
||||
}
|
||||
|
||||
// plain/text body
|
||||
if (hasBody) {
|
||||
return options.body
|
||||
}
|
||||
|
||||
// no body was provided
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -6,8 +6,6 @@ import type {
|
||||
ProjectListResponses,
|
||||
ProjectCurrentData,
|
||||
ProjectCurrentResponses,
|
||||
EventSubscribeData,
|
||||
EventSubscribeResponses,
|
||||
ConfigGetData,
|
||||
ConfigGetResponses,
|
||||
ToolRegisterData,
|
||||
@@ -101,6 +99,8 @@ import type {
|
||||
AuthSetData,
|
||||
AuthSetResponses,
|
||||
AuthSetErrors,
|
||||
EventSubscribeData,
|
||||
EventSubscribeResponses,
|
||||
} from "./types.gen.js"
|
||||
import { client as _heyApiClient } from "./client.gen.js"
|
||||
|
||||
@@ -153,18 +153,6 @@ class Project extends _HeyApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
class Event extends _HeyApiClient {
|
||||
/**
|
||||
* Get events
|
||||
*/
|
||||
public subscribe<ThrowOnError extends boolean = false>(options?: Options<EventSubscribeData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).get.sse<EventSubscribeResponses, unknown, ThrowOnError>({
|
||||
url: "/event",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class Config extends _HeyApiClient {
|
||||
/**
|
||||
* Get config info
|
||||
@@ -671,6 +659,18 @@ class Auth extends _HeyApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
class Event extends _HeyApiClient {
|
||||
/**
|
||||
* Get events
|
||||
*/
|
||||
public subscribe<ThrowOnError extends boolean = false>(options?: Options<EventSubscribeData, ThrowOnError>) {
|
||||
return (options?.client ?? this._client).sse.get<EventSubscribeResponses, unknown, ThrowOnError>({
|
||||
url: "/event",
|
||||
...options,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class OpencodeClient extends _HeyApiClient {
|
||||
/**
|
||||
* Respond to a permission request
|
||||
@@ -688,7 +688,6 @@ export class OpencodeClient extends _HeyApiClient {
|
||||
})
|
||||
}
|
||||
project = new Project({ client: this._client })
|
||||
event = new Event({ client: this._client })
|
||||
config = new Config({ client: this._client })
|
||||
tool = new Tool({ client: this._client })
|
||||
path = new Path({ client: this._client })
|
||||
@@ -699,4 +698,5 @@ export class OpencodeClient extends _HeyApiClient {
|
||||
app = new App({ client: this._client })
|
||||
tui = new Tui({ client: this._client })
|
||||
auth = new Auth({ client: this._client })
|
||||
event = new Event({ client: this._client })
|
||||
}
|
||||
|
||||
@@ -10,440 +10,6 @@ export type Project = {
|
||||
}
|
||||
}
|
||||
|
||||
export type EventInstallationUpdated = {
|
||||
type: "installation.updated"
|
||||
properties: {
|
||||
version: string
|
||||
}
|
||||
}
|
||||
|
||||
export type EventLspClientDiagnostics = {
|
||||
type: "lsp.client.diagnostics"
|
||||
properties: {
|
||||
serverID: string
|
||||
path: string
|
||||
}
|
||||
}
|
||||
|
||||
export type UserMessage = {
|
||||
id: string
|
||||
sessionID: string
|
||||
role: "user"
|
||||
time: {
|
||||
created: number
|
||||
}
|
||||
}
|
||||
|
||||
export type ProviderAuthError = {
|
||||
name: "ProviderAuthError"
|
||||
data: {
|
||||
providerID: string
|
||||
message: string
|
||||
}
|
||||
}
|
||||
|
||||
export type UnknownError = {
|
||||
name: "UnknownError"
|
||||
data: {
|
||||
message: string
|
||||
}
|
||||
}
|
||||
|
||||
export type MessageOutputLengthError = {
|
||||
name: "MessageOutputLengthError"
|
||||
data: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
|
||||
export type MessageAbortedError = {
|
||||
name: "MessageAbortedError"
|
||||
data: {
|
||||
message: string
|
||||
}
|
||||
}
|
||||
|
||||
export type AssistantMessage = {
|
||||
id: string
|
||||
sessionID: string
|
||||
role: "assistant"
|
||||
time: {
|
||||
created: number
|
||||
completed?: number
|
||||
}
|
||||
error?: ProviderAuthError | UnknownError | MessageOutputLengthError | MessageAbortedError
|
||||
system: Array<string>
|
||||
modelID: string
|
||||
providerID: string
|
||||
mode: string
|
||||
path: {
|
||||
cwd: string
|
||||
root: string
|
||||
}
|
||||
summary?: boolean
|
||||
cost: number
|
||||
tokens: {
|
||||
input: number
|
||||
output: number
|
||||
reasoning: number
|
||||
cache: {
|
||||
read: number
|
||||
write: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type Message = UserMessage | AssistantMessage
|
||||
|
||||
export type EventMessageUpdated = {
|
||||
type: "message.updated"
|
||||
properties: {
|
||||
info: Message
|
||||
}
|
||||
}
|
||||
|
||||
export type EventMessageRemoved = {
|
||||
type: "message.removed"
|
||||
properties: {
|
||||
sessionID: string
|
||||
messageID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type TextPart = {
|
||||
id: string
|
||||
sessionID: string
|
||||
messageID: string
|
||||
type: "text"
|
||||
text: string
|
||||
synthetic?: boolean
|
||||
time?: {
|
||||
start: number
|
||||
end?: number
|
||||
}
|
||||
}
|
||||
|
||||
export type ReasoningPart = {
|
||||
id: string
|
||||
sessionID: string
|
||||
messageID: string
|
||||
type: "reasoning"
|
||||
text: string
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
time: {
|
||||
start: number
|
||||
end?: number
|
||||
}
|
||||
}
|
||||
|
||||
export type FilePartSourceText = {
|
||||
value: string
|
||||
start: number
|
||||
end: number
|
||||
}
|
||||
|
||||
export type FileSource = {
|
||||
text: FilePartSourceText
|
||||
type: "file"
|
||||
path: string
|
||||
}
|
||||
|
||||
export type Range = {
|
||||
start: {
|
||||
line: number
|
||||
character: number
|
||||
}
|
||||
end: {
|
||||
line: number
|
||||
character: number
|
||||
}
|
||||
}
|
||||
|
||||
export type SymbolSource = {
|
||||
text: FilePartSourceText
|
||||
type: "symbol"
|
||||
path: string
|
||||
range: Range
|
||||
name: string
|
||||
kind: number
|
||||
}
|
||||
|
||||
export type FilePartSource = FileSource | SymbolSource
|
||||
|
||||
export type FilePart = {
|
||||
id: string
|
||||
sessionID: string
|
||||
messageID: string
|
||||
type: "file"
|
||||
mime: string
|
||||
filename?: string
|
||||
url: string
|
||||
source?: FilePartSource
|
||||
}
|
||||
|
||||
export type ToolStatePending = {
|
||||
status: "pending"
|
||||
}
|
||||
|
||||
export type ToolStateRunning = {
|
||||
status: "running"
|
||||
input: unknown
|
||||
title?: string
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
time: {
|
||||
start: number
|
||||
}
|
||||
}
|
||||
|
||||
export type ToolStateCompleted = {
|
||||
status: "completed"
|
||||
input: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
output: string
|
||||
title: string
|
||||
metadata: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
time: {
|
||||
start: number
|
||||
end: number
|
||||
compacted?: number
|
||||
}
|
||||
}
|
||||
|
||||
export type ToolStateError = {
|
||||
status: "error"
|
||||
input: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
error: string
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
time: {
|
||||
start: number
|
||||
end: number
|
||||
}
|
||||
}
|
||||
|
||||
export type ToolState = ToolStatePending | ToolStateRunning | ToolStateCompleted | ToolStateError
|
||||
|
||||
export type ToolPart = {
|
||||
id: string
|
||||
sessionID: string
|
||||
messageID: string
|
||||
type: "tool"
|
||||
callID: string
|
||||
tool: string
|
||||
state: ToolState
|
||||
}
|
||||
|
||||
export type StepStartPart = {
|
||||
id: string
|
||||
sessionID: string
|
||||
messageID: string
|
||||
type: "step-start"
|
||||
}
|
||||
|
||||
export type StepFinishPart = {
|
||||
id: string
|
||||
sessionID: string
|
||||
messageID: string
|
||||
type: "step-finish"
|
||||
cost: number
|
||||
tokens: {
|
||||
input: number
|
||||
output: number
|
||||
reasoning: number
|
||||
cache: {
|
||||
read: number
|
||||
write: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type SnapshotPart = {
|
||||
id: string
|
||||
sessionID: string
|
||||
messageID: string
|
||||
type: "snapshot"
|
||||
snapshot: string
|
||||
}
|
||||
|
||||
export type PatchPart = {
|
||||
id: string
|
||||
sessionID: string
|
||||
messageID: string
|
||||
type: "patch"
|
||||
hash: string
|
||||
files: Array<string>
|
||||
}
|
||||
|
||||
export type AgentPart = {
|
||||
id: string
|
||||
sessionID: string
|
||||
messageID: string
|
||||
type: "agent"
|
||||
name: string
|
||||
source?: {
|
||||
value: string
|
||||
start: number
|
||||
end: number
|
||||
}
|
||||
}
|
||||
|
||||
export type Part =
|
||||
| TextPart
|
||||
| ReasoningPart
|
||||
| FilePart
|
||||
| ToolPart
|
||||
| StepStartPart
|
||||
| StepFinishPart
|
||||
| SnapshotPart
|
||||
| PatchPart
|
||||
| AgentPart
|
||||
|
||||
export type EventMessagePartUpdated = {
|
||||
type: "message.part.updated"
|
||||
properties: {
|
||||
part: Part
|
||||
}
|
||||
}
|
||||
|
||||
export type EventMessagePartRemoved = {
|
||||
type: "message.part.removed"
|
||||
properties: {
|
||||
sessionID: string
|
||||
messageID: string
|
||||
partID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type EventSessionCompacted = {
|
||||
type: "session.compacted"
|
||||
properties: {
|
||||
sessionID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type Permission = {
|
||||
id: string
|
||||
type: string
|
||||
pattern?: string | Array<string>
|
||||
sessionID: string
|
||||
messageID: string
|
||||
callID?: string
|
||||
title: string
|
||||
metadata: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
time: {
|
||||
created: number
|
||||
}
|
||||
}
|
||||
|
||||
export type EventPermissionUpdated = {
|
||||
type: "permission.updated"
|
||||
properties: Permission
|
||||
}
|
||||
|
||||
export type EventPermissionReplied = {
|
||||
type: "permission.replied"
|
||||
properties: {
|
||||
sessionID: string
|
||||
permissionID: string
|
||||
response: string
|
||||
}
|
||||
}
|
||||
|
||||
export type EventFileEdited = {
|
||||
type: "file.edited"
|
||||
properties: {
|
||||
file: string
|
||||
}
|
||||
}
|
||||
|
||||
export type EventSessionIdle = {
|
||||
type: "session.idle"
|
||||
properties: {
|
||||
sessionID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type Session = {
|
||||
id: string
|
||||
projectID: string
|
||||
directory: string
|
||||
parentID?: string
|
||||
share?: {
|
||||
url: string
|
||||
}
|
||||
title: string
|
||||
version: string
|
||||
time: {
|
||||
created: number
|
||||
updated: number
|
||||
compacting?: number
|
||||
}
|
||||
revert?: {
|
||||
messageID: string
|
||||
partID?: string
|
||||
snapshot?: string
|
||||
diff?: string
|
||||
}
|
||||
}
|
||||
|
||||
export type EventSessionUpdated = {
|
||||
type: "session.updated"
|
||||
properties: {
|
||||
info: Session
|
||||
}
|
||||
}
|
||||
|
||||
export type EventSessionDeleted = {
|
||||
type: "session.deleted"
|
||||
properties: {
|
||||
info: Session
|
||||
}
|
||||
}
|
||||
|
||||
export type EventSessionError = {
|
||||
type: "session.error"
|
||||
properties: {
|
||||
sessionID?: string
|
||||
error?: ProviderAuthError | UnknownError | MessageOutputLengthError | MessageAbortedError
|
||||
}
|
||||
}
|
||||
|
||||
export type EventServerConnected = {
|
||||
type: "server.connected"
|
||||
properties: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
|
||||
export type Event =
|
||||
| EventInstallationUpdated
|
||||
| EventLspClientDiagnostics
|
||||
| EventMessageUpdated
|
||||
| EventMessageRemoved
|
||||
| EventMessagePartUpdated
|
||||
| EventMessagePartRemoved
|
||||
| EventSessionCompacted
|
||||
| EventPermissionUpdated
|
||||
| EventPermissionReplied
|
||||
| EventFileEdited
|
||||
| EventSessionIdle
|
||||
| EventSessionUpdated
|
||||
| EventSessionDeleted
|
||||
| EventSessionError
|
||||
| EventServerConnected
|
||||
|
||||
/**
|
||||
* Custom keybind configurations
|
||||
*/
|
||||
@@ -983,6 +549,297 @@ export type Path = {
|
||||
directory: string
|
||||
}
|
||||
|
||||
export type Session = {
|
||||
id: string
|
||||
projectID: string
|
||||
directory: string
|
||||
parentID?: string
|
||||
share?: {
|
||||
url: string
|
||||
}
|
||||
title: string
|
||||
version: string
|
||||
time: {
|
||||
created: number
|
||||
updated: number
|
||||
compacting?: number
|
||||
}
|
||||
revert?: {
|
||||
messageID: string
|
||||
partID?: string
|
||||
snapshot?: string
|
||||
diff?: string
|
||||
}
|
||||
}
|
||||
|
||||
export type UserMessage = {
|
||||
id: string
|
||||
sessionID: string
|
||||
role: "user"
|
||||
time: {
|
||||
created: number
|
||||
}
|
||||
}
|
||||
|
||||
export type ProviderAuthError = {
|
||||
name: "ProviderAuthError"
|
||||
data: {
|
||||
providerID: string
|
||||
message: string
|
||||
}
|
||||
}
|
||||
|
||||
export type UnknownError = {
|
||||
name: "UnknownError"
|
||||
data: {
|
||||
message: string
|
||||
}
|
||||
}
|
||||
|
||||
export type MessageOutputLengthError = {
|
||||
name: "MessageOutputLengthError"
|
||||
data: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
|
||||
export type MessageAbortedError = {
|
||||
name: "MessageAbortedError"
|
||||
data: {
|
||||
message: string
|
||||
}
|
||||
}
|
||||
|
||||
export type AssistantMessage = {
|
||||
id: string
|
||||
sessionID: string
|
||||
role: "assistant"
|
||||
time: {
|
||||
created: number
|
||||
completed?: number
|
||||
}
|
||||
error?: ProviderAuthError | UnknownError | MessageOutputLengthError | MessageAbortedError
|
||||
system: Array<string>
|
||||
modelID: string
|
||||
providerID: string
|
||||
mode: string
|
||||
path: {
|
||||
cwd: string
|
||||
root: string
|
||||
}
|
||||
summary?: boolean
|
||||
cost: number
|
||||
tokens: {
|
||||
input: number
|
||||
output: number
|
||||
reasoning: number
|
||||
cache: {
|
||||
read: number
|
||||
write: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type Message = UserMessage | AssistantMessage
|
||||
|
||||
export type TextPart = {
|
||||
id: string
|
||||
sessionID: string
|
||||
messageID: string
|
||||
type: "text"
|
||||
text: string
|
||||
synthetic?: boolean
|
||||
time?: {
|
||||
start: number
|
||||
end?: number
|
||||
}
|
||||
}
|
||||
|
||||
export type ReasoningPart = {
|
||||
id: string
|
||||
sessionID: string
|
||||
messageID: string
|
||||
type: "reasoning"
|
||||
text: string
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
time: {
|
||||
start: number
|
||||
end?: number
|
||||
}
|
||||
}
|
||||
|
||||
export type FilePartSourceText = {
|
||||
value: string
|
||||
start: number
|
||||
end: number
|
||||
}
|
||||
|
||||
export type FileSource = {
|
||||
text: FilePartSourceText
|
||||
type: "file"
|
||||
path: string
|
||||
}
|
||||
|
||||
export type Range = {
|
||||
start: {
|
||||
line: number
|
||||
character: number
|
||||
}
|
||||
end: {
|
||||
line: number
|
||||
character: number
|
||||
}
|
||||
}
|
||||
|
||||
export type SymbolSource = {
|
||||
text: FilePartSourceText
|
||||
type: "symbol"
|
||||
path: string
|
||||
range: Range
|
||||
name: string
|
||||
kind: number
|
||||
}
|
||||
|
||||
export type FilePartSource = FileSource | SymbolSource
|
||||
|
||||
export type FilePart = {
|
||||
id: string
|
||||
sessionID: string
|
||||
messageID: string
|
||||
type: "file"
|
||||
mime: string
|
||||
filename?: string
|
||||
url: string
|
||||
source?: FilePartSource
|
||||
}
|
||||
|
||||
export type ToolStatePending = {
|
||||
status: "pending"
|
||||
}
|
||||
|
||||
export type ToolStateRunning = {
|
||||
status: "running"
|
||||
input: unknown
|
||||
title?: string
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
time: {
|
||||
start: number
|
||||
}
|
||||
}
|
||||
|
||||
export type ToolStateCompleted = {
|
||||
status: "completed"
|
||||
input: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
output: string
|
||||
title: string
|
||||
metadata: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
time: {
|
||||
start: number
|
||||
end: number
|
||||
compacted?: number
|
||||
}
|
||||
}
|
||||
|
||||
export type ToolStateError = {
|
||||
status: "error"
|
||||
input: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
error: string
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
time: {
|
||||
start: number
|
||||
end: number
|
||||
}
|
||||
}
|
||||
|
||||
export type ToolState = ToolStatePending | ToolStateRunning | ToolStateCompleted | ToolStateError
|
||||
|
||||
export type ToolPart = {
|
||||
id: string
|
||||
sessionID: string
|
||||
messageID: string
|
||||
type: "tool"
|
||||
callID: string
|
||||
tool: string
|
||||
state: ToolState
|
||||
}
|
||||
|
||||
export type StepStartPart = {
|
||||
id: string
|
||||
sessionID: string
|
||||
messageID: string
|
||||
type: "step-start"
|
||||
}
|
||||
|
||||
export type StepFinishPart = {
|
||||
id: string
|
||||
sessionID: string
|
||||
messageID: string
|
||||
type: "step-finish"
|
||||
cost: number
|
||||
tokens: {
|
||||
input: number
|
||||
output: number
|
||||
reasoning: number
|
||||
cache: {
|
||||
read: number
|
||||
write: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type SnapshotPart = {
|
||||
id: string
|
||||
sessionID: string
|
||||
messageID: string
|
||||
type: "snapshot"
|
||||
snapshot: string
|
||||
}
|
||||
|
||||
export type PatchPart = {
|
||||
id: string
|
||||
sessionID: string
|
||||
messageID: string
|
||||
type: "patch"
|
||||
hash: string
|
||||
files: Array<string>
|
||||
}
|
||||
|
||||
export type AgentPart = {
|
||||
id: string
|
||||
sessionID: string
|
||||
messageID: string
|
||||
type: "agent"
|
||||
name: string
|
||||
source?: {
|
||||
value: string
|
||||
start: number
|
||||
end: number
|
||||
}
|
||||
}
|
||||
|
||||
export type Part =
|
||||
| TextPart
|
||||
| ReasoningPart
|
||||
| FilePart
|
||||
| ToolPart
|
||||
| StepStartPart
|
||||
| StepFinishPart
|
||||
| SnapshotPart
|
||||
| PatchPart
|
||||
| AgentPart
|
||||
|
||||
export type TextPartInput = {
|
||||
id?: string
|
||||
type: "text"
|
||||
@@ -1151,6 +1008,166 @@ export type WellKnownAuth = {
|
||||
|
||||
export type Auth = OAuth | ApiAuth | WellKnownAuth
|
||||
|
||||
export type EventInstallationUpdated = {
|
||||
type: "installation.updated"
|
||||
properties: {
|
||||
version: string
|
||||
}
|
||||
}
|
||||
|
||||
export type EventLspClientDiagnostics = {
|
||||
type: "lsp.client.diagnostics"
|
||||
properties: {
|
||||
serverID: string
|
||||
path: string
|
||||
}
|
||||
}
|
||||
|
||||
export type EventMessageUpdated = {
|
||||
type: "message.updated"
|
||||
properties: {
|
||||
info: Message
|
||||
}
|
||||
}
|
||||
|
||||
export type EventMessageRemoved = {
|
||||
type: "message.removed"
|
||||
properties: {
|
||||
sessionID: string
|
||||
messageID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type EventMessagePartUpdated = {
|
||||
type: "message.part.updated"
|
||||
properties: {
|
||||
part: Part
|
||||
}
|
||||
}
|
||||
|
||||
export type EventMessagePartRemoved = {
|
||||
type: "message.part.removed"
|
||||
properties: {
|
||||
sessionID: string
|
||||
messageID: string
|
||||
partID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type EventSessionCompacted = {
|
||||
type: "session.compacted"
|
||||
properties: {
|
||||
sessionID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type Permission = {
|
||||
id: string
|
||||
type: string
|
||||
pattern?: string | Array<string>
|
||||
sessionID: string
|
||||
messageID: string
|
||||
callID?: string
|
||||
title: string
|
||||
metadata: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
time: {
|
||||
created: number
|
||||
}
|
||||
}
|
||||
|
||||
export type EventPermissionUpdated = {
|
||||
type: "permission.updated"
|
||||
properties: Permission
|
||||
}
|
||||
|
||||
export type EventPermissionReplied = {
|
||||
type: "permission.replied"
|
||||
properties: {
|
||||
sessionID: string
|
||||
permissionID: string
|
||||
response: string
|
||||
}
|
||||
}
|
||||
|
||||
export type EventFileEdited = {
|
||||
type: "file.edited"
|
||||
properties: {
|
||||
file: string
|
||||
}
|
||||
}
|
||||
|
||||
export type EventSessionIdle = {
|
||||
type: "session.idle"
|
||||
properties: {
|
||||
sessionID: string
|
||||
}
|
||||
}
|
||||
|
||||
export type EventSessionUpdated = {
|
||||
type: "session.updated"
|
||||
properties: {
|
||||
info: Session
|
||||
}
|
||||
}
|
||||
|
||||
export type EventSessionDeleted = {
|
||||
type: "session.deleted"
|
||||
properties: {
|
||||
info: Session
|
||||
}
|
||||
}
|
||||
|
||||
export type EventSessionError = {
|
||||
type: "session.error"
|
||||
properties: {
|
||||
sessionID?: string
|
||||
error?: ProviderAuthError | UnknownError | MessageOutputLengthError | MessageAbortedError
|
||||
}
|
||||
}
|
||||
|
||||
export type EventServerConnected = {
|
||||
type: "server.connected"
|
||||
properties: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
}
|
||||
|
||||
export type EventFileWatcherUpdated = {
|
||||
type: "file.watcher.updated"
|
||||
properties: {
|
||||
file: string
|
||||
event: "add" | "change" | "unlink"
|
||||
}
|
||||
}
|
||||
|
||||
export type EventIdeInstalled = {
|
||||
type: "ide.installed"
|
||||
properties: {
|
||||
ide: string
|
||||
}
|
||||
}
|
||||
|
||||
export type Event =
|
||||
| EventInstallationUpdated
|
||||
| EventLspClientDiagnostics
|
||||
| EventMessageUpdated
|
||||
| EventMessageRemoved
|
||||
| EventMessagePartUpdated
|
||||
| EventMessagePartRemoved
|
||||
| EventSessionCompacted
|
||||
| EventPermissionUpdated
|
||||
| EventPermissionReplied
|
||||
| EventFileEdited
|
||||
| EventSessionIdle
|
||||
| EventSessionUpdated
|
||||
| EventSessionDeleted
|
||||
| EventSessionError
|
||||
| EventServerConnected
|
||||
| EventFileWatcherUpdated
|
||||
| EventIdeInstalled
|
||||
|
||||
export type ProjectListData = {
|
||||
body?: never
|
||||
path?: never
|
||||
@@ -1187,24 +1204,6 @@ export type ProjectCurrentResponses = {
|
||||
|
||||
export type ProjectCurrentResponse = ProjectCurrentResponses[keyof ProjectCurrentResponses]
|
||||
|
||||
export type EventSubscribeData = {
|
||||
body?: never
|
||||
path?: never
|
||||
query?: {
|
||||
directory?: string
|
||||
}
|
||||
url: "/event"
|
||||
}
|
||||
|
||||
export type EventSubscribeResponses = {
|
||||
/**
|
||||
* Event stream
|
||||
*/
|
||||
200: Event
|
||||
}
|
||||
|
||||
export type EventSubscribeResponse = EventSubscribeResponses[keyof EventSubscribeResponses]
|
||||
|
||||
export type ConfigGetData = {
|
||||
body?: never
|
||||
path?: never
|
||||
@@ -2210,6 +2209,24 @@ export type AuthSetResponses = {
|
||||
|
||||
export type AuthSetResponse = AuthSetResponses[keyof AuthSetResponses]
|
||||
|
||||
export type EventSubscribeData = {
|
||||
body?: never
|
||||
path?: never
|
||||
query?: {
|
||||
directory?: string
|
||||
}
|
||||
url: "/event"
|
||||
}
|
||||
|
||||
export type EventSubscribeResponses = {
|
||||
/**
|
||||
* Event stream
|
||||
*/
|
||||
200: Event
|
||||
}
|
||||
|
||||
export type EventSubscribeResponse = EventSubscribeResponses[keyof EventSubscribeResponses]
|
||||
|
||||
export type ClientOptions = {
|
||||
baseUrl: `${string}://${string}` | (string & {})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user