docsPackagessmartchats-backend

smartchats-backend

The typed backend contract for SmartChats: the SmartChatsBackend interface, the shared error type, HTTP helpers for NDJSON streams, the AuthProvider interface and media storage helpers.

Two packages implement it:

  • smartchats-backend-local (LocalBackend, LocalAuthProvider), which talks to smartchats-local-server
  • smartchats-backend-firebase (closed source), which talks to the cloud Firebase Functions

The app imports types from this package. Its bootstrap code picks which implementation to construct.

What’s in the package

ModulePurpose
types.tsSmartChatsBackend, BackendCapabilities, every sub-API (LLMAPI, TTSAPI, EmbeddingsAPI, DataAPI, UsageAPI, KeysAPI, BillingAPI, ToolsAPI, IntegrationsAPI, InsightsAPI, StorageAPI), BackendError
auth_provider.tsAuthProvider, AuthUser, AuthCapabilities, SignInMethod, EmailCredentials
http/postStream, readNdjson, base64ToPcmBuffer, statusToBackendErrorCode, toServerArgs, createLLMStreamResult, createLLMTTSStreamResult
storage.tsMedia upload limits, MIME allow list, key and cursor helpers, argument validators

The contract

interface SmartChatsBackend {
  readonly capabilities: BackendCapabilities;
  readonly id: string;               // "firebase" | "local" | user-defined
 
  readonly llm: LLMAPI;              // stream(), streamWithTTS(), warmup?()
  readonly tts: TTSAPI;              // stream(), warmup?()
  readonly embeddings: EmbeddingsAPI;
  readonly data: DataAPI;            // query(), healthCheck()
  readonly usage: UsageAPI;
  readonly keys: KeysAPI;            // BYO provider keys
  readonly billing: BillingAPI;      // throws 'not_supported' when !capabilities.billing
  readonly tools: ToolsAPI;          // search(), fetchUrl()
  readonly integrations: IntegrationsAPI;
  readonly insights: InsightsAPI;    // emit(events)
  readonly storage: StorageAPI;      // throws 'not_supported' when !capabilities.storage
 
  health(): Promise<HealthReport>;
  providers(): Promise<ProvidersReport>;
}

LLM and TTS are streaming only. llm.streamWithTTS(args) takes LLMCallArgs plus voice, speed, instructions, tts_provider and an optional verdict block ({ model, effort?, cap_ms?, judge?, threshold? }), and yields LLMTTSEvents: text, verdict, text_end, audio_start, audio, audio_end and server timing events. See llm-service for the verdict.

BackendCapabilities

All flags are booleans. The UI uses them to show or hide features.

FlagMeaning
billingCredit and tier billing is active (billing UI, checkout)
byoKeysServer-side bring-your-own key management
embeddingsEmbedding generation
searchWeb search tool
urlFetchURL fetch and readability parsing
integrationsThird-party integrations (Google Docs, Gmail, Calendar)
insightsTelemetry ingestion
storageMedia storage (StorageAPI)

providers() is separate from capabilities: it reports which LLM and TTS providers can serve requests right now, given server keys and saved BYO keys.

BackendError

class BackendError extends Error {
  constructor(
    code: BackendErrorCode,
    message: string,
    retryable: boolean = false,
    details?: unknown,
  );
}

BackendErrorCode is one of insufficient_credits, provider_error, not_supported, network_error, rate_limited, invalid_request, not_found, server_error, aborted or unknown. statusToBackendErrorCode(status) maps 401 to invalid_request, 402 to insufficient_credits, 429 to rate_limited, 5xx to server_error and anything else to provider_error.

postStream

import { postStream, readNdjson } from 'smartchats-backend';
 
const response = await postStream(url, body, headers, signal);   // Promise<Response>
for await (const frame of readNdjson(response)) {
  // one parsed object per line; malformed lines are skipped
}

postStream(url, body, headers, signal?) POSTs a JSON body and returns the Response. Headers, including auth, are the caller’s. Failures become BackendErrors:

  • aborted (by signal or AbortError) becomes aborted, not retryable
  • a network error becomes network_error, retryable
  • a non-2xx status becomes the mapped code, retryable when the status is 500 or above

Retryable errors are retried twice, after 250 ms and 750 ms, unless the signal has aborted. Retries happen before any body is read.

⚠️

Safari reports an aborted fetch as TypeError: Load failed, not AbortError. postStream checks signal.aborted as well as the error name. Code that copies the pattern should do the same.

createLLMStreamResult(response) and createLLMTTSStreamResult(...) turn a streaming response into the LLMStreamResult and LLMTTSStreamResult shapes. toServerArgs(args) converts LLMCallArgs to the server payload (it builds text_format from schema and schema_name).

Authentication

Auth is not part of SmartChatsBackend. A backend gets its bearer token when it is constructed. The app uses a separate AuthProvider:

interface AuthProvider {
  readonly capabilities: AuthCapabilities;   // { required: boolean, methods: SignInMethod[] }
  getCurrentUser(): AuthUser | null;         // { uid, email, displayName }
  getIdToken(): Promise<string | null>;
  onAuthChange(callback: (user: AuthUser | null) => void): () => void;
  signIn(method: SignInMethod, credentials?: EmailCredentials): Promise<void>;
  signOut(): Promise<void>;
  sendPasswordReset?(email: string): Promise<void>;
}

SignInMethod is 'google' | 'email' | 'anonymous'. EmailCredentials is { email, password, signup? }. sendPasswordReset is optional: a provider without email accounts leaves it out and the UI hides the link.

LocalAuthProvider (in smartchats-backend-local) reports required: false, always returns one local user and makes sign-in and sign-out no-ops. LocalBackend takes { baseUrl, apiKey? } and sends Authorization: Bearer <apiKey> when a key is set.

Storage helpers

storage.ts is shared by client and server so both enforce the same rules: STORAGE_ALLOWED_MIME, isAllowedMime, kindForMime, buildMediaKey, usesMultipart and planParts (multipart above STORAGE_MULTIPART_THRESHOLD_BYTES, 50 MiB), thumbnail helpers, encodeMediaCursor and decodeMediaCursor, and the validators validateCreateUploadArgs and validateListMediaArgs.

Other exports

SMARTCHATS_REQUIRED_TABLES, SMARTCHATS_DEFAULT_LOCAL_PORT (4242), SMARTCHATS_DEFAULT_LOCAL_URL and LLM_PROVIDERS (openai, anthropic, google, xai, together, azure).

See also