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 +82 -33
- package/dist/index.mjs +82 -33
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
2312
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
2310
|
-
|
|
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
|
}
|