@elizaos/python 2.0.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +239 -0
  3. package/elizaos/__init__.py +280 -0
  4. package/elizaos/action_docs.py +149 -0
  5. package/elizaos/advanced_capabilities/__init__.py +85 -0
  6. package/elizaos/advanced_capabilities/actions/__init__.py +54 -0
  7. package/elizaos/advanced_capabilities/actions/add_contact.py +139 -0
  8. package/elizaos/advanced_capabilities/actions/follow_room.py +151 -0
  9. package/elizaos/advanced_capabilities/actions/image_generation.py +148 -0
  10. package/elizaos/advanced_capabilities/actions/mute_room.py +164 -0
  11. package/elizaos/advanced_capabilities/actions/remove_contact.py +145 -0
  12. package/elizaos/advanced_capabilities/actions/roles.py +207 -0
  13. package/elizaos/advanced_capabilities/actions/schedule_follow_up.py +154 -0
  14. package/elizaos/advanced_capabilities/actions/search_contacts.py +145 -0
  15. package/elizaos/advanced_capabilities/actions/send_message.py +187 -0
  16. package/elizaos/advanced_capabilities/actions/settings.py +151 -0
  17. package/elizaos/advanced_capabilities/actions/unfollow_room.py +164 -0
  18. package/elizaos/advanced_capabilities/actions/unmute_room.py +164 -0
  19. package/elizaos/advanced_capabilities/actions/update_contact.py +164 -0
  20. package/elizaos/advanced_capabilities/actions/update_entity.py +161 -0
  21. package/elizaos/advanced_capabilities/evaluators/__init__.py +18 -0
  22. package/elizaos/advanced_capabilities/evaluators/reflection.py +134 -0
  23. package/elizaos/advanced_capabilities/evaluators/relationship_extraction.py +203 -0
  24. package/elizaos/advanced_capabilities/providers/__init__.py +36 -0
  25. package/elizaos/advanced_capabilities/providers/agent_settings.py +60 -0
  26. package/elizaos/advanced_capabilities/providers/contacts.py +77 -0
  27. package/elizaos/advanced_capabilities/providers/facts.py +82 -0
  28. package/elizaos/advanced_capabilities/providers/follow_ups.py +113 -0
  29. package/elizaos/advanced_capabilities/providers/knowledge.py +83 -0
  30. package/elizaos/advanced_capabilities/providers/relationships.py +112 -0
  31. package/elizaos/advanced_capabilities/providers/roles.py +97 -0
  32. package/elizaos/advanced_capabilities/providers/settings.py +51 -0
  33. package/elizaos/advanced_capabilities/services/__init__.py +18 -0
  34. package/elizaos/advanced_capabilities/services/follow_up.py +138 -0
  35. package/elizaos/advanced_capabilities/services/rolodex.py +244 -0
  36. package/elizaos/advanced_memory/__init__.py +3 -0
  37. package/elizaos/advanced_memory/evaluators.py +97 -0
  38. package/elizaos/advanced_memory/memory_service.py +556 -0
  39. package/elizaos/advanced_memory/plugin.py +30 -0
  40. package/elizaos/advanced_memory/prompts.py +12 -0
  41. package/elizaos/advanced_memory/providers.py +90 -0
  42. package/elizaos/advanced_memory/types.py +65 -0
  43. package/elizaos/advanced_planning/__init__.py +10 -0
  44. package/elizaos/advanced_planning/actions.py +145 -0
  45. package/elizaos/advanced_planning/message_classifier.py +127 -0
  46. package/elizaos/advanced_planning/planning_service.py +712 -0
  47. package/elizaos/advanced_planning/plugin.py +40 -0
  48. package/elizaos/advanced_planning/prompts.py +4 -0
  49. package/elizaos/basic_capabilities/__init__.py +66 -0
  50. package/elizaos/basic_capabilities/actions/__init__.py +24 -0
  51. package/elizaos/basic_capabilities/actions/choice.py +140 -0
  52. package/elizaos/basic_capabilities/actions/ignore.py +66 -0
  53. package/elizaos/basic_capabilities/actions/none.py +56 -0
  54. package/elizaos/basic_capabilities/actions/reply.py +120 -0
  55. package/elizaos/basic_capabilities/providers/__init__.py +54 -0
  56. package/elizaos/basic_capabilities/providers/action_state.py +113 -0
  57. package/elizaos/basic_capabilities/providers/actions.py +263 -0
  58. package/elizaos/basic_capabilities/providers/attachments.py +76 -0
  59. package/elizaos/basic_capabilities/providers/capabilities.py +62 -0
  60. package/elizaos/basic_capabilities/providers/character.py +113 -0
  61. package/elizaos/basic_capabilities/providers/choice.py +73 -0
  62. package/elizaos/basic_capabilities/providers/context_bench.py +44 -0
  63. package/elizaos/basic_capabilities/providers/current_time.py +58 -0
  64. package/elizaos/basic_capabilities/providers/entities.py +99 -0
  65. package/elizaos/basic_capabilities/providers/evaluators.py +54 -0
  66. package/elizaos/basic_capabilities/providers/providers_list.py +55 -0
  67. package/elizaos/basic_capabilities/providers/recent_messages.py +85 -0
  68. package/elizaos/basic_capabilities/providers/time.py +45 -0
  69. package/elizaos/basic_capabilities/providers/world.py +93 -0
  70. package/elizaos/basic_capabilities/services/__init__.py +18 -0
  71. package/elizaos/basic_capabilities/services/embedding.py +122 -0
  72. package/elizaos/basic_capabilities/services/task.py +178 -0
  73. package/elizaos/bootstrap/__init__.py +12 -0
  74. package/elizaos/bootstrap/actions/__init__.py +68 -0
  75. package/elizaos/bootstrap/actions/add_contact.py +149 -0
  76. package/elizaos/bootstrap/actions/choice.py +147 -0
  77. package/elizaos/bootstrap/actions/follow_room.py +151 -0
  78. package/elizaos/bootstrap/actions/ignore.py +80 -0
  79. package/elizaos/bootstrap/actions/image_generation.py +135 -0
  80. package/elizaos/bootstrap/actions/mute_room.py +151 -0
  81. package/elizaos/bootstrap/actions/none.py +71 -0
  82. package/elizaos/bootstrap/actions/remove_contact.py +159 -0
  83. package/elizaos/bootstrap/actions/reply.py +140 -0
  84. package/elizaos/bootstrap/actions/roles.py +193 -0
  85. package/elizaos/bootstrap/actions/schedule_follow_up.py +164 -0
  86. package/elizaos/bootstrap/actions/search_contacts.py +159 -0
  87. package/elizaos/bootstrap/actions/send_message.py +173 -0
  88. package/elizaos/bootstrap/actions/settings.py +165 -0
  89. package/elizaos/bootstrap/actions/unfollow_room.py +151 -0
  90. package/elizaos/bootstrap/actions/unmute_room.py +151 -0
  91. package/elizaos/bootstrap/actions/update_contact.py +178 -0
  92. package/elizaos/bootstrap/actions/update_entity.py +175 -0
  93. package/elizaos/bootstrap/autonomy/__init__.py +18 -0
  94. package/elizaos/bootstrap/autonomy/action.py +197 -0
  95. package/elizaos/bootstrap/autonomy/providers.py +165 -0
  96. package/elizaos/bootstrap/autonomy/routes.py +171 -0
  97. package/elizaos/bootstrap/autonomy/service.py +562 -0
  98. package/elizaos/bootstrap/autonomy/types.py +18 -0
  99. package/elizaos/bootstrap/evaluators/__init__.py +19 -0
  100. package/elizaos/bootstrap/evaluators/reflection.py +118 -0
  101. package/elizaos/bootstrap/evaluators/relationship_extraction.py +192 -0
  102. package/elizaos/bootstrap/plugin.py +140 -0
  103. package/elizaos/bootstrap/providers/__init__.py +80 -0
  104. package/elizaos/bootstrap/providers/action_state.py +71 -0
  105. package/elizaos/bootstrap/providers/actions.py +256 -0
  106. package/elizaos/bootstrap/providers/agent_settings.py +63 -0
  107. package/elizaos/bootstrap/providers/attachments.py +76 -0
  108. package/elizaos/bootstrap/providers/capabilities.py +66 -0
  109. package/elizaos/bootstrap/providers/character.py +128 -0
  110. package/elizaos/bootstrap/providers/choice.py +77 -0
  111. package/elizaos/bootstrap/providers/contacts.py +78 -0
  112. package/elizaos/bootstrap/providers/context_bench.py +49 -0
  113. package/elizaos/bootstrap/providers/current_time.py +56 -0
  114. package/elizaos/bootstrap/providers/entities.py +99 -0
  115. package/elizaos/bootstrap/providers/evaluators.py +58 -0
  116. package/elizaos/bootstrap/providers/facts.py +86 -0
  117. package/elizaos/bootstrap/providers/follow_ups.py +116 -0
  118. package/elizaos/bootstrap/providers/knowledge.py +73 -0
  119. package/elizaos/bootstrap/providers/providers_list.py +59 -0
  120. package/elizaos/bootstrap/providers/recent_messages.py +85 -0
  121. package/elizaos/bootstrap/providers/relationships.py +106 -0
  122. package/elizaos/bootstrap/providers/roles.py +95 -0
  123. package/elizaos/bootstrap/providers/settings.py +55 -0
  124. package/elizaos/bootstrap/providers/time.py +45 -0
  125. package/elizaos/bootstrap/providers/world.py +97 -0
  126. package/elizaos/bootstrap/services/__init__.py +26 -0
  127. package/elizaos/bootstrap/services/embedding.py +122 -0
  128. package/elizaos/bootstrap/services/follow_up.py +138 -0
  129. package/elizaos/bootstrap/services/rolodex.py +244 -0
  130. package/elizaos/bootstrap/services/task.py +585 -0
  131. package/elizaos/bootstrap/types.py +54 -0
  132. package/elizaos/bootstrap/utils/__init__.py +7 -0
  133. package/elizaos/bootstrap/utils/xml.py +69 -0
  134. package/elizaos/character.py +149 -0
  135. package/elizaos/logger.py +179 -0
  136. package/elizaos/media/__init__.py +45 -0
  137. package/elizaos/media/mime.py +315 -0
  138. package/elizaos/media/search.py +161 -0
  139. package/elizaos/media/tests/__init__.py +1 -0
  140. package/elizaos/media/tests/test_mime.py +117 -0
  141. package/elizaos/media/tests/test_search.py +156 -0
  142. package/elizaos/plugin.py +191 -0
  143. package/elizaos/prompts.py +1071 -0
  144. package/elizaos/py.typed +0 -0
  145. package/elizaos/runtime.py +2572 -0
  146. package/elizaos/services/__init__.py +49 -0
  147. package/elizaos/services/hook_service.py +511 -0
  148. package/elizaos/services/message_service.py +1248 -0
  149. package/elizaos/settings.py +182 -0
  150. package/elizaos/streaming_context.py +159 -0
  151. package/elizaos/trajectory_context.py +18 -0
  152. package/elizaos/types/__init__.py +512 -0
  153. package/elizaos/types/agent.py +31 -0
  154. package/elizaos/types/components.py +208 -0
  155. package/elizaos/types/database.py +64 -0
  156. package/elizaos/types/environment.py +46 -0
  157. package/elizaos/types/events.py +47 -0
  158. package/elizaos/types/memory.py +45 -0
  159. package/elizaos/types/model.py +393 -0
  160. package/elizaos/types/plugin.py +188 -0
  161. package/elizaos/types/primitives.py +100 -0
  162. package/elizaos/types/runtime.py +460 -0
  163. package/elizaos/types/service.py +113 -0
  164. package/elizaos/types/service_interfaces.py +244 -0
  165. package/elizaos/types/state.py +188 -0
  166. package/elizaos/types/task.py +29 -0
  167. package/elizaos/utils/__init__.py +108 -0
  168. package/elizaos/utils/spec_examples.py +48 -0
  169. package/elizaos/utils/streaming.py +426 -0
  170. package/elizaos_atropos_shared/__init__.py +1 -0
  171. package/elizaos_atropos_shared/canonical_eliza.py +282 -0
  172. package/package.json +19 -0
  173. package/pyproject.toml +143 -0
  174. package/requirements-dev.in +11 -0
  175. package/requirements-dev.lock +134 -0
  176. package/requirements.in +9 -0
  177. package/requirements.lock +64 -0
  178. package/tests/__init__.py +0 -0
  179. package/tests/test_action_parameters.py +154 -0
  180. package/tests/test_actions_provider_examples.py +39 -0
  181. package/tests/test_advanced_memory_behavior.py +96 -0
  182. package/tests/test_advanced_memory_flag.py +30 -0
  183. package/tests/test_advanced_planning_behavior.py +225 -0
  184. package/tests/test_advanced_planning_flag.py +26 -0
  185. package/tests/test_autonomy.py +445 -0
  186. package/tests/test_bootstrap_initialize.py +37 -0
  187. package/tests/test_character.py +163 -0
  188. package/tests/test_character_provider.py +231 -0
  189. package/tests/test_dynamic_prompt_exec.py +561 -0
  190. package/tests/test_logger_redaction.py +43 -0
  191. package/tests/test_plugin.py +117 -0
  192. package/tests/test_runtime.py +422 -0
  193. package/tests/test_salt_production_enforcement.py +22 -0
  194. package/tests/test_settings_crypto.py +118 -0
  195. package/tests/test_streaming.py +295 -0
  196. package/tests/test_types.py +221 -0
  197. package/tests/test_uuid_parity.py +46 -0
@@ -0,0 +1,182 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import os
5
+ import secrets
6
+ from collections.abc import Mapping
7
+
8
+ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
9
+ from cryptography.hazmat.primitives.ciphers.aead import AESGCM
10
+ from cryptography.hazmat.primitives.padding import PKCS7
11
+
12
+
13
+ def get_salt() -> str:
14
+ salt = os.environ.get("SECRET_SALT", "secretsalt")
15
+ node_env = os.environ.get("NODE_ENV", "").strip().lower()
16
+ allow_default = os.environ.get("ELIZA_ALLOW_DEFAULT_SECRET_SALT", "").strip().lower() == "true"
17
+ if node_env == "production" and salt == "secretsalt" and not allow_default:
18
+ raise RuntimeError(
19
+ "SECRET_SALT must be set to a non-default value in production. "
20
+ "Set ELIZA_ALLOW_DEFAULT_SECRET_SALT=true to override (not recommended)."
21
+ )
22
+ return salt
23
+
24
+
25
+ def _derive_key(salt: str) -> bytes:
26
+ return hashlib.sha256(salt.encode("utf-8")).digest()[:32]
27
+
28
+
29
+ def _looks_encrypted(value: str) -> bool:
30
+ parts = value.split(":")
31
+ # v2: v2:ivHex:ciphertextHex:tagHex
32
+ if len(parts) == 4 and parts[0] == "v2":
33
+ try:
34
+ iv = bytes.fromhex(parts[1])
35
+ tag = bytes.fromhex(parts[3])
36
+ except ValueError:
37
+ return False
38
+ return len(iv) == 12 and len(tag) == 16
39
+
40
+ # v1 legacy: ivHex:ciphertextHex
41
+ if len(parts) != 2:
42
+ return False
43
+ try:
44
+ iv = bytes.fromhex(parts[0])
45
+ except ValueError:
46
+ return False
47
+ return len(iv) == 16
48
+
49
+
50
+ def encrypt_string_value(value: object, salt: str) -> object:
51
+ if value is None or isinstance(value, (bool, int, float)):
52
+ return value
53
+ if not isinstance(value, str):
54
+ return value
55
+
56
+ if _looks_encrypted(value):
57
+ return value
58
+
59
+ # v2: AES-256-GCM with integrity tag
60
+ key = _derive_key(salt)
61
+ iv = secrets.token_bytes(12)
62
+ aad = b"elizaos:settings:v2"
63
+ aesgcm = AESGCM(key)
64
+ ciphertext_and_tag = aesgcm.encrypt(iv, value.encode("utf-8"), aad)
65
+ ciphertext = ciphertext_and_tag[:-16]
66
+ tag = ciphertext_and_tag[-16:]
67
+ return f"v2:{iv.hex()}:{ciphertext.hex()}:{tag.hex()}"
68
+
69
+
70
+ def decrypt_string_value(value: object, salt: str) -> object:
71
+ if not isinstance(value, str):
72
+ return value
73
+
74
+ parts = value.split(":")
75
+ if len(parts) == 4 and parts[0] == "v2":
76
+ try:
77
+ iv = bytes.fromhex(parts[1])
78
+ ciphertext = bytes.fromhex(parts[2])
79
+ tag = bytes.fromhex(parts[3])
80
+ except ValueError:
81
+ return value
82
+ if len(iv) != 12 or len(tag) != 16:
83
+ return value
84
+
85
+ key = _derive_key(salt)
86
+ aad = b"elizaos:settings:v2"
87
+ aesgcm = AESGCM(key)
88
+ try:
89
+ plaintext_bytes = aesgcm.decrypt(iv, ciphertext + tag, aad)
90
+ except Exception:
91
+ return value
92
+ try:
93
+ return plaintext_bytes.decode("utf-8")
94
+ except UnicodeDecodeError:
95
+ return value
96
+
97
+ if len(parts) != 2:
98
+ return value
99
+
100
+ iv_hex, ciphertext_hex = parts
101
+ try:
102
+ iv = bytes.fromhex(iv_hex)
103
+ except ValueError:
104
+ return value
105
+ if len(iv) != 16:
106
+ return value
107
+
108
+ try:
109
+ ciphertext = bytes.fromhex(ciphertext_hex)
110
+ except ValueError:
111
+ return value
112
+
113
+ key = _derive_key(salt)
114
+ cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
115
+ decryptor = cipher.decryptor()
116
+
117
+ try:
118
+ padded = decryptor.update(ciphertext) + decryptor.finalize()
119
+ except Exception:
120
+ return value
121
+
122
+ try:
123
+ unpadder = PKCS7(128).unpadder()
124
+ plaintext_bytes = unpadder.update(padded) + unpadder.finalize()
125
+ except ValueError:
126
+ return value
127
+
128
+ try:
129
+ return plaintext_bytes.decode("utf-8")
130
+ except UnicodeDecodeError:
131
+ return value
132
+
133
+
134
+ def encrypt_object_values(obj: Mapping[str, object], salt: str) -> dict[str, object]:
135
+ result: dict[str, object] = {}
136
+ for key, value in obj.items():
137
+ if isinstance(value, str) and value:
138
+ result[key] = encrypt_string_value(value, salt)
139
+ else:
140
+ result[key] = value
141
+ return result
142
+
143
+
144
+ def decrypt_object_values(obj: Mapping[str, object], salt: str) -> dict[str, object]:
145
+ result: dict[str, object] = {}
146
+ for key, value in obj.items():
147
+ if isinstance(value, str) and value:
148
+ result[key] = decrypt_string_value(value, salt)
149
+ else:
150
+ result[key] = value
151
+ return result
152
+
153
+
154
+ def migrate_encrypted_string_value(value: object, salt: str) -> object:
155
+ """
156
+ Migrate a legacy v1 encrypted string (AES-CBC) to v2 (AES-GCM).
157
+
158
+ - v2 values are returned unchanged
159
+ - v1 values are decrypted then re-encrypted as v2
160
+ - non-encrypted values are returned unchanged
161
+ """
162
+ if not isinstance(value, str):
163
+ return value
164
+ if value.startswith("v2:"):
165
+ return value
166
+ if not _looks_encrypted(value):
167
+ return value
168
+
169
+ decrypted = decrypt_string_value(value, salt)
170
+ if decrypted == value:
171
+ return value
172
+ return encrypt_string_value(decrypted, salt)
173
+
174
+
175
+ def migrate_object_values(obj: Mapping[str, object], salt: str) -> dict[str, object]:
176
+ result: dict[str, object] = {}
177
+ for key, value in obj.items():
178
+ result[key] = migrate_encrypted_string_value(value, salt)
179
+ return result
180
+
181
+
182
+ decrypt_secret = decrypt_string_value
@@ -0,0 +1,159 @@
1
+ """Streaming context management for automatic streaming in use_model calls.
2
+
3
+ Follows the OpenTelemetry ContextManager pattern (matching TypeScript implementation):
4
+ - Interface for context management
5
+ - Uses contextvars for async-safe context propagation
6
+ - Automatic streaming in nested async calls
7
+
8
+ This provides parity with TypeScript's streaming-context.ts.
9
+ """
10
+
11
+ from collections.abc import Callable
12
+ from contextvars import ContextVar
13
+ from dataclasses import dataclass
14
+ from typing import Any, TypeVar
15
+
16
+ # Type for generic return values
17
+ T = TypeVar("T")
18
+
19
+
20
+ @dataclass
21
+ class StreamingContext:
22
+ """Streaming context containing callbacks for streaming lifecycle.
23
+
24
+ Attributes:
25
+ on_stream_chunk: Called for each chunk of streamed content.
26
+ on_stream_end: Called when a use_model streaming call completes.
27
+ message_id: Optional message ID for tracking.
28
+ abort_signal: Optional callable returning True if cancelled.
29
+ """
30
+
31
+ on_stream_chunk: Callable[[str, str | None], Any]
32
+ """Called for each chunk of streamed content (chunk, message_id) -> None"""
33
+
34
+ on_stream_end: Callable[[], None] | None = None
35
+ """Called when a use_model streaming call completes (allows reset between calls)"""
36
+
37
+ message_id: str | None = None
38
+ """Optional message ID for tracking"""
39
+
40
+ abort_signal: Callable[[], bool] | None = None
41
+ """Optional abort signal - callable returning True if cancelled"""
42
+
43
+
44
+ # Context variable for async-safe streaming context propagation
45
+ _streaming_context: ContextVar[StreamingContext | None] = ContextVar(
46
+ "streaming_context", default=None
47
+ )
48
+
49
+
50
+ def run_with_streaming_context(
51
+ context: StreamingContext | None,
52
+ fn: Callable[[], T],
53
+ ) -> T:
54
+ """Run a function with a streaming context.
55
+
56
+ All use_model calls within this function will automatically use streaming
57
+ if a context with on_stream_chunk is provided.
58
+
59
+ Example:
60
+ async def handle_request(message: Memory) -> None:
61
+ async def on_chunk(chunk: str, msg_id: str | None) -> None:
62
+ await send_sse(chunk)
63
+
64
+ ctx = StreamingContext(on_stream_chunk=on_chunk)
65
+ await run_with_streaming_context(
66
+ ctx,
67
+ lambda: runtime.process_message(message)
68
+ )
69
+
70
+ Args:
71
+ context: The streaming context with on_stream_chunk callback.
72
+ fn: The function to run with streaming context.
73
+
74
+ Returns:
75
+ The result of the function.
76
+ """
77
+ token = _streaming_context.set(context)
78
+ try:
79
+ return fn()
80
+ finally:
81
+ _streaming_context.reset(token)
82
+
83
+
84
+ async def run_with_streaming_context_async(
85
+ context: StreamingContext | None,
86
+ fn: Callable[[], Any],
87
+ ) -> Any:
88
+ """Run an async function with a streaming context.
89
+
90
+ Same as run_with_streaming_context but for async functions.
91
+
92
+ Example:
93
+ async def handle_request(message: Memory) -> None:
94
+ async def on_chunk(chunk: str, msg_id: str | None) -> None:
95
+ await send_sse(chunk)
96
+
97
+ ctx = StreamingContext(on_stream_chunk=on_chunk)
98
+ await run_with_streaming_context_async(
99
+ ctx,
100
+ lambda: runtime.process_message(message)
101
+ )
102
+
103
+ Args:
104
+ context: The streaming context with on_stream_chunk callback.
105
+ fn: The async function to run with streaming context.
106
+
107
+ Returns:
108
+ The result of the async function.
109
+ """
110
+ token = _streaming_context.set(context)
111
+ try:
112
+ result = fn()
113
+ # Handle both sync and async functions
114
+ if hasattr(result, "__await__"):
115
+ return await result
116
+ return result
117
+ finally:
118
+ _streaming_context.reset(token)
119
+
120
+
121
+ def get_streaming_context() -> StreamingContext | None:
122
+ """Get the currently active streaming context.
123
+
124
+ Called by use_model to check if automatic streaming should be enabled.
125
+
126
+ Returns:
127
+ The current streaming context or None.
128
+ """
129
+ return _streaming_context.get()
130
+
131
+
132
+ def set_streaming_context(context: StreamingContext | None) -> None:
133
+ """Set the streaming context directly.
134
+
135
+ This is useful for frameworks that manage their own context lifecycle.
136
+ Prefer using run_with_streaming_context when possible.
137
+
138
+ Args:
139
+ context: The streaming context to set, or None to clear.
140
+ """
141
+ _streaming_context.set(context)
142
+
143
+
144
+ def clear_streaming_context() -> None:
145
+ """Clear the streaming context.
146
+
147
+ This is useful for cleanup after streaming operations.
148
+ """
149
+ _streaming_context.set(None)
150
+
151
+
152
+ __all__ = [
153
+ "StreamingContext",
154
+ "run_with_streaming_context",
155
+ "run_with_streaming_context_async",
156
+ "get_streaming_context",
157
+ "set_streaming_context",
158
+ "clear_streaming_context",
159
+ ]
@@ -0,0 +1,18 @@
1
+ from __future__ import annotations
2
+
3
+ from contextlib import contextmanager
4
+ from contextvars import ContextVar
5
+
6
+ # Async-safe context for associating runtime/model calls with a trajectory step.
7
+ CURRENT_TRAJECTORY_STEP_ID: ContextVar[str | None] = ContextVar(
8
+ "CURRENT_TRAJECTORY_STEP_ID", default=None
9
+ )
10
+
11
+
12
+ @contextmanager
13
+ def bind_trajectory_step(step_id: str | None):
14
+ token = CURRENT_TRAJECTORY_STEP_ID.set(step_id)
15
+ try:
16
+ yield
17
+ finally:
18
+ CURRENT_TRAJECTORY_STEP_ID.reset(token)