Skip to content

Comments

[STG-1182] cache config#1581

Merged
sameelarif merged 18 commits intomainfrom
sameelarif/stg-1182-cache-bypass-from-sdk
Feb 24, 2026
Merged

[STG-1182] cache config#1581
sameelarif merged 18 commits intomainfrom
sameelarif/stg-1182-cache-bypass-from-sdk

Conversation

@sameelarif
Copy link
Member

@sameelarif sameelarif commented Jan 20, 2026

why

Once API-side caching is deployed, the feature will be opt-out. Users should have a way to disable the cache from the client.

what changed

Added a serverCache parameter to the constructor which adds the x-bb-skip-cache header to all outbound API requests. This parameter is also present on act, extract, and observe functions to allow for further configuration.

test plan


Summary by cubic

Adds global and per-request control of server-side caching for act/extract/observe in Browserbase mode. Caching is on by default; you can opt out. HIT/MISS is logged, and results include cacheStatus (Linear STG-1182).

  • New Features

    • Added serverCache to V3Options and act/extract/observe options (default true). Sends browserbase-cache-bypass when false, reads browserbase-cache-status and SSE cacheHit, logs HIT/MISS, and sets cacheStatus on ActResult/ExtractResult/ObserveResult. Observe now returns ObserveResult (Action[] with optional cacheStatus).
    • Updated public types and docs. Expanded tests with server integration checks for bypass header behavior and cache-status semantics (including HIT on repeated identical requests), plus ActCache variable-key handling/replay.
  • Bug Fixes

    • Fixed cache miss logging when variables are missing during ActCache replay, and suppressed cache status logging when a request explicitly bypasses the cache.

Written for commit b35d29e. Summary will update on new commits. Review in cubic


Note

Medium Risk
Touches the Browserbase API request/response path (new headers, SSE parsing behavior, and result type shape), which could affect caching behavior or backwards compatibility for consumers relying on exact response objects.

Overview
Adds server-side caching support for Browserbase API mode with a new serverCache flag on V3Options (instance default) and per-call overrides in act(), extract(), and observe().

StagehandAPIClient now sends browserbase-cache-bypass: true when caching is disabled, reads browserbase-cache-status (and SSE cacheHit) to log HIT/MISS, and attaches cacheStatus?: "HIT" | "MISS" onto ActResult and ExtractResult.

Updates public types/tests, adds new unit tests for ActCache variable-key behavior, and refreshes docs + changeset to announce server-side caching and the opt-out mechanism.

Written by Cursor Bugbot for commit b5ce917. This will update automatically on new commits. Configure here.

@changeset-bot
Copy link

changeset-bot bot commented Jan 20, 2026

🦋 Changeset detected

Latest commit: b35d29e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@browserbasehq/stagehand Patch
@browserbasehq/stagehand-evals Patch
@browserbasehq/stagehand-server Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@sameelarif sameelarif changed the title [STG-1182] SDK cache bypass [STG-1182 cache bypass Jan 20, 2026
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 4 files

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 20, 2026

Greptile Summary

This PR adds cache bypass functionality to the SDK by introducing a disableCaching parameter at both the instance level (constructor) and per-method level (act(), extract(), observe()). When enabled, the SDK adds an x-bb-skip-cache: true header to API requests, allowing users to opt out of server-side caching.

  • Instance-level disableCaching in V3Options applies to all requests by default
  • Method-level disableCaching in options for act(), extract(), and observe() allows per-request override
  • The shouldSkipCache() helper implements proper precedence: method-level setting overrides instance-level setting
  • All parameter additions include clear JSDoc documentation

The implementation is clean and follows the existing patterns in the codebase. Cache control is appropriately scoped to the three AI-powered methods (act, extract, observe) where server-side caching is most relevant, while navigation and agent execution remain unaffected.

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • The implementation is straightforward and additive-only, adding optional parameters with sensible defaults (false). The precedence logic is correct (method-level overrides instance-level), and the changes follow existing patterns in the codebase. No breaking changes or security concerns.
  • No files require special attention

Important Files Changed

Filename Overview
packages/core/lib/v3/types/public/options.ts added optional disableCaching parameter to V3Options interface with clear documentation
packages/core/lib/v3/types/public/methods.ts added optional disableCaching parameter to ActOptions, ExtractOptions, and ObserveOptions with documentation
packages/core/lib/v3/v3.ts passed disableCaching from options to StagehandAPIClient constructor
packages/core/lib/v3/api.ts implemented cache bypass logic with instance and method-level control, added x-bb-skip-cache header when caching is disabled

Sequence Diagram

sequenceDiagram
    participant User
    participant V3
    participant StagehandAPIClient
    participant API as Stagehand API

    Note over User,API: Instance-level cache control
    User->>V3: new V3({ disableCaching: true })
    V3->>StagehandAPIClient: new StagehandAPIClient({ disableCaching: true })
    Note over StagehandAPIClient: this.disableCaching = true

    Note over User,API: Method-level cache control (overrides instance)
    User->>V3: act(instruction, { disableCaching: false })
    V3->>StagehandAPIClient: act({ options: { disableCaching: false } })
    StagehandAPIClient->>StagehandAPIClient: Extract disableCaching from options
    StagehandAPIClient->>StagehandAPIClient: execute({ method: "act", disableCaching: false })
    StagehandAPIClient->>StagehandAPIClient: shouldSkipCache(false)
    Note over StagehandAPIClient: Returns false (method override)
    StagehandAPIClient->>API: POST /sessions/{id}/act<br/>(no x-bb-skip-cache header)
    API-->>StagehandAPIClient: Response (from cache)
    StagehandAPIClient-->>V3: ActResult
    V3-->>User: ActResult

    Note over User,API: Using instance-level setting
    User->>V3: extract(instruction, schema)
    V3->>StagehandAPIClient: extract({ options: undefined })
    StagehandAPIClient->>StagehandAPIClient: Extract disableCaching (undefined)
    StagehandAPIClient->>StagehandAPIClient: execute({ method: "extract", disableCaching: undefined })
    StagehandAPIClient->>StagehandAPIClient: shouldSkipCache(undefined)
    Note over StagehandAPIClient: Returns true (instance default)
    StagehandAPIClient->>StagehandAPIClient: Add x-bb-skip-cache: true header
    StagehandAPIClient->>API: POST /sessions/{id}/extract<br/>(with x-bb-skip-cache: true)
    API-->>StagehandAPIClient: Response (cache bypassed)
    StagehandAPIClient-->>V3: ExtractResult
    V3-->>User: ExtractResult
Loading

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 20, 2026

Greptile's behavior is changing!

From now on, if a review finishes with no comments, we will not post an additional "statistics" comment to confirm that our review found nothing to comment on. However, you can confirm that we reviewed your changes in the status check section.

This feature can be toggled off in your Code Review Settings by deselecting "Create a status check for each PR".

@sameelarif sameelarif changed the title [STG-1182 cache bypass [STG-1182] cache bypass Jan 20, 2026
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name=".changeset/slow-insects-lie.md">

<violation number="1" location=".changeset/slow-insects-lie.md:5">
P3: Fix typos in the release note (`thereshold` → `threshold`, `it's inference` → `its inference`) to improve clarity.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 6 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/core/lib/v3/api.ts">

<violation number="1" location="packages/core/lib/v3/api.ts:59">
P1: Rule violated: **Any breaking changes to Stagehand REST API client / server implementation must be covered by an integration test under packages/server/test**

Breaking API client change lacks integration test coverage. The `serverCache` parameter (renamed from `disableCaching`) controls the `x-bb-skip-cache` header sent to the server, but no integration test in `packages/server/test` verifies this behavior.

Per the rule, breaking changes to header handling in the API client must be covered by integration tests. Consider adding a test that:
1. Verifies the `x-bb-skip-cache: true` header is sent when `serverCache: false`
2. Verifies the header is not sent when `serverCache: true` (default)</violation>
</file>

<file name=".changeset/slow-insects-lie.md">

<violation number="1" location=".changeset/slow-insects-lie.md:5">
P2: The changeset now documents `serverCache: false`, but this PR introduces `disableCaching`. This will mislead users to a non-existent option in the release notes. Align the changelog with the actual option name.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

* Defaults to true (caching enabled).
* Can be overridden per-method in act(), extract(), and observe() options.
*/
serverCache?: boolean;
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Rule violated: Any breaking changes to Stagehand REST API client / server implementation must be covered by an integration test under packages/server/test

Breaking API client change lacks integration test coverage. The serverCache parameter (renamed from disableCaching) controls the x-bb-skip-cache header sent to the server, but no integration test in packages/server/test verifies this behavior.

Per the rule, breaking changes to header handling in the API client must be covered by integration tests. Consider adding a test that:

  1. Verifies the x-bb-skip-cache: true header is sent when serverCache: false
  2. Verifies the header is not sent when serverCache: true (default)
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/lib/v3/api.ts, line 59:

<comment>Breaking API client change lacks integration test coverage. The `serverCache` parameter (renamed from `disableCaching`) controls the `x-bb-skip-cache` header sent to the server, but no integration test in `packages/server/test` verifies this behavior.

Per the rule, breaking changes to header handling in the API client must be covered by integration tests. Consider adding a test that:
1. Verifies the `x-bb-skip-cache: true` header is sent when `serverCache: false`
2. Verifies the header is not sent when `serverCache: true` (default)</comment>

<file context>
@@ -51,10 +51,12 @@ interface StagehandAPIConstructorParams {
    * Can be overridden per-method in act(), extract(), and observe() options.
    */
-  disableCaching?: boolean;
+  serverCache?: boolean;
 }
 
</file context>
Fix with Cubic

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sameelarif we need to add

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 4 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/core/lib/v3/api.ts">

<violation number="1" location="packages/core/lib/v3/api.ts:645">
P2: The cache-status handling block (~25 lines: computing `finalCacheStatus`, logging, attaching to result) is copy-pasted verbatim in two places within `execute` — the main SSE loop and the done-buffer fallback. Extract this into a private helper (e.g., `attachCacheStatus(result, method, cacheStatus, eventData)`) to avoid the maintenance risk of these blocks diverging silently.</violation>

<violation number="2" location="packages/core/lib/v3/api.ts:787">
P1: Rule violated: **Any breaking changes to Stagehand REST API client / server implementation must be covered by an integration test under packages/server/test**

Breaking API client changes (header rename `x-bb-skip-cache` → `browserbase-cache-bypass`, new `browserbase-cache-status` response header handling, and `cacheStatus` result property) have no integration test coverage under `packages/server/test/`. Per project rules, any breaking changes to the API client in `packages/core/lib/v3/api.ts` must be covered by at least one integration test. Add tests that verify:
1. The `browserbase-cache-bypass: true` header is sent when `serverCache` is disabled
2. The `browserbase-cache-status` response header is correctly read and attached as `cacheStatus` on `ActResult`/`ExtractResult`
3. Per-method `serverCache` override takes precedence over the instance-level setting</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.


// Add cache bypass header if caching is disabled
if (!this.shouldUseCache(serverCache)) {
defaultHeaders["browserbase-cache-bypass"] = "true";
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Rule violated: Any breaking changes to Stagehand REST API client / server implementation must be covered by an integration test under packages/server/test

Breaking API client changes (header rename x-bb-skip-cachebrowserbase-cache-bypass, new browserbase-cache-status response header handling, and cacheStatus result property) have no integration test coverage under packages/server/test/. Per project rules, any breaking changes to the API client in packages/core/lib/v3/api.ts must be covered by at least one integration test. Add tests that verify:

  1. The browserbase-cache-bypass: true header is sent when serverCache is disabled
  2. The browserbase-cache-status response header is correctly read and attached as cacheStatus on ActResult/ExtractResult
  3. Per-method serverCache override takes precedence over the instance-level setting
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/lib/v3/api.ts, line 787:

<comment>Breaking API client changes (header rename `x-bb-skip-cache` → `browserbase-cache-bypass`, new `browserbase-cache-status` response header handling, and `cacheStatus` result property) have no integration test coverage under `packages/server/test/`. Per project rules, any breaking changes to the API client in `packages/core/lib/v3/api.ts` must be covered by at least one integration test. Add tests that verify:
1. The `browserbase-cache-bypass: true` header is sent when `serverCache` is disabled
2. The `browserbase-cache-status` response header is correctly read and attached as `cacheStatus` on `ActResult`/`ExtractResult`
3. Per-method `serverCache` override takes precedence over the instance-level setting</comment>

<file context>
@@ -718,9 +782,9 @@ export class StagehandAPIClient {
+    // Add cache bypass header if caching is disabled
     if (!this.shouldUseCache(serverCache)) {
-      defaultHeaders["x-bb-skip-cache"] = "true";
+      defaultHeaders["browserbase-cache-bypass"] = "true";
     }
 
</file context>
Fix with Cubic

@sameelarif sameelarif changed the title [STG-1182] cache bypass [STG-1182] cache config + docs Feb 17, 2026
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 1 file (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/core/lib/v3/api.ts">

<violation number="1" location="packages/core/lib/v3/api.ts:712">
P1: Rule violated: **Any breaking changes to Stagehand REST API client / server implementation must be covered by an integration test under packages/server/test**

The new `attachCacheStatus` method and the broader cache bypass feature (sending `browserbase-cache-bypass` header, reading `browserbase-cache-status` from responses, attaching `cacheStatus` to results) have no integration test coverage in `packages/server/test/`. Per the rule, changes to the API client implementation in `packages/core/lib/v3/api.ts` must be covered by at least one integration test under `packages/server/test/`. Consider adding tests that verify:
1. The `browserbase-cache-bypass: true` header is sent when `serverCache` is `false`.
2. The `browserbase-cache-status` response header is correctly read and attached to `ActResult`/`ExtractResult`.
3. Method-level `serverCache` overrides the instance-level setting.</violation>

<violation number="2" location="packages/core/lib/v3/api.ts:712">
P2: Misplaced JSDoc: this comment describes `shouldUseCache` but now documents `attachCacheStatus` due to the method being inserted in between. Add a proper JSDoc for `attachCacheStatus` and move this comment back above `shouldUseCache`.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

* Determine if caching should be enabled for a request.
* Method-level setting takes precedence over instance-level setting.
*/
private attachCacheStatus<T>(
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Rule violated: Any breaking changes to Stagehand REST API client / server implementation must be covered by an integration test under packages/server/test

The new attachCacheStatus method and the broader cache bypass feature (sending browserbase-cache-bypass header, reading browserbase-cache-status from responses, attaching cacheStatus to results) have no integration test coverage in packages/server/test/. Per the rule, changes to the API client implementation in packages/core/lib/v3/api.ts must be covered by at least one integration test under packages/server/test/. Consider adding tests that verify:

  1. The browserbase-cache-bypass: true header is sent when serverCache is false.
  2. The browserbase-cache-status response header is correctly read and attached to ActResult/ExtractResult.
  3. Method-level serverCache overrides the instance-level setting.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/lib/v3/api.ts, line 712:

<comment>The new `attachCacheStatus` method and the broader cache bypass feature (sending `browserbase-cache-bypass` header, reading `browserbase-cache-status` from responses, attaching `cacheStatus` to results) have no integration test coverage in `packages/server/test/`. Per the rule, changes to the API client implementation in `packages/core/lib/v3/api.ts` must be covered by at least one integration test under `packages/server/test/`. Consider adding tests that verify:
1. The `browserbase-cache-bypass: true` header is sent when `serverCache` is `false`.
2. The `browserbase-cache-status` response header is correctly read and attached to `ActResult`/`ExtractResult`.
3. Method-level `serverCache` overrides the instance-level setting.</comment>

<file context>
@@ -757,6 +709,41 @@ export class StagehandAPIClient {
    * Determine if caching should be enabled for a request.
    * Method-level setting takes precedence over instance-level setting.
    */
+  private attachCacheStatus<T>(
+    result: T,
+    method: string,
</file context>
Fix with Cubic

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is kicking off a free Cloud Agent to fix these issues. This run is complimentary, but you can enable Autofix for all future PRs in the Cursor dashboard.

}
// Otherwise, use instance-level setting
return this.serverCache;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing tests for server cache API logic

Low Severity

This PR adds business logic in packages/core/lib/v3/api.ts — new methods shouldUseCache(), attachCacheStatus(), the browserbase-cache-bypass header injection, and browserbase-cache-status header reading — but no corresponding tests cover these code paths. The new cache-variables.test.ts file tests ActCache variable handling (local caching), which is unrelated to the server-side caching feature.

Additional Locations (1)

Fix in Cursor Fix in Web

Triggered by team rule: Tests

"cache-status": { value: cacheStatus, type: "string" },
},
});
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate cache status logging on every request

Low Severity

For act, extract, and observe calls, the cache status is logged twice per request — once in request() (category "api", level 2) and again in attachCacheStatus() (category "cache", level 1). The request()-level log fires for all requests including non-cacheable ones, while attachCacheStatus() provides a more informative method-specific message. The generic log in request() is redundant for cached methods and adds noise.

Additional Locations (1)

Fix in Cursor Fix in Web

@cursor
Copy link

cursor bot commented Feb 24, 2026

Bugbot Autofix prepared fixes for 2 of the 2 bugs found in the latest run.

  • ✅ Fixed: Missing tests for server cache API logic
    • Added comprehensive tests in api-server-cache.test.ts covering shouldUseCache(), attachCacheStatus(), cache bypass header injection, and cache status reading for act/extract/observe methods.
  • ✅ Fixed: Duplicate cache status logging on every request
    • Removed redundant cache status logging from request() method since attachCacheStatus() already provides a more informative method-specific log.

Create PR

Or push these changes by commenting:

@cursor push 12d13abd3d
Preview (12d13abd3d)
diff --git a/packages/core/lib/v3/api.ts b/packages/core/lib/v3/api.ts
--- a/packages/core/lib/v3/api.ts
+++ b/packages/core/lib/v3/api.ts
@@ -877,19 +877,6 @@
       },
     });
 
-    // Log cache status if present in response headers
-    const cacheStatus = response.headers.get("browserbase-cache-status");
-    if (cacheStatus) {
-      this.logger({
-        category: "api",
-        message: `server cache ${cacheStatus.toLowerCase()}`,
-        level: 2,
-        auxiliary: {
-          "cache-status": { value: cacheStatus, type: "string" },
-        },
-      });
-    }
-
     return response;
   }
 }

diff --git a/packages/core/tests/api-server-cache.test.ts b/packages/core/tests/api-server-cache.test.ts
new file mode 100644
--- /dev/null
+++ b/packages/core/tests/api-server-cache.test.ts
@@ -1,0 +1,469 @@
+import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
+
+function createMockResponse(
+  status: number,
+  body: string,
+  headers: Record<string, string> = {},
+): Response {
+  const headersObj = new Headers(headers);
+  return {
+    ok: status >= 200 && status < 300,
+    status,
+    headers: headersObj,
+    text: () => Promise.resolve(body),
+    json: () => Promise.resolve(JSON.parse(body)),
+    body: {
+      getReader: () => ({
+        read: vi
+          .fn()
+          .mockResolvedValueOnce({
+            value: new TextEncoder().encode(body),
+            done: false,
+          })
+          .mockResolvedValueOnce({ done: true }),
+      }),
+    },
+  } as unknown as Response;
+}
+
+function createSSEResponse(
+  result: unknown,
+  cacheStatus?: "HIT" | "MISS",
+  cacheHit?: boolean,
+): Response {
+  const eventData = {
+    type: "system",
+    data: {
+      status: "finished",
+      result,
+      cacheHit,
+    },
+  };
+  const body = `data: ${JSON.stringify(eventData)}\n\n`;
+  const headers: Record<string, string> = {};
+  if (cacheStatus) {
+    headers["browserbase-cache-status"] = cacheStatus;
+  }
+  return createMockResponse(200, body, headers);
+}
+
+const mockFetch = vi.fn().mockImplementation((url: string) => {
+  if (url.includes("/sessions/start")) {
+    return Promise.resolve(
+      createMockResponse(
+        200,
+        JSON.stringify({
+          success: true,
+          data: { sessionId: "test-session-123", available: true },
+        }),
+      ),
+    );
+  }
+
+  return Promise.resolve(createSSEResponse({ success: true }));
+});
+
+vi.mock("fetch-cookie", () => ({
+  default: () => mockFetch,
+}));
+
+import { StagehandAPIClient } from "../lib/v3/api.js";
+
+describe("StagehandAPIClient server cache logic", () => {
+  beforeEach(() => {
+    mockFetch.mockClear();
+  });
+
+  afterEach(() => {
+    vi.restoreAllMocks();
+  });
+
+  describe("shouldUseCache behavior", () => {
+    it("does not send cache bypass header when serverCache is true (default)", async () => {
+      const logger = vi.fn();
+      const client = new StagehandAPIClient({
+        apiKey: "test-key",
+        projectId: "test-project",
+        logger,
+        serverCache: true,
+      });
+
+      await client.init({
+        modelName: "openai/gpt-4.1-mini",
+        modelApiKey: "test-model-key",
+      });
+
+      const initCallCount = mockFetch.mock.calls.length;
+      mockFetch.mockResolvedValueOnce(createSSEResponse({ success: true }));
+
+      await client.act({ input: "click button" });
+
+      const actHeaders = mockFetch.mock.calls[initCallCount]?.[1]
+        ?.headers as Record<string, string>;
+      expect(actHeaders).toBeDefined();
+      expect(actHeaders["browserbase-cache-bypass"]).toBeUndefined();
+    });
+
+    it("sends cache bypass header when serverCache is false at instance level", async () => {
+      const logger = vi.fn();
+      const client = new StagehandAPIClient({
+        apiKey: "test-key",
+        projectId: "test-project",
+        logger,
+        serverCache: false,
+      });
+
+      await client.init({
+        modelName: "openai/gpt-4.1-mini",
+        modelApiKey: "test-model-key",
+      });
+
+      const initCallCount = mockFetch.mock.calls.length;
+      mockFetch.mockResolvedValueOnce(createSSEResponse({ success: true }));
+
+      await client.act({ input: "click button" });
+
+      const actHeaders = mockFetch.mock.calls[initCallCount]?.[1]
+        ?.headers as Record<string, string>;
+      expect(actHeaders).toBeDefined();
+      expect(actHeaders["browserbase-cache-bypass"]).toBe("true");
+    });
+
+    it("method-level serverCache=false overrides instance-level serverCache=true", async () => {
+      const logger = vi.fn();
+      const client = new StagehandAPIClient({
+        apiKey: "test-key",
+        projectId: "test-project",
+        logger,
+        serverCache: true,
+      });
+
+      await client.init({
+        modelName: "openai/gpt-4.1-mini",
+        modelApiKey: "test-model-key",
+      });
+
+      const initCallCount = mockFetch.mock.calls.length;
+      mockFetch.mockResolvedValueOnce(createSSEResponse({ success: true }));
+
+      await client.act({
+        input: "click button",
+        options: { serverCache: false },
+      });
+
+      const actHeaders = mockFetch.mock.calls[initCallCount]?.[1]
+        ?.headers as Record<string, string>;
+      expect(actHeaders).toBeDefined();
+      expect(actHeaders["browserbase-cache-bypass"]).toBe("true");
+    });
+
+    it("method-level serverCache=true overrides instance-level serverCache=false", async () => {
+      const logger = vi.fn();
+      const client = new StagehandAPIClient({
+        apiKey: "test-key",
+        projectId: "test-project",
+        logger,
+        serverCache: false,
+      });
+
+      await client.init({
+        modelName: "openai/gpt-4.1-mini",
+        modelApiKey: "test-model-key",
+      });
+
+      const initCallCount = mockFetch.mock.calls.length;
+      mockFetch.mockResolvedValueOnce(createSSEResponse({ success: true }));
+
+      await client.act({
+        input: "click button",
+        options: { serverCache: true },
+      });
+
+      const actHeaders = mockFetch.mock.calls[initCallCount]?.[1]
+        ?.headers as Record<string, string>;
+      expect(actHeaders).toBeDefined();
+      expect(actHeaders["browserbase-cache-bypass"]).toBeUndefined();
+    });
+
+    it("defaults to serverCache=true when not specified", async () => {
+      const logger = vi.fn();
+      const client = new StagehandAPIClient({
+        apiKey: "test-key",
+        projectId: "test-project",
+        logger,
+      });
+
+      await client.init({
+        modelName: "openai/gpt-4.1-mini",
+        modelApiKey: "test-model-key",
+      });
+
+      const initCallCount = mockFetch.mock.calls.length;
+      mockFetch.mockResolvedValueOnce(createSSEResponse({ success: true }));
+
+      await client.act({ input: "click button" });
+
+      const actHeaders = mockFetch.mock.calls[initCallCount]?.[1]
+        ?.headers as Record<string, string>;
+      expect(actHeaders).toBeDefined();
+      expect(actHeaders["browserbase-cache-bypass"]).toBeUndefined();
+    });
+  });
+
+  describe("attachCacheStatus behavior", () => {
+    it("attaches cacheStatus HIT from response header to act result", async () => {
+      const logger = vi.fn();
+      const client = new StagehandAPIClient({
+        apiKey: "test-key",
+        projectId: "test-project",
+        logger,
+      });
+
+      await client.init({
+        modelName: "openai/gpt-4.1-mini",
+        modelApiKey: "test-model-key",
+      });
+
+      mockFetch.mockResolvedValueOnce(
+        createSSEResponse(
+          { success: true, message: "action performed" },
+          "HIT",
+        ),
+      );
+
+      const result = await client.act({ input: "click button" });
+
+      expect(result.cacheStatus).toBe("HIT");
+    });
+
+    it("attaches cacheStatus MISS from response header to act result", async () => {
+      const logger = vi.fn();
+      const client = new StagehandAPIClient({
+        apiKey: "test-key",
+        projectId: "test-project",
+        logger,
+      });
+
+      await client.init({
+        modelName: "openai/gpt-4.1-mini",
+        modelApiKey: "test-model-key",
+      });
+
+      mockFetch.mockResolvedValueOnce(
+        createSSEResponse(
+          { success: true, message: "action performed" },
+          "MISS",
+        ),
+      );
+
+      const result = await client.act({ input: "click button" });
+
+      expect(result.cacheStatus).toBe("MISS");
+    });
+
+    it("attaches cacheStatus from SSE event cacheHit field when header is missing", async () => {
+      const logger = vi.fn();
+      const client = new StagehandAPIClient({
+        apiKey: "test-key",
+        projectId: "test-project",
+        logger,
+      });
+
+      await client.init({
+        modelName: "openai/gpt-4.1-mini",
+        modelApiKey: "test-model-key",
+      });
+
+      mockFetch.mockResolvedValueOnce(
+        createSSEResponse(
+          { success: true, message: "action performed" },
+          undefined,
+          true,
+        ),
+      );
+
+      const result = await client.act({ input: "click button" });
+
+      expect(result.cacheStatus).toBe("HIT");
+    });
+
+    it("attaches cacheStatus MISS from SSE event cacheHit=false when header is missing", async () => {
+      const logger = vi.fn();
+      const client = new StagehandAPIClient({
+        apiKey: "test-key",
+        projectId: "test-project",
+        logger,
+      });
+
+      await client.init({
+        modelName: "openai/gpt-4.1-mini",
+        modelApiKey: "test-model-key",
+      });
+
+      mockFetch.mockResolvedValueOnce(
+        createSSEResponse(
+          { success: true, message: "action performed" },
+          undefined,
+          false,
+        ),
+      );
+
+      const result = await client.act({ input: "click button" });
+
+      expect(result.cacheStatus).toBe("MISS");
+    });
+
+    it("logs cache status for act method", async () => {
+      const logger = vi.fn();
+      const client = new StagehandAPIClient({
+        apiKey: "test-key",
+        projectId: "test-project",
+        logger,
+      });
+
+      await client.init({
+        modelName: "openai/gpt-4.1-mini",
+        modelApiKey: "test-model-key",
+      });
+
+      mockFetch.mockResolvedValueOnce(
+        createSSEResponse({ success: true }, "HIT"),
+      );
+
+      await client.act({ input: "click button" });
+
+      const cacheLogCalls = logger.mock.calls.filter(
+        (call) => call[0]?.category === "cache",
+      );
+      expect(cacheLogCalls.length).toBe(1);
+      expect(cacheLogCalls[0][0].message).toBe("act server cache hit");
+    });
+
+    it("logs cache status for extract method", async () => {
+      const logger = vi.fn();
+      const client = new StagehandAPIClient({
+        apiKey: "test-key",
+        projectId: "test-project",
+        logger,
+      });
+
+      await client.init({
+        modelName: "openai/gpt-4.1-mini",
+        modelApiKey: "test-model-key",
+      });
+
+      mockFetch.mockResolvedValueOnce(
+        createSSEResponse({ extraction: "test data" }, "MISS"),
+      );
+
+      await client.extract({ instruction: "extract data" });
+
+      const cacheLogCalls = logger.mock.calls.filter(
+        (call) => call[0]?.category === "cache",
+      );
+      expect(cacheLogCalls.length).toBe(1);
+      expect(cacheLogCalls[0][0].message).toBe("extract server cache miss");
+    });
+
+    it("logs cache status for observe method", async () => {
+      const logger = vi.fn();
+      const client = new StagehandAPIClient({
+        apiKey: "test-key",
+        projectId: "test-project",
+        logger,
+      });
+
+      await client.init({
+        modelName: "openai/gpt-4.1-mini",
+        modelApiKey: "test-model-key",
+      });
+
+      mockFetch.mockResolvedValueOnce(createSSEResponse([], "HIT"));
+
+      await client.observe({ instruction: "find buttons" });
+
+      const cacheLogCalls = logger.mock.calls.filter(
+        (call) => call[0]?.category === "cache",
+      );
+      expect(cacheLogCalls.length).toBe(1);
+      expect(cacheLogCalls[0][0].message).toBe("observe server cache hit");
+    });
+
+    it("does not attach cacheStatus to observe result (returns array)", async () => {
+      const logger = vi.fn();
+      const client = new StagehandAPIClient({
+        apiKey: "test-key",
+        projectId: "test-project",
+        logger,
+      });
+
+      await client.init({
+        modelName: "openai/gpt-4.1-mini",
+        modelApiKey: "test-model-key",
+      });
+
+      mockFetch.mockResolvedValueOnce(createSSEResponse([], "HIT"));
+
+      const result = await client.observe({ instruction: "find buttons" });
+
+      expect(Array.isArray(result)).toBe(true);
+      expect(
+        (result as unknown as { cacheStatus?: string }).cacheStatus,
+      ).toBeUndefined();
+    });
+  });
+
+  describe("cache bypass header for different methods", () => {
+    it("applies cache bypass header to extract requests", async () => {
+      const logger = vi.fn();
+      const client = new StagehandAPIClient({
+        apiKey: "test-key",
+        projectId: "test-project",
+        logger,
+        serverCache: false,
+      });
+
+      await client.init({
+        modelName: "openai/gpt-4.1-mini",
+        modelApiKey: "test-model-key",
+      });
+
+      const initCallCount = mockFetch.mock.calls.length;
+      mockFetch.mockResolvedValueOnce(
+        createSSEResponse({ extraction: "data" }),
+      );
+
+      await client.extract({ instruction: "get data" });
+
+      const extractHeaders = mockFetch.mock.calls[initCallCount]?.[1]
+        ?.headers as Record<string, string>;
+      expect(extractHeaders).toBeDefined();
+      expect(extractHeaders["browserbase-cache-bypass"]).toBe("true");
+    });
+
+    it("applies cache bypass header to observe requests", async () => {
+      const logger = vi.fn();
+      const client = new StagehandAPIClient({
+        apiKey: "test-key",
+        projectId: "test-project",
+        logger,
+        serverCache: false,
+      });
+
+      await client.init({
+        modelName: "openai/gpt-4.1-mini",
+        modelApiKey: "test-model-key",
+      });
+
+      const initCallCount = mockFetch.mock.calls.length;
+      mockFetch.mockResolvedValueOnce(createSSEResponse([]));
+
+      await client.observe({ instruction: "find buttons" });
+
+      const observeHeaders = mockFetch.mock.calls[initCallCount]?.[1]
+        ?.headers as Record<string, string>;
+      expect(observeHeaders).toBeDefined();
+      expect(observeHeaders["browserbase-cache-bypass"]).toBe("true");
+    });
+  });
+});
This Bugbot Autofix run was free. To enable Autofix for future PRs, go to the Cursor dashboard.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should add the serverCache param into the stagehand constructor reference secton

@sameelarif sameelarif changed the title [STG-1182] cache config + docs [STG-1182] cache config Feb 24, 2026
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 4 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/core/lib/v3/types/public/methods.ts">

<violation number="1" location="packages/core/lib/v3/types/public/methods.ts:93">
P2: The `Action[] & { cacheStatus?: ... }` intersection pattern is fragile: any array operation (`.filter()`, `.map()`, spread, etc.) will silently drop the `cacheStatus` property. This differs from how `ActResult` handles the same concern (using a proper interface with an `actions` field).

Additionally, the `observe()` method in `v3.ts` still returns `Promise<Action[]>` rather than `Promise<ObserveResult>`, so TypeScript consumers won't see `cacheStatus` without an explicit cast.

Consider wrapping the result in an object (like `ActResult` does) or, if backward compatibility requires returning an array, at minimum updating the `observe()` return type to `Promise<ObserveResult>` so the property is discoverable.</violation>
</file>

<file name="packages/core/lib/v3/api.ts">

<violation number="1" location="packages/core/lib/v3/api.ts:722">
P2: Custom agent: **Any breaking changes to Stagehand REST API client / server implementation must be covered by an integration test under packages/server/test**

The new cache-status suppression behavior when `serverCache=false` is not covered by any integration test. When a request is made with caching disabled but the server still returns a `browserbase-cache-status` header or SSE `cacheHit`, the status is now intentionally suppressed (passed as `null`/empty). Add a test in `packages/server/test/integration/api-server-cache.test.ts` that verifies `result.cacheStatus` is `undefined` when `serverCache: false` even if the server responds with a cache status header.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

* `browserbase-cache-status` header so callers can tell whether the result
* was served from the server-side cache.
*/
export type ObserveResult = Action[] & { cacheStatus?: "HIT" | "MISS" };
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The Action[] & { cacheStatus?: ... } intersection pattern is fragile: any array operation (.filter(), .map(), spread, etc.) will silently drop the cacheStatus property. This differs from how ActResult handles the same concern (using a proper interface with an actions field).

Additionally, the observe() method in v3.ts still returns Promise<Action[]> rather than Promise<ObserveResult>, so TypeScript consumers won't see cacheStatus without an explicit cast.

Consider wrapping the result in an object (like ActResult does) or, if backward compatibility requires returning an array, at minimum updating the observe() return type to Promise<ObserveResult> so the property is discoverable.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/lib/v3/types/public/methods.ts, line 93:

<comment>The `Action[] & { cacheStatus?: ... }` intersection pattern is fragile: any array operation (`.filter()`, `.map()`, spread, etc.) will silently drop the `cacheStatus` property. This differs from how `ActResult` handles the same concern (using a proper interface with an `actions` field).

Additionally, the `observe()` method in `v3.ts` still returns `Promise<Action[]>` rather than `Promise<ObserveResult>`, so TypeScript consumers won't see `cacheStatus` without an explicit cast.

Consider wrapping the result in an object (like `ActResult` does) or, if backward compatibility requires returning an array, at minimum updating the `observe()` return type to `Promise<ObserveResult>` so the property is discoverable.</comment>

<file context>
@@ -84,6 +84,14 @@ export interface ObserveOptions {
+ * `browserbase-cache-status` header so callers can tell whether the result
+ * was served from the server-side cache.
+ */
+export type ObserveResult = Action[] & { cacheStatus?: "HIT" | "MISS" };
+
 export enum V3FunctionName {
</file context>
Fix with Cubic


// If caching was bypassed for this request, suppress cache status
// so we don't log or surface a MISS that the server emits anyway.
const cacheEnabled = this.shouldUseCache(serverCache);
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Custom agent: Any breaking changes to Stagehand REST API client / server implementation must be covered by an integration test under packages/server/test

The new cache-status suppression behavior when serverCache=false is not covered by any integration test. When a request is made with caching disabled but the server still returns a browserbase-cache-status header or SSE cacheHit, the status is now intentionally suppressed (passed as null/empty). Add a test in packages/server/test/integration/api-server-cache.test.ts that verifies result.cacheStatus is undefined when serverCache: false even if the server responds with a cache status header.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/lib/v3/api.ts, line 722:

<comment>The new cache-status suppression behavior when `serverCache=false` is not covered by any integration test. When a request is made with caching disabled but the server still returns a `browserbase-cache-status` header or SSE `cacheHit`, the status is now intentionally suppressed (passed as `null`/empty). Add a test in `packages/server/test/integration/api-server-cache.test.ts` that verifies `result.cacheStatus` is `undefined` when `serverCache: false` even if the server responds with a cache status header.</comment>

<file context>
@@ -716,11 +717,14 @@ export class StagehandAPIClient {
 
+              // If caching was bypassed for this request, suppress cache status
+              // so we don't log or surface a MISS that the server emits anyway.
+              const cacheEnabled = this.shouldUseCache(serverCache);
               return this.attachCacheStatus(
                 eventData.data.result as T,
</file context>
Fix with Cubic

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/server/test/integration/api-server-cache.test.ts">

<violation number="1" location="packages/server/test/integration/api-server-cache.test.ts:87">
P2: Both tests in `browserbase-cache-status response header` have conditional assertions (`if (cacheStatus !== null)`) that make them vacuous no-ops when the header is absent. These tests will pass green even if server-side caching is completely broken or never deployed, masking regressions. Consider either (a) requiring the header to be present (fail the test if it's missing) or (b) explicitly marking these tests as skipped when caching isn't available, so the test suite honestly reflects what's being validated.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

assertFetchStatus(ctx, HTTP_OK, "Extract should succeed");

const cacheStatus = ctx.headers.get("browserbase-cache-status");
if (cacheStatus !== null) {
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Both tests in browserbase-cache-status response header have conditional assertions (if (cacheStatus !== null)) that make them vacuous no-ops when the header is absent. These tests will pass green even if server-side caching is completely broken or never deployed, masking regressions. Consider either (a) requiring the header to be present (fail the test if it's missing) or (b) explicitly marking these tests as skipped when caching isn't available, so the test suite honestly reflects what's being validated.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/server/test/integration/api-server-cache.test.ts, line 87:

<comment>Both tests in `browserbase-cache-status response header` have conditional assertions (`if (cacheStatus !== null)`) that make them vacuous no-ops when the header is absent. These tests will pass green even if server-side caching is completely broken or never deployed, masking regressions. Consider either (a) requiring the header to be present (fail the test if it's missing) or (b) explicitly marking these tests as skipped when caching isn't available, so the test suite honestly reflects what's being validated.</comment>

<file context>
@@ -1,218 +1,123 @@
-      fetchSpy.mockResolvedValueOnce(
-        sseResponse(EXTRACT_RESULT, { "browserbase-cache-status": "HIT" }),
+    const cacheStatus = ctx.headers.get("browserbase-cache-status");
+    if (cacheStatus !== null) {
+      assert.ok(
+        cacheStatus === "HIT" || cacheStatus === "MISS",
</file context>
Fix with Cubic

@sameelarif sameelarif merged commit 49ead1e into main Feb 24, 2026
168 checks passed
@github-actions github-actions bot mentioned this pull request Feb 24, 2026
miguelg719 pushed a commit that referenced this pull request Feb 24, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @browserbasehq/stagehand@3.1.0

### Minor Changes

- [#1681](#1681)
[`e3db9aa`](e3db9aa)
Thanks [@tkattkat](https://github.com/tkattkat)! - Add cookie management
APIs: `context.addCookies()`, `context.clearCookies()`, &
`context.cookies()`

- [#1672](#1672)
[`b65756e`](b65756e)
Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - add boolean
keepAlive parameter to allow for configuring whether the browser should
be closed when stagehand.close() is called.

- [#1708](#1708)
[`176d420`](176d420)
Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - add
context.setExtraHTTPHeaders()

- [#1611](#1611)
[`8a3c066`](8a3c066)
Thanks [@monadoid](https://github.com/monadoid)! - Using `mode` enum
instead of old `cua` boolean in openapi spec

### Patch Changes

- [#1683](#1683)
[`7584f3e`](7584f3e)
Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - fix:
include shadow DOM in .count() & .nth() & support xpath predicates

- [#1644](#1644)
[`1e1c9c1`](1e1c9c1)
Thanks [@monadoid](https://github.com/monadoid)! - Fix unhandled CDP
detaches by returning the original sendCDP promise

- [#1729](#1729)
[`6bef890`](6bef890)
Thanks [@shrey150](https://github.com/shrey150)! - fix: support Claude
4.6 (Opus and Sonnet) in CUA mode by using the correct
`computer_20251124` tool version and `computer-use-2025-11-24` beta
header

- [#1647](#1647)
[`ffd4b33`](ffd4b33)
Thanks [@tkattkat](https://github.com/tkattkat)! - Fix [Agent] - Address
bug causing issues with continuing a conversation from past messages in
dom mode

- [#1614](#1614)
[`677bff5`](677bff5)
Thanks [@miguelg719](https://github.com/miguelg719)! - Enforce
<number>-<number> regex validation on act/observe for elementId

- [#1580](#1580)
[`65ff464`](65ff464)
Thanks [@tkattkat](https://github.com/tkattkat)! - Add unified variables
support across act and agent with a single VariableValue type

- [#1666](#1666)
[`101bcf2`](101bcf2)
Thanks [@Kylejeong2](https://github.com/Kylejeong2)! - add support for
codex models

- [#1728](#1728)
[`0a94301`](0a94301)
Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - handle
potential race condition on `.close()` when using the Stagehand API

- [#1664](#1664)
[`b27c04d`](b27c04d)
Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - fixes issue
with context.addInitScript() where scripts were not being applied to out
of process iframes (OOPIFs), and popup pages with same process iframes
(SPIFs)

- [#1632](#1632)
[`afbd08b`](afbd08b)
Thanks [@pirate](https://github.com/pirate)! - Remove automatic `.env`
loading via `dotenv`.

If your app relies on `.env` files, install `dotenv` and load it
explicitly in your code:

    ```ts
    import dotenv from "dotenv";
    dotenv.config({ path: ".env" });
    ```

- [#1624](#1624)
[`0e8d569`](0e8d569)
Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - fix issue
where screenshot masks were not being applied to dialog elements

- [#1596](#1596)
[`ff0f979`](ff0f979)
Thanks [@tkattkat](https://github.com/tkattkat)! - Update usage/metrics
handling in agent

- [#1631](#1631)
[`2d89d2b`](2d89d2b)
Thanks [@miguelg719](https://github.com/miguelg719)! - Add right and
middle click support to act and observe

- [#1697](#1697)
[`aac9a19`](aac9a19)
Thanks [@shrey150](https://github.com/shrey150)! - fix: support
`<frame>` elements in XPath frame boundary detection so `act()` works on
legacy `<frameset>` pages

- [#1692](#1692)
[`06de50f`](06de50f)
Thanks [@shrey150](https://github.com/shrey150)! - fix: skip piercer
injection for chrome-extension:// and other non-HTML targets

- [#1613](#1613)
[`aa4d981`](aa4d981)
Thanks [@miguelg719](https://github.com/miguelg719)! -
SupportedUnderstudyAction Enum validation for 'method' on act/observe
inference

- [#1652](#1652)
[`18b1e3b`](18b1e3b)
Thanks [@miguelg719](https://github.com/miguelg719)! - Add support for
gemini 3 flash and pro in hybrid/cua agent

- [#1706](#1706)
[`957d82b`](957d82b)
Thanks [@chrisreadsf](https://github.com/chrisreadsf)! - Add GLM to
prompt-based JSON fallback for models without native structured output
support

- [#1633](#1633)
[`22e371a`](22e371a)
Thanks [@tkattkat](https://github.com/tkattkat)! - Add warning when
incorrect models are used with agents hybrid mode

- [#1673](#1673)
[`d29b91f`](d29b91f)
Thanks [@miguelg719](https://github.com/miguelg719)! - Add multi-region
support for Stagehand API with region-specific endpoints

- [#1695](#1695)
[`7b4f817`](7b4f817)
Thanks [@tkattkat](https://github.com/tkattkat)! - Fix: zod bug when
pinning zod to v3 and using structured output in agent

- [#1609](#1609)
[`3f9ca4d`](3f9ca4d)
Thanks [@miguelg719](https://github.com/miguelg719)! - Add
SupportedUnderstudyActions to observe system prompt

- [#1581](#1581)
[`49ead1e`](49ead1e)
Thanks [@sameelarif](https://github.com/sameelarif)! - **Server-side
caching is now available.**

When running `env: "BROWSERBASE"`, Stagehand automatically caches
`act()`, `extract()`, and `observe()` results server-side — repeated
calls with the same inputs return instantly without consuming LLM
tokens.

Caching is enabled by default and can be disabled via `serverCache:
false` on the Stagehand instance or per individual call. Check out the
[browserbase blog](https://www.browserbase.com/blog/stagehand-caching)
for more details.

- [#1642](#1642)
[`3673369`](3673369)
Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - fix issue
where scripts added via context.addInitScripts() were not being injected
into new pages that were opened via popups (eg, clicking a link that
opens a new page) and/or calling context.newPage(url)

- [#1735](#1735)
[`c465e87`](c465e87)
Thanks [@monadoid](https://github.com/monadoid)! - Supports request
header authentication with connectToMCPServer

- [#1705](#1705)
[`ae533e4`](ae533e4)
Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - include
error cause in UnderstudyCommandException

- [#1636](#1636)
[`ea33052`](ea33052)
Thanks [@miguelg719](https://github.com/miguelg719)! - Include
executionModel on the AgentConfigSchema

- [#1679](#1679)
[`5764ede`](5764ede)
Thanks [@shrey150](https://github.com/shrey150)! - fix issue where
locator.count() was not working with xpaths that have attribute
predicates

- [#1646](#1646)
[`f09b184`](f09b184)
Thanks [@miguelg719](https://github.com/miguelg719)! - Add user-agent to
CDP connections

- [#1637](#1637)
[`a7d29de`](a7d29de)
Thanks [@miguelg719](https://github.com/miguelg719)! - Improve error and
warning message for legacy model format

- [#1685](#1685)
[`d334399`](d334399)
Thanks [@tkattkat](https://github.com/tkattkat)! - Bump ai sdk & google
provider version

- [#1662](#1662)
[`44416da`](44416da)
Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - fix issue
where locator.fill() was not working on elements that require direct
value setting

- [#1612](#1612)
[`bdd8b4e`](bdd8b4e)
Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - fix issue
where screenshot mask was only being applied to the first element that
the locator resolved to. masks now apply to all matching elements.

## @browserbasehq/stagehand-server@3.6.0

### Minor Changes

- [#1611](#1611)
[`8a3c066`](8a3c066)
Thanks [@monadoid](https://github.com/monadoid)! - Using `mode` enum
instead of old `cua` boolean in openapi spec

### Patch Changes

- [#1604](#1604)
[`4753078`](4753078)
Thanks [@miguelg719](https://github.com/miguelg719)! - Enable bedrock

- [#1636](#1636)
[`ea33052`](ea33052)
Thanks [@miguelg719](https://github.com/miguelg719)! - Include
executionModel on the AgentConfigSchema

- [#1602](#1602)
[`22a0502`](22a0502)
Thanks [@miguelg719](https://github.com/miguelg719)! - Include vertex as
a supported provider

- Updated dependencies
\[[`7584f3e`](7584f3e),
[`1e1c9c1`](1e1c9c1),
[`6bef890`](6bef890),
[`ffd4b33`](ffd4b33),
[`677bff5`](677bff5),
[`65ff464`](65ff464),
[`101bcf2`](101bcf2),
[`0a94301`](0a94301),
[`b27c04d`](b27c04d),
[`afbd08b`](afbd08b),
[`e3db9aa`](e3db9aa),
[`0e8d569`](0e8d569),
[`ff0f979`](ff0f979),
[`2d89d2b`](2d89d2b),
[`aac9a19`](aac9a19),
[`06de50f`](06de50f),
[`aa4d981`](aa4d981),
[`18b1e3b`](18b1e3b),
[`957d82b`](957d82b),
[`b65756e`](b65756e),
[`22e371a`](22e371a),
[`d29b91f`](d29b91f),
[`7b4f817`](7b4f817),
[`176d420`](176d420),
[`3f9ca4d`](3f9ca4d),
[`8a3c066`](8a3c066),
[`49ead1e`](49ead1e),
[`3673369`](3673369),
[`c465e87`](c465e87),
[`ae533e4`](ae533e4),
[`ea33052`](ea33052),
[`5764ede`](5764ede),
[`f09b184`](f09b184),
[`a7d29de`](a7d29de),
[`d334399`](d334399),
[`44416da`](44416da),
[`bdd8b4e`](bdd8b4e)]:
    -   @browserbasehq/stagehand@3.1.0

## @browserbasehq/stagehand-evals@1.1.8

### Patch Changes

- Updated dependencies
\[[`7584f3e`](7584f3e),
[`1e1c9c1`](1e1c9c1),
[`6bef890`](6bef890),
[`ffd4b33`](ffd4b33),
[`677bff5`](677bff5),
[`65ff464`](65ff464),
[`101bcf2`](101bcf2),
[`0a94301`](0a94301),
[`b27c04d`](b27c04d),
[`afbd08b`](afbd08b),
[`e3db9aa`](e3db9aa),
[`0e8d569`](0e8d569),
[`ff0f979`](ff0f979),
[`2d89d2b`](2d89d2b),
[`aac9a19`](aac9a19),
[`06de50f`](06de50f),
[`aa4d981`](aa4d981),
[`18b1e3b`](18b1e3b),
[`957d82b`](957d82b),
[`b65756e`](b65756e),
[`22e371a`](22e371a),
[`d29b91f`](d29b91f),
[`7b4f817`](7b4f817),
[`176d420`](176d420),
[`3f9ca4d`](3f9ca4d),
[`8a3c066`](8a3c066),
[`49ead1e`](49ead1e),
[`3673369`](3673369),
[`c465e87`](c465e87),
[`ae533e4`](ae533e4),
[`ea33052`](ea33052),
[`5764ede`](5764ede),
[`f09b184`](f09b184),
[`a7d29de`](a7d29de),
[`d334399`](d334399),
[`44416da`](44416da),
[`bdd8b4e`](bdd8b4e)]:
    -   @browserbasehq/stagehand@3.1.0

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants