@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,178 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from datetime import UTC, datetime
5
+ from enum import Enum
6
+ from typing import TYPE_CHECKING
7
+ from uuid import UUID, uuid4
8
+
9
+ from elizaos.types import Service, ServiceType
10
+
11
+ if TYPE_CHECKING:
12
+ from elizaos.types import IAgentRuntime
13
+
14
+
15
+ class TaskStatus(str, Enum):
16
+ PENDING = "PENDING"
17
+ IN_PROGRESS = "IN_PROGRESS"
18
+ COMPLETED = "COMPLETED"
19
+ FAILED = "FAILED"
20
+ CANCELLED = "CANCELLED"
21
+
22
+
23
+ class TaskPriority(str, Enum):
24
+ LOW = "LOW"
25
+ MEDIUM = "MEDIUM"
26
+ HIGH = "HIGH"
27
+ URGENT = "URGENT"
28
+
29
+
30
+ @dataclass
31
+ class Task:
32
+ id: UUID
33
+ name: str
34
+ description: str
35
+ status: TaskStatus
36
+ priority: TaskPriority
37
+ created_at: datetime
38
+ updated_at: datetime
39
+ completed_at: datetime | None = None
40
+ metadata: dict[str, str | int | float | bool | None] = field(default_factory=dict)
41
+ assignee_id: UUID | None = None
42
+ parent_id: UUID | None = None
43
+
44
+
45
+ class TaskService(Service):
46
+ name = "task"
47
+ service_type = ServiceType.TASK
48
+
49
+ @property
50
+ def capability_description(self) -> str:
51
+ return "Task management service for creating, tracking, and completing tasks."
52
+
53
+ def __init__(self) -> None:
54
+ self._tasks: dict[UUID, Task] = {}
55
+ self._runtime: IAgentRuntime | None = None
56
+
57
+ @classmethod
58
+ async def start(cls, runtime: IAgentRuntime) -> TaskService:
59
+ service = cls()
60
+ service._runtime = runtime
61
+ runtime.logger.info(
62
+ "Task service started",
63
+ src="service:task",
64
+ agentId=str(runtime.agent_id),
65
+ )
66
+ return service
67
+
68
+ async def stop(self) -> None:
69
+ if self._runtime:
70
+ self._runtime.logger.info(
71
+ "Task service stopped",
72
+ src="service:task",
73
+ agentId=str(self._runtime.agent_id),
74
+ )
75
+ self._tasks.clear()
76
+ self._runtime = None
77
+
78
+ async def create_task(
79
+ self,
80
+ name: str,
81
+ description: str,
82
+ priority: TaskPriority = TaskPriority.MEDIUM,
83
+ assignee_id: UUID | None = None,
84
+ parent_id: UUID | None = None,
85
+ metadata: dict[str, str | int | float | bool | None] | None = None,
86
+ ) -> Task:
87
+ now = datetime.now(UTC)
88
+ task = Task(
89
+ id=uuid4(),
90
+ name=name,
91
+ description=description,
92
+ status=TaskStatus.PENDING,
93
+ priority=priority,
94
+ created_at=now,
95
+ updated_at=now,
96
+ assignee_id=assignee_id,
97
+ parent_id=parent_id,
98
+ metadata=metadata or {},
99
+ )
100
+ self._tasks[task.id] = task
101
+
102
+ if self._runtime:
103
+ self._runtime.logger.debug(
104
+ "Task created",
105
+ src="service:task",
106
+ taskId=str(task.id),
107
+ taskName=name,
108
+ )
109
+
110
+ return task
111
+
112
+ async def get_task(self, task_id: UUID) -> Task | None:
113
+ return self._tasks.get(task_id)
114
+
115
+ async def update_task_status(
116
+ self,
117
+ task_id: UUID,
118
+ status: TaskStatus,
119
+ ) -> Task | None:
120
+ task = self._tasks.get(task_id)
121
+ if task is None:
122
+ return None
123
+
124
+ task.status = status
125
+ task.updated_at = datetime.now(UTC)
126
+
127
+ if status == TaskStatus.COMPLETED:
128
+ task.completed_at = task.updated_at
129
+
130
+ if self._runtime:
131
+ self._runtime.logger.debug(
132
+ "Task status updated",
133
+ src="service:task",
134
+ taskId=str(task_id),
135
+ newStatus=status.value,
136
+ )
137
+
138
+ return task
139
+
140
+ async def get_tasks_by_status(
141
+ self,
142
+ status: TaskStatus,
143
+ ) -> list[Task]:
144
+ return [t for t in self._tasks.values() if t.status == status]
145
+
146
+ async def get_tasks_by_priority(
147
+ self,
148
+ priority: TaskPriority,
149
+ ) -> list[Task]:
150
+ return [t for t in self._tasks.values() if t.priority == priority]
151
+
152
+ async def get_pending_tasks(self) -> list[Task]:
153
+ pending = [t for t in self._tasks.values() if t.status == TaskStatus.PENDING]
154
+ priority_order = {
155
+ TaskPriority.URGENT: 0,
156
+ TaskPriority.HIGH: 1,
157
+ TaskPriority.MEDIUM: 2,
158
+ TaskPriority.LOW: 3,
159
+ }
160
+ return sorted(pending, key=lambda t: priority_order[t.priority])
161
+
162
+ async def complete_task(self, task_id: UUID) -> Task | None:
163
+ return await self.update_task_status(task_id, TaskStatus.COMPLETED)
164
+
165
+ async def cancel_task(self, task_id: UUID) -> Task | None:
166
+ return await self.update_task_status(task_id, TaskStatus.CANCELLED)
167
+
168
+ async def delete_task(self, task_id: UUID) -> bool:
169
+ if task_id in self._tasks:
170
+ del self._tasks[task_id]
171
+ if self._runtime:
172
+ self._runtime.logger.debug(
173
+ "Task deleted",
174
+ src="service:task",
175
+ taskId=str(task_id),
176
+ )
177
+ return True
178
+ return False
@@ -0,0 +1,12 @@
1
+ """elizaOS Bootstrap Plugin - Python implementation."""
2
+
3
+ from .plugin import bootstrap_plugin, create_bootstrap_plugin
4
+ from .types import CapabilityConfig
5
+
6
+ __version__ = "2.0.0-alpha.0"
7
+ __all__ = [
8
+ "bootstrap_plugin",
9
+ "create_bootstrap_plugin",
10
+ "CapabilityConfig",
11
+ "__version__",
12
+ ]
@@ -0,0 +1,68 @@
1
+ from .add_contact import add_contact_action
2
+ from .choice import choose_option_action
3
+ from .follow_room import follow_room_action
4
+ from .ignore import ignore_action
5
+ from .image_generation import generate_image_action
6
+ from .mute_room import mute_room_action
7
+ from .none import none_action
8
+ from .remove_contact import remove_contact_action
9
+ from .reply import reply_action
10
+ from .roles import update_role_action
11
+ from .schedule_follow_up import schedule_follow_up_action
12
+ from .search_contacts import search_contacts_action
13
+ from .send_message import send_message_action
14
+ from .settings import update_settings_action
15
+ from .unfollow_room import unfollow_room_action
16
+ from .unmute_room import unmute_room_action
17
+ from .update_contact import update_contact_action
18
+ from .update_entity import update_entity_action
19
+
20
+ __all__ = [
21
+ "add_contact_action",
22
+ "choose_option_action",
23
+ "follow_room_action",
24
+ "generate_image_action",
25
+ "ignore_action",
26
+ "mute_room_action",
27
+ "none_action",
28
+ "remove_contact_action",
29
+ "reply_action",
30
+ "schedule_follow_up_action",
31
+ "search_contacts_action",
32
+ "send_message_action",
33
+ "unfollow_room_action",
34
+ "unmute_room_action",
35
+ "update_contact_action",
36
+ "update_entity_action",
37
+ "update_role_action",
38
+ "update_settings_action",
39
+ "BASIC_ACTIONS",
40
+ "EXTENDED_ACTIONS",
41
+ "ALL_ACTIONS",
42
+ ]
43
+
44
+ BASIC_ACTIONS = [
45
+ reply_action,
46
+ ignore_action,
47
+ none_action,
48
+ ]
49
+
50
+ EXTENDED_ACTIONS = [
51
+ add_contact_action,
52
+ choose_option_action,
53
+ follow_room_action,
54
+ generate_image_action,
55
+ mute_room_action,
56
+ remove_contact_action,
57
+ schedule_follow_up_action,
58
+ search_contacts_action,
59
+ send_message_action,
60
+ unfollow_room_action,
61
+ unmute_room_action,
62
+ update_contact_action,
63
+ update_entity_action,
64
+ update_role_action,
65
+ update_settings_action,
66
+ ]
67
+
68
+ ALL_ACTIONS = BASIC_ACTIONS + EXTENDED_ACTIONS
@@ -0,0 +1,149 @@
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
+
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("ADD_CONTACT")
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 AddContactAction:
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 ContactPreferences, 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", "ENTITIES"])
83
+
84
+ prompt = runtime.compose_prompt_from_state(
85
+ state=state,
86
+ template=ADD_CONTACT_TEMPLATE,
87
+ )
88
+
89
+ response = await runtime.use_model(ModelType.TEXT_SMALL, {"prompt": prompt})
90
+ parsed = parse_key_value_xml(response)
91
+
92
+ if not parsed or not parsed.get("contactName"):
93
+ return ActionResult(
94
+ text="Could not extract contact information",
95
+ success=False,
96
+ values={"error": True},
97
+ data={"error": "Failed to parse contact info"},
98
+ )
99
+
100
+ contact_name = str(parsed.get("contactName", ""))
101
+ categories_str = str(parsed.get("categories", "acquaintance"))
102
+ categories = [c.strip() for c in categories_str.split(",") if c.strip()]
103
+ notes = str(parsed.get("notes", ""))
104
+ reason = str(parsed.get("reason", ""))
105
+
106
+ entity_id = message.entity_id
107
+ preferences = ContactPreferences(notes=notes) if notes else None
108
+
109
+ await rolodex_service.add_contact(
110
+ entity_id=entity_id,
111
+ categories=categories,
112
+ preferences=preferences,
113
+ )
114
+
115
+ response_text = (
116
+ f"I've added {contact_name} to your contacts as {', '.join(categories)}. {reason}"
117
+ )
118
+
119
+ if callback:
120
+ await callback(Content(text=response_text, actions=["ADD_CONTACT"]))
121
+
122
+ return ActionResult(
123
+ text=response_text,
124
+ success=True,
125
+ values={
126
+ "contactId": str(entity_id),
127
+ "contactName": contact_name,
128
+ "categoriesStr": ",".join(categories),
129
+ },
130
+ data={
131
+ "contactId": str(entity_id),
132
+ "contactName": contact_name,
133
+ "categories": ",".join(categories),
134
+ },
135
+ )
136
+
137
+ @property
138
+ def examples(self) -> list[list[ActionExample]]:
139
+ return _convert_spec_examples()
140
+
141
+
142
+ add_contact_action = Action(
143
+ name=AddContactAction.name,
144
+ similes=AddContactAction().similes,
145
+ description=AddContactAction.description,
146
+ validate=AddContactAction().validate,
147
+ handler=AddContactAction().handler,
148
+ examples=AddContactAction().examples,
149
+ )
@@ -0,0 +1,147 @@
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 CHOOSE_OPTION_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("CHOOSE_OPTION")
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 spec_examples:
22
+ return [
23
+ [
24
+ ActionExample(
25
+ name=msg.get("name", ""),
26
+ content=Content(
27
+ text=msg.get("content", {}).get("text", ""),
28
+ actions=msg.get("content", {}).get("actions"),
29
+ ),
30
+ )
31
+ for msg in example
32
+ ]
33
+ for example in spec_examples
34
+ ]
35
+ return []
36
+
37
+
38
+ @dataclass
39
+ class ChooseOptionAction:
40
+ name: str = _spec["name"]
41
+ similes: list[str] = field(default_factory=lambda: list(_spec.get("similes", [])))
42
+ description: str = _spec["description"]
43
+
44
+ async def validate(
45
+ self, runtime: IAgentRuntime, message: Memory, _state: State | None = None
46
+ ) -> bool:
47
+ if message.content and message.content.options:
48
+ return len(message.content.options) > 0
49
+ return True
50
+
51
+ async def handler(
52
+ self,
53
+ runtime: IAgentRuntime,
54
+ message: Memory,
55
+ state: State | None = None,
56
+ options: HandlerOptions | None = None,
57
+ callback: HandlerCallback | None = None,
58
+ responses: list[Memory] | None = None,
59
+ ) -> ActionResult:
60
+ if state is None:
61
+ raise ValueError("State is required for CHOOSE_OPTION action")
62
+
63
+ available_options: list[dict[str, str]] = []
64
+ if message.content and message.content.options:
65
+ available_options = message.content.options
66
+
67
+ if not available_options:
68
+ return ActionResult(
69
+ text="No options available to choose from",
70
+ values={"success": False, "error": "no_options"},
71
+ data={"actionName": "CHOOSE_OPTION"},
72
+ success=False,
73
+ )
74
+
75
+ state = await runtime.compose_state(message, ["RECENT_MESSAGES", "ACTION_STATE"])
76
+
77
+ options_context = "\n".join(
78
+ f"- [{opt.get('id', idx)}] {opt.get('label', '')}: {opt.get('description', '')}"
79
+ for idx, opt in enumerate(available_options)
80
+ )
81
+
82
+ template = (
83
+ runtime.character.templates.get("chooseOptionTemplate")
84
+ if runtime.character.templates and "chooseOptionTemplate" in runtime.character.templates
85
+ else CHOOSE_OPTION_TEMPLATE
86
+ )
87
+ prompt = runtime.compose_prompt(state=state, template=template)
88
+ prompt = prompt.replace("{{options}}", options_context)
89
+
90
+ response_text = await runtime.use_model(ModelType.TEXT_LARGE, prompt=prompt)
91
+ parsed_xml = parse_key_value_xml(response_text)
92
+
93
+ if parsed_xml is None:
94
+ raise ValueError("Failed to parse XML response")
95
+
96
+ thought = str(parsed_xml.get("thought", ""))
97
+ selected_id = str(parsed_xml.get("selected_id", ""))
98
+
99
+ if not selected_id:
100
+ raise ValueError("No option selected")
101
+
102
+ selected_option = next(
103
+ (opt for opt in available_options if str(opt.get("id", "")) == selected_id),
104
+ None,
105
+ )
106
+
107
+ if selected_option is None:
108
+ raise ValueError(f"Selected option ID '{selected_id}' not found")
109
+
110
+ response_content = Content(
111
+ thought=thought,
112
+ text=f"Selected option: {selected_option.get('label', selected_id)}",
113
+ actions=["CHOOSE_OPTION"],
114
+ )
115
+
116
+ if callback:
117
+ await callback(response_content)
118
+
119
+ return ActionResult(
120
+ text=f"Selected option: {selected_option.get('label', selected_id)}",
121
+ values={
122
+ "success": True,
123
+ "selectedId": selected_id,
124
+ "selectedLabel": selected_option.get("label", ""),
125
+ "thought": thought,
126
+ },
127
+ data={
128
+ "actionName": "CHOOSE_OPTION",
129
+ "selectedOption": selected_option,
130
+ "thought": thought,
131
+ },
132
+ success=True,
133
+ )
134
+
135
+ @property
136
+ def examples(self) -> list[list[ActionExample]]:
137
+ return _convert_spec_examples()
138
+
139
+
140
+ choose_option_action = Action(
141
+ name=ChooseOptionAction.name,
142
+ similes=ChooseOptionAction().similes,
143
+ description=ChooseOptionAction.description,
144
+ validate=ChooseOptionAction().validate,
145
+ handler=ChooseOptionAction().handler,
146
+ examples=ChooseOptionAction().examples,
147
+ )
@@ -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.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("FOLLOW_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 spec_examples:
20
+ return [
21
+ [
22
+ ActionExample(
23
+ name=msg.get("name", ""),
24
+ content=Content(
25
+ text=msg.get("content", {}).get("text", ""),
26
+ actions=msg.get("content", {}).get("actions"),
27
+ ),
28
+ )
29
+ for msg in example
30
+ ]
31
+ for example in spec_examples
32
+ ]
33
+ return []
34
+
35
+
36
+ @dataclass
37
+ class FollowRoomAction:
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
+ room_id = message.room_id
46
+ if not room_id:
47
+ return False
48
+
49
+ room = await runtime.get_room(room_id)
50
+ if room is None:
51
+ return False
52
+
53
+ world_id = room.world_id
54
+ if world_id:
55
+ world = await runtime.get_world(world_id)
56
+ if world and world.metadata:
57
+ followed_rooms = world.metadata.get("followedRooms", [])
58
+ if str(room_id) in followed_rooms:
59
+ return False
60
+
61
+ return True
62
+
63
+ async def handler(
64
+ self,
65
+ runtime: IAgentRuntime,
66
+ message: Memory,
67
+ state: State | None = None,
68
+ options: HandlerOptions | None = None,
69
+ callback: HandlerCallback | None = None,
70
+ responses: list[Memory] | None = None,
71
+ ) -> ActionResult:
72
+ room_id = message.room_id
73
+ if not room_id:
74
+ return ActionResult(
75
+ text="No room specified to follow",
76
+ values={"success": False, "error": "no_room_id"},
77
+ data={"actionName": "FOLLOW_ROOM"},
78
+ success=False,
79
+ )
80
+
81
+ room = await runtime.get_room(room_id)
82
+ if room is None:
83
+ return ActionResult(
84
+ text="Room not found",
85
+ values={"success": False, "error": "room_not_found"},
86
+ data={"actionName": "FOLLOW_ROOM"},
87
+ success=False,
88
+ )
89
+
90
+ room_name = str(room.name) if room.name else "Unknown Room"
91
+
92
+ world_id = room.world_id
93
+ if world_id:
94
+ world = await runtime.get_world(world_id)
95
+ if world and world.metadata:
96
+ followed_rooms = list(world.metadata.get("followedRooms", []))
97
+ room_id_str = str(room_id)
98
+
99
+ if room_id_str not in followed_rooms:
100
+ followed_rooms.append(room_id_str)
101
+ world.metadata["followedRooms"] = followed_rooms
102
+ await runtime.update_world(world)
103
+
104
+ await runtime.create_memory(
105
+ content=Content(
106
+ text=f"Now following room: {room_name}",
107
+ actions=["FOLLOW_ROOM"],
108
+ ),
109
+ room_id=room_id,
110
+ entity_id=runtime.agent_id,
111
+ memory_type="action",
112
+ metadata={"type": "FOLLOW_ROOM", "roomName": room_name},
113
+ )
114
+
115
+ response_content = Content(
116
+ text=f"I am now following {room_name} and will monitor its messages.",
117
+ actions=["FOLLOW_ROOM"],
118
+ )
119
+
120
+ if callback:
121
+ await callback(response_content)
122
+
123
+ return ActionResult(
124
+ text=f"Now following room: {room_name}",
125
+ values={
126
+ "success": True,
127
+ "following": True,
128
+ "roomId": str(room_id),
129
+ "roomName": room_name,
130
+ },
131
+ data={
132
+ "actionName": "FOLLOW_ROOM",
133
+ "roomId": str(room_id),
134
+ "roomName": room_name,
135
+ },
136
+ success=True,
137
+ )
138
+
139
+ @property
140
+ def examples(self) -> list[list[ActionExample]]:
141
+ return _convert_spec_examples()
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
+ )