utcp-websocket 1.0.0__tar.gz
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.
- utcp_websocket-1.0.0/PKG-INFO +435 -0
- utcp_websocket-1.0.0/README.md +408 -0
- utcp_websocket-1.0.0/pyproject.toml +44 -0
- utcp_websocket-1.0.0/setup.cfg +4 -0
- utcp_websocket-1.0.0/src/utcp_websocket/__init__.py +23 -0
- utcp_websocket-1.0.0/src/utcp_websocket/websocket_call_template.py +165 -0
- utcp_websocket-1.0.0/src/utcp_websocket/websocket_communication_protocol.py +447 -0
- utcp_websocket-1.0.0/src/utcp_websocket.egg-info/PKG-INFO +435 -0
- utcp_websocket-1.0.0/src/utcp_websocket.egg-info/SOURCES.txt +12 -0
- utcp_websocket-1.0.0/src/utcp_websocket.egg-info/dependency_links.txt +1 -0
- utcp_websocket-1.0.0/src/utcp_websocket.egg-info/entry_points.txt +2 -0
- utcp_websocket-1.0.0/src/utcp_websocket.egg-info/requires.txt +12 -0
- utcp_websocket-1.0.0/src/utcp_websocket.egg-info/top_level.txt +1 -0
- utcp_websocket-1.0.0/tests/test_websocket_call_template.py +135 -0
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: utcp-websocket
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: UTCP communication protocol plugin for WebSocket real-time bidirectional communication.
|
|
5
|
+
Author: UTCP Contributors
|
|
6
|
+
License-Expression: MPL-2.0
|
|
7
|
+
Project-URL: Homepage, https://utcp.io
|
|
8
|
+
Project-URL: Source, https://github.com/universal-tool-calling-protocol/python-utcp
|
|
9
|
+
Project-URL: Issues, https://github.com/universal-tool-calling-protocol/python-utcp/issues
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: pydantic>=2.0
|
|
17
|
+
Requires-Dist: aiohttp>=3.8
|
|
18
|
+
Requires-Dist: utcp>=1.0
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: build; extra == "dev"
|
|
21
|
+
Requires-Dist: pytest; extra == "dev"
|
|
22
|
+
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest-aiohttp; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
25
|
+
Requires-Dist: coverage; extra == "dev"
|
|
26
|
+
Requires-Dist: twine; extra == "dev"
|
|
27
|
+
|
|
28
|
+
# UTCP WebSocket Plugin
|
|
29
|
+
|
|
30
|
+
WebSocket communication protocol plugin for UTCP, enabling real-time bidirectional communication with **maximum flexibility** to support ANY WebSocket endpoint format.
|
|
31
|
+
|
|
32
|
+
## Key Feature: Maximum Flexibility
|
|
33
|
+
|
|
34
|
+
**The WebSocket plugin is designed to work with ANY existing WebSocket endpoint without modification.**
|
|
35
|
+
|
|
36
|
+
Unlike other implementations that enforce specific message structures, this plugin:
|
|
37
|
+
- ✅ **No enforced request format**: Use `message` templates with `UTCP_ARG_arg_name_UTCP_ARG` placeholders
|
|
38
|
+
- ✅ **No enforced response format**: Returns raw responses by default
|
|
39
|
+
- ✅ **Works with existing endpoints**: No need to modify your WebSocket servers
|
|
40
|
+
- ✅ **Flexible templating**: Support dict or string message templates
|
|
41
|
+
|
|
42
|
+
This addresses the UTCP principle: "Talk to as many WebSocket endpoints as possible."
|
|
43
|
+
|
|
44
|
+
## Features
|
|
45
|
+
|
|
46
|
+
- ✅ **Maximum Flexibility**: Works with ANY WebSocket endpoint without modification
|
|
47
|
+
- ✅ **Flexible Message Templates**: Dict or string templates with `UTCP_ARG_arg_name_UTCP_ARG` placeholders
|
|
48
|
+
- ✅ **No Enforced Structure**: Send/receive messages in any format
|
|
49
|
+
- ✅ **Real-time Communication**: Bidirectional WebSocket connections
|
|
50
|
+
- ✅ **Multiple Authentication**: API Key, Basic Auth, and OAuth2 support
|
|
51
|
+
- ✅ **Connection Management**: Keep-alive, reconnection, and connection pooling
|
|
52
|
+
- ✅ **Streaming Support**: Both single-response and streaming execution
|
|
53
|
+
- ✅ **Security Enforced**: WSS required (or ws://localhost for development)
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install utcp-websocket
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
For development:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install -e plugins/communication_protocols/websocket
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Quick Start
|
|
68
|
+
|
|
69
|
+
### Basic Usage (No Template - Maximum Flexibility)
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from utcp.utcp_client import UtcpClient
|
|
73
|
+
|
|
74
|
+
# Works with ANY WebSocket endpoint - just sends arguments as JSON
|
|
75
|
+
client = await UtcpClient.create(config={
|
|
76
|
+
"manual_call_templates": [{
|
|
77
|
+
"name": "my_websocket",
|
|
78
|
+
"call_template_type": "websocket",
|
|
79
|
+
"url": "wss://api.example.com/ws"
|
|
80
|
+
}]
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
# Sends: {"user_id": "123", "action": "getData"}
|
|
84
|
+
result = await client.call_tool("my_websocket.get_data", {
|
|
85
|
+
"user_id": "123",
|
|
86
|
+
"action": "getData"
|
|
87
|
+
})
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### With Message Template (Dict)
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
{
|
|
94
|
+
"name": "formatted_ws",
|
|
95
|
+
"call_template_type": "websocket",
|
|
96
|
+
"url": "wss://api.example.com/ws",
|
|
97
|
+
"message": {
|
|
98
|
+
"type": "request",
|
|
99
|
+
"action": "UTCP_ARG_action_UTCP_ARG",
|
|
100
|
+
"params": {
|
|
101
|
+
"user_id": "UTCP_ARG_user_id_UTCP_ARG",
|
|
102
|
+
"query": "UTCP_ARG_query_UTCP_ARG"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Calling with `{"action": "search", "user_id": "123", "query": "test"}` sends:
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"type": "request",
|
|
112
|
+
"action": "search",
|
|
113
|
+
"params": {
|
|
114
|
+
"user_id": "123",
|
|
115
|
+
"query": "test"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### With Message Template (String)
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
{
|
|
124
|
+
"name": "text_ws",
|
|
125
|
+
"call_template_type": "websocket",
|
|
126
|
+
"url": "wss://iot.example.com/ws",
|
|
127
|
+
"message": "CMD:UTCP_ARG_command_UTCP_ARG;DEVICE:UTCP_ARG_device_id_UTCP_ARG;VALUE:UTCP_ARG_value_UTCP_ARG"
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Calling with `{"command": "SET_TEMP", "device_id": "dev123", "value": "25"}` sends:
|
|
132
|
+
```
|
|
133
|
+
CMD:SET_TEMP;DEVICE:dev123;VALUE:25
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Configuration Options
|
|
137
|
+
|
|
138
|
+
### WebSocketCallTemplate Fields
|
|
139
|
+
|
|
140
|
+
| Field | Type | Required | Default | Description |
|
|
141
|
+
|-------|------|----------|---------|-------------|
|
|
142
|
+
| `call_template_type` | string | Yes | `"websocket"` | Must be "websocket" |
|
|
143
|
+
| `url` | string | Yes | - | WebSocket URL (wss:// or ws://localhost) |
|
|
144
|
+
| `message` | string\|dict | No | `null` | Message template with UTCP_ARG_arg_name_UTCP_ARG placeholders |
|
|
145
|
+
| `response_format` | string | No | `null` | Expected response format ("json", "text", "raw") |
|
|
146
|
+
| `protocol` | string | No | `null` | WebSocket subprotocol |
|
|
147
|
+
| `keep_alive` | boolean | No | `true` | Enable persistent connection with heartbeat |
|
|
148
|
+
| `timeout` | integer | No | `30` | Timeout in seconds |
|
|
149
|
+
| `headers` | object | No | `null` | Static headers for handshake |
|
|
150
|
+
| `header_fields` | array | No | `null` | Tool arguments to map to headers |
|
|
151
|
+
| `auth` | object | No | `null` | Authentication configuration |
|
|
152
|
+
|
|
153
|
+
## Message Templating
|
|
154
|
+
|
|
155
|
+
### No Template (Default - Maximum Flexibility)
|
|
156
|
+
|
|
157
|
+
If `message` is not specified, arguments are sent as-is in JSON format:
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
# Config
|
|
161
|
+
{"call_template_type": "websocket", "url": "wss://api.example.com/ws"}
|
|
162
|
+
|
|
163
|
+
# Call
|
|
164
|
+
await client.call_tool("ws.tool", {"foo": "bar", "baz": 123})
|
|
165
|
+
|
|
166
|
+
# Sends exactly:
|
|
167
|
+
{"foo": "bar", "baz": 123}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
This works with **any** WebSocket endpoint that accepts JSON.
|
|
171
|
+
|
|
172
|
+
### Dict Template
|
|
173
|
+
|
|
174
|
+
Use dict templates for structured messages:
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
{
|
|
178
|
+
"message": {
|
|
179
|
+
"jsonrpc": "2.0",
|
|
180
|
+
"method": "UTCP_ARG_method_UTCP_ARG",
|
|
181
|
+
"params": "UTCP_ARG_params_UTCP_ARG",
|
|
182
|
+
"id": 1
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### String Template
|
|
188
|
+
|
|
189
|
+
Use string templates for text-based protocols:
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
{
|
|
193
|
+
"message": "GET UTCP_ARG_resource_UTCP_ARG HTTP/1.1\r\nHost: UTCP_ARG_host_UTCP_ARG\r\n\r\n"
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Nested Templates
|
|
198
|
+
|
|
199
|
+
Templates work recursively in dicts and lists:
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
{
|
|
203
|
+
"message": {
|
|
204
|
+
"type": "command",
|
|
205
|
+
"data": {
|
|
206
|
+
"commands": ["UTCP_ARG_cmd1_UTCP_ARG", "UTCP_ARG_cmd2_UTCP_ARG"],
|
|
207
|
+
"metadata": {
|
|
208
|
+
"user": "UTCP_ARG_user_UTCP_ARG",
|
|
209
|
+
"timestamp": "2025-01-01"
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Response Handling
|
|
217
|
+
|
|
218
|
+
### No Format Specification (Default)
|
|
219
|
+
|
|
220
|
+
By default, responses are returned as-is (maximum flexibility):
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
# Returns whatever the WebSocket sends - could be JSON string, text, or binary
|
|
224
|
+
result = await client.call_tool("ws.tool", {...})
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### JSON Format
|
|
228
|
+
|
|
229
|
+
Parse responses as JSON:
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
{
|
|
233
|
+
"call_template_type": "websocket",
|
|
234
|
+
"url": "wss://api.example.com/ws",
|
|
235
|
+
"response_format": "json"
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Text Format
|
|
240
|
+
|
|
241
|
+
Return responses as text strings:
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
{
|
|
245
|
+
"response_format": "text"
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Raw Format
|
|
250
|
+
|
|
251
|
+
Return responses without any processing:
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
{
|
|
255
|
+
"response_format": "raw"
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Real-World Examples
|
|
260
|
+
|
|
261
|
+
### Example 1: Stock Price WebSocket (No Template)
|
|
262
|
+
|
|
263
|
+
Works with existing stock APIs without modification:
|
|
264
|
+
|
|
265
|
+
```python
|
|
266
|
+
{
|
|
267
|
+
"name": "stocks",
|
|
268
|
+
"call_template_type": "websocket",
|
|
269
|
+
"url": "wss://stream.example.com/stocks",
|
|
270
|
+
"auth": {
|
|
271
|
+
"auth_type": "api_key",
|
|
272
|
+
"api_key": "${STOCK_API_KEY}",
|
|
273
|
+
"var_name": "Authorization",
|
|
274
|
+
"location": "header"
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
# Sends: {"symbol": "AAPL", "action": "subscribe"}
|
|
279
|
+
await client.call_tool("stocks.subscribe", {
|
|
280
|
+
"symbol": "AAPL",
|
|
281
|
+
"action": "subscribe"
|
|
282
|
+
})
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Example 2: IoT Device Control (String Template)
|
|
286
|
+
|
|
287
|
+
```python
|
|
288
|
+
{
|
|
289
|
+
"name": "iot",
|
|
290
|
+
"call_template_type": "websocket",
|
|
291
|
+
"url": "wss://iot.example.com/devices",
|
|
292
|
+
"message": "DEVICE:UTCP_ARG_device_id_UTCP_ARG CMD:UTCP_ARG_command_UTCP_ARG VAL:UTCP_ARG_value_UTCP_ARG"
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
# Sends: "DEVICE:light_01 CMD:SET_BRIGHTNESS VAL:75"
|
|
296
|
+
await client.call_tool("iot.control", {
|
|
297
|
+
"device_id": "light_01",
|
|
298
|
+
"command": "SET_BRIGHTNESS",
|
|
299
|
+
"value": "75"
|
|
300
|
+
})
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Example 3: JSON-RPC WebSocket (Dict Template)
|
|
304
|
+
|
|
305
|
+
```python
|
|
306
|
+
{
|
|
307
|
+
"name": "jsonrpc",
|
|
308
|
+
"call_template_type": "websocket",
|
|
309
|
+
"url": "wss://rpc.example.com/ws",
|
|
310
|
+
"message": {
|
|
311
|
+
"jsonrpc": "2.0",
|
|
312
|
+
"method": "UTCP_ARG_method_UTCP_ARG",
|
|
313
|
+
"params": "UTCP_ARG_params_UTCP_ARG",
|
|
314
|
+
"id": 1
|
|
315
|
+
},
|
|
316
|
+
"response_format": "json"
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
# Sends: {"jsonrpc": "2.0", "method": "getUser", "params": "{\"id\": 123}", "id": 1}
|
|
320
|
+
# Note: params is stringified since it's a non-string value in the template
|
|
321
|
+
result = await client.call_tool("jsonrpc.call", {
|
|
322
|
+
"method": "getUser",
|
|
323
|
+
"params": {"id": 123}
|
|
324
|
+
})
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Example 4: Chat Application (Dict Template)
|
|
328
|
+
|
|
329
|
+
```python
|
|
330
|
+
{
|
|
331
|
+
"name": "chat",
|
|
332
|
+
"call_template_type": "websocket",
|
|
333
|
+
"url": "wss://chat.example.com/ws",
|
|
334
|
+
"message": {
|
|
335
|
+
"type": "message",
|
|
336
|
+
"channel": "UTCP_ARG_channel_UTCP_ARG",
|
|
337
|
+
"user": "UTCP_ARG_user_UTCP_ARG",
|
|
338
|
+
"text": "UTCP_ARG_text_UTCP_ARG",
|
|
339
|
+
"timestamp": "{{now}}"
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## Authentication
|
|
345
|
+
|
|
346
|
+
### API Key Authentication
|
|
347
|
+
|
|
348
|
+
```python
|
|
349
|
+
{
|
|
350
|
+
"auth": {
|
|
351
|
+
"auth_type": "api_key",
|
|
352
|
+
"api_key": "${API_KEY}",
|
|
353
|
+
"var_name": "Authorization",
|
|
354
|
+
"location": "header"
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Basic Authentication
|
|
360
|
+
|
|
361
|
+
```python
|
|
362
|
+
{
|
|
363
|
+
"auth": {
|
|
364
|
+
"auth_type": "basic",
|
|
365
|
+
"username": "${USERNAME}",
|
|
366
|
+
"password": "${PASSWORD}"
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### OAuth2 Authentication
|
|
372
|
+
|
|
373
|
+
```python
|
|
374
|
+
{
|
|
375
|
+
"auth": {
|
|
376
|
+
"auth_type": "oauth2",
|
|
377
|
+
"client_id": "${CLIENT_ID}",
|
|
378
|
+
"client_secret": "${CLIENT_SECRET}",
|
|
379
|
+
"token_url": "https://auth.example.com/token",
|
|
380
|
+
"scope": "read write"
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
## Streaming Responses
|
|
386
|
+
|
|
387
|
+
```python
|
|
388
|
+
async for chunk in client.call_tool_streaming("ws.stream", {"query": "data"}):
|
|
389
|
+
print(chunk)
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
## Security
|
|
393
|
+
|
|
394
|
+
- **WSS Required**: Production URLs must use `wss://` for encrypted communication
|
|
395
|
+
- **Localhost Exception**: `ws://localhost` and `ws://127.0.0.1` allowed for development
|
|
396
|
+
- **Authentication**: Full support for API Key, Basic Auth, and OAuth2
|
|
397
|
+
- **Token Caching**: OAuth2 tokens are cached for reuse; refresh must be handled by the service or manual re-auth.
|
|
398
|
+
|
|
399
|
+
## Best Practices
|
|
400
|
+
|
|
401
|
+
1. **Start Simple**: Don't use `message` template unless your endpoint requires specific format
|
|
402
|
+
2. **Use WSS in Production**: Always use `wss://` for secure connections
|
|
403
|
+
3. **Set Appropriate Timeouts**: Configure timeouts based on expected response times
|
|
404
|
+
4. **Test Without Template First**: Try without `message` template to see if it works
|
|
405
|
+
5. **Add Template Only When Needed**: Only add `message` template if endpoint requires specific structure
|
|
406
|
+
|
|
407
|
+
## Comparison with Enforced Formats
|
|
408
|
+
|
|
409
|
+
| Approach | Flexibility | Works with Existing Endpoints |
|
|
410
|
+
|----------|-------------|------------------------------|
|
|
411
|
+
| **UTCP WebSocket (This Plugin)** | ✅ Maximum | ✅ Yes - works with any endpoint |
|
|
412
|
+
| Enforced request/response structure | ❌ Limited | ❌ No - requires endpoint modification |
|
|
413
|
+
| UTCP-specific message format | ❌ Limited | ❌ No - only works with UTCP servers |
|
|
414
|
+
|
|
415
|
+
## Testing
|
|
416
|
+
|
|
417
|
+
Run tests:
|
|
418
|
+
|
|
419
|
+
```bash
|
|
420
|
+
pytest plugins/communication_protocols/websocket/tests/ -v
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
With coverage:
|
|
424
|
+
|
|
425
|
+
```bash
|
|
426
|
+
pytest plugins/communication_protocols/websocket/tests/ --cov=utcp_websocket --cov-report=term-missing
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
## Contributing
|
|
430
|
+
|
|
431
|
+
Contributions are welcome! Please see the [main repository](https://github.com/universal-tool-calling-protocol/python-utcp) for contribution guidelines.
|
|
432
|
+
|
|
433
|
+
## License
|
|
434
|
+
|
|
435
|
+
Mozilla Public License 2.0 (MPL-2.0)
|