Documentation Index
Fetch the complete documentation index at: https://mintlify.com/danny-avila/librechat/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The librechat-data-provider package is a shared TypeScript library used by both frontend and backend for:
- API endpoints - URL construction
- Data service - HTTP request wrappers
- Type definitions - Shared types and schemas
- Query/Mutation keys - React Query key constants
Location: packages/data-provider/
Package Structure
packages/data-provider/src/
├── api-endpoints.ts # API URL builders
├── data-service.ts # HTTP request functions
├── keys.ts # QueryKeys and MutationKeys
├── types/ # TypeScript type definitions
│ ├── queries.ts
│ ├── mutations.ts
│ ├── agents.ts
│ ├── assistants.ts
│ └── ...
├── schemas.ts # Zod schemas and types
└── request.ts # Axios wrapper
API Endpoints
From packages/data-provider/src/api-endpoints.ts:1:
Endpoint Builders
let BASE_URL = '';
if (typeof process === 'undefined' || process.browser === true) {
// Client-side: read from <base> element
const baseEl = document.querySelector('base');
BASE_URL = baseEl?.getAttribute('href') || '/';
}
if (BASE_URL && BASE_URL.endsWith('/')) {
BASE_URL = BASE_URL.slice(0, -1);
}
export const apiBaseUrl = () => BASE_URL;
// Query parameter builder
const buildQuery = (params: Record<string, unknown>): string => {
const query = Object.entries(params)
.filter(([, value]) => {
if (Array.isArray(value)) {
return value.length > 0;
}
return value !== undefined && value !== null && value !== '';
})
.map(([key, value]) => {
if (Array.isArray(value)) {
return value.map((v) => `${key}=${encodeURIComponent(v)}`).join('&');
}
return `${key}=${encodeURIComponent(String(value))}`;
})
.join('&');
return query ? `?${query}` : '';
};
Example Endpoints
// Simple endpoints
export const user = () => `${BASE_URL}/api/user`;
export const balance = () => `${BASE_URL}/api/balance`;
export const health = () => `${BASE_URL}/health`;
// Parameterized endpoints
export const messages = (params: MessagesListParams) => {
const { conversationId, messageId, ...rest } = params;
if (conversationId && messageId) {
return `${BASE_URL}/api/messages/${conversationId}/${messageId}`;
}
if (conversationId) {
return `${BASE_URL}/api/messages/${conversationId}`;
}
return `${BASE_URL}/api/messages${buildQuery(rest)}`;
};
// With query parameters
export const getSharedLinks = (
pageSize: number,
isPublic: boolean,
sortBy: 'title' | 'createdAt',
sortDirection: 'asc' | 'desc',
search?: string,
cursor?: string,
) =>
`${BASE_URL}/api/share?pageSize=${pageSize}&isPublic=${isPublic}&sortBy=${sortBy}&sortDirection=${sortDirection}${
search ? `&search=${encodeURIComponent(search)}` : ''
}${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ''}`;
// RESTful resource endpoints
export const shareMessages = (shareId: string) => `${BASE_URL}/api/share/${shareId}`;
export const apiKeys = () => `${BASE_URL}/api/api-keys`;
export const apiKeyById = (id: string) => `${BASE_URL}/api/api-keys/${id}`;
Best Practices:
- Always use
encodeURIComponent for dynamic URL parameters
- Use
buildQuery helper for query strings
- Return absolute URLs from endpoint functions
Data Service
From packages/data-provider/src/data-service.ts:1:
HTTP Request Functions
import request from './request'; // Axios wrapper
import * as endpoints from './api-endpoints';
import type * as t from './types';
// Simple GET request
export function getPresets(): Promise<t.TPreset[]> {
return request.get(endpoints.presets());
}
// GET with dynamic parameter
export function getSharedMessages(shareId: string): Promise<t.TSharedMessagesResponse> {
return request.get(endpoints.shareMessages(shareId));
}
// GET with query parameters
export const listSharedLinks = async (
params: t.SharedLinksListParams,
): Promise<t.SharedLinksResponse> => {
const { pageSize, isPublic, sortBy, sortDirection, search, cursor } = params;
return request.get(
endpoints.getSharedLinks(pageSize, isPublic, sortBy, sortDirection, search, cursor),
);
};
// POST with payload
export function createSharedLink(
conversationId: string,
targetMessageId?: string,
): Promise<t.TSharedLinkResponse> {
return request.post(endpoints.createSharedLink(conversationId), { targetMessageId });
}
// PATCH request
export function updateSharedLink(shareId: string): Promise<t.TSharedLinkResponse> {
return request.patch(endpoints.updateSharedLink(shareId));
}
// DELETE request
export function deleteSharedLink(shareId: string): Promise<t.TDeleteSharedLinkResponse> {
return request.delete(endpoints.shareMessages(shareId));
}
// PUT with validation
export function updateUserKey(payload: t.TUpdateUserKeyRequest) {
const { value } = payload;
if (!value) {
throw new Error('value is required');
}
return request.put(endpoints.keys(), payload);
}
Request Wrapper
The request object (from request.ts) is an Axios instance with interceptors:
import axios from 'axios';
const request = axios.create({
headers: {
'Content-Type': 'application/json',
},
});
// Request interceptor (add auth tokens, etc.)
request.interceptors.request.use(
(config) => {
// Add authorization header if available
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
// Response interceptor (handle errors)
request.interceptors.response.use(
(response) => response.data,
(error) => {
// Handle 401 unauthorized
if (error.response?.status === 401) {
// Trigger logout or refresh token
}
return Promise.reject(error);
}
);
export default request;
Type Definitions
Types are organized in packages/data-provider/src/types/:
Query Types
From packages/data-provider/src/types/queries.ts:
export interface ConversationListParams {
isArchived?: boolean;
sortBy?: 'createdAt' | 'updatedAt' | 'title';
sortDirection?: 'asc' | 'desc';
tags?: string[];
search?: string;
cursor?: string;
}
export interface ConversationListResponse {
conversations: TConversation[];
pageNumber: number;
pageSize: number;
pages: number;
nextCursor?: string;
}
export interface MessagesListParams {
conversationId?: string;
messageId?: string;
sortBy?: string;
sortDirection?: 'asc' | 'desc';
pageSize?: number;
search?: string;
cursor?: string;
}
export interface MessagesListResponse {
messages: TMessage[];
nextCursor?: string;
}
Mutation Types
From packages/data-provider/src/types/mutations.ts:
export interface TUpdateConversationRequest {
conversationId: string;
title?: string;
tags?: string[];
}
export interface TUpdateConversationResponse extends TConversation {}
export interface TDeleteConversationRequest {
conversationId: string;
source?: string;
}
export interface TDeleteConversationResponse {
acknowledged: boolean;
deletedCount: number;
messages: {
acknowledged: boolean;
deletedCount: number;
};
}
Shared Schemas
From packages/data-provider/src/schemas.ts:
import { z } from 'zod';
export const TPresetSchema = z.object({
presetId: z.string().optional(),
title: z.string(),
endpoint: z.string(),
model: z.string().optional(),
temperature: z.number().optional(),
// ... more fields
});
export type TPreset = z.infer<typeof TPresetSchema>;
Using Data Provider in Frontend
1. Import from data-provider
import { QueryKeys, dataService } from 'librechat-data-provider';
import type t from 'librechat-data-provider';
2. Create Query Hook
From client/src/data-provider/Messages/queries.ts:1:
import { useQuery } from '@tanstack/react-query';
import { QueryKeys, dataService } from 'librechat-data-provider';
import type * as t from 'librechat-data-provider';
import type { UseQueryOptions, QueryObserverResult } from '@tanstack/react-query';
export const useGetMessagesByConvoId = <TData = t.TMessage[]>(
id: string,
config?: UseQueryOptions<t.TMessage[], unknown, TData>,
): QueryObserverResult<TData> => {
const queryClient = useQueryClient();
return useQuery<t.TMessage[], unknown, TData>(
[QueryKeys.messages, id],
async () => {
const result = await dataService.getMessagesByConvoId(id);
// Custom processing if needed
return result;
},
{
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchOnMount: false,
...config,
},
);
};
3. Export from Feature Module
// client/src/data-provider/Messages/index.ts
export * from './queries';
export * from './mutations';
// client/src/data-provider/index.ts
export * from './Messages';
export * from './Agents';
export * from './Auth';
// ... more features
4. Use in Component
import { useGetMessagesByConvoId } from '~/data-provider';
function MessageList({ conversationId }: Props) {
const { data: messages, isLoading, error } = useGetMessagesByConvoId(conversationId);
if (isLoading) return <LoadingSpinner />;
if (error) return <ErrorDisplay error={error} />;
return (
<div>
{messages?.map((message) => (
<MessageCard key={message.messageId} message={message} />
))}
</div>
);
}
Adding New Endpoints
1. Add Endpoint to api-endpoints.ts
// packages/data-provider/src/api-endpoints.ts
export const agentRuns = (agentId: string) =>
`${BASE_URL}/api/agents/${encodeURIComponent(agentId)}/runs`;
export const agentRunById = (agentId: string, runId: string) =>
`${BASE_URL}/api/agents/${encodeURIComponent(agentId)}/runs/${encodeURIComponent(runId)}`;
2. Add Type Definitions
// packages/data-provider/src/types/agents.ts
export interface AgentRun {
id: string;
agentId: string;
status: 'pending' | 'running' | 'completed' | 'failed';
createdAt: string;
completedAt?: string;
}
export interface AgentRunsResponse {
runs: AgentRun[];
nextCursor?: string;
}
3. Add Data Service Function
// packages/data-provider/src/data-service.ts
import type { AgentRunsResponse, AgentRun } from './types/agents';
export function listAgentRuns(agentId: string): Promise<AgentRunsResponse> {
return request.get(endpoints.agentRuns(agentId));
}
export function getAgentRun(agentId: string, runId: string): Promise<AgentRun> {
return request.get(endpoints.agentRunById(agentId, runId));
}
4. Add Query Key
// packages/data-provider/src/keys.ts
export enum QueryKeys {
// ... existing keys
agentRuns = 'agentRuns',
agentRun = 'agentRun',
}
5. Create Query Hook
// client/src/data-provider/Agents/queries.ts
import { QueryKeys, dataService } from 'librechat-data-provider';
import type { AgentRunsResponse } from 'librechat-data-provider';
export const useListAgentRunsQuery = (agentId: string) => {
return useQuery<AgentRunsResponse>(
[QueryKeys.agentRuns, agentId],
() => dataService.listAgentRuns(agentId),
{
enabled: !!agentId,
staleTime: 30000, // 30 seconds
},
);
};
6. Rebuild Package
After making changes to packages/data-provider:
# From project root
npm run build:data-provider
Query Key Organization
From packages/data-provider/src/keys.ts:1:
export enum QueryKeys {
// User & Auth
user = 'user',
balance = 'balance',
// Conversations
conversation = 'conversation',
allConversations = 'allConversations',
archivedConversations = 'archivedConversations',
conversationTags = 'conversationTags',
// Messages
messages = 'messages',
sharedMessages = 'sharedMessages',
// Agents
agents = 'agents',
agent = 'agent',
agentTools = 'agentTools',
agentCategories = 'agentCategories',
marketplaceAgents = 'marketplaceAgents',
// Files
files = 'files',
fileConfig = 'fileConfig',
// Config
endpoints = 'endpoints',
startupConfig = 'startupConfig',
}
// Dynamic query keys that require parameters
export const DynamicQueryKeys = {
agentFiles: (agentId: string) => ['agentFiles', agentId] as const,
} as const;
Key Patterns:
- Singular for single items:
agent, conversation
- Plural for lists:
agents, messages
- Descriptive prefixes:
all, archived, marketplace
- Dynamic keys for parameterized queries
Best Practices
Type Safety
// Always import types from data-provider
import type { Agent, AgentListResponse } from 'librechat-data-provider';
// Use proper generic typing
const { data } = useQuery<AgentListResponse>(
[QueryKeys.agents],
() => dataService.listAgents(),
);
Error Handling
const { data, error, isError } = useGetAgentByIdQuery(agentId);
if (isError) {
// error is typed based on your error handling setup
console.error('Failed to load agent:', error);
}
Request Cancellation
// React Query automatically cancels queries when component unmounts
const { data } = useQuery(
[QueryKeys.agents],
({ signal }) => {
// Pass AbortSignal to axios
return request.get(endpoints.agents(), { signal });
},
);
Next Steps