add-provider
Add a new storage or service provider to the core package. Use when implementing a new backend for StorageService (e.g., a new database) or a new service provider (e.g., a new LLM backend).
Context
Providers implement interfaces defined in core. They are selected at runtime via config
(e.g., STORAGE_PROVIDER_TYPE). Tier 3 providers lazy-load their dependencies to keep the
core bundle small.
Provider interfaces
| Domain | Interface file |
|---|---|
| Storage | src/storage/core/IStorageProvider.ts |
| LLM | src/services/llm/core/ILlmProvider.ts |
| Speech | src/services/speech/core/ISpeechProvider.ts |
Read the relevant interface fully before implementing — each has distinct required members.
ISpeechProvider in particular requires readonly name, readonly supportsTTS,
readonly supportsSTT, and healthCheck() in addition to the capability methods;
these flags drive routing in SpeechService.
File conventions
Provider file location and naming differ by domain:
-
Storage — nested subdirectory, PascalCase file:
src/storage/providers/{{provider-name}}/{{provider-name}}Provider.ts(e.g.,src/storage/providers/inMemory/inMemoryProvider.ts) -
LLM / Speech — flat directory, kebab-case with
.provider.tssuffix:src/services/llm/providers/{{provider-name}}.provider.tssrc/services/speech/providers/{{provider-name}}.provider.ts(e.g.,src/services/llm/providers/openrouter.provider.ts,src/services/speech/providers/elevenlabs.provider.ts)
Steps
-
Identify the provider interface — read the interface file for the target domain (see table above).
-
Create the provider file following the file convention for its domain (see above).
-
Implement the interface — all methods must be implemented.
-
Lazy-load dependencies if Tier 3:
let _client: SomeClient | undefined; async function getClient(): Promise<SomeClient> { if (!_client) { const { SomeClient } = await import('some-package'); _client = new SomeClient(/* config */); } return _client; } -
Register the provider — the registration point differs by domain:
-
Storage — add a
caseto theswitchinsrc/storage/core/storageFactory.tsinsidecreateStorageProvider(). Import the new provider class at the top of that file. -
Speech — two changes required:
- Add the new provider string literal to the
providerunion inSpeechProviderConfig(src/services/speech/types.ts, fieldprovider). - Add a
caseto theswitchincreateSpeechProvider()(src/services/speech/core/SpeechService.ts). Import the new provider class at the top of that file.
- Add the new provider string literal to the
-
LLM — currently only one provider exists (
OpenRouterProvider); it is instantiated directly insrc/core/app.tsrather than through a factory switch. Add a factory function or extendapp.tsas needed, then updateILlmProviderconsumers accordingly.
-
-
Update the Worker-compatible provider list if the new storage provider runs in Cloudflare Workers. The list is an inline array in
storageFactory.tsat theisServerless()guard:// src/storage/core/storageFactory.ts ~line 112 !['in-memory', 'cloudflare-r2', 'cloudflare-kv', 'cloudflare-d1'].includes(providerType)Add the new provider string to this array. Non-storage providers have no equivalent gate.
-
Add the dependency as an optional peer dependency in
package.jsonif Tier 3. -
Run
bun run devcheckto verify.
Checklist
- Provider file created with JSDoc
@fileoverview+@moduleheader - Interface fully implemented (including
name,supportsTTS/supportsSTTfor speech) - Tier 3 dependencies lazy-loaded (not top-level imports)
- Registered in the correct factory for the domain (see Step 5)
- Speech:
providerliteral added toSpeechProviderConfigunion intypes.ts - Storage: Worker-compatible array in
storageFactory.tsupdated if applicable - Optional peer dependency added to
package.jsonif Tier 3 -
bun run devcheckpasses - Integration tested with the target backend