agent-starter-pack 0.15.7__py3-none-any.whl → 0.17.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.

Potentially problematic release.


This version of agent-starter-pack might be problematic. Click here for more details.

Files changed (195) hide show
  1. {agents → agent_starter_pack/agents}/adk_base/.template/templateconfig.yaml +1 -1
  2. {agents/live_api → agent_starter_pack/agents/adk_live}/.template/templateconfig.yaml +5 -7
  3. agent_starter_pack/agents/adk_live/README.md +32 -0
  4. agent_starter_pack/agents/adk_live/app/agent.py +48 -0
  5. agent_starter_pack/agents/adk_live/tests/unit/test_dummy.py +38 -0
  6. {agents → agent_starter_pack/agents}/agentic_rag/.template/templateconfig.yaml +1 -1
  7. agent_starter_pack/agents/crewai_coding_crew/app/agent.py +47 -0
  8. agent_starter_pack/agents/langgraph_base_react/app/agent.py +34 -0
  9. {src → agent_starter_pack}/base_template/GEMINI.md +1 -1
  10. {src → agent_starter_pack}/base_template/Makefile +130 -61
  11. {src → agent_starter_pack}/base_template/README.md +6 -6
  12. {src → agent_starter_pack}/base_template/deployment/terraform/dev/apis.tf +1 -1
  13. {src → agent_starter_pack}/base_template/deployment/terraform/dev/log_sinks.tf +31 -25
  14. {src → agent_starter_pack}/base_template/deployment/terraform/dev/providers.tf +1 -1
  15. {src → agent_starter_pack}/base_template/deployment/terraform/dev/variables.tf +1 -1
  16. {src → agent_starter_pack}/base_template/deployment/terraform/github.tf +14 -0
  17. {src → agent_starter_pack}/base_template/deployment/terraform/locals.tf +1 -1
  18. {src → agent_starter_pack}/base_template/deployment/terraform/log_sinks.tf +37 -28
  19. {src → agent_starter_pack}/base_template/deployment/terraform/providers.tf +1 -1
  20. {src → agent_starter_pack}/base_template/deployment/terraform/variables.tf +1 -1
  21. {src → agent_starter_pack}/base_template/deployment/terraform/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}build_triggers.tf{% else %}unused_build_triggers.tf{% endif %} +4 -2
  22. {src → agent_starter_pack}/base_template/pyproject.toml +22 -21
  23. {src → agent_starter_pack}/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/deploy-to-prod.yaml +5 -5
  24. {src → agent_starter_pack}/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/pr_checks.yaml +3 -3
  25. {src → agent_starter_pack}/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/staging.yaml +74 -11
  26. {src → agent_starter_pack}/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/deploy-to-prod.yaml +6 -6
  27. {src → agent_starter_pack}/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/pr_checks.yaml +4 -4
  28. {src → agent_starter_pack}/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/staging.yaml +95 -13
  29. {src → agent_starter_pack}/base_template/{{cookiecutter.agent_directory}}/utils/tracing.py +1 -1
  30. {src → agent_starter_pack}/base_template/{{cookiecutter.agent_directory}}/utils/typing.py +4 -4
  31. {src → agent_starter_pack}/cli/commands/setup_cicd.py +1 -1
  32. {src → agent_starter_pack}/cli/main.py +2 -2
  33. {src → agent_starter_pack}/cli/utils/gcp.py +1 -1
  34. {src → agent_starter_pack}/cli/utils/remote_template.py +12 -9
  35. {src → agent_starter_pack}/cli/utils/template.py +19 -15
  36. agent_starter_pack/deployment_targets/agent_engine/deployment/terraform/{% if not cookiecutter.is_adk_live %}service.tf{% else %}unused_service.tf{% endif %} +82 -0
  37. agent_starter_pack/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +387 -0
  38. agent_starter_pack/deployment_targets/agent_engine/tests/load_test/README.md +84 -0
  39. agent_starter_pack/deployment_targets/agent_engine/tests/load_test/load_test.py +255 -0
  40. {src → agent_starter_pack}/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/agent_engine_app.py +40 -14
  41. {src → agent_starter_pack}/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/utils/deployment.py +13 -4
  42. agent_starter_pack/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/utils/{% if cookiecutter.is_adk_live %}expose_app.py{% else %}unused_expose_app.py{% endif %} +520 -0
  43. {src → agent_starter_pack}/deployment_targets/cloud_run/Dockerfile +3 -3
  44. {src → agent_starter_pack}/deployment_targets/cloud_run/deployment/terraform/dev/service.tf +4 -4
  45. {src → agent_starter_pack}/deployment_targets/cloud_run/deployment/terraform/service.tf +7 -7
  46. {src → agent_starter_pack}/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +207 -5
  47. {src → agent_starter_pack}/deployment_targets/cloud_run/tests/load_test/README.md +82 -0
  48. agent_starter_pack/deployment_targets/cloud_run/tests/load_test/load_test.py +249 -0
  49. {src → agent_starter_pack}/deployment_targets/cloud_run/{{cookiecutter.agent_directory}}/server.py +190 -146
  50. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/package-lock.json +39 -1007
  51. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/package.json +1 -9
  52. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/App.tsx +1 -1
  53. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/logger/Logger.tsx +8 -3
  54. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/logger/logger.scss +26 -0
  55. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/side-panel/SidePanel.tsx +11 -5
  56. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/side-panel/side-panel.scss +146 -115
  57. agent_starter_pack/frontends/adk_live_react/frontend/src/components/transcription-preview/TranscriptionPreview.tsx +106 -0
  58. agent_starter_pack/frontends/adk_live_react/frontend/src/components/transcription-preview/transcription-preview.scss +150 -0
  59. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/hooks/use-live-api.ts +8 -2
  60. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/multimodal-live-types.ts +38 -2
  61. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/audio-recorder.ts +1 -1
  62. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/audio-streamer.ts +1 -1
  63. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/multimodal-live-client.ts +204 -23
  64. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/utils.ts +27 -5
  65. {src → agent_starter_pack}/frontends/streamlit/frontend/utils/local_chat_history.py +2 -0
  66. {src → agent_starter_pack}/resources/docs/adk-cheatsheet.md +5 -5
  67. agent_starter_pack/resources/idx/.idx/dev.nix +64 -0
  68. agent_starter_pack/resources/idx/idx-template.json +6 -0
  69. {src → agent_starter_pack}/resources/idx/idx-template.nix +2 -3
  70. {src → agent_starter_pack}/resources/locks/uv-adk_base-agent_engine.lock +1079 -954
  71. {src → agent_starter_pack}/resources/locks/uv-adk_base-cloud_run.lock +1441 -1309
  72. agent_starter_pack/resources/locks/uv-adk_live-agent_engine.lock +4229 -0
  73. agent_starter_pack/resources/locks/uv-adk_live-cloud_run.lock +4822 -0
  74. {src → agent_starter_pack}/resources/locks/uv-agentic_rag-agent_engine.lock +1107 -997
  75. {src → agent_starter_pack}/resources/locks/uv-agentic_rag-cloud_run.lock +1485 -1368
  76. {src → agent_starter_pack}/resources/locks/uv-crewai_coding_crew-agent_engine.lock +1294 -1297
  77. {src → agent_starter_pack}/resources/locks/uv-crewai_coding_crew-cloud_run.lock +2028 -1807
  78. {src → agent_starter_pack}/resources/locks/uv-langgraph_base_react-agent_engine.lock +1176 -1197
  79. {src → agent_starter_pack}/resources/locks/uv-langgraph_base_react-cloud_run.lock +1947 -1679
  80. {src → agent_starter_pack}/utils/generate_locks.py +12 -7
  81. {src → agent_starter_pack}/utils/lock_utils.py +2 -2
  82. {src → agent_starter_pack}/utils/watch_and_rebuild.py +1 -1
  83. {agent_starter_pack-0.15.7.dist-info → agent_starter_pack-0.17.0.dist-info}/METADATA +17 -18
  84. agent_starter_pack-0.17.0.dist-info/RECORD +179 -0
  85. agent_starter_pack-0.17.0.dist-info/entry_points.txt +2 -0
  86. llm.txt +1 -1
  87. agent_starter_pack-0.15.7.dist-info/RECORD +0 -176
  88. agent_starter_pack-0.15.7.dist-info/entry_points.txt +0 -2
  89. agents/crewai_coding_crew/app/agent.py +0 -86
  90. agents/langgraph_base_react/app/agent.py +0 -73
  91. agents/live_api/README.md +0 -37
  92. agents/live_api/app/agent.py +0 -72
  93. agents/live_api/tests/integration/test_server_e2e.py +0 -260
  94. agents/live_api/tests/load_test/load_test.py +0 -40
  95. agents/live_api/tests/unit/test_server.py +0 -144
  96. src/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +0 -186
  97. src/deployment_targets/agent_engine/tests/load_test/README.md +0 -37
  98. src/deployment_targets/agent_engine/tests/load_test/load_test.py +0 -126
  99. src/deployment_targets/cloud_run/tests/load_test/load_test.py +0 -122
  100. src/resources/idx/.idx/dev.nix +0 -50
  101. src/resources/idx/idx-template.json +0 -21
  102. src/resources/locks/uv-live_api-cloud_run.lock +0 -6118
  103. {agents → agent_starter_pack/agents}/README.md +0 -0
  104. {agents → agent_starter_pack/agents}/adk_base/README.md +0 -0
  105. {agents → agent_starter_pack/agents}/adk_base/app/__init__.py +0 -0
  106. {agents → agent_starter_pack/agents}/adk_base/app/agent.py +0 -0
  107. {agents → agent_starter_pack/agents}/adk_base/notebooks/adk_app_testing.ipynb +0 -0
  108. {agents → agent_starter_pack/agents}/adk_base/notebooks/evaluating_adk_agent.ipynb +0 -0
  109. {agents → agent_starter_pack/agents}/adk_base/tests/integration/test_agent.py +0 -0
  110. {agents → agent_starter_pack/agents}/agentic_rag/README.md +0 -0
  111. {agents → agent_starter_pack/agents}/agentic_rag/app/__init__.py +0 -0
  112. {agents → agent_starter_pack/agents}/agentic_rag/app/agent.py +0 -0
  113. {agents → agent_starter_pack/agents}/agentic_rag/app/retrievers.py +0 -0
  114. {agents → agent_starter_pack/agents}/agentic_rag/app/templates.py +0 -0
  115. {agents → agent_starter_pack/agents}/agentic_rag/notebooks/adk_app_testing.ipynb +0 -0
  116. {agents → agent_starter_pack/agents}/agentic_rag/notebooks/evaluating_adk_agent.ipynb +0 -0
  117. {agents → agent_starter_pack/agents}/agentic_rag/tests/integration/test_agent.py +0 -0
  118. {agents → agent_starter_pack/agents}/crewai_coding_crew/.template/templateconfig.yaml +0 -0
  119. {agents → agent_starter_pack/agents}/crewai_coding_crew/README.md +0 -0
  120. {agents → agent_starter_pack/agents}/crewai_coding_crew/app/crew/config/agents.yaml +0 -0
  121. {agents → agent_starter_pack/agents}/crewai_coding_crew/app/crew/config/tasks.yaml +0 -0
  122. {agents → agent_starter_pack/agents}/crewai_coding_crew/app/crew/crew.py +0 -0
  123. {agents → agent_starter_pack/agents}/crewai_coding_crew/notebooks/evaluating_crewai_agent.ipynb +0 -0
  124. {agents → agent_starter_pack/agents}/crewai_coding_crew/notebooks/evaluating_langgraph_agent.ipynb +0 -0
  125. {agents → agent_starter_pack/agents}/crewai_coding_crew/tests/integration/test_agent.py +0 -0
  126. {agents → agent_starter_pack/agents}/langgraph_base_react/.template/templateconfig.yaml +0 -0
  127. {agents → agent_starter_pack/agents}/langgraph_base_react/README.md +0 -0
  128. {agents → agent_starter_pack/agents}/langgraph_base_react/notebooks/evaluating_langgraph_agent.ipynb +0 -0
  129. {agents → agent_starter_pack/agents}/langgraph_base_react/tests/integration/test_agent.py +0 -0
  130. {src → agent_starter_pack}/base_template/.gitignore +0 -0
  131. {src → agent_starter_pack}/base_template/deployment/README.md +0 -0
  132. {src → agent_starter_pack}/base_template/deployment/terraform/apis.tf +0 -0
  133. {src → agent_starter_pack}/base_template/deployment/terraform/dev/iam.tf +0 -0
  134. {src → agent_starter_pack}/base_template/deployment/terraform/dev/storage.tf +0 -0
  135. {src → agent_starter_pack}/base_template/deployment/terraform/dev/vars/env.tfvars +0 -0
  136. {src → agent_starter_pack}/base_template/deployment/terraform/iam.tf +0 -0
  137. {src → agent_starter_pack}/base_template/deployment/terraform/service_accounts.tf +0 -0
  138. {src → agent_starter_pack}/base_template/deployment/terraform/storage.tf +0 -0
  139. {src → agent_starter_pack}/base_template/deployment/terraform/vars/env.tfvars +0 -0
  140. {src → agent_starter_pack}/base_template/deployment/terraform/{% if cookiecutter.cicd_runner == 'github_actions' %}wif.tf{% else %}unused_wif.tf{% endif %} +0 -0
  141. {src → agent_starter_pack}/base_template/tests/unit/test_dummy.py +0 -0
  142. {src → agent_starter_pack}/base_template/{{cookiecutter.agent_directory}}/utils/gcs.py +0 -0
  143. {src → agent_starter_pack}/cli/commands/create.py +0 -0
  144. {src → agent_starter_pack}/cli/commands/enhance.py +0 -0
  145. {src → agent_starter_pack}/cli/commands/list.py +0 -0
  146. {src → agent_starter_pack}/cli/utils/__init__.py +0 -0
  147. {src → agent_starter_pack}/cli/utils/cicd.py +0 -0
  148. {src → agent_starter_pack}/cli/utils/datastores.py +0 -0
  149. {src → agent_starter_pack}/cli/utils/logging.py +0 -0
  150. {src → agent_starter_pack}/cli/utils/version.py +0 -0
  151. {src → agent_starter_pack}/data_ingestion/README.md +0 -0
  152. {src → agent_starter_pack}/data_ingestion/data_ingestion_pipeline/components/ingest_data.py +0 -0
  153. {src → agent_starter_pack}/data_ingestion/data_ingestion_pipeline/components/process_data.py +0 -0
  154. {src → agent_starter_pack}/data_ingestion/data_ingestion_pipeline/pipeline.py +0 -0
  155. {src → agent_starter_pack}/data_ingestion/data_ingestion_pipeline/submit_pipeline.py +0 -0
  156. {src → agent_starter_pack}/data_ingestion/pyproject.toml +0 -0
  157. {src → agent_starter_pack}/data_ingestion/uv.lock +0 -0
  158. {src → agent_starter_pack}/deployment_targets/agent_engine/deployment_metadata.json +0 -0
  159. {src → agent_starter_pack}/deployment_targets/agent_engine/notebooks/intro_agent_engine.ipynb +0 -0
  160. {src → agent_starter_pack}/deployment_targets/agent_engine/tests/load_test/.results/.placeholder +0 -0
  161. {src → agent_starter_pack}/deployment_targets/cloud_run/tests/load_test/.results/.placeholder +0 -0
  162. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/public/favicon.ico +0 -0
  163. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/public/index.html +0 -0
  164. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/public/robots.txt +0 -0
  165. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/App.scss +0 -0
  166. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/App.test.tsx +0 -0
  167. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/audio-pulse/AudioPulse.tsx +0 -0
  168. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/audio-pulse/audio-pulse.scss +0 -0
  169. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/components/logger/mock-logs.ts +0 -0
  170. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/contexts/LiveAPIContext.tsx +0 -0
  171. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/hooks/use-media-stream-mux.ts +0 -0
  172. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/hooks/use-screen-capture.ts +0 -0
  173. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/hooks/use-webcam.ts +0 -0
  174. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/index.css +0 -0
  175. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/index.tsx +0 -0
  176. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/react-app-env.d.ts +0 -0
  177. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/reportWebVitals.ts +0 -0
  178. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/setupTests.ts +0 -0
  179. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/audioworklet-registry.ts +0 -0
  180. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/store-logger.ts +0 -0
  181. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/worklets/audio-processing.ts +0 -0
  182. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/src/utils/worklets/vol-meter.ts +0 -0
  183. {src/frontends/live_api_react → agent_starter_pack/frontends/adk_live_react}/frontend/tsconfig.json +0 -0
  184. {src → agent_starter_pack}/frontends/streamlit/frontend/side_bar.py +0 -0
  185. {src → agent_starter_pack}/frontends/streamlit/frontend/streamlit_app.py +0 -0
  186. {src → agent_starter_pack}/frontends/streamlit/frontend/style/app_markdown.py +0 -0
  187. {src → agent_starter_pack}/frontends/streamlit/frontend/utils/chat_utils.py +0 -0
  188. {src → agent_starter_pack}/frontends/streamlit/frontend/utils/message_editing.py +0 -0
  189. {src → agent_starter_pack}/frontends/streamlit/frontend/utils/multimodal_utils.py +0 -0
  190. {src → agent_starter_pack}/frontends/streamlit/frontend/utils/stream_handler.py +0 -0
  191. {src → agent_starter_pack}/frontends/streamlit/frontend/utils/title_summary.py +0 -0
  192. {src → agent_starter_pack}/resources/containers/data_processing/Dockerfile +0 -0
  193. {src → agent_starter_pack}/resources/containers/e2e-tests/Dockerfile +0 -0
  194. {agent_starter_pack-0.15.7.dist-info → agent_starter_pack-0.17.0.dist-info}/WHEEL +0 -0
  195. {agent_starter_pack-0.15.7.dist-info → agent_starter_pack-0.17.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,520 @@
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 asyncio
16
+ import json
17
+ import logging
18
+ from collections.abc import Callable
19
+ from pathlib import Path
20
+ from typing import Any, Literal
21
+
22
+ import backoff
23
+ import google.auth
24
+ import vertexai
25
+ from fastapi import FastAPI, HTTPException, WebSocket
26
+ from fastapi.middleware.cors import CORSMiddleware
27
+ from fastapi.responses import FileResponse
28
+ from fastapi.staticfiles import StaticFiles
29
+ from google.cloud import logging as google_cloud_logging
30
+ from pydantic import BaseModel
31
+ from websockets.exceptions import ConnectionClosedError
32
+
33
+ app = FastAPI()
34
+ app.add_middleware(
35
+ CORSMiddleware,
36
+ allow_origins=["*"],
37
+ allow_methods=["*"],
38
+ allow_headers=["*"],
39
+ )
40
+
41
+ # Get the path to the frontend build directory
42
+ current_dir = Path(__file__).parent
43
+ frontend_build_dir = current_dir.parent.parent / "frontend" / "build"
44
+
45
+ # Mount static files if build directory exists
46
+ if frontend_build_dir.exists():
47
+ app.mount(
48
+ "/static",
49
+ StaticFiles(directory=str(frontend_build_dir / "static")),
50
+ name="static",
51
+ )
52
+ logging_client = google_cloud_logging.Client()
53
+ logger = logging_client.logger(__name__)
54
+ logging.basicConfig(level=logging.INFO)
55
+
56
+ # Initialize default configuration
57
+ app.state.config = {
58
+ "use_remote_agent": False,
59
+ "remote_agent_engine_id": None,
60
+ "project_id": None,
61
+ "location": "us-central1",
62
+ "local_agent_path": "..agent.root_agent",
63
+ "agent_engine_class_path": "..agent_engine_app.AgentEngineApp",
64
+ }
65
+
66
+
67
+ class WebSocketToQueueAdapter:
68
+ """Adapter to convert WebSocket messages to an asyncio Queue for the agent engine."""
69
+
70
+ def __init__(
71
+ self,
72
+ websocket: WebSocket,
73
+ agent_engine: Any = None,
74
+ remote_config: dict[str, Any] | None = None,
75
+ ):
76
+ """Initialize the adapter.
77
+
78
+ Args:
79
+ websocket: The client websocket connection
80
+ agent_engine: The agent engine instance with bidi_stream_query method (None if using remote)
81
+ remote_config: Remote agent engine configuration (project_id, location, remote_agent_engine_id)
82
+ """
83
+ self.websocket = websocket
84
+ self.agent_engine = agent_engine
85
+ self.remote_config = remote_config
86
+ self.input_queue: asyncio.Queue[dict] = asyncio.Queue()
87
+ self.first_message = True
88
+
89
+ def _transform_remote_agent_engine_response(self, response: dict) -> dict:
90
+ """Transform remote Agent Engine bidiStreamOutput to ADK Event format for frontend."""
91
+ # Check if this is a remote Agent Engine bidiStreamOutput
92
+ bidi_output = response.get("bidiStreamOutput")
93
+ if not bidi_output:
94
+ # Not a remote agent engine response, return as-is
95
+ return response
96
+
97
+ # Transform to ADK Event format that frontend already handles
98
+ # Just unwrap the bidiStreamOutput wrapper - the content is already in ADK Event format
99
+ return bidi_output
100
+
101
+ async def receive_from_client(self) -> None:
102
+ """Listen for messages from the client and put them in the queue."""
103
+ while True:
104
+ try:
105
+ # Use receive() instead of receive_json() to handle both text and binary data
106
+ message = await self.websocket.receive()
107
+
108
+ # Handle different message types
109
+ if "text" in message:
110
+ # Parse JSON text messages
111
+ data = json.loads(message["text"])
112
+
113
+ if isinstance(data, dict):
114
+ # Skip setup messages - they're for backend logging only, not valid LiveRequest format
115
+ if "setup" in data:
116
+ # Log setup information
117
+ logger.log_struct(
118
+ {**data["setup"], "type": "setup"}, severity="INFO"
119
+ )
120
+ logging.info(
121
+ "Received setup message (not forwarding to agent)"
122
+ )
123
+ continue
124
+
125
+ # Frontend handles message format for both modes
126
+ await self.input_queue.put(data)
127
+ else:
128
+ logging.warning(
129
+ f"Received unexpected JSON structure from client: {data}"
130
+ )
131
+
132
+ elif "bytes" in message:
133
+ # Handle binary data
134
+ # Convert binary to appropriate format for agent engine
135
+ await self.input_queue.put({"binary_data": message["bytes"]})
136
+
137
+ else:
138
+ logging.warning(
139
+ f"Received unexpected message type from client: {message}"
140
+ )
141
+
142
+ except ConnectionClosedError as e:
143
+ logging.warning(f"Client closed connection: {e}")
144
+ break
145
+ except json.JSONDecodeError as e:
146
+ logging.error(f"Error parsing JSON from client: {e}")
147
+ break
148
+ except Exception as e:
149
+ logging.error(f"Error receiving from client: {e!s}")
150
+ break
151
+
152
+ async def run_agent_engine(self) -> None:
153
+ """Run the agent engine with the input queue."""
154
+ try:
155
+ if self.agent_engine is not None:
156
+ # Local agent engine mode
157
+ # Give the agent engine a moment to initialize before sending setupComplete
158
+ await asyncio.sleep(1)
159
+
160
+ # Send setupComplete after initialization delay
161
+ setup_complete_response: dict = {"setupComplete": {}}
162
+ await self.websocket.send_json(setup_complete_response)
163
+
164
+ async for response in self.agent_engine.bidi_stream_query(
165
+ self.input_queue
166
+ ):
167
+ # Send responses from agent engine to the websocket client
168
+ if response is not None:
169
+ await self.websocket.send_json(response)
170
+
171
+ # Check for error responses
172
+ if isinstance(response, dict) and "error" in response:
173
+ logging.error(f"Agent engine error: {response['error']}")
174
+ break
175
+ else:
176
+ # Remote agent engine mode
177
+ # Don't send setupComplete until remote connection is established
178
+ assert self.remote_config is not None, (
179
+ "remote_config must be set for remote mode"
180
+ )
181
+ await self.run_remote_agent_engine(
182
+ project_id=self.remote_config["project_id"],
183
+ location=self.remote_config["location"],
184
+ remote_agent_engine_id=self.remote_config["remote_agent_engine_id"],
185
+ )
186
+ except Exception as e:
187
+ logging.error(f"Error in agent engine: {e}")
188
+ await self.websocket.send_json({"error": str(e)})
189
+
190
+ async def run_remote_agent_engine(
191
+ self, project_id: str, location: str, remote_agent_engine_id: str
192
+ ) -> None:
193
+ """Run the remote agent engine connection."""
194
+ client = vertexai.Client(
195
+ project=project_id,
196
+ location=location,
197
+ )
198
+
199
+ async with client.aio.live.agent_engines.connect(
200
+ agent_engine=remote_agent_engine_id,
201
+ config={"class_method": "bidi_stream_query"},
202
+ ) as session:
203
+ # Send setupComplete only after remote connection is established
204
+ logging.info("Remote agent engine connection established")
205
+ setup_complete_response: dict = {"setupComplete": {}}
206
+ await self.websocket.send_json(setup_complete_response)
207
+
208
+ # Create task to forward messages from queue to remote session
209
+ async def forward_to_remote() -> None:
210
+ while True:
211
+ try:
212
+ message = await self.input_queue.get()
213
+ await session.send(message)
214
+ except Exception as e:
215
+ logging.error(f"Error forwarding to remote: {e}")
216
+ break
217
+
218
+ # Create task to receive from remote and send to websocket
219
+ async def receive_from_remote() -> None:
220
+ while True:
221
+ try:
222
+ response = await session.receive()
223
+ if response is not None:
224
+ # Transform remote Agent Engine bidiStreamOutput format to frontend format
225
+ transformed = self._transform_remote_agent_engine_response(
226
+ response
227
+ )
228
+ if transformed:
229
+ await self.websocket.send_json(transformed)
230
+
231
+ # Check for error responses
232
+ if isinstance(response, dict) and "error" in response:
233
+ logging.error(
234
+ f"Remote agent engine error: {response['error']}"
235
+ )
236
+ break
237
+ except Exception as e:
238
+ logging.error(f"Error receiving from remote: {e}")
239
+ break
240
+
241
+ await asyncio.gather(
242
+ forward_to_remote(),
243
+ receive_from_remote(),
244
+ )
245
+
246
+
247
+ def _dynamic_import(path: str) -> Any:
248
+ """Dynamically import an object from a given path.
249
+
250
+ Args:
251
+ path: Python import path (e.g., '..agent.root_agent')
252
+
253
+ Returns:
254
+ The imported object
255
+ """
256
+ import importlib
257
+
258
+ module_path, object_name = path.rsplit(".", 1)
259
+ module = importlib.import_module(module_path, package=__package__)
260
+ return getattr(module, object_name)
261
+
262
+
263
+ def get_connect_and_run_callable(
264
+ websocket: WebSocket, config: dict[str, Any]
265
+ ) -> Callable:
266
+ """Create a callable that handles agent engine connection with retry logic.
267
+
268
+ Args:
269
+ websocket: The client websocket connection
270
+ config: Configuration dict with agent engine settings
271
+
272
+ Returns:
273
+ Callable: An async function that establishes and manages the agent engine connection
274
+ """
275
+
276
+ async def on_backoff(details: backoff._typing.Details) -> None:
277
+ await websocket.send_json(
278
+ {
279
+ "status": f"Model connection error, retrying in {details['wait']} seconds..."
280
+ }
281
+ )
282
+
283
+ @backoff.on_exception(
284
+ backoff.expo, ConnectionClosedError, max_tries=10, on_backoff=on_backoff
285
+ )
286
+ async def connect_and_run() -> None:
287
+ if config["use_remote_agent"]:
288
+ # Remote agent engine mode
289
+ logging.info(
290
+ f"Connecting to remote agent engine: {config['remote_agent_engine_id']}"
291
+ )
292
+ remote_config = {
293
+ "project_id": config["project_id"],
294
+ "location": config["location"],
295
+ "remote_agent_engine_id": config["remote_agent_engine_id"],
296
+ }
297
+ adapter = WebSocketToQueueAdapter(
298
+ websocket, agent_engine=None, remote_config=remote_config
299
+ )
300
+ else:
301
+ # Local agent engine mode
302
+ # Dynamically import agent and engine class
303
+ agent_callable = _dynamic_import(config["local_agent_path"])
304
+ logging.info(
305
+ f"Starting local agent engine with callable: {agent_callable.__name__ if hasattr(agent_callable, '__name__') else agent_callable}"
306
+ )
307
+
308
+ AgentEngineApp = _dynamic_import(config["agent_engine_class_path"])
309
+ agent_engine = AgentEngineApp(agent=agent_callable)
310
+ adapter = WebSocketToQueueAdapter(websocket, agent_engine)
311
+
312
+ logging.info("Starting bidirectional communication with agent engine")
313
+ await asyncio.gather(
314
+ adapter.receive_from_client(),
315
+ adapter.run_agent_engine(),
316
+ )
317
+
318
+ return connect_and_run
319
+
320
+
321
+ @app.websocket("/ws")
322
+ async def websocket_endpoint(websocket: WebSocket) -> None:
323
+ """Handle new websocket connections."""
324
+ await websocket.accept()
325
+ connect_and_run = get_connect_and_run_callable(websocket, app.state.config)
326
+ await connect_and_run()
327
+
328
+
329
+ class Feedback(BaseModel):
330
+ """Represents feedback for a conversation."""
331
+
332
+ score: int | float
333
+ text: str | None = ""
334
+ run_id: str
335
+ user_id: str | None
336
+ log_type: Literal["feedback"] = "feedback"
337
+
338
+
339
+ @app.post("/feedback")
340
+ def collect_feedback(feedback: Feedback) -> dict[str, str]:
341
+ """Collect and log feedback.
342
+
343
+ Args:
344
+ feedback: The feedback data to log
345
+
346
+ Returns:
347
+ Success message
348
+ """
349
+ logger.log_struct(feedback.model_dump(), severity="INFO")
350
+ return {"status": "success"}
351
+
352
+
353
+ @app.get("/")
354
+ async def serve_frontend_root() -> FileResponse:
355
+ """Serve the frontend index.html at the root path."""
356
+ index_file = frontend_build_dir / "index.html"
357
+ if index_file.exists():
358
+ return FileResponse(str(index_file))
359
+ raise HTTPException(
360
+ status_code=404,
361
+ detail="Frontend not built. Run 'npm run build' in the frontend directory.",
362
+ )
363
+
364
+
365
+ @app.get("/{full_path:path}")
366
+ async def serve_frontend_spa(full_path: str) -> FileResponse:
367
+ """Catch-all route to serve the frontend for SPA routing.
368
+
369
+ This ensures that client-side routes are handled by the React app.
370
+ Excludes API routes (ws, feedback) and static assets.
371
+ """
372
+ # Don't intercept API routes
373
+ if full_path.startswith(("ws", "feedback", "static", "api")):
374
+ raise HTTPException(status_code=404, detail="Not found")
375
+
376
+ # Serve index.html for all other routes (SPA routing)
377
+ index_file = frontend_build_dir / "index.html"
378
+ if index_file.exists():
379
+ return FileResponse(str(index_file))
380
+ raise HTTPException(
381
+ status_code=404,
382
+ detail="Frontend not built. Run 'npm run build' in the frontend directory.",
383
+ )
384
+
385
+
386
+ # Main execution
387
+ if __name__ == "__main__":
388
+ import argparse
389
+
390
+ import uvicorn
391
+
392
+ parser = argparse.ArgumentParser(description="Agent Engine Proxy Server")
393
+ parser.add_argument(
394
+ "--mode",
395
+ choices=["local", "remote"],
396
+ default="local",
397
+ help="Agent engine mode: 'local' for local agent or 'remote' for deployed agent engine",
398
+ )
399
+ parser.add_argument(
400
+ "--remote-id",
401
+ type=str,
402
+ help="Remote agent engine ID (required when mode=remote)",
403
+ )
404
+ parser.add_argument(
405
+ "--project-id", type=str, help="GCP project ID (required when mode=remote)"
406
+ )
407
+ parser.add_argument(
408
+ "--location",
409
+ type=str,
410
+ default="us-central1",
411
+ help="GCP location (default: us-central1)",
412
+ )
413
+ parser.add_argument(
414
+ "--local-agent",
415
+ type=str,
416
+ default="..agent.root_agent",
417
+ help="Python path to local agent callable (e.g., 'app.agent.root_agent')",
418
+ )
419
+ parser.add_argument(
420
+ "--agent-engine-class",
421
+ type=str,
422
+ default="..agent_engine_app.AgentEngineApp",
423
+ help="Python path to agent engine class",
424
+ )
425
+ parser.add_argument(
426
+ "--port",
427
+ type=int,
428
+ default=8000,
429
+ help="Port to run the server on (default: 8000)",
430
+ )
431
+ parser.add_argument(
432
+ "--host",
433
+ type=str,
434
+ default="localhost",
435
+ help="Host to run the server on (default: localhost)",
436
+ )
437
+
438
+ args = parser.parse_args()
439
+
440
+ # Initialize configuration
441
+ config: dict[str, Any] = {
442
+ "use_remote_agent": False,
443
+ "remote_agent_engine_id": None,
444
+ "project_id": None,
445
+ "location": "us-central1",
446
+ "local_agent_path": args.local_agent,
447
+ "agent_engine_class_path": args.agent_engine_class,
448
+ }
449
+
450
+ if args.mode == "remote":
451
+ config["use_remote_agent"] = True
452
+
453
+ # Try to load from deployment_metadata.json if remote-id not provided
454
+ if not args.remote_id:
455
+ deployment_metadata_path = (
456
+ Path(__file__).parent.parent.parent / "deployment_metadata.json"
457
+ )
458
+ if deployment_metadata_path.exists():
459
+ with open(deployment_metadata_path) as f:
460
+ metadata = json.load(f)
461
+ config["remote_agent_engine_id"] = metadata.get(
462
+ "remote_agent_engine_id"
463
+ )
464
+ if not config["remote_agent_engine_id"]:
465
+ parser.error(
466
+ "No remote_agent_engine_id found in deployment_metadata.json"
467
+ )
468
+ print("Loaded remote agent engine ID from deployment_metadata.json")
469
+ else:
470
+ parser.error(
471
+ "--remote-id is required when deployment_metadata.json is not found"
472
+ )
473
+ else:
474
+ config["remote_agent_engine_id"] = args.remote_id
475
+
476
+ # Extract project ID from remote agent engine ID if not provided
477
+ if not args.project_id:
478
+ # Format: projects/PROJECT_ID/locations/LOCATION/reasoningEngines/ENGINE_ID
479
+ import re
480
+
481
+ match = re.match(
482
+ r"projects/([^/]+)/locations/([^/]+)/reasoningEngines/",
483
+ config["remote_agent_engine_id"],
484
+ )
485
+ if match:
486
+ config["project_id"] = match.group(1)
487
+ extracted_location = match.group(2)
488
+ config["location"] = (
489
+ args.location
490
+ if args.location != "us-central1"
491
+ else extracted_location
492
+ )
493
+ print("Extracted project ID and location from remote agent engine ID")
494
+ else:
495
+ # Fall back to google.auth.default()
496
+ try:
497
+ _, config["project_id"] = google.auth.default()
498
+ config["location"] = args.location
499
+ print(
500
+ f"Using default project ID from google.auth: {config['project_id']}"
501
+ )
502
+ except Exception as e:
503
+ parser.error(f"Could not determine project ID: {e}")
504
+ else:
505
+ config["project_id"] = args.project_id
506
+ config["location"] = args.location
507
+
508
+ print("Starting server in REMOTE mode:")
509
+ print(f" Remote Agent Engine ID: {config['remote_agent_engine_id']}")
510
+ print(f" Project ID: {config['project_id']}")
511
+ print(f" Location: {config['location']}")
512
+ else:
513
+ print("Starting server in LOCAL mode")
514
+ print(f" Using agent: {config['local_agent_path']}")
515
+ print(f" Using agent engine class: {config['agent_engine_class_path']}")
516
+
517
+ # Store configuration in app state
518
+ app.state.config = config
519
+
520
+ uvicorn.run(app, host=args.host, port=args.port)
@@ -14,9 +14,9 @@
14
14
 
15
15
  FROM python:3.11-slim
16
16
 
17
- RUN pip install --no-cache-dir uv==0.6.12
17
+ RUN pip install --no-cache-dir uv==0.8.13
18
18
 
19
- {%- if cookiecutter.agent_name == 'live_api' %}
19
+ {%- if cookiecutter.is_adk_live %}
20
20
  # Install Node.js for frontend build
21
21
  RUN apt-get update && apt-get install -y \
22
22
  curl \
@@ -32,7 +32,7 @@ COPY ./pyproject.toml ./README.md ./uv.lock* ./
32
32
 
33
33
  COPY ./{{cookiecutter.agent_directory}} ./{{cookiecutter.agent_directory}}
34
34
 
35
- {%- if cookiecutter.agent_name == 'live_api' %}
35
+ {%- if cookiecutter.is_adk_live %}
36
36
  # Copy and build frontend
37
37
  COPY ./frontend ./frontend
38
38
  RUN cd frontend && npm ci && npm run build
@@ -18,7 +18,7 @@ data "google_project" "project" {
18
18
  }
19
19
 
20
20
 
21
- {%- if "adk" in cookiecutter.tags and cookiecutter.session_type == "alloydb" %}
21
+ {%- if cookiecutter.is_adk and cookiecutter.session_type == "alloydb" %}
22
22
 
23
23
  # VPC Network for AlloyDB
24
24
  resource "google_compute_network" "default" {
@@ -134,7 +134,7 @@ resource "google_cloud_run_v2_service" "app" {
134
134
  deletion_protection = false
135
135
  ingress = "INGRESS_TRAFFIC_ALL"
136
136
  labels = {
137
- {%- if "adk" in cookiecutter.tags %}
137
+ {%- if cookiecutter.is_adk %}
138
138
  "created-by" = "adk"
139
139
  {%- endif %}
140
140
  {%- if cookiecutter.agent_garden %}
@@ -186,7 +186,7 @@ resource "google_cloud_run_v2_service" "app" {
186
186
  {%- endif %}
187
187
  {%- endif %}
188
188
 
189
- {%- if "adk" in cookiecutter.tags and cookiecutter.session_type == "alloydb" %}
189
+ {%- if cookiecutter.is_adk and cookiecutter.session_type == "alloydb" %}
190
190
 
191
191
  env {
192
192
  name = "DB_HOST"
@@ -215,7 +215,7 @@ resource "google_cloud_run_v2_service" "app" {
215
215
 
216
216
  session_affinity = true
217
217
 
218
- {%- if "adk" in cookiecutter.tags and cookiecutter.session_type == "alloydb" %}
218
+ {%- if cookiecutter.is_adk and cookiecutter.session_type == "alloydb" %}
219
219
  # VPC access for AlloyDB connectivity
220
220
  vpc_access {
221
221
  network_interfaces {
@@ -19,7 +19,7 @@ data "google_project" "project" {
19
19
  project_id = local.deploy_project_ids[each.key]
20
20
  }
21
21
 
22
- {%- if "adk" in cookiecutter.tags and cookiecutter.session_type == "alloydb" %}
22
+ {%- if cookiecutter.is_adk and cookiecutter.session_type == "alloydb" %}
23
23
 
24
24
  # VPC Network for AlloyDB
25
25
  resource "google_compute_network" "default" {
@@ -155,7 +155,7 @@ resource "google_cloud_run_v2_service" "app_staging" {
155
155
  deletion_protection = false
156
156
  ingress = "INGRESS_TRAFFIC_ALL"
157
157
  labels = {
158
- {%- if "adk" in cookiecutter.tags %}
158
+ {%- if cookiecutter.is_adk %}
159
159
  "created-by" = "adk"
160
160
  {%- endif %}
161
161
  {%- if cookiecutter.agent_garden %}
@@ -209,7 +209,7 @@ resource "google_cloud_run_v2_service" "app_staging" {
209
209
  {%- endif %}
210
210
  {%- endif %}
211
211
 
212
- {%- if "adk" in cookiecutter.tags and cookiecutter.session_type == "alloydb" %}
212
+ {%- if cookiecutter.is_adk and cookiecutter.session_type == "alloydb" %}
213
213
 
214
214
  env {
215
215
  name = "DB_HOST"
@@ -238,7 +238,7 @@ resource "google_cloud_run_v2_service" "app_staging" {
238
238
 
239
239
  session_affinity = true
240
240
 
241
- {%- if "adk" in cookiecutter.tags and cookiecutter.session_type == "alloydb" %}
241
+ {%- if cookiecutter.is_adk and cookiecutter.session_type == "alloydb" %}
242
242
  # VPC access for AlloyDB connectivity
243
243
  vpc_access {
244
244
  network_interfaces {
@@ -273,7 +273,7 @@ resource "google_cloud_run_v2_service" "app_prod" {
273
273
  deletion_protection = false
274
274
  ingress = "INGRESS_TRAFFIC_ALL"
275
275
  labels = {
276
- {%- if "adk" in cookiecutter.tags %}
276
+ {%- if cookiecutter.is_adk %}
277
277
  "created-by" = "adk"
278
278
  {%- endif %}
279
279
  {%- if cookiecutter.agent_garden %}
@@ -327,7 +327,7 @@ resource "google_cloud_run_v2_service" "app_prod" {
327
327
  {%- endif %}
328
328
  {%- endif %}
329
329
 
330
- {%- if "adk" in cookiecutter.tags and cookiecutter.session_type == "alloydb" %}
330
+ {%- if cookiecutter.is_adk and cookiecutter.session_type == "alloydb" %}
331
331
 
332
332
  env {
333
333
  name = "DB_HOST"
@@ -356,7 +356,7 @@ resource "google_cloud_run_v2_service" "app_prod" {
356
356
 
357
357
  session_affinity = true
358
358
 
359
- {%- if "adk" in cookiecutter.tags and cookiecutter.session_type == "alloydb" %}
359
+ {%- if cookiecutter.is_adk and cookiecutter.session_type == "alloydb" %}
360
360
  # VPC access for AlloyDB connectivity
361
361
  vpc_access {
362
362
  network_interfaces {