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,357 @@
1
+ """Planning and task management middleware for agents."""
2
+
3
+ from collections.abc import Awaitable, Callable
4
+ from typing import Annotated, Any, Literal, cast
5
+
6
+ from coco_core.messages import AIMessage, SystemMessage, ToolMessage
7
+ from coco_core.tools import InjectedToolCallId, StructuredTool, tool
8
+ from coco_graph.runtime import Runtime
9
+ from coco_graph.types import Command
10
+ from pydantic import BaseModel
11
+ from typing_extensions import NotRequired, TypedDict, override
12
+
13
+ from coco.agents.middleware.types import (
14
+ AgentMiddleware,
15
+ AgentState,
16
+ ContextT,
17
+ ModelRequest,
18
+ ModelResponse,
19
+ OmitFromInput,
20
+ ResponseT,
21
+ )
22
+ from coco.tools import ToolRuntime
23
+
24
+
25
+ class Todo(TypedDict):
26
+ """A single todo item with content and status."""
27
+
28
+ content: str
29
+ """The content/description of the todo item."""
30
+
31
+ status: Literal["pending", "in_progress", "completed"]
32
+ """The current status of the todo item."""
33
+
34
+
35
+ class PlanningState(AgentState[ResponseT]):
36
+ """State schema for the todo middleware.
37
+
38
+ Type Parameters:
39
+ ResponseT: The type of the structured response. Defaults to `Any`.
40
+ """
41
+
42
+ todos: Annotated[NotRequired[list[Todo]], OmitFromInput]
43
+ """List of todo items for tracking task progress."""
44
+
45
+
46
+ class WriteTodosInput(BaseModel):
47
+ """Input schema for the `write_todos` tool."""
48
+
49
+ todos: list[Todo]
50
+
51
+
52
+ WRITE_TODOS_TOOL_DESCRIPTION = """Use this tool to create and manage a structured task list for your current work session. This helps you track progress and organize complex tasks.
53
+
54
+ Only use this tool if you think it will be helpful in staying organized. If the user's request is trivial and takes less than 3 steps, it is better to NOT use this tool and just do the task directly.
55
+
56
+ ## When to Use This Tool
57
+
58
+ Use this tool in these scenarios:
59
+
60
+ 1. Complex multi-step tasks - When a task requires 3 or more distinct steps or actions
61
+ 2. Non-trivial and complex tasks - Tasks that require careful planning or multiple operations
62
+ 3. User explicitly requests todo list - When the user directly asks you to use the todo list
63
+ 4. User provides multiple tasks - When users provide a list of things to be done (numbered or comma-separated)
64
+ 5. The plan may need future revisions or updates based on results from the first few steps
65
+
66
+ ## How to Use This Tool
67
+
68
+ 1. When you start working on a task - Mark it as in_progress BEFORE beginning work.
69
+ 2. After completing a task - Mark it as completed and add any new follow-up tasks discovered during implementation.
70
+ 3. You can also update future tasks, such as deleting them if they are no longer necessary, or adding new tasks that are necessary. Don't change previously completed tasks.
71
+ 4. You can make several updates to the todo list at once. For example, when you complete a task, you can mark the next task you need to start as in_progress.
72
+
73
+ ## When NOT to Use This Tool
74
+
75
+ It is important to skip using this tool when:
76
+ 1. There is only a single, straightforward task
77
+ 2. The task is trivial and tracking it provides no benefit
78
+ 3. The task can be completed in less than 3 trivial steps
79
+ 4. The task is purely conversational or informational
80
+
81
+ ## Task States and Management
82
+
83
+ 1. **Task States**: Use these states to track progress:
84
+ - pending: Task not yet started
85
+ - in_progress: Currently working on (you can have multiple tasks in_progress at a time if they are not related to each other and can be run in parallel)
86
+ - completed: Task finished successfully
87
+
88
+ 2. **Task Management**:
89
+ - Update task status in real-time as you work
90
+ - Mark tasks complete IMMEDIATELY after finishing (don't batch completions)
91
+ - Complete current tasks before starting new ones
92
+ - Remove tasks that are no longer relevant from the list entirely
93
+ - IMPORTANT: When you write this todo list, you should mark your first task (or tasks) as in_progress immediately!.
94
+ - IMPORTANT: Unless all tasks are completed, you should always have at least one task in_progress.
95
+
96
+ 3. **Task Completion Requirements**:
97
+ - ONLY mark a task as completed when you have FULLY accomplished it
98
+ - If you encounter errors, blockers, or cannot finish, keep the task as in_progress
99
+ - When blocked, create a new task describing what needs to be resolved
100
+ - Never mark a task as completed if:
101
+ - There are unresolved issues or errors
102
+ - Work is partial or incomplete
103
+ - You encountered blockers that prevent completion
104
+ - You couldn't find necessary resources or dependencies
105
+ - Quality standards haven't been met
106
+
107
+ 4. **Task Breakdown**:
108
+ - Create specific, actionable items
109
+ - Break complex tasks into smaller, manageable steps
110
+ - Use clear, descriptive task names
111
+
112
+ Being proactive with task management ensures you complete all requirements successfully
113
+ Remember: If you only need to make a few tool calls to complete a task, and it is clear what you need to do, it is better to just do the task directly and NOT call this tool at all.
114
+
115
+ ## When You Finish
116
+
117
+ `write_todos` tracks your work; it does not deliver the answer. Whatever the user asked for — computations, summaries, comparisons, data — must appear as text content in a message after your final `write_todos` call. Marking the last todo complete is not itself an answer to the user.""" # noqa: E501
118
+
119
+ WRITE_TODOS_SYSTEM_PROMPT = """## `write_todos`
120
+
121
+ You have access to the `write_todos` tool to help you manage and plan complex objectives.
122
+ Use this tool for complex objectives to ensure that you are tracking each necessary step.
123
+ This tool is very helpful for planning complex objectives, and for breaking down these larger complex objectives into smaller steps.
124
+
125
+ It is critical that you mark todos as completed as soon as you are done with a step. Do not batch up multiple steps before marking them as completed.
126
+ For simple objectives that only require a few steps, it is better to just complete the objective directly and NOT use this tool.
127
+ Writing todos takes time and tokens, use it when it is helpful for managing complex many-step problems! But not for simple few-step requests.
128
+
129
+ ## Important To-Do List Usage Notes to Remember
130
+
131
+ - The `write_todos` tool should never be called multiple times in parallel.
132
+ - Don't be afraid to revise the To-Do list as you go. New information may reveal new tasks that need to be done, or old tasks that are irrelevant.
133
+
134
+ ## Finishing a task
135
+
136
+ When you finish all work, write your final answer in the message AFTER your last `write_todos` call — not in the same turn as that call. Start the final message with the substantive content the user asked for — the data, computation, summary, or analysis. The user wants the result, not confirmation that the work is done.""" # noqa: E501
137
+
138
+
139
+ @tool(description=WRITE_TODOS_TOOL_DESCRIPTION)
140
+ def write_todos(
141
+ todos: list[Todo], tool_call_id: Annotated[str, InjectedToolCallId]
142
+ ) -> Command[Any]:
143
+ """Create and manage a structured task list for your current work session."""
144
+ return Command(
145
+ update={
146
+ "todos": todos,
147
+ "messages": [ToolMessage(f"Updated todo list to {todos}", tool_call_id=tool_call_id)],
148
+ }
149
+ )
150
+
151
+
152
+ # Dynamically create the write_todos tool with the custom description
153
+ def _write_todos(
154
+ runtime: ToolRuntime[ContextT, PlanningState[ResponseT]], todos: list[Todo]
155
+ ) -> Command[Any]:
156
+ """Create and manage a structured task list for your current work session."""
157
+ return Command(
158
+ update={
159
+ "todos": todos,
160
+ "messages": [
161
+ ToolMessage(f"Updated todo list to {todos}", tool_call_id=runtime.tool_call_id)
162
+ ],
163
+ }
164
+ )
165
+
166
+
167
+ async def _awrite_todos(
168
+ runtime: ToolRuntime[ContextT, PlanningState[ResponseT]], todos: list[Todo]
169
+ ) -> Command[Any]:
170
+ """Create and manage a structured task list for your current work session."""
171
+ return _write_todos(runtime, todos)
172
+
173
+
174
+ class TodoListMiddleware(AgentMiddleware[PlanningState[ResponseT], ContextT, ResponseT]):
175
+ """Middleware that provides todo list management capabilities to agents.
176
+
177
+ This middleware adds a `write_todos` tool that allows agents to create and manage
178
+ structured task lists for complex multi-step operations. It's designed to help
179
+ agents track progress, organize complex tasks, and provide users with visibility
180
+ into task completion status.
181
+
182
+ The middleware automatically injects system prompts that guide the agent on when
183
+ and how to use the todo functionality effectively. It also enforces that the
184
+ `write_todos` tool is called at most once per model turn, since the tool replaces
185
+ the entire todo list and parallel calls would create ambiguity about precedence.
186
+
187
+ Example:
188
+ ```python
189
+ from coco.agents.middleware import TodoListMiddleware
190
+ from coco.agents import create_agent
191
+
192
+ agent = create_agent("openai:gpt-5.5", middleware=[TodoListMiddleware()])
193
+
194
+ # Agent now has access to write_todos tool and todo state tracking
195
+ result = await agent.invoke({"messages": [HumanMessage("Help me refactor my codebase")]})
196
+
197
+ print(result["todos"]) # Array of todo items with status tracking
198
+ ```
199
+ """
200
+
201
+ state_schema = PlanningState # type: ignore[assignment]
202
+
203
+ def __init__(
204
+ self,
205
+ *,
206
+ system_prompt: str = WRITE_TODOS_SYSTEM_PROMPT,
207
+ tool_description: str = WRITE_TODOS_TOOL_DESCRIPTION,
208
+ ) -> None:
209
+ """Initialize the `TodoListMiddleware` with optional custom prompts.
210
+
211
+ Args:
212
+ system_prompt: Custom system prompt to guide the agent on using the todo
213
+ tool.
214
+ tool_description: Custom description for the `write_todos` tool.
215
+ """
216
+ super().__init__()
217
+ self.system_prompt = system_prompt
218
+ self.tool_description = tool_description
219
+
220
+ self.tools = [
221
+ StructuredTool.from_function(
222
+ name="write_todos",
223
+ description=tool_description,
224
+ func=_write_todos,
225
+ coroutine=_awrite_todos,
226
+ args_schema=WriteTodosInput,
227
+ infer_schema=False,
228
+ )
229
+ ]
230
+
231
+ def wrap_model_call(
232
+ self,
233
+ request: ModelRequest[ContextT],
234
+ handler: Callable[[ModelRequest[ContextT]], ModelResponse[ResponseT]],
235
+ ) -> ModelResponse[ResponseT] | AIMessage:
236
+ """Update the system message to include the todo system prompt.
237
+
238
+ Args:
239
+ request: Model request to execute (includes state and runtime).
240
+ handler: Async callback that executes the model request and returns
241
+ `ModelResponse`.
242
+
243
+ Returns:
244
+ The model call result.
245
+ """
246
+ if request.system_message is not None:
247
+ new_system_content = [
248
+ *request.system_message.content_blocks,
249
+ {"type": "text", "text": f"\n\n{self.system_prompt}"},
250
+ ]
251
+ else:
252
+ new_system_content = [{"type": "text", "text": self.system_prompt}]
253
+ new_system_message = SystemMessage(
254
+ content=cast("list[str | dict[str, str]]", new_system_content)
255
+ )
256
+ return handler(request.override(system_message=new_system_message))
257
+
258
+ async def awrap_model_call(
259
+ self,
260
+ request: ModelRequest[ContextT],
261
+ handler: Callable[[ModelRequest[ContextT]], Awaitable[ModelResponse[ResponseT]]],
262
+ ) -> ModelResponse[ResponseT] | AIMessage:
263
+ """Update the system message to include the todo system prompt.
264
+
265
+ Args:
266
+ request: Model request to execute (includes state and runtime).
267
+ handler: Async callback that executes the model request and returns
268
+ `ModelResponse`.
269
+
270
+ Returns:
271
+ The model call result.
272
+ """
273
+ if request.system_message is not None:
274
+ new_system_content = [
275
+ *request.system_message.content_blocks,
276
+ {"type": "text", "text": f"\n\n{self.system_prompt}"},
277
+ ]
278
+ else:
279
+ new_system_content = [{"type": "text", "text": self.system_prompt}]
280
+ new_system_message = SystemMessage(
281
+ content=cast("list[str | dict[str, str]]", new_system_content)
282
+ )
283
+ return await handler(request.override(system_message=new_system_message))
284
+
285
+ @override
286
+ def after_model(
287
+ self, state: PlanningState[ResponseT], runtime: Runtime[ContextT]
288
+ ) -> dict[str, Any] | None:
289
+ """Check for parallel write_todos tool calls and return errors if detected.
290
+
291
+ The todo list is designed to be updated at most once per model turn. Since
292
+ the `write_todos` tool replaces the entire todo list with each call, making
293
+ multiple parallel calls would create ambiguity about which update should take
294
+ precedence. This method prevents such conflicts by rejecting any response that
295
+ contains multiple write_todos tool calls.
296
+
297
+ Args:
298
+ state: The current agent state containing messages.
299
+ runtime: The CocoGraph runtime instance.
300
+
301
+ Returns:
302
+ A dict containing error ToolMessages for each write_todos call if multiple
303
+ parallel calls are detected, otherwise None to allow normal execution.
304
+ """
305
+ messages = state["messages"]
306
+ if not messages:
307
+ return None
308
+
309
+ last_ai_msg = next((msg for msg in reversed(messages) if isinstance(msg, AIMessage)), None)
310
+ if not last_ai_msg or not last_ai_msg.tool_calls:
311
+ return None
312
+
313
+ # Count write_todos tool calls
314
+ write_todos_calls = [tc for tc in last_ai_msg.tool_calls if tc["name"] == "write_todos"]
315
+
316
+ if len(write_todos_calls) > 1:
317
+ # Create error tool messages for all write_todos calls
318
+ error_messages = [
319
+ ToolMessage(
320
+ content=(
321
+ "Error: The `write_todos` tool should never be called multiple times "
322
+ "in parallel. Please call it only once per model invocation to update "
323
+ "the todo list."
324
+ ),
325
+ tool_call_id=tc["id"],
326
+ status="error",
327
+ )
328
+ for tc in write_todos_calls
329
+ ]
330
+
331
+ # Keep the tool calls in the AI message but return error messages
332
+ # This follows the same pattern as HumanInTheLoopMiddleware
333
+ return {"messages": error_messages}
334
+
335
+ return None
336
+
337
+ @override
338
+ async def aafter_model(
339
+ self, state: PlanningState[ResponseT], runtime: Runtime[ContextT]
340
+ ) -> dict[str, Any] | None:
341
+ """Check for parallel write_todos tool calls and return errors if detected.
342
+
343
+ Async version of `after_model`. The todo list is designed to be updated at
344
+ most once per model turn. Since the `write_todos` tool replaces the entire
345
+ todo list with each call, making multiple parallel calls would create ambiguity
346
+ about which update should take precedence. This method prevents such conflicts
347
+ by rejecting any response that contains multiple write_todos tool calls.
348
+
349
+ Args:
350
+ state: The current agent state containing messages.
351
+ runtime: The CocoGraph runtime instance.
352
+
353
+ Returns:
354
+ A dict containing error ToolMessages for each write_todos call if multiple
355
+ parallel calls are detected, otherwise None to allow normal execution.
356
+ """
357
+ return self.after_model(state, runtime)