@applica-software-guru/persona-sdk
v0.1.89
Published
[](https://www.npmjs.com/package/@applica-software-guru/persona-sdk) [](#) [ => import('@applica-software-guru/persona-sdk').then((mod) => mod.PersonaRuntimeProvider), {
ssr: false,
});
const logger = new PersonaConsoleLogger();
function Chat() {
return (
<PersonaRuntimeProvider
dev
logger={logger}
protocols={{
rest: true,
webrtc: true,
websocket: true,
}}
session={'<session-id>'}
apiKey="<api-key>"
agentId={'<agent-name>'}
>
<div className="h-dvh w-full">
<Thread />
</div>
</PersonaRuntimeProvider>
);
}Note: When using WebRTC, it is recommended to import PersonaRuntimeProvider dynamically with ssr: false (as shown above) to ensure compatibility with server-side rendering frameworks like Next.js.
Development Mode
Use the dev prop to enable development mode. This redirects all traffic to a local installation of Persona (e.g., localhost:8000).
Accessing Messages
usePersonaRuntimeMessages
The usePersonaRuntimeMessages hook provides a simple way to access the current list of messages managed by the Persona SDK runtime. This hook returns an array of PersonaMessage objects, which represent the conversation history, including user, assistant, and reasoning messages.
Usage Example:
import { usePersonaRuntimeMessages } from '@applica-software-guru/persona-sdk';
function MessageLogger() {
const messages = usePersonaRuntimeMessages();
return (
<div>
<h3>Messages</h3>
<pre>{JSON.stringify(messages, null, 2)}</pre>
</div>
);
}This hook must be used within a PersonaRuntimeProvider.
usePersonaRuntime and getMessages
The usePersonaRuntime hook gives you access to the Persona runtime context, which now includes a getMessages method. This method returns the current array of PersonaMessage objects, allowing you to programmatically retrieve the latest messages at any time.
Usage Example:
import { usePersonaRuntime } from '@applica-software-guru/persona-sdk';
function MyComponent() {
const { getMessages } = usePersonaRuntime();
const messages = getMessages();
// Use messages as needed
}Both approaches are useful for building custom UI components, analytics, or debugging tools that need access to the chat history.
Message Filtering
The messageFilter prop allows you to process and filter messages before they are displayed in the chat interface. This provides powerful customization capabilities for controlling what messages users see and how they are presented.
Basic Usage
Add a messageFilter function to your PersonaRuntimeProvider:
import { PersonaRuntimeProvider, MessageFilter, PersonaMessage } from '@applica-software-guru/persona-sdk';
const myMessageFilter: MessageFilter = (messages: PersonaMessage[]) => {
// Process and return the filtered messages
return messages.filter((message) => message.type !== 'reasoning');
};
function App() {
return (
<PersonaRuntimeProvider apiKey="your-api-key" agentId="your-agent-id" protocols={{ rest: true }} messageFilter={myMessageFilter}>
{/* Your app content */}
</PersonaRuntimeProvider>
);
}MessageFilter Type
type MessageFilter = (messages: PersonaMessage[]) => PersonaMessage[];The function receives an array of PersonaMessage objects and must return an array of PersonaMessage objects.
Common Use Cases
1. Hide Reasoning Messages
const hideReasoningFilter: MessageFilter = (messages) => {
return messages.filter((message) => message.type !== 'reasoning');
};2. Content Filtering
const profanityFilter: MessageFilter = (messages) => {
const bannedWords = ['spam', 'inappropriate'];
return messages.filter((message) => {
const text = message.text.toLowerCase();
return !bannedWords.some((word) => text.includes(word));
});
};3. Message Limit
const messageLimitFilter: MessageFilter = (messages) => {
const maxMessages = 50;
return messages.length > maxMessages ? messages.slice(-maxMessages) : messages;
};4. Text Transformation
const transformTextFilter: MessageFilter = (messages) => {
return messages.map((message) => ({
...message,
text: message.text.replace(/\b(TODO|FIXME)\b/g, '⚠️ $1'),
}));
};5. Combining Multiple Filters
const combinedFilter: MessageFilter = (messages) => {
let filtered = hideReasoningFilter(messages);
filtered = profanityFilter(filtered);
filtered = messageLimitFilter(filtered);
return filtered;
};Important Notes
- The filter is applied to all messages, including both incoming messages from the assistant and outgoing messages from the user
- The filter function should be pure (no side effects) and return a new array
- Filters are applied every time the message list is updated
- If no filter is provided, all messages are displayed as-is
- Be careful with message ordering when filtering, as it may affect conversation context
Performance Considerations
- Keep filter functions lightweight as they run on every message update
- For expensive operations, consider debouncing or memoization
- Avoid creating new filter functions on every render to prevent unnecessary re-processing
For complete working examples, see the examples/message-filter-example.tsx file in the repository.
Message Storage
The Persona SDK provides a flexible message storage system that allows you to persist conversation history across sessions. By default, messages are stored in memory, but you can easily switch to localStorage or implement your own custom storage solution.
Storage Implementations
InMemoryMessageStorage (Default)
Messages are stored in memory and lost on page reload. This is the default if no storage is specified.
import { PersonaRuntimeProvider, InMemoryMessageStorage } from '@applica-software-guru/persona-sdk';
const storage = new InMemoryMessageStorage();
<PersonaRuntimeProvider apiKey="your-api-key" agentId="your-agent-id" protocols={{ rest: true }} threads={true} messageStorage={storage}>
{/* Your app */}
</PersonaRuntimeProvider>;LocalStorageMessageStorage
Messages persist across page reloads using browser localStorage.
import { PersonaRuntimeProvider, LocalStorageMessageStorage } from '@applica-software-guru/persona-sdk';
const storage = new LocalStorageMessageStorage('my_app_messages_');
<PersonaRuntimeProvider apiKey="your-api-key" agentId="your-agent-id" protocols={{ rest: true }} threads={true} messageStorage={storage}>
{/* Your app */}
</PersonaRuntimeProvider>;Custom Storage Implementation
You can implement your own storage solution by implementing the MessageStorage interface:
import { MessageStorage, PersonaMessage, Session } from '@applica-software-guru/persona-sdk';
class CustomMessageStorage implements MessageStorage {
async saveMessages(sessionId: Session, messages: PersonaMessage[]): Promise<void> {
// Your custom save logic (e.g., IndexedDB, remote API)
await fetch('/api/save-messages', {
method: 'POST',
body: JSON.stringify({ sessionId, messages }),
});
}
async loadMessages(sessionId: Session): Promise<PersonaMessage[]> {
// Your custom load logic
const response = await fetch(`/api/load-messages/${sessionId}`);
return await response.json();
}
async deleteMessages(sessionId: Session): Promise<void> {
// Your custom delete logic
await fetch(`/api/delete-messages/${sessionId}`, { method: 'DELETE' });
}
async clearAll(): Promise<void> {
// Clear all messages
await fetch('/api/clear-all-messages', { method: 'DELETE' });
}
async getAllSessionIds(): Promise<Session[]> {
// Get all session IDs
const response = await fetch('/api/session-ids');
return await response.json();
}
}
const customStorage = new CustomMessageStorage();
<PersonaRuntimeProvider
messageStorage={customStorage}
// ... other props
/>;How It Works
When thread management is enabled (threads={true}):
- Each thread has its own session ID
- Messages are automatically saved to storage when updated
- When switching threads, messages are loaded from storage
- This allows seamless conversation persistence across sessions
Protocols Overview
The Persona SDK supports multiple communication protocols to provide flexibility and performance. These protocols can work together, sharing the same session and data, to ensure the best possible experience.
REST
- Description: A simple and reliable protocol for sending and receiving data.
- Use Case: Ideal for applications where real-time communication is not critical.
WebSocket
- Description: A full-duplex communication protocol that enables real-time interaction between the client and server.
- Use Case: Suitable for scenarios requiring low-latency communication, such as live chat applications.
WebRTC
- Description: A peer-to-peer communication protocol designed primarily for ultra-fast audio sessions, while also supporting high-speed messaging with minimal latency.
- Use Case: Perfect for real-time, interactive applications where rapid audio communication and responsiveness are critical.
- Why WebRTC?: By creating direct peer-to-peer connections, WebRTC eliminates unnecessary server delays, ensuring the fastest possible audio sessions and seamless message exchanges.
Pro Tip: For optimal audio performance, make sure WebRTC is enabled in the protocols configuration of your application.
How Protocols Work Together
The Persona SDK runtime is designed to use multiple protocols simultaneously, ensuring flexibility and reliability. Here’s how it works:
- Shared Session: All protocols share the same session and operate on the same data, ensuring consistency across communication channels.
- Protocol Selection: The runtime automatically selects the fastest and most reliable protocol from the list of enabled protocols. For example:
- If WebRTC is available and started, it will be prioritized for its ultra-low latency.
- If WebRTC is not available, WebSocket will be used for real-time communication.
- REST will act as a fallback for non-real-time communication.
- WebRTC Startup: WebRTC requires manual startup by the user (e.g., clicking a microphone icon to enable audio/video). Once started, WebRTC participates in the list of reliable protocols and is used for the fastest communication.
- Seamless Integration: Even if WebRTC is not started, other protocols like WebSocket and REST will continue to function, ensuring uninterrupted communication.
This design allows the Persona SDK to adapt dynamically to the available protocols, providing the best possible performance and reliability.
API Reference
Props for PersonaRuntimeProvider
| Prop | Type | Default Value | Description |
| --------------- | ------------- | ----------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| dev | boolean | false | Enable development mode for local Persona API usage. |
| logger | function | console.log | Custom logger for debugging and logging messages. |
| protocols | object | { rest: true, webrtc: true, websocket: true } | Protocols to use for the chat. Can be enabled/disabled using boolean values. |
| session | string | undefined | Session ID for the chat. Required for the chat to function. |
| apiKey | string | toolkit-test | API key for authentication. Required for the chat to function. |
| agentId | string | default | Agent ID for the chat. Required for the chat to function. |
| messageFilter | MessageFilter | undefined | Optional function to filter and transform messages before they are displayed in the chat interface. |
Customization
The Persona SDK allows you to extend its functionality by creating custom protocols and loggers.
Custom Protocols
You can implement your own protocol by extending the PersonaProtocolBase class. For example:
import { PersonaProtocolBase } from '@applica-software-guru/persona-sdk';
class CustomProtocol extends PersonaProtocolBase {
// Implement required methods like connect, disconnect, send, etc.
}Example: Custom Protocol Implementation
Here’s an example of a custom protocol named CustomProtocol:
import { PersonaProtocolBase } from '@applica-software-guru/persona-sdk';
class CustomProtocol extends PersonaProtocolBase {
public getName(): string {
return 'custom';
}
public async connect(): Promise<void> {
console.log('[CustomProtocol] Connecting...');
this.setStatus('connected');
}
public async disconnect(): Promise<void> {
console.log('[CustomProtocol] Disconnecting...');
this.setStatus('disconnected');
}
public async send(message: string): Promise<void> {
console.log(`[CustomProtocol] Sending message: ${message}`);
this.notifyMessage({ role: 'assistant', type: 'text', text: `Echo: ${message}` });
}
}Custom Loggers
You can create a custom logger by implementing the PersonaLogger interface:
import { PersonaLogger } from '@applica-software-guru/persona-sdk';
class CustomLogger implements PersonaLogger {
log(message: string, ...args: unknown[]): void {
console.log(`[CustomLogger] ${message}`, ...args);
}
info(message: string, ...args: unknown[]): void {
console.info(`[CustomLogger] ${message}`, ...args);
}
warn(message: string, ...args: unknown[]): void {
console.warn(`[CustomLogger] ${message}`, ...args);
}
error(message: string, ...args: unknown[]): void {
console.error(`[CustomLogger] ${message}`, ...args);
}
debug(message: string, ...args: unknown[]): void {
console.debug(`[CustomLogger] ${message}`, ...args);
}
}Transaction Protocol
The Transaction Protocol in the Persona SDK is designed to handle structured interactions between the client and the Persona API. It allows you to define and execute specific operations (tools) in response to function calls.
How It Works
- Transaction Handling: The protocol listens for incoming transactions and invokes the appropriate tool based on the function call.
- Session Management: It shares the same session as other protocols, ensuring consistency.
- Custom Logic: You can define custom tools to handle specific function calls.
Usage Example
To enable the Transaction Protocol, include it in the protocols configuration of the PersonaRuntimeProvider:
<PersonaRuntimeProvider
dev
logger={logger}
protocols={{
transaction: async (transaction) => {
await transaction.invoke({
get_user_agent: () => navigator.userAgent || 'unknown',
get_client_date: () => ({ date: new Date().toISOString() }),
});
},
}}
session="your-session-id"
apiKey="your-api-key"
agentId="your-agent-id"
>
<YourComponent />
</PersonaRuntimeProvider>In this example:
- The
transactionprotocol is configured with a callback function. - The callback defines tools (
get_user_agentandget_client_date) that the protocol can invoke.
Using the tools Prop in PersonaRuntimeProvider
The tools prop allows you to define custom tools that the Transaction Protocol can invoke in response to function calls. As of vNEXT, you can provide tools in two ways:
1. As an Object (Legacy, Backward Compatible)
Important: When using the legacy object format, your agent configuration must also include the tool schema for each tool. If the schema is missing, the configured tools will not work as expected. This is not required when using the array format with
createTool.
You can define tools as an object where each key is the tool's name and the value is the function to execute:
<PersonaRuntimeProvider
tools={{
get_user_agent: () => navigator.userAgent || 'unknown',
get_client_date: () => ({ date: new Date().toISOString() }),
}}
...otherProps
/>2. As an Array of Tool Instances (Recommended)
For better type safety, schema support, and documentation, you can define tools using the createTool utility and pass an array of tool instances:
import { createTool } from '@applica-software-guru/persona-sdk';
const tools = [
createTool({
name: 'sum',
title: 'Sum two numbers',
description: 'Sums two numbers and returns the result.',
parameters: {
a: { type: 'number', description: 'First number to sum', required: true },
b: { type: 'number', description: 'Second number to sum', required: true },
},
output: {
result: { type: 'number', description: 'Sum of the two numbers' },
},
implementation: async (a: number, b: number) => {
return { result: a + b };
},
}),
// Add more tools as needed
];
<PersonaRuntimeProvider
tools={tools}
...otherProps
/>Recommended: Use the array format with createTool for new projects. This enables better validation, documentation, and future extensibility.
How It Works
- The SDK will automatically normalize the tools you provide, regardless of the format.
- When a transaction is received, the Transaction Protocol invokes the appropriate tool by name.
- Both synchronous and asynchronous tool functions are supported.
Best Practices
- Prefer the array format with
createToolfor new codebases. - Use descriptive names and provide parameter/output schemas for each tool.
- Handle errors gracefully in your tool implementations.
Managing Tools
Tools are custom functions that the Transaction Protocol can invoke in response to specific function calls. They allow you to extend the SDK's functionality by defining your own operations.
Defining Tools
Tools are defined as key-value pairs, where the key is the tool's name, and the value is the function to execute.
Example:
const tools = {
get_user_agent: () => navigator.userAgent || 'unknown',
get_client_date: () => ({ date: new Date().toISOString() }),
};Using Tools in Transactions
When a transaction is received, the protocol matches the function call's name with the tool's name and executes the corresponding function.
Example:
transaction.invoke(tools);Error Handling
If a tool is not found or an error occurs during execution, the transaction will fail. You can handle these scenarios by implementing error handling in your tools.
Example:
const tools = {
risky_tool: () => {
try {
// Perform some operation
return { success: true };
} catch (error) {
throw new Error('Tool execution failed');
}
},
};Enhanced Tools Support
The tools prop now supports both synchronous and asynchronous tools. For example:
<PersonaRuntimeProvider
tools={{
get_user_agent_async: async () => {
const userAgent = navigator.userAgent || 'unknown';
return { userAgent };
},
get_user_agent: () => {
const userAgent = navigator.userAgent || 'unknown';
return { userAgent };
},
}}
...otherProps
/>This allows for greater flexibility in defining tools that may require asynchronous operations.
Best Practices
- Tool Naming: Use descriptive names for tools to make them easy to identify.
- Error Handling: Ensure tools handle errors gracefully to avoid unexpected failures.
- Testing: Test tools thoroughly to ensure they work as expected in different scenarios.
- Security: Avoid exposing sensitive data or performing unsafe operations in tools.
By leveraging the Transaction Protocol and tools, you can create a highly customizable and extensible integration with the Persona SDK.
Advanced Transaction Handling
In addition to using the tools prop for automatic transaction handling, you can manage transactions manually by inspecting the function call and executing the complete or fail methods of the persistable entity passed to the transaction handler.
Manual Transaction Handling
When handling transactions manually, you gain full control over the transaction lifecycle. This allows you to inspect the function call, perform custom logic, and explicitly mark the transaction as complete or failed.
Example Usage
Here’s an example of how to handle transactions manually:
<PersonaRuntimeProvider
dev
logger={logger}
protocols={{
transaction: async (transaction) => {
const { functionCall, persistable } = transaction;
try {
// Inspect the function call
if (functionCall.name === 'get_user_agent') {
const result = navigator.userAgent || 'unknown';
await persistable.complete(result);
} else if (functionCall.name === 'get_client_date') {
const result = { date: new Date().toISOString() };
await persistable.complete(result);
} else {
// Handle unknown function calls
throw new Error(`Unknown function call: ${functionCall.name}`);
}
} catch (error) {
// Mark the transaction as failed
await persistable.fail({ error: error.message });
}
},
}}
session="your-session-id"
apiKey="your-api-key"
agentId="your-agent-id"
>
<YourComponent />
</PersonaRuntimeProvider>How It Works
- Inspect Function Call: The
functionCallobject contains details about the function being invoked, such as its name and arguments. - Custom Logic: Based on the function call, you can perform custom logic to determine the appropriate response.
- Complete or Fail: Use the
completemethod to return a successful result or thefailmethod to indicate an error.
Benefits
- Flexibility: Allows you to handle complex scenarios that may not be possible with predefined tools.
- Error Handling: Provides a mechanism to gracefully handle errors and unknown function calls.
Best Practices
- Always inspect the
functionCallobject to ensure you are handling the correct function. - Use descriptive error messages when calling the
failmethod to make debugging easier. - Test your transaction handler thoroughly to ensure it works as expected in all scenarios.
By combining manual transaction handling with the tools prop, you can create a robust and flexible integration with the Persona SDK.
Contributing
We welcome contributions! To get started:
- Fork the repository.
- Create a new branch for your feature or bugfix.
- Submit a pull request with a detailed description of your changes.
Please ensure your code adheres to the existing style and passes all linting and testing checks.
License
This project is licensed under the MIT License. See the LICENSE file for details.
Support
For questions or support, contact us at [email protected].
Acknowledgments
This SDK is built on top of assistant-ui, a powerful framework for building conversational interfaces.
