@browserbasehq/convex-stagehand 0.0.3 → 0.1.0
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.
- package/README.md +16 -17
- package/dist/client/index.d.ts +61 -4
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +89 -18
- package/dist/client/index.js.map +1 -1
- package/dist/component/_generated/component.d.ts +181 -10
- package/dist/component/_generated/component.d.ts.map +1 -1
- package/dist/component/_generated/component.js +1 -1
- package/dist/component/_generated/dataModel.d.ts +33 -2
- package/dist/component/_generated/dataModel.d.ts.map +1 -1
- package/dist/component/api.d.ts +78 -17
- package/dist/component/api.d.ts.map +1 -1
- package/dist/component/api.js +93 -33
- package/dist/component/api.js.map +1 -1
- package/dist/component/lib.d.ts +23 -5
- package/dist/component/lib.d.ts.map +1 -1
- package/dist/component/lib.js +489 -50
- package/dist/component/lib.js.map +1 -1
- package/dist/component/schema.d.ts +3 -1
- package/dist/component/schema.d.ts.map +1 -1
- package/dist/component/schema.js +1 -0
- package/dist/component/schema.js.map +1 -1
- package/dist/test.d.ts +3 -1
- package/dist/test.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client/index.ts +151 -21
- package/src/component/_generated/component.ts +213 -12
- package/src/component/_generated/dataModel.ts +45 -2
- package/src/component/api.ts +225 -68
- package/src/component/lib.ts +639 -57
- package/src/component/schema.ts +8 -0
package/src/component/lib.ts
CHANGED
|
@@ -5,15 +5,52 @@
|
|
|
5
5
|
* Supports both automatic session management and manual session control.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { action } from "./_generated/server.js";
|
|
8
|
+
import { action, internalMutation, internalQuery } from "./_generated/server.js";
|
|
9
|
+
import { internal } from "./_generated/api.js";
|
|
9
10
|
import { v } from "convex/values";
|
|
10
11
|
import * as api from "./api.js";
|
|
11
12
|
|
|
13
|
+
const DEFAULT_BROWSERBASE_REGION: api.BrowserbaseRegion = "us-west-2";
|
|
14
|
+
|
|
15
|
+
type SessionStatus = "active" | "completed" | "error";
|
|
16
|
+
type SessionOperation = "extract" | "act" | "observe" | "workflow";
|
|
17
|
+
|
|
18
|
+
type SessionMetadataPatch = {
|
|
19
|
+
sessionId: string;
|
|
20
|
+
region?: api.BrowserbaseRegion;
|
|
21
|
+
status?: SessionStatus;
|
|
22
|
+
operation?: SessionOperation;
|
|
23
|
+
url?: string;
|
|
24
|
+
endedAt?: number;
|
|
25
|
+
error?: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const browserbaseRegionValidator = v.union(
|
|
29
|
+
v.literal("us-west-2"),
|
|
30
|
+
v.literal("us-east-1"),
|
|
31
|
+
v.literal("eu-central-1"),
|
|
32
|
+
v.literal("ap-southeast-1"),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const sessionStatusValidator = v.union(
|
|
36
|
+
v.literal("active"),
|
|
37
|
+
v.literal("completed"),
|
|
38
|
+
v.literal("error"),
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const sessionOperationValidator = v.union(
|
|
42
|
+
v.literal("extract"),
|
|
43
|
+
v.literal("act"),
|
|
44
|
+
v.literal("observe"),
|
|
45
|
+
v.literal("workflow"),
|
|
46
|
+
);
|
|
47
|
+
|
|
12
48
|
const observedActionValidator = v.object({
|
|
13
49
|
description: v.string(),
|
|
14
50
|
selector: v.string(),
|
|
15
|
-
method: v.string(),
|
|
51
|
+
method: v.optional(v.string()),
|
|
16
52
|
arguments: v.optional(v.array(v.string())),
|
|
53
|
+
backendNodeId: v.optional(v.number()),
|
|
17
54
|
});
|
|
18
55
|
|
|
19
56
|
const waitUntilValidator = v.union(
|
|
@@ -22,13 +59,228 @@ const waitUntilValidator = v.union(
|
|
|
22
59
|
v.literal("networkidle"),
|
|
23
60
|
);
|
|
24
61
|
|
|
62
|
+
/** Session-level config forwarded from the client's StagehandConfig for ephemeral sessions. */
|
|
63
|
+
const sessionConfigValidator = v.optional(
|
|
64
|
+
v.object({
|
|
65
|
+
domSettleTimeoutMs: v.optional(v.number()),
|
|
66
|
+
selfHeal: v.optional(v.boolean()),
|
|
67
|
+
systemPrompt: v.optional(v.string()),
|
|
68
|
+
verbose: v.optional(v.number()),
|
|
69
|
+
experimental: v.optional(v.boolean()),
|
|
70
|
+
}),
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const modelValidator = v.optional(
|
|
74
|
+
v.union(
|
|
75
|
+
v.string(),
|
|
76
|
+
v.object({
|
|
77
|
+
modelName: v.optional(v.string()),
|
|
78
|
+
apiKey: v.optional(v.string()),
|
|
79
|
+
baseURL: v.optional(v.string()),
|
|
80
|
+
provider: v.optional(v.string()),
|
|
81
|
+
}),
|
|
82
|
+
),
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const variablesValidator = v.optional(v.record(v.string(), v.string()));
|
|
86
|
+
|
|
25
87
|
const agentActionValidator = v.object({
|
|
26
88
|
type: v.string(),
|
|
27
89
|
action: v.optional(v.string()),
|
|
28
90
|
reasoning: v.optional(v.string()),
|
|
29
91
|
timeMs: v.optional(v.number()),
|
|
92
|
+
taskCompleted: v.optional(v.boolean()),
|
|
93
|
+
pageText: v.optional(v.string()),
|
|
94
|
+
pageUrl: v.optional(v.string()),
|
|
95
|
+
instruction: v.optional(v.string()),
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
function isBrowserbaseRegion(value: unknown): value is api.BrowserbaseRegion {
|
|
99
|
+
return (
|
|
100
|
+
value === "us-west-2" ||
|
|
101
|
+
value === "us-east-1" ||
|
|
102
|
+
value === "eu-central-1" ||
|
|
103
|
+
value === "ap-southeast-1"
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function getRequestedRegion(
|
|
108
|
+
browserbaseSessionCreateParams: unknown,
|
|
109
|
+
): api.BrowserbaseRegion | undefined {
|
|
110
|
+
const maybeRegion = (
|
|
111
|
+
browserbaseSessionCreateParams as api.BrowserbaseSessionCreateParams | undefined
|
|
112
|
+
)?.region;
|
|
113
|
+
if (isBrowserbaseRegion(maybeRegion)) {
|
|
114
|
+
return maybeRegion;
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function extractRegionFromError(error: unknown): api.BrowserbaseRegion | undefined {
|
|
120
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
121
|
+
const match = message.match(/Session is in region '([^']+)'/i);
|
|
122
|
+
const parsedRegion = match?.[1];
|
|
123
|
+
if (isBrowserbaseRegion(parsedRegion)) {
|
|
124
|
+
return parsedRegion;
|
|
125
|
+
}
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export const upsertSessionMetadata = internalMutation({
|
|
130
|
+
args: {
|
|
131
|
+
sessionId: v.string(),
|
|
132
|
+
region: v.optional(browserbaseRegionValidator),
|
|
133
|
+
status: v.optional(sessionStatusValidator),
|
|
134
|
+
operation: v.optional(sessionOperationValidator),
|
|
135
|
+
url: v.optional(v.string()),
|
|
136
|
+
endedAt: v.optional(v.number()),
|
|
137
|
+
error: v.optional(v.string()),
|
|
138
|
+
},
|
|
139
|
+
returns: v.null(),
|
|
140
|
+
handler: async (ctx: any, args: any) => {
|
|
141
|
+
const existing = await ctx.db
|
|
142
|
+
.query("sessions")
|
|
143
|
+
.withIndex("by_sessionId", (q: any) => q.eq("sessionId", args.sessionId))
|
|
144
|
+
.first();
|
|
145
|
+
|
|
146
|
+
if (existing) {
|
|
147
|
+
const patch: Record<string, unknown> = {};
|
|
148
|
+
if (args.region !== undefined) patch.region = args.region;
|
|
149
|
+
if (args.status !== undefined) patch.status = args.status;
|
|
150
|
+
if (args.operation !== undefined) patch.operation = args.operation;
|
|
151
|
+
if (args.url !== undefined) patch.url = args.url;
|
|
152
|
+
if (args.endedAt !== undefined) patch.endedAt = args.endedAt;
|
|
153
|
+
if (args.error !== undefined) patch.error = args.error;
|
|
154
|
+
await ctx.db.patch(existing._id, patch);
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
await ctx.db.insert("sessions", {
|
|
159
|
+
sessionId: args.sessionId,
|
|
160
|
+
region: args.region,
|
|
161
|
+
startedAt: Date.now(),
|
|
162
|
+
endedAt: args.endedAt,
|
|
163
|
+
status: args.status ?? "active",
|
|
164
|
+
operation: args.operation ?? "workflow",
|
|
165
|
+
url: args.url ?? "",
|
|
166
|
+
error: args.error,
|
|
167
|
+
});
|
|
168
|
+
return null;
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
export const getSessionRegion = internalQuery({
|
|
173
|
+
args: {
|
|
174
|
+
sessionId: v.string(),
|
|
175
|
+
},
|
|
176
|
+
returns: v.union(browserbaseRegionValidator, v.null()),
|
|
177
|
+
handler: async (ctx: any, args: any) => {
|
|
178
|
+
const session = await ctx.db
|
|
179
|
+
.query("sessions")
|
|
180
|
+
.withIndex("by_sessionId", (q: any) => q.eq("sessionId", args.sessionId))
|
|
181
|
+
.first();
|
|
182
|
+
return session?.region ?? null;
|
|
183
|
+
},
|
|
30
184
|
});
|
|
31
185
|
|
|
186
|
+
async function resolveSessionRegion(
|
|
187
|
+
ctx: any,
|
|
188
|
+
sessionId: string,
|
|
189
|
+
fallback?: api.BrowserbaseRegion,
|
|
190
|
+
): Promise<api.BrowserbaseRegion | undefined> {
|
|
191
|
+
const storedRegion = await ctx.runQuery(internal.lib.getSessionRegion, {
|
|
192
|
+
sessionId,
|
|
193
|
+
});
|
|
194
|
+
return storedRegion ?? fallback ?? undefined;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function persistSessionMetadata(
|
|
198
|
+
ctx: any,
|
|
199
|
+
args: SessionMetadataPatch,
|
|
200
|
+
): Promise<void> {
|
|
201
|
+
await ctx.runMutation(internal.lib.upsertSessionMetadata, args);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function runWithRegionRetry<T>(
|
|
205
|
+
ctx: any,
|
|
206
|
+
args: {
|
|
207
|
+
sessionId: string;
|
|
208
|
+
initialRegion?: api.BrowserbaseRegion;
|
|
209
|
+
run: (region?: api.BrowserbaseRegion) => Promise<T>;
|
|
210
|
+
onRegionResolved?: (region: api.BrowserbaseRegion) => Promise<void>;
|
|
211
|
+
},
|
|
212
|
+
): Promise<T> {
|
|
213
|
+
try {
|
|
214
|
+
return await args.run(args.initialRegion);
|
|
215
|
+
} catch (error) {
|
|
216
|
+
const parsedRegion = extractRegionFromError(error);
|
|
217
|
+
if (!parsedRegion || parsedRegion === args.initialRegion) {
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
await persistSessionMetadata(ctx, {
|
|
222
|
+
sessionId: args.sessionId,
|
|
223
|
+
region: parsedRegion,
|
|
224
|
+
status: "active",
|
|
225
|
+
});
|
|
226
|
+
if (args.onRegionResolved) {
|
|
227
|
+
await args.onRegionResolved(parsedRegion);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return args.run(parsedRegion);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function endSessionWithRouting(
|
|
235
|
+
ctx: any,
|
|
236
|
+
args: {
|
|
237
|
+
sessionId: string;
|
|
238
|
+
config: api.ApiConfig;
|
|
239
|
+
fallbackRegion?: api.BrowserbaseRegion;
|
|
240
|
+
},
|
|
241
|
+
): Promise<boolean> {
|
|
242
|
+
try {
|
|
243
|
+
let resolvedRegion =
|
|
244
|
+
(await resolveSessionRegion(ctx, args.sessionId, args.fallbackRegion)) ??
|
|
245
|
+
DEFAULT_BROWSERBASE_REGION;
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
await runWithRegionRetry(ctx, {
|
|
249
|
+
sessionId: args.sessionId,
|
|
250
|
+
initialRegion: resolvedRegion,
|
|
251
|
+
onRegionResolved: async (region) => {
|
|
252
|
+
resolvedRegion = region;
|
|
253
|
+
},
|
|
254
|
+
run: async (region) => {
|
|
255
|
+
await api.endSession(args.sessionId, args.config, region);
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
await persistSessionMetadata(ctx, {
|
|
260
|
+
sessionId: args.sessionId,
|
|
261
|
+
region: resolvedRegion,
|
|
262
|
+
status: "completed",
|
|
263
|
+
endedAt: Date.now(),
|
|
264
|
+
});
|
|
265
|
+
return true;
|
|
266
|
+
} catch {
|
|
267
|
+
try {
|
|
268
|
+
await persistSessionMetadata(ctx, {
|
|
269
|
+
sessionId: args.sessionId,
|
|
270
|
+
region: resolvedRegion,
|
|
271
|
+
status: "error",
|
|
272
|
+
error: "Failed to end Stagehand session",
|
|
273
|
+
});
|
|
274
|
+
} catch {
|
|
275
|
+
// Best-effort metadata persistence — don't mask the original failure.
|
|
276
|
+
}
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
} catch {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
32
284
|
/**
|
|
33
285
|
* Start a new browser session.
|
|
34
286
|
* Returns session info including cdpUrl for direct Playwright/Puppeteer connection.
|
|
@@ -40,8 +292,9 @@ export const startSession = action({
|
|
|
40
292
|
modelApiKey: v.string(),
|
|
41
293
|
modelName: v.optional(v.string()),
|
|
42
294
|
url: v.string(),
|
|
43
|
-
|
|
295
|
+
browserbaseSessionID: v.optional(v.string()),
|
|
44
296
|
browserbaseSessionCreateParams: v.optional(v.any()),
|
|
297
|
+
model: modelValidator,
|
|
45
298
|
options: v.optional(
|
|
46
299
|
v.object({
|
|
47
300
|
timeout: v.optional(v.number()),
|
|
@@ -49,15 +302,20 @@ export const startSession = action({
|
|
|
49
302
|
domSettleTimeoutMs: v.optional(v.number()),
|
|
50
303
|
selfHeal: v.optional(v.boolean()),
|
|
51
304
|
systemPrompt: v.optional(v.string()),
|
|
305
|
+
verbose: v.optional(v.number()),
|
|
306
|
+
experimental: v.optional(v.boolean()),
|
|
52
307
|
}),
|
|
53
308
|
),
|
|
54
309
|
},
|
|
55
310
|
returns: v.object({
|
|
56
311
|
sessionId: v.string(),
|
|
57
|
-
browserbaseSessionId: v.optional(v.string()),
|
|
58
312
|
cdpUrl: v.optional(v.string()),
|
|
59
313
|
}),
|
|
60
|
-
handler: async (
|
|
314
|
+
handler: async (ctx: any, args: any) => {
|
|
315
|
+
let resolvedRegion =
|
|
316
|
+
getRequestedRegion(args.browserbaseSessionCreateParams) ??
|
|
317
|
+
DEFAULT_BROWSERBASE_REGION;
|
|
318
|
+
|
|
61
319
|
const config: api.ApiConfig = {
|
|
62
320
|
browserbaseApiKey: args.browserbaseApiKey,
|
|
63
321
|
browserbaseProjectId: args.browserbaseProjectId,
|
|
@@ -66,26 +324,54 @@ export const startSession = action({
|
|
|
66
324
|
};
|
|
67
325
|
|
|
68
326
|
const session = await api.startSession(config, {
|
|
69
|
-
|
|
327
|
+
browserbaseSessionID: args.browserbaseSessionID,
|
|
70
328
|
browserbaseSessionCreateParams: args.browserbaseSessionCreateParams,
|
|
329
|
+
model: args.model,
|
|
71
330
|
domSettleTimeoutMs: args.options?.domSettleTimeoutMs,
|
|
72
331
|
selfHeal: args.options?.selfHeal,
|
|
73
332
|
systemPrompt: args.options?.systemPrompt,
|
|
333
|
+
verbose: args.options?.verbose,
|
|
334
|
+
experimental: args.options?.experimental,
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
await persistSessionMetadata(ctx, {
|
|
338
|
+
sessionId: session.sessionId,
|
|
339
|
+
region: resolvedRegion,
|
|
340
|
+
status: "active",
|
|
341
|
+
operation: "workflow",
|
|
342
|
+
url: args.url,
|
|
74
343
|
});
|
|
75
344
|
|
|
76
345
|
try {
|
|
77
|
-
await
|
|
78
|
-
|
|
79
|
-
|
|
346
|
+
await runWithRegionRetry(ctx, {
|
|
347
|
+
sessionId: session.sessionId,
|
|
348
|
+
initialRegion: resolvedRegion,
|
|
349
|
+
onRegionResolved: async (region) => {
|
|
350
|
+
resolvedRegion = region;
|
|
351
|
+
},
|
|
352
|
+
run: async (region) =>
|
|
353
|
+
api.navigate(
|
|
354
|
+
session.sessionId,
|
|
355
|
+
args.url,
|
|
356
|
+
config,
|
|
357
|
+
{
|
|
358
|
+
waitUntil: args.options?.waitUntil,
|
|
359
|
+
timeout: args.options?.timeout,
|
|
360
|
+
},
|
|
361
|
+
region,
|
|
362
|
+
),
|
|
80
363
|
});
|
|
81
364
|
|
|
82
365
|
return {
|
|
83
366
|
sessionId: session.sessionId,
|
|
84
|
-
|
|
85
|
-
cdpUrl: session.cdpUrl,
|
|
367
|
+
cdpUrl: session.cdpUrl ?? undefined,
|
|
86
368
|
};
|
|
87
369
|
} catch (error) {
|
|
88
|
-
await
|
|
370
|
+
await endSessionWithRouting(ctx, {
|
|
371
|
+
sessionId: session.sessionId,
|
|
372
|
+
config,
|
|
373
|
+
fallbackRegion: resolvedRegion,
|
|
374
|
+
});
|
|
89
375
|
throw error;
|
|
90
376
|
}
|
|
91
377
|
},
|
|
@@ -99,18 +385,23 @@ export const endSession = action({
|
|
|
99
385
|
browserbaseApiKey: v.string(),
|
|
100
386
|
browserbaseProjectId: v.string(),
|
|
101
387
|
modelApiKey: v.string(),
|
|
388
|
+
modelName: v.optional(v.string()),
|
|
102
389
|
sessionId: v.string(),
|
|
103
390
|
},
|
|
104
391
|
returns: v.object({ success: v.boolean() }),
|
|
105
|
-
handler: async (
|
|
392
|
+
handler: async (ctx: any, args: any) => {
|
|
106
393
|
const config: api.ApiConfig = {
|
|
107
394
|
browserbaseApiKey: args.browserbaseApiKey,
|
|
108
395
|
browserbaseProjectId: args.browserbaseProjectId,
|
|
109
396
|
modelApiKey: args.modelApiKey,
|
|
397
|
+
modelName: args.modelName,
|
|
110
398
|
};
|
|
111
399
|
|
|
112
|
-
await
|
|
113
|
-
|
|
400
|
+
const success = await endSessionWithRouting(ctx, {
|
|
401
|
+
sessionId: args.sessionId,
|
|
402
|
+
config,
|
|
403
|
+
});
|
|
404
|
+
return { success };
|
|
114
405
|
},
|
|
115
406
|
});
|
|
116
407
|
|
|
@@ -130,15 +421,18 @@ export const extract = action({
|
|
|
130
421
|
instruction: v.string(),
|
|
131
422
|
schema: v.any(),
|
|
132
423
|
browserbaseSessionCreateParams: v.optional(v.any()),
|
|
424
|
+
model: modelValidator,
|
|
425
|
+
sessionConfig: sessionConfigValidator,
|
|
133
426
|
options: v.optional(
|
|
134
427
|
v.object({
|
|
135
428
|
timeout: v.optional(v.number()),
|
|
136
429
|
waitUntil: v.optional(waitUntilValidator),
|
|
430
|
+
selector: v.optional(v.string()),
|
|
137
431
|
}),
|
|
138
432
|
),
|
|
139
433
|
},
|
|
140
434
|
returns: v.any(),
|
|
141
|
-
handler: async (
|
|
435
|
+
handler: async (ctx: any, args: any) => {
|
|
142
436
|
if (!args.sessionId && !args.url) {
|
|
143
437
|
throw new Error("Either sessionId or url must be provided");
|
|
144
438
|
}
|
|
@@ -152,37 +446,94 @@ export const extract = action({
|
|
|
152
446
|
|
|
153
447
|
const ownSession = !args.sessionId;
|
|
154
448
|
let sessionId = args.sessionId;
|
|
449
|
+
let resolvedRegion: api.BrowserbaseRegion | undefined;
|
|
155
450
|
|
|
156
451
|
if (ownSession) {
|
|
452
|
+
resolvedRegion =
|
|
453
|
+
getRequestedRegion(args.browserbaseSessionCreateParams) ??
|
|
454
|
+
DEFAULT_BROWSERBASE_REGION;
|
|
157
455
|
const session = await api.startSession(config, {
|
|
158
456
|
browserbaseSessionCreateParams: args.browserbaseSessionCreateParams,
|
|
457
|
+
model: args.model,
|
|
458
|
+
...args.sessionConfig,
|
|
159
459
|
});
|
|
160
460
|
sessionId = session.sessionId;
|
|
461
|
+
await persistSessionMetadata(ctx, {
|
|
462
|
+
sessionId,
|
|
463
|
+
region: resolvedRegion,
|
|
464
|
+
status: "active",
|
|
465
|
+
operation: "extract",
|
|
466
|
+
url: args.url,
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (!sessionId) {
|
|
471
|
+
throw new Error("Failed to initialize session");
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (!ownSession) {
|
|
475
|
+
resolvedRegion = await resolveSessionRegion(ctx, sessionId, resolvedRegion);
|
|
161
476
|
}
|
|
162
477
|
|
|
163
478
|
try {
|
|
164
479
|
if (ownSession && args.url) {
|
|
165
|
-
await
|
|
166
|
-
|
|
167
|
-
|
|
480
|
+
await runWithRegionRetry(ctx, {
|
|
481
|
+
sessionId,
|
|
482
|
+
initialRegion: resolvedRegion,
|
|
483
|
+
onRegionResolved: async (region) => {
|
|
484
|
+
resolvedRegion = region;
|
|
485
|
+
},
|
|
486
|
+
run: async (region) =>
|
|
487
|
+
api.navigate(
|
|
488
|
+
sessionId,
|
|
489
|
+
args.url,
|
|
490
|
+
config,
|
|
491
|
+
{
|
|
492
|
+
waitUntil: args.options?.waitUntil,
|
|
493
|
+
timeout: args.options?.timeout,
|
|
494
|
+
},
|
|
495
|
+
region,
|
|
496
|
+
),
|
|
168
497
|
});
|
|
169
498
|
}
|
|
170
499
|
|
|
171
|
-
const result = await
|
|
500
|
+
const result = await runWithRegionRetry(ctx, {
|
|
172
501
|
sessionId,
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
502
|
+
initialRegion: resolvedRegion,
|
|
503
|
+
onRegionResolved: async (region) => {
|
|
504
|
+
resolvedRegion = region;
|
|
505
|
+
},
|
|
506
|
+
run: async (region) =>
|
|
507
|
+
api.extract(
|
|
508
|
+
sessionId,
|
|
509
|
+
args.instruction,
|
|
510
|
+
args.schema,
|
|
511
|
+
config,
|
|
512
|
+
{
|
|
513
|
+
model: args.model,
|
|
514
|
+
timeout: args.options?.timeout,
|
|
515
|
+
selector: args.options?.selector,
|
|
516
|
+
},
|
|
517
|
+
region,
|
|
518
|
+
),
|
|
519
|
+
});
|
|
177
520
|
|
|
178
521
|
if (ownSession) {
|
|
179
|
-
await
|
|
522
|
+
await endSessionWithRouting(ctx, {
|
|
523
|
+
sessionId,
|
|
524
|
+
config,
|
|
525
|
+
fallbackRegion: resolvedRegion,
|
|
526
|
+
});
|
|
180
527
|
}
|
|
181
528
|
|
|
182
529
|
return result.result;
|
|
183
530
|
} catch (error) {
|
|
184
531
|
if (ownSession) {
|
|
185
|
-
await
|
|
532
|
+
await endSessionWithRouting(ctx, {
|
|
533
|
+
sessionId,
|
|
534
|
+
config,
|
|
535
|
+
fallbackRegion: resolvedRegion,
|
|
536
|
+
});
|
|
186
537
|
}
|
|
187
538
|
throw error;
|
|
188
539
|
}
|
|
@@ -204,10 +555,13 @@ export const act = action({
|
|
|
204
555
|
url: v.optional(v.string()),
|
|
205
556
|
action: v.string(),
|
|
206
557
|
browserbaseSessionCreateParams: v.optional(v.any()),
|
|
558
|
+
model: modelValidator,
|
|
559
|
+
sessionConfig: sessionConfigValidator,
|
|
207
560
|
options: v.optional(
|
|
208
561
|
v.object({
|
|
209
562
|
timeout: v.optional(v.number()),
|
|
210
563
|
waitUntil: v.optional(waitUntilValidator),
|
|
564
|
+
variables: variablesValidator,
|
|
211
565
|
}),
|
|
212
566
|
),
|
|
213
567
|
},
|
|
@@ -216,7 +570,7 @@ export const act = action({
|
|
|
216
570
|
message: v.string(),
|
|
217
571
|
actionDescription: v.string(),
|
|
218
572
|
}),
|
|
219
|
-
handler: async (
|
|
573
|
+
handler: async (ctx: any, args: any) => {
|
|
220
574
|
if (!args.sessionId && !args.url) {
|
|
221
575
|
throw new Error("Either sessionId or url must be provided");
|
|
222
576
|
}
|
|
@@ -230,26 +584,83 @@ export const act = action({
|
|
|
230
584
|
|
|
231
585
|
const ownSession = !args.sessionId;
|
|
232
586
|
let sessionId = args.sessionId;
|
|
587
|
+
let resolvedRegion: api.BrowserbaseRegion | undefined;
|
|
233
588
|
|
|
234
589
|
if (ownSession) {
|
|
590
|
+
resolvedRegion =
|
|
591
|
+
getRequestedRegion(args.browserbaseSessionCreateParams) ??
|
|
592
|
+
DEFAULT_BROWSERBASE_REGION;
|
|
235
593
|
const session = await api.startSession(config, {
|
|
236
594
|
browserbaseSessionCreateParams: args.browserbaseSessionCreateParams,
|
|
595
|
+
model: args.model,
|
|
596
|
+
...args.sessionConfig,
|
|
237
597
|
});
|
|
238
598
|
sessionId = session.sessionId;
|
|
599
|
+
await persistSessionMetadata(ctx, {
|
|
600
|
+
sessionId,
|
|
601
|
+
region: resolvedRegion,
|
|
602
|
+
status: "active",
|
|
603
|
+
operation: "act",
|
|
604
|
+
url: args.url,
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
if (!sessionId) {
|
|
609
|
+
throw new Error("Failed to initialize session");
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
if (!ownSession) {
|
|
613
|
+
resolvedRegion = await resolveSessionRegion(ctx, sessionId, resolvedRegion);
|
|
239
614
|
}
|
|
240
615
|
|
|
241
616
|
try {
|
|
242
617
|
if (ownSession && args.url) {
|
|
243
|
-
await
|
|
244
|
-
|
|
245
|
-
|
|
618
|
+
await runWithRegionRetry(ctx, {
|
|
619
|
+
sessionId,
|
|
620
|
+
initialRegion: resolvedRegion,
|
|
621
|
+
onRegionResolved: async (region) => {
|
|
622
|
+
resolvedRegion = region;
|
|
623
|
+
},
|
|
624
|
+
run: async (region) =>
|
|
625
|
+
api.navigate(
|
|
626
|
+
sessionId,
|
|
627
|
+
args.url,
|
|
628
|
+
config,
|
|
629
|
+
{
|
|
630
|
+
waitUntil: args.options?.waitUntil,
|
|
631
|
+
timeout: args.options?.timeout,
|
|
632
|
+
},
|
|
633
|
+
region,
|
|
634
|
+
),
|
|
246
635
|
});
|
|
247
636
|
}
|
|
248
637
|
|
|
249
|
-
const result = await
|
|
638
|
+
const result = await runWithRegionRetry(ctx, {
|
|
639
|
+
sessionId,
|
|
640
|
+
initialRegion: resolvedRegion,
|
|
641
|
+
onRegionResolved: async (region) => {
|
|
642
|
+
resolvedRegion = region;
|
|
643
|
+
},
|
|
644
|
+
run: async (region) =>
|
|
645
|
+
api.act(
|
|
646
|
+
sessionId,
|
|
647
|
+
args.action,
|
|
648
|
+
config,
|
|
649
|
+
{
|
|
650
|
+
model: args.model,
|
|
651
|
+
variables: args.options?.variables,
|
|
652
|
+
timeout: args.options?.timeout,
|
|
653
|
+
},
|
|
654
|
+
region,
|
|
655
|
+
),
|
|
656
|
+
});
|
|
250
657
|
|
|
251
658
|
if (ownSession) {
|
|
252
|
-
await
|
|
659
|
+
await endSessionWithRouting(ctx, {
|
|
660
|
+
sessionId,
|
|
661
|
+
config,
|
|
662
|
+
fallbackRegion: resolvedRegion,
|
|
663
|
+
});
|
|
253
664
|
}
|
|
254
665
|
|
|
255
666
|
return {
|
|
@@ -259,7 +670,11 @@ export const act = action({
|
|
|
259
670
|
};
|
|
260
671
|
} catch (error) {
|
|
261
672
|
if (ownSession) {
|
|
262
|
-
await
|
|
673
|
+
await endSessionWithRouting(ctx, {
|
|
674
|
+
sessionId,
|
|
675
|
+
config,
|
|
676
|
+
fallbackRegion: resolvedRegion,
|
|
677
|
+
});
|
|
263
678
|
}
|
|
264
679
|
throw error;
|
|
265
680
|
}
|
|
@@ -281,15 +696,18 @@ export const observe = action({
|
|
|
281
696
|
url: v.optional(v.string()),
|
|
282
697
|
instruction: v.string(),
|
|
283
698
|
browserbaseSessionCreateParams: v.optional(v.any()),
|
|
699
|
+
model: modelValidator,
|
|
700
|
+
sessionConfig: sessionConfigValidator,
|
|
284
701
|
options: v.optional(
|
|
285
702
|
v.object({
|
|
286
703
|
timeout: v.optional(v.number()),
|
|
287
704
|
waitUntil: v.optional(waitUntilValidator),
|
|
705
|
+
selector: v.optional(v.string()),
|
|
288
706
|
}),
|
|
289
707
|
),
|
|
290
708
|
},
|
|
291
709
|
returns: v.array(observedActionValidator),
|
|
292
|
-
handler: async (
|
|
710
|
+
handler: async (ctx: any, args: any) => {
|
|
293
711
|
if (!args.sessionId && !args.url) {
|
|
294
712
|
throw new Error("Either sessionId or url must be provided");
|
|
295
713
|
}
|
|
@@ -303,26 +721,83 @@ export const observe = action({
|
|
|
303
721
|
|
|
304
722
|
const ownSession = !args.sessionId;
|
|
305
723
|
let sessionId = args.sessionId;
|
|
724
|
+
let resolvedRegion: api.BrowserbaseRegion | undefined;
|
|
306
725
|
|
|
307
726
|
if (ownSession) {
|
|
727
|
+
resolvedRegion =
|
|
728
|
+
getRequestedRegion(args.browserbaseSessionCreateParams) ??
|
|
729
|
+
DEFAULT_BROWSERBASE_REGION;
|
|
308
730
|
const session = await api.startSession(config, {
|
|
309
731
|
browserbaseSessionCreateParams: args.browserbaseSessionCreateParams,
|
|
732
|
+
model: args.model,
|
|
733
|
+
...args.sessionConfig,
|
|
310
734
|
});
|
|
311
735
|
sessionId = session.sessionId;
|
|
736
|
+
await persistSessionMetadata(ctx, {
|
|
737
|
+
sessionId,
|
|
738
|
+
region: resolvedRegion,
|
|
739
|
+
status: "active",
|
|
740
|
+
operation: "observe",
|
|
741
|
+
url: args.url,
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
if (!sessionId) {
|
|
746
|
+
throw new Error("Failed to initialize session");
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
if (!ownSession) {
|
|
750
|
+
resolvedRegion = await resolveSessionRegion(ctx, sessionId, resolvedRegion);
|
|
312
751
|
}
|
|
313
752
|
|
|
314
753
|
try {
|
|
315
754
|
if (ownSession && args.url) {
|
|
316
|
-
await
|
|
317
|
-
|
|
318
|
-
|
|
755
|
+
await runWithRegionRetry(ctx, {
|
|
756
|
+
sessionId,
|
|
757
|
+
initialRegion: resolvedRegion,
|
|
758
|
+
onRegionResolved: async (region) => {
|
|
759
|
+
resolvedRegion = region;
|
|
760
|
+
},
|
|
761
|
+
run: async (region) =>
|
|
762
|
+
api.navigate(
|
|
763
|
+
sessionId,
|
|
764
|
+
args.url,
|
|
765
|
+
config,
|
|
766
|
+
{
|
|
767
|
+
waitUntil: args.options?.waitUntil,
|
|
768
|
+
timeout: args.options?.timeout,
|
|
769
|
+
},
|
|
770
|
+
region,
|
|
771
|
+
),
|
|
319
772
|
});
|
|
320
773
|
}
|
|
321
774
|
|
|
322
|
-
const result = await
|
|
775
|
+
const result = await runWithRegionRetry(ctx, {
|
|
776
|
+
sessionId,
|
|
777
|
+
initialRegion: resolvedRegion,
|
|
778
|
+
onRegionResolved: async (region) => {
|
|
779
|
+
resolvedRegion = region;
|
|
780
|
+
},
|
|
781
|
+
run: async (region) =>
|
|
782
|
+
api.observe(
|
|
783
|
+
sessionId,
|
|
784
|
+
args.instruction,
|
|
785
|
+
config,
|
|
786
|
+
{
|
|
787
|
+
model: args.model,
|
|
788
|
+
timeout: args.options?.timeout,
|
|
789
|
+
selector: args.options?.selector,
|
|
790
|
+
},
|
|
791
|
+
region,
|
|
792
|
+
),
|
|
793
|
+
});
|
|
323
794
|
|
|
324
795
|
if (ownSession) {
|
|
325
|
-
await
|
|
796
|
+
await endSessionWithRouting(ctx, {
|
|
797
|
+
sessionId,
|
|
798
|
+
config,
|
|
799
|
+
fallbackRegion: resolvedRegion,
|
|
800
|
+
});
|
|
326
801
|
}
|
|
327
802
|
|
|
328
803
|
return result.result.map((action) => ({
|
|
@@ -330,10 +805,15 @@ export const observe = action({
|
|
|
330
805
|
selector: action.selector,
|
|
331
806
|
method: action.method,
|
|
332
807
|
arguments: action.arguments,
|
|
808
|
+
backendNodeId: action.backendNodeId,
|
|
333
809
|
}));
|
|
334
810
|
} catch (error) {
|
|
335
811
|
if (ownSession) {
|
|
336
|
-
await
|
|
812
|
+
await endSessionWithRouting(ctx, {
|
|
813
|
+
sessionId,
|
|
814
|
+
config,
|
|
815
|
+
fallbackRegion: resolvedRegion,
|
|
816
|
+
});
|
|
337
817
|
}
|
|
338
818
|
throw error;
|
|
339
819
|
}
|
|
@@ -356,13 +836,20 @@ export const agent = action({
|
|
|
356
836
|
url: v.optional(v.string()),
|
|
357
837
|
instruction: v.string(),
|
|
358
838
|
browserbaseSessionCreateParams: v.optional(v.any()),
|
|
839
|
+
model: modelValidator,
|
|
840
|
+
sessionConfig: sessionConfigValidator,
|
|
359
841
|
options: v.optional(
|
|
360
842
|
v.object({
|
|
361
843
|
cua: v.optional(v.boolean()),
|
|
844
|
+
mode: v.optional(v.string()),
|
|
362
845
|
maxSteps: v.optional(v.number()),
|
|
363
846
|
systemPrompt: v.optional(v.string()),
|
|
364
847
|
timeout: v.optional(v.number()),
|
|
365
848
|
waitUntil: v.optional(waitUntilValidator),
|
|
849
|
+
executionModel: modelValidator,
|
|
850
|
+
provider: v.optional(v.string()),
|
|
851
|
+
highlightCursor: v.optional(v.boolean()),
|
|
852
|
+
shouldCache: v.optional(v.boolean()),
|
|
366
853
|
}),
|
|
367
854
|
),
|
|
368
855
|
},
|
|
@@ -371,8 +858,18 @@ export const agent = action({
|
|
|
371
858
|
completed: v.boolean(),
|
|
372
859
|
message: v.string(),
|
|
373
860
|
success: v.boolean(),
|
|
861
|
+
metadata: v.optional(v.any()),
|
|
862
|
+
usage: v.optional(
|
|
863
|
+
v.object({
|
|
864
|
+
input_tokens: v.number(),
|
|
865
|
+
output_tokens: v.number(),
|
|
866
|
+
reasoning_tokens: v.optional(v.number()),
|
|
867
|
+
cached_input_tokens: v.optional(v.number()),
|
|
868
|
+
inference_time_ms: v.number(),
|
|
869
|
+
}),
|
|
870
|
+
),
|
|
374
871
|
}),
|
|
375
|
-
handler: async (
|
|
872
|
+
handler: async (ctx: any, args: any) => {
|
|
376
873
|
if (!args.sessionId && !args.url) {
|
|
377
874
|
throw new Error("Either sessionId or url must be provided");
|
|
378
875
|
}
|
|
@@ -386,43 +883,128 @@ export const agent = action({
|
|
|
386
883
|
|
|
387
884
|
const ownSession = !args.sessionId;
|
|
388
885
|
let sessionId = args.sessionId;
|
|
886
|
+
let resolvedRegion: api.BrowserbaseRegion | undefined;
|
|
389
887
|
|
|
390
888
|
if (ownSession) {
|
|
889
|
+
resolvedRegion =
|
|
890
|
+
getRequestedRegion(args.browserbaseSessionCreateParams) ??
|
|
891
|
+
DEFAULT_BROWSERBASE_REGION;
|
|
391
892
|
const session = await api.startSession(config, {
|
|
392
893
|
browserbaseSessionCreateParams: args.browserbaseSessionCreateParams,
|
|
894
|
+
model: args.model,
|
|
895
|
+
...args.sessionConfig,
|
|
393
896
|
});
|
|
394
897
|
sessionId = session.sessionId;
|
|
898
|
+
await persistSessionMetadata(ctx, {
|
|
899
|
+
sessionId,
|
|
900
|
+
region: resolvedRegion,
|
|
901
|
+
status: "active",
|
|
902
|
+
operation: "workflow",
|
|
903
|
+
url: args.url,
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
if (!sessionId) {
|
|
908
|
+
throw new Error("Failed to initialize session");
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
if (!ownSession) {
|
|
912
|
+
resolvedRegion = await resolveSessionRegion(ctx, sessionId, resolvedRegion);
|
|
395
913
|
}
|
|
396
914
|
|
|
397
915
|
try {
|
|
398
916
|
if (ownSession && args.url) {
|
|
399
|
-
await
|
|
400
|
-
|
|
401
|
-
|
|
917
|
+
await runWithRegionRetry(ctx, {
|
|
918
|
+
sessionId,
|
|
919
|
+
initialRegion: resolvedRegion,
|
|
920
|
+
onRegionResolved: async (region) => {
|
|
921
|
+
resolvedRegion = region;
|
|
922
|
+
},
|
|
923
|
+
run: async (region) =>
|
|
924
|
+
api.navigate(
|
|
925
|
+
sessionId,
|
|
926
|
+
args.url,
|
|
927
|
+
config,
|
|
928
|
+
{
|
|
929
|
+
waitUntil: args.options?.waitUntil,
|
|
930
|
+
timeout: args.options?.timeout,
|
|
931
|
+
},
|
|
932
|
+
region,
|
|
933
|
+
),
|
|
402
934
|
});
|
|
403
935
|
}
|
|
404
936
|
|
|
405
|
-
const result = await
|
|
937
|
+
const result = await runWithRegionRetry(ctx, {
|
|
406
938
|
sessionId,
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
},
|
|
411
|
-
{
|
|
412
|
-
instruction: args.instruction,
|
|
413
|
-
maxSteps: args.options?.maxSteps,
|
|
939
|
+
initialRegion: resolvedRegion,
|
|
940
|
+
onRegionResolved: async (region) => {
|
|
941
|
+
resolvedRegion = region;
|
|
414
942
|
},
|
|
415
|
-
|
|
416
|
-
|
|
943
|
+
run: async (region) =>
|
|
944
|
+
api.agentExecute(
|
|
945
|
+
sessionId,
|
|
946
|
+
{
|
|
947
|
+
cua: args.options?.cua,
|
|
948
|
+
mode: args.options?.mode,
|
|
949
|
+
model: args.model,
|
|
950
|
+
systemPrompt: args.options?.systemPrompt,
|
|
951
|
+
executionModel: args.options?.executionModel,
|
|
952
|
+
provider: args.options?.provider,
|
|
953
|
+
},
|
|
954
|
+
{
|
|
955
|
+
instruction: args.instruction,
|
|
956
|
+
maxSteps: args.options?.maxSteps,
|
|
957
|
+
highlightCursor: args.options?.highlightCursor,
|
|
958
|
+
},
|
|
959
|
+
config,
|
|
960
|
+
args.options?.shouldCache,
|
|
961
|
+
region,
|
|
962
|
+
),
|
|
963
|
+
});
|
|
417
964
|
|
|
418
965
|
if (ownSession) {
|
|
419
|
-
await
|
|
966
|
+
await endSessionWithRouting(ctx, {
|
|
967
|
+
sessionId,
|
|
968
|
+
config,
|
|
969
|
+
fallbackRegion: resolvedRegion,
|
|
970
|
+
});
|
|
420
971
|
}
|
|
421
972
|
|
|
422
|
-
return
|
|
973
|
+
// Strip passthrough fields to match the Convex return validator.
|
|
974
|
+
// The API may return extra fields (e.g. timestamp, messages) not in the validator.
|
|
975
|
+
const r = result.result;
|
|
976
|
+
return {
|
|
977
|
+
actions: r.actions.map((a: any) => ({
|
|
978
|
+
type: a.type,
|
|
979
|
+
action: a.action,
|
|
980
|
+
reasoning: a.reasoning,
|
|
981
|
+
timeMs: a.timeMs,
|
|
982
|
+
taskCompleted: a.taskCompleted,
|
|
983
|
+
pageText: a.pageText,
|
|
984
|
+
pageUrl: a.pageUrl,
|
|
985
|
+
instruction: a.instruction,
|
|
986
|
+
})),
|
|
987
|
+
completed: r.completed,
|
|
988
|
+
message: r.message,
|
|
989
|
+
success: r.success,
|
|
990
|
+
metadata: r.metadata,
|
|
991
|
+
usage: r.usage
|
|
992
|
+
? {
|
|
993
|
+
input_tokens: r.usage.input_tokens,
|
|
994
|
+
output_tokens: r.usage.output_tokens,
|
|
995
|
+
reasoning_tokens: r.usage.reasoning_tokens,
|
|
996
|
+
cached_input_tokens: r.usage.cached_input_tokens,
|
|
997
|
+
inference_time_ms: r.usage.inference_time_ms,
|
|
998
|
+
}
|
|
999
|
+
: undefined,
|
|
1000
|
+
};
|
|
423
1001
|
} catch (error) {
|
|
424
1002
|
if (ownSession) {
|
|
425
|
-
await
|
|
1003
|
+
await endSessionWithRouting(ctx, {
|
|
1004
|
+
sessionId,
|
|
1005
|
+
config,
|
|
1006
|
+
fallbackRegion: resolvedRegion,
|
|
1007
|
+
});
|
|
426
1008
|
}
|
|
427
1009
|
throw error;
|
|
428
1010
|
}
|