@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
@@ -10,7 +10,7 @@ from uuid import UUID, uuid4
10
10
  from google.protobuf.json_format import MessageToDict
11
11
 
12
12
  from elizaos.logger import Logger
13
- from elizaos.types.components import ActionContext, ActionResult, HandlerCallback, HandlerOptions
13
+ from elizaos.types.components import ActionContext, ActionResult, HandlerCallback
14
14
  from elizaos.types.memory import Memory
15
15
  from elizaos.types.primitives import Content
16
16
  from elizaos.types.service import Service
@@ -538,6 +538,8 @@ Focus on:
538
538
  ]
539
539
  results = await asyncio.gather(*tasks, return_exceptions=True)
540
540
  for r in results:
541
+ if isinstance(r, Exception):
542
+ raise RuntimeError(f"Plan step failed during parallel execution: {r}") from r
541
543
  if isinstance(r, ActionResult):
542
544
  execution.results.append(r)
543
545
 
@@ -586,6 +588,10 @@ Focus on:
586
588
 
587
589
  for step, r in zip(ready_batch, results, strict=False):
588
590
  completed_count += 1
591
+ if isinstance(r, Exception):
592
+ raise RuntimeError(
593
+ f"Plan step '{step.action_name}' failed during DAG execution: {r}"
594
+ ) from r
589
595
  if isinstance(r, ActionResult):
590
596
  execution.results.append(r)
591
597
 
@@ -622,13 +628,16 @@ Focus on:
622
628
  if execution.abort_event.is_set():
623
629
  raise RuntimeError("Plan execution aborted")
624
630
  try:
625
- options = HandlerOptions(
626
- action_context=action_context,
627
- parameters=step.parameters,
628
- )
629
- # Attach extra execution context (allowed by extra="allow")
630
- options.previous_results = previous_results # type: ignore[attr-defined]
631
- options.context = {"workingMemory": execution.working_memory} # type: ignore[attr-defined]
631
+ options = type(
632
+ "_PlanningHandlerOptions",
633
+ (),
634
+ {
635
+ "action_context": action_context,
636
+ "parameters": step.parameters,
637
+ "previous_results": previous_results,
638
+ "context": {"workingMemory": execution.working_memory},
639
+ },
640
+ )()
632
641
 
633
642
  validate_fn = getattr(action, "validate", None) or getattr(
634
643
  action, "validate_fn", None
@@ -641,12 +650,15 @@ Focus on:
641
650
  if result is None:
642
651
  return None
643
652
 
644
- if result.data is None:
645
- result.data = {}
646
- if isinstance(result.data, dict):
647
- result.data["stepId"] = str(step.id)
648
- result.data["actionName"] = step.action_name
649
- result.data["executedAt"] = int(time.time() * 1000)
653
+ step_metadata = {
654
+ "stepId": str(step.id),
655
+ "actionName": step.action_name,
656
+ "executedAt": int(time.time() * 1000),
657
+ }
658
+ if isinstance(result.data, dict) or hasattr(result.data, "update"):
659
+ result.data.update(step_metadata)
660
+ else:
661
+ result.data = step_metadata
650
662
  return result
651
663
  except Exception as e:
652
664
  retries += 1
@@ -18,7 +18,6 @@ from .providers import (
18
18
  actions_provider,
19
19
  attachments_provider,
20
20
  basic_providers,
21
- capabilities_provider,
22
21
  character_provider,
23
22
  choice_provider,
24
23
  context_bench_provider,
@@ -48,7 +47,6 @@ __all__ = [
48
47
  "action_state_provider",
49
48
  "actions_provider",
50
49
  "attachments_provider",
51
- "capabilities_provider",
52
50
  "character_provider",
53
51
  "choice_provider",
54
52
  "context_bench_provider",
@@ -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(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
+ )