11import { type Context , Hono } from "hono" ;
22import { validator as zValidator , describeRoute , resolver } from "hono-openapi" ;
3- import z from "zod/v4" ;
4- import { errorResponseSchema } from "@assistant/schemas" ;
3+ import {
4+ autoConnectSchema ,
5+ cancelRunSchema ,
6+ errorResponseSchema ,
7+ executeSandboxRunSchema ,
8+ githubConnectionSchema ,
9+ listRunsQuerySchema ,
10+ type AutoConnectPayload ,
11+ type CancelRunPayload ,
12+ type ExecuteSandboxRunPayload ,
13+ type GitHubConnectionPayload ,
14+ type ListRunsQueryPayload ,
15+ } from "@assistant/schemas" ;
516
617import { getServiceContext } from "~/lib/context/serviceContext" ;
718import { ResponseFactory } from "~/lib/http/ResponseFactory" ;
@@ -10,53 +21,21 @@ import { createRouteLogger } from "~/middleware/loggerMiddleware";
1021import type { IUser } from "~/types" ;
1122import { executeSandboxRunStream } from "~/services/apps/sandbox/execute-stream" ;
1223import {
13- parseSandboxRunData ,
14- toSandboxRunResponse ,
15- } from "~/services/apps/sandbox/run-data" ;
24+ getSandboxRunForUser ,
25+ listSandboxRunsForUser ,
26+ requestSandboxRunCancellation ,
27+ } from "~/services/apps/sandbox/runs" ;
1628import { listGitHubAppConnectionsForUser } from "~/services/github/connections" ;
1729import {
1830 deleteGitHubConnectionForUser ,
1931 upsertGitHubConnectionFromDefaultAppForUser ,
2032 upsertGitHubConnectionForUser ,
2133} from "~/services/github/manage-connections" ;
2234import { AssistantError , ErrorType } from "~/utils/errors" ;
23- import { safeParseJson } from "~/utils/json" ;
24- import { SANDBOX_RUNS_APP_ID , SANDBOX_RUN_ITEM_TYPE } from "~/constants/app" ;
2535
2636const app = new Hono ( ) ;
2737const routeLogger = createRouteLogger ( "apps/sandbox" ) ;
2838
29- const githubConnectionSchema = z . object ( {
30- installationId : z . number ( ) . int ( ) . positive ( ) ,
31- appId : z . string ( ) . trim ( ) . min ( 1 ) ,
32- privateKey : z . string ( ) . trim ( ) . min ( 1 ) ,
33- webhookSecret : z . string ( ) . trim ( ) . min ( 1 ) . optional ( ) ,
34- repositories : z . array ( z . string ( ) . trim ( ) . min ( 1 ) ) . optional ( ) ,
35- } ) ;
36-
37- const executeSandboxRunSchema = z . object ( {
38- installationId : z . number ( ) . int ( ) . positive ( ) ,
39- repo : z
40- . string ( )
41- . trim ( )
42- . min ( 1 )
43- . regex ( / ^ [ \w . - ] + \/ [ \w . - ] + $ / , "repo must be in owner/repo format" ) ,
44- task : z . string ( ) . trim ( ) . min ( 1 ) ,
45- model : z . string ( ) . trim ( ) . min ( 1 ) . optional ( ) ,
46- shouldCommit : z . boolean ( ) . optional ( ) ,
47- } ) ;
48-
49- const autoConnectSchema = z . object ( {
50- installationId : z . number ( ) . int ( ) . positive ( ) ,
51- repositories : z . array ( z . string ( ) . trim ( ) . min ( 1 ) ) . optional ( ) ,
52- } ) ;
53-
54- const listRunsQuerySchema = z . object ( {
55- installationId : z . coerce . number ( ) . int ( ) . positive ( ) . optional ( ) ,
56- repo : z . string ( ) . trim ( ) . min ( 1 ) . optional ( ) ,
57- limit : z . coerce . number ( ) . int ( ) . min ( 1 ) . max ( 100 ) . default ( 30 ) ,
58- } ) ;
59-
6039app . use ( "/*" , ( c , next ) => {
6140 routeLogger . info ( `Processing apps/sandbox route: ${ c . req . path } ` ) ;
6241 return next ( ) ;
@@ -177,9 +156,7 @@ app.post(
177156 zValidator ( "json" , githubConnectionSchema ) ,
178157 async ( c : Context ) => {
179158 const user = c . get ( "user" ) as IUser ;
180- const payload = c . req . valid ( "json" as never ) as z . infer <
181- typeof githubConnectionSchema
182- > ;
159+ const payload = c . req . valid ( "json" as never ) as GitHubConnectionPayload ;
183160 const serviceContext = getServiceContext ( c ) ;
184161
185162 await upsertGitHubConnectionForUser ( serviceContext , user . id , payload ) ;
@@ -216,9 +193,7 @@ app.post(
216193 zValidator ( "json" , autoConnectSchema ) ,
217194 async ( c : Context ) => {
218195 const user = c . get ( "user" ) as IUser ;
219- const payload = c . req . valid ( "json" as never ) as z . infer <
220- typeof autoConnectSchema
221- > ;
196+ const payload = c . req . valid ( "json" as never ) as AutoConnectPayload ;
222197 const serviceContext = getServiceContext ( c ) ;
223198
224199 await upsertGitHubConnectionFromDefaultAppForUser ( serviceContext , user . id , {
@@ -307,44 +282,16 @@ app.get(
307282 zValidator ( "query" , listRunsQuerySchema ) ,
308283 async ( c : Context ) => {
309284 const user = c . get ( "user" ) as IUser ;
310- const { installationId, repo, limit } = c . req . valid (
311- "query" as never ,
312- ) as z . infer < typeof listRunsQuerySchema > ;
285+ const payload = c . req . valid ( "query" as never ) as ListRunsQueryPayload ;
313286 const serviceContext = getServiceContext ( c ) ;
314- const records =
315- await serviceContext . repositories . appData . getAppDataByUserAndApp (
316- user . id ,
317- SANDBOX_RUNS_APP_ID ,
318- ) ;
319287
320- const runs = records
321- . map ( ( record ) => {
322- const parsed = parseSandboxRunData ( safeParseJson ( record . data ) ) ;
323- if ( ! parsed ) {
324- return null ;
325- }
326-
327- if (
328- installationId !== undefined &&
329- parsed . installationId !== installationId
330- ) {
331- return null ;
332- }
333-
334- if ( repo && parsed . repo . toLowerCase ( ) !== repo . toLowerCase ( ) ) {
335- return null ;
336- }
337-
338- return toSandboxRunResponse ( parsed ) ;
339- } )
340- . filter ( ( run ) : run is ReturnType < typeof toSandboxRunResponse > =>
341- Boolean ( run ) ,
342- )
343- . sort (
344- ( a , b ) =>
345- new Date ( b . updatedAt ) . getTime ( ) - new Date ( a . updatedAt ) . getTime ( ) ,
346- )
347- . slice ( 0 , limit ) ;
288+ const runs = await listSandboxRunsForUser ( {
289+ context : serviceContext ,
290+ userId : user . id ,
291+ installationId : payload . installationId ,
292+ repo : payload . repo ,
293+ limit : payload . limit ,
294+ } ) ;
348295
349296 return ResponseFactory . success ( c , { runs } ) ;
350297 } ,
@@ -375,33 +322,59 @@ app.get(
375322 async ( c : Context ) => {
376323 const user = c . get ( "user" ) as IUser ;
377324 const runId = c . req . param ( "runId" ) ;
378-
379325 if ( ! runId ) {
380326 throw new AssistantError ( "runId is required" , ErrorType . PARAMS_ERROR ) ;
381327 }
382328
383- const serviceContext = getServiceContext ( c ) ;
384- const records =
385- await serviceContext . repositories . appData . getAppDataByUserAppAndItem (
386- user . id ,
387- SANDBOX_RUNS_APP_ID ,
388- runId ,
389- SANDBOX_RUN_ITEM_TYPE ,
390- ) ;
329+ const run = await getSandboxRunForUser ( {
330+ context : getServiceContext ( c ) ,
331+ userId : user . id ,
332+ runId,
333+ } ) ;
391334
392- if ( ! records . length ) {
393- throw new AssistantError ( "Sandbox run not found" , ErrorType . NOT_FOUND ) ;
394- }
335+ return ResponseFactory . success ( c , { run } ) ;
336+ } ,
337+ ) ;
395338
396- const run = parseSandboxRunData ( safeParseJson ( records [ 0 ] . data ) ) ;
397- if ( ! run ) {
398- throw new AssistantError (
399- "Sandbox run payload is invalid" ,
400- ErrorType . NOT_FOUND ,
401- ) ;
339+ app . post (
340+ "/runs/:runId/cancel" ,
341+ describeRoute ( {
342+ tags : [ "apps" ] ,
343+ description : "Cancel a running sandbox run" ,
344+ responses : {
345+ 200 : {
346+ description : "Sandbox run cancellation was processed" ,
347+ content : {
348+ "application/json" : { } ,
349+ } ,
350+ } ,
351+ 401 : {
352+ description : "Unauthorized" ,
353+ content : {
354+ "application/json" : {
355+ schema : resolver ( errorResponseSchema ) ,
356+ } ,
357+ } ,
358+ } ,
359+ } ,
360+ } ) ,
361+ zValidator ( "json" , cancelRunSchema ) ,
362+ async ( c : Context ) => {
363+ const user = c . get ( "user" ) as IUser ;
364+ const runId = c . req . param ( "runId" ) ;
365+ const payload = c . req . valid ( "json" as never ) as CancelRunPayload ;
366+ if ( ! runId ) {
367+ throw new AssistantError ( "runId is required" , ErrorType . PARAMS_ERROR ) ;
402368 }
403369
404- return ResponseFactory . success ( c , { run : toSandboxRunResponse ( run ) } ) ;
370+ const result = await requestSandboxRunCancellation ( {
371+ context : getServiceContext ( c ) ,
372+ userId : user . id ,
373+ runId,
374+ reason : payload . reason ,
375+ } ) ;
376+
377+ return ResponseFactory . success ( c , result ) ;
405378 } ,
406379) ;
407380
@@ -431,9 +404,7 @@ app.post(
431404 async ( c : Context ) => {
432405 const user = c . get ( "user" ) as IUser ;
433406 const serviceContext = getServiceContext ( c ) ;
434- const payload = c . req . valid ( "json" as never ) as z . infer <
435- typeof executeSandboxRunSchema
436- > ;
407+ const payload = c . req . valid ( "json" as never ) as ExecuteSandboxRunPayload ;
437408
438409 return executeSandboxRunStream ( {
439410 env : c . env ,
0 commit comments