coco-framework 0.1.0__py3-none-any.whl

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 (400) hide show
  1. coco/__init__.py +3 -0
  2. coco/agents/__init__.py +9 -0
  3. coco/agents/_subagent_transformer.py +307 -0
  4. coco/agents/factory.py +2164 -0
  5. coco/agents/middleware/__init__.py +100 -0
  6. coco/agents/middleware/_execution.py +385 -0
  7. coco/agents/middleware/_redaction.py +454 -0
  8. coco/agents/middleware/_retry.py +134 -0
  9. coco/agents/middleware/_trace_policy.py +61 -0
  10. coco/agents/middleware/context_editing.py +306 -0
  11. coco/agents/middleware/file_search.py +433 -0
  12. coco/agents/middleware/human_in_the_loop.py +680 -0
  13. coco/agents/middleware/internal_call_transformer.py +108 -0
  14. coco/agents/middleware/model_call_limit.py +267 -0
  15. coco/agents/middleware/model_fallback.py +417 -0
  16. coco/agents/middleware/model_retry.py +327 -0
  17. coco/agents/middleware/pii.py +878 -0
  18. coco/agents/middleware/provider_tool_search.py +307 -0
  19. coco/agents/middleware/shell_tool.py +949 -0
  20. coco/agents/middleware/summarization.py +910 -0
  21. coco/agents/middleware/todo.py +357 -0
  22. coco/agents/middleware/tool_call_limit.py +495 -0
  23. coco/agents/middleware/tool_emulator.py +251 -0
  24. coco/agents/middleware/tool_error.py +209 -0
  25. coco/agents/middleware/tool_retry.py +421 -0
  26. coco/agents/middleware/tool_selection.py +515 -0
  27. coco/agents/middleware/types.py +2201 -0
  28. coco/agents/structured_output.py +472 -0
  29. coco/chat_models/__init__.py +7 -0
  30. coco/chat_models/base.py +1082 -0
  31. coco/embeddings/__init__.py +18 -0
  32. coco/embeddings/base.py +279 -0
  33. coco/mcp/__init__.py +36 -0
  34. coco/mcp/adapter.py +245 -0
  35. coco/mcp/elicitation.py +394 -0
  36. coco/mcp/tools.py +307 -0
  37. coco/messages/__init__.py +73 -0
  38. coco/py.typed +0 -0
  39. coco/rate_limiters/__init__.py +13 -0
  40. coco/tools/__init__.py +22 -0
  41. coco/tools/tool_node.py +20 -0
  42. coco_anthropic/__init__.py +15 -0
  43. coco_anthropic/_client_utils.py +81 -0
  44. coco_anthropic/_compat.py +228 -0
  45. coco_anthropic/_sdk_compat.py +124 -0
  46. coco_anthropic/_version.py +3 -0
  47. coco_anthropic/chat_models.py +3349 -0
  48. coco_anthropic/data/__init__.py +1 -0
  49. coco_anthropic/data/_profiles.py +491 -0
  50. coco_anthropic/data/profile_augmentations.toml +69 -0
  51. coco_anthropic/experimental.py +140 -0
  52. coco_anthropic/llms.py +428 -0
  53. coco_anthropic/middleware/__init__.py +25 -0
  54. coco_anthropic/middleware/anthropic_tools.py +1197 -0
  55. coco_anthropic/middleware/bash.py +84 -0
  56. coco_anthropic/middleware/file_search.py +360 -0
  57. coco_anthropic/middleware/prompt_caching.py +276 -0
  58. coco_anthropic/output_parsers.py +93 -0
  59. coco_anthropic/py.typed +0 -0
  60. coco_core/__init__.py +20 -0
  61. coco_core/_api/__init__.py +87 -0
  62. coco_core/_api/beta_decorator.py +266 -0
  63. coco_core/_api/deprecation.py +637 -0
  64. coco_core/_api/internal.py +23 -0
  65. coco_core/_api/path.py +50 -0
  66. coco_core/_import_utils.py +41 -0
  67. coco_core/_protocol.py +625 -0
  68. coco_core/_security/__init__.py +36 -0
  69. coco_core/_security/_exceptions.py +9 -0
  70. coco_core/_security/_policy.py +313 -0
  71. coco_core/_security/_ssrf_protection.py +155 -0
  72. coco_core/_security/_transport.py +254 -0
  73. coco_core/agents.py +258 -0
  74. coco_core/caches.py +272 -0
  75. coco_core/callbacks/__init__.py +132 -0
  76. coco_core/callbacks/base.py +1229 -0
  77. coco_core/callbacks/file.py +266 -0
  78. coco_core/callbacks/manager.py +2826 -0
  79. coco_core/callbacks/stdout.py +123 -0
  80. coco_core/callbacks/streaming_stdout.py +154 -0
  81. coco_core/callbacks/usage.py +124 -0
  82. coco_core/chat_history.py +267 -0
  83. coco_core/chat_loaders.py +26 -0
  84. coco_core/chat_sessions.py +19 -0
  85. coco_core/cross_encoders.py +18 -0
  86. coco_core/document_loaders/__init__.py +36 -0
  87. coco_core/document_loaders/base.py +155 -0
  88. coco_core/document_loaders/blob_loaders.py +38 -0
  89. coco_core/documents/__init__.py +55 -0
  90. coco_core/documents/base.py +347 -0
  91. coco_core/documents/compressor.py +74 -0
  92. coco_core/documents/transformers.py +79 -0
  93. coco_core/embeddings/__init__.py +31 -0
  94. coco_core/embeddings/embeddings.py +78 -0
  95. coco_core/embeddings/fake.py +129 -0
  96. coco_core/env.py +22 -0
  97. coco_core/example_selectors/__init__.py +47 -0
  98. coco_core/example_selectors/base.py +60 -0
  99. coco_core/example_selectors/length_based.py +131 -0
  100. coco_core/example_selectors/semantic_similarity.py +366 -0
  101. coco_core/exceptions.py +166 -0
  102. coco_core/globals.py +72 -0
  103. coco_core/indexing/__init__.py +53 -0
  104. coco_core/indexing/api.py +954 -0
  105. coco_core/indexing/base.py +661 -0
  106. coco_core/indexing/in_memory.py +104 -0
  107. coco_core/language_models/__init__.py +116 -0
  108. coco_core/language_models/_compat_bridge.py +844 -0
  109. coco_core/language_models/_utils.py +343 -0
  110. coco_core/language_models/base.py +500 -0
  111. coco_core/language_models/chat_model_stream.py +1525 -0
  112. coco_core/language_models/chat_models.py +2748 -0
  113. coco_core/language_models/fake.py +137 -0
  114. coco_core/language_models/fake_chat_models.py +396 -0
  115. coco_core/language_models/llms.py +1569 -0
  116. coco_core/language_models/model_profile.py +183 -0
  117. coco_core/load/__init__.py +44 -0
  118. coco_core/load/_validation.py +191 -0
  119. coco_core/load/dump.py +120 -0
  120. coco_core/load/load.py +828 -0
  121. coco_core/load/mapping.py +1097 -0
  122. coco_core/load/serializable.py +406 -0
  123. coco_core/messages/__init__.py +198 -0
  124. coco_core/messages/ai.py +854 -0
  125. coco_core/messages/base.py +518 -0
  126. coco_core/messages/block_translators/__init__.py +112 -0
  127. coco_core/messages/block_translators/anthropic.py +534 -0
  128. coco_core/messages/block_translators/bedrock.py +108 -0
  129. coco_core/messages/block_translators/bedrock_converse.py +329 -0
  130. coco_core/messages/block_translators/coco_v0.py +303 -0
  131. coco_core/messages/block_translators/google_genai.py +610 -0
  132. coco_core/messages/block_translators/google_vertexai.py +21 -0
  133. coco_core/messages/block_translators/groq.py +157 -0
  134. coco_core/messages/block_translators/openai.py +1089 -0
  135. coco_core/messages/chat.py +64 -0
  136. coco_core/messages/content.py +1488 -0
  137. coco_core/messages/function.py +62 -0
  138. coco_core/messages/human.py +70 -0
  139. coco_core/messages/modifier.py +33 -0
  140. coco_core/messages/system.py +70 -0
  141. coco_core/messages/tool.py +418 -0
  142. coco_core/messages/utils.py +2442 -0
  143. coco_core/output_parsers/__init__.py +101 -0
  144. coco_core/output_parsers/base.py +361 -0
  145. coco_core/output_parsers/format_instructions.py +16 -0
  146. coco_core/output_parsers/json.py +139 -0
  147. coco_core/output_parsers/list.py +249 -0
  148. coco_core/output_parsers/openai_functions.py +324 -0
  149. coco_core/output_parsers/openai_tools.py +383 -0
  150. coco_core/output_parsers/pydantic.py +141 -0
  151. coco_core/output_parsers/string.py +63 -0
  152. coco_core/output_parsers/transform.py +175 -0
  153. coco_core/output_parsers/xml.py +301 -0
  154. coco_core/outputs/__init__.py +66 -0
  155. coco_core/outputs/chat_generation.py +147 -0
  156. coco_core/outputs/chat_result.py +39 -0
  157. coco_core/outputs/generation.py +80 -0
  158. coco_core/outputs/llm_result.py +112 -0
  159. coco_core/outputs/run_info.py +22 -0
  160. coco_core/prompt_values.py +161 -0
  161. coco_core/prompts/__init__.py +101 -0
  162. coco_core/prompts/base.py +508 -0
  163. coco_core/prompts/chat.py +1495 -0
  164. coco_core/prompts/dict.py +177 -0
  165. coco_core/prompts/few_shot.py +483 -0
  166. coco_core/prompts/few_shot_with_templates.py +239 -0
  167. coco_core/prompts/image.py +177 -0
  168. coco_core/prompts/loading.py +296 -0
  169. coco_core/prompts/message.py +97 -0
  170. coco_core/prompts/prompt.py +312 -0
  171. coco_core/prompts/string.py +399 -0
  172. coco_core/prompts/structured.py +214 -0
  173. coco_core/py.typed +0 -0
  174. coco_core/rate_limiters.py +254 -0
  175. coco_core/retrievers.py +328 -0
  176. coco_core/runnables/__init__.py +136 -0
  177. coco_core/runnables/base.py +6725 -0
  178. coco_core/runnables/branch.py +457 -0
  179. coco_core/runnables/config.py +712 -0
  180. coco_core/runnables/configurable.py +710 -0
  181. coco_core/runnables/fallbacks.py +663 -0
  182. coco_core/runnables/graph.py +741 -0
  183. coco_core/runnables/graph_ascii.py +366 -0
  184. coco_core/runnables/graph_mermaid.py +503 -0
  185. coco_core/runnables/graph_png.py +215 -0
  186. coco_core/runnables/history.py +631 -0
  187. coco_core/runnables/passthrough.py +849 -0
  188. coco_core/runnables/retry.py +379 -0
  189. coco_core/runnables/router.py +239 -0
  190. coco_core/runnables/schema.py +188 -0
  191. coco_core/runnables/utils.py +798 -0
  192. coco_core/stores.py +291 -0
  193. coco_core/structured_query.py +203 -0
  194. coco_core/sys_info.py +154 -0
  195. coco_core/tools/__init__.py +95 -0
  196. coco_core/tools/base.py +1948 -0
  197. coco_core/tools/convert.py +497 -0
  198. coco_core/tools/render.py +67 -0
  199. coco_core/tools/retriever.py +94 -0
  200. coco_core/tools/simple.py +208 -0
  201. coco_core/tools/structured.py +322 -0
  202. coco_core/tracers/__init__.py +47 -0
  203. coco_core/tracers/_compat.py +95 -0
  204. coco_core/tracers/_context.py +337 -0
  205. coco_core/tracers/_streaming.py +53 -0
  206. coco_core/tracers/base.py +955 -0
  207. coco_core/tracers/coco.py +279 -0
  208. coco_core/tracers/context.py +205 -0
  209. coco_core/tracers/core.py +766 -0
  210. coco_core/tracers/event_stream.py +1101 -0
  211. coco_core/tracers/log_stream.py +765 -0
  212. coco_core/tracers/memory_stream.py +200 -0
  213. coco_core/tracers/root_listeners.py +130 -0
  214. coco_core/tracers/run_collector.py +40 -0
  215. coco_core/tracers/schemas.py +14 -0
  216. coco_core/tracers/stdout.py +205 -0
  217. coco_core/utils/__init__.py +111 -0
  218. coco_core/utils/_gateway.py +274 -0
  219. coco_core/utils/_merge.py +218 -0
  220. coco_core/utils/aiter.py +357 -0
  221. coco_core/utils/env.py +86 -0
  222. coco_core/utils/formatting.py +80 -0
  223. coco_core/utils/function_calling.py +869 -0
  224. coco_core/utils/html.py +132 -0
  225. coco_core/utils/image.py +15 -0
  226. coco_core/utils/input.py +82 -0
  227. coco_core/utils/interactive_env.py +12 -0
  228. coco_core/utils/iter.py +229 -0
  229. coco_core/utils/json.py +230 -0
  230. coco_core/utils/json_schema.py +273 -0
  231. coco_core/utils/mustache.py +706 -0
  232. coco_core/utils/pydantic.py +631 -0
  233. coco_core/utils/strings.py +69 -0
  234. coco_core/utils/usage.py +61 -0
  235. coco_core/utils/utils.py +528 -0
  236. coco_core/utils/uuid.py +57 -0
  237. coco_core/vectorstores/__init__.py +53 -0
  238. coco_core/vectorstores/base.py +1111 -0
  239. coco_core/vectorstores/in_memory.py +546 -0
  240. coco_core/vectorstores/utils.py +163 -0
  241. coco_core/version.py +3 -0
  242. coco_framework-0.1.0.dist-info/METADATA +184 -0
  243. coco_framework-0.1.0.dist-info/RECORD +400 -0
  244. coco_framework-0.1.0.dist-info/WHEEL +4 -0
  245. coco_framework-0.1.0.dist-info/licenses/LICENSE +24 -0
  246. coco_framework-0.1.0.dist-info/licenses/NOTICE +15 -0
  247. coco_graph/_internal/__init__.py +4 -0
  248. coco_graph/_internal/_cache.py +31 -0
  249. coco_graph/_internal/_config.py +474 -0
  250. coco_graph/_internal/_constants.py +140 -0
  251. coco_graph/_internal/_fields.py +213 -0
  252. coco_graph/_internal/_future.py +220 -0
  253. coco_graph/_internal/_pydantic.py +275 -0
  254. coco_graph/_internal/_queue.py +124 -0
  255. coco_graph/_internal/_replay.py +90 -0
  256. coco_graph/_internal/_retry.py +29 -0
  257. coco_graph/_internal/_runnable.py +969 -0
  258. coco_graph/_internal/_scratchpad.py +19 -0
  259. coco_graph/_internal/_serde.py +253 -0
  260. coco_graph/_internal/_timeout.py +25 -0
  261. coco_graph/_internal/_typing.py +54 -0
  262. coco_graph/cache/base/__init__.py +48 -0
  263. coco_graph/cache/base/py.typed +0 -0
  264. coco_graph/cache/memory/__init__.py +73 -0
  265. coco_graph/cache/redis/__init__.py +144 -0
  266. coco_graph/cache/sqlite/__init__.py +120 -0
  267. coco_graph/callbacks.py +394 -0
  268. coco_graph/channels/__init__.py +29 -0
  269. coco_graph/channels/any_value.py +72 -0
  270. coco_graph/channels/base.py +121 -0
  271. coco_graph/channels/binop.py +155 -0
  272. coco_graph/channels/delta.py +202 -0
  273. coco_graph/channels/ephemeral_value.py +79 -0
  274. coco_graph/channels/last_value.py +151 -0
  275. coco_graph/channels/named_barrier_value.py +167 -0
  276. coco_graph/channels/topic.py +94 -0
  277. coco_graph/channels/untracked_value.py +73 -0
  278. coco_graph/checkpoint/base/__init__.py +861 -0
  279. coco_graph/checkpoint/base/id.py +109 -0
  280. coco_graph/checkpoint/base/py.typed +0 -0
  281. coco_graph/checkpoint/memory/__init__.py +698 -0
  282. coco_graph/checkpoint/memory/py.typed +0 -0
  283. coco_graph/checkpoint/postgres/__init__.py +602 -0
  284. coco_graph/checkpoint/postgres/_ainternal.py +23 -0
  285. coco_graph/checkpoint/postgres/_internal.py +21 -0
  286. coco_graph/checkpoint/postgres/aio.py +691 -0
  287. coco_graph/checkpoint/postgres/base.py +666 -0
  288. coco_graph/checkpoint/postgres/py.typed +0 -0
  289. coco_graph/checkpoint/postgres/shallow.py +967 -0
  290. coco_graph/checkpoint/serde/__init__.py +0 -0
  291. coco_graph/checkpoint/serde/_msgpack.py +97 -0
  292. coco_graph/checkpoint/serde/base.py +64 -0
  293. coco_graph/checkpoint/serde/encrypted.py +80 -0
  294. coco_graph/checkpoint/serde/event_hooks.py +52 -0
  295. coco_graph/checkpoint/serde/jsonplus.py +883 -0
  296. coco_graph/checkpoint/serde/py.typed +0 -0
  297. coco_graph/checkpoint/serde/types.py +68 -0
  298. coco_graph/checkpoint/sqlite/__init__.py +645 -0
  299. coco_graph/checkpoint/sqlite/_delta.py +172 -0
  300. coco_graph/checkpoint/sqlite/aio.py +738 -0
  301. coco_graph/checkpoint/sqlite/py.typed +0 -0
  302. coco_graph/checkpoint/sqlite/utils.py +116 -0
  303. coco_graph/config.py +196 -0
  304. coco_graph/constants.py +64 -0
  305. coco_graph/errors.py +241 -0
  306. coco_graph/func/__init__.py +620 -0
  307. coco_graph/graph/__init__.py +12 -0
  308. coco_graph/graph/_branch.py +225 -0
  309. coco_graph/graph/_node.py +103 -0
  310. coco_graph/graph/message.py +437 -0
  311. coco_graph/graph/state.py +1978 -0
  312. coco_graph/graph/ui.py +227 -0
  313. coco_graph/managed/__init__.py +3 -0
  314. coco_graph/managed/base.py +31 -0
  315. coco_graph/managed/is_last_step.py +24 -0
  316. coco_graph/prebuilt/__init__.py +23 -0
  317. coco_graph/prebuilt/_tool_call_stream.py +117 -0
  318. coco_graph/prebuilt/_tool_call_transformer.py +165 -0
  319. coco_graph/prebuilt/chat_agent_executor.py +1015 -0
  320. coco_graph/prebuilt/interrupt.py +105 -0
  321. coco_graph/prebuilt/py.typed +0 -0
  322. coco_graph/prebuilt/tool_node.py +2030 -0
  323. coco_graph/prebuilt/tool_validator.py +221 -0
  324. coco_graph/pregel/__init__.py +3 -0
  325. coco_graph/pregel/_algo.py +1460 -0
  326. coco_graph/pregel/_call.py +298 -0
  327. coco_graph/pregel/_checkpoint.py +331 -0
  328. coco_graph/pregel/_config.py +0 -0
  329. coco_graph/pregel/_draw.py +294 -0
  330. coco_graph/pregel/_executor.py +223 -0
  331. coco_graph/pregel/_io.py +174 -0
  332. coco_graph/pregel/_log.py +3 -0
  333. coco_graph/pregel/_loop.py +1988 -0
  334. coco_graph/pregel/_messages.py +461 -0
  335. coco_graph/pregel/_read.py +312 -0
  336. coco_graph/pregel/_retry.py +854 -0
  337. coco_graph/pregel/_runner.py +941 -0
  338. coco_graph/pregel/_tools.py +268 -0
  339. coco_graph/pregel/_utils.py +221 -0
  340. coco_graph/pregel/_validate.py +120 -0
  341. coco_graph/pregel/_write.py +192 -0
  342. coco_graph/pregel/debug.py +302 -0
  343. coco_graph/pregel/main.py +4364 -0
  344. coco_graph/pregel/protocol.py +288 -0
  345. coco_graph/pregel/types.py +38 -0
  346. coco_graph/py.typed +0 -0
  347. coco_graph/runtime.py +350 -0
  348. coco_graph/store/base/__init__.py +1322 -0
  349. coco_graph/store/base/batch.py +371 -0
  350. coco_graph/store/base/embed.py +436 -0
  351. coco_graph/store/base/py.typed +0 -0
  352. coco_graph/store/memory/__init__.py +592 -0
  353. coco_graph/store/memory/py.typed +0 -0
  354. coco_graph/store/postgres/__init__.py +4 -0
  355. coco_graph/store/postgres/aio.py +592 -0
  356. coco_graph/store/postgres/base.py +1480 -0
  357. coco_graph/store/postgres/py.typed +0 -0
  358. coco_graph/store/sqlite/__init__.py +4 -0
  359. coco_graph/store/sqlite/aio.py +632 -0
  360. coco_graph/store/sqlite/base.py +1514 -0
  361. coco_graph/stream/__init__.py +45 -0
  362. coco_graph/stream/_convert.py +32 -0
  363. coco_graph/stream/_mux.py +523 -0
  364. coco_graph/stream/_types.py +330 -0
  365. coco_graph/stream/run_stream.py +735 -0
  366. coco_graph/stream/stream_channel.py +341 -0
  367. coco_graph/stream/transformers.py +1039 -0
  368. coco_graph/types.py +1079 -0
  369. coco_graph/typing.py +48 -0
  370. coco_graph/utils/__init__.py +1 -0
  371. coco_graph/utils/config.py +4 -0
  372. coco_graph/utils/runnable.py +3 -0
  373. coco_graph/version.py +13 -0
  374. coco_graph/warnings.py +69 -0
  375. coco_openai/__init__.py +20 -0
  376. coco_openai/_compat.py +45 -0
  377. coco_openai/_version.py +3 -0
  378. coco_openai/chat_models/__init__.py +6 -0
  379. coco_openai/chat_models/_client_utils.py +684 -0
  380. coco_openai/chat_models/_compat.py +525 -0
  381. coco_openai/chat_models/azure.py +1227 -0
  382. coco_openai/chat_models/base.py +5654 -0
  383. coco_openai/chat_models/codex.py +557 -0
  384. coco_openai/chatgpt_oauth.py +1063 -0
  385. coco_openai/data/__init__.py +1 -0
  386. coco_openai/data/_profiles.py +1655 -0
  387. coco_openai/data/profile_augmentations.toml +127 -0
  388. coco_openai/embeddings/__init__.py +6 -0
  389. coco_openai/embeddings/azure.py +240 -0
  390. coco_openai/embeddings/base.py +820 -0
  391. coco_openai/llms/__init__.py +6 -0
  392. coco_openai/llms/azure.py +249 -0
  393. coco_openai/llms/base.py +919 -0
  394. coco_openai/middleware/__init__.py +11 -0
  395. coco_openai/middleware/openai_moderation.py +484 -0
  396. coco_openai/output_parsers/__init__.py +9 -0
  397. coco_openai/output_parsers/tools.py +9 -0
  398. coco_openai/py.typed +0 -0
  399. coco_openai/tools/__init__.py +5 -0
  400. coco_openai/tools/custom_tool.py +111 -0
@@ -0,0 +1,330 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import logging
5
+ from abc import ABC, abstractmethod
6
+ from collections.abc import Coroutine
7
+ from typing import Any, ClassVar, Literal
8
+
9
+ from typing_extensions import NotRequired, TypedDict
10
+
11
+ _logger = logging.getLogger(__name__)
12
+
13
+
14
+ class _ProtocolEventParams(TypedDict):
15
+ """Parameters for a protocol event.
16
+
17
+ `timestamp` is wall-clock milliseconds since the epoch and can go
18
+ backwards across NTP adjustments — use `ProtocolEvent.seq` for
19
+ ordering.
20
+ """
21
+
22
+ namespace: list[str]
23
+ timestamp: int
24
+ data: Any
25
+ interrupts: NotRequired[tuple[Any, ...]]
26
+
27
+
28
+ class ProtocolEvent(TypedDict):
29
+ """A protocol event emitted by the streaming infrastructure.
30
+
31
+ Wraps a raw stream part (values, messages, custom, etc.) in a uniform
32
+ envelope with a monotonic sequence number assigned by the root StreamMux.
33
+ Consumers that need a total order across root events should use `seq`, not
34
+ `params.timestamp` (which is wall-clock and not monotonic).
35
+ """
36
+
37
+ type: Literal["event"]
38
+ event_id: NotRequired[str] # snake_case to match the trustcoco-protocol wire field
39
+ seq: NotRequired[int]
40
+ method: str # StreamMode value: "values", "messages", "custom", etc.
41
+ params: _ProtocolEventParams
42
+
43
+
44
+ class StreamTransformer(ABC):
45
+ """Extension point for custom stream projections.
46
+
47
+ Transformers observe protocol events flowing through the StreamMux and
48
+ build typed derived projections (StreamChannels, promises, etc.).
49
+
50
+ Set `_native = True` on a transformer to have its projection keys
51
+ exposed as direct attributes on the run stream (in addition to
52
+ appearing in `run.extensions`).
53
+
54
+ Subclasses must implement `init` and override at least one of
55
+ `process` / `aprocess`. The `finalize` / `afinalize` and `fail` /
56
+ `afail` hooks are optional — the default implementations are no-ops.
57
+ StreamChannel instances in the projection dict are auto-closed /
58
+ auto-failed by the mux, so most transformers don't need `finalize`
59
+ or `fail` at all.
60
+
61
+ Transformers that need async work pick the async lane by:
62
+
63
+ 1. Overriding `aprocess` (and optionally `afinalize` / `afail`), or
64
+ 2. Calling `self.schedule(coro)` from inside a sync `process`, or
65
+ 3. Setting `requires_async = True` explicitly.
66
+
67
+ The mux detects these cases at registration and raises if they're
68
+ used under sync `stream()` — they only work under `astream()`.
69
+
70
+ Use `aprocess` when the pump must wait for async work before the
71
+ next transformer sees the event (e.g. PII redaction that mutates
72
+ `event` in place). Use `schedule()` for decoupled async work whose
73
+ result lands on an independent projection (e.g. async moderation
74
+ scoring, cost lookup, external tracing).
75
+
76
+ Attributes:
77
+ scope: Namespace the transformer operates within — `()` for the
78
+ root mux. Set at construction from the mux's scope (each
79
+ factory is called as `factory(scope)`).
80
+ requires_async: Explicit opt-in for transformers that need a
81
+ running event loop but don't override any async method (for
82
+ example, transformers that call `schedule()` from a sync
83
+ `process`). The mux also auto-detects the async lane when
84
+ `aprocess`, `afinalize`, or `afail` is overridden.
85
+ supports_sync: Set True only for transformers that override
86
+ async-lane hooks while still fully supporting the sync lane.
87
+ Such transformers may be registered under `stream()`.
88
+ required_stream_modes: Stream modes the graph must emit for
89
+ this transformer to have anything to process. Computed as
90
+ the union across all registered transformers to determine
91
+ which modes a `stream_events(version="v3")` run requests from the graph.
92
+ Empty tuple means the transformer consumes only synthetic
93
+ events (or is purely passive).
94
+ before_builtins: Opt-in for transformers that must run *before*
95
+ built-in transformers like `MessagesTransformer` and
96
+ `ToolCallTransformer`. The mux partitions factories by this
97
+ flag at registration time: `before_builtins = True`
98
+ transformers are registered first, then everything else in
99
+ the order supplied. Within each lane, registration order is
100
+ preserved. This is the supported hook for content-mutating
101
+ transformers (PII redaction, profanity filters, etc.) whose
102
+ mutations must land before built-ins eagerly snapshot text
103
+ fields into their projections. **Foot-gun:** transformers
104
+ in this lane see `tasks` events before `LifecycleTransformer`
105
+ and `SubgraphTransformer` consume them — mutating
106
+ `event["params"]["namespace"]` or the data dict's
107
+ `id` / `result` / `error` / `interrupts` fields will desync
108
+ their bookkeeping. Observe freely; mutate only fields no
109
+ built-in reads (e.g. `delta.text` on `messages` events).
110
+ """
111
+
112
+ requires_async: ClassVar[bool] = False
113
+ supports_sync: ClassVar[bool] = False
114
+ required_stream_modes: ClassVar[tuple[str, ...]] = ()
115
+ before_builtins: ClassVar[bool] = False
116
+
117
+ def __init__(self, scope: tuple[str, ...] = ()) -> None:
118
+ """Initialize the transformer with its mux's scope.
119
+
120
+ Args:
121
+ scope: The namespace tuple the owning mux is scoped to.
122
+ `()` for the root. Factories receive this at
123
+ construction time (`factory(scope)` in `StreamMux`).
124
+ """
125
+ self.scope: tuple[str, ...] = scope
126
+
127
+ @abstractmethod
128
+ def init(self) -> dict[str, Any]:
129
+ """Return the projection dict.
130
+
131
+ Keys become entries in `run.extensions`. If the transformer has
132
+ `_native = True`, keys are also set as direct attributes on the
133
+ run stream.
134
+
135
+ StreamChannel instances in the return value are automatically
136
+ wired by the StreamMux for protocol event auto-forwarding.
137
+ """
138
+ ...
139
+
140
+ def _on_register(self, mux: Any) -> None:
141
+ """Called by `StreamMux._register` after this transformer is wired in.
142
+
143
+ Default is a no-op. Override to capture a reference to the
144
+ owning mux — needed for transformers that build mini-muxes
145
+ via `mux._make_child(...)` (e.g. `SubgraphTransformer`).
146
+ """
147
+
148
+ def process(self, event: ProtocolEvent) -> bool:
149
+ """Handle an event on the sync lane.
150
+
151
+ Called for every event before it is appended to the main event
152
+ log. Subclasses must override either `process` or `aprocess`.
153
+ The default raises so a missing override fails loudly rather
154
+ than silently passing every event through.
155
+
156
+ Args:
157
+ event: The protocol event to observe.
158
+
159
+ Returns:
160
+ True to keep the event in the main log, False to suppress it.
161
+ """
162
+ raise NotImplementedError(
163
+ f"{type(self).__name__} must override process() or aprocess()"
164
+ )
165
+
166
+ async def aprocess(self, event: ProtocolEvent) -> bool:
167
+ """Handle an event on the async lane.
168
+
169
+ The mux awaits this before dispatching to the next transformer,
170
+ so a slow `aprocess` serializes the pipeline. Use it only when
171
+ a later transformer — or a consumer reading the event
172
+ synchronously — must see the result of the async work (e.g.
173
+ PII redaction that mutates `event` in place).
174
+
175
+ The default delegates to `process`, so purely-sync transformers
176
+ run unchanged under `astream()`.
177
+
178
+ Args:
179
+ event: The protocol event to observe.
180
+
181
+ Returns:
182
+ True to keep the event in the main log, False to suppress it.
183
+ """
184
+ return self.process(event)
185
+
186
+ def finalize(self) -> None:
187
+ """Called when the run ends normally (sync lane).
188
+
189
+ Override to close StreamChannels, resolve promises, or perform
190
+ other teardown. StreamChannel instances in the projection dict
191
+ are auto-closed by the mux.
192
+ """
193
+
194
+ async def afinalize(self) -> None:
195
+ """Called when the run ends normally (async lane).
196
+
197
+ By the time this runs, the mux has already awaited every task
198
+ started via `schedule()`, so StreamChannels can be closed here
199
+ without a last-task-wins race.
200
+
201
+ The default delegates to `finalize`.
202
+ """
203
+ self.finalize()
204
+
205
+ def fail(self, err: BaseException) -> None:
206
+ """Called when the run ends with an error (sync lane).
207
+
208
+ Override to fail StreamChannels, reject promises, or perform
209
+ other teardown. StreamChannel instances in the projection dict
210
+ are auto-failed by the mux.
211
+
212
+ Args:
213
+ err: The exception that ended the run.
214
+ """
215
+
216
+ async def afail(self, err: BaseException) -> None:
217
+ """Called when the run ends with an error (async lane).
218
+
219
+ The mux cancels and awaits every task started via `schedule()`
220
+ before calling this, so cleanup doesn't race with in-flight work.
221
+
222
+ The default delegates to `fail`.
223
+
224
+ Args:
225
+ err: The exception that ended the run.
226
+ """
227
+ self.fail(err)
228
+
229
+ # ------------------------------------------------------------------
230
+ # Scheduled async work
231
+ # ------------------------------------------------------------------
232
+
233
+ def schedule(
234
+ self,
235
+ coro: Coroutine[Any, Any, Any],
236
+ *,
237
+ on_error: Literal["log", "raise"] = "log",
238
+ ) -> asyncio.Task[Any]:
239
+ """Schedule a coroutine tied to this transformer's lifecycle.
240
+
241
+ The mux holds the task reference, awaits all scheduled tasks
242
+ during `aclose()` before calling `afinalize()`, and cancels
243
+ them on `afail()`. Authors don't need to track tasks or
244
+ implement the last-task-closes-the-log dance.
245
+
246
+ Requires a running event loop — call only under `astream()`.
247
+ Set `requires_async = True` on the class so registration under
248
+ sync `stream()` fails fast with a clear message.
249
+
250
+ Args:
251
+ coro: The coroutine to run. Its lifecycle is owned by the
252
+ mux from this point on.
253
+ on_error: `"log"` (default) catches and logs any exception
254
+ the coroutine raises, so a single failure doesn't tear
255
+ down the run. `"raise"` lets the exception propagate
256
+ when the mux joins pendings, converting the close path
257
+ into the fail path.
258
+
259
+ Returns:
260
+ The asyncio Task. Authors rarely need to await it directly
261
+ — consumers read results from whatever projection the
262
+ coroutine pushes into.
263
+
264
+ Raises:
265
+ RuntimeError: If called without a running event loop (i.e.
266
+ under sync `stream()` rather than `astream()`).
267
+ """
268
+ try:
269
+ asyncio.get_running_loop()
270
+ except RuntimeError:
271
+ raise RuntimeError(
272
+ f"{type(self).__name__}.schedule() requires a running "
273
+ "event loop; this transformer must run under astream(), "
274
+ "not stream(). Set requires_async=True on the class so "
275
+ "this fails at registration rather than at first event."
276
+ ) from None
277
+
278
+ wrapped = self._wrap_scheduled(coro) if on_error == "log" else coro
279
+ task = asyncio.create_task(wrapped)
280
+ tasks = self._scheduled_task_set()
281
+ tasks.add(task)
282
+ task.add_done_callback(tasks.discard)
283
+ return task
284
+
285
+ @staticmethod
286
+ async def _wrap_scheduled(coro: Coroutine[Any, Any, Any]) -> Any:
287
+ try:
288
+ return await coro
289
+ except asyncio.CancelledError:
290
+ raise
291
+ except BaseException:
292
+ _logger.exception("Scheduled StreamTransformer task failed")
293
+
294
+ def _scheduled_task_set(self) -> set[asyncio.Task[Any]]:
295
+ """Return the lazily-allocated task set.
296
+
297
+ Avoids requiring subclasses to call `super().__init__()`.
298
+ """
299
+ tasks: set[asyncio.Task[Any]] | None = getattr(
300
+ self, "_stream_scheduled_tasks", None
301
+ )
302
+ if tasks is None:
303
+ tasks = set()
304
+ self._stream_scheduled_tasks = tasks
305
+ return tasks
306
+
307
+
308
+ def transformer_requires_async(transformer: StreamTransformer) -> bool:
309
+ """Return True if the transformer needs a running event loop.
310
+
311
+ A transformer requires async if it explicitly opts in
312
+ (`requires_async = True`) or overrides any of the async-lane methods
313
+ (`aprocess`, `afinalize`, `afail`) without also declaring that it
314
+ supports the sync lane.
315
+
316
+ Args:
317
+ transformer: The transformer to inspect.
318
+
319
+ Returns:
320
+ True if the transformer cannot run under sync `stream()`.
321
+ """
322
+ if transformer.requires_async:
323
+ return True
324
+ if transformer.supports_sync:
325
+ return False
326
+ cls = type(transformer)
327
+ for name in ("aprocess", "afinalize", "afail"):
328
+ if getattr(cls, name) is not getattr(StreamTransformer, name):
329
+ return True
330
+ return False