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,525 @@
1
+ """Converts between AIMessage output formats, governed by `output_version`.
2
+
3
+ `output_version` is an attribute on ChatOpenAI.
4
+
5
+ Supported values are `None`, `'v0'`, and `'responses/v1'`.
6
+
7
+ `'v0'` corresponds to the format as of `ChatOpenAI` v0.3. For the Responses API, it
8
+ stores reasoning and tool outputs in `AIMessage.additional_kwargs`:
9
+
10
+ ```python
11
+ AIMessage(
12
+ content=[
13
+ {"type": "text", "text": "Hello, world!", "annotations": [{"type": "foo"}]}
14
+ ],
15
+ additional_kwargs={
16
+ "reasoning": {
17
+ "type": "reasoning",
18
+ "id": "rs_123",
19
+ "summary": [{"type": "summary_text", "text": "Reasoning summary"}],
20
+ },
21
+ "tool_outputs": [
22
+ {
23
+ "type": "web_search_call",
24
+ "id": "websearch_123",
25
+ "status": "completed",
26
+ }
27
+ ],
28
+ "refusal": "I cannot assist with that.",
29
+ },
30
+ response_metadata={"id": "resp_123"},
31
+ id="msg_123",
32
+ )
33
+ ```
34
+
35
+ `'responses/v1'` is only applicable to the Responses API. It retains information
36
+ about response item sequencing and accommodates multiple reasoning items by
37
+ representing these items in the content sequence:
38
+
39
+ ```python
40
+ AIMessage(
41
+ content=[
42
+ {
43
+ "type": "reasoning",
44
+ "summary": [{"type": "summary_text", "text": "Reasoning summary"}],
45
+ "id": "rs_123",
46
+ },
47
+ {
48
+ "type": "text",
49
+ "text": "Hello, world!",
50
+ "annotations": [{"type": "foo"}],
51
+ "id": "msg_123",
52
+ },
53
+ {"type": "refusal", "refusal": "I cannot assist with that."},
54
+ {"type": "web_search_call", "id": "websearch_123", "status": "completed"},
55
+ ],
56
+ response_metadata={"id": "resp_123"},
57
+ id="resp_123",
58
+ )
59
+ ```
60
+
61
+ There are other, small improvements as well-- e.g., we store message IDs on text
62
+ content blocks, rather than on the AIMessage.id, which now stores the response ID.
63
+
64
+ For backwards compatibility, this module provides functions to convert between the
65
+ formats. The functions are used internally by ChatOpenAI.
66
+ """
67
+
68
+ from __future__ import annotations
69
+
70
+ import json
71
+ from collections.abc import Iterable, Iterator
72
+ from typing import Any, cast
73
+
74
+ from coco_core.messages import AIMessage, is_data_content_block
75
+ from coco_core.messages import content as types
76
+
77
+
78
+ def _unwrap_non_standard(block: dict) -> dict:
79
+ """Unwrap a provider-native dictionary from a standard content block."""
80
+ if block.get("type") == "non_standard" and isinstance(
81
+ value := block.get("value"),
82
+ dict,
83
+ ):
84
+ return value
85
+ return block
86
+
87
+
88
+ _FUNCTION_CALL_IDS_MAP_KEY = "__openai_function_call_ids__"
89
+
90
+
91
+ # v0.3 / Responses
92
+ def _convert_to_v03_ai_message(
93
+ message: AIMessage, has_reasoning: bool = False
94
+ ) -> AIMessage:
95
+ """Mutate an `AIMessage` to the old-style v0.3 format."""
96
+ if isinstance(message.content, list):
97
+ new_content: list[dict | str] = []
98
+ for block in message.content:
99
+ if isinstance(block, dict):
100
+ if block.get("type") == "reasoning":
101
+ # Store a reasoning item in additional_kwargs (overwriting as in
102
+ # v0.3)
103
+ _ = block.pop("index", None)
104
+ if has_reasoning:
105
+ _ = block.pop("id", None)
106
+ _ = block.pop("type", None)
107
+ message.additional_kwargs["reasoning"] = block
108
+ elif block.get("type") in (
109
+ "web_search_call",
110
+ "file_search_call",
111
+ "computer_call",
112
+ "code_interpreter_call",
113
+ "mcp_call",
114
+ "mcp_list_tools",
115
+ "mcp_approval_request",
116
+ "image_generation_call",
117
+ "tool_search_call",
118
+ "tool_search_output",
119
+ "apply_patch_call",
120
+ "apply_patch_call_output",
121
+ ):
122
+ # Store built-in tool calls in additional_kwargs
123
+ if "tool_outputs" not in message.additional_kwargs:
124
+ message.additional_kwargs["tool_outputs"] = []
125
+ message.additional_kwargs["tool_outputs"].append(block)
126
+ elif block.get("type") == "function_call":
127
+ # Store function call item IDs in additional_kwargs, otherwise
128
+ # discard function call items.
129
+ if _FUNCTION_CALL_IDS_MAP_KEY not in message.additional_kwargs:
130
+ message.additional_kwargs[_FUNCTION_CALL_IDS_MAP_KEY] = {}
131
+ if (call_id := block.get("call_id")) and (
132
+ function_call_id := block.get("id")
133
+ ):
134
+ message.additional_kwargs[_FUNCTION_CALL_IDS_MAP_KEY][
135
+ call_id
136
+ ] = function_call_id
137
+ elif (block.get("type") == "refusal") and (
138
+ refusal := block.get("refusal")
139
+ ):
140
+ # Store a refusal item in additional_kwargs (overwriting as in
141
+ # v0.3)
142
+ message.additional_kwargs["refusal"] = refusal
143
+ elif block.get("type") == "text":
144
+ # Store a message item ID on AIMessage.id
145
+ if "id" in block:
146
+ message.id = block["id"]
147
+ new_content.append({k: v for k, v in block.items() if k != "id"})
148
+ elif (
149
+ set(block.keys()) == {"id", "index"}
150
+ and isinstance(block["id"], str)
151
+ and block["id"].startswith("msg_")
152
+ ):
153
+ # Drop message IDs in streaming case
154
+ new_content.append({"index": block["index"]})
155
+ else:
156
+ new_content.append(block)
157
+ else:
158
+ new_content.append(block)
159
+ message.content = new_content
160
+ if isinstance(message.id, str) and message.id.startswith("resp_"):
161
+ message.id = None
162
+ else:
163
+ pass
164
+
165
+ return message
166
+
167
+
168
+ # v1 / Chat Completions
169
+ def _convert_from_v1_to_chat_completions(message: AIMessage) -> AIMessage:
170
+ """Convert a v1 message to the Chat Completions format."""
171
+ if isinstance(message.content, list):
172
+ new_content: list = []
173
+ for block in message.content:
174
+ if isinstance(block, dict):
175
+ block_type = block.get("type")
176
+ if block_type == "text":
177
+ # Strip annotations
178
+ new_content.append({"type": "text", "text": block["text"]})
179
+ elif block_type in ("reasoning", "tool_call", "invalid_tool_call"):
180
+ pass
181
+ else:
182
+ new_content.append(block)
183
+ else:
184
+ new_content.append(block)
185
+ return message.model_copy(update={"content": new_content})
186
+
187
+ return message
188
+
189
+
190
+ # v1 / Responses
191
+ def _convert_annotation_from_v1(annotation: types.Annotation) -> dict[str, Any]:
192
+ """Convert a v1 `Annotation` to the v0.3 format (for Responses API)."""
193
+ if annotation["type"] == "citation":
194
+ new_ann: dict[str, Any] = {}
195
+ for field in ("end_index", "start_index"):
196
+ if field in annotation:
197
+ new_ann[field] = annotation[field]
198
+
199
+ if "url" in annotation:
200
+ # URL citation
201
+ if "title" in annotation:
202
+ new_ann["title"] = annotation["title"]
203
+ new_ann["type"] = "url_citation"
204
+ new_ann["url"] = annotation["url"]
205
+
206
+ if extra_fields := annotation.get("extras"):
207
+ new_ann.update(dict(extra_fields.items()))
208
+ else:
209
+ # Document citation
210
+ new_ann["type"] = "file_citation"
211
+
212
+ if extra_fields := annotation.get("extras"):
213
+ new_ann.update(dict(extra_fields.items()))
214
+
215
+ if "title" in annotation:
216
+ new_ann["filename"] = annotation["title"]
217
+
218
+ return new_ann
219
+
220
+ if annotation["type"] == "non_standard_annotation":
221
+ return annotation["value"]
222
+
223
+ return dict(annotation)
224
+
225
+
226
+ def _same_reasoning_item(first: dict[str, Any], second: dict[str, Any]) -> bool:
227
+ """Return whether two reasoning fragments share an item identity."""
228
+ first_has_id = "id" in first
229
+ second_has_id = "id" in second
230
+ return first_has_id == second_has_id and (
231
+ not first_has_id or first["id"] == second["id"]
232
+ )
233
+
234
+
235
+ def _implode_reasoning_blocks(blocks: list[dict[str, Any]]) -> Iterable[dict[str, Any]]:
236
+ i = 0
237
+ n = len(blocks)
238
+
239
+ while i < n:
240
+ block = blocks[i]
241
+
242
+ # Skip non-reasoning blocks or blocks already in Responses format
243
+ if block.get("type") != "reasoning" or "summary" in block:
244
+ yield dict(block)
245
+ i += 1
246
+ continue
247
+ elif "reasoning" not in block and "summary" not in block:
248
+ # {"type": "reasoning", "id": "rs_..."}
249
+ oai_format = {**block, "summary": []}
250
+ if "extras" in oai_format:
251
+ oai_format.update(oai_format.pop("extras"))
252
+ oai_format["type"] = oai_format.pop("type", "reasoning")
253
+ if "encrypted_content" in oai_format:
254
+ oai_format["encrypted_content"] = oai_format.pop("encrypted_content")
255
+ yield oai_format
256
+ i += 1
257
+ continue
258
+ else:
259
+ pass
260
+
261
+ summary: list[dict[str, str]] = [
262
+ {"type": "summary_text", "text": block.get("reasoning", "")}
263
+ ]
264
+ # 'common' is every field except the exploded 'reasoning'
265
+ common = {k: v for k, v in block.items() if k != "reasoning"}
266
+ if "extras" in common:
267
+ common.update(common.pop("extras"))
268
+
269
+ i += 1
270
+ while i < n:
271
+ next_ = blocks[i]
272
+ if (
273
+ next_.get("type") == "reasoning"
274
+ and "reasoning" in next_
275
+ and _same_reasoning_item(block, next_)
276
+ ):
277
+ summary.append(
278
+ {"type": "summary_text", "text": next_.get("reasoning", "")}
279
+ )
280
+ i += 1
281
+ else:
282
+ break
283
+
284
+ merged = dict(common)
285
+ merged["summary"] = summary
286
+ merged["type"] = merged.pop("type", "reasoning")
287
+ yield merged
288
+
289
+
290
+ def _consolidate_calls(items: Iterable[dict[str, Any]]) -> Iterator[dict[str, Any]]:
291
+ """Generator that walks through *items* and, whenever it meets the pair.
292
+
293
+ {"type": "server_tool_call", "name": "web_search", "id": X, ...}
294
+ {"type": "server_tool_result", "id": X}
295
+
296
+ merges them into
297
+
298
+ {"id": X,
299
+ "output": ...,
300
+ "status": ...,
301
+ "type": "web_search_call"}
302
+
303
+ keeping every other element untouched.
304
+ """
305
+ items = iter(items) # make sure we have a true iterator
306
+ for current in items:
307
+ # Only a call can start a pair worth collapsing
308
+ if current.get("type") != "server_tool_call":
309
+ yield current
310
+ continue
311
+
312
+ try:
313
+ nxt = next(items) # look-ahead one element
314
+ except StopIteration: # no "result" - just yield the call back
315
+ yield current
316
+ break
317
+
318
+ # If this really is the matching "result" - collapse
319
+ if nxt.get("type") == "server_tool_result" and nxt.get(
320
+ "tool_call_id"
321
+ ) == current.get("id"):
322
+ if current.get("name") == "web_search":
323
+ collapsed = {"id": current["id"]}
324
+ if "args" in current:
325
+ # N.B. as of 2025-09-17 OpenAI raises BadRequestError if sources
326
+ # are passed back in
327
+ collapsed["action"] = current["args"]
328
+
329
+ if status := nxt.get("status"):
330
+ if status == "success":
331
+ collapsed["status"] = "completed"
332
+ elif status == "error":
333
+ collapsed["status"] = "failed"
334
+ elif nxt.get("extras", {}).get("status"):
335
+ collapsed["status"] = nxt["extras"]["status"]
336
+ else:
337
+ pass
338
+ collapsed["type"] = "web_search_call"
339
+
340
+ if current.get("name") == "file_search":
341
+ collapsed = {"id": current["id"]}
342
+ if "args" in current and "queries" in current["args"]:
343
+ collapsed["queries"] = current["args"]["queries"]
344
+
345
+ if "output" in nxt:
346
+ collapsed["results"] = nxt["output"]
347
+ if status := nxt.get("status"):
348
+ if status == "success":
349
+ collapsed["status"] = "completed"
350
+ elif status == "error":
351
+ collapsed["status"] = "failed"
352
+ elif nxt.get("extras", {}).get("status"):
353
+ collapsed["status"] = nxt["extras"]["status"]
354
+ else:
355
+ pass
356
+ collapsed["type"] = "file_search_call"
357
+
358
+ elif current.get("name") == "code_interpreter":
359
+ collapsed = {"id": current["id"]}
360
+ if "args" in current and "code" in current["args"]:
361
+ collapsed["code"] = current["args"]["code"]
362
+ for key in ("container_id",):
363
+ if key in current:
364
+ collapsed[key] = current[key]
365
+ elif key in current.get("extras", {}):
366
+ collapsed[key] = current["extras"][key]
367
+ else:
368
+ pass
369
+
370
+ if "output" in nxt:
371
+ collapsed["outputs"] = nxt["output"]
372
+ if status := nxt.get("status"):
373
+ if status == "success":
374
+ collapsed["status"] = "completed"
375
+ elif status == "error":
376
+ collapsed["status"] = "failed"
377
+ elif nxt.get("extras", {}).get("status"):
378
+ collapsed["status"] = nxt["extras"]["status"]
379
+ collapsed["type"] = "code_interpreter_call"
380
+
381
+ elif current.get("name") == "remote_mcp":
382
+ collapsed = {"id": current["id"]}
383
+ if "args" in current:
384
+ collapsed["arguments"] = json.dumps(
385
+ current["args"], separators=(",", ":")
386
+ )
387
+ elif "arguments" in current.get("extras", {}):
388
+ collapsed["arguments"] = current["extras"]["arguments"]
389
+ else:
390
+ pass
391
+
392
+ if tool_name := current.get("extras", {}).get("tool_name"):
393
+ collapsed["name"] = tool_name
394
+ if server_label := current.get("extras", {}).get("server_label"):
395
+ collapsed["server_label"] = server_label
396
+ collapsed["type"] = "mcp_call"
397
+
398
+ if approval_id := current.get("extras", {}).get("approval_request_id"):
399
+ collapsed["approval_request_id"] = approval_id
400
+ if error := nxt.get("extras", {}).get("error"):
401
+ collapsed["error"] = error
402
+ if "output" in nxt:
403
+ collapsed["output"] = nxt["output"]
404
+ for k, v in current.get("extras", {}).items():
405
+ if k not in ("server_label", "arguments", "tool_name", "error"):
406
+ collapsed[k] = v
407
+
408
+ elif current.get("name") == "mcp_list_tools":
409
+ collapsed = {"id": current["id"]}
410
+ if server_label := current.get("extras", {}).get("server_label"):
411
+ collapsed["server_label"] = server_label
412
+ if "output" in nxt:
413
+ collapsed["tools"] = nxt["output"]
414
+ collapsed["type"] = "mcp_list_tools"
415
+ if error := nxt.get("extras", {}).get("error"):
416
+ collapsed["error"] = error
417
+ for k, v in current.get("extras", {}).items():
418
+ if k not in ("server_label", "error"):
419
+ collapsed[k] = v
420
+ else:
421
+ pass
422
+
423
+ yield collapsed
424
+
425
+ else:
426
+ # Not a matching pair - emit both, in original order
427
+ yield current
428
+ yield nxt
429
+
430
+
431
+ def _convert_from_v1_to_responses(
432
+ content: list[types.ContentBlock], tool_calls: list[types.ToolCall]
433
+ ) -> list[dict[str, Any]]:
434
+ new_content: list = []
435
+ for block in content:
436
+ if "type" not in block:
437
+ continue
438
+ if block["type"] == "text" and "annotations" in block:
439
+ # Need a copy because we're changing the annotations list
440
+ new_block = dict(block)
441
+ new_block["annotations"] = [
442
+ _convert_annotation_from_v1(a) for a in block["annotations"]
443
+ ]
444
+ new_content.append(new_block)
445
+ elif block["type"] == "tool_call":
446
+ new_block = {"type": "function_call", "call_id": block["id"]}
447
+ if "extras" in block and "item_id" in block["extras"]:
448
+ new_block["id"] = block["extras"]["item_id"]
449
+ if "name" in block:
450
+ new_block["name"] = block["name"]
451
+ if "extras" in block and "arguments" in block["extras"]:
452
+ new_block["arguments"] = block["extras"]["arguments"]
453
+ if any(key not in new_block for key in ("name", "arguments")):
454
+ matching_tool_calls = [
455
+ call for call in tool_calls if call["id"] == block["id"]
456
+ ]
457
+ if matching_tool_calls:
458
+ tool_call = matching_tool_calls[0]
459
+ if "name" not in new_block:
460
+ new_block["name"] = tool_call["name"]
461
+ if "arguments" not in new_block:
462
+ new_block["arguments"] = json.dumps(
463
+ tool_call["args"], separators=(",", ":")
464
+ )
465
+ if "extras" in block:
466
+ for extra_key in ("status", "namespace", "async"):
467
+ if extra_key in block["extras"]:
468
+ new_block[extra_key] = block["extras"][extra_key]
469
+ new_content.append(new_block)
470
+
471
+ elif block["type"] == "server_tool_call" and block.get("name") == "tool_search":
472
+ extras = block.get("extras", {})
473
+ new_block = {"id": block["id"]}
474
+ status = extras.get("status")
475
+ if status:
476
+ new_block["status"] = status
477
+ new_block["type"] = "tool_search_call"
478
+ if "args" in block:
479
+ new_block["arguments"] = block["args"]
480
+ execution = extras.get("execution")
481
+ if execution:
482
+ new_block["execution"] = execution
483
+ new_content.append(new_block)
484
+
485
+ elif (
486
+ block["type"] == "server_tool_result"
487
+ and block.get("extras", {}).get("name") == "tool_search"
488
+ ):
489
+ extras = block.get("extras", {})
490
+ new_block = {"id": block.get("tool_call_id", "")}
491
+ status = block.get("status")
492
+ if status == "success":
493
+ new_block["status"] = "completed"
494
+ elif status == "error":
495
+ new_block["status"] = "failed"
496
+ elif status:
497
+ new_block["status"] = status
498
+ new_block["type"] = "tool_search_output"
499
+ new_block["execution"] = "server"
500
+ output: dict = block.get("output", {})
501
+ if isinstance(output, dict) and "tools" in output:
502
+ new_block["tools"] = output["tools"]
503
+ new_content.append(new_block)
504
+
505
+ elif (
506
+ is_data_content_block(cast(dict, block))
507
+ and block["type"] == "image"
508
+ and "base64" in block
509
+ and isinstance(block.get("id"), str)
510
+ and block["id"].startswith("ig_")
511
+ ):
512
+ new_block = {"type": "image_generation_call", "result": block["base64"]}
513
+ for extra_key in ("id", "status"):
514
+ if extra_key in block:
515
+ new_block[extra_key] = block[extra_key] # type: ignore[literal-required]
516
+ elif extra_key in block.get("extras", {}):
517
+ new_block[extra_key] = block["extras"][extra_key]
518
+ new_content.append(new_block)
519
+ elif block["type"] == "non_standard" and "value" in block:
520
+ new_content.append(block["value"])
521
+ else:
522
+ new_content.append(block)
523
+
524
+ new_content = list(_implode_reasoning_blocks(new_content))
525
+ return list(_consolidate_calls(new_content))