@microsoft/m365agentsplayground-cli 0.2.25-alpha.20260507-efe1416.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +341 -0
- package/build/cardValidator.d.ts +18 -0
- package/build/cardValidator.d.ts.map +1 -0
- package/build/cardValidator.js +47 -0
- package/build/conversationServer.d.ts +29 -0
- package/build/conversationServer.d.ts.map +1 -0
- package/build/conversationServer.js +127 -0
- package/build/conversationTypes.d.ts +146 -0
- package/build/conversationTypes.d.ts.map +1 -0
- package/build/conversationTypes.js +5 -0
- package/build/index.d.ts +14 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +25 -0
- package/build/notificationSender.d.ts +16 -0
- package/build/notificationSender.d.ts.map +1 -0
- package/build/notificationSender.js +120 -0
- package/build/responseCapture.d.ts +29 -0
- package/build/responseCapture.d.ts.map +1 -0
- package/build/responseCapture.js +119 -0
- package/build/runConversation.d.ts +17 -0
- package/build/runConversation.d.ts.map +1 -0
- package/build/runConversation.js +338 -0
- package/build/serverManager.d.ts +46 -0
- package/build/serverManager.d.ts.map +1 -0
- package/build/serverManager.js +149 -0
- package/build/start-server.d.ts +9 -0
- package/build/start-server.d.ts.map +1 -0
- package/build/start-server.js +23 -0
- package/build/testClient.d.ts +146 -0
- package/build/testClient.d.ts.map +1 -0
- package/build/testClient.js +434 -0
- package/build/types.d.ts +125 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +7 -0
- package/build/websocketClient.d.ts +56 -0
- package/build/websocketClient.d.ts.map +1 -0
- package/build/websocketClient.js +129 -0
- package/package.json +36 -0
- package/src/cardValidator.ts +56 -0
- package/src/conversationServer.ts +147 -0
- package/src/conversationTypes.ts +169 -0
- package/src/index.ts +37 -0
- package/src/notificationSender.ts +135 -0
- package/src/responseCapture.ts +145 -0
- package/src/runConversation.ts +379 -0
- package/src/serverManager.ts +172 -0
- package/src/start-server.ts +26 -0
- package/src/testClient.ts +515 -0
- package/src/types.ts +155 -0
- package/src/websocketClient.ts +153 -0
- package/tsconfig.json +16 -0
package/README.md
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
# playground-cli
|
|
2
|
+
|
|
3
|
+
A programmatic simulation library for Microsoft 365 bots/agents. Drive your bot's responses without a browser or manual interaction — ideal for automated testing, CI pipelines, and LLM-based evaluation.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
The package is part of the monorepo. Link it in your project:
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@microsoft/m365agentsplayground-cli": "file:../../packages/playground-cli"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Build the package before use:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm run build --workspace=packages/playground-cli
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { TestClient } from "@microsoft/m365agentsplayground-cli";
|
|
27
|
+
|
|
28
|
+
const client = new TestClient({
|
|
29
|
+
botEndpoint: "http://localhost:3978/api/messages",
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
await client.start();
|
|
33
|
+
const [response] = await client.sendMessage("Hello!");
|
|
34
|
+
console.log(response.text);
|
|
35
|
+
await client.stop();
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## API
|
|
39
|
+
|
|
40
|
+
### TestClient
|
|
41
|
+
|
|
42
|
+
The main class for interacting with your bot.
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
new TestClient(config: TestClientConfig)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
#### TestClientConfig
|
|
49
|
+
|
|
50
|
+
| Property | Type | Default | Description |
|
|
51
|
+
| -------------- | ------------------------------ | ---------- | -------------------------------- |
|
|
52
|
+
| `botEndpoint` | `string` | _required_ | Your bot's endpoint URL |
|
|
53
|
+
| `timeout` | `number` | `5000` | Response timeout in milliseconds |
|
|
54
|
+
| `bot` | `BotConfig` | - | Bot identity configuration |
|
|
55
|
+
| `deliveryMode` | `"expectReplies" \| "default"` | - | How the bot delivers responses |
|
|
56
|
+
|
|
57
|
+
#### BotConfig
|
|
58
|
+
|
|
59
|
+
| Property | Type | Description |
|
|
60
|
+
| --------------- | ---------------------------------- | ------------------------------------------------------ |
|
|
61
|
+
| `id` | `string` | Bot's unique identifier |
|
|
62
|
+
| `name` | `string` | Display name (max 42 chars) |
|
|
63
|
+
| `role` | `"user" \| "bot" \| "agenticUser"` | Bot role |
|
|
64
|
+
| `tenantId` | `string` | Tenant ID (also sets `activity.conversation.tenantId`) |
|
|
65
|
+
| `agenticUserId` | `string` | User ID for agentic bots |
|
|
66
|
+
| `agenticAppId` | `string` | App ID for agentic bots |
|
|
67
|
+
|
|
68
|
+
#### Methods
|
|
69
|
+
|
|
70
|
+
| Method | Returns | Description |
|
|
71
|
+
| --------------------- | ------------------------ | ------------------------------------ |
|
|
72
|
+
| `start()` | `Promise<void>` | Initialize the client |
|
|
73
|
+
| `stop()` | `Promise<void>` | Shut down and clean up |
|
|
74
|
+
| `sendMessage(text)` | `Promise<BotResponse[]>` | Send a message, wait for response(s) |
|
|
75
|
+
| `getMessages()` | `Message[]` | All messages in the conversation |
|
|
76
|
+
| `getLastBotMessage()` | `Message \| undefined` | Most recent bot message |
|
|
77
|
+
| `getConversationId()` | `string` | Current conversation ID |
|
|
78
|
+
| `newConversation()` | `void` | Reset conversation state |
|
|
79
|
+
|
|
80
|
+
#### WebSocket Events
|
|
81
|
+
|
|
82
|
+
TestClient extends EventEmitter and emits real-time events:
|
|
83
|
+
|
|
84
|
+
| Event | Payload | Description |
|
|
85
|
+
| ----------------- | ---------------------- | ------------------------------------- |
|
|
86
|
+
| `message:created` | `ICreateMessageAction` | New message from bot or user |
|
|
87
|
+
| `message:updated` | `IUpdateMessageAction` | Message was updated (streaming, edit) |
|
|
88
|
+
| `typing` | `ITypingAction` | Bot typing indicator |
|
|
89
|
+
| `websocket:error` | `Error` | WebSocket connection error |
|
|
90
|
+
| `websocket:close` | - | WebSocket connection closed |
|
|
91
|
+
|
|
92
|
+
### BotResponse
|
|
93
|
+
|
|
94
|
+
Returned by `sendMessage()`.
|
|
95
|
+
|
|
96
|
+
| Property | Type | Description |
|
|
97
|
+
| ------------- | --------------- | ------------------------------------------ |
|
|
98
|
+
| `messageId` | `string` | Unique message ID |
|
|
99
|
+
| `text` | `string?` | Text content |
|
|
100
|
+
| `attachments` | `Attachment[]?` | Attachments (Adaptive Cards, etc.) |
|
|
101
|
+
| `timestamp` | `number` | Unix timestamp |
|
|
102
|
+
| `raw` | `Message` | Raw Message object for detailed inspection |
|
|
103
|
+
|
|
104
|
+
## Conversation Server
|
|
105
|
+
|
|
106
|
+
An HTTP wrapper around TestClient so non-Node languages (Python, curl, etc.) can drive bot conversations by POSTing JSON.
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# From the repo root (after install + build):
|
|
110
|
+
npm run server --workspace=packages/agents-simulator -- --port 9000
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Or programmatically:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import { createConversationServer } from "agents-simulator";
|
|
117
|
+
const server = await createConversationServer({ port: 9000 });
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### `POST /run-conversation`
|
|
121
|
+
|
|
122
|
+
Run a multi-turn conversation:
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"config": {
|
|
127
|
+
"botEndpoint": "http://localhost:3978/api/messages",
|
|
128
|
+
"timeout": 120000,
|
|
129
|
+
"deliveryMode": "expectReplies",
|
|
130
|
+
"bot": { "id": "bot@tenant.onmicrosoft.com", "name": "My Bot" },
|
|
131
|
+
"personas": {
|
|
132
|
+
"alice": { "id": "alice-id", "name": "Alice", "email": "alice@example.com" }
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
"scenario": "greeting-test",
|
|
136
|
+
"input": {
|
|
137
|
+
"turns": [
|
|
138
|
+
{ "test_id": "t1", "prompt": "Hello!" },
|
|
139
|
+
{ "test_id": "t2", "prompt": "What can you do?" }
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Response:**
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
{
|
|
149
|
+
"type": "conversation_result",
|
|
150
|
+
"scenario": "greeting-test",
|
|
151
|
+
"status": "Completed",
|
|
152
|
+
"duration_seconds": 4.2,
|
|
153
|
+
"turns": [
|
|
154
|
+
{
|
|
155
|
+
"test_id": "t1",
|
|
156
|
+
"prompt": "Hello!",
|
|
157
|
+
"actual_response": "Hi! I'd be happy to help. What do you need?",
|
|
158
|
+
"status": "Completed",
|
|
159
|
+
"duration_seconds": 1.8
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"test_id": "t2",
|
|
163
|
+
"prompt": "What can you do?",
|
|
164
|
+
"actual_response": "I can help you with...",
|
|
165
|
+
"status": "Completed",
|
|
166
|
+
"duration_seconds": 2.4
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Configuration
|
|
173
|
+
|
|
174
|
+
The `config` object in the request body:
|
|
175
|
+
|
|
176
|
+
| Field | Type | Default | Description |
|
|
177
|
+
| -------------- | ------------------------------- | ----------------- | ------------------------------------- |
|
|
178
|
+
| `botEndpoint` | `string` | _required_ | Bot's messaging endpoint URL |
|
|
179
|
+
| `timeout` | `number` | `120000` | Per-turn response timeout (ms) |
|
|
180
|
+
| `deliveryMode` | `"expectReplies" \| "default"` | `"expectReplies"` | How the bot delivers responses |
|
|
181
|
+
| `chatType` | `"personal" \| "group" \| "channel"` | `"personal"` | Default chat context for all turns |
|
|
182
|
+
| `streamingSettleDelayMs` | `number` | `800` | Quiet-period (ms) for streaming bots — see below |
|
|
183
|
+
| `bot` | `BotConfig` | - | Bot identity (see above) |
|
|
184
|
+
| `personas` | `Record<string, PersonaConfig>` | - | Named personas for notification turns |
|
|
185
|
+
|
|
186
|
+
> **Streaming bots (teams-ai / teams.ts `stream: true`):** When the simulator receives the placeholder `"Loading stream results..."`, it automatically switches into streaming mode. It first waits for a `streamType:"final"` WebSocket event (the precise end-of-stream signal from teams-ai and teams.ts SDK) — this is instant and accurate. If the bot doesn't send `streamType`, it falls back to a quiet-period: waits until no new `updateActivity` events arrive for `streamingSettleDelayMs` ms (default: 800 ms). Increase `streamingSettleDelayMs` only if you have a slow LLM that pauses mid-stream for over 800 ms. For bots with `stream: false` this option has no effect.
|
|
187
|
+
|
|
188
|
+
### Turn Types
|
|
189
|
+
|
|
190
|
+
Each turn has an optional `turn_type` that controls what kind of Bot Framework activity is sent:
|
|
191
|
+
|
|
192
|
+
| Turn type | Description |
|
|
193
|
+
| ------------------- | ------------------------------------------------ |
|
|
194
|
+
| `"chat"` | Standard chat message (default) |
|
|
195
|
+
| `"sendEmail"` | Email notification activity |
|
|
196
|
+
| `"mentionInWord"` | Word document mention notification activity |
|
|
197
|
+
| `"meetingStart"` | Meeting started event |
|
|
198
|
+
| `"meetingEnd"` | Meeting ended event |
|
|
199
|
+
| `"participantJoin"` | Meeting participant joined event |
|
|
200
|
+
| `"participantLeave"`| Meeting participant left event |
|
|
201
|
+
| `"messageReaction"` | Reaction added to a message (like, heart, etc.) |
|
|
202
|
+
| `"install"` | Bot installation update |
|
|
203
|
+
| `"userAdded"` | User added to conversation |
|
|
204
|
+
| `"botAdded"` | Bot added to conversation |
|
|
205
|
+
| `"channelCreated"` | Channel created (team context required) |
|
|
206
|
+
| `"teamRenamed"` | Team renamed (team context required) |
|
|
207
|
+
|
|
208
|
+
All values from `CustomActivityTemplateType` are supported. You can mix turn types within a single conversation:
|
|
209
|
+
|
|
210
|
+
```json
|
|
211
|
+
{
|
|
212
|
+
"input": {
|
|
213
|
+
"turns": [
|
|
214
|
+
{ "test_id": "t1", "prompt": "Hello", "turn_type": "chat" },
|
|
215
|
+
{
|
|
216
|
+
"test_id": "t2",
|
|
217
|
+
"prompt": "<html><body>Customer complaint about order #789...</body></html>",
|
|
218
|
+
"turn_type": "sendEmail",
|
|
219
|
+
"persona": "customer1"
|
|
220
|
+
},
|
|
221
|
+
{ "test_id": "t3", "prompt": "Summarize the email I just received", "turn_type": "chat" },
|
|
222
|
+
{ "test_id": "t4", "prompt": "", "turn_type": "meetingStart" },
|
|
223
|
+
{ "test_id": "t5", "prompt": "", "turn_type": "messageReaction", "reaction_type": "like" }
|
|
224
|
+
]
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
#### Turn-specific fields
|
|
230
|
+
|
|
231
|
+
| Field | Applies to | Description |
|
|
232
|
+
| ----------------- | ------------------------------- | -------------------------------------------------------- |
|
|
233
|
+
| `reaction_type` | `messageReaction` | Reaction emoji: `like` (default), `heart`, `laugh`, `surprised`, `sad`, `angry` |
|
|
234
|
+
| `reply_to_id` | `messageReaction` | Message ID to react to. Defaults to last bot message. |
|
|
235
|
+
| `prompt_metadata.meetingTitle` | `meetingStart`, `meetingEnd` | Override the meeting title in the event. |
|
|
236
|
+
| `prompt_metadata.documentUrl` | `mentionInWord` | Override the document URL. |
|
|
237
|
+
| `prompt_metadata.documentName`| `mentionInWord` | Override the document name. |
|
|
238
|
+
|
|
239
|
+
### Chat Types
|
|
240
|
+
|
|
241
|
+
The `chat_type` field on a turn (or `chatType` in config) controls the conversation context:
|
|
242
|
+
|
|
243
|
+
| Chat type | Description |
|
|
244
|
+
| ------------ | ---------------------------------------------- |
|
|
245
|
+
| `"personal"` | 1:1 personal chat with the bot (default) |
|
|
246
|
+
| `"group"` | Group chat context |
|
|
247
|
+
| `"channel"` | Team channel context (uses general channel) |
|
|
248
|
+
|
|
249
|
+
Set the default for the whole conversation in `config.chatType`, or override per turn with `turn.chat_type`:
|
|
250
|
+
|
|
251
|
+
```json
|
|
252
|
+
{
|
|
253
|
+
"config": {
|
|
254
|
+
"botEndpoint": "http://localhost:3978/api/messages",
|
|
255
|
+
"chatType": "group"
|
|
256
|
+
},
|
|
257
|
+
"scenario": "group-chat-test",
|
|
258
|
+
"input": {
|
|
259
|
+
"turns": [
|
|
260
|
+
{ "test_id": "t1", "prompt": "@bot Hello everyone!", "turn_type": "chat" },
|
|
261
|
+
{ "test_id": "t2", "prompt": "Personal follow-up", "turn_type": "chat", "chat_type": "personal" }
|
|
262
|
+
]
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Parallel Testing
|
|
268
|
+
|
|
269
|
+
Multiple `TestClient` instances in the same process each get a unique `conversationId`, so concurrent tests don't interfere with each other's messages.
|
|
270
|
+
|
|
271
|
+
To test multiple bot endpoints in parallel, run separate `conversationServer` processes on different ports:
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
# Terminal 1 — bot-a on port 9001
|
|
275
|
+
npm run server --workspace=packages/agents-simulator -- --port 9001
|
|
276
|
+
|
|
277
|
+
# Terminal 2 — bot-b on port 9002
|
|
278
|
+
npm run server --workspace=packages/agents-simulator -- --port 9002
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Then point each test suite at its own server URL.
|
|
282
|
+
|
|
283
|
+
### Personas
|
|
284
|
+
|
|
285
|
+
Personas set the `from` identity on notification activities. Define them in `config.personas` and reference by name:
|
|
286
|
+
|
|
287
|
+
- **Conversation-level default:** Set `input.persona` to apply to all turns
|
|
288
|
+
- **Per-turn override:** Set `turn.persona` to override for a specific turn
|
|
289
|
+
- **Persona fields:** `id` (required), `name` (required), `email` (optional — used as `from.id` when present)
|
|
290
|
+
|
|
291
|
+
### Turn Result Statuses
|
|
292
|
+
|
|
293
|
+
| Status | Description |
|
|
294
|
+
| ----------- | ----------------------------------------------- |
|
|
295
|
+
| `Completed` | Bot responded successfully |
|
|
296
|
+
| `TimedOut` | No response within the configured timeout |
|
|
297
|
+
| `Errored` | An error occurred during execution |
|
|
298
|
+
| `Skipped` | Turn was skipped because a previous turn failed |
|
|
299
|
+
|
|
300
|
+
### Example: curl
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
curl http://localhost:9000/health
|
|
304
|
+
|
|
305
|
+
curl -X POST http://localhost:9000/run-conversation \
|
|
306
|
+
-H "Content-Type: application/json" \
|
|
307
|
+
-d '{
|
|
308
|
+
"config": { "botEndpoint": "http://localhost:3978/api/messages" },
|
|
309
|
+
"scenario": "smoke-test",
|
|
310
|
+
"input": { "turns": [{ "test_id": "t1", "prompt": "Hello" }] }
|
|
311
|
+
}'
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Example: Python
|
|
315
|
+
|
|
316
|
+
```python
|
|
317
|
+
import requests
|
|
318
|
+
|
|
319
|
+
resp = requests.post("http://localhost:9000/run-conversation", json={
|
|
320
|
+
"config": {"botEndpoint": "http://localhost:3978/api/messages"},
|
|
321
|
+
"scenario": "my-test",
|
|
322
|
+
"input": {
|
|
323
|
+
"turns": [
|
|
324
|
+
{"test_id": "t1", "prompt": "Hello"},
|
|
325
|
+
{"test_id": "t2", "prompt": "What can you do?"},
|
|
326
|
+
]
|
|
327
|
+
},
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
for turn in resp.json()["turns"]:
|
|
331
|
+
print(f"[{turn['status']}] {turn['actual_response'][:80]}")
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### `GET /health`
|
|
335
|
+
|
|
336
|
+
Returns `{ "status": "ok" }`.
|
|
337
|
+
|
|
338
|
+
## Example Project
|
|
339
|
+
|
|
340
|
+
See `samples/agents-simulator-example` for a complete example with Mocha tests and a sanity-test script.
|
|
341
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adaptive Card schema validation using the official `adaptivecards` package.
|
|
3
|
+
*
|
|
4
|
+
* Validates that a card payload is a well-formed Adaptive Card that Teams can
|
|
5
|
+
* parse. Does NOT check visual rendering — only structural/schema correctness.
|
|
6
|
+
*/
|
|
7
|
+
export interface CardValidationError {
|
|
8
|
+
/** Human-readable description of the issue */
|
|
9
|
+
message: string;
|
|
10
|
+
/** "parsing" | "schema" */
|
|
11
|
+
phase: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Validate a raw Adaptive Card JSON payload.
|
|
15
|
+
* Returns an empty array if the card is valid.
|
|
16
|
+
*/
|
|
17
|
+
export declare function validateAdaptiveCard(payload: Record<string, unknown>): CardValidationError[];
|
|
18
|
+
//# sourceMappingURL=cardValidator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cardValidator.d.ts","sourceRoot":"","sources":["../src/cardValidator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,WAAW,mBAAmB;IAClC,8CAA8C;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAElC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,mBAAmB,EAAE,CAgCvB"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Adaptive Card schema validation using the official `adaptivecards` package.
|
|
4
|
+
*
|
|
5
|
+
* Validates that a card payload is a well-formed Adaptive Card that Teams can
|
|
6
|
+
* parse. Does NOT check visual rendering — only structural/schema correctness.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.validateAdaptiveCard = void 0;
|
|
10
|
+
const adaptivecards_1 = require("adaptivecards");
|
|
11
|
+
/**
|
|
12
|
+
* Validate a raw Adaptive Card JSON payload.
|
|
13
|
+
* Returns an empty array if the card is valid.
|
|
14
|
+
*/
|
|
15
|
+
function validateAdaptiveCard(
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
17
|
+
payload) {
|
|
18
|
+
const errors = [];
|
|
19
|
+
// Basic type guard: must have type === "AdaptiveCard"
|
|
20
|
+
if (payload.type !== "AdaptiveCard") {
|
|
21
|
+
errors.push({
|
|
22
|
+
message: `Expected type "AdaptiveCard", got "${payload.type ?? "(missing)"}"`,
|
|
23
|
+
phase: "schema",
|
|
24
|
+
});
|
|
25
|
+
return errors; // No point parsing further
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const card = new adaptivecards_1.AdaptiveCard();
|
|
29
|
+
const context = new adaptivecards_1.SerializationContext();
|
|
30
|
+
card.parse(payload, context);
|
|
31
|
+
for (let i = 0; i < context.eventCount; i++) {
|
|
32
|
+
const event = context.getEventAt(i);
|
|
33
|
+
errors.push({
|
|
34
|
+
message: event.message ?? String(event),
|
|
35
|
+
phase: event.phase !== undefined ? String(event.phase) : "parsing",
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
errors.push({
|
|
41
|
+
message: err instanceof Error ? err.message : String(err),
|
|
42
|
+
phase: "parsing",
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return errors;
|
|
46
|
+
}
|
|
47
|
+
exports.validateAdaptiveCard = validateAdaptiveCard;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP server for multi-turn conversation execution.
|
|
3
|
+
*
|
|
4
|
+
* Provides a factory function that creates an HTTP server with:
|
|
5
|
+
* POST /run-conversation — execute a multi-turn conversation
|
|
6
|
+
* GET /health — health check
|
|
7
|
+
*
|
|
8
|
+
* All diagnostic logging goes to stderr. The returned port can be
|
|
9
|
+
* written to stdout by the caller (CLI wrapper).
|
|
10
|
+
*/
|
|
11
|
+
export interface ConversationServerOptions {
|
|
12
|
+
/** Port to listen on. Default: 0 (OS-assigned). */
|
|
13
|
+
port?: number;
|
|
14
|
+
/** Host to bind to. Default: "127.0.0.1". */
|
|
15
|
+
host?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ConversationServer {
|
|
18
|
+
/** The port the server is listening on. */
|
|
19
|
+
port: number;
|
|
20
|
+
/** Gracefully shut down the server. */
|
|
21
|
+
close: () => Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Create and start a conversation server.
|
|
25
|
+
*
|
|
26
|
+
* Returns a handle with the assigned port and a close() method.
|
|
27
|
+
*/
|
|
28
|
+
export declare function createConversationServer(options?: ConversationServerOptions): Promise<ConversationServer>;
|
|
29
|
+
//# sourceMappingURL=conversationServer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversationServer.d.ts","sourceRoot":"","sources":["../src/conversationServer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAUH,MAAM,WAAW,yBAAyB;IACxC,mDAAmD;IACnD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AA4ED;;;;GAIG;AACH,wBAAsB,wBAAwB,CAC5C,OAAO,CAAC,EAAE,yBAAyB,GAClC,OAAO,CAAC,kBAAkB,CAAC,CAgC7B"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* HTTP server for multi-turn conversation execution.
|
|
4
|
+
*
|
|
5
|
+
* Provides a factory function that creates an HTTP server with:
|
|
6
|
+
* POST /run-conversation — execute a multi-turn conversation
|
|
7
|
+
* GET /health — health check
|
|
8
|
+
*
|
|
9
|
+
* All diagnostic logging goes to stderr. The returned port can be
|
|
10
|
+
* written to stdout by the caller (CLI wrapper).
|
|
11
|
+
*/
|
|
12
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
15
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
16
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
17
|
+
}
|
|
18
|
+
Object.defineProperty(o, k2, desc);
|
|
19
|
+
}) : (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
o[k2] = m[k];
|
|
22
|
+
}));
|
|
23
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
24
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
25
|
+
}) : function(o, v) {
|
|
26
|
+
o["default"] = v;
|
|
27
|
+
});
|
|
28
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.createConversationServer = void 0;
|
|
37
|
+
const http = __importStar(require("http"));
|
|
38
|
+
const runConversation_1 = require("./runConversation");
|
|
39
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
40
|
+
// Request body parsing
|
|
41
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
42
|
+
function readBody(req) {
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
const chunks = [];
|
|
45
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
46
|
+
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
|
|
47
|
+
req.on("error", reject);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
async function handleRequest(req, res) {
|
|
51
|
+
const url = req.url ?? "";
|
|
52
|
+
const method = req.method ?? "";
|
|
53
|
+
if (method === "GET" && url === "/health") {
|
|
54
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
55
|
+
res.end(JSON.stringify({ status: "ok" }));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (method === "POST" && url === "/run-conversation") {
|
|
59
|
+
let body;
|
|
60
|
+
try {
|
|
61
|
+
const raw = await readBody(req);
|
|
62
|
+
body = JSON.parse(raw);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
66
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
67
|
+
res.end(JSON.stringify({ error: `Invalid JSON: ${message}` }));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (!body.config || !body.scenario || !body.input) {
|
|
71
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
72
|
+
res.end(JSON.stringify({ error: "Missing required fields: config, scenario, input" }));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
(0, runConversation_1.log)(`HTTP request: ${body.scenario}`);
|
|
77
|
+
const result = await (0, runConversation_1.runConversation)(body.config, body.scenario, body.input);
|
|
78
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
79
|
+
res.end(JSON.stringify(result));
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
83
|
+
(0, runConversation_1.logError)(`Unhandled error in runConversation: ${message}`);
|
|
84
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
85
|
+
res.end(JSON.stringify({ error: message }));
|
|
86
|
+
}
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
90
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
91
|
+
}
|
|
92
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
93
|
+
// Factory
|
|
94
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
95
|
+
/**
|
|
96
|
+
* Create and start a conversation server.
|
|
97
|
+
*
|
|
98
|
+
* Returns a handle with the assigned port and a close() method.
|
|
99
|
+
*/
|
|
100
|
+
async function createConversationServer(options) {
|
|
101
|
+
const port = options?.port ?? 0;
|
|
102
|
+
const host = options?.host ?? "127.0.0.1";
|
|
103
|
+
const server = http.createServer((req, res) => {
|
|
104
|
+
handleRequest(req, res).catch((err) => {
|
|
105
|
+
(0, runConversation_1.logError)(`Request handler crashed: ${String(err)}`);
|
|
106
|
+
if (!res.headersSent) {
|
|
107
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
108
|
+
}
|
|
109
|
+
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
return new Promise((resolve, reject) => {
|
|
113
|
+
server.once("error", reject);
|
|
114
|
+
server.listen(port, host, () => {
|
|
115
|
+
const addr = server.address();
|
|
116
|
+
const assignedPort = typeof addr === "object" && addr ? addr.port : port;
|
|
117
|
+
(0, runConversation_1.log)(`Server listening on http://${host}:${assignedPort}`);
|
|
118
|
+
resolve({
|
|
119
|
+
port: assignedPort,
|
|
120
|
+
close: () => new Promise((res, rej) => {
|
|
121
|
+
server.close((err) => (err ? rej(err) : res()));
|
|
122
|
+
}),
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
exports.createConversationServer = createConversationServer;
|