@browserbasehq/convex-stagehand 0.0.1

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 (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +477 -0
  3. package/dist/client/index.d.ts +258 -0
  4. package/dist/client/index.d.ts.map +1 -0
  5. package/dist/client/index.js +216 -0
  6. package/dist/client/index.js.map +1 -0
  7. package/dist/component/_generated/api.d.ts +36 -0
  8. package/dist/component/_generated/api.d.ts.map +1 -0
  9. package/dist/component/_generated/api.js +31 -0
  10. package/dist/component/_generated/api.js.map +1 -0
  11. package/dist/component/_generated/component.d.ts +24 -0
  12. package/dist/component/_generated/component.d.ts.map +1 -0
  13. package/dist/component/_generated/component.js +11 -0
  14. package/dist/component/_generated/component.js.map +1 -0
  15. package/dist/component/_generated/dataModel.d.ts +15 -0
  16. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  17. package/dist/component/_generated/dataModel.js +11 -0
  18. package/dist/component/_generated/dataModel.js.map +1 -0
  19. package/dist/component/_generated/server.d.ts +121 -0
  20. package/dist/component/_generated/server.d.ts.map +1 -0
  21. package/dist/component/_generated/server.js +78 -0
  22. package/dist/component/_generated/server.js.map +1 -0
  23. package/dist/component/api.d.ts +90 -0
  24. package/dist/component/api.d.ts.map +1 -0
  25. package/dist/component/api.js +112 -0
  26. package/dist/component/api.js.map +1 -0
  27. package/dist/component/convex.config.d.ts +3 -0
  28. package/dist/component/convex.config.d.ts.map +1 -0
  29. package/dist/component/convex.config.js +3 -0
  30. package/dist/component/convex.config.js.map +1 -0
  31. package/dist/component/lib.d.ts +62 -0
  32. package/dist/component/lib.d.ts.map +1 -0
  33. package/dist/component/lib.js +356 -0
  34. package/dist/component/lib.js.map +1 -0
  35. package/dist/component/schema.d.ts +23 -0
  36. package/dist/component/schema.d.ts.map +1 -0
  37. package/dist/component/schema.js +14 -0
  38. package/dist/component/schema.js.map +1 -0
  39. package/dist/test.d.ts +48 -0
  40. package/dist/test.d.ts.map +1 -0
  41. package/dist/test.js +25 -0
  42. package/dist/test.js.map +1 -0
  43. package/package.json +87 -0
  44. package/src/client/index.ts +348 -0
  45. package/src/component/README.md +90 -0
  46. package/src/component/_generated/api.ts +52 -0
  47. package/src/component/_generated/component.ts +26 -0
  48. package/src/component/_generated/dataModel.ts +17 -0
  49. package/src/component/_generated/server.ts +156 -0
  50. package/src/component/api.ts +243 -0
  51. package/src/component/convex.config.ts +3 -0
  52. package/src/component/lib.ts +416 -0
  53. package/src/component/schema.ts +23 -0
  54. package/src/component/tsconfig.json +25 -0
  55. package/src/test.ts +33 -0
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Stagehand REST API Client
3
+ *
4
+ * Wraps the Stagehand API at https://api.stagehand.browserbase.com
5
+ */
6
+
7
+ const API_BASE = "https://api.stagehand.browserbase.com/v1";
8
+
9
+ export interface ApiConfig {
10
+ browserbaseApiKey: string;
11
+ browserbaseProjectId: string;
12
+ modelApiKey: string;
13
+ modelName?: string;
14
+ }
15
+
16
+ export interface SessionData {
17
+ sessionId: string;
18
+ browserbaseSessionId?: string;
19
+ cdpUrl?: string;
20
+ available: boolean;
21
+ }
22
+
23
+ export interface StartSessionOptions {
24
+ browserbaseSessionId?: string;
25
+ domSettleTimeoutMs?: number;
26
+ selfHeal?: boolean;
27
+ systemPrompt?: string;
28
+ }
29
+
30
+ export interface ApiResponse<T> {
31
+ data: T;
32
+ success: boolean;
33
+ }
34
+
35
+ function getHeaders(config: ApiConfig): Record<string, string> {
36
+ return {
37
+ "Content-Type": "application/json",
38
+ "x-bb-api-key": config.browserbaseApiKey,
39
+ "x-bb-project-id": config.browserbaseProjectId,
40
+ "x-model-api-key": config.modelApiKey,
41
+ };
42
+ }
43
+
44
+ async function handleResponse<T>(response: Response): Promise<T> {
45
+ if (!response.ok) {
46
+ const errorText = await response.text();
47
+ throw new Error(
48
+ `Stagehand API error (${response.status}): ${errorText}`,
49
+ );
50
+ }
51
+ const json = (await response.json()) as ApiResponse<T>;
52
+ if (!json.success) {
53
+ throw new Error(`Stagehand API returned success: false`);
54
+ }
55
+ return json.data;
56
+ }
57
+
58
+ export async function startSession(
59
+ config: ApiConfig,
60
+ options?: StartSessionOptions,
61
+ ): Promise<SessionData> {
62
+ const response = await fetch(`${API_BASE}/sessions/start`, {
63
+ method: "POST",
64
+ headers: getHeaders(config),
65
+ body: JSON.stringify({
66
+ modelName: config.modelName || "openai/gpt-4o",
67
+ browserbaseSessionId: options?.browserbaseSessionId,
68
+ domSettleTimeoutMs: options?.domSettleTimeoutMs,
69
+ selfHeal: options?.selfHeal,
70
+ systemPrompt: options?.systemPrompt,
71
+ }),
72
+ });
73
+ return handleResponse<SessionData>(response);
74
+ }
75
+
76
+ export async function endSession(
77
+ sessionId: string,
78
+ config: ApiConfig,
79
+ ): Promise<void> {
80
+ try {
81
+ await fetch(`${API_BASE}/sessions/${sessionId}/end`, {
82
+ method: "POST",
83
+ headers: getHeaders(config),
84
+ });
85
+ } catch {
86
+ // Ignore errors when ending session - best effort cleanup
87
+ }
88
+ }
89
+
90
+ export interface NavigateOptions {
91
+ waitUntil?: "load" | "domcontentloaded" | "networkidle";
92
+ timeout?: number;
93
+ }
94
+
95
+ export async function navigate(
96
+ sessionId: string,
97
+ url: string,
98
+ config: ApiConfig,
99
+ options?: NavigateOptions,
100
+ ): Promise<void> {
101
+ const response = await fetch(`${API_BASE}/sessions/${sessionId}/navigate`, {
102
+ method: "POST",
103
+ headers: getHeaders(config),
104
+ body: JSON.stringify({
105
+ url,
106
+ options: {
107
+ waitUntil: options?.waitUntil || "networkidle",
108
+ timeout: options?.timeout,
109
+ },
110
+ }),
111
+ });
112
+ await handleResponse(response);
113
+ }
114
+
115
+ export interface ExtractResult<T = any> {
116
+ result: T;
117
+ actionId: string;
118
+ }
119
+
120
+ export async function extract(
121
+ sessionId: string,
122
+ instruction: string,
123
+ schema: any,
124
+ config: ApiConfig,
125
+ ): Promise<ExtractResult> {
126
+ const response = await fetch(`${API_BASE}/sessions/${sessionId}/extract`, {
127
+ method: "POST",
128
+ headers: getHeaders(config),
129
+ body: JSON.stringify({
130
+ instruction,
131
+ schema,
132
+ }),
133
+ });
134
+ return handleResponse<ExtractResult>(response);
135
+ }
136
+
137
+ export interface ActResult {
138
+ result: {
139
+ actionDescription: string;
140
+ actions: Array<{
141
+ description: string;
142
+ selector: string;
143
+ arguments?: string[];
144
+ method: string;
145
+ }>;
146
+ message: string;
147
+ success: boolean;
148
+ };
149
+ actionId: string;
150
+ }
151
+
152
+ export async function act(
153
+ sessionId: string,
154
+ action: string,
155
+ config: ApiConfig,
156
+ ): Promise<ActResult> {
157
+ const response = await fetch(`${API_BASE}/sessions/${sessionId}/act`, {
158
+ method: "POST",
159
+ headers: getHeaders(config),
160
+ body: JSON.stringify({
161
+ input: action,
162
+ }),
163
+ });
164
+ return handleResponse<ActResult>(response);
165
+ }
166
+
167
+ export interface ObserveResult {
168
+ result: Array<{
169
+ description: string;
170
+ selector: string;
171
+ arguments?: string[];
172
+ backendNodeId?: number;
173
+ method: string;
174
+ }>;
175
+ actionId: string;
176
+ }
177
+
178
+ export async function observe(
179
+ sessionId: string,
180
+ instruction: string,
181
+ config: ApiConfig,
182
+ ): Promise<ObserveResult> {
183
+ const response = await fetch(`${API_BASE}/sessions/${sessionId}/observe`, {
184
+ method: "POST",
185
+ headers: getHeaders(config),
186
+ body: JSON.stringify({
187
+ instruction,
188
+ }),
189
+ });
190
+ return handleResponse<ObserveResult>(response);
191
+ }
192
+
193
+ export interface AgentConfig {
194
+ cua?: boolean;
195
+ model?: string;
196
+ systemPrompt?: string;
197
+ }
198
+
199
+ export interface AgentExecuteOptions {
200
+ instruction: string;
201
+ maxSteps?: number;
202
+ }
203
+
204
+ export interface AgentAction {
205
+ type: string;
206
+ action?: string;
207
+ reasoning?: string;
208
+ timeMs?: number;
209
+ }
210
+
211
+ export interface AgentExecuteResult {
212
+ result: {
213
+ actions: AgentAction[];
214
+ completed: boolean;
215
+ message: string;
216
+ success: boolean;
217
+ };
218
+ }
219
+
220
+ export async function agentExecute(
221
+ sessionId: string,
222
+ agentConfig: AgentConfig,
223
+ executeOptions: AgentExecuteOptions,
224
+ config: ApiConfig,
225
+ ): Promise<AgentExecuteResult> {
226
+ const response = await fetch(
227
+ `${API_BASE}/sessions/${sessionId}/agent/execute`,
228
+ {
229
+ method: "POST",
230
+ headers: getHeaders(config),
231
+ body: JSON.stringify({
232
+ agentConfig: {
233
+ cua: agentConfig.cua,
234
+ model: agentConfig.model,
235
+ systemPrompt: agentConfig.systemPrompt,
236
+ },
237
+ instruction: executeOptions.instruction,
238
+ maxSteps: executeOptions.maxSteps,
239
+ }),
240
+ },
241
+ );
242
+ return handleResponse<AgentExecuteResult>(response);
243
+ }
@@ -0,0 +1,3 @@
1
+ import { defineComponent } from "convex/server";
2
+
3
+ export default defineComponent("stagehand");
@@ -0,0 +1,416 @@
1
+ /**
2
+ * Stagehand Component Library
3
+ *
4
+ * AI-powered browser automation actions using the Stagehand REST API.
5
+ * Supports both automatic session management and manual session control.
6
+ */
7
+
8
+ import { action } from "./_generated/server.js";
9
+ import { v } from "convex/values";
10
+ import * as api from "./api.js";
11
+
12
+ const observedActionValidator = v.object({
13
+ description: v.string(),
14
+ selector: v.string(),
15
+ method: v.string(),
16
+ arguments: v.optional(v.array(v.string())),
17
+ });
18
+
19
+ const waitUntilValidator = v.union(
20
+ v.literal("load"),
21
+ v.literal("domcontentloaded"),
22
+ v.literal("networkidle"),
23
+ );
24
+
25
+ const agentActionValidator = v.object({
26
+ type: v.string(),
27
+ action: v.optional(v.string()),
28
+ reasoning: v.optional(v.string()),
29
+ timeMs: v.optional(v.number()),
30
+ });
31
+
32
+ /**
33
+ * Start a new browser session.
34
+ * Returns session info including cdpUrl for direct Playwright/Puppeteer connection.
35
+ */
36
+ export const startSession = action({
37
+ args: {
38
+ browserbaseApiKey: v.string(),
39
+ browserbaseProjectId: v.string(),
40
+ modelApiKey: v.string(),
41
+ modelName: v.optional(v.string()),
42
+ url: v.string(),
43
+ browserbaseSessionId: v.optional(v.string()),
44
+ options: v.optional(
45
+ v.object({
46
+ timeout: v.optional(v.number()),
47
+ waitUntil: v.optional(waitUntilValidator),
48
+ domSettleTimeoutMs: v.optional(v.number()),
49
+ selfHeal: v.optional(v.boolean()),
50
+ systemPrompt: v.optional(v.string()),
51
+ }),
52
+ ),
53
+ },
54
+ returns: v.object({
55
+ sessionId: v.string(),
56
+ browserbaseSessionId: v.optional(v.string()),
57
+ cdpUrl: v.optional(v.string()),
58
+ }),
59
+ handler: async (_ctx: any, args: any) => {
60
+ const config: api.ApiConfig = {
61
+ browserbaseApiKey: args.browserbaseApiKey,
62
+ browserbaseProjectId: args.browserbaseProjectId,
63
+ modelApiKey: args.modelApiKey,
64
+ modelName: args.modelName,
65
+ };
66
+
67
+ const session = await api.startSession(config, {
68
+ browserbaseSessionId: args.browserbaseSessionId,
69
+ domSettleTimeoutMs: args.options?.domSettleTimeoutMs,
70
+ selfHeal: args.options?.selfHeal,
71
+ systemPrompt: args.options?.systemPrompt,
72
+ });
73
+
74
+ try {
75
+ await api.navigate(session.sessionId, args.url, config, {
76
+ waitUntil: args.options?.waitUntil,
77
+ timeout: args.options?.timeout,
78
+ });
79
+
80
+ return {
81
+ sessionId: session.sessionId,
82
+ browserbaseSessionId: session.browserbaseSessionId,
83
+ cdpUrl: session.cdpUrl,
84
+ };
85
+ } catch (error) {
86
+ await api.endSession(session.sessionId, config);
87
+ throw error;
88
+ }
89
+ },
90
+ });
91
+
92
+ /**
93
+ * End a browser session.
94
+ */
95
+ export const endSession = action({
96
+ args: {
97
+ browserbaseApiKey: v.string(),
98
+ browserbaseProjectId: v.string(),
99
+ modelApiKey: v.string(),
100
+ sessionId: v.string(),
101
+ },
102
+ returns: v.object({ success: v.boolean() }),
103
+ handler: async (_ctx: any, args: any) => {
104
+ const config: api.ApiConfig = {
105
+ browserbaseApiKey: args.browserbaseApiKey,
106
+ browserbaseProjectId: args.browserbaseProjectId,
107
+ modelApiKey: args.modelApiKey,
108
+ };
109
+
110
+ await api.endSession(args.sessionId, config);
111
+ return { success: true };
112
+ },
113
+ });
114
+
115
+ /**
116
+ * Extract structured data from a web page using AI.
117
+ * If sessionId is provided, uses existing session (doesn't end it).
118
+ * Otherwise, handles full session lifecycle: start -> navigate -> extract -> end
119
+ */
120
+ export const extract = action({
121
+ args: {
122
+ browserbaseApiKey: v.string(),
123
+ browserbaseProjectId: v.string(),
124
+ modelApiKey: v.string(),
125
+ modelName: v.optional(v.string()),
126
+ sessionId: v.optional(v.string()),
127
+ url: v.optional(v.string()),
128
+ instruction: v.string(),
129
+ schema: v.any(),
130
+ options: v.optional(
131
+ v.object({
132
+ timeout: v.optional(v.number()),
133
+ waitUntil: v.optional(waitUntilValidator),
134
+ }),
135
+ ),
136
+ },
137
+ returns: v.any(),
138
+ handler: async (_ctx: any, args: any) => {
139
+ if (!args.sessionId && !args.url) {
140
+ throw new Error("Either sessionId or url must be provided");
141
+ }
142
+
143
+ const config: api.ApiConfig = {
144
+ browserbaseApiKey: args.browserbaseApiKey,
145
+ browserbaseProjectId: args.browserbaseProjectId,
146
+ modelApiKey: args.modelApiKey,
147
+ modelName: args.modelName,
148
+ };
149
+
150
+ const ownSession = !args.sessionId;
151
+ let sessionId = args.sessionId;
152
+
153
+ if (ownSession) {
154
+ const session = await api.startSession(config);
155
+ sessionId = session.sessionId;
156
+ }
157
+
158
+ try {
159
+ if (ownSession && args.url) {
160
+ await api.navigate(sessionId, args.url, config, {
161
+ waitUntil: args.options?.waitUntil,
162
+ timeout: args.options?.timeout,
163
+ });
164
+ }
165
+
166
+ const result = await api.extract(
167
+ sessionId,
168
+ args.instruction,
169
+ args.schema,
170
+ config,
171
+ );
172
+
173
+ if (ownSession) {
174
+ await api.endSession(sessionId, config);
175
+ }
176
+
177
+ return result.result;
178
+ } catch (error) {
179
+ if (ownSession) {
180
+ await api.endSession(sessionId, config);
181
+ }
182
+ throw error;
183
+ }
184
+ },
185
+ });
186
+
187
+ /**
188
+ * Execute browser actions using natural language instructions.
189
+ * If sessionId is provided, uses existing session (doesn't end it).
190
+ * Otherwise, handles full session lifecycle: start -> navigate -> act -> end
191
+ */
192
+ export const act = action({
193
+ args: {
194
+ browserbaseApiKey: v.string(),
195
+ browserbaseProjectId: v.string(),
196
+ modelApiKey: v.string(),
197
+ modelName: v.optional(v.string()),
198
+ sessionId: v.optional(v.string()),
199
+ url: v.optional(v.string()),
200
+ action: v.string(),
201
+ options: v.optional(
202
+ v.object({
203
+ timeout: v.optional(v.number()),
204
+ waitUntil: v.optional(waitUntilValidator),
205
+ }),
206
+ ),
207
+ },
208
+ returns: v.object({
209
+ success: v.boolean(),
210
+ message: v.string(),
211
+ actionDescription: v.string(),
212
+ }),
213
+ handler: async (_ctx: any, args: any) => {
214
+ if (!args.sessionId && !args.url) {
215
+ throw new Error("Either sessionId or url must be provided");
216
+ }
217
+
218
+ const config: api.ApiConfig = {
219
+ browserbaseApiKey: args.browserbaseApiKey,
220
+ browserbaseProjectId: args.browserbaseProjectId,
221
+ modelApiKey: args.modelApiKey,
222
+ modelName: args.modelName,
223
+ };
224
+
225
+ const ownSession = !args.sessionId;
226
+ let sessionId = args.sessionId;
227
+
228
+ if (ownSession) {
229
+ const session = await api.startSession(config);
230
+ sessionId = session.sessionId;
231
+ }
232
+
233
+ try {
234
+ if (ownSession && args.url) {
235
+ await api.navigate(sessionId, args.url, config, {
236
+ waitUntil: args.options?.waitUntil,
237
+ timeout: args.options?.timeout,
238
+ });
239
+ }
240
+
241
+ const result = await api.act(sessionId, args.action, config);
242
+
243
+ if (ownSession) {
244
+ await api.endSession(sessionId, config);
245
+ }
246
+
247
+ return {
248
+ success: result.result.success,
249
+ message: result.result.message,
250
+ actionDescription: result.result.actionDescription,
251
+ };
252
+ } catch (error) {
253
+ if (ownSession) {
254
+ await api.endSession(sessionId, config);
255
+ }
256
+ throw error;
257
+ }
258
+ },
259
+ });
260
+
261
+ /**
262
+ * Find available actions on a web page matching an instruction.
263
+ * If sessionId is provided, uses existing session (doesn't end it).
264
+ * Otherwise, handles full session lifecycle: start -> navigate -> observe -> end
265
+ */
266
+ export const observe = action({
267
+ args: {
268
+ browserbaseApiKey: v.string(),
269
+ browserbaseProjectId: v.string(),
270
+ modelApiKey: v.string(),
271
+ modelName: v.optional(v.string()),
272
+ sessionId: v.optional(v.string()),
273
+ url: v.optional(v.string()),
274
+ instruction: v.string(),
275
+ options: v.optional(
276
+ v.object({
277
+ timeout: v.optional(v.number()),
278
+ waitUntil: v.optional(waitUntilValidator),
279
+ }),
280
+ ),
281
+ },
282
+ returns: v.array(observedActionValidator),
283
+ handler: async (_ctx: any, args: any) => {
284
+ if (!args.sessionId && !args.url) {
285
+ throw new Error("Either sessionId or url must be provided");
286
+ }
287
+
288
+ const config: api.ApiConfig = {
289
+ browserbaseApiKey: args.browserbaseApiKey,
290
+ browserbaseProjectId: args.browserbaseProjectId,
291
+ modelApiKey: args.modelApiKey,
292
+ modelName: args.modelName,
293
+ };
294
+
295
+ const ownSession = !args.sessionId;
296
+ let sessionId = args.sessionId;
297
+
298
+ if (ownSession) {
299
+ const session = await api.startSession(config);
300
+ sessionId = session.sessionId;
301
+ }
302
+
303
+ try {
304
+ if (ownSession && args.url) {
305
+ await api.navigate(sessionId, args.url, config, {
306
+ waitUntil: args.options?.waitUntil,
307
+ timeout: args.options?.timeout,
308
+ });
309
+ }
310
+
311
+ const result = await api.observe(sessionId, args.instruction, config);
312
+
313
+ if (ownSession) {
314
+ await api.endSession(sessionId, config);
315
+ }
316
+
317
+ return result.result.map((action) => ({
318
+ description: action.description,
319
+ selector: action.selector,
320
+ method: action.method,
321
+ arguments: action.arguments,
322
+ }));
323
+ } catch (error) {
324
+ if (ownSession) {
325
+ await api.endSession(sessionId, config);
326
+ }
327
+ throw error;
328
+ }
329
+ },
330
+ });
331
+
332
+ /**
333
+ * Execute autonomous multi-step browser automation using an AI agent.
334
+ * The agent interprets the instruction and decides what actions to take.
335
+ * If sessionId is provided, uses existing session (doesn't end it).
336
+ * Otherwise, handles full session lifecycle.
337
+ */
338
+ export const agent = action({
339
+ args: {
340
+ browserbaseApiKey: v.string(),
341
+ browserbaseProjectId: v.string(),
342
+ modelApiKey: v.string(),
343
+ modelName: v.optional(v.string()),
344
+ sessionId: v.optional(v.string()),
345
+ url: v.optional(v.string()),
346
+ instruction: v.string(),
347
+ options: v.optional(
348
+ v.object({
349
+ cua: v.optional(v.boolean()),
350
+ maxSteps: v.optional(v.number()),
351
+ systemPrompt: v.optional(v.string()),
352
+ timeout: v.optional(v.number()),
353
+ waitUntil: v.optional(waitUntilValidator),
354
+ }),
355
+ ),
356
+ },
357
+ returns: v.object({
358
+ actions: v.array(agentActionValidator),
359
+ completed: v.boolean(),
360
+ message: v.string(),
361
+ success: v.boolean(),
362
+ }),
363
+ handler: async (_ctx: any, args: any) => {
364
+ if (!args.sessionId && !args.url) {
365
+ throw new Error("Either sessionId or url must be provided");
366
+ }
367
+
368
+ const config: api.ApiConfig = {
369
+ browserbaseApiKey: args.browserbaseApiKey,
370
+ browserbaseProjectId: args.browserbaseProjectId,
371
+ modelApiKey: args.modelApiKey,
372
+ modelName: args.modelName,
373
+ };
374
+
375
+ const ownSession = !args.sessionId;
376
+ let sessionId = args.sessionId;
377
+
378
+ if (ownSession) {
379
+ const session = await api.startSession(config);
380
+ sessionId = session.sessionId;
381
+ }
382
+
383
+ try {
384
+ if (ownSession && args.url) {
385
+ await api.navigate(sessionId, args.url, config, {
386
+ waitUntil: args.options?.waitUntil,
387
+ timeout: args.options?.timeout,
388
+ });
389
+ }
390
+
391
+ const result = await api.agentExecute(
392
+ sessionId,
393
+ {
394
+ cua: args.options?.cua,
395
+ systemPrompt: args.options?.systemPrompt,
396
+ },
397
+ {
398
+ instruction: args.instruction,
399
+ maxSteps: args.options?.maxSteps,
400
+ },
401
+ config,
402
+ );
403
+
404
+ if (ownSession) {
405
+ await api.endSession(sessionId, config);
406
+ }
407
+
408
+ return result.result;
409
+ } catch (error) {
410
+ if (ownSession) {
411
+ await api.endSession(sessionId, config);
412
+ }
413
+ throw error;
414
+ }
415
+ },
416
+ });
@@ -0,0 +1,23 @@
1
+ import { defineSchema, defineTable } from "convex/server";
2
+ import { v } from "convex/values";
3
+
4
+ export default defineSchema({
5
+ sessions: defineTable({
6
+ sessionId: v.string(),
7
+ startedAt: v.number(),
8
+ endedAt: v.optional(v.number()),
9
+ status: v.union(
10
+ v.literal("active"),
11
+ v.literal("completed"),
12
+ v.literal("error"),
13
+ ),
14
+ operation: v.union(
15
+ v.literal("extract"),
16
+ v.literal("act"),
17
+ v.literal("observe"),
18
+ v.literal("workflow"),
19
+ ),
20
+ url: v.string(),
21
+ error: v.optional(v.string()),
22
+ }).index("by_sessionId", ["sessionId"]),
23
+ });
@@ -0,0 +1,25 @@
1
+ {
2
+ /* This TypeScript project config describes the environment that
3
+ * Convex functions run in and is used to typecheck them.
4
+ * You can modify it, but some settings are required to use Convex.
5
+ */
6
+ "compilerOptions": {
7
+ /* These settings are not required by Convex and can be modified. */
8
+ "allowJs": true,
9
+ "strict": true,
10
+ "moduleResolution": "Bundler",
11
+ "jsx": "react-jsx",
12
+ "skipLibCheck": true,
13
+ "allowSyntheticDefaultImports": true,
14
+
15
+ /* These compiler options are required by Convex */
16
+ "target": "ESNext",
17
+ "lib": ["ES2021", "dom"],
18
+ "forceConsistentCasingInFileNames": true,
19
+ "module": "ESNext",
20
+ "isolatedModules": true,
21
+ "noEmit": true
22
+ },
23
+ "include": ["./**/*"],
24
+ "exclude": ["./_generated"]
25
+ }