> ## Documentation Index
> Fetch the complete documentation index at: https://docs.foglamp.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# AI SDK v4 to v6 (wrap)

> Instrument older AI SDK versions by wrapping the module.

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.

```ts theme={null}
import * as ai from "ai";
import { wrap } from "foglamp/wrap";

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

// Bind a context and get the AI SDK's own functions 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.",
});
```

<Note>
  `wrap()` supports AI SDK v4 and later. On v7, prefer the native
  [`foglamp()`](/sdk/overview) collector. The package declares
  `ai@^4 || ^5 || ^6 || ^7.0.0-beta.1` as a peer dependency.
</Note>

## 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.

```ts theme={null}
// agent module: static identity, bound once
const { ToolLoopAgent } = fog.with({ agentName: "brand-analysis" });
const brandAnalysisAgent = new ToolLoopAgent({ model, tools, output });

// request handler: run context, set at the boundary
await fog.run(
  { workflowName: "brand-onboarding", workflowRunId: brandId, metadata: { brandId } },
  () => runOnboarding(brand) // every wrapped call inside is attributed
);
```

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()`.

<Note>
  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](/sdk/overview#nested-calls-inside-tools)). 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.
</Note>

**`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:

```ts theme={null}
const { generateText, ToolLoopAgent } = fog.with({
  agentName: "retriever",
  workflowName: "support-ticket",
  workflowRunId: ticket.id,
  customer: { id: account.id, name: account.name }, // optional, per-customer cost
});
```

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:

```ts theme={null}
await fog.generateText({
  model,
  prompt,
  foglamp: {
    traceName: "classify-email",
    sessionId: user.threadId,
    metadata: { environment: "production" },
  },
});
```

The context fields are the same as
[`fog.integration(context)`](/sdk/overview): `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`:

```ts theme={null}
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 `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](/sdk/runtimes)). 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.

```ts theme={null}
const fog = wrap(ai, { context: { agentName: "support" } });
// … after your handler's work …
await fog.flush();
```

## Configuration

`wrap(ai, options)` accepts every [configuration](/sdk/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.
