Repository: browserbase/stagehand
File: packages/core/lib/v3/agent/tools/scroll.ts (TypeScript)

Code before:
      const cy = Math.floor(h / 2);
      const deltaY = direction === "up" ? -scrollDistance : scrollDistance;

      await page.scroll(cx, cy, 0, deltaY);

      v3.recordAgentReplayStep({
        type: "scroll",
        deltaX: 0,
        deltaY,
        anchor: { x: cx, y: cy },
      });

      return {
        success: true,
        message: `Scrolled ${percentage}% ${direction} (${scrollDistance}px)`,
        scrolledPixels: scrollDistance,
      };
    },
    toModelOutput: (result) => {
      const content: ModelOutputContentItem[] = [
        {
          type: "text",
          text: JSON.stringify({
            success: result.success,
            message: result.message,
            scrolledPixels: result.scrolledPixels,
          }),
        },
      ];
      return { type: "content", value: content };
    },
  });

/**
 * Scroll tool for hybrid mode (grounding models).
 * Supports optional coordinates for scrolling within nested scrollable elements.
 */
export const scrollVisionTool = (v3: V3, provider?: string) =>
  tool({
    description: `Scroll the page up or down. For general page scrolling, no coordinates needed. Only provide coordinates when scrolling inside a nested scrollable element (e.g., a dropdown menu, modal with overflow, or scrollable sidebar). Default is 80%, and what should be typically used for general page scrolling`,
    inputSchema: z.object({
      direction: z.enum(["up", "down"]),
      coordinates: z
        .array(z.number())
        .optional()
        .describe(
          "Only use coordinates for scrolling inside a nested scrollable element - provide (x, y) within that element",
        ),
      percentage: z.number().min(1).max(200).optional(),
    }),
    execute: async ({

Review comment (suggestion):
would it be better to return a `type: json` here instead of `[{type: 'text',  'text': JSON.stringify(...)}]`

Code after:
      const cy = Math.floor(h / 2);
      const deltaY = direction === "up" ? -scrollDistance : scrollDistance;

      await page.scroll(cx, cy, 0, deltaY);

      v3.recordAgentReplayStep({
        type: "scroll",
        deltaX: 0,
        deltaY,
        anchor: { x: cx, y: cy },
      });

      return {
        success: true,
        message: `Scrolled ${percentage}% ${direction} (${scrollDistance}px)`,
        scrolledPixels: scrollDistance,
      };
    },
    toModelOutput: (result) => {
      return {
        type: "json",
        value: {
          success: result.success,
          message: result.message,
          scrolledPixels: result.scrolledPixels,
        },
      };
    },
  });

/**
 * Scroll tool for hybrid mode (grounding models).
 * Supports optional coordinates for scrolling within nested scrollable elements.
 */
export const scrollVisionTool = (v3: V3, provider?: string) =>
  tool({
    description: `Scroll the page up or down. For general page scrolling, no coordinates needed. Only provide coordinates when scrolling inside a nested scrollable element (e.g., a dropdown menu, modal with overflow, or scrollable sidebar). Default is 80%, and what should be typically used for general page scrolling`,
    inputSchema: z.object({
      direction: z.enum(["up", "down"]),
      coordinates: z
        .array(z.number())
        .optional()
        .describe(
          "Only use coordinates for scrolling inside a nested scrollable element - provide (x, y) within that element",
        ),
      percentage: z.number().min(1).max(200).optional(),
    }),
    execute: async ({
      direction,
      coordinates,
      percentage = 80,