@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,145 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from elizaos.bootstrap.utils.xml import parse_key_value_xml
|
|
7
|
+
from elizaos.generated.spec_helpers import require_action_spec
|
|
8
|
+
from elizaos.prompts import SEARCH_CONTACTS_TEMPLATE
|
|
9
|
+
from elizaos.types import (
|
|
10
|
+
Action,
|
|
11
|
+
ActionExample,
|
|
12
|
+
ActionResult,
|
|
13
|
+
Content,
|
|
14
|
+
ModelType,
|
|
15
|
+
)
|
|
16
|
+
from elizaos.utils.spec_examples import convert_spec_examples
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from elizaos.types import (
|
|
20
|
+
HandlerCallback,
|
|
21
|
+
HandlerOptions,
|
|
22
|
+
IAgentRuntime,
|
|
23
|
+
Memory,
|
|
24
|
+
State,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# Get text content from centralized specs
|
|
28
|
+
_spec = require_action_spec("SEARCH_CONTACTS")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _convert_spec_examples() -> list[list[ActionExample]]:
|
|
32
|
+
"""Convert spec examples to ActionExample format."""
|
|
33
|
+
return convert_spec_examples(_spec)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class SearchContactsAction:
|
|
38
|
+
name: str = _spec["name"]
|
|
39
|
+
similes: list[str] = field(default_factory=lambda: list(_spec.get("similes", [])))
|
|
40
|
+
description: str = _spec["description"]
|
|
41
|
+
|
|
42
|
+
async def validate(
|
|
43
|
+
self, runtime: IAgentRuntime, _message: Memory, _state: State | None = None
|
|
44
|
+
) -> bool:
|
|
45
|
+
rolodex_service = runtime.get_service("rolodex")
|
|
46
|
+
return rolodex_service is not None
|
|
47
|
+
|
|
48
|
+
async def handler(
|
|
49
|
+
self,
|
|
50
|
+
runtime: IAgentRuntime,
|
|
51
|
+
message: Memory,
|
|
52
|
+
state: State | None = None,
|
|
53
|
+
options: HandlerOptions | None = None,
|
|
54
|
+
callback: HandlerCallback | None = None,
|
|
55
|
+
responses: list[Memory] | None = None,
|
|
56
|
+
) -> ActionResult:
|
|
57
|
+
from elizaos.bootstrap.services.rolodex import RolodexService
|
|
58
|
+
|
|
59
|
+
rolodex_service = runtime.get_service("rolodex")
|
|
60
|
+
if not rolodex_service or not isinstance(rolodex_service, RolodexService):
|
|
61
|
+
return ActionResult(
|
|
62
|
+
text="Rolodex service not available",
|
|
63
|
+
success=False,
|
|
64
|
+
values={"error": True},
|
|
65
|
+
data={"error": "RolodexService not available"},
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
state = await runtime.compose_state(message, ["RECENT_MESSAGES"])
|
|
69
|
+
|
|
70
|
+
prompt = runtime.compose_prompt_from_state(
|
|
71
|
+
state=state,
|
|
72
|
+
template=SEARCH_CONTACTS_TEMPLATE,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
response = await runtime.use_model(ModelType.TEXT_SMALL, {"prompt": prompt})
|
|
76
|
+
parsed = parse_key_value_xml(response)
|
|
77
|
+
|
|
78
|
+
categories = None
|
|
79
|
+
tags = None
|
|
80
|
+
search_term = None
|
|
81
|
+
|
|
82
|
+
if parsed:
|
|
83
|
+
if parsed.get("categories"):
|
|
84
|
+
categories = [c.strip() for c in str(parsed["categories"]).split(",") if c.strip()]
|
|
85
|
+
if parsed.get("searchTerm"):
|
|
86
|
+
search_term = str(parsed["searchTerm"])
|
|
87
|
+
if parsed.get("tags"):
|
|
88
|
+
tags = [t.strip() for t in str(parsed["tags"]).split(",") if t.strip()]
|
|
89
|
+
|
|
90
|
+
contacts = await rolodex_service.search_contacts(
|
|
91
|
+
categories=categories,
|
|
92
|
+
tags=tags,
|
|
93
|
+
search_term=search_term,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
contact_details: list[dict[str, str]] = []
|
|
97
|
+
for contact in contacts:
|
|
98
|
+
entity = await runtime.get_entity(str(contact.entity_id))
|
|
99
|
+
name = entity.name if entity and entity.name else "Unknown"
|
|
100
|
+
contact_details.append(
|
|
101
|
+
{
|
|
102
|
+
"id": str(contact.entity_id),
|
|
103
|
+
"name": name,
|
|
104
|
+
"categories": ",".join(contact.categories),
|
|
105
|
+
"tags": ",".join(contact.tags),
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
if not contact_details:
|
|
110
|
+
response_text = "No contacts found matching your criteria."
|
|
111
|
+
else:
|
|
112
|
+
response_text = f"I found {len(contact_details)} contact(s):\n"
|
|
113
|
+
for detail in contact_details:
|
|
114
|
+
response_text += f"- {detail['name']}"
|
|
115
|
+
if detail["categories"]:
|
|
116
|
+
response_text += f" [{detail['categories']}]"
|
|
117
|
+
response_text += "\n"
|
|
118
|
+
|
|
119
|
+
if callback:
|
|
120
|
+
await callback(Content(text=response_text.strip(), actions=["SEARCH_CONTACTS"]))
|
|
121
|
+
|
|
122
|
+
return ActionResult(
|
|
123
|
+
text=response_text.strip(),
|
|
124
|
+
success=True,
|
|
125
|
+
values={
|
|
126
|
+
"count": len(contact_details),
|
|
127
|
+
},
|
|
128
|
+
data={
|
|
129
|
+
"count": len(contact_details),
|
|
130
|
+
},
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
@property
|
|
134
|
+
def examples(self) -> list[list[ActionExample]]:
|
|
135
|
+
return _convert_spec_examples()
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
search_contacts_action = Action(
|
|
139
|
+
name=SearchContactsAction.name,
|
|
140
|
+
similes=SearchContactsAction().similes,
|
|
141
|
+
description=SearchContactsAction.description,
|
|
142
|
+
validate=SearchContactsAction().validate,
|
|
143
|
+
handler=SearchContactsAction().handler,
|
|
144
|
+
examples=SearchContactsAction().examples,
|
|
145
|
+
)
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from elizaos.generated.spec_helpers import require_action_spec
|
|
8
|
+
from elizaos.types import Action, ActionExample, ActionResult, Content
|
|
9
|
+
from elizaos.types.memory import Memory as MemoryType
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from elizaos.types import HandlerCallback, HandlerOptions, IAgentRuntime, Memory, State
|
|
13
|
+
|
|
14
|
+
# Get text content from centralized specs
|
|
15
|
+
_spec = require_action_spec("SEND_MESSAGE")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _convert_spec_examples() -> list[list[ActionExample]]:
|
|
19
|
+
"""Convert spec examples to ActionExample format."""
|
|
20
|
+
spec_examples = _spec.get("examples", [])
|
|
21
|
+
if not isinstance(spec_examples, list):
|
|
22
|
+
return []
|
|
23
|
+
result: list[list[ActionExample]] = []
|
|
24
|
+
for example in spec_examples:
|
|
25
|
+
if not isinstance(example, list):
|
|
26
|
+
continue
|
|
27
|
+
row: list[ActionExample] = []
|
|
28
|
+
for msg in example:
|
|
29
|
+
if not isinstance(msg, dict):
|
|
30
|
+
continue
|
|
31
|
+
content = msg.get("content", {})
|
|
32
|
+
text = ""
|
|
33
|
+
actions: list[str] | None = None
|
|
34
|
+
if isinstance(content, dict):
|
|
35
|
+
text_val = content.get("text", "")
|
|
36
|
+
text = str(text_val) if text_val else ""
|
|
37
|
+
actions_val = content.get("actions")
|
|
38
|
+
if isinstance(actions_val, list) and all(isinstance(a, str) for a in actions_val):
|
|
39
|
+
actions = list(actions_val)
|
|
40
|
+
row.append(
|
|
41
|
+
ActionExample(
|
|
42
|
+
name=str(msg.get("name", "")),
|
|
43
|
+
content=Content(text=text, actions=actions),
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
if row:
|
|
47
|
+
result.append(row)
|
|
48
|
+
return result
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class SendMessageAction:
|
|
53
|
+
name: str = _spec["name"]
|
|
54
|
+
similes: list[str] = field(default_factory=lambda: list(_spec.get("similes", [])))
|
|
55
|
+
description: str = _spec["description"]
|
|
56
|
+
|
|
57
|
+
async def validate(
|
|
58
|
+
self, runtime: IAgentRuntime, message: Memory, _state: State | None = None
|
|
59
|
+
) -> bool:
|
|
60
|
+
if message.content and message.content.target:
|
|
61
|
+
return True
|
|
62
|
+
return True
|
|
63
|
+
|
|
64
|
+
async def handler(
|
|
65
|
+
self,
|
|
66
|
+
runtime: IAgentRuntime,
|
|
67
|
+
message: Memory,
|
|
68
|
+
state: State | None = None,
|
|
69
|
+
options: HandlerOptions | None = None,
|
|
70
|
+
callback: HandlerCallback | None = None,
|
|
71
|
+
responses: list[Memory] | None = None,
|
|
72
|
+
) -> ActionResult:
|
|
73
|
+
message_text = ""
|
|
74
|
+
if responses and responses[0].content:
|
|
75
|
+
message_text = str(responses[0].content.text or "")
|
|
76
|
+
|
|
77
|
+
if not message_text:
|
|
78
|
+
return ActionResult(
|
|
79
|
+
text="No message content to send",
|
|
80
|
+
values={"success": False, "error": "no_content"},
|
|
81
|
+
data={"actionName": "SEND_MESSAGE"},
|
|
82
|
+
success=False,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
from uuid import UUID as StdUUID
|
|
86
|
+
|
|
87
|
+
target_room_id_val: str | None = str(message.room_id) if message.room_id else None
|
|
88
|
+
target_entity_id: str | None = None
|
|
89
|
+
|
|
90
|
+
if message.content and message.content.target:
|
|
91
|
+
target = message.content.target
|
|
92
|
+
if isinstance(target, dict):
|
|
93
|
+
room_str = target.get("roomId")
|
|
94
|
+
entity_str = target.get("entityId")
|
|
95
|
+
if room_str:
|
|
96
|
+
with contextlib.suppress(ValueError):
|
|
97
|
+
target_room_id_val = str(StdUUID(str(room_str)))
|
|
98
|
+
if entity_str:
|
|
99
|
+
with contextlib.suppress(ValueError):
|
|
100
|
+
target_entity_id = str(StdUUID(str(entity_str)))
|
|
101
|
+
|
|
102
|
+
if not target_room_id_val:
|
|
103
|
+
return ActionResult(
|
|
104
|
+
text="No target room specified",
|
|
105
|
+
values={"success": False, "error": "no_target"},
|
|
106
|
+
data={"actionName": "SEND_MESSAGE"},
|
|
107
|
+
success=False,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
message_content = Content(
|
|
111
|
+
text=message_text,
|
|
112
|
+
source="agent",
|
|
113
|
+
actions=["SEND_MESSAGE"],
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Create the message memory for event emission
|
|
117
|
+
import time
|
|
118
|
+
import uuid as uuid_module
|
|
119
|
+
|
|
120
|
+
from elizaos.types.primitives import as_uuid
|
|
121
|
+
|
|
122
|
+
message_memory = MemoryType(
|
|
123
|
+
id=as_uuid(str(uuid_module.uuid4())),
|
|
124
|
+
entity_id=runtime.agent_id,
|
|
125
|
+
room_id=as_uuid(target_room_id_val) if target_room_id_val else None,
|
|
126
|
+
content=message_content,
|
|
127
|
+
created_at=int(time.time() * 1000),
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
await runtime.create_memory(
|
|
131
|
+
content=message_content,
|
|
132
|
+
room_id=target_room_id_val,
|
|
133
|
+
entity_id=runtime.agent_id,
|
|
134
|
+
memory_type="message",
|
|
135
|
+
metadata={
|
|
136
|
+
"type": "SEND_MESSAGE",
|
|
137
|
+
"targetEntityId": str(target_entity_id) if target_entity_id else None,
|
|
138
|
+
},
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# Emit MESSAGE_SENT event
|
|
142
|
+
await runtime.emit_event(
|
|
143
|
+
"MESSAGE_SENT",
|
|
144
|
+
{
|
|
145
|
+
"runtime": runtime,
|
|
146
|
+
"source": "send-message-action",
|
|
147
|
+
"message": message_memory,
|
|
148
|
+
},
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
response_content = Content(
|
|
152
|
+
text=f"Message sent: {message_text[:50]}...",
|
|
153
|
+
actions=["SEND_MESSAGE"],
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
if callback:
|
|
157
|
+
await callback(response_content)
|
|
158
|
+
|
|
159
|
+
return ActionResult(
|
|
160
|
+
text="Message sent to room",
|
|
161
|
+
values={
|
|
162
|
+
"success": True,
|
|
163
|
+
"messageSent": True,
|
|
164
|
+
"targetRoomId": str(target_room_id_val),
|
|
165
|
+
"targetEntityId": str(target_entity_id) if target_entity_id else None,
|
|
166
|
+
},
|
|
167
|
+
data={
|
|
168
|
+
"actionName": "SEND_MESSAGE",
|
|
169
|
+
"targetRoomId": str(target_room_id_val),
|
|
170
|
+
"messagePreview": message_text[:100],
|
|
171
|
+
},
|
|
172
|
+
success=True,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
@property
|
|
176
|
+
def examples(self) -> list[list[ActionExample]]:
|
|
177
|
+
return _convert_spec_examples()
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
send_message_action = Action(
|
|
181
|
+
name=SendMessageAction.name,
|
|
182
|
+
similes=SendMessageAction().similes,
|
|
183
|
+
description=SendMessageAction.description,
|
|
184
|
+
validate=SendMessageAction().validate,
|
|
185
|
+
handler=SendMessageAction().handler,
|
|
186
|
+
examples=SendMessageAction().examples,
|
|
187
|
+
)
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from elizaos.bootstrap.utils.xml import parse_key_value_xml
|
|
7
|
+
from elizaos.generated.spec_helpers import require_action_spec
|
|
8
|
+
from elizaos.prompts import UPDATE_SETTINGS_TEMPLATE
|
|
9
|
+
from elizaos.types import Action, ActionExample, ActionResult, Content, ModelType
|
|
10
|
+
from elizaos.utils.spec_examples import convert_spec_examples
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from elizaos.types import HandlerCallback, HandlerOptions, IAgentRuntime, Memory, State
|
|
14
|
+
|
|
15
|
+
# Get text content from centralized specs
|
|
16
|
+
_spec = require_action_spec("UPDATE_SETTINGS")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _convert_spec_examples() -> list[list[ActionExample]]:
|
|
20
|
+
"""Convert spec examples to ActionExample format."""
|
|
21
|
+
return convert_spec_examples(_spec)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class SettingUpdate:
|
|
26
|
+
key: str
|
|
27
|
+
value: str
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class UpdateSettingsAction:
|
|
32
|
+
name: str = _spec["name"]
|
|
33
|
+
similes: list[str] = field(default_factory=lambda: list(_spec.get("similes", [])))
|
|
34
|
+
description: str = _spec["description"]
|
|
35
|
+
|
|
36
|
+
async def validate(
|
|
37
|
+
self, runtime: IAgentRuntime, _message: Memory, _state: State | None = None
|
|
38
|
+
) -> bool:
|
|
39
|
+
return True
|
|
40
|
+
|
|
41
|
+
async def handler(
|
|
42
|
+
self,
|
|
43
|
+
runtime: IAgentRuntime,
|
|
44
|
+
message: Memory,
|
|
45
|
+
state: State | None = None,
|
|
46
|
+
options: HandlerOptions | None = None,
|
|
47
|
+
callback: HandlerCallback | None = None,
|
|
48
|
+
responses: list[Memory] | None = None,
|
|
49
|
+
) -> ActionResult:
|
|
50
|
+
if state is None:
|
|
51
|
+
raise ValueError("State is required for UPDATE_SETTINGS action")
|
|
52
|
+
|
|
53
|
+
state = await runtime.compose_state(
|
|
54
|
+
message, ["RECENT_MESSAGES", "ACTION_STATE", "AGENT_SETTINGS"]
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
current_settings = runtime.get_all_settings()
|
|
58
|
+
settings_context = "\n".join(
|
|
59
|
+
f"- {key}: {value}"
|
|
60
|
+
for key, value in current_settings.items()
|
|
61
|
+
if not key.lower().endswith(("key", "secret", "password", "token"))
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
template = (
|
|
65
|
+
runtime.character.templates.get("updateSettingsTemplate")
|
|
66
|
+
if runtime.character.templates
|
|
67
|
+
and "updateSettingsTemplate" in runtime.character.templates
|
|
68
|
+
else UPDATE_SETTINGS_TEMPLATE
|
|
69
|
+
)
|
|
70
|
+
prompt = runtime.compose_prompt(state=state, template=template)
|
|
71
|
+
prompt = prompt.replace("{{settings}}", settings_context)
|
|
72
|
+
|
|
73
|
+
response_text = await runtime.use_model(ModelType.TEXT_LARGE, prompt=prompt)
|
|
74
|
+
parsed_xml = parse_key_value_xml(response_text)
|
|
75
|
+
|
|
76
|
+
if parsed_xml is None:
|
|
77
|
+
raise ValueError("Failed to parse XML response")
|
|
78
|
+
|
|
79
|
+
thought = str(parsed_xml.get("thought", ""))
|
|
80
|
+
updates_raw = parsed_xml.get("updates", [])
|
|
81
|
+
|
|
82
|
+
updated_settings: list[SettingUpdate] = []
|
|
83
|
+
|
|
84
|
+
if isinstance(updates_raw, list):
|
|
85
|
+
for update in updates_raw:
|
|
86
|
+
if isinstance(update, dict):
|
|
87
|
+
key = str(update.get("key", ""))
|
|
88
|
+
value = str(update.get("value", ""))
|
|
89
|
+
if key and value:
|
|
90
|
+
updated_settings.append(SettingUpdate(key=key, value=value))
|
|
91
|
+
elif isinstance(updates_raw, dict):
|
|
92
|
+
update_list = updates_raw.get("update", [])
|
|
93
|
+
if isinstance(update_list, dict):
|
|
94
|
+
update_list = [update_list]
|
|
95
|
+
for update in update_list:
|
|
96
|
+
if isinstance(update, dict):
|
|
97
|
+
key = str(update.get("key", ""))
|
|
98
|
+
value = str(update.get("value", ""))
|
|
99
|
+
if key and value:
|
|
100
|
+
updated_settings.append(SettingUpdate(key=key, value=value))
|
|
101
|
+
|
|
102
|
+
if not updated_settings:
|
|
103
|
+
return ActionResult(
|
|
104
|
+
text="No settings to update",
|
|
105
|
+
values={"success": True, "noChanges": True},
|
|
106
|
+
data={"actionName": "UPDATE_SETTINGS", "thought": thought},
|
|
107
|
+
success=True,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
for setting in updated_settings:
|
|
111
|
+
runtime.set_setting(setting.key, setting.value)
|
|
112
|
+
|
|
113
|
+
updated_keys = [s.key for s in updated_settings]
|
|
114
|
+
|
|
115
|
+
response_content = Content(
|
|
116
|
+
text=f"Updated {len(updated_settings)} setting(s): {', '.join(updated_keys)}",
|
|
117
|
+
actions=["UPDATE_SETTINGS"],
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
if callback:
|
|
121
|
+
await callback(response_content)
|
|
122
|
+
|
|
123
|
+
return ActionResult(
|
|
124
|
+
text=f"Updated settings: {', '.join(updated_keys)}",
|
|
125
|
+
values={
|
|
126
|
+
"success": True,
|
|
127
|
+
"settingsUpdated": True,
|
|
128
|
+
"updatedCount": len(updated_settings),
|
|
129
|
+
"updatedKeys": ", ".join(updated_keys),
|
|
130
|
+
},
|
|
131
|
+
data={
|
|
132
|
+
"actionName": "UPDATE_SETTINGS",
|
|
133
|
+
"updatedSettings": updated_keys,
|
|
134
|
+
"thought": thought,
|
|
135
|
+
},
|
|
136
|
+
success=True,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def examples(self) -> list[list[ActionExample]]:
|
|
141
|
+
return _convert_spec_examples()
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
update_settings_action = Action(
|
|
145
|
+
name=UpdateSettingsAction.name,
|
|
146
|
+
similes=UpdateSettingsAction().similes,
|
|
147
|
+
description=UpdateSettingsAction.description,
|
|
148
|
+
validate=UpdateSettingsAction().validate,
|
|
149
|
+
handler=UpdateSettingsAction().handler,
|
|
150
|
+
examples=UpdateSettingsAction().examples,
|
|
151
|
+
)
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from elizaos.generated.spec_helpers import require_action_spec
|
|
7
|
+
from elizaos.types import Action, ActionExample, ActionResult, Content
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from elizaos.types import HandlerCallback, HandlerOptions, IAgentRuntime, Memory, State
|
|
11
|
+
|
|
12
|
+
# Get text content from centralized specs
|
|
13
|
+
_spec = require_action_spec("UNFOLLOW_ROOM")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _convert_spec_examples() -> list[list[ActionExample]]:
|
|
17
|
+
"""Convert spec examples to ActionExample format."""
|
|
18
|
+
spec_examples = _spec.get("examples", [])
|
|
19
|
+
if not isinstance(spec_examples, list):
|
|
20
|
+
return []
|
|
21
|
+
result: list[list[ActionExample]] = []
|
|
22
|
+
for example in spec_examples:
|
|
23
|
+
if not isinstance(example, list):
|
|
24
|
+
continue
|
|
25
|
+
row: list[ActionExample] = []
|
|
26
|
+
for msg in example:
|
|
27
|
+
if not isinstance(msg, dict):
|
|
28
|
+
continue
|
|
29
|
+
content = msg.get("content", {})
|
|
30
|
+
text = ""
|
|
31
|
+
actions: list[str] | None = None
|
|
32
|
+
if isinstance(content, dict):
|
|
33
|
+
text_val = content.get("text", "")
|
|
34
|
+
text = str(text_val) if text_val else ""
|
|
35
|
+
actions_val = content.get("actions")
|
|
36
|
+
if isinstance(actions_val, list) and all(isinstance(a, str) for a in actions_val):
|
|
37
|
+
actions = list(actions_val)
|
|
38
|
+
row.append(
|
|
39
|
+
ActionExample(
|
|
40
|
+
name=str(msg.get("name", "")),
|
|
41
|
+
content=Content(text=text, actions=actions),
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
if row:
|
|
45
|
+
result.append(row)
|
|
46
|
+
return result
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class UnfollowRoomAction:
|
|
51
|
+
name: str = _spec["name"]
|
|
52
|
+
similes: list[str] = field(default_factory=lambda: list(_spec.get("similes", [])))
|
|
53
|
+
description: str = _spec["description"]
|
|
54
|
+
|
|
55
|
+
async def validate(
|
|
56
|
+
self, runtime: IAgentRuntime, message: Memory, _state: State | None = None
|
|
57
|
+
) -> bool:
|
|
58
|
+
room_id = message.room_id
|
|
59
|
+
if not room_id:
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
room = await runtime.get_room(room_id)
|
|
63
|
+
if room is None:
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
world_id = room.world_id
|
|
67
|
+
if world_id:
|
|
68
|
+
world = await runtime.get_world(world_id)
|
|
69
|
+
if world and world.metadata:
|
|
70
|
+
followed_rooms = world.metadata.get("followedRooms", [])
|
|
71
|
+
if str(room_id) in followed_rooms:
|
|
72
|
+
return True
|
|
73
|
+
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
async def handler(
|
|
77
|
+
self,
|
|
78
|
+
runtime: IAgentRuntime,
|
|
79
|
+
message: Memory,
|
|
80
|
+
state: State | None = None,
|
|
81
|
+
options: HandlerOptions | None = None,
|
|
82
|
+
callback: HandlerCallback | None = None,
|
|
83
|
+
responses: list[Memory] | None = None,
|
|
84
|
+
) -> ActionResult:
|
|
85
|
+
room_id = message.room_id
|
|
86
|
+
if not room_id:
|
|
87
|
+
return ActionResult(
|
|
88
|
+
text="No room specified to unfollow",
|
|
89
|
+
values={"success": False, "error": "no_room_id"},
|
|
90
|
+
data={"actionName": "UNFOLLOW_ROOM"},
|
|
91
|
+
success=False,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
room = await runtime.get_room(room_id)
|
|
95
|
+
if room is None:
|
|
96
|
+
return ActionResult(
|
|
97
|
+
text="Room not found",
|
|
98
|
+
values={"success": False, "error": "room_not_found"},
|
|
99
|
+
data={"actionName": "UNFOLLOW_ROOM"},
|
|
100
|
+
success=False,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
room_name = str(room.name) if room.name else "Unknown Room"
|
|
104
|
+
|
|
105
|
+
world_id = room.world_id
|
|
106
|
+
if world_id:
|
|
107
|
+
world = await runtime.get_world(world_id)
|
|
108
|
+
if world and world.metadata:
|
|
109
|
+
followed_rooms = list(world.metadata.get("followedRooms", []))
|
|
110
|
+
room_id_str = str(room_id)
|
|
111
|
+
|
|
112
|
+
if room_id_str in followed_rooms:
|
|
113
|
+
followed_rooms.remove(room_id_str)
|
|
114
|
+
world.metadata["followedRooms"] = followed_rooms
|
|
115
|
+
await runtime.update_world(world)
|
|
116
|
+
|
|
117
|
+
await runtime.create_memory(
|
|
118
|
+
content=Content(
|
|
119
|
+
text=f"Stopped following room: {room_name}",
|
|
120
|
+
actions=["UNFOLLOW_ROOM"],
|
|
121
|
+
),
|
|
122
|
+
room_id=room_id,
|
|
123
|
+
entity_id=runtime.agent_id,
|
|
124
|
+
memory_type="action",
|
|
125
|
+
metadata={"type": "UNFOLLOW_ROOM", "roomName": room_name},
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
response_content = Content(
|
|
129
|
+
text=f"I am no longer following {room_name}.",
|
|
130
|
+
actions=["UNFOLLOW_ROOM"],
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
if callback:
|
|
134
|
+
await callback(response_content)
|
|
135
|
+
|
|
136
|
+
return ActionResult(
|
|
137
|
+
text=f"Stopped following room: {room_name}",
|
|
138
|
+
values={
|
|
139
|
+
"success": True,
|
|
140
|
+
"unfollowed": True,
|
|
141
|
+
"roomId": str(room_id),
|
|
142
|
+
"roomName": room_name,
|
|
143
|
+
},
|
|
144
|
+
data={
|
|
145
|
+
"actionName": "UNFOLLOW_ROOM",
|
|
146
|
+
"roomId": str(room_id),
|
|
147
|
+
"roomName": room_name,
|
|
148
|
+
},
|
|
149
|
+
success=True,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
@property
|
|
153
|
+
def examples(self) -> list[list[ActionExample]]:
|
|
154
|
+
return _convert_spec_examples()
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
unfollow_room_action = Action(
|
|
158
|
+
name=UnfollowRoomAction.name,
|
|
159
|
+
similes=UnfollowRoomAction().similes,
|
|
160
|
+
description=UnfollowRoomAction.description,
|
|
161
|
+
validate=UnfollowRoomAction().validate,
|
|
162
|
+
handler=UnfollowRoomAction().handler,
|
|
163
|
+
examples=UnfollowRoomAction().examples,
|
|
164
|
+
)
|