Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions command-snapshot.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
[
{
"alias": [],
"command": "agent:activate",
"flagAliases": [],
"flagChars": ["n", "o"],
"flags": ["api-name", "api-version", "flags-dir", "json", "target-org"],
"plugin": "@salesforce/plugin-agent"
},
{
"alias": [],
"command": "agent:create",
Expand All @@ -7,6 +15,14 @@
"flags": ["api-name", "api-version", "flags-dir", "json", "name", "planner-id", "preview", "spec", "target-org"],
"plugin": "@salesforce/plugin-agent"
},
{
"alias": [],
"command": "agent:deactivate",
"flagAliases": [],
"flagChars": ["n", "o"],
"flags": ["api-name", "api-version", "flags-dir", "json", "target-org"],
"plugin": "@salesforce/plugin-agent"
},
{
"alias": [],
"command": "agent:generate:agent-spec",
Expand Down
27 changes: 27 additions & 0 deletions messages/agent.activate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# summary

Activate an agent in an org.

# description

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.

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.

# examples

- Activate an agent in your default target org by being prompted:

<%= config.bin %> <%= command.id %>

- Activate an agent with API name Resort_Manager in the org with alias "my-org":

<%= config.bin %> <%= command.id %> --api-name Resort_Manager --target-org my-org

# flags.api-name.summary

API name of the agent to activate.

# error.missingRequiredFlags

Missing required flags: %s.
15 changes: 15 additions & 0 deletions messages/agent.activation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# error.noAgentsInOrg

No agents found in org %s.

# error.missingAgentInOrg

Agent %s not found in org %s.

# error.agentIsDeleted

Agent %s has been deleted and can't be activated.

# error.agentIsDefault

Agent %s is the default Agentforce agent in your org and you can't change its activation status.
27 changes: 27 additions & 0 deletions messages/agent.deactivate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# summary

Deactivate an agent in an org.

# description

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.

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.

# examples

- Deactivate an agent in your default target org by being prompted:

<%= config.bin %> <%= command.id %>

- Deactivate the agent Resort_Manager in the org with alias "my_org":

<%= config.bin %> <%= command.id %> --api-name Resort_Manager --target-org my-org

# flags.api-name.summary

API name of the agent to deactivate.

# error.missingRequiredFlags

Missing required flags: %s.
4 changes: 2 additions & 2 deletions messages/agent.preview.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ This command is useful to test if the agent responds to your utterances as you e

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.

Find the agent's API name in its main details page in your org's Agent page in Setup.
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.

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.

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

# examples

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

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

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"@inquirer/prompts": "^7.2.0",
"@oclif/core": "^4",
"@oclif/multi-stage-output": "^0.8.20",
"@salesforce/agents": "^0.16.0",
"@salesforce/agents": "0.17.0",
"@salesforce/core": "^8.18.5",
"@salesforce/kit": "^3.2.3",
"@salesforce/sf-plugins-core": "^12.2.3",
Expand Down Expand Up @@ -105,7 +105,7 @@
"prepack": "sf-prepack",
"prepare": "sf-install",
"test": "wireit",
"test:nuts": "nyc mocha \"**/*.nut.ts\" --slow 4500 --timeout 600000 --parallel --reporter-options maxDiffSize=15000",
"test:nuts": "nyc mocha \"**/*.nut.ts\" --slow 4500 --timeout 600000 --reporter-options maxDiffSize=15000",
"test:only": "wireit",
"version": "oclif readme"
},
Expand Down
100 changes: 100 additions & 0 deletions src/agentActivation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Copyright (c) 2024, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import { Connection, Messages, Org, SfError } from '@salesforce/core';
import { Agent, type BotMetadata } from '@salesforce/agents';
import { select } from '@inquirer/prompts';

type Choice<Value> = {
value: Value;
name?: string;
disabled?: boolean | string;
};
type AgentValue = {
Id: string;
DeveloperName: string;
};

Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@salesforce/plugin-agent', 'agent.activation');

export const UNSUPPORTED_AGENTS = ['Copilot_for_Salesforce'];
export const agentIsUnsupported = (devName: string): boolean => UNSUPPORTED_AGENTS.includes(devName);

export const validateAgent = (agent: BotMetadata): boolean => {
if (agent.IsDeleted) {
throw messages.createError('error.agentIsDeleted', [agent.DeveloperName]);
}
if (agentIsUnsupported(agent.DeveloperName)) {
throw messages.createError('error.agentIsDefault', [agent.DeveloperName]);
}
return true;
};

export const getAgentChoices = (agents: BotMetadata[], status: 'Active' | 'Inactive'): Array<Choice<AgentValue>> =>
agents.map((agent) => {
let disabled: string | boolean = false;

const lastBotVersion = agent.BotVersions.records[agent.BotVersions.records.length - 1];
if (lastBotVersion.Status === status) {
disabled = `(Already ${status})`;
}
if (agentIsUnsupported(agent.DeveloperName)) {
disabled = '(Not Supported)';
}

return {
name: agent.DeveloperName,
value: {
Id: agent.Id,
DeveloperName: agent.DeveloperName,
},
disabled,
};
});

export const getAgentForActivation = async (config: {
conn: Connection;
targetOrg: Org;
status: 'Active' | 'Inactive';
apiNameFlag?: string;
}): Promise<Agent> => {
const { conn, targetOrg, status, apiNameFlag } = config;

let agentsInOrg: BotMetadata[] = [];
try {
agentsInOrg = await Agent.listRemote(conn);
} catch (error) {
throw SfError.create({
message: 'Error listing agents in org',
name: 'NoAgentsInOrgError',
cause: error,
});
}

if (!agentsInOrg.length) {
throw messages.createError('error.noAgentsInOrg', [targetOrg.getUsername()]);
}

let selectedAgent: BotMetadata | undefined;

if (apiNameFlag) {
selectedAgent = agentsInOrg.find((agent) => agent.DeveloperName === apiNameFlag);
if (!selectedAgent) {
throw messages.createError('error.missingAgentInOrg', [apiNameFlag, targetOrg.getUsername()]);
}
validateAgent(selectedAgent);
} else {
const agentChoice = await select({
message: 'Select an agent',
choices: getAgentChoices(agentsInOrg, status),
});
selectedAgent = agentsInOrg.find((agent) => agent.DeveloperName === agentChoice.DeveloperName);
}

return new Agent({ connection: conn, nameOrId: selectedAgent!.Id });
};
45 changes: 45 additions & 0 deletions src/commands/agent/activate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2024, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
import { Messages } from '@salesforce/core';
import { getAgentForActivation } from '../../agentActivation.js';

Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@salesforce/plugin-agent', 'agent.activate');

export default class AgentActivate extends SfCommand<void> {
public static readonly summary = messages.getMessage('summary');
public static readonly description = messages.getMessage('description');
public static readonly examples = messages.getMessages('examples');

public static readonly flags = {
'target-org': Flags.requiredOrg(),
'api-version': Flags.orgApiVersion(),
'api-name': Flags.string({
summary: messages.getMessage('flags.api-name.summary'),
char: 'n',
}),
};

public async run(): Promise<void> {
const { flags } = await this.parse(AgentActivate);

const apiNameFlag = flags['api-name'];
const targetOrg = flags['target-org'];
const conn = targetOrg.getConnection(flags['api-version']);

if (!apiNameFlag && this.jsonEnabled()) {
throw messages.createError('error.missingRequiredFlags', ['api-name']);
}

const agent = await getAgentForActivation({ conn, targetOrg, status: 'Active', apiNameFlag });
await agent.activate();
const agentName = (await agent.getBotMetadata()).DeveloperName;

this.log(`Agent ${agentName} activated.`);
}
}
45 changes: 45 additions & 0 deletions src/commands/agent/deactivate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2024, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
import { Messages } from '@salesforce/core';
import { getAgentForActivation } from '../../agentActivation.js';

Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@salesforce/plugin-agent', 'agent.deactivate');

export default class AgentDeactivate extends SfCommand<void> {
public static readonly summary = messages.getMessage('summary');
public static readonly description = messages.getMessage('description');
public static readonly examples = messages.getMessages('examples');

public static readonly flags = {
'target-org': Flags.requiredOrg(),
'api-version': Flags.orgApiVersion(),
'api-name': Flags.string({
summary: messages.getMessage('flags.api-name.summary'),
char: 'n',
}),
};

public async run(): Promise<void> {
const { flags } = await this.parse(AgentDeactivate);

const apiNameFlag = flags['api-name'];
const targetOrg = flags['target-org'];
const conn = targetOrg.getConnection(flags['api-version']);

if (!apiNameFlag && this.jsonEnabled()) {
throw messages.createError('error.missingRequiredFlags', ['api-name']);
}

const agent = await getAgentForActivation({ conn, targetOrg, status: 'Inactive', apiNameFlag });
await agent.deactivate();
const agentName = (await agent.getBotMetadata()).DeveloperName;

this.log(`Agent ${agentName} deactivated.`);
}
}
Loading
Loading