@salesforce/plugin-agent 1.32.9 → 1.32.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -126,7 +126,7 @@ EXAMPLES
126
126
  $ sf agent activate --api-name Resort_Manager --version 2 --target-org my-org
127
127
  ```
128
128
 
129
- _See code: [src/commands/agent/activate.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.9/src/commands/agent/activate.ts)_
129
+ _See code: [src/commands/agent/activate.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.11/src/commands/agent/activate.ts)_
130
130
 
131
131
  ## `sf agent create`
132
132
 
@@ -193,7 +193,7 @@ EXAMPLES
193
193
  $ sf agent create --name "Resort Manager" --spec specs/resortManagerAgent.yaml --preview
194
194
  ```
195
195
 
196
- _See code: [src/commands/agent/create.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.9/src/commands/agent/create.ts)_
196
+ _See code: [src/commands/agent/create.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.11/src/commands/agent/create.ts)_
197
197
 
198
198
  ## `sf agent deactivate`
199
199
 
@@ -234,7 +234,7 @@ EXAMPLES
234
234
  $ sf agent deactivate --api-name Resort_Manager --target-org my-org
235
235
  ```
236
236
 
237
- _See code: [src/commands/agent/deactivate.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.9/src/commands/agent/deactivate.ts)_
237
+ _See code: [src/commands/agent/deactivate.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.11/src/commands/agent/deactivate.ts)_
238
238
 
239
239
  ## `sf agent generate agent-spec`
240
240
 
@@ -341,7 +341,7 @@ EXAMPLES
341
341
  $ sf agent generate agent-spec --tone formal --agent-user resortmanager@myorg.com
342
342
  ```
343
343
 
344
- _See code: [src/commands/agent/generate/agent-spec.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.9/src/commands/agent/generate/agent-spec.ts)_
344
+ _See code: [src/commands/agent/generate/agent-spec.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.11/src/commands/agent/generate/agent-spec.ts)_
345
345
 
346
346
  ## `sf agent generate authoring-bundle`
347
347
 
@@ -418,18 +418,22 @@ EXAMPLES
418
418
  other-package-dir/main/default --target-org my-dev-org
419
419
  ```
420
420
 
421
- _See code: [src/commands/agent/generate/authoring-bundle.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.9/src/commands/agent/generate/authoring-bundle.ts)_
421
+ _See code: [src/commands/agent/generate/authoring-bundle.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.11/src/commands/agent/generate/authoring-bundle.ts)_
422
422
 
423
423
  ## `sf agent generate template`
424
424
 
425
- Generate an agent template from an existing agent in your DX project so you can then package the template in a managed package.
425
+ Generate an agent template from an existing agent in your DX project so you can then package the template in a second-generation managed package.
426
426
 
427
427
  ```
428
428
  USAGE
429
- $ sf agent generate template --agent-version <value> -f <value> [--json] [--flags-dir <value>] [--api-version <value>]
429
+ $ sf agent generate template -s <value> --agent-version <value> -f <value> [--json] [--flags-dir <value>] [--api-version
430
+ <value>] [-r <value>]
430
431
 
431
432
  FLAGS
432
433
  -f, --agent-file=<value> (required) Path to an agent (Bot) metadata file.
434
+ -r, --output-dir=<value> Directory where the generated BotTemplate and GenAiPlannerBundle files are saved.
435
+ -s, --source-org=<value> (required) Username or alias of the namespaced scratch org that contains the agent which
436
+ this template is based on.
433
437
  --agent-version=<value> (required) Version of the agent (BotVersion).
434
438
  --api-version=<value> Override the api version used for api requests made by this command
435
439
 
@@ -438,35 +442,45 @@ GLOBAL FLAGS
438
442
  --json Format output as json.
439
443
 
440
444
  DESCRIPTION
441
- Generate an agent template from an existing agent in your DX project so you can then package the template in a managed
442
- package.
445
+ Generate an agent template from an existing agent in your DX project so you can then package the template in a
446
+ second-generation managed package.
447
+
448
+ WARNING: This command doesn't work for agents that were created from an Agent Script file. In other words, you can't
449
+ currently package an agent template for agents that use Agent Script.
443
450
 
444
451
  At a high-level, agents are defined by the Bot, BotVersion, and GenAiPlannerBundle metadata types. The
445
452
  GenAiPlannerBundle type in turn defines the agent's topics and actions. This command uses the metadata files for these
446
- three types, located in your local DX project, to generate a BotTemplate file for a specific agent (Bot). You then use
447
- the BotTemplate file, along with the GenAiPlannerBundle file that references the BotTemplate, to package the template
448
- in a managed package that you can share between orgs or on AppExchange.
453
+ three types, located in your local DX project, to generate a BotTemplate metadata file for a specific agent (Bot). You
454
+ then use the BotTemplate metadata file, along with the GenAiPlannerBundle metadata file that references the
455
+ BotTemplate, to package the template in a managed package that you can share between orgs or on AppExchange.
449
456
 
450
457
  Use the --agent-file flag to specify the relative or full pathname of the Bot metadata file, such as
451
458
  force-app/main/default/bots/My_Awesome_Agent/My_Awesome_Agent.bot-meta.xml. A single Bot can have multiple
452
- BotVersions, so use the --agent-version flag to specify the version. The corresponding BotVersion file must exist
453
- locally. For example, if you specify "--agent-version 4", then the file
459
+ BotVersions, so use the --agent-version flag to specify the version. The corresponding BotVersion metadata file must
460
+ exist locally. For example, if you specify "--agent-version 4", then the file
454
461
  force-app/main/default/bots/My_Awesome_Agent/v4.botVersion-meta.xml must exist.
455
462
 
456
- The new BotTemplate file is generated in the "botTemplates" directory in your local package directory, and has the
457
- name <Agent_API_name>_v<Version>_Template.botTemplate-meta.xml, such as
458
- force-app/main/default/botTemplates/My_Awesome_Agent_v4_Template.botTemplate-meta.xml. The command displays the full
459
- pathname of the generated files when it completes.
463
+ The new BotTemplate metadata file is generated in the "botTemplates" directory in the output directory specified with
464
+ the --output-dir flag, and has the name <Agent_API_name>\_v<Version>\_Template.botTemplate-meta.xml, such as
465
+ my-package/botTemplates/My_Awesome_Agent_v4_Template.botTemplate-meta.xml. The command displays the full pathname of
466
+ the generated files when it completes.
467
+
468
+ See "Develop and Package Agent Templates Using Scratch Orgs"
469
+ (https://developer.salesforce.com/docs/atlas.en-us.pkg2_dev.meta/pkg2_dev/dev2gp_package_agent_templates.htm) for
470
+ details about the complete process, which includes using a scratch org to create and test the agent, retrieving the
471
+ agent metadata to your DX project, running this command to create the agent template, and then packaging the template.
460
472
 
461
473
  EXAMPLES
462
- Generate an agent template from a Bot metadata file in your DX project that corresponds to the My_Awesome_Agent
463
- agent; use version 1 of the agent.
474
+ Generate an agent template from the My_Awesome_Agent Bot metadata file in your DX project and save the BotTemplate
475
+ and GenAiPlannerBundle to the specified directory; use version 1 of the agent. The agent that the template is based
476
+ on is in the org with alias "my-scratch-org":
464
477
 
465
478
  $ sf agent generate template --agent-file \
466
- force-app/main/default/bots/My_Awesome_Agent/My_Awesome_Agent.bot-meta.xml --agent-version 1
479
+ force-app/main/default/bots/My_Awesome_Agent/My_Awesome_Agent.bot-meta.xml --agent-version 1 --output-dir \
480
+ my-package --source-org my-scratch-org
467
481
  ```
468
482
 
469
- _See code: [src/commands/agent/generate/template.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.9/src/commands/agent/generate/template.ts)_
483
+ _See code: [src/commands/agent/generate/template.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.11/src/commands/agent/generate/template.ts)_
470
484
 
471
485
  ## `sf agent generate test-spec`
472
486
 
@@ -531,7 +545,7 @@ EXAMPLES
531
545
  force-app//main/default/aiEvaluationDefinitions/Resort_Manager_Tests.aiEvaluationDefinition-meta.xml
532
546
  ```
533
547
 
534
- _See code: [src/commands/agent/generate/test-spec.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.9/src/commands/agent/generate/test-spec.ts)_
548
+ _See code: [src/commands/agent/generate/test-spec.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.11/src/commands/agent/generate/test-spec.ts)_
535
549
 
536
550
  ## `sf agent preview`
537
551
 
@@ -604,7 +618,7 @@ EXAMPLES
604
618
  $ sf agent preview --use-live-actions --apex-debug --output-dir transcripts/my-preview
605
619
  ```
606
620
 
607
- _See code: [src/commands/agent/preview.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.9/src/commands/agent/preview.ts)_
621
+ _See code: [src/commands/agent/preview.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.11/src/commands/agent/preview.ts)_
608
622
 
609
623
  ## `sf agent preview end`
610
624
 
@@ -659,7 +673,7 @@ EXAMPLES
659
673
  $ sf agent preview end --authoring-bundle My_Local_Agent
660
674
  ```
661
675
 
662
- _See code: [src/commands/agent/preview/end.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.9/src/commands/agent/preview/end.ts)_
676
+ _See code: [src/commands/agent/preview/end.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.11/src/commands/agent/preview/end.ts)_
663
677
 
664
678
  ## `sf agent preview send`
665
679
 
@@ -717,7 +731,7 @@ EXAMPLES
717
731
  $ sf agent preview send --utterance "what can you help me with?" --authoring-bundle My_Local_Agent
718
732
  ```
719
733
 
720
- _See code: [src/commands/agent/preview/send.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.9/src/commands/agent/preview/send.ts)_
734
+ _See code: [src/commands/agent/preview/send.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.11/src/commands/agent/preview/send.ts)_
721
735
 
722
736
  ## `sf agent preview sessions`
723
737
 
@@ -750,7 +764,7 @@ EXAMPLES
750
764
  $ sf agent preview sessions
751
765
  ```
752
766
 
753
- _See code: [src/commands/agent/preview/sessions.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.9/src/commands/agent/preview/sessions.ts)_
767
+ _See code: [src/commands/agent/preview/sessions.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.11/src/commands/agent/preview/sessions.ts)_
754
768
 
755
769
  ## `sf agent preview start`
756
770
 
@@ -807,7 +821,7 @@ EXAMPLES
807
821
  $ sf agent preview start --api-name My_Published_Agent
808
822
  ```
809
823
 
810
- _See code: [src/commands/agent/preview/start.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.9/src/commands/agent/preview/start.ts)_
824
+ _See code: [src/commands/agent/preview/start.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.11/src/commands/agent/preview/start.ts)_
811
825
 
812
826
  ## `sf agent publish authoring-bundle`
813
827
 
@@ -856,7 +870,7 @@ EXAMPLES
856
870
  $ sf agent publish authoring-bundle --api-name MyAuthoringbundle --target-org my-dev-org
857
871
  ```
858
872
 
859
- _See code: [src/commands/agent/publish/authoring-bundle.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.9/src/commands/agent/publish/authoring-bundle.ts)_
873
+ _See code: [src/commands/agent/publish/authoring-bundle.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.11/src/commands/agent/publish/authoring-bundle.ts)_
860
874
 
861
875
  ## `sf agent test create`
862
876
 
@@ -911,7 +925,7 @@ EXAMPLES
911
925
  $ sf agent test create --spec specs/Resort_Manager-testSpec.yaml --api-name Resort_Manager_Test --preview
912
926
  ```
913
927
 
914
- _See code: [src/commands/agent/test/create.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.9/src/commands/agent/test/create.ts)_
928
+ _See code: [src/commands/agent/test/create.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.11/src/commands/agent/test/create.ts)_
915
929
 
916
930
  ## `sf agent test list`
917
931
 
@@ -946,7 +960,7 @@ EXAMPLES
946
960
  $ sf agent test list --target-org my-org
947
961
  ```
948
962
 
949
- _See code: [src/commands/agent/test/list.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.9/src/commands/agent/test/list.ts)_
963
+ _See code: [src/commands/agent/test/list.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.11/src/commands/agent/test/list.ts)_
950
964
 
951
965
  ## `sf agent test results`
952
966
 
@@ -1012,7 +1026,7 @@ FLAG DESCRIPTIONS
1012
1026
  expression when using custom evaluations.
1013
1027
  ```
1014
1028
 
1015
- _See code: [src/commands/agent/test/results.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.9/src/commands/agent/test/results.ts)_
1029
+ _See code: [src/commands/agent/test/results.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.11/src/commands/agent/test/results.ts)_
1016
1030
 
1017
1031
  ## `sf agent test resume`
1018
1032
 
@@ -1085,7 +1099,7 @@ FLAG DESCRIPTIONS
1085
1099
  expression when using custom evaluations.
1086
1100
  ```
1087
1101
 
1088
- _See code: [src/commands/agent/test/resume.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.9/src/commands/agent/test/resume.ts)_
1102
+ _See code: [src/commands/agent/test/resume.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.11/src/commands/agent/test/resume.ts)_
1089
1103
 
1090
1104
  ## `sf agent test run`
1091
1105
 
@@ -1159,7 +1173,7 @@ FLAG DESCRIPTIONS
1159
1173
  expression when using custom evaluations.
1160
1174
  ```
1161
1175
 
1162
- _See code: [src/commands/agent/test/run.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.9/src/commands/agent/test/run.ts)_
1176
+ _See code: [src/commands/agent/test/run.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.11/src/commands/agent/test/run.ts)_
1163
1177
 
1164
1178
  ## `sf agent validate authoring-bundle`
1165
1179
 
@@ -1206,6 +1220,6 @@ EXAMPLES
1206
1220
  $ sf agent validate authoring-bundle --api-name MyAuthoringBundle --target-org my-dev-org
1207
1221
  ```
1208
1222
 
1209
- _See code: [src/commands/agent/validate/authoring-bundle.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.9/src/commands/agent/validate/authoring-bundle.ts)_
1223
+ _See code: [src/commands/agent/validate/authoring-bundle.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.11/src/commands/agent/validate/authoring-bundle.ts)_
1210
1224
 
1211
1225
  <!-- commandsstop -->
@@ -1,8 +1,15 @@
1
1
  import { SfCommand } from '@salesforce/sf-plugins-core';
2
- import type { BotTemplate, GenAiPlanner, BotDialogGroup, ConversationDefinitionGoal, ConversationVariable } from '@salesforce/types/metadata';
2
+ import type { Connection } from '@salesforce/core';
3
+ import type { BotTemplate, GenAiPlannerBundle, BotDialogGroup, ConversationDefinitionGoal, ConversationVariable, GenAiFunction, GenAiPlugin, GenAiPlannerFunctionDef } from '@salesforce/types/metadata';
4
+ /** Global function names that are allowed to be emitted in genAiFunctions when converting localActionLinks. */
5
+ export declare const ALLOWED_GLOBAL_FUNCTIONS: Set<string>;
3
6
  export type GenAiPlannerBundleExt = {
4
- GenAiPlannerBundle: GenAiPlanner & {
7
+ GenAiPlannerBundle: GenAiPlannerBundle & {
5
8
  botTemplate?: string;
9
+ localActionLinks?: GenAiPlannerFunctionDef[];
10
+ localTopicLinks: GenAiPlannerFunctionDef[];
11
+ localTopics?: GenAiPlugin[];
12
+ plannerActions?: GenAiFunction[];
6
13
  };
7
14
  };
8
15
  export type BotTemplateExt = {
@@ -28,8 +35,44 @@ export default class AgentGenerateTemplate extends SfCommand<AgentGenerateTempla
28
35
  static readonly requiresProject = true;
29
36
  static readonly flags: {
30
37
  'api-version': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
38
+ 'source-org': import("@oclif/core/interfaces").OptionFlag<import("@salesforce/core").Org, import("@oclif/core/interfaces").CustomOptions>;
31
39
  'agent-version': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
32
40
  'agent-file': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
41
+ 'output-dir': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
33
42
  };
34
43
  run(): Promise<AgentGenerateTemplateResult>;
35
44
  }
45
+ /**
46
+ * Extracts local topics and actions from the GenAiPlannerBundle and validates that each has a `source` reference to its global counterpart.
47
+ * Throws if any local topic or action is missing `source`.
48
+ *
49
+ * @param genAiPlannerBundleMetaJson - The GenAiPlannerBundle metadata to read from
50
+ * @returns { localTopics, localActions } - The local topics and the flattened local actions from all plugins
51
+ */
52
+ export declare const getLocalAssets: (genAiPlannerBundleMetaJson: GenAiPlannerBundleExt) => {
53
+ localTopics: GenAiPlugin[];
54
+ localActions: GenAiFunction[];
55
+ };
56
+ /**
57
+ * Uses localTopics' <source> elements to identify global assets, then updates topic links (genAiPlugins), action links (genAiFunctions), attributeMappings and ruleExpressionAssignments.
58
+ * Replaces localTopicLinks with genAiPlugins. Replaces localActionLinks with genAiFunctions.
59
+ */
60
+ export declare const replaceReferencesToGlobalAssets: (genAiPlannerBundleMetaJson: GenAiPlannerBundleExt, localTopics: GenAiPlugin[]) => void;
61
+ /** Tooling API row for GenAiPluginDefinition or GenAiFunctionDefinition (shared field shape). */
62
+ export type GenAiBundledAssetToolingRecord = {
63
+ Id: string;
64
+ DeveloperName: string;
65
+ NamespacePrefix: string | null;
66
+ IsLocal: boolean;
67
+ Source: string | null;
68
+ };
69
+ /**
70
+ * Validates that local topics and actions reference global assets that exist in the source org.
71
+ *
72
+ * @param localTopics - Local genAiPlugin topics (GenAiPluginDefinition)
73
+ * @param localActions - Local genAiFunction actions (GenAiFunctionDefinition)
74
+ * @param connection - Source org connection (--source-org)
75
+ * @param namespaceFromSfdxProject - `namespace` from sfdx-project.json (empty if unset)
76
+ * @param warn - Command warn hook (e.g. `(msg) => this.warn(msg)`)
77
+ */
78
+ export declare const validateGlobalAssets: (localTopics: GenAiPlugin[], localActions: GenAiFunction[], connection: Connection, namespaceFromSfdxProject: string, warn: (msg: string) => void) => Promise<void>;
@@ -14,10 +14,13 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import { join, dirname, basename, resolve } from 'node:path';
17
- import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
17
+ import { cpSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
18
18
  import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
19
- import { Messages, SfError } from '@salesforce/core';
19
+ import { Messages, SfError, validateSalesforceId } from '@salesforce/core';
20
+ import { ensureArray } from '@salesforce/kit';
20
21
  import { XMLParser, XMLBuilder } from 'fast-xml-parser';
22
+ /** Global function names that are allowed to be emitted in genAiFunctions when converting localActionLinks. */
23
+ export const ALLOWED_GLOBAL_FUNCTIONS = new Set(['EmployeeCopilot__AnswerQuestionsWithKnowledge']);
21
24
  Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
22
25
  const messages = Messages.loadMessages('@salesforce/plugin-agent', 'agent.generate.template');
23
26
  export default class AgentGenerateTemplate extends SfCommand {
@@ -27,6 +30,10 @@ export default class AgentGenerateTemplate extends SfCommand {
27
30
  static requiresProject = true;
28
31
  static flags = {
29
32
  'api-version': Flags.orgApiVersion(),
33
+ 'source-org': Flags.requiredOrg({
34
+ summary: messages.getMessage('flags.source-org.summary'),
35
+ char: 's',
36
+ }),
30
37
  'agent-version': Flags.integer({
31
38
  summary: messages.getMessage('flags.agent-version.summary'),
32
39
  required: true,
@@ -37,10 +44,14 @@ export default class AgentGenerateTemplate extends SfCommand {
37
44
  required: true,
38
45
  exists: true,
39
46
  }),
47
+ 'output-dir': Flags.directory({
48
+ summary: messages.getMessage('flags.output-dir.summary'),
49
+ char: 'r',
50
+ }),
40
51
  };
41
52
  async run() {
42
53
  const { flags } = await this.parse(AgentGenerateTemplate);
43
- const { 'agent-file': agentFile, 'agent-version': botVersion } = flags;
54
+ const { 'agent-file': agentFile, 'agent-version': botVersion, 'output-dir': outputDir } = flags;
44
55
  if (!agentFile.endsWith('.bot-meta.xml')) {
45
56
  throw new SfError(messages.getMessage('error.invalid-agent-file'));
46
57
  }
@@ -50,24 +61,41 @@ export default class AgentGenerateTemplate extends SfCommand {
50
61
  // Since we are cloning the GenAiPlannerBundle, we need to use a different name than the Agent (Bot) we started with
51
62
  // We will use this name for the BotTemplate also to make it clear they are related
52
63
  const finalFilename = `${botName}_v${botVersion}_Template`;
53
- // Build the base dir from the AgentFile
64
+ // Base path for reading metadata
54
65
  const basePath = resolve(dirname(agentFile), '..', '..');
55
66
  const botDir = join(basePath, 'bots', botName);
56
67
  const genAiPlannerBundleDir = join(basePath, 'genAiPlannerBundles');
57
- const botTemplateDir = join(basePath, 'botTemplates');
68
+ const outputBase = resolve(outputDir ?? basePath);
69
+ const botTemplateDir = join(outputBase, 'botTemplates');
70
+ const outputGenAiPlannerBundleDir = join(outputBase, 'genAiPlannerBundles');
58
71
  const botTemplateFilePath = join(botTemplateDir, `${finalFilename}.botTemplate-meta.xml`);
59
- const clonedGenAiPlannerBundleFilePath = join(genAiPlannerBundleDir, finalFilename, `${finalFilename}.genAiPlannerBundle`);
72
+ const clonedGenAiPlannerBundleFilePath = join(outputGenAiPlannerBundleDir, finalFilename, `${finalFilename}.genAiPlannerBundle`);
60
73
  // Parse the metadata files as JSON
61
74
  const botJson = xmlToJson(join(botDir, `${botName}.bot-meta.xml`), parser);
75
+ if (botJson.Bot.agentDSLEnabled) {
76
+ throw new SfError(messages.getMessage('error.nga-agent-not-supported'));
77
+ }
62
78
  const botVersionJson = xmlToJson(join(botDir, `v${botVersion}.botVersion-meta.xml`), parser);
63
79
  const genAiPlannerBundleMetaJson = xmlToJson(join(genAiPlannerBundleDir, botName, `${botName}.genAiPlannerBundle`), parser);
64
80
  // Modify the metadata files for final output
65
- // TODO: Confirm this name (might be conversationDefinitionPlanners)
66
81
  genAiPlannerBundleMetaJson.GenAiPlannerBundle.botTemplate = finalFilename;
82
+ // Process local assets
83
+ const { localTopics, localActions } = getLocalAssets(genAiPlannerBundleMetaJson);
84
+ const connection = flags['source-org'].getConnection(flags['api-version']);
85
+ const resolvedProjectConfig = await this.project.resolveProjectConfig();
86
+ const namespaceFromSfdxProject = typeof resolvedProjectConfig.namespace === 'string' ? resolvedProjectConfig.namespace : '';
87
+ await validateGlobalAssets(localTopics, localActions, connection, namespaceFromSfdxProject, (msg) => this.warn(msg));
88
+ replaceReferencesToGlobalAssets(genAiPlannerBundleMetaJson, localTopics);
67
89
  const botTemplate = convertBotToBotTemplate(botJson, botVersionJson, finalFilename, botTemplateFilePath);
68
90
  // Build and save the metadata files
69
91
  jsonToXml(clonedGenAiPlannerBundleFilePath, genAiPlannerBundleMetaJson, builder);
70
92
  jsonToXml(botTemplateFilePath, botTemplate, builder);
93
+ if (outputDir) {
94
+ const copiedDirs = copyMetadataDirsIfPresent(basePath, outputBase);
95
+ if (copiedDirs.length > 0) {
96
+ this.warn(messages.getMessage('warn.copied-asset-directories', [copiedDirs.join(', ')]));
97
+ }
98
+ }
71
99
  this.log(`\nSaved BotTemplate to:\n - ${botTemplateFilePath}`);
72
100
  this.log(`Saved GenAiPlannerBundle to:\n - ${clonedGenAiPlannerBundleFilePath}`);
73
101
  return {
@@ -134,7 +162,228 @@ const jsonToXml = (filename, json, builder) => {
134
162
  writeFileSync(filename, xml);
135
163
  }
136
164
  catch (error) {
137
- throw new SfError(`Failed save to file: ${filename}`);
165
+ throw new SfError(`Failed save to file: ${filename}`, undefined, undefined, undefined, error);
166
+ }
167
+ };
168
+ /**
169
+ * Extracts local topics and actions from the GenAiPlannerBundle and validates that each has a `source` reference to its global counterpart.
170
+ * Throws if any local topic or action is missing `source`.
171
+ *
172
+ * @param genAiPlannerBundleMetaJson - The GenAiPlannerBundle metadata to read from
173
+ * @returns { localTopics, localActions } - The local topics and the flattened local actions from all plugins
174
+ */
175
+ export const getLocalAssets = (genAiPlannerBundleMetaJson) => {
176
+ const rawLocalTopics = genAiPlannerBundleMetaJson.GenAiPlannerBundle.localTopics;
177
+ const localTopics = Array.isArray(rawLocalTopics) ? rawLocalTopics : rawLocalTopics ? [rawLocalTopics] : [];
178
+ const localTopicsWithoutSource = localTopics.filter((topic) => !topic.source);
179
+ if (localTopicsWithoutSource.length > 0) {
180
+ throw new SfError(messages.getMessage('error.local-topics-without-source', [
181
+ localTopicsWithoutSource.map((topic) => topic.developerName).join(', '),
182
+ ]));
183
+ }
184
+ const actionsFromPlugins = localTopics.flatMap((plugin) => Array.isArray(plugin.localActions) ? plugin.localActions : plugin.localActions ? [plugin.localActions] : []);
185
+ const plannerBundle = genAiPlannerBundleMetaJson.GenAiPlannerBundle;
186
+ const plannerActions = Array.isArray(plannerBundle.plannerActions)
187
+ ? plannerBundle.plannerActions
188
+ : plannerBundle.plannerActions
189
+ ? [plannerBundle.plannerActions]
190
+ : [];
191
+ // localActions are the actions from the plugins and the plannerActions
192
+ const localActions = [...actionsFromPlugins, ...plannerActions];
193
+ if (localActions.length > 0) {
194
+ const localActionsWithoutSource = localActions.filter((action) => !action.source);
195
+ if (localActionsWithoutSource.length > 0) {
196
+ throw new SfError(messages.getMessage('error.local-actions-without-source', [
197
+ localActionsWithoutSource.map((action) => action.developerName ?? action.fullName).join(', '),
198
+ ]));
199
+ }
200
+ }
201
+ return { localTopics, localActions };
202
+ };
203
+ /**
204
+ * Uses localTopics' <source> elements to identify global assets, then updates topic links (genAiPlugins), action links (genAiFunctions), attributeMappings and ruleExpressionAssignments.
205
+ * Replaces localTopicLinks with genAiPlugins. Replaces localActionLinks with genAiFunctions.
206
+ */
207
+ export const replaceReferencesToGlobalAssets = (genAiPlannerBundleMetaJson, localTopics) => {
208
+ const plannerBundle = genAiPlannerBundleMetaJson.GenAiPlannerBundle;
209
+ const localToGlobalAssets = buildLocalToGlobalAssetMap(localTopics, plannerBundle);
210
+ // replace localTopicLinks with global genAiPlugins
211
+ plannerBundle.genAiPlugins = localTopics.map((topic) => ({
212
+ genAiPluginName: topic.source,
213
+ }));
214
+ plannerBundle.localTopicLinks = [];
215
+ // Replaces localActionLinks with global genAiFunctions (only names in ALLOWED_GLOBAL_FUNCTIONS are emitted)
216
+ if (plannerBundle.localActionLinks) {
217
+ plannerBundle.localActionLinks = Array.isArray(plannerBundle.localActionLinks)
218
+ ? plannerBundle.localActionLinks
219
+ : [plannerBundle.localActionLinks];
220
+ const allowedFound = new Set();
221
+ for (const link of plannerBundle.localActionLinks) {
222
+ const globalName = localToGlobalAssets.get(link.genAiFunctionName);
223
+ if (globalName && ALLOWED_GLOBAL_FUNCTIONS.has(globalName)) {
224
+ allowedFound.add(globalName);
225
+ }
226
+ }
227
+ plannerBundle.genAiFunctions = [...allowedFound].map((genAiFunctionName) => ({ genAiFunctionName }));
228
+ plannerBundle.localActionLinks = [];
229
+ }
230
+ // replace references in attributeMappings and ruleExpressionAssignments
231
+ const attributeMappings = ensureArray(plannerBundle.attributeMappings);
232
+ for (const mapping of attributeMappings) {
233
+ if (mapping.attributeName) {
234
+ mapping.attributeName = replaceLocalRefsWithGlobal(mapping.attributeName, localToGlobalAssets);
235
+ }
236
+ }
237
+ const ruleExpressionAssignments = ensureArray(plannerBundle.ruleExpressionAssignments);
238
+ for (const assignment of ruleExpressionAssignments) {
239
+ if (assignment.targetName) {
240
+ assignment.targetName = replaceLocalRefsWithGlobal(assignment.targetName, localToGlobalAssets);
241
+ }
242
+ }
243
+ // delete local assets from the GenAiPlannerBundle
244
+ plannerBundle.localTopics = [];
245
+ plannerBundle.plannerActions = [];
246
+ };
247
+ /**
248
+ * Queries the org for asset definitions (topics or functions) and checks each local asset against the results.
249
+ *
250
+ * @returns { referenceAssetFromManagedPackage, notFoundInOrg } - The assets that are referenced from managed packages and the assets that are not found in the org
251
+ */
252
+ const doValidateGlobalAssets = async (connection, toolingObject, localAssets, namespaceFromSfdxProject) => {
253
+ const developerNames = localAssets.map((asset) => asset.fullName).filter((name) => Boolean(name));
254
+ // early return if there are no local assets to validate
255
+ if (developerNames.length === 0) {
256
+ return { referenceAssetFromManagedPackage: new Set(), notFoundInOrg: new Set() };
257
+ }
258
+ const inClause = developerNames.map((name) => `'${String(name).replace(/'/g, "''")}'`).join(', ');
259
+ const soql = `SELECT Id, DeveloperName, NamespacePrefix, IsLocal, Source FROM ${toolingObject} WHERE DeveloperName IN (${inClause}) OR IsLocal = false`;
260
+ const result = await connection.tooling.query(soql);
261
+ const assetDefinitionResults = result.records ?? [];
262
+ const localAssetDefinitionResults = new Map();
263
+ const globalAssetDefinitionById = new Map();
264
+ for (const assetDefinition of assetDefinitionResults) {
265
+ if (assetDefinition.IsLocal) {
266
+ localAssetDefinitionResults.set(assetDefinition.DeveloperName, assetDefinition);
267
+ }
268
+ else {
269
+ globalAssetDefinitionById.set(assetDefinition.Id, assetDefinition);
270
+ }
271
+ }
272
+ const referenceAssetFromManagedPackage = new Set();
273
+ const notFoundInOrg = new Set();
274
+ for (const localAsset of localAssets) {
275
+ const currentAssetDefinitionResult = localAssetDefinitionResults.get(localAsset.fullName);
276
+ if (currentAssetDefinitionResult) {
277
+ // if source is an Id, it means it's pointing to a record created in the org
278
+ // if is not an Id, it means it's pointing to an standard OOTB Salesforce asset
279
+ if (validateSalesforceId(currentAssetDefinitionResult.Source)) {
280
+ const globalAsset = globalAssetDefinitionById.get(currentAssetDefinitionResult.Source);
281
+ if (globalAsset) {
282
+ // find global assets from managed packages other than the one in the sfdx-project.json
283
+ if (globalAsset.NamespacePrefix && globalAsset.NamespacePrefix !== namespaceFromSfdxProject) {
284
+ referenceAssetFromManagedPackage.add(`${globalAsset.NamespacePrefix}__${globalAsset.DeveloperName}`);
285
+ }
286
+ }
287
+ else {
288
+ notFoundInOrg.add(currentAssetDefinitionResult.Source);
289
+ }
290
+ }
291
+ else {
292
+ // OOTB Salesforce asset, no validation needed
293
+ }
294
+ }
295
+ else {
296
+ notFoundInOrg.add(localAsset.fullName);
297
+ }
298
+ }
299
+ return {
300
+ referenceAssetFromManagedPackage,
301
+ notFoundInOrg,
302
+ };
303
+ };
304
+ /**
305
+ * Validates that local topics and actions reference global assets that exist in the source org.
306
+ *
307
+ * @param localTopics - Local genAiPlugin topics (GenAiPluginDefinition)
308
+ * @param localActions - Local genAiFunction actions (GenAiFunctionDefinition)
309
+ * @param connection - Source org connection (--source-org)
310
+ * @param namespaceFromSfdxProject - `namespace` from sfdx-project.json (empty if unset)
311
+ * @param warn - Command warn hook (e.g. `(msg) => this.warn(msg)`)
312
+ */
313
+ export const validateGlobalAssets = async (localTopics, localActions, connection, namespaceFromSfdxProject, warn) => {
314
+ const topicsValidationResults = await doValidateGlobalAssets(connection, 'GenAiPluginDefinition', localTopics, namespaceFromSfdxProject);
315
+ const actionsValidationResults = await doValidateGlobalAssets(connection, 'GenAiFunctionDefinition', localActions, namespaceFromSfdxProject);
316
+ const referenceAssetFromManagedPackage = new Set([
317
+ ...topicsValidationResults.referenceAssetFromManagedPackage,
318
+ ...actionsValidationResults.referenceAssetFromManagedPackage,
319
+ ]);
320
+ const notFoundInOrg = new Set([
321
+ ...topicsValidationResults.notFoundInOrg,
322
+ ...actionsValidationResults.notFoundInOrg,
323
+ ]);
324
+ if (referenceAssetFromManagedPackage.size > 0) {
325
+ warn(messages.getMessage('warn.reference-asset-from-managed-package', [
326
+ [...referenceAssetFromManagedPackage].join(', '),
327
+ ]));
328
+ }
329
+ if (notFoundInOrg.size > 0) {
330
+ throw new SfError(messages.getMessage('error.global-asset-not-found', [[...notFoundInOrg].join(', ')]));
331
+ }
332
+ };
333
+ /**
334
+ * Builds a map from local asset names to their global (source) asset names.
335
+ *
336
+ * @param localTopics - The local topics of the GenAiPlannerBundle
337
+ * @param plannerBundle - The GenAiPlannerBundle (for plannerActions)
338
+ * @returns A map of local asset name → global asset name
339
+ */
340
+ const buildLocalToGlobalAssetMap = (localTopics, plannerBundle) => {
341
+ const map = new Map();
342
+ for (const topic of localTopics) {
343
+ map.set(topic.fullName, topic.source);
344
+ const actions = Array.isArray(topic.localActions)
345
+ ? topic.localActions
346
+ : topic.localActions
347
+ ? [topic.localActions]
348
+ : [];
349
+ for (const action of actions) {
350
+ map.set(action.fullName, action.source);
351
+ }
352
+ }
353
+ const plannerActions = Array.isArray(plannerBundle.plannerActions)
354
+ ? plannerBundle.plannerActions
355
+ : plannerBundle.plannerActions
356
+ ? [plannerBundle.plannerActions]
357
+ : [];
358
+ for (const action of plannerActions) {
359
+ if (action.fullName && action.source)
360
+ map.set(action.fullName, action.source);
361
+ }
362
+ return map;
363
+ };
364
+ /**
365
+ * Replaces dot-separated local refs with global names. Each segment is replaced only when present in localToGlobalMap;
366
+ * segments not in localToGlobalMap (e.g. namespace, attribute path) are kept as-is. Used for attributeName, targetName, expression.
367
+ */
368
+ const replaceLocalRefsWithGlobal = (value, localToGlobalMap) => value
369
+ .split('.')
370
+ .map((segment) => localToGlobalMap.get(segment) ?? segment)
371
+ .join('.');
372
+ /**
373
+ * Copies metadata directories from the source package (basePath) to the output directory if they exist.
374
+ * Source path is derived from the agent file location (e.g. force-app/main/default).
375
+ */
376
+ const copyMetadataDirsIfPresent = (basePath, outputBase) => {
377
+ const METADATA_DIRS_TO_COPY = ['genAiPlugins', 'genAiFunctions', 'classes', 'flows', 'genAiPromptTemplates'];
378
+ const copiedDirs = [];
379
+ for (const dirName of METADATA_DIRS_TO_COPY) {
380
+ const srcDir = join(basePath, dirName);
381
+ if (existsSync(srcDir) && statSync(srcDir).isDirectory()) {
382
+ const destDir = join(outputBase, dirName);
383
+ cpSync(srcDir, destDir, { recursive: true });
384
+ copiedDirs.push(dirName);
385
+ }
138
386
  }
387
+ return copiedDirs;
139
388
  };
140
389
  //# sourceMappingURL=template.js.map