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
coco_core/load/load.py ADDED
@@ -0,0 +1,828 @@
1
+ """Load Coco objects from JSON strings or objects.
2
+
3
+ ## How it works
4
+
5
+ Each `Serializable` Coco object has a unique identifier (its "class path"), which
6
+ is a list of strings representing the module path and class name. For example:
7
+
8
+ - `AIMessage` -> `["coco_core", "messages", "ai", "AIMessage"]`
9
+ - `ChatPromptTemplate` -> `["coco_core", "prompts", "chat", "ChatPromptTemplate"]`
10
+
11
+ When deserializing, the class path from the JSON `'id'` field is checked against an
12
+ allowlist. If the class is not in the allowlist, deserialization raises a `ValueError`.
13
+
14
+ ## Threat model
15
+
16
+ A serialized Coco payload crosses a trust boundary because the manifest
17
+ may contain serialized objects and configuration that affect runtime behavior.
18
+ For example, a payload can configure a chat model with a custom `base_url`,
19
+ custom headers, a different model name, or other constructor arguments. These
20
+ are supported features, but they also mean the payload contents should be
21
+ treated as executable configuration rather than plain text.
22
+
23
+ Concretely, deserialization instantiates Python objects, so any constructor
24
+ (`__init__`) or validator on an allowed class can run during `load()`. A
25
+ crafted payload that is allowed to reach an unintended class — or an intended
26
+ class with attacker-controlled kwargs — could cause network calls, file
27
+ operations, or environment-variable access while the object is being built.
28
+
29
+ !!! warning "Do not use with untrusted input"
30
+
31
+ If the source is untrusted, avoid calling `load()` / `loads()` on it. If
32
+ you must, restrict `allowed_objects` to types that do not execute logic
33
+ during init — `allowed_objects='messages'` (or an explicit list of
34
+ message classes) is the safe choice. Keep `secrets_from_env=False`.
35
+
36
+ The `allowed_objects` parameter controls which classes can be deserialized:
37
+
38
+ - **Explicit list of classes** (recommended for untrusted input): only those
39
+ specific classes are allowed.
40
+ - **`'messages'`**: chat-message classes only (e.g. `AIMessage`,
41
+ `HumanMessage`). Safe for untrusted input.
42
+ - **`'core'` (current default)** — *unsafe with untrusted manifests.*
43
+ Classes defined in the serialization mappings under `coco_core`
44
+ (messages, documents, prompts, etc.).
45
+ - **`'all'`** — *unsafe with untrusted manifests.* Every class in the
46
+ serialization mappings, including partner chat models and LLMs and their
47
+ constructor kwargs (endpoint URLs, headers, model names, etc.).
48
+
49
+ !!! note "Side effects in allowed classes"
50
+
51
+ Deserialization calls `__init__` on allowed classes. If those classes perform
52
+ side effects during initialization (network calls, file operations, etc.),
53
+ those side effects will occur. The allowlist prevents instantiation of
54
+ classes outside the allowlist, but does not sandbox the allowed classes
55
+ themselves or constrain their constructor kwargs.
56
+
57
+ For example, an untrusted manifest could deserialize a chat model whose
58
+ `base_url` (or `endpoint_url`) points at an attacker-controlled host. Any
59
+ request that model makes is then directed there — a Server-Side Request
60
+ Forgery (SSRF) vector. This is *expected behavior*: deserialization
61
+ faithfully reconstructs the configuration carried by the manifest, custom
62
+ endpoints included, and Coco does not special-case or strip such
63
+ kwargs. The mitigation is to **only deserialize manifests you trust**,
64
+ and for untrusted input to restrict `allowed_objects` to `'messages'`
65
+ or an explicit list of classes that take no endpoint configuration.
66
+
67
+ Import paths are also validated against trusted namespaces before any module is
68
+ imported.
69
+
70
+ ### Best practices
71
+
72
+ - Use the most restrictive `allowed_objects` possible. For untrusted input,
73
+ pass an explicit list of classes or `'messages'`. `'core'` and `'all'`
74
+ are unsafe with untrusted manifests — only use them when the source
75
+ serves the entire payload, including its configuration.
76
+ - Keep `secrets_from_env` set to `False` (the default). If you must use it,
77
+ ensure the serialized data comes from a fully trusted source, as a crafted
78
+ payload can read arbitrary environment variables.
79
+ - When using `secrets_map`, include only the specific secrets that the
80
+ serialized object requires.
81
+
82
+ ### Injection protection (escape-based)
83
+
84
+ During serialization, plain dicts that contain an `'lc'` key are escaped by wrapping
85
+ them: `{"__lc_escaped__": {...}}`. During deserialization, escaped dicts are unwrapped
86
+ and returned as plain dicts, NOT instantiated as LC objects.
87
+
88
+ This is an allowlist approach: only dicts explicitly produced by
89
+ `Serializable.to_json()` (which are NOT escaped) are treated as LC objects;
90
+ everything else is user data.
91
+
92
+ Even if an attacker's payload includes `__lc_escaped__` wrappers, it will be unwrapped
93
+ to plain dicts and NOT instantiated as malicious objects.
94
+
95
+ ## Examples
96
+
97
+ ```python
98
+ from coco_core.load import load
99
+ from coco_core.prompts import ChatPromptTemplate
100
+ from coco_core.messages import AIMessage, HumanMessage
101
+
102
+ # Use default allowlist (classes from mappings) - recommended
103
+ obj = load(data)
104
+
105
+ # Allow only specific classes (most restrictive)
106
+ obj = load(
107
+ data,
108
+ allowed_objects=[
109
+ ChatPromptTemplate,
110
+ AIMessage,
111
+ HumanMessage,
112
+ ],
113
+ )
114
+ ```
115
+ """
116
+
117
+ import importlib
118
+ import json
119
+ import os
120
+ from collections.abc import Callable, Iterable
121
+ from typing import Any, Literal, cast
122
+
123
+ from coco_core._api import beta
124
+ from coco_core._api.deprecation import warn_deprecated
125
+ from coco_core.load._validation import _is_escaped_dict, _unescape_value
126
+ from coco_core.load.mapping import (
127
+ _JS_SERIALIZABLE_MAPPING,
128
+ _OG_SERIALIZABLE_MAPPING,
129
+ OLD_CORE_NAMESPACES_MAPPING,
130
+ SERIALIZABLE_MAPPING,
131
+ )
132
+ from coco_core.load.serializable import Serializable
133
+
134
+ DEFAULT_NAMESPACES = [
135
+ "coco",
136
+ "coco_core",
137
+ "coco_community",
138
+ "coco_anthropic",
139
+ "coco_groq",
140
+ "coco_google_genai",
141
+ "coco_aws",
142
+ "coco_openai",
143
+ "coco_google_vertexai",
144
+ "coco_mistralai",
145
+ "coco_fireworks",
146
+ "coco_xai",
147
+ "coco_sambanova",
148
+ "coco_perplexity",
149
+ ]
150
+ # Namespaces for which only deserializing via the SERIALIZABLE_MAPPING is allowed.
151
+ # Load by path is not allowed.
152
+ DISALLOW_LOAD_FROM_PATH = [
153
+ "coco_community",
154
+ "coco",
155
+ ]
156
+
157
+ ALL_SERIALIZABLE_MAPPINGS = {
158
+ **SERIALIZABLE_MAPPING,
159
+ **OLD_CORE_NAMESPACES_MAPPING,
160
+ **_OG_SERIALIZABLE_MAPPING,
161
+ **_JS_SERIALIZABLE_MAPPING,
162
+ }
163
+
164
+ # Modern message classes admitted by `allowed_objects='messages'`. Legacy types
165
+ # (BaseMessage / BaseMessageChunk, ChatMessage / ChatMessageChunk, FunctionMessage /
166
+ # FunctionMessageChunk) are intentionally excluded — `BaseMessage` is abstract and
167
+ # the chat/function variants are superseded by `ToolMessage` and tool calling.
168
+ _MESSAGES_ALLOWED_CLASS_NAMES = frozenset(
169
+ {
170
+ "AIMessage",
171
+ "AIMessageChunk",
172
+ "HumanMessage",
173
+ "HumanMessageChunk",
174
+ "SystemMessage",
175
+ "SystemMessageChunk",
176
+ "ToolMessage",
177
+ "ToolMessageChunk",
178
+ "RemoveMessage",
179
+ }
180
+ )
181
+
182
+ # Cache for the default allowed class paths computed from mappings
183
+ # Maps mode ("all", "core", or "messages") to the cached set of paths
184
+ _default_class_paths_cache: dict[str, set[tuple[str, ...]]] = {}
185
+
186
+
187
+ def _get_default_allowed_class_paths(
188
+ allowed_object_mode: Literal["all", "core", "messages"],
189
+ ) -> set[tuple[str, ...]]:
190
+ """Get the default allowed class paths from the serialization mappings.
191
+
192
+ This uses the mappings as the source of truth for what classes are allowed
193
+ by default. Both the legacy paths (keys) and current paths (values) are included.
194
+
195
+ Args:
196
+ allowed_object_mode: either `'all'`, `'core'`, or `'messages'`.
197
+
198
+ Returns:
199
+ Set of class path tuples that are allowed by default.
200
+ """
201
+ if allowed_object_mode in _default_class_paths_cache:
202
+ return _default_class_paths_cache[allowed_object_mode]
203
+
204
+ allowed_paths: set[tuple[str, ...]] = set()
205
+ for key, value in ALL_SERIALIZABLE_MAPPINGS.items():
206
+ if allowed_object_mode == "core" and value[0] != "coco_core":
207
+ continue
208
+ if allowed_object_mode == "messages" and (
209
+ value[0] != "coco_core"
210
+ or value[-1] not in _MESSAGES_ALLOWED_CLASS_NAMES
211
+ ):
212
+ continue
213
+ allowed_paths.add(key)
214
+ allowed_paths.add(value)
215
+
216
+ _default_class_paths_cache[allowed_object_mode] = allowed_paths
217
+ return _default_class_paths_cache[allowed_object_mode]
218
+
219
+
220
+ def _block_jinja2_templates(
221
+ class_path: tuple[str, ...],
222
+ kwargs: dict[str, Any],
223
+ ) -> None:
224
+ """Block jinja2 templates during deserialization for security.
225
+
226
+ Jinja2 templates can execute arbitrary code, so they are blocked by default when
227
+ deserializing objects with `template_format='jinja2'`.
228
+
229
+ Note:
230
+ We intentionally do NOT check the `class_path` here to keep this simple and
231
+ future-proof. If any new class is added that accepts `template_format='jinja2'`,
232
+ it will be automatically blocked without needing to update this function.
233
+
234
+ Args:
235
+ class_path: The class path tuple being deserialized (unused).
236
+ kwargs: The kwargs dict for the class constructor.
237
+
238
+ Raises:
239
+ ValueError: If `template_format` is `'jinja2'`.
240
+ """
241
+ _ = class_path # Unused - see docstring for rationale. Kept to satisfy signature.
242
+ if kwargs.get("template_format") == "jinja2":
243
+ msg = (
244
+ "Jinja2 templates are not allowed during deserialization for security "
245
+ "reasons. Use 'f-string' template format instead, or explicitly allow "
246
+ "jinja2 by providing a custom init_validator."
247
+ )
248
+ raise ValueError(msg)
249
+
250
+
251
+ def default_init_validator(
252
+ class_path: tuple[str, ...],
253
+ kwargs: dict[str, Any],
254
+ ) -> None:
255
+ """Default init validator that blocks jinja2 templates.
256
+
257
+ This is the default validator used by `load()` and `loads()` when no custom
258
+ validator is provided.
259
+
260
+ Args:
261
+ class_path: The class path tuple being deserialized.
262
+ kwargs: The kwargs dict for the class constructor.
263
+
264
+ Raises:
265
+ ValueError: If template_format is `'jinja2'`.
266
+ """
267
+ _block_jinja2_templates(class_path, kwargs)
268
+
269
+
270
+ AllowedObject = type[Serializable]
271
+ """Type alias for classes that can be included in the `allowed_objects` parameter.
272
+
273
+ Must be a `Serializable` subclass (the class itself, not an instance).
274
+ """
275
+
276
+ InitValidator = Callable[[tuple[str, ...], dict[str, Any]], None]
277
+ """Type alias for a callable that validates kwargs during deserialization.
278
+
279
+ The callable receives:
280
+
281
+ - `class_path`: A tuple of strings identifying the class being instantiated
282
+ (e.g., `('coco', 'schema', 'messages', 'AIMessage')`).
283
+ - `kwargs`: The kwargs dict that will be passed to the constructor.
284
+
285
+ The validator should raise an exception if the object should not be deserialized.
286
+ """
287
+
288
+
289
+ def _compute_allowed_class_paths(
290
+ allowed_objects: Iterable[AllowedObject],
291
+ import_mappings: dict[tuple[str, ...], tuple[str, ...]],
292
+ ) -> set[tuple[str, ...]]:
293
+ """Return allowed class paths from an explicit list of classes.
294
+
295
+ A class path is a tuple of strings identifying a serializable class, derived from
296
+ `Serializable.lc_id()`. For example: `('coco_core', 'messages', 'AIMessage')`.
297
+
298
+ Args:
299
+ allowed_objects: Iterable of `Serializable` subclasses to allow.
300
+ import_mappings: Mapping of legacy class paths to current class paths.
301
+
302
+ Returns:
303
+ Set of allowed class paths.
304
+
305
+ Example:
306
+ ```python
307
+ # Allow a specific class
308
+ _compute_allowed_class_paths([MyPrompt], {}) ->
309
+ {("coco_core", "prompts", "MyPrompt")}
310
+
311
+ # Include legacy paths that map to the same class
312
+ import_mappings = {("old", "Prompt"): ("coco_core", "prompts", "MyPrompt")}
313
+ _compute_allowed_class_paths([MyPrompt], import_mappings) ->
314
+ {("coco_core", "prompts", "MyPrompt"), ("old", "Prompt")}
315
+ ```
316
+ """
317
+ allowed_objects_list = list(allowed_objects)
318
+
319
+ allowed_class_paths: set[tuple[str, ...]] = set()
320
+ for allowed_obj in allowed_objects_list:
321
+ if not isinstance(allowed_obj, type) or not issubclass(
322
+ allowed_obj, Serializable
323
+ ):
324
+ msg = "allowed_objects must contain Serializable subclasses." # type: ignore[unreachable]
325
+ raise TypeError(msg)
326
+
327
+ class_path = tuple(allowed_obj.lc_id())
328
+ allowed_class_paths.add(class_path)
329
+ # Add legacy paths that map to the same class.
330
+ for mapping_key, mapping_value in import_mappings.items():
331
+ if tuple(mapping_value) == class_path:
332
+ allowed_class_paths.add(mapping_key)
333
+ return allowed_class_paths
334
+
335
+
336
+ class Reviver:
337
+ """Reviver for JSON objects.
338
+
339
+ Used as the `object_hook` for `json.loads` to reconstruct Coco objects from
340
+ their serialized JSON representation.
341
+
342
+ Only classes in the allowlist can be instantiated.
343
+ """
344
+
345
+ def __init__(
346
+ self,
347
+ allowed_objects: Iterable[AllowedObject]
348
+ | Literal["all", "core", "messages"]
349
+ | None = None,
350
+ secrets_map: dict[str, str] | None = None,
351
+ valid_namespaces: list[str] | None = None,
352
+ secrets_from_env: bool = False, # noqa: FBT001,FBT002
353
+ additional_import_mappings: dict[tuple[str, ...], tuple[str, ...]]
354
+ | None = None,
355
+ *,
356
+ ignore_unserializable_fields: bool = False,
357
+ init_validator: InitValidator | None = default_init_validator,
358
+ ) -> None:
359
+ """Initialize the reviver.
360
+
361
+ See the module docstring for the threat model around `load()`/`loads()`:
362
+ a serialized payload may carry constructor configuration that affects
363
+ runtime behavior (custom `base_url`, headers, model name, etc.). Do not
364
+ use `'core'` or `'all'` with untrusted manifests.
365
+
366
+ Args:
367
+ allowed_objects: Allowlist of classes that can be deserialized.
368
+ - Explicit list of classes (recommended for untrusted input):
369
+ only those specific classes are allowed.
370
+ - `'messages'`: chat-message classes only (e.g. `AIMessage`,
371
+ `HumanMessage`). Safe for untrusted input.
372
+ - `'core'` (current default): unsafe with untrusted manifests.
373
+ Classes defined in the serialization mappings under
374
+ `coco_core`.
375
+ - `'all'`: unsafe with untrusted manifests. Every class in the
376
+ serialization mappings, including partner chat models and
377
+ LLMs and their constructor kwargs. See
378
+ `coco_core.load.mapping` for the full list.
379
+ secrets_map: A map of secrets to load.
380
+
381
+ Only include the specific secrets the serialized object
382
+ requires. If a secret is not found in the map, it will be loaded
383
+ from the environment if `secrets_from_env` is `True`.
384
+ valid_namespaces: Additional namespaces (modules) to allow during
385
+ deserialization, beyond the default trusted namespaces.
386
+ secrets_from_env: Whether to load secrets from the environment.
387
+
388
+ A crafted payload can name arbitrary environment variables in
389
+ its `secret` fields, so enabling this on untrusted data can leak
390
+ sensitive values. Keep this `False` (the default) unless the
391
+ serialized data is fully trusted.
392
+ additional_import_mappings: A dictionary of additional namespace mappings.
393
+
394
+ You can use this to override default mappings or add new mappings.
395
+
396
+ When `allowed_objects` is `None` (using defaults), paths from these
397
+ mappings are also added to the allowed class paths.
398
+ ignore_unserializable_fields: Whether to ignore unserializable fields.
399
+ init_validator: Optional callable to validate kwargs before instantiation.
400
+
401
+ If provided, this function is called with `(class_path, kwargs)` where
402
+ `class_path` is the class path tuple and `kwargs` is the kwargs dict.
403
+ The validator should raise an exception if the object should not be
404
+ deserialized, otherwise return `None`.
405
+
406
+ Defaults to `default_init_validator` which blocks jinja2 templates.
407
+ """
408
+ if allowed_objects is None:
409
+ warn_deprecated(
410
+ since="1.3.3",
411
+ message=(
412
+ "The default value of `allowed_objects` will change in a future "
413
+ "version. Pass an explicit value (e.g., "
414
+ "allowed_objects='messages' or allowed_objects='core') to suppress "
415
+ "this warning."
416
+ ),
417
+ pending=True,
418
+ )
419
+ allowed_objects = "core"
420
+
421
+ self.secrets_from_env = secrets_from_env
422
+ self.secrets_map = secrets_map or {}
423
+ # By default, only support coco, but user can pass in additional namespaces
424
+ self.valid_namespaces = (
425
+ [*DEFAULT_NAMESPACES, *valid_namespaces]
426
+ if valid_namespaces
427
+ else DEFAULT_NAMESPACES
428
+ )
429
+ self.additional_import_mappings = additional_import_mappings or {}
430
+ self.import_mappings = (
431
+ {
432
+ **ALL_SERIALIZABLE_MAPPINGS,
433
+ **self.additional_import_mappings,
434
+ }
435
+ if self.additional_import_mappings
436
+ else ALL_SERIALIZABLE_MAPPINGS
437
+ )
438
+ # Compute allowed class paths:
439
+ # - "all" -> use default paths from mappings (+ additional_import_mappings)
440
+ # - Explicit list -> compute from those classes
441
+ if allowed_objects in ("all", "core", "messages"):
442
+ self.allowed_class_paths: set[tuple[str, ...]] | None = (
443
+ _get_default_allowed_class_paths(allowed_objects).copy()
444
+ )
445
+ # Add paths from additional_import_mappings to the defaults
446
+ if self.additional_import_mappings:
447
+ for key, value in self.additional_import_mappings.items():
448
+ self.allowed_class_paths.add(key)
449
+ self.allowed_class_paths.add(value)
450
+ else:
451
+ self.allowed_class_paths = _compute_allowed_class_paths(
452
+ cast("Iterable[AllowedObject]", allowed_objects), self.import_mappings
453
+ )
454
+ self.ignore_unserializable_fields = ignore_unserializable_fields
455
+ self.init_validator = init_validator
456
+
457
+ def __call__(self, value: dict[str, Any]) -> Any:
458
+ """Revive the value.
459
+
460
+ Args:
461
+ value: The value to revive.
462
+
463
+ Returns:
464
+ The revived value.
465
+
466
+ Raises:
467
+ ValueError: If the namespace is invalid.
468
+ ValueError: If trying to deserialize something that cannot
469
+ be deserialized in the current version of trustcoco-core.
470
+ NotImplementedError: If the object is not implemented and
471
+ `ignore_unserializable_fields` is False.
472
+ """
473
+ if (
474
+ value.get("lc") == 1
475
+ and value.get("type") == "secret"
476
+ and value.get("id") is not None
477
+ ):
478
+ [key] = value["id"]
479
+ if key in self.secrets_map:
480
+ return self.secrets_map[key]
481
+ if self.secrets_from_env and key in os.environ and os.environ[key]:
482
+ return os.environ[key]
483
+ return None
484
+
485
+ if (
486
+ value.get("lc") == 1
487
+ and value.get("type") == "not_implemented"
488
+ and value.get("id") is not None
489
+ ):
490
+ if self.ignore_unserializable_fields:
491
+ return None
492
+ msg = (
493
+ "Trying to load an object that doesn't implement "
494
+ f"serialization: {value}"
495
+ )
496
+ raise NotImplementedError(msg)
497
+
498
+ if (
499
+ value.get("lc") == 1
500
+ and value.get("type") == "constructor"
501
+ and value.get("id") is not None
502
+ ):
503
+ [*namespace, name] = value["id"]
504
+ mapping_key = tuple(value["id"])
505
+
506
+ if (
507
+ self.allowed_class_paths is not None
508
+ and mapping_key not in self.allowed_class_paths
509
+ ):
510
+ msg = (
511
+ f"Deserialization of {mapping_key!r} is not allowed. "
512
+ "The default (allowed_objects='core') only permits core "
513
+ "trustcoco-core classes. To allow trusted partner integrations, "
514
+ "use allowed_objects='all'. Alternatively, pass an explicit list "
515
+ "of allowed classes via allowed_objects=[...]. "
516
+ "See coco_core.load.mapping for the full allowlist."
517
+ )
518
+ raise ValueError(msg)
519
+
520
+ if (
521
+ namespace[0] not in self.valid_namespaces
522
+ # The root namespace ["coco"] is not a valid identifier.
523
+ or namespace == ["coco"]
524
+ ):
525
+ msg = f"Invalid namespace: {value}"
526
+ raise ValueError(msg)
527
+ # Determine explicit import path
528
+ if mapping_key in self.import_mappings:
529
+ import_path = self.import_mappings[mapping_key]
530
+ # Split into module and name
531
+ import_dir, name = import_path[:-1], import_path[-1]
532
+ elif namespace[0] in DISALLOW_LOAD_FROM_PATH:
533
+ msg = (
534
+ "Trying to deserialize something that cannot "
535
+ "be deserialized in current version of trustcoco-core: "
536
+ f"{mapping_key}."
537
+ )
538
+ raise ValueError(msg)
539
+ else:
540
+ # Otherwise, treat namespace as path.
541
+ import_dir = namespace
542
+
543
+ # Validate import path is in trusted namespaces before importing
544
+ if import_dir[0] not in self.valid_namespaces:
545
+ msg = f"Invalid namespace: {value}"
546
+ raise ValueError(msg)
547
+
548
+ # We don't need to recurse on kwargs
549
+ # as json.loads will do that for us.
550
+ kwargs = value.get("kwargs", {})
551
+
552
+ # Run the init_validator (e.g., jinja2 blocking) before importing
553
+ # to fail fast on security violations.
554
+ if self.init_validator is not None:
555
+ self.init_validator(mapping_key, kwargs)
556
+
557
+ mod = importlib.import_module(".".join(import_dir))
558
+
559
+ cls = getattr(mod, name)
560
+
561
+ # The class must be a subclass of Serializable.
562
+ if not issubclass(cls, Serializable):
563
+ msg = f"Invalid namespace: {value}"
564
+ raise ValueError(msg)
565
+
566
+ return cls(**kwargs)
567
+
568
+ return value
569
+
570
+
571
+ @beta()
572
+ def loads(
573
+ text: str,
574
+ *,
575
+ allowed_objects: Iterable[AllowedObject]
576
+ | Literal["all", "core", "messages"]
577
+ | None = None,
578
+ secrets_map: dict[str, str] | None = None,
579
+ valid_namespaces: list[str] | None = None,
580
+ secrets_from_env: bool = False,
581
+ additional_import_mappings: dict[tuple[str, ...], tuple[str, ...]] | None = None,
582
+ ignore_unserializable_fields: bool = False,
583
+ init_validator: InitValidator | None = default_init_validator,
584
+ ) -> Any:
585
+ """Revive a Coco class from a JSON string.
586
+
587
+ Equivalent to `load(json.loads(text))`.
588
+
589
+ Only classes in the allowlist can be instantiated. The default allowlist
590
+ includes core Coco types (messages, prompts, documents, etc.). See
591
+ `coco_core.load.mapping` for the full list.
592
+
593
+ !!! warning "Do not use with untrusted input"
594
+
595
+ A serialized payload may carry constructor kwargs that affect runtime
596
+ behavior (custom `base_url`, headers, model name, etc.), so it should be
597
+ treated as executable configuration rather than plain text. For example,
598
+ deserializing a model whose `base_url` points at an attacker-controlled
599
+ host can result in Server-Side Request Forgery (SSRF); this is expected
600
+ behavior, since `loads()` faithfully reconstructs the configuration in
601
+ the manifest. If the source is untrusted, avoid calling `loads()` on it;
602
+ if you must, pass `allowed_objects='messages'` or an explicit list of
603
+ message classes. See the module-level threat model for details.
604
+
605
+ Args:
606
+ text: The string to load.
607
+ allowed_objects: Allowlist of classes that can be deserialized.
608
+
609
+ - Explicit list of classes (recommended for untrusted input): only
610
+ those specific classes are allowed.
611
+ - `'messages'`: chat-message classes only. Safe for untrusted input.
612
+ - `'core'` (current default): unsafe with untrusted manifests.
613
+ Classes defined in the serialization mappings under
614
+ `coco_core`.
615
+ - `'all'`: unsafe with untrusted manifests. Every class in the
616
+ serialization mappings, including partner chat models and LLMs
617
+ and their constructor kwargs. See `coco_core.load.mapping`
618
+ for the full list.
619
+ - `[]`: Disallow all deserialization (will raise on any object).
620
+ secrets_map: A map of secrets to load.
621
+
622
+ Only include the specific secrets the serialized object requires. If
623
+ a secret is not found in the map, it will be loaded from the
624
+ environment if `secrets_from_env` is `True`.
625
+ valid_namespaces: Additional namespaces (modules) to allow during
626
+ deserialization, beyond the default trusted namespaces.
627
+ secrets_from_env: Whether to load secrets from the environment.
628
+
629
+ A crafted payload can name arbitrary environment variables in its
630
+ `secret` fields, so enabling this on untrusted data can leak
631
+ sensitive values. Keep this `False` (the default) unless the
632
+ serialized data is fully trusted.
633
+ additional_import_mappings: A dictionary of additional namespace mappings.
634
+
635
+ You can use this to override default mappings or add new mappings.
636
+
637
+ When `allowed_objects` is `None` (using defaults), paths from these
638
+ mappings are also added to the allowed class paths.
639
+ ignore_unserializable_fields: Whether to ignore unserializable fields.
640
+ init_validator: Optional callable to validate kwargs before instantiation.
641
+
642
+ If provided, this function is called with `(class_path, kwargs)` where
643
+ `class_path` is the class path tuple and `kwargs` is the kwargs dict.
644
+ The validator should raise an exception if the object should not be
645
+ deserialized, otherwise return `None`.
646
+
647
+ Defaults to `default_init_validator` which blocks jinja2 templates.
648
+
649
+ Returns:
650
+ Revived Coco objects.
651
+
652
+ Raises:
653
+ ValueError: If an object's class path is not in the `allowed_objects` allowlist.
654
+ """
655
+ if allowed_objects is None:
656
+ warn_deprecated(
657
+ since="1.3.3",
658
+ message=(
659
+ "The default value of `allowed_objects` will change in a future "
660
+ "version. Pass an explicit list of allowed classes (or "
661
+ "'messages' for untrusted input that contains only chat "
662
+ "messages) to suppress this warning."
663
+ ),
664
+ pending=True,
665
+ )
666
+ allowed_objects = "core"
667
+
668
+ # Parse JSON and delegate to load() for proper escape handling
669
+ raw_obj = json.loads(text)
670
+ return load(
671
+ raw_obj,
672
+ allowed_objects=allowed_objects,
673
+ secrets_map=secrets_map,
674
+ valid_namespaces=valid_namespaces,
675
+ secrets_from_env=secrets_from_env,
676
+ additional_import_mappings=additional_import_mappings,
677
+ ignore_unserializable_fields=ignore_unserializable_fields,
678
+ init_validator=init_validator,
679
+ )
680
+
681
+
682
+ @beta()
683
+ def load(
684
+ obj: Any,
685
+ *,
686
+ allowed_objects: Iterable[AllowedObject]
687
+ | Literal["all", "core", "messages"]
688
+ | None = None,
689
+ secrets_map: dict[str, str] | None = None,
690
+ valid_namespaces: list[str] | None = None,
691
+ secrets_from_env: bool = False,
692
+ additional_import_mappings: dict[tuple[str, ...], tuple[str, ...]] | None = None,
693
+ ignore_unserializable_fields: bool = False,
694
+ init_validator: InitValidator | None = default_init_validator,
695
+ ) -> Any:
696
+ """Revive a Coco class from a JSON object.
697
+
698
+ Use this if you already have a parsed JSON object, eg. from `json.load` or
699
+ `orjson.loads`.
700
+
701
+ Only classes in the allowlist can be instantiated. The default allowlist
702
+ includes core Coco types (messages, prompts, documents, etc.). See
703
+ `coco_core.load.mapping` for the full list.
704
+
705
+ !!! warning "Do not use with untrusted input"
706
+
707
+ A serialized payload may carry constructor kwargs that affect runtime
708
+ behavior (custom `base_url`, headers, model name, etc.), so it should be
709
+ treated as executable configuration rather than plain text. For example,
710
+ deserializing a model whose `base_url` points at an attacker-controlled
711
+ host can result in Server-Side Request Forgery (SSRF); this is expected
712
+ behavior, since `load()` faithfully reconstructs the configuration in
713
+ the manifest. If the source is untrusted, avoid calling `load()` on it;
714
+ if you must, pass `allowed_objects='messages'` or an explicit list of
715
+ message classes. See the module-level threat model for details.
716
+
717
+ Args:
718
+ obj: The object to load.
719
+ allowed_objects: Allowlist of classes that can be deserialized.
720
+
721
+ - Explicit list of classes (recommended for untrusted input): only
722
+ those specific classes are allowed.
723
+ - `'messages'`: chat-message classes only. Safe for untrusted input.
724
+ - `'core'` (current default): unsafe with untrusted manifests.
725
+ Classes defined in the serialization mappings under
726
+ `coco_core`.
727
+ - `'all'`: unsafe with untrusted manifests. Every class in the
728
+ serialization mappings, including partner chat models and LLMs
729
+ and their constructor kwargs. See `coco_core.load.mapping`
730
+ for the full list.
731
+ - `[]`: Disallow all deserialization (will raise on any object).
732
+ secrets_map: A map of secrets to load.
733
+
734
+ Only include the specific secrets the serialized object requires.
735
+
736
+ If a secret is not found in the map, it will be loaded from the environment
737
+ if `secrets_from_env` is `True`.
738
+ valid_namespaces: Additional namespaces (modules) to allow during
739
+ deserialization, beyond the default trusted namespaces.
740
+ secrets_from_env: Whether to load secrets from the environment.
741
+
742
+ A crafted payload can name arbitrary environment variables in its
743
+ `secret` fields, so enabling this on untrusted data can leak
744
+ sensitive values. Keep this `False` (the default) unless the
745
+ serialized data is fully trusted.
746
+ additional_import_mappings: A dictionary of additional namespace mappings.
747
+
748
+ You can use this to override default mappings or add new mappings.
749
+
750
+ When `allowed_objects` is `None` (using defaults), paths from these
751
+ mappings are also added to the allowed class paths.
752
+ ignore_unserializable_fields: Whether to ignore unserializable fields.
753
+ init_validator: Optional callable to validate kwargs before instantiation.
754
+
755
+ If provided, this function is called with `(class_path, kwargs)` where
756
+ `class_path` is the class path tuple and `kwargs` is the kwargs dict.
757
+ The validator should raise an exception if the object should not be
758
+ deserialized, otherwise return `None`.
759
+
760
+ Defaults to `default_init_validator` which blocks jinja2 templates.
761
+
762
+ Returns:
763
+ Revived Coco objects.
764
+
765
+ Raises:
766
+ ValueError: If an object's class path is not in the `allowed_objects` allowlist.
767
+
768
+ Example:
769
+ ```python
770
+ from coco_core.load import load, dumpd
771
+ from coco_core.messages import AIMessage
772
+
773
+ msg = AIMessage(content="Hello")
774
+ data = dumpd(msg)
775
+
776
+ # Deserialize using default allowlist
777
+ loaded = load(data)
778
+
779
+ # Or with explicit allowlist
780
+ loaded = load(data, allowed_objects=[AIMessage])
781
+
782
+ # Or extend defaults with additional mappings
783
+ loaded = load(
784
+ data,
785
+ additional_import_mappings={
786
+ ("my_pkg", "MyClass"): ("my_pkg", "module", "MyClass"),
787
+ },
788
+ )
789
+ ```
790
+ """
791
+ if allowed_objects is None:
792
+ warn_deprecated(
793
+ since="1.3.3",
794
+ message=(
795
+ "The default value of `allowed_objects` will change in a future "
796
+ "version. Pass an explicit list of allowed classes (or "
797
+ "'messages' for untrusted input that contains only chat "
798
+ "messages) to suppress this warning."
799
+ ),
800
+ pending=True,
801
+ )
802
+ allowed_objects = "core"
803
+
804
+ reviver = Reviver(
805
+ allowed_objects,
806
+ secrets_map,
807
+ valid_namespaces,
808
+ secrets_from_env,
809
+ additional_import_mappings,
810
+ ignore_unserializable_fields=ignore_unserializable_fields,
811
+ init_validator=init_validator,
812
+ )
813
+
814
+ def _load(obj: Any) -> Any:
815
+ if isinstance(obj, dict):
816
+ # Check for escaped dict FIRST (before recursing).
817
+ # Escaped dicts are user data that should NOT be processed as LC objects.
818
+ if _is_escaped_dict(obj):
819
+ return _unescape_value(obj)
820
+
821
+ # Not escaped - recurse into children then apply reviver
822
+ loaded_obj = {k: _load(v) for k, v in obj.items()}
823
+ return reviver(loaded_obj)
824
+ if isinstance(obj, list):
825
+ return [_load(o) for o in obj]
826
+ return obj
827
+
828
+ return _load(obj)