@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,203 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from collections.abc import Awaitable, Callable
5
+ from typing import TYPE_CHECKING
6
+
7
+ from elizaos.bootstrap.types import EvaluatorResult
8
+ from elizaos.generated.spec_helpers import require_evaluator_spec
9
+ from elizaos.types import ActionResult, Evaluator, HandlerOptions
10
+
11
+ if TYPE_CHECKING:
12
+ from elizaos.types import Content, IAgentRuntime, Memory, State
13
+
14
+ # Get text content from centralized specs
15
+ _spec = require_evaluator_spec("RELATIONSHIP_EXTRACTION")
16
+
17
+ X_HANDLE_PATTERN = re.compile(r"@[\w]+")
18
+ EMAIL_PATTERN = re.compile(r"[\w.+-]+@[\w.-]+\.\w+")
19
+ PHONE_PATTERN = re.compile(r"\+?[\d\s\-()]{10,}")
20
+ DISCORD_PATTERN = re.compile(r"[\w]+#\d{4}")
21
+
22
+
23
+ def extract_platform_identities(text: str) -> list[dict[str, str | bool | float]]:
24
+ identities: list[dict[str, str | bool | float]] = []
25
+
26
+ for match in X_HANDLE_PATTERN.finditer(text):
27
+ handle = match.group()
28
+ if handle.lower() not in ("@here", "@everyone", "@channel"):
29
+ identities.append(
30
+ {
31
+ "platform": "x",
32
+ "handle": handle,
33
+ "verified": False,
34
+ "confidence": 0.7,
35
+ }
36
+ )
37
+
38
+ for match in EMAIL_PATTERN.finditer(text):
39
+ identities.append(
40
+ {
41
+ "platform": "email",
42
+ "handle": match.group(),
43
+ "verified": False,
44
+ "confidence": 0.9,
45
+ }
46
+ )
47
+
48
+ for match in DISCORD_PATTERN.finditer(text):
49
+ identities.append(
50
+ {
51
+ "platform": "discord",
52
+ "handle": match.group(),
53
+ "verified": False,
54
+ "confidence": 0.8,
55
+ }
56
+ )
57
+
58
+ return identities
59
+
60
+
61
+ def detect_relationship_indicators(text: str) -> list[dict[str, str | float]]:
62
+ indicators: list[dict[str, str | float]] = []
63
+
64
+ friend_patterns = [
65
+ r"my friend",
66
+ r"good friend",
67
+ r"best friend",
68
+ r"close friend",
69
+ r"we're friends",
70
+ ]
71
+ for pattern in friend_patterns:
72
+ if re.search(pattern, text, re.IGNORECASE):
73
+ indicators.append(
74
+ {
75
+ "type": "friend",
76
+ "sentiment": "positive",
77
+ "confidence": 0.8,
78
+ }
79
+ )
80
+ break
81
+
82
+ colleague_patterns = [
83
+ r"my colleague",
84
+ r"coworker",
85
+ r"co-worker",
86
+ r"work together",
87
+ r"at work",
88
+ ]
89
+ for pattern in colleague_patterns:
90
+ if re.search(pattern, text, re.IGNORECASE):
91
+ indicators.append(
92
+ {
93
+ "type": "colleague",
94
+ "sentiment": "neutral",
95
+ "confidence": 0.8,
96
+ }
97
+ )
98
+ break
99
+
100
+ family_patterns = [
101
+ r"my (brother|sister|mom|dad|mother|father|parent|son|daughter|child)",
102
+ r"my family",
103
+ r"family member",
104
+ ]
105
+ for pattern in family_patterns:
106
+ if re.search(pattern, text, re.IGNORECASE):
107
+ indicators.append(
108
+ {
109
+ "type": "family",
110
+ "sentiment": "positive",
111
+ "confidence": 0.9,
112
+ }
113
+ )
114
+ break
115
+
116
+ return indicators
117
+
118
+
119
+ async def evaluate_relationship_extraction(
120
+ runtime: IAgentRuntime,
121
+ message: Memory,
122
+ state: State | None = None,
123
+ ) -> EvaluatorResult:
124
+ text = message.content.text if message.content else ""
125
+
126
+ if not text:
127
+ return EvaluatorResult(
128
+ score=50,
129
+ passed=True,
130
+ reason="No text to analyze",
131
+ details={"noText": True},
132
+ )
133
+
134
+ identities = extract_platform_identities(text)
135
+
136
+ indicators = detect_relationship_indicators(text)
137
+
138
+ if identities and message.entity_id:
139
+ entity = await runtime.get_entity(str(message.entity_id))
140
+ if entity:
141
+ metadata = entity.metadata or {}
142
+ existing_identities = metadata.get("platformIdentities", [])
143
+ if isinstance(existing_identities, list):
144
+ for identity in identities:
145
+ exists = any(
146
+ i.get("platform") == identity["platform"]
147
+ and i.get("handle") == identity["handle"]
148
+ for i in existing_identities
149
+ if isinstance(i, dict)
150
+ )
151
+ if not exists:
152
+ existing_identities.append(identity)
153
+ metadata["platformIdentities"] = existing_identities
154
+ entity.metadata = metadata
155
+ await runtime.update_entity(entity)
156
+
157
+ runtime.logger.info(
158
+ f"Completed extraction: src=evaluator:relationship_extraction agentId={runtime.agent_id} identitiesFound={len(identities)} indicatorsFound={len(indicators)}"
159
+ )
160
+
161
+ return EvaluatorResult(
162
+ score=70,
163
+ passed=True,
164
+ reason=f"Found {len(identities)} identities and {len(indicators)} relationship indicators",
165
+ details={
166
+ "identitiesCount": len(identities),
167
+ "indicatorsCount": len(indicators),
168
+ },
169
+ )
170
+
171
+
172
+ async def validate_relationship_extraction(
173
+ runtime: IAgentRuntime,
174
+ message: Memory,
175
+ _state: State | None = None,
176
+ ) -> bool:
177
+ return message.content is not None and bool(message.content.text)
178
+
179
+
180
+ async def _relationship_extraction_handler(
181
+ runtime: IAgentRuntime,
182
+ message: Memory,
183
+ state: State | None = None,
184
+ options: HandlerOptions | None = None,
185
+ callback: Callable[[Content], Awaitable[list[Memory]]] | None = None,
186
+ responses: list[Memory] | None = None,
187
+ ) -> ActionResult | None:
188
+ """Wrapper handler that matches the expected signature."""
189
+ _ = options, callback, responses # Unused parameters
190
+ result = await evaluate_relationship_extraction(runtime, message, state)
191
+ # Return None as ActionResult - evaluators don't typically return action results
192
+ return ActionResult(text=result.reason, success=result.passed, values={}, data={})
193
+
194
+
195
+ relationship_extraction_evaluator = Evaluator(
196
+ name=str(_spec["name"]),
197
+ description=str(_spec["description"]),
198
+ similes=list(_spec.get("similes", [])) if _spec.get("similes") else [],
199
+ validate=validate_relationship_extraction,
200
+ handler=_relationship_extraction_handler,
201
+ always_run=bool(_spec.get("alwaysRun", False)),
202
+ examples=[],
203
+ )
@@ -0,0 +1,36 @@
1
+ """Advanced Providers - Extended providers for agent operation.
2
+
3
+ Extended providers that can be enabled with `advanced_capabilities=True`.
4
+ """
5
+
6
+ from .agent_settings import agent_settings_provider
7
+ from .contacts import contacts_provider
8
+ from .facts import facts_provider
9
+ from .follow_ups import follow_ups_provider
10
+ from .knowledge import knowledge_provider
11
+ from .relationships import relationships_provider
12
+ from .roles import roles_provider
13
+ from .settings import settings_provider
14
+
15
+ __all__ = [
16
+ "agent_settings_provider",
17
+ "contacts_provider",
18
+ "facts_provider",
19
+ "follow_ups_provider",
20
+ "knowledge_provider",
21
+ "relationships_provider",
22
+ "roles_provider",
23
+ "settings_provider",
24
+ "advanced_providers",
25
+ ]
26
+
27
+ advanced_providers = [
28
+ contacts_provider,
29
+ facts_provider,
30
+ follow_ups_provider,
31
+ knowledge_provider,
32
+ relationships_provider,
33
+ roles_provider,
34
+ agent_settings_provider,
35
+ settings_provider,
36
+ ]
@@ -0,0 +1,60 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from elizaos.types import Provider, ProviderResult
6
+
7
+ if TYPE_CHECKING:
8
+ from elizaos.types import IAgentRuntime, Memory, State
9
+
10
+
11
+ SENSITIVE_KEY_PATTERNS = (
12
+ "key",
13
+ "secret",
14
+ "password",
15
+ "token",
16
+ "credential",
17
+ "auth",
18
+ "private",
19
+ )
20
+
21
+
22
+ async def get_agent_settings_context(
23
+ runtime: IAgentRuntime,
24
+ message: Memory,
25
+ state: State | None = None,
26
+ ) -> ProviderResult:
27
+ all_settings = runtime.get_all_settings()
28
+
29
+ safe_settings: dict[str, str] = {}
30
+ for key, value in all_settings.items():
31
+ if not any(pattern in key.lower() for pattern in SENSITIVE_KEY_PATTERNS):
32
+ safe_settings[key] = str(value)
33
+
34
+ sections: list[str] = []
35
+ if safe_settings:
36
+ sections.append("# Agent Settings")
37
+ for key, value in safe_settings.items():
38
+ display_value = value if len(value) <= 50 else value[:50] + "..."
39
+ sections.append(f"- {key}: {display_value}")
40
+
41
+ context_text = "\n".join(sections) if sections else ""
42
+
43
+ return ProviderResult(
44
+ text=context_text,
45
+ values={
46
+ "settingsCount": len(safe_settings),
47
+ "hasSettings": len(safe_settings) > 0,
48
+ },
49
+ data={
50
+ "settings": safe_settings,
51
+ },
52
+ )
53
+
54
+
55
+ agent_settings_provider = Provider(
56
+ name="AGENT_SETTINGS",
57
+ description="Provides the agent's current configuration settings (filtered for security)",
58
+ get=get_agent_settings_context,
59
+ dynamic=True,
60
+ )
@@ -0,0 +1,77 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from typing import TYPE_CHECKING
5
+
6
+ from elizaos.types import Provider, ProviderResult
7
+
8
+ if TYPE_CHECKING:
9
+ from elizaos.types import IAgentRuntime, Memory, State
10
+
11
+
12
+ async def get_contacts_context(
13
+ runtime: IAgentRuntime,
14
+ message: Memory,
15
+ state: State | None = None,
16
+ ) -> ProviderResult:
17
+ from elizaos.bootstrap.services.rolodex import RolodexService
18
+
19
+ rolodex_service = runtime.get_service("rolodex")
20
+ if not rolodex_service or not isinstance(rolodex_service, RolodexService):
21
+ return ProviderResult(text="", values={}, data={})
22
+
23
+ contacts = await rolodex_service.get_all_contacts()
24
+
25
+ if not contacts:
26
+ return ProviderResult(text="No contacts in rolodex.", values={"contactCount": 0}, data={})
27
+
28
+ entities = await asyncio.gather(
29
+ *(runtime.get_entity(str(contact.entity_id)) for contact in contacts)
30
+ )
31
+ contact_details: list[dict[str, str]] = []
32
+ for contact, entity in zip(contacts, entities, strict=False):
33
+ name = entity.name if entity and entity.name else "Unknown"
34
+ contact_details.append(
35
+ {
36
+ "id": str(contact.entity_id),
37
+ "name": name,
38
+ "categories": ",".join(contact.categories),
39
+ "tags": ",".join(contact.tags),
40
+ }
41
+ )
42
+
43
+ grouped: dict[str, list[dict[str, str]]] = {}
44
+ for detail in contact_details:
45
+ for cat in detail["categories"].split(","):
46
+ cat = cat.strip()
47
+ if cat:
48
+ grouped.setdefault(cat, []).append(detail)
49
+
50
+ text_summary = f"You have {len(contacts)} contacts in your rolodex:\n"
51
+
52
+ for category, items in grouped.items():
53
+ text_summary += f"\n{category.capitalize()}s ({len(items)}):\n"
54
+ for item in items:
55
+ text_summary += f"- {item['name']}"
56
+ if item["tags"]:
57
+ text_summary += f" [{item['tags']}]"
58
+ text_summary += "\n"
59
+
60
+ category_counts = {cat: len(items) for cat, items in grouped.items()}
61
+
62
+ return ProviderResult(
63
+ text=text_summary.strip(),
64
+ values={
65
+ "contactCount": len(contacts),
66
+ **category_counts,
67
+ },
68
+ data=category_counts,
69
+ )
70
+
71
+
72
+ contacts_provider = Provider(
73
+ name="CONTACTS",
74
+ description="Provides contact information from the rolodex",
75
+ get=get_contacts_context,
76
+ dynamic=True,
77
+ )
@@ -0,0 +1,82 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from elizaos.types import Provider, ProviderResult
6
+
7
+ if TYPE_CHECKING:
8
+ from elizaos.types import IAgentRuntime, Memory, State
9
+
10
+
11
+ async def get_facts_context(
12
+ runtime: IAgentRuntime,
13
+ message: Memory,
14
+ state: State | None = None,
15
+ ) -> ProviderResult:
16
+ sections: list[str] = []
17
+ facts_list: list[dict[str, str]] = []
18
+
19
+ entity_id = message.entity_id
20
+ room_id = message.room_id
21
+
22
+ if entity_id:
23
+ sender_facts = await runtime.get_memories(
24
+ entity_id=entity_id,
25
+ memory_type="fact",
26
+ limit=10,
27
+ )
28
+
29
+ if sender_facts:
30
+ sender = await runtime.get_entity(entity_id)
31
+ sender_name = sender.name if sender and sender.name else "User"
32
+ sections.append(f"\n## Facts about {sender_name}")
33
+
34
+ for fact in sender_facts:
35
+ if fact.content and fact.content.text:
36
+ fact_text = fact.content.text
37
+ if len(fact_text) > 200:
38
+ fact_text = fact_text[:200] + "..."
39
+ facts_list.append(
40
+ {"entityId": str(entity_id), "entityName": sender_name, "fact": fact_text}
41
+ )
42
+ sections.append(f"- {fact_text}")
43
+
44
+ if room_id:
45
+ room_facts = await runtime.get_memories(
46
+ room_id=room_id,
47
+ memory_type="fact",
48
+ limit=5,
49
+ )
50
+
51
+ if room_facts:
52
+ sections.append("\n## Room Context Facts")
53
+ for fact in room_facts:
54
+ if fact.content and fact.content.text:
55
+ fact_text = fact.content.text
56
+ if len(fact_text) > 200:
57
+ fact_text = fact_text[:200] + "..."
58
+ facts_list.append({"roomId": str(room_id), "fact": fact_text})
59
+ sections.append(f"- {fact_text}")
60
+
61
+ context_text = ""
62
+ if sections:
63
+ context_text = "# Known Facts" + "\n".join(sections)
64
+
65
+ return ProviderResult(
66
+ text=context_text,
67
+ values={
68
+ "factCount": len(facts_list),
69
+ "hasFacts": len(facts_list) > 0,
70
+ },
71
+ data={
72
+ "facts": facts_list,
73
+ },
74
+ )
75
+
76
+
77
+ facts_provider = Provider(
78
+ name="FACTS",
79
+ description="Provides known facts about entities learned through conversation",
80
+ get=get_facts_context,
81
+ dynamic=True,
82
+ )
@@ -0,0 +1,113 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from datetime import UTC, datetime
5
+ from typing import TYPE_CHECKING
6
+
7
+ from elizaos.types import Provider, ProviderResult
8
+
9
+ if TYPE_CHECKING:
10
+ from elizaos.types import IAgentRuntime, Memory, State
11
+
12
+
13
+ async def get_follow_ups_context(
14
+ runtime: IAgentRuntime,
15
+ message: Memory,
16
+ state: State | None = None,
17
+ ) -> ProviderResult:
18
+ from elizaos.bootstrap.services.follow_up import FollowUpService
19
+
20
+ follow_up_service = runtime.get_service("follow_up")
21
+ if not follow_up_service or not isinstance(follow_up_service, FollowUpService):
22
+ return ProviderResult(text="", values={}, data={})
23
+
24
+ upcoming = await follow_up_service.get_upcoming_follow_ups(days_ahead=7, include_overdue=True)
25
+
26
+ if not upcoming:
27
+ return ProviderResult(
28
+ text="No upcoming follow-ups scheduled.", values={"followUpCount": 0}, data={}
29
+ )
30
+
31
+ now = datetime.now(UTC)
32
+
33
+ overdue_items: list[dict[str, str]] = []
34
+ upcoming_items: list[dict[str, str]] = []
35
+
36
+ entities = await asyncio.gather(*(runtime.get_entity(str(task.entity_id)) for task in upcoming))
37
+ for task, entity in zip(upcoming, entities, strict=False):
38
+ scheduled = datetime.fromisoformat(task.scheduled_at.replace("Z", "+00:00"))
39
+ name = entity.name if entity and entity.name else "Unknown"
40
+
41
+ item = {
42
+ "entityId": str(task.entity_id),
43
+ "name": name,
44
+ "scheduledAt": task.scheduled_at,
45
+ "reason": task.reason,
46
+ "priority": task.priority,
47
+ }
48
+
49
+ if scheduled < now:
50
+ days_overdue = (now - scheduled).days
51
+ item["daysOverdue"] = str(days_overdue)
52
+ overdue_items.append(item)
53
+ else:
54
+ days_until = (scheduled - now).days
55
+ item["daysUntil"] = str(days_until)
56
+ upcoming_items.append(item)
57
+
58
+ text_summary = f"You have {len(upcoming)} follow-up(s) scheduled:\n"
59
+
60
+ if overdue_items:
61
+ text_summary += f"\nOverdue ({len(overdue_items)}):\n"
62
+ for item in overdue_items:
63
+ text_summary += f"- {item['name']} ({item['daysOverdue']} days overdue)"
64
+ if item.get("reason"):
65
+ text_summary += f" - {item['reason']}"
66
+ text_summary += "\n"
67
+
68
+ if upcoming_items:
69
+ text_summary += f"\nUpcoming ({len(upcoming_items)}):\n"
70
+ for item in upcoming_items:
71
+ days = int(item["daysUntil"])
72
+ if days == 0:
73
+ time_str = "today"
74
+ elif days == 1:
75
+ time_str = "tomorrow"
76
+ else:
77
+ time_str = f"in {days} days"
78
+ text_summary += f"- {item['name']} ({time_str})"
79
+ if item.get("reason"):
80
+ text_summary += f" - {item['reason']}"
81
+ text_summary += "\n"
82
+
83
+ suggestions = await follow_up_service.get_follow_up_suggestions()
84
+ if suggestions:
85
+ text_summary += "\nSuggested follow-ups:\n"
86
+ for s in suggestions[:3]:
87
+ text_summary += (
88
+ f"- {s.entity_name} ({s.days_since_last_contact} days since last contact)\n"
89
+ )
90
+
91
+ return ProviderResult(
92
+ text=text_summary.strip(),
93
+ values={
94
+ "followUpCount": len(upcoming),
95
+ "overdueCount": len(overdue_items),
96
+ "upcomingCount": len(upcoming_items),
97
+ "suggestionsCount": len(suggestions),
98
+ },
99
+ data={
100
+ "followUpCount": len(upcoming),
101
+ "overdueCount": len(overdue_items),
102
+ "upcomingCount": len(upcoming_items),
103
+ "suggestionsCount": len(suggestions),
104
+ },
105
+ )
106
+
107
+
108
+ follow_ups_provider = Provider(
109
+ name="FOLLOW_UPS",
110
+ description="Provides information about upcoming follow-ups and reminders",
111
+ get=get_follow_ups_context,
112
+ dynamic=True,
113
+ )
@@ -0,0 +1,83 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from elizaos.types import Provider, ProviderResult
6
+
7
+ if TYPE_CHECKING:
8
+ from elizaos.types import IAgentRuntime, Memory, State
9
+
10
+
11
+ async def get_knowledge_context(
12
+ runtime: IAgentRuntime,
13
+ message: Memory,
14
+ state: State | None = None,
15
+ ) -> ProviderResult:
16
+ sections: list[str] = []
17
+ knowledge_entries: list[dict[str, str]] = []
18
+
19
+ query_text = ""
20
+ if message.content and message.content.text:
21
+ query_text = message.content.text
22
+
23
+ if not query_text:
24
+ return ProviderResult(
25
+ text="", values={"knowledgeCount": 0, "hasKnowledge": False}, data={"entries": []}
26
+ )
27
+
28
+ relevant_knowledge = await runtime.search_knowledge(
29
+ query=query_text,
30
+ limit=5,
31
+ )
32
+
33
+ for entry in relevant_knowledge:
34
+ # Handle both dict and object entries
35
+ if isinstance(entry, dict):
36
+ content = entry.get("content", {})
37
+ text = content.get("text", "") if isinstance(content, dict) else ""
38
+ entry_id = entry.get("id", "")
39
+ metadata = entry.get("metadata", {})
40
+ else:
41
+ content = getattr(entry, "content", None)
42
+ text = getattr(content, "text", "") if content else ""
43
+ entry_id = getattr(entry, "id", "")
44
+ metadata = getattr(entry, "metadata", {})
45
+
46
+ if text:
47
+ knowledge_text = text
48
+ if len(knowledge_text) > 500:
49
+ knowledge_text = knowledge_text[:500] + "..."
50
+
51
+ source = "unknown"
52
+ if isinstance(metadata, dict):
53
+ source = str(metadata.get("source", "unknown"))
54
+
55
+ entry_dict = {
56
+ "id": str(entry_id) if entry_id else "",
57
+ "text": knowledge_text,
58
+ "source": source,
59
+ }
60
+ knowledge_entries.append(entry_dict)
61
+ sections.append(f"- {knowledge_text}")
62
+
63
+ context_text = "# Relevant Knowledge\n" + "\n".join(sections) if sections else ""
64
+
65
+ return ProviderResult(
66
+ text=context_text,
67
+ values={
68
+ "knowledgeCount": len(knowledge_entries),
69
+ "hasKnowledge": len(knowledge_entries) > 0,
70
+ },
71
+ data={
72
+ "entries": knowledge_entries,
73
+ "query": query_text,
74
+ },
75
+ )
76
+
77
+
78
+ knowledge_provider = Provider(
79
+ name="KNOWLEDGE",
80
+ description="Provides relevant knowledge from the agent's knowledge base based on semantic similarity",
81
+ get=get_knowledge_context,
82
+ dynamic=True,
83
+ )