Web ↔︎ AI Integration
How the web application reaches the AI Python package.
The request path
The browser never talks to the AI backend directly. A chat message travels through a Next.js proxy route, then the AI Gateway, which fans out to the selected engine:
The proxy route
web/app/api/ag-ui-chat/route.ts (POST) is the single entry point:
- Accepts
{ message, auth, stream, sessionId, system_type, dataset_scope, conversation_history }from the client. - With
stream: trueit returns aResponse(new ReadableStream(...))withContent-Type: text/event-stream. Inside the stream it server-sidefetches${AI_SERVER_URL}/chat/agui/stream, then pumps the backend SSE through — buffering by newline and re-emitting eachdata:line — before appending a syntheticRUN_FINISHEDand aSESSION_METADATAcustom event and persisting the assistant artifacts to Supabase. maxDuration = 600with a 10-minute abort on the upstream fetch.
The body sent upstream is { message, conversation_history, session_id, threadId, jwt_token, system_type, dataset_scope } with an Authorization: Bearer <jwt> header.
Two supporting routes: web/app/api/systems/route.ts (GET /api/systems proxies the gateway’s /systems · /systems/verified) and the older web/app/api/mcp-chat/route.ts.
Configuration
| Env var | Target | Notes |
|---|---|---|
AI_SERVER_URL |
Gateway :8080 (fallback http://127.0.0.1:8080) |
The only backend URL web code reads (ag-ui-chat, systems, mcp-chat). Set to http://ai-server:8080 in docker-compose.yml. |
KAWA_ADAPTER_URL |
Kawa adapter :8082 | Set on the web container but consumed by the gateway, not by web code. |
ADK_BACKEND_URL |
ADK Runtime :50052 | Used by the gateway. |
KAVAI_GATEWAY_URL |
Gateway :8080 | Read by Kawa, calling back for image skills (ADR-007). http://ai-server:8080 under compose; localhost on Cloud Run. |
NEXT_PUBLIC_APP_URL |
Web app :3000 | Read by the gateway to reach POST /api/media/resolve. The browser-facing value on the web container is the public origin; the gateway’s own default (http://localhost:3000) is the in-pod one. |
The web app targets the gateway only; the gateway does the engine fan-out from system_type (SYSTEM_TO_BACKEND in ai/src/kavai/gateway/app.py).
Traffic is not one-way, though. The gateway calls into the web app for media resolution, and Kawa calls into the gateway for image skills — so all three processes must be able to reach each other, which is why they are co-located rather than addressed by URL. See Environments and operations.
The AG-UI client
web/hooks/use-agui-client.ts — useAGUIClient() — is the browser consumer:
sendMessage()optimistically appends the user message, resolves/refreshes the JWT, POSTs{ message, stream: true, sessionId, system_type, auth: { jwt_token }, threadId }to/api/ag-ui-chat, then reads the SSE viaresponse.body.getReader(), splits on newlines, and parses eachdata:line into anAGUIEvent.- Events are dispatched through the official AgentSubscriber pattern (
createSubscriber):TEXT_MESSAGE_START/CONTENT/END(token buffering),TOOL_CALL_START/RESULT,STATE_SNAPSHOT,STATE_DELTA(JSON-Patch),RUN_STARTED/FINISHED/ERROR, andCUSTOMevents (IMAGE_GALLERY,DATASET_LIST,THERMAL_ANALYSIS,MARKDOWN_REPORT,THREE_D_OVERVIEW,CDC_PROVENANCE,IMAGE_ANALYSIS_RESULT— the latter recorded per image UUID for the gallery viewer’s annotation overlays). - It exposes
messages,agentState,isProcessing, andlastProvenanceto React; queues messages while offline (@/lib/message-queue) and drains on reconnect; and hydrates history from Supabase vialoadSession().
Related: web/lib/ag-ui/types.ts (EventType, AGUIEvent, AgentSubscriber), web/lib/chat/ag-ui-persistence.ts and ag-ui-message-deserializer.ts (persist / restore), and web/lib/copilot/actions.ts (callAGUIBackend() for CopilotKit actions). The normative event shapes are in CONTRACT-CDC-001.
Engine selection
The valid engine ids are argus | dataadk | kawa | orion (SystemId in web/lib/workspaces/config.ts). The selection chain:
- Verified list —
web/hooks/use-ai-engines.ts(useVerifiedAIEngines) fetches/api/systems?verified=true→ the gateway’s/systems/verified. - User default —
localStorage['kavai_default_system'], set from the Settings card (components/settings/system-settings-card.tsx); legacyhermesnormalizes tokawa. - Workspace default —
WorkspaceConfig.defaultEngine(e.g. the Integrity workspace defaults tokawa). useSelectedEngine()resolves the effective engine (stored default if still verified, elsedataadk, else the first verified engine).- The chat surface (
components/chat/ai-chat-interface.tsx) treatsselectedSystemas the source of truth and setssystem_typeon the request;useAGUIClient({ system })maps it into the POST body.
So: kavai_default_system / workspace defaultEngine → useSelectedEngine → selectedSystem → system_type → /api/ag-ui-chat → gateway → ADK (:50052) or Kawa (:8082).