Skip to content

Commit afdb37b

Browse files
fix: update agent preview to start to work with simulated agents
1 parent 37ef2b0 commit afdb37b

File tree

1 file changed

+100
-28
lines changed

1 file changed

+100
-28
lines changed

src/commands/agent/preview.ts

Lines changed: 100 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@
1515
*/
1616

1717
import { resolve, join } from 'node:path';
18+
import { readdirSync, statSync } from 'node:fs';
1819
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
19-
import { AuthInfo, Connection, Messages, SfError } from '@salesforce/core';
20+
import { AuthInfo, Connection, Messages, SfError, SfProject } from '@salesforce/core';
2021
import React from 'react';
2122
import { render } from 'ink';
2223
import { env } from '@salesforce/kit';
23-
import { AgentPreview as Preview } from '@salesforce/agents';
24+
import { AgentPreview as Preview, AgentSimulate, findAuthoringBundle } from '@salesforce/agents';
2425
import { select, confirm, input } from '@inquirer/prompts';
2526
import { AgentPreviewReact } from '../../components/agent-preview-react.js';
2627

@@ -43,10 +44,17 @@ type Choice<Value> = {
4344
disabled?: boolean | string;
4445
};
4546

47+
enum AgentSource {
48+
ORG = 'org',
49+
LOCAL = 'local',
50+
}
51+
4652
type AgentValue = {
4753
Id: string;
4854
DeveloperName: string;
49-
};
55+
source: AgentSource.ORG;
56+
} |
57+
{ DeveloperName: string; source: AgentSource.LOCAL; path: string };
5058

5159
// https://developer.salesforce.com/docs/einstein/genai/guide/agent-api-get-started.html#prerequisites
5260
export const UNSUPPORTED_AGENTS = ['Copilot_for_Salesforce'];
@@ -93,13 +101,30 @@ export default class AgentPreview extends SfCommand<AgentPreviewResult> {
93101
const authInfo = await AuthInfo.create({
94102
username: flags['target-org'].getUsername(),
95103
});
96-
if (!(flags['client-app'] ?? env.getString('SF_DEMO_AGENT_CLIENT_APP'))) {
97-
throw new SfError('SF_DEMO_AGENT_CLIENT_APP is unset!');
104+
// Get client app - check flag first, then auth file, then env var
105+
let clientApp = flags['client-app'];
106+
107+
if (!clientApp) {
108+
const clientApps = getClientAppsFromAuth(authInfo);
109+
110+
if (clientApps.length === 1) {
111+
clientApp = clientApps[0];
112+
} else if (clientApps.length > 1) {
113+
clientApp = await select({
114+
message: 'Select a client app',
115+
choices: clientApps.map((app) => ({ value: app, name: app })),
116+
});
117+
}
118+
}
119+
120+
if (!clientApp) {
121+
// at this point we should throw an error
122+
throw new SfError('No client app found.');
98123
}
99124

100125
const jwtConn = await Connection.create({
101126
authInfo,
102-
clientApp: env.getString('SF_DEMO_AGENT_CLIENT_APP') ?? flags['client-app'],
127+
clientApp,
103128
});
104129

105130
const agentsQuery = await conn.query<AgentData>(
@@ -110,32 +135,42 @@ export default class AgentPreview extends SfCommand<AgentPreviewResult> {
110135

111136
const agentsInOrg = agentsQuery.records;
112137

113-
let selectedAgent;
138+
let selectedAgent: AgentValue | undefined;
114139

115140
if (flags['authoring-bundle']) {
116-
const envAgentName = env.getString('SF_DEMO_AGENT');
117-
const agent = agentsQuery.records.find((a) => a.DeveloperName === envAgentName);
141+
const bundlePath = findAuthoringBundle(this.project!.getPath(), flags['authoring-bundle']);
142+
if (!bundlePath) {
143+
throw new SfError(`Could not find authoring bundle for ${flags['authoring-bundle']}`);
144+
}
118145
selectedAgent = {
119-
Id:
120-
agent?.Id ??
121-
`Couldn't find an agent in ${agentsQuery.records.map((a) => a.DeveloperName).join(', ')} matching ${
122-
envAgentName ?? '!SF_DEMO_AGENT is unset!'
123-
}`,
124146
DeveloperName: flags['authoring-bundle'],
147+
source: AgentSource.LOCAL,
148+
path: bundlePath,
125149
};
126150
} else if (apiNameFlag) {
127-
selectedAgent = agentsInOrg.find((agent) => agent.DeveloperName === apiNameFlag);
151+
const agent = agentsInOrg.find((a) => a.DeveloperName === apiNameFlag);
152+
if (!agent) throw new Error(`No valid Agents were found with the Api Name ${apiNameFlag}.`);
153+
validateAgent(agent);
154+
selectedAgent = {
155+
Id: agent.Id,
156+
DeveloperName: agent.DeveloperName,
157+
source: AgentSource.ORG,
158+
};
128159
if (!selectedAgent) throw new Error(`No valid Agents were found with the Api Name ${apiNameFlag}.`);
129-
validateAgent(selectedAgent);
130160
} else {
131161
selectedAgent = await select({
132162
message: 'Select an agent',
133-
choices: getAgentChoices(agentsInOrg),
163+
choices: getAgentChoices(agentsInOrg, this.project!),
134164
});
135165
}
136166

137167
const outputDir = await resolveOutputDir(flags['output-dir'], flags['apex-debug']);
138-
const agentPreview = new Preview(jwtConn, selectedAgent.Id);
168+
// Both classes share the same interface for the methods we need
169+
const agentPreview = selectedAgent.source === AgentSource.ORG ?
170+
new Preview(jwtConn, selectedAgent.Id) :
171+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
172+
new AgentSimulate(jwtConn, selectedAgent.path, true) as unknown as Preview;
173+
139174
agentPreview.toggleApexDebugMode(flags['apex-debug']);
140175

141176
const instance = render(
@@ -172,22 +207,59 @@ export const validateAgent = (agent: AgentData): boolean => {
172207
return true;
173208
};
174209

175-
export const getAgentChoices = (agents: AgentData[]): Array<Choice<AgentValue>> =>
176-
agents.map((agent) => {
177-
let disabled: string | boolean = false;
210+
export const getAgentChoices = (
211+
agents: AgentData[],
212+
project: SfProject
213+
): Array<Choice<AgentValue>> => {
214+
const choices: Array<Choice<AgentValue>> = [];
178215

179-
if (agentIsInactive(agent)) disabled = '(Inactive)';
180-
if (agentIsUnsupported(agent.DeveloperName)) disabled = '(Not Supported)';
216+
// Add org agents
217+
for (const agent of agents) {
218+
if (agentIsInactive(agent) || agentIsUnsupported(agent.DeveloperName)) {
219+
continue;
220+
}
181221

182-
return {
183-
name: agent.DeveloperName,
222+
choices.push({
223+
name: `${agent.DeveloperName} (org)`,
184224
value: {
185225
Id: agent.Id,
186226
DeveloperName: agent.DeveloperName,
227+
source: AgentSource.ORG,
187228
},
188-
disabled,
189-
};
190-
});
229+
});
230+
}
231+
232+
// Add local agents from authoring bundles
233+
const localAgents = findAuthoringBundle(project.getPath(), '*');
234+
if (localAgents) {
235+
const bundlePath = localAgents.replace(/\/[^/]+$/, ''); // Get parent directory
236+
const agentDirs = readdirSync(bundlePath).filter((dir) =>
237+
statSync(join(bundlePath, dir)).isDirectory()
238+
);
239+
240+
agentDirs.forEach((agentDir) => {
241+
choices.push({
242+
name: `${agentDir} (local)`,
243+
value: {
244+
DeveloperName: agentDir,
245+
source: AgentSource.LOCAL,
246+
path: join(bundlePath, agentDir),
247+
},
248+
});
249+
});
250+
}
251+
252+
return choices;
253+
};
254+
255+
256+
export const getClientAppsFromAuth = (authInfo: AuthInfo): string[] => {
257+
const config = authInfo.getConnectionOptions();
258+
const clientApps = Object.entries(config)
259+
.filter(([key]) => key.startsWith('oauthClientApp_'))
260+
.map(([, value]) => value as string);
261+
return clientApps;
262+
};
191263

192264
export const resolveOutputDir = async (
193265
outputDir: string | undefined,

0 commit comments

Comments
 (0)