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.
- agent_starter_pack/agents/README.md +7 -0
- agents/langgraph_base_react/template/.templateconfig.yaml → agent_starter_pack/agents/adk_a2a_base/.template/templateconfig.yaml +5 -10
- agent_starter_pack/agents/adk_a2a_base/README.md +37 -0
- src/deployment_targets/cloud_run/Dockerfile → agent_starter_pack/agents/adk_a2a_base/app/__init__.py +2 -14
- agent_starter_pack/agents/adk_a2a_base/app/agent.py +70 -0
- agent_starter_pack/agents/adk_a2a_base/notebooks/adk_a2a_app_testing.ipynb +583 -0
- agents/crewai_coding_crew/notebooks/evaluating_crewai_agent.ipynb → agent_starter_pack/agents/adk_a2a_base/notebooks/evaluating_adk_agent.ipynb +163 -199
- {agents/adk_base → agent_starter_pack/agents/adk_a2a_base}/tests/integration/test_agent.py +2 -2
- agents/adk_base/template/.templateconfig.yaml → agent_starter_pack/agents/adk_base/.template/templateconfig.yaml +4 -1
- {agents → agent_starter_pack/agents}/adk_base/README.md +1 -1
- agent_starter_pack/agents/adk_base/app/__init__.py +17 -0
- {agents → agent_starter_pack/agents}/adk_base/app/agent.py +5 -2
- {agents → agent_starter_pack/agents}/adk_base/notebooks/adk_app_testing.ipynb +128 -82
- agents/langgraph_base_react/notebooks/evaluating_langgraph_agent.ipynb → agent_starter_pack/agents/adk_base/notebooks/evaluating_adk_agent.ipynb +181 -207
- agent_starter_pack/agents/adk_base/tests/integration/test_agent.py +58 -0
- agents/crewai_coding_crew/template/.templateconfig.yaml → agent_starter_pack/agents/adk_live/.template/templateconfig.yaml +5 -9
- agent_starter_pack/agents/adk_live/README.md +32 -0
- agent_starter_pack/agents/adk_live/app/__init__.py +17 -0
- agent_starter_pack/agents/adk_live/app/agent.py +51 -0
- agent_starter_pack/agents/adk_live/tests/unit/test_dummy.py +38 -0
- agents/agentic_rag/template/.templateconfig.yaml → agent_starter_pack/agents/agentic_rag/.template/templateconfig.yaml +7 -3
- {agents → agent_starter_pack/agents}/agentic_rag/README.md +1 -1
- agent_starter_pack/agents/agentic_rag/app/__init__.py +17 -0
- {agents → agent_starter_pack/agents}/agentic_rag/app/agent.py +8 -4
- {agents → agent_starter_pack/agents}/agentic_rag/notebooks/adk_app_testing.ipynb +128 -82
- agent_starter_pack/agents/agentic_rag/notebooks/evaluating_adk_agent.ipynb +1535 -0
- {agents → agent_starter_pack/agents}/agentic_rag/tests/integration/test_agent.py +3 -3
- agent_starter_pack/agents/langgraph_base/.template/templateconfig.yaml +31 -0
- agent_starter_pack/agents/langgraph_base/README.md +30 -0
- agent_starter_pack/agents/langgraph_base/app/__init__.py +17 -0
- agent_starter_pack/agents/langgraph_base/app/agent.py +34 -0
- {agents/crewai_coding_crew → agent_starter_pack/agents/langgraph_base}/notebooks/evaluating_langgraph_agent.ipynb +30 -17
- {agents/langgraph_base_react → agent_starter_pack/agents/langgraph_base}/tests/integration/test_agent.py +2 -2
- {src → agent_starter_pack}/base_template/.gitignore +5 -3
- agent_starter_pack/base_template/GEMINI.md +5 -0
- agent_starter_pack/base_template/Makefile +339 -0
- agent_starter_pack/base_template/README.md +267 -0
- agent_starter_pack/base_template/deployment/README.md +11 -0
- {src → agent_starter_pack}/base_template/deployment/terraform/apis.tf +2 -2
- {src → agent_starter_pack}/base_template/deployment/terraform/dev/apis.tf +6 -1
- {src → agent_starter_pack}/base_template/deployment/terraform/dev/iam.tf +12 -11
- {src → agent_starter_pack}/base_template/deployment/terraform/dev/providers.tf +5 -1
- {src → agent_starter_pack}/base_template/deployment/terraform/dev/storage.tf +1 -1
- {src → agent_starter_pack}/base_template/deployment/terraform/dev/variables.tf +10 -10
- agent_starter_pack/base_template/deployment/terraform/dev/{% if cookiecutter.is_adk %}telemetry.tf{% else %}unused_telemetry.tf{% endif %} +193 -0
- agent_starter_pack/base_template/deployment/terraform/github.tf +337 -0
- {src → agent_starter_pack}/base_template/deployment/terraform/iam.tf +20 -41
- {src → agent_starter_pack}/base_template/deployment/terraform/locals.tf +10 -3
- {src/resources/setup_cicd → agent_starter_pack/base_template/deployment/terraform}/providers.tf +8 -1
- {src → agent_starter_pack}/base_template/deployment/terraform/service_accounts.tf +7 -8
- agent_starter_pack/base_template/deployment/terraform/sql/completions.sql +138 -0
- {src → agent_starter_pack}/base_template/deployment/terraform/storage.tf +7 -16
- {src → agent_starter_pack}/base_template/deployment/terraform/variables.tf +61 -28
- {src → agent_starter_pack}/base_template/deployment/terraform/vars/env.tfvars +6 -1
- agent_starter_pack/base_template/deployment/terraform/{% if cookiecutter.cicd_runner == 'github_actions' %}wif.tf{% else %}unused_wif.tf{% endif %} +43 -0
- 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
- agent_starter_pack/base_template/deployment/terraform/{% if cookiecutter.is_adk %}telemetry.tf{% else %}unused_telemetry.tf{% endif %} +206 -0
- {src → agent_starter_pack}/base_template/pyproject.toml +24 -37
- agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/deploy-to-prod.yaml +132 -0
- agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/pr_checks.yaml +65 -0
- agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/staging.yaml +259 -0
- 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
- 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
- agent_starter_pack/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/staging.yaml +322 -0
- agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils/telemetry.py +96 -0
- {src/base_template/app/utils → agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils}/typing.py +7 -9
- 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
- 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
- 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
- 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
- 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
- agent_starter_pack/cli/commands/create.py +1256 -0
- agent_starter_pack/cli/commands/enhance.py +611 -0
- agent_starter_pack/cli/commands/list.py +203 -0
- agent_starter_pack/cli/commands/register_gemini_enterprise.py +1070 -0
- agent_starter_pack/cli/commands/setup_cicd.py +862 -0
- {src → agent_starter_pack}/cli/main.py +10 -2
- {src → agent_starter_pack}/cli/utils/cicd.py +139 -48
- agent_starter_pack/cli/utils/gcp.py +263 -0
- agent_starter_pack/cli/utils/logging.py +103 -0
- agent_starter_pack/cli/utils/remote_template.py +677 -0
- agent_starter_pack/cli/utils/template.py +1466 -0
- {src → agent_starter_pack}/data_ingestion/data_ingestion_pipeline/components/process_data.py +1 -1
- {src → agent_starter_pack}/data_ingestion/data_ingestion_pipeline/submit_pipeline.py +20 -6
- {src → agent_starter_pack}/data_ingestion/pyproject.toml +1 -0
- {src → agent_starter_pack}/data_ingestion/uv.lock +602 -494
- agent_starter_pack/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +484 -0
- agent_starter_pack/deployment_targets/agent_engine/tests/load_test/README.md +84 -0
- agent_starter_pack/deployment_targets/agent_engine/tests/load_test/load_test.py +424 -0
- agent_starter_pack/deployment_targets/agent_engine/tests/{% if cookiecutter.is_a2a %}helpers.py{% else %}unused_helpers.py{% endif %} +138 -0
- agent_starter_pack/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/agent_engine_app.py +263 -0
- agent_starter_pack/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/app_utils/deploy.py +414 -0
- 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
- agent_starter_pack/deployment_targets/cloud_run/Dockerfile +51 -0
- agent_starter_pack/deployment_targets/cloud_run/deployment/terraform/dev/service.tf +243 -0
- agent_starter_pack/deployment_targets/cloud_run/deployment/terraform/service.tf +417 -0
- agent_starter_pack/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +705 -0
- agent_starter_pack/deployment_targets/cloud_run/tests/load_test/.results/.placeholder +321 -0
- agent_starter_pack/deployment_targets/cloud_run/tests/load_test/README.md +165 -0
- agent_starter_pack/deployment_targets/cloud_run/tests/load_test/load_test.py +329 -0
- agent_starter_pack/deployment_targets/cloud_run/{{cookiecutter.agent_directory}}/fast_api_app.py +556 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/package-lock.json +79 -1044
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/package.json +1 -9
- agent_starter_pack/frontends/adk_live_react/frontend/src/App.tsx +65 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/logger/Logger.tsx +8 -3
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/logger/logger.scss +26 -0
- agent_starter_pack/frontends/adk_live_react/frontend/src/components/side-panel/SidePanel.tsx +516 -0
- agent_starter_pack/frontends/adk_live_react/frontend/src/components/side-panel/side-panel.scss +563 -0
- agent_starter_pack/frontends/adk_live_react/frontend/src/components/transcription-preview/TranscriptionPreview.tsx +106 -0
- agent_starter_pack/frontends/adk_live_react/frontend/src/components/transcription-preview/transcription-preview.scss +150 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/hooks/use-live-api.ts +8 -2
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/multimodal-live-types.ts +40 -2
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/audio-recorder.ts +1 -1
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/audio-streamer.ts +1 -1
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/multimodal-live-client.ts +210 -24
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/utils.ts +27 -5
- agent_starter_pack/resources/docs/adk-cheatsheet.md +1628 -0
- agent_starter_pack/resources/locks/uv-adk_a2a_base-agent_engine.lock +4966 -0
- agent_starter_pack/resources/locks/uv-adk_a2a_base-cloud_run.lock +5011 -0
- agent_starter_pack/resources/locks/uv-adk_base-agent_engine.lock +4946 -0
- agent_starter_pack/resources/locks/uv-adk_base-cloud_run.lock +4991 -0
- agent_starter_pack/resources/locks/uv-adk_live-agent_engine.lock +4963 -0
- agent_starter_pack/resources/locks/uv-adk_live-cloud_run.lock +5006 -0
- agent_starter_pack/resources/locks/uv-agentic_rag-agent_engine.lock +5487 -0
- agent_starter_pack/resources/locks/uv-agentic_rag-cloud_run.lock +5532 -0
- agent_starter_pack/resources/locks/uv-langgraph_base-agent_engine.lock +5788 -0
- agent_starter_pack/resources/locks/uv-langgraph_base-cloud_run.lock +5811 -0
- {src → agent_starter_pack}/utils/generate_locks.py +15 -12
- {src → agent_starter_pack}/utils/lock_utils.py +4 -7
- {src → agent_starter_pack}/utils/watch_and_rebuild.py +2 -2
- agent_starter_pack-0.21.0.dist-info/METADATA +182 -0
- agent_starter_pack-0.21.0.dist-info/RECORD +171 -0
- agent_starter_pack-0.21.0.dist-info/entry_points.txt +2 -0
- llm.txt +362 -0
- agent_starter_pack-0.3.3.dist-info/METADATA +0 -164
- agent_starter_pack-0.3.3.dist-info/RECORD +0 -176
- agent_starter_pack-0.3.3.dist-info/entry_points.txt +0 -2
- agents/crewai_coding_crew/README.md +0 -34
- agents/crewai_coding_crew/app/agent.py +0 -86
- agents/crewai_coding_crew/app/crew/config/agents.yaml +0 -39
- agents/crewai_coding_crew/app/crew/config/tasks.yaml +0 -37
- agents/crewai_coding_crew/app/crew/crew.py +0 -71
- agents/crewai_coding_crew/tests/integration/test_agent.py +0 -47
- agents/langgraph_base_react/README.md +0 -9
- agents/langgraph_base_react/app/agent.py +0 -73
- agents/live_api/README.md +0 -37
- agents/live_api/app/agent.py +0 -78
- agents/live_api/app/server.py +0 -196
- agents/live_api/app/templates.py +0 -51
- agents/live_api/app/vector_store.py +0 -55
- agents/live_api/template/.templateconfig.yaml +0 -29
- agents/live_api/tests/integration/test_server_e2e.py +0 -254
- agents/live_api/tests/load_test/load_test.py +0 -40
- agents/live_api/tests/unit/test_server.py +0 -143
- src/base_template/Makefile +0 -72
- src/base_template/README.md +0 -208
- src/base_template/app/__init__.py +0 -3
- src/base_template/app/utils/tracing.py +0 -155
- src/base_template/deployment/README.md +0 -126
- src/base_template/deployment/cd/staging.yaml +0 -216
- src/base_template/deployment/terraform/dev/log_sinks.tf +0 -63
- src/base_template/deployment/terraform/log_sinks.tf +0 -70
- src/base_template/deployment/terraform/providers.tf +0 -37
- src/cli/commands/create.py +0 -664
- src/cli/commands/setup_cicd.py +0 -829
- src/cli/utils/gcp.py +0 -117
- src/cli/utils/logging.py +0 -51
- src/cli/utils/template.py +0 -737
- src/deployment_targets/agent_engine/app/agent_engine_app.py +0 -336
- src/deployment_targets/agent_engine/notebooks/intro_agent_engine.ipynb +0 -1025
- src/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +0 -183
- src/deployment_targets/agent_engine/tests/load_test/README.md +0 -42
- src/deployment_targets/agent_engine/tests/load_test/load_test.py +0 -107
- src/deployment_targets/cloud_run/app/server.py +0 -154
- src/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +0 -249
- src/deployment_targets/cloud_run/tests/load_test/.results/.placeholder +0 -0
- src/deployment_targets/cloud_run/tests/load_test/README.md +0 -83
- src/deployment_targets/cloud_run/tests/load_test/load_test.py +0 -118
- src/deployment_targets/cloud_run/uv.lock +0 -6952
- src/frontends/live_api_react/frontend/src/App.tsx +0 -205
- src/frontends/live_api_react/frontend/src/components/control-tray/ControlTray.tsx +0 -217
- src/frontends/live_api_react/frontend/src/components/control-tray/control-tray.scss +0 -201
- src/frontends/live_api_react/frontend/src/components/side-panel/SidePanel.tsx +0 -161
- src/frontends/live_api_react/frontend/src/components/side-panel/side-panel.scss +0 -285
- src/frontends/streamlit/frontend/side_bar.py +0 -214
- src/frontends/streamlit/frontend/streamlit_app.py +0 -265
- src/frontends/streamlit/frontend/style/app_markdown.py +0 -37
- src/frontends/streamlit/frontend/utils/chat_utils.py +0 -67
- src/frontends/streamlit/frontend/utils/local_chat_history.py +0 -125
- src/frontends/streamlit/frontend/utils/message_editing.py +0 -59
- src/frontends/streamlit/frontend/utils/multimodal_utils.py +0 -217
- src/frontends/streamlit/frontend/utils/stream_handler.py +0 -301
- src/frontends/streamlit/frontend/utils/title_summary.py +0 -94
- src/frontends/streamlit_adk/frontend/side_bar.py +0 -214
- src/frontends/streamlit_adk/frontend/streamlit_app.py +0 -314
- src/frontends/streamlit_adk/frontend/style/app_markdown.py +0 -37
- src/frontends/streamlit_adk/frontend/utils/chat_utils.py +0 -84
- src/frontends/streamlit_adk/frontend/utils/local_chat_history.py +0 -110
- src/frontends/streamlit_adk/frontend/utils/message_editing.py +0 -61
- src/frontends/streamlit_adk/frontend/utils/multimodal_utils.py +0 -223
- src/frontends/streamlit_adk/frontend/utils/stream_handler.py +0 -311
- src/frontends/streamlit_adk/frontend/utils/title_summary.py +0 -129
- src/resources/containers/data_processing/Dockerfile +0 -27
- src/resources/containers/e2e-tests/Dockerfile +0 -19
- src/resources/idx/.idx/dev.nix +0 -57
- src/resources/idx/idx-template.json +0 -21
- src/resources/idx/idx-template.nix +0 -26
- src/resources/locks/uv-adk_base-agent_engine.lock +0 -5338
- src/resources/locks/uv-adk_base-cloud_run.lock +0 -5930
- src/resources/locks/uv-agentic_rag-agent_engine.lock +0 -5528
- src/resources/locks/uv-agentic_rag-cloud_run.lock +0 -6120
- src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +0 -6231
- src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +0 -6839
- src/resources/locks/uv-langgraph_base_react-agent_engine.lock +0 -5233
- src/resources/locks/uv-langgraph_base_react-cloud_run.lock +0 -5862
- src/resources/locks/uv-live_api-cloud_run.lock +0 -5832
- src/resources/setup_cicd/cicd_variables.tf +0 -41
- src/resources/setup_cicd/github.tf +0 -87
- {agents → agent_starter_pack/agents}/agentic_rag/app/retrievers.py +0 -0
- {agents → agent_starter_pack/agents}/agentic_rag/app/templates.py +0 -0
- {src → agent_starter_pack}/base_template/deployment/terraform/dev/vars/env.tfvars +0 -0
- {src → agent_starter_pack}/base_template/tests/unit/test_dummy.py +0 -0
- {src/deployment_targets/agent_engine/app/utils → agent_starter_pack/base_template/{{cookiecutter.agent_directory}}/app_utils}/gcs.py +0 -0
- {src → agent_starter_pack}/cli/utils/__init__.py +0 -0
- {src → agent_starter_pack}/cli/utils/datastores.py +0 -0
- {src → agent_starter_pack}/cli/utils/version.py +0 -0
- {src → agent_starter_pack}/data_ingestion/README.md +0 -0
- {src → agent_starter_pack}/data_ingestion/data_ingestion_pipeline/components/ingest_data.py +0 -0
- {src → agent_starter_pack}/data_ingestion/data_ingestion_pipeline/pipeline.py +0 -0
- {src → agent_starter_pack}/deployment_targets/agent_engine/deployment_metadata.json +0 -0
- {src → agent_starter_pack}/deployment_targets/agent_engine/tests/load_test/.results/.placeholder +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/public/favicon.ico +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/public/index.html +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/public/robots.txt +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/App.scss +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/App.test.tsx +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/audio-pulse/AudioPulse.tsx +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/audio-pulse/audio-pulse.scss +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/logger/mock-logs.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/contexts/LiveAPIContext.tsx +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/hooks/use-media-stream-mux.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/hooks/use-screen-capture.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/hooks/use-webcam.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/index.css +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/index.tsx +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/react-app-env.d.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/reportWebVitals.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/setupTests.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/audioworklet-registry.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/store-logger.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/worklets/audio-processing.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/worklets/vol-meter.ts +0 -0
- {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/tsconfig.json +0 -0
- {agent_starter_pack-0.3.3.dist-info → agent_starter_pack-0.21.0.dist-info}/WHEEL +0 -0
- {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}")
|