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

# Browser (JavaScript)

> Trace an AI agent that runs in the browser, with a write-only key locked to your domains.

`@bryel/browser` traces AI-SDK calls that run **in the browser** — when your agent
issues model calls from client code rather than a server. Same OpenInference
spans as the server SDK, batched and non-blocking, with no app-boot cost.

<Note>If your model calls run on a server, use [TypeScript](/sdk/typescript) instead — it adds nothing to your client bundle.</Note>

<Steps>
  <Step title="Create a browser key">
    In the dashboard, open **Developer → API keys → Browser key** and list the
    origins your app runs on (e.g. `https://app.example.com`). You get a
    publishable `bkp_…` key: write-only and locked to those origins, so it is
    safe to ship in client code.
  </Step>

  <Step title="Install">
    <CodeGroup>
      ```bash bun theme={"theme":{"light":"github-light","dark":"github-dark"}}
      bun add @bryel/browser
      ```

      ```bash npm theme={"theme":{"light":"github-light","dark":"github-dark"}}
      npm i @bryel/browser
      ```

      ```bash pnpm theme={"theme":{"light":"github-light","dark":"github-dark"}}
      pnpm add @bryel/browser
      ```

      ```bash yarn theme={"theme":{"light":"github-light","dark":"github-dark"}}
      yarn add @bryel/browser
      ```
    </CodeGroup>
  </Step>

  <Step title="Create a tracer and pass it per call">
    Build the tracer where you trace — not at app boot — and hand it to the AI
    SDK. Spans flush in the background.

    ```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
    import { createBryelTracer } from "@bryel/browser";
    import { streamText } from "ai";

    const tracer = createBryelTracer({ apiKey: "bkp_…", serviceName: "studio-agent" });

    await streamText({
      model,
      experimental_telemetry: { isEnabled: true, tracer, metadata: { sessionId, userId } },
      prompt,
    });
    ```
  </Step>
</Steps>

## Why it won't slow your app

* **No app-boot cost.** With `createBryelTracer` you build the tracer only where
  you trace; nothing runs at startup, and the package lazy-imports.
* **Non-blocking.** Spans batch and POST in the background, off your request path
  — calls never wait on export, and export failures are swallowed.
* **\~21 KB gzipped**, all-in. Lazy-load it so it stays off your critical path.

## Excluding traffic

Capture is per call. Gate `isEnabled` on your own logic to skip a cohort — e.g.
`isEnabled: tier !== "enterprise"` — and those calls emit no spans; their data
never leaves the browser.

## Global registration (optional)

If your app has no other OpenTelemetry setup, register a global provider and drop
the `tracer`:

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { registerBryelBrowser } from "@bryel/browser";

registerBryelBrowser({ apiKey: "bkp_…", serviceName: "studio-agent" });
// then: experimental_telemetry: { isEnabled: true }
```

<Warning>Use a publishable `bkp_` key in the browser — never a secret `bk_` key. A browser bundle is public; `bkp_` keys are write-only and origin-locked.</Warning>

## API

### `createBryelTracer(options): Tracer`

Returns an OpenTelemetry `Tracer` to pass into `experimental_telemetry.tracer`.
No global registration — build it where you trace.

### `registerBryelBrowser(options): WebTracerProvider`

Registers a global browser provider; returns it for `forceFlush()` / `shutdown()`.

### Options

<ParamField path="apiKey" type="string" required>A **publishable** key (`bkp_…`) — write-only, origin-locked. Never a secret `bk_` key.</ParamField>
<ParamField path="endpoint" type="string" default="https://ingest.eu.bryel.ai/v1/traces">OTLP/HTTP endpoint. Override for self-hosting.</ParamField>
<ParamField path="serviceName" type="string">OpenTelemetry `service.name`.</ParamField>
<ParamField path="headers" type="Record<string, string>">Extra headers, merged after auth.</ParamField>
