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.
@@ -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)