-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add initial agent job spec create and mock
- Loading branch information
Showing
9 changed files
with
1,062 additions
and
2,804 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
/* | ||
* 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 { join } from 'node:path'; | ||
import { readFileSync, statSync } from 'node:fs'; | ||
import { inspect } from 'node:util'; | ||
import { Connection, Logger, SfError, SfProject } from '@salesforce/core'; | ||
import { getMockDir } from './mockDir.js'; | ||
import { | ||
type SfAgent, | ||
type AgentCreateConfig, | ||
type AgentJobSpec, | ||
type AgentJobSpecCreateConfig, | ||
type AgentJobSpecCreateResponse | ||
} from './types.js' | ||
|
||
export class Agent implements SfAgent { | ||
private logger: Logger; | ||
private mockDir?: string; | ||
|
||
public constructor(private connection: Connection, private project: SfProject) { | ||
this.logger = Logger.childFromRoot(this.constructor.name); | ||
this.mockDir = getMockDir(); | ||
} | ||
|
||
public async create(config: AgentCreateConfig): Promise<void> { | ||
this.logger.debug(`Creating Agent using config: ${inspect(config)} in project: ${this.project.getPath()}`); | ||
// Generate a GenAiPlanner in the local project and deploy | ||
|
||
// make API request to /services/data/{api-version}/connect/attach-agent-topics | ||
|
||
// on success, retrieve all Agent metadata | ||
} | ||
|
||
/** | ||
* Create an agent spec from provided data. | ||
* | ||
* @param config The configuration used to generate an agent spec. | ||
*/ | ||
public async createSpec(config: AgentJobSpecCreateConfig): Promise<AgentJobSpec> { | ||
this.verifyAgentSpecConfig(config); | ||
|
||
let agentSpec: AgentJobSpec; | ||
|
||
if (this.mockDir) { | ||
const specFileName = `${config.name}Spec.json`; | ||
const specFilePath = join(this.mockDir, `${specFileName}`); | ||
try { | ||
this.logger.debug(`Using mock directory: ${this.mockDir} for agent job spec creation`); | ||
statSync(specFilePath); | ||
} catch(err) { | ||
throw SfError.create({ | ||
name: 'MissingMockFile', | ||
message: `SF_MOCK_DIR [${this.mockDir}] must contain a spec file with name ${specFileName}`, | ||
cause: err, | ||
}); | ||
} | ||
try { | ||
this.logger.debug(`Returning mock agent spec file: ${specFilePath}`); | ||
agentSpec = JSON.parse(readFileSync(specFilePath, 'utf8')) as AgentJobSpec; | ||
} catch(err) { | ||
throw SfError.create({ | ||
name: 'InvalidMockFile', | ||
message: `SF_MOCK_DIR [${this.mockDir}] must contain a valid spec file with name ${specFileName}`, | ||
cause: err, | ||
actions: [ | ||
'Check that the file is readable', | ||
'Check that the file is a valid JSON array of jobTitle and jobDescription objects' | ||
] | ||
}); | ||
} | ||
} else { | ||
// TODO: We'll probably want to wrap this for better error handling but let's see | ||
// what it looks like first. | ||
const response = await this.connection.requestGet<AgentJobSpecCreateResponse>(this.buildAgentJobSpecUrl(config), { | ||
retry: { maxRetries: 3 } | ||
}); | ||
if (response.isSuccess) { | ||
agentSpec = response?.jobSpecs as AgentJobSpec; | ||
} else { | ||
throw SfError.create({ | ||
name: 'AgentJobSpecCreateError', | ||
message: response.errorMessage ?? 'unknown', | ||
}); | ||
} | ||
} | ||
|
||
return agentSpec; | ||
} | ||
|
||
private verifyAgentSpecConfig(config: AgentJobSpecCreateConfig): void { | ||
// TBD: for now just return. At some point verify all required config values. | ||
if (config) return; | ||
} | ||
|
||
private buildAgentJobSpecUrl(config: AgentJobSpecCreateConfig): string { | ||
const { type, role, companyName, companyDescription, companyWebsite } = config; | ||
const website = companyWebsite ? `&${companyWebsite}` : ''; | ||
return `/connect/agent-job-spec?${type}&${role}&${companyName}&${companyDescription}${website}`; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 { resolve } from 'node:path'; | ||
import { type Stats, statSync } from 'node:fs'; | ||
import { SfError } from '@salesforce/core'; | ||
import { env } from '@salesforce/kit'; | ||
|
||
/** | ||
* If the `SF_MOCK_DIR` environment variable is set, resolve to an absolue path | ||
* and ensure the directory exits, then return the path. | ||
* | ||
* NOTE: THIS SHOULD BE MOVED TO SOME OTHER LIBRARY LIKE `@salesforce/kit`. | ||
* | ||
* @returns the absolute path to an existing directory used for mocking behavior | ||
*/ | ||
export const getMockDir = (): string | undefined => { | ||
const mockDir = env.getString('SF_MOCK_DIR'); | ||
if (mockDir) { | ||
let mockDirStat: Stats; | ||
try { | ||
mockDirStat = statSync(resolve(mockDir)); | ||
} catch (err) { | ||
throw SfError.create({ | ||
name: 'InvalidMockDir', | ||
message: `SF_MOCK_DIR [${mockDir}] not found`, | ||
cause: err, | ||
actions: ['If you\'re trying to mock agent behavior you must create the mock directory and add expected mock files to it.'] | ||
}); | ||
} | ||
|
||
if (!mockDirStat.isDirectory()) { | ||
throw SfError.create({ | ||
name: 'InvalidMockDir', | ||
message: `SF_MOCK_DIR [${mockDir}] is not a directory`, | ||
actions: ['If you\'re trying to mock agent behavior you must create the mock directory and add expected mock files to it.'] | ||
}); | ||
} | ||
return mockDir; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
/* | ||
* 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 | ||
*/ | ||
|
||
/** | ||
* An agent spec is a list of job titles and descriptions | ||
* to be performed by the agent. | ||
*/ | ||
export type AgentJobSpec = [{ | ||
jobTitle: string; | ||
jobDescription: string; | ||
}]; | ||
|
||
/** | ||
* The parameters needed to generate an agent spec. | ||
*/ | ||
export type AgentJobSpecCreateConfig = { | ||
name: string; | ||
type: 'customer_facing' | 'employee_facing'; | ||
role: string; | ||
companyName: string; | ||
companyDescription: string; | ||
companyWebsite?: string; | ||
} | ||
|
||
/** | ||
* The parameters needed to generate an agent in an org. | ||
* | ||
* NOTE: This is likely to change with planned serverside APIs. | ||
*/ | ||
export type AgentCreateConfig = AgentJobSpecCreateConfig & { | ||
spec: AgentJobSpec; | ||
} | ||
|
||
/** | ||
* An interface for working with Agents. | ||
*/ | ||
export type SfAgent = { | ||
create(config: AgentCreateConfig): Promise<void>; | ||
createSpec(config: AgentJobSpecCreateConfig): Promise<AgentJobSpec>; | ||
} | ||
|
||
/** | ||
* The response from the `agent-job-spec` API. | ||
*/ | ||
export type AgentJobSpecCreateResponse = { | ||
isSuccess: boolean; | ||
errorMessage?: string; | ||
jobSpecs?: AgentJobSpec; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/* | ||
* Copyright (c) 2023, 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 { expect } from 'chai'; | ||
import { MockTestOrgData, TestContext } from '@salesforce/core/testSetup'; | ||
import { SfProject } from '@salesforce/core'; | ||
import { Agent } from '../src/agent.js'; | ||
|
||
describe('agent job spec create test', () => { | ||
|
||
const $$ = new TestContext(); | ||
const testOrg = new MockTestOrgData(); | ||
$$.inProject(true); | ||
|
||
it('runs agent run test', async () => { | ||
const connection = await testOrg.getConnection(); | ||
const sfProject = SfProject.getInstance(); | ||
const agent = new Agent(connection, sfProject); | ||
const output = agent.createSpec({ | ||
name: 'MyFirstAgent', | ||
type: 'customer_facing', | ||
role: 'answer questions about vacation rentals', | ||
companyName: 'Coral Cloud Enterprises', | ||
companyDescription: 'Provide vacation rentals and activities', | ||
}); | ||
expect(output).to.be.ok; | ||
}); | ||
}); |
Oops, something went wrong.