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,735 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ from collections.abc import AsyncIterator, Awaitable, Callable, Iterator, Mapping
5
+ from types import MappingProxyType, TracebackType
6
+ from typing import TYPE_CHECKING, Any, NoReturn
7
+
8
+ from coco_core._api import beta
9
+
10
+ from coco_graph.stream._convert import convert_to_protocol_event
11
+ from coco_graph.stream._mux import StreamMux
12
+ from coco_graph.stream._types import ProtocolEvent
13
+
14
+ if TYPE_CHECKING:
15
+ from coco_core.language_models.chat_model_stream import (
16
+ AsyncChatModelStream,
17
+ ChatModelStream,
18
+ )
19
+
20
+ from coco_graph.stream.stream_channel import StreamChannel
21
+ from coco_graph.stream.transformers import LifecyclePayload, SubgraphStatus
22
+
23
+
24
+ def _drive_until_done(pump: Callable[[], bool]) -> None:
25
+ """Call the sync pump until it returns False."""
26
+ while pump():
27
+ pass
28
+
29
+
30
+ async def _adrive_until_done(pump: Callable[[], Awaitable[bool]]) -> None:
31
+ """Call the async pump until it returns False."""
32
+ while await pump():
33
+ pass
34
+
35
+
36
+ def _raise_missing_projection(run: object, name: str) -> NoReturn:
37
+ """Raise after normal attribute lookup fails for a projection.
38
+
39
+ Registered native projections are installed directly on the run instance
40
+ during `__init__`, so `__getattr__` is never called for them. At this point
41
+ the requested name is necessarily missing; the mux is inspected only to
42
+ include the valid registered projection names in the error message.
43
+
44
+ Read `_mux` directly from `__dict__` because it may not exist yet on a
45
+ partially initialized instance. Accessing `run._mux` in that case would
46
+ invoke `__getattr__` again and recurse indefinitely.
47
+ """
48
+ mux = run.__dict__.get("_mux")
49
+ registered = sorted(mux.native_keys) if mux is not None else []
50
+ raise AttributeError(
51
+ f"{type(run).__name__!r} object has no attribute {name!r} "
52
+ f"(registered projections: {', '.join(registered) or 'none'})"
53
+ )
54
+
55
+
56
+ @beta(message="The v3 streaming protocol on Pregel is experimental.")
57
+ class GraphRunStream:
58
+ """Sync run stream with caller-driven pumping.
59
+
60
+ The caller's iteration on any projection (`values`, `messages`,
61
+ raw events, or `output`) drives the graph forward. No background
62
+ thread is used — the caller's `for` loop is the pump.
63
+
64
+ Projections are single-consumer — iterating `run.values` twice
65
+ raises. Use `projection.tee(n)` if you genuinely need fan-out.
66
+
67
+ All transformer projections live in `extensions`. Native transformer
68
+ projections (those with `_native = True`) are also set as direct
69
+ attributes on this instance (e.g. `run.values`, `run.messages`).
70
+
71
+ !!! warning
72
+
73
+ Returned by `Pregel.stream_events(version="v3")`, which is
74
+ experimental and may change.
75
+ """
76
+
77
+ # Native projections, attached dynamically by the `setattr` loop in
78
+ # `__init__` and declared here so type checkers see them.
79
+ #
80
+ # Always registered by `stream_events(version="v3")`:
81
+ values: StreamChannel[dict[str, Any]]
82
+ messages: StreamChannel[ChatModelStream]
83
+ lifecycle: StreamChannel[LifecyclePayload]
84
+ subgraphs: StreamChannel[SubgraphRunStream]
85
+ # Registered on demand via `compile(transformers=...)` or
86
+ # `stream_events(transformers=...)`; reading one whose transformer was not
87
+ # registered raises AttributeError. Projections contributed by transformers
88
+ # outside this package are covered by `__getattr__` instead.
89
+ updates: StreamChannel[dict[str, Any]]
90
+ custom: StreamChannel[Any]
91
+ checkpoints: StreamChannel[dict[str, Any]]
92
+ debug: StreamChannel[dict[str, Any]]
93
+ tasks: StreamChannel[dict[str, Any]]
94
+
95
+ def __getattr__(self, name: str) -> StreamChannel[Any]:
96
+ """Type the projections of transformers declared outside this package.
97
+
98
+ Projection names come from a registry, so no annotation here can name
99
+ them all. The cost is that a misspelling type-checks too, and fails at
100
+ runtime instead.
101
+ """
102
+ _raise_missing_projection(self, name)
103
+
104
+ def __init__(
105
+ self,
106
+ graph_iter: Iterator[Any] | None,
107
+ mux: StreamMux,
108
+ *,
109
+ wire_pump: bool = True,
110
+ ) -> None:
111
+ """Initialize the run stream.
112
+
113
+ Args:
114
+ graph_iter: Pull-based iterator over the graph's stream,
115
+ or `None` for nested run streams whose pump is driven
116
+ by an outer run (e.g. `SubgraphRunStream`).
117
+ mux: The StreamMux owning projections and the main log.
118
+ wire_pump: When True (default), bind `_pump_next` as the
119
+ mux's pump callable. Subclasses that inherit a parent
120
+ pump via `StreamMux._make_child` should pass False to
121
+ preserve the parent binding.
122
+ """
123
+ self._graph_iter = graph_iter
124
+ self._mux = mux
125
+ self.extensions: Mapping[str, Any] = MappingProxyType(mux.extensions)
126
+ self._exhausted = False
127
+ self._latest: dict[str, Any] | None = None
128
+ self._interrupted = False
129
+ self._interrupts: list[Any] = []
130
+ self._scope_list: list[str] = list(mux.scope)
131
+ for key in mux.native_keys:
132
+ setattr(self, key, mux.extensions[key])
133
+ if wire_pump:
134
+ self._wire_request_more(mux)
135
+
136
+ def _wire_request_more(self, mux: StreamMux) -> None:
137
+ """Wire the sync pull callback through the mux.
138
+
139
+ Routing through `mux.bind_pump` (rather than walking
140
+ projections directly here) lets child mini-muxes built by
141
+ `mux._make_child(...)` inherit the same pump callable, so
142
+ cursors on a subgraph handle's projections drive the root
143
+ pump just like cursors on `run.values` do.
144
+ """
145
+ mux.bind_pump(self._pump_next)
146
+
147
+ def _observe_event(self, event: ProtocolEvent) -> None:
148
+ """Track values-event state for output/interrupted/interrupts."""
149
+ if event["method"] != "values":
150
+ return
151
+ params = event["params"]
152
+ if params["namespace"] != self._scope_list:
153
+ return
154
+ self._latest = params["data"]
155
+ interrupts = params.get("interrupts", ())
156
+ if interrupts:
157
+ self._interrupted = True
158
+ self._interrupts.extend(interrupts)
159
+
160
+ def _pump_next(self) -> bool:
161
+ """Pull one event from the graph and push it through the mux.
162
+
163
+ Returns:
164
+ True if an event was pulled, False if the graph is exhausted
165
+ or has raised. Always False when constructed with
166
+ `graph_iter=None` (the run is driven by an outer pump).
167
+ """
168
+ if self._exhausted or self._graph_iter is None:
169
+ return False
170
+ try:
171
+ part = next(self._graph_iter)
172
+ event = convert_to_protocol_event(part)
173
+ self._observe_event(event)
174
+ self._mux.push(event)
175
+ return True
176
+ except StopIteration:
177
+ self._mux.close()
178
+ self._exhausted = True
179
+ return False
180
+ except Exception as e:
181
+ self._mux.fail(e)
182
+ self._exhausted = True
183
+ return False
184
+
185
+ def abort(self) -> None:
186
+ """Stop the run early.
187
+
188
+ Closes the underlying graph iterator (propagating `GeneratorExit`
189
+ so in-flight nodes and subgraphs are cancelled), closes the mux,
190
+ and marks the stream exhausted. Idempotent.
191
+ """
192
+ if self._exhausted:
193
+ return
194
+ self._exhausted = True
195
+ graph_iter = self._graph_iter
196
+ self._graph_iter = None
197
+ if (
198
+ graph_iter is not None
199
+ and (close := getattr(graph_iter, "close", None)) is not None
200
+ ):
201
+ try:
202
+ close()
203
+ except Exception:
204
+ pass
205
+ try:
206
+ self._mux.close()
207
+ except Exception:
208
+ pass
209
+
210
+ def __enter__(self) -> GraphRunStream:
211
+ return self
212
+
213
+ def __exit__(
214
+ self,
215
+ exc_type: type[BaseException] | None,
216
+ exc: BaseException | None,
217
+ tb: TracebackType | None,
218
+ ) -> None:
219
+ self.abort()
220
+
221
+ @property
222
+ def output(self) -> dict[str, Any] | None:
223
+ """Drive the run to completion and return the final state."""
224
+ _drive_until_done(self._pump_next)
225
+ if (err := self._mux._events._error) is not None:
226
+ raise err
227
+ return self._latest
228
+
229
+ @property
230
+ def interrupted(self) -> bool:
231
+ """Drive the run to completion, then return whether it was
232
+ interrupted.
233
+
234
+ Raises:
235
+ BaseException: If the run ended with an error.
236
+ """
237
+ _drive_until_done(self._pump_next)
238
+ if (err := self._mux._events._error) is not None:
239
+ raise err
240
+ return self._interrupted
241
+
242
+ @property
243
+ def interrupts(self) -> list[Any]:
244
+ """Drive the run to completion, then return interrupt payloads.
245
+
246
+ Raises:
247
+ BaseException: If the run ended with an error.
248
+ """
249
+ _drive_until_done(self._pump_next)
250
+ if (err := self._mux._events._error) is not None:
251
+ raise err
252
+ return self._interrupts
253
+
254
+ def __iter__(self) -> Iterator[ProtocolEvent]:
255
+ """Subscribe to the main event log and iterate protocol events."""
256
+ return iter(self._mux._events)
257
+
258
+ def interleave(self, *names: str) -> Iterator[tuple[str, Any]]:
259
+ """Iterate multiple projections in arrival order, yielding ``(name, item)``.
260
+
261
+ Items are ordered by a monotonic push stamp assigned when each
262
+ transformer pushes into its `StreamChannel`. This gives strict
263
+ arrival ordering across projections, unlike round-robin.
264
+
265
+ Args:
266
+ *names: Projection keys to interleave. Must match keys in
267
+ ``extensions``.
268
+
269
+ Yields:
270
+ ``(name, item)`` tuples in arrival order across the named
271
+ projections.
272
+
273
+ Each named channel is locked for the duration of iteration and
274
+ released when the generator completes, is closed, or raises.
275
+ Channels cannot be subscribed concurrently — use `.tee(n)` if
276
+ you need fan-out.
277
+
278
+ Raises:
279
+ KeyError: If a name doesn't match a registered projection.
280
+
281
+ Example:
282
+ ```python
283
+ for name, item in run.interleave("messages", "values"):
284
+ if name == "messages":
285
+ print("msg:", item)
286
+ else:
287
+ print("val:", item)
288
+ ```
289
+ """
290
+ from coco_graph.stream.stream_channel import StreamChannel
291
+
292
+ channels: dict[str, StreamChannel[Any]] = {}
293
+ try:
294
+ for name in names:
295
+ ch = self.extensions[name]
296
+ if not isinstance(ch, StreamChannel):
297
+ raise TypeError(
298
+ f"interleave() requires StreamChannel projections, "
299
+ f"got {type(ch).__name__} for {name!r}"
300
+ )
301
+ if ch._is_async is None:
302
+ raise TypeError(
303
+ f"StreamChannel {name!r} has not been bound yet. "
304
+ "Register the transformer with a StreamMux first."
305
+ )
306
+ if ch._is_async:
307
+ raise TypeError(
308
+ f"StreamChannel {name!r} is bound to async mode — "
309
+ "sync interleave() cannot consume async channels."
310
+ )
311
+ if ch._subscribed:
312
+ raise RuntimeError(
313
+ f"StreamChannel {name!r} already has a subscriber; "
314
+ "use .tee(n) for fan-out."
315
+ )
316
+ ch._subscribed = True
317
+ channels[name] = ch
318
+
319
+ done: set[str] = set()
320
+
321
+ while len(done) < len(channels):
322
+ best: tuple[int, str] | None = None
323
+ for name, ch in channels.items():
324
+ if name in done:
325
+ continue
326
+ if ch._closed and not ch._items:
327
+ if ch._error is not None:
328
+ raise ch._error
329
+ done.add(name)
330
+ continue
331
+ if ch._items:
332
+ stamp = ch._items[0][0]
333
+ if best is None or stamp < best[0]:
334
+ best = (stamp, name)
335
+
336
+ if best is not None:
337
+ _stamp, item = channels[best[1]]._items.popleft()
338
+ yield (best[1], item)
339
+ else:
340
+ pump = self._mux._pump_fn
341
+ if pump is None or not pump():
342
+ before = len(done)
343
+ for name, ch in channels.items():
344
+ if name not in done and not ch._items:
345
+ if ch._closed:
346
+ if ch._error is not None:
347
+ raise ch._error
348
+ done.add(name)
349
+ if len(done) == before:
350
+ break
351
+ finally:
352
+ for ch in channels.values():
353
+ ch._subscribed = False
354
+
355
+
356
+ @beta(message="The v3 streaming protocol on Pregel is experimental.")
357
+ class AsyncGraphRunStream:
358
+ """Async run stream with caller-driven pumping.
359
+
360
+ Async iteration on any projection drives the graph forward — there
361
+ is no background task. Concurrent consumers share a single-flight
362
+ pump via an `asyncio.Lock`, so each awaiting cursor contributes one
363
+ event per acquisition. Backpressure comes from the logs: when a
364
+ subscribed log's buffer reaches `maxlen`, `apush` awaits the
365
+ subscriber to drain, which holds back the pump and paces the graph.
366
+
367
+ Projections are single-consumer — a second `aiter(run.values)`
368
+ raises. Use `projection.tee(n)` for fan-out.
369
+
370
+ Use as an async context manager to guarantee clean shutdown on
371
+ early exit:
372
+
373
+ ```python
374
+ async with await handler.astream(input) as run:
375
+ async for msg in run.messages:
376
+ ...
377
+ ```
378
+
379
+ !!! warning
380
+
381
+ Awaited from `Pregel.astream_events(version="v3")`, which is
382
+ experimental and may change.
383
+ """
384
+
385
+ # Native projections, attached dynamically by the `setattr` loop in
386
+ # `__init__` and declared here so type checkers see them.
387
+ #
388
+ # Always registered by `astream_events(version="v3")`:
389
+ values: StreamChannel[dict[str, Any]]
390
+ messages: StreamChannel[AsyncChatModelStream]
391
+ lifecycle: StreamChannel[LifecyclePayload]
392
+ subgraphs: StreamChannel[AsyncSubgraphRunStream]
393
+ # Registered on demand; see `GraphRunStream`.
394
+ updates: StreamChannel[dict[str, Any]]
395
+ custom: StreamChannel[Any]
396
+ checkpoints: StreamChannel[dict[str, Any]]
397
+ debug: StreamChannel[dict[str, Any]]
398
+ tasks: StreamChannel[dict[str, Any]]
399
+
400
+ def __getattr__(self, name: str) -> StreamChannel[Any]:
401
+ """Type projections declared elsewhere. See `GraphRunStream`."""
402
+ _raise_missing_projection(self, name)
403
+
404
+ def __init__(
405
+ self,
406
+ graph_aiter: AsyncIterator[Any] | None,
407
+ mux: StreamMux,
408
+ *,
409
+ wire_pump: bool = True,
410
+ ) -> None:
411
+ """Initialize the async run stream.
412
+
413
+ Args:
414
+ graph_aiter: Async iterator over the graph's stream, or
415
+ `None` for nested run streams whose pump is driven by
416
+ an outer run (e.g. `AsyncSubgraphRunStream`).
417
+ mux: The StreamMux owning projections and the main log.
418
+ wire_pump: When True (default), bind `_apump_next` as the
419
+ mux's async pump callable. Subclasses that inherit a
420
+ parent pump via `StreamMux._make_child` should pass
421
+ False to preserve the parent binding.
422
+ """
423
+ self._graph_aiter = graph_aiter
424
+ self._mux = mux
425
+ self.extensions: Mapping[str, Any] = MappingProxyType(mux.extensions)
426
+ self._exhausted = False
427
+ self._latest: dict[str, Any] | None = None
428
+ self._interrupted = False
429
+ self._interrupts: list[Any] = []
430
+ self._scope_list: list[str] = list(mux.scope)
431
+ self._pump_cond = asyncio.Condition()
432
+ self._pumping = False
433
+ self._anext_task: asyncio.Future[Any] | None = None
434
+ self._aborting = False
435
+ for key in mux.native_keys:
436
+ setattr(self, key, mux.extensions[key])
437
+ if wire_pump:
438
+ self._wire_arequest_more(mux)
439
+
440
+ def _observe_event(self, event: ProtocolEvent) -> None:
441
+ """Track values-event state for output/interrupted/interrupts."""
442
+ if event["method"] != "values":
443
+ return
444
+ params = event["params"]
445
+ if params["namespace"] != self._scope_list:
446
+ return
447
+ self._latest = params["data"]
448
+ interrupts = params.get("interrupts", ())
449
+ if interrupts:
450
+ self._interrupted = True
451
+ self._interrupts.extend(interrupts)
452
+
453
+ def _wire_arequest_more(self, mux: StreamMux) -> None:
454
+ """Wire the async pull callback through the mux.
455
+
456
+ Mirrors `_wire_request_more`: routing through
457
+ `mux.bind_apump` lets child mini-muxes inherit the pump
458
+ callable so cursors on subgraph handles drive the root
459
+ pump.
460
+ """
461
+ mux.bind_apump(self._apump_next)
462
+
463
+ async def _apump_next(self) -> bool:
464
+ """Drive one pump step, or wait for the active pumper to drive one.
465
+
466
+ "Take-a-number" semantics: at most one task at a time calls
467
+ `graph_aiter.__anext__()` (asyncio iterators can't be advanced
468
+ concurrently). Other callers wait on a Condition that the
469
+ active pumper notifies after each step. This lets a "passive"
470
+ consumer — one whose projection's buffer is being filled by the
471
+ active pumper's push — wake up as soon as its data lands,
472
+ instead of queueing on the pump and only observing its data one
473
+ graph event late.
474
+
475
+ `except Exception` is intentional — `CancelledError` and other
476
+ `BaseException` subclasses propagate, matching asyncio's
477
+ cancellation contract.
478
+
479
+ Returns:
480
+ True if a pump step completed (by this task or another),
481
+ False if the graph is exhausted.
482
+ """
483
+ async with self._pump_cond:
484
+ if self._exhausted or self._graph_aiter is None:
485
+ return False
486
+ if self._pumping:
487
+ # Another task is pumping; wait for its progress signal.
488
+ await self._pump_cond.wait()
489
+ return not self._exhausted
490
+ self._pumping = True
491
+
492
+ try:
493
+ try:
494
+ # Run the pull as a child task so `abort()` can cancel it
495
+ # mid-flight. Cancelling propagates `CancelledError` into the
496
+ # graph generator frame -> Pregel loop -> nested subgraph
497
+ # nodes, which a bare `aclose()` cannot do while the generator
498
+ # is running ("asynchronous generator is already running").
499
+ self._anext_task = asyncio.ensure_future(self._graph_aiter.__anext__())
500
+ try:
501
+ part = await self._anext_task
502
+ except asyncio.CancelledError:
503
+ if self._aborting:
504
+ # Abort-initiated cancel: stop gracefully.
505
+ self._exhausted = True
506
+ return False
507
+ # Genuine external cancel of this task: also stop the
508
+ # in-flight pull, then propagate.
509
+ self._anext_task.cancel()
510
+ raise
511
+ finally:
512
+ self._anext_task = None
513
+ event = convert_to_protocol_event(part)
514
+ self._observe_event(event)
515
+ await self._mux.apush(event)
516
+ return True
517
+ except StopAsyncIteration:
518
+ self._exhausted = True
519
+ await self._mux.aclose()
520
+ return False
521
+ except Exception as e:
522
+ self._exhausted = True
523
+ await self._mux.afail(e)
524
+ return False
525
+ finally:
526
+ async with self._pump_cond:
527
+ self._pumping = False
528
+ self._pump_cond.notify_all()
529
+
530
+ async def abort(self) -> None:
531
+ """Stop the run early.
532
+
533
+ Marks the stream exhausted and wakes any pump-waiters. Cancels an
534
+ in-flight pull if one is running, then closes the underlying graph
535
+ iterator, so running nodes and nested subgraphs are cancelled
536
+ whether or not a pump is mid-pull. Closes the mux; any `apush`
537
+ blocked on backpressure wakes and returns without appending.
538
+ Idempotent.
539
+ """
540
+ async with self._pump_cond:
541
+ if self._exhausted:
542
+ return
543
+ self._exhausted = True
544
+ self._aborting = True
545
+ graph_aiter = self._graph_aiter
546
+ self._graph_aiter = None
547
+ anext_task = self._anext_task
548
+ self._pump_cond.notify_all()
549
+ # If a pump is mid-pull, cancel it so the cancellation propagates
550
+ # into running nodes and nested subgraphs. Once it settles the
551
+ # generator is no longer running, so the `aclose()` below is a safe
552
+ # final cleanup (and handles the no-in-flight-pull case directly).
553
+ if anext_task is not None and not anext_task.done():
554
+ anext_task.cancel()
555
+ try:
556
+ await anext_task
557
+ except (asyncio.CancelledError, Exception):
558
+ pass
559
+ if (
560
+ graph_aiter is not None
561
+ and (aclose := getattr(graph_aiter, "aclose", None)) is not None
562
+ ):
563
+ try:
564
+ await aclose()
565
+ except Exception:
566
+ pass
567
+ try:
568
+ await self._mux.aclose()
569
+ except Exception:
570
+ pass
571
+
572
+ async def __aenter__(self) -> AsyncGraphRunStream:
573
+ return self
574
+
575
+ async def __aexit__(
576
+ self,
577
+ exc_type: type[BaseException] | None,
578
+ exc: BaseException | None,
579
+ tb: TracebackType | None,
580
+ ) -> None:
581
+ await self.abort()
582
+
583
+ async def output(self) -> dict[str, Any] | None:
584
+ """Drive the run to completion and return the final state.
585
+
586
+ Methods (not properties) on the async lane so `run.output`
587
+ without `await` raises at type-check time instead of silently
588
+ yielding a coroutine object.
589
+
590
+ Example:
591
+ ```python
592
+ output = await run.output()
593
+ ```
594
+
595
+ Raises:
596
+ BaseException: If the run ended with an error.
597
+ """
598
+ await _adrive_until_done(self._apump_next)
599
+ if (err := self._mux._events._error) is not None:
600
+ raise err
601
+ return self._latest
602
+
603
+ async def interrupted(self) -> bool:
604
+ """Drive the run to completion and return whether it was
605
+ interrupted.
606
+
607
+ Raises:
608
+ BaseException: If the run ended with an error.
609
+ """
610
+ await _adrive_until_done(self._apump_next)
611
+ if (err := self._mux._events._error) is not None:
612
+ raise err
613
+ return self._interrupted
614
+
615
+ async def interrupts(self) -> list[Any]:
616
+ """Drive the run to completion and return interrupt payloads.
617
+
618
+ Raises:
619
+ BaseException: If the run ended with an error.
620
+ """
621
+ await _adrive_until_done(self._apump_next)
622
+ if (err := self._mux._events._error) is not None:
623
+ raise err
624
+ return self._interrupts
625
+
626
+ def __aiter__(self) -> AsyncIterator[ProtocolEvent]:
627
+ """Subscribe to the main event log and iterate protocol events."""
628
+ return self._mux._events.__aiter__()
629
+
630
+
631
+ class _SubgraphRunStreamMixin:
632
+ """Subgraph metadata + parent-pump delegation shared by both lanes.
633
+
634
+ Inherits from `GraphRunStream` (or `AsyncGraphRunStream`) with
635
+ `graph_iter=None` + `wire_pump=False` — the mini-mux is driven
636
+ by the parent's pump (inherited via `StreamMux._make_child`), and
637
+ the handle never pulls upstream itself. Pump-driving methods
638
+ delegate to the parent pump so `handle.output` and friends drive
639
+ the root run.
640
+
641
+ Subclasses set the parent pump function captured at construction
642
+ (`_parent_pump_fn` / `_parent_apump_fn`) and override
643
+ `_pump_next` / `_apump_next` to delegate to it.
644
+
645
+ Status is updated in place by `SubgraphTransformer`. Iterate
646
+ `run.subgraphs` to receive handles as subgraphs spawn, then
647
+ drill into projections inside the loop body **before** the next
648
+ pump cycle — same lazy-subscribe constraint as root projections.
649
+ """
650
+
651
+ path: tuple[str, ...]
652
+ graph_name: str | None
653
+ trigger_call_id: str | None
654
+ status: SubgraphStatus
655
+ error: str | None
656
+ _seen_terminal: bool
657
+
658
+
659
+ class SubgraphRunStream(GraphRunStream, _SubgraphRunStreamMixin):
660
+ """Sync handle for a discovered subgraph (extends `GraphRunStream`)."""
661
+
662
+ def __init__(
663
+ self,
664
+ mux: StreamMux,
665
+ *,
666
+ path: tuple[str, ...],
667
+ graph_name: str | None = None,
668
+ trigger_call_id: str | None = None,
669
+ ) -> None:
670
+ # Capture the parent-inherited pump before super().__init__
671
+ # touches anything; we delegate to it from `_pump_next`.
672
+ self._parent_pump_fn: Callable[[], bool] | None = mux._pump_fn
673
+ super().__init__(
674
+ graph_iter=None,
675
+ mux=mux,
676
+ wire_pump=False,
677
+ )
678
+ self.path = path
679
+ self.graph_name = graph_name
680
+ self.trigger_call_id = trigger_call_id
681
+ self.status = "started"
682
+ self.error = None
683
+ self._seen_terminal = False
684
+
685
+ def _pump_next(self) -> bool:
686
+ """Delegate to the parent's pump.
687
+
688
+ Cursors on this handle's projections call here when their
689
+ buffers empty. Driving the parent fans events into our
690
+ mini-mux, transparently advancing the whole run.
691
+ """
692
+ if (
693
+ self._exhausted
694
+ or self._seen_terminal
695
+ or self._mux._events._closed
696
+ or self._parent_pump_fn is None
697
+ ):
698
+ return False
699
+ return self._parent_pump_fn()
700
+
701
+
702
+ class AsyncSubgraphRunStream(AsyncGraphRunStream, _SubgraphRunStreamMixin):
703
+ """Async handle for a discovered subgraph (extends `AsyncGraphRunStream`)."""
704
+
705
+ def __init__(
706
+ self,
707
+ mux: StreamMux,
708
+ *,
709
+ path: tuple[str, ...],
710
+ graph_name: str | None = None,
711
+ trigger_call_id: str | None = None,
712
+ ) -> None:
713
+ self._parent_apump_fn: Callable[[], Awaitable[bool]] | None = mux._apump_fn
714
+ super().__init__(
715
+ graph_aiter=None,
716
+ mux=mux,
717
+ wire_pump=False,
718
+ )
719
+ self.path = path
720
+ self.graph_name = graph_name
721
+ self.trigger_call_id = trigger_call_id
722
+ self.status = "started"
723
+ self.error = None
724
+ self._seen_terminal = False
725
+
726
+ async def _apump_next(self) -> bool:
727
+ """Delegate to the parent's async pump."""
728
+ if (
729
+ self._exhausted
730
+ or self._seen_terminal
731
+ or self._mux._events._closed
732
+ or self._parent_apump_fn is None
733
+ ):
734
+ return False
735
+ return await self._parent_apump_fn()