Conversations
Conversations
Section titled “Conversations”UPP gives you full control over conversation history. You can use raw arrays or the Thread utility class.
Understanding Turns
Section titled “Understanding Turns”Every call to generate() or stream() produces a Turn:
const turn = await claude.generate('Hello!');
// A Turn contains:console.log(turn.messages); // All messages from this callconsole.log(turn.response); // The final assistant messageconsole.log(turn.usage); // Token usageconsole.log(turn.cycles); // Number of inference cyclesconsole.log(turn.toolExecutions); // Tools called (if any)For a simple request without tools, the turn contains two messages:
- Your
UserMessage - The
AssistantMessageresponse
Using Raw Arrays
Section titled “Using Raw Arrays”The simplest approach is a plain array:
import { llm, Message } from '@providerprotocol/ai';import { anthropic } from '@providerprotocol/ai/anthropic';
const claude = llm({ model: anthropic('claude-sonnet-4-20250514'), system: 'You are a helpful assistant.',});
const history: Message[] = [];
// First turnconst turn1 = await claude.generate(history, 'My name is Alice.');history.push(...turn1.messages);
// Second turn - model remembers contextconst turn2 = await claude.generate(history, 'What is my name?');history.push(...turn2.messages);
console.log(turn2.response.text);// "Your name is Alice."Using the Thread Class
Section titled “Using the Thread Class”Thread provides convenience methods for common operations:
import { llm, Thread } from '@providerprotocol/ai';import { anthropic } from '@providerprotocol/ai/anthropic';
const claude = llm({ model: anthropic('claude-sonnet-4-20250514'), system: 'You are a helpful assistant.',});
const thread = new Thread();
// Generate and append in one stepconst turn1 = await claude.generate(thread, 'My name is Bob.');thread.append(turn1);
const turn2 = await claude.generate(thread, 'What did I tell you?');thread.append(turn2);
console.log(turn2.response.text);// "You told me your name is Bob."console.log(thread.length);// 4 messagesThread Methods
Section titled “Thread Methods”Building Threads
Section titled “Building Threads”const thread = new Thread();
// Add messages programmaticallythread.user('Hello!');thread.assistant('Hi there! How can I help?');
// Append a turn (adds all messages from the turn)const turn = await claude.generate(thread, 'What is 2+2?');thread.append(turn);Accessing Messages
Section titled “Accessing Messages”// Get all messagesconst messages = thread.messages;
// Get by typeconst userMessages = thread.filter('user');const assistantMessages = thread.filter('assistant');
// Get recent messagesconst last5 = thread.tail(5);
// Iteratefor (const message of thread) { console.log(message.type, message.text);}Slicing and Truncating
Section titled “Slicing and Truncating”Manage context window by keeping only recent messages:
// Keep only last 20 messagesconst recentThread = thread.slice(-20);
// Or truncate in placeconst messages = thread.tail(20);const truncatedThread = new Thread(messages);Clearing
Section titled “Clearing”thread.clear();console.log(thread.length); // 0Persistence
Section titled “Persistence”Using Persistence Middleware
Section titled “Using Persistence Middleware”The persistence middleware automatically loads and saves threads around each request:
import { llm } from '@providerprotocol/ai';import { anthropic } from '@providerprotocol/ai/anthropic';import { persistenceMiddleware, PersistenceAdapter } from '@providerprotocol/ai/middleware/persistence';
const adapter = new PersistenceAdapter({ id: 'conversation-123', load: async (id) => loadThreadFromDatabase(id), save: async (id, thread, turn) => { await saveThreadToDatabase(id, thread); },});
const claude = llm({ model: anthropic('claude-sonnet-4-20250514'), middleware: [persistenceMiddleware({ adapter })],});
// History is loaded automatically before each request// and saved automatically after completionconst turn = await claude.generate('Hello!');Manual Serialization
Section titled “Manual Serialization”For more control, use toJSON() and fromJSON():
// Convert to JSONconst json = thread.toJSON();
// Save to fileawait Bun.write('conversation.json', JSON.stringify(json, null, 2));
// Or localStoragelocalStorage.setItem('chat', JSON.stringify(json));// Load from fileconst data = await Bun.file('conversation.json').json();const thread = Thread.fromJSON(data);
// Or from localStorageconst json = JSON.parse(localStorage.getItem('chat')!);const thread = Thread.fromJSON(json);
// Continue the conversationconst turn = await claude.generate(thread, 'Continue where we left off');thread.append(turn);Thread JSON Format
Section titled “Thread JSON Format”interface ThreadJSON { id: string; messages: MessageJSON[]; createdAt: string; updatedAt: string;}
interface MessageJSON { id: string; type: 'user' | 'assistant' | 'tool_result'; content: ContentBlock[]; toolCalls?: ToolCall[]; results?: ToolResult[]; metadata?: MessageMetadata; timestamp: string;}Message Types
Section titled “Message Types”UPP has three message types:
UserMessage
Section titled “UserMessage”Messages from the user:
import { UserMessage } from '@providerprotocol/ai';
const msg = new UserMessage('Hello!');// Or with content blocksconst msg2 = new UserMessage([ { type: 'text', text: 'What is in this image?' }, { type: 'image', source: { type: 'url', url: 'https://...' }, mimeType: 'image/png' },]);AssistantMessage
Section titled “AssistantMessage”Responses from the model:
// Usually from turn.responseconst assistant = turn.response;
console.log(assistant.text); // Text contentconsole.log(assistant.toolCalls); // Tool calls (if any)console.log(assistant.hasToolCalls); // BooleanToolResultMessage
Section titled “ToolResultMessage”Results from tool executions:
// Usually created automatically by UPP// But you can create them manually:import { ToolResultMessage } from '@providerprotocol/ai';
const results = new ToolResultMessage([ { toolCallId: 'call_123', result: { temp: 72 } },]);Turns with Tools
Section titled “Turns with Tools”When tools are involved, a turn contains multiple messages:
const turn = await claude.generate('What is the weather in Tokyo?');
// Turn messages:// 1. UserMessage: "What is the weather in Tokyo?"// 2. AssistantMessage: { toolCalls: [{ name: 'get_weather', ... }] }// 3. ToolResultMessage: [{ result: '72°F, sunny' }]// 4. AssistantMessage: "The weather in Tokyo is 72°F and sunny!"
console.log(turn.messages.length); // 4console.log(turn.cycles); // 2 (initial + after tool)Token Usage
Section titled “Token Usage”Track token consumption:
const turn = await claude.generate('Write a poem');
console.log(turn.usage.inputTokens); // Input tokensconsole.log(turn.usage.outputTokens); // Output tokensconsole.log(turn.usage.totalTokens); // Totalconsole.log(turn.usage.cacheReadTokens); // Cache hitsconsole.log(turn.usage.cacheWriteTokens); // Cache writes (Anthropic)
// Per-cycle breakdownif (turn.usage.cycles) { for (const cycle of turn.usage.cycles) { console.log('Cycle:', cycle.inputTokens, cycle.outputTokens); }}Streaming with History
Section titled “Streaming with History”const thread = new Thread();
const stream = claude.stream(thread, 'Tell me a story');
for await (const event of stream) { if (event.type === 'text_delta') { process.stdout.write(event.delta.text ?? ''); }}
// Append the completed turnconst turn = await stream.turn;thread.append(turn);Context Window Management
Section titled “Context Window Management”Manage long conversations to stay within model limits:
const MAX_MESSAGES = 50;
async function chat(thread: Thread, userInput: string) { // Truncate if too long if (thread.length > MAX_MESSAGES) { const recent = thread.slice(-MAX_MESSAGES); thread.clear(); thread.push(...recent.messages); }
const turn = await claude.generate(thread, userInput); thread.append(turn);
return turn.response.text;}When to Use Thread vs Array
Section titled “When to Use Thread vs Array”| Scenario | Recommendation |
|---|---|
| Simple one-off queries | No history needed |
| Multi-turn chat | Thread |
| Persistence required | Thread with serialization |
| Custom history logic | Raw array |
| Maximum control | Raw array |