1515 */
1616import { join , relative , resolve as pathResolve , sep } from 'node:path' ;
1717import { format } from 'node:util' ;
18+ import { EOL } from 'node:os' ;
1819import { isString } from '@salesforce/ts-types' ;
1920import JSZip from 'jszip' ;
2021import fs from 'graceful-fs' ;
@@ -23,10 +24,10 @@ import { Messages } from '@salesforce/core/messages';
2324import { SfError } from '@salesforce/core/sfError' ;
2425import { envVars } from '@salesforce/core/envVars' ;
2526import { ensureArray } from '@salesforce/kit' ;
26- import { RegistryAccess } from '../registry/registryAccess ' ;
27+ import { RegistryAccess } from '../registry' ;
2728import { ReplacementEvent } from '../convert/types' ;
28- import { MetadataConverter } from '../convert/metadataConverter ' ;
29- import { ComponentSet } from '../collections/componentSet ' ;
29+ import { MetadataConverter } from '../convert' ;
30+ import { ComponentSet } from '../collections' ;
3031import { MetadataTransfer , MetadataTransferOptions } from './metadataTransfer' ;
3132import {
3233 AsyncResult ,
@@ -204,7 +205,116 @@ export class MetadataApiDeploy extends MetadataTransfer<
204205 // this is used as the version in the manifest (package.xml).
205206 this . components . sourceApiVersion ??= apiVersion ;
206207 }
208+ if ( this . options . components ) {
209+ // we must ensure AiAuthoringBundles compile before deployment
210+ // Use optimized getter method instead of filtering all components
211+ const aabComponents = this . options . components . getAiAuthoringBundles ( ) . toArray ( ) ;
212+
213+ if ( aabComponents . length > 0 ) {
214+ // we need to use a namedJWT connection for this request
215+ const { accessToken, instanceUrl } = connection . getConnectionOptions ( ) ;
216+ if ( ! instanceUrl ) {
217+ throw SfError . create ( {
218+ name : 'ApiAccessError' ,
219+ message : 'Missing Instance URL for org connection' ,
220+ } ) ;
221+ }
222+ if ( ! accessToken ) {
223+ throw SfError . create ( {
224+ name : 'ApiAccessError' ,
225+ message : 'Missing Access Token for org connection' ,
226+ } ) ;
227+ }
228+ const url = `${ instanceUrl } /agentforce/bootstrap/nameduser` ;
229+ // For the namdeduser endpoint request to work we need to delete the access token
230+ delete connection . accessToken ;
231+ const response = await connection . request < {
232+ access_token : string ;
233+ } > (
234+ {
235+ method : 'GET' ,
236+ url,
237+ headers : {
238+ 'Content-Type' : 'application/json' ,
239+ Cookie : `sid=${ accessToken } ` ,
240+ } ,
241+ } ,
242+ { retry : { maxRetries : 3 } }
243+ ) ;
244+ connection . accessToken = response . access_token ;
245+ const results = await Promise . all (
246+ aabComponents . map ( async ( aab ) => {
247+ // aab.content points to a directory, we need to find the .agent file and read it
248+ if ( ! aab . content ) {
249+ throw new SfError (
250+ messages . getMessage ( 'error_expected_source_files' , [ aab . fullName , 'aiauthoringbundle' ] ) ,
251+ 'ExpectedSourceFilesError'
252+ ) ;
253+ }
254+
255+ const contentPath = aab . tree . find ( 'content' , aab . name , aab . content ) ;
256+
257+ if ( ! contentPath ) {
258+ // if this didn't exist, they'll have deploy issues anyways, but we can check here for type reasons
259+ throw new SfError ( `No .agent file found in directory: ${ aab . content } ` , 'MissingAgentFileError' ) ;
260+ }
261+
262+ const agentContent = await fs . promises . readFile ( contentPath , 'utf-8' ) ;
263+
264+ // to avoid circular dependencies between libraries, just call the compile endpoint here
265+ const result = await connection . request < {
266+ // minimal typings here, more is returned, just using what we need
267+ status : 'failure' | 'success' ;
268+ errors : Array < {
269+ description : string ;
270+ lineStart : number ;
271+ colStart : number ;
272+ } > ;
273+ // name added here for post-processing convenience
274+ name : string ;
275+ } > ( {
276+ method : 'POST' ,
277+ // this will need to be api.salesforce once changes are in prod
278+ url : 'https://test.api.salesforce.com/einstein/ai-agent/v1.1/authoring/scripts' ,
279+ headers : {
280+ 'x-client-name' : 'afdx' ,
281+ 'content-type' : 'application/json' ,
282+ } ,
283+ body : JSON . stringify ( {
284+ assets : [
285+ {
286+ type : 'AFScript' ,
287+ name : 'AFScript' ,
288+ content : agentContent ,
289+ } ,
290+ ] ,
291+ afScriptVersion : '1.0.1' ,
292+ } ) ,
293+ } ) ;
294+ result . name = aab . name ;
295+ return result ;
296+ } )
297+ ) ;
207298
299+ const errors = results
300+ . filter ( ( result ) => result . status === 'failure' )
301+ . map ( ( result ) =>
302+ result . errors . map ( ( r ) => `${ result . name } .agent: ${ r . description } ${ r . lineStart } :${ r . colStart } ` ) . join ( EOL )
303+ ) ;
304+
305+ if ( errors . length > 0 ) {
306+ throw SfError . create ( {
307+ message : `${ EOL } ${ errors . join ( EOL ) } ` ,
308+ name : 'AgentCompilationError' ,
309+ } ) ;
310+ } else {
311+ // everything successfully compiled
312+ // stop using named user jwt access token
313+ delete connection . accessToken ;
314+ await connection . refreshAuth ( ) ;
315+ }
316+ }
317+ }
208318 // only do event hooks if source, (NOT a metadata format) deploy
209319 if ( this . options . components ) {
210320 await LifecycleInstance . emit ( 'scopedPreDeploy' , {
0 commit comments