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,219 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import io
|
4
|
+
import shutil
|
5
|
+
import subprocess
|
6
|
+
import tarfile
|
7
|
+
import tempfile
|
8
|
+
import time
|
9
|
+
from collections.abc import Iterator
|
10
|
+
from contextlib import contextmanager
|
11
|
+
from pathlib import Path
|
12
|
+
from typing import IO, Any
|
13
|
+
|
14
|
+
from pydantic import Field
|
15
|
+
|
16
|
+
from agentex.lib.sdk.config.agent_config import AgentConfig
|
17
|
+
from agentex.lib.sdk.config.build_config import BuildConfig
|
18
|
+
from agentex.lib.sdk.config.deployment_config import DeploymentConfig
|
19
|
+
from agentex.lib.sdk.config.local_development_config import LocalDevelopmentConfig
|
20
|
+
from agentex.lib.utils.logging import make_logger
|
21
|
+
from agentex.lib.utils.model_utils import BaseModel
|
22
|
+
|
23
|
+
logger = make_logger(__name__)
|
24
|
+
|
25
|
+
|
26
|
+
class AgentManifest(BaseModel):
|
27
|
+
"""
|
28
|
+
Represents a manifest file that describes how to build and deploy an agent.
|
29
|
+
"""
|
30
|
+
|
31
|
+
build: BuildConfig
|
32
|
+
agent: AgentConfig
|
33
|
+
local_development: LocalDevelopmentConfig | None = Field(
|
34
|
+
default=None, description="Configuration for local development"
|
35
|
+
)
|
36
|
+
deployment: DeploymentConfig | None = Field(
|
37
|
+
default=None, description="Deployment configuration for the agent"
|
38
|
+
)
|
39
|
+
|
40
|
+
def context_manager(self, build_context_root: Path) -> BuildContextManager:
|
41
|
+
"""
|
42
|
+
Creates a build context manager
|
43
|
+
"""
|
44
|
+
return BuildContextManager(
|
45
|
+
agent_manifest=self, build_context_root=build_context_root
|
46
|
+
)
|
47
|
+
|
48
|
+
|
49
|
+
class BuildContextManager:
|
50
|
+
"""
|
51
|
+
A gateway used to manage the build context for a docker image
|
52
|
+
"""
|
53
|
+
|
54
|
+
def __init__(self, agent_manifest: AgentManifest, build_context_root: Path):
|
55
|
+
self.agent_manifest = agent_manifest
|
56
|
+
self.build_context_root = build_context_root
|
57
|
+
self._temp_dir: tempfile.TemporaryDirectory | None = None
|
58
|
+
|
59
|
+
self.path: Path | None = None
|
60
|
+
self.dockerfile_path = "Dockerfile"
|
61
|
+
self.dockerignore_path = ".dockerignore"
|
62
|
+
self.directory_paths: list[Path] = []
|
63
|
+
|
64
|
+
def __enter__(self) -> BuildContextManager:
|
65
|
+
self._temp_dir = tempfile.TemporaryDirectory()
|
66
|
+
self.path = Path(self._temp_dir.name)
|
67
|
+
|
68
|
+
dockerfile_path = (
|
69
|
+
self.build_context_root / self.agent_manifest.build.context.dockerfile
|
70
|
+
)
|
71
|
+
self.add_dockerfile(root_path=self.path, dockerfile_path=dockerfile_path)
|
72
|
+
|
73
|
+
ignore_patterns = []
|
74
|
+
if self.agent_manifest.build.context.dockerignore:
|
75
|
+
dockerignore_path = (
|
76
|
+
self.build_context_root / self.agent_manifest.build.context.dockerignore
|
77
|
+
)
|
78
|
+
self.add_dockerignore(
|
79
|
+
root_path=self.path, dockerignore_path=dockerignore_path
|
80
|
+
)
|
81
|
+
ignore_patterns = _extract_dockerignore_patterns(dockerignore_path)
|
82
|
+
|
83
|
+
for directory in self.agent_manifest.build.context.include_paths:
|
84
|
+
directory_path = self.build_context_root / directory
|
85
|
+
self.add_directory(
|
86
|
+
root_path=self.path,
|
87
|
+
directory_path=directory_path,
|
88
|
+
context_root=self.build_context_root,
|
89
|
+
ignore_patterns=ignore_patterns,
|
90
|
+
)
|
91
|
+
|
92
|
+
return self
|
93
|
+
|
94
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
95
|
+
if self._temp_dir:
|
96
|
+
self._temp_dir.cleanup()
|
97
|
+
|
98
|
+
def add_dockerfile(self, root_path: Path, dockerfile_path: Path) -> None:
|
99
|
+
"""
|
100
|
+
Copies a dockerfile to the temporary context directory root
|
101
|
+
"""
|
102
|
+
shutil.copy2(dockerfile_path, root_path / self.dockerfile_path)
|
103
|
+
|
104
|
+
def add_dockerignore(self, root_path: Path, dockerignore_path: Path) -> None:
|
105
|
+
"""
|
106
|
+
Copies a dockerignore to the temporary context directory root
|
107
|
+
"""
|
108
|
+
shutil.copy2(str(dockerignore_path), root_path / self.dockerignore_path)
|
109
|
+
|
110
|
+
def add_directory(
|
111
|
+
self,
|
112
|
+
root_path: Path,
|
113
|
+
directory_path: Path,
|
114
|
+
context_root: Path,
|
115
|
+
ignore_patterns: list[str] | None = None,
|
116
|
+
) -> None:
|
117
|
+
"""
|
118
|
+
Copies a directory to the temporary context directory root while maintaining its relative
|
119
|
+
path to the context root.
|
120
|
+
"""
|
121
|
+
directory_copy_start_time = time.time()
|
122
|
+
last_log_time = directory_copy_start_time
|
123
|
+
|
124
|
+
def copy_function_with_progress(src, dst):
|
125
|
+
nonlocal directory_copy_start_time
|
126
|
+
nonlocal last_log_time
|
127
|
+
logger.info(f"Adding {src} to build context...")
|
128
|
+
shutil.copy2(src, dst)
|
129
|
+
current_time = time.time()
|
130
|
+
time_elapsed = current_time - directory_copy_start_time
|
131
|
+
|
132
|
+
if time_elapsed > 1 and current_time - last_log_time >= 1:
|
133
|
+
logger.info(
|
134
|
+
f"Time elapsed copying ({directory_path}): {time_elapsed} "
|
135
|
+
f"seconds"
|
136
|
+
)
|
137
|
+
last_log_time = current_time
|
138
|
+
if time_elapsed > 5:
|
139
|
+
logger.warning(
|
140
|
+
f"This may take a while... "
|
141
|
+
f"Consider adding {directory_path} or {src} to your .dockerignore file."
|
142
|
+
)
|
143
|
+
|
144
|
+
directory_path_relative_to_root = directory_path.relative_to(context_root)
|
145
|
+
all_ignore_patterns = [f"{root_path}*"]
|
146
|
+
if ignore_patterns:
|
147
|
+
all_ignore_patterns += ignore_patterns
|
148
|
+
shutil.copytree(
|
149
|
+
src=directory_path,
|
150
|
+
dst=root_path / directory_path_relative_to_root,
|
151
|
+
ignore=shutil.ignore_patterns(*all_ignore_patterns),
|
152
|
+
dirs_exist_ok=True,
|
153
|
+
copy_function=copy_function_with_progress,
|
154
|
+
)
|
155
|
+
self.directory_paths.append(directory_path_relative_to_root)
|
156
|
+
|
157
|
+
@contextmanager
|
158
|
+
def zip_stream(self, root_path: Path | None = None) -> Iterator[IO[bytes]]:
|
159
|
+
"""
|
160
|
+
Creates a tar archive of the temporary context directory
|
161
|
+
and returns a stream of the archive.
|
162
|
+
"""
|
163
|
+
if not root_path:
|
164
|
+
raise ValueError("root_path must be provided")
|
165
|
+
context = str(root_path.absolute())
|
166
|
+
folders_to_include = "."
|
167
|
+
tar_command = ["tar", "-C", context, "-cf", "-"]
|
168
|
+
tar_command.extend(folders_to_include)
|
169
|
+
|
170
|
+
logger.info(f"Creating archive: {' '.join(tar_command)}")
|
171
|
+
|
172
|
+
with subprocess.Popen(
|
173
|
+
tar_command,
|
174
|
+
stdout=subprocess.PIPE,
|
175
|
+
stderr=subprocess.DEVNULL,
|
176
|
+
) as proc:
|
177
|
+
assert proc.stdout is not None
|
178
|
+
try:
|
179
|
+
yield proc.stdout
|
180
|
+
finally:
|
181
|
+
pass
|
182
|
+
|
183
|
+
@staticmethod
|
184
|
+
@contextmanager
|
185
|
+
def zipped(root_path: Path | None = None) -> Iterator[IO[bytes]]:
|
186
|
+
"""
|
187
|
+
Creates a tar.gz archive of the temporary context directory
|
188
|
+
and returns a stream of the archive.
|
189
|
+
"""
|
190
|
+
if not root_path:
|
191
|
+
raise ValueError("root_path must be provided")
|
192
|
+
|
193
|
+
tar_buffer = io.BytesIO()
|
194
|
+
|
195
|
+
with tarfile.open(fileobj=tar_buffer, mode="w:gz") as tar_file:
|
196
|
+
for path in Path(root_path).rglob(
|
197
|
+
"*"
|
198
|
+
): # Recursively add files to the tar.gz
|
199
|
+
if path.is_file(): # Ensure that we're only adding files
|
200
|
+
tar_file.add(path, arcname=path.relative_to(root_path))
|
201
|
+
|
202
|
+
tar_buffer.seek(0) # Reset the buffer position to the beginning
|
203
|
+
yield tar_buffer
|
204
|
+
|
205
|
+
|
206
|
+
def _extract_dockerignore_patterns(dockerignore_path: Path) -> list[str]:
|
207
|
+
"""
|
208
|
+
Extracts glob patterns to ignore from the dockerignore into a list of patterns
|
209
|
+
:param dockerignore_path: Path to the dockerignore to extract patterns from
|
210
|
+
:return: List of glob patterns to ignore
|
211
|
+
:rtype: List[str]
|
212
|
+
"""
|
213
|
+
ignore_patterns = []
|
214
|
+
with open(dockerignore_path) as file:
|
215
|
+
for line in file:
|
216
|
+
ignored_filepath = line.split("#", 1)[0].strip()
|
217
|
+
if ignored_filepath:
|
218
|
+
ignore_patterns.append(ignored_filepath)
|
219
|
+
return ignore_patterns
|
@@ -0,0 +1,35 @@
|
|
1
|
+
from pydantic import Field
|
2
|
+
|
3
|
+
from agentex.lib.utils.model_utils import BaseModel
|
4
|
+
|
5
|
+
|
6
|
+
class BuildContext(BaseModel):
|
7
|
+
"""
|
8
|
+
Represents the context in which the Docker image should be built.
|
9
|
+
"""
|
10
|
+
|
11
|
+
root: str = Field(
|
12
|
+
...,
|
13
|
+
description="The root directory of the build context. Should be specified relative to the location of the "
|
14
|
+
"build config file.",
|
15
|
+
)
|
16
|
+
include_paths: list[str] = Field(
|
17
|
+
default_factory=list,
|
18
|
+
description="The paths to include in the build context. Should be specified relative to the root directory.",
|
19
|
+
)
|
20
|
+
dockerfile: str = Field(
|
21
|
+
...,
|
22
|
+
description="The path to the Dockerfile. Should be specified relative to the root directory.",
|
23
|
+
)
|
24
|
+
dockerignore: str | None = Field(
|
25
|
+
None,
|
26
|
+
description="The path to the .dockerignore file. Should be specified relative to the root directory.",
|
27
|
+
)
|
28
|
+
|
29
|
+
|
30
|
+
class BuildConfig(BaseModel):
|
31
|
+
"""
|
32
|
+
Represents a configuration for building the action as a Docker image.
|
33
|
+
"""
|
34
|
+
|
35
|
+
context: BuildContext
|
@@ -0,0 +1,117 @@
|
|
1
|
+
from typing import Any
|
2
|
+
|
3
|
+
from pydantic import Field
|
4
|
+
|
5
|
+
from agentex.lib.utils.model_utils import BaseModel
|
6
|
+
|
7
|
+
|
8
|
+
class ImageConfig(BaseModel):
|
9
|
+
"""Configuration for container images"""
|
10
|
+
|
11
|
+
repository: str = Field(..., description="Container image repository URL")
|
12
|
+
tag: str = Field(default="latest", description="Container image tag")
|
13
|
+
|
14
|
+
|
15
|
+
class ImagePullSecretConfig(BaseModel):
|
16
|
+
"""Configuration for image pull secrets"""
|
17
|
+
|
18
|
+
name: str = Field(..., description="Name of the image pull secret")
|
19
|
+
|
20
|
+
|
21
|
+
class ResourceRequirements(BaseModel):
|
22
|
+
"""Resource requirements for containers"""
|
23
|
+
|
24
|
+
cpu: str = Field(
|
25
|
+
default="500m", description="CPU request/limit (e.g., '500m', '1')"
|
26
|
+
)
|
27
|
+
memory: str = Field(
|
28
|
+
default="1Gi", description="Memory request/limit (e.g., '1Gi', '512Mi')"
|
29
|
+
)
|
30
|
+
|
31
|
+
|
32
|
+
class ResourceConfig(BaseModel):
|
33
|
+
"""Resource configuration for containers"""
|
34
|
+
|
35
|
+
requests: ResourceRequirements = Field(
|
36
|
+
default_factory=ResourceRequirements, description="Resource requests"
|
37
|
+
)
|
38
|
+
limits: ResourceRequirements = Field(
|
39
|
+
default_factory=ResourceRequirements, description="Resource limits"
|
40
|
+
)
|
41
|
+
|
42
|
+
|
43
|
+
class GlobalDeploymentConfig(BaseModel):
|
44
|
+
"""Global deployment configuration that applies to all clusters"""
|
45
|
+
|
46
|
+
agent: dict[str, str] = Field(
|
47
|
+
default_factory=dict, description="Agent metadata (name, description)"
|
48
|
+
)
|
49
|
+
replicaCount: int = Field(default=1, description="Number of replicas to deploy")
|
50
|
+
resources: ResourceConfig = Field(
|
51
|
+
default_factory=ResourceConfig, description="Resource requirements"
|
52
|
+
)
|
53
|
+
|
54
|
+
|
55
|
+
class DeploymentConfig(BaseModel):
|
56
|
+
"""Main deployment configuration in the manifest"""
|
57
|
+
|
58
|
+
image: ImageConfig = Field(..., description="Container image configuration")
|
59
|
+
imagePullSecrets: list[ImagePullSecretConfig] | None = Field(
|
60
|
+
default=None, description="Image pull secrets to use for the deployment"
|
61
|
+
)
|
62
|
+
global_config: GlobalDeploymentConfig = Field(
|
63
|
+
default_factory=GlobalDeploymentConfig,
|
64
|
+
description="Global deployment settings",
|
65
|
+
alias="global",
|
66
|
+
)
|
67
|
+
|
68
|
+
class Config:
|
69
|
+
validate_by_name = True
|
70
|
+
|
71
|
+
|
72
|
+
class ClusterConfig(BaseModel):
|
73
|
+
"""Per-cluster deployment overrides"""
|
74
|
+
|
75
|
+
image: ImageConfig | None = Field(
|
76
|
+
default=None, description="Cluster-specific image overrides"
|
77
|
+
)
|
78
|
+
replicaCount: int | None = Field(
|
79
|
+
default=None, description="Cluster-specific replica count"
|
80
|
+
)
|
81
|
+
resources: ResourceConfig | None = Field(
|
82
|
+
default=None, description="Cluster-specific resource overrides"
|
83
|
+
)
|
84
|
+
env: list[dict[str, str]] | None = Field(
|
85
|
+
default=None, description="Additional environment variables for this cluster"
|
86
|
+
)
|
87
|
+
# Allow additional arbitrary overrides for advanced users
|
88
|
+
additional_overrides: dict[str, Any] | None = Field(
|
89
|
+
default=None, description="Additional helm chart value overrides"
|
90
|
+
)
|
91
|
+
|
92
|
+
|
93
|
+
class InjectedImagePullSecretValues(BaseModel):
|
94
|
+
"""Values for image pull secrets"""
|
95
|
+
|
96
|
+
registry: str = Field(..., description="Registry of the image pull secret")
|
97
|
+
username: str = Field(..., description="Username of the image pull secret")
|
98
|
+
password: str = Field(..., description="Password of the image pull secret")
|
99
|
+
email: str | None = Field(
|
100
|
+
default=None, description="Email of the image pull secret"
|
101
|
+
)
|
102
|
+
|
103
|
+
|
104
|
+
class InjectedSecretsValues(BaseModel):
|
105
|
+
"""Values for injected secrets"""
|
106
|
+
|
107
|
+
# Defined as a dictionary because the names need to be unique
|
108
|
+
credentials: dict[str, Any] = Field(
|
109
|
+
default_factory=dict, description="Secrets to inject into the deployment"
|
110
|
+
)
|
111
|
+
imagePullSecrets: dict[str, InjectedImagePullSecretValues] = Field(
|
112
|
+
default_factory=dict,
|
113
|
+
description="Image pull secrets to inject into the deployment",
|
114
|
+
)
|
115
|
+
|
116
|
+
class Config:
|
117
|
+
validate_by_name = True
|
@@ -0,0 +1,56 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
|
3
|
+
from pydantic import Field, validator
|
4
|
+
|
5
|
+
from agentex.lib.utils.model_utils import BaseModel
|
6
|
+
|
7
|
+
|
8
|
+
class LocalAgentConfig(BaseModel):
|
9
|
+
"""Configuration for local agent development"""
|
10
|
+
|
11
|
+
port: int = Field(
|
12
|
+
...,
|
13
|
+
description="The port where the agent's ACP server is running locally",
|
14
|
+
gt=0,
|
15
|
+
lt=65536,
|
16
|
+
)
|
17
|
+
host_address: str = Field(
|
18
|
+
default="host.docker.internal",
|
19
|
+
description="The host address where the agent's ACP server can be reached (e.g., host.docker.internal for Docker, localhost for direct)",
|
20
|
+
)
|
21
|
+
|
22
|
+
|
23
|
+
class LocalPathsConfig(BaseModel):
|
24
|
+
"""Configuration for local file paths"""
|
25
|
+
|
26
|
+
acp: str = Field(
|
27
|
+
default="project/acp.py",
|
28
|
+
description="Path to the ACP server file. Can be relative to manifest directory or absolute.",
|
29
|
+
)
|
30
|
+
worker: str | None = Field(
|
31
|
+
default=None,
|
32
|
+
description="Path to the temporal worker file. Can be relative to manifest directory or absolute. (only for temporal agents)",
|
33
|
+
)
|
34
|
+
|
35
|
+
@validator("acp", "worker")
|
36
|
+
def validate_path_format(cls, v):
|
37
|
+
"""Validate that the path is a reasonable format"""
|
38
|
+
if v is None:
|
39
|
+
return v
|
40
|
+
|
41
|
+
# Convert to Path to validate format
|
42
|
+
try:
|
43
|
+
Path(v)
|
44
|
+
except Exception as e:
|
45
|
+
raise ValueError(f"Invalid path format: {v}") from e
|
46
|
+
|
47
|
+
return v
|
48
|
+
|
49
|
+
|
50
|
+
class LocalDevelopmentConfig(BaseModel):
|
51
|
+
"""Configuration for local development environment"""
|
52
|
+
|
53
|
+
agent: LocalAgentConfig = Field(..., description="Local agent configuration")
|
54
|
+
paths: LocalPathsConfig | None = Field(
|
55
|
+
default=None, description="File paths for local development"
|
56
|
+
)
|
@@ -0,0 +1,103 @@
|
|
1
|
+
import os
|
2
|
+
import re
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Any, TypeVar
|
5
|
+
|
6
|
+
import yaml
|
7
|
+
from jinja2 import BaseLoader, Environment, StrictUndefined, TemplateError
|
8
|
+
|
9
|
+
T = TypeVar("T")
|
10
|
+
|
11
|
+
|
12
|
+
class ConfigResolutionError(Exception):
|
13
|
+
def __init__(self, message: str) -> None:
|
14
|
+
super().__init__(message)
|
15
|
+
self.status_code = 400
|
16
|
+
|
17
|
+
|
18
|
+
def _preprocess_template(template_str: str) -> str:
|
19
|
+
# Replace $env. and $variables. with unique internal names
|
20
|
+
return template_str.replace("{{ $env.", "{{ __special_env__.").replace(
|
21
|
+
"{{ $variables.", "{{ __special_variables__."
|
22
|
+
)
|
23
|
+
|
24
|
+
|
25
|
+
def _extract_variables_section(raw_config_str: str) -> str:
|
26
|
+
# Use regex to extract the variables: ... block (YAML top-level)
|
27
|
+
match = re.search(
|
28
|
+
r"(^variables:.*?)(^config:|\Z)", raw_config_str, re.DOTALL | re.MULTILINE
|
29
|
+
)
|
30
|
+
if not match:
|
31
|
+
return ""
|
32
|
+
return match.group(1)
|
33
|
+
|
34
|
+
|
35
|
+
def ProjectConfigLoader(
|
36
|
+
config_path: str, model: type[T] | None = None, env_path: str | None = None
|
37
|
+
) -> dict[str, Any] | T:
|
38
|
+
config_path = Path(config_path)
|
39
|
+
env_path = Path(env_path) if env_path else config_path.parent / ".env"
|
40
|
+
env = _load_env(env_path)
|
41
|
+
raw_config_str = _load_file_as_str(config_path)
|
42
|
+
raw_config_str = _preprocess_template(raw_config_str)
|
43
|
+
|
44
|
+
# Extract and render only the variables section
|
45
|
+
variables_section_str = _extract_variables_section(raw_config_str)
|
46
|
+
env_context = {"__special_env__": env, "__special_variables__": {}}
|
47
|
+
try:
|
48
|
+
env_only_template = Environment(
|
49
|
+
loader=BaseLoader(),
|
50
|
+
undefined=StrictUndefined,
|
51
|
+
keep_trailing_newline=True,
|
52
|
+
autoescape=False,
|
53
|
+
).from_string(variables_section_str)
|
54
|
+
rendered_variables_yaml = env_only_template.render(**env_context)
|
55
|
+
variables_dict = yaml.safe_load(rendered_variables_yaml).get("variables", {})
|
56
|
+
except Exception as e:
|
57
|
+
raise ConfigResolutionError(f"Error rendering variables with $env: {e}") from e
|
58
|
+
# Second pass: render the whole config with both __special_env__ and resolved __special_variables__
|
59
|
+
full_context = {"__special_env__": env, "__special_variables__": variables_dict}
|
60
|
+
rendered_config_str = _jinja_render(raw_config_str, full_context)
|
61
|
+
try:
|
62
|
+
rendered_config = yaml.safe_load(rendered_config_str)
|
63
|
+
except Exception as e:
|
64
|
+
raise ConfigResolutionError(f"Error loading rendered YAML: {e}") from e
|
65
|
+
if "config" not in rendered_config:
|
66
|
+
raise ConfigResolutionError("Missing 'config' section in config file.")
|
67
|
+
config_section = rendered_config["config"]
|
68
|
+
if model is not None:
|
69
|
+
return model(**config_section)
|
70
|
+
return config_section
|
71
|
+
|
72
|
+
|
73
|
+
def _load_env(env_path: Path) -> dict[str, str]:
|
74
|
+
env = dict(os.environ)
|
75
|
+
if env_path.exists():
|
76
|
+
with open(env_path) as f:
|
77
|
+
for line in f:
|
78
|
+
line = line.strip()
|
79
|
+
if not line or line.startswith("#"):
|
80
|
+
continue
|
81
|
+
if "=" in line:
|
82
|
+
k, v = line.split("=", 1)
|
83
|
+
env[k.strip()] = v.strip()
|
84
|
+
return env
|
85
|
+
|
86
|
+
|
87
|
+
def _load_file_as_str(path: Path) -> str:
|
88
|
+
with open(path) as f:
|
89
|
+
return f.read()
|
90
|
+
|
91
|
+
|
92
|
+
def _jinja_render(template_str: str, context: dict) -> str:
|
93
|
+
try:
|
94
|
+
env = Environment(
|
95
|
+
loader=BaseLoader(),
|
96
|
+
undefined=StrictUndefined,
|
97
|
+
keep_trailing_newline=True,
|
98
|
+
autoescape=False,
|
99
|
+
)
|
100
|
+
template = env.from_string(template_str)
|
101
|
+
return template.render(**context)
|
102
|
+
except TemplateError as e:
|
103
|
+
raise ConfigResolutionError(f"Jinja template error: {e}") from e
|