agent-starter-pack 0.10.0__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.0.dist-info → agent_starter_pack-0.11.0.dist-info}/METADATA +2 -2
- {agent_starter_pack-0.10.0.dist-info → agent_starter_pack-0.11.0.dist-info}/RECORD +46 -45
- agents/adk_gemini_fullstack/.template/templateconfig.yaml +1 -0
- 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
- agents/live_api/tests/unit/test_server.py +2 -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 +232 -101
- src/cli/commands/enhance.py +248 -0
- src/cli/commands/list.py +23 -11
- src/cli/commands/setup_cicd.py +1 -1
- 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.0.dist-info → agent_starter_pack-0.11.0.dist-info}/WHEEL +0 -0
- {agent_starter_pack-0.10.0.dist-info → agent_starter_pack-0.11.0.dist-info}/entry_points.txt +0 -0
- {agent_starter_pack-0.10.0.dist-info → agent_starter_pack-0.11.0.dist-info}/licenses/LICENSE +0 -0
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 {
|