
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
arthur-sdk
Advanced tools
Client-side SDK for interacting with Arthur Intelligence and Booths system.
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.
npm install arthur-sdk
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
interruptURL: 'https://your-api.com/api/interrupt', // 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!');
The main SDK class for interacting with Arthur Intelligence.
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')interruptURL (string): Absolute URL for interrupting interactions (e.g., 'https://api.example.com/api/interrupt')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 configurationsOptional Parameters
sessionId (string): Existing session ID to resume a previous conversation
undefinedBasic 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',
interruptURL: 'https://api.example.com/api/interrupt',
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',
interruptURL: 'https://api.example.com/api/interrupt',
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',
interruptURL: 'https://api.example.com/api/interrupt',
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',
interruptURL: 'http://localhost:8080/api/interrupt',
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',
interruptURL: process.env.ARTHUR_API_URL + '/api/interrupt',
agentConfig: {
agent: 'armor',
toolRegistry: productionToolRegistry,
boothRegistry: productionBoothRegistry,
},
sessionId: getStoredSessionId(), // Optional: restore session
});
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.
await sdk.sendMessage('Hello, Arthur!');
// Send a simple text message
await sdk.sendMessage(message);
// Send complex message array
await sdk.sendMessage(messages);
message (string | ResponseInputItem[]): The message to send. Can be a simple string or an array of ResponseInputItem objects for complex message structures.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.).
"Cannot send message while EventSource is active. Close the EventSource first with clearSession() or use sendMessageAndListen() for interactive sessions."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);
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.
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.
// First send a message to establish session
await sdk.sendMessage('Initial message');
// Then start listening manually
sdk.startInteractionLoopListener();
// Start listening (no parameters)
sdk.startInteractionLoopListener();
None. This method requires that both a session ID and request key have already been established through a previous sendMessage or sendMessageAndListen call.
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.
// 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');
};
Send a message and automatically start listening for responses via the interaction loop. This is the most commonly used method for interactive conversations.
await sdk.sendMessageAndListen('Hello, I need help with my tasks');
// Send text message and start listening
await sdk.sendMessageAndListen(message);
// Send complex message array and start listening
await sdk.sendMessageAndListen(messages);
message (string | ResponseInputItem[]): The message to send. Can be a simple string or an array of ResponseInputItem objects for complex message structures.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.
// 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');
Set a callback function to handle incoming messages and message updates in real-time.
sdk.onMessagesReceived = (messages) => {
console.log('New messages:', messages);
};
// Set the callback function
sdk.onMessagesReceived = callback;
// Get the current callback function
const currentCallback = sdk.onMessagesReceived;
callback ((messages: ConversationMessage[]) => void): Function that receives the complete array of conversation messages whenever messages are received or updated.void - This is a setter property. The callback function itself should not return anything.
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();
}
};
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
}
}
Custom Messages Messages with flexible, server-defined content for specialized use cases like forms, cards, or interactive components.
// CustomConversationMessage
{
type: 'custom_message',
role: 'assistant', // Can also be 'user', 'developer', or 'Unknown'
content: {
// Flexible content structure - can be any valid JSON
// Example: interactive form
formType: 'contact',
fields: [
{ name: 'email', label: 'Email', type: 'email', required: true },
{ name: 'message', label: 'Message', type: 'textarea' }
]
},
id: 'custom-msg-123',
time: '2024-01-15T10:30:00Z',
loading: false
}
When to use custom messages:
Handling custom messages:
sdk.onMessagesReceived = (messages) => {
messages.forEach((message) => {
if (message.type === 'custom_message') {
// Handle custom message based on content structure
console.log('Custom message received:', message.content);
// Example: render a custom form
if (message.content.formType === 'contact') {
renderContactForm(message.content.fields);
}
}
});
};
Important notes:
content field can contain any valid JSON structuremessage.type === 'custom_message' to identifyloading property can indicate if the custom content is still being preparedHandling 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);
}
} else if (message.type === 'custom_message') {
// Handle custom message
console.log('Custom message:', message.content);
handleCustomMessage(message);
}
});
};
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' }
// ]
Gracefully interrupt an active interaction session. This method allows you to stop ongoing LLM processing, tool executions, or streaming responses without losing conversation context. The interruption will trigger turn-end events that provide visibility into the interruption reason.
// Interrupt the current session
const result = await sdk.interrupt();
if (result.success) {
console.log('Successfully interrupted:', result.message);
} else {
console.error('Failed to interrupt:', result.error);
}
const result = await sdk.interrupt();
None. The method uses the internal session ID to identify which session to interrupt.
interface InterruptResponse {
success: boolean;
message?: string; // Success message from server
error?: string; // Error message if interruption failed
}
Common use cases for the interrupt functionality:
{ success: false, error: 'No active session to interrupt' }Basic interruption with user feedback Handle user-initiated stop requests with UI updates.
// User clicks "Stop" button
const handleStopClick = async () => {
showLoadingSpinner('Stopping...');
const result = await sdk.interrupt();
hideLoadingSpinner();
if (result.success) {
showNotification('Operation stopped successfully', 'success');
} else {
showNotification(`Failed to stop: ${result.error}`, 'error');
}
};
Conditional interruption based on timeout Automatically interrupt long-running operations.
// Set up timeout-based interruption
let interactionTimeout: NodeJS.Timeout;
// Start interaction with timeout
const startInteractionWithTimeout = async (
message: string,
timeoutMs: number = 30000,
) => {
// Clear any existing timeout
if (interactionTimeout) {
clearTimeout(interactionTimeout);
}
// Set up automatic interruption
interactionTimeout = setTimeout(async () => {
console.log(`Operation timed out after ${timeoutMs}ms, interrupting...`);
const result = await sdk.interrupt();
if (result.success) {
console.log('Successfully interrupted due to timeout');
showTimeoutNotification();
}
}, timeoutMs);
// Start the interaction
await sdk.sendMessageAndListen(message);
};
// Clear timeout when interaction completes
sdk.onInteractionLoopComplete = () => {
if (interactionTimeout) {
clearTimeout(interactionTimeout);
interactionTimeout = null;
}
};
Error handling and retry logic Robust interruption with fallback options.
const safeInterrupt = async (maxRetries: number = 3): Promise<boolean> => {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const result = await sdk.interrupt();
if (result.success) {
console.log(`Interruption succeeded on attempt ${attempt}`);
return true;
}
console.warn(`Interruption attempt ${attempt} failed: ${result.error}`);
// Wait before retrying
if (attempt < maxRetries) {
await new Promise((resolve) => setTimeout(resolve, 1000 * attempt));
}
} catch (error) {
console.error(`Interruption attempt ${attempt} threw error:`, error);
}
}
console.error(`All ${maxRetries} interruption attempts failed`);
// Fallback: clear session entirely
sdk.clearSession();
console.log('Cleared session as fallback');
return false;
};
Integration with session management Handle interruption in session lifecycle.
class ConversationManager {
private sdk: ArthurSDK;
private isInterrupting = false;
constructor(sdk: ArthurSDK) {
this.sdk = sdk;
this.setupEventHandlers();
}
private setupEventHandlers() {
// Handle turn-end events (including interruptions)
this.sdk.onMessagesReceived = (messages) => {
// Look for developer messages indicating interruption
const lastMessage = messages[messages.length - 1];
if (lastMessage?.role === 'developer' && this.isInterrupting) {
this.handleInterruptionComplete();
}
this.updateUI(messages);
};
this.sdk.onInteractionLoopComplete = () => {
this.isInterrupting = false;
this.enableUserInput();
};
}
async interrupt(): Promise<boolean> {
if (this.isInterrupting) {
console.log('Interruption already in progress');
return false;
}
this.isInterrupting = true;
this.disableUserInput();
this.showInterruptionSpinner();
const result = await this.sdk.interrupt();
if (!result.success) {
this.isInterrupting = false;
this.hideInterruptionSpinner();
this.enableUserInput();
this.showError(`Failed to interrupt: ${result.error}`);
return false;
}
return true;
}
private handleInterruptionComplete() {
console.log('Interruption completed successfully');
this.hideInterruptionSpinner();
this.showSuccess('Operation interrupted successfully');
}
private updateUI(messages: ConversationMessage[]) {
// Update chat interface with new messages
}
private showInterruptionSpinner() {
// Show "Stopping..." spinner
}
private hideInterruptionSpinner() {
// Hide interruption spinner
}
private enableUserInput() {
// Re-enable message input and send buttons
}
private disableUserInput() {
// Disable message input and send buttons
}
private showError(message: string) {
// Display error notification
}
private showSuccess(message: string) {
// Display success notification
}
}
When an interruption is successful, the server sends a turn-end event that creates a visible message in the conversation. This provides transparency about what happened and maintains conversation context.
Turn-end event structure:
interface TurnEndEvent {
role: 'turn-end';
reason: 'user-interruption' | 'natural-completion';
content: string; // Human-readable description of what happened
}
How turn-end events appear in your conversation:
sdk.onMessagesReceived = (messages) => {
// After successful interruption, you'll receive messages like:
// [
// { type: 'message', role: 'user', content: 'Process this large dataset...' },
// { type: 'tool_call', role: 'assistant', loading: false, content: { name: 'data-processor', ... } },
// { type: 'message', role: 'developer', content: 'Operation interrupted by user', id: '...', time: '...' },
// // ^ This developer message was created by the turn-end event
// ]
const lastMessage = messages[messages.length - 1];
if (lastMessage?.role === 'developer') {
console.log('System notification:', lastMessage.content);
}
};
Common error scenarios and how to handle them:
const handleInterruptErrors = async () => {
const result = await sdk.interrupt();
if (!result.success) {
switch (result.error) {
case 'No active session to interrupt':
console.log('No active conversation to interrupt');
// Handle case where user tries to interrupt without active session
showInfo('No active conversation to stop');
break;
case 'Unauthorized':
case 'Authorization header is required':
console.error('Authentication failed for interrupt request');
// Handle auth errors - may need to refresh tokens
handleAuthError();
break;
case 'Session not found or access denied':
console.error('Session no longer exists or access denied');
// Handle case where session expired or was cleared
sdk.clearSession();
showError('Session expired. Please start a new conversation.');
break;
default:
console.error('Unknown interruption error:', result.error);
// Handle unexpected errors
showError('Failed to stop operation. Please try again.');
}
}
};
The interrupt method is designed for reliability:
clearSession()Performance best practices:
// ✅ Good: Single interrupt call with error handling
const handleInterrupt = async () => {
try {
const result = await sdk.interrupt();
return result.success;
} catch (error) {
console.error('Interrupt request failed:', error);
return false;
}
};
// ❌ Avoid: Rapid multiple interrupt calls
const avoidThis = async () => {
// Don't do this - one call is sufficient
await sdk.interrupt();
await sdk.interrupt();
await sdk.interrupt();
};
// ✅ Good: Interrupt with timeout protection
const interruptWithTimeout = async (timeoutMs = 5000): Promise<boolean> => {
const timeoutPromise = new Promise<{ success: false; error: string }>(
(_, reject) => {
setTimeout(
() => reject(new Error('Interrupt request timed out')),
timeoutMs,
);
},
);
try {
const result = await Promise.race([sdk.interrupt(), timeoutPromise]);
return result.success;
} catch (error) {
console.error('Interrupt timed out or failed:', error);
return false;
}
};
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.
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:
The Solution: The SDK automatically queues messages when a request is already being processed, ensuring:
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');
Messages are processed in strict FIFO (First In, First Out) order:
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 workflow
🔒 Race Condition Prevention
📋 Proper Message Ordering
🔄 Request Key Management
requestKey when processed⚡ Seamless User Experience
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:
isProcessingRequest is truerequestKey from the serverError Handling:
clearSession()) rejects all queued messagesThe message queueing system is completely transparent and maintains full backward compatibility:
sendMessageAndListen() calls work unchangedWhile 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 state
Note: 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.
Add a new message to the conversation array. This immediately triggers the onMessagesReceived callback with the updated messages array.
const newMessage = {
type: 'message',
role: 'user',
content: 'This is a manually added message',
id: 'manual-msg-1',
};
sdk.pushMessage(newMessage);
sdk.pushMessage(message);
message (ConversationMessage): The message object to add to the conversation. Must be either ConversationMessageUserText or ConversationMessageToolCall.void - This method does not return anything. The message is added to the internal messages array and immediately triggers the onMessagesReceived callback.
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);
Update an existing message in the conversation by its ID. Useful for updating loading states or modifying message content.
sdk.updateMessage('msg-123', (message) => ({
...message,
loading: false,
}));
sdk.updateMessage(id, updater);
id (string): The unique identifier of the message to updateupdater ((message: ConversationMessage) => ConversationMessage): Function that receives the current message and returns the updated messagevoid - This method does not return anything. The message is updated in the internal messages array and immediately triggers the onMessagesReceived callback.
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
});
Replace the entire conversation messages array. This immediately triggers the onMessagesReceived callback with the new messages.
const newMessages = [
{ type: 'message', role: 'user', content: 'Hello', id: 'msg-1' },
{ type: 'message', role: 'assistant', content: 'Hi there!', id: 'msg-2' },
];
sdk.setMessages(newMessages);
sdk.setMessages(messages);
messages (ConversationMessage[]): Array of conversation messages to replace the current messages withvoid - This method does not return anything. The messages array is replaced and immediately triggers the onMessagesReceived callback.
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);
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.
// Clear session and messages (start completely fresh)
sdk.clearSession();
// Clear session but keep messages for display purposes
sdk.clearSession(false);
sdk.clearSession(clearMessages);
clearMessages (boolean, optional): Whether to clear the messages array. Default: truevoid - 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.
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);
}
};
Set multiple custom headers that will be included in all HTTP requests to the server.
sdk.setAdditionalHeaders({
Authorization: 'Bearer your-token-here',
'X-API-Version': '2.1',
'X-Client-Source': 'web-app',
});
sdk.setAdditionalHeaders(headers);
headers (Record<string, string>): Object containing header key-value pairs to set. This will replace all existing additional headers.void - This method does not return anything.
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',
});
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.
sdk.setHeader('Authorization', 'Bearer new-token-here');
sdk.setHeader(key, value);
key (string): The header name/key to setvalue (string): The header value to setvoid - This method does not return anything.
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');
Remove a specific header from the additional headers collection. The header will no longer be included in HTTP requests.
sdk.removeHeader('X-Debug-Mode');
sdk.removeHeader(key);
key (string): The header name/key to removevoid - This method does not return anything. If the header doesn't exist, no error is thrown.
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');
}
Get a copy of all additional headers currently set. Returns an object containing all custom headers that will be included in HTTP requests.
const headers = sdk.getAdditionalHeaders();
console.log(headers); // { 'Authorization': 'Bearer token', 'X-API-Version': '2.1' }
const headers = sdk.getAdditionalHeaders();
None.
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.
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);
Direct access to the current conversation messages array. This property provides read and write access to the internal messages collection.
// Read current messages
console.log('Current messages:', sdk.messages);
// Add a message directly (not recommended - use pushMessage instead)
sdk.messages.push(newMessage);
ConversationMessage[] - Array containing all conversation messages in chronological order.
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);
Getter property that returns the messages array. This is an alias for the messages property, providing the same functionality with a more descriptive name.
const allMessages = sdk.messagesList;
console.log('Total messages:', allMessages.length);
ConversationMessage[] - Array containing all conversation messages, identical to the messages property.
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); // true
Choosing 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;
Access to the ToolRegistry instance used by the SDK. This provides access to all registered tools and their configurations.
const weatherTool = sdk.toolRegistry.getTool('weather-api');
console.log('Weather tool:', weatherTool);
ToolRegistry - The ToolRegistry instance containing all registered tools and their execution logic.
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);
}
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.
if (sdk.eventSource) {
console.log('Connection state:', sdk.eventSource.readyState);
} else {
console.log('Not connected to server events');
}
EventSource | null - The EventSource instance for receiving real-time updates from the server, or null when disconnected.
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');
});
}
## Event Callbacks
### ArthurSDK.onInteractionLoopComplete
> **⚠️ Deprecated**: This method is deprecated. Use [`onTurnEnd`](#arthursdk.onturnend) instead for more accurate turn completion detection with detailed information about why the turn ended.
Set a callback function that executes when the interaction loop ends (conversation completes or connection closes).
#### Example
```typescript
// ⚠️ Deprecated approach
sdk.onInteractionLoopComplete = () => {
console.log('Conversation completed');
};
// ✅ Recommended approach using onTurnEnd
sdk.onTurnEnd = (turnEndEvent) => {
console.log(`Turn ended: ${turnEndEvent.reason} - ${turnEndEvent.content}`);
if (turnEndEvent.reason === 'natural-completion') {
console.log('AI finished responding naturally');
} else if (turnEndEvent.reason === 'user-interruption') {
console.log('User interrupted the response');
}
};
// Set the callback function
sdk.onInteractionLoopComplete = callback;
// Get the current callback function
const currentCallback = sdk.onInteractionLoopComplete;
callback (() => void): Function that takes no parameters and is called when the interaction loop completes.void - This is a setter property. The callback function itself should not return anything.
To migrate from onInteractionLoopComplete to onTurnEnd:
// Old approach
sdk.onInteractionLoopComplete = () => {
// Handle completion
};
// New approach with more information
sdk.onTurnEnd = (turnEndEvent) => {
// Access turn end reason and details
console.log(turnEndEvent.reason); // 'natural-completion' or 'user-interruption'
console.log(turnEndEvent.content); // Descriptive message
// Handle completion with more context
};
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);
}
};
Set a callback function that executes whenever the session ID changes, typically when starting a new conversation.
sdk.onSessionIdChanged = (sessionId) => {
console.log('Session ID changed:', sessionId);
};
// Set the callback function
sdk.onSessionIdChanged = callback;
// Get the current callback function
const currentCallback = sdk.onSessionIdChanged;
callback ((sessionId: string | undefined) => void): Function that receives the new session ID as a parameter, or undefined when the session is cleared.void - This is a setter property. The callback function itself should not return anything.
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);
};
// 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',
interruptURL: 'https://api.example.com/api/interrupt',
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 manually
Set a callback function that executes whenever a turn-end event occurs. Turn-end events indicate when an interaction loop completes, either through natural completion or user interruption. This callback provides direct access to turn-end events without needing to filter through general message callbacks.
sdk.onTurnEnd = (turnEndEvent) => {
if (turnEndEvent.reason === 'user-interruption') {
console.log('User interrupted:', turnEndEvent.content);
// Handle interruption-specific logic
hideLoadingSpinner();
showNotification('Operation was interrupted');
} else if (turnEndEvent.reason === 'natural-completion') {
console.log('Task completed:', turnEndEvent.content);
// Handle completion-specific logic
markTaskComplete();
showSuccessMessage(turnEndEvent.content);
}
};
// Set the callback function
sdk.onTurnEnd = callback;
// Get the current callback function
const currentCallback = sdk.onTurnEnd;
callback ((turnEndEvent: TurnEndEvent) => void): Function that receives turn-end events with the following properties:
role: Always 'turn-end'reason: Either 'user-interruption' or 'natural-completion'content: Human-readable description of what happenedvoid - This is a setter property. The callback function itself should not return anything.
interface TurnEndEvent {
role: 'turn-end';
reason: 'user-interruption' | 'natural-completion';
content: string;
}
UI state management based on turn-end reasons Update user interface elements based on how interactions end.
sdk.onTurnEnd = (turnEndEvent) => {
const { reason, content } = turnEndEvent;
switch (reason) {
case 'user-interruption':
// User clicked stop or interrupted
updateUIState('interrupted');
hideProgressBar();
showAlert('info', 'Operation stopped: ' + content);
enableUserInput();
break;
case 'natural-completion':
// Task completed successfully
updateUIState('completed');
hideProgressBar();
showAlert('success', 'Completed: ' + content);
enableUserInput();
playCompletionSound();
break;
default:
console.warn('Unknown turn-end reason:', reason);
}
// Always log for debugging
console.log(`[Turn End] ${reason}: ${content}`);
};
Analytics and monitoring Track interaction completion patterns for analytics.
sdk.onTurnEnd = (turnEndEvent) => {
// Send analytics event
analytics.track('interaction_ended', {
reason: turnEndEvent.reason,
content_length: turnEndEvent.content.length,
session_id: getCurrentSessionId(),
timestamp: new Date().toISOString(),
});
// Update metrics dashboard
if (turnEndEvent.reason === 'user-interruption') {
incrementCounter('interruptions');
} else {
incrementCounter('completions');
}
// Log for debugging
console.log(`[Analytics] Interaction ended: ${turnEndEvent.reason}`);
};
Error recovery and resilience Handle different completion scenarios with appropriate recovery logic.
sdk.onTurnEnd = (turnEndEvent) => {
if (turnEndEvent.reason === 'user-interruption') {
// Check if this was an intentional stop or error recovery
if (
turnEndEvent.content.includes('error') ||
turnEndEvent.content.includes('failed')
) {
// Error-based interruption
showErrorDialog('Operation failed', turnEndEvent.content);
logError('interaction_interrupted_due_to_error', turnEndEvent.content);
} else {
// User-initiated stop
showNotification('Operation stopped by user');
// Optionally offer to resume
showResumeOption(getCurrentContext());
}
} else {
// Natural completion - check if further action is needed
const results = parseCompletionResults(turnEndEvent.content);
if (results.requiresFollowup) {
scheduleFollowupTask(results.followupAction);
}
}
};
Integration with workflow management Coordinate turn-end events with larger application workflows.
class WorkflowManager {
private currentWorkflow: Workflow | null = null;
setupSDK(sdk: ArthurSDK) {
sdk.onTurnEnd = (turnEndEvent) => {
if (!this.currentWorkflow) return;
if (turnEndEvent.reason === 'user-interruption') {
// Handle workflow interruption
this.currentWorkflow.markAsInterrupted(turnEndEvent.content);
this.saveWorkflowState(); // For potential resume
this.notifyWorkflowStakeholders('interrupted');
} else {
// Handle workflow completion
this.currentWorkflow.markAsCompleted(turnEndEvent.content);
this.triggerNextWorkflowStep();
this.notifyWorkflowStakeholders('completed');
}
this.updateWorkflowDashboard();
};
}
private triggerNextWorkflowStep() {
// Move to next step in workflow
}
private saveWorkflowState() {
// Persist workflow state for resumption
}
private notifyWorkflowStakeholders(status: string) {
// Send notifications to relevant parties
}
}
The onTurnEnd callback is triggered before the turn-end message is added to the conversation, allowing you to:
sdk.onTurnEnd = (turnEndEvent) => {
// This runs FIRST - before the developer message is created
console.log('Turn-end callback triggered:', turnEndEvent.reason);
};
sdk.onMessagesReceived = (messages) => {
// This runs AFTER - when the developer message is added
const lastMessage = messages[messages.length - 1];
if (lastMessage.role === 'developer') {
console.log('Developer message added:', lastMessage.content);
}
};
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.
interact()) for all operationsRequired 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 requestsBasic 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',
},
});
Send a message to a booth and receive an immediate response.
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);
}
const result = await syncSDK.interact({
message,
booth,
clientId,
toolmodules? // Optional
});
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)'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 assistanceinterface SyncResponse {
success: boolean;
output?: ResponseInputItem[];
error?: string;
}
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,
});
Set multiple custom headers for all requests.
syncSDK.setHeaders({
Authorization: 'Bearer new-token',
'X-Request-Source': 'mobile-app',
'Content-Language': 'en-US',
});
Set a single header.
syncSDK.setHeader('Authorization', 'Bearer updated-token');
Remove a specific header.
syncSDK.removeHeader('X-Debug-Mode');
Get all current headers.
const currentHeaders = syncSDK.getHeaders();
console.log('Current headers:', currentHeaders);
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);
}
}
"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 toolimport { 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);
}
Manages tool configurations and execution. Tools should be registered first as they are referenced by booths.
Register a single tool in the registry. If a tool with the same name already exists, it will be overwritten.
toolRegistry.registerTool({
name: 'weather-api',
description: 'Get weather information',
parameters: { location: 'string' },
execute: async ({ location }) => {
return { temperature: 75, conditions: 'sunny' };
},
});
toolRegistry.registerTool(tool);
tool (ToolModule): Tool configuration object containing name, description, parameters, and optional execute functionvoid - This method does not return anything.
Register multiple tools at once.
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);
toolRegistry.registerTools(tools);
tools (ToolModule[]): Array of tool configuration objects to registervoid - This method does not return anything.
Retrieve a tool by its name.
const weatherTool = toolRegistry.getTool('weather-api');
if (weatherTool) {
console.log('Tool found:', weatherTool.description);
}
const tool = toolRegistry.getTool(toolName);
toolName (string): The name of the tool to retrieveToolModule | undefined - The tool configuration if found, undefined otherwise.
Returns all registered tools as a record object indexed by tool names.
const allTools = toolRegistry.getAllTools();
Object.keys(allTools).forEach((toolName) => {
console.log(`Tool: ${toolName}`, allTools[toolName]);
});
const tools = toolRegistry.getAllTools();
None.
Record<string, ToolModule> - Object containing all tools indexed by their names.
Returns all tools without their execute functions, useful for serialization when sending to the server.
const toolsForServer = toolRegistry.getAllToolsWithoutExecute();
// Safe to JSON.stringify - no functions included
const tools = toolRegistry.getAllToolsWithoutExecute();
None.
Omit<ToolModule, 'execute'>[] - Array of tools without execute functions.
Remove a tool from the registry by its name.
try {
toolRegistry.unregisterTool('deprecated-tool');
console.log('Tool removed successfully');
} catch (error) {
console.error('Tool not found:', error.message);
}
toolRegistry.unregisterTool(toolName);
toolName (string): The name of the tool to removevoid - This method does not return anything. Throws an error if the tool doesn't exist.
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() };
},
});
Manages booth configurations within the system. Booths should be registered after tools so they can reference existing tools.
Register a single booth configuration in the registry.
boothRegistry.registerBooth({
id: 'weather-assistant',
name: 'Weather Assistant',
description: 'Provides weather information and forecasts',
context: 'You are a helpful weather assistant.',
tools: ['weather-api'],
});
boothRegistry.registerBooth(boothConfig);
boothConfig (BoothConfig): Booth configuration object containing id, role, description, and optional tools arrayvoid - This method does not return anything.
Register multiple booth configurations at once.
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);
boothRegistry.registerBooths(boothConfigs);
boothConfigs (BoothConfig[]): Array of booth configuration objects to registervoid - This method does not return anything.
Retrieve a booth configuration by its unique ID.
const weatherBooth = boothRegistry.getBoothById('weather-assistant');
if (weatherBooth) {
console.log('Booth found:', weatherBooth.name);
}
const booth = boothRegistry.getBoothById(boothId);
boothId (string): The unique identifier of the booth to retrieveBoothConfig | undefined - The booth configuration if found, undefined otherwise.
Returns all registered booth configurations as an array.
const allBooths = boothRegistry.getAllBooths();
allBooths.forEach((booth) => {
console.log(`Booth: ${booth.name} - ${booth.description}`);
});
const booths = boothRegistry.getAllBooths();
None.
BoothConfig[] - Array containing all registered booth configurations.
Remove a booth from the registry by its ID.
const wasRemoved = boothRegistry.unregisterBooth('deprecated-booth');
if (wasRemoved) {
console.log('Booth removed successfully');
} else {
console.log('Booth not found');
}
const success = boothRegistry.unregisterBooth(boothId);
boothId (string): The unique identifier of the booth to removeboolean - True if the booth was found and removed, false otherwise.
Get all booth configurations as an array, useful for serialization.
const boothArray = boothRegistry.toArray();
// Same as getAllBooths(), provided for consistency
const booths = boothRegistry.toArray();
None.
BoothConfig[] - Array containing all registered booth configurations.
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)
});
The booth examples above demonstrate the flexible tool access system:
weather-assistant
['weather-api']email-assistant
['send-email']office-assistant
['weather-api', 'send-email']math-tutor
The Arthur SDK provides a flexible system for controlling which tools are available to which booths. There are two ways to configure tool access:
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');
}
},
});
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)
});
For any given booth, the available tools are:
global: true)tools array// 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)
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
}
});
};
// 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());
// 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);
};
The SDK exports comprehensive TypeScript types for all interfaces:
ConversationMessage
Union type for all conversation messages
ConversationMessageUserText
Text messages from user/assistant
ConversationMessageToolCall
Tool call messages
CustomConversationMessage
Custom messages with flexible content from server-defined interactions
MessageResponse
Response from message API
InterruptResponse
Response from interrupt API endpoint
TurnEndEvent
Event sent when interaction loops complete or are interrupted
IncomingMessage
Union type for all incoming server messages
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 boothOptional Properties
tools (string[]): Array of tool names available to this boothplugins (string[]): Array of plugin names (future use)Required Properties
name (string): Unique tool identifierdescription (string): Description of what the tool doesparameters (Record<string, any>): Object describing the tool's parametersOptional Properties
execute (Function): Function that executes the tool logicglobal (boolean): If true, tool is available to all boothsIncomingMessageBoothsCommand
Booth command messages
IncomingMessageExecuteTools
Tool execution requests
IncomingMessageToolCall
Tool call notifications
IncomingMessageToolCallResult
Tool call results
IncomingMessageFunctionCallOutput
Function call outputs
IncomingCustomMessage
Custom messages with flexible content
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
isTurnEndEvent(message: any): boolean
Check if message is a turn-end event
Note on custom messages: Custom messages can be identified using message.type === 'custom_message'. No dedicated type guard is exported as the content structure is application-specific.
Understanding how the Arthur server handles sessions and request keys is crucial for proper conversation management:
The Arthur SDK now uses a dual-token authentication system with both session IDs and request keys:
Session IDs (sessionId):
clearSession() or starting a completely new conversationRequest Keys (requestKey):
When sending a message without a session ID:
clearSession() or when starting a completely new conversationImportant: 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.
The SDK handles the runtime session management automatically, but developers are responsible for persistence:
What the SDK does:
onSessionIdChanged callback when sessions changeWhat developers must do:
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
});
Starting Fresh Conversations:
sdk.clearSession() to clear the current session (recommended)sessionId when initializing the SDKMaintaining Conversation History:
onSessionIdChanged is triggeredMulti-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,
});
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');
}
}
// Interrupt-specific error handling
const result = await sdk.interrupt();
if (!result.success) {
if (result.error === 'No active session to interrupt') {
console.log('No active conversation to interrupt');
} else if (result.error?.includes('Authorization')) {
console.error('Authentication failed for interrupt request');
} else {
console.error('Interruption failed:', result.error);
}
}
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 call sendMessage() while EventSource is connected"No requestKey available for EventSource connection" - Missing request key when starting streaming connection"No active session to interrupt" - Trying to interrupt without an established sessionTool-Related Errors:
"Tool [toolName] could not be found" - Referenced tool is not registered in the ToolRegistryNetwork and Server Errors:
The SDK uses modern browser APIs:
Ensure your target browsers support these APIs or include appropriate polyfills.
ISC
FAQs
Client-side SDK for interacting with Arthur Intelligence and Booths system.
The npm package arthur-sdk receives a total of 17 weekly downloads. As such, arthur-sdk popularity was classified as not popular.
We found that arthur-sdk demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.