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,534 @@
1
+ """Derivations of standard content blocks from Anthropic content."""
2
+
3
+ import json
4
+ from collections.abc import Iterator
5
+ from typing import Any, cast
6
+
7
+ from coco_core.messages import AIMessage, AIMessageChunk
8
+ from coco_core.messages import content as types
9
+
10
+
11
+ def _populate_extras(
12
+ standard_block: types.ContentBlock, block: dict[str, Any], known_fields: set[str]
13
+ ) -> types.ContentBlock:
14
+ """Mutate a block, populating extras."""
15
+ if standard_block.get("type") == "non_standard":
16
+ return standard_block
17
+
18
+ for key, value in block.items():
19
+ if key not in known_fields:
20
+ if "extras" not in standard_block:
21
+ # Below type-ignores are because mypy thinks a non-standard block can
22
+ # get here, although we exclude them above.
23
+ standard_block["extras"] = {} # type: ignore[typeddict-unknown-key]
24
+ standard_block["extras"][key] = value # type: ignore[typeddict-item]
25
+
26
+ return standard_block
27
+
28
+
29
+ def _non_standard_block(block: dict[str, Any]) -> types.NonStandardContentBlock:
30
+ return {"type": "non_standard", "value": block}
31
+
32
+
33
+ def _convert_to_v1_from_anthropic_input(
34
+ content: list[types.ContentBlock],
35
+ ) -> list[types.ContentBlock]:
36
+ """Convert Anthropic format blocks to v1 format.
37
+
38
+ During the `content_blocks` parsing process, we wrap blocks not recognized as a v1
39
+ block as a `'non_standard'` block with the original block stored in the `value`
40
+ field. This function attempts to unpack those blocks and convert any blocks that
41
+ might be Anthropic format to v1 ContentBlocks.
42
+
43
+ If conversion fails, the block is left as a `'non_standard'` block.
44
+
45
+ Args:
46
+ content: List of content blocks to process.
47
+
48
+ Returns:
49
+ Updated list with Anthropic blocks converted to v1 format.
50
+ """
51
+
52
+ def _iter_blocks() -> Iterator[types.ContentBlock]:
53
+ blocks: list[dict[str, Any]] = [
54
+ cast("dict[str, Any]", block)
55
+ if block.get("type") != "non_standard"
56
+ else block["value"] # type: ignore[typeddict-item] # this is only non-standard blocks
57
+ for block in content
58
+ ]
59
+ for block in blocks:
60
+ block_type = block.get("type")
61
+ source = block.get("source")
62
+
63
+ if (
64
+ block_type == "document"
65
+ and isinstance(source, dict)
66
+ and "type" in source
67
+ ):
68
+ if source["type"] == "base64":
69
+ if "data" not in source or "media_type" not in source:
70
+ yield _non_standard_block(block)
71
+ continue
72
+ file_block: types.FileContentBlock = {
73
+ "type": "file",
74
+ "base64": source["data"],
75
+ "mime_type": source["media_type"],
76
+ }
77
+ _populate_extras(file_block, block, {"type", "source"})
78
+ yield file_block
79
+
80
+ elif source["type"] == "url":
81
+ if "url" not in source:
82
+ yield _non_standard_block(block)
83
+ continue
84
+ file_block = {
85
+ "type": "file",
86
+ "url": source["url"],
87
+ }
88
+ _populate_extras(file_block, block, {"type", "source"})
89
+ yield file_block
90
+
91
+ elif source["type"] == "file":
92
+ if "file_id" not in source:
93
+ yield _non_standard_block(block)
94
+ continue
95
+ file_block = {
96
+ "type": "file",
97
+ "id": source["file_id"],
98
+ }
99
+ _populate_extras(file_block, block, {"type", "source"})
100
+ yield file_block
101
+
102
+ elif source["type"] == "text":
103
+ if "data" not in source:
104
+ yield _non_standard_block(block)
105
+ continue
106
+ plain_text_block: types.PlainTextContentBlock = {
107
+ "type": "text-plain",
108
+ "text": source["data"],
109
+ "mime_type": block.get("media_type", "text/plain"),
110
+ }
111
+ _populate_extras(plain_text_block, block, {"type", "source"})
112
+ yield plain_text_block
113
+
114
+ else:
115
+ yield _non_standard_block(block)
116
+
117
+ elif (
118
+ block_type == "image" and isinstance(source, dict) and "type" in source
119
+ ):
120
+ if source["type"] == "base64":
121
+ if "data" not in source or "media_type" not in source:
122
+ yield _non_standard_block(block)
123
+ continue
124
+ image_block: types.ImageContentBlock = {
125
+ "type": "image",
126
+ "base64": source["data"],
127
+ "mime_type": source["media_type"],
128
+ }
129
+ _populate_extras(image_block, block, {"type", "source"})
130
+ yield image_block
131
+
132
+ elif source["type"] == "url":
133
+ if "url" not in source:
134
+ yield _non_standard_block(block)
135
+ continue
136
+ image_block = {
137
+ "type": "image",
138
+ "url": source["url"],
139
+ }
140
+ _populate_extras(image_block, block, {"type", "source"})
141
+ yield image_block
142
+
143
+ elif source["type"] == "file":
144
+ if "file_id" not in source:
145
+ yield _non_standard_block(block)
146
+ continue
147
+ image_block = {
148
+ "type": "image",
149
+ "id": source["file_id"],
150
+ }
151
+ _populate_extras(image_block, block, {"type", "source"})
152
+ yield image_block
153
+
154
+ else:
155
+ yield _non_standard_block(block)
156
+
157
+ elif block_type in types.KNOWN_BLOCK_TYPES:
158
+ yield cast("types.ContentBlock", block)
159
+
160
+ else:
161
+ yield _non_standard_block(block)
162
+
163
+ return list(_iter_blocks())
164
+
165
+
166
+ def _convert_citation_to_v1(citation: dict[str, Any]) -> types.Annotation:
167
+ citation_type = citation.get("type")
168
+
169
+ if citation_type == "web_search_result_location":
170
+ if "cited_text" not in citation or "url" not in citation:
171
+ return {
172
+ "type": "non_standard_annotation",
173
+ "value": citation,
174
+ }
175
+ url_citation: types.Citation = {
176
+ "type": "citation",
177
+ "cited_text": citation["cited_text"],
178
+ "url": citation["url"],
179
+ }
180
+ if title := citation.get("title"):
181
+ url_citation["title"] = title
182
+ known_fields = {"type", "cited_text", "url", "title", "index", "extras"}
183
+ for key, value in citation.items():
184
+ if key not in known_fields:
185
+ if "extras" not in url_citation:
186
+ url_citation["extras"] = {}
187
+ url_citation["extras"][key] = value
188
+
189
+ return url_citation
190
+
191
+ if citation_type in {
192
+ "char_location",
193
+ "content_block_location",
194
+ "page_location",
195
+ "search_result_location",
196
+ }:
197
+ if "cited_text" not in citation:
198
+ return {
199
+ "type": "non_standard_annotation",
200
+ "value": citation,
201
+ }
202
+ document_citation: types.Citation = {
203
+ "type": "citation",
204
+ "cited_text": citation["cited_text"],
205
+ }
206
+ if "document_title" in citation:
207
+ document_citation["title"] = citation["document_title"]
208
+ elif title := citation.get("title"):
209
+ document_citation["title"] = title
210
+ known_fields = {
211
+ "type",
212
+ "cited_text",
213
+ "document_title",
214
+ "title",
215
+ "index",
216
+ "extras",
217
+ }
218
+ for key, value in citation.items():
219
+ if key not in known_fields:
220
+ if "extras" not in document_citation:
221
+ document_citation["extras"] = {}
222
+ document_citation["extras"][key] = value
223
+
224
+ return document_citation
225
+
226
+ return {
227
+ "type": "non_standard_annotation",
228
+ "value": citation,
229
+ }
230
+
231
+
232
+ def _convert_to_v1_from_anthropic(message: AIMessage) -> list[types.ContentBlock]:
233
+ """Convert Anthropic message content to v1 format."""
234
+ content: list[str | dict[str, Any]]
235
+ if isinstance(message.content, str):
236
+ content = [{"type": "text", "text": message.content}]
237
+ else:
238
+ content = message.content
239
+
240
+ def _iter_blocks() -> Iterator[types.ContentBlock]:
241
+ for block in content:
242
+ if not isinstance(block, dict):
243
+ continue
244
+ block_type = block.get("type")
245
+
246
+ if block_type == "text":
247
+ if citations := block.get("citations"):
248
+ text_block: types.TextContentBlock = {
249
+ "type": "text",
250
+ "text": block.get("text", ""),
251
+ "annotations": [_convert_citation_to_v1(a) for a in citations],
252
+ }
253
+ else:
254
+ text_block = {"type": "text", "text": block["text"]}
255
+ if "index" in block:
256
+ text_block["index"] = block["index"]
257
+ yield text_block
258
+
259
+ elif block_type == "thinking":
260
+ reasoning_block: types.ReasoningContentBlock = {
261
+ "type": "reasoning",
262
+ "reasoning": block.get("thinking", ""),
263
+ }
264
+ if "index" in block:
265
+ reasoning_block["index"] = block["index"]
266
+ known_fields = {"type", "thinking", "index", "extras"}
267
+ for key in block:
268
+ if key not in known_fields:
269
+ if "extras" not in reasoning_block:
270
+ reasoning_block["extras"] = {}
271
+ reasoning_block["extras"][key] = block[key]
272
+ yield reasoning_block
273
+
274
+ elif block_type == "tool_use":
275
+ if (
276
+ isinstance(message, AIMessageChunk)
277
+ and len(message.tool_call_chunks) == 1
278
+ and message.chunk_position != "last"
279
+ ):
280
+ # Isolated chunk
281
+ chunk = message.tool_call_chunks[0]
282
+
283
+ tool_call_chunk = types.ToolCallChunk(
284
+ name=chunk.get("name"),
285
+ id=chunk.get("id"),
286
+ args=chunk.get("args"),
287
+ type="tool_call_chunk",
288
+ )
289
+ if "caller" in block:
290
+ tool_call_chunk["extras"] = {"caller": block["caller"]}
291
+
292
+ index = chunk.get("index")
293
+ if index is not None:
294
+ tool_call_chunk["index"] = index
295
+ yield tool_call_chunk
296
+ else:
297
+ tool_call_block: types.ToolCall | None = None
298
+ # Non-streaming or gathered chunk
299
+ if len(message.tool_calls) == 1:
300
+ tool_call_block = {
301
+ "type": "tool_call",
302
+ "name": message.tool_calls[0]["name"],
303
+ "args": message.tool_calls[0]["args"],
304
+ "id": message.tool_calls[0].get("id"),
305
+ }
306
+ elif call_id := block.get("id"):
307
+ for tc in message.tool_calls:
308
+ if tc.get("id") == call_id:
309
+ tool_call_block = {
310
+ "type": "tool_call",
311
+ "name": tc["name"],
312
+ "args": tc["args"],
313
+ "id": tc.get("id"),
314
+ }
315
+ break
316
+ if not tool_call_block:
317
+ tool_call_block = {
318
+ "type": "tool_call",
319
+ "name": block.get("name", ""),
320
+ "args": block.get("input", {}),
321
+ "id": block.get("id", ""),
322
+ }
323
+ if "index" in block:
324
+ tool_call_block["index"] = block["index"]
325
+ if "caller" in block:
326
+ if "extras" not in tool_call_block:
327
+ tool_call_block["extras"] = {}
328
+ tool_call_block["extras"]["caller"] = block["caller"]
329
+
330
+ yield tool_call_block
331
+
332
+ elif block_type == "input_json_delta" and isinstance(
333
+ message, AIMessageChunk
334
+ ):
335
+ if len(message.tool_call_chunks) == 1:
336
+ chunk = message.tool_call_chunks[0]
337
+ tool_call_chunk = types.ToolCallChunk(
338
+ name=chunk.get("name"),
339
+ id=chunk.get("id"),
340
+ args=chunk.get("args"),
341
+ type="tool_call_chunk",
342
+ )
343
+ index = chunk.get("index")
344
+ if index is not None:
345
+ tool_call_chunk["index"] = index
346
+ yield tool_call_chunk
347
+
348
+ else:
349
+ server_tool_call_chunk: types.ServerToolCallChunk = {
350
+ "type": "server_tool_call_chunk",
351
+ "args": block.get("partial_json", ""),
352
+ }
353
+ if "index" in block:
354
+ server_tool_call_chunk["index"] = block["index"]
355
+ yield server_tool_call_chunk
356
+
357
+ elif block_type == "server_tool_use":
358
+ if block.get("name") == "code_execution":
359
+ server_tool_use_name = "code_interpreter"
360
+ else:
361
+ server_tool_use_name = block.get("name", "")
362
+ if (
363
+ isinstance(message, AIMessageChunk)
364
+ and block.get("input") == {}
365
+ and "partial_json" not in block
366
+ and message.chunk_position != "last"
367
+ ):
368
+ # First chunk in a stream
369
+ server_tool_call_chunk = {
370
+ "type": "server_tool_call_chunk",
371
+ "name": server_tool_use_name,
372
+ "args": "",
373
+ "id": block.get("id", ""),
374
+ }
375
+ if "index" in block:
376
+ server_tool_call_chunk["index"] = block["index"]
377
+ known_fields = {"type", "name", "input", "id", "index"}
378
+ _populate_extras(server_tool_call_chunk, block, known_fields)
379
+ yield server_tool_call_chunk
380
+ else:
381
+ server_tool_call: types.ServerToolCall = {
382
+ "type": "server_tool_call",
383
+ "name": server_tool_use_name,
384
+ "args": block.get("input", {}),
385
+ "id": block.get("id", ""),
386
+ }
387
+
388
+ if block.get("input") == {} and "partial_json" in block:
389
+ try:
390
+ input_ = json.loads(block["partial_json"])
391
+ if isinstance(input_, dict):
392
+ server_tool_call["args"] = input_
393
+ except json.JSONDecodeError:
394
+ pass
395
+
396
+ if "index" in block:
397
+ server_tool_call["index"] = block["index"]
398
+ known_fields = {
399
+ "type",
400
+ "name",
401
+ "input",
402
+ "partial_json",
403
+ "id",
404
+ "index",
405
+ }
406
+ _populate_extras(server_tool_call, block, known_fields)
407
+
408
+ yield server_tool_call
409
+
410
+ elif block_type == "mcp_tool_use":
411
+ if (
412
+ isinstance(message, AIMessageChunk)
413
+ and block.get("input") == {}
414
+ and "partial_json" not in block
415
+ and message.chunk_position != "last"
416
+ ):
417
+ # First chunk in a stream
418
+ server_tool_call_chunk = {
419
+ "type": "server_tool_call_chunk",
420
+ "name": "remote_mcp",
421
+ "args": "",
422
+ "id": block.get("id", ""),
423
+ }
424
+ if "name" in block:
425
+ server_tool_call_chunk["extras"] = {"tool_name": block["name"]}
426
+ known_fields = {"type", "name", "input", "id", "index"}
427
+ _populate_extras(server_tool_call_chunk, block, known_fields)
428
+ if "index" in block:
429
+ server_tool_call_chunk["index"] = block["index"]
430
+ yield server_tool_call_chunk
431
+ else:
432
+ server_tool_call = {
433
+ "type": "server_tool_call",
434
+ "name": "remote_mcp",
435
+ "args": block.get("input", {}),
436
+ "id": block.get("id", ""),
437
+ }
438
+
439
+ if block.get("input") == {} and "partial_json" in block:
440
+ try:
441
+ input_ = json.loads(block["partial_json"])
442
+ if isinstance(input_, dict):
443
+ server_tool_call["args"] = input_
444
+ except json.JSONDecodeError:
445
+ pass
446
+
447
+ if "name" in block:
448
+ server_tool_call["extras"] = {"tool_name": block["name"]}
449
+ known_fields = {
450
+ "type",
451
+ "name",
452
+ "input",
453
+ "partial_json",
454
+ "id",
455
+ "index",
456
+ }
457
+ _populate_extras(server_tool_call, block, known_fields)
458
+ if "index" in block:
459
+ server_tool_call["index"] = block["index"]
460
+
461
+ yield server_tool_call
462
+
463
+ elif block_type and block_type.endswith("_tool_result"):
464
+ server_tool_result: types.ServerToolResult = {
465
+ "type": "server_tool_result",
466
+ "tool_call_id": block.get("tool_use_id", ""),
467
+ "status": "success",
468
+ "extras": {"block_type": block_type},
469
+ }
470
+ if output := block.get("content", []):
471
+ server_tool_result["output"] = output
472
+ if isinstance(output, dict) and output.get(
473
+ "error_code" # web_search, code_interpreter
474
+ ):
475
+ server_tool_result["status"] = "error"
476
+ if block.get("is_error"): # mcp_tool_result
477
+ server_tool_result["status"] = "error"
478
+ if "index" in block:
479
+ server_tool_result["index"] = block["index"]
480
+
481
+ known_fields = {"type", "tool_use_id", "content", "is_error", "index"}
482
+ _populate_extras(server_tool_result, block, known_fields)
483
+
484
+ yield server_tool_result
485
+
486
+ else:
487
+ new_block: types.NonStandardContentBlock = {
488
+ "type": "non_standard",
489
+ "value": block,
490
+ }
491
+ if "index" in new_block["value"]:
492
+ new_block["index"] = new_block["value"].pop("index")
493
+ yield new_block
494
+
495
+ return list(_iter_blocks())
496
+
497
+
498
+ def translate_content(message: AIMessage) -> list[types.ContentBlock]:
499
+ """Derive standard content blocks from a message with Anthropic content.
500
+
501
+ Args:
502
+ message: The message to translate.
503
+
504
+ Returns:
505
+ The derived content blocks.
506
+ """
507
+ return _convert_to_v1_from_anthropic(message)
508
+
509
+
510
+ def translate_content_chunk(message: AIMessageChunk) -> list[types.ContentBlock]:
511
+ """Derive standard content blocks from a message chunk with Anthropic content.
512
+
513
+ Args:
514
+ message: The message chunk to translate.
515
+
516
+ Returns:
517
+ The derived content blocks.
518
+ """
519
+ return _convert_to_v1_from_anthropic(message)
520
+
521
+
522
+ def _register_anthropic_translator() -> None:
523
+ """Register the Anthropic translator with the central registry.
524
+
525
+ Run automatically when the module is imported.
526
+ """
527
+ from coco_core.messages.block_translators import ( # noqa: PLC0415
528
+ register_translator,
529
+ )
530
+
531
+ register_translator("anthropic", translate_content, translate_content_chunk)
532
+
533
+
534
+ _register_anthropic_translator()
@@ -0,0 +1,108 @@
1
+ """Derivations of standard content blocks from Bedrock content."""
2
+
3
+ from coco_core.messages import AIMessage, AIMessageChunk
4
+ from coco_core.messages import content as types
5
+ from coco_core.messages.block_translators.anthropic import (
6
+ _convert_to_v1_from_anthropic,
7
+ )
8
+
9
+
10
+ def _convert_to_v1_from_bedrock(message: AIMessage) -> list[types.ContentBlock]:
11
+ """Convert bedrock message content to v1 format."""
12
+ out = _convert_to_v1_from_anthropic(message)
13
+
14
+ content_tool_call_ids = {
15
+ block.get("id")
16
+ for block in out
17
+ if isinstance(block, dict) and block.get("type") == "tool_call"
18
+ }
19
+ for tool_call in message.tool_calls:
20
+ if (id_ := tool_call.get("id")) and id_ not in content_tool_call_ids:
21
+ tool_call_block: types.ToolCall = {
22
+ "type": "tool_call",
23
+ "id": id_,
24
+ "name": tool_call["name"],
25
+ "args": tool_call["args"],
26
+ }
27
+ if "index" in tool_call:
28
+ tool_call_block["index"] = tool_call["index"] # type: ignore[typeddict-item]
29
+ if "extras" in tool_call:
30
+ tool_call_block["extras"] = tool_call["extras"] # type: ignore[typeddict-item]
31
+ out.append(tool_call_block)
32
+ return out
33
+
34
+
35
+ def _convert_to_v1_from_bedrock_chunk(
36
+ message: AIMessageChunk,
37
+ ) -> list[types.ContentBlock]:
38
+ """Convert bedrock message chunk content to v1 format."""
39
+ if (
40
+ message.content == ""
41
+ and not message.additional_kwargs
42
+ and not message.tool_calls
43
+ ):
44
+ # Bedrock outputs multiple chunks containing response metadata
45
+ return []
46
+
47
+ out = _convert_to_v1_from_anthropic(message)
48
+
49
+ if (
50
+ message.tool_call_chunks
51
+ and not message.content
52
+ and message.chunk_position != "last" # keep tool_calls if aggregated
53
+ ):
54
+ for tool_call_chunk in message.tool_call_chunks:
55
+ tc: types.ToolCallChunk = {
56
+ "type": "tool_call_chunk",
57
+ "id": tool_call_chunk.get("id"),
58
+ "name": tool_call_chunk.get("name"),
59
+ "args": tool_call_chunk.get("args"),
60
+ }
61
+ if (idx := tool_call_chunk.get("index")) is not None:
62
+ tc["index"] = idx
63
+ out.append(tc)
64
+ return out
65
+
66
+
67
+ def translate_content(message: AIMessage) -> list[types.ContentBlock]:
68
+ """Derive standard content blocks from a message with Bedrock content.
69
+
70
+ Args:
71
+ message: The message to translate.
72
+
73
+ Returns:
74
+ The derived content blocks.
75
+ """
76
+ if "claude" not in message.response_metadata.get("model_name", "").lower():
77
+ raise NotImplementedError # fall back to best-effort parsing
78
+ return _convert_to_v1_from_bedrock(message)
79
+
80
+
81
+ def translate_content_chunk(message: AIMessageChunk) -> list[types.ContentBlock]:
82
+ """Derive standard content blocks from a message chunk with Bedrock content.
83
+
84
+ Args:
85
+ message: The message chunk to translate.
86
+
87
+ Returns:
88
+ The derived content blocks.
89
+ """
90
+ # TODO: add model_name to all Bedrock chunks and update core merging logic
91
+ # to not append during aggregation. Then raise NotImplementedError here if
92
+ # not an Anthropic model to fall back to best-effort parsing.
93
+ return _convert_to_v1_from_bedrock_chunk(message)
94
+
95
+
96
+ def _register_bedrock_translator() -> None:
97
+ """Register the bedrock translator with the central registry.
98
+
99
+ Run automatically when the module is imported.
100
+ """
101
+ from coco_core.messages.block_translators import ( # noqa: PLC0415
102
+ register_translator,
103
+ )
104
+
105
+ register_translator("bedrock", translate_content, translate_content_chunk)
106
+
107
+
108
+ _register_bedrock_translator()