opentelemetry-instrumentation-browserbase
v0.1.0
Published
OpenTelemetry instrumentation for Browserbase and Stagehand.
Downloads
13
Maintainers
Readme
opentelemetry-instrumentation-browserbase
OpenTelemetry API-only instrumentation helpers for Browserbase and Stagehand browser agents.
This package wraps existing SDK objects with JavaScript proxies. It does not install an OpenTelemetry SDK, exporter, or provider; the host app owns that setup.
Five-Minute Quickstart
Install the library, Stagehand, and the OpenTelemetry packages your app uses:
npm install opentelemetry-instrumentation-browserbase @browserbasehq/stagehand @opentelemetry/api @opentelemetry/api-logs @opentelemetry/sdk-nodeSet your collector endpoint with the standard OpenTelemetry environment variables used by your app, then wrap your Stagehand instance:
import { NodeSDK } from "@opentelemetry/sdk-node";
import { Stagehand } from "@browserbasehq/stagehand";
import {
instrumentStagehand,
startWorkflow,
withAgentScopeLogger
} from "opentelemetry-instrumentation-browserbase";
const sdk = new NodeSDK();
sdk.start();
const stagehand = instrumentStagehand(
new Stagehand(
withAgentScopeLogger({
env: "LOCAL",
modelName: "google/gemini-2.5-flash"
})
)
);
await startWorkflow("example.browser_task", async () => {
await stagehand.init();
await stagehand.page.goto("https://example.com");
await stagehand.observe("find the main heading");
await stagehand.extract("extract the page title");
await stagehand.close();
});
await sdk.shutdown();Two-line wrapper shape:
const stagehand = instrumentStagehand(new Stagehand(withAgentScopeLogger(options)));
await startWorkflow("checkout", () => stagehand.init());For direct Browserbase SDK calls:
npm install opentelemetry-instrumentation-browserbase @opentelemetry/api @opentelemetry/api-logsconst bb = instrumentBrowserbase(new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY }));
await bb.search.web({ query: "site example", numResults: 5 });For plain Playwright/Puppeteer on Browserbase, attach CAPTCHA telemetry directly to a page:
attachCaptchaListener(page);Public API
instrumentStagehand(stagehand, options?)instrumentBrowserbase(client, options?)startWorkflow(name, attrs, fn)getWorkflow(ctx?)attachCaptchaListener(page, options?)agentScopeLogger()andwithAgentScopeLogger(options)configureRedaction(options)configureCost(options)ATTR,SPAN, andMETRIC- Advanced helpers for custom instrumentation:
withSpan,emitLog, andrunRiskDetectors
Config Reference
| Option | Where | Default | Description |
| --- | --- | --- | --- |
| instrumentStagehand(..., { attributes }) | Stagehand wrapper | {} | Static caller attributes added to Stagehand spans. |
| instrumentStagehand(..., { captureInstructions }) | Stagehand wrapper | true | Set false to suppress natural-language instruction/action text attributes. |
| instrumentStagehand(..., { attributeHook }) | Stagehand wrapper | none | Last-chance hook to add, change, or remove span attributes before they are set. |
| instrumentStagehand(..., { riskDetectors }) | Stagehand wrapper | built-ins | Replaces the built-in pure risk detector list. |
| instrumentBrowserbase(..., { fetchFallbackContentLength }) | Browserbase wrapper | 512 | Minimum content length before Fetch is treated as suspiciously short. |
| instrumentBrowserbase(..., { captureSearchQuery }) | Browserbase wrapper | true | Set false to suppress browserbase.search.query. |
| instrumentBrowserbase(..., { attributeHook }) | Browserbase wrapper | none | Last-chance hook to add, change, or remove span attributes before they are set. |
| startWorkflow(name, attrs, fn) | Workflow root | generated run id | Creates root span and mutable workflow state. |
| configureCost({ pricing }) | Cost module | built-in table | Overrides per-model input/output/cached-input pricing. |
| configureRedaction({ extraPatterns, disableDefaults }) | Redaction module | defaults enabled | Redacts sensitive attributes before span/log emission. |
| withAgentScopeLogger(options) | Stagehand constructor options | disablePino: true | Injects the OTel log bridge into Stagehand options. |
connectUrl and seleniumRemoteUrl are dropped unconditionally by redaction. Dynamic variable
values are never recorded; only browser_agent.variables_used=true is emitted.
Stagehand support is for v3 instance-level act, observe, and extract. Stagehand v2
page-level methods are not wrapped automatically; use direct page instrumentation where
needed.
Attributes
| Attribute | Type | Description |
| --- | --- | --- |
| gen_ai.request.model | string | Model requested for an AI-backed browser operation. |
| gen_ai.usage.input_tokens | int | Input tokens used by an AI-backed browser operation. |
| gen_ai.usage.output_tokens | int | Output tokens used by an AI-backed browser operation. |
| gen_ai.usage.cached_input_tokens | int | Cached input tokens when the runtime reports them. |
| gen_ai.operation.name | string | AI operation name such as act, observe, extract, or agent.execute. |
| url.full | string | Full URL involved in the operation. |
| url.domain | string | Domain component of the URL. |
| http.response.status_code | int | HTTP response status code. |
| server.address | string | Server address contacted by the operation. |
| error.type | string | Error class or derived type name. |
| browser_agent.failure.origin | string | page, model, or infra; unknown errors default to infra. |
| browser_agent.workflow.name | string | Caller-provided workflow name. |
| browser_agent.run_id | string | Workflow correlation id. |
| browser_agent.status | string | Workflow status: ok or error. |
| browser_agent.action.method | string | Browser action method. |
| browser_agent.action.selector | string | Selector or element reference used for an action. |
| browser_agent.action.success | boolean | Whether a browser action succeeded. |
| browser_agent.action.self_healed | boolean | Whether an action recovered by using an alternate target; emitted only when the Stagehand result exposes it. |
| browser_agent.action.description | string | Natural-language action instruction or description. |
| browser_agent.cache.status | string | Conditional cache state for AI-planned work; local Stagehand runs usually do not expose it. |
| browser_agent.extract.schema | string | Schema name or description used for extraction. |
| browser_agent.extract.result_count | int | Count of extraction results. |
| browser_agent.extract.empty | boolean | Whether extraction returned no usable content. |
| browser_agent.extract.screenshot_used | boolean | Whether screenshot input was requested or used. |
| browser_agent.extract.scoped | boolean | Whether extraction was scoped to a selector or sub-tree. |
| browser_agent.observe.instruction | string | Natural-language observation instruction. |
| browser_agent.observe.action_count | int | Number of candidate actions returned. |
| browser_agent.auth.captcha.encountered | boolean | Whether a CAPTCHA challenge was observed. |
| browser_agent.auth.captcha.solved | boolean | Whether the CAPTCHA challenge was solved. |
| browser_agent.auth.captcha.duration_ms | int | CAPTCHA solve duration in milliseconds. |
| browser_agent.auth.context.reused | boolean | Whether persisted browser state was reused. |
| browser_agent.auth.login_avoided | boolean | Whether state reuse avoided an interactive login. |
| browser_agent.auth.state | string | Auth state such as valid, expired, or missing. |
| browser_agent.path | string | Browser path selected for the operation: fetch or session. |
| browser_agent.path.fallback_reason | string | Reason for changing path. |
| browser_agent.risk.categories | string[] | Risk categories detected during the workflow. |
| browser_agent.risk.injection | boolean | Instruction-injection risk flag. |
| browser_agent.risk.credentials | boolean | Credential-solicitation risk flag. |
| browser_agent.risk.secrets | boolean | Secret-material risk flag. |
| browser_agent.risk.loop | boolean | Repeated-action loop risk flag. |
| browser_agent.risk.domain | boolean | Unexpected-domain risk flag. |
| browser_agent.risk.max_severity | string | Maximum risk severity. |
| browser_agent.risk.finding_count | int | Number of findings produced by a risk scan. |
| browser_agent.variables_used | boolean | Whether dynamic variables were supplied; values are never recorded. |
| browserbase.session_id | string | Browserbase session identifier. |
| browserbase.session_url | string | Browserbase session URL. |
| browserbase.debug_url | string | Browserbase live debugging URL. |
| browserbase.replay_url | string | Browserbase replay URL. |
| browserbase.region | string | Browserbase region. |
| browserbase.request_id | string | Browserbase API request id. |
| browserbase.proxy_bytes | int | Bytes transferred through Browserbase proxy infrastructure. |
| browserbase.context_id | string | Browserbase context id. |
| browserbase.search.query | string | Redacted Browserbase Search query. |
| browserbase.search.requested_count | int | Requested Search result count. |
| browserbase.search.returned_count | int | Returned Search result count. |
| browserbase.search.result_domains | string[] | Distinct domains returned by Search. |
| browserbase.fetch.content_type | string | Fetch response content type. |
| browserbase.fetch.encoding | string | Fetch response encoding. |
| browserbase.fetch.size | int | Fetch response size. |
| browserbase.session.status | string | Browserbase session status. |
| browserbase.session.duration_ms | int | Session or session-operation duration in milliseconds. |
| browserbase.replay.page_count | int | Number of replay pages returned. |
| browserbase.logs.event_count | int | Conditional count emitted only when session-log ingestion runs. |
| browserbase.logs.network_error_count | int | Conditional count emitted only when session-log ingestion runs. |
| browserbase.logs.console_error_count | int | Conditional count emitted only when session-log ingestion runs. |
| browserbase.usage.browser_minutes | double | Browser minutes from usage APIs when available; current usage responses can be zero and do not drive duration panels. |
| llm.cost.usd | double | Estimated LLM cost in USD. |
| llm.cost.unknown_model | boolean | Whether cost could not be computed for the model. |
| llm.gateway | string | Gateway or provider prefix used on LLM cost metrics. |
Metrics
| Instrument | Type | Unit | Dimensions |
| --- | --- | --- | --- |
| browser_agent.workflow.duration | Histogram | ms | workflow name, run id |
| browser_agent.action.count | Counter | {action} | caller attributes |
| browser_agent.extract.result_count | Histogram | {result} | caller attributes |
| browser_agent.extract.empty.count | Counter | {call} | caller attributes |
| browser_agent.auth.captcha.count | Counter | {challenge} | workflow/session attributes |
| browser_agent.auth.captcha.duration | Histogram | ms | workflow/session attributes |
| browserbase.proxy.bytes | Counter | By | workflow/session attributes |
| browserbase.session.duration | Histogram | ms | workflow/session attributes |
| llm.cost.usd | Counter | USD | gen_ai.request.model, llm.gateway |
Logs
Risk findings and Stagehand bridge messages emit real OTel log records through
@opentelemetry/api-logs. They no-op until the host app registers a logs provider.
Risk logs use a structured body:
{
"risk.category": "injection",
"risk.severity": "high",
"risk.evidence": "[redacted or excerpt]",
"risk.source": "browser_agent.risk.scan"
}Logs are emitted inside the active span context.
CAPTCHA Telemetry
Browserbase emits page console messages named browserbase-solving-started and
browserbase-solving-finished when its CAPTCHA solver runs. This package converts those
messages into browserbase.captcha.solve spans with browser_agent.auth.captcha.*
attributes.
Runtime support observed with Stagehand v3.7.1:
env: "BROWSERBASE"with default hosted Stagehand API mode does not expose those page console events to the local Node process. Solve-duration telemetry is not available in that mode.env: "BROWSERBASE", disableAPI: truedrives the Browserbase session through a local CDP/Playwright connection. In this mode the console events are visible andbrowserbase.captcha.solvespans include a real duration.- Plain Browserbase SDK plus Playwright-over-CDP can also observe the console events when
the listener is attached to the Playwright
Page.
If your runtime hides page console events, report only challenge encounter signals derived from DOM behavior. Do not invent solve durations.
Known Runtime Behaviors
- Stagehand v3.7.1 rejects a top-level
contextId. The working shape is:
new Stagehand({
env: "BROWSERBASE",
browserbaseSessionCreateParams: {
browserSettings: {
context: { id: contextId, persist: true }
}
}
});- Stagehand's Browserbase page wrapper does not expose every Playwright convenience method
in every runtime. In particular,
fill()was not available in Browserbase mode during field testing; use Stagehand actions, DOM evaluation, or an underlying Playwright page when exact control is required. - Browserbase-mode self-heal can attempt to use a local provider key. Disable self-heal or provide a real provider key when using that path.
- Failed Stagehand actions return
ActResult.success === falsewith a message instead of throwing. Instrumentation treats that as an error span and deriveserror.typefrom the returned message. - Node auto HTTP instrumentation can capture Browserbase
signingKeyquery parameters from replay/CDP URLs unless query redaction is configured in the host app. Configure the host app's HTTP instrumentationredactedQueryParamsto includesigningKey,token,api_key,apikey,key,secret, andsignature. - Duplicate
@opentelemetry/api-logsversions can silently swallow log records because the app and library may use different global API singletons. Keep a single resolved version across the workspace.
Roadmap
The following are intentionally not part of the frozen dashboard schema:
sessions.downloads.listfile artifact summaries.- Session log event ingestion as individual OTel log records.
- Full context CRUD coverage beyond the current reusable-context signals.
- Certificate and extension wrapping.
- Browserbase Agents API and Runs API wrapping.
- CDP-layer instrumentation for direct Playwright/Puppeteer users.
- Replay timestamp deep-linking.
- LLM-backed risk detector backends.
- Usage API panels. Keep the wrapper summary, but do not depend on usage values for session duration.
