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,100 @@
1
+ """Entrypoint to using [middleware](https://docs.langchain.com/oss/python/langchain/middleware) plugins with [Agents](https://docs.langchain.com/oss/python/langchain/agents).""" # noqa: E501
2
+
3
+ from coco_graph.runtime import Runtime
4
+
5
+ from coco.agents.middleware._trace_policy import configure_trace_policy
6
+ from coco.agents.middleware.context_editing import ClearToolUsesEdit, ContextEditingMiddleware
7
+ from coco.agents.middleware.file_search import FilesystemFileSearchMiddleware
8
+ from coco.agents.middleware.human_in_the_loop import (
9
+ HumanInTheLoopMiddleware,
10
+ InterruptOnConfig,
11
+ )
12
+ from coco.agents.middleware.model_call_limit import ModelCallLimitMiddleware
13
+ from coco.agents.middleware.model_fallback import ModelFallbackMiddleware
14
+ from coco.agents.middleware.model_retry import ModelRetryMiddleware
15
+ from coco.agents.middleware.pii import PIIDetectionError, PIIMatch, PIIMiddleware
16
+ from coco.agents.middleware.provider_tool_search import ProviderToolSearchMiddleware
17
+ from coco.agents.middleware.shell_tool import (
18
+ CodexSandboxExecutionPolicy,
19
+ DockerExecutionPolicy,
20
+ HostExecutionPolicy,
21
+ RedactionRule,
22
+ ShellToolMiddleware,
23
+ )
24
+ from coco.agents.middleware.summarization import SummarizationMiddleware, TriggerClause
25
+ from coco.agents.middleware.todo import TodoListMiddleware
26
+ from coco.agents.middleware.tool_call_limit import ToolCallLimitMiddleware
27
+ from coco.agents.middleware.tool_emulator import LLMToolEmulator
28
+ from coco.agents.middleware.tool_error import ToolErrorMiddleware
29
+ from coco.agents.middleware.tool_retry import ToolRetryMiddleware
30
+ from coco.agents.middleware.tool_selection import LLMToolSelectorMiddleware
31
+ from coco.agents.middleware.types import (
32
+ AgentMiddleware,
33
+ AgentState,
34
+ ExtendedModelResponse,
35
+ InputAgentState,
36
+ ModelCallResult,
37
+ ModelRequest,
38
+ ModelResponse,
39
+ OutputAgentState,
40
+ ToolCallRequest,
41
+ TracePolicy,
42
+ after_agent,
43
+ after_model,
44
+ before_agent,
45
+ before_model,
46
+ dynamic_prompt,
47
+ hook_config,
48
+ omit_payload,
49
+ wrap_model_call,
50
+ wrap_tool_call,
51
+ )
52
+
53
+ __all__ = [
54
+ "AgentMiddleware",
55
+ "AgentState",
56
+ "ClearToolUsesEdit",
57
+ "CodexSandboxExecutionPolicy",
58
+ "ContextEditingMiddleware",
59
+ "DockerExecutionPolicy",
60
+ "ExtendedModelResponse",
61
+ "FilesystemFileSearchMiddleware",
62
+ "HostExecutionPolicy",
63
+ "HumanInTheLoopMiddleware",
64
+ "InputAgentState",
65
+ "InterruptOnConfig",
66
+ "LLMToolEmulator",
67
+ "LLMToolSelectorMiddleware",
68
+ "ModelCallLimitMiddleware",
69
+ "ModelCallResult",
70
+ "ModelFallbackMiddleware",
71
+ "ModelRequest",
72
+ "ModelResponse",
73
+ "ModelRetryMiddleware",
74
+ "OutputAgentState",
75
+ "PIIDetectionError",
76
+ "PIIMatch",
77
+ "PIIMiddleware",
78
+ "ProviderToolSearchMiddleware",
79
+ "RedactionRule",
80
+ "Runtime",
81
+ "ShellToolMiddleware",
82
+ "SummarizationMiddleware",
83
+ "TodoListMiddleware",
84
+ "ToolCallLimitMiddleware",
85
+ "ToolCallRequest",
86
+ "ToolErrorMiddleware",
87
+ "ToolRetryMiddleware",
88
+ "TracePolicy",
89
+ "TriggerClause",
90
+ "after_agent",
91
+ "after_model",
92
+ "before_agent",
93
+ "before_model",
94
+ "configure_trace_policy",
95
+ "dynamic_prompt",
96
+ "hook_config",
97
+ "omit_payload",
98
+ "wrap_model_call",
99
+ "wrap_tool_call",
100
+ ]
@@ -0,0 +1,385 @@
1
+ """Execution policies for the persistent shell middleware."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import abc
6
+ import json
7
+ import os
8
+ import shutil
9
+ import subprocess
10
+ import sys
11
+ import typing
12
+ from collections.abc import Mapping, Sequence
13
+ from dataclasses import dataclass, field
14
+ from pathlib import Path
15
+
16
+ try: # pragma: no cover - optional dependency on POSIX platforms
17
+ import resource
18
+
19
+ _HAS_RESOURCE = True
20
+ except ImportError: # pragma: no cover - non-POSIX systems
21
+ _HAS_RESOURCE = False
22
+
23
+
24
+ SHELL_TEMP_PREFIX = "trustcoco-shell-"
25
+
26
+
27
+ def _launch_subprocess(
28
+ command: Sequence[str],
29
+ *,
30
+ env: Mapping[str, str],
31
+ cwd: Path,
32
+ preexec_fn: typing.Callable[[], None] | None,
33
+ start_new_session: bool,
34
+ ) -> subprocess.Popen[str]:
35
+ return subprocess.Popen( # noqa: S603
36
+ list(command),
37
+ stdin=subprocess.PIPE,
38
+ stdout=subprocess.PIPE,
39
+ stderr=subprocess.PIPE,
40
+ cwd=cwd,
41
+ text=True,
42
+ encoding="utf-8",
43
+ errors="replace",
44
+ bufsize=1,
45
+ env=env,
46
+ preexec_fn=preexec_fn, # noqa: PLW1509
47
+ start_new_session=start_new_session,
48
+ )
49
+
50
+
51
+ if typing.TYPE_CHECKING:
52
+ from collections.abc import Mapping, Sequence
53
+ from pathlib import Path
54
+
55
+
56
+ @dataclass
57
+ class BaseExecutionPolicy(abc.ABC):
58
+ """Configuration contract for persistent shell sessions.
59
+
60
+ Concrete subclasses encapsulate how a shell process is launched and constrained.
61
+
62
+ Each policy documents its security guarantees and the operating environments in
63
+ which it is appropriate. Use `HostExecutionPolicy` for trusted, same-host execution;
64
+ `CodexSandboxExecutionPolicy` when the Codex CLI sandbox is available and you want
65
+ additional syscall restrictions; and `DockerExecutionPolicy` for container-level
66
+ isolation using Docker.
67
+ """
68
+
69
+ command_timeout: float = 30.0
70
+ startup_timeout: float = 30.0
71
+ termination_timeout: float = 10.0
72
+ max_output_lines: int = 100
73
+ max_output_bytes: int | None = None
74
+
75
+ def __post_init__(self) -> None:
76
+ if self.max_output_lines <= 0:
77
+ msg = "max_output_lines must be positive."
78
+ raise ValueError(msg)
79
+
80
+ @abc.abstractmethod
81
+ def spawn(
82
+ self,
83
+ *,
84
+ workspace: Path,
85
+ env: Mapping[str, str],
86
+ command: Sequence[str],
87
+ ) -> subprocess.Popen[str]:
88
+ """Launch the persistent shell process."""
89
+
90
+
91
+ @dataclass
92
+ class HostExecutionPolicy(BaseExecutionPolicy):
93
+ """Run the shell directly on the host process.
94
+
95
+ This policy is best suited for trusted or single-tenant environments (CI jobs,
96
+ developer workstations, pre-sandboxed containers) where the agent must access the
97
+ host filesystem and tooling without additional isolation. Enforces optional CPU and
98
+ memory limits to prevent runaway commands but offers **no** filesystem or network
99
+ sandboxing; commands can modify anything the process user can reach.
100
+
101
+ On Linux platforms resource limits are applied with `resource.prlimit` after the
102
+ shell starts. On macOS, where `prlimit` is unavailable, limits are set in a
103
+ `preexec_fn` before `exec`. In both cases the shell runs in its own process group
104
+ so timeouts can terminate the full subtree.
105
+ """
106
+
107
+ cpu_time_seconds: int | None = None
108
+ memory_bytes: int | None = None
109
+ create_process_group: bool = True
110
+
111
+ _limits_requested: bool = field(init=False, repr=False, default=False)
112
+
113
+ def __post_init__(self) -> None:
114
+ super().__post_init__()
115
+ if self.cpu_time_seconds is not None and self.cpu_time_seconds <= 0:
116
+ msg = "cpu_time_seconds must be positive if provided."
117
+ raise ValueError(msg)
118
+ if self.memory_bytes is not None and self.memory_bytes <= 0:
119
+ msg = "memory_bytes must be positive if provided."
120
+ raise ValueError(msg)
121
+ self._limits_requested = any(
122
+ value is not None for value in (self.cpu_time_seconds, self.memory_bytes)
123
+ )
124
+ if self._limits_requested and not _HAS_RESOURCE:
125
+ msg = (
126
+ "HostExecutionPolicy cpu/memory limits require the Python 'resource' module. "
127
+ "Either remove the limits or run on a POSIX platform."
128
+ )
129
+ raise RuntimeError(msg)
130
+
131
+ def spawn(
132
+ self,
133
+ *,
134
+ workspace: Path,
135
+ env: Mapping[str, str],
136
+ command: Sequence[str],
137
+ ) -> subprocess.Popen[str]:
138
+ process = _launch_subprocess(
139
+ list(command),
140
+ env=env,
141
+ cwd=workspace,
142
+ preexec_fn=self._create_preexec_fn(),
143
+ start_new_session=self.create_process_group,
144
+ )
145
+ self._apply_post_spawn_limits(process)
146
+ return process
147
+
148
+ def _create_preexec_fn(self) -> typing.Callable[[], None] | None:
149
+ if not self._limits_requested or self._can_use_prlimit():
150
+ return None
151
+
152
+ def _configure() -> None: # pragma: no cover - depends on OS
153
+ if self.cpu_time_seconds is not None:
154
+ limit = (self.cpu_time_seconds, self.cpu_time_seconds)
155
+ resource.setrlimit(resource.RLIMIT_CPU, limit)
156
+ if self.memory_bytes is not None:
157
+ limit = (self.memory_bytes, self.memory_bytes)
158
+ if hasattr(resource, "RLIMIT_AS"):
159
+ resource.setrlimit(resource.RLIMIT_AS, limit)
160
+ elif hasattr(resource, "RLIMIT_DATA"):
161
+ resource.setrlimit(resource.RLIMIT_DATA, limit)
162
+
163
+ return _configure
164
+
165
+ def _apply_post_spawn_limits(self, process: subprocess.Popen[str]) -> None:
166
+ if not self._limits_requested or not self._can_use_prlimit():
167
+ return
168
+ if not _HAS_RESOURCE: # pragma: no cover - defensive
169
+ return
170
+ pid = process.pid
171
+ try:
172
+ prlimit = typing.cast("typing.Any", resource).prlimit
173
+ if self.cpu_time_seconds is not None:
174
+ prlimit(pid, resource.RLIMIT_CPU, (self.cpu_time_seconds, self.cpu_time_seconds))
175
+ if self.memory_bytes is not None:
176
+ limit = (self.memory_bytes, self.memory_bytes)
177
+ if hasattr(resource, "RLIMIT_AS"):
178
+ prlimit(pid, resource.RLIMIT_AS, limit)
179
+ elif hasattr(resource, "RLIMIT_DATA"):
180
+ prlimit(pid, resource.RLIMIT_DATA, limit)
181
+ except OSError as exc: # pragma: no cover - depends on platform support
182
+ msg = "Failed to apply resource limits via prlimit."
183
+ raise RuntimeError(msg) from exc
184
+
185
+ @staticmethod
186
+ def _can_use_prlimit() -> bool:
187
+ return _HAS_RESOURCE and hasattr(resource, "prlimit") and sys.platform.startswith("linux")
188
+
189
+
190
+ @dataclass
191
+ class CodexSandboxExecutionPolicy(BaseExecutionPolicy):
192
+ """Launch the shell through the Codex CLI sandbox.
193
+
194
+ Ideal when you have the Codex CLI installed and want the additional syscall and
195
+ filesystem restrictions provided by Anthropic's Seatbelt (macOS) or Landlock/seccomp
196
+ (Linux) profiles. Commands still run on the host, but within the sandbox requested by
197
+ the CLI. If the Codex binary is unavailable or the runtime lacks the required
198
+ kernel features (e.g., Landlock inside some containers), process startup fails with a
199
+ `RuntimeError`.
200
+
201
+ Configure sandbox behavior via `config_overrides` to align with your Codex CLI
202
+ profile. This policy does not add its own resource limits; combine it with
203
+ host-level guards (cgroups, container resource limits) as needed.
204
+ """
205
+
206
+ binary: str = "codex"
207
+ platform: typing.Literal["auto", "macos", "linux"] = "auto"
208
+ config_overrides: Mapping[str, typing.Any] = field(default_factory=dict)
209
+
210
+ def spawn(
211
+ self,
212
+ *,
213
+ workspace: Path,
214
+ env: Mapping[str, str],
215
+ command: Sequence[str],
216
+ ) -> subprocess.Popen[str]:
217
+ full_command = self._build_command(command)
218
+ return _launch_subprocess(
219
+ full_command,
220
+ env=env,
221
+ cwd=workspace,
222
+ preexec_fn=None,
223
+ start_new_session=True,
224
+ )
225
+
226
+ def _build_command(self, command: Sequence[str]) -> list[str]:
227
+ binary = self._resolve_binary()
228
+ platform_arg = self._determine_platform()
229
+ full_command: list[str] = [binary, "sandbox", platform_arg]
230
+ for key, value in sorted(dict(self.config_overrides).items()):
231
+ full_command.extend(["-c", f"{key}={self._format_override(value)}"])
232
+ full_command.append("--")
233
+ full_command.extend(command)
234
+ return full_command
235
+
236
+ def _resolve_binary(self) -> str:
237
+ path = shutil.which(self.binary)
238
+ if path is None:
239
+ msg = (
240
+ "Codex sandbox policy requires the '%s' CLI to be installed and available on PATH."
241
+ )
242
+ raise RuntimeError(msg % self.binary)
243
+ return path
244
+
245
+ def _determine_platform(self) -> str:
246
+ if self.platform != "auto":
247
+ return self.platform
248
+ if sys.platform.startswith("linux"):
249
+ return "linux"
250
+ if sys.platform == "darwin": # type: ignore[unreachable, unused-ignore]
251
+ return "macos"
252
+ msg = ( # type: ignore[unreachable, unused-ignore]
253
+ "Codex sandbox policy could not determine a supported platform; "
254
+ "set 'platform' explicitly."
255
+ )
256
+ raise RuntimeError(msg)
257
+
258
+ @staticmethod
259
+ def _format_override(value: typing.Any) -> str:
260
+ try:
261
+ return json.dumps(value)
262
+ except TypeError:
263
+ return str(value)
264
+
265
+
266
+ @dataclass
267
+ class DockerExecutionPolicy(BaseExecutionPolicy):
268
+ """Run the shell inside a dedicated Docker container.
269
+
270
+ Choose this policy when commands originate from untrusted users or you require
271
+ strong isolation between sessions. By default the workspace is bind-mounted only
272
+ when it refers to an existing non-temporary directory; ephemeral sessions run
273
+ without a mount to minimise host exposure. The container's network namespace is
274
+ disabled by default (`--network none`) and you can enable further hardening via
275
+ `read_only_rootfs` and `user`.
276
+
277
+ The security guarantees depend on your Docker daemon configuration. Run the agent on
278
+ a host where Docker is locked down (rootless mode, AppArmor/SELinux, etc.) and
279
+ review any additional volumes or capabilities passed through `extra_run_args`. The
280
+ default image is `python:3.12-alpine3.19`; supply a custom image if you need
281
+ preinstalled tooling.
282
+ """
283
+
284
+ binary: str = "docker"
285
+ image: str = "python:3.12-alpine3.19"
286
+ remove_container_on_exit: bool = True
287
+ network_enabled: bool = False
288
+ extra_run_args: Sequence[str] | None = None
289
+ memory_bytes: int | None = None
290
+ cpu_time_seconds: typing.Any | None = None
291
+ cpus: str | None = None
292
+ read_only_rootfs: bool = False
293
+ user: str | None = None
294
+
295
+ def __post_init__(self) -> None:
296
+ super().__post_init__()
297
+ if self.memory_bytes is not None and self.memory_bytes <= 0:
298
+ msg = "memory_bytes must be positive if provided."
299
+ raise ValueError(msg)
300
+ if self.cpu_time_seconds is not None:
301
+ msg = (
302
+ "DockerExecutionPolicy does not support cpu_time_seconds; configure CPU limits "
303
+ "using Docker run options such as '--cpus'."
304
+ )
305
+ raise RuntimeError(msg)
306
+ if self.cpus is not None and not self.cpus.strip():
307
+ msg = "cpus must be a non-empty string when provided."
308
+ raise ValueError(msg)
309
+ if self.user is not None and not self.user.strip():
310
+ msg = "user must be a non-empty string when provided."
311
+ raise ValueError(msg)
312
+ self.extra_run_args = tuple(self.extra_run_args or ())
313
+
314
+ def spawn(
315
+ self,
316
+ *,
317
+ workspace: Path,
318
+ env: Mapping[str, str],
319
+ command: Sequence[str],
320
+ ) -> subprocess.Popen[str]:
321
+ full_command = self._build_command(workspace, env, command)
322
+ host_env = os.environ.copy()
323
+ return _launch_subprocess(
324
+ full_command,
325
+ env=host_env,
326
+ cwd=workspace,
327
+ preexec_fn=None,
328
+ start_new_session=True,
329
+ )
330
+
331
+ def _build_command(
332
+ self,
333
+ workspace: Path,
334
+ env: Mapping[str, str],
335
+ command: Sequence[str],
336
+ ) -> list[str]:
337
+ binary = self._resolve_binary()
338
+ full_command: list[str] = [binary, "run", "-i"]
339
+ if self.remove_container_on_exit:
340
+ full_command.append("--rm")
341
+ if not self.network_enabled:
342
+ full_command.extend(["--network", "none"])
343
+ if self.memory_bytes is not None:
344
+ full_command.extend(["--memory", str(self.memory_bytes)])
345
+ if self._should_mount_workspace(workspace):
346
+ host_path = str(workspace)
347
+ full_command.extend(["-v", f"{host_path}:{host_path}"])
348
+ full_command.extend(["-w", host_path])
349
+ else:
350
+ full_command.extend(["-w", "/"])
351
+ if self.read_only_rootfs:
352
+ full_command.append("--read-only")
353
+ for key, value in env.items():
354
+ full_command.extend(["-e", f"{key}={value}"])
355
+ if self.cpus is not None:
356
+ full_command.extend(["--cpus", self.cpus])
357
+ if self.user is not None:
358
+ full_command.extend(["--user", self.user])
359
+ if self.extra_run_args:
360
+ full_command.extend(self.extra_run_args)
361
+ full_command.append(self.image)
362
+ full_command.extend(command)
363
+ return full_command
364
+
365
+ @staticmethod
366
+ def _should_mount_workspace(workspace: Path) -> bool:
367
+ return not workspace.name.startswith(SHELL_TEMP_PREFIX)
368
+
369
+ def _resolve_binary(self) -> str:
370
+ path = shutil.which(self.binary)
371
+ if path is None:
372
+ msg = (
373
+ "Docker execution policy requires the '%s' CLI to be installed"
374
+ " and available on PATH."
375
+ )
376
+ raise RuntimeError(msg % self.binary)
377
+ return path
378
+
379
+
380
+ __all__ = [
381
+ "BaseExecutionPolicy",
382
+ "CodexSandboxExecutionPolicy",
383
+ "DockerExecutionPolicy",
384
+ "HostExecutionPolicy",
385
+ ]