TypeScript
Install¶
2-line drop-in wrapper (recommended)¶
import OpenAI from 'openai'
import { withRunForge } from '../sdk-ts/index'
const openai = withRunForge(new OpenAI({ apiKey: process.env.OPENAI_API_KEY }), {
projectId: process.env.RUNFORGE_PROJECT_ID, // optional if set in env
})
const res = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: 'Hello!' }]
})
Explicit tracker API¶
class RunForge {
constructor(opts: { apiKey: string; endpoint?: string; projectId?: string })
track<T>(metadataOrFn: Record<string, any> | (() => Promise<T>), maybeFn?: () => Promise<T>): Promise<T>
}
await rf.track({ model, experiment? }, () => client.call(...)).
- Auto‑extracts tokens and cost from provider response:
- OpenRouter: uses usage.total_cost → exact cost (costSource="provider").
- OpenAI/Anthropic: reads usage tokens; client may estimate via tokencost; server verifies and computes authoritative cost (costSource="catalog").
- Sends only usage metadata to /api/ingest. Do not send prompts/outputs.
Example (explicit tracker)¶
import OpenAI from 'openai'
import { RunForge, withRunForge } from '../sdk-ts/index'
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY! })
const rf = new RunForge({ apiKey: process.env.RUNFORGE_API_KEY!, endpoint: process.env.RUNFORGE_ENDPOINT, projectId: process.env.RUNFORGE_PROJECT_ID })
await rf.track({ model: 'gpt-4o-mini', experiment: 'chat-v2' }, () =>
openai.chat.completions.create({ model: 'gpt-4o-mini', messages: [{ role: 'user', content: 'hi' }] })
)
Streaming¶
- For OpenAI, prefer
stream_options: { include_usage: true }to receive final usage in the last chunk. - If you accumulate streamed text, compute output tokens at the end with
countStreamingOutputTokens(model, fullText)and POST once.
Options & metadata¶
metadataobject is forwarded asmetadatato ingestion; you may includeexperiment,traceId, or custom fields.- Set
traceIdyourself inmetadatato propagate an OTel trace ID (optional). - Client marks
costSource: 'estimated'andcostEstimated: true; server sets authoritative values.
Error & retries¶
- If the wrapped call throws, RunForge still emits telemetry with
status: 'error'and the error code. - Use
runIdto de‑dupe retries (ingest is idempotent byrunId).
Privacy¶
- Do not send prompts/outputs; the SDK and route are designed to handle usage metadata only.
See also: 05-apis.md, sdk-auto-extraction-guide.md