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

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 (99) hide show
  1. package/elizaos/__init__.py +0 -1
  2. package/elizaos/advanced_capabilities/actions/schedule_follow_up.py +70 -15
  3. package/elizaos/advanced_capabilities/actions/send_message.py +9 -184
  4. package/elizaos/advanced_memory/memory_service.py +15 -17
  5. package/elizaos/advanced_planning/planning_service.py +26 -14
  6. package/elizaos/basic_capabilities/providers/actions.py +118 -29
  7. package/elizaos/basic_capabilities/providers/character.py +19 -21
  8. package/elizaos/basic_capabilities/providers/current_time.py +7 -4
  9. package/elizaos/basic_capabilities/providers/time.py +7 -4
  10. package/elizaos/bootstrap/__init__.py +21 -2
  11. package/elizaos/bootstrap/actions/schedule_follow_up.py +65 -7
  12. package/elizaos/bootstrap/actions/send_message.py +162 -15
  13. package/elizaos/bootstrap/autonomy/service.py +230 -28
  14. package/elizaos/bootstrap/providers/actions.py +118 -27
  15. package/elizaos/bootstrap/providers/agent_settings.py +1 -0
  16. package/elizaos/bootstrap/providers/attachments.py +1 -0
  17. package/elizaos/bootstrap/providers/capabilities.py +1 -0
  18. package/elizaos/bootstrap/providers/character.py +1 -0
  19. package/elizaos/bootstrap/providers/choice.py +1 -0
  20. package/elizaos/bootstrap/providers/contacts.py +1 -0
  21. package/elizaos/bootstrap/providers/current_time.py +8 -2
  22. package/elizaos/bootstrap/providers/entities.py +1 -0
  23. package/elizaos/bootstrap/providers/evaluators.py +1 -0
  24. package/elizaos/bootstrap/providers/facts.py +1 -0
  25. package/elizaos/bootstrap/providers/follow_ups.py +1 -0
  26. package/elizaos/bootstrap/providers/knowledge.py +1 -0
  27. package/elizaos/bootstrap/providers/providers_list.py +1 -0
  28. package/elizaos/bootstrap/providers/relationships.py +1 -0
  29. package/elizaos/bootstrap/providers/roles.py +1 -0
  30. package/elizaos/bootstrap/providers/settings.py +1 -0
  31. package/elizaos/bootstrap/providers/time.py +8 -4
  32. package/elizaos/bootstrap/providers/world.py +1 -0
  33. package/elizaos/deterministic.py +193 -0
  34. package/elizaos/generated/__init__.py +1 -0
  35. package/elizaos/generated/action_docs.py +3181 -0
  36. package/elizaos/generated/spec_helpers.py +175 -0
  37. package/elizaos/media/mime.py +2 -2
  38. package/elizaos/media/search.py +23 -23
  39. package/elizaos/runtime.py +152 -39
  40. package/elizaos/services/message_service.py +2 -6
  41. package/elizaos/types/components.py +2 -2
  42. package/elizaos/types/generated/__init__.py +12 -0
  43. package/elizaos/types/generated/eliza/v1/agent_pb2.py +63 -0
  44. package/elizaos/types/generated/eliza/v1/agent_pb2.pyi +161 -0
  45. package/elizaos/types/generated/eliza/v1/components_pb2.py +65 -0
  46. package/elizaos/types/generated/eliza/v1/components_pb2.pyi +160 -0
  47. package/elizaos/types/generated/eliza/v1/database_pb2.py +78 -0
  48. package/elizaos/types/generated/eliza/v1/database_pb2.pyi +305 -0
  49. package/elizaos/types/generated/eliza/v1/environment_pb2.py +58 -0
  50. package/elizaos/types/generated/eliza/v1/environment_pb2.pyi +135 -0
  51. package/elizaos/types/generated/eliza/v1/events_pb2.py +82 -0
  52. package/elizaos/types/generated/eliza/v1/events_pb2.pyi +322 -0
  53. package/elizaos/types/generated/eliza/v1/ipc_pb2.py +113 -0
  54. package/elizaos/types/generated/eliza/v1/ipc_pb2.pyi +367 -0
  55. package/elizaos/types/generated/eliza/v1/knowledge_pb2.py +41 -0
  56. package/elizaos/types/generated/eliza/v1/knowledge_pb2.pyi +26 -0
  57. package/elizaos/types/generated/eliza/v1/memory_pb2.py +55 -0
  58. package/elizaos/types/generated/eliza/v1/memory_pb2.pyi +111 -0
  59. package/elizaos/types/generated/eliza/v1/message_service_pb2.py +48 -0
  60. package/elizaos/types/generated/eliza/v1/message_service_pb2.pyi +69 -0
  61. package/elizaos/types/generated/eliza/v1/messaging_pb2.py +51 -0
  62. package/elizaos/types/generated/eliza/v1/messaging_pb2.pyi +97 -0
  63. package/elizaos/types/generated/eliza/v1/model_pb2.py +84 -0
  64. package/elizaos/types/generated/eliza/v1/model_pb2.pyi +280 -0
  65. package/elizaos/types/generated/eliza/v1/payment_pb2.py +44 -0
  66. package/elizaos/types/generated/eliza/v1/payment_pb2.pyi +70 -0
  67. package/elizaos/types/generated/eliza/v1/plugin_pb2.py +68 -0
  68. package/elizaos/types/generated/eliza/v1/plugin_pb2.pyi +145 -0
  69. package/elizaos/types/generated/eliza/v1/primitives_pb2.py +48 -0
  70. package/elizaos/types/generated/eliza/v1/primitives_pb2.pyi +92 -0
  71. package/elizaos/types/generated/eliza/v1/prompts_pb2.py +52 -0
  72. package/elizaos/types/generated/eliza/v1/prompts_pb2.pyi +74 -0
  73. package/elizaos/types/generated/eliza/v1/service_interfaces_pb2.py +211 -0
  74. package/elizaos/types/generated/eliza/v1/service_interfaces_pb2.pyi +1296 -0
  75. package/elizaos/types/generated/eliza/v1/service_pb2.py +42 -0
  76. package/elizaos/types/generated/eliza/v1/service_pb2.pyi +69 -0
  77. package/elizaos/types/generated/eliza/v1/settings_pb2.py +58 -0
  78. package/elizaos/types/generated/eliza/v1/settings_pb2.pyi +85 -0
  79. package/elizaos/types/generated/eliza/v1/state_pb2.py +60 -0
  80. package/elizaos/types/generated/eliza/v1/state_pb2.pyi +114 -0
  81. package/elizaos/types/generated/eliza/v1/task_pb2.py +42 -0
  82. package/elizaos/types/generated/eliza/v1/task_pb2.pyi +58 -0
  83. package/elizaos/types/generated/eliza/v1/tee_pb2.py +52 -0
  84. package/elizaos/types/generated/eliza/v1/tee_pb2.pyi +90 -0
  85. package/elizaos/types/generated/eliza/v1/testing_pb2.py +39 -0
  86. package/elizaos/types/generated/eliza/v1/testing_pb2.pyi +23 -0
  87. package/elizaos/types/model.py +3 -0
  88. package/elizaos/types/runtime.py +1 -1
  89. package/package.json +3 -2
  90. package/tests/test_action_parameters.py +2 -3
  91. package/tests/test_advanced_memory_behavior.py +0 -2
  92. package/tests/test_advanced_memory_flag.py +0 -2
  93. package/tests/test_advanced_planning_behavior.py +11 -5
  94. package/tests/test_autonomy.py +11 -1
  95. package/tests/test_runtime.py +8 -17
  96. package/tests/test_schedule_follow_up_action.py +260 -0
  97. package/tests/test_send_message_action_targets.py +114 -0
  98. package/tests/test_settings_crypto.py +0 -2
  99. package/uv.lock +1565 -0
@@ -0,0 +1,175 @@
1
+ """
2
+ Helper functions to lookup action/provider/evaluator specs by name.
3
+ These allow language-specific implementations to import their text content
4
+ (description, similes, examples) from the centralized specs.
5
+
6
+ DO NOT EDIT the spec data - update packages/prompts/specs/** and regenerate.
7
+ """
8
+
9
+ from .action_docs import (
10
+ ActionDoc,
11
+ EvaluatorDoc,
12
+ ProviderDoc,
13
+ all_action_docs,
14
+ all_evaluator_docs,
15
+ all_provider_docs,
16
+ core_action_docs,
17
+ core_evaluator_docs,
18
+ core_provider_docs,
19
+ )
20
+
21
+
22
+ def _get_items(doc: object, key: str) -> list[dict[str, object]]:
23
+ if not isinstance(doc, dict):
24
+ return []
25
+ raw = doc.get(key)
26
+ if not isinstance(raw, list):
27
+ return []
28
+ return [item for item in raw if isinstance(item, dict)]
29
+
30
+
31
+ # Build lookup dicts for O(1) access
32
+ _core_action_map: dict[str, ActionDoc] = {}
33
+ for action in _get_items(core_action_docs, "actions"):
34
+ name = action.get("name")
35
+ if isinstance(name, str):
36
+ _core_action_map[name] = action # type: ignore[assignment]
37
+
38
+ _all_action_map: dict[str, ActionDoc] = {}
39
+ for action in _get_items(all_action_docs, "actions"):
40
+ name = action.get("name")
41
+ if isinstance(name, str):
42
+ _all_action_map[name] = action # type: ignore[assignment]
43
+
44
+ _core_provider_map: dict[str, ProviderDoc] = {}
45
+ for provider in _get_items(core_provider_docs, "providers"):
46
+ name = provider.get("name")
47
+ if isinstance(name, str):
48
+ _core_provider_map[name] = provider # type: ignore[assignment]
49
+
50
+ _all_provider_map: dict[str, ProviderDoc] = {}
51
+ for provider in _get_items(all_provider_docs, "providers"):
52
+ name = provider.get("name")
53
+ if isinstance(name, str):
54
+ _all_provider_map[name] = provider # type: ignore[assignment]
55
+
56
+ _core_evaluator_map: dict[str, EvaluatorDoc] = {}
57
+ for evaluator in _get_items(core_evaluator_docs, "evaluators"):
58
+ name = evaluator.get("name")
59
+ if isinstance(name, str):
60
+ _core_evaluator_map[name] = evaluator # type: ignore[assignment]
61
+
62
+ _all_evaluator_map: dict[str, EvaluatorDoc] = {}
63
+ for evaluator in _get_items(all_evaluator_docs, "evaluators"):
64
+ name = evaluator.get("name")
65
+ if isinstance(name, str):
66
+ _all_evaluator_map[name] = evaluator # type: ignore[assignment]
67
+
68
+
69
+ def get_action_spec(name: str) -> ActionDoc | None:
70
+ """
71
+ Get an action spec by name from the core specs.
72
+
73
+ Args:
74
+ name: The action name (e.g., "REPLY", "IGNORE")
75
+
76
+ Returns:
77
+ The action spec or None if not found
78
+ """
79
+ return _core_action_map.get(name) or _all_action_map.get(name)
80
+
81
+
82
+ def require_action_spec(name: str) -> ActionDoc:
83
+ """
84
+ Get an action spec by name, raising if not found.
85
+
86
+ Args:
87
+ name: The action name
88
+
89
+ Returns:
90
+ The action spec
91
+
92
+ Raises:
93
+ ValueError: If the action is not found
94
+ """
95
+ spec = get_action_spec(name)
96
+ if spec is None:
97
+ raise ValueError(f"Action spec not found: {name}")
98
+ return spec
99
+
100
+
101
+ def get_provider_spec(name: str) -> ProviderDoc | None:
102
+ """
103
+ Get a provider spec by name from the core specs.
104
+
105
+ Args:
106
+ name: The provider name (e.g., "CHARACTER", "TIME")
107
+
108
+ Returns:
109
+ The provider spec or None if not found
110
+ """
111
+ return _core_provider_map.get(name) or _all_provider_map.get(name)
112
+
113
+
114
+ def require_provider_spec(name: str) -> ProviderDoc:
115
+ """
116
+ Get a provider spec by name, raising if not found.
117
+
118
+ Args:
119
+ name: The provider name
120
+
121
+ Returns:
122
+ The provider spec
123
+
124
+ Raises:
125
+ ValueError: If the provider is not found
126
+ """
127
+ spec = get_provider_spec(name)
128
+ if spec is None:
129
+ raise ValueError(f"Provider spec not found: {name}")
130
+ return spec
131
+
132
+
133
+ def get_evaluator_spec(name: str) -> EvaluatorDoc | None:
134
+ """
135
+ Get an evaluator spec by name from the core specs.
136
+
137
+ Args:
138
+ name: The evaluator name (e.g., "REFLECTION")
139
+
140
+ Returns:
141
+ The evaluator spec or None if not found
142
+ """
143
+ return _core_evaluator_map.get(name) or _all_evaluator_map.get(name)
144
+
145
+
146
+ def require_evaluator_spec(name: str) -> EvaluatorDoc:
147
+ """
148
+ Get an evaluator spec by name, raising if not found.
149
+
150
+ Args:
151
+ name: The evaluator name
152
+
153
+ Returns:
154
+ The evaluator spec
155
+
156
+ Raises:
157
+ ValueError: If the evaluator is not found
158
+ """
159
+ spec = get_evaluator_spec(name)
160
+ if spec is None:
161
+ raise ValueError(f"Evaluator spec not found: {name}")
162
+ return spec
163
+
164
+
165
+ __all__ = [
166
+ "ActionDoc",
167
+ "ProviderDoc",
168
+ "EvaluatorDoc",
169
+ "get_action_spec",
170
+ "require_action_spec",
171
+ "get_provider_spec",
172
+ "require_provider_spec",
173
+ "get_evaluator_spec",
174
+ "require_evaluator_spec",
175
+ ]
@@ -12,14 +12,14 @@ from urllib.parse import urlparse
12
12
 
13
13
  # Try to import python-magic for MIME sniffing, fallback to filetype
14
14
  try:
15
- import magic
15
+ import magic # type: ignore[import-not-found]
16
16
 
17
17
  HAS_MAGIC = True
18
18
  except ImportError:
19
19
  HAS_MAGIC = False
20
20
 
21
21
  try:
22
- import filetype
22
+ import filetype # type: ignore[import-not-found]
23
23
 
24
24
  HAS_FILETYPE = True
25
25
  except ImportError:
@@ -109,36 +109,36 @@ def merge_hybrid_results(
109
109
  by_id: dict[str, dict] = {}
110
110
 
111
111
  # Add vector search results
112
- for r in vector:
113
- by_id[r.id] = {
114
- "id": r.id,
115
- "path": r.path,
116
- "start_line": r.start_line,
117
- "end_line": r.end_line,
118
- "source": r.source,
119
- "snippet": r.snippet,
120
- "vector_score": r.vector_score,
112
+ for vector_result in vector:
113
+ by_id[vector_result.id] = {
114
+ "id": vector_result.id,
115
+ "path": vector_result.path,
116
+ "start_line": vector_result.start_line,
117
+ "end_line": vector_result.end_line,
118
+ "source": vector_result.source,
119
+ "snippet": vector_result.snippet,
120
+ "vector_score": vector_result.vector_score,
121
121
  "text_score": 0.0,
122
122
  }
123
123
 
124
124
  # Merge keyword search results
125
- for r in keyword:
126
- if r.id in by_id:
127
- existing = by_id[r.id]
128
- existing["text_score"] = r.text_score
125
+ for keyword_result in keyword:
126
+ if keyword_result.id in by_id:
127
+ existing = by_id[keyword_result.id]
128
+ existing["text_score"] = keyword_result.text_score
129
129
  # Prefer keyword snippet if available (may have highlights)
130
- if r.snippet:
131
- existing["snippet"] = r.snippet
130
+ if keyword_result.snippet:
131
+ existing["snippet"] = keyword_result.snippet
132
132
  else:
133
- by_id[r.id] = {
134
- "id": r.id,
135
- "path": r.path,
136
- "start_line": r.start_line,
137
- "end_line": r.end_line,
138
- "source": r.source,
139
- "snippet": r.snippet,
133
+ by_id[keyword_result.id] = {
134
+ "id": keyword_result.id,
135
+ "path": keyword_result.path,
136
+ "start_line": keyword_result.start_line,
137
+ "end_line": keyword_result.end_line,
138
+ "source": keyword_result.source,
139
+ "snippet": keyword_result.snippet,
140
140
  "vector_score": 0.0,
141
- "text_score": r.text_score,
141
+ "text_score": keyword_result.text_score,
142
142
  }
143
143
 
144
144
  # Calculate weighted scores and create results
@@ -1,14 +1,22 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import contextlib
4
5
  import re
5
6
  import uuid
6
7
  import xml.etree.ElementTree as ET
7
- from collections.abc import AsyncIterator, Awaitable, Callable
8
+ from collections.abc import AsyncIterator, Awaitable, Callable, Mapping, MutableMapping
8
9
  from dataclasses import dataclass
9
10
  from typing import Any
10
11
 
12
+ from google.protobuf.struct_pb2 import Value as StructValue
13
+
11
14
  from elizaos.action_docs import with_canonical_action_docs, with_canonical_evaluator_docs
15
+ from elizaos.deterministic import (
16
+ build_conversation_seed,
17
+ deterministic_hex,
18
+ deterministic_uuid,
19
+ )
12
20
  from elizaos.logger import Logger, create_logger
13
21
  from elizaos.settings import decrypt_secret, get_salt
14
22
  from elizaos.types.agent import Character, TemplateType
@@ -18,6 +26,7 @@ from elizaos.types.components import (
18
26
  Evaluator,
19
27
  HandlerCallback,
20
28
  HandlerOptions,
29
+ PreEvaluatorResult,
21
30
  Provider,
22
31
  )
23
32
  from elizaos.types.database import AgentRunSummaryResult, IDatabaseAdapter, Log
@@ -81,6 +90,48 @@ class StreamingModelHandlerWrapper:
81
90
 
82
91
  _anonymous_agent_counter = 0
83
92
 
93
+ _MISSING = object()
94
+
95
+
96
+ def _struct_value_to_python(value: StructValue) -> object | None:
97
+ kind = value.WhichOneof("kind")
98
+ if kind == "null_value":
99
+ return None
100
+ if kind == "number_value":
101
+ return value.number_value
102
+ if kind == "string_value":
103
+ return value.string_value
104
+ if kind == "bool_value":
105
+ return value.bool_value
106
+ if kind == "struct_value":
107
+ return {
108
+ key: _struct_value_to_python(item) for key, item in value.struct_value.fields.items()
109
+ }
110
+ if kind == "list_value":
111
+ return [_struct_value_to_python(item) for item in value.list_value.values]
112
+ return None
113
+
114
+
115
+ def _is_struct_compatible(value: object) -> bool:
116
+ if value is None:
117
+ return True
118
+ if isinstance(value, (str, int, float, bool)):
119
+ return True
120
+ if isinstance(value, list):
121
+ return all(_is_struct_compatible(item) for item in value)
122
+ if isinstance(value, Mapping):
123
+ return all(
124
+ isinstance(map_key, str) and _is_struct_compatible(map_value)
125
+ for map_key, map_value in value.items()
126
+ )
127
+ return False
128
+
129
+
130
+ def _to_runtime_setting_value(value: object | None) -> str | bool | int | float | None:
131
+ if value is None or isinstance(value, (str, bool, int, float)):
132
+ return value
133
+ return str(value)
134
+
84
135
 
85
136
  class AgentRuntime(IAgentRuntime):
86
137
  def __init__(
@@ -354,37 +405,58 @@ class AgentRuntime(IAgentRuntime):
354
405
  if secret:
355
406
  if self._character.secrets is None:
356
407
  self._character.secrets = {}
357
- if isinstance(self._character.secrets, dict):
408
+ if isinstance(self._character.secrets, MutableMapping):
358
409
  self._character.secrets[key] = value # type: ignore[assignment]
359
410
  else:
360
411
  # Fall back to internal settings dict for protobuf objects
361
- self._settings[key] = value
412
+ self._settings[key] = _to_runtime_setting_value(value)
362
413
  return
363
414
 
364
415
  # Try to set on character.settings if it's a dict
365
- if isinstance(self._character.settings, dict):
416
+ if isinstance(self._character.settings, MutableMapping):
366
417
  self._character.settings[key] = value # type: ignore[assignment]
418
+ return
419
+
420
+ settings_extra = getattr(self._character.settings, "extra", None)
421
+ if (
422
+ settings_extra is not None
423
+ and hasattr(settings_extra, "update")
424
+ and _is_struct_compatible(value)
425
+ ):
426
+ settings_extra.update({key: value})
367
427
  else:
368
428
  # Fall back to internal settings dict for protobuf objects
369
- self._settings[key] = value
429
+ self._settings[key] = _to_runtime_setting_value(value)
370
430
 
371
431
  def get_setting(self, key: str) -> object | None:
372
432
  settings = self._character.settings
373
433
  secrets = self._character.secrets
374
434
 
375
- nested_secrets: dict[str, object] | None = None
376
- if isinstance(settings, dict):
435
+ nested_secrets: Mapping[str, object] | None = None
436
+ extra_value: object = _MISSING
437
+ if isinstance(settings, Mapping):
377
438
  nested = settings.get("secrets")
378
- if isinstance(nested, dict):
439
+ if isinstance(nested, Mapping):
379
440
  nested_secrets = nested
441
+ else:
442
+ settings_extra = getattr(settings, "extra", None)
443
+ settings_fields = getattr(settings_extra, "fields", None)
444
+ if isinstance(settings_fields, Mapping) and key in settings_fields:
445
+ struct_candidate = settings_fields[key]
446
+ if isinstance(struct_candidate, StructValue):
447
+ extra_value = _struct_value_to_python(struct_candidate)
448
+ else:
449
+ extra_value = struct_candidate
380
450
 
381
451
  value: object | None
382
- if isinstance(secrets, dict) and key in secrets:
452
+ if isinstance(secrets, Mapping) and key in secrets:
383
453
  value = secrets.get(key)
384
- elif isinstance(settings, dict) and key in settings:
454
+ elif isinstance(settings, Mapping) and key in settings:
385
455
  value = settings.get(key)
386
- elif isinstance(nested_secrets, dict) and key in nested_secrets:
456
+ elif isinstance(nested_secrets, Mapping) and key in nested_secrets:
387
457
  value = nested_secrets.get(key)
458
+ elif extra_value is not _MISSING:
459
+ value = extra_value
388
460
  else:
389
461
  value = self._settings.get(key)
390
462
 
@@ -409,12 +481,17 @@ class AgentRuntime(IAgentRuntime):
409
481
 
410
482
  def get_all_settings(self) -> dict[str, object | None]:
411
483
  keys: set[str] = set(self._settings.keys())
412
- if isinstance(self._character.settings, dict):
484
+ if isinstance(self._character.settings, Mapping):
413
485
  keys.update(self._character.settings.keys())
414
486
  nested = self._character.settings.get("secrets")
415
- if isinstance(nested, dict):
487
+ if isinstance(nested, Mapping):
416
488
  keys.update(nested.keys())
417
- if isinstance(self._character.secrets, dict):
489
+ else:
490
+ settings_extra = getattr(self._character.settings, "extra", None)
491
+ settings_fields = getattr(settings_extra, "fields", None)
492
+ if isinstance(settings_fields, Mapping):
493
+ keys.update(settings_fields.keys())
494
+ if isinstance(self._character.secrets, Mapping):
418
495
  keys.update(self._character.secrets.keys())
419
496
 
420
497
  return {k: self.get_setting(k) for k in keys}
@@ -608,8 +685,14 @@ class AgentRuntime(IAgentRuntime):
608
685
  errors.append(
609
686
  f"Required parameter '{param_def.name}' was not provided for action {action.name}"
610
687
  )
611
- elif getattr(param_def.schema, "default_value", None):
612
- validated[param_def.name] = param_def.schema.default_value
688
+ else:
689
+ default_value = getattr(param_def.schema, "default_value", None)
690
+ if isinstance(default_value, StructValue):
691
+ parsed_default = _struct_value_to_python(default_value)
692
+ if parsed_default is not None:
693
+ validated[param_def.name] = parsed_default
694
+ elif default_value is not None:
695
+ validated[param_def.name] = default_value
613
696
  continue
614
697
 
615
698
  schema_type = param_def.schema.type
@@ -770,6 +853,7 @@ class AgentRuntime(IAgentRuntime):
770
853
  if data_params is not None:
771
854
  # Convert protobuf Struct to dict for _parse_action_params
772
855
  from google.protobuf.json_format import MessageToDict
856
+
773
857
  if hasattr(data_params, "DESCRIPTOR"):
774
858
  params_raw = MessageToDict(data_params)
775
859
  else:
@@ -802,11 +886,8 @@ class AgentRuntime(IAgentRuntime):
802
886
  actionName=action.name,
803
887
  errors=errors,
804
888
  )
805
- try:
889
+ with contextlib.suppress(AttributeError, ValueError):
806
890
  options_obj.parameter_errors = errors
807
- except (AttributeError, ValueError):
808
- # Protobuf HandlerOptions may not have parameter_errors field
809
- pass
810
891
 
811
892
  if validated_params:
812
893
  from google.protobuf import struct_pb2
@@ -891,7 +972,7 @@ class AgentRuntime(IAgentRuntime):
891
972
  self,
892
973
  message: Memory,
893
974
  state: State | None = None,
894
- ) -> "PreEvaluatorResult":
975
+ ) -> PreEvaluatorResult:
895
976
  """Run phase='pre' evaluators as middleware before memory storage.
896
977
 
897
978
  Pre-evaluators can inspect, rewrite, or block a message before it
@@ -902,8 +983,6 @@ class AgentRuntime(IAgentRuntime):
902
983
  Returns:
903
984
  A merged PreEvaluatorResult.
904
985
  """
905
- from elizaos.types.components import PreEvaluatorResult
906
-
907
986
  pre_evaluators = [e for e in self._evaluators if getattr(e, "phase", "post") == "pre"]
908
987
  if not pre_evaluators:
909
988
  return PreEvaluatorResult(blocked=False)
@@ -1982,6 +2061,12 @@ class AgentRuntime(IAgentRuntime):
1982
2061
 
1983
2062
  schema_key = ",".join(s.field for s in schema)
1984
2063
  model_schema_key = f"{model_type_str}:{schema_key}"
2064
+ deterministic_seed = build_conversation_seed(
2065
+ self,
2066
+ None,
2067
+ state,
2068
+ f"dynamic-prompt:{model_schema_key}",
2069
+ )
1985
2070
 
1986
2071
  # Get validation level from settings or options (mirrors TypeScript behavior)
1987
2072
  default_context_level = 2
@@ -2023,7 +2108,11 @@ class AgentRuntime(IAgentRuntime):
2023
2108
  row.validate_field if row.validate_field is not None else default_validate
2024
2109
  )
2025
2110
  if needs_validation:
2026
- per_field_codes[row.field] = str(uuid.uuid4())[:8]
2111
+ per_field_codes[row.field] = deterministic_hex(
2112
+ deterministic_seed,
2113
+ f"field-code:{row.field}",
2114
+ 8,
2115
+ )
2027
2116
 
2028
2117
  # Streaming extractor (created on first iteration if streaming enabled)
2029
2118
  extractor: ValidationStreamExtractor | None = None
@@ -2147,9 +2236,18 @@ class AgentRuntime(IAgentRuntime):
2147
2236
  example_lines.append(container_end)
2148
2237
  example = "\n".join(example_lines)
2149
2238
 
2150
- init_code = str(uuid.uuid4())
2151
- mid_code = str(uuid.uuid4())
2152
- final_code = str(uuid.uuid4())
2239
+ init_code = deterministic_uuid(
2240
+ deterministic_seed,
2241
+ f"init-code:{current_retry}",
2242
+ )
2243
+ mid_code = deterministic_uuid(
2244
+ deterministic_seed,
2245
+ f"mid-code:{current_retry}",
2246
+ )
2247
+ final_code = deterministic_uuid(
2248
+ deterministic_seed,
2249
+ f"final-code:{current_retry}",
2250
+ )
2153
2251
 
2154
2252
  section_start = "<output>" if is_xml else "# Strict Output instructions"
2155
2253
  section_end = "</output>" if is_xml else ""
@@ -2223,25 +2321,40 @@ end code: {final_code}
2223
2321
  if not stream_fields and any(row.field == "text" for row in schema):
2224
2322
  stream_fields = ["text"]
2225
2323
 
2226
- stream_message_id = f"stream-{uuid.uuid4().hex[:12]}"
2324
+ stream_message_id = "stream-" + deterministic_hex(
2325
+ deterministic_seed,
2326
+ f"stream-message-id:{current_retry}",
2327
+ 20,
2328
+ )
2329
+
2330
+ on_stream_chunk = options.on_stream_chunk
2331
+ on_stream_event = options.on_stream_event
2332
+
2333
+ def _emit_chunk(
2334
+ chunk: str,
2335
+ _field: str | None,
2336
+ cb=on_stream_chunk,
2337
+ msg_id=stream_message_id,
2338
+ ) -> None:
2339
+ if cb is not None:
2340
+ cb(chunk, msg_id)
2341
+
2342
+ def _emit_event(
2343
+ event: StreamEvent,
2344
+ cb=on_stream_event,
2345
+ msg_id=stream_message_id,
2346
+ ) -> None:
2347
+ if cb is not None:
2348
+ cb(event, msg_id)
2227
2349
 
2228
- # Capture stream_message_id in default parameter to avoid late binding
2229
2350
  extractor = ValidationStreamExtractor(
2230
2351
  ValidationStreamExtractorConfig(
2231
2352
  level=validation_level,
2232
2353
  schema=schema,
2233
2354
  stream_fields=stream_fields,
2234
2355
  expected_codes=per_field_codes,
2235
- on_chunk=lambda chunk,
2236
- _field,
2237
- msg_id=stream_message_id: options.on_stream_chunk(chunk, msg_id)
2238
- if options.on_stream_chunk
2239
- else None,
2240
- on_event=lambda event, msg_id=stream_message_id: options.on_stream_event(
2241
- event, msg_id
2242
- )
2243
- if options.on_stream_event
2244
- else None,
2356
+ on_chunk=_emit_chunk,
2357
+ on_event=_emit_event if on_stream_event is not None else None,
2245
2358
  abort_signal=options.abort_signal,
2246
2359
  has_rich_consumer=has_rich_consumer,
2247
2360
  )
@@ -430,9 +430,7 @@ class DefaultMessageService(IMessageService):
430
430
  # or rewrite it (e.g. redact credentials).
431
431
  pre_result = await runtime.evaluate_pre(message)
432
432
  if pre_result.blocked:
433
- runtime.logger.warning(
434
- f"Message blocked by pre-evaluator: {pre_result.reason}"
435
- )
433
+ runtime.logger.warning(f"Message blocked by pre-evaluator: {pre_result.reason}")
436
434
  return MessageProcessingResult(
437
435
  did_respond=False,
438
436
  response_content=None,
@@ -440,9 +438,7 @@ class DefaultMessageService(IMessageService):
440
438
  state=None,
441
439
  )
442
440
  if pre_result.rewritten_text is not None:
443
- runtime.logger.info(
444
- f"Pre-evaluator rewrote message text: {pre_result.reason}"
445
- )
441
+ runtime.logger.info(f"Pre-evaluator rewrote message text: {pre_result.reason}")
446
442
  message.content.text = pre_result.rewritten_text
447
443
 
448
444
  # Step 1: Save incoming message to memory (if adapter available)
@@ -1,8 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from collections.abc import Awaitable, Callable
4
- from dataclasses import dataclass, field
5
- from enum import Enum
4
+ from dataclasses import dataclass
6
5
  from typing import TYPE_CHECKING, Literal, TypeAlias
7
6
 
8
7
  from elizaos.types.generated.eliza.v1 import components_pb2
@@ -47,6 +46,7 @@ class PreEvaluatorResult:
47
46
  rewritten_text: str | None = None
48
47
  reason: str | None = None
49
48
 
49
+
50
50
  # Proto-backed data types
51
51
  ActionExample = components_pb2.ActionExample
52
52
  ActionParameterSchema = components_pb2.ActionParameterSchema
@@ -0,0 +1,12 @@
1
+ """Generated protobuf modules for elizaOS types.
2
+
3
+ This __init__.py adds the 'generated' directory to sys.path so that
4
+ protobuf-generated files can resolve ``from eliza.v1 import …`` imports.
5
+ """
6
+
7
+ import os
8
+ import sys
9
+
10
+ _generated_dir = os.path.dirname(os.path.abspath(__file__))
11
+ if _generated_dir not in sys.path:
12
+ sys.path.insert(0, _generated_dir)