ff.wrap(client) returns a transparent proxy of your LLM client. You call it exactly as before; behind each messages.create (Anthropic), chat.completions.create (OpenAI), or embeddings.create it captures tokens, cost, latency, HTTP status, and (opt-in) prompt/completion content, then records them to your Supabase.
ANTHROPIC_API_KEY / OPENAI_API_KEY), your own env name passed explicitly, or createAIClient({ apiKey }). FirstFlow reads no credentials; it only observes the calls.
The firstflow call metadata
Add a firstflow field to any wrapped call. It is read and stripped before the request reaches the provider.
string
The end user the call belongs to. Stored on
firstflow_llm_calls.string
Groups the call into a conversation. When set with
userId, the wrap also
records the prompting user message and the assistant reply to
firstflow_conversation_messages.string
Alias for
conversationId pass whichever name your code already uses (the
browser SDK calls this shared id sessionId). When both are set,
conversationId wins.Record<string, unknown>
Arbitrary tags merged into the recorded row’s
metadata.Scope: user vs. conversation
What you pass decides the scope of what’s recorded:Bring your own OpenAI-compatible endpoint
@firstflow/runtime-server/aiclient is a pre-wrapped client for any OpenAI-compatible server (Ollama, vLLM, LM Studio, LocalAI, a gateway):
openai is an optional peer dependency loaded only by this subpath. Install it (pnpm add openai) when you use createAIClient; Anthropic-only apps never pull it in. Equivalent to new Firstflow({ persistence }).wrap(new OpenAI({ baseURL, apiKey })).
Content capture
captureContent: true on new Firstflow({...}) stores prompt and completion text in firstflow_conversation_messages. Leave it off (the default) to record only metadata tokens, cost, latency, status.