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.
- {agent_starter_pack-0.13.0.dist-info → agent_starter_pack-0.14.0.dist-info}/METADATA +11 -3
- {agent_starter_pack-0.13.0.dist-info → agent_starter_pack-0.14.0.dist-info}/RECORD +22 -24
- agents/adk_base/notebooks/evaluating_adk_agent.ipynb +78 -71
- agents/agentic_rag/notebooks/evaluating_adk_agent.ipynb +78 -71
- llm.txt +87 -39
- src/base_template/Makefile +17 -2
- src/cli/commands/create.py +27 -5
- src/cli/commands/enhance.py +132 -6
- src/cli/commands/setup_cicd.py +91 -69
- src/cli/utils/cicd.py +105 -0
- src/cli/utils/gcp.py +19 -13
- src/cli/utils/logging.py +13 -1
- src/cli/utils/template.py +3 -0
- src/frontends/live_api_react/frontend/package-lock.json +9 -9
- src/frontends/live_api_react/frontend/src/App.tsx +12 -153
- src/frontends/live_api_react/frontend/src/components/side-panel/SidePanel.tsx +352 -3
- src/frontends/live_api_react/frontend/src/components/side-panel/side-panel.scss +249 -2
- src/frontends/live_api_react/frontend/src/utils/multimodal-live-client.ts +4 -1
- src/resources/docs/adk-cheatsheet.md +285 -38
- src/frontends/live_api_react/frontend/src/components/control-tray/ControlTray.tsx +0 -217
- src/frontends/live_api_react/frontend/src/components/control-tray/control-tray.scss +0 -201
- {agent_starter_pack-0.13.0.dist-info → agent_starter_pack-0.14.0.dist-info}/WHEEL +0 -0
- {agent_starter_pack-0.13.0.dist-info → agent_starter_pack-0.14.0.dist-info}/entry_points.txt +0 -0
- {agent_starter_pack-0.13.0.dist-info → agent_starter_pack-0.14.0.dist-info}/licenses/LICENSE +0 -0
src/cli/commands/enhance.py
CHANGED
@@ -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
|
-
"--
|
101
|
-
|
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
|
-
|
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
|
-
|
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=
|
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,
|
src/cli/commands/setup_cicd.py
CHANGED
@@ -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,
|
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
|
-
|
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
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
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
|
-
|
327
|
-
|
328
|
-
|
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
|
596
|
-
if
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
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(
|
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(
|
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
|
123
|
+
def get_user_agent(context: str | None = None) -> str:
|
124
|
+
"""Returns a custom user agent string."""
|
121
125
|
version = get_current_version()
|
122
|
-
|
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": [
|