@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.
- package/LICENSE +26 -0
- package/README.md +239 -0
- package/elizaos/__init__.py +280 -0
- package/elizaos/action_docs.py +149 -0
- package/elizaos/advanced_capabilities/__init__.py +85 -0
- package/elizaos/advanced_capabilities/actions/__init__.py +54 -0
- package/elizaos/advanced_capabilities/actions/add_contact.py +139 -0
- package/elizaos/advanced_capabilities/actions/follow_room.py +151 -0
- package/elizaos/advanced_capabilities/actions/image_generation.py +148 -0
- package/elizaos/advanced_capabilities/actions/mute_room.py +164 -0
- package/elizaos/advanced_capabilities/actions/remove_contact.py +145 -0
- package/elizaos/advanced_capabilities/actions/roles.py +207 -0
- package/elizaos/advanced_capabilities/actions/schedule_follow_up.py +154 -0
- package/elizaos/advanced_capabilities/actions/search_contacts.py +145 -0
- package/elizaos/advanced_capabilities/actions/send_message.py +187 -0
- package/elizaos/advanced_capabilities/actions/settings.py +151 -0
- package/elizaos/advanced_capabilities/actions/unfollow_room.py +164 -0
- package/elizaos/advanced_capabilities/actions/unmute_room.py +164 -0
- package/elizaos/advanced_capabilities/actions/update_contact.py +164 -0
- package/elizaos/advanced_capabilities/actions/update_entity.py +161 -0
- package/elizaos/advanced_capabilities/evaluators/__init__.py +18 -0
- package/elizaos/advanced_capabilities/evaluators/reflection.py +134 -0
- package/elizaos/advanced_capabilities/evaluators/relationship_extraction.py +203 -0
- package/elizaos/advanced_capabilities/providers/__init__.py +36 -0
- package/elizaos/advanced_capabilities/providers/agent_settings.py +60 -0
- package/elizaos/advanced_capabilities/providers/contacts.py +77 -0
- package/elizaos/advanced_capabilities/providers/facts.py +82 -0
- package/elizaos/advanced_capabilities/providers/follow_ups.py +113 -0
- package/elizaos/advanced_capabilities/providers/knowledge.py +83 -0
- package/elizaos/advanced_capabilities/providers/relationships.py +112 -0
- package/elizaos/advanced_capabilities/providers/roles.py +97 -0
- package/elizaos/advanced_capabilities/providers/settings.py +51 -0
- package/elizaos/advanced_capabilities/services/__init__.py +18 -0
- package/elizaos/advanced_capabilities/services/follow_up.py +138 -0
- package/elizaos/advanced_capabilities/services/rolodex.py +244 -0
- package/elizaos/advanced_memory/__init__.py +3 -0
- package/elizaos/advanced_memory/evaluators.py +97 -0
- package/elizaos/advanced_memory/memory_service.py +556 -0
- package/elizaos/advanced_memory/plugin.py +30 -0
- package/elizaos/advanced_memory/prompts.py +12 -0
- package/elizaos/advanced_memory/providers.py +90 -0
- package/elizaos/advanced_memory/types.py +65 -0
- package/elizaos/advanced_planning/__init__.py +10 -0
- package/elizaos/advanced_planning/actions.py +145 -0
- package/elizaos/advanced_planning/message_classifier.py +127 -0
- package/elizaos/advanced_planning/planning_service.py +712 -0
- package/elizaos/advanced_planning/plugin.py +40 -0
- package/elizaos/advanced_planning/prompts.py +4 -0
- package/elizaos/basic_capabilities/__init__.py +66 -0
- package/elizaos/basic_capabilities/actions/__init__.py +24 -0
- package/elizaos/basic_capabilities/actions/choice.py +140 -0
- package/elizaos/basic_capabilities/actions/ignore.py +66 -0
- package/elizaos/basic_capabilities/actions/none.py +56 -0
- package/elizaos/basic_capabilities/actions/reply.py +120 -0
- package/elizaos/basic_capabilities/providers/__init__.py +54 -0
- package/elizaos/basic_capabilities/providers/action_state.py +113 -0
- package/elizaos/basic_capabilities/providers/actions.py +263 -0
- package/elizaos/basic_capabilities/providers/attachments.py +76 -0
- package/elizaos/basic_capabilities/providers/capabilities.py +62 -0
- package/elizaos/basic_capabilities/providers/character.py +113 -0
- package/elizaos/basic_capabilities/providers/choice.py +73 -0
- package/elizaos/basic_capabilities/providers/context_bench.py +44 -0
- package/elizaos/basic_capabilities/providers/current_time.py +58 -0
- package/elizaos/basic_capabilities/providers/entities.py +99 -0
- package/elizaos/basic_capabilities/providers/evaluators.py +54 -0
- package/elizaos/basic_capabilities/providers/providers_list.py +55 -0
- package/elizaos/basic_capabilities/providers/recent_messages.py +85 -0
- package/elizaos/basic_capabilities/providers/time.py +45 -0
- package/elizaos/basic_capabilities/providers/world.py +93 -0
- package/elizaos/basic_capabilities/services/__init__.py +18 -0
- package/elizaos/basic_capabilities/services/embedding.py +122 -0
- package/elizaos/basic_capabilities/services/task.py +178 -0
- package/elizaos/bootstrap/__init__.py +12 -0
- package/elizaos/bootstrap/actions/__init__.py +68 -0
- package/elizaos/bootstrap/actions/add_contact.py +149 -0
- package/elizaos/bootstrap/actions/choice.py +147 -0
- package/elizaos/bootstrap/actions/follow_room.py +151 -0
- package/elizaos/bootstrap/actions/ignore.py +80 -0
- package/elizaos/bootstrap/actions/image_generation.py +135 -0
- package/elizaos/bootstrap/actions/mute_room.py +151 -0
- package/elizaos/bootstrap/actions/none.py +71 -0
- package/elizaos/bootstrap/actions/remove_contact.py +159 -0
- package/elizaos/bootstrap/actions/reply.py +140 -0
- package/elizaos/bootstrap/actions/roles.py +193 -0
- package/elizaos/bootstrap/actions/schedule_follow_up.py +164 -0
- package/elizaos/bootstrap/actions/search_contacts.py +159 -0
- package/elizaos/bootstrap/actions/send_message.py +173 -0
- package/elizaos/bootstrap/actions/settings.py +165 -0
- package/elizaos/bootstrap/actions/unfollow_room.py +151 -0
- package/elizaos/bootstrap/actions/unmute_room.py +151 -0
- package/elizaos/bootstrap/actions/update_contact.py +178 -0
- package/elizaos/bootstrap/actions/update_entity.py +175 -0
- package/elizaos/bootstrap/autonomy/__init__.py +18 -0
- package/elizaos/bootstrap/autonomy/action.py +197 -0
- package/elizaos/bootstrap/autonomy/providers.py +165 -0
- package/elizaos/bootstrap/autonomy/routes.py +171 -0
- package/elizaos/bootstrap/autonomy/service.py +562 -0
- package/elizaos/bootstrap/autonomy/types.py +18 -0
- package/elizaos/bootstrap/evaluators/__init__.py +19 -0
- package/elizaos/bootstrap/evaluators/reflection.py +118 -0
- package/elizaos/bootstrap/evaluators/relationship_extraction.py +192 -0
- package/elizaos/bootstrap/plugin.py +140 -0
- package/elizaos/bootstrap/providers/__init__.py +80 -0
- package/elizaos/bootstrap/providers/action_state.py +71 -0
- package/elizaos/bootstrap/providers/actions.py +256 -0
- package/elizaos/bootstrap/providers/agent_settings.py +63 -0
- package/elizaos/bootstrap/providers/attachments.py +76 -0
- package/elizaos/bootstrap/providers/capabilities.py +66 -0
- package/elizaos/bootstrap/providers/character.py +128 -0
- package/elizaos/bootstrap/providers/choice.py +77 -0
- package/elizaos/bootstrap/providers/contacts.py +78 -0
- package/elizaos/bootstrap/providers/context_bench.py +49 -0
- package/elizaos/bootstrap/providers/current_time.py +56 -0
- package/elizaos/bootstrap/providers/entities.py +99 -0
- package/elizaos/bootstrap/providers/evaluators.py +58 -0
- package/elizaos/bootstrap/providers/facts.py +86 -0
- package/elizaos/bootstrap/providers/follow_ups.py +116 -0
- package/elizaos/bootstrap/providers/knowledge.py +73 -0
- package/elizaos/bootstrap/providers/providers_list.py +59 -0
- package/elizaos/bootstrap/providers/recent_messages.py +85 -0
- package/elizaos/bootstrap/providers/relationships.py +106 -0
- package/elizaos/bootstrap/providers/roles.py +95 -0
- package/elizaos/bootstrap/providers/settings.py +55 -0
- package/elizaos/bootstrap/providers/time.py +45 -0
- package/elizaos/bootstrap/providers/world.py +97 -0
- package/elizaos/bootstrap/services/__init__.py +26 -0
- package/elizaos/bootstrap/services/embedding.py +122 -0
- package/elizaos/bootstrap/services/follow_up.py +138 -0
- package/elizaos/bootstrap/services/rolodex.py +244 -0
- package/elizaos/bootstrap/services/task.py +585 -0
- package/elizaos/bootstrap/types.py +54 -0
- package/elizaos/bootstrap/utils/__init__.py +7 -0
- package/elizaos/bootstrap/utils/xml.py +69 -0
- package/elizaos/character.py +149 -0
- package/elizaos/logger.py +179 -0
- package/elizaos/media/__init__.py +45 -0
- package/elizaos/media/mime.py +315 -0
- package/elizaos/media/search.py +161 -0
- package/elizaos/media/tests/__init__.py +1 -0
- package/elizaos/media/tests/test_mime.py +117 -0
- package/elizaos/media/tests/test_search.py +156 -0
- package/elizaos/plugin.py +191 -0
- package/elizaos/prompts.py +1071 -0
- package/elizaos/py.typed +0 -0
- package/elizaos/runtime.py +2572 -0
- package/elizaos/services/__init__.py +49 -0
- package/elizaos/services/hook_service.py +511 -0
- package/elizaos/services/message_service.py +1248 -0
- package/elizaos/settings.py +182 -0
- package/elizaos/streaming_context.py +159 -0
- package/elizaos/trajectory_context.py +18 -0
- package/elizaos/types/__init__.py +512 -0
- package/elizaos/types/agent.py +31 -0
- package/elizaos/types/components.py +208 -0
- package/elizaos/types/database.py +64 -0
- package/elizaos/types/environment.py +46 -0
- package/elizaos/types/events.py +47 -0
- package/elizaos/types/memory.py +45 -0
- package/elizaos/types/model.py +393 -0
- package/elizaos/types/plugin.py +188 -0
- package/elizaos/types/primitives.py +100 -0
- package/elizaos/types/runtime.py +460 -0
- package/elizaos/types/service.py +113 -0
- package/elizaos/types/service_interfaces.py +244 -0
- package/elizaos/types/state.py +188 -0
- package/elizaos/types/task.py +29 -0
- package/elizaos/utils/__init__.py +108 -0
- package/elizaos/utils/spec_examples.py +48 -0
- package/elizaos/utils/streaming.py +426 -0
- package/elizaos_atropos_shared/__init__.py +1 -0
- package/elizaos_atropos_shared/canonical_eliza.py +282 -0
- package/package.json +19 -0
- package/pyproject.toml +143 -0
- package/requirements-dev.in +11 -0
- package/requirements-dev.lock +134 -0
- package/requirements.in +9 -0
- package/requirements.lock +64 -0
- package/tests/__init__.py +0 -0
- package/tests/test_action_parameters.py +154 -0
- package/tests/test_actions_provider_examples.py +39 -0
- package/tests/test_advanced_memory_behavior.py +96 -0
- package/tests/test_advanced_memory_flag.py +30 -0
- package/tests/test_advanced_planning_behavior.py +225 -0
- package/tests/test_advanced_planning_flag.py +26 -0
- package/tests/test_autonomy.py +445 -0
- package/tests/test_bootstrap_initialize.py +37 -0
- package/tests/test_character.py +163 -0
- package/tests/test_character_provider.py +231 -0
- package/tests/test_dynamic_prompt_exec.py +561 -0
- package/tests/test_logger_redaction.py +43 -0
- package/tests/test_plugin.py +117 -0
- package/tests/test_runtime.py +422 -0
- package/tests/test_salt_production_enforcement.py +22 -0
- package/tests/test_settings_crypto.py +118 -0
- package/tests/test_streaming.py +295 -0
- package/tests/test_types.py +221 -0
- 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
|