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,183 @@
1
+ """Model profile types and utilities."""
2
+
3
+ import logging
4
+ import warnings
5
+ from typing import get_type_hints
6
+
7
+ from pydantic import ConfigDict
8
+ from typing_extensions import TypedDict
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class ModelProfile(TypedDict, total=False):
14
+ """Description of a chat model's capabilities, exposed via `model.profile`.
15
+
16
+ See the
17
+ [model profiles guide](https://docs.langchain.com/oss/python/langchain/models#model-profiles)
18
+ for concepts and usage. Data is sourced from
19
+ [models.dev](https://github.com/sst/models.dev), augmented with additional
20
+ fields, and generated by the
21
+ [`trustcoco-model-profiles`](https://github.com/langchain-ai/langchain/tree/master/libs/model-profiles)
22
+ package (via its `trustcoco-profiles` CLI).
23
+
24
+ !!! warning "Beta feature"
25
+
26
+ Fields and format are subject to change. This is a `total=False`
27
+ `TypedDict`, so any field may be absent — guard accesses with `.get()`.
28
+ """
29
+
30
+ __pydantic_config__ = ConfigDict(extra="allow") # type: ignore[misc]
31
+
32
+ # --- Model metadata ---
33
+
34
+ name: str
35
+ """Human-readable model name (e.g., `'GPT-5'`)."""
36
+
37
+ status: str
38
+ """Model lifecycle status (e.g., `'active'`, `'deprecated'`)."""
39
+
40
+ release_date: str
41
+ """Model release date (ISO 8601 format, e.g., `'2025-06-01'`)."""
42
+
43
+ last_updated: str
44
+ """Date the model was last updated (ISO 8601 format)."""
45
+
46
+ open_weights: bool
47
+ """Whether the model weights are openly available."""
48
+
49
+ # --- Input constraints ---
50
+
51
+ max_input_tokens: int
52
+ """Maximum context window (tokens)."""
53
+
54
+ text_inputs: bool
55
+ """Whether text inputs are supported."""
56
+
57
+ image_inputs: bool
58
+ """Whether image inputs are supported."""
59
+ # TODO: add more detail about formats?
60
+
61
+ image_url_inputs: bool
62
+ """Whether [image URL inputs](https://docs.langchain.com/oss/python/langchain/models#multimodal)
63
+ are supported."""
64
+
65
+ pdf_inputs: bool
66
+ """Whether [PDF inputs](https://docs.langchain.com/oss/python/langchain/models#multimodal)
67
+ are supported."""
68
+ # TODO: add more detail about formats? e.g. bytes or base64
69
+
70
+ audio_inputs: bool
71
+ """Whether [audio inputs](https://docs.langchain.com/oss/python/langchain/models#multimodal)
72
+ are supported."""
73
+ # TODO: add more detail about formats? e.g. bytes or base64
74
+
75
+ video_inputs: bool
76
+ """Whether [video inputs](https://docs.langchain.com/oss/python/langchain/models#multimodal)
77
+ are supported."""
78
+ # TODO: add more detail about formats? e.g. bytes or base64
79
+
80
+ image_tool_message: bool
81
+ """Whether images can be included in `ToolMessage` content."""
82
+
83
+ pdf_tool_message: bool
84
+ """Whether PDFs can be included in `ToolMessage` content."""
85
+
86
+ # --- Output constraints ---
87
+
88
+ max_output_tokens: int
89
+ """Maximum output tokens."""
90
+
91
+ reasoning_output: bool
92
+ """Whether the model supports [reasoning / chain-of-thought](https://docs.langchain.com/oss/python/langchain/models#reasoning)."""
93
+
94
+ reasoning_effort_levels: list[str]
95
+ """Supported reasoning-effort levels (e.g. `['low', 'medium', 'high']`).
96
+
97
+ Absent or empty if the model does not support a configurable reasoning
98
+ effort. Only meaningful when `reasoning_output` is `True`.
99
+ """
100
+
101
+ reasoning_effort_default: str
102
+ """The provider's documented default reasoning-effort level, if known.
103
+
104
+ Absent when no default is documented. Only meaningful when
105
+ `reasoning_effort_levels` is non-empty; not necessarily a member of
106
+ `reasoning_effort_levels` itself (a model may default to unconfigurable
107
+ behavior distinct from any explicit level).
108
+ """
109
+
110
+ text_outputs: bool
111
+ """Whether text outputs are supported."""
112
+
113
+ image_outputs: bool
114
+ """Whether [image outputs](https://docs.langchain.com/oss/python/langchain/models#multimodal)
115
+ are supported."""
116
+
117
+ audio_outputs: bool
118
+ """Whether [audio outputs](https://docs.langchain.com/oss/python/langchain/models#multimodal)
119
+ are supported."""
120
+
121
+ video_outputs: bool
122
+ """Whether [video outputs](https://docs.langchain.com/oss/python/langchain/models#multimodal)
123
+ are supported."""
124
+
125
+ # --- Tool calling ---
126
+ tool_calling: bool
127
+ """Whether the model supports [tool calling](https://docs.langchain.com/oss/python/langchain/models#tool-calling)."""
128
+
129
+ tool_choice: bool
130
+ """Whether the model supports [tool choice](https://docs.langchain.com/oss/python/langchain/models#forcing-tool-calls)."""
131
+
132
+ tool_call_streaming: bool
133
+ """Whether the model returns properly structured `tool_call_chunks` when streaming.
134
+
135
+ Only meaningful when `tool_calling` is `True`.
136
+ """
137
+
138
+ # --- Structured output ---
139
+ structured_output: bool
140
+ """Whether the model supports native [structured output](https://docs.langchain.com/oss/python/langchain/models#structured-outputs)."""
141
+
142
+ # --- Other capabilities ---
143
+
144
+ attachment: bool
145
+ """Whether the model supports file attachments."""
146
+
147
+ temperature: bool
148
+ """Whether the model supports a temperature parameter."""
149
+
150
+
151
+ ModelProfileRegistry = dict[str, ModelProfile]
152
+ """Registry mapping model identifiers or names to their ModelProfile."""
153
+
154
+
155
+ def _warn_unknown_profile_keys(profile: ModelProfile) -> None:
156
+ """Warn if `profile` contains keys not declared on `ModelProfile`.
157
+
158
+ Args:
159
+ profile: The model profile dict to check for undeclared keys.
160
+ """
161
+ if not isinstance(profile, dict):
162
+ return # type: ignore[unreachable]
163
+
164
+ try:
165
+ declared = frozenset(get_type_hints(ModelProfile).keys())
166
+ except (TypeError, NameError):
167
+ # get_type_hints raises NameError on unresolvable forward refs and
168
+ # TypeError when annotations evaluate to non-type objects.
169
+ logger.debug(
170
+ "Could not resolve type hints for ModelProfile; "
171
+ "skipping unknown-key check.",
172
+ exc_info=True,
173
+ )
174
+ return
175
+
176
+ extra = sorted(set(profile) - declared)
177
+ if extra:
178
+ warnings.warn(
179
+ f"Unrecognized keys in model profile: {extra}. "
180
+ f"This may indicate a version mismatch between trustcoco-core "
181
+ f"and your provider package. Consider upgrading trustcoco-core.",
182
+ stacklevel=2,
183
+ )
@@ -0,0 +1,44 @@
1
+ """**Load** module helps with serialization and deserialization."""
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from coco_core._import_utils import import_attr
6
+
7
+ if TYPE_CHECKING:
8
+ from coco_core.load.dump import dumpd, dumps
9
+ from coco_core.load.load import InitValidator, loads
10
+ from coco_core.load.serializable import Serializable
11
+
12
+ # Unfortunately, we have to eagerly import load from coco_core/load/load.py
13
+ # eagerly to avoid a namespace conflict. We want users to still be able to use
14
+ # `from coco_core.load import load` to get the load function, but
15
+ # the `from coco_core.load.load import load` absolute import should also work.
16
+ from coco_core.load.load import load
17
+
18
+ __all__ = (
19
+ "InitValidator",
20
+ "Serializable",
21
+ "dumpd",
22
+ "dumps",
23
+ "load",
24
+ "loads",
25
+ )
26
+
27
+ _dynamic_imports = {
28
+ "dumpd": "dump",
29
+ "dumps": "dump",
30
+ "InitValidator": "load",
31
+ "loads": "load",
32
+ "Serializable": "serializable",
33
+ }
34
+
35
+
36
+ def __getattr__(attr_name: str) -> object:
37
+ module_name = _dynamic_imports.get(attr_name)
38
+ result = import_attr(attr_name, module_name, __spec__.parent)
39
+ globals()[attr_name] = result
40
+ return result
41
+
42
+
43
+ def __dir__() -> list[str]:
44
+ return list(__all__)
@@ -0,0 +1,191 @@
1
+ """Validation utilities for Coco serialization.
2
+
3
+ Provides escape-based protection against injection attacks in serialized objects. The
4
+ approach uses an allowlist design: only dicts explicitly produced by
5
+ `Serializable.to_json()` are treated as LC objects during deserialization.
6
+
7
+ ## How escaping works
8
+
9
+ During serialization, plain dicts (user data) that contain an `'lc'` key are wrapped:
10
+
11
+ ```python
12
+ {"lc": 1, ...} # user data that looks like LC object
13
+ # becomes:
14
+ {"__lc_escaped__": {"lc": 1, ...}}
15
+ ```
16
+
17
+ During deserialization, escaped dicts are unwrapped and returned as plain dicts,
18
+ NOT instantiated as LC objects.
19
+ """
20
+
21
+ from typing import Any, cast
22
+
23
+ from coco_core.load.serializable import (
24
+ Serializable,
25
+ to_json_not_implemented,
26
+ )
27
+
28
+ _LC_ESCAPED_KEY = "__lc_escaped__"
29
+ """Sentinel key used to mark escaped user dicts during serialization.
30
+
31
+ When a plain dict contains 'lc' key (which could be confused with LC objects),
32
+ we wrap it as {"__lc_escaped__": {...original...}}.
33
+ """
34
+
35
+
36
+ def _needs_escaping(obj: dict[str, Any]) -> bool:
37
+ """Check if a dict needs escaping to prevent confusion with LC objects.
38
+
39
+ A dict needs escaping if:
40
+
41
+ 1. It has an `'lc'` key (could be confused with LC serialization format)
42
+ 2. It has only the escape key (would be mistaken for an escaped dict)
43
+ """
44
+ return "lc" in obj or (len(obj) == 1 and _LC_ESCAPED_KEY in obj)
45
+
46
+
47
+ def _escape_dict(obj: dict[str, Any]) -> dict[str, Any]:
48
+ """Wrap a dict in the escape marker.
49
+
50
+ Example:
51
+ ```python
52
+ {"key": "value"} # becomes {"__lc_escaped__": {"key": "value"}}
53
+ ```
54
+ """
55
+ return {_LC_ESCAPED_KEY: obj}
56
+
57
+
58
+ def _is_escaped_dict(obj: dict[str, Any]) -> bool:
59
+ """Check if a dict is an escaped user dict.
60
+
61
+ Example:
62
+ ```python
63
+ {"__lc_escaped__": {...}} # is an escaped dict
64
+ ```
65
+ """
66
+ return len(obj) == 1 and _LC_ESCAPED_KEY in obj
67
+
68
+
69
+ def _serialize_value(obj: Any) -> Any:
70
+ """Serialize a value with escaping of user dicts.
71
+
72
+ Called recursively on kwarg values to escape any plain dicts that could be confused
73
+ with LC objects.
74
+
75
+ Args:
76
+ obj: The value to serialize.
77
+
78
+ Returns:
79
+ The serialized value with user dicts escaped as needed.
80
+ """
81
+ if isinstance(obj, Serializable):
82
+ # This is an LC object - serialize it properly (not escaped)
83
+ return _serialize_lc_object(obj)
84
+ if isinstance(obj, dict):
85
+ if not all(isinstance(k, (str, int, float, bool, type(None))) for k in obj):
86
+ # if keys are not json serializable
87
+ return to_json_not_implemented(obj)
88
+ # Check if dict needs escaping BEFORE recursing into values.
89
+ # If it needs escaping, wrap it as-is - the contents are user data that
90
+ # will be returned as-is during deserialization (no instantiation).
91
+ # This prevents re-escaping of already-escaped nested content.
92
+ if _needs_escaping(obj):
93
+ return _escape_dict(obj)
94
+ # Safe dict (no 'lc' key) - recurse into values
95
+ return {k: _serialize_value(v) for k, v in obj.items()}
96
+ if isinstance(obj, (list, tuple)):
97
+ return [_serialize_value(item) for item in obj]
98
+ if isinstance(obj, (str, int, float, bool, type(None))):
99
+ return obj
100
+
101
+ # Non-JSON-serializable object (datetime, custom objects, etc.)
102
+ return to_json_not_implemented(obj)
103
+
104
+
105
+ def _get_secret_keys(obj: Serializable) -> set[str]:
106
+ """Return the merged set of constructor kwarg names declared as secrets.
107
+
108
+ Mirrors the MRO walk in `Serializable.to_json` so the keys returned here
109
+ match the keys whose values `_replace_secrets` rewrites into secret
110
+ markers. Used by `_serialize_lc_object` to decide which kwargs to skip
111
+ when escaping user data.
112
+ """
113
+ secrets: dict[str, str] = {}
114
+ model_fields = type(obj).model_fields
115
+ for cls in [None, *obj.__class__.mro()]:
116
+ if cls is Serializable:
117
+ break
118
+ this = cast("Serializable", obj if cls is None else super(cls, obj))
119
+ secrets.update(this.lc_secrets)
120
+ for key in list(secrets):
121
+ if (key in model_fields) and (alias := model_fields[key].alias) is not None:
122
+ secrets[alias] = secrets[key]
123
+ return set(secrets)
124
+
125
+
126
+ def _serialize_lc_object(obj: Any) -> dict[str, Any]:
127
+ """Serialize a `Serializable` object with escaping of user data in kwargs.
128
+
129
+ Args:
130
+ obj: The `Serializable` object to serialize.
131
+
132
+ Returns:
133
+ The serialized dict with user data in kwargs escaped as needed.
134
+
135
+ Note:
136
+ Kwargs values are processed with `_serialize_value` to escape user data
137
+ (like metadata) that contains `'lc'` keys. Secret fields are identified
138
+ by the class's declared `lc_secrets` and skipped because `to_json()`
139
+ already converted their values to secret markers.
140
+
141
+ The check is key-based rather than shape-based. A shape-based check
142
+ ("this dict looks like a secret marker") can be forged by user data,
143
+ letting attacker-controlled free-form dicts bypass escaping and reach
144
+ the Reviver.
145
+ """
146
+ if not isinstance(obj, Serializable):
147
+ msg = f"Expected Serializable, got {type(obj)}"
148
+ raise TypeError(msg)
149
+
150
+ serialized: dict[str, Any] = dict(obj.to_json())
151
+
152
+ # Process kwargs to escape user data that could be confused with LC objects.
153
+ # Skip kwargs declared as secrets - `to_json()` already replaced their
154
+ # values with secret markers via `_replace_secrets`.
155
+ if serialized.get("type") == "constructor" and "kwargs" in serialized:
156
+ secret_keys = _get_secret_keys(obj)
157
+ serialized["kwargs"] = {
158
+ k: v if k in secret_keys else _serialize_value(v)
159
+ for k, v in serialized["kwargs"].items()
160
+ }
161
+
162
+ return serialized
163
+
164
+
165
+ def _unescape_value(obj: Any) -> Any:
166
+ """Unescape a value, processing escape markers in dict values and lists.
167
+
168
+ When an escaped dict is encountered (`{"__lc_escaped__": ...}`), it's
169
+ unwrapped and the contents are returned AS-IS (no further processing).
170
+ The contents represent user data that should not be modified.
171
+
172
+ For regular dicts and lists, we recurse to find any nested escape markers.
173
+
174
+ Args:
175
+ obj: The value to unescape.
176
+
177
+ Returns:
178
+ The unescaped value.
179
+ """
180
+ if isinstance(obj, dict):
181
+ if _is_escaped_dict(obj):
182
+ # Unwrap and return the user data as-is (no further unescaping).
183
+ # The contents are user data that may contain more escape keys,
184
+ # but those are part of the user's actual data.
185
+ return obj[_LC_ESCAPED_KEY]
186
+
187
+ # Regular dict - recurse into values to find nested escape markers
188
+ return {k: _unescape_value(v) for k, v in obj.items()}
189
+ if isinstance(obj, list):
190
+ return [_unescape_value(item) for item in obj]
191
+ return obj
coco_core/load/dump.py ADDED
@@ -0,0 +1,120 @@
1
+ """Serialize Coco objects to JSON.
2
+
3
+ Provides `dumps` (to JSON string) and `dumpd` (to dict) for serializing
4
+ `Serializable` objects.
5
+
6
+ ## Escaping
7
+
8
+ During serialization, plain dicts (user data) that contain an `'lc'` key are escaped
9
+ by wrapping them: `{"__lc_escaped__": {...original...}}`. This prevents injection
10
+ attacks where malicious data could trick the deserializer into instantiating
11
+ arbitrary classes. The escape marker is removed during deserialization.
12
+
13
+ This is an allowlist approach: only dicts explicitly produced by
14
+ `Serializable.to_json()` are treated as LC objects; everything else is escaped if it
15
+ could be confused with the LC format.
16
+ """
17
+
18
+ import json
19
+ from typing import Any
20
+
21
+ from pydantic import BaseModel
22
+
23
+ from coco_core.load._validation import _serialize_value
24
+ from coco_core.load.serializable import Serializable, to_json_not_implemented
25
+ from coco_core.messages import AIMessage
26
+ from coco_core.outputs import ChatGeneration
27
+
28
+
29
+ def default(obj: Any) -> Any:
30
+ """Return a default value for an object.
31
+
32
+ Args:
33
+ obj: The object to serialize to json if it is a Serializable object.
34
+
35
+ Returns:
36
+ A JSON serializable object or a SerializedNotImplemented object.
37
+ """
38
+ if isinstance(obj, Serializable):
39
+ return obj.to_json()
40
+ return to_json_not_implemented(obj)
41
+
42
+
43
+ def _dump_pydantic_models(obj: Any) -> Any:
44
+ """Convert nested Pydantic models to dicts for JSON serialization.
45
+
46
+ Handles the special case where a `ChatGeneration` contains an `AIMessage`
47
+ with a parsed Pydantic model in `additional_kwargs["parsed"]`. Since
48
+ Pydantic models aren't directly JSON serializable, this converts them to
49
+ dicts.
50
+
51
+ Args:
52
+ obj: The object to process.
53
+
54
+ Returns:
55
+ A copy of the object with nested Pydantic models converted to dicts, or
56
+ the original object unchanged if no conversion was needed.
57
+ """
58
+ if (
59
+ isinstance(obj, ChatGeneration)
60
+ and isinstance(obj.message, AIMessage)
61
+ and (parsed := obj.message.additional_kwargs.get("parsed"))
62
+ and isinstance(parsed, BaseModel)
63
+ ):
64
+ obj_copy = obj.model_copy(deep=True)
65
+ obj_copy.message.additional_kwargs["parsed"] = parsed.model_dump()
66
+ return obj_copy
67
+ return obj
68
+
69
+
70
+ def dumps(obj: Any, *, pretty: bool = False, **kwargs: Any) -> str:
71
+ """Return a JSON string representation of an object.
72
+
73
+ Note:
74
+ Plain dicts containing an `'lc'` key are automatically escaped to prevent
75
+ confusion with LC serialization format. The escape marker is removed during
76
+ deserialization.
77
+
78
+ Args:
79
+ obj: The object to dump.
80
+ pretty: Whether to pretty print the json.
81
+
82
+ If `True`, the json will be indented by either 2 spaces or the amount
83
+ provided in the `indent` kwarg.
84
+ **kwargs: Additional arguments to pass to `json.dumps`
85
+
86
+ Returns:
87
+ A JSON string representation of the object.
88
+
89
+ Raises:
90
+ ValueError: If `default` is passed as a kwarg.
91
+ """
92
+ if "default" in kwargs:
93
+ msg = "`default` should not be passed to dumps"
94
+ raise ValueError(msg)
95
+
96
+ obj = _dump_pydantic_models(obj)
97
+ serialized = _serialize_value(obj)
98
+
99
+ if pretty:
100
+ indent = kwargs.pop("indent", 2)
101
+ return json.dumps(serialized, indent=indent, **kwargs)
102
+ return json.dumps(serialized, **kwargs)
103
+
104
+
105
+ def dumpd(obj: Any) -> Any:
106
+ """Return a dict representation of an object.
107
+
108
+ Note:
109
+ Plain dicts containing an `'lc'` key are automatically escaped to prevent
110
+ confusion with LC serialization format. The escape marker is removed during
111
+ deserialization.
112
+
113
+ Args:
114
+ obj: The object to dump.
115
+
116
+ Returns:
117
+ Dictionary that can be serialized to json using `json.dumps`.
118
+ """
119
+ obj = _dump_pydantic_models(obj)
120
+ return _serialize_value(obj)