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,34 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "{{ project_name }}"
7
+ version = "0.1.0"
8
+ description = "{{ description }}"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ dependencies = [
12
+ "agentex-sdk",
13
+ "scale-gp",
14
+ "temporalio",
15
+ ]
16
+
17
+ [project.optional-dependencies]
18
+ dev = [
19
+ "pytest",
20
+ "black",
21
+ "isort",
22
+ "flake8",
23
+ ]
24
+
25
+ [tool.hatch.build.targets.wheel]
26
+ packages = ["project"]
27
+
28
+ [tool.black]
29
+ line-length = 88
30
+ target-version = ['py312']
31
+
32
+ [tool.isort]
33
+ profile = "black"
34
+ line_length = 88
@@ -0,0 +1,5 @@
1
+ # Install agentex-sdk from local path
2
+ agentex-sdk
3
+
4
+ # Scale GenAI Platform Python SDK
5
+ scale-gp
@@ -0,0 +1,14 @@
1
+ import typer
2
+ from rich.console import Console
3
+
4
+ console = Console()
5
+
6
+
7
+ def handle_questionary_cancellation(
8
+ result: str | None, operation: str = "operation"
9
+ ) -> str:
10
+ """Handle questionary cancellation by checking for None and exiting gracefully"""
11
+ if result is None:
12
+ console.print(f"[yellow]{operation.capitalize()} cancelled by user[/yellow]")
13
+ raise typer.Exit(0)
14
+ return result
@@ -0,0 +1,103 @@
1
+ import subprocess
2
+
3
+ from rich.console import Console
4
+ from rich.prompt import Confirm, Prompt
5
+
6
+ from agentex.lib.types.credentials import CredentialMapping
7
+
8
+ console = Console()
9
+
10
+
11
+ def check_secret_exists(secret_name: str, namespace: str) -> bool:
12
+ """Check if a Kubernetes secret exists in the given namespace."""
13
+ try:
14
+ result = subprocess.run(
15
+ ["kubectl", "get", "secret", secret_name, "-n", namespace],
16
+ capture_output=True,
17
+ text=True,
18
+ check=False,
19
+ )
20
+ return result.returncode == 0
21
+ except Exception:
22
+ return False
23
+
24
+
25
+ def create_env_var_secret(credential: CredentialMapping, namespace: str) -> bool:
26
+ """Create a generic secret for environment variable credentials."""
27
+ console.print(
28
+ f"[yellow]Secret '{credential.secret_name}' not found in namespace '{namespace}'[/yellow]"
29
+ )
30
+
31
+ if not Confirm.ask(
32
+ f"Would you like to create the secret '{credential.secret_name}'?"
33
+ ):
34
+ return False
35
+
36
+ # Prompt for the secret value
37
+ secret_value = Prompt.ask(
38
+ f"Enter the value for '{credential.secret_key}'", password=True
39
+ )
40
+
41
+ try:
42
+ # Create the secret using kubectl
43
+ subprocess.run(
44
+ [
45
+ "kubectl",
46
+ "create",
47
+ "secret",
48
+ "generic",
49
+ credential.secret_name,
50
+ f"--from-literal={credential.secret_key}={secret_value}",
51
+ "-n",
52
+ namespace,
53
+ ],
54
+ capture_output=True,
55
+ text=True,
56
+ check=True,
57
+ )
58
+
59
+ console.print(
60
+ f"[green]✓ Created secret '{credential.secret_name}' in namespace '{namespace}'[/green]"
61
+ )
62
+ return True
63
+
64
+ except subprocess.CalledProcessError as e:
65
+ console.print(f"[red]✗ Failed to create secret: {e.stderr}[/red]")
66
+ return False
67
+
68
+
69
+ # def create_image_pull_secret(credential: ImagePullCredential, namespace: str) -> bool:
70
+ # """Create an image pull secret with interactive prompts."""
71
+ # console.print(f"[yellow]Image pull secret '{credential.secret_name}' not found in namespace '{namespace}'[/yellow]")
72
+
73
+ # if not Confirm.ask(f"Would you like to create the image pull secret '{credential.secret_name}'?"):
74
+ # return False
75
+
76
+ # # Prompt for registry details
77
+ # registry_server = Prompt.ask("Docker registry server (e.g., docker.io, gcr.io)")
78
+ # username = Prompt.ask("Username")
79
+ # password = Prompt.ask("Password", password=True)
80
+ # email = Prompt.ask("Email (optional)", default="")
81
+
82
+ # try:
83
+ # # Create the image pull secret using kubectl
84
+ # cmd = [
85
+ # "kubectl", "create", "secret", "docker-registry",
86
+ # credential.secret_name,
87
+ # f"--docker-server={registry_server}",
88
+ # f"--docker-username={username}",
89
+ # f"--docker-password={password}",
90
+ # "-n", namespace
91
+ # ]
92
+
93
+ # if email:
94
+ # cmd.append(f"--docker-email={email}")
95
+
96
+ # result = subprocess.run(cmd, capture_output=True, text=True, check=True)
97
+
98
+ # console.print(f"[green]✓ Created image pull secret '{credential.secret_name}' in namespace '{namespace}'[/green]")
99
+ # return True
100
+
101
+ # except subprocess.CalledProcessError as e:
102
+ # console.print(f"[red]✗ Failed to create image pull secret: {e.stderr}[/red]")
103
+ # return False
@@ -0,0 +1,6 @@
1
+ class HelmError(Exception):
2
+ """An error occurred during helm operations"""
3
+
4
+
5
+ class DeploymentError(Exception):
6
+ """An error occurred during deployment"""
@@ -0,0 +1,135 @@
1
+ import subprocess
2
+
3
+ from kubernetes import client, config
4
+ from kubernetes.client.rest import ApiException
5
+ from rich.console import Console
6
+
7
+ from agentex.lib.cli.utils.exceptions import DeploymentError
8
+ from agentex.lib.utils.logging import make_logger
9
+
10
+ logger = make_logger(__name__)
11
+ console = Console()
12
+
13
+
14
+ class KubernetesClientManager:
15
+ """Manages Kubernetes clients for different contexts"""
16
+
17
+ def __init__(self):
18
+ self._clients: dict[str, client.CoreV1Api] = {}
19
+
20
+ def get_client(self, context: str | None = None) -> client.CoreV1Api:
21
+ """Get a Kubernetes client for the specified context"""
22
+ if context is None:
23
+ context = get_current_context()
24
+
25
+ if context not in self._clients:
26
+ try:
27
+ # Load config for specific context
28
+ config.load_kube_config(context=context)
29
+ self._clients[context] = client.CoreV1Api()
30
+ logger.info(f"Created Kubernetes client for context: {context}")
31
+ except Exception as e:
32
+ raise DeploymentError(
33
+ f"Failed to create Kubernetes client for context '{context}': {e}"
34
+ ) from e
35
+
36
+ return self._clients[context]
37
+
38
+ def clear_cache(self):
39
+ """Clear cached clients (useful when contexts change)"""
40
+ self._clients.clear()
41
+
42
+
43
+ def get_current_context() -> str:
44
+ """Get the current kubectl context"""
45
+ try:
46
+ contexts, active_context = config.list_kube_config_contexts()
47
+ if active_context is None:
48
+ raise DeploymentError("No active kubectl context found")
49
+ return active_context["name"]
50
+ except Exception as e:
51
+ raise DeploymentError(f"Failed to get current kubectl context: {e}") from e
52
+
53
+
54
+ # Global client manager instance
55
+ _client_manager = KubernetesClientManager()
56
+
57
+
58
+ def list_available_contexts() -> list[str]:
59
+ """List all available kubectl contexts"""
60
+ try:
61
+ contexts, _ = config.list_kube_config_contexts()
62
+ return [ctx["name"] for ctx in contexts]
63
+ except Exception as e:
64
+ raise DeploymentError(f"Failed to list kubectl contexts: {e}") from e
65
+
66
+
67
+ def validate_cluster_context(cluster_name: str) -> bool:
68
+ """Check if a cluster name corresponds to an available kubectl context"""
69
+ try:
70
+ available_contexts = list_available_contexts()
71
+ return cluster_name in available_contexts
72
+ except DeploymentError:
73
+ return False
74
+
75
+
76
+ def switch_kubectl_context(cluster_name: str) -> None:
77
+ """Switch to the specified kubectl context"""
78
+ try:
79
+ # Use subprocess for context switching as it's a local kubeconfig operation
80
+ subprocess.run(
81
+ ["kubectl", "config", "use-context", cluster_name],
82
+ capture_output=True,
83
+ text=True,
84
+ check=True,
85
+ )
86
+ # Clear client cache since context changed
87
+ _client_manager.clear_cache()
88
+ logger.info(f"Switched to kubectl context: {cluster_name}")
89
+ except (subprocess.CalledProcessError, FileNotFoundError) as e:
90
+ raise DeploymentError(
91
+ f"Failed to switch to kubectl context '{cluster_name}': {e}"
92
+ ) from e
93
+
94
+
95
+ def validate_namespace(namespace: str, context: str | None = None) -> bool:
96
+ """Check if a namespace exists in the specified cluster context"""
97
+ try:
98
+ k8s_client = _client_manager.get_client(context)
99
+ k8s_client.read_namespace(name=namespace)
100
+ return True
101
+ except ApiException as e:
102
+ if e.status == 404:
103
+ return False
104
+ raise DeploymentError(f"Failed to validate namespace '{namespace}': {e}") from e
105
+ except Exception as e:
106
+ raise DeploymentError(f"Failed to validate namespace '{namespace}': {e}") from e
107
+
108
+
109
+ def check_and_switch_cluster_context(cluster_name: str) -> None:
110
+ """Check and switch to the specified kubectl context"""
111
+ # Validate cluster context
112
+ if not validate_cluster_context(cluster_name):
113
+ available_contexts = list_available_contexts()
114
+ raise DeploymentError(
115
+ f"Cluster '{cluster_name}' not found in kubectl contexts.\n"
116
+ f"Available contexts: {', '.join(available_contexts)}\n"
117
+ f"Please ensure you have a valid kubeconfig for this cluster."
118
+ )
119
+
120
+ # Switch to the specified cluster context
121
+ current_context = get_current_context()
122
+ if current_context != cluster_name:
123
+ console.print(
124
+ f"[blue]ℹ[/blue] Switching from context '{current_context}' to '{cluster_name}'"
125
+ )
126
+ switch_kubectl_context(cluster_name)
127
+ else:
128
+ console.print(
129
+ f"[blue]ℹ[/blue] Using current kubectl context: [bold]{cluster_name}[/bold]"
130
+ )
131
+
132
+
133
+ def get_k8s_client(context: str | None = None) -> client.CoreV1Api:
134
+ """Get a Kubernetes client for the specified context (or current context if None)"""
135
+ return _client_manager.get_client(context)
@@ -0,0 +1,185 @@
1
+ import base64
2
+
3
+ from kubernetes import client
4
+ from kubernetes.client.rest import ApiException
5
+ from rich.console import Console
6
+
7
+ from agentex.lib.cli.utils.kubectl_utils import get_k8s_client
8
+ from agentex.lib.utils.logging import make_logger
9
+
10
+ logger = make_logger(__name__)
11
+ console = Console()
12
+
13
+ KUBERNETES_SECRET_TYPE_OPAQUE = "Opaque"
14
+ KUBERNETES_SECRET_TYPE_DOCKERCONFIGJSON = "kubernetes.io/dockerconfigjson"
15
+ KUBERNETES_SECRET_TYPE_BASIC_AUTH = "kubernetes.io/basic-auth"
16
+ KUBERNETES_SECRET_TYPE_TLS = "kubernetes.io/tls"
17
+
18
+ VALID_SECRET_TYPES = [
19
+ KUBERNETES_SECRET_TYPE_OPAQUE,
20
+ KUBERNETES_SECRET_TYPE_DOCKERCONFIGJSON,
21
+ KUBERNETES_SECRET_TYPE_BASIC_AUTH,
22
+ KUBERNETES_SECRET_TYPE_TLS,
23
+ ]
24
+
25
+ KUBERNETES_SECRET_TO_MANIFEST_KEY = {
26
+ KUBERNETES_SECRET_TYPE_OPAQUE: "credentials",
27
+ KUBERNETES_SECRET_TYPE_DOCKERCONFIGJSON: "imagePullSecrets",
28
+ }
29
+
30
+
31
+ def _create_secret_object(
32
+ name: str, data: dict[str, str], secret_type: str = KUBERNETES_SECRET_TYPE_OPAQUE
33
+ ) -> client.V1Secret:
34
+ """Helper to create a V1Secret object with multiple key-value pairs"""
35
+ return client.V1Secret(
36
+ metadata=client.V1ObjectMeta(name=name),
37
+ type=secret_type,
38
+ string_data=data, # Use string_data for automatic base64 encoding
39
+ )
40
+
41
+
42
+ def create_secret_with_data(
43
+ name: str, data: dict[str, str], namespace: str, context: str | None = None
44
+ ) -> None:
45
+ """Create a new Kubernetes secret with multiple key-value pairs"""
46
+ v1 = get_k8s_client(context)
47
+
48
+ try:
49
+ # Check if secret exists
50
+ v1.read_namespaced_secret(name=name, namespace=namespace)
51
+ console.print(
52
+ f"[red]Error: Secret '{name}' already exists in namespace '{namespace}'[/red]"
53
+ )
54
+ return
55
+ except ApiException as e:
56
+ if e.status != 404: # If error is not "Not Found"
57
+ raise
58
+
59
+ # Create the secret
60
+ secret = _create_secret_object(name, data)
61
+
62
+ try:
63
+ v1.create_namespaced_secret(namespace=namespace, body=secret)
64
+ console.print(
65
+ f"[green]Created secret '{name}' in namespace '{namespace}' with {len(data)} keys[/green]"
66
+ )
67
+ except ApiException as e:
68
+ console.print(f"[red]Error creating secret: {e.reason}[/red]")
69
+ raise RuntimeError(f"Failed to create secret: {str(e)}") from e
70
+
71
+
72
+ def update_secret_with_data(
73
+ name: str, data: dict[str, str], namespace: str, context: str | None = None
74
+ ) -> None:
75
+ """Create or update a Kubernetes secret with multiple key-value pairs"""
76
+ v1 = get_k8s_client(context)
77
+ secret = _create_secret_object(name, data)
78
+
79
+ try:
80
+ # Try to update first
81
+ v1.replace_namespaced_secret(name=name, namespace=namespace, body=secret)
82
+ console.print(
83
+ f"[green]Updated secret '{name}' in namespace '{namespace}' with {len(data)} keys[/green]"
84
+ )
85
+ except ApiException as e:
86
+ if e.status == 404:
87
+ # Secret doesn't exist, create it
88
+ try:
89
+ v1.create_namespaced_secret(namespace=namespace, body=secret)
90
+ console.print(
91
+ f"[green]Created secret '{name}' in namespace '{namespace}' with {len(data)} keys[/green]"
92
+ )
93
+ except ApiException as create_error:
94
+ console.print(
95
+ f"[red]Error creating secret: {create_error.reason}[/red]"
96
+ )
97
+ raise RuntimeError(
98
+ f"Failed to create secret: {str(create_error)}"
99
+ ) from create_error
100
+ else:
101
+ console.print(f"[red]Error updating secret: {e.reason}[/red]")
102
+ raise RuntimeError(f"Failed to update secret: {str(e)}") from e
103
+
104
+
105
+ def create_image_pull_secret_with_data(
106
+ name: str, data: dict[str, str], namespace: str, context: str | None = None
107
+ ) -> None:
108
+ """Create a new Kubernetes image pull secret with dockerconfigjson type"""
109
+ v1 = get_k8s_client(context)
110
+
111
+ try:
112
+ # Check if secret exists
113
+ v1.read_namespaced_secret(name=name, namespace=namespace)
114
+ console.print(
115
+ f"[red]Error: Secret '{name}' already exists in namespace '{namespace}'[/red]"
116
+ )
117
+ return
118
+ except ApiException as e:
119
+ if e.status != 404: # If error is not "Not Found"
120
+ raise
121
+
122
+ # Create the secret with dockerconfigjson type
123
+ secret = _create_secret_object(name, data, KUBERNETES_SECRET_TYPE_DOCKERCONFIGJSON)
124
+
125
+ try:
126
+ v1.create_namespaced_secret(namespace=namespace, body=secret)
127
+ console.print(
128
+ f"[green]Created image pull secret '{name}' in namespace '{namespace}' with {len(data)} keys[/green]"
129
+ )
130
+ except ApiException as e:
131
+ console.print(f"[red]Error creating image pull secret: {e.reason}[/red]")
132
+ raise RuntimeError(f"Failed to create image pull secret: {str(e)}") from e
133
+
134
+
135
+ def update_image_pull_secret_with_data(
136
+ name: str, data: dict[str, str], namespace: str, context: str | None = None
137
+ ) -> None:
138
+ """Create or update a Kubernetes image pull secret with dockerconfigjson type"""
139
+ v1 = get_k8s_client(context)
140
+ secret = _create_secret_object(name, data, KUBERNETES_SECRET_TYPE_DOCKERCONFIGJSON)
141
+
142
+ try:
143
+ # Try to update first
144
+ v1.replace_namespaced_secret(name=name, namespace=namespace, body=secret)
145
+ console.print(
146
+ f"[green]Updated image pull secret '{name}' in namespace '{namespace}' with {len(data)} keys[/green]"
147
+ )
148
+ except ApiException as e:
149
+ if e.status == 404:
150
+ # Secret doesn't exist, create it
151
+ try:
152
+ v1.create_namespaced_secret(namespace=namespace, body=secret)
153
+ console.print(
154
+ f"[green]Created image pull secret '{name}' in namespace '{namespace}' with {len(data)} keys[/green]"
155
+ )
156
+ except ApiException as create_error:
157
+ console.print(
158
+ f"[red]Error creating image pull secret: {create_error.reason}[/red]"
159
+ )
160
+ raise RuntimeError(
161
+ f"Failed to create image pull secret: {str(create_error)}"
162
+ ) from create_error
163
+ else:
164
+ console.print(f"[red]Error updating image pull secret: {e.reason}[/red]")
165
+ raise RuntimeError(f"Failed to update image pull secret: {str(e)}") from e
166
+
167
+
168
+ def get_secret_data(
169
+ name: str, namespace: str, context: str | None = None
170
+ ) -> dict[str, str]:
171
+ """Get the actual data from a secret"""
172
+ v1 = get_k8s_client(context)
173
+ try:
174
+ secret = v1.read_namespaced_secret(name=name, namespace=namespace)
175
+ if secret.data:
176
+ # Decode base64 data
177
+ return {
178
+ key: base64.b64decode(value).decode("utf-8")
179
+ for key, value in secret.data.items()
180
+ }
181
+ return {}
182
+ except ApiException as e:
183
+ if e.status == 404:
184
+ return {}
185
+ raise RuntimeError(f"Failed to get secret data: {str(e)}") from e
File without changes
File without changes
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,46 @@
1
+ from collections.abc import AsyncGenerator, Generator
2
+
3
+ import litellm as llm
4
+
5
+ from agentex.lib.core.adapters.llm.port import LLMGateway
6
+ from agentex.lib.types.llm_messages import Completion
7
+ from agentex.lib.utils.logging import make_logger
8
+
9
+ logger = make_logger(__name__)
10
+
11
+
12
+ class LiteLLMGateway(LLMGateway):
13
+ def completion(self, *args, **kwargs) -> Completion:
14
+ if kwargs.get("stream", True):
15
+ raise ValueError(
16
+ "Please use self.completion_stream instead of self.completion to stream responses"
17
+ )
18
+
19
+ response = llm.completion(*args, **kwargs)
20
+ return Completion.model_validate(response)
21
+
22
+ def completion_stream(self, *args, **kwargs) -> Generator[Completion, None, None]:
23
+ if not kwargs.get("stream"):
24
+ raise ValueError("To use streaming, please set stream=True in the kwargs")
25
+
26
+ for chunk in llm.completion(*args, **kwargs):
27
+ yield Completion.model_validate(chunk)
28
+
29
+ async def acompletion(self, *args, **kwargs) -> Completion:
30
+ if kwargs.get("stream", True):
31
+ raise ValueError(
32
+ "Please use self.acompletion_stream instead of self.acompletion to stream responses"
33
+ )
34
+
35
+ # Return a single completion for non-streaming
36
+ response = await llm.acompletion(*args, **kwargs)
37
+ return Completion.model_validate(response)
38
+
39
+ async def acompletion_stream(
40
+ self, *args, **kwargs
41
+ ) -> AsyncGenerator[Completion, None]:
42
+ if not kwargs.get("stream"):
43
+ raise ValueError("To use streaming, please set stream=True in the kwargs")
44
+
45
+ async for chunk in await llm.acompletion(*args, **kwargs):
46
+ yield Completion.model_validate(chunk)
@@ -0,0 +1,55 @@
1
+ import os
2
+ from collections.abc import AsyncGenerator, Generator
3
+
4
+ from scale_gp import AsyncSGPClient, SGPClient
5
+
6
+ from agentex.lib.core.adapters.llm.port import LLMGateway
7
+ from agentex.lib.types.llm_messages import Completion
8
+ from agentex.lib.utils.logging import make_logger
9
+
10
+ logger = make_logger(__name__)
11
+
12
+
13
+ class SGPLLMGateway(LLMGateway):
14
+ def __init__(self, sgp_api_key: str | None = None):
15
+ self.sync_client = SGPClient(api_key=os.environ.get("SGP_API_KEY", sgp_api_key))
16
+ self.async_client = AsyncSGPClient(
17
+ api_key=os.environ.get("SGP_API_KEY", sgp_api_key)
18
+ )
19
+
20
+ def completion(self, *args, **kwargs) -> Completion:
21
+ if kwargs.get("stream", True):
22
+ raise ValueError(
23
+ "Please use self.completion_stream instead of self.completion to stream responses"
24
+ )
25
+
26
+ response = self.sync_client.beta.chat.completions.create(*args, **kwargs)
27
+ return Completion.model_validate(response)
28
+
29
+ def completion_stream(self, *args, **kwargs) -> Generator[Completion, None, None]:
30
+ if not kwargs.get("stream"):
31
+ raise ValueError("To use streaming, please set stream=True in the kwargs")
32
+
33
+ for chunk in self.sync_client.beta.chat.completions.create(*args, **kwargs):
34
+ yield Completion.model_validate(chunk)
35
+
36
+ async def acompletion(self, *args, **kwargs) -> Completion:
37
+ if kwargs.get("stream", True):
38
+ raise ValueError(
39
+ "Please use self.acompletion_stream instead of self.acompletion to stream responses"
40
+ )
41
+
42
+ # Return a single completion for non-streaming
43
+ response = await self.async_client.beta.chat.completions.create(*args, **kwargs)
44
+ return Completion.model_validate(response)
45
+
46
+ async def acompletion_stream(
47
+ self, *args, **kwargs
48
+ ) -> AsyncGenerator[Completion, None]:
49
+ if not kwargs.get("stream"):
50
+ raise ValueError("To use streaming, please set stream=True in the kwargs")
51
+
52
+ async for chunk in await self.async_client.beta.chat.completions.create(
53
+ *args, **kwargs
54
+ ):
55
+ yield Completion.model_validate(chunk)
@@ -0,0 +1,24 @@
1
+ from abc import ABC, abstractmethod
2
+ from collections.abc import AsyncGenerator, Generator
3
+
4
+ from agentex.lib.types.llm_messages import Completion
5
+
6
+
7
+ class LLMGateway(ABC):
8
+ @abstractmethod
9
+ def completion(self, *args, **kwargs) -> Completion:
10
+ raise NotImplementedError
11
+
12
+ @abstractmethod
13
+ def completion_stream(self, *args, **kwargs) -> Generator[Completion, None, None]:
14
+ raise NotImplementedError
15
+
16
+ @abstractmethod
17
+ async def acompletion(self, *args, **kwargs) -> Completion:
18
+ raise NotImplementedError
19
+
20
+ @abstractmethod
21
+ async def acompletion_stream(
22
+ self, *args, **kwargs
23
+ ) -> AsyncGenerator[Completion, None]:
24
+ raise NotImplementedError