agentscope-runtime 0.2.0b2__py3-none-any.whl → 1.0.0__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.
- agentscope_runtime/adapters/__init__.py +0 -0
- agentscope_runtime/adapters/agentscope/__init__.py +0 -0
- agentscope_runtime/adapters/agentscope/long_term_memory/__init__.py +6 -0
- agentscope_runtime/adapters/agentscope/long_term_memory/_long_term_memory_adapter.py +258 -0
- agentscope_runtime/adapters/agentscope/memory/__init__.py +6 -0
- agentscope_runtime/adapters/agentscope/memory/_memory_adapter.py +152 -0
- agentscope_runtime/adapters/agentscope/message.py +535 -0
- agentscope_runtime/adapters/agentscope/stream.py +506 -0
- agentscope_runtime/adapters/agentscope/tool/__init__.py +9 -0
- agentscope_runtime/adapters/agentscope/tool/sandbox_tool.py +69 -0
- agentscope_runtime/adapters/agentscope/tool/tool.py +233 -0
- agentscope_runtime/adapters/autogen/__init__.py +0 -0
- agentscope_runtime/adapters/autogen/tool/__init__.py +7 -0
- agentscope_runtime/adapters/autogen/tool/tool.py +211 -0
- agentscope_runtime/adapters/text/__init__.py +0 -0
- agentscope_runtime/adapters/text/stream.py +29 -0
- agentscope_runtime/common/collections/redis_mapping.py +4 -1
- agentscope_runtime/common/container_clients/fc_client.py +855 -0
- agentscope_runtime/common/utils/__init__.py +0 -0
- agentscope_runtime/common/utils/lazy_loader.py +57 -0
- agentscope_runtime/engine/__init__.py +25 -18
- agentscope_runtime/engine/app/agent_app.py +161 -91
- agentscope_runtime/engine/app/base_app.py +4 -118
- agentscope_runtime/engine/constant.py +8 -0
- agentscope_runtime/engine/deployers/__init__.py +8 -0
- agentscope_runtime/engine/deployers/adapter/__init__.py +2 -0
- agentscope_runtime/engine/deployers/adapter/a2a/a2a_adapter_utils.py +0 -21
- agentscope_runtime/engine/deployers/adapter/a2a/a2a_protocol_adapter.py +28 -9
- agentscope_runtime/engine/deployers/adapter/responses/__init__.py +2 -0
- agentscope_runtime/engine/deployers/adapter/responses/response_api_adapter_utils.py +5 -2
- agentscope_runtime/engine/deployers/adapter/responses/response_api_protocol_adapter.py +1 -1
- agentscope_runtime/engine/deployers/agentrun_deployer.py +2541 -0
- agentscope_runtime/engine/deployers/cli_fc_deploy.py +1 -1
- agentscope_runtime/engine/deployers/kubernetes_deployer.py +9 -21
- agentscope_runtime/engine/deployers/local_deployer.py +47 -74
- agentscope_runtime/engine/deployers/modelstudio_deployer.py +216 -50
- agentscope_runtime/engine/deployers/utils/app_runner_utils.py +29 -0
- agentscope_runtime/engine/deployers/utils/detached_app.py +510 -0
- agentscope_runtime/engine/deployers/utils/docker_image_utils/__init__.py +1 -1
- agentscope_runtime/engine/deployers/utils/docker_image_utils/dockerfile_generator.py +1 -1
- agentscope_runtime/engine/deployers/utils/docker_image_utils/{runner_image_factory.py → image_factory.py} +121 -61
- agentscope_runtime/engine/deployers/utils/package.py +693 -0
- agentscope_runtime/engine/deployers/utils/service_utils/__init__.py +0 -5
- agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +301 -282
- agentscope_runtime/engine/deployers/utils/service_utils/fastapi_templates.py +2 -4
- agentscope_runtime/engine/deployers/utils/service_utils/process_manager.py +23 -1
- agentscope_runtime/engine/deployers/utils/templates/app_main.py.j2 +84 -0
- agentscope_runtime/engine/deployers/utils/templates/runner_main.py.j2 +95 -0
- agentscope_runtime/engine/deployers/utils/{service_utils → templates}/standalone_main.py.j2 +0 -45
- agentscope_runtime/engine/deployers/utils/wheel_packager.py +119 -18
- agentscope_runtime/engine/helpers/runner.py +40 -0
- agentscope_runtime/engine/runner.py +171 -130
- agentscope_runtime/engine/schemas/agent_schemas.py +114 -3
- agentscope_runtime/engine/schemas/modelstudio_llm.py +4 -2
- agentscope_runtime/engine/schemas/oai_llm.py +23 -23
- agentscope_runtime/engine/schemas/response_api.py +65 -0
- agentscope_runtime/engine/schemas/session.py +24 -0
- agentscope_runtime/engine/services/__init__.py +0 -9
- agentscope_runtime/engine/services/agent_state/__init__.py +16 -0
- agentscope_runtime/engine/services/agent_state/redis_state_service.py +113 -0
- agentscope_runtime/engine/services/agent_state/state_service.py +179 -0
- agentscope_runtime/engine/services/memory/__init__.py +24 -0
- agentscope_runtime/engine/services/{mem0_memory_service.py → memory/mem0_memory_service.py} +17 -13
- agentscope_runtime/engine/services/{memory_service.py → memory/memory_service.py} +28 -7
- agentscope_runtime/engine/services/{redis_memory_service.py → memory/redis_memory_service.py} +1 -1
- agentscope_runtime/engine/services/{reme_personal_memory_service.py → memory/reme_personal_memory_service.py} +9 -6
- agentscope_runtime/engine/services/{reme_task_memory_service.py → memory/reme_task_memory_service.py} +2 -2
- agentscope_runtime/engine/services/{tablestore_memory_service.py → memory/tablestore_memory_service.py} +12 -18
- agentscope_runtime/engine/services/sandbox/__init__.py +13 -0
- agentscope_runtime/engine/services/{sandbox_service.py → sandbox/sandbox_service.py} +86 -71
- agentscope_runtime/engine/services/session_history/__init__.py +23 -0
- agentscope_runtime/engine/services/{redis_session_history_service.py → session_history/redis_session_history_service.py} +3 -2
- agentscope_runtime/engine/services/{session_history_service.py → session_history/session_history_service.py} +44 -34
- agentscope_runtime/engine/services/{tablestore_session_history_service.py → session_history/tablestore_session_history_service.py} +14 -19
- agentscope_runtime/engine/services/utils/tablestore_service_utils.py +2 -2
- agentscope_runtime/engine/tracing/base.py +10 -9
- agentscope_runtime/engine/tracing/message_util.py +1 -1
- agentscope_runtime/engine/tracing/tracing_util.py +7 -2
- agentscope_runtime/engine/tracing/wrapper.py +49 -31
- agentscope_runtime/sandbox/__init__.py +10 -2
- agentscope_runtime/sandbox/box/agentbay/__init__.py +4 -0
- agentscope_runtime/sandbox/box/agentbay/agentbay_sandbox.py +559 -0
- agentscope_runtime/sandbox/box/base/base_sandbox.py +12 -0
- agentscope_runtime/sandbox/box/browser/browser_sandbox.py +115 -11
- agentscope_runtime/sandbox/box/cloud/__init__.py +4 -0
- agentscope_runtime/sandbox/box/cloud/cloud_sandbox.py +254 -0
- agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +66 -0
- agentscope_runtime/sandbox/box/gui/gui_sandbox.py +42 -0
- agentscope_runtime/sandbox/box/mobile/__init__.py +4 -0
- agentscope_runtime/sandbox/box/mobile/box/__init__.py +0 -0
- agentscope_runtime/sandbox/box/mobile/mobile_sandbox.py +216 -0
- agentscope_runtime/sandbox/box/training_box/training_box.py +2 -2
- agentscope_runtime/sandbox/client/http_client.py +1 -0
- agentscope_runtime/sandbox/enums.py +2 -0
- agentscope_runtime/sandbox/manager/sandbox_manager.py +15 -2
- agentscope_runtime/sandbox/manager/server/app.py +12 -0
- agentscope_runtime/sandbox/manager/server/config.py +19 -0
- agentscope_runtime/sandbox/model/manager_config.py +79 -2
- agentscope_runtime/sandbox/utils.py +0 -18
- agentscope_runtime/tools/RAGs/__init__.py +0 -0
- agentscope_runtime/tools/RAGs/modelstudio_rag.py +377 -0
- agentscope_runtime/tools/RAGs/modelstudio_rag_lite.py +219 -0
- agentscope_runtime/tools/__init__.py +119 -0
- agentscope_runtime/tools/_constants.py +18 -0
- agentscope_runtime/tools/alipay/__init__.py +4 -0
- agentscope_runtime/tools/alipay/base.py +334 -0
- agentscope_runtime/tools/alipay/payment.py +835 -0
- agentscope_runtime/tools/alipay/subscribe.py +551 -0
- agentscope_runtime/tools/base.py +264 -0
- agentscope_runtime/tools/cli/__init__.py +0 -0
- agentscope_runtime/tools/cli/modelstudio_mcp_server.py +78 -0
- agentscope_runtime/tools/generations/__init__.py +75 -0
- agentscope_runtime/tools/generations/async_image_to_video.py +350 -0
- agentscope_runtime/tools/generations/async_image_to_video_wan25.py +366 -0
- agentscope_runtime/tools/generations/async_speech_to_video.py +422 -0
- agentscope_runtime/tools/generations/async_text_to_video.py +320 -0
- agentscope_runtime/tools/generations/async_text_to_video_wan25.py +334 -0
- agentscope_runtime/tools/generations/image_edit.py +208 -0
- agentscope_runtime/tools/generations/image_edit_wan25.py +193 -0
- agentscope_runtime/tools/generations/image_generation.py +202 -0
- agentscope_runtime/tools/generations/image_generation_wan25.py +201 -0
- agentscope_runtime/tools/generations/image_style_repaint.py +208 -0
- agentscope_runtime/tools/generations/image_to_video.py +233 -0
- agentscope_runtime/tools/generations/qwen_image_edit.py +205 -0
- agentscope_runtime/tools/generations/qwen_image_generation.py +214 -0
- agentscope_runtime/tools/generations/qwen_text_to_speech.py +154 -0
- agentscope_runtime/tools/generations/speech_to_text.py +260 -0
- agentscope_runtime/tools/generations/speech_to_video.py +314 -0
- agentscope_runtime/tools/generations/text_to_video.py +221 -0
- agentscope_runtime/tools/mcp_wrapper.py +215 -0
- agentscope_runtime/tools/realtime_clients/__init__.py +13 -0
- agentscope_runtime/tools/realtime_clients/asr_client.py +27 -0
- agentscope_runtime/tools/realtime_clients/azure_asr_client.py +195 -0
- agentscope_runtime/tools/realtime_clients/azure_tts_client.py +383 -0
- agentscope_runtime/tools/realtime_clients/modelstudio_asr_client.py +151 -0
- agentscope_runtime/tools/realtime_clients/modelstudio_tts_client.py +199 -0
- agentscope_runtime/tools/realtime_clients/realtime_tool.py +55 -0
- agentscope_runtime/tools/realtime_clients/tts_client.py +33 -0
- agentscope_runtime/tools/searches/__init__.py +3 -0
- agentscope_runtime/tools/searches/modelstudio_search.py +877 -0
- agentscope_runtime/tools/searches/modelstudio_search_lite.py +310 -0
- agentscope_runtime/tools/utils/__init__.py +0 -0
- agentscope_runtime/tools/utils/api_key_util.py +45 -0
- agentscope_runtime/tools/utils/crypto_utils.py +99 -0
- agentscope_runtime/tools/utils/mcp_util.py +35 -0
- agentscope_runtime/version.py +1 -1
- {agentscope_runtime-0.2.0b2.dist-info → agentscope_runtime-1.0.0.dist-info}/METADATA +240 -168
- agentscope_runtime-1.0.0.dist-info/RECORD +240 -0
- {agentscope_runtime-0.2.0b2.dist-info → agentscope_runtime-1.0.0.dist-info}/entry_points.txt +1 -0
- agentscope_runtime/engine/agents/__init__.py +0 -2
- agentscope_runtime/engine/agents/agentscope_agent.py +0 -488
- agentscope_runtime/engine/agents/agno_agent.py +0 -220
- agentscope_runtime/engine/agents/autogen_agent.py +0 -250
- agentscope_runtime/engine/agents/base_agent.py +0 -29
- agentscope_runtime/engine/agents/langgraph_agent.py +0 -59
- agentscope_runtime/engine/agents/utils.py +0 -53
- agentscope_runtime/engine/deployers/utils/package_project_utils.py +0 -1163
- agentscope_runtime/engine/deployers/utils/service_utils/service_config.py +0 -75
- agentscope_runtime/engine/deployers/utils/service_utils/service_factory.py +0 -220
- agentscope_runtime/engine/helpers/helper.py +0 -179
- agentscope_runtime/engine/schemas/context.py +0 -54
- agentscope_runtime/engine/services/context_manager.py +0 -164
- agentscope_runtime/engine/services/environment_manager.py +0 -50
- agentscope_runtime/engine/services/manager.py +0 -174
- agentscope_runtime/engine/services/rag_service.py +0 -195
- agentscope_runtime/engine/services/tablestore_rag_service.py +0 -143
- agentscope_runtime/sandbox/tools/__init__.py +0 -12
- agentscope_runtime/sandbox/tools/base/__init__.py +0 -8
- agentscope_runtime/sandbox/tools/base/tool.py +0 -52
- agentscope_runtime/sandbox/tools/browser/__init__.py +0 -57
- agentscope_runtime/sandbox/tools/browser/tool.py +0 -597
- agentscope_runtime/sandbox/tools/filesystem/__init__.py +0 -32
- agentscope_runtime/sandbox/tools/filesystem/tool.py +0 -319
- agentscope_runtime/sandbox/tools/function_tool.py +0 -321
- agentscope_runtime/sandbox/tools/gui/__init__.py +0 -7
- agentscope_runtime/sandbox/tools/gui/tool.py +0 -77
- agentscope_runtime/sandbox/tools/mcp_tool.py +0 -195
- agentscope_runtime/sandbox/tools/sandbox_tool.py +0 -104
- agentscope_runtime/sandbox/tools/tool.py +0 -238
- agentscope_runtime/sandbox/tools/utils.py +0 -68
- agentscope_runtime-0.2.0b2.dist-info/RECORD +0 -183
- {agentscope_runtime-0.2.0b2.dist-info → agentscope_runtime-1.0.0.dist-info}/WHEEL +0 -0
- {agentscope_runtime-0.2.0b2.dist-info → agentscope_runtime-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {agentscope_runtime-0.2.0b2.dist-info → agentscope_runtime-1.0.0.dist-info}/top_level.txt +0 -0
|
@@ -1,1163 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
# pylint:disable=too-many-boolean-expressions, too-many-nested-blocks
|
|
3
|
-
# pylint:disable=too-many-return-statements, unused-variable
|
|
4
|
-
# pylint:disable=cell-var-from-loop, too-many-branches, too-many-statements
|
|
5
|
-
|
|
6
|
-
import ast
|
|
7
|
-
import hashlib
|
|
8
|
-
import inspect
|
|
9
|
-
import os
|
|
10
|
-
import re
|
|
11
|
-
import shutil
|
|
12
|
-
import tarfile
|
|
13
|
-
import tempfile
|
|
14
|
-
from pathlib import Path
|
|
15
|
-
from typing import List, Optional, Any, Tuple, Dict
|
|
16
|
-
|
|
17
|
-
from pydantic import BaseModel
|
|
18
|
-
|
|
19
|
-
try:
|
|
20
|
-
import tomllib # Python 3.11+
|
|
21
|
-
except ImportError:
|
|
22
|
-
try:
|
|
23
|
-
import tomli as tomllib # type: ignore[no-redef]
|
|
24
|
-
except ImportError:
|
|
25
|
-
tomllib = None
|
|
26
|
-
|
|
27
|
-
from .service_utils.fastapi_templates import FastAPITemplateManager
|
|
28
|
-
from .service_utils.service_config import ServicesConfig
|
|
29
|
-
|
|
30
|
-
# Default template will be loaded from template file
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def _get_package_version() -> str:
|
|
34
|
-
"""
|
|
35
|
-
Get the package version from pyproject.toml file.
|
|
36
|
-
|
|
37
|
-
Returns:
|
|
38
|
-
str: The version string, or empty string if not found
|
|
39
|
-
"""
|
|
40
|
-
# Try to find pyproject.toml in the current directory and parent
|
|
41
|
-
# directories
|
|
42
|
-
current_dir = Path(__file__).parent
|
|
43
|
-
for _ in range(6): # Look up to 6 levels up
|
|
44
|
-
pyproject_path = current_dir / "pyproject.toml"
|
|
45
|
-
if pyproject_path.exists():
|
|
46
|
-
break
|
|
47
|
-
current_dir = current_dir.parent
|
|
48
|
-
else:
|
|
49
|
-
# Also try the current working directory
|
|
50
|
-
pyproject_path = Path(os.getcwd()) / "pyproject.toml"
|
|
51
|
-
if not pyproject_path.exists():
|
|
52
|
-
return ""
|
|
53
|
-
|
|
54
|
-
try:
|
|
55
|
-
# Use tomllib to parse
|
|
56
|
-
with open(pyproject_path, "rb") as f:
|
|
57
|
-
data = tomllib.load(f)
|
|
58
|
-
project = data.get("project", {})
|
|
59
|
-
return project.get("version", "")
|
|
60
|
-
except Exception:
|
|
61
|
-
return ""
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def _prepare_custom_endpoints_for_template(
|
|
65
|
-
custom_endpoints: Optional[List[Dict]],
|
|
66
|
-
temp_dir: str,
|
|
67
|
-
) -> Tuple[Optional[List[Dict]], List[str]]:
|
|
68
|
-
"""
|
|
69
|
-
Prepare custom endpoints for template rendering.
|
|
70
|
-
Copy handler source directories to ensure all dependencies are available.
|
|
71
|
-
|
|
72
|
-
Args:
|
|
73
|
-
custom_endpoints: List of custom endpoint configurations
|
|
74
|
-
temp_dir: Temporary directory where files will be copied
|
|
75
|
-
|
|
76
|
-
Returns:
|
|
77
|
-
Tuple of:
|
|
78
|
-
- Prepared endpoint configurations with file information
|
|
79
|
-
- List of copied directory names (for sys.path setup)
|
|
80
|
-
"""
|
|
81
|
-
if not custom_endpoints:
|
|
82
|
-
return None, []
|
|
83
|
-
|
|
84
|
-
prepared_endpoints = []
|
|
85
|
-
handler_dirs_copied = set() # Track copied directories to avoid duplicates
|
|
86
|
-
copied_dir_names = [] # Track directory names for sys.path
|
|
87
|
-
|
|
88
|
-
for endpoint in custom_endpoints:
|
|
89
|
-
prepared_endpoint = {
|
|
90
|
-
"path": endpoint.get("path", "/unknown"),
|
|
91
|
-
"methods": endpoint.get("methods", ["POST"]),
|
|
92
|
-
"module": endpoint.get("module"),
|
|
93
|
-
"function_name": endpoint.get("function_name"),
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
# Try to get handler source file if handler is provided
|
|
97
|
-
handler = endpoint.get("handler")
|
|
98
|
-
if handler and callable(handler):
|
|
99
|
-
try:
|
|
100
|
-
# Get the source file of the handler
|
|
101
|
-
handler_file = inspect.getfile(handler)
|
|
102
|
-
handler_name = handler.__name__
|
|
103
|
-
|
|
104
|
-
# Skip if it's a built-in or from site-packages
|
|
105
|
-
if (
|
|
106
|
-
not handler_file.endswith(".py")
|
|
107
|
-
or "site-packages" in handler_file
|
|
108
|
-
):
|
|
109
|
-
raise ValueError("Handler from non-user code")
|
|
110
|
-
|
|
111
|
-
# Get the directory containing the handler file
|
|
112
|
-
handler_dir = os.path.dirname(os.path.abspath(handler_file))
|
|
113
|
-
|
|
114
|
-
# Copy the entire working directory if not already copied
|
|
115
|
-
if handler_dir not in handler_dirs_copied:
|
|
116
|
-
# Create a subdirectory name for this handler's context
|
|
117
|
-
dir_name = os.path.basename(handler_dir)
|
|
118
|
-
if not dir_name or dir_name == ".":
|
|
119
|
-
dir_name = "handler_context"
|
|
120
|
-
|
|
121
|
-
# Sanitize directory name
|
|
122
|
-
dir_name = re.sub(r"[^a-zA-Z0-9_]", "_", dir_name)
|
|
123
|
-
|
|
124
|
-
# Ensure unique directory name
|
|
125
|
-
counter = 1
|
|
126
|
-
base_dir_name = dir_name
|
|
127
|
-
dest_context_dir = os.path.join(temp_dir, dir_name)
|
|
128
|
-
while os.path.exists(dest_context_dir):
|
|
129
|
-
dir_name = f"{base_dir_name}_{counter}"
|
|
130
|
-
dest_context_dir = os.path.join(temp_dir, dir_name)
|
|
131
|
-
counter += 1
|
|
132
|
-
|
|
133
|
-
# Copy entire directory structure
|
|
134
|
-
# Exclude common non-essential directories
|
|
135
|
-
ignore_patterns = shutil.ignore_patterns(
|
|
136
|
-
"__pycache__",
|
|
137
|
-
"*.pyc",
|
|
138
|
-
"*.pyo",
|
|
139
|
-
".git",
|
|
140
|
-
".gitignore",
|
|
141
|
-
".pytest_cache",
|
|
142
|
-
".mypy_cache",
|
|
143
|
-
".tox",
|
|
144
|
-
"venv",
|
|
145
|
-
"env",
|
|
146
|
-
".venv",
|
|
147
|
-
".env",
|
|
148
|
-
"node_modules",
|
|
149
|
-
".DS_Store",
|
|
150
|
-
"*.egg-info",
|
|
151
|
-
"build",
|
|
152
|
-
"dist",
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
shutil.copytree(
|
|
156
|
-
handler_dir,
|
|
157
|
-
dest_context_dir,
|
|
158
|
-
ignore=ignore_patterns,
|
|
159
|
-
dirs_exist_ok=True,
|
|
160
|
-
)
|
|
161
|
-
|
|
162
|
-
handler_dirs_copied.add(handler_dir)
|
|
163
|
-
copied_dir_names.append(dir_name)
|
|
164
|
-
else:
|
|
165
|
-
# Find the existing copied directory name
|
|
166
|
-
for existing_dir in os.listdir(temp_dir):
|
|
167
|
-
existing_path = os.path.join(temp_dir, existing_dir)
|
|
168
|
-
if os.path.isdir(existing_path):
|
|
169
|
-
# Check if this is the directory we already copied
|
|
170
|
-
original_handler_basename = os.path.basename(
|
|
171
|
-
handler_dir,
|
|
172
|
-
)
|
|
173
|
-
if existing_dir.startswith(
|
|
174
|
-
re.sub(
|
|
175
|
-
r"[^a-zA-Z0-9_]",
|
|
176
|
-
"_",
|
|
177
|
-
original_handler_basename,
|
|
178
|
-
),
|
|
179
|
-
):
|
|
180
|
-
dir_name = existing_dir
|
|
181
|
-
break
|
|
182
|
-
else:
|
|
183
|
-
# Fallback if not found
|
|
184
|
-
dir_name = re.sub(
|
|
185
|
-
r"[^a-zA-Z0-9_]",
|
|
186
|
-
"_",
|
|
187
|
-
os.path.basename(handler_dir),
|
|
188
|
-
)
|
|
189
|
-
|
|
190
|
-
# Calculate the module path relative to the handler directory
|
|
191
|
-
handler_file_rel = os.path.relpath(handler_file, handler_dir)
|
|
192
|
-
# Convert file path to module path
|
|
193
|
-
module_parts = os.path.splitext(handler_file_rel)[0].split(
|
|
194
|
-
os.sep,
|
|
195
|
-
)
|
|
196
|
-
if module_parts[-1] == "__init__":
|
|
197
|
-
module_parts = module_parts[:-1]
|
|
198
|
-
|
|
199
|
-
# Construct the full import path
|
|
200
|
-
if module_parts:
|
|
201
|
-
module_path = f"{dir_name}.{'.'.join(module_parts)}"
|
|
202
|
-
else:
|
|
203
|
-
module_path = dir_name
|
|
204
|
-
|
|
205
|
-
# Set the module and function name for template
|
|
206
|
-
prepared_endpoint["handler_module"] = module_path
|
|
207
|
-
prepared_endpoint["function_name"] = handler_name
|
|
208
|
-
|
|
209
|
-
except (OSError, TypeError, ValueError) as e:
|
|
210
|
-
# If source file extraction fails, try module/function_name
|
|
211
|
-
import traceback
|
|
212
|
-
|
|
213
|
-
print(f"Warning: Failed to copy handler directory: {e}")
|
|
214
|
-
traceback.print_exc()
|
|
215
|
-
|
|
216
|
-
# Add inline code if no handler module/function available
|
|
217
|
-
if not prepared_endpoint.get("handler_module") and (
|
|
218
|
-
not prepared_endpoint["module"]
|
|
219
|
-
or not prepared_endpoint["function_name"]
|
|
220
|
-
):
|
|
221
|
-
prepared_endpoint["inline_code"] = endpoint.get(
|
|
222
|
-
"inline_code",
|
|
223
|
-
'lambda request: {"error": "Handler not available"}',
|
|
224
|
-
)
|
|
225
|
-
|
|
226
|
-
prepared_endpoints.append(prepared_endpoint)
|
|
227
|
-
|
|
228
|
-
return prepared_endpoints, copied_dir_names
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
class PackageConfig(BaseModel):
|
|
232
|
-
"""Configuration for project packaging"""
|
|
233
|
-
|
|
234
|
-
requirements: Optional[List[str]] = None
|
|
235
|
-
extra_packages: Optional[List[str]] = None
|
|
236
|
-
output_dir: Optional[str] = None
|
|
237
|
-
endpoint_path: Optional[str] = "/process"
|
|
238
|
-
deployment_mode: Optional[str] = "standalone" # New: deployment mode
|
|
239
|
-
services_config: Optional[
|
|
240
|
-
ServicesConfig
|
|
241
|
-
] = None # New: services configuration
|
|
242
|
-
protocol_adapters: Optional[List[Any]] = None # New: protocol adapters
|
|
243
|
-
custom_endpoints: Optional[
|
|
244
|
-
List[Dict]
|
|
245
|
-
] = None # New: custom endpoints configuration
|
|
246
|
-
# Celery configuration parameters
|
|
247
|
-
broker_url: Optional[str] = None
|
|
248
|
-
backend_url: Optional[str] = None
|
|
249
|
-
enable_embedded_worker: bool = False
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
def _find_agent_source_file(
|
|
253
|
-
agent_obj: Any,
|
|
254
|
-
agent_name: str,
|
|
255
|
-
caller_frame,
|
|
256
|
-
) -> str:
|
|
257
|
-
"""
|
|
258
|
-
Find the file that contains the agent instance definition (where the
|
|
259
|
-
agent variable is assigned).
|
|
260
|
-
This prioritizes finding where the agent instance was created rather
|
|
261
|
-
than where the class is defined.
|
|
262
|
-
"""
|
|
263
|
-
|
|
264
|
-
# Method 1: Search through the call stack to find where the agent
|
|
265
|
-
# instance was defined
|
|
266
|
-
frame = caller_frame
|
|
267
|
-
found_files = [] # Store potential files for analysis
|
|
268
|
-
agent_names_in_frames = [] # Store agent names found in each frame
|
|
269
|
-
|
|
270
|
-
while frame:
|
|
271
|
-
try:
|
|
272
|
-
frame_filename = frame.f_code.co_filename
|
|
273
|
-
|
|
274
|
-
# Skip internal/system files and focus on user code
|
|
275
|
-
if (
|
|
276
|
-
not frame_filename.endswith(".py")
|
|
277
|
-
or "site-packages" in frame_filename
|
|
278
|
-
):
|
|
279
|
-
frame = frame.f_back
|
|
280
|
-
continue
|
|
281
|
-
|
|
282
|
-
# Check if this frame contains our agent variable
|
|
283
|
-
frame_locals = frame.f_locals
|
|
284
|
-
frame_globals = frame.f_globals
|
|
285
|
-
|
|
286
|
-
# Look for the agent object (by identity, not name) in both
|
|
287
|
-
# locals and globals
|
|
288
|
-
found_agent_name = None
|
|
289
|
-
for var_name, var_value in frame_locals.items():
|
|
290
|
-
if var_value is agent_obj:
|
|
291
|
-
found_agent_name = var_name
|
|
292
|
-
break
|
|
293
|
-
|
|
294
|
-
if not found_agent_name:
|
|
295
|
-
for var_name, var_value in frame_globals.items():
|
|
296
|
-
if var_value is agent_obj:
|
|
297
|
-
found_agent_name = var_name
|
|
298
|
-
break
|
|
299
|
-
|
|
300
|
-
if found_agent_name:
|
|
301
|
-
# Found the frame where this agent instance exists
|
|
302
|
-
found_files.append(frame_filename)
|
|
303
|
-
agent_names_in_frames.append(found_agent_name)
|
|
304
|
-
|
|
305
|
-
except (AttributeError, TypeError):
|
|
306
|
-
# Handle any errors in frame inspection
|
|
307
|
-
pass
|
|
308
|
-
|
|
309
|
-
frame = frame.f_back
|
|
310
|
-
|
|
311
|
-
# Method 2: Analyze found files to determine which one contains the
|
|
312
|
-
# actual instance definition
|
|
313
|
-
# Reverse the order to prioritize files found later in the stack (
|
|
314
|
-
# typically user code)
|
|
315
|
-
for i, file_path in enumerate(reversed(found_files)):
|
|
316
|
-
# Get the corresponding agent name for this file
|
|
317
|
-
agent_name_in_file = agent_names_in_frames[len(found_files) - 1 - i]
|
|
318
|
-
|
|
319
|
-
try:
|
|
320
|
-
with open(file_path, "r", encoding="utf-8") as f:
|
|
321
|
-
content = f.read()
|
|
322
|
-
|
|
323
|
-
# Check if this file contains an import statement for the agent
|
|
324
|
-
# If so, we should look for the original source file
|
|
325
|
-
import_patterns = [
|
|
326
|
-
rf"^[^\#]*from\s+(\w+)\s+import\s+.*"
|
|
327
|
-
rf"{re.escape(agent_name_in_file)}",
|
|
328
|
-
rf"^[^\#]*from\s+([\w.]+)\s+import\s+.*"
|
|
329
|
-
rf"{re.escape(agent_name_in_file)}",
|
|
330
|
-
]
|
|
331
|
-
|
|
332
|
-
# Check if this file imports the agent from another module
|
|
333
|
-
lines = content.split("\n")
|
|
334
|
-
for line in lines:
|
|
335
|
-
for import_pattern in import_patterns:
|
|
336
|
-
match = re.search(import_pattern, line)
|
|
337
|
-
if match:
|
|
338
|
-
module_name = match.group(1)
|
|
339
|
-
# Try to find the source module file
|
|
340
|
-
current_dir = os.path.dirname(file_path)
|
|
341
|
-
|
|
342
|
-
# Convert dotted module name to filesystem path
|
|
343
|
-
module_path = module_name.replace(".", os.sep)
|
|
344
|
-
|
|
345
|
-
# Try different possible paths for the source module
|
|
346
|
-
possible_paths = [
|
|
347
|
-
# Same directory - simple module name
|
|
348
|
-
os.path.join(
|
|
349
|
-
current_dir,
|
|
350
|
-
f"{module_name}.py",
|
|
351
|
-
),
|
|
352
|
-
# Same directory - dotted path
|
|
353
|
-
os.path.join(
|
|
354
|
-
current_dir,
|
|
355
|
-
f"{module_path}.py",
|
|
356
|
-
),
|
|
357
|
-
# Package in same directory
|
|
358
|
-
os.path.join(
|
|
359
|
-
current_dir,
|
|
360
|
-
module_name,
|
|
361
|
-
"__init__.py",
|
|
362
|
-
),
|
|
363
|
-
# Package with dotted path
|
|
364
|
-
os.path.join(
|
|
365
|
-
current_dir,
|
|
366
|
-
module_path,
|
|
367
|
-
"__init__.py",
|
|
368
|
-
),
|
|
369
|
-
# Parent directory - simple module name
|
|
370
|
-
os.path.join(
|
|
371
|
-
os.path.dirname(current_dir),
|
|
372
|
-
f"{module_name}.py",
|
|
373
|
-
),
|
|
374
|
-
# Parent directory - dotted path
|
|
375
|
-
os.path.join(
|
|
376
|
-
os.path.dirname(current_dir),
|
|
377
|
-
f"{module_path}.py",
|
|
378
|
-
),
|
|
379
|
-
# Current working directory - simple module name
|
|
380
|
-
os.path.join(
|
|
381
|
-
os.getcwd(),
|
|
382
|
-
f"{module_name}.py",
|
|
383
|
-
),
|
|
384
|
-
# Current working directory - dotted path
|
|
385
|
-
os.path.join(
|
|
386
|
-
os.getcwd(),
|
|
387
|
-
f"{module_path}.py",
|
|
388
|
-
),
|
|
389
|
-
# Current working directory - package
|
|
390
|
-
os.path.join(
|
|
391
|
-
os.getcwd(),
|
|
392
|
-
module_path,
|
|
393
|
-
"__init__.py",
|
|
394
|
-
),
|
|
395
|
-
]
|
|
396
|
-
|
|
397
|
-
for source_path in possible_paths:
|
|
398
|
-
if os.path.exists(source_path):
|
|
399
|
-
# Check if this source file contains the
|
|
400
|
-
# actual assignment
|
|
401
|
-
try:
|
|
402
|
-
with open(
|
|
403
|
-
source_path,
|
|
404
|
-
"r",
|
|
405
|
-
encoding="utf-8",
|
|
406
|
-
) as src_f:
|
|
407
|
-
src_content = src_f.read()
|
|
408
|
-
|
|
409
|
-
# Look for the assignment in the source
|
|
410
|
-
# file
|
|
411
|
-
assignment_patterns = [
|
|
412
|
-
rf"^[^\#]*{re.escape(agent_name_in_file)}" # noqa E501
|
|
413
|
-
rf"\s*=\s*\w+\(",
|
|
414
|
-
rf"^[^\#]*{re.escape(agent_name_in_file)}" # noqa E501
|
|
415
|
-
rf"\s*=\s*[\w.]+\(",
|
|
416
|
-
]
|
|
417
|
-
|
|
418
|
-
src_lines = src_content.split("\n")
|
|
419
|
-
for src_line in src_lines:
|
|
420
|
-
if (
|
|
421
|
-
not src_line.strip()
|
|
422
|
-
or src_line.strip().startswith("#")
|
|
423
|
-
or src_line.strip().startswith(
|
|
424
|
-
"def ",
|
|
425
|
-
)
|
|
426
|
-
or src_line.strip().startswith(
|
|
427
|
-
"from ",
|
|
428
|
-
)
|
|
429
|
-
or src_line.strip().startswith(
|
|
430
|
-
"import ",
|
|
431
|
-
)
|
|
432
|
-
or src_line.strip().startswith(
|
|
433
|
-
"class ",
|
|
434
|
-
)
|
|
435
|
-
):
|
|
436
|
-
continue
|
|
437
|
-
|
|
438
|
-
for (
|
|
439
|
-
assign_pattern
|
|
440
|
-
) in assignment_patterns:
|
|
441
|
-
if re.search(
|
|
442
|
-
assign_pattern,
|
|
443
|
-
src_line,
|
|
444
|
-
):
|
|
445
|
-
if "=" in src_line:
|
|
446
|
-
left_side = src_line.split(
|
|
447
|
-
"=",
|
|
448
|
-
)[0]
|
|
449
|
-
if (
|
|
450
|
-
agent_name_in_file
|
|
451
|
-
in left_side
|
|
452
|
-
and "("
|
|
453
|
-
not in left_side
|
|
454
|
-
):
|
|
455
|
-
indent_level = len(
|
|
456
|
-
src_line,
|
|
457
|
-
) - len(
|
|
458
|
-
src_line.lstrip(),
|
|
459
|
-
)
|
|
460
|
-
if indent_level <= 4:
|
|
461
|
-
return source_path
|
|
462
|
-
|
|
463
|
-
except (OSError, UnicodeDecodeError):
|
|
464
|
-
continue
|
|
465
|
-
break # Found import, no need to check other patterns
|
|
466
|
-
|
|
467
|
-
# If no import found, check if this file itself contains the
|
|
468
|
-
# assignment
|
|
469
|
-
assignment_patterns = [
|
|
470
|
-
# direct assignment: agent_name = ClassName(
|
|
471
|
-
rf"^[^\#]*{re.escape(agent_name_in_file)}\s*=\s*\w+\(",
|
|
472
|
-
# module assignment: agent_name = module.ClassName(
|
|
473
|
-
rf"^[^\#]*{re.escape(agent_name_in_file)}\s*=\s*[\w.]+\(",
|
|
474
|
-
]
|
|
475
|
-
|
|
476
|
-
# Look for actual variable assignment (not function parameters
|
|
477
|
-
# or imports)
|
|
478
|
-
for line_num, line in enumerate(lines):
|
|
479
|
-
stripped_line = line.strip()
|
|
480
|
-
# Skip comments, empty lines, function definitions, and imports
|
|
481
|
-
if (
|
|
482
|
-
not stripped_line
|
|
483
|
-
or stripped_line.startswith("#")
|
|
484
|
-
or stripped_line.startswith("def ")
|
|
485
|
-
or stripped_line.startswith("from ")
|
|
486
|
-
or stripped_line.startswith("import ")
|
|
487
|
-
or stripped_line.startswith("class ")
|
|
488
|
-
):
|
|
489
|
-
continue
|
|
490
|
-
|
|
491
|
-
# Check if this line contains the agent assignment
|
|
492
|
-
for pattern in assignment_patterns:
|
|
493
|
-
if re.search(pattern, line):
|
|
494
|
-
# Double check that this is a real assignment,
|
|
495
|
-
# not inside function parameters by checking if the
|
|
496
|
-
# line has '=' and the agent_name is on the left side
|
|
497
|
-
if "=" in line:
|
|
498
|
-
left_side = line.split("=")[0]
|
|
499
|
-
if (
|
|
500
|
-
agent_name_in_file in left_side
|
|
501
|
-
and "(" not in left_side
|
|
502
|
-
):
|
|
503
|
-
# Additional context check: make sure this
|
|
504
|
-
# is not indented too much (likely inside a
|
|
505
|
-
# function if heavily indented)
|
|
506
|
-
indent_level = len(line) - len(line.lstrip())
|
|
507
|
-
if (
|
|
508
|
-
indent_level <= 4
|
|
509
|
-
): # Top level or minimal indentation
|
|
510
|
-
return file_path
|
|
511
|
-
|
|
512
|
-
except (OSError, UnicodeDecodeError):
|
|
513
|
-
# If we can't read the file, continue to next file
|
|
514
|
-
continue
|
|
515
|
-
|
|
516
|
-
# Method 3: If no assignment pattern found, return the first found file
|
|
517
|
-
if found_files:
|
|
518
|
-
return found_files[0]
|
|
519
|
-
|
|
520
|
-
# Method 4: Fall back to original caller-based approach if stack search
|
|
521
|
-
# fails
|
|
522
|
-
caller_filename = caller_frame.f_code.co_filename
|
|
523
|
-
caller_dir = os.path.dirname(caller_filename)
|
|
524
|
-
|
|
525
|
-
# Check if we have the import information in the caller's globals
|
|
526
|
-
# Look for module objects that might contain the agent
|
|
527
|
-
for var_name, var_obj in caller_frame.f_globals.items():
|
|
528
|
-
if hasattr(var_obj, "__file__") and hasattr(var_obj, agent_name):
|
|
529
|
-
# This looks like a module that contains our agent
|
|
530
|
-
if getattr(var_obj, agent_name, None) is caller_frame.f_locals.get(
|
|
531
|
-
agent_name,
|
|
532
|
-
):
|
|
533
|
-
return var_obj.__file__
|
|
534
|
-
|
|
535
|
-
# If direct lookup failed, try to parse the import statements
|
|
536
|
-
try:
|
|
537
|
-
with open(caller_filename, "r", encoding="utf-8") as f:
|
|
538
|
-
content = f.read()
|
|
539
|
-
|
|
540
|
-
tree = ast.parse(content)
|
|
541
|
-
|
|
542
|
-
for node in ast.walk(tree):
|
|
543
|
-
if isinstance(node, ast.ImportFrom):
|
|
544
|
-
# Look for "from module_name import agent_name"
|
|
545
|
-
if node.names and node.module:
|
|
546
|
-
for alias in node.names:
|
|
547
|
-
imported_name = (
|
|
548
|
-
alias.asname if alias.asname else alias.name
|
|
549
|
-
)
|
|
550
|
-
if imported_name == agent_name:
|
|
551
|
-
# Found the import statement
|
|
552
|
-
module_path = os.path.join(
|
|
553
|
-
caller_dir,
|
|
554
|
-
f"{node.module}.py",
|
|
555
|
-
)
|
|
556
|
-
if os.path.exists(module_path):
|
|
557
|
-
return module_path
|
|
558
|
-
# Try relative import
|
|
559
|
-
if node.level > 0: # relative import
|
|
560
|
-
parent_path = caller_dir
|
|
561
|
-
for _ in range(node.level - 1):
|
|
562
|
-
parent_path = os.path.dirname(parent_path)
|
|
563
|
-
module_path = os.path.join(
|
|
564
|
-
parent_path,
|
|
565
|
-
f"{node.module}.py",
|
|
566
|
-
)
|
|
567
|
-
if os.path.exists(module_path):
|
|
568
|
-
return module_path
|
|
569
|
-
|
|
570
|
-
elif isinstance(node, ast.Import):
|
|
571
|
-
# Look for "import module_name" where agent might be
|
|
572
|
-
# module_name.agent_name
|
|
573
|
-
for alias in node.names:
|
|
574
|
-
module_name = alias.asname if alias.asname else alias.name
|
|
575
|
-
if module_name in caller_frame.f_globals:
|
|
576
|
-
module_obj = caller_frame.f_globals[module_name]
|
|
577
|
-
if hasattr(module_obj, "__file__") and hasattr(
|
|
578
|
-
module_obj,
|
|
579
|
-
agent_name,
|
|
580
|
-
):
|
|
581
|
-
return module_obj.__file__
|
|
582
|
-
|
|
583
|
-
except Exception as e:
|
|
584
|
-
# If parsing fails, we'll fall back to the caller file
|
|
585
|
-
print(e)
|
|
586
|
-
|
|
587
|
-
return caller_filename
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
def _extract_agent_name_from_source(
|
|
591
|
-
agent_file_path: str,
|
|
592
|
-
agent_obj: Any,
|
|
593
|
-
) -> str:
|
|
594
|
-
"""
|
|
595
|
-
Extract the actual variable name of the agent from the source file by
|
|
596
|
-
looking for variable assignments and trying to match the object type.
|
|
597
|
-
|
|
598
|
-
Args:
|
|
599
|
-
agent_file_path: Path to the source file containing agent definition
|
|
600
|
-
agent_obj: The agent object to match
|
|
601
|
-
|
|
602
|
-
Returns:
|
|
603
|
-
str: The variable name used in the source file, or "agent" as fallback
|
|
604
|
-
"""
|
|
605
|
-
try:
|
|
606
|
-
with open(agent_file_path, "r", encoding="utf-8") as f:
|
|
607
|
-
content = f.read()
|
|
608
|
-
|
|
609
|
-
# Get the class name of the agent object
|
|
610
|
-
agent_class_name = agent_obj.__class__.__name__
|
|
611
|
-
|
|
612
|
-
lines = content.split("\n")
|
|
613
|
-
potential_names = []
|
|
614
|
-
|
|
615
|
-
for line in lines:
|
|
616
|
-
stripped_line = line.strip()
|
|
617
|
-
# Skip comments, empty lines, function definitions, and imports
|
|
618
|
-
if (
|
|
619
|
-
not stripped_line
|
|
620
|
-
or stripped_line.startswith("#")
|
|
621
|
-
or stripped_line.startswith("def ")
|
|
622
|
-
or stripped_line.startswith("from ")
|
|
623
|
-
or stripped_line.startswith("import ")
|
|
624
|
-
or stripped_line.startswith("class ")
|
|
625
|
-
):
|
|
626
|
-
continue
|
|
627
|
-
|
|
628
|
-
# Look for variable assignment patterns: var_name = ...
|
|
629
|
-
if "=" in line:
|
|
630
|
-
left_side = line.split("=")[0].strip()
|
|
631
|
-
right_side = line.split("=", 1)[1].strip()
|
|
632
|
-
|
|
633
|
-
# Make sure it's a simple variable assignment (not inside
|
|
634
|
-
# parentheses or functions)
|
|
635
|
-
if (
|
|
636
|
-
left_side
|
|
637
|
-
and "(" not in left_side
|
|
638
|
-
and left_side.isidentifier()
|
|
639
|
-
and not left_side.startswith("_")
|
|
640
|
-
): # Skip private variables
|
|
641
|
-
# Check indentation level - should be top level or
|
|
642
|
-
# minimal indentation
|
|
643
|
-
indent_level = len(line) - len(line.lstrip())
|
|
644
|
-
if indent_level <= 4: # Top level or minimal indentation
|
|
645
|
-
# Check if the right side contains the agent class name
|
|
646
|
-
if agent_class_name in right_side:
|
|
647
|
-
# This is likely our agent assignment
|
|
648
|
-
potential_names.insert(0, left_side)
|
|
649
|
-
# # Also check for assignments that might create the
|
|
650
|
-
# agent through constructor calls
|
|
651
|
-
# elif "(" in right_side:
|
|
652
|
-
# potential_names.append(left_side)
|
|
653
|
-
|
|
654
|
-
# Return the first potential name found (prioritizing class name
|
|
655
|
-
# matches)
|
|
656
|
-
if potential_names:
|
|
657
|
-
return potential_names[0]
|
|
658
|
-
|
|
659
|
-
except (OSError, UnicodeDecodeError):
|
|
660
|
-
pass
|
|
661
|
-
|
|
662
|
-
return "agent" # fallback
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
def _calculate_directory_hash(directory_path: str) -> str:
|
|
666
|
-
"""
|
|
667
|
-
Calculate a hash representing the entire contents of a directory.
|
|
668
|
-
|
|
669
|
-
Args:
|
|
670
|
-
directory_path: Path to the directory to hash
|
|
671
|
-
|
|
672
|
-
Returns:
|
|
673
|
-
str: SHA256 hash of the directory contents
|
|
674
|
-
"""
|
|
675
|
-
hasher = hashlib.sha256()
|
|
676
|
-
|
|
677
|
-
if not os.path.exists(directory_path):
|
|
678
|
-
return ""
|
|
679
|
-
|
|
680
|
-
# Walk through directory and hash all file contents and paths
|
|
681
|
-
for root, dirs, files in sorted(os.walk(directory_path)):
|
|
682
|
-
# Sort to ensure consistent ordering
|
|
683
|
-
dirs.sort()
|
|
684
|
-
files.sort()
|
|
685
|
-
|
|
686
|
-
for filename in files:
|
|
687
|
-
file_path = os.path.join(root, filename)
|
|
688
|
-
|
|
689
|
-
# Hash the relative path
|
|
690
|
-
rel_path = os.path.relpath(file_path, directory_path)
|
|
691
|
-
hasher.update(rel_path.encode("utf-8"))
|
|
692
|
-
|
|
693
|
-
# Hash the file contents
|
|
694
|
-
try:
|
|
695
|
-
with open(file_path, "rb") as f:
|
|
696
|
-
for chunk in iter(lambda: f.read(4096), b""):
|
|
697
|
-
hasher.update(chunk)
|
|
698
|
-
except (OSError, IOError):
|
|
699
|
-
# Skip files that can't be read
|
|
700
|
-
continue
|
|
701
|
-
|
|
702
|
-
return hasher.hexdigest()
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
def _compare_directories(old_dir: str, new_dir: str) -> bool:
|
|
706
|
-
"""
|
|
707
|
-
Compare two directories to see if their contents are identical.
|
|
708
|
-
|
|
709
|
-
Args:
|
|
710
|
-
old_dir: Path to the old directory
|
|
711
|
-
new_dir: Path to the new directory
|
|
712
|
-
|
|
713
|
-
Returns:
|
|
714
|
-
bool: True if directories have identical contents, False otherwise
|
|
715
|
-
"""
|
|
716
|
-
old_hash = _calculate_directory_hash(old_dir)
|
|
717
|
-
new_hash = _calculate_directory_hash(new_dir)
|
|
718
|
-
|
|
719
|
-
return old_hash == new_hash and old_hash != ""
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
def package_project(
|
|
723
|
-
agent: Any,
|
|
724
|
-
config: PackageConfig,
|
|
725
|
-
dockerfile_path: Optional[str] = None,
|
|
726
|
-
template: Optional[str] = None, # Use template file by default
|
|
727
|
-
) -> Tuple[str, bool]:
|
|
728
|
-
"""
|
|
729
|
-
Package a project with agent and dependencies into a temporary directory.
|
|
730
|
-
|
|
731
|
-
Args:
|
|
732
|
-
agent: The agent object to be packaged
|
|
733
|
-
config: The configuration of the package
|
|
734
|
-
dockerfile_path: Path to the Docker file
|
|
735
|
-
template: User override template string
|
|
736
|
-
(if None, uses standalone template file)
|
|
737
|
-
|
|
738
|
-
Returns:
|
|
739
|
-
Tuple[str, bool]: A tuple containing:
|
|
740
|
-
- str: Path to the directory containing the packaged project
|
|
741
|
-
- bool: True if the directory was updated,
|
|
742
|
-
False if no update was needed
|
|
743
|
-
"""
|
|
744
|
-
# Create temporary directory
|
|
745
|
-
original_temp_dir = temp_dir = None
|
|
746
|
-
if config.output_dir is None:
|
|
747
|
-
temp_dir = tempfile.mkdtemp(prefix="agentscope_package_")
|
|
748
|
-
needs_update = True # New directory always needs update
|
|
749
|
-
else:
|
|
750
|
-
temp_dir = config.output_dir
|
|
751
|
-
# Check if directory exists and has content
|
|
752
|
-
if os.path.exists(temp_dir) and os.listdir(temp_dir):
|
|
753
|
-
# Directory exists and has content, create a temporary directory
|
|
754
|
-
# first to generate new content for comparison
|
|
755
|
-
original_temp_dir = temp_dir
|
|
756
|
-
temp_dir = tempfile.mkdtemp(prefix="agentscope_package_new_")
|
|
757
|
-
# copy docker file to this place
|
|
758
|
-
if dockerfile_path:
|
|
759
|
-
shutil.copy(
|
|
760
|
-
dockerfile_path,
|
|
761
|
-
os.path.join(
|
|
762
|
-
temp_dir,
|
|
763
|
-
"Dockerfile",
|
|
764
|
-
),
|
|
765
|
-
)
|
|
766
|
-
needs_update = None # Will be determined after comparison
|
|
767
|
-
else:
|
|
768
|
-
# Directory doesn't exist or is empty, needs update
|
|
769
|
-
needs_update = True
|
|
770
|
-
# Create directory if it doesn't exist
|
|
771
|
-
if not os.path.exists(temp_dir):
|
|
772
|
-
os.makedirs(temp_dir)
|
|
773
|
-
|
|
774
|
-
try:
|
|
775
|
-
# Extract agent variable name from the caller's frame
|
|
776
|
-
frame = inspect.currentframe()
|
|
777
|
-
caller_frame = frame.f_back
|
|
778
|
-
agent_name = None
|
|
779
|
-
|
|
780
|
-
# Look for the agent variable name in caller's locals and globals
|
|
781
|
-
for var_name, var_value in caller_frame.f_locals.items():
|
|
782
|
-
if var_value is agent:
|
|
783
|
-
agent_name = var_name
|
|
784
|
-
break
|
|
785
|
-
|
|
786
|
-
if not agent_name:
|
|
787
|
-
for var_name, var_value in caller_frame.f_globals.items():
|
|
788
|
-
if var_value is agent:
|
|
789
|
-
agent_name = var_name
|
|
790
|
-
break
|
|
791
|
-
|
|
792
|
-
if not agent_name:
|
|
793
|
-
agent_name = "agent" # fallback name
|
|
794
|
-
|
|
795
|
-
# Find the source file for the agent
|
|
796
|
-
agent_file_path = _find_agent_source_file(
|
|
797
|
-
agent,
|
|
798
|
-
agent_name,
|
|
799
|
-
caller_frame,
|
|
800
|
-
)
|
|
801
|
-
|
|
802
|
-
if not os.path.exists(agent_file_path):
|
|
803
|
-
raise ValueError(
|
|
804
|
-
f"Unable to locate agent source file: {agent_file_path}",
|
|
805
|
-
)
|
|
806
|
-
|
|
807
|
-
# Extract the actual agent variable name from the source file
|
|
808
|
-
actual_agent_name = _extract_agent_name_from_source(
|
|
809
|
-
agent_file_path,
|
|
810
|
-
agent,
|
|
811
|
-
)
|
|
812
|
-
|
|
813
|
-
# Use the actual name from source file for the template
|
|
814
|
-
agent_name = actual_agent_name
|
|
815
|
-
|
|
816
|
-
# Copy agent file to temp directory as agent_file.py
|
|
817
|
-
agent_dest_path = os.path.join(temp_dir, "agent_file.py")
|
|
818
|
-
shutil.copy2(agent_file_path, agent_dest_path)
|
|
819
|
-
|
|
820
|
-
# Copy extra package files
|
|
821
|
-
if config.extra_packages:
|
|
822
|
-
# Get the base directory from the agent_file_path for relative path
|
|
823
|
-
# calculation
|
|
824
|
-
caller_dir = os.path.dirname(agent_file_path)
|
|
825
|
-
|
|
826
|
-
for extra_path in config.extra_packages:
|
|
827
|
-
if os.path.isfile(extra_path):
|
|
828
|
-
# Calculate relative path from caller directory
|
|
829
|
-
if os.path.isabs(extra_path):
|
|
830
|
-
try:
|
|
831
|
-
# Try to get relative path from caller directory
|
|
832
|
-
rel_path = os.path.relpath(extra_path, caller_dir)
|
|
833
|
-
# If the relative path goes up beyond the caller
|
|
834
|
-
# directory, just use filename
|
|
835
|
-
if rel_path.startswith(".."):
|
|
836
|
-
dest_path = os.path.join(
|
|
837
|
-
temp_dir,
|
|
838
|
-
os.path.basename(extra_path),
|
|
839
|
-
)
|
|
840
|
-
else:
|
|
841
|
-
dest_path = os.path.join(temp_dir, rel_path)
|
|
842
|
-
except ValueError:
|
|
843
|
-
# If relative path calculation fails (e.g.,
|
|
844
|
-
# different drives on Windows)
|
|
845
|
-
dest_path = os.path.join(
|
|
846
|
-
temp_dir,
|
|
847
|
-
os.path.basename(extra_path),
|
|
848
|
-
)
|
|
849
|
-
else:
|
|
850
|
-
# If it's already a relative path, use it as is
|
|
851
|
-
dest_path = os.path.join(temp_dir, extra_path)
|
|
852
|
-
|
|
853
|
-
# Create destination directory if it doesn't exist
|
|
854
|
-
dest_dir = os.path.dirname(dest_path)
|
|
855
|
-
if dest_dir and not os.path.exists(dest_dir):
|
|
856
|
-
os.makedirs(dest_dir)
|
|
857
|
-
|
|
858
|
-
# Copy file to destination
|
|
859
|
-
shutil.copy2(extra_path, dest_path)
|
|
860
|
-
|
|
861
|
-
elif os.path.isdir(extra_path):
|
|
862
|
-
# Calculate relative path for directory
|
|
863
|
-
if os.path.isabs(extra_path):
|
|
864
|
-
try:
|
|
865
|
-
rel_path = os.path.relpath(extra_path, caller_dir)
|
|
866
|
-
if rel_path.startswith(".."):
|
|
867
|
-
dest_path = os.path.join(
|
|
868
|
-
temp_dir,
|
|
869
|
-
os.path.basename(extra_path),
|
|
870
|
-
)
|
|
871
|
-
else:
|
|
872
|
-
dest_path = os.path.join(temp_dir, rel_path)
|
|
873
|
-
except ValueError:
|
|
874
|
-
dest_path = os.path.join(
|
|
875
|
-
temp_dir,
|
|
876
|
-
os.path.basename(extra_path),
|
|
877
|
-
)
|
|
878
|
-
else:
|
|
879
|
-
dest_path = os.path.join(temp_dir, extra_path)
|
|
880
|
-
|
|
881
|
-
# Copy directory to destination
|
|
882
|
-
shutil.copytree(extra_path, dest_path, dirs_exist_ok=True)
|
|
883
|
-
|
|
884
|
-
# Use template manager for better template handling
|
|
885
|
-
template_manager = FastAPITemplateManager()
|
|
886
|
-
|
|
887
|
-
# Convert protocol_adapters to string representation for template
|
|
888
|
-
protocol_adapters_str = None
|
|
889
|
-
if config.protocol_adapters:
|
|
890
|
-
# For standalone deployment, we need to generate code that
|
|
891
|
-
# creates the adapters
|
|
892
|
-
# This is a simplified approach - in practice, you might want
|
|
893
|
-
# more sophisticated serialization
|
|
894
|
-
adapter_imports = []
|
|
895
|
-
adapter_instances = []
|
|
896
|
-
for i, adapter in enumerate(config.protocol_adapters):
|
|
897
|
-
adapter_class = adapter.__class__
|
|
898
|
-
adapter_module = adapter_class.__module__
|
|
899
|
-
adapter_name = adapter_class.__name__
|
|
900
|
-
|
|
901
|
-
# Add import
|
|
902
|
-
adapter_imports.append(
|
|
903
|
-
f"from {adapter_module} import {adapter_name}",
|
|
904
|
-
)
|
|
905
|
-
|
|
906
|
-
# Add instance creation (simplified - doesn't handle
|
|
907
|
-
# complex constructor args)
|
|
908
|
-
adapter_instances.append(f"{adapter_name}(agent=agent)")
|
|
909
|
-
|
|
910
|
-
# Create the protocol_adapters array string
|
|
911
|
-
if adapter_instances:
|
|
912
|
-
imports_str = "\n".join(adapter_imports)
|
|
913
|
-
instances_str = "[" + ", ".join(adapter_instances) + "]"
|
|
914
|
-
protocol_adapters_str = (
|
|
915
|
-
f"# Protocol adapter imports\n{imports_str}\n\n"
|
|
916
|
-
f"# Protocol adapters\nprotocol_adapters = {instances_str}"
|
|
917
|
-
)
|
|
918
|
-
|
|
919
|
-
# Convert celery_config to string representation for template
|
|
920
|
-
celery_config_str = None
|
|
921
|
-
config_lines = []
|
|
922
|
-
|
|
923
|
-
# Generate celery configuration code
|
|
924
|
-
config_lines.append("# Celery configuration")
|
|
925
|
-
|
|
926
|
-
if config.broker_url:
|
|
927
|
-
config_lines.append(
|
|
928
|
-
f'celery_config["broker_url"] = "{config.broker_url}"',
|
|
929
|
-
)
|
|
930
|
-
|
|
931
|
-
if config.backend_url:
|
|
932
|
-
config_lines.append(
|
|
933
|
-
f'celery_config["backend_url"] = "{config.backend_url}"',
|
|
934
|
-
)
|
|
935
|
-
|
|
936
|
-
if config.enable_embedded_worker:
|
|
937
|
-
config_lines.append(
|
|
938
|
-
f'celery_config["enable_embedded_worker"] = '
|
|
939
|
-
f"{config.enable_embedded_worker}",
|
|
940
|
-
)
|
|
941
|
-
|
|
942
|
-
if config_lines:
|
|
943
|
-
celery_config_str = "\n".join(config_lines)
|
|
944
|
-
|
|
945
|
-
# Prepare custom endpoints and get copied directory names
|
|
946
|
-
(
|
|
947
|
-
custom_endpoints_data,
|
|
948
|
-
handler_dirs,
|
|
949
|
-
) = _prepare_custom_endpoints_for_template(
|
|
950
|
-
config.custom_endpoints,
|
|
951
|
-
temp_dir,
|
|
952
|
-
)
|
|
953
|
-
|
|
954
|
-
# Render template - use template file by default,
|
|
955
|
-
# or user-provided string
|
|
956
|
-
if template is None:
|
|
957
|
-
# Use standalone template file
|
|
958
|
-
main_content = template_manager.render_standalone_template(
|
|
959
|
-
agent_name=agent_name,
|
|
960
|
-
endpoint_path=config.endpoint_path or "/process",
|
|
961
|
-
deployment_mode=config.deployment_mode or "standalone",
|
|
962
|
-
protocol_adapters=protocol_adapters_str,
|
|
963
|
-
celery_config=celery_config_str,
|
|
964
|
-
custom_endpoints=custom_endpoints_data,
|
|
965
|
-
handler_dirs=handler_dirs,
|
|
966
|
-
)
|
|
967
|
-
else:
|
|
968
|
-
# Use user-provided template string
|
|
969
|
-
main_content = template_manager.render_template_from_string(
|
|
970
|
-
template,
|
|
971
|
-
agent_name=agent_name,
|
|
972
|
-
endpoint_path=config.endpoint_path,
|
|
973
|
-
deployment_mode=config.deployment_mode or "standalone",
|
|
974
|
-
protocol_adapters=protocol_adapters_str,
|
|
975
|
-
celery_config=celery_config_str,
|
|
976
|
-
custom_endpoints=custom_endpoints_data,
|
|
977
|
-
handler_dirs=handler_dirs,
|
|
978
|
-
)
|
|
979
|
-
|
|
980
|
-
# Write main.py
|
|
981
|
-
main_file_path = os.path.join(temp_dir, "main.py")
|
|
982
|
-
with open(main_file_path, "w", encoding="utf-8") as f:
|
|
983
|
-
f.write(main_content)
|
|
984
|
-
|
|
985
|
-
# Generate requirements.txt with unified dependencies
|
|
986
|
-
requirements_path = os.path.join(temp_dir, "requirements.txt")
|
|
987
|
-
with open(requirements_path, "w", encoding="utf-8") as f:
|
|
988
|
-
# Get the current package version
|
|
989
|
-
package_version = _get_package_version()
|
|
990
|
-
|
|
991
|
-
# Add base requirements for the unified runtime
|
|
992
|
-
if package_version:
|
|
993
|
-
base_requirements = [
|
|
994
|
-
"fastapi",
|
|
995
|
-
"uvicorn",
|
|
996
|
-
f"agentscope-runtime=={package_version}",
|
|
997
|
-
f"agentscope-runtime[sandbox]=={package_version}",
|
|
998
|
-
f"agentscope-runtime[deployment]=={package_version}",
|
|
999
|
-
"pydantic",
|
|
1000
|
-
"jinja2", # For template rendering
|
|
1001
|
-
"psutil",
|
|
1002
|
-
"redis", # For process management
|
|
1003
|
-
]
|
|
1004
|
-
else:
|
|
1005
|
-
# Fallback to unversioned if version cannot be determined
|
|
1006
|
-
base_requirements = [
|
|
1007
|
-
"fastapi",
|
|
1008
|
-
"uvicorn",
|
|
1009
|
-
"agentscope-runtime",
|
|
1010
|
-
"agentscope-runtime[sandbox]",
|
|
1011
|
-
"agentscope-runtime[deployment]",
|
|
1012
|
-
"pydantic",
|
|
1013
|
-
"jinja2", # For template rendering
|
|
1014
|
-
"psutil", # For process management
|
|
1015
|
-
"redis", # For process management
|
|
1016
|
-
]
|
|
1017
|
-
if not config.requirements:
|
|
1018
|
-
config.requirements = []
|
|
1019
|
-
|
|
1020
|
-
# Add Celery requirements if Celery is configured
|
|
1021
|
-
celery_requirements = []
|
|
1022
|
-
if (
|
|
1023
|
-
config.broker_url
|
|
1024
|
-
or config.backend_url
|
|
1025
|
-
or config.enable_embedded_worker
|
|
1026
|
-
):
|
|
1027
|
-
celery_requirements = ["celery", "redis"]
|
|
1028
|
-
|
|
1029
|
-
# Combine base requirements with user requirements and Celery
|
|
1030
|
-
# requirements
|
|
1031
|
-
all_requirements = sorted(
|
|
1032
|
-
list(
|
|
1033
|
-
set(
|
|
1034
|
-
base_requirements
|
|
1035
|
-
+ config.requirements
|
|
1036
|
-
+ celery_requirements,
|
|
1037
|
-
),
|
|
1038
|
-
),
|
|
1039
|
-
)
|
|
1040
|
-
for req in all_requirements:
|
|
1041
|
-
f.write(f"{req}\n")
|
|
1042
|
-
|
|
1043
|
-
# Generate services configuration file if specified
|
|
1044
|
-
if config.services_config:
|
|
1045
|
-
config_path = os.path.join(temp_dir, "services_config.json")
|
|
1046
|
-
import json
|
|
1047
|
-
|
|
1048
|
-
with open(config_path, "w", encoding="utf-8") as f:
|
|
1049
|
-
json.dump(config.services_config.model_dump(), f, indent=2)
|
|
1050
|
-
|
|
1051
|
-
# If we need to determine if update is needed (existing directory case)
|
|
1052
|
-
if needs_update is None and original_temp_dir is not None:
|
|
1053
|
-
# Compare the original directory with the new content
|
|
1054
|
-
if _compare_directories(original_temp_dir, temp_dir):
|
|
1055
|
-
# Content is identical, no update needed
|
|
1056
|
-
needs_update = False
|
|
1057
|
-
# Clean up the temporary new directory and return
|
|
1058
|
-
# original directory
|
|
1059
|
-
if os.path.exists(temp_dir):
|
|
1060
|
-
shutil.rmtree(temp_dir)
|
|
1061
|
-
return original_temp_dir, needs_update
|
|
1062
|
-
else:
|
|
1063
|
-
# Content is different, update needed
|
|
1064
|
-
needs_update = True
|
|
1065
|
-
# Replace the content in the original directory
|
|
1066
|
-
# First, clear the original directory
|
|
1067
|
-
for item in os.listdir(original_temp_dir):
|
|
1068
|
-
item_path = os.path.join(original_temp_dir, item)
|
|
1069
|
-
if os.path.isdir(item_path):
|
|
1070
|
-
shutil.rmtree(item_path)
|
|
1071
|
-
else:
|
|
1072
|
-
os.remove(item_path)
|
|
1073
|
-
|
|
1074
|
-
# Copy new content to original directory
|
|
1075
|
-
for item in os.listdir(temp_dir):
|
|
1076
|
-
src_path = os.path.join(temp_dir, item)
|
|
1077
|
-
dst_path = os.path.join(original_temp_dir, item)
|
|
1078
|
-
if os.path.isdir(src_path):
|
|
1079
|
-
shutil.copytree(src_path, dst_path)
|
|
1080
|
-
else:
|
|
1081
|
-
shutil.copy2(src_path, dst_path)
|
|
1082
|
-
|
|
1083
|
-
# Clean up temporary directory
|
|
1084
|
-
if os.path.exists(temp_dir):
|
|
1085
|
-
shutil.rmtree(temp_dir)
|
|
1086
|
-
return original_temp_dir, needs_update
|
|
1087
|
-
|
|
1088
|
-
return temp_dir, needs_update or True
|
|
1089
|
-
|
|
1090
|
-
except Exception as e:
|
|
1091
|
-
# Clean up on error
|
|
1092
|
-
if os.path.exists(temp_dir) and temp_dir != config.output_dir:
|
|
1093
|
-
shutil.rmtree(temp_dir)
|
|
1094
|
-
# If we're using a temporary directory for comparison, clean it up
|
|
1095
|
-
if (
|
|
1096
|
-
original_temp_dir
|
|
1097
|
-
and temp_dir != original_temp_dir
|
|
1098
|
-
and os.path.exists(temp_dir)
|
|
1099
|
-
):
|
|
1100
|
-
shutil.rmtree(temp_dir)
|
|
1101
|
-
raise e
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
def create_tar_gz(
|
|
1105
|
-
directory_path: str,
|
|
1106
|
-
output_path: Optional[str] = None,
|
|
1107
|
-
) -> str:
|
|
1108
|
-
"""
|
|
1109
|
-
Package a directory into a tar.gz file.
|
|
1110
|
-
|
|
1111
|
-
Args:
|
|
1112
|
-
directory_path: Path to the directory to package
|
|
1113
|
-
output_path: Optional output path for the tar.gz file. If not provided,
|
|
1114
|
-
will create the tar.gz in the same parent directory as
|
|
1115
|
-
the source directory
|
|
1116
|
-
|
|
1117
|
-
Returns:
|
|
1118
|
-
str: Path to the created tar.gz file
|
|
1119
|
-
|
|
1120
|
-
Raises:
|
|
1121
|
-
ValueError: If the directory doesn't exist
|
|
1122
|
-
OSError: If there's an error creating the tar.gz file
|
|
1123
|
-
"""
|
|
1124
|
-
if not os.path.exists(directory_path):
|
|
1125
|
-
raise ValueError(f"Directory does not exist: {directory_path}")
|
|
1126
|
-
|
|
1127
|
-
if not os.path.isdir(directory_path):
|
|
1128
|
-
raise ValueError(f"Path is not a directory: {directory_path}")
|
|
1129
|
-
|
|
1130
|
-
# Generate output path if not provided
|
|
1131
|
-
if output_path is None:
|
|
1132
|
-
dir_name = os.path.basename(os.path.normpath(directory_path))
|
|
1133
|
-
parent_dir = os.path.dirname(directory_path)
|
|
1134
|
-
output_path = os.path.join(parent_dir, f"{dir_name}.tar.gz")
|
|
1135
|
-
|
|
1136
|
-
try:
|
|
1137
|
-
with tarfile.open(output_path, "w:gz") as tar:
|
|
1138
|
-
# Add all contents of the directory to the tar file
|
|
1139
|
-
for root, dirs, files in os.walk(directory_path):
|
|
1140
|
-
for file in files:
|
|
1141
|
-
file_path = os.path.join(root, file)
|
|
1142
|
-
# Calculate the archive name (relative to the source
|
|
1143
|
-
# directory)
|
|
1144
|
-
arcname = os.path.relpath(file_path, directory_path)
|
|
1145
|
-
tar.add(file_path, arcname=arcname)
|
|
1146
|
-
|
|
1147
|
-
# Also add empty directories
|
|
1148
|
-
for dir_name in dirs:
|
|
1149
|
-
dir_path = os.path.join(root, dir_name)
|
|
1150
|
-
if not os.listdir(dir_path): # Empty directory
|
|
1151
|
-
arcname = os.path.relpath(dir_path, directory_path)
|
|
1152
|
-
tar.add(dir_path, arcname=arcname)
|
|
1153
|
-
|
|
1154
|
-
return output_path
|
|
1155
|
-
|
|
1156
|
-
except Exception as e:
|
|
1157
|
-
# Clean up partial file if it exists
|
|
1158
|
-
if os.path.exists(output_path):
|
|
1159
|
-
try:
|
|
1160
|
-
os.remove(output_path)
|
|
1161
|
-
except OSError:
|
|
1162
|
-
pass
|
|
1163
|
-
raise OSError(f"Failed to create tar.gz file: {str(e)}") from e
|