11import { ComputeClient , stripImageDigest } from "@internal/compute" ;
2+ import type { TemplateCreateResultEntry } from "@internal/compute" ;
3+ import { MachinePresetName } from "@trigger.dev/core/v3" ;
24import { machinePresetFromName } from "~/v3/machinePresets.server" ;
35import { env } from "~/env.server" ;
46import { logger } from "~/services/logger.server" ;
@@ -10,8 +12,16 @@ import { resolveComputeAccess } from "../regionAccess.server";
1012
1113type TemplateCreationMode = "required" | "shadow" | "skip" ;
1214
15+ type ResolvedPreset = {
16+ name : MachinePresetName ;
17+ cpu : number ;
18+ memory_gb : number ;
19+ } ;
20+
1321export class ComputeTemplateCreationService {
1422 private client : ComputeClient | undefined ;
23+ private presets : ResolvedPreset [ ] ;
24+ private requiredPresets : Set < MachinePresetName > ;
1525
1626 constructor ( ) {
1727 if ( env . COMPUTE_GATEWAY_URL ) {
@@ -21,6 +31,12 @@ export class ComputeTemplateCreationService {
2131 timeoutMs : 5 * 60 * 1000 , // 5 minutes
2232 } ) ;
2333 }
34+
35+ this . presets = env . COMPUTE_TEMPLATE_MACHINE_PRESETS . map ( ( name ) => {
36+ const machine = machinePresetFromName ( name ) ;
37+ return { name, cpu : machine . cpu , memory_gb : machine . memory } ;
38+ } ) ;
39+ this . requiredPresets = new Set ( env . COMPUTE_TEMPLATE_MACHINE_PRESETS_REQUIRED ) ;
2440 }
2541
2642 /**
@@ -48,12 +64,12 @@ export class ComputeTemplateCreationService {
4864
4965 if ( mode === "shadow" ) {
5066 this . createTemplate ( options . imageReference , { background : true } )
51- . then ( ( result ) => {
52- if ( ! result . success ) {
67+ . then ( ( outcome ) => {
68+ if ( outcome . error ) {
5369 logger . error ( "Shadow template creation failed" , {
5470 id : options . deploymentFriendlyId ,
5571 imageReference : options . imageReference ,
56- error : result . error ,
72+ error : outcome . error ,
5773 } ) ;
5874 }
5975 } )
@@ -81,31 +97,39 @@ export class ComputeTemplateCreationService {
8197 logger . info ( "Creating compute template (required mode)" , {
8298 id : options . deploymentFriendlyId ,
8399 imageReference : options . imageReference ,
100+ presets : this . presets . map ( ( p ) => p . name ) ,
101+ requiredPresets : [ ...this . requiredPresets ] ,
84102 } ) ;
85103
86- const result = await this . createTemplate ( options . imageReference ) ;
104+ const outcome = await this . createTemplate ( options . imageReference ) ;
105+ const failureMessage = this . failureMessageForRequiredMode (
106+ outcome ,
107+ options . deploymentFriendlyId ,
108+ options . imageReference
109+ ) ;
87110
88- if ( ! result . success ) {
111+ if ( failureMessage ) {
89112 logger . error ( "Compute template creation failed" , {
90113 id : options . deploymentFriendlyId ,
91114 imageReference : options . imageReference ,
92- error : result . error ,
115+ error : failureMessage ,
93116 } ) ;
94117
95118 const failService = new FailDeploymentService ( ) ;
96119 await failService . call ( options . authenticatedEnv , options . deploymentFriendlyId , {
97120 error : {
98121 name : "TemplateCreationFailed" ,
99- message : `Failed to create compute template: ${ result . error } ` ,
122+ message : `Failed to create compute template: ${ failureMessage } ` ,
100123 } ,
101124 } ) ;
102125
103- throw new ServiceValidationError ( `Compute template creation failed: ${ result . error } ` ) ;
126+ throw new ServiceValidationError ( `Compute template creation failed: ${ failureMessage } ` ) ;
104127 }
105128
106129 logger . info ( "Compute template created" , {
107130 id : options . deploymentFriendlyId ,
108131 imageReference : options . imageReference ,
132+ results : outcome . results . length ,
109133 } ) ;
110134 }
111135
@@ -154,29 +178,104 @@ export class ComputeTemplateCreationService {
154178 async createTemplate (
155179 imageReference : string ,
156180 options ?: { background ?: boolean }
157- ) : Promise < { success : boolean ; error ?: string } > {
181+ ) : Promise < CreateTemplateOutcome > {
158182 if ( ! this . client ) {
159- return { success : false , error : "Compute gateway not configured" } ;
183+ return { error : "Compute gateway not configured" , results : [ ] } ;
160184 }
161185
162186 try {
163- // Templates are resource-agnostic - these values don't affect template content.
164- const machine = machinePresetFromName ( "small-1x" ) ;
187+ const machineConfigs = this . presets . map ( ( p ) => ( {
188+ cpu : p . cpu ,
189+ memory_gb : p . memory_gb ,
190+ } ) ) ;
165191
166- await this . client . templates . create ( {
192+ const response = await this . client . templates . create ( {
167193 image : stripImageDigest ( imageReference ) ,
168- cpu : machine . cpu ,
169- memory_gb : machine . memory ,
194+ machine_configs : machineConfigs ,
170195 background : options ?. background ,
171196 } ) ;
172- return { success : true } ;
197+
198+ // Background mode (202 Accepted): no body to inspect.
199+ if ( options ?. background || ! response ) {
200+ return { results : [ ] } ;
201+ }
202+
203+ return {
204+ error : response . error ,
205+ results : response . results ,
206+ } ;
173207 } catch ( error ) {
174208 const message = error instanceof Error ? error . message : "Unknown error" ;
175209 logger . error ( "Failed to create compute template" , {
176210 imageReference,
177211 error : message ,
178212 } ) ;
179- return { success : false , error : message } ;
213+ return { error : message , results : [ ] } ;
214+ }
215+ }
216+
217+ // Returns a human-readable failure message if any required preset failed
218+ // or the request itself failed. Optional preset failures are logged and
219+ // do not contribute to the message. Returns undefined on success.
220+ private failureMessageForRequiredMode (
221+ outcome : CreateTemplateOutcome ,
222+ deploymentFriendlyId : string ,
223+ imageReference : string
224+ ) : string | undefined {
225+ if ( this . presets . length === 0 ) {
226+ return undefined ;
227+ }
228+
229+ const failures : string [ ] = [ ] ;
230+
231+ this . presets . forEach ( ( preset ) => {
232+ const isRequired = this . requiredPresets . has ( preset . name ) ;
233+ // Match results to presets by (cpu, memory_gb) content with a small
234+ // epsilon to tolerate float round-trip noise (memory_gb passes through
235+ // gb -> mb -> gb conversion in the compute layer).
236+ const result = outcome . results . find (
237+ ( r ) =>
238+ Math . abs ( r . machine_config . cpu - preset . cpu ) < 1e-9 &&
239+ Math . abs ( r . machine_config . memory_gb - preset . memory_gb ) < 1e-9
240+ ) ;
241+
242+ if ( ! result ) {
243+ if ( isRequired ) {
244+ failures . push ( `${ preset . name } : not built` ) ;
245+ } else {
246+ logger . warn ( "Optional compute template preset not built" , {
247+ id : deploymentFriendlyId ,
248+ imageReference,
249+ preset : preset . name ,
250+ } ) ;
251+ }
252+ return ;
253+ }
254+
255+ if ( result . error ) {
256+ if ( isRequired ) {
257+ failures . push ( `${ preset . name } : ${ result . error } ` ) ;
258+ } else {
259+ logger . warn ( "Optional compute template preset failed" , {
260+ id : deploymentFriendlyId ,
261+ imageReference,
262+ preset : preset . name ,
263+ error : result . error ,
264+ } ) ;
265+ }
266+ }
267+ } ) ;
268+
269+ // Surface request-level errors only when no per-preset failure attributed.
270+ if ( outcome . error && failures . length === 0 ) {
271+ failures . push ( outcome . error ) ;
180272 }
273+
274+ return failures . length > 0 ? failures . join ( "; " ) : undefined ;
181275 }
182276}
277+
278+ type CreateTemplateOutcome = {
279+ error ?: string ;
280+ results : TemplateCreateResultEntry [ ] ;
281+ } ;
0 commit comments