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,19 @@
1
+ import dataclasses
2
+ from collections.abc import Callable
3
+ from typing import Any
4
+
5
+ from coco_graph.types import _DC_KWARGS
6
+
7
+
8
+ @dataclasses.dataclass(**_DC_KWARGS)
9
+ class PregelScratchpad:
10
+ step: int
11
+ stop: int
12
+ # call
13
+ call_counter: Callable[[], int]
14
+ # interrupt
15
+ interrupt_counter: Callable[[], int]
16
+ get_null_resume: Callable[[bool], Any]
17
+ resume: list[Any]
18
+ # subgraph
19
+ subgraph_counter: Callable[[], int]
@@ -0,0 +1,253 @@
1
+ from __future__ import annotations
2
+
3
+ import dataclasses
4
+ import logging
5
+ import sys
6
+ import types
7
+ from collections import deque
8
+ from enum import Enum
9
+ from typing import (
10
+ Annotated,
11
+ Any,
12
+ Literal,
13
+ Union,
14
+ get_args,
15
+ get_origin,
16
+ get_type_hints,
17
+ )
18
+
19
+ from coco_core import messages as lc_messages
20
+ from coco_graph.checkpoint.base import BaseCheckpointSaver
21
+ from pydantic import BaseModel
22
+ from typing_extensions import NotRequired, Required, is_typeddict
23
+
24
+ try:
25
+ from coco_graph.checkpoint.serde._msgpack import (
26
+ STRICT_MSGPACK_ENABLED,
27
+ )
28
+ except ImportError:
29
+ STRICT_MSGPACK_ENABLED = False
30
+
31
+ _warned_allowlist_unsupported = False
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+
36
+ def _supports_checkpointer_allowlist() -> bool:
37
+ return hasattr(BaseCheckpointSaver, "with_allowlist")
38
+
39
+
40
+ _SUPPORTS_ALLOWLIST = _supports_checkpointer_allowlist()
41
+
42
+
43
+ def apply_checkpointer_allowlist(
44
+ checkpointer: Any, allowlist: set[tuple[str, ...]] | None
45
+ ) -> Any:
46
+ if not checkpointer or allowlist is None or checkpointer in (True, False):
47
+ return checkpointer
48
+ if not _SUPPORTS_ALLOWLIST:
49
+ global _warned_allowlist_unsupported
50
+ if not _warned_allowlist_unsupported:
51
+ logger.warning(
52
+ "Checkpointer does not support with_allowlist; strict msgpack "
53
+ "allowlist will be skipped."
54
+ )
55
+ _warned_allowlist_unsupported = True
56
+ return checkpointer
57
+ return checkpointer.with_allowlist(allowlist)
58
+
59
+
60
+ def curated_core_allowlist() -> set[tuple[str, ...]]:
61
+ allowlist: set[tuple[str, ...]] = set()
62
+ for name in (
63
+ "BaseMessage",
64
+ "BaseMessageChunk",
65
+ "HumanMessage",
66
+ "HumanMessageChunk",
67
+ "AIMessage",
68
+ "AIMessageChunk",
69
+ "SystemMessage",
70
+ "SystemMessageChunk",
71
+ "ChatMessage",
72
+ "ChatMessageChunk",
73
+ "ToolMessage",
74
+ "ToolMessageChunk",
75
+ "FunctionMessage",
76
+ "FunctionMessageChunk",
77
+ "RemoveMessage",
78
+ ):
79
+ cls = getattr(lc_messages, name, None)
80
+ if cls is None:
81
+ continue
82
+ allowlist.add((cls.__module__, cls.__name__))
83
+
84
+ return allowlist
85
+
86
+
87
+ def build_serde_allowlist(
88
+ *,
89
+ schemas: list[type[Any]] | None = None,
90
+ channels: dict[str, Any] | None = None,
91
+ ) -> set[tuple[str, ...]]:
92
+ allowlist = curated_core_allowlist()
93
+ if schemas:
94
+ schemas = [schema for schema in schemas if schema is not None]
95
+ return allowlist | collect_allowlist_from_schemas(
96
+ schemas=schemas,
97
+ channels=channels,
98
+ )
99
+
100
+
101
+ def collect_allowlist_from_schemas(
102
+ *,
103
+ schemas: list[type[Any]] | None = None,
104
+ channels: dict[str, Any] | None = None,
105
+ ) -> set[tuple[str, ...]]:
106
+ allowlist: set[tuple[str, ...]] = set()
107
+ seen: set[Any] = set()
108
+ seen_ids: set[int] = set()
109
+
110
+ if schemas:
111
+ for schema in schemas:
112
+ _collect_from_type(schema, allowlist, seen, seen_ids)
113
+
114
+ if channels:
115
+ for channel in channels.values():
116
+ value_type = getattr(channel, "ValueType", None)
117
+ if value_type is not None:
118
+ _collect_from_type(value_type, allowlist, seen, seen_ids)
119
+ update_type = getattr(channel, "UpdateType", None)
120
+ if update_type is not None:
121
+ _collect_from_type(update_type, allowlist, seen, seen_ids)
122
+
123
+ return allowlist
124
+
125
+
126
+ def _collect_from_type(
127
+ typ: Any,
128
+ allowlist: set[tuple[str, ...]],
129
+ seen: set[Any],
130
+ seen_ids: set[int],
131
+ ) -> None:
132
+ if _already_seen(typ, seen, seen_ids):
133
+ return
134
+
135
+ if typ is Any or typ is None:
136
+ return
137
+
138
+ if typ is Literal:
139
+ return
140
+
141
+ if isinstance(typ, types.UnionType):
142
+ for arg in typ.__args__:
143
+ _collect_from_type(arg, allowlist, seen, seen_ids)
144
+ return
145
+
146
+ origin = get_origin(typ)
147
+ if origin is Union:
148
+ for arg in get_args(typ):
149
+ _collect_from_type(arg, allowlist, seen, seen_ids)
150
+ return
151
+ if origin is Annotated or origin in (Required, NotRequired):
152
+ args = get_args(typ)
153
+ if args:
154
+ _collect_from_type(args[0], allowlist, seen, seen_ids)
155
+ return
156
+
157
+ if origin is Literal:
158
+ return
159
+
160
+ if origin in (list, set, tuple, dict, deque, frozenset):
161
+ for arg in get_args(typ):
162
+ _collect_from_type(arg, allowlist, seen, seen_ids)
163
+ return
164
+
165
+ if hasattr(typ, "__supertype__"):
166
+ _collect_from_type(typ.__supertype__, allowlist, seen, seen_ids)
167
+ return
168
+
169
+ if is_typeddict(typ):
170
+ for field_type in _safe_get_type_hints(typ).values():
171
+ _collect_from_type(field_type, allowlist, seen, seen_ids)
172
+ return
173
+
174
+ if _is_pydantic_model(typ):
175
+ allowlist.add((typ.__module__, typ.__name__))
176
+ field_types = _safe_get_type_hints(typ)
177
+ if field_types:
178
+ for field_type in field_types.values():
179
+ _collect_from_type(field_type, allowlist, seen, seen_ids)
180
+ else:
181
+ for field_type in _pydantic_field_types(typ):
182
+ _collect_from_type(field_type, allowlist, seen, seen_ids)
183
+ return
184
+
185
+ if dataclasses.is_dataclass(typ):
186
+ if typ_name := getattr(typ, "__name__", None):
187
+ allowlist.add((typ.__module__, typ_name))
188
+ field_types = _safe_get_type_hints(typ)
189
+ if field_types:
190
+ for field_type in field_types.values():
191
+ _collect_from_type(field_type, allowlist, seen, seen_ids)
192
+ else:
193
+ for field in dataclasses.fields(typ):
194
+ _collect_from_type(field.type, allowlist, seen, seen_ids)
195
+ return
196
+
197
+ if isinstance(typ, type) and issubclass(typ, Enum):
198
+ allowlist.add((typ.__module__, typ.__name__))
199
+ return
200
+
201
+
202
+ def _already_seen(typ: Any, seen: set[Any], seen_ids: set[int]) -> bool:
203
+ try:
204
+ if typ in seen:
205
+ return True
206
+ seen.add(typ)
207
+ return False
208
+ except TypeError:
209
+ typ_id = id(typ)
210
+ if typ_id in seen_ids:
211
+ return True
212
+ seen_ids.add(typ_id)
213
+ return False
214
+
215
+
216
+ def _safe_get_type_hints(typ: Any) -> dict[str, Any]:
217
+ try:
218
+ module = sys.modules.get(getattr(typ, "__module__", ""))
219
+ globalns = module.__dict__ if module else None
220
+ localns = dict(vars(typ)) if hasattr(typ, "__dict__") else None
221
+ return get_type_hints(
222
+ typ, globalns=globalns, localns=localns, include_extras=True
223
+ )
224
+ except Exception:
225
+ return {}
226
+
227
+
228
+ def _is_pydantic_model(typ: Any) -> bool:
229
+ if not isinstance(typ, type):
230
+ return False
231
+ if issubclass(typ, BaseModel):
232
+ return True
233
+ try:
234
+ from pydantic.v1 import BaseModel as BaseModelV1
235
+ except Exception:
236
+ return False
237
+ return issubclass(typ, BaseModelV1)
238
+
239
+
240
+ def _pydantic_field_types(typ: type[Any]) -> list[Any]:
241
+ if hasattr(typ, "model_fields"):
242
+ return [
243
+ field.annotation
244
+ for field in typ.model_fields.values()
245
+ if getattr(field, "annotation", None) is not None
246
+ ]
247
+ if hasattr(typ, "__fields__"):
248
+ return [
249
+ field.outer_type_
250
+ for field in typ.__fields__.values()
251
+ if getattr(field, "outer_type_", None) is not None
252
+ ]
253
+ return []
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import timedelta
4
+ from typing import Literal
5
+
6
+ from coco_graph.types import TimeoutPolicy
7
+
8
+ _SYNC_TIMEOUT_PREFIX = (
9
+ "Node timeouts are only supported for async nodes because sync Python "
10
+ "execution cannot be safely cancelled in-process."
11
+ )
12
+
13
+
14
+ def coerce_timeout_policy(
15
+ value: float | timedelta | TimeoutPolicy | None,
16
+ ) -> TimeoutPolicy | None:
17
+ """Normalize a timeout value to positive-second policy fields."""
18
+ return TimeoutPolicy.coerce(value)
19
+
20
+
21
+ def sync_timeout_unsupported(
22
+ name: str, *, kind: Literal["Node", "Task"] = "Node"
23
+ ) -> ValueError:
24
+ """Build the canonical error for using `timeout` with a sync target."""
25
+ return ValueError(f"{_SYNC_TIMEOUT_PREFIX} {kind} {name!r} is sync.")
@@ -0,0 +1,54 @@
1
+ """Private typing utilities for CocoGraph."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import Field
6
+ from typing import Any, ClassVar, Protocol, TypeAlias
7
+
8
+ from pydantic import BaseModel
9
+ from typing_extensions import TypedDict
10
+
11
+
12
+ class TypedDictLikeV1(Protocol):
13
+ """Protocol to represent types that behave like TypedDicts
14
+
15
+ Version 1: using `ClassVar` for keys."""
16
+
17
+ __required_keys__: ClassVar[frozenset[str]]
18
+ __optional_keys__: ClassVar[frozenset[str]]
19
+
20
+
21
+ class TypedDictLikeV2(Protocol):
22
+ """Protocol to represent types that behave like TypedDicts
23
+
24
+ Version 2: not using `ClassVar` for keys."""
25
+
26
+ __required_keys__: frozenset[str]
27
+ __optional_keys__: frozenset[str]
28
+
29
+
30
+ class DataclassLike(Protocol):
31
+ """Protocol to represent types that behave like dataclasses.
32
+
33
+ Inspired by the private _DataclassT from dataclasses that uses a similar protocol as a bound."""
34
+
35
+ __dataclass_fields__: ClassVar[dict[str, Field[Any]]]
36
+
37
+
38
+ StateLike: TypeAlias = TypedDictLikeV1 | TypedDictLikeV2 | DataclassLike | BaseModel
39
+ """Type alias for state-like types.
40
+
41
+ It can either be a `TypedDict`, `dataclass`, or Pydantic `BaseModel`.
42
+ Note: we cannot use either `TypedDict` or `dataclass` directly due to limitations in type checking.
43
+ """
44
+
45
+ MISSING = object()
46
+ """Unset sentinel value."""
47
+
48
+
49
+ class DeprecatedKwargs(TypedDict):
50
+ """TypedDict to use for extra keyword arguments, enabling type checking warnings for deprecated arguments."""
51
+
52
+
53
+ EMPTY_SEQ: tuple[str, ...] = tuple()
54
+ """An empty sequence of strings."""
@@ -0,0 +1,48 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from collections.abc import Mapping, Sequence
5
+ from typing import Generic, TypeVar
6
+
7
+ from coco_graph.checkpoint.serde.base import SerializerProtocol
8
+ from coco_graph.checkpoint.serde.jsonplus import JsonPlusSerializer
9
+
10
+ ValueT = TypeVar("ValueT")
11
+ Namespace = tuple[str, ...]
12
+ FullKey = tuple[Namespace, str]
13
+
14
+
15
+ class BaseCache(ABC, Generic[ValueT]):
16
+ """Base class for a cache."""
17
+
18
+ serde: SerializerProtocol = JsonPlusSerializer(pickle_fallback=False)
19
+
20
+ def __init__(self, *, serde: SerializerProtocol | None = None) -> None:
21
+ """Initialize the cache with a serializer."""
22
+ self.serde = serde or self.serde
23
+
24
+ @abstractmethod
25
+ def get(self, keys: Sequence[FullKey]) -> dict[FullKey, ValueT]:
26
+ """Get the cached values for the given keys."""
27
+
28
+ @abstractmethod
29
+ async def aget(self, keys: Sequence[FullKey]) -> dict[FullKey, ValueT]:
30
+ """Asynchronously get the cached values for the given keys."""
31
+
32
+ @abstractmethod
33
+ def set(self, pairs: Mapping[FullKey, tuple[ValueT, int | None]]) -> None:
34
+ """Set the cached values for the given keys and TTLs."""
35
+
36
+ @abstractmethod
37
+ async def aset(self, pairs: Mapping[FullKey, tuple[ValueT, int | None]]) -> None:
38
+ """Asynchronously set the cached values for the given keys and TTLs."""
39
+
40
+ @abstractmethod
41
+ def clear(self, namespaces: Sequence[Namespace] | None = None) -> None:
42
+ """Delete the cached values for the given namespaces.
43
+ If no namespaces are provided, clear all cached values."""
44
+
45
+ @abstractmethod
46
+ async def aclear(self, namespaces: Sequence[Namespace] | None = None) -> None:
47
+ """Asynchronously delete the cached values for the given namespaces.
48
+ If no namespaces are provided, clear all cached values."""
File without changes
@@ -0,0 +1,73 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+ import threading
5
+ from collections.abc import Mapping, Sequence
6
+
7
+ from coco_graph.cache.base import BaseCache, FullKey, Namespace, ValueT
8
+ from coco_graph.checkpoint.serde.base import SerializerProtocol
9
+
10
+
11
+ class InMemoryCache(BaseCache[ValueT]):
12
+ def __init__(self, *, serde: SerializerProtocol | None = None):
13
+ super().__init__(serde=serde)
14
+ self._cache: dict[Namespace, dict[str, tuple[str, bytes, float | None]]] = {}
15
+ self._lock = threading.RLock()
16
+
17
+ def get(self, keys: Sequence[FullKey]) -> dict[FullKey, ValueT]:
18
+ """Get the cached values for the given keys."""
19
+ with self._lock:
20
+ if not keys:
21
+ return {}
22
+ now = datetime.datetime.now(datetime.timezone.utc).timestamp()
23
+ values: dict[FullKey, ValueT] = {}
24
+ for ns_tuple, key in keys:
25
+ ns = Namespace(ns_tuple)
26
+ if ns in self._cache and key in self._cache[ns]:
27
+ enc, val, expiry = self._cache[ns][key]
28
+ if expiry is None or now < expiry:
29
+ values[(ns, key)] = self.serde.loads_typed((enc, val))
30
+ else:
31
+ del self._cache[ns][key]
32
+ return values
33
+
34
+ async def aget(self, keys: Sequence[FullKey]) -> dict[FullKey, ValueT]:
35
+ """Asynchronously get the cached values for the given keys."""
36
+ return self.get(keys)
37
+
38
+ def set(self, keys: Mapping[FullKey, tuple[ValueT, int | None]]) -> None:
39
+ """Set the cached values for the given keys."""
40
+ with self._lock:
41
+ now = datetime.datetime.now(datetime.timezone.utc)
42
+ for (ns, key), (value, ttl) in keys.items():
43
+ if ttl is not None:
44
+ delta = datetime.timedelta(seconds=ttl)
45
+ expiry: float | None = (now + delta).timestamp()
46
+ else:
47
+ expiry = None
48
+ if ns not in self._cache:
49
+ self._cache[ns] = {}
50
+ self._cache[ns][key] = (
51
+ *self.serde.dumps_typed(value),
52
+ expiry,
53
+ )
54
+
55
+ async def aset(self, keys: Mapping[FullKey, tuple[ValueT, int | None]]) -> None:
56
+ """Asynchronously set the cached values for the given keys."""
57
+ self.set(keys)
58
+
59
+ def clear(self, namespaces: Sequence[Namespace] | None = None) -> None:
60
+ """Delete the cached values for the given namespaces.
61
+ If no namespaces are provided, clear all cached values."""
62
+ with self._lock:
63
+ if namespaces is None:
64
+ self._cache.clear()
65
+ else:
66
+ for ns in namespaces:
67
+ if ns in self._cache:
68
+ del self._cache[ns]
69
+
70
+ async def aclear(self, namespaces: Sequence[Namespace] | None = None) -> None:
71
+ """Asynchronously delete the cached values for the given namespaces.
72
+ If no namespaces are provided, clear all cached values."""
73
+ self.clear(namespaces)
@@ -0,0 +1,144 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping, Sequence
4
+ from typing import Any
5
+
6
+ from coco_graph.cache.base import BaseCache, FullKey, Namespace, ValueT
7
+ from coco_graph.checkpoint.serde.base import SerializerProtocol
8
+
9
+
10
+ class RedisCache(BaseCache[ValueT]):
11
+ """Redis-based cache implementation with TTL support."""
12
+
13
+ def __init__(
14
+ self,
15
+ redis: Any,
16
+ *,
17
+ serde: SerializerProtocol | None = None,
18
+ prefix: str = "coco_graph:cache:",
19
+ ) -> None:
20
+ """Initialize the cache with a Redis client.
21
+
22
+ Args:
23
+ redis: Redis client instance (sync or async)
24
+ serde: Serializer to use for values
25
+ prefix: Key prefix for all cached values
26
+ """
27
+ super().__init__(serde=serde)
28
+ self.redis = redis
29
+ self.prefix = prefix
30
+
31
+ def _make_key(self, ns: Namespace, key: str) -> str:
32
+ """Create a Redis key from namespace and key."""
33
+ ns_str = ":".join(ns) if ns else ""
34
+ return f"{self.prefix}{ns_str}:{key}" if ns_str else f"{self.prefix}{key}"
35
+
36
+ def _parse_key(self, redis_key: str) -> tuple[Namespace, str]:
37
+ """Parse a Redis key back to namespace and key."""
38
+ if not redis_key.startswith(self.prefix):
39
+ raise ValueError(
40
+ f"Key {redis_key} does not start with prefix {self.prefix}"
41
+ )
42
+
43
+ remaining = redis_key[len(self.prefix) :]
44
+ if ":" in remaining:
45
+ parts = remaining.split(":")
46
+ key = parts[-1]
47
+ ns_parts = parts[:-1]
48
+ return (tuple(ns_parts), key)
49
+ else:
50
+ return (tuple(), remaining)
51
+
52
+ def get(self, keys: Sequence[FullKey]) -> dict[FullKey, ValueT]:
53
+ """Get the cached values for the given keys."""
54
+ if not keys:
55
+ return {}
56
+
57
+ # Build Redis keys
58
+ redis_keys = [self._make_key(ns, key) for ns, key in keys]
59
+
60
+ # Get values from Redis using MGET
61
+ try:
62
+ raw_values = self.redis.mget(redis_keys)
63
+ except Exception:
64
+ # If Redis is unavailable, return empty dict
65
+ return {}
66
+
67
+ values: dict[FullKey, ValueT] = {}
68
+ for i, raw_value in enumerate(raw_values):
69
+ if raw_value is not None:
70
+ try:
71
+ # Deserialize the value
72
+ encoding, data = raw_value.split(b":", 1)
73
+ values[keys[i]] = self.serde.loads_typed((encoding.decode(), data))
74
+ except Exception:
75
+ # Skip corrupted entries
76
+ continue
77
+
78
+ return values
79
+
80
+ async def aget(self, keys: Sequence[FullKey]) -> dict[FullKey, ValueT]:
81
+ """Asynchronously get the cached values for the given keys."""
82
+ return self.get(keys)
83
+
84
+ def set(self, mapping: Mapping[FullKey, tuple[ValueT, int | None]]) -> None:
85
+ """Set the cached values for the given keys and TTLs."""
86
+ if not mapping:
87
+ return
88
+
89
+ # Use pipeline for efficient batch operations
90
+ pipe = self.redis.pipeline()
91
+
92
+ for (ns, key), (value, ttl) in mapping.items():
93
+ redis_key = self._make_key(ns, key)
94
+ encoding, data = self.serde.dumps_typed(value)
95
+
96
+ # Store as "encoding:data" format
97
+ serialized_value = f"{encoding}:".encode() + data
98
+
99
+ if ttl is not None:
100
+ pipe.setex(redis_key, ttl, serialized_value)
101
+ else:
102
+ pipe.set(redis_key, serialized_value)
103
+
104
+ try:
105
+ pipe.execute()
106
+ except Exception:
107
+ # Silently fail if Redis is unavailable
108
+ pass
109
+
110
+ async def aset(self, mapping: Mapping[FullKey, tuple[ValueT, int | None]]) -> None:
111
+ """Asynchronously set the cached values for the given keys and TTLs."""
112
+ self.set(mapping)
113
+
114
+ def clear(self, namespaces: Sequence[Namespace] | None = None) -> None:
115
+ """Delete the cached values for the given namespaces.
116
+ If no namespaces are provided, clear all cached values."""
117
+ try:
118
+ if namespaces is None:
119
+ # Clear all keys with our prefix
120
+ pattern = f"{self.prefix}*"
121
+ keys = self.redis.keys(pattern)
122
+ if keys:
123
+ self.redis.delete(*keys)
124
+ else:
125
+ # Clear specific namespaces
126
+ keys_to_delete = []
127
+ for ns in namespaces:
128
+ ns_str = ":".join(ns) if ns else ""
129
+ pattern = (
130
+ f"{self.prefix}{ns_str}:*" if ns_str else f"{self.prefix}*"
131
+ )
132
+ keys = self.redis.keys(pattern)
133
+ keys_to_delete.extend(keys)
134
+
135
+ if keys_to_delete:
136
+ self.redis.delete(*keys_to_delete)
137
+ except Exception:
138
+ # Silently fail if Redis is unavailable
139
+ pass
140
+
141
+ async def aclear(self, namespaces: Sequence[Namespace] | None = None) -> None:
142
+ """Asynchronously delete the cached values for the given namespaces.
143
+ If no namespaces are provided, clear all cached values."""
144
+ self.clear(namespaces)