ODAI’s primary interaction surface is a WebSocket endpoint that delivers streaming text, tool call events, agent handoff events, and suggested prompts as a chat session progresses.
Endpoint
WSS /chats/{chat_id}?token={auth_token}
Parameters
Unique identifier for the chat session. Use a UUID or any client-generated string. If the chat does not exist it is created automatically; if it does exist the conversation history is resumed.
Firebase ID token for the authenticated user. The token is validated server-side on connection. The connection is closed immediately if the token is invalid or expired.
Connection lifecycle
Obtain a Firebase ID token
Sign in with Firebase Authentication on the client and retrieve the current user’s ID token.const token = await firebase.auth().currentUser.getIdToken();
Open the WebSocket
Connect to the endpoint, appending the token as a query parameter.const chatId = crypto.randomUUID();
const ws = new WebSocket(
`wss://api.odai.com/chats/${chatId}?token=${token}`
);
Send a message
Once the connection is open, send a JSON-encoded message object.ws.addEventListener("open", () => {
ws.send(JSON.stringify({
message: "What's my Gmail inbox?",
thread_id: crypto.randomUUID()
}));
});
Receive streaming events
Listen for incoming messages. Each message is a JSON object with a type field that identifies the event. See Response event types below.ws.addEventListener("message", (event) => {
const data = JSON.parse(event.data);
handleEvent(data);
});
Disconnect
Close the socket when the session ends. The server removes the connection from its active pool automatically.
Every message you send must be a JSON string with the following shape:
{
"message": "User prompt text",
"thread_id": "unique_thread_identifier"
}
The natural language prompt from the user. The orchestrator parses this text to decide which agents to invoke.
A client-generated identifier for this specific message turn. Use a UUID. This is tracked for analytics and is included in stored chat responses.
Response event types
The server sends multiple JSON messages per user prompt. Each message has a type field.
raw_response_event — text delta
Streamed character chunk from the active agent. Clients should append delta to a text buffer to reconstruct the full response.
{
"type": "raw_response_event",
"delta": "Here are your ",
"current_agent": "GMail"
}
Emitted when the agent invokes a tool. Use this to show a progress indicator to the user.
{
"type": "tool_call",
"name": "fetch_google_email_inbox",
"description": "Fetching Inbox...",
"current_agent": "GMail"
}
The structured result returned by a tool. Only sent when the tool’s display_response flag is true. The output field contains the full ToolResponse payload.
{
"type": "tool_output",
"output": {
"response_type": "google_email_inbox",
"agent_name": "GMail",
"friendly_name": "GMAIL Inbox",
"display_response": true,
"response": [...]
},
"current_agent": "GMail"
}
agent_updated
Emitted when control transfers from the orchestrator to a specialized agent.
{
"type": "agent_updated",
"new_agent": "GMail",
"current_agent": "GMail",
"name": "GMail"
}
handoff
Emitted when a handoff function call is detected in the event stream.
{
"type": "handoff",
"name": "transfer_to_gmail",
"current_agent": "ODAI"
}
llm_response
The complete text response from the current agent for this turn. Sent after all text deltas have been streamed.
{
"type": "llm_response",
"current_agent": "GMail",
"response": "Here are your 10 most recent inbox messages: ..."
}
suggested_prompts
Sent after end_of_stream. Contains follow-up prompt suggestions generated from the conversation context.
{
"type": "suggested_prompts",
"prompts": [
"Reply to the message from Alice",
"Search for emails about the invoice",
"Send an email to Bob"
]
}
end_of_stream
Signals that the agent has finished processing the current user message. After receiving this event, no further response events will arrive for this turn (except suggested_prompts).
{
"type": "end_of_stream"
}
Complete JavaScript example
async function startChat() {
// 1. Get Firebase ID token
const token = await firebase.auth().currentUser.getIdToken(/* forceRefresh */ true);
const chatId = crypto.randomUUID();
// 2. Open WebSocket connection
const ws = new WebSocket(`wss://api.odai.com/chats/${chatId}?token=${token}`);
let responseBuffer = "";
ws.addEventListener("open", () => {
console.log("Connected");
// 3. Send a message
ws.send(JSON.stringify({
message: "What are my recent Yelp-worthy restaurants in SF?",
thread_id: crypto.randomUUID()
}));
});
ws.addEventListener("message", (event) => {
const data = JSON.parse(event.data);
switch (data.type) {
case "agent_updated":
console.log(`Agent: ${data.new_agent}`);
break;
case "tool_call":
console.log(`Tool: ${data.description}`);
break;
case "raw_response_event":
responseBuffer += data.delta;
process.stdout.write(data.delta); // stream to UI
break;
case "tool_output":
console.log("Tool output:", data.output.response_type);
renderToolOutput(data.output);
break;
case "end_of_stream":
console.log("\n[Done]");
break;
case "suggested_prompts":
renderSuggestedPrompts(data.prompts);
break;
}
});
ws.addEventListener("close", (event) => {
console.log("Disconnected:", event.code, event.reason);
});
ws.addEventListener("error", (error) => {
console.error("WebSocket error:", error);
});
}
Reconnection handling
The server does not automatically reconnect on drop. Implement exponential backoff on the client:
function connectWithRetry(chatId, getToken, attempt = 0) {
const delay = Math.min(1000 * 2 ** attempt, 30000);
setTimeout(async () => {
try {
const token = await getToken();
const ws = new WebSocket(
`wss://api.odai.com/chats/${chatId}?token=${token}`
);
ws.addEventListener("open", () => {
console.log("Reconnected");
attempt = 0; // reset on success
});
ws.addEventListener("close", () => {
connectWithRetry(chatId, getToken, attempt + 1);
});
} catch (err) {
connectWithRetry(chatId, getToken, attempt + 1);
}
}, delay);
}
Always fetch a fresh Firebase ID token before reconnecting. Tokens expire after one hour and a stale token will cause the server to reject the new connection.