"""Google Chat (Workspace add-on, HTTP) -> agent on Agent Runtime. Chat POSTs an event here; we forward the text to the agent and reply synchronously with the agent's answer. Auth: this Cloud Run service is private; only the Workspace add-ons service account has roles/run.invoker, so Cloud Run itself rejects anyone else. """ import os import agentplatform from fastapi import FastAPI, Request AGENT_NAME = os.environ["AGENT_NAME"] # projects/.../locations/.../reasoningEngines/ID _, PROJECT, _, LOCATION, *_ = AGENT_NAME.split("/") agent = agentplatform.Client(project=PROJECT, location=LOCATION).runtimes.get(name=AGENT_NAME) app = FastAPI() def reply(text: str) -> dict: return {"hostAppDataAction": {"chatDataAction": {"createMessageAction": {"message": {"text": text}}}}} async def session_for(user_id: str) -> str: # Stateless: reuse the user's most recent Agent Runtime session, else create one. # ponytail: one conversation per user across all spaces; key on space+user if you need separation. sessions = (await agent.async_list_sessions(user_id=user_id)).get("sessions", []) if sessions: return max(sessions, key=lambda s: s.get("lastUpdateTime", s.get("last_update_time", 0)))["id"] return (await agent.async_create_session(user_id=user_id))["id"] @app.post("/") async def chat_event(request: Request) -> dict: chat = (await request.json()).get("chat", {}) if "addedToSpacePayload" in chat: return reply("Hi! Ask me to convert currencies, e.g. *100 USD to EUR*.") message = chat.get("messagePayload", {}).get("message") if not message: return {} # other events (removed from space, etc.): nothing to say text = (message.get("argumentText") or message.get("text") or "").strip() # argumentText drops the @mention user_id = message["sender"]["name"] # "users/123..." — stable per person session_id = await session_for(user_id) answer = [] async for event in agent.async_stream_query(user_id=user_id, session_id=session_id, message=text): answer += [p["text"] for p in (event.get("content") or {}).get("parts", []) if p.get("text")] return reply(answer[-1] if answer else "Sorry, I have no answer for that.")