OpenAI adapter
@receipta/openai wraps the openai SDK (v5+, including current v6) so every chat completion emits a receipt.
How it works
No fork. The OpenAI SDK accepts a fetch constructor option and invokes it per HTTP attempt (including retries — verified firsthand). receipta injects a fetch that:
- reads the request body (model, messages, params),
- delegates to the real
fetch, clone()s the response and reads the clone (the original stays unconsumed so the SDK's own parser sees an intact body — non-interference),- builds a receipt with usage, model, and the
x-request-idheader, - appends it to the store,
- returns the original response.
Receipt emission is wrapped in try/catch — it never throws into your call.
Usage
ts
import OpenAI from 'openai';
import { withReceipts } from '@receipta/openai';
import { openStore, generateKeyPair } from '@receipta/core';
const store = await openStore('./receipts.log.receipta');
const signer = generateKeyPair();
const client = withReceipts(
OpenAI,
{ apiKey: process.env.OPENAI_API_KEY! },
{
store,
signer,
actor: { type: 'service', id: 'my-app' },
captureMode: 'full', // or "metadata_only" to omit content
},
);
const res = await client.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: 'Hello' }],
});What gets captured
provider:"openai"model: from the response bodyusage:prompt_tokens/completion_tokens(mapped toinput_tokens/output_tokens)request_id: thex-request-idheaderattempt_index: best-effort, sourced from the Stainless SDK'sx-stainless-retry-countrequest header (0on the first attempt, incrementing on retry); omitted when the header is absentoutcome:successfor 2xx,errorotherwisecontent: the request + response bodies (whencaptureMode: "full")content_commitments: HMAC-SHA256 over request/response (keyed, not bare digests)
Streaming
For streaming responses, the fetch wrapper buffers the cloned SSE stream and computes the output commitment over the final assembled message (not intermediate chunks).