@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.
- package/LICENSE +26 -0
- package/README.md +239 -0
- package/elizaos/__init__.py +280 -0
- package/elizaos/action_docs.py +149 -0
- package/elizaos/advanced_capabilities/__init__.py +85 -0
- package/elizaos/advanced_capabilities/actions/__init__.py +54 -0
- package/elizaos/advanced_capabilities/actions/add_contact.py +139 -0
- package/elizaos/advanced_capabilities/actions/follow_room.py +151 -0
- package/elizaos/advanced_capabilities/actions/image_generation.py +148 -0
- package/elizaos/advanced_capabilities/actions/mute_room.py +164 -0
- package/elizaos/advanced_capabilities/actions/remove_contact.py +145 -0
- package/elizaos/advanced_capabilities/actions/roles.py +207 -0
- package/elizaos/advanced_capabilities/actions/schedule_follow_up.py +154 -0
- package/elizaos/advanced_capabilities/actions/search_contacts.py +145 -0
- package/elizaos/advanced_capabilities/actions/send_message.py +187 -0
- package/elizaos/advanced_capabilities/actions/settings.py +151 -0
- package/elizaos/advanced_capabilities/actions/unfollow_room.py +164 -0
- package/elizaos/advanced_capabilities/actions/unmute_room.py +164 -0
- package/elizaos/advanced_capabilities/actions/update_contact.py +164 -0
- package/elizaos/advanced_capabilities/actions/update_entity.py +161 -0
- package/elizaos/advanced_capabilities/evaluators/__init__.py +18 -0
- package/elizaos/advanced_capabilities/evaluators/reflection.py +134 -0
- package/elizaos/advanced_capabilities/evaluators/relationship_extraction.py +203 -0
- package/elizaos/advanced_capabilities/providers/__init__.py +36 -0
- package/elizaos/advanced_capabilities/providers/agent_settings.py +60 -0
- package/elizaos/advanced_capabilities/providers/contacts.py +77 -0
- package/elizaos/advanced_capabilities/providers/facts.py +82 -0
- package/elizaos/advanced_capabilities/providers/follow_ups.py +113 -0
- package/elizaos/advanced_capabilities/providers/knowledge.py +83 -0
- package/elizaos/advanced_capabilities/providers/relationships.py +112 -0
- package/elizaos/advanced_capabilities/providers/roles.py +97 -0
- package/elizaos/advanced_capabilities/providers/settings.py +51 -0
- package/elizaos/advanced_capabilities/services/__init__.py +18 -0
- package/elizaos/advanced_capabilities/services/follow_up.py +138 -0
- package/elizaos/advanced_capabilities/services/rolodex.py +244 -0
- package/elizaos/advanced_memory/__init__.py +3 -0
- package/elizaos/advanced_memory/evaluators.py +97 -0
- package/elizaos/advanced_memory/memory_service.py +556 -0
- package/elizaos/advanced_memory/plugin.py +30 -0
- package/elizaos/advanced_memory/prompts.py +12 -0
- package/elizaos/advanced_memory/providers.py +90 -0
- package/elizaos/advanced_memory/types.py +65 -0
- package/elizaos/advanced_planning/__init__.py +10 -0
- package/elizaos/advanced_planning/actions.py +145 -0
- package/elizaos/advanced_planning/message_classifier.py +127 -0
- package/elizaos/advanced_planning/planning_service.py +712 -0
- package/elizaos/advanced_planning/plugin.py +40 -0
- package/elizaos/advanced_planning/prompts.py +4 -0
- package/elizaos/basic_capabilities/__init__.py +66 -0
- package/elizaos/basic_capabilities/actions/__init__.py +24 -0
- package/elizaos/basic_capabilities/actions/choice.py +140 -0
- package/elizaos/basic_capabilities/actions/ignore.py +66 -0
- package/elizaos/basic_capabilities/actions/none.py +56 -0
- package/elizaos/basic_capabilities/actions/reply.py +120 -0
- package/elizaos/basic_capabilities/providers/__init__.py +54 -0
- package/elizaos/basic_capabilities/providers/action_state.py +113 -0
- package/elizaos/basic_capabilities/providers/actions.py +263 -0
- package/elizaos/basic_capabilities/providers/attachments.py +76 -0
- package/elizaos/basic_capabilities/providers/capabilities.py +62 -0
- package/elizaos/basic_capabilities/providers/character.py +113 -0
- package/elizaos/basic_capabilities/providers/choice.py +73 -0
- package/elizaos/basic_capabilities/providers/context_bench.py +44 -0
- package/elizaos/basic_capabilities/providers/current_time.py +58 -0
- package/elizaos/basic_capabilities/providers/entities.py +99 -0
- package/elizaos/basic_capabilities/providers/evaluators.py +54 -0
- package/elizaos/basic_capabilities/providers/providers_list.py +55 -0
- package/elizaos/basic_capabilities/providers/recent_messages.py +85 -0
- package/elizaos/basic_capabilities/providers/time.py +45 -0
- package/elizaos/basic_capabilities/providers/world.py +93 -0
- package/elizaos/basic_capabilities/services/__init__.py +18 -0
- package/elizaos/basic_capabilities/services/embedding.py +122 -0
- package/elizaos/basic_capabilities/services/task.py +178 -0
- package/elizaos/bootstrap/__init__.py +12 -0
- package/elizaos/bootstrap/actions/__init__.py +68 -0
- package/elizaos/bootstrap/actions/add_contact.py +149 -0
- package/elizaos/bootstrap/actions/choice.py +147 -0
- package/elizaos/bootstrap/actions/follow_room.py +151 -0
- package/elizaos/bootstrap/actions/ignore.py +80 -0
- package/elizaos/bootstrap/actions/image_generation.py +135 -0
- package/elizaos/bootstrap/actions/mute_room.py +151 -0
- package/elizaos/bootstrap/actions/none.py +71 -0
- package/elizaos/bootstrap/actions/remove_contact.py +159 -0
- package/elizaos/bootstrap/actions/reply.py +140 -0
- package/elizaos/bootstrap/actions/roles.py +193 -0
- package/elizaos/bootstrap/actions/schedule_follow_up.py +164 -0
- package/elizaos/bootstrap/actions/search_contacts.py +159 -0
- package/elizaos/bootstrap/actions/send_message.py +173 -0
- package/elizaos/bootstrap/actions/settings.py +165 -0
- package/elizaos/bootstrap/actions/unfollow_room.py +151 -0
- package/elizaos/bootstrap/actions/unmute_room.py +151 -0
- package/elizaos/bootstrap/actions/update_contact.py +178 -0
- package/elizaos/bootstrap/actions/update_entity.py +175 -0
- package/elizaos/bootstrap/autonomy/__init__.py +18 -0
- package/elizaos/bootstrap/autonomy/action.py +197 -0
- package/elizaos/bootstrap/autonomy/providers.py +165 -0
- package/elizaos/bootstrap/autonomy/routes.py +171 -0
- package/elizaos/bootstrap/autonomy/service.py +562 -0
- package/elizaos/bootstrap/autonomy/types.py +18 -0
- package/elizaos/bootstrap/evaluators/__init__.py +19 -0
- package/elizaos/bootstrap/evaluators/reflection.py +118 -0
- package/elizaos/bootstrap/evaluators/relationship_extraction.py +192 -0
- package/elizaos/bootstrap/plugin.py +140 -0
- package/elizaos/bootstrap/providers/__init__.py +80 -0
- package/elizaos/bootstrap/providers/action_state.py +71 -0
- package/elizaos/bootstrap/providers/actions.py +256 -0
- package/elizaos/bootstrap/providers/agent_settings.py +63 -0
- package/elizaos/bootstrap/providers/attachments.py +76 -0
- package/elizaos/bootstrap/providers/capabilities.py +66 -0
- package/elizaos/bootstrap/providers/character.py +128 -0
- package/elizaos/bootstrap/providers/choice.py +77 -0
- package/elizaos/bootstrap/providers/contacts.py +78 -0
- package/elizaos/bootstrap/providers/context_bench.py +49 -0
- package/elizaos/bootstrap/providers/current_time.py +56 -0
- package/elizaos/bootstrap/providers/entities.py +99 -0
- package/elizaos/bootstrap/providers/evaluators.py +58 -0
- package/elizaos/bootstrap/providers/facts.py +86 -0
- package/elizaos/bootstrap/providers/follow_ups.py +116 -0
- package/elizaos/bootstrap/providers/knowledge.py +73 -0
- package/elizaos/bootstrap/providers/providers_list.py +59 -0
- package/elizaos/bootstrap/providers/recent_messages.py +85 -0
- package/elizaos/bootstrap/providers/relationships.py +106 -0
- package/elizaos/bootstrap/providers/roles.py +95 -0
- package/elizaos/bootstrap/providers/settings.py +55 -0
- package/elizaos/bootstrap/providers/time.py +45 -0
- package/elizaos/bootstrap/providers/world.py +97 -0
- package/elizaos/bootstrap/services/__init__.py +26 -0
- package/elizaos/bootstrap/services/embedding.py +122 -0
- package/elizaos/bootstrap/services/follow_up.py +138 -0
- package/elizaos/bootstrap/services/rolodex.py +244 -0
- package/elizaos/bootstrap/services/task.py +585 -0
- package/elizaos/bootstrap/types.py +54 -0
- package/elizaos/bootstrap/utils/__init__.py +7 -0
- package/elizaos/bootstrap/utils/xml.py +69 -0
- package/elizaos/character.py +149 -0
- package/elizaos/logger.py +179 -0
- package/elizaos/media/__init__.py +45 -0
- package/elizaos/media/mime.py +315 -0
- package/elizaos/media/search.py +161 -0
- package/elizaos/media/tests/__init__.py +1 -0
- package/elizaos/media/tests/test_mime.py +117 -0
- package/elizaos/media/tests/test_search.py +156 -0
- package/elizaos/plugin.py +191 -0
- package/elizaos/prompts.py +1071 -0
- package/elizaos/py.typed +0 -0
- package/elizaos/runtime.py +2572 -0
- package/elizaos/services/__init__.py +49 -0
- package/elizaos/services/hook_service.py +511 -0
- package/elizaos/services/message_service.py +1248 -0
- package/elizaos/settings.py +182 -0
- package/elizaos/streaming_context.py +159 -0
- package/elizaos/trajectory_context.py +18 -0
- package/elizaos/types/__init__.py +512 -0
- package/elizaos/types/agent.py +31 -0
- package/elizaos/types/components.py +208 -0
- package/elizaos/types/database.py +64 -0
- package/elizaos/types/environment.py +46 -0
- package/elizaos/types/events.py +47 -0
- package/elizaos/types/memory.py +45 -0
- package/elizaos/types/model.py +393 -0
- package/elizaos/types/plugin.py +188 -0
- package/elizaos/types/primitives.py +100 -0
- package/elizaos/types/runtime.py +460 -0
- package/elizaos/types/service.py +113 -0
- package/elizaos/types/service_interfaces.py +244 -0
- package/elizaos/types/state.py +188 -0
- package/elizaos/types/task.py +29 -0
- package/elizaos/utils/__init__.py +108 -0
- package/elizaos/utils/spec_examples.py +48 -0
- package/elizaos/utils/streaming.py +426 -0
- package/elizaos_atropos_shared/__init__.py +1 -0
- package/elizaos_atropos_shared/canonical_eliza.py +282 -0
- package/package.json +19 -0
- package/pyproject.toml +143 -0
- package/requirements-dev.in +11 -0
- package/requirements-dev.lock +134 -0
- package/requirements.in +9 -0
- package/requirements.lock +64 -0
- package/tests/__init__.py +0 -0
- package/tests/test_action_parameters.py +154 -0
- package/tests/test_actions_provider_examples.py +39 -0
- package/tests/test_advanced_memory_behavior.py +96 -0
- package/tests/test_advanced_memory_flag.py +30 -0
- package/tests/test_advanced_planning_behavior.py +225 -0
- package/tests/test_advanced_planning_flag.py +26 -0
- package/tests/test_autonomy.py +445 -0
- package/tests/test_bootstrap_initialize.py +37 -0
- package/tests/test_character.py +163 -0
- package/tests/test_character_provider.py +231 -0
- package/tests/test_dynamic_prompt_exec.py +561 -0
- package/tests/test_logger_redaction.py +43 -0
- package/tests/test_plugin.py +117 -0
- package/tests/test_runtime.py +422 -0
- package/tests/test_salt_production_enforcement.py +22 -0
- package/tests/test_settings_crypto.py +118 -0
- package/tests/test_streaming.py +295 -0
- package/tests/test_types.py +221 -0
- 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)
|