1
1
import * as YAML from "yaml" ;
2
+ import { ZodError } from "zod" ;
2
3
import { PlatformClient , Registry } from "../interfaces/index.js" ;
3
4
import { encodeSecretLocation } from "../interfaces/SecretResult.js" ;
4
5
import {
@@ -17,6 +18,7 @@ import {
17
18
configYamlSchema ,
18
19
Rule ,
19
20
} from "../schemas/index.js" ;
21
+ import { ConfigResult , ConfigValidationError } from "../validation.js" ;
20
22
import {
21
23
packageIdentifierToShorthandSlug ,
22
24
useProxyForUnrenderedSecrets ,
@@ -30,16 +32,25 @@ export function parseConfigYaml(configYaml: string): ConfigYaml {
30
32
if ( result . success ) {
31
33
return result . data ;
32
34
}
33
- throw new Error (
34
- result . error . errors
35
- . map ( ( e ) => `${ e . path . join ( "." ) } : ${ e . message } ` )
36
- . join ( "" ) ,
37
- ) ;
35
+
36
+ throw new Error ( formatZodError ( result . error ) , {
37
+ cause : "result.success was false" ,
38
+ } ) ;
38
39
} catch ( e ) {
39
- console . log ( "Failed to parse rolled assistant:" , configYaml ) ;
40
- throw new Error (
41
- `Failed to parse assistant:\n${ e instanceof Error ? e . message : e } ` ,
42
- ) ;
40
+ console . error ( "Failed to parse rolled assistant:" , configYaml ) ;
41
+ if (
42
+ e instanceof Error &&
43
+ "cause" in e &&
44
+ e . cause === "result.success was false"
45
+ ) {
46
+ throw new Error ( `Failed to parse assistant: ${ e . message } ` ) ;
47
+ } else if ( e instanceof ZodError ) {
48
+ throw new Error ( `Failed to parse assistant: ${ formatZodError ( e ) } ` ) ;
49
+ } else {
50
+ throw new Error (
51
+ `Failed to parse assistant: ${ e instanceof Error ? e . message : e } ` ,
52
+ ) ;
53
+ }
43
54
}
44
55
}
45
56
@@ -49,9 +60,10 @@ export function parseAssistantUnrolled(configYaml: string): AssistantUnrolled {
49
60
const result = assistantUnrolledSchema . parse ( parsed ) ;
50
61
return result ;
51
62
} catch ( e : any ) {
52
- throw new Error (
63
+ console . error (
53
64
`Failed to parse unrolled assistant: ${ e . message } \n\n${ configYaml } ` ,
54
65
) ;
66
+ throw new Error ( `Failed to parse unrolled assistant: ${ formatZodError ( e ) } ` ) ;
55
67
}
56
68
}
57
69
@@ -61,7 +73,7 @@ export function parseBlock(configYaml: string): Block {
61
73
const result = blockSchema . parse ( parsed ) ;
62
74
return result ;
63
75
} catch ( e : any ) {
64
- throw new Error ( `Failed to parse block: ${ e . message } ` ) ;
76
+ throw new Error ( `Failed to parse block: ${ formatZodError ( e ) } ` ) ;
65
77
}
66
78
}
67
79
@@ -183,6 +195,7 @@ async function extractRenderedSecretsMap(
183
195
export interface BaseUnrollAssistantOptions {
184
196
renderSecrets : boolean ;
185
197
injectBlocks ?: PackageIdentifier [ ] ;
198
+ asConfigResult ?: true ;
186
199
}
187
200
188
201
export interface DoNotRenderSecretsUnrollAssistantOptions
@@ -204,14 +217,30 @@ export type UnrollAssistantOptions =
204
217
| DoNotRenderSecretsUnrollAssistantOptions
205
218
| RenderSecretsUnrollAssistantOptions ;
206
219
220
+ // Overload to satisfy existing consumers of unrollAssistant.
221
+ export async function unrollAssistant (
222
+ id : PackageIdentifier ,
223
+ registry : Registry ,
224
+ options : UnrollAssistantOptions & { asConfigResult : true } ,
225
+ ) : Promise < ConfigResult < AssistantUnrolled > > ;
226
+
207
227
export async function unrollAssistant (
208
228
id : PackageIdentifier ,
209
229
registry : Registry ,
210
230
options : UnrollAssistantOptions ,
211
- ) : Promise < AssistantUnrolled > {
231
+ ) : Promise < AssistantUnrolled > ;
232
+
233
+ export async function unrollAssistant (
234
+ id : PackageIdentifier ,
235
+ registry : Registry ,
236
+ options : UnrollAssistantOptions ,
237
+ ) : Promise < AssistantUnrolled | ConfigResult < AssistantUnrolled > > {
212
238
// Request the content from the registry
213
239
const rawContent = await registry . getContent ( id ) ;
214
- return unrollAssistantFromContent ( id , rawContent , registry , options ) ;
240
+
241
+ const result = unrollAssistantFromContent ( id , rawContent , registry , options ) ;
242
+
243
+ return result ;
215
244
}
216
245
217
246
function renderTemplateData (
@@ -236,7 +265,7 @@ export async function unrollAssistantFromContent(
236
265
rawYaml : string ,
237
266
registry : Registry ,
238
267
options : UnrollAssistantOptions ,
239
- ) : Promise < AssistantUnrolled > {
268
+ ) : Promise < AssistantUnrolled | ConfigResult < AssistantUnrolled > > {
240
269
// Parse string to Zod-validated YAML
241
270
let parsedYaml = parseConfigYaml ( rawYaml ) ;
242
271
@@ -245,10 +274,15 @@ export async function unrollAssistantFromContent(
245
274
parsedYaml ,
246
275
registry ,
247
276
options . injectBlocks ,
277
+ options . asConfigResult ?? false ,
248
278
) ;
249
279
250
280
// Back to a string so we can fill in template variables
251
- const rawUnrolledYaml = YAML . stringify ( unrolledAssistant ) ;
281
+ const rawUnrolledYaml = options . asConfigResult
282
+ ? YAML . stringify (
283
+ ( unrolledAssistant as ConfigResult < AssistantUnrolled > ) . config ,
284
+ )
285
+ : YAML . stringify ( unrolledAssistant ) ;
252
286
253
287
// Convert all of the template variables to FQSNs
254
288
// Secrets from the block will have the assistant slug prepended to the FQSN
@@ -277,14 +311,28 @@ export async function unrollAssistantFromContent(
277
311
options . orgScopeId ,
278
312
options . onPremProxyUrl ,
279
313
) ;
314
+
315
+ if ( options . asConfigResult ) {
316
+ return {
317
+ config : finalConfig ,
318
+ errors : ( unrolledAssistant as ConfigResult < AssistantUnrolled > ) . errors ,
319
+ configLoadInterrupted : (
320
+ unrolledAssistant as ConfigResult < AssistantUnrolled >
321
+ ) . configLoadInterrupted ,
322
+ } ;
323
+ }
324
+
280
325
return finalConfig ;
281
326
}
282
327
283
328
export async function unrollBlocks (
284
329
assistant : ConfigYaml ,
285
330
registry : Registry ,
286
331
injectBlocks : PackageIdentifier [ ] | undefined ,
287
- ) : Promise < AssistantUnrolled > {
332
+ asConfigError : boolean ,
333
+ ) : Promise < AssistantUnrolled | ConfigResult < AssistantUnrolled > > {
334
+ const errors : ConfigValidationError [ ] = [ ] ;
335
+
288
336
const unrolledAssistant : AssistantUnrolled = {
289
337
name : assistant . name ,
290
338
version : assistant . version ,
@@ -316,8 +364,23 @@ export async function unrollBlocks(
316
364
) ;
317
365
}
318
366
} catch ( err ) {
367
+ let msg = "" ;
368
+ if (
369
+ typeof unrolledBlock . uses !== "string" &&
370
+ "filePath" in unrolledBlock . uses
371
+ ) {
372
+ msg = `${ ( err as Error ) . message } .\n> ${ unrolledBlock . uses . filePath } ` ;
373
+ } else {
374
+ msg = `${ ( err as Error ) . message } .\n> ${ JSON . stringify ( unrolledBlock . uses ) } ` ;
375
+ }
376
+
377
+ errors . push ( {
378
+ fatal : false ,
379
+ message : msg ,
380
+ } ) ;
381
+
319
382
console . error (
320
- `Failed to unroll block ${ unrolledBlock . uses } : ${ ( err as Error ) . message } ` ,
383
+ `Failed to unroll block ${ JSON . stringify ( unrolledBlock . uses ) } : ${ ( err as Error ) . message } ` ,
321
384
) ;
322
385
sectionBlocks . push ( null ) ;
323
386
}
@@ -349,6 +412,11 @@ export async function unrollBlocks(
349
412
rules . push ( block ) ;
350
413
}
351
414
} catch ( err ) {
415
+ errors . push ( {
416
+ fatal : false ,
417
+ message : `${ ( err as Error ) . message } :\n${ rule . uses } ` ,
418
+ } ) ;
419
+
352
420
console . error (
353
421
`Failed to unroll block ${ rule . uses } : ${ ( err as Error ) . message } ` ,
354
422
) ;
@@ -381,12 +449,36 @@ export async function unrollBlocks(
381
449
) ;
382
450
}
383
451
} catch ( err ) {
452
+ let msg = "" ;
453
+ if ( injectBlock . uriType === "file" ) {
454
+ msg = `${ ( err as Error ) . message } .\n> ${ injectBlock . filePath } ` ;
455
+ } else {
456
+ msg = `${ ( err as Error ) . message } .\n> ${ injectBlock . fullSlug } ` ;
457
+ }
458
+ errors . push ( {
459
+ fatal : false ,
460
+ message : msg ,
461
+ } ) ;
462
+
384
463
console . error (
385
- `Failed to unroll block ${ injectBlock } : ${ ( err as Error ) . message } ` ,
464
+ `Failed to unroll block ${ JSON . stringify ( injectBlock ) } : ${ ( err as Error ) . message } ` ,
386
465
) ;
387
466
}
388
467
}
389
468
469
+ if ( asConfigError ) {
470
+ const configResult : ConfigResult < AssistantUnrolled > = {
471
+ config : undefined ,
472
+ errors : undefined ,
473
+ configLoadInterrupted : false ,
474
+ } ;
475
+ configResult . config = unrolledAssistant ;
476
+ if ( errors . length > 0 ) {
477
+ configResult . errors = errors ;
478
+ }
479
+ return configResult ;
480
+ }
481
+
390
482
return unrolledAssistant ;
391
483
}
392
484
@@ -439,3 +531,15 @@ export function mergeOverrides<T extends Record<string, any>>(
439
531
}
440
532
return block ;
441
533
}
534
+
535
+ function formatZodError ( error : any ) : string {
536
+ if ( error . errors && Array . isArray ( error . errors ) ) {
537
+ return error . errors
538
+ . map ( ( e : any ) => {
539
+ const path = e . path . length > 0 ? `${ e . path . join ( "." ) } : ` : "" ;
540
+ return `${ path } ${ e . message } ` ;
541
+ } )
542
+ . join ( ", " ) ;
543
+ }
544
+ return error . message || "Validation failed" ;
545
+ }
0 commit comments