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
coco/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """Main entrypoint into Coco."""
2
+
3
+ __version__ = "1.4.2"
@@ -0,0 +1,9 @@
1
+ """Entrypoint to building [Agents](https://docs.langchain.com/oss/python/langchain/agents) with Coco.""" # noqa: E501
2
+
3
+ from coco.agents.factory import create_agent
4
+ from coco.agents.middleware.types import AgentState
5
+
6
+ __all__ = [
7
+ "AgentState",
8
+ "create_agent",
9
+ ]
@@ -0,0 +1,307 @@
1
+ """Surface nested named agents as typed `run.subagents` handles.
2
+
3
+ Detects subagents via the `lc_agent_name` transition that coco_graph's base
4
+ `_TasksLifecycleBase` now computes. `create_agent(name=...)` binds
5
+ `lc_agent_name` into the run config; the base transformer records, per
6
+ namespace, the `lc_agent_name` seen on each task start (first-write-wins).
7
+
8
+ A subagent boundary is a nested run whose `lc_agent_name` is set *and* differs
9
+ from its parent namespace's `lc_agent_name`. Plain subgraphs inherit the
10
+ parent's name (so they compare equal and are excluded); unnamed agents have
11
+ `lc_agent_name == None` (also excluded). For genuine subagents the base also
12
+ recovers the originating tool call and exposes it as a `cause`
13
+ (`{"type": "toolCall", "tool_call_id": ...}`) via `self._pending_cause`, joined
14
+ from the parent task's pending tool calls.
15
+
16
+ This transformer gates on that boundary and surfaces a typed handle on
17
+ `run.subagents`, then forwards child-scope events into the handle's mux so the
18
+ nested run can be consumed independently.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import logging
24
+ from typing import TYPE_CHECKING, Any, ClassVar
25
+
26
+ from coco_graph.stream.run_stream import (
27
+ AsyncSubgraphRunStream,
28
+ SubgraphRunStream,
29
+ )
30
+ from coco_graph.stream.stream_channel import StreamChannel
31
+ from coco_graph.stream.transformers import (
32
+ SubgraphStatus,
33
+ _TasksLifecycleBase,
34
+ )
35
+
36
+ if TYPE_CHECKING:
37
+ from coco_core._protocol import LifecycleCause
38
+ from coco_graph.stream._mux import StreamMux
39
+ from coco_graph.stream._types import ProtocolEvent
40
+
41
+ logger = logging.getLogger(__name__)
42
+
43
+
44
+ class SubagentRunStream(SubgraphRunStream):
45
+ """Typed sync handle for a nested named-agent execution.
46
+
47
+ Surfaces on `run.subagents` when a nested run's `lc_agent_name` differs
48
+ from its parent's (i.e., a `create_agent(name=...)` dispatched from a tool).
49
+ """
50
+
51
+ def __init__(
52
+ self,
53
+ mux: StreamMux,
54
+ *,
55
+ path: tuple[str, ...],
56
+ graph_name: str | None = None,
57
+ trigger_call_id: str | None = None,
58
+ cause: LifecycleCause | None = None,
59
+ ) -> None:
60
+ super().__init__(
61
+ mux,
62
+ path=path,
63
+ graph_name=graph_name,
64
+ trigger_call_id=trigger_call_id,
65
+ )
66
+ self._cause = cause
67
+
68
+ @property
69
+ def name(self) -> str | None:
70
+ """Subagent name (the nested run's `lc_agent_name`)."""
71
+ return self.graph_name
72
+
73
+ @property
74
+ def cause(self) -> LifecycleCause | None:
75
+ """Causation edge — the tool call that triggered this subagent.
76
+
77
+ Returns the `LifecycleCause` recovered by the base transformer (a
78
+ `{"type": "toolCall", "tool_call_id": ...}` dict) when the originating
79
+ tool call could be joined, else `None`.
80
+ """
81
+ return self._cause
82
+
83
+
84
+ class AsyncSubagentRunStream(AsyncSubgraphRunStream):
85
+ """Typed async handle for a nested named-agent execution."""
86
+
87
+ def __init__(
88
+ self,
89
+ mux: StreamMux,
90
+ *,
91
+ path: tuple[str, ...],
92
+ graph_name: str | None = None,
93
+ trigger_call_id: str | None = None,
94
+ cause: LifecycleCause | None = None,
95
+ ) -> None:
96
+ super().__init__(
97
+ mux,
98
+ path=path,
99
+ graph_name=graph_name,
100
+ trigger_call_id=trigger_call_id,
101
+ )
102
+ self._cause = cause
103
+
104
+ @property
105
+ def name(self) -> str | None:
106
+ """Subagent name (the nested run's `lc_agent_name`)."""
107
+ return self.graph_name
108
+
109
+ @property
110
+ def cause(self) -> LifecycleCause | None:
111
+ """Causation edge — the tool call that triggered this subagent.
112
+
113
+ Returns the `LifecycleCause` recovered by the base transformer (a
114
+ `{"type": "toolCall", "tool_call_id": ...}` dict) when the originating
115
+ tool call could be joined, else `None`.
116
+ """
117
+ return self._cause
118
+
119
+
120
+ class SubagentTransformer(_TasksLifecycleBase):
121
+ """Promote nested named agents into typed handles on `run.subagents`.
122
+
123
+ The base `_TasksLifecycleBase` records each namespace's `lc_agent_name`
124
+ (set by `create_agent(name=...)`) and, on every task start, fires
125
+ `_on_started` with the resolved `graph_name` and a `cause` for genuine
126
+ subagent boundaries. This transformer gates on that boundary using the
127
+ inherited `_lc_by_ns` map: a nested run is a subagent when it carries an
128
+ `lc_agent_name`. Same-named nested agents (e.g. a subagent that invokes
129
+ itself) are surfaced; unnamed agents (`None`) are excluded. Trade-off: a
130
+ non-agent subgraph that inherited the parent's name will also surface.
131
+
132
+ On the first matching task start it builds a child mux and emits a typed
133
+ handle on `run.subagents`, then forwards subsequent child-scope events into
134
+ that handle so the nested run can be consumed independently.
135
+ """
136
+
137
+ _native: ClassVar[bool] = True
138
+ # Overrides `aprocess` but also runs unchanged on the sync lane via
139
+ # `process`, so it must not be forced into an async-only run.
140
+ supports_sync: ClassVar[bool] = True
141
+
142
+ def __init__(self, scope: tuple[str, ...] = ()) -> None:
143
+ super().__init__(scope)
144
+ self._log: StreamChannel[SubagentRunStream | AsyncSubagentRunStream] = StreamChannel()
145
+ self._handles: dict[tuple[str, ...], SubagentRunStream | AsyncSubagentRunStream] = {}
146
+ self._mux: StreamMux | None = None
147
+
148
+ def init(self) -> dict[str, Any]:
149
+ return {"subagents": self._log}
150
+
151
+ def _on_register(self, mux: StreamMux) -> None:
152
+ self._mux = mux
153
+
154
+ def _should_track(self, ns: tuple[str, ...]) -> bool:
155
+ depth = len(self.scope)
156
+ return len(ns) == depth + 1 and ns[:depth] == self.scope
157
+
158
+ def _on_started(
159
+ self,
160
+ ns: tuple[str, ...],
161
+ graph_name: str | None,
162
+ trigger_call_id: str | None,
163
+ ) -> None:
164
+ # coco_graph >=1.2.4 delivers the triggering `cause` via the base's
165
+ # `self._pending_cause` instance state rather than an `_on_started`
166
+ # keyword argument, so overrides predating `cause` keep working. Read it
167
+ # here to surface the originating tool call on the handle.
168
+ cause = self._pending_cause
169
+ child_lc = self._lc_by_ns.get(ns)
170
+ # Surface any nested run carrying an lc_agent_name (set by create_agent).
171
+ # A same-named nested agent — e.g. a subagent that invokes itself —
172
+ # re-asserts its own name and is surfaced. Unnamed runs (None) are
173
+ # excluded. Trade-off: a non-agent subgraph that inherited the parent's
174
+ # name also surfaces; null lc_agent_name when invoking such a graph to
175
+ # exclude it.
176
+ if child_lc is None:
177
+ return
178
+ if self._mux is None or ns in self._handles:
179
+ return
180
+ try:
181
+ child_mux = self._mux._make_child(ns) # noqa: SLF001
182
+ except RuntimeError:
183
+ logger.debug("SubagentTransformer: could not create child mux for %s", ns)
184
+ return
185
+
186
+ handle_cls = AsyncSubagentRunStream if child_mux.is_async else SubagentRunStream
187
+ handle = handle_cls(
188
+ mux=child_mux,
189
+ path=ns,
190
+ graph_name=graph_name,
191
+ trigger_call_id=trigger_call_id,
192
+ cause=cause,
193
+ )
194
+ self._handles[ns] = handle
195
+ self._log.push(handle)
196
+
197
+ def _on_terminal(
198
+ self,
199
+ ns: tuple[str, ...],
200
+ status: SubgraphStatus,
201
+ error: str | None,
202
+ ) -> None:
203
+ handle = self._handles.get(ns)
204
+ if handle is None or not self._mark_terminal(handle, status, error):
205
+ return
206
+ self._close_or_fail_handle(handle, status, error)
207
+
208
+ async def _aon_terminal(
209
+ self,
210
+ ns: tuple[str, ...],
211
+ status: SubgraphStatus,
212
+ error: str | None,
213
+ ) -> None:
214
+ handle = self._handles.get(ns)
215
+ if handle is None or not self._mark_terminal(handle, status, error):
216
+ return
217
+ await self._aclose_or_fail_handle(handle, status, error)
218
+
219
+ def _mark_terminal(
220
+ self,
221
+ handle: SubagentRunStream | AsyncSubagentRunStream,
222
+ status: SubgraphStatus,
223
+ error: str | None,
224
+ ) -> bool:
225
+ """Mark a handle terminal once. Returns True on the first transition."""
226
+ if handle._seen_terminal: # noqa: SLF001
227
+ return False
228
+ handle.status = status
229
+ if error is not None and handle.error is None:
230
+ handle.error = error
231
+ handle._seen_terminal = True # noqa: SLF001
232
+ return True
233
+
234
+ def _close_or_fail_handle(
235
+ self,
236
+ handle: SubagentRunStream | AsyncSubagentRunStream,
237
+ status: SubgraphStatus,
238
+ error: str | None,
239
+ ) -> None:
240
+ if handle._mux is None or handle._mux._events._closed: # noqa: SLF001
241
+ return
242
+ if status == "failed":
243
+ handle._mux.fail(RuntimeError(error or "Subagent failed")) # noqa: SLF001
244
+ else:
245
+ handle._mux.close() # noqa: SLF001
246
+
247
+ async def _aclose_or_fail_handle(
248
+ self,
249
+ handle: SubagentRunStream | AsyncSubagentRunStream,
250
+ status: SubgraphStatus,
251
+ error: str | None,
252
+ ) -> None:
253
+ if handle._mux is None or handle._mux._events._closed: # noqa: SLF001
254
+ return
255
+ if status == "failed":
256
+ await handle._mux.afail(RuntimeError(error or "Subagent failed")) # noqa: SLF001
257
+ else:
258
+ await handle._mux.aclose() # noqa: SLF001
259
+
260
+ def _handle_for_event(
261
+ self, event: ProtocolEvent
262
+ ) -> SubagentRunStream | AsyncSubagentRunStream | None:
263
+ ns = tuple(event["params"]["namespace"])
264
+ depth = len(self.scope)
265
+ if len(ns) < depth + 1:
266
+ return None
267
+ handle = self._handles.get(ns[: depth + 1])
268
+ if handle is None or handle._mux is None or handle._mux._events._closed: # noqa: SLF001
269
+ return None
270
+ return handle
271
+
272
+ def process(self, event: ProtocolEvent) -> bool:
273
+ # Run tasks bookkeeping first so a `started` handle exists by the
274
+ # time we forward the event into the child mini-mux.
275
+ keep = super().process(event)
276
+ handle = self._handle_for_event(event)
277
+ if handle is not None:
278
+ handle._observe_event(event) # noqa: SLF001
279
+ handle._mux.push(event) # noqa: SLF001
280
+ return keep
281
+
282
+ async def aprocess(self, event: ProtocolEvent) -> bool:
283
+ # Async counterpart: repeat the tasks bookkeeping here and forward into
284
+ # the child mini-mux through its async lane so the subagent's own
285
+ # transformers are driven on the correct (async) lane instead of being
286
+ # double-driven via the sync `process`/`push` path.
287
+ if event["method"] == "tasks":
288
+ ns = tuple(event["params"]["namespace"])
289
+ data = event["params"]["data"]
290
+ if "result" in data:
291
+ for child_ns, status, error in self._pop_terminal_transitions(ns, data):
292
+ await self._aon_terminal(child_ns, status, error)
293
+ else:
294
+ # Mirror the sync bookkeeping so the async lane observes parent
295
+ # identity / pending tool calls before discriminating a
296
+ # subagent boundary in `_handle_task_start` -> `_on_started`.
297
+ self._record_identity(ns, data)
298
+ self._record_pending_tool_calls(data)
299
+ self._handle_task_start(ns, data)
300
+ keep = False
301
+ else:
302
+ keep = True
303
+ handle = self._handle_for_event(event)
304
+ if handle is not None:
305
+ handle._observe_event(event) # noqa: SLF001
306
+ await handle._mux.apush(event) # noqa: SLF001
307
+ return keep