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,45 @@
1
+ """Streaming infrastructure for CocoGraph.
2
+
3
+ Compile a graph with `transformers=[...]` and call `graph.stream_events(version="v3")` /
4
+ `graph.astream_events(version="v3")` to drive a transformer pipeline that projects the
5
+ graph's raw events into ergonomic per-channel streams.
6
+ """
7
+
8
+ from coco_graph.stream._types import ProtocolEvent, StreamTransformer
9
+ from coco_graph.stream.run_stream import (
10
+ AsyncGraphRunStream,
11
+ AsyncSubgraphRunStream,
12
+ GraphRunStream,
13
+ SubgraphRunStream,
14
+ )
15
+ from coco_graph.stream.stream_channel import StreamChannel
16
+ from coco_graph.stream.transformers import (
17
+ CheckpointsTransformer,
18
+ CustomTransformer,
19
+ DebugTransformer,
20
+ LifecyclePayload,
21
+ LifecycleTransformer,
22
+ SubgraphStatus,
23
+ SubgraphTransformer,
24
+ TasksTransformer,
25
+ UpdatesTransformer,
26
+ )
27
+
28
+ __all__ = [
29
+ "AsyncGraphRunStream",
30
+ "AsyncSubgraphRunStream",
31
+ "CheckpointsTransformer",
32
+ "CustomTransformer",
33
+ "DebugTransformer",
34
+ "GraphRunStream",
35
+ "LifecyclePayload",
36
+ "LifecycleTransformer",
37
+ "ProtocolEvent",
38
+ "StreamChannel",
39
+ "StreamTransformer",
40
+ "SubgraphRunStream",
41
+ "SubgraphStatus",
42
+ "SubgraphTransformer",
43
+ "TasksTransformer",
44
+ "UpdatesTransformer",
45
+ ]
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from typing import Any, cast
5
+
6
+ from coco_graph.stream._types import ProtocolEvent, _ProtocolEventParams
7
+ from coco_graph.types import StreamPart
8
+
9
+
10
+ def convert_to_protocol_event(part: StreamPart) -> ProtocolEvent:
11
+ """Convert a v2 StreamPart to a ProtocolEvent.
12
+
13
+ Args:
14
+ part: A stream part with keys `type`, `ns`, `data`, and
15
+ optionally `interrupts` (present on values events).
16
+
17
+ Returns:
18
+ The equivalent ProtocolEvent.
19
+ """
20
+ part_dict = cast(dict[str, Any], part)
21
+ params: _ProtocolEventParams = {
22
+ "namespace": list(part_dict["ns"]),
23
+ "timestamp": int(time.time() * 1000),
24
+ "data": part_dict["data"],
25
+ }
26
+ if "interrupts" in part_dict:
27
+ params["interrupts"] = part_dict["interrupts"]
28
+ return {
29
+ "type": "event",
30
+ "method": part_dict["type"],
31
+ "params": params,
32
+ }
@@ -0,0 +1,523 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import time
5
+ from collections.abc import Awaitable, Callable
6
+ from typing import Any
7
+
8
+ from coco_graph.stream._types import (
9
+ ProtocolEvent,
10
+ StreamTransformer,
11
+ transformer_requires_async,
12
+ )
13
+ from coco_graph.stream.stream_channel import StreamChannel
14
+
15
+ TransformerFactory = Callable[["tuple[str, ...]"], StreamTransformer]
16
+ """Factory that builds a scoped transformer for a mux.
17
+
18
+ Called once per `StreamMux` with the mux's scope (typically `()` for
19
+ the root). Standard transformer classes accept a single positional
20
+ scope argument, so the class itself is a valid factory. User
21
+ transformers can close over their config:
22
+ `lambda scope: MyTransformer(scope, foo=...)`.
23
+ """
24
+
25
+
26
+ class StreamMux:
27
+ """Central event dispatcher for the streaming infrastructure.
28
+
29
+ Owns the main event log and routes events through a transformer
30
+ pipeline. StreamChannels with a name discovered in transformer
31
+ projections are auto-wired so that every `push()` also injects a
32
+ `ProtocolEvent` into the main log. StreamChannels without a name
33
+ are local-only.
34
+
35
+ Pass `is_async=True` when the mux will be consumed via async
36
+ iteration (`handler.astream()`). All StreamChannel instances
37
+ discovered during registration are automatically bound to the
38
+ matching mode.
39
+
40
+ Attributes:
41
+ extensions: Merged projection dict across all registered
42
+ transformers. Treat as read-only — mutations won't be
43
+ reflected back in individual transformers' state.
44
+ native_keys: Projection keys contributed by transformers with
45
+ `_native = True`.
46
+ """
47
+
48
+ def __init__(
49
+ self,
50
+ transformers: list[StreamTransformer] | None = None,
51
+ *,
52
+ is_async: bool = False,
53
+ factories: list[TransformerFactory] | None = None,
54
+ scope: tuple[str, ...] = (),
55
+ _assign_seq: bool = True,
56
+ ) -> None:
57
+ """Initialize the mux and register transformers in order.
58
+
59
+ Callers pass either `transformers` (pre-built instances) or
60
+ `factories` (callables producing fresh instances per mux). Each
61
+ transformer's `init()` is called, projections are merged into
62
+ `extensions`, `_native` keys are recorded in `native_keys`, and
63
+ any StreamChannel instances are bound and (if named) wired.
64
+
65
+ Transformers with `StreamTransformer.before_builtins = True` are
66
+ registered ahead of the rest, preserving relative order within
67
+ each lane. This lets content-mutating transformers (PII
68
+ redaction, content filters, etc.) run before built-ins like
69
+ `MessagesTransformer` that eagerly snapshot text fields into
70
+ their projections. See `StreamTransformer.before_builtins` for
71
+ the contract and foot-guns.
72
+
73
+ Args:
74
+ transformers: Already-built transformer instances. Registered
75
+ only on this mux — they are NOT cloned into child
76
+ mini-muxes built by `_make_child`. Use `factories` for
77
+ transformers that should propagate to nested scopes.
78
+ is_async: True for async dispatch (`apush` / `aclose` /
79
+ `afail`), False for the sync path.
80
+ factories: One-argument callables `(scope) -> StreamTransformer`.
81
+ Called once with this mux's `scope` here, and cloned
82
+ again per child scope by `_make_child` so each
83
+ sub-mux gets fresh instances.
84
+ scope: The namespace the mux operates within. The root mux
85
+ is `()`.
86
+ _assign_seq: Internal flag for child muxes. Root muxes assign
87
+ monotonic `seq` numbers when appending to their main event
88
+ log; child muxes share forwarded event objects and must not
89
+ mutate their envelopes.
90
+
91
+ Raises:
92
+ RuntimeError: If any transformer requires an async run but
93
+ the mux is in sync mode.
94
+ TypeError: If a transformer's `init()` doesn't return a dict.
95
+ ValueError: If transformers' projection keys collide.
96
+ """
97
+ self.is_async = is_async
98
+ self.scope: tuple[str, ...] = scope
99
+ self._assign_seq = _assign_seq
100
+ self._events: StreamChannel[ProtocolEvent] = StreamChannel()
101
+ self._events._bind(is_async=is_async)
102
+ self._events._bind_mux(self)
103
+ self._transformers: list[StreamTransformer] = []
104
+ self._channels: list[StreamChannel[Any]] = []
105
+ self._seq = 0
106
+ self._push_seq = 0
107
+
108
+ self.extensions: dict[str, Any] = {}
109
+ self.native_keys: set[str] = set()
110
+ self._projection_owners: dict[str, str] = {}
111
+ self._transformer_by_key: dict[str, StreamTransformer] = {}
112
+
113
+ # Stored only when constructed from factories — used by
114
+ # `_make_child` to clone the transformer pipeline at a deeper
115
+ # scope. Pre-built transformers can't be cloned, so a mux
116
+ # built with `transformers=` rejects child construction.
117
+ self._factories: list[TransformerFactory] | None = (
118
+ list(factories) if factories is not None else None
119
+ )
120
+ self._pump_fn: Callable[[], bool] | None = None
121
+ self._apump_fn: Callable[[], Awaitable[bool]] | None = None
122
+
123
+ # Factories run first (they propagate to child mini-muxes via
124
+ # `_make_child`), then any pre-built `transformers=` instances
125
+ # are registered as root-only — they aren't cloned for child
126
+ # scopes. Within each group, transformers with
127
+ # `before_builtins = True` are registered ahead of the rest so
128
+ # they observe (and may mutate) events before built-ins like
129
+ # `MessagesTransformer`. The order *within* each lane matches
130
+ # the supplied sequence.
131
+ pre: list[StreamTransformer] = []
132
+ rest: list[StreamTransformer] = []
133
+ if factories is not None:
134
+ for factory in factories:
135
+ transformer = factory(scope)
136
+ (
137
+ pre if getattr(transformer, "before_builtins", False) else rest
138
+ ).append(transformer)
139
+ for transformer in (*pre, *rest):
140
+ self._register(transformer)
141
+ pre.clear()
142
+ rest.clear()
143
+ for transformer in transformers or ():
144
+ (pre if getattr(transformer, "before_builtins", False) else rest).append(
145
+ transformer
146
+ )
147
+ for transformer in (*pre, *rest):
148
+ self._register(transformer)
149
+
150
+ def transformer_by_key(self, key: str) -> StreamTransformer | None:
151
+ """Return the transformer that contributed `key` to the projection."""
152
+ return self._transformer_by_key.get(key)
153
+
154
+ def _next_push_seq(self) -> int:
155
+ self._push_seq += 1
156
+ return self._push_seq
157
+
158
+ # ------------------------------------------------------------------
159
+ # Pump wiring + mini-mux nesting
160
+ # ------------------------------------------------------------------
161
+
162
+ def bind_pump(self, fn: Callable[[], bool]) -> None:
163
+ """Wire the sync pull callback onto every projection in this mux.
164
+
165
+ Records the pump on the mux so child mini-muxes built by
166
+ `_make_child` can inherit it. Propagates to:
167
+ - the main event log (`self._events`)
168
+ - every projection StreamChannel in `extensions`
169
+ - any registered transformer that exposes `_bind_pump` (e.g.
170
+ `MessagesTransformer` so `ChatModelStream` instances drive the
171
+ shared pump from their cursors)
172
+ """
173
+ self._pump_fn = fn
174
+ self._events._request_more = fn
175
+ for ch in self._channels:
176
+ ch._request_more = fn
177
+ for transformer in self._transformers:
178
+ bind = getattr(transformer, "_bind_pump", None)
179
+ if bind is not None:
180
+ bind(fn)
181
+
182
+ def bind_apump(self, fn: Callable[[], Awaitable[bool]]) -> None:
183
+ """Async counterpart to `bind_pump`."""
184
+ self._apump_fn = fn
185
+ self._events._arequest_more = fn
186
+ for ch in self._channels:
187
+ ch._arequest_more = fn
188
+ for transformer in self._transformers:
189
+ abind = getattr(transformer, "_bind_apump", None)
190
+ if abind is not None:
191
+ abind(fn)
192
+
193
+ def _make_child(self, scope: tuple[str, ...]) -> StreamMux:
194
+ """Build a mini-mux with the same factories scoped to `scope`.
195
+
196
+ Used by `SubgraphTransformer` to attach a fresh transformer
197
+ pipeline to each discovered subgraph handle. The child mux
198
+ inherits the current pump bindings (so cursors on its
199
+ projection logs drive the root pump), carries the same factory
200
+ list forward to any grandchild subgraphs, and does not assign
201
+ `seq` numbers so forwarded events can be shared without
202
+ mutating their envelope.
203
+
204
+ Raises:
205
+ RuntimeError: If the mux was not constructed with
206
+ `factories=`. Mini-muxes require factories so each scope
207
+ gets its own fresh transformer instances.
208
+ """
209
+ if self._factories is None:
210
+ raise RuntimeError(
211
+ "StreamMux._make_child requires the mux to be constructed "
212
+ "with `factories=`; pre-built transformers can't be "
213
+ "cloned to a new scope."
214
+ )
215
+ child = StreamMux(
216
+ factories=self._factories,
217
+ is_async=self.is_async,
218
+ scope=scope,
219
+ _assign_seq=False,
220
+ )
221
+ if self._pump_fn is not None:
222
+ child.bind_pump(self._pump_fn)
223
+ if self._apump_fn is not None:
224
+ child.bind_apump(self._apump_fn)
225
+ return child
226
+
227
+ def _register(self, transformer: StreamTransformer) -> None:
228
+ """Register a single transformer.
229
+
230
+ Calls `transformer.init()`, stores the transformer for event
231
+ processing, binds any StreamChannel instances in the projection,
232
+ and merges the projection into `extensions`.
233
+ """
234
+ if transformer_requires_async(transformer) and not self.is_async:
235
+ raise RuntimeError(
236
+ f"{type(transformer).__name__} requires an async run — "
237
+ "it overrides aprocess/afinalize/afail or sets "
238
+ "requires_async=True. Use astream(), not stream()."
239
+ )
240
+ projection = transformer.init()
241
+ if not isinstance(projection, dict):
242
+ raise TypeError(
243
+ f"StreamTransformer.init() must return a dict, "
244
+ f"got {type(projection).__name__}"
245
+ )
246
+ conflicts = set(projection) & set(self.extensions)
247
+ if conflicts:
248
+ attributions = ", ".join(
249
+ f"{key!r} (owned by {self._projection_owners[key]})"
250
+ for key in sorted(conflicts)
251
+ )
252
+ raise ValueError(
253
+ f"Transformer {type(transformer).__name__} returned "
254
+ f"projection keys that conflict with already-registered "
255
+ f"keys: {attributions}"
256
+ )
257
+ is_native = bool(getattr(transformer, "_native", False))
258
+ self._transformers.append(transformer)
259
+ self._bind_and_wire(projection, native=is_native)
260
+ self.extensions.update(projection)
261
+ owner_name = type(transformer).__name__
262
+ for key in projection:
263
+ self._projection_owners[key] = owner_name
264
+ self._transformer_by_key[key] = transformer
265
+ if is_native:
266
+ self.native_keys.update(projection.keys())
267
+ transformer._on_register(self)
268
+
269
+ def push(self, event: ProtocolEvent) -> None:
270
+ """Route an event through all transformers, then append to the main log.
271
+
272
+ Each transformer's `process()` is called in registration order.
273
+ If any transformer returns False, the event is suppressed from
274
+ the main log, but transformers that already saw it keep their
275
+ side effects.
276
+
277
+ On the root mux, `seq` is assigned right before an event enters
278
+ the main log, not before the transformer pipeline runs. This
279
+ ensures that events auto-forwarded from StreamChannels during
280
+ `process()` get earlier seq numbers than the original event,
281
+ preserving monotonic ordering in the root log. Child muxes do
282
+ not assign `seq`, so subgraph forwarding can share event objects
283
+ without mutating their envelopes.
284
+
285
+ Args:
286
+ event: The protocol event to dispatch.
287
+ """
288
+ keep = True
289
+ for transformer in self._transformers:
290
+ if not transformer.process(event):
291
+ keep = False
292
+ if keep:
293
+ if self._assign_seq:
294
+ self._seq += 1
295
+ event["seq"] = self._seq
296
+ self._events.push(event)
297
+
298
+ def close(self) -> None:
299
+ """Finalize all transformers, close all projections and the main log.
300
+
301
+ StreamChannels discovered in transformer projections are
302
+ auto-closed after `finalize()` runs — transformers don't need
303
+ to close them manually. If any transformer's `finalize()` raises,
304
+ the remaining transformers, projections, and the main log are
305
+ still closed; the first error is re-raised after cleanup
306
+ completes.
307
+
308
+ Raises:
309
+ BaseException: The first error raised by a transformer's
310
+ `finalize()`, re-raised after cleanup finishes.
311
+ """
312
+ first_error: BaseException | None = None
313
+ for transformer in self._transformers:
314
+ try:
315
+ transformer.finalize()
316
+ except BaseException as e:
317
+ if first_error is None:
318
+ first_error = e
319
+ for ch in self._channels:
320
+ if not ch._closed:
321
+ ch.close()
322
+ self._events.close()
323
+ if first_error is not None:
324
+ raise first_error
325
+
326
+ def fail(self, err: BaseException) -> None:
327
+ """Fail all transformers, projections, and the main log.
328
+
329
+ StreamChannels discovered in transformer projections are
330
+ auto-failed — transformers don't need to fail them manually.
331
+ If any transformer's `fail()` raises, the remaining
332
+ transformers, projections, and the main log are still failed.
333
+
334
+ Args:
335
+ err: The exception that ended the run.
336
+ """
337
+ for transformer in self._transformers:
338
+ try:
339
+ transformer.fail(err)
340
+ except BaseException:
341
+ pass
342
+ for ch in self._channels:
343
+ if not ch._closed:
344
+ ch.fail(err)
345
+ self._events.fail(err)
346
+
347
+ # ------------------------------------------------------------------
348
+ # Async dispatch
349
+ # ------------------------------------------------------------------
350
+
351
+ async def apush(self, event: ProtocolEvent) -> None:
352
+ """Dispatch an event on the async lane.
353
+
354
+ Awaits each transformer's `aprocess` in registration order
355
+ before appending to the main log. A slow `aprocess` serializes
356
+ the pipeline by design — that's the guarantee that lets a later
357
+ transformer (or a synchronous consumer) see the result of the
358
+ async work. For decoupled work, use `schedule()` from inside
359
+ `process` / `aprocess` instead.
360
+
361
+ The main log append is a non-blocking `push` — matching v1's
362
+ `put_nowait` shape. The root mux assigns `seq`; child muxes do
363
+ not, so forwarded subgraph events can be shared without copying.
364
+ Memory is bounded by caller pace via the caller-driven pump; see
365
+ `StreamChannel` for the full tradeoff story.
366
+
367
+ Args:
368
+ event: The protocol event to dispatch.
369
+ """
370
+ keep = True
371
+ for transformer in self._transformers:
372
+ if not await transformer.aprocess(event):
373
+ keep = False
374
+ if keep:
375
+ if self._assign_seq:
376
+ self._seq += 1
377
+ event["seq"] = self._seq
378
+ self._events.push(event)
379
+
380
+ async def aclose(self) -> None:
381
+ """Finalize on the async lane.
382
+
383
+ Awaits every task started via `StreamTransformer.schedule()`
384
+ across all transformers, then calls `afinalize()` on each,
385
+ then auto-closes channels and the main event log.
386
+
387
+ If any scheduled task raised under `on_error="raise"`, or any
388
+ transformer's `afinalize` raises, the exception propagates.
389
+ The caller (the pump) handles it by routing into `afail`.
390
+
391
+ Raises:
392
+ BaseException: The first scheduled-task or `afinalize`
393
+ error, re-raised after cleanup.
394
+ """
395
+ pending = self._collect_scheduled_tasks()
396
+ if pending:
397
+ results = await asyncio.gather(*pending, return_exceptions=True)
398
+ first_err = next(
399
+ (
400
+ r
401
+ for r in results
402
+ if isinstance(r, BaseException)
403
+ and not isinstance(r, asyncio.CancelledError)
404
+ ),
405
+ None,
406
+ )
407
+ if first_err is not None:
408
+ raise first_err
409
+
410
+ first_error: BaseException | None = None
411
+ for transformer in self._transformers:
412
+ try:
413
+ await transformer.afinalize()
414
+ except BaseException as e:
415
+ if first_error is None:
416
+ first_error = e
417
+ for ch in self._channels:
418
+ if not ch._closed:
419
+ ch.close()
420
+ self._events.close()
421
+ if first_error is not None:
422
+ raise first_error
423
+
424
+ async def afail(self, err: BaseException) -> None:
425
+ """Fail on the async lane.
426
+
427
+ Cancels every scheduled task across all transformers, awaits
428
+ them to completion, then runs each transformer's `afail` hook
429
+ and auto-fails channels and the main event log.
430
+
431
+ Args:
432
+ err: The exception that ended the run.
433
+ """
434
+ pending = self._collect_scheduled_tasks()
435
+ for task in pending:
436
+ task.cancel()
437
+ if pending:
438
+ await asyncio.gather(*pending, return_exceptions=True)
439
+
440
+ for transformer in self._transformers:
441
+ try:
442
+ await transformer.afail(err)
443
+ except BaseException:
444
+ pass
445
+ for ch in self._channels:
446
+ if not ch._closed:
447
+ ch.fail(err)
448
+ if not self._events._closed:
449
+ self._events.fail(err)
450
+
451
+ def _collect_scheduled_tasks(self) -> list[asyncio.Task[Any]]:
452
+ """Return a snapshot of in-flight tasks scheduled via transformers."""
453
+ return [
454
+ task
455
+ for transformer in self._transformers
456
+ for task in getattr(transformer, "_stream_scheduled_tasks", ())
457
+ if not task.done()
458
+ ]
459
+
460
+ # ------------------------------------------------------------------
461
+ # Binding and StreamChannel auto-wiring
462
+ # ------------------------------------------------------------------
463
+
464
+ def _bind_and_wire(
465
+ self, projection: dict[str, Any], *, native: bool = False
466
+ ) -> None:
467
+ """Bind and optionally wire StreamChannel instances in a projection.
468
+
469
+ All StreamChannels are bound and tracked. Channels with a name
470
+ are additionally wired for protocol auto-forwarding.
471
+
472
+ Args:
473
+ projection: The projection dict returned by a transformer's
474
+ `init()`.
475
+ native: True when the owning transformer is `_native`.
476
+ Named channels owned by a native transformer use the
477
+ channel name directly as the protocol method;
478
+ user-defined channels are prefixed with `custom:`.
479
+ """
480
+ for value in projection.values():
481
+ if isinstance(value, StreamChannel):
482
+ value._bind(is_async=self.is_async)
483
+ value._bind_mux(self)
484
+ self._channels.append(value)
485
+ if value.name is not None:
486
+ method = value.name if native else f"custom:{value.name}"
487
+
488
+ def _make_forward(method_name: str) -> Callable[[Any], None]:
489
+ def _forward(item: Any) -> None:
490
+ self._forward(method_name, item)
491
+
492
+ return _forward
493
+
494
+ value._wire(_make_forward(method))
495
+
496
+ def _forward(self, method: str, item: Any) -> None:
497
+ """Inject a ProtocolEvent for a StreamChannel push.
498
+
499
+ Forwarded events bypass the transformer pipeline to avoid
500
+ infinite recursion (a transformer that pushes to a channel
501
+ during `process()` would re-trigger itself). These events are
502
+ visible in this mux's main event log but are not passed through
503
+ transformers' `process()` methods. Only the root mux assigns
504
+ `seq` to forwarded channel events.
505
+
506
+ Args:
507
+ method: The full protocol method (already with or without
508
+ the `custom:` prefix; resolved by `_bind_and_wire`).
509
+ item: The payload pushed onto the channel.
510
+ """
511
+ event: ProtocolEvent = {
512
+ "type": "event",
513
+ "method": method,
514
+ "params": {
515
+ "namespace": [],
516
+ "timestamp": int(time.time() * 1000),
517
+ "data": item,
518
+ },
519
+ }
520
+ if self._assign_seq:
521
+ self._seq += 1
522
+ event["seq"] = self._seq
523
+ self._events.push(event)