foglamp() collector relies on the telemetry-integrations API introduced in
AI SDK v7 (registerTelemetry / telemetry: { integrations }). If you’re on
AI SDK v4, v5, or v6, use the foglamp/wrap entry point instead: it wraps the
AI SDK functions and produces the same traces to the same ingest endpoint.
wrap() supports AI SDK v4 and later. On v7, prefer the native
foglamp() collector. The package declares
ai@^4 || ^5 || ^6 || ^7.0.0-beta.1 as a peer dependency.What it captures
Each wrapped call becomes one trace, with the same shape as the v7 path:generateText/streamText— a root span plus onellmspan per step and onetoolspan per tool execution.generateObject/streamObject— a root span plus a singlellmspan.ToolLoopAgent/Experimental_Agent—wrap()also returns wrapped, drop-in versions of the module’s agent classes.agent.generate()/agent.stream()produce the same trace shape asgenerateText/streamText, including exact tool timing. If the agent has anidand noagentNamewas set, theidbecomes the trace’sagentName.- Tool timing is exact.
wrap()times each tool’sexecutedirectly, so tool spans carry a real measured duration (not an estimate). - Streaming throughput. For
streamText, foglamp observes the stream via the call’sonChunkcallback to record time-to-first-token and the intra-stream token curve that powers tokens/sec and replay — without consuming or altering your stream. - Provider signals. Every
llmspan — text, object, and agent steps alike — carries the provider signals it reports: groundingsources(output-gated), the OpenAI-stylesystem_fingerprint, safety ratings, and normalized rate-limit headroom. The model-vs-tool latency split is v7-only;wrapmeasures exact tool timing but never a separable model-call window, so it omitsmodelCallMsrather than estimate it.
Per-call context
Contexts layer, later layers winning per field:wrap(ai, { context }) sets
the default, fog.run(context, fn) sets the ambient run context,
fog.with(context) binds on top of both, and a call-time foglamp option
wins over all three. metadata maps merge across layers (inner keys win)
instead of replacing each other.
fog.run() is the no-plumbing path for run-scoped context — workflow run
ids, session ids, request metadata. It stores the context for the duration of
the callback (via AsyncLocalStorage), so every wrapped call inside — however
deeply nested — picks it up with zero signature changes: no parameters to
thread through intermediate functions, and module-level singleton agents stay
singletons.
run()s merge inner-over-outer. Works on Node, Bun, Deno, and
Vercel/Cloudflare edge runtimes (anywhere node:async_hooks exists). The v7
collector has the same method: fog.run() layers under fog.integration().
with() is the TypeScript-first path — it returns the wrapped functions
and agent classes typed exactly as the AI SDK’s originals, so generics,
Output.object result types, and tool typings all survive. No type shims or
casts needed:
foglamp call option also works on any wrapped call and is stripped before
the args reach the AI SDK. Calls that pass it lose the AI SDK’s generic result
types (TypeScript can’t thread generics through the widened signature), so
prefer with() when you need the typed result:
fog.integration(context):
traceName, agentName, workflowName + workflowRunId, sessionId, and
metadata.
Agent classes
When the module exportsToolLoopAgent (v6/v7) or Experimental_Agent (v5),
wrap() returns wrapped versions with the same constructor and methods —
instrument them in place; there is no need to rewrite agent code to
generateText:
onStepFinish / onFinish callbacks still run; foglamp
composes with them. One gap: agent streams expose no onChunk, so
agent.stream() traces carry no time-to-first-token or token-curve samples
(plain streamText does).
Your callbacks are preserved
If you passonChunk, onStepFinish, onFinish, or onError to a wrapped call,
foglamp composes with them — your callback always runs; foglamp’s telemetry
runs alongside and never throws into your app.
Flushing
wrap() returns flush() and shutdown() alongside the wrapped functions —
use them exactly as you would on the collector (see
Runtimes & flushing). Serverless platforms are detected
automatically (the VERCEL / AWS_LAMBDA_FUNCTION_NAME env vars, or a
waitUntil you pass): the transport switches to per-call flushing, and on
Vercel it keeps the invocation alive by reading waitUntil straight from the
runtime’s request context — nothing to install or wire. On other serverless
platforms pass waitUntil in the config (e.g.
Cloudflare’s ctx.waitUntil) or await fog.flush() before the handler
returns.
Configuration
wrap(ai, options) accepts every configuration field the
collector does (apiKey, endpoint, recordInputs, recordOutputs,
maxPayloadChars, waitUntil, …), plus context for the wrap-time default.
Limitations vs. v7
- A client-side tool (one with no
executefunction — your app runs it) can’t be timed directly, so its activity is attributed at step boundaries rather than with an exact duration. Tools with anexecuteare timed precisely. wrap()instruments a module you pass in; there is no globalregisterTelemetryequivalent on v4–v6.

