@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,140 @@
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 REPLY_TEMPLATE
9
+ from elizaos.types import (
10
+ Action,
11
+ ActionExample,
12
+ ActionResult,
13
+ Content,
14
+ ModelType,
15
+ )
16
+
17
+ if TYPE_CHECKING:
18
+ from elizaos.types import (
19
+ HandlerCallback,
20
+ HandlerOptions,
21
+ IAgentRuntime,
22
+ Memory,
23
+ State,
24
+ )
25
+
26
+ # Get text content from centralized specs
27
+ _spec = require_action_spec("REPLY")
28
+
29
+
30
+ def _convert_spec_examples() -> list[list[ActionExample]]:
31
+ """Convert spec examples to ActionExample format."""
32
+ spec_examples = _spec.get("examples", [])
33
+ if spec_examples:
34
+ return [
35
+ [
36
+ ActionExample(
37
+ name=msg.get("name", ""),
38
+ content=Content(
39
+ text=msg.get("content", {}).get("text", ""),
40
+ actions=msg.get("content", {}).get("actions"),
41
+ ),
42
+ )
43
+ for msg in example
44
+ ]
45
+ for example in spec_examples
46
+ ]
47
+ return []
48
+
49
+
50
+ @dataclass
51
+ class ReplyAction:
52
+ name: str = _spec["name"]
53
+ similes: list[str] = field(default_factory=lambda: list(_spec.get("similes", [])))
54
+ description: str = _spec["description"]
55
+
56
+ async def validate(
57
+ self, runtime: IAgentRuntime, _message: Memory, _state: State | None = None
58
+ ) -> bool:
59
+ return True
60
+
61
+ async def handler(
62
+ self,
63
+ runtime: IAgentRuntime,
64
+ message: Memory,
65
+ state: State | None = None,
66
+ options: HandlerOptions | None = None,
67
+ callback: HandlerCallback | None = None,
68
+ responses: list[Memory] | None = None,
69
+ ) -> ActionResult:
70
+ all_providers: list[str] = []
71
+ if responses:
72
+ for res in responses:
73
+ if res.content and res.content.providers:
74
+ all_providers.extend(res.content.providers)
75
+
76
+ state = await runtime.compose_state(
77
+ message, [*all_providers, "RECENT_MESSAGES", "ACTION_STATE"]
78
+ )
79
+
80
+ template = REPLY_TEMPLATE
81
+ if runtime.character.templates and "replyTemplate" in runtime.character.templates:
82
+ template = runtime.character.templates["replyTemplate"]
83
+
84
+ prompt = runtime.compose_prompt_from_state(state=state, template=template)
85
+
86
+ response = await runtime.use_model(
87
+ ModelType.TEXT_LARGE,
88
+ {
89
+ "prompt": prompt,
90
+ "system": str(runtime.character.system or ""),
91
+ },
92
+ )
93
+
94
+ parsed = parse_key_value_xml(response)
95
+ thought = parsed.get("thought", "") if parsed else ""
96
+ text = parsed.get("text", "") if parsed else ""
97
+
98
+ thought = str(thought) if thought else ""
99
+ text = str(text) if text else ""
100
+
101
+ response_content = Content(
102
+ thought=thought,
103
+ text=text,
104
+ actions=["REPLY"],
105
+ )
106
+
107
+ if callback:
108
+ await callback(response_content)
109
+
110
+ return ActionResult(
111
+ text=f"Generated reply: {text}",
112
+ values={
113
+ "success": True,
114
+ "responded": True,
115
+ "lastReply": text,
116
+ "lastReplyTime": runtime.get_current_time_ms(),
117
+ "thoughtProcess": thought,
118
+ },
119
+ data={
120
+ "actionName": "REPLY",
121
+ "responseThought": thought,
122
+ "responseText": text,
123
+ "messageGenerated": True,
124
+ },
125
+ success=True,
126
+ )
127
+
128
+ @property
129
+ def examples(self) -> list[list[ActionExample]]:
130
+ return _convert_spec_examples()
131
+
132
+
133
+ reply_action = Action(
134
+ name=ReplyAction.name,
135
+ similes=ReplyAction().similes,
136
+ description=ReplyAction.description,
137
+ validate=ReplyAction().validate,
138
+ handler=ReplyAction().handler,
139
+ examples=ReplyAction().examples,
140
+ )
@@ -0,0 +1,193 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from enum import Enum
5
+ from typing import TYPE_CHECKING
6
+ from uuid import UUID
7
+
8
+ from elizaos.bootstrap.utils.xml import parse_key_value_xml
9
+ from elizaos.generated.spec_helpers import require_action_spec
10
+ from elizaos.types import Action, ActionExample, ActionResult, Content, ModelType
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_ROLE")
17
+
18
+
19
+ def _convert_spec_examples() -> list[list[ActionExample]]:
20
+ """Convert spec examples to ActionExample format."""
21
+ spec_examples = _spec.get("examples", [])
22
+ if spec_examples:
23
+ return [
24
+ [
25
+ ActionExample(
26
+ name=msg.get("name", ""),
27
+ content=Content(
28
+ text=msg.get("content", {}).get("text", ""),
29
+ actions=msg.get("content", {}).get("actions"),
30
+ ),
31
+ )
32
+ for msg in example
33
+ ]
34
+ for example in spec_examples
35
+ ]
36
+ return []
37
+
38
+
39
+ class Role(str, Enum):
40
+ OWNER = "OWNER"
41
+ ADMIN = "ADMIN"
42
+ MEMBER = "MEMBER"
43
+ GUEST = "GUEST"
44
+ NONE = "NONE"
45
+
46
+
47
+ @dataclass
48
+ class UpdateRoleAction:
49
+ name: str = _spec["name"]
50
+ similes: list[str] = field(default_factory=lambda: list(_spec.get("similes", [])))
51
+ description: str = _spec["description"]
52
+
53
+ async def validate(
54
+ self, runtime: IAgentRuntime, message: Memory, _state: State | None = None
55
+ ) -> bool:
56
+ room_id = message.room_id
57
+ if not room_id:
58
+ return False
59
+
60
+ room = await runtime.get_room(room_id)
61
+ if room is None or room.world_id is None:
62
+ return False
63
+
64
+ world = await runtime.get_world(room.world_id)
65
+ if world is None or world.metadata is None:
66
+ return False
67
+
68
+ roles = world.metadata.get("roles", {})
69
+ agent_role = roles.get(str(runtime.agent_id), Role.NONE.value)
70
+
71
+ return agent_role in (Role.OWNER.value, Role.ADMIN.value)
72
+
73
+ async def handler(
74
+ self,
75
+ runtime: IAgentRuntime,
76
+ message: Memory,
77
+ state: State | None = None,
78
+ options: HandlerOptions | None = None,
79
+ callback: HandlerCallback | None = None,
80
+ responses: list[Memory] | None = None,
81
+ ) -> ActionResult:
82
+ if state is None:
83
+ raise ValueError("State is required for UPDATE_ROLE action")
84
+
85
+ room_id = message.room_id
86
+ if not room_id:
87
+ return ActionResult(
88
+ text="No room context for role update",
89
+ values={"success": False, "error": "no_room_id"},
90
+ data={"actionName": "UPDATE_ROLE"},
91
+ success=False,
92
+ )
93
+
94
+ room = await runtime.get_room(room_id)
95
+ if room is None or room.world_id is None:
96
+ return ActionResult(
97
+ text="Room or world not found",
98
+ values={"success": False, "error": "room_not_found"},
99
+ data={"actionName": "UPDATE_ROLE"},
100
+ success=False,
101
+ )
102
+
103
+ world = await runtime.get_world(room.world_id)
104
+ if world is None or world.metadata is None:
105
+ return ActionResult(
106
+ text="World not found",
107
+ values={"success": False, "error": "world_not_found"},
108
+ data={"actionName": "UPDATE_ROLE"},
109
+ success=False,
110
+ )
111
+
112
+ state = await runtime.compose_state(
113
+ message, ["RECENT_MESSAGES", "ACTION_STATE", "WORLD_INFO"]
114
+ )
115
+
116
+ current_roles = world.metadata.get("roles", {})
117
+ roles_context = "\n".join(
118
+ f"- {entity_id}: {role}" for entity_id, role in current_roles.items()
119
+ )
120
+
121
+ template = (
122
+ runtime.character.templates.get("updateRoleTemplate")
123
+ if runtime.character.templates and "updateRoleTemplate" in runtime.character.templates
124
+ else UPDATE_ROLE_TEMPLATE
125
+ )
126
+ prompt = runtime.compose_prompt(state=state, template=template)
127
+ prompt = prompt.replace("{{roles}}", roles_context)
128
+
129
+ response_text = await runtime.use_model(ModelType.TEXT_LARGE, prompt=prompt)
130
+ parsed_xml = parse_key_value_xml(response_text)
131
+
132
+ if parsed_xml is None:
133
+ raise ValueError("Failed to parse XML response")
134
+
135
+ thought = str(parsed_xml.get("thought", ""))
136
+ entity_id_str = str(parsed_xml.get("entity_id", ""))
137
+ new_role_str = str(parsed_xml.get("new_role", "")).upper()
138
+
139
+ if not entity_id_str:
140
+ raise ValueError("No entity ID provided")
141
+
142
+ if new_role_str not in [r.value for r in Role]:
143
+ raise ValueError(f"Invalid role: {new_role_str}")
144
+
145
+ entity_id = UUID(entity_id_str)
146
+
147
+ roles = dict(world.metadata.get("roles", {}))
148
+ old_role = roles.get(str(entity_id), Role.NONE.value)
149
+ roles[str(entity_id)] = new_role_str
150
+ world.metadata["roles"] = roles
151
+
152
+ await runtime.update_world(world)
153
+
154
+ response_content = Content(
155
+ text=f"Updated role for {entity_id_str}: {old_role} -> {new_role_str}",
156
+ actions=["UPDATE_ROLE"],
157
+ )
158
+
159
+ if callback:
160
+ await callback(response_content)
161
+
162
+ return ActionResult(
163
+ text=f"Role updated: {entity_id_str} is now {new_role_str}",
164
+ values={
165
+ "success": True,
166
+ "roleUpdated": True,
167
+ "entityId": str(entity_id),
168
+ "oldRole": old_role,
169
+ "newRole": new_role_str,
170
+ },
171
+ data={
172
+ "actionName": "UPDATE_ROLE",
173
+ "entityId": str(entity_id),
174
+ "oldRole": old_role,
175
+ "newRole": new_role_str,
176
+ "thought": thought,
177
+ },
178
+ success=True,
179
+ )
180
+
181
+ @property
182
+ def examples(self) -> list[list[ActionExample]]:
183
+ return _convert_spec_examples()
184
+
185
+
186
+ update_role_action = Action(
187
+ name=UpdateRoleAction.name,
188
+ similes=UpdateRoleAction().similes,
189
+ description=UpdateRoleAction.description,
190
+ validate=UpdateRoleAction().validate,
191
+ handler=UpdateRoleAction().handler,
192
+ examples=UpdateRoleAction().examples,
193
+ )
@@ -0,0 +1,164 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from datetime import datetime
5
+ from typing import TYPE_CHECKING
6
+
7
+ from elizaos.bootstrap.utils.xml import parse_key_value_xml
8
+ from elizaos.generated.spec_helpers import require_action_spec
9
+ from elizaos.prompts import SCHEDULE_FOLLOW_UP_TEMPLATE
10
+ from elizaos.types import (
11
+ Action,
12
+ ActionExample,
13
+ ActionResult,
14
+ Content,
15
+ ModelType,
16
+ )
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("SCHEDULE_FOLLOW_UP")
29
+
30
+
31
+ def _convert_spec_examples() -> list[list[ActionExample]]:
32
+ """Convert spec examples to ActionExample format."""
33
+ spec_examples = _spec.get("examples", [])
34
+ if spec_examples:
35
+ return [
36
+ [
37
+ ActionExample(
38
+ name=msg.get("name", ""),
39
+ content=Content(
40
+ text=msg.get("content", {}).get("text", ""),
41
+ actions=msg.get("content", {}).get("actions"),
42
+ ),
43
+ )
44
+ for msg in example
45
+ ]
46
+ for example in spec_examples
47
+ ]
48
+ return []
49
+
50
+
51
+ @dataclass
52
+ class ScheduleFollowUpAction:
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
+ rolodex_service = runtime.get_service("rolodex")
61
+ follow_up_service = runtime.get_service("follow_up")
62
+ return rolodex_service is not None and follow_up_service is not None
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
+ from elizaos.bootstrap.services.follow_up import FollowUpService
74
+ from elizaos.bootstrap.services.rolodex import RolodexService
75
+
76
+ rolodex_service = runtime.get_service("rolodex")
77
+ follow_up_service = runtime.get_service("follow_up")
78
+
79
+ if not rolodex_service or not isinstance(rolodex_service, RolodexService):
80
+ return ActionResult(
81
+ text="Rolodex service not available",
82
+ success=False,
83
+ values={"error": True},
84
+ data={"error": "RolodexService not available"},
85
+ )
86
+
87
+ if not follow_up_service or not isinstance(follow_up_service, FollowUpService):
88
+ return ActionResult(
89
+ text="Follow-up service not available",
90
+ success=False,
91
+ values={"error": True},
92
+ data={"error": "FollowUpService not available"},
93
+ )
94
+
95
+ state = await runtime.compose_state(message, ["RECENT_MESSAGES", "ENTITIES"])
96
+ state.values["currentDateTime"] = datetime.utcnow().isoformat()
97
+
98
+ prompt = runtime.compose_prompt_from_state(
99
+ state=state,
100
+ template=SCHEDULE_FOLLOW_UP_TEMPLATE,
101
+ )
102
+
103
+ response = await runtime.use_model(ModelType.TEXT_SMALL, {"prompt": prompt})
104
+ parsed = parse_key_value_xml(response)
105
+
106
+ if not parsed or not parsed.get("contactName"):
107
+ return ActionResult(
108
+ text="Could not extract follow-up information",
109
+ success=False,
110
+ values={"error": True},
111
+ data={"error": "Failed to parse follow-up info"},
112
+ )
113
+
114
+ contact_name = str(parsed.get("contactName", ""))
115
+ scheduled_at_str = str(parsed.get("scheduledAt", ""))
116
+ reason = str(parsed.get("reason", "Follow-up"))
117
+ priority = str(parsed.get("priority", "medium"))
118
+ follow_up_message = str(parsed.get("message", ""))
119
+
120
+ scheduled_at = datetime.fromisoformat(scheduled_at_str.replace("Z", "+00:00"))
121
+
122
+ entity_id = message.entity_id
123
+ await follow_up_service.schedule_follow_up(
124
+ entity_id=entity_id,
125
+ scheduled_at=scheduled_at,
126
+ reason=reason,
127
+ priority=priority,
128
+ message=follow_up_message,
129
+ )
130
+
131
+ response_text = f"I've scheduled a follow-up with {contact_name} for {scheduled_at.strftime('%B %d, %Y')}. Reason: {reason}"
132
+
133
+ if callback:
134
+ await callback(Content(text=response_text, actions=["SCHEDULE_FOLLOW_UP"]))
135
+
136
+ return ActionResult(
137
+ text=response_text,
138
+ success=True,
139
+ values={
140
+ "contactId": str(entity_id),
141
+ "scheduledAt": scheduled_at.isoformat(),
142
+ },
143
+ data={
144
+ "contactId": str(entity_id),
145
+ "contactName": contact_name,
146
+ "scheduledAt": scheduled_at.isoformat(),
147
+ "reason": reason,
148
+ "priority": priority,
149
+ },
150
+ )
151
+
152
+ @property
153
+ def examples(self) -> list[list[ActionExample]]:
154
+ return _convert_spec_examples()
155
+
156
+
157
+ schedule_follow_up_action = Action(
158
+ name=ScheduleFollowUpAction.name,
159
+ similes=ScheduleFollowUpAction().similes,
160
+ description=ScheduleFollowUpAction.description,
161
+ validate=ScheduleFollowUpAction().validate,
162
+ handler=ScheduleFollowUpAction().handler,
163
+ examples=ScheduleFollowUpAction().examples,
164
+ )
@@ -0,0 +1,159 @@
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
+
17
+ if TYPE_CHECKING:
18
+ from elizaos.types import (
19
+ HandlerCallback,
20
+ HandlerOptions,
21
+ IAgentRuntime,
22
+ Memory,
23
+ State,
24
+ )
25
+
26
+ # Get text content from centralized specs
27
+ _spec = require_action_spec("SEARCH_CONTACTS")
28
+
29
+
30
+ def _convert_spec_examples() -> list[list[ActionExample]]:
31
+ """Convert spec examples to ActionExample format."""
32
+ spec_examples = _spec.get("examples", [])
33
+ if spec_examples:
34
+ return [
35
+ [
36
+ ActionExample(
37
+ name=msg.get("name", ""),
38
+ content=Content(
39
+ text=msg.get("content", {}).get("text", ""),
40
+ actions=msg.get("content", {}).get("actions"),
41
+ ),
42
+ )
43
+ for msg in example
44
+ ]
45
+ for example in spec_examples
46
+ ]
47
+ return []
48
+
49
+
50
+ @dataclass
51
+ class SearchContactsAction:
52
+ name: str = _spec["name"]
53
+ similes: list[str] = field(default_factory=lambda: list(_spec.get("similes", [])))
54
+ description: str = _spec["description"]
55
+
56
+ async def validate(
57
+ self, runtime: IAgentRuntime, _message: Memory, _state: State | None = None
58
+ ) -> bool:
59
+ rolodex_service = runtime.get_service("rolodex")
60
+ return rolodex_service is not None
61
+
62
+ async def handler(
63
+ self,
64
+ runtime: IAgentRuntime,
65
+ message: Memory,
66
+ state: State | None = None,
67
+ options: HandlerOptions | None = None,
68
+ callback: HandlerCallback | None = None,
69
+ responses: list[Memory] | None = None,
70
+ ) -> ActionResult:
71
+ from elizaos.bootstrap.services.rolodex import RolodexService
72
+
73
+ rolodex_service = runtime.get_service("rolodex")
74
+ if not rolodex_service or not isinstance(rolodex_service, RolodexService):
75
+ return ActionResult(
76
+ text="Rolodex service not available",
77
+ success=False,
78
+ values={"error": True},
79
+ data={"error": "RolodexService not available"},
80
+ )
81
+
82
+ state = await runtime.compose_state(message, ["RECENT_MESSAGES"])
83
+
84
+ prompt = runtime.compose_prompt_from_state(
85
+ state=state,
86
+ template=SEARCH_CONTACTS_TEMPLATE,
87
+ )
88
+
89
+ response = await runtime.use_model(ModelType.TEXT_SMALL, {"prompt": prompt})
90
+ parsed = parse_key_value_xml(response)
91
+
92
+ categories = None
93
+ tags = None
94
+ search_term = None
95
+
96
+ if parsed:
97
+ if parsed.get("categories"):
98
+ categories = [c.strip() for c in str(parsed["categories"]).split(",") if c.strip()]
99
+ if parsed.get("searchTerm"):
100
+ search_term = str(parsed["searchTerm"])
101
+ if parsed.get("tags"):
102
+ tags = [t.strip() for t in str(parsed["tags"]).split(",") if t.strip()]
103
+
104
+ contacts = await rolodex_service.search_contacts(
105
+ categories=categories,
106
+ tags=tags,
107
+ search_term=search_term,
108
+ )
109
+
110
+ contact_details: list[dict[str, str]] = []
111
+ for contact in contacts:
112
+ entity = await runtime.get_entity(contact.entity_id)
113
+ name = entity.name if entity and entity.name else "Unknown"
114
+ contact_details.append(
115
+ {
116
+ "id": str(contact.entity_id),
117
+ "name": name,
118
+ "categories": ",".join(contact.categories),
119
+ "tags": ",".join(contact.tags),
120
+ }
121
+ )
122
+
123
+ if not contact_details:
124
+ response_text = "No contacts found matching your criteria."
125
+ else:
126
+ response_text = f"I found {len(contact_details)} contact(s):\n"
127
+ for detail in contact_details:
128
+ response_text += f"- {detail['name']}"
129
+ if detail["categories"]:
130
+ response_text += f" [{detail['categories']}]"
131
+ response_text += "\n"
132
+
133
+ if callback:
134
+ await callback(Content(text=response_text.strip(), actions=["SEARCH_CONTACTS"]))
135
+
136
+ return ActionResult(
137
+ text=response_text.strip(),
138
+ success=True,
139
+ values={
140
+ "count": len(contact_details),
141
+ },
142
+ data={
143
+ "count": len(contact_details),
144
+ },
145
+ )
146
+
147
+ @property
148
+ def examples(self) -> list[list[ActionExample]]:
149
+ return _convert_spec_examples()
150
+
151
+
152
+ search_contacts_action = Action(
153
+ name=SearchContactsAction.name,
154
+ similes=SearchContactsAction().similes,
155
+ description=SearchContactsAction.description,
156
+ validate=SearchContactsAction().validate,
157
+ handler=SearchContactsAction().handler,
158
+ examples=SearchContactsAction().examples,
159
+ )