2025-06-01 02:41:00 +08:00
import { Log } from "../util/log"
import { Bus } from "../bus"
2025-11-08 09:59:02 +08:00
import { describeRoute , generateSpecs , validator , resolver , openAPIRouteHandler } from "hono-openapi"
2025-06-01 02:41:00 +08:00
import { Hono } from "hono"
2025-09-15 15:28:08 +08:00
import { cors } from "hono/cors"
2025-10-24 21:46:28 +08:00
import { stream , streamSSE } from "hono/streaming"
2025-11-05 01:33:03 +08:00
import { proxy } from "hono/proxy"
2025-06-05 05:38:15 +08:00
import { Session } from "../session"
2025-10-27 03:50:41 +08:00
import z from "zod"
2025-06-01 02:41:00 +08:00
import { Provider } from "../provider/provider"
2025-06-20 20:48:42 +08:00
import { mapValues } from "remeda"
2025-11-25 01:56:00 +08:00
import { NamedError } from "@opencode-ai/util/error"
2025-06-13 11:10:03 +08:00
import { ModelsDev } from "../provider/models"
2025-07-01 02:46:18 +08:00
import { Ripgrep } from "../file/ripgrep"
2025-06-19 10:20:03 +08:00
import { Config } from "../config/config"
2025-07-03 05:08:06 +08:00
import { File } from "../file"
import { LSP } from "../lsp"
2025-11-01 23:14:39 +08:00
import { Format } from "../format"
2025-07-08 03:53:43 +08:00
import { MessageV2 } from "../session/message-v2"
2025-11-01 03:07:36 +08:00
import { TuiRoute } from "./tui"
2025-07-31 22:34:43 +08:00
import { Permission } from "../permission"
2025-09-02 05:15:49 +08:00
import { Instance } from "../project/instance"
2025-11-26 11:39:20 +08:00
import { Vcs } from "../project/vcs"
2025-08-08 04:32:12 +08:00
import { Agent } from "../agent/agent"
2025-08-15 04:24:46 +08:00
import { Auth } from "../auth"
2025-08-23 05:04:28 +08:00
import { Command } from "../command"
2025-11-21 13:21:06 +08:00
import { ProviderAuth } from "../provider/auth"
2025-09-02 05:15:49 +08:00
import { Global } from "../global"
import { ProjectRoute } from "./project"
2025-09-09 04:25:04 +08:00
import { ToolRegistry } from "../tool/registry"
import { zodToJsonSchema } from "zod-to-json-schema"
2025-09-13 17:46:14 +08:00
import { SessionPrompt } from "../session/prompt"
import { SessionCompaction } from "../session/compaction"
import { SessionRevert } from "../session/revert"
2025-09-17 13:16:49 +08:00
import { lazy } from "../util/lazy"
2025-10-07 06:51:57 +08:00
import { Todo } from "../session/todo"
2025-09-19 17:11:29 +08:00
import { InstanceBootstrap } from "../project/bootstrap"
2025-10-07 16:04:19 +08:00
import { MCP } from "../mcp"
2025-10-15 23:53:09 +08:00
import { Storage } from "../storage/storage"
import type { ContentfulStatusCode } from "hono/utils/http-status"
2025-11-01 03:07:36 +08:00
import { TuiEvent } from "@/cli/cmd/tui/event"
2025-10-21 05:58:48 +08:00
import { Snapshot } from "@/snapshot"
2025-10-24 04:28:20 +08:00
import { SessionSummary } from "@/session/summary"
2025-11-15 01:32:43 +08:00
import { GlobalBus } from "@/bus/global"
2025-11-17 23:57:18 +08:00
import { SessionStatus } from "@/session/status"
2025-06-10 02:01:11 +08:00
2025-11-20 22:42:24 +08:00
// @ts-ignore This global is needed to prevent ai-sdk from logging warnings to stdout https://github.com/vercel/ai/blob/2dc67e0ef538307f21368db32d5a12345d98831b/packages/ai/src/logger/log-warnings.ts#L85
globalThis . AI_SDK_LOG_WARNINGS = false
2025-06-10 02:01:11 +08:00
const ERRORS = {
400 : {
description : "Bad request" ,
content : {
"application/json" : {
schema : resolver (
z
. object ( {
2025-11-17 14:06:40 +08:00
data : z.any ( ) ,
2025-10-15 23:53:09 +08:00
errors : z.array ( z . record ( z . string ( ) , z . any ( ) ) ) ,
success : z.literal ( false ) ,
2025-06-10 02:01:11 +08:00
} )
2025-09-15 15:12:07 +08:00
. meta ( {
2025-10-15 23:53:09 +08:00
ref : "BadRequestError" ,
2025-06-10 02:01:11 +08:00
} ) ,
) ,
} ,
} ,
} ,
2025-10-15 23:53:09 +08:00
404 : {
description : "Not found" ,
content : {
"application/json" : {
2025-10-19 00:49:29 +08:00
schema : resolver ( Storage . NotFoundError . Schema ) ,
2025-10-15 23:53:09 +08:00
} ,
} ,
} ,
2025-06-10 02:01:11 +08:00
} as const
2025-05-19 02:13:04 +08:00
2025-10-15 23:53:09 +08:00
function errors ( . . . codes : number [ ] ) {
2025-10-24 04:04:58 +08:00
return Object . fromEntries ( codes . map ( ( code ) = > [ code , ERRORS [ code as keyof typeof ERRORS ] ] ) )
2025-10-15 23:53:09 +08:00
}
2025-05-19 02:13:04 +08:00
export namespace Server {
2025-06-01 02:41:00 +08:00
const log = Log . create ( { service : "server" } )
2025-05-19 02:13:04 +08:00
2025-07-29 10:58:12 +08:00
export const Event = {
Connected : Bus.event ( "server.connected" , z . object ( { } ) ) ,
}
2025-09-02 05:15:49 +08:00
const app = new Hono ( )
2025-09-17 13:16:49 +08:00
export const App = lazy ( ( ) = >
app
. onError ( ( err , c ) = > {
log . error ( "failed" , {
error : err ,
} )
if ( err instanceof NamedError ) {
2025-10-15 23:53:09 +08:00
let status : ContentfulStatusCode
2025-10-19 00:49:29 +08:00
if ( err instanceof Storage . NotFoundError ) status = 404
else if ( err instanceof Provider . ModelNotFoundError ) status = 400
else status = 500
2025-10-15 23:53:09 +08:00
return c . json ( err . toObject ( ) , { status } )
2025-09-17 13:16:49 +08:00
}
2025-10-24 04:04:58 +08:00
const message = err instanceof Error && err . stack ? err.stack : err.toString ( )
2025-10-14 03:10:35 +08:00
return c . json ( new NamedError . Unknown ( { message } ) . toObject ( ) , {
2025-10-15 23:53:09 +08:00
status : 500 ,
2025-07-08 03:53:43 +08:00
} )
2025-06-05 08:49:28 +08:00
} )
2025-09-17 13:16:49 +08:00
. use ( async ( c , next ) = > {
const skipLogging = c . req . path === "/log"
if ( ! skipLogging ) {
log . info ( "request" , {
method : c.req.method ,
path : c.req.path ,
} )
}
2025-11-09 09:17:46 +08:00
const timer = log . time ( "request" , {
method : c.req.method ,
path : c.req.path ,
} )
2025-09-17 13:16:49 +08:00
await next ( )
if ( ! skipLogging ) {
2025-11-09 09:17:46 +08:00
timer . stop ( )
2025-09-17 13:16:49 +08:00
}
} )
2025-11-19 01:00:27 +08:00
. use ( cors ( ) )
2025-11-15 01:32:43 +08:00
. get (
"/global/event" ,
describeRoute ( {
description : "Get events" ,
2025-11-15 01:42:32 +08:00
operationId : "global.event" ,
2025-11-15 01:32:43 +08:00
responses : {
200 : {
description : "Event stream" ,
content : {
"text/event-stream" : {
schema : resolver (
2025-11-15 01:35:44 +08:00
z
. object ( {
directory : z.string ( ) ,
payload : Bus.payloads ( ) ,
} )
. meta ( {
ref : "GlobalEvent" ,
} ) ,
2025-11-15 01:32:43 +08:00
) ,
} ,
} ,
} ,
} ,
} ) ,
async ( c ) = > {
log . info ( "global event connected" )
return streamSSE ( c , async ( stream ) = > {
async function handler ( event : any ) {
await stream . writeSSE ( {
data : JSON.stringify ( event ) ,
} )
}
GlobalBus . on ( "event" , handler )
await new Promise < void > ( ( resolve ) = > {
stream . onAbort ( ( ) = > {
GlobalBus . off ( "event" , handler )
resolve ( )
log . info ( "global event disconnected" )
} )
} )
} )
} ,
)
2025-09-17 13:16:49 +08:00
. use ( async ( c , next ) = > {
2025-11-19 01:00:27 +08:00
const directory = c . req . query ( "directory" ) ? ? c . req . header ( "x-opencode-directory" ) ? ? process . cwd ( )
2025-09-19 17:11:29 +08:00
return Instance . provide ( {
directory ,
init : InstanceBootstrap ,
async fn() {
return next ( )
} ,
2025-09-02 05:15:49 +08:00
} )
2025-06-04 01:00:27 +08:00
} )
2025-09-17 13:16:49 +08:00
. get (
"/doc" ,
openAPIRouteHandler ( app , {
documentation : {
info : {
title : "opencode" ,
version : "0.0.3" ,
description : "opencode api" ,
2025-05-29 23:32:55 +08:00
} ,
2025-09-17 13:16:49 +08:00
openapi : "3.1.1" ,
2025-05-29 23:32:55 +08:00
} ,
2025-09-17 13:16:49 +08:00
} ) ,
)
. 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 ) ,
} ,
2025-06-03 23:59:03 +08:00
} ,
} ,
} ,
2025-09-17 13:16:49 +08:00
} ) ,
async ( c ) = > {
return c . json ( await Config . get ( ) )
2025-06-03 23:59:03 +08:00
} ,
2025-09-17 13:16:49 +08:00
)
2025-11-12 10:30:38 +08:00
2025-09-26 14:34:51 +08:00
. patch (
"/config" ,
describeRoute ( {
description : "Update config" ,
operationId : "config.update" ,
responses : {
200 : {
description : "Successfully updated config" ,
content : {
"application/json" : {
schema : resolver ( Config . Info ) ,
} ,
} ,
} ,
2025-10-15 23:53:09 +08:00
. . . errors ( 400 ) ,
2025-09-26 14:34:51 +08:00
} ,
} ) ,
validator ( "json" , Config . Info ) ,
async ( c ) = > {
const config = c . req . valid ( "json" )
await Config . update ( config )
return c . json ( config )
} ,
)
2025-09-17 13:16:49 +08:00
. get (
"/experimental/tool/ids" ,
describeRoute ( {
2025-10-24 04:04:58 +08:00
description : "List all tool IDs (including built-in and dynamically registered)" ,
2025-09-17 13:16:49 +08:00
operationId : "tool.ids" ,
responses : {
200 : {
description : "Tool IDs" ,
content : {
"application/json" : {
2025-10-24 04:04:58 +08:00
schema : resolver ( z . array ( z . string ( ) ) . meta ( { ref : "ToolIDs" } ) ) ,
2025-09-17 13:16:49 +08:00
} ,
2025-09-09 04:25:04 +08:00
} ,
} ,
2025-10-15 23:53:09 +08:00
. . . errors ( 400 ) ,
2025-09-09 04:25:04 +08:00
} ,
2025-09-17 13:16:49 +08:00
} ) ,
async ( c ) = > {
2025-09-18 15:58:21 +08:00
return c . json ( await ToolRegistry . ids ( ) )
2025-09-09 04:25:04 +08:00
} ,
2025-09-17 13:16:49 +08:00
)
. get (
"/experimental/tool" ,
describeRoute ( {
2025-10-24 04:04:58 +08:00
description : "List tools with JSON schema parameters for a provider/model" ,
2025-09-17 13:16:49 +08:00
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" } ) ,
) ,
} ,
2025-09-09 04:25:04 +08:00
} ,
} ,
2025-10-15 23:53:09 +08:00
. . . errors ( 400 ) ,
2025-09-09 04:25:04 +08:00
} ,
} ) ,
2025-09-17 13:16:49 +08:00
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
2025-11-08 09:59:02 +08:00
parameters : ( t . parameters as any ) ? . _def ? zodToJsonSchema ( t . parameters as any ) : t . parameters ,
2025-09-17 13:16:49 +08:00
} ) ) ,
)
} ,
)
2025-11-21 13:21:06 +08:00
. post (
"/instance/dispose" ,
describeRoute ( {
description : "Dispose the current instance" ,
operationId : "instance.dispose" ,
responses : {
200 : {
description : "Instance disposed" ,
content : {
"application/json" : {
schema : resolver ( z . boolean ( ) ) ,
} ,
} ,
} ,
} ,
} ) ,
async ( c ) = > {
await Instance . dispose ( )
return c . json ( true )
} ,
)
2025-09-17 13:16:49 +08:00
. 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" ,
} ) ,
) ,
} ,
2025-06-04 02:24:45 +08:00
} ,
} ,
} ,
2025-09-17 13:16:49 +08:00
} ) ,
async ( c ) = > {
return c . json ( {
state : Global.Path.state ,
config : Global.Path.config ,
worktree : Instance.worktree ,
directory : Instance.directory ,
} )
2025-06-04 02:24:45 +08:00
} ,
2025-09-17 13:16:49 +08:00
)
2025-11-26 11:39:20 +08:00
. get (
"/vcs" ,
describeRoute ( {
description : "Get VCS info for the current instance" ,
operationId : "vcs.get" ,
responses : {
200 : {
description : "VCS info" ,
content : {
"application/json" : {
2025-11-27 01:34:48 +08:00
schema : resolver ( Vcs . Info ) ,
2025-11-26 11:39:20 +08:00
} ,
} ,
} ,
} ,
} ) ,
async ( c ) = > {
const branch = await Vcs . branch ( )
return c . json ( {
2025-11-27 01:34:48 +08:00
branch ,
2025-11-26 11:39:20 +08:00
} )
} ,
)
2025-09-17 13:16:49 +08:00
. get (
"/session" ,
describeRoute ( {
description : "List all sessions" ,
operationId : "session.list" ,
responses : {
200 : {
description : "List of sessions" ,
content : {
"application/json" : {
schema : resolver ( Session . Info . array ( ) ) ,
} ,
2025-06-04 00:38:48 +08:00
} ,
} ,
} ,
2025-09-17 13:16:49 +08:00
} ) ,
async ( c ) = > {
const sessions = await Array . fromAsync ( Session . list ( ) )
sessions . sort ( ( a , b ) = > b . time . updated - a . time . updated )
return c . json ( sessions )
2025-06-04 00:38:48 +08:00
} ,
2025-09-17 13:16:49 +08:00
)
2025-11-17 23:57:18 +08:00
. get (
"/session/status" ,
describeRoute ( {
description : "Get session status" ,
operationId : "session.status" ,
responses : {
200 : {
description : "Get session status" ,
content : {
"application/json" : {
schema : resolver ( z . record ( z . string ( ) , SessionStatus . Info ) ) ,
} ,
} ,
} ,
. . . errors ( 400 ) ,
} ,
} ) ,
async ( c ) = > {
const result = SessionStatus . list ( )
return c . json ( result )
} ,
)
2025-09-17 13:16:49 +08:00
. get (
"/session/:id" ,
describeRoute ( {
description : "Get session" ,
operationId : "session.get" ,
responses : {
200 : {
description : "Get session" ,
content : {
"application/json" : {
schema : resolver ( Session . Info ) ,
} ,
2025-06-02 03:01:57 +08:00
} ,
} ,
2025-10-15 23:53:09 +08:00
. . . errors ( 400 , 404 ) ,
2025-06-02 03:01:57 +08:00
} ,
2025-08-07 08:24:36 +08:00
} ) ,
2025-09-17 13:16:49 +08:00
validator (
"param" ,
z . object ( {
2025-10-07 07:37:30 +08:00
id : Session.get.schema ,
2025-09-17 13:16:49 +08:00
} ) ,
) ,
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 ( ) ) ,
} ,
2025-08-15 21:49:19 +08:00
} ,
} ,
2025-10-15 23:53:09 +08:00
. . . errors ( 400 , 404 ) ,
2025-08-15 21:49:19 +08:00
} ,
2025-09-02 05:15:49 +08:00
} ) ,
2025-09-17 13:16:49 +08:00
validator (
"param" ,
z . object ( {
2025-10-07 07:37:30 +08:00
id : Session.children.schema ,
2025-09-17 13:16:49 +08:00
} ) ,
) ,
async ( c ) = > {
const sessionID = c . req . valid ( "param" ) . id
const session = await Session . children ( sessionID )
return c . json ( session )
} ,
)
2025-10-07 06:51:57 +08:00
. get (
"/session/:id/todo" ,
describeRoute ( {
description : "Get the todo list for a session" ,
operationId : "session.todo" ,
responses : {
200 : {
description : "Todo list" ,
content : {
"application/json" : {
schema : resolver ( Todo . Info . array ( ) ) ,
} ,
} ,
} ,
2025-10-15 23:53:09 +08:00
. . . errors ( 400 , 404 ) ,
2025-10-07 06:51:57 +08:00
} ,
} ) ,
validator (
"param" ,
z . object ( {
id : z.string ( ) . meta ( { description : "Session ID" } ) ,
} ) ,
) ,
async ( c ) = > {
const sessionID = c . req . valid ( "param" ) . id
const todos = await Todo . get ( sessionID )
return c . json ( todos )
} ,
)
2025-09-17 13:16:49 +08:00
. post (
"/session" ,
describeRoute ( {
description : "Create a new session" ,
operationId : "session.create" ,
responses : {
2025-10-15 23:53:09 +08:00
. . . errors ( 400 ) ,
2025-09-17 13:16:49 +08:00
200 : {
description : "Successfully created session" ,
content : {
"application/json" : {
schema : resolver ( Session . Info ) ,
} ,
2025-05-20 23:11:06 +08:00
} ,
} ,
} ,
2025-09-17 13:16:49 +08:00
} ) ,
2025-10-07 07:37:30 +08:00
validator ( "json" , Session . create . schema . optional ( ) ) ,
2025-09-17 13:16:49 +08:00
async ( c ) = > {
const body = c . req . valid ( "json" ) ? ? { }
2025-10-07 07:37:30 +08:00
const session = await Session . create ( body )
2025-09-17 13:16:49 +08:00
return c . json ( session )
2025-05-20 23:11:06 +08:00
} ,
2025-09-17 13:16:49 +08:00
)
. 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 ( ) ) ,
} ,
2025-05-27 06:06:41 +08:00
} ,
} ,
2025-10-15 23:53:09 +08:00
. . . errors ( 400 , 404 ) ,
2025-05-27 06:06:41 +08:00
} ,
2025-09-02 05:15:49 +08:00
} ) ,
2025-09-17 13:16:49 +08:00
validator (
"param" ,
z . object ( {
2025-10-07 07:37:30 +08:00
id : Session.remove.schema ,
2025-09-17 13:16:49 +08:00
} ) ,
) ,
async ( c ) = > {
2025-11-01 03:07:36 +08:00
const sessionID = c . req . valid ( "param" ) . id
await Session . remove ( sessionID )
await Bus . publish ( TuiEvent . CommandExecute , {
command : "session.list" ,
} )
2025-09-17 13:16:49 +08:00
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 ) ,
} ,
2025-08-13 04:22:03 +08:00
} ,
} ,
2025-10-15 23:53:09 +08:00
. . . errors ( 400 , 404 ) ,
2025-08-13 04:22:03 +08:00
} ,
} ) ,
2025-09-17 13:16:49 +08:00
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" )
2025-08-13 04:22:03 +08:00
2025-09-17 13:16:49 +08:00
const updatedSession = await Session . update ( sessionID , ( session ) = > {
if ( updates . title !== undefined ) {
session . title = updates . title
}
} )
2025-08-13 04:22:03 +08:00
2025-09-17 13:16:49 +08:00
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 ( ) ) ,
} ,
2025-06-21 03:22:41 +08:00
} ,
} ,
2025-10-15 23:53:09 +08:00
. . . errors ( 400 , 404 ) ,
2025-06-21 03:22:41 +08:00
} ,
2025-09-02 05:15:49 +08:00
} ) ,
2025-09-17 13:16:49 +08:00
validator (
"param" ,
z . object ( {
id : z.string ( ) . meta ( { description : "Session ID" } ) ,
} ) ,
) ,
2025-10-07 07:37:30 +08:00
validator ( "json" , Session . initialize . schema . omit ( { sessionID : true } ) ) ,
2025-09-17 13:16:49 +08:00
async ( c ) = > {
const sessionID = c . req . valid ( "param" ) . id
const body = c . req . valid ( "json" )
await Session . initialize ( { . . . body , sessionID } )
return c . json ( true )
} ,
)
2025-10-07 06:51:57 +08:00
. post (
"/session/:id/fork" ,
describeRoute ( {
description : "Fork an existing session at a specific message" ,
operationId : "session.fork" ,
responses : {
200 : {
description : "200" ,
content : {
"application/json" : {
schema : resolver ( Session . Info ) ,
} ,
} ,
} ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
2025-10-07 07:37:30 +08:00
id : Session.fork.schema.shape.sessionID ,
2025-10-07 06:51:57 +08:00
} ) ,
) ,
validator ( "json" , Session . fork . schema . omit ( { sessionID : true } ) ) ,
async ( c ) = > {
const sessionID = c . req . valid ( "param" ) . id
const body = c . req . valid ( "json" )
const result = await Session . fork ( { . . . body , sessionID } )
return c . json ( result )
} ,
)
2025-09-17 13:16:49 +08:00
. post (
"/session/:id/abort" ,
describeRoute ( {
description : "Abort a session" ,
operationId : "session.abort" ,
responses : {
200 : {
description : "Aborted session" ,
content : {
"application/json" : {
schema : resolver ( z . boolean ( ) ) ,
} ,
2025-05-28 03:34:46 +08:00
} ,
} ,
2025-10-15 23:53:09 +08:00
. . . errors ( 400 , 404 ) ,
2025-05-28 03:34:46 +08:00
} ,
2025-09-02 05:15:49 +08:00
} ) ,
2025-09-17 13:16:49 +08:00
validator (
"param" ,
z . object ( {
id : z.string ( ) ,
} ) ,
) ,
async ( c ) = > {
2025-11-17 23:57:18 +08:00
SessionPrompt . cancel ( c . req . valid ( "param" ) . id )
return c . json ( true )
2025-09-17 13:16:49 +08:00
} ,
)
. 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 ) ,
} ,
2025-05-29 03:07:51 +08:00
} ,
} ,
2025-10-15 23:53:09 +08:00
. . . errors ( 400 , 404 ) ,
2025-05-29 03:07:51 +08:00
} ,
2025-09-02 05:15:49 +08:00
} ) ,
2025-09-17 13:16:49 +08:00
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 )
} ,
)
2025-10-21 05:58:48 +08:00
. get (
"/session/:id/diff" ,
describeRoute ( {
description : "Get the diff that resulted from this user message" ,
operationId : "session.diff" ,
responses : {
200 : {
description : "Successfully retrieved diff" ,
content : {
"application/json" : {
schema : resolver ( Snapshot . FileDiff . array ( ) ) ,
} ,
} ,
} ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
2025-10-24 04:28:20 +08:00
id : SessionSummary.diff.schema.shape.sessionID ,
2025-10-21 05:58:48 +08:00
} ) ,
) ,
validator (
"query" ,
z . object ( {
2025-10-24 04:28:20 +08:00
messageID : SessionSummary.diff.schema.shape.messageID ,
2025-10-21 05:58:48 +08:00
} ) ,
) ,
async ( c ) = > {
const query = c . req . valid ( "query" )
const params = c . req . valid ( "param" )
2025-10-24 04:28:20 +08:00
const result = await SessionSummary . diff ( {
2025-10-21 05:58:48 +08:00
sessionID : params.id ,
messageID : query.messageID ,
} )
return c . json ( result )
} ,
)
2025-09-17 13:16:49 +08:00
. 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 ) ,
} ,
2025-06-25 00:07:41 +08:00
} ,
} ,
2025-10-15 23:53:09 +08:00
. . . errors ( 400 , 404 ) ,
2025-06-25 00:07:41 +08:00
} ,
2025-09-02 05:15:49 +08:00
} ) ,
2025-09-17 13:16:49 +08:00
validator (
"param" ,
z . object ( {
2025-10-07 07:37:30 +08:00
id : Session.unshare.schema ,
2025-09-17 13:16:49 +08:00
} ) ,
) ,
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 ( ) ) ,
} ,
2025-05-30 01:17:56 +08:00
} ,
} ,
2025-10-15 23:53:09 +08:00
. . . errors ( 400 , 404 ) ,
2025-05-30 01:17:56 +08:00
} ,
2025-09-02 05:15:49 +08:00
} ) ,
2025-09-17 13:16:49 +08:00
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" )
2025-11-21 16:13:10 +08:00
const msgs = await Session . messages ( { sessionID : id } )
let currentAgent = "build"
for ( let i = msgs . length - 1 ; i >= 0 ; i -- ) {
const info = msgs [ i ] . info
if ( info . role === "user" ) {
currentAgent = info . agent || "build"
break
}
}
2025-11-17 23:57:18 +08:00
await SessionCompaction . create ( {
sessionID : id ,
2025-11-21 16:13:10 +08:00
agent : currentAgent ,
2025-11-17 23:57:18 +08:00
model : {
providerID : body.providerID ,
modelID : body.modelID ,
} ,
2025-11-26 02:10:56 +08:00
auto : false ,
2025-11-17 23:57:18 +08:00
} )
await SessionPrompt . loop ( id )
2025-09-17 13:16:49 +08:00
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 ( ) ) ,
} ,
2025-07-31 22:34:43 +08:00
} ,
} ,
2025-10-15 23:53:09 +08:00
. . . errors ( 400 , 404 ) ,
2025-07-31 22:34:43 +08:00
} ,
2025-09-02 05:15:49 +08:00
} ) ,
2025-09-17 13:16:49 +08:00
validator (
"param" ,
z . object ( {
id : z.string ( ) . meta ( { description : "Session ID" } ) ,
} ) ,
) ,
2025-11-07 02:20:13 +08:00
validator (
"query" ,
z . object ( {
2025-11-08 02:03:54 +08:00
limit : z.coerce.number ( ) . optional ( ) ,
2025-11-07 02:20:13 +08:00
} ) ,
) ,
2025-09-17 13:16:49 +08:00
async ( c ) = > {
2025-11-07 02:20:13 +08:00
const query = c . req . valid ( "query" )
const messages = await Session . messages ( {
sessionID : c.req.valid ( "param" ) . id ,
limit : query.limit ,
} )
return c . json ( messages )
2025-09-17 13:16:49 +08:00
} ,
)
2025-11-05 02:32:56 +08:00
. get (
"/session/:id/diff" ,
describeRoute ( {
description : "Get the diff for this session" ,
operationId : "session.diff" ,
responses : {
200 : {
description : "List of diffs" ,
content : {
"application/json" : {
schema : resolver ( Snapshot . FileDiff . array ( ) ) ,
} ,
} ,
} ,
. . . errors ( 400 , 404 ) ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
id : z.string ( ) . meta ( { description : "Session ID" } ) ,
} ) ,
) ,
async ( c ) = > {
const diff = await Session . diff ( c . req . valid ( "param" ) . id )
return c . json ( diff )
} ,
)
2025-09-17 13:16:49 +08:00
. 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 ( ) ,
} ) ,
) ,
} ,
2025-05-29 03:07:51 +08:00
} ,
} ,
2025-10-15 23:53:09 +08:00
. . . errors ( 400 , 404 ) ,
2025-05-29 03:07:51 +08:00
} ,
2025-09-02 05:15:49 +08:00
} ) ,
2025-09-17 13:16:49 +08:00
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" )
2025-11-08 03:46:58 +08:00
const message = await MessageV2 . get ( {
2025-10-21 05:58:48 +08:00
sessionID : params.id ,
messageID : params.messageID ,
} )
2025-09-17 13:16:49 +08:00
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 ( ) ,
} ) ,
) ,
} ,
2025-08-23 05:04:28 +08:00
} ,
} ,
2025-10-15 23:53:09 +08:00
. . . errors ( 400 , 404 ) ,
2025-08-23 05:04:28 +08:00
} ,
2025-09-02 05:15:49 +08:00
} ) ,
2025-09-17 13:16:49 +08:00
validator (
"param" ,
z . object ( {
id : z.string ( ) . meta ( { description : "Session ID" } ) ,
} ) ,
) ,
validator ( "json" , SessionPrompt . PromptInput . omit ( { sessionID : true } ) ) ,
async ( c ) = > {
2025-10-24 21:46:28 +08:00
c . status ( 200 )
c . header ( "Content-Type" , "application/json" )
return stream ( c , async ( stream ) = > {
const sessionID = c . req . valid ( "param" ) . id
const body = c . req . valid ( "json" )
const msg = await SessionPrompt . prompt ( { . . . body , sessionID } )
stream . write ( JSON . stringify ( msg ) )
} )
2025-09-17 13:16:49 +08:00
} ,
)
2025-11-25 13:52:57 +08:00
. post (
"/session/:id/prompt_async" ,
describeRoute ( {
description : "Create and send a new message to a session, start if needed and return immediately" ,
operationId : "session.prompt_async" ,
responses : {
204 : {
description : "Prompt accepted" ,
} ,
. . . errors ( 400 , 404 ) ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
id : z.string ( ) . meta ( { description : "Session ID" } ) ,
} ) ,
) ,
validator ( "json" , SessionPrompt . PromptInput . omit ( { sessionID : true } ) ) ,
async ( c ) = > {
c . status ( 204 )
c . header ( "Content-Type" , "application/json" )
return stream ( c , async ( stream ) = > {
const sessionID = c . req . valid ( "param" ) . id
const body = c . req . valid ( "json" )
SessionPrompt . prompt ( { . . . body , sessionID } )
} )
} ,
)
2025-09-17 13:16:49 +08:00
. 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 ( ) ,
} ) ,
) ,
} ,
2025-08-14 01:28:51 +08:00
} ,
} ,
2025-10-15 23:53:09 +08:00
. . . errors ( 400 , 404 ) ,
2025-08-14 01:28:51 +08:00
} ,
2025-09-02 05:15:49 +08:00
} ) ,
2025-09-17 13:16:49 +08:00
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 ) ,
} ,
2025-07-24 08:30:46 +08:00
} ,
} ,
2025-10-15 23:53:09 +08:00
. . . errors ( 400 , 404 ) ,
2025-07-24 08:30:46 +08:00
} ,
2025-09-02 05:15:49 +08:00
} ) ,
2025-09-17 13:16:49 +08:00
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 ) ,
} ,
2025-07-24 08:30:46 +08:00
} ,
} ,
2025-10-15 23:53:09 +08:00
. . . errors ( 400 , 404 ) ,
2025-07-24 08:30:46 +08:00
} ,
2025-09-02 05:15:49 +08:00
} ) ,
2025-09-17 13:16:49 +08:00
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" ) )
2025-10-21 05:58:48 +08:00
const session = await SessionRevert . revert ( {
sessionID : id ,
. . . c . req . valid ( "json" ) ,
} )
2025-09-17 13:16:49 +08:00
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 ) ,
} ,
2025-07-31 22:34:43 +08:00
} ,
} ,
2025-10-15 23:53:09 +08:00
. . . errors ( 400 , 404 ) ,
2025-07-31 22:34:43 +08:00
} ,
2025-09-02 05:15:49 +08:00
} ) ,
2025-09-17 13:16:49 +08:00
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 ( ) ) ,
} ,
2025-08-23 05:04:28 +08:00
} ,
} ,
2025-10-15 23:53:09 +08:00
. . . errors ( 400 , 404 ) ,
2025-08-23 05:04:28 +08:00
} ,
2025-09-02 05:15:49 +08:00
} ) ,
2025-09-17 13:16:49 +08:00
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
2025-10-21 05:58:48 +08:00
Permission . respond ( {
sessionID : id ,
permissionID ,
response : c.req.valid ( "json" ) . response ,
} )
2025-09-17 13:16:49 +08:00
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 ( ) ) ,
} ,
2025-05-29 00:53:22 +08:00
} ,
} ,
} ,
2025-09-17 13:16:49 +08:00
} ) ,
async ( c ) = > {
const commands = await Command . list ( )
return c . json ( commands )
2025-05-29 00:53:22 +08:00
} ,
2025-09-17 13:16:49 +08:00
)
. 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 ( ) ) ,
} ) ,
) ,
} ,
2025-07-03 05:08:06 +08:00
} ,
} ,
} ,
2025-09-17 13:16:49 +08:00
} ) ,
async ( c ) = > {
2025-11-09 09:17:46 +08:00
using _ = log . time ( "providers" )
2025-10-24 04:04:58 +08:00
const providers = await Provider . list ( ) . then ( ( x ) = > mapValues ( x , ( item ) = > item . info ) )
2025-09-17 13:16:49 +08:00
return c . json ( {
providers : Object.values ( providers ) ,
2025-11-08 09:59:02 +08:00
default : mapValues ( providers , ( item ) = > Provider . sort ( Object . values ( item . models ) ) [ 0 ] . id ) ,
2025-09-17 13:16:49 +08:00
} )
2025-07-03 05:08:06 +08:00
} ,
2025-09-17 13:16:49 +08:00
)
2025-11-21 13:21:06 +08:00
. get (
"/provider" ,
describeRoute ( {
description : "List all providers" ,
operationId : "provider.list" ,
responses : {
200 : {
description : "List of providers" ,
content : {
"application/json" : {
schema : resolver (
z . object ( {
all : ModelsDev.Provider.array ( ) ,
default : z . record ( z . string ( ) , z . string ( ) ) ,
connected : z.array ( z . string ( ) ) ,
} ) ,
) ,
} ,
} ,
} ,
} ,
} ) ,
async ( c ) = > {
const providers = await ModelsDev . get ( )
const connected = await Provider . list ( ) . then ( ( x ) = > Object . keys ( x ) )
return c . json ( {
all : Object.values ( providers ) ,
default : mapValues ( providers , ( item ) = > Provider . sort ( Object . values ( item . models ) ) [ 0 ] . id ) ,
connected ,
} )
} ,
)
. get (
"/provider/auth" ,
describeRoute ( {
description : "Get provider authentication methods" ,
operationId : "provider.auth" ,
responses : {
200 : {
description : "Provider auth methods" ,
content : {
"application/json" : {
schema : resolver ( z . record ( z . string ( ) , z . array ( ProviderAuth . Method ) ) ) ,
} ,
} ,
} ,
} ,
} ) ,
async ( c ) = > {
return c . json ( await ProviderAuth . methods ( ) )
} ,
)
. post (
"/provider/:id/oauth/authorize" ,
describeRoute ( {
description : "Authorize a provider using OAuth" ,
operationId : "provider.oauth.authorize" ,
responses : {
200 : {
description : "Authorization URL and method" ,
content : {
"application/json" : {
schema : resolver ( ProviderAuth . Authorization . optional ( ) ) ,
} ,
} ,
} ,
. . . errors ( 400 ) ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
id : z.string ( ) . meta ( { description : "Provider ID" } ) ,
} ) ,
) ,
validator (
"json" ,
z . object ( {
method : z.number ( ) . meta ( { description : "Auth method index" } ) ,
} ) ,
) ,
async ( c ) = > {
const id = c . req . valid ( "param" ) . id
const { method } = c . req . valid ( "json" )
const result = await ProviderAuth . authorize ( {
providerID : id ,
method ,
} )
return c . json ( result )
} ,
)
. post (
"/provider/:id/oauth/callback" ,
describeRoute ( {
description : "Handle OAuth callback for a provider" ,
operationId : "provider.oauth.callback" ,
responses : {
200 : {
description : "OAuth callback processed successfully" ,
content : {
"application/json" : {
schema : resolver ( z . boolean ( ) ) ,
} ,
} ,
} ,
. . . errors ( 400 ) ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
id : z.string ( ) . meta ( { description : "Provider ID" } ) ,
} ) ,
) ,
validator (
"json" ,
z . object ( {
method : z.number ( ) . meta ( { description : "Auth method index" } ) ,
code : z.string ( ) . optional ( ) . meta ( { description : "OAuth authorization code" } ) ,
} ) ,
) ,
async ( c ) = > {
const id = c . req . valid ( "param" ) . id
const { method , code } = c . req . valid ( "json" )
await ProviderAuth . callback ( {
providerID : id ,
method ,
code ,
} )
return c . json ( true )
} ,
)
2025-09-17 13:16:49 +08:00
. 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 ( ) ) ,
} ,
2025-06-12 11:59:51 +08:00
} ,
} ,
} ,
2025-09-02 05:15:49 +08:00
} ) ,
2025-09-17 13:16:49 +08:00
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 ( ) ) ,
} ,
2025-07-03 05:08:06 +08:00
} ,
} ,
} ,
2025-09-02 05:15:49 +08:00
} ) ,
2025-09-17 13:16:49 +08:00
validator (
"query" ,
z . object ( {
query : z.string ( ) ,
2025-11-15 07:48:23 +08:00
dirs : z.enum ( [ "true" , "false" ] ) . optional ( ) ,
2025-09-17 13:16:49 +08:00
} ) ,
) ,
async ( c ) = > {
const query = c . req . valid ( "query" ) . query
2025-11-04 00:53:38 +08:00
const dirs = c . req . valid ( "query" ) . dirs
2025-10-01 15:37:01 +08:00
const results = await File . search ( {
2025-09-17 13:16:49 +08:00
query ,
limit : 10 ,
2025-11-04 05:35:46 +08:00
dirs : dirs !== "false" ,
2025-09-17 13:16:49 +08:00
} )
2025-10-01 15:37:01 +08:00
return c . json ( results )
2025-09-17 13:16:49 +08:00
} ,
)
. get (
"/find/symbol" ,
describeRoute ( {
description : "Find workspace symbols" ,
operationId : "find.symbols" ,
responses : {
200 : {
description : "Symbols" ,
content : {
"application/json" : {
schema : resolver ( LSP . Symbol . array ( ) ) ,
} ,
2025-08-28 04:27:49 +08:00
} ,
} ,
} ,
2025-09-02 05:15:49 +08:00
} ) ,
2025-09-17 13:16:49 +08:00
validator (
"query" ,
z . object ( {
query : z.string ( ) ,
} ) ,
) ,
async ( c ) = > {
2025-10-14 02:05:54 +08:00
/ *
2025-09-17 13:16:49 +08:00
const query = c . req . valid ( "query" ) . query
const result = await LSP . workspaceSymbol ( query )
return c . json ( result )
2025-10-14 02:05:54 +08:00
* /
return c . json ( [ ] )
2025-09-17 13:16:49 +08:00
} ,
)
. 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 ( ) ) ,
} ,
2025-07-03 05:08:06 +08:00
} ,
} ,
} ,
2025-09-02 05:15:49 +08:00
} ) ,
2025-09-17 13:16:49 +08:00
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 ) ,
} ,
2025-07-03 05:08:06 +08:00
} ,
} ,
} ,
} ) ,
2025-09-17 13:16:49 +08:00
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 ( ) ) ,
} ,
2025-09-02 05:15:49 +08:00
} ,
} ,
} ,
2025-09-17 13:16:49 +08:00
} ) ,
async ( c ) = > {
const content = await File . status ( )
return c . json ( content )
2025-07-03 05:08:06 +08:00
} ,
2025-09-17 13:16:49 +08:00
)
. 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 ( ) ) ,
} ,
2025-07-09 21:16:10 +08:00
} ,
} ,
2025-10-15 23:53:09 +08:00
. . . errors ( 400 ) ,
2025-07-09 21:16:10 +08:00
} ,
} ) ,
2025-09-17 13:16:49 +08:00
validator (
"json" ,
z . object ( {
2025-10-24 04:04:58 +08:00
service : z.string ( ) . meta ( { description : "Service name for the log entry" } ) ,
level : z.enum ( [ "debug" , "info" , "error" , "warn" ] ) . meta ( { description : "Log level" } ) ,
2025-09-17 13:16:49 +08:00
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 } )
2025-07-09 21:16:10 +08:00
2025-09-17 13:16:49 +08:00
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
}
2025-07-09 21:16:10 +08:00
2025-09-17 13:16:49 +08:00
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 ( ) ) ,
} ,
2025-07-10 03:44:59 +08:00
} ,
} ,
} ,
2025-09-17 13:16:49 +08:00
} ) ,
async ( c ) = > {
const modes = await Agent . list ( )
return c . json ( modes )
2025-07-10 03:44:59 +08:00
} ,
2025-09-17 13:16:49 +08:00
)
2025-10-07 16:04:19 +08:00
. get (
"/mcp" ,
describeRoute ( {
description : "Get MCP server status" ,
operationId : "mcp.status" ,
responses : {
200 : {
description : "MCP server status" ,
content : {
"application/json" : {
2025-11-01 03:07:36 +08:00
schema : resolver ( z . record ( z . string ( ) , MCP . Status ) ) ,
2025-10-07 16:04:19 +08:00
} ,
} ,
} ,
} ,
} ) ,
async ( c ) = > {
return c . json ( await MCP . status ( ) )
} ,
)
2025-11-08 03:57:12 +08:00
. post (
"/mcp" ,
describeRoute ( {
description : "Add MCP server dynamically" ,
operationId : "mcp.add" ,
responses : {
200 : {
description : "MCP server added successfully" ,
content : {
"application/json" : {
schema : resolver ( z . record ( z . string ( ) , MCP . Status ) ) ,
} ,
} ,
} ,
. . . errors ( 400 ) ,
} ,
} ) ,
validator (
"json" ,
z . object ( {
name : z.string ( ) ,
config : Config.Mcp ,
} ) ,
) ,
async ( c ) = > {
const { name , config } = c . req . valid ( "json" )
const result = await MCP . add ( name , config )
return c . json ( result . status )
} ,
)
2025-11-01 03:07:36 +08:00
. get (
"/lsp" ,
describeRoute ( {
description : "Get LSP server status" ,
operationId : "lsp.status" ,
responses : {
200 : {
description : "LSP server status" ,
content : {
"application/json" : {
schema : resolver ( LSP . Status . array ( ) ) ,
} ,
} ,
} ,
} ,
} ) ,
async ( c ) = > {
return c . json ( await LSP . status ( ) )
} ,
)
2025-11-01 23:14:39 +08:00
. get (
"/formatter" ,
describeRoute ( {
description : "Get formatter status" ,
operationId : "formatter.status" ,
responses : {
200 : {
description : "Formatter status" ,
content : {
"application/json" : {
schema : resolver ( Format . Status . array ( ) ) ,
} ,
} ,
} ,
} ,
} ) ,
async ( c ) = > {
return c . json ( await Format . status ( ) )
} ,
)
2025-09-17 13:16:49 +08:00
. 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 ( ) ) ,
} ,
2025-07-22 07:53:22 +08:00
} ,
} ,
2025-10-15 23:53:09 +08:00
. . . errors ( 400 ) ,
2025-07-22 07:53:22 +08:00
} ,
} ) ,
2025-11-01 03:07:36 +08:00
validator ( "json" , TuiEvent . PromptAppend . properties ) ,
async ( c ) = > {
await Bus . publish ( TuiEvent . PromptAppend , c . req . valid ( "json" ) )
return c . json ( true )
} ,
2025-09-17 13:16:49 +08:00
)
. 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 ( ) ) ,
} ,
2025-07-22 07:53:22 +08:00
} ,
} ,
} ,
2025-09-17 13:16:49 +08:00
} ) ,
2025-11-01 03:07:36 +08:00
async ( c ) = > {
// TODO: open dialog
return c . json ( true )
} ,
2025-09-17 13:16:49 +08:00
)
. 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 ( ) ) ,
} ,
2025-08-01 00:24:23 +08:00
} ,
} ,
} ,
2025-09-17 13:16:49 +08:00
} ) ,
2025-11-01 03:07:36 +08:00
async ( c ) = > {
await Bus . publish ( TuiEvent . CommandExecute , {
command : "session.list" ,
} )
return c . json ( true )
} ,
2025-09-17 13:16:49 +08:00
)
. 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 ( ) ) ,
} ,
2025-08-01 00:24:23 +08:00
} ,
} ,
} ,
2025-09-17 13:16:49 +08:00
} ) ,
2025-11-01 03:07:36 +08:00
async ( c ) = > {
await Bus . publish ( TuiEvent . CommandExecute , {
command : "session.list" ,
} )
return c . json ( true )
} ,
2025-09-17 13:16:49 +08:00
)
. 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 ( ) ) ,
} ,
2025-08-01 00:24:23 +08:00
} ,
} ,
} ,
2025-09-17 13:16:49 +08:00
} ) ,
2025-11-01 03:07:36 +08:00
async ( c ) = > {
await Bus . publish ( TuiEvent . CommandExecute , {
command : "model.list" ,
} )
return c . json ( true )
} ,
2025-09-17 13:16:49 +08:00
)
. 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 ( ) ) ,
} ,
2025-08-01 00:24:23 +08:00
} ,
} ,
} ,
2025-09-17 13:16:49 +08:00
} ) ,
2025-11-01 03:07:36 +08:00
async ( c ) = > {
await Bus . publish ( TuiEvent . CommandExecute , {
command : "prompt.submit" ,
} )
return c . json ( true )
} ,
2025-09-17 13:16:49 +08:00
)
. 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 ( ) ) ,
} ,
2025-08-01 00:24:23 +08:00
} ,
} ,
} ,
2025-09-17 13:16:49 +08:00
} ) ,
2025-11-01 03:07:36 +08:00
async ( c ) = > {
await Bus . publish ( TuiEvent . CommandExecute , {
command : "prompt.clear" ,
} )
return c . json ( true )
} ,
2025-09-17 13:16:49 +08:00
)
. 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 ( ) ) ,
} ,
2025-08-01 00:24:23 +08:00
} ,
} ,
2025-10-15 23:53:09 +08:00
. . . errors ( 400 ) ,
2025-08-01 00:24:23 +08:00
} ,
} ) ,
2025-11-01 03:07:36 +08:00
validator ( "json" , z . object ( { command : z.string ( ) } ) ) ,
async ( c ) = > {
const command = c . req . valid ( "json" ) . command
await Bus . publish ( TuiEvent . CommandExecute , {
// @ts-expect-error
command : {
session_new : "session.new" ,
session_share : "session.share" ,
session_interrupt : "session.interrupt" ,
session_compact : "session.compact" ,
messages_page_up : "session.page.up" ,
messages_page_down : "session.page.down" ,
messages_half_page_up : "session.half.page.up" ,
messages_half_page_down : "session.half.page.down" ,
messages_first : "session.first" ,
messages_last : "session.last" ,
agent_cycle : "agent.cycle" ,
} [ command ] ,
} )
return c . json ( true )
} ,
2025-09-17 13:16:49 +08:00
)
. 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 ( ) ) ,
} ,
2025-08-15 21:39:58 +08:00
} ,
} ,
} ,
} ) ,
2025-11-01 03:07:36 +08:00
validator ( "json" , TuiEvent . ToastShow . properties ) ,
async ( c ) = > {
await Bus . publish ( TuiEvent . ToastShow , c . req . valid ( "json" ) )
return c . json ( true )
} ,
)
. post (
"/tui/publish" ,
describeRoute ( {
description : "Publish a TUI event" ,
operationId : "tui.publish" ,
responses : {
200 : {
description : "Event published successfully" ,
content : {
"application/json" : {
schema : resolver ( z . boolean ( ) ) ,
} ,
} ,
} ,
. . . errors ( 400 ) ,
} ,
} ) ,
2025-09-17 13:16:49 +08:00
validator (
"json" ,
2025-11-01 03:07:36 +08:00
z . union (
Object . values ( TuiEvent ) . map ( ( def ) = > {
return z
. object ( {
type : z . literal ( def . type ) ,
properties : def.properties ,
} )
. meta ( {
ref : "Event" + "." + def . type ,
} )
} ) ,
) ,
2025-09-17 13:16:49 +08:00
) ,
2025-11-01 03:07:36 +08:00
async ( c ) = > {
const evt = c . req . valid ( "json" )
2025-11-08 09:59:02 +08:00
await Bus . publish ( Object . values ( TuiEvent ) . find ( ( def ) = > def . type === evt . type ) ! , evt . properties )
2025-11-01 03:07:36 +08:00
return c . json ( true )
} ,
2025-09-17 13:16:49 +08:00
)
. 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 ( ) ) ,
} ,
2025-08-15 04:24:46 +08:00
} ,
} ,
2025-10-15 23:53:09 +08:00
. . . errors ( 400 ) ,
2025-08-15 04:24:46 +08:00
} ,
2025-09-17 13:16:49 +08:00
} ) ,
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 )
2025-08-15 04:24:46 +08:00
} ,
2025-09-17 13:16:49 +08:00
)
. get (
"/event" ,
describeRoute ( {
description : "Get events" ,
operationId : "event.subscribe" ,
responses : {
200 : {
description : "Event stream" ,
content : {
"text/event-stream" : {
2025-11-15 01:35:44 +08:00
schema : resolver ( Bus . payloads ( ) ) ,
2025-09-17 13:16:49 +08:00
} ,
} ,
} ,
} ,
2025-09-02 05:15:49 +08:00
} ) ,
2025-09-17 13:16:49 +08:00
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 ) ,
} )
2025-12-01 22:13:53 +08:00
if ( event . type === Bus . InstanceDisposed . type ) {
stream . close ( )
}
2025-09-17 13:16:49 +08:00
} )
await new Promise < void > ( ( resolve ) = > {
stream . onAbort ( ( ) = > {
unsub ( )
resolve ( )
log . info ( "event disconnected" )
} )
} )
} )
} ,
2025-11-05 01:33:03 +08:00
)
. all ( "/*" , async ( c ) = > {
return proxy ( ` https://desktop.dev.opencode.ai ${ c . req . path } ` , {
. . . c . req ,
headers : {
host : "desktop.dev.opencode.ai" ,
} ,
} )
} ) ,
2025-09-17 13:16:49 +08:00
)
2025-05-19 10:30:41 +08:00
export async function openapi() {
2025-09-17 13:16:49 +08:00
const result = await generateSpecs ( App ( ) , {
2025-05-19 10:30:41 +08:00
documentation : {
info : {
title : "opencode" ,
version : "1.0.0" ,
description : "opencode api" ,
} ,
2025-08-15 00:10:32 +08:00
openapi : "3.1.1" ,
2025-05-19 10:30:41 +08:00
} ,
2025-06-01 02:41:00 +08:00
} )
return result
2025-05-19 02:28:08 +08:00
}
2025-05-19 02:13:04 +08:00
2025-06-25 08:52:09 +08:00
export function listen ( opts : { port : number ; hostname : string } ) {
2025-05-19 02:28:08 +08:00
const server = Bun . serve ( {
2025-06-25 08:52:09 +08:00
port : opts.port ,
hostname : opts.hostname ,
2025-05-19 02:13:04 +08:00
idleTimeout : 0 ,
2025-09-17 13:16:49 +08:00
fetch : App ( ) . fetch ,
2025-06-01 02:41:00 +08:00
} )
return server
2025-05-18 09:31:42 +08:00
}
}