Skip to content

Commit aa946ef

Browse files
committed
feat: add support to cancel exectuions and WS
1 parent cccadfe commit aa946ef

20 files changed

Lines changed: 1051 additions & 42 deletions

SERVER.md

Lines changed: 153 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
# Voiceflow CLI API Server
22

3-
The Voiceflow CLI now includes an HTTP API server that exposes test execution functionality as REST endpoints with auto-generated OpenAPI/Swagger documentation.
3+
The Voiceflow CLI now includes an HTTP API server that exposes test execution functionality as REST endpoints with auto-generated OpenAPI/Swagger documentation and **WebSocket support for real-time log streaming**.
44

55
## Features
66

77
- **HTTP API**: Execute test suites via REST endpoints
8+
- **WebSocket API**: Execute, cancel, and monitor test suites over a persistent WebSocket connection with real-time log streaming
89
- **Real-time Logging**: Capture and return test execution logs in API responses
910
- **OpenAPI/Swagger**: Auto-generated API documentation at `/swagger/index.html`
1011
- **Asynchronous Execution**: Non-blocking test execution with status tracking
@@ -140,6 +141,157 @@ Once the server is running, you can access the interactive API documentation at:
140141
http://localhost:8080/swagger/index.html
141142
```
142143

144+
## WebSocket API
145+
146+
Connect to `ws://localhost:8080/ws` for a persistent connection with real-time log streaming. The WebSocket endpoint exposes the same functionality as the REST API but pushes log lines as they happen instead of requiring polling.
147+
148+
### Protocol
149+
150+
All messages are JSON objects.
151+
152+
**Client → Server:**
153+
154+
| Action | Description | Fields |
155+
|--------|-------------|--------|
156+
| `execute` | Start a test suite | `{ "action": "execute", "data": <TestExecutionRequest> }` |
157+
| `cancel` | Cancel a running execution | `{ "action": "cancel", "id": "<execution-id>" }` |
158+
| `status` | Get execution status | `{ "action": "status", "id": "<execution-id>" }` |
159+
160+
**Server → Client:**
161+
162+
| Type | Description |
163+
|------|-------------|
164+
| `log` | Real-time log line: `{ "type": "log", "id": "...", "message": "..." }` |
165+
| `status` | Status update: `{ "type": "status", "id": "...", "data": <TestStatusResponse> }` |
166+
| `result` | Final result when execution finishes: `{ "type": "result", "id": "...", "data": <TestStatusResponse> }` |
167+
| `error` | Error message: `{ "type": "error", "message": "..." }` |
168+
169+
### WebSocket Examples
170+
171+
#### Using websocat (CLI)
172+
173+
```bash
174+
# Install: brew install websocat
175+
websocat ws://localhost:8080/ws
176+
```
177+
178+
Then paste JSON messages:
179+
180+
```json
181+
{"action":"execute","data":{"suite":{"name":"Example Suite","description":"Test","environment_name":"production","tests":[{"id":"test_1","test":{"name":"Example test","description":"Test","interactions":[{"id":"t1","user":{"type":"text","text":"hi"},"agent":{"validate":[{"type":"contains","value":"hello"}]}}]}}]}}}
182+
```
183+
184+
Cancel a running execution:
185+
186+
```json
187+
{"action":"cancel","id":"<execution-id-from-status-message>"}
188+
```
189+
190+
#### Using JavaScript
191+
192+
```javascript
193+
const ws = new WebSocket('ws://localhost:8080/ws');
194+
195+
ws.onopen = () => {
196+
// Execute a test suite
197+
ws.send(JSON.stringify({
198+
action: 'execute',
199+
data: {
200+
api_key: 'your_api_key',
201+
suite: {
202+
name: 'Example Suite',
203+
description: 'Suite used as an example',
204+
environment_name: 'production',
205+
tests: [
206+
{
207+
id: 'test_1',
208+
test: {
209+
name: 'Example test',
210+
description: 'These are some tests',
211+
interactions: [
212+
{
213+
id: 'test_1_1',
214+
user: { type: 'text', text: 'hi' },
215+
agent: { validate: [{ type: 'contains', value: 'hello' }] }
216+
}
217+
]
218+
}
219+
}
220+
]
221+
}
222+
}
223+
}));
224+
};
225+
226+
ws.onmessage = (event) => {
227+
const msg = JSON.parse(event.data);
228+
switch (msg.type) {
229+
case 'log':
230+
console.log(`[LOG] ${msg.message}`);
231+
break;
232+
case 'status':
233+
console.log(`[STATUS] ${msg.id}: ${msg.message || msg.data.status}`);
234+
// Cancel example: ws.send(JSON.stringify({ action: 'cancel', id: msg.id }));
235+
break;
236+
case 'result':
237+
console.log('[RESULT]', msg.data);
238+
break;
239+
case 'error':
240+
console.error('[ERROR]', msg.message);
241+
break;
242+
}
243+
};
244+
```
245+
246+
#### Using Python
247+
248+
```python
249+
import asyncio
250+
import json
251+
import websockets
252+
253+
async def run_tests():
254+
async with websockets.connect('ws://localhost:8080/ws') as ws:
255+
# Execute a test suite
256+
await ws.send(json.dumps({
257+
'action': 'execute',
258+
'data': {
259+
'api_key': 'your_api_key',
260+
'suite': {
261+
'name': 'Example Suite',
262+
'description': 'Test',
263+
'environment_name': 'production',
264+
'tests': [{
265+
'id': 'test_1',
266+
'test': {
267+
'name': 'Example test',
268+
'description': 'Test',
269+
'interactions': [{
270+
'id': 't1',
271+
'user': {'type': 'text', 'text': 'hi'},
272+
'agent': {'validate': [{'type': 'contains', 'value': 'hello'}]}
273+
}]
274+
}
275+
}]
276+
}
277+
}
278+
}))
279+
280+
# Read messages until execution completes
281+
async for message in ws:
282+
msg = json.loads(message)
283+
if msg['type'] == 'log':
284+
print(f"[LOG] {msg['message']}")
285+
elif msg['type'] == 'result':
286+
print(f"[RESULT] {msg['data']}")
287+
break
288+
elif msg['type'] == 'error':
289+
print(f"[ERROR] {msg['message']}")
290+
break
291+
292+
asyncio.run(run_tests())
293+
```
294+
143295
## Usage Examples
144296

145297
### Using curl

docs/docs/server/api-endpoints.md

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
11
# API Endpoints
22

3-
## Health Check
3+
## REST API
4+
5+
### Health Check
46
```http
57
GET /health
68
```
79

810
Returns the health status of the server.
911

10-
## Execute Test Suite
12+
**Response:**
13+
```json
14+
{
15+
"status": "healthy",
16+
"time": "2026-02-23T14:00:00+01:00"
17+
}
18+
```
19+
20+
### Execute Test Suite
1121
```http
1222
POST /api/v1/tests/execute
1323
Content-Type: application/json
@@ -49,7 +59,7 @@ Content-Type: application/json
4959
}
5060
```
5161

52-
**Response:**
62+
**Response (202 Accepted):**
5363
```json
5464
{
5565
"id": "123e4567-e89b-12d3-a456-426614174000",
@@ -59,23 +69,23 @@ Content-Type: application/json
5969
}
6070
```
6171

62-
Executes a test suite asynchronously and returns an execution ID for tracking. The suite configuration and tests are now embedded directly in the request body, making the API more HTTP-friendly and eliminating the need for file system access.
72+
Executes a test suite asynchronously and returns an execution ID for tracking. The suite configuration and tests are embedded directly in the request body, making the API more HTTP-friendly and eliminating the need for file system access.
6373

64-
### Request Parameters
74+
#### Request Parameters
6575

6676
- `api_key` (optional): Override the global Voiceflow API key for this specific test execution
6777
- `voiceflow_subdomain` (optional): Override the global Voiceflow subdomain for this specific test execution. This allows you to test against different Voiceflow environments or custom subdomains without affecting the global configuration
6878
- `suite`: The test suite configuration containing the test definitions
6979

70-
### Using Custom Subdomains
80+
#### Using Custom Subdomains
7181

7282
When you specify a `voiceflow_subdomain`, the API will use that subdomain for all interactions in the test suite. For example:
7383

7484
- If you set `"voiceflow_subdomain": "my-custom-env"`, requests will be sent to `https://general-runtime.my-custom-env.voiceflow.com`
7585
- If you omit this field, the global subdomain configuration will be used
7686
- This is particularly useful for testing against staging environments or customer-specific deployments
7787

78-
## Get Test Status
88+
### Get Test Status
7989
```http
8090
GET /api/v1/tests/status/{execution_id}
8191
```
@@ -97,7 +107,33 @@ GET /api/v1/tests/status/{execution_id}
97107

98108
Retrieves the current status and logs of a test execution.
99109

100-
## System Information
110+
### Cancel Test Execution
111+
```http
112+
POST /api/v1/tests/cancel/{execution_id}
113+
```
114+
115+
Cancels a running test execution.
116+
117+
**Response (200 OK):**
118+
```json
119+
{
120+
"id": "123e4567-e89b-12d3-a456-426614174000",
121+
"status": "cancelled",
122+
"started_at": "2023-01-01T00:00:00Z",
123+
"completed_at": "2023-01-01T00:02:30Z",
124+
"logs": [
125+
"Starting test suite execution...",
126+
"Test suite execution cancelled"
127+
]
128+
}
129+
```
130+
131+
**Error Responses:**
132+
133+
- `404 Not Found`: No execution with the given ID
134+
- `409 Conflict`: Execution is not running (already completed, failed, or cancelled)
135+
136+
### System Information
101137
```http
102138
GET /api/v1/system/info
103139
```
@@ -114,6 +150,31 @@ GET /api/v1/system/info
114150

115151
Returns system information about the running server instance.
116152

153+
## WebSocket API
154+
155+
Connect to `ws://localhost:8080/ws` for a persistent connection with real-time log streaming. The WebSocket endpoint exposes the same functionality as the REST API but pushes log lines as they happen instead of requiring polling.
156+
157+
### Protocol
158+
159+
All messages are JSON objects.
160+
161+
**Client → Server:**
162+
163+
| Action | Description | Fields |
164+
|--------|-------------|--------|
165+
| `execute` | Start a test suite | `{ "action": "execute", "data": <TestExecutionRequest> }` |
166+
| `cancel` | Cancel a running execution | `{ "action": "cancel", "id": "<execution-id>" }` |
167+
| `status` | Get execution status | `{ "action": "status", "id": "<execution-id>" }` |
168+
169+
**Server → Client:**
170+
171+
| Type | Description |
172+
|------|-------------|
173+
| `log` | Real-time log line: `{ "type": "log", "id": "...", "message": "..." }` |
174+
| `status` | Status update: `{ "type": "status", "id": "...", "data": <TestStatusResponse> }` |
175+
| `result` | Final result when execution finishes: `{ "type": "result", "id": "...", "data": <TestStatusResponse> }` |
176+
| `error` | Error message: `{ "type": "error", "message": "..." }` |
177+
117178
## OpenAPI/Swagger Documentation
118179

119180
Once the server is running, you can access the interactive API documentation at:

docs/docs/server/introduction.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
# API Server Introduction
22

3-
The Voiceflow CLI now includes an HTTP API server that exposes test execution functionality as REST endpoints with auto-generated OpenAPI/Swagger documentation.
3+
The Voiceflow CLI now includes an HTTP API server that exposes test execution functionality as REST endpoints with auto-generated OpenAPI/Swagger documentation and **WebSocket support for real-time log streaming**.
44

55
## Features
66

77
- **HTTP API**: Execute test suites via REST endpoints
8+
- **WebSocket API**: Execute, cancel, and monitor test suites over a persistent WebSocket connection with real-time log streaming
89
- **Real-time Logging**: Capture and return test execution logs in API responses
910
- **OpenAPI/Swagger**: Auto-generated API documentation at `/swagger/index.html`
1011
- **Asynchronous Execution**: Non-blocking test execution with status tracking
12+
- **Cancellation Support**: Cancel running test executions via REST or WebSocket
1113
- **Custom Subdomains**: Support for per-request Voiceflow subdomain overrides
1214
- **Multi-Environment Testing**: Concurrent testing against different environments
1315
- **Thread Safety**: No race conditions when testing multiple subdomains simultaneously

docs/docs/server/public-instance.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ If you want to try out the Voiceflow CLI server without setting it up locally, y
66

77
**Base URL:** https://voiceflow-cli-api.xavidop.me/
88

9+
**WebSocket URL:** wss://voiceflow-cli-api.xavidop.me/ws
10+
911
## Interactive API Documentation
1012

1113
You can explore and test the API endpoints directly in your browser using our Swagger playground:
@@ -23,4 +25,4 @@ The Swagger interface allows you to:
2325
1. Visit the [Swagger playground](https://docs.voiceflow.com/reference/post_api-v1-tests-execute#/)
2426
2. Explore the available endpoints
2527
3. Try out the API calls with your own data
26-
4. Use the examples from the [usage examples](usage-examples.md) by replacing `http://localhost:8080` with `https://voiceflow-cli-api.xavidop.me`
28+
4. Use the examples from the [usage examples](usage-examples.md) by replacing `http://localhost:8080` with `https://voiceflow-cli-api.xavidop.me` (and `ws://localhost:8080` with `wss://voiceflow-cli-api.xavidop.me` for WebSocket)

docs/docs/server/security-troubleshooting.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
- **CORS**: Cross-Origin Resource Sharing (CORS) is enabled by default. You can disable it using `--cors=false` if not needed.
1212

13+
- **WebSocket**: The WebSocket endpoint (`/ws`) accepts connections from all origins by default. In production, consider restricting allowed origins via a reverse proxy.
14+
1315
## Troubleshooting
1416

1517
### Server Won't Start
@@ -43,6 +45,31 @@
4345
2. Verify the server is running and accessible
4446
3. Check the server logs for any startup errors
4547

48+
### REST Execute Returns Immediately
49+
50+
**Problem**: `POST /api/v1/tests/execute` returns right away with status `"running"` instead of waiting for the tests to finish.
51+
52+
**Explanation**: This is expected behavior. The REST endpoint is **asynchronous** — it starts the test execution in the background and returns an execution ID immediately.
53+
54+
**Solutions**:
55+
56+
1. Poll the status endpoint to track progress:
57+
```bash
58+
curl http://localhost:8080/api/v1/tests/status/YOUR_EXECUTION_ID
59+
```
60+
61+
2. If you want real-time streaming of logs as they happen, use the **WebSocket endpoint** (`/ws`) instead — see the [usage examples](usage-examples.md#websocket-api) for details.
62+
63+
### WebSocket Connection Closes Immediately
64+
65+
**Problem**: WebSocket connection closes right after connecting.
66+
67+
**Solutions**:
68+
69+
1. Make sure you're using a proper WebSocket client (e.g., `wscat`, `websocat`), not `curl` — HTTP tools like `curl` do not support WebSocket
70+
2. Check server logs for upgrade errors
71+
3. Verify the URL uses the `ws://` scheme: `ws://localhost:8080/ws`
72+
4673
### Logs Not Appearing
4774

4875
**Problem**: Test execution logs are not visible or incomplete.

0 commit comments

Comments
 (0)