@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,112 @@
|
|
|
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
|
+
def format_relationship(
|
|
12
|
+
relationship: dict[str, str | int | list[str] | dict[str, str]],
|
|
13
|
+
target_name: str,
|
|
14
|
+
) -> str:
|
|
15
|
+
tags = relationship.get("tags", [])
|
|
16
|
+
tags_str = (", ".join(tags) if tags else "none") if isinstance(tags, list) else str(tags)
|
|
17
|
+
|
|
18
|
+
interactions = relationship.get("metadata", {})
|
|
19
|
+
interaction_count = interactions.get("interactions", 0) if isinstance(interactions, dict) else 0
|
|
20
|
+
|
|
21
|
+
return f"- {target_name}: tags=[{tags_str}], interactions={interaction_count}"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
async def get_relationships(
|
|
25
|
+
runtime: IAgentRuntime,
|
|
26
|
+
message: Memory,
|
|
27
|
+
state: State | None = None,
|
|
28
|
+
) -> ProviderResult:
|
|
29
|
+
entity_id = message.entity_id
|
|
30
|
+
if not entity_id:
|
|
31
|
+
return ProviderResult(
|
|
32
|
+
text="No relationships found.",
|
|
33
|
+
values={"relationshipCount": 0},
|
|
34
|
+
data={"relationships": []},
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
relationships = await runtime.get_relationships({"entityId": str(entity_id)})
|
|
39
|
+
except Exception as e:
|
|
40
|
+
runtime.logger.debug(
|
|
41
|
+
f"Failed to get relationships: src=provider:relationships agentId={runtime.agent_id} error={e}"
|
|
42
|
+
)
|
|
43
|
+
relationships = []
|
|
44
|
+
|
|
45
|
+
if not relationships:
|
|
46
|
+
return ProviderResult(
|
|
47
|
+
text="No relationships found.",
|
|
48
|
+
values={"relationshipCount": 0},
|
|
49
|
+
data={"relationships": []},
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def _get_interactions(r: object) -> int:
|
|
53
|
+
if isinstance(r, dict):
|
|
54
|
+
meta = r.get("metadata", {})
|
|
55
|
+
if isinstance(meta, dict):
|
|
56
|
+
val = meta.get("interactions", 0)
|
|
57
|
+
return int(val) if isinstance(val, (int, float)) else 0
|
|
58
|
+
return 0
|
|
59
|
+
|
|
60
|
+
sorted_relationships = sorted(
|
|
61
|
+
relationships,
|
|
62
|
+
key=_get_interactions,
|
|
63
|
+
reverse=True,
|
|
64
|
+
)[:30]
|
|
65
|
+
|
|
66
|
+
formatted_relationships: list[str] = []
|
|
67
|
+
entity_cache: dict[str, str] = {}
|
|
68
|
+
for rel in sorted_relationships:
|
|
69
|
+
if not isinstance(rel, dict):
|
|
70
|
+
continue
|
|
71
|
+
target_id = rel.get("targetEntityId")
|
|
72
|
+
if not target_id:
|
|
73
|
+
continue
|
|
74
|
+
|
|
75
|
+
target_id_str = str(target_id)
|
|
76
|
+
target_name = entity_cache.get(target_id_str)
|
|
77
|
+
if target_name is None:
|
|
78
|
+
target_entity = await runtime.get_entity(target_id_str)
|
|
79
|
+
target_name = target_entity.name if target_entity else target_id_str[:8]
|
|
80
|
+
entity_cache[target_id_str] = target_name
|
|
81
|
+
|
|
82
|
+
formatted_relationships.append(format_relationship(rel, target_name))
|
|
83
|
+
|
|
84
|
+
if not formatted_relationships:
|
|
85
|
+
return ProviderResult(
|
|
86
|
+
text="No relationships found.",
|
|
87
|
+
values={"relationshipCount": 0},
|
|
88
|
+
data={"relationships": []},
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
sender_name = message.content.sender_name if message.content else "Unknown"
|
|
92
|
+
text = f"# {runtime.character.name} has observed {sender_name} interacting with:\n" + "\n".join(
|
|
93
|
+
formatted_relationships
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
return ProviderResult(
|
|
97
|
+
text=text,
|
|
98
|
+
values={
|
|
99
|
+
"relationshipCount": len(sorted_relationships),
|
|
100
|
+
},
|
|
101
|
+
data={
|
|
102
|
+
"relationships": sorted_relationships,
|
|
103
|
+
},
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
relationships_provider = Provider(
|
|
108
|
+
name="RELATIONSHIPS",
|
|
109
|
+
description="Relationships between entities observed by the agent",
|
|
110
|
+
get=get_relationships,
|
|
111
|
+
dynamic=True,
|
|
112
|
+
)
|
|
@@ -0,0 +1,97 @@
|
|
|
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
|
+
def format_role_info(entity_name: str, role: str) -> str:
|
|
13
|
+
return f"- {entity_name}: {role}"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def get_roles(
|
|
17
|
+
runtime: IAgentRuntime,
|
|
18
|
+
message: Memory,
|
|
19
|
+
state: State | None = None,
|
|
20
|
+
) -> ProviderResult:
|
|
21
|
+
role_info: list[dict[str, str]] = []
|
|
22
|
+
|
|
23
|
+
world_id = None
|
|
24
|
+
if state and hasattr(state, "world"):
|
|
25
|
+
world = state.world
|
|
26
|
+
if world and hasattr(world, "id"):
|
|
27
|
+
world_id = world.id
|
|
28
|
+
|
|
29
|
+
if not world_id and message.room_id:
|
|
30
|
+
room = await runtime.get_room(message.room_id)
|
|
31
|
+
if room and hasattr(room, "world_id"):
|
|
32
|
+
world_id = room.world_id
|
|
33
|
+
|
|
34
|
+
if world_id:
|
|
35
|
+
world = await runtime.get_world(world_id)
|
|
36
|
+
if world and hasattr(world, "metadata"):
|
|
37
|
+
roles = world.metadata.get("roles", {})
|
|
38
|
+
if isinstance(roles, dict):
|
|
39
|
+
entity_ids = list(roles.keys())
|
|
40
|
+
entities = await asyncio.gather(
|
|
41
|
+
*(runtime.get_entity(entity_id) for entity_id in entity_ids)
|
|
42
|
+
)
|
|
43
|
+
for entity_id, entity, role in zip(
|
|
44
|
+
entity_ids, entities, roles.values(), strict=False
|
|
45
|
+
):
|
|
46
|
+
entity_name = entity.name if entity else str(entity_id)[:8]
|
|
47
|
+
|
|
48
|
+
role_info.append(
|
|
49
|
+
{
|
|
50
|
+
"entityId": str(entity_id),
|
|
51
|
+
"entityName": entity_name,
|
|
52
|
+
"role": str(role),
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
if message.entity_id:
|
|
57
|
+
entity = await runtime.get_entity(message.entity_id)
|
|
58
|
+
if entity and hasattr(entity, "metadata"):
|
|
59
|
+
sender_role = entity.metadata.get("role")
|
|
60
|
+
if sender_role:
|
|
61
|
+
existing = next(
|
|
62
|
+
(r for r in role_info if r["entityId"] == str(message.entity_id)), None
|
|
63
|
+
)
|
|
64
|
+
if not existing:
|
|
65
|
+
role_info.append(
|
|
66
|
+
{
|
|
67
|
+
"entityId": str(message.entity_id),
|
|
68
|
+
"entityName": entity.name or "Unknown",
|
|
69
|
+
"role": str(sender_role),
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
if not role_info:
|
|
74
|
+
return ProviderResult(text="", values={"roleCount": 0}, data={"roles": []})
|
|
75
|
+
|
|
76
|
+
formatted_roles = "\n".join(format_role_info(r["entityName"], r["role"]) for r in role_info)
|
|
77
|
+
|
|
78
|
+
text = f"# Entity Roles\n{formatted_roles}"
|
|
79
|
+
|
|
80
|
+
return ProviderResult(
|
|
81
|
+
text=text,
|
|
82
|
+
values={
|
|
83
|
+
"roleCount": len(role_info),
|
|
84
|
+
"roles": {r["entityName"]: r["role"] for r in role_info},
|
|
85
|
+
},
|
|
86
|
+
data={
|
|
87
|
+
"roles": role_info,
|
|
88
|
+
},
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
roles_provider = Provider(
|
|
93
|
+
name="ROLES",
|
|
94
|
+
description="Roles assigned to entities in the current context",
|
|
95
|
+
get=get_roles,
|
|
96
|
+
dynamic=True,
|
|
97
|
+
)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from elizaos.bootstrap.providers.agent_settings import SENSITIVE_KEY_PATTERNS
|
|
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_settings_context(
|
|
13
|
+
runtime: IAgentRuntime,
|
|
14
|
+
message: Memory,
|
|
15
|
+
state: State | None = None,
|
|
16
|
+
) -> ProviderResult:
|
|
17
|
+
_ = message, state
|
|
18
|
+
|
|
19
|
+
all_settings = runtime.get_all_settings()
|
|
20
|
+
|
|
21
|
+
safe_settings: dict[str, str] = {}
|
|
22
|
+
for key, value in all_settings.items():
|
|
23
|
+
if not any(pattern in key.lower() for pattern in SENSITIVE_KEY_PATTERNS):
|
|
24
|
+
safe_settings[key] = str(value)
|
|
25
|
+
|
|
26
|
+
lines: list[str] = []
|
|
27
|
+
if safe_settings:
|
|
28
|
+
lines.append("## Current Configuration")
|
|
29
|
+
for key, value in safe_settings.items():
|
|
30
|
+
display_value = value if len(value) <= 50 else value[:50] + "..."
|
|
31
|
+
lines.append(f"- {key}: {display_value}")
|
|
32
|
+
|
|
33
|
+
context_text = "\n".join(lines) if lines else ""
|
|
34
|
+
|
|
35
|
+
return ProviderResult(
|
|
36
|
+
text=context_text,
|
|
37
|
+
values={
|
|
38
|
+
"settings": context_text,
|
|
39
|
+
"settingsCount": len(safe_settings),
|
|
40
|
+
"hasSettings": len(safe_settings) > 0,
|
|
41
|
+
},
|
|
42
|
+
data={"settings": safe_settings},
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
settings_provider = Provider(
|
|
47
|
+
name="SETTINGS",
|
|
48
|
+
description="Current settings for the agent/server (filtered for security)",
|
|
49
|
+
get=get_settings_context,
|
|
50
|
+
dynamic=True,
|
|
51
|
+
)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Advanced Services - Extended services for agent operation.
|
|
2
|
+
|
|
3
|
+
Services that can be enabled with `advanced_capabilities=True`.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .follow_up import FollowUpService
|
|
7
|
+
from .rolodex import RolodexService
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"FollowUpService",
|
|
11
|
+
"RolodexService",
|
|
12
|
+
"advanced_services",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
advanced_services: list[type] = [
|
|
16
|
+
RolodexService,
|
|
17
|
+
FollowUpService,
|
|
18
|
+
]
|
|
@@ -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
|