Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
7cdaa0c
zodv4
tkattkat Oct 22, 2025
004614a
cleanup
tkattkat Oct 22, 2025
e9d5548
add comment
tkattkat Oct 22, 2025
3306f21
remove logging
tkattkat Oct 22, 2025
c5c8c62
rm
tkattkat Oct 22, 2025
0a79127
clean
tkattkat Oct 22, 2025
6e18601
rm
tkattkat Oct 22, 2025
77ada5c
Merge branch 'v3' into zodv4
tkattkat Oct 28, 2025
ed6e6eb
remove json schema to zod
tkattkat Oct 28, 2025
4f25abe
Merge branch 'v3' into zodv4
tkattkat Oct 28, 2025
3998f54
remove zod to schema
tkattkat Oct 28, 2025
04e6211
Merge branch 'zodv4' of https://github.com/browserbase/stagehand into…
tkattkat Oct 28, 2025
6e00759
remove zod v3
tkattkat Oct 28, 2025
b15fd50
move zod out of peer dependencies
tkattkat Oct 28, 2025
a45de88
Merge branch 'v3' into zodv4
miguelg719 Oct 28, 2025
4658860
test release
miguelg719 Oct 28, 2025
b341515
revert testing changes
tkattkat Nov 3, 2025
b4bd179
Merge remote-tracking branch 'origin/main' into zodv4
tkattkat Nov 3, 2025
f5d2a31
remove zod version pinning from few examples
tkattkat Nov 3, 2025
606d0ed
add changeset
tkattkat Nov 3, 2025
ad1e461
format
tkattkat Nov 3, 2025
14452c5
Merge remote-tracking branch 'origin/main' into zodv4
tkattkat Nov 7, 2025
ea8ba38
Merge main into zodv4: resolve LLMParsedResponse conflict
tkattkat Nov 14, 2025
2acd949
remove /v3 from record video script
tkattkat Nov 14, 2025
8d70d31
zodv34
tkattkat Nov 15, 2025
d43a25f
package
tkattkat Nov 17, 2025
3d67515
rv
tkattkat Nov 17, 2025
cb5a685
export new type
tkattkat Nov 17, 2025
7afc1d7
remove zod to json schema
tkattkat Nov 17, 2025
546b38e
remove dev dependencies for zod
tkattkat Nov 17, 2025
2b384cb
vitest
tkattkat Nov 17, 2025
2d5f674
spacing
tkattkat Nov 17, 2025
fbe3ab4
Merge remote-tracking branch 'origin/main' into zodv34
tkattkat Nov 17, 2025
42b194d
Merge remote-tracking branch 'origin/main' into zodv34
tkattkat Nov 17, 2025
dce8c83
update lock file
tkattkat Nov 17, 2025
1eff250
update zod
tkattkat Nov 17, 2025
6edbf7c
add overloads for schema types
tkattkat Nov 18, 2025
f7a8d19
nice
tkattkat Nov 18, 2025
3898a83
clean up openai clients
tkattkat Nov 18, 2025
a4a1420
format + revert changes to operator example
tkattkat Nov 18, 2025
f2f7109
remove fallback to empty schema
tkattkat Nov 18, 2025
19368ce
clean up unnecessary code
tkattkat Nov 18, 2025
d2af6c3
update package.json
tkattkat Nov 18, 2025
84e1266
Update dark-pans-carry.md
tkattkat Nov 18, 2025
8f001bc
update test
tkattkat Nov 18, 2025
91e427e
update zod to schema
tkattkat Nov 18, 2025
f701902
format
tkattkat Nov 18, 2025
d66a5d6
Merge remote-tracking branch 'origin/main' into zodv34
tkattkat Nov 19, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/dark-pans-carry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@browserbasehq/stagehand": patch
---

Add support for zod 4, while maintaining backwards compatibility for zod 3
2 changes: 1 addition & 1 deletion packages/core/examples/2048.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Stagehand } from "../lib/v3";
import { z } from "zod/v3";
import { z } from "zod";

async function example() {
console.log("🎮 Starting 2048 bot...");
Expand Down
2 changes: 1 addition & 1 deletion packages/core/examples/agent-custom-tools.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* This example shows how to pass custom tools to stagehand agent (both CUA and non-CUA)
*/
import { z } from "zod/v3";
import { z } from "zod";
import { tool } from "ai";
import { Stagehand } from "../lib/v3";
import chalk from "chalk";
Expand Down
2 changes: 1 addition & 1 deletion packages/core/examples/custom_client_aisdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/
import { Stagehand } from "../lib/v3";
import { AISdkClient } from "./external_clients/aisdk";
import { z } from "zod/v3";
import { z } from "zod";
import { openai } from "@ai-sdk/openai";

async function example() {
Expand Down
4 changes: 3 additions & 1 deletion packages/core/examples/custom_client_langchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@
*
* You will need to reference the Langchain Client in /external_clients/langchain.ts
*/
import { z } from "zod/v3";
import { z } from "zod";
import { Stagehand } from "../lib/v3";
import { LangchainClient } from "./external_clients/langchain";
import { ChatOpenAI } from "@langchain/openai";

async function example() {
// @ts-expect-error Type instantiation is excessively deep and possibly infinite
const stagehand = new Stagehand({
env: "BROWSERBASE",
verbose: 1,
// @ts-expect-error Type instantiation is excessively deep and possibly infinite
llmClient: new LangchainClient(
new ChatOpenAI({
model: "gpt-4o",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/examples/custom_client_openai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* You will need to reference the Custom OpenAI Client in /external_clients/customOpenAI.ts
*/
import { Stagehand } from "../lib/v3";
import { z } from "zod/v3";
import { z } from "zod";
import { CustomOpenAIClient } from "./external_clients/customOpenAI";
import OpenAI from "openai";

Expand Down
21 changes: 13 additions & 8 deletions packages/core/examples/external_clients/customOpenAI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
LLMClient,
} from "../../lib/v3";
import OpenAI from "openai";
import { zodResponseFormat } from "openai/helpers/zod";
import type {
ChatCompletion,
ChatCompletionAssistantMessageParam,
Expand All @@ -22,10 +21,10 @@ import type {
ChatCompletionSystemMessageParam,
ChatCompletionUserMessageParam,
} from "openai/resources/chat/completions";
import { z } from "zod/v3";
import { CreateChatCompletionResponseError } from "../../lib/v3";
import { StagehandZodSchema, toJsonSchema } from "../../lib/v3/zodCompat";

function validateZodSchema(schema: z.ZodTypeAny, data: unknown) {
function validateZodSchema(schema: StagehandZodSchema, data: unknown) {
try {
schema.parse(data);
return true;
Expand Down Expand Up @@ -83,12 +82,18 @@ export class CustomOpenAIClient extends LLMClient {
);
}

let responseFormat = undefined;
let responseFormat:
| ChatCompletionCreateParamsNonStreaming["response_format"]
| undefined;
if (options.response_model) {
responseFormat = zodResponseFormat(
options.response_model.schema,
options.response_model.name,
);
const responseModelSchema = options.response_model.schema;
responseFormat = {
type: "json_schema",
json_schema: {
name: options.response_model.name,
schema: toJsonSchema(responseModelSchema),
},
};
}

/* eslint-disable */
Expand Down
7 changes: 3 additions & 4 deletions packages/core/examples/external_clients/langchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import {
LLMClient,
AvailableModel,
} from "../../lib/v3";
import { zodToJsonSchema } from "zod-to-json-schema";
import {
AIMessage,
BaseMessageLike,
HumanMessage,
SystemMessage,
} from "@langchain/core/messages";
import { ChatCompletion } from "openai/resources";
import { toJsonSchema } from "../../lib/v3/zodCompat";

export class LangchainClient extends LLMClient {
public type = "langchainClient" as const;
Expand Down Expand Up @@ -60,9 +60,8 @@ export class LangchainClient extends LLMClient {
);

if (options.response_model) {
const responseSchema = zodToJsonSchema(options.response_model.schema, {
$refStrategy: "none",
});
//ref string no longer needed, this is now default behavior
const responseSchema = toJsonSchema(options.response_model.schema);
const structuredModel = this.model.withStructuredOutput(responseSchema);
const response = await structuredModel.invoke(formattedMessages);

Expand Down
4 changes: 0 additions & 4 deletions packages/core/examples/operator-example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,14 @@
*
* To learn more about Stagehand Agents, see: https://docs.stagehand.dev/concepts/agent
*/

import { Stagehand } from "../lib/v3";
import dotenv from "dotenv";
import chalk from "chalk";

// Load environment variables
dotenv.config();

async function main() {
console.log(`\n${chalk.bold("Stagehand 🤘 Operator Example")}\n`);

// Initialize Stagehand
const stagehand = new Stagehand({
env: "LOCAL",
Expand Down Expand Up @@ -48,5 +45,4 @@ async function main() {
// await stagehand.close();
}
}

main();
2 changes: 1 addition & 1 deletion packages/core/examples/parameterizeApiKey.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Stagehand } from "../lib/v3";
import { z } from "zod/v3";
import { z } from "zod";

/**
* This example shows how to parameterize the API key for the LLM provider.
Expand Down
2 changes: 1 addition & 1 deletion packages/core/examples/v3/patchright.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Stagehand } from "../../lib/v3";
import { chromium } from "patchright-core";
import { z } from "zod/v3";
import { z } from "zod";

async function example(stagehand: Stagehand) {
const browser = await chromium.connectOverCDP({
Expand Down
2 changes: 1 addition & 1 deletion packages/core/examples/v3/playwright.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Stagehand } from "../../lib/v3";
import { chromium } from "playwright-core";
import { z } from "zod/v3";
import { z } from "zod";

async function example(stagehand: Stagehand) {
const browser = await chromium.connectOverCDP({
Expand Down
2 changes: 1 addition & 1 deletion packages/core/examples/v3/recordVideo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import path from "node:path";
import { mkdir } from "node:fs/promises";
import { Stagehand } from "../../lib/v3";
import { chromium } from "playwright-core";
import { z } from "zod/v3";
import { z } from "zod";

async function recordPlaywrightVideo(stagehand: Stagehand): Promise<void> {
const browser = await chromium.connectOverCDP({
Expand Down
2 changes: 1 addition & 1 deletion packages/core/examples/v3/targetedExtract.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Stagehand } from "../../lib/v3";
import { z } from "zod/v3";
import { z } from "zod";

async function example(stagehand: Stagehand) {
const page = stagehand.context.pages()[0];
Expand Down
2 changes: 1 addition & 1 deletion packages/core/examples/v3_example.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { V3 } from "../lib/v3";
import { z } from "zod/v3";
import { z } from "zod";

async function example(v3: V3) {
const page = v3.context.pages()[0];
Expand Down
38 changes: 11 additions & 27 deletions packages/core/lib/inference.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { z } from "zod/v3";
import { z } from "zod";
import { LogLine } from "./v3/types/public/logs";
import { ChatMessage, LLMClient } from "./v3/llm/LLMClient";
import {
Expand All @@ -11,25 +11,12 @@ import {
buildObserveUserMessage,
} from "./prompt";
import { appendSummary, writeTimestampedTxtFile } from "./inferenceLogUtils";
import type { InferStagehandSchema, StagehandZodObject } from "./v3/zodCompat";

/** Simple usage shape if your LLM returns usage tokens. */
interface LLMUsage {
prompt_tokens: number;
completion_tokens: number;
total_tokens: number;
reasoning_tokens?: number;
cached_input_tokens?: number;
}

/**
* For calls that use a schema: the LLMClient may return { data: T; usage?: LLMUsage }
*/
export interface LLMParsedResponse<T> {
data: T;
usage?: LLMUsage;
}
// Re-export for backward compatibility
export type { LLMParsedResponse, LLMUsage } from "./v3/llm/LLMClient";

export async function extract({
export async function extract<T extends StagehandZodObject>({
instruction,
domElements,
schema,
Expand All @@ -40,7 +27,7 @@ export async function extract({
}: {
instruction: string;
domElements: string;
schema: z.ZodObject<z.ZodRawShape>;
schema: T;
llmClient: LLMClient;
userProvidedInstructions?: string;
logger: (message: LogLine) => void;
Expand All @@ -59,7 +46,7 @@ export async function extract({
),
});

type ExtractionResponse = z.infer<typeof schema>;
type ExtractionResponse = InferStagehandSchema<T>;
type MetadataResponse = z.infer<typeof metadataSchema>;

const isUsingAnthropic = llmClient.type === "anthropic";
Expand Down Expand Up @@ -103,8 +90,7 @@ export async function extract({
});
const extractEndTime = Date.now();

const { data: extractedData, usage: extractUsage } =
extractionResponse as LLMParsedResponse<ExtractionResponse>;
const { data: extractedData, usage: extractUsage } = extractionResponse;

let extractResponseFile = "";
if (logInferenceToFile) {
Expand Down Expand Up @@ -175,7 +161,7 @@ export async function extract({
progress: metadataResponseProgress,
},
usage: metadataResponseUsage,
} = metadataResponse as LLMParsedResponse<MetadataResponse>;
} = metadataResponse;

let metadataResponseFile = "";
if (logInferenceToFile) {
Expand Down Expand Up @@ -322,8 +308,7 @@ export async function observe({
const end = Date.now();
const usageTimeMs = end - start;

const { data: observeData, usage: observeUsage } =
rawResponse as LLMParsedResponse<ObserveResponse>;
const { data: observeData, usage: observeUsage } = rawResponse;
const promptTokens = observeUsage?.prompt_tokens ?? 0;
const completionTokens = observeUsage?.completion_tokens ?? 0;
const reasoningTokens = observeUsage?.reasoning_tokens ?? 0;
Expand Down Expand Up @@ -456,8 +441,7 @@ export async function act({
const end = Date.now();
const usageTimeMs = end - start;

const { data: actData, usage: actUsage } =
rawResponse as LLMParsedResponse<ActResponse>;
const { data: actData, usage: actUsage } = rawResponse;
const promptTokens = actUsage?.prompt_tokens ?? 0;
const completionTokens = actUsage?.completion_tokens ?? 0;
const reasoningTokens = actUsage?.reasoning_tokens ?? 0;
Expand Down
Loading