gatewaysdk 0.3.2__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 (376) hide show
  1. gatewaysdk/__init__.py +540 -0
  2. gatewaysdk/adapter/__init__.py +15 -0
  3. gatewaysdk/adapter/base.py +94 -0
  4. gatewaysdk/adapter/messages.py +270 -0
  5. gatewaysdk/adapter/triplet.py +1026 -0
  6. gatewaysdk/algorithm/__init__.py +39 -0
  7. gatewaysdk/algorithm/apo/__init__.py +5 -0
  8. gatewaysdk/algorithm/apo/apo.py +898 -0
  9. gatewaysdk/algorithm/apo/prompts/apply_edit_variant01.poml +22 -0
  10. gatewaysdk/algorithm/apo/prompts/apply_edit_variant02.poml +18 -0
  11. gatewaysdk/algorithm/apo/prompts/text_gradient_variant01.poml +18 -0
  12. gatewaysdk/algorithm/apo/prompts/text_gradient_variant02.poml +16 -0
  13. gatewaysdk/algorithm/apo/prompts/text_gradient_variant03.poml +107 -0
  14. gatewaysdk/algorithm/base.py +262 -0
  15. gatewaysdk/algorithm/decorator.py +264 -0
  16. gatewaysdk/algorithm/evals/__init__.py +7 -0
  17. gatewaysdk/algorithm/evals/evals.py +217 -0
  18. gatewaysdk/algorithm/fast.py +250 -0
  19. gatewaysdk/algorithm/gepa/__init__.py +61 -0
  20. gatewaysdk/algorithm/gepa/adapter.py +495 -0
  21. gatewaysdk/algorithm/gepa/gepa.py +570 -0
  22. gatewaysdk/algorithm/gepa/lib/__init__.py +18 -0
  23. gatewaysdk/algorithm/gepa/lib/adapters/README.md +12 -0
  24. gatewaysdk/algorithm/gepa/lib/adapters/__init__.py +0 -0
  25. gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/README.md +341 -0
  26. gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/__init__.py +1 -0
  27. gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/anymaths_adapter.py +174 -0
  28. gatewaysdk/algorithm/gepa/lib/adapters/anymaths_adapter/requirements.txt +1 -0
  29. gatewaysdk/algorithm/gepa/lib/adapters/default_adapter/README.md +0 -0
  30. gatewaysdk/algorithm/gepa/lib/adapters/default_adapter/__init__.py +0 -0
  31. gatewaysdk/algorithm/gepa/lib/adapters/default_adapter/default_adapter.py +209 -0
  32. gatewaysdk/algorithm/gepa/lib/adapters/dspy_adapter/README.md +7 -0
  33. gatewaysdk/algorithm/gepa/lib/adapters/dspy_adapter/__init__.py +0 -0
  34. gatewaysdk/algorithm/gepa/lib/adapters/dspy_adapter/dspy_adapter.py +307 -0
  35. gatewaysdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/README.md +99 -0
  36. gatewaysdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/dspy_program_proposal_signature.py +137 -0
  37. gatewaysdk/algorithm/gepa/lib/adapters/dspy_full_program_adapter/full_program_adapter.py +268 -0
  38. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/GEPA_RAG.md +621 -0
  39. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/__init__.py +56 -0
  40. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/evaluation_metrics.py +226 -0
  41. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/generic_rag_adapter.py +496 -0
  42. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/rag_pipeline.py +238 -0
  43. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_store_interface.py +212 -0
  44. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/__init__.py +2 -0
  45. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/chroma_store.py +196 -0
  46. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/lancedb_store.py +422 -0
  47. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/milvus_store.py +409 -0
  48. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/qdrant_store.py +368 -0
  49. gatewaysdk/algorithm/gepa/lib/adapters/generic_rag_adapter/vector_stores/weaviate_store.py +418 -0
  50. gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/README.md +552 -0
  51. gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/__init__.py +37 -0
  52. gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/mcp_adapter.py +699 -0
  53. gatewaysdk/algorithm/gepa/lib/adapters/mcp_adapter/mcp_client.py +364 -0
  54. gatewaysdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/README.md +9 -0
  55. gatewaysdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/__init__.py +0 -0
  56. gatewaysdk/algorithm/gepa/lib/adapters/terminal_bench_adapter/terminal_bench_adapter.py +217 -0
  57. gatewaysdk/algorithm/gepa/lib/api.py +382 -0
  58. gatewaysdk/algorithm/gepa/lib/core/__init__.py +0 -0
  59. gatewaysdk/algorithm/gepa/lib/core/adapter.py +180 -0
  60. gatewaysdk/algorithm/gepa/lib/core/data_loader.py +74 -0
  61. gatewaysdk/algorithm/gepa/lib/core/engine.py +379 -0
  62. gatewaysdk/algorithm/gepa/lib/core/result.py +233 -0
  63. gatewaysdk/algorithm/gepa/lib/core/state.py +636 -0
  64. gatewaysdk/algorithm/gepa/lib/examples/__init__.py +0 -0
  65. gatewaysdk/algorithm/gepa/lib/examples/aime.py +24 -0
  66. gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/eval_default.py +111 -0
  67. gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/prompt-templates/instruction_prompt.txt +9 -0
  68. gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/prompt-templates/optimal_prompt.txt +24 -0
  69. gatewaysdk/algorithm/gepa/lib/examples/anymaths-bench/train_anymaths.py +177 -0
  70. gatewaysdk/algorithm/gepa/lib/examples/dspy_full_program_evolution/arc_agi.ipynb +25705 -0
  71. gatewaysdk/algorithm/gepa/lib/examples/dspy_full_program_evolution/example.ipynb +348 -0
  72. gatewaysdk/algorithm/gepa/lib/examples/mcp_adapter/__init__.py +4 -0
  73. gatewaysdk/algorithm/gepa/lib/examples/mcp_adapter/mcp_optimization_example.py +456 -0
  74. gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/RAG_GUIDE.md +613 -0
  75. gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/__init__.py +9 -0
  76. gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/rag_optimization.py +820 -0
  77. gatewaysdk/algorithm/gepa/lib/examples/rag_adapter/requirements-rag.txt +29 -0
  78. gatewaysdk/algorithm/gepa/lib/examples/terminal-bench/prompt-templates/instruction_prompt.txt +16 -0
  79. gatewaysdk/algorithm/gepa/lib/examples/terminal-bench/prompt-templates/terminus.txt +9 -0
  80. gatewaysdk/algorithm/gepa/lib/examples/terminal-bench/train_terminus.py +161 -0
  81. gatewaysdk/algorithm/gepa/lib/gepa_utils.py +117 -0
  82. gatewaysdk/algorithm/gepa/lib/logging/__init__.py +0 -0
  83. gatewaysdk/algorithm/gepa/lib/logging/experiment_tracker.py +187 -0
  84. gatewaysdk/algorithm/gepa/lib/logging/logger.py +75 -0
  85. gatewaysdk/algorithm/gepa/lib/logging/utils.py +103 -0
  86. gatewaysdk/algorithm/gepa/lib/proposer/__init__.py +0 -0
  87. gatewaysdk/algorithm/gepa/lib/proposer/base.py +31 -0
  88. gatewaysdk/algorithm/gepa/lib/proposer/merge.py +357 -0
  89. gatewaysdk/algorithm/gepa/lib/proposer/reflective_mutation/__init__.py +0 -0
  90. gatewaysdk/algorithm/gepa/lib/proposer/reflective_mutation/base.py +49 -0
  91. gatewaysdk/algorithm/gepa/lib/proposer/reflective_mutation/reflective_mutation.py +176 -0
  92. gatewaysdk/algorithm/gepa/lib/py.typed +0 -0
  93. gatewaysdk/algorithm/gepa/lib/strategies/__init__.py +0 -0
  94. gatewaysdk/algorithm/gepa/lib/strategies/batch_sampler.py +77 -0
  95. gatewaysdk/algorithm/gepa/lib/strategies/candidate_selector.py +50 -0
  96. gatewaysdk/algorithm/gepa/lib/strategies/component_selector.py +36 -0
  97. gatewaysdk/algorithm/gepa/lib/strategies/eval_policy.py +64 -0
  98. gatewaysdk/algorithm/gepa/lib/strategies/instruction_proposal.py +126 -0
  99. gatewaysdk/algorithm/gepa/lib/utils/__init__.py +10 -0
  100. gatewaysdk/algorithm/gepa/lib/utils/stop_condition.py +196 -0
  101. gatewaysdk/algorithm/gepa/tracing.py +105 -0
  102. gatewaysdk/algorithm/utils.py +177 -0
  103. gatewaysdk/algorithm/verl/__init__.py +5 -0
  104. gatewaysdk/algorithm/verl/interface.py +202 -0
  105. gatewaysdk/automations.py +111 -0
  106. gatewaysdk/benchmark_hub/README.md +88 -0
  107. gatewaysdk/benchmark_hub/__init__.py +119 -0
  108. gatewaysdk/benchmark_hub/_archive.py +189 -0
  109. gatewaysdk/benchmark_hub/_tar.py +6 -0
  110. gatewaysdk/benchmark_hub/client.py +992 -0
  111. gatewaysdk/benchmark_hub/dispatch_shard.py +34 -0
  112. gatewaysdk/benchmark_hub/eval_config.py +20 -0
  113. gatewaysdk/benchmark_hub/evals.py +351 -0
  114. gatewaysdk/benchmark_hub/harbor_adapter.py +673 -0
  115. gatewaysdk/benchmark_hub/path_utils.py +81 -0
  116. gatewaysdk/benchmark_hub/save_utils.py +101 -0
  117. gatewaysdk/benchmark_hub/verifiers_adapter.py +327 -0
  118. gatewaysdk/benchmark_hub/versioning.py +72 -0
  119. gatewaysdk/build.py +515 -0
  120. gatewaysdk/cli/__init__.py +58 -0
  121. gatewaysdk/cli/agent_runner.py +132 -0
  122. gatewaysdk/cli/http_client.py +115 -0
  123. gatewaysdk/cli/platform.py +4086 -0
  124. gatewaysdk/cli/prometheus.py +115 -0
  125. gatewaysdk/cli/release_gate.py +215 -0
  126. gatewaysdk/cli/store.py +131 -0
  127. gatewaysdk/cli/vllm.py +29 -0
  128. gatewaysdk/client.py +406 -0
  129. gatewaysdk/config.py +348 -0
  130. gatewaysdk/connectors/__init__.py +25 -0
  131. gatewaysdk/connectors/client.py +203 -0
  132. gatewaysdk/connectors/skill.py +25 -0
  133. gatewaysdk/connectors/template.py +106 -0
  134. gatewaysdk/context.py +606 -0
  135. gatewaysdk/emitter/__init__.py +43 -0
  136. gatewaysdk/emitter/annotation.py +370 -0
  137. gatewaysdk/emitter/exception.py +54 -0
  138. gatewaysdk/emitter/message.py +61 -0
  139. gatewaysdk/emitter/object.py +117 -0
  140. gatewaysdk/emitter/reward.py +320 -0
  141. gatewaysdk/env_var.py +156 -0
  142. gatewaysdk/environment/__init__.py +108 -0
  143. gatewaysdk/environment/_bundle.py +281 -0
  144. gatewaysdk/environment/_harbor.py +276 -0
  145. gatewaysdk/environment/_materialize.py +97 -0
  146. gatewaysdk/environment/_tar.py +33 -0
  147. gatewaysdk/environment/_world.py +346 -0
  148. gatewaysdk/environment/core.py +527 -0
  149. gatewaysdk/environment/errors.py +58 -0
  150. gatewaysdk/environment/runtime.py +150 -0
  151. gatewaysdk/environment/schema/__init__.py +35 -0
  152. gatewaysdk/environment/schema/__main__.py +225 -0
  153. gatewaysdk/environment/schema/_slack_fidelity.py +116 -0
  154. gatewaysdk/environment/schema/_slack_scenario.py +183 -0
  155. gatewaysdk/environment/schema/api.py +1628 -0
  156. gatewaysdk/environment/schema/batch.py +755 -0
  157. gatewaysdk/environment/schema/compiler.py +615 -0
  158. gatewaysdk/environment/schema/conform.py +428 -0
  159. gatewaysdk/environment/schema/connector.py +777 -0
  160. gatewaysdk/environment/schema/connectors/apple-business-manager/handlers.py +17 -0
  161. gatewaysdk/environment/schema/connectors/apple-business-manager/parity.json +28 -0
  162. gatewaysdk/environment/schema/connectors/apple-business-manager/provenance.json +233 -0
  163. gatewaysdk/environment/schema/connectors/apple-business-manager/scope.toml +112 -0
  164. gatewaysdk/environment/schema/connectors/apple-business-manager/world.json +1380 -0
  165. gatewaysdk/environment/schema/connectors/base.json +28 -0
  166. gatewaysdk/environment/schema/connectors/custom/handlers.py +18 -0
  167. gatewaysdk/environment/schema/connectors/custom/world.json +123 -0
  168. gatewaysdk/environment/schema/connectors/github/handlers.py +81 -0
  169. gatewaysdk/environment/schema/connectors/github/parity.json +37 -0
  170. gatewaysdk/environment/schema/connectors/github/provenance.json +134 -0
  171. gatewaysdk/environment/schema/connectors/github/scope.toml +158 -0
  172. gatewaysdk/environment/schema/connectors/github/world.json +5869 -0
  173. gatewaysdk/environment/schema/connectors/google-calendar/handlers.py +23 -0
  174. gatewaysdk/environment/schema/connectors/google-calendar/world.json +209 -0
  175. gatewaysdk/environment/schema/connectors/google-drive/handlers.py +18 -0
  176. gatewaysdk/environment/schema/connectors/google-drive/world.json +286 -0
  177. gatewaysdk/environment/schema/connectors/jamf/handlers.py +34 -0
  178. gatewaysdk/environment/schema/connectors/jamf/parity.json +49 -0
  179. gatewaysdk/environment/schema/connectors/jamf/provenance.json +181 -0
  180. gatewaysdk/environment/schema/connectors/jamf/scope.toml +128 -0
  181. gatewaysdk/environment/schema/connectors/jamf/world.json +2218 -0
  182. gatewaysdk/environment/schema/connectors/jira/handlers.py +28 -0
  183. gatewaysdk/environment/schema/connectors/jira/world.json +389 -0
  184. gatewaysdk/environment/schema/connectors/kandji/handlers.py +38 -0
  185. gatewaysdk/environment/schema/connectors/kandji/parity.json +29 -0
  186. gatewaysdk/environment/schema/connectors/kandji/provenance.json +152 -0
  187. gatewaysdk/environment/schema/connectors/kandji/scope.toml +120 -0
  188. gatewaysdk/environment/schema/connectors/kandji/world.json +765 -0
  189. gatewaysdk/environment/schema/connectors/linear/handlers.py +20 -0
  190. gatewaysdk/environment/schema/connectors/linear/world.json +363 -0
  191. gatewaysdk/environment/schema/connectors/microsoft-teams/fidelity.json +45 -0
  192. gatewaysdk/environment/schema/connectors/microsoft-teams/handlers.py +1 -0
  193. gatewaysdk/environment/schema/connectors/microsoft-teams/provenance.json +851 -0
  194. gatewaysdk/environment/schema/connectors/microsoft-teams/scope.md +231 -0
  195. gatewaysdk/environment/schema/connectors/microsoft-teams/teams-types.json +1710 -0
  196. gatewaysdk/environment/schema/connectors/microsoft-teams/world.json +858 -0
  197. gatewaysdk/environment/schema/connectors/netsuite/handlers.py +32 -0
  198. gatewaysdk/environment/schema/connectors/netsuite/parity.json +50 -0
  199. gatewaysdk/environment/schema/connectors/netsuite/provenance.json +137 -0
  200. gatewaysdk/environment/schema/connectors/netsuite/scope.toml +78 -0
  201. gatewaysdk/environment/schema/connectors/netsuite/world.json +16243 -0
  202. gatewaysdk/environment/schema/connectors/salesforce/handlers.py +806 -0
  203. gatewaysdk/environment/schema/connectors/salesforce/parity.json +46 -0
  204. gatewaysdk/environment/schema/connectors/salesforce/provenance.json +143 -0
  205. gatewaysdk/environment/schema/connectors/salesforce/scope.toml +174 -0
  206. gatewaysdk/environment/schema/connectors/salesforce/world.json +4695 -0
  207. gatewaysdk/environment/schema/connectors/slack/MODEL.md +236 -0
  208. gatewaysdk/environment/schema/connectors/slack/capabilities.json +1039 -0
  209. gatewaysdk/environment/schema/connectors/slack/handlers.py +762 -0
  210. gatewaysdk/environment/schema/connectors/slack/parity.json +65 -0
  211. gatewaysdk/environment/schema/connectors/slack/provenance.json +294 -0
  212. gatewaysdk/environment/schema/connectors/slack/scope.toml +44 -0
  213. gatewaysdk/environment/schema/connectors/slack/world.json +4993 -0
  214. gatewaysdk/environment/schema/connectors/workday/handlers.py +75 -0
  215. gatewaysdk/environment/schema/connectors/workday/parity.json +36 -0
  216. gatewaysdk/environment/schema/connectors/workday/provenance.json +179 -0
  217. gatewaysdk/environment/schema/connectors/workday/scope.toml +180 -0
  218. gatewaysdk/environment/schema/connectors/workday/world.json +806 -0
  219. gatewaysdk/environment/schema/data.py +593 -0
  220. gatewaysdk/environment/schema/extract.py +263 -0
  221. gatewaysdk/environment/schema/host.py +2437 -0
  222. gatewaysdk/environment/schema/host_surfaces.py +275 -0
  223. gatewaysdk/environment/schema/platform.py +1272 -0
  224. gatewaysdk/environment/schema/scaffold.py +250 -0
  225. gatewaysdk/environment/schema/skill.py +118 -0
  226. gatewaysdk/environment/schema/skills/agents/connector-template-author.md +202 -0
  227. gatewaysdk/environment/schema/skills/connector-schema-authoring/CONNECTORS.md +331 -0
  228. gatewaysdk/environment/schema/skills/connector-schema-authoring/CONVENTIONS.md +100 -0
  229. gatewaysdk/environment/schema/skills/connector-schema-authoring/SKILL.md +313 -0
  230. gatewaysdk/environment/schema/skills/world-data-ingestion/ROWS.md +223 -0
  231. gatewaysdk/environment/schema/skills/world-data-ingestion/SKILL.md +293 -0
  232. gatewaysdk/environment/schema/skills/worlds-getting-started/SKILL.md +390 -0
  233. gatewaysdk/environment/schema/slack.py +757 -0
  234. gatewaysdk/environment/schema/snapshot.py +479 -0
  235. gatewaysdk/environment/schema/store.py +904 -0
  236. gatewaysdk/environment/schema/tasks.py +525 -0
  237. gatewaysdk/environment/schema/tools_world.py +322 -0
  238. gatewaysdk/environment/schema/validation.py +577 -0
  239. gatewaysdk/environment/schema/worker.py +78 -0
  240. gatewaysdk/environment/schema/workers.py +500 -0
  241. gatewaysdk/environment/schema/world_tests.py +336 -0
  242. gatewaysdk/environments/__init__.py +24 -0
  243. gatewaysdk/environments/client.py +486 -0
  244. gatewaysdk/environments/types.py +318 -0
  245. gatewaysdk/execution/__init__.py +15 -0
  246. gatewaysdk/execution/base.py +64 -0
  247. gatewaysdk/execution/client_server.py +443 -0
  248. gatewaysdk/execution/events.py +69 -0
  249. gatewaysdk/execution/inter_process.py +16 -0
  250. gatewaysdk/execution/shared_memory.py +282 -0
  251. gatewaysdk/experiments/__init__.py +90 -0
  252. gatewaysdk/experiments/assignment.py +177 -0
  253. gatewaysdk/experiments/client.py +877 -0
  254. gatewaysdk/experiments/exposure.py +222 -0
  255. gatewaysdk/experiments/types.py +81 -0
  256. gatewaysdk/importers/__init__.py +49 -0
  257. gatewaysdk/importers/_normalize.py +91 -0
  258. gatewaysdk/importers/builder.py +173 -0
  259. gatewaysdk/importers/client.py +95 -0
  260. gatewaysdk/importers/langsmith.py +196 -0
  261. gatewaysdk/importers/recipes.py +249 -0
  262. gatewaysdk/instrumentation/__init__.py +300 -0
  263. gatewaysdk/instrumentation/agentops.py +314 -0
  264. gatewaysdk/instrumentation/agentops_langchain.py +45 -0
  265. gatewaysdk/instrumentation/base.py +119 -0
  266. gatewaysdk/instrumentation/litellm.py +83 -0
  267. gatewaysdk/instrumentation/registry.py +273 -0
  268. gatewaysdk/instrumentation/vllm.py +81 -0
  269. gatewaysdk/instrumentation/weave.py +500 -0
  270. gatewaysdk/integrations/__init__.py +15 -0
  271. gatewaysdk/integrations/gateway/__init__.py +11 -0
  272. gatewaysdk/integrations/gateway/client.py +171 -0
  273. gatewaysdk/integrations/tool_access.py +549 -0
  274. gatewaysdk/litagent/__init__.py +11 -0
  275. gatewaysdk/litagent/decorator.py +536 -0
  276. gatewaysdk/litagent/litagent.py +252 -0
  277. gatewaysdk/llm_proxy.py +1742 -0
  278. gatewaysdk/logging.py +370 -0
  279. gatewaysdk/memory.py +278 -0
  280. gatewaysdk/personas.py +186 -0
  281. gatewaysdk/platform/__init__.py +17 -0
  282. gatewaysdk/platform/builder.py +221 -0
  283. gatewaysdk/platform/compatibility.py +67 -0
  284. gatewaysdk/platform/manifest.py +185 -0
  285. gatewaysdk/platform/orchestrator.py +901 -0
  286. gatewaysdk/platform/registry.py +122 -0
  287. gatewaysdk/platform/worker.py +864 -0
  288. gatewaysdk/replay/__init__.py +1032 -0
  289. gatewaysdk/replay/pytest.py +56 -0
  290. gatewaysdk/reward.py +7 -0
  291. gatewaysdk/run.py +1781 -0
  292. gatewaysdk/runner/__init__.py +11 -0
  293. gatewaysdk/runner/agent.py +878 -0
  294. gatewaysdk/runner/base.py +182 -0
  295. gatewaysdk/runner/legacy.py +309 -0
  296. gatewaysdk/security.py +700 -0
  297. gatewaysdk/semconv.py +170 -0
  298. gatewaysdk/server.py +399 -0
  299. gatewaysdk/sessions.py +282 -0
  300. gatewaysdk/store/__init__.py +45 -0
  301. gatewaysdk/store/base.py +908 -0
  302. gatewaysdk/store/client_server.py +2093 -0
  303. gatewaysdk/store/collection/__init__.py +30 -0
  304. gatewaysdk/store/collection/base.py +587 -0
  305. gatewaysdk/store/collection/memory.py +970 -0
  306. gatewaysdk/store/collection/mongo.py +1412 -0
  307. gatewaysdk/store/collection_based.py +1823 -0
  308. gatewaysdk/store/gateway.py +983 -0
  309. gatewaysdk/store/gateway_listener.py +465 -0
  310. gatewaysdk/store/listener.py +58 -0
  311. gatewaysdk/store/memory.py +396 -0
  312. gatewaysdk/store/mongo.py +165 -0
  313. gatewaysdk/store/redis_stream.py +517 -0
  314. gatewaysdk/store/sqlite.py +3 -0
  315. gatewaysdk/store/threading.py +370 -0
  316. gatewaysdk/store/utils.py +142 -0
  317. gatewaysdk/tracer/__init__.py +14 -0
  318. gatewaysdk/tracer/base.py +286 -0
  319. gatewaysdk/tracer/dummy.py +106 -0
  320. gatewaysdk/tracer/otel.py +559 -0
  321. gatewaysdk/tracing/__init__.py +110 -0
  322. gatewaysdk/tracing/api.py +808 -0
  323. gatewaysdk/tracing/attributes.py +9 -0
  324. gatewaysdk/tracing/context.py +272 -0
  325. gatewaysdk/tracing/exporters/__init__.py +10 -0
  326. gatewaysdk/tracing/exporters/gateway.py +228 -0
  327. gatewaysdk/tracing/identity.py +288 -0
  328. gatewaysdk/tracing/init.py +620 -0
  329. gatewaysdk/tracing/instrumentors/__init__.py +15 -0
  330. gatewaysdk/tracing/instrumentors/claude_agent_sdk.py +766 -0
  331. gatewaysdk/tracing/instrumentors/instrumentation_principles.md +294 -0
  332. gatewaysdk/tracing/instrumentors/registry.py +352 -0
  333. gatewaysdk/tracing/mapping.py +729 -0
  334. gatewaysdk/tracing/processors.py +393 -0
  335. gatewaysdk/tracing/push.py +617 -0
  336. gatewaysdk/tracing/push_models.py +247 -0
  337. gatewaysdk/tracing/semconv.py +294 -0
  338. gatewaysdk/tracing/span_builder.py +356 -0
  339. gatewaysdk/trainer/__init__.py +6 -0
  340. gatewaysdk/trainer/init_utils.py +263 -0
  341. gatewaysdk/trainer/legacy.py +359 -0
  342. gatewaysdk/trainer/registry.py +12 -0
  343. gatewaysdk/trainer/trainer.py +638 -0
  344. gatewaysdk/types/__init__.py +63 -0
  345. gatewaysdk/types/core.py +556 -0
  346. gatewaysdk/types/resources.py +204 -0
  347. gatewaysdk/types/tracer.py +515 -0
  348. gatewaysdk/types/tracing.py +162 -0
  349. gatewaysdk/users.py +251 -0
  350. gatewaysdk/utils/__init__.py +1 -0
  351. gatewaysdk/utils/id.py +18 -0
  352. gatewaysdk/utils/metrics.py +1025 -0
  353. gatewaysdk/utils/otel.py +550 -0
  354. gatewaysdk/utils/otlp.py +556 -0
  355. gatewaysdk/utils/redact.py +22 -0
  356. gatewaysdk/utils/server_launcher.py +1045 -0
  357. gatewaysdk/utils/system_snapshot.py +90 -0
  358. gatewaysdk/verl/__init__.py +8 -0
  359. gatewaysdk/verl/__main__.py +6 -0
  360. gatewaysdk/verl/async_server.py +46 -0
  361. gatewaysdk/verl/config.yaml +27 -0
  362. gatewaysdk/verl/daemon.py +1154 -0
  363. gatewaysdk/verl/dataset.py +44 -0
  364. gatewaysdk/verl/entrypoint.py +248 -0
  365. gatewaysdk/verl/trainer.py +549 -0
  366. gatewaysdk/world_browser.py +748 -0
  367. gatewaysdk/world_data.py +453 -0
  368. gatewaysdk/world_sessions.py +978 -0
  369. gatewaysdk/world_tasks.py +285 -0
  370. gatewaysdk/world_tools.py +115 -0
  371. gatewaysdk/worlds.py +639 -0
  372. gatewaysdk-0.3.2.dist-info/METADATA +236 -0
  373. gatewaysdk-0.3.2.dist-info/RECORD +376 -0
  374. gatewaysdk-0.3.2.dist-info/WHEEL +4 -0
  375. gatewaysdk-0.3.2.dist-info/entry_points.txt +5 -0
  376. gatewaysdk-0.3.2.dist-info/licenses/LICENSE +19 -0
@@ -0,0 +1,762 @@
1
+ """Slack collaboration operations over one canonical, validated schema store."""
2
+
3
+ import base64
4
+ import functools
5
+ import hashlib
6
+ import json
7
+ from fractions import Fraction
8
+ from pathlib import Path
9
+
10
+ from gateway_env import store, tool
11
+
12
+
13
+ @tool(name="list_channels")
14
+ def list_channels(workspace_id):
15
+ if Path(__file__).with_name("slack-principal.json").is_file():
16
+ if workspace_id != _principal()[0]["workspace_id"]:
17
+ raise ValueError("channel_not_found")
18
+ return [
19
+ row
20
+ for row in store.list("slack_channels")
21
+ if row["workspace_id"] == workspace_id
22
+ and (not (row.get("is_private") or row.get("is_im") or row.get("is_mpim")) or _member(row["id"]))
23
+ ]
24
+ return [channel for channel in store.list("slack_channels") if channel["workspace_id"] == workspace_id]
25
+
26
+
27
+ def _timestamp_key(message):
28
+ """Compare decimal strings exactly, including unequal fractional widths."""
29
+ timestamp = message["ts"]
30
+ seconds, separator, fraction = timestamp.partition(".")
31
+ if not separator or not seconds or not fraction or any(c not in "0123456789" for c in seconds + fraction):
32
+ raise ValueError("list_messages requires decimal Slack timestamp strings")
33
+ seconds = seconds.lstrip("0") or "0"
34
+ return len(seconds), seconds, fraction.rstrip("0"), timestamp
35
+
36
+
37
+ @tool(name="list_messages")
38
+ def list_messages(workspace_id, channel_id):
39
+ if Path(__file__).with_name("slack-principal.json").is_file():
40
+ if workspace_id != _principal()[0]["workspace_id"]:
41
+ raise ValueError("channel_not_found")
42
+ _channel(channel_id)
43
+ messages = [
44
+ message
45
+ for message in store.list("slack_messages")
46
+ if message["workspace_id"] == workspace_id and message["channel_id"] == channel_id
47
+ ]
48
+ return sorted(messages, key=_timestamp_key)
49
+
50
+
51
+ @tool(name="post_message")
52
+ def post_message(message):
53
+ if Path(__file__).with_name("slack-principal.json").is_file():
54
+ principal, _ = _principal()
55
+ if message["workspace_id"] != principal["workspace_id"] or message.get("user") != principal["user_id"]:
56
+ raise ValueError("no_permission")
57
+ _channel(message["channel_id"], write=True, membership=True)
58
+ if (
59
+ not message.get("user")
60
+ or not message.get("text")
61
+ or any(field in message for field in ("bot_id", "app_id", "subtype"))
62
+ ):
63
+ raise ValueError("post_message supports top-level human text messages only")
64
+ member = {
65
+ "workspace_id": message["workspace_id"],
66
+ "channel_id": message["channel_id"],
67
+ "user_id": message["user"],
68
+ }
69
+ if store.get("slack_memberships", member) is None:
70
+ raise ValueError("not_in_channel: the author is not a member of this channel")
71
+ author = store.get("slack_users", {"workspace_id": message["workspace_id"], "id": message["user"]})
72
+ if author.get("deleted") or author.get("is_bot") or author.get("is_app_user"):
73
+ raise ValueError("post_message supports top-level human text messages only")
74
+ return store.insert("slack_messages", message, source={"record": "tool.post_message"})
75
+
76
+
77
+ # Slack-shaped executable subset. Every adapter delegates to these canonical
78
+ # operations; principal identity is immutable bundle configuration, never input.
79
+ class _SlackError(Exception):
80
+ pass
81
+
82
+
83
+ def _api(fn):
84
+ @functools.wraps(fn)
85
+ def invoke(*args, **kwargs):
86
+ try:
87
+ return fn(*args, **kwargs)
88
+ except _SlackError as error:
89
+ return {"ok": False, "error": str(error)}
90
+
91
+ return invoke
92
+
93
+
94
+ def _principal():
95
+ path = Path(__file__).with_name("slack-principal.json")
96
+ if not path.is_file():
97
+ raise _SlackError("not_authed")
98
+ principal = json.loads(path.read_text())
99
+ actor = store.get("slack_users", {"workspace_id": principal["workspace_id"], "id": principal["user_id"]})
100
+ if actor is None or actor.get("deleted") or actor.get("suspended"):
101
+ raise _SlackError("account_inactive")
102
+ return principal, actor
103
+
104
+
105
+ def _rows(entity, **where):
106
+ principal, _ = _principal()
107
+ return [
108
+ row
109
+ for row in store.list(entity)
110
+ if row.get("workspace_id") == principal["workspace_id"] and all(row.get(k) == v for k, v in where.items())
111
+ ]
112
+
113
+
114
+ class _ReadRows:
115
+ """Lazy lookup maps for one read response, never shared with mutations."""
116
+
117
+ def __init__(self):
118
+ self.workspace = _principal()[0]["workspace_id"]
119
+ self.entities = {}
120
+ self.indexes = {}
121
+
122
+ def __call__(self, entity, **where):
123
+ if entity not in self.entities:
124
+ self.entities[entity] = [row for row in store.list(entity) if row.get("workspace_id") == self.workspace]
125
+ values = self.entities[entity]
126
+ if not where:
127
+ return values
128
+ fields = tuple(sorted(where))
129
+ key = entity, fields
130
+ if key not in self.indexes:
131
+ groups = {}
132
+ for row in values:
133
+ groups.setdefault(tuple(row.get(field) for field in fields), []).append(row)
134
+ self.indexes[key] = groups
135
+ return self.indexes[key].get(tuple(where[field] for field in fields), [])
136
+
137
+
138
+ def _key(**fields):
139
+ principal, _ = _principal()
140
+ return {"workspace_id": principal["workspace_id"], **fields}
141
+
142
+
143
+ def _source(method):
144
+ principal, _ = _principal()
145
+ return {"record": "tool.slack." + method + ":" + principal["user_id"]}
146
+
147
+
148
+ def _member(channel):
149
+ principal, _ = _principal()
150
+ return store.get("slack_memberships", _key(channel_id=channel, user_id=principal["user_id"])) is not None
151
+
152
+
153
+ def _channel(channel, *, write=False, membership=False):
154
+ principal, actor = _principal()
155
+ row = store.get("slack_channels", _key(id=channel))
156
+ if row is None or ((row.get("is_private") or row.get("is_im") or row.get("is_mpim")) and not _member(channel)):
157
+ raise _SlackError("channel_not_found")
158
+ if (membership or actor.get("is_bot")) and not _member(channel):
159
+ raise _SlackError("not_in_channel")
160
+ if write and row.get("is_archived"):
161
+ raise _SlackError("is_archived")
162
+ state = store.get("slack_conversation_states", _key(channel_id=channel, user_id=principal["user_id"]))
163
+ if write and (row.get("is_frozen") or (state and state.get("is_read_only"))):
164
+ raise _SlackError("restricted_action")
165
+ return row
166
+
167
+
168
+ def _public(row):
169
+ return {k: v for k, v in row.items() if k not in ("workspace_id", "channel_id")}
170
+
171
+
172
+ def _conversation(row):
173
+ result = _public(row)
174
+ result["is_member"] = _member(row["id"])
175
+ principal, _ = _principal()
176
+ state = store.get("slack_conversation_states", _key(channel_id=row["id"], user_id=principal["user_id"]))
177
+ if state:
178
+ result.update(
179
+ {
180
+ k: v
181
+ for k, v in state.items()
182
+ if k not in ("workspace_id", "channel_id", "user_id", "counterpart_user_id")
183
+ }
184
+ )
185
+ if "counterpart_user_id" in state:
186
+ result["user"] = state["counterpart_user_id"]
187
+ result["is_member"] = _member(row["id"])
188
+ return result
189
+
190
+
191
+ def _page(items, field, scope, cursor="", limit=100, *, render=None):
192
+ token = hashlib.sha256(json.dumps(scope, sort_keys=True).encode()).hexdigest()[:20]
193
+ offset = 0
194
+ if cursor:
195
+ try:
196
+ parsed = json.loads(base64.urlsafe_b64decode(cursor.encode()).decode())
197
+ if parsed["scope"] != token or type(parsed["offset"]) is not int or parsed["offset"] < 0:
198
+ raise ValueError()
199
+ offset = parsed["offset"]
200
+ except (ValueError, KeyError, TypeError, UnicodeError):
201
+ raise _SlackError("invalid_cursor") from None
202
+ end = offset + limit
203
+ next_cursor = (
204
+ base64.urlsafe_b64encode(json.dumps({"scope": token, "offset": end}, separators=(",", ":")).encode()).decode()
205
+ if end < len(items)
206
+ else ""
207
+ )
208
+ return {
209
+ "ok": True,
210
+ field: [render(item) for item in items[offset:end]] if render else items[offset:end],
211
+ "response_metadata": {"next_cursor": next_cursor},
212
+ "has_more": bool(next_cursor),
213
+ }
214
+
215
+
216
+ def _number(ts, error="invalid_ts_latest"):
217
+ try:
218
+ if not isinstance(ts, str) or not ts or not all(c in "0123456789" for c in ts):
219
+ _timestamp_key({"ts": ts})
220
+ return Fraction(ts)
221
+ except (ValueError, TypeError, AttributeError, ZeroDivisionError):
222
+ raise _SlackError(error) from None
223
+
224
+
225
+ def _message(channel, ts, *, write=False):
226
+ _channel(channel, write=write, membership=write)
227
+ _number(ts, "bad_timestamp")
228
+ row = store.get("slack_messages", _key(channel_id=channel, ts=ts))
229
+ if row is None:
230
+ raise _SlackError("message_not_found")
231
+ return row
232
+
233
+
234
+ def _render_message(row, rows=None):
235
+ rows = rows or _rows
236
+ result = _public(row)
237
+ channel, ts = row["channel_id"], row["ts"]
238
+ thread = store.get("slack_message_threads", _key(channel_id=channel, reply_ts=ts))
239
+ children = rows("slack_message_threads", channel_id=channel, parent_ts=ts)
240
+ if thread or children:
241
+ result["thread_ts"] = thread["parent_ts"] if thread else ts
242
+ reactions = rows("slack_reaction_summaries", channel_id=channel, message_ts=ts)
243
+ if reactions:
244
+ result["reactions"] = [
245
+ {
246
+ "name": r["name"],
247
+ "count": r["count"],
248
+ "users": [
249
+ u["user_id"]
250
+ for u in rows("slack_reaction_users", channel_id=channel, message_ts=ts, name=r["name"])
251
+ ],
252
+ }
253
+ for r in reactions
254
+ ]
255
+ files = []
256
+ for association in rows("slack_message_files", channel_id=channel, message_ts=ts):
257
+ value = store.get("slack_files", _key(id=association["file_id"]))
258
+ files.append((association.get("position", 0), _public(value)))
259
+ for association in rows("slack_unavailable_message_files", channel_id=channel, message_ts=ts):
260
+ files.append((association.get("position", 0), {"id": association["file_id"], **association["reference"]}))
261
+ if files:
262
+ result["files"] = [value for _, value in sorted(files, key=lambda pair: (pair[0], pair[1]["id"]))]
263
+ return result
264
+
265
+
266
+ def _next_ts():
267
+ # A deterministic logical clock, explicitly not vendor/server wall time.
268
+ seconds = (
269
+ max(
270
+ (int(_number(row["ts"]).numerator // _number(row["ts"]).denominator) for row in _rows("slack_messages")),
271
+ default=1700000000,
272
+ )
273
+ + 1
274
+ )
275
+ return str(seconds) + ".000001"
276
+
277
+
278
+ def _user(row, rows=None):
279
+ rows = rows or _rows
280
+ result = _public(row)
281
+ if isinstance(result.get("profile"), dict):
282
+ profile = dict(result["profile"])
283
+ marker = profile.pop("fields_state", None)
284
+ fields = rows("slack_user_profile_fields", user_id=row["id"])
285
+ if marker == "null":
286
+ profile["fields"] = None
287
+ elif marker == "present" or fields:
288
+ profile["fields"] = {
289
+ f["field_id"]: {k: v for k, v in f.items() if k not in ("workspace_id", "user_id", "field_id")}
290
+ for f in fields
291
+ }
292
+ result["profile"] = profile
293
+ return result
294
+
295
+
296
+ @tool(name="users.list")
297
+ @_api
298
+ def users_list(cursor="", limit=100):
299
+ rows = _ReadRows()
300
+ return _page(
301
+ rows("slack_users"),
302
+ "members",
303
+ ["users.list", _principal()[0]],
304
+ cursor,
305
+ limit,
306
+ render=lambda row: _user(row, rows),
307
+ )
308
+
309
+
310
+ @tool(name="users.info")
311
+ @_api
312
+ def users_info(user):
313
+ value = store.get("slack_users", _key(id=user))
314
+ if value is None:
315
+ raise _SlackError("user_not_found")
316
+ return {"ok": True, "user": _user(value)}
317
+
318
+
319
+ @tool(name="conversations.list")
320
+ @_api
321
+ def conversations_list(cursor="", limit=100, types="public_channel", exclude_archived=False):
322
+ allowed = types.split(",")
323
+ if any(kind not in ("public_channel", "private_channel", "im", "mpim") for kind in allowed):
324
+ raise _SlackError("invalid_types")
325
+ values = []
326
+ for row in _rows("slack_channels"):
327
+ kind = (
328
+ "im"
329
+ if row.get("is_im")
330
+ else "mpim" if row.get("is_mpim") else "private_channel" if row.get("is_private") else "public_channel"
331
+ )
332
+ if (
333
+ kind not in allowed
334
+ or (exclude_archived and row.get("is_archived"))
335
+ or (kind != "public_channel" and not _member(row["id"]))
336
+ ):
337
+ continue
338
+ values.append(row)
339
+ return _page(
340
+ values,
341
+ "channels",
342
+ ["conversations.list", _principal()[0], types, exclude_archived],
343
+ cursor,
344
+ limit,
345
+ render=_conversation,
346
+ )
347
+
348
+
349
+ @tool(name="conversations.info")
350
+ @_api
351
+ def conversations_info(channel):
352
+ return {"ok": True, "channel": _conversation(_channel(channel))}
353
+
354
+
355
+ @tool(name="conversations.members")
356
+ @_api
357
+ def conversations_members(channel, cursor="", limit=100):
358
+ _channel(channel)
359
+ return _page(
360
+ [row["user_id"] for row in _rows("slack_memberships", channel_id=channel)],
361
+ "members",
362
+ ["conversations.members", _principal()[0], channel],
363
+ cursor,
364
+ limit,
365
+ )
366
+
367
+
368
+ def _history(channel, *, parent=None, cursor="", limit=100, oldest=None, latest=None, inclusive=False):
369
+ _channel(channel)
370
+ if parent is not None:
371
+ selected = _message(channel, parent)
372
+ association = store.get("slack_message_threads", _key(channel_id=channel, reply_ts=parent))
373
+ if association:
374
+ parent = association["parent_ts"]
375
+ if selected.get("subtype") in ("channel_join", "channel_leave"):
376
+ raise _SlackError("thread_not_found")
377
+ lower = _number(oldest, "invalid_ts_oldest") if oldest is not None else None
378
+ upper = _number(latest) if latest is not None else None
379
+ rows = _ReadRows()
380
+ threads = {row["reply_ts"]: row["parent_ts"] for row in rows("slack_message_threads", channel_id=channel)}
381
+ selected = []
382
+ for row in rows("slack_messages", channel_id=channel):
383
+ ts = row["ts"]
384
+ if parent is None and ts in threads and row.get("subtype") != "thread_broadcast":
385
+ continue
386
+ if parent is not None and ts != parent and threads.get(ts) != parent:
387
+ continue
388
+ numeric = _number(ts)
389
+ if lower is not None and (numeric < lower or (numeric == lower and not inclusive)):
390
+ continue
391
+ if upper is not None and (numeric > upper or (numeric == upper and not inclusive)):
392
+ continue
393
+ selected.append(row)
394
+ selected.sort(key=_timestamp_key, reverse=parent is None)
395
+ return _page(
396
+ selected,
397
+ "messages",
398
+ ["history", _principal()[0], channel, parent, oldest, latest, inclusive],
399
+ cursor,
400
+ limit,
401
+ render=lambda row: _render_message(row, rows),
402
+ )
403
+
404
+
405
+ @tool(name="conversations.history")
406
+ @_api
407
+ def conversations_history(channel, cursor="", limit=100, oldest=None, latest=None, inclusive=False):
408
+ return _history(channel, cursor=cursor, limit=limit, oldest=oldest, latest=latest, inclusive=inclusive)
409
+
410
+
411
+ @tool(name="conversations.replies")
412
+ @_api
413
+ def conversations_replies(channel, ts, cursor="", limit=100, oldest=None, latest=None, inclusive=False):
414
+ return _history(channel, parent=ts, cursor=cursor, limit=limit, oldest=oldest, latest=latest, inclusive=inclusive)
415
+
416
+
417
+ @tool(name="conversations.join")
418
+ @_api
419
+ def conversations_join(channel):
420
+ row = _channel(channel, write=True)
421
+ if row.get("is_private") or row.get("is_im") or row.get("is_mpim"):
422
+ raise _SlackError("method_not_supported_for_channel_type")
423
+ principal, _ = _principal()
424
+ if not _member(channel):
425
+ store.insert(
426
+ "slack_memberships",
427
+ _key(channel_id=channel, user_id=principal["user_id"]),
428
+ source=_source("conversations.join"),
429
+ )
430
+ if "num_members" in row:
431
+ store.update(
432
+ "slack_channels",
433
+ _key(id=channel),
434
+ {"num_members": row["num_members"] + 1},
435
+ source=_source("conversations.join"),
436
+ )
437
+ return {"ok": True, "channel": _conversation(store.get("slack_channels", _key(id=channel)))}
438
+
439
+
440
+ @tool(name="conversations.leave")
441
+ @_api
442
+ def conversations_leave(channel):
443
+ row = _channel(channel, write=True, membership=True)
444
+ if row.get("is_general") or row.get("is_im"):
445
+ raise _SlackError("cant_leave_general" if row.get("is_general") else "method_not_supported_for_channel_type")
446
+ principal, _ = _principal()
447
+ store.delete(
448
+ "slack_memberships",
449
+ _key(channel_id=channel, user_id=principal["user_id"]),
450
+ source=_source("conversations.leave"),
451
+ )
452
+ if "num_members" in row:
453
+ store.update(
454
+ "slack_channels",
455
+ _key(id=channel),
456
+ {"num_members": max(0, row["num_members"] - 1)},
457
+ source=_source("conversations.leave"),
458
+ )
459
+ return {"ok": True}
460
+
461
+
462
+ @tool(name="chat.postMessage")
463
+ @_api
464
+ def chat_post_message(
465
+ channel, text=None, blocks=None, attachments=None, thread_ts=None, reply_broadcast=False, client_msg_id=None
466
+ ):
467
+ _channel(channel, write=True, membership=True)
468
+ if not text and not blocks and not attachments:
469
+ raise _SlackError("no_text")
470
+ if reply_broadcast and thread_ts is None:
471
+ raise _SlackError("invalid_arguments")
472
+ principal, _ = _principal()
473
+ parent = None
474
+ if thread_ts:
475
+ parent = _message(channel, thread_ts)
476
+ if store.get("slack_message_threads", _key(channel_id=channel, reply_ts=thread_ts)) or parent.get(
477
+ "subtype"
478
+ ) in ("channel_join", "channel_leave"):
479
+ raise _SlackError("thread_not_found")
480
+ if client_msg_id:
481
+ prior = [
482
+ m
483
+ for m in _rows("slack_messages", channel_id=channel, user=principal["user_id"])
484
+ if m.get("client_msg_id") == client_msg_id
485
+ ]
486
+ if prior:
487
+ return {"ok": True, "channel": channel, "ts": prior[0]["ts"], "message": _render_message(prior[0])}
488
+ value = _key(channel_id=channel, ts=_next_ts(), type="message", user=principal["user_id"])
489
+ for name, content in (
490
+ ("text", text),
491
+ ("blocks", blocks),
492
+ ("attachments", attachments),
493
+ ("client_msg_id", client_msg_id),
494
+ ):
495
+ if content is not None:
496
+ value[name] = content
497
+ if reply_broadcast:
498
+ value["subtype"] = "thread_broadcast"
499
+ store.insert("slack_messages", value, source=_source("chat.postMessage"))
500
+ if parent:
501
+ store.insert(
502
+ "slack_message_threads",
503
+ _key(channel_id=channel, reply_ts=value["ts"], parent_ts=thread_ts),
504
+ source=_source("chat.postMessage"),
505
+ )
506
+ store.update(
507
+ "slack_messages",
508
+ _key(channel_id=channel, ts=thread_ts),
509
+ {"reply_count": parent.get("reply_count", 0) + 1, "latest_reply": value["ts"]},
510
+ source=_source("chat.postMessage"),
511
+ )
512
+ return {"ok": True, "channel": channel, "ts": value["ts"], "message": _render_message(value)}
513
+
514
+
515
+ @tool(name="chat.update")
516
+ @_api
517
+ def chat_update(channel, ts, text=None, blocks=None, attachments=None):
518
+ row = _message(channel, ts, write=True)
519
+ principal, _ = _principal()
520
+ if row.get("user") != principal["user_id"] or row.get("subtype") not in (None, "me_message", "thread_broadcast"):
521
+ raise _SlackError("cant_update_message")
522
+ patch = {
523
+ name: value
524
+ for name, value in (("text", text), ("blocks", blocks), ("attachments", attachments))
525
+ if value is not None
526
+ }
527
+ if not patch:
528
+ raise _SlackError("no_text")
529
+ if text is not None and blocks is None:
530
+ patch["blocks"] = []
531
+ patch["edited"] = {"user": principal["user_id"], "ts": _next_ts()}
532
+ updated = store.update("slack_messages", _key(channel_id=channel, ts=ts), patch, source=_source("chat.update"))
533
+ return {
534
+ "ok": True,
535
+ "channel": channel,
536
+ "ts": ts,
537
+ "text": updated.get("text", ""),
538
+ "message": _render_message(updated),
539
+ }
540
+
541
+
542
+ @tool(name="chat.delete")
543
+ @_api
544
+ def chat_delete(channel, ts):
545
+ row = _message(channel, ts, write=True)
546
+ principal, _ = _principal()
547
+ if row.get("user") != principal["user_id"]:
548
+ raise _SlackError("cant_delete_message")
549
+ if (
550
+ _rows("slack_message_threads", channel_id=channel, parent_ts=ts)
551
+ or _rows("slack_scheduled_message_threads", channel_id=channel, parent_ts=ts)
552
+ or _rows("slack_ephemeral_message_threads", channel_id=channel, parent_ts=ts)
553
+ ):
554
+ raise _SlackError("unsupported_thread_root_delete")
555
+ association = store.get("slack_message_threads", _key(channel_id=channel, reply_ts=ts))
556
+ if association:
557
+ store.delete("slack_message_threads", _key(channel_id=channel, reply_ts=ts), source=_source("chat.delete"))
558
+ parent = _message(channel, association["parent_ts"])
559
+ store.update(
560
+ "slack_messages",
561
+ _key(channel_id=channel, ts=parent["ts"]),
562
+ {"reply_count": max(0, parent.get("reply_count", 0) - 1)},
563
+ source=_source("chat.delete"),
564
+ )
565
+ for entity, extra in (
566
+ ("slack_reaction_users", ("name", "user_id")),
567
+ ("slack_reaction_summaries", ("name",)),
568
+ ("slack_message_user_states", ("user_id",)),
569
+ ("slack_file_shares", ("file_id",)),
570
+ ("slack_message_files", ("file_id",)),
571
+ ("slack_unavailable_message_files", ("file_id",)),
572
+ ("slack_pins", ()),
573
+ ):
574
+ for dependent in _rows(entity, channel_id=channel, message_ts=ts):
575
+ store.delete(
576
+ entity,
577
+ _key(channel_id=channel, message_ts=ts, **{k: dependent[k] for k in extra}),
578
+ source=_source("chat.delete"),
579
+ )
580
+ store.delete("slack_messages", _key(channel_id=channel, ts=ts), source=_source("chat.delete"))
581
+ return {"ok": True, "channel": channel, "ts": ts}
582
+
583
+
584
+ def _reaction(channel, timestamp, name, remove=False):
585
+ _message(channel, timestamp, write=True)
586
+ if not name or any(c.isspace() for c in name):
587
+ raise _SlackError("invalid_name")
588
+ principal, _ = _principal()
589
+ key = _key(channel_id=channel, message_ts=timestamp, name=name)
590
+ actor_key = {**key, "user_id": principal["user_id"]}
591
+ actor = store.get("slack_reaction_users", actor_key)
592
+ summary = store.get("slack_reaction_summaries", key)
593
+ method = "reactions.remove" if remove else "reactions.add"
594
+ if remove:
595
+ if actor is None:
596
+ raise _SlackError("no_reaction")
597
+ store.delete("slack_reaction_users", actor_key, source=_source(method))
598
+ count = max(0, summary["count"] - 1)
599
+ if count == 0 and not _rows("slack_reaction_users", channel_id=channel, message_ts=timestamp, name=name):
600
+ store.delete("slack_reaction_summaries", key, source=_source(method))
601
+ else:
602
+ store.update("slack_reaction_summaries", key, {"count": count}, source=_source(method))
603
+ else:
604
+ if actor:
605
+ raise _SlackError("already_reacted")
606
+ if summary:
607
+ store.update("slack_reaction_summaries", key, {"count": summary["count"] + 1}, source=_source(method))
608
+ else:
609
+ store.insert("slack_reaction_summaries", {**key, "count": 1}, source=_source(method))
610
+ store.insert("slack_reaction_users", actor_key, source=_source(method))
611
+ return {"ok": True}
612
+
613
+
614
+ @tool(name="reactions.add")
615
+ @_api
616
+ def reactions_add(channel, timestamp, name):
617
+ return _reaction(channel, timestamp, name)
618
+
619
+
620
+ @tool(name="reactions.remove")
621
+ @_api
622
+ def reactions_remove(channel, timestamp, name):
623
+ return _reaction(channel, timestamp, name, remove=True)
624
+
625
+
626
+ @tool(name="reactions.get")
627
+ @_api
628
+ def reactions_get(channel, timestamp):
629
+ return {"ok": True, "type": "message", "channel": channel, "message": _render_message(_message(channel, timestamp))}
630
+
631
+
632
+ def _file(file_id, *, row=None, rows=None, visible_channels=None):
633
+ rows = rows or _rows
634
+ row = store.get("slack_files", _key(id=file_id)) if row is None else row
635
+ principal, _ = _principal()
636
+ if row is None:
637
+ raise _SlackError("file_not_found")
638
+ visible = row.get("user") == principal["user_id"] or row.get("is_public")
639
+ channels = visible_channels if visible_channels is not None else {}
640
+ for share in rows("slack_message_files", file_id=file_id):
641
+ channel = share["channel_id"]
642
+ if channel not in channels:
643
+ try:
644
+ _channel(channel)
645
+ channels[channel] = True
646
+ except _SlackError:
647
+ channels[channel] = False
648
+ visible = visible or channels[channel]
649
+ if not visible:
650
+ raise _SlackError("file_not_found")
651
+ return row
652
+
653
+
654
+ @tool(name="files.info")
655
+ @_api
656
+ def files_info(file):
657
+ return {"ok": True, "file": _public(_file(file))}
658
+
659
+
660
+ @tool(name="files.list")
661
+ @_api
662
+ def files_list(channel=None, user=None, page=1, count=100):
663
+ if channel:
664
+ _channel(channel)
665
+ files = []
666
+ rows = _ReadRows()
667
+ visible_channels = {}
668
+ for row in rows("slack_files"):
669
+ if user and row.get("user") != user:
670
+ continue
671
+ if channel and not rows("slack_message_files", channel_id=channel, file_id=row["id"]):
672
+ continue
673
+ try:
674
+ files.append(_file(row["id"], row=row, rows=rows, visible_channels=visible_channels))
675
+ except _SlackError:
676
+ continue
677
+ return {
678
+ "ok": True,
679
+ "files": [_public(row) for row in files[(page - 1) * count : page * count]],
680
+ "paging": {"count": count, "total": len(files), "page": page, "pages": (len(files) + count - 1) // count},
681
+ }
682
+
683
+
684
+ @tool(name="pins.list")
685
+ @_api
686
+ def pins_list(channel):
687
+ _channel(channel)
688
+ rows = _ReadRows()
689
+ return {
690
+ "ok": True,
691
+ "items": [
692
+ {
693
+ "type": "message",
694
+ "channel": channel,
695
+ "message": _render_message(_message(channel, r["message_ts"]), rows),
696
+ **{k: r[k] for k in ("created", "created_by") if k in r},
697
+ }
698
+ for r in rows("slack_pins", channel_id=channel)
699
+ ],
700
+ }
701
+
702
+
703
+ @tool(name="pins.add")
704
+ @_api
705
+ def pins_add(channel, timestamp):
706
+ _message(channel, timestamp, write=True)
707
+ key = _key(channel_id=channel, message_ts=timestamp)
708
+ if store.get("slack_pins", key):
709
+ raise _SlackError("already_pinned")
710
+ store.insert("slack_pins", {**key, "created_by": _principal()[0]["user_id"]}, source=_source("pins.add"))
711
+ return {"ok": True}
712
+
713
+
714
+ @tool(name="pins.remove")
715
+ @_api
716
+ def pins_remove(channel, timestamp):
717
+ _message(channel, timestamp, write=True)
718
+ key = _key(channel_id=channel, message_ts=timestamp)
719
+ if store.get("slack_pins", key) is None:
720
+ raise _SlackError("no_pin")
721
+ store.delete("slack_pins", key, source=_source("pins.remove"))
722
+ return {"ok": True}
723
+
724
+
725
+ @tool(name="bookmarks.list")
726
+ @_api
727
+ def bookmarks_list(channel_id):
728
+ _channel(channel_id)
729
+ return {"ok": True, "bookmarks": [_public(r) for r in _rows("slack_bookmarks", channel_id=channel_id)]}
730
+
731
+
732
+ @tool(name="usergroups.list")
733
+ @_api
734
+ def usergroups_list(include_users=False, include_disabled=False):
735
+ values = []
736
+ rows = _ReadRows()
737
+ for row in rows("slack_usergroups"):
738
+ if row.get("date_delete") and not include_disabled:
739
+ continue
740
+ value = _public(row)
741
+ value.pop("users_state", None)
742
+ if include_users:
743
+ value["users"] = [r["user_id"] for r in rows("slack_usergroup_memberships", usergroup_id=row["id"])]
744
+ values.append(value)
745
+ return {"ok": True, "usergroups": values}
746
+
747
+
748
+ @tool(name="usergroups.users.list")
749
+ @_api
750
+ def usergroups_users_list(usergroup):
751
+ if store.get("slack_usergroups", _key(id=usergroup)) is None:
752
+ raise _SlackError("usergroup_not_found")
753
+ return {"ok": True, "users": [r["user_id"] for r in _rows("slack_usergroup_memberships", usergroup_id=usergroup)]}
754
+
755
+
756
+ @tool(name="users.getPresence")
757
+ @_api
758
+ def users_get_presence(user):
759
+ row = store.get("slack_presence", _key(user_id=user))
760
+ if row is None:
761
+ raise _SlackError("presence_not_observed")
762
+ return {"ok": True, **{k: v for k, v in _public(row).items() if k != "user_id"}}