agent-starter-pack 0.11.2__py3-none-any.whl → 0.12.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. {agent_starter_pack-0.11.2.dist-info → agent_starter_pack-0.12.1.dist-info}/METADATA +2 -1
  2. {agent_starter_pack-0.11.2.dist-info → agent_starter_pack-0.12.1.dist-info}/RECORD +51 -78
  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 +30 -2
  24. src/cli/commands/enhance.py +98 -15
  25. src/cli/commands/list.py +6 -1
  26. src/cli/utils/remote_template.py +5 -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/frontends/live_api_react/frontend/package-lock.json +9 -9
  36. src/resources/docs/adk-cheatsheet.md +3 -3
  37. src/resources/locks/uv-adk_base-agent_engine.lock +452 -452
  38. src/resources/locks/uv-adk_base-cloud_run.lock +571 -568
  39. src/resources/locks/uv-agentic_rag-agent_engine.lock +565 -566
  40. src/resources/locks/uv-agentic_rag-cloud_run.lock +716 -713
  41. src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +729 -735
  42. src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +923 -940
  43. src/resources/locks/uv-langgraph_base_react-agent_engine.lock +658 -664
  44. src/resources/locks/uv-langgraph_base_react-cloud_run.lock +852 -869
  45. src/resources/locks/uv-live_api-cloud_run.lock +758 -775
  46. src/base_template/app/__init__.py +0 -3
  47. src/deployment_targets/cloud_run/app/server.py +0 -206
  48. src/frontends/adk_gemini_fullstack/frontend/components.json +0 -21
  49. src/frontends/adk_gemini_fullstack/frontend/eslint.config.js +0 -28
  50. src/frontends/adk_gemini_fullstack/frontend/index.html +0 -12
  51. src/frontends/adk_gemini_fullstack/frontend/package-lock.json +0 -6105
  52. src/frontends/adk_gemini_fullstack/frontend/package.json +0 -47
  53. src/frontends/adk_gemini_fullstack/frontend/src/App.tsx +0 -564
  54. src/frontends/adk_gemini_fullstack/frontend/src/components/ActivityTimeline.tsx +0 -244
  55. src/frontends/adk_gemini_fullstack/frontend/src/components/ChatMessagesView.tsx +0 -420
  56. src/frontends/adk_gemini_fullstack/frontend/src/components/InputForm.tsx +0 -60
  57. src/frontends/adk_gemini_fullstack/frontend/src/components/WelcomeScreen.tsx +0 -56
  58. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/badge.tsx +0 -46
  59. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/button.tsx +0 -59
  60. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/card.tsx +0 -92
  61. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/input.tsx +0 -21
  62. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/scroll-area.tsx +0 -56
  63. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/select.tsx +0 -183
  64. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/tabs.tsx +0 -64
  65. src/frontends/adk_gemini_fullstack/frontend/src/components/ui/textarea.tsx +0 -18
  66. src/frontends/adk_gemini_fullstack/frontend/src/global.css +0 -154
  67. src/frontends/adk_gemini_fullstack/frontend/src/main.tsx +0 -13
  68. src/frontends/adk_gemini_fullstack/frontend/src/utils.ts +0 -7
  69. src/frontends/adk_gemini_fullstack/frontend/src/vite-env.d.ts +0 -1
  70. src/frontends/adk_gemini_fullstack/frontend/tsconfig.json +0 -28
  71. src/frontends/adk_gemini_fullstack/frontend/tsconfig.node.json +0 -24
  72. src/frontends/adk_gemini_fullstack/frontend/vite.config.ts +0 -41
  73. src/resources/locks/uv-adk_gemini_fullstack-agent_engine.lock +0 -3938
  74. src/resources/locks/uv-adk_gemini_fullstack-cloud_run.lock +0 -4501
  75. {agent_starter_pack-0.11.2.dist-info → agent_starter_pack-0.12.1.dist-info}/WHEEL +0 -0
  76. {agent_starter_pack-0.11.2.dist-info → agent_starter_pack-0.12.1.dist-info}/entry_points.txt +0 -0
  77. {agent_starter_pack-0.11.2.dist-info → agent_starter_pack-0.12.1.dist-info}/licenses/LICENSE +0 -0
  78. /src/base_template/{app → {{cookiecutter.agent_directory}}}/utils/gcs.py +0 -0
  79. /src/base_template/{app → {{cookiecutter.agent_directory}}}/utils/tracing.py +0 -0
  80. /src/base_template/{app → {{cookiecutter.agent_directory}}}/utils/typing.py +0 -0
@@ -108,7 +108,7 @@ steps:
108
108
  - "-c"
109
109
  - |
110
110
  uv export --no-hashes --no-sources --no-header --no-dev --no-emit-project --no-annotate --locked > .requirements.txt
111
- uv run app/agent_engine_app.py \
111
+ uv run {{cookiecutter.agent_directory}}/agent_engine_app.py \
112
112
  --project ${_STAGING_PROJECT_ID} \
113
113
  --location ${_REGION} \
114
114
  --service-account ${_APP_SA_EMAIL_STAGING} \
@@ -18,12 +18,17 @@ import os
18
18
  import pathlib
19
19
  import shutil
20
20
  import subprocess
21
+ import sys
21
22
  import tempfile
22
23
  from collections.abc import Callable
23
24
 
24
25
  import click
25
- import tomllib
26
26
  from click.core import ParameterSource
27
+
28
+ if sys.version_info >= (3, 11):
29
+ import tomllib
30
+ else:
31
+ import tomli as tomllib
27
32
  from rich.console import Console
28
33
  from rich.prompt import IntPrompt, Prompt
29
34
 
@@ -101,6 +106,11 @@ def shared_template_options(f: Callable) -> Callable:
101
106
  type=click.Choice(["agent_engine", "cloud_run"]),
102
107
  help="Deployment target name",
103
108
  )(f)
109
+ f = click.option(
110
+ "--agent-directory",
111
+ "-dir",
112
+ help="Name of the agent directory (overrides template default)",
113
+ )(f)
104
114
  return f
105
115
 
106
116
 
@@ -232,8 +242,10 @@ def create(
232
242
  region: str,
233
243
  skip_checks: bool,
234
244
  in_folder: bool,
245
+ agent_directory: str | None,
235
246
  base_template: str | None = None,
236
247
  skip_welcome: bool = False,
248
+ cli_overrides: dict | None = None,
237
249
  ) -> None:
238
250
  """Create GCP-based AI agent projects from templates."""
239
251
  try:
@@ -434,6 +446,14 @@ def create(
434
446
  / ".template"
435
447
  )
436
448
  config = load_template_config(template_path)
449
+
450
+ # Apply CLI overrides for local templates if provided (e.g., from enhance command)
451
+ if cli_overrides:
452
+ config = merge_template_configs(config, cli_overrides)
453
+ if debug:
454
+ logging.debug(
455
+ f"Applied CLI overrides to local template config: {cli_overrides}"
456
+ )
437
457
  # Data ingestion and datastore selection
438
458
  if include_data_ingestion or datastore:
439
459
  include_data_ingestion = True
@@ -610,6 +630,13 @@ def create(
610
630
  if debug:
611
631
  logging.debug(f"Output directory: {destination_dir}")
612
632
 
633
+ # Construct CLI overrides for template processing
634
+ final_cli_overrides = cli_overrides or {}
635
+ if agent_directory:
636
+ if "settings" not in final_cli_overrides:
637
+ final_cli_overrides["settings"] = {}
638
+ final_cli_overrides["settings"]["agent_directory"] = agent_directory
639
+
613
640
  try:
614
641
  # Process template (handles both local and remote templates)
615
642
  process_template(
@@ -623,8 +650,9 @@ def create(
623
650
  session_type=final_session_type,
624
651
  output_dir=destination_dir,
625
652
  remote_template_path=template_source_path,
626
- remote_config=config if template_source_path else None,
653
+ remote_config=config,
627
654
  in_folder=in_folder,
655
+ cli_overrides=final_cli_overrides,
628
656
  )
629
657
 
630
658
  # Replace region in all files if a different region was specified
@@ -13,11 +13,18 @@
13
13
  # limitations under the License.
14
14
 
15
15
  import pathlib
16
+ import sys
17
+ from typing import Any
16
18
 
17
19
  import click
18
20
  from rich.console import Console
19
21
  from rich.prompt import IntPrompt
20
22
 
23
+ if sys.version_info >= (3, 11):
24
+ import tomllib
25
+ else:
26
+ import tomli as tomllib
27
+
21
28
  from ..utils.logging import display_welcome_banner, handle_cli_error
22
29
  from ..utils.template import get_available_agents
23
30
  from .create import (
@@ -48,7 +55,7 @@ def display_base_template_selection(current_base: str) -> str:
48
55
  choice_num = 1
49
56
  current_choice = None
50
57
 
51
- for _num, agent in agents.items():
58
+ for agent in agents.values():
52
59
  template_choices[choice_num] = agent["name"]
53
60
  current_indicator = " (current)" if agent["name"] == current_base else ""
54
61
  console.print(
@@ -89,6 +96,10 @@ def display_base_template_selection(current_base: str) -> str:
89
96
  "--base-template",
90
97
  help="Base template to inherit from (e.g., adk_base, langgraph_base_react, agentic_rag)",
91
98
  )
99
+ @click.option(
100
+ "--agent-directory",
101
+ help="Custom directory name for agent files (default: 'app' or auto-detected from pyproject.toml)",
102
+ )
92
103
  @shared_template_options
93
104
  @handle_cli_error
94
105
  def enhance(
@@ -105,6 +116,7 @@ def enhance(
105
116
  region: str,
106
117
  skip_checks: bool,
107
118
  base_template: str | None,
119
+ agent_directory: str | None,
108
120
  ) -> None:
109
121
  """Enhance your existing project with AI agent capabilities.
110
122
 
@@ -113,7 +125,8 @@ def enhance(
113
125
  creating a new project directory.
114
126
 
115
127
  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.).
128
+ with agent code organized in an agent directory (default: /app, configurable via
129
+ --agent-directory).
117
130
 
118
131
  TEMPLATE_PATH can be:
119
132
  - A local directory path (e.g., . for current directory)
@@ -191,10 +204,13 @@ def enhance(
191
204
  load_remote_template_config,
192
205
  )
193
206
 
194
- # Prepare CLI overrides for base template
195
- cli_overrides = {}
207
+ # Prepare CLI overrides for base template and agent directory
208
+ cli_overrides: dict[str, Any] = {}
196
209
  if base_template:
197
210
  cli_overrides["base_template"] = base_template
211
+ if agent_directory:
212
+ cli_overrides["settings"] = cli_overrides.get("settings", {})
213
+ cli_overrides["settings"]["agent_directory"] = agent_directory
198
214
 
199
215
  # Load config from current directory for inheritance info
200
216
  current_dir = pathlib.Path.cwd()
@@ -209,6 +225,10 @@ def enhance(
209
225
  if selected_base_template != original_base_template_name:
210
226
  # Update CLI overrides with the selected base template
211
227
  cli_overrides["base_template"] = selected_base_template
228
+ # Preserve agent_directory override if it was set
229
+ if agent_directory:
230
+ cli_overrides["settings"] = cli_overrides.get("settings", {})
231
+ cli_overrides["settings"]["agent_directory"] = agent_directory
212
232
  base_template = selected_base_template
213
233
  console.print(
214
234
  f"✅ Selected base template: [cyan]{selected_base_template}[/cyan]"
@@ -232,9 +252,51 @@ def enhance(
232
252
  # Validate project structure when using current directory template
233
253
  if template_path == pathlib.Path("."):
234
254
  current_dir = pathlib.Path.cwd()
235
- app_folder = current_dir / "app"
236
255
 
237
- if not app_folder.exists() or not app_folder.is_dir():
256
+ # Determine agent directory: CLI param > pyproject.toml detection > default
257
+ detected_agent_directory = "app" # default
258
+ if not agent_directory: # Only try to detect if not provided via CLI
259
+ pyproject_path = current_dir / "pyproject.toml"
260
+ if pyproject_path.exists():
261
+ try:
262
+ with open(pyproject_path, "rb") as f:
263
+ pyproject_data = tomllib.load(f)
264
+ packages = (
265
+ pyproject_data.get("tool", {})
266
+ .get("hatch", {})
267
+ .get("build", {})
268
+ .get("targets", {})
269
+ .get("wheel", {})
270
+ .get("packages", [])
271
+ )
272
+ if packages:
273
+ # Find the first package that isn't 'frontend'
274
+ for pkg in packages:
275
+ if pkg != "frontend":
276
+ detected_agent_directory = pkg
277
+ break
278
+ except Exception as e:
279
+ if debug:
280
+ console.print(
281
+ f"[dim]Could not auto-detect agent directory: {e}[/dim]"
282
+ )
283
+ pass # Fall back to default
284
+
285
+ final_agent_directory = agent_directory or detected_agent_directory
286
+
287
+ # Show info about agent directory selection
288
+ if agent_directory:
289
+ console.print(
290
+ f"ℹ️ Using CLI-specified agent directory: [cyan]{agent_directory}[/cyan]"
291
+ )
292
+ elif detected_agent_directory != "app":
293
+ console.print(
294
+ f"ℹ️ Auto-detected agent directory: [cyan]{detected_agent_directory}[/cyan]"
295
+ )
296
+
297
+ agent_folder = current_dir / final_agent_directory
298
+
299
+ if not agent_folder.exists() or not agent_folder.is_dir():
238
300
  console.print()
239
301
  console.print(
240
302
  "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
@@ -245,42 +307,61 @@ def enhance(
245
307
  )
246
308
  console.print()
247
309
  console.print(
248
- "📁 [bold]Expected Structure:[/bold] [cyan]/app[/cyan] folder containing your agent code"
310
+ f"📁 [bold]Expected Structure:[/bold] [cyan]/{final_agent_directory}[/cyan] folder containing your agent code"
249
311
  )
250
312
  console.print(f"📍 [bold]Current Directory:[/bold] {current_dir}")
251
- console.print("❌ [bold red]Missing:[/bold red] /app folder")
313
+ console.print(
314
+ f"❌ [bold red]Missing:[/bold red] /{final_agent_directory} folder"
315
+ )
252
316
  console.print()
253
317
  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]"
318
+ f"The enhance command can still proceed, but for best compatibility"
319
+ f" your agent code should be organized in a /{final_agent_directory} folder structure."
256
320
  )
257
321
  console.print()
258
322
 
259
323
  # Ask for confirmation after showing the structure warning
324
+ console.print("💡 Options:")
260
325
  console.print(
261
- "💡 [dim]Consider creating an /app folder for better compatibility.[/dim]"
326
+ f" Create a /{final_agent_directory} folder and move your agent code there"
262
327
  )
328
+ if final_agent_directory == "app":
329
+ console.print(
330
+ " • Use [cyan]--agent-directory <custom_name>[/cyan] if your agent code is in a different directory"
331
+ )
332
+ else:
333
+ console.print(
334
+ " • Use [cyan]--agent-directory <custom_name>[/cyan] to specify your existing agent directory"
335
+ )
263
336
  console.print()
264
337
 
265
338
  if not auto_approve:
266
339
  if not click.confirm(
267
- "Continue with enhancement despite missing /app folder?",
340
+ f"Continue with enhancement despite missing /{final_agent_directory} folder?",
268
341
  default=True,
269
342
  ):
270
343
  console.print("✋ [yellow]Enhancement cancelled.[/yellow]")
271
344
  return
272
345
  else:
273
346
  # Check for common agent files
274
- agent_py = app_folder / "agent.py"
347
+ agent_py = agent_folder / "agent.py"
275
348
  if agent_py.exists():
276
349
  console.print(
277
- "Detected existing agent structure with [cyan]/app/agent.py[/cyan]"
350
+ f"Detected existing agent structure with [cyan]/{final_agent_directory}/agent.py[/cyan]"
278
351
  )
279
352
  else:
280
353
  console.print(
281
- "ℹ️ [blue]Found /app folder[/blue] - ensure your agent code is properly organized within it"
354
+ f"ℹ️ [blue]Found /{final_agent_directory} folder[/blue] - ensure your agent code is properly organized within it, including an agent.py file"
282
355
  )
283
356
 
357
+ # Prepare CLI overrides to pass to create command
358
+ final_cli_overrides: dict[str, Any] = {}
359
+ if base_template:
360
+ final_cli_overrides["base_template"] = base_template
361
+ if agent_directory:
362
+ final_cli_overrides["settings"] = final_cli_overrides.get("settings", {})
363
+ final_cli_overrides["settings"]["agent_directory"] = agent_directory
364
+
284
365
  # Call the create command with in-folder mode enabled
285
366
  ctx.invoke(
286
367
  create,
@@ -297,6 +378,8 @@ def enhance(
297
378
  region=region,
298
379
  skip_checks=skip_checks,
299
380
  in_folder=True, # Always use in-folder mode for enhance
381
+ agent_directory=agent_directory,
300
382
  base_template=base_template,
301
383
  skip_welcome=True, # Skip welcome message since enhance shows its own
384
+ cli_overrides=final_cli_overrides if final_cli_overrides else None,
302
385
  )
src/cli/commands/list.py CHANGED
@@ -14,9 +14,14 @@
14
14
 
15
15
  import logging
16
16
  import pathlib
17
+ import sys
17
18
 
18
19
  import click
19
- import tomllib
20
+
21
+ if sys.version_info >= (3, 11):
22
+ import tomllib
23
+ else:
24
+ import tomli as tomllib
20
25
  from rich.console import Console
21
26
  from rich.table import Table
22
27
 
@@ -18,11 +18,15 @@ import pathlib
18
18
  import re
19
19
  import shutil
20
20
  import subprocess
21
+ import sys
21
22
  import tempfile
22
23
  from dataclasses import dataclass
23
24
  from typing import Any
24
25
 
25
- import tomllib
26
+ if sys.version_info >= (3, 11):
27
+ import tomllib
28
+ else:
29
+ import tomli as tomllib
26
30
  from jinja2 import Environment
27
31
 
28
32
 
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