Skip to main content
The 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.
import * as ai from "ai";
import { wrap } from "foglamp/wrap";

const fog = wrap(ai, {
  context: { agentName: "support" }, // default trace context for every call
});

// Bind a context and get the AI SDK's own signatures back, fully typed.
const { generateText } = fog.with({ agentName: "summarizer" });

// Use exactly like the AI SDK — traces are captured automatically.
const { text } = await generateText({
  model: openai("gpt-4o"),
  prompt: "Summarize this ticket.",
});
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 one llm span per step and one tool span per tool execution.
  • generateObject / streamObject — a root span plus a single llm span.
  • ToolLoopAgent / Experimental_Agentwrap() also returns wrapped, drop-in versions of the module’s agent classes. agent.generate() / agent.stream() produce the same trace shape as generateText / streamText, including exact tool timing. If the agent has an id and no agentName was set, the id becomes the trace’s agentName.
  • Tool timing is exact. wrap() times each tool’s execute directly, so tool spans carry a real measured duration (not an estimate).
  • Streaming throughput. For streamText, foglamp observes the stream via the call’s onChunk callback 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 llm span — text, object, and agent steps alike — carries the provider signals it reports: grounding sources (output-gated), the OpenAI-style system_fingerprint, safety ratings, and normalized rate-limit headroom. The model-vs-tool latency split is v7-only; wrap measures exact tool timing but never a separable model-call window, so it omits modelCallMs rather 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.
// agent module: static identity, bound once
const { ToolLoopAgent } = fog.with({ agentName: "brand-analysis" });
const brandAnalysisAgent = new ToolLoopAgent({ model, tools, output });

// request handler: dynamic run context, set at the boundary
await fog.run(
  { workflowName: "brand-onboarding", workflowRunId: brandId, metadata: { brandId } },
  () => runOnboarding(brand) // every wrapped call inside is attributed, however deep
);
Nested 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:
const { generateText, ToolLoopAgent } = fog.with({
  agentName: "retriever",
  workflowName: "support-ticket",
  workflowRunId: ticket.id,
});
The 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:
await fog.generateText({
  model,
  prompt,
  foglamp: {
    traceName: "classify-email",
    sessionId: user.threadId,
    metadata: { environment: "production" },
  },
});
The context fields are identical to 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:
const { ToolLoopAgent } = fog.with({ agentName: "research" });

const agent = new ToolLoopAgent({
  model: openai("gpt-4o"),
  tools: { search },
  stopWhen: stepCountIs(5),
});
await agent.generate({ prompt }); // traced: root + llm steps + tool spans
Your settings-level 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 pass onChunk, 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.
const fog = wrap(ai, { context: { agentName: "support" } });
// … after your handler's work …
await fog.flush();

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 execute function — 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 an execute are timed precisely.
  • wrap() instruments a module you pass in; there is no global registerTelemetry equivalent on v4–v6.