@@ -52,6 +52,7 @@ import type {
5252 PriorityJudgmentArtefact ,
5353 RepoSelectionArtefact ,
5454 SafetyJudgmentArtefact ,
55+ SandboxCustomImage ,
5556 SandboxEnvironment ,
5657 SandboxEnvironmentInput ,
5758 Signal ,
@@ -117,6 +118,13 @@ export class SeatPaymentFailedError extends Error {
117118 }
118119}
119120
121+ export class SandboxCustomImagesDisabledError extends Error {
122+ constructor ( message ?: string ) {
123+ super ( message ?? "Custom sandbox images are not enabled" ) ;
124+ this . name = "SandboxCustomImagesDisabledError" ;
125+ }
126+ }
127+
120128export type UsageLimitType = "burst" | "sustained" | null ;
121129
122130// Stable message so callers recognize this after a saga reduces the error to a string.
@@ -475,6 +483,7 @@ interface CloudRunOptions {
475483 model ?: string ;
476484 reasoningLevel ?: string ;
477485 sandboxEnvironmentId ?: string ;
486+ customImageId ?: string ;
478487 prAuthorshipMode ?: PrAuthorshipMode ;
479488 runSource ?: CloudRunSource ;
480489 signalReportId ?: string ;
@@ -546,6 +555,9 @@ function buildCloudRunRequestBody(
546555 if ( options ?. sandboxEnvironmentId ) {
547556 body . sandbox_environment_id = options . sandboxEnvironmentId ;
548557 }
558+ if ( options ?. customImageId ) {
559+ body . custom_image_id = options . customImageId ;
560+ }
549561 if ( options ?. prAuthorshipMode ) {
550562 body . pr_authorship_mode = options . prAuthorshipMode ;
551563 }
@@ -4279,6 +4291,155 @@ export class PostHogAPIClient {
42794291 }
42804292 }
42814293
4294+ async listSandboxCustomImages ( ) : Promise < SandboxCustomImage [ ] > {
4295+ const teamId = await this . getTeamId ( ) ;
4296+ const url = new URL (
4297+ `${ this . api . baseUrl } /api/projects/${ teamId } /sandbox_custom_images/` ,
4298+ ) ;
4299+ const response = await this . api . fetcher . fetch ( {
4300+ method : "get" ,
4301+ url,
4302+ path : `/api/projects/${ teamId } /sandbox_custom_images/` ,
4303+ } ) ;
4304+ if ( ! response . ok ) {
4305+ if ( response . status === 403 ) {
4306+ const errorData = ( await response . json ( ) . catch ( ( ) => ( { } ) ) ) as {
4307+ detail ?: string ;
4308+ } ;
4309+ throw new SandboxCustomImagesDisabledError ( errorData . detail ) ;
4310+ }
4311+ throw new Error (
4312+ `Failed to fetch sandbox custom images: ${ response . statusText } ` ,
4313+ ) ;
4314+ }
4315+ const data = ( await response . json ( ) ) as {
4316+ results ?: SandboxCustomImage [ ] ;
4317+ } ;
4318+ return data . results ?? [ ] ;
4319+ }
4320+
4321+ async createSandboxCustomImage ( input : {
4322+ name : string ;
4323+ description ?: string ;
4324+ repository ?: string | null ;
4325+ private ?: boolean ;
4326+ } ) : Promise < SandboxCustomImage > {
4327+ const teamId = await this . getTeamId ( ) ;
4328+ const url = new URL (
4329+ `${ this . api . baseUrl } /api/projects/${ teamId } /sandbox_custom_images/` ,
4330+ ) ;
4331+ const response = await this . api . fetcher . fetch ( {
4332+ method : "post" ,
4333+ url,
4334+ path : `/api/projects/${ teamId } /sandbox_custom_images/` ,
4335+ overrides : {
4336+ body : JSON . stringify ( input ) ,
4337+ } ,
4338+ } ) ;
4339+ if ( ! response . ok ) {
4340+ const errorData = ( await response . json ( ) . catch ( ( ) => ( { } ) ) ) as {
4341+ detail ?: string ;
4342+ } ;
4343+ throw new Error (
4344+ errorData . detail ??
4345+ `Failed to create sandbox custom image: ${ response . statusText } ` ,
4346+ ) ;
4347+ }
4348+ return ( await response . json ( ) ) as SandboxCustomImage ;
4349+ }
4350+
4351+ async getSandboxCustomImage ( id : string ) : Promise < SandboxCustomImage > {
4352+ const teamId = await this . getTeamId ( ) ;
4353+ const url = new URL (
4354+ `${ this . api . baseUrl } /api/projects/${ teamId } /sandbox_custom_images/${ id } /` ,
4355+ ) ;
4356+ const response = await this . api . fetcher . fetch ( {
4357+ method : "get" ,
4358+ url,
4359+ path : `/api/projects/${ teamId } /sandbox_custom_images/${ id } /` ,
4360+ } ) ;
4361+ if ( ! response . ok ) {
4362+ throw new Error (
4363+ `Failed to fetch sandbox custom image: ${ response . statusText } ` ,
4364+ ) ;
4365+ }
4366+ return ( await response . json ( ) ) as SandboxCustomImage ;
4367+ }
4368+
4369+ async ensureSandboxCustomImageBuilderTask (
4370+ id : string ,
4371+ ) : Promise < SandboxCustomImage > {
4372+ const teamId = await this . getTeamId ( ) ;
4373+ const url = new URL (
4374+ `${ this . api . baseUrl } /api/projects/${ teamId } /sandbox_custom_images/${ id } /builder_task/` ,
4375+ ) ;
4376+ const response = await this . api . fetcher . fetch ( {
4377+ method : "post" ,
4378+ url,
4379+ path : `/api/projects/${ teamId } /sandbox_custom_images/${ id } /builder_task/` ,
4380+ overrides : {
4381+ body : JSON . stringify ( { } ) ,
4382+ } ,
4383+ } ) ;
4384+ if ( ! response . ok ) {
4385+ const errorData = ( await response . json ( ) . catch ( ( ) => ( { } ) ) ) as {
4386+ detail ?: string ;
4387+ } ;
4388+ throw new Error (
4389+ errorData . detail ??
4390+ `Failed to open image builder session: ${ response . statusText } ` ,
4391+ ) ;
4392+ }
4393+ return ( await response . json ( ) ) as SandboxCustomImage ;
4394+ }
4395+
4396+ async buildSandboxCustomImage (
4397+ id : string ,
4398+ specYaml ?: string | null ,
4399+ ) : Promise < SandboxCustomImage > {
4400+ const teamId = await this . getTeamId ( ) ;
4401+ const url = new URL (
4402+ `${ this . api . baseUrl } /api/projects/${ teamId } /sandbox_custom_images/${ id } /build/` ,
4403+ ) ;
4404+ const response = await this . api . fetcher . fetch ( {
4405+ method : "post" ,
4406+ url,
4407+ path : `/api/projects/${ teamId } /sandbox_custom_images/${ id } /build/` ,
4408+ overrides : {
4409+ body : JSON . stringify (
4410+ specYaml === undefined ? { } : { spec_yaml : specYaml } ,
4411+ ) ,
4412+ } ,
4413+ } ) ;
4414+ if ( ! response . ok ) {
4415+ const errorData = ( await response . json ( ) . catch ( ( ) => ( { } ) ) ) as {
4416+ detail ?: string ;
4417+ } ;
4418+ throw new Error (
4419+ errorData . detail ??
4420+ `Failed to build sandbox custom image: ${ response . statusText } ` ,
4421+ ) ;
4422+ }
4423+ return ( await response . json ( ) ) as SandboxCustomImage ;
4424+ }
4425+
4426+ async deleteSandboxCustomImage ( id : string ) : Promise < void > {
4427+ const teamId = await this . getTeamId ( ) ;
4428+ const url = new URL (
4429+ `${ this . api . baseUrl } /api/projects/${ teamId } /sandbox_custom_images/${ id } /` ,
4430+ ) ;
4431+ const response = await this . api . fetcher . fetch ( {
4432+ method : "delete" ,
4433+ url,
4434+ path : `/api/projects/${ teamId } /sandbox_custom_images/${ id } /` ,
4435+ } ) ;
4436+ if ( ! response . ok ) {
4437+ throw new Error (
4438+ `Failed to delete sandbox custom image: ${ response . statusText } ` ,
4439+ ) ;
4440+ }
4441+ }
4442+
42824443 /** Find an exported asset by session recording ID. */
42834444 async findExportBySessionRecordingId (
42844445 projectId : number ,
0 commit comments