Skip to content

Commit

Permalink
Merge branch 'main' into add/cards-sample
Browse files Browse the repository at this point in the history
  • Loading branch information
rido-min authored Feb 22, 2025
2 parents 63f5375 + 6be0d9b commit 724add3
Show file tree
Hide file tree
Showing 20 changed files with 534 additions and 2 deletions.
20 changes: 20 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet
{
"name": "universal",
"image": "mcr.microsoft.com/devcontainers/universal:focal",
"features": {
"ghcr.io/devcontainers/features/azure-cli:1": {},
"ghcr.io/devcontainers/features/powershell:1": {},
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
"ghcr.io/stuartleeks/dev-container-features/dev-tunnels:0": {}
},
"customizations": {
"vscode": {
"extensions": [
"GitHub.copilot-chat",
"TeamsDevApp.vscode-adaptive-cards"
]
}
}
}
6 changes: 6 additions & 0 deletions .github/workflows/ci-node.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ jobs:

- name: Build echo bot
working-directory: ./samples/basic/echo-bot/nodejs/
run: |
npm install
npm run build
- name: Build Copilot Studio client sample
working-directory: ./samples/basic/copilotstudio-client/nodejs/
run: |
npm install
npm run build
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ FodyWeavers.xsd

#Ignore env files
*.env

dist/
devTools/
node_modules/
node_modules/

20 changes: 20 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [

{
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}/samples/skill/node-echo-skill/src/index.js",
"outFiles": [ "${workspaceFolder}/samples/skill/node-echo-skill/node_modules/@microsoft/**/*.js" ]

}
]
}
3 changes: 3 additions & 0 deletions samples/basic/copilotstudio-client/nodejs/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
registry=https://registry.npmjs.org/
@microsoft:registry=https://pkgs.dev.azure.com/ConversationalAI/BotFramework/_packaging/SDK/npm/registry/
package-lock=false
76 changes: 76 additions & 0 deletions samples/basic/copilotstudio-client/nodejs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Copilot Studio Client

This is a sample to show how to use the `@microsoft/agents-copilotstudio-client` package to talk to an Agent hosted in CopilotStudio.


## Prerequisite

To set up this sample, you will need the following:

1. [Node.js](https://nodejs.org) version 20 or higher

```bash
# determine node version
node --version
```
2. An Agent Created in Microsoft Copilot Studio or access to an existing Agent.
3. Ability to Create an Application Identity in Azure for a Public Client/Native App Registration Or access to an existing Public Client/Native App registration with the CopilotStudio.Copilot.Invoke API Permission assigned.

## Create an Agent in Copilot Studio

1. Create an Agent in [Copilot Studio](https://copilotstudio.microsoft.com)
1. Publish your newly created Copilot
2. Goto Settings => Advanced => Metadata and copy the following values, You will need them later:
1. Schema name
2. Environment Id

## Create an Application Registration in Entra ID

This step will require permissions to create application identities in your Azure tenant. For this sample, you will create a Native Client Application Identity, which does not have secrets.

1. Open https://portal.azure.com
2. Navigate to Entra Id
3. Create a new App Registration in Entra ID
1. Provide a Name
2. Choose "Accounts in this organization directory only"
3. In the "Select a Platform" list, Choose "Public Client/native (mobile & desktop)
4. In the Redirect URI url box, type in `http://localhost` (**note: use HTTP, not HTTPS**)
5. Then click register.
4. In your newly created application
1. On the Overview page, Note down for use later when configuring the example application:
1. The Application (client) ID
2. The Directory (tenant) ID
2. Go to API Permissions in `Manage` section
3. Click Add Permission
1. In the side panel that appears, Click the tab `API's my organization uses`
2. Search for `Power Platform API`.
1. *If you do not see `Power Platform API` see the note at the bottom of this section.*
3. In the permissions list, choose `CopilotStudio` and Check `CopilotStudio.Copilots.Invoke`
4. Click `Add Permissions`
4. (Optional) Click `Grant Admin consent for copilotsdk`
> [!TIP]
> If you do not see `Power Platform API` in the list of API's your organization uses, you need to add the Power Platform API to your tenant. To do that, goto [Power Platform API Authentication](https://learn.microsoft.com/power-platform/admin/programmability-authentication-v2#step-2-configure-api-permissions) and follow the instructions on Step 2 to add the Power Platform Admin API to your Tenant
## Instructions - Configure the Example Application
With the above information, you can now run the client `CopilostStudioClient` sample.
1. Open the `env.TEMPLATE` file and rename it to `.env`.
2. Configure the values based on what was recorded during the setup phase.
```bash
environmentId="" # Environment ID of environment with the CopilotStudio App.
botIdentifier="" # 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 Copilot.
```
3. Run the CopilotStudioClient sample using `npm start`, which will install the packages, build the project and run it.
This should challenge you to login and connect to the Copilot Studio Hosted bot, allowing you to communicate via a console interface.
## Authentication
The DirectToEngine Client requires a User Token to operate. For this sample, we are using a user interactive flow to get the user token for the application ID created above.
26 changes: 26 additions & 0 deletions samples/basic/copilotstudio-client/nodejs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "copilotstudio-client",
"version": "1.0.0",
"private": true,
"description": "Agents Copilot Studio Client sample",
"author": "Microsoft",
"license": "MIT",
"main": "./dist/index.js",
"type": "module",
"scripts": {
"build": "tsc --build",
"prestart": "npm run build",
"start": "node --env-file .env ./dist/index.js"
},
"dependencies": {
"@microsoft/agents-copilotstudio-client": "0.1.20",
"@azure/msal-node": "^3.2.3",
"open": "^10.1.0"
},
"devDependencies": {
"@types/debug": "^4.1.12",
"@types/node": "^22.13.4",
"typescript": "^5.7.3"
},
"keywords": []
}
103 changes: 103 additions & 0 deletions samples/basic/copilotstudio-client/nodejs/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/

import * as msal from '@azure/msal-node'
import { Activity, ActivityTypes, CardAction } from '@microsoft/agents-bot-activity'
import { ConnectionSettings, loadCopilotStudioConnectionSettingsFromEnv, 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'

async function acquireToken(settings: ConnectionSettings): Promise<string> {
const msalConfig = {
auth: {
clientId: settings.appClientId,
authority: `https://login.microsoftonline.com/${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,
}
}
}
const pca = new msal.PublicClientApplication(msalConfig)
const tokenRequest = {
scopes: ['https://api.powerplatform.com/.default'],
redirectUri: 'http://localhost',
openBrowser: async (url: string) => {
await open(url)
}
}
let token
try {
const accounts = await pca.getAllAccounts()
if (accounts.length > 0) {
const response2 = await pca.acquireTokenSilent({ account: accounts[0], scopes: tokenRequest.scopes })
token = response2.accessToken
} else {
const response = await pca.acquireTokenInteractive(tokenRequest)
token = response.accessToken
}
} catch (error) {
console.error('Error acquiring token interactively:', error)
const response = await pca.acquireTokenInteractive(tokenRequest)
token = response.accessToken
}
return token
}

const createClient = async (): Promise<CopilotStudioClient> => {
const settings = loadCopilotStudioConnectionSettingsFromEnv()
const token = await acquireToken(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
}

const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})

const askQuestion = async (copilotClient: CopilotStudioClient, conversationId: string) => {
rl.question('\n>>>: ', async (answer) => {
if (answer.toLowerCase() === 'exit') {
rl.close()
} else {
const replies = await copilotClient.askQuestionAsync(answer, conversationId)
replies.forEach((act: Activity) => {
if (act.type === ActivityTypes.Message) {
console.log(`\n${act.text}`)
act.suggestedActions?.actions.forEach((action: CardAction) => console.log(action.value))
} else if (act.type === ActivityTypes.EndOfConversation) {
console.log(`\n${act.text}`)
rl.close()
}
})
await askQuestion(copilotClient, conversationId)
}
})
}

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!)
}

main().catch(e => console.log(e))
49 changes: 49 additions & 0 deletions samples/basic/copilotstudio-client/nodejs/src/msalCachePlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import fs from 'fs'
import { ICachePlugin, TokenCacheContext } from '@azure/msal-node'

export class MsalCachePlugin implements ICachePlugin {
private cacheLocation: string = ''
constructor(cacheLocation: string) {
this.cacheLocation = cacheLocation
}

async beforeCacheAccess(tokenCacheContext: TokenCacheContext): Promise<void> {
return new Promise((resolve, reject) => {
if (fs.existsSync(this.cacheLocation)) {
fs.readFile(this.cacheLocation, 'utf-8', (error, data) => {
if (error) {
reject(error)
} else {
tokenCacheContext.tokenCache.deserialize(data)
resolve()
}
})
} else {
fs.writeFile(this.cacheLocation, tokenCacheContext.tokenCache.serialize(), (error) => {
if (error) {
reject(error)
}
})
}
})
}

async afterCacheAccess(tokenCacheContext: TokenCacheContext): Promise<void> {
return new Promise((resolve, reject) => {
if (tokenCacheContext.cacheHasChanged) {
fs.writeFile(this.cacheLocation, tokenCacheContext.tokenCache.serialize(), (error) => {
if (error) {
reject(error)
}
resolve()
})
} else {
resolve()
}
})
}
}
20 changes: 20 additions & 0 deletions samples/basic/copilotstudio-client/nodejs/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"compilerOptions": {
"incremental": true,
"lib": ["ES2021"],
"target": "es2019",
"declaration": true,
"sourceMap": true,
"composite": true,
"strict": true,
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"module": "ESNext",
"rootDir": "src",
"outDir": "dist",
"tsBuildInfoFile": "dist/.tsbuildinfo"
}
}
4 changes: 4 additions & 0 deletions samples/complex/copilotstudio-skill/nodejs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules/
dist/
.env
package-lock.json
2 changes: 2 additions & 0 deletions samples/complex/copilotstudio-skill/nodejs/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@microsoft:registry=https://pkgs.dev.azure.com/ConversationalAI/BotFramework/_packaging/SDK/npm/registry/
package-lock=false
19 changes: 19 additions & 0 deletions samples/complex/copilotstudio-skill/nodejs/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Debug index",
"program": "${workspaceFolder}/src/index.js",
"request": "launch",
"skipFiles": [
"<node_internals>/**"
],
"type": "node",
"envFile": "${workspaceFolder}/.env"
},

]
}
Loading

0 comments on commit 724add3

Please sign in to comment.