@@ -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 }
@@ -4402,6 +4414,155 @@ export class PostHogAPIClient {
44024414 }
44034415 }
44044416
4417+ async listSandboxCustomImages ( ) : Promise < SandboxCustomImage [ ] > {
4418+ const teamId = await this . getTeamId ( ) ;
4419+ const url = new URL (
4420+ `${ this . api . baseUrl } /api/projects/${ teamId } /sandbox_custom_images/` ,
4421+ ) ;
4422+ const response = await this . api . fetcher . fetch ( {
4423+ method : "get" ,
4424+ url,
4425+ path : `/api/projects/${ teamId } /sandbox_custom_images/` ,
4426+ } ) ;
4427+ if ( ! response . ok ) {
4428+ if ( response . status === 403 ) {
4429+ const errorData = ( await response . json ( ) . catch ( ( ) => ( { } ) ) ) as {
4430+ detail ?: string ;
4431+ } ;
4432+ throw new SandboxCustomImagesDisabledError ( errorData . detail ) ;
4433+ }
4434+ throw new Error (
4435+ `Failed to fetch sandbox custom images: ${ response . statusText } ` ,
4436+ ) ;
4437+ }
4438+ const data = ( await response . json ( ) ) as {
4439+ results ?: SandboxCustomImage [ ] ;
4440+ } ;
4441+ return data . results ?? [ ] ;
4442+ }
4443+
4444+ async createSandboxCustomImage ( input : {
4445+ name : string ;
4446+ description ?: string ;
4447+ repository ?: string | null ;
4448+ private ?: boolean ;
4449+ } ) : Promise < SandboxCustomImage > {
4450+ const teamId = await this . getTeamId ( ) ;
4451+ const url = new URL (
4452+ `${ this . api . baseUrl } /api/projects/${ teamId } /sandbox_custom_images/` ,
4453+ ) ;
4454+ const response = await this . api . fetcher . fetch ( {
4455+ method : "post" ,
4456+ url,
4457+ path : `/api/projects/${ teamId } /sandbox_custom_images/` ,
4458+ overrides : {
4459+ body : JSON . stringify ( input ) ,
4460+ } ,
4461+ } ) ;
4462+ if ( ! response . ok ) {
4463+ const errorData = ( await response . json ( ) . catch ( ( ) => ( { } ) ) ) as {
4464+ detail ?: string ;
4465+ } ;
4466+ throw new Error (
4467+ errorData . detail ??
4468+ `Failed to create sandbox custom image: ${ response . statusText } ` ,
4469+ ) ;
4470+ }
4471+ return ( await response . json ( ) ) as SandboxCustomImage ;
4472+ }
4473+
4474+ async getSandboxCustomImage ( id : string ) : Promise < SandboxCustomImage > {
4475+ const teamId = await this . getTeamId ( ) ;
4476+ const url = new URL (
4477+ `${ this . api . baseUrl } /api/projects/${ teamId } /sandbox_custom_images/${ id } /` ,
4478+ ) ;
4479+ const response = await this . api . fetcher . fetch ( {
4480+ method : "get" ,
4481+ url,
4482+ path : `/api/projects/${ teamId } /sandbox_custom_images/${ id } /` ,
4483+ } ) ;
4484+ if ( ! response . ok ) {
4485+ throw new Error (
4486+ `Failed to fetch sandbox custom image: ${ response . statusText } ` ,
4487+ ) ;
4488+ }
4489+ return ( await response . json ( ) ) as SandboxCustomImage ;
4490+ }
4491+
4492+ async ensureSandboxCustomImageBuilderTask (
4493+ id : string ,
4494+ ) : Promise < SandboxCustomImage > {
4495+ const teamId = await this . getTeamId ( ) ;
4496+ const url = new URL (
4497+ `${ this . api . baseUrl } /api/projects/${ teamId } /sandbox_custom_images/${ id } /builder_task/` ,
4498+ ) ;
4499+ const response = await this . api . fetcher . fetch ( {
4500+ method : "post" ,
4501+ url,
4502+ path : `/api/projects/${ teamId } /sandbox_custom_images/${ id } /builder_task/` ,
4503+ overrides : {
4504+ body : JSON . stringify ( { } ) ,
4505+ } ,
4506+ } ) ;
4507+ if ( ! response . ok ) {
4508+ const errorData = ( await response . json ( ) . catch ( ( ) => ( { } ) ) ) as {
4509+ detail ?: string ;
4510+ } ;
4511+ throw new Error (
4512+ errorData . detail ??
4513+ `Failed to open image builder session: ${ response . statusText } ` ,
4514+ ) ;
4515+ }
4516+ return ( await response . json ( ) ) as SandboxCustomImage ;
4517+ }
4518+
4519+ async buildSandboxCustomImage (
4520+ id : string ,
4521+ specYaml ?: string | null ,
4522+ ) : Promise < SandboxCustomImage > {
4523+ const teamId = await this . getTeamId ( ) ;
4524+ const url = new URL (
4525+ `${ this . api . baseUrl } /api/projects/${ teamId } /sandbox_custom_images/${ id } /build/` ,
4526+ ) ;
4527+ const response = await this . api . fetcher . fetch ( {
4528+ method : "post" ,
4529+ url,
4530+ path : `/api/projects/${ teamId } /sandbox_custom_images/${ id } /build/` ,
4531+ overrides : {
4532+ body : JSON . stringify (
4533+ specYaml === undefined ? { } : { spec_yaml : specYaml } ,
4534+ ) ,
4535+ } ,
4536+ } ) ;
4537+ if ( ! response . ok ) {
4538+ const errorData = ( await response . json ( ) . catch ( ( ) => ( { } ) ) ) as {
4539+ detail ?: string ;
4540+ } ;
4541+ throw new Error (
4542+ errorData . detail ??
4543+ `Failed to build sandbox custom image: ${ response . statusText } ` ,
4544+ ) ;
4545+ }
4546+ return ( await response . json ( ) ) as SandboxCustomImage ;
4547+ }
4548+
4549+ async deleteSandboxCustomImage ( id : string ) : Promise < void > {
4550+ const teamId = await this . getTeamId ( ) ;
4551+ const url = new URL (
4552+ `${ this . api . baseUrl } /api/projects/${ teamId } /sandbox_custom_images/${ id } /` ,
4553+ ) ;
4554+ const response = await this . api . fetcher . fetch ( {
4555+ method : "delete" ,
4556+ url,
4557+ path : `/api/projects/${ teamId } /sandbox_custom_images/${ id } /` ,
4558+ } ) ;
4559+ if ( ! response . ok ) {
4560+ throw new Error (
4561+ `Failed to delete sandbox custom image: ${ response . statusText } ` ,
4562+ ) ;
4563+ }
4564+ }
4565+
44054566 /** Find an exported asset by session recording ID. */
44064567 async findExportBySessionRecordingId (
44074568 projectId : number ,
0 commit comments