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 +87 -35
- package/dist/index.d.cts +2 -0
- package/dist/index.mjs +87 -35
- 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,
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
2312
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
2310
|
-
|
|
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
|
}
|