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,518 @@
1
+ """Base message."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any, cast, overload
6
+
7
+ from pydantic import ConfigDict, Field
8
+
9
+ from coco_core._api.deprecation import warn_deprecated
10
+ from coco_core.load.serializable import Serializable
11
+ from coco_core.messages import content as types
12
+ from coco_core.utils import get_bolded_text
13
+ from coco_core.utils._merge import merge_dicts, merge_lists
14
+ from coco_core.utils.interactive_env import is_interactive_env
15
+
16
+ if TYPE_CHECKING:
17
+ from collections.abc import Sequence
18
+
19
+ from typing_extensions import Self
20
+
21
+ from coco_core.prompts.chat import ChatPromptTemplate
22
+
23
+
24
+ def _extract_reasoning_from_additional_kwargs(
25
+ message: BaseMessage,
26
+ ) -> types.ReasoningContentBlock | None:
27
+ """Extract `reasoning_content` from `additional_kwargs`.
28
+
29
+ Handles reasoning content stored in various formats:
30
+ - `additional_kwargs["reasoning_content"]` (string) - Ollama, DeepSeek, XAI, Groq
31
+
32
+ Args:
33
+ message: The message to extract reasoning from.
34
+
35
+ Returns:
36
+ A `ReasoningContentBlock` if reasoning content is found, None otherwise.
37
+ """
38
+ additional_kwargs = getattr(message, "additional_kwargs", {})
39
+
40
+ reasoning_content = additional_kwargs.get("reasoning_content")
41
+ if reasoning_content is not None and isinstance(reasoning_content, str):
42
+ return {"type": "reasoning", "reasoning": reasoning_content}
43
+
44
+ return None
45
+
46
+
47
+ class TextAccessor(str):
48
+ """String-like object that supports both property and method access patterns.
49
+
50
+ Exists to maintain backward compatibility while transitioning from method-based to
51
+ property-based text access in message objects. In Coco <v1.0, message text was
52
+ accessed via `.text()` method calls. In v1.0=<, the preferred pattern is property
53
+ access via `.text`.
54
+
55
+ Rather than breaking existing code immediately, `TextAccessor` allows both
56
+ patterns:
57
+ - Modern property access: `message.text` (returns string directly)
58
+ - Legacy method access: `message.text()` (callable, emits deprecation warning)
59
+
60
+ """
61
+
62
+ __slots__ = ()
63
+
64
+ def __new__(cls, value: str) -> Self:
65
+ """Create new TextAccessor instance."""
66
+ return str.__new__(cls, value)
67
+
68
+ def __call__(self) -> str:
69
+ """Enable method-style text access for backward compatibility.
70
+
71
+ This method exists solely to support legacy code that calls `.text()`
72
+ as a method. New code should use property access (`.text`) instead.
73
+
74
+ !!! deprecated
75
+ As of `trustcoco-core` 1.0.0, calling `.text()` as a method is deprecated.
76
+ Use `.text` as a property instead. This method will be removed in 2.0.0.
77
+
78
+ Returns:
79
+ The string content, identical to property access.
80
+
81
+ """
82
+ warn_deprecated(
83
+ since="1.0.0",
84
+ message=(
85
+ "Calling .text() as a method is deprecated. "
86
+ "Use .text as a property instead (e.g., message.text)."
87
+ ),
88
+ removal="2.0.0",
89
+ )
90
+ return str(self)
91
+
92
+
93
+ class BaseMessage(Serializable):
94
+ """Base abstract message class.
95
+
96
+ Messages are the inputs and outputs of a chat model.
97
+
98
+ Examples include [`HumanMessage`][coco.messages.HumanMessage],
99
+ [`AIMessage`][coco.messages.AIMessage], and
100
+ [`SystemMessage`][coco.messages.SystemMessage].
101
+ """
102
+
103
+ content: str | list[str | dict[Any, Any]]
104
+ """The contents of the message."""
105
+
106
+ additional_kwargs: dict[Any, Any] = Field(default_factory=dict)
107
+ """Reserved for additional payload data associated with the message.
108
+
109
+ For example, for a message from an AI, this could include tool calls as
110
+ encoded by the model provider.
111
+
112
+ """
113
+
114
+ response_metadata: dict[Any, Any] = Field(default_factory=dict)
115
+ """Examples: response headers, logprobs, token counts, model name."""
116
+
117
+ type: str
118
+ """The type of the message. Must be a string that is unique to the message type.
119
+
120
+ The purpose of this field is to allow for easy identification of the message type
121
+ when deserializing messages.
122
+
123
+ """
124
+
125
+ name: str | None = None
126
+ """An optional name for the message.
127
+
128
+ This can be used to provide a human-readable name for the message.
129
+
130
+ Usage of this field is optional, and whether it's used or not is up to the
131
+ model implementation.
132
+
133
+ """
134
+
135
+ id: str | None = Field(default=None, coerce_numbers_to_str=True)
136
+ """An optional unique identifier for the message.
137
+
138
+ This should ideally be provided by the provider/model which created the message.
139
+
140
+ """
141
+
142
+ model_config = ConfigDict(
143
+ extra="allow",
144
+ )
145
+
146
+ @overload
147
+ def __init__(
148
+ self,
149
+ content: str | list[str | dict[Any, Any]],
150
+ **kwargs: Any,
151
+ ) -> None: ...
152
+
153
+ @overload
154
+ def __init__(
155
+ self,
156
+ content: str | list[str | dict[Any, Any]] | None = None,
157
+ content_blocks: list[types.ContentBlock] | None = None,
158
+ **kwargs: Any,
159
+ ) -> None: ...
160
+
161
+ def __init__(
162
+ self,
163
+ content: str | list[str | dict[Any, Any]] | None = None,
164
+ content_blocks: list[types.ContentBlock] | None = None,
165
+ **kwargs: Any,
166
+ ) -> None:
167
+ """Initialize a `BaseMessage`.
168
+
169
+ Specify `content` as positional arg or `content_blocks` for typing.
170
+
171
+ Args:
172
+ content: The contents of the message.
173
+ content_blocks: Typed standard content.
174
+ **kwargs: Additional arguments to pass to the parent class.
175
+ """
176
+ if content_blocks is not None:
177
+ super().__init__(content=content_blocks, **kwargs)
178
+ else:
179
+ super().__init__(content=content, **kwargs)
180
+
181
+ @classmethod
182
+ def is_lc_serializable(cls) -> bool:
183
+ """`BaseMessage` is serializable.
184
+
185
+ Returns:
186
+ True
187
+ """
188
+ return True
189
+
190
+ @classmethod
191
+ def get_lc_namespace(cls) -> list[str]:
192
+ """Get the namespace of the Coco object.
193
+
194
+ Returns:
195
+ `["coco", "schema", "messages"]`
196
+ """
197
+ return ["coco", "schema", "messages"]
198
+
199
+ @property
200
+ def content_blocks(self) -> list[types.ContentBlock]:
201
+ r"""Load content blocks from the message content.
202
+
203
+ !!! version-added "Added in `trustcoco-core` 1.0.0"
204
+
205
+ """
206
+ # Needed here to avoid circular import, as these classes import BaseMessages
207
+ from coco_core.messages.block_translators.anthropic import ( # noqa: PLC0415
208
+ _convert_to_v1_from_anthropic_input,
209
+ )
210
+ from coco_core.messages.block_translators.bedrock_converse import ( # noqa: PLC0415
211
+ _convert_to_v1_from_converse_input,
212
+ )
213
+ from coco_core.messages.block_translators.google_genai import ( # noqa: PLC0415
214
+ _convert_to_v1_from_genai_input,
215
+ )
216
+ from coco_core.messages.block_translators.coco_v0 import ( # noqa: PLC0415
217
+ _convert_v0_multimodal_input_to_v1,
218
+ )
219
+ from coco_core.messages.block_translators.openai import ( # noqa: PLC0415
220
+ _convert_to_v1_from_chat_completions_input,
221
+ )
222
+
223
+ blocks: list[types.ContentBlock] = []
224
+ content = (
225
+ # Transpose string content to list, otherwise assumed to be list
226
+ [self.content]
227
+ if isinstance(self.content, str) and self.content
228
+ else self.content
229
+ )
230
+ for item in content:
231
+ if isinstance(item, str):
232
+ # Plain string content is treated as a text block
233
+ blocks.append({"type": "text", "text": item})
234
+ elif isinstance(item, dict):
235
+ item_type = item.get("type")
236
+ if item_type not in types.KNOWN_BLOCK_TYPES:
237
+ # Handle all provider-specific or None type blocks as non-standard -
238
+ # we'll come back to these later
239
+ blocks.append({"type": "non_standard", "value": item})
240
+ else:
241
+ # Guard against v0 blocks that share the same `type` keys
242
+ if "source_type" in item:
243
+ blocks.append({"type": "non_standard", "value": item})
244
+ continue
245
+
246
+ # This can't be a v0 block (since they require `source_type`),
247
+ # so it's a known v1 block type
248
+ blocks.append(cast("types.ContentBlock", item))
249
+
250
+ # Subsequent passes: attempt to unpack non-standard blocks.
251
+ # This is the last stop - if we can't parse it here, it is left as non-standard
252
+ for parsing_step in [
253
+ _convert_v0_multimodal_input_to_v1,
254
+ _convert_to_v1_from_chat_completions_input,
255
+ _convert_to_v1_from_anthropic_input,
256
+ _convert_to_v1_from_genai_input,
257
+ _convert_to_v1_from_converse_input,
258
+ ]:
259
+ blocks = parsing_step(blocks)
260
+ return blocks
261
+
262
+ @property
263
+ def text(self) -> TextAccessor:
264
+ """Get the text content of the message as a string.
265
+
266
+ Can be used as both property (`message.text`) and method (`message.text()`).
267
+
268
+ Handles both string and list content types (e.g. for content blocks). Only
269
+ extracts blocks with `type: 'text'`; other block types are ignored.
270
+
271
+ !!! deprecated
272
+ As of `trustcoco-core` 1.0.0, calling `.text()` as a method is deprecated.
273
+ Use `.text` as a property instead. This method will be removed in 2.0.0.
274
+
275
+ Returns:
276
+ The text content of the message.
277
+
278
+ """
279
+ if isinstance(self.content, str):
280
+ text_value = self.content
281
+ else:
282
+ # Must be a list
283
+ blocks = [
284
+ block
285
+ for block in self.content
286
+ if isinstance(block, str)
287
+ or (block.get("type") == "text" and isinstance(block.get("text"), str))
288
+ ]
289
+ text_value = "".join(
290
+ block if isinstance(block, str) else block["text"] for block in blocks
291
+ )
292
+ return TextAccessor(text_value)
293
+
294
+ def __add__(self, other: Any) -> ChatPromptTemplate:
295
+ """Concatenate this message with another message.
296
+
297
+ Args:
298
+ other: Another message to concatenate with this one.
299
+
300
+ Returns:
301
+ A ChatPromptTemplate containing both messages.
302
+ """
303
+ # Import locally to prevent circular imports.
304
+ from coco_core.prompts.chat import ChatPromptTemplate # noqa: PLC0415
305
+
306
+ prompt = ChatPromptTemplate(messages=[self])
307
+ return prompt.__add__(other)
308
+
309
+ def pretty_repr(
310
+ self,
311
+ html: bool = False, # noqa: FBT001,FBT002
312
+ ) -> str:
313
+ """Get a pretty representation of the message.
314
+
315
+ Args:
316
+ html: Whether to format the message as HTML. If `True`, the message will be
317
+ formatted with HTML tags.
318
+
319
+ Returns:
320
+ A pretty representation of the message.
321
+
322
+ Example:
323
+ ```python
324
+ from coco_core.messages import HumanMessage
325
+
326
+ msg = HumanMessage(content="What is the capital of France?")
327
+ print(msg.pretty_repr())
328
+ ```
329
+
330
+ Results in:
331
+
332
+ ```txt
333
+ ================================ Human Message =================================
334
+
335
+ What is the capital of France?
336
+ ```
337
+ """ # noqa: E501
338
+ title = get_msg_title_repr(self.type.title() + " Message", bold=html)
339
+ # TODO: handle non-string content.
340
+ if self.name is not None:
341
+ title += f"\nName: {self.name}"
342
+ return f"{title}\n\n{self.content}"
343
+
344
+ def pretty_print(self) -> None:
345
+ """Print a pretty representation of the message.
346
+
347
+ Example:
348
+ ```python
349
+ from coco_core.messages import AIMessage
350
+
351
+ msg = AIMessage(content="The capital of France is Paris.")
352
+ msg.pretty_print()
353
+ ```
354
+
355
+ Results in:
356
+
357
+ ```txt
358
+ ================================== Ai Message ==================================
359
+
360
+ The capital of France is Paris.
361
+ ```
362
+ """ # noqa: E501
363
+ print(self.pretty_repr(html=is_interactive_env())) # noqa: T201
364
+
365
+
366
+ def merge_content(
367
+ first_content: str | list[str | dict[Any, Any]],
368
+ *contents: str | list[str | dict[Any, Any]],
369
+ ) -> str | list[str | dict[Any, Any]]:
370
+ """Merge multiple message contents.
371
+
372
+ Args:
373
+ first_content: The first `content`. Can be a string or a list.
374
+ contents: The other `content`s. Can be a string or a list.
375
+
376
+ Returns:
377
+ The merged content.
378
+
379
+ """
380
+ merged: str | list[str | dict[Any, Any]]
381
+ merged = "" if first_content is None else first_content
382
+
383
+ for content in contents:
384
+ # If current is a string
385
+ if isinstance(merged, str):
386
+ # If the next chunk is also a string, then merge them naively
387
+ if isinstance(content, str):
388
+ merged += content
389
+ # If the next chunk is a list, add the current to the start of the list
390
+ else:
391
+ merged = [merged, *content]
392
+ elif isinstance(content, list):
393
+ # If both are lists
394
+ merged = merge_lists(merged, content) # type: ignore[assignment]
395
+ # If the first content is a list, and the second content is a string
396
+ # If the last element of the first content is a string
397
+ # Add the second content to the last element
398
+ elif merged and isinstance(merged[-1], str):
399
+ merged[-1] += content
400
+ # If second content is an empty string, treat as a no-op
401
+ elif content == "":
402
+ pass
403
+ # Otherwise, add the second content as a new element of the list
404
+ elif merged:
405
+ merged.append(content)
406
+ return merged
407
+
408
+
409
+ class BaseMessageChunk(BaseMessage):
410
+ """Message chunk, which can be concatenated with other Message chunks."""
411
+
412
+ def __add__(self, other: Any) -> BaseMessageChunk: # type: ignore[override]
413
+ """Message chunks support concatenation with other message chunks.
414
+
415
+ This functionality is useful to combine message chunks yielded from
416
+ a streaming model into a complete message.
417
+
418
+ Args:
419
+ other: Another message chunk to concatenate with this one.
420
+
421
+ Returns:
422
+ A new message chunk that is the concatenation of this message chunk
423
+ and the other message chunk.
424
+
425
+ Raises:
426
+ TypeError: If the other object is not a message chunk.
427
+
428
+ Example:
429
+ ```txt
430
+ AIMessageChunk(content="Hello", ...)
431
+ + AIMessageChunk(content=" World", ...)
432
+ = AIMessageChunk(content="Hello World", ...)
433
+ ```
434
+ """
435
+ if isinstance(other, BaseMessageChunk):
436
+ # If both are (subclasses of) BaseMessageChunk,
437
+ # concat into a single BaseMessageChunk
438
+
439
+ return self.__class__(
440
+ id=self.id,
441
+ type=self.type,
442
+ content=merge_content(self.content, other.content),
443
+ additional_kwargs=merge_dicts(
444
+ self.additional_kwargs, other.additional_kwargs
445
+ ),
446
+ response_metadata=merge_dicts(
447
+ self.response_metadata, other.response_metadata
448
+ ),
449
+ )
450
+ if isinstance(other, list) and all(
451
+ isinstance(o, BaseMessageChunk) for o in other
452
+ ):
453
+ content = merge_content(self.content, *(o.content for o in other))
454
+ additional_kwargs = merge_dicts(
455
+ self.additional_kwargs, *(o.additional_kwargs for o in other)
456
+ )
457
+ response_metadata = merge_dicts(
458
+ self.response_metadata, *(o.response_metadata for o in other)
459
+ )
460
+ return self.__class__( # type: ignore[call-arg]
461
+ id=self.id,
462
+ content=content,
463
+ additional_kwargs=additional_kwargs,
464
+ response_metadata=response_metadata,
465
+ )
466
+ msg = (
467
+ 'unsupported operand type(s) for +: "'
468
+ f"{self.__class__.__name__}"
469
+ f'" and "{other.__class__.__name__}"'
470
+ )
471
+ raise TypeError(msg)
472
+
473
+
474
+ def message_to_dict(message: BaseMessage) -> dict[str, Any]:
475
+ """Convert a Message to a dictionary.
476
+
477
+ Args:
478
+ message: Message to convert.
479
+
480
+ Returns:
481
+ Message as a dict. The dict will have a `type` key with the message type
482
+ and a `data` key with the message data as a dict.
483
+
484
+ """
485
+ return {"type": message.type, "data": message.model_dump()}
486
+
487
+
488
+ def messages_to_dict(messages: Sequence[BaseMessage]) -> list[dict[str, Any]]:
489
+ """Convert a sequence of Messages to a list of dictionaries.
490
+
491
+ Args:
492
+ messages: Sequence of messages (as `BaseMessage`s) to convert.
493
+
494
+ Returns:
495
+ List of messages as dicts.
496
+
497
+ """
498
+ return [message_to_dict(m) for m in messages]
499
+
500
+
501
+ def get_msg_title_repr(title: str, *, bold: bool = False) -> str:
502
+ """Get a title representation for a message.
503
+
504
+ Args:
505
+ title: The title.
506
+ bold: Whether to bold the title.
507
+
508
+ Returns:
509
+ The title representation.
510
+
511
+ """
512
+ padded = " " + title + " "
513
+ sep_len = (80 - len(padded)) // 2
514
+ sep = "=" * sep_len
515
+ second_sep = sep + "=" if len(padded) % 2 else sep
516
+ if bold:
517
+ padded = get_bolded_text(padded)
518
+ return f"{sep}{padded}{second_sep}"
@@ -0,0 +1,112 @@
1
+ """Derivations of standard content blocks from provider content.
2
+
3
+ `AIMessage` will first attempt to use a provider-specific translator if
4
+ `model_provider` is set in `response_metadata` on the message. Consequently, each
5
+ provider translator must handle all possible content response types from the provider,
6
+ including text.
7
+
8
+ If no provider is set, or if the provider does not have a registered translator,
9
+ `AIMessage` will fall back to best-effort parsing of the content into blocks using
10
+ the implementation in `BaseMessage`.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from typing import TYPE_CHECKING
16
+
17
+ if TYPE_CHECKING:
18
+ from collections.abc import Callable
19
+
20
+ from coco_core.messages import AIMessage, AIMessageChunk
21
+ from coco_core.messages import content as types
22
+
23
+ # Provider to translator mapping
24
+ PROVIDER_TRANSLATORS: dict[str, dict[str, Callable[..., list[types.ContentBlock]]]] = {}
25
+ """Map model provider names to translator functions.
26
+
27
+ The dictionary maps provider names (e.g. `'openai'`, `'anthropic'`) to another
28
+ dictionary with two keys:
29
+ - `'translate_content'`: Function to translate `AIMessage` content.
30
+ - `'translate_content_chunk'`: Function to translate `AIMessageChunk` content.
31
+
32
+ When calling `content_blocks` on an `AIMessage` or `AIMessageChunk`, if
33
+ `model_provider` is set in `response_metadata`, the corresponding translator
34
+ functions will be used to parse the content into blocks. Otherwise, best-effort parsing
35
+ in `BaseMessage` will be used.
36
+ """
37
+
38
+
39
+ def register_translator(
40
+ provider: str,
41
+ translate_content: Callable[[AIMessage], list[types.ContentBlock]],
42
+ translate_content_chunk: Callable[[AIMessageChunk], list[types.ContentBlock]],
43
+ ) -> None:
44
+ """Register content translators for a provider in `PROVIDER_TRANSLATORS`.
45
+
46
+ Args:
47
+ provider: The model provider name (e.g. `'openai'`, `'anthropic'`).
48
+ translate_content: Function to translate `AIMessage` content.
49
+ translate_content_chunk: Function to translate `AIMessageChunk` content.
50
+ """
51
+ PROVIDER_TRANSLATORS[provider] = {
52
+ "translate_content": translate_content,
53
+ "translate_content_chunk": translate_content_chunk,
54
+ }
55
+
56
+
57
+ def get_translator(
58
+ provider: str,
59
+ ) -> dict[str, Callable[..., list[types.ContentBlock]]] | None:
60
+ """Get the translator functions for a provider.
61
+
62
+ Args:
63
+ provider: The model provider name.
64
+
65
+ Returns:
66
+ Dictionary with `'translate_content'` and `'translate_content_chunk'`
67
+ functions, or None if no translator is registered for the provider. In such
68
+ case, best-effort parsing in `BaseMessage` will be used.
69
+ """
70
+ return PROVIDER_TRANSLATORS.get(provider)
71
+
72
+
73
+ def _register_translators() -> None:
74
+ """Register all translators in trustcoco-core.
75
+
76
+ A unit test ensures all modules in `block_translators` are represented here.
77
+
78
+ For translators implemented outside trustcoco-core, they can be registered by
79
+ calling `register_translator` from within the integration package.
80
+ """
81
+ from coco_core.messages.block_translators.anthropic import ( # noqa: PLC0415
82
+ _register_anthropic_translator,
83
+ )
84
+ from coco_core.messages.block_translators.bedrock import ( # noqa: PLC0415
85
+ _register_bedrock_translator,
86
+ )
87
+ from coco_core.messages.block_translators.bedrock_converse import ( # noqa: PLC0415
88
+ _register_bedrock_converse_translator,
89
+ )
90
+ from coco_core.messages.block_translators.google_genai import ( # noqa: PLC0415
91
+ _register_google_genai_translator,
92
+ )
93
+ from coco_core.messages.block_translators.google_vertexai import ( # noqa: PLC0415
94
+ _register_google_vertexai_translator,
95
+ )
96
+ from coco_core.messages.block_translators.groq import ( # noqa: PLC0415
97
+ _register_groq_translator,
98
+ )
99
+ from coco_core.messages.block_translators.openai import ( # noqa: PLC0415
100
+ _register_openai_translator,
101
+ )
102
+
103
+ _register_bedrock_translator()
104
+ _register_bedrock_converse_translator()
105
+ _register_anthropic_translator()
106
+ _register_google_genai_translator()
107
+ _register_google_vertexai_translator()
108
+ _register_groq_translator()
109
+ _register_openai_translator()
110
+
111
+
112
+ _register_translators()