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,307 @@
1
+ """Provider-side tool search middleware."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import warnings
6
+ from collections.abc import Mapping
7
+ from typing import TYPE_CHECKING, Any, TypeAlias
8
+
9
+ from coco_core.tools import BaseTool
10
+ from typing_extensions import NotRequired, TypedDict
11
+
12
+ from coco.chat_models.base import _attempt_infer_model_provider
13
+
14
+ if TYPE_CHECKING:
15
+ from collections.abc import Awaitable, Callable
16
+
17
+ from coco_core.language_models.chat_models import BaseChatModel
18
+ from coco_core.messages import AIMessage
19
+
20
+ from coco.agents.middleware.types import (
21
+ AgentMiddleware,
22
+ AgentState,
23
+ ContextT,
24
+ ModelRequest,
25
+ ModelResponse,
26
+ ResponseT,
27
+ )
28
+
29
+ ToolIdentifier: TypeAlias = str | BaseTool
30
+ """Tool name or tool instance that can be deferred behind provider tool search."""
31
+
32
+
33
+ class _ServerToolSearchSpec(TypedDict):
34
+ """Provider-native tool search tool descriptor sent to the model as a tool."""
35
+
36
+ type: str
37
+ name: NotRequired[str]
38
+
39
+
40
+ # Provider-native tool search descriptors keyed by normalized provider name (see
41
+ # `_normalize_provider`). This mapping is the single source of truth for which
42
+ # providers support server-side tool search.
43
+ #
44
+ # The identifiers below are version-stamped by the providers and can go stale;
45
+ # re-verify against provider docs when updating:
46
+ # - Anthropic: https://docs.langchain.com/oss/python/integrations/chat/anthropic#tool-search
47
+ # - OpenAI: server-side `tool_search` tool.
48
+ _SERVER_TOOL_SEARCH_TOOLS: dict[str, _ServerToolSearchSpec] = {
49
+ "anthropic": {
50
+ "type": "tool_search_tool_bm25_20251119",
51
+ "name": "tool_search_tool_bm25",
52
+ },
53
+ "openai": {"type": "tool_search"},
54
+ }
55
+
56
+
57
+ class ProviderToolSearchMiddleware(AgentMiddleware[AgentState[ResponseT], ContextT, ResponseT]):
58
+ """Defer selected tools behind provider-native tool search.
59
+
60
+ Instead of sending every tool schema on every turn, this middleware marks
61
+ selected tools as deferred (via `extras["defer_loading"]`) and injects the
62
+ provider's server-side tool search tool. The provider then retrieves the
63
+ full schema of a deferred tool only when the model needs it, which keeps the
64
+ request payload small when many tools are bound.
65
+
66
+ A tool is deferred when its name (or instance) is passed in `searchable_tools`,
67
+ or when it already carries `extras["defer_loading"] is True`.
68
+
69
+ Only providers with server-side tool search are supported (currently
70
+ Anthropic and OpenAI). The provider is inferred from the bound model.
71
+
72
+ !!! warning
73
+
74
+ This relies on provider-native tool search and only takes effect for
75
+ supported providers. If a tool is deferred but the model's provider
76
+ cannot be identified or does not support tool search, the model call
77
+ raises `ValueError`. When no tool is deferred, the middleware passes the
78
+ request through unchanged regardless of provider.
79
+
80
+ Example:
81
+ ```python
82
+ from coco.agents import create_agent
83
+ from coco.agents.middleware import ProviderToolSearchMiddleware
84
+
85
+ agent = create_agent(
86
+ "anthropic:claude-opus-4-8",
87
+ tools=[get_weather, send_email, lookup_order],
88
+ middleware=[ProviderToolSearchMiddleware(searchable_tools=["lookup_order"])],
89
+ )
90
+ ```
91
+ """
92
+
93
+ def __init__(self, *, searchable_tools: list[ToolIdentifier] | None = None) -> None:
94
+ """Initialize provider-side tool search.
95
+
96
+ Args:
97
+ searchable_tools: Tools or tool names to defer behind provider-native
98
+ tool search.
99
+ """
100
+ super().__init__()
101
+ self.searchable_tool_names = _to_tool_names(searchable_tools)
102
+
103
+ def _prepare_request(self, request: ModelRequest[ContextT]) -> ModelRequest[ContextT]:
104
+ """Prepare a model request with deferred tools and provider search.
105
+
106
+ Validates that every name in `searchable_tools` is bound to the model,
107
+ then (only when at least one tool is deferred) resolves the model's
108
+ provider and injects the provider-native tool search tool. Requests with
109
+ no deferred tools pass through unchanged.
110
+
111
+ Args:
112
+ request: Model request to prepare.
113
+
114
+ Returns:
115
+ The original request when nothing is deferred, otherwise a new
116
+ request with deferred tools and the provider search tool appended.
117
+
118
+ Raises:
119
+ ValueError: If `searchable_tools` references a tool not bound to the
120
+ model, or if a tool is deferred but the model's provider cannot
121
+ be identified or does not support server-side tool search.
122
+ """
123
+ tools = request.tools
124
+ if self.searchable_tool_names:
125
+ available = {tool.name for tool in tools if isinstance(tool, BaseTool)}
126
+ unknown = sorted(self.searchable_tool_names - available)
127
+ if unknown:
128
+ msg = (
129
+ "ProviderToolSearchMiddleware: searchable_tools references "
130
+ f"tool(s) not bound to the model: {', '.join(unknown)}"
131
+ )
132
+ raise ValueError(msg)
133
+
134
+ if not any(_is_deferred_tool(tool, self.searchable_tool_names) for tool in tools):
135
+ return request
136
+
137
+ provider = _get_model_provider(request.model, request.runtime)
138
+ if provider is None:
139
+ msg = (
140
+ "ProviderToolSearchMiddleware could not determine the provider for "
141
+ f"model {request.model.__class__.__name__!r}; server-side tool search "
142
+ f"supports: {', '.join(sorted(_SERVER_TOOL_SEARCH_TOOLS))}"
143
+ )
144
+ raise ValueError(msg)
145
+ if provider not in _SERVER_TOOL_SEARCH_TOOLS:
146
+ msg = (
147
+ "ProviderToolSearchMiddleware requires a provider with server-side "
148
+ f"tool search, but got {provider!r}; supported providers: "
149
+ f"{', '.join(sorted(_SERVER_TOOL_SEARCH_TOOLS))}"
150
+ )
151
+ raise ValueError(msg)
152
+
153
+ bound_tools = [_defer_tool_if_needed(tool, self.searchable_tool_names) for tool in tools]
154
+ return request.override(tools=[*bound_tools, dict(_SERVER_TOOL_SEARCH_TOOLS[provider])])
155
+
156
+ def wrap_model_call(
157
+ self,
158
+ request: ModelRequest[ContextT],
159
+ handler: Callable[[ModelRequest[ContextT]], ModelResponse[ResponseT]],
160
+ ) -> ModelResponse[ResponseT] | AIMessage:
161
+ """Defer tools before invoking the model.
162
+
163
+ Args:
164
+ request: Model request to execute.
165
+ handler: Callback that executes the model request.
166
+
167
+ Returns:
168
+ The model call result.
169
+
170
+ Raises:
171
+ ValueError: If `searchable_tools` references a tool not bound to the
172
+ model, or if a tool is deferred but the model's provider cannot
173
+ be identified or does not support server-side tool search.
174
+ """
175
+ return handler(self._prepare_request(request))
176
+
177
+ async def awrap_model_call(
178
+ self,
179
+ request: ModelRequest[ContextT],
180
+ handler: Callable[[ModelRequest[ContextT]], Awaitable[ModelResponse[ResponseT]]],
181
+ ) -> ModelResponse[ResponseT] | AIMessage:
182
+ """Defer tools before asynchronously invoking the model.
183
+
184
+ Args:
185
+ request: Model request to execute.
186
+ handler: Callback that executes the model request.
187
+
188
+ Returns:
189
+ The model call result.
190
+
191
+ Raises:
192
+ ValueError: If `searchable_tools` references a tool not bound to the
193
+ model, or if a tool is deferred but the model's provider cannot
194
+ be identified or does not support server-side tool search.
195
+ """
196
+ return await handler(self._prepare_request(request))
197
+
198
+
199
+ def _to_tool_names(tools: list[ToolIdentifier] | None) -> set[str]:
200
+ """Convert tool identifiers to names."""
201
+ if tools is None:
202
+ return set()
203
+ return {tool if isinstance(tool, str) else tool.name for tool in tools}
204
+
205
+
206
+ def _is_deferred_tool(tool: BaseTool | dict[str, Any], tool_names: set[str]) -> bool:
207
+ """Return whether a tool should be deferred.
208
+
209
+ Only `BaseTool` instances can be deferred; dict-form tools (e.g. provider
210
+ tool specs) have no `extras` or name to match and are never deferred.
211
+ """
212
+ if not isinstance(tool, BaseTool):
213
+ return False
214
+ extras = tool.extras if isinstance(tool.extras, dict) else {}
215
+ return extras.get("defer_loading") is True or tool.name in tool_names
216
+
217
+
218
+ def _defer_tool_if_needed(
219
+ tool: BaseTool | dict[str, Any], tool_names: set[str]
220
+ ) -> BaseTool | dict[str, Any]:
221
+ """Return the tool with `defer_loading` set, or unchanged if not deferred.
222
+
223
+ Returns the input unchanged when the tool should not be deferred or is not a
224
+ `BaseTool` (only `BaseTool` instances carry the `extras` that flags deferral).
225
+ """
226
+ if not _is_deferred_tool(tool, tool_names):
227
+ return tool
228
+ if not isinstance(tool, BaseTool):
229
+ return tool
230
+ extras = {**(tool.extras or {}), "defer_loading": True}
231
+ return tool.model_copy(update={"extras": extras})
232
+
233
+
234
+ def _get_model_provider(model: BaseChatModel, runtime: Any) -> str | None:
235
+ """Infer the normalized provider name for server-side tool search.
236
+
237
+ Returns `None` when no provider can be identified, so callers can
238
+ distinguish a detection failure from a provider that is simply unsupported.
239
+ """
240
+ default_config = getattr(model, "_default_config", None)
241
+ model_params_fn = getattr(model, "_model_params", None)
242
+ if callable(model_params_fn):
243
+ config = getattr(runtime, "config", None)
244
+ # `_model_params` expects a config mapping (or None); coerce a malformed
245
+ # non-mapping config to None so it is treated as "no config" rather than
246
+ # raising deep inside the configurable model.
247
+ if config is not None and not isinstance(config, Mapping):
248
+ config = None
249
+ model_params = model_params_fn(config)
250
+ if isinstance(model_params, dict):
251
+ params = (
252
+ {**default_config, **model_params}
253
+ if isinstance(default_config, dict)
254
+ else model_params
255
+ )
256
+ if provider := _provider_from_params(params):
257
+ return provider
258
+
259
+ if isinstance(default_config, dict) and (provider := _provider_from_params(default_config)):
260
+ return provider
261
+
262
+ get_ls_params = getattr(model, "_get_ls_params", None)
263
+ if callable(get_ls_params):
264
+ ls_params = get_ls_params()
265
+ if isinstance(ls_params, dict) and isinstance(ls_params.get("ls_provider"), str):
266
+ return _normalize_provider(ls_params["ls_provider"])
267
+
268
+ return _provider_from_class_name(model.__class__.__name__)
269
+
270
+
271
+ def _provider_from_params(params: dict[str, Any]) -> str | None:
272
+ """Infer the provider from model parameters, or `None` if absent."""
273
+ provider = params.get("model_provider")
274
+ if isinstance(provider, str):
275
+ return _normalize_provider(provider)
276
+ model_name = params.get("model")
277
+ if isinstance(model_name, str):
278
+ return _provider_from_model_name(model_name)
279
+ return None
280
+
281
+
282
+ def _provider_from_model_name(model_name: str) -> str | None:
283
+ """Infer the provider from a model name, or `None` if unrecognized."""
284
+ provider, _, rest = model_name.partition(":")
285
+ if rest:
286
+ return _normalize_provider(provider)
287
+ # The inferred provider is only used for a registry lookup here, so suppress the
288
+ # `model_provider` inference deprecation warning that `_attempt_infer_model_provider`
289
+ # emits for some names (e.g. `gemini*`); its guidance is irrelevant to routing.
290
+ with warnings.catch_warnings():
291
+ warnings.simplefilter("ignore", DeprecationWarning)
292
+ inferred = _attempt_infer_model_provider(model_name)
293
+ return _normalize_provider(inferred) if inferred else None
294
+
295
+
296
+ def _provider_from_class_name(class_name: str) -> str | None:
297
+ """Infer the provider from a model class name, or `None` if unrecognized."""
298
+ if class_name in {"ChatAnthropic", "AnthropicChat"}:
299
+ return "anthropic"
300
+ if class_name in {"ChatOpenAI", "OpenAIChat"}:
301
+ return "openai"
302
+ return None
303
+
304
+
305
+ def _normalize_provider(provider: str) -> str:
306
+ """Normalize a provider identifier by lowercasing and mapping `-` to `_`."""
307
+ return provider.replace("-", "_").lower()