Skip to content

Commit

Permalink
Merge pull request #30 from forcedotcom/sh/create-spec-v2
Browse files Browse the repository at this point in the history
feat: add create spec v2 API
  • Loading branch information
WillieRuemmele authored Jan 16, 2025
2 parents 6348932 + e22b460 commit 72460df
Show file tree
Hide file tree
Showing 7 changed files with 212 additions and 13 deletions.
10 changes: 4 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
## Contributing

1. Familiarize yourself with the codebase by reading the docs, in
particular the [developing](./contributing/developing.md) doc.
particular the [developing](./developing.md) doc.
1. Create a new issue before starting your project so that we can keep track of
what you're trying to add/fix. That way, we can also offer suggestions or
let you know if there is already an effort in progress.
1. Fork this repository.
1. Set up your environment using the information in the [developing](./contributing/developing.md) doc.
1. Create a _topic_ branch in your fork based on the correct branch (usually the **develop** branch, see [Branches section](./contributing/developing.md)). Note: this step is recommended but technically not required if contributing using a fork.
1. Set up your environment using the information in the [developing](./developing.md) doc.
1. Create a _topic_ branch in your fork based on the correct branch (usually the **develop** branch, see [Branches section](./developing.md)). Note: this step is recommended but technically not required if contributing using a fork.
1. Edit the code in your fork.
1. Sign the CLA (see [CLA](#cla)).
1. Send us a pull request when you're done. We'll review your code, suggest any
Expand All @@ -34,6 +34,4 @@ Pull request merging is restricted to squash and merge only.

## Helpful Resources

- All of the files in the [contributing](./contributing) folder have useful information, particularly the previously-mentioned [developing](./contributing/developing.md) doc.
- The [Source-Deploy-Retrieve Handbook](./HANDBOOK.md) contains an overview of all of the code in this project. This easy-to-read document can serve as an introduction and overview of the code and concepts, or as a reference for what a given module accomplishes and why it was designed.
- The [API documentation](https://forcedotcom.github.io/source-deploy-retrieve/) has details on using the classes and methods.
- [developing](./developing.md) doc.
3 changes: 3 additions & 0 deletions messages/agents.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# invalidAgentSpecConfig

Missing one or more of the required agent spec arguments: type, role, companyName, companyDescription
72 changes: 65 additions & 7 deletions src/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,27 @@
import { inspect } from 'node:util';
import path from 'node:path';
import fs from 'node:fs';
import { Connection, Lifecycle, Logger, SfError, SfProject } from '@salesforce/core';
import { Connection, Lifecycle, Logger, Messages, SfError, SfProject } from '@salesforce/core';
import { ComponentSetBuilder } from '@salesforce/source-deploy-retrieve';
import { Duration } from '@salesforce/kit';
import {
type SfAgent,
type AgentCreateConfig,
type AgentCreateResponse,
type AgentJobSpec,
type AgentJobSpecV2,
type AgentJobSpecCreateConfig,
type AgentJobSpecCreateConfigV2,
type AgentJobSpecCreateResponse,
AttachAgentTopicsBody,
type AttachAgentTopicsBody,
type DraftAgentTopicsBody,
type DraftAgentTopicsResponse,
} from './types.js';
import { MaybeMock } from './maybe-mock';

Messages.importMessagesDirectory(__dirname);
const messages = Messages.loadMessages('@salesforce/agents', 'agents');

/**
* Events emitted during Agent.create() for consumers to listen to and keep track of progress
*
Expand Down Expand Up @@ -58,6 +65,8 @@ export class Agent implements SfAgent {
* From an AgentCreateConfig, deploy the required metadata, call the connect/attach-agent-topics endpoint, and then retrieve
* the newly updated metadata back to the local project
*
* @deprecated Use the V2 APIs.
*
* @param {AgentCreateConfig} config
* @returns {Promise<AgentCreateResponse>}
*/
Expand Down Expand Up @@ -131,11 +140,11 @@ export class Agent implements SfAgent {
/**
* Create an agent spec from provided data.
*
* @deprecated Use the V2 APIs.
*
* @param config The configuration used to generate an agent spec.
*/
public async createSpec(config: AgentJobSpecCreateConfig): Promise<AgentJobSpec> {
this.verifyAgentSpecConfig(config);

let agentSpec: AgentJobSpec;
const response = await this.maybeMock.request<AgentJobSpecCreateResponse>('GET', this.buildAgentJobSpecUrl(config));
if (response.isSuccess && response.jobSpecs) {
Expand All @@ -150,10 +159,59 @@ export class Agent implements SfAgent {
return agentSpec;
}

/**
* Create an agent spec from provided data.
*
* V2 API: /connect/ai-assist/draft-agent-topics
*
* @param config The configuration used to generate an agent spec.
*/
public async createSpecV2(config: AgentJobSpecCreateConfigV2): Promise<AgentJobSpecV2> {
this.verifyAgentSpecConfig(config);

const url = '/connect/ai-assist/draft-agent-topics';

const body: DraftAgentTopicsBody = {
agentType: config.agentType,
generationInfo: {
defaultInfo: {
role: config.role,
companyName: config.companyName,
companyDescription: config.companyDescription,
},
},
generationSettings: {
maxNumOfTopics: config.maxNumOfTopics ?? 10,
},
};
if (config.companyWebsite) {
body.generationInfo.defaultInfo.companyWebsite = config.companyWebsite;
}
if (config.promptTemplateName) {
body.generationInfo.customizedInfo = { promptTemplateName: config.promptTemplateName };
if (config.groundingContext) {
body.generationInfo.customizedInfo.groundingContext = config.groundingContext;
}
}

const response = await this.maybeMock.request<DraftAgentTopicsResponse>('POST', url, body);

if (response.isSuccess && response.topics) {
return { ...config, topics: response.topics };
} else {
throw SfError.create({
name: 'AgentJobSpecCreateError',
message: response.errorMessage ?? 'unknown',
});
}
}

// eslint-disable-next-line class-methods-use-this
private verifyAgentSpecConfig(config: AgentJobSpecCreateConfig): void {
// TBD: for now just return. At some point verify all required config values.
if (config) return;
private verifyAgentSpecConfig(config: AgentJobSpecCreateConfigV2): void {
const { agentType, role, companyName, companyDescription } = config;
if (!agentType || !role || !companyName || !companyDescription) {
throw messages.createError('invalidAgentSpecConfig');
}
}

// eslint-disable-next-line class-methods-use-this
Expand Down
5 changes: 5 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@ export {
type AgentCreateConfig,
type AgentCreateResponse,
type AgentJobSpec,
type AgentJobSpecV2,
type AgentJobSpecCreateConfig,
type AgentJobSpecCreateConfigV2,
type AgentJobSpecCreateResponse,
type DraftAgentTopics,
type DraftAgentTopicsBody,
type DraftAgentTopicsResponse,
SfAgent,
} from './types';
export { Agent, AgentCreateLifecycleStages } from './agent';
Expand Down
71 changes: 71 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ export type AgentJobSpec = [
jobDescription: string;
}
];

export type AgentJobSpecV2 = AgentJobSpecCreateConfigV2 & {
topics: DraftAgentTopics;
};

/**
* The body POST'd to /services/data/{api-version}/connect/attach-agent-topics
*/
Expand All @@ -40,6 +45,34 @@ export type AgentJobSpecCreateConfig = {
companyWebsite?: string;
};

/**
* The parameters used to generate an agent spec V2.
*/
export type AgentJobSpecCreateConfigV2 = {
/**
* Internal type is copilots; used by customers' employees.
* Customer type is agents; used by customers' customers.
*/
agentType: 'customer' | 'internal';
role: string;
companyName: string;
companyDescription: string;
companyWebsite?: string;
/**
* The maximum number of topics to create in the spec.
* Default is 10.
*/
maxNumOfTopics?: number;
/**
* Developer name of the prompt template.
*/
promptTemplateName?: string;
/**
* Context info to be used in customized prompt template
*/
groundingContext?: string;
};

/**
* The parameters used to generate an agent in an org.
*
Expand All @@ -49,6 +82,28 @@ export type AgentCreateConfig = AgentJobSpecCreateConfig & {
jobSpec: AgentJobSpec;
};

/**
* The request body to send to the `draft-agent-topics` API.
*/
export type DraftAgentTopicsBody = {
agentType: 'customer' | 'internal';
generationInfo: {
defaultInfo: {
role: string;
companyName: string;
companyDescription: string;
companyWebsite?: string;
};
customizedInfo?: {
promptTemplateName: string;
groundingContext?: string;
};
};
generationSettings: {
maxNumOfTopics?: number;
};
};

/**
* An interface for working with Agents.
*/
Expand All @@ -73,3 +128,19 @@ export type AgentCreateResponse = {
isSuccess: boolean;
errorMessage?: string;
};

export type DraftAgentTopics = [
{
name: string;
description: string;
}
];

/**
* The response from the `draft-agent-topics` API.
*/
export type DraftAgentTopicsResponse = {
isSuccess: boolean;
errorMessage?: string;
topics: DraftAgentTopics;
};
19 changes: 19 additions & 0 deletions test/agents.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,25 @@ describe('Agents', () => {
expect(output).to.be.ok;
});

it('createSpecV2 (mock behavior) should return a spec', async () => {
const sfProject = SfProject.getInstance();
const agent = new Agent(connection, sfProject);
const agentType = 'customer';
const companyName = 'Coral Cloud Enterprises';
const output = await agent.createSpecV2({
agentType,
role: 'answer questions about vacation_rentals',
companyName,
companyDescription: 'Provide vacation rentals and activities',
});

expect(output).to.have.property('topics');
expect(output).to.have.property('agentType', agentType);
expect(output).to.have.property('companyName', companyName);
expect(output.topics).to.be.an('array').with.lengthOf(10);
expect(output.topics[0]).to.have.property('name', 'Guest_Experience_Enhancement');
});

it('create', async () => {
const sfProject = SfProject.getInstance();
const agent = new Agent(connection, sfProject);
Expand Down
45 changes: 45 additions & 0 deletions test/mocks/connect_ai-assist_draft-agent-topics.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"isSuccess": true,
"topics": [
{
"name": "Guest_Experience_Enhancement",
"description": "Develop and implement entertainment programs to enhance guest experience."
},
{
"name": "Event_Planning_and_Execution",
"description": "Plan, organize, and execute resort events and activities."
},
{
"name": "Vendor_Management",
"description": "Coordinate with external vendors for event supplies and services."
},
{
"name": "Staff_Training_and_Development",
"description": "Train and develop staff to deliver exceptional entertainment services."
},
{
"name": "Budget_Management",
"description": "Manage budgets for entertainment activities and events."
},
{
"name": "Guest_Feedback_Analysis",
"description": "Collect and analyze guest feedback to improve entertainment offerings."
},
{
"name": "Marketing_Collaboration",
"description": "Work with marketing to promote events and entertainment activities."
},
{
"name": "Technology_Integration",
"description": "Utilize technology to enhance guest engagement and streamline operations."
},
{
"name": "Safety_and_Compliance",
"description": "Ensure all entertainment activities comply with safety regulations."
},
{
"name": "Performance_Monitoring",
"description": "Monitor and evaluate the performance of entertainment programs."
}
]
}

0 comments on commit 72460df

Please sign in to comment.