agentscope-runtime 0.1.5b2__py3-none-any.whl → 0.2.0b2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. agentscope_runtime/common/__init__.py +0 -0
  2. agentscope_runtime/common/collections/in_memory_mapping.py +27 -0
  3. agentscope_runtime/common/collections/redis_mapping.py +42 -0
  4. agentscope_runtime/common/container_clients/__init__.py +0 -0
  5. agentscope_runtime/common/container_clients/agentrun_client.py +1098 -0
  6. agentscope_runtime/common/container_clients/docker_client.py +250 -0
  7. agentscope_runtime/{sandbox/manager → common}/container_clients/kubernetes_client.py +6 -13
  8. agentscope_runtime/engine/__init__.py +12 -0
  9. agentscope_runtime/engine/agents/agentscope_agent.py +488 -0
  10. agentscope_runtime/engine/agents/agno_agent.py +26 -27
  11. agentscope_runtime/engine/agents/autogen_agent.py +13 -8
  12. agentscope_runtime/engine/agents/utils.py +53 -0
  13. agentscope_runtime/engine/app/__init__.py +6 -0
  14. agentscope_runtime/engine/app/agent_app.py +239 -0
  15. agentscope_runtime/engine/app/base_app.py +181 -0
  16. agentscope_runtime/engine/app/celery_mixin.py +92 -0
  17. agentscope_runtime/engine/deployers/base.py +1 -0
  18. agentscope_runtime/engine/deployers/cli_fc_deploy.py +39 -20
  19. agentscope_runtime/engine/deployers/kubernetes_deployer.py +12 -5
  20. agentscope_runtime/engine/deployers/local_deployer.py +61 -3
  21. agentscope_runtime/engine/deployers/modelstudio_deployer.py +10 -11
  22. agentscope_runtime/engine/deployers/utils/docker_image_utils/runner_image_factory.py +9 -0
  23. agentscope_runtime/engine/deployers/utils/package_project_utils.py +234 -3
  24. agentscope_runtime/engine/deployers/utils/service_utils/fastapi_factory.py +567 -7
  25. agentscope_runtime/engine/deployers/utils/service_utils/standalone_main.py.j2 +211 -0
  26. agentscope_runtime/engine/deployers/utils/wheel_packager.py +1 -1
  27. agentscope_runtime/engine/helpers/helper.py +60 -41
  28. agentscope_runtime/engine/runner.py +35 -24
  29. agentscope_runtime/engine/schemas/agent_schemas.py +42 -0
  30. agentscope_runtime/engine/schemas/modelstudio_llm.py +14 -14
  31. agentscope_runtime/engine/services/sandbox_service.py +62 -70
  32. agentscope_runtime/engine/services/tablestore_memory_service.py +307 -0
  33. agentscope_runtime/engine/services/tablestore_rag_service.py +143 -0
  34. agentscope_runtime/engine/services/tablestore_session_history_service.py +293 -0
  35. agentscope_runtime/engine/services/utils/__init__.py +0 -0
  36. agentscope_runtime/engine/services/utils/tablestore_service_utils.py +352 -0
  37. agentscope_runtime/engine/tracing/__init__.py +9 -3
  38. agentscope_runtime/engine/tracing/asyncio_util.py +24 -0
  39. agentscope_runtime/engine/tracing/base.py +66 -34
  40. agentscope_runtime/engine/tracing/local_logging_handler.py +45 -31
  41. agentscope_runtime/engine/tracing/message_util.py +528 -0
  42. agentscope_runtime/engine/tracing/tracing_metric.py +20 -8
  43. agentscope_runtime/engine/tracing/tracing_util.py +130 -0
  44. agentscope_runtime/engine/tracing/wrapper.py +794 -169
  45. agentscope_runtime/sandbox/__init__.py +2 -0
  46. agentscope_runtime/sandbox/box/base/__init__.py +4 -0
  47. agentscope_runtime/sandbox/box/base/base_sandbox.py +6 -4
  48. agentscope_runtime/sandbox/box/browser/__init__.py +4 -0
  49. agentscope_runtime/sandbox/box/browser/browser_sandbox.py +10 -14
  50. agentscope_runtime/sandbox/box/dummy/__init__.py +4 -0
  51. agentscope_runtime/sandbox/box/dummy/dummy_sandbox.py +2 -1
  52. agentscope_runtime/sandbox/box/filesystem/__init__.py +4 -0
  53. agentscope_runtime/sandbox/box/filesystem/filesystem_sandbox.py +10 -7
  54. agentscope_runtime/sandbox/box/gui/__init__.py +4 -0
  55. agentscope_runtime/sandbox/box/gui/box/__init__.py +0 -0
  56. agentscope_runtime/sandbox/box/gui/gui_sandbox.py +81 -0
  57. agentscope_runtime/sandbox/box/sandbox.py +5 -2
  58. agentscope_runtime/sandbox/box/shared/routers/generic.py +20 -1
  59. agentscope_runtime/sandbox/box/training_box/__init__.py +4 -0
  60. agentscope_runtime/sandbox/box/training_box/training_box.py +7 -54
  61. agentscope_runtime/sandbox/build.py +143 -58
  62. agentscope_runtime/sandbox/client/http_client.py +87 -59
  63. agentscope_runtime/sandbox/client/training_client.py +0 -1
  64. agentscope_runtime/sandbox/constant.py +27 -1
  65. agentscope_runtime/sandbox/custom/custom_sandbox.py +7 -6
  66. agentscope_runtime/sandbox/custom/example.py +4 -3
  67. agentscope_runtime/sandbox/enums.py +1 -1
  68. agentscope_runtime/sandbox/manager/sandbox_manager.py +212 -106
  69. agentscope_runtime/sandbox/manager/server/app.py +82 -14
  70. agentscope_runtime/sandbox/manager/server/config.py +50 -3
  71. agentscope_runtime/sandbox/model/container.py +12 -23
  72. agentscope_runtime/sandbox/model/manager_config.py +93 -5
  73. agentscope_runtime/sandbox/registry.py +1 -1
  74. agentscope_runtime/sandbox/tools/gui/__init__.py +7 -0
  75. agentscope_runtime/sandbox/tools/gui/tool.py +77 -0
  76. agentscope_runtime/sandbox/tools/mcp_tool.py +6 -2
  77. agentscope_runtime/sandbox/tools/tool.py +4 -0
  78. agentscope_runtime/sandbox/utils.py +124 -0
  79. agentscope_runtime/version.py +1 -1
  80. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0b2.dist-info}/METADATA +214 -102
  81. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0b2.dist-info}/RECORD +94 -78
  82. agentscope_runtime/engine/agents/agentscope_agent/__init__.py +0 -6
  83. agentscope_runtime/engine/agents/agentscope_agent/agent.py +0 -401
  84. agentscope_runtime/engine/agents/agentscope_agent/hooks.py +0 -169
  85. agentscope_runtime/engine/agents/llm_agent.py +0 -51
  86. agentscope_runtime/engine/llms/__init__.py +0 -3
  87. agentscope_runtime/engine/llms/base_llm.py +0 -60
  88. agentscope_runtime/engine/llms/qwen_llm.py +0 -47
  89. agentscope_runtime/sandbox/manager/collections/in_memory_mapping.py +0 -22
  90. agentscope_runtime/sandbox/manager/collections/redis_mapping.py +0 -26
  91. agentscope_runtime/sandbox/manager/container_clients/__init__.py +0 -10
  92. agentscope_runtime/sandbox/manager/container_clients/docker_client.py +0 -422
  93. /agentscope_runtime/{sandbox/manager → common}/collections/__init__.py +0 -0
  94. /agentscope_runtime/{sandbox/manager → common}/collections/base_mapping.py +0 -0
  95. /agentscope_runtime/{sandbox/manager → common}/collections/base_queue.py +0 -0
  96. /agentscope_runtime/{sandbox/manager → common}/collections/base_set.py +0 -0
  97. /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_queue.py +0 -0
  98. /agentscope_runtime/{sandbox/manager → common}/collections/in_memory_set.py +0 -0
  99. /agentscope_runtime/{sandbox/manager → common}/collections/redis_queue.py +0 -0
  100. /agentscope_runtime/{sandbox/manager → common}/collections/redis_set.py +0 -0
  101. /agentscope_runtime/{sandbox/manager → common}/container_clients/base_client.py +0 -0
  102. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0b2.dist-info}/WHEEL +0 -0
  103. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0b2.dist-info}/entry_points.txt +0 -0
  104. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0b2.dist-info}/licenses/LICENSE +0 -0
  105. {agentscope_runtime-0.1.5b2.dist-info → agentscope_runtime-0.2.0b2.dist-info}/top_level.txt +0 -0
@@ -29,7 +29,8 @@ class LocalDeployManager(DeployManager):
29
29
  self,
30
30
  host: str = "127.0.0.1",
31
31
  port: int = 8000,
32
- shutdown_timeout: int = 120,
32
+ shutdown_timeout: int = 30,
33
+ startup_timeout: int = 30,
33
34
  logger: Optional[logging.Logger] = None,
34
35
  ):
35
36
  """Initialize LocalDeployManager.
@@ -44,6 +45,7 @@ class LocalDeployManager(DeployManager):
44
45
  self.host = host
45
46
  self.port = port
46
47
  self._shutdown_timeout = shutdown_timeout
48
+ self._startup_timeout = startup_timeout
47
49
  self._logger = logger or logging.getLogger(__name__)
48
50
 
49
51
  # State management
@@ -66,6 +68,7 @@ class LocalDeployManager(DeployManager):
66
68
 
67
69
  async def deploy(
68
70
  self,
71
+ app=None,
69
72
  runner: Optional[Any] = None,
70
73
  endpoint_path: str = "/process",
71
74
  request_model: Optional[Type] = None,
@@ -75,7 +78,11 @@ class LocalDeployManager(DeployManager):
75
78
  after_finish: Optional[Callable] = None,
76
79
  mode: DeploymentMode = DeploymentMode.DAEMON_THREAD,
77
80
  services_config: Optional[ServicesConfig] = None,
81
+ custom_endpoints: Optional[List[Dict]] = None,
78
82
  protocol_adapters: Optional[list[ProtocolAdapter]] = None,
83
+ broker_url: Optional[str] = None,
84
+ backend_url: Optional[str] = None,
85
+ enable_embedded_worker: bool = False,
79
86
  **kwargs: Any,
80
87
  ) -> Dict[str, str]:
81
88
  """Deploy using unified FastAPI architecture.
@@ -90,7 +97,12 @@ class LocalDeployManager(DeployManager):
90
97
  after_finish: Callback function called after server finishes
91
98
  mode: Deployment mode
92
99
  services_config: Services configuration
100
+ custom_endpoints: Custom endpoints from agent app
93
101
  protocol_adapters: Protocol adapters
102
+ broker_url: Celery broker URL for background task processing
103
+ backend_url: Celery backend URL for result storage
104
+ enable_embedded_worker: Whether to run Celery worker
105
+ embedded in the app
94
106
  **kwargs: Additional keyword arguments
95
107
 
96
108
  Returns:
@@ -102,6 +114,19 @@ class LocalDeployManager(DeployManager):
102
114
  if self.is_running:
103
115
  raise RuntimeError("Service is already running")
104
116
 
117
+ self._app = app
118
+ if self._app is not None:
119
+ runner = self._app._runner
120
+ endpoint_path = self._app.endpoint_path
121
+ response_type = self._app.response_type
122
+ stream = self._app.stream
123
+ request_model = self._app.request_model
124
+ before_start = self._app.before_start
125
+ after_finish = self._app.after_finish
126
+ backend_url = self._app.backend_url
127
+ broker_url = self._app.broker_url
128
+ custom_endpoints = self._app.custom_endpoints
129
+ protocol_adapters = self._app.protocol_adapters
105
130
  try:
106
131
  if mode == DeploymentMode.DAEMON_THREAD:
107
132
  return await self._deploy_daemon_thread(
@@ -113,7 +138,11 @@ class LocalDeployManager(DeployManager):
113
138
  before_start=before_start,
114
139
  after_finish=after_finish,
115
140
  services_config=services_config,
141
+ custom_endpoints=custom_endpoints,
116
142
  protocol_adapters=protocol_adapters,
143
+ broker_url=broker_url,
144
+ backend_url=backend_url,
145
+ enable_embedded_worker=enable_embedded_worker,
117
146
  **kwargs,
118
147
  )
119
148
  elif mode == DeploymentMode.DETACHED_PROCESS:
@@ -126,6 +155,7 @@ class LocalDeployManager(DeployManager):
126
155
  before_start=before_start,
127
156
  after_finish=after_finish,
128
157
  services_config=services_config,
158
+ custom_endpoints=custom_endpoints,
129
159
  protocol_adapters=protocol_adapters,
130
160
  **kwargs,
131
161
  )
@@ -143,16 +173,22 @@ class LocalDeployManager(DeployManager):
143
173
  self,
144
174
  runner: Optional[Any] = None,
145
175
  protocol_adapters: Optional[list[ProtocolAdapter]] = None,
176
+ broker_url: Optional[str] = None,
177
+ backend_url: Optional[str] = None,
178
+ enable_embedded_worker: bool = False,
146
179
  **kwargs,
147
180
  ) -> Dict[str, str]:
148
181
  """Deploy in daemon thread mode."""
149
182
  self._logger.info("Deploying FastAPI service in daemon thread mode...")
150
183
 
151
- # Create FastAPI app using factory
184
+ # Create FastAPI app using factory with Celery support
152
185
  app = FastAPIAppFactory.create_app(
153
186
  runner=runner,
154
187
  mode=DeploymentMode.DAEMON_THREAD,
155
188
  protocol_adapters=protocol_adapters,
189
+ broker_url=broker_url,
190
+ backend_url=backend_url,
191
+ enable_embedded_worker=enable_embedded_worker,
156
192
  **kwargs,
157
193
  )
158
194
 
@@ -174,7 +210,7 @@ class LocalDeployManager(DeployManager):
174
210
  self._server_thread.start()
175
211
 
176
212
  # Wait for server to start
177
- await self._wait_for_server_ready()
213
+ await self._wait_for_server_ready(self._startup_timeout)
178
214
 
179
215
  self.is_running = True
180
216
  self.deploy_id = f"daemon_{self.host}_{self.port}"
@@ -207,6 +243,8 @@ class LocalDeployManager(DeployManager):
207
243
  )
208
244
 
209
245
  agent = runner._agent
246
+ if "agent" in kwargs:
247
+ kwargs.pop("agent")
210
248
 
211
249
  # Create package project for detached deployment
212
250
  project_dir = await self.create_detached_project(
@@ -272,6 +310,13 @@ class LocalDeployManager(DeployManager):
272
310
  extra_packages: Optional[List[str]] = None,
273
311
  services_config: Optional[ServicesConfig] = None,
274
312
  protocol_adapters: Optional[list[ProtocolAdapter]] = None,
313
+ custom_endpoints: Optional[
314
+ List[Dict]
315
+ ] = None, # New parameter for custom endpoints
316
+ # Celery parameters
317
+ broker_url: Optional[str] = None,
318
+ backend_url: Optional[str] = None,
319
+ enable_embedded_worker: bool = False,
275
320
  **kwargs, # pylint: disable=unused-argument
276
321
  ) -> str:
277
322
  """Create detached project using package_project method."""
@@ -288,6 +333,11 @@ class LocalDeployManager(DeployManager):
288
333
  extra_packages=extra_packages,
289
334
  protocol_adapters=protocol_adapters,
290
335
  services_config=services_config,
336
+ custom_endpoints=custom_endpoints, # Add custom endpoints
337
+ # Celery configuration
338
+ broker_url=broker_url,
339
+ backend_url=backend_url,
340
+ enable_embedded_worker=enable_embedded_worker,
291
341
  requirements=requirements
292
342
  + (
293
343
  ["redis"]
@@ -301,6 +351,14 @@ class LocalDeployManager(DeployManager):
301
351
  if config
302
352
  )
303
353
  else []
354
+ )
355
+ + (
356
+ [
357
+ "celery",
358
+ "redis",
359
+ ] # Add Celery and Redis if Celery is configured
360
+ if broker_url or backend_url
361
+ else []
304
362
  ),
305
363
  )
306
364
 
@@ -201,16 +201,6 @@ async def _oss_create_bucket_if_not_exists(client, bucket_name: str) -> None:
201
201
  )
202
202
 
203
203
 
204
- def _create_bucket_name(prefix: str, base_name: str) -> str:
205
- import re as _re
206
-
207
- ts = time.strftime("%Y%m%d-%H%M%S", time.gmtime())
208
- base = _re.sub(r"\s+", "-", base_name)
209
- base = _re.sub(r"[^a-zA-Z0-9-]", "", base).lower().strip("-")
210
- name = f"{prefix}-{base}-{ts}"
211
- return name[:63]
212
-
213
-
214
204
  async def _oss_put_and_presign(
215
205
  client,
216
206
  bucket_name: str,
@@ -569,6 +559,9 @@ class ModelstudioDeployManager(DeployManager):
569
559
  external_whl_path: Optional[str] = None,
570
560
  agent_id: Optional[str] = None,
571
561
  agent_desc: Optional[str] = None,
562
+ custom_endpoints: Optional[
563
+ List[Dict]
564
+ ] = None, # New parameter for custom endpoints
572
565
  **kwargs,
573
566
  ) -> Dict[str, str]:
574
567
  """
@@ -579,7 +572,10 @@ class ModelstudioDeployManager(DeployManager):
579
572
  """
580
573
  if not agent_id:
581
574
  if not runner and not project_dir and not external_whl_path:
582
- raise ValueError("")
575
+ raise ValueError(
576
+ "Either runner, project_dir, "
577
+ "or external_whl_path must be provided.",
578
+ )
583
579
 
584
580
  # convert services_config to Model body
585
581
  if services_config and isinstance(services_config, dict):
@@ -588,6 +584,8 @@ class ModelstudioDeployManager(DeployManager):
588
584
  try:
589
585
  if runner:
590
586
  agent = runner._agent
587
+ if "agent" in kwargs:
588
+ kwargs.pop("agent")
591
589
 
592
590
  # Create package project for detached deployment
593
591
  project_dir = await LocalDeployManager.create_detached_project(
@@ -595,6 +593,7 @@ class ModelstudioDeployManager(DeployManager):
595
593
  endpoint_path=endpoint_path,
596
594
  services_config=services_config, # type: ignore[arg-type]
597
595
  protocol_adapters=protocol_adapters,
596
+ custom_endpoints=custom_endpoints, # Pass custom endpoints
598
597
  requirements=requirements,
599
598
  extra_packages=extra_packages,
600
599
  **kwargs,
@@ -39,6 +39,9 @@ class RunnerImageConfig(BaseModel):
39
39
  endpoint_path: str = "/process"
40
40
  protocol_adapters: Optional[List] = None # New: protocol adapters
41
41
  services_config: Optional[ServicesConfig] = None
42
+ custom_endpoints: Optional[
43
+ List[Dict]
44
+ ] = None # New: custom endpoints configuration
42
45
 
43
46
  # Docker configuration
44
47
  base_image: str = "python:3.10-slim-bookworm"
@@ -184,6 +187,7 @@ class RunnerImageFactory:
184
187
  endpoint_path=config.endpoint_path,
185
188
  protocol_adapters=config.protocol_adapters,
186
189
  services_config=config.services_config,
190
+ custom_endpoints=config.custom_endpoints,
187
191
  ),
188
192
  dockerfile_path=dockerfile_path,
189
193
  # caller_depth is no longer needed due to automatic
@@ -245,6 +249,9 @@ class RunnerImageFactory:
245
249
  push_to_registry: bool = False,
246
250
  services_config: Optional[ServicesConfig] = None,
247
251
  protocol_adapters: Optional[List] = None, # New: protocol adapters
252
+ custom_endpoints: Optional[
253
+ List[Dict]
254
+ ] = None, # New parameter for custom endpoints
248
255
  **kwargs,
249
256
  ) -> str:
250
257
  """
@@ -261,6 +268,7 @@ class RunnerImageFactory:
261
268
  push_to_registry: Whether to push to registry
262
269
  services_config: Optional services config
263
270
  protocol_adapters: Protocol adapters
271
+ custom_endpoints: Custom endpoints from agent app
264
272
  **kwargs: Additional configuration options
265
273
 
266
274
  Returns:
@@ -276,6 +284,7 @@ class RunnerImageFactory:
276
284
  push_to_registry=push_to_registry,
277
285
  protocol_adapters=protocol_adapters,
278
286
  services_config=services_config,
287
+ custom_endpoints=custom_endpoints,
279
288
  **kwargs,
280
289
  )
281
290
 
@@ -12,7 +12,7 @@ import shutil
12
12
  import tarfile
13
13
  import tempfile
14
14
  from pathlib import Path
15
- from typing import List, Optional, Any, Tuple
15
+ from typing import List, Optional, Any, Tuple, Dict
16
16
 
17
17
  from pydantic import BaseModel
18
18
 
@@ -61,6 +61,173 @@ def _get_package_version() -> str:
61
61
  return ""
62
62
 
63
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
+
64
231
  class PackageConfig(BaseModel):
65
232
  """Configuration for project packaging"""
66
233
 
@@ -73,6 +240,13 @@ class PackageConfig(BaseModel):
73
240
  ServicesConfig
74
241
  ] = None # New: services configuration
75
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
76
250
 
77
251
 
78
252
  def _find_agent_source_file(
@@ -742,6 +916,41 @@ def package_project(
742
916
  f"# Protocol adapters\nprotocol_adapters = {instances_str}"
743
917
  )
744
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
+
745
954
  # Render template - use template file by default,
746
955
  # or user-provided string
747
956
  if template is None:
@@ -751,6 +960,9 @@ def package_project(
751
960
  endpoint_path=config.endpoint_path or "/process",
752
961
  deployment_mode=config.deployment_mode or "standalone",
753
962
  protocol_adapters=protocol_adapters_str,
963
+ celery_config=celery_config_str,
964
+ custom_endpoints=custom_endpoints_data,
965
+ handler_dirs=handler_dirs,
754
966
  )
755
967
  else:
756
968
  # Use user-provided template string
@@ -760,6 +972,9 @@ def package_project(
760
972
  endpoint_path=config.endpoint_path,
761
973
  deployment_mode=config.deployment_mode or "standalone",
762
974
  protocol_adapters=protocol_adapters_str,
975
+ celery_config=celery_config_str,
976
+ custom_endpoints=custom_endpoints_data,
977
+ handler_dirs=handler_dirs,
763
978
  )
764
979
 
765
980
  # Write main.py
@@ -802,9 +1017,25 @@ def package_project(
802
1017
  if not config.requirements:
803
1018
  config.requirements = []
804
1019
 
805
- # Combine base requirements with user requirements
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
806
1031
  all_requirements = sorted(
807
- list(set(base_requirements + config.requirements)),
1032
+ list(
1033
+ set(
1034
+ base_requirements
1035
+ + config.requirements
1036
+ + celery_requirements,
1037
+ ),
1038
+ ),
808
1039
  )
809
1040
  for req in all_requirements:
810
1041
  f.write(f"{req}\n")