unaiverse 0.1.6__cp313-cp313-macosx_10_13_x86_64.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.

Potentially problematic release.


This version of unaiverse might be problematic. Click here for more details.

Files changed (50) hide show
  1. unaiverse/__init__.py +19 -0
  2. unaiverse/agent.py +2008 -0
  3. unaiverse/agent_basics.py +1846 -0
  4. unaiverse/clock.py +191 -0
  5. unaiverse/dataprops.py +1209 -0
  6. unaiverse/hsm.py +1880 -0
  7. unaiverse/modules/__init__.py +18 -0
  8. unaiverse/modules/cnu/__init__.py +17 -0
  9. unaiverse/modules/cnu/cnus.py +536 -0
  10. unaiverse/modules/cnu/layers.py +261 -0
  11. unaiverse/modules/cnu/psi.py +60 -0
  12. unaiverse/modules/hl/__init__.py +15 -0
  13. unaiverse/modules/hl/hl_utils.py +411 -0
  14. unaiverse/modules/networks.py +1509 -0
  15. unaiverse/modules/utils.py +680 -0
  16. unaiverse/networking/__init__.py +16 -0
  17. unaiverse/networking/node/__init__.py +18 -0
  18. unaiverse/networking/node/connpool.py +1261 -0
  19. unaiverse/networking/node/node.py +2223 -0
  20. unaiverse/networking/node/profile.py +446 -0
  21. unaiverse/networking/node/tokens.py +79 -0
  22. unaiverse/networking/p2p/__init__.py +198 -0
  23. unaiverse/networking/p2p/go.mod +127 -0
  24. unaiverse/networking/p2p/go.sum +548 -0
  25. unaiverse/networking/p2p/golibp2p.py +18 -0
  26. unaiverse/networking/p2p/golibp2p.pyi +135 -0
  27. unaiverse/networking/p2p/lib.go +2714 -0
  28. unaiverse/networking/p2p/lib.go.sha256 +1 -0
  29. unaiverse/networking/p2p/lib_types.py +312 -0
  30. unaiverse/networking/p2p/message_pb2.py +63 -0
  31. unaiverse/networking/p2p/messages.py +265 -0
  32. unaiverse/networking/p2p/mylogger.py +77 -0
  33. unaiverse/networking/p2p/p2p.py +929 -0
  34. unaiverse/networking/p2p/proto-go/message.pb.go +616 -0
  35. unaiverse/networking/p2p/unailib.cpython-313-darwin.so +0 -0
  36. unaiverse/streamlib/__init__.py +15 -0
  37. unaiverse/streamlib/streamlib.py +210 -0
  38. unaiverse/streams.py +770 -0
  39. unaiverse/utils/__init__.py +16 -0
  40. unaiverse/utils/ask_lone_wolf.json +27 -0
  41. unaiverse/utils/lone_wolf.json +19 -0
  42. unaiverse/utils/misc.py +305 -0
  43. unaiverse/utils/sandbox.py +293 -0
  44. unaiverse/utils/server.py +435 -0
  45. unaiverse/world.py +175 -0
  46. unaiverse-0.1.6.dist-info/METADATA +365 -0
  47. unaiverse-0.1.6.dist-info/RECORD +50 -0
  48. unaiverse-0.1.6.dist-info/WHEEL +6 -0
  49. unaiverse-0.1.6.dist-info/licenses/LICENSE +43 -0
  50. unaiverse-0.1.6.dist-info/top_level.txt +1 -0
@@ -0,0 +1 @@
1
+ 9715da07770e39746f5889cc25ef308badd9a48e82cd5b15cb98cad24213da89
@@ -0,0 +1,312 @@
1
+ """
2
+ █████ █████ ██████ █████ █████ █████ █████ ██████████ ███████████ █████████ ██████████
3
+ ░░███ ░░███ ░░██████ ░░███ ░░███ ░░███ ░░███ ░░███░░░░░█░░███░░░░░███ ███░░░░░███░░███░░░░░█
4
+ ░███ ░███ ░███░███ ░███ ██████ ░███ ░███ ░███ ░███ █ ░ ░███ ░███ ░███ ░░░ ░███ █ ░
5
+ ░███ ░███ ░███░░███░███ ░░░░░███ ░███ ░███ ░███ ░██████ ░██████████ ░░█████████ ░██████
6
+ ░███ ░███ ░███ ░░██████ ███████ ░███ ░░███ ███ ░███░░█ ░███░░░░░███ ░░░░░░░░███ ░███░░█
7
+ ░███ ░███ ░███ ░░█████ ███░░███ ░███ ░░░█████░ ░███ ░ █ ░███ ░███ ███ ░███ ░███ ░ █
8
+ ░░████████ █████ ░░█████░░████████ █████ ░░███ ██████████ █████ █████░░█████████ ██████████
9
+ ░░░░░░░░ ░░░░░ ░░░░░ ░░░░░░░░ ░░░░░ ░░░ ░░░░░░░░░░ ░░░░░ ░░░░░ ░░░░░░░░░ ░░░░░░░░░░
10
+ A Collectionless AI Project (https://collectionless.ai)
11
+ Registration/Login: https://unaiverse.io
12
+ Code Repositories: https://github.com/collectionlessai/
13
+ Main Developers: Stefano Melacci (Project Leader), Christian Di Maio, Tommaso Guidi
14
+ """
15
+ import json
16
+ import ctypes
17
+ import logging
18
+ from threading import Lock
19
+ from typing import Optional, List, Any, TYPE_CHECKING
20
+
21
+ from .golibp2p import GoLibP2P # Assuming this class loads the library
22
+
23
+ logger = logging.getLogger('LIB-TYPES')
24
+
25
+
26
+ class TypeInterface:
27
+ """
28
+ Helper class for converting between Python types and Go types using ctypes.
29
+ """
30
+ def __init__(self, libp2p_instance: GoLibP2P):
31
+ self.__freed_pointers: set[int] = set() # Track freed pointers to prevent double-free errors
32
+ self.__freed_pointers_lock: Any = Lock() # A threading lock
33
+ self.libp2p: GoLibP2P = libp2p_instance # Store the shared library object instance
34
+
35
+ def to_go_string(self, s: str) -> bytes:
36
+ """
37
+ Converts a Python string to a UTF-8 encoded Python 'bytes' object.
38
+
39
+ This 'bytes' object is suitable for direct use with ctypes when passing
40
+ to a C function expecting a 'char*' (ctypes.c_char_p), as ctypes
41
+ will automatically pass a pointer to the byte string's data.
42
+
43
+ Args:
44
+ s: The Python string.
45
+
46
+ Returns:
47
+ A Python 'bytes' object containing the UTF-8 encoded string.
48
+ """
49
+ if s is None:
50
+ s = ""
51
+ return s.encode("utf-8")
52
+
53
+ def from_go_string(self, cstr: bytes) -> str:
54
+ """
55
+ Converts a C char pointer (Go string) to a Python string.
56
+
57
+ Args:
58
+ cstr: The C char pointer.
59
+
60
+ Returns:
61
+ The decoded Python string.
62
+ """
63
+ if not cstr:
64
+ return ""
65
+ return cstr.decode("utf-8")
66
+
67
+ def to_go_int(self, i: int) -> ctypes.c_int:
68
+ """
69
+ Converts a Python integer to a Go-compatible ctypes integer.
70
+
71
+ Args:
72
+ i: The Python integer.
73
+
74
+ Returns:
75
+ A ctypes.c_int equivalent.
76
+ """
77
+ return ctypes.c_int(i)
78
+
79
+ def from_go_int(self, val: ctypes.c_int) -> int:
80
+ """
81
+ Converts a ctypes.c_int from Go to a Python integer.
82
+
83
+ Args:
84
+ val: The ctypes.c_int value.
85
+
86
+ Returns:
87
+ The corresponding Python integer.
88
+ """
89
+ return int(val)
90
+
91
+ def to_go_float(self, f: float) -> ctypes.c_float:
92
+ """
93
+ Converts a Python float to a Go-compatible ctypes float.
94
+
95
+ Args:
96
+ f: The Python float.
97
+
98
+ Returns:
99
+ A ctypes.c_float equivalent.
100
+ """
101
+ return ctypes.c_float(f)
102
+
103
+ def from_go_float(self, val: ctypes.c_float) -> float:
104
+ """
105
+ Converts a ctypes.c_float from Go to a Python float.
106
+
107
+ Args:
108
+ val: The ctypes.c_float value.
109
+
110
+ Returns:
111
+ The corresponding Python float.
112
+ """
113
+ return float(val)
114
+
115
+ def to_go_bool(self, b: bool) -> ctypes.c_int:
116
+ """
117
+ Converts a Python boolean to a Go-compatible integer (1 if True, 0 if False).
118
+
119
+ Args:
120
+ b: The Python boolean.
121
+
122
+ Returns:
123
+ A ctypes.c_int (1 or 0).
124
+ """
125
+ return ctypes.c_int(1 if b else 0)
126
+
127
+ def from_go_bool(self, val: ctypes.c_int) -> bool:
128
+ """
129
+ Converts a Go-compatible integer (ctypes.c_int) to a Python boolean.
130
+
131
+ Args:
132
+ val: The ctypes.c_int value.
133
+
134
+ Returns:
135
+ True if the value equals 1, False otherwise.
136
+ """
137
+ return val == 1
138
+
139
+ def to_go_bytes(self, b: bytes) -> ctypes.c_char_p:
140
+ """
141
+ Converts a Python bytes object to a Go-compatible C char pointer.
142
+
143
+ Args:
144
+ b: The Python bytes.
145
+
146
+ Returns:
147
+ A ctypes.c_char_p pointing to the byte data.
148
+ """
149
+ if b is None:
150
+ b = b""
151
+ buf = ctypes.create_string_buffer(b, len(b))
152
+ return ctypes.cast(buf, ctypes.c_char_p)
153
+
154
+ def from_go_bytes(self, cptr: ctypes.c_char_p, length: int) -> bytes:
155
+ """
156
+ Converts a Go pointer representing a byte array to a Python bytes object.
157
+
158
+ Args:
159
+ cptr: The C pointer to the byte array.
160
+ length: The number of bytes to read.
161
+
162
+ Returns:
163
+ A Python bytes object containing the read data.
164
+ """
165
+ if not cptr or length <= 0:
166
+ return bytes()
167
+ return ctypes.string_at(cptr, length)
168
+
169
+ def from_go_ptr_to_json(self, c_void_ptr_val: int) -> Any:
170
+ """
171
+ Converts a C void* pointer (returned by Go as int) pointing to a
172
+ null-terminated C string containing JSON into a Python object.
173
+
174
+ It reads the string, parses it as JSON, and crucially frees the C memory
175
+ using the provided FreeString function from the Go library.
176
+
177
+ Args:
178
+ c_void_ptr_val: The integer value representing the C pointer address.
179
+
180
+ Returns:
181
+ The parsed Python object from the JSON string.
182
+
183
+ Raises:
184
+ GoLibError: If the pointer is NULL, reading/decoding fails, or
185
+ JSON parsing fails.
186
+ TypeError: When go_lib is not a valid ctypes library object.
187
+ """
188
+
189
+ json_string: Optional[str] = None # To store the string for error reporting
190
+
191
+ if not c_void_ptr_val: # Check if the address is NULL (0)
192
+ raise print("Received a NULL pointer from Go function")
193
+
194
+ try:
195
+
196
+ # --- Double-Free Check (Before Reading/Casting) ---
197
+ self.__freed_pointers_lock.acquire() # Acquire lock if using threading
198
+ if c_void_ptr_val in self.__freed_pointers:
199
+
200
+ # This indicates a serious logic error elsewhere - the pointer
201
+ # was already freed but somehow passed here again.
202
+ logger.warning(f"🔥🔥🔥 ATTEMPT TO PROCESS ALREADY FREED POINTER {hex(c_void_ptr_val)}! 🔥🔥🔥")
203
+
204
+ # Raising an error is safer than trying to read potentially invalid memory.
205
+ logger.error(f"Attempt to process pointer {hex(c_void_ptr_val)} which was already freed",
206
+ pointer_val=c_void_ptr_val)
207
+ raise Exception(f"Attempt to process pointer {hex(c_void_ptr_val)} which was already freed",
208
+ pointer_val=c_void_ptr_val)
209
+ self.__freed_pointers_lock.release() # Release lock if using threading
210
+
211
+ # --- Cast void* to c_char_p and Read String ---
212
+ try:
213
+
214
+ # Perform the cast only when needed for reading
215
+ c_char_ptr_for_read = ctypes.cast(c_void_ptr_val, ctypes.c_char_p)
216
+ raw_bytes = ctypes.string_at(c_char_ptr_for_read)
217
+ json_string = raw_bytes.decode('utf-8')
218
+
219
+ # Logger.debug(f"Read string (len={len(json_string)}) from pointer {hex(c_void_ptr_val)}: %.100s...", json_string)
220
+ except (ctypes.ArgumentError, ValueError, UnicodeDecodeError) as read_err:
221
+ logger.error(f"Failed to read/decode string from pointer {hex(c_void_ptr_val)}: {read_err}", exc_info=False)
222
+
223
+ # Even if reading fails, the pointer itself *might* still be valid C memory
224
+ # that Go expects us to free. We will proceed to free it in finally.
225
+ raise Exception(f"Failed to read string from pointer {hex(c_void_ptr_val)}: {read_err}",
226
+ pointer_val=c_void_ptr_val) from read_err
227
+ except Exception as unexpected_read_err: # Catch other potential ctypes issues
228
+ logger.error(f"Unexpected error reading C string from pointer {hex(c_void_ptr_val)}: {unexpected_read_err}", exc_info=True)
229
+ raise Exception(f"Unexpected error reading C string from pointer {hex(c_void_ptr_val)}: {unexpected_read_err}",
230
+ pointer_val=c_void_ptr_val) from unexpected_read_err
231
+
232
+ # --- Check for Empty String ---
233
+
234
+ # --- Parse JSON ---
235
+ try:
236
+
237
+ # Now that we have the string, parse it
238
+ logger.debug(f"Parsing JSON from string: {json_string}")
239
+ parsed_data = json.loads(json_string)
240
+ logger.debug(f"Parsed JSON data: {parsed_data}")
241
+
242
+ # Logger.debug(f"Successfully parsed JSON from pointer {hex(c_void_ptr_val)}")
243
+ return parsed_data # Return the parsed Python object
244
+
245
+ except json.JSONDecodeError as json_err:
246
+ logger.error(f"Failed to decode JSON from pointer {hex(c_void_ptr_val)}: {json_err}", exc_info=False)
247
+
248
+ # Again, the pointer is likely valid C memory, but the content is bad.
249
+ # Let the block handle freeing.
250
+ raise Exception(f"Failed to decode JSON from pointer {hex(c_void_ptr_val)}: {json_err}",
251
+ pointer_val=c_void_ptr_val) from json_err
252
+
253
+ finally:
254
+
255
+ # --- CRITICAL: Free C Memory ---
256
+ # This block executes even if errors occurred during read/parse,
257
+ # ensuring we attempt to free any non-NULL pointer received from Go.
258
+ with self.__freed_pointers_lock:
259
+ if c_void_ptr_val:
260
+ logger.info(f"🐍 FINALLY: Freeing pointer {hex(c_void_ptr_val)}...")
261
+ if c_void_ptr_val in self.__freed_pointers:
262
+
263
+ # This check is technically redundant if the initial check worked,
264
+ # but provides an extra safety layer in case of concurrency issues
265
+ # (if freed_pointers is shared without locks - which it shouldn't be).
266
+ logger.warning(f"🔥🔥🔥 DOUBLE FREE DETECTED in finally block for {hex(c_void_ptr_val)}! Skipping FreeString call again. 🔥🔥🔥")
267
+ else:
268
+
269
+ # Add before calling free
270
+ try:
271
+ self.libp2p.FreeString(c_void_ptr_val) # Pass the original void* value
272
+ logger.info(f"✅ FINALLY: FreeString successful for {hex(c_void_ptr_val)}.")
273
+ except Exception as free_err:
274
+
275
+ # Log if FreeString fails, but don't raise from finally
276
+ # as it might hide the original error.
277
+ logger.critical(f"🚨 FAILED TO FREE C MEMORY for pointer {hex(c_void_ptr_val)} via FreeString: {free_err}", exc_info=True)
278
+
279
+ # Consider removing from freed_pointers if free failed?
280
+ # freed_pointers.discard(c_void_ptr_val) # Maybe, to allow retry? Risky.
281
+ # But if FreeString fails, the pointer is likely invalid anyway.
282
+
283
+ def to_go_json(self, data: Any) -> bytes:
284
+ """
285
+ Encodes a Python object to a JSON string, returning a UTF-8 encoded
286
+ Python 'bytes' object.
287
+
288
+ This 'bytes' object is suitable for direct use with ctypes when passing
289
+ to a C function expecting a 'char*' (ctypes.c_char_p).
290
+
291
+ Args:
292
+ data: The Python object (e.g., dict, list) to encode.
293
+
294
+ Returns:
295
+ A Python 'bytes' object containing the JSON string, UTF-8 encoded.
296
+ """
297
+ json_str = json.dumps(data)
298
+ return self.to_go_string(json_str)
299
+
300
+ def from_go_string_to_list(self, cstr: ctypes.c_char_p) -> List[Any]:
301
+ """
302
+ Decodes a JSON-encoded list from a Go C char pointer into a Python list.
303
+
304
+ Args:
305
+ cstr: The Go string (C char pointer) containing a JSON list.
306
+
307
+ Returns:
308
+ A Python list representing the JSON data.
309
+ """
310
+ s = self.from_go_string(cstr)
311
+
312
+ return json.loads(s)
@@ -0,0 +1,63 @@
1
+ """
2
+ █████ █████ ██████ █████ █████ █████ █████ ██████████ ███████████ █████████ ██████████
3
+ ░░███ ░░███ ░░██████ ░░███ ░░███ ░░███ ░░███ ░░███░░░░░█░░███░░░░░███ ███░░░░░███░░███░░░░░█
4
+ ░███ ░███ ░███░███ ░███ ██████ ░███ ░███ ░███ ░███ █ ░ ░███ ░███ ░███ ░░░ ░███ █ ░
5
+ ░███ ░███ ░███░░███░███ ░░░░░███ ░███ ░███ ░███ ░██████ ░██████████ ░░█████████ ░██████
6
+ ░███ ░███ ░███ ░░██████ ███████ ░███ ░░███ ███ ░███░░█ ░███░░░░░███ ░░░░░░░░███ ░███░░█
7
+ ░███ ░███ ░███ ░░█████ ███░░███ ░███ ░░░█████░ ░███ ░ █ ░███ ░███ ███ ░███ ░███ ░ █
8
+ ░░████████ █████ ░░█████░░████████ █████ ░░███ ██████████ █████ █████░░█████████ ██████████
9
+ ░░░░░░░░ ░░░░░ ░░░░░ ░░░░░░░░ ░░░░░ ░░░ ░░░░░░░░░░ ░░░░░ ░░░░░ ░░░░░░░░░ ░░░░░░░░░░
10
+ A Collectionless AI Project (https://collectionless.ai)
11
+ Registration/Login: https://unaiverse.io
12
+ Code Repositories: https://github.com/collectionlessai/
13
+ Main Developers: Stefano Melacci (Project Leader), Christian Di Maio, Tommaso Guidi
14
+ """
15
+ from google.protobuf import descriptor as _descriptor
16
+ from google.protobuf.internal import builder as _builder
17
+ from google.protobuf import descriptor_pool as _descriptor_pool
18
+ from google.protobuf import runtime_version as _runtime_version
19
+ from google.protobuf import symbol_database as _symbol_database
20
+ _runtime_version.ValidateProtobufRuntimeVersion(
21
+ _runtime_version.Domain.PUBLIC,
22
+ 6,
23
+ 31,
24
+ 1,
25
+ '',
26
+ 'message.proto'
27
+ )
28
+
29
+ # @@protoc_insertion_point(imports)
30
+
31
+ _sym_db = _symbol_database.Default()
32
+
33
+
34
+
35
+
36
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\rmessage.proto\x12\x03p2p\"\xc0\x01\n\x07Message\x12\x0e\n\x06sender\x18\x01 \x01(\t\x12\x14\n\x0c\x63ontent_type\x18\x02 \x01(\t\x12\x0f\n\x07\x63hannel\x18\x03 \x01(\t\x12\x11\n\tpiggyback\x18\x04 \x01(\t\x12\x15\n\rtimestamp_net\x18\x05 \x01(\t\x12\x31\n\rstream_sample\x18\x06 \x01(\x0b\x32\x18.p2p.StreamSampleContentH\x00\x12\x16\n\x0cjson_content\x18\x07 \x01(\tH\x00\x42\t\n\x07\x63ontent\"\x90\x01\n\x13StreamSampleContent\x12\x36\n\x07samples\x18\x01 \x03(\x0b\x32%.p2p.StreamSampleContent.SamplesEntry\x1a\x41\n\x0cSamplesEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12 \n\x05value\x18\x02 \x01(\x0b\x32\x11.p2p.StreamSample:\x02\x38\x01\"e\n\x0cStreamSample\x12\x1d\n\x04\x64\x61ta\x18\x01 \x01(\x0b\x32\x0f.p2p.SampleData\x12\x10\n\x08\x64\x61ta_tag\x18\x02 \x01(\x05\x12\x16\n\tdata_uuid\x18\x03 \x01(\tH\x00\x88\x01\x01\x42\x0c\n\n_data_uuid\"\x8e\x01\n\nSampleData\x12&\n\x0btensor_data\x18\x01 \x01(\x0b\x32\x0f.p2p.TensorDataH\x00\x12$\n\nimage_data\x18\x02 \x01(\x0b\x32\x0e.p2p.ImageDataH\x00\x12\"\n\ttext_data\x18\x03 \x01(\x0b\x32\r.p2p.TextDataH\x00\x42\x0e\n\x0c\x64\x61ta_payload\"8\n\nTensorData\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\x12\r\n\x05\x64type\x18\x02 \x01(\t\x12\r\n\x05shape\x18\x03 \x03(\x05\"\x19\n\tImageData\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\"\x18\n\x08TextData\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\tB\x0cZ\n./proto-gob\x06proto3')
37
+
38
+ _globals = globals()
39
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
40
+ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'message_pb2', _globals)
41
+ if not _descriptor._USE_C_DESCRIPTORS:
42
+ _globals['DESCRIPTOR']._loaded_options = None
43
+ _globals['DESCRIPTOR']._serialized_options = b'Z\n./proto-go'
44
+ _globals['_STREAMSAMPLECONTENT_SAMPLESENTRY']._loaded_options = None
45
+ _globals['_STREAMSAMPLECONTENT_SAMPLESENTRY']._serialized_options = b'8\001'
46
+ _globals['_MESSAGE']._serialized_start=23
47
+ _globals['_MESSAGE']._serialized_end=215
48
+ _globals['_STREAMSAMPLECONTENT']._serialized_start=218
49
+ _globals['_STREAMSAMPLECONTENT']._serialized_end=362
50
+ _globals['_STREAMSAMPLECONTENT_SAMPLESENTRY']._serialized_start=297
51
+ _globals['_STREAMSAMPLECONTENT_SAMPLESENTRY']._serialized_end=362
52
+ _globals['_STREAMSAMPLE']._serialized_start=364
53
+ _globals['_STREAMSAMPLE']._serialized_end=465
54
+ _globals['_SAMPLEDATA']._serialized_start=468
55
+ _globals['_SAMPLEDATA']._serialized_end=610
56
+ _globals['_TENSORDATA']._serialized_start=612
57
+ _globals['_TENSORDATA']._serialized_end=668
58
+ _globals['_IMAGEDATA']._serialized_start=670
59
+ _globals['_IMAGEDATA']._serialized_end=695
60
+ _globals['_TEXTDATA']._serialized_start=697
61
+ _globals['_TEXTDATA']._serialized_end=721
62
+
63
+ # @@protoc_insertion_point(module_scope)
@@ -0,0 +1,265 @@
1
+ """
2
+ █████ █████ ██████ █████ █████ █████ █████ ██████████ ███████████ █████████ ██████████
3
+ ░░███ ░░███ ░░██████ ░░███ ░░███ ░░███ ░░███ ░░███░░░░░█░░███░░░░░███ ███░░░░░███░░███░░░░░█
4
+ ░███ ░███ ░███░███ ░███ ██████ ░███ ░███ ░███ ░███ █ ░ ░███ ░███ ░███ ░░░ ░███ █ ░
5
+ ░███ ░███ ░███░░███░███ ░░░░░███ ░███ ░███ ░███ ░██████ ░██████████ ░░█████████ ░██████
6
+ ░███ ░███ ░███ ░░██████ ███████ ░███ ░░███ ███ ░███░░█ ░███░░░░░███ ░░░░░░░░███ ░███░░█
7
+ ░███ ░███ ░███ ░░█████ ███░░███ ░███ ░░░█████░ ░███ ░ █ ░███ ░███ ███ ░███ ░███ ░ █
8
+ ░░████████ █████ ░░█████░░████████ █████ ░░███ ██████████ █████ █████░░█████████ ██████████
9
+ ░░░░░░░░ ░░░░░ ░░░░░ ░░░░░░░░ ░░░░░ ░░░ ░░░░░░░░░░ ░░░░░ ░░░░░ ░░░░░░░░░ ░░░░░░░░░░
10
+ A Collectionless AI Project (https://collectionless.ai)
11
+ Registration/Login: https://unaiverse.io
12
+ Code Repositories: https://github.com/collectionlessai/
13
+ Main Developers: Stefano Melacci (Project Leader), Christian Di Maio, Tommaso Guidi
14
+ """
15
+ import io
16
+ import gzip
17
+ import json
18
+ import torch
19
+ from PIL import Image
20
+ from datetime import datetime, timezone
21
+
22
+ # Import the Protobuf-generated module
23
+ try:
24
+ from . import message_pb2 as pb
25
+ except ImportError:
26
+ print("Error: message_pb2.py not found. Please compile the .proto file first.")
27
+ raise
28
+
29
+
30
+ class Msg:
31
+
32
+ # Message content types
33
+ PROFILE = "profile"
34
+ WORLD_APPROVAL = "world_approval"
35
+ AGENT_APPROVAL = "agent_approval"
36
+ PROFILE_REQUEST = "profile_request"
37
+ ADDRESS_UPDATE = "address_update"
38
+ STREAM_SAMPLE = "stream_sample"
39
+ ACTION_REQUEST = "action_request"
40
+ ROLE_SUGGESTION = "role_suggestion"
41
+ HSM = "hsm"
42
+ MISC = "misc"
43
+ GET_CV_FROM_ROOT = "get_cv_from_root"
44
+ BADGE_SUGGESTIONS = "badge_suggestions"
45
+ INSPECT_ON = "inspect_on"
46
+ INSPECT_CMD = "inspect_cmd"
47
+ WORLD_AGENTS_LIST = "world_agents_list"
48
+ CONSOLE_AND_BEHAV_STATUS = "console_and_behav_status"
49
+
50
+ # Collections
51
+ CONTENT_TYPES = {PROFILE, WORLD_APPROVAL, AGENT_APPROVAL, PROFILE_REQUEST, ADDRESS_UPDATE,
52
+ STREAM_SAMPLE, ACTION_REQUEST, ROLE_SUGGESTION, HSM, MISC, GET_CV_FROM_ROOT,
53
+ BADGE_SUGGESTIONS, INSPECT_ON, INSPECT_CMD, WORLD_AGENTS_LIST, CONSOLE_AND_BEHAV_STATUS}
54
+
55
+ def __init__(self,
56
+ sender: str | None = None,
57
+ content: any = None,
58
+ timestamp_net: str | None = None,
59
+ channel: str | None = None,
60
+ content_type: str = MISC,
61
+ piggyback: str | None = None,
62
+ _proto_msg: pb.Message = None):
63
+ """The constructor should be used either to create a new message filling the fields,
64
+ or to parse an existing Protobuf message (passing _proto_msg). In the latter case,
65
+ the other fields are ignored and the Protobuf message is used as-is. The message is
66
+ simply stored in the internal `_proto_msg` field and other fields can be accessed
67
+ through properties."""
68
+
69
+ self._decoded_content: any = None # Cache for decompressed content
70
+
71
+ if _proto_msg is not None:
72
+
73
+ # Check if any other arguments were simultaneously provided
74
+ other_args = [sender, content, timestamp_net, channel, piggyback]
75
+ if any(arg is not None for arg in other_args):
76
+ raise ValueError("Cannot specify other arguments when creating a Msg from a _proto_msg.")
77
+
78
+ # This path is used by from_bytes, message is already built
79
+ self._proto_msg = _proto_msg
80
+ return
81
+
82
+ # Sanity checks
83
+ assert sender is not None, "Sender must be specified for a new message."
84
+ assert isinstance(sender, str), "Sender must be a string"
85
+ assert timestamp_net is None or isinstance(timestamp_net, str), "Invalid timestamp_net"
86
+ assert channel is None or isinstance(channel, str), "Invalid channel"
87
+ assert content_type in Msg.CONTENT_TYPES, "Invalid content type"
88
+
89
+ # --- SMART CONSTRUCTOR: Populates the correct 'oneof' field ---
90
+ self._proto_msg = pb.Message()
91
+ self._proto_msg.sender = sender if sender is not None else ""
92
+ self._proto_msg.timestamp_net = timestamp_net if timestamp_net is not None else \
93
+ datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S.%f")
94
+ self._proto_msg.content_type = content_type if content_type is not None else self.MISC
95
+ self._proto_msg.channel = channel if channel is not None else "<unknown>"
96
+ self._proto_msg.piggyback = piggyback if piggyback is not None else ""
97
+
98
+ if content is None or content == "<empty>":
99
+ return # Nothing to set in the 'oneof'
100
+
101
+ # Route the content to the correct builder
102
+ if content_type == Msg.STREAM_SAMPLE:
103
+ self._build_stream_sample_content(content)
104
+ else:
105
+
106
+ # All other structured types use the generic json_content field
107
+ self._build_json_content(content)
108
+
109
+ def __str__(self):
110
+ return (f"Msg(sender={self.sender[:12]}..., content_type={self.content_type}, "
111
+ f"channel='{self.channel}', content_len={len(self.to_bytes())} bytes)")
112
+
113
+ # --- Properties for lazy-loading and easy access ---
114
+ @property
115
+ def sender(self):
116
+ return self._proto_msg.sender
117
+
118
+ @sender.setter
119
+ def sender(self, value: str):
120
+ self._proto_msg.sender = value if value is not None else ""
121
+
122
+ @property
123
+ def content_type(self):
124
+ return self._proto_msg.content_type
125
+
126
+ @content_type.setter
127
+ def content_type(self, value: str):
128
+ self._proto_msg.content_type = value if value is not None else self.MISC
129
+
130
+ @property
131
+ def channel(self):
132
+ return self._proto_msg.channel
133
+
134
+ @channel.setter
135
+ def channel(self, value: str):
136
+ self._proto_msg.channel = value if value is not None else "<unknown>"
137
+
138
+ @property
139
+ def piggyback(self):
140
+ return self._proto_msg.piggyback
141
+
142
+ @piggyback.setter
143
+ def piggyback(self, value: str):
144
+ self._proto_msg.piggyback = value if value is not None else ""
145
+
146
+ @property
147
+ def timestamp_net(self):
148
+ return self._proto_msg.timestamp_net
149
+
150
+ @timestamp_net.setter
151
+ def timestamp_net(self, value: str):
152
+ self._proto_msg.timestamp_net = value if value is not None else ""
153
+
154
+
155
+ @property
156
+ def content(self) -> any:
157
+ """The main content of the message, decoded on-the-fly with caching."""
158
+ if self._decoded_content is not None:
159
+ return self._decoded_content
160
+
161
+ payload_type = self._proto_msg.WhichOneof("content")
162
+ if payload_type == "stream_sample":
163
+ self._decoded_content = self._parse_stream_sample_content()
164
+ elif payload_type == "json_content":
165
+ self._decoded_content = self._parse_json_content()
166
+ else:
167
+ self._decoded_content = "<empty>"
168
+
169
+ return self._decoded_content
170
+
171
+ # --- Serialization / Deserialization ---
172
+ def to_bytes(self) -> bytes:
173
+ """Serializes the internal Protobuf message to bytes."""
174
+ return self._proto_msg.SerializeToString()
175
+
176
+ @classmethod
177
+ def from_bytes(cls, msg_bytes: bytes) -> 'Msg':
178
+ """Deserializes a byte array into a new Msg instance."""
179
+ pb_msg = pb.Message()
180
+ pb_msg.ParseFromString(msg_bytes)
181
+
182
+ # Pass the parsed protobuf message to the constructor
183
+ return cls(_proto_msg=pb_msg)
184
+
185
+ # --- Internal Helper Methods ---
186
+ def _build_json_content(self, content: dict):
187
+ """Populates the generic json_content field."""
188
+ self._proto_msg.json_content = json.dumps(content)
189
+
190
+ def _parse_json_content(self) -> dict:
191
+ """Parses the generic json_content field back to a dict."""
192
+ return json.loads(self._proto_msg.json_content)
193
+
194
+ def _build_stream_sample_content(self, samples_dict: dict):
195
+ """Builds the complex StreamSampleContent message from a dict."""
196
+ content_pb = self._proto_msg.stream_sample
197
+ for name, sample_info in samples_dict.items():
198
+ data = sample_info.get('data')
199
+ if data is None:
200
+ continue
201
+
202
+ stream_sample_pb = content_pb.samples[name]
203
+ stream_sample_pb.data_tag = sample_info.get('data_tag', -1)
204
+ uuid = sample_info.get('data_uuid')
205
+
206
+ # Only set the field if the uuid is not None
207
+ if uuid is not None:
208
+ stream_sample_pb.data_uuid = uuid
209
+
210
+ if isinstance(data, torch.Tensor):
211
+ raw_bytes = data.detach().cpu().numpy().tobytes()
212
+ with io.BytesIO() as buffer:
213
+ with gzip.GzipFile(fileobj=buffer, mode='wb') as f:
214
+ f.write(raw_bytes)
215
+ stream_sample_pb.data.tensor_data.data = buffer.getvalue()
216
+ stream_sample_pb.data.tensor_data.dtype = str(data.dtype).split('.')[-1]
217
+ stream_sample_pb.data.tensor_data.shape.extend(list(data.shape))
218
+
219
+ elif isinstance(data, Image.Image):
220
+ with io.BytesIO() as buffer:
221
+ data.save(buffer, format="PNG", optimize=True, compress_level=9)
222
+ stream_sample_pb.data.image_data.data = buffer.getvalue()
223
+
224
+ elif isinstance(data, str):
225
+ stream_sample_pb.data.text_data.data = data
226
+
227
+ def _parse_stream_sample_content(self) -> dict:
228
+ """
229
+ Parses the internal StreamSampleContent message back into a Python
230
+ dictionary of tensors and images.
231
+ """
232
+ py_dict = {}
233
+
234
+ # Iterate through the Protobuf map ('samples')
235
+ for name, sample_pb in self._proto_msg.stream_sample.samples.items():
236
+ data_payload = sample_pb.data
237
+ data = None
238
+
239
+ # Check which field in the 'oneof' is set
240
+ payload_type = data_payload.WhichOneof("data_payload")
241
+
242
+ if payload_type == "tensor_data":
243
+ tensor_data = data_payload.tensor_data
244
+
245
+ # Decompress and reconstruct the tensor
246
+ with gzip.GzipFile(fileobj=io.BytesIO(tensor_data.data), mode='rb') as f:
247
+ raw_bytes = f.read()
248
+ data = torch.frombuffer(
249
+ bytearray(raw_bytes),
250
+ dtype=getattr(torch, tensor_data.dtype)
251
+ ).reshape(list(tensor_data.shape))
252
+
253
+ elif payload_type == "image_data":
254
+ data = Image.open(io.BytesIO(data_payload.image_data.data))
255
+
256
+ elif payload_type == "text_data":
257
+ data = data_payload.text_data.data
258
+
259
+ # Build the final Python dictionary for this sample
260
+ py_dict[name] = {
261
+ 'data': data,
262
+ 'data_tag': sample_pb.data_tag,
263
+ 'data_uuid': sample_pb.data_uuid if sample_pb.HasField("data_uuid") else None
264
+ }
265
+ return py_dict