agent-starter-pack 0.13.0__py3-none-any.whl → 0.14.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 (24) hide show
  1. {agent_starter_pack-0.13.0.dist-info → agent_starter_pack-0.14.0.dist-info}/METADATA +11 -3
  2. {agent_starter_pack-0.13.0.dist-info → agent_starter_pack-0.14.0.dist-info}/RECORD +22 -24
  3. agents/adk_base/notebooks/evaluating_adk_agent.ipynb +78 -71
  4. agents/agentic_rag/notebooks/evaluating_adk_agent.ipynb +78 -71
  5. llm.txt +87 -39
  6. src/base_template/Makefile +17 -2
  7. src/cli/commands/create.py +27 -5
  8. src/cli/commands/enhance.py +132 -6
  9. src/cli/commands/setup_cicd.py +91 -69
  10. src/cli/utils/cicd.py +105 -0
  11. src/cli/utils/gcp.py +19 -13
  12. src/cli/utils/logging.py +13 -1
  13. src/cli/utils/template.py +3 -0
  14. src/frontends/live_api_react/frontend/package-lock.json +9 -9
  15. src/frontends/live_api_react/frontend/src/App.tsx +12 -153
  16. src/frontends/live_api_react/frontend/src/components/side-panel/SidePanel.tsx +352 -3
  17. src/frontends/live_api_react/frontend/src/components/side-panel/side-panel.scss +249 -2
  18. src/frontends/live_api_react/frontend/src/utils/multimodal-live-client.ts +4 -1
  19. src/resources/docs/adk-cheatsheet.md +285 -38
  20. src/frontends/live_api_react/frontend/src/components/control-tray/ControlTray.tsx +0 -217
  21. src/frontends/live_api_react/frontend/src/components/control-tray/control-tray.scss +0 -201
  22. {agent_starter_pack-0.13.0.dist-info → agent_starter_pack-0.14.0.dist-info}/WHEEL +0 -0
  23. {agent_starter_pack-0.13.0.dist-info → agent_starter_pack-0.14.0.dist-info}/entry_points.txt +0 -0
  24. {agent_starter_pack-0.13.0.dist-info → agent_starter_pack-0.14.0.dist-info}/licenses/LICENSE +0 -0
@@ -18,7 +18,7 @@ from typing import Any
18
18
 
19
19
  import click
20
20
  from rich.console import Console
21
- from rich.prompt import IntPrompt
21
+ from rich.prompt import IntPrompt, Prompt
22
22
 
23
23
  if sys.version_info >= (3, 11):
24
24
  import tomllib
@@ -36,6 +36,19 @@ from .create import (
36
36
 
37
37
  console = Console()
38
38
 
39
+ # Directories to exclude when scanning for agent directories
40
+ _EXCLUDED_DIRS = {
41
+ ".git",
42
+ ".github",
43
+ "__pycache__",
44
+ "node_modules",
45
+ ".venv",
46
+ "venv",
47
+ "build",
48
+ "dist",
49
+ ".terraform",
50
+ }
51
+
39
52
 
40
53
  def display_base_template_selection(current_base: str) -> str:
41
54
  """Display available base templates and prompt for selection."""
@@ -79,6 +92,88 @@ def display_base_template_selection(current_base: str) -> str:
79
92
  raise ValueError(f"Invalid base template selection: {choice}")
80
93
 
81
94
 
95
+ def display_agent_directory_selection(
96
+ current_dir: pathlib.Path, detected_directory: str
97
+ ) -> str:
98
+ """Display available directories and prompt for agent directory selection."""
99
+ console.print()
100
+ console.print("📁 [bold]Agent Directory Selection[/bold]")
101
+ console.print()
102
+ console.print("Choose where your agent code is located:")
103
+
104
+ # Get all directories in the current path (excluding hidden and common non-agent dirs)
105
+ available_dirs = [
106
+ item.name
107
+ for item in current_dir.iterdir()
108
+ if (
109
+ item.is_dir()
110
+ and not item.name.startswith(".")
111
+ and item.name not in _EXCLUDED_DIRS
112
+ )
113
+ ]
114
+
115
+ # Sort directories and create choices
116
+ available_dirs.sort()
117
+
118
+ directory_choices = {}
119
+ choice_num = 1
120
+ default_choice = None
121
+
122
+ # Only include the detected directory if it actually exists
123
+ if detected_directory in available_dirs:
124
+ directory_choices[choice_num] = detected_directory
125
+ current_indicator = (
126
+ " (detected)" if detected_directory != "app" else " (default)"
127
+ )
128
+ console.print(
129
+ f" {choice_num}. [bold]{detected_directory}[/]{current_indicator}"
130
+ )
131
+ default_choice = choice_num
132
+ choice_num += 1
133
+ # Remove from available_dirs to avoid duplication
134
+ available_dirs.remove(detected_directory)
135
+
136
+ # Add other available directories
137
+ for dir_name in available_dirs:
138
+ directory_choices[choice_num] = dir_name
139
+ # Check if this directory might contain agent code
140
+ agent_files_exist = any((current_dir / dir_name).glob("*agent*.py"))
141
+ hint = " (contains agent*.py)" if agent_files_exist else ""
142
+ console.print(f" {choice_num}. [bold]{dir_name}[/]{hint}")
143
+ if (
144
+ default_choice is None
145
+ ): # If no detected directory exists, use first available as default
146
+ default_choice = choice_num
147
+ choice_num += 1
148
+
149
+ # Add option for custom directory
150
+ custom_choice = choice_num
151
+ directory_choices[custom_choice] = "__custom__"
152
+ console.print(f" {custom_choice}. [bold]Enter custom directory name[/]")
153
+
154
+ # If no directories found and no default set, default to custom option
155
+ if default_choice is None:
156
+ default_choice = custom_choice
157
+
158
+ console.print()
159
+ choice = IntPrompt.ask(
160
+ "Select agent directory", default=default_choice, show_default=True
161
+ )
162
+
163
+ if choice in directory_choices:
164
+ selected = directory_choices[choice]
165
+ if selected == "__custom__":
166
+ console.print()
167
+ custom_dir = Prompt.ask(
168
+ "Enter custom agent directory name", default=detected_directory
169
+ )
170
+ return custom_dir
171
+ else:
172
+ return selected
173
+ else:
174
+ raise ValueError(f"Invalid agent directory selection: {choice}")
175
+
176
+
82
177
  @click.command()
83
178
  @click.pass_context
84
179
  @click.argument(
@@ -94,11 +189,13 @@ def display_base_template_selection(current_base: str) -> str:
94
189
  )
95
190
  @click.option(
96
191
  "--base-template",
192
+ "-b",
97
193
  help="Base template to inherit from (e.g., adk_base, langgraph_base_react, agentic_rag)",
98
194
  )
99
195
  @click.option(
100
- "--agent-directory",
101
- help="Custom directory name for agent files (default: 'app' or auto-detected from pyproject.toml)",
196
+ "--adk",
197
+ is_flag=True,
198
+ help="Shortcut for --base-template adk_base",
102
199
  )
103
200
  @shared_template_options
104
201
  @handle_cli_error
@@ -115,7 +212,9 @@ def enhance(
115
212
  auto_approve: bool,
116
213
  region: str,
117
214
  skip_checks: bool,
215
+ agent_garden: bool,
118
216
  base_template: str | None,
217
+ adk: bool,
119
218
  agent_directory: str | None,
120
219
  ) -> None:
121
220
  """Enhance your existing project with AI agent capabilities.
@@ -139,6 +238,14 @@ def enhance(
139
238
  # Display welcome banner for enhance command
140
239
  display_welcome_banner(enhance_mode=True)
141
240
 
241
+ # Handle --adk shortcut
242
+ if adk:
243
+ if base_template:
244
+ raise click.ClickException(
245
+ "Cannot use --adk with --base-template. Use one or the other."
246
+ )
247
+ base_template = "adk_base"
248
+
142
249
  # Validate base template if provided
143
250
  if base_template and not validate_base_template(base_template):
144
251
  available_templates = get_available_base_templates()
@@ -282,7 +389,21 @@ def enhance(
282
389
  )
283
390
  pass # Fall back to default
284
391
 
285
- final_agent_directory = agent_directory or detected_agent_directory
392
+ # Check if detected/default app folder exists before showing interactive selection
393
+ app_folder_exists = (current_dir / detected_agent_directory).exists()
394
+
395
+ # Interactive agent directory selection if not provided via CLI, no app folder exists, and not auto-approved
396
+ if not agent_directory and not app_folder_exists and not auto_approve:
397
+ selected_agent_directory = display_agent_directory_selection(
398
+ current_dir, detected_agent_directory
399
+ )
400
+ final_agent_directory = selected_agent_directory
401
+ console.print(
402
+ f"✅ Selected agent directory: [cyan]{selected_agent_directory}[/cyan]"
403
+ )
404
+ console.print()
405
+ else:
406
+ final_agent_directory = agent_directory or detected_agent_directory
286
407
 
287
408
  # Show info about agent directory selection
288
409
  if agent_directory:
@@ -358,7 +479,9 @@ def enhance(
358
479
  final_cli_overrides: dict[str, Any] = {}
359
480
  if base_template:
360
481
  final_cli_overrides["base_template"] = base_template
361
- if agent_directory:
482
+
483
+ # For current directory templates, ensure agent_directory is included in cli_overrides
484
+ if template_path == pathlib.Path(".") and agent_directory:
362
485
  final_cli_overrides["settings"] = final_cli_overrides.get("settings", {})
363
486
  final_cli_overrides["settings"]["agent_directory"] = agent_directory
364
487
 
@@ -378,7 +501,10 @@ def enhance(
378
501
  region=region,
379
502
  skip_checks=skip_checks,
380
503
  in_folder=True, # Always use in-folder mode for enhance
381
- agent_directory=agent_directory,
504
+ agent_directory=final_agent_directory
505
+ if template_path == pathlib.Path(".")
506
+ else agent_directory,
507
+ agent_garden=agent_garden,
382
508
  base_template=base_template,
383
509
  skip_welcome=True, # Skip welcome message since enhance shows its own
384
510
  cli_overrides=final_cli_overrides if final_cli_overrides else None,
@@ -272,15 +272,24 @@ def update_build_triggers(tf_dir: Path) -> None:
272
272
 
273
273
 
274
274
  def prompt_for_repository_details(
275
- repository_name: str | None = None, repository_owner: str | None = None
275
+ repository_name: str | None = None,
276
+ repository_owner: str | None = None,
277
+ create_repository: bool = False,
278
+ use_existing_repository: bool = False,
276
279
  ) -> tuple[str, str, bool]:
277
280
  """Interactive prompt for repository details with option to use existing repo."""
278
281
  # Get current GitHub username as default owner
279
282
  result = run_command(["gh", "api", "user", "--jq", ".login"], capture_output=True)
280
283
  default_owner = result.stdout.strip()
281
- create_repository = False
282
284
 
283
- if not (repository_name and repository_owner):
285
+ # Step 1: Determine create_repository value
286
+ if create_repository and use_existing_repository:
287
+ raise ValueError(
288
+ "Cannot specify both create_repository and use_existing_repository"
289
+ )
290
+
291
+ # If neither flag is set, prompt for the choice
292
+ if not create_repository and not use_existing_repository:
284
293
  console.print("\n📦 Repository Configuration", style="bold blue")
285
294
  console.print("Choose an option:")
286
295
  console.print("1. Create new repository")
@@ -289,44 +298,41 @@ def prompt_for_repository_details(
289
298
  choice = click.prompt(
290
299
  "Select option", type=click.Choice(["1", "2"]), default="1"
291
300
  )
292
- if choice == "1":
293
- # New repository
294
- create_repository = True
295
- if not repository_name:
296
- # Get project name from pyproject.toml
297
- with open("pyproject.toml", encoding="utf-8") as f:
298
- for line in f:
299
- if line.strip().startswith("name ="):
300
- default_name = line.split("=")[1].strip().strip("\"'")
301
- break
302
- else:
303
- default_name = f"genai-app-{int(time.time())}"
304
- repository_name = click.prompt(
305
- "Enter new repository name", default=default_name
306
- )
307
- if not repository_owner:
308
- repository_owner = click.prompt(
309
- "Enter repository owner", default=default_owner
310
- )
311
- else:
312
- # Existing repository
313
- create_repository = False
314
- while True:
315
- repo_url = click.prompt(
316
- "Enter existing repository URL (e.g., https://github.com/owner/repo)"
317
- )
318
- # Extract owner and repo name from URL
319
- match = re.search(r"github\.com/([^/]+)/([^/]+)", repo_url)
320
- if match:
321
- repository_owner = match.group(1)
322
- # Remove .git suffix if present
323
- repository_name = match.group(2).rstrip(".git")
324
- break
301
+ create_repository = choice == "1"
302
+ # If use_existing_repository is True, create_repository should be False
303
+ elif use_existing_repository:
304
+ create_repository = False
305
+ # Otherwise create_repository is already True from the flag
306
+
307
+ # Step 2: Get repository name if missing
308
+ if not repository_name:
309
+ # Get project name from pyproject.toml as default
310
+ try:
311
+ with open("pyproject.toml", encoding="utf-8") as f:
312
+ for line in f:
313
+ if line.strip().startswith("name ="):
314
+ default_name = line.split("=")[1].strip().strip("\"'")
315
+ break
325
316
  else:
326
- console.print(
327
- "❌ Invalid repository URL format. Please use https://github.com/owner/repo",
328
- style="bold red",
329
- )
317
+ default_name = f"genai-app-{int(time.time())}"
318
+ except FileNotFoundError:
319
+ default_name = f"genai-app-{int(time.time())}"
320
+
321
+ prompt_text = (
322
+ "Enter new repository name"
323
+ if create_repository
324
+ else "Enter existing repository name"
325
+ )
326
+ repository_name = click.prompt(prompt_text, default=default_name)
327
+
328
+ # Step 3: Get repository owner if missing
329
+ if not repository_owner:
330
+ prompt_text = (
331
+ "Enter repository owner"
332
+ if create_repository
333
+ else "Enter existing repository owner"
334
+ )
335
+ repository_owner = click.prompt(prompt_text, default=default_owner)
330
336
 
331
337
  if repository_name is None or repository_owner is None:
332
338
  raise ValueError("Repository name and owner must be provided")
@@ -487,6 +493,12 @@ console = Console()
487
493
  default=False,
488
494
  help="Flag indicating whether to create a new repository",
489
495
  )
496
+ @click.option(
497
+ "--use-existing-repository",
498
+ is_flag=True,
499
+ default=False,
500
+ help="Flag indicating whether to use an existing repository",
501
+ )
490
502
  @backoff.on_exception(
491
503
  backoff.expo,
492
504
  (subprocess.CalledProcessError, click.ClickException),
@@ -508,9 +520,16 @@ def setup_cicd(
508
520
  debug: bool,
509
521
  auto_approve: bool,
510
522
  create_repository: bool,
523
+ use_existing_repository: bool,
511
524
  ) -> None:
512
525
  """Set up CI/CD infrastructure using Terraform."""
513
526
 
527
+ # Validate mutually exclusive flags
528
+ if create_repository and use_existing_repository:
529
+ raise click.UsageError(
530
+ "Cannot specify both --create-repository and --use-existing-repository flags"
531
+ )
532
+
514
533
  # Check if we're in the root folder by looking for pyproject.toml
515
534
  if not Path("pyproject.toml").exists():
516
535
  raise click.UsageError(
@@ -592,36 +611,39 @@ def setup_cicd(
592
611
  console.print("\n🔍 Checking GitHub CLI scopes...")
593
612
  check_github_scopes(cicd_runner)
594
613
 
595
- # Gather repository details if not provided
596
- if not (repository_name and repository_owner):
597
- if auto_approve:
598
- # Auto-generate repository details when auto-approve is used
599
- if not repository_owner:
600
- repository_owner = run_command(
601
- ["gh", "api", "user", "--jq", ".login"], capture_output=True
602
- ).stdout.strip()
603
- if not repository_name:
604
- # Get project name from pyproject.toml or generate one
605
- try:
606
- with open("pyproject.toml", encoding="utf-8") as f:
607
- for line in f:
608
- if line.strip().startswith("name ="):
609
- repository_name = (
610
- line.split("=")[1].strip().strip("\"'")
611
- )
612
- break
613
- else:
614
- repository_name = f"genai-app-{int(time.time())}"
615
- except FileNotFoundError:
616
- repository_name = f"genai-app-{int(time.time())}"
617
- console.print(
618
- f"✅ Auto-generated repository: {repository_owner}/{repository_name}"
619
- )
620
- # Keep the CLI argument value for create_repository
621
- else:
622
- repository_name, repository_owner, create_repository = (
623
- prompt_for_repository_details(repository_name, repository_owner)
614
+ # Gather repository details
615
+ if auto_approve:
616
+ # Auto-generate repository details when auto-approve is used
617
+ if not repository_owner:
618
+ repository_owner = run_command(
619
+ ["gh", "api", "user", "--jq", ".login"], capture_output=True
620
+ ).stdout.strip()
621
+ if not repository_name:
622
+ # Get project name from pyproject.toml or generate one
623
+ try:
624
+ with open("pyproject.toml", encoding="utf-8") as f:
625
+ for line in f:
626
+ if line.strip().startswith("name ="):
627
+ repository_name = line.split("=")[1].strip().strip("\"'")
628
+ break
629
+ else:
630
+ repository_name = f"genai-app-{int(time.time())}"
631
+ except FileNotFoundError:
632
+ repository_name = f"genai-app-{int(time.time())}"
633
+ console.print(
634
+ f"✅ Auto-generated repository: {repository_owner}/{repository_name}"
635
+ )
636
+ # Keep the CLI argument value for create_repository
637
+ else:
638
+ # Use prompt_for_repository_details to fill in any missing information
639
+ repository_name, repository_owner, create_repository = (
640
+ prompt_for_repository_details(
641
+ repository_name,
642
+ repository_owner,
643
+ create_repository,
644
+ use_existing_repository,
624
645
  )
646
+ )
625
647
 
626
648
  assert repository_name is not None, "Repository name must be provided"
627
649
  assert repository_owner is not None, "Repository owner must be provided"
src/cli/utils/cicd.py CHANGED
@@ -103,6 +103,111 @@ def create_github_connection(
103
103
  """
104
104
  console.print("\n🔗 Creating GitHub connection...")
105
105
 
106
+ # First, ensure Cloud Build API is enabled
107
+ console.print("🔧 Ensuring Cloud Build API is enabled...")
108
+ try:
109
+ run_command(
110
+ [
111
+ "gcloud",
112
+ "services",
113
+ "enable",
114
+ "cloudbuild.googleapis.com",
115
+ "--project",
116
+ project_id,
117
+ ],
118
+ capture_output=True,
119
+ check=False, # Don't fail if already enabled
120
+ )
121
+ console.print("✅ Cloud Build API enabled")
122
+
123
+ # Wait for the API to fully initialize and create the service account
124
+ console.print(
125
+ "⏳ Waiting for Cloud Build service account to be created (this typically takes 5-10 seconds)..."
126
+ )
127
+ time.sleep(10)
128
+ except subprocess.CalledProcessError as e:
129
+ console.print(f"⚠️ Could not enable Cloud Build API: {e}", style="yellow")
130
+
131
+ # Get the Cloud Build service account and grant permissions with retry logic
132
+ try:
133
+ project_number_result = run_command(
134
+ [
135
+ "gcloud",
136
+ "projects",
137
+ "describe",
138
+ project_id,
139
+ "--format=value(projectNumber)",
140
+ ],
141
+ capture_output=True,
142
+ check=True,
143
+ )
144
+ project_number = project_number_result.stdout.strip()
145
+ cloud_build_sa = (
146
+ f"service-{project_number}@gcp-sa-cloudbuild.iam.gserviceaccount.com"
147
+ )
148
+
149
+ console.print(
150
+ "🔧 Granting Secret Manager permissions to Cloud Build service account..."
151
+ )
152
+
153
+ # Try to grant permissions with retry logic
154
+ max_retries = 3
155
+ for attempt in range(max_retries):
156
+ try:
157
+ # Grant Secret Manager Admin role to Cloud Build service account
158
+ result = run_command(
159
+ [
160
+ "gcloud",
161
+ "projects",
162
+ "add-iam-policy-binding",
163
+ project_id,
164
+ f"--member=serviceAccount:{cloud_build_sa}",
165
+ "--role=roles/secretmanager.admin",
166
+ "--condition=None",
167
+ ],
168
+ capture_output=True,
169
+ check=True,
170
+ )
171
+ console.print(
172
+ "✅ Secret Manager permissions granted to Cloud Build service account"
173
+ )
174
+ break
175
+ except subprocess.CalledProcessError as e:
176
+ if "does not exist" in str(e.stderr) and attempt < max_retries - 1:
177
+ console.print(
178
+ f"⚠️ Service account not ready yet (attempt {attempt + 1}/{max_retries}). Retrying in 10 seconds...",
179
+ style="yellow",
180
+ )
181
+ time.sleep(10)
182
+ elif attempt < max_retries - 1:
183
+ console.print(
184
+ f"⚠️ Failed to grant permissions (attempt {attempt + 1}/{max_retries}). Retrying in 5 seconds...",
185
+ style="yellow",
186
+ )
187
+ time.sleep(5)
188
+ else:
189
+ console.print(
190
+ f"⚠️ Could not grant Secret Manager permissions after {max_retries} attempts",
191
+ style="yellow",
192
+ )
193
+ console.print(
194
+ "You may need to manually grant the permissions if the connection creation fails."
195
+ )
196
+ # Don't fail here, let the connection creation attempt proceed
197
+
198
+ # Wait for IAM changes to propagate
199
+ console.print(
200
+ "⏳ Waiting for IAM permissions to propagate (this typically takes 5-10 seconds)..."
201
+ )
202
+ time.sleep(10) # Give IAM time to propagate before proceeding
203
+ except subprocess.CalledProcessError as e:
204
+ console.print(
205
+ f"⚠️ Could not setup Cloud Build service account: {e}", style="yellow"
206
+ )
207
+ console.print(
208
+ "You may need to manually grant the permissions if the connection creation fails."
209
+ )
210
+
106
211
  def try_create_connection() -> subprocess.CompletedProcess[str]:
107
212
  cmd = [
108
213
  "gcloud",
src/cli/utils/gcp.py CHANGED
@@ -35,12 +35,14 @@ from src.cli.utils.version import PACKAGE_NAME, get_current_version
35
35
  console = Console()
36
36
 
37
37
 
38
- def enable_vertex_ai_api(project_id: str, auto_approve: bool = False) -> bool:
38
+ def enable_vertex_ai_api(
39
+ project_id: str, auto_approve: bool = False, context: str | None = None
40
+ ) -> bool:
39
41
  """Enable Vertex AI API with user confirmation and propagation waiting."""
40
42
  api_name = "aiplatform.googleapis.com"
41
43
 
42
44
  # First test if API is already working with a direct connection
43
- if _test_vertex_ai_connection(project_id):
45
+ if _test_vertex_ai_connection(project_id, context=context):
44
46
  return True
45
47
 
46
48
  if not auto_approve:
@@ -80,7 +82,7 @@ def enable_vertex_ai_api(project_id: str, auto_approve: bool = False) -> bool:
80
82
  start_time = time.time()
81
83
 
82
84
  while time.time() - start_time < max_wait_time:
83
- if _test_vertex_ai_connection(project_id):
85
+ if _test_vertex_ai_connection(project_id, context=context):
84
86
  console.print("✓ Vertex AI API is now available")
85
87
  return True
86
88
  time.sleep(check_interval)
@@ -97,7 +99,9 @@ def enable_vertex_ai_api(project_id: str, auto_approve: bool = False) -> bool:
97
99
  return False
98
100
 
99
101
 
100
- def _test_vertex_ai_connection(project_id: str, location: str = "us-central1") -> bool:
102
+ def _test_vertex_ai_connection(
103
+ project_id: str, location: str = "us-central1", context: str | None = None
104
+ ) -> bool:
101
105
  """Test Vertex AI connection without raising exceptions."""
102
106
  try:
103
107
  credentials, _ = google.auth.default()
@@ -106,7 +110,7 @@ def _test_vertex_ai_connection(project_id: str, location: str = "us-central1") -
106
110
  client_options=ClientOptions(
107
111
  api_endpoint=f"{location}-aiplatform.googleapis.com"
108
112
  ),
109
- client_info=get_client_info(),
113
+ client_info=get_client_info(context),
110
114
  transport=initializer.global_config._api_transport,
111
115
  )
112
116
  request = get_dummy_request(project_id=project_id)
@@ -116,15 +120,16 @@ def _test_vertex_ai_connection(project_id: str, location: str = "us-central1") -
116
120
  return False
117
121
 
118
122
 
119
- def get_user_agent() -> str:
120
- """Returns custom user agent header tuple (version, agent string)."""
123
+ def get_user_agent(context: str | None = None) -> str:
124
+ """Returns a custom user agent string."""
121
125
  version = get_current_version()
122
- return f"{version}-{PACKAGE_NAME}/{version}-{PACKAGE_NAME}"
126
+ prefix = "ag" if context == "agent-garden" else ""
127
+ return f"{prefix}{version}-{PACKAGE_NAME}/{prefix}{version}-{PACKAGE_NAME}"
123
128
 
124
129
 
125
- def get_client_info() -> ClientInfo:
130
+ def get_client_info(context: str | None = None) -> ClientInfo:
126
131
  """Returns ClientInfo with custom user agent."""
127
- user_agent = get_user_agent()
132
+ user_agent = get_user_agent(context)
128
133
  return ClientInfo(client_library_version=user_agent, user_agent=user_agent)
129
134
 
130
135
 
@@ -140,14 +145,15 @@ def verify_vertex_connection(
140
145
  project_id: str,
141
146
  location: str = "us-central1",
142
147
  auto_approve: bool = False,
148
+ context: str | None = None,
143
149
  ) -> None:
144
150
  """Verifies Vertex AI connection with a test Gemini request."""
145
151
  # First try direct connection - if it works, we're done
146
- if _test_vertex_ai_connection(project_id, location):
152
+ if _test_vertex_ai_connection(project_id, location, context):
147
153
  return
148
154
 
149
155
  # If that failed, try to enable the API
150
- if not enable_vertex_ai_api(project_id, auto_approve):
156
+ if not enable_vertex_ai_api(project_id, auto_approve, context):
151
157
  raise Exception("Vertex AI API is not enabled and user declined to enable it")
152
158
 
153
159
  # After enabling, test again with proper error handling
@@ -157,7 +163,7 @@ def verify_vertex_connection(
157
163
  client_options=ClientOptions(
158
164
  api_endpoint=f"{location}-aiplatform.googleapis.com"
159
165
  ),
160
- client_info=get_client_info(),
166
+ client_info=get_client_info(context),
161
167
  transport=initializer.global_config._api_transport,
162
168
  )
163
169
  request = get_dummy_request(project_id=project_id)
src/cli/utils/logging.py CHANGED
@@ -25,13 +25,14 @@ F = TypeVar("F", bound=Callable[..., Any])
25
25
 
26
26
 
27
27
  def display_welcome_banner(
28
- agent: str | None = None, enhance_mode: bool = False
28
+ agent: str | None = None, enhance_mode: bool = False, agent_garden: bool = False
29
29
  ) -> None:
30
30
  """Display the Agent Starter Pack welcome banner.
31
31
 
32
32
  Args:
33
33
  agent: Optional agent specification to customize the welcome message
34
34
  enhance_mode: Whether this is for enhancement mode
35
+ agent_garden: Whether this deployment is from Agent Garden
35
36
  """
36
37
  if enhance_mode:
37
38
  console.print(
@@ -42,6 +43,17 @@ def display_welcome_banner(
42
43
  "Enhancing your existing project with production-ready agent capabilities!\n",
43
44
  style="green",
44
45
  )
46
+ elif agent_garden:
47
+ console.print(
48
+ "\n=== Welcome to Agent Garden! 🌱 ===",
49
+ style="bold blue",
50
+ )
51
+ console.print(
52
+ "Powered by [link=https://goo.gle/agent-starter-pack]Google Cloud - Agent Starter Pack [/link]\n",
53
+ )
54
+ console.print(
55
+ "This tool will help you deploy production-ready AI agents from Agent Garden to Google Cloud!\n"
56
+ )
45
57
  elif agent and agent.startswith("adk@"):
46
58
  console.print(
47
59
  "\n=== Welcome to [link=https://github.com/google/adk-samples]google/adk-samples[/link]! ✨ ===",
src/cli/utils/template.py CHANGED
@@ -448,6 +448,7 @@ def process_template(
448
448
  remote_config: dict[str, Any] | None = None,
449
449
  in_folder: bool = False,
450
450
  cli_overrides: dict[str, Any] | None = None,
451
+ agent_garden: bool = False,
451
452
  ) -> None:
452
453
  """Process the template directory and create a new project.
453
454
 
@@ -465,6 +466,7 @@ def process_template(
465
466
  remote_config: Optional remote template configuration
466
467
  in_folder: Whether to template directly into the output directory instead of creating a subdirectory
467
468
  cli_overrides: Optional CLI override values that should take precedence over template config
469
+ agent_garden: Whether this deployment is from Agent Garden
468
470
  """
469
471
  logging.debug(f"Processing template from {template_dir}")
470
472
  logging.debug(f"Project name: {project_name}")
@@ -703,6 +705,7 @@ def process_template(
703
705
  "data_ingestion": include_data_ingestion,
704
706
  "datastore_type": datastore if datastore else "",
705
707
  "agent_directory": get_agent_directory(template_config, cli_overrides),
708
+ "agent_garden": agent_garden,
706
709
  "adk_cheatsheet": adk_cheatsheet_content,
707
710
  "llm_txt": llm_txt_content,
708
711
  "_copy_without_render": [