@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,85 @@
1
+ """Advanced Capabilities - Extended features for agent operation.
2
+
3
+ This module provides advanced capabilities that can be enabled with
4
+ `advanced_capabilities=True` or `enable_extended=True`:
5
+ - Extended actions (contacts, room management, image generation, etc.)
6
+ - Extended providers (facts, knowledge, relationships, etc.)
7
+ - Evaluators (reflection, relationship extraction)
8
+ - Extended services (rolodex, follow-up scheduling)
9
+ """
10
+
11
+ from .actions import (
12
+ add_contact_action,
13
+ advanced_actions,
14
+ follow_room_action,
15
+ generate_image_action,
16
+ mute_room_action,
17
+ remove_contact_action,
18
+ schedule_follow_up_action,
19
+ search_contacts_action,
20
+ send_message_action,
21
+ unfollow_room_action,
22
+ unmute_room_action,
23
+ update_contact_action,
24
+ update_entity_action,
25
+ update_role_action,
26
+ update_settings_action,
27
+ )
28
+ from .evaluators import (
29
+ advanced_evaluators,
30
+ reflection_evaluator,
31
+ relationship_extraction_evaluator,
32
+ )
33
+ from .providers import (
34
+ advanced_providers,
35
+ agent_settings_provider,
36
+ contacts_provider,
37
+ facts_provider,
38
+ follow_ups_provider,
39
+ knowledge_provider,
40
+ relationships_provider,
41
+ roles_provider,
42
+ settings_provider,
43
+ )
44
+ from .services import (
45
+ FollowUpService,
46
+ RolodexService,
47
+ advanced_services,
48
+ )
49
+
50
+ __all__ = [
51
+ # Actions
52
+ "advanced_actions",
53
+ "add_contact_action",
54
+ "follow_room_action",
55
+ "generate_image_action",
56
+ "mute_room_action",
57
+ "remove_contact_action",
58
+ "schedule_follow_up_action",
59
+ "search_contacts_action",
60
+ "send_message_action",
61
+ "unfollow_room_action",
62
+ "unmute_room_action",
63
+ "update_contact_action",
64
+ "update_entity_action",
65
+ "update_role_action",
66
+ "update_settings_action",
67
+ # Providers
68
+ "advanced_providers",
69
+ "agent_settings_provider",
70
+ "contacts_provider",
71
+ "facts_provider",
72
+ "follow_ups_provider",
73
+ "knowledge_provider",
74
+ "relationships_provider",
75
+ "roles_provider",
76
+ "settings_provider",
77
+ # Evaluators
78
+ "advanced_evaluators",
79
+ "reflection_evaluator",
80
+ "relationship_extraction_evaluator",
81
+ # Services
82
+ "advanced_services",
83
+ "FollowUpService",
84
+ "RolodexService",
85
+ ]
@@ -0,0 +1,54 @@
1
+ """Advanced Actions - Extended actions for agent operation.
2
+
3
+ Extended actions that can be enabled with `advanced_capabilities=True`.
4
+ """
5
+
6
+ from .add_contact import add_contact_action
7
+ from .follow_room import follow_room_action
8
+ from .image_generation import generate_image_action
9
+ from .mute_room import mute_room_action
10
+ from .remove_contact import remove_contact_action
11
+ from .roles import update_role_action
12
+ from .schedule_follow_up import schedule_follow_up_action
13
+ from .search_contacts import search_contacts_action
14
+ from .send_message import send_message_action
15
+ from .settings import update_settings_action
16
+ from .unfollow_room import unfollow_room_action
17
+ from .unmute_room import unmute_room_action
18
+ from .update_contact import update_contact_action
19
+ from .update_entity import update_entity_action
20
+
21
+ __all__ = [
22
+ "add_contact_action",
23
+ "follow_room_action",
24
+ "generate_image_action",
25
+ "mute_room_action",
26
+ "remove_contact_action",
27
+ "schedule_follow_up_action",
28
+ "search_contacts_action",
29
+ "send_message_action",
30
+ "unfollow_room_action",
31
+ "unmute_room_action",
32
+ "update_contact_action",
33
+ "update_entity_action",
34
+ "update_role_action",
35
+ "update_settings_action",
36
+ "advanced_actions",
37
+ ]
38
+
39
+ advanced_actions = [
40
+ add_contact_action,
41
+ follow_room_action,
42
+ generate_image_action,
43
+ mute_room_action,
44
+ remove_contact_action,
45
+ schedule_follow_up_action,
46
+ search_contacts_action,
47
+ send_message_action,
48
+ unfollow_room_action,
49
+ unmute_room_action,
50
+ update_contact_action,
51
+ update_entity_action,
52
+ update_role_action,
53
+ update_settings_action,
54
+ ]
@@ -0,0 +1,139 @@
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 ADD_CONTACT_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("ADD_CONTACT")
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 AddContactAction:
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 ContactPreferences, 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", "ENTITIES"])
69
+
70
+ prompt = runtime.compose_prompt_from_state(
71
+ state=state,
72
+ template=ADD_CONTACT_TEMPLATE,
73
+ )
74
+
75
+ response = await runtime.use_model(ModelType.TEXT_SMALL, {"prompt": prompt})
76
+ parsed = parse_key_value_xml(response)
77
+
78
+ if not parsed or not parsed.get("contactName"):
79
+ return ActionResult(
80
+ text="Could not extract contact information",
81
+ success=False,
82
+ values={"error": True},
83
+ data={"error": "Failed to parse contact info"},
84
+ )
85
+
86
+ contact_name = str(parsed.get("contactName", ""))
87
+ categories_str = str(parsed.get("categories", "acquaintance"))
88
+ categories = [c.strip() for c in categories_str.split(",") if c.strip()]
89
+ notes = str(parsed.get("notes", ""))
90
+ reason = str(parsed.get("reason", ""))
91
+
92
+ from uuid import UUID as StdUUID
93
+
94
+ entity_id = message.entity_id
95
+ entity_id_uuid = StdUUID(str(entity_id)) if entity_id else None
96
+ preferences = ContactPreferences(notes=notes) if notes else None
97
+
98
+ if entity_id_uuid:
99
+ await rolodex_service.add_contact(
100
+ entity_id=entity_id_uuid,
101
+ categories=categories,
102
+ preferences=preferences,
103
+ )
104
+
105
+ response_text = (
106
+ f"I've added {contact_name} to your contacts as {', '.join(categories)}. {reason}"
107
+ )
108
+
109
+ if callback:
110
+ await callback(Content(text=response_text, actions=["ADD_CONTACT"]))
111
+
112
+ return ActionResult(
113
+ text=response_text,
114
+ success=True,
115
+ values={
116
+ "contactId": str(entity_id),
117
+ "contactName": contact_name,
118
+ "categoriesStr": ",".join(categories),
119
+ },
120
+ data={
121
+ "contactId": str(entity_id),
122
+ "contactName": contact_name,
123
+ "categories": ",".join(categories),
124
+ },
125
+ )
126
+
127
+ @property
128
+ def examples(self) -> list[list[ActionExample]]:
129
+ return _convert_spec_examples()
130
+
131
+
132
+ add_contact_action = Action(
133
+ name=AddContactAction.name,
134
+ similes=AddContactAction().similes,
135
+ description=AddContactAction.description,
136
+ validate=AddContactAction().validate,
137
+ handler=AddContactAction().handler,
138
+ examples=AddContactAction().examples,
139
+ )
@@ -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.types import Action, ActionExample, ActionResult, Content
7
+
8
+ if TYPE_CHECKING:
9
+ from elizaos.types import HandlerCallback, HandlerOptions, IAgentRuntime, Memory, State
10
+
11
+
12
+ @dataclass
13
+ class FollowRoomAction:
14
+ name: str = "FOLLOW_ROOM"
15
+ similes: list[str] = field(
16
+ default_factory=lambda: [
17
+ "JOIN_ROOM",
18
+ "SUBSCRIBE_ROOM",
19
+ "WATCH_ROOM",
20
+ "ENTER_ROOM",
21
+ ]
22
+ )
23
+ description: str = (
24
+ "Follow a room to receive updates and monitor messages. "
25
+ "Use this when you want to actively engage with a room's content."
26
+ )
27
+
28
+ async def validate(
29
+ self, runtime: IAgentRuntime, message: Memory, _state: State | None = None
30
+ ) -> bool:
31
+ room_id = message.room_id
32
+ if not room_id:
33
+ return False
34
+
35
+ room = await runtime.get_room(room_id)
36
+ if room is None:
37
+ return False
38
+
39
+ world_id = room.world_id
40
+ if world_id:
41
+ world = await runtime.get_world(world_id)
42
+ if world and world.metadata:
43
+ followed_rooms = world.metadata.get("followedRooms", [])
44
+ if str(room_id) in followed_rooms:
45
+ return False
46
+
47
+ return True
48
+
49
+ async def handler(
50
+ self,
51
+ runtime: IAgentRuntime,
52
+ message: Memory,
53
+ state: State | None = None,
54
+ options: HandlerOptions | None = None,
55
+ callback: HandlerCallback | None = None,
56
+ responses: list[Memory] | None = None,
57
+ ) -> ActionResult:
58
+ room_id = message.room_id
59
+ if not room_id:
60
+ return ActionResult(
61
+ text="No room specified to follow",
62
+ values={"success": False, "error": "no_room_id"},
63
+ data={"actionName": "FOLLOW_ROOM"},
64
+ success=False,
65
+ )
66
+
67
+ room = await runtime.get_room(room_id)
68
+ if room is None:
69
+ return ActionResult(
70
+ text="Room not found",
71
+ values={"success": False, "error": "room_not_found"},
72
+ data={"actionName": "FOLLOW_ROOM"},
73
+ success=False,
74
+ )
75
+
76
+ room_name = str(room.name) if room.name else "Unknown Room"
77
+
78
+ world_id = room.world_id
79
+ if world_id:
80
+ world = await runtime.get_world(world_id)
81
+ if world and world.metadata:
82
+ followed_rooms = list(world.metadata.get("followedRooms", []))
83
+ room_id_str = str(room_id)
84
+
85
+ if room_id_str not in followed_rooms:
86
+ followed_rooms.append(room_id_str)
87
+ world.metadata["followedRooms"] = followed_rooms
88
+ await runtime.update_world(world)
89
+
90
+ await runtime.create_memory(
91
+ content=Content(
92
+ text=f"Now following room: {room_name}",
93
+ actions=["FOLLOW_ROOM"],
94
+ ),
95
+ room_id=room_id,
96
+ entity_id=runtime.agent_id,
97
+ memory_type="action",
98
+ metadata={"type": "FOLLOW_ROOM", "roomName": room_name},
99
+ )
100
+
101
+ response_content = Content(
102
+ text=f"I am now following {room_name} and will monitor its messages.",
103
+ actions=["FOLLOW_ROOM"],
104
+ )
105
+
106
+ if callback:
107
+ await callback(response_content)
108
+
109
+ return ActionResult(
110
+ text=f"Now following room: {room_name}",
111
+ values={
112
+ "success": True,
113
+ "following": True,
114
+ "roomId": str(room_id),
115
+ "roomName": room_name,
116
+ },
117
+ data={
118
+ "actionName": "FOLLOW_ROOM",
119
+ "roomId": str(room_id),
120
+ "roomName": room_name,
121
+ },
122
+ success=True,
123
+ )
124
+
125
+ @property
126
+ def examples(self) -> list[list[ActionExample]]:
127
+ return [
128
+ [
129
+ ActionExample(
130
+ name="{{name1}}",
131
+ content=Content(text="Can you keep an eye on this channel?"),
132
+ ),
133
+ ActionExample(
134
+ name="{{name2}}",
135
+ content=Content(
136
+ text="I'll follow this room and monitor its activity.",
137
+ actions=["FOLLOW_ROOM"],
138
+ ),
139
+ ),
140
+ ],
141
+ ]
142
+
143
+
144
+ follow_room_action = Action(
145
+ name=FollowRoomAction.name,
146
+ similes=FollowRoomAction().similes,
147
+ description=FollowRoomAction.description,
148
+ validate=FollowRoomAction().validate,
149
+ handler=FollowRoomAction().handler,
150
+ examples=FollowRoomAction().examples,
151
+ )
@@ -0,0 +1,148 @@
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 IMAGE_GENERATION_TEMPLATE
9
+ from elizaos.types import Action, ActionExample, ActionResult, Content, ModelType
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("GENERATE_IMAGE")
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 GenerateImageAction:
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
+ return runtime.has_model(ModelType.IMAGE)
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
+ if state is None:
72
+ raise ValueError("State is required for GENERATE_IMAGE action")
73
+
74
+ state = await runtime.compose_state(message, ["RECENT_MESSAGES", "ACTION_STATE"])
75
+
76
+ template = (
77
+ runtime.character.templates.get("imageGenerationTemplate")
78
+ if runtime.character.templates
79
+ and "imageGenerationTemplate" in runtime.character.templates
80
+ else IMAGE_GENERATION_TEMPLATE
81
+ )
82
+ prompt = runtime.compose_prompt(state=state, template=template)
83
+
84
+ prompt_response = await runtime.use_model(ModelType.TEXT_LARGE, prompt=prompt)
85
+ parsed_xml = parse_key_value_xml(prompt_response)
86
+
87
+ if parsed_xml is None:
88
+ raise ValueError("Failed to parse XML response for image prompt")
89
+
90
+ thought = str(parsed_xml.get("thought", ""))
91
+ image_prompt = str(parsed_xml.get("prompt", ""))
92
+
93
+ if not image_prompt:
94
+ raise ValueError("No image prompt generated")
95
+
96
+ image_result = await runtime.use_model(
97
+ ModelType.IMAGE,
98
+ prompt=image_prompt,
99
+ )
100
+
101
+ image_url: str | None = None
102
+ if isinstance(image_result, str):
103
+ image_url = image_result
104
+ elif isinstance(image_result, dict):
105
+ image_url = image_result.get("url") or image_result.get("data")
106
+
107
+ if not image_url:
108
+ raise ValueError("No image URL returned from generation")
109
+
110
+ response_content = Content(
111
+ text=f"Generated image with prompt: {image_prompt}",
112
+ attachments=[{"type": "image", "url": image_url}],
113
+ actions=["GENERATE_IMAGE"],
114
+ )
115
+
116
+ if callback:
117
+ await callback(response_content)
118
+
119
+ return ActionResult(
120
+ text=f"Generated image: {image_prompt}",
121
+ values={
122
+ "success": True,
123
+ "imageGenerated": True,
124
+ "imageUrl": image_url,
125
+ "imagePrompt": image_prompt,
126
+ },
127
+ data={
128
+ "actionName": "GENERATE_IMAGE",
129
+ "prompt": image_prompt,
130
+ "thought": thought,
131
+ "imageUrl": image_url,
132
+ },
133
+ success=True,
134
+ )
135
+
136
+ @property
137
+ def examples(self) -> list[list[ActionExample]]:
138
+ return _convert_spec_examples()
139
+
140
+
141
+ generate_image_action = Action(
142
+ name=GenerateImageAction.name,
143
+ similes=GenerateImageAction().similes,
144
+ description=GenerateImageAction.description,
145
+ validate=GenerateImageAction().validate,
146
+ handler=GenerateImageAction().handler,
147
+ examples=GenerateImageAction().examples,
148
+ )