gpt-driver-node 1.0.5 → 1.0.7

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/dist/index.cjs CHANGED
@@ -11,29 +11,6 @@ var winston = require('winston');
11
11
  var zod = require('zod');
12
12
  var crypto = require('node:crypto');
13
13
 
14
- const delay = async (milliseconds) => {
15
- await new Promise((resolve) => setTimeout(resolve, milliseconds));
16
- };
17
- function buildUrl(base, extraPath) {
18
- let baseUrl = base.toString();
19
- if (baseUrl.endsWith("/")) {
20
- baseUrl = baseUrl.slice(0, -1);
21
- }
22
- if (!extraPath.startsWith("/")) {
23
- extraPath = "/" + extraPath;
24
- }
25
- return `${baseUrl}${extraPath}`;
26
- }
27
- const getImageDimensions = async (base64) => {
28
- const base64Data = base64.replace(/^data:image\/\w+;base64,/, "");
29
- const buffer = Buffer.from(base64Data, "base64");
30
- const metadata = await sharp(buffer).metadata();
31
- if (!metadata.width || !metadata.height) {
32
- throw new Error("Unable to get image dimensions");
33
- }
34
- return { width: metadata.width, height: metadata.height };
35
- };
36
-
37
14
  const colors = {
38
15
  reset: "\x1B[0m",
39
16
  bold: "\x1B[1m",
@@ -95,6 +72,75 @@ ${logStyles.gray(stack)}` : logMessage;
95
72
  ]
96
73
  });
97
74
 
75
+ const delay = async (milliseconds) => {
76
+ await new Promise((resolve) => setTimeout(resolve, milliseconds));
77
+ };
78
+ function isRetryableNetworkError(error) {
79
+ const retryableCodes = [
80
+ "ECONNRESET",
81
+ "ETIMEDOUT",
82
+ "ECONNREFUSED",
83
+ "ENOTFOUND",
84
+ "ENETUNREACH",
85
+ "EAI_AGAIN",
86
+ "EPIPE",
87
+ "ECONNABORTED"
88
+ ];
89
+ if (error.code && retryableCodes.includes(error.code)) {
90
+ return true;
91
+ }
92
+ if (error.message?.includes("socket disconnected") || error.message?.includes("TLS") || error.message?.includes("SSL")) {
93
+ return true;
94
+ }
95
+ if (error.code === "ECONNABORTED" && error.message?.includes("timeout")) {
96
+ return true;
97
+ }
98
+ if (error.response?.status >= 500) {
99
+ return true;
100
+ }
101
+ return false;
102
+ }
103
+ async function requestWithRetry(config, maxRetries = 3, baseDelayMs = 1e3) {
104
+ let lastError;
105
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
106
+ try {
107
+ const response = await axios.request(config);
108
+ return response.data;
109
+ } catch (error) {
110
+ lastError = error;
111
+ const isRetryable = isRetryableNetworkError(error);
112
+ if (!isRetryable || attempt === maxRetries) {
113
+ throw error;
114
+ }
115
+ const delayMs = baseDelayMs * Math.pow(2, attempt - 1);
116
+ globalLogger.warn(
117
+ `Request failed (attempt ${attempt}/${maxRetries}): ${error.message}. Retrying in ${delayMs}ms...`
118
+ );
119
+ await delay(delayMs);
120
+ }
121
+ }
122
+ throw lastError;
123
+ }
124
+ function buildUrl(base, extraPath) {
125
+ let baseUrl = base.toString();
126
+ if (baseUrl.endsWith("/")) {
127
+ baseUrl = baseUrl.slice(0, -1);
128
+ }
129
+ if (!extraPath.startsWith("/")) {
130
+ extraPath = "/" + extraPath;
131
+ }
132
+ return `${baseUrl}${extraPath}`;
133
+ }
134
+ const getImageDimensions = async (base64) => {
135
+ const base64Data = base64.replace(/^data:image\/\w+;base64,/, "");
136
+ const buffer = Buffer.from(base64Data, "base64");
137
+ const metadata = await sharp(buffer).metadata();
138
+ if (!metadata.width || !metadata.height) {
139
+ throw new Error("Unable to get image dimensions");
140
+ }
141
+ return { width: metadata.width, height: metadata.height };
142
+ };
143
+
98
144
  const waitForStableScreen = async (getScreenshot, options = {}) => {
99
145
  const {
100
146
  maxTimeoutSec = 5,
@@ -908,6 +954,7 @@ class GptDriver {
908
954
  step_number = 1;
909
955
  organisationId;
910
956
  configFilePath;
957
+ additionalUserContext;
911
958
  // Session Execution Stats
912
959
  _stats_startTime = 0;
913
960
  _stats_executedSteps = 0;
@@ -943,6 +990,7 @@ class GptDriver {
943
990
  this.cachingMode = config.cachingMode ?? "NONE";
944
991
  this.organisationId = config.organisationId;
945
992
  this.configFilePath = config.configFilePath;
993
+ this.additionalUserContext = config.additionalUserContext;
946
994
  if (config.useGptDriverCloud) {
947
995
  if (config.serverConfig.device?.platform == null) {
948
996
  throw new Error("Platform is missing. Please specify the platform when using GPTDriver Cloud.");
@@ -1069,7 +1117,8 @@ class GptDriver {
1069
1117
  },
1070
1118
  use_internal_virtual_device: this.useGptDriverCloud,
1071
1119
  build_id: this.buildId,
1072
- caching_mode: this.cachingMode
1120
+ caching_mode: this.cachingMode,
1121
+ ...this.additionalUserContext && { additional_user_context: this.additionalUserContext }
1073
1122
  }
1074
1123
  );
1075
1124
  this.gptDriverSessionId = response.data.sessionId;
@@ -1571,7 +1620,7 @@ ${"=".repeat(50)}`);
1571
1620
  const smartLoopEndTime = performance.now();
1572
1621
  globalLogger.debug(`[Performance] Smart loop completed in ${(smartLoopEndTime - smartLoopStartTime).toFixed(2)}ms`);
1573
1622
  if (!result.success) {
1574
- throw new Error(result.error || "Smart loop execution failed");
1623
+ await this.setSessionFailed();
1575
1624
  }
1576
1625
  if (result.cacheHitCount) {
1577
1626
  this._stats_cacheHits += result.cacheHitCount;
@@ -2277,7 +2326,6 @@ ${"=".repeat(50)}`);
2277
2326
  let conditionSucceeded = false;
2278
2327
  while (!conditionSucceeded) {
2279
2328
  let screenshot;
2280
- let originalScreenshotBase64 = null;
2281
2329
  if (!this.useGptDriverCloud) {
2282
2330
  const stabilityResult = await waitForStableScreen(
2283
2331
  () => this.getScreenshot(this.appiumSessionConfig)
@@ -2288,8 +2336,9 @@ ${"=".repeat(50)}`);
2288
2336
  }
2289
2337
  }
2290
2338
  globalLogger.info("Requesting next action from GPT Driver...");
2291
- const response = await axios.request(
2292
- {
2339
+ let responseData;
2340
+ try {
2341
+ responseData = await requestWithRetry({
2293
2342
  url: `${this.gptDriverBaseUrl}/sessions/${this.gptDriverSessionId}/execute`,
2294
2343
  method: "POST",
2295
2344
  data: {
@@ -2299,18 +2348,21 @@ ${"=".repeat(50)}`);
2299
2348
  caching_mode: cachingMode ?? this.cachingMode,
2300
2349
  step_number: this.step_number
2301
2350
  }
2302
- }
2303
- );
2304
- const executeStatus = response.data.status;
2351
+ });
2352
+ } catch (error) {
2353
+ globalLogger.error(`GPT Driver request failed: ${error.message}`);
2354
+ globalLogger.error(`Stack trace: ${error.stack}`);
2355
+ throw error;
2356
+ }
2357
+ const executeStatus = responseData.status;
2305
2358
  if (executeStatus === "failed") {
2306
- const errorMessage = response.data?.commands?.at(0)?.data;
2359
+ const errorMessage = responseData?.commands?.at(0)?.data;
2307
2360
  globalLogger.error(`Execution failed: ${errorMessage ?? "Unknown error"}`);
2308
2361
  throw new Error(errorMessage ?? "Execution failed");
2309
2362
  }
2310
2363
  conditionSucceeded = executeStatus !== "inProgress";
2311
- const executeResponse = response.data;
2312
- globalLogger.debug(`Received ${executeResponse.commands.length} command(s) to execute`);
2313
- for (const appiumCommand of executeResponse.commands) {
2364
+ globalLogger.debug(`Received ${responseData.commands.length} command(s) to execute`);
2365
+ for (const appiumCommand of responseData.commands) {
2314
2366
  await this.executeCommand(appiumCommand);
2315
2367
  }
2316
2368
  }
package/dist/index.d.cts CHANGED
@@ -22,6 +22,7 @@ interface GptDriverConfig {
22
22
  useGptDriverCloud?: boolean;
23
23
  buildId?: string;
24
24
  cachingMode?: CachingMode;
25
+ additionalUserContext?: string;
25
26
  organisationId?: string;
26
27
  configFilePath?: string;
27
28
  }
@@ -512,6 +513,7 @@ declare class GptDriver {
512
513
  private step_number;
513
514
  private organisationId?;
514
515
  private configFilePath?;
516
+ private additionalUserContext?;
515
517
  private _stats_startTime;
516
518
  private _stats_executedSteps;
517
519
  private _stats_cacheHits;
package/dist/index.mjs CHANGED
@@ -9,29 +9,6 @@ import winston from 'winston';
9
9
  import { z } from 'zod';
10
10
  import crypto from 'node:crypto';
11
11
 
12
- const delay = async (milliseconds) => {
13
- await new Promise((resolve) => setTimeout(resolve, milliseconds));
14
- };
15
- function buildUrl(base, extraPath) {
16
- let baseUrl = base.toString();
17
- if (baseUrl.endsWith("/")) {
18
- baseUrl = baseUrl.slice(0, -1);
19
- }
20
- if (!extraPath.startsWith("/")) {
21
- extraPath = "/" + extraPath;
22
- }
23
- return `${baseUrl}${extraPath}`;
24
- }
25
- const getImageDimensions = async (base64) => {
26
- const base64Data = base64.replace(/^data:image\/\w+;base64,/, "");
27
- const buffer = Buffer.from(base64Data, "base64");
28
- const metadata = await sharp(buffer).metadata();
29
- if (!metadata.width || !metadata.height) {
30
- throw new Error("Unable to get image dimensions");
31
- }
32
- return { width: metadata.width, height: metadata.height };
33
- };
34
-
35
12
  const colors = {
36
13
  reset: "\x1B[0m",
37
14
  bold: "\x1B[1m",
@@ -93,6 +70,75 @@ ${logStyles.gray(stack)}` : logMessage;
93
70
  ]
94
71
  });
95
72
 
73
+ const delay = async (milliseconds) => {
74
+ await new Promise((resolve) => setTimeout(resolve, milliseconds));
75
+ };
76
+ function isRetryableNetworkError(error) {
77
+ const retryableCodes = [
78
+ "ECONNRESET",
79
+ "ETIMEDOUT",
80
+ "ECONNREFUSED",
81
+ "ENOTFOUND",
82
+ "ENETUNREACH",
83
+ "EAI_AGAIN",
84
+ "EPIPE",
85
+ "ECONNABORTED"
86
+ ];
87
+ if (error.code && retryableCodes.includes(error.code)) {
88
+ return true;
89
+ }
90
+ if (error.message?.includes("socket disconnected") || error.message?.includes("TLS") || error.message?.includes("SSL")) {
91
+ return true;
92
+ }
93
+ if (error.code === "ECONNABORTED" && error.message?.includes("timeout")) {
94
+ return true;
95
+ }
96
+ if (error.response?.status >= 500) {
97
+ return true;
98
+ }
99
+ return false;
100
+ }
101
+ async function requestWithRetry(config, maxRetries = 3, baseDelayMs = 1e3) {
102
+ let lastError;
103
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
104
+ try {
105
+ const response = await axios.request(config);
106
+ return response.data;
107
+ } catch (error) {
108
+ lastError = error;
109
+ const isRetryable = isRetryableNetworkError(error);
110
+ if (!isRetryable || attempt === maxRetries) {
111
+ throw error;
112
+ }
113
+ const delayMs = baseDelayMs * Math.pow(2, attempt - 1);
114
+ globalLogger.warn(
115
+ `Request failed (attempt ${attempt}/${maxRetries}): ${error.message}. Retrying in ${delayMs}ms...`
116
+ );
117
+ await delay(delayMs);
118
+ }
119
+ }
120
+ throw lastError;
121
+ }
122
+ function buildUrl(base, extraPath) {
123
+ let baseUrl = base.toString();
124
+ if (baseUrl.endsWith("/")) {
125
+ baseUrl = baseUrl.slice(0, -1);
126
+ }
127
+ if (!extraPath.startsWith("/")) {
128
+ extraPath = "/" + extraPath;
129
+ }
130
+ return `${baseUrl}${extraPath}`;
131
+ }
132
+ const getImageDimensions = async (base64) => {
133
+ const base64Data = base64.replace(/^data:image\/\w+;base64,/, "");
134
+ const buffer = Buffer.from(base64Data, "base64");
135
+ const metadata = await sharp(buffer).metadata();
136
+ if (!metadata.width || !metadata.height) {
137
+ throw new Error("Unable to get image dimensions");
138
+ }
139
+ return { width: metadata.width, height: metadata.height };
140
+ };
141
+
96
142
  const waitForStableScreen = async (getScreenshot, options = {}) => {
97
143
  const {
98
144
  maxTimeoutSec = 5,
@@ -906,6 +952,7 @@ class GptDriver {
906
952
  step_number = 1;
907
953
  organisationId;
908
954
  configFilePath;
955
+ additionalUserContext;
909
956
  // Session Execution Stats
910
957
  _stats_startTime = 0;
911
958
  _stats_executedSteps = 0;
@@ -941,6 +988,7 @@ class GptDriver {
941
988
  this.cachingMode = config.cachingMode ?? "NONE";
942
989
  this.organisationId = config.organisationId;
943
990
  this.configFilePath = config.configFilePath;
991
+ this.additionalUserContext = config.additionalUserContext;
944
992
  if (config.useGptDriverCloud) {
945
993
  if (config.serverConfig.device?.platform == null) {
946
994
  throw new Error("Platform is missing. Please specify the platform when using GPTDriver Cloud.");
@@ -1067,7 +1115,8 @@ class GptDriver {
1067
1115
  },
1068
1116
  use_internal_virtual_device: this.useGptDriverCloud,
1069
1117
  build_id: this.buildId,
1070
- caching_mode: this.cachingMode
1118
+ caching_mode: this.cachingMode,
1119
+ ...this.additionalUserContext && { additional_user_context: this.additionalUserContext }
1071
1120
  }
1072
1121
  );
1073
1122
  this.gptDriverSessionId = response.data.sessionId;
@@ -1569,7 +1618,7 @@ ${"=".repeat(50)}`);
1569
1618
  const smartLoopEndTime = performance.now();
1570
1619
  globalLogger.debug(`[Performance] Smart loop completed in ${(smartLoopEndTime - smartLoopStartTime).toFixed(2)}ms`);
1571
1620
  if (!result.success) {
1572
- throw new Error(result.error || "Smart loop execution failed");
1621
+ await this.setSessionFailed();
1573
1622
  }
1574
1623
  if (result.cacheHitCount) {
1575
1624
  this._stats_cacheHits += result.cacheHitCount;
@@ -2275,7 +2324,6 @@ ${"=".repeat(50)}`);
2275
2324
  let conditionSucceeded = false;
2276
2325
  while (!conditionSucceeded) {
2277
2326
  let screenshot;
2278
- let originalScreenshotBase64 = null;
2279
2327
  if (!this.useGptDriverCloud) {
2280
2328
  const stabilityResult = await waitForStableScreen(
2281
2329
  () => this.getScreenshot(this.appiumSessionConfig)
@@ -2286,8 +2334,9 @@ ${"=".repeat(50)}`);
2286
2334
  }
2287
2335
  }
2288
2336
  globalLogger.info("Requesting next action from GPT Driver...");
2289
- const response = await axios.request(
2290
- {
2337
+ let responseData;
2338
+ try {
2339
+ responseData = await requestWithRetry({
2291
2340
  url: `${this.gptDriverBaseUrl}/sessions/${this.gptDriverSessionId}/execute`,
2292
2341
  method: "POST",
2293
2342
  data: {
@@ -2297,18 +2346,21 @@ ${"=".repeat(50)}`);
2297
2346
  caching_mode: cachingMode ?? this.cachingMode,
2298
2347
  step_number: this.step_number
2299
2348
  }
2300
- }
2301
- );
2302
- const executeStatus = response.data.status;
2349
+ });
2350
+ } catch (error) {
2351
+ globalLogger.error(`GPT Driver request failed: ${error.message}`);
2352
+ globalLogger.error(`Stack trace: ${error.stack}`);
2353
+ throw error;
2354
+ }
2355
+ const executeStatus = responseData.status;
2303
2356
  if (executeStatus === "failed") {
2304
- const errorMessage = response.data?.commands?.at(0)?.data;
2357
+ const errorMessage = responseData?.commands?.at(0)?.data;
2305
2358
  globalLogger.error(`Execution failed: ${errorMessage ?? "Unknown error"}`);
2306
2359
  throw new Error(errorMessage ?? "Execution failed");
2307
2360
  }
2308
2361
  conditionSucceeded = executeStatus !== "inProgress";
2309
- const executeResponse = response.data;
2310
- globalLogger.debug(`Received ${executeResponse.commands.length} command(s) to execute`);
2311
- for (const appiumCommand of executeResponse.commands) {
2362
+ globalLogger.debug(`Received ${responseData.commands.length} command(s) to execute`);
2363
+ for (const appiumCommand of responseData.commands) {
2312
2364
  await this.executeCommand(appiumCommand);
2313
2365
  }
2314
2366
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gpt-driver-node",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "main": "./dist/index.cjs",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.cts",