gpt-driver-node 1.0.5 → 1.0.6

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,
@@ -2277,7 +2323,6 @@ ${"=".repeat(50)}`);
2277
2323
  let conditionSucceeded = false;
2278
2324
  while (!conditionSucceeded) {
2279
2325
  let screenshot;
2280
- let originalScreenshotBase64 = null;
2281
2326
  if (!this.useGptDriverCloud) {
2282
2327
  const stabilityResult = await waitForStableScreen(
2283
2328
  () => this.getScreenshot(this.appiumSessionConfig)
@@ -2288,8 +2333,9 @@ ${"=".repeat(50)}`);
2288
2333
  }
2289
2334
  }
2290
2335
  globalLogger.info("Requesting next action from GPT Driver...");
2291
- const response = await axios.request(
2292
- {
2336
+ let responseData;
2337
+ try {
2338
+ responseData = await requestWithRetry({
2293
2339
  url: `${this.gptDriverBaseUrl}/sessions/${this.gptDriverSessionId}/execute`,
2294
2340
  method: "POST",
2295
2341
  data: {
@@ -2299,18 +2345,21 @@ ${"=".repeat(50)}`);
2299
2345
  caching_mode: cachingMode ?? this.cachingMode,
2300
2346
  step_number: this.step_number
2301
2347
  }
2302
- }
2303
- );
2304
- const executeStatus = response.data.status;
2348
+ });
2349
+ } catch (error) {
2350
+ globalLogger.error(`GPT Driver request failed: ${error.message}`);
2351
+ globalLogger.error(`Stack trace: ${error.stack}`);
2352
+ throw error;
2353
+ }
2354
+ const executeStatus = responseData.status;
2305
2355
  if (executeStatus === "failed") {
2306
- const errorMessage = response.data?.commands?.at(0)?.data;
2356
+ const errorMessage = responseData?.commands?.at(0)?.data;
2307
2357
  globalLogger.error(`Execution failed: ${errorMessage ?? "Unknown error"}`);
2308
2358
  throw new Error(errorMessage ?? "Execution failed");
2309
2359
  }
2310
2360
  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) {
2361
+ globalLogger.debug(`Received ${responseData.commands.length} command(s) to execute`);
2362
+ for (const appiumCommand of responseData.commands) {
2314
2363
  await this.executeCommand(appiumCommand);
2315
2364
  }
2316
2365
  }
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,
@@ -2275,7 +2321,6 @@ ${"=".repeat(50)}`);
2275
2321
  let conditionSucceeded = false;
2276
2322
  while (!conditionSucceeded) {
2277
2323
  let screenshot;
2278
- let originalScreenshotBase64 = null;
2279
2324
  if (!this.useGptDriverCloud) {
2280
2325
  const stabilityResult = await waitForStableScreen(
2281
2326
  () => this.getScreenshot(this.appiumSessionConfig)
@@ -2286,8 +2331,9 @@ ${"=".repeat(50)}`);
2286
2331
  }
2287
2332
  }
2288
2333
  globalLogger.info("Requesting next action from GPT Driver...");
2289
- const response = await axios.request(
2290
- {
2334
+ let responseData;
2335
+ try {
2336
+ responseData = await requestWithRetry({
2291
2337
  url: `${this.gptDriverBaseUrl}/sessions/${this.gptDriverSessionId}/execute`,
2292
2338
  method: "POST",
2293
2339
  data: {
@@ -2297,18 +2343,21 @@ ${"=".repeat(50)}`);
2297
2343
  caching_mode: cachingMode ?? this.cachingMode,
2298
2344
  step_number: this.step_number
2299
2345
  }
2300
- }
2301
- );
2302
- const executeStatus = response.data.status;
2346
+ });
2347
+ } catch (error) {
2348
+ globalLogger.error(`GPT Driver request failed: ${error.message}`);
2349
+ globalLogger.error(`Stack trace: ${error.stack}`);
2350
+ throw error;
2351
+ }
2352
+ const executeStatus = responseData.status;
2303
2353
  if (executeStatus === "failed") {
2304
- const errorMessage = response.data?.commands?.at(0)?.data;
2354
+ const errorMessage = responseData?.commands?.at(0)?.data;
2305
2355
  globalLogger.error(`Execution failed: ${errorMessage ?? "Unknown error"}`);
2306
2356
  throw new Error(errorMessage ?? "Execution failed");
2307
2357
  }
2308
2358
  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) {
2359
+ globalLogger.debug(`Received ${responseData.commands.length} command(s) to execute`);
2360
+ for (const appiumCommand of responseData.commands) {
2312
2361
  await this.executeCommand(appiumCommand);
2313
2362
  }
2314
2363
  }
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.6",
4
4
  "main": "./dist/index.cjs",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.cts",