@elizaos/python 2.0.0-alpha.10 → 2.0.0-alpha.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/elizaos/__init__.py +0 -1
  2. package/elizaos/advanced_capabilities/__init__.py +6 -41
  3. package/elizaos/advanced_capabilities/actions/__init__.py +1 -21
  4. package/elizaos/advanced_capabilities/actions/add_contact.py +21 -11
  5. package/elizaos/advanced_capabilities/actions/follow_room.py +28 -28
  6. package/elizaos/advanced_capabilities/actions/image_generation.py +13 -26
  7. package/elizaos/advanced_capabilities/actions/mute_room.py +13 -26
  8. package/elizaos/advanced_capabilities/actions/remove_contact.py +16 -2
  9. package/elizaos/advanced_capabilities/actions/roles.py +13 -27
  10. package/elizaos/advanced_capabilities/actions/schedule_follow_up.py +70 -15
  11. package/elizaos/advanced_capabilities/actions/search_contacts.py +17 -3
  12. package/elizaos/advanced_capabilities/actions/send_message.py +183 -50
  13. package/elizaos/advanced_capabilities/actions/settings.py +16 -2
  14. package/elizaos/advanced_capabilities/actions/unfollow_room.py +13 -26
  15. package/elizaos/advanced_capabilities/actions/unmute_room.py +13 -26
  16. package/elizaos/advanced_capabilities/actions/update_contact.py +16 -2
  17. package/elizaos/advanced_capabilities/actions/update_entity.py +16 -2
  18. package/elizaos/advanced_capabilities/evaluators/__init__.py +2 -9
  19. package/elizaos/advanced_capabilities/evaluators/reflection.py +3 -132
  20. package/elizaos/advanced_capabilities/evaluators/relationship_extraction.py +5 -201
  21. package/elizaos/advanced_capabilities/providers/__init__.py +1 -12
  22. package/elizaos/advanced_capabilities/providers/knowledge.py +24 -3
  23. package/elizaos/advanced_capabilities/services/__init__.py +2 -9
  24. package/elizaos/advanced_memory/actions/reset_session.py +11 -0
  25. package/elizaos/advanced_memory/evaluators/reflection.py +134 -0
  26. package/elizaos/advanced_memory/evaluators/relationship_extraction.py +203 -0
  27. package/elizaos/advanced_memory/memory_service.py +15 -17
  28. package/elizaos/advanced_memory/test_advanced_memory.py +357 -0
  29. package/elizaos/advanced_planning/actions/schedule_follow_up.py +222 -0
  30. package/elizaos/advanced_planning/planning_service.py +26 -14
  31. package/elizaos/basic_capabilities/__init__.py +0 -2
  32. package/elizaos/basic_capabilities/providers/__init__.py +0 -3
  33. package/elizaos/basic_capabilities/providers/actions.py +118 -29
  34. package/elizaos/basic_capabilities/providers/agent_settings.py +64 -0
  35. package/elizaos/basic_capabilities/providers/character.py +19 -21
  36. package/elizaos/basic_capabilities/providers/contacts.py +79 -0
  37. package/elizaos/basic_capabilities/providers/current_time.py +7 -4
  38. package/elizaos/basic_capabilities/providers/facts.py +87 -0
  39. package/elizaos/basic_capabilities/providers/follow_ups.py +117 -0
  40. package/elizaos/basic_capabilities/providers/knowledge.py +97 -0
  41. package/elizaos/basic_capabilities/providers/relationships.py +107 -0
  42. package/elizaos/basic_capabilities/providers/roles.py +96 -0
  43. package/elizaos/basic_capabilities/providers/settings.py +56 -0
  44. package/elizaos/basic_capabilities/providers/time.py +7 -4
  45. package/elizaos/bootstrap/__init__.py +21 -2
  46. package/elizaos/bootstrap/actions/schedule_follow_up.py +65 -7
  47. package/elizaos/bootstrap/actions/send_message.py +162 -15
  48. package/elizaos/bootstrap/autonomy/__init__.py +5 -1
  49. package/elizaos/bootstrap/autonomy/action.py +161 -0
  50. package/elizaos/bootstrap/autonomy/evaluators.py +217 -0
  51. package/elizaos/bootstrap/autonomy/service.py +238 -28
  52. package/elizaos/bootstrap/plugin.py +7 -0
  53. package/elizaos/bootstrap/providers/actions.py +118 -27
  54. package/elizaos/bootstrap/providers/agent_settings.py +1 -0
  55. package/elizaos/bootstrap/providers/attachments.py +1 -0
  56. package/elizaos/bootstrap/providers/capabilities.py +1 -0
  57. package/elizaos/bootstrap/providers/character.py +1 -0
  58. package/elizaos/bootstrap/providers/choice.py +1 -0
  59. package/elizaos/bootstrap/providers/contacts.py +1 -0
  60. package/elizaos/bootstrap/providers/current_time.py +8 -2
  61. package/elizaos/bootstrap/providers/entities.py +1 -0
  62. package/elizaos/bootstrap/providers/evaluators.py +1 -0
  63. package/elizaos/bootstrap/providers/facts.py +1 -0
  64. package/elizaos/bootstrap/providers/follow_ups.py +1 -0
  65. package/elizaos/bootstrap/providers/knowledge.py +27 -3
  66. package/elizaos/bootstrap/providers/providers_list.py +1 -0
  67. package/elizaos/bootstrap/providers/relationships.py +1 -0
  68. package/elizaos/bootstrap/providers/roles.py +1 -0
  69. package/elizaos/bootstrap/providers/settings.py +1 -0
  70. package/elizaos/bootstrap/providers/time.py +8 -4
  71. package/elizaos/bootstrap/providers/world.py +1 -0
  72. package/elizaos/bootstrap/services/embedding.py +156 -1
  73. package/elizaos/deterministic.py +193 -0
  74. package/elizaos/generated/__init__.py +1 -0
  75. package/elizaos/generated/action_docs.py +3181 -0
  76. package/elizaos/generated/spec_helpers.py +175 -0
  77. package/elizaos/media/mime.py +2 -2
  78. package/elizaos/media/search.py +23 -23
  79. package/elizaos/runtime.py +215 -57
  80. package/elizaos/services/message_service.py +175 -29
  81. package/elizaos/types/components.py +2 -2
  82. package/elizaos/types/generated/__init__.py +12 -0
  83. package/elizaos/types/generated/eliza/v1/agent_pb2.py +63 -0
  84. package/elizaos/types/generated/eliza/v1/agent_pb2.pyi +159 -0
  85. package/elizaos/types/generated/eliza/v1/components_pb2.py +65 -0
  86. package/elizaos/types/generated/eliza/v1/components_pb2.pyi +160 -0
  87. package/elizaos/types/generated/eliza/v1/database_pb2.py +78 -0
  88. package/elizaos/types/generated/eliza/v1/database_pb2.pyi +305 -0
  89. package/elizaos/types/generated/eliza/v1/environment_pb2.py +58 -0
  90. package/elizaos/types/generated/eliza/v1/environment_pb2.pyi +135 -0
  91. package/elizaos/types/generated/eliza/v1/events_pb2.py +82 -0
  92. package/elizaos/types/generated/eliza/v1/events_pb2.pyi +322 -0
  93. package/elizaos/types/generated/eliza/v1/ipc_pb2.py +113 -0
  94. package/elizaos/types/generated/eliza/v1/ipc_pb2.pyi +367 -0
  95. package/elizaos/types/generated/eliza/v1/knowledge_pb2.py +41 -0
  96. package/elizaos/types/generated/eliza/v1/knowledge_pb2.pyi +26 -0
  97. package/elizaos/types/generated/eliza/v1/memory_pb2.py +55 -0
  98. package/elizaos/types/generated/eliza/v1/memory_pb2.pyi +111 -0
  99. package/elizaos/types/generated/eliza/v1/message_service_pb2.py +48 -0
  100. package/elizaos/types/generated/eliza/v1/message_service_pb2.pyi +69 -0
  101. package/elizaos/types/generated/eliza/v1/messaging_pb2.py +51 -0
  102. package/elizaos/types/generated/eliza/v1/messaging_pb2.pyi +97 -0
  103. package/elizaos/types/generated/eliza/v1/model_pb2.py +84 -0
  104. package/elizaos/types/generated/eliza/v1/model_pb2.pyi +280 -0
  105. package/elizaos/types/generated/eliza/v1/payment_pb2.py +44 -0
  106. package/elizaos/types/generated/eliza/v1/payment_pb2.pyi +70 -0
  107. package/elizaos/types/generated/eliza/v1/plugin_pb2.py +68 -0
  108. package/elizaos/types/generated/eliza/v1/plugin_pb2.pyi +145 -0
  109. package/elizaos/types/generated/eliza/v1/primitives_pb2.py +48 -0
  110. package/elizaos/types/generated/eliza/v1/primitives_pb2.pyi +92 -0
  111. package/elizaos/types/generated/eliza/v1/prompts_pb2.py +52 -0
  112. package/elizaos/types/generated/eliza/v1/prompts_pb2.pyi +74 -0
  113. package/elizaos/types/generated/eliza/v1/service_interfaces_pb2.py +211 -0
  114. package/elizaos/types/generated/eliza/v1/service_interfaces_pb2.pyi +1296 -0
  115. package/elizaos/types/generated/eliza/v1/service_pb2.py +42 -0
  116. package/elizaos/types/generated/eliza/v1/service_pb2.pyi +69 -0
  117. package/elizaos/types/generated/eliza/v1/settings_pb2.py +58 -0
  118. package/elizaos/types/generated/eliza/v1/settings_pb2.pyi +85 -0
  119. package/elizaos/types/generated/eliza/v1/state_pb2.py +60 -0
  120. package/elizaos/types/generated/eliza/v1/state_pb2.pyi +114 -0
  121. package/elizaos/types/generated/eliza/v1/task_pb2.py +42 -0
  122. package/elizaos/types/generated/eliza/v1/task_pb2.pyi +58 -0
  123. package/elizaos/types/generated/eliza/v1/tee_pb2.py +52 -0
  124. package/elizaos/types/generated/eliza/v1/tee_pb2.pyi +90 -0
  125. package/elizaos/types/generated/eliza/v1/testing_pb2.py +39 -0
  126. package/elizaos/types/generated/eliza/v1/testing_pb2.pyi +23 -0
  127. package/elizaos/types/model.py +30 -0
  128. package/elizaos/types/runtime.py +6 -2
  129. package/elizaos/utils/validation.py +76 -0
  130. package/package.json +3 -2
  131. package/tests/test_action_parameters.py +2 -3
  132. package/tests/test_actions_provider_examples.py +58 -1
  133. package/tests/test_advanced_memory_behavior.py +0 -2
  134. package/tests/test_advanced_memory_flag.py +0 -2
  135. package/tests/test_advanced_planning_behavior.py +11 -5
  136. package/tests/test_async_embedding.py +124 -0
  137. package/tests/test_autonomy.py +24 -3
  138. package/tests/test_runtime.py +8 -17
  139. package/tests/test_schedule_follow_up_action.py +260 -0
  140. package/tests/test_send_message_action_targets.py +114 -0
  141. package/tests/test_settings_crypto.py +0 -2
  142. package/tests/test_validation.py +141 -0
  143. package/tests/verify_memory_architecture.py +192 -0
  144. package/uv.lock +1565 -0
  145. package/elizaos/basic_capabilities/providers/capabilities.py +0 -62
@@ -108,7 +108,6 @@ from elizaos.types import (
108
108
  string_to_uuid,
109
109
  )
110
110
  from elizaos.types.database import IDatabaseAdapter # noqa: E402
111
- from elizaos.types.environment import ChannelType # noqa: E402
112
111
  from elizaos.types.primitives import ( # noqa: E402
113
112
  ChannelType,
114
113
  Content,
@@ -1,85 +1,50 @@
1
1
  """Advanced Capabilities - Extended features for agent operation.
2
2
 
3
3
  This module provides advanced capabilities that can be enabled with
4
- `advanced_capabilities=True` or `enable_extended=True`:
5
- - Extended actions (contacts, room management, image generation, etc.)
6
- - Extended providers (facts, knowledge, relationships, etc.)
7
- - Evaluators (reflection, relationship extraction)
8
- - Extended services (rolodex, follow-up scheduling)
4
+ `advanced_capabilities=True` or `enable_extended=True`.
5
+
6
+ Relationship/contact extraction and social-memory features are owned by
7
+ `plugin-rolodex` and are intentionally not auto-registered here.
9
8
  """
10
9
 
11
10
  from .actions import (
12
- add_contact_action,
13
11
  advanced_actions,
14
12
  follow_room_action,
15
13
  generate_image_action,
16
14
  mute_room_action,
17
- remove_contact_action,
18
- schedule_follow_up_action,
19
- search_contacts_action,
20
- send_message_action,
21
15
  unfollow_room_action,
22
16
  unmute_room_action,
23
- update_contact_action,
24
- update_entity_action,
25
17
  update_role_action,
26
18
  update_settings_action,
27
19
  )
28
- from .evaluators import (
29
- advanced_evaluators,
30
- reflection_evaluator,
31
- relationship_extraction_evaluator,
32
- )
20
+ from .evaluators import advanced_evaluators
33
21
  from .providers import (
34
22
  advanced_providers,
35
23
  agent_settings_provider,
36
- contacts_provider,
37
- facts_provider,
38
- follow_ups_provider,
39
24
  knowledge_provider,
40
- relationships_provider,
41
25
  roles_provider,
42
26
  settings_provider,
43
27
  )
44
- from .services import (
45
- FollowUpService,
46
- RolodexService,
47
- advanced_services,
48
- )
28
+ from .services import advanced_services
49
29
 
50
30
  __all__ = [
51
31
  # Actions
52
32
  "advanced_actions",
53
- "add_contact_action",
54
33
  "follow_room_action",
55
34
  "generate_image_action",
56
35
  "mute_room_action",
57
- "remove_contact_action",
58
- "schedule_follow_up_action",
59
- "search_contacts_action",
60
- "send_message_action",
61
36
  "unfollow_room_action",
62
37
  "unmute_room_action",
63
- "update_contact_action",
64
- "update_entity_action",
65
38
  "update_role_action",
66
39
  "update_settings_action",
67
40
  # Providers
68
41
  "advanced_providers",
69
42
  "agent_settings_provider",
70
- "contacts_provider",
71
- "facts_provider",
72
- "follow_ups_provider",
73
43
  "knowledge_provider",
74
- "relationships_provider",
75
44
  "roles_provider",
76
45
  "settings_provider",
77
46
  # Evaluators
78
47
  "advanced_evaluators",
79
- "reflection_evaluator",
80
- "relationship_extraction_evaluator",
81
48
  # Services
82
49
  "advanced_services",
83
- "FollowUpService",
84
- "RolodexService",
85
50
  ]
@@ -3,52 +3,32 @@
3
3
  Extended actions that can be enabled with `advanced_capabilities=True`.
4
4
  """
5
5
 
6
- from .add_contact import add_contact_action
7
6
  from .follow_room import follow_room_action
8
7
  from .image_generation import generate_image_action
9
8
  from .mute_room import mute_room_action
10
- from .remove_contact import remove_contact_action
11
9
  from .roles import update_role_action
12
- from .schedule_follow_up import schedule_follow_up_action
13
- from .search_contacts import search_contacts_action
14
- from .send_message import send_message_action
15
10
  from .settings import update_settings_action
16
11
  from .unfollow_room import unfollow_room_action
17
12
  from .unmute_room import unmute_room_action
18
- from .update_contact import update_contact_action
19
- from .update_entity import update_entity_action
20
13
 
21
14
  __all__ = [
22
- "add_contact_action",
23
15
  "follow_room_action",
24
16
  "generate_image_action",
25
17
  "mute_room_action",
26
- "remove_contact_action",
27
- "schedule_follow_up_action",
28
- "search_contacts_action",
29
- "send_message_action",
30
18
  "unfollow_room_action",
31
19
  "unmute_room_action",
32
- "update_contact_action",
33
- "update_entity_action",
34
20
  "update_role_action",
35
21
  "update_settings_action",
36
22
  "advanced_actions",
37
23
  ]
38
24
 
25
+ # Rolodex/contact actions are provided by plugin-rolodex.
39
26
  advanced_actions = [
40
- add_contact_action,
41
27
  follow_room_action,
42
28
  generate_image_action,
43
29
  mute_room_action,
44
- remove_contact_action,
45
- schedule_follow_up_action,
46
- search_contacts_action,
47
- send_message_action,
48
30
  unfollow_room_action,
49
31
  unmute_room_action,
50
- update_contact_action,
51
- update_entity_action,
52
32
  update_role_action,
53
33
  update_settings_action,
54
34
  ]
@@ -13,7 +13,6 @@ from elizaos.types import (
13
13
  Content,
14
14
  ModelType,
15
15
  )
16
- from elizaos.utils.spec_examples import convert_spec_examples
17
16
 
18
17
  if TYPE_CHECKING:
19
18
  from elizaos.types import (
@@ -30,7 +29,22 @@ _spec = require_action_spec("ADD_CONTACT")
30
29
 
31
30
  def _convert_spec_examples() -> list[list[ActionExample]]:
32
31
  """Convert spec examples to ActionExample format."""
33
- return convert_spec_examples(_spec)
32
+ spec_examples = _spec.get("examples", [])
33
+ if spec_examples:
34
+ return [
35
+ [
36
+ ActionExample(
37
+ name=msg.get("name", ""),
38
+ content=Content(
39
+ text=msg.get("content", {}).get("text", ""),
40
+ actions=msg.get("content", {}).get("actions"),
41
+ ),
42
+ )
43
+ for msg in example
44
+ ]
45
+ for example in spec_examples
46
+ ]
47
+ return []
34
48
 
35
49
 
36
50
  @dataclass
@@ -89,18 +103,14 @@ class AddContactAction:
89
103
  notes = str(parsed.get("notes", ""))
90
104
  reason = str(parsed.get("reason", ""))
91
105
 
92
- from uuid import UUID as StdUUID
93
-
94
106
  entity_id = message.entity_id
95
- entity_id_uuid = StdUUID(str(entity_id)) if entity_id else None
96
107
  preferences = ContactPreferences(notes=notes) if notes else None
97
108
 
98
- if entity_id_uuid:
99
- await rolodex_service.add_contact(
100
- entity_id=entity_id_uuid,
101
- categories=categories,
102
- preferences=preferences,
103
- )
109
+ await rolodex_service.add_contact(
110
+ entity_id=entity_id,
111
+ categories=categories,
112
+ preferences=preferences,
113
+ )
104
114
 
105
115
  response_text = (
106
116
  f"I've added {contact_name} to your contacts as {', '.join(categories)}. {reason}"
@@ -3,27 +3,41 @@ from __future__ import annotations
3
3
  from dataclasses import dataclass, field
4
4
  from typing import TYPE_CHECKING
5
5
 
6
+ from elizaos.generated.spec_helpers import require_action_spec
6
7
  from elizaos.types import Action, ActionExample, ActionResult, Content
7
8
 
8
9
  if TYPE_CHECKING:
9
10
  from elizaos.types import HandlerCallback, HandlerOptions, IAgentRuntime, Memory, State
10
11
 
12
+ # Get text content from centralized specs
13
+ _spec = require_action_spec("FOLLOW_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 spec_examples:
20
+ return [
21
+ [
22
+ ActionExample(
23
+ name=msg.get("name", ""),
24
+ content=Content(
25
+ text=msg.get("content", {}).get("text", ""),
26
+ actions=msg.get("content", {}).get("actions"),
27
+ ),
28
+ )
29
+ for msg in example
30
+ ]
31
+ for example in spec_examples
32
+ ]
33
+ return []
34
+
11
35
 
12
36
  @dataclass
13
37
  class FollowRoomAction:
14
- name: str = "FOLLOW_ROOM"
15
- similes: list[str] = field(
16
- default_factory=lambda: [
17
- "JOIN_ROOM",
18
- "SUBSCRIBE_ROOM",
19
- "WATCH_ROOM",
20
- "ENTER_ROOM",
21
- ]
22
- )
23
- description: str = (
24
- "Follow a room to receive updates and monitor messages. "
25
- "Use this when you want to actively engage with a room's content."
26
- )
38
+ name: str = _spec["name"]
39
+ similes: list[str] = field(default_factory=lambda: list(_spec.get("similes", [])))
40
+ description: str = _spec["description"]
27
41
 
28
42
  async def validate(
29
43
  self, runtime: IAgentRuntime, message: Memory, _state: State | None = None
@@ -124,21 +138,7 @@ class FollowRoomAction:
124
138
 
125
139
  @property
126
140
  def examples(self) -> list[list[ActionExample]]:
127
- return [
128
- [
129
- ActionExample(
130
- name="{{name1}}",
131
- content=Content(text="Can you keep an eye on this channel?"),
132
- ),
133
- ActionExample(
134
- name="{{name2}}",
135
- content=Content(
136
- text="I'll follow this room and monitor its activity.",
137
- actions=["FOLLOW_ROOM"],
138
- ),
139
- ),
140
- ],
141
- ]
141
+ return _convert_spec_examples()
142
142
 
143
143
 
144
144
  follow_room_action = Action(
@@ -18,34 +18,21 @@ _spec = require_action_spec("GENERATE_IMAGE")
18
18
  def _convert_spec_examples() -> list[list[ActionExample]]:
19
19
  """Convert spec examples to ActionExample format."""
20
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(
21
+ if spec_examples:
22
+ return [
23
+ [
41
24
  ActionExample(
42
- name=str(msg.get("name", "")),
43
- content=Content(text=text, actions=actions),
25
+ name=msg.get("name", ""),
26
+ content=Content(
27
+ text=msg.get("content", {}).get("text", ""),
28
+ actions=msg.get("content", {}).get("actions"),
29
+ ),
44
30
  )
45
- )
46
- if row:
47
- result.append(row)
48
- return result
31
+ for msg in example
32
+ ]
33
+ for example in spec_examples
34
+ ]
35
+ return []
49
36
 
50
37
 
51
38
  @dataclass
@@ -16,34 +16,21 @@ _spec = require_action_spec("MUTE_ROOM")
16
16
  def _convert_spec_examples() -> list[list[ActionExample]]:
17
17
  """Convert spec examples to ActionExample format."""
18
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(
19
+ if spec_examples:
20
+ return [
21
+ [
39
22
  ActionExample(
40
- name=str(msg.get("name", "")),
41
- content=Content(text=text, actions=actions),
23
+ name=msg.get("name", ""),
24
+ content=Content(
25
+ text=msg.get("content", {}).get("text", ""),
26
+ actions=msg.get("content", {}).get("actions"),
27
+ ),
42
28
  )
43
- )
44
- if row:
45
- result.append(row)
46
- return result
29
+ for msg in example
30
+ ]
31
+ for example in spec_examples
32
+ ]
33
+ return []
47
34
 
48
35
 
49
36
  @dataclass
@@ -13,7 +13,6 @@ from elizaos.types import (
13
13
  Content,
14
14
  ModelType,
15
15
  )
16
- from elizaos.utils.spec_examples import convert_spec_examples
17
16
 
18
17
  if TYPE_CHECKING:
19
18
  from elizaos.types import (
@@ -30,7 +29,22 @@ _spec = require_action_spec("REMOVE_CONTACT")
30
29
 
31
30
  def _convert_spec_examples() -> list[list[ActionExample]]:
32
31
  """Convert spec examples to ActionExample format."""
33
- return convert_spec_examples(_spec)
32
+ spec_examples = _spec.get("examples", [])
33
+ if spec_examples:
34
+ return [
35
+ [
36
+ ActionExample(
37
+ name=msg.get("name", ""),
38
+ content=Content(
39
+ text=msg.get("content", {}).get("text", ""),
40
+ actions=msg.get("content", {}).get("actions"),
41
+ ),
42
+ )
43
+ for msg in example
44
+ ]
45
+ for example in spec_examples
46
+ ]
47
+ return []
34
48
 
35
49
 
36
50
  @dataclass
@@ -7,7 +7,6 @@ from uuid import UUID
7
7
 
8
8
  from elizaos.bootstrap.utils.xml import parse_key_value_xml
9
9
  from elizaos.generated.spec_helpers import require_action_spec
10
- from elizaos.prompts import UPDATE_ROLE_TEMPLATE
11
10
  from elizaos.types import Action, ActionExample, ActionResult, Content, ModelType
12
11
 
13
12
  if TYPE_CHECKING:
@@ -20,34 +19,21 @@ _spec = require_action_spec("UPDATE_ROLE")
20
19
  def _convert_spec_examples() -> list[list[ActionExample]]:
21
20
  """Convert spec examples to ActionExample format."""
22
21
  spec_examples = _spec.get("examples", [])
23
- if not isinstance(spec_examples, list):
24
- return []
25
- result: list[list[ActionExample]] = []
26
- for example in spec_examples:
27
- if not isinstance(example, list):
28
- continue
29
- row: list[ActionExample] = []
30
- for msg in example:
31
- if not isinstance(msg, dict):
32
- continue
33
- content = msg.get("content", {})
34
- text = ""
35
- actions: list[str] | None = None
36
- if isinstance(content, dict):
37
- text_val = content.get("text", "")
38
- text = str(text_val) if text_val else ""
39
- actions_val = content.get("actions")
40
- if isinstance(actions_val, list) and all(isinstance(a, str) for a in actions_val):
41
- actions = list(actions_val)
42
- row.append(
22
+ if spec_examples:
23
+ return [
24
+ [
43
25
  ActionExample(
44
- name=str(msg.get("name", "")),
45
- content=Content(text=text, actions=actions),
26
+ name=msg.get("name", ""),
27
+ content=Content(
28
+ text=msg.get("content", {}).get("text", ""),
29
+ actions=msg.get("content", {}).get("actions"),
30
+ ),
46
31
  )
47
- )
48
- if row:
49
- result.append(row)
50
- return result
32
+ for msg in example
33
+ ]
34
+ for example in spec_examples
35
+ ]
36
+ return []
51
37
 
52
38
 
53
39
  class Role(str, Enum):
@@ -3,8 +3,10 @@ from __future__ import annotations
3
3
  from dataclasses import dataclass, field
4
4
  from datetime import datetime
5
5
  from typing import TYPE_CHECKING
6
+ from uuid import UUID as StdUUID
6
7
 
7
8
  from elizaos.bootstrap.utils.xml import parse_key_value_xml
9
+ from elizaos.deterministic import get_prompt_reference_datetime
8
10
  from elizaos.generated.spec_helpers import require_action_spec
9
11
  from elizaos.prompts import SCHEDULE_FOLLOW_UP_TEMPLATE
10
12
  from elizaos.types import (
@@ -34,6 +36,25 @@ def _convert_spec_examples() -> list[list[ActionExample]]:
34
36
  return convert_spec_examples(_spec)
35
37
 
36
38
 
39
+ def _normalize_priority(raw_priority: str) -> str:
40
+ normalized = raw_priority.strip().lower()
41
+ if normalized in {"high", "medium", "low"}:
42
+ return normalized
43
+ return "medium"
44
+
45
+
46
+ def _coerce_uuid(value: object | None) -> StdUUID | None:
47
+ if value is None:
48
+ return None
49
+ text = str(value).strip()
50
+ if not text:
51
+ return None
52
+ try:
53
+ return StdUUID(text)
54
+ except ValueError:
55
+ return None
56
+
57
+
37
58
  @dataclass
38
59
  class ScheduleFollowUpAction:
39
60
  name: str = _spec["name"]
@@ -79,7 +100,12 @@ class ScheduleFollowUpAction:
79
100
  )
80
101
 
81
102
  state = await runtime.compose_state(message, ["RECENT_MESSAGES", "ENTITIES"])
82
- state.values["currentDateTime"] = datetime.utcnow().isoformat()
103
+ state.values["currentDateTime"] = get_prompt_reference_datetime(
104
+ runtime,
105
+ message,
106
+ state,
107
+ "action:schedule_follow_up",
108
+ ).isoformat()
83
109
 
84
110
  prompt = runtime.compose_prompt_from_state(
85
111
  state=state,
@@ -100,24 +126,53 @@ class ScheduleFollowUpAction:
100
126
  contact_name = str(parsed.get("contactName", ""))
101
127
  scheduled_at_str = str(parsed.get("scheduledAt", ""))
102
128
  reason = str(parsed.get("reason", "Follow-up"))
103
- priority = str(parsed.get("priority", "medium"))
129
+ priority = _normalize_priority(str(parsed.get("priority", "medium")))
104
130
  follow_up_message = str(parsed.get("message", ""))
131
+ parsed_entity_id = _coerce_uuid(parsed.get("entityId"))
132
+ message_entity_id = _coerce_uuid(message.entity_id)
133
+
134
+ try:
135
+ scheduled_at = datetime.fromisoformat(scheduled_at_str.replace("Z", "+00:00"))
136
+ except ValueError:
137
+ return ActionResult(
138
+ text="Could not parse the follow-up date/time",
139
+ success=False,
140
+ values={"error": True},
141
+ data={"error": "Invalid follow-up datetime"},
142
+ )
105
143
 
106
- scheduled_at = datetime.fromisoformat(scheduled_at_str.replace("Z", "+00:00"))
144
+ entity_id_uuid = parsed_entity_id or message_entity_id
107
145
 
108
- from uuid import UUID as StdUUID
146
+ if entity_id_uuid is None and contact_name:
147
+ contacts = await rolodex_service.search_contacts(search_term=contact_name)
148
+ if contacts:
149
+ entity_id_uuid = contacts[0].entity_id
109
150
 
110
- entity_id = message.entity_id
111
- entity_id_uuid = StdUUID(str(entity_id)) if entity_id else None
112
- if entity_id_uuid:
113
- await follow_up_service.schedule_follow_up(
114
- entity_id=entity_id_uuid,
115
- scheduled_at=scheduled_at,
116
- reason=reason,
117
- priority=priority,
118
- message=follow_up_message,
151
+ if entity_id_uuid is None:
152
+ return ActionResult(
153
+ text=f"Could not determine which contact to schedule for ({contact_name}).",
154
+ success=False,
155
+ values={"error": True},
156
+ data={"error": "Missing contact entity id"},
119
157
  )
120
158
 
159
+ contact = await rolodex_service.get_contact(entity_id_uuid)
160
+ if contact is None:
161
+ return ActionResult(
162
+ text=f"Contact '{contact_name}' was not found in the rolodex.",
163
+ success=False,
164
+ values={"error": True},
165
+ data={"error": "Contact not found"},
166
+ )
167
+
168
+ await follow_up_service.schedule_follow_up(
169
+ entity_id=entity_id_uuid,
170
+ scheduled_at=scheduled_at,
171
+ reason=reason,
172
+ priority=priority,
173
+ message=follow_up_message,
174
+ )
175
+
121
176
  response_text = f"I've scheduled a follow-up with {contact_name} for {scheduled_at.strftime('%B %d, %Y')}. Reason: {reason}"
122
177
 
123
178
  if callback:
@@ -127,11 +182,11 @@ class ScheduleFollowUpAction:
127
182
  text=response_text,
128
183
  success=True,
129
184
  values={
130
- "contactId": str(entity_id),
185
+ "contactId": str(entity_id_uuid),
131
186
  "scheduledAt": scheduled_at.isoformat(),
132
187
  },
133
188
  data={
134
- "contactId": str(entity_id),
189
+ "contactId": str(entity_id_uuid),
135
190
  "contactName": contact_name,
136
191
  "scheduledAt": scheduled_at.isoformat(),
137
192
  "reason": reason,
@@ -13,7 +13,6 @@ from elizaos.types import (
13
13
  Content,
14
14
  ModelType,
15
15
  )
16
- from elizaos.utils.spec_examples import convert_spec_examples
17
16
 
18
17
  if TYPE_CHECKING:
19
18
  from elizaos.types import (
@@ -30,7 +29,22 @@ _spec = require_action_spec("SEARCH_CONTACTS")
30
29
 
31
30
  def _convert_spec_examples() -> list[list[ActionExample]]:
32
31
  """Convert spec examples to ActionExample format."""
33
- return convert_spec_examples(_spec)
32
+ spec_examples = _spec.get("examples", [])
33
+ if spec_examples:
34
+ return [
35
+ [
36
+ ActionExample(
37
+ name=msg.get("name", ""),
38
+ content=Content(
39
+ text=msg.get("content", {}).get("text", ""),
40
+ actions=msg.get("content", {}).get("actions"),
41
+ ),
42
+ )
43
+ for msg in example
44
+ ]
45
+ for example in spec_examples
46
+ ]
47
+ return []
34
48
 
35
49
 
36
50
  @dataclass
@@ -95,7 +109,7 @@ class SearchContactsAction:
95
109
 
96
110
  contact_details: list[dict[str, str]] = []
97
111
  for contact in contacts:
98
- entity = await runtime.get_entity(str(contact.entity_id))
112
+ entity = await runtime.get_entity(contact.entity_id)
99
113
  name = entity.name if entity and entity.name else "Unknown"
100
114
  contact_details.append(
101
115
  {