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,84 @@
1
+ """Anthropic-specific middleware for the Claude bash tool."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Awaitable, Callable
6
+ from typing import Any
7
+
8
+ from coco.agents.middleware.shell_tool import ShellToolMiddleware
9
+ from coco.agents.middleware.types import (
10
+ ModelRequest,
11
+ ModelResponse,
12
+ )
13
+
14
+ # Tool type constants for Anthropic
15
+ BASH_TOOL_TYPE = "bash_20250124"
16
+ BASH_TOOL_NAME = "bash"
17
+
18
+
19
+ class ClaudeBashToolMiddleware(ShellToolMiddleware):
20
+ """Middleware that exposes Anthropic's native bash tool to models."""
21
+
22
+ def __init__(
23
+ self,
24
+ workspace_root: str | None = None,
25
+ *,
26
+ startup_commands: tuple[str, ...] | list[str] | str | None = None,
27
+ shutdown_commands: tuple[str, ...] | list[str] | str | None = None,
28
+ execution_policy: Any | None = None,
29
+ redaction_rules: tuple[Any, ...] | list[Any] | None = None,
30
+ tool_description: str | None = None,
31
+ env: dict[str, Any] | None = None,
32
+ ) -> None:
33
+ """Initialize middleware for Claude's native bash tool.
34
+
35
+ Args:
36
+ workspace_root: Base directory for the shell session.
37
+
38
+ If omitted, a temporary directory is created.
39
+ startup_commands: Optional commands executed after the session starts.
40
+ shutdown_commands: Optional commands executed before session shutdown.
41
+ execution_policy: Execution policy controlling timeouts and limits.
42
+ redaction_rules: Optional redaction rules to sanitize output.
43
+ tool_description: Optional override for tool description.
44
+ env: Optional environment variables for the shell session.
45
+ """
46
+ super().__init__(
47
+ workspace_root=workspace_root,
48
+ startup_commands=startup_commands,
49
+ shutdown_commands=shutdown_commands,
50
+ execution_policy=execution_policy,
51
+ redaction_rules=redaction_rules,
52
+ tool_description=tool_description,
53
+ tool_name=BASH_TOOL_NAME,
54
+ shell_command=("/bin/bash",),
55
+ env=env,
56
+ )
57
+ # Parent class now creates the tool with name "bash" via tool_name parameter
58
+
59
+ def wrap_model_call(
60
+ self,
61
+ request: ModelRequest,
62
+ handler: Callable[[ModelRequest], ModelResponse],
63
+ ) -> ModelResponse:
64
+ """Replace parent's shell tool with Claude's bash descriptor."""
65
+ filtered = [
66
+ t for t in request.tools if getattr(t, "name", None) != BASH_TOOL_NAME
67
+ ]
68
+ tools = [*filtered, {"type": BASH_TOOL_TYPE, "name": BASH_TOOL_NAME}]
69
+ return handler(request.override(tools=tools))
70
+
71
+ async def awrap_model_call(
72
+ self,
73
+ request: ModelRequest,
74
+ handler: Callable[[ModelRequest], Awaitable[ModelResponse]],
75
+ ) -> ModelResponse:
76
+ """Async: replace parent's shell tool with Claude's bash descriptor."""
77
+ filtered = [
78
+ t for t in request.tools if getattr(t, "name", None) != BASH_TOOL_NAME
79
+ ]
80
+ tools = [*filtered, {"type": BASH_TOOL_TYPE, "name": BASH_TOOL_NAME}]
81
+ return await handler(request.override(tools=tools))
82
+
83
+
84
+ __all__ = ["ClaudeBashToolMiddleware"]
@@ -0,0 +1,360 @@
1
+ """File search middleware for Anthropic text editor and memory tools.
2
+
3
+ This module provides Glob and Grep search tools that operate on files stored
4
+ in state or filesystem.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import fnmatch
10
+ import re
11
+ from pathlib import Path, PurePosixPath
12
+ from typing import TYPE_CHECKING, Literal, cast
13
+
14
+ if TYPE_CHECKING:
15
+ from typing import Any
16
+
17
+ from coco.agents.middleware.types import AgentMiddleware
18
+ from coco.tools import ToolRuntime, tool
19
+
20
+ from coco_anthropic.middleware.anthropic_tools import AnthropicToolsState
21
+
22
+
23
+ def _expand_include_patterns(pattern: str) -> list[str] | None:
24
+ """Expand brace patterns like `*.{py,pyi}` into a list of globs."""
25
+ if "}" in pattern and "{" not in pattern:
26
+ return None
27
+
28
+ expanded: list[str] = []
29
+
30
+ def _expand(current: str) -> None:
31
+ start = current.find("{")
32
+ if start == -1:
33
+ expanded.append(current)
34
+ return
35
+
36
+ end = current.find("}", start)
37
+ if end == -1:
38
+ msg = f"Unbalanced brace in pattern: '{current}' is missing a '}}'."
39
+ raise ValueError(msg)
40
+
41
+ prefix = current[:start]
42
+ suffix = current[end + 1 :]
43
+ inner = current[start + 1 : end]
44
+ if not inner:
45
+ msg = f"Empty brace expansion in pattern: '{current}'."
46
+ raise ValueError(msg)
47
+
48
+ for option in inner.split(","):
49
+ _expand(prefix + option + suffix)
50
+
51
+ try:
52
+ _expand(pattern)
53
+ except ValueError:
54
+ return None
55
+
56
+ return expanded
57
+
58
+
59
+ def _is_valid_include_pattern(pattern: str) -> bool:
60
+ """Validate glob pattern used for include filters."""
61
+ if not pattern:
62
+ return False
63
+
64
+ if any(char in pattern for char in ("\x00", "\n", "\r")):
65
+ return False
66
+
67
+ expanded = _expand_include_patterns(pattern)
68
+ if expanded is None:
69
+ return False
70
+
71
+ try:
72
+ for candidate in expanded:
73
+ re.compile(fnmatch.translate(candidate))
74
+ except re.error:
75
+ return False
76
+
77
+ return True
78
+
79
+
80
+ def _match_include_pattern(basename: str, pattern: str) -> bool:
81
+ """Return `True` if the basename matches the include pattern."""
82
+ expanded = _expand_include_patterns(pattern)
83
+ if not expanded:
84
+ return False
85
+
86
+ return any(fnmatch.fnmatch(basename, candidate) for candidate in expanded)
87
+
88
+
89
+ class StateFileSearchMiddleware(AgentMiddleware):
90
+ """Provides Glob and Grep search over state-based files.
91
+
92
+ This middleware adds two tools that search through virtual files in state:
93
+
94
+ - Glob: Fast file pattern matching by file path
95
+ - Grep: Fast content search using regular expressions
96
+
97
+ Example:
98
+ ```python
99
+ from coco.agents import create_agent
100
+ from coco.agents.middleware import (
101
+ StateTextEditorToolMiddleware,
102
+ StateFileSearchMiddleware,
103
+ )
104
+
105
+ agent = create_agent(
106
+ model=model,
107
+ tools=[],
108
+ middleware=[
109
+ StateTextEditorToolMiddleware(),
110
+ StateFileSearchMiddleware(),
111
+ ],
112
+ )
113
+ ```
114
+ """
115
+
116
+ state_schema = AnthropicToolsState
117
+
118
+ def __init__(
119
+ self,
120
+ *,
121
+ state_key: str = "text_editor_files",
122
+ ) -> None:
123
+ """Initialize the search middleware.
124
+
125
+ Args:
126
+ state_key: State key to search
127
+
128
+ Use `'memory_files'` to search memory tool files.
129
+ """
130
+ self.state_key = state_key
131
+
132
+ # Create tool instances
133
+ @tool
134
+ def glob_search( # noqa: D417
135
+ runtime: ToolRuntime[None, AnthropicToolsState],
136
+ pattern: str,
137
+ path: str = "/",
138
+ ) -> str:
139
+ """Fast file pattern matching tool that works with any codebase size.
140
+
141
+ Supports glob patterns like `**/*.js` or `src/**/*.ts`.
142
+
143
+ Returns matching file paths sorted by modification time.
144
+
145
+ Use this tool when you need to find files by name patterns.
146
+
147
+ Args:
148
+ pattern: The glob pattern to match files against.
149
+ path: The directory to search in.
150
+
151
+ If not specified, searches from root.
152
+
153
+ Returns:
154
+ Newline-separated list of matching file paths, sorted by modification
155
+ time (most recently modified first).
156
+
157
+ Returns `'No files found'` if no matches.
158
+ """
159
+ return self._handle_glob_search(pattern, path, runtime.state)
160
+
161
+ @tool
162
+ def grep_search( # noqa: D417
163
+ runtime: ToolRuntime[None, AnthropicToolsState],
164
+ pattern: str,
165
+ path: str = "/",
166
+ include: str | None = None,
167
+ output_mode: Literal[
168
+ "files_with_matches", "content", "count"
169
+ ] = "files_with_matches",
170
+ ) -> str:
171
+ """Fast content search tool that works with any codebase size.
172
+
173
+ Searches file contents using regular expressions.
174
+
175
+ Supports full regex syntax and filters files by pattern with the include
176
+ parameter.
177
+
178
+ Args:
179
+ pattern: The regular expression pattern to search for in file contents.
180
+ path: The directory to search in. If not specified, searches from root.
181
+ include: File pattern to filter (e.g., `'*.js'`, `'*.{ts,tsx}'`).
182
+ output_mode: Output format.
183
+
184
+ Options:
185
+
186
+ - `'files_with_matches'`: Only file paths containing matches
187
+ - `'content'`: Matching lines with file:line:content format
188
+ - `'count'`: Count of matches per file
189
+
190
+ Returns:
191
+ Search results formatted according to `output_mode`.
192
+
193
+ Returns `'No matches found'` if no results.
194
+ """
195
+ return self._handle_grep_search(
196
+ pattern, path, include, output_mode, runtime.state
197
+ )
198
+
199
+ self.glob_search = glob_search
200
+ self.grep_search = grep_search
201
+ self.tools = [glob_search, grep_search]
202
+
203
+ def _handle_glob_search(
204
+ self,
205
+ pattern: str,
206
+ path: str,
207
+ state: AnthropicToolsState,
208
+ ) -> str:
209
+ """Handle glob search operation.
210
+
211
+ Args:
212
+ pattern: The glob pattern to match files against.
213
+ path: The directory to search in.
214
+ state: The current agent state.
215
+
216
+ Returns:
217
+ Newline-separated list of matching file paths, sorted by modification
218
+ time (most recently modified first).
219
+
220
+ Returns `'No files found'` if no matches.
221
+ """
222
+ # Normalize base path
223
+ base_path = path if path.startswith("/") else "/" + path
224
+
225
+ # Get files from state
226
+ files = cast("dict[str, Any]", state.get(self.state_key, {}))
227
+
228
+ # Match files
229
+ matches = []
230
+ for file_path, file_data in files.items():
231
+ if file_path.startswith(base_path):
232
+ # Get relative path from base
233
+ if base_path == "/":
234
+ relative = file_path[1:] # Remove leading /
235
+ elif file_path == base_path:
236
+ relative = Path(file_path).name
237
+ elif file_path.startswith(base_path + "/"):
238
+ relative = file_path[len(base_path) + 1 :]
239
+ else:
240
+ continue
241
+
242
+ # Match against pattern
243
+ # Handle ** pattern which requires special care
244
+ # PurePosixPath.match doesn't match single-level paths
245
+ # against **/pattern
246
+ is_match = PurePosixPath(relative).match(pattern)
247
+ if not is_match and pattern.startswith("**/"):
248
+ # Also try matching without the **/ prefix for files in base dir
249
+ is_match = PurePosixPath(relative).match(pattern[3:])
250
+
251
+ if is_match:
252
+ matches.append((file_path, file_data["modified_at"]))
253
+
254
+ if not matches:
255
+ return "No files found"
256
+
257
+ # Sort by modification time
258
+ matches.sort(key=lambda x: x[1], reverse=True)
259
+ file_paths = [path for path, _ in matches]
260
+
261
+ return "\n".join(file_paths)
262
+
263
+ def _handle_grep_search(
264
+ self,
265
+ pattern: str,
266
+ path: str,
267
+ include: str | None,
268
+ output_mode: str,
269
+ state: AnthropicToolsState,
270
+ ) -> str:
271
+ """Handle grep search operation.
272
+
273
+ Args:
274
+ pattern: The regular expression pattern to search for in file contents.
275
+ path: The directory to search in.
276
+ include: File pattern to filter (e.g., `'*.js'`, `'*.{ts,tsx}'`).
277
+ output_mode: Output format.
278
+ state: The current agent state.
279
+
280
+ Returns:
281
+ Search results formatted according to `output_mode`.
282
+
283
+ Returns `'No matches found'` if no results.
284
+ """
285
+ # Normalize base path
286
+ base_path = path if path.startswith("/") else "/" + path
287
+
288
+ # Compile regex pattern (for validation)
289
+ try:
290
+ regex = re.compile(pattern)
291
+ except re.error as e:
292
+ return f"Invalid regex pattern: {e}"
293
+
294
+ if include and not _is_valid_include_pattern(include):
295
+ return "Invalid include pattern"
296
+
297
+ # Search files
298
+ files = cast("dict[str, Any]", state.get(self.state_key, {}))
299
+ results: dict[str, list[tuple[int, str]]] = {}
300
+
301
+ for file_path, file_data in files.items():
302
+ if (
303
+ base_path != "/" # noqa: PLR1714
304
+ and file_path != base_path
305
+ and not file_path.startswith(base_path + "/")
306
+ ):
307
+ continue
308
+
309
+ # Check include filter
310
+ if include:
311
+ basename = Path(file_path).name
312
+ if not _match_include_pattern(basename, include):
313
+ continue
314
+
315
+ # Search file content
316
+ for line_num, line in enumerate(file_data["content"], 1):
317
+ if regex.search(line):
318
+ if file_path not in results:
319
+ results[file_path] = []
320
+ results[file_path].append((line_num, line))
321
+
322
+ if not results:
323
+ return "No matches found"
324
+
325
+ # Format output based on mode
326
+ return self._format_grep_results(results, output_mode)
327
+
328
+ def _format_grep_results(
329
+ self,
330
+ results: dict[str, list[tuple[int, str]]],
331
+ output_mode: str,
332
+ ) -> str:
333
+ """Format grep results based on output mode."""
334
+ if output_mode == "files_with_matches":
335
+ # Just return file paths
336
+ return "\n".join(sorted(results.keys()))
337
+
338
+ if output_mode == "content":
339
+ # Return file:line:content format
340
+ lines = []
341
+ for file_path in sorted(results.keys()):
342
+ for line_num, line in results[file_path]:
343
+ lines.append(f"{file_path}:{line_num}:{line}")
344
+ return "\n".join(lines)
345
+
346
+ if output_mode == "count":
347
+ # Return file:count format
348
+ lines = []
349
+ for file_path in sorted(results.keys()):
350
+ count = len(results[file_path])
351
+ lines.append(f"{file_path}:{count}")
352
+ return "\n".join(lines)
353
+
354
+ # Default to files_with_matches
355
+ return "\n".join(sorted(results.keys()))
356
+
357
+
358
+ __all__ = [
359
+ "StateFileSearchMiddleware",
360
+ ]