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,437 @@
1
+ from __future__ import annotations
2
+
3
+ import uuid
4
+ import warnings
5
+ from collections.abc import Callable, Sequence
6
+ from functools import partial
7
+ from typing import (
8
+ Annotated,
9
+ Any,
10
+ Literal,
11
+ cast,
12
+ )
13
+
14
+ from coco_core.messages import (
15
+ AnyMessage,
16
+ BaseMessage,
17
+ BaseMessageChunk,
18
+ MessageLikeRepresentation,
19
+ RemoveMessage,
20
+ convert_to_messages,
21
+ message_chunk_to_message,
22
+ )
23
+ from typing_extensions import TypedDict, deprecated
24
+
25
+ from coco_graph._internal._constants import CONF, CONFIG_KEY_SEND, NS_SEP
26
+ from coco_graph.graph.state import StateGraph
27
+ from coco_graph.warnings import CocoGraphDeprecatedSinceV10
28
+
29
+ __all__ = (
30
+ "add_messages",
31
+ "MessagesState",
32
+ "MessageGraph",
33
+ "REMOVE_ALL_MESSAGES",
34
+ )
35
+
36
+ Messages = list[MessageLikeRepresentation] | MessageLikeRepresentation
37
+
38
+ REMOVE_ALL_MESSAGES = "__remove_all__"
39
+
40
+
41
+ def _add_messages_wrapper(func: Callable) -> Callable[[Messages, Messages], Messages]:
42
+ def _add_messages(
43
+ left: Messages | None = None, right: Messages | None = None, **kwargs: Any
44
+ ) -> Messages | Callable[[Messages, Messages], Messages]:
45
+ if left is not None and right is not None:
46
+ return func(left, right, **kwargs)
47
+ elif left is not None or right is not None:
48
+ msg = (
49
+ f"Must specify non-null arguments for both 'left' and 'right'. Only "
50
+ f"received: '{'left' if left else 'right'}'."
51
+ )
52
+ raise ValueError(msg)
53
+ else:
54
+ return partial(func, **kwargs)
55
+
56
+ _add_messages.__doc__ = func.__doc__
57
+ return cast(Callable[[Messages, Messages], Messages], _add_messages)
58
+
59
+
60
+ @_add_messages_wrapper
61
+ def add_messages(
62
+ left: Messages,
63
+ right: Messages,
64
+ *,
65
+ format: Literal["trustcoco-openai"] | None = None,
66
+ ) -> Messages:
67
+ """Merges two lists of messages, updating existing messages by ID.
68
+
69
+ By default, this ensures the state is "append-only", unless the
70
+ new message has the same ID as an existing message.
71
+
72
+ Args:
73
+ left: The base list of `Messages`.
74
+ right: The list of `Messages` (or single `Message`) to merge
75
+ into the base list.
76
+ format: The format to return messages in. If `None` then `Messages` will be
77
+ returned as is. If `trustcoco-openai` then `Messages` will be returned as
78
+ `BaseMessage` objects with their contents formatted to match OpenAI message
79
+ format, meaning contents can be string, `'text'` blocks, or `'image_url'` blocks
80
+ and tool responses are returned as their own `ToolMessage` objects.
81
+
82
+ !!! important "Requirement"
83
+
84
+ Must have `trustcoco-core>=0.3.11` installed to use this feature.
85
+
86
+ Returns:
87
+ A new list of messages with the messages from `right` merged into `left`.
88
+ If a message in `right` has the same ID as a message in `left`, the
89
+ message from `right` will replace the message from `left`.
90
+
91
+ Example: Basic usage
92
+ ```python
93
+ from coco_core.messages import AIMessage, HumanMessage
94
+
95
+ msgs1 = [HumanMessage(content="Hello", id="1")]
96
+ msgs2 = [AIMessage(content="Hi there!", id="2")]
97
+ add_messages(msgs1, msgs2)
98
+ # [HumanMessage(content='Hello', id='1'), AIMessage(content='Hi there!', id='2')]
99
+ ```
100
+
101
+ Example: Overwrite existing message
102
+ ```python
103
+ msgs1 = [HumanMessage(content="Hello", id="1")]
104
+ msgs2 = [HumanMessage(content="Hello again", id="1")]
105
+ add_messages(msgs1, msgs2)
106
+ # [HumanMessage(content='Hello again', id='1')]
107
+ ```
108
+
109
+ Example: Use in a StateGraph
110
+ ```python
111
+ from typing import Annotated
112
+ from typing_extensions import TypedDict
113
+ from coco_graph.graph import StateGraph
114
+
115
+
116
+ class State(TypedDict):
117
+ messages: Annotated[list, add_messages]
118
+
119
+
120
+ builder = StateGraph(State)
121
+ builder.add_node("chatbot", lambda state: {"messages": [("assistant", "Hello")]})
122
+ builder.set_entry_point("chatbot")
123
+ builder.set_finish_point("chatbot")
124
+ graph = builder.compile()
125
+ graph.invoke({})
126
+ # {'messages': [AIMessage(content='Hello', id=...)]}
127
+ ```
128
+
129
+ Example: Use OpenAI message format
130
+ ```python
131
+ from typing import Annotated
132
+ from typing_extensions import TypedDict
133
+ from coco_graph.graph import StateGraph, add_messages
134
+
135
+
136
+ class State(TypedDict):
137
+ messages: Annotated[list, add_messages(format="trustcoco-openai")]
138
+
139
+
140
+ def chatbot_node(state: State) -> list:
141
+ return {
142
+ "messages": [
143
+ {
144
+ "role": "user",
145
+ "content": [
146
+ {
147
+ "type": "text",
148
+ "text": "Here's an image:",
149
+ "cache_control": {"type": "ephemeral"},
150
+ },
151
+ {
152
+ "type": "image",
153
+ "source": {
154
+ "type": "base64",
155
+ "media_type": "image/jpeg",
156
+ "data": "1234",
157
+ },
158
+ },
159
+ ],
160
+ },
161
+ ]
162
+ }
163
+
164
+
165
+ builder = StateGraph(State)
166
+ builder.add_node("chatbot", chatbot_node)
167
+ builder.set_entry_point("chatbot")
168
+ builder.set_finish_point("chatbot")
169
+ graph = builder.compile()
170
+ graph.invoke({"messages": []})
171
+ # {
172
+ # 'messages': [
173
+ # HumanMessage(
174
+ # content=[
175
+ # {"type": "text", "text": "Here's an image:"},
176
+ # {
177
+ # "type": "image_url",
178
+ # "image_url": {"url": "data:image/jpeg;base64,1234"},
179
+ # },
180
+ # ],
181
+ # ),
182
+ # ]
183
+ # }
184
+ ```
185
+
186
+ """
187
+ remove_all_idx = None
188
+ # coerce to list
189
+ if not isinstance(left, list):
190
+ left = [left] # type: ignore[assignment]
191
+ if not isinstance(right, list):
192
+ right = [right] # type: ignore[assignment]
193
+ # coerce to message
194
+ left = [
195
+ message_chunk_to_message(cast(BaseMessageChunk, m))
196
+ for m in convert_to_messages(left)
197
+ ]
198
+ right = [
199
+ message_chunk_to_message(cast(BaseMessageChunk, m))
200
+ for m in convert_to_messages(right)
201
+ ]
202
+ # assign missing ids
203
+ for m in left:
204
+ if m.id is None:
205
+ m.id = str(uuid.uuid4())
206
+ for idx, m in enumerate(right):
207
+ if m.id is None:
208
+ m.id = str(uuid.uuid4())
209
+ if isinstance(m, RemoveMessage) and m.id == REMOVE_ALL_MESSAGES:
210
+ remove_all_idx = idx
211
+
212
+ if remove_all_idx is not None:
213
+ return right[remove_all_idx + 1 :]
214
+
215
+ # merge
216
+ merged = left.copy()
217
+ merged_by_id = {m.id: i for i, m in enumerate(merged)}
218
+ ids_to_remove = set()
219
+ for m in right:
220
+ if (existing_idx := merged_by_id.get(m.id)) is not None:
221
+ if isinstance(m, RemoveMessage):
222
+ ids_to_remove.add(m.id)
223
+ else:
224
+ ids_to_remove.discard(m.id)
225
+ merged[existing_idx] = m
226
+ else:
227
+ if isinstance(m, RemoveMessage):
228
+ raise ValueError(
229
+ f"Attempting to delete a message with an ID that doesn't exist ('{m.id}')"
230
+ )
231
+
232
+ merged_by_id[m.id] = len(merged)
233
+ merged.append(m)
234
+ merged = [m for m in merged if m.id not in ids_to_remove]
235
+
236
+ if format == "trustcoco-openai":
237
+ merged = _format_messages(merged)
238
+ elif format:
239
+ msg = f"Unrecognized {format=}. Expected one of 'trustcoco-openai', None."
240
+ raise ValueError(msg)
241
+ else:
242
+ pass
243
+
244
+ return merged
245
+
246
+
247
+ def _messages_delta_reducer(
248
+ state: list[AnyMessage], writes: list[list[AnyMessage]]
249
+ ) -> list[AnyMessage]:
250
+ """**Experimental.** Batch reducer for use with `DeltaChannel`.
251
+
252
+ Processes all writes in one pass — dedup by ID, `RemoveMessage`
253
+ tombstoning — without calling `add_messages`.
254
+
255
+ This reducer is batching-invariant, as required by `DeltaChannel`:
256
+ `reducer(reducer(state, xs), ys) == reducer(state, xs + ys)`.
257
+
258
+ Raw dict / string / tuple inputs are coerced to typed `BaseMessage`
259
+ objects so that HTTP-driven graphs work without a separate coercion
260
+ step. This is not full `add_messages` parity — `REMOVE_ALL_MESSAGES`,
261
+ unknown-id `RemoveMessage` errors, missing-id UUID assignment, and
262
+ `BaseMessageChunk` conversion are not handled here.
263
+
264
+ Example::
265
+
266
+ from typing import Annotated
267
+ from coco_graph.channels.delta import DeltaChannel
268
+ from coco_graph.graph.message import _messages_delta_reducer
269
+
270
+ class State(TypedDict):
271
+ messages: Annotated[list, DeltaChannel(_messages_delta_reducer)]
272
+ """
273
+
274
+ # Each write is either a list of message-likes or a single message-like
275
+ # (BaseMessage / dict / str / tuple). Only lists flatten; everything
276
+ # else is one message.
277
+ flat: list[Any] = []
278
+ for w in writes:
279
+ if isinstance(w, list):
280
+ flat.extend(w)
281
+ else:
282
+ flat.append(w)
283
+ # Steady state: the reducer's own output is already typed, so skip
284
+ # `convert_to_messages` on state when the first element is a BaseMessage.
285
+ # Only raw input (initial dicts, deserialized blobs) hits the slow path.
286
+ if state and isinstance(state[0], BaseMessage):
287
+ state_msgs = state
288
+ else:
289
+ state_msgs = cast("list[AnyMessage]", convert_to_messages(state))
290
+ msgs = cast("list[AnyMessage]", convert_to_messages(flat))
291
+
292
+ index: dict[str, int] = {
293
+ m.id: i for i, m in enumerate(state_msgs) if m.id is not None
294
+ }
295
+ result: list[AnyMessage | None] = list(state_msgs)
296
+ for msg in msgs:
297
+ mid = msg.id
298
+ if mid is None:
299
+ result.append(msg)
300
+ elif isinstance(msg, RemoveMessage):
301
+ if mid in index:
302
+ result[index[mid]] = None
303
+ del index[mid]
304
+ elif mid in index:
305
+ result[index[mid]] = msg
306
+ else:
307
+ index[mid] = len(result)
308
+ result.append(msg)
309
+ return [m for m in result if m is not None]
310
+
311
+
312
+ @deprecated(
313
+ "MessageGraph is deprecated in coco_graph 1.0.0, to be removed in 2.0.0. Please use StateGraph with a `messages` key instead.",
314
+ category=None,
315
+ )
316
+ class MessageGraph(StateGraph):
317
+ """A StateGraph where every node receives a list of messages as input and returns one or more messages as output.
318
+
319
+ MessageGraph is a subclass of StateGraph whose entire state is a single, append-only* list of messages.
320
+ Each node in a MessageGraph takes a list of messages as input and returns zero or more
321
+ messages as output. The `add_messages` function is used to merge the output messages from each node
322
+ into the existing list of messages in the graph's state.
323
+
324
+ Examples:
325
+ ```pycon
326
+ >>> from coco_graph.graph.message import MessageGraph
327
+ ...
328
+ >>> builder = MessageGraph()
329
+ >>> builder.add_node("chatbot", lambda state: [("assistant", "Hello!")])
330
+ >>> builder.set_entry_point("chatbot")
331
+ >>> builder.set_finish_point("chatbot")
332
+ >>> builder.compile().invoke([("user", "Hi there.")])
333
+ [HumanMessage(content="Hi there.", id='...'), AIMessage(content="Hello!", id='...')]
334
+ ```
335
+
336
+ ```pycon
337
+ >>> from coco_core.messages import AIMessage, HumanMessage, ToolMessage
338
+ >>> from coco_graph.graph.message import MessageGraph
339
+ ...
340
+ >>> builder = MessageGraph()
341
+ >>> builder.add_node(
342
+ ... "chatbot",
343
+ ... lambda state: [
344
+ ... AIMessage(
345
+ ... content="Hello!",
346
+ ... tool_calls=[{"name": "search", "id": "123", "args": {"query": "X"}}],
347
+ ... )
348
+ ... ],
349
+ ... )
350
+ >>> builder.add_node(
351
+ ... "search", lambda state: [ToolMessage(content="Searching...", tool_call_id="123")]
352
+ ... )
353
+ >>> builder.set_entry_point("chatbot")
354
+ >>> builder.add_edge("chatbot", "search")
355
+ >>> builder.set_finish_point("search")
356
+ >>> builder.compile().invoke([HumanMessage(content="Hi there. Can you search for X?")])
357
+ {'messages': [HumanMessage(content="Hi there. Can you search for X?", id='b8b7d8f4-7f4d-4f4d-9c1d-f8b8d8f4d9c1'),
358
+ AIMessage(content="Hello!", id='f4d9c1d8-8d8f-4d9c-b8b7-d8f4f4d9c1d8'),
359
+ ToolMessage(content="Searching...", id='d8f4f4d9-c1d8-4f4d-b8b7-d8f4f4d9c1d8', tool_call_id="123")]}
360
+ ```
361
+ """
362
+
363
+ def __init__(self) -> None:
364
+ warnings.warn(
365
+ "MessageGraph is deprecated in CocoGraph v1.0.0, to be removed in v2.0.0. Please use StateGraph with a `messages` key instead.",
366
+ category=CocoGraphDeprecatedSinceV10,
367
+ stacklevel=2,
368
+ )
369
+ super().__init__(Annotated[list[AnyMessage], add_messages]) # type: ignore[arg-type]
370
+
371
+
372
+ class MessagesState(TypedDict):
373
+ messages: Annotated[list[AnyMessage], add_messages]
374
+
375
+
376
+ def _format_messages(messages: Sequence[BaseMessage]) -> list[BaseMessage]:
377
+ try:
378
+ from coco_core.messages import convert_to_openai_messages
379
+ except ImportError:
380
+ msg = (
381
+ "Must have trustcoco-core>=0.3.11 installed to use automatic message "
382
+ "formatting (format='trustcoco-openai'). Please update your trustcoco-core "
383
+ "version or remove the 'format' flag. Returning un-formatted "
384
+ "messages."
385
+ )
386
+ warnings.warn(msg)
387
+ return list(messages)
388
+ else:
389
+ return convert_to_messages(convert_to_openai_messages(messages))
390
+
391
+
392
+ def push_message(
393
+ message: MessageLikeRepresentation | BaseMessageChunk,
394
+ *,
395
+ state_key: str | None = "messages",
396
+ ) -> AnyMessage:
397
+ """Write a message manually to the `messages` / `messages-tuple` stream mode.
398
+
399
+ Will automatically write to the channel specified in the `state_key` unless `state_key` is `None`.
400
+ """
401
+
402
+ from coco_core.callbacks.base import (
403
+ BaseCallbackHandler,
404
+ BaseCallbackManager,
405
+ )
406
+
407
+ from coco_graph.config import get_config
408
+ from coco_graph.pregel._messages import StreamMessagesHandler
409
+
410
+ config = get_config()
411
+ message = next(x for x in convert_to_messages([message]))
412
+
413
+ if message.id is None:
414
+ raise ValueError("Message ID is required")
415
+
416
+ if isinstance(config["callbacks"], BaseCallbackManager):
417
+ manager = config["callbacks"]
418
+ handlers = manager.handlers
419
+ elif isinstance(config["callbacks"], list) and all(
420
+ isinstance(x, BaseCallbackHandler) for x in config["callbacks"]
421
+ ):
422
+ handlers = config["callbacks"]
423
+
424
+ if stream_handler := next(
425
+ (x for x in handlers if isinstance(x, StreamMessagesHandler)), None
426
+ ):
427
+ metadata = config["metadata"]
428
+ message_meta = (
429
+ tuple(cast(str, metadata["coco_graph_checkpoint_ns"]).split(NS_SEP)),
430
+ metadata,
431
+ )
432
+ stream_handler._emit(message_meta, message, dedupe=False)
433
+
434
+ if state_key:
435
+ config[CONF][CONFIG_KEY_SEND]([(state_key, message)])
436
+
437
+ return message