agent-starter-pack 0.10.1__py3-none-any.whl → 0.11.1__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 (50) hide show
  1. {agent_starter_pack-0.10.1.dist-info → agent_starter_pack-0.11.1.dist-info}/METADATA +10 -3
  2. {agent_starter_pack-0.10.1.dist-info → agent_starter_pack-0.11.1.dist-info}/RECORD +43 -49
  3. agents/crewai_coding_crew/.template/templateconfig.yaml +2 -2
  4. agents/crewai_coding_crew/tests/integration/test_agent.py +1 -1
  5. agents/langgraph_base_react/.template/templateconfig.yaml +1 -1
  6. agents/langgraph_base_react/tests/integration/test_agent.py +1 -1
  7. src/base_template/deployment/terraform/dev/iam.tf +12 -11
  8. src/base_template/deployment/terraform/dev/variables.tf +2 -7
  9. src/base_template/deployment/terraform/github.tf +14 -0
  10. src/base_template/deployment/terraform/iam.tf +10 -7
  11. src/base_template/deployment/terraform/service_accounts.tf +4 -5
  12. src/base_template/deployment/terraform/variables.tf +2 -7
  13. src/base_template/deployment/terraform/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}build_triggers.tf{% else %}unused_build_triggers.tf{% endif %} +4 -2
  14. src/base_template/pyproject.toml +2 -2
  15. src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/deploy-to-prod.yaml +3 -0
  16. src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/staging.yaml +1 -0
  17. src/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/deploy-to-prod.yaml +1 -0
  18. src/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/staging.yaml +1 -0
  19. src/cli/commands/create.py +202 -100
  20. src/cli/commands/enhance.py +302 -0
  21. src/cli/commands/list.py +23 -11
  22. src/cli/main.py +2 -0
  23. src/cli/utils/logging.py +40 -0
  24. src/cli/utils/remote_template.py +55 -16
  25. src/cli/utils/template.py +212 -94
  26. src/deployment_targets/agent_engine/app/agent_engine_app.py +8 -0
  27. src/deployment_targets/cloud_run/app/server.py +1 -1
  28. src/deployment_targets/cloud_run/deployment/terraform/dev/service.tf +1 -1
  29. src/deployment_targets/cloud_run/deployment/terraform/service.tf +2 -2
  30. src/resources/locks/uv-adk_base-agent_engine.lock +312 -312
  31. src/resources/locks/uv-adk_base-cloud_run.lock +403 -404
  32. src/resources/locks/uv-adk_gemini_fullstack-agent_engine.lock +312 -312
  33. src/resources/locks/uv-adk_gemini_fullstack-cloud_run.lock +403 -404
  34. src/resources/locks/uv-agentic_rag-agent_engine.lock +371 -371
  35. src/resources/locks/uv-agentic_rag-cloud_run.lock +477 -478
  36. src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +661 -591
  37. src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +868 -760
  38. src/resources/locks/uv-langgraph_base_react-agent_engine.lock +496 -446
  39. src/resources/locks/uv-langgraph_base_react-cloud_run.lock +639 -565
  40. src/resources/locks/uv-live_api-cloud_run.lock +584 -510
  41. agents/adk_gemini_fullstack/.template/templateconfig.yaml +0 -39
  42. agents/adk_gemini_fullstack/README.md +0 -27
  43. agents/adk_gemini_fullstack/app/agent.py +0 -412
  44. agents/adk_gemini_fullstack/app/config.py +0 -46
  45. agents/adk_gemini_fullstack/notebooks/adk_app_testing.ipynb +0 -355
  46. agents/adk_gemini_fullstack/notebooks/evaluating_adk_agent.ipynb +0 -1528
  47. agents/adk_gemini_fullstack/tests/integration/test_agent.py +0 -58
  48. {agent_starter_pack-0.10.1.dist-info → agent_starter_pack-0.11.1.dist-info}/WHEEL +0 -0
  49. {agent_starter_pack-0.10.1.dist-info → agent_starter_pack-0.11.1.dist-info}/entry_points.txt +0 -0
  50. {agent_starter_pack-0.10.1.dist-info → agent_starter_pack-0.11.1.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
- output_dir = temp_path / project_name
693
- final_destination = destination_dir / project_name
694
-
695
- logging.debug(f"Moving project from {output_dir} to {final_destination}")
696
-
697
- if output_dir.exists():
698
- if final_destination.exists():
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
- # Delete appropriate files based on ADK tag
714
- if "adk" in tags:
715
- files_to_delete = [final_destination / f for f in NON_ADK_FILES]
716
- else:
717
- files_to_delete = [final_destination / f for f in ADK_FILES]
718
-
719
- for file_path in files_to_delete:
720
- if file_path.exists():
721
- file_path.unlink()
722
- logging.debug(f"Deleted {file_path}")
723
-
724
- # Clean up unused_* files and directories created by conditional templates
725
- import glob
726
-
727
- unused_patterns = [
728
- final_destination / "unused_*",
729
- final_destination / "**" / "unused_*",
730
- ]
731
-
732
- for pattern in unused_patterns:
733
- for unused_path_str in glob.glob(str(pattern), recursive=True):
734
- unused_path = pathlib.Path(unused_path_str)
735
- if unused_path.exists():
736
- if unused_path.is_dir():
737
- shutil.rmtree(unused_path)
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"Deleted unused directory: {unused_path}"
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
- unused_path.unlink()
743
- logging.debug(f"Deleted unused file: {unused_path}")
744
-
745
- # Handle pyproject.toml and uv.lock files
746
- if is_remote and remote_template_path:
747
- # For remote templates, use their pyproject.toml and uv.lock if they exist
748
- remote_pyproject = remote_template_path / "pyproject.toml"
749
- remote_uv_lock = remote_template_path / "uv.lock"
750
-
751
- if remote_pyproject.exists():
752
- shutil.copy2(
753
- remote_pyproject, final_destination / "pyproject.toml"
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"Copied lock file from {lock_path} to {final_destination}/uv.lock"
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
- # Replace cookiecutter project name with actual project name in lock file
780
- lock_file_path = final_destination / "uv.lock"
781
- with open(lock_file_path, "r+", encoding="utf-8") as f:
782
- content = f.read()
783
- f.seek(0)
784
- f.write(
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"Updated project name in lock file at {lock_file_path}"
822
+ f"Project successfully created at {final_destination}"
792
823
  )
793
- else:
794
- logging.error(f"Generated project directory not found at {output_dir}")
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"Generated project directory not found at {output_dir}"
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.cloud_run_app_sa.email
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.cloud_run_app_sa["staging"].email
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.cloud_run_app_sa["prod"].email
325
+ service_account = google_service_account.app_sa["prod"].email
326
326
  max_instance_request_concurrency = 40
327
327
 
328
328
  scaling {