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_graph/graph/ui.py ADDED
@@ -0,0 +1,227 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Literal, cast
4
+ from uuid import uuid4
5
+
6
+ from coco_core.messages import AnyMessage
7
+ from typing_extensions import TypedDict
8
+
9
+ from coco_graph.config import get_config, get_stream_writer
10
+ from coco_graph.constants import CONF
11
+
12
+ __all__ = (
13
+ "UIMessage",
14
+ "RemoveUIMessage",
15
+ "AnyUIMessage",
16
+ "push_ui_message",
17
+ "delete_ui_message",
18
+ "ui_message_reducer",
19
+ )
20
+
21
+
22
+ class UIMessage(TypedDict):
23
+ """A message type for UI updates in CocoGraph.
24
+
25
+ This TypedDict represents a UI message that can be sent to update the UI state.
26
+ It contains information about the UI component to render and its properties.
27
+
28
+ Attributes:
29
+ type: Literal type indicating this is a UI message.
30
+ id: Unique identifier for the UI message.
31
+ name: Name of the UI component to render.
32
+ props: Properties to pass to the UI component.
33
+ metadata: Additional metadata about the UI message.
34
+ """
35
+
36
+ type: Literal["ui"]
37
+ id: str
38
+ name: str
39
+ props: dict[str, Any]
40
+ metadata: dict[str, Any]
41
+
42
+
43
+ class RemoveUIMessage(TypedDict):
44
+ """A message type for removing UI components in CocoGraph.
45
+
46
+ This TypedDict represents a message that can be sent to remove a UI component
47
+ from the current state.
48
+
49
+ Attributes:
50
+ type: Literal type indicating this is a remove-ui message.
51
+ id: Unique identifier of the UI message to remove.
52
+ """
53
+
54
+ type: Literal["remove-ui"]
55
+ id: str
56
+
57
+
58
+ AnyUIMessage = UIMessage | RemoveUIMessage
59
+
60
+
61
+ def push_ui_message(
62
+ name: str,
63
+ props: dict[str, Any],
64
+ *,
65
+ id: str | None = None,
66
+ metadata: dict[str, Any] | None = None,
67
+ message: AnyMessage | None = None,
68
+ state_key: str | None = "ui",
69
+ merge: bool = False,
70
+ ) -> UIMessage:
71
+ """Push a new UI message to update the UI state.
72
+
73
+ This function creates and sends a UI message that will be rendered in the UI.
74
+ It also updates the graph state with the new UI message.
75
+
76
+ Args:
77
+ name: Name of the UI component to render.
78
+ props: Properties to pass to the UI component.
79
+ id: Optional unique identifier for the UI message.
80
+ If not provided, a random UUID will be generated.
81
+ metadata: Optional additional metadata about the UI message.
82
+ message: Optional message object to associate with the UI message.
83
+ state_key: Key in the graph state where the UI messages are stored.
84
+ merge: Whether to merge props with existing UI message (True) or replace
85
+ them (False).
86
+
87
+ Returns:
88
+ The created UI message.
89
+
90
+ Example:
91
+ ```python
92
+ push_ui_message(
93
+ name="component-name",
94
+ props={"content": "Hello world"},
95
+ )
96
+ ```
97
+
98
+ """
99
+ from coco_graph._internal._constants import CONFIG_KEY_SEND
100
+
101
+ writer = get_stream_writer()
102
+ config = get_config()
103
+
104
+ message_id = None
105
+ if message:
106
+ if isinstance(message, dict) and "id" in message:
107
+ message_id = message.get("id")
108
+ elif hasattr(message, "id"):
109
+ message_id = message.id
110
+
111
+ evt: UIMessage = {
112
+ "type": "ui",
113
+ "id": id or str(uuid4()),
114
+ "name": name,
115
+ "props": props,
116
+ "metadata": {
117
+ "merge": merge,
118
+ "run_id": config.get("run_id", None),
119
+ "tags": config.get("tags", None),
120
+ "name": config.get("run_name", None),
121
+ **(metadata or {}),
122
+ **({"message_id": message_id} if message_id else {}),
123
+ },
124
+ }
125
+
126
+ writer(evt)
127
+ if state_key:
128
+ config[CONF][CONFIG_KEY_SEND]([(state_key, evt)])
129
+
130
+ return evt
131
+
132
+
133
+ def delete_ui_message(id: str, *, state_key: str = "ui") -> RemoveUIMessage:
134
+ """Delete a UI message by ID from the UI state.
135
+
136
+ This function creates and sends a message to remove a UI component from the current state.
137
+ It also updates the graph state to remove the UI message.
138
+
139
+ Args:
140
+ id: Unique identifier of the UI component to remove.
141
+ state_key: Key in the graph state where the UI messages are stored. Defaults to "ui".
142
+
143
+ Returns:
144
+ The remove UI message.
145
+
146
+ Example:
147
+ ```python
148
+ delete_ui_message("message-123")
149
+ ```
150
+
151
+ """
152
+ from coco_graph._internal._constants import CONFIG_KEY_SEND
153
+
154
+ writer = get_stream_writer()
155
+ config = get_config()
156
+
157
+ evt: RemoveUIMessage = {"type": "remove-ui", "id": id}
158
+
159
+ writer(evt)
160
+ config[CONF][CONFIG_KEY_SEND]([(state_key, evt)])
161
+
162
+ return evt
163
+
164
+
165
+ def ui_message_reducer(
166
+ left: list[AnyUIMessage] | AnyUIMessage,
167
+ right: list[AnyUIMessage] | AnyUIMessage,
168
+ ) -> list[AnyUIMessage]:
169
+ """Merge two lists of UI messages, supporting removing UI messages.
170
+
171
+ This function combines two lists of UI messages, handling both regular UI messages
172
+ and `remove-ui` messages. When a `remove-ui` message is encountered, it removes any
173
+ UI message with the matching ID from the current state.
174
+
175
+ Args:
176
+ left: First list of UI messages or single UI message.
177
+ right: Second list of UI messages or single UI message.
178
+
179
+ Returns:
180
+ Combined list of UI messages with removals applied.
181
+
182
+ Example:
183
+ ```python
184
+ messages = ui_message_reducer(
185
+ [{"type": "ui", "id": "1", "name": "Chat", "props": {}}],
186
+ {"type": "remove-ui", "id": "1"},
187
+ )
188
+ ```
189
+
190
+ """
191
+ if not isinstance(left, list):
192
+ left = [left]
193
+
194
+ if not isinstance(right, list):
195
+ right = [right]
196
+
197
+ # merge messages
198
+ merged = left.copy()
199
+ merged_by_id = {m.get("id"): i for i, m in enumerate(merged)}
200
+ ids_to_remove = set()
201
+
202
+ for msg in right:
203
+ msg_id = msg.get("id")
204
+
205
+ if (existing_idx := merged_by_id.get(msg_id)) is not None:
206
+ if msg.get("type") == "remove-ui":
207
+ ids_to_remove.add(msg_id)
208
+ else:
209
+ ids_to_remove.discard(msg_id)
210
+
211
+ if cast(UIMessage, msg).get("metadata", {}).get("merge", False):
212
+ prev_msg = merged[existing_idx]
213
+ msg = msg.copy()
214
+ msg["props"] = {**prev_msg["props"], **msg["props"]}
215
+
216
+ merged[existing_idx] = msg
217
+ else:
218
+ if msg.get("type") == "remove-ui":
219
+ raise ValueError(
220
+ f"Attempting to delete an UI message with an ID that doesn't exist ('{msg_id}')"
221
+ )
222
+
223
+ merged_by_id[msg_id] = len(merged)
224
+ merged.append(msg)
225
+
226
+ merged = [m for m in merged if m.get("id") not in ids_to_remove]
227
+ return merged
@@ -0,0 +1,3 @@
1
+ from coco_graph.managed.is_last_step import IsLastStep, RemainingSteps
2
+
3
+ __all__ = ("IsLastStep", "RemainingSteps")
@@ -0,0 +1,31 @@
1
+ from abc import ABC, abstractmethod
2
+ from inspect import isclass
3
+ from typing import (
4
+ Any,
5
+ Generic,
6
+ TypeGuard,
7
+ TypeVar,
8
+ )
9
+
10
+ from coco_graph._internal._scratchpad import PregelScratchpad
11
+
12
+ V = TypeVar("V")
13
+ U = TypeVar("U")
14
+
15
+ __all__ = ("ManagedValueSpec", "ManagedValueMapping")
16
+
17
+
18
+ class ManagedValue(ABC, Generic[V]):
19
+ @staticmethod
20
+ @abstractmethod
21
+ def get(scratchpad: PregelScratchpad) -> V: ...
22
+
23
+
24
+ ManagedValueSpec = type[ManagedValue]
25
+
26
+
27
+ def is_managed_value(value: Any) -> TypeGuard[ManagedValueSpec]:
28
+ return isclass(value) and issubclass(value, ManagedValue)
29
+
30
+
31
+ ManagedValueMapping = dict[str, ManagedValueSpec]
@@ -0,0 +1,24 @@
1
+ from typing import Annotated
2
+
3
+ from coco_graph._internal._scratchpad import PregelScratchpad
4
+ from coco_graph.managed.base import ManagedValue
5
+
6
+ __all__ = ("IsLastStep", "RemainingStepsManager")
7
+
8
+
9
+ class IsLastStepManager(ManagedValue[bool]):
10
+ @staticmethod
11
+ def get(scratchpad: PregelScratchpad) -> bool:
12
+ return scratchpad.step == scratchpad.stop - 1
13
+
14
+
15
+ IsLastStep = Annotated[bool, IsLastStepManager]
16
+
17
+
18
+ class RemainingStepsManager(ManagedValue[int]):
19
+ @staticmethod
20
+ def get(scratchpad: PregelScratchpad) -> int:
21
+ return scratchpad.stop - scratchpad.step
22
+
23
+
24
+ RemainingSteps = Annotated[int, RemainingStepsManager]
@@ -0,0 +1,23 @@
1
+ """coco_graph.prebuilt exposes a higher-level API for creating and executing agents and tools."""
2
+
3
+ from coco_graph.prebuilt._tool_call_transformer import ToolCallTransformer
4
+ from coco_graph.prebuilt.chat_agent_executor import create_react_agent
5
+ from coco_graph.prebuilt.tool_node import (
6
+ InjectedState,
7
+ InjectedStore,
8
+ ToolNode,
9
+ ToolRuntime,
10
+ tools_condition,
11
+ )
12
+ from coco_graph.prebuilt.tool_validator import ValidationNode
13
+
14
+ __all__ = [
15
+ "create_react_agent",
16
+ "ToolNode",
17
+ "ToolCallTransformer",
18
+ "tools_condition",
19
+ "ValidationNode",
20
+ "InjectedState",
21
+ "InjectedStore",
22
+ "ToolRuntime",
23
+ ]
@@ -0,0 +1,117 @@
1
+ """In-process handle for a single tool call's streaming execution.
2
+
3
+ Mirrors the shape of `ChatModelStream` from trustcoco-core but simpler —
4
+ a tool has one output channel, no content-block multiplexing. Populated
5
+ by `ToolCallTransformer` as `tool-started` / `tool-output-delta` /
6
+ `tool-finished` / `tool-error` events flow in on the `tools` channel.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from collections.abc import AsyncIterator, Iterator
12
+ from typing import Any
13
+
14
+ from coco_graph.stream.stream_channel import StreamChannel
15
+
16
+
17
+ class ToolCallStream:
18
+ """Scoped view of a single tool call's lifecycle.
19
+
20
+ Yielded on `run.tool_calls` once per `tool-started` event. Fields
21
+ are populated as events arrive:
22
+
23
+ - `tool_call_id`, `tool_name`, `input`: stable from the start event.
24
+ - `output_deltas`: a `StreamChannel` of delta chunks. Iterate (sync or
25
+ async) to consume partial output in arrival order.
26
+ - `output`: terminal payload from `tool-finished`, or `None` if the
27
+ call failed or is still in flight.
28
+ - `error`: terminal error string from `tool-error`, or `None` if the
29
+ call succeeded or is still in flight.
30
+ - `completed`: True once a terminal event (`tool-finished` or
31
+ `tool-error`) has been observed.
32
+
33
+ `ToolCallStream` is not meant to be constructed by end users — it's
34
+ produced by `ToolCallTransformer` as events flow through the mux.
35
+ """
36
+
37
+ def __init__(
38
+ self,
39
+ tool_call_id: str,
40
+ tool_name: str,
41
+ input: dict[str, Any] | None = None,
42
+ ) -> None:
43
+ """Initialize a fresh handle for a tool call.
44
+
45
+ Args:
46
+ tool_call_id: The `tool_call_id` from the AIMessage.
47
+ tool_name: The tool's name.
48
+ input: The tool's input arguments (as reported by
49
+ `on_tool_start`), or `None` if none were captured.
50
+ """
51
+ self.tool_call_id = tool_call_id
52
+ self.tool_name = tool_name
53
+ self.input = input
54
+ self._output_deltas: StreamChannel[Any] = StreamChannel()
55
+ self.output: Any = None
56
+ self.error: str | None = None
57
+ self.completed = False
58
+
59
+ @property
60
+ def output_deltas(self) -> StreamChannel[Any]:
61
+ """The channel of streamed `tool-output-delta` payloads.
62
+
63
+ Iterate (sync or async depending on how the run was started)
64
+ to consume partial output in arrival order. The log closes when
65
+ the tool finishes or errors.
66
+ """
67
+ return self._output_deltas
68
+
69
+ def _bind(self, *, is_async: bool) -> None:
70
+ """Bind the deltas log to sync or async iteration.
71
+
72
+ Called by `ToolCallTransformer` when constructing this handle so
73
+ the log matches the enclosing mux's mode.
74
+ """
75
+ self._output_deltas._bind(is_async=is_async)
76
+
77
+ def _push_delta(self, delta: Any) -> None:
78
+ self._output_deltas.push(delta)
79
+
80
+ def _finish(self, output: Any) -> None:
81
+ self.output = output
82
+ self.completed = True
83
+ self._output_deltas.close()
84
+
85
+ def _fail(self, message: str) -> None:
86
+ self.error = message
87
+ self.completed = True
88
+ self._output_deltas.close()
89
+
90
+ def __iter__(self) -> Iterator[Any]:
91
+ """Iterate delta chunks synchronously.
92
+
93
+ Equivalent to `iter(self.output_deltas)`. Raises `TypeError` if
94
+ the underlying log is bound to async mode.
95
+ """
96
+ return iter(self._output_deltas)
97
+
98
+ def __aiter__(self) -> AsyncIterator[Any]:
99
+ """Iterate delta chunks asynchronously.
100
+
101
+ Equivalent to `aiter(self.output_deltas)`. Raises `TypeError`
102
+ if the underlying log is bound to sync mode.
103
+ """
104
+ return self._output_deltas.__aiter__()
105
+
106
+ def __repr__(self) -> str:
107
+ status = (
108
+ "completed"
109
+ if self.completed and self.error is None
110
+ else "failed"
111
+ if self.completed
112
+ else "running"
113
+ )
114
+ return (
115
+ f"ToolCallStream(tool_call_id={self.tool_call_id!r}, "
116
+ f"tool_name={self.tool_name!r}, status={status})"
117
+ )
@@ -0,0 +1,165 @@
1
+ """Transformer that projects `tools` channel events into `ToolCallStream`s."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Awaitable, Callable
6
+ from typing import Any
7
+
8
+ from coco_core.messages import ToolMessage
9
+ from coco_graph.stream._types import ProtocolEvent, StreamTransformer
10
+ from coco_graph.stream.stream_channel import StreamChannel
11
+
12
+ from coco_graph.prebuilt._tool_call_stream import ToolCallStream
13
+
14
+
15
+ def _is_serialized_tool_message(value: Any) -> bool:
16
+ """Detect a serialized Coco `ToolMessage` payload.
17
+
18
+ Example:
19
+ {
20
+ "lc": 1,
21
+ "type": "constructor",
22
+ "id": ["coco_core", "messages", "ToolMessage"],
23
+ "kwargs": {"content": "raw tool result", "tool_call_id": "call_1"},
24
+ }
25
+ """
26
+ return (
27
+ isinstance(value, dict)
28
+ and value.get("type") == "constructor"
29
+ and isinstance(value.get("id"), list)
30
+ and value["id"][-1] == "ToolMessage"
31
+ )
32
+
33
+
34
+ def _normalize_tool_output(output: Any) -> Any:
35
+ if isinstance(output, ToolMessage):
36
+ return output.content
37
+ if _is_serialized_tool_message(output):
38
+ kwargs = output.get("kwargs")
39
+ if isinstance(kwargs, dict):
40
+ return kwargs.get("content")
41
+ return output
42
+
43
+
44
+ class ToolCallTransformer(StreamTransformer):
45
+ """Project `tools` channel events into `ToolCallStream` handles.
46
+
47
+ Each `tool-started` event spawns a `ToolCallStream`, pushed onto
48
+ `run.tool_calls`. Subsequent `tool-output-delta` events append to
49
+ that stream's deltas log; `tool-finished` and `tool-error` close it.
50
+
51
+ Native transformer — the `tool_calls` projection is exposed as a
52
+ direct attribute on the run stream.
53
+
54
+ A nameless `StreamChannel[ToolCallStream]` is used (no protocol
55
+ auto-forwarding) because the live handles are not serializable and
56
+ should not be injected into the main event log. Wire consumers
57
+ subscribe to the `tools` channel instead, where the raw protocol
58
+ events flow through untouched by this transformer (`process`
59
+ returns `True`).
60
+
61
+ Registered explicitly by users at compile time via
62
+ `builder.compile(transformers=[ToolCallTransformer])` — not a
63
+ default built-in, so the `tools` channel is user-opt-in.
64
+ """
65
+
66
+ _native = True
67
+ required_stream_modes = ("tools",)
68
+
69
+ def __init__(self, scope: tuple[str, ...] = ()) -> None:
70
+ super().__init__(scope)
71
+ self._log: StreamChannel[ToolCallStream] = StreamChannel()
72
+ self._active: dict[str, ToolCallStream] = {}
73
+ self._is_async = False
74
+ self._pump_fn: Callable[[], bool] | None = None
75
+ self._apump_fn: Callable[[], Awaitable[bool]] | None = None
76
+
77
+ def init(self) -> dict[str, Any]:
78
+ return {"tool_calls": self._log}
79
+
80
+ def _bind_pump(self, fn: Callable[[], bool]) -> None:
81
+ """Wire the sync pull callback onto this transformer.
82
+
83
+ Called by `StreamMux.bind_pump`. Stored so each new
84
+ `ToolCallStream` created by `process` can wire its deltas log
85
+ for pump-driven iteration.
86
+ """
87
+ self._pump_fn = fn
88
+ self._is_async = False
89
+
90
+ def _bind_apump(self, fn: Callable[[], Awaitable[bool]]) -> None:
91
+ """Async counterpart to `_bind_pump`."""
92
+ self._apump_fn = fn
93
+ self._is_async = True
94
+
95
+ def _new_stream(
96
+ self,
97
+ tool_call_id: str,
98
+ tool_name: str,
99
+ tool_input: dict[str, Any] | None,
100
+ ) -> ToolCallStream:
101
+ stream = ToolCallStream(tool_call_id, tool_name, tool_input)
102
+ stream._bind(is_async=self._is_async)
103
+ if self._apump_fn is not None:
104
+ stream._output_deltas._arequest_more = self._apump_fn
105
+ if self._pump_fn is not None:
106
+ stream._output_deltas._request_more = self._pump_fn
107
+ return stream
108
+
109
+ def process(self, event: ProtocolEvent) -> bool:
110
+ if event["method"] != "tools":
111
+ return True
112
+
113
+ # Only project events emitted at this transformer's scope. Subgraph
114
+ # events still flow through the parent's mux (the parent's main
115
+ # event log keeps them) but they belong to the child mini-mux's
116
+ # `tool_calls` projection, not the parent's.
117
+ if tuple(event["params"]["namespace"]) != self.scope:
118
+ return True
119
+
120
+ data = event["params"]["data"]
121
+ tool_call_id = data.get("tool_call_id")
122
+ if tool_call_id is None:
123
+ return True
124
+ event_type = data.get("event")
125
+
126
+ stream: ToolCallStream | None
127
+ if event_type == "tool-started":
128
+ stream = self._new_stream(
129
+ tool_call_id,
130
+ data.get("tool_name", ""),
131
+ data.get("input"),
132
+ )
133
+ self._active[tool_call_id] = stream
134
+ self._log.push(stream)
135
+ elif event_type == "tool-output-delta":
136
+ stream = self._active.get(tool_call_id)
137
+ if stream is not None:
138
+ stream._push_delta(data.get("delta"))
139
+ elif event_type == "tool-finished":
140
+ stream = self._active.pop(tool_call_id, None)
141
+ if stream is not None:
142
+ stream._finish(_normalize_tool_output(data.get("output")))
143
+ elif event_type == "tool-error":
144
+ stream = self._active.pop(tool_call_id, None)
145
+ if stream is not None:
146
+ stream._fail(data.get("message", ""))
147
+
148
+ # Pass-through — wire consumers subscribe to the `tools` channel
149
+ # directly and reconstruct handles client-side.
150
+ return True
151
+
152
+ def finalize(self) -> None:
153
+ """Close any still-active tool streams left open at run end."""
154
+ for stream in self._active.values():
155
+ if not stream.completed:
156
+ stream._finish(None)
157
+ self._active.clear()
158
+
159
+ def fail(self, err: BaseException) -> None:
160
+ """Fail any still-active tool streams when the run errors."""
161
+ message = str(err)
162
+ for stream in self._active.values():
163
+ if not stream.completed:
164
+ stream._fail(message)
165
+ self._active.clear()