agent-starter-pack 0.15.6__py3-none-any.whl → 0.16.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.
- {agent_starter_pack-0.15.6.dist-info → agent_starter_pack-0.16.0.dist-info}/METADATA +2 -2
- {agent_starter_pack-0.15.6.dist-info → agent_starter_pack-0.16.0.dist-info}/RECORD +97 -95
- agents/adk_base/.template/templateconfig.yaml +1 -1
- agents/{live_api → adk_live}/.template/templateconfig.yaml +5 -7
- agents/adk_live/README.md +31 -0
- agents/adk_live/app/agent.py +48 -0
- agents/adk_live/tests/unit/test_dummy.py +38 -0
- agents/agentic_rag/.template/templateconfig.yaml +1 -1
- agents/crewai_coding_crew/app/agent.py +18 -57
- agents/langgraph_base_react/app/agent.py +7 -46
- llm.txt +1 -1
- src/base_template/GEMINI.md +1 -1
- src/base_template/Makefile +130 -61
- src/base_template/README.md +6 -6
- src/base_template/deployment/terraform/dev/apis.tf +1 -1
- src/base_template/deployment/terraform/dev/variables.tf +1 -1
- src/base_template/deployment/terraform/locals.tf +1 -1
- src/base_template/deployment/terraform/variables.tf +1 -1
- src/base_template/pyproject.toml +22 -21
- src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/deploy-to-prod.yaml +2 -2
- src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/pr_checks.yaml +1 -1
- src/base_template/{% if cookiecutter.cicd_runner == 'github_actions' %}.github{% else %}unused_github{% endif %}/workflows/staging.yaml +71 -8
- src/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/deploy-to-prod.yaml +2 -2
- src/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/pr_checks.yaml +1 -1
- src/base_template/{% if cookiecutter.cicd_runner == 'google_cloud_build' %}.cloudbuild{% else %}unused_.cloudbuild{% endif %}/staging.yaml +90 -8
- src/base_template/{{cookiecutter.agent_directory}}/utils/tracing.py +1 -1
- src/base_template/{{cookiecutter.agent_directory}}/utils/typing.py +4 -4
- src/cli/commands/create.py +1 -1
- src/cli/utils/template.py +12 -5
- src/deployment_targets/agent_engine/tests/integration/test_agent_engine_app.py +205 -4
- src/deployment_targets/agent_engine/tests/load_test/README.md +47 -0
- src/deployment_targets/agent_engine/tests/load_test/load_test.py +132 -3
- src/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/agent_engine_app.py +11 -3
- src/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/utils/deployment.py +5 -1
- src/deployment_targets/agent_engine/{{cookiecutter.agent_directory}}/utils/{% if cookiecutter.is_adk_live %}expose_app.py{% else %}unused_expose_app.py{% endif %} +461 -0
- src/deployment_targets/cloud_run/Dockerfile +3 -3
- src/deployment_targets/cloud_run/deployment/terraform/dev/service.tf +4 -4
- src/deployment_targets/cloud_run/deployment/terraform/service.tf +7 -7
- src/deployment_targets/cloud_run/tests/integration/test_server_e2e.py +207 -5
- src/deployment_targets/cloud_run/tests/load_test/README.md +82 -0
- src/deployment_targets/cloud_run/tests/load_test/load_test.py +130 -3
- src/deployment_targets/cloud_run/{{cookiecutter.agent_directory}}/server.py +178 -146
- src/frontends/{live_api_react → adk_live_react}/frontend/package-lock.json +39 -1007
- src/frontends/{live_api_react → adk_live_react}/frontend/package.json +1 -9
- src/frontends/{live_api_react → adk_live_react}/frontend/src/App.tsx +1 -1
- src/frontends/{live_api_react → adk_live_react}/frontend/src/components/logger/Logger.tsx +8 -3
- src/frontends/{live_api_react → adk_live_react}/frontend/src/components/logger/logger.scss +26 -0
- src/frontends/{live_api_react → adk_live_react}/frontend/src/components/side-panel/SidePanel.tsx +11 -5
- src/frontends/{live_api_react → adk_live_react}/frontend/src/components/side-panel/side-panel.scss +146 -115
- src/frontends/adk_live_react/frontend/src/components/transcription-preview/TranscriptionPreview.tsx +106 -0
- src/frontends/adk_live_react/frontend/src/components/transcription-preview/transcription-preview.scss +150 -0
- src/frontends/{live_api_react → adk_live_react}/frontend/src/hooks/use-live-api.ts +8 -2
- src/frontends/{live_api_react → adk_live_react}/frontend/src/multimodal-live-types.ts +38 -2
- src/frontends/{live_api_react → adk_live_react}/frontend/src/utils/audio-recorder.ts +1 -1
- src/frontends/{live_api_react → adk_live_react}/frontend/src/utils/audio-streamer.ts +1 -1
- src/frontends/{live_api_react → adk_live_react}/frontend/src/utils/multimodal-live-client.ts +204 -23
- src/frontends/{live_api_react → adk_live_react}/frontend/src/utils/utils.ts +27 -5
- src/frontends/streamlit/frontend/utils/local_chat_history.py +2 -0
- src/resources/idx/.idx/dev.nix +25 -11
- src/resources/idx/idx-template.json +1 -16
- src/resources/idx/idx-template.nix +2 -3
- src/resources/locks/uv-adk_base-agent_engine.lock +434 -349
- src/resources/locks/uv-adk_base-cloud_run.lock +502 -409
- src/resources/locks/uv-adk_live-agent_engine.lock +4189 -0
- src/resources/locks/{uv-live_api-cloud_run.lock → uv-adk_live-cloud_run.lock} +884 -2219
- src/resources/locks/uv-agentic_rag-agent_engine.lock +473 -388
- src/resources/locks/uv-agentic_rag-cloud_run.lock +557 -464
- src/resources/locks/uv-crewai_coding_crew-agent_engine.lock +498 -515
- src/resources/locks/uv-crewai_coding_crew-cloud_run.lock +898 -687
- src/resources/locks/uv-langgraph_base_react-agent_engine.lock +455 -483
- src/resources/locks/uv-langgraph_base_react-cloud_run.lock +910 -645
- src/utils/generate_locks.py +8 -4
- agents/live_api/README.md +0 -37
- agents/live_api/app/agent.py +0 -72
- agents/live_api/tests/integration/test_server_e2e.py +0 -260
- agents/live_api/tests/load_test/load_test.py +0 -40
- agents/live_api/tests/unit/test_server.py +0 -144
- {agent_starter_pack-0.15.6.dist-info → agent_starter_pack-0.16.0.dist-info}/WHEEL +0 -0
- {agent_starter_pack-0.15.6.dist-info → agent_starter_pack-0.16.0.dist-info}/entry_points.txt +0 -0
- {agent_starter_pack-0.15.6.dist-info → agent_starter_pack-0.16.0.dist-info}/licenses/LICENSE +0 -0
- /src/frontends/{live_api_react → adk_live_react}/frontend/public/favicon.ico +0 -0
- /src/frontends/{live_api_react → adk_live_react}/frontend/public/index.html +0 -0
- /src/frontends/{live_api_react → adk_live_react}/frontend/public/robots.txt +0 -0
- /src/frontends/{live_api_react → adk_live_react}/frontend/src/App.scss +0 -0
- /src/frontends/{live_api_react → adk_live_react}/frontend/src/App.test.tsx +0 -0
- /src/frontends/{live_api_react → adk_live_react}/frontend/src/components/audio-pulse/AudioPulse.tsx +0 -0
- /src/frontends/{live_api_react → adk_live_react}/frontend/src/components/audio-pulse/audio-pulse.scss +0 -0
- /src/frontends/{live_api_react → adk_live_react}/frontend/src/components/logger/mock-logs.ts +0 -0
- /src/frontends/{live_api_react → adk_live_react}/frontend/src/contexts/LiveAPIContext.tsx +0 -0
- /src/frontends/{live_api_react → adk_live_react}/frontend/src/hooks/use-media-stream-mux.ts +0 -0
- /src/frontends/{live_api_react → adk_live_react}/frontend/src/hooks/use-screen-capture.ts +0 -0
- /src/frontends/{live_api_react → adk_live_react}/frontend/src/hooks/use-webcam.ts +0 -0
- /src/frontends/{live_api_react → adk_live_react}/frontend/src/index.css +0 -0
- /src/frontends/{live_api_react → adk_live_react}/frontend/src/index.tsx +0 -0
- /src/frontends/{live_api_react → adk_live_react}/frontend/src/react-app-env.d.ts +0 -0
- /src/frontends/{live_api_react → adk_live_react}/frontend/src/reportWebVitals.ts +0 -0
- /src/frontends/{live_api_react → adk_live_react}/frontend/src/setupTests.ts +0 -0
- /src/frontends/{live_api_react → adk_live_react}/frontend/src/utils/audioworklet-registry.ts +0 -0
- /src/frontends/{live_api_react → adk_live_react}/frontend/src/utils/store-logger.ts +0 -0
- /src/frontends/{live_api_react → adk_live_react}/frontend/src/utils/worklets/audio-processing.ts +0 -0
- /src/frontends/{live_api_react → adk_live_react}/frontend/src/utils/worklets/vol-meter.ts +0 -0
- /src/frontends/{live_api_react → adk_live_react}/frontend/tsconfig.json +0 -0
|
@@ -11,26 +11,29 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
-
{% if cookiecutter.agent_name == "
|
|
14
|
+
{% if cookiecutter.agent_name == "adk_live" %}
|
|
15
15
|
import asyncio
|
|
16
16
|
import json
|
|
17
17
|
import logging
|
|
18
18
|
from collections.abc import Callable
|
|
19
19
|
from pathlib import Path
|
|
20
|
-
from typing import Any, Literal
|
|
21
20
|
|
|
22
21
|
import backoff
|
|
23
22
|
from fastapi import FastAPI, HTTPException, WebSocket
|
|
24
23
|
from fastapi.middleware.cors import CORSMiddleware
|
|
25
24
|
from fastapi.responses import FileResponse
|
|
26
25
|
from fastapi.staticfiles import StaticFiles
|
|
26
|
+
from google.adk.agents.live_request_queue import LiveRequest, LiveRequestQueue
|
|
27
|
+
from google.adk.artifacts.in_memory_artifact_service import InMemoryArtifactService
|
|
28
|
+
from google.adk.memory.in_memory_memory_service import InMemoryMemoryService
|
|
29
|
+
from google.adk.runners import Runner
|
|
30
|
+
from google.adk.sessions.in_memory_session_service import InMemorySessionService
|
|
27
31
|
from google.cloud import logging as google_cloud_logging
|
|
28
|
-
from
|
|
29
|
-
from google.genai.types import LiveServerToolCall
|
|
30
|
-
from pydantic import BaseModel
|
|
32
|
+
from vertexai.agent_engines import _utils
|
|
31
33
|
from websockets.exceptions import ConnectionClosedError
|
|
32
34
|
|
|
33
|
-
from .agent import
|
|
35
|
+
from .agent import root_agent
|
|
36
|
+
from .utils.typing import Feedback
|
|
34
37
|
|
|
35
38
|
app = FastAPI()
|
|
36
39
|
app.add_middleware(
|
|
@@ -56,117 +59,160 @@ logger = logging_client.logger(__name__)
|
|
|
56
59
|
logging.basicConfig(level=logging.INFO)
|
|
57
60
|
|
|
58
61
|
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
# Initialize ADK services
|
|
63
|
+
session_service = InMemorySessionService()
|
|
64
|
+
artifact_service = InMemoryArtifactService()
|
|
65
|
+
memory_service = InMemoryMemoryService()
|
|
61
66
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
# Initialize ADK runner
|
|
68
|
+
runner = Runner(
|
|
69
|
+
agent=root_agent,
|
|
70
|
+
session_service=session_service,
|
|
71
|
+
artifact_service=artifact_service,
|
|
72
|
+
memory_service=memory_service,
|
|
73
|
+
app_name="live-app",
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class AgentSession:
|
|
78
|
+
"""Manages bidirectional communication between a client and the agent."""
|
|
79
|
+
|
|
80
|
+
def __init__(self, websocket: WebSocket) -> None:
|
|
81
|
+
"""Initialize the agent session.
|
|
66
82
|
|
|
67
83
|
Args:
|
|
68
|
-
session: The Gemini session
|
|
69
84
|
websocket: The client websocket connection
|
|
70
|
-
user_id: Unique identifier for this client
|
|
71
|
-
tool_functions: Dictionary of available tool functions
|
|
72
85
|
"""
|
|
73
|
-
self.session = session
|
|
74
86
|
self.websocket = websocket
|
|
75
|
-
self.
|
|
76
|
-
self.user_id =
|
|
77
|
-
self.
|
|
78
|
-
self._tool_tasks: list[asyncio.Task] = []
|
|
87
|
+
self.input_queue: asyncio.Queue[dict] = asyncio.Queue()
|
|
88
|
+
self.user_id: str | None = None
|
|
89
|
+
self.session_id: str | None = None
|
|
79
90
|
|
|
80
91
|
async def receive_from_client(self) -> None:
|
|
81
|
-
"""Listen for and
|
|
82
|
-
|
|
83
|
-
Continuously receives messages and forwards audio data to Gemini.
|
|
84
|
-
Handles connection errors gracefully.
|
|
85
|
-
"""
|
|
92
|
+
"""Listen for messages from the client and put them in the queue."""
|
|
86
93
|
while True:
|
|
87
94
|
try:
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
95
|
+
message = await self.websocket.receive()
|
|
96
|
+
|
|
97
|
+
if "text" in message:
|
|
98
|
+
data = json.loads(message["text"])
|
|
99
|
+
|
|
100
|
+
if isinstance(data, dict):
|
|
101
|
+
# Skip setup messages - they're for backend logging only
|
|
102
|
+
if "setup" in data:
|
|
103
|
+
logger.log_struct(
|
|
104
|
+
{**data["setup"], "type": "setup"}, severity="INFO"
|
|
105
|
+
)
|
|
106
|
+
logging.info(
|
|
107
|
+
"Received setup message (not forwarding to agent)"
|
|
108
|
+
)
|
|
109
|
+
continue
|
|
110
|
+
|
|
111
|
+
# Forward message to agent engine
|
|
112
|
+
await self.input_queue.put(data)
|
|
113
|
+
else:
|
|
114
|
+
logging.warning(
|
|
115
|
+
f"Received unexpected JSON structure from client: {data}"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
elif "bytes" in message:
|
|
119
|
+
# Handle binary data
|
|
120
|
+
await self.input_queue.put({"binary_data": message["bytes"]})
|
|
121
|
+
|
|
100
122
|
else:
|
|
101
|
-
logging.warning(
|
|
123
|
+
logging.warning(
|
|
124
|
+
f"Received unexpected message type from client: {message}"
|
|
125
|
+
)
|
|
126
|
+
|
|
102
127
|
except ConnectionClosedError as e:
|
|
103
|
-
logging.warning(f"Client
|
|
128
|
+
logging.warning(f"Client closed connection: {e}")
|
|
129
|
+
break
|
|
130
|
+
except json.JSONDecodeError as e:
|
|
131
|
+
logging.error(f"Error parsing JSON from client: {e}")
|
|
104
132
|
break
|
|
105
133
|
except Exception as e:
|
|
106
|
-
logging.error(f"Error receiving from client
|
|
134
|
+
logging.error(f"Error receiving from client: {e!s}")
|
|
107
135
|
break
|
|
108
136
|
|
|
109
|
-
def
|
|
110
|
-
"""
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
# Handle both async and sync functions appropriately
|
|
132
|
-
if asyncio.iscoroutinefunction(func):
|
|
133
|
-
# Function is already async
|
|
134
|
-
response = await func(**args)
|
|
135
|
-
else:
|
|
136
|
-
# Run sync function in a thread pool to avoid blocking
|
|
137
|
-
response = await asyncio.to_thread(func, **args)
|
|
138
|
-
|
|
139
|
-
tool_response = types.LiveClientToolResponse(
|
|
140
|
-
function_responses=[
|
|
141
|
-
types.FunctionResponse(name=fc.name, id=fc.id, response=response)
|
|
142
|
-
]
|
|
143
|
-
)
|
|
144
|
-
logging.debug(f"Tool response: {tool_response}")
|
|
145
|
-
await session.send(input=tool_response)
|
|
146
|
-
|
|
147
|
-
async def receive_from_gemini(self) -> None:
|
|
148
|
-
"""Listen for and process messages from Gemini without blocking."""
|
|
149
|
-
while result := await self.session._ws.recv(decode=False):
|
|
150
|
-
await self.websocket.send_bytes(result)
|
|
151
|
-
raw_message = json.loads(result)
|
|
152
|
-
if "toolCall" in raw_message:
|
|
153
|
-
message = types.LiveServerMessage.model_validate(raw_message)
|
|
154
|
-
tool_call = LiveServerToolCall.model_validate(message.tool_call)
|
|
155
|
-
# Create a separate task to handle the tool call without blocking
|
|
156
|
-
task = asyncio.create_task(
|
|
157
|
-
self._handle_tool_call(self.session, tool_call)
|
|
137
|
+
async def run_agent(self) -> None:
|
|
138
|
+
"""Run the agent with the input queue using bidi_stream_query protocol."""
|
|
139
|
+
try:
|
|
140
|
+
# Send setupComplete immediately
|
|
141
|
+
setup_complete_response: dict = {"setupComplete": {}}
|
|
142
|
+
await self.websocket.send_json(setup_complete_response)
|
|
143
|
+
|
|
144
|
+
# Wait for first request with user_id
|
|
145
|
+
first_request = await self.input_queue.get()
|
|
146
|
+
self.user_id = first_request.get("user_id")
|
|
147
|
+
if not self.user_id:
|
|
148
|
+
raise ValueError("The first request must have a user_id.")
|
|
149
|
+
|
|
150
|
+
self.session_id = first_request.get("session_id")
|
|
151
|
+
first_live_request = first_request.get("live_request")
|
|
152
|
+
|
|
153
|
+
# Create session if needed
|
|
154
|
+
if not self.session_id:
|
|
155
|
+
session = await session_service.create_session(
|
|
156
|
+
app_name="live-app",
|
|
157
|
+
user_id=self.user_id,
|
|
158
158
|
)
|
|
159
|
-
self.
|
|
159
|
+
self.session_id = session.id
|
|
160
|
+
|
|
161
|
+
# Create LiveRequestQueue
|
|
162
|
+
live_request_queue = LiveRequestQueue()
|
|
163
|
+
|
|
164
|
+
# Add first live request if present
|
|
165
|
+
if first_live_request and isinstance(first_live_request, dict):
|
|
166
|
+
live_request_queue.send(LiveRequest.model_validate(first_live_request))
|
|
167
|
+
|
|
168
|
+
# Forward requests from input_queue to live_request_queue
|
|
169
|
+
async def _forward_requests() -> None:
|
|
170
|
+
while True:
|
|
171
|
+
request = await self.input_queue.get()
|
|
172
|
+
live_request = LiveRequest.model_validate(request)
|
|
173
|
+
live_request_queue.send(live_request)
|
|
174
|
+
|
|
175
|
+
# Forward events from agent to websocket
|
|
176
|
+
async def _forward_events() -> None:
|
|
177
|
+
events_async = runner.run_live(
|
|
178
|
+
user_id=self.user_id,
|
|
179
|
+
session_id=self.session_id,
|
|
180
|
+
live_request_queue=live_request_queue,
|
|
181
|
+
)
|
|
182
|
+
async for event in events_async:
|
|
183
|
+
event_dict = _utils.dump_event_for_json(event)
|
|
184
|
+
await self.websocket.send_json(event_dict)
|
|
185
|
+
|
|
186
|
+
# Check for error responses
|
|
187
|
+
if isinstance(event_dict, dict) and "error" in event_dict:
|
|
188
|
+
logging.error(f"Agent error: {event_dict['error']}")
|
|
189
|
+
break
|
|
190
|
+
|
|
191
|
+
# Run both tasks
|
|
192
|
+
requests_task = asyncio.create_task(_forward_requests())
|
|
193
|
+
|
|
194
|
+
try:
|
|
195
|
+
await _forward_events()
|
|
196
|
+
finally:
|
|
197
|
+
requests_task.cancel()
|
|
198
|
+
try:
|
|
199
|
+
await requests_task
|
|
200
|
+
except asyncio.CancelledError:
|
|
201
|
+
pass
|
|
202
|
+
|
|
203
|
+
except Exception as e:
|
|
204
|
+
logging.error(f"Error in agent: {e}")
|
|
205
|
+
await self.websocket.send_json({"error": str(e)})
|
|
160
206
|
|
|
161
207
|
|
|
162
208
|
def get_connect_and_run_callable(websocket: WebSocket) -> Callable:
|
|
163
|
-
"""Create a callable that handles
|
|
209
|
+
"""Create a callable that handles agent connection with retry logic.
|
|
164
210
|
|
|
165
211
|
Args:
|
|
166
212
|
websocket: The client websocket connection
|
|
167
213
|
|
|
168
214
|
Returns:
|
|
169
|
-
Callable: An async function that establishes and manages the
|
|
215
|
+
Callable: An async function that establishes and manages the agent connection
|
|
170
216
|
"""
|
|
171
217
|
|
|
172
218
|
async def on_backoff(details: backoff._typing.Details) -> None:
|
|
@@ -180,18 +226,14 @@ def get_connect_and_run_callable(websocket: WebSocket) -> Callable:
|
|
|
180
226
|
backoff.expo, ConnectionClosedError, max_tries=10, on_backoff=on_backoff
|
|
181
227
|
)
|
|
182
228
|
async def connect_and_run() -> None:
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
)
|
|
190
|
-
|
|
191
|
-
await asyncio.gather(
|
|
192
|
-
gemini_session.receive_from_client(),
|
|
193
|
-
gemini_session.receive_from_gemini(),
|
|
194
|
-
)
|
|
229
|
+
logging.info("Starting ADK agent")
|
|
230
|
+
session = AgentSession(websocket)
|
|
231
|
+
|
|
232
|
+
logging.info("Starting bidirectional communication with agent")
|
|
233
|
+
await asyncio.gather(
|
|
234
|
+
session.receive_from_client(),
|
|
235
|
+
session.run_agent(),
|
|
236
|
+
)
|
|
195
237
|
|
|
196
238
|
return connect_and_run
|
|
197
239
|
|
|
@@ -204,15 +246,38 @@ async def websocket_endpoint(websocket: WebSocket) -> None:
|
|
|
204
246
|
await connect_and_run()
|
|
205
247
|
|
|
206
248
|
|
|
207
|
-
|
|
208
|
-
|
|
249
|
+
@app.get("/")
|
|
250
|
+
async def serve_frontend_root() -> FileResponse:
|
|
251
|
+
"""Serve the frontend index.html at the root path."""
|
|
252
|
+
index_file = frontend_build_dir / "index.html"
|
|
253
|
+
if index_file.exists():
|
|
254
|
+
return FileResponse(str(index_file))
|
|
255
|
+
raise HTTPException(
|
|
256
|
+
status_code=404,
|
|
257
|
+
detail="Frontend not built. Run 'npm run build' in the frontend directory.",
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
@app.get("/{full_path:path}")
|
|
262
|
+
async def serve_frontend_spa(full_path: str) -> FileResponse:
|
|
263
|
+
"""Catch-all route to serve the frontend for SPA routing.
|
|
264
|
+
|
|
265
|
+
This ensures that client-side routes are handled by the React app.
|
|
266
|
+
Excludes API routes (ws, feedback) and static assets.
|
|
267
|
+
"""
|
|
268
|
+
# Don't intercept API routes
|
|
269
|
+
if full_path.startswith(("ws", "feedback", "static", "api")):
|
|
270
|
+
raise HTTPException(status_code=404, detail="Not found")
|
|
209
271
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
272
|
+
# Serve index.html for all other routes (SPA routing)
|
|
273
|
+
index_file = frontend_build_dir / "index.html"
|
|
274
|
+
if index_file.exists():
|
|
275
|
+
return FileResponse(str(index_file))
|
|
276
|
+
raise HTTPException(
|
|
277
|
+
status_code=404,
|
|
278
|
+
detail="Frontend not built. Run 'npm run build' in the frontend directory.",
|
|
279
|
+
)
|
|
280
|
+
{% elif cookiecutter.is_adk %}
|
|
216
281
|
import os
|
|
217
282
|
|
|
218
283
|
import google.auth
|
|
@@ -358,7 +423,7 @@ def stream_messages(
|
|
|
358
423
|
set_tracing_properties(config)
|
|
359
424
|
input_dict = input.model_dump()
|
|
360
425
|
|
|
361
|
-
for data in agent.stream(input_dict, config=config, stream_mode="messages"):
|
|
426
|
+
for data in agent.stream(input_dict, config=config, stream_mode="messages"):
|
|
362
427
|
yield dumps(data) + "\n"
|
|
363
428
|
|
|
364
429
|
|
|
@@ -397,40 +462,7 @@ def collect_feedback(feedback: Feedback) -> dict[str, str]:
|
|
|
397
462
|
"""
|
|
398
463
|
logger.log_struct(feedback.model_dump(), severity="INFO")
|
|
399
464
|
return {"status": "success"}
|
|
400
|
-
{% if cookiecutter.agent_name == "live_api" %}
|
|
401
|
-
|
|
402
|
-
@app.get("/")
|
|
403
|
-
async def serve_frontend_root() -> FileResponse:
|
|
404
|
-
"""Serve the frontend index.html at the root path."""
|
|
405
|
-
index_file = frontend_build_dir / "index.html"
|
|
406
|
-
if index_file.exists():
|
|
407
|
-
return FileResponse(str(index_file))
|
|
408
|
-
raise HTTPException(
|
|
409
|
-
status_code=404,
|
|
410
|
-
detail="Frontend not built. Run 'npm run build' in the frontend directory.",
|
|
411
|
-
)
|
|
412
|
-
|
|
413
465
|
|
|
414
|
-
@app.get("/{full_path:path}")
|
|
415
|
-
async def serve_frontend_spa(full_path: str) -> FileResponse:
|
|
416
|
-
"""Catch-all route to serve the frontend for SPA routing.
|
|
417
|
-
|
|
418
|
-
This ensures that client-side routes are handled by the React app.
|
|
419
|
-
Excludes API routes (ws, feedback) and static assets.
|
|
420
|
-
"""
|
|
421
|
-
# Don't intercept API routes
|
|
422
|
-
if full_path.startswith(("ws", "feedback", "static", "api")):
|
|
423
|
-
raise HTTPException(status_code=404, detail="Not found")
|
|
424
|
-
|
|
425
|
-
# Serve index.html for all other routes (SPA routing)
|
|
426
|
-
index_file = frontend_build_dir / "index.html"
|
|
427
|
-
if index_file.exists():
|
|
428
|
-
return FileResponse(str(index_file))
|
|
429
|
-
raise HTTPException(
|
|
430
|
-
status_code=404,
|
|
431
|
-
detail="Frontend not built. Run 'npm run build' in the frontend directory.",
|
|
432
|
-
)
|
|
433
|
-
{% endif %}
|
|
434
466
|
|
|
435
467
|
# Main execution
|
|
436
468
|
if __name__ == "__main__":
|