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.
The end user the call belongs to. Stored on
firstflow_llm_calls.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.Alias for
conversationId pass whichever name your code already uses (the
browser SDK calls this shared id sessionId). When both are set,
conversationId wins.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.