@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,145 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import TYPE_CHECKING
5
+
6
+ from elizaos.bootstrap.utils.xml import parse_key_value_xml
7
+ from elizaos.generated.spec_helpers import require_action_spec
8
+ from elizaos.prompts import SEARCH_CONTACTS_TEMPLATE
9
+ from elizaos.types import (
10
+ Action,
11
+ ActionExample,
12
+ ActionResult,
13
+ Content,
14
+ ModelType,
15
+ )
16
+ from elizaos.utils.spec_examples import convert_spec_examples
17
+
18
+ if TYPE_CHECKING:
19
+ from elizaos.types import (
20
+ HandlerCallback,
21
+ HandlerOptions,
22
+ IAgentRuntime,
23
+ Memory,
24
+ State,
25
+ )
26
+
27
+ # Get text content from centralized specs
28
+ _spec = require_action_spec("SEARCH_CONTACTS")
29
+
30
+
31
+ def _convert_spec_examples() -> list[list[ActionExample]]:
32
+ """Convert spec examples to ActionExample format."""
33
+ return convert_spec_examples(_spec)
34
+
35
+
36
+ @dataclass
37
+ class SearchContactsAction:
38
+ name: str = _spec["name"]
39
+ similes: list[str] = field(default_factory=lambda: list(_spec.get("similes", [])))
40
+ description: str = _spec["description"]
41
+
42
+ async def validate(
43
+ self, runtime: IAgentRuntime, _message: Memory, _state: State | None = None
44
+ ) -> bool:
45
+ rolodex_service = runtime.get_service("rolodex")
46
+ return rolodex_service is not None
47
+
48
+ async def handler(
49
+ self,
50
+ runtime: IAgentRuntime,
51
+ message: Memory,
52
+ state: State | None = None,
53
+ options: HandlerOptions | None = None,
54
+ callback: HandlerCallback | None = None,
55
+ responses: list[Memory] | None = None,
56
+ ) -> ActionResult:
57
+ from elizaos.bootstrap.services.rolodex import RolodexService
58
+
59
+ rolodex_service = runtime.get_service("rolodex")
60
+ if not rolodex_service or not isinstance(rolodex_service, RolodexService):
61
+ return ActionResult(
62
+ text="Rolodex service not available",
63
+ success=False,
64
+ values={"error": True},
65
+ data={"error": "RolodexService not available"},
66
+ )
67
+
68
+ state = await runtime.compose_state(message, ["RECENT_MESSAGES"])
69
+
70
+ prompt = runtime.compose_prompt_from_state(
71
+ state=state,
72
+ template=SEARCH_CONTACTS_TEMPLATE,
73
+ )
74
+
75
+ response = await runtime.use_model(ModelType.TEXT_SMALL, {"prompt": prompt})
76
+ parsed = parse_key_value_xml(response)
77
+
78
+ categories = None
79
+ tags = None
80
+ search_term = None
81
+
82
+ if parsed:
83
+ if parsed.get("categories"):
84
+ categories = [c.strip() for c in str(parsed["categories"]).split(",") if c.strip()]
85
+ if parsed.get("searchTerm"):
86
+ search_term = str(parsed["searchTerm"])
87
+ if parsed.get("tags"):
88
+ tags = [t.strip() for t in str(parsed["tags"]).split(",") if t.strip()]
89
+
90
+ contacts = await rolodex_service.search_contacts(
91
+ categories=categories,
92
+ tags=tags,
93
+ search_term=search_term,
94
+ )
95
+
96
+ contact_details: list[dict[str, str]] = []
97
+ for contact in contacts:
98
+ entity = await runtime.get_entity(str(contact.entity_id))
99
+ name = entity.name if entity and entity.name else "Unknown"
100
+ contact_details.append(
101
+ {
102
+ "id": str(contact.entity_id),
103
+ "name": name,
104
+ "categories": ",".join(contact.categories),
105
+ "tags": ",".join(contact.tags),
106
+ }
107
+ )
108
+
109
+ if not contact_details:
110
+ response_text = "No contacts found matching your criteria."
111
+ else:
112
+ response_text = f"I found {len(contact_details)} contact(s):\n"
113
+ for detail in contact_details:
114
+ response_text += f"- {detail['name']}"
115
+ if detail["categories"]:
116
+ response_text += f" [{detail['categories']}]"
117
+ response_text += "\n"
118
+
119
+ if callback:
120
+ await callback(Content(text=response_text.strip(), actions=["SEARCH_CONTACTS"]))
121
+
122
+ return ActionResult(
123
+ text=response_text.strip(),
124
+ success=True,
125
+ values={
126
+ "count": len(contact_details),
127
+ },
128
+ data={
129
+ "count": len(contact_details),
130
+ },
131
+ )
132
+
133
+ @property
134
+ def examples(self) -> list[list[ActionExample]]:
135
+ return _convert_spec_examples()
136
+
137
+
138
+ search_contacts_action = Action(
139
+ name=SearchContactsAction.name,
140
+ similes=SearchContactsAction().similes,
141
+ description=SearchContactsAction.description,
142
+ validate=SearchContactsAction().validate,
143
+ handler=SearchContactsAction().handler,
144
+ examples=SearchContactsAction().examples,
145
+ )
@@ -0,0 +1,187 @@
1
+ from __future__ import annotations
2
+
3
+ import contextlib
4
+ from dataclasses import dataclass, field
5
+ from typing import TYPE_CHECKING
6
+
7
+ from elizaos.generated.spec_helpers import require_action_spec
8
+ from elizaos.types import Action, ActionExample, ActionResult, Content
9
+ from elizaos.types.memory import Memory as MemoryType
10
+
11
+ if TYPE_CHECKING:
12
+ from elizaos.types import HandlerCallback, HandlerOptions, IAgentRuntime, Memory, State
13
+
14
+ # Get text content from centralized specs
15
+ _spec = require_action_spec("SEND_MESSAGE")
16
+
17
+
18
+ def _convert_spec_examples() -> list[list[ActionExample]]:
19
+ """Convert spec examples to ActionExample format."""
20
+ spec_examples = _spec.get("examples", [])
21
+ if not isinstance(spec_examples, list):
22
+ return []
23
+ result: list[list[ActionExample]] = []
24
+ for example in spec_examples:
25
+ if not isinstance(example, list):
26
+ continue
27
+ row: list[ActionExample] = []
28
+ for msg in example:
29
+ if not isinstance(msg, dict):
30
+ continue
31
+ content = msg.get("content", {})
32
+ text = ""
33
+ actions: list[str] | None = None
34
+ if isinstance(content, dict):
35
+ text_val = content.get("text", "")
36
+ text = str(text_val) if text_val else ""
37
+ actions_val = content.get("actions")
38
+ if isinstance(actions_val, list) and all(isinstance(a, str) for a in actions_val):
39
+ actions = list(actions_val)
40
+ row.append(
41
+ ActionExample(
42
+ name=str(msg.get("name", "")),
43
+ content=Content(text=text, actions=actions),
44
+ )
45
+ )
46
+ if row:
47
+ result.append(row)
48
+ return result
49
+
50
+
51
+ @dataclass
52
+ class SendMessageAction:
53
+ name: str = _spec["name"]
54
+ similes: list[str] = field(default_factory=lambda: list(_spec.get("similes", [])))
55
+ description: str = _spec["description"]
56
+
57
+ async def validate(
58
+ self, runtime: IAgentRuntime, message: Memory, _state: State | None = None
59
+ ) -> bool:
60
+ if message.content and message.content.target:
61
+ return True
62
+ return True
63
+
64
+ async def handler(
65
+ self,
66
+ runtime: IAgentRuntime,
67
+ message: Memory,
68
+ state: State | None = None,
69
+ options: HandlerOptions | None = None,
70
+ callback: HandlerCallback | None = None,
71
+ responses: list[Memory] | None = None,
72
+ ) -> ActionResult:
73
+ message_text = ""
74
+ if responses and responses[0].content:
75
+ message_text = str(responses[0].content.text or "")
76
+
77
+ if not message_text:
78
+ return ActionResult(
79
+ text="No message content to send",
80
+ values={"success": False, "error": "no_content"},
81
+ data={"actionName": "SEND_MESSAGE"},
82
+ success=False,
83
+ )
84
+
85
+ from uuid import UUID as StdUUID
86
+
87
+ target_room_id_val: str | None = str(message.room_id) if message.room_id else None
88
+ target_entity_id: str | None = None
89
+
90
+ if message.content and message.content.target:
91
+ target = message.content.target
92
+ if isinstance(target, dict):
93
+ room_str = target.get("roomId")
94
+ entity_str = target.get("entityId")
95
+ if room_str:
96
+ with contextlib.suppress(ValueError):
97
+ target_room_id_val = str(StdUUID(str(room_str)))
98
+ if entity_str:
99
+ with contextlib.suppress(ValueError):
100
+ target_entity_id = str(StdUUID(str(entity_str)))
101
+
102
+ if not target_room_id_val:
103
+ return ActionResult(
104
+ text="No target room specified",
105
+ values={"success": False, "error": "no_target"},
106
+ data={"actionName": "SEND_MESSAGE"},
107
+ success=False,
108
+ )
109
+
110
+ message_content = Content(
111
+ text=message_text,
112
+ source="agent",
113
+ actions=["SEND_MESSAGE"],
114
+ )
115
+
116
+ # Create the message memory for event emission
117
+ import time
118
+ import uuid as uuid_module
119
+
120
+ from elizaos.types.primitives import as_uuid
121
+
122
+ message_memory = MemoryType(
123
+ id=as_uuid(str(uuid_module.uuid4())),
124
+ entity_id=runtime.agent_id,
125
+ room_id=as_uuid(target_room_id_val) if target_room_id_val else None,
126
+ content=message_content,
127
+ created_at=int(time.time() * 1000),
128
+ )
129
+
130
+ await runtime.create_memory(
131
+ content=message_content,
132
+ room_id=target_room_id_val,
133
+ entity_id=runtime.agent_id,
134
+ memory_type="message",
135
+ metadata={
136
+ "type": "SEND_MESSAGE",
137
+ "targetEntityId": str(target_entity_id) if target_entity_id else None,
138
+ },
139
+ )
140
+
141
+ # Emit MESSAGE_SENT event
142
+ await runtime.emit_event(
143
+ "MESSAGE_SENT",
144
+ {
145
+ "runtime": runtime,
146
+ "source": "send-message-action",
147
+ "message": message_memory,
148
+ },
149
+ )
150
+
151
+ response_content = Content(
152
+ text=f"Message sent: {message_text[:50]}...",
153
+ actions=["SEND_MESSAGE"],
154
+ )
155
+
156
+ if callback:
157
+ await callback(response_content)
158
+
159
+ return ActionResult(
160
+ text="Message sent to room",
161
+ values={
162
+ "success": True,
163
+ "messageSent": True,
164
+ "targetRoomId": str(target_room_id_val),
165
+ "targetEntityId": str(target_entity_id) if target_entity_id else None,
166
+ },
167
+ data={
168
+ "actionName": "SEND_MESSAGE",
169
+ "targetRoomId": str(target_room_id_val),
170
+ "messagePreview": message_text[:100],
171
+ },
172
+ success=True,
173
+ )
174
+
175
+ @property
176
+ def examples(self) -> list[list[ActionExample]]:
177
+ return _convert_spec_examples()
178
+
179
+
180
+ send_message_action = Action(
181
+ name=SendMessageAction.name,
182
+ similes=SendMessageAction().similes,
183
+ description=SendMessageAction.description,
184
+ validate=SendMessageAction().validate,
185
+ handler=SendMessageAction().handler,
186
+ examples=SendMessageAction().examples,
187
+ )
@@ -0,0 +1,151 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import TYPE_CHECKING
5
+
6
+ from elizaos.bootstrap.utils.xml import parse_key_value_xml
7
+ from elizaos.generated.spec_helpers import require_action_spec
8
+ from elizaos.prompts import UPDATE_SETTINGS_TEMPLATE
9
+ from elizaos.types import Action, ActionExample, ActionResult, Content, ModelType
10
+ from elizaos.utils.spec_examples import convert_spec_examples
11
+
12
+ if TYPE_CHECKING:
13
+ from elizaos.types import HandlerCallback, HandlerOptions, IAgentRuntime, Memory, State
14
+
15
+ # Get text content from centralized specs
16
+ _spec = require_action_spec("UPDATE_SETTINGS")
17
+
18
+
19
+ def _convert_spec_examples() -> list[list[ActionExample]]:
20
+ """Convert spec examples to ActionExample format."""
21
+ return convert_spec_examples(_spec)
22
+
23
+
24
+ @dataclass
25
+ class SettingUpdate:
26
+ key: str
27
+ value: str
28
+
29
+
30
+ @dataclass
31
+ class UpdateSettingsAction:
32
+ name: str = _spec["name"]
33
+ similes: list[str] = field(default_factory=lambda: list(_spec.get("similes", [])))
34
+ description: str = _spec["description"]
35
+
36
+ async def validate(
37
+ self, runtime: IAgentRuntime, _message: Memory, _state: State | None = None
38
+ ) -> bool:
39
+ return True
40
+
41
+ async def handler(
42
+ self,
43
+ runtime: IAgentRuntime,
44
+ message: Memory,
45
+ state: State | None = None,
46
+ options: HandlerOptions | None = None,
47
+ callback: HandlerCallback | None = None,
48
+ responses: list[Memory] | None = None,
49
+ ) -> ActionResult:
50
+ if state is None:
51
+ raise ValueError("State is required for UPDATE_SETTINGS action")
52
+
53
+ state = await runtime.compose_state(
54
+ message, ["RECENT_MESSAGES", "ACTION_STATE", "AGENT_SETTINGS"]
55
+ )
56
+
57
+ current_settings = runtime.get_all_settings()
58
+ settings_context = "\n".join(
59
+ f"- {key}: {value}"
60
+ for key, value in current_settings.items()
61
+ if not key.lower().endswith(("key", "secret", "password", "token"))
62
+ )
63
+
64
+ template = (
65
+ runtime.character.templates.get("updateSettingsTemplate")
66
+ if runtime.character.templates
67
+ and "updateSettingsTemplate" in runtime.character.templates
68
+ else UPDATE_SETTINGS_TEMPLATE
69
+ )
70
+ prompt = runtime.compose_prompt(state=state, template=template)
71
+ prompt = prompt.replace("{{settings}}", settings_context)
72
+
73
+ response_text = await runtime.use_model(ModelType.TEXT_LARGE, prompt=prompt)
74
+ parsed_xml = parse_key_value_xml(response_text)
75
+
76
+ if parsed_xml is None:
77
+ raise ValueError("Failed to parse XML response")
78
+
79
+ thought = str(parsed_xml.get("thought", ""))
80
+ updates_raw = parsed_xml.get("updates", [])
81
+
82
+ updated_settings: list[SettingUpdate] = []
83
+
84
+ if isinstance(updates_raw, list):
85
+ for update in updates_raw:
86
+ if isinstance(update, dict):
87
+ key = str(update.get("key", ""))
88
+ value = str(update.get("value", ""))
89
+ if key and value:
90
+ updated_settings.append(SettingUpdate(key=key, value=value))
91
+ elif isinstance(updates_raw, dict):
92
+ update_list = updates_raw.get("update", [])
93
+ if isinstance(update_list, dict):
94
+ update_list = [update_list]
95
+ for update in update_list:
96
+ if isinstance(update, dict):
97
+ key = str(update.get("key", ""))
98
+ value = str(update.get("value", ""))
99
+ if key and value:
100
+ updated_settings.append(SettingUpdate(key=key, value=value))
101
+
102
+ if not updated_settings:
103
+ return ActionResult(
104
+ text="No settings to update",
105
+ values={"success": True, "noChanges": True},
106
+ data={"actionName": "UPDATE_SETTINGS", "thought": thought},
107
+ success=True,
108
+ )
109
+
110
+ for setting in updated_settings:
111
+ runtime.set_setting(setting.key, setting.value)
112
+
113
+ updated_keys = [s.key for s in updated_settings]
114
+
115
+ response_content = Content(
116
+ text=f"Updated {len(updated_settings)} setting(s): {', '.join(updated_keys)}",
117
+ actions=["UPDATE_SETTINGS"],
118
+ )
119
+
120
+ if callback:
121
+ await callback(response_content)
122
+
123
+ return ActionResult(
124
+ text=f"Updated settings: {', '.join(updated_keys)}",
125
+ values={
126
+ "success": True,
127
+ "settingsUpdated": True,
128
+ "updatedCount": len(updated_settings),
129
+ "updatedKeys": ", ".join(updated_keys),
130
+ },
131
+ data={
132
+ "actionName": "UPDATE_SETTINGS",
133
+ "updatedSettings": updated_keys,
134
+ "thought": thought,
135
+ },
136
+ success=True,
137
+ )
138
+
139
+ @property
140
+ def examples(self) -> list[list[ActionExample]]:
141
+ return _convert_spec_examples()
142
+
143
+
144
+ update_settings_action = Action(
145
+ name=UpdateSettingsAction.name,
146
+ similes=UpdateSettingsAction().similes,
147
+ description=UpdateSettingsAction.description,
148
+ validate=UpdateSettingsAction().validate,
149
+ handler=UpdateSettingsAction().handler,
150
+ examples=UpdateSettingsAction().examples,
151
+ )
@@ -0,0 +1,164 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import TYPE_CHECKING
5
+
6
+ from elizaos.generated.spec_helpers import require_action_spec
7
+ from elizaos.types import Action, ActionExample, ActionResult, Content
8
+
9
+ if TYPE_CHECKING:
10
+ from elizaos.types import HandlerCallback, HandlerOptions, IAgentRuntime, Memory, State
11
+
12
+ # Get text content from centralized specs
13
+ _spec = require_action_spec("UNFOLLOW_ROOM")
14
+
15
+
16
+ def _convert_spec_examples() -> list[list[ActionExample]]:
17
+ """Convert spec examples to ActionExample format."""
18
+ spec_examples = _spec.get("examples", [])
19
+ if not isinstance(spec_examples, list):
20
+ return []
21
+ result: list[list[ActionExample]] = []
22
+ for example in spec_examples:
23
+ if not isinstance(example, list):
24
+ continue
25
+ row: list[ActionExample] = []
26
+ for msg in example:
27
+ if not isinstance(msg, dict):
28
+ continue
29
+ content = msg.get("content", {})
30
+ text = ""
31
+ actions: list[str] | None = None
32
+ if isinstance(content, dict):
33
+ text_val = content.get("text", "")
34
+ text = str(text_val) if text_val else ""
35
+ actions_val = content.get("actions")
36
+ if isinstance(actions_val, list) and all(isinstance(a, str) for a in actions_val):
37
+ actions = list(actions_val)
38
+ row.append(
39
+ ActionExample(
40
+ name=str(msg.get("name", "")),
41
+ content=Content(text=text, actions=actions),
42
+ )
43
+ )
44
+ if row:
45
+ result.append(row)
46
+ return result
47
+
48
+
49
+ @dataclass
50
+ class UnfollowRoomAction:
51
+ name: str = _spec["name"]
52
+ similes: list[str] = field(default_factory=lambda: list(_spec.get("similes", [])))
53
+ description: str = _spec["description"]
54
+
55
+ async def validate(
56
+ self, runtime: IAgentRuntime, message: Memory, _state: State | None = None
57
+ ) -> bool:
58
+ room_id = message.room_id
59
+ if not room_id:
60
+ return False
61
+
62
+ room = await runtime.get_room(room_id)
63
+ if room is None:
64
+ return False
65
+
66
+ world_id = room.world_id
67
+ if world_id:
68
+ world = await runtime.get_world(world_id)
69
+ if world and world.metadata:
70
+ followed_rooms = world.metadata.get("followedRooms", [])
71
+ if str(room_id) in followed_rooms:
72
+ return True
73
+
74
+ return False
75
+
76
+ async def handler(
77
+ self,
78
+ runtime: IAgentRuntime,
79
+ message: Memory,
80
+ state: State | None = None,
81
+ options: HandlerOptions | None = None,
82
+ callback: HandlerCallback | None = None,
83
+ responses: list[Memory] | None = None,
84
+ ) -> ActionResult:
85
+ room_id = message.room_id
86
+ if not room_id:
87
+ return ActionResult(
88
+ text="No room specified to unfollow",
89
+ values={"success": False, "error": "no_room_id"},
90
+ data={"actionName": "UNFOLLOW_ROOM"},
91
+ success=False,
92
+ )
93
+
94
+ room = await runtime.get_room(room_id)
95
+ if room is None:
96
+ return ActionResult(
97
+ text="Room not found",
98
+ values={"success": False, "error": "room_not_found"},
99
+ data={"actionName": "UNFOLLOW_ROOM"},
100
+ success=False,
101
+ )
102
+
103
+ room_name = str(room.name) if room.name else "Unknown Room"
104
+
105
+ world_id = room.world_id
106
+ if world_id:
107
+ world = await runtime.get_world(world_id)
108
+ if world and world.metadata:
109
+ followed_rooms = list(world.metadata.get("followedRooms", []))
110
+ room_id_str = str(room_id)
111
+
112
+ if room_id_str in followed_rooms:
113
+ followed_rooms.remove(room_id_str)
114
+ world.metadata["followedRooms"] = followed_rooms
115
+ await runtime.update_world(world)
116
+
117
+ await runtime.create_memory(
118
+ content=Content(
119
+ text=f"Stopped following room: {room_name}",
120
+ actions=["UNFOLLOW_ROOM"],
121
+ ),
122
+ room_id=room_id,
123
+ entity_id=runtime.agent_id,
124
+ memory_type="action",
125
+ metadata={"type": "UNFOLLOW_ROOM", "roomName": room_name},
126
+ )
127
+
128
+ response_content = Content(
129
+ text=f"I am no longer following {room_name}.",
130
+ actions=["UNFOLLOW_ROOM"],
131
+ )
132
+
133
+ if callback:
134
+ await callback(response_content)
135
+
136
+ return ActionResult(
137
+ text=f"Stopped following room: {room_name}",
138
+ values={
139
+ "success": True,
140
+ "unfollowed": True,
141
+ "roomId": str(room_id),
142
+ "roomName": room_name,
143
+ },
144
+ data={
145
+ "actionName": "UNFOLLOW_ROOM",
146
+ "roomId": str(room_id),
147
+ "roomName": room_name,
148
+ },
149
+ success=True,
150
+ )
151
+
152
+ @property
153
+ def examples(self) -> list[list[ActionExample]]:
154
+ return _convert_spec_examples()
155
+
156
+
157
+ unfollow_room_action = Action(
158
+ name=UnfollowRoomAction.name,
159
+ similes=UnfollowRoomAction().similes,
160
+ description=UnfollowRoomAction.description,
161
+ validate=UnfollowRoomAction().validate,
162
+ handler=UnfollowRoomAction().handler,
163
+ examples=UnfollowRoomAction().examples,
164
+ )