@@ -54,6 +54,7 @@ import type {
5454 PriorityJudgmentArtefact ,
5555 RepoSelectionArtefact ,
5656 SafetyJudgmentArtefact ,
57+ SandboxCustomImage ,
5758 SandboxEnvironment ,
5859 SandboxEnvironmentInput ,
5960 Signal ,
@@ -122,6 +123,13 @@ export class SeatPaymentFailedError extends Error {
122123 }
123124}
124125
126+ export class SandboxCustomImagesDisabledError extends Error {
127+ constructor ( message ?: string ) {
128+ super ( message ?? "Custom sandbox images are not enabled" ) ;
129+ this . name = "SandboxCustomImagesDisabledError" ;
130+ }
131+ }
132+
125133export type UsageLimitType = "burst" | "sustained" | null ;
126134
127135// Stable message so callers recognize this after a saga reduces the error to a string.
@@ -478,6 +486,7 @@ interface CloudRunOptions {
478486 model ?: string ;
479487 reasoningLevel ?: string ;
480488 sandboxEnvironmentId ?: string ;
489+ customImageId ?: string ;
481490 prAuthorshipMode ?: PrAuthorshipMode ;
482491 autoPublish ?: boolean ;
483492 runSource ?: CloudRunSource ;
@@ -550,6 +559,9 @@ function buildCloudRunRequestBody(
550559 if ( options ?. sandboxEnvironmentId ) {
551560 body . sandbox_environment_id = options . sandboxEnvironmentId ;
552561 }
562+ if ( options ?. customImageId ) {
563+ body . custom_image_id = options . customImageId ;
564+ }
553565 if ( options ?. prAuthorshipMode ) {
554566 body . pr_authorship_mode = options . prAuthorshipMode ;
555567 }
@@ -4463,6 +4475,155 @@ export class PostHogAPIClient {
44634475 }
44644476 }
44654477
4478+ async listSandboxCustomImages ( ) : Promise < SandboxCustomImage [ ] > {
4479+ const teamId = await this . getTeamId ( ) ;
4480+ const url = new URL (
4481+ `${ this . api . baseUrl } /api/projects/${ teamId } /sandbox_custom_images/` ,
4482+ ) ;
4483+ const response = await this . api . fetcher . fetch ( {
4484+ method : "get" ,
4485+ url,
4486+ path : `/api/projects/${ teamId } /sandbox_custom_images/` ,
4487+ } ) ;
4488+ if ( ! response . ok ) {
4489+ if ( response . status === 403 ) {
4490+ const errorData = ( await response . json ( ) . catch ( ( ) => ( { } ) ) ) as {
4491+ detail ?: string ;
4492+ } ;
4493+ throw new SandboxCustomImagesDisabledError ( errorData . detail ) ;
4494+ }
4495+ throw new Error (
4496+ `Failed to fetch sandbox custom images: ${ response . statusText } ` ,
4497+ ) ;
4498+ }
4499+ const data = ( await response . json ( ) ) as {
4500+ results ?: SandboxCustomImage [ ] ;
4501+ } ;
4502+ return data . results ?? [ ] ;
4503+ }
4504+
4505+ async createSandboxCustomImage ( input : {
4506+ name : string ;
4507+ description ?: string ;
4508+ repository ?: string | null ;
4509+ private ?: boolean ;
4510+ } ) : Promise < SandboxCustomImage > {
4511+ const teamId = await this . getTeamId ( ) ;
4512+ const url = new URL (
4513+ `${ this . api . baseUrl } /api/projects/${ teamId } /sandbox_custom_images/` ,
4514+ ) ;
4515+ const response = await this . api . fetcher . fetch ( {
4516+ method : "post" ,
4517+ url,
4518+ path : `/api/projects/${ teamId } /sandbox_custom_images/` ,
4519+ overrides : {
4520+ body : JSON . stringify ( input ) ,
4521+ } ,
4522+ } ) ;
4523+ if ( ! response . ok ) {
4524+ const errorData = ( await response . json ( ) . catch ( ( ) => ( { } ) ) ) as {
4525+ detail ?: string ;
4526+ } ;
4527+ throw new Error (
4528+ errorData . detail ??
4529+ `Failed to create sandbox custom image: ${ response . statusText } ` ,
4530+ ) ;
4531+ }
4532+ return ( await response . json ( ) ) as SandboxCustomImage ;
4533+ }
4534+
4535+ async getSandboxCustomImage ( id : string ) : Promise < SandboxCustomImage > {
4536+ const teamId = await this . getTeamId ( ) ;
4537+ const url = new URL (
4538+ `${ this . api . baseUrl } /api/projects/${ teamId } /sandbox_custom_images/${ id } /` ,
4539+ ) ;
4540+ const response = await this . api . fetcher . fetch ( {
4541+ method : "get" ,
4542+ url,
4543+ path : `/api/projects/${ teamId } /sandbox_custom_images/${ id } /` ,
4544+ } ) ;
4545+ if ( ! response . ok ) {
4546+ throw new Error (
4547+ `Failed to fetch sandbox custom image: ${ response . statusText } ` ,
4548+ ) ;
4549+ }
4550+ return ( await response . json ( ) ) as SandboxCustomImage ;
4551+ }
4552+
4553+ async ensureSandboxCustomImageBuilderTask (
4554+ id : string ,
4555+ ) : Promise < SandboxCustomImage > {
4556+ const teamId = await this . getTeamId ( ) ;
4557+ const url = new URL (
4558+ `${ this . api . baseUrl } /api/projects/${ teamId } /sandbox_custom_images/${ id } /builder_task/` ,
4559+ ) ;
4560+ const response = await this . api . fetcher . fetch ( {
4561+ method : "post" ,
4562+ url,
4563+ path : `/api/projects/${ teamId } /sandbox_custom_images/${ id } /builder_task/` ,
4564+ overrides : {
4565+ body : JSON . stringify ( { } ) ,
4566+ } ,
4567+ } ) ;
4568+ if ( ! response . ok ) {
4569+ const errorData = ( await response . json ( ) . catch ( ( ) => ( { } ) ) ) as {
4570+ detail ?: string ;
4571+ } ;
4572+ throw new Error (
4573+ errorData . detail ??
4574+ `Failed to open image builder session: ${ response . statusText } ` ,
4575+ ) ;
4576+ }
4577+ return ( await response . json ( ) ) as SandboxCustomImage ;
4578+ }
4579+
4580+ async buildSandboxCustomImage (
4581+ id : string ,
4582+ specYaml ?: string | null ,
4583+ ) : Promise < SandboxCustomImage > {
4584+ const teamId = await this . getTeamId ( ) ;
4585+ const url = new URL (
4586+ `${ this . api . baseUrl } /api/projects/${ teamId } /sandbox_custom_images/${ id } /build/` ,
4587+ ) ;
4588+ const response = await this . api . fetcher . fetch ( {
4589+ method : "post" ,
4590+ url,
4591+ path : `/api/projects/${ teamId } /sandbox_custom_images/${ id } /build/` ,
4592+ overrides : {
4593+ body : JSON . stringify (
4594+ specYaml === undefined ? { } : { spec_yaml : specYaml } ,
4595+ ) ,
4596+ } ,
4597+ } ) ;
4598+ if ( ! response . ok ) {
4599+ const errorData = ( await response . json ( ) . catch ( ( ) => ( { } ) ) ) as {
4600+ detail ?: string ;
4601+ } ;
4602+ throw new Error (
4603+ errorData . detail ??
4604+ `Failed to build sandbox custom image: ${ response . statusText } ` ,
4605+ ) ;
4606+ }
4607+ return ( await response . json ( ) ) as SandboxCustomImage ;
4608+ }
4609+
4610+ async deleteSandboxCustomImage ( id : string ) : Promise < void > {
4611+ const teamId = await this . getTeamId ( ) ;
4612+ const url = new URL (
4613+ `${ this . api . baseUrl } /api/projects/${ teamId } /sandbox_custom_images/${ id } /` ,
4614+ ) ;
4615+ const response = await this . api . fetcher . fetch ( {
4616+ method : "delete" ,
4617+ url,
4618+ path : `/api/projects/${ teamId } /sandbox_custom_images/${ id } /` ,
4619+ } ) ;
4620+ if ( ! response . ok ) {
4621+ throw new Error (
4622+ `Failed to delete sandbox custom image: ${ response . statusText } ` ,
4623+ ) ;
4624+ }
4625+ }
4626+
44664627 /** Find an exported asset by session recording ID. */
44674628 async findExportBySessionRecordingId (
44684629 projectId : number ,
0 commit comments