agent-starter-pack 0.11.2__py3-none-any.whl → 0.12.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 (68) hide show
  1. {agent_starter_pack-0.11.2.dist-info → agent_starter_pack-0.12.0.dist-info}/METADATA +1 -1
  2. {agent_starter_pack-0.11.2.dist-info → agent_starter_pack-0.12.0.dist-info}/RECORD +41 -66
  3. agents/adk_base/app/__init__.py +17 -0
  4. agents/adk_base/notebooks/adk_app_testing.ipynb +4 -1
  5. agents/adk_base/tests/integration/test_agent.py +1 -1
  6. agents/agentic_rag/app/__init__.py +17 -0
  7. agents/agentic_rag/app/agent.py +2 -2
  8. agents/agentic_rag/notebooks/adk_app_testing.ipynb +4 -1
  9. agents/agentic_rag/tests/integration/test_agent.py +2 -2
  10. agents/crewai_coding_crew/tests/integration/test_agent.py +1 -1
  11. agents/langgraph_base_react/tests/integration/test_agent.py +1 -1
  12. agents/live_api/tests/unit/test_server.py +6 -6
  13. llm.txt +15 -4
  14. src/base_template/Makefile +5 -5
  15. src/base_template/README.md +4 -4
  16. src/base_template/deployment/terraform/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}build_triggers.tf{% else %}unused_build_triggers.tf{% endif %} +2 -2
  17. src/base_template/pyproject.toml +2 -2
  18. src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/deploy-to-prod.yaml +1 -1
  19. src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/pr_checks.yaml +1 -1
  20. src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/staging.yaml +2 -2
  21. src/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/deploy-to-prod.yaml +1 -1
  22. src/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/staging.yaml +1 -1
  23. src/cli/commands/create.py +25 -2
  24. src/cli/commands/enhance.py +94 -15
  25. src/cli/commands/list.py +1 -1
  26. src/cli/utils/remote_template.py +1 -1
  27. src/cli/utils/template.py +120 -41
  28. src/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +3 -3
  29. src/deployment_targets/agent_engine/{app → {{cookiecutter.agent_directory}}}/agent_engine_app.py +10 -10
  30. src/deployment_targets/cloud_run/Dockerfile +2 -2
  31. src/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +3 -3
  32. src/deployment_targets/cloud_run/tests/load_test/README.md +1 -1
  33. src/deployment_targets/cloud_run/tests/load_test/load_test.py +2 -2
  34. {agents/live_api/app → src/deployment_targets/cloud_run/{{cookiecutter.agent_directory}}}/server.py +186 -7
  35. src/resources/docs/adk-cheatsheet.md +3 -3
  36. src/base_template/app/__init__.py +0 -3
  37. src/deployment_targets/cloud_run/app/server.py +0 -206
  38. src/frontends/adk_gemini_fullstack/frontend/components.json +0 -21
  39. src/frontends/adk_gemini_fullstack/frontend/eslint.config.js +0 -28
  40. src/frontends/adk_gemini_fullstack/frontend/index.html +0 -12
  41. src/frontends/adk_gemini_fullstack/frontend/package-lock.json +0 -6105
  42. src/frontends/adk_gemini_fullstack/frontend/package.json +0 -47
  43. src/frontends/adk_gemini_fullstack/frontend/src/App.tsx +0 -564
  44. src/frontends/adk_gemini_fullstack/frontend/src/components/ActivityTimeline.tsx +0 -244
  45. src/frontends/adk_gemini_fullstack/frontend/src/components/ChatMessagesView.tsx +0 -420
  46. src/frontends/adk_gemini_fullstack/frontend/src/components/InputForm.tsx +0 -60
  47. src/frontends/adk_gemini_fullstack/frontend/src/components/WelcomeScreen.tsx +0 -56
  48. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/badge.tsx +0 -46
  49. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/button.tsx +0 -59
  50. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/card.tsx +0 -92
  51. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/input.tsx +0 -21
  52. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/scroll-area.tsx +0 -56
  53. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/select.tsx +0 -183
  54. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/tabs.tsx +0 -64
  55. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/textarea.tsx +0 -18
  56. src/frontends/adk_gemini_fullstack/frontend/src/global.css +0 -154
  57. src/frontends/adk_gemini_fullstack/frontend/src/main.tsx +0 -13
  58. src/frontends/adk_gemini_fullstack/frontend/src/utils.ts +0 -7
  59. src/frontends/adk_gemini_fullstack/frontend/src/vite-env.d.ts +0 -1
  60. src/frontends/adk_gemini_fullstack/frontend/tsconfig.json +0 -28
  61. src/frontends/adk_gemini_fullstack/frontend/tsconfig.node.json +0 -24
  62. src/frontends/adk_gemini_fullstack/frontend/vite.config.ts +0 -41
  63. {agent_starter_pack-0.11.2.dist-info → agent_starter_pack-0.12.0.dist-info}/WHEEL +0 -0
  64. {agent_starter_pack-0.11.2.dist-info → agent_starter_pack-0.12.0.dist-info}/entry_points.txt +0 -0
  65. {agent_starter_pack-0.11.2.dist-info → agent_starter_pack-0.12.0.dist-info}/licenses/LICENSE +0 -0
  66. /src/base_template/{app → {{cookiecutter.agent_directory}}}/utils/gcs.py +0 -0
  67. /src/base_template/{app → {{cookiecutter.agent_directory}}}/utils/tracing.py +0 -0
  68. /src/base_template/{app → {{cookiecutter.agent_directory}}}/utils/typing.py +0 -0
@@ -19,10 +19,10 @@ import pathlib
19
19
  import shutil
20
20
  import subprocess
21
21
  import tempfile
22
+ import tomllib
22
23
  from collections.abc import Callable
23
24
 
24
25
  import click
25
- import tomllib
26
26
  from click.core import ParameterSource
27
27
  from rich.console import Console
28
28
  from rich.prompt import IntPrompt, Prompt
@@ -101,6 +101,11 @@ def shared_template_options(f: Callable) -> Callable:
101
101
  type=click.Choice(["agent_engine", "cloud_run"]),
102
102
  help="Deployment target name",
103
103
  )(f)
104
+ f = click.option(
105
+ "--agent-directory",
106
+ "-dir",
107
+ help="Name of the agent directory (overrides template default)",
108
+ )(f)
104
109
  return f
105
110
 
106
111
 
@@ -232,8 +237,10 @@ def create(
232
237
  region: str,
233
238
  skip_checks: bool,
234
239
  in_folder: bool,
240
+ agent_directory: str | None,
235
241
  base_template: str | None = None,
236
242
  skip_welcome: bool = False,
243
+ cli_overrides: dict | None = None,
237
244
  ) -> None:
238
245
  """Create GCP-based AI agent projects from templates."""
239
246
  try:
@@ -434,6 +441,14 @@ def create(
434
441
  / ".template"
435
442
  )
436
443
  config = load_template_config(template_path)
444
+
445
+ # Apply CLI overrides for local templates if provided (e.g., from enhance command)
446
+ if cli_overrides:
447
+ config = merge_template_configs(config, cli_overrides)
448
+ if debug:
449
+ logging.debug(
450
+ f"Applied CLI overrides to local template config: {cli_overrides}"
451
+ )
437
452
  # Data ingestion and datastore selection
438
453
  if include_data_ingestion or datastore:
439
454
  include_data_ingestion = True
@@ -610,6 +625,13 @@ def create(
610
625
  if debug:
611
626
  logging.debug(f"Output directory: {destination_dir}")
612
627
 
628
+ # Construct CLI overrides for template processing
629
+ final_cli_overrides = cli_overrides or {}
630
+ if agent_directory:
631
+ if "settings" not in final_cli_overrides:
632
+ final_cli_overrides["settings"] = {}
633
+ final_cli_overrides["settings"]["agent_directory"] = agent_directory
634
+
613
635
  try:
614
636
  # Process template (handles both local and remote templates)
615
637
  process_template(
@@ -623,8 +645,9 @@ def create(
623
645
  session_type=final_session_type,
624
646
  output_dir=destination_dir,
625
647
  remote_template_path=template_source_path,
626
- remote_config=config if template_source_path else None,
648
+ remote_config=config,
627
649
  in_folder=in_folder,
650
+ cli_overrides=final_cli_overrides,
628
651
  )
629
652
 
630
653
  # Replace region in all files if a different region was specified
@@ -13,6 +13,7 @@
13
13
  # limitations under the License.
14
14
 
15
15
  import pathlib
16
+ from typing import Any
16
17
 
17
18
  import click
18
19
  from rich.console import Console
@@ -48,7 +49,7 @@ def display_base_template_selection(current_base: str) -> str:
48
49
  choice_num = 1
49
50
  current_choice = None
50
51
 
51
- for _num, agent in agents.items():
52
+ for agent in agents.values():
52
53
  template_choices[choice_num] = agent["name"]
53
54
  current_indicator = " (current)" if agent["name"] == current_base else ""
54
55
  console.print(
@@ -89,6 +90,10 @@ def display_base_template_selection(current_base: str) -> str:
89
90
  "--base-template",
90
91
  help="Base template to inherit from (e.g., adk_base, langgraph_base_react, agentic_rag)",
91
92
  )
93
+ @click.option(
94
+ "--agent-directory",
95
+ help="Custom directory name for agent files (default: 'app' or auto-detected from pyproject.toml)",
96
+ )
92
97
  @shared_template_options
93
98
  @handle_cli_error
94
99
  def enhance(
@@ -105,6 +110,7 @@ def enhance(
105
110
  region: str,
106
111
  skip_checks: bool,
107
112
  base_template: str | None,
113
+ agent_directory: str | None,
108
114
  ) -> None:
109
115
  """Enhance your existing project with AI agent capabilities.
110
116
 
@@ -113,7 +119,8 @@ def enhance(
113
119
  creating a new project directory.
114
120
 
115
121
  For best compatibility, your project should follow the agent-starter-pack structure
116
- with agent code organized in an /app folder (containing agent.py, etc.).
122
+ with agent code organized in an agent directory (default: /app, configurable via
123
+ --agent-directory).
117
124
 
118
125
  TEMPLATE_PATH can be:
119
126
  - A local directory path (e.g., . for current directory)
@@ -191,10 +198,13 @@ def enhance(
191
198
  load_remote_template_config,
192
199
  )
193
200
 
194
- # Prepare CLI overrides for base template
195
- cli_overrides = {}
201
+ # Prepare CLI overrides for base template and agent directory
202
+ cli_overrides: dict[str, Any] = {}
196
203
  if base_template:
197
204
  cli_overrides["base_template"] = base_template
205
+ if agent_directory:
206
+ cli_overrides["settings"] = cli_overrides.get("settings", {})
207
+ cli_overrides["settings"]["agent_directory"] = agent_directory
198
208
 
199
209
  # Load config from current directory for inheritance info
200
210
  current_dir = pathlib.Path.cwd()
@@ -209,6 +219,10 @@ def enhance(
209
219
  if selected_base_template != original_base_template_name:
210
220
  # Update CLI overrides with the selected base template
211
221
  cli_overrides["base_template"] = selected_base_template
222
+ # Preserve agent_directory override if it was set
223
+ if agent_directory:
224
+ cli_overrides["settings"] = cli_overrides.get("settings", {})
225
+ cli_overrides["settings"]["agent_directory"] = agent_directory
212
226
  base_template = selected_base_template
213
227
  console.print(
214
228
  f"✅ Selected base template: [cyan]{selected_base_template}[/cyan]"
@@ -232,9 +246,53 @@ def enhance(
232
246
  # Validate project structure when using current directory template
233
247
  if template_path == pathlib.Path("."):
234
248
  current_dir = pathlib.Path.cwd()
235
- app_folder = current_dir / "app"
236
249
 
237
- if not app_folder.exists() or not app_folder.is_dir():
250
+ # Determine agent directory: CLI param > pyproject.toml detection > default
251
+ detected_agent_directory = "app" # default
252
+ if not agent_directory: # Only try to detect if not provided via CLI
253
+ pyproject_path = current_dir / "pyproject.toml"
254
+ if pyproject_path.exists():
255
+ try:
256
+ import tomllib
257
+
258
+ with open(pyproject_path, "rb") as f:
259
+ pyproject_data = tomllib.load(f)
260
+ packages = (
261
+ pyproject_data.get("tool", {})
262
+ .get("hatch", {})
263
+ .get("build", {})
264
+ .get("targets", {})
265
+ .get("wheel", {})
266
+ .get("packages", [])
267
+ )
268
+ if packages:
269
+ # Find the first package that isn't 'frontend'
270
+ for pkg in packages:
271
+ if pkg != "frontend":
272
+ detected_agent_directory = pkg
273
+ break
274
+ except Exception as e:
275
+ if debug:
276
+ console.print(
277
+ f"[dim]Could not auto-detect agent directory: {e}[/dim]"
278
+ )
279
+ pass # Fall back to default
280
+
281
+ final_agent_directory = agent_directory or detected_agent_directory
282
+
283
+ # Show info about agent directory selection
284
+ if agent_directory:
285
+ console.print(
286
+ f"ℹ️ Using CLI-specified agent directory: [cyan]{agent_directory}[/cyan]"
287
+ )
288
+ elif detected_agent_directory != "app":
289
+ console.print(
290
+ f"ℹ️ Auto-detected agent directory: [cyan]{detected_agent_directory}[/cyan]"
291
+ )
292
+
293
+ agent_folder = current_dir / final_agent_directory
294
+
295
+ if not agent_folder.exists() or not agent_folder.is_dir():
238
296
  console.print()
239
297
  console.print(
240
298
  "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
@@ -245,42 +303,61 @@ def enhance(
245
303
  )
246
304
  console.print()
247
305
  console.print(
248
- "📁 [bold]Expected Structure:[/bold] [cyan]/app[/cyan] folder containing your agent code"
306
+ f"📁 [bold]Expected Structure:[/bold] [cyan]/{final_agent_directory}[/cyan] folder containing your agent code"
249
307
  )
250
308
  console.print(f"📍 [bold]Current Directory:[/bold] {current_dir}")
251
- console.print("❌ [bold red]Missing:[/bold red] /app folder")
309
+ console.print(
310
+ f"❌ [bold red]Missing:[/bold red] /{final_agent_directory} folder"
311
+ )
252
312
  console.print()
253
313
  console.print(
254
- "[dim]The enhance command can still proceed, but for best compatibility"
255
- " your agent code should be organized in an /app folder structure.[/dim]"
314
+ f"The enhance command can still proceed, but for best compatibility"
315
+ f" your agent code should be organized in a /{final_agent_directory} folder structure."
256
316
  )
257
317
  console.print()
258
318
 
259
319
  # Ask for confirmation after showing the structure warning
320
+ console.print("💡 Options:")
260
321
  console.print(
261
- "💡 [dim]Consider creating an /app folder for better compatibility.[/dim]"
322
+ f" Create a /{final_agent_directory} folder and move your agent code there"
262
323
  )
324
+ if final_agent_directory == "app":
325
+ console.print(
326
+ " • Use [cyan]--agent-directory <custom_name>[/cyan] if your agent code is in a different directory"
327
+ )
328
+ else:
329
+ console.print(
330
+ " • Use [cyan]--agent-directory <custom_name>[/cyan] to specify your existing agent directory"
331
+ )
263
332
  console.print()
264
333
 
265
334
  if not auto_approve:
266
335
  if not click.confirm(
267
- "Continue with enhancement despite missing /app folder?",
336
+ f"Continue with enhancement despite missing /{final_agent_directory} folder?",
268
337
  default=True,
269
338
  ):
270
339
  console.print("✋ [yellow]Enhancement cancelled.[/yellow]")
271
340
  return
272
341
  else:
273
342
  # Check for common agent files
274
- agent_py = app_folder / "agent.py"
343
+ agent_py = agent_folder / "agent.py"
275
344
  if agent_py.exists():
276
345
  console.print(
277
- "Detected existing agent structure with [cyan]/app/agent.py[/cyan]"
346
+ f"Detected existing agent structure with [cyan]/{final_agent_directory}/agent.py[/cyan]"
278
347
  )
279
348
  else:
280
349
  console.print(
281
- "ℹ️ [blue]Found /app folder[/blue] - ensure your agent code is properly organized within it"
350
+ f"ℹ️ [blue]Found /{final_agent_directory} folder[/blue] - ensure your agent code is properly organized within it, including an agent.py file"
282
351
  )
283
352
 
353
+ # Prepare CLI overrides to pass to create command
354
+ final_cli_overrides: dict[str, Any] = {}
355
+ if base_template:
356
+ final_cli_overrides["base_template"] = base_template
357
+ if agent_directory:
358
+ final_cli_overrides["settings"] = final_cli_overrides.get("settings", {})
359
+ final_cli_overrides["settings"]["agent_directory"] = agent_directory
360
+
284
361
  # Call the create command with in-folder mode enabled
285
362
  ctx.invoke(
286
363
  create,
@@ -297,6 +374,8 @@ def enhance(
297
374
  region=region,
298
375
  skip_checks=skip_checks,
299
376
  in_folder=True, # Always use in-folder mode for enhance
377
+ agent_directory=agent_directory,
300
378
  base_template=base_template,
301
379
  skip_welcome=True, # Skip welcome message since enhance shows its own
380
+ cli_overrides=final_cli_overrides if final_cli_overrides else None,
302
381
  )
src/cli/commands/list.py CHANGED
@@ -14,9 +14,9 @@
14
14
 
15
15
  import logging
16
16
  import pathlib
17
+ import tomllib
17
18
 
18
19
  import click
19
- import tomllib
20
20
  from rich.console import Console
21
21
  from rich.table import Table
22
22
 
@@ -19,10 +19,10 @@ import re
19
19
  import shutil
20
20
  import subprocess
21
21
  import tempfile
22
+ import tomllib
22
23
  from dataclasses import dataclass
23
24
  from typing import Any
24
25
 
25
- import tomllib
26
26
  from jinja2 import Environment
27
27
 
28
28
 
src/cli/utils/template.py CHANGED
@@ -34,9 +34,6 @@ from .remote_template import (
34
34
  render_and_merge_makefiles,
35
35
  )
36
36
 
37
- ADK_FILES = ["app/__init__.py"]
38
- NON_ADK_FILES: list[str] = []
39
-
40
37
 
41
38
  @dataclass
42
39
  class TemplateConfig:
@@ -72,7 +69,11 @@ class TemplateConfig:
72
69
  raise ValueError(f"Error loading template config: {err}") from err
73
70
 
74
71
 
75
- OVERWRITE_FOLDERS = ["app", "frontend", "tests", "notebooks"]
72
+ def get_overwrite_folders(agent_directory: str) -> list[str]:
73
+ """Get folders to overwrite with configurable agent directory."""
74
+ return [agent_directory, "frontend", "tests", "notebooks"]
75
+
76
+
76
77
  TEMPLATE_CONFIG_FILE = "templateconfig.yaml"
77
78
  DEPLOYMENT_FOLDERS = ["cloud_run", "agent_engine"]
78
79
  DEFAULT_FRONTEND = "streamlit"
@@ -446,6 +447,7 @@ def process_template(
446
447
  remote_template_path: pathlib.Path | None = None,
447
448
  remote_config: dict[str, Any] | None = None,
448
449
  in_folder: bool = False,
450
+ cli_overrides: dict[str, Any] | None = None,
449
451
  ) -> None:
450
452
  """Process the template directory and create a new project.
451
453
 
@@ -462,12 +464,25 @@ def process_template(
462
464
  remote_template_path: Optional path to remote template for overlay
463
465
  remote_config: Optional remote template configuration
464
466
  in_folder: Whether to template directly into the output directory instead of creating a subdirectory
467
+ cli_overrides: Optional CLI override values that should take precedence over template config
465
468
  """
466
469
  logging.debug(f"Processing template from {template_dir}")
467
470
  logging.debug(f"Project name: {project_name}")
468
471
  logging.debug(f"Include pipeline: {datastore}")
469
472
  logging.debug(f"Output directory: {output_dir}")
470
473
 
474
+ def get_agent_directory(
475
+ template_config: dict[str, Any], cli_overrides: dict[str, Any] | None = None
476
+ ) -> str:
477
+ """Get agent directory with CLI override support."""
478
+ if (
479
+ cli_overrides
480
+ and "settings" in cli_overrides
481
+ and "agent_directory" in cli_overrides["settings"]
482
+ ):
483
+ return cli_overrides["settings"]["agent_directory"]
484
+ return template_config.get("settings", {}).get("agent_directory", "app")
485
+
471
486
  # Handle remote vs local templates
472
487
  is_remote = remote_template_path is not None
473
488
 
@@ -519,7 +534,21 @@ def process_template(
519
534
  base_template_path = (
520
535
  pathlib.Path(__file__).parent.parent.parent / "base_template"
521
536
  )
522
- copy_files(base_template_path, project_template, agent_name, overwrite=True)
537
+ # Get agent directory from config early for use in file copying
538
+ # Load config early to get agent_directory
539
+ if is_remote:
540
+ early_config = remote_config or {}
541
+ else:
542
+ template_path = pathlib.Path(template_dir)
543
+ early_config = load_template_config(template_path)
544
+ agent_directory = get_agent_directory(early_config, cli_overrides)
545
+ copy_files(
546
+ base_template_path,
547
+ project_template,
548
+ agent_name,
549
+ overwrite=True,
550
+ agent_directory=agent_directory,
551
+ )
523
552
  logging.debug(f"1. Copied base template from {base_template_path}")
524
553
 
525
554
  # 2. Process deployment target if specified
@@ -535,6 +564,7 @@ def process_template(
535
564
  project_template,
536
565
  agent_name=agent_name,
537
566
  overwrite=True,
567
+ agent_directory=agent_directory,
538
568
  )
539
569
  logging.debug(
540
570
  f"2. Processed deployment files for target: {deployment_target}"
@@ -556,27 +586,11 @@ def process_template(
556
586
  copy_frontend_files(frontend_type, project_template)
557
587
  logging.debug(f"4. Processed frontend files for type: {frontend_type}")
558
588
 
559
- # 5. Copy agent-specific files to override base template
560
- if agent_path.exists():
561
- for folder in OVERWRITE_FOLDERS:
562
- agent_folder = agent_path / folder
563
- project_folder = project_template / folder
564
- if agent_folder.exists():
565
- logging.debug(f"5. Copying agent folder {folder} with override")
566
- copy_files(
567
- agent_folder, project_folder, agent_name, overwrite=True
568
- )
569
-
570
- # 6. Finally, overlay remote template files if present
589
+ # 6. Skip remote template files during cookiecutter processing
590
+ # Remote files will be copied after cookiecutter to avoid Jinja conflicts
571
591
  if is_remote and remote_template_path:
572
592
  logging.debug(
573
- f"6. Overlaying remote template files from {remote_template_path}"
574
- )
575
- copy_files(
576
- remote_template_path,
577
- project_template,
578
- agent_name=agent_name,
579
- overwrite=True,
593
+ "6. Skipping remote template files during cookiecutter processing - will copy after templating"
580
594
  )
581
595
 
582
596
  # Load and validate template config first
@@ -602,6 +616,45 @@ def process_template(
602
616
  # Use the already loaded config
603
617
  template_config = config
604
618
 
619
+ # 5. Copy agent-specific files to override base template (using final config)
620
+ if agent_path.exists():
621
+ agent_directory = get_agent_directory(template_config, cli_overrides)
622
+
623
+ # Get the template's default agent directory (usually "app")
624
+ template_agent_directory = template_config.get("settings", {}).get(
625
+ "agent_directory", "app"
626
+ )
627
+
628
+ # Copy agent directory (always from "app" to target directory)
629
+ source_agent_folder = agent_path / template_agent_directory
630
+ target_agent_folder = project_template / agent_directory
631
+ if source_agent_folder.exists():
632
+ logging.debug(
633
+ f"5. Copying agent folder {template_agent_directory} -> {agent_directory} with override"
634
+ )
635
+ copy_files(
636
+ source_agent_folder,
637
+ target_agent_folder,
638
+ agent_name,
639
+ overwrite=True,
640
+ agent_directory=agent_directory,
641
+ )
642
+
643
+ # Copy other folders (frontend, tests, notebooks)
644
+ other_folders = ["frontend", "tests", "notebooks"]
645
+ for folder in other_folders:
646
+ agent_folder = agent_path / folder
647
+ project_folder = project_template / folder
648
+ if agent_folder.exists():
649
+ logging.debug(f"5. Copying {folder} folder with override")
650
+ copy_files(
651
+ agent_folder,
652
+ project_folder,
653
+ agent_name,
654
+ overwrite=True,
655
+ agent_directory=agent_directory,
656
+ )
657
+
605
658
  # Check if data processing should be included
606
659
  if include_data_ingestion and datastore:
607
660
  logging.debug(
@@ -649,6 +702,7 @@ def process_template(
649
702
  "extra_dependencies": [extra_deps],
650
703
  "data_ingestion": include_data_ingestion,
651
704
  "datastore_type": datastore if datastore else "",
705
+ "agent_directory": get_agent_directory(template_config, cli_overrides),
652
706
  "adk_cheatsheet": adk_cheatsheet_content,
653
707
  "llm_txt": llm_txt_content,
654
708
  "_copy_without_render": [
@@ -664,7 +718,9 @@ def process_template(
664
718
  "*templates.py", # Don't render templates files
665
719
  "Makefile", # Don't render Makefile - handled by render_and_merge_makefiles
666
720
  # Don't render agent.py unless it's agentic_rag
667
- "app/agent.py" if agent_name != "agentic_rag" else "",
721
+ f"{get_agent_directory(template_config, cli_overrides)}/agent.py"
722
+ if agent_name != "agentic_rag"
723
+ else "",
668
724
  ],
669
725
  }
670
726
 
@@ -690,6 +746,21 @@ def process_template(
690
746
  )
691
747
  logging.debug("Template processing completed successfully")
692
748
 
749
+ # Now overlay remote template files if present (after cookiecutter processing)
750
+ if is_remote and remote_template_path:
751
+ generated_project_dir = temp_path / project_name
752
+ logging.debug(
753
+ f"Copying remote template files from {remote_template_path} to {generated_project_dir}"
754
+ )
755
+ copy_files(
756
+ remote_template_path,
757
+ generated_project_dir,
758
+ agent_name=agent_name,
759
+ overwrite=True,
760
+ agent_directory=agent_directory,
761
+ )
762
+ logging.debug("Remote template files copied successfully")
763
+
693
764
  # Move the generated project to the final destination
694
765
  generated_project_dir = temp_path / project_name
695
766
 
@@ -842,15 +913,7 @@ def process_template(
842
913
  )
843
914
 
844
915
  # 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}")
916
+ agent_directory = get_agent_directory(template_config, cli_overrides)
854
917
 
855
918
  # Clean up unused_* files and directories created by conditional templates
856
919
  import glob
@@ -923,11 +986,15 @@ def process_template(
923
986
  os.chdir(original_dir)
924
987
 
925
988
 
926
- def should_exclude_path(path: pathlib.Path, agent_name: str) -> bool:
989
+ def should_exclude_path(
990
+ path: pathlib.Path, agent_name: str, agent_directory: str = "app"
991
+ ) -> bool:
927
992
  """Determine if a path should be excluded based on the agent type."""
928
993
  if agent_name == "live_api":
929
- # Exclude the unit test utils folder and app/utils folder for live_api
930
- if "tests/unit/test_utils" in str(path) or "app/utils" in str(path):
994
+ # Exclude the unit test utils folder and agent utils folder for live_api
995
+ if "tests/unit/test_utils" in str(path) or f"{agent_directory}/utils" in str(
996
+ path
997
+ ):
931
998
  logging.debug(f"Excluding path for live_api: {path}")
932
999
  return True
933
1000
  return False
@@ -938,6 +1005,7 @@ def copy_files(
938
1005
  dst: pathlib.Path,
939
1006
  agent_name: str | None = None,
940
1007
  overwrite: bool = False,
1008
+ agent_directory: str = "app",
941
1009
  ) -> None:
942
1010
  """
943
1011
  Copy files with configurable behavior for exclusions and overwrites.
@@ -947,6 +1015,7 @@ def copy_files(
947
1015
  dst: Destination path
948
1016
  agent_name: Name of the agent (for agent-specific exclusions)
949
1017
  overwrite: Whether to overwrite existing files (True) or skip them (False)
1018
+ agent_directory: Name of the agent directory (for agent-specific exclusions)
950
1019
  """
951
1020
 
952
1021
  def should_skip(path: pathlib.Path) -> bool:
@@ -957,7 +1026,9 @@ def copy_files(
957
1026
  return True
958
1027
  if ".git" in path.parts:
959
1028
  return True
960
- if agent_name is not None and should_exclude_path(path, agent_name):
1029
+ if agent_name is not None and should_exclude_path(
1030
+ path, agent_name, agent_directory
1031
+ ):
961
1032
  return True
962
1033
  if path.is_dir() and path.name == ".template":
963
1034
  return True
@@ -970,9 +1041,10 @@ def copy_files(
970
1041
  if should_skip(item):
971
1042
  logging.debug(f"Skipping file/directory: {item}")
972
1043
  continue
1044
+
973
1045
  d = dst / item.name
974
1046
  if item.is_dir():
975
- copy_files(item, d, agent_name, overwrite)
1047
+ copy_files(item, d, agent_name, overwrite, agent_directory)
976
1048
  else:
977
1049
  if overwrite or not d.exists():
978
1050
  logging.debug(f"Copying file: {item} -> {d}")
@@ -1012,7 +1084,10 @@ def copy_frontend_files(frontend_type: str, project_template: pathlib.Path) -> N
1012
1084
 
1013
1085
 
1014
1086
  def copy_deployment_files(
1015
- deployment_target: str, agent_name: str, project_template: pathlib.Path
1087
+ deployment_target: str,
1088
+ agent_name: str,
1089
+ project_template: pathlib.Path,
1090
+ agent_directory: str = "app",
1016
1091
  ) -> None:
1017
1092
  """Copy files from the specified deployment target folder."""
1018
1093
  if not deployment_target:
@@ -1028,7 +1103,11 @@ def copy_deployment_files(
1028
1103
  logging.debug(f"Copying deployment files from {deployment_path}")
1029
1104
  # Pass agent_name to respect agent-specific exclusions
1030
1105
  copy_files(
1031
- deployment_path, project_template, agent_name=agent_name, overwrite=True
1106
+ deployment_path,
1107
+ project_template,
1108
+ agent_name=agent_name,
1109
+ overwrite=True,
1110
+ agent_directory=agent_directory,
1032
1111
  )
1033
1112
  else:
1034
1113
  logging.warning(f"Deployment target directory not found: {deployment_path}")
@@ -18,11 +18,11 @@ import pytest
18
18
  {%- if "adk" in cookiecutter.tags %}
19
19
  from google.adk.events.event import Event
20
20
 
21
- from app.agent import root_agent
22
- from app.agent_engine_app import AgentEngineApp
21
+ from {{cookiecutter.agent_directory}}.agent import root_agent
22
+ from {{cookiecutter.agent_directory}}.agent_engine_app import AgentEngineApp
23
23
  {%- else %}
24
24
 
25
- from app.agent_engine_app import AgentEngineApp
25
+ from {{cookiecutter.agent_directory}}.agent_engine_app import AgentEngineApp
26
26
  {%- endif %}
27
27
 
28
28
 
@@ -30,10 +30,10 @@ from opentelemetry.sdk.trace import TracerProvider, export
30
30
  from vertexai import agent_engines
31
31
  from vertexai.preview.reasoning_engines import AdkApp
32
32
 
33
- from app.agent import root_agent
34
- from app.utils.gcs import create_bucket_if_not_exists
35
- from app.utils.tracing import CloudTraceLoggingSpanExporter
36
- from app.utils.typing import Feedback
33
+ from {{cookiecutter.agent_directory}}.agent import root_agent
34
+ from {{cookiecutter.agent_directory}}.utils.gcs import create_bucket_if_not_exists
35
+ from {{cookiecutter.agent_directory}}.utils.tracing import CloudTraceLoggingSpanExporter
36
+ from {{cookiecutter.agent_directory}}.utils.typing import Feedback
37
37
 
38
38
 
39
39
  class AgentEngineApp(AdkApp):
@@ -95,9 +95,9 @@ from langchain_core.runnables import RunnableConfig
95
95
  from traceloop.sdk import Instruments, Traceloop
96
96
  from vertexai import agent_engines
97
97
 
98
- from app.utils.gcs import create_bucket_if_not_exists
99
- from app.utils.tracing import CloudTraceLoggingSpanExporter
100
- from app.utils.typing import Feedback, InputChat, dumpd, ensure_valid_config
98
+ from {{cookiecutter.agent_directory}}.utils.gcs import create_bucket_if_not_exists
99
+ from {{cookiecutter.agent_directory}}.utils.tracing import CloudTraceLoggingSpanExporter
100
+ from {{cookiecutter.agent_directory}}.utils.typing import Feedback, InputChat, dumpd, ensure_valid_config
101
101
 
102
102
 
103
103
  class AgentEngineApp:
@@ -110,7 +110,7 @@ class AgentEngineApp:
110
110
  def set_up(self) -> None:
111
111
  """The set_up method is used to define application initialization logic"""
112
112
  # Lazy import agent at setup time to avoid deployment dependencies
113
- from app.agent import agent
113
+ from {{cookiecutter.agent_directory}}.agent import agent
114
114
 
115
115
  logging_client = google_cloud_logging.Client(project=self.project_id)
116
116
  self.logger = logging_client.logger(__name__)
@@ -206,7 +206,7 @@ def deploy_agent_engine_app(
206
206
  location: str,
207
207
  agent_name: str | None = None,
208
208
  requirements_file: str = ".requirements.txt",
209
- extra_packages: list[str] = ["./app"],
209
+ extra_packages: list[str] = ["./{{cookiecutter.agent_directory}}"],
210
210
  env_vars: dict[str, str] = {},
211
211
  service_account: str | None = None,
212
212
  ) -> agent_engines.AgentEngine:
@@ -305,7 +305,7 @@ if __name__ == "__main__":
305
305
  parser.add_argument(
306
306
  "--extra-packages",
307
307
  nargs="+",
308
- default=["./app"],
308
+ default=["./{{cookiecutter.agent_directory}}"],
309
309
  help="Additional packages to include",
310
310
  )
311
311
  parser.add_argument(