@crawlee/stagehand 3.17.1-beta.9 → 3.18.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/index.mjs CHANGED
@@ -50,6 +50,7 @@ export const RequestQueue = mod.RequestQueue;
50
50
  export const RequestQueueV1 = mod.RequestQueueV1;
51
51
  export const RequestQueueV2 = mod.RequestQueueV2;
52
52
  export const RequestState = mod.RequestState;
53
+ export const RequestValidationError = mod.RequestValidationError;
53
54
  export const RetryRequestError = mod.RetryRequestError;
54
55
  export const Router = mod.Router;
55
56
  export const STATE_PERSISTENCE_KEY = mod.STATE_PERSISTENCE_KEY;
@@ -81,6 +82,7 @@ export const createEventLoopLoadSignal = mod.createEventLoopLoadSignal;
81
82
  export const createRequestOptions = mod.createRequestOptions;
82
83
  export const createRequests = mod.createRequests;
83
84
  export const createStagehandRouter = mod.createStagehandRouter;
85
+ export const defaultRoute = mod.defaultRoute;
84
86
  export const deserializeArray = mod.deserializeArray;
85
87
  export const enqueueLinks = mod.enqueueLinks;
86
88
  export const evaluateLoadSignalSample = mod.evaluateLoadSignalSample;
@@ -103,5 +105,6 @@ export const tryAbsoluteURL = mod.tryAbsoluteURL;
103
105
  export const updateEnqueueLinksPatternCache = mod.updateEnqueueLinksPatternCache;
104
106
  export const useState = mod.useState;
105
107
  export const validateGlobPattern = mod.validateGlobPattern;
108
+ export const validateUserData = mod.validateUserData;
106
109
  export const validators = mod.validators;
107
110
  export const withCheckedStorageAccess = mod.withCheckedStorageAccess;
@@ -30,6 +30,20 @@ export declare class StagehandController extends BrowserController<BrowserType,
30
30
  * We use Playwright's browser API directly since we connected via CDP.
31
31
  */
32
32
  protected _newPage(_contextOptions?: unknown): Promise<Page>;
33
+ /**
34
+ * Waits until Stagehand knows about the page, so that `act()`/`extract()`/`observe()` can resolve it.
35
+ *
36
+ * Stagehand only learns about pages from CDP `Target.attachedToTarget` events on its own connection,
37
+ * and it maps them by main frame id. We create pages through a separate `connectOverCDP()` handle, so
38
+ * `context.newPage()` resolves before Stagehand has processed that event — the page is unresolvable for
39
+ * a short window, and the AI methods fail with 'Failed to resolve V3 Page from Playwright page'.
40
+ * Stagehand's own `newPage()` polls for the same reason.
41
+ */
42
+ private waitForStagehandToRegisterPage;
43
+ /**
44
+ * Reads the page's main frame id, which is the key Stagehand resolves pages by.
45
+ */
46
+ private getMainFrameId;
33
47
  /**
34
48
  * Normalizes proxy options for Playwright.
35
49
  */
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.StagehandController = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const browser_pool_1 = require("@crawlee/browser-pool");
6
+ const utils_1 = require("@crawlee/utils");
6
7
  const log_1 = tslib_1.__importDefault(require("@apify/log"));
7
8
  /**
8
9
  * StagehandController manages the lifecycle of a Stagehand-controlled browser for Crawlee's BrowserPool.
@@ -63,6 +64,13 @@ class StagehandController extends browser_pool_1.BrowserController {
63
64
  page.once('close', () => {
64
65
  this.activePages--;
65
66
  });
67
+ try {
68
+ await this.waitForStagehandToRegisterPage(page);
69
+ }
70
+ catch (error) {
71
+ await page.close().catch(() => { });
72
+ throw error;
73
+ }
66
74
  return page;
67
75
  }
68
76
  catch (error) {
@@ -71,6 +79,42 @@ class StagehandController extends browser_pool_1.BrowserController {
71
79
  });
72
80
  }
73
81
  }
82
+ /**
83
+ * Waits until Stagehand knows about the page, so that `act()`/`extract()`/`observe()` can resolve it.
84
+ *
85
+ * Stagehand only learns about pages from CDP `Target.attachedToTarget` events on its own connection,
86
+ * and it maps them by main frame id. We create pages through a separate `connectOverCDP()` handle, so
87
+ * `context.newPage()` resolves before Stagehand has processed that event — the page is unresolvable for
88
+ * a short window, and the AI methods fail with 'Failed to resolve V3 Page from Playwright page'.
89
+ * Stagehand's own `newPage()` polls for the same reason.
90
+ */
91
+ async waitForStagehandToRegisterPage(page, timeoutMs = 10000) {
92
+ const stagehand = this.getStagehand();
93
+ const mainFrameId = await this.getMainFrameId(page);
94
+ const deadline = Date.now() + timeoutMs;
95
+ while (Date.now() < deadline) {
96
+ if (stagehand.context.resolvePageByMainFrameId(mainFrameId)) {
97
+ return;
98
+ }
99
+ await (0, utils_1.sleep)(25);
100
+ }
101
+ // Stagehand skips registration entirely when it cannot install its helper script into the page's CDP
102
+ // session, so this is not always just a slow attach - it can never resolve.
103
+ throw new Error(`Stagehand did not register the page within ${timeoutMs}ms`);
104
+ }
105
+ /**
106
+ * Reads the page's main frame id, which is the key Stagehand resolves pages by.
107
+ */
108
+ async getMainFrameId(page) {
109
+ const cdpSession = await page.context().newCDPSession(page);
110
+ try {
111
+ const { frameTree } = await cdpSession.send('Page.getFrameTree');
112
+ return frameTree.frame.id;
113
+ }
114
+ finally {
115
+ await cdpSession.detach().catch(() => { });
116
+ }
117
+ }
74
118
  /**
75
119
  * Normalizes proxy options for Playwright.
76
120
  */
@@ -1,5 +1,5 @@
1
1
  import type { Action, ActOptions, ActResult, AgentConfig, ExtractOptions, LLMClient, ModelConfiguration, NonStreamingAgentInstance, ObserveOptions, Stagehand, StreamingAgentInstance } from '@browserbasehq/stagehand';
2
- import type { BrowserCrawlerOptions, BrowserCrawlingContext, BrowserHook, BrowserRequestHandler, GetUserDataFromRequest, LoadedContext, RouterRoutes } from '@crawlee/browser';
2
+ import type { BrowserCrawlerOptions, BrowserCrawlingContext, BrowserHook, BrowserRequestHandler, GetUserDataFromRequest, LoadedContext, RouterHandler, RouterRoutes, RouteSchemas, RoutesFromSchemas } from '@crawlee/browser';
3
3
  import { BrowserCrawler, Configuration } from '@crawlee/browser';
4
4
  import type { Dictionary } from '@crawlee/types';
5
5
  // @ts-ignore optional peer dependency or compatibility with es2022
@@ -408,6 +408,9 @@ export declare class StagehandCrawler extends BrowserCrawler<{
408
408
  // @ts-ignore optional peer dependency or compatibility with es2022
409
409
  statisticsOptions: import("ow").ObjectPredicate<object> & import("ow").BasePredicate<object | undefined>;
410
410
  };
411
+ /** Set while `_runRequestHandler` has swapped `userProvidedRequestHandler` for its page-enhancing wrapper. */
412
+ private unwrappedRequestHandler?;
413
+ protected get userRequestHandler(): BrowserRequestHandler<StagehandCrawlingContext>;
411
414
  /**
412
415
  * Creates a new instance of StagehandCrawler.
413
416
  *
@@ -458,5 +461,6 @@ export declare class StagehandCrawler extends BrowserCrawler<{
458
461
  * });
459
462
  * ```
460
463
  */
461
- // @ts-ignore optional peer dependency or compatibility with es2022
462
- export declare function createStagehandRouter<Context extends StagehandCrawlingContext = StagehandCrawlingContext, UserData extends Dictionary = GetUserDataFromRequest<Context['request']>>(routes?: RouterRoutes<Context, UserData>): import("@crawlee/browser").RouterHandler<Context>;
464
+ export declare function createStagehandRouter<Context extends StagehandCrawlingContext = StagehandCrawlingContext, Routes extends Record<keyof Routes, Dictionary> = Record<string, GetUserDataFromRequest<Context['request']>>>(routes?: RouterRoutes<Context, Routes>): RouterHandler<Context, Routes>;
465
+ export declare function createStagehandRouter<Context extends StagehandCrawlingContext = StagehandCrawlingContext, UserData extends Dictionary = GetUserDataFromRequest<Context['request']>>(routes?: RouterRoutes<Context, Record<string, UserData>>): RouterHandler<Context, Record<string, UserData>>;
466
+ export declare function createStagehandRouter<Context extends StagehandCrawlingContext = StagehandCrawlingContext, const Schemas extends RouteSchemas = RouteSchemas>(schemas: Schemas): RouterHandler<Context, RoutesFromSchemas<Schemas>>;
@@ -57,6 +57,9 @@ const stagehand_utils_1 = require("./utils/stagehand-utils");
57
57
  * ```
58
58
  */
59
59
  class StagehandCrawler extends browser_1.BrowserCrawler {
60
+ get userRequestHandler() {
61
+ return this.unwrappedRequestHandler ?? super.userRequestHandler;
62
+ }
60
63
  /**
61
64
  * Creates a new instance of StagehandCrawler.
62
65
  *
@@ -88,6 +91,13 @@ class StagehandCrawler extends browser_1.BrowserCrawler {
88
91
  writable: true,
89
92
  value: config
90
93
  });
94
+ /** Set while `_runRequestHandler` has swapped `userProvidedRequestHandler` for its page-enhancing wrapper. */
95
+ Object.defineProperty(this, "unwrappedRequestHandler", {
96
+ enumerable: true,
97
+ configurable: true,
98
+ writable: true,
99
+ value: void 0
100
+ });
91
101
  }
92
102
  /**
93
103
  * Overrides the request handler to enhance the page with Stagehand AI methods.
@@ -105,6 +115,9 @@ class StagehandCrawler extends browser_1.BrowserCrawler {
105
115
  async _runRequestHandler(crawlingContext) {
106
116
  // Store the original handler (could be this.requestHandler or this.router)
107
117
  const originalHandler = this.userProvidedRequestHandler;
118
+ // Keep `userRequestHandler` pointing at the unwrapped handler: the swap below is live while the user's
119
+ // handler runs, which is exactly when `context.addRequests`/`enqueueLinks` resolve router metadata.
120
+ this.unwrappedRequestHandler = originalHandler;
108
121
  // Replace with a wrapper that enhances the page before calling the user's handler
109
122
  this.userProvidedRequestHandler = async (ctx) => {
110
123
  // Get Stagehand instance from controller
@@ -122,6 +135,7 @@ class StagehandCrawler extends browser_1.BrowserCrawler {
122
135
  finally {
123
136
  // Restore original handler
124
137
  this.userProvidedRequestHandler = originalHandler;
138
+ this.unwrappedRequestHandler = undefined;
125
139
  }
126
140
  }
127
141
  /**
@@ -144,30 +158,6 @@ Object.defineProperty(StagehandCrawler, "optionsShape", {
144
158
  browserPoolOptions: ow_1.default.optional.object,
145
159
  }
146
160
  });
147
- /**
148
- * Creates a new router for StagehandCrawler with type-safe route handlers.
149
- *
150
- * @param options - Router options
151
- * @returns Configured router instance
152
- *
153
- * @example
154
- * ```typescript
155
- * const router = createStagehandRouter();
156
- *
157
- * router.addHandler('product', async ({ page, request, log }) => {
158
- * log.info(`Processing product: ${request.url}`);
159
- * const data = await page.extract('Get product info', schema);
160
- * });
161
- *
162
- * router.addDefaultHandler(async ({ page, enqueueLinks }) => {
163
- * await enqueueLinks({ globs: ['https://example.com/products/*'] });
164
- * });
165
- *
166
- * const crawler = new StagehandCrawler({
167
- * requestHandler: router,
168
- * });
169
- * ```
170
- */
171
- function createStagehandRouter(routes) {
172
- return browser_1.Router.create(routes);
161
+ function createStagehandRouter(routesOrSchemas) {
162
+ return browser_1.Router.create(routesOrSchemas);
173
163
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crawlee/stagehand",
3
- "version": "3.17.1-beta.9",
3
+ "version": "3.18.0",
4
4
  "description": "AI-powered web crawling with Stagehand integration for Crawlee - enables natural language browser automation with act(), extract(), and observe() methods.",
5
5
  "engines": {
6
6
  "node": ">=16.0.0"
@@ -57,12 +57,12 @@
57
57
  },
58
58
  "dependencies": {
59
59
  "@apify/log": "^2.4.0",
60
- "@apify/timeout": "^0.3.1",
61
- "@crawlee/browser": "3.17.1-beta.9",
62
- "@crawlee/browser-pool": "3.17.1-beta.9",
63
- "@crawlee/core": "3.17.1-beta.9",
64
- "@crawlee/types": "3.17.1-beta.9",
65
- "@crawlee/utils": "3.17.1-beta.9",
60
+ "@apify/timeout": "^0.4.0",
61
+ "@crawlee/browser": "3.18.0",
62
+ "@crawlee/browser-pool": "3.18.0",
63
+ "@crawlee/core": "3.18.0",
64
+ "@crawlee/types": "3.18.0",
65
+ "@crawlee/utils": "3.18.0",
66
66
  "ow": "^0.28.1",
67
67
  "tslib": "^2.4.0"
68
68
  },
@@ -94,5 +94,5 @@
94
94
  }
95
95
  }
96
96
  },
97
- "gitHead": "c547ce4489d8617620a51516cc885187503f3136"
97
+ "gitHead": "49c115e1ce3b3fbf2bef61f16bffeadfdcf5bd19"
98
98
  }