@@ -9,6 +9,13 @@ import {
99 getAgent ,
1010 type Agent ,
1111} from "../../services/agentService.js" ;
12+ import { getObject } from "../../services/objectService.js" ;
13+ import {
14+ DEFAULT_MOUNT_PATH ,
15+ sanitizeMountSegment ,
16+ adjustFileExtension ,
17+ getDefaultAgentMountPath ,
18+ } from "../../utils/mount.js" ;
1219
1320interface CreateOptions {
1421 name ?: string ;
@@ -32,7 +39,7 @@ interface CreateOptions {
3239 gateways ?: string [ ] ;
3340 mcp ?: string [ ] ;
3441 agent ?: string [ ] ;
35- agentPath ?: string ;
42+ object ?: string [ ] ;
3643 output ?: string ;
3744}
3845
@@ -293,28 +300,100 @@ export async function createDevbox(options: CreateOptions = {}) {
293300 createRequest . mcp = parseMcpSpecs ( options . mcp ) ;
294301 }
295302
296- // Handle agent mount
303+ // Parse agent mounts (format: name_or_id or name_or_id:/mount/path)
304+ const resolvedAgents : { agent : Agent ; path ?: string } [ ] = [ ] ;
297305 if ( options . agent && options . agent . length > 0 ) {
298- if ( options . agent . length > 1 ) {
299- throw new Error (
300- "Mounting multiple agents via rli is not supported yet" ,
301- ) ;
306+ const parsedAgentSpecs : { idOrName : string ; path ?: string } [ ] = [ ] ;
307+ for ( const spec of options . agent ) {
308+ const colonIdx = spec . indexOf ( ":" ) ;
309+ // Only treat colon as separator if what follows looks like an absolute path
310+ if ( colonIdx > 0 && spec [ colonIdx + 1 ] === "/" ) {
311+ parsedAgentSpecs . push ( {
312+ idOrName : spec . substring ( 0 , colonIdx ) ,
313+ path : spec . substring ( colonIdx + 1 ) ,
314+ } ) ;
315+ } else {
316+ parsedAgentSpecs . push ( { idOrName : spec } ) ;
317+ }
302318 }
303- const agent = await resolveAgent ( options . agent [ 0 ] ) ;
304- const mount : Record < string , unknown > = {
305- type : "agent_mount" ,
306- agent_id : agent . id ,
307- agent_name : null ,
308- } ;
309- // agent_path only makes sense for git and object agents. Since
310- // we don't know at this stage what type of agent it is,
311- // however, we'll let the server error inform the user if they
312- // add this option in a case where it doesn't make sense.
313- if ( options . agentPath ) {
314- mount . agent_path = options . agentPath ;
319+ const resolved = await Promise . all (
320+ parsedAgentSpecs . map ( async ( { idOrName, path } ) => ( {
321+ agent : await resolveAgent ( idOrName ) ,
322+ path,
323+ } ) ) ,
324+ ) ;
325+ resolvedAgents . push ( ...resolved ) ;
326+ }
327+
328+ // Parse object mounts (format: object_id or object_id:/mount/path)
329+ const objectMounts : { object_id : string ; object_path : string } [ ] = [ ] ;
330+ if ( options . object && options . object . length > 0 ) {
331+ const parsedObjectSpecs : {
332+ objectId : string ;
333+ explicitPath ?: string ;
334+ } [ ] = [ ] ;
335+ for ( const spec of options . object ) {
336+ const colonIdx = spec . indexOf ( ":" ) ;
337+ if ( colonIdx > 0 && spec [ colonIdx + 1 ] === "/" ) {
338+ parsedObjectSpecs . push ( {
339+ objectId : spec . substring ( 0 , colonIdx ) ,
340+ explicitPath : spec . substring ( colonIdx + 1 ) ,
341+ } ) ;
342+ } else {
343+ parsedObjectSpecs . push ( { objectId : spec } ) ;
344+ }
315345 }
346+ const resolved = await Promise . all (
347+ parsedObjectSpecs . map ( async ( { objectId, explicitPath } ) => {
348+ if ( explicitPath ) {
349+ return { object_id : objectId , object_path : explicitPath } ;
350+ }
351+ // No path specified — fetch object to generate default
352+ const obj = await getObject ( objectId ) ;
353+ const name = obj . name ;
354+ const contentType = obj . content_type ;
355+ if ( name ) {
356+ const adjusted = adjustFileExtension ( name , contentType ) ;
357+ const s = sanitizeMountSegment ( adjusted ) ;
358+ const objectPath = s
359+ ? `${ DEFAULT_MOUNT_PATH } /${ s } `
360+ : `${ DEFAULT_MOUNT_PATH } /object_${ objectId . slice ( - 8 ) } ` ;
361+ return { object_id : objectId , object_path : objectPath } ;
362+ }
363+ return {
364+ object_id : objectId ,
365+ object_path : `${ DEFAULT_MOUNT_PATH } /object_${ objectId . slice ( - 8 ) } ` ,
366+ } ;
367+ } ) ,
368+ ) ;
369+ objectMounts . push ( ...resolved ) ;
370+ }
371+
372+ // Add mounts (agents + objects)
373+ if ( resolvedAgents . length > 0 || objectMounts . length > 0 ) {
316374 if ( ! createRequest . mounts ) createRequest . mounts = [ ] ;
317- ( createRequest . mounts as unknown [ ] ) . push ( mount ) ;
375+ for ( const { agent, path } of resolvedAgents ) {
376+ const mount : Record < string , unknown > = {
377+ type : "agent_mount" ,
378+ agent_id : agent . id ,
379+ agent_name : null ,
380+ } ;
381+ const sourceType = agent . source ?. type ;
382+ const needsPath = sourceType === "git" || sourceType === "object" ;
383+ const effectivePath =
384+ path || ( needsPath ? getDefaultAgentMountPath ( agent ) : undefined ) ;
385+ if ( effectivePath ) {
386+ mount . agent_path = effectivePath ;
387+ }
388+ ( createRequest . mounts as unknown [ ] ) . push ( mount ) ;
389+ }
390+ for ( const om of objectMounts ) {
391+ ( createRequest . mounts as unknown [ ] ) . push ( {
392+ type : "object_mount" ,
393+ object_id : om . object_id ,
394+ object_path : om . object_path ,
395+ } ) ;
396+ }
318397 }
319398
320399 if ( Object . keys ( launchParameters ) . length > 0 ) {
0 commit comments