agent-starter-pack 0.10.1__py3-none-any.whl → 0.11.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.
- {agent_starter_pack-0.10.1.dist-info → agent_starter_pack-0.11.0.dist-info}/METADATA +2 -2
- {agent_starter_pack-0.10.1.dist-info → agent_starter_pack-0.11.0.dist-info}/RECORD +43 -42
- agents/crewai_coding_crew/.template/templateconfig.yaml +2 -2
- agents/crewai_coding_crew/tests/integration/test_agent.py +1 -1
- agents/langgraph_base_react/.template/templateconfig.yaml +1 -1
- agents/langgraph_base_react/tests/integration/test_agent.py +1 -1
- src/base_template/deployment/terraform/dev/iam.tf +12 -11
- src/base_template/deployment/terraform/dev/variables.tf +2 -7
- src/base_template/deployment/terraform/github.tf +14 -0
- src/base_template/deployment/terraform/iam.tf +10 -7
- src/base_template/deployment/terraform/service_accounts.tf +4 -5
- src/base_template/deployment/terraform/variables.tf +2 -7
- src/base_template/deployment/terraform/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}build_triggers.tf{% else %}unused_build_triggers.tf{% endif %} +4 -2
- src/base_template/pyproject.toml +2 -2
- src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/deploy-to-prod.yaml +1 -0
- src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/staging.yaml +1 -0
- src/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/deploy-to-prod.yaml +1 -0
- src/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/staging.yaml +1 -0
- src/cli/commands/create.py +202 -100
- src/cli/commands/enhance.py +248 -0
- src/cli/commands/list.py +23 -11
- src/cli/main.py +2 -0
- src/cli/utils/logging.py +40 -0
- src/cli/utils/remote_template.py +55 -16
- src/cli/utils/template.py +212 -94
- src/deployment_targets/agent_engine/app/agent_engine_app.py +8 -0
- src/deployment_targets/cloud_run/app/server.py +1 -1
- src/deployment_targets/cloud_run/deployment/terraform/dev/service.tf +1 -1
- src/deployment_targets/cloud_run/deployment/terraform/service.tf +2 -2
- src/resources/locks/uv-adk_base-agent_engine.lock +312 -312
- src/resources/locks/uv-adk_base-cloud_run.lock +403 -404
- src/resources/locks/uv-adk_gemini_fullstack-agent_engine.lock +312 -312
- src/resources/locks/uv-adk_gemini_fullstack-cloud_run.lock +403 -404
- src/resources/locks/uv-agentic_rag-agent_engine.lock +371 -371
- src/resources/locks/uv-agentic_rag-cloud_run.lock +477 -478
- src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +661 -591
- src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +868 -760
- src/resources/locks/uv-langgraph_base_react-agent_engine.lock +496 -446
- src/resources/locks/uv-langgraph_base_react-cloud_run.lock +639 -565
- src/resources/locks/uv-live_api-cloud_run.lock +584 -510
- {agent_starter_pack-0.10.1.dist-info → agent_starter_pack-0.11.0.dist-info}/WHEEL +0 -0
- {agent_starter_pack-0.10.1.dist-info → agent_starter_pack-0.11.0.dist-info}/entry_points.txt +0 -0
- {agent_starter_pack-0.10.1.dist-info → agent_starter_pack-0.11.0.dist-info}/licenses/LICENSE +0 -0
src/cli/commands/list.py
CHANGED
@@ -16,7 +16,7 @@ import logging
|
|
16
16
|
import pathlib
|
17
17
|
|
18
18
|
import click
|
19
|
-
import
|
19
|
+
import tomllib
|
20
20
|
from rich.console import Console
|
21
21
|
from rich.table import Table
|
22
22
|
|
@@ -42,25 +42,37 @@ def display_agents_from_path(base_path: pathlib.Path, source_name: str) -> None:
|
|
42
42
|
return
|
43
43
|
|
44
44
|
found_agents = False
|
45
|
-
# Search for
|
46
|
-
for config_path in sorted(base_path.glob("**/
|
45
|
+
# Search for pyproject.toml files to identify agents (explicit opt-in)
|
46
|
+
for config_path in sorted(base_path.glob("**/pyproject.toml")):
|
47
47
|
try:
|
48
|
-
with open(config_path,
|
49
|
-
|
48
|
+
with open(config_path, "rb") as f:
|
49
|
+
pyproject_data = tomllib.load(f)
|
50
50
|
|
51
|
-
|
52
|
-
|
51
|
+
config = pyproject_data.get("tool", {}).get("agent-starter-pack", {})
|
52
|
+
|
53
|
+
# Skip pyproject.toml files that don't have agent-starter-pack config
|
54
|
+
if not config:
|
55
|
+
continue
|
56
|
+
|
57
|
+
template_root = config_path.parent
|
58
|
+
|
59
|
+
# Use fallbacks to [project] section if needed
|
60
|
+
project_info = pyproject_data.get("project", {})
|
61
|
+
agent_name = (
|
62
|
+
config.get("name") or project_info.get("name") or template_root.name
|
63
|
+
)
|
64
|
+
description = (
|
65
|
+
config.get("description") or project_info.get("description") or ""
|
66
|
+
)
|
53
67
|
|
54
68
|
# Display the agent's path relative to the scanned directory
|
55
|
-
relative_path =
|
69
|
+
relative_path = template_root.relative_to(base_path)
|
56
70
|
|
57
71
|
table.add_row(agent_name, f"/{relative_path}", description)
|
58
72
|
found_agents = True
|
59
73
|
|
60
74
|
except Exception as e:
|
61
|
-
logging.warning(
|
62
|
-
f"Could not load agent from {config_path.parent.parent}: {e}"
|
63
|
-
)
|
75
|
+
logging.warning(f"Could not load agent from {config_path.parent}: {e}")
|
64
76
|
|
65
77
|
if not found_agents:
|
66
78
|
console.print(f"No agents found in {source_name}", style="yellow")
|
src/cli/main.py
CHANGED
@@ -18,6 +18,7 @@ import click
|
|
18
18
|
from rich.console import Console
|
19
19
|
|
20
20
|
from .commands.create import create
|
21
|
+
from .commands.enhance import enhance
|
21
22
|
from .commands.list import list_agents
|
22
23
|
from .commands.setup_cicd import setup_cicd
|
23
24
|
from .utils import display_update_message
|
@@ -53,6 +54,7 @@ def cli() -> None:
|
|
53
54
|
|
54
55
|
# Register commands
|
55
56
|
cli.add_command(create)
|
57
|
+
cli.add_command(enhance)
|
56
58
|
cli.add_command(setup_cicd)
|
57
59
|
cli.add_command(list_agents, name="list")
|
58
60
|
|
src/cli/utils/logging.py
CHANGED
@@ -24,6 +24,46 @@ console = Console()
|
|
24
24
|
F = TypeVar("F", bound=Callable[..., Any])
|
25
25
|
|
26
26
|
|
27
|
+
def display_welcome_banner(
|
28
|
+
agent: str | None = None, enhance_mode: bool = False
|
29
|
+
) -> None:
|
30
|
+
"""Display the Agent Starter Pack welcome banner.
|
31
|
+
|
32
|
+
Args:
|
33
|
+
agent: Optional agent specification to customize the welcome message
|
34
|
+
enhance_mode: Whether this is for enhancement mode
|
35
|
+
"""
|
36
|
+
if enhance_mode:
|
37
|
+
console.print(
|
38
|
+
"\n=== Google Cloud Agent Starter Pack 🚀===",
|
39
|
+
style="bold blue",
|
40
|
+
)
|
41
|
+
console.print(
|
42
|
+
"Enhancing your existing project with production-ready agent capabilities!\n",
|
43
|
+
style="green",
|
44
|
+
)
|
45
|
+
elif agent and agent.startswith("adk@"):
|
46
|
+
console.print(
|
47
|
+
"\n=== Welcome to [link=https://github.com/google/adk-samples]google/adk-samples[/link]! ✨ ===",
|
48
|
+
style="bold blue",
|
49
|
+
)
|
50
|
+
console.print(
|
51
|
+
"Powered by [link=https://goo.gle/agent-starter-pack]Google Cloud - Agent Starter Pack [/link]\n",
|
52
|
+
)
|
53
|
+
console.print(
|
54
|
+
"This tool will help you create an end-to-end production-ready AI agent in Google Cloud!\n"
|
55
|
+
)
|
56
|
+
else:
|
57
|
+
console.print(
|
58
|
+
"\n=== Google Cloud Agent Starter Pack 🚀===",
|
59
|
+
style="bold blue",
|
60
|
+
)
|
61
|
+
console.print("Welcome to the Agent Starter Pack!")
|
62
|
+
console.print(
|
63
|
+
"This tool will help you create an end-to-end production-ready AI agent in Google Cloud!\n"
|
64
|
+
)
|
65
|
+
|
66
|
+
|
27
67
|
def handle_cli_error(f: F) -> F:
|
28
68
|
"""Decorator to handle CLI errors gracefully.
|
29
69
|
|
src/cli/utils/remote_template.py
CHANGED
@@ -22,7 +22,7 @@ import tempfile
|
|
22
22
|
from dataclasses import dataclass
|
23
23
|
from typing import Any
|
24
24
|
|
25
|
-
import
|
25
|
+
import tomllib
|
26
26
|
from jinja2 import Environment
|
27
27
|
|
28
28
|
|
@@ -183,27 +183,66 @@ def fetch_remote_template(
|
|
183
183
|
) from e
|
184
184
|
|
185
185
|
|
186
|
-
def load_remote_template_config(
|
187
|
-
|
186
|
+
def load_remote_template_config(
|
187
|
+
template_dir: pathlib.Path, cli_overrides: dict[str, Any] | None = None
|
188
|
+
) -> dict[str, Any]:
|
189
|
+
"""Load template configuration from remote template's pyproject.toml with CLI overrides.
|
190
|
+
|
191
|
+
Loads configuration from [tool.agent-starter-pack] section with fallbacks
|
192
|
+
to [project] section for name and description if not specified. CLI overrides
|
193
|
+
take precedence over all other sources.
|
188
194
|
|
189
195
|
Args:
|
190
196
|
template_dir: Path to template directory
|
197
|
+
cli_overrides: Configuration overrides from CLI (takes highest precedence)
|
191
198
|
|
192
199
|
Returns:
|
193
|
-
Template configuration dictionary
|
200
|
+
Template configuration dictionary with merged sources
|
194
201
|
"""
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
202
|
+
config = {}
|
203
|
+
|
204
|
+
# Start with defaults
|
205
|
+
defaults = {
|
206
|
+
"base_template": "adk_base",
|
207
|
+
"name": template_dir.name,
|
208
|
+
"description": "",
|
209
|
+
}
|
210
|
+
config.update(defaults)
|
211
|
+
|
212
|
+
# Load from pyproject.toml if it exists
|
213
|
+
pyproject_path = template_dir / "pyproject.toml"
|
214
|
+
if pyproject_path.exists():
|
215
|
+
try:
|
216
|
+
with open(pyproject_path, "rb") as f:
|
217
|
+
pyproject_data = tomllib.load(f)
|
218
|
+
|
219
|
+
# Extract the agent-starter-pack configuration
|
220
|
+
toml_config = pyproject_data.get("tool", {}).get("agent-starter-pack", {})
|
221
|
+
|
222
|
+
# Fallback to [project] fields if not specified in agent-starter-pack section
|
223
|
+
project_info = pyproject_data.get("project", {})
|
224
|
+
|
225
|
+
# Apply pyproject.toml configuration (overrides defaults)
|
226
|
+
if toml_config:
|
227
|
+
config.update(toml_config)
|
228
|
+
|
229
|
+
# Apply [project] fallbacks if not already set
|
230
|
+
if "name" not in toml_config and "name" in project_info:
|
231
|
+
config["name"] = project_info["name"]
|
232
|
+
|
233
|
+
if "description" not in toml_config and "description" in project_info:
|
234
|
+
config["description"] = project_info["description"]
|
235
|
+
|
236
|
+
logging.debug(f"Loaded template config from {pyproject_path}")
|
237
|
+
except Exception as e:
|
238
|
+
logging.error(f"Error loading pyproject.toml config: {e}")
|
239
|
+
|
240
|
+
# Apply CLI overrides (highest precedence) using deep merge
|
241
|
+
if cli_overrides:
|
242
|
+
config = merge_template_configs(config, cli_overrides)
|
243
|
+
logging.debug(f"Applied CLI overrides: {cli_overrides}")
|
244
|
+
|
245
|
+
return config
|
207
246
|
|
208
247
|
|
209
248
|
def get_base_template_name(config: dict[str, Any]) -> str:
|
src/cli/utils/template.py
CHANGED
@@ -445,6 +445,7 @@ def process_template(
|
|
445
445
|
output_dir: pathlib.Path | None = None,
|
446
446
|
remote_template_path: pathlib.Path | None = None,
|
447
447
|
remote_config: dict[str, Any] | None = None,
|
448
|
+
in_folder: bool = False,
|
448
449
|
) -> None:
|
449
450
|
"""Process the template directory and create a new project.
|
450
451
|
|
@@ -460,6 +461,7 @@ def process_template(
|
|
460
461
|
output_dir: Optional output directory path, defaults to current directory
|
461
462
|
remote_template_path: Optional path to remote template for overlay
|
462
463
|
remote_config: Optional remote template configuration
|
464
|
+
in_folder: Whether to template directly into the output directory instead of creating a subdirectory
|
463
465
|
"""
|
464
466
|
logging.debug(f"Processing template from {template_dir}")
|
465
467
|
logging.debug(f"Project name: {project_name}")
|
@@ -689,112 +691,228 @@ def process_template(
|
|
689
691
|
logging.debug("Template processing completed successfully")
|
690
692
|
|
691
693
|
# Move the generated project to the final destination
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
shutil.rmtree(final_destination)
|
700
|
-
shutil.copytree(output_dir, final_destination, dirs_exist_ok=True)
|
701
|
-
logging.debug(f"Project successfully created at {final_destination}")
|
702
|
-
|
703
|
-
# Render and merge Makefiles.
|
704
|
-
# If it's a local template, remote_template_path will be None,
|
705
|
-
# and only the base Makefile will be rendered.
|
706
|
-
render_and_merge_makefiles(
|
707
|
-
base_template_path=base_template_path,
|
708
|
-
final_destination=final_destination,
|
709
|
-
cookiecutter_config=cookiecutter_config,
|
710
|
-
remote_template_path=remote_template_path,
|
694
|
+
generated_project_dir = temp_path / project_name
|
695
|
+
|
696
|
+
if in_folder:
|
697
|
+
# For in-folder mode, copy files directly to the destination directory
|
698
|
+
final_destination = destination_dir
|
699
|
+
logging.debug(
|
700
|
+
f"In-folder mode: copying files from {generated_project_dir} to {final_destination}"
|
711
701
|
)
|
712
702
|
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
703
|
+
if generated_project_dir.exists():
|
704
|
+
# Copy all files from generated project to destination directory
|
705
|
+
for item in generated_project_dir.iterdir():
|
706
|
+
dest_item = final_destination / item.name
|
707
|
+
|
708
|
+
# Special handling for README files - always preserve existing README
|
709
|
+
# Special handling for pyproject.toml files - only preserve for in-folder updates
|
710
|
+
should_preserve_file = item.name.lower().startswith(
|
711
|
+
"readme"
|
712
|
+
) or (item.name == "pyproject.toml" and in_folder)
|
713
|
+
if (
|
714
|
+
should_preserve_file
|
715
|
+
and (final_destination / item.name).exists()
|
716
|
+
):
|
717
|
+
# The existing file stays, use base template file with starter_pack prefix
|
718
|
+
base_name = item.stem
|
719
|
+
extension = item.suffix
|
720
|
+
dest_item = (
|
721
|
+
final_destination
|
722
|
+
/ f"starter_pack_{base_name}{extension}"
|
723
|
+
)
|
724
|
+
|
725
|
+
# Try to use base template file instead of templated file
|
726
|
+
base_file = base_template_path / item.name
|
727
|
+
if base_file.exists():
|
738
728
|
logging.debug(
|
739
|
-
f"
|
729
|
+
f"{item.name} conflict: preserving existing {item.name}, using base template {item.name} as starter_pack_{base_name}{extension}"
|
740
730
|
)
|
731
|
+
# Process the base template file through cookiecutter
|
732
|
+
try:
|
733
|
+
import tempfile as tmp_module
|
734
|
+
|
735
|
+
with (
|
736
|
+
tmp_module.TemporaryDirectory() as temp_file_dir
|
737
|
+
):
|
738
|
+
temp_file_path = pathlib.Path(temp_file_dir)
|
739
|
+
|
740
|
+
# Create a minimal cookiecutter structure for just the file
|
741
|
+
file_template_dir = (
|
742
|
+
temp_file_path / "file_template"
|
743
|
+
)
|
744
|
+
file_template_dir.mkdir()
|
745
|
+
file_project_dir = (
|
746
|
+
file_template_dir
|
747
|
+
/ "{{cookiecutter.project_name}}"
|
748
|
+
)
|
749
|
+
file_project_dir.mkdir()
|
750
|
+
|
751
|
+
# Copy base file to template structure
|
752
|
+
shutil.copy2(
|
753
|
+
base_file, file_project_dir / item.name
|
754
|
+
)
|
755
|
+
|
756
|
+
# Create cookiecutter.json with same config as main template
|
757
|
+
with open(
|
758
|
+
file_template_dir / "cookiecutter.json",
|
759
|
+
"w",
|
760
|
+
encoding="utf-8",
|
761
|
+
) as f:
|
762
|
+
json.dump(cookiecutter_config, f, indent=4)
|
763
|
+
|
764
|
+
# Process the file template
|
765
|
+
cookiecutter(
|
766
|
+
str(file_template_dir),
|
767
|
+
no_input=True,
|
768
|
+
overwrite_if_exists=True,
|
769
|
+
output_dir=str(temp_file_path),
|
770
|
+
extra_context={
|
771
|
+
"project_name": project_name,
|
772
|
+
"agent_name": agent_name,
|
773
|
+
},
|
774
|
+
)
|
775
|
+
|
776
|
+
# Copy the processed file
|
777
|
+
processed_file = (
|
778
|
+
temp_file_path / project_name / item.name
|
779
|
+
)
|
780
|
+
if processed_file.exists():
|
781
|
+
shutil.copy2(processed_file, dest_item)
|
782
|
+
else:
|
783
|
+
# Fallback to original behavior if processing fails
|
784
|
+
shutil.copy2(item, dest_item)
|
785
|
+
|
786
|
+
except Exception as e:
|
787
|
+
logging.warning(
|
788
|
+
f"Failed to process base template {item.name}: {e}. Using templated {item.name} instead."
|
789
|
+
)
|
790
|
+
shutil.copy2(item, dest_item)
|
741
791
|
else:
|
742
|
-
|
743
|
-
logging.debug(
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
logging.debug("Used pyproject.toml from remote template")
|
756
|
-
|
757
|
-
if remote_uv_lock.exists():
|
758
|
-
shutil.copy2(remote_uv_lock, final_destination / "uv.lock")
|
759
|
-
logging.debug("Used uv.lock from remote template")
|
760
|
-
elif deployment_target:
|
761
|
-
# For local templates, use the existing logic
|
762
|
-
lock_path = (
|
763
|
-
pathlib.Path(__file__).parent.parent.parent.parent
|
764
|
-
/ "src"
|
765
|
-
/ "resources"
|
766
|
-
/ "locks"
|
767
|
-
/ f"uv-{agent_name}-{deployment_target}.lock"
|
768
|
-
)
|
769
|
-
logging.debug(f"Looking for lock file at: {lock_path}")
|
770
|
-
logging.debug(f"Lock file exists: {lock_path.exists()}")
|
771
|
-
if not lock_path.exists():
|
772
|
-
raise FileNotFoundError(f"Lock file not found: {lock_path}")
|
773
|
-
# Copy and rename to uv.lock in the project directory
|
774
|
-
shutil.copy2(lock_path, final_destination / "uv.lock")
|
792
|
+
# Fallback to original behavior if base file doesn't exist
|
793
|
+
logging.debug(
|
794
|
+
f"{item.name} conflict: preserving existing {item.name}, saving templated {item.name} as starter_pack_{base_name}{extension}"
|
795
|
+
)
|
796
|
+
shutil.copy2(item, dest_item)
|
797
|
+
else:
|
798
|
+
# Normal file copying
|
799
|
+
if item.is_dir():
|
800
|
+
if dest_item.exists():
|
801
|
+
shutil.rmtree(dest_item)
|
802
|
+
shutil.copytree(item, dest_item, dirs_exist_ok=True)
|
803
|
+
else:
|
804
|
+
shutil.copy2(item, dest_item)
|
775
805
|
logging.debug(
|
776
|
-
f"
|
806
|
+
f"Project files successfully copied to {final_destination}"
|
777
807
|
)
|
808
|
+
else:
|
809
|
+
# Standard mode: create project subdirectory
|
810
|
+
final_destination = destination_dir / project_name
|
811
|
+
logging.debug(
|
812
|
+
f"Standard mode: moving project from {generated_project_dir} to {final_destination}"
|
813
|
+
)
|
778
814
|
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
content.replace(
|
786
|
-
"{{cookiecutter.project_name}}", project_name
|
787
|
-
)
|
788
|
-
)
|
789
|
-
f.truncate()
|
815
|
+
if generated_project_dir.exists():
|
816
|
+
if final_destination.exists():
|
817
|
+
shutil.rmtree(final_destination)
|
818
|
+
shutil.copytree(
|
819
|
+
generated_project_dir, final_destination, dirs_exist_ok=True
|
820
|
+
)
|
790
821
|
logging.debug(
|
791
|
-
f"
|
822
|
+
f"Project successfully created at {final_destination}"
|
792
823
|
)
|
793
|
-
|
794
|
-
|
824
|
+
|
825
|
+
# Always check if the project was successfully created before proceeding
|
826
|
+
if not final_destination.exists():
|
827
|
+
logging.error(
|
828
|
+
f"Final destination directory not found at {final_destination}"
|
829
|
+
)
|
795
830
|
raise FileNotFoundError(
|
796
|
-
f"
|
831
|
+
f"Final destination directory not found at {final_destination}"
|
832
|
+
)
|
833
|
+
|
834
|
+
# Render and merge Makefiles.
|
835
|
+
# If it's a local template, remote_template_path will be None,
|
836
|
+
# and only the base Makefile will be rendered.
|
837
|
+
render_and_merge_makefiles(
|
838
|
+
base_template_path=base_template_path,
|
839
|
+
final_destination=final_destination,
|
840
|
+
cookiecutter_config=cookiecutter_config,
|
841
|
+
remote_template_path=remote_template_path,
|
842
|
+
)
|
843
|
+
|
844
|
+
# Delete appropriate files based on ADK tag
|
845
|
+
if "adk" in tags:
|
846
|
+
files_to_delete = [final_destination / f for f in NON_ADK_FILES]
|
847
|
+
else:
|
848
|
+
files_to_delete = [final_destination / f for f in ADK_FILES]
|
849
|
+
|
850
|
+
for file_path in files_to_delete:
|
851
|
+
if file_path.exists():
|
852
|
+
file_path.unlink()
|
853
|
+
logging.debug(f"Deleted {file_path}")
|
854
|
+
|
855
|
+
# Clean up unused_* files and directories created by conditional templates
|
856
|
+
import glob
|
857
|
+
|
858
|
+
unused_patterns = [
|
859
|
+
final_destination / "unused_*",
|
860
|
+
final_destination / "**" / "unused_*",
|
861
|
+
]
|
862
|
+
|
863
|
+
for pattern in unused_patterns:
|
864
|
+
for unused_path_str in glob.glob(str(pattern), recursive=True):
|
865
|
+
unused_path = pathlib.Path(unused_path_str)
|
866
|
+
if unused_path.exists():
|
867
|
+
if unused_path.is_dir():
|
868
|
+
shutil.rmtree(unused_path)
|
869
|
+
logging.debug(f"Deleted unused directory: {unused_path}")
|
870
|
+
else:
|
871
|
+
unused_path.unlink()
|
872
|
+
logging.debug(f"Deleted unused file: {unused_path}")
|
873
|
+
|
874
|
+
# Handle pyproject.toml and uv.lock files
|
875
|
+
if is_remote and remote_template_path:
|
876
|
+
# For remote templates, use their pyproject.toml and uv.lock if they exist
|
877
|
+
remote_pyproject = remote_template_path / "pyproject.toml"
|
878
|
+
remote_uv_lock = remote_template_path / "uv.lock"
|
879
|
+
|
880
|
+
if remote_pyproject.exists():
|
881
|
+
shutil.copy2(remote_pyproject, final_destination / "pyproject.toml")
|
882
|
+
logging.debug("Used pyproject.toml from remote template")
|
883
|
+
|
884
|
+
if remote_uv_lock.exists():
|
885
|
+
shutil.copy2(remote_uv_lock, final_destination / "uv.lock")
|
886
|
+
logging.debug("Used uv.lock from remote template")
|
887
|
+
elif deployment_target:
|
888
|
+
# For local templates, use the existing logic
|
889
|
+
lock_path = (
|
890
|
+
pathlib.Path(__file__).parent.parent.parent.parent
|
891
|
+
/ "src"
|
892
|
+
/ "resources"
|
893
|
+
/ "locks"
|
894
|
+
/ f"uv-{agent_name}-{deployment_target}.lock"
|
797
895
|
)
|
896
|
+
logging.debug(f"Looking for lock file at: {lock_path}")
|
897
|
+
logging.debug(f"Lock file exists: {lock_path.exists()}")
|
898
|
+
if not lock_path.exists():
|
899
|
+
raise FileNotFoundError(f"Lock file not found: {lock_path}")
|
900
|
+
# Copy and rename to uv.lock in the project directory
|
901
|
+
shutil.copy2(lock_path, final_destination / "uv.lock")
|
902
|
+
logging.debug(
|
903
|
+
f"Copied lock file from {lock_path} to {final_destination}/uv.lock"
|
904
|
+
)
|
905
|
+
|
906
|
+
# Replace cookiecutter project name with actual project name in lock file
|
907
|
+
lock_file_path = final_destination / "uv.lock"
|
908
|
+
with open(lock_file_path, "r+", encoding="utf-8") as f:
|
909
|
+
content = f.read()
|
910
|
+
f.seek(0)
|
911
|
+
f.write(
|
912
|
+
content.replace("{{cookiecutter.project_name}}", project_name)
|
913
|
+
)
|
914
|
+
f.truncate()
|
915
|
+
logging.debug(f"Updated project name in lock file at {lock_file_path}")
|
798
916
|
|
799
917
|
except Exception as e:
|
800
918
|
logging.error(f"Failed to process template: {e!s}")
|
@@ -208,6 +208,7 @@ def deploy_agent_engine_app(
|
|
208
208
|
requirements_file: str = ".requirements.txt",
|
209
209
|
extra_packages: list[str] = ["./app"],
|
210
210
|
env_vars: dict[str, str] = {},
|
211
|
+
service_account: str | None = None,
|
211
212
|
) -> agent_engines.AgentEngine:
|
212
213
|
"""Deploy the agent engine app to Vertex AI."""
|
213
214
|
|
@@ -247,6 +248,7 @@ def deploy_agent_engine_app(
|
|
247
248
|
"description": "{{cookiecutter.agent_description}}",
|
248
249
|
"extra_packages": extra_packages,
|
249
250
|
"env_vars": env_vars,
|
251
|
+
"service_account": service_account,
|
250
252
|
}
|
251
253
|
logging.info(f"Agent config: {agent_config}")
|
252
254
|
agent_config["requirements"] = requirements
|
@@ -310,6 +312,11 @@ if __name__ == "__main__":
|
|
310
312
|
"--set-env-vars",
|
311
313
|
help="Comma-separated list of environment variables in KEY=VALUE format",
|
312
314
|
)
|
315
|
+
parser.add_argument(
|
316
|
+
"--service-account",
|
317
|
+
default=None,
|
318
|
+
help="Service account email to use for the agent engine",
|
319
|
+
)
|
313
320
|
args = parser.parse_args()
|
314
321
|
|
315
322
|
# Parse environment variables if provided
|
@@ -337,4 +344,5 @@ if __name__ == "__main__":
|
|
337
344
|
requirements_file=args.requirements_file,
|
338
345
|
extra_packages=args.extra_packages,
|
339
346
|
env_vars=env_vars,
|
347
|
+
service_account=args.service_account,
|
340
348
|
)
|
@@ -157,7 +157,7 @@ def stream_messages(
|
|
157
157
|
set_tracing_properties(config)
|
158
158
|
input_dict = input.model_dump()
|
159
159
|
|
160
|
-
for data in agent.stream(input_dict, config=config, stream_mode="messages"):
|
160
|
+
for data in agent.stream(input_dict, config=config, stream_mode="messages"): # type: ignore[arg-type]
|
161
161
|
yield dumps(data) + "\n"
|
162
162
|
|
163
163
|
|
@@ -193,7 +193,7 @@ resource "google_cloud_run_v2_service" "app" {
|
|
193
193
|
{%- endif %}
|
194
194
|
}
|
195
195
|
|
196
|
-
service_account = google_service_account.
|
196
|
+
service_account = google_service_account.app_sa.email
|
197
197
|
max_instance_request_concurrency = 40
|
198
198
|
|
199
199
|
scaling {
|
@@ -216,7 +216,7 @@ resource "google_cloud_run_v2_service" "app_staging" {
|
|
216
216
|
{%- endif %}
|
217
217
|
}
|
218
218
|
|
219
|
-
service_account = google_service_account.
|
219
|
+
service_account = google_service_account.app_sa["staging"].email
|
220
220
|
max_instance_request_concurrency = 40
|
221
221
|
|
222
222
|
scaling {
|
@@ -322,7 +322,7 @@ resource "google_cloud_run_v2_service" "app_prod" {
|
|
322
322
|
{%- endif %}
|
323
323
|
}
|
324
324
|
|
325
|
-
service_account = google_service_account.
|
325
|
+
service_account = google_service_account.app_sa["prod"].email
|
326
326
|
max_instance_request_concurrency = 40
|
327
327
|
|
328
328
|
scaling {
|