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

Code before:
import { tool } from "ai";
import { z } from "zod";
import type { V3 } from "../../v3";
import {
  processCoordinates,
  isGoogleProvider,
} from "../utils/coordinateNormalization";

function waitForTimeout(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

export const typeTool = (v3: V3, provider?: string) =>
  tool({
    description:
      "Type text into an element using its coordinates. This will click the element and then type the text into it (this is the most reliable way to type into an element, always use this over act, unless the element is not visible in the screenshot, but shown in ariaTree)",
    inputSchema: z.object({
      describe: z
        .string()
        .describe(
          "Describe the element to type into in a short, specific phrase that mentions the element type and a good visual description",
        ),
      text: z.string().describe("The text to type into the element"),
      coordinates: z
        .array(z.number())
        .describe("The (x, y) coordinates to type into the element"),
    }),
    execute: async ({ describe, coordinates, text }) => {
      try {
        const page = await v3.context.awaitActivePage();
        const processed = processCoordinates(
          coordinates[0],
          coordinates[1],
          provider,
        );

        v3.logger({
          category: "agent",
          message: `Agent calling tool: type`,
          level: 1,
          auxiliary: {
            arguments: {
              value: JSON.stringify({ describe, coordinates, processed, text }),
              type: "string",
            },
          },
        });
        // Click the element first, then type
        await page.click(processed.x, processed.y);
        // Google models need extra delay for page to settle after click
        if (isGoogleProvider(provider)) {
          await waitForTimeout(1000);
        }
        await page.type(text);
        v3.recordAgentReplayStep({
          type: "type",
          instruction: describe,
          playwrightArguments: {
            coordinates: [processed.x, processed.y],
            text,
          },
        });
        return { success: true, describe, text };
      } catch (error) {
        return {
          success: false,
          error: `Error typing: ${(error as Error).message}`,
        };
      }
    },
  });


Review comment (suggestion):
I think if there's no xpath maybe we can fallback to clicking at x,y to attempt a focus, and then blindly type into the page. Often typing into the page works fine 

Code after:
          returnXpath: true,
        });

        // Google models need extra delay for page to settle after click
        if (isGoogleProvider(provider)) {
          await waitForTimeout(1000);
        }

        await page.type(text);

        // Record as an "act" step with proper Action for deterministic replay
        const normalizedXpath = ensureXPath(xpath);
        if (normalizedXpath) {
          const action: Action = {
            selector: normalizedXpath,
            description: describe,
            method: "type",
            arguments: [text],
          };
          v3.recordAgentReplayStep({
            type: "act",
            instruction: describe,
            actions: [action],
            actionDescription: describe,
          });
        }

        return { success: true, describe, text };
      } catch (error) {
        return {
          success: false,
          error: `Error typing: ${(error as Error).message}`,
        };
      }
    },
  });