@browserbasehq/convex-stagehand 0.0.2 → 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 +17 -18
- package/dist/client/index.d.ts +119 -4
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +89 -13
- 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 -16
- package/dist/component/api.d.ts.map +1 -1
- package/dist/component/api.js +94 -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 +507 -54
- 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 +218 -16
- package/src/component/_generated/component.ts +213 -12
- package/src/component/_generated/dataModel.ts +45 -2
- package/src/component/api.ts +225 -66
- package/src/component/lib.ts +657 -61
- 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()),
|
|
30
96
|
});
|
|
31
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
|
+
},
|
|
184
|
+
});
|
|
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,7 +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()),
|
|
296
|
+
browserbaseSessionCreateParams: v.optional(v.any()),
|
|
297
|
+
model: modelValidator,
|
|
44
298
|
options: v.optional(
|
|
45
299
|
v.object({
|
|
46
300
|
timeout: v.optional(v.number()),
|
|
@@ -48,15 +302,20 @@ export const startSession = action({
|
|
|
48
302
|
domSettleTimeoutMs: v.optional(v.number()),
|
|
49
303
|
selfHeal: v.optional(v.boolean()),
|
|
50
304
|
systemPrompt: v.optional(v.string()),
|
|
305
|
+
verbose: v.optional(v.number()),
|
|
306
|
+
experimental: v.optional(v.boolean()),
|
|
51
307
|
}),
|
|
52
308
|
),
|
|
53
309
|
},
|
|
54
310
|
returns: v.object({
|
|
55
311
|
sessionId: v.string(),
|
|
56
|
-
browserbaseSessionId: v.optional(v.string()),
|
|
57
312
|
cdpUrl: v.optional(v.string()),
|
|
58
313
|
}),
|
|
59
|
-
handler: async (
|
|
314
|
+
handler: async (ctx: any, args: any) => {
|
|
315
|
+
let resolvedRegion =
|
|
316
|
+
getRequestedRegion(args.browserbaseSessionCreateParams) ??
|
|
317
|
+
DEFAULT_BROWSERBASE_REGION;
|
|
318
|
+
|
|
60
319
|
const config: api.ApiConfig = {
|
|
61
320
|
browserbaseApiKey: args.browserbaseApiKey,
|
|
62
321
|
browserbaseProjectId: args.browserbaseProjectId,
|
|
@@ -65,25 +324,54 @@ export const startSession = action({
|
|
|
65
324
|
};
|
|
66
325
|
|
|
67
326
|
const session = await api.startSession(config, {
|
|
68
|
-
|
|
327
|
+
browserbaseSessionID: args.browserbaseSessionID,
|
|
328
|
+
browserbaseSessionCreateParams: args.browserbaseSessionCreateParams,
|
|
329
|
+
model: args.model,
|
|
69
330
|
domSettleTimeoutMs: args.options?.domSettleTimeoutMs,
|
|
70
331
|
selfHeal: args.options?.selfHeal,
|
|
71
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,
|
|
72
343
|
});
|
|
73
344
|
|
|
74
345
|
try {
|
|
75
|
-
await
|
|
76
|
-
|
|
77
|
-
|
|
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
|
+
),
|
|
78
363
|
});
|
|
79
364
|
|
|
80
365
|
return {
|
|
81
366
|
sessionId: session.sessionId,
|
|
82
|
-
|
|
83
|
-
cdpUrl: session.cdpUrl,
|
|
367
|
+
cdpUrl: session.cdpUrl ?? undefined,
|
|
84
368
|
};
|
|
85
369
|
} catch (error) {
|
|
86
|
-
await
|
|
370
|
+
await endSessionWithRouting(ctx, {
|
|
371
|
+
sessionId: session.sessionId,
|
|
372
|
+
config,
|
|
373
|
+
fallbackRegion: resolvedRegion,
|
|
374
|
+
});
|
|
87
375
|
throw error;
|
|
88
376
|
}
|
|
89
377
|
},
|
|
@@ -97,18 +385,23 @@ export const endSession = action({
|
|
|
97
385
|
browserbaseApiKey: v.string(),
|
|
98
386
|
browserbaseProjectId: v.string(),
|
|
99
387
|
modelApiKey: v.string(),
|
|
388
|
+
modelName: v.optional(v.string()),
|
|
100
389
|
sessionId: v.string(),
|
|
101
390
|
},
|
|
102
391
|
returns: v.object({ success: v.boolean() }),
|
|
103
|
-
handler: async (
|
|
392
|
+
handler: async (ctx: any, args: any) => {
|
|
104
393
|
const config: api.ApiConfig = {
|
|
105
394
|
browserbaseApiKey: args.browserbaseApiKey,
|
|
106
395
|
browserbaseProjectId: args.browserbaseProjectId,
|
|
107
396
|
modelApiKey: args.modelApiKey,
|
|
397
|
+
modelName: args.modelName,
|
|
108
398
|
};
|
|
109
399
|
|
|
110
|
-
await
|
|
111
|
-
|
|
400
|
+
const success = await endSessionWithRouting(ctx, {
|
|
401
|
+
sessionId: args.sessionId,
|
|
402
|
+
config,
|
|
403
|
+
});
|
|
404
|
+
return { success };
|
|
112
405
|
},
|
|
113
406
|
});
|
|
114
407
|
|
|
@@ -127,15 +420,19 @@ export const extract = action({
|
|
|
127
420
|
url: v.optional(v.string()),
|
|
128
421
|
instruction: v.string(),
|
|
129
422
|
schema: v.any(),
|
|
423
|
+
browserbaseSessionCreateParams: v.optional(v.any()),
|
|
424
|
+
model: modelValidator,
|
|
425
|
+
sessionConfig: sessionConfigValidator,
|
|
130
426
|
options: v.optional(
|
|
131
427
|
v.object({
|
|
132
428
|
timeout: v.optional(v.number()),
|
|
133
429
|
waitUntil: v.optional(waitUntilValidator),
|
|
430
|
+
selector: v.optional(v.string()),
|
|
134
431
|
}),
|
|
135
432
|
),
|
|
136
433
|
},
|
|
137
434
|
returns: v.any(),
|
|
138
|
-
handler: async (
|
|
435
|
+
handler: async (ctx: any, args: any) => {
|
|
139
436
|
if (!args.sessionId && !args.url) {
|
|
140
437
|
throw new Error("Either sessionId or url must be provided");
|
|
141
438
|
}
|
|
@@ -149,35 +446,94 @@ export const extract = action({
|
|
|
149
446
|
|
|
150
447
|
const ownSession = !args.sessionId;
|
|
151
448
|
let sessionId = args.sessionId;
|
|
449
|
+
let resolvedRegion: api.BrowserbaseRegion | undefined;
|
|
152
450
|
|
|
153
451
|
if (ownSession) {
|
|
154
|
-
|
|
452
|
+
resolvedRegion =
|
|
453
|
+
getRequestedRegion(args.browserbaseSessionCreateParams) ??
|
|
454
|
+
DEFAULT_BROWSERBASE_REGION;
|
|
455
|
+
const session = await api.startSession(config, {
|
|
456
|
+
browserbaseSessionCreateParams: args.browserbaseSessionCreateParams,
|
|
457
|
+
model: args.model,
|
|
458
|
+
...args.sessionConfig,
|
|
459
|
+
});
|
|
155
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);
|
|
156
476
|
}
|
|
157
477
|
|
|
158
478
|
try {
|
|
159
479
|
if (ownSession && args.url) {
|
|
160
|
-
await
|
|
161
|
-
|
|
162
|
-
|
|
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
|
+
),
|
|
163
497
|
});
|
|
164
498
|
}
|
|
165
499
|
|
|
166
|
-
const result = await
|
|
500
|
+
const result = await runWithRegionRetry(ctx, {
|
|
167
501
|
sessionId,
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
+
});
|
|
172
520
|
|
|
173
521
|
if (ownSession) {
|
|
174
|
-
await
|
|
522
|
+
await endSessionWithRouting(ctx, {
|
|
523
|
+
sessionId,
|
|
524
|
+
config,
|
|
525
|
+
fallbackRegion: resolvedRegion,
|
|
526
|
+
});
|
|
175
527
|
}
|
|
176
528
|
|
|
177
529
|
return result.result;
|
|
178
530
|
} catch (error) {
|
|
179
531
|
if (ownSession) {
|
|
180
|
-
await
|
|
532
|
+
await endSessionWithRouting(ctx, {
|
|
533
|
+
sessionId,
|
|
534
|
+
config,
|
|
535
|
+
fallbackRegion: resolvedRegion,
|
|
536
|
+
});
|
|
181
537
|
}
|
|
182
538
|
throw error;
|
|
183
539
|
}
|
|
@@ -198,10 +554,14 @@ export const act = action({
|
|
|
198
554
|
sessionId: v.optional(v.string()),
|
|
199
555
|
url: v.optional(v.string()),
|
|
200
556
|
action: v.string(),
|
|
557
|
+
browserbaseSessionCreateParams: v.optional(v.any()),
|
|
558
|
+
model: modelValidator,
|
|
559
|
+
sessionConfig: sessionConfigValidator,
|
|
201
560
|
options: v.optional(
|
|
202
561
|
v.object({
|
|
203
562
|
timeout: v.optional(v.number()),
|
|
204
563
|
waitUntil: v.optional(waitUntilValidator),
|
|
564
|
+
variables: variablesValidator,
|
|
205
565
|
}),
|
|
206
566
|
),
|
|
207
567
|
},
|
|
@@ -210,7 +570,7 @@ export const act = action({
|
|
|
210
570
|
message: v.string(),
|
|
211
571
|
actionDescription: v.string(),
|
|
212
572
|
}),
|
|
213
|
-
handler: async (
|
|
573
|
+
handler: async (ctx: any, args: any) => {
|
|
214
574
|
if (!args.sessionId && !args.url) {
|
|
215
575
|
throw new Error("Either sessionId or url must be provided");
|
|
216
576
|
}
|
|
@@ -224,24 +584,83 @@ export const act = action({
|
|
|
224
584
|
|
|
225
585
|
const ownSession = !args.sessionId;
|
|
226
586
|
let sessionId = args.sessionId;
|
|
587
|
+
let resolvedRegion: api.BrowserbaseRegion | undefined;
|
|
227
588
|
|
|
228
589
|
if (ownSession) {
|
|
229
|
-
|
|
590
|
+
resolvedRegion =
|
|
591
|
+
getRequestedRegion(args.browserbaseSessionCreateParams) ??
|
|
592
|
+
DEFAULT_BROWSERBASE_REGION;
|
|
593
|
+
const session = await api.startSession(config, {
|
|
594
|
+
browserbaseSessionCreateParams: args.browserbaseSessionCreateParams,
|
|
595
|
+
model: args.model,
|
|
596
|
+
...args.sessionConfig,
|
|
597
|
+
});
|
|
230
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);
|
|
231
614
|
}
|
|
232
615
|
|
|
233
616
|
try {
|
|
234
617
|
if (ownSession && args.url) {
|
|
235
|
-
await
|
|
236
|
-
|
|
237
|
-
|
|
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
|
+
),
|
|
238
635
|
});
|
|
239
636
|
}
|
|
240
637
|
|
|
241
|
-
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
|
+
});
|
|
242
657
|
|
|
243
658
|
if (ownSession) {
|
|
244
|
-
await
|
|
659
|
+
await endSessionWithRouting(ctx, {
|
|
660
|
+
sessionId,
|
|
661
|
+
config,
|
|
662
|
+
fallbackRegion: resolvedRegion,
|
|
663
|
+
});
|
|
245
664
|
}
|
|
246
665
|
|
|
247
666
|
return {
|
|
@@ -251,7 +670,11 @@ export const act = action({
|
|
|
251
670
|
};
|
|
252
671
|
} catch (error) {
|
|
253
672
|
if (ownSession) {
|
|
254
|
-
await
|
|
673
|
+
await endSessionWithRouting(ctx, {
|
|
674
|
+
sessionId,
|
|
675
|
+
config,
|
|
676
|
+
fallbackRegion: resolvedRegion,
|
|
677
|
+
});
|
|
255
678
|
}
|
|
256
679
|
throw error;
|
|
257
680
|
}
|
|
@@ -272,15 +695,19 @@ export const observe = action({
|
|
|
272
695
|
sessionId: v.optional(v.string()),
|
|
273
696
|
url: v.optional(v.string()),
|
|
274
697
|
instruction: v.string(),
|
|
698
|
+
browserbaseSessionCreateParams: v.optional(v.any()),
|
|
699
|
+
model: modelValidator,
|
|
700
|
+
sessionConfig: sessionConfigValidator,
|
|
275
701
|
options: v.optional(
|
|
276
702
|
v.object({
|
|
277
703
|
timeout: v.optional(v.number()),
|
|
278
704
|
waitUntil: v.optional(waitUntilValidator),
|
|
705
|
+
selector: v.optional(v.string()),
|
|
279
706
|
}),
|
|
280
707
|
),
|
|
281
708
|
},
|
|
282
709
|
returns: v.array(observedActionValidator),
|
|
283
|
-
handler: async (
|
|
710
|
+
handler: async (ctx: any, args: any) => {
|
|
284
711
|
if (!args.sessionId && !args.url) {
|
|
285
712
|
throw new Error("Either sessionId or url must be provided");
|
|
286
713
|
}
|
|
@@ -294,24 +721,83 @@ export const observe = action({
|
|
|
294
721
|
|
|
295
722
|
const ownSession = !args.sessionId;
|
|
296
723
|
let sessionId = args.sessionId;
|
|
724
|
+
let resolvedRegion: api.BrowserbaseRegion | undefined;
|
|
297
725
|
|
|
298
726
|
if (ownSession) {
|
|
299
|
-
|
|
727
|
+
resolvedRegion =
|
|
728
|
+
getRequestedRegion(args.browserbaseSessionCreateParams) ??
|
|
729
|
+
DEFAULT_BROWSERBASE_REGION;
|
|
730
|
+
const session = await api.startSession(config, {
|
|
731
|
+
browserbaseSessionCreateParams: args.browserbaseSessionCreateParams,
|
|
732
|
+
model: args.model,
|
|
733
|
+
...args.sessionConfig,
|
|
734
|
+
});
|
|
300
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);
|
|
301
751
|
}
|
|
302
752
|
|
|
303
753
|
try {
|
|
304
754
|
if (ownSession && args.url) {
|
|
305
|
-
await
|
|
306
|
-
|
|
307
|
-
|
|
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
|
+
),
|
|
308
772
|
});
|
|
309
773
|
}
|
|
310
774
|
|
|
311
|
-
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
|
+
});
|
|
312
794
|
|
|
313
795
|
if (ownSession) {
|
|
314
|
-
await
|
|
796
|
+
await endSessionWithRouting(ctx, {
|
|
797
|
+
sessionId,
|
|
798
|
+
config,
|
|
799
|
+
fallbackRegion: resolvedRegion,
|
|
800
|
+
});
|
|
315
801
|
}
|
|
316
802
|
|
|
317
803
|
return result.result.map((action) => ({
|
|
@@ -319,10 +805,15 @@ export const observe = action({
|
|
|
319
805
|
selector: action.selector,
|
|
320
806
|
method: action.method,
|
|
321
807
|
arguments: action.arguments,
|
|
808
|
+
backendNodeId: action.backendNodeId,
|
|
322
809
|
}));
|
|
323
810
|
} catch (error) {
|
|
324
811
|
if (ownSession) {
|
|
325
|
-
await
|
|
812
|
+
await endSessionWithRouting(ctx, {
|
|
813
|
+
sessionId,
|
|
814
|
+
config,
|
|
815
|
+
fallbackRegion: resolvedRegion,
|
|
816
|
+
});
|
|
326
817
|
}
|
|
327
818
|
throw error;
|
|
328
819
|
}
|
|
@@ -344,13 +835,21 @@ export const agent = action({
|
|
|
344
835
|
sessionId: v.optional(v.string()),
|
|
345
836
|
url: v.optional(v.string()),
|
|
346
837
|
instruction: v.string(),
|
|
838
|
+
browserbaseSessionCreateParams: v.optional(v.any()),
|
|
839
|
+
model: modelValidator,
|
|
840
|
+
sessionConfig: sessionConfigValidator,
|
|
347
841
|
options: v.optional(
|
|
348
842
|
v.object({
|
|
349
843
|
cua: v.optional(v.boolean()),
|
|
844
|
+
mode: v.optional(v.string()),
|
|
350
845
|
maxSteps: v.optional(v.number()),
|
|
351
846
|
systemPrompt: v.optional(v.string()),
|
|
352
847
|
timeout: v.optional(v.number()),
|
|
353
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()),
|
|
354
853
|
}),
|
|
355
854
|
),
|
|
356
855
|
},
|
|
@@ -359,8 +858,18 @@ export const agent = action({
|
|
|
359
858
|
completed: v.boolean(),
|
|
360
859
|
message: v.string(),
|
|
361
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
|
+
),
|
|
362
871
|
}),
|
|
363
|
-
handler: async (
|
|
872
|
+
handler: async (ctx: any, args: any) => {
|
|
364
873
|
if (!args.sessionId && !args.url) {
|
|
365
874
|
throw new Error("Either sessionId or url must be provided");
|
|
366
875
|
}
|
|
@@ -374,41 +883,128 @@ export const agent = action({
|
|
|
374
883
|
|
|
375
884
|
const ownSession = !args.sessionId;
|
|
376
885
|
let sessionId = args.sessionId;
|
|
886
|
+
let resolvedRegion: api.BrowserbaseRegion | undefined;
|
|
377
887
|
|
|
378
888
|
if (ownSession) {
|
|
379
|
-
|
|
889
|
+
resolvedRegion =
|
|
890
|
+
getRequestedRegion(args.browserbaseSessionCreateParams) ??
|
|
891
|
+
DEFAULT_BROWSERBASE_REGION;
|
|
892
|
+
const session = await api.startSession(config, {
|
|
893
|
+
browserbaseSessionCreateParams: args.browserbaseSessionCreateParams,
|
|
894
|
+
model: args.model,
|
|
895
|
+
...args.sessionConfig,
|
|
896
|
+
});
|
|
380
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);
|
|
381
913
|
}
|
|
382
914
|
|
|
383
915
|
try {
|
|
384
916
|
if (ownSession && args.url) {
|
|
385
|
-
await
|
|
386
|
-
|
|
387
|
-
|
|
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
|
+
),
|
|
388
934
|
});
|
|
389
935
|
}
|
|
390
936
|
|
|
391
|
-
const result = await
|
|
937
|
+
const result = await runWithRegionRetry(ctx, {
|
|
392
938
|
sessionId,
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
},
|
|
397
|
-
{
|
|
398
|
-
instruction: args.instruction,
|
|
399
|
-
maxSteps: args.options?.maxSteps,
|
|
939
|
+
initialRegion: resolvedRegion,
|
|
940
|
+
onRegionResolved: async (region) => {
|
|
941
|
+
resolvedRegion = region;
|
|
400
942
|
},
|
|
401
|
-
|
|
402
|
-
|
|
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
|
+
});
|
|
403
964
|
|
|
404
965
|
if (ownSession) {
|
|
405
|
-
await
|
|
966
|
+
await endSessionWithRouting(ctx, {
|
|
967
|
+
sessionId,
|
|
968
|
+
config,
|
|
969
|
+
fallbackRegion: resolvedRegion,
|
|
970
|
+
});
|
|
406
971
|
}
|
|
407
972
|
|
|
408
|
-
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
|
+
};
|
|
409
1001
|
} catch (error) {
|
|
410
1002
|
if (ownSession) {
|
|
411
|
-
await
|
|
1003
|
+
await endSessionWithRouting(ctx, {
|
|
1004
|
+
sessionId,
|
|
1005
|
+
config,
|
|
1006
|
+
fallbackRegion: resolvedRegion,
|
|
1007
|
+
});
|
|
412
1008
|
}
|
|
413
1009
|
throw error;
|
|
414
1010
|
}
|