@salesforce/plugin-agent 1.32.10 → 1.32.12
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 +49 -35
- package/lib/commands/agent/generate/template.d.ts +45 -2
- package/lib/commands/agent/generate/template.js +257 -8
- package/lib/commands/agent/generate/template.js.map +1 -1
- package/messages/agent.generate.template.md +50 -9
- package/oclif.manifest.json +424 -406
- package/package.json +3 -3
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.
|
|
129
|
+
_See code: [src/commands/agent/activate.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.12/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.
|
|
196
|
+
_See code: [src/commands/agent/create.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.12/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.
|
|
237
|
+
_See code: [src/commands/agent/deactivate.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.12/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.
|
|
344
|
+
_See code: [src/commands/agent/generate/agent-spec.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.12/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.
|
|
421
|
+
_See code: [src/commands/agent/generate/authoring-bundle.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.12/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
|
|
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
|
|
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
|
|
447
|
-
the BotTemplate file, along with the GenAiPlannerBundle file that references the
|
|
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
|
|
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
|
|
457
|
-
name <Agent_API_name
|
|
458
|
-
|
|
459
|
-
|
|
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
|
|
463
|
-
|
|
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.
|
|
483
|
+
_See code: [src/commands/agent/generate/template.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.12/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.
|
|
548
|
+
_See code: [src/commands/agent/generate/test-spec.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.12/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.
|
|
621
|
+
_See code: [src/commands/agent/preview.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.12/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.
|
|
676
|
+
_See code: [src/commands/agent/preview/end.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.12/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.
|
|
734
|
+
_See code: [src/commands/agent/preview/send.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.12/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.
|
|
767
|
+
_See code: [src/commands/agent/preview/sessions.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.12/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.
|
|
824
|
+
_See code: [src/commands/agent/preview/start.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.12/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.
|
|
873
|
+
_See code: [src/commands/agent/publish/authoring-bundle.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.12/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.
|
|
928
|
+
_See code: [src/commands/agent/test/create.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.12/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.
|
|
963
|
+
_See code: [src/commands/agent/test/list.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.12/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.
|
|
1029
|
+
_See code: [src/commands/agent/test/results.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.12/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.
|
|
1102
|
+
_See code: [src/commands/agent/test/resume.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.12/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.
|
|
1176
|
+
_See code: [src/commands/agent/test/run.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.12/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.
|
|
1223
|
+
_See code: [src/commands/agent/validate/authoring-bundle.ts](https://github.com/salesforcecli/plugin-agent/blob/1.32.12/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 {
|
|
2
|
+
import type { Connection } from '@salesforce/core';
|
|
3
|
+
import type { BotTemplate, BotDialogGroup, GenAiPlannerBundle, 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:
|
|
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
|
-
//
|
|
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
|
|
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(
|
|
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
|