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.
- agentex/__init__.py +103 -0
- agentex/_base_client.py +1992 -0
- agentex/_client.py +506 -0
- agentex/_compat.py +219 -0
- agentex/_constants.py +14 -0
- agentex/_exceptions.py +108 -0
- agentex/_files.py +123 -0
- agentex/_models.py +829 -0
- agentex/_qs.py +150 -0
- agentex/_resource.py +43 -0
- agentex/_response.py +830 -0
- agentex/_streaming.py +333 -0
- agentex/_types.py +219 -0
- agentex/_utils/__init__.py +57 -0
- agentex/_utils/_logs.py +25 -0
- agentex/_utils/_proxy.py +65 -0
- agentex/_utils/_reflection.py +42 -0
- agentex/_utils/_resources_proxy.py +24 -0
- agentex/_utils/_streams.py +12 -0
- agentex/_utils/_sync.py +86 -0
- agentex/_utils/_transform.py +447 -0
- agentex/_utils/_typing.py +151 -0
- agentex/_utils/_utils.py +422 -0
- agentex/_version.py +4 -0
- agentex/lib/.keep +4 -0
- agentex/lib/__init__.py +0 -0
- agentex/lib/adk/__init__.py +41 -0
- agentex/lib/adk/_modules/__init__.py +0 -0
- agentex/lib/adk/_modules/acp.py +247 -0
- agentex/lib/adk/_modules/agent_task_tracker.py +176 -0
- agentex/lib/adk/_modules/agents.py +77 -0
- agentex/lib/adk/_modules/events.py +141 -0
- agentex/lib/adk/_modules/messages.py +285 -0
- agentex/lib/adk/_modules/state.py +291 -0
- agentex/lib/adk/_modules/streaming.py +75 -0
- agentex/lib/adk/_modules/tasks.py +124 -0
- agentex/lib/adk/_modules/tracing.py +194 -0
- agentex/lib/adk/providers/__init__.py +9 -0
- agentex/lib/adk/providers/_modules/__init__.py +0 -0
- agentex/lib/adk/providers/_modules/litellm.py +232 -0
- agentex/lib/adk/providers/_modules/openai.py +416 -0
- agentex/lib/adk/providers/_modules/sgp.py +85 -0
- agentex/lib/adk/utils/__init__.py +5 -0
- agentex/lib/adk/utils/_modules/__init__.py +0 -0
- agentex/lib/adk/utils/_modules/templating.py +94 -0
- agentex/lib/cli/__init__.py +0 -0
- agentex/lib/cli/commands/__init__.py +0 -0
- agentex/lib/cli/commands/agents.py +328 -0
- agentex/lib/cli/commands/init.py +227 -0
- agentex/lib/cli/commands/main.py +33 -0
- agentex/lib/cli/commands/secrets.py +169 -0
- agentex/lib/cli/commands/tasks.py +118 -0
- agentex/lib/cli/commands/uv.py +133 -0
- agentex/lib/cli/handlers/__init__.py +0 -0
- agentex/lib/cli/handlers/agent_handlers.py +160 -0
- agentex/lib/cli/handlers/cleanup_handlers.py +186 -0
- agentex/lib/cli/handlers/deploy_handlers.py +351 -0
- agentex/lib/cli/handlers/run_handlers.py +452 -0
- agentex/lib/cli/handlers/secret_handlers.py +670 -0
- agentex/lib/cli/templates/default/.dockerignore.j2 +43 -0
- agentex/lib/cli/templates/default/Dockerfile-uv.j2 +42 -0
- agentex/lib/cli/templates/default/Dockerfile.j2 +42 -0
- agentex/lib/cli/templates/default/README.md.j2 +193 -0
- agentex/lib/cli/templates/default/deploy/example.yaml.j2 +55 -0
- agentex/lib/cli/templates/default/manifest.yaml.j2 +116 -0
- agentex/lib/cli/templates/default/project/acp.py.j2 +29 -0
- agentex/lib/cli/templates/default/pyproject.toml.j2 +33 -0
- agentex/lib/cli/templates/default/requirements.txt.j2 +5 -0
- agentex/lib/cli/templates/deploy/Screenshot 2025-03-19 at 10.36.57/342/200/257AM.png +0 -0
- agentex/lib/cli/templates/deploy/example.yaml.j2 +55 -0
- agentex/lib/cli/templates/sync/.dockerignore.j2 +43 -0
- agentex/lib/cli/templates/sync/Dockerfile-uv.j2 +42 -0
- agentex/lib/cli/templates/sync/Dockerfile.j2 +42 -0
- agentex/lib/cli/templates/sync/README.md.j2 +293 -0
- agentex/lib/cli/templates/sync/deploy/example.yaml.j2 +55 -0
- agentex/lib/cli/templates/sync/manifest.yaml.j2 +116 -0
- agentex/lib/cli/templates/sync/project/acp.py.j2 +26 -0
- agentex/lib/cli/templates/sync/pyproject.toml.j2 +33 -0
- agentex/lib/cli/templates/sync/requirements.txt.j2 +5 -0
- agentex/lib/cli/templates/temporal/.dockerignore.j2 +43 -0
- agentex/lib/cli/templates/temporal/Dockerfile-uv.j2 +48 -0
- agentex/lib/cli/templates/temporal/Dockerfile.j2 +48 -0
- agentex/lib/cli/templates/temporal/README.md.j2 +316 -0
- agentex/lib/cli/templates/temporal/deploy/example.yaml.j2 +55 -0
- agentex/lib/cli/templates/temporal/manifest.yaml.j2 +137 -0
- agentex/lib/cli/templates/temporal/project/acp.py.j2 +30 -0
- agentex/lib/cli/templates/temporal/project/run_worker.py.j2 +33 -0
- agentex/lib/cli/templates/temporal/project/workflow.py.j2 +66 -0
- agentex/lib/cli/templates/temporal/pyproject.toml.j2 +34 -0
- agentex/lib/cli/templates/temporal/requirements.txt.j2 +5 -0
- agentex/lib/cli/utils/cli_utils.py +14 -0
- agentex/lib/cli/utils/credential_utils.py +103 -0
- agentex/lib/cli/utils/exceptions.py +6 -0
- agentex/lib/cli/utils/kubectl_utils.py +135 -0
- agentex/lib/cli/utils/kubernetes_secrets_utils.py +185 -0
- agentex/lib/core/__init__.py +0 -0
- agentex/lib/core/adapters/__init__.py +0 -0
- agentex/lib/core/adapters/llm/__init__.py +1 -0
- agentex/lib/core/adapters/llm/adapter_litellm.py +46 -0
- agentex/lib/core/adapters/llm/adapter_sgp.py +55 -0
- agentex/lib/core/adapters/llm/port.py +24 -0
- agentex/lib/core/adapters/streams/adapter_redis.py +128 -0
- agentex/lib/core/adapters/streams/port.py +50 -0
- agentex/lib/core/clients/__init__.py +1 -0
- agentex/lib/core/clients/temporal/__init__.py +0 -0
- agentex/lib/core/clients/temporal/temporal_client.py +181 -0
- agentex/lib/core/clients/temporal/types.py +47 -0
- agentex/lib/core/clients/temporal/utils.py +56 -0
- agentex/lib/core/services/__init__.py +0 -0
- agentex/lib/core/services/adk/__init__.py +0 -0
- agentex/lib/core/services/adk/acp/__init__.py +0 -0
- agentex/lib/core/services/adk/acp/acp.py +210 -0
- agentex/lib/core/services/adk/agent_task_tracker.py +85 -0
- agentex/lib/core/services/adk/agents.py +43 -0
- agentex/lib/core/services/adk/events.py +61 -0
- agentex/lib/core/services/adk/messages.py +164 -0
- agentex/lib/core/services/adk/providers/__init__.py +0 -0
- agentex/lib/core/services/adk/providers/litellm.py +256 -0
- agentex/lib/core/services/adk/providers/openai.py +723 -0
- agentex/lib/core/services/adk/providers/sgp.py +99 -0
- agentex/lib/core/services/adk/state.py +120 -0
- agentex/lib/core/services/adk/streaming.py +262 -0
- agentex/lib/core/services/adk/tasks.py +69 -0
- agentex/lib/core/services/adk/tracing.py +36 -0
- agentex/lib/core/services/adk/utils/__init__.py +0 -0
- agentex/lib/core/services/adk/utils/templating.py +58 -0
- agentex/lib/core/temporal/__init__.py +0 -0
- agentex/lib/core/temporal/activities/__init__.py +207 -0
- agentex/lib/core/temporal/activities/activity_helpers.py +37 -0
- agentex/lib/core/temporal/activities/adk/__init__.py +0 -0
- agentex/lib/core/temporal/activities/adk/acp/__init__.py +0 -0
- agentex/lib/core/temporal/activities/adk/acp/acp_activities.py +86 -0
- agentex/lib/core/temporal/activities/adk/agent_task_tracker_activities.py +76 -0
- agentex/lib/core/temporal/activities/adk/agents_activities.py +35 -0
- agentex/lib/core/temporal/activities/adk/events_activities.py +50 -0
- agentex/lib/core/temporal/activities/adk/messages_activities.py +94 -0
- agentex/lib/core/temporal/activities/adk/providers/__init__.py +0 -0
- agentex/lib/core/temporal/activities/adk/providers/litellm_activities.py +71 -0
- agentex/lib/core/temporal/activities/adk/providers/openai_activities.py +210 -0
- agentex/lib/core/temporal/activities/adk/providers/sgp_activities.py +42 -0
- agentex/lib/core/temporal/activities/adk/state_activities.py +85 -0
- agentex/lib/core/temporal/activities/adk/streaming_activities.py +33 -0
- agentex/lib/core/temporal/activities/adk/tasks_activities.py +48 -0
- agentex/lib/core/temporal/activities/adk/tracing_activities.py +55 -0
- agentex/lib/core/temporal/activities/adk/utils/__init__.py +0 -0
- agentex/lib/core/temporal/activities/adk/utils/templating_activities.py +41 -0
- agentex/lib/core/temporal/services/__init__.py +0 -0
- agentex/lib/core/temporal/services/temporal_task_service.py +69 -0
- agentex/lib/core/temporal/types/__init__.py +0 -0
- agentex/lib/core/temporal/types/workflow.py +5 -0
- agentex/lib/core/temporal/workers/__init__.py +0 -0
- agentex/lib/core/temporal/workers/worker.py +162 -0
- agentex/lib/core/temporal/workflows/workflow.py +26 -0
- agentex/lib/core/tracing/__init__.py +5 -0
- agentex/lib/core/tracing/processors/agentex_tracing_processor.py +117 -0
- agentex/lib/core/tracing/processors/sgp_tracing_processor.py +119 -0
- agentex/lib/core/tracing/processors/tracing_processor_interface.py +40 -0
- agentex/lib/core/tracing/trace.py +311 -0
- agentex/lib/core/tracing/tracer.py +70 -0
- agentex/lib/core/tracing/tracing_processor_manager.py +62 -0
- agentex/lib/environment_variables.py +87 -0
- agentex/lib/py.typed +0 -0
- agentex/lib/sdk/__init__.py +0 -0
- agentex/lib/sdk/config/__init__.py +0 -0
- agentex/lib/sdk/config/agent_config.py +61 -0
- agentex/lib/sdk/config/agent_manifest.py +219 -0
- agentex/lib/sdk/config/build_config.py +35 -0
- agentex/lib/sdk/config/deployment_config.py +117 -0
- agentex/lib/sdk/config/local_development_config.py +56 -0
- agentex/lib/sdk/config/project_config.py +103 -0
- agentex/lib/sdk/fastacp/__init__.py +3 -0
- agentex/lib/sdk/fastacp/base/base_acp_server.py +406 -0
- agentex/lib/sdk/fastacp/fastacp.py +74 -0
- agentex/lib/sdk/fastacp/impl/agentic_base_acp.py +72 -0
- agentex/lib/sdk/fastacp/impl/sync_acp.py +109 -0
- agentex/lib/sdk/fastacp/impl/temporal_acp.py +97 -0
- agentex/lib/sdk/fastacp/tests/README.md +297 -0
- agentex/lib/sdk/fastacp/tests/conftest.py +307 -0
- agentex/lib/sdk/fastacp/tests/pytest.ini +10 -0
- agentex/lib/sdk/fastacp/tests/run_tests.py +227 -0
- agentex/lib/sdk/fastacp/tests/test_base_acp_server.py +450 -0
- agentex/lib/sdk/fastacp/tests/test_fastacp_factory.py +344 -0
- agentex/lib/sdk/fastacp/tests/test_integration.py +477 -0
- agentex/lib/sdk/state_machine/__init__.py +6 -0
- agentex/lib/sdk/state_machine/noop_workflow.py +21 -0
- agentex/lib/sdk/state_machine/state.py +10 -0
- agentex/lib/sdk/state_machine/state_machine.py +189 -0
- agentex/lib/sdk/state_machine/state_workflow.py +16 -0
- agentex/lib/sdk/utils/__init__.py +0 -0
- agentex/lib/sdk/utils/messages.py +223 -0
- agentex/lib/types/__init__.py +0 -0
- agentex/lib/types/acp.py +94 -0
- agentex/lib/types/agent_configs.py +79 -0
- agentex/lib/types/agent_results.py +29 -0
- agentex/lib/types/credentials.py +34 -0
- agentex/lib/types/fastacp.py +61 -0
- agentex/lib/types/files.py +13 -0
- agentex/lib/types/json_rpc.py +49 -0
- agentex/lib/types/llm_messages.py +354 -0
- agentex/lib/types/task_message_updates.py +171 -0
- agentex/lib/types/tracing.py +34 -0
- agentex/lib/utils/__init__.py +0 -0
- agentex/lib/utils/completions.py +131 -0
- agentex/lib/utils/console.py +14 -0
- agentex/lib/utils/io.py +29 -0
- agentex/lib/utils/iterables.py +14 -0
- agentex/lib/utils/json_schema.py +23 -0
- agentex/lib/utils/logging.py +31 -0
- agentex/lib/utils/mcp.py +17 -0
- agentex/lib/utils/model_utils.py +46 -0
- agentex/lib/utils/parsing.py +15 -0
- agentex/lib/utils/regex.py +6 -0
- agentex/lib/utils/temporal.py +13 -0
- agentex/py.typed +0 -0
- agentex/resources/__init__.py +103 -0
- agentex/resources/agents.py +707 -0
- agentex/resources/events.py +294 -0
- agentex/resources/messages/__init__.py +33 -0
- agentex/resources/messages/batch.py +271 -0
- agentex/resources/messages/messages.py +492 -0
- agentex/resources/spans.py +557 -0
- agentex/resources/states.py +544 -0
- agentex/resources/tasks.py +615 -0
- agentex/resources/tracker.py +384 -0
- agentex/types/__init__.py +56 -0
- agentex/types/acp_type.py +7 -0
- agentex/types/agent.py +29 -0
- agentex/types/agent_list_params.py +13 -0
- agentex/types/agent_list_response.py +10 -0
- agentex/types/agent_rpc_by_name_params.py +21 -0
- agentex/types/agent_rpc_params.py +51 -0
- agentex/types/agent_rpc_params1.py +21 -0
- agentex/types/agent_rpc_response.py +20 -0
- agentex/types/agent_rpc_result.py +90 -0
- agentex/types/agent_task_tracker.py +34 -0
- agentex/types/data_content.py +30 -0
- agentex/types/data_content_param.py +31 -0
- agentex/types/data_delta.py +14 -0
- agentex/types/event.py +29 -0
- agentex/types/event_list_params.py +22 -0
- agentex/types/event_list_response.py +10 -0
- agentex/types/message_author.py +7 -0
- agentex/types/message_create_params.py +18 -0
- agentex/types/message_list_params.py +14 -0
- agentex/types/message_list_response.py +10 -0
- agentex/types/message_style.py +7 -0
- agentex/types/message_update_params.py +18 -0
- agentex/types/messages/__init__.py +8 -0
- agentex/types/messages/batch_create_params.py +16 -0
- agentex/types/messages/batch_create_response.py +10 -0
- agentex/types/messages/batch_update_params.py +16 -0
- agentex/types/messages/batch_update_response.py +10 -0
- agentex/types/shared/__init__.py +3 -0
- agentex/types/shared/task_message_update.py +83 -0
- agentex/types/span.py +36 -0
- agentex/types/span_create_params.py +40 -0
- agentex/types/span_list_params.py +12 -0
- agentex/types/span_list_response.py +10 -0
- agentex/types/span_update_params.py +37 -0
- agentex/types/state.py +25 -0
- agentex/types/state_create_params.py +16 -0
- agentex/types/state_list_params.py +16 -0
- agentex/types/state_list_response.py +10 -0
- agentex/types/state_update_params.py +16 -0
- agentex/types/task.py +23 -0
- agentex/types/task_delete_by_name_response.py +8 -0
- agentex/types/task_delete_response.py +8 -0
- agentex/types/task_list_response.py +10 -0
- agentex/types/task_message.py +33 -0
- agentex/types/task_message_content.py +16 -0
- agentex/types/task_message_content_param.py +17 -0
- agentex/types/task_message_delta.py +16 -0
- agentex/types/text_content.py +53 -0
- agentex/types/text_content_param.py +54 -0
- agentex/types/text_delta.py +14 -0
- agentex/types/tool_request_content.py +36 -0
- agentex/types/tool_request_content_param.py +37 -0
- agentex/types/tool_request_delta.py +18 -0
- agentex/types/tool_response_content.py +36 -0
- agentex/types/tool_response_content_param.py +36 -0
- agentex/types/tool_response_delta.py +18 -0
- agentex/types/tracker_list_params.py +16 -0
- agentex/types/tracker_list_response.py +10 -0
- agentex/types/tracker_update_params.py +19 -0
- agentex_sdk-0.1.0a6.dist-info/METADATA +426 -0
- agentex_sdk-0.1.0a6.dist-info/RECORD +289 -0
- agentex_sdk-0.1.0a6.dist-info/WHEEL +4 -0
- agentex_sdk-0.1.0a6.dist-info/entry_points.txt +2 -0
- 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,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,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
|