@@ -52,6 +52,7 @@ import type {
5252 PriorityJudgmentArtefact ,
5353 RepoSelectionArtefact ,
5454 SafetyJudgmentArtefact ,
55+ SandboxCustomImage ,
5556 SandboxEnvironment ,
5657 SandboxEnvironmentInput ,
5758 Signal ,
@@ -119,6 +120,13 @@ export class SeatPaymentFailedError extends Error {
119120 }
120121}
121122
123+ export class SandboxCustomImagesDisabledError extends Error {
124+ constructor ( message ?: string ) {
125+ super ( message ?? "Custom sandbox images are not enabled" ) ;
126+ this . name = "SandboxCustomImagesDisabledError" ;
127+ }
128+ }
129+
122130export type UsageLimitType = "burst" | "sustained" | null ;
123131
124132// Stable message so callers recognize this after a saga reduces the error to a string.
@@ -477,6 +485,7 @@ interface CloudRunOptions {
477485 model ?: string ;
478486 reasoningLevel ?: string ;
479487 sandboxEnvironmentId ?: string ;
488+ customImageId ?: string ;
480489 prAuthorshipMode ?: PrAuthorshipMode ;
481490 runSource ?: CloudRunSource ;
482491 signalReportId ?: string ;
@@ -548,6 +557,9 @@ function buildCloudRunRequestBody(
548557 if ( options ?. sandboxEnvironmentId ) {
549558 body . sandbox_environment_id = options . sandboxEnvironmentId ;
550559 }
560+ if ( options ?. customImageId ) {
561+ body . custom_image_id = options . customImageId ;
562+ }
551563 if ( options ?. prAuthorshipMode ) {
552564 body . pr_authorship_mode = options . prAuthorshipMode ;
553565 }
@@ -4404,6 +4416,155 @@ export class PostHogAPIClient {
44044416 }
44054417 }
44064418
4419+ async listSandboxCustomImages ( ) : Promise < SandboxCustomImage [ ] > {
4420+ const teamId = await this . getTeamId ( ) ;
4421+ const url = new URL (
4422+ `${ this . api . baseUrl } /api/projects/${ teamId } /sandbox_custom_images/` ,
4423+ ) ;
4424+ const response = await this . api . fetcher . fetch ( {
4425+ method : "get" ,
4426+ url,
4427+ path : `/api/projects/${ teamId } /sandbox_custom_images/` ,
4428+ } ) ;
4429+ if ( ! response . ok ) {
4430+ if ( response . status === 403 ) {
4431+ const errorData = ( await response . json ( ) . catch ( ( ) => ( { } ) ) ) as {
4432+ detail ?: string ;
4433+ } ;
4434+ throw new SandboxCustomImagesDisabledError ( errorData . detail ) ;
4435+ }
4436+ throw new Error (
4437+ `Failed to fetch sandbox custom images: ${ response . statusText } ` ,
4438+ ) ;
4439+ }
4440+ const data = ( await response . json ( ) ) as {
4441+ results ?: SandboxCustomImage [ ] ;
4442+ } ;
4443+ return data . results ?? [ ] ;
4444+ }
4445+
4446+ async createSandboxCustomImage ( input : {
4447+ name : string ;
4448+ description ?: string ;
4449+ repository ?: string | null ;
4450+ private ?: boolean ;
4451+ } ) : Promise < SandboxCustomImage > {
4452+ const teamId = await this . getTeamId ( ) ;
4453+ const url = new URL (
4454+ `${ this . api . baseUrl } /api/projects/${ teamId } /sandbox_custom_images/` ,
4455+ ) ;
4456+ const response = await this . api . fetcher . fetch ( {
4457+ method : "post" ,
4458+ url,
4459+ path : `/api/projects/${ teamId } /sandbox_custom_images/` ,
4460+ overrides : {
4461+ body : JSON . stringify ( input ) ,
4462+ } ,
4463+ } ) ;
4464+ if ( ! response . ok ) {
4465+ const errorData = ( await response . json ( ) . catch ( ( ) => ( { } ) ) ) as {
4466+ detail ?: string ;
4467+ } ;
4468+ throw new Error (
4469+ errorData . detail ??
4470+ `Failed to create sandbox custom image: ${ response . statusText } ` ,
4471+ ) ;
4472+ }
4473+ return ( await response . json ( ) ) as SandboxCustomImage ;
4474+ }
4475+
4476+ async getSandboxCustomImage ( id : string ) : Promise < SandboxCustomImage > {
4477+ const teamId = await this . getTeamId ( ) ;
4478+ const url = new URL (
4479+ `${ this . api . baseUrl } /api/projects/${ teamId } /sandbox_custom_images/${ id } /` ,
4480+ ) ;
4481+ const response = await this . api . fetcher . fetch ( {
4482+ method : "get" ,
4483+ url,
4484+ path : `/api/projects/${ teamId } /sandbox_custom_images/${ id } /` ,
4485+ } ) ;
4486+ if ( ! response . ok ) {
4487+ throw new Error (
4488+ `Failed to fetch sandbox custom image: ${ response . statusText } ` ,
4489+ ) ;
4490+ }
4491+ return ( await response . json ( ) ) as SandboxCustomImage ;
4492+ }
4493+
4494+ async ensureSandboxCustomImageBuilderTask (
4495+ id : string ,
4496+ ) : Promise < SandboxCustomImage > {
4497+ const teamId = await this . getTeamId ( ) ;
4498+ const url = new URL (
4499+ `${ this . api . baseUrl } /api/projects/${ teamId } /sandbox_custom_images/${ id } /builder_task/` ,
4500+ ) ;
4501+ const response = await this . api . fetcher . fetch ( {
4502+ method : "post" ,
4503+ url,
4504+ path : `/api/projects/${ teamId } /sandbox_custom_images/${ id } /builder_task/` ,
4505+ overrides : {
4506+ body : JSON . stringify ( { } ) ,
4507+ } ,
4508+ } ) ;
4509+ if ( ! response . ok ) {
4510+ const errorData = ( await response . json ( ) . catch ( ( ) => ( { } ) ) ) as {
4511+ detail ?: string ;
4512+ } ;
4513+ throw new Error (
4514+ errorData . detail ??
4515+ `Failed to open image builder session: ${ response . statusText } ` ,
4516+ ) ;
4517+ }
4518+ return ( await response . json ( ) ) as SandboxCustomImage ;
4519+ }
4520+
4521+ async buildSandboxCustomImage (
4522+ id : string ,
4523+ specYaml ?: string | null ,
4524+ ) : Promise < SandboxCustomImage > {
4525+ const teamId = await this . getTeamId ( ) ;
4526+ const url = new URL (
4527+ `${ this . api . baseUrl } /api/projects/${ teamId } /sandbox_custom_images/${ id } /build/` ,
4528+ ) ;
4529+ const response = await this . api . fetcher . fetch ( {
4530+ method : "post" ,
4531+ url,
4532+ path : `/api/projects/${ teamId } /sandbox_custom_images/${ id } /build/` ,
4533+ overrides : {
4534+ body : JSON . stringify (
4535+ specYaml === undefined ? { } : { spec_yaml : specYaml } ,
4536+ ) ,
4537+ } ,
4538+ } ) ;
4539+ if ( ! response . ok ) {
4540+ const errorData = ( await response . json ( ) . catch ( ( ) => ( { } ) ) ) as {
4541+ detail ?: string ;
4542+ } ;
4543+ throw new Error (
4544+ errorData . detail ??
4545+ `Failed to build sandbox custom image: ${ response . statusText } ` ,
4546+ ) ;
4547+ }
4548+ return ( await response . json ( ) ) as SandboxCustomImage ;
4549+ }
4550+
4551+ async deleteSandboxCustomImage ( id : string ) : Promise < void > {
4552+ const teamId = await this . getTeamId ( ) ;
4553+ const url = new URL (
4554+ `${ this . api . baseUrl } /api/projects/${ teamId } /sandbox_custom_images/${ id } /` ,
4555+ ) ;
4556+ const response = await this . api . fetcher . fetch ( {
4557+ method : "delete" ,
4558+ url,
4559+ path : `/api/projects/${ teamId } /sandbox_custom_images/${ id } /` ,
4560+ } ) ;
4561+ if ( ! response . ok ) {
4562+ throw new Error (
4563+ `Failed to delete sandbox custom image: ${ response . statusText } ` ,
4564+ ) ;
4565+ }
4566+ }
4567+
44074568 /** Find an exported asset by session recording ID. */
44084569 async findExportBySessionRecordingId (
44094570 projectId : number ,
0 commit comments