Package Exports
- arthur-sdk
- arthur-sdk/package.json
Readme
Arthur SDK
A client-side TypeScript SDK for interacting with Arthur Intelligence and the Booths system. Includes both full-featured ArthurSDK for interactive conversations and ArthurSyncSDK for simple synchronous booth interactions.
Table of Contents
- Installation
- Quick Start Guide
- API Reference
- Booth and Tool Relationships
- Advanced Usage Patterns
- Types
- Session Management Details
- Error Handling
- Browser Compatibility
- License
Installation
npm install arthur-sdkQuick Start Guide
Follow this step-by-step process to set up and use the Arthur SDK:
import {
ArthurSDK,
ArthurSyncSDK,
BoothRegistry,
ToolRegistry,
} from 'arthur-sdk';
// STEP 1: Create registries
const toolRegistry = new ToolRegistry();
const boothRegistry = new BoothRegistry();
// STEP 2: Register tools first (they are referenced by booths)
toolRegistry.registerTool({
name: 'example-tool',
description: 'An example tool',
parameters: { input: 'string' },
global: true, // Available to all booths
execute: async (args) => {
return { result: 'Tool executed successfully', input: args.input };
},
});
// STEP 3: Register booths (they can reference the tools)
boothRegistry.registerBooth({
id: 'example-booth',
name: 'Example Booth',
description: 'An example booth',
context: 'This is an example booth for demonstration purposes.',
// No tools array = only has access to global tools
});
// STEP 4: Initialize the SDK
const sdk = new ArthurSDK({
userId: 'your-user-id',
clientId: 'your-client-id', // Required for client identification
interactURL: 'https://your-api.com/api/message', // Must be absolute URL
interactionEventsURL: 'https://your-api.com/api/interactionloop', // Must be absolute URL
agentConfig: {
agent: 'armor',
boothRegistry,
toolRegistry,
},
sessionId: 'existing-session-id', // Optional: resume existing session
});
// STEP 5: Set up callbacks
sdk.onMessagesReceived = (messages) => {
console.log('New messages:', messages);
};
sdk.onInteractionLoopComplete = () => {
console.log('Interaction loop completed');
};
sdk.onSessionIdChanged = (sessionId) => {
console.log('Session ID changed:', sessionId);
localStorage.setItem('arthur-session-id', sessionId);
};
// STEP 6: Configure headers (optional)
sdk.setAdditionalHeaders({
Authorization: 'Bearer your-token',
'X-Custom-Header': 'custom-value',
});
// STEP 7: Send messages and interact
await sdk.sendMessageAndListen('Hello, Arthur!');API Reference
ArthurSDK
The main SDK class for interacting with Arthur Intelligence.
Constructor Options
Required Parameters
userId(string): Unique identifier for the userclientId(string): Unique identifier for the client instanceinteractURL(string): Absolute URL for sending messages (e.g., 'https://api.example.com/api/message')interactionEventsURL(string): Absolute URL for the interaction loop (e.g., 'https://api.example.com/api/interactionloop')agentConfig(AgentConfig): Agent configuration containing:agent(AgentType): Type of agent ('armor' | 'custom')boothRegistry(BoothRegistry, optional): Registry for managing booth configurationstoolRegistry(ToolRegistry, optional): Registry for managing tool configurations
Optional Parameters
sessionId(string): Existing session ID to resume a previous conversation- Default:
undefined
- Default:
Constructor Examples
Basic initialization for a new conversation
The simplest setup using only required parameters. Default empty registries will be created automatically.
const basicSdk = new ArthurSDK({
userId: 'user-12345',
clientId: 'web-client-001',
interactURL: 'https://api.example.com/api/message',
interactionEventsURL: 'https://api.example.com/api/interactionloop',
agentConfig: {
agent: 'armor',
// boothRegistry and toolRegistry are optional - defaults to empty registries
},
});Using custom tool and booth registries
Most common pattern where you provide pre-configured registries with your tools and booths.
const customToolRegistry = new ToolRegistry();
const customBoothRegistry = new BoothRegistry();
// ... register your tools and booths first
const customSdk = new ArthurSDK({
userId: 'user-12345',
clientId: 'web-client-001',
interactURL: 'https://api.example.com/api/message',
interactionEventsURL: 'https://api.example.com/api/interactionloop',
agentConfig: {
agent: 'armor',
toolRegistry: customToolRegistry,
boothRegistry: customBoothRegistry,
},
});Resuming an existing conversation session
Use this when you want to continue a previous conversation by providing the session ID.
const resumedSdk = new ArthurSDK({
userId: 'user-12345',
clientId: 'web-client-001',
interactURL: 'https://api.example.com/api/message',
interactionEventsURL: 'https://api.example.com/api/interactionloop',
agentConfig: {
agent: 'armor',
toolRegistry: customToolRegistry,
boothRegistry: customBoothRegistry,
},
sessionId: 'session-abc-456', // Resume previous conversation
});Development and testing setup
Simplified configuration for local development with localhost URLs.
const devSdk = new ArthurSDK({
userId: 'dev-user',
clientId: 'dev-client',
interactURL: 'http://localhost:8080/api/message',
interactionEventsURL: 'http://localhost:8080/api/interactionloop',
agentConfig: {
agent: 'armor',
},
});Production setup with environment variables
Enterprise-ready configuration using environment variables and session persistence.
const prodSdk = new ArthurSDK({
userId: process.env.USER_ID!,
clientId: process.env.CLIENT_ID!,
interactURL: process.env.ARTHUR_API_URL + '/api/message',
interactionEventsURL: process.env.ARTHUR_API_URL + '/api/interactionloop',
agentConfig: {
agent: 'armor',
toolRegistry: productionToolRegistry,
boothRegistry: productionBoothRegistry,
},
sessionId: getStoredSessionId(), // Optional: restore session
});Core Methods
ArthurSDK.sendMessage()
Send a message to the Arthur Intelligence API without automatically starting the interaction loop listener.
⚠️ Important: This method cannot be called while an EventSource connection is active (i.e., after calling sendMessageAndListen() or startInteractionLoopListener()). Use clearSession() first to close the connection, or use sendMessageAndListen() for interactive sessions.
Example
await sdk.sendMessage('Hello, Arthur!');Syntax
// Send a simple text message
await sdk.sendMessage(message);
// Send complex message array
await sdk.sendMessage(messages);Parameters
message(string | ResponseInputItem[]): The message to send. Can be a simple string or an array of ResponseInputItem objects for complex message structures.
Return Type
Promise<void> - Returns a promise that resolves when the message has been sent successfully. The promise will reject if there's an error during sending (network issues, invalid message format, etc.).
Error Conditions
- Throws an error if called while EventSource is active:
"Cannot send message while EventSource is active. Close the EventSource first with clearSession() or use sendMessageAndListen() for interactive sessions."
More Examples
Simple user query
Most common usage - sending a basic user message.
await sdk.sendMessage('What is the weather today?');System logging and notifications
Use developer role for internal system messages, logging, or debugging information.
await sdk.sendMessage([{ role: 'developer', content: 'User logged in' }]);User query with developer context
Add hidden context that helps the LLM understand the user's situation without the user seeing it.
const userQueryWithContext: ResponseInputItem[] = [
{
role: 'user',
content: 'What is the weather forecast for tomorrow?',
},
{
role: 'developer',
content:
'User is located in New York, NY. Use metric units for temperature.',
},
];
await sdk.sendMessage(userQueryWithContext);Assistant gaslighting (fake conversation history)
Inject an assistant message that the assistant will believe it actually said, making it part of its conversation history.
const gaslightingExample: ResponseInputItem[] = [
{
role: 'user',
content: 'Help me with email drafting',
},
{
role: 'assistant',
content:
'I specialize in professional email composition and always use formal tone.',
},
];
await sdk.sendMessage(gaslightingExample);Combining developer context with assistant gaslighting
Use both techniques together for maximum control over the LLM's understanding and behavior.
const fullControlExample: ResponseInputItem[] = [
{
role: 'user',
content: 'Write a status update email',
},
{
role: 'developer',
content:
'User is a project manager sending weekly update to stakeholders. Include metrics and next steps.',
},
{
role: 'assistant',
content:
'I create structured, professional status emails with clear sections and actionable items.',
},
];
await sdk.sendMessage(fullControlExample);Under The Hood
You can send messages without starting the interaction loop listener. These messages will be queued in the session until you're ready to start the listener. This is useful for sending background messages without blocking the UI. The LLM will not respond immediately to these messages, but they will be stored.
Important: When you start the interaction loop listener, the SDK will process all queued messages from the session. The LLM sees messages in chronological order, so make sure to send all your messages BEFORE starting the listener, otherwise the LLM will see the last message as the most recent in the conversation.
Session Management: Once an EventSource connection is active (via sendMessageAndListen() or startInteractionLoopListener()), you cannot call sendMessage() again until you close the connection with clearSession(). This prevents session ID mismatches that could break real-time message handling.
ArthurSDK.startInteractionLoopListener()
Manually start listening for server-sent events from the interaction loop. Requires that a session ID and request key already exist (from a previous sendMessage call). The request key is used for authentication and is consumed during the EventSource connection setup.
Example
// First send a message to establish session
await sdk.sendMessage('Initial message');
// Then start listening manually
sdk.startInteractionLoopListener();Syntax
// Start listening (no parameters)
sdk.startInteractionLoopListener();Parameters
None. This method requires that both a session ID and request key have already been established through a previous sendMessage or sendMessageAndListen call.
Return Type
void - This method returns immediately after starting the EventSource connection. It does not return a promise. Responses will be handled through the registered callback functions (onMessagesReceived, onInteractionLoopComplete). Will throw an error if no session ID or request key exists.
More Examples
// CORRECT: Send all messages first, then start listening
await sdk.sendMessage('Setup conversation');
await sdk.sendMessage([{ role: 'developer', content: 'User context info' }]);
await sdk.sendMessage('Main user query');
// Now start listening - LLM will see messages in correct order
sdk.startInteractionLoopListener();
// INCORRECT: Don't interleave messages and listener starts
await sdk.sendMessage('First message');
sdk.startInteractionLoopListener(); // LLM thinks this is the last message
// Cannot call sendMessage here - EventSource is active!
// await sdk.sendMessage('This will throw an error');
// CORRECT: To send more messages after starting listener, clear session first
sdk.clearSession(); // Close EventSource
await sdk.sendMessage('New message after clearing session');
// Restart listening after connection issues
try {
sdk.startInteractionLoopListener();
} catch (error) {
if (error.message.includes('No session ID found')) {
// Need to send a message first to establish session
await sdk.sendMessage('Reconnecting...');
sdk.startInteractionLoopListener();
}
}
// Error handling
sdk.onInteractionLoopComplete = () => {
console.log('Conversation ended, can restart if needed');
};ArthurSDK.sendMessageAndListen()
Send a message and automatically start listening for responses via the interaction loop. This is the most commonly used method for interactive conversations.
Example
await sdk.sendMessageAndListen('Hello, I need help with my tasks');Syntax
// Send text message and start listening
await sdk.sendMessageAndListen(message);
// Send complex message array and start listening
await sdk.sendMessageAndListen(messages);Parameters
message(string | ResponseInputItem[]): The message to send. Can be a simple string or an array of ResponseInputItem objects for complex message structures.
Return Type
Promise<void> - Returns a promise that resolves when the message has been sent successfully and the interaction loop listener has been started. The promise will reject if there's an error during sending or if starting the listener fails.
More Examples
// Start a new conversation
await sdk.sendMessageAndListen('I need help with weather and email');
// Continue an existing conversation
await sdk.sendMessageAndListen('Can you send that email now?');
// Send with complex message structure
await sdk.sendMessageAndListen([
{ role: 'user', content: 'Please help me with these tasks:' },
{ role: 'user', content: '1. Check weather in New York' },
{ role: 'user', content: '2. Send email to john@example.com' },
]);Multi-task request with full context control
Combine user query, developer context, and assistant gaslighting for complex interactive tasks.
const advancedMessage: ResponseInputItem[] = [
{
role: 'user',
content: 'I need help with weather and sending an important email',
},
{
role: 'developer',
content:
'Priority request - user is traveling tomorrow and needs weather info for NYC. Email recipient is their boss.',
},
{
role: 'assistant',
content:
'I always provide detailed weather forecasts and help craft professional emails with appropriate tone.',
},
];
await sdk.sendMessageAndListen(advancedMessage);// Interactive session with immediate response handling
sdk.onMessagesReceived = (messages) => {
// Handle real-time responses
messages.forEach(msg => console.log(msg.content));
};
await sdk.sendMessageAndListen('Start interactive session');ArthurSDK.onMessagesReceived
Set a callback function to handle incoming messages and message updates in real-time.
Example
sdk.onMessagesReceived = (messages) => {
console.log('New messages:', messages);
};Syntax
// Set the callback function
sdk.onMessagesReceived = callback;
// Get the current callback function
const currentCallback = sdk.onMessagesReceived;Parameters
callback((messages: ConversationMessage[]) => void): Function that receives the complete array of conversation messages whenever messages are received or updated.
Return Type
void - This is a setter property. The callback function itself should not return anything.
More Examples
Basic message logging
Simple logging of all incoming messages.
sdk.onMessagesReceived = (messages) => {
messages.forEach((msg) => {
console.log(`${msg.role}: ${msg.content}`);
});
};UI update with message filtering
Update your UI and handle different message types appropriately.
sdk.onMessagesReceived = (messages) => {
const userMessages = messages.filter((msg) => msg.type === 'message');
const toolCalls = messages.filter((msg) => msg.type === 'tool_call');
// Update UI with user/assistant messages
updateChatUI(userMessages);
// Show loading indicators for active tool calls
toolCalls.forEach((toolCall) => {
if (toolCall.loading) {
showToolLoadingIndicator(toolCall.content.name);
}
});
};Message state management
Integration with state management systems.
sdk.onMessagesReceived = (messages) => {
// Update your application state
dispatch(updateMessages(messages));
// Save to local storage for persistence
localStorage.setItem('chat-messages', JSON.stringify(messages));
// Trigger other side effects
if (messages.length > 0) {
markConversationAsActive();
}
};Message Types and Shapes
The messages array passed to your callback contains ConversationMessage objects. Important: This array includes ALL messages in the conversation, including the messages you sent via sendMessage() and sendMessageAndListen(). You don't need to manually add your sent messages to your UI - they're automatically included in this array.
Here are the different types you'll encounter:
User/Assistant Text Messages
Standard conversation messages from users or AI assistants.
// ConversationMessageUserText
{
type: 'message',
role: 'user' | 'assistant' | 'developer',
content: 'Hello, I need help with weather',
id: 'msg-123',
time: '2024-01-15T10:30:00Z',
loading: false
}Tool Call Messages
Messages representing tool executions in progress or completed.
// ConversationMessageToolCall
{
type: 'tool_call',
role: 'assistant',
loading: true, // true while executing, false when complete
id: 'call-456',
time: '2024-01-15T10:30:05Z',
content: {
call_id: 'call-456',
name: 'weather-api',
results: { temperature: 72, conditions: 'sunny' } // present when loading: false
}
}Handling Different Message Types
Use type guards to handle each message type appropriately.
import {
isConversationMessageUserText,
isConversationMessageToolCall,
} from 'arthur-sdk';
sdk.onMessagesReceived = (messages) => {
messages.forEach((message) => {
if (isConversationMessageUserText(message)) {
// Handle text message
console.log(`${message.role}: ${message.content}`);
displayTextMessage(message);
} else if (isConversationMessageToolCall(message)) {
// Handle tool call message
if (message.loading) {
showToolExecutionIndicator(message.content.name);
} else {
hideToolExecutionIndicator(message.content.name);
displayToolResults(message.content.results);
}
}
});
};Complete Message Flow Example
How messages evolve during a typical conversation with tool usage.
// Initial messages array might look like:
[
{
type: 'message',
role: 'user',
content: 'What is the weather in New York?',
id: 'msg-1',
},
{
type: 'message',
role: 'assistant',
content: "I'll check the weather in New York for you.",
id: 'msg-2',
},
{
type: 'tool_call',
role: 'assistant',
loading: true, // Tool is executing
id: 'call-1',
content: {
call_id: 'call-1',
name: 'weather-api',
},
},
][
// After tool execution completes, the same array updates to:
// ... previous messages unchanged ...
({
type: 'tool_call',
role: 'assistant',
loading: false, // Tool completed
id: 'call-1',
content: {
call_id: 'call-1',
name: 'weather-api',
results: { temperature: 68, conditions: 'partly cloudy' },
},
},
{
type: 'message',
role: 'assistant',
content: 'The weather in New York is 68°F and partly cloudy.',
id: 'msg-3',
})
];Important Note About Message Inclusion
The messages array automatically includes your sent messages - no manual UI updates needed for sent messages.
// ❌ WRONG: Don't manually add sent messages to your UI
await sdk.sendMessage('Hello!');
addMessageToUI({ role: 'user', content: 'Hello!' }); // Don't do this!
// ✅ CORRECT: Just send the message and let the callback handle UI updates
sdk.onMessagesReceived = (messages) => {
updateCompleteUI(messages); // This already includes your sent message
};
await sdk.sendMessage('Hello!');
// The callback will receive:
// [
// { type: 'message', role: 'user', content: 'Hello!', id: 'msg-1' },
// { type: 'message', role: 'assistant', content: 'Hi there!', id: 'msg-2' }
// ]Message Queueing System
The Arthur SDK includes a sophisticated message queueing system that prevents race conditions and ensures proper message ordering during interactive conversations. This system automatically manages overlapping requests to maintain conversation integrity.
How It Works
The Problem Solved: When multiple messages are sent while an interaction is still active (such as during LLM tool execution or response streaming), they could previously interrupt each other, causing:
- Lost responses from ongoing tool calls
- Broken conversation context
- Race conditions between overlapping requests
The Solution: The SDK automatically queues messages when a request is already being processed, ensuring:
- ✅ Messages are processed in the correct order
- ✅ Ongoing tool executions complete without interruption
- ✅ LLM conversation flow remains intact
- ✅ No race conditions between overlapping requests
Automatic Behavior
The queueing system works transparently - no code changes are required. Your existing sendMessageAndListen() calls will automatically be queued when appropriate:
// This works seamlessly - no changes needed to your code
await sdk.sendMessageAndListen('Analyze this data');
// If LLM makes tool calls during the above request,
// this message will be automatically queued until the first request completes
await sdk.sendMessageAndListen('Also check the weather');Request Processing Order
Messages are processed in strict FIFO (First In, First Out) order:
- Active Request Processing: Only one request is processed at a time
- Automatic Queueing: Subsequent messages are queued automatically
- Sequential Processing: Queued messages are processed only after the current request completes
- Tool Call Preservation: Tool execution results are queued and sent in the correct sequence
Real-World Example Scenario
Here's what happens in a complex interaction where the user interrupts an ongoing LLM workflow:
// 1. User starts a complex request
await sdk.sendMessageAndListen('Analyze sales data and send summary email');
// 2. LLM processes request and makes sequential tool calls:
// - Calls data-fetcher tool
// - Calls data-analyzer tool
// - Starts streaming response with analysis
// - Calls email-sender tool
// 3. User sends interruption message while LLM is still working
await sdk.sendMessageAndListen('Also add weather forecast to the email');
// ↳ This is automatically QUEUED, not processed immediately
// 4. LLM completes its entire workflow:
// - Finishes tool executions
// - Completes response streaming
// - Sends 'end' command
// 5. THEN the queued user message is processed:
// - Fresh request with new requestKey
// - Proper conversation context maintained
// - No interruption of the previous workflowKey Benefits
🔒 Race Condition Prevention
- Eliminates conflicts between overlapping requests
- Prevents premature closing of active EventSource connections
- Ensures tool execution results are never lost
📋 Proper Message Ordering
- FIFO processing guarantees messages are handled in the correct sequence
- Tool execution results are sent before user interruptions are processed
- Conversation context remains coherent and logical
🔄 Request Key Management
- Each queued message gets a fresh, unique
requestKeywhen processed - Single-use authentication tokens are properly managed
- No token reuse errors or authentication conflicts
⚡ Seamless User Experience
- Users can send messages anytime without worrying about timing
- No need to wait for previous requests to complete
- Natural conversation flow is preserved
Technical Details
Request States:
isProcessingRequest: Boolean flag indicating if a request is activerequestQueue: Array of pending messages awaiting processingcurrentRequestId: Unique identifier for the active request (debugging)
Queue Processing:
- New messages are queued when
isProcessingRequestistrue - Queue processing begins when EventSource closes (booth 'end' command)
- Each queued message receives a fresh
requestKeyfrom the server - Processing continues until the queue is empty
Error Handling:
- Failed requests mark themselves complete to allow queue processing
- Session clearing (
clearSession()) rejects all queued messages - EventSource errors trigger queue processing to maintain flow
Backward Compatibility
The message queueing system is completely transparent and maintains full backward compatibility:
- ✅ All existing
sendMessageAndListen()calls work unchanged - ✅ No API modifications or breaking changes
- ✅ Same promises and callback behavior
- ✅ Existing error handling patterns remain valid
Advanced Configuration
While the queueing system works automatically, you can monitor its behavior:
// The SDK exposes these properties for debugging (not recommended for production use)
console.log('Is processing request:', (sdk as any).isProcessingRequest);
console.log('Queue length:', (sdk as any).requestQueue.length);
// Session clearing properly handles queued messages
sdk.clearSession(); // Rejects all queued messages and resets stateNote: The internal queue properties are not part of the public API and should not be relied upon in production code. They're mentioned here for debugging purposes only.
ArthurSDK.pushMessage()
Add a new message to the conversation array. This immediately triggers the onMessagesReceived callback with the updated messages array.
Example
const newMessage = {
type: 'message',
role: 'user',
content: 'This is a manually added message',
id: 'manual-msg-1',
};
sdk.pushMessage(newMessage);Syntax
sdk.pushMessage(message);Parameters
message(ConversationMessage): The message object to add to the conversation. Must be eitherConversationMessageUserTextorConversationMessageToolCall.
Return Type
void - This method does not return anything. The message is added to the internal messages array and immediately triggers the onMessagesReceived callback.
More Examples
Adding a user message programmatically
Useful for injecting messages from external sources or cached conversations.
const userMessage: ConversationMessage = {
type: 'message',
role: 'user',
content: 'Restored from cache',
id: 'cached-msg-1',
time: '2024-01-15T09:00:00Z',
};
sdk.pushMessage(userMessage);Adding a tool call message
For reconstructing conversations that included tool executions.
const toolMessage: ConversationMessage = {
type: 'tool_call',
role: 'assistant',
loading: false,
id: 'tool-call-1',
content: {
call_id: 'tool-call-1',
name: 'weather-api',
results: { temperature: 75, conditions: 'sunny' },
},
};
sdk.pushMessage(toolMessage);ArthurSDK.updateMessage()
Update an existing message in the conversation by its ID. Useful for updating loading states or modifying message content.
Example
sdk.updateMessage('msg-123', (message) => ({
...message,
loading: false,
}));Syntax
sdk.updateMessage(id, updater);Parameters
id(string): The unique identifier of the message to updateupdater((message: ConversationMessage) => ConversationMessage): Function that receives the current message and returns the updated message
Return Type
void - This method does not return anything. The message is updated in the internal messages array and immediately triggers the onMessagesReceived callback.
More Examples
Update tool call completion status
Mark a tool call as completed and add results.
sdk.updateMessage('tool-call-456', (msg) => ({
...msg,
loading: false,
content: {
...msg.content,
results: { success: true, data: 'operation completed' },
},
}));Update message content
Modify the text content of an existing message.
sdk.updateMessage('msg-789', (msg) => ({
...msg,
content: msg.content + ' (edited)',
time: new Date().toISOString(),
}));Conditional updates
Only update if certain conditions are met.
sdk.updateMessage('msg-101', (msg) => {
if (msg.type === 'tool_call' && msg.loading) {
return { ...msg, loading: false };
}
return msg; // No change if conditions not met
});ArthurSDK.setMessages()
Replace the entire conversation messages array. This immediately triggers the onMessagesReceived callback with the new messages.
Example
const newMessages = [
{ type: 'message', role: 'user', content: 'Hello', id: 'msg-1' },
{ type: 'message', role: 'assistant', content: 'Hi there!', id: 'msg-2' },
];
sdk.setMessages(newMessages);Syntax
sdk.setMessages(messages);Parameters
messages(ConversationMessage[]): Array of conversation messages to replace the current messages with
Return Type
void - This method does not return anything. The messages array is replaced and immediately triggers the onMessagesReceived callback.
More Examples
Load conversation from storage
Restore a previously saved conversation.
const savedMessages = JSON.parse(
localStorage.getItem('conversation-history') || '[]',
);
sdk.setMessages(savedMessages);Reset conversation
Clear all messages and start fresh.
sdk.setMessages([]);Initialize with system message
Start a conversation with a predefined system context.
const initialMessages = [
{
type: 'message',
role: 'assistant',
content: "Welcome! I'm ready to help you with weather and email tasks.",
id: 'welcome-msg',
},
];
sdk.setMessages(initialMessages);ArthurSDK.clearSession()
Clear the current session ID and optionally clear the conversation messages. This will cause the next sendMessage() call to create a new session on the server.
Example
// Clear session and messages (start completely fresh)
sdk.clearSession();
// Clear session but keep messages for display purposes
sdk.clearSession(false);Syntax
sdk.clearSession(clearMessages);Parameters
clearMessages(boolean, optional): Whether to clear the messages array. Default:true
Return Type
void - This method does not return anything. The session is cleared, optionally messages are cleared, any active EventSource connection is closed, and the onSessionIdChanged callback is triggered with undefined.
More Examples
Complete session reset (default behavior)
Clear both the session and all conversation messages for a completely fresh start.
// This is the default - clears session AND messages
sdk.clearSession();
// Equivalent to: sdk.clearSession(true);
// Next message will create a brand new session
await sdk.sendMessage('Starting a new conversation');Keep messages for UI display
Clear the session but preserve messages in the UI for user reference.
// Keep messages visible but start new session
sdk.clearSession(false);
// Messages still visible to user, but next message starts new server session
await sdk.sendMessage('Continue with new session');Programmatic session management
Integrate session clearing with your application logic.
// Handle user logout - clear everything
function handleLogout() {
sdk.clearSession(); // Clear session and messages
localStorage.removeItem('arthur-session-id'); // Clear stored session
redirectToLogin();
}
// Handle "new conversation" button
function startNewConversation() {
sdk.clearSession(); // Fresh session and clear messages
showWelcomeMessage();
}
// Handle session timeout - keep messages for reference
function handleSessionTimeout() {
sdk.clearSession(false); // Clear session but keep messages
showSessionExpiredNotification();
}With session change callback handling
Handle the session clearing in your callback.
sdk.onSessionIdChanged = (sessionId) => {
if (sessionId === undefined) {
// Session was cleared
console.log('Session cleared - next message will create new session');
localStorage.removeItem('arthur-session-id');
updateUIForNoSession();
} else {
// New session created
console.log('New session:', sessionId);
localStorage.setItem('arthur-session-id', sessionId);
updateUIForActiveSession(sessionId);
}
};
// This will trigger the callback with undefined
sdk.clearSession();Error recovery and reconnection
Use session clearing for error recovery scenarios.
// Handle connection errors by starting fresh
sdk.onInteractionLoopComplete = () => {
if (sdk.eventSource?.readyState === EventSource.CLOSED) {
console.log('Connection lost - clearing session for fresh start');
sdk.clearSession(false); // Keep messages but clear session
// Attempt to reconnect with fresh session
setTimeout(async () => {
try {
await sdk.sendMessage('Reconnecting...');
} catch (error) {
console.error('Failed to reconnect:', error);
}
}, 2000);
}
};ArthurSDK.setAdditionalHeaders()
Set multiple custom headers that will be included in all HTTP requests to the server.
Example
sdk.setAdditionalHeaders({
Authorization: 'Bearer your-token-here',
'X-API-Version': '2.1',
'X-Client-Source': 'web-app',
});Syntax
sdk.setAdditionalHeaders(headers);Parameters
headers(Record<string, string>): Object containing header key-value pairs to set. This will replace all existing additional headers.
Return Type
void - This method does not return anything.
More Examples
Authentication headers
Set authorization and API credentials for secured endpoints.
sdk.setAdditionalHeaders({
Authorization: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
'X-API-Key': 'your-api-key-here',
});Custom request context
Add application-specific metadata to requests.
sdk.setAdditionalHeaders({
'X-User-Role': 'admin',
'X-Organization-ID': 'org-12345',
'X-Request-Source': 'dashboard',
'X-Client-Version': '1.2.3',
});Replacing existing headers
Since this method replaces all headers, use it for complete header resets.
// Initial headers
sdk.setAdditionalHeaders({
Authorization: 'Bearer old-token',
'X-Version': '1.0',
});
// Replace with new set (old headers are removed)
sdk.setAdditionalHeaders({
Authorization: 'Bearer new-token',
'X-Version': '2.0',
'X-Feature-Flag': 'new-ui',
});ArthurSDK.setHeader()
Set a single custom header that will be included in all HTTP requests to the server. If the header already exists, it will be updated.
Example
sdk.setHeader('Authorization', 'Bearer new-token-here');Syntax
sdk.setHeader(key, value);Parameters
key(string): The header name/key to setvalue(string): The header value to set
Return Type
void - This method does not return anything.
More Examples
Update authentication token
Refresh authorization headers without affecting other headers.
// Set initial auth
sdk.setHeader('Authorization', 'Bearer initial-token');
// Later update just the auth token
sdk.setHeader('Authorization', 'Bearer refreshed-token');Add API versioning
Specify API version for backward compatibility.
sdk.setHeader('X-API-Version', '2.1');
sdk.setHeader('Accept', 'application/vnd.api+json;version=2.1');Request tracking and debugging
Add correlation IDs and debug information.
sdk.setHeader('X-Request-ID', generateUUID());
sdk.setHeader('X-Debug-Mode', 'true');
sdk.setHeader('X-Source-Component', 'chat-widget');ArthurSDK.removeHeader()
Remove a specific header from the additional headers collection. The header will no longer be included in HTTP requests.
Example
sdk.removeHeader('X-Debug-Mode');Syntax
sdk.removeHeader(key);Parameters
key(string): The header name/key to remove
Return Type
void - This method does not return anything. If the header doesn't exist, no error is thrown.
More Examples
Remove authentication
Clear authorization headers when logging out.
sdk.removeHeader('Authorization');
sdk.removeHeader('X-API-Key');Clean up temporary headers
Remove debug or temporary headers after use.
// Set temporary debug headers
sdk.setHeader('X-Debug-Session', 'debug-123');
sdk.setHeader('X-Trace-Enabled', 'true');
// Remove them when debugging is complete
sdk.removeHeader('X-Debug-Session');
sdk.removeHeader('X-Trace-Enabled');Conditional header removal
Remove headers based on application state.
// Remove development headers in production
if (process.env.NODE_ENV === 'production') {
sdk.removeHeader('X-Debug-Mode');
sdk.removeHeader('X-Test-Environment');
}ArthurSDK.getAdditionalHeaders()
Get a copy of all additional headers currently set. Returns an object containing all custom headers that will be included in HTTP requests.
Example
const headers = sdk.getAdditionalHeaders();
console.log(headers); // { 'Authorization': 'Bearer token', 'X-API-Version': '2.1' }Syntax
const headers = sdk.getAdditionalHeaders();Parameters
None.
Return Type
Record<string, string> - Object containing all additional headers as key-value pairs. This is a copy, so modifying the returned object won't affect the SDK's headers.
More Examples
Header inspection and debugging
Check what headers are currently set for debugging purposes.
const currentHeaders = sdk.getAdditionalHeaders();
console.log('Current headers:', currentHeaders);
// Check if specific header exists
if ('Authorization' in currentHeaders) {
console.log('Authorization header is set');
}Backup and restore headers
Save current headers before making temporary changes.
// Backup current headers
const originalHeaders = sdk.getAdditionalHeaders();
// Make temporary changes
sdk.setAdditionalHeaders({
'X-Test-Mode': 'true',
'X-Mock-Data': 'enabled',
});
// Later restore original headers
sdk.setAdditionalHeaders(originalHeaders);Header validation and sanitization
Validate headers before sending requests.
const headers = sdk.getAdditionalHeaders();
// Validate required headers
const requiredHeaders = ['Authorization', 'X-API-Version'];
const missingHeaders = requiredHeaders.filter((header) => !(header in headers));
if (missingHeaders.length > 0) {
console.warn('Missing required headers:', missingHeaders);
}
// Sanitize sensitive information in logs
const sanitizedHeaders = { ...headers };
if (sanitizedHeaders.Authorization) {
sanitizedHeaders.Authorization = 'Bearer [REDACTED]';
}
console.log('Headers for logging:', sanitizedHeaders);ArthurSDK.messages
Direct access to the current conversation messages array. This property provides read and write access to the internal messages collection.
Example
// Read current messages
console.log('Current messages:', sdk.messages);
// Add a message directly (not recommended - use pushMessage instead)
sdk.messages.push(newMessage);Type
ConversationMessage[] - Array containing all conversation messages in chronological order.
More Examples
Reading conversation state
Access current conversation messages for display or analysis.
const messageCount = sdk.messages.length;
const lastMessage = sdk.messages[sdk.messages.length - 1];
console.log(`Conversation has ${messageCount} messages`);
if (lastMessage) {
console.log('Last message:', lastMessage.content);
}Filtering messages by type
Extract specific types of messages from the conversation.
const userMessages = sdk.messages.filter(
(msg) => msg.type === 'message' && msg.role === 'user',
);
const toolCalls = sdk.messages.filter((msg) => msg.type === 'tool_call');
const activeToolCalls = toolCalls.filter((call) => call.loading);Direct manipulation (use with caution)
While direct access is available, using the SDK methods is preferred.
// ❌ Direct manipulation - can bypass callbacks
sdk.messages.push(newMessage);
// ✅ Preferred approach - triggers callbacks
sdk.pushMessage(newMessage);ArthurSDK.messagesList
Getter property that returns the messages array. This is an alias for the messages property, providing the same functionality with a more descriptive name.
Example
const allMessages = sdk.messagesList;
console.log('Total messages:', allMessages.length);Type
ConversationMessage[] - Array containing all conversation messages, identical to the messages property.
More Examples
Equivalent access patterns
Both properties provide the same data.
// These are functionally identical
const messages1 = sdk.messages;
const messages2 = sdk.messagesList;
console.log(messages1 === messages2); // trueChoosing between messages and messagesList
Use whichever naming convention fits your codebase better.
// Shorter, more direct
const count = sdk.messages.length;
// More explicit, self-documenting
const messageHistory = sdk.messagesList;ArthurSDK.toolRegistry
Access to the ToolRegistry instance used by the SDK. This provides access to all registered tools and their configurations.
Example
const weatherTool = sdk.toolRegistry.getTool('weather-api');
console.log('Weather tool:', weatherTool);Type
ToolRegistry - The ToolRegistry instance containing all registered tools and their execution logic.
More Examples
Tool inspection and debugging
Examine available tools and their configurations.
const allTools = sdk.toolRegistry.getAllTools();
const toolNames = sdk.toolRegistry.getToolNames();
console.log('Available tools:', toolNames);
console.log('Tool configurations:', allTools);Runtime tool registration
Add new tools after SDK initialization.
// Register additional tools at runtime
sdk.toolRegistry.registerTool({
name: 'custom-calculator',
description: 'Performs custom calculations',
parameters: { expression: 'string' },
execute: async (args) => {
return { result: eval(args.expression) }; // Use carefully in production!
},
});Tool execution verification
Check if required tools are available before operations.
const requiredTools = ['weather-api', 'email-sender'];
const missingTools = requiredTools.filter(
(toolName) => !sdk.toolRegistry.getTool(toolName),
);
if (missingTools.length > 0) {
console.error('Missing required tools:', missingTools);
}ArthurSDK.eventSource
Access to the current EventSource instance used for server-sent events. This property is null when not connected to the interaction loop. The EventSource connection uses both session ID and request key for authentication.
Example
if (sdk.eventSource) {
console.log('Connection state:', sdk.eventSource.readyState);
} else {
console.log('Not connected to server events');
}Type
EventSource | null - The EventSource instance for receiving real-time updates from the server, or null when disconnected.
More Examples
Connection state monitoring
Monitor the real-time connection status.
if (sdk.eventSource) {
const states = {
[EventSource.CONNECTING]: 'Connecting',
[EventSource.OPEN]: 'Connected',
[EventSource.CLOSED]: 'Disconnected',
};
console.log('EventSource state:', states[sdk.eventSource.readyState]);
} else {
console.log('EventSource not initialized');
}Connection management
Manually manage connection lifecycle if needed.
// Check if connected before attempting operations
const isConnected = sdk.eventSource?.readyState === EventSource.OPEN;
if (isConnected) {
console.log('Ready to receive real-time updates');
} else {
console.log('Connection not available for real-time updates');
}
// Force close connection (usually handled by SDK)
if (sdk.eventSource) {
sdk.eventSource.close();
}Debug connection issues
Add custom event listeners for debugging connection problems.
if (sdk.eventSource) {
sdk.eventSource.addEventListener('error', (event) => {
console.error('EventSource error:', event);
});
sdk.eventSource.addEventListener('open', () => {
console.log('EventSource connection opened');
});
}
### ArthurSDK.onInteractionLoopComplete
Set a callback function that executes when the interaction loop ends (conversation completes or connection closes).
#### Example
```typescript
sdk.onInteractionLoopComplete = () => {
console.log('Conversation completed');
};Syntax
// Set the callback function
sdk.onInteractionLoopComplete = callback;
// Get the current callback function
const currentCallback = sdk.onInteractionLoopComplete;Parameters
callback(() => void): Function that takes no parameters and is called when the interaction loop completes.
Return Type
void - This is a setter property. The callback function itself should not return anything.
More Examples
UI cleanup and state reset
Clean up loading states and reset UI when conversation ends.
sdk.onInteractionLoopComplete = () => {
// Hide loading indicators
hideLoadingSpinner();
// Reset UI state
setConversationStatus('completed');
// Enable new conversation button
enableNewConversationButton();
};Logging and analytics
Track conversation completion for analytics and debugging.
sdk.onInteractionLoopComplete = () => {
// Log conversation metrics
console.log('Conversation ended at:', new Date().toISOString());
// Send analytics event
analytics.track('conversation_completed', {
duration: Date.now() - conversationStartTime,
messageCount: sdk.messages.length,
});
// Clean up resources
cleanupConversationResources();
};Error handling and reconnection
Handle unexpected disconnections and implement retry logic.
sdk.onInteractionLoopComplete = () => {
if (sdk.eventSource?.readyState === EventSource.CLOSED) {
console.log('Connection closed unexpectedly');
// Attempt to reconnect after delay
setTimeout(() => {
if (confirmReconnection()) {
sdk.startInteractionLoopListener();
}
}, 5000);
}
};ArthurSDK.onSessionIdChanged
Set a callback function that executes whenever the session ID changes, typically when starting a new conversation.
Example
sdk.onSessionIdChanged = (sessionId) => {
console.log('Session ID changed:', sessionId);
};Syntax
// Set the callback function
sdk.onSessionIdChanged = callback;
// Get the current callback function
const currentCallback = sdk.onSessionIdChanged;Parameters
callback((sessionId: string | undefined) => void): Function that receives the new session ID as a parameter, orundefinedwhen the session is cleared.
Return Type
void - This is a setter property. The callback function itself should not return anything.
More Examples
Session persistence
Save session IDs for conversation continuity across app restarts.
sdk.onSessionIdChanged = (sessionId) => {
// Save to localStorage for persistence
localStorage.setItem('arthur-session-id', sessionId);
// Update application state
setCurrentSessionId(sessionId);
// Log session change
console.log('New session started:', sessionId);
};Multi-user session management
Handle session changes in multi-user applications.
sdk.onSessionIdChanged = (sessionId) => {
// Associate session with current user
const userId = getCurrentUserId();
saveUserSession(userId, sessionId);
// Update session metadata
updateSessionMetadata({
sessionId,
userId,
startedAt: new Date().toISOString(),
status: 'active',
});
};Session analytics and monitoring
Track session creation for monitoring and analytics.
sdk.onSessionIdChanged = (sessionId) => {
// Send analytics event
analytics.track('session_started', {
sessionId,
timestamp: Date.now(),
userAgent: navigator.userAgent,
});
// Update monitoring dashboards
sessionMonitor.recordNewSession(sessionId);
// Set up session timeout monitoring
setupSessionTimeout(sessionId);
};SDK Initialization and Usage Examples
// Step 4: Initialize SDK with all options
const sdk = new ArthurSDK({
userId: 'user-123',
clientId: 'client-456',
interactURL: 'https://api.example.com/api/message',
interactionEventsURL: 'https://api.example.com/api/interactionloop',
agentConfig: {
agent: 'armor',
toolRegistry, // Previously created with tools
boothRegistry, // Previously created with booths
},
sessionId: 'existing-session-id', // Optional: resume session
});
// Step 5: Set up event handlers
sdk.onMessagesReceived = (messages) => {
// Update your UI with new messages
messages.forEach((message) => {
if (message.type === 'message') {
console.log(`${message.role}: ${message.content}`);
} else if (message.type === 'tool_call') {
console.log(`Tool call: ${message.content.name}`);
}
});
};
sdk.onInteractionLoopComplete = () => {
// Conversation ended, update UI
console.log('Conversation completed');
hideLoadingSpinner();
};
sdk.onSessionIdChanged = (sessionId) => {
// Save session for later use
localStorage.setItem('arthur-session', sessionId);
};
// Step 6: Configure authentication/headers
sdk.setAdditionalHeaders({
Authorization: 'Bearer ' + getAuthToken(),
'X-User-Context': 'web-app',
});
// Step 7: Send messages and listen for responses
await sdk.sendMessageAndListen('Hello, I need help with weather and email');
// Alternative: Send without listening (for fire-and-forget)
await sdk.sendMessage('Just logging this message');
// Manual control of interaction loop
await sdk.sendMessage('Setup message');
sdk.startInteractionLoopListener(); // Start listening manuallyArthurSyncSDK
The ArthurSyncSDK provides a simplified interface for synchronous interactions with individual booths without the full agent interaction loop. This SDK is designed for single, direct booth calls that return immediate responses.
Key Differences from ArthurSDK
- Synchronous: Single request-response pattern, no event streaming
- Stateless: No session management or conversation history
- Direct booth interaction: Works with both predefined booth IDs and custom booth configurations
- Simpler API: Just one main method (
interact()) for all operations - Arbitrary URLs: Full control over API endpoints
Constructor Options
Required Parameters
syncApiUrl(string): Absolute URL for the sync API endpoint (e.g., 'https://api.example.com/interact-sync')
Optional Parameters
headers(Record<string, string>): Initial headers to include in all requests
Constructor Examples
Basic initialization
import { ArthurSyncSDK } from 'arthur-sdk';
const syncSDK = new ArthurSyncSDK({
syncApiUrl: 'https://api.example.com/interact-sync',
});With authentication headers
const syncSDK = new ArthurSyncSDK({
syncApiUrl: 'https://api.example.com/interact-sync',
headers: {
Authorization: 'Bearer your-token-here',
'X-Client-Version': '1.0.0',
},
});ArthurSyncSDK.interact()
Send a message to a booth and receive an immediate response.
Example
const response = await syncSDK.interact({
message: 'What are the current flagged numbers?',
booth: 'call-reputation-insights-booth',
clientId: 'arthur_cli',
});
if (response.success) {
console.log('Response:', response.output);
} else {
console.error('Error:', response.error);
}Syntax
const result = await syncSDK.interact({
message,
booth,
clientId,
toolmodules? // Optional
});Parameters
message(string | ResponseInputItem[]): The message to send to the boothbooth(BoothId | BoothConfig): Either a predefined booth ID or custom booth configurationclientId(ClientId): Client identifier ('arkon' | 'arthur_cli' | 'armor_dial')toolmodules(ToolModule[], optional): Additional tools (only with custom BoothConfig)
Available Booth IDs
'base-booth': General assistance for ARMOR platform'call-reputation-insights-booth': Call reputation analysis and insights'concurrent-session-booth': Session management and retrieval'data-analytics-booth': Data analytics and reporting'onboarding-booth': User onboarding assistance
Return Type
interface SyncResponse {
success: boolean;
output?: ResponseInputItem[];
error?: string;
}More Examples
Using predefined booth IDs
// Simple string message
const response1 = await syncSDK.interact({
message: 'Help me with onboarding',
booth: 'onboarding-booth',
clientId: 'arthur_cli',
});
// Complex message array
const response2 = await syncSDK.interact({
message: [
{ type: 'text', text: 'Analyze this data: ' },
{ type: 'text', text: 'Sales data for Q1 2024' },
],
booth: 'data-analytics-booth',
clientId: 'arthur_cli',
});Using custom booth configuration
const customBooth = {
id: 'custom-support',
name: 'Custom Support Booth',
description: 'Provides specialized customer support',
context:
'You are a helpful customer support agent specializing in technical issues.',
tools: ['knowledge-search', 'ticket-creator'],
};
const response = await syncSDK.interact({
message: 'I need help with my account',
booth: customBooth,
clientId: 'arthur_cli',
});Using custom booth with toolmodules
const customBooth = {
id: 'analytics-booth',
name: 'Analytics Booth',
description: 'Data analysis booth',
context: 'You are a data analyst with access to specialized tools.',
tools: ['data-processor'],
};
const toolmodules = [
{
name: 'data-processor',
description: 'Processes raw data',
parameters: {
type: 'object',
properties: {
data: { type: 'string' },
format: { type: 'string', enum: ['json', 'csv'] },
},
},
global: false,
execute: async (args) => ({
processed: true,
records: parseData(args.data, args.format),
}),
},
];
const response = await syncSDK.interact({
message: 'Process this CSV data: name,age,city\\nJohn,25,NYC',
booth: customBooth,
clientId: 'arthur_cli',
toolmodules,
});Header Management
ArthurSyncSDK.setHeaders()
Set multiple custom headers for all requests.
syncSDK.setHeaders({
Authorization: 'Bearer new-token',
'X-Request-Source': 'mobile-app',
'Content-Language': 'en-US',
});ArthurSyncSDK.setHeader()
Set a single header.
syncSDK.setHeader('Authorization', 'Bearer updated-token');ArthurSyncSDK.removeHeader()
Remove a specific header.
syncSDK.removeHeader('X-Debug-Mode');ArthurSyncSDK.getHeaders()
Get all current headers.
const currentHeaders = syncSDK.getHeaders();
console.log('Current headers:', currentHeaders);Error Handling
The sync SDK handles errors gracefully and returns them in the response:
const response = await syncSDK.interact({
message: 'Test message',
booth: 'invalid-booth-id',
clientId: 'arthur_cli',
});
if (!response.success) {
switch (response.error) {
case 'message is required':
console.error('Message cannot be empty');
break;
case 'Client arthur_cli does not have access to booth invalid-booth-id':
console.error('Access denied to booth');
break;
default:
console.error('Unknown error:', response.error);
}
}Common Error Messages
"message is required"- Missing or empty message"clientId is required"- Missing client ID"Client {clientId} does not have access to booth {boothId}"- Access denied"booth must be either a valid BOOTH_ID (...) or a BoothConfig object"- Invalid booth"toolmodules can only be provided when booth is a BoothConfig object"- Invalid tool usage"toolmodules[{index}] ({toolName}) is not compatible with this booth configuration"- Incompatible tool
Complete Usage Example
import { ArthurSyncSDK, BoothConfig, ToolModule } from 'arthur-sdk';
// Initialize SDK
const syncSDK = new ArthurSyncSDK({
syncApiUrl: 'https://api.example.com/interact-sync',
headers: {
Authorization: 'Bearer your-token',
'X-Client-App': 'my-app',
},
});
// Example 1: Simple booth interaction
const quickResponse = await syncSDK.interact({
message: 'What are the current flagged phone numbers?',
booth: 'call-reputation-insights-booth',
clientId: 'arthur_cli',
});
if (quickResponse.success) {
console.log('Flagged numbers:', quickResponse.output);
}
// Example 2: Custom booth with tools
const customBooth: BoothConfig = {
id: 'sales-analyzer',
name: 'Sales Data Analyzer',
description: 'Analyzes sales performance data',
context:
'You are a sales analyst. Use the data-processor tool to analyze sales data and provide insights.',
tools: ['data-processor', 'chart-generator'],
};
const customTools: ToolModule[] = [
{
name: 'data-processor',
description: 'Processes sales data',
parameters: {
type: 'object',
properties: {
period: { type: 'string' },
metrics: { type: 'array', items: { type: 'string' } },
},
},
global: false,
execute: async ({ period, metrics }) => ({
summary: `Processed ${metrics.length} metrics for ${period}`,
data: generateAnalysis(period, metrics),
}),
},
];
const analysisResponse = await syncSDK.interact({
message:
'Analyze our Q1 sales performance focusing on revenue and conversion rates',
booth: customBooth,
clientId: 'arthur_cli',
toolmodules: customTools,
});
if (analysisResponse.success) {
console.log('Sales analysis:', analysisResponse.output);
} else {
console.error('Analysis failed:', analysisResponse.error);
}ToolRegistry
Manages tool configurations and execution. Tools should be registered first as they are referenced by booths.
ToolRegistry.registerTool()
Register a single tool in the registry. If a tool with the same name already exists, it will be overwritten.
Example
toolRegistry.registerTool({
name: 'weather-api',
description: 'Get weather information',
parameters: { location: 'string' },
execute: async ({ location }) => {
return { temperature: 75, conditions: 'sunny' };
},
});Syntax
toolRegistry.registerTool(tool);Parameters
tool(ToolModule): Tool configuration object containing name, description, parameters, and optional execute function
Return Type
void - This method does not return anything.
ToolRegistry.registerTools()
Register multiple tools at once.
Example
const tools = [
{
name: 'calculator',
description: 'Mathematical calculations',
parameters: { operation: 'string', a: 'number', b: 'number' },
global: true,
},
{
name: 'email-sender',
description: 'Send emails',
parameters: { to: 'string', subject: 'string', body: 'string' },
},
];
toolRegistry.registerTools(tools);Syntax
toolRegistry.registerTools(tools);Parameters
tools(ToolModule[]): Array of tool configuration objects to register
Return Type
void - This method does not return anything.
ToolRegistry.getTool()
Retrieve a tool by its name.
Example
const weatherTool = toolRegistry.getTool('weather-api');
if (weatherTool) {
console.log('Tool found:', weatherTool.description);
}Syntax
const tool = toolRegistry.getTool(toolName);Parameters
toolName(string): The name of the tool to retrieve
Return Type
ToolModule | undefined - The tool configuration if found, undefined otherwise.
ToolRegistry.getAllTools()
Returns all registered tools as a record object indexed by tool names.
Example
const allTools = toolRegistry.getAllTools();
Object.keys(allTools).forEach((toolName) => {
console.log(`Tool: ${toolName}`, allTools[toolName]);
});Syntax
const tools = toolRegistry.getAllTools();Parameters
None.
Return Type
Record<string, ToolModule> - Object containing all tools indexed by their names.
ToolRegistry.getAllToolsWithoutExecute()
Returns all tools without their execute functions, useful for serialization when sending to the server.
Example
const toolsForServer = toolRegistry.getAllToolsWithoutExecute();
// Safe to JSON.stringify - no functions includedSyntax
const tools = toolRegistry.getAllToolsWithoutExecute();Parameters
None.
Return Type
Omit<ToolModule, 'execute'>[] - Array of tools without execute functions.
ToolRegistry.unregisterTool()
Remove a tool from the registry by its name.
Example
try {
toolRegistry.unregisterTool('deprecated-tool');
console.log('Tool removed successfully');
} catch (error) {
console.error('Tool not found:', error.message);
}Syntax
toolRegistry.unregisterTool(toolName);Parameters
toolName(string): The name of the tool to remove
Return Type
void - This method does not return anything. Throws an error if the tool doesn't exist.
Tool Registration Examples
const toolRegistry = new ToolRegistry();
// Step 1: Register global tools (available to all booths)
toolRegistry.registerTool({
name: 'calculator',
description: 'Performs mathematical calculations',
parameters: {
operation: 'string',
a: 'number',
b: 'number',
},
global: true, // Available to ALL booths
execute: async ({ operation, a, b }) => {
switch (operation) {
case 'add':
return { result: a + b };
case 'subtract':
return { result: a - b };
case 'multiply':
return { result: a * b };
case 'divide':
return { result: a / b };
default:
throw new Error('Unknown operation');
}
},
});
// Step 2: Register booth-specific tools
toolRegistry.registerTool({
name: 'weather-api',
description: 'Get weather information for a location',
parameters: {
location: 'string',
units: 'string',
},
// No global flag = booth-specific tool
execute: async ({ location, units = 'metric' }) => {
// Weather API implementation
const response = await fetch(
`/api/weather?location=${location}&units=${units}`,
);
return await response.json();
},
});
toolRegistry.registerTool({
name: 'send-email',
description: 'Send an email message',
parameters: {
to: 'string',
subject: 'string',
body: 'string',
},
global: false, // Explicitly booth-specific
execute: async ({ to, subject, body }) => {
// Email sending implementation
return { sent: true, messageId: 'msg-' + Date.now() };
},
});BoothRegistry
Manages booth configurations within the system. Booths should be registered after tools so they can reference existing tools.
BoothRegistry.registerBooth()
Register a single booth configuration in the registry.
Example
boothRegistry.registerBooth({
id: 'weather-assistant',
name: 'Weather Assistant',
description: 'Provides weather information and forecasts',
context: 'You are a helpful weather assistant.',
tools: ['weather-api'],
});Syntax
boothRegistry.registerBooth(boothConfig);Parameters
boothConfig(BoothConfig): Booth configuration object containing id, role, description, and optional tools array
Return Type
void - This method does not return anything.
BoothRegistry.registerBooths()
Register multiple booth configurations at once.
Example
const booths = [
{
id: 'weather-assistant',
name: 'Weather Assistant',
description: 'Weather information',
context: 'You help with weather queries.',
},
{
id: 'email-assistant',
name: 'Email Assistant',
description: 'Email management',
context: 'You help with email tasks.',
tools: ['send-email'],
},
];
boothRegistry.registerBooths(booths);Syntax
boothRegistry.registerBooths(boothConfigs);Parameters
boothConfigs(BoothConfig[]): Array of booth configuration objects to register
Return Type
void - This method does not return anything.
BoothRegistry.getBoothById()
Retrieve a booth configuration by its unique ID.
Example
const weatherBooth = boothRegistry.getBoothById('weather-assistant');
if (weatherBooth) {
console.log('Booth found:', weatherBooth.name);
}Syntax
const booth = boothRegistry.getBoothById(boothId);Parameters
boothId(string): The unique identifier of the booth to retrieve
Return Type
BoothConfig | undefined - The booth configuration if found, undefined otherwise.
BoothRegistry.getAllBooths()
Returns all registered booth configurations as an array.
Example
const allBooths = boothRegistry.getAllBooths();
allBooths.forEach((booth) => {
console.log(`Booth: ${booth.name} - ${booth.description}`);
});Syntax
const booths = boothRegistry.getAllBooths();Parameters
None.
Return Type
BoothConfig[] - Array containing all registered booth configurations.
BoothRegistry.unregisterBooth()
Remove a booth from the registry by its ID.
Example
const wasRemoved = boothRegistry.unregisterBooth('deprecated-booth');
if (wasRemoved) {
console.log('Booth removed successfully');
} else {
console.log('Booth not found');
}Syntax
const success = boothRegistry.unregisterBooth(boothId);Parameters
boothId(string): The unique identifier of the booth to remove
Return Type
boolean - True if the booth was found and removed, false otherwise.
BoothRegistry.toArray()
Get all booth configurations as an array, useful for serialization.
Example
const boothArray = boothRegistry.toArray();
// Same as getAllBooths(), provided for consistencySyntax
const booths = boothRegistry.toArray();Parameters
None.
Return Type
BoothConfig[] - Array containing all registered booth configurations.
Booth Registration Examples
const boothRegistry = new BoothRegistry();
// Step 3: Register booths with tool access control
boothRegistry.registerBooth({
id: 'weather-assistant',
name: 'Weather Assistant',
description: 'Provides weather information and forecasts',
context:
'You are a helpful weather assistant. Use the weather-api tool to get current conditions and forecasts.',
tools: ['weather-api'], // Has access to weather-api + all global tools (calculator)
});
boothRegistry.registerBooth({
id: 'email-assistant',
name: 'Email Assistant',
description: 'Helps with email composition and sending',
context:
'You are an email assistant. Help users compose and send emails using the send-email tool.',
tools: ['send-email'], // Has access to send-email + all global tools (calculator)
});
boothRegistry.registerBooth({
id: 'office-assistant',
name: 'General Office Assistant',
description: 'Multi-purpose assistant for office tasks',
context:
'You are a general office assistant with access to multiple tools. Help with calculations, weather, and emails.',
tools: ['weather-api', 'send-email'], // Has access to both tools + all global tools
});
boothRegistry.registerBooth({
id: 'math-tutor',
name: 'Math Tutor',
description: 'Helps with mathematical problems',
context:
'You are a math tutor. Help students solve problems using the calculator tool.',
// No tools array = only has access to global tools (calculator)
});Understanding Tool Access
The booth examples above demonstrate the flexible tool access system:
weather-assistant
- Tools Array:
['weather-api'] - Available Tools: weather-api + calculator (global)
email-assistant
- Tools Array:
['send-email'] - Available Tools: send-email + calculator (global)
office-assistant
- Tools Array:
['weather-api', 'send-email'] - Available Tools: weather-api + send-email + calculator (global)
math-tutor
- Tools Array: (none)
- Available Tools: calculator (global only)
Booth and Tool Relationships
The Arthur SDK provides a flexible system for controlling which tools are available to which booths. There are two ways to configure tool access:
Global Tools
Tools marked with global: true are available to all booths in the system. These are typically utility functions or common operations that any booth might need.
// Global tool - available to all booths
toolRegistry.registerTool({
name: 'calculator',
description: 'Performs mathematical calculations',
parameters: { operation: 'string', a: 'number', b: 'number' },
global: true, // This tool is available to all booths
execute: async ({ operation, a, b }) => {
switch (operation) {
case 'add':
return { result: a + b };
case 'subtract':
return { result: a - b };
case 'multiply':
return { result: a * b };
case 'divide':
return { result: a / b };
default:
throw new Error('Unknown operation');
}
},
});Booth-Specific Tools
Tools without the global flag (or with global: false) are only available to booths that explicitly include them in their tools array. This provides fine-grained control over tool access.
// Booth-specific tools
toolRegistry.registerTool({
name: 'weather-api',
description: 'Get weather information',
parameters: { location: 'string' },
// No global flag = booth-specific tool
execute: async ({ location }) => {
// Weather API implementation
return { temperature: 72, conditions: 'sunny' };
},
});
toolRegistry.registerTool({
name: 'send-email',
description: 'Send an email',
parameters: { to: 'string', subject: 'string', body: 'string' },
global: false, // Explicitly not global
execute: async ({ to, subject, body }) => {
// Email sending implementation
return { sent: true, messageId: 'msg-123' };
},
});
// Register booths with specific tool access
boothRegistry.registerBooth({
id: 'weather-assistant',
name: 'Weather Assistant',
description: 'Provides weather information',
context:
'You are a weather assistant. Use the weather-api tool to get current conditions.',
tools: ['weather-api'], // Only has access to weather-api tool (plus global tools)
});
boothRegistry.registerBooth({
id: 'email-assistant',
name: 'Email Assistant',
description: 'Helps with email management',
context:
'You are an email assistant. You can send emails using the send-email tool.',
tools: ['send-email'], // Only has access to send-email tool (plus global tools)
});
boothRegistry.registerBooth({
id: 'office-assistant',
name: 'Office Assistant',
description: 'General office assistant',
context: 'You are a general office assistant with access to multiple tools.',
tools: ['weather-api', 'send-email'], // Has access to both tools (plus global tools)
});Tool Access Summary
For any given booth, the available tools are:
- All global tools (
global: true) - Tools listed in the booth's
toolsarray
// Example: What tools are available to each booth?
// weather-assistant booth can use:
// - calculator (global: true)
// - weather-api (in booth's tools array)
// email-assistant booth can use:
// - calculator (global: true)
// - send-email (in booth's tools array)
// office-assistant booth can use:
// - calculator (global: true)
// - weather-api (in booth's tools array)
// - send-email (in booth's tools array)Best Practices
- Use global tools for utilities: Math, text processing, common operations
- Use booth-specific tools for specialized functions: API calls, domain-specific operations
- Group related booths with similar tool access: Customer service booths might share CRM tools
- Consider security implications: Don't give sensitive tools global access
Advanced Usage Patterns
Message Handling with Types
import { ConversationMessage, isConversationMessageUserText } from 'arthur-sdk';
sdk.onMessagesReceived = (messages: ConversationMessage[]) => {
messages.forEach((message) => {
if (isConversationMessageUserText(message)) {
console.log('User/Assistant message:', message.content);
} else if (message.type === 'tool_call') {
console.log('Tool call:', message.content.name);
// Show loading indicator for tool execution
}
});
};Dynamic Header Management
// Update headers based on authentication state
function updateAuthHeaders(token: string) {
sdk.setHeader('Authorization', `Bearer ${token}`);
}
// Remove authentication when logging out
function clearAuth() {
sdk.removeHeader('Authorization');
}
// Add request tracking
sdk.setHeader('X-Request-ID', generateRequestId());Session Persistence
// Load existing session on app startup
const savedSessionId = localStorage.getItem('arthur-session-id');
const sdk = new ArthurSDK({
userId: getCurrentUserId(),
clientId: getClientId(),
interactURL: 'https://api.example.com/api/message',
interactionEventsURL: 'https://api.example.com/api/interactionloop',
agentConfig: {
agent: 'armor',
toolRegistry,
boothRegistry,
},
sessionId: savedSessionId, // Resume if available
});
// Save new session IDs automatically
sdk.onSessionIdChanged = (sessionId) => {
localStorage.setItem('arthur-session-id', sessionId);
};Types
The SDK exports comprehensive TypeScript types for all interfaces:
Core Types
ConversationMessage
Union type for all conversation messages
ConversationMessageUserText
Text messages from user/assistant
ConversationMessageToolCall
Tool call messages
MessageResponse
Response from message API
IncomingMessage
Union type for all incoming server messages
BoothConfig Structure
Required Properties
id(string): Unique booth identifiername(string): Display name for the boothdescription(string): Brief description of the booth's purposecontext(string): System context/prompt for the booth
Optional Properties
tools(string[]): Array of tool names available to this boothplugins(string[]): Array of plugin names (future use)
ToolModule Structure
Required Properties
name(string): Unique tool identifierdescription(string): Description of what the tool doesparameters(Record<string, any>): Object describing the tool's parameters
Optional Properties
execute(Function): Function that executes the tool logicglobal(boolean): If true, tool is available to all booths
Message Types
IncomingMessageBoothsCommand
Booth command messages
IncomingMessageExecuteTools
Tool execution requests
IncomingMessageToolCall
Tool call notifications
IncomingMessageToolCallResult
Tool call results
IncomingMessageFunctionCallOutput
Function call outputs
Type Guards
isConversationMessageToolCall(message: any): boolean
Check if message is a tool call
isConversationMessageUserText(message: any): boolean
Check if message is user/assistant text
isResponseInputItem(message: any): boolean
Check if message is a response input item
Session Management Details
Understanding how the Arthur server handles sessions and request keys is crucial for proper conversation management:
Server Session and Authentication Behavior
The Arthur SDK now uses a dual-token authentication system with both session IDs and request keys:
Session IDs (sessionId):
- Persist throughout the entire conversation lifecycle
- Provide conversation continuity and context
- Only cleared when explicitly calling
clearSession()or starting a completely new conversation
Request Keys (requestKey):
- Single-use authentication tokens generated fresh on each interaction
- Required for all streaming (EventSource) connections as a query parameter
- Consumed and cleared immediately after EventSource creation
- Cannot be reused once consumed by the server
When sending a message without a session ID:
- A new session is automatically created on the server
- The session ID persists throughout the entire conversation lifecycle
- Sessions are only cleared when explicitly calling
clearSession()or when starting a completely new conversation
Important: Session IDs now persist between messages. Once a session is established, it continues throughout the conversation. The server only creates new sessions when no session ID is provided in the initial request.
SDK Session Responsibilities
The SDK handles the runtime session management automatically, but developers are responsible for persistence:
What the SDK does:
- Automatically stores the session ID and request key received from the server
- Includes the current session ID in subsequent requests
- Includes the request key in EventSource URLs for streaming connections
- Clears request keys after use (single-use tokens)
- Triggers
onSessionIdChangedcallback when sessions change - Provides access to the current session via internal state
What developers must do:
- Store sessions locally if persistence across app restarts is needed
- Remove sessions from the SDK to intentionally start fresh conversations
Session Persistence Examples
Save session for persistence:
sdk.onSessionIdChanged = (sessionId) => {
// Store session for later restoration
localStorage.setItem('arthur-session-id', sessionId);
console.log('Session saved:', sessionId);
};Restore previous session:
const savedSessionId = localStorage.getItem('arthur-session-id');
const sdk = new ArthurSDK({
userId: 'user-123',
clientId: 'client-456',
interactURL: 'https://api.example.com/api/message',
interactionEventsURL: 'https://api.example.com/api/interactionloop',
agentConfig: {
agent: 'armor',
// Include your registries here
},
sessionId: savedSessionId, // Resume previous conversation
});Purge conversation (start fresh):
// Method 1: Use clearSession() - RECOMMENDED
sdk.clearSession(); // Clears session and messages, triggers callback
// Next sendMessage() call will create a new session
// Method 2: Clear session but keep messages visible
sdk.clearSession(false); // Clear session but keep messages in UI
// Next sendMessage() call will create a new session
// Method 3: Manual approach (not recommended)
localStorage.removeItem('arthur-session-id');
const newSdk = new ArthurSDK({
userId: 'user-123',
clientId: 'client-456',
interactURL: 'https://api.example.com/api/message',
interactionEventsURL: 'https://api.example.com/api/interactionloop',
agentConfig: {
agent: 'armor',
// Include your registries here
},
// No sessionId = fresh session will be created
});Important Session Implications
Starting Fresh Conversations:
- Use
sdk.clearSession()to clear the current session (recommended) - Or simply don't provide a
sessionIdwhen initializing the SDK - Or remove the stored session ID from your persistence layer
- The server will automatically create a new session on the next message
Maintaining Conversation History:
- Always save the session ID when
onSessionIdChangedis triggered - Provide the saved session ID when reinitializing the SDK
- The server will continue the existing conversation context
- Session IDs persist throughout the conversation - no need to constantly re-save them
Multi-User Applications:
// Store sessions per user
sdk.onSessionIdChanged = (sessionId) => {
const userId = getCurrentUserId();
if (sessionId === undefined) {
// Session cleared - remove from storage
localStorage.removeItem(`arthur-session-${userId}`);
} else {
// New session - store it
localStorage.setItem(`arthur-session-${userId}`, sessionId);
}
};
// Restore user-specific session
const userId = getCurrentUserId();
const userSession = localStorage.getItem(`arthur-session-${userId}`);
const sdk = new ArthurSDK({
userId,
clientId: 'multi-user-client',
interactURL: 'https://api.example.com/api/message',
interactionEventsURL: 'https://api.example.com/api/interactionloop',
agentConfig: {
agent: 'armor',
// Include your registries here
},
sessionId: userSession,
});Error Handling
The SDK includes built-in error handling for common scenarios:
try {
await sdk.sendMessageAndListen('Hello');
} catch (error) {
if (error.message.includes('No session ID found')) {
// Handle session initialization error
console.log('Please establish a session first');
} else if (
error.message.includes('Cannot send message while EventSource is active')
) {
// Handle EventSource constraint error
console.log('Close the EventSource connection first with clearSession()');
} else if (
error.message.includes('Tool') &&
error.message.includes('could not be found')
) {
// Handle missing tool error
console.log('Required tool is not registered');
}
}Common Error Messages
Session and Authentication Errors:
"No session ID found"- Trying to start interaction loop without an established session"Cannot send message while EventSource is active. Close the EventSource first with clearSession() or use sendMessageAndListen() for interactive sessions."- Attempting to callsendMessage()while EventSource is connected"No requestKey available for EventSource connection"- Missing request key when starting streaming connection
Tool-Related Errors:
"Tool [toolName] could not be found"- Referenced tool is not registered in the ToolRegistry
Network and Server Errors:
- HTTP 401/403 responses - Authentication issues (check headers and tokens)
- EventSource connection failures - Network issues or server unavailability
- Invalid request key errors - Single-use token already consumed or expired
Browser Compatibility
The SDK uses modern browser APIs:
- EventSource: For server-sent events (widely supported)
- fetch: For HTTP requests (polyfill available for older browsers)
- URL constructor: For URL manipulation (supported in all modern browsers)
Ensure your target browsers support these APIs or include appropriate polyfills.
License
ISC