diff --git a/packages/protocol/schemas.ts b/packages/protocol/schemas.ts index d709fda25..7ab8b023f 100644 --- a/packages/protocol/schemas.ts +++ b/packages/protocol/schemas.ts @@ -210,7 +210,7 @@ export const ModelNameSchema = z }); export const CookieSchema = z - .object({ + .strictObject({ name: z.string(), value: z.string(), domain: z.string(), @@ -220,11 +220,10 @@ export const CookieSchema = z secure: z.boolean(), sameSite: z.enum(["Strict", "Lax", "None"]), }) - .strict() .meta({ id: "Cookie" }); export const CookieParamSchema = z - .object({ + .strictObject({ name: z.string(), value: z.string(), url: z.string().optional(), @@ -235,7 +234,6 @@ export const CookieParamSchema = z secure: z.boolean().optional(), sameSite: z.enum(["Strict", "Lax", "None"]).optional(), }) - .strict() .superRefine((cookie, context) => { let parsedUrl: URL | undefined; let invalidUrl = false; @@ -315,14 +313,13 @@ export const CookieParamSchema = z .meta({ id: "CookieParam" }); export const CookieRegexSchema = z - .object({ + .strictObject({ source: z.string(), flags: z .string() .regex(/^[dgimsuvy]*$/) .optional(), }) - .strict() .superRefine(({ source, flags }, context) => { try { new RegExp(source, flags); @@ -340,20 +337,18 @@ export const CookieFilterSchema = z .meta({ id: "CookieFilter" }); export const ClearCookieOptionsSchema = z - .object({ + .strictObject({ name: CookieFilterSchema.optional(), domain: CookieFilterSchema.optional(), path: CookieFilterSchema.optional(), }) - .strict() .meta({ id: "ClearCookieOptions" }); export const DomainPolicySchema = z - .object({ + .strictObject({ allowedDomains: z.array(z.string()).optional(), blockedDomains: z.array(z.string()).optional(), }) - .strict() .meta({ id: "DomainPolicy" }); // These schemas follow the MCP createMessage message and content shapes, with @@ -361,41 +356,37 @@ export const DomainPolicySchema = z export const LLMRoleSchema = z.enum(["user", "assistant"]).meta({ id: "LLMRole" }); export const LLMAnnotationsSchema = z - .object({ + .strictObject({ audience: z.array(LLMRoleSchema).optional(), priority: z.number().min(0).max(1).optional(), lastModified: z.string().optional(), }) - .strict() .meta({ id: "LLMAnnotations" }); export const LLMTextContentSchema = z - .object({ + .strictObject({ type: z.literal("text"), text: z.string(), annotations: LLMAnnotationsSchema.optional(), }) - .strict() .meta({ id: "LLMTextContent" }); export const LLMImageContentSchema = z - .object({ + .strictObject({ type: z.literal("image"), data: z.base64().meta({ format: "byte" }), mimeType: z.string(), annotations: LLMAnnotationsSchema.optional(), }) - .strict() .meta({ id: "LLMImageContent" }); export const LLMToolUseContentSchema = z - .object({ + .strictObject({ type: z.literal("tool_use"), id: z.string(), name: z.string(), input: z.record(z.string(), z.json()), }) - .strict() .meta({ id: "LLMToolUseContent" }); const LLMToolResultContentBlockSchema = z @@ -403,14 +394,13 @@ const LLMToolResultContentBlockSchema = z .meta({ id: "LLMToolResultContentBlock" }); export const LLMToolResultContentSchema = z - .object({ + .strictObject({ type: z.literal("tool_result"), toolUseId: z.string(), content: z.array(LLMToolResultContentBlockSchema), structuredContent: z.record(z.string(), z.json()).optional(), isError: z.boolean().optional(), }) - .strict() .meta({ id: "LLMToolResultContent" }); export const LLMMessageContentBlockSchema = z @@ -423,53 +413,48 @@ export const LLMMessageContentBlockSchema = z .meta({ id: "LLMMessageContentBlock" }); export const LLMMessageSchema = z - .object({ + .strictObject({ role: LLMRoleSchema, content: z.union([LLMMessageContentBlockSchema, z.array(LLMMessageContentBlockSchema)]), }) - .strict() .meta({ id: "LLMMessage" }); export const LLMToolAnnotationsSchema = z - .object({ + .strictObject({ title: z.string().optional(), readOnlyHint: z.boolean().optional(), destructiveHint: z.boolean().optional(), idempotentHint: z.boolean().optional(), openWorldHint: z.boolean().optional(), }) - .strict() .meta({ id: "LLMToolAnnotations" }); export const LLMToolExecutionSchema = z - .object({ + .strictObject({ taskSupport: z.enum(["forbidden", "optional", "required"]).optional(), }) - .strict() .meta({ id: "LLMToolExecution" }); export const LLMToolIconSchema = z - .object({ + .strictObject({ src: z.url(), mimeType: z.string().optional(), sizes: z.array(z.string()).optional(), theme: z.enum(["light", "dark"]).optional(), }) - .strict() .meta({ id: "LLMToolIcon" }); const LLMToolJsonSchema = z - .object({ + .strictObject({ $schema: z.string().optional(), type: z.literal("object"), properties: z.record(z.string(), z.record(z.string(), z.json())).optional(), required: z.array(z.string()).optional(), }) - .strict() .meta({ id: "LLMToolJson" }); export const LLMToolSchema = z - .object({ + .strictObject({ type: z.literal("function"), name: z.string(), description: z.string(), @@ -479,7 +464,7 @@ export const LLMToolSchema = z .meta({ id: "LLMTool" }); export const LLMClientToolSchema = z - .object({ + .strictObject({ name: z.string(), title: z.string().optional(), icons: z.array(LLMToolIconSchema).optional(), @@ -489,31 +474,27 @@ export const LLMClientToolSchema = z outputSchema: LLMToolJsonSchema.optional(), annotations: LLMToolAnnotationsSchema.optional(), }) - .strict() .meta({ id: "LLMClientTool" }); export const LLMToolChoiceSchema = z - .object({ + .strictObject({ mode: z.enum(["auto", "required", "none"]).optional(), }) - .strict() .meta({ id: "LLMToolChoice" }); export const LLMTextResponseFormatSchema = z - .object({ + .strictObject({ type: z.literal("text"), }) - .strict() .meta({ id: "LLMTextResponseFormat" }); export const LLMJsonSchemaResponseFormatSchema = z - .object({ + .strictObject({ type: z.literal("json_schema"), name: z.string(), description: z.string().optional(), schema: z.json(), }) - .strict() .meta({ id: "LLMJsonSchemaResponseFormat" }); export const LLMResponseFormatSchema = z @@ -521,52 +502,45 @@ export const LLMResponseFormatSchema = z .meta({ id: "LLMResponseFormat" }); const LLMGenerateBaseParamsSchema = z - .object({ + .strictObject({ messages: z.array(LLMMessageSchema), systemPrompt: z.string().optional(), temperature: z.number().optional(), stopSequences: z.array(z.string()).optional(), }) - .strict() .meta({ id: "LLMGenerateBaseParams" }); export const LLMMessageGenerateParamsSchema = LLMGenerateBaseParamsSchema.extend({ tools: z.array(LLMClientToolSchema).optional(), toolChoice: LLMToolChoiceSchema.optional(), responseFormat: LLMTextResponseFormatSchema.optional(), -}) - .strict() - .meta({ id: "LLMMessageGenerateParams" }); +}).meta({ id: "LLMMessageGenerateParams" }); export const LLMStructuredGenerateParamsSchema = LLMGenerateBaseParamsSchema.extend({ responseFormat: LLMJsonSchemaResponseFormatSchema, -}) - .strict() - .meta({ id: "LLMStructuredGenerateParams" }); +}).meta({ id: "LLMStructuredGenerateParams" }); export const LLMGenerateParamsSchema = z .union([LLMStructuredGenerateParamsSchema, LLMMessageGenerateParamsSchema]) .meta({ id: "LLMGenerateParams" }); export const LLMUsageSchema = z - .object({ + .strictObject({ inputTokens: z.number().int().nonnegative(), outputTokens: z.number().int().nonnegative(), totalTokens: z.number().int().nonnegative(), reasoningTokens: z.number().int().nonnegative().optional(), cachedInputTokens: z.number().int().nonnegative().optional(), }) - .strict() .meta({ id: "LLMUsage" }); const LLMGenerateBaseResultSchema = z - .object({ + .strictObject({ role: LLMRoleSchema, content: z.union([LLMMessageContentBlockSchema, z.array(LLMMessageContentBlockSchema)]), stopReason: z.string().optional(), usage: LLMUsageSchema.optional(), }) - .catchall(z.json()) .meta({ id: "LLMGenerateBaseResult" }); export const LLMMessageGenerateResultSchema = LLMGenerateBaseResultSchema.extend({ @@ -617,21 +591,18 @@ export const VariableValueSchema = z .union([ VariablePrimitiveSchema, z - .object({ + .strictObject({ value: VariablePrimitiveSchema, description: z.string().optional(), }) - .strict() .meta({ id: "DescribedVariableValue" }), ]) .meta({ id: "VariableValue" }); export const VariablesSchema = z.record(z.string(), VariableValueSchema).meta({ id: "Variables" }); -const staleLocatorHandleFields = ["page", "frame", "element"] as const; - -const PageLocatorKnownSchema = z - .object({ +export const PageLocatorSchema = z + .strictObject({ pageIdx: z.number().int().nonnegative().nullable().optional(), url: z.string().nullable().optional(), title: z.string().nullable().optional(), @@ -640,32 +611,17 @@ const PageLocatorKnownSchema = z tabId: z.number().int().nonnegative().nullable().optional(), frameId: z.string().nullable().optional(), }) - .meta({ id: "PageLocatorKnown" }); - -export const PageLocatorSchema = PageLocatorKnownSchema.loose() - .superRefine((value, ctx) => { - for (const key of staleLocatorHandleFields) { - if (key in value) { - ctx.addIssue({ - code: "custom", - message: `Unrecognized key: "${key}"`, - path: [key], - }); - } - } - }) - .meta({ id: "PageLocator" }) as unknown as typeof PageLocatorKnownSchema; + .meta({ id: "PageLocator" }); export const LocatorSchema = z - .object({ + .strictObject({ selector: z.string().min(1), nth: z.number().int().nonnegative().optional(), }) - .strict() .meta({ id: "Locator" }); export const StagehandMetricsSchema = z - .object({ + .strictObject({ actPromptTokens: z.number(), actCompletionTokens: z.number(), actReasoningTokens: z.number(), @@ -687,7 +643,6 @@ export const StagehandMetricsSchema = z totalCachedInputTokens: z.number(), totalInferenceTimeMs: z.number(), }) - .strict() .meta({ id: "StagehandMetrics" }); const CacheStatusSchema = z.enum(["HIT", "MISS"]).meta({ id: "CacheStatus" }); @@ -706,16 +661,15 @@ export const CachingSchema = z .meta({ id: "Caching" }); export const ApiKeyAuthSchema = z - .object({ + .strictObject({ type: z.literal("apiKey"), apiKey: z.string().min(1), }) - .strict() .meta({ id: "ApiKeyAuth" }); /** Detailed model configuration object */ export const GoogleServiceAccountCredentialsSchema = z - .object({ + .strictObject({ type: z.literal("service_account").optional(), projectId: z.string().optional(), privateKeyId: z.string().optional(), @@ -728,11 +682,10 @@ export const GoogleServiceAccountCredentialsSchema = z clientX509CertUrl: z.url().optional(), universeDomain: z.string().optional(), }) - .strict() .meta({ id: "GoogleServiceAccountCredentials" }); export const GoogleServiceAccountAuthSchema = z - .object({ + .strictObject({ type: z.literal("googleServiceAccount").meta({ description: "Use inline Google Cloud service account credentials for provider authentication", @@ -753,11 +706,10 @@ export const GoogleServiceAccountAuthSchema = z description: "Google Cloud universe domain", }), }) - .strict() .meta({ id: "GoogleServiceAccountAuth" }); export const AzureEntraIdAuthSchema = z - .object({ + .strictObject({ type: z.literal("azureEntraId").meta({ description: "Use a Microsoft Entra ID bearer token for authentication", }), @@ -765,11 +717,10 @@ export const AzureEntraIdAuthSchema = z description: "Microsoft Entra ID bearer token for Azure OpenAI", }), }) - .strict() .meta({ id: "AzureEntraIdAuth" }); export const VertexProviderOptionsSchema = z - .object({ + .strictObject({ project: z.string().meta({ description: "Google Cloud project ID for Vertex AI models", example: "my-gcp-project", @@ -785,11 +736,10 @@ export const VertexProviderOptionsSchema = z description: "Custom headers sent with every request to the Vertex AI provider", }), }) - .strict() .meta({ id: "VertexProviderOptions" }); export const AzureProviderOptionsSchema = z - .object({ + .strictObject({ resourceName: z.string().optional().meta({ description: "Azure OpenAI resource name", example: "my-azure-openai-resource", @@ -808,27 +758,24 @@ export const AzureProviderOptionsSchema = z description: "Custom headers sent with every request to the Azure OpenAI provider", }), }) - .strict() .meta({ id: "AzureProviderOptions" }); export const VertexModelProviderOptionsSchema = z - .object({ + .strictObject({ type: z.literal("vertex"), options: VertexProviderOptionsSchema.meta({ description: "Vertex AI provider-specific settings", }), }) - .strict() .meta({ id: "VertexModelProviderOptions" }); export const AzureModelProviderOptionsSchema = z - .object({ + .strictObject({ type: z.literal("azure"), options: AzureProviderOptionsSchema.meta({ description: "Azure OpenAI provider-specific settings", }), }) - .strict() .meta({ id: "AzureModelProviderOptions" }); export const ThinkingEffortSchema = z @@ -848,7 +795,7 @@ export const ModelProviderOptionsSchema = z .meta({ id: "ModelProviderOptions" }); export const ClientOptionsBaseSchema = z - .object({ + .strictObject({ provider: ModelProviderSchema.optional(), auth: ModelAuthSchema.optional(), providerOptions: ModelProviderOptionsSchema.optional(), @@ -859,7 +806,6 @@ export const ClientOptionsBaseSchema = z headers: z.record(z.string(), z.string()).optional(), reasoningEffort: z.string().optional(), }) - .strict() .meta({ id: "ClientOptionsBase" }); export const ClientOptionsSchema = ClientOptionsBaseSchema.default({}).meta({ @@ -867,7 +813,7 @@ export const ClientOptionsSchema = ClientOptionsBaseSchema.default({}).meta({ }); const ModelConnectionSchema = z - .object({ + .strictObject({ apiKey: z.string().min(1).optional().meta({ description: "API key for the model provider", example: "sk-some-openai-api-key", @@ -876,7 +822,6 @@ const ModelConnectionSchema = z description: "Custom headers sent with every request to the model provider", }), }) - .strict() .meta({ id: "ModelConnection" }); export const KnownModelConfigSchema = ModelConnectionSchema.extend({ @@ -884,9 +829,7 @@ export const KnownModelConfigSchema = ModelConnectionSchema.extend({ description: "An explicitly supported model name with its provider prefix", example: "openai/gpt-5.4-mini", }), -}) - .strict() - .meta({ id: "KnownModelConfig" }); +}).meta({ id: "KnownModelConfig" }); export const CustomModelConfigSchema = ModelConnectionSchema.extend({ modelName: z.string().min(1).meta({ @@ -897,9 +840,7 @@ export const CustomModelConfigSchema = ModelConnectionSchema.extend({ description: "Base URL for the custom OpenAI-compatible endpoint", example: "https://models.example.com/v1", }), -}) - .strict() - .meta({ id: "CustomModelConfig" }); +}).meta({ id: "CustomModelConfig" }); export const ModelConfigSchema = z .union([KnownModelConfigSchema, CustomModelConfigSchema]) @@ -907,15 +848,14 @@ export const ModelConfigSchema = z /** Serializable reference to an LLM implemented by the connected Stagehand client. */ export const ClientModelReferenceSchema = z - .object({ + .strictObject({ source: z.literal("client"), }) - .strict() .meta({ id: "ClientModelReference" }); /** Browserbase viewport configuration. */ export const BrowserbaseViewportSchema = z - .object({ + .strictObject({ width: z.number().optional(), height: z.number().optional(), }) @@ -923,7 +863,7 @@ export const BrowserbaseViewportSchema = z /** Browserbase fingerprint screen configuration. */ export const BrowserbaseFingerprintScreenSchema = z - .object({ + .strictObject({ maxHeight: z.number().optional(), maxWidth: z.number().optional(), minHeight: z.number().optional(), @@ -933,7 +873,7 @@ export const BrowserbaseFingerprintScreenSchema = z /** Browserbase fingerprint configuration for stealth mode. */ export const BrowserbaseFingerprintSchema = z - .object({ + .strictObject({ browsers: z.array(z.enum(["chrome", "edge", "firefox", "safari"])).optional(), devices: z.array(z.enum(["desktop", "mobile"])).optional(), httpVersion: z.enum(["1", "2"]).optional(), @@ -945,7 +885,7 @@ export const BrowserbaseFingerprintSchema = z /** Browserbase context configuration for session persistence. */ export const BrowserbaseContextSchema = z - .object({ + .strictObject({ id: z.string(), persist: z.boolean().optional(), }) @@ -953,7 +893,7 @@ export const BrowserbaseContextSchema = z /** Browserbase browser settings for session creation. */ export const BrowserbaseBrowserSettingsSchema = z - .object({ + .strictObject({ advancedStealth: z.boolean().optional(), blockAds: z.boolean().optional(), captchaImageSelector: z.string().optional(), @@ -972,7 +912,7 @@ export const BrowserbaseBrowserSettingsSchema = z /** Browserbase managed proxy geolocation configuration. */ export const BrowserbaseProxyGeolocationSchema = z - .object({ + .strictObject({ country: z.string(), city: z.string().optional(), state: z.string().optional(), @@ -981,7 +921,7 @@ export const BrowserbaseProxyGeolocationSchema = z /** Browserbase managed proxy configuration. */ export const BrowserbaseProxyConfigSchema = z - .object({ + .strictObject({ type: z.literal("browserbase"), domainPattern: z.string().optional(), geolocation: BrowserbaseProxyGeolocationSchema.optional(), @@ -990,7 +930,7 @@ export const BrowserbaseProxyConfigSchema = z /** External proxy configuration. */ export const ExternalProxyConfigSchema = z - .object({ + .strictObject({ type: z.literal("external"), server: z.string(), domainPattern: z.string().optional(), @@ -1011,7 +951,7 @@ export const BrowserbaseRegionSchema = z /** Browserbase session creation parameters. */ export const BrowserbaseSessionCreateParamsSchema = z - .object({ + .strictObject({ browserSettings: BrowserbaseBrowserSettingsSchema.optional(), extensionId: z.string().optional(), keepAlive: z.boolean().optional(), @@ -1020,20 +960,17 @@ export const BrowserbaseSessionCreateParamsSchema = z timeout: z.number().optional(), userMetadata: z.record(z.string(), z.unknown()).optional(), }) - .strict() .meta({ id: "BrowserbaseSessionCreateParams" }); /** Browserbase configuration available to both the SDK and the service worker. */ export const BrowserbaseBrowserSourceSchema = BrowserbaseSessionCreateParamsSchema.extend({ type: z.literal("browserbase"), sessionId: z.string().min(1), -}) - .strict() - .meta({ id: "BrowserbaseBrowserSource" }); +}).meta({ id: "BrowserbaseBrowserSource" }); /** Browser launch options for local browsers. */ export const LocalBrowserLaunchOptionsSchema = z - .object({ + .strictObject({ args: z.array(z.string()).optional(), executablePath: z.string().optional(), port: z.number().optional(), @@ -1044,7 +981,7 @@ export const LocalBrowserLaunchOptionsSchema = z chromiumSandbox: z.boolean().optional(), ignoreDefaultArgs: z.union([z.boolean(), z.array(z.string())]).optional(), proxy: z - .object({ + .strictObject({ server: z.string(), bypass: z.string().optional(), username: z.string().optional(), @@ -1052,7 +989,7 @@ export const LocalBrowserLaunchOptionsSchema = z }) .optional(), locale: z.string().optional(), - viewport: z.object({ width: z.number(), height: z.number() }).optional(), + viewport: z.strictObject({ width: z.number(), height: z.number() }).optional(), deviceScaleFactor: z.number().optional(), hasTouch: z.boolean().optional(), ignoreHTTPSErrors: z.boolean().optional(), @@ -1061,12 +998,11 @@ export const LocalBrowserLaunchOptionsSchema = z acceptDownloads: z.boolean().optional(), keepAlive: z.boolean().optional(), }) - .strict() .meta({ id: "LocalBrowserLaunchOptions" }); /** Action object returned by observe and used by act */ export const ActionSchema = z - .object({ + .strictObject({ selector: z.string().meta({ description: "CSS selector or XPath for the element", example: "[data-testid='submit-button']", @@ -1097,7 +1033,7 @@ export const ActionSchema = z // ============================================================================= export const ActOptionsSchema = z - .object({ + .strictObject({ model: ModelConfigSchema.optional().meta({ description: "Complete model configuration for this call; when omitted, the initialized Stagehand model is used", @@ -1124,12 +1060,11 @@ export const ActOptionsSchema = z description: "Override the instance-level cache setting for this request", }), }) - .optional() .meta({ id: "ActOptions" }); /** Inner act result data */ export const ActResultDataSchema = z - .object({ + .strictObject({ success: z.boolean().meta({ description: "Whether the action completed successfully", example: true, @@ -1149,7 +1084,7 @@ export const ActResultDataSchema = z .meta({ id: "ActResultData" }); export const ActResultSchema = z - .object({ + .strictObject({ result: ActResultDataSchema, actionId: z.string().optional().meta({ description: "Action ID for tracking", @@ -1165,7 +1100,7 @@ export const ActResultSchema = z // ============================================================================= export const ExtractOptionsSchema = z - .object({ + .strictObject({ model: ModelConfigSchema.optional().meta({ description: "Complete model configuration for this call; when omitted, the initialized Stagehand model is used", @@ -1197,11 +1132,10 @@ export const ExtractOptionsSchema = z description: "Override the instance-level cache setting for this request", }), }) - .optional() .meta({ id: "ExtractOptions" }); export const ExtractResultSchema = z - .object({ + .strictObject({ result: z.unknown().meta({ description: "Extracted data matching the requested schema", override: ({ jsonSchema }: { jsonSchema: Record }) => { @@ -1222,7 +1156,7 @@ export const ExtractResultSchema = z // ============================================================================= export const ObserveOptionsSchema = z - .object({ + .strictObject({ model: ModelConfigSchema.optional().meta({ description: "Complete model configuration for this call; when omitted, the initialized Stagehand model is used", @@ -1260,11 +1194,10 @@ export const ObserveOptionsSchema = z description: "Override the instance-level cache setting for this request", }), }) - .optional() .meta({ id: "ObserveOptions" }); export const ObserveResultSchema = z - .object({ + .strictObject({ result: z.array(ActionSchema), actionId: z.string().optional().meta({ description: "Action ID for tracking", @@ -1275,18 +1208,17 @@ export const ObserveResultSchema = z }) .meta({ id: "ObserveResult" }); -export const EmptyParamsSchema = z.object({}).strict().meta({ id: "EmptyParams" }); +export const EmptyParamsSchema = z.strictObject({}).meta({ id: "EmptyParams" }); export const LoadStateSchema = z .enum(["load", "domcontentloaded", "networkidle"]) .meta({ id: "LoadState" }); export const PageNavigationOptionsSchema = z - .object({ + .strictObject({ waitUntil: LoadStateSchema.optional(), timeout: z.number().int().positive().optional(), }) - .strict() .meta({ id: "PageNavigationOptions" }); export const PageVoidResultSchema = z @@ -1311,53 +1243,47 @@ export const ContextCloseResultSchema = z .meta({ id: "ContextCloseResult" }); export const PageCoordinateResultSchema = z - .object({ + .strictObject({ xpath: z.string(), }) - .strict() .meta({ id: "PageCoordinateResult" }); export const PageScreenshotClipSchema = z - .object({ + .strictObject({ x: z.number(), y: z.number(), width: z.number().positive(), height: z.number().positive(), }) - .strict() .meta({ id: "PageScreenshotClip" }); export const SnapshotResultSchema = z - .object({ + .strictObject({ formattedTree: z.string(), xpathMap: z.record(z.string(), z.string()), urlMap: z.record(z.string(), z.string()), }) - .strict() .meta({ id: "SnapshotResult" }); export const PageSnapshotOptionsSchema = z - .object({ + .strictObject({ includeIframes: z.boolean().optional(), }) - .strict() .meta({ id: "PageSnapshotOptions" }); export const PageRefSchema = z - .object({ + .strictObject({ pageId: z.string(), url: z.string().optional(), title: z.string().optional(), }) - .strict() .meta({ id: "PageRef" }); export const LocatorDescriptorSchema = z - .object({ + .strictObject({ pageId: z.string(), ...LocatorSchema.shape, }) - .strict() .meta({ id: "LocatorDescriptor" }); export const DEFAULT_TELEMETRY_CONFIG = { @@ -1381,7 +1307,7 @@ export const ImplementationInfoSchema = z .meta({ id: "ImplementationInfo" }); export const RuntimeDescriptorSchema = z - .looseObject({ + .strictObject({ protocolVersion: z.int().positive(), serverInfo: ImplementationInfoSchema.extend({ name: z.literal("stagehand"), @@ -1403,7 +1329,7 @@ export const TelemetryConfigSchema = z .meta({ id: "TelemetryConfig" }); export const StagehandInitParamsSchema = z - .object({ + .strictObject({ apiKey: z.string().min(1).optional(), browser: BrowserbaseBrowserSourceSchema.optional(), model: z.union([ModelConfigSchema, ClientModelReferenceSchema]).optional(), @@ -1416,145 +1342,125 @@ export const StagehandInitParamsSchema = z "Server-side caching of act/observe/extract results for this instance: a boolean toggle, or an object with an optional hit-count threshold. Requires a Browserbase apiKey and browser sessionId. Can be overridden per request via options.cache.", }), }) - .strict() .meta({ id: "StagehandInitParams" }); export const RuntimeConfigureParamsSchema = z - .object({ + .strictObject({ protocolVersion: z.int().positive().optional(), clientInfo: ImplementationInfoSchema.optional(), cdpUrl: z.string().min(1), telemetry: TelemetryConfigSchema.default(DEFAULT_TELEMETRY_CONFIG), logLevel: z.enum(["off", "error", "warn", "info", "debug"]).default("info"), }) - .strict() .meta({ id: "RuntimeConfigureParams" }); export const StagehandActParamsSchema = z - .object({ + .strictObject({ pageId: z.string().min(1), input: z.string().min(1), - options: ActOptionsSchema, + options: ActOptionsSchema.optional(), }) - .strict() .meta({ id: "StagehandActParams" }); export const StagehandObserveParamsSchema = z - .object({ + .strictObject({ pageId: z.string().min(1), instruction: z.string().optional(), - options: ObserveOptionsSchema, + options: ObserveOptionsSchema.optional(), }) - .strict() .meta({ id: "StagehandObserveParams" }); export const StagehandExtractParamsSchema = z - .object({ + .strictObject({ pageId: z.string().min(1), instruction: z.string().min(1), schema: z.json(), - options: ExtractOptionsSchema, + options: ExtractOptionsSchema.optional(), }) - .strict() .meta({ id: "StagehandExtractParams" }); export const ContextNewPageParamsSchema = z - .object({ + .strictObject({ url: z.string().optional(), }) - .strict() .meta({ id: "ContextNewPageParams" }); export const ContextSetActivePageParamsSchema = z - .object({ + .strictObject({ pageId: z.string(), }) - .strict() .meta({ id: "ContextSetActivePageParams" }); export const ContextAddInitScriptParamsSchema = z - .object({ + .strictObject({ source: z.string(), }) - .strict() .meta({ id: "ContextAddInitScriptParams" }); export const ContextSetExtraHTTPHeadersParamsSchema = z - .object({ + .strictObject({ headers: z.record(z.string(), z.string()), }) - .strict() .meta({ id: "ContextSetExtraHTTPHeadersParams" }); export const ContextSetDomainPolicyParamsSchema = z - .object({ + .strictObject({ policy: DomainPolicySchema.nullable(), }) - .strict() .meta({ id: "ContextSetDomainPolicyParams" }); export const ContextCookiesParamsSchema = z - .object({ + .strictObject({ urls: z.union([z.string(), z.array(z.string())]).optional(), }) - .strict() .meta({ id: "ContextCookiesParams" }); export const ContextAddCookiesParamsSchema = z - .object({ + .strictObject({ cookies: z.array(CookieParamSchema), }) - .strict() .meta({ id: "ContextAddCookiesParams" }); export const ContextClearCookiesParamsSchema = z - .object({ + .strictObject({ options: ClearCookieOptionsSchema.optional(), }) - .strict() .meta({ id: "ContextClearCookiesParams" }); export const ContextClipboardTargetSchema = z - .object({ + .strictObject({ pageId: z.string().optional(), }) - .strict() .meta({ id: "ContextClipboardTarget" }); export const ContextClipboardReadTextParamsSchema = ContextClipboardTargetSchema; export const ContextClipboardWriteTextParamsSchema = ContextClipboardTargetSchema.extend({ text: z.string(), -}) - .strict() - .meta({ id: "ContextClipboardWriteTextParams" }); +}).meta({ id: "ContextClipboardWriteTextParams" }); export const ContextClipboardClearParamsSchema = ContextClipboardTargetSchema; export const ContextClipboardPasteParamsSchema = ContextClipboardTargetSchema.extend({ shortcut: z.enum(["ControlOrMeta+V", "Meta+V", "Control+V"]).optional(), -}) - .strict() - .meta({ id: "ContextClipboardPasteParams" }); +}).meta({ id: "ContextClipboardPasteParams" }); export const ContextClipboardCopyParamsSchema = ContextClipboardTargetSchema; export const ContextClipboardCutParamsSchema = ContextClipboardTargetSchema; export const PageGotoParamsSchema = z - .object({ + .strictObject({ pageId: z.string(), url: z.string().min(1), options: PageNavigationOptionsSchema.optional(), }) - .strict() .meta({ id: "PageGotoParams" }); export const PageIdParamsSchema = z - .object({ + .strictObject({ pageId: z.string(), }) - .strict() .meta({ id: "PageIdParams" }); export const MouseButtonSchema = z.enum(["left", "right", "middle"]).meta({ id: "MouseButton" }); @@ -1563,54 +1469,41 @@ export const PageReloadParamsSchema = PageIdParamsSchema.extend({ options: PageNavigationOptionsSchema.extend({ ignoreCache: z.boolean().optional(), }) - .strict() .meta({ id: "PageReloadOptions" }) .optional(), -}) - .strict() - .meta({ id: "PageReloadParams" }); +}).meta({ id: "PageReloadParams" }); export const PageGoBackParamsSchema = PageIdParamsSchema.extend({ options: PageNavigationOptionsSchema.optional(), -}) - .strict() - .meta({ id: "PageGoBackParams" }); +}).meta({ id: "PageGoBackParams" }); export const PageGoForwardParamsSchema = PageIdParamsSchema.extend({ options: PageNavigationOptionsSchema.optional(), -}) - .strict() - .meta({ id: "PageGoForwardParams" }); +}).meta({ id: "PageGoForwardParams" }); export const PageClickParamsSchema = PageIdParamsSchema.extend({ x: z.number(), y: z.number(), options: z - .object({ + .strictObject({ button: MouseButtonSchema.optional(), clickCount: z.number().int().positive().optional(), returnXpath: z.boolean().optional(), }) - .strict() .meta({ id: "PageClickOptions" }) .optional(), -}) - .strict() - .meta({ id: "PageClickParams" }); +}).meta({ id: "PageClickParams" }); export const PageHoverParamsSchema = PageIdParamsSchema.extend({ x: z.number(), y: z.number(), options: z - .object({ + .strictObject({ returnXpath: z.boolean().optional(), }) - .strict() .meta({ id: "PageHoverOptions" }) .optional(), -}) - .strict() - .meta({ id: "PageHoverParams" }); +}).meta({ id: "PageHoverParams" }); export const PageScrollParamsSchema = PageIdParamsSchema.extend({ x: z.number(), @@ -1618,15 +1511,12 @@ export const PageScrollParamsSchema = PageIdParamsSchema.extend({ deltaX: z.number(), deltaY: z.number(), options: z - .object({ + .strictObject({ returnXpath: z.boolean().optional(), }) - .strict() .meta({ id: "PageScrollOptions" }) .optional(), -}) - .strict() - .meta({ id: "PageScrollParams" }); +}).meta({ id: "PageScrollParams" }); export const PageDragAndDropParamsSchema = PageIdParamsSchema.extend({ fromX: z.number(), @@ -1634,66 +1524,51 @@ export const PageDragAndDropParamsSchema = PageIdParamsSchema.extend({ toX: z.number(), toY: z.number(), options: z - .object({ + .strictObject({ button: MouseButtonSchema.optional(), steps: z.number().int().positive().optional(), delay: z.number().nonnegative().optional(), returnXpath: z.boolean().optional(), }) - .strict() .meta({ id: "PageDragAndDropOptions" }) .optional(), -}) - .strict() - .meta({ id: "PageDragAndDropParams" }); +}).meta({ id: "PageDragAndDropParams" }); export const PageTypeParamsSchema = PageIdParamsSchema.extend({ text: z.string(), options: z - .object({ + .strictObject({ delay: z.number().nonnegative().optional(), withMistakes: z.boolean().optional(), }) - .strict() .meta({ id: "PageTypeOptions" }) .optional(), -}) - .strict() - .meta({ id: "PageTypeParams" }); +}).meta({ id: "PageTypeParams" }); export const PageKeyPressParamsSchema = PageIdParamsSchema.extend({ key: z.string().min(1), options: z - .object({ + .strictObject({ delay: z.number().nonnegative().optional(), }) - .strict() .meta({ id: "PageKeyPressOptions" }) .optional(), -}) - .strict() - .meta({ id: "PageKeyPressParams" }); +}).meta({ id: "PageKeyPressParams" }); export const PageEvaluateParamsSchema = PageIdParamsSchema.extend({ expression: z.string(), -}) - .strict() - .meta({ id: "PageEvaluateParams" }); +}).meta({ id: "PageEvaluateParams" }); export const PageAddInitScriptParamsSchema = PageIdParamsSchema.extend({ source: z.string(), -}) - .strict() - .meta({ id: "PageAddInitScriptParams" }); +}).meta({ id: "PageAddInitScriptParams" }); export const PageSetExtraHTTPHeadersParamsSchema = PageIdParamsSchema.extend({ headers: z.record(z.string(), z.string()), -}) - .strict() - .meta({ id: "PageSetExtraHTTPHeadersParams" }); +}).meta({ id: "PageSetExtraHTTPHeadersParams" }); export const PageScreenshotOptionsSchema = z - .object({ + .strictObject({ animations: z.enum(["disabled", "allow"]).optional(), caret: z.enum(["hide", "initial"]).optional(), clip: PageScreenshotClipSchema.optional(), @@ -1707,7 +1582,6 @@ export const PageScreenshotOptionsSchema = z timeout: z.number().nonnegative().optional(), type: z.enum(["png", "jpeg"]).optional(), }) - .strict() .refine((options) => !(options.fullPage && options.clip), { message: "fullPage and clip cannot be used together", path: ["clip"], @@ -1720,181 +1594,143 @@ export const PageScreenshotOptionsSchema = z export const PageScreenshotParamsSchema = PageIdParamsSchema.extend({ options: PageScreenshotOptionsSchema.optional(), -}) - .strict() - .meta({ id: "PageScreenshotParams" }); +}).meta({ id: "PageScreenshotParams" }); export const PageSnapshotParamsSchema = PageIdParamsSchema.extend({ options: PageSnapshotOptionsSchema.optional(), -}) - .strict() - .meta({ id: "PageSnapshotParams" }); +}).meta({ id: "PageSnapshotParams" }); export const PageSetViewportSizeParamsSchema = PageIdParamsSchema.extend({ width: z.number().int().positive(), height: z.number().int().positive(), options: z - .object({ + .strictObject({ deviceScaleFactor: z.number().positive().optional(), }) - .strict() .meta({ id: "PageSetViewportSizeOptions" }) .optional(), -}) - .strict() - .meta({ id: "PageSetViewportSizeParams" }); +}).meta({ id: "PageSetViewportSizeParams" }); export const PageWaitForLoadStateParamsSchema = PageIdParamsSchema.extend({ state: LoadStateSchema, timeout: z.number().int().nonnegative().optional(), -}) - .strict() - .meta({ id: "PageWaitForLoadStateParams" }); +}).meta({ id: "PageWaitForLoadStateParams" }); export const PageWaitForTimeoutParamsSchema = PageIdParamsSchema.extend({ ms: z.number().int().nonnegative(), -}) - .strict() - .meta({ id: "PageWaitForTimeoutParams" }); +}).meta({ id: "PageWaitForTimeoutParams" }); export const PageWaitForSelectorParamsSchema = PageIdParamsSchema.extend({ selector: z.string().min(1), options: z - .object({ + .strictObject({ state: z.enum(["attached", "detached", "visible", "hidden"]).optional(), timeout: z.number().int().nonnegative().optional(), pierceShadow: z.boolean().optional(), }) - .strict() .meta({ id: "PageWaitForSelectorOptions" }) .optional(), -}) - .strict() - .meta({ id: "PageWaitForSelectorParams" }); +}).meta({ id: "PageWaitForSelectorParams" }); export const LocatorClickParamsSchema = LocatorDescriptorSchema.extend({ options: z - .object({ + .strictObject({ button: MouseButtonSchema.optional(), clickCount: z.number().int().positive().optional(), }) - .strict() .meta({ id: "LocatorClickOptions" }) .optional(), -}) - .strict() - .meta({ id: "LocatorClickParams" }); +}).meta({ id: "LocatorClickParams" }); export const LocatorFillParamsSchema = LocatorDescriptorSchema.extend({ value: z.string(), -}) - .strict() - .meta({ id: "LocatorFillParams" }); +}).meta({ id: "LocatorFillParams" }); export const LocatorScrollToParamsSchema = LocatorDescriptorSchema.extend({ percent: z.union([z.number(), z.string()]), -}) - .strict() - .meta({ id: "LocatorScrollToParams" }); +}).meta({ id: "LocatorScrollToParams" }); export const RgbaColorSchema = z - .object({ + .strictObject({ r: z.number(), g: z.number(), b: z.number(), a: z.number().optional(), }) - .strict() .meta({ id: "RgbaColor" }); export const LocatorHighlightParamsSchema = LocatorDescriptorSchema.extend({ options: z - .object({ + .strictObject({ durationMs: z.number().int().nonnegative().optional(), borderColor: RgbaColorSchema.optional(), contentColor: RgbaColorSchema.optional(), }) - .strict() .meta({ id: "LocatorHighlightOptions" }) .optional(), -}) - .strict() - .meta({ id: "LocatorHighlightParams" }); +}).meta({ id: "LocatorHighlightParams" }); export const LocatorSendClickEventParamsSchema = LocatorDescriptorSchema.extend({ options: z - .object({ + .strictObject({ bubbles: z.boolean().optional(), cancelable: z.boolean().optional(), composed: z.boolean().optional(), detail: z.number().optional(), }) - .strict() .meta({ id: "LocatorSendClickEventOptions" }) .optional(), -}) - .strict() - .meta({ id: "LocatorSendClickEventParams" }); +}).meta({ id: "LocatorSendClickEventParams" }); export const LocatorTypeParamsSchema = LocatorDescriptorSchema.extend({ text: z.string(), options: z - .object({ + .strictObject({ delay: z.number().nonnegative().optional(), }) - .strict() .meta({ id: "LocatorTypeOptions" }) .optional(), -}) - .strict() - .meta({ id: "LocatorTypeParams" }); +}).meta({ id: "LocatorTypeParams" }); export const LocatorSelectOptionParamsSchema = LocatorDescriptorSchema.extend({ values: z.union([z.string(), z.array(z.string())]), -}) - .strict() - .meta({ id: "LocatorSelectOptionParams" }); +}).meta({ id: "LocatorSelectOptionParams" }); export const StagehandPingResultSchema = z - .object({ + .strictObject({ ok: z.literal(true), runtime: z.literal("service_worker"), }) - .strict() .meta({ id: "StagehandPingResult" }); export const RuntimeConfigureResultSchema = z - .object({ + .strictObject({ configured: z.literal(true), }) - .strict() .meta({ id: "RuntimeConfigureResult" }); export const RuntimeLoopbackStatusResultSchema = z - .object({ + .strictObject({ configured: z.boolean(), connected: z.boolean(), }) - .strict() .meta({ id: "RuntimeLoopbackStatusResult" }); export const BrowserGetVersionResultSchema = z - .object({ + .strictObject({ protocolVersion: z.string().optional(), product: z.string().optional(), revision: z.string().optional(), userAgent: z.string().optional(), jsVersion: z.string().optional(), }) - .strict() .meta({ id: "BrowserGetVersionResult" }); export const StagehandInitResultSchema = z - .object({ + .strictObject({ initialized: z.literal(true), pages: z.array(PageRefSchema), }) - .strict() .meta({ id: "StagehandInitResult" }); export const StagehandCloseResultSchema = z @@ -1911,38 +1747,33 @@ export const ContextActivePageResultSchema = PageRefSchema.nullable().meta({ }); export const ContextGetDomainPolicyResultSchema = z - .object({ + .strictObject({ policy: DomainPolicySchema.nullable(), }) - .strict() .meta({ id: "ContextGetDomainPolicyResult" }); export const ContextCookiesResultSchema = z - .object({ + .strictObject({ cookies: z.array(CookieSchema), }) - .strict() .meta({ id: "ContextCookiesResult" }); export const ContextClipboardReadTextResultSchema = z - .object({ + .strictObject({ text: z.string(), }) - .strict() .meta({ id: "ContextClipboardReadTextResult" }); export const PageUrlResultSchema = z - .object({ + .strictObject({ url: z.string(), }) - .strict() .meta({ id: "PageUrlResult" }); export const PageTitleResultSchema = z - .object({ + .strictObject({ title: z.string(), }) - .strict() .meta({ id: "PageTitleResult" }); export const PageCloseResultSchema = z @@ -1953,33 +1784,29 @@ export const PageCloseResultSchema = z .meta({ id: "PageCloseResult" }); export const PageDragAndDropResultSchema = z - .object({ + .strictObject({ fromXpath: z.string(), toXpath: z.string(), }) - .strict() .meta({ id: "PageDragAndDropResult" }); export const PageEvaluateResultSchema = z - .object({ + .strictObject({ value: z.json(), }) - .strict() .meta({ id: "PageEvaluateResult" }); export const PageScreenshotResultSchema = z - .object({ + .strictObject({ data: z.base64().meta({ format: "byte" }), type: z.enum(["png", "jpeg"]), }) - .strict() .meta({ id: "PageScreenshotResult" }); export const PageWaitForSelectorResultSchema = z - .object({ + .strictObject({ matched: z.boolean(), }) - .strict() .meta({ id: "PageWaitForSelectorResult" }); export const LocatorClickResultSchema = z @@ -2004,52 +1831,45 @@ export const LocatorHoverResultSchema = z .meta({ id: "LocatorHoverResult" }); export const LocatorCountResultSchema = z - .object({ + .strictObject({ count: z.number().int().nonnegative(), }) - .strict() .meta({ id: "LocatorCountResult" }); export const LocatorIsCheckedResultSchema = z - .object({ + .strictObject({ checked: z.boolean(), }) - .strict() .meta({ id: "LocatorIsCheckedResult" }); export const LocatorInputValueResultSchema = z - .object({ + .strictObject({ value: z.string(), }) - .strict() .meta({ id: "LocatorInputValueResult" }); export const LocatorIsVisibleResultSchema = z - .object({ + .strictObject({ visible: z.boolean(), }) - .strict() .meta({ id: "LocatorIsVisibleResult" }); export const LocatorInnerTextResultSchema = z - .object({ + .strictObject({ text: z.string(), }) - .strict() .meta({ id: "LocatorInnerTextResult" }); export const LocatorInnerHtmlResultSchema = z - .object({ + .strictObject({ html: z.string(), }) - .strict() .meta({ id: "LocatorInnerHtmlResult" }); export const LocatorTextContentResultSchema = z - .object({ + .strictObject({ textContent: z.string(), }) - .strict() .meta({ id: "LocatorTextContentResult" }); export const LocatorScrollToResultSchema = z @@ -2060,11 +1880,10 @@ export const LocatorScrollToResultSchema = z .meta({ id: "LocatorScrollToResult" }); export const LocatorCentroidResultSchema = z - .object({ + .strictObject({ x: z.number(), y: z.number(), }) - .strict() .meta({ id: "LocatorCentroidResult" }); export const LocatorHighlightResultSchema = z @@ -2089,10 +1908,9 @@ export const LocatorTypeResultSchema = z .meta({ id: "LocatorTypeResult" }); export const LocatorSelectOptionResultSchema = z - .object({ + .strictObject({ values: z.array(z.string()), }) - .strict() .meta({ id: "LocatorSelectOptionResult" }); export const StagehandLogLevelSchema = z diff --git a/packages/protocol/stagehand.v4.json b/packages/protocol/stagehand.v4.json index 32294180d..d3a50f9d5 100644 --- a/packages/protocol/stagehand.v4.json +++ b/packages/protocol/stagehand.v4.json @@ -1274,7 +1274,8 @@ "viewport": { "$ref": "#/$defs/BrowserbaseViewport" } - } + }, + "additionalProperties": false }, "BrowserbaseContext": { "type": "object", @@ -1286,7 +1287,8 @@ "type": "boolean" } }, - "required": ["id"] + "required": ["id"], + "additionalProperties": false }, "BrowserbaseFingerprint": { "type": "object", @@ -1325,7 +1327,8 @@ "screen": { "$ref": "#/$defs/BrowserbaseFingerprintScreen" } - } + }, + "additionalProperties": false }, "BrowserbaseFingerprintScreen": { "type": "object", @@ -1342,7 +1345,8 @@ "min_width": { "type": "number" } - } + }, + "additionalProperties": false }, "BrowserbaseViewport": { "type": "object", @@ -1353,7 +1357,8 @@ "height": { "type": "number" } - } + }, + "additionalProperties": false }, "ProxyConfig": { "oneOf": [ @@ -1379,7 +1384,8 @@ "$ref": "#/$defs/BrowserbaseProxyGeolocation" } }, - "required": ["type"] + "required": ["type"], + "additionalProperties": false }, "BrowserbaseProxyGeolocation": { "type": "object", @@ -1394,7 +1400,8 @@ "type": "string" } }, - "required": ["country"] + "required": ["country"], + "additionalProperties": false }, "ExternalProxyConfig": { "type": "object", @@ -1416,7 +1423,8 @@ "type": "string" } }, - "required": ["type", "server"] + "required": ["type", "server"], + "additionalProperties": false }, "BrowserbaseRegion": { "type": "string", @@ -1656,7 +1664,8 @@ "description": "Override the instance-level cache setting for this request", "$ref": "#/$defs/Caching" } - } + }, + "additionalProperties": false }, "Variables": { "type": "object", @@ -1734,7 +1743,8 @@ "$ref": "#/$defs/CacheStatus" } }, - "required": ["result"] + "required": ["result"], + "additionalProperties": false }, "ActResultData": { "type": "object", @@ -1762,7 +1772,8 @@ "description": "List of actions that were executed" } }, - "required": ["success", "message", "action_description", "actions"] + "required": ["success", "message", "action_description", "actions"], + "additionalProperties": false }, "Action": { "type": "object", @@ -1792,6 +1803,7 @@ } }, "required": ["selector", "description"], + "additionalProperties": false, "description": "Action object returned by observe and used by act" }, "CacheStatus": { @@ -1859,7 +1871,8 @@ "description": "Override the instance-level cache setting for this request", "$ref": "#/$defs/Caching" } - } + }, + "additionalProperties": false }, "ObserveResult": { "type": "object", @@ -1879,7 +1892,8 @@ "$ref": "#/$defs/CacheStatus" } }, - "required": ["result"] + "required": ["result"], + "additionalProperties": false }, "StagehandExtractParams": { "type": "object", @@ -1971,7 +1985,8 @@ "description": "Override the instance-level cache setting for this request", "$ref": "#/$defs/Caching" } - } + }, + "additionalProperties": false }, "ExtractResult": { "type": "object", @@ -1988,7 +2003,8 @@ "$ref": "#/$defs/CacheStatus" } }, - "required": ["result"] + "required": ["result"], + "additionalProperties": false }, "StagehandMetrics": { "type": "object", @@ -2654,9 +2670,7 @@ } }, "required": ["role", "content", "output_format"], - "additionalProperties": { - "$ref": "#/$defs/__schema5" - } + "additionalProperties": false }, "LLMUsage": { "type": "object", @@ -2690,37 +2704,6 @@ "required": ["input_tokens", "output_tokens", "total_tokens"], "additionalProperties": false }, - "__schema5": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - }, - { - "type": "null" - }, - { - "type": "array", - "items": { - "$ref": "#/$defs/__schema5" - } - }, - { - "type": "object", - "propertyNames": { - "type": "string" - }, - "additionalProperties": { - "$ref": "#/$defs/__schema5" - } - } - ] - }, "LLMStructuredGenerateResult": { "type": "object", "properties": { @@ -2751,15 +2734,13 @@ "const": "json_schema" }, "structured_content": { - "$ref": "#/$defs/__schema6" + "$ref": "#/$defs/__schema5" } }, "required": ["role", "content", "output_format", "structured_content"], - "additionalProperties": { - "$ref": "#/$defs/__schema5" - } + "additionalProperties": false }, - "__schema6": { + "__schema5": { "anyOf": [ { "type": "string" @@ -2776,7 +2757,7 @@ { "type": "array", "items": { - "$ref": "#/$defs/__schema6" + "$ref": "#/$defs/__schema5" } }, { @@ -2785,7 +2766,7 @@ "type": "string" }, "additionalProperties": { - "$ref": "#/$defs/__schema6" + "$ref": "#/$defs/__schema5" } } ] @@ -3526,13 +3507,13 @@ "type": "object", "properties": { "value": { - "$ref": "#/$defs/__schema7" + "$ref": "#/$defs/__schema6" } }, "required": ["value"], "additionalProperties": false }, - "__schema7": { + "__schema6": { "anyOf": [ { "type": "string" @@ -3549,7 +3530,7 @@ { "type": "array", "items": { - "$ref": "#/$defs/__schema7" + "$ref": "#/$defs/__schema6" } }, { @@ -3558,7 +3539,7 @@ "type": "string" }, "additionalProperties": { - "$ref": "#/$defs/__schema7" + "$ref": "#/$defs/__schema6" } } ] @@ -4332,10 +4313,10 @@ "type": "string" }, "additionalProperties": { - "$ref": "#/$defs/__schema8" + "$ref": "#/$defs/__schema7" } }, - "__schema8": { + "__schema7": { "anyOf": [ { "type": "string" @@ -4352,7 +4333,7 @@ { "type": "array", "items": { - "$ref": "#/$defs/__schema8" + "$ref": "#/$defs/__schema7" } }, { @@ -4361,7 +4342,7 @@ "type": "string" }, "additionalProperties": { - "$ref": "#/$defs/__schema8" + "$ref": "#/$defs/__schema7" } } ] @@ -6210,7 +6191,7 @@ "const": "2.0" }, "result": { - "$ref": "#/$defs/__schema9" + "$ref": "#/$defs/__schema8" }, "id": { "$ref": "#/$defs/JSONRPCRequestId" @@ -6219,7 +6200,7 @@ "required": ["jsonrpc", "result", "id"], "additionalProperties": false }, - "__schema9": { + "__schema8": { "anyOf": [ { "type": "string" @@ -6236,7 +6217,7 @@ { "type": "array", "items": { - "$ref": "#/$defs/__schema9" + "$ref": "#/$defs/__schema8" } }, { @@ -6245,7 +6226,7 @@ "type": "string" }, "additionalProperties": { - "$ref": "#/$defs/__schema9" + "$ref": "#/$defs/__schema8" } } ] @@ -6279,13 +6260,13 @@ "type": "string" }, "data": { - "$ref": "#/$defs/__schema10" + "$ref": "#/$defs/__schema9" } }, "required": ["code", "message"], "additionalProperties": false }, - "__schema10": { + "__schema9": { "anyOf": [ { "type": "string" @@ -6302,7 +6283,7 @@ { "type": "array", "items": { - "$ref": "#/$defs/__schema10" + "$ref": "#/$defs/__schema9" } }, { @@ -6311,7 +6292,7 @@ "type": "string" }, "additionalProperties": { - "$ref": "#/$defs/__schema10" + "$ref": "#/$defs/__schema9" } } ] diff --git a/packages/protocol/tests/protocol/client-llm.test.ts b/packages/protocol/tests/protocol/client-llm.test.ts index 02944b2ed..a32afda6e 100644 --- a/packages/protocol/tests/protocol/client-llm.test.ts +++ b/packages/protocol/tests/protocol/client-llm.test.ts @@ -70,5 +70,14 @@ describe("client-side LLM protocol", () => { structuredContent: { finalAnswer: "four" }, }), ).toMatchObject({ structuredContent: { finalAnswer: "four" } }); + + expect(() => + LLMGenerateResultSchema.parse({ + role: "assistant", + content: { type: "text", text: "Four" }, + outputFormat: "text", + providerMetadata: { requestId: "request-1" }, + }), + ).toThrow(); }); }); diff --git a/packages/protocol/tests/protocol/object-model-protocol.test.ts b/packages/protocol/tests/protocol/object-model-protocol.test.ts index 52e44be8e..5baf0994e 100644 --- a/packages/protocol/tests/protocol/object-model-protocol.test.ts +++ b/packages/protocol/tests/protocol/object-model-protocol.test.ts @@ -6,6 +6,7 @@ import { StagehandRpcNotificationSchema, StagehandRpcRequestSchema, } from "../../schema-registry.js"; +import { PageLocatorSchema } from "../../schemas.js"; describe("Stagehand object-model protocol", () => { it("derives every Stagehand method name from the RPC definitions", () => { @@ -14,6 +15,15 @@ describe("Stagehand object-model protocol", () => { ); }); + it("rejects unknown page locator fields", () => { + expect(() => + PageLocatorSchema.parse({ + pageIdx: 0, + page: { targetId: "target-1" }, + }), + ).toThrow(); + }); + it("defines stagehand init as a JSON-RPC method", () => { const params = StagehandMethods.stagehandInit.params.parse({ apiKey: "bb_key", @@ -270,6 +280,16 @@ describe("Stagehand object-model protocol", () => { }), ).toThrow(); }); + + it("rejects unknown action option fields", () => { + expect(() => + StagehandMethods.stagehandAct.params.parse({ + pageId: "target-1", + input: "Click the submit button", + options: { tiemout: 1_000 }, + }), + ).toThrow(); + }); it("requires page ids for page methods", () => { expect(() => StagehandMethods.pageGoto.params.parse({ diff --git a/packages/protocol/tests/rpc-client/cdp-client.test.ts b/packages/protocol/tests/rpc-client/cdp-client.test.ts index f515b2c38..3a0fec9ab 100644 --- a/packages/protocol/tests/rpc-client/cdp-client.test.ts +++ b/packages/protocol/tests/rpc-client/cdp-client.test.ts @@ -571,7 +571,8 @@ describe("waitForRuntimeReady", () => { ).rejects.toBeInstanceOf(StagehandRuntimeIncompatibleError); }); - it("accepts compatible markers with unknown descriptor fields", async () => { + it("does not accept markers with unknown descriptor fields", async () => { + let now = 0; const cdp = new FakeCdp().on("Runtime.evaluate", () => ({ result: { value: { @@ -583,10 +584,14 @@ describe("waitForRuntimeReady", () => { await expect( waitForRuntimeReady(cdp, "worker-session", { - timeout: 1_000, - delayFn: async () => {}, + pollIntervalMs: 1, + timeout: 1, + nowFn: () => now, + delayFn: async (ms) => { + now += ms; + }, }), - ).resolves.toBeUndefined(); + ).rejects.toThrow("Timed out waiting for the Stagehand extension runtime to become ready"); }); }); diff --git a/packages/sdk-python/src/stagehand/_generated/models.py b/packages/sdk-python/src/stagehand/_generated/models.py index 14ff92a52..bb33c9b4e 100644 --- a/packages/sdk-python/src/stagehand/_generated/models.py +++ b/packages/sdk-python/src/stagehand/_generated/models.py @@ -4,7 +4,7 @@ from __future__ import annotations from enum import StrEnum -from typing import Annotated, Any, Dict, Literal, Optional, Union +from typing import Annotated, Any, Literal, Optional, Union from pydantic import ( ConfigDict, @@ -25,6 +25,7 @@ class ActOptions(WireModel): model_config = ConfigDict( + extra="forbid", validate_by_name=True, ) model: Optional[ModelConfig] = None @@ -54,6 +55,7 @@ class ActOptions(WireModel): class ActResult(WireModel): model_config = ConfigDict( + extra="forbid", validate_by_name=True, ) result: ActResultData @@ -65,6 +67,7 @@ class ActResult(WireModel): class ActResultData(WireModel): model_config = ConfigDict( + extra="forbid", validate_by_name=True, ) success: Annotated[StrictBool, Field(examples=[True])] @@ -97,6 +100,7 @@ class Action(WireModel): """Action object returned by observe and used by act""" model_config = ConfigDict( + extra="forbid", validate_by_name=True, ) selector: Annotated[StrictStr, Field(examples=["[data-testid='submit-button']"])] @@ -162,6 +166,7 @@ class BrowserGetVersionResult(WireModel): class BrowserbaseBrowserSettings(WireModel): model_config = ConfigDict( + extra="forbid", validate_by_name=True, ) advanced_stealth: Optional[StrictBool] = None @@ -197,6 +202,7 @@ class BrowserbaseBrowserSource(WireModel): class BrowserbaseContext(WireModel): model_config = ConfigDict( + extra="forbid", validate_by_name=True, ) id: StrictStr @@ -205,6 +211,7 @@ class BrowserbaseContext(WireModel): class BrowserbaseFingerprint(WireModel): model_config = ConfigDict( + extra="forbid", validate_by_name=True, ) browsers: Optional[list[Browser]] = None @@ -217,6 +224,7 @@ class BrowserbaseFingerprint(WireModel): class BrowserbaseFingerprintScreen(WireModel): model_config = ConfigDict( + extra="forbid", validate_by_name=True, ) max_height: Optional[StrictFloat] = None @@ -227,6 +235,7 @@ class BrowserbaseFingerprintScreen(WireModel): class BrowserbaseProxyConfig(WireModel): model_config = ConfigDict( + extra="forbid", validate_by_name=True, ) type: Literal["browserbase"] @@ -236,6 +245,7 @@ class BrowserbaseProxyConfig(WireModel): class BrowserbaseProxyGeolocation(WireModel): model_config = ConfigDict( + extra="forbid", validate_by_name=True, ) country: StrictStr @@ -252,6 +262,7 @@ class BrowserbaseRegion(StrEnum): class BrowserbaseViewport(WireModel): model_config = ConfigDict( + extra="forbid", validate_by_name=True, ) width: Optional[StrictFloat] = None @@ -543,6 +554,7 @@ class EmptyParams(WireModel): class ExternalProxyConfig(WireModel): model_config = ConfigDict( + extra="forbid", validate_by_name=True, ) type: Literal["external"] @@ -554,6 +566,7 @@ class ExternalProxyConfig(WireModel): class ExtractOptions(WireModel): model_config = ConfigDict( + extra="forbid", validate_by_name=True, ) model: Optional[ModelConfig] = None @@ -593,6 +606,7 @@ class ExtractOptions(WireModel): class ExtractResult(WireModel): model_config = ConfigDict( + extra="forbid", validate_by_name=True, ) result: Any @@ -615,12 +629,6 @@ class FieldSchema1( root: Optional[Union[StrictStr, StrictFloat, StrictBool, list[Optional["FieldSchema1"]], dict[StrictStr, Optional["FieldSchema1"]]]] -class FieldSchema10( - RootModel[Optional[Union[StrictStr, StrictFloat, StrictBool, list[Optional["FieldSchema10"]], dict[StrictStr, Optional["FieldSchema10"]]]]] -): - root: Optional[Union[StrictStr, StrictFloat, StrictBool, list[Optional["FieldSchema10"]], dict[StrictStr, Optional["FieldSchema10"]]]] - - class FieldSchema2( RootModel[Optional[Union[StrictStr, StrictFloat, StrictBool, list[Optional["FieldSchema2"]], dict[StrictStr, Optional["FieldSchema2"]]]]] ): @@ -708,7 +716,7 @@ class JSONRPCErrorObject(WireModel): ) code: Annotated[StrictInt, Field(ge=-9007199254740991, le=9007199254740991)] message: StrictStr - data: Optional[FieldSchema10] = None + data: Optional[FieldSchema9] = None class JSONRPCRequestId(RootModel[StrictInt]): @@ -816,12 +824,9 @@ class LLMMessageGenerateParams(WireModel): class LLMMessageGenerateResult(WireModel): model_config = ConfigDict( - extra="allow", + extra="forbid", validate_by_name=True, ) - __annotations__ = { - "__pydantic_extra__": Dict[str, Optional[FieldSchema5]], - } role: LLMRole content: Union[LLMMessageContentBlock, list[LLMMessageContentBlock]] stop_reason: Optional[StrictStr] = None @@ -854,18 +859,15 @@ class LLMGenerateParams( class LLMStructuredGenerateResult(WireModel): model_config = ConfigDict( - extra="allow", + extra="forbid", validate_by_name=True, ) - __annotations__ = { - "__pydantic_extra__": Dict[str, Optional[FieldSchema5]], - } role: LLMRole content: Union[LLMMessageContentBlock, list[LLMMessageContentBlock]] stop_reason: Optional[StrictStr] = None usage: Optional[LLMUsage] = None output_format: Literal["json_schema"] - structured_content: Optional[FieldSchema6] + structured_content: Optional[FieldSchema5] class LLMGenerateResult( @@ -1290,6 +1292,7 @@ class MouseButton(StrEnum): class ObserveOptions(WireModel): model_config = ConfigDict( + extra="forbid", validate_by_name=True, ) model: Optional[ModelConfig] = None @@ -1334,6 +1337,7 @@ class ObserveOptions(WireModel): class ObserveResult(WireModel): model_config = ConfigDict( + extra="forbid", validate_by_name=True, ) result: list[Action] @@ -1468,7 +1472,7 @@ class PageEvaluateResult(WireModel): extra="forbid", validate_by_name=True, ) - value: Optional[FieldSchema7] + value: Optional[FieldSchema6] class PageGoBackParams(WireModel): @@ -1943,8 +1947,8 @@ class StagehandLog(WireModel): data: StagehandLogData -class StagehandLogData(RootModel[dict[StrictStr, Optional[FieldSchema8]]]): - root: dict[StrictStr, Optional[FieldSchema8]] +class StagehandLogData(RootModel[dict[StrictStr, Optional[FieldSchema7]]]): + root: dict[StrictStr, Optional[FieldSchema7]] class StagehandLogLevel(StrEnum): @@ -2055,7 +2059,6 @@ class Variables(RootModel[dict[StrictStr, VariableValue]]): FieldSchema0.model_rebuild() FieldSchema1.model_rebuild() -FieldSchema10.model_rebuild() FieldSchema2.model_rebuild() FieldSchema3.model_rebuild() FieldSchema4.model_rebuild() diff --git a/packages/sdk-ts/src/clientSchemas.ts b/packages/sdk-ts/src/clientSchemas.ts index 0eefc8a50..1ed8b0355 100644 --- a/packages/sdk-ts/src/clientSchemas.ts +++ b/packages/sdk-ts/src/clientSchemas.ts @@ -24,7 +24,7 @@ import { Page } from "./page.js"; const BrowserbaseClientBrowserSettingsSchema = BrowserbaseBrowserSettingsSchema.omit({ extensionId: true, -}).strict(); +}); /** Browserbase source fields exposed by the TS SDK. Stagehand provisions its own extension. */ export const BrowserbaseBrowserSourceSchema = BrowserbaseSessionCreateParamsSchema.omit({ @@ -35,22 +35,18 @@ export const BrowserbaseBrowserSourceSchema = BrowserbaseSessionCreateParamsSche type: z.literal("browserbase"), browserSettings: BrowserbaseClientBrowserSettingsSchema.optional(), }) - .strict() .meta({ id: "BrowserbaseClientBrowserSource" }); export const LocalBrowserSourceSchema = LocalBrowserLaunchOptionsSchema.extend({ type: z.literal("local"), -}) - .strict() - .meta({ id: "LocalBrowserSource" }); +}).meta({ id: "LocalBrowserSource" }); export const CdpBrowserSourceSchema = z - .object({ + .strictObject({ type: z.literal("cdp"), cdpUrl: z.string().min(1), headers: z.record(z.string(), z.string()).optional(), }) - .strict() .meta({ id: "CdpBrowserSource" }); export const BrowserSourceSchema = z @@ -63,13 +59,12 @@ export const BrowserSourceSchema = z /** An LLM callback implemented locally by the SDK consumer. It never crosses the wire. */ export const ClientLLMSchema = z - .object({ + .strictObject({ generate: z.function({ input: [LLMGenerateParamsSchema], output: z.promise(LLMGenerateResultSchema), }), }) - .strict() .meta({ id: "ClientLLM" }); export const StagehandClientLogLevelSchema = z @@ -95,26 +90,17 @@ export const StagehandClientLoggingConfigSchema = z }) .meta({ id: "StagehandClientLoggingConfig" }); -export const StagehandClientActOptionsSchema = ActOptionsSchema.unwrap() - .extend({ - page: z.instanceof(Page).optional(), - }) - .strict() - .meta({ id: "StagehandClientActOptions" }); +export const StagehandClientActOptionsSchema = ActOptionsSchema.extend({ + page: z.instanceof(Page).optional(), +}).meta({ id: "StagehandClientActOptions" }); -export const StagehandClientObserveOptionsSchema = ObserveOptionsSchema.unwrap() - .extend({ - page: z.instanceof(Page).optional(), - }) - .strict() - .meta({ id: "StagehandClientObserveOptions" }); +export const StagehandClientObserveOptionsSchema = ObserveOptionsSchema.extend({ + page: z.instanceof(Page).optional(), +}).meta({ id: "StagehandClientObserveOptions" }); -export const StagehandClientExtractOptionsSchema = ExtractOptionsSchema.unwrap() - .extend({ - page: z.instanceof(Page).optional(), - }) - .strict() - .meta({ id: "StagehandClientExtractOptions" }); +export const StagehandClientExtractOptionsSchema = ExtractOptionsSchema.extend({ + page: z.instanceof(Page).optional(), +}).meta({ id: "StagehandClientExtractOptions" }); export const StagehandClientInitParamsSchema = StagehandInitParamsSchema.extend({ browser: BrowserSourceSchema.default({ type: "browserbase" }), @@ -124,7 +110,6 @@ export const StagehandClientInitParamsSchema = StagehandInitParamsSchema.extend( format: "pretty", }), }) - .strict() .superRefine((params, context) => { if (params.browser.type === "browserbase" && params.apiKey === undefined) { context.addIssue({ diff --git a/packages/sdk-ts/tests/runtimeCompatibility.test.ts b/packages/sdk-ts/tests/runtimeCompatibility.test.ts index 5fe3b8fed..82172e277 100644 --- a/packages/sdk-ts/tests/runtimeCompatibility.test.ts +++ b/packages/sdk-ts/tests/runtimeCompatibility.test.ts @@ -9,8 +9,6 @@ const requirement: RuntimeRequirement = { maximumProtocolVersion: 6, }; const marker = (protocolVersion: number) => ({ - name: "stagehand", - version: "stagehand.v4", protocolVersion, serverInfo: { name: "stagehand", version: "4.0.0" }, }); @@ -72,12 +70,12 @@ describe("negotiateRuntimeCompatibility", () => { kind: "unknown", reason: "unreadable-marker", })); - it("accepts unknown marker keys", () => + it("reports unknown marker keys as unreadable", () => expect( negotiateRuntimeCompatibility(requirement, { ...marker(4), status: "ready" }), ).toMatchObject({ - kind: "compatible", - protocolVersion: 4, + kind: "unknown", + reason: "unreadable-marker", })); it("does not throw for an unreadable proxy", () => { const raw = new Proxy({}, { get: () => throwOnRead() }); @@ -88,7 +86,7 @@ describe("negotiateRuntimeCompatibility", () => { }); it("is deterministic and does not mutate inputs", () => { const required = { ...requirement }; - const reported = { ...marker(4), status: "ready" }; + const reported = marker(4); const before = structuredClone({ required, reported }); expect(negotiateRuntimeCompatibility(required, reported)).toEqual( negotiateRuntimeCompatibility(required, reported), diff --git a/packages/server/tests/runtime-descriptor.test.ts b/packages/server/tests/runtime-descriptor.test.ts index eacf7e310..bec01ddfd 100644 --- a/packages/server/tests/runtime-descriptor.test.ts +++ b/packages/server/tests/runtime-descriptor.test.ts @@ -32,7 +32,7 @@ describe("runtime descriptor", () => { expect(STAGEHAND_RUNTIME_VERSION).toBe(serverPackage.version); }); - it("preserves unknown descriptor fields", () => { + it("rejects unknown descriptor fields", () => { const descriptor = { protocolVersion: 1, serverInfo: { @@ -42,6 +42,6 @@ describe("runtime descriptor", () => { status: "ready", }; - expect(RuntimeDescriptorSchema.parse(descriptor)).toStrictEqual(descriptor); + expect(() => RuntimeDescriptorSchema.parse(descriptor)).toThrow(); }); }); diff --git a/rules/ast-grep/protocol-schema-strictness.test.ts b/rules/ast-grep/protocol-schema-strictness.test.ts new file mode 100644 index 000000000..d8abd2f5d --- /dev/null +++ b/rules/ast-grep/protocol-schema-strictness.test.ts @@ -0,0 +1,29 @@ +import { readFile } from "node:fs/promises"; +import { parse } from "@ast-grep/napi"; +import { describe, expect, it } from "vitest"; + +const protocolSchemasUrl = new URL("../../packages/protocol/schemas.ts", import.meta.url); + +describe("Protocol schema object strictness", () => { + it("uses z.strictObject for every object schema in schemas.ts", async () => { + const root = parse("typescript", await readFile(protocolSchemasUrl, "utf8")).root(); + + // Dynamic data must live in explicit z.json() or z.record() fields inside a strict object. + const forbiddenObjectApis = [ + ["z.object", "z.object($$$ARGS)"], + ["z.looseObject", "z.looseObject($$$ARGS)"], + [".strict()", "$SCHEMA.strict()"], + [".loose()", "$SCHEMA.loose()"], + [".passthrough()", "$SCHEMA.passthrough()"], + [".catchall()", "$SCHEMA.catchall($$$ARGS)"], + ] as const; + + const violations = forbiddenObjectApis.flatMap(([name, pattern]) => + root.findAll({ rule: { pattern } }).map(() => name), + ); + + expect(violations, "All object schemas in schemas.ts must use z.strictObject").toStrictEqual( + [], + ); + }); +});