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,343 @@
1
+ import re
2
+ from collections.abc import Sequence
3
+ from typing import (
4
+ TYPE_CHECKING,
5
+ Any,
6
+ Literal,
7
+ TypedDict,
8
+ TypeVar,
9
+ )
10
+
11
+ if TYPE_CHECKING:
12
+ from coco_core.messages import BaseMessage
13
+ from coco_core.messages.content import (
14
+ ContentBlock,
15
+ )
16
+
17
+
18
+ def _filter_invocation_params_for_tracing(params: dict[str, Any]) -> dict[str, Any]:
19
+ """Filter out large/inappropriate fields from invocation params for tracing.
20
+
21
+ Removes fields like tools, functions, messages, response_format that can be large.
22
+
23
+ Args:
24
+ params: The invocation parameters to filter.
25
+
26
+ Returns:
27
+ The filtered parameters with large fields removed.
28
+ """
29
+ excluded_keys = {"tools", "functions", "messages", "response_format"}
30
+ return {k: v for k, v in params.items() if k not in excluded_keys}
31
+
32
+
33
+ def is_openai_data_block(
34
+ block: dict[str, Any], filter_: Literal["image", "audio", "file"] | None = None
35
+ ) -> bool:
36
+ """Check whether a block contains multimodal data in OpenAI Chat Completions format.
37
+
38
+ Supports both data and ID-style blocks (e.g. `'file_data'` and `'file_id'`)
39
+
40
+ If additional keys are present, they are ignored / will not affect outcome as long
41
+ as the required keys are present and valid.
42
+
43
+ Args:
44
+ block: The content block to check.
45
+ filter_: If provided, only return True for blocks matching this specific type.
46
+ - "image": Only match image_url blocks
47
+ - "audio": Only match input_audio blocks
48
+ - "file": Only match file blocks
49
+ If `None`, match any valid OpenAI data block type. Note that this means that
50
+ if the block has a valid OpenAI data type but the filter_ is set to a
51
+ different type, this function will return False.
52
+
53
+ Returns:
54
+ `True` if the block is a valid OpenAI data block and matches the filter_
55
+ (if provided).
56
+
57
+ """
58
+ if block.get("type") == "image_url":
59
+ if filter_ is not None and filter_ != "image":
60
+ return False
61
+ if (
62
+ (set(block.keys()) <= {"type", "image_url", "detail"})
63
+ and (image_url := block.get("image_url"))
64
+ and isinstance(image_url, dict)
65
+ ):
66
+ url = image_url.get("url")
67
+ if isinstance(url, str):
68
+ # Required per OpenAI spec
69
+ return True
70
+ # Ignore `'detail'` since it's optional and specific to OpenAI
71
+
72
+ elif block.get("type") == "input_audio":
73
+ if filter_ is not None and filter_ != "audio":
74
+ return False
75
+ if (audio := block.get("input_audio")) and isinstance(audio, dict):
76
+ audio_data = audio.get("data")
77
+ audio_format = audio.get("format")
78
+ # Both required per OpenAI spec
79
+ if isinstance(audio_data, str) and isinstance(audio_format, str):
80
+ return True
81
+
82
+ elif block.get("type") == "file":
83
+ if filter_ is not None and filter_ != "file":
84
+ return False
85
+ if (file := block.get("file")) and isinstance(file, dict):
86
+ file_data = file.get("file_data")
87
+ file_id = file.get("file_id")
88
+ # Files can be either base64-encoded or pre-uploaded with an ID
89
+ if isinstance(file_data, str) or isinstance(file_id, str):
90
+ return True
91
+
92
+ else:
93
+ return False
94
+
95
+ # Has no `'type'` key
96
+ return False
97
+
98
+
99
+ class ParsedDataUri(TypedDict):
100
+ source_type: Literal["base64"]
101
+ data: str
102
+ mime_type: str
103
+
104
+
105
+ def _parse_data_uri(uri: str) -> ParsedDataUri | None:
106
+ """Parse a data URI into its components.
107
+
108
+ If parsing fails, return `None`. If either MIME type or data is missing, return
109
+ `None`.
110
+
111
+ Example:
112
+ ```python
113
+ data_uri = "data:image/jpeg;base64,/9j/4AAQSkZJRg..."
114
+ parsed = _parse_data_uri(data_uri)
115
+
116
+ assert parsed == {
117
+ "source_type": "base64",
118
+ "mime_type": "image/jpeg",
119
+ "data": "/9j/4AAQSkZJRg...",
120
+ }
121
+ ```
122
+ """
123
+ regex = r"^data:(?P<mime_type>[^;]+);base64,(?P<data>.+)$"
124
+ match = re.match(regex, uri)
125
+ if match is None:
126
+ return None
127
+
128
+ mime_type = match.group("mime_type")
129
+ data = match.group("data")
130
+ if not mime_type or not data:
131
+ return None
132
+
133
+ return {
134
+ "source_type": "base64",
135
+ "data": data,
136
+ "mime_type": mime_type,
137
+ }
138
+
139
+
140
+ def _normalize_messages(
141
+ messages: Sequence["BaseMessage"],
142
+ ) -> list["BaseMessage"]:
143
+ """Normalize message formats to Coco v1 standard content blocks.
144
+
145
+ Chat models already implement support for:
146
+ - Images in OpenAI Chat Completions format
147
+ These will be passed through unchanged
148
+ - Coco v1 standard content blocks
149
+
150
+ This function extends support to:
151
+ - `[Audio](https://platform.openai.com/docs/api-reference/chat/create) and
152
+ `[file](https://platform.openai.com/docs/api-reference/files) data in OpenAI
153
+ Chat Completions format
154
+ - Images are technically supported but we expect chat models to handle them
155
+ directly; this may change in the future
156
+ - Coco v0 standard content blocks for backward compatibility
157
+
158
+ !!! warning "Behavior changed in `trustcoco-core` 1.0.0"
159
+
160
+ In previous versions, this function returned messages in Coco v0 format.
161
+ Now, it returns messages in Coco v1 format, which upgraded chat models now
162
+ expect to receive when passing back in message history. For backward
163
+ compatibility, this function will convert v0 message content to v1 format.
164
+
165
+ ??? note "v0 Content Block Schemas"
166
+
167
+ `URLContentBlock`:
168
+
169
+ ```python
170
+ {
171
+ mime_type: NotRequired[str]
172
+ type: Literal['image', 'audio', 'file'],
173
+ source_type: Literal['url'],
174
+ url: str,
175
+ }
176
+ ```
177
+
178
+ `Base64ContentBlock`:
179
+
180
+ ```python
181
+ {
182
+ mime_type: NotRequired[str]
183
+ type: Literal['image', 'audio', 'file'],
184
+ source_type: Literal['base64'],
185
+ data: str,
186
+ }
187
+ ```
188
+
189
+ `IDContentBlock`:
190
+
191
+ (In practice, this was never used)
192
+
193
+ ```python
194
+ {
195
+ type: Literal["image", "audio", "file"],
196
+ source_type: Literal["id"],
197
+ id: str,
198
+ }
199
+ ```
200
+
201
+ `PlainTextContentBlock`:
202
+
203
+ ```python
204
+ {
205
+ mime_type: NotRequired[str]
206
+ type: Literal['file'],
207
+ source_type: Literal['text'],
208
+ url: str,
209
+ }
210
+ ```
211
+
212
+ If a v1 message is passed in, it will be returned as-is, meaning it is safe to
213
+ always pass in v1 messages to this function for assurance.
214
+
215
+ For posterity, here are the OpenAI Chat Completions schemas we expect:
216
+
217
+ Chat Completions image. Can be URL-based or base64-encoded. Supports MIME types
218
+ png, jpeg/jpg, webp, static gif:
219
+ {
220
+ "type": Literal['image_url'],
221
+ "image_url": {
222
+ "url": Union["data:$MIME_TYPE;base64,$BASE64_ENCODED_IMAGE", "$IMAGE_URL"],
223
+ "detail": Literal['low', 'high', 'auto'] = 'auto', # Supported by OpenAI
224
+ }
225
+ }
226
+
227
+ Chat Completions audio:
228
+ {
229
+ "type": Literal['input_audio'],
230
+ "input_audio": {
231
+ "format": Literal['wav', 'mp3'],
232
+ "data": str = "$BASE64_ENCODED_AUDIO",
233
+ },
234
+ }
235
+
236
+ Chat Completions files: either base64 or pre-uploaded file ID
237
+ {
238
+ "type": Literal['file'],
239
+ "file": Union[
240
+ {
241
+ "filename": str | None = "$FILENAME",
242
+ "file_data": str = "$BASE64_ENCODED_FILE",
243
+ },
244
+ {
245
+ "file_id": str = "$FILE_ID", # For pre-uploaded files to OpenAI
246
+ },
247
+ ],
248
+ }
249
+
250
+ """
251
+ from coco_core.messages.block_translators.coco_v0 import ( # noqa: PLC0415
252
+ _convert_legacy_v0_content_block_to_v1,
253
+ )
254
+ from coco_core.messages.block_translators.openai import ( # noqa: PLC0415
255
+ _convert_openai_format_to_data_block,
256
+ )
257
+
258
+ formatted_messages = []
259
+ for message in messages:
260
+ # We preserve input messages - the caller may reuse them elsewhere and expects
261
+ # them to remain unchanged. We only create a copy if we need to translate.
262
+ formatted_message = message
263
+
264
+ if isinstance(message.content, list):
265
+ for idx, block in enumerate(message.content):
266
+ # OpenAI Chat Completions multimodal data blocks to v1 standard
267
+ if (
268
+ isinstance(block, dict)
269
+ and block.get("type") in {"input_audio", "file"}
270
+ # Discriminate between OpenAI/LC format since they share `'type'`
271
+ and is_openai_data_block(block)
272
+ ):
273
+ formatted_message = _ensure_message_copy(message, formatted_message)
274
+
275
+ converted_block = _convert_openai_format_to_data_block(block)
276
+ _update_content_block(formatted_message, idx, converted_block)
277
+
278
+ # Convert multimodal Coco v0 to v1 standard content blocks
279
+ elif (
280
+ isinstance(block, dict)
281
+ and block.get("type")
282
+ in {
283
+ "image",
284
+ "audio",
285
+ "file",
286
+ }
287
+ and block.get("source_type") # v1 doesn't have `source_type`
288
+ in {
289
+ "url",
290
+ "base64",
291
+ "id",
292
+ "text",
293
+ }
294
+ ):
295
+ formatted_message = _ensure_message_copy(message, formatted_message)
296
+
297
+ converted_block = _convert_legacy_v0_content_block_to_v1(block)
298
+ _update_content_block(formatted_message, idx, converted_block)
299
+ continue
300
+
301
+ # else, pass through blocks that look like they have v1 format unchanged
302
+
303
+ formatted_messages.append(formatted_message)
304
+
305
+ return formatted_messages
306
+
307
+
308
+ T = TypeVar("T", bound="BaseMessage")
309
+
310
+
311
+ def _ensure_message_copy(message: T, formatted_message: T) -> T:
312
+ """Create a copy of the message if it hasn't been copied yet."""
313
+ if formatted_message is message:
314
+ formatted_message = message.model_copy()
315
+ # Shallow-copy content list to allow modifications
316
+ formatted_message.content = list(formatted_message.content)
317
+ return formatted_message
318
+
319
+
320
+ def _update_content_block(
321
+ formatted_message: "BaseMessage", idx: int, new_block: ContentBlock | dict[str, Any]
322
+ ) -> None:
323
+ """Update a content block at the given index, handling type issues."""
324
+ # Type ignore needed because:
325
+ # - `BaseMessage.content` is typed as `Union[str, list[Union[str, dict]]]`
326
+ # - When content is str, indexing fails (index error)
327
+ # - When content is list, the items are `Union[str, dict]` but we're assigning
328
+ # `Union[ContentBlock, dict]` where ContentBlock is richer than dict
329
+ # - This is safe because we only call this when we've verified content is a list and
330
+ # we're doing content block conversions
331
+ formatted_message.content[idx] = new_block # type: ignore[index, assignment]
332
+
333
+
334
+ def _update_message_content_to_blocks(message: T, output_version: str) -> T:
335
+ return message.model_copy(
336
+ update={
337
+ "content": message.content_blocks,
338
+ "response_metadata": {
339
+ **message.response_metadata,
340
+ "output_version": output_version,
341
+ },
342
+ }
343
+ )