agentex-sdk 0.1.0a6__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 (289) hide show
  1. agentex/__init__.py +103 -0
  2. agentex/_base_client.py +1992 -0
  3. agentex/_client.py +506 -0
  4. agentex/_compat.py +219 -0
  5. agentex/_constants.py +14 -0
  6. agentex/_exceptions.py +108 -0
  7. agentex/_files.py +123 -0
  8. agentex/_models.py +829 -0
  9. agentex/_qs.py +150 -0
  10. agentex/_resource.py +43 -0
  11. agentex/_response.py +830 -0
  12. agentex/_streaming.py +333 -0
  13. agentex/_types.py +219 -0
  14. agentex/_utils/__init__.py +57 -0
  15. agentex/_utils/_logs.py +25 -0
  16. agentex/_utils/_proxy.py +65 -0
  17. agentex/_utils/_reflection.py +42 -0
  18. agentex/_utils/_resources_proxy.py +24 -0
  19. agentex/_utils/_streams.py +12 -0
  20. agentex/_utils/_sync.py +86 -0
  21. agentex/_utils/_transform.py +447 -0
  22. agentex/_utils/_typing.py +151 -0
  23. agentex/_utils/_utils.py +422 -0
  24. agentex/_version.py +4 -0
  25. agentex/lib/.keep +4 -0
  26. agentex/lib/__init__.py +0 -0
  27. agentex/lib/adk/__init__.py +41 -0
  28. agentex/lib/adk/_modules/__init__.py +0 -0
  29. agentex/lib/adk/_modules/acp.py +247 -0
  30. agentex/lib/adk/_modules/agent_task_tracker.py +176 -0
  31. agentex/lib/adk/_modules/agents.py +77 -0
  32. agentex/lib/adk/_modules/events.py +141 -0
  33. agentex/lib/adk/_modules/messages.py +285 -0
  34. agentex/lib/adk/_modules/state.py +291 -0
  35. agentex/lib/adk/_modules/streaming.py +75 -0
  36. agentex/lib/adk/_modules/tasks.py +124 -0
  37. agentex/lib/adk/_modules/tracing.py +194 -0
  38. agentex/lib/adk/providers/__init__.py +9 -0
  39. agentex/lib/adk/providers/_modules/__init__.py +0 -0
  40. agentex/lib/adk/providers/_modules/litellm.py +232 -0
  41. agentex/lib/adk/providers/_modules/openai.py +416 -0
  42. agentex/lib/adk/providers/_modules/sgp.py +85 -0
  43. agentex/lib/adk/utils/__init__.py +5 -0
  44. agentex/lib/adk/utils/_modules/__init__.py +0 -0
  45. agentex/lib/adk/utils/_modules/templating.py +94 -0
  46. agentex/lib/cli/__init__.py +0 -0
  47. agentex/lib/cli/commands/__init__.py +0 -0
  48. agentex/lib/cli/commands/agents.py +328 -0
  49. agentex/lib/cli/commands/init.py +227 -0
  50. agentex/lib/cli/commands/main.py +33 -0
  51. agentex/lib/cli/commands/secrets.py +169 -0
  52. agentex/lib/cli/commands/tasks.py +118 -0
  53. agentex/lib/cli/commands/uv.py +133 -0
  54. agentex/lib/cli/handlers/__init__.py +0 -0
  55. agentex/lib/cli/handlers/agent_handlers.py +160 -0
  56. agentex/lib/cli/handlers/cleanup_handlers.py +186 -0
  57. agentex/lib/cli/handlers/deploy_handlers.py +351 -0
  58. agentex/lib/cli/handlers/run_handlers.py +452 -0
  59. agentex/lib/cli/handlers/secret_handlers.py +670 -0
  60. agentex/lib/cli/templates/default/.dockerignore.j2 +43 -0
  61. agentex/lib/cli/templates/default/Dockerfile-uv.j2 +42 -0
  62. agentex/lib/cli/templates/default/Dockerfile.j2 +42 -0
  63. agentex/lib/cli/templates/default/README.md.j2 +193 -0
  64. agentex/lib/cli/templates/default/deploy/example.yaml.j2 +55 -0
  65. agentex/lib/cli/templates/default/manifest.yaml.j2 +116 -0
  66. agentex/lib/cli/templates/default/project/acp.py.j2 +29 -0
  67. agentex/lib/cli/templates/default/pyproject.toml.j2 +33 -0
  68. agentex/lib/cli/templates/default/requirements.txt.j2 +5 -0
  69. agentex/lib/cli/templates/deploy/Screenshot 2025-03-19 at 10.36.57/342/200/257AM.png +0 -0
  70. agentex/lib/cli/templates/deploy/example.yaml.j2 +55 -0
  71. agentex/lib/cli/templates/sync/.dockerignore.j2 +43 -0
  72. agentex/lib/cli/templates/sync/Dockerfile-uv.j2 +42 -0
  73. agentex/lib/cli/templates/sync/Dockerfile.j2 +42 -0
  74. agentex/lib/cli/templates/sync/README.md.j2 +293 -0
  75. agentex/lib/cli/templates/sync/deploy/example.yaml.j2 +55 -0
  76. agentex/lib/cli/templates/sync/manifest.yaml.j2 +116 -0
  77. agentex/lib/cli/templates/sync/project/acp.py.j2 +26 -0
  78. agentex/lib/cli/templates/sync/pyproject.toml.j2 +33 -0
  79. agentex/lib/cli/templates/sync/requirements.txt.j2 +5 -0
  80. agentex/lib/cli/templates/temporal/.dockerignore.j2 +43 -0
  81. agentex/lib/cli/templates/temporal/Dockerfile-uv.j2 +48 -0
  82. agentex/lib/cli/templates/temporal/Dockerfile.j2 +48 -0
  83. agentex/lib/cli/templates/temporal/README.md.j2 +316 -0
  84. agentex/lib/cli/templates/temporal/deploy/example.yaml.j2 +55 -0
  85. agentex/lib/cli/templates/temporal/manifest.yaml.j2 +137 -0
  86. agentex/lib/cli/templates/temporal/project/acp.py.j2 +30 -0
  87. agentex/lib/cli/templates/temporal/project/run_worker.py.j2 +33 -0
  88. agentex/lib/cli/templates/temporal/project/workflow.py.j2 +66 -0
  89. agentex/lib/cli/templates/temporal/pyproject.toml.j2 +34 -0
  90. agentex/lib/cli/templates/temporal/requirements.txt.j2 +5 -0
  91. agentex/lib/cli/utils/cli_utils.py +14 -0
  92. agentex/lib/cli/utils/credential_utils.py +103 -0
  93. agentex/lib/cli/utils/exceptions.py +6 -0
  94. agentex/lib/cli/utils/kubectl_utils.py +135 -0
  95. agentex/lib/cli/utils/kubernetes_secrets_utils.py +185 -0
  96. agentex/lib/core/__init__.py +0 -0
  97. agentex/lib/core/adapters/__init__.py +0 -0
  98. agentex/lib/core/adapters/llm/__init__.py +1 -0
  99. agentex/lib/core/adapters/llm/adapter_litellm.py +46 -0
  100. agentex/lib/core/adapters/llm/adapter_sgp.py +55 -0
  101. agentex/lib/core/adapters/llm/port.py +24 -0
  102. agentex/lib/core/adapters/streams/adapter_redis.py +128 -0
  103. agentex/lib/core/adapters/streams/port.py +50 -0
  104. agentex/lib/core/clients/__init__.py +1 -0
  105. agentex/lib/core/clients/temporal/__init__.py +0 -0
  106. agentex/lib/core/clients/temporal/temporal_client.py +181 -0
  107. agentex/lib/core/clients/temporal/types.py +47 -0
  108. agentex/lib/core/clients/temporal/utils.py +56 -0
  109. agentex/lib/core/services/__init__.py +0 -0
  110. agentex/lib/core/services/adk/__init__.py +0 -0
  111. agentex/lib/core/services/adk/acp/__init__.py +0 -0
  112. agentex/lib/core/services/adk/acp/acp.py +210 -0
  113. agentex/lib/core/services/adk/agent_task_tracker.py +85 -0
  114. agentex/lib/core/services/adk/agents.py +43 -0
  115. agentex/lib/core/services/adk/events.py +61 -0
  116. agentex/lib/core/services/adk/messages.py +164 -0
  117. agentex/lib/core/services/adk/providers/__init__.py +0 -0
  118. agentex/lib/core/services/adk/providers/litellm.py +256 -0
  119. agentex/lib/core/services/adk/providers/openai.py +723 -0
  120. agentex/lib/core/services/adk/providers/sgp.py +99 -0
  121. agentex/lib/core/services/adk/state.py +120 -0
  122. agentex/lib/core/services/adk/streaming.py +262 -0
  123. agentex/lib/core/services/adk/tasks.py +69 -0
  124. agentex/lib/core/services/adk/tracing.py +36 -0
  125. agentex/lib/core/services/adk/utils/__init__.py +0 -0
  126. agentex/lib/core/services/adk/utils/templating.py +58 -0
  127. agentex/lib/core/temporal/__init__.py +0 -0
  128. agentex/lib/core/temporal/activities/__init__.py +207 -0
  129. agentex/lib/core/temporal/activities/activity_helpers.py +37 -0
  130. agentex/lib/core/temporal/activities/adk/__init__.py +0 -0
  131. agentex/lib/core/temporal/activities/adk/acp/__init__.py +0 -0
  132. agentex/lib/core/temporal/activities/adk/acp/acp_activities.py +86 -0
  133. agentex/lib/core/temporal/activities/adk/agent_task_tracker_activities.py +76 -0
  134. agentex/lib/core/temporal/activities/adk/agents_activities.py +35 -0
  135. agentex/lib/core/temporal/activities/adk/events_activities.py +50 -0
  136. agentex/lib/core/temporal/activities/adk/messages_activities.py +94 -0
  137. agentex/lib/core/temporal/activities/adk/providers/__init__.py +0 -0
  138. agentex/lib/core/temporal/activities/adk/providers/litellm_activities.py +71 -0
  139. agentex/lib/core/temporal/activities/adk/providers/openai_activities.py +210 -0
  140. agentex/lib/core/temporal/activities/adk/providers/sgp_activities.py +42 -0
  141. agentex/lib/core/temporal/activities/adk/state_activities.py +85 -0
  142. agentex/lib/core/temporal/activities/adk/streaming_activities.py +33 -0
  143. agentex/lib/core/temporal/activities/adk/tasks_activities.py +48 -0
  144. agentex/lib/core/temporal/activities/adk/tracing_activities.py +55 -0
  145. agentex/lib/core/temporal/activities/adk/utils/__init__.py +0 -0
  146. agentex/lib/core/temporal/activities/adk/utils/templating_activities.py +41 -0
  147. agentex/lib/core/temporal/services/__init__.py +0 -0
  148. agentex/lib/core/temporal/services/temporal_task_service.py +69 -0
  149. agentex/lib/core/temporal/types/__init__.py +0 -0
  150. agentex/lib/core/temporal/types/workflow.py +5 -0
  151. agentex/lib/core/temporal/workers/__init__.py +0 -0
  152. agentex/lib/core/temporal/workers/worker.py +162 -0
  153. agentex/lib/core/temporal/workflows/workflow.py +26 -0
  154. agentex/lib/core/tracing/__init__.py +5 -0
  155. agentex/lib/core/tracing/processors/agentex_tracing_processor.py +117 -0
  156. agentex/lib/core/tracing/processors/sgp_tracing_processor.py +119 -0
  157. agentex/lib/core/tracing/processors/tracing_processor_interface.py +40 -0
  158. agentex/lib/core/tracing/trace.py +311 -0
  159. agentex/lib/core/tracing/tracer.py +70 -0
  160. agentex/lib/core/tracing/tracing_processor_manager.py +62 -0
  161. agentex/lib/environment_variables.py +87 -0
  162. agentex/lib/py.typed +0 -0
  163. agentex/lib/sdk/__init__.py +0 -0
  164. agentex/lib/sdk/config/__init__.py +0 -0
  165. agentex/lib/sdk/config/agent_config.py +61 -0
  166. agentex/lib/sdk/config/agent_manifest.py +219 -0
  167. agentex/lib/sdk/config/build_config.py +35 -0
  168. agentex/lib/sdk/config/deployment_config.py +117 -0
  169. agentex/lib/sdk/config/local_development_config.py +56 -0
  170. agentex/lib/sdk/config/project_config.py +103 -0
  171. agentex/lib/sdk/fastacp/__init__.py +3 -0
  172. agentex/lib/sdk/fastacp/base/base_acp_server.py +406 -0
  173. agentex/lib/sdk/fastacp/fastacp.py +74 -0
  174. agentex/lib/sdk/fastacp/impl/agentic_base_acp.py +72 -0
  175. agentex/lib/sdk/fastacp/impl/sync_acp.py +109 -0
  176. agentex/lib/sdk/fastacp/impl/temporal_acp.py +97 -0
  177. agentex/lib/sdk/fastacp/tests/README.md +297 -0
  178. agentex/lib/sdk/fastacp/tests/conftest.py +307 -0
  179. agentex/lib/sdk/fastacp/tests/pytest.ini +10 -0
  180. agentex/lib/sdk/fastacp/tests/run_tests.py +227 -0
  181. agentex/lib/sdk/fastacp/tests/test_base_acp_server.py +450 -0
  182. agentex/lib/sdk/fastacp/tests/test_fastacp_factory.py +344 -0
  183. agentex/lib/sdk/fastacp/tests/test_integration.py +477 -0
  184. agentex/lib/sdk/state_machine/__init__.py +6 -0
  185. agentex/lib/sdk/state_machine/noop_workflow.py +21 -0
  186. agentex/lib/sdk/state_machine/state.py +10 -0
  187. agentex/lib/sdk/state_machine/state_machine.py +189 -0
  188. agentex/lib/sdk/state_machine/state_workflow.py +16 -0
  189. agentex/lib/sdk/utils/__init__.py +0 -0
  190. agentex/lib/sdk/utils/messages.py +223 -0
  191. agentex/lib/types/__init__.py +0 -0
  192. agentex/lib/types/acp.py +94 -0
  193. agentex/lib/types/agent_configs.py +79 -0
  194. agentex/lib/types/agent_results.py +29 -0
  195. agentex/lib/types/credentials.py +34 -0
  196. agentex/lib/types/fastacp.py +61 -0
  197. agentex/lib/types/files.py +13 -0
  198. agentex/lib/types/json_rpc.py +49 -0
  199. agentex/lib/types/llm_messages.py +354 -0
  200. agentex/lib/types/task_message_updates.py +171 -0
  201. agentex/lib/types/tracing.py +34 -0
  202. agentex/lib/utils/__init__.py +0 -0
  203. agentex/lib/utils/completions.py +131 -0
  204. agentex/lib/utils/console.py +14 -0
  205. agentex/lib/utils/io.py +29 -0
  206. agentex/lib/utils/iterables.py +14 -0
  207. agentex/lib/utils/json_schema.py +23 -0
  208. agentex/lib/utils/logging.py +31 -0
  209. agentex/lib/utils/mcp.py +17 -0
  210. agentex/lib/utils/model_utils.py +46 -0
  211. agentex/lib/utils/parsing.py +15 -0
  212. agentex/lib/utils/regex.py +6 -0
  213. agentex/lib/utils/temporal.py +13 -0
  214. agentex/py.typed +0 -0
  215. agentex/resources/__init__.py +103 -0
  216. agentex/resources/agents.py +707 -0
  217. agentex/resources/events.py +294 -0
  218. agentex/resources/messages/__init__.py +33 -0
  219. agentex/resources/messages/batch.py +271 -0
  220. agentex/resources/messages/messages.py +492 -0
  221. agentex/resources/spans.py +557 -0
  222. agentex/resources/states.py +544 -0
  223. agentex/resources/tasks.py +615 -0
  224. agentex/resources/tracker.py +384 -0
  225. agentex/types/__init__.py +56 -0
  226. agentex/types/acp_type.py +7 -0
  227. agentex/types/agent.py +29 -0
  228. agentex/types/agent_list_params.py +13 -0
  229. agentex/types/agent_list_response.py +10 -0
  230. agentex/types/agent_rpc_by_name_params.py +21 -0
  231. agentex/types/agent_rpc_params.py +51 -0
  232. agentex/types/agent_rpc_params1.py +21 -0
  233. agentex/types/agent_rpc_response.py +20 -0
  234. agentex/types/agent_rpc_result.py +90 -0
  235. agentex/types/agent_task_tracker.py +34 -0
  236. agentex/types/data_content.py +30 -0
  237. agentex/types/data_content_param.py +31 -0
  238. agentex/types/data_delta.py +14 -0
  239. agentex/types/event.py +29 -0
  240. agentex/types/event_list_params.py +22 -0
  241. agentex/types/event_list_response.py +10 -0
  242. agentex/types/message_author.py +7 -0
  243. agentex/types/message_create_params.py +18 -0
  244. agentex/types/message_list_params.py +14 -0
  245. agentex/types/message_list_response.py +10 -0
  246. agentex/types/message_style.py +7 -0
  247. agentex/types/message_update_params.py +18 -0
  248. agentex/types/messages/__init__.py +8 -0
  249. agentex/types/messages/batch_create_params.py +16 -0
  250. agentex/types/messages/batch_create_response.py +10 -0
  251. agentex/types/messages/batch_update_params.py +16 -0
  252. agentex/types/messages/batch_update_response.py +10 -0
  253. agentex/types/shared/__init__.py +3 -0
  254. agentex/types/shared/task_message_update.py +83 -0
  255. agentex/types/span.py +36 -0
  256. agentex/types/span_create_params.py +40 -0
  257. agentex/types/span_list_params.py +12 -0
  258. agentex/types/span_list_response.py +10 -0
  259. agentex/types/span_update_params.py +37 -0
  260. agentex/types/state.py +25 -0
  261. agentex/types/state_create_params.py +16 -0
  262. agentex/types/state_list_params.py +16 -0
  263. agentex/types/state_list_response.py +10 -0
  264. agentex/types/state_update_params.py +16 -0
  265. agentex/types/task.py +23 -0
  266. agentex/types/task_delete_by_name_response.py +8 -0
  267. agentex/types/task_delete_response.py +8 -0
  268. agentex/types/task_list_response.py +10 -0
  269. agentex/types/task_message.py +33 -0
  270. agentex/types/task_message_content.py +16 -0
  271. agentex/types/task_message_content_param.py +17 -0
  272. agentex/types/task_message_delta.py +16 -0
  273. agentex/types/text_content.py +53 -0
  274. agentex/types/text_content_param.py +54 -0
  275. agentex/types/text_delta.py +14 -0
  276. agentex/types/tool_request_content.py +36 -0
  277. agentex/types/tool_request_content_param.py +37 -0
  278. agentex/types/tool_request_delta.py +18 -0
  279. agentex/types/tool_response_content.py +36 -0
  280. agentex/types/tool_response_content_param.py +36 -0
  281. agentex/types/tool_response_delta.py +18 -0
  282. agentex/types/tracker_list_params.py +16 -0
  283. agentex/types/tracker_list_response.py +10 -0
  284. agentex/types/tracker_update_params.py +19 -0
  285. agentex_sdk-0.1.0a6.dist-info/METADATA +426 -0
  286. agentex_sdk-0.1.0a6.dist-info/RECORD +289 -0
  287. agentex_sdk-0.1.0a6.dist-info/WHEEL +4 -0
  288. agentex_sdk-0.1.0a6.dist-info/entry_points.txt +2 -0
  289. agentex_sdk-0.1.0a6.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,99 @@
1
+ import base64
2
+ import os
3
+ import tempfile
4
+
5
+ from scale_gp import SGPClient
6
+
7
+ from agentex.lib.core.tracing.tracer import AsyncTracer
8
+ from agentex.lib.types.files import FileContentResponse
9
+ from agentex.lib.utils.logging import make_logger
10
+ from agentex.lib.utils.temporal import heartbeat_if_in_workflow
11
+
12
+ logger = make_logger(__name__)
13
+
14
+
15
+ class SGPService:
16
+ def __init__(self, sgp_client: SGPClient, tracer: AsyncTracer):
17
+ self.sgp_client = sgp_client
18
+ self.tracer = tracer
19
+
20
+ async def download_file_content(
21
+ self,
22
+ file_id: str,
23
+ filename: str,
24
+ trace_id: str | None = None,
25
+ parent_span_id: str | None = None,
26
+ ) -> FileContentResponse:
27
+ """
28
+ Download file content from SGP.
29
+
30
+ Args:
31
+ file_id: The ID of the file to download.
32
+ filename: The filename of the file to download.
33
+ trace_id: The trace ID for tracing.
34
+ parent_span_id: The parent span ID for tracing.
35
+
36
+ Returns:
37
+ FileContentResponse with mime_type and base64_content for constructing LLM input.
38
+ """
39
+ trace = self.tracer.trace(trace_id)
40
+ async with trace.span(
41
+ parent_id=parent_span_id,
42
+ name="download_file_content",
43
+ input={"file_id": file_id, "filename": filename},
44
+ ) as span:
45
+ logger.info(f"Downloading file content for file_id: {file_id}")
46
+ heartbeat_if_in_workflow("downloading file content")
47
+
48
+ # Get the SGP response
49
+ response = self.sgp_client.beta.files.content(file_id)
50
+ heartbeat_if_in_workflow("file content downloaded")
51
+
52
+ # Determine mime type based on file extension
53
+ mime_type = "application/pdf" # Default
54
+ file_extension = os.path.splitext(filename)[1].lower()
55
+ if file_extension:
56
+ if file_extension == ".pdf":
57
+ mime_type = "application/pdf"
58
+ elif file_extension in [".doc", ".docx"]:
59
+ mime_type = "application/msword"
60
+ elif file_extension in [".txt", ".text"]:
61
+ mime_type = "text/plain"
62
+ elif file_extension in [".png"]:
63
+ mime_type = "image/png"
64
+ elif file_extension in [".jpg", ".jpeg"]:
65
+ mime_type = "image/jpeg"
66
+
67
+ # Use a named temporary file - simpler approach
68
+ with tempfile.NamedTemporaryFile(suffix=file_extension) as temp_file:
69
+ heartbeat_if_in_workflow(f"saving to temp file: {temp_file.name}")
70
+
71
+ # Use write_to_file method if available
72
+ if hasattr(response, "write_to_file"):
73
+ response.write_to_file(temp_file.name)
74
+ else:
75
+ # Fallback to direct writing
76
+ content_bytes = response.read()
77
+ temp_file.write(content_bytes)
78
+ temp_file.flush()
79
+
80
+ # Seek to beginning of file for reading
81
+ temp_file.seek(0)
82
+
83
+ # Read the file in binary mode - exactly like the example
84
+ data = temp_file.read()
85
+
86
+ # Encode to base64
87
+ base64_content = base64.b64encode(data).decode("utf-8")
88
+
89
+ result = FileContentResponse(
90
+ mime_type=mime_type, base64_content=base64_content
91
+ )
92
+
93
+ # Record metadata for tracing
94
+ span.output = {
95
+ "file_id": file_id,
96
+ "mime_type": result.mime_type,
97
+ "content_size": len(result.base64_content),
98
+ }
99
+ return result
@@ -0,0 +1,120 @@
1
+ from typing import Any, Dict
2
+
3
+ from agentex import AsyncAgentex
4
+ from agentex.lib.core.tracing.tracer import AsyncTracer
5
+ from agentex.types.state import State
6
+ from agentex.lib.utils.logging import make_logger
7
+
8
+ logger = make_logger(__name__)
9
+
10
+
11
+ class StateService:
12
+ def __init__(
13
+ self, agentex_client: AsyncAgentex, tracer: AsyncTracer
14
+ ):
15
+ self._agentex_client = agentex_client
16
+ self._tracer = tracer
17
+
18
+ async def create_state(
19
+ self,
20
+ task_id: str,
21
+ agent_id: str,
22
+ state: dict[str, Any],
23
+ trace_id: str | None = None,
24
+ parent_span_id: str | None = None,
25
+ ) -> State:
26
+ trace = self._tracer.trace(trace_id)
27
+ async with trace.span(
28
+ parent_id=parent_span_id,
29
+ name="create_state",
30
+ input={"task_id": task_id, "agent_id": agent_id, "state": state},
31
+ ) as span:
32
+ state_model = await self._agentex_client.states.create(
33
+ task_id=task_id,
34
+ agent_id=agent_id,
35
+ state=state,
36
+ )
37
+ if span:
38
+ span.output = state_model.model_dump()
39
+ return state_model
40
+
41
+ async def get_state(
42
+ self,
43
+ state_id: str | None = None,
44
+ task_id: str | None = None,
45
+ agent_id: str | None = None,
46
+ trace_id: str | None = None,
47
+ parent_span_id: str | None = None,
48
+ ) -> State | None:
49
+ trace = self._tracer.trace(trace_id) if self._tracer else None
50
+ async with trace.span(
51
+ parent_id=parent_span_id,
52
+ name="get_state",
53
+ input={
54
+ "state_id": state_id,
55
+ "task_id": task_id,
56
+ "agent_id": agent_id,
57
+ },
58
+ ) as span:
59
+ if state_id:
60
+ state = await self._agentex_client.states.retrieve(state_id=state_id)
61
+ elif task_id and agent_id:
62
+ states = await self._agentex_client.states.list(
63
+ task_id=task_id,
64
+ agent_id=agent_id,
65
+ )
66
+ state = states[0] if states else None
67
+ else:
68
+ raise ValueError(
69
+ "Must provide either state_id or both task_id and agent_id"
70
+ )
71
+ if span:
72
+ span.output = state.model_dump() if state else None
73
+ return state
74
+
75
+ async def update_state(
76
+ self,
77
+ state_id: str,
78
+ task_id: str,
79
+ agent_id: str,
80
+ state: Dict[str, object],
81
+ trace_id: str | None = None,
82
+ parent_span_id: str | None = None,
83
+ ) -> State:
84
+ trace = self._tracer.trace(trace_id)
85
+ async with trace.span(
86
+ parent_id=parent_span_id,
87
+ name="update_state",
88
+ input={
89
+ "state_id": state_id,
90
+ "task_id": task_id,
91
+ "agent_id": agent_id,
92
+ "state": state,
93
+ },
94
+ ) as span:
95
+ state_model = await self._agentex_client.states.update(
96
+ state_id=state_id,
97
+ task_id=task_id,
98
+ agent_id=agent_id,
99
+ state=state,
100
+ )
101
+ if span:
102
+ span.output = state_model.model_dump()
103
+ return state_model
104
+
105
+ async def delete_state(
106
+ self,
107
+ state_id: str,
108
+ trace_id: str | None = None,
109
+ parent_span_id: str | None = None,
110
+ ) -> State:
111
+ trace = self._tracer.trace(trace_id)
112
+ async with trace.span(
113
+ parent_id=parent_span_id,
114
+ name="delete_state",
115
+ input={"state_id": state_id},
116
+ ) as span:
117
+ state = await self._agentex_client.states.delete(state_id)
118
+ if span:
119
+ span.output = state.model_dump()
120
+ return state
@@ -0,0 +1,262 @@
1
+ import json
2
+ from typing import Literal, cast
3
+
4
+ from agentex import AsyncAgentex
5
+ from agentex.lib.core.adapters.streams.port import EventStreamRepository
6
+ from agentex.lib.types.task_message_updates import (
7
+ TaskMessageDelta,
8
+ TaskMessageUpdate,
9
+ TextDelta,
10
+ DataDelta,
11
+ ToolRequestDelta,
12
+ ToolResponseDelta,
13
+ StreamTaskMessage,
14
+ StreamTaskMessageStart,
15
+ StreamTaskMessageDelta,
16
+ StreamTaskMessageFull,
17
+ StreamTaskMessageDone,
18
+ )
19
+ from agentex.lib.utils.logging import make_logger
20
+ from agentex.types.data_content import DataContent
21
+ from agentex.types.task_message import (
22
+ TaskMessage,
23
+ TaskMessageContent,
24
+ )
25
+ from agentex.types.task_message_content_param import TaskMessageContentParam
26
+ from agentex.types.text_content import TextContent
27
+ from agentex.types.tool_request_content import ToolRequestContent
28
+ from agentex.types.tool_response_content import ToolResponseContent
29
+
30
+ logger = make_logger(__name__)
31
+
32
+
33
+ def _get_stream_topic(task_id: str) -> str:
34
+ return f"task:{task_id}"
35
+
36
+
37
+ class DeltaAccumulator:
38
+ def __init__(self):
39
+ self._accumulated_deltas: list[TaskMessageDelta] = []
40
+ self._delta_type: Literal["text", "data", "tool_request", "tool_response"] | None = None
41
+
42
+ def add_delta(self, delta: TaskMessageDelta):
43
+ if self._delta_type is None:
44
+ if delta.type == "text":
45
+ self._delta_type = "text"
46
+ elif delta.type == "data":
47
+ self._delta_type = "data"
48
+ elif delta.type == "tool_request":
49
+ self._delta_type = "tool_request"
50
+ elif delta.type == "tool_response":
51
+ self._delta_type = "tool_response"
52
+ else:
53
+ raise ValueError(f"Unknown delta type: {delta.type}")
54
+ else:
55
+ if self._delta_type != delta.type:
56
+ raise ValueError(
57
+ f"Delta type mismatch: {self._delta_type} != {delta.type}"
58
+ )
59
+
60
+ self._accumulated_deltas.append(delta)
61
+
62
+ def convert_to_content(self) -> TaskMessageContent:
63
+ if self._delta_type == "text":
64
+ # Type assertion: we know all deltas are TextDelta when _delta_type is TEXT
65
+ text_deltas = [delta for delta in self._accumulated_deltas if isinstance(delta, TextDelta)]
66
+ text_content_str = "".join(
67
+ [delta.text_delta or "" for delta in text_deltas]
68
+ )
69
+ return TextContent(
70
+ author="agent",
71
+ content=text_content_str,
72
+ )
73
+ elif self._delta_type == "data":
74
+ # Type assertion: we know all deltas are DataDelta when _delta_type is DATA
75
+ data_deltas = [delta for delta in self._accumulated_deltas if isinstance(delta, DataDelta)]
76
+ data_content_str = "".join(
77
+ [delta.data_delta or "" for delta in data_deltas]
78
+ )
79
+ try:
80
+ data = json.loads(data_content_str)
81
+ except json.JSONDecodeError as e:
82
+ raise ValueError(
83
+ f"Accumulated data content is not valid JSON: {data_content_str}"
84
+ ) from e
85
+ return DataContent(
86
+ author="agent",
87
+ data=data,
88
+ )
89
+ elif self._delta_type == "tool_request":
90
+ # Type assertion: we know all deltas are ToolRequestDelta when _delta_type is TOOL_REQUEST
91
+ tool_request_deltas = [delta for delta in self._accumulated_deltas if isinstance(delta, ToolRequestDelta)]
92
+ arguments_content_str = "".join(
93
+ [delta.arguments_delta or "" for delta in tool_request_deltas]
94
+ )
95
+ try:
96
+ arguments = json.loads(arguments_content_str)
97
+ except json.JSONDecodeError as e:
98
+ raise ValueError(
99
+ f"Accumulated tool request arguments is not valid JSON: {arguments_content_str}"
100
+ ) from e
101
+ return ToolRequestContent(
102
+ author="agent",
103
+ tool_call_id=tool_request_deltas[0].tool_call_id,
104
+ name=tool_request_deltas[0].name,
105
+ arguments=arguments,
106
+ )
107
+ elif self._delta_type == "tool_response":
108
+ # Type assertion: we know all deltas are ToolResponseDelta when _delta_type is TOOL_RESPONSE
109
+ tool_response_deltas = [delta for delta in self._accumulated_deltas if isinstance(delta, ToolResponseDelta)]
110
+ tool_response_content_str = "".join(
111
+ [delta.tool_response_delta or "" for delta in tool_response_deltas]
112
+ )
113
+ return ToolResponseContent(
114
+ author="agent",
115
+ tool_call_id=tool_response_deltas[0].tool_call_id,
116
+ name=tool_response_deltas[0].name,
117
+ content=tool_response_content_str,
118
+ )
119
+ else:
120
+ raise ValueError(f"Unknown delta type: {self._delta_type}")
121
+
122
+
123
+ class StreamingTaskMessageContext:
124
+ def __init__(
125
+ self,
126
+ task_id: str,
127
+ initial_content: TaskMessageContent,
128
+ agentex_client: AsyncAgentex,
129
+ streaming_service: "StreamingService",
130
+ ):
131
+ self.task_id = task_id
132
+ self.initial_content = initial_content
133
+ self.task_message: TaskMessage | None = None
134
+ self._agentex_client = agentex_client
135
+ self._streaming_service = streaming_service
136
+ self._is_closed = False
137
+ self._delta_accumulator = DeltaAccumulator()
138
+
139
+ async def __aenter__(self) -> "StreamingTaskMessageContext":
140
+ return await self.open()
141
+
142
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
143
+ return await self.close()
144
+
145
+ async def open(self) -> "StreamingTaskMessageContext":
146
+ self._is_closed = False
147
+
148
+ self.task_message = await self._agentex_client.messages.create(
149
+ task_id=self.task_id,
150
+ content=self.initial_content.model_dump(),
151
+ streaming_status="IN_PROGRESS",
152
+ )
153
+
154
+ # Send the START event
155
+ start_event = StreamTaskMessageStart(
156
+ parent_task_message=self.task_message,
157
+ content=self.initial_content,
158
+ )
159
+ await self._streaming_service.stream_update(start_event)
160
+
161
+ return self
162
+
163
+ async def close(self) -> TaskMessage:
164
+ """Close the streaming context."""
165
+ if not self.task_message:
166
+ raise ValueError("Context not properly initialized - no task message")
167
+
168
+ if self._is_closed:
169
+ return self.task_message # Already done
170
+
171
+ # Send the DONE event
172
+ done_event = StreamTaskMessageDone(parent_task_message=self.task_message)
173
+ await self._streaming_service.stream_update(done_event)
174
+
175
+ # Update the task message with the final content
176
+ if self._delta_accumulator._accumulated_deltas:
177
+ self.task_message.content = self._delta_accumulator.convert_to_content()
178
+
179
+ await self._agentex_client.messages.update(
180
+ task_id=self.task_id,
181
+ message_id=self.task_message.id,
182
+ content=self.task_message.content.model_dump(),
183
+ streaming_status="DONE",
184
+ )
185
+
186
+ # Mark the context as done
187
+ self._is_closed = True
188
+ return self.task_message
189
+
190
+ async def stream_update(
191
+ self, update: StreamTaskMessage
192
+ ) -> StreamTaskMessage | None:
193
+ """Stream an update to the repository."""
194
+ if self._is_closed:
195
+ raise ValueError("Context is already done")
196
+
197
+ if not self.task_message:
198
+ raise ValueError("Context not properly initialized - no task message")
199
+
200
+ if isinstance(update, StreamTaskMessageDelta):
201
+ if update.delta is not None:
202
+ self._delta_accumulator.add_delta(update.delta)
203
+
204
+ result = await self._streaming_service.stream_update(update)
205
+
206
+ if isinstance(update, StreamTaskMessageDone):
207
+ await self.close()
208
+ return update
209
+ elif isinstance(update, StreamTaskMessageFull):
210
+ await self._agentex_client.messages.update(
211
+ task_id=self.task_id,
212
+ message_id=update.parent_task_message.id,
213
+ content=update.content.model_dump(),
214
+ streaming_status="DONE",
215
+ )
216
+ self._is_closed = True
217
+ return result
218
+
219
+
220
+ class StreamingService:
221
+ def __init__(
222
+ self,
223
+ agentex_client: AsyncAgentex,
224
+ stream_repository: EventStreamRepository,
225
+ ):
226
+ self._agentex_client = agentex_client
227
+ self._stream_repository = stream_repository
228
+
229
+ def streaming_task_message_context(
230
+ self,
231
+ task_id: str,
232
+ initial_content: TaskMessageContent,
233
+ ) -> StreamingTaskMessageContext:
234
+ return StreamingTaskMessageContext(
235
+ task_id=task_id,
236
+ initial_content=initial_content,
237
+ agentex_client=self._agentex_client,
238
+ streaming_service=self,
239
+ )
240
+
241
+ async def stream_update(
242
+ self, update: TaskMessageUpdate
243
+ ) -> TaskMessageUpdate | None:
244
+ """
245
+ Stream an update to the repository.
246
+
247
+ Args:
248
+ update: The update to stream
249
+
250
+ Returns:
251
+ True if event was streamed successfully, False otherwise
252
+ """
253
+ stream_topic = _get_stream_topic(update.parent_task_message.task_id)
254
+
255
+ try:
256
+ await self._stream_repository.send_event(
257
+ topic=stream_topic, event=update.model_dump(mode="json") # type: ignore
258
+ )
259
+ return update
260
+ except Exception as e:
261
+ logger.exception(f"Failed to stream event: {e}")
262
+ return None
@@ -0,0 +1,69 @@
1
+ from agentex import AsyncAgentex
2
+ from agentex.lib.core.tracing.tracer import AsyncTracer
3
+ from agentex.types.task import Task
4
+ from agentex.lib.utils.logging import make_logger
5
+ from agentex.lib.utils.temporal import heartbeat_if_in_workflow
6
+
7
+ logger = make_logger(__name__)
8
+
9
+
10
+ class TasksService:
11
+ def __init__(
12
+ self,
13
+ agentex_client: AsyncAgentex,
14
+ tracer: AsyncTracer,
15
+ ):
16
+ self._agentex_client = agentex_client
17
+ self._tracer = tracer
18
+
19
+ async def get_task(
20
+ self,
21
+ task_id: str | None = None,
22
+ task_name: str | None = None,
23
+ trace_id: str | None = None,
24
+ parent_span_id: str | None = None,
25
+ ) -> Task:
26
+ trace = self._tracer.trace(trace_id)
27
+ async with trace.span(
28
+ parent_id=parent_span_id,
29
+ name="get_task",
30
+ input={"task_id": task_id, "task_name": task_name},
31
+ ) as span:
32
+ heartbeat_if_in_workflow("get task")
33
+ if not task_id and not task_name:
34
+ raise ValueError("Either task_id or task_name must be provided.")
35
+ if task_id:
36
+ task_model = await self._agentex_client.tasks.retrieve(task_id=task_id)
37
+ elif task_name:
38
+ task_model = await self._agentex_client.tasks.retrieve_by_name(task_name=task_name)
39
+ else:
40
+ raise ValueError("Either task_id or task_name must be provided.")
41
+ if span:
42
+ span.output = task_model.model_dump()
43
+ return task_model
44
+
45
+ async def delete_task(
46
+ self,
47
+ task_id: str | None = None,
48
+ task_name: str | None = None,
49
+ trace_id: str | None = None,
50
+ parent_span_id: str | None = None,
51
+ ) -> Task:
52
+ trace = self._tracer.trace(trace_id) if self._tracer else None
53
+ async with trace.span(
54
+ parent_id=parent_span_id,
55
+ name="delete_task",
56
+ input={"task_id": task_id, "task_name": task_name},
57
+ ) as span:
58
+ heartbeat_if_in_workflow("delete task")
59
+ if not task_id and not task_name:
60
+ raise ValueError("Either task_id or task_name must be provided.")
61
+ if task_id:
62
+ task_model = await self._agentex_client.tasks.delete(task_id=task_id)
63
+ elif task_name:
64
+ task_model = await self._agentex_client.tasks.delete_by_name(task_name=task_name)
65
+ else:
66
+ raise ValueError("Either task_id or task_name must be provided.")
67
+ if span:
68
+ span.output = task_model.model_dump()
69
+ return task_model
@@ -0,0 +1,36 @@
1
+ from typing import Any
2
+ from agentex.lib.core.tracing.tracer import AsyncTracer
3
+ from agentex.types.span import Span
4
+ from agentex.lib.utils.logging import make_logger
5
+ from agentex.lib.utils.model_utils import BaseModel
6
+ from agentex.lib.utils.temporal import heartbeat_if_in_workflow
7
+
8
+ logger = make_logger(__name__)
9
+
10
+
11
+ class TracingService:
12
+ def __init__(self, tracer: AsyncTracer):
13
+ self._tracer = tracer
14
+
15
+ async def start_span(
16
+ self,
17
+ trace_id: str,
18
+ name: str,
19
+ parent_id: str | None = None,
20
+ input: list[Any] | dict[str, Any] | BaseModel | None = None,
21
+ data: list[Any] | dict[str, Any] | BaseModel | None = None,
22
+ ) -> Span | None:
23
+ trace = self._tracer.trace(trace_id)
24
+ async with trace.span(
25
+ parent_id=parent_id,
26
+ name=name,
27
+ input=input or {},
28
+ data=data,
29
+ ) as span:
30
+ heartbeat_if_in_workflow("start span")
31
+ return span if span else None
32
+
33
+ async def end_span(self, trace_id: str, span: Span) -> Span:
34
+ trace = self._tracer.trace(trace_id)
35
+ await trace.end_span(span)
36
+ return span
File without changes
@@ -0,0 +1,58 @@
1
+ from datetime import datetime
2
+ from typing import Any
3
+
4
+ from jinja2 import BaseLoader, Environment
5
+
6
+ from agentex.lib.core.tracing.tracer import AsyncTracer
7
+ from agentex.lib.utils.temporal import heartbeat_if_in_workflow
8
+
9
+ # Create a Jinja environment
10
+ JINJA_ENV = Environment(
11
+ loader=BaseLoader(),
12
+ trim_blocks=True,
13
+ lstrip_blocks=True,
14
+ extensions=["jinja2.ext.do"],
15
+ )
16
+
17
+
18
+ class TemplatingService:
19
+ def __init__(self, tracer: AsyncTracer | None = None):
20
+ self.tracer = tracer
21
+
22
+ async def render_jinja(
23
+ self,
24
+ template: str,
25
+ variables: dict[str, Any],
26
+ trace_id: str | None = None,
27
+ parent_span_id: str | None = None,
28
+ ) -> str:
29
+ """
30
+ Activity that renders a Jinja template with the provided data.
31
+
32
+ Args:
33
+ template: The template string to render.
34
+ variables: The variables to render the template with.
35
+ trace_id: The trace ID for tracing.
36
+ parent_span_id: The parent span ID for tracing.
37
+
38
+ Returns:
39
+ The rendered template as a string
40
+ """
41
+ trace = self.tracer.trace(trace_id)
42
+ async with trace.span(
43
+ parent_id=parent_span_id,
44
+ name="render_jinja",
45
+ input={"template": template, "variables": variables},
46
+ ) as span:
47
+ heartbeat_if_in_workflow("render jinja")
48
+ global_variables = {
49
+ "datetime": datetime,
50
+ }
51
+ jinja_template = JINJA_ENV.from_string(template, globals=global_variables)
52
+ try:
53
+ rendered_template = jinja_template.render(variables)
54
+ if span:
55
+ span.output = {"jinja_output": rendered_template}
56
+ return rendered_template
57
+ except Exception as e:
58
+ raise ValueError(f"Error rendering Jinja template: {str(e)}") from e
File without changes