From 9e423e13ee43916ffe567f16fe267588e54a7511 Mon Sep 17 00:00:00 2001 From: Shrey Pandya Date: Tue, 4 Aug 2026 14:30:27 -0700 Subject: [PATCH] feat(vercel): add Stagehand code-mode binding --- .github/workflows/stagehand-codemode.yml | 21 +++++++++++++ .../vercel/stagehand-codemode/README.md | 10 +++++++ .../vercel/stagehand-codemode/package.json | 18 +++++++++++ .../vercel/stagehand-codemode/src/agent.ts | 24 +++++++++++++++ .../vercel/stagehand-codemode/src/smoke.ts | 28 +++++++++++++++++ .../vercel/stagehand-codemode/tsconfig.json | 11 +++++++ packages/stagehand-codemode/README.md | 21 +++++++++++++ packages/stagehand-codemode/package.json | 13 ++++++-- packages/stagehand-codemode/src/ai-sdk.ts | 30 +++++++++++++++++++ packages/stagehand-codemode/tsdown.config.ts | 2 +- pnpm-workspace.yaml | 1 + 11 files changed, 176 insertions(+), 3 deletions(-) create mode 100644 examples/integrations/vercel/stagehand-codemode/README.md create mode 100644 examples/integrations/vercel/stagehand-codemode/package.json create mode 100644 examples/integrations/vercel/stagehand-codemode/src/agent.ts create mode 100644 examples/integrations/vercel/stagehand-codemode/src/smoke.ts create mode 100644 examples/integrations/vercel/stagehand-codemode/tsconfig.json create mode 100644 packages/stagehand-codemode/src/ai-sdk.ts diff --git a/.github/workflows/stagehand-codemode.yml b/.github/workflows/stagehand-codemode.yml index 826e92e..550144e 100644 --- a/.github/workflows/stagehand-codemode.yml +++ b/.github/workflows/stagehand-codemode.yml @@ -4,12 +4,16 @@ on: pull_request: paths: - '.github/workflows/stagehand-codemode.yml' + - 'examples/integrations/vercel/stagehand-codemode/**' - 'packages/stagehand-codemode/**' + - 'pnpm-workspace.yaml' push: branches: [main] paths: - '.github/workflows/stagehand-codemode.yml' + - 'examples/integrations/vercel/stagehand-codemode/**' - 'packages/stagehand-codemode/**' + - 'pnpm-workspace.yaml' permissions: contents: read @@ -30,3 +34,20 @@ jobs: - run: pnpm install --no-frozen-lockfile - run: pnpm --filter @browserbasehq/stagehand-codemode run typecheck - run: pnpm --filter @browserbasehq/stagehand-codemode run build + + vercel: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: 10.9.0 + - uses: actions/setup-node@v4 + with: + node-version: 24.x + + - run: pnpm install --no-frozen-lockfile + - run: pnpm --filter @browserbasehq/stagehand-codemode run build + - run: pnpm --filter stagehand-codemode-ai-sdk-example run check + - run: pnpm --filter stagehand-codemode-ai-sdk-example run smoke diff --git a/examples/integrations/vercel/stagehand-codemode/README.md b/examples/integrations/vercel/stagehand-codemode/README.md new file mode 100644 index 0000000..e6609ff --- /dev/null +++ b/examples/integrations/vercel/stagehand-codemode/README.md @@ -0,0 +1,10 @@ +# Vercel AI SDK + Stagehand code mode + +This binding hosts `StagehandCodeExecutor` directly in the agent process and exposes it as one AI +SDK tool. It does not launch MCP because the AI SDK already has a native tool contract. + +The agent receives the shared Stagehand V4 syntax skill through `system`, while the tool description +contains the same reference for tool-level context. One binding instance must be reused for the +whole `generateText` call and closed afterward. + +See [`src/agent.ts`](./src/agent.ts). diff --git a/examples/integrations/vercel/stagehand-codemode/package.json b/examples/integrations/vercel/stagehand-codemode/package.json new file mode 100644 index 0000000..7bc10eb --- /dev/null +++ b/examples/integrations/vercel/stagehand-codemode/package.json @@ -0,0 +1,18 @@ +{ + "name": "stagehand-codemode-ai-sdk-example", + "private": true, + "type": "module", + "scripts": { + "check": "tsc --noEmit", + "smoke": "tsx src/smoke.ts" + }, + "dependencies": { + "@browserbasehq/stagehand-codemode": "workspace:*", + "ai": "^7.0.51" + }, + "devDependencies": { + "@types/node": "^25.0.9", + "tsx": "^4.20.6", + "typescript": "^5.9.3" + } +} diff --git a/examples/integrations/vercel/stagehand-codemode/src/agent.ts b/examples/integrations/vercel/stagehand-codemode/src/agent.ts new file mode 100644 index 0000000..60244ac --- /dev/null +++ b/examples/integrations/vercel/stagehand-codemode/src/agent.ts @@ -0,0 +1,24 @@ +import { generateText, stepCountIs, type LanguageModel } from 'ai'; +import { STAGEHAND_CODEMODE_SKILL } from '@browserbasehq/stagehand-codemode'; +import { createStagehandTool } from '@browserbasehq/stagehand-codemode/ai-sdk'; + +export async function runStagehandAgent( + model: LanguageModel, + prompt: string +): Promise { + const stagehand = createStagehandTool({ + browserbaseApiKey: process.env.BROWSERBASE_API_KEY, + }); + try { + const result = await generateText({ + model, + system: STAGEHAND_CODEMODE_SKILL, + prompt, + tools: { code_execute: stagehand.tool }, + stopWhen: stepCountIs(8), + }); + return result.text; + } finally { + await stagehand.close(); + } +} diff --git a/examples/integrations/vercel/stagehand-codemode/src/smoke.ts b/examples/integrations/vercel/stagehand-codemode/src/smoke.ts new file mode 100644 index 0000000..a01be95 --- /dev/null +++ b/examples/integrations/vercel/stagehand-codemode/src/smoke.ts @@ -0,0 +1,28 @@ +import { STAGEHAND_CODEMODE_SKILL } from '@browserbasehq/stagehand-codemode'; +import { createStagehandTool } from '@browserbasehq/stagehand-codemode/ai-sdk'; + +const stagehand = createStagehandTool({}); +try { + if (typeof stagehand.tool.execute !== 'function') { + throw new Error('AI SDK binding did not expose an executable tool.'); + } + const description = stagehand.tool.description; + if ( + typeof description !== 'string' || + !description.includes('Stagehand V4 code-mode syntax') + ) { + throw new Error( + 'AI SDK tool description did not include the Stagehand syntax skill.' + ); + } + if (!STAGEHAND_CODEMODE_SKILL.includes('stagehand.extract')) { + throw new Error( + 'AI SDK system instructions did not load the Stagehand syntax skill.' + ); + } + process.stdout.write( + 'Vercel AI SDK native tool binding PASS: code_execute\n' + ); +} finally { + await stagehand.close(); +} diff --git a/examples/integrations/vercel/stagehand-codemode/tsconfig.json b/examples/integrations/vercel/stagehand-codemode/tsconfig.json new file mode 100644 index 0000000..4f77ad9 --- /dev/null +++ b/examples/integrations/vercel/stagehand-codemode/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../../../tsconfig.json", + "compilerOptions": { + "module": "NodeNext", + "moduleResolution": "NodeNext", + "target": "ES2022", + "types": ["node"], + "noEmit": true + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/stagehand-codemode/README.md b/packages/stagehand-codemode/README.md index 09fa708..22e6feb 100644 --- a/packages/stagehand-codemode/README.md +++ b/packages/stagehand-codemode/README.md @@ -154,3 +154,24 @@ globals, and in-process SDK state available to that process. Use Stagehand Code Mode only with trusted agents in a trusted execution environment. Applications that execute untrusted code must provide a real isolation boundary, such as a restricted container, virtual machine, or purpose-built code sandbox. + +## Vercel AI SDK + +```ts +import { generateText } from 'ai'; +import { createStagehandTool } from '@browserbasehq/stagehand-codemode/ai-sdk'; + +const stagehand = createStagehandTool({ + browserbaseApiKey: process.env.BROWSERBASE_API_KEY, +}); + +try { + await generateText({ + model, + tools: { code_execute: stagehand.tool }, + prompt: 'Open example.com and return its title.', + }); +} finally { + await stagehand.close(); +} +``` diff --git a/packages/stagehand-codemode/package.json b/packages/stagehand-codemode/package.json index 6ec966c..508b3c0 100644 --- a/packages/stagehand-codemode/package.json +++ b/packages/stagehand-codemode/package.json @@ -2,12 +2,16 @@ "name": "@browserbasehq/stagehand-codemode", "version": "0.0.0", "private": true, - "description": "Local stdio MCP tool for Stagehand code mode", + "description": "Local stdio MCP and agent-tool bindings for Stagehand code mode", "type": "module", "exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" + }, + "./ai-sdk": { + "types": "./dist/ai-sdk.d.ts", + "import": "./dist/ai-sdk.js" } }, "scripts": { @@ -19,15 +23,20 @@ "zod": "^4.4.3" }, "peerDependencies": { - "@browserbasehq/stagehand": "*" + "@browserbasehq/stagehand": "*", + "ai": ">=5.0.0" }, "peerDependenciesMeta": { "@browserbasehq/stagehand": { "optional": true + }, + "ai": { + "optional": true } }, "devDependencies": { "@types/node": "^25.0.9", + "ai": "^7.0.51", "tsdown": "^0.15.4", "typescript": "^5.9.3" }, diff --git a/packages/stagehand-codemode/src/ai-sdk.ts b/packages/stagehand-codemode/src/ai-sdk.ts new file mode 100644 index 0000000..fd2f2f0 --- /dev/null +++ b/packages/stagehand-codemode/src/ai-sdk.ts @@ -0,0 +1,30 @@ +import { tool, type Tool } from 'ai'; +import { + StagehandCodeExecutor, + type StagehandCodeExecutorOptions, +} from './executor.js'; +import { + CODE_EXECUTE_DESCRIPTION, + codeExecuteSchema, +} from './tool-contract.js'; + +export type StagehandToolBinding = { + tool: Tool; + close(): Promise; +}; + +export function createStagehandTool( + options: StagehandCodeExecutorOptions +): StagehandToolBinding { + const executor = new StagehandCodeExecutor(options); + const stagehandTool = tool({ + description: CODE_EXECUTE_DESCRIPTION, + inputSchema: codeExecuteSchema, + execute: (input, execution) => + executor.execute(input, execution.abortSignal), + }); + return { + tool: stagehandTool, + close: () => executor.close(), + }; +} diff --git a/packages/stagehand-codemode/tsdown.config.ts b/packages/stagehand-codemode/tsdown.config.ts index 191135e..61f9712 100644 --- a/packages/stagehand-codemode/tsdown.config.ts +++ b/packages/stagehand-codemode/tsdown.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from 'tsdown'; export default defineConfig({ - entry: ['src/index.ts', 'src/stdio-server.ts'], + entry: ['src/index.ts', 'src/ai-sdk.ts', 'src/stdio-server.ts'], format: ['esm'], platform: 'node', target: 'node22', diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index c949490..3195168 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,4 @@ packages: - 'examples/*' + - 'examples/integrations/vercel/stagehand-codemode' - 'packages/*'