From eaa205ced963af6cb518c2740ebe25bac2aec1a0 Mon Sep 17 00:00:00 2001 From: mkm17 Date: Sun, 17 Aug 2025 21:38:10 +0200 Subject: [PATCH] Adds `spo agent add` command. Closes #6763 --- docs/docs/cmd/spo/agent/agent-add.mdx | 171 +++ docs/src/config/sidebars.ts | 9 + src/m365/spo/commands.ts | 1 + src/m365/spo/commands/agent/agent-add.spec.ts | 988 ++++++++++++++++++ src/m365/spo/commands/agent/agent-add.ts | 251 +++++ 5 files changed, 1420 insertions(+) create mode 100644 docs/docs/cmd/spo/agent/agent-add.mdx create mode 100644 src/m365/spo/commands/agent/agent-add.spec.ts create mode 100644 src/m365/spo/commands/agent/agent-add.ts diff --git a/docs/docs/cmd/spo/agent/agent-add.mdx b/docs/docs/cmd/spo/agent/agent-add.mdx new file mode 100644 index 00000000000..d7d96b9382d --- /dev/null +++ b/docs/docs/cmd/spo/agent/agent-add.mdx @@ -0,0 +1,171 @@ +import Global from '/docs/cmd/_global.mdx'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# spo agent add + +Adds a new SharePoint agent + +## Usage + +```sh +m365 spo agent add [options] +``` + +## Options + +```md definition-list +`-u, --webUrl ` +: URL of the site where the agent should be added. + +`-n, --name ` +: The name of the agent. + +`-a, --agentInstructions ` +: The definition of the agent's role, tone, and limitations. + +`-w, --welcomeMessage ` +: The welcome message. + +`-s, --sourceUrls ` +: Comma-separated list of URLs of the SharePoint sites, libraries, or files that are the sources of information. + +`-d, --description ` +: The brief description of the agent's objective. + +`-i, --icon [icon]` +: The URL of the icon. + +`-c, --conversationStarters [conversationStarters]` +: Starter prompts. Please provide each prompt separated by a comma. +``` + + + +## Examples + +Add a SharePoint agent with site and library sources. + +```sh +m365 spo agent add --webUrl https://contoso.sharepoint.com/sites/projectSite --name "Project Assistant" --agentInstructions "You are a helpful assistant for project management tasks. Be professional and concise." --welcomeMessage "Hello! I'm here to help you with project management tasks." --sourceUrls "https://contoso.sharepoint.com/sites/projectSite,https://contoso.sharepoint.com/sites/projectSite/Shared Documents/Forms/AllItems.aspx" --description "A helpful agent for project management assistance" +``` + +Add a SharePoint agent with icon and conversation starters. + +```sh +m365 spo agent add --webUrl https://contoso.sharepoint.com/sites/projectSite --name "HR Assistant" --agentInstructions "You are an HR assistant. Help with HR policies and procedures." --welcomeMessage "Welcome! I can help you with HR-related questions." --sourceUrls "https://contoso.sharepoint.com/sites/hr" --icon "https://contoso.sharepoint.com/sites/projectSite/SiteAssets/hr-icon.png" --conversationStarters "What are the vacation policies?,How do I submit a timesheet?,Where can I find the employee handbook?" --description "A helpful agent for project management assistance" +``` + +Add a basic SharePoint agent with minimal configuration. + +```sh +m365 spo agent add --webUrl https://contoso.sharepoint.com/sites/marketing --name "Marketing Bot" --agentInstructions "Assist with marketing campaign information and resources." --welcomeMessage "Hi! I can help you find marketing resources." --sourceUrls "https://contoso.sharepoint.com/sites/marketing/Campaigns" --description "A helpful agent for project management assistance" +``` + +## Response + + + + + ```json + { + "CheckInComment": "", + "CheckOutType": 2, + "ContentTag": "{95784749-23D7-410D-BAEF-E694328F89AB},1,1", + "CustomizedPageStatus": 0, + "ETag": "\"{95784749-23D7-410D-BAEF-E694328F89AB},1\"", + "Exists": true, + "ExistsAllowThrowForPolicyFailures": true, + "ExistsWithException": true, + "IrmEnabled": false, + "Length": "4543", + "Level": 1, + "LinkingUri": null, + "LinkingUrl": "", + "MajorVersion": 1, + "MinorVersion": 0, + "Name": "Test Agent (4).agent", + "ServerRelativeUrl": "/sites/TestSite/SiteAssets/Copilots/Test Agent.agent", + "TimeCreated": "2025-08-19T06:49:03Z", + "TimeLastModified": "2025-08-19T06:49:03Z", + "Title": null, + "UIVersion": 512, + "UIVersionLabel": "1.0", + "UniqueId": "95784749-23d7-410d-baef-e694328f89ab" + } + ``` + + + + + ```text + CheckOutType : 2 + ContentTag : {802D355C-0917-44D9-A51C-6FD873572700},1,1 + CustomizedPageStatus : 0 + ETag : "{802D355C-0917-44D9-A51C-6FD873572700},1" + Exists : true + ExistsAllowThrowForPolicyFailures: true + ExistsWithException : true + IrmEnabled : false + Length : 4543 + Level : 1 + LinkingUri : null + LinkingUrl : + MajorVersion : 1 + MinorVersion : 0 + Name : Test Agent.agent + ServerRelativeUrl : /sites/TestSite/SiteAssets/Copilots/Test Agent.agent + TimeCreated : 2025-08-19T06:48:26Z + TimeLastModified : 2025-08-19T06:48:26Z + Title : null + UIVersion : 512 + UIVersionLabel : 1.0 + UniqueId : 802d355c-0917-44d9-a51c-6fd873572700 + ``` + + + + + ```csv + CheckInComment,CheckOutType,ContentTag,CustomizedPageStatus,ETag,Exists,ExistsAllowThrowForPolicyFailures,ExistsWithException,IrmEnabled,Length,Level,Linkin + gUri,LinkingUrl,MajorVersion,MinorVersion,Name,ServerRelativeUrl,TimeCreated,TimeLastModified,Title,UIVersion,UIVersionLabel,UniqueId + ,2,"{A8541C6C-163E-48B9-AD8C-33D9669C148A},1,1",0,"""{A8541C6C-163E-48B9-AD8C-33D9669C148A},1""",1,1,1,0,4543,1,,,1,0,Test Agent.agent,/sites/TestSite/SiteAssets/Copilots/Test Agent.agent,2025-08-19T06:47:57Z,2025-08-19T06:47:57Z,,512,1.0,a8541c6c-163e-48b9-ad8c-33d9669c148a + ``` + + + + + ```md + # spo agent add --debug "false" --verbose "false" --webUrl "https://contoso.sharepoint.com/sites/TestSite/" --name "Test Agent" -- +agentInstructions "You are a comprehensive test agent" --welcomeMessage "Welcome to the comprehensive test agent" --sourceUrls "https://contoso.sharepoint.com/sites/TestSite/Shared%20Documents/Forms/AllItems" --description "A comprehensive test agent" + Date: 19/08/2025 + + ## Test Agent.agent (ad6f7a4e-4582-4cc4-bae6-65ade97e5e71) + + Property | Value + ---------|------- + CheckInComment | + CheckOutType | 2 + ContentTag | {AD6F7A4E-4582-4CC4-BAE6-65ADE97E5E71},1,1 + CustomizedPageStatus | 0 + ETag | "{AD6F7A4E-4582-4CC4-BAE6-65ADE97E5E71},1" + Exists | true + ExistsAllowThrowForPolicyFailures | true + ExistsWithException | true + IrmEnabled | false + Length | 4543 + Level | 1 + LinkingUrl | + MajorVersion | 1 + MinorVersion | 0 + Name | Test Agent.agent + ServerRelativeUrl | /sites/TestSite/SiteAssets/Copilots/Test Agent.agent + TimeCreated | 2025-08-19T06:46:30Z + TimeLastModified | 2025-08-19T06:46:30Z + UIVersion | 512 + UIVersionLabel | 1.0 + UniqueId | ad6f7a4e-4582-4cc4-bae6-65ade97e5e71 + ``` + + + diff --git a/docs/src/config/sidebars.ts b/docs/src/config/sidebars.ts index 255acff1a88..49917442121 100644 --- a/docs/src/config/sidebars.ts +++ b/docs/src/config/sidebars.ts @@ -2303,6 +2303,15 @@ const sidebars: SidebarsConfig = { label: 'set', id: 'cmd/spo/spo-set' }, + { + agent: [ + { + type: 'doc', + label: 'agent add', + id: 'cmd/spo/agent/agent-add' + }, + ] + }, { app: [ { diff --git a/src/m365/spo/commands.ts b/src/m365/spo/commands.ts index af9f2844b94..59bd05a601c 100644 --- a/src/m365/spo/commands.ts +++ b/src/m365/spo/commands.ts @@ -1,6 +1,7 @@ const prefix: string = 'spo'; export default { + AGENT_ADD: `${prefix} agent add`, APP_ADD: `${prefix} app add`, APP_DEPLOY: `${prefix} app deploy`, APP_GET: `${prefix} app get`, diff --git a/src/m365/spo/commands/agent/agent-add.spec.ts b/src/m365/spo/commands/agent/agent-add.spec.ts new file mode 100644 index 00000000000..8654b47c9fa --- /dev/null +++ b/src/m365/spo/commands/agent/agent-add.spec.ts @@ -0,0 +1,988 @@ +/* eslint-disable camelcase */ +import assert from 'assert'; +import sinon from 'sinon'; +import { z } from 'zod'; +import auth from '../../../../Auth.js'; +import { cli } from '../../../../cli/cli.js'; +import { CommandInfo } from '../../../../cli/CommandInfo.js'; +import { Logger } from '../../../../cli/Logger.js'; +import { CommandError } from '../../../../Command.js'; +import request from '../../../../request.js'; +import { telemetry } from '../../../../telemetry.js'; +import { pid } from '../../../../utils/pid.js'; +import { session } from '../../../../utils/session.js'; +import { sinonUtil } from '../../../../utils/sinonUtil.js'; +import { spo } from '../../../../utils/spo.js'; +import commands from '../../commands.js'; +import command from './agent-add.js'; + +describe(commands.AGENT_ADD, () => { + let log: any[]; + let logger: Logger; + let commandInfo: CommandInfo; + let commandOptionsSchema: z.ZodTypeAny; + + before(() => { + sinon.stub(auth, 'restoreAuth').resolves(); + sinon.stub(telemetry, 'trackEvent').resolves(); + sinon.stub(pid, 'getProcessName').returns(''); + sinon.stub(session, 'getId').returns(''); + sinon.stub(spo, 'getRequestDigest').resolves({ + FormDigestValue: 'ABC', + FormDigestTimeoutSeconds: 1800, + FormDigestExpiresAt: new Date(), + WebFullUrl: 'https://contoso.sharepoint.com' + }); + sinon.stub(spo, 'ensureFolder').resolves(); + auth.connection.active = true; + commandInfo = cli.getCommandInfo(command); + commandOptionsSchema = commandInfo.command.getSchemaToParse()!; + }); + + beforeEach(() => { + log = []; + logger = { + log: async (msg: string) => { + log.push(msg); + }, + logRaw: async (msg: string) => { + log.push(msg); + }, + logToStderr: async (msg: string) => { + log.push(msg); + } + }; + }); + + afterEach(() => { + sinonUtil.restore([ + request.post, + request.get + ]); + }); + + after(() => { + sinon.restore(); + auth.connection.active = false; + }); + + it('has correct name', () => { + assert.strictEqual(command.name, commands.AGENT_ADD); + }); + + it('has a description', () => { + assert.notStrictEqual(command.description, null); + }); + + it('creates a SharePoint agent with required options', async () => { + const postStub = sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === "https://contoso.sharepoint.com/sites/test/_api/web/GetFolderByServerRelativePath(DecodedUrl='/sites/test/SiteAssets/Copilots')/Files/AddUsingPath(DecodedUrl='Test%20Agent.agent',EnsureUniqueFileName=true,AutoCheckoutOnInvalidData=true)" + ) { + return; + } + + if (opts.url === 'https://contoso.sharepoint.com/sites/test/_api/web/lists/EnsureSiteAssetsLibrary()') { + return; + } + + if (opts.url === 'https://contoso.sharepoint.com/sites/test/_api/search/postquery') { + return { + PrimaryQueryResult: { + RelevantResults: { + Table: { + Rows: [ + { + Cells: [ + { Key: "contentclass", Value: "STS_ListItem_DocumentLibrary" }, + { Key: "Title", Value: "Test Document" }, + { Key: "Path", Value: "https://contoso.sharepoint.com/sites/test/Shared Documents/Test Document.docx" }, + { Key: "SiteName", Value: "Test Site" }, + { Key: "SiteTitle", Value: "Test Site" }, + { Key: "ListID", Value: "b1a5e7c2-3d4f-4e6a-9b8c-2f3e4d5c6b7a" }, + { Key: "ListItemID", Value: "a7c6b5d4-e3f2-1a09-b8c7-6e5d4c3b2a1f" }, + { Key: "SiteID", Value: "f1e2d3c4-b5a6-7890-1234-56789abcdef0" }, + { Key: "WebId", Value: "123e4567-e89b-12d3-a456-426614174000" }, + { Key: "UniqueID", Value: "{0f1e2d3c-4b5a-6789-0123-456789abcdef}" }, + { Key: "IsDocument", Value: "true" }, + { Key: "IsContainer", Value: "false" } + ] + } + ] + } + } + } + }; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { + options: { + webUrl: 'https://contoso.sharepoint.com/sites/test', + name: 'Test Agent', + agentInstructions: 'You are a helpful test agent', + welcomeMessage: 'Hello! I am your test agent.', + sourceUrls: 'https://contoso.sharepoint.com/sites/test', + description: 'A test agent' + } + }); + + assert.deepStrictEqual(postStub.lastCall.args[0].data, + { + schemaVersion: "0.2.0", + customCopilotConfig: { + conversationStarters: { + conversationStarterList: [], + welcomeMessage: { + text: "Hello! I am your test agent." + } + }, + gptDefinition: { + name: "Test Agent", + description: "A test agent", + instructions: "You are a helpful test agent", + capabilities: [ + { + name: "OneDriveAndSharePoint", + items_by_sharepoint_ids: [ + { + url: "https://contoso.sharepoint.com/sites/test", + name: "Test Document", + site_id: "f1e2d3c4-b5a6-7890-1234-56789abcdef0", + web_id: "123e4567-e89b-12d3-a456-426614174000", + list_id: "b1a5e7c2-3d4f-4e6a-9b8c-2f3e4d5c6b7a", + unique_id: "0f1e2d3c-4b5a-6789-0123-456789abcdef", + type: "File" + } + ], + items_by_url: [] + } + ] + }, + icon: "data:image/png;base64,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" + } + } + ); + }); + + it('creates a SharePoint agent with all options including optional ones', async () => { + const postStub = sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === "https://contoso.sharepoint.com/sites/test/_api/web/GetFolderByServerRelativePath(DecodedUrl='/sites/test/SiteAssets/Copilots')/Files/AddUsingPath(DecodedUrl='Complete%20Agent.agent',EnsureUniqueFileName=true,AutoCheckoutOnInvalidData=true)" + ) { + return; + } + + if (opts.url === 'https://contoso.sharepoint.com/sites/test/_api/web/lists/EnsureSiteAssetsLibrary()') { + return; + } + + if (opts.url === 'https://contoso.sharepoint.com/sites/test/_api/search/postquery') { + return { + PrimaryQueryResult: { + RelevantResults: { + Table: { + Rows: [ + { + Cells: [ + { Key: "contentclass", Value: "STS_ListItem_DocumentLibrary" }, + { Key: "Title", Value: "Test Document" }, + { Key: "Path", Value: "https://contoso.sharepoint.com/sites/test/Shared Documents/Test Document.docx" }, + { Key: "SiteName", Value: "Test Site" }, + { Key: "SiteTitle", Value: "Test Site" }, + { Key: "ListID", Value: "b1a5e7c2-3d4f-4e6a-9b8c-2f3e4d5c6b7a" }, + { Key: "ListItemID", Value: "a7c6b5d4-e3f2-1a09-b8c7-6e5d4c3b2a1f" }, + { Key: "SiteID", Value: "f1e2d3c4-b5a6-7890-1234-56789abcdef0" }, + { Key: "WebId", Value: "123e4567-e89b-12d3-a456-426614174000" }, + { Key: "UniqueID", Value: "{0f1e2d3c-4b5a-6789-0123-456789abcdef}" }, + { Key: "IsDocument", Value: "true" }, + { Key: "IsContainer", Value: "false" } + ] + } + ] + } + } + } + }; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { + options: { + webUrl: 'https://contoso.sharepoint.com/sites/test', + name: 'Complete Agent', + agentInstructions: 'You are a comprehensive test agent', + welcomeMessage: 'Welcome to the comprehensive test agent', + sourceUrls: 'https://contoso.sharepoint.com/sites/test/Shared Documents/Test Document.docx,https://contoso.sharepoint.com/sites/test/Shared Documents/Test Document.docx', + description: 'A comprehensive test agent', + icon: 'https://contoso.sharepoint.com/sites/test/SiteAssets/agent-icon.png', + conversationStarters: 'What can you help me with?,Show me recent documents,Help me with my tasks', + verbose: true + } + }); + + assert.deepStrictEqual(postStub.lastCall.args[0].data, + { + schemaVersion: "0.2.0", + customCopilotConfig: { + conversationStarters: { + conversationStarterList: [ + { + text: "What can you help me with?" + }, + { + text: "Show me recent documents" + }, + { + text: "Help me with my tasks" + } + ], + welcomeMessage: { + text: "Welcome to the comprehensive test agent" + } + }, + gptDefinition: { + name: "Complete Agent", + description: "A comprehensive test agent", + instructions: "You are a comprehensive test agent", + capabilities: [ + { + name: "OneDriveAndSharePoint", + items_by_sharepoint_ids: [ + { + url: "https://contoso.sharepoint.com/sites/test/Shared Documents/Test Document.docx", + name: "Test Document", + site_id: "f1e2d3c4-b5a6-7890-1234-56789abcdef0", + web_id: "123e4567-e89b-12d3-a456-426614174000", + list_id: "b1a5e7c2-3d4f-4e6a-9b8c-2f3e4d5c6b7a", + unique_id: "0f1e2d3c-4b5a-6789-0123-456789abcdef", + type: "File" + }, + { + url: "https://contoso.sharepoint.com/sites/test/Shared Documents/Test Document.docx", + name: "Test Document", + site_id: "f1e2d3c4-b5a6-7890-1234-56789abcdef0", + web_id: "123e4567-e89b-12d3-a456-426614174000", + list_id: "b1a5e7c2-3d4f-4e6a-9b8c-2f3e4d5c6b7a", + unique_id: "0f1e2d3c-4b5a-6789-0123-456789abcdef", + type: "File" + } + ], + items_by_url: [ + ] + } + ] + }, + icon: "https://contoso.sharepoint.com/sites/test/SiteAssets/agent-icon.png" + } + } + ); + }); + + it('creates a SharePoint agent with not found resource in search', async () => { + const postStub = sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === "https://contoso.sharepoint.com/sites/test/_api/web/GetFolderByServerRelativePath(DecodedUrl='/sites/test/SiteAssets/Copilots')/Files/AddUsingPath(DecodedUrl='Complete%20Agent.agent',EnsureUniqueFileName=true,AutoCheckoutOnInvalidData=true)" + ) { + return; + } + + if (opts.url === 'https://contoso.sharepoint.com/sites/test/_api/web/lists/EnsureSiteAssetsLibrary()') { + return; + } + + if (opts.url === 'https://contoso.sharepoint.com/sites/test/_api/search/postquery') { + return { + PrimaryQueryResult: { + RelevantResults: { + Table: { + Rows: [ + ] + } + } + } + }; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { + options: { + webUrl: 'https://contoso.sharepoint.com/sites/test', + name: 'Complete Agent', + agentInstructions: 'You are a comprehensive test agent', + welcomeMessage: 'Welcome to the comprehensive test agent', + sourceUrls: 'https://contoso.sharepoint.com/sites/test/Shared Documents/Test Document.docx', + description: 'A comprehensive test agent', + icon: 'https://contoso.sharepoint.com/sites/test/SiteAssets/agent-icon.png', + conversationStarters: 'What can you help me with?,Show me recent documents,Help me with my tasks' + } + }); + + assert.deepStrictEqual(postStub.lastCall.args[0].data, + { + schemaVersion: "0.2.0", + customCopilotConfig: { + conversationStarters: { + conversationStarterList: [ + { + text: "What can you help me with?" + }, + { + text: "Show me recent documents" + }, + { + text: "Help me with my tasks" + } + ], + welcomeMessage: { + text: "Welcome to the comprehensive test agent" + } + }, + gptDefinition: { + name: "Complete Agent", + description: "A comprehensive test agent", + instructions: "You are a comprehensive test agent", + capabilities: [ + { + name: "OneDriveAndSharePoint", + items_by_sharepoint_ids: [ + ], + items_by_url: [ + ] + } + ] + }, + icon: "https://contoso.sharepoint.com/sites/test/SiteAssets/agent-icon.png" + } + } + ); + }); + + it('creates a SharePoint agent with Site resource', async () => { + const postStub = sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === "https://contoso.sharepoint.com/sites/test/_api/web/GetFolderByServerRelativePath(DecodedUrl='/sites/test/SiteAssets/Copilots')/Files/AddUsingPath(DecodedUrl='Test%20Agent.agent',EnsureUniqueFileName=true,AutoCheckoutOnInvalidData=true)" + ) { + return; + } + + if (opts.url === 'https://contoso.sharepoint.com/sites/test/_api/web/lists/EnsureSiteAssetsLibrary()') { + return; + } + + if (opts.url === 'https://contoso.sharepoint.com/sites/test/_api/search/postquery') { + return { + PrimaryQueryResult: { + RelevantResults: { + Table: { + Rows: [ + { + Cells: [ + { Key: "contentclass", Value: "STS_Site" }, + { Key: "Title", Value: "Test Site" }, + { Key: "Path", Value: "https://contoso.sharepoint.com/sites/test" }, + { Key: "SiteName", Value: "Test Site" }, + { Key: "SiteTitle", Value: "Test Site" }, + { Key: "SiteID", Value: "f1e2d3c4-b5a6-7890-1234-56789abcdef0" }, + { Key: "WebId", Value: "123e4567-e89b-12d3-a456-426614174000" }, + { Key: "UniqueID", Value: "{0f1e2d3c-4b5a-6789-0123-456789abcdef}" }, + { Key: "IsDocument", Value: "false" }, + { Key: "IsContainer", Value: "false" } + ] + } + ] + } + } + } + }; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { + options: { + webUrl: 'https://contoso.sharepoint.com/sites/test', + name: 'Test Agent', + agentInstructions: 'You are a helpful test agent', + welcomeMessage: 'Hello! I am your test agent.', + sourceUrls: 'https://contoso.sharepoint.com/sites/test', + description: 'A test agent' + } + }); + + assert.deepStrictEqual(postStub.lastCall.args[0].data, + { + schemaVersion: "0.2.0", + customCopilotConfig: { + conversationStarters: { + conversationStarterList: [], + welcomeMessage: { + text: "Hello! I am your test agent." + } + }, + gptDefinition: { + name: "Test Agent", + description: "A test agent", + instructions: "You are a helpful test agent", + capabilities: [ + { + name: "OneDriveAndSharePoint", + items_by_sharepoint_ids: [ + ], + items_by_url: [ + { + url: "https://contoso.sharepoint.com/sites/test", + name: "Test Site", + site_id: "f1e2d3c4-b5a6-7890-1234-56789abcdef0", + web_id: "123e4567-e89b-12d3-a456-426614174000", + list_id: "", + unique_id: "0f1e2d3c-4b5a-6789-0123-456789abcdef", + type: "Site" + } + ] + } + ] + }, + icon: "data:image/png;base64,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" + } + } + ); + }); + + it('creates a SharePoint agent with Subsite resource', async () => { + const postStub = sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === "https://contoso.sharepoint.com/sites/test/_api/web/GetFolderByServerRelativePath(DecodedUrl='/sites/test/SiteAssets/Copilots')/Files/AddUsingPath(DecodedUrl='Test%20Agent.agent',EnsureUniqueFileName=true,AutoCheckoutOnInvalidData=true)" + ) { + return; + } + + if (opts.url === 'https://contoso.sharepoint.com/sites/test/_api/web/lists/EnsureSiteAssetsLibrary()') { + return; + } + + if (opts.url === 'https://contoso.sharepoint.com/sites/test/_api/search/postquery') { + return { + PrimaryQueryResult: { + RelevantResults: { + Table: { + Rows: [ + { + Cells: [ + { Key: "contentclass", Value: "STS_Web" }, + { Key: "Title", Value: "Test Site" }, + { Key: "Path", Value: "https://contoso.sharepoint.com/sites/test/subsite" }, + { Key: "SiteName", Value: "Test Site" }, + { Key: "SiteTitle", Value: "Test Site" }, + { Key: "SiteID", Value: "f1e2d3c4-b5a6-7890-1234-56789abcdef0" }, + { Key: "WebId", Value: "123e4567-e89b-12d3-a456-426614174000" }, + { Key: "UniqueID", Value: "{0f1e2d3c-4b5a-6789-0123-456789abcdef}" }, + { Key: "IsDocument", Value: "false" }, + { Key: "IsContainer", Value: "false" } + ] + } + ] + } + } + } + }; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { + options: { + webUrl: 'https://contoso.sharepoint.com/sites/test', + name: 'Test Agent', + agentInstructions: 'You are a helpful test agent', + welcomeMessage: 'Hello! I am your test agent.', + sourceUrls: 'https://contoso.sharepoint.com/sites/test/subsite', + description: 'A test agent' + } + }); + + assert.deepStrictEqual(postStub.lastCall.args[0].data, + { + schemaVersion: "0.2.0", + customCopilotConfig: { + conversationStarters: { + conversationStarterList: [], + welcomeMessage: { + text: "Hello! I am your test agent." + } + }, + gptDefinition: { + name: "Test Agent", + description: "A test agent", + instructions: "You are a helpful test agent", + capabilities: [ + { + name: "OneDriveAndSharePoint", + items_by_sharepoint_ids: [ + ], + items_by_url: [ + { + url: "https://contoso.sharepoint.com/sites/test/subsite", + name: "Test Site", + site_id: "f1e2d3c4-b5a6-7890-1234-56789abcdef0", + web_id: "123e4567-e89b-12d3-a456-426614174000", + list_id: "", + unique_id: "0f1e2d3c-4b5a-6789-0123-456789abcdef", + type: "Site" + } + ] + } + ] + }, + icon: "data:image/png;base64,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" + } + } + ); + }); + + it('creates a SharePoint agent with Document Library resource', async () => { + const postStub = sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === "https://contoso.sharepoint.com/sites/test/_api/web/GetFolderByServerRelativePath(DecodedUrl='/sites/test/SiteAssets/Copilots')/Files/AddUsingPath(DecodedUrl='Test%20Agent.agent',EnsureUniqueFileName=true,AutoCheckoutOnInvalidData=true)" + ) { + return; + } + + if (opts.url === 'https://contoso.sharepoint.com/sites/test/_api/web/lists/EnsureSiteAssetsLibrary()') { + return; + } + + if (opts.url === 'https://contoso.sharepoint.com/sites/test/_api/search/postquery') { + return { + PrimaryQueryResult: { + RelevantResults: { + Table: { + Rows: [ + { + Cells: [ + { Key: "contentclass", Value: "STS_List_DocumentLibrary" }, + { Key: "Title", Value: "Test Library" }, + { Key: "Path", Value: "https://contoso.sharepoint.com/sites/test/documents" }, + { Key: "SiteName", Value: "Test Site" }, + { Key: "SiteTitle", Value: "Test Site" }, + { Key: "SiteID", Value: "f1e2d3c4-b5a6-7890-1234-56789abcdef0" }, + { Key: "WebId", Value: "123e4567-e89b-12d3-a456-426614174000" }, + { Key: "ListID", Value: "b1a5e7c2-3d4f-4e6a-9b8c-2f3e4d5c6b7a" }, + { Key: "UniqueID", Value: "{0f1e2d3c-4b5a-6789-0123-456789abcdef}" }, + { Key: "IsDocument", Value: "false" }, + { Key: "IsContainer", Value: "false" } + ] + } + ] + } + } + } + }; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { + options: { + webUrl: 'https://contoso.sharepoint.com/sites/test', + name: 'Test Agent', + agentInstructions: 'You are a helpful test agent', + welcomeMessage: 'Hello! I am your test agent.', + sourceUrls: 'https://contoso.sharepoint.com/sites/test/documents', + description: 'A test agent' + } + }); + + assert.deepStrictEqual(postStub.lastCall.args[0].data, + { + schemaVersion: "0.2.0", + customCopilotConfig: { + conversationStarters: { + conversationStarterList: [], + welcomeMessage: { + text: "Hello! I am your test agent." + } + }, + gptDefinition: { + name: "Test Agent", + description: "A test agent", + instructions: "You are a helpful test agent", + capabilities: [ + { + name: "OneDriveAndSharePoint", + items_by_sharepoint_ids: [ + ], + items_by_url: [ + { + url: "https://contoso.sharepoint.com/sites/test/documents", + name: "Test Library", + site_id: "f1e2d3c4-b5a6-7890-1234-56789abcdef0", + web_id: "123e4567-e89b-12d3-a456-426614174000", + list_id: "b1a5e7c2-3d4f-4e6a-9b8c-2f3e4d5c6b7a", + unique_id: "0f1e2d3c-4b5a-6789-0123-456789abcdef", + type: "List" + } + ] + } + ] + }, + icon: "data:image/png;base64,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" + } + } + ); + }); + + it('creates a SharePoint agent with Folder resource', async () => { + const postStub = sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === "https://contoso.sharepoint.com/sites/test/_api/web/GetFolderByServerRelativePath(DecodedUrl='/sites/test/SiteAssets/Copilots')/Files/AddUsingPath(DecodedUrl='Test%20Agent.agent',EnsureUniqueFileName=true,AutoCheckoutOnInvalidData=true)" + ) { + return; + } + + if (opts.url === 'https://contoso.sharepoint.com/sites/test/_api/web/lists/EnsureSiteAssetsLibrary()') { + return; + } + + if (opts.url === 'https://contoso.sharepoint.com/sites/test/_api/search/postquery') { + return { + PrimaryQueryResult: { + RelevantResults: { + Table: { + Rows: [ + { + Cells: [ + { Key: "contentclass", Value: "STS_ListItem_DocumentLibrary" }, + { Key: "Title", Value: "Test Folder" }, + { Key: "Path", Value: "https://contoso.sharepoint.com/sites/test/documents/folder" }, + { Key: "SiteName", Value: "Test Site" }, + { Key: "SiteTitle", Value: "Test Site" }, + { Key: "SiteID", Value: "f1e2d3c4-b5a6-7890-1234-56789abcdef0" }, + { Key: "WebId", Value: "123e4567-e89b-12d3-a456-426614174000" }, + { Key: "ListID", Value: "b1a5e7c2-3d4f-4e6a-9b8c-2f3e4d5c6b7a" }, + { Key: "UniqueID", Value: "{0f1e2d3c-4b5a-6789-0123-456789abcdef}" }, + { Key: "IsDocument", Value: "false" }, + { Key: "IsContainer", Value: "true" } + ] + } + ] + } + } + } + }; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { + options: { + webUrl: 'https://contoso.sharepoint.com/sites/test', + name: 'Test Agent', + agentInstructions: 'You are a helpful test agent', + welcomeMessage: 'Hello! I am your test agent.', + sourceUrls: 'https://contoso.sharepoint.com/sites/test/documents/folder', + description: 'A test agent' + } + }); + + assert.deepStrictEqual(postStub.lastCall.args[0].data, + { + schemaVersion: "0.2.0", + customCopilotConfig: { + conversationStarters: { + conversationStarterList: [], + welcomeMessage: { + text: "Hello! I am your test agent." + } + }, + gptDefinition: { + name: "Test Agent", + description: "A test agent", + instructions: "You are a helpful test agent", + capabilities: [ + { + name: "OneDriveAndSharePoint", + items_by_sharepoint_ids: [ + ], + items_by_url: [ + { + url: "https://contoso.sharepoint.com/sites/test/documents/folder", + name: "Test Folder", + site_id: "f1e2d3c4-b5a6-7890-1234-56789abcdef0", + web_id: "123e4567-e89b-12d3-a456-426614174000", + list_id: "b1a5e7c2-3d4f-4e6a-9b8c-2f3e4d5c6b7a", + unique_id: "0f1e2d3c-4b5a-6789-0123-456789abcdef", + type: "Folder" + } + ] + } + ] + }, + icon: "data:image/png;base64,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" + } + } + ); + }); + + it('handles API errors properly', async () => { + const errorMessage = 'Agent creation failed'; + + sinon.stub(request, 'post').rejects(new Error(errorMessage)); + + await assert.rejects(command.action(logger, { + options: { + webUrl: 'https://contoso.sharepoint.com/sites/test', + name: 'Error Agent', + agentInstructions: 'This will fail', + welcomeMessage: 'This should not work', + sourceUrls: 'https://contoso.sharepoint.com/sites/test' + } + }), new CommandError(errorMessage)); + }); + + it('fails validation if webUrl is not a valid SharePoint URL', async () => { + const actual = commandOptionsSchema.safeParse({ + webUrl: 'invalid-url', + name: 'Test Agent', + agentInstructions: 'Test instructions', + welcomeMessage: 'Test welcome', + sourceUrls: 'https://contoso.sharepoint.com', + description: 'A test agent' + }); + assert.strictEqual(actual.success, false); + }); + + it('fails validation if name is empty', async () => { + const actual = commandOptionsSchema.safeParse({ + webUrl: 'https://contoso.sharepoint.com/sites/test', + agentInstructions: 'Test instructions', + welcomeMessage: 'Test welcome', + sourceUrls: 'https://contoso.sharepoint.com', + description: 'A test agent' + }); + assert.strictEqual(actual.success, false); + }); + + it('fails validation if description is empty', async () => { + const actual = commandOptionsSchema.safeParse({ + webUrl: 'https://contoso.sharepoint.com/sites/test', + name: 'Test Agent', + agentInstructions: 'Test instructions', + welcomeMessage: 'Test welcome', + sourceUrls: 'https://contoso.sharepoint.com' + }); + assert.strictEqual(actual.success, false); + }); + + it('fails validation if agentInstructions is empty', async () => { + const actual = commandOptionsSchema.safeParse({ + webUrl: 'https://contoso.sharepoint.com/sites/test', + name: 'Test Agent', + welcomeMessage: 'Test welcome', + sourceUrls: 'https://contoso.sharepoint.com', + description: 'A test agent' + }); + assert.strictEqual(actual.success, false); + }); + + it('fails validation if welcomeMessage is empty', async () => { + const actual = commandOptionsSchema.safeParse({ + webUrl: 'https://contoso.sharepoint.com/sites/test', + name: 'Test Agent', + agentInstructions: 'Test instructions', + sourceUrls: 'https://contoso.sharepoint.com', + description: 'A test agent' + }); + assert.strictEqual(actual.success, false); + }); + + it('fails validation if sourceUrls is empty', async () => { + const actual = commandOptionsSchema.safeParse({ + webUrl: 'https://contoso.sharepoint.com/sites/test', + name: 'Test Agent', + agentInstructions: 'Test instructions', + welcomeMessage: 'Test welcome', + description: 'A test agent' + }); + assert.strictEqual(actual.success, false); + }); + + it('fails validation if sourceUrls contains invalid SharePoint URLs', async () => { + const actual = commandOptionsSchema.safeParse({ + webUrl: 'https://contoso.sharepoint.com/sites/test', + name: 'Test Agent', + agentInstructions: 'Test instructions', + welcomeMessage: 'Test welcome', + sourceUrls: 'https://contoso.sharepoint.com,invalid-url,https://contoso.sharepoint.com/sites/docs', + description: 'A test agent' + }); + assert.strictEqual(actual.success, false); + }); + + it('passes validation with all required options', async () => { + const actual = commandOptionsSchema.safeParse({ + webUrl: 'https://contoso.sharepoint.com/sites/test', + name: 'Test Agent', + agentInstructions: 'Test instructions', + welcomeMessage: 'Test welcome', + sourceUrls: 'https://contoso.sharepoint.com', + description: 'A test agent' + }); + assert.strictEqual(actual.success, true); + }); + + it('passes validation with multiple valid source URLs', async () => { + const actual = commandOptionsSchema.safeParse({ + webUrl: 'https://contoso.sharepoint.com/sites/test', + name: 'Test Agent', + agentInstructions: 'Test instructions', + welcomeMessage: 'Test welcome', + sourceUrls: 'https://contoso.sharepoint.com/sites/test,https://contoso.sharepoint.com/sites/docs,https://contoso.sharepoint.com/sites/team', + description: 'A test agent' + }); + assert.strictEqual(actual.success, true); + }); + + it('passes validation with all options including optional ones', async () => { + const actual = commandOptionsSchema.safeParse({ + webUrl: 'https://contoso.sharepoint.com/sites/test', + name: 'Complete Agent', + agentInstructions: 'Comprehensive test instructions', + welcomeMessage: 'Welcome to the complete test', + sourceUrls: 'https://contoso.sharepoint.com/sites/test,https://contoso.sharepoint.com/sites/docs', + description: 'A complete test agent', + icon: 'https://contoso.sharepoint.com/sites/test/SiteAssets/icon.png', + conversationStarters: 'Hello,How can I help?,What do you need?' + }); + assert.strictEqual(actual.success, true); + }); + + it('passes validation with valid icon URL', async () => { + const actual = commandOptionsSchema.safeParse({ + webUrl: 'https://contoso.sharepoint.com/sites/test', + name: 'Test Agent', + agentInstructions: 'Test instructions', + welcomeMessage: 'Test welcome', + sourceUrls: 'https://contoso.sharepoint.com', + icon: 'https://example.com/icon.png', + description: 'A test agent' + }); + assert.strictEqual(actual.success, true); + }); + + it('handles empty UniqueID correctly', async () => { + const postStub = sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === "https://contoso.sharepoint.com/sites/test/_api/web/GetFolderByServerRelativePath(DecodedUrl='/sites/test/SiteAssets/Copilots')/Files/AddUsingPath(DecodedUrl='Test%20Agent.agent',EnsureUniqueFileName=true,AutoCheckoutOnInvalidData=true)" + ) { + return; + } + + if (opts.url === 'https://contoso.sharepoint.com/sites/test/_api/web/lists/EnsureSiteAssetsLibrary()') { + return; + } + + if (opts.url === 'https://contoso.sharepoint.com/sites/test/_api/search/postquery') { + return { + PrimaryQueryResult: { + RelevantResults: { + Table: { + Rows: [ + { + Cells: [ + { Key: "contentclass", Value: "STS_ListItem_DocumentLibrary" }, + { Key: "Title", Value: "Test Document" }, + { Key: "Path", Value: "https://contoso.sharepoint.com/sites/test/Shared Documents/Test Document.docx" }, + { Key: "SiteName", Value: "Test Site" }, + { Key: "SiteTitle", Value: "Test Site" }, + { Key: "ListID", Value: "b1a5e7c2-3d4f-4e6a-9b8c-2f3e4d5c6b7a" }, + { Key: "ListItemID", Value: "a7c6b5d4-e3f2-1a09-b8c7-6e5d4c3b2a1f" }, + { Key: "SiteID", Value: "f1e2d3c4-b5a6-7890-1234-56789abcdef0" }, + { Key: "WebId", Value: "123e4567-e89b-12d3-a456-426614174000" }, + { Key: "IsDocument", Value: "true" }, + { Key: "IsContainer", Value: "false" } + ] + } + ] + } + } + } + }; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { + options: { + webUrl: 'https://contoso.sharepoint.com/sites/test', + name: 'Test Agent', + agentInstructions: 'You are a helpful test agent', + welcomeMessage: 'Hello! I am your test agent.', + sourceUrls: 'https://contoso.sharepoint.com/sites/test', + description: 'A test agent' + } + }); + + assert.deepStrictEqual(postStub.lastCall.args[0].data.customCopilotConfig.gptDefinition.capabilities[0].items_by_sharepoint_ids[0].unique_id, ""); + }); + + it('handles UniqueID without curly braces correctly', async () => { + const postStub = sinon.stub(request, 'post').callsFake(async (opts) => { + if (opts.url === "https://contoso.sharepoint.com/sites/test/_api/web/GetFolderByServerRelativePath(DecodedUrl='/sites/test/SiteAssets/Copilots')/Files/AddUsingPath(DecodedUrl='Test%20Agent.agent',EnsureUniqueFileName=true,AutoCheckoutOnInvalidData=true)" + ) { + return; + } + + if (opts.url === 'https://contoso.sharepoint.com/sites/test/_api/web/lists/EnsureSiteAssetsLibrary()') { + return; + } + + if (opts.url === 'https://contoso.sharepoint.com/sites/test/_api/search/postquery') { + return { + PrimaryQueryResult: { + RelevantResults: { + Table: { + Rows: [ + { + Cells: [ + { Key: "contentclass", Value: "STS_ListItem_DocumentLibrary" }, + { Key: "Title", Value: "Test Document" }, + { Key: "Path", Value: "https://contoso.sharepoint.com/sites/test/Shared Documents/Test Document.docx" }, + { Key: "SiteName", Value: "Test Site" }, + { Key: "SiteTitle", Value: "Test Site" }, + { Key: "ListID", Value: "b1a5e7c2-3d4f-4e6a-9b8c-2f3e4d5c6b7a" }, + { Key: "ListItemID", Value: "a7c6b5d4-e3f2-1a09-b8c7-6e5d4c3b2a1f" }, + { Key: "SiteID", Value: "f1e2d3c4-b5a6-7890-1234-56789abcdef0" }, + { Key: "WebId", Value: "123e4567-e89b-12d3-a456-426614174000" }, + { Key: "UniqueID", Value: "0f1e2d3c-4b5a-6789-0123-456789abcdef" }, + { Key: "IsDocument", Value: "true" }, + { Key: "IsContainer", Value: "false" } + ] + } + ] + } + } + } + }; + } + + throw 'Invalid request'; + }); + + await command.action(logger, { + options: { + webUrl: 'https://contoso.sharepoint.com/sites/test', + name: 'Test Agent', + agentInstructions: 'You are a helpful test agent', + welcomeMessage: 'Hello! I am your test agent.', + sourceUrls: 'https://contoso.sharepoint.com/sites/test', + description: 'A test agent' + } + }); + + assert.deepStrictEqual(postStub.lastCall.args[0].data.customCopilotConfig.gptDefinition.capabilities[0].items_by_sharepoint_ids[0].unique_id, "0f1e2d3c-4b5a-6789-0123-456789abcdef"); + }); +}); diff --git a/src/m365/spo/commands/agent/agent-add.ts b/src/m365/spo/commands/agent/agent-add.ts new file mode 100644 index 00000000000..14770952ff5 --- /dev/null +++ b/src/m365/spo/commands/agent/agent-add.ts @@ -0,0 +1,251 @@ +/* eslint-disable camelcase */ +import { z } from 'zod'; +import { Logger } from '../../../../cli/Logger.js'; +import { globalOptionsZod } from '../../../../Command.js'; +import request, { CliRequestOptions } from '../../../../request.js'; +import { validation } from '../../../../utils/validation.js'; +import { zod } from '../../../../utils/zod.js'; +import SpoCommand from '../../../base/SpoCommand.js'; +import commands from '../../commands.js'; +import { urlUtil } from '../../../../utils/urlUtil.js'; +import { spo } from '../../../../utils/spo.js'; +import { formatting } from '../../../../utils/formatting.js'; +import { SearchResult } from '../search/datatypes/SearchResult.js'; +import { ResultTableRow } from '../search/datatypes/ResultTableRow.js'; +import { SearchResultProperty } from '../search/datatypes/SearchResultProperty.js'; + +const options = globalOptionsZod + .extend({ + webUrl: zod.alias('u', z.string().refine(url => validation.isValidSharePointUrl(url) === true, { + message: 'Specify a valid SharePoint site URL' + })), + name: zod.alias('n', z.string()), + agentInstructions: zod.alias('a', z.string()), + welcomeMessage: zod.alias('w', z.string()), + sourceUrls: zod.alias('s', z.string().refine(urls => { + const urlArray = urls.split(',').map(url => url.trim()); + return urlArray.every(url => url && validation.isValidSharePointUrl(url) === true); + }, { + message: 'All source URLs must be valid SharePoint URLs' + })), + description: zod.alias('d', z.string()), + icon: zod.alias('i', z.string().optional()), + conversationStarters: zod.alias('c', z.string().optional()) + }) + .strict(); + +declare type Options = z.infer; + +interface CommandArgs { + options: Options; +} + +interface AgentSource { + url: string; + name: string; + site_id: string; + web_id: string; + list_id: string; + unique_id: string; + type: string; +} + +const urlSourceMap: Record = { + STS_ListItem_DocumentLibrary: 'File', + STS_List_DocumentLibrary: 'List', + STS_Web: 'Site', + STS_Site: 'Site' +}; + +interface AgentCapabilities { + name: string; + items_by_sharepoint_ids: AgentSource[]; + items_by_url: AgentSource[]; +} + +interface AgentRequestBody { + schemaVersion: string; + customCopilotConfig: { + conversationStarters: { + conversationStarterList: Array<{ text: string }>; + welcomeMessage: { text: string }; + }; + gptDefinition: { + name: string; + description: string; + instructions: string; + capabilities: AgentCapabilities[]; + }; + icon: string; + }; +} + +class SpoAgentAddCommand extends SpoCommand { + public get name(): string { + return commands.AGENT_ADD; + } + + public get description(): string { + return 'Adds a new SharePoint agent'; + } + + public get schema(): z.ZodTypeAny | undefined { + return options; + } + + public async commandAction(logger: Logger, args: CommandArgs): Promise { + try { + if (this.verbose) { + await logger.logToStderr(`Adding SharePoint agent '${args.options.name}' to site '${args.options.webUrl}'...`); + } + + await this.ensureSiteAssetsLibrary(args.options.webUrl, logger); + await spo.ensureFolder(args.options.webUrl, 'SiteAssets/Copilots', logger, this.verbose); + + const sourceUrls = args.options.sourceUrls.split(','); + + const capabilities: AgentCapabilities = await this.resolveSourceUrls(sourceUrls, args.options.webUrl, logger); + + const conversationStartersArray = args.options.conversationStarters + ? args.options.conversationStarters.split(',').map((starter: string) => { return { text: starter }; }) + : []; + + const cliM365DefaultIcon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAIJklEQVR4nO2bCWwUVRjHv9m73RYEBEQR5RLKpSgKqHggtBQRBAQFEY94GzUcVlBDQIkhGhUFJCqCBFDuUzkrN4qCii1GG4UKqFwCgba7OzO7M37fN3a729mFbh+IJO+XkG135r33vf981xtAOTGgnwmSauM43wZc6EgBBZECCiIFFEQKKIgUUBApoCBSQEGkgIJIAQWRAgoiBRRECiiIFFAQKaAgUkBBpICCSAEFkQIKIgUURAooiBRQECmgIFJAQaSAgkgBBZECCiIFFMQuoOMC0NTlAsXjOd9WMK7KX6Q/+TQYhw+B+vkKMFX1fNiUGEUBb7ds8OTmgvOyhvy7cewY6Fs3Q2jRQjBDoaTj3FdfA+7ON4Kz0RUATieU5I2w3eaoXx8yXnol6fL6zp0QnDXT9r1NQMXnA9+9g8DTPQdC8+eCtnEDgGGksNNzQ/rjT4Lnjm5glpSAtmUz7kgHV5u24O3TF5wtsqB03BiASCRujJKRAf7nh4ELBSTMU6cggs6RCEedi8HR4FJrrwn2q9SokXCcTUBt+9fgbJkFjtq12Ru9Pe+E0OxZoO/6IeVNny1cWa1YPOPwYSh5eTQKcdK64HZD5rjXwNWyJbg7dgL9q20Vg9DT/C++BK4WLSBcWADBObMgsndv0jUctWrxZ2jJIgjNm1tl22wJj4woee4Z9L55YAaD7PZ+dO2MV8aA88rGVZ7Ymt0Rl1MddeuBs1nzqLGx9zkbXg7Opk1B8WfYpnG1b8+fav7aCvHYWB20zZuse1q0jBvjRcFJPP3776D09fGnFY9Q/rWJ0kIq2DyQoHwSWjgf1HVrwNd/AOae7uBqdzVkTmjL4ROa+xku9PcZJ6ecQuMCUyaDN7cnOJs0iV6jjQUmvcvX04Y+hCFUx7qA4aNtWA+BaR9hSIb5K3XFctDy8zF8T9ltRREZ9LhYPLgehXSQ54nYxlXGUau2NV+KAjpHtc4am/QqFpHwD9+DtnUrKDVrojc2Yi/0ZGeD4vXhU93DXpAMzy23YnK+BNwdOkBk/z7QUZhI8V5wXtIAnI0bgxs9y5uTC+GiX0DfiNcOHEBPbGh5E84b/uVnayJNA7OsDCAcjl8AC0Ta4Ae4AKhYSIy//rLEqFsX0u4bzOPDu3eDD/OkN6cHuK+9jqs3rWOz9fauvD8a4+lyC3i73gGutu1AwQdqHEqcN9mEVP6NNHkQGexq145/p6TMnpq/zr45hMKePExdtRKCMz6umOfyRpD51jv8s7p6FQSnT4tec7e/FvyjX2bBS0YOt9vQHFNA7Toc6p4uXcDVug1omzawl0fn6HA9+PNGgXHkiOXZKDR1FFQg6Wd9x7dQ9tabccUiY+yr4GrVOuG+KeoCk9/DDdulShjCyaA8Ujp+nBV29w9BL0JBH3mUwzP46RzQv9mecBwZHDfPgf1gnDjOYUMCxlLudZQvE+Hr1ZtbklibgjM/ibuHKip/1qvHOTKEBcQ4cYKjxz8yD9zX3wDe7tmgrlkdHUO5zwwEQF2+FDQqRhj25LG+IUPZIyNFRaCuXQ2VqVbXHC74EUpG5UHgvYmWEFj+/SNe4GpZ9UksjzUDZfHfU76iJ52koQ/hBsvemACB9yfzA6OoyJzwBqaYiypu8nqtJTB8A1MmsXg89e/FmFs/4J89XbvFzUv5+OTDQyG0eBGHrHH0KAsc/NiKDk92TkJ7qn3sUGpgTsRc5ci0+iNTxzwVDKQ+UYr/ySKyZw82tTu4P6UwVJcvQ0+rD75+/WOMsz4ot1YOu3BhIYcz5Ts60cTbYjdG27YFB+mcmxOdflIKYbYtLQ28GEbeu3pbOcU0/q3Mn/JTSxkl9SGxUIh6e/eJ834KRZ46Pd0+AD2c2jMqigoKaCbI3XFgMTMDQauRJgGxoMVSdQFxMWpnqK2hxYnw7kIIzp5lVePqUgUPTHvsCT6OBT6Yyk1x/Hj7BOU9n6v5VbZrJKojM5OrevlRlXPd0AfZq9WlS2z3K34/mJrKwlfmzALSWfLGm7AtGMQtCRuIRSA0Zzb3cv8FJuYwKgjUFlUW0N2xo2XTb79Gv6MHGvnzD2zam3FV17EVK8eD52nqGfUfd0XF5/YJ87g3uwdomPdihaL2hu4PFxQk7CdPKyD1QVxtmzTl3ykZ0wlFw56tKs3p2UJdu4aTuOfW2zgSuNpj6PFZuEcubziEuTAKChOcMZ0b+fRhI9CrFmNbtB+PfFmYfnqx56kLF0RvN44eAW39l3xcpHZGXfkFn7mdmBZ8ve7ivaqLF9gNgyQCcnuCwlG7wvbgyURdsYxPBEnfepxD6PhWOnYMns2fAs9NN/Ofcsh7AlOnYOU8GDeGOoWyiW9DOoa/D5vqckgs6hnJQ2MJYLU1sVjQG5/0Z56tuB+dJjjtQyxIRQltszXSvnsGgm/AQA5dMCL4ZNZDaMG8aCuQCtzEYktBx6PKr8bo9EChYRw6zOtUWKRgW9SAvcg4eBAqQ9fo3ExtDr12i+zblzAPRqfD9alboFxGqSBMoX6a6KH87mrajF9UmCdPWvefptDYBPQPHwnuTp1B/24n57nIH/Zjj6QCWwhHiou5gQz/tPt82HPBYROQ3odJqk7KjXRVobfBvrv74gru6k+CuZH+asE4fvzsGXaWOacC8qt0QQG1bXiw/x8LmNLrLImdC+DvMP/fSAEFkQIKIgUURAooiBRQECmgIFJAQaSAgkgBBZECCiIFFEQKKIgUUBApoCBSQEGkgIJIAQWRAgoiBRRECiiIFFAQKaAgUkBBpICCSAEFkQIKIgUU5B91HS13TtrWPgAAAABJRU5ErkJggg=='; + + const requestBody: AgentRequestBody = { + schemaVersion: "0.2.0", + customCopilotConfig: { + conversationStarters: { + conversationStarterList: conversationStartersArray, + welcomeMessage: { + text: args.options.welcomeMessage + } + }, + gptDefinition: + { + name: args.options.name, + description: args.options.description, + instructions: args.options.agentInstructions, + capabilities: [capabilities] + }, + icon: args.options.icon || cliM365DefaultIcon + } + }; + + const serverRelativePath = urlUtil.getServerRelativePath(args.options.webUrl, '/SiteAssets/Copilots/'); + + const requestOptions: CliRequestOptions = { + url: `${args.options.webUrl}/_api/web/GetFolderByServerRelativePath(DecodedUrl='${serverRelativePath}')/Files/AddUsingPath(DecodedUrl='${formatting.encodeQueryParameter(args.options.name)}.agent',EnsureUniqueFileName=true,AutoCheckoutOnInvalidData=true)`, + headers: { + 'Accept': 'application/json;odata=nometadata', + 'Content-Type': 'application/json;odata=nometadata' + }, + data: requestBody, + responseType: 'json' + }; + + const result = await request.post(requestOptions); + + if (this.verbose) { + await logger.logToStderr(`Agent '${args.options.name}' has been successfully created.`); + } + + await logger.log(result); + } + catch (err: any) { + this.handleRejectedODataJsonPromise(err); + } + } + + private async ensureSiteAssetsLibrary(webUrl: string, logger: Logger): Promise { + if (this.verbose) { + await logger.logToStderr(`Ensuring Site Assets library exists at ${webUrl}...`); + } + + const requestOptions: CliRequestOptions = { + url: `${webUrl}/_api/web/lists/EnsureSiteAssetsLibrary()`, + headers: { + 'Accept': 'application/json;odata=nometadata' + }, + responseType: 'json' + }; + + await request.post(requestOptions); + } + + private async resolveSourceUrls(sourceUrls: string[], webUrl: string, logger: Logger): Promise { + const resolvedUrls: AgentSource[] = []; + const resolvedFiles: AgentSource[] = []; + + for (const sourceUrl of sourceUrls) { + if (this.verbose) { + await logger.logToStderr(`Resolving source URL: ${sourceUrl}`); + } + + const requestBody = { + request: { + QueryTemplate: "({searchterms}) (contentclass:STS_Web OR contentclass:STS_Site OR contentclass:STS_ListItem_DocumentLibrary OR contentclass:STS_List_DocumentLibrary)", + Querytext: `Path=\"${sourceUrl}\"`, + SelectProperties: ["contentclass", "Title", "Path", "SiteName", "SiteTitle", "ListID", "ListItemID", "SiteID", "WebId", "UniqueID", "IsDocument", "IsContainer"], + RowLimit: 1, + TrimDuplicates: false + } + }; + + const requestOptions: CliRequestOptions = { + url: `${webUrl}/_api/search/postquery`, + headers: { + 'Accept': 'application/json;odata=nometadata' + }, + responseType: 'json', + data: requestBody + }; + + const response: SearchResult = await request.post(requestOptions); + + if (response.PrimaryQueryResult.RelevantResults.Table.Rows.length === 0) { + await logger.logToStderr(`${sourceUrl} has been skipped because no results were found.`); + continue; + } + + const row = response.PrimaryQueryResult.RelevantResults.Table.Rows[0]; + const isContainer = this.getCellValue(row, "IsContainer"); + + let uniqueId = this.getCellValue(row, "UniqueID"); + if (uniqueId.startsWith('{') && uniqueId.endsWith('}')) { + uniqueId = uniqueId.slice(1, -1); + } + + const contentClass = this.getCellValue(row, "contentclass"); + + const resolvedItem = { + url: sourceUrl, + name: this.getCellValue(row, "Title"), + site_id: this.getCellValue(row, "SiteID"), + web_id: this.getCellValue(row, "WebId"), + list_id: this.getCellValue(row, "ListID") || '', + unique_id: uniqueId, + type: isContainer === 'true' && contentClass === 'STS_ListItem_DocumentLibrary' ? 'Folder' : urlSourceMap[contentClass] + }; + + if (isContainer === 'false' && contentClass === 'STS_ListItem_DocumentLibrary') { + resolvedFiles.push(resolvedItem); + } + else { + resolvedUrls.push(resolvedItem); + } + } + + return { + name: "OneDriveAndSharePoint", + items_by_sharepoint_ids: resolvedFiles, + items_by_url: resolvedUrls + }; + } + + private getCellValue(row: ResultTableRow, key: string): string { + return row.Cells.find((cell: SearchResultProperty) => cell.Key === key)?.Value || ''; + } +} + +export default new SpoAgentAddCommand();