@cockpitify/node
v0.1.3
Published
Records each user's AI token usage and cost to your own Supabase project (Cockpitify).
Maintainers
Readme
@cockpitify/node
Records each user's AI token usage to ops.usage in your own Supabase project. Wrap your AI client once per request; the wrapped client behaves exactly like the original.
import OpenAI from 'npm:openai';
import { Ops } from 'npm:@cockpitify/node';
const ai = Ops.wrap(new OpenAI({ baseURL: 'https://api.deepseek.com', apiKey }), {
userId: user.id,
supabase, // service-role client; writes to ops.usage
});Using an AI coding assistant? Point it at AI.md (also in node_modules/@cockpitify/node/AI.md).
Supported clients
- OpenAI SDK and compatibles (DeepSeek, Groq, OpenRouter via
baseURL)chat.completions.create(streaming or not)chat.completions.stream()responses.create
- Anthropic SDK
messages.create(streaming or not)messages.stream()- the same two under
beta.messages
- Raw HTTP:
Ops.wrapFetch(fetch, opts). Handles OpenAI-compatible chat, Responses and Anthropic, both JSON and SSE. - Vercel AI SDK:
wrapLanguageModel({ model, middleware: Ops.middleware(opts) }).
Calls not measured in tokens
Speech-to-text, text-to-speech or image calls are recorded by hand (ops 0.17.0+):
Ops.record({ userId, supabase, provider: 'groq', model: 'whisper-large-v3', units: 742, unit: 'seconds' });
Ops.record({ userId, supabase, provider: 'elevenlabs', model: 'eleven_multilingual_v2', units: 48000, unit: 'characters', costUsd: 0.36 });Without costUsd the row stays unpriced: ops shows the amount, not a guessed cost.
Streaming
Streaming is fully supported, and chunks reach your code unchanged and without delay.
- OpenAI-compatible streams only report usage when
stream_options.include_usageis set, so it is added for you.- The extra usage-only chunk it produces is hidden from your code, unless you asked for it yourself.
- Groq is the exception: it reports usage on its own, so its requests are left untouched.
- If a stream is cut (user left,
break, abort, network error), there is no usage report from the provider. Tokens are then estimated from the prompt and the text seen so far, and the row is stored withestimated = true.
Guarantees
- Fail-open. A recording failure never throws into your code or slows the AI call. It goes to
onError, which by default logs one warning. - Writes outlive the response. On Supabase Edge Functions, writes run through
EdgeRuntime.waitUntil. Elsewhere, callawait Ops.flush()before the process exits. - Setup mistakes fail loudly. A missing
userId, or neithersupabasenorsinkgiven, throws at wrap time.
Options
| Option | |
|---|---|
| userId | Required. The end user to attribute usage to. |
| supabase / sink | Where rows go: ops.usage through a supabase-js client, or your own (row) => Promise. |
| feature | Optional tag, e.g. 'chat', 'transcribe'. |
| prices | USD per 1M tokens per model (prefix match). Without it cost_usd is null and the database prices the row. |
| provider | Overrides the provider inferred from baseURL / URL. |
| injectStreamUsage | Set false to never touch stream_options. |
| onError | Receives recording errors. |
