Skip to content

Commit 394525e

Browse files
authored
Merge pull request #182 from salesforcecli/sh/activate-agent
feat: adds agent activate and deactivate commands (W-19127491)
2 parents 6f4a520 + 2bc2167 commit 394525e

File tree

13 files changed

+642
-313
lines changed

13 files changed

+642
-313
lines changed

command-snapshot.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
[
2+
{
3+
"alias": [],
4+
"command": "agent:activate",
5+
"flagAliases": [],
6+
"flagChars": ["n", "o"],
7+
"flags": ["api-name", "api-version", "flags-dir", "json", "target-org"],
8+
"plugin": "@salesforce/plugin-agent"
9+
},
210
{
311
"alias": [],
412
"command": "agent:create",
@@ -7,6 +15,14 @@
715
"flags": ["api-name", "api-version", "flags-dir", "json", "name", "planner-id", "preview", "spec", "target-org"],
816
"plugin": "@salesforce/plugin-agent"
917
},
18+
{
19+
"alias": [],
20+
"command": "agent:deactivate",
21+
"flagAliases": [],
22+
"flagChars": ["n", "o"],
23+
"flags": ["api-name", "api-version", "flags-dir", "json", "target-org"],
24+
"plugin": "@salesforce/plugin-agent"
25+
},
1026
{
1127
"alias": [],
1228
"command": "agent:generate:agent-spec",

messages/agent.activate.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# summary
2+
3+
Activate an agent in an org.
4+
5+
# description
6+
7+
Activating an agent makes it immediately available to your users. An agent must be active before you can preview it with the "agent preview" CLI command or VS Code.
8+
9+
You must know the agent's API name to activate it; you can either be prompted for it or you can specify it with the --api-name flag. Find the agent's API name in its Agent Details page of your org's Agentforce Studio UI in Setup.
10+
11+
# examples
12+
13+
- Activate an agent in your default target org by being prompted:
14+
15+
<%= config.bin %> <%= command.id %>
16+
17+
- Activate an agent with API name Resort_Manager in the org with alias "my-org":
18+
19+
<%= config.bin %> <%= command.id %> --api-name Resort_Manager --target-org my-org
20+
21+
# flags.api-name.summary
22+
23+
API name of the agent to activate.
24+
25+
# error.missingRequiredFlags
26+
27+
Missing required flags: %s.

messages/agent.activation.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# error.noAgentsInOrg
2+
3+
No agents found in org %s.
4+
5+
# error.missingAgentInOrg
6+
7+
Agent %s not found in org %s.
8+
9+
# error.agentIsDeleted
10+
11+
Agent %s has been deleted and can't be activated.
12+
13+
# error.agentIsDefault
14+
15+
Agent %s is the default Agentforce agent in your org and you can't change its activation status.

messages/agent.deactivate.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# summary
2+
3+
Deactivate an agent in an org.
4+
5+
# description
6+
7+
Deactivating an agent makes it unavailable to your users. To make changes to an agent, such as adding or removing topics or actions, you must deactivate it. You can't preview an agent with the "agent preview" CLI command or VS Code if it's deactivated.
8+
9+
You must know the agent's API name to deactivate it; you can either be prompted for it or you can specify it with the --api-name flag. Find the agent's API name in its Agent Details page of your org's Agentforce Studio UI in Setup.
10+
11+
# examples
12+
13+
- Deactivate an agent in your default target org by being prompted:
14+
15+
<%= config.bin %> <%= command.id %>
16+
17+
- Deactivate the agent Resort_Manager in the org with alias "my_org":
18+
19+
<%= config.bin %> <%= command.id %> --api-name Resort_Manager --target-org my-org
20+
21+
# flags.api-name.summary
22+
23+
API name of the agent to deactivate.
24+
25+
# error.missingRequiredFlags
26+
27+
Missing required flags: %s.

messages/agent.preview.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ This command is useful to test if the agent responds to your utterances as you e
1010

1111
When the session concludes, the command asks if you want to save the API responses and chat transcripts. By default, the files are saved to the "./temp/agent-preview" directory. Specify a new default directory by setting the environment variable "SF_AGENT_PREVIEW_OUTPUT_DIR" to the directory. Or you can pass the directory to the --output-dir flag.
1212

13-
Find the agent's API name in its main details page in your org's Agent page in Setup.
13+
Find the agent's API name in its Agent Details page of your org's Agentforce Studio UI in Setup. If your agent is currently deactivated, use the "agent activate" CLI command to activate it.
1414

1515
IMPORTANT: Before you use this command, you must complete a number of configuration steps in your org and your DX project. The examples in this help assume you've completed the steps. See "Preview an Agent" in the "Agentforce Developer Guide" for complete documentation: https://developer.salesforce.com/docs/einstein/genai/guide/agent-dx-preview.html.
1616

@@ -32,7 +32,7 @@ Enable Apex debug logging during the agent preview conversation.
3232

3333
# examples
3434

35-
- Interact with an agent with API name "Resort_Manager" in the org with alias "my-org" and the linked "agent-app" connected app:
35+
- Interact with an agent with API name Resort_Manager in the org with alias "my-org" and the linked "agent-app" connected app:
3636

3737
<%= config.bin %> <%= command.id %> --api-name Resort_Manager --target-org my-org --client-app agent-app
3838

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"@inquirer/prompts": "^7.2.0",
1010
"@oclif/core": "^4",
1111
"@oclif/multi-stage-output": "^0.8.20",
12-
"@salesforce/agents": "^0.16.0",
12+
"@salesforce/agents": "0.17.0",
1313
"@salesforce/core": "^8.18.5",
1414
"@salesforce/kit": "^3.2.3",
1515
"@salesforce/sf-plugins-core": "^12.2.3",
@@ -105,7 +105,7 @@
105105
"prepack": "sf-prepack",
106106
"prepare": "sf-install",
107107
"test": "wireit",
108-
"test:nuts": "nyc mocha \"**/*.nut.ts\" --slow 4500 --timeout 600000 --parallel --reporter-options maxDiffSize=15000",
108+
"test:nuts": "nyc mocha \"**/*.nut.ts\" --slow 4500 --timeout 600000 --reporter-options maxDiffSize=15000",
109109
"test:only": "wireit",
110110
"version": "oclif readme"
111111
},

src/agentActivation.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* Copyright (c) 2024, salesforce.com, inc.
3+
* All rights reserved.
4+
* Licensed under the BSD 3-Clause license.
5+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
8+
import { Connection, Messages, Org, SfError } from '@salesforce/core';
9+
import { Agent, type BotMetadata } from '@salesforce/agents';
10+
import { select } from '@inquirer/prompts';
11+
12+
type Choice<Value> = {
13+
value: Value;
14+
name?: string;
15+
disabled?: boolean | string;
16+
};
17+
type AgentValue = {
18+
Id: string;
19+
DeveloperName: string;
20+
};
21+
22+
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
23+
const messages = Messages.loadMessages('@salesforce/plugin-agent', 'agent.activation');
24+
25+
export const UNSUPPORTED_AGENTS = ['Copilot_for_Salesforce'];
26+
export const agentIsUnsupported = (devName: string): boolean => UNSUPPORTED_AGENTS.includes(devName);
27+
28+
export const validateAgent = (agent: BotMetadata): boolean => {
29+
if (agent.IsDeleted) {
30+
throw messages.createError('error.agentIsDeleted', [agent.DeveloperName]);
31+
}
32+
if (agentIsUnsupported(agent.DeveloperName)) {
33+
throw messages.createError('error.agentIsDefault', [agent.DeveloperName]);
34+
}
35+
return true;
36+
};
37+
38+
export const getAgentChoices = (agents: BotMetadata[], status: 'Active' | 'Inactive'): Array<Choice<AgentValue>> =>
39+
agents.map((agent) => {
40+
let disabled: string | boolean = false;
41+
42+
const lastBotVersion = agent.BotVersions.records[agent.BotVersions.records.length - 1];
43+
if (lastBotVersion.Status === status) {
44+
disabled = `(Already ${status})`;
45+
}
46+
if (agentIsUnsupported(agent.DeveloperName)) {
47+
disabled = '(Not Supported)';
48+
}
49+
50+
return {
51+
name: agent.DeveloperName,
52+
value: {
53+
Id: agent.Id,
54+
DeveloperName: agent.DeveloperName,
55+
},
56+
disabled,
57+
};
58+
});
59+
60+
export const getAgentForActivation = async (config: {
61+
conn: Connection;
62+
targetOrg: Org;
63+
status: 'Active' | 'Inactive';
64+
apiNameFlag?: string;
65+
}): Promise<Agent> => {
66+
const { conn, targetOrg, status, apiNameFlag } = config;
67+
68+
let agentsInOrg: BotMetadata[] = [];
69+
try {
70+
agentsInOrg = await Agent.listRemote(conn);
71+
} catch (error) {
72+
throw SfError.create({
73+
message: 'Error listing agents in org',
74+
name: 'NoAgentsInOrgError',
75+
cause: error,
76+
});
77+
}
78+
79+
if (!agentsInOrg.length) {
80+
throw messages.createError('error.noAgentsInOrg', [targetOrg.getUsername()]);
81+
}
82+
83+
let selectedAgent: BotMetadata | undefined;
84+
85+
if (apiNameFlag) {
86+
selectedAgent = agentsInOrg.find((agent) => agent.DeveloperName === apiNameFlag);
87+
if (!selectedAgent) {
88+
throw messages.createError('error.missingAgentInOrg', [apiNameFlag, targetOrg.getUsername()]);
89+
}
90+
validateAgent(selectedAgent);
91+
} else {
92+
const agentChoice = await select({
93+
message: 'Select an agent',
94+
choices: getAgentChoices(agentsInOrg, status),
95+
});
96+
selectedAgent = agentsInOrg.find((agent) => agent.DeveloperName === agentChoice.DeveloperName);
97+
}
98+
99+
return new Agent({ connection: conn, nameOrId: selectedAgent!.Id });
100+
};

src/commands/agent/activate.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright (c) 2024, salesforce.com, inc.
3+
* All rights reserved.
4+
* Licensed under the BSD 3-Clause license.
5+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
8+
import { Messages } from '@salesforce/core';
9+
import { getAgentForActivation } from '../../agentActivation.js';
10+
11+
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
12+
const messages = Messages.loadMessages('@salesforce/plugin-agent', 'agent.activate');
13+
14+
export default class AgentActivate extends SfCommand<void> {
15+
public static readonly summary = messages.getMessage('summary');
16+
public static readonly description = messages.getMessage('description');
17+
public static readonly examples = messages.getMessages('examples');
18+
19+
public static readonly flags = {
20+
'target-org': Flags.requiredOrg(),
21+
'api-version': Flags.orgApiVersion(),
22+
'api-name': Flags.string({
23+
summary: messages.getMessage('flags.api-name.summary'),
24+
char: 'n',
25+
}),
26+
};
27+
28+
public async run(): Promise<void> {
29+
const { flags } = await this.parse(AgentActivate);
30+
31+
const apiNameFlag = flags['api-name'];
32+
const targetOrg = flags['target-org'];
33+
const conn = targetOrg.getConnection(flags['api-version']);
34+
35+
if (!apiNameFlag && this.jsonEnabled()) {
36+
throw messages.createError('error.missingRequiredFlags', ['api-name']);
37+
}
38+
39+
const agent = await getAgentForActivation({ conn, targetOrg, status: 'Active', apiNameFlag });
40+
await agent.activate();
41+
const agentName = (await agent.getBotMetadata()).DeveloperName;
42+
43+
this.log(`Agent ${agentName} activated.`);
44+
}
45+
}

src/commands/agent/deactivate.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright (c) 2024, salesforce.com, inc.
3+
* All rights reserved.
4+
* Licensed under the BSD 3-Clause license.
5+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
8+
import { Messages } from '@salesforce/core';
9+
import { getAgentForActivation } from '../../agentActivation.js';
10+
11+
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
12+
const messages = Messages.loadMessages('@salesforce/plugin-agent', 'agent.deactivate');
13+
14+
export default class AgentDeactivate extends SfCommand<void> {
15+
public static readonly summary = messages.getMessage('summary');
16+
public static readonly description = messages.getMessage('description');
17+
public static readonly examples = messages.getMessages('examples');
18+
19+
public static readonly flags = {
20+
'target-org': Flags.requiredOrg(),
21+
'api-version': Flags.orgApiVersion(),
22+
'api-name': Flags.string({
23+
summary: messages.getMessage('flags.api-name.summary'),
24+
char: 'n',
25+
}),
26+
};
27+
28+
public async run(): Promise<void> {
29+
const { flags } = await this.parse(AgentDeactivate);
30+
31+
const apiNameFlag = flags['api-name'];
32+
const targetOrg = flags['target-org'];
33+
const conn = targetOrg.getConnection(flags['api-version']);
34+
35+
if (!apiNameFlag && this.jsonEnabled()) {
36+
throw messages.createError('error.missingRequiredFlags', ['api-name']);
37+
}
38+
39+
const agent = await getAgentForActivation({ conn, targetOrg, status: 'Inactive', apiNameFlag });
40+
await agent.deactivate();
41+
const agentName = (await agent.getBotMetadata()).DeveloperName;
42+
43+
this.log(`Agent ${agentName} deactivated.`);
44+
}
45+
}

0 commit comments

Comments
 (0)