diff --git a/.changeset/swift-taxis-listen.md b/.changeset/swift-taxis-listen.md new file mode 100644 index 000000000..67390d991 --- /dev/null +++ b/.changeset/swift-taxis-listen.md @@ -0,0 +1,6 @@ +--- +"@browserbasehq/stagehand": patch +"@browserbasehq/stagehand-python": patch +--- + +Track the Stagehand SDK language in Browserbase session and runtime metadata. diff --git a/packages/sdk-python/src/stagehand/rpc_client.py b/packages/sdk-python/src/stagehand/rpc_client.py index 5fa24d088..6f658c9a6 100644 --- a/packages/sdk-python/src/stagehand/rpc_client.py +++ b/packages/sdk-python/src/stagehand/rpc_client.py @@ -5,6 +5,7 @@ import json from collections.abc import Awaitable, Callable, Coroutine from contextlib import suppress +from importlib.metadata import version from typing import Annotated, Literal, Protocol, TypeVar, cast from pydantic import BaseModel, ConfigDict, Field, JsonValue, TypeAdapter, ValidationError @@ -13,6 +14,10 @@ _MAX_REQUEST_ID = 9_007_199_254_740_991 _MAX_PENDING_NOTIFICATIONS = 100 +_STAGEHAND_SDK_CLIENT_INFO = models.ImplementationInfo( + name="stagehand-sdk-python", + version=version("stagehand"), +) ParamsT = TypeVar("ParamsT", bound=BaseModel) ResultT = TypeVar("ResultT", bound=BaseModel) @@ -479,6 +484,7 @@ async def connect_rpc_client( ) client = RPCClient(cdp, request_timeout_ms=command_timeout_ms) configure = models.RuntimeConfigureParams( + client_info=_STAGEHAND_SDK_CLIENT_INFO, cdp_url=cdp.web_socket_debugger_url, **({"telemetry": telemetry} if telemetry is not None else {}), log_level=models.LogLevel(log_level), diff --git a/packages/sdk-python/tests/test_rpc_client.py b/packages/sdk-python/tests/test_rpc_client.py index 8b14c413c..cc905553b 100644 --- a/packages/sdk-python/tests/test_rpc_client.py +++ b/packages/sdk-python/tests/test_rpc_client.py @@ -1,4 +1,5 @@ import asyncio +from importlib.metadata import version from typing import ClassVar, cast import pytest @@ -487,6 +488,10 @@ async def test_connect_rpc_client_passes_cdp_options_and_configures_the_runtime( "id": 1, "method": "runtime.configure", "params": { + "client_info": { + "name": "stagehand-sdk-python", + "version": version("stagehand"), + }, "cdp_url": "ws://resolved.example/devtools/browser/1", "log_level": "info", "telemetry": { diff --git a/packages/sdk-ts/src/browserbaseSession.ts b/packages/sdk-ts/src/browserbaseSession.ts index 9e889982b..31cd1e1aa 100644 --- a/packages/sdk-ts/src/browserbaseSession.ts +++ b/packages/sdk-ts/src/browserbaseSession.ts @@ -7,6 +7,7 @@ import { type BrowserbaseExtensionSdk, type ProvisionedBrowserbaseExtension, } from "./browserbaseExtension.js"; +import { STAGEHAND_SESSION_METADATA } from "./sdkIdentity.js"; export type BrowserbaseSessionClient = { createSession( @@ -55,6 +56,10 @@ export function createBrowserbaseSessionClient( session = await browserbase.createSession({ ...params, extensionId: extension.extensionId, + userMetadata: { + ...params.userMetadata, + ...STAGEHAND_SESSION_METADATA, + }, }); } catch (error) { await extension.cleanup().catch(() => undefined); diff --git a/packages/sdk-ts/src/rpcClient.ts b/packages/sdk-ts/src/rpcClient.ts index 39e2d5963..c661b3081 100644 --- a/packages/sdk-ts/src/rpcClient.ts +++ b/packages/sdk-ts/src/rpcClient.ts @@ -42,7 +42,7 @@ import { import type { StagehandRpcNotification } from "../../protocol/types.js"; import { z } from "zod/v4"; import { CDPClient, type ServiceWorkerInfo } from "./cdpClient.js"; -import { STAGEHAND_SDK_VERSION } from "./version.js"; +import { STAGEHAND_SDK_CLIENT_INFO } from "./sdkIdentity.js"; type PendingRequest = { method: RPCMethod; @@ -59,10 +59,6 @@ type RegisteredRequestHandler = { const TRACER = trace.getTracer("@browserbasehq/stagehand"); const W3C_TRACE_CONTEXT_PROPAGATOR = new W3CTraceContextPropagator(); const MAX_PENDING_NOTIFICATIONS = 100; -const STAGEHAND_SDK_CLIENT_INFO = { - name: "stagehand-sdk-ts", - version: STAGEHAND_SDK_VERSION, -} as const; const RPCClientOptionsBaseSchema = z .object({ diff --git a/packages/sdk-ts/src/sdkIdentity.ts b/packages/sdk-ts/src/sdkIdentity.ts new file mode 100644 index 000000000..8bff13992 --- /dev/null +++ b/packages/sdk-ts/src/sdkIdentity.ts @@ -0,0 +1,17 @@ +import { STAGEHAND_SDK_VERSION } from "./version.js"; + +const STAGEHAND_SDK_IDENTITY = { + name: "stagehand-sdk-ts", + language: "typescript", + version: STAGEHAND_SDK_VERSION, +} as const; + +export const STAGEHAND_SDK_CLIENT_INFO = { + name: STAGEHAND_SDK_IDENTITY.name, + version: STAGEHAND_SDK_IDENTITY.version, +} as const; + +export const STAGEHAND_SESSION_METADATA = { + stagehand: "true", + stagehand_sdk_language: STAGEHAND_SDK_IDENTITY.language, +} as const; diff --git a/packages/sdk-ts/tests/browserbaseSession.test.ts b/packages/sdk-ts/tests/browserbaseSession.test.ts index e112fd930..dc1e60903 100644 --- a/packages/sdk-ts/tests/browserbaseSession.test.ts +++ b/packages/sdk-ts/tests/browserbaseSession.test.ts @@ -52,7 +52,11 @@ describe("Browserbase session creation", () => { const session = await client.createSession({ keepAlive: false, region: "eu-central-1", - userMetadata: { suite: "unit" }, + userMetadata: { + stagehand: "false", + stagehand_sdk_language: "python", + suite: "unit", + }, }); expect(provisionExtension).toHaveBeenCalledWith(browserbase); @@ -60,7 +64,11 @@ describe("Browserbase session creation", () => { extensionId: "ext_stagehand", keepAlive: false, region: "eu-central-1", - userMetadata: { suite: "unit" }, + userMetadata: { + stagehand: "true", + stagehand_sdk_language: "typescript", + suite: "unit", + }, }); expect(session.cdpUrl).toBe("wss://connect.browserbase.com/devtools/browser/session_123"); expect(session.sessionId).toBe("session_123");