Skip to main content
The foglamp() collector needs the telemetry API introduced in AI SDK v7. On 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 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, one llm span per step, and one tool span per tool call.
  • generateObject / streamObject: a root span plus one llm span.
  • Agent classes: wrap() also returns wrapped, drop-in versions of ToolLoopAgent and Experimental_Agent. agent.generate() and agent.stream() are traced like generateText and streamText. If the agent has an id and no agentName was set, the id is used as the agentName.
  • Exact tool timing: wrap() times each tool’s execute directly, so tool spans have a real measured duration.
  • Streaming stats: for streamText, Foglamp watches the stream through the call’s onChunk callback to record time to first token and the token curve behind tokens/sec and replay. It never consumes or changes your stream.
  • Provider signals: every llm span carries what the provider reports, such as grounding sources, the OpenAI-style system_fingerprint, safety ratings, and rate-limit headroom. One difference from v7: wrap measures exact tool time but cannot separate out the model-only window, so it omits modelCallMs rather than guess.

Per-call context

Contexts stack in layers, and later layers win per field: wrap(ai, { context }) sets the default, fog.run(context, fn) sets context for a block of code, fog.with(context) binds on top of both, and a call-time foglamp option wins over all three. metadata maps merge across layers, with inner keys winning. fog.run() sets context for everything inside a callback. Use it for things scoped to one run: workflow run ids, session ids, request metadata. Every wrapped call inside the callback picks up the context, no matter how deeply nested, with no parameters to pass around. Module-level singleton agents stay singletons.
Nested run() calls merge, inner over outer. Works on Node, Bun, Deno, and Vercel or Cloudflare edge runtimes (anywhere node:async_hooks exists). The v7 collector has the same method: fog.run() layers under fog.integration().
On the v7 collector, a model call made inside a tool’s execute inherits the parent call’s workflow and session context automatically (see the SDK overview). The wrap path has no such hook, so on v4 to v6 use fog.run() to share context with a tool that calls back into the model.
fog.with() keeps your types. It returns the wrapped functions and agent classes typed exactly like the AI SDK’s originals, so generics, Output.object result types, and tool typings all survive:
You can also pass a foglamp option on any wrapped call; it is removed before the arguments reach the AI SDK. Calls that use it lose the AI SDK’s generic result types, so prefer with() when you need the typed result:
The context fields are the same as fog.integration(context): traceName, agentName, workflowName + workflowRunId, sessionId, and metadata.

Agent classes

When the module exports ToolLoopAgent (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:
Your onStepFinish and onFinish callbacks still run; Foglamp composes with them. One gap: agent streams expose no onChunk, so agent.stream() traces have no time to first token or token-curve samples (plain streamText does).

Your callbacks are preserved

If you pass onChunk, onStepFinish, onFinish, or onError to a wrapped call, your callback always runs. Foglamp’s telemetry runs alongside it and never throws into your app.

Flushing

wrap() returns flush() and shutdown() alongside the wrapped functions. Use them exactly as on the collector (see Runtimes and flushing). Serverless platforms are detected automatically, and on Vercel the invocation is kept alive with waitUntil from the runtime’s request context, with nothing to install. On other serverless platforms pass waitUntil in the config (for example 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, and so on), plus context for the default context.

Limits compared to v7

  • A client-side tool (one with no execute function, run by your app) can’t be timed directly, so its time is attributed at step boundaries rather than as an exact duration. Tools with an execute are timed precisely.
  • wrap() instruments a module you pass in; there is no global registerTelemetry on v4 to v6.