Skip to main content

Frontend-Backend Communication Protocol

HTTP Agent is fully compatible with the AG-UI Protocol, using SSE for real-time streaming communication between frontend and backend.

You can directly integrate using the AG-UI TypeScript SDK's HttpAgent:

import { HttpAgent } from "@ag-ui/client";

const agent = new HttpAgent({
url: "https://your-endpoint/send-message",
headers: {
Authorization: "Bearer your-api-key",
},
});

POST /send-message

Request Body

The request body follows the AG-UI protocol's RunAgentInput structure:

interface RunAgentInput {
threadId: string; // Conversation thread ID
runId: string; // Unique ID for this run
parentRunId?: string; // Optional, parent run ID (for branching/backtracking scenarios)
state?: any; // Optional, current state
messages: Message[]; // Message history
tools: Tool[]; // Client-side tool list
context: Context[]; // Context information
forwardedProps?: any; // Optional, forwarded properties
}

Notes:

  • threadId remains consistent throughout the conversation, the server uses this ID to maintain session state
  • runId generates a new unique ID for each call
  • messages is the message list

Message Types

type Message =
| DeveloperMessage
| SystemMessage
| AssistantMessage
| UserMessage
| ToolMessage;

interface DeveloperMessage {
id: string;
role: "developer";
content: string;
name?: string;
}

interface SystemMessage {
id: string;
role: "system";
content: string;
name?: string;
}

interface AssistantMessage {
id: string;
role: "assistant";
content?: string;
name?: string;
toolCalls?: ToolCall[];
}

interface UserMessage {
id: string;
role: "user";
content: string | InputContent[];
name?: string;
}

interface ToolMessage {
id: string;
role: "tool";
content: string;
toolCallId: string;
error?: string;
}

interface ToolCall {
id: string;
type: "function";
function: { name: string; arguments: string };
}

// Multimodal input content
type InputContent =
| { type: "text"; text: string }
| { type: "binary"; mimeType: string; id?: string; url?: string; data?: string; filename?: string };

Tool Definition

interface Tool {
name: string;
description: string;
parameters: any; // JSON Schema
}

Context Definition

interface Context {
description: string;
value: string;
}

Example Request:

{
"threadId": "550e8400-e29b-41d4-a716-446655440000",
"runId": "run_001",
"messages": [
{ "id": "msg_1", "role": "user", "content": "What's the weather like in Beijing today?" }
],
"tools": [
{
"name": "get_weather",
"description": "Get weather for a specified city",
"parameters": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
}
}
],
"context": [],
"state": {}
}

Response Events

The response is an SSE (Server-Sent Events) stream with the following format:

  • Each event: data: {json}\n\n (JSON followed by two newline characters)

The AG-UI protocol defines the following event types:

Event TypeDescription
RUN_STARTEDAgent run started
RUN_FINISHEDAgent run finished
RUN_ERRORAgent run error
TEXT_MESSAGE_STARTText message started
TEXT_MESSAGE_CONTENTText content delta
TEXT_MESSAGE_ENDText message ended
TOOL_CALL_STARTTool call started
TOOL_CALL_ARGSTool arguments delta
TOOL_CALL_ENDTool call ended
TOOL_CALL_RESULTTool execution result
STATE_SNAPSHOTState snapshot
STATE_DELTAState delta update (JSON Patch)
MESSAGES_SNAPSHOTMessage history snapshot
STEP_STARTEDStep started
STEP_FINISHEDStep finished

TOOL_CALL_ARGS Concatenation Rules:

Tool arguments are transmitted as streaming deltas. The client needs to concatenate in order the delta fields from multiple TOOL_CALL_ARGS events with the same toolCallId to get the complete JSON string.

// Event stream
data: {"type":"TOOL_CALL_ARGS","toolCallId":"call_1","delta":"{\"cit"}
data: {"type":"TOOL_CALL_ARGS","toolCallId":"call_1","delta":"y\":\"Bei"}
data: {"type":"TOOL_CALL_ARGS","toolCallId":"call_1","delta":"jing\"}"}

// Concatenation result
"{\"city\":\"Beijing\"}" → JSON.parse() → { city: "Beijing" }

Note: When there are multiple parallel tool calls, events with different toolCallId values may appear interleaved. The client should use toolCallId to distinguish and track each tool call.

Event Structures

// Lifecycle events
interface RunStartedEvent {
type: "RUN_STARTED";
threadId: string;
runId: string;
parentRunId?: string;
timestamp?: number;
}

interface RunFinishedEvent {
type: "RUN_FINISHED";
threadId: string;
runId: string;
result?: any;
timestamp?: number;
}

interface RunErrorEvent {
type: "RUN_ERROR";
message: string;
code?: string;
timestamp?: number;
}

// Text message events
interface TextMessageStartEvent {
type: "TEXT_MESSAGE_START";
messageId: string;
role: "developer" | "system" | "assistant" | "user";
}

interface TextMessageContentEvent {
type: "TEXT_MESSAGE_CONTENT";
messageId: string;
delta: string; // Non-empty text delta
}

interface TextMessageEndEvent {
type: "TEXT_MESSAGE_END";
messageId: string;
}

// Tool call events
interface ToolCallStartEvent {
type: "TOOL_CALL_START";
toolCallId: string;
toolCallName: string;
parentMessageId?: string;
}

interface ToolCallArgsEvent {
type: "TOOL_CALL_ARGS";
toolCallId: string;
delta: string;
}

interface ToolCallEndEvent {
type: "TOOL_CALL_END";
toolCallId: string;
}

interface ToolCallResultEvent {
type: "TOOL_CALL_RESULT";
messageId: string;
toolCallId: string;
content: string;
}

// State management events
interface StateSnapshotEvent {
type: "STATE_SNAPSHOT";
snapshot: any;
}

interface StateDeltaEvent {
type: "STATE_DELTA";
delta: JsonPatchOperation[]; // RFC 6902 JSON Patch
}

interface MessagesSnapshotEvent {
type: "MESSAGES_SNAPSHOT";
messages: Message[];
}

// Step events
interface StepStartedEvent {
type: "STEP_STARTED";
stepName: string;
}

interface StepFinishedEvent {
type: "STEP_FINISHED";
stepName: string;
}

Response Example

data: {"type":"RUN_STARTED","threadId":"thread_001","runId":"run_001"}
data: {"type":"TEXT_MESSAGE_START","messageId":"msg_1","role":"assistant"}
data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg_1","delta":"Let me check"}
data: {"type":"TEXT_MESSAGE_END","messageId":"msg_1"}
data: {"type":"TOOL_CALL_START","toolCallId":"call_123","toolCallName":"get_weather","parentMessageId":"msg_1"}
data: {"type":"TOOL_CALL_ARGS","toolCallId":"call_123","delta":"{\"city\":\"Beijing\"}"}
data: {"type":"TOOL_CALL_END","toolCallId":"call_123"}
data: {"type":"TOOL_CALL_RESULT","messageId":"msg_2","toolCallId":"call_123","content":"Sunny, 25°C"}
data: {"type":"TEXT_MESSAGE_START","messageId":"msg_3","role":"assistant"}
data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg_3","delta":"Beijing is sunny today with a temperature of 25°C."}
data: {"type":"TEXT_MESSAGE_END","messageId":"msg_3"}
data: {"type":"RUN_FINISHED","threadId":"thread_001","runId":"run_001"}

Frontend Tools vs Server-side Tools

An Agent can use both frontend tools and server-side tools:

FeatureFrontend ToolsServer-side Tools
Definition LocationFrontend, passed via tools parameterInside the server-side Agent
Execution LocationFrontend (browser)Server
Result DeliveryFrontend sends back via ToolMessageServer returns TOOL_CALL_RESULT event

Frontend Decision Logic:

  1. After receiving TOOL_CALL_END, check if the tool name is in the frontend's tools list
  2. If in the list: This is a frontend tool, wait for RUN_FINISHED, then execute the tool on the frontend and send the result as a ToolMessage
  3. If not in the list: This is a server-side tool, wait for the TOOL_CALL_RESULT event

Sequence Diagrams

Basic Text Response

Frontend Tool Call Flow

The frontend passes tool definitions via the tools parameter. When the Agent calls a frontend tool, the frontend is responsible for execution and sending back the result.

Server-side Tool Call Flow

Server-side tools are defined and executed on the server. The frontend doesn't need to handle tool execution logic.

Human-in-the-Loop

Human-in-the-loop is implemented through frontend tools. When user input is needed, the Agent calls a frontend-defined tool, and the frontend displays UI and collects input when executing that tool.

Example: Confirmation Tool Definition

const confirmTool = {
name: "confirmAction",
description: "Request user confirmation for an action",
parameters: {
type: "object",
properties: {
action: { type: "string", description: "The action to confirm" },
importance: { type: "string", enum: ["low", "medium", "high", "critical"] }
},
required: ["action"]
}
};

Scenario Examples

Scenario 1: Pure Conversation

Request:

{
"threadId": "thread_001",
"runId": "run_001",
"messages": [
{ "id": "msg_1", "role": "user", "content": "Hello" }
],
"tools": [],
"context": []
}

Response:

data: {"type":"RUN_STARTED","threadId":"thread_001","runId":"run_001"}
data: {"type":"TEXT_MESSAGE_START","messageId":"msg_2","role":"assistant"}
data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg_2","delta":"Hello"}
data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg_2","delta":"! How can I help you?"}
data: {"type":"TEXT_MESSAGE_END","messageId":"msg_2"}
data: {"type":"RUN_FINISHED","threadId":"thread_001","runId":"run_001"}

Scenario 2: Frontend Tool Call

The frontend defines a search_local_files tool (only the browser can access local files).

First Request:

{
"threadId": "thread_003",
"runId": "run_003",
"messages": [
{ "id": "msg_1", "role": "user", "content": "Help me search for report files locally" }
],
"tools": [
{
"name": "search_local_files",
"description": "Search user's local files",
"parameters": { "type": "object", "properties": { "keyword": { "type": "string" } } }
}
],
"context": []
}

First Response:

data: {"type":"RUN_STARTED","threadId":"thread_003","runId":"run_003"}
data: {"type":"TOOL_CALL_START","toolCallId":"call_002","toolCallName":"search_local_files"}
data: {"type":"TOOL_CALL_ARGS","toolCallId":"call_002","delta":"{\"keyword\":\"report\"}"}
data: {"type":"TOOL_CALL_END","toolCallId":"call_002"}
data: {"type":"RUN_FINISHED","threadId":"thread_003","runId":"run_003"}

Frontend Processing:

  1. Collect toolCalls from events: {id: "call_002", type: "function", function: {name: "search_local_files", arguments: "{\"keyword\":\"report\"}"}}
  2. Execute the tool and get the result
  3. Send the second request

Second Request:

{
"threadId": "thread_003",
"runId": "run_004",
"messages": [
{ "id": "msg_1", "role": "user", "content": "Help me search for report files locally" },
{
"id": "msg_2",
"role": "assistant",
"toolCalls": [
{ "id": "call_002", "type": "function", "function": { "name": "search_local_files", "arguments": "{\"keyword\":\"report\"}" } }
]
},
{
"id": "msg_3",
"role": "tool",
"toolCallId": "call_002",
"content": "[\"2024_annual_report.pdf\", \"Q3_report.docx\"]"
}
],
"tools": [...],
"context": []
}

Second Response:

data: {"type":"RUN_STARTED","threadId":"thread_003","runId":"run_004"}
data: {"type":"TEXT_MESSAGE_START","messageId":"msg_4","role":"assistant"}
data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg_4","delta":"Found 2 files: 2024_annual_report.pdf and Q3_report.docx"}
data: {"type":"TEXT_MESSAGE_END","messageId":"msg_4"}
data: {"type":"RUN_FINISHED","threadId":"thread_003","runId":"run_004"}

Scenario 3: Server-side Tool Call

The server defines a get_weather tool, the frontend doesn't need to pass this tool.

Request:

{
"threadId": "thread_002",
"runId": "run_002",
"messages": [
{ "id": "msg_1", "role": "user", "content": "What's the weather like in Beijing?" }
],
"tools": [],
"context": []
}

Response:

data: {"type":"RUN_STARTED","threadId":"thread_002","runId":"run_002"}
data: {"type":"TEXT_MESSAGE_START","messageId":"msg_2","role":"assistant"}
data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg_2","delta":"Let me check"}
data: {"type":"TEXT_MESSAGE_END","messageId":"msg_2"}
data: {"type":"TOOL_CALL_START","toolCallId":"call_001","toolCallName":"get_weather","parentMessageId":"msg_2"}
data: {"type":"TOOL_CALL_ARGS","toolCallId":"call_001","delta":"{\"city\":\"Beijing\"}"}
data: {"type":"TOOL_CALL_END","toolCallId":"call_001"}
data: {"type":"TOOL_CALL_RESULT","messageId":"msg_tool_1","toolCallId":"call_001","content":"Sunny, 25°C"}
data: {"type":"TEXT_MESSAGE_START","messageId":"msg_3","role":"assistant"}
data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg_3","delta":"Beijing is sunny today, 25°C."}
data: {"type":"TEXT_MESSAGE_END","messageId":"msg_3"}
data: {"type":"RUN_FINISHED","threadId":"thread_002","runId":"run_002"}

Notes:

  • Frontend receives TOOL_CALL_START, the tool name get_weather is not in the frontend tools list
  • Frontend waits for TOOL_CALL_RESULT event and displays the result directly
  • No frontend tool execution or additional requests needed

Scenario 4: Human-in-the-Loop

Implementing user confirmation through frontend tools.

First Request:

{
"threadId": "thread_004",
"runId": "run_005",
"messages": [
{ "id": "msg_1", "role": "user", "content": "Delete all temporary files" }
],
"tools": [
{
"name": "confirmAction",
"description": "Request user confirmation for dangerous operations",
"parameters": {
"type": "object",
"properties": {
"action": { "type": "string" },
"count": { "type": "number" }
},
"required": ["action"]
}
}
],
"context": []
}

First Response:

data: {"type":"RUN_STARTED","threadId":"thread_004","runId":"run_005"}
data: {"type":"TEXT_MESSAGE_START","messageId":"msg_2","role":"assistant"}
data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg_2","delta":"About to delete 15 temporary files"}
data: {"type":"TEXT_MESSAGE_END","messageId":"msg_2"}
data: {"type":"TOOL_CALL_START","toolCallId":"call_003","toolCallName":"confirmAction","parentMessageId":"msg_2"}
data: {"type":"TOOL_CALL_ARGS","toolCallId":"call_003","delta":"{\"action\":\"delete temporary files\",\"count\":15}"}
data: {"type":"TOOL_CALL_END","toolCallId":"call_003"}
data: {"type":"RUN_FINISHED","threadId":"thread_004","runId":"run_005"}

Client Processing:

  1. Parse confirmAction tool call arguments
  2. Display confirmation dialog
  3. User clicks "Confirm"
  4. Send new request with tool result

Second Request:

{
"threadId": "thread_004",
"runId": "run_006",
"messages": [
{ "id": "msg_1", "role": "user", "content": "Delete all temporary files" },
{
"id": "msg_2",
"role": "assistant",
"content": "About to delete 15 temporary files",
"toolCalls": [
{ "id": "call_003", "type": "function", "function": { "name": "confirmAction", "arguments": "{\"action\":\"delete temporary files\",\"count\":15}" } }
]
},
{
"id": "msg_3",
"role": "tool",
"toolCallId": "call_003",
"content": "confirmed"
}
],
"tools": [...],
"context": []
}

Second Response:

data: {"type":"RUN_STARTED","threadId":"thread_004","runId":"run_006"}
data: {"type":"TEXT_MESSAGE_START","messageId":"msg_4","role":"assistant"}
data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg_4","delta":"Successfully deleted 15 temporary files."}
data: {"type":"TEXT_MESSAGE_END","messageId":"msg_4"}
data: {"type":"RUN_FINISHED","threadId":"thread_004","runId":"run_006"}