Skip to content

Commit 2abf5b9

Browse files
authored
Make projectId optional for Browserbase sessions (browserbase#1800)
1 parent a9424ff commit 2abf5b9

13 files changed

Lines changed: 92 additions & 44 deletions

File tree

.changeset/optional-project-id.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@browserbasehq/stagehand": patch
3+
---
4+
5+
Make projectId optional for Browserbase sessions — only BROWSERBASE_API_KEY is required

packages/core/lib/v3/api.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export function getApiUrlForRegion(
8484
*/
8585
interface StagehandAPIConstructorParams {
8686
apiKey: string;
87-
projectId: string;
87+
projectId?: string;
8888
logger: (message: LogLine) => void;
8989
/**
9090
* When true, enables server-side caching by default for all requests.
@@ -174,7 +174,7 @@ interface ClientObserveParameters {
174174

175175
export class StagehandAPIClient {
176176
private apiKey: string;
177-
private projectId: string;
177+
private projectId?: string;
178178
private sessionId?: string;
179179
private modelApiKey: string;
180180
private modelProvider?: string;
@@ -844,7 +844,7 @@ export class StagehandAPIClient {
844844
): Promise<Response> {
845845
const defaultHeaders: Record<string, string> = {
846846
"x-bb-api-key": this.apiKey,
847-
"x-bb-project-id": this.projectId,
847+
...(this.projectId ? { "x-bb-project-id": this.projectId } : {}),
848848
"x-bb-session-id": this.sessionId,
849849
// we want real-time logs, so we stream the response
850850
"x-stream-response": "true",

packages/core/lib/v3/launch/browserbase.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { getEnvTimeoutMs, withTimeout } from "../timeoutConfig.js";
88

99
export async function createBrowserbaseSession(
1010
apiKey: string,
11-
projectId: string,
11+
projectId?: string,
1212
params?: BrowserbaseSessionCreateParams,
1313
resumeSessionId?: string,
1414
): Promise<{ ws: string; sessionId: string; bb: Browserbase }> {
@@ -50,8 +50,9 @@ export async function createBrowserbaseSession(
5050
} = params ?? {};
5151

5252
// satisfies check ensures our BrowserbaseSessionCreateParamsSchema stays in sync with SDK
53+
const resolvedProjectId = overrideProjectId ?? projectId;
5354
const createPayload = {
54-
projectId: overrideProjectId ?? projectId,
55+
...(resolvedProjectId ? { projectId: resolvedProjectId } : {}),
5556
...rest,
5657
browserSettings: {
5758
...(browserSettings ?? {}),

packages/core/lib/v3/shutdown/supervisor.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ const cleanupBrowserbase = async (
118118
cfg: Extract<ShutdownSupervisorConfig, { kind: "STAGEHAND_API" }>,
119119
reason: string,
120120
) => {
121-
if (!cfg.apiKey || !cfg.projectId || !cfg.sessionId) return;
121+
if (!cfg.apiKey || !cfg.sessionId) return;
122122
try {
123123
console.error(
124124
`[shutdown-supervisor] Ending Browserbase session ${cfg.sessionId} ` +
@@ -127,8 +127,8 @@ const cleanupBrowserbase = async (
127127
const bb = new Browserbase({ apiKey: cfg.apiKey });
128128
await bb.sessions.update(cfg.sessionId, {
129129
status: "REQUEST_RELEASE",
130-
projectId: cfg.projectId,
131-
});
130+
...(cfg.projectId ? { projectId: cfg.projectId } : {}),
131+
} as Browserbase.Sessions.SessionUpdateParams);
132132
} catch {
133133
// best-effort cleanup
134134
}

packages/core/lib/v3/types/private/shutdown.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export type ShutdownSupervisorConfig =
1414
kind: "STAGEHAND_API";
1515
sessionId: string;
1616
apiKey: string;
17-
projectId: string;
17+
projectId?: string;
1818
};
1919

2020
export interface ShutdownSupervisorHandle {

packages/core/lib/v3/v3.ts

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -886,12 +886,6 @@ export class V3 {
886886

887887
if (this.opts.env === "BROWSERBASE") {
888888
const { apiKey, projectId } = this.requireBrowserbaseCreds();
889-
if (!apiKey || !projectId) {
890-
throw new MissingEnvironmentVariableError(
891-
"BROWSERBASE_API_KEY and BROWSERBASE_PROJECT_ID",
892-
"Browserbase environment",
893-
);
894-
}
895889
this.logger({
896890
category: "init",
897891
message: "Starting browserbase session",
@@ -912,18 +906,25 @@ export class V3 {
912906
logger: this.logger,
913907
serverCache: this.opts.serverCache,
914908
});
909+
const {
910+
projectId: overrideProjectId,
911+
browserSettings,
912+
userMetadata,
913+
...restSessionParams
914+
} = effectiveSessionParams;
915+
const resolvedProjectId = overrideProjectId ?? projectId;
915916
const createSessionPayload = {
916-
projectId: effectiveSessionParams.projectId ?? projectId,
917-
...effectiveSessionParams,
917+
...(resolvedProjectId ? { projectId: resolvedProjectId } : {}),
918+
...restSessionParams,
918919
browserSettings: {
919-
...(effectiveSessionParams.browserSettings ?? {}),
920-
viewport: effectiveSessionParams.browserSettings?.viewport ?? {
920+
...(browserSettings ?? {}),
921+
viewport: browserSettings?.viewport ?? {
921922
width: 1288,
922923
height: 711,
923924
},
924925
},
925926
userMetadata: {
926-
...(effectiveSessionParams.userMetadata ?? {}),
927+
...(userMetadata ?? {}),
927928
stagehand: "true",
928929
},
929930
};
@@ -1474,7 +1475,10 @@ export class V3 {
14741475
}
14751476

14761477
/** Guard: ensure Browserbase credentials exist in options. */
1477-
private requireBrowserbaseCreds(): { apiKey: string; projectId: string } {
1478+
private requireBrowserbaseCreds(): {
1479+
apiKey: string;
1480+
projectId?: string;
1481+
} {
14781482
let { apiKey, projectId } = this.opts;
14791483

14801484
// Fall back to environment variables if not explicitly provided
@@ -1484,19 +1488,16 @@ export class V3 {
14841488
projectId =
14851489
process.env.BROWSERBASE_PROJECT_ID ?? process.env.BB_PROJECT_ID;
14861490

1487-
if (!apiKey || !projectId) {
1488-
const missing: string[] = [];
1489-
if (!apiKey) missing.push("BROWSERBASE_API_KEY");
1490-
if (!projectId) missing.push("BROWSERBASE_PROJECT_ID");
1491+
if (!apiKey) {
14911492
throw new MissingEnvironmentVariableError(
1492-
missing.join(", "),
1493+
"BROWSERBASE_API_KEY",
14931494
"Browserbase",
14941495
);
14951496
}
14961497

14971498
// Cache resolved values back into opts for consistency
14981499
this.opts.apiKey = apiKey;
1499-
this.opts.projectId = projectId;
1500+
if (projectId) this.opts.projectId = projectId;
15001501

15011502
// Informational log
15021503
this.logger({

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
"dependencies": {
7070
"@ai-sdk/provider": "^2.0.0",
7171
"@anthropic-ai/sdk": "0.39.0",
72-
"@browserbasehq/sdk": "^2.4.0",
72+
"@browserbasehq/sdk": "^2.7.0",
7373
"@google/genai": "^1.22.0",
7474
"@langchain/openai": "^0.4.4",
7575
"@modelcontextprotocol/sdk": "^1.17.2",

packages/server-v3/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"gen:openapi": "tsx scripts/gen-openapi.ts"
2222
},
2323
"dependencies": {
24-
"@browserbasehq/sdk": "^2.4.0",
24+
"@browserbasehq/sdk": "^2.7.0",
2525
"@browserbasehq/stagehand": "workspace:*",
2626
"@fastify/cors": "^11.0.1",
2727
"@fastify/swagger": "^9.6.1",

packages/server-v3/src/routes/v1/sessions/start.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ const startRouteHandler: RouteHandler = withErrorHandling(
111111
bbApiKey = getOptionalHeader(request, "x-bb-api-key");
112112
bbProjectId = getOptionalHeader(request, "x-bb-project-id");
113113

114-
if (!bbApiKey || !bbProjectId) {
114+
if (!bbApiKey) {
115115
return error(
116116
reply,
117117
"Missing required headers for browserbase sessions",
@@ -131,8 +131,10 @@ const startRouteHandler: RouteHandler = withErrorHandling(
131131
return error(reply, "Browserbase session missing connectUrl");
132132
}
133133
} else {
134+
const resolvedProjectId =
135+
browserbaseSessionCreateParams?.projectId ?? bbProjectId;
134136
const createPayload = {
135-
projectId: browserbaseSessionCreateParams?.projectId ?? bbProjectId,
137+
...(resolvedProjectId ? { projectId: resolvedProjectId } : {}),
136138
...browserbaseSessionCreateParams,
137139
browserSettings: {
138140
...(browserbaseSessionCreateParams?.browserSettings ?? {}),

packages/server-v3/test/integration/v3/start.test.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -690,10 +690,47 @@ describe("POST /v1/sessions/start - V3 format", () => {
690690
},
691691
);
692692

693-
// Should fail because browserbase requires x-bb-api-key and x-bb-project-id headers
693+
// Should fail because browserbase requires x-bb-api-key header
694694
assertFetchStatus(ctx, HTTP_BAD_REQUEST, "Request should fail with 400");
695695
});
696696

697+
it("should start browserbase session with API key but no project ID", async () => {
698+
if (!bbApiKey) return; // skip when credentials unavailable
699+
700+
const url = getBaseUrl();
701+
const bbHeaders = {
702+
...getHeaders("3.0.0"),
703+
"x-bb-api-key": bbApiKey,
704+
// intentionally omitting x-bb-project-id
705+
};
706+
707+
const ctx = await fetchWithContext<StartResponse>(
708+
`${url}/v1/sessions/start`,
709+
{
710+
method: "POST",
711+
headers: bbHeaders,
712+
body: JSON.stringify({
713+
modelName: "gpt-4.1-nano",
714+
browser: { type: "browserbase" },
715+
}),
716+
},
717+
);
718+
719+
assertFetchStatus(
720+
ctx,
721+
HTTP_OK,
722+
"Request should succeed without project ID",
723+
);
724+
assertFetchOk(ctx.body !== null, "Should have response body", ctx);
725+
assertFetchOk(
726+
isSuccessResponse(ctx.body),
727+
"Should return a successful start response",
728+
ctx,
729+
);
730+
731+
await endSession(ctx.body.data.sessionId, bbHeaders);
732+
});
733+
697734
// =============================================================================
698735
// Multi-Region Support Tests
699736
// =============================================================================

0 commit comments

Comments
 (0)