@browserbasehq/stagehand 1.0.3-alpha.2 → 1.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 CHANGED
@@ -101,6 +101,7 @@ import { z } from "zod";
101
101
 
102
102
  const stagehand = new Stagehand({
103
103
  env: "BROWSERBASE",
104
+ enableCaching: true,
104
105
  });
105
106
  ```
106
107
 
@@ -137,6 +138,7 @@ This constructor is used to create an instance of Stagehand.
137
138
  - `2`: LLM-client level logging (most granular)
138
139
  - `debugDom`: a `boolean` that draws bounding boxes around elements presented to the LLM during automation.
139
140
  - `domSettleTimeoutMs`: an `integer` that specifies the timeout in milliseconds for waiting for the DOM to settle. Defaults to 30000 (30 seconds).
141
+ - `enableCaching`: a `boolean` that enables caching of LLM responses. When set to `true`, the LLM requests will be cached on disk and reused for identical requests. Defaults to `false`.
140
142
 
141
143
  - **Returns:**
142
144
 
@@ -278,7 +280,6 @@ Stagehand currently supports the following models from OpenAI and Anthropic:
278
280
 
279
281
  These models can be specified when initializing the `Stagehand` instance or when calling methods like `act()` and `extract()`.
280
282
 
281
-
282
283
  ## How It Works
283
284
 
284
285
  The SDK has two major phases:
@@ -342,12 +343,14 @@ const productInfo = await stagehand.extract({
342
343
  - **Break down complex tasks into smaller, atomic steps**
343
344
 
344
345
  Instead of combining actions:
346
+
345
347
  ```javascript
346
348
  // Avoid this
347
349
  await stagehand.act({ action: "log in and purchase the first item" });
348
350
  ```
349
351
 
350
352
  Split them into individual steps:
353
+
351
354
  ```javascript
352
355
  await stagehand.act({ action: "click the login button" });
353
356
  // ...additional steps to log in...
@@ -385,11 +388,10 @@ await stagehand.act({ action: "fill out the form and submit it" });
385
388
  await stagehand.act({ action: "book the cheapest flight available" });
386
389
  ```
387
390
 
388
- By following these guidelines, you'll increase the reliability and effectiveness of your web automations with Stagehand. Remember, Stagehand excels at executing precise, well-defined actions so keeping your instructions atomic will lead to the best outcomes.
391
+ By following these guidelines, you'll increase the reliability and effectiveness of your web automations with Stagehand. Remember, Stagehand excels at executing precise, well-defined actions so keeping your instructions atomic will lead to the best outcomes.
389
392
 
390
393
  We leave the agentic behaviour to higher-level agentic systems which can use Stagehand as a tool.
391
394
 
392
-
393
395
  ## Roadmap
394
396
 
395
397
  At a high level, we're focused on improving reliability, speed, and cost in that order of priority.
@@ -464,7 +466,7 @@ Stagehand uses [tsup](https://github.com/egoist/tsup) to build the SDK and vanil
464
466
 
465
467
  ## Acknowledgements
466
468
 
467
- This project heavily relies on [Playwright](https://playwright.dev/) as a resilient backbone to automate the web. It also would not be possible without the awesome techniques and discoveries made by [tarsier](https://github.com/reworkd/tarsier), and [fuji-web](https://github.com/fuji-web).
469
+ This project heavily relies on [Playwright](https://playwright.dev/) as a resilient backbone to automate the web. It also would not be possible without the awesome techniques and discoveries made by [tarsier](https://github.com/reworkd/tarsier), and [fuji-web](https://github.com/normal-computing/fuji-web).
468
470
 
469
471
  [Jeremy Press](https://x.com/jeremypress) wrote the original MVP of Stagehand and continues to be a major ally to the project.
470
472
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Page, BrowserContext } from '@playwright/test';
2
2
  import { z } from 'zod';
3
+ import { Browserbase } from '@browserbasehq/sdk';
3
4
 
4
5
  interface ChatMessage {
5
6
  role: "system" | "user" | "assistant";
@@ -40,21 +41,25 @@ type AvailableModel = "gpt-4o" | "gpt-4o-mini" | "gpt-4o-2024-08-06" | "claude-3
40
41
  declare class LLMProvider {
41
42
  private modelToProviderMap;
42
43
  private logger;
44
+ private enableCaching;
45
+ private cache;
43
46
  constructor(logger: (message: {
44
47
  category?: string;
45
48
  message: string;
46
- }) => void);
47
- getClient(modelName: AvailableModel): LLMClient;
49
+ }) => void, enableCaching: boolean);
50
+ cleanRequestCache(requestId: string): void;
51
+ getClient(modelName: AvailableModel, requestId: string): LLMClient;
48
52
  }
49
53
 
50
54
  declare class Stagehand {
51
55
  private llmProvider;
52
56
  private observations;
53
57
  private actions;
54
- private id;
55
58
  page: Page;
56
59
  context: BrowserContext;
57
60
  private env;
61
+ private apiKey;
62
+ private projectId;
58
63
  private verbose;
59
64
  private debugDom;
60
65
  private defaultModelName;
@@ -62,8 +67,13 @@ declare class Stagehand {
62
67
  private logger;
63
68
  private externalLogger?;
64
69
  private domSettleTimeoutMs;
65
- constructor({ env, verbose, debugDom, llmProvider, headless, logger, domSettleTimeoutMs, }?: {
70
+ private browserBaseSessionCreateParams?;
71
+ private enableCaching;
72
+ private browserbaseResumeSessionID?;
73
+ constructor({ env, apiKey, projectId, verbose, debugDom, llmProvider, headless, logger, browserBaseSessionCreateParams, domSettleTimeoutMs, enableCaching, browserbaseResumeSessionID, }?: {
66
74
  env: "LOCAL" | "BROWSERBASE";
75
+ apiKey?: string;
76
+ projectId?: string;
67
77
  verbose?: 0 | 1 | 2;
68
78
  debugDom?: boolean;
69
79
  llmProvider?: LLMProvider;
@@ -74,6 +84,9 @@ declare class Stagehand {
74
84
  level?: 0 | 1 | 2;
75
85
  }) => void;
76
86
  domSettleTimeoutMs?: number;
87
+ browserBaseSessionCreateParams?: Browserbase.Sessions.SessionCreateParams;
88
+ enableCaching?: boolean;
89
+ browserbaseResumeSessionID?: string;
77
90
  });
78
91
  init({ modelName, }?: {
79
92
  modelName?: AvailableModel;
@@ -81,6 +94,9 @@ declare class Stagehand {
81
94
  debugUrl: string;
82
95
  sessionUrl: string;
83
96
  }>;
97
+ initFromPage(page: Page, modelName?: AvailableModel): Promise<{
98
+ context: BrowserContext;
99
+ }>;
84
100
  private pending_logs_to_send_to_browserbase;
85
101
  private is_processing_browserbase_logs;
86
102
  log({ message, category, level, }: {
package/dist/index.js CHANGED
@@ -84,6 +84,7 @@ module.exports = __toCommonJS(lib_exports);
84
84
  var import_test = require("@playwright/test");
85
85
  var import_crypto = __toESM(require("crypto"));
86
86
  var import_fs2 = __toESM(require("fs"));
87
+ var import_sdk2 = require("@browserbasehq/sdk");
87
88
 
88
89
  // lib/prompt.ts
89
90
  var actSystemPrompt = `
@@ -283,8 +284,10 @@ var metadataSystemPrompt = `You are an AI assistant tasked with evaluating the p
283
284
  Analyze the extraction response and determine if the task is completed or if more information is needed.
284
285
 
285
286
  Strictly abide by the following criteria:
286
- 1. If you are certain that the instruction is completed, set the completion status to true, even if there are still chunks left.
287
- 2. If there could still be more information to extract and there are still chunks left, set the completion status to false.`;
287
+ 1. Once the instruction has been satisfied by the current extraction response, ALWAYS set completion status to true and stop processing, regardless of remaining chunks.
288
+ 2. Only set completion status to false if BOTH of these conditions are true:
289
+ - The instruction has not been satisfied yet
290
+ - There are still chunks left to process (chunksTotal > chunksSeen)`;
288
291
  function buildMetadataSystemPrompt() {
289
292
  return {
290
293
  role: "system",
@@ -296,8 +299,8 @@ function buildMetadataPrompt(instruction, extractionResponse, chunksSeen, chunks
296
299
  role: "user",
297
300
  content: `Instruction: ${instruction}
298
301
  Extracted content: ${JSON.stringify(extractionResponse, null, 2)}
299
- Chunks seen: ${chunksSeen}
300
- Chunks total: ${chunksTotal}`
302
+ chunksSeen: ${chunksSeen}
303
+ chunksTotal: ${chunksTotal}`
301
304
  };
302
305
  }
303
306
  var observeSystemPrompt = `
@@ -332,6 +335,7 @@ var modelsWithVision = [
332
335
  "gpt-4o-mini",
333
336
  "claude-3-5-sonnet-latest",
334
337
  "claude-3-5-sonnet-20240620",
338
+ "claude-3-5-sonnet-20241022",
335
339
  "gpt-4o-2024-08-06"
336
340
  ];
337
341
  var AnnotatedScreenshotText = "This is a screenshot of the current page state with the elements annotated on it. Each element id is annotated with a number to the top left of it. Duplicate annotations at the same location are under each other vertically.";
@@ -345,9 +349,10 @@ function verifyActCompletion(_0) {
345
349
  modelName,
346
350
  screenshot,
347
351
  domElements,
348
- logger
352
+ logger,
353
+ requestId
349
354
  }) {
350
- const llmClient = llmProvider.getClient(modelName);
355
+ const llmClient = llmProvider.getClient(modelName, requestId);
351
356
  const messages = [
352
357
  buildVerifyActCompletionSystemPrompt(),
353
358
  buildVerifyActCompletionUserPrompt(goal, steps, domElements)
@@ -396,9 +401,10 @@ function act(_0) {
396
401
  modelName,
397
402
  screenshot,
398
403
  retries = 0,
399
- logger
404
+ logger,
405
+ requestId
400
406
  }) {
401
- const llmClient = llmProvider.getClient(modelName);
407
+ const llmClient = llmProvider.getClient(modelName, requestId);
402
408
  const messages = [
403
409
  buildActSystemPrompt(),
404
410
  buildActUserPrompt(action, steps, domElements)
@@ -435,7 +441,8 @@ function act(_0) {
435
441
  llmProvider,
436
442
  modelName,
437
443
  retries: retries + 1,
438
- logger
444
+ logger,
445
+ requestId
439
446
  });
440
447
  }
441
448
  });
@@ -450,9 +457,10 @@ function extract(_0) {
450
457
  llmProvider,
451
458
  modelName,
452
459
  chunksSeen,
453
- chunksTotal
460
+ chunksTotal,
461
+ requestId
454
462
  }) {
455
- const llmClient = llmProvider.getClient(modelName);
463
+ const llmClient = llmProvider.getClient(modelName, requestId);
456
464
  const extractionResponse = yield llmClient.createChatCompletion({
457
465
  model: modelName,
458
466
  messages: [
@@ -525,7 +533,8 @@ function observe(_0) {
525
533
  domElements,
526
534
  llmProvider,
527
535
  modelName,
528
- image
536
+ image,
537
+ requestId
529
538
  }) {
530
539
  const observeSchema = import_zod.z.object({
531
540
  elements: import_zod.z.array(
@@ -537,7 +546,7 @@ function observe(_0) {
537
546
  })
538
547
  ).describe("an array of elements that match the instruction")
539
548
  });
540
- const llmClient = llmProvider.getClient(modelName);
549
+ const llmClient = llmProvider.getClient(modelName, requestId);
541
550
  const observationResponse = yield llmClient.createChatCompletion({
542
551
  model: modelName,
543
552
  messages: [
@@ -565,12 +574,31 @@ function observe(_0) {
565
574
  var import_openai = __toESM(require("openai"));
566
575
  var import_zod2 = require("openai/helpers/zod");
567
576
  var OpenAIClient = class {
568
- constructor(logger) {
577
+ constructor(logger, enableCaching = false, cache, requestId) {
569
578
  this.client = new import_openai.default();
570
579
  this.logger = logger;
580
+ this.requestId = requestId;
581
+ this.cache = cache;
582
+ this.enableCaching = enableCaching;
571
583
  }
572
584
  createChatCompletion(options) {
573
585
  return __async(this, null, function* () {
586
+ const cacheOptions = {
587
+ model: options.model,
588
+ messages: options.messages,
589
+ temperature: options.temperature,
590
+ top_p: options.top_p,
591
+ frequency_penalty: options.frequency_penalty,
592
+ presence_penalty: options.presence_penalty,
593
+ image: options.image,
594
+ response_model: options.response_model
595
+ };
596
+ if (this.enableCaching) {
597
+ const cachedResponse = yield this.cache.get(cacheOptions, this.requestId);
598
+ if (cachedResponse) {
599
+ return cachedResponse;
600
+ }
601
+ }
574
602
  if (options.image) {
575
603
  const screenshotMessage = {
576
604
  role: "user",
@@ -600,8 +628,18 @@ var OpenAIClient = class {
600
628
  if (response_model) {
601
629
  const extractedData = response.choices[0].message.content;
602
630
  const parsedData = JSON.parse(extractedData);
631
+ if (this.enableCaching) {
632
+ this.cache.set(
633
+ cacheOptions,
634
+ __spreadValues({}, parsedData),
635
+ this.requestId
636
+ );
637
+ }
603
638
  return __spreadValues({}, parsedData);
604
639
  }
640
+ if (this.enableCaching) {
641
+ this.cache.set(cacheOptions, response, this.requestId);
642
+ }
605
643
  return response;
606
644
  });
607
645
  }
@@ -611,16 +649,33 @@ var OpenAIClient = class {
611
649
  var import_sdk = __toESM(require("@anthropic-ai/sdk"));
612
650
  var import_zod_to_json_schema = require("zod-to-json-schema");
613
651
  var AnthropicClient = class {
614
- constructor(logger) {
652
+ constructor(logger, enableCaching = false, cache, requestId) {
615
653
  this.client = new import_sdk.default({
616
654
  apiKey: process.env.ANTHROPIC_API_KEY
617
- // Make sure to set this environment variable
618
655
  });
619
656
  this.logger = logger;
657
+ this.cache = cache;
658
+ this.enableCaching = enableCaching;
659
+ this.requestId = requestId;
620
660
  }
621
661
  createChatCompletion(options) {
622
662
  return __async(this, null, function* () {
623
663
  var _a, _b, _c, _d, _e, _f, _g;
664
+ const cacheOptions = {
665
+ model: options.model,
666
+ messages: options.messages,
667
+ temperature: options.temperature,
668
+ image: options.image,
669
+ response_model: options.response_model,
670
+ tools: options.tools,
671
+ retries: options.retries
672
+ };
673
+ if (this.enableCaching) {
674
+ const cachedResponse = yield this.cache.get(cacheOptions, this.requestId);
675
+ if (cachedResponse) {
676
+ return cachedResponse;
677
+ }
678
+ }
624
679
  const systemMessage = options.messages.find((msg) => msg.role === "system");
625
680
  const userMessages = options.messages.filter(
626
681
  (msg) => msg.role !== "system"
@@ -722,26 +777,318 @@ var AnthropicClient = class {
722
777
  if (options.response_model) {
723
778
  const toolUse = response.content.find((c) => c.type === "tool_use");
724
779
  if (toolUse && "input" in toolUse) {
725
- return toolUse.input;
780
+ const result = toolUse.input;
781
+ if (this.enableCaching) {
782
+ this.cache.set(cacheOptions, result, this.requestId);
783
+ }
784
+ return result;
726
785
  } else {
727
- if (!options.retries || options.retries < 2) {
786
+ if (!options.retries || options.retries < 5) {
728
787
  return this.createChatCompletion(__spreadProps(__spreadValues({}, options), {
729
788
  retries: ((_g = options.retries) != null ? _g : 0) + 1
730
789
  }));
731
790
  }
732
791
  throw new Error(
733
- "Extraction failed: No tool use with input in response"
792
+ "Create Chat Completion Failed: No tool use with input in response"
734
793
  );
735
794
  }
736
795
  }
796
+ if (this.enableCaching) {
797
+ this.cache.set(cacheOptions, transformedResponse, this.requestId);
798
+ }
737
799
  return transformedResponse;
738
800
  });
739
801
  }
740
802
  };
741
803
 
804
+ // lib/llm/LLMCache.ts
805
+ var fs = __toESM(require("fs"));
806
+ var path = __toESM(require("path"));
807
+ var crypto = __toESM(require("crypto"));
808
+ var LLMCache = class {
809
+ constructor(logger, cacheDir = path.join(process.cwd(), "tmp", ".cache"), cacheFile = "llm_calls.json") {
810
+ this.CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
811
+ // 1 week in milliseconds
812
+ this.CLEANUP_PROBABILITY = 0.01;
813
+ // 1% chance
814
+ this.LOCK_TIMEOUT_MS = 1e3;
815
+ this.lock_acquired = false;
816
+ this.count_lock_acquire_failures = 0;
817
+ this.request_id_to_used_hashes = {};
818
+ this.logger = logger;
819
+ this.cacheDir = cacheDir;
820
+ this.cacheFile = path.join(cacheDir, cacheFile);
821
+ this.lockFile = path.join(cacheDir, "llm_cache.lock");
822
+ this.ensureCacheDirectory();
823
+ this.setupProcessHandlers();
824
+ }
825
+ setupProcessHandlers() {
826
+ const releaseLockAndExit = () => {
827
+ this.releaseLock();
828
+ process.exit();
829
+ };
830
+ process.on("exit", releaseLockAndExit);
831
+ process.on("SIGINT", releaseLockAndExit);
832
+ process.on("SIGTERM", releaseLockAndExit);
833
+ process.on("uncaughtException", (err) => {
834
+ this.logger({
835
+ category: "llm_cache",
836
+ message: `Uncaught exception: ${err}`,
837
+ level: 2
838
+ });
839
+ if (this.lock_acquired) {
840
+ releaseLockAndExit();
841
+ }
842
+ });
843
+ }
844
+ ensureCacheDirectory() {
845
+ if (!fs.existsSync(this.cacheDir)) {
846
+ fs.mkdirSync(this.cacheDir, { recursive: true });
847
+ }
848
+ }
849
+ createHash(data) {
850
+ const hash = crypto.createHash("sha256");
851
+ return hash.update(JSON.stringify(data)).digest("hex");
852
+ }
853
+ sleep(ms) {
854
+ return new Promise((resolve) => setTimeout(resolve, ms));
855
+ }
856
+ acquireLock() {
857
+ return __async(this, null, function* () {
858
+ const startTime = Date.now();
859
+ while (Date.now() - startTime < this.LOCK_TIMEOUT_MS) {
860
+ try {
861
+ if (fs.existsSync(this.lockFile)) {
862
+ const lockAge = Date.now() - fs.statSync(this.lockFile).mtimeMs;
863
+ if (lockAge > this.LOCK_TIMEOUT_MS) {
864
+ fs.unlinkSync(this.lockFile);
865
+ }
866
+ }
867
+ fs.writeFileSync(this.lockFile, process.pid.toString(), { flag: "wx" });
868
+ this.count_lock_acquire_failures = 0;
869
+ this.lock_acquired = true;
870
+ return true;
871
+ } catch (error) {
872
+ yield this.sleep(5);
873
+ }
874
+ }
875
+ this.logger({
876
+ category: "llm_cache",
877
+ message: "Failed to acquire lock after timeout",
878
+ level: 2
879
+ });
880
+ this.count_lock_acquire_failures++;
881
+ if (this.count_lock_acquire_failures >= 3) {
882
+ this.logger({
883
+ category: "llm_cache",
884
+ message: "Failed to acquire lock 3 times in a row. Releasing lock manually.",
885
+ level: 1
886
+ });
887
+ this.releaseLock();
888
+ }
889
+ return false;
890
+ });
891
+ }
892
+ releaseLock() {
893
+ try {
894
+ if (fs.existsSync(this.lockFile)) {
895
+ fs.unlinkSync(this.lockFile);
896
+ }
897
+ this.lock_acquired = false;
898
+ } catch (error) {
899
+ this.logger({
900
+ category: "llm_cache",
901
+ message: `Error releasing lock: ${error}`,
902
+ level: 2
903
+ });
904
+ }
905
+ }
906
+ readCache() {
907
+ if (fs.existsSync(this.cacheFile)) {
908
+ return JSON.parse(fs.readFileSync(this.cacheFile, "utf-8"));
909
+ }
910
+ return {};
911
+ }
912
+ writeCache(cache) {
913
+ try {
914
+ if (Math.random() < this.CLEANUP_PROBABILITY) {
915
+ this.cleanupStaleEntries(cache);
916
+ }
917
+ fs.writeFileSync(this.cacheFile, JSON.stringify(cache, null, 2));
918
+ } finally {
919
+ this.releaseLock();
920
+ }
921
+ }
922
+ cleanupStaleEntries(cache) {
923
+ if (!this.acquireLock()) {
924
+ this.logger({
925
+ category: "llm_cache",
926
+ message: "Failed to acquire lock for cleaning up cache",
927
+ level: 2
928
+ });
929
+ return;
930
+ }
931
+ try {
932
+ const now = Date.now();
933
+ let entriesRemoved = 0;
934
+ for (const [hash, entry] of Object.entries(cache)) {
935
+ if (now - entry.timestamp > this.CACHE_MAX_AGE_MS) {
936
+ delete cache[hash];
937
+ entriesRemoved++;
938
+ }
939
+ }
940
+ if (entriesRemoved > 0) {
941
+ this.logger({
942
+ category: "llm_cache",
943
+ message: `Cleaned up ${entriesRemoved} stale cache entries`,
944
+ level: 1
945
+ });
946
+ }
947
+ } catch (error) {
948
+ this.logger({
949
+ category: "llm_cache",
950
+ message: `Error cleaning up stale cache entries: ${error}`,
951
+ level: 1
952
+ });
953
+ } finally {
954
+ this.releaseLock();
955
+ }
956
+ }
957
+ resetCache() {
958
+ if (!this.acquireLock()) {
959
+ this.logger({
960
+ category: "llm_cache",
961
+ message: "Failed to acquire lock for resetting cache",
962
+ level: 2
963
+ });
964
+ return;
965
+ }
966
+ try {
967
+ this.ensureCacheDirectory();
968
+ fs.writeFileSync(this.cacheFile, "{}");
969
+ } finally {
970
+ this.releaseLock();
971
+ }
972
+ }
973
+ get(options, requestId) {
974
+ return __async(this, null, function* () {
975
+ var _a, _b;
976
+ if (!(yield this.acquireLock())) {
977
+ this.logger({
978
+ category: "llm_cache",
979
+ message: "Failed to acquire lock for getting cache",
980
+ level: 2
981
+ });
982
+ return null;
983
+ }
984
+ try {
985
+ const hash = this.createHash(options);
986
+ const cache = this.readCache();
987
+ if (cache[hash]) {
988
+ this.logger({
989
+ category: "llm_cache",
990
+ message: "Cache hit",
991
+ level: 1
992
+ });
993
+ (_b = (_a = this.request_id_to_used_hashes)[requestId]) != null ? _b : _a[requestId] = [];
994
+ this.request_id_to_used_hashes[requestId].push(hash);
995
+ return cache[hash].response;
996
+ }
997
+ return null;
998
+ } catch (error) {
999
+ this.logger({
1000
+ category: "llm_cache",
1001
+ message: `Error getting cache: ${error}. Resetting cache.`,
1002
+ level: 1
1003
+ });
1004
+ this.resetCache();
1005
+ return null;
1006
+ } finally {
1007
+ this.releaseLock();
1008
+ }
1009
+ });
1010
+ }
1011
+ deleteCacheForRequestId(requestId) {
1012
+ return __async(this, null, function* () {
1013
+ var _a;
1014
+ if (!(yield this.acquireLock())) {
1015
+ this.logger({
1016
+ category: "llm_cache",
1017
+ message: "Failed to acquire lock for deleting cache",
1018
+ level: 2
1019
+ });
1020
+ return;
1021
+ }
1022
+ try {
1023
+ const cache = this.readCache();
1024
+ let entriesRemoved = [];
1025
+ for (const hash of (_a = this.request_id_to_used_hashes[requestId]) != null ? _a : []) {
1026
+ if (cache[hash]) {
1027
+ entriesRemoved.push(cache[hash]);
1028
+ delete cache[hash];
1029
+ }
1030
+ }
1031
+ this.logger({
1032
+ category: "llm_cache",
1033
+ message: `Deleted ${entriesRemoved.length} cache entries for requestId ${requestId}`,
1034
+ level: 1
1035
+ });
1036
+ this.writeCache(cache);
1037
+ } catch (exception) {
1038
+ this.logger({
1039
+ category: "llm_cache",
1040
+ message: `Error deleting cache for requestId ${requestId}: ${exception}`,
1041
+ level: 1
1042
+ });
1043
+ } finally {
1044
+ this.releaseLock();
1045
+ }
1046
+ });
1047
+ }
1048
+ set(options, response, requestId) {
1049
+ return __async(this, null, function* () {
1050
+ var _a, _b;
1051
+ if (!(yield this.acquireLock())) {
1052
+ this.logger({
1053
+ category: "llm_cache",
1054
+ message: "Failed to acquire lock for setting cache",
1055
+ level: 2
1056
+ });
1057
+ return;
1058
+ }
1059
+ try {
1060
+ const hash = this.createHash(options);
1061
+ const cache = this.readCache();
1062
+ cache[hash] = {
1063
+ response,
1064
+ timestamp: Date.now(),
1065
+ requestId
1066
+ };
1067
+ this.writeCache(cache);
1068
+ (_b = (_a = this.request_id_to_used_hashes)[requestId]) != null ? _b : _a[requestId] = [];
1069
+ this.request_id_to_used_hashes[requestId].push(hash);
1070
+ this.logger({
1071
+ category: "llm_cache",
1072
+ message: "Cache miss - saved new response",
1073
+ level: 1
1074
+ });
1075
+ } catch (error) {
1076
+ this.logger({
1077
+ category: "llm_cache",
1078
+ message: `Error setting cache: ${error}. Resetting cache.`,
1079
+ level: 1
1080
+ });
1081
+ this.resetCache();
1082
+ } finally {
1083
+ this.releaseLock();
1084
+ }
1085
+ });
1086
+ }
1087
+ };
1088
+
742
1089
  // lib/llm/LLMProvider.ts
743
1090
  var LLMProvider = class {
744
- constructor(logger) {
1091
+ constructor(logger, enableCaching) {
745
1092
  this.modelToProviderMap = {
746
1093
  "gpt-4o": "openai",
747
1094
  "gpt-4o-mini": "openai",
@@ -751,71 +1098,44 @@ var LLMProvider = class {
751
1098
  "claude-3-5-sonnet-20241022": "anthropic"
752
1099
  };
753
1100
  this.logger = logger;
1101
+ this.enableCaching = enableCaching;
1102
+ this.cache = new LLMCache(logger);
1103
+ }
1104
+ cleanRequestCache(requestId) {
1105
+ this.logger({
1106
+ category: "llm_cache",
1107
+ message: `Cleaning up cache for requestId: ${requestId}`
1108
+ });
1109
+ this.cache.deleteCacheForRequestId(requestId);
754
1110
  }
755
- getClient(modelName) {
1111
+ getClient(modelName, requestId) {
756
1112
  const provider = this.modelToProviderMap[modelName];
757
1113
  if (!provider) {
758
1114
  throw new Error(`Unsupported model: ${modelName}`);
759
1115
  }
760
1116
  switch (provider) {
761
1117
  case "openai":
762
- return new OpenAIClient(this.logger);
1118
+ return new OpenAIClient(
1119
+ this.logger,
1120
+ this.enableCaching,
1121
+ this.cache,
1122
+ requestId
1123
+ );
763
1124
  case "anthropic":
764
- return new AnthropicClient(this.logger);
1125
+ return new AnthropicClient(
1126
+ this.logger,
1127
+ this.enableCaching,
1128
+ this.cache,
1129
+ requestId
1130
+ );
765
1131
  default:
766
1132
  throw new Error(`Unsupported provider: ${provider}`);
767
1133
  }
768
1134
  }
769
1135
  };
770
1136
 
771
- // lib/browserbase.ts
772
- var Browserbase = class {
773
- createSession() {
774
- return __async(this, null, function* () {
775
- if (!process.env.BROWSERBASE_API_KEY || !process.env.BROWSERBASE_PROJECT_ID) {
776
- throw new Error(
777
- "BROWSERBASE_API_KEY and BROWSERBASE_PROJECT_ID must be set"
778
- );
779
- }
780
- const response = yield fetch(`https://www.browserbase.com/v1/sessions`, {
781
- method: "POST",
782
- headers: {
783
- "x-bb-api-key": `${process.env.BROWSERBASE_API_KEY}`,
784
- "Content-Type": "application/json"
785
- },
786
- body: JSON.stringify({
787
- projectId: process.env.BROWSERBASE_PROJECT_ID
788
- })
789
- });
790
- const json = yield response.json();
791
- if (json.error) {
792
- throw new Error(json.error);
793
- }
794
- return {
795
- sessionId: json.id,
796
- connectUrl: json.connectUrl
797
- };
798
- });
799
- }
800
- retrieveDebugConnectionURL(sessionId) {
801
- return __async(this, null, function* () {
802
- if (!process.env.BROWSERBASE_API_KEY) {
803
- throw new Error("BROWSERBASE_API_KEY must be set");
804
- }
805
- const response = yield fetch(
806
- `https://www.browserbase.com/v1/sessions/${sessionId}/debug`,
807
- {
808
- method: "GET",
809
- headers: {
810
- "x-bb-api-key": `${process.env.BROWSERBASE_API_KEY}`
811
- }
812
- }
813
- );
814
- const json = yield response.json();
815
- return json.debuggerFullscreenUrl;
816
- });
817
- }
818
- };
1137
+ // lib/index.ts
1138
+ var import_path2 = __toESM(require("path"));
819
1139
 
820
1140
  // lib/vision.ts
821
1141
  var import_fs = __toESM(require("fs"));
@@ -999,40 +1319,85 @@ var ScreenshotService = class _ScreenshotService {
999
1319
 
1000
1320
  // lib/index.ts
1001
1321
  require("dotenv").config({ path: ".env" });
1002
- function getBrowser(env = "LOCAL", headless = false, logger) {
1322
+ function getBrowser(apiKey, projectId, env = "LOCAL", headless = false, logger, browserbaseSessionCreateParams, browserbaseResumeSessionID) {
1003
1323
  return __async(this, null, function* () {
1004
- if (env === "BROWSERBASE" && !process.env.BROWSERBASE_API_KEY) {
1005
- logger({
1006
- category: "Init",
1007
- message: "BROWSERBASE_API_KEY is required to use BROWSERBASE env. Defaulting to LOCAL.",
1008
- level: 0
1009
- });
1010
- env = "LOCAL";
1011
- }
1012
- if (env === "BROWSERBASE" && !process.env.BROWSERBASE_PROJECT_ID) {
1013
- logger({
1014
- category: "Init",
1015
- message: "BROWSERBASE_PROJECT_ID is required to use BROWSERBASE env. Defaulting to LOCAL.",
1016
- level: 0
1017
- });
1018
- env = "LOCAL";
1324
+ if (env === "BROWSERBASE") {
1325
+ if (!apiKey) {
1326
+ logger({
1327
+ category: "Init",
1328
+ message: "BROWSERBASE_API_KEY is required to use BROWSERBASE env. Defaulting to LOCAL.",
1329
+ level: 0
1330
+ });
1331
+ env = "LOCAL";
1332
+ }
1333
+ if (!projectId) {
1334
+ logger({
1335
+ category: "Init",
1336
+ message: "BROWSERBASE_PROJECT_ID is required for some Browserbase features that may not work without it.",
1337
+ level: 1
1338
+ });
1339
+ }
1019
1340
  }
1020
1341
  if (env === "BROWSERBASE") {
1342
+ if (!apiKey) {
1343
+ throw new Error("BROWSERBASE_API_KEY is required.");
1344
+ }
1021
1345
  let debugUrl = void 0;
1022
1346
  let sessionUrl = void 0;
1023
- logger({
1024
- category: "Init",
1025
- message: "Connecting you to Browserbase...",
1026
- level: 0
1347
+ let sessionId;
1348
+ let connectUrl;
1349
+ const browserbase = new import_sdk2.Browserbase({
1350
+ apiKey
1027
1351
  });
1028
- const browserbase = new Browserbase();
1029
- const { sessionId, connectUrl } = yield browserbase.createSession();
1352
+ if (browserbaseResumeSessionID) {
1353
+ try {
1354
+ const sessionStatus = yield browserbase.sessions.retrieve(
1355
+ browserbaseResumeSessionID
1356
+ );
1357
+ if (sessionStatus.status !== "RUNNING") {
1358
+ throw new Error(
1359
+ `Session ${browserbaseResumeSessionID} is not running (status: ${sessionStatus.status})`
1360
+ );
1361
+ }
1362
+ sessionId = browserbaseResumeSessionID;
1363
+ connectUrl = `wss://connect.browserbase.com?apiKey=${apiKey}&sessionId=${sessionId}`;
1364
+ logger({
1365
+ category: "Init",
1366
+ message: "Resuming existing Browserbase session...",
1367
+ level: 0
1368
+ });
1369
+ } catch (error) {
1370
+ logger({
1371
+ category: "Init",
1372
+ message: `Failed to resume session ${browserbaseResumeSessionID}: ${error.message}`,
1373
+ level: 0
1374
+ });
1375
+ throw error;
1376
+ }
1377
+ } else {
1378
+ logger({
1379
+ category: "Init",
1380
+ message: "Creating new Browserbase session...",
1381
+ level: 0
1382
+ });
1383
+ if (!projectId) {
1384
+ throw new Error(
1385
+ "BROWSERBASE_PROJECT_ID is required for new Browserbase sessions."
1386
+ );
1387
+ }
1388
+ const session = yield browserbase.sessions.create(__spreadValues({
1389
+ projectId
1390
+ }, browserbaseSessionCreateParams));
1391
+ sessionId = session.id;
1392
+ connectUrl = session.connectUrl;
1393
+ }
1030
1394
  const browser = yield import_test.chromium.connectOverCDP(connectUrl);
1031
- debugUrl = yield browserbase.retrieveDebugConnectionURL(sessionId);
1395
+ const { debuggerUrl } = yield browserbase.sessions.debug(sessionId);
1396
+ debugUrl = debuggerUrl;
1032
1397
  sessionUrl = `https://www.browserbase.com/sessions/${sessionId}`;
1033
1398
  logger({
1034
1399
  category: "Init",
1035
- message: `Browserbase session started.
1400
+ message: `Browserbase session ${browserbaseResumeSessionID ? "resumed" : "started"}.
1036
1401
 
1037
1402
  Session Url: ${sessionUrl}
1038
1403
 
@@ -1119,12 +1484,17 @@ function applyStealthScripts(context) {
1119
1484
  var Stagehand = class {
1120
1485
  constructor({
1121
1486
  env,
1122
- verbose = 0,
1123
- debugDom = false,
1487
+ apiKey,
1488
+ projectId,
1489
+ verbose,
1490
+ debugDom,
1124
1491
  llmProvider,
1125
- headless = false,
1492
+ headless,
1126
1493
  logger,
1127
- domSettleTimeoutMs = 6e4
1494
+ browserBaseSessionCreateParams,
1495
+ domSettleTimeoutMs,
1496
+ enableCaching,
1497
+ browserbaseResumeSessionID
1128
1498
  } = {
1129
1499
  env: "BROWSERBASE"
1130
1500
  }) {
@@ -1133,24 +1503,33 @@ var Stagehand = class {
1133
1503
  this.is_processing_browserbase_logs = false;
1134
1504
  this.externalLogger = logger;
1135
1505
  this.logger = this.log.bind(this);
1136
- this.llmProvider = llmProvider || new LLMProvider(this.logger);
1506
+ this.enableCaching = enableCaching != null ? enableCaching : false;
1507
+ this.llmProvider = llmProvider || new LLMProvider(this.logger, this.enableCaching);
1137
1508
  this.env = env;
1138
1509
  this.observations = {};
1510
+ this.apiKey = apiKey;
1511
+ this.projectId = projectId;
1139
1512
  this.actions = {};
1140
- this.verbose = verbose;
1141
- this.debugDom = debugDom;
1513
+ this.verbose = verbose != null ? verbose : 0;
1514
+ this.debugDom = debugDom != null ? debugDom : false;
1142
1515
  this.defaultModelName = "gpt-4o";
1143
- this.headless = headless;
1144
- this.domSettleTimeoutMs = domSettleTimeoutMs;
1516
+ this.domSettleTimeoutMs = domSettleTimeoutMs != null ? domSettleTimeoutMs : 6e4;
1517
+ this.headless = headless != null ? headless : false;
1518
+ this.browserBaseSessionCreateParams = browserBaseSessionCreateParams;
1519
+ this.browserbaseResumeSessionID = browserbaseResumeSessionID;
1145
1520
  }
1146
1521
  init() {
1147
1522
  return __async(this, arguments, function* ({
1148
1523
  modelName = "gpt-4o"
1149
1524
  } = {}) {
1150
1525
  const { context, debugUrl, sessionUrl } = yield getBrowser(
1526
+ this.apiKey,
1527
+ this.projectId,
1151
1528
  this.env,
1152
1529
  this.headless,
1153
- this.logger
1530
+ this.logger,
1531
+ this.browserBaseSessionCreateParams,
1532
+ this.browserbaseResumeSessionID
1154
1533
  ).catch((e) => {
1155
1534
  console.error("Error in init:", e);
1156
1535
  return { context: void 0, debugUrl: void 0, sessionUrl: void 0 };
@@ -1169,11 +1548,44 @@ var Stagehand = class {
1169
1548
  yield this.page.setViewportSize({ width: 1280, height: 720 });
1170
1549
  }
1171
1550
  yield this.page.addInitScript({
1172
- content: '(()=>{var B=Object.defineProperty;var M=Object.getOwnPropertySymbols;var I=Object.prototype.hasOwnProperty,V=Object.prototype.propertyIsEnumerable;var $=(e,t,o)=>t in e?B(e,t,{enumerable:!0,configurable:!0,writable:!0,value:o}):e[t]=o,x=(e,t)=>{for(var o in t||(t={}))I.call(t,o)&&$(e,o,t[o]);if(M)for(var o of M(t))V.call(t,o)&&$(e,o,t[o]);return e};var d=(e,t,o)=>new Promise((n,s)=>{var c=a=>{try{l(o.next(a))}catch(u){s(u)}},i=a=>{try{l(o.throw(a))}catch(u){s(u)}},l=a=>a.done?n(a.value):Promise.resolve(a.value).then(c,i);l((o=o.apply(e,t)).next())});function _(e){return d(this,null,function*(){let{chunk:t,chunksArray:o}=yield Q(e),{outputString:n,selectorMap:s}=yield C(t);return console.log(`Stagehand (Browser Process): Extracted dom elements:${n}`),{outputString:n,selectorMap:s,chunk:t,chunks:o}})}function F(){return d(this,null,function*(){console.log("Stagehand (Browser Process): Processing all of DOM");let e=window.innerHeight,t=document.documentElement.scrollHeight,o=Math.ceil(t/e),n=0,s=[];for(let l=0;l<o;l++){let a=yield C(l,!0,n);s.push(a),n+=Object.keys(a.selectorMap).length}yield k(0);let c=s.map(l=>l.outputString).join(""),i=s.reduce((l,a)=>x(x({},l),a.selectorMap),{});return console.log(`Stagehand (Browser Process): All dom elements: ${c}`),{outputString:c,selectorMap:i}})}function k(e){return d(this,null,function*(){window.scrollTo({top:e,left:0,behavior:"smooth"}),yield new Promise(t=>{let o,n=()=>{clearTimeout(o),o=window.setTimeout(()=>{window.removeEventListener("scroll",n),t()},200)};window.addEventListener("scroll",n,{passive:!0}),n()})})}function C(e,t=!0,o=0){return d(this,null,function*(){console.time("processElements:total");let s=window.innerHeight*e,c=document.documentElement.scrollHeight-window.innerHeight,i=Math.min(s,c);t&&(console.time("processElements:scroll"),yield k(i),console.timeEnd("processElements:scroll"));let l=[],a=[...document.body.childNodes],u=new Map;for(console.log("Stagehand (Browser Process): Generating candidate elements"),console.time("processElements:findCandidates");a.length>0;){let r=a.pop(),h=!1;if(r&&b(r)){let p=r.childNodes.length;for(let w=p-1;w>=0;w--){let E=r.childNodes[w];a.push(E)}J(r)&&D(r)&&H(r)&&(h=!0),K(r)&&D(r)&&H(r)&&(h=!0)}r&&y(r)&&G(r)&&(h=!0),h&&l.push(r)}console.timeEnd("processElements:findCandidates");let m={},g="";return console.log(`Stagehand (Browser Process): Processing candidate elements: ${l.length}`),console.time("processElements:processCandidates"),l.forEach((r,h)=>{var w,E;let p=u.get(r);if(p||(p=j(r),u.set(r,p)),y(r)){let f=(w=r.textContent)==null?void 0:w.trim();f&&(g+=`${h+o}:${f}`)}else if(b(r)){let f=r.tagName.toLowerCase(),A=X(r),L=`<${f}${A?" "+A:""}>`,P=`</${f}>`,O=((E=r.textContent)==null?void 0:E.trim())||"";g+=`${h+o}:${L}${O}${P}`}m[h+o]=p}),console.timeEnd("processElements:processCandidates"),console.timeEnd("processElements:total"),{outputString:g,selectorMap:m}})}function X(e){let o=["id","class","href","src","aria-label","aria-name","aria-role","aria-description","aria-expanded","aria-haspopup"].map(n=>{let s=e.getAttribute(n);return s?`${n}="${s}"`:""}).filter(n=>n!=="");return Array.from(e.attributes).forEach(n=>{n.name.startsWith("data-")&&o.push(`${n.name}="${n.value}"`)}),o.join(" ")}window.processDom=_;window.processAllOfDom=F;window.processElements=C;window.scrollToHeight=k;function j(e){if(b(e)&&e.id)return`//*[@id="${e.id}"]`;let t=[];for(;e&&(y(e)||b(e));){let o=0,n=!1,s=e.parentElement?Array.from(e.parentElement.childNodes):[];for(let c=0;c<s.length;c++){let i=s[c];if(i.nodeType===e.nodeType&&i.nodeName===e.nodeName&&(o=o+1,n=!0,i.isSameNode(e)))break}if(e.nodeName!=="#text"){let c=e.nodeName.toLowerCase(),i=n?`[${o}]`:"";t.unshift(`${c}${i}`)}e=e.parentElement}return t.length?`/${t.join("/")}`:""}var U=["SVG","IFRAME","SCRIPT","STYLE","LINK"],Y=["A","BUTTON","DETAILS","EMBED","INPUT","LABEL","MENU","MENUITEM","OBJECT","SELECT","TEXTAREA","SUMMARY"],z=["button","menu","menuitem","link","checkbox","radio","slider","tab","tabpanel","textbox","combobox","grid","listbox","option","progressbar","scrollbar","searchbox","switch","tree","treeitem","spinbutton","tooltip"],q=["menu","menuitem","button"];function b(e){return e.nodeType===Node.ELEMENT_NODE}function y(e){var o;let t=(o=e.textContent)==null?void 0:o.trim().replace(/s/g,"");return e.nodeType===Node.TEXT_NODE&&t!==""}var H=e=>{let t=e.getBoundingClientRect();return t.width===0||t.height===0||t.top<0||t.top>window.innerHeight||!R(e,t)?!1:e.checkVisibility({checkOpacity:!0,checkVisibilityCSS:!0})},G=e=>{let t=document.createRange();t.selectNodeContents(e);let o=t.getBoundingClientRect();if(o.width===0||o.height===0||o.top<0||o.top>window.innerHeight)return!1;let n=e.parentElement;return!n||!R(n,o)?!1:n.checkVisibility({checkOpacity:!0,checkVisibilityCSS:!0})};function R(e,t){return[{x:t.left+t.width*.25,y:t.top+t.height*.25},{x:t.left+t.width*.75,y:t.top+t.height*.25},{x:t.left+t.width*.25,y:t.top+t.height*.75},{x:t.left+t.width*.75,y:t.top+t.height*.75},{x:t.left+t.width/2,y:t.top+t.height/2}].some(n=>{let c=document.elementFromPoint(n.x,n.y);for(;c&&c!==document.body;){if(c.isSameNode(e))return!0;c=c.parentElement}return!1})}var D=e=>!(e.hasAttribute("disabled")||e.hasAttribute("hidden")||e.getAttribute("aria-disabled")==="true"),J=e=>{let t=e.tagName,o=e.getAttribute("role"),n=e.getAttribute("aria-role");return t&&Y.includes(t)||o&&z.includes(o)||n&&q.includes(n)},K=e=>e.textContent===""?!1:e.childNodes.length===0?!U.includes(e.tagName):!!(e.childNodes.length===1&&y(e.childNodes[0]));function Q(e){return d(this,null,function*(){let t=window.innerHeight,o=document.documentElement.scrollHeight,n=Math.ceil(o/t),s=Array.from({length:n},(u,m)=>m),c=s.filter(u=>!e.includes(u)),i=window.scrollY,a=c.reduce((u,m)=>{let g=t*m,r=t*u;return Math.abs(i-g)<Math.abs(i-r)?m:u},c[0]);if(a===void 0)throw new Error(`No chunks remaining to check: ${c}`);return{chunk:a,chunksArray:s}})}function W(){return d(this,null,function*(){return new Promise(e=>{let t=()=>setTimeout(()=>{e()},2e3),o=t();new MutationObserver(()=>{clearTimeout(o),o=t()}).observe(window.document.body,{childList:!0,subtree:!0})})})}window.waitForDomSettle=W;function Z(){return d(this,null,function*(){window.chunkNumber=0;let{selectorMap:e,outputString:t}=yield window.processElements(window.chunkNumber);T(e),S()})}function T(e){N(),Object.entries(e).forEach(([t,o])=>{let n=document.evaluate(o,document,null,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue;if(n){let s;if(n.nodeType===Node.ELEMENT_NODE)s=n.getBoundingClientRect();else{let l=document.createRange();l.selectNodeContents(n),s=l.getBoundingClientRect()}let c="grey",i=document.createElement("div");i.style.position="absolute",i.style.left=`${s.left+window.scrollX}px`,i.style.top=`${s.top+window.scrollY}px`,i.style.padding="2px",i.style.width=`${s.width}px`,i.style.height=`${s.height}px`,i.style.backgroundColor=c,i.className="stagehand-marker",i.style.opacity="0.3",i.style.zIndex="1000000000",i.style.border="1px solid",i.style.pointerEvents="none",document.body.appendChild(i)}})}function ee(){return d(this,null,function*(){N(),v()})}function N(){document.querySelectorAll(".stagehand-marker").forEach(t=>{t.remove()})}function v(){document.querySelectorAll(".stagehand-nav").forEach(t=>{t.remove()})}function S(){let e=window.innerHeight,t=document.documentElement.scrollHeight,o=Math.ceil(t/e);if(window.chunkNumber>0){let n=document.createElement("button");n.className="stagehand-nav",n.textContent="Previous",n.style.marginLeft="50px",n.style.position="fixed",n.style.bottom="10px",n.style.left="50%",n.style.transform="translateX(-50%)",n.style.zIndex="1000000000",n.onclick=()=>d(this,null,function*(){N(),v(),window.chunkNumber-=1,window.scrollTo(0,window.chunkNumber*window.innerHeight),yield window.waitForDomSettle();let{selectorMap:s}=yield processElements(window.chunkNumber);T(s),S()}),document.body.appendChild(n)}if(o>window.chunkNumber){let n=document.createElement("button");n.className="stagehand-nav",n.textContent="Next",n.style.marginRight="50px",n.style.position="fixed",n.style.bottom="10px",n.style.right="50%",n.style.transform="translateX(50%)",n.style.zIndex="1000000000",n.onclick=()=>d(this,null,function*(){N(),v(),window.chunkNumber+=1,window.scrollTo(0,window.chunkNumber*window.innerHeight),yield window.waitForDomSettle();let{selectorMap:s}=yield processElements(window.chunkNumber);T(s),S()}),document.body.appendChild(n)}}window.debugDom=Z;window.cleanupDebug=ee;})();'
1551
+ path: import_path2.default.join(__dirname, "..", "dist", "dom", "build", "process.js")
1552
+ });
1553
+ yield this.page.addInitScript({
1554
+ path: import_path2.default.join(__dirname, "..", "dist", "dom", "build", "utils.js")
1555
+ });
1556
+ yield this.page.addInitScript({
1557
+ path: import_path2.default.join(__dirname, "..", "dist", "dom", "build", "debug.js")
1173
1558
  });
1174
1559
  return { debugUrl, sessionUrl };
1175
1560
  });
1176
1561
  }
1562
+ initFromPage(page, modelName) {
1563
+ return __async(this, null, function* () {
1564
+ this.page = page;
1565
+ this.context = page.context();
1566
+ this.defaultModelName = modelName || this.defaultModelName;
1567
+ const originalGoto = this.page.goto.bind(this.page);
1568
+ this.page.goto = (url, options) => __async(this, null, function* () {
1569
+ const result = yield originalGoto(url, options);
1570
+ yield this.page.waitForLoadState("domcontentloaded");
1571
+ yield this._waitForSettledDom();
1572
+ return result;
1573
+ });
1574
+ if (this.headless) {
1575
+ yield this.page.setViewportSize({ width: 1280, height: 720 });
1576
+ }
1577
+ yield this.page.addInitScript({
1578
+ path: import_path2.default.join(__dirname, "..", "dist", "dom", "build", "process.js")
1579
+ });
1580
+ yield this.page.addInitScript({
1581
+ path: import_path2.default.join(__dirname, "..", "dist", "dom", "build", "utils.js")
1582
+ });
1583
+ yield this.page.addInitScript({
1584
+ path: import_path2.default.join(__dirname, "..", "dist", "dom", "build", "debug.js")
1585
+ });
1586
+ return { context: this.context };
1587
+ });
1588
+ }
1177
1589
  log({
1178
1590
  message,
1179
1591
  category,
@@ -1233,11 +1645,9 @@ var Stagehand = class {
1233
1645
  return __async(this, null, function* () {
1234
1646
  try {
1235
1647
  const timeout = timeoutMs != null ? timeoutMs : this.domSettleTimeoutMs;
1236
- const timeoutPromise = new Promise((resolve) => {
1237
- setTimeout(() => {
1238
- console.warn(
1239
- `[stagehand:dom] DOM settle timeout of ${timeout}ms exceeded, continuing anyway`
1240
- );
1648
+ let timeoutHandle;
1649
+ const timeoutPromise = new Promise((resolve, reject) => {
1650
+ timeoutHandle = setTimeout(() => {
1241
1651
  this.log({
1242
1652
  category: "dom",
1243
1653
  message: `DOM settle timeout of ${timeout}ms exceeded, continuing anyway`,
@@ -1246,16 +1656,12 @@ var Stagehand = class {
1246
1656
  resolve();
1247
1657
  }, timeout);
1248
1658
  });
1249
- yield Promise.race([
1250
- (() => __async(this, null, function* () {
1251
- yield this.page.waitForSelector("body");
1252
- yield this.page.waitForLoadState("domcontentloaded");
1253
- yield this.page.evaluate(() => {
1659
+ try {
1660
+ yield Promise.race([
1661
+ this.page.evaluate(() => {
1254
1662
  return new Promise((resolve) => {
1255
1663
  if (typeof window.waitForDomSettle === "function") {
1256
- window.waitForDomSettle().then(() => {
1257
- resolve();
1258
- });
1664
+ window.waitForDomSettle().then(resolve);
1259
1665
  } else {
1260
1666
  console.warn(
1261
1667
  "waitForDomSettle is not defined, considering DOM as settled"
@@ -1263,10 +1669,14 @@ var Stagehand = class {
1263
1669
  resolve();
1264
1670
  }
1265
1671
  });
1266
- });
1267
- }))(),
1268
- timeoutPromise
1269
- ]);
1672
+ }),
1673
+ this.page.waitForLoadState("domcontentloaded"),
1674
+ this.page.waitForSelector("body"),
1675
+ timeoutPromise
1676
+ ]);
1677
+ } finally {
1678
+ clearTimeout(timeoutHandle);
1679
+ }
1270
1680
  } catch (e) {
1271
1681
  this.log({
1272
1682
  category: "dom",
@@ -1336,7 +1746,8 @@ Trace: ${e.stack}`,
1336
1746
  progress = "",
1337
1747
  content = {},
1338
1748
  chunksSeen = [],
1339
- modelName
1749
+ modelName,
1750
+ requestId
1340
1751
  }) {
1341
1752
  this.log({
1342
1753
  category: "extraction",
@@ -1363,7 +1774,8 @@ Trace: ${e.stack}`,
1363
1774
  schema,
1364
1775
  modelName: modelName || this.defaultModelName,
1365
1776
  chunksSeen: chunksSeen.length,
1366
- chunksTotal: chunks.length
1777
+ chunksTotal: chunks.length,
1778
+ requestId
1367
1779
  });
1368
1780
  const _a = extractionResponse, {
1369
1781
  metadata: { progress: newProgress, completed }
@@ -1407,7 +1819,8 @@ Trace: ${e.stack}`,
1407
1819
  instruction,
1408
1820
  useVision,
1409
1821
  fullPage,
1410
- modelName
1822
+ modelName,
1823
+ requestId
1411
1824
  }) {
1412
1825
  if (!instruction) {
1413
1826
  instruction = `Find elements that can be used for any future actions in the page. These may be navigation links, related pages, section/subsection links, buttons, or other interactive elements. Be comprehensive: if there are multiple elements that may be relevant for future actions, return all of them.`;
@@ -1447,7 +1860,8 @@ Trace: ${e.stack}`,
1447
1860
  domElements: outputString,
1448
1861
  llmProvider: this.llmProvider,
1449
1862
  modelName: modelName || this.defaultModelName,
1450
- image: annotatedScreenshot
1863
+ image: annotatedScreenshot,
1864
+ requestId
1451
1865
  });
1452
1866
  const elementsWithSelectors = observationResponse.elements.map(
1453
1867
  (element) => {
@@ -1476,7 +1890,8 @@ Trace: ${e.stack}`,
1476
1890
  modelName,
1477
1891
  useVision,
1478
1892
  verifierUseVision,
1479
- retries = 0
1893
+ retries = 0,
1894
+ requestId
1480
1895
  }) {
1481
1896
  var _a;
1482
1897
  const model = modelName != null ? modelName : this.defaultModelName;
@@ -1536,7 +1951,8 @@ Trace: ${e.stack}`,
1536
1951
  llmProvider: this.llmProvider,
1537
1952
  modelName: model,
1538
1953
  screenshot: annotatedScreenshot,
1539
- logger: this.logger
1954
+ logger: this.logger,
1955
+ requestId
1540
1956
  });
1541
1957
  this.log({
1542
1958
  category: "action",
@@ -1558,7 +1974,8 @@ Trace: ${e.stack}`,
1558
1974
  chunksSeen,
1559
1975
  modelName,
1560
1976
  useVision,
1561
- verifierUseVision
1977
+ verifierUseVision,
1978
+ requestId
1562
1979
  });
1563
1980
  } else if (useVision === "fallback") {
1564
1981
  this.log({
@@ -1573,9 +1990,13 @@ Trace: ${e.stack}`,
1573
1990
  chunksSeen,
1574
1991
  modelName,
1575
1992
  useVision: true,
1576
- verifierUseVision
1993
+ verifierUseVision,
1994
+ requestId
1577
1995
  });
1578
1996
  } else {
1997
+ if (this.enableCaching) {
1998
+ this.llmProvider.cleanRequestCache(requestId);
1999
+ }
1579
2000
  return {
1580
2001
  success: false,
1581
2002
  message: `Action was not able to be completed.`,
@@ -1632,7 +2053,8 @@ Trace: ${e.stack}`,
1632
2053
  useVision,
1633
2054
  verifierUseVision,
1634
2055
  retries: retries + 1,
1635
- chunksSeen
2056
+ chunksSeen,
2057
+ requestId
1636
2058
  });
1637
2059
  }
1638
2060
  }
@@ -1661,7 +2083,8 @@ Trace: ${e.stack}`,
1661
2083
  useVision,
1662
2084
  verifierUseVision,
1663
2085
  retries: retries + 1,
1664
- chunksSeen
2086
+ chunksSeen,
2087
+ requestId
1665
2088
  });
1666
2089
  }
1667
2090
  }
@@ -1684,7 +2107,8 @@ Trace: ${e.stack}`,
1684
2107
  useVision,
1685
2108
  verifierUseVision,
1686
2109
  retries: retries + 1,
1687
- chunksSeen
2110
+ chunksSeen,
2111
+ requestId
1688
2112
  });
1689
2113
  }
1690
2114
  }
@@ -1713,7 +2137,8 @@ Trace: ${e.stack}`,
1713
2137
  useVision,
1714
2138
  verifierUseVision,
1715
2139
  retries: retries + 1,
1716
- chunksSeen
2140
+ chunksSeen,
2141
+ requestId
1717
2142
  });
1718
2143
  }
1719
2144
  }
@@ -1782,9 +2207,13 @@ Trace: ${e.stack}`,
1782
2207
  useVision,
1783
2208
  verifierUseVision,
1784
2209
  retries: retries + 1,
1785
- chunksSeen
2210
+ chunksSeen,
2211
+ requestId
1786
2212
  });
1787
2213
  } else {
2214
+ if (this.enableCaching) {
2215
+ this.llmProvider.cleanRequestCache(requestId);
2216
+ }
1788
2217
  return {
1789
2218
  success: false,
1790
2219
  message: `Internal error: Chosen method ${method} is invalid`,
@@ -1851,7 +2280,8 @@ Trace: ${e.stack}`,
1851
2280
  modelName: model,
1852
2281
  screenshot: fullpageScreenshot,
1853
2282
  domElements,
1854
- logger: this.logger
2283
+ logger: this.logger,
2284
+ requestId
1855
2285
  });
1856
2286
  this.log({
1857
2287
  category: "action",
@@ -1871,7 +2301,8 @@ Trace: ${e.stack}`,
1871
2301
  modelName,
1872
2302
  chunksSeen,
1873
2303
  useVision,
1874
- verifierUseVision
2304
+ verifierUseVision,
2305
+ requestId
1875
2306
  });
1876
2307
  } else {
1877
2308
  this.log({
@@ -1901,10 +2332,14 @@ Trace: ${error.stack}`,
1901
2332
  useVision,
1902
2333
  verifierUseVision,
1903
2334
  retries: retries + 1,
1904
- chunksSeen
2335
+ chunksSeen,
2336
+ requestId
1905
2337
  });
1906
2338
  }
1907
2339
  yield this._recordAction(action, "");
2340
+ if (this.enableCaching) {
2341
+ this.llmProvider.cleanRequestCache(requestId);
2342
+ }
1908
2343
  return {
1909
2344
  success: false,
1910
2345
  message: `Error performing action: ${error.message}`,
@@ -1920,12 +2355,32 @@ Trace: ${error.stack}`,
1920
2355
  useVision = "fallback"
1921
2356
  }) {
1922
2357
  useVision = useVision != null ? useVision : "fallback";
2358
+ const requestId = Math.random().toString(36).substring(2);
2359
+ this.logger({
2360
+ category: "act",
2361
+ message: `Running act with action: ${action}, requestId: ${requestId}`
2362
+ });
1923
2363
  return this._act({
1924
2364
  action,
1925
2365
  modelName,
1926
2366
  chunksSeen: [],
1927
2367
  useVision,
1928
- verifierUseVision: useVision !== false
2368
+ verifierUseVision: useVision !== false,
2369
+ requestId
2370
+ }).catch((e) => {
2371
+ this.logger({
2372
+ category: "act",
2373
+ message: `Error acting: ${e.message}
2374
+ Trace: ${e.stack}`
2375
+ });
2376
+ if (this.enableCaching) {
2377
+ this.llmProvider.cleanRequestCache(requestId);
2378
+ }
2379
+ return {
2380
+ success: false,
2381
+ message: `Internal error: Error acting: ${e.message}`,
2382
+ action
2383
+ };
1929
2384
  });
1930
2385
  });
1931
2386
  }
@@ -1935,21 +2390,53 @@ Trace: ${error.stack}`,
1935
2390
  schema,
1936
2391
  modelName
1937
2392
  }) {
2393
+ const requestId = Math.random().toString(36).substring(2);
2394
+ this.logger({
2395
+ category: "extract",
2396
+ message: `Running extract with instruction: ${instruction}, requestId: ${requestId}`
2397
+ });
1938
2398
  return this._extract({
1939
2399
  instruction,
1940
2400
  schema,
1941
- modelName
2401
+ modelName,
2402
+ requestId
2403
+ }).catch((e) => {
2404
+ this.logger({
2405
+ category: "extract",
2406
+ message: `Internal error: Error extracting: ${e.message}
2407
+ Trace: ${e.stack}`
2408
+ });
2409
+ if (this.enableCaching) {
2410
+ this.llmProvider.cleanRequestCache(requestId);
2411
+ }
2412
+ throw e;
1942
2413
  });
1943
2414
  });
1944
2415
  }
1945
2416
  observe(options) {
1946
2417
  return __async(this, null, function* () {
1947
2418
  var _a, _b;
2419
+ const requestId = Math.random().toString(36).substring(2);
2420
+ this.logger({
2421
+ category: "observe",
2422
+ message: `Running observe with instruction: ${options == null ? void 0 : options.instruction}, requestId: ${requestId}`
2423
+ });
1948
2424
  return this._observe({
1949
2425
  instruction: (_a = options == null ? void 0 : options.instruction) != null ? _a : "Find actions that can be performed on this page.",
1950
2426
  modelName: options == null ? void 0 : options.modelName,
1951
2427
  useVision: (_b = options == null ? void 0 : options.useVision) != null ? _b : false,
1952
- fullPage: false
2428
+ fullPage: false,
2429
+ requestId
2430
+ }).catch((e) => {
2431
+ this.logger({
2432
+ category: "observe",
2433
+ message: `Error observing: ${e.message}
2434
+ Trace: ${e.stack}`
2435
+ });
2436
+ if (this.enableCaching) {
2437
+ this.llmProvider.cleanRequestCache(requestId);
2438
+ }
2439
+ throw e;
1953
2440
  });
1954
2441
  });
1955
2442
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@browserbasehq/stagehand",
3
- "version": "1.0.3-alpha.2",
3
+ "version": "1.1.0",
4
4
  "description": "An AI web browsing framework focused on simplicity and extensibility.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -50,6 +50,7 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "@anthropic-ai/sdk": "^0.27.3",
53
+ "@browserbasehq/sdk": "^2.0.0",
53
54
  "anthropic": "^0.0.0",
54
55
  "anthropic-ai": "^0.0.10",
55
56
  "sharp": "^0.33.5",