cloudbrain-client 1.0.0__py3-none-any.whl
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.
- cloudbrain_client/__init__.py +46 -0
- cloudbrain_client/ai_conversation_helper.py +502 -0
- cloudbrain_client/ai_websocket_client.py +427 -0
- cloudbrain_client/cloudbrain_client.py +598 -0
- cloudbrain_client/cloudbrain_quick.py +120 -0
- cloudbrain_client/message_poller.py +211 -0
- cloudbrain_client-1.0.0.dist-info/METADATA +207 -0
- cloudbrain_client-1.0.0.dist-info/RECORD +11 -0
- cloudbrain_client-1.0.0.dist-info/WHEEL +5 -0
- cloudbrain_client-1.0.0.dist-info/entry_points.txt +3 -0
- cloudbrain_client-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
AI WebSocket Client - Robust version with error handling
|
|
4
|
+
Usage: python ai_websocket_client_robust.py [server_type] [ai_id]
|
|
5
|
+
Example: python ai_websocket_client_robust.py 2 3
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import websockets
|
|
10
|
+
import json
|
|
11
|
+
import sys
|
|
12
|
+
import os
|
|
13
|
+
import select
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
from typing import Optional, Callable
|
|
16
|
+
|
|
17
|
+
class AIWebSocketClient:
|
|
18
|
+
"""Generic WebSocket client for AI communication"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, ai_id: int, server_url: str = 'ws://127.0.0.1:8766'):
|
|
21
|
+
self.ai_id = ai_id
|
|
22
|
+
self.server_url = server_url
|
|
23
|
+
self.ws = None
|
|
24
|
+
self.connected = False
|
|
25
|
+
self.message_handlers = []
|
|
26
|
+
self.ai_name = None
|
|
27
|
+
self.ai_expertise = None
|
|
28
|
+
self.ai_version = None
|
|
29
|
+
|
|
30
|
+
async def connect(self, start_message_loop=True):
|
|
31
|
+
"""Connect to WebSocket server"""
|
|
32
|
+
try:
|
|
33
|
+
print(f"š Connecting to {self.server_url}...")
|
|
34
|
+
self.ws = await websockets.connect(self.server_url)
|
|
35
|
+
|
|
36
|
+
# Authenticate - libsql simulator expects just ai_id
|
|
37
|
+
auth_msg = {
|
|
38
|
+
'ai_id': self.ai_id
|
|
39
|
+
}
|
|
40
|
+
await self.ws.send(json.dumps(auth_msg))
|
|
41
|
+
|
|
42
|
+
# Wait for welcome message
|
|
43
|
+
welcome_msg = await self.ws.recv()
|
|
44
|
+
welcome_data = json.loads(welcome_msg)
|
|
45
|
+
|
|
46
|
+
if welcome_data.get('type') == 'connected':
|
|
47
|
+
self.ai_name = welcome_data.get('ai_name')
|
|
48
|
+
self.ai_nickname = welcome_data.get('ai_nickname')
|
|
49
|
+
self.ai_expertise = welcome_data.get('ai_expertise')
|
|
50
|
+
self.ai_version = welcome_data.get('ai_version')
|
|
51
|
+
self.connected = True
|
|
52
|
+
|
|
53
|
+
display_name = f"{self.ai_name}"
|
|
54
|
+
if self.ai_nickname:
|
|
55
|
+
display_name = f"{self.ai_name} ({self.ai_nickname})"
|
|
56
|
+
print(f"ā
Connected as {display_name} (AI {self.ai_id})")
|
|
57
|
+
print(f"šÆ Expertise: {self.ai_expertise}")
|
|
58
|
+
print(f"š¦ Version: {self.ai_version}")
|
|
59
|
+
|
|
60
|
+
# Start message loop only if requested
|
|
61
|
+
if start_message_loop:
|
|
62
|
+
await self.message_loop()
|
|
63
|
+
else:
|
|
64
|
+
error = welcome_data.get('error', 'Unknown error')
|
|
65
|
+
print(f"ā Connection failed: {error}")
|
|
66
|
+
|
|
67
|
+
except Exception as e:
|
|
68
|
+
print(f"ā Connection error: {e}")
|
|
69
|
+
print(f"ā Error type: {type(e).__name__}")
|
|
70
|
+
|
|
71
|
+
async def message_loop(self):
|
|
72
|
+
"""Handle incoming messages"""
|
|
73
|
+
try:
|
|
74
|
+
async for message in self.ws:
|
|
75
|
+
try:
|
|
76
|
+
data = json.loads(message)
|
|
77
|
+
await self.handle_message(data)
|
|
78
|
+
except json.JSONDecodeError:
|
|
79
|
+
print(f"ā Invalid JSON: {message[:100]}")
|
|
80
|
+
except Exception as e:
|
|
81
|
+
print(f"ā Error handling message: {e}")
|
|
82
|
+
|
|
83
|
+
except websockets.exceptions.ConnectionClosed:
|
|
84
|
+
print("š Connection closed")
|
|
85
|
+
self.connected = False
|
|
86
|
+
except Exception as e:
|
|
87
|
+
print(f"ā Error in message loop: {e}")
|
|
88
|
+
self.connected = False
|
|
89
|
+
|
|
90
|
+
async def handle_message(self, data: dict):
|
|
91
|
+
"""Handle incoming message"""
|
|
92
|
+
message_type = data.get('type')
|
|
93
|
+
|
|
94
|
+
if message_type == 'new_message':
|
|
95
|
+
await self.handle_new_message(data)
|
|
96
|
+
elif message_type == 'message':
|
|
97
|
+
await self.handle_new_message(data)
|
|
98
|
+
elif message_type == 'online_users':
|
|
99
|
+
await self.handle_online_users(data)
|
|
100
|
+
elif message_type == 'system_message':
|
|
101
|
+
await self.handle_system_message(data)
|
|
102
|
+
elif message_type == 'insert':
|
|
103
|
+
await self.handle_insert_notification(data)
|
|
104
|
+
elif message_type == 'query_result':
|
|
105
|
+
await self.handle_query_result(data)
|
|
106
|
+
elif message_type == 'subscribed':
|
|
107
|
+
print(f"ā
Subscribed to {data.get('table')}")
|
|
108
|
+
elif message_type == 'error':
|
|
109
|
+
print(f"ā Server error: {data.get('message')}")
|
|
110
|
+
else:
|
|
111
|
+
print(f"ā ļø Unknown message type: {message_type}")
|
|
112
|
+
|
|
113
|
+
# Call registered handlers
|
|
114
|
+
for handler in self.message_handlers:
|
|
115
|
+
try:
|
|
116
|
+
await handler(data)
|
|
117
|
+
except Exception as e:
|
|
118
|
+
print(f"ā Handler error: {e}")
|
|
119
|
+
|
|
120
|
+
async def handle_new_message(self, data: dict):
|
|
121
|
+
"""Handle new message from another AI"""
|
|
122
|
+
sender_name = data.get('sender_name', 'Unknown')
|
|
123
|
+
content = data.get('content', '')
|
|
124
|
+
message_type = data.get('message_type', 'message')
|
|
125
|
+
|
|
126
|
+
print(f"\nšØ New message from {sender_name}:")
|
|
127
|
+
print(f" Type: {message_type}")
|
|
128
|
+
print(f" Content: {content}")
|
|
129
|
+
print()
|
|
130
|
+
|
|
131
|
+
async def handle_online_users(self, data: dict):
|
|
132
|
+
"""Handle online users list"""
|
|
133
|
+
users = data.get('users', [])
|
|
134
|
+
print(f"\nš„ Online users ({len(users)}):")
|
|
135
|
+
for user in users:
|
|
136
|
+
print(f" - {user.get('name')} (AI {user.get('id')})")
|
|
137
|
+
print()
|
|
138
|
+
|
|
139
|
+
async def handle_system_message(self, data: dict):
|
|
140
|
+
"""Handle system message"""
|
|
141
|
+
message = data.get('message', '')
|
|
142
|
+
print(f"\nš¢ System: {message}\n")
|
|
143
|
+
|
|
144
|
+
async def handle_insert_notification(self, data: dict):
|
|
145
|
+
"""Handle database insert notification"""
|
|
146
|
+
table = data.get('table', '')
|
|
147
|
+
row_id = data.get('row_id', '')
|
|
148
|
+
print(f"\nš Database update: {table} (ID: {row_id})\n")
|
|
149
|
+
|
|
150
|
+
async def handle_query_result(self, data: dict):
|
|
151
|
+
"""Handle SQL query result"""
|
|
152
|
+
results = data.get('results', [])
|
|
153
|
+
rows_affected = data.get('rows_affected', 0)
|
|
154
|
+
|
|
155
|
+
print(f"\nš Query results ({rows_affected} rows):")
|
|
156
|
+
for row in results:
|
|
157
|
+
print(f" {row}")
|
|
158
|
+
print()
|
|
159
|
+
|
|
160
|
+
async def send_message(self, message_type: str = 'message', content: str = '',
|
|
161
|
+
metadata: dict = None, conversation_id: int = 1):
|
|
162
|
+
"""Send message to server"""
|
|
163
|
+
if not self.connected:
|
|
164
|
+
print("ā Not connected to server")
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
message = {
|
|
168
|
+
'type': 'send_message',
|
|
169
|
+
'conversation_id': conversation_id,
|
|
170
|
+
'message_type': message_type,
|
|
171
|
+
'content': content,
|
|
172
|
+
'metadata': metadata or {}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
await self.ws.send(json.dumps(message))
|
|
176
|
+
print(f"ā
Message sent: {message_type}")
|
|
177
|
+
|
|
178
|
+
async def get_online_users(self):
|
|
179
|
+
"""Request list of online users"""
|
|
180
|
+
if not self.connected:
|
|
181
|
+
print("ā Not connected to server")
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
message = {
|
|
185
|
+
'type': 'get_online_users'
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
await self.ws.send(json.dumps(message))
|
|
189
|
+
|
|
190
|
+
async def subscribe(self, table: str, events: list = ['INSERT']):
|
|
191
|
+
"""Subscribe to table changes (libsql style)"""
|
|
192
|
+
if not self.connected:
|
|
193
|
+
print("ā Not connected to server")
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
message = {
|
|
197
|
+
'type': 'subscribe',
|
|
198
|
+
'table': table,
|
|
199
|
+
'events': events
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
await self.ws.send(json.dumps(message))
|
|
203
|
+
|
|
204
|
+
async def execute_sql(self, sql: str, params: list = None):
|
|
205
|
+
"""Execute SQL (libsql style)"""
|
|
206
|
+
if not self.connected:
|
|
207
|
+
print("ā Not connected to server")
|
|
208
|
+
return
|
|
209
|
+
|
|
210
|
+
message = {
|
|
211
|
+
'type': 'execute',
|
|
212
|
+
'sql': sql,
|
|
213
|
+
'params': params or []
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
await self.ws.send(json.dumps(message))
|
|
217
|
+
|
|
218
|
+
async def send_heartbeat(self):
|
|
219
|
+
"""Send heartbeat to keep connection alive"""
|
|
220
|
+
if not self.connected:
|
|
221
|
+
return
|
|
222
|
+
|
|
223
|
+
message = {
|
|
224
|
+
'type': 'heartbeat'
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
await self.ws.send(json.dumps(message))
|
|
228
|
+
|
|
229
|
+
async def close(self):
|
|
230
|
+
"""Close connection"""
|
|
231
|
+
if self.ws:
|
|
232
|
+
await self.ws.close()
|
|
233
|
+
self.connected = False
|
|
234
|
+
print("š Connection closed")
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def check_dependencies():
|
|
238
|
+
"""Check if required dependencies are installed"""
|
|
239
|
+
try:
|
|
240
|
+
import websockets
|
|
241
|
+
return True
|
|
242
|
+
except ImportError:
|
|
243
|
+
print("ā Error: websockets is not installed")
|
|
244
|
+
print("š” Install with: pip install websockets")
|
|
245
|
+
return False
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def check_server_running(host='127.0.0.1', port=8766):
|
|
249
|
+
"""Check if server is running"""
|
|
250
|
+
import socket
|
|
251
|
+
try:
|
|
252
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
253
|
+
sock.settimeout(1)
|
|
254
|
+
result = sock.connect_ex((host, port))
|
|
255
|
+
sock.close()
|
|
256
|
+
return result == 0
|
|
257
|
+
except:
|
|
258
|
+
return False
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
async def main():
|
|
262
|
+
"""Main function with error handling"""
|
|
263
|
+
print("=" * 60)
|
|
264
|
+
print("š¤ AI WebSocket Client (Robust)")
|
|
265
|
+
print("=" * 60)
|
|
266
|
+
print()
|
|
267
|
+
|
|
268
|
+
# Show quick start info
|
|
269
|
+
print("š Quick Start:")
|
|
270
|
+
print(" Command line: python ai_websocket_client_robust.py [ai_id]")
|
|
271
|
+
print(" Example: python ai_websocket_client_robust.py 3")
|
|
272
|
+
print()
|
|
273
|
+
|
|
274
|
+
# Show server info
|
|
275
|
+
print("š Server Info:")
|
|
276
|
+
print(" Address: ws://127.0.0.1:8766")
|
|
277
|
+
print(" Type: libsql Simulator")
|
|
278
|
+
print(" Mode: Local (no internet needed)")
|
|
279
|
+
print()
|
|
280
|
+
|
|
281
|
+
# Show available AIs
|
|
282
|
+
print("š Available AIs:")
|
|
283
|
+
try:
|
|
284
|
+
import sqlite3
|
|
285
|
+
conn = sqlite3.connect('ai_db/cloudbrain.db')
|
|
286
|
+
cursor = conn.cursor()
|
|
287
|
+
cursor.execute("SELECT id, name, expertise FROM ai_profiles ORDER BY id")
|
|
288
|
+
ais = cursor.fetchall()
|
|
289
|
+
conn.close()
|
|
290
|
+
|
|
291
|
+
for ai in ais:
|
|
292
|
+
print(f" ID {ai[0]}: {ai[1]}")
|
|
293
|
+
print(f" {ai[2]}")
|
|
294
|
+
print()
|
|
295
|
+
except Exception as e:
|
|
296
|
+
print(" (Could not load AI list)")
|
|
297
|
+
print(" Run: sqlite3 ai_db/cloudbrain.db \"SELECT id, name FROM ai_profiles;\"")
|
|
298
|
+
print()
|
|
299
|
+
|
|
300
|
+
# Show usage
|
|
301
|
+
print("š” Usage:")
|
|
302
|
+
print(" 1. Activate virtual environment: source .venv/bin/activate")
|
|
303
|
+
print(" 2. Run this script: python ai_websocket_client_robust.py [your_ai_id]")
|
|
304
|
+
print(" 3. Replace [your_ai_id] with your actual AI ID from above")
|
|
305
|
+
print()
|
|
306
|
+
|
|
307
|
+
# Check dependencies
|
|
308
|
+
if not check_dependencies():
|
|
309
|
+
sys.exit(1)
|
|
310
|
+
|
|
311
|
+
# Parse command line arguments
|
|
312
|
+
ai_id = None
|
|
313
|
+
keep_alive = True
|
|
314
|
+
|
|
315
|
+
if len(sys.argv) >= 2:
|
|
316
|
+
try:
|
|
317
|
+
ai_id = int(sys.argv[1])
|
|
318
|
+
except ValueError:
|
|
319
|
+
# Check if it's a flag
|
|
320
|
+
if sys.argv[1] in ['--help', '-h']:
|
|
321
|
+
print("Usage: python ai_websocket_client.py [ai_id] [--no-keep-alive]")
|
|
322
|
+
print()
|
|
323
|
+
print("Arguments:")
|
|
324
|
+
print(" ai_id Your AI ID (required)")
|
|
325
|
+
print(" --no-keep-alive Exit after successful connection (optional)")
|
|
326
|
+
print()
|
|
327
|
+
print("Examples:")
|
|
328
|
+
print(" python ai_websocket_client.py 3")
|
|
329
|
+
print(" python ai_websocket_client.py 3 --no-keep-alive")
|
|
330
|
+
sys.exit(0)
|
|
331
|
+
else:
|
|
332
|
+
print(f"ā Error: Invalid argument '{sys.argv[1]}'")
|
|
333
|
+
print("š” Use --help for usage information")
|
|
334
|
+
sys.exit(1)
|
|
335
|
+
|
|
336
|
+
# Check for --no-keep-alive flag
|
|
337
|
+
if '--no-keep-alive' in sys.argv:
|
|
338
|
+
keep_alive = False
|
|
339
|
+
|
|
340
|
+
# Server URL (fixed to libsql simulator)
|
|
341
|
+
server_url = 'ws://127.0.0.1:8766'
|
|
342
|
+
print("š Connecting to libsql Simulator...")
|
|
343
|
+
|
|
344
|
+
# Check if server is running
|
|
345
|
+
if not check_server_running(port=8766):
|
|
346
|
+
print(f"ā Error: Server is not running on port 8766")
|
|
347
|
+
print("š” Start the server first:")
|
|
348
|
+
print(" source .venv/bin/activate")
|
|
349
|
+
print(" python libsql_local_simulator.py")
|
|
350
|
+
sys.exit(1)
|
|
351
|
+
|
|
352
|
+
# Get AI ID
|
|
353
|
+
if not ai_id:
|
|
354
|
+
print("\nš Available AIs:")
|
|
355
|
+
print("Run: sqlite3 ai_db/cloudbrain.db \"SELECT id, name FROM ai_profiles;\"")
|
|
356
|
+
print()
|
|
357
|
+
try:
|
|
358
|
+
ai_id_input = input("Enter your AI ID: ").strip()
|
|
359
|
+
if not ai_id_input:
|
|
360
|
+
print("ā Error: AI ID is required")
|
|
361
|
+
sys.exit(1)
|
|
362
|
+
ai_id = int(ai_id_input)
|
|
363
|
+
except EOFError:
|
|
364
|
+
print("ā Error: Cannot read input in non-interactive mode")
|
|
365
|
+
print("š” Use command line arguments: python ai_websocket_client_robust.py 3")
|
|
366
|
+
sys.exit(1)
|
|
367
|
+
except ValueError:
|
|
368
|
+
print("ā Error: AI ID must be a number")
|
|
369
|
+
sys.exit(1)
|
|
370
|
+
|
|
371
|
+
# Create client
|
|
372
|
+
client = AIWebSocketClient(ai_id=ai_id, server_url=server_url)
|
|
373
|
+
|
|
374
|
+
# Connect (don't start message loop if --no-keep-alive)
|
|
375
|
+
await client.connect(start_message_loop=keep_alive)
|
|
376
|
+
|
|
377
|
+
if not client.connected:
|
|
378
|
+
print("ā Failed to connect")
|
|
379
|
+
sys.exit(1)
|
|
380
|
+
|
|
381
|
+
# Only send message and show success if not keeping alive
|
|
382
|
+
if not keep_alive:
|
|
383
|
+
# Example: Send a message
|
|
384
|
+
await client.send_message(
|
|
385
|
+
message_type='message',
|
|
386
|
+
content='Hello! I am connected via WebSocket!',
|
|
387
|
+
metadata={'connection_type': 'websocket'}
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
print("\nā
Connection successful!")
|
|
391
|
+
print("š” Exiting (--no-keep-alive flag used)")
|
|
392
|
+
await client.close()
|
|
393
|
+
else:
|
|
394
|
+
# Example: Get online users
|
|
395
|
+
await client.get_online_users()
|
|
396
|
+
await asyncio.sleep(1)
|
|
397
|
+
|
|
398
|
+
# Example: Send a message
|
|
399
|
+
await client.send_message(
|
|
400
|
+
message_type='message',
|
|
401
|
+
content='Hello! I am connected via WebSocket!',
|
|
402
|
+
metadata={'connection_type': 'websocket'}
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
print("\nā
Connection successful!")
|
|
406
|
+
print("š” Press Ctrl+C to disconnect")
|
|
407
|
+
print()
|
|
408
|
+
|
|
409
|
+
try:
|
|
410
|
+
while client.connected:
|
|
411
|
+
await asyncio.sleep(1)
|
|
412
|
+
except KeyboardInterrupt:
|
|
413
|
+
print("\nš Disconnecting...")
|
|
414
|
+
await client.close()
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
if __name__ == "__main__":
|
|
418
|
+
try:
|
|
419
|
+
asyncio.run(main())
|
|
420
|
+
except KeyboardInterrupt:
|
|
421
|
+
print("\nš Client stopped")
|
|
422
|
+
except Exception as e:
|
|
423
|
+
print(f"ā Fatal error: {e}")
|
|
424
|
+
print(f"ā Error type: {type(e).__name__}")
|
|
425
|
+
import traceback
|
|
426
|
+
traceback.print_exc()
|
|
427
|
+
sys.exit(1)
|