Skip to content
Draft
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
13 changes: 12 additions & 1 deletion samples/nodejs/copilotstudio-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,20 @@ With the above information, you can now run the client `CopilostStudioClient` sa

```bash
environmentId="" # Environment ID of environment with the CopilotStudio App.
agentIdentifier="" # Schema Name of the Copilot to use
schemaName="" # Schema Name of the Copilot to use
tenantId="" # Tenant ID of the App Registration used to login, this should be in the same tenant as the Copilot.
appClientId="" # App ID of the App Registration used to login, this should be in the same tenant as the CopilotStudio environment.
directConnectUrl="" # The URL to connect directly to the Copilot Studio service. When provided, `environmentId` + `schemaName` are ignored.
```

#### Optional Configuration
This sample lets you configure the following settings in the .env file:
```bash
authorityEndpoint="" # The login authority to use for the connection. Default: "https://login.microsoftonline.com".
cloud="" # The cloud hosting the Power Platform Services. Default: "Prod".
customPowerPlatformCloud="" # The Power Platform API endpoint when cloud is set to "Other".
copilotAgentType="" # The type of Copilot Studio Agent (Published or Prebuilt). Default: "Published".
useExperimentalEndpoint="" # The flag to use the URL provided via the "x-ms-d2e-experimental" header for subsequent calls to the Copilot Studio service.
```

3. Run the CopilotStudioClient sample using `npm start`, which will install the packages, build the project and run it.
Expand Down
27 changes: 19 additions & 8 deletions samples/nodejs/copilotstudio-client/env.TEMPLATE
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
environmentId=
agentIdentifier=
tenantId=
appClientId=
# cloud= # PowerPlatformCloud. eg 'Cloud | Gov'
# customPowerPlatformCloud= # Power Platform API endpoint to use if Cloud is configured as "Other"
# agentType="" # AgentType enum. eg 'Published'
DEBUG=copilot-studio-client:error
# rename to .env

# Authentication settings (MSAL)
appClientId="" # App ID of the App Registration used to log in; should be in the same tenant as the Copilot Studio agent.
tenantId="" # Tenant ID of the App Registration used to log in; must match the Copilot's tenant.
authorityEndpoint="" # Login authority to use for the connection. Default value is "https://login.microsoftonline.com".
useS2SConnection=false # Flag to enable token acquisition with an "appClientSecret".
appClientSecret="" # Client secret of the App Registration used for the S2S connection.

# Copilot Studio Agent Configuration
environmentId="" # Environment ID of the Copilot Studio App (required if directConnectUrl is empty).
schemaName="" # Schema Name of the Copilot Studio App (required if directConnectUrl is empty).
directConnectUrl="" # URL used to connect to the Copilot Studio service (use this OR environmentId + schemaName).
cloud="" # Cloud hosting the Power Platform Services. Default value is "Prod". Set to "Other" when using customPowerPlatformCloud.
customPowerPlatformCloud="" # Power Platform API endpoint to use if Cloud is configured as "Other".
copilotAgentType="" # Type of Copilot Studio Agent (Published or Prebuilt). Default value is "Published".
useExperimentalEndpoint=false # Flag to use the "x-ms-d2e-experimental" header URL on subsequent calls to the Copilot Studio service.

DEBUG=copilot-studio-client:error
45 changes: 27 additions & 18 deletions samples/nodejs/copilotstudio-client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions samples/nodejs/copilotstudio-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@
"start": "node --env-file .env ./dist/index.js"
},
"dependencies": {
"@azure/msal-node": "^3.5.3",
"@azure/msal-node": "^3.7.2",
"@microsoft/agents-copilotstudio-client": "^1.0.0",
"open": "^10.1.2"
},
"devDependencies": {
"@types/debug": "^4.1.12",
"@types/node": "^22.13.4",
"typescript": "^5.7.3"
"@types/node": "^24.3.0",
"typescript": "^5.9.2"
},
"keywords": []
}
92 changes: 62 additions & 30 deletions samples/nodejs/copilotstudio-client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,65 +5,98 @@

import * as msal from '@azure/msal-node'
import { Activity, ActivityTypes, CardAction } from '@microsoft/agents-activity'
import { ConnectionSettings, loadCopilotStudioConnectionSettingsFromEnv, CopilotStudioClient } from '@microsoft/agents-copilotstudio-client'
import { ConnectionSettings, CopilotStudioClient } from '@microsoft/agents-copilotstudio-client'
import pkg from '@microsoft/agents-copilotstudio-client/package.json' with { type: 'json' }
import readline from 'readline'
import open from 'open'
import os from 'os'
import path from 'path'

import { MsalCachePlugin } from './msalCachePlugin.js'
import { SampleConnectionSettings } from './sampleConnectionSettings.js'

async function acquireToken (settings: ConnectionSettings): Promise<string> {
const msalConfig = {
interface S2SConnectionSettings extends ConnectionSettings {
appClientSecret?: string
}

async function acquireS2SToken (baseConfig: msal.Configuration, settings: S2SConnectionSettings): Promise<string> {
const cca = new msal.ConfidentialClientApplication({
...baseConfig,
auth: {
clientId: settings.appClientId,
authority: `https://login.microsoftonline.com/${settings.tenantId}`,
},
cache: {
cachePlugin: new MsalCachePlugin(path.join(os.tmpdir(), 'mcssample.tockencache.json'))
},
system: {
loggerOptions: {
loggerCallback (loglevel: msal.LogLevel, message: string, containsPii: boolean) {
if (!containsPii) {
console.log(loglevel, message)
}
},
piiLoggingEnabled: false,
logLevel: msal.LogLevel.Verbose,
}
...baseConfig.auth,
clientSecret: settings.appClientSecret!
}
})

try {
const response = await cca.acquireTokenByClientCredential({ scopes: [CopilotStudioClient.scopeFromSettings(settings)] })
if (!response?.accessToken) {
throw new Error('Failed to acquire token')
}

return response?.accessToken
} catch (error) {
console.error('Error acquiring token for client credential:', error)
throw error
}
const pca = new msal.PublicClientApplication(msalConfig)
}

async function acquireToken (baseConfig: msal.Configuration, settings: ConnectionSettings): Promise<string> {
const tokenRequest = {
scopes: ['https://api.powerplatform.com/.default'],
redirectUri: 'http://localhost',
scopes: [CopilotStudioClient.scopeFromSettings(settings)],
openBrowser: async (url: string) => {
await open(url)
}
}
let token

const pca = new msal.PublicClientApplication(baseConfig)

try {
const accounts = await pca.getAllAccounts()
if (accounts.length > 0) {
const response2 = await pca.acquireTokenSilent({ account: accounts[0], scopes: tokenRequest.scopes })
token = response2.accessToken
return response2.accessToken
} else {
const response = await pca.acquireTokenInteractive(tokenRequest)
token = response.accessToken
return response.accessToken
}
} catch (error) {
console.error('Error acquiring token interactively:', error)
const response = await pca.acquireTokenInteractive(tokenRequest)
token = response.accessToken
return response.accessToken
}
}

function getToken (settings: SampleConnectionSettings) : Promise<string> {
const msalConfig: msal.Configuration = {
auth: {
clientId: settings.appClientId!,
authority: `${settings.authority}/${settings.tenantId}`,
},
cache: {
cachePlugin: new MsalCachePlugin(path.join(os.tmpdir(), 'msal.usercache.json'))
},
system: {
loggerOptions: {
loggerCallback (loglevel: msal.LogLevel, message: string, containsPii: boolean) {
console.log(message)
},
piiLoggingEnabled: false,
logLevel: msal.LogLevel.Verbose,
}
}
}
return token

if (settings.useS2SConnection) {
return acquireS2SToken(msalConfig, settings)
}

return acquireToken(msalConfig, settings)
}

const createClient = async (): Promise<CopilotStudioClient> => {
const settings = loadCopilotStudioConnectionSettingsFromEnv()
const token = await acquireToken(settings)
const settings = new SampleConnectionSettings()
const token = await getToken(settings)
const copilotClient = new CopilotStudioClient(settings, token)
console.log(`Copilot Studio Client Version: ${pkg.version}, running with settings: ${JSON.stringify(settings, null, 2)}`)
return copilotClient
Expand Down Expand Up @@ -98,7 +131,6 @@ const askQuestion = async (copilotClient: CopilotStudioClient, conversationId: s
const main = async () => {
const copilotClient = await createClient()
const act: Activity = await copilotClient.startConversationAsync(true)
console.log('\nSuggested Actions: ')
act.suggestedActions?.actions.forEach((action: CardAction) => console.log(action.value))
await askQuestion(copilotClient, act.conversation?.id!)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/

import { ConnectionSettings, loadCopilotStudioConnectionSettingsFromEnv } from '@microsoft/agents-copilotstudio-client'

export class SampleConnectionSettings extends ConnectionSettings {
public readonly appClientId: string = ''
public readonly tenantId: string = ''
public readonly authority: string = 'https://login.microsoftonline.com'
public readonly useS2SConnection: boolean = false
public readonly appClientSecret: string = ''

constructor () {
const settings = loadCopilotStudioConnectionSettingsFromEnv()
super(settings)

if (!process.env.appClientId) {
throw new Error('appClientId is required')
}

if (!process.env.tenantId) {
throw new Error('tenantId is required')
}

this.appClientId = process.env.appClientId!
this.tenantId = process.env.tenantId!
this.authority = process.env.authority ?? 'https://login.microsoftonline.com'
this.useS2SConnection = process.env.useS2SConnection === 'true'
this.appClientSecret = process.env.appClientSecret ?? ''

if (this.useS2SConnection && !this.appClientSecret) {
throw new Error('appClientSecret is required for S2S connection')
}
}
}
4 changes: 2 additions & 2 deletions samples/nodejs/copilotstudio-webchat-react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ This step requires permissions to create application identities in your Azure te
appClientId: 'your-app-client-id-here',
tenantId: 'your-tenant-id-here',
environmentId: 'your-environment-id-here',
agentIdentifier: 'your-schema-name-here',
schemaName: 'your-schema-name-here',
}
}
```
Expand Down Expand Up @@ -151,7 +151,7 @@ Configuration is handled through `settings.js` which emulates Node.js process.en
process.env.appClientId // Your Azure AD app registration ID
process.env.tenantId // Your Azure AD tenant ID
process.env.environmentId // Copilot Studio environment ID
process.env.agentIdentifier // Copilot Studio schema name
process.env.schemaName // Copilot Studio schema name
// ... other configuration options
```

Expand Down
Loading
Loading