mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-04-22 13:31:32 +00:00
Providers Module: How To Add a New Provider
This guide is the canonical checklist for adding a provider to the unified provider system.
The goal is to make provider onboarding deterministic for both humans and AI agents.
Architecture Summary
Each provider is composed of 3 sub-capabilities behind one wrapper:
auth(IProviderAuth): install/auth statusmcp(IProviderMcp): MCP server read/write/list for provider-native config filessessions(IProviderSessions): normalize live events and fetch persisted history
Main interfaces:
server/shared/interfaces.tsserver/shared/types.tsserver/modules/providers/shared/base/abstract.provider.tsserver/modules/providers/shared/mcp/mcp.provider.ts
Main registry/services:
server/modules/providers/provider.registry.tsserver/modules/providers/services/provider-auth.service.tsserver/modules/providers/services/mcp.service.tsserver/modules/providers/services/sessions.service.ts
Files You Must Add
Create server/modules/providers/list/<provider>/ with:
<provider>.provider.ts<provider>-auth.provider.ts<provider>-mcp.provider.ts<provider>-sessions.provider.ts
Follow the existing structure in claude, codex, cursor, or gemini.
Step-by-Step Checklist
- Add provider id to shared union types.
- Update
server/shared/types.tsLLMProvider. - Also update
src/types/app.tsLLMProvider(frontend type).
- Implement the provider wrapper.
- Extend
AbstractProvider. - Expose
readonly auth,readonly mcp, andreadonly sessions. - Call
super('<provider>').
- Implement auth provider (
<provider>-auth.provider.ts).
- Implement
IProviderAuth#getStatus(). - Return
{ installed, provider, authenticated, email, method, error? }. - Use existing helpers from
server/shared/utils.ts(readObjectRecord,readOptionalString, etc.) where relevant.
- Implement MCP provider (
<provider>-mcp.provider.ts).
- Extend
McpProvider. - Define supported scopes/transports in
super('<provider>', scopes, transports). - Implement:
readScopedServers(...)writeScopedServers(...)buildServerConfig(...)normalizeServerConfig(...)
- Reuse shared validation behavior in
McpProvider(scope/transport checks).
- Implement sessions provider (
<provider>-sessions.provider.ts).
- Implement
IProviderSessions:normalizeMessage(raw, sessionId)fetchHistory(sessionId, options)
- Normalize to
NormalizedMessageusingcreateNormalizedMessage(...). - For filesystem-backed sessions, sanitize path inputs (
sessionId, workspace paths) before reading files/databases. - Keep pagination semantics consistent:
limit: nullmeans unboundedlimit: 0means empty page- include
total,hasMore,offset,limitcorrectly
- Ensure normalized message ids are unique per output message.
- Register provider in backend registry/router.
server/modules/providers/provider.registry.ts:- import the new provider class
- add it to the
providersmap
server/modules/providers/provider.routes.ts:- update
parseProvider(...)whitelist
- update
- Wire runtime execution path (outside this module).
If the provider should run live chat commands, also update runtime routing:
server/routes/agent.jsprovider validation and dispatchserver/index.jsprovider routing/command handling/valid provider lists- Add or wire provider runtime implementation module (similar to
claude-sdk.js,cursor-cli.js,openai-codex.js,gemini-cli.js)
- Add model constants and UI integration (outside this module).
shared/modelConstants.jsprovider model list + default- Provider selection and state hooks:
src/components/chat/hooks/useChatProviderState.tssrc/components/chat/view/subcomponents/ProviderSelectionEmptyState.tsx
- Auth/login modal command text:
src/components/provider-auth/view/ProviderLoginModal.tsx
Minimal Templates
Use these as a starting point.
// <provider>.provider.ts
import { AbstractProvider } from '@/modules/providers/shared/base/abstract.provider.js';
import { <Provider>AuthProvider } from './<provider>-auth.provider.js';
import { <Provider>McpProvider } from './<provider>-mcp.provider.js';
import { <Provider>SessionsProvider } from './<provider>-sessions.provider.js';
import type { IProviderAuth, IProviderSessions } from '@/shared/interfaces.js';
export class <Provider>Provider extends AbstractProvider {
readonly mcp = new <Provider>McpProvider();
readonly auth: IProviderAuth = new <Provider>AuthProvider();
readonly sessions: IProviderSessions = new <Provider>SessionsProvider();
constructor() {
super('<provider>');
}
}
// <provider>-sessions.provider.ts
import type { IProviderSessions } from '@/shared/interfaces.js';
import type { FetchHistoryOptions, FetchHistoryResult, NormalizedMessage } from '@/shared/types.js';
import { createNormalizedMessage, readObjectRecord } from '@/shared/utils.js';
const PROVIDER = '<provider>';
export class <Provider>SessionsProvider implements IProviderSessions {
normalizeMessage(rawMessage: unknown, sessionId: string | null): NormalizedMessage[] {
const raw = readObjectRecord(rawMessage);
if (!raw) {
return [];
}
return [createNormalizedMessage({
provider: PROVIDER,
kind: 'text',
role: 'assistant',
sessionId,
content: String(raw.content ?? ''),
})];
}
async fetchHistory(
sessionId: string,
options: FetchHistoryOptions = {},
): Promise<FetchHistoryResult> {
const { limit = null, offset = 0 } = options;
const all: NormalizedMessage[] = [];
if (limit === null) {
return { messages: all.slice(offset), total: all.length, hasMore: false, offset, limit: null };
}
const start = Math.max(0, offset);
const safeLimit = Math.max(0, limit);
const page = safeLimit === 0 ? [] : all.slice(start, start + safeLimit);
return {
messages: page,
total: all.length,
hasMore: safeLimit === 0 ? start < all.length : start + safeLimit < all.length,
offset: start,
limit: safeLimit,
};
}
}
AI Prompt Template
Use this prompt for AI-assisted implementation:
Add a new provider "<provider>" using the provider module architecture.
Requirements:
1) Create:
- server/modules/providers/list/<provider>/<provider>.provider.ts
- server/modules/providers/list/<provider>/<provider>-auth.provider.ts
- server/modules/providers/list/<provider>/<provider>-mcp.provider.ts
- server/modules/providers/list/<provider>/<provider>-sessions.provider.ts
2) Register in:
- server/modules/providers/provider.registry.ts
- server/modules/providers/provider.routes.ts (parseProvider whitelist)
- server/shared/types.ts LLMProvider
- src/types/app.ts LLMProvider
3) Reuse helper utilities and follow existing style from codex/claude/cursor/gemini.
4) Ensure sessions:
- unique normalized message IDs
- safe path handling for disk/db session sources
- correct pagination for limit=null and limit=0
5) Run:
- npx eslint <touched server files>
- npx tsc --noEmit -p server/tsconfig.json
Validation Checklist
Run these after implementation:
npx eslint server/modules/providers/**/*.ts server/shared/types.ts server/shared/interfaces.ts
npx tsc --noEmit -p server/tsconfig.json
Quick API smoke tests:
GET /api/providers/<provider>/auth/statusGET /api/providers/<provider>/mcp/serversPOST /api/providers/<provider>/mcp/serversGET /api/sessions/<sessionId>/messages?provider=<provider>&limit=50&offset=0
Common Mistakes
- Adding provider files but forgetting
provider.registry.ts. - Updating backend
LLMProviderbut not frontendsrc/types/app.ts. - Hardcoding provider whitelists in routes and missing one location.
- Returning duplicate message ids in
normalizeMessage. - Treating
limit === 0as unbounded instead of empty page. - Building file paths from raw
sessionIdwithout validation.