@elizaos/python 2.0.0-alpha.10 → 2.0.0-alpha.11

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 (99) hide show
  1. package/elizaos/__init__.py +0 -1
  2. package/elizaos/advanced_capabilities/actions/schedule_follow_up.py +70 -15
  3. package/elizaos/advanced_capabilities/actions/send_message.py +9 -184
  4. package/elizaos/advanced_memory/memory_service.py +15 -17
  5. package/elizaos/advanced_planning/planning_service.py +26 -14
  6. package/elizaos/basic_capabilities/providers/actions.py +118 -29
  7. package/elizaos/basic_capabilities/providers/character.py +19 -21
  8. package/elizaos/basic_capabilities/providers/current_time.py +7 -4
  9. package/elizaos/basic_capabilities/providers/time.py +7 -4
  10. package/elizaos/bootstrap/__init__.py +21 -2
  11. package/elizaos/bootstrap/actions/schedule_follow_up.py +65 -7
  12. package/elizaos/bootstrap/actions/send_message.py +162 -15
  13. package/elizaos/bootstrap/autonomy/service.py +230 -28
  14. package/elizaos/bootstrap/providers/actions.py +118 -27
  15. package/elizaos/bootstrap/providers/agent_settings.py +1 -0
  16. package/elizaos/bootstrap/providers/attachments.py +1 -0
  17. package/elizaos/bootstrap/providers/capabilities.py +1 -0
  18. package/elizaos/bootstrap/providers/character.py +1 -0
  19. package/elizaos/bootstrap/providers/choice.py +1 -0
  20. package/elizaos/bootstrap/providers/contacts.py +1 -0
  21. package/elizaos/bootstrap/providers/current_time.py +8 -2
  22. package/elizaos/bootstrap/providers/entities.py +1 -0
  23. package/elizaos/bootstrap/providers/evaluators.py +1 -0
  24. package/elizaos/bootstrap/providers/facts.py +1 -0
  25. package/elizaos/bootstrap/providers/follow_ups.py +1 -0
  26. package/elizaos/bootstrap/providers/knowledge.py +1 -0
  27. package/elizaos/bootstrap/providers/providers_list.py +1 -0
  28. package/elizaos/bootstrap/providers/relationships.py +1 -0
  29. package/elizaos/bootstrap/providers/roles.py +1 -0
  30. package/elizaos/bootstrap/providers/settings.py +1 -0
  31. package/elizaos/bootstrap/providers/time.py +8 -4
  32. package/elizaos/bootstrap/providers/world.py +1 -0
  33. package/elizaos/deterministic.py +193 -0
  34. package/elizaos/generated/__init__.py +1 -0
  35. package/elizaos/generated/action_docs.py +3181 -0
  36. package/elizaos/generated/spec_helpers.py +175 -0
  37. package/elizaos/media/mime.py +2 -2
  38. package/elizaos/media/search.py +23 -23
  39. package/elizaos/runtime.py +152 -39
  40. package/elizaos/services/message_service.py +2 -6
  41. package/elizaos/types/components.py +2 -2
  42. package/elizaos/types/generated/__init__.py +12 -0
  43. package/elizaos/types/generated/eliza/v1/agent_pb2.py +63 -0
  44. package/elizaos/types/generated/eliza/v1/agent_pb2.pyi +161 -0
  45. package/elizaos/types/generated/eliza/v1/components_pb2.py +65 -0
  46. package/elizaos/types/generated/eliza/v1/components_pb2.pyi +160 -0
  47. package/elizaos/types/generated/eliza/v1/database_pb2.py +78 -0
  48. package/elizaos/types/generated/eliza/v1/database_pb2.pyi +305 -0
  49. package/elizaos/types/generated/eliza/v1/environment_pb2.py +58 -0
  50. package/elizaos/types/generated/eliza/v1/environment_pb2.pyi +135 -0
  51. package/elizaos/types/generated/eliza/v1/events_pb2.py +82 -0
  52. package/elizaos/types/generated/eliza/v1/events_pb2.pyi +322 -0
  53. package/elizaos/types/generated/eliza/v1/ipc_pb2.py +113 -0
  54. package/elizaos/types/generated/eliza/v1/ipc_pb2.pyi +367 -0
  55. package/elizaos/types/generated/eliza/v1/knowledge_pb2.py +41 -0
  56. package/elizaos/types/generated/eliza/v1/knowledge_pb2.pyi +26 -0
  57. package/elizaos/types/generated/eliza/v1/memory_pb2.py +55 -0
  58. package/elizaos/types/generated/eliza/v1/memory_pb2.pyi +111 -0
  59. package/elizaos/types/generated/eliza/v1/message_service_pb2.py +48 -0
  60. package/elizaos/types/generated/eliza/v1/message_service_pb2.pyi +69 -0
  61. package/elizaos/types/generated/eliza/v1/messaging_pb2.py +51 -0
  62. package/elizaos/types/generated/eliza/v1/messaging_pb2.pyi +97 -0
  63. package/elizaos/types/generated/eliza/v1/model_pb2.py +84 -0
  64. package/elizaos/types/generated/eliza/v1/model_pb2.pyi +280 -0
  65. package/elizaos/types/generated/eliza/v1/payment_pb2.py +44 -0
  66. package/elizaos/types/generated/eliza/v1/payment_pb2.pyi +70 -0
  67. package/elizaos/types/generated/eliza/v1/plugin_pb2.py +68 -0
  68. package/elizaos/types/generated/eliza/v1/plugin_pb2.pyi +145 -0
  69. package/elizaos/types/generated/eliza/v1/primitives_pb2.py +48 -0
  70. package/elizaos/types/generated/eliza/v1/primitives_pb2.pyi +92 -0
  71. package/elizaos/types/generated/eliza/v1/prompts_pb2.py +52 -0
  72. package/elizaos/types/generated/eliza/v1/prompts_pb2.pyi +74 -0
  73. package/elizaos/types/generated/eliza/v1/service_interfaces_pb2.py +211 -0
  74. package/elizaos/types/generated/eliza/v1/service_interfaces_pb2.pyi +1296 -0
  75. package/elizaos/types/generated/eliza/v1/service_pb2.py +42 -0
  76. package/elizaos/types/generated/eliza/v1/service_pb2.pyi +69 -0
  77. package/elizaos/types/generated/eliza/v1/settings_pb2.py +58 -0
  78. package/elizaos/types/generated/eliza/v1/settings_pb2.pyi +85 -0
  79. package/elizaos/types/generated/eliza/v1/state_pb2.py +60 -0
  80. package/elizaos/types/generated/eliza/v1/state_pb2.pyi +114 -0
  81. package/elizaos/types/generated/eliza/v1/task_pb2.py +42 -0
  82. package/elizaos/types/generated/eliza/v1/task_pb2.pyi +58 -0
  83. package/elizaos/types/generated/eliza/v1/tee_pb2.py +52 -0
  84. package/elizaos/types/generated/eliza/v1/tee_pb2.pyi +90 -0
  85. package/elizaos/types/generated/eliza/v1/testing_pb2.py +39 -0
  86. package/elizaos/types/generated/eliza/v1/testing_pb2.pyi +23 -0
  87. package/elizaos/types/model.py +3 -0
  88. package/elizaos/types/runtime.py +1 -1
  89. package/package.json +3 -2
  90. package/tests/test_action_parameters.py +2 -3
  91. package/tests/test_advanced_memory_behavior.py +0 -2
  92. package/tests/test_advanced_memory_flag.py +0 -2
  93. package/tests/test_advanced_planning_behavior.py +11 -5
  94. package/tests/test_autonomy.py +11 -1
  95. package/tests/test_runtime.py +8 -17
  96. package/tests/test_schedule_follow_up_action.py +260 -0
  97. package/tests/test_send_message_action_targets.py +114 -0
  98. package/tests/test_settings_crypto.py +0 -2
  99. package/uv.lock +1565 -0
@@ -0,0 +1,260 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from types import SimpleNamespace
5
+ from typing import Protocol
6
+
7
+ import pytest
8
+
9
+ from elizaos.advanced_capabilities.actions.schedule_follow_up import (
10
+ schedule_follow_up_action as advanced_schedule_follow_up_action,
11
+ )
12
+ from elizaos.bootstrap.actions.schedule_follow_up import (
13
+ schedule_follow_up_action as bootstrap_schedule_follow_up_action,
14
+ )
15
+ from elizaos.bootstrap.services.follow_up import FollowUpService
16
+ from elizaos.bootstrap.services.rolodex import RolodexService
17
+ from elizaos.types import Content, Memory, as_uuid
18
+
19
+
20
+ class RolodexLike(Protocol):
21
+ async def search_contacts(
22
+ self,
23
+ categories: list[str] | None = None,
24
+ tags: list[str] | None = None,
25
+ search_term: str | None = None,
26
+ ) -> list[SimpleNamespace]: ...
27
+
28
+ async def get_contact(self, entity_id: object) -> SimpleNamespace | None: ...
29
+
30
+
31
+ class ActionLike(Protocol):
32
+ async def handler(
33
+ self,
34
+ runtime: object,
35
+ message: Memory,
36
+ state: object | None,
37
+ options: object | None,
38
+ callback: object | None,
39
+ responses: list[Memory] | None,
40
+ ) -> object: ...
41
+
42
+
43
+ @dataclass
44
+ class FakeFollowUpCall:
45
+ entity_id: object
46
+ priority: str
47
+
48
+
49
+ class FakeFollowUpService(FollowUpService):
50
+ def __init__(self) -> None:
51
+ self.calls: list[FakeFollowUpCall] = []
52
+
53
+ async def schedule_follow_up(
54
+ self,
55
+ entity_id: object,
56
+ scheduled_at: object,
57
+ reason: str,
58
+ priority: str = "medium",
59
+ message: str | None = None,
60
+ ) -> SimpleNamespace:
61
+ self.calls.append(FakeFollowUpCall(entity_id=entity_id, priority=priority))
62
+ return SimpleNamespace(
63
+ entity_id=entity_id,
64
+ scheduled_at=str(scheduled_at),
65
+ reason=reason,
66
+ priority=priority,
67
+ message=message,
68
+ )
69
+
70
+
71
+ class FakeRolodexService(RolodexService):
72
+ def __init__(self, contacts: list[SimpleNamespace]) -> None:
73
+ self._contacts = contacts
74
+
75
+ async def search_contacts(
76
+ self,
77
+ categories: list[str] | None = None,
78
+ tags: list[str] | None = None,
79
+ search_term: str | None = None,
80
+ ) -> list[SimpleNamespace]:
81
+ if not search_term:
82
+ return self._contacts
83
+ lowered = search_term.lower()
84
+ return [
85
+ contact
86
+ for contact in self._contacts
87
+ if lowered in str(contact.entity_id).lower()
88
+ or lowered in str(getattr(contact, "name", "")).lower()
89
+ ]
90
+
91
+ async def get_contact(self, entity_id: object) -> SimpleNamespace | None:
92
+ for contact in self._contacts:
93
+ if contact.entity_id == entity_id:
94
+ return contact
95
+ return None
96
+
97
+
98
+ class FakeRuntime:
99
+ def __init__(self, rolodex: RolodexLike, follow_up: FakeFollowUpService, response: str) -> None:
100
+ self._rolodex = rolodex
101
+ self._follow_up = follow_up
102
+ self._response = response
103
+
104
+ def get_service(self, name: str) -> object | None:
105
+ if name == "rolodex":
106
+ return self._rolodex
107
+ if name == "follow_up":
108
+ return self._follow_up
109
+ return None
110
+
111
+ async def compose_state(self, _message: Memory, _providers: list[str]) -> SimpleNamespace:
112
+ return SimpleNamespace(values={}, data={}, text="")
113
+
114
+ def get_setting(self, _key: str) -> object | None:
115
+ return None
116
+
117
+ def compose_prompt_from_state(self, state: object, template: str) -> str:
118
+ return f"{template}\n{state}"
119
+
120
+ async def use_model(self, _model_type: object, _params: dict[str, object]) -> str:
121
+ return self._response
122
+
123
+
124
+ @pytest.mark.asyncio
125
+ @pytest.mark.parametrize(
126
+ "action_under_test",
127
+ [bootstrap_schedule_follow_up_action, advanced_schedule_follow_up_action],
128
+ )
129
+ async def test_schedule_follow_up_fails_when_contact_unresolved(
130
+ action_under_test: ActionLike,
131
+ ) -> None:
132
+ follow_up_service = FakeFollowUpService()
133
+ rolodex_service = FakeRolodexService(contacts=[])
134
+ runtime = FakeRuntime(
135
+ rolodex=rolodex_service,
136
+ follow_up=follow_up_service,
137
+ response=(
138
+ "<response>"
139
+ "<contactName>missing-contact</contactName>"
140
+ "<scheduledAt>2026-01-02T12:00:00Z</scheduledAt>"
141
+ "<reason>Check-in</reason>"
142
+ "<priority>high</priority>"
143
+ "</response>"
144
+ ),
145
+ )
146
+
147
+ message = Memory(
148
+ id=as_uuid("70000000-0000-0000-0000-000000000001"),
149
+ room_id=as_uuid("70000000-0000-0000-0000-000000000002"),
150
+ entity_id=None,
151
+ content=Content(text="follow up with missing-contact next week"),
152
+ )
153
+
154
+ result = await action_under_test.handler(
155
+ runtime,
156
+ message,
157
+ None,
158
+ None,
159
+ None,
160
+ None,
161
+ )
162
+
163
+ assert result is not None
164
+ assert result.success is False
165
+ assert len(follow_up_service.calls) == 0
166
+
167
+
168
+ @pytest.mark.asyncio
169
+ @pytest.mark.parametrize(
170
+ "action_under_test",
171
+ [bootstrap_schedule_follow_up_action, advanced_schedule_follow_up_action],
172
+ )
173
+ async def test_schedule_follow_up_normalizes_priority_and_schedules(
174
+ action_under_test: ActionLike,
175
+ ) -> None:
176
+ follow_up_service = FakeFollowUpService()
177
+ contact_id = as_uuid("80000000-0000-0000-0000-000000000001")
178
+ rolodex_service = FakeRolodexService(
179
+ contacts=[SimpleNamespace(entity_id=contact_id, name="known-contact")]
180
+ )
181
+ runtime = FakeRuntime(
182
+ rolodex=rolodex_service,
183
+ follow_up=follow_up_service,
184
+ response=(
185
+ "<response>"
186
+ "<contactName>known-contact</contactName>"
187
+ "<scheduledAt>2026-01-03T09:30:00Z</scheduledAt>"
188
+ "<reason>Status update</reason>"
189
+ "<priority>urgent</priority>"
190
+ "</response>"
191
+ ),
192
+ )
193
+
194
+ message = Memory(
195
+ id=as_uuid("80000000-0000-0000-0000-000000000002"),
196
+ room_id=as_uuid("80000000-0000-0000-0000-000000000003"),
197
+ entity_id=None,
198
+ content=Content(text="schedule follow-up with known-contact"),
199
+ )
200
+
201
+ result = await action_under_test.handler(
202
+ runtime,
203
+ message,
204
+ None,
205
+ None,
206
+ None,
207
+ None,
208
+ )
209
+
210
+ assert result is not None
211
+ assert result.success is True
212
+ assert len(follow_up_service.calls) == 1
213
+ assert follow_up_service.calls[0].priority == "medium"
214
+
215
+
216
+ @pytest.mark.asyncio
217
+ @pytest.mark.parametrize(
218
+ "action_under_test",
219
+ [bootstrap_schedule_follow_up_action, advanced_schedule_follow_up_action],
220
+ )
221
+ async def test_schedule_follow_up_rejects_invalid_scheduled_at(
222
+ action_under_test: ActionLike,
223
+ ) -> None:
224
+ follow_up_service = FakeFollowUpService()
225
+ contact_id = as_uuid("90000000-0000-0000-0000-000000000001")
226
+ rolodex_service = FakeRolodexService(
227
+ contacts=[SimpleNamespace(entity_id=contact_id, name="known-contact")]
228
+ )
229
+ runtime = FakeRuntime(
230
+ rolodex=rolodex_service,
231
+ follow_up=follow_up_service,
232
+ response=(
233
+ "<response>"
234
+ "<contactName>known-contact</contactName>"
235
+ "<scheduledAt>not-a-date</scheduledAt>"
236
+ "<reason>Status update</reason>"
237
+ "<priority>high</priority>"
238
+ "</response>"
239
+ ),
240
+ )
241
+
242
+ message = Memory(
243
+ id=as_uuid("90000000-0000-0000-0000-000000000002"),
244
+ room_id=as_uuid("90000000-0000-0000-0000-000000000003"),
245
+ entity_id=None,
246
+ content=Content(text="schedule follow-up with known-contact"),
247
+ )
248
+
249
+ result = await action_under_test.handler(
250
+ runtime,
251
+ message,
252
+ None,
253
+ None,
254
+ None,
255
+ None,
256
+ )
257
+
258
+ assert result is not None
259
+ assert result.success is False
260
+ assert len(follow_up_service.calls) == 0
@@ -0,0 +1,114 @@
1
+ from __future__ import annotations
2
+
3
+ from types import SimpleNamespace
4
+ from unittest.mock import AsyncMock, MagicMock
5
+
6
+ import pytest
7
+
8
+ from elizaos.advanced_capabilities.actions.send_message import (
9
+ send_message_action as advanced_send_message_action,
10
+ )
11
+ from elizaos.bootstrap.actions.send_message import (
12
+ send_message_action as bootstrap_send_message_action,
13
+ )
14
+ from elizaos.types.memory import Memory
15
+ from elizaos.types.primitives import Content, as_uuid
16
+
17
+
18
+ def _make_runtime() -> MagicMock:
19
+ runtime = MagicMock()
20
+ runtime.agent_id = as_uuid("42345678-1234-1234-1234-123456789001")
21
+ runtime.create_memory = AsyncMock()
22
+ runtime.emit_event = AsyncMock()
23
+ runtime.send_message_to_target = AsyncMock()
24
+ runtime.get_room = AsyncMock(return_value=None)
25
+ runtime.get_rooms = AsyncMock(return_value=[])
26
+ runtime.get_entities_for_room = AsyncMock(return_value=[])
27
+ return runtime
28
+
29
+
30
+ def _make_message() -> Memory:
31
+ return Memory(
32
+ id=as_uuid("42345678-1234-1234-1234-123456789010"),
33
+ entity_id=as_uuid("42345678-1234-1234-1234-123456789011"),
34
+ room_id=as_uuid("42345678-1234-1234-1234-123456789012"),
35
+ content=Content(text="fallback", source="telegram"),
36
+ )
37
+
38
+
39
+ @pytest.mark.asyncio
40
+ @pytest.mark.parametrize(
41
+ "action_under_test",
42
+ [bootstrap_send_message_action, advanced_send_message_action],
43
+ )
44
+ async def test_send_message_uses_room_target_parameters(action_under_test: object) -> None:
45
+ runtime = _make_runtime()
46
+ message = _make_message()
47
+ target_room_id = "42345678-1234-1234-1234-123456789099"
48
+
49
+ result = await action_under_test.handler(
50
+ runtime,
51
+ message,
52
+ None,
53
+ SimpleNamespace(
54
+ parameters={
55
+ "targetType": "room",
56
+ "target": target_room_id,
57
+ "source": "discord",
58
+ "text": "ship it",
59
+ }
60
+ ),
61
+ None,
62
+ None,
63
+ )
64
+
65
+ assert result.success is True
66
+ assert result.values["targetType"] == "room"
67
+ assert result.values["targetRoomId"] == target_room_id
68
+ runtime.create_memory.assert_awaited_once()
69
+ create_kwargs = runtime.create_memory.await_args.kwargs
70
+ assert create_kwargs["room_id"] == as_uuid(target_room_id)
71
+
72
+ runtime.send_message_to_target.assert_awaited_once()
73
+ send_target = runtime.send_message_to_target.await_args.args[0]
74
+ assert str(send_target.room_id) == target_room_id
75
+ assert send_target.source == "discord"
76
+
77
+
78
+ @pytest.mark.asyncio
79
+ @pytest.mark.parametrize(
80
+ "action_under_test",
81
+ [bootstrap_send_message_action, advanced_send_message_action],
82
+ )
83
+ async def test_send_message_resolves_user_target_from_room_entities(
84
+ action_under_test: object,
85
+ ) -> None:
86
+ runtime = _make_runtime()
87
+ message = _make_message()
88
+ target_entity_id = as_uuid("42345678-1234-1234-1234-123456789088")
89
+ runtime.get_entities_for_room = AsyncMock(
90
+ return_value=[SimpleNamespace(id=target_entity_id, names=["Alice"])]
91
+ )
92
+
93
+ result = await action_under_test.handler(
94
+ runtime,
95
+ message,
96
+ None,
97
+ SimpleNamespace(
98
+ parameters={
99
+ "targetType": "user",
100
+ "target": "alice",
101
+ "source": "discord",
102
+ "text": "hello",
103
+ }
104
+ ),
105
+ None,
106
+ None,
107
+ )
108
+
109
+ assert result.success is True
110
+ assert result.values["targetType"] == "user"
111
+ assert result.values["targetEntityId"] == target_entity_id
112
+ runtime.send_message_to_target.assert_awaited_once()
113
+ send_target = runtime.send_message_to_target.await_args.args[0]
114
+ assert str(send_target.entity_id) == target_entity_id
@@ -78,7 +78,6 @@ class TestSettingsCrypto:
78
78
  assert migrated.startswith("v2:")
79
79
  assert decrypt_string_value(migrated, salt) == plaintext
80
80
 
81
- @pytest.mark.skip(reason="Runtime get_setting from secrets not yet implemented")
82
81
  def test_runtime_get_setting_decrypts_secret_strings(
83
82
  self, monkeypatch: pytest.MonkeyPatch
84
83
  ) -> None:
@@ -96,7 +95,6 @@ class TestSettingsCrypto:
96
95
  runtime = AgentRuntime(character=character)
97
96
  assert runtime.get_setting("API_KEY") == "super-secret"
98
97
 
99
- @pytest.mark.skip(reason="Runtime get_setting from secrets not yet implemented")
100
98
  def test_runtime_get_setting_coerces_true_false_strings(
101
99
  self, monkeypatch: pytest.MonkeyPatch
102
100
  ) -> None: