Coco App chat_chat Process: How Messages Flow from React UI to AI Response

The chat_chat command in Coco App orchestrates a four-layer pipeline that transports user prompts from the React frontend through a Rust backend to the remote Coco server, then streams AI responses back via event listeners.

The chat_chat process powers real-time AI conversations in the infinilabs/coco-app repository, bridging the React-based user interface with backend streaming endpoints. This technical deep dive examines how the system handles message transmission, distinguishes between desktop and web transports, and manages server-sent event streams using cross-platform adapters.

The Four-Layer Message Architecture

The complete round-trip consists of four distinct layers that handle serialization, transport, and streaming. Each layer corresponds to specific source files in the codebase:

  • Layer 1: UI Interactionsrc/hooks/useChatActions.ts captures user input via handleSendMessage and initializes the chat session
  • Layer 2: Platform Abstractionsrc/utils/platformAdapter.ts routes commands to Tauri or web implementations based on the runtime environment
  • Layer 3: Rust Command Bridgesrc-tauri/src/assistant/mod.rs executes HTTP requests to the Coco server using HttpClient::advanced_post
  • Layer 4: Event Streaming – Event listeners in useChatActions.ts process the byte-stream response via handleChatCreateStreamMessage

Step 1: Initiating Messages with handleSendMessage

When a user submits a prompt, the UI invokes handleSendMessage from src/hooks/useChatActions.ts (lines 73-80). This hook first validates that the active chat session exists, then delegates to the internal sendMessage function while passing the message payload and attachments.

import { useChatActions } from '@/hooks/useChatActions';

export default function ChatInput({ activeChat }) {
  const { handleSendMessage } = useChatActions();

  const onSubmit = async (text: string) => {
    await handleSendMessage(activeChat, { message: text });
  };

  return (
    <input
      placeholder="Ask Coco…"
      onKeyDown={e => e.key === 'Enter' && onSubmit(e.currentTarget.value)}
    />
  );
}

The handleSendMessage wrapper ensures the chat history is initialized before triggering the transport layer, preventing orphaned messages in the UI state.

Step 2: Platform Adapter Routing

The sendMessage function (lines 31-44 in src/hooks/useChatActions.ts) constructs a unique client-id using the pattern chat-stream-${clientId}-${timestamp} and determines the execution path based on the runtime environment. This client ID becomes the correlation key for matching responses to requests.

Desktop: Tauri Command Invocation

For desktop applications, the code invokes platformAdapter.commands("chat_chat", ...), which routes to the Tauri Rust backend:

const sendMessage = useCallback(
  async (newChat, params) => {
    const timestamp = Date.now();
    const clientId = `chat-stream-${clientId}-${timestamp}`;

    const queryParams = { /* query configuration */ };
    await platformAdapter.commands('chat_chat', {
      serverId: currentService?.id,
      sessionId: newChat._id,
      queryParams,
      message: params.message,
      attachments: params.attachments,
      clientId,
    });
    resetChatState();
  },
  [/* deps */],
);

Web: Direct Stream Fetching

In browser environments, the same hook calls streamPost from src/api/streamFetch.ts (lines 1-60), which uses the native fetch API with ReadableStream handling to consume server-sent events directly without Tauri mediation. This web fallback maintains identical logical steps while bypassing the Rust command layer.

Step 3: Rust Backend Processing

For desktop clients, platformAdapter.commands resolves to the chat_chat Tauri command defined in src-tauri/src/assistant/mod.rs (lines 76-84). This Rust function serializes the payload and establishes a streaming HTTP connection to the remote Coco server via HttpClient::advanced_post.

#[tauri::command]
pub async fn chat_chat(
    app_handle: AppHandle,
    server_id: String,
    session_id: String,
    message: Option<String>,
    attachments: Option<Vec<String>>,
    query_params: Option<HashMap<String, Value>>,
    client_id: String,
) -> Result<(), String> {
    // Build request body
    let request_message = ChatRequestMessage { message, attachments };
    let body = Some(serde_json::to_string(&request_message)?.into());

    // POST to the remote Coco server, receive a streaming response
    let response = HttpClient::advanced_post(
        &server_id,
        &format!("/chat/{}/_chat", session_id),
        None,
        convert_query_params_to_strings(query_params),
        body,
    )
    .await?;

    // Forward each line to the front-end
    let mut lines = tokio::io::BufReader::new(
        tokio_util::io::StreamReader::new(
            response.bytes_stream().map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))
        )
    )
    .lines();

    while let Ok(Some(line)) = lines.next_line().await {
        app_handle.emit(&client_id, line).ok();
    }
    Ok(())
}

The command POSTs to /chat/{session_id}/_chat, then reads the response byte-stream line-by-line using tokio::io::BufReader, emitting each line to the frontend using the supplied client_id as the event identifier.

Step 4: Listening for Streamed Responses

Before invoking the command, the React hook registers an event listener using the matching client_id. In src/hooks/useChatActions.ts (lines 257-264), platformAdapter.listenEvent captures each emitted line and processes it through handleChatCreateStreamMessage to update the chat view in real-time.

useEffect(() => {
  const clientId = `chat-stream-${clientId}-${timestamp}`;
  const unlisten = await platformAdapter.listenEvent(clientId, (event) => {
    handleChatCreateStreamMessage(event.payload);
  });
  return () => unlisten();
}, [clientId, timestamp]);

This listener appends each chunk to the chat view as it arrives, creating the streaming effect users experience while the AI generates responses.

Key Source Files in the chat_chat Flow

Understanding these files provides complete visibility into the message pipeline according to the infinilabs/coco-app source code:

Summary

The chat_chat process in Coco App implements a sophisticated cross-platform messaging pipeline:

  • Initialization occurs through handleSendMessage in the React hooks layer, ensuring session validity before transport via src/hooks/useChatActions.ts
  • Transport abstraction routes desktop requests through Tauri's Rust bridge while web clients use native fetch streams in src/api/streamFetch.ts
  • Rust command execution handles HTTP POST requests to /chat/<session>/_chat and manages byte-stream reading via HttpClient::advanced_post in src-tauri/src/assistant/mod.rs
  • Event-driven responses stream back to the UI using client-specific identifiers, with listeners in useChatActions.ts processing each chunk via handleChatCreateStreamMessage

Frequently Asked Questions

What is the purpose of the client_id in the chat_chat process?

The client_id (formatted as chat-stream-${clientId}-${timestamp}) serves as a unique correlation identifier that matches the Rust backend's event emissions with the correct frontend event listener. According to the source code in src/hooks/useChatActions.ts, this ID ensures that streaming responses from multiple concurrent chat sessions route to their respective UI components without collision by acting as the event channel name.

How does Coco App handle chat messaging in web browsers versus desktop applications?

The application uses conditional transport logic within sendMessage. Desktop builds invoke platformAdapter.commands("chat_chat", ...) which triggers the Tauri Rust command in src-tauri/src/assistant/mod.rs. Web builds call streamPost from src/api/streamFetch.ts, which uses the browser's native fetch API with ReadableStream to consume the same Coco server endpoint directly, bypassing the Rust layer while maintaining identical streaming behavior.

Which Rust function actually sends the HTTP request to the Coco server?

The chat_chat function in src-tauri/src/assistant/mod.rs (lines 76-84) creates the HTTP request using HttpClient::advanced_post. This function serializes the message and attachments into a ChatRequestMessage, POSTs to the /chat/{session_id}/_chat endpoint, and then streams the response body line-by-line back to the JavaScript frontend via Tauri's app_handle.emit event system.

Where does the streaming response get parsed and displayed in the UI?

The React hook useChatActions registers an event listener using platformAdapter.listenEvent with the specific client_id before sending the message. When the Rust backend emits lines via app_handle.emit(&client_id, line), this listener receives the payload and passes it to handleChatCreateStreamMessage, which appends the content to the active chat view in real-time, creating the streaming text effect.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →