11import * as InstanceState from "@/effect/instance-state"
2+ import { AppRuntime } from "@/effect/app-runtime"
3+ import { Permission } from "@/permission"
24import { Instance } from "@/project/instance"
5+ import { SessionShare } from "@/share"
36import { Session } from "@/session"
47import { MessageV2 } from "@/session/message-v2"
8+ import { SessionPrompt } from "@/session/prompt"
59import { SessionStatus } from "@/session/status"
610import { SessionSummary } from "@/session/summary"
711import { Todo } from "@/session/todo"
@@ -26,6 +30,18 @@ const MessagesQuery = Schema.Struct({
2630 before : Schema . optional ( Schema . String ) ,
2731} )
2832const StatusMap = Schema . Record ( Schema . String , SessionStatus . Info )
33+ const UpdatePayload = Schema . Struct ( {
34+ title : Schema . optional ( Schema . String ) ,
35+ permission : Schema . optional ( Permission . Ruleset ) ,
36+ time : Schema . optional (
37+ Schema . Struct ( {
38+ archived : Schema . optional ( Schema . Number ) ,
39+ } ) ,
40+ ) ,
41+ } ) . annotate ( { identifier : "SessionUpdateInput" } )
42+ const ForkPayload = Schema . Struct ( Struct . omit ( Session . ForkInput . fields , [ "sessionID" ] ) ) . annotate ( {
43+ identifier : "SessionForkInput" ,
44+ } )
2945
3046export const SessionPaths = {
3147 list : root ,
@@ -36,6 +52,11 @@ export const SessionPaths = {
3652 diff : `${ root } /:sessionID/diff` ,
3753 messages : `${ root } /:sessionID/message` ,
3854 message : `${ root } /:sessionID/message/:messageID` ,
55+ create : root ,
56+ remove : `${ root } /:sessionID` ,
57+ update : `${ root } /:sessionID` ,
58+ fork : `${ root } /:sessionID/fork` ,
59+ abort : `${ root } /:sessionID/abort` ,
3960} as const
4061
4162export const SessionApi = HttpApi . make ( "session" )
@@ -123,6 +144,58 @@ export const SessionApi = HttpApi.make("session")
123144 description : "Retrieve a specific message from a session by its message ID." ,
124145 } ) ,
125146 ) ,
147+ HttpApiEndpoint . post ( "create" , SessionPaths . create , {
148+ payload : Session . CreateInput ,
149+ success : Session . Info ,
150+ } ) . annotateMerge (
151+ OpenApi . annotations ( {
152+ identifier : "session.create" ,
153+ summary : "Create session" ,
154+ description : "Create a new OpenCode session for interacting with AI assistants and managing conversations." ,
155+ } ) ,
156+ ) ,
157+ HttpApiEndpoint . delete ( "remove" , SessionPaths . remove , {
158+ params : { sessionID : SessionID } ,
159+ success : Schema . Boolean ,
160+ } ) . annotateMerge (
161+ OpenApi . annotations ( {
162+ identifier : "session.delete" ,
163+ summary : "Delete session" ,
164+ description : "Delete a session and permanently remove all associated data, including messages and history." ,
165+ } ) ,
166+ ) ,
167+ HttpApiEndpoint . patch ( "update" , SessionPaths . update , {
168+ params : { sessionID : SessionID } ,
169+ payload : UpdatePayload ,
170+ success : Session . Info ,
171+ } ) . annotateMerge (
172+ OpenApi . annotations ( {
173+ identifier : "session.update" ,
174+ summary : "Update session" ,
175+ description : "Update properties of an existing session, such as title or other metadata." ,
176+ } ) ,
177+ ) ,
178+ HttpApiEndpoint . post ( "fork" , SessionPaths . fork , {
179+ params : { sessionID : SessionID } ,
180+ payload : ForkPayload ,
181+ success : Session . Info ,
182+ } ) . annotateMerge (
183+ OpenApi . annotations ( {
184+ identifier : "session.fork" ,
185+ summary : "Fork session" ,
186+ description : "Create a new session by forking an existing session at a specific message point." ,
187+ } ) ,
188+ ) ,
189+ HttpApiEndpoint . post ( "abort" , SessionPaths . abort , {
190+ params : { sessionID : SessionID } ,
191+ success : Schema . Boolean ,
192+ } ) . annotateMerge (
193+ OpenApi . annotations ( {
194+ identifier : "session.abort" ,
195+ summary : "Abort session" ,
196+ description : "Abort an active session and stop any ongoing AI processing or command execution." ,
197+ } ) ,
198+ ) ,
126199 )
127200 . annotateMerge (
128201 OpenApi . annotations ( {
@@ -222,6 +295,86 @@ export const sessionHandlers = Layer.unwrap(
222295 )
223296 } )
224297
298+ const create = Effect . fn ( "SessionHttpApi.create" ) ( function * ( ctx : { payload : Session . CreateInput } ) {
299+ const instance = yield * InstanceState . context
300+ return yield * Effect . promise ( ( ) =>
301+ Instance . restore ( instance , ( ) =>
302+ AppRuntime . runPromise ( SessionShare . Service . use ( ( svc ) => svc . create ( ctx . payload ) ) . pipe ( Effect . provide ( SessionShare . defaultLayer ) ) ) ,
303+ ) ,
304+ )
305+ } )
306+
307+ const remove = Effect . fn ( "SessionHttpApi.remove" ) ( function * ( ctx : { params : { sessionID : SessionID } } ) {
308+ const instance = yield * InstanceState . context
309+ yield * Effect . promise ( ( ) =>
310+ Instance . restore ( instance , ( ) =>
311+ AppRuntime . runPromise ( Session . Service . use ( ( svc ) => svc . remove ( ctx . params . sessionID ) ) . pipe ( Effect . provide ( Session . defaultLayer ) ) ) ,
312+ ) ,
313+ )
314+ return true
315+ } )
316+
317+ const update = Effect . fn ( "SessionHttpApi.update" ) ( function * ( ctx : {
318+ params : { sessionID : SessionID }
319+ payload : typeof UpdatePayload . Type
320+ } ) {
321+ const instance = yield * InstanceState . context
322+ return yield * Effect . promise ( ( ) =>
323+ Instance . restore ( instance , ( ) =>
324+ AppRuntime . runPromise (
325+ Session . Service . use ( ( svc ) =>
326+ Effect . gen ( function * ( ) {
327+ const current = yield * svc . get ( ctx . params . sessionID )
328+ if ( ctx . payload . title !== undefined ) {
329+ yield * svc . setTitle ( { sessionID : ctx . params . sessionID , title : ctx . payload . title } )
330+ }
331+ if ( ctx . payload . permission !== undefined ) {
332+ yield * svc . setPermission ( {
333+ sessionID : ctx . params . sessionID ,
334+ permission : Permission . merge ( current . permission ?? [ ] , ctx . payload . permission ) ,
335+ } )
336+ }
337+ if ( ctx . payload . time ?. archived !== undefined ) {
338+ yield * svc . setArchived ( { sessionID : ctx . params . sessionID , time : ctx . payload . time . archived } )
339+ }
340+ return yield * svc . get ( ctx . params . sessionID )
341+ } ) ,
342+ ) . pipe ( Effect . provide ( Session . defaultLayer ) ) ,
343+ ) ,
344+ ) ,
345+ )
346+ } )
347+
348+ const fork = Effect . fn ( "SessionHttpApi.fork" ) ( function * ( ctx : {
349+ params : { sessionID : SessionID }
350+ payload : typeof ForkPayload . Type
351+ } ) {
352+ const instance = yield * InstanceState . context
353+ return yield * Effect . promise ( ( ) =>
354+ Instance . restore ( instance , ( ) =>
355+ AppRuntime . runPromise (
356+ Session . Service . use ( ( svc ) => svc . fork ( { sessionID : ctx . params . sessionID , messageID : ctx . payload . messageID } ) ) . pipe (
357+ Effect . provide ( Session . defaultLayer ) ,
358+ ) ,
359+ ) ,
360+ ) ,
361+ )
362+ } )
363+
364+ const abort = Effect . fn ( "SessionHttpApi.abort" ) ( function * ( ctx : { params : { sessionID : SessionID } } ) {
365+ const instance = yield * InstanceState . context
366+ yield * Effect . promise ( ( ) =>
367+ Instance . restore ( instance , ( ) =>
368+ AppRuntime . runPromise (
369+ SessionPrompt . Service . use ( ( svc ) => svc . cancel ( ctx . params . sessionID ) ) . pipe (
370+ Effect . provide ( SessionPrompt . defaultLayer ) ,
371+ ) ,
372+ ) ,
373+ ) ,
374+ )
375+ return true
376+ } )
377+
225378 return HttpApiBuilder . group ( SessionApi , "session" , ( handlers ) =>
226379 handlers
227380 . handle ( "list" , list )
@@ -231,7 +384,12 @@ export const sessionHandlers = Layer.unwrap(
231384 . handle ( "todo" , todo )
232385 . handle ( "diff" , diff )
233386 . handle ( "messages" , messages )
234- . handle ( "message" , message ) ,
387+ . handle ( "message" , message )
388+ . handle ( "create" , create )
389+ . handle ( "remove" , remove )
390+ . handle ( "update" , update )
391+ . handle ( "fork" , fork )
392+ . handle ( "abort" , abort ) ,
235393 )
236394 } ) ,
237395) . pipe (
0 commit comments