agent-starter-pack 0.3.3__py3-none-any.whl → 0.21.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 (255) hide show
  1. agent_starter_pack/agents/README.md +7 -0
  2. agents/langgraph_base_react/template/.templateconfig.yaml → agent_starter_pack/agents/adk_a2a_base/.template/templateconfig.yaml +5 -10
  3. agent_starter_pack/agents/adk_a2a_base/README.md +37 -0
  4. src/deployment_targets/cloud_run/Dockerfile → agent_starter_pack/agents/adk_a2a_base/app/__init__.py +2 -14
  5. agent_starter_pack/agents/adk_a2a_base/app/agent.py +70 -0
  6. agent_starter_pack/agents/adk_a2a_base/notebooks/adk_a2a_app_testing.ipynb +583 -0
  7. agents/crewai_coding_crew/notebooks/evaluating_crewai_agent.ipynb → agent_starter_pack/agents/adk_a2a_base/notebooks/evaluating_adk_agent.ipynb +163 -199
  8. {agents/adk_base → agent_starter_pack/agents/adk_a2a_base}/tests/integration/test_agent.py +2 -2
  9. agents/adk_base/template/.templateconfig.yaml → agent_starter_pack/agents/adk_base/.template/templateconfig.yaml +4 -1
  10. {agents → agent_starter_pack/agents}/adk_base/README.md +1 -1
  11. agent_starter_pack/agents/adk_base/app/__init__.py +17 -0
  12. {agents → agent_starter_pack/agents}/adk_base/app/agent.py +5 -2
  13. {agents → agent_starter_pack/agents}/adk_base/notebooks/adk_app_testing.ipynb +128 -82
  14. agents/langgraph_base_react/notebooks/evaluating_langgraph_agent.ipynb → agent_starter_pack/agents/adk_base/notebooks/evaluating_adk_agent.ipynb +181 -207
  15. agent_starter_pack/agents/adk_base/tests/integration/test_agent.py +58 -0
  16. agents/crewai_coding_crew/template/.templateconfig.yaml → agent_starter_pack/agents/adk_live/.template/templateconfig.yaml +5 -9
  17. agent_starter_pack/agents/adk_live/README.md +32 -0
  18. agent_starter_pack/agents/adk_live/app/__init__.py +17 -0
  19. agent_starter_pack/agents/adk_live/app/agent.py +51 -0
  20. agent_starter_pack/agents/adk_live/tests/unit/test_dummy.py +38 -0
  21. agents/agentic_rag/template/.templateconfig.yaml → agent_starter_pack/agents/agentic_rag/.template/templateconfig.yaml +7 -3
  22. {agents → agent_starter_pack/agents}/agentic_rag/README.md +1 -1
  23. agent_starter_pack/agents/agentic_rag/app/__init__.py +17 -0
  24. {agents → agent_starter_pack/agents}/agentic_rag/app/agent.py +8 -4
  25. {agents → agent_starter_pack/agents}/agentic_rag/notebooks/adk_app_testing.ipynb +128 -82
  26. agent_starter_pack/agents/agentic_rag/notebooks/evaluating_adk_agent.ipynb +1535 -0
  27. {agents → agent_starter_pack/agents}/agentic_rag/tests/integration/test_agent.py +3 -3
  28. agent_starter_pack/agents/langgraph_base/.template/templateconfig.yaml +31 -0
  29. agent_starter_pack/agents/langgraph_base/README.md +30 -0
  30. agent_starter_pack/agents/langgraph_base/app/__init__.py +17 -0
  31. agent_starter_pack/agents/langgraph_base/app/agent.py +34 -0
  32. {agents/crewai_coding_crew → agent_starter_pack/agents/langgraph_base}/notebooks/evaluating_langgraph_agent.ipynb +30 -17
  33. {agents/langgraph_base_react → agent_starter_pack/agents/langgraph_base}/tests/integration/test_agent.py +2 -2
  34. {src → agent_starter_pack}/base_template/.gitignore +5 -3
  35. agent_starter_pack/base_template/GEMINI.md +5 -0
  36. agent_starter_pack/base_template/Makefile +339 -0
  37. agent_starter_pack/base_template/README.md +267 -0
  38. agent_starter_pack/base_template/deployment/README.md +11 -0
  39. {src → agent_starter_pack}/base_template/deployment/terraform/apis.tf +2 -2
  40. {src → agent_starter_pack}/base_template/deployment/terraform/dev/apis.tf +6 -1
  41. {src → agent_starter_pack}/base_template/deployment/terraform/dev/iam.tf +12 -11
  42. {src → agent_starter_pack}/base_template/deployment/terraform/dev/providers.tf +5 -1
  43. {src → agent_starter_pack}/base_template/deployment/terraform/dev/storage.tf +1 -1
  44. {src → agent_starter_pack}/base_template/deployment/terraform/dev/variables.tf +10 -10
  45. agent_starter_pack/base_template/deployment/terraform/dev/{% if cookiecutter.is_adk %}telemetry.tf{% else %}unused_telemetry.tf{% endif %} +193 -0
  46. agent_starter_pack/base_template/deployment/terraform/github.tf +337 -0
  47. {src → agent_starter_pack}/base_template/deployment/terraform/iam.tf +20 -41
  48. {src → agent_starter_pack}/base_template/deployment/terraform/locals.tf +10 -3
  49. {src/resources/setup_cicd → agent_starter_pack/base_template/deployment/terraform}/providers.tf +8 -1
  50. {src → agent_starter_pack}/base_template/deployment/terraform/service_accounts.tf +7 -8
  51. agent_starter_pack/base_template/deployment/terraform/sql/completions.sql +138 -0
  52. {src → agent_starter_pack}/base_template/deployment/terraform/storage.tf +7 -16
  53. {src → agent_starter_pack}/base_template/deployment/terraform/variables.tf +61 -28
  54. {src → agent_starter_pack}/base_template/deployment/terraform/vars/env.tfvars +6 -1
  55. agent_starter_pack/base_template/deployment/terraform/{% if cookiecutter.cicd_runner == 'github_actions' %}wif.tf{% else %}unused_wif.tf{% endif %} +43 -0
  56. src/base_template/deployment/terraform/build_triggers.tf → agent_starter_pack/base_template/deployment/terraform/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}build_triggers.tf{% else %}unused_build_triggers.tf{% endif %} +55 -38
  57. agent_starter_pack/base_template/deployment/terraform/{% if cookiecutter.is_adk %}telemetry.tf{% else %}unused_telemetry.tf{% endif %} +206 -0
  58. {src → agent_starter_pack}/base_template/pyproject.toml +24 -37
  59. agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/deploy-to-prod.yaml +132 -0
  60. agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/pr_checks.yaml +65 -0
  61. agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/staging.yaml +259 -0
  62. src/base_template/deployment/cd/deploy-to-prod.yaml → agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/deploy-to-prod.yaml +38 -30
  63. src/base_template/deployment/ci/pr_checks.yaml → agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/pr_checks.yaml +5 -5
  64. agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/staging.yaml +322 -0
  65. agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils/telemetry.py +96 -0
  66. {src/base_template/app/utils → agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils}/typing.py +7 -9
  67. agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils/{% if cookiecutter.is_a2a and cookiecutter.agent_name == 'langgraph_base' %}converters{% else %}unused_converters{% endif %}/__init__.py +25 -0
  68. agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils/{% if cookiecutter.is_a2a and cookiecutter.agent_name == 'langgraph_base' %}converters{% else %}unused_converters{% endif %}/part_converter.py +138 -0
  69. agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils/{% if cookiecutter.is_a2a and cookiecutter.agent_name == 'langgraph_base' %}executor{% else %}unused_executor{% endif %}/__init__.py +13 -0
  70. agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils/{% if cookiecutter.is_a2a and cookiecutter.agent_name == 'langgraph_base' %}executor{% else %}unused_executor{% endif %}/a2a_agent_executor.py +265 -0
  71. agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils/{% if cookiecutter.is_a2a and cookiecutter.agent_name == 'langgraph_base' %}executor{% else %}unused_executor{% endif %}/task_result_aggregator.py +152 -0
  72. agent_starter_pack/cli/commands/create.py +1256 -0
  73. agent_starter_pack/cli/commands/enhance.py +611 -0
  74. agent_starter_pack/cli/commands/list.py +203 -0
  75. agent_starter_pack/cli/commands/register_gemini_enterprise.py +1070 -0
  76. agent_starter_pack/cli/commands/setup_cicd.py +862 -0
  77. {src → agent_starter_pack}/cli/main.py +10 -2
  78. {src → agent_starter_pack}/cli/utils/cicd.py +139 -48
  79. agent_starter_pack/cli/utils/gcp.py +263 -0
  80. agent_starter_pack/cli/utils/logging.py +103 -0
  81. agent_starter_pack/cli/utils/remote_template.py +677 -0
  82. agent_starter_pack/cli/utils/template.py +1466 -0
  83. {src → agent_starter_pack}/data_ingestion/data_ingestion_pipeline/components/process_data.py +1 -1
  84. {src → agent_starter_pack}/data_ingestion/data_ingestion_pipeline/submit_pipeline.py +20 -6
  85. {src → agent_starter_pack}/data_ingestion/pyproject.toml +1 -0
  86. {src → agent_starter_pack}/data_ingestion/uv.lock +602 -494
  87. agent_starter_pack/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +484 -0
  88. agent_starter_pack/deployment_targets/agent_engine/tests/load_test/README.md +84 -0
  89. agent_starter_pack/deployment_targets/agent_engine/tests/load_test/load_test.py +424 -0
  90. agent_starter_pack/deployment_targets/agent_engine/tests/{% if cookiecutter.is_a2a %}helpers.py{% else %}unused_helpers.py{% endif %} +138 -0
  91. agent_starter_pack/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/agent_engine_app.py +263 -0
  92. agent_starter_pack/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/app_utils/deploy.py +414 -0
  93. agent_starter_pack/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/app_utils/{% if cookiecutter.is_adk_live %}expose_app.py{% else %}unused_expose_app.py{% endif %} +519 -0
  94. agent_starter_pack/deployment_targets/cloud_run/Dockerfile +51 -0
  95. agent_starter_pack/deployment_targets/cloud_run/deployment/terraform/dev/service.tf +243 -0
  96. agent_starter_pack/deployment_targets/cloud_run/deployment/terraform/service.tf +417 -0
  97. agent_starter_pack/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +705 -0
  98. agent_starter_pack/deployment_targets/cloud_run/tests/load_test/.results/.placeholder +321 -0
  99. agent_starter_pack/deployment_targets/cloud_run/tests/load_test/README.md +165 -0
  100. agent_starter_pack/deployment_targets/cloud_run/tests/load_test/load_test.py +329 -0
  101. agent_starter_pack/deployment_targets/cloud_run/{{cookiecutter.agent_directory}}/fast_api_app.py +556 -0
  102. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/package-lock.json +79 -1044
  103. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/package.json +1 -9
  104. agent_starter_pack/frontends/adk_live_react/frontend/src/App.tsx +65 -0
  105. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/logger/Logger.tsx +8 -3
  106. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/logger/logger.scss +26 -0
  107. agent_starter_pack/frontends/adk_live_react/frontend/src/components/side-panel/SidePanel.tsx +516 -0
  108. agent_starter_pack/frontends/adk_live_react/frontend/src/components/side-panel/side-panel.scss +563 -0
  109. agent_starter_pack/frontends/adk_live_react/frontend/src/components/transcription-preview/TranscriptionPreview.tsx +106 -0
  110. agent_starter_pack/frontends/adk_live_react/frontend/src/components/transcription-preview/transcription-preview.scss +150 -0
  111. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/hooks/use-live-api.ts +8 -2
  112. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/multimodal-live-types.ts +40 -2
  113. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/audio-recorder.ts +1 -1
  114. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/audio-streamer.ts +1 -1
  115. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/multimodal-live-client.ts +210 -24
  116. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/utils.ts +27 -5
  117. agent_starter_pack/resources/docs/adk-cheatsheet.md +1628 -0
  118. agent_starter_pack/resources/locks/uv-adk_a2a_base-agent_engine.lock +4966 -0
  119. agent_starter_pack/resources/locks/uv-adk_a2a_base-cloud_run.lock +5011 -0
  120. agent_starter_pack/resources/locks/uv-adk_base-agent_engine.lock +4946 -0
  121. agent_starter_pack/resources/locks/uv-adk_base-cloud_run.lock +4991 -0
  122. agent_starter_pack/resources/locks/uv-adk_live-agent_engine.lock +4963 -0
  123. agent_starter_pack/resources/locks/uv-adk_live-cloud_run.lock +5006 -0
  124. agent_starter_pack/resources/locks/uv-agentic_rag-agent_engine.lock +5487 -0
  125. agent_starter_pack/resources/locks/uv-agentic_rag-cloud_run.lock +5532 -0
  126. agent_starter_pack/resources/locks/uv-langgraph_base-agent_engine.lock +5788 -0
  127. agent_starter_pack/resources/locks/uv-langgraph_base-cloud_run.lock +5811 -0
  128. {src → agent_starter_pack}/utils/generate_locks.py +15 -12
  129. {src → agent_starter_pack}/utils/lock_utils.py +4 -7
  130. {src → agent_starter_pack}/utils/watch_and_rebuild.py +2 -2
  131. agent_starter_pack-0.21.0.dist-info/METADATA +182 -0
  132. agent_starter_pack-0.21.0.dist-info/RECORD +171 -0
  133. agent_starter_pack-0.21.0.dist-info/entry_points.txt +2 -0
  134. llm.txt +362 -0
  135. agent_starter_pack-0.3.3.dist-info/METADATA +0 -164
  136. agent_starter_pack-0.3.3.dist-info/RECORD +0 -176
  137. agent_starter_pack-0.3.3.dist-info/entry_points.txt +0 -2
  138. agents/crewai_coding_crew/README.md +0 -34
  139. agents/crewai_coding_crew/app/agent.py +0 -86
  140. agents/crewai_coding_crew/app/crew/config/agents.yaml +0 -39
  141. agents/crewai_coding_crew/app/crew/config/tasks.yaml +0 -37
  142. agents/crewai_coding_crew/app/crew/crew.py +0 -71
  143. agents/crewai_coding_crew/tests/integration/test_agent.py +0 -47
  144. agents/langgraph_base_react/README.md +0 -9
  145. agents/langgraph_base_react/app/agent.py +0 -73
  146. agents/live_api/README.md +0 -37
  147. agents/live_api/app/agent.py +0 -78
  148. agents/live_api/app/server.py +0 -196
  149. agents/live_api/app/templates.py +0 -51
  150. agents/live_api/app/vector_store.py +0 -55
  151. agents/live_api/template/.templateconfig.yaml +0 -29
  152. agents/live_api/tests/integration/test_server_e2e.py +0 -254
  153. agents/live_api/tests/load_test/load_test.py +0 -40
  154. agents/live_api/tests/unit/test_server.py +0 -143
  155. src/base_template/Makefile +0 -72
  156. src/base_template/README.md +0 -208
  157. src/base_template/app/__init__.py +0 -3
  158. src/base_template/app/utils/tracing.py +0 -155
  159. src/base_template/deployment/README.md +0 -126
  160. src/base_template/deployment/cd/staging.yaml +0 -216
  161. src/base_template/deployment/terraform/dev/log_sinks.tf +0 -63
  162. src/base_template/deployment/terraform/log_sinks.tf +0 -70
  163. src/base_template/deployment/terraform/providers.tf +0 -37
  164. src/cli/commands/create.py +0 -664
  165. src/cli/commands/setup_cicd.py +0 -829
  166. src/cli/utils/gcp.py +0 -117
  167. src/cli/utils/logging.py +0 -51
  168. src/cli/utils/template.py +0 -737
  169. src/deployment_targets/agent_engine/app/agent_engine_app.py +0 -336
  170. src/deployment_targets/agent_engine/notebooks/intro_agent_engine.ipynb +0 -1025
  171. src/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +0 -183
  172. src/deployment_targets/agent_engine/tests/load_test/README.md +0 -42
  173. src/deployment_targets/agent_engine/tests/load_test/load_test.py +0 -107
  174. src/deployment_targets/cloud_run/app/server.py +0 -154
  175. src/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +0 -249
  176. src/deployment_targets/cloud_run/tests/load_test/.results/.placeholder +0 -0
  177. src/deployment_targets/cloud_run/tests/load_test/README.md +0 -83
  178. src/deployment_targets/cloud_run/tests/load_test/load_test.py +0 -118
  179. src/deployment_targets/cloud_run/uv.lock +0 -6952
  180. src/frontends/live_api_react/frontend/src/App.tsx +0 -205
  181. src/frontends/live_api_react/frontend/src/components/control-tray/ControlTray.tsx +0 -217
  182. src/frontends/live_api_react/frontend/src/components/control-tray/control-tray.scss +0 -201
  183. src/frontends/live_api_react/frontend/src/components/side-panel/SidePanel.tsx +0 -161
  184. src/frontends/live_api_react/frontend/src/components/side-panel/side-panel.scss +0 -285
  185. src/frontends/streamlit/frontend/side_bar.py +0 -214
  186. src/frontends/streamlit/frontend/streamlit_app.py +0 -265
  187. src/frontends/streamlit/frontend/style/app_markdown.py +0 -37
  188. src/frontends/streamlit/frontend/utils/chat_utils.py +0 -67
  189. src/frontends/streamlit/frontend/utils/local_chat_history.py +0 -125
  190. src/frontends/streamlit/frontend/utils/message_editing.py +0 -59
  191. src/frontends/streamlit/frontend/utils/multimodal_utils.py +0 -217
  192. src/frontends/streamlit/frontend/utils/stream_handler.py +0 -301
  193. src/frontends/streamlit/frontend/utils/title_summary.py +0 -94
  194. src/frontends/streamlit_adk/frontend/side_bar.py +0 -214
  195. src/frontends/streamlit_adk/frontend/streamlit_app.py +0 -314
  196. src/frontends/streamlit_adk/frontend/style/app_markdown.py +0 -37
  197. src/frontends/streamlit_adk/frontend/utils/chat_utils.py +0 -84
  198. src/frontends/streamlit_adk/frontend/utils/local_chat_history.py +0 -110
  199. src/frontends/streamlit_adk/frontend/utils/message_editing.py +0 -61
  200. src/frontends/streamlit_adk/frontend/utils/multimodal_utils.py +0 -223
  201. src/frontends/streamlit_adk/frontend/utils/stream_handler.py +0 -311
  202. src/frontends/streamlit_adk/frontend/utils/title_summary.py +0 -129
  203. src/resources/containers/data_processing/Dockerfile +0 -27
  204. src/resources/containers/e2e-tests/Dockerfile +0 -19
  205. src/resources/idx/.idx/dev.nix +0 -57
  206. src/resources/idx/idx-template.json +0 -21
  207. src/resources/idx/idx-template.nix +0 -26
  208. src/resources/locks/uv-adk_base-agent_engine.lock +0 -5338
  209. src/resources/locks/uv-adk_base-cloud_run.lock +0 -5930
  210. src/resources/locks/uv-agentic_rag-agent_engine.lock +0 -5528
  211. src/resources/locks/uv-agentic_rag-cloud_run.lock +0 -6120
  212. src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +0 -6231
  213. src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +0 -6839
  214. src/resources/locks/uv-langgraph_base_react-agent_engine.lock +0 -5233
  215. src/resources/locks/uv-langgraph_base_react-cloud_run.lock +0 -5862
  216. src/resources/locks/uv-live_api-cloud_run.lock +0 -5832
  217. src/resources/setup_cicd/cicd_variables.tf +0 -41
  218. src/resources/setup_cicd/github.tf +0 -87
  219. {agents → agent_starter_pack/agents}/agentic_rag/app/retrievers.py +0 -0
  220. {agents → agent_starter_pack/agents}/agentic_rag/app/templates.py +0 -0
  221. {src → agent_starter_pack}/base_template/deployment/terraform/dev/vars/env.tfvars +0 -0
  222. {src → agent_starter_pack}/base_template/tests/unit/test_dummy.py +0 -0
  223. {src/deployment_targets/agent_engine/app/utils → agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils}/gcs.py +0 -0
  224. {src → agent_starter_pack}/cli/utils/__init__.py +0 -0
  225. {src → agent_starter_pack}/cli/utils/datastores.py +0 -0
  226. {src → agent_starter_pack}/cli/utils/version.py +0 -0
  227. {src → agent_starter_pack}/data_ingestion/README.md +0 -0
  228. {src → agent_starter_pack}/data_ingestion/data_ingestion_pipeline/components/ingest_data.py +0 -0
  229. {src → agent_starter_pack}/data_ingestion/data_ingestion_pipeline/pipeline.py +0 -0
  230. {src → agent_starter_pack}/deployment_targets/agent_engine/deployment_metadata.json +0 -0
  231. {src → agent_starter_pack}/deployment_targets/agent_engine/tests/load_test/.results/.placeholder +0 -0
  232. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/public/favicon.ico +0 -0
  233. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/public/index.html +0 -0
  234. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/public/robots.txt +0 -0
  235. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/App.scss +0 -0
  236. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/App.test.tsx +0 -0
  237. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/audio-pulse/AudioPulse.tsx +0 -0
  238. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/audio-pulse/audio-pulse.scss +0 -0
  239. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/logger/mock-logs.ts +0 -0
  240. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/contexts/LiveAPIContext.tsx +0 -0
  241. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/hooks/use-media-stream-mux.ts +0 -0
  242. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/hooks/use-screen-capture.ts +0 -0
  243. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/hooks/use-webcam.ts +0 -0
  244. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/index.css +0 -0
  245. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/index.tsx +0 -0
  246. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/react-app-env.d.ts +0 -0
  247. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/reportWebVitals.ts +0 -0
  248. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/setupTests.ts +0 -0
  249. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/audioworklet-registry.ts +0 -0
  250. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/store-logger.ts +0 -0
  251. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/worklets/audio-processing.ts +0 -0
  252. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/worklets/vol-meter.ts +0 -0
  253. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/tsconfig.json +0 -0
  254. {agent_starter_pack-0.3.3.dist-info → agent_starter_pack-0.21.0.dist-info}/WHEEL +0 -0
  255. {agent_starter_pack-0.3.3.dist-info → agent_starter_pack-0.21.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,1466 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import json
16
+ import logging
17
+ import os
18
+ import pathlib
19
+ import re
20
+ import shutil
21
+ import subprocess
22
+ import sys
23
+ import tempfile
24
+ from dataclasses import dataclass
25
+ from typing import Any
26
+
27
+ import yaml
28
+ from cookiecutter.main import cookiecutter
29
+ from rich.console import Console
30
+ from rich.prompt import Confirm, IntPrompt, Prompt
31
+
32
+ from agent_starter_pack.cli.utils.version import get_current_version
33
+
34
+ from .datastores import DATASTORES
35
+ from .remote_template import (
36
+ get_base_template_name,
37
+ render_and_merge_makefiles,
38
+ )
39
+
40
+
41
+ def add_base_template_dependencies_interactively(
42
+ project_path: pathlib.Path,
43
+ base_dependencies: list[str],
44
+ base_template_name: str,
45
+ auto_approve: bool = False,
46
+ ) -> bool:
47
+ """Interactively add base template dependencies using uv add.
48
+
49
+ Args:
50
+ project_path: Path to the project directory
51
+ base_dependencies: List of dependencies from base template's extra_dependencies
52
+ base_template_name: Name of the base template being used
53
+ auto_approve: Whether to skip confirmation and auto-install
54
+
55
+ Returns:
56
+ True if dependencies were added successfully, False otherwise
57
+ """
58
+ if not base_dependencies:
59
+ return True
60
+
61
+ console = Console()
62
+
63
+ # Construct dependency string once for reuse
64
+ deps_str = " ".join(f"'{dep}'" for dep in base_dependencies)
65
+
66
+ # Show what dependencies will be added
67
+ console.print(
68
+ f"\n✓ Base template override: Using '{base_template_name}' as foundation",
69
+ style="bold cyan",
70
+ )
71
+ console.print(" This requires adding the following dependencies:", style="white")
72
+ for dep in base_dependencies:
73
+ console.print(f" • {dep}", style="yellow")
74
+
75
+ # Ask for confirmation unless auto-approve
76
+ should_add = True
77
+ if not auto_approve:
78
+ should_add = Confirm.ask(
79
+ "\n? Add these dependencies automatically?", default=True
80
+ )
81
+
82
+ if not should_add:
83
+ console.print("\n⚠️ Skipped dependency installation.", style="yellow")
84
+ console.print(" To add them manually later, run:", style="dim")
85
+ console.print(f" cd {project_path.name}", style="dim")
86
+ console.print(f" uv add {deps_str}\n", style="dim")
87
+ return False
88
+
89
+ # Run uv add
90
+ try:
91
+ if auto_approve:
92
+ console.print(
93
+ f"✓ Auto-installing dependencies: {', '.join(base_dependencies)}",
94
+ style="bold cyan",
95
+ )
96
+ else:
97
+ console.print(f"\n✓ Running: uv add {deps_str}", style="bold cyan")
98
+
99
+ # Run uv add in the project directory
100
+ cmd = ["uv", "add"] + base_dependencies
101
+ result = subprocess.run(
102
+ cmd,
103
+ cwd=project_path,
104
+ capture_output=True,
105
+ text=True,
106
+ check=True,
107
+ )
108
+
109
+ # Show success message
110
+ if not auto_approve:
111
+ # Show a summary line from uv output
112
+ output_lines = result.stderr.strip().split("\n")
113
+ for line in output_lines:
114
+ if "Resolved" in line or "Installed" in line:
115
+ console.print(f" {line}", style="dim")
116
+ break
117
+
118
+ console.print("✓ Dependencies added successfully\n", style="bold green")
119
+ return True
120
+
121
+ except subprocess.CalledProcessError as e:
122
+ console.print(
123
+ f"\n✗ Failed to add dependencies: {e.stderr.strip()}", style="bold red"
124
+ )
125
+ console.print(" You can add them manually:", style="yellow")
126
+ console.print(f" cd {project_path.name}", style="dim")
127
+ console.print(f" uv add {deps_str}\n", style="dim")
128
+ return False
129
+ except FileNotFoundError:
130
+ console.print(
131
+ "\n✗ uv command not found. Please install uv first.", style="bold red"
132
+ )
133
+ console.print(" Install from: https://docs.astral.sh/uv/", style="dim")
134
+ console.print("\n To add dependencies manually:", style="yellow")
135
+ console.print(f" cd {project_path.name}", style="dim")
136
+ console.print(f" uv add {deps_str}\n", style="dim")
137
+ return False
138
+
139
+
140
+ def validate_agent_directory_name(agent_dir: str) -> None:
141
+ """Validate that an agent directory name is a valid Python identifier.
142
+
143
+ Args:
144
+ agent_dir: The agent directory name to validate
145
+
146
+ Raises:
147
+ ValueError: If the agent directory name is not a valid Python identifier
148
+ """
149
+ if "-" in agent_dir:
150
+ raise ValueError(
151
+ f"Agent directory '{agent_dir}' contains hyphens (-) which are not allowed. "
152
+ "Agent directories must be valid Python identifiers since they're used as module names. "
153
+ "Please use underscores (_) or lowercase letters instead."
154
+ )
155
+
156
+ if not agent_dir.replace("_", "a").isidentifier():
157
+ raise ValueError(
158
+ f"Agent directory '{agent_dir}' is not a valid Python identifier. "
159
+ "Agent directories must be valid Python identifiers since they're used as module names. "
160
+ "Please use only lowercase letters, numbers, and underscores, and don't start with a number."
161
+ )
162
+
163
+
164
+ @dataclass
165
+ class TemplateConfig:
166
+ name: str
167
+ description: str
168
+ settings: dict[str, bool | list[str]]
169
+
170
+ @classmethod
171
+ def from_file(cls, config_path: pathlib.Path) -> "TemplateConfig":
172
+ """Load template config from file with validation"""
173
+ try:
174
+ with open(config_path, encoding="utf-8") as f:
175
+ data = yaml.safe_load(f)
176
+
177
+ if not isinstance(data, dict):
178
+ raise ValueError(f"Invalid template config format in {config_path}")
179
+
180
+ required_fields = ["name", "description", "settings"]
181
+ missing_fields = [f for f in required_fields if f not in data]
182
+ if missing_fields:
183
+ raise ValueError(
184
+ f"Missing required fields in template config: {missing_fields}"
185
+ )
186
+
187
+ return cls(
188
+ name=data["name"],
189
+ description=data["description"],
190
+ settings=data["settings"],
191
+ )
192
+ except yaml.YAMLError as err:
193
+ raise ValueError(f"Invalid YAML in template config: {err}") from err
194
+ except Exception as err:
195
+ raise ValueError(f"Error loading template config: {err}") from err
196
+
197
+
198
+ def get_overwrite_folders(agent_directory: str) -> list[str]:
199
+ """Get folders to overwrite with configurable agent directory."""
200
+ return [agent_directory, "frontend", "tests", "notebooks"]
201
+
202
+
203
+ TEMPLATE_CONFIG_FILE = "templateconfig.yaml"
204
+ DEPLOYMENT_FOLDERS = ["cloud_run", "agent_engine"]
205
+ DEFAULT_FRONTEND = "None"
206
+
207
+
208
+ def get_available_agents(deployment_target: str | None = None) -> dict:
209
+ """Dynamically load available agents from the agents directory.
210
+
211
+ Args:
212
+ deployment_target: Optional deployment target to filter agents
213
+ """
214
+ # Define priority agents that should appear first
215
+ PRIORITY_AGENTS = [
216
+ "adk_base",
217
+ "adk_a2a_base",
218
+ "adk_live",
219
+ "agentic_rag",
220
+ "langgraph_base",
221
+ ]
222
+
223
+ agents_list = []
224
+ priority_agents_dict = dict.fromkeys(PRIORITY_AGENTS) # Track priority agents
225
+ agents_dir = pathlib.Path(__file__).parent.parent.parent / "agents"
226
+
227
+ for agent_dir in agents_dir.iterdir():
228
+ if agent_dir.is_dir() and not agent_dir.name.startswith("__"):
229
+ template_config_path = agent_dir / ".template" / "templateconfig.yaml"
230
+ if template_config_path.exists():
231
+ try:
232
+ with open(template_config_path, encoding="utf-8") as f:
233
+ config = yaml.safe_load(f)
234
+ agent_name = agent_dir.name
235
+
236
+ # Skip if deployment target specified and agent doesn't support it
237
+ if deployment_target:
238
+ targets = config.get("settings", {}).get(
239
+ "deployment_targets", []
240
+ )
241
+ if isinstance(targets, str):
242
+ targets = [targets]
243
+ if deployment_target not in targets:
244
+ continue
245
+
246
+ description = config.get("description", "No description available")
247
+ agent_info = {"name": agent_name, "description": description}
248
+
249
+ # Add to priority list or regular list based on agent name
250
+ if agent_name in PRIORITY_AGENTS:
251
+ priority_agents_dict[agent_name] = agent_info
252
+ else:
253
+ agents_list.append(agent_info)
254
+ except Exception as e:
255
+ logging.warning(f"Could not load agent from {agent_dir}: {e}")
256
+
257
+ # Sort the non-priority agents
258
+ agents_list.sort(key=lambda x: x["name"])
259
+
260
+ # Create priority agents list in the exact order specified
261
+ priority_agents = [
262
+ info for name, info in priority_agents_dict.items() if info is not None
263
+ ]
264
+
265
+ # Combine priority agents with regular agents
266
+ combined_agents = priority_agents + agents_list
267
+
268
+ # Convert to numbered dictionary starting from 1
269
+ agents = {i + 1: agent for i, agent in enumerate(combined_agents)}
270
+
271
+ return agents
272
+
273
+
274
+ def load_template_config(template_dir: pathlib.Path) -> dict[str, Any]:
275
+ """Read .templateconfig.yaml file to get agent configuration."""
276
+ config_file = template_dir / TEMPLATE_CONFIG_FILE
277
+ if not config_file.exists():
278
+ return {}
279
+
280
+ try:
281
+ with open(config_file, encoding="utf-8") as f:
282
+ config = yaml.safe_load(f)
283
+ return config if config else {}
284
+ except Exception as e:
285
+ logging.error(f"Error loading template config: {e}")
286
+ return {}
287
+
288
+
289
+ def get_deployment_targets(
290
+ agent_name: str, remote_config: dict[str, Any] | None = None
291
+ ) -> list:
292
+ """Get available deployment targets for the selected agent."""
293
+ if remote_config:
294
+ config = remote_config
295
+ else:
296
+ template_path = (
297
+ pathlib.Path(__file__).parent.parent.parent
298
+ / "agents"
299
+ / agent_name
300
+ / ".template"
301
+ )
302
+ config = load_template_config(template_path)
303
+
304
+ if not config:
305
+ return []
306
+
307
+ targets = config.get("settings", {}).get("deployment_targets", [])
308
+ return targets if isinstance(targets, list) else [targets]
309
+
310
+
311
+ def prompt_deployment_target(
312
+ agent_name: str, remote_config: dict[str, Any] | None = None
313
+ ) -> str:
314
+ """Ask user to select a deployment target for the agent."""
315
+ targets = get_deployment_targets(agent_name, remote_config=remote_config)
316
+
317
+ # Define deployment target friendly names and descriptions
318
+ TARGET_INFO = {
319
+ "agent_engine": {
320
+ "display_name": "Vertex AI Agent Engine",
321
+ "description": "Vertex AI Managed platform for scalable agent deployments",
322
+ },
323
+ "cloud_run": {
324
+ "display_name": "Cloud Run",
325
+ "description": "GCP Serverless container execution",
326
+ },
327
+ }
328
+
329
+ if not targets:
330
+ return ""
331
+
332
+ console = Console()
333
+ console.print("\n> Please select a deployment target:")
334
+ for idx, target in enumerate(targets, 1):
335
+ info = TARGET_INFO.get(target, {})
336
+ display_name = info.get("display_name", target)
337
+ description = info.get("description", "")
338
+ console.print(f"{idx}. [bold]{display_name}[/] - [dim]{description}[/]")
339
+
340
+ choice = IntPrompt.ask(
341
+ "\nEnter the number of your deployment target choice",
342
+ default=1,
343
+ show_default=True,
344
+ )
345
+ return targets[choice - 1]
346
+
347
+
348
+ def prompt_session_type_selection() -> str:
349
+ """Ask user to select a session type for Cloud Run deployment."""
350
+ console = Console()
351
+
352
+ session_types = {
353
+ "in_memory": {
354
+ "display_name": "In-memory session",
355
+ "description": "Session data stored in memory - ideal for stateless applications",
356
+ },
357
+ "cloud_sql": {
358
+ "display_name": "Cloud SQL (PostgreSQL)",
359
+ "description": "Managed PostgreSQL database for robust session persistence",
360
+ },
361
+ "agent_engine": {
362
+ "display_name": "Vertex AI Agent Engine",
363
+ "description": "Managed session service that automatically handles conversation history",
364
+ },
365
+ }
366
+
367
+ console.print("\n> Please select a session type:")
368
+ for idx, (_key, info) in enumerate(session_types.items(), 1):
369
+ console.print(
370
+ f"{idx}. [bold]{info['display_name']}[/] - [dim]{info['description']}[/]"
371
+ )
372
+
373
+ choice = IntPrompt.ask(
374
+ "\nEnter the number of your session type choice",
375
+ default=1,
376
+ show_default=True,
377
+ )
378
+
379
+ return list(session_types.keys())[choice - 1]
380
+
381
+
382
+ def prompt_datastore_selection(
383
+ agent_name: str, from_cli_flag: bool = False
384
+ ) -> str | None:
385
+ """Ask user to select a datastore type if the agent supports data ingestion.
386
+
387
+ Args:
388
+ agent_name: Name of the agent
389
+ from_cli_flag: Whether this is being called due to explicit --include-data-ingestion flag
390
+ """
391
+ console = Console()
392
+
393
+ # If this is from CLI flag, skip the "would you like to include" prompt
394
+ if from_cli_flag:
395
+ console.print("\n> Please select a datastore type for your data:")
396
+
397
+ # Display options with descriptions
398
+ for i, (_key, info) in enumerate(DATASTORES.items(), 1):
399
+ console.print(
400
+ f"{i}. [bold]{info['name']}[/] - [dim]{info['description']}[/]"
401
+ )
402
+
403
+ choice = Prompt.ask(
404
+ "\nEnter the number of your choice",
405
+ choices=[str(i) for i in range(1, len(DATASTORES) + 1)],
406
+ default="1",
407
+ )
408
+
409
+ # Convert choice number to datastore type
410
+ datastore_type = list(DATASTORES.keys())[int(choice) - 1]
411
+ return datastore_type
412
+
413
+ # Otherwise, proceed with normal flow
414
+ template_path = (
415
+ pathlib.Path(__file__).parent.parent.parent
416
+ / "agents"
417
+ / agent_name
418
+ / ".template"
419
+ )
420
+ config = load_template_config(template_path)
421
+
422
+ if config:
423
+ # If requires_data_ingestion is true, prompt for datastore type without asking if they want it
424
+ if config.get("settings", {}).get("requires_data_ingestion"):
425
+ console.print("\n> This agent includes a data ingestion pipeline.")
426
+ console.print("> Please select a datastore type for your data:")
427
+
428
+ # Display options with descriptions
429
+ for i, (_key, info) in enumerate(DATASTORES.items(), 1):
430
+ console.print(
431
+ f"{i}. [bold]{info['name']}[/] - [dim]{info['description']}[/]"
432
+ )
433
+ choice = Prompt.ask(
434
+ "\nEnter the number of your choice",
435
+ choices=[str(i) for i in range(1, len(DATASTORES) + 1)],
436
+ default="1",
437
+ )
438
+
439
+ # Convert choice number to datastore type
440
+ datastore_type = list(DATASTORES.keys())[int(choice) - 1]
441
+ return datastore_type
442
+
443
+ # Only prompt if the agent has optional data ingestion support
444
+ if "requires_data_ingestion" in config.get("settings", {}):
445
+ include = (
446
+ Prompt.ask(
447
+ "\n> This agent supports data ingestion. Would you like to include a data pipeline?",
448
+ choices=["y", "n"],
449
+ default="n",
450
+ ).lower()
451
+ == "y"
452
+ )
453
+
454
+ if include:
455
+ console.print("\n> Please select a datastore type for your data:")
456
+
457
+ # Display options with descriptions
458
+ for i, (_key, info) in enumerate(DATASTORES.items(), 1):
459
+ console.print(
460
+ f"{i}. [bold]{info['name']}[/] - [dim]{info['description']}[/]"
461
+ )
462
+
463
+ choice = Prompt.ask(
464
+ "\nEnter the number of your choice",
465
+ choices=[str(i) for i in range(1, len(DATASTORES) + 1)],
466
+ default="1",
467
+ )
468
+
469
+ # Convert choice number to datastore type
470
+ datastore_type = list(DATASTORES.keys())[int(choice) - 1]
471
+ return datastore_type
472
+
473
+ # If we get here, we need to prompt for datastore selection for explicit --include-data-ingestion flag
474
+ console.print(
475
+ "\n> Please select a datastore type for your data ingestion pipeline:"
476
+ )
477
+ # Display options with descriptions
478
+ for i, (_key, info) in enumerate(DATASTORES.items(), 1):
479
+ console.print(f"{i}. [bold]{info['name']}[/] - [dim]{info['description']}[/]")
480
+
481
+ choice = Prompt.ask(
482
+ "\nEnter the number of your choice",
483
+ choices=[str(i) for i in range(1, len(DATASTORES) + 1)],
484
+ default="1",
485
+ )
486
+
487
+ # Convert choice number to datastore type
488
+ datastore_type = list(DATASTORES.keys())[int(choice) - 1]
489
+ return datastore_type
490
+
491
+
492
+ def prompt_cicd_runner_selection() -> str:
493
+ """Ask user to select a CI/CD runner."""
494
+ console = Console()
495
+
496
+ cicd_runners = {
497
+ "google_cloud_build": {
498
+ "display_name": "Google Cloud Build",
499
+ "description": "Fully managed CI/CD, deeply integrated with GCP for fast, consistent builds and deployments.",
500
+ },
501
+ "github_actions": {
502
+ "display_name": "GitHub Actions",
503
+ "description": "GitHub Actions: CI/CD with secure workload identity federation directly in GitHub.",
504
+ },
505
+ }
506
+
507
+ console.print("\n> Please select a CI/CD runner:")
508
+ for idx, (_key, info) in enumerate(cicd_runners.items(), 1):
509
+ console.print(
510
+ f"{idx}. [bold]{info['display_name']}[/] - [dim]{info['description']}[/]"
511
+ )
512
+
513
+ choice = IntPrompt.ask(
514
+ "\nEnter the number of your CI/CD runner choice",
515
+ default=1,
516
+ show_default=True,
517
+ )
518
+
519
+ return list(cicd_runners.keys())[choice - 1]
520
+
521
+
522
+ def get_template_path(agent_name: str, debug: bool = False) -> pathlib.Path:
523
+ """Get the absolute path to the agent template directory."""
524
+ current_dir = pathlib.Path(__file__).parent.parent.parent
525
+ template_path = current_dir / "agents" / agent_name / ".template"
526
+ if debug:
527
+ logging.debug(f"Looking for template in: {template_path}")
528
+ logging.debug(f"Template exists: {template_path.exists()}")
529
+ if template_path.exists():
530
+ logging.debug(f"Template contents: {list(template_path.iterdir())}")
531
+
532
+ if not template_path.exists():
533
+ raise ValueError(f"Template directory not found at {template_path}")
534
+
535
+ return template_path
536
+
537
+
538
+ def copy_data_ingestion_files(
539
+ project_template: pathlib.Path, datastore_type: str
540
+ ) -> None:
541
+ """Copy data processing files to the project template for cookiecutter templating.
542
+
543
+ Args:
544
+ project_template: Path to the project template directory
545
+ datastore_type: Type of datastore to use for data ingestion
546
+ """
547
+ data_ingestion_src = pathlib.Path(__file__).parent.parent.parent / "data_ingestion"
548
+ data_ingestion_dst = project_template / "data_ingestion"
549
+
550
+ if data_ingestion_src.exists():
551
+ logging.debug(
552
+ f"Copying data processing files from {data_ingestion_src} to {data_ingestion_dst}"
553
+ )
554
+
555
+ copy_files(data_ingestion_src, data_ingestion_dst, overwrite=True)
556
+
557
+ logging.debug(f"Data ingestion files prepared for datastore: {datastore_type}")
558
+ else:
559
+ logging.warning(
560
+ f"Data processing source directory not found at {data_ingestion_src}"
561
+ )
562
+
563
+
564
+ def _extract_agent_garden_labels(
565
+ agent_garden: bool,
566
+ remote_spec: Any | None,
567
+ remote_template_path: pathlib.Path | None,
568
+ ) -> tuple[str | None, str | None]:
569
+ """Extract agent sample ID and publisher for Agent Garden labeling.
570
+
571
+ This function supports two mechanisms for extracting label information:
572
+ 1. From remote_spec metadata (for ADK samples)
573
+ 2. Fallback to pyproject.toml parsing (for version-locked templates)
574
+
575
+ Args:
576
+ agent_garden: Whether this deployment is from Agent Garden
577
+ remote_spec: Remote template spec with ADK samples metadata
578
+ remote_template_path: Path to remote template directory
579
+
580
+ Returns:
581
+ Tuple of (agent_sample_id, agent_sample_publisher) or (None, None) if no labels found
582
+ """
583
+ if not agent_garden:
584
+ return None, None
585
+
586
+ agent_sample_id = None
587
+ agent_sample_publisher = None
588
+
589
+ # Handle remote specs with ADK samples metadata
590
+ if (
591
+ remote_spec
592
+ and hasattr(remote_spec, "is_adk_samples")
593
+ and remote_spec.is_adk_samples
594
+ ):
595
+ # For ADK samples, template_path is like "python/agents/sample-name"
596
+ agent_sample_id = pathlib.Path(remote_spec.template_path).name
597
+ # For ADK samples, publisher is always "google"
598
+ agent_sample_publisher = "google"
599
+ logging.debug(f"Detected ADK sample from remote_spec: {agent_sample_id}")
600
+ return agent_sample_id, agent_sample_publisher
601
+
602
+ # Fallback: Detect ADK samples from pyproject.toml (for version-locked templates)
603
+ if remote_template_path:
604
+ pyproject_path = remote_template_path / "pyproject.toml"
605
+ if pyproject_path.exists():
606
+ try:
607
+ if sys.version_info >= (3, 11):
608
+ import tomllib
609
+ else:
610
+ import tomli as tomllib
611
+
612
+ with open(pyproject_path, "rb") as toml_file:
613
+ pyproject_data = tomllib.load(toml_file)
614
+
615
+ # Extract project name from pyproject.toml
616
+ project_name_from_toml = pyproject_data.get("project", {}).get("name")
617
+
618
+ if project_name_from_toml:
619
+ agent_sample_id = project_name_from_toml
620
+ agent_sample_publisher = "google" # ADK samples are from Google
621
+ logging.debug(
622
+ f"Detected ADK sample from pyproject.toml: {agent_sample_id}"
623
+ )
624
+ except Exception as e:
625
+ logging.debug(f"Failed to read pyproject.toml: {e}")
626
+
627
+ return agent_sample_id, agent_sample_publisher
628
+
629
+
630
+ def _inject_app_object_if_missing(
631
+ agent_py_path: pathlib.Path, agent_directory: str, console: Console
632
+ ) -> None:
633
+ """Inject app object into agent.py if missing (backward compatibility for ADK).
634
+
635
+ Args:
636
+ agent_py_path: Path to the agent.py file
637
+ agent_directory: Name of the agent directory for logging
638
+ console: Rich console for user feedback
639
+ """
640
+ try:
641
+ content = agent_py_path.read_text(encoding="utf-8")
642
+ # Check for app object (assignment, function definition, or import)
643
+ app_patterns = [
644
+ r"^\s*app\s*=", # assignment: app = ...
645
+ r"^\s*def\s+app\(", # function: def app(...)
646
+ r"from\s+.*\s+import\s+.*\bapp\b", # import: from ... import app
647
+ ]
648
+ has_app = any(
649
+ re.search(pattern, content, re.MULTILINE) for pattern in app_patterns
650
+ )
651
+
652
+ if not has_app:
653
+ console.print(
654
+ f"ℹ️ Adding 'app' object to [cyan]{agent_directory}/agent.py[/cyan] for backward compatibility",
655
+ style="dim",
656
+ )
657
+ # Add import and app object at the end of the file
658
+ content = content.rstrip()
659
+ if "from google.adk.apps.app import App" not in content:
660
+ content += "\n\nfrom google.adk.apps.app import App\n"
661
+ content += '\napp = App(root_agent=root_agent, name="app")\n'
662
+
663
+ # Write the modified content back
664
+ agent_py_path.write_text(content, encoding="utf-8")
665
+ except Exception as e:
666
+ logging.warning(
667
+ f"Could not inject app object into {agent_directory}/agent.py: {type(e).__name__}: {e}"
668
+ )
669
+
670
+
671
+ def process_template(
672
+ agent_name: str,
673
+ template_dir: pathlib.Path,
674
+ project_name: str,
675
+ deployment_target: str | None = None,
676
+ cicd_runner: str | None = None,
677
+ include_data_ingestion: bool = False,
678
+ datastore: str | None = None,
679
+ session_type: str | None = None,
680
+ output_dir: pathlib.Path | None = None,
681
+ remote_template_path: pathlib.Path | None = None,
682
+ remote_config: dict[str, Any] | None = None,
683
+ in_folder: bool = False,
684
+ cli_overrides: dict[str, Any] | None = None,
685
+ agent_garden: bool = False,
686
+ remote_spec: Any | None = None,
687
+ ) -> None:
688
+ """Process the template directory and create a new project.
689
+
690
+ Args:
691
+ agent_name: Name of the agent template to use
692
+ template_dir: Directory containing the template files
693
+ project_name: Name of the project to create
694
+ deployment_target: Optional deployment target (agent_engine or cloud_run)
695
+ cicd_runner: Optional CI/CD runner to use
696
+ include_data_ingestion: Whether to include data pipeline components
697
+ datastore: Optional datastore type for data ingestion
698
+ session_type: Optional session type for cloud_run deployment
699
+ output_dir: Optional output directory path, defaults to current directory
700
+ remote_template_path: Optional path to remote template for overlay
701
+ remote_config: Optional remote template configuration
702
+ in_folder: Whether to template directly into the output directory instead of creating a subdirectory
703
+ cli_overrides: Optional CLI override values that should take precedence over template config
704
+ agent_garden: Whether this deployment is from Agent Garden
705
+ """
706
+ logging.debug(f"Processing template from {template_dir}")
707
+ logging.debug(f"Project name: {project_name}")
708
+ logging.debug(f"Include pipeline: {datastore}")
709
+ logging.debug(f"Output directory: {output_dir}")
710
+
711
+ # Create console for user feedback
712
+ console = Console()
713
+
714
+ def get_agent_directory(
715
+ template_config: dict[str, Any], cli_overrides: dict[str, Any] | None = None
716
+ ) -> str:
717
+ """Get agent directory with CLI override support."""
718
+ agent_dir = None
719
+ if (
720
+ cli_overrides
721
+ and "settings" in cli_overrides
722
+ and "agent_directory" in cli_overrides["settings"]
723
+ ):
724
+ agent_dir = cli_overrides["settings"]["agent_directory"]
725
+ else:
726
+ agent_dir = template_config.get("settings", {}).get(
727
+ "agent_directory", "app"
728
+ )
729
+
730
+ # Validate agent directory is a valid Python identifier
731
+ validate_agent_directory_name(agent_dir)
732
+
733
+ return agent_dir
734
+
735
+ # Handle remote vs local templates
736
+ is_remote = remote_template_path is not None
737
+
738
+ if is_remote:
739
+ # For remote templates, determine the base template
740
+ base_template_name = get_base_template_name(remote_config or {})
741
+ agent_path = (
742
+ pathlib.Path(__file__).parent.parent.parent / "agents" / base_template_name
743
+ )
744
+ logging.debug(f"Remote template using base: {base_template_name}")
745
+ else:
746
+ # For local templates, use the existing logic
747
+ agent_path = template_dir.parent # Get parent of template dir
748
+
749
+ logging.debug(f"agent path: {agent_path}")
750
+ logging.debug(f"agent path exists: {agent_path.exists()}")
751
+ logging.debug(
752
+ f"agent path contents: {list(agent_path.iterdir()) if agent_path.exists() else 'N/A'}"
753
+ )
754
+
755
+ base_template_path = pathlib.Path(__file__).parent.parent.parent / "base_template"
756
+
757
+ # Use provided output_dir or current directory
758
+ destination_dir = output_dir if output_dir else pathlib.Path.cwd()
759
+
760
+ # Create output directory if it doesn't exist
761
+ if not destination_dir.exists():
762
+ destination_dir.mkdir(parents=True)
763
+
764
+ # Create a new temporary directory and use it as our working directory
765
+ with tempfile.TemporaryDirectory() as temp_dir:
766
+ temp_path = pathlib.Path(temp_dir)
767
+
768
+ # Important: Store the original working directory
769
+ original_dir = pathlib.Path.cwd()
770
+
771
+ try:
772
+ os.chdir(temp_path) # Change to temp directory
773
+
774
+ # Extract agent sample info for labeling when using agent garden with remote templates
775
+ agent_sample_id, agent_sample_publisher = _extract_agent_garden_labels(
776
+ agent_garden, remote_spec, remote_template_path
777
+ )
778
+
779
+ # Create the cookiecutter template structure
780
+ cookiecutter_template = temp_path / "template"
781
+ cookiecutter_template.mkdir(parents=True)
782
+ project_template = cookiecutter_template / "{{cookiecutter.project_name}}"
783
+ project_template.mkdir(parents=True)
784
+
785
+ # 1. First copy base template files
786
+ base_template_path = (
787
+ pathlib.Path(__file__).parent.parent.parent / "base_template"
788
+ )
789
+ # Get agent directory from config early for use in file copying
790
+ # Load config early to get agent_directory
791
+ if remote_config:
792
+ early_config = remote_config
793
+ else:
794
+ template_path = pathlib.Path(template_dir)
795
+ early_config = load_template_config(template_path)
796
+ agent_directory = get_agent_directory(early_config, cli_overrides)
797
+ copy_files(
798
+ base_template_path,
799
+ project_template,
800
+ agent_name,
801
+ overwrite=True,
802
+ agent_directory=agent_directory,
803
+ )
804
+ logging.debug(f"1. Copied base template from {base_template_path}")
805
+
806
+ # 2. Process deployment target if specified
807
+ if deployment_target and deployment_target in DEPLOYMENT_FOLDERS:
808
+ deployment_path = (
809
+ pathlib.Path(__file__).parent.parent.parent
810
+ / "deployment_targets"
811
+ / deployment_target
812
+ )
813
+ if deployment_path.exists():
814
+ copy_files(
815
+ deployment_path,
816
+ project_template,
817
+ agent_name=agent_name,
818
+ overwrite=True,
819
+ agent_directory=agent_directory,
820
+ )
821
+ logging.debug(
822
+ f"2. Processed deployment files for target: {deployment_target}"
823
+ )
824
+
825
+ # 3. Copy data ingestion files if needed
826
+ if include_data_ingestion and datastore:
827
+ logging.debug(
828
+ f"3. Including data processing files with datastore: {datastore}"
829
+ )
830
+ copy_data_ingestion_files(project_template, datastore)
831
+
832
+ # 4. Skip remote template files during cookiecutter processing
833
+ # Remote files will be copied after cookiecutter to avoid Jinja conflicts
834
+ if is_remote and remote_template_path:
835
+ logging.debug(
836
+ "4. Skipping remote template files during cookiecutter processing - will copy after templating"
837
+ )
838
+
839
+ # Load and validate template config first
840
+ if remote_config:
841
+ config = remote_config
842
+ else:
843
+ template_path = pathlib.Path(template_dir)
844
+ config = load_template_config(template_path)
845
+
846
+ if not config:
847
+ raise ValueError("Could not load template config")
848
+
849
+ # Validate deployment target
850
+ available_targets = config.get("settings", {}).get("deployment_targets", [])
851
+ if isinstance(available_targets, str):
852
+ available_targets = [available_targets]
853
+
854
+ if deployment_target and deployment_target not in available_targets:
855
+ raise ValueError(
856
+ f"Invalid deployment target '{deployment_target}'. Available targets: {available_targets}"
857
+ )
858
+
859
+ # Use the already loaded config
860
+ template_config = config
861
+
862
+ # Process frontend files (after config is properly loaded with CLI overrides)
863
+ frontend_type = template_config.get("settings", {}).get(
864
+ "frontend_type", DEFAULT_FRONTEND
865
+ )
866
+ copy_frontend_files(frontend_type, project_template)
867
+ logging.debug(f"5. Processed frontend files for type: {frontend_type}")
868
+
869
+ # 6. Copy agent-specific files to override base template (using final config)
870
+ if agent_path.exists():
871
+ agent_directory = get_agent_directory(template_config, cli_overrides)
872
+
873
+ # Get the template's default agent directory (usually "app")
874
+ template_agent_directory = template_config.get("settings", {}).get(
875
+ "agent_directory", "app"
876
+ )
877
+
878
+ # Copy agent directory (always from "app" to target directory)
879
+ source_agent_folder = agent_path / template_agent_directory
880
+ target_agent_folder = project_template / agent_directory
881
+ if source_agent_folder.exists():
882
+ logging.debug(
883
+ f"6. Copying agent folder {template_agent_directory} -> {agent_directory} with override"
884
+ )
885
+ copy_files(
886
+ source_agent_folder,
887
+ target_agent_folder,
888
+ agent_name,
889
+ overwrite=True,
890
+ agent_directory=agent_directory,
891
+ )
892
+
893
+ # Copy other folders (frontend, tests, notebooks)
894
+ other_folders = ["frontend", "tests", "notebooks"]
895
+ for folder in other_folders:
896
+ agent_folder = agent_path / folder
897
+ project_folder = project_template / folder
898
+ if agent_folder.exists():
899
+ logging.debug(f"6. Copying {folder} folder with override")
900
+ copy_files(
901
+ agent_folder,
902
+ project_folder,
903
+ agent_name,
904
+ overwrite=True,
905
+ agent_directory=agent_directory,
906
+ )
907
+
908
+ # Check if data processing should be included
909
+ if include_data_ingestion and datastore:
910
+ logging.debug(
911
+ f"Including data processing files with datastore: {datastore}"
912
+ )
913
+ copy_data_ingestion_files(project_template, datastore)
914
+
915
+ # Create cookiecutter.json in the template root
916
+ # Get settings from template config
917
+ settings = template_config.get("settings", {})
918
+ extra_deps = settings.get("extra_dependencies", [])
919
+ frontend_type = settings.get("frontend_type", DEFAULT_FRONTEND)
920
+ tags = settings.get("tags", ["None"])
921
+
922
+ # Load adk-cheatsheet.md and llm.txt for injection
923
+ adk_cheatsheet_path = (
924
+ pathlib.Path(__file__).parent.parent.parent
925
+ / "resources"
926
+ / "docs"
927
+ / "adk-cheatsheet.md"
928
+ )
929
+ with open(adk_cheatsheet_path, encoding="utf-8") as md_file:
930
+ adk_cheatsheet_content = md_file.read()
931
+
932
+ llm_txt_path = (
933
+ pathlib.Path(__file__).parent.parent.parent.parent / "llm.txt"
934
+ )
935
+ with open(llm_txt_path, encoding="utf-8") as txt_file:
936
+ llm_txt_content = txt_file.read()
937
+
938
+ cookiecutter_config = {
939
+ "project_name": project_name,
940
+ "agent_name": agent_name,
941
+ "package_version": get_current_version(),
942
+ "agent_description": template_config.get("description", ""),
943
+ "example_question": template_config.get("example_question", "").ljust(
944
+ 61
945
+ ),
946
+ "settings": settings,
947
+ "tags": tags,
948
+ "is_adk": "adk" in tags,
949
+ "is_adk_live": "adk_live" in tags,
950
+ "is_a2a": "a2a" in tags,
951
+ "deployment_target": deployment_target or "",
952
+ "cicd_runner": cicd_runner or "google_cloud_build",
953
+ "session_type": session_type or "",
954
+ "frontend_type": frontend_type,
955
+ "extra_dependencies": [extra_deps],
956
+ "data_ingestion": include_data_ingestion,
957
+ "datastore_type": datastore if datastore else "",
958
+ "agent_directory": get_agent_directory(template_config, cli_overrides),
959
+ "agent_garden": agent_garden,
960
+ "agent_sample_id": agent_sample_id or "",
961
+ "agent_sample_publisher": agent_sample_publisher or "",
962
+ "adk_cheatsheet": adk_cheatsheet_content,
963
+ "llm_txt": llm_txt_content,
964
+ "_copy_without_render": [
965
+ "*.ipynb", # Don't render notebooks
966
+ "*.json", # Don't render JSON files
967
+ "*.tsx", # Don't render TypeScript React files
968
+ "*.ts", # Don't render TypeScript files
969
+ "*.jsx", # Don't render JavaScript React files
970
+ "*.js", # Don't render JavaScript files
971
+ "*.css", # Don't render CSS files
972
+ "frontend/**/*", # Don't render frontend directory recursively
973
+ "notebooks/*", # Don't render notebooks directory
974
+ ".git/*", # Don't render git directory
975
+ "__pycache__/*", # Don't render cache
976
+ "**/__pycache__/*",
977
+ ".pytest_cache/*",
978
+ ".venv/*",
979
+ "*templates.py", # Don't render templates files
980
+ "Makefile", # Don't render Makefile - handled by render_and_merge_makefiles
981
+ # Don't render agent.py unless it's agentic_rag
982
+ f"{get_agent_directory(template_config, cli_overrides)}/agent.py"
983
+ if agent_name != "agentic_rag"
984
+ else "",
985
+ ],
986
+ }
987
+
988
+ with open(
989
+ cookiecutter_template / "cookiecutter.json", "w", encoding="utf-8"
990
+ ) as json_file:
991
+ json.dump(cookiecutter_config, json_file, indent=4)
992
+
993
+ logging.debug(f"Template structure created at {cookiecutter_template}")
994
+ logging.debug(
995
+ f"Directory contents: {list(cookiecutter_template.iterdir())}"
996
+ )
997
+
998
+ # Process the template
999
+ cookiecutter(
1000
+ str(cookiecutter_template),
1001
+ no_input=True,
1002
+ overwrite_if_exists=True,
1003
+ extra_context={
1004
+ "project_name": project_name,
1005
+ "agent_name": agent_name,
1006
+ },
1007
+ )
1008
+ logging.debug("Template processing completed successfully")
1009
+
1010
+ # Now overlay remote template files if present (after cookiecutter processing)
1011
+ if is_remote and remote_template_path:
1012
+ generated_project_dir = temp_path / project_name
1013
+ logging.debug(
1014
+ f"Copying remote template files from {remote_template_path} to {generated_project_dir}"
1015
+ )
1016
+
1017
+ # Preserve base template README and pyproject.toml files before overwriting
1018
+ preserve_files = ["README.md"]
1019
+
1020
+ # Only preserve pyproject.toml if the remote template doesn't have starter pack integration
1021
+ remote_pyproject = remote_template_path / "pyproject.toml"
1022
+ if remote_pyproject.exists():
1023
+ try:
1024
+ remote_pyproject_content = remote_pyproject.read_text()
1025
+ # Check for starter pack integration markers
1026
+ has_starter_pack_integration = (
1027
+ "[tool.agent-starter-pack]" in remote_pyproject_content
1028
+ )
1029
+ if not has_starter_pack_integration:
1030
+ preserve_files.append("pyproject.toml")
1031
+ logging.debug(
1032
+ "Remote pyproject.toml lacks starter pack integration - will preserve base template version"
1033
+ )
1034
+ else:
1035
+ logging.debug(
1036
+ "Remote pyproject.toml has starter pack integration - using remote version only"
1037
+ )
1038
+ except Exception as e:
1039
+ logging.warning(
1040
+ f"Could not read remote pyproject.toml: {e}. Will preserve base template version."
1041
+ )
1042
+ preserve_files.append("pyproject.toml")
1043
+ else:
1044
+ preserve_files.append("pyproject.toml")
1045
+
1046
+ for preserve_file in preserve_files:
1047
+ base_file = generated_project_dir / preserve_file
1048
+ remote_file = remote_template_path / preserve_file
1049
+
1050
+ if base_file.exists() and remote_file.exists():
1051
+ # Preserve the base template file with starter_pack prefix
1052
+ base_name = pathlib.Path(preserve_file).stem
1053
+ extension = pathlib.Path(preserve_file).suffix
1054
+ preserved_file = (
1055
+ generated_project_dir
1056
+ / f"starter_pack_{base_name}{extension}"
1057
+ )
1058
+ shutil.copy2(base_file, preserved_file)
1059
+ logging.debug(
1060
+ f"Preserved base template {preserve_file} as starter_pack_{base_name}{extension}"
1061
+ )
1062
+
1063
+ copy_files(
1064
+ remote_template_path,
1065
+ generated_project_dir,
1066
+ agent_name=agent_name,
1067
+ overwrite=True,
1068
+ agent_directory=agent_directory,
1069
+ )
1070
+ logging.debug("Remote template files copied successfully")
1071
+
1072
+ # Inject app object if missing (backward compatibility for ADK remote templates)
1073
+ # Only inject for ADK agents with agent_engine deployment
1074
+ is_adk = "adk" in tags
1075
+ agent_py_path = generated_project_dir / agent_directory / "agent.py"
1076
+ if (
1077
+ is_adk
1078
+ and agent_py_path.exists()
1079
+ and deployment_target == "agent_engine"
1080
+ ):
1081
+ _inject_app_object_if_missing(
1082
+ agent_py_path, agent_directory, console
1083
+ )
1084
+
1085
+ # Move the generated project to the final destination
1086
+ generated_project_dir = temp_path / project_name
1087
+
1088
+ if in_folder:
1089
+ # For in-folder mode, copy files directly to the destination directory
1090
+ final_destination = destination_dir
1091
+ logging.debug(
1092
+ f"In-folder mode: copying files from {generated_project_dir} to {final_destination}"
1093
+ )
1094
+
1095
+ if generated_project_dir.exists():
1096
+ # Copy all files from generated project to destination directory
1097
+ for item in generated_project_dir.iterdir():
1098
+ dest_item = final_destination / item.name
1099
+
1100
+ # Special handling for README files - always preserve existing README
1101
+ # Special handling for pyproject.toml files - only preserve for in-folder updates
1102
+ should_preserve_file = item.name.lower().startswith(
1103
+ "readme"
1104
+ ) or (item.name == "pyproject.toml" and in_folder)
1105
+ if (
1106
+ should_preserve_file
1107
+ and (final_destination / item.name).exists()
1108
+ ):
1109
+ # The existing file stays, use base template file with starter_pack prefix
1110
+ base_name = item.stem
1111
+ extension = item.suffix
1112
+ dest_item = (
1113
+ final_destination
1114
+ / f"starter_pack_{base_name}{extension}"
1115
+ )
1116
+
1117
+ # Try to use base template file instead of templated file
1118
+ base_file = base_template_path / item.name
1119
+ if base_file.exists():
1120
+ logging.debug(
1121
+ f"{item.name} conflict: preserving existing {item.name}, using base template {item.name} as starter_pack_{base_name}{extension}"
1122
+ )
1123
+ # Process the base template file through cookiecutter
1124
+ try:
1125
+ import tempfile as tmp_module
1126
+
1127
+ with (
1128
+ tmp_module.TemporaryDirectory() as temp_file_dir
1129
+ ):
1130
+ temp_file_path = pathlib.Path(temp_file_dir)
1131
+
1132
+ # Create a minimal cookiecutter structure for just the file
1133
+ file_template_dir = (
1134
+ temp_file_path / "file_template"
1135
+ )
1136
+ file_template_dir.mkdir()
1137
+ file_project_dir = (
1138
+ file_template_dir
1139
+ / "{{cookiecutter.project_name}}"
1140
+ )
1141
+ file_project_dir.mkdir()
1142
+
1143
+ # Copy base file to template structure
1144
+ shutil.copy2(
1145
+ base_file, file_project_dir / item.name
1146
+ )
1147
+
1148
+ # Create cookiecutter.json with same config as main template
1149
+ with open(
1150
+ file_template_dir / "cookiecutter.json",
1151
+ "w",
1152
+ encoding="utf-8",
1153
+ ) as config_file:
1154
+ json.dump(
1155
+ cookiecutter_config,
1156
+ config_file,
1157
+ indent=4,
1158
+ )
1159
+
1160
+ # Process the file template
1161
+ cookiecutter(
1162
+ str(file_template_dir),
1163
+ no_input=True,
1164
+ overwrite_if_exists=True,
1165
+ output_dir=str(temp_file_path),
1166
+ extra_context={
1167
+ "project_name": project_name,
1168
+ "agent_name": agent_name,
1169
+ },
1170
+ )
1171
+
1172
+ # Copy the processed file
1173
+ processed_file = (
1174
+ temp_file_path / project_name / item.name
1175
+ )
1176
+ if processed_file.exists():
1177
+ shutil.copy2(processed_file, dest_item)
1178
+ else:
1179
+ # Fallback to original behavior if processing fails
1180
+ shutil.copy2(item, dest_item)
1181
+
1182
+ except Exception as e:
1183
+ logging.warning(
1184
+ f"Failed to process base template {item.name}: {e}. Using templated {item.name} instead."
1185
+ )
1186
+ shutil.copy2(item, dest_item)
1187
+ else:
1188
+ # Fallback to original behavior if base file doesn't exist
1189
+ logging.debug(
1190
+ f"{item.name} conflict: preserving existing {item.name}, saving templated {item.name} as starter_pack_{base_name}{extension}"
1191
+ )
1192
+ shutil.copy2(item, dest_item)
1193
+ else:
1194
+ # Normal file copying
1195
+ if item.is_dir():
1196
+ if dest_item.exists():
1197
+ shutil.rmtree(dest_item)
1198
+ shutil.copytree(item, dest_item, dirs_exist_ok=True)
1199
+ else:
1200
+ shutil.copy2(item, dest_item)
1201
+ logging.debug(
1202
+ f"Project files successfully copied to {final_destination}"
1203
+ )
1204
+ else:
1205
+ # Standard mode: create project subdirectory
1206
+ final_destination = destination_dir / project_name
1207
+ logging.debug(
1208
+ f"Standard mode: moving project from {generated_project_dir} to {final_destination}"
1209
+ )
1210
+
1211
+ if generated_project_dir.exists():
1212
+ # Check for existing README and pyproject.toml files before removing destination
1213
+ existing_preserved_files = []
1214
+ if final_destination.exists():
1215
+ for item in final_destination.iterdir():
1216
+ if item.is_file() and (
1217
+ item.name.lower().startswith("readme")
1218
+ or item.name == "pyproject.toml"
1219
+ ):
1220
+ existing_preserved_files.append(
1221
+ (item.name, item.read_text())
1222
+ )
1223
+ shutil.rmtree(final_destination)
1224
+
1225
+ shutil.copytree(
1226
+ generated_project_dir, final_destination, dirs_exist_ok=True
1227
+ )
1228
+
1229
+ # Restore existing README and pyproject.toml files with starter_pack prefix
1230
+ for file_name, file_content in existing_preserved_files:
1231
+ base_name = pathlib.Path(file_name).stem
1232
+ extension = pathlib.Path(file_name).suffix
1233
+ preserved_file_path = (
1234
+ final_destination / f"starter_pack_{base_name}{extension}"
1235
+ )
1236
+ preserved_file_path.write_text(file_content)
1237
+ logging.debug(
1238
+ f"File preservation: existing {file_name} preserved as starter_pack_{base_name}{extension}"
1239
+ )
1240
+
1241
+ logging.debug(
1242
+ f"Project successfully created at {final_destination}"
1243
+ )
1244
+
1245
+ # Always check if the project was successfully created before proceeding
1246
+ if not final_destination.exists():
1247
+ logging.error(
1248
+ f"Final destination directory not found at {final_destination}"
1249
+ )
1250
+ raise FileNotFoundError(
1251
+ f"Final destination directory not found at {final_destination}"
1252
+ )
1253
+
1254
+ # Render and merge Makefiles.
1255
+ # If it's a local template, remote_template_path will be None,
1256
+ # and only the base Makefile will be rendered.
1257
+ render_and_merge_makefiles(
1258
+ base_template_path=base_template_path,
1259
+ final_destination=final_destination,
1260
+ cookiecutter_config=cookiecutter_config,
1261
+ remote_template_path=remote_template_path,
1262
+ )
1263
+
1264
+ # Delete appropriate files based on ADK tag
1265
+ agent_directory = get_agent_directory(template_config, cli_overrides)
1266
+
1267
+ # Clean up unused_* files and directories created by conditional templates
1268
+ import glob
1269
+
1270
+ unused_patterns = [
1271
+ final_destination / "unused_*",
1272
+ final_destination / "**" / "unused_*",
1273
+ ]
1274
+
1275
+ for pattern in unused_patterns:
1276
+ for unused_path_str in glob.glob(str(pattern), recursive=True):
1277
+ unused_path = pathlib.Path(unused_path_str)
1278
+ if unused_path.exists():
1279
+ if unused_path.is_dir():
1280
+ shutil.rmtree(unused_path)
1281
+ logging.debug(f"Deleted unused directory: {unused_path}")
1282
+ else:
1283
+ unused_path.unlink()
1284
+ logging.debug(f"Deleted unused file: {unused_path}")
1285
+
1286
+ # Handle pyproject.toml and uv.lock files
1287
+ if is_remote and remote_template_path:
1288
+ # For remote templates, use their pyproject.toml and uv.lock if they exist
1289
+ remote_pyproject = remote_template_path / "pyproject.toml"
1290
+ remote_uv_lock = remote_template_path / "uv.lock"
1291
+
1292
+ if remote_pyproject.exists():
1293
+ shutil.copy2(remote_pyproject, final_destination / "pyproject.toml")
1294
+ logging.debug("Used pyproject.toml from remote template")
1295
+
1296
+ if remote_uv_lock.exists():
1297
+ shutil.copy2(remote_uv_lock, final_destination / "uv.lock")
1298
+ logging.debug("Used uv.lock from remote template")
1299
+ elif deployment_target:
1300
+ # For local templates, use the existing logic
1301
+ lock_path = (
1302
+ pathlib.Path(__file__).parent.parent.parent
1303
+ / "resources"
1304
+ / "locks"
1305
+ / f"uv-{agent_name}-{deployment_target}.lock"
1306
+ )
1307
+ logging.debug(f"Looking for lock file at: {lock_path}")
1308
+ logging.debug(f"Lock file exists: {lock_path.exists()}")
1309
+ if not lock_path.exists():
1310
+ raise FileNotFoundError(f"Lock file not found: {lock_path}")
1311
+ # Copy and rename to uv.lock in the project directory
1312
+ shutil.copy2(lock_path, final_destination / "uv.lock")
1313
+ logging.debug(
1314
+ f"Copied lock file from {lock_path} to {final_destination}/uv.lock"
1315
+ )
1316
+
1317
+ # Replace cookiecutter project name with actual project name in lock file
1318
+ lock_file_path = final_destination / "uv.lock"
1319
+ with open(lock_file_path, "r+", encoding="utf-8") as lock_file:
1320
+ content = lock_file.read()
1321
+ lock_file.seek(0)
1322
+ lock_file.write(
1323
+ content.replace("{{cookiecutter.project_name}}", project_name)
1324
+ )
1325
+ lock_file.truncate()
1326
+ logging.debug(f"Updated project name in lock file at {lock_file_path}")
1327
+
1328
+ except Exception as e:
1329
+ logging.error(f"Failed to process template: {e!s}")
1330
+ raise
1331
+
1332
+ finally:
1333
+ # Always restore the original working directory
1334
+ os.chdir(original_dir)
1335
+
1336
+
1337
+ def should_exclude_path(
1338
+ path: pathlib.Path, agent_name: str, agent_directory: str = "app"
1339
+ ) -> bool:
1340
+ """Determine if a path should be excluded based on the agent type."""
1341
+ if agent_name == "adk_live":
1342
+ # Exclude the unit test utils folder and agent utils folder for adk_live
1343
+ if "tests/unit/test_utils" in str(path) or f"{agent_directory}/utils" in str(
1344
+ path
1345
+ ):
1346
+ logging.debug(f"Excluding path for adk_live: {path}")
1347
+ return True
1348
+ return False
1349
+
1350
+
1351
+ def copy_files(
1352
+ src: pathlib.Path,
1353
+ dst: pathlib.Path,
1354
+ agent_name: str | None = None,
1355
+ overwrite: bool = False,
1356
+ agent_directory: str = "app",
1357
+ ) -> None:
1358
+ """
1359
+ Copy files with configurable behavior for exclusions and overwrites.
1360
+
1361
+ Args:
1362
+ src: Source path
1363
+ dst: Destination path
1364
+ agent_name: Name of the agent (for agent-specific exclusions)
1365
+ overwrite: Whether to overwrite existing files (True) or skip them (False)
1366
+ agent_directory: Name of the agent directory (for agent-specific exclusions)
1367
+ """
1368
+
1369
+ def should_skip(path: pathlib.Path) -> bool:
1370
+ """Determine if a file/directory should be skipped during copying."""
1371
+ if path.suffix in [".pyc"]:
1372
+ return True
1373
+ if "__pycache__" in str(path) or path.name == "__pycache__":
1374
+ return True
1375
+ if ".git" in path.parts:
1376
+ return True
1377
+ if agent_name is not None and should_exclude_path(
1378
+ path, agent_name, agent_directory
1379
+ ):
1380
+ return True
1381
+ if path.is_dir() and path.name == ".template":
1382
+ return True
1383
+ return False
1384
+
1385
+ if src.is_dir():
1386
+ if not dst.exists():
1387
+ dst.mkdir(parents=True)
1388
+ for item in src.iterdir():
1389
+ if should_skip(item):
1390
+ logging.debug(f"Skipping file/directory: {item}")
1391
+ continue
1392
+
1393
+ d = dst / item.name
1394
+ if item.is_dir():
1395
+ copy_files(item, d, agent_name, overwrite, agent_directory)
1396
+ else:
1397
+ if overwrite or not d.exists():
1398
+ logging.debug(f"Copying file: {item} -> {d}")
1399
+ shutil.copy2(item, d)
1400
+ else:
1401
+ logging.debug(f"Skipping existing file: {d}")
1402
+ else:
1403
+ if not should_skip(src):
1404
+ if overwrite or not dst.exists():
1405
+ shutil.copy2(src, dst)
1406
+
1407
+
1408
+ def copy_frontend_files(frontend_type: str, project_template: pathlib.Path) -> None:
1409
+ """Copy files from the specified frontend folder directly to project root."""
1410
+ # Skip copying if frontend_type is "None" or empty
1411
+ if not frontend_type or frontend_type == "None":
1412
+ logging.debug("Frontend type is 'None' or empty, skipping frontend files")
1413
+ return
1414
+
1415
+ # Skip copying if frontend_type is "inspector" - it's installed at runtime via make inspector
1416
+ if frontend_type == "inspector":
1417
+ logging.debug("Frontend type is 'inspector', skipping (installed at runtime)")
1418
+ return
1419
+
1420
+ # Get the frontends directory path
1421
+ frontends_path = (
1422
+ pathlib.Path(__file__).parent.parent.parent / "frontends" / frontend_type
1423
+ )
1424
+
1425
+ if frontends_path.exists():
1426
+ logging.debug(f"Copying frontend files from {frontends_path}")
1427
+ # Copy frontend files directly to project root instead of a nested frontend directory
1428
+ copy_files(frontends_path, project_template, overwrite=True)
1429
+ else:
1430
+ logging.warning(f"Frontend type directory not found: {frontends_path}")
1431
+ # Don't fall back to default if it's "None" - just skip
1432
+ if DEFAULT_FRONTEND != "None":
1433
+ logging.info(f"Falling back to default frontend: {DEFAULT_FRONTEND}")
1434
+ copy_frontend_files(DEFAULT_FRONTEND, project_template)
1435
+ else:
1436
+ logging.debug("No default frontend configured, skipping frontend files")
1437
+
1438
+
1439
+ def copy_deployment_files(
1440
+ deployment_target: str,
1441
+ agent_name: str,
1442
+ project_template: pathlib.Path,
1443
+ agent_directory: str = "app",
1444
+ ) -> None:
1445
+ """Copy files from the specified deployment target folder."""
1446
+ if not deployment_target:
1447
+ return
1448
+
1449
+ deployment_path = (
1450
+ pathlib.Path(__file__).parent.parent.parent
1451
+ / "deployment_targets"
1452
+ / deployment_target
1453
+ )
1454
+
1455
+ if deployment_path.exists():
1456
+ logging.debug(f"Copying deployment files from {deployment_path}")
1457
+ # Pass agent_name to respect agent-specific exclusions
1458
+ copy_files(
1459
+ deployment_path,
1460
+ project_template,
1461
+ agent_name=agent_name,
1462
+ overwrite=True,
1463
+ agent_directory=agent_directory,
1464
+ )
1465
+ else:
1466
+ logging.warning(f"Deployment target directory not found: {deployment_path}")