
Research
Two Malicious Rust Crates Impersonate Popular Logger to Steal Wallet Keys
Socket uncovers malicious Rust crates impersonating fast_log to steal Solana and Ethereum wallet keys from source code.
@redhat-cloud-services/ai-client-state
Advanced tools
State management for AI client conversations with event-driven architecture and cross-package compatibility.
State management for AI client conversations with event-driven architecture and cross-package compatibility.
IAIClient
interfacenpm install @redhat-cloud-services/ai-client-state
import { createClientStateManager } from '@redhat-cloud-services/ai-client-state';
import { IFDClient } from '@redhat-cloud-services/arh-client';
// Create any AI client (ARH, Lightspeed, etc.)
const client = new IFDClient({
baseUrl: 'https://your-api.com',
fetchFunction: (input, init) => fetch(input, init) // Arrow function to preserve context
});
// Create state manager
const stateManager = createClientStateManager(client);
// Initialize (no longer auto-creates conversations)
await stateManager.init();
// LAZY INITIALIZATION: First sendMessage auto-creates conversation
await stateManager.sendMessage('Hello AI!'); // Auto-promotes temporary conversation
IMPORTANT: When configuring the fetchFunction
in AI clients, always use arrow functions or properly bound functions to preserve the this
context and avoid reference issues.
CRITICAL: Do NOT set 'Content-Type'
headers in your fetchFunction - the AI client will set these internally based on the endpoint requirements. Setting custom Content-Type can interfere with the client's internal logic and cause request failures.
// DON'T DO THIS - 'this' context can be lost
const client = new IFDClient({
baseUrl: 'https://your-api.com',
fetchFunction: fetch // Direct reference can cause context issues
});
// DON'T DO THIS - 'this' binding issues
const customFetch = function(input, init) {
// 'this' may not refer to expected object
return fetch(input, init);
};
// ALWAYS USE ARROW FUNCTIONS for fetchFunction
const client = new IFDClient({
baseUrl: 'https://your-api.com',
fetchFunction: (input, init) => fetch(input, init)
});
// Alternative: Bind to window to preserve context
const client = new IFDClient({
baseUrl: 'https://your-api.com',
fetchFunction: fetch.bind(window)
});
import { createClientStateManager } from '@redhat-cloud-services/ai-client-state';
import { IFDClient } from '@redhat-cloud-services/arh-client';
// Imaginary token retrieval function
async function getToken(): Promise<string> {
// Your token retrieval logic here
return 'your-jwt-token-here';
}
// CORRECT: Use arrow function for authenticated fetch
const client = new IFDClient({
baseUrl: 'https://your-api.com',
fetchFunction: async (input, init) => {
const token = await getToken();
return fetch(input, {
...init,
headers: {
...init?.headers,
'Authorization': `Bearer ${token}`
}
});
}
});
const stateManager = createClientStateManager(client);
await stateManager.init();
// For more complex auth scenarios with error handling
const createAuthenticatedClient = async () => {
const client = new IFDClient({
baseUrl: 'https://your-api.com',
fetchFunction: async (input, init) => {
try {
const token = await getToken();
const response = await fetch(input, {
...init,
headers: {
...init?.headers,
'Authorization': `Bearer ${token}`,
'User-Agent': 'AI-Client/1.0'
// DO NOT set 'Content-Type' - the client handles this internally
}
});
// Handle token refresh if needed
if (response.status === 401) {
const newToken = await getToken(); // Refresh token
return fetch(input, {
...init,
headers: {
...init?.headers,
'Authorization': `Bearer ${newToken}`
// DO NOT set 'Content-Type' - the client handles this internally
}
});
}
return response;
} catch (error) {
console.error('Authentication error:', error);
throw error;
}
}
});
return createClientStateManager(client);
};
// Usage
const stateManager = await createAuthenticatedClient();
await stateManager.init();
The state manager supports conversation locking to prevent users from sending messages to conversations that are no longer active or have been archived. This feature provides a better user experience by preventing confusion and ensuring messages are only sent to appropriate conversations.
The state manager now uses lazy initialization by default. Conversations are created automatically on first sendMessage()
call, providing a seamless user experience.
const stateManager = createClientStateManager(client);
await stateManager.init(); // No conversations created
// First sendMessage automatically creates conversation
await stateManager.sendMessage('Hello'); // Creates temporary conversation, then promotes to real conversation
// Check if current conversation is temporary (should be false after promotion)
const isTemp = stateManager.isTemporaryConversation(); // false after successful promotion
'__temp_conversation__'
constant for temporary stateclient.createNewConversation()
You can still create conversations manually if needed:
// Create conversation explicitly
const conversation = await stateManager.createNewConversation();
await stateManager.setActiveConversationId(conversation.id);
// Now send messages to the specific conversation
await stateManager.sendMessage('Hello explicit conversation');
export type StateManager<
T extends Record<string, unknown> = Record<string, unknown>,
C extends IAIClient<T> = IAIClient<T>
> = {
// Initialization
init(): Promise<void>;
isInitialized(): boolean;
isInitializing(): boolean;
// Conversation Management
setActiveConversationId(conversationId: string): Promise<void>;
getActiveConversationId(): string | null;
getActiveConversationMessages(): Message<T>[];
getConversations(): Conversation<T>[];
createNewConversation(force?: boolean): Promise<IConversation>;
isTemporaryConversation(): boolean;
// Message Management
sendMessage(query: UserQuery, options?: MessageOptions): Promise<any>;
getMessageInProgress(): boolean;
// State Access
getState(): ClientState<T>;
getInitLimitation(): ClientInitLimitation | undefined;
// Event System
subscribe(event: Events, callback: () => void): () => void;
// Client Access
getClient(): C;
}
export type UserQuery = string;
export interface MessageOptions {
stream?: boolean;
[key: string]: unknown;
}
export enum Events {
MESSAGE = 'message',
ACTIVE_CONVERSATION = 'active-conversation',
IN_PROGRESS = 'in-progress',
CONVERSATIONS = 'conversations',
INITIALIZING_MESSAGES = 'initializing-messages',
INIT_LIMITATION = 'init-limitation',
}
The state manager wraps any IAIClient
and provides conversation state management:
import { createClientStateManager, Events } from '@redhat-cloud-services/ai-client-state';
const stateManager = createClientStateManager(client);
// Initialize the state manager
await stateManager.init();
// Check initialization status
if (stateManager.isInitialized()) {
console.log('State manager ready');
}
if (stateManager.isInitializing()) {
console.log('State manager initializing...');
}
import { Message, Conversation } from '@redhat-cloud-services/ai-client-state';
// Message structure (automatically created by state manager)
interface Message<T = Record<string, unknown>> {
id: string;
answer: string;
role: 'user' | 'bot';
additionalAttributes?: T;
date: Date;
}
// Conversation structure
interface Conversation<T = Record<string, unknown>> {
id: string;
title: string;
messages: Message<T>[];
locked: boolean; // Prevents new messages when true
createdAt: Date;
}
// Example: Messages are automatically created when you send strings
await stateManager.sendMessage('What is OpenShift?');
// This creates a user message internally and triggers the bot response
// Access messages from the conversation
const messages = stateManager.getActiveConversationMessages();
console.log('User message:', messages[0]); // { id: '...', answer: 'What is OpenShift?', role: 'user' }
console.log('Bot response:', messages[1]); // { id: '...', answer: 'OpenShift is...', role: 'bot' }
// Set active conversation (async)
await stateManager.setActiveConversationId('conv-123');
// Get all conversations
const conversations = stateManager.getConversations();
console.log('All conversations:', conversations);
// Create new conversation
const newConversation = await stateManager.createNewConversation();
console.log('Created conversation:', newConversation.id);
// Get messages from active conversation
const messages = stateManager.getActiveConversationMessages();
// Access raw state for advanced use cases
const state = stateManager.getState();
console.log('All conversations:', state.conversations);
console.log('Active conversation:', state.activeConversationId);
Important: The
sendMessage
method takes a string (UserQuery
), not aMessage
object. The state manager automatically createsMessage
objects internally for both user input and bot responses.
// Send non-streaming message (pass string directly)
const response = await stateManager.sendMessage('Explain Kubernetes pods');
console.log('Bot response:', response);
// Send streaming message (uses client's default streaming handler)
await stateManager.sendMessage('Tell me about container orchestration', { stream: true });
// Messages are automatically updated as chunks arrive
const messages = stateManager.getActiveConversationMessages();
const botResponse = messages.find(m => m.role === 'bot');
console.log('Streaming response so far:', botResponse?.answer);
import { MessageOptions } from '@redhat-cloud-services/ai-client-state';
const options: MessageOptions = {
stream: true,
customHeader: 'value',
// Any additional options are passed to the underlying client
};
await stateManager.sendMessage('Your message here', options);
import { Events } from '@redhat-cloud-services/ai-client-state';
// Available Events:
// Events.MESSAGE - When messages are added/updated
// Events.ACTIVE_CONVERSATION - When active conversation changes
// Events.IN_PROGRESS - When message sending status changes
// Events.CONVERSATIONS - When conversation list changes
// Events.INITIALIZING_MESSAGES - When conversation history is being loaded
// Events.INIT_LIMITATION - When client initialization has limitations
// Subscribe to message updates
const unsubscribeMessages = stateManager.subscribe(Events.MESSAGE, () => {
const messages = stateManager.getActiveConversationMessages();
console.log('Messages updated:', messages.length);
});
// Subscribe to conversation changes
const unsubscribeConversation = stateManager.subscribe(Events.ACTIVE_CONVERSATION, () => {
const state = stateManager.getState();
console.log('Active conversation changed:', state.activeConversationId);
});
// Subscribe to progress updates
const unsubscribeProgress = stateManager.subscribe(Events.IN_PROGRESS, () => {
const isInProgress = stateManager.getMessageInProgress();
console.log('Message in progress:', isInProgress);
});
// Subscribe to conversation list changes
const unsubscribeConversations = stateManager.subscribe(Events.CONVERSATIONS, () => {
const conversations = stateManager.getConversations();
console.log('Conversations updated:', conversations.length);
});
// Subscribe to message initialization events
const unsubscribeInitializing = stateManager.subscribe(Events.INITIALIZING_MESSAGES, () => {
const isInitializing = stateManager.isInitializing();
console.log('Messages initializing:', isInitializing);
});
// Cleanup subscriptions
unsubscribeMessages();
unsubscribeConversation();
unsubscribeProgress();
unsubscribeConversations();
unsubscribeInitializing();
// Check if a message is currently being sent
const isInProgress = stateManager.getMessageInProgress();
if (isInProgress) {
console.log('Please wait, message being processed...');
} else {
console.log('Ready to send next message');
}
import { IFDClient } from '@redhat-cloud-services/arh-client';
import { createClientStateManager } from '@redhat-cloud-services/ai-client-state';
const arhClient = new IFDClient({
baseUrl: 'https://arh-api.redhat.com',
fetchFunction: authenticatedFetch
});
const stateManager = createClientStateManager(arhClient);
await stateManager.init();
import { LightspeedClient } from '@redhat-cloud-services/lightspeed-client';
import { createClientStateManager } from '@redhat-cloud-services/ai-client-state';
const lightspeedClient = new LightspeedClient({
baseUrl: 'https://lightspeed-api.openshift.com',
fetchFunction: (input, init) => fetch(input, init)
});
const stateManager = createClientStateManager(lightspeedClient);
await stateManager.init();
import {
IAIClient,
IConversation,
IMessageResponse,
ClientInitLimitation,
IInitErrorResponse
} from '@redhat-cloud-services/ai-client-common';
import { createClientStateManager } from '@redhat-cloud-services/ai-client-state';
class CustomClient implements IAIClient {
async init(): Promise<{
conversations: IConversation[];
limitation?: ClientInitLimitation;
error?: IInitErrorResponse;
}> {
return {
conversations: []
};
}
async createNewConversation(): Promise<IConversation> {
return {
id: crypto.randomUUID(),
title: 'New Conversation',
locked: false,
createdAt: new Date()
};
}
async sendMessage(conversationId: string, message: string): Promise<IMessageResponse<Record<string, unknown>>> {
// Your custom implementation
return {
messageId: crypto.randomUUID(),
answer: 'Custom response',
conversationId,
additionalAttributes: {}
};
}
// ... implement other IAIClient methods
}
const customClient = new CustomClient();
const stateManager = createClientStateManager(customClient);
// Create multiple conversations
await stateManager.init(); // No longer auto-creates conversations
// Create conversations explicitly
const conversation1 = await stateManager.createNewConversation();
const conversation2 = await stateManager.createNewConversation();
// Switch between conversations
await stateManager.setActiveConversationId(conversation1.id);
await stateManager.sendMessage('First message');
await stateManager.setActiveConversationId(conversation2.id);
await stateManager.sendMessage('Second message');
// Get all conversations
const allConversations = stateManager.getConversations();
console.log('Total conversations:', allConversations.length);
// Access specific conversation from state
const state = stateManager.getState();
const conv1 = state.conversations[conversation1.id];
const conv2 = state.conversations[conversation2.id];
try {
await stateManager.sendMessage('Your message here');
} catch (error) {
console.error('Failed to send message:', error);
// State manager automatically cleans up failed messages
const messages = stateManager.getActiveConversationMessages();
// Failed bot message will be removed from conversation
}
const state = stateManager.getState();
console.log('Initialization status:', {
isInitialized: state.isInitialized,
isInitializing: state.isInitializing
});
console.log('Conversation state:', {
activeConversationId: state.activeConversationId,
conversationCount: Object.keys(state.conversations).length,
messageInProgress: state.messageInProgress
});
// Access specific conversation details
Object.entries(state.conversations).forEach(([id, conversation]) => {
console.log(`Conversation ${id}:`, {
messageCount: conversation.messages.length,
lastMessage: conversation.messages[conversation.messages.length - 1]
});
});
Works seamlessly with:
Run nx build ai-client-state
to build the library.
Run nx test ai-client-state
to execute the unit tests via Jest.
This package follows the workspace standards:
FAQs
State management for AI client conversations with event-driven architecture and cross-package compatibility.
The npm package @redhat-cloud-services/ai-client-state receives a total of 114 weekly downloads. As such, @redhat-cloud-services/ai-client-state popularity was classified as not popular.
We found that @redhat-cloud-services/ai-client-state demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 5 open source maintainers 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.
Research
Socket uncovers malicious Rust crates impersonating fast_log to steal Solana and Ethereum wallet keys from source code.
Research
A malicious package uses a QR code as steganography in an innovative technique.
Research
/Security News
Socket identified 80 fake candidates targeting engineering roles, including suspected North Korean operators, exposing the new reality of hiring as a security function.