Skip to content

Commit 8a8de00

Browse files
Add reasoning/thinking activities sample (#418)
1 parent a745abe commit 8a8de00

File tree

109 files changed

+6284
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

109 files changed

+6284
-0
lines changed

ShowReasoningSample/README.md

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# Showing Agent Reasoning in Custom UIs
2+
3+
> **IMPORTANT!** This is the sample repository for [https://microsoft.github.io/mcscatblog/posts/show-reasoning-agents-sdk/](This article). Follow the article for a more detailed explanation.
4+
5+
Render Anthropic reasoning traces from Microsoft 365 Copilot Studio agents inside your own UI. This README distills the companion article posted on MCS CAT blog.
6+
7+
## Why Bubble Up Reasoning?
8+
- Strengthen trust in automated or assisted decisions.
9+
- Give operators visibility into multi-step, decision-heavy workflows.
10+
- Help end users judge the suitability of an answer before acting on it.
11+
12+
## Demo Scenario
13+
The reference sample (static HTML + JS) simulates an organization triaging monday.com tickets with an Anthropic-enabled Copilot Studio agent. Submitting a new ticket shows incremental reasoning as typing activities, and near-duplicate tickets are merged automatically instead of duplicated.
14+
15+
## Prerequisites
16+
- Copilot Studio agent configured with an Anthropic model (Settings -> Agent model).
17+
- Custom UI wired to the Microsoft 365 Agents SDK.
18+
- Optional backend summarization endpoint to shorten verbose reasoning (recommended for UX). GPT-family models do not yet emit reasoning traces.
19+
20+
## Core Flow
21+
1. **Initialize the client**
22+
```js
23+
import { CopilotStudioClient } from '@microsoft/agents-copilotstudio-client';
24+
import { acquireToken } from './acquireToken.js';
25+
import { settings } from './settings.js';
26+
27+
export const createCopilotClient = async () => {
28+
const token = await acquireToken(settings);
29+
return new CopilotStudioClient(settings, token);
30+
};
31+
```
32+
2. **Start a conversation**
33+
```js
34+
const copilotClient = await createCopilotClient();
35+
let conversationId;
36+
37+
for await (const act of copilotClient.startConversationAsync(true)) {
38+
conversationId = act.conversation?.id ?? conversationId;
39+
if (conversationId) break;
40+
}
41+
```
42+
3. **Send a prompt**
43+
```js
44+
const prompt = `Create the following ticket:\n\nTitle: ${shortTitle}\nDescription: ${longDescription}`;
45+
const activityStream = copilotClient.askQuestionAsync(prompt, conversationId);
46+
```
47+
4. **Capture reasoning and answers**
48+
```js
49+
for await (const activity of activityStream) {
50+
if (!activity) continue;
51+
52+
const activityType = activity.type?.toLowerCase();
53+
54+
if (activityType === 'typing' && activity.channelData?.streamType === 'informative') {
55+
const streamKey = resolveStreamKey(activity);
56+
const previousActivity = streamLastActivity.get(streamKey);
57+
58+
if (previousActivity && isContinuationOfPrevious(previousActivity, activity)) {
59+
streamLastActivity.set(streamKey, activity);
60+
continue;
61+
}
62+
63+
await flushActivity(previousActivity, false);
64+
streamLastActivity.set(streamKey, activity);
65+
continue;
66+
}
67+
68+
if (activityType === 'message') {
69+
agentMessages.push(activity.text);
70+
continue;
71+
}
72+
}
73+
```
74+
75+
### Detect Informative Typing
76+
```js
77+
const isReasoningTyping = (activity) =>
78+
(activity?.type || '').toLowerCase() === 'typing' &&
79+
activity?.channelData?.streamType === 'informative';
80+
```
81+
82+
## Summarize Long Thoughts
83+
Reasoning chunks can be lengthy. Capture completed thoughts and optionally POST them to a backend summarizer:
84+
```js
85+
async function summarizeSafely(text) {
86+
try {
87+
const res = await fetch('/api/summarize', {
88+
method: 'POST',
89+
headers: { 'Content-Type': 'application/json' },
90+
body: JSON.stringify({ text })
91+
});
92+
const { summary } = await res.json();
93+
return summary.trim();
94+
} catch {
95+
return null;
96+
}
97+
}
98+
```
99+
Keep API keys server-side and apply rate limiting. The sample UI exposes an input control so end users can supply their summarizer key during demos.
100+
101+
## UI Pattern Tips
102+
- Show a calm, rotating status label once users submit a ticket (for example, "Reviewing possible options...").
103+
- Keep the spinner visible while informative typing events are active.
104+
- Prepend the newest summarized reasoning to the top, keeping the latest five items.
105+
- Add a subtle entrance animation and collapse the panel once the final answer arrives, with an optional "Show details" toggle.
106+
107+
## Summary Checklist
108+
1. Switch your agent to an Anthropic model.
109+
2. Iterate the Microsoft 365 Agents SDK activity stream.
110+
3. Detect reasoning via `type === 'typing'` and `channelData.streamType === 'informative'`.
111+
4. Group reasoning chunks by `channelData.streamId`.
112+
5. Flush pending reasoning when you receive the final message.
113+
6. Optionally summarize server-side to protect secrets.
114+
7. Render a compact Thinking panel with recent updates.
115+
116+
## Try It Yourself
117+
- Clone the reference implementation.
118+
- Configure Copilot Studio with an Anthropic model.
119+
- Run the sample locally, observe informative typing streams, and integrate your summarizer.
120+
121+
Questions or feedback on the pattern are welcome.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License.
4+
*/
5+
6+
export async function acquireToken (settings) {
7+
const msalInstance = new window.msal.PublicClientApplication({
8+
auth: {
9+
clientId: settings.appClientId,
10+
authority: `https://login.microsoftonline.com/${settings.tenantId}`,
11+
},
12+
})
13+
14+
await msalInstance.initialize()
15+
const loginRequest = {
16+
scopes: ['https://api.powerplatform.com/.default'],
17+
redirectUri: window.location.origin,
18+
}
19+
20+
// When there are not accounts or the acquireTokenSilent fails,
21+
// it will fall back to loginPopup.
22+
try {
23+
const accounts = await msalInstance.getAllAccounts()
24+
if (accounts.length > 0) {
25+
const response = await msalInstance.acquireTokenSilent({
26+
...loginRequest,
27+
account: accounts[0],
28+
})
29+
return response.accessToken
30+
}
31+
} catch (e) {
32+
if (!(e instanceof window.msal.InteractionRequiredAuthError)) {
33+
throw e
34+
}
35+
}
36+
37+
const response = await msalInstance.loginPopup(loginRequest)
38+
return response.accessToken
39+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License.
4+
*/
5+
6+
import { CopilotStudioClient } from '@microsoft/agents-copilotstudio-client'
7+
8+
import { acquireToken } from './acquireToken.js'
9+
import { settings } from './settings.js'
10+
11+
export const createCopilotClient = async () => {
12+
const token = await acquireToken(settings)
13+
return new CopilotStudioClient(settings, token)
14+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License.
4+
*/
5+
6+
// This file emulates the process object in node.
7+
// rename this file to settings.js before running this test sample
8+
import { ConnectionSettings } from '@microsoft/agents-copilotstudio-client'
9+
10+
// Flag to enable debug mode, which will store the debug information in localStorage.
11+
// Copilot Studio Client uses the "debug" library for logging (https://github.com/debug-js/debug?tab=readme-ov-file#browser-support).
12+
window.localStorage.debug = 'copilot-studio-client'
13+
14+
export const settings = new ConnectionSettings({
15+
// App ID of the App Registration used to log in, this should be in the same tenant as the Copilot.
16+
appClientId: 'APP-REGISTRATION-CLIENT-ID-HERE',
17+
// Tenant ID of the App Registration used to log in, this should be in the same tenant as the Copilot.
18+
tenantId: 'YOUR-TENANT-ID-HERE',
19+
// Authority endpoint for the Azure AD login. Default is 'https://login.microsoftonline.com'.
20+
authority: 'https://login.microsoftonline.com',
21+
// Environment ID of the environment with the Copilot Studio App.
22+
environmentId: undefined,
23+
// Schema Name of the Copilot to use.
24+
agentIdentifier: undefined,
25+
// PowerPlatformCloud enum key.
26+
cloud: undefined,
27+
// Power Platform API endpoint to use if Cloud is configured as "Other".
28+
customPowerPlatformCloud: undefined,
29+
// AgentType enum key.
30+
copilotAgentType: undefined,
31+
// URL used to connect to the Copilot Studio service.
32+
directConnectUrl: "YOUR-DIRECT-CONNECT-URL-HERE",
33+
// Flag to use the "x-ms-d2e-experimental" header URL on subsequent calls to the Copilot Studio service.
34+
useExperimentalEndpoint: false
35+
})

ShowReasoningSample/index.html

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Next-Gen Incident Desk</title>
7+
<link rel="preconnect" href="https://fonts.googleapis.com" />
8+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
10+
<link rel="stylesheet" href="styles.css" />
11+
<script src="https://unpkg.com/@azure/msal-browser@4.13.1/lib/msal-browser.js"></script>
12+
<script type="importmap">
13+
{
14+
"imports": {
15+
"@microsoft/agents-copilotstudio-client": "https://unpkg.com/@microsoft/agents-copilotstudio-client@1.1.0-alpha.58/dist/src/browser.mjs"
16+
}
17+
}
18+
</script>
19+
</head>
20+
<body>
21+
<div class="background">
22+
<div class="blob blob-one"></div>
23+
<div class="blob blob-two"></div>
24+
<div class="orb orb-one"></div>
25+
<div class="orb orb-two"></div>
26+
</div>
27+
28+
<header class="topbar">
29+
<div class="brand">
30+
<span class="brand-mark">PP</span>
31+
<span class="brand-text">PPCC Ticket Portal</span>
32+
</div>
33+
<a class="cta" href="#new-ticket">Raise a Ticket</a>
34+
</header>
35+
36+
<main>
37+
<section class="hero">
38+
<div class="hero-copy">
39+
<h1>Hyper-intelligent ticketing, one agent away.</h1>
40+
<p>
41+
Feed our AI-native ITSM engine and let it unravel the root cause, urgency,
42+
and best responder in seconds thanks to Copilot Studio. Craft your
43+
description, we decode the rest.
44+
</p>
45+
<a class="hero-button" href="#new-ticket">Submit an Issue</a>
46+
</div>
47+
<div class="hero-visual" aria-hidden="true">
48+
<div class="pulse"></div>
49+
<div class="card card-one">
50+
<h3>Auto Insights</h3>
51+
<p>Intent detection &amp; responder routing.</p>
52+
</div>
53+
<div class="card card-two">
54+
<h3>Resolution Boost</h3>
55+
<p>90% faster assignment, fewer escalations.</p>
56+
</div>
57+
<div class="floating-chip">
58+
<span class="chip-dot"></span>
59+
<span>AI Ops Ready</span>
60+
</div>
61+
</div>
62+
</section>
63+
64+
<section id="new-ticket" class="form-section">
65+
<div class="form-card">
66+
<div class="form-headline">
67+
<h2>Open a new support request</h2>
68+
<p>
69+
Keep the title sharp. Use the long description to narrate the full
70+
story – our Copilot Studio Agent will ingests it to extract signals, timelines,
71+
stakeholders, and impact automatically.
72+
</p>
73+
</div>
74+
<form id="ticket-form" novalidate>
75+
<label class="input-group">
76+
<span>Short title</span>
77+
<input type="text" name="shortTitle" id="shortTitle" placeholder="e.g. VPN outage for remote teams" required maxlength="120" />
78+
</label>
79+
<label class="input-group">
80+
<span>Long description</span>
81+
<textarea name="longDescription" id="longDescription" rows="6" placeholder="Walk us through the affected systems, time stamps, and any experiments you already ran. Our AI will autodetect what matters." required></textarea>
82+
<small class="helper-text">This narrative fuels our Copilot Studio auto-triage engine.</small>
83+
</label>
84+
<label class="input-group input-group--api-key" hidden>
85+
<span>Azure OpenAI key</span>
86+
<input type="password" name="apiKey" id="apiKey" placeholder="Paste your Azure OpenAI key" value="SAMPLE-KEY-HERE" autocomplete="off" />
87+
<small class="helper-text">Secured locally for this session only.</small>
88+
</label>
89+
<button type="submit" class="submit-button">Raise Ticket</button>
90+
<div id="thinking" class="thinking" hidden>
91+
<div class="thinking-spinner" aria-hidden="true"></div>
92+
<div class="thinking-content">
93+
<span class="thinking-label">Copilot is thinking</span>
94+
<div id="thinking-updates" class="thinking-updates" aria-live="polite"></div>
95+
</div>
96+
</div>
97+
</form>
98+
<div id="status" class="status" role="status" aria-live="polite"></div>
99+
</div>
100+
</section>
101+
</main>
102+
103+
<footer class="footer">
104+
<p>&copy; <span id="year">2025</span> PPCC Microsoft Demo. Built for proactive operations.</p>
105+
</footer>
106+
107+
<script src="script.js" type="module"></script>
108+
109+
</body>
110+
</html>

0 commit comments

Comments
 (0)