@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,138 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from datetime import UTC, datetime
5
+ from typing import TYPE_CHECKING
6
+ from uuid import UUID
7
+
8
+ from elizaos.types import Service, ServiceType
9
+
10
+ if TYPE_CHECKING:
11
+ from elizaos.types import IAgentRuntime
12
+
13
+
14
+ @dataclass
15
+ class FollowUpTask:
16
+ entity_id: UUID
17
+ reason: str
18
+ message: str | None = None
19
+ priority: str = "medium"
20
+ scheduled_at: str = ""
21
+ metadata: dict[str, str | int | float | bool] = field(default_factory=dict)
22
+
23
+
24
+ @dataclass
25
+ class FollowUpSuggestion:
26
+ entity_id: UUID
27
+ entity_name: str
28
+ days_since_last_contact: int
29
+ relationship_strength: float
30
+ suggested_reason: str
31
+
32
+
33
+ class FollowUpService(Service):
34
+ name = "follow_up"
35
+ service_type = ServiceType.TASK
36
+
37
+ @property
38
+ def capability_description(self) -> str:
39
+ return "Follow-up scheduling and reminder management service"
40
+
41
+ def __init__(self) -> None:
42
+ self._follow_ups: dict[UUID, FollowUpTask] = {}
43
+ self._runtime: IAgentRuntime | None = None
44
+
45
+ @classmethod
46
+ async def start(cls, runtime: IAgentRuntime) -> FollowUpService:
47
+ service = cls()
48
+ service._runtime = runtime
49
+ runtime.logger.info(
50
+ "Follow-up service started",
51
+ src="service:follow_up",
52
+ agentId=str(runtime.agent_id),
53
+ )
54
+ return service
55
+
56
+ async def stop(self) -> None:
57
+ if self._runtime:
58
+ self._runtime.logger.info(
59
+ "Follow-up service stopped",
60
+ src="service:follow_up",
61
+ agentId=str(self._runtime.agent_id),
62
+ )
63
+ self._follow_ups.clear()
64
+ self._runtime = None
65
+
66
+ async def schedule_follow_up(
67
+ self,
68
+ entity_id: UUID,
69
+ scheduled_at: datetime,
70
+ reason: str,
71
+ priority: str = "medium",
72
+ message: str | None = None,
73
+ ) -> FollowUpTask:
74
+ task = FollowUpTask(
75
+ entity_id=entity_id,
76
+ reason=reason,
77
+ message=message,
78
+ priority=priority,
79
+ scheduled_at=scheduled_at.isoformat(),
80
+ )
81
+
82
+ self._follow_ups[entity_id] = task
83
+
84
+ if self._runtime:
85
+ self._runtime.logger.info(
86
+ f"Scheduled follow-up with {entity_id}",
87
+ src="service:follow_up",
88
+ scheduled_at=task.scheduled_at,
89
+ )
90
+
91
+ return task
92
+
93
+ async def get_follow_up(self, entity_id: UUID) -> FollowUpTask | None:
94
+ return self._follow_ups.get(entity_id)
95
+
96
+ async def cancel_follow_up(self, entity_id: UUID) -> bool:
97
+ if entity_id in self._follow_ups:
98
+ del self._follow_ups[entity_id]
99
+ return True
100
+ return False
101
+
102
+ async def get_upcoming_follow_ups(
103
+ self,
104
+ days_ahead: int = 7,
105
+ include_overdue: bool = True,
106
+ ) -> list[FollowUpTask]:
107
+ now = datetime.now(UTC)
108
+ results: list[FollowUpTask] = []
109
+
110
+ for task in self._follow_ups.values():
111
+ scheduled = datetime.fromisoformat(task.scheduled_at.replace("Z", "+00:00"))
112
+ days_until = (scheduled - now).days
113
+
114
+ if include_overdue and days_until < 0 or 0 <= days_until <= days_ahead:
115
+ results.append(task)
116
+
117
+ results.sort(key=lambda t: t.scheduled_at)
118
+ return results
119
+
120
+ async def get_overdue_follow_ups(self) -> list[FollowUpTask]:
121
+ now = datetime.now(UTC)
122
+ results: list[FollowUpTask] = []
123
+
124
+ for task in self._follow_ups.values():
125
+ scheduled = datetime.fromisoformat(task.scheduled_at.replace("Z", "+00:00"))
126
+ if scheduled < now:
127
+ results.append(task)
128
+
129
+ return results
130
+
131
+ async def get_follow_up_suggestions(
132
+ self,
133
+ max_suggestions: int = 5,
134
+ ) -> list[FollowUpSuggestion]:
135
+ return []
136
+
137
+ async def complete_follow_up(self, entity_id: UUID) -> bool:
138
+ return await self.cancel_follow_up(entity_id)
@@ -0,0 +1,244 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from datetime import datetime
5
+ from enum import Enum
6
+ from typing import TYPE_CHECKING
7
+ from uuid import UUID
8
+
9
+ from elizaos.types import Service, ServiceType
10
+
11
+ if TYPE_CHECKING:
12
+ from elizaos.types import IAgentRuntime
13
+
14
+
15
+ class ContactCategory(str, Enum):
16
+ FRIEND = "friend"
17
+ FAMILY = "family"
18
+ COLLEAGUE = "colleague"
19
+ ACQUAINTANCE = "acquaintance"
20
+ VIP = "vip"
21
+ BUSINESS = "business"
22
+
23
+
24
+ @dataclass
25
+ class ContactPreferences:
26
+ preferred_channel: str | None = None
27
+ timezone: str | None = None
28
+ language: str | None = None
29
+ contact_frequency: str | None = None
30
+ do_not_disturb: bool = False
31
+ notes: str | None = None
32
+
33
+
34
+ @dataclass
35
+ class ContactInfo:
36
+ entity_id: UUID
37
+ categories: list[str] = field(default_factory=list)
38
+ tags: list[str] = field(default_factory=list)
39
+ preferences: ContactPreferences = field(default_factory=ContactPreferences)
40
+ custom_fields: dict[str, str | int | float | bool] = field(default_factory=dict)
41
+ privacy_level: str = "private"
42
+ last_modified: str = ""
43
+
44
+
45
+ @dataclass
46
+ class RelationshipAnalytics:
47
+ strength: float = 0.0
48
+ interaction_count: int = 0
49
+ last_interaction_at: str | None = None
50
+ average_response_time: float | None = None
51
+ sentiment_score: float | None = None
52
+ topics_discussed: list[str] = field(default_factory=list)
53
+
54
+
55
+ def calculate_relationship_strength(
56
+ interaction_count: int,
57
+ last_interaction_at: str | None = None,
58
+ message_quality: float = 5.0,
59
+ relationship_type: str = "acquaintance",
60
+ ) -> float:
61
+ interaction_score = min(interaction_count * 2, 40)
62
+
63
+ recency_score = 0.0
64
+ if last_interaction_at:
65
+ last_dt = datetime.fromisoformat(last_interaction_at.replace("Z", "+00:00"))
66
+ days_since = (datetime.now(last_dt.tzinfo) - last_dt).days
67
+ if days_since < 1:
68
+ recency_score = 30
69
+ elif days_since < 7:
70
+ recency_score = 25
71
+ elif days_since < 30:
72
+ recency_score = 15
73
+ elif days_since < 90:
74
+ recency_score = 5
75
+
76
+ quality_score = min(message_quality * 2, 20)
77
+
78
+ relationship_bonus = {
79
+ "family": 10,
80
+ "friend": 8,
81
+ "colleague": 6,
82
+ "acquaintance": 4,
83
+ "unknown": 0,
84
+ }
85
+
86
+ total = (
87
+ interaction_score
88
+ + recency_score
89
+ + quality_score
90
+ + relationship_bonus.get(relationship_type, 0)
91
+ )
92
+ return max(0.0, min(100.0, round(total, 1)))
93
+
94
+
95
+ class RolodexService(Service):
96
+ name = "rolodex"
97
+ service_type = ServiceType.UNKNOWN
98
+
99
+ @property
100
+ def capability_description(self) -> str:
101
+ return "Comprehensive contact and relationship management service"
102
+
103
+ def __init__(self) -> None:
104
+ self._contacts: dict[UUID, ContactInfo] = {}
105
+ self._analytics: dict[str, RelationshipAnalytics] = {}
106
+ self._runtime: IAgentRuntime | None = None
107
+
108
+ @classmethod
109
+ async def start(cls, runtime: IAgentRuntime) -> RolodexService:
110
+ service = cls()
111
+ service._runtime = runtime
112
+ runtime.logger.info(
113
+ "Rolodex service started",
114
+ src="service:rolodex",
115
+ agentId=str(runtime.agent_id),
116
+ )
117
+ return service
118
+
119
+ async def stop(self) -> None:
120
+ if self._runtime:
121
+ self._runtime.logger.info(
122
+ "Rolodex service stopped",
123
+ src="service:rolodex",
124
+ agentId=str(self._runtime.agent_id),
125
+ )
126
+ self._contacts.clear()
127
+ self._analytics.clear()
128
+ self._runtime = None
129
+
130
+ async def add_contact(
131
+ self,
132
+ entity_id: UUID,
133
+ categories: list[str] | None = None,
134
+ preferences: ContactPreferences | None = None,
135
+ custom_fields: dict[str, str | int | float | bool] | None = None,
136
+ ) -> ContactInfo:
137
+ contact = ContactInfo(
138
+ entity_id=entity_id,
139
+ categories=categories or ["acquaintance"],
140
+ tags=[],
141
+ preferences=preferences or ContactPreferences(),
142
+ custom_fields=custom_fields or {},
143
+ privacy_level="private",
144
+ last_modified=datetime.utcnow().isoformat(),
145
+ )
146
+
147
+ self._contacts[entity_id] = contact
148
+
149
+ if self._runtime:
150
+ self._runtime.logger.info(
151
+ f"Added contact {entity_id}",
152
+ src="service:rolodex",
153
+ categories=contact.categories,
154
+ )
155
+
156
+ return contact
157
+
158
+ async def get_contact(self, entity_id: UUID) -> ContactInfo | None:
159
+ return self._contacts.get(entity_id)
160
+
161
+ async def update_contact(
162
+ self,
163
+ entity_id: UUID,
164
+ categories: list[str] | None = None,
165
+ tags: list[str] | None = None,
166
+ preferences: ContactPreferences | None = None,
167
+ custom_fields: dict[str, str | int | float | bool] | None = None,
168
+ ) -> ContactInfo | None:
169
+ contact = self._contacts.get(entity_id)
170
+ if not contact:
171
+ return None
172
+
173
+ if categories is not None:
174
+ contact.categories = categories
175
+ if tags is not None:
176
+ contact.tags = tags
177
+ if preferences is not None:
178
+ contact.preferences = preferences
179
+ if custom_fields is not None:
180
+ contact.custom_fields = custom_fields
181
+
182
+ contact.last_modified = datetime.utcnow().isoformat()
183
+
184
+ return contact
185
+
186
+ async def remove_contact(self, entity_id: UUID) -> bool:
187
+ if entity_id in self._contacts:
188
+ del self._contacts[entity_id]
189
+ return True
190
+ return False
191
+
192
+ async def search_contacts(
193
+ self,
194
+ categories: list[str] | None = None,
195
+ tags: list[str] | None = None,
196
+ search_term: str | None = None,
197
+ ) -> list[ContactInfo]:
198
+ results = list(self._contacts.values())
199
+
200
+ if categories:
201
+ results = [c for c in results if any(cat in c.categories for cat in categories)]
202
+
203
+ if tags:
204
+ results = [c for c in results if any(tag in c.tags for tag in tags)]
205
+
206
+ return results
207
+
208
+ async def get_all_contacts(self) -> list[ContactInfo]:
209
+ return list(self._contacts.values())
210
+
211
+ async def get_relationship_analytics(
212
+ self,
213
+ entity_id: UUID,
214
+ ) -> RelationshipAnalytics | None:
215
+ key = str(entity_id)
216
+ return self._analytics.get(key)
217
+
218
+ async def update_relationship_analytics(
219
+ self,
220
+ entity_id: UUID,
221
+ interaction_count: int | None = None,
222
+ last_interaction_at: str | None = None,
223
+ ) -> RelationshipAnalytics:
224
+ key = str(entity_id)
225
+ analytics = self._analytics.get(key) or RelationshipAnalytics()
226
+
227
+ if interaction_count is not None:
228
+ analytics.interaction_count = interaction_count
229
+ if last_interaction_at is not None:
230
+ analytics.last_interaction_at = last_interaction_at
231
+
232
+ contact = self._contacts.get(entity_id)
233
+ relationship_type = "acquaintance"
234
+ if contact and contact.categories:
235
+ relationship_type = contact.categories[0]
236
+
237
+ analytics.strength = calculate_relationship_strength(
238
+ analytics.interaction_count,
239
+ analytics.last_interaction_at,
240
+ relationship_type=relationship_type,
241
+ )
242
+
243
+ self._analytics[key] = analytics
244
+ return analytics