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,766 @@
1
+ # Copyright (c) Gateway. All rights reserved.
2
+
3
+ """Instrumentor for claude-agent-sdk.
4
+
5
+ This module provides automatic instrumentation for the Claude Agent SDK,
6
+ capturing conversation turns, tool calls, and response metadata.
7
+
8
+ Spans are annotated with semantic attributes following Gateway conventions,
9
+ ensuring proper processing and display in the Gateway dashboard.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import functools
15
+ import json
16
+ import logging
17
+ from typing import TYPE_CHECKING, Any, AsyncIterator, Optional
18
+
19
+ from opentelemetry import trace
20
+ from opentelemetry.context import Context
21
+ from opentelemetry.trace import Span, SpanKind, Status, StatusCode
22
+
23
+ from ..semconv import ( # Gateway observation attributes (primary namespace, resolved by attributeAliases.ts); OpenTelemetry GenAI attributes (widely supported); OpenInference attributes; GatewaySDK custom attributes; Claude-specific attributes
24
+ CLAUDE_API_ERROR,
25
+ CLAUDE_ERRORS,
26
+ CLAUDE_MESSAGE_UUID,
27
+ CLAUDE_MODEL_USAGE,
28
+ CLAUDE_PARENT_TOOL_USE_ID,
29
+ CLAUDE_RATE_LIMIT_STATUS,
30
+ CLAUDE_RATE_LIMIT_TYPE,
31
+ CLAUDE_STOP_REASON,
32
+ CLAUDE_STRUCTURED_OUTPUT,
33
+ CLAUDE_SYSTEM_DATA,
34
+ CLAUDE_SYSTEM_SUBTYPE,
35
+ CLAUDE_THINKING_SIGNATURE,
36
+ GEN_AI_CONVERSATION_ID,
37
+ GEN_AI_OPERATION_NAME,
38
+ GEN_AI_REQUEST_MODEL,
39
+ GEN_AI_RESPONSE_FINISH_REASONS,
40
+ GEN_AI_RESPONSE_ID,
41
+ GEN_AI_RESPONSE_MODEL,
42
+ GEN_AI_SYSTEM,
43
+ GEN_AI_TOOL_CALL_ARGUMENTS,
44
+ GEN_AI_TOOL_CALL_RESULT,
45
+ GEN_AI_TOOL_NAME,
46
+ GEN_AI_USAGE_COST,
47
+ GEN_AI_USAGE_INPUT_TOKENS,
48
+ GEN_AI_USAGE_OUTPUT_TOKENS,
49
+ INPUT_MIME_TYPE,
50
+ INPUT_VALUE,
51
+ LLM_MODEL_NAME,
52
+ LLM_PROVIDER,
53
+ LLM_SYSTEM,
54
+ LLM_TOKEN_COUNT_COMPLETION,
55
+ LLM_TOKEN_COUNT_PROMPT,
56
+ LLM_TOKEN_COUNT_TOTAL,
57
+ GATEWAY_DURATION_API_MS,
58
+ GATEWAY_DURATION_MS,
59
+ GATEWAY_LLM_COST_USD,
60
+ GATEWAY_LLM_THINKING,
61
+ GATEWAY_NUM_TURNS,
62
+ GATEWAY_TOOL_IS_ERROR,
63
+ MIME_TYPE_JSON,
64
+ MIME_TYPE_TEXT,
65
+ MODEL_NAME,
66
+ OBSERVATION_INPUT,
67
+ OBSERVATION_OUTPUT,
68
+ OPENINFERENCE_SPAN_KIND,
69
+ OUTPUT_MIME_TYPE,
70
+ OUTPUT_VALUE,
71
+ SESSION_ID,
72
+ SPAN_KIND_LLM,
73
+ SPAN_KIND_TOOL,
74
+ TOOL_NAME,
75
+ TRACE_NAME,
76
+ )
77
+ from .registry import BaseInstrumentor, _is_package_available
78
+
79
+ if TYPE_CHECKING:
80
+ from openinference.instrumentation import TraceConfig
81
+
82
+ logger = logging.getLogger(__name__)
83
+
84
+ # Tracer name for claude-agent-sdk spans
85
+ TRACER_NAME = "gatewaysdk.instrumentation.claude_agent_sdk"
86
+
87
+ # Attribute to store span on client instance
88
+ _CLIENT_SPAN_ATTR = "_gatewaysdk_conversation_span"
89
+
90
+ # Session IDs that indicate "no real session was provided" — ResultMessage session_id
91
+ # should only override the span when it is NOT one of these placeholder values.
92
+ # "default" is the instrumented_query() default parameter; placeholder values from
93
+ # test helpers or SDK internals should not clobber a user-provided session_id.
94
+ _SESSION_ID_PLACEHOLDERS = frozenset({"default"})
95
+
96
+ # Max length for auto-generated trace names derived from the prompt.
97
+ _TRACE_NAME_MAX_LEN = 80
98
+
99
+
100
+ class ClaudeAgentSDKInstrumentor(BaseInstrumentor):
101
+ """Instrumentor for claude-agent-sdk.
102
+
103
+ This instrumentor wraps the ClaudeSDKClient to automatically create
104
+ OpenTelemetry spans for:
105
+ - Conversation turns (query calls)
106
+ - Tool executions
107
+ - Response processing
108
+
109
+ Example::
110
+
111
+ import gatewaysdk.tracing as tracing
112
+
113
+ # Auto-instruments claude-agent-sdk if installed
114
+ tracing.init()
115
+
116
+ # Now all ClaudeSDKClient usage is traced
117
+ from claude_agent_sdk import ClaudeSDKClient
118
+ client = ClaudeSDKClient()
119
+ # ...
120
+ """
121
+
122
+ def __init__(self):
123
+ self._instrumented = False
124
+ self._original_query: Optional[Any] = None
125
+ self._original_receive_messages: Optional[Any] = None
126
+ self._original_receive_response: Optional[Any] = None
127
+
128
+ @property
129
+ def name(self) -> str:
130
+ return "claude_agent_sdk"
131
+
132
+ @property
133
+ def package_name(self) -> str:
134
+ return "claude_agent_sdk"
135
+
136
+ def is_available(self) -> bool:
137
+ """Check if claude-agent-sdk is installed."""
138
+ return _is_package_available("claude_agent_sdk")
139
+
140
+ def instrument(self, trace_config: Optional["TraceConfig"] = None) -> None:
141
+ """Activate instrumentation for claude-agent-sdk.
142
+
143
+ This wraps ClaudeSDKClient.query() and receive_messages() to
144
+ automatically create spans for conversation turns.
145
+ """
146
+ if self._instrumented:
147
+ logger.debug("claude_agent_sdk already instrumented")
148
+ return
149
+
150
+ if not self.is_available():
151
+ logger.debug("claude_agent_sdk not available, skipping instrumentation")
152
+ return
153
+
154
+ try:
155
+ from claude_agent_sdk import ClaudeSDKClient
156
+
157
+ # Store original methods
158
+ self._original_query = ClaudeSDKClient.query
159
+ self._original_receive_messages = ClaudeSDKClient.receive_messages
160
+
161
+ # Create instrumented versions
162
+ instrumented_query = self._create_instrumented_query(self._original_query)
163
+ instrumented_receive_messages = self._create_instrumented_receive_messages(self._original_receive_messages)
164
+
165
+ # Patch the methods
166
+ ClaudeSDKClient.query = instrumented_query
167
+ ClaudeSDKClient.receive_messages = instrumented_receive_messages
168
+
169
+ # NOTE: do NOT also wrap receive_response(). It is a thin convenience
170
+ # method that internally does `async for m in self.receive_messages()`
171
+ # — i.e. it delegates to the (already patched) receive_messages. Also
172
+ # wrapping it double-instruments every message when a caller uses
173
+ # receive_response(): duplicated TOOL/GENERATION spans and a nested
174
+ # attach()/detach() that raises "Token was created in a different
175
+ # Context". Patching receive_messages alone covers both entry points.
176
+ self._original_receive_response = None
177
+
178
+ self._instrumented = True
179
+ logger.info("Instrumented claude_agent_sdk")
180
+
181
+ except Exception as e:
182
+ logger.warning("Failed to instrument claude_agent_sdk: %s", e)
183
+ raise
184
+
185
+ def uninstrument(self) -> None:
186
+ """Deactivate instrumentation for claude-agent-sdk."""
187
+ if not self._instrumented:
188
+ return
189
+
190
+ try:
191
+ from claude_agent_sdk import ClaudeSDKClient
192
+
193
+ # Restore original methods
194
+ if self._original_query is not None:
195
+ ClaudeSDKClient.query = self._original_query
196
+ if self._original_receive_messages is not None:
197
+ ClaudeSDKClient.receive_messages = self._original_receive_messages
198
+ if self._original_receive_response is not None:
199
+ ClaudeSDKClient.receive_response = self._original_receive_response
200
+
201
+ self._instrumented = False
202
+ self._original_query = None
203
+ self._original_receive_messages = None
204
+ self._original_receive_response = None
205
+ logger.info("Uninstrumented claude_agent_sdk")
206
+
207
+ except Exception as e:
208
+ logger.warning("Failed to uninstrument claude_agent_sdk: %s", e)
209
+
210
+ def _create_instrumented_query(self, original_query):
211
+ """Create an instrumented version of ClaudeSDKClient.query().
212
+
213
+ This creates a span and stores it on the client instance so that
214
+ receive_messages() can add output attributes to it later.
215
+ """
216
+
217
+ @functools.wraps(original_query)
218
+ async def instrumented_query(
219
+ client_self,
220
+ prompt: str | Any,
221
+ session_id: str = "default",
222
+ ) -> None:
223
+ tracer = trace.get_tracer(TRACER_NAME)
224
+
225
+ # Get model from client options
226
+ model = getattr(client_self.options, "model", None) or "claude"
227
+
228
+ # Create span as a NEW TRACE (fresh context = each turn is its own trace)
229
+ span = tracer.start_span(
230
+ "llm.conversation_turn",
231
+ kind=SpanKind.CLIENT,
232
+ context=Context(), # Empty context creates a new root trace
233
+ )
234
+
235
+ # Record input using multiple conventions for compatibility
236
+ input_value = prompt if isinstance(prompt, str) else str(prompt)
237
+
238
+ # Langfuse attributes (REQUIRED for OtelIngestionProcessor to extract I/O)
239
+ span.set_attribute(OBSERVATION_INPUT, input_value)
240
+ span.set_attribute(MODEL_NAME, model)
241
+
242
+ # OpenInference attributes
243
+ span.set_attribute(OPENINFERENCE_SPAN_KIND, SPAN_KIND_LLM)
244
+ span.set_attribute(INPUT_VALUE, input_value)
245
+ span.set_attribute(INPUT_MIME_TYPE, MIME_TYPE_TEXT)
246
+ span.set_attribute(LLM_MODEL_NAME, model)
247
+ span.set_attribute(LLM_SYSTEM, "anthropic")
248
+ span.set_attribute(LLM_PROVIDER, "anthropic")
249
+
250
+ # GenAI attributes
251
+ span.set_attribute(GEN_AI_REQUEST_MODEL, model)
252
+ span.set_attribute(GEN_AI_SYSTEM, "anthropic")
253
+ span.set_attribute(GEN_AI_OPERATION_NAME, "chat")
254
+
255
+ # Session ID — only set when the caller provided a real value.
256
+ # Placeholders like "default" are skipped to avoid creating ghost
257
+ # sessions in Gateway that group all sessionless traces together.
258
+ if session_id not in _SESSION_ID_PLACEHOLDERS:
259
+ span.set_attribute(SESSION_ID, session_id)
260
+ span.set_attribute(GEN_AI_CONVERSATION_ID, session_id)
261
+ else:
262
+ # Auto-generate a session ID per client instance so conversations
263
+ # are grouped without requiring the user to pass one manually.
264
+ auto_session = getattr(client_self, "_gatewaysdk_auto_session_id", None)
265
+ if auto_session is None:
266
+ import uuid
267
+
268
+ auto_session = f"claude-{uuid.uuid4().hex[:12]}"
269
+ client_self._gatewaysdk_auto_session_id = auto_session
270
+ span.set_attribute(SESSION_ID, auto_session)
271
+ span.set_attribute(GEN_AI_CONVERSATION_ID, auto_session)
272
+
273
+ # Trace name — derive from the prompt so traces are distinguishable
274
+ # in the Gateway UI. Truncate to keep names readable.
275
+ trace_label = input_value.strip().replace("\n", " ")
276
+ if len(trace_label) > _TRACE_NAME_MAX_LEN:
277
+ trace_label = trace_label[:_TRACE_NAME_MAX_LEN] + "…"
278
+ span.set_attribute(TRACE_NAME, trace_label)
279
+
280
+ # Store span on client instance for receive_messages to use
281
+ setattr(client_self, _CLIENT_SPAN_ATTR, span)
282
+
283
+ try:
284
+ # Call original method
285
+ result = await original_query(client_self, prompt, session_id)
286
+ return result
287
+ except Exception as e:
288
+ # On error, end the span here
289
+ span.set_status(Status(StatusCode.ERROR, str(e)))
290
+ span.record_exception(e)
291
+ span.end()
292
+ # Clear the stored span
293
+ if hasattr(client_self, _CLIENT_SPAN_ATTR):
294
+ delattr(client_self, _CLIENT_SPAN_ATTR)
295
+ raise
296
+
297
+ return instrumented_query
298
+
299
+ def _create_instrumented_receive_messages(self, original_receive_messages):
300
+ """Create an instrumented version of ClaudeSDKClient.receive_messages().
301
+
302
+ This uses the span created by query() to record output attributes,
303
+ then ends the span when ResultMessage is received.
304
+
305
+ The output is structured as a sequence of blocks to preserve the order:
306
+ text → tool_call → text (if that's how the response came)
307
+
308
+ Tool calls appear as child spans under the conversation turn span.
309
+ """
310
+
311
+ @functools.wraps(original_receive_messages)
312
+ async def instrumented_receive_messages(client_self) -> AsyncIterator[Any]:
313
+ tracer = trace.get_tracer(TRACER_NAME)
314
+
315
+ # Import message types
316
+ from claude_agent_sdk import (
317
+ AssistantMessage,
318
+ ResultMessage,
319
+ SystemMessage,
320
+ TextBlock,
321
+ ThinkingBlock,
322
+ ToolResultBlock,
323
+ ToolUseBlock,
324
+ UserMessage,
325
+ )
326
+
327
+ # Import new message types with graceful fallback for older SDK versions
328
+ try:
329
+ from claude_agent_sdk import TaskStartedMessage
330
+ except ImportError:
331
+ TaskStartedMessage = None
332
+
333
+ try:
334
+ from claude_agent_sdk import TaskProgressMessage
335
+ except ImportError:
336
+ TaskProgressMessage = None
337
+
338
+ try:
339
+ from claude_agent_sdk import TaskNotificationMessage
340
+ except ImportError:
341
+ TaskNotificationMessage = None
342
+
343
+ try:
344
+ from claude_agent_sdk import RateLimitEvent
345
+ except ImportError:
346
+ RateLimitEvent = None
347
+
348
+ try:
349
+ from claude_agent_sdk import StreamEvent
350
+ except ImportError:
351
+ StreamEvent = None
352
+
353
+ # Get the span created by query()
354
+ conversation_span: Optional[Span] = getattr(client_self, _CLIENT_SPAN_ATTR, None)
355
+
356
+ # Parent context for the spans this instrumentor creates. Those spans
357
+ # are started with context=parent_ctx explicitly (see below), so we do
358
+ # NOT attach() it for the generator's lifetime. Holding an attached
359
+ # context across the generator's yields makes the teardown detach()
360
+ # run in a different context and log a benign "Failed to detach
361
+ # context"; and since the Claude Agent SDK executes tools in a separate
362
+ # subprocess, an attached parent-process context never reached them
363
+ # anyway. Explicit parent context gives correct nesting with no noise.
364
+ parent_ctx = None
365
+ if conversation_span:
366
+ parent_ctx = trace.set_span_in_context(conversation_span)
367
+
368
+ # Track state for this conversation turn
369
+ output_blocks = [] # Sequence of blocks: text, tool_call, text...
370
+ collected_thinking = []
371
+ thinking_signatures = [] # ThinkingBlock signatures
372
+ tool_spans = {} # Map tool_use_id to span
373
+ current_text_buffer = []
374
+ system_events = [] # SystemMessage events
375
+
376
+ try:
377
+ async for message in original_receive_messages(client_self):
378
+ if isinstance(message, AssistantMessage):
379
+ model = getattr(message, "model", None)
380
+ if conversation_span and conversation_span.is_recording():
381
+ # Update model from response (GenAI convention)
382
+ if model:
383
+ conversation_span.set_attribute(GEN_AI_RESPONSE_MODEL, model)
384
+ conversation_span.set_attribute(MODEL_NAME, model)
385
+ conversation_span.set_attribute(LLM_MODEL_NAME, model)
386
+
387
+ # Capture API error if present
388
+ if message.error:
389
+ conversation_span.set_attribute(CLAUDE_API_ERROR, message.error)
390
+
391
+ # Capture parent_tool_use_id for sub-agent tracking
392
+ if message.parent_tool_use_id:
393
+ conversation_span.set_attribute(CLAUDE_PARENT_TOOL_USE_ID, message.parent_tool_use_id)
394
+
395
+ # Message ID for correlation with Anthropic logs
396
+ if hasattr(message, "message_id") and message.message_id:
397
+ conversation_span.set_attribute(GEN_AI_RESPONSE_ID, message.message_id)
398
+
399
+ # Per-turn stop reason
400
+ if hasattr(message, "stop_reason") and message.stop_reason:
401
+ conversation_span.set_attribute(GEN_AI_RESPONSE_FINISH_REASONS, [message.stop_reason])
402
+
403
+ # Per-turn usage (captured as events since there may be multiple turns)
404
+ if hasattr(message, "usage") and message.usage:
405
+ usage_attrs = {}
406
+ input_tokens = message.usage.get("input_tokens")
407
+ output_tokens = message.usage.get("output_tokens")
408
+ if input_tokens is not None:
409
+ usage_attrs[GEN_AI_USAGE_INPUT_TOKENS] = input_tokens
410
+ if output_tokens is not None:
411
+ usage_attrs[GEN_AI_USAGE_OUTPUT_TOKENS] = output_tokens
412
+ if usage_attrs:
413
+ conversation_span.add_event("turn_usage", attributes=usage_attrs)
414
+
415
+ for block in message.content:
416
+ if isinstance(block, TextBlock):
417
+ current_text_buffer.append(block.text)
418
+
419
+ elif isinstance(block, ThinkingBlock):
420
+ collected_thinking.append(block.thinking)
421
+ # Capture thinking signature if present
422
+ if hasattr(block, "signature") and block.signature:
423
+ thinking_signatures.append(block.signature)
424
+
425
+ elif isinstance(block, ToolUseBlock):
426
+ # Flush any buffered text before tool call
427
+ if current_text_buffer:
428
+ output_blocks.append({"type": "text", "content": "\n".join(current_text_buffer)})
429
+ current_text_buffer = []
430
+
431
+ # Add tool call block (will be updated with result)
432
+ output_blocks.append(
433
+ {
434
+ "type": "tool_call",
435
+ "name": block.name,
436
+ "id": block.id,
437
+ "input": block.input,
438
+ "output": None,
439
+ }
440
+ )
441
+
442
+ # Create a child span for this tool call
443
+ # Note: Span timing is based on when we receive ToolUseBlock/ToolResultBlock messages,
444
+ # not when the tool actually executes in Claude CLI. This may cause timing inaccuracies.
445
+ tool_span = tracer.start_span(
446
+ f"tool.{block.name}",
447
+ kind=SpanKind.INTERNAL,
448
+ context=parent_ctx,
449
+ )
450
+
451
+ # OpenInference attributes
452
+ tool_span.set_attribute(OPENINFERENCE_SPAN_KIND, SPAN_KIND_TOOL)
453
+ tool_span.set_attribute(TOOL_NAME, block.name)
454
+
455
+ # GenAI attributes
456
+ tool_span.set_attribute(GEN_AI_TOOL_NAME, block.name)
457
+ tool_span.set_attribute(GEN_AI_OPERATION_NAME, "tool_call")
458
+
459
+ try:
460
+ input_json = json.dumps(block.input)
461
+ # GenAI
462
+ tool_span.set_attribute(GEN_AI_TOOL_CALL_ARGUMENTS, input_json)
463
+ # Langfuse
464
+ tool_span.set_attribute(OBSERVATION_INPUT, input_json)
465
+ # OpenInference
466
+ tool_span.set_attribute(INPUT_VALUE, input_json)
467
+ tool_span.set_attribute(INPUT_MIME_TYPE, MIME_TYPE_JSON)
468
+ except (TypeError, ValueError):
469
+ input_str = str(block.input)
470
+ tool_span.set_attribute(GEN_AI_TOOL_CALL_ARGUMENTS, input_str)
471
+ tool_span.set_attribute(OBSERVATION_INPUT, input_str)
472
+ tool_span.set_attribute(INPUT_VALUE, input_str)
473
+ tool_span.set_attribute(INPUT_MIME_TYPE, MIME_TYPE_TEXT)
474
+
475
+ tool_spans[block.id] = tool_span
476
+
477
+ elif isinstance(message, UserMessage):
478
+ # Capture UserMessage uuid and parent_tool_use_id if present
479
+ if conversation_span and conversation_span.is_recording():
480
+ if hasattr(message, "uuid") and message.uuid:
481
+ conversation_span.set_attribute(CLAUDE_MESSAGE_UUID, message.uuid)
482
+ if hasattr(message, "parent_tool_use_id") and message.parent_tool_use_id:
483
+ conversation_span.set_attribute(CLAUDE_PARENT_TOOL_USE_ID, message.parent_tool_use_id)
484
+
485
+ # Check for tool results
486
+ if isinstance(message.content, list):
487
+ for block in message.content:
488
+ if isinstance(block, ToolResultBlock):
489
+ tool_span = tool_spans.get(block.tool_use_id)
490
+ content_str = str(block.content) if block.content else ""
491
+ is_error = getattr(block, "is_error", False) or False
492
+
493
+ # Update the tool_call block in output_blocks
494
+ for ob in output_blocks:
495
+ if ob.get("type") == "tool_call" and ob.get("id") == block.tool_use_id:
496
+ ob["output"] = content_str
497
+ ob["is_error"] = is_error
498
+ break
499
+
500
+ if tool_span:
501
+ # GenAI
502
+ tool_span.set_attribute(GEN_AI_TOOL_CALL_RESULT, content_str)
503
+ # Langfuse
504
+ tool_span.set_attribute(OBSERVATION_OUTPUT, content_str)
505
+ # OpenInference
506
+ tool_span.set_attribute(OUTPUT_VALUE, content_str)
507
+ # Determine output mime type based on content (validate JSON)
508
+ output_mime = MIME_TYPE_TEXT
509
+ stripped = content_str.strip()
510
+ if stripped and stripped[0] in ("{", "["):
511
+ try:
512
+ json.loads(content_str)
513
+ output_mime = MIME_TYPE_JSON
514
+ except (json.JSONDecodeError, ValueError):
515
+ pass
516
+ tool_span.set_attribute(OUTPUT_MIME_TYPE, output_mime)
517
+ # GatewaySDK
518
+ tool_span.set_attribute(GATEWAY_TOOL_IS_ERROR, is_error)
519
+
520
+ if is_error:
521
+ tool_span.set_status(Status(StatusCode.ERROR, content_str))
522
+ else:
523
+ tool_span.set_status(Status(StatusCode.OK))
524
+
525
+ tool_span.end()
526
+ del tool_spans[block.tool_use_id]
527
+
528
+ # Task messages MUST be checked before SystemMessage because
529
+ # they are subclasses of SystemMessage — isinstance(TaskStartedMessage(), SystemMessage) is True.
530
+ elif TaskStartedMessage is not None and isinstance(message, TaskStartedMessage):
531
+ if conversation_span and conversation_span.is_recording():
532
+ event_attrs = {}
533
+ if hasattr(message, "task_id") and message.task_id:
534
+ event_attrs["claude.task.id"] = message.task_id
535
+ if hasattr(message, "description") and message.description:
536
+ event_attrs["claude.task.description"] = message.description
537
+ if hasattr(message, "tool_use_id") and message.tool_use_id:
538
+ event_attrs["claude.task.tool_use_id"] = message.tool_use_id
539
+ if hasattr(message, "task_type") and message.task_type:
540
+ event_attrs["claude.task.type"] = message.task_type
541
+ if hasattr(message, "session_id") and message.session_id:
542
+ event_attrs["claude.task.session_id"] = message.session_id
543
+ conversation_span.add_event("task.started", attributes=event_attrs)
544
+
545
+ elif TaskProgressMessage is not None and isinstance(message, TaskProgressMessage):
546
+ if conversation_span and conversation_span.is_recording():
547
+ event_attrs = {}
548
+ if hasattr(message, "task_id") and message.task_id:
549
+ event_attrs["claude.task.id"] = message.task_id
550
+ if hasattr(message, "description") and message.description:
551
+ event_attrs["claude.task.description"] = message.description
552
+ if hasattr(message, "tool_use_id") and message.tool_use_id:
553
+ event_attrs["claude.task.tool_use_id"] = message.tool_use_id
554
+ if hasattr(message, "last_tool_name") and message.last_tool_name:
555
+ event_attrs["claude.task.last_tool"] = message.last_tool_name
556
+ if hasattr(message, "usage") and message.usage:
557
+ try:
558
+ event_attrs["claude.task.usage"] = json.dumps(message.usage)
559
+ except (TypeError, ValueError):
560
+ event_attrs["claude.task.usage"] = str(message.usage)
561
+ conversation_span.add_event("task.progress", attributes=event_attrs)
562
+
563
+ elif TaskNotificationMessage is not None and isinstance(message, TaskNotificationMessage):
564
+ if conversation_span and conversation_span.is_recording():
565
+ event_attrs = {}
566
+ if hasattr(message, "task_id") and message.task_id:
567
+ event_attrs["claude.task.id"] = message.task_id
568
+ if hasattr(message, "status") and message.status:
569
+ event_attrs["claude.task.status"] = message.status
570
+ if hasattr(message, "summary") and message.summary:
571
+ event_attrs["claude.task.summary"] = message.summary
572
+ if hasattr(message, "tool_use_id") and message.tool_use_id:
573
+ event_attrs["claude.task.tool_use_id"] = message.tool_use_id
574
+ if hasattr(message, "output_file") and message.output_file:
575
+ event_attrs["claude.task.output_file"] = message.output_file
576
+ if hasattr(message, "usage") and message.usage:
577
+ try:
578
+ event_attrs["claude.task.usage"] = json.dumps(message.usage)
579
+ except (TypeError, ValueError):
580
+ event_attrs["claude.task.usage"] = str(message.usage)
581
+ conversation_span.add_event("task.notification", attributes=event_attrs)
582
+
583
+ elif isinstance(message, SystemMessage):
584
+ # Generic SystemMessage handler (non-task system events)
585
+ system_events.append({"subtype": message.subtype, "data": message.data})
586
+
587
+ if conversation_span and conversation_span.is_recording():
588
+ event_attrs = {CLAUDE_SYSTEM_SUBTYPE: message.subtype}
589
+ if message.data:
590
+ try:
591
+ event_attrs[CLAUDE_SYSTEM_DATA] = json.dumps(message.data)
592
+ except (TypeError, ValueError):
593
+ event_attrs[CLAUDE_SYSTEM_DATA] = str(message.data)
594
+ conversation_span.add_event(f"system.{message.subtype}", attributes=event_attrs)
595
+
596
+ elif RateLimitEvent is not None and isinstance(message, RateLimitEvent):
597
+ if conversation_span and conversation_span.is_recording():
598
+ event_attrs = {}
599
+ # Real SDK uses rate_limit_info nested object
600
+ rli = getattr(message, "rate_limit_info", None)
601
+ if rli:
602
+ if hasattr(rli, "rate_limit_type") and rli.rate_limit_type:
603
+ event_attrs[CLAUDE_RATE_LIMIT_TYPE] = rli.rate_limit_type
604
+ if hasattr(rli, "status") and rli.status:
605
+ event_attrs[CLAUDE_RATE_LIMIT_STATUS] = rli.status
606
+ conversation_span.add_event("rate_limit", attributes=event_attrs)
607
+
608
+ elif StreamEvent is not None and isinstance(message, StreamEvent):
609
+ if conversation_span and conversation_span.is_recording():
610
+ event_type = getattr(message, "type", "stream")
611
+ conversation_span.add_event(f"stream.{event_type}")
612
+
613
+ elif isinstance(message, ResultMessage):
614
+ # Flush any remaining text buffer
615
+ if current_text_buffer:
616
+ output_blocks.append({"type": "text", "content": "\n".join(current_text_buffer)})
617
+ current_text_buffer = []
618
+
619
+ # Record final metrics on the conversation span
620
+ if conversation_span and conversation_span.is_recording():
621
+ # Output attributes (multiple conventions)
622
+ if output_blocks:
623
+ text_only = all(b["type"] == "text" for b in output_blocks)
624
+ if text_only:
625
+ full_response = "\n".join(b["content"] for b in output_blocks)
626
+ # Langfuse
627
+ conversation_span.set_attribute(OBSERVATION_OUTPUT, full_response)
628
+ # OpenInference
629
+ conversation_span.set_attribute(OUTPUT_VALUE, full_response)
630
+ conversation_span.set_attribute(OUTPUT_MIME_TYPE, MIME_TYPE_TEXT)
631
+ else:
632
+ output_json = json.dumps(output_blocks, indent=2)
633
+ # Langfuse
634
+ conversation_span.set_attribute(OBSERVATION_OUTPUT, output_json)
635
+ # OpenInference
636
+ conversation_span.set_attribute(OUTPUT_VALUE, output_json)
637
+ conversation_span.set_attribute(OUTPUT_MIME_TYPE, MIME_TYPE_JSON)
638
+
639
+ # Thinking content
640
+ if collected_thinking:
641
+ conversation_span.set_attribute(GATEWAY_LLM_THINKING, "\n".join(collected_thinking))
642
+
643
+ # Thinking signatures (Claude-specific)
644
+ if thinking_signatures:
645
+ conversation_span.set_attribute(
646
+ CLAUDE_THINKING_SIGNATURE, "\n".join(thinking_signatures)
647
+ )
648
+
649
+ # Structured output (Claude-specific)
650
+ if hasattr(message, "structured_output") and message.structured_output is not None:
651
+ try:
652
+ conversation_span.set_attribute(
653
+ CLAUDE_STRUCTURED_OUTPUT, json.dumps(message.structured_output)
654
+ )
655
+ except (TypeError, ValueError):
656
+ conversation_span.set_attribute(
657
+ CLAUDE_STRUCTURED_OUTPUT, str(message.structured_output)
658
+ )
659
+
660
+ # Cost (GenAI and GatewaySDK)
661
+ if message.total_cost_usd is not None:
662
+ conversation_span.set_attribute(GEN_AI_USAGE_COST, message.total_cost_usd)
663
+ conversation_span.set_attribute(GATEWAY_LLM_COST_USD, message.total_cost_usd)
664
+
665
+ # Token usage (GenAI and OpenInference)
666
+ if message.usage:
667
+ usage = message.usage
668
+ input_tokens = usage.get("input_tokens")
669
+ output_tokens = usage.get("output_tokens")
670
+
671
+ if input_tokens is not None:
672
+ # GenAI
673
+ conversation_span.set_attribute(GEN_AI_USAGE_INPUT_TOKENS, input_tokens)
674
+ # OpenInference
675
+ conversation_span.set_attribute(LLM_TOKEN_COUNT_PROMPT, input_tokens)
676
+
677
+ if output_tokens is not None:
678
+ # GenAI
679
+ conversation_span.set_attribute(GEN_AI_USAGE_OUTPUT_TOKENS, output_tokens)
680
+ # OpenInference
681
+ conversation_span.set_attribute(LLM_TOKEN_COUNT_COMPLETION, output_tokens)
682
+
683
+ if input_tokens is not None and output_tokens is not None:
684
+ conversation_span.set_attribute(LLM_TOKEN_COUNT_TOTAL, input_tokens + output_tokens)
685
+
686
+ # Duration (GatewaySDK)
687
+ conversation_span.set_attribute(GATEWAY_DURATION_MS, message.duration_ms)
688
+ conversation_span.set_attribute(GATEWAY_DURATION_API_MS, message.duration_api_ms)
689
+
690
+ # Turns (GatewaySDK)
691
+ conversation_span.set_attribute(GATEWAY_NUM_TURNS, message.num_turns)
692
+
693
+ # Session ID: update from ResultMessage only when it provides a
694
+ # real session (not a placeholder). In production the SDK always
695
+ # returns the authoritative session_id here; placeholders only
696
+ # appear in test helpers or when no session was created.
697
+ if (
698
+ hasattr(message, "session_id")
699
+ and message.session_id
700
+ and message.session_id not in _SESSION_ID_PLACEHOLDERS
701
+ ):
702
+ conversation_span.set_attribute(SESSION_ID, message.session_id)
703
+ conversation_span.set_attribute(GEN_AI_CONVERSATION_ID, message.session_id)
704
+
705
+ # Stop reason (why the conversation ended)
706
+ if hasattr(message, "stop_reason") and message.stop_reason:
707
+ conversation_span.set_attribute(CLAUDE_STOP_REASON, message.stop_reason)
708
+ conversation_span.set_attribute(GEN_AI_RESPONSE_FINISH_REASONS, [message.stop_reason])
709
+
710
+ # Errors (non-fatal errors during the turn)
711
+ if hasattr(message, "errors") and message.errors:
712
+ try:
713
+ conversation_span.set_attribute(CLAUDE_ERRORS, json.dumps(message.errors))
714
+ except (TypeError, ValueError):
715
+ conversation_span.set_attribute(CLAUDE_ERRORS, str(message.errors))
716
+
717
+ # Per-model usage breakdown
718
+ if hasattr(message, "model_usage") and message.model_usage:
719
+ try:
720
+ conversation_span.set_attribute(CLAUDE_MODEL_USAGE, json.dumps(message.model_usage))
721
+ except (TypeError, ValueError):
722
+ conversation_span.set_attribute(CLAUDE_MODEL_USAGE, str(message.model_usage))
723
+
724
+ # Error status
725
+ if message.is_error:
726
+ conversation_span.set_status(
727
+ Status(StatusCode.ERROR, message.result or "Unknown error")
728
+ )
729
+ else:
730
+ conversation_span.set_status(Status(StatusCode.OK))
731
+
732
+ # End the conversation span
733
+ conversation_span.end()
734
+
735
+ # Clear the stored span
736
+ if hasattr(client_self, _CLIENT_SPAN_ATTR):
737
+ delattr(client_self, _CLIENT_SPAN_ATTR)
738
+
739
+ # Yield the original message unchanged
740
+ yield message
741
+
742
+ finally:
743
+ # Clean up any unclosed tool spans
744
+ for tool_id, tool_span in tool_spans.items():
745
+ tool_span.set_status(Status(StatusCode.ERROR, "Tool span not properly closed"))
746
+ tool_span.end()
747
+
748
+ # Ensure conversation span is ended if not already
749
+ if hasattr(client_self, _CLIENT_SPAN_ATTR):
750
+ remaining_span = getattr(client_self, _CLIENT_SPAN_ATTR)
751
+ if remaining_span and remaining_span.is_recording():
752
+ if current_text_buffer:
753
+ output_blocks.append({"type": "text", "content": "\n".join(current_text_buffer)})
754
+ if output_blocks:
755
+ text_only = all(b["type"] == "text" for b in output_blocks)
756
+ if text_only:
757
+ remaining_span.set_attribute(
758
+ OBSERVATION_OUTPUT, "\n".join(b["content"] for b in output_blocks)
759
+ )
760
+ else:
761
+ remaining_span.set_attribute(OBSERVATION_OUTPUT, json.dumps(output_blocks, indent=2))
762
+ remaining_span.set_status(Status(StatusCode.OK))
763
+ remaining_span.end()
764
+ delattr(client_self, _CLIENT_SPAN_ATTR)
765
+
766
+ return instrumented_receive_messages