2026-01-16 21:21:13 +00:00
import { Hono } from "hono"
import { stream } from "hono/streaming"
import { describeRoute , validator , resolver } from "hono-openapi"
2026-03-11 23:40:50 +00:00
import { SessionID , MessageID , PartID } from "@/session/schema"
2026-01-16 21:21:13 +00:00
import z from "zod"
import { Session } from "../../session"
import { MessageV2 } from "../../session/message-v2"
import { SessionPrompt } from "../../session/prompt"
2026-04-09 20:03:40 +00:00
import { SessionRunState } from "@/session/run-state"
2026-01-16 21:21:13 +00:00
import { SessionCompaction } from "../../session/compaction"
import { SessionRevert } from "../../session/revert"
2026-04-16 03:28:46 +00:00
import { SessionShare } from "@/share"
2026-01-16 21:21:13 +00:00
import { SessionStatus } from "@/session/status"
import { SessionSummary } from "@/session/summary"
import { Todo } from "../../session/todo"
2026-04-12 00:01:52 +00:00
import { Effect } from "effect"
2026-04-11 01:47:28 +00:00
import { AppRuntime } from "../../effect/app-runtime"
2026-01-16 21:21:13 +00:00
import { Agent } from "../../agent/agent"
2026-03-21 04:51:35 +00:00
import { Snapshot } from "@/snapshot"
2026-04-09 20:28:42 +00:00
import { Command } from "../../command"
2026-04-16 03:15:58 +00:00
import { Log } from "../../util"
2026-03-21 04:51:35 +00:00
import { Permission } from "@/permission"
2026-03-12 01:49:57 +00:00
import { PermissionID } from "@/permission/schema"
2026-03-12 13:27:52 +00:00
import { ModelID , ProviderID } from "@/provider/schema"
2026-01-16 21:21:13 +00:00
import { errors } from "../error"
import { lazy } from "../../util/lazy"
2026-03-24 17:50:55 +00:00
import { Bus } from "../../bus"
2026-04-15 14:26:20 +00:00
import { NamedError } from "@opencode-ai/shared/util/error"
2026-04-15 21:32:56 +00:00
import { jsonRequest } from "./trace"
2026-01-16 21:21:13 +00:00
const log = Log . create ( { service : "server" } )
export const SessionRoutes = lazy ( ( ) = >
new Hono ( )
. get (
"/" ,
describeRoute ( {
summary : "List sessions" ,
description : "Get a list of all OpenCode sessions, sorted by most recently updated." ,
operationId : "session.list" ,
responses : {
200 : {
description : "List of sessions" ,
content : {
"application/json" : {
schema : resolver ( Session . Info . array ( ) ) ,
} ,
} ,
} ,
} ,
} ) ,
validator (
"query" ,
z . object ( {
directory : z.string ( ) . optional ( ) . meta ( { description : "Filter sessions by project directory" } ) ,
roots : z.coerce.boolean ( ) . optional ( ) . meta ( { description : "Only return root sessions (no parentID)" } ) ,
start : z.coerce
. number ( )
. optional ( )
. meta ( { description : "Filter sessions updated on or after this timestamp (milliseconds since epoch)" } ) ,
search : z.string ( ) . optional ( ) . meta ( { description : "Filter sessions by title (case-insensitive)" } ) ,
limit : z.coerce.number ( ) . optional ( ) . meta ( { description : "Maximum number of sessions to return" } ) ,
} ) ,
) ,
async ( c ) = > {
const query = c . req . valid ( "query" )
const sessions : Session.Info [ ] = [ ]
2026-02-14 18:40:49 +00:00
for await ( const session of Session . list ( {
directory : query.directory ,
roots : query.roots ,
start : query.start ,
search : query.search ,
limit : query.limit ,
} ) ) {
2026-01-16 21:21:13 +00:00
sessions . push ( session )
}
return c . json ( sessions )
} ,
)
. get (
"/status" ,
describeRoute ( {
summary : "Get session status" ,
description : "Retrieve the current status of all sessions, including active, idle, and completed states." ,
operationId : "session.status" ,
responses : {
200 : {
description : "Get session status" ,
content : {
"application/json" : {
schema : resolver ( z . record ( z . string ( ) , SessionStatus . Info ) ) ,
} ,
} ,
} ,
. . . errors ( 400 ) ,
} ,
} ) ,
2026-04-15 21:32:56 +00:00
async ( c ) = >
jsonRequest ( "SessionRoutes.status" , c , function * ( ) {
const svc = yield * SessionStatus . Service
return Object . fromEntries ( yield * svc . list ( ) )
} ) ,
2026-01-16 21:21:13 +00:00
)
. get (
"/:sessionID" ,
describeRoute ( {
summary : "Get session" ,
description : "Retrieve detailed information about a specific OpenCode session." ,
tags : [ "Session" ] ,
operationId : "session.get" ,
responses : {
200 : {
description : "Get session" ,
content : {
"application/json" : {
schema : resolver ( Session . Info ) ,
} ,
} ,
} ,
. . . errors ( 400 , 404 ) ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
2026-04-14 17:45:13 +00:00
sessionID : Session.GetInput ,
2026-01-16 21:21:13 +00:00
} ) ,
) ,
async ( c ) = > {
const sessionID = c . req . valid ( "param" ) . sessionID
2026-04-15 21:32:56 +00:00
return jsonRequest ( "SessionRoutes.get" , c , function * ( ) {
const session = yield * Session . Service
return yield * session . get ( sessionID )
} )
2026-01-16 21:21:13 +00:00
} ,
)
. get (
"/:sessionID/children" ,
describeRoute ( {
summary : "Get session children" ,
tags : [ "Session" ] ,
description : "Retrieve all child sessions that were forked from the specified parent session." ,
operationId : "session.children" ,
responses : {
200 : {
description : "List of children" ,
content : {
"application/json" : {
schema : resolver ( Session . Info . array ( ) ) ,
} ,
} ,
} ,
. . . errors ( 400 , 404 ) ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
2026-04-14 17:45:13 +00:00
sessionID : Session.ChildrenInput ,
2026-01-16 21:21:13 +00:00
} ) ,
) ,
async ( c ) = > {
const sessionID = c . req . valid ( "param" ) . sessionID
2026-04-15 21:32:56 +00:00
return jsonRequest ( "SessionRoutes.children" , c , function * ( ) {
const session = yield * Session . Service
return yield * session . children ( sessionID )
} )
2026-01-16 21:21:13 +00:00
} ,
)
. get (
"/:sessionID/todo" ,
describeRoute ( {
summary : "Get session todos" ,
description : "Retrieve the todo list associated with a specific session, showing tasks and action items." ,
operationId : "session.todo" ,
responses : {
200 : {
description : "Todo list" ,
content : {
"application/json" : {
schema : resolver ( Todo . Info . array ( ) ) ,
} ,
} ,
} ,
. . . errors ( 400 , 404 ) ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
2026-03-11 23:16:56 +00:00
sessionID : SessionID.zod ,
2026-01-16 21:21:13 +00:00
} ) ,
) ,
async ( c ) = > {
const sessionID = c . req . valid ( "param" ) . sessionID
2026-04-15 21:32:56 +00:00
return jsonRequest ( "SessionRoutes.todo" , c , function * ( ) {
const todo = yield * Todo . Service
return yield * todo . get ( sessionID )
} )
2026-01-16 21:21:13 +00:00
} ,
)
. post (
"/" ,
describeRoute ( {
summary : "Create session" ,
description : "Create a new OpenCode session for interacting with AI assistants and managing conversations." ,
operationId : "session.create" ,
responses : {
. . . errors ( 400 ) ,
200 : {
description : "Successfully created session" ,
content : {
"application/json" : {
schema : resolver ( Session . Info ) ,
} ,
} ,
} ,
} ,
} ) ,
2026-04-14 17:45:13 +00:00
validator ( "json" , Session . CreateInput ) ,
2026-01-16 21:21:13 +00:00
async ( c ) = > {
const body = c . req . valid ( "json" ) ? ? { }
2026-04-14 16:38:11 +00:00
const session = await AppRuntime . runPromise ( SessionShare . Service . use ( ( svc ) = > svc . create ( body ) ) )
2026-01-16 21:21:13 +00:00
return c . json ( session )
} ,
)
. delete (
"/:sessionID" ,
describeRoute ( {
summary : "Delete session" ,
description : "Delete a session and permanently remove all associated data, including messages and history." ,
operationId : "session.delete" ,
responses : {
200 : {
description : "Successfully deleted session" ,
content : {
"application/json" : {
schema : resolver ( z . boolean ( ) ) ,
} ,
} ,
} ,
. . . errors ( 400 , 404 ) ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
2026-04-14 17:45:13 +00:00
sessionID : Session.RemoveInput ,
2026-01-16 21:21:13 +00:00
} ) ,
) ,
async ( c ) = > {
const sessionID = c . req . valid ( "param" ) . sessionID
2026-04-14 17:45:13 +00:00
await AppRuntime . runPromise ( Session . Service . use ( ( svc ) = > svc . remove ( sessionID ) ) )
2026-01-16 21:21:13 +00:00
return c . json ( true )
} ,
)
. patch (
"/:sessionID" ,
describeRoute ( {
summary : "Update session" ,
description : "Update properties of an existing session, such as title or other metadata." ,
operationId : "session.update" ,
responses : {
200 : {
description : "Successfully updated session" ,
content : {
"application/json" : {
schema : resolver ( Session . Info ) ,
} ,
} ,
} ,
. . . errors ( 400 , 404 ) ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
2026-03-11 23:16:56 +00:00
sessionID : SessionID.zod ,
2026-01-16 21:21:13 +00:00
} ) ,
) ,
validator (
"json" ,
z . object ( {
title : z.string ( ) . optional ( ) ,
2026-04-15 21:28:01 +00:00
permission : Permission.Ruleset.zod.optional ( ) ,
2026-01-16 21:21:13 +00:00
time : z
. object ( {
archived : z.number ( ) . optional ( ) ,
} )
. optional ( ) ,
} ) ,
) ,
async ( c ) = > {
const sessionID = c . req . valid ( "param" ) . sessionID
const updates = c . req . valid ( "json" )
2026-04-14 17:45:13 +00:00
const session = await AppRuntime . runPromise (
Effect . gen ( function * ( ) {
const session = yield * Session . Service
const current = yield * session . get ( sessionID )
2026-01-16 21:21:13 +00:00
2026-04-14 17:45:13 +00:00
if ( updates . title !== undefined ) {
yield * session . setTitle ( { sessionID , title : updates.title } )
}
if ( updates . permission !== undefined ) {
yield * session . setPermission ( {
sessionID ,
permission : Permission.merge ( current . permission ? ? [ ] , updates . permission ) ,
} )
}
if ( updates . time ? . archived !== undefined ) {
yield * session . setArchived ( { sessionID , time : updates.time.archived } )
}
2026-01-16 21:21:13 +00:00
2026-04-14 17:45:13 +00:00
return yield * session . get ( sessionID )
} ) ,
)
2026-02-14 04:19:02 +00:00
return c . json ( session )
2026-01-16 21:21:13 +00:00
} ,
)
2026-04-09 20:28:42 +00:00
// TODO(v2): remove this dedicated route and rely on the normal `/init` command flow.
2026-01-16 21:21:13 +00:00
. post (
"/:sessionID/init" ,
describeRoute ( {
summary : "Initialize session" ,
description :
"Analyze the current application and create an AGENTS.md file with project-specific agent configurations." ,
operationId : "session.init" ,
responses : {
200 : {
description : "200" ,
content : {
"application/json" : {
schema : resolver ( z . boolean ( ) ) ,
} ,
} ,
} ,
. . . errors ( 400 , 404 ) ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
2026-03-11 23:16:56 +00:00
sessionID : SessionID.zod ,
2026-01-16 21:21:13 +00:00
} ) ,
) ,
2026-04-09 20:28:42 +00:00
validator (
"json" ,
z . object ( {
modelID : ModelID.zod ,
providerID : ProviderID.zod ,
messageID : MessageID.zod ,
} ) ,
) ,
2026-01-16 21:21:13 +00:00
async ( c ) = > {
const sessionID = c . req . valid ( "param" ) . sessionID
const body = c . req . valid ( "json" )
2026-04-14 17:45:13 +00:00
await AppRuntime . runPromise (
SessionPrompt . Service . use ( ( svc ) = >
svc . command ( {
sessionID ,
messageID : body.messageID ,
model : body.providerID + "/" + body . modelID ,
command : Command.Default.INIT ,
arguments : "" ,
} ) ,
) ,
)
2026-01-16 21:21:13 +00:00
return c . json ( true )
} ,
)
. post (
"/:sessionID/fork" ,
describeRoute ( {
summary : "Fork session" ,
description : "Create a new session by forking an existing session at a specific message point." ,
operationId : "session.fork" ,
responses : {
200 : {
description : "200" ,
content : {
"application/json" : {
schema : resolver ( Session . Info ) ,
} ,
} ,
} ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
2026-04-14 17:45:13 +00:00
sessionID : Session.ForkInput.shape.sessionID ,
2026-01-16 21:21:13 +00:00
} ) ,
) ,
2026-04-14 17:45:13 +00:00
validator ( "json" , Session . ForkInput . omit ( { sessionID : true } ) ) ,
2026-01-16 21:21:13 +00:00
async ( c ) = > {
const sessionID = c . req . valid ( "param" ) . sessionID
const body = c . req . valid ( "json" )
2026-04-14 17:45:13 +00:00
const result = await AppRuntime . runPromise ( Session . Service . use ( ( svc ) = > svc . fork ( { . . . body , sessionID } ) ) )
2026-01-16 21:21:13 +00:00
return c . json ( result )
} ,
)
. post (
"/:sessionID/abort" ,
describeRoute ( {
summary : "Abort session" ,
description : "Abort an active session and stop any ongoing AI processing or command execution." ,
operationId : "session.abort" ,
responses : {
200 : {
description : "Aborted session" ,
content : {
"application/json" : {
schema : resolver ( z . boolean ( ) ) ,
} ,
} ,
} ,
. . . errors ( 400 , 404 ) ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
2026-03-11 23:16:56 +00:00
sessionID : SessionID.zod ,
2026-01-16 21:21:13 +00:00
} ) ,
) ,
async ( c ) = > {
2026-04-14 17:45:13 +00:00
await AppRuntime . runPromise ( SessionPrompt . Service . use ( ( svc ) = > svc . cancel ( c . req . valid ( "param" ) . sessionID ) ) )
2026-01-16 21:21:13 +00:00
return c . json ( true )
} ,
)
. post (
"/:sessionID/share" ,
describeRoute ( {
summary : "Share session" ,
description : "Create a shareable link for a session, allowing others to view the conversation." ,
operationId : "session.share" ,
responses : {
200 : {
description : "Successfully shared session" ,
content : {
"application/json" : {
schema : resolver ( Session . Info ) ,
} ,
} ,
} ,
. . . errors ( 400 , 404 ) ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
2026-03-11 23:16:56 +00:00
sessionID : SessionID.zod ,
2026-01-16 21:21:13 +00:00
} ) ,
) ,
async ( c ) = > {
const sessionID = c . req . valid ( "param" ) . sessionID
2026-04-14 16:38:11 +00:00
const session = await AppRuntime . runPromise (
Effect . gen ( function * ( ) {
const share = yield * SessionShare . Service
const session = yield * Session . Service
yield * share . share ( sessionID )
return yield * session . get ( sessionID )
} ) ,
)
2026-01-16 21:21:13 +00:00
return c . json ( session )
} ,
)
. get (
"/:sessionID/diff" ,
describeRoute ( {
summary : "Get message diff" ,
description : "Get the file changes (diff) that resulted from a specific user message in the session." ,
operationId : "session.diff" ,
responses : {
200 : {
description : "Successfully retrieved diff" ,
content : {
"application/json" : {
schema : resolver ( Snapshot . FileDiff . array ( ) ) ,
} ,
} ,
} ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
2026-03-31 23:14:45 +00:00
sessionID : SessionSummary.DiffInput.shape.sessionID ,
2026-01-16 21:21:13 +00:00
} ) ,
) ,
validator (
"query" ,
z . object ( {
2026-03-31 23:14:45 +00:00
messageID : SessionSummary.DiffInput.shape.messageID ,
2026-01-16 21:21:13 +00:00
} ) ,
) ,
async ( c ) = > {
const query = c . req . valid ( "query" )
const params = c . req . valid ( "param" )
2026-04-13 19:35:38 +00:00
const result = await AppRuntime . runPromise (
SessionSummary . Service . use ( ( summary ) = >
summary . diff ( {
sessionID : params.sessionID ,
messageID : query.messageID ,
} ) ,
) ,
)
2026-01-16 21:21:13 +00:00
return c . json ( result )
} ,
)
. delete (
"/:sessionID/share" ,
describeRoute ( {
summary : "Unshare session" ,
description : "Remove the shareable link for a session, making it private again." ,
operationId : "session.unshare" ,
responses : {
200 : {
description : "Successfully unshared session" ,
content : {
"application/json" : {
schema : resolver ( Session . Info ) ,
} ,
} ,
} ,
. . . errors ( 400 , 404 ) ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
2026-04-10 01:47:48 +00:00
sessionID : SessionID.zod ,
2026-01-16 21:21:13 +00:00
} ) ,
) ,
async ( c ) = > {
const sessionID = c . req . valid ( "param" ) . sessionID
2026-04-14 16:38:11 +00:00
const session = await AppRuntime . runPromise (
Effect . gen ( function * ( ) {
const share = yield * SessionShare . Service
const session = yield * Session . Service
yield * share . unshare ( sessionID )
return yield * session . get ( sessionID )
} ) ,
)
2026-01-16 21:21:13 +00:00
return c . json ( session )
} ,
)
. post (
"/:sessionID/summarize" ,
describeRoute ( {
summary : "Summarize session" ,
description : "Generate a concise summary of the session using AI compaction to preserve key information." ,
operationId : "session.summarize" ,
responses : {
200 : {
description : "Summarized session" ,
content : {
"application/json" : {
schema : resolver ( z . boolean ( ) ) ,
} ,
} ,
} ,
. . . errors ( 400 , 404 ) ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
2026-03-11 23:16:56 +00:00
sessionID : SessionID.zod ,
2026-01-16 21:21:13 +00:00
} ) ,
) ,
validator (
"json" ,
z . object ( {
2026-03-12 13:27:52 +00:00
providerID : ProviderID.zod ,
modelID : ModelID.zod ,
2026-01-16 21:21:13 +00:00
auto : z.boolean ( ) . optional ( ) . default ( false ) ,
} ) ,
) ,
async ( c ) = > {
const sessionID = c . req . valid ( "param" ) . sessionID
const body = c . req . valid ( "json" )
2026-04-13 20:16:13 +00:00
await AppRuntime . runPromise (
Effect . gen ( function * ( ) {
const session = yield * Session . Service
const revert = yield * SessionRevert . Service
const compact = yield * SessionCompaction . Service
const prompt = yield * SessionPrompt . Service
const agent = yield * Agent . Service
yield * revert . cleanup ( yield * session . get ( sessionID ) )
const msgs = yield * session . messages ( { sessionID } )
const defaultAgent = yield * agent . defaultAgent ( )
let currentAgent = defaultAgent
for ( let i = msgs . length - 1 ; i >= 0 ; i -- ) {
const info = msgs [ i ] . info
if ( info . role === "user" ) {
currentAgent = info . agent || defaultAgent
break
}
}
yield * compact . create ( {
sessionID ,
agent : currentAgent ,
model : {
providerID : body.providerID ,
modelID : body.modelID ,
} ,
auto : body.auto ,
} )
yield * prompt . loop ( { sessionID } )
} ) ,
)
2026-01-16 21:21:13 +00:00
return c . json ( true )
} ,
)
. get (
"/:sessionID/message" ,
describeRoute ( {
summary : "Get session messages" ,
description : "Retrieve all messages in a session, including user prompts and AI responses." ,
operationId : "session.messages" ,
responses : {
200 : {
description : "List of messages" ,
content : {
"application/json" : {
schema : resolver ( MessageV2 . WithParts . array ( ) ) ,
} ,
} ,
} ,
. . . errors ( 400 , 404 ) ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
2026-03-11 23:16:56 +00:00
sessionID : SessionID.zod ,
2026-01-16 21:21:13 +00:00
} ) ,
) ,
validator (
"query" ,
2026-03-13 10:18:43 +00:00
z
. object ( {
limit : z.coerce
. number ( )
. int ( )
. min ( 0 )
. optional ( )
. meta ( { description : "Maximum number of messages to return" } ) ,
before : z
. string ( )
. optional ( )
. meta ( { description : "Opaque cursor for loading older messages" } )
. refine (
( value ) = > {
if ( ! value ) return true
try {
MessageV2 . cursor . decode ( value )
return true
} catch {
return false
}
} ,
{ message : "Invalid cursor" } ,
) ,
} )
. refine ( ( value ) = > ! value . before || value . limit !== undefined , {
message : "before requires limit" ,
path : [ "before" ] ,
} ) ,
2026-01-16 21:21:13 +00:00
) ,
async ( c ) = > {
const query = c . req . valid ( "query" )
2026-03-13 10:18:43 +00:00
const sessionID = c . req . valid ( "param" ) . sessionID
2026-04-14 17:45:13 +00:00
if ( query . limit === undefined || query . limit === 0 ) {
const messages = await AppRuntime . runPromise (
Effect . gen ( function * ( ) {
const session = yield * Session . Service
yield * session . get ( sessionID )
return yield * session . messages ( { sessionID } )
} ) ,
)
2026-03-13 10:18:43 +00:00
return c . json ( messages )
}
const page = await MessageV2 . page ( {
sessionID ,
2026-01-16 21:21:13 +00:00
limit : query.limit ,
2026-03-13 10:18:43 +00:00
before : query.before ,
2026-01-16 21:21:13 +00:00
} )
2026-03-13 10:18:43 +00:00
if ( page . cursor ) {
const url = new URL ( c . req . url )
url . searchParams . set ( "limit" , query . limit . toString ( ) )
url . searchParams . set ( "before" , page . cursor )
c . header ( "Access-Control-Expose-Headers" , "Link, X-Next-Cursor" )
2026-04-16 00:45:19 +00:00
c . header ( "Link" , ` < ${ url . toString ( ) } >; rel="next" ` )
2026-03-13 10:18:43 +00:00
c . header ( "X-Next-Cursor" , page . cursor )
}
return c . json ( page . items )
2026-01-16 21:21:13 +00:00
} ,
)
. get (
"/:sessionID/message/:messageID" ,
describeRoute ( {
summary : "Get message" ,
description : "Retrieve a specific message from a session by its message ID." ,
operationId : "session.message" ,
responses : {
200 : {
description : "Message" ,
content : {
"application/json" : {
schema : resolver (
z . object ( {
info : MessageV2.Info ,
parts : MessageV2.Part.array ( ) ,
} ) ,
) ,
} ,
} ,
} ,
. . . errors ( 400 , 404 ) ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
2026-03-11 23:16:56 +00:00
sessionID : SessionID.zod ,
2026-03-11 23:30:17 +00:00
messageID : MessageID.zod ,
2026-01-16 21:21:13 +00:00
} ) ,
) ,
async ( c ) = > {
const params = c . req . valid ( "param" )
const message = await MessageV2 . get ( {
sessionID : params.sessionID ,
messageID : params.messageID ,
} )
return c . json ( message )
} ,
)
2026-02-25 14:25:26 +00:00
. delete (
"/:sessionID/message/:messageID" ,
describeRoute ( {
summary : "Delete message" ,
description :
"Permanently delete a specific message (and all of its parts) from a session. This does not revert any file changes that may have been made while processing the message." ,
operationId : "session.deleteMessage" ,
responses : {
200 : {
description : "Successfully deleted message" ,
content : {
"application/json" : {
schema : resolver ( z . boolean ( ) ) ,
} ,
} ,
} ,
. . . errors ( 400 , 404 ) ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
2026-03-11 23:16:56 +00:00
sessionID : SessionID.zod ,
2026-03-11 23:30:17 +00:00
messageID : MessageID.zod ,
2026-02-25 14:25:26 +00:00
} ) ,
) ,
async ( c ) = > {
const params = c . req . valid ( "param" )
2026-04-12 00:01:52 +00:00
await AppRuntime . runPromise (
Effect . gen ( function * ( ) {
const state = yield * SessionRunState . Service
const session = yield * Session . Service
yield * state . assertNotBusy ( params . sessionID )
yield * session . removeMessage ( {
sessionID : params.sessionID ,
messageID : params.messageID ,
} )
} ) ,
)
2026-02-25 14:25:26 +00:00
return c . json ( true )
} ,
)
2026-01-16 21:21:13 +00:00
. delete (
"/:sessionID/message/:messageID/part/:partID" ,
describeRoute ( {
description : "Delete a part from a message" ,
operationId : "part.delete" ,
responses : {
200 : {
description : "Successfully deleted part" ,
content : {
"application/json" : {
schema : resolver ( z . boolean ( ) ) ,
} ,
} ,
} ,
. . . errors ( 400 , 404 ) ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
2026-03-11 23:16:56 +00:00
sessionID : SessionID.zod ,
2026-03-11 23:30:17 +00:00
messageID : MessageID.zod ,
2026-03-11 23:40:50 +00:00
partID : PartID.zod ,
2026-01-16 21:21:13 +00:00
} ) ,
) ,
async ( c ) = > {
const params = c . req . valid ( "param" )
2026-04-14 17:45:13 +00:00
await AppRuntime . runPromise (
Session . Service . use ( ( svc ) = >
svc . removePart ( {
sessionID : params.sessionID ,
messageID : params.messageID ,
partID : params.partID ,
} ) ,
) ,
)
2026-01-16 21:21:13 +00:00
return c . json ( true )
} ,
)
. patch (
"/:sessionID/message/:messageID/part/:partID" ,
describeRoute ( {
description : "Update a part in a message" ,
operationId : "part.update" ,
responses : {
200 : {
description : "Successfully updated part" ,
content : {
"application/json" : {
schema : resolver ( MessageV2 . Part ) ,
} ,
} ,
} ,
. . . errors ( 400 , 404 ) ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
2026-03-11 23:16:56 +00:00
sessionID : SessionID.zod ,
2026-03-11 23:30:17 +00:00
messageID : MessageID.zod ,
2026-03-11 23:40:50 +00:00
partID : PartID.zod ,
2026-01-16 21:21:13 +00:00
} ) ,
) ,
validator ( "json" , MessageV2 . Part ) ,
async ( c ) = > {
const params = c . req . valid ( "param" )
const body = c . req . valid ( "json" )
if ( body . id !== params . partID || body . messageID !== params . messageID || body . sessionID !== params . sessionID ) {
throw new Error (
` Part mismatch: body.id=' ${ body . id } ' vs partID=' ${ params . partID } ', body.messageID=' ${ body . messageID } ' vs messageID=' ${ params . messageID } ', body.sessionID=' ${ body . sessionID } ' vs sessionID=' ${ params . sessionID } ' ` ,
)
}
2026-04-14 17:45:13 +00:00
const part = await AppRuntime . runPromise ( Session . Service . use ( ( svc ) = > svc . updatePart ( body ) ) )
2026-01-16 21:21:13 +00:00
return c . json ( part )
} ,
)
. post (
"/:sessionID/message" ,
describeRoute ( {
summary : "Send message" ,
description : "Create and send a new message to a session, streaming the AI response." ,
operationId : "session.prompt" ,
responses : {
200 : {
description : "Created message" ,
content : {
"application/json" : {
schema : resolver (
z . object ( {
info : MessageV2.Assistant ,
parts : MessageV2.Part.array ( ) ,
} ) ,
) ,
} ,
} ,
} ,
. . . errors ( 400 , 404 ) ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
2026-03-11 23:16:56 +00:00
sessionID : SessionID.zod ,
2026-01-16 21:21:13 +00:00
} ) ,
) ,
validator ( "json" , SessionPrompt . PromptInput . omit ( { sessionID : true } ) ) ,
async ( c ) = > {
c . status ( 200 )
c . header ( "Content-Type" , "application/json" )
return stream ( c , async ( stream ) = > {
const sessionID = c . req . valid ( "param" ) . sessionID
const body = c . req . valid ( "json" )
2026-04-14 17:45:13 +00:00
const msg = await AppRuntime . runPromise (
SessionPrompt . Service . use ( ( svc ) = > svc . prompt ( { . . . body , sessionID } ) ) ,
)
2026-04-16 03:27:32 +00:00
void stream . write ( JSON . stringify ( msg ) )
2026-01-16 21:21:13 +00:00
} )
} ,
)
. post (
"/:sessionID/prompt_async" ,
describeRoute ( {
summary : "Send async message" ,
description :
"Create and send a new message to a session asynchronously, starting the session if needed and returning immediately." ,
operationId : "session.prompt_async" ,
responses : {
204 : {
description : "Prompt accepted" ,
} ,
. . . errors ( 400 , 404 ) ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
2026-03-11 23:16:56 +00:00
sessionID : SessionID.zod ,
2026-01-16 21:21:13 +00:00
} ) ,
) ,
validator ( "json" , SessionPrompt . PromptInput . omit ( { sessionID : true } ) ) ,
async ( c ) = > {
2026-04-09 05:18:46 +00:00
const sessionID = c . req . valid ( "param" ) . sessionID
const body = c . req . valid ( "json" )
2026-04-16 03:27:32 +00:00
void AppRuntime . runPromise ( SessionPrompt . Service . use ( ( svc ) = > svc . prompt ( { . . . body , sessionID } ) ) ) . catch (
( err ) = > {
log . error ( "prompt_async failed" , { sessionID , error : err } )
void Bus . publish ( Session . Event . Error , {
sessionID ,
error : new NamedError . Unknown ( { message : err instanceof Error ? err.message : String ( err ) } ) . toObject ( ) ,
} )
} ,
)
2026-04-09 05:18:46 +00:00
return c . body ( null , 204 )
2026-01-16 21:21:13 +00:00
} ,
)
. post (
"/:sessionID/command" ,
describeRoute ( {
summary : "Send command" ,
description : "Send a new command to a session for execution by the AI assistant." ,
operationId : "session.command" ,
responses : {
200 : {
description : "Created message" ,
content : {
"application/json" : {
schema : resolver (
z . object ( {
info : MessageV2.Assistant ,
parts : MessageV2.Part.array ( ) ,
} ) ,
) ,
} ,
} ,
} ,
. . . errors ( 400 , 404 ) ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
2026-03-11 23:16:56 +00:00
sessionID : SessionID.zod ,
2026-01-16 21:21:13 +00:00
} ) ,
) ,
validator ( "json" , SessionPrompt . CommandInput . omit ( { sessionID : true } ) ) ,
async ( c ) = > {
const sessionID = c . req . valid ( "param" ) . sessionID
const body = c . req . valid ( "json" )
2026-04-14 17:45:13 +00:00
const msg = await AppRuntime . runPromise ( SessionPrompt . Service . use ( ( svc ) = > svc . command ( { . . . body , sessionID } ) ) )
2026-01-16 21:21:13 +00:00
return c . json ( msg )
} ,
)
. post (
"/:sessionID/shell" ,
describeRoute ( {
summary : "Run shell command" ,
description : "Execute a shell command within the session context and return the AI's response." ,
operationId : "session.shell" ,
responses : {
200 : {
description : "Created message" ,
content : {
"application/json" : {
2026-04-08 17:56:15 +00:00
schema : resolver ( MessageV2 . WithParts ) ,
2026-01-16 21:21:13 +00:00
} ,
} ,
} ,
. . . errors ( 400 , 404 ) ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
2026-03-11 23:16:56 +00:00
sessionID : SessionID.zod ,
2026-01-16 21:21:13 +00:00
} ) ,
) ,
validator ( "json" , SessionPrompt . ShellInput . omit ( { sessionID : true } ) ) ,
async ( c ) = > {
const sessionID = c . req . valid ( "param" ) . sessionID
const body = c . req . valid ( "json" )
2026-04-14 17:45:13 +00:00
const msg = await AppRuntime . runPromise ( SessionPrompt . Service . use ( ( svc ) = > svc . shell ( { . . . body , sessionID } ) ) )
2026-01-16 21:21:13 +00:00
return c . json ( msg )
} ,
)
. post (
"/:sessionID/revert" ,
describeRoute ( {
summary : "Revert message" ,
description : "Revert a specific message in a session, undoing its effects and restoring the previous state." ,
operationId : "session.revert" ,
responses : {
200 : {
description : "Updated session" ,
content : {
"application/json" : {
schema : resolver ( Session . Info ) ,
} ,
} ,
} ,
. . . errors ( 400 , 404 ) ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
2026-03-11 23:16:56 +00:00
sessionID : SessionID.zod ,
2026-01-16 21:21:13 +00:00
} ) ,
) ,
validator ( "json" , SessionRevert . RevertInput . omit ( { sessionID : true } ) ) ,
async ( c ) = > {
const sessionID = c . req . valid ( "param" ) . sessionID
log . info ( "revert" , c . req . valid ( "json" ) )
2026-04-13 20:16:13 +00:00
const session = await AppRuntime . runPromise (
SessionRevert . Service . use ( ( svc ) = >
svc . revert ( {
sessionID ,
. . . c . req . valid ( "json" ) ,
} ) ,
) ,
)
2026-01-16 21:21:13 +00:00
return c . json ( session )
} ,
)
. post (
"/:sessionID/unrevert" ,
describeRoute ( {
summary : "Restore reverted messages" ,
description : "Restore all previously reverted messages in a session." ,
operationId : "session.unrevert" ,
responses : {
200 : {
description : "Updated session" ,
content : {
"application/json" : {
schema : resolver ( Session . Info ) ,
} ,
} ,
} ,
. . . errors ( 400 , 404 ) ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
2026-03-11 23:16:56 +00:00
sessionID : SessionID.zod ,
2026-01-16 21:21:13 +00:00
} ) ,
) ,
async ( c ) = > {
const sessionID = c . req . valid ( "param" ) . sessionID
2026-04-13 20:16:13 +00:00
const session = await AppRuntime . runPromise ( SessionRevert . Service . use ( ( svc ) = > svc . unrevert ( { sessionID } ) ) )
2026-01-16 21:21:13 +00:00
return c . json ( session )
} ,
)
. post (
"/:sessionID/permissions/:permissionID" ,
describeRoute ( {
summary : "Respond to permission" ,
deprecated : true ,
description : "Approve or deny a permission request from the AI assistant." ,
operationId : "permission.respond" ,
responses : {
200 : {
description : "Permission processed successfully" ,
content : {
"application/json" : {
schema : resolver ( z . boolean ( ) ) ,
} ,
} ,
} ,
. . . errors ( 400 , 404 ) ,
} ,
} ) ,
validator (
"param" ,
z . object ( {
2026-03-11 23:16:56 +00:00
sessionID : SessionID.zod ,
2026-03-12 01:49:57 +00:00
permissionID : PermissionID.zod ,
2026-01-16 21:21:13 +00:00
} ) ,
) ,
2026-04-15 21:28:01 +00:00
validator ( "json" , z . object ( { response : Permission.Reply.zod } ) ) ,
2026-01-16 21:21:13 +00:00
async ( c ) = > {
const params = c . req . valid ( "param" )
2026-04-13 23:33:58 +00:00
await AppRuntime . runPromise (
Permission . Service . use ( ( svc ) = >
svc . reply ( {
requestID : params.permissionID ,
reply : c.req.valid ( "json" ) . response ,
} ) ,
) ,
)
2026-01-16 21:21:13 +00:00
return c . json ( true )
} ,
) ,
)