@salesforce/plugin-agent 1.15.1-dev.3 → 1.15.2
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 +29 -33
- package/lib/commands/agent/create.js +2 -2
- package/lib/commands/agent/create.js.map +1 -1
- package/lib/commands/agent/generate/agent-spec.d.ts +1 -1
- package/lib/commands/agent/generate/agent-spec.js +3 -4
- package/lib/commands/agent/generate/agent-spec.js.map +1 -1
- package/lib/commands/agent/generate/test-spec.d.ts +0 -5
- package/lib/commands/agent/generate/test-spec.js +78 -196
- package/lib/commands/agent/generate/test-spec.js.map +1 -1
- package/lib/commands/agent/test/create.d.ts +2 -3
- package/lib/commands/agent/test/create.js +21 -80
- package/lib/commands/agent/test/create.js.map +1 -1
- package/lib/flags.d.ts +0 -1
- package/lib/flags.js +0 -31
- package/lib/flags.js.map +1 -1
- package/lib/handleTestResults.js +21 -2
- package/lib/handleTestResults.js.map +1 -1
- package/lib/read-dir.d.ts +1 -0
- package/lib/read-dir.js +16 -0
- package/lib/read-dir.js.map +1 -0
- package/messages/agent.generate.agent-spec.md +1 -1
- package/messages/agent.generate.test-spec.md +0 -16
- package/messages/agent.test.create.md +8 -18
- package/npm-shrinkwrap.json +30 -522
- package/oclif.lock +11 -115
- package/oclif.manifest.json +12 -38
- package/package.json +5 -7
- package/lib/yes-no-cancel.d.ts +0 -10
- package/lib/yes-no-cancel.js +0 -60
- package/lib/yes-no-cancel.js.map +0 -1
|
@@ -1,75 +1,45 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright (c)
|
|
2
|
+
* Copyright (c) 2024, salesforce.com, inc.
|
|
3
3
|
* All rights reserved.
|
|
4
4
|
* Licensed under the BSD 3-Clause license.
|
|
5
5
|
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
6
|
*/
|
|
7
|
+
import { join } from 'node:path';
|
|
7
8
|
import { readFile } from 'node:fs/promises';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { Messages, SfError, SfProject } from '@salesforce/core';
|
|
12
|
-
import { writeTestSpec, generateTestSpecFromAiEvalDefinition } from '@salesforce/agents';
|
|
9
|
+
import { SfCommand } from '@salesforce/sf-plugins-core';
|
|
10
|
+
import { Messages, SfError } from '@salesforce/core';
|
|
11
|
+
import { generateTestSpec } from '@salesforce/agents';
|
|
13
12
|
import { select, input, confirm, checkbox } from '@inquirer/prompts';
|
|
14
13
|
import { XMLParser } from 'fast-xml-parser';
|
|
15
|
-
import { ComponentSetBuilder } from '@salesforce/source-deploy-retrieve';
|
|
16
|
-
import { warn } from '@oclif/core/errors';
|
|
17
14
|
import { theme } from '../../../inquirer-theme.js';
|
|
18
|
-
import
|
|
15
|
+
import { readDir } from '../../../read-dir.js';
|
|
19
16
|
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
|
|
20
17
|
const messages = Messages.loadMessages('@salesforce/plugin-agent', 'agent.generate.test-spec');
|
|
21
18
|
function castArray(value) {
|
|
22
|
-
if (!value)
|
|
23
|
-
return [];
|
|
24
19
|
return Array.isArray(value) ? value : [value];
|
|
25
20
|
}
|
|
26
|
-
|
|
27
|
-
* Prompts the user for test case information through interactive prompts.
|
|
28
|
-
*
|
|
29
|
-
* @param genAiPlugins - Record mapping topic names to GenAiPlugin XML file paths (used to find the related actions)
|
|
30
|
-
* @param genAiFunctions - Array of GenAiFunction names from the GenAiPlanner
|
|
31
|
-
* @returns Promise resolving to a TestCase object containing:
|
|
32
|
-
* - utterance: The user input string
|
|
33
|
-
* - expectedTopic: The expected topic for classification
|
|
34
|
-
* - expectedActions: Array of expected action names
|
|
35
|
-
* - expectedOutcome: Expected outcome string
|
|
36
|
-
*
|
|
37
|
-
* @remarks
|
|
38
|
-
* This function guides users through creating a test case by:
|
|
39
|
-
* 1. Prompting for an utterance
|
|
40
|
-
* 2. Selecting an expected topic (from GenAiPlugins specified in the Bot's GenAiPlanner)
|
|
41
|
-
* 3. Choosing expected actions (from GenAiFunctions in the GenAiPlanner or GenAiPlugin)
|
|
42
|
-
* 4. Defining an expected outcome
|
|
43
|
-
*/
|
|
44
|
-
async function promptForTestCase(genAiPlugins, genAiFunctions) {
|
|
21
|
+
async function promptForTestCase(genAiPlugins) {
|
|
45
22
|
const utterance = await input({
|
|
46
23
|
message: 'Utterance',
|
|
47
24
|
validate: (d) => d.length > 0 || 'utterance cannot be empty',
|
|
48
25
|
theme,
|
|
49
26
|
});
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
theme,
|
|
54
|
-
});
|
|
55
|
-
// GenAiFunctions (aka actions) can be defined in the GenAiPlugin or GenAiPlanner
|
|
56
|
-
// the actions from the planner are passed in as an argument to this function
|
|
57
|
-
// the actions from the plugin are read from the GenAiPlugin file
|
|
58
|
-
let actions = [];
|
|
59
|
-
if (genAiPlugins[expectedTopic]) {
|
|
60
|
-
const genAiPluginXml = await readFile(genAiPlugins[expectedTopic], 'utf-8');
|
|
61
|
-
const parser = new XMLParser();
|
|
62
|
-
const parsed = parser.parse(genAiPluginXml);
|
|
63
|
-
actions = castArray(parsed.GenAiPlugin.genAiFunctions ?? []).map((f) => f.functionName);
|
|
64
|
-
}
|
|
65
|
-
const expectedActions = await checkbox({
|
|
27
|
+
const customKey = '<OTHER>';
|
|
28
|
+
const topics = Object.keys(genAiPlugins);
|
|
29
|
+
const askForOtherActions = async () => (await input({
|
|
66
30
|
message: 'Expected action(s)',
|
|
67
|
-
|
|
31
|
+
validate: (d) => {
|
|
32
|
+
if (!d.length) {
|
|
33
|
+
return 'expected value cannot be empty';
|
|
34
|
+
}
|
|
35
|
+
return true;
|
|
36
|
+
},
|
|
68
37
|
theme,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
38
|
+
}))
|
|
39
|
+
.split(',')
|
|
40
|
+
.map((a) => a.trim());
|
|
41
|
+
const askForBotRating = async () => input({
|
|
42
|
+
message: 'Expected response',
|
|
73
43
|
validate: (d) => {
|
|
74
44
|
if (!d.length) {
|
|
75
45
|
return 'expected value cannot be empty';
|
|
@@ -78,155 +48,62 @@ async function promptForTestCase(genAiPlugins, genAiFunctions) {
|
|
|
78
48
|
},
|
|
79
49
|
theme,
|
|
80
50
|
});
|
|
51
|
+
const expectedTopic = await select({
|
|
52
|
+
message: 'Expected topic',
|
|
53
|
+
choices: [...topics, customKey],
|
|
54
|
+
theme,
|
|
55
|
+
});
|
|
56
|
+
if (expectedTopic === customKey) {
|
|
57
|
+
return {
|
|
58
|
+
utterance,
|
|
59
|
+
expectedTopic: await input({
|
|
60
|
+
message: 'Expected topic',
|
|
61
|
+
validate: (d) => {
|
|
62
|
+
if (!d.length) {
|
|
63
|
+
return 'expected value cannot be empty';
|
|
64
|
+
}
|
|
65
|
+
return true;
|
|
66
|
+
},
|
|
67
|
+
theme,
|
|
68
|
+
}),
|
|
69
|
+
// If the user selects OTHER for the topic, then we don't have a genAiPlugin to get actions from so we ask for them for custom input
|
|
70
|
+
expectedActions: await askForOtherActions(),
|
|
71
|
+
expectedOutcome: await askForBotRating(),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const genAiPluginXml = await readFile(genAiPlugins[expectedTopic], 'utf-8');
|
|
75
|
+
const parser = new XMLParser();
|
|
76
|
+
const parsed = parser.parse(genAiPluginXml);
|
|
77
|
+
const actions = castArray(parsed.GenAiPlugin.genAiFunctions).map((f) => f.functionName);
|
|
78
|
+
let expectedActions = await checkbox({
|
|
79
|
+
message: 'Expected action(s)',
|
|
80
|
+
choices: [...actions, customKey],
|
|
81
|
+
theme,
|
|
82
|
+
required: true,
|
|
83
|
+
});
|
|
84
|
+
if (expectedActions.includes(customKey)) {
|
|
85
|
+
const additional = await askForOtherActions();
|
|
86
|
+
expectedActions = [...expectedActions.filter((a) => a !== customKey), ...additional];
|
|
87
|
+
}
|
|
88
|
+
const expectedOutcome = await askForBotRating();
|
|
81
89
|
return {
|
|
82
90
|
utterance,
|
|
83
|
-
expectedTopic,
|
|
84
91
|
expectedActions,
|
|
85
92
|
expectedOutcome,
|
|
93
|
+
expectedTopic,
|
|
86
94
|
};
|
|
87
95
|
}
|
|
88
|
-
function getMetadataFilePaths(cs, type) {
|
|
89
|
-
return [...cs.filter((component) => component.type.name === type && component.fullName !== '*')].reduce((acc, component) => ({
|
|
90
|
-
...acc,
|
|
91
|
-
[component.fullName]: cs.getComponentFilenamesByNameAndType({
|
|
92
|
-
fullName: component.fullName,
|
|
93
|
-
type,
|
|
94
|
-
})[0],
|
|
95
|
-
}), {});
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Retrieves GenAIPlugins and GenAiFunctions from a Bot's GenAiPlanner
|
|
99
|
-
*
|
|
100
|
-
* We have to get the bot version and planner for the selected bot so that we can get
|
|
101
|
-
* the actions (GenAiFunctions) and topics (GenAiPlugins) that can be selected for the
|
|
102
|
-
* test cases.
|
|
103
|
-
*
|
|
104
|
-
* The BotVersion tells us which GenAiPlanner to use, and the GenAiPlanner
|
|
105
|
-
* tells us which GenAiPlugins and GenAiFunctions are available. More GenAiFunctions
|
|
106
|
-
* might be available in the GenAiPlugin, so we read those later when the user
|
|
107
|
-
* has selected a GenAiPlugin/topic - inside of `promptForTestCase`.
|
|
108
|
-
*
|
|
109
|
-
* @param subjectName - The name of the Bot to analyze
|
|
110
|
-
* @param cs - ComponentSet containing Bot, GenAiPlanner, and GenAiPlugin components
|
|
111
|
-
*
|
|
112
|
-
* @returns Object containing:
|
|
113
|
-
* - genAiPlugins: Record of plugin names to their file paths
|
|
114
|
-
* - genAiFunctions: Array of function names
|
|
115
|
-
*/
|
|
116
|
-
async function getPluginsAndFunctions(subjectName, cs) {
|
|
117
|
-
const botVersions = getMetadataFilePaths(cs, 'Bot');
|
|
118
|
-
const genAiPlanners = getMetadataFilePaths(cs, 'GenAiPlanner');
|
|
119
|
-
const parser = new XMLParser();
|
|
120
|
-
const botVersionXml = await readFile(botVersions[subjectName], 'utf-8');
|
|
121
|
-
const parsedBotVersion = parser.parse(botVersionXml);
|
|
122
|
-
const plannerXml = await readFile(genAiPlanners[parsedBotVersion.BotVersion.conversationDefinitionPlanners.genAiPlannerName ?? subjectName], 'utf-8');
|
|
123
|
-
const parsedPlanner = parser.parse(plannerXml);
|
|
124
|
-
const genAiFunctions = castArray(parsedPlanner.GenAiPlanner.genAiFunctions).map(({ genAiFunctionName }) => genAiFunctionName);
|
|
125
|
-
const genAiPlugins = castArray(parsedPlanner.GenAiPlanner.genAiPlugins).reduce((acc, { genAiPluginName }) => ({
|
|
126
|
-
...acc,
|
|
127
|
-
[genAiPluginName]: cs.getComponentFilenamesByNameAndType({
|
|
128
|
-
fullName: genAiPluginName,
|
|
129
|
-
type: 'GenAiPlugin',
|
|
130
|
-
})[0],
|
|
131
|
-
}), {});
|
|
132
|
-
return { genAiPlugins, genAiFunctions };
|
|
133
|
-
}
|
|
134
|
-
function ensureYamlExtension(filePath) {
|
|
135
|
-
const parsedPath = parse(filePath);
|
|
136
|
-
if (parsedPath.ext === '.yaml' || parsedPath.ext === '.yml')
|
|
137
|
-
return filePath;
|
|
138
|
-
const normalized = `${join(parsedPath.dir, parsedPath.name)}.yaml`;
|
|
139
|
-
warn(`Provided file path does not have a .yaml or .yml extension. Normalizing to ${normalized}`);
|
|
140
|
-
return normalized;
|
|
141
|
-
}
|
|
142
|
-
async function promptUntilUniqueFile(subjectName, filePath) {
|
|
143
|
-
const outputFile = filePath ??
|
|
144
|
-
(await input({
|
|
145
|
-
message: 'Enter a path for the test spec file',
|
|
146
|
-
validate(d) {
|
|
147
|
-
if (!d.length) {
|
|
148
|
-
return 'Path cannot be empty';
|
|
149
|
-
}
|
|
150
|
-
return true;
|
|
151
|
-
},
|
|
152
|
-
theme,
|
|
153
|
-
}));
|
|
154
|
-
const normalized = ensureYamlExtension(outputFile);
|
|
155
|
-
if (!existsSync(normalized)) {
|
|
156
|
-
return normalized;
|
|
157
|
-
}
|
|
158
|
-
const confirmation = await yesNoOrCancel({
|
|
159
|
-
message: `File ${normalized} already exists. Overwrite?`,
|
|
160
|
-
default: false,
|
|
161
|
-
});
|
|
162
|
-
if (confirmation === 'cancel') {
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
if (!confirmation) {
|
|
166
|
-
return promptUntilUniqueFile(subjectName);
|
|
167
|
-
}
|
|
168
|
-
return normalized;
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* If the user provides the --force-overwrite flag, then we'll use the default file path (either the one provided by --output-file or the default path).
|
|
172
|
-
* If the user doesn't provide it, we'll prompt the user for a file path until they provide a unique one or cancel.
|
|
173
|
-
*/
|
|
174
|
-
async function determineFilePath(subjectName, outputFile, forceOverwrite) {
|
|
175
|
-
const defaultFile = ensureYamlExtension(outputFile ?? join('specs', `${subjectName}-testSpec.yaml`));
|
|
176
|
-
return forceOverwrite ? defaultFile : promptUntilUniqueFile(subjectName, defaultFile);
|
|
177
|
-
}
|
|
178
96
|
export default class AgentGenerateTestSpec extends SfCommand {
|
|
179
97
|
static summary = messages.getMessage('summary');
|
|
180
98
|
static description = messages.getMessage('description');
|
|
181
99
|
static examples = messages.getMessages('examples');
|
|
182
100
|
static enableJsonFlag = false;
|
|
183
101
|
static state = 'beta';
|
|
184
|
-
static flags = {
|
|
185
|
-
'from-definition': Flags.string({
|
|
186
|
-
char: 'd',
|
|
187
|
-
summary: messages.getMessage('flags.from-definition.summary'),
|
|
188
|
-
}),
|
|
189
|
-
'force-overwrite': Flags.boolean({
|
|
190
|
-
summary: messages.getMessage('flags.force-overwrite.summary'),
|
|
191
|
-
}),
|
|
192
|
-
'output-file': Flags.file({
|
|
193
|
-
char: 'f',
|
|
194
|
-
summary: messages.getMessage('flags.output-file.summary'),
|
|
195
|
-
parse: async (raw) => Promise.resolve(raw ? ensureYamlExtension(raw) : undefined),
|
|
196
|
-
}),
|
|
197
|
-
};
|
|
198
102
|
async run() {
|
|
199
|
-
const
|
|
200
|
-
const
|
|
201
|
-
const cs = await ComponentSetBuilder.build({
|
|
202
|
-
metadata: {
|
|
203
|
-
metadataEntries: ['GenAiPlanner', 'GenAiPlugin', 'Bot', 'AiEvaluationDefinition'],
|
|
204
|
-
directoryPaths,
|
|
205
|
-
},
|
|
206
|
-
});
|
|
207
|
-
if (flags['from-definition']) {
|
|
208
|
-
const aiEvalDefs = getMetadataFilePaths(cs, 'AiEvaluationDefinition');
|
|
209
|
-
if (!aiEvalDefs[flags['from-definition']]) {
|
|
210
|
-
throw new SfError(`AiEvaluationDefinition ${flags['from-definition']} not found`, 'AiEvalDefinitionNotFoundError');
|
|
211
|
-
}
|
|
212
|
-
const spec = await generateTestSpecFromAiEvalDefinition(aiEvalDefs[flags['from-definition']]);
|
|
213
|
-
const outputFile = await determineFilePath(spec.subjectName, flags['output-file'], flags['force-overwrite']);
|
|
214
|
-
if (!outputFile) {
|
|
215
|
-
this.log(messages.getMessage('info.cancel'));
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
await writeTestSpec(spec, outputFile);
|
|
219
|
-
this.log(`Created ${outputFile}`);
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
const bots = [
|
|
223
|
-
...cs
|
|
224
|
-
.filter((component) => component.type.name === 'Bot')
|
|
225
|
-
.map((c) => c.fullName)
|
|
226
|
-
.filter((n) => n !== '*'),
|
|
227
|
-
];
|
|
103
|
+
const botsDir = join('force-app', 'main', 'default', 'bots');
|
|
104
|
+
const bots = await readDir(botsDir);
|
|
228
105
|
if (bots.length === 0) {
|
|
229
|
-
throw new SfError(`No agents found in ${
|
|
106
|
+
throw new SfError(`No agents found in ${botsDir}`, 'NoAgentsFoundError');
|
|
230
107
|
}
|
|
231
108
|
const subjectType = await select({
|
|
232
109
|
message: 'What are you testing',
|
|
@@ -238,12 +115,6 @@ export default class AgentGenerateTestSpec extends SfCommand {
|
|
|
238
115
|
choices: bots,
|
|
239
116
|
theme,
|
|
240
117
|
});
|
|
241
|
-
const outputFile = await determineFilePath(subjectName, flags['output-file'], flags['force-overwrite']);
|
|
242
|
-
if (!outputFile) {
|
|
243
|
-
this.log(messages.getMessage('info.cancel'));
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
const { genAiPlugins, genAiFunctions } = await getPluginsAndFunctions(subjectName, cs);
|
|
247
118
|
const name = await input({
|
|
248
119
|
message: 'Enter a name for the test definition',
|
|
249
120
|
validate(d) {
|
|
@@ -252,6 +123,12 @@ export default class AgentGenerateTestSpec extends SfCommand {
|
|
|
252
123
|
return 'Name cannot be empty';
|
|
253
124
|
}
|
|
254
125
|
return true;
|
|
126
|
+
// TODO: add back validation once we refine the regex
|
|
127
|
+
// check against FORTY_CHAR_API_NAME_REGEX
|
|
128
|
+
// if (!FORTY_CHAR_API_NAME_REGEX.test(d)) {
|
|
129
|
+
// return 'The non-namespaced portion an API name must begin with a letter, contain only letters, numbers, and underscores, not contain consecutive underscores, and not end with an underscore.';
|
|
130
|
+
// }
|
|
131
|
+
// return true;
|
|
255
132
|
},
|
|
256
133
|
theme,
|
|
257
134
|
});
|
|
@@ -259,26 +136,31 @@ export default class AgentGenerateTestSpec extends SfCommand {
|
|
|
259
136
|
message: 'Enter a description for test definition (optional)',
|
|
260
137
|
theme,
|
|
261
138
|
});
|
|
139
|
+
const genAiPluginDir = join('force-app', 'main', 'default', 'genAiPlugins');
|
|
140
|
+
const genAiPlugins = Object.fromEntries((await readDir(genAiPluginDir)).map((genAiPlugin) => [
|
|
141
|
+
genAiPlugin.replace('.genAiPlugin-meta.xml', ''),
|
|
142
|
+
join(genAiPluginDir, genAiPlugin),
|
|
143
|
+
]));
|
|
262
144
|
const testCases = [];
|
|
263
145
|
do {
|
|
264
146
|
this.log();
|
|
265
147
|
this.styledHeader(`Adding test case #${testCases.length + 1}`);
|
|
266
148
|
// eslint-disable-next-line no-await-in-loop
|
|
267
|
-
testCases.push(await promptForTestCase(genAiPlugins
|
|
149
|
+
testCases.push(await promptForTestCase(genAiPlugins));
|
|
268
150
|
} while ( // eslint-disable-next-line no-await-in-loop
|
|
269
151
|
await confirm({
|
|
270
152
|
message: 'Would you like to add another test case',
|
|
271
153
|
default: true,
|
|
272
154
|
}));
|
|
273
155
|
this.log();
|
|
274
|
-
await
|
|
156
|
+
await generateTestSpec({
|
|
275
157
|
name,
|
|
276
158
|
description,
|
|
277
159
|
subjectType,
|
|
278
160
|
subjectName,
|
|
279
161
|
testCases,
|
|
280
|
-
},
|
|
281
|
-
this.log(`Created ${
|
|
162
|
+
}, `${name}-test-spec.yaml`);
|
|
163
|
+
this.log(`Created ${name}-test-spec.yaml`);
|
|
282
164
|
}
|
|
283
165
|
}
|
|
284
166
|
//# sourceMappingURL=test-spec.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-spec.js","sourceRoot":"","sources":["../../../../src/commands/agent/generate/test-spec.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"test-spec.js","sourceRoot":"","sources":["../../../../src/commands/agent/generate/test-spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,12 +11,11 @@ export default class AgentTestCreate extends SfCommand<AgentTestCreateResult> {
|
|
|
11
11
|
static readonly examples: string[];
|
|
12
12
|
static readonly state = "beta";
|
|
13
13
|
static readonly flags: {
|
|
14
|
+
spec: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
15
|
'target-org': import("@oclif/core/interfaces").OptionFlag<import("@salesforce/core").Org, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
16
|
'api-version': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
17
|
preview: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
17
|
-
'
|
|
18
|
-
spec: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
19
|
-
"test-api-name": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
18
|
+
'no-prompt': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
20
19
|
};
|
|
21
20
|
private mso?;
|
|
22
21
|
run(): Promise<AgentTestCreateResult>;
|
|
@@ -1,115 +1,49 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright (c)
|
|
2
|
+
* Copyright (c) 2024, salesforce.com, inc.
|
|
3
3
|
* All rights reserved.
|
|
4
4
|
* Licensed under the BSD 3-Clause license.
|
|
5
5
|
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
6
|
*/
|
|
7
|
-
import { join
|
|
8
|
-
import { existsSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
9
8
|
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
|
|
10
9
|
import { Lifecycle, Messages } from '@salesforce/core';
|
|
11
10
|
import { AgentTester, AgentTestCreateLifecycleStages } from '@salesforce/agents';
|
|
12
11
|
import { MultiStageOutput } from '@oclif/multi-stage-output';
|
|
13
|
-
import { makeFlags, promptForFlag, promptForYamlFile } from '../../../flags.js';
|
|
14
|
-
import yesNoOrCancel from '../../../yes-no-cancel.js';
|
|
15
12
|
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
|
|
16
13
|
const messages = Messages.loadMessages('@salesforce/plugin-agent', 'agent.test.create');
|
|
17
|
-
const FLAGGABLE_PROMPTS = {
|
|
18
|
-
'test-api-name': {
|
|
19
|
-
message: messages.getMessage('flags.test-api-name.summary'),
|
|
20
|
-
validate: (d) => {
|
|
21
|
-
if (!d.length) {
|
|
22
|
-
return 'API name cannot be empty';
|
|
23
|
-
}
|
|
24
|
-
if (d.length > 80) {
|
|
25
|
-
return 'API name cannot be over 80 characters.';
|
|
26
|
-
}
|
|
27
|
-
const regex = /^[A-Za-z][A-Za-z0-9_]*[A-Za-z0-9]+$/;
|
|
28
|
-
if (!regex.test(d)) {
|
|
29
|
-
return 'Invalid API name.';
|
|
30
|
-
}
|
|
31
|
-
return true;
|
|
32
|
-
},
|
|
33
|
-
required: true,
|
|
34
|
-
},
|
|
35
|
-
spec: {
|
|
36
|
-
message: messages.getMessage('flags.spec.summary'),
|
|
37
|
-
validate: (d) => {
|
|
38
|
-
const specPath = resolve(d);
|
|
39
|
-
if (!existsSync(specPath)) {
|
|
40
|
-
return 'Please enter an existing test spec (yaml) file';
|
|
41
|
-
}
|
|
42
|
-
return true;
|
|
43
|
-
},
|
|
44
|
-
required: true,
|
|
45
|
-
},
|
|
46
|
-
};
|
|
47
|
-
async function promptUntilUniqueName(agentTester, name) {
|
|
48
|
-
const apiName = name ?? (await promptForFlag(FLAGGABLE_PROMPTS['test-api-name']));
|
|
49
|
-
const existingDefinitions = await agentTester.list();
|
|
50
|
-
if (existingDefinitions.some((d) => d.fullName === apiName)) {
|
|
51
|
-
const confirmation = await yesNoOrCancel({
|
|
52
|
-
message: messages.getMessage('prompt.confirm', [apiName]),
|
|
53
|
-
default: false,
|
|
54
|
-
});
|
|
55
|
-
if (confirmation === 'cancel') {
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
if (!confirmation) {
|
|
59
|
-
return promptUntilUniqueName(agentTester);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return apiName;
|
|
63
|
-
}
|
|
64
14
|
export default class AgentTestCreate extends SfCommand {
|
|
65
15
|
static summary = messages.getMessage('summary');
|
|
66
16
|
static description = messages.getMessage('description');
|
|
67
17
|
static examples = messages.getMessages('examples');
|
|
68
18
|
static state = 'beta';
|
|
69
19
|
static flags = {
|
|
70
|
-
|
|
20
|
+
spec: Flags.file({
|
|
21
|
+
summary: messages.getMessage('flags.spec.summary'),
|
|
22
|
+
description: messages.getMessage('flags.spec.description'),
|
|
23
|
+
char: 's',
|
|
24
|
+
required: true,
|
|
25
|
+
exists: true,
|
|
26
|
+
}),
|
|
71
27
|
'target-org': Flags.requiredOrg(),
|
|
72
28
|
'api-version': Flags.orgApiVersion(),
|
|
73
29
|
preview: Flags.boolean({
|
|
74
30
|
summary: messages.getMessage('flags.preview.summary'),
|
|
75
31
|
}),
|
|
76
|
-
'
|
|
77
|
-
summary: messages.getMessage('flags.
|
|
32
|
+
'no-prompt': Flags.boolean({
|
|
33
|
+
summary: messages.getMessage('flags.no-prompt.summary'),
|
|
34
|
+
char: 'p',
|
|
78
35
|
}),
|
|
79
36
|
};
|
|
80
37
|
mso;
|
|
81
38
|
async run() {
|
|
82
39
|
const { flags } = await this.parse(AgentTestCreate);
|
|
83
|
-
// throw error if --json is used and not all required flags are provided
|
|
84
|
-
if (this.jsonEnabled()) {
|
|
85
|
-
if (!flags['test-api-name']) {
|
|
86
|
-
throw messages.createError('error.missingRequiredFlags', ['test-api-name']);
|
|
87
|
-
}
|
|
88
|
-
if (!flags.spec) {
|
|
89
|
-
throw messages.createError('error.missingRequiredFlags', ['spec']);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
if (flags['force-overwrite'] && !flags['test-api-name']) {
|
|
93
|
-
throw messages.createError('error.missingRequiredFlags', ['test-api-name']);
|
|
94
|
-
}
|
|
95
40
|
const agentTester = new AgentTester(flags['target-org'].getConnection(flags['api-version']));
|
|
96
|
-
const apiName = flags['force-overwrite']
|
|
97
|
-
? flags['test-api-name']
|
|
98
|
-
: await promptUntilUniqueName(agentTester, flags['test-api-name']);
|
|
99
|
-
if (!apiName) {
|
|
100
|
-
this.log(messages.getMessage('info.cancel'));
|
|
101
|
-
return {
|
|
102
|
-
path: '',
|
|
103
|
-
contents: '',
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
const spec = flags.spec ?? (await promptForYamlFile(FLAGGABLE_PROMPTS.spec));
|
|
107
41
|
const lifecycle = Lifecycle.getInstance();
|
|
108
42
|
lifecycle.on(AgentTestCreateLifecycleStages.CreatingLocalMetadata, async () => {
|
|
109
43
|
this.mso = new MultiStageOutput({
|
|
110
44
|
jsonEnabled: this.jsonEnabled(),
|
|
111
45
|
stages: Object.values(AgentTestCreateLifecycleStages),
|
|
112
|
-
title: `Creating test for ${spec}`,
|
|
46
|
+
title: `Creating test for ${flags.spec}`,
|
|
113
47
|
});
|
|
114
48
|
this.mso?.skipTo(AgentTestCreateLifecycleStages.CreatingLocalMetadata);
|
|
115
49
|
return Promise.resolve();
|
|
@@ -127,9 +61,16 @@ export default class AgentTestCreate extends SfCommand {
|
|
|
127
61
|
}
|
|
128
62
|
return Promise.resolve();
|
|
129
63
|
});
|
|
130
|
-
const
|
|
64
|
+
const confirmationCallback = flags['no-prompt']
|
|
65
|
+
? async () => Promise.resolve(true)
|
|
66
|
+
: async (spec) => this.confirm({
|
|
67
|
+
message: messages.getMessage('prompt.confirm', [spec.name]),
|
|
68
|
+
defaultAnswer: false,
|
|
69
|
+
});
|
|
70
|
+
const { path, contents } = await agentTester.create(flags.spec, {
|
|
131
71
|
outputDir: join('force-app', 'main', 'default', 'aiEvaluationDefinitions'),
|
|
132
72
|
preview: flags.preview,
|
|
73
|
+
confirmationCallback,
|
|
133
74
|
});
|
|
134
75
|
if (flags.preview) {
|
|
135
76
|
this.mso?.skipTo(AgentTestCreateLifecycleStages.Done);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create.js","sourceRoot":"","sources":["../../../../src/commands/agent/test/create.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,IAAI,EAAE,
|
|
1
|
+
{"version":3,"file":"create.js","sourceRoot":"","sources":["../../../../src/commands/agent/test/create.ts"],"names":[],"mappings":"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"}
|
package/lib/flags.d.ts
CHANGED
|
@@ -20,7 +20,6 @@ export declare const testOutputDirFlag: Interfaces.FlagDefinition<string, Interf
|
|
|
20
20
|
requiredOrDefaulted: false;
|
|
21
21
|
}>;
|
|
22
22
|
export declare function makeFlags<T extends Record<string, FlaggablePrompt>>(flaggablePrompts: T): FlagsOfPrompts<T>;
|
|
23
|
-
export declare const promptForYamlFile: (flagDef: FlaggablePrompt) => Promise<string>;
|
|
24
23
|
export declare const promptForFlag: (flagDef: FlaggablePrompt) => Promise<string>;
|
|
25
24
|
export declare const validateAgentType: (agentType?: string, required?: boolean) => string | undefined;
|
|
26
25
|
export declare const validateMaxTopics: (maxTopics?: number) => number | undefined;
|
package/lib/flags.js
CHANGED
|
@@ -4,13 +4,10 @@
|
|
|
4
4
|
* Licensed under the BSD 3-Clause license.
|
|
5
5
|
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
|
6
6
|
*/
|
|
7
|
-
import { readdir } from 'node:fs/promises';
|
|
8
|
-
import { join, relative } from 'node:path';
|
|
9
7
|
import { Flags } from '@salesforce/sf-plugins-core';
|
|
10
8
|
import { Messages, SfError } from '@salesforce/core';
|
|
11
9
|
import { camelCaseToTitleCase } from '@salesforce/kit';
|
|
12
10
|
import { select, input as inquirerInput } from '@inquirer/prompts';
|
|
13
|
-
import autocomplete from 'inquirer-autocomplete-standalone';
|
|
14
11
|
import { theme } from './inquirer-theme.js';
|
|
15
12
|
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
|
|
16
13
|
const messages = Messages.loadMessages('@salesforce/plugin-agent', 'shared');
|
|
@@ -45,34 +42,6 @@ export function makeFlags(flaggablePrompts) {
|
|
|
45
42
|
}),
|
|
46
43
|
]));
|
|
47
44
|
}
|
|
48
|
-
async function traverseForYamlFiles(dir) {
|
|
49
|
-
const files = await readdir(dir, { withFileTypes: true });
|
|
50
|
-
const results = [];
|
|
51
|
-
for (const file of files) {
|
|
52
|
-
const fullPath = join(dir, file.name);
|
|
53
|
-
if (file.isDirectory()) {
|
|
54
|
-
// eslint-disable-next-line no-await-in-loop
|
|
55
|
-
results.push(...(await traverseForYamlFiles(fullPath)));
|
|
56
|
-
}
|
|
57
|
-
else if (file.name.endsWith('.yaml') || file.name.endsWith('.yml')) {
|
|
58
|
-
results.push(fullPath);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return results;
|
|
62
|
-
}
|
|
63
|
-
export const promptForYamlFile = async (flagDef) => {
|
|
64
|
-
const yamlFiles = await traverseForYamlFiles(process.cwd());
|
|
65
|
-
return autocomplete({
|
|
66
|
-
message: flagDef.message,
|
|
67
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
68
|
-
source: async (input) => {
|
|
69
|
-
const arr = yamlFiles.map((o) => ({ name: relative(process.cwd(), o), value: o }));
|
|
70
|
-
if (!input)
|
|
71
|
-
return arr;
|
|
72
|
-
return arr.filter((o) => o.name.includes(input));
|
|
73
|
-
},
|
|
74
|
-
});
|
|
75
|
-
};
|
|
76
45
|
export const promptForFlag = async (flagDef) => {
|
|
77
46
|
const message = flagDef.promptMessage ?? flagDef.message.replace(/\.$/, '');
|
|
78
47
|
if (flagDef.options) {
|