@meetopenbot/stagehand 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +118 -121
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -1,134 +1,131 @@
1
- // index.ts
1
+ // src/index.ts
2
+ import { defineAgentPlugin } from "@meetopenbot/plugin-sdk";
3
+
4
+ // src/agent.ts
2
5
  import { Stagehand } from "@browserbasehq/stagehand";
6
+ import { textReply as textReply2 } from "@meetopenbot/plugin-sdk";
7
+
8
+ // src/config.ts
3
9
  import {
4
- agentOutput,
5
- definePlugin,
6
- shouldHandleInvoke,
7
- uiWidget
10
+ lookupSecret,
11
+ readConfigModel,
12
+ trimmedString
8
13
  } from "@meetopenbot/plugin-sdk";
9
- var PROVIDER_CONFIG = {
10
- openai: { label: "OpenAI", envVar: "OPENAI_API_KEY", defaultModel: "openai/gpt-4o-mini" },
11
- anthropic: { label: "Anthropic", envVar: "ANTHROPIC_API_KEY", defaultModel: "anthropic/claude-sonnet-4-5-20250929" },
12
- google: { label: "Google Generative AI", envVar: "GOOGLE_GENERATIVE_AI_API_KEY", defaultModel: "google/gemini-2.5-flash" }
13
- };
14
- var configSchema = {
14
+ var pluginConfigSchema = {
15
15
  type: "object",
16
16
  properties: {
17
- apiKey: { type: "string", format: "password" },
18
- provider: { type: "string", enum: ["openai", "anthropic", "google"] },
19
- model: { type: "string" },
20
- env: { type: "string", enum: ["LOCAL", "BROWSERBASE"] },
21
- browserbaseApiKey: { type: "string", format: "password" },
22
- browserbaseProjectId: { type: "string" },
23
- headless: { type: "boolean", default: true },
24
- maxSteps: { type: "integer", default: 25 },
25
- mode: { type: "string", enum: ["dom", "hybrid", "cua"] }
17
+ env: { type: "string", enum: ["LOCAL", "BROWSERBASE"], description: "Browser environment." }
26
18
  }
27
19
  };
28
- var plugin = definePlugin({
29
- name: "Stagehand",
30
- description: "Stagehand AI browser automation",
31
- configSchema,
32
- factory: (context) => (builder) => {
33
- const env = process.env ?? {};
34
- builder.on("agent:invoke", async function* (event) {
35
- if (!shouldHandleInvoke(event, context.agentId)) return;
36
- const content = event.data?.content;
37
- const threadId = event.meta?.threadId;
38
- if (!content) return;
39
- const config = context.config;
40
- const requestedEnv = config.env || (env.BROWSERBASE_API_KEY ? "BROWSERBASE" : "LOCAL");
41
- const provider = config.provider || "openai";
42
- const pConfig = PROVIDER_CONFIG[provider] || PROVIDER_CONFIG.openai;
43
- const apiKey = config.apiKey || env[pConfig.envVar];
44
- const model = config.model || pConfig.defaultModel;
45
- if (!apiKey && requestedEnv === "LOCAL") {
46
- yield uiWidget({
47
- agentId: context.agentId,
48
- threadId,
49
- widget: {
50
- widgetId: `stagehand_api_key_${Date.now()}`,
51
- kind: "form",
52
- title: "API Key Required",
53
- description: `Stagehand needs an API key for ${pConfig.label}.`,
54
- fields: [
55
- { id: "provider", label: "Provider", type: "select", options: Object.entries(PROVIDER_CONFIG).map(([k, v]) => ({ value: k, label: v.label })), defaultValue: provider, required: true },
56
- { id: "apiKey", label: "API Key", type: "text", required: true }
57
- ],
58
- submitLabel: "Save Key",
59
- metadata: { type: "api_key_request" }
60
- }
61
- });
62
- return;
63
- }
64
- let stagehand = null;
65
- try {
66
- yield agentOutput({ agentId: context.agentId, threadId, content: `Starting Stagehand (${requestedEnv.toLowerCase()})...` });
67
- stagehand = new Stagehand({
68
- env: requestedEnv,
69
- model: apiKey ? { modelName: model, apiKey } : model,
70
- apiKey: config.browserbaseApiKey ?? env.BROWSERBASE_API_KEY,
71
- projectId: config.browserbaseProjectId ?? env.BROWSERBASE_PROJECT_ID,
72
- localBrowserLaunchOptions: { headless: config.headless ?? true },
73
- experimental: true
74
- });
75
- await stagehand.init();
76
- const agent = stagehand.agent({
77
- systemPrompt: context.agentDetails?.instructions ?? void 0,
78
- mode: config.mode,
79
- stream: true
80
- });
81
- const streamResult = await agent.execute({
82
- instruction: content,
83
- maxSteps: config.maxSteps ?? 25
84
- });
85
- for await (const part of streamResult.fullStream) {
86
- if (part.type === "tool-call") {
87
- yield agentOutput({
88
- agentId: context.agentId,
89
- threadId,
90
- content: `
91
- > [Tool Call] ${part.toolName}: ${JSON.stringify(part.input, null, 2)}
92
- `
93
- });
94
- }
95
- if (part.type === "tool-result") {
96
- yield agentOutput({
97
- agentId: context.agentId,
98
- threadId,
99
- content: `
100
- > [Tool Result] ${part.toolName}: ${JSON.stringify(part.output, null, 2).slice(0, 800)}
101
- `
102
- });
103
- }
104
- }
105
- const result = await streamResult.result;
106
- if (result.message) yield agentOutput({ agentId: context.agentId, threadId, content: result.message });
107
- if (result.output && Object.keys(result.output).length > 0) {
108
- yield agentOutput({ agentId: context.agentId, threadId, content: "```json\n" + JSON.stringify(result.output, null, 2) + "\n```" });
109
- }
110
- } catch (error) {
111
- yield agentOutput({ agentId: context.agentId, threadId, content: `Error: ${error.message}` });
112
- } finally {
113
- if (stagehand) await stagehand.close().catch(() => {
114
- });
115
- }
20
+ function resolveStagehandConfig(config) {
21
+ const browserbaseApiKey = lookupSecret({ envKeys: ["BROWSERBASE_API_KEY"] });
22
+ const requestedEnv = trimmedString(config.env) || (browserbaseApiKey ? "BROWSERBASE" : "LOCAL");
23
+ return {
24
+ requestedEnv,
25
+ model: readConfigModel(config, "openai/gpt-4o-mini"),
26
+ apiKey: lookupSecret({ envKeys: ["OPENAI_API_KEY"] }),
27
+ browserbaseApiKey,
28
+ browserbaseProjectId: lookupSecret({ envKeys: ["BROWSERBASE_PROJECT_ID"] }),
29
+ headless: true,
30
+ maxSteps: 25
31
+ };
32
+ }
33
+
34
+ // src/stream.ts
35
+ import {
36
+ createTextReplyBuffer,
37
+ textReply
38
+ } from "@meetopenbot/plugin-sdk";
39
+ var classifyStagehandPart = (part) => {
40
+ if (part.type === "text-delta") {
41
+ const text = part.text ?? part.delta ?? "";
42
+ return text ? { type: "delta", text } : { type: "skip" };
43
+ }
44
+ if (part.type === "step-finish" || part.type === "finish" || part.type === "tool-call") {
45
+ return { type: "flush" };
46
+ }
47
+ return { type: "skip" };
48
+ };
49
+ async function* runStagehandStream(fullStream) {
50
+ const replies = createTextReplyBuffer();
51
+ for await (const part of fullStream) {
52
+ yield* replies.apply(classifyStagehandPart(part));
53
+ }
54
+ yield* replies.end();
55
+ }
56
+ function finalStagehandReply(args) {
57
+ const items = [];
58
+ if (!args.yielded && args.message?.trim()) items.push(textReply(args.message.trim()));
59
+ if (args.output && typeof args.output === "object" && Object.keys(args.output).length > 0) {
60
+ items.push(textReply(JSON.stringify(args.output, null, 2)));
61
+ }
62
+ return items;
63
+ }
64
+
65
+ // src/agent.ts
66
+ async function* runStagehandTurn({
67
+ prompt,
68
+ context
69
+ }) {
70
+ const config = resolveStagehandConfig(context.config);
71
+ if (config.requestedEnv === "BROWSERBASE" && (!config.browserbaseApiKey || !config.browserbaseProjectId)) {
72
+ yield textReply2(
73
+ "Stagehand Browserbase setup is incomplete. Set `BROWSERBASE_API_KEY` and `BROWSERBASE_PROJECT_ID` in workspace settings."
74
+ );
75
+ return;
76
+ }
77
+ if (config.requestedEnv === "LOCAL" && !config.apiKey) {
78
+ yield textReply2("Stagehand local setup is incomplete. Set `OPENAI_API_KEY` in workspace settings.");
79
+ return;
80
+ }
81
+ let stagehand = null;
82
+ try {
83
+ stagehand = new Stagehand({
84
+ env: config.requestedEnv,
85
+ model: config.apiKey ? { modelName: config.model, apiKey: config.apiKey } : config.model,
86
+ apiKey: config.browserbaseApiKey,
87
+ projectId: config.browserbaseProjectId,
88
+ localBrowserLaunchOptions: { headless: config.headless },
89
+ experimental: true
116
90
  });
117
- builder.on("client:ui:widget:response", async function* (event) {
118
- const { metadata, values, widgetId } = event.data ?? {};
119
- if (metadata?.type !== "api_key_request") return;
120
- const provider = values?.provider;
121
- const apiKey = values?.apiKey;
122
- const pConfig = PROVIDER_CONFIG[provider];
123
- if (pConfig && apiKey) {
124
- await context.storage.createVariable({ key: pConfig.envVar, value: apiKey, secret: true });
125
- env[pConfig.envVar] = apiKey;
126
- yield uiWidget({ agentId: context.agentId, widget: { widgetId, kind: "message", title: "Success", body: "API key saved. Please retry your request.", state: "submitted", actions: [{ id: "ok", label: "OK" }] } });
127
- }
91
+ await stagehand.init();
92
+ const agent = stagehand.agent({
93
+ systemPrompt: context.agentDetails?.instructions ?? void 0,
94
+ stream: true
95
+ });
96
+ const streamResult = await agent.execute({
97
+ instruction: prompt,
98
+ maxSteps: config.maxSteps
99
+ });
100
+ let yielded = false;
101
+ for await (const item of runStagehandStream(streamResult.fullStream)) {
102
+ yielded = true;
103
+ yield item;
104
+ }
105
+ const result = await streamResult.result;
106
+ for (const item of finalStagehandReply({
107
+ yielded,
108
+ message: result.message,
109
+ output: result.output
110
+ })) {
111
+ yield item;
112
+ }
113
+ } finally {
114
+ if (stagehand) await stagehand.close().catch(() => {
128
115
  });
129
116
  }
117
+ }
118
+
119
+ // src/index.ts
120
+ var plugin = await defineAgentPlugin({
121
+ name: "Stagehand",
122
+ description: "Stagehand AI browser automation",
123
+ models: { providers: ["openai"], default: "openai/gpt-4o-mini" },
124
+ configSchema: pluginConfigSchema,
125
+ run: runStagehandTurn
130
126
  });
131
- var plugin_stagehand_default = plugin;
127
+ var src_default = plugin;
132
128
  export {
133
- plugin_stagehand_default as default
129
+ src_default as default,
130
+ plugin
134
131
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meetopenbot/stagehand",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "type": "module",
5
5
  "description": "Stagehand AI browser automation plugin for OpenBot",
6
6
  "main": "./dist/index.js",
@@ -24,7 +24,7 @@
24
24
  "@browserbasehq/stagehand": "^3.3.0",
25
25
  "playwright": "^1.52.0",
26
26
  "zod": "^4.3.5",
27
- "@meetopenbot/plugin-sdk": "^0.2.0"
27
+ "@meetopenbot/plugin-sdk": "^0.4.0"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@types/node": "^20.10.1",
@@ -32,9 +32,9 @@
32
32
  },
33
33
  "types": "./dist/index.d.ts",
34
34
  "scripts": {
35
- "build": "esbuild index.ts --bundle --platform=node --target=node18 --format=esm --packages=external --outfile=dist/index.js && node ../../scripts/write-plugin-declaration.mjs",
35
+ "build": "esbuild src/index.ts --bundle --platform=node --target=node18 --format=esm --packages=external --outfile=dist/index.js && node ../../scripts/write-plugin-declaration.mjs",
36
36
  "setup:browser": "playwright install chromium",
37
- "dev": "esbuild index.ts --bundle --platform=node --target=node18 --format=esm --packages=external --outfile=dist/index.js --watch",
38
- "typecheck": "tsc --noEmit --allowImportingTsExtensions --module ESNext --moduleResolution Bundler --target ES2022 --skipLibCheck index.ts"
37
+ "dev": "esbuild src/index.ts --bundle --platform=node --target=node18 --format=esm --packages=external --outfile=dist/index.js --watch",
38
+ "typecheck": "tsc -p tsconfig.json --noEmit"
39
39
  }
40
40
  }