agent-starter-pack 0.7.1__py3-none-any.whl → 0.9.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.
Files changed (64) hide show
  1. {agent_starter_pack-0.7.1.dist-info → agent_starter_pack-0.9.0.dist-info}/METADATA +7 -6
  2. {agent_starter_pack-0.7.1.dist-info → agent_starter_pack-0.9.0.dist-info}/RECORD +63 -59
  3. agents/README.md +7 -0
  4. agents/adk_base/{template/.templateconfig.yaml → .template/templateconfig.yaml} +3 -1
  5. agents/adk_base/notebooks/adk_app_testing.ipynb +8 -6
  6. agents/adk_gemini_fullstack/{template/.templateconfig.yaml → .template/templateconfig.yaml} +3 -2
  7. agents/adk_gemini_fullstack/notebooks/adk_app_testing.ipynb +8 -6
  8. agents/agentic_rag/{template/.templateconfig.yaml → .template/templateconfig.yaml} +2 -1
  9. agents/agentic_rag/notebooks/adk_app_testing.ipynb +8 -6
  10. agents/crewai_coding_crew/{template/.templateconfig.yaml → .template/templateconfig.yaml} +1 -1
  11. llm.txt +7 -0
  12. src/base_template/Makefile +5 -1
  13. src/base_template/README.md +2 -2
  14. src/base_template/deployment/cd/deploy-to-prod.yaml +1 -16
  15. src/base_template/deployment/cd/staging.yaml +4 -19
  16. src/base_template/deployment/terraform/apis.tf +2 -2
  17. src/base_template/deployment/terraform/build_triggers.tf +5 -5
  18. src/base_template/deployment/terraform/dev/apis.tf +8 -1
  19. src/base_template/deployment/terraform/dev/variables.tf +3 -1
  20. src/base_template/deployment/terraform/iam.tf +8 -8
  21. src/base_template/deployment/terraform/locals.tf +9 -2
  22. src/base_template/deployment/terraform/log_sinks.tf +2 -2
  23. src/base_template/deployment/terraform/service_accounts.tf +3 -3
  24. src/base_template/deployment/terraform/storage.tf +7 -7
  25. src/base_template/deployment/terraform/variables.tf +3 -0
  26. src/base_template/pyproject.toml +4 -3
  27. src/cli/commands/create.py +191 -41
  28. src/cli/commands/list.py +158 -0
  29. src/cli/commands/setup_cicd.py +2 -2
  30. src/cli/main.py +2 -0
  31. src/cli/utils/cicd.py +2 -2
  32. src/cli/utils/remote_template.py +254 -0
  33. src/cli/utils/template.py +134 -25
  34. src/deployment_targets/agent_engine/app/agent_engine_app.py +7 -7
  35. src/deployment_targets/agent_engine/tests/load_test/README.md +1 -6
  36. src/deployment_targets/agent_engine/tests/load_test/load_test.py +13 -3
  37. src/deployment_targets/cloud_run/Dockerfile +3 -0
  38. src/deployment_targets/cloud_run/app/server.py +18 -0
  39. src/deployment_targets/cloud_run/deployment/terraform/dev/service.tf +231 -0
  40. src/deployment_targets/cloud_run/deployment/terraform/service.tf +360 -0
  41. src/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +8 -5
  42. src/deployment_targets/cloud_run/tests/load_test/README.md +2 -2
  43. src/deployment_targets/cloud_run/tests/load_test/load_test.py +21 -17
  44. src/frontends/adk_gemini_fullstack/frontend/src/App.tsx +2 -3
  45. src/resources/docs/adk-cheatsheet.md +1 -1
  46. src/resources/locks/uv-adk_base-agent_engine.lock +873 -236
  47. src/resources/locks/uv-adk_base-cloud_run.lock +1169 -283
  48. src/resources/locks/uv-adk_gemini_fullstack-agent_engine.lock +873 -236
  49. src/resources/locks/uv-adk_gemini_fullstack-cloud_run.lock +1169 -283
  50. src/resources/locks/uv-agentic_rag-agent_engine.lock +508 -373
  51. src/resources/locks/uv-agentic_rag-cloud_run.lock +668 -469
  52. src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +582 -587
  53. src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +791 -733
  54. src/resources/locks/uv-langgraph_base_react-agent_engine.lock +587 -478
  55. src/resources/locks/uv-langgraph_base_react-cloud_run.lock +799 -627
  56. src/resources/locks/uv-live_api-cloud_run.lock +803 -603
  57. src/resources/setup_cicd/github.tf +2 -2
  58. src/utils/lock_utils.py +1 -1
  59. src/deployment_targets/cloud_run/uv.lock +0 -6952
  60. {agent_starter_pack-0.7.1.dist-info → agent_starter_pack-0.9.0.dist-info}/WHEEL +0 -0
  61. {agent_starter_pack-0.7.1.dist-info → agent_starter_pack-0.9.0.dist-info}/entry_points.txt +0 -0
  62. {agent_starter_pack-0.7.1.dist-info → agent_starter_pack-0.9.0.dist-info}/licenses/LICENSE +0 -0
  63. /agents/langgraph_base_react/{template/.templateconfig.yaml → .template/templateconfig.yaml} +0 -0
  64. /agents/live_api/{template/.templateconfig.yaml → .template/templateconfig.yaml} +0 -0
@@ -24,7 +24,14 @@ locals {
24
24
  "bigquery.googleapis.com",
25
25
  "serviceusage.googleapis.com",
26
26
  "logging.googleapis.com",
27
- "cloudtrace.googleapis.com"
27
+ "cloudtrace.googleapis.com",
28
+ {%- if "adk" in cookiecutter.tags and cookiecutter.session_type == "alloydb" %}
29
+ "compute.googleapis.com",
30
+ "servicenetworking.googleapis.com",
31
+ "alloydb.googleapis.com",
32
+ "secretmanager.googleapis.com",
33
+ "dns.googleapis.com"
34
+ {%- endif %}
28
35
  ]
29
36
  }
30
37
 
@@ -54,6 +54,9 @@ variable "agentengine_sa_roles" {
54
54
  {% endif %}
55
55
  type = list(string)
56
56
  default = [
57
+ {%- if "adk" in cookiecutter.tags and cookiecutter.session_type == "alloydb" %}
58
+ "roles/secretmanager.secretAccessor",
59
+ {%- endif %}
57
60
  "roles/aiplatform.user",
58
61
  "roles/discoveryengine.editor",
59
62
  "roles/logging.logWriter",
@@ -61,7 +64,6 @@ variable "agentengine_sa_roles" {
61
64
  "roles/storage.admin"
62
65
  ]
63
66
  }
64
-
65
67
  {% if cookiecutter.data_ingestion %}
66
68
 
67
69
  variable "pipelines_roles" {
@@ -25,7 +25,7 @@ resource "google_project_iam_member" "cicd_project_roles" {
25
25
  project = var.cicd_runner_project_id
26
26
  role = each.value
27
27
  member = "serviceAccount:${resource.google_service_account.cicd_runner_sa.email}"
28
- depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.shared_services]
28
+ depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.deploy_project_services]
29
29
 
30
30
  }
31
31
 
@@ -42,7 +42,7 @@ resource "google_project_iam_member" "other_projects_roles" {
42
42
  project = each.value.project_id
43
43
  role = each.value.role
44
44
  member = "serviceAccount:${resource.google_service_account.cicd_runner_sa.email}"
45
- depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.shared_services]
45
+ depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.deploy_project_services]
46
46
  }
47
47
  {% if cookiecutter.deployment_target == 'cloud_run' %}
48
48
  # 3. Allow Cloud Run service SA to pull containers stored in the CICD project
@@ -52,7 +52,7 @@ resource "google_project_iam_member" "cicd_run_invoker_artifact_registry_reader"
52
52
 
53
53
  role = "roles/artifactregistry.reader"
54
54
  member = "serviceAccount:service-${data.google_project.projects[each.key].number}@serverless-robot-prod.iam.gserviceaccount.com"
55
- depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.shared_services]
55
+ depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.deploy_project_services]
56
56
 
57
57
  }
58
58
 
@@ -69,7 +69,7 @@ resource "google_project_iam_member" "cloud_run_app_sa_roles" {
69
69
  project = each.value.project
70
70
  role = each.value.role
71
71
  member = "serviceAccount:${google_service_account.cloud_run_app_sa[split(",", each.key)[0]].email}"
72
- depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.shared_services]
72
+ depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.deploy_project_services]
73
73
  }
74
74
  {% elif cookiecutter.deployment_target == 'agent_engine' %}
75
75
  resource "google_project_service_identity" "vertex_sa" {
@@ -92,7 +92,7 @@ resource "google_project_iam_member" "vertex_ai_sa_permissions" {
92
92
  project = each.value.project
93
93
  role = each.value.role
94
94
  member = "serviceAccount:service-${data.google_project.projects[split("_", each.key)[0]].number}@gcp-sa-aiplatform-re.iam.gserviceaccount.com"
95
- depends_on = [resource.google_project_service.shared_services, resource.google_project_service_identity.vertex_sa]
95
+ depends_on = [resource.google_project_service.deploy_project_services, resource.google_project_service_identity.vertex_sa]
96
96
  }
97
97
  {% endif %}
98
98
 
@@ -101,14 +101,14 @@ resource "google_service_account_iam_member" "cicd_run_invoker_token_creator" {
101
101
  service_account_id = google_service_account.cicd_runner_sa.name
102
102
  role = "roles/iam.serviceAccountTokenCreator"
103
103
  member = "serviceAccount:${resource.google_service_account.cicd_runner_sa.email}"
104
- depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.shared_services]
104
+ depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.deploy_project_services]
105
105
  }
106
106
  # Special assignment: Allow the CICD SA to impersonate himself for trigger creation
107
107
  resource "google_service_account_iam_member" "cicd_run_invoker_account_user" {
108
108
  service_account_id = google_service_account.cicd_runner_sa.name
109
109
  role = "roles/iam.serviceAccountUser"
110
110
  member = "serviceAccount:${resource.google_service_account.cicd_runner_sa.email}"
111
- depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.shared_services]
111
+ depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.deploy_project_services]
112
112
  }
113
113
 
114
114
  {%- if cookiecutter.data_ingestion %}
@@ -125,6 +125,6 @@ resource "google_project_iam_member" "vertexai_pipeline_sa_roles" {
125
125
  project = each.value.project
126
126
  role = each.value.role
127
127
  member = "serviceAccount:${google_service_account.vertexai_pipeline_app_sa[split(",", each.key)[0]].email}"
128
- depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.shared_services]
128
+ depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.deploy_project_services]
129
129
  }
130
130
  {%- endif %}
@@ -23,7 +23,7 @@ locals {
23
23
  "cloudtrace.googleapis.com"
24
24
  ]
25
25
 
26
- shared_services = [
26
+ deploy_project_services = [
27
27
  "aiplatform.googleapis.com",
28
28
  "run.googleapis.com",
29
29
  "discoveryengine.googleapis.com",
@@ -32,7 +32,14 @@ locals {
32
32
  "bigquery.googleapis.com",
33
33
  "serviceusage.googleapis.com",
34
34
  "logging.googleapis.com",
35
- "cloudtrace.googleapis.com"
35
+ "cloudtrace.googleapis.com",
36
+ {%- if "adk" in cookiecutter.tags and cookiecutter.session_type == "alloydb" %}
37
+ "compute.googleapis.com",
38
+ "servicenetworking.googleapis.com",
39
+ "alloydb.googleapis.com",
40
+ "secretmanager.googleapis.com",
41
+ "dns.googleapis.com"
42
+ {%- endif %}
36
43
  ]
37
44
 
38
45
  deploy_project_ids = {
@@ -26,7 +26,7 @@ resource "google_bigquery_dataset" "feedback_dataset" {
26
26
  dataset_id = replace("${var.project_name}_feedback", "-", "_")
27
27
  friendly_name = "${var.project_name}_feedback"
28
28
  location = var.region
29
- depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.shared_services]
29
+ depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.deploy_project_services]
30
30
  }
31
31
 
32
32
  resource "google_bigquery_dataset" "telemetry_logs_dataset" {
@@ -35,7 +35,7 @@ resource "google_bigquery_dataset" "telemetry_logs_dataset" {
35
35
  dataset_id = replace("${var.project_name}_telemetry", "-", "_")
36
36
  friendly_name = "${var.project_name}_telemetry"
37
37
  location = var.region
38
- depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.shared_services]
38
+ depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.deploy_project_services]
39
39
  }
40
40
 
41
41
  module "log_export_to_bigquery" {
@@ -16,7 +16,7 @@ resource "google_service_account" "cicd_runner_sa" {
16
16
  account_id = "${var.project_name}-cb"
17
17
  display_name = "CICD Runner SA"
18
18
  project = var.cicd_runner_project_id
19
- depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.shared_services]
19
+ depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.deploy_project_services]
20
20
  }
21
21
  {% if cookiecutter.deployment_target == 'cloud_run' %}
22
22
  resource "google_service_account" "cloud_run_app_sa" {
@@ -25,7 +25,7 @@ resource "google_service_account" "cloud_run_app_sa" {
25
25
  account_id = "${var.project_name}-cr"
26
26
  display_name = "Cloud Run Generative AI app SA"
27
27
  project = each.value
28
- depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.shared_services]
28
+ depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.deploy_project_services]
29
29
  }
30
30
  {% endif %}
31
31
 
@@ -37,6 +37,6 @@ resource "google_service_account" "vertexai_pipeline_app_sa" {
37
37
  account_id = "${var.project_name}-rag"
38
38
  display_name = "Vertex AI Pipeline app SA"
39
39
  project = each.value
40
- depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.shared_services]
40
+ depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.deploy_project_services]
41
41
  }
42
42
  {% endif %}
@@ -23,7 +23,7 @@ resource "google_storage_bucket" "bucket_load_test_results" {
23
23
  project = var.cicd_runner_project_id
24
24
  uniform_bucket_level_access = true
25
25
  force_destroy = true
26
- depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.shared_services]
26
+ depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.deploy_project_services]
27
27
  }
28
28
 
29
29
  resource "google_storage_bucket" "logs_data_bucket" {
@@ -34,7 +34,7 @@ resource "google_storage_bucket" "logs_data_bucket" {
34
34
  uniform_bucket_level_access = true
35
35
  force_destroy = true
36
36
 
37
- depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.shared_services]
37
+ depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.deploy_project_services]
38
38
  }
39
39
  {% if cookiecutter.deployment_target == 'cloud_run' %}
40
40
  resource "google_artifact_registry_repository" "repo-artifacts-genai" {
@@ -43,7 +43,7 @@ resource "google_artifact_registry_repository" "repo-artifacts-genai" {
43
43
  description = "Repo for Generative AI applications"
44
44
  format = "DOCKER"
45
45
  project = var.cicd_runner_project_id
46
- depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.shared_services]
46
+ depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.deploy_project_services]
47
47
  }
48
48
  {% endif %}
49
49
 
@@ -56,7 +56,7 @@ resource "google_storage_bucket" "data_ingestion_pipeline_gcs_root" {
56
56
  uniform_bucket_level_access = true
57
57
  force_destroy = true
58
58
 
59
- depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.shared_services]
59
+ depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.deploy_project_services]
60
60
  }
61
61
 
62
62
  {% if cookiecutter.datastore_type == "vertex_ai_search" %}
@@ -71,7 +71,7 @@ resource "google_discovery_engine_data_store" "data_store_staging" {
71
71
  solution_types = ["SOLUTION_TYPE_SEARCH"]
72
72
  create_advanced_site_search = false
73
73
  provider = google.staging_billing_override
74
- depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.shared_services]
74
+ depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.deploy_project_services]
75
75
  }
76
76
 
77
77
  resource "google_discovery_engine_search_engine" "search_engine_staging" {
@@ -97,7 +97,7 @@ resource "google_discovery_engine_data_store" "data_store_prod" {
97
97
  solution_types = ["SOLUTION_TYPE_SEARCH"]
98
98
  create_advanced_site_search = false
99
99
  provider = google.prod_billing_override
100
- depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.shared_services]
100
+ depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.deploy_project_services]
101
101
  }
102
102
 
103
103
  resource "google_discovery_engine_search_engine" "search_engine_prod" {
@@ -122,7 +122,7 @@ resource "google_storage_bucket" "vector_search_data_bucket" {
122
122
  uniform_bucket_level_access = true
123
123
  force_destroy = true
124
124
 
125
- depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.shared_services]
125
+ depends_on = [resource.google_project_service.cicd_services, resource.google_project_service.deploy_project_services]
126
126
  }
127
127
 
128
128
  resource "google_vertex_ai_index" "vector_search_index_staging" {
@@ -76,6 +76,7 @@ variable "agentengine_sa_roles" {
76
76
  default = [
77
77
  {%- if cookiecutter.deployment_target == 'cloud_run' %}
78
78
  "roles/run.invoker",
79
+ "roles/secretmanager.secretAccessor",
79
80
  {%- endif %}
80
81
  "roles/aiplatform.user",
81
82
  "roles/discoveryengine.editor",
@@ -84,6 +85,8 @@ variable "agentengine_sa_roles" {
84
85
  "roles/storage.admin"
85
86
  ]
86
87
  }
88
+ {%- if cookiecutter.deployment_target == 'cloud_run' %}
89
+ {%- endif %}
87
90
 
88
91
  variable "cicd_roles" {
89
92
  description = "List of roles to assign to the CICD runner service account in the CICD project"
@@ -16,11 +16,12 @@ dependencies = [
16
16
  {%- endif %}
17
17
  "google-cloud-logging~=3.11.4",
18
18
  {%- if cookiecutter.deployment_target == 'cloud_run' %}
19
- "google-cloud-aiplatform[evaluation]~=1.99.0",
19
+ "google-cloud-aiplatform[evaluation]~=1.103.0",
20
20
  "fastapi~=0.115.8",
21
- "uvicorn~=0.34.0"
21
+ "uvicorn~=0.34.0",
22
+ "psycopg2-binary>=2.9.10",
22
23
  {%- elif cookiecutter.deployment_target == 'agent_engine' %}
23
- "google-cloud-aiplatform[evaluation,agent-engines]~=1.99.0"
24
+ "google-cloud-aiplatform[evaluation,agent-engines]~=1.103.0"
24
25
  {%- endif %}
25
26
  ]
26
27
  {% if cookiecutter.deployment_target == 'cloud_run' %}
@@ -25,6 +25,13 @@ from rich.prompt import IntPrompt, Prompt
25
25
  from ..utils.datastores import DATASTORE_TYPES
26
26
  from ..utils.gcp import verify_credentials, verify_vertex_connection
27
27
  from ..utils.logging import handle_cli_error
28
+ from ..utils.remote_template import (
29
+ fetch_remote_template,
30
+ get_base_template_name,
31
+ load_remote_template_config,
32
+ merge_template_configs,
33
+ parse_agent_spec,
34
+ )
28
35
  from ..utils.template import (
29
36
  get_available_agents,
30
37
  get_template_path,
@@ -32,6 +39,7 @@ from ..utils.template import (
32
39
  process_template,
33
40
  prompt_datastore_selection,
34
41
  prompt_deployment_target,
42
+ prompt_session_type_selection,
35
43
  )
36
44
 
37
45
  console = Console()
@@ -74,7 +82,11 @@ def normalize_project_name(project_name: str) -> str:
74
82
  @click.command()
75
83
  @click.pass_context
76
84
  @click.argument("project_name")
77
- @click.option("--agent", "-a", help="agent name or number to use")
85
+ @click.option(
86
+ "--agent",
87
+ "-a",
88
+ help="Template identifier to use. Can be a local agent name (e.g., `chat_agent`), a local path (`local@/path/to/template`), an `adk-samples` shortcut (e.g., `adk@data-science`), or a remote Git URL. Both shorthand (e.g., `github.com/org/repo/path@main`) and full URLs from your browser (e.g., `https://github.com/org/repo/tree/main/path`) are supported. Lists available local templates if omitted.",
89
+ )
78
90
  @click.option(
79
91
  "--deployment-target",
80
92
  "-d",
@@ -93,6 +105,11 @@ def normalize_project_name(project_name: str) -> str:
93
105
  type=click.Choice(DATASTORE_TYPES),
94
106
  help="Type of datastore to use for data ingestion (requires --include-data-ingestion)",
95
107
  )
108
+ @click.option(
109
+ "--session-type",
110
+ type=click.Choice(["in_memory", "alloydb"]),
111
+ help="Type of session storage to use",
112
+ )
96
113
  @click.option("--debug", is_flag=True, help="Enable debug logging")
97
114
  @click.option(
98
115
  "--output-dir",
@@ -122,6 +139,7 @@ def create(
122
139
  deployment_target: str | None,
123
140
  include_data_ingestion: bool,
124
141
  datastore: str | None,
142
+ session_type: str | None,
125
143
  debug: bool,
126
144
  output_dir: str | None,
127
145
  auto_approve: bool,
@@ -131,11 +149,26 @@ def create(
131
149
  """Create GCP-based AI agent projects from templates."""
132
150
  try:
133
151
  # Display welcome banner
134
- console.print("\n=== GCP Agent Starter Pack :rocket:===", style="bold blue")
135
- console.print("Welcome to the Agent Starter Pack!")
136
- console.print(
137
- "This tool will help you create an end-to-end production-ready AI agent in Google Cloud!\n"
138
- )
152
+ if agent and agent.startswith("adk@"):
153
+ console.print(
154
+ "\n=== Welcome to [link=https://github.com/google/adk-samples]google/adk-samples[/link]! ✨ ===",
155
+ style="bold blue",
156
+ )
157
+ console.print(
158
+ "Powered by ([link=https://goo.gle/agent-starter-pack]Google's Agent Starter Pack [/link])\n",
159
+ )
160
+ console.print(
161
+ "This tool will help you create an end-to-end production-ready AI agent in Google Cloud!\n"
162
+ )
163
+ else:
164
+ console.print(
165
+ "\n=== Google Cloud Agent Starter Pack :rocket:===",
166
+ style="bold blue",
167
+ )
168
+ console.print("Welcome to the Agent Starter Pack!")
169
+ console.print(
170
+ "This tool will help you create an end-to-end production-ready AI agent in Google Cloud!\n"
171
+ )
139
172
  # Validate project name
140
173
  if len(project_name) > 26:
141
174
  console.print(
@@ -165,23 +198,51 @@ def create(
165
198
  )
166
199
  return
167
200
 
168
- # Agent selection
201
+ # Agent selection - handle remote templates
169
202
  selected_agent = None
203
+ template_source_path = None
204
+
170
205
  if agent:
171
- agents = get_available_agents()
172
- # First check if it's a valid agent name
173
- if any(p["name"] == agent for p in agents.values()):
174
- selected_agent = agent
206
+ if agent.startswith("local@"):
207
+ path_str = agent.split("@", 1)[1]
208
+ template_source_path = pathlib.Path(path_str).resolve()
209
+ if not template_source_path.is_dir():
210
+ raise click.ClickException(
211
+ f"Local path not found or not a directory: {template_source_path}"
212
+ )
213
+ selected_agent = f"local_{template_source_path.name}"
214
+ console.print(f"Using local template: {template_source_path}")
175
215
  else:
176
- # Try numeric agent selection if input is a number
177
- try:
178
- agent_num = int(agent)
179
- if agent_num in agents:
180
- selected_agent = agents[agent_num]["name"]
216
+ # Check if it's a remote template specification
217
+ remote_spec = parse_agent_spec(agent)
218
+ if remote_spec:
219
+ if remote_spec.is_adk_samples:
220
+ console.print(
221
+ f"> Fetching template: {remote_spec.template_path}",
222
+ style="bold blue",
223
+ )
224
+ else:
225
+ console.print(f"Fetching remote template: {agent}")
226
+ template_source_path = fetch_remote_template(remote_spec)
227
+ selected_agent = f"remote_{hash(agent)}" # Generate unique name for remote template
228
+ else:
229
+ # Handle local agent selection
230
+ agents = get_available_agents()
231
+ # First check if it's a valid agent name
232
+ if any(p["name"] == agent for p in agents.values()):
233
+ selected_agent = agent
181
234
  else:
182
- raise ValueError(f"Invalid agent number: {agent_num}")
183
- except ValueError as err:
184
- raise ValueError(f"Invalid agent name or number: {agent}") from err
235
+ # Try numeric agent selection if input is a number
236
+ try:
237
+ agent_num = int(agent)
238
+ if agent_num in agents:
239
+ selected_agent = agents[agent_num]["name"]
240
+ else:
241
+ raise ValueError(f"Invalid agent number: {agent_num}")
242
+ except ValueError as err:
243
+ raise ValueError(
244
+ f"Invalid agent name or number: {agent}"
245
+ ) from err
185
246
 
186
247
  final_agent = (
187
248
  selected_agent
@@ -191,13 +252,54 @@ def create(
191
252
  if debug:
192
253
  logging.debug(f"Selected agent: {agent}")
193
254
 
194
- template_path = (
195
- pathlib.Path(__file__).parent.parent.parent.parent
196
- / "agents"
197
- / final_agent
198
- / "template"
199
- )
200
- config = load_template_config(template_path)
255
+ # Load template configuration based on whether it's remote or local
256
+ if template_source_path:
257
+ # Load remote template config
258
+ source_config = load_remote_template_config(template_source_path)
259
+
260
+ # Check if remote template has the required structure
261
+ template_config_path = (
262
+ template_source_path / ".template" / "templateconfig.yaml"
263
+ )
264
+
265
+ if not template_config_path.exists():
266
+ console.print(
267
+ "Error: Template is invalid or improperly structured.",
268
+ style="bold red",
269
+ )
270
+ console.print(
271
+ "Templates must contain a '.template/templateconfig.yaml' file.\n\n"
272
+ "Expected structure:\n"
273
+ " your-template/\n"
274
+ " └── .template/\n"
275
+ " ├── templateconfig.yaml\n"
276
+ " └── [other template files...]",
277
+ style="yellow",
278
+ )
279
+ return
280
+
281
+ # Load base template config for inheritance
282
+ base_template_name = get_base_template_name(source_config)
283
+ base_template_path = (
284
+ pathlib.Path(__file__).parent.parent.parent.parent
285
+ / "agents"
286
+ / base_template_name
287
+ / ".template"
288
+ )
289
+ base_config = load_template_config(base_template_path)
290
+
291
+ # Merge configs: remote inherits from and overrides base
292
+ config = merge_template_configs(base_config, source_config)
293
+ # For remote templates, use the template/ subdirectory as the template source
294
+ template_path = template_source_path / ".template"
295
+ else:
296
+ template_path = (
297
+ pathlib.Path(__file__).parent.parent.parent.parent
298
+ / "agents"
299
+ / final_agent
300
+ / ".template"
301
+ )
302
+ config = load_template_config(template_path)
201
303
  # Data ingestion and datastore selection
202
304
  if include_data_ingestion or datastore:
203
305
  # If datastore is specified but include_data_ingestion is not, set it to True
@@ -224,14 +326,54 @@ def create(
224
326
  logging.debug(f"Selected datastore type: {datastore}")
225
327
 
226
328
  # Deployment target selection
329
+ # For remote templates, we need to use the base template name for deployment target selection
330
+ deployment_agent_name = final_agent
331
+ remote_config = None
332
+ if template_source_path:
333
+ # Use the base template name from remote config for deployment target selection
334
+ deployment_agent_name = get_base_template_name(config)
335
+ remote_config = config
336
+
227
337
  final_deployment = (
228
338
  deployment_target
229
339
  if deployment_target
230
- else prompt_deployment_target(final_agent)
340
+ else prompt_deployment_target(
341
+ deployment_agent_name, remote_config=remote_config
342
+ )
231
343
  )
232
344
  if debug:
233
345
  logging.debug(f"Selected deployment target: {final_deployment}")
234
346
 
347
+ # Session type validation and selection (only for agents that require session management)
348
+ final_session_type = session_type
349
+
350
+ # Check if agent requires session management
351
+ requires_session = config.get("settings", {}).get("requires_session", False)
352
+
353
+ if requires_session:
354
+ if final_deployment == "agent_engine" and session_type:
355
+ console.print(
356
+ "Error: --session-type cannot be used with agent_engine deployment target. "
357
+ "Agent Engine handles session management internally.",
358
+ style="bold red",
359
+ )
360
+ return
361
+
362
+ if final_deployment in ("cloud_run") and not session_type:
363
+ final_session_type = prompt_session_type_selection()
364
+ else:
365
+ # Agents that don't require session management always use in-memory sessions
366
+ final_session_type = "in_memory"
367
+ if session_type and session_type != "in_memory":
368
+ console.print(
369
+ "Warning: Session type options are only available for agents that require session management. "
370
+ "Using in-memory sessions for this agent.",
371
+ style="yellow",
372
+ )
373
+
374
+ if debug and final_session_type:
375
+ logging.debug(f"Selected session type: {final_session_type}")
376
+
235
377
  # Region confirmation (if not explicitly passed)
236
378
  if (
237
379
  not auto_approve
@@ -270,7 +412,10 @@ def create(
270
412
  )
271
413
 
272
414
  # Process template
273
- template_path = get_template_path(final_agent, debug=debug)
415
+ if not template_source_path:
416
+ template_path = get_template_path(final_agent, debug=debug)
417
+ # template_path is already set above for remote templates
418
+
274
419
  if debug:
275
420
  logging.debug(f"Template path: {template_path}")
276
421
  logging.debug(f"Processing template for project: {project_name}")
@@ -282,19 +427,24 @@ def create(
282
427
  if debug:
283
428
  logging.debug(f"Output directory: {destination_dir}")
284
429
 
285
- process_template(
286
- final_agent,
287
- template_path,
288
- project_name,
289
- deployment_target=final_deployment,
290
- include_data_ingestion=include_data_ingestion,
291
- datastore=datastore,
292
- output_dir=destination_dir,
293
- )
294
-
295
- # Replace region in all files if a different region was specified
296
- if region != "us-central1":
297
- replace_region_in_files(project_path, region, debug=debug)
430
+ try:
431
+ # Process template (handles both local and remote templates)
432
+ process_template(
433
+ final_agent,
434
+ template_path,
435
+ project_name,
436
+ deployment_target=final_deployment,
437
+ include_data_ingestion=include_data_ingestion,
438
+ datastore=datastore,
439
+ session_type=final_session_type,
440
+ output_dir=destination_dir,
441
+ remote_template_path=template_source_path,
442
+ remote_config=config if template_source_path else None,
443
+ )
444
+ finally:
445
+ # Replace region in all files if a different region was specified
446
+ if region != "us-central1":
447
+ replace_region_in_files(project_path, region, debug=debug)
298
448
 
299
449
  project_path = destination_dir / project_name
300
450
  cd_path = project_path if output_dir else project_name