# 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 status - `mcp` (`IProviderMcp`): MCP server read/write/list for provider-native config files - `sessions` (`IProviderSessions`): normalize live events and fetch persisted history Main interfaces: - `server/shared/interfaces.ts` - `server/shared/types.ts` - `server/modules/providers/shared/base/abstract.provider.ts` - `server/modules/providers/shared/mcp/mcp.provider.ts` Main registry/services: - `server/modules/providers/provider.registry.ts` - `server/modules/providers/services/provider-auth.service.ts` - `server/modules/providers/services/mcp.service.ts` - `server/modules/providers/services/sessions.service.ts` ## Files You Must Add Create `server/modules/providers/list//` with: - `.provider.ts` - `-auth.provider.ts` - `-mcp.provider.ts` - `-sessions.provider.ts` Follow the existing structure in `claude`, `codex`, `cursor`, or `gemini`. ## Step-by-Step Checklist 1. Add provider id to shared union types. - Update `server/shared/types.ts` `LLMProvider`. - Also update `src/types/app.ts` `LLMProvider` (frontend type). 2. Implement the provider wrapper. - Extend `AbstractProvider`. - Expose `readonly auth`, `readonly mcp`, and `readonly sessions`. - Call `super('')`. 3. Implement auth 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. 4. Implement MCP provider (`-mcp.provider.ts`). - Extend `McpProvider`. - Define supported scopes/transports in `super('', scopes, transports)`. - Implement: - `readScopedServers(...)` - `writeScopedServers(...)` - `buildServerConfig(...)` - `normalizeServerConfig(...)` - Reuse shared validation behavior in `McpProvider` (scope/transport checks). 5. Implement sessions provider (`-sessions.provider.ts`). - Implement `IProviderSessions`: - `normalizeMessage(raw, sessionId)` - `fetchHistory(sessionId, options)` - Normalize to `NormalizedMessage` using `createNormalizedMessage(...)`. - For filesystem-backed sessions, sanitize path inputs (`sessionId`, workspace paths) before reading files/databases. - Keep pagination semantics consistent: - `limit: null` means unbounded - `limit: 0` means empty page - include `total`, `hasMore`, `offset`, `limit` correctly - Ensure normalized message ids are unique per output message. 6. Register provider in backend registry/router. - `server/modules/providers/provider.registry.ts`: - import the new provider class - add it to the `providers` map - `server/modules/providers/provider.routes.ts`: - update `parseProvider(...)` whitelist 7. Wire runtime execution path (outside this module). If the provider should run live chat commands, also update runtime routing: - `server/routes/agent.js` provider validation and dispatch - `server/index.js` provider 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`) 8. Add model constants and UI integration (outside this module). - `shared/modelConstants.js` provider model list + default - Provider selection and state hooks: - `src/components/chat/hooks/useChatProviderState.ts` - `src/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. ```ts // .provider.ts import { AbstractProvider } from '@/modules/providers/shared/base/abstract.provider.js'; import { AuthProvider } from './-auth.provider.js'; import { McpProvider } from './-mcp.provider.js'; import { SessionsProvider } from './-sessions.provider.js'; import type { IProviderAuth, IProviderSessions } from '@/shared/interfaces.js'; export class Provider extends AbstractProvider { readonly mcp = new McpProvider(); readonly auth: IProviderAuth = new AuthProvider(); readonly sessions: IProviderSessions = new SessionsProvider(); constructor() { super(''); } } ``` ```ts // -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 = ''; export class 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 { 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: ```text Add a new provider "" using the provider module architecture. Requirements: 1) Create: - server/modules/providers/list//.provider.ts - server/modules/providers/list//-auth.provider.ts - server/modules/providers/list//-mcp.provider.ts - server/modules/providers/list//-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 - npx tsc --noEmit -p server/tsconfig.json ``` ## Validation Checklist Run these after implementation: ```bash 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//auth/status` - `GET /api/providers//mcp/servers` - `POST /api/providers//mcp/servers` - `GET /api/sessions//messages?provider=&limit=50&offset=0` ## Common Mistakes - Adding provider files but forgetting `provider.registry.ts`. - Updating backend `LLMProvider` but not frontend `src/types/app.ts`. - Hardcoding provider whitelists in routes and missing one location. - Returning duplicate message ids in `normalizeMessage`. - Treating `limit === 0` as unbounded instead of empty page. - Building file paths from raw `sessionId` without validation.