@elizaos/python 2.0.0-alpha.10

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.
Files changed (197) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +239 -0
  3. package/elizaos/__init__.py +280 -0
  4. package/elizaos/action_docs.py +149 -0
  5. package/elizaos/advanced_capabilities/__init__.py +85 -0
  6. package/elizaos/advanced_capabilities/actions/__init__.py +54 -0
  7. package/elizaos/advanced_capabilities/actions/add_contact.py +139 -0
  8. package/elizaos/advanced_capabilities/actions/follow_room.py +151 -0
  9. package/elizaos/advanced_capabilities/actions/image_generation.py +148 -0
  10. package/elizaos/advanced_capabilities/actions/mute_room.py +164 -0
  11. package/elizaos/advanced_capabilities/actions/remove_contact.py +145 -0
  12. package/elizaos/advanced_capabilities/actions/roles.py +207 -0
  13. package/elizaos/advanced_capabilities/actions/schedule_follow_up.py +154 -0
  14. package/elizaos/advanced_capabilities/actions/search_contacts.py +145 -0
  15. package/elizaos/advanced_capabilities/actions/send_message.py +187 -0
  16. package/elizaos/advanced_capabilities/actions/settings.py +151 -0
  17. package/elizaos/advanced_capabilities/actions/unfollow_room.py +164 -0
  18. package/elizaos/advanced_capabilities/actions/unmute_room.py +164 -0
  19. package/elizaos/advanced_capabilities/actions/update_contact.py +164 -0
  20. package/elizaos/advanced_capabilities/actions/update_entity.py +161 -0
  21. package/elizaos/advanced_capabilities/evaluators/__init__.py +18 -0
  22. package/elizaos/advanced_capabilities/evaluators/reflection.py +134 -0
  23. package/elizaos/advanced_capabilities/evaluators/relationship_extraction.py +203 -0
  24. package/elizaos/advanced_capabilities/providers/__init__.py +36 -0
  25. package/elizaos/advanced_capabilities/providers/agent_settings.py +60 -0
  26. package/elizaos/advanced_capabilities/providers/contacts.py +77 -0
  27. package/elizaos/advanced_capabilities/providers/facts.py +82 -0
  28. package/elizaos/advanced_capabilities/providers/follow_ups.py +113 -0
  29. package/elizaos/advanced_capabilities/providers/knowledge.py +83 -0
  30. package/elizaos/advanced_capabilities/providers/relationships.py +112 -0
  31. package/elizaos/advanced_capabilities/providers/roles.py +97 -0
  32. package/elizaos/advanced_capabilities/providers/settings.py +51 -0
  33. package/elizaos/advanced_capabilities/services/__init__.py +18 -0
  34. package/elizaos/advanced_capabilities/services/follow_up.py +138 -0
  35. package/elizaos/advanced_capabilities/services/rolodex.py +244 -0
  36. package/elizaos/advanced_memory/__init__.py +3 -0
  37. package/elizaos/advanced_memory/evaluators.py +97 -0
  38. package/elizaos/advanced_memory/memory_service.py +556 -0
  39. package/elizaos/advanced_memory/plugin.py +30 -0
  40. package/elizaos/advanced_memory/prompts.py +12 -0
  41. package/elizaos/advanced_memory/providers.py +90 -0
  42. package/elizaos/advanced_memory/types.py +65 -0
  43. package/elizaos/advanced_planning/__init__.py +10 -0
  44. package/elizaos/advanced_planning/actions.py +145 -0
  45. package/elizaos/advanced_planning/message_classifier.py +127 -0
  46. package/elizaos/advanced_planning/planning_service.py +712 -0
  47. package/elizaos/advanced_planning/plugin.py +40 -0
  48. package/elizaos/advanced_planning/prompts.py +4 -0
  49. package/elizaos/basic_capabilities/__init__.py +66 -0
  50. package/elizaos/basic_capabilities/actions/__init__.py +24 -0
  51. package/elizaos/basic_capabilities/actions/choice.py +140 -0
  52. package/elizaos/basic_capabilities/actions/ignore.py +66 -0
  53. package/elizaos/basic_capabilities/actions/none.py +56 -0
  54. package/elizaos/basic_capabilities/actions/reply.py +120 -0
  55. package/elizaos/basic_capabilities/providers/__init__.py +54 -0
  56. package/elizaos/basic_capabilities/providers/action_state.py +113 -0
  57. package/elizaos/basic_capabilities/providers/actions.py +263 -0
  58. package/elizaos/basic_capabilities/providers/attachments.py +76 -0
  59. package/elizaos/basic_capabilities/providers/capabilities.py +62 -0
  60. package/elizaos/basic_capabilities/providers/character.py +113 -0
  61. package/elizaos/basic_capabilities/providers/choice.py +73 -0
  62. package/elizaos/basic_capabilities/providers/context_bench.py +44 -0
  63. package/elizaos/basic_capabilities/providers/current_time.py +58 -0
  64. package/elizaos/basic_capabilities/providers/entities.py +99 -0
  65. package/elizaos/basic_capabilities/providers/evaluators.py +54 -0
  66. package/elizaos/basic_capabilities/providers/providers_list.py +55 -0
  67. package/elizaos/basic_capabilities/providers/recent_messages.py +85 -0
  68. package/elizaos/basic_capabilities/providers/time.py +45 -0
  69. package/elizaos/basic_capabilities/providers/world.py +93 -0
  70. package/elizaos/basic_capabilities/services/__init__.py +18 -0
  71. package/elizaos/basic_capabilities/services/embedding.py +122 -0
  72. package/elizaos/basic_capabilities/services/task.py +178 -0
  73. package/elizaos/bootstrap/__init__.py +12 -0
  74. package/elizaos/bootstrap/actions/__init__.py +68 -0
  75. package/elizaos/bootstrap/actions/add_contact.py +149 -0
  76. package/elizaos/bootstrap/actions/choice.py +147 -0
  77. package/elizaos/bootstrap/actions/follow_room.py +151 -0
  78. package/elizaos/bootstrap/actions/ignore.py +80 -0
  79. package/elizaos/bootstrap/actions/image_generation.py +135 -0
  80. package/elizaos/bootstrap/actions/mute_room.py +151 -0
  81. package/elizaos/bootstrap/actions/none.py +71 -0
  82. package/elizaos/bootstrap/actions/remove_contact.py +159 -0
  83. package/elizaos/bootstrap/actions/reply.py +140 -0
  84. package/elizaos/bootstrap/actions/roles.py +193 -0
  85. package/elizaos/bootstrap/actions/schedule_follow_up.py +164 -0
  86. package/elizaos/bootstrap/actions/search_contacts.py +159 -0
  87. package/elizaos/bootstrap/actions/send_message.py +173 -0
  88. package/elizaos/bootstrap/actions/settings.py +165 -0
  89. package/elizaos/bootstrap/actions/unfollow_room.py +151 -0
  90. package/elizaos/bootstrap/actions/unmute_room.py +151 -0
  91. package/elizaos/bootstrap/actions/update_contact.py +178 -0
  92. package/elizaos/bootstrap/actions/update_entity.py +175 -0
  93. package/elizaos/bootstrap/autonomy/__init__.py +18 -0
  94. package/elizaos/bootstrap/autonomy/action.py +197 -0
  95. package/elizaos/bootstrap/autonomy/providers.py +165 -0
  96. package/elizaos/bootstrap/autonomy/routes.py +171 -0
  97. package/elizaos/bootstrap/autonomy/service.py +562 -0
  98. package/elizaos/bootstrap/autonomy/types.py +18 -0
  99. package/elizaos/bootstrap/evaluators/__init__.py +19 -0
  100. package/elizaos/bootstrap/evaluators/reflection.py +118 -0
  101. package/elizaos/bootstrap/evaluators/relationship_extraction.py +192 -0
  102. package/elizaos/bootstrap/plugin.py +140 -0
  103. package/elizaos/bootstrap/providers/__init__.py +80 -0
  104. package/elizaos/bootstrap/providers/action_state.py +71 -0
  105. package/elizaos/bootstrap/providers/actions.py +256 -0
  106. package/elizaos/bootstrap/providers/agent_settings.py +63 -0
  107. package/elizaos/bootstrap/providers/attachments.py +76 -0
  108. package/elizaos/bootstrap/providers/capabilities.py +66 -0
  109. package/elizaos/bootstrap/providers/character.py +128 -0
  110. package/elizaos/bootstrap/providers/choice.py +77 -0
  111. package/elizaos/bootstrap/providers/contacts.py +78 -0
  112. package/elizaos/bootstrap/providers/context_bench.py +49 -0
  113. package/elizaos/bootstrap/providers/current_time.py +56 -0
  114. package/elizaos/bootstrap/providers/entities.py +99 -0
  115. package/elizaos/bootstrap/providers/evaluators.py +58 -0
  116. package/elizaos/bootstrap/providers/facts.py +86 -0
  117. package/elizaos/bootstrap/providers/follow_ups.py +116 -0
  118. package/elizaos/bootstrap/providers/knowledge.py +73 -0
  119. package/elizaos/bootstrap/providers/providers_list.py +59 -0
  120. package/elizaos/bootstrap/providers/recent_messages.py +85 -0
  121. package/elizaos/bootstrap/providers/relationships.py +106 -0
  122. package/elizaos/bootstrap/providers/roles.py +95 -0
  123. package/elizaos/bootstrap/providers/settings.py +55 -0
  124. package/elizaos/bootstrap/providers/time.py +45 -0
  125. package/elizaos/bootstrap/providers/world.py +97 -0
  126. package/elizaos/bootstrap/services/__init__.py +26 -0
  127. package/elizaos/bootstrap/services/embedding.py +122 -0
  128. package/elizaos/bootstrap/services/follow_up.py +138 -0
  129. package/elizaos/bootstrap/services/rolodex.py +244 -0
  130. package/elizaos/bootstrap/services/task.py +585 -0
  131. package/elizaos/bootstrap/types.py +54 -0
  132. package/elizaos/bootstrap/utils/__init__.py +7 -0
  133. package/elizaos/bootstrap/utils/xml.py +69 -0
  134. package/elizaos/character.py +149 -0
  135. package/elizaos/logger.py +179 -0
  136. package/elizaos/media/__init__.py +45 -0
  137. package/elizaos/media/mime.py +315 -0
  138. package/elizaos/media/search.py +161 -0
  139. package/elizaos/media/tests/__init__.py +1 -0
  140. package/elizaos/media/tests/test_mime.py +117 -0
  141. package/elizaos/media/tests/test_search.py +156 -0
  142. package/elizaos/plugin.py +191 -0
  143. package/elizaos/prompts.py +1071 -0
  144. package/elizaos/py.typed +0 -0
  145. package/elizaos/runtime.py +2572 -0
  146. package/elizaos/services/__init__.py +49 -0
  147. package/elizaos/services/hook_service.py +511 -0
  148. package/elizaos/services/message_service.py +1248 -0
  149. package/elizaos/settings.py +182 -0
  150. package/elizaos/streaming_context.py +159 -0
  151. package/elizaos/trajectory_context.py +18 -0
  152. package/elizaos/types/__init__.py +512 -0
  153. package/elizaos/types/agent.py +31 -0
  154. package/elizaos/types/components.py +208 -0
  155. package/elizaos/types/database.py +64 -0
  156. package/elizaos/types/environment.py +46 -0
  157. package/elizaos/types/events.py +47 -0
  158. package/elizaos/types/memory.py +45 -0
  159. package/elizaos/types/model.py +393 -0
  160. package/elizaos/types/plugin.py +188 -0
  161. package/elizaos/types/primitives.py +100 -0
  162. package/elizaos/types/runtime.py +460 -0
  163. package/elizaos/types/service.py +113 -0
  164. package/elizaos/types/service_interfaces.py +244 -0
  165. package/elizaos/types/state.py +188 -0
  166. package/elizaos/types/task.py +29 -0
  167. package/elizaos/utils/__init__.py +108 -0
  168. package/elizaos/utils/spec_examples.py +48 -0
  169. package/elizaos/utils/streaming.py +426 -0
  170. package/elizaos_atropos_shared/__init__.py +1 -0
  171. package/elizaos_atropos_shared/canonical_eliza.py +282 -0
  172. package/package.json +19 -0
  173. package/pyproject.toml +143 -0
  174. package/requirements-dev.in +11 -0
  175. package/requirements-dev.lock +134 -0
  176. package/requirements.in +9 -0
  177. package/requirements.lock +64 -0
  178. package/tests/__init__.py +0 -0
  179. package/tests/test_action_parameters.py +154 -0
  180. package/tests/test_actions_provider_examples.py +39 -0
  181. package/tests/test_advanced_memory_behavior.py +96 -0
  182. package/tests/test_advanced_memory_flag.py +30 -0
  183. package/tests/test_advanced_planning_behavior.py +225 -0
  184. package/tests/test_advanced_planning_flag.py +26 -0
  185. package/tests/test_autonomy.py +445 -0
  186. package/tests/test_bootstrap_initialize.py +37 -0
  187. package/tests/test_character.py +163 -0
  188. package/tests/test_character_provider.py +231 -0
  189. package/tests/test_dynamic_prompt_exec.py +561 -0
  190. package/tests/test_logger_redaction.py +43 -0
  191. package/tests/test_plugin.py +117 -0
  192. package/tests/test_runtime.py +422 -0
  193. package/tests/test_salt_production_enforcement.py +22 -0
  194. package/tests/test_settings_crypto.py +118 -0
  195. package/tests/test_streaming.py +295 -0
  196. package/tests/test_types.py +221 -0
  197. package/tests/test_uuid_parity.py +46 -0
@@ -0,0 +1,295 @@
1
+ """Tests for streaming utilities.
2
+
3
+ This test module validates the streaming extractors:
4
+ 1. MarkableExtractor - passthrough with external completion control
5
+ 2. ValidationStreamExtractor - validation-aware streaming
6
+ 3. Cross-language parity with TypeScript and Rust
7
+ """
8
+
9
+ import pytest
10
+
11
+ from elizaos.types.state import SchemaRow, StreamEvent, StreamEventType
12
+ from elizaos.utils.streaming import (
13
+ MAX_CHUNK_SIZE,
14
+ ChunkSizeError,
15
+ ExtractorState,
16
+ FieldState,
17
+ MarkableExtractor,
18
+ ValidationDiagnosis,
19
+ ValidationStreamExtractor,
20
+ ValidationStreamExtractorConfig,
21
+ validate_chunk_size,
22
+ )
23
+
24
+
25
+ class TestMarkableExtractor:
26
+ """Tests for MarkableExtractor."""
27
+
28
+ def test_new(self) -> None:
29
+ """Test creating a new extractor."""
30
+ extractor = MarkableExtractor()
31
+ assert extractor.done is False
32
+
33
+ def test_passthrough(self) -> None:
34
+ """Test that chunks pass through unchanged."""
35
+ extractor = MarkableExtractor()
36
+ output = extractor.push("test chunk")
37
+ assert output == "test chunk"
38
+
39
+ def test_mark_complete(self) -> None:
40
+ """Test marking extractor as complete."""
41
+ extractor = MarkableExtractor()
42
+ assert extractor.done is False
43
+ extractor.mark_complete()
44
+ assert extractor.done is True
45
+
46
+ def test_reset(self) -> None:
47
+ """Test resetting extractor state."""
48
+ extractor = MarkableExtractor()
49
+ extractor.mark_complete()
50
+ assert extractor.done is True
51
+ extractor.reset()
52
+ assert extractor.done is False
53
+
54
+ def test_flush(self) -> None:
55
+ """Test flush returns empty string."""
56
+ extractor = MarkableExtractor()
57
+ assert extractor.flush() == ""
58
+
59
+
60
+ class TestChunkValidation:
61
+ """Tests for chunk size validation."""
62
+
63
+ def test_validate_chunk_size_ok(self) -> None:
64
+ """Test that small chunks pass validation."""
65
+ # Should not raise
66
+ validate_chunk_size("small chunk")
67
+
68
+ def test_validate_chunk_size_error(self) -> None:
69
+ """Test that large chunks fail validation."""
70
+ large_chunk = "x" * (MAX_CHUNK_SIZE + 1)
71
+ with pytest.raises(ChunkSizeError) as exc_info:
72
+ validate_chunk_size(large_chunk)
73
+
74
+ error = exc_info.value
75
+ assert "exceeds maximum" in str(error)
76
+
77
+
78
+ class TestValidationStreamExtractor:
79
+ """Tests for ValidationStreamExtractor."""
80
+
81
+ def test_level0_immediate_streaming(self) -> None:
82
+ """Test level 0 streams content immediately."""
83
+ chunks_received: list[tuple[str, str | None]] = []
84
+
85
+ config = ValidationStreamExtractorConfig(
86
+ level=0,
87
+ schema=[SchemaRow("text", "Response text")],
88
+ stream_fields=["text"],
89
+ expected_codes={},
90
+ on_chunk=lambda chunk, field: chunks_received.append((chunk, field)),
91
+ )
92
+
93
+ extractor = ValidationStreamExtractor(config)
94
+ assert extractor.done is False
95
+ assert extractor.get_state() == ExtractorState.STREAMING
96
+
97
+ # Push XML content
98
+ extractor.push("<text>Hello ")
99
+ extractor.push("World</text>")
100
+
101
+ # At level 0, content should be emitted immediately
102
+ assert len(chunks_received) > 0
103
+
104
+ # Flush to complete
105
+ extractor.flush()
106
+ assert extractor.done is True
107
+ assert extractor.get_state() == ExtractorState.COMPLETE
108
+
109
+ def test_diagnosis(self) -> None:
110
+ """Test diagnosis of extraction state."""
111
+ config = ValidationStreamExtractorConfig(
112
+ level=1,
113
+ schema=[
114
+ SchemaRow("field1", "First field"),
115
+ SchemaRow("field2", "Second field"),
116
+ ],
117
+ stream_fields=["field1", "field2"],
118
+ expected_codes={},
119
+ on_chunk=lambda _c, _f: None,
120
+ )
121
+
122
+ extractor = ValidationStreamExtractor(config)
123
+ extractor.push("<field1>content</field1>") # Only field1 is complete
124
+
125
+ diagnosis = extractor.diagnose()
126
+ # field2 should be either missing or incomplete
127
+ assert "field2" in diagnosis.missing_fields or "field2" in diagnosis.incomplete_fields
128
+
129
+ def test_signal_retry(self) -> None:
130
+ """Test signaling a retry attempt."""
131
+ retry_separator_received = [False]
132
+
133
+ def on_chunk(chunk: str, field: str | None) -> None:
134
+ if "let me start again" in chunk:
135
+ retry_separator_received[0] = True
136
+
137
+ config = ValidationStreamExtractorConfig(
138
+ level=0,
139
+ schema=[SchemaRow("text", "Response")],
140
+ stream_fields=["text"],
141
+ expected_codes={},
142
+ on_chunk=on_chunk,
143
+ )
144
+
145
+ extractor = ValidationStreamExtractor(config)
146
+ result = extractor.signal_retry(1)
147
+
148
+ assert result["validated_fields"] == [] # No validated fields yet
149
+ assert extractor.get_state() == ExtractorState.RETRYING
150
+ assert retry_separator_received[0] is True # Separator was emitted
151
+
152
+ def test_signal_error(self) -> None:
153
+ """Test signaling an error."""
154
+ config = ValidationStreamExtractorConfig(
155
+ level=0,
156
+ schema=[SchemaRow("text", "Response")],
157
+ stream_fields=["text"],
158
+ expected_codes={},
159
+ on_chunk=lambda _c, _f: None,
160
+ )
161
+
162
+ extractor = ValidationStreamExtractor(config)
163
+ extractor.signal_error("Test error")
164
+
165
+ assert extractor.done is True
166
+ assert extractor.get_state() == ExtractorState.FAILED
167
+
168
+ def test_abort_signal(self) -> None:
169
+ """Test abort signal handling."""
170
+ aborted = [False]
171
+
172
+ config = ValidationStreamExtractorConfig(
173
+ level=0,
174
+ schema=[SchemaRow("text", "Response")],
175
+ stream_fields=["text"],
176
+ expected_codes={},
177
+ on_chunk=lambda _c, _f: None,
178
+ abort_signal=lambda: aborted[0],
179
+ )
180
+
181
+ extractor = ValidationStreamExtractor(config)
182
+
183
+ # Push before abort
184
+ extractor.push("<text>Hello")
185
+ assert extractor.done is False
186
+
187
+ # Set abort signal
188
+ aborted[0] = True
189
+
190
+ # Push after abort - should transition to failed
191
+ extractor.push(" World</text>")
192
+ assert extractor.done is True
193
+ assert extractor.get_state() == ExtractorState.FAILED
194
+
195
+ def test_rich_consumer_events(self) -> None:
196
+ """Test rich event emission to consumers."""
197
+ events_received: list[StreamEvent] = []
198
+
199
+ config = ValidationStreamExtractorConfig(
200
+ level=0,
201
+ schema=[SchemaRow("text", "Response")],
202
+ stream_fields=["text"],
203
+ expected_codes={},
204
+ on_chunk=lambda _c, _f: None,
205
+ on_event=lambda event: events_received.append(event),
206
+ has_rich_consumer=True,
207
+ )
208
+
209
+ extractor = ValidationStreamExtractor(config)
210
+ extractor.push("<text>Hello</text>")
211
+ extractor.flush()
212
+
213
+ # Should have received chunk and complete events
214
+ event_types = [e.event_type for e in events_received]
215
+ assert StreamEventType.CHUNK in event_types
216
+ assert StreamEventType.COMPLETE in event_types
217
+
218
+ def test_get_validated_fields(self) -> None:
219
+ """Test getting validated fields."""
220
+ config = ValidationStreamExtractorConfig(
221
+ level=0,
222
+ schema=[SchemaRow("text", "Response")],
223
+ stream_fields=["text"],
224
+ expected_codes={},
225
+ on_chunk=lambda _c, _f: None,
226
+ )
227
+
228
+ extractor = ValidationStreamExtractor(config)
229
+ extractor.push("<text>Content</text>")
230
+
231
+ # At level 0 without validation codes, fields aren't in validated_fields
232
+ # They're just emitted directly
233
+ validated = extractor.get_validated_fields()
234
+ # The implementation may or may not add to validated_fields at level 0
235
+ # Just verify the method works
236
+ assert isinstance(validated, dict)
237
+
238
+
239
+ class TestEnumValues:
240
+ """Tests for enum value consistency."""
241
+
242
+ def test_extractor_state_values(self) -> None:
243
+ """Test ExtractorState values match TypeScript/Rust."""
244
+ assert ExtractorState.STREAMING == "streaming"
245
+ assert ExtractorState.VALIDATING == "validating"
246
+ assert ExtractorState.RETRYING == "retrying"
247
+ assert ExtractorState.COMPLETE == "complete"
248
+ assert ExtractorState.FAILED == "failed"
249
+
250
+ def test_field_state_values(self) -> None:
251
+ """Test FieldState values match TypeScript/Rust."""
252
+ assert FieldState.PENDING == "pending"
253
+ assert FieldState.PARTIAL == "partial"
254
+ assert FieldState.COMPLETE == "complete"
255
+ assert FieldState.INVALID == "invalid"
256
+
257
+
258
+ class TestCrossLanguageParity:
259
+ """Tests for cross-language parity."""
260
+
261
+ def test_validation_stream_extractor_config_fields(self) -> None:
262
+ """Test ValidationStreamExtractorConfig has same fields as TypeScript."""
263
+ # These fields must match TypeScript ValidationStreamExtractorConfig
264
+
265
+ # Check that config accepts all fields
266
+ config = ValidationStreamExtractorConfig(
267
+ level=0,
268
+ schema=[],
269
+ stream_fields=[],
270
+ expected_codes={},
271
+ on_chunk=lambda _c, _f: None,
272
+ on_event=lambda _e: None,
273
+ abort_signal=lambda: False,
274
+ has_rich_consumer=True,
275
+ )
276
+
277
+ # If we get here, all fields are accepted
278
+ assert config.level == 0
279
+ assert config.has_rich_consumer is True
280
+
281
+ def test_validation_diagnosis_fields(self) -> None:
282
+ """Test ValidationDiagnosis has same fields as TypeScript."""
283
+ diagnosis = ValidationDiagnosis(
284
+ missing_fields=["a"],
285
+ invalid_fields=["b"],
286
+ incomplete_fields=["c"],
287
+ )
288
+
289
+ assert diagnosis.missing_fields == ["a"]
290
+ assert diagnosis.invalid_fields == ["b"]
291
+ assert diagnosis.incomplete_fields == ["c"]
292
+
293
+ def test_max_chunk_size_matches(self) -> None:
294
+ """Test MAX_CHUNK_SIZE matches TypeScript (1MB)."""
295
+ assert MAX_CHUNK_SIZE == 1024 * 1024
@@ -0,0 +1,221 @@
1
+ import pytest
2
+ from google.protobuf import struct_pb2
3
+
4
+ from elizaos.types import (
5
+ DEFAULT_UUID,
6
+ Character,
7
+ Content,
8
+ Entity,
9
+ Memory,
10
+ Plugin,
11
+ Room,
12
+ State,
13
+ Task,
14
+ TaskStatus,
15
+ World,
16
+ as_uuid,
17
+ )
18
+
19
+
20
+ class TestUUID:
21
+ def test_valid_uuid(self) -> None:
22
+ valid_uuid = "12345678-1234-1234-1234-123456789012"
23
+ result = as_uuid(valid_uuid)
24
+ assert result == valid_uuid
25
+
26
+ def test_invalid_uuid_format(self) -> None:
27
+ with pytest.raises(ValueError, match="Invalid UUID format"):
28
+ as_uuid("not-a-uuid")
29
+
30
+ def test_empty_uuid(self) -> None:
31
+ with pytest.raises(ValueError, match="Invalid UUID format"):
32
+ as_uuid("")
33
+
34
+ def test_uuid_case_insensitive(self) -> None:
35
+ lower = "12345678-1234-1234-1234-123456789012"
36
+ upper = "12345678-1234-1234-1234-123456789012".upper()
37
+ assert as_uuid(lower) == lower
38
+ assert as_uuid(upper) == upper
39
+
40
+ def test_default_uuid(self) -> None:
41
+ assert DEFAULT_UUID == "00000000-0000-0000-0000-000000000000"
42
+ assert as_uuid(DEFAULT_UUID) == DEFAULT_UUID
43
+
44
+ def test_default_uuid_can_be_used_in_memory(self) -> None:
45
+ memory = Memory(
46
+ entity_id=as_uuid("12345678-1234-1234-1234-123456789012"),
47
+ room_id=DEFAULT_UUID,
48
+ content=Content(text="Hello"),
49
+ )
50
+ assert memory.room_id == DEFAULT_UUID
51
+
52
+
53
+ class TestContent:
54
+ def test_minimal_content(self) -> None:
55
+ content = Content(text="Hello world")
56
+ assert content.text == "Hello world"
57
+ assert content.thought == "" # Protobuf defaults optional string to ""
58
+ assert list(content.actions) == []
59
+
60
+ def test_full_content(self) -> None:
61
+ content = Content(
62
+ text="Hello world",
63
+ thought="Thinking about response",
64
+ actions=["RESPOND", "SEARCH"],
65
+ providers=["KNOWLEDGE"],
66
+ source="cli",
67
+ target="user",
68
+ )
69
+ assert content.text == "Hello world"
70
+ assert content.thought == "Thinking about response"
71
+ assert content.actions == ["RESPOND", "SEARCH"]
72
+ assert content.providers == ["KNOWLEDGE"]
73
+
74
+
75
+ class TestMemory:
76
+ def test_minimal_memory(self) -> None:
77
+ memory = Memory(
78
+ entity_id=as_uuid("12345678-1234-1234-1234-123456789012"),
79
+ room_id=as_uuid("12345678-1234-1234-1234-123456789013"),
80
+ content=Content(text="Hello"),
81
+ )
82
+ assert memory.entity_id == "12345678-1234-1234-1234-123456789012"
83
+ assert memory.content.text == "Hello"
84
+
85
+ def test_memory_with_embedding(self) -> None:
86
+ embedding = [0.1, 0.2, 0.3, 0.4, 0.5]
87
+ memory = Memory(
88
+ entity_id=as_uuid("12345678-1234-1234-1234-123456789012"),
89
+ room_id=as_uuid("12345678-1234-1234-1234-123456789013"),
90
+ content=Content(text="Hello"),
91
+ embedding=embedding,
92
+ )
93
+ # Use approximate comparison due to float32 precision in protobuf
94
+ assert list(memory.embedding) == pytest.approx(embedding, rel=1e-6)
95
+
96
+
97
+ class TestCharacter:
98
+ def test_minimal_character(self) -> None:
99
+ character = Character(
100
+ name="TestAgent",
101
+ bio=["A test agent for testing."],
102
+ )
103
+ assert character.name == "TestAgent"
104
+ assert character.bio == ["A test agent for testing."]
105
+
106
+ def test_full_character(self) -> None:
107
+ character = Character(
108
+ name="TestAgent",
109
+ username="testagent",
110
+ bio=["Line 1", "Line 2"],
111
+ system="You are a test agent.",
112
+ topics=["testing", "automation"],
113
+ adjectives=["helpful", "precise"],
114
+ plugins=["@elizaos/plugin-sql"],
115
+ settings=None,
116
+ secrets={"API_KEY": "secret"},
117
+ )
118
+ assert character.name == "TestAgent"
119
+ assert character.username == "testagent"
120
+ assert character.bio == ["Line 1", "Line 2"]
121
+ assert character.topics == ["testing", "automation"]
122
+
123
+
124
+ class TestEntity:
125
+ def test_minimal_entity(self) -> None:
126
+ entity = Entity(
127
+ names=["TestUser"],
128
+ agent_id=as_uuid("12345678-1234-1234-1234-123456789012"),
129
+ )
130
+ assert entity.names == ["TestUser"]
131
+ assert entity.metadata is not None
132
+
133
+ def test_entity_with_metadata(self) -> None:
134
+ metadata = struct_pb2.Struct()
135
+ metadata.update({"email": "test@example.com"})
136
+ entity = Entity(
137
+ names=["TestUser"],
138
+ agent_id=as_uuid("12345678-1234-1234-1234-123456789012"),
139
+ metadata=metadata,
140
+ )
141
+ assert entity.metadata is not None
142
+
143
+
144
+ class TestRoom:
145
+ def test_room_creation(self) -> None:
146
+ room = Room(
147
+ id=as_uuid("12345678-1234-1234-1234-123456789012"),
148
+ source="cli",
149
+ type="DM",
150
+ )
151
+ assert room.type == "DM"
152
+ assert room.source == "cli"
153
+
154
+ def test_room_with_world(self) -> None:
155
+ room = Room(
156
+ id=as_uuid("12345678-1234-1234-1234-123456789012"),
157
+ source="discord",
158
+ type="GROUP",
159
+ world_id=as_uuid("12345678-1234-1234-1234-123456789013"),
160
+ name="general",
161
+ )
162
+ assert room.name == "general"
163
+ assert room.world_id == "12345678-1234-1234-1234-123456789013"
164
+
165
+
166
+ class TestWorld:
167
+ def test_world_creation(self) -> None:
168
+ world = World(
169
+ id=as_uuid("12345678-1234-1234-1234-123456789012"),
170
+ agent_id=as_uuid("12345678-1234-1234-1234-123456789013"),
171
+ name="Test World",
172
+ )
173
+ assert world.name == "Test World"
174
+
175
+
176
+ class TestPlugin:
177
+ def test_minimal_plugin(self) -> None:
178
+ plugin = Plugin(
179
+ name="test-plugin",
180
+ description="A test plugin",
181
+ )
182
+ assert plugin.name == "test-plugin"
183
+ assert plugin.description == "A test plugin"
184
+
185
+ def test_plugin_with_dependencies(self) -> None:
186
+ plugin = Plugin(
187
+ name="test-plugin",
188
+ description="A test plugin",
189
+ dependencies=["core-plugin", "util-plugin"],
190
+ )
191
+ assert plugin.dependencies == ["core-plugin", "util-plugin"]
192
+
193
+
194
+ class TestState:
195
+ def test_empty_state(self) -> None:
196
+ state = State()
197
+ assert state.values is not None
198
+ assert state.text == ""
199
+
200
+ def test_state_with_data(self) -> None:
201
+ state = State(text="State context")
202
+ assert state.text == "State context"
203
+
204
+
205
+ class TestTask:
206
+ def test_task_creation(self) -> None:
207
+ task = Task(
208
+ name="test-task",
209
+ description="A test task",
210
+ )
211
+ assert task.name == "test-task"
212
+ assert task.status == TaskStatus.TASK_STATUS_UNSPECIFIED
213
+
214
+ def test_task_with_metadata(self) -> None:
215
+ task = Task(
216
+ name="scheduled-task",
217
+ status=TaskStatus.TASK_STATUS_IN_PROGRESS,
218
+ tags=["important", "daily"],
219
+ )
220
+ assert task.status == TaskStatus.TASK_STATUS_IN_PROGRESS
221
+ assert "important" in (task.tags or [])
@@ -0,0 +1,46 @@
1
+ import uuid as uuid_module
2
+
3
+ import pytest
4
+
5
+ from elizaos.types import string_to_uuid
6
+
7
+
8
+ class TestStringToUuidParity:
9
+ def test_deterministic_vectors_match_typescript(self) -> None:
10
+ vectors = [
11
+ ("test", "a94a8fe5-ccb1-0ba6-9c4c-0873d391e987"),
12
+ ("hello world", "f0355dd5-2823-054c-ae66-a0b12842c215"),
13
+ ("", "da39a3ee-5e6b-0b0d-b255-bfef95601890"),
14
+ ("123", "40bd0015-6308-0fc3-9165-329ea1ff5c5e"),
15
+ ("user:agent", "a49810ce-da30-0d3b-97ee-d4d47774d8af"),
16
+ ]
17
+
18
+ for input_str, expected in vectors:
19
+ assert string_to_uuid(input_str) == expected
20
+
21
+ def test_returns_existing_uuid_unchanged(self) -> None:
22
+ existing = "550e8400-e29b-41d4-a716-446655440000"
23
+ assert string_to_uuid(existing) == existing
24
+
25
+ def test_number_inputs(self) -> None:
26
+ assert string_to_uuid(42) == string_to_uuid("42")
27
+ assert string_to_uuid(0) == string_to_uuid("0")
28
+ assert string_to_uuid(-1) == string_to_uuid("-1")
29
+
30
+ def test_invalid_inputs_raise_type_error(self) -> None:
31
+ with pytest.raises(TypeError):
32
+ string_to_uuid(None) # type: ignore[arg-type]
33
+ with pytest.raises(TypeError):
34
+ string_to_uuid({"x": 1}) # type: ignore[arg-type]
35
+
36
+ def test_sets_variant_and_version_bits(self) -> None:
37
+ uuid_str = string_to_uuid("test")
38
+ parts = uuid_str.split("-")
39
+ assert len(parts) == 5
40
+
41
+ variant_byte = int(parts[3][0:2], 16)
42
+ assert (variant_byte & 0xC0) == 0x80
43
+
44
+ assert parts[2][0] == "0"
45
+
46
+ uuid_module.UUID(uuid_str)