Skip to main content

Overview

Conversations in T3Router allow you to maintain context across multiple message exchanges. The Client manages conversation state through thread IDs and message history, enabling natural multi-turn interactions.

Thread Management

What is a Thread?

A thread is a unique identifier that groups related messages together. When you send your first message, T3Router automatically creates a thread ID (UUID) and maintains it for subsequent messages in the same conversation.
use t3router::t3::client::Client;
use t3router::t3::message::{Message, Type};
use t3router::t3::config::Config;

let mut client = Client::new(cookies, session_id);
client.init().await?;

let config = Config::new();

// First message creates a new thread
client.send(
    "gemini-2.5-flash-lite",
    Some(Message::new(Type::User, "Hello!".to_string())),
    Some(config.clone()),
).await?;

// Get the thread ID
if let Some(thread_id) = client.get_thread_id() {
    println!("Thread ID: {}", thread_id);
}
Method Signature:
pub fn get_thread_id(&self) -> Option<&String>

Starting New Conversations

Use new_conversation() to reset the thread and start fresh:
// First conversation
client.send(
    "gemini-2.5-flash-lite",
    Some(Message::new(Type::User, "Tell me about Paris".to_string())),
    Some(config.clone()),
).await?;

// Start a completely new conversation
client.new_conversation();

client.send(
    "gemini-2.5-flash-lite",
    Some(Message::new(Type::User, "Now tell me about Tokyo".to_string())),
    Some(config.clone()),
).await?;
Method Signature:
pub fn new_conversation(&mut self)
This method:
  • Resets the thread ID to None
  • Clears all messages from the conversation history
  • Ensures the next message starts a fresh context

Message History

The Client maintains a vector of all messages in the current conversation, automatically including both user messages and assistant responses.

Viewing Message History

Access all messages in the current conversation:
for msg in client.get_messages() {
    let role = match msg.role {
        Type::User => "User",
        Type::Assistant => "Assistant",
    };
    println!("{}: {}", role, msg.content);
}
Method Signature:
pub fn get_messages(&self) -> &Vec<Message>

Adding Messages

Manually append messages to the conversation history:
client.append_message(Message::new(
    Type::User,
    "What are the top 3 attractions?".to_string()
));

// Send without a new message to use the appended message
let response = client.send(
    "gemini-2.5-flash-lite",
    None,
    Some(config),
).await?;
Method Signature:
pub fn append_message(&mut self, message: Message)

Clearing Messages

Clear the message history while keeping the same thread:
client.clear_messages();
Method Signature:
pub fn clear_messages(&mut self)
Clearing messages removes the conversation history but doesn’t reset the thread ID. Use new_conversation() to reset both.

Multi-Turn Conversations

Natural Conversation Flow

let mut client = Client::new(cookies, session_id);
client.init().await?;

let config = Config::new();

// Turn 1
let response1 = client.send(
    "gemini-2.5-flash-lite",
    Some(Message::new(
        Type::User,
        "I'm planning a trip to Paris. What are the top 3 attractions?".to_string()
    )),
    Some(config.clone()),
).await?;
println!("Assistant: {}", response1.content);

// Turn 2 - maintains context from turn 1
let response2 = client.send(
    "gemini-2.5-flash-lite",
    Some(Message::new(
        Type::User,
        "Tell me more about the first one.".to_string()
    )),
    Some(config.clone()),
).await?;
println!("Assistant: {}", response2.content);

// Turn 3 - still maintains full context
let response3 = client.send(
    "gemini-2.5-flash-lite",
    Some(Message::new(
        Type::User,
        "What's the best time to visit?".to_string()
    )),
    Some(config.clone()),
).await?;
println!("Assistant: {}", response3.content);

Pre-populated Conversations

Build conversation context before sending:
client.new_conversation();

// Set up game context
client.append_message(Message::new(
    Type::User,
    "Let's play a word association game.".to_string()
));

client.append_message(Message::new(
    Type::Assistant,
    "Great! I'm ready to play. Go ahead with your first word!".to_string()
));

client.append_message(Message::new(
    Type::User,
    "Ocean".to_string()
));

client.append_message(Message::new(
    Type::Assistant,
    "Waves".to_string()
));

// Continue the game
client.append_message(Message::new(
    Type::User,
    "Beach".to_string()
));

let response = client.send(
    "gemini-2.5-flash-lite",
    None,
    Some(config),
).await?;
println!("Assistant: {}", response.content);

Conversation Patterns

Two Approaches to Sending

Approach 1: Send with Message

Include the new message directly in the send() call:
let response = client.send(
    "gemini-2.5-flash-lite",
    Some(Message::new(Type::User, "Hello".to_string())),
    Some(config),
).await?;

Approach 2: Append Then Send

Append the message first, then send:
client.append_message(Message::new(
    Type::User,
    "Hello".to_string()
));

let response = client.send(
    "gemini-2.5-flash-lite",
    None,
    Some(config),
).await?;
Both approaches are functionally equivalent. Use approach 1 for simple messages and approach 2 when building complex conversation histories.

Conversation State

Tracking Conversation Progress

println!("Thread ID: {:?}", client.get_thread_id());
println!("Messages in conversation: {}", client.get_messages().len());

// Check if conversation has started
if client.get_thread_id().is_some() {
    println!("Active conversation in progress");
} else {
    println!("No active conversation");
}

Mixed Content Conversations

Conversations can include both text and images:
use t3router::t3::message::ContentType;
use std::path::Path;

client.new_conversation();

// Text message
let response1 = client.send(
    "gemini-2.5-flash-lite",
    Some(Message::new(Type::User, "What makes a good landscape photo?".to_string())),
    Some(config.clone()),
).await?;
println!("Text response: {}", response1.content);

// Image generation in same conversation
let save_path = Path::new("output/landscape.png");
let response2 = client.send_with_image_download(
    "gemini-imagen-4",
    Some(Message::new(
        Type::User,
        "Now create an example based on what you described".to_string()
    )),
    Some(config.clone()),
    Some(save_path),
).await?;

if matches!(response2.content_type, ContentType::Image) {
    println!("Image generated in same conversation!");
}

println!("Total messages: {}", client.get_messages().len());

Best Practices

Start a new conversation when switching to a completely different topic to avoid context pollution.
Long conversations consume more tokens. Consider starting fresh when conversations get very long (50+ messages).
The Client automatically adds assistant responses to history. Don’t manually add them unless reconstructing conversations.
When you need to set up specific conversation context, use multiple append_message() calls before sending.

Error Handling

// Attempting to send with no messages
client.new_conversation();
let response = client.send("model", None, Some(config)).await?;
// Returns: Message with content "Error: No messages to send"

// Correct approach
client.append_message(Message::new(Type::User, "Hello".to_string()));
let response = client.send("model", None, Some(config)).await?;
  • Client - Learn about the Client interface
  • Messages - Understanding message types
  • Models - Choosing models for conversations

Build docs developers (and LLMs) love