@elizaos/python 2.0.0-alpha.3 → 2.0.0-alpha.30

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 (167) 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 +24 -13
  5. package/elizaos/advanced_capabilities/actions/follow_room.py +29 -29
  6. package/elizaos/advanced_capabilities/actions/image_generation.py +15 -28
  7. package/elizaos/advanced_capabilities/actions/mute_room.py +15 -28
  8. package/elizaos/advanced_capabilities/actions/remove_contact.py +17 -3
  9. package/elizaos/advanced_capabilities/actions/roles.py +17 -30
  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 +184 -51
  13. package/elizaos/advanced_capabilities/actions/settings.py +17 -3
  14. package/elizaos/advanced_capabilities/actions/unfollow_room.py +15 -28
  15. package/elizaos/advanced_capabilities/actions/unmute_room.py +15 -28
  16. package/elizaos/advanced_capabilities/actions/update_contact.py +17 -3
  17. package/elizaos/advanced_capabilities/actions/update_entity.py +17 -3
  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 +23 -3
  23. package/elizaos/advanced_capabilities/services/__init__.py +2 -9
  24. package/elizaos/advanced_capabilities/services/rolodex.py +2 -2
  25. package/elizaos/advanced_memory/actions/reset_session.py +143 -0
  26. package/elizaos/advanced_memory/evaluators/reflection.py +134 -0
  27. package/elizaos/advanced_memory/evaluators/relationship_extraction.py +203 -0
  28. package/elizaos/advanced_memory/memory_service.py +69 -27
  29. package/elizaos/advanced_memory/plugin.py +2 -1
  30. package/elizaos/advanced_memory/test_advanced_memory.py +357 -0
  31. package/elizaos/advanced_memory/types.py +2 -2
  32. package/elizaos/advanced_planning/actions/schedule_follow_up.py +222 -0
  33. package/elizaos/advanced_planning/planning_service.py +26 -14
  34. package/elizaos/basic_capabilities/__init__.py +0 -2
  35. package/elizaos/basic_capabilities/providers/__init__.py +0 -3
  36. package/elizaos/basic_capabilities/providers/actions.py +118 -29
  37. package/elizaos/basic_capabilities/providers/agent_settings.py +64 -0
  38. package/elizaos/basic_capabilities/providers/character.py +19 -21
  39. package/elizaos/basic_capabilities/providers/contacts.py +79 -0
  40. package/elizaos/basic_capabilities/providers/current_time.py +7 -4
  41. package/elizaos/basic_capabilities/providers/facts.py +87 -0
  42. package/elizaos/basic_capabilities/providers/follow_ups.py +117 -0
  43. package/elizaos/basic_capabilities/providers/knowledge.py +96 -0
  44. package/elizaos/basic_capabilities/providers/recent_messages.py +5 -0
  45. package/elizaos/basic_capabilities/providers/relationships.py +113 -0
  46. package/elizaos/basic_capabilities/providers/roles.py +96 -0
  47. package/elizaos/basic_capabilities/providers/settings.py +56 -0
  48. package/elizaos/basic_capabilities/providers/time.py +7 -4
  49. package/elizaos/basic_capabilities/services/embedding.py +10 -7
  50. package/elizaos/basic_capabilities/services/task.py +3 -3
  51. package/elizaos/bootstrap/__init__.py +21 -2
  52. package/elizaos/bootstrap/actions/__init__.py +3 -0
  53. package/elizaos/bootstrap/actions/reset_session.py +3 -0
  54. package/elizaos/bootstrap/actions/roles.py +5 -4
  55. package/elizaos/bootstrap/actions/schedule_follow_up.py +65 -7
  56. package/elizaos/bootstrap/actions/send_message.py +162 -15
  57. package/elizaos/bootstrap/autonomy/__init__.py +5 -1
  58. package/elizaos/bootstrap/autonomy/action.py +161 -0
  59. package/elizaos/bootstrap/autonomy/evaluators.py +217 -0
  60. package/elizaos/bootstrap/autonomy/service.py +238 -28
  61. package/elizaos/bootstrap/plugin.py +7 -0
  62. package/elizaos/bootstrap/providers/actions.py +118 -27
  63. package/elizaos/bootstrap/providers/agent_settings.py +1 -0
  64. package/elizaos/bootstrap/providers/attachments.py +1 -0
  65. package/elizaos/bootstrap/providers/capabilities.py +1 -0
  66. package/elizaos/bootstrap/providers/character.py +1 -0
  67. package/elizaos/bootstrap/providers/choice.py +1 -0
  68. package/elizaos/bootstrap/providers/contacts.py +1 -0
  69. package/elizaos/bootstrap/providers/current_time.py +8 -2
  70. package/elizaos/bootstrap/providers/entities.py +1 -0
  71. package/elizaos/bootstrap/providers/evaluators.py +1 -0
  72. package/elizaos/bootstrap/providers/facts.py +1 -0
  73. package/elizaos/bootstrap/providers/follow_ups.py +1 -0
  74. package/elizaos/bootstrap/providers/knowledge.py +26 -3
  75. package/elizaos/bootstrap/providers/providers_list.py +1 -0
  76. package/elizaos/bootstrap/providers/recent_messages.py +5 -0
  77. package/elizaos/bootstrap/providers/relationships.py +20 -13
  78. package/elizaos/bootstrap/providers/roles.py +1 -0
  79. package/elizaos/bootstrap/providers/settings.py +1 -0
  80. package/elizaos/bootstrap/providers/time.py +8 -4
  81. package/elizaos/bootstrap/providers/world.py +1 -0
  82. package/elizaos/bootstrap/services/embedding.py +206 -8
  83. package/elizaos/bootstrap/services/rolodex.py +2 -2
  84. package/elizaos/bootstrap/services/task.py +3 -3
  85. package/elizaos/deterministic.py +193 -0
  86. package/elizaos/generated/__init__.py +1 -0
  87. package/elizaos/generated/action_docs.py +3181 -0
  88. package/elizaos/generated/spec_helpers.py +175 -0
  89. package/elizaos/media/mime.py +4 -4
  90. package/elizaos/media/search.py +23 -23
  91. package/elizaos/runtime.py +223 -64
  92. package/elizaos/services/hook_service.py +3 -3
  93. package/elizaos/services/message_service.py +175 -29
  94. package/elizaos/types/components.py +2 -2
  95. package/elizaos/types/generated/__init__.py +12 -0
  96. package/elizaos/types/generated/eliza/v1/agent_pb2.py +63 -0
  97. package/elizaos/types/generated/eliza/v1/agent_pb2.pyi +159 -0
  98. package/elizaos/types/generated/eliza/v1/components_pb2.py +65 -0
  99. package/elizaos/types/generated/eliza/v1/components_pb2.pyi +160 -0
  100. package/elizaos/types/generated/eliza/v1/database_pb2.py +78 -0
  101. package/elizaos/types/generated/eliza/v1/database_pb2.pyi +305 -0
  102. package/elizaos/types/generated/eliza/v1/environment_pb2.py +58 -0
  103. package/elizaos/types/generated/eliza/v1/environment_pb2.pyi +135 -0
  104. package/elizaos/types/generated/eliza/v1/events_pb2.py +82 -0
  105. package/elizaos/types/generated/eliza/v1/events_pb2.pyi +322 -0
  106. package/elizaos/types/generated/eliza/v1/ipc_pb2.py +113 -0
  107. package/elizaos/types/generated/eliza/v1/ipc_pb2.pyi +367 -0
  108. package/elizaos/types/generated/eliza/v1/knowledge_pb2.py +41 -0
  109. package/elizaos/types/generated/eliza/v1/knowledge_pb2.pyi +26 -0
  110. package/elizaos/types/generated/eliza/v1/memory_pb2.py +55 -0
  111. package/elizaos/types/generated/eliza/v1/memory_pb2.pyi +111 -0
  112. package/elizaos/types/generated/eliza/v1/message_service_pb2.py +48 -0
  113. package/elizaos/types/generated/eliza/v1/message_service_pb2.pyi +69 -0
  114. package/elizaos/types/generated/eliza/v1/messaging_pb2.py +51 -0
  115. package/elizaos/types/generated/eliza/v1/messaging_pb2.pyi +97 -0
  116. package/elizaos/types/generated/eliza/v1/model_pb2.py +84 -0
  117. package/elizaos/types/generated/eliza/v1/model_pb2.pyi +280 -0
  118. package/elizaos/types/generated/eliza/v1/payment_pb2.py +44 -0
  119. package/elizaos/types/generated/eliza/v1/payment_pb2.pyi +70 -0
  120. package/elizaos/types/generated/eliza/v1/plugin_pb2.py +68 -0
  121. package/elizaos/types/generated/eliza/v1/plugin_pb2.pyi +145 -0
  122. package/elizaos/types/generated/eliza/v1/primitives_pb2.py +48 -0
  123. package/elizaos/types/generated/eliza/v1/primitives_pb2.pyi +92 -0
  124. package/elizaos/types/generated/eliza/v1/prompts_pb2.py +52 -0
  125. package/elizaos/types/generated/eliza/v1/prompts_pb2.pyi +74 -0
  126. package/elizaos/types/generated/eliza/v1/service_interfaces_pb2.py +211 -0
  127. package/elizaos/types/generated/eliza/v1/service_interfaces_pb2.pyi +1296 -0
  128. package/elizaos/types/generated/eliza/v1/service_pb2.py +42 -0
  129. package/elizaos/types/generated/eliza/v1/service_pb2.pyi +69 -0
  130. package/elizaos/types/generated/eliza/v1/settings_pb2.py +58 -0
  131. package/elizaos/types/generated/eliza/v1/settings_pb2.pyi +85 -0
  132. package/elizaos/types/generated/eliza/v1/state_pb2.py +60 -0
  133. package/elizaos/types/generated/eliza/v1/state_pb2.pyi +114 -0
  134. package/elizaos/types/generated/eliza/v1/task_pb2.py +42 -0
  135. package/elizaos/types/generated/eliza/v1/task_pb2.pyi +58 -0
  136. package/elizaos/types/generated/eliza/v1/tee_pb2.py +52 -0
  137. package/elizaos/types/generated/eliza/v1/tee_pb2.pyi +90 -0
  138. package/elizaos/types/generated/eliza/v1/testing_pb2.py +39 -0
  139. package/elizaos/types/generated/eliza/v1/testing_pb2.pyi +23 -0
  140. package/elizaos/types/model.py +33 -3
  141. package/elizaos/types/primitives.py +3 -3
  142. package/elizaos/types/runtime.py +17 -3
  143. package/elizaos/types/state.py +2 -2
  144. package/elizaos/utils/streaming.py +3 -3
  145. package/elizaos/utils/validation.py +76 -0
  146. package/package.json +4 -3
  147. package/pyproject.toml +1 -2
  148. package/requirements-dev.lock +2 -2
  149. package/requirements.in +1 -2
  150. package/requirements.lock +2 -2
  151. package/tests/test_action_parameters.py +2 -3
  152. package/tests/test_actions_provider_examples.py +58 -1
  153. package/tests/test_advanced_memory_behavior.py +0 -2
  154. package/tests/test_advanced_memory_flag.py +0 -2
  155. package/tests/test_advanced_planning_behavior.py +11 -5
  156. package/tests/test_async_embedding.py +124 -0
  157. package/tests/test_autonomy.py +24 -3
  158. package/tests/test_history_compaction.py +104 -0
  159. package/tests/test_memory_bounds.py +115 -0
  160. package/tests/test_runtime.py +8 -17
  161. package/tests/test_schedule_follow_up_action.py +260 -0
  162. package/tests/test_send_message_action_targets.py +114 -0
  163. package/tests/test_settings_crypto.py +0 -2
  164. package/tests/test_validation.py +141 -0
  165. package/tests/verify_memory_architecture.py +192 -0
  166. package/uv.lock +1565 -0
  167. package/elizaos/basic_capabilities/providers/capabilities.py +0 -62
@@ -6,7 +6,6 @@ Fundamental providers included by default in the bootstrap plugin.
6
6
  from .action_state import action_state_provider
7
7
  from .actions import actions_provider
8
8
  from .attachments import attachments_provider
9
- from .capabilities import capabilities_provider
10
9
  from .character import character_provider
11
10
  from .choice import choice_provider
12
11
  from .context_bench import context_bench_provider
@@ -22,7 +21,6 @@ __all__ = [
22
21
  "action_state_provider",
23
22
  "actions_provider",
24
23
  "attachments_provider",
25
- "capabilities_provider",
26
24
  "character_provider",
27
25
  "choice_provider",
28
26
  "context_bench_provider",
@@ -40,7 +38,6 @@ basic_providers = [
40
38
  actions_provider,
41
39
  action_state_provider,
42
40
  attachments_provider,
43
- capabilities_provider,
44
41
  character_provider,
45
42
  choice_provider,
46
43
  context_bench_provider,
@@ -1,10 +1,16 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING
3
+ import contextlib
4
+ from typing import TYPE_CHECKING, TypeVar, cast
4
5
 
5
6
  from google.protobuf.json_format import MessageToDict
6
7
 
7
8
  from elizaos.action_docs import get_canonical_action_example_calls
9
+ from elizaos.deterministic import (
10
+ build_conversation_seed,
11
+ build_deterministic_seed,
12
+ deterministic_int,
13
+ )
8
14
  from elizaos.generated.spec_helpers import require_provider_spec
9
15
  from elizaos.types import Provider, ProviderResult
10
16
  from elizaos.types.components import ActionExample
@@ -23,10 +29,6 @@ if TYPE_CHECKING:
23
29
  _spec = require_provider_spec("ACTIONS")
24
30
 
25
31
 
26
- def format_action_names(actions: list[Action]) -> str:
27
- return ", ".join(action.name for action in actions)
28
-
29
-
30
32
  def _format_parameter_type(schema: ActionParameterSchema) -> str:
31
33
  if schema.type == "number" and (schema.minimum is not None or schema.maximum is not None):
32
34
  min_val = schema.minimum if schema.minimum is not None else "∞"
@@ -35,9 +37,10 @@ def _format_parameter_type(schema: ActionParameterSchema) -> str:
35
37
  return schema.type
36
38
 
37
39
 
38
- def _get_param_schema(param: ActionParameter) -> object:
40
+ def _get_param_schema(param: ActionParameter) -> ActionParameterSchema | None:
39
41
  """Get schema from ActionParameter, handling both Pydantic and protobuf variants."""
40
- return getattr(param, "schema_def", None) or getattr(param, "schema", None)
42
+ schema = getattr(param, "schema_def", None) or getattr(param, "schema", None)
43
+ return cast("ActionParameterSchema | None", schema)
41
44
 
42
45
 
43
46
  def _format_action_parameters(parameters: list[ActionParameter]) -> str:
@@ -64,9 +67,23 @@ def _format_action_parameters(parameters: list[ActionParameter]) -> str:
64
67
  return "\n".join(lines)
65
68
 
66
69
 
67
- def format_actions(actions: list[Action]) -> str:
70
+ T = TypeVar("T")
71
+
72
+
73
+ def _deterministic_shuffle(items: list[T], seed: str, surface: str = "shuffle") -> list[T]:
74
+ shuffled = list(items)
75
+ for i in range(len(shuffled) - 1, 0, -1):
76
+ j = deterministic_int(seed, f"{surface}:{i}", i + 1)
77
+ shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
78
+ return shuffled
79
+
80
+
81
+ def format_actions(actions: list[Action], seed: str | None = None) -> str:
82
+ deterministic_seed = seed or build_deterministic_seed(
83
+ ["actions-format", ",".join(action.name for action in actions)]
84
+ )
68
85
  lines: list[str] = []
69
- for action in actions:
86
+ for action in _deterministic_shuffle(actions, deterministic_seed, "actions"):
70
87
  line = f"- **{action.name}**: {action.description or 'No description'}"
71
88
  if action.parameters:
72
89
  params_text = _format_action_parameters(action.parameters)
@@ -83,7 +100,24 @@ def _replace_name_placeholders(text: str) -> str:
83
100
  return text
84
101
 
85
102
 
86
- def format_action_examples(actions: list[Action], max_examples: int = 10) -> str:
103
+ def _replace_name_placeholders_seeded(text: str, seed: str, example_index: int) -> str:
104
+ names = ["Alex", "Jordan", "Sam", "Taylor", "Riley"]
105
+ output = text
106
+ for placeholder_index in range(1, 6):
107
+ name_index = deterministic_int(
108
+ seed,
109
+ f"example:{example_index}:name:{placeholder_index}",
110
+ len(names),
111
+ )
112
+ output = output.replace(f"{{{{name{placeholder_index}}}}}", names[name_index])
113
+ return output
114
+
115
+
116
+ def format_action_examples(
117
+ actions: list[Action],
118
+ max_examples: int = 10,
119
+ seed: str | None = None,
120
+ ) -> str:
87
121
  """
88
122
  Format a deterministic subset of action examples for prompt context.
89
123
 
@@ -92,27 +126,60 @@ def format_action_examples(actions: list[Action], max_examples: int = 10) -> str
92
126
  if max_examples <= 0:
93
127
  return ""
94
128
 
95
- examples: list[list[ActionExample]] = []
96
- for action in sorted(actions, key=lambda a: a.name):
97
- if not action.examples:
98
- continue
99
- for ex in action.examples:
100
- if isinstance(ex, list) and ex:
101
- examples.append(ex)
102
- if len(examples) >= max_examples:
103
- break
104
- if len(examples) >= max_examples:
105
- break
106
-
107
- if not examples:
129
+ actions_with_examples = [
130
+ action
131
+ for action in actions
132
+ if action.examples and isinstance(action.examples, list) and len(action.examples) > 0
133
+ ]
134
+ if not actions_with_examples:
108
135
  return ""
109
136
 
137
+ examples_copy: list[list[list[ActionExample]]] = [
138
+ [example for example in (action.examples or []) if isinstance(example, list) and example]
139
+ for action in actions_with_examples
140
+ ]
141
+ available_action_indices = [
142
+ idx for idx, action_examples in enumerate(examples_copy) if action_examples
143
+ ]
144
+
145
+ selection_seed = seed or build_deterministic_seed(
146
+ [
147
+ "action-examples",
148
+ ",".join(action.name for action in actions_with_examples),
149
+ max_examples,
150
+ ]
151
+ )
152
+
153
+ selected_examples: list[list[ActionExample]] = []
154
+ iteration = 0
155
+ while len(selected_examples) < max_examples and available_action_indices:
156
+ random_index = deterministic_int(
157
+ selection_seed,
158
+ f"action-index:{iteration}",
159
+ len(available_action_indices),
160
+ )
161
+ action_index = available_action_indices[random_index]
162
+ action_examples = examples_copy[action_index]
163
+
164
+ example_index = deterministic_int(
165
+ selection_seed,
166
+ f"example-index:{iteration}",
167
+ len(action_examples),
168
+ )
169
+ selected_examples.append(action_examples.pop(example_index))
170
+ iteration += 1
171
+
172
+ if not action_examples:
173
+ available_action_indices.pop(random_index)
174
+
110
175
  blocks: list[str] = []
111
- for ex in examples:
176
+ for example_index, ex in enumerate(selected_examples):
112
177
  lines: list[str] = []
113
178
  for msg in ex:
114
179
  msg_text = msg.content.text if msg.content and msg.content.text else ""
115
- lines.append(f"{msg.name}: {_replace_name_placeholders(msg_text)}")
180
+ lines.append(
181
+ f"{msg.name}: {_replace_name_placeholders_seeded(msg_text, selection_seed, example_index)}"
182
+ )
116
183
  blocks.append("\n".join(lines))
117
184
 
118
185
  return "\n\n".join(blocks)
@@ -182,6 +249,17 @@ def format_action_call_examples(actions: list[Action], max_examples: int = 5) ->
182
249
  return "\n\n".join(blocks)
183
250
 
184
251
 
252
+ def format_action_names(actions: list[Action], seed: str | None = None) -> str:
253
+ if not actions:
254
+ return ""
255
+
256
+ deterministic_seed = seed or build_deterministic_seed(
257
+ ["action-names", ",".join(action.name for action in actions)]
258
+ )
259
+ shuffled = _deterministic_shuffle(actions, deterministic_seed, "actions")
260
+ return ", ".join(action.name for action in shuffled)
261
+
262
+
185
263
  async def get_actions(
186
264
  runtime: IAgentRuntime,
187
265
  message: Memory,
@@ -193,16 +271,27 @@ async def get_actions(
193
271
  # Support both validate and validate_fn for backwards compatibility
194
272
  validate_fn = getattr(action, "validate", None) or getattr(action, "validate_fn", None)
195
273
  if validate_fn:
196
- is_valid = await validate_fn(runtime, message, state)
274
+ try:
275
+ is_valid = await validate_fn(runtime, message, state)
276
+ except Exception:
277
+ if hasattr(runtime, "logger"):
278
+ with contextlib.suppress(Exception):
279
+ runtime.logger.warning(
280
+ f"Action validation failed for {action.name}; excluding from prompt"
281
+ )
282
+ is_valid = False
197
283
  if is_valid:
198
284
  validated_actions.append(action)
199
285
  else:
200
286
  # If no validation function, include the action
201
287
  validated_actions.append(action)
202
288
 
203
- action_names = format_action_names(validated_actions)
204
- actions_text = format_actions(validated_actions)
205
- examples_text = format_action_examples(validated_actions, max_examples=10)
289
+ action_seed = build_conversation_seed(runtime, message, state, "provider:actions")
290
+ action_names = format_action_names(validated_actions, seed=f"{action_seed}:names")
291
+ actions_text = format_actions(validated_actions, seed=f"{action_seed}:descriptions")
292
+ examples_text = format_action_examples(
293
+ validated_actions, max_examples=10, seed=f"{action_seed}:examples"
294
+ )
206
295
  call_examples_text = format_action_call_examples(validated_actions, max_examples=5)
207
296
 
208
297
  text_parts: list[str] = [f"Possible response actions: {action_names}"]
@@ -0,0 +1,64 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from elizaos.generated.spec_helpers import require_provider_spec
6
+ from elizaos.types import Provider, ProviderResult
7
+
8
+ if TYPE_CHECKING:
9
+ from elizaos.types import IAgentRuntime, Memory, State
10
+
11
+ # Get text content from centralized specs
12
+ _spec = require_provider_spec("AGENT_SETTINGS")
13
+
14
+ SENSITIVE_KEY_PATTERNS = (
15
+ "key",
16
+ "secret",
17
+ "password",
18
+ "token",
19
+ "credential",
20
+ "auth",
21
+ "private",
22
+ )
23
+
24
+
25
+ async def get_agent_settings_context(
26
+ runtime: IAgentRuntime,
27
+ message: Memory,
28
+ state: State | None = None,
29
+ ) -> ProviderResult:
30
+ all_settings = runtime.get_all_settings()
31
+
32
+ safe_settings: dict[str, str] = {}
33
+ for key, value in all_settings.items():
34
+ if not any(pattern in key.lower() for pattern in SENSITIVE_KEY_PATTERNS):
35
+ safe_settings[key] = str(value)
36
+
37
+ sections: list[str] = []
38
+ if safe_settings:
39
+ sections.append("# Agent Settings")
40
+ for key, value in safe_settings.items():
41
+ display_value = value if len(value) <= 50 else value[:50] + "..."
42
+ sections.append(f"- {key}: {display_value}")
43
+
44
+ context_text = "\n".join(sections) if sections else ""
45
+
46
+ return ProviderResult(
47
+ text=context_text,
48
+ values={
49
+ "settingsCount": len(safe_settings),
50
+ "hasSettings": len(safe_settings) > 0,
51
+ },
52
+ data={
53
+ "settings": safe_settings,
54
+ },
55
+ )
56
+
57
+
58
+ agent_settings_provider = Provider(
59
+ name=_spec["name"],
60
+ description=_spec["description"],
61
+ get=get_agent_settings_context,
62
+ dynamic=_spec.get("dynamic", True),
63
+ position=_spec.get("position"),
64
+ )
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from collections.abc import Iterable
3
4
  from typing import TYPE_CHECKING
4
5
 
5
6
  from elizaos.generated.spec_helpers import require_provider_spec
@@ -27,6 +28,19 @@ def _resolve_name_list(items: list[str], name: str) -> list[str]:
27
28
  return [_resolve_name(s, name) for s in items]
28
29
 
29
30
 
31
+ def _coerce_text_list(value: object) -> list[str]:
32
+ """Normalize protobuf repeated fields and plain Python values to list[str]."""
33
+ if value is None:
34
+ return []
35
+ if isinstance(value, str):
36
+ return [value]
37
+ if isinstance(value, (list, tuple)):
38
+ return [str(item) for item in value]
39
+ if isinstance(value, Iterable):
40
+ return [str(item) for item in value]
41
+ return [str(value)]
42
+
43
+
30
44
  async def get_character_context(
31
45
  runtime: IAgentRuntime,
32
46
  message: Memory,
@@ -47,11 +61,7 @@ async def get_character_context(
47
61
  sections.append(f"\n## Bio\n{bio_text}")
48
62
 
49
63
  if character.adjectives:
50
- adjectives = (
51
- character.adjectives
52
- if isinstance(character.adjectives, list)
53
- else [character.adjectives]
54
- )
64
+ adjectives = _coerce_text_list(character.adjectives)
55
65
  resolved_adjectives = _resolve_name_list(adjectives, agent_name)
56
66
  sections.append(f"\n## Personality Traits\n{', '.join(resolved_adjectives)}")
57
67
 
@@ -65,34 +75,22 @@ async def get_character_context(
65
75
  sections.append(f"\n## Background\n{lore_text}")
66
76
 
67
77
  if character.topics:
68
- topics = character.topics if isinstance(character.topics, list) else [character.topics]
78
+ topics = _coerce_text_list(character.topics)
69
79
  resolved_topics = _resolve_name_list(topics, agent_name)
70
80
  sections.append(f"\n## Knowledge Areas\n{', '.join(resolved_topics)}")
71
81
 
72
82
  if character.style:
73
83
  style_sections: list[str] = []
74
84
  if character.style.all:
75
- all_style = (
76
- character.style.all
77
- if isinstance(character.style.all, list)
78
- else [character.style.all]
79
- )
85
+ all_style = _coerce_text_list(character.style.all)
80
86
  resolved_all = _resolve_name_list(all_style, agent_name)
81
87
  style_sections.append(f"General: {', '.join(resolved_all)}")
82
88
  if character.style.chat:
83
- chat_style = (
84
- character.style.chat
85
- if isinstance(character.style.chat, list)
86
- else [character.style.chat]
87
- )
89
+ chat_style = _coerce_text_list(character.style.chat)
88
90
  resolved_chat = _resolve_name_list(chat_style, agent_name)
89
91
  style_sections.append(f"Chat: {', '.join(resolved_chat)}")
90
92
  if character.style.post:
91
- post_style = (
92
- character.style.post
93
- if isinstance(character.style.post, list)
94
- else [character.style.post]
95
- )
93
+ post_style = _coerce_text_list(character.style.post)
96
94
  resolved_post = _resolve_name_list(post_style, agent_name)
97
95
  style_sections.append(f"Posts: {', '.join(resolved_post)}")
98
96
  if style_sections:
@@ -0,0 +1,79 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from elizaos.generated.spec_helpers import require_provider_spec
6
+ from elizaos.types import Provider, ProviderResult
7
+
8
+ if TYPE_CHECKING:
9
+ from elizaos.types import IAgentRuntime, Memory, State
10
+
11
+ # Get text content from centralized specs
12
+ _spec = require_provider_spec("CONTACTS")
13
+
14
+
15
+ async def get_contacts_context(
16
+ runtime: IAgentRuntime,
17
+ message: Memory,
18
+ state: State | None = None,
19
+ ) -> ProviderResult:
20
+ from elizaos.bootstrap.services.rolodex import RolodexService
21
+
22
+ rolodex_service = runtime.get_service("rolodex")
23
+ if not rolodex_service or not isinstance(rolodex_service, RolodexService):
24
+ return ProviderResult(text="", values={}, data={})
25
+
26
+ contacts = await rolodex_service.get_all_contacts()
27
+
28
+ if not contacts:
29
+ return ProviderResult(text="No contacts in rolodex.", values={"contactCount": 0}, data={})
30
+
31
+ contact_details: list[dict[str, str]] = []
32
+ for contact in contacts:
33
+ entity = await runtime.get_entity(str(contact.entity_id))
34
+ name = entity.name if entity and entity.name else "Unknown"
35
+ contact_details.append(
36
+ {
37
+ "id": str(contact.entity_id),
38
+ "name": name,
39
+ "categories": ",".join(contact.categories),
40
+ "tags": ",".join(contact.tags),
41
+ }
42
+ )
43
+
44
+ grouped: dict[str, list[dict[str, str]]] = {}
45
+ for detail in contact_details:
46
+ for cat in detail["categories"].split(","):
47
+ cat = cat.strip()
48
+ if cat:
49
+ grouped.setdefault(cat, []).append(detail)
50
+
51
+ text_summary = f"You have {len(contacts)} contacts in your rolodex:\n"
52
+
53
+ for category, items in grouped.items():
54
+ text_summary += f"\n{category.capitalize()}s ({len(items)}):\n"
55
+ for item in items:
56
+ text_summary += f"- {item['name']}"
57
+ if item["tags"]:
58
+ text_summary += f" [{item['tags']}]"
59
+ text_summary += "\n"
60
+
61
+ category_counts = {cat: len(items) for cat, items in grouped.items()}
62
+
63
+ return ProviderResult(
64
+ text=text_summary.strip(),
65
+ values={
66
+ "contactCount": len(contacts),
67
+ **category_counts,
68
+ },
69
+ data=category_counts,
70
+ )
71
+
72
+
73
+ contacts_provider = Provider(
74
+ name=_spec["name"],
75
+ description=_spec["description"],
76
+ get=get_contacts_context,
77
+ dynamic=_spec.get("dynamic", True),
78
+ position=_spec.get("position"),
79
+ )
@@ -1,8 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
- from datetime import UTC, datetime
4
3
  from typing import TYPE_CHECKING
5
4
 
5
+ from elizaos.deterministic import get_prompt_reference_datetime
6
6
  from elizaos.generated.spec_helpers import require_provider_spec
7
7
  from elizaos.types import Provider, ProviderResult
8
8
 
@@ -18,9 +18,12 @@ async def get_current_time_context(
18
18
  message: Memory,
19
19
  state: State | None = None,
20
20
  ) -> ProviderResult:
21
- _ = runtime, message, state
22
-
23
- now = datetime.now(UTC)
21
+ now = get_prompt_reference_datetime(
22
+ runtime,
23
+ message,
24
+ state,
25
+ "provider:current_time",
26
+ )
24
27
 
25
28
  iso_timestamp = now.isoformat()
26
29
  human_readable = now.strftime("%A, %B %d, %Y at %H:%M:%S UTC")
@@ -0,0 +1,87 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from elizaos.generated.spec_helpers import require_provider_spec
6
+ from elizaos.types import Provider, ProviderResult
7
+
8
+ if TYPE_CHECKING:
9
+ from elizaos.types import IAgentRuntime, Memory, State
10
+
11
+ # Get text content from centralized specs
12
+ _spec = require_provider_spec("FACTS")
13
+
14
+
15
+ async def get_facts_context(
16
+ runtime: IAgentRuntime,
17
+ message: Memory,
18
+ state: State | None = None,
19
+ ) -> ProviderResult:
20
+ sections: list[str] = []
21
+ facts_list: list[dict[str, str]] = []
22
+
23
+ entity_id = message.entity_id
24
+ room_id = message.room_id
25
+
26
+ if entity_id:
27
+ sender_facts = await runtime.get_memories(
28
+ entity_id=entity_id,
29
+ memory_type="fact",
30
+ limit=10,
31
+ )
32
+
33
+ if sender_facts:
34
+ sender = await runtime.get_entity(entity_id)
35
+ sender_name = sender.name if sender and sender.name else "User"
36
+ sections.append(f"\n## Facts about {sender_name}")
37
+
38
+ for fact in sender_facts:
39
+ if fact.content and fact.content.text:
40
+ fact_text = fact.content.text
41
+ if len(fact_text) > 200:
42
+ fact_text = fact_text[:200] + "..."
43
+ facts_list.append(
44
+ {"entityId": str(entity_id), "entityName": sender_name, "fact": fact_text}
45
+ )
46
+ sections.append(f"- {fact_text}")
47
+
48
+ if room_id:
49
+ room_facts = await runtime.get_memories(
50
+ room_id=room_id,
51
+ memory_type="fact",
52
+ limit=5,
53
+ )
54
+
55
+ if room_facts:
56
+ sections.append("\n## Room Context Facts")
57
+ for fact in room_facts:
58
+ if fact.content and fact.content.text:
59
+ fact_text = fact.content.text
60
+ if len(fact_text) > 200:
61
+ fact_text = fact_text[:200] + "..."
62
+ facts_list.append({"roomId": str(room_id), "fact": fact_text})
63
+ sections.append(f"- {fact_text}")
64
+
65
+ context_text = ""
66
+ if sections:
67
+ context_text = "# Known Facts" + "\n".join(sections)
68
+
69
+ return ProviderResult(
70
+ text=context_text,
71
+ values={
72
+ "factCount": len(facts_list),
73
+ "hasFacts": len(facts_list) > 0,
74
+ },
75
+ data={
76
+ "facts": facts_list,
77
+ },
78
+ )
79
+
80
+
81
+ facts_provider = Provider(
82
+ name=_spec["name"],
83
+ description=_spec["description"],
84
+ get=get_facts_context,
85
+ dynamic=_spec.get("dynamic", True),
86
+ position=_spec.get("position"),
87
+ )