agi 0.3.0 → 0.4.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 +74 -1
- package/dist/index.d.mts +609 -6
- package/dist/index.d.ts +609 -6
- package/dist/index.js +788 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +783 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -1
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import { createParser } from 'eventsource-parser';
|
|
2
|
+
import { spawn } from 'child_process';
|
|
3
|
+
import { EventEmitter } from 'events';
|
|
4
|
+
import { createInterface } from 'readline';
|
|
5
|
+
import { existsSync } from 'fs';
|
|
6
|
+
import { dirname, join, delimiter } from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import { platform, arch } from 'os';
|
|
9
|
+
import { createRequire } from 'module';
|
|
2
10
|
|
|
3
11
|
// src/http.ts
|
|
4
12
|
|
|
@@ -112,6 +120,43 @@ var HTTPClient = class {
|
|
|
112
120
|
}
|
|
113
121
|
throw lastError || new AGIError("Request failed after retries");
|
|
114
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Make an HTTP request to an absolute URL with retries and error handling
|
|
125
|
+
*/
|
|
126
|
+
async requestUrl(method, url, options) {
|
|
127
|
+
const headers = this.buildHeaders(options?.headers);
|
|
128
|
+
let lastError;
|
|
129
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
130
|
+
try {
|
|
131
|
+
const controller = new AbortController();
|
|
132
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
133
|
+
const response = await fetch(url, {
|
|
134
|
+
method,
|
|
135
|
+
headers,
|
|
136
|
+
body: options?.json ? JSON.stringify(options.json) : void 0,
|
|
137
|
+
signal: controller.signal
|
|
138
|
+
});
|
|
139
|
+
clearTimeout(timeoutId);
|
|
140
|
+
if (!response.ok) {
|
|
141
|
+
await this.handleErrorResponse(response);
|
|
142
|
+
}
|
|
143
|
+
const data = await response.json();
|
|
144
|
+
return data;
|
|
145
|
+
} catch (error) {
|
|
146
|
+
lastError = error;
|
|
147
|
+
if (error instanceof AGIError && error.statusCode && error.statusCode < 500) {
|
|
148
|
+
if (error.statusCode !== 429) {
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (attempt === this.maxRetries) {
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
await this.sleep(Math.pow(2, attempt) * 1e3);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
throw lastError || new AGIError("Request failed after retries");
|
|
159
|
+
}
|
|
115
160
|
/**
|
|
116
161
|
* Stream Server-Sent Events from an endpoint
|
|
117
162
|
*/
|
|
@@ -192,10 +237,11 @@ var HTTPClient = class {
|
|
|
192
237
|
}
|
|
193
238
|
async handleErrorResponse(response) {
|
|
194
239
|
let errorData;
|
|
240
|
+
const text = await response.text();
|
|
195
241
|
try {
|
|
196
|
-
errorData =
|
|
242
|
+
errorData = JSON.parse(text);
|
|
197
243
|
} catch {
|
|
198
|
-
errorData =
|
|
244
|
+
errorData = text;
|
|
199
245
|
}
|
|
200
246
|
const errorMessage = typeof errorData === "object" && errorData.message ? errorData.message : typeof errorData === "string" ? errorData : `HTTP ${response.status}: ${response.statusText}`;
|
|
201
247
|
switch (response.status) {
|
|
@@ -577,7 +623,8 @@ function normalizeSessionResponse(data) {
|
|
|
577
623
|
status: data.status,
|
|
578
624
|
createdAt: data.created_at ?? data.createdAt,
|
|
579
625
|
environmentId: data.environment_id ?? data.environmentId,
|
|
580
|
-
goal: data.goal
|
|
626
|
+
goal: data.goal,
|
|
627
|
+
agentSessionType: data.agent_session_type ?? data.agentSessionType
|
|
581
628
|
};
|
|
582
629
|
}
|
|
583
630
|
|
|
@@ -590,16 +637,24 @@ var SessionsResource = class {
|
|
|
590
637
|
/**
|
|
591
638
|
* Create a new agent session
|
|
592
639
|
*
|
|
593
|
-
* @param agentName - Agent model to use (e.g., "agi-0", "agi-
|
|
640
|
+
* @param agentName - Agent model to use (e.g., "agi-0", "agi-2-claude")
|
|
594
641
|
* @param options - Session creation options
|
|
595
|
-
* @returns SessionResponse with
|
|
642
|
+
* @returns SessionResponse with sessionId, vncUrl, agentUrl, status, etc.
|
|
596
643
|
*
|
|
597
644
|
* @example
|
|
598
645
|
* ```typescript
|
|
646
|
+
* // Standard browser session
|
|
599
647
|
* const session = await client.sessions.create('agi-0', {
|
|
600
648
|
* webhookUrl: 'https://yourapp.com/webhook',
|
|
601
649
|
* maxSteps: 200
|
|
602
650
|
* });
|
|
651
|
+
*
|
|
652
|
+
* // Desktop session (client-managed)
|
|
653
|
+
* const session = await client.sessions.create('agi-2-claude', {
|
|
654
|
+
* agentSessionType: 'desktop',
|
|
655
|
+
* goal: 'Open calculator and compute 2+2'
|
|
656
|
+
* });
|
|
657
|
+
* console.log(session.agentUrl); // Use with client.desktop.step()
|
|
603
658
|
* ```
|
|
604
659
|
*/
|
|
605
660
|
async create(agentName = "agi-0", options) {
|
|
@@ -612,6 +667,10 @@ var SessionsResource = class {
|
|
|
612
667
|
if (options?.restoreFromEnvironmentId) {
|
|
613
668
|
payload.restore_from_environment_id = options.restoreFromEnvironmentId;
|
|
614
669
|
}
|
|
670
|
+
if (options?.agentSessionType) {
|
|
671
|
+
payload.agent_session_type = options.agentSessionType;
|
|
672
|
+
}
|
|
673
|
+
if (options?.cdpUrl) payload.cdp_url = options.cdpUrl;
|
|
615
674
|
const response = await this.http.request("POST", "/v1/sessions", {
|
|
616
675
|
json: payload
|
|
617
676
|
});
|
|
@@ -876,6 +935,90 @@ var SessionsResource = class {
|
|
|
876
935
|
async screenshot(sessionId) {
|
|
877
936
|
return this.http.request("GET", `/v1/sessions/${sessionId}/screenshot`);
|
|
878
937
|
}
|
|
938
|
+
// ===== CLIENT-DRIVEN SESSION CONTROL =====
|
|
939
|
+
/**
|
|
940
|
+
* Execute a single step for client-driven sessions (desktop mode).
|
|
941
|
+
*
|
|
942
|
+
* In desktop mode (agentSessionType="desktop"), the client manages the
|
|
943
|
+
* execution loop. This method sends a screenshot to the agent and receives
|
|
944
|
+
* actions to execute locally.
|
|
945
|
+
*
|
|
946
|
+
* @param agentUrl - Agent service URL from session.agentUrl
|
|
947
|
+
* @param sessionId - Session ID (required for routing in shared sandbox)
|
|
948
|
+
* @param screenshot - Base64-encoded screenshot (full resolution, JPEG or PNG)
|
|
949
|
+
* @param message - Optional user message (goal on first call, or follow-up instruction)
|
|
950
|
+
* @returns StepDesktopResponse with actions, thinking, finished, askUser, and step
|
|
951
|
+
*
|
|
952
|
+
* @example
|
|
953
|
+
* ```typescript
|
|
954
|
+
* // Create a desktop session
|
|
955
|
+
* const session = await client.sessions.create('agi-2-claude', {
|
|
956
|
+
* agentSessionType: 'desktop',
|
|
957
|
+
* goal: 'Open calculator and compute 2+2'
|
|
958
|
+
* });
|
|
959
|
+
*
|
|
960
|
+
* // Client-managed loop
|
|
961
|
+
* let finished = false;
|
|
962
|
+
* while (!finished) {
|
|
963
|
+
* const screenshot = captureScreenshot(); // Client captures
|
|
964
|
+
* const result = await client.sessions.step(
|
|
965
|
+
* session.agentUrl!,
|
|
966
|
+
* session.sessionId,
|
|
967
|
+
* screenshot
|
|
968
|
+
* );
|
|
969
|
+
* executeActions(result.actions); // Client executes
|
|
970
|
+
* finished = result.finished;
|
|
971
|
+
* if (result.askUser) {
|
|
972
|
+
* const answer = await promptUser(result.askUser);
|
|
973
|
+
* // Send answer in next step
|
|
974
|
+
* }
|
|
975
|
+
* }
|
|
976
|
+
* ```
|
|
977
|
+
*/
|
|
978
|
+
async step(agentUrl, sessionId, screenshot, message) {
|
|
979
|
+
const url = `${agentUrl.replace(/\/$/, "")}/step_desktop`;
|
|
980
|
+
const payload = { screenshot, session_id: sessionId };
|
|
981
|
+
if (message !== void 0) {
|
|
982
|
+
payload.message = message;
|
|
983
|
+
}
|
|
984
|
+
const response = await this.http.requestUrl("POST", url, {
|
|
985
|
+
json: payload
|
|
986
|
+
});
|
|
987
|
+
return {
|
|
988
|
+
actions: response.actions || [],
|
|
989
|
+
thinking: response.thinking,
|
|
990
|
+
finished: response.finished ?? false,
|
|
991
|
+
askUser: response.ask_user ?? response.askUser,
|
|
992
|
+
step: response.step ?? 0
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
// ===== MODELS =====
|
|
996
|
+
/**
|
|
997
|
+
* List available agent models.
|
|
998
|
+
*
|
|
999
|
+
* @param filter - Optional filter: "cdp" for browser agents, "desktop" for
|
|
1000
|
+
* desktop agents
|
|
1001
|
+
* @returns ModelsResponse with list of available model names
|
|
1002
|
+
*
|
|
1003
|
+
* @example
|
|
1004
|
+
* ```typescript
|
|
1005
|
+
* // List all models
|
|
1006
|
+
* const models = await client.sessions.listModels();
|
|
1007
|
+
* console.log(models.models);
|
|
1008
|
+
*
|
|
1009
|
+
* // List only desktop-compatible models
|
|
1010
|
+
* const desktopModels = await client.sessions.listModels('desktop');
|
|
1011
|
+
* console.log(desktopModels.models);
|
|
1012
|
+
* // ['agi-2-claude', 'agi-2-qwen']
|
|
1013
|
+
* ```
|
|
1014
|
+
*/
|
|
1015
|
+
async listModels(filter) {
|
|
1016
|
+
const query = {};
|
|
1017
|
+
if (filter) {
|
|
1018
|
+
query.filter = filter;
|
|
1019
|
+
}
|
|
1020
|
+
return this.http.request("GET", "/v1/models", { query });
|
|
1021
|
+
}
|
|
879
1022
|
};
|
|
880
1023
|
|
|
881
1024
|
// src/client.ts
|
|
@@ -941,6 +1084,640 @@ var AGIClient = class {
|
|
|
941
1084
|
}
|
|
942
1085
|
};
|
|
943
1086
|
|
|
944
|
-
|
|
1087
|
+
// src/loop.ts
|
|
1088
|
+
var AgentLoop = class {
|
|
1089
|
+
client;
|
|
1090
|
+
agentUrl;
|
|
1091
|
+
sessionId;
|
|
1092
|
+
captureScreenshot;
|
|
1093
|
+
executeActions;
|
|
1094
|
+
onThinking;
|
|
1095
|
+
onAskUser;
|
|
1096
|
+
onStep;
|
|
1097
|
+
stepDelay;
|
|
1098
|
+
_state = "idle";
|
|
1099
|
+
_lastResult = null;
|
|
1100
|
+
_currentStep = 0;
|
|
1101
|
+
// Pause control using Promise-based approach
|
|
1102
|
+
pauseResolve = null;
|
|
1103
|
+
pausePromise = null;
|
|
1104
|
+
constructor(options) {
|
|
1105
|
+
this.client = options.client;
|
|
1106
|
+
this.agentUrl = options.agentUrl;
|
|
1107
|
+
this.sessionId = options.sessionId;
|
|
1108
|
+
this.captureScreenshot = options.captureScreenshot;
|
|
1109
|
+
this.executeActions = options.executeActions;
|
|
1110
|
+
this.onThinking = options.onThinking;
|
|
1111
|
+
this.onAskUser = options.onAskUser;
|
|
1112
|
+
this.onStep = options.onStep;
|
|
1113
|
+
this.stepDelay = options.stepDelay ?? 0;
|
|
1114
|
+
}
|
|
1115
|
+
/** Current state of the loop. */
|
|
1116
|
+
get state() {
|
|
1117
|
+
return this._state;
|
|
1118
|
+
}
|
|
1119
|
+
/** Current step number. */
|
|
1120
|
+
get currentStep() {
|
|
1121
|
+
return this._currentStep;
|
|
1122
|
+
}
|
|
1123
|
+
/** Last step result, if any. */
|
|
1124
|
+
get lastResult() {
|
|
1125
|
+
return this._lastResult;
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1128
|
+
* Start the execution loop.
|
|
1129
|
+
*
|
|
1130
|
+
* Runs the loop until the task is finished, stopped, or an unhandled
|
|
1131
|
+
* ask_user question is encountered.
|
|
1132
|
+
*
|
|
1133
|
+
* @param message - Optional initial message (goal or instruction).
|
|
1134
|
+
* Usually not needed if goal was set during session creation.
|
|
1135
|
+
* @returns The final StepDesktopResponse
|
|
1136
|
+
* @throws Error if loop is already running
|
|
1137
|
+
*/
|
|
1138
|
+
async start(message) {
|
|
1139
|
+
if (this._state === "running") {
|
|
1140
|
+
throw new Error("Loop is already running");
|
|
1141
|
+
}
|
|
1142
|
+
this._state = "running";
|
|
1143
|
+
let currentMessage = message;
|
|
1144
|
+
let result = null;
|
|
1145
|
+
try {
|
|
1146
|
+
while (true) {
|
|
1147
|
+
if (this.pausePromise) {
|
|
1148
|
+
await this.pausePromise;
|
|
1149
|
+
}
|
|
1150
|
+
const currentState = this._state;
|
|
1151
|
+
if (currentState === "paused") {
|
|
1152
|
+
continue;
|
|
1153
|
+
}
|
|
1154
|
+
if (currentState !== "running") {
|
|
1155
|
+
break;
|
|
1156
|
+
}
|
|
1157
|
+
const screenshot = await this.captureScreenshot();
|
|
1158
|
+
result = await this.client.sessions.step(
|
|
1159
|
+
this.agentUrl,
|
|
1160
|
+
this.sessionId,
|
|
1161
|
+
screenshot,
|
|
1162
|
+
currentMessage
|
|
1163
|
+
);
|
|
1164
|
+
currentMessage = void 0;
|
|
1165
|
+
this._lastResult = result;
|
|
1166
|
+
this._currentStep = result.step;
|
|
1167
|
+
if (result.thinking && this.onThinking) {
|
|
1168
|
+
this.onThinking(result.thinking);
|
|
1169
|
+
}
|
|
1170
|
+
if (this.onStep) {
|
|
1171
|
+
this.onStep(result.step, result);
|
|
1172
|
+
}
|
|
1173
|
+
if (result.askUser) {
|
|
1174
|
+
if (this.onAskUser) {
|
|
1175
|
+
const answer = await this.onAskUser(result.askUser);
|
|
1176
|
+
currentMessage = answer;
|
|
1177
|
+
} else {
|
|
1178
|
+
this._state = "stopped";
|
|
1179
|
+
return result;
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
if (result.actions.length > 0) {
|
|
1183
|
+
await this.executeActions(result.actions);
|
|
1184
|
+
}
|
|
1185
|
+
if (result.finished) {
|
|
1186
|
+
this._state = "finished";
|
|
1187
|
+
return result;
|
|
1188
|
+
}
|
|
1189
|
+
if (this.stepDelay > 0) {
|
|
1190
|
+
await new Promise((resolve) => setTimeout(resolve, this.stepDelay));
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
} catch (error) {
|
|
1194
|
+
this._state = "stopped";
|
|
1195
|
+
throw error;
|
|
1196
|
+
}
|
|
1197
|
+
if (result === null) {
|
|
1198
|
+
result = {
|
|
1199
|
+
actions: [],
|
|
1200
|
+
thinking: void 0,
|
|
1201
|
+
finished: true,
|
|
1202
|
+
askUser: void 0,
|
|
1203
|
+
step: this._currentStep
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1206
|
+
return result;
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Pause the execution loop.
|
|
1210
|
+
*
|
|
1211
|
+
* The loop will complete the current step before pausing.
|
|
1212
|
+
* Call resume() to continue.
|
|
1213
|
+
*/
|
|
1214
|
+
pause() {
|
|
1215
|
+
if (this._state !== "running") {
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
this._state = "paused";
|
|
1219
|
+
this.pausePromise = new Promise((resolve) => {
|
|
1220
|
+
this.pauseResolve = resolve;
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* Resume a paused loop.
|
|
1225
|
+
*/
|
|
1226
|
+
resume() {
|
|
1227
|
+
if (this._state !== "paused") {
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
this._state = "running";
|
|
1231
|
+
if (this.pauseResolve) {
|
|
1232
|
+
this.pauseResolve();
|
|
1233
|
+
this.pauseResolve = null;
|
|
1234
|
+
this.pausePromise = null;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
/**
|
|
1238
|
+
* Stop the execution loop.
|
|
1239
|
+
*
|
|
1240
|
+
* The loop will complete the current step before stopping.
|
|
1241
|
+
*/
|
|
1242
|
+
stop() {
|
|
1243
|
+
this._state = "stopped";
|
|
1244
|
+
if (this.pauseResolve) {
|
|
1245
|
+
this.pauseResolve();
|
|
1246
|
+
this.pauseResolve = null;
|
|
1247
|
+
this.pausePromise = null;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
/** Check if the loop is currently running. */
|
|
1251
|
+
isRunning() {
|
|
1252
|
+
return this._state === "running";
|
|
1253
|
+
}
|
|
1254
|
+
/** Check if the loop is currently paused. */
|
|
1255
|
+
isPaused() {
|
|
1256
|
+
return this._state === "paused";
|
|
1257
|
+
}
|
|
1258
|
+
/** Check if the loop has finished successfully. */
|
|
1259
|
+
isFinished() {
|
|
1260
|
+
return this._state === "finished";
|
|
1261
|
+
}
|
|
1262
|
+
};
|
|
1263
|
+
var __filename$1 = fileURLToPath(import.meta.url);
|
|
1264
|
+
var __dirname$1 = dirname(__filename$1);
|
|
1265
|
+
var require2 = createRequire(import.meta.url);
|
|
1266
|
+
function getPlatformId() {
|
|
1267
|
+
const os = platform();
|
|
1268
|
+
const cpu = arch();
|
|
1269
|
+
if (os === "darwin") {
|
|
1270
|
+
return cpu === "arm64" ? "darwin-arm64" : "darwin-x64";
|
|
1271
|
+
} else if (os === "linux") {
|
|
1272
|
+
return "linux-x64";
|
|
1273
|
+
} else if (os === "win32") {
|
|
1274
|
+
return "win32-x64";
|
|
1275
|
+
}
|
|
1276
|
+
throw new Error(`Unsupported platform: ${os}-${cpu}`);
|
|
1277
|
+
}
|
|
1278
|
+
function getBinaryFilename(platformId) {
|
|
1279
|
+
const id = platformId ?? getPlatformId();
|
|
1280
|
+
if (id === "win32-x64") {
|
|
1281
|
+
return "agi-driver.exe";
|
|
1282
|
+
}
|
|
1283
|
+
return "agi-driver";
|
|
1284
|
+
}
|
|
1285
|
+
function getSearchPaths(platformId) {
|
|
1286
|
+
const filename = getBinaryFilename(platformId);
|
|
1287
|
+
const paths = [];
|
|
1288
|
+
const packageName = `@agi/agi-${platformId}`;
|
|
1289
|
+
try {
|
|
1290
|
+
const packagePath = require2.resolve(`${packageName}/package.json`);
|
|
1291
|
+
const packageDir = dirname(packagePath);
|
|
1292
|
+
paths.push(join(packageDir, filename));
|
|
1293
|
+
} catch {
|
|
1294
|
+
}
|
|
1295
|
+
paths.push(join(__dirname$1, "..", "..", "bin", filename));
|
|
1296
|
+
paths.push(join(__dirname$1, "..", "..", "..", "bin", filename));
|
|
1297
|
+
const envPath = process.env.PATH || "";
|
|
1298
|
+
for (const dir of envPath.split(delimiter)) {
|
|
1299
|
+
if (dir) {
|
|
1300
|
+
paths.push(join(dir, filename));
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
return paths;
|
|
1304
|
+
}
|
|
1305
|
+
function getPythonFallback() {
|
|
1306
|
+
const driverPath = process.env.AGI_DRIVER_PATH;
|
|
1307
|
+
if (driverPath && existsSync(join(driverPath, "__main__.py"))) {
|
|
1308
|
+
return {
|
|
1309
|
+
command: process.env.PYTHON_PATH || "python",
|
|
1310
|
+
args: ["-m", "agi_driver"]
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1313
|
+
return null;
|
|
1314
|
+
}
|
|
1315
|
+
function findBinaryPath() {
|
|
1316
|
+
const platformId = getPlatformId();
|
|
1317
|
+
const searchPaths = getSearchPaths(platformId);
|
|
1318
|
+
for (const path of searchPaths) {
|
|
1319
|
+
if (existsSync(path)) {
|
|
1320
|
+
return path;
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
throw new Error(
|
|
1324
|
+
`Could not find agi-driver binary for ${platformId}. Searched: ${searchPaths.join(", ")}. Install the optional dependency @agi/agi-${platformId} or ensure agi-driver is in PATH.`
|
|
1325
|
+
);
|
|
1326
|
+
}
|
|
1327
|
+
function isBinaryAvailable() {
|
|
1328
|
+
try {
|
|
1329
|
+
findBinaryPath();
|
|
1330
|
+
return true;
|
|
1331
|
+
} catch {
|
|
1332
|
+
return false;
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
// src/driver/protocol.ts
|
|
1337
|
+
function parseEvent(line) {
|
|
1338
|
+
const data = JSON.parse(line);
|
|
1339
|
+
return data;
|
|
1340
|
+
}
|
|
1341
|
+
function serializeCommand(command) {
|
|
1342
|
+
return JSON.stringify(command);
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
// src/driver/driver.ts
|
|
1346
|
+
var AgentDriver = class extends EventEmitter {
|
|
1347
|
+
binaryPath;
|
|
1348
|
+
pythonFallback;
|
|
1349
|
+
model;
|
|
1350
|
+
platform;
|
|
1351
|
+
mode;
|
|
1352
|
+
env;
|
|
1353
|
+
process = null;
|
|
1354
|
+
readline = null;
|
|
1355
|
+
state = "idle";
|
|
1356
|
+
step = 0;
|
|
1357
|
+
sessionId = "";
|
|
1358
|
+
screenWidth = 0;
|
|
1359
|
+
screenHeight = 0;
|
|
1360
|
+
resolveStart = null;
|
|
1361
|
+
rejectStart = null;
|
|
1362
|
+
// Pending callbacks for user interaction
|
|
1363
|
+
pendingConfirm = null;
|
|
1364
|
+
pendingAnswer = null;
|
|
1365
|
+
constructor(options = {}) {
|
|
1366
|
+
super();
|
|
1367
|
+
let binaryPath = null;
|
|
1368
|
+
try {
|
|
1369
|
+
binaryPath = options.binaryPath ?? findBinaryPath();
|
|
1370
|
+
} catch {
|
|
1371
|
+
}
|
|
1372
|
+
this.binaryPath = binaryPath;
|
|
1373
|
+
this.pythonFallback = binaryPath ? null : getPythonFallback();
|
|
1374
|
+
if (!this.binaryPath && !this.pythonFallback) {
|
|
1375
|
+
throw new Error(
|
|
1376
|
+
"Could not find agi-driver binary and Python fallback is not available. Set AGI_DRIVER_PATH to the agi_driver source directory for development."
|
|
1377
|
+
);
|
|
1378
|
+
}
|
|
1379
|
+
this.model = options.model ?? "claude-sonnet";
|
|
1380
|
+
this.platform = options.platform ?? "desktop";
|
|
1381
|
+
this.mode = options.mode ?? "";
|
|
1382
|
+
this.env = options.env ?? {};
|
|
1383
|
+
}
|
|
1384
|
+
/**
|
|
1385
|
+
* Get the current state of the driver.
|
|
1386
|
+
*/
|
|
1387
|
+
get currentState() {
|
|
1388
|
+
return this.state;
|
|
1389
|
+
}
|
|
1390
|
+
/**
|
|
1391
|
+
* Get the current step number.
|
|
1392
|
+
*/
|
|
1393
|
+
get currentStep() {
|
|
1394
|
+
return this.step;
|
|
1395
|
+
}
|
|
1396
|
+
/**
|
|
1397
|
+
* Check if the driver is running.
|
|
1398
|
+
*/
|
|
1399
|
+
get isRunning() {
|
|
1400
|
+
return this.state === "running";
|
|
1401
|
+
}
|
|
1402
|
+
/**
|
|
1403
|
+
* Check if the driver is waiting for user input.
|
|
1404
|
+
*/
|
|
1405
|
+
get isWaiting() {
|
|
1406
|
+
return this.state === "waiting_confirmation" || this.state === "waiting_answer";
|
|
1407
|
+
}
|
|
1408
|
+
/**
|
|
1409
|
+
* Start the agent with a goal.
|
|
1410
|
+
*
|
|
1411
|
+
* @param goal - The task for the agent to accomplish
|
|
1412
|
+
* @param screenshot - Initial screenshot (base64-encoded). Not needed in local mode.
|
|
1413
|
+
* @param screenWidth - Screen width in pixels. Not needed in local mode.
|
|
1414
|
+
* @param screenHeight - Screen height in pixels. Not needed in local mode.
|
|
1415
|
+
* @param mode - Override the mode set in DriverOptions.
|
|
1416
|
+
* @returns Promise that resolves when the agent finishes
|
|
1417
|
+
*/
|
|
1418
|
+
async start(goal, screenshot = "", screenWidth = 0, screenHeight = 0, mode) {
|
|
1419
|
+
if (this.process) {
|
|
1420
|
+
throw new Error("Driver is already running");
|
|
1421
|
+
}
|
|
1422
|
+
this.sessionId = `session_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
1423
|
+
this.screenWidth = screenWidth;
|
|
1424
|
+
this.screenHeight = screenHeight;
|
|
1425
|
+
return new Promise((resolve, reject) => {
|
|
1426
|
+
this.resolveStart = resolve;
|
|
1427
|
+
this.rejectStart = reject;
|
|
1428
|
+
if (this.binaryPath) {
|
|
1429
|
+
this.process = spawn(this.binaryPath, [], {
|
|
1430
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1431
|
+
env: {
|
|
1432
|
+
...process.env,
|
|
1433
|
+
...this.env
|
|
1434
|
+
}
|
|
1435
|
+
});
|
|
1436
|
+
} else if (this.pythonFallback) {
|
|
1437
|
+
this.process = spawn(this.pythonFallback.command, this.pythonFallback.args, {
|
|
1438
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1439
|
+
env: {
|
|
1440
|
+
...process.env,
|
|
1441
|
+
...this.env,
|
|
1442
|
+
PYTHONPATH: process.env.AGI_DRIVER_PATH ? `${process.env.AGI_DRIVER_PATH}/..` : ""
|
|
1443
|
+
},
|
|
1444
|
+
cwd: process.env.AGI_DRIVER_PATH ? `${process.env.AGI_DRIVER_PATH}/..` : void 0
|
|
1445
|
+
});
|
|
1446
|
+
} else {
|
|
1447
|
+
reject(new Error("No binary or Python fallback available"));
|
|
1448
|
+
return;
|
|
1449
|
+
}
|
|
1450
|
+
this.process.on("error", (err) => {
|
|
1451
|
+
this.cleanup();
|
|
1452
|
+
if (this.rejectStart) {
|
|
1453
|
+
this.rejectStart(err);
|
|
1454
|
+
this.rejectStart = null;
|
|
1455
|
+
this.resolveStart = null;
|
|
1456
|
+
}
|
|
1457
|
+
});
|
|
1458
|
+
this.process.on("exit", (code) => {
|
|
1459
|
+
this.cleanup();
|
|
1460
|
+
if (this.rejectStart) {
|
|
1461
|
+
this.rejectStart(new Error(`Driver exited with code ${code}`));
|
|
1462
|
+
this.rejectStart = null;
|
|
1463
|
+
this.resolveStart = null;
|
|
1464
|
+
}
|
|
1465
|
+
});
|
|
1466
|
+
this.readline = createInterface({
|
|
1467
|
+
input: this.process.stdout,
|
|
1468
|
+
crlfDelay: Infinity
|
|
1469
|
+
});
|
|
1470
|
+
this.readline.on("line", (line) => {
|
|
1471
|
+
this.handleLine(line).catch((err) => {
|
|
1472
|
+
this.emit("error", {
|
|
1473
|
+
event: "error",
|
|
1474
|
+
message: `Error handling line: ${err.message}`,
|
|
1475
|
+
code: "parse_error",
|
|
1476
|
+
recoverable: false,
|
|
1477
|
+
step: this.step
|
|
1478
|
+
});
|
|
1479
|
+
});
|
|
1480
|
+
});
|
|
1481
|
+
this.process.stderr?.on("data", (data) => {
|
|
1482
|
+
this.emit("stderr", data.toString());
|
|
1483
|
+
});
|
|
1484
|
+
this.once("ready", () => {
|
|
1485
|
+
const startCmd = {
|
|
1486
|
+
command: "start",
|
|
1487
|
+
session_id: this.sessionId,
|
|
1488
|
+
goal,
|
|
1489
|
+
screenshot,
|
|
1490
|
+
screen_width: screenWidth,
|
|
1491
|
+
screen_height: screenHeight,
|
|
1492
|
+
platform: this.platform,
|
|
1493
|
+
model: this.model,
|
|
1494
|
+
mode: mode ?? this.mode
|
|
1495
|
+
};
|
|
1496
|
+
this.sendCommand(startCmd);
|
|
1497
|
+
});
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1500
|
+
/**
|
|
1501
|
+
* Send a new screenshot to the driver.
|
|
1502
|
+
*
|
|
1503
|
+
* @param screenshot - Base64-encoded screenshot
|
|
1504
|
+
* @param screenWidth - Screen width in pixels
|
|
1505
|
+
* @param screenHeight - Screen height in pixels
|
|
1506
|
+
*/
|
|
1507
|
+
sendScreenshot(screenshot, screenWidth, screenHeight) {
|
|
1508
|
+
if (!this.process) {
|
|
1509
|
+
throw new Error("Driver is not running");
|
|
1510
|
+
}
|
|
1511
|
+
const cmd = {
|
|
1512
|
+
command: "screenshot",
|
|
1513
|
+
data: screenshot,
|
|
1514
|
+
screen_width: screenWidth ?? this.screenWidth,
|
|
1515
|
+
screen_height: screenHeight ?? this.screenHeight
|
|
1516
|
+
};
|
|
1517
|
+
this.sendCommand(cmd);
|
|
1518
|
+
}
|
|
1519
|
+
/**
|
|
1520
|
+
* Pause the driver.
|
|
1521
|
+
*/
|
|
1522
|
+
pause() {
|
|
1523
|
+
if (!this.process) return;
|
|
1524
|
+
this.sendCommand({ command: "pause" });
|
|
1525
|
+
}
|
|
1526
|
+
/**
|
|
1527
|
+
* Resume the driver.
|
|
1528
|
+
*/
|
|
1529
|
+
resume() {
|
|
1530
|
+
if (!this.process) return;
|
|
1531
|
+
this.sendCommand({ command: "resume" });
|
|
1532
|
+
}
|
|
1533
|
+
/**
|
|
1534
|
+
* Stop the driver.
|
|
1535
|
+
*
|
|
1536
|
+
* @param reason - Reason for stopping
|
|
1537
|
+
*/
|
|
1538
|
+
async stop(reason) {
|
|
1539
|
+
if (!this.process) return;
|
|
1540
|
+
const cmd = {
|
|
1541
|
+
command: "stop",
|
|
1542
|
+
reason
|
|
1543
|
+
};
|
|
1544
|
+
this.sendCommand(cmd);
|
|
1545
|
+
await new Promise((resolve) => {
|
|
1546
|
+
if (!this.process) {
|
|
1547
|
+
resolve();
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1550
|
+
this.process.once("exit", () => resolve());
|
|
1551
|
+
setTimeout(() => {
|
|
1552
|
+
if (this.process) {
|
|
1553
|
+
this.process.kill();
|
|
1554
|
+
}
|
|
1555
|
+
resolve();
|
|
1556
|
+
}, 1e3);
|
|
1557
|
+
});
|
|
1558
|
+
this.cleanup();
|
|
1559
|
+
}
|
|
1560
|
+
/**
|
|
1561
|
+
* Respond to a confirmation request.
|
|
1562
|
+
*
|
|
1563
|
+
* @param approved - Whether the action is approved
|
|
1564
|
+
* @param message - Optional message to send with the response
|
|
1565
|
+
*/
|
|
1566
|
+
respondConfirm(approved, message) {
|
|
1567
|
+
if (!this.process || this.state !== "waiting_confirmation") {
|
|
1568
|
+
throw new Error("Not waiting for confirmation");
|
|
1569
|
+
}
|
|
1570
|
+
const cmd = {
|
|
1571
|
+
command: "confirm",
|
|
1572
|
+
approved,
|
|
1573
|
+
message
|
|
1574
|
+
};
|
|
1575
|
+
this.sendCommand(cmd);
|
|
1576
|
+
if (this.pendingConfirm) {
|
|
1577
|
+
this.pendingConfirm(approved, message);
|
|
1578
|
+
this.pendingConfirm = null;
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
/**
|
|
1582
|
+
* Respond to a question.
|
|
1583
|
+
*
|
|
1584
|
+
* @param text - The answer text
|
|
1585
|
+
* @param questionId - Optional question ID
|
|
1586
|
+
*/
|
|
1587
|
+
respondAnswer(text, questionId) {
|
|
1588
|
+
if (!this.process || this.state !== "waiting_answer") {
|
|
1589
|
+
throw new Error("Not waiting for answer");
|
|
1590
|
+
}
|
|
1591
|
+
const cmd = {
|
|
1592
|
+
command: "answer",
|
|
1593
|
+
text,
|
|
1594
|
+
question_id: questionId
|
|
1595
|
+
};
|
|
1596
|
+
this.sendCommand(cmd);
|
|
1597
|
+
if (this.pendingAnswer) {
|
|
1598
|
+
this.pendingAnswer(text);
|
|
1599
|
+
this.pendingAnswer = null;
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
sendCommand(cmd) {
|
|
1603
|
+
if (!this.process?.stdin) return;
|
|
1604
|
+
const line = serializeCommand(cmd) + "\n";
|
|
1605
|
+
this.process.stdin.write(line);
|
|
1606
|
+
}
|
|
1607
|
+
async handleLine(line) {
|
|
1608
|
+
if (!line.trim()) return;
|
|
1609
|
+
let event;
|
|
1610
|
+
try {
|
|
1611
|
+
event = parseEvent(line);
|
|
1612
|
+
} catch (e) {
|
|
1613
|
+
console.error("Failed to parse event:", line);
|
|
1614
|
+
return;
|
|
1615
|
+
}
|
|
1616
|
+
this.step = event.step;
|
|
1617
|
+
this.emit("event", event);
|
|
1618
|
+
switch (event.event) {
|
|
1619
|
+
case "ready":
|
|
1620
|
+
this.emit("ready", event);
|
|
1621
|
+
break;
|
|
1622
|
+
case "state_change":
|
|
1623
|
+
this.state = event.state;
|
|
1624
|
+
this.emit("state_change", event.state, event);
|
|
1625
|
+
break;
|
|
1626
|
+
case "thinking":
|
|
1627
|
+
this.emit("thinking", event.text, event);
|
|
1628
|
+
break;
|
|
1629
|
+
case "action":
|
|
1630
|
+
this.emit("action", event.action, event);
|
|
1631
|
+
break;
|
|
1632
|
+
case "confirm": {
|
|
1633
|
+
this.state = "waiting_confirmation";
|
|
1634
|
+
const confirmListeners = this.rawListeners("confirm");
|
|
1635
|
+
let confirmHandled = false;
|
|
1636
|
+
for (const listener of confirmListeners) {
|
|
1637
|
+
try {
|
|
1638
|
+
const approved = await listener(event.reason, event);
|
|
1639
|
+
if (typeof approved === "boolean" && !confirmHandled) {
|
|
1640
|
+
this.respondConfirm(approved);
|
|
1641
|
+
confirmHandled = true;
|
|
1642
|
+
}
|
|
1643
|
+
} catch {
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
break;
|
|
1647
|
+
}
|
|
1648
|
+
case "ask_question": {
|
|
1649
|
+
this.state = "waiting_answer";
|
|
1650
|
+
const questionListeners = this.rawListeners("ask_question");
|
|
1651
|
+
let questionHandled = false;
|
|
1652
|
+
for (const listener of questionListeners) {
|
|
1653
|
+
try {
|
|
1654
|
+
const answer = await listener(event.question, event);
|
|
1655
|
+
if (typeof answer === "string" && !questionHandled) {
|
|
1656
|
+
this.respondAnswer(answer, event.question_id);
|
|
1657
|
+
questionHandled = true;
|
|
1658
|
+
}
|
|
1659
|
+
} catch {
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
break;
|
|
1663
|
+
}
|
|
1664
|
+
case "screenshot_captured":
|
|
1665
|
+
this.emit("screenshot_captured", event);
|
|
1666
|
+
break;
|
|
1667
|
+
case "finished":
|
|
1668
|
+
this.handleFinished(event);
|
|
1669
|
+
break;
|
|
1670
|
+
case "error":
|
|
1671
|
+
this.handleError(event);
|
|
1672
|
+
break;
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
handleFinished(event) {
|
|
1676
|
+
this.state = "finished";
|
|
1677
|
+
this.emit("finished", event);
|
|
1678
|
+
if (this.resolveStart) {
|
|
1679
|
+
this.resolveStart({
|
|
1680
|
+
success: event.success,
|
|
1681
|
+
reason: event.reason,
|
|
1682
|
+
summary: event.summary,
|
|
1683
|
+
step: event.step
|
|
1684
|
+
});
|
|
1685
|
+
this.resolveStart = null;
|
|
1686
|
+
this.rejectStart = null;
|
|
1687
|
+
}
|
|
1688
|
+
this.cleanup();
|
|
1689
|
+
}
|
|
1690
|
+
handleError(event) {
|
|
1691
|
+
this.emit("error", event);
|
|
1692
|
+
if (!event.recoverable) {
|
|
1693
|
+
this.state = "error";
|
|
1694
|
+
if (this.rejectStart) {
|
|
1695
|
+
this.rejectStart(new Error(`${event.code}: ${event.message}`));
|
|
1696
|
+
this.rejectStart = null;
|
|
1697
|
+
this.resolveStart = null;
|
|
1698
|
+
}
|
|
1699
|
+
this.cleanup();
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
cleanup() {
|
|
1703
|
+
if (this.readline) {
|
|
1704
|
+
this.readline.close();
|
|
1705
|
+
this.readline = null;
|
|
1706
|
+
}
|
|
1707
|
+
if (this.rejectStart) {
|
|
1708
|
+
this.rejectStart(new Error("Driver stopped"));
|
|
1709
|
+
this.rejectStart = null;
|
|
1710
|
+
this.resolveStart = null;
|
|
1711
|
+
}
|
|
1712
|
+
if (this.process) {
|
|
1713
|
+
this.process.kill();
|
|
1714
|
+
this.process = null;
|
|
1715
|
+
}
|
|
1716
|
+
this.pendingConfirm = null;
|
|
1717
|
+
this.pendingAnswer = null;
|
|
1718
|
+
}
|
|
1719
|
+
};
|
|
1720
|
+
|
|
1721
|
+
export { AGIClient, AGIError, APIError, AgentDriver, AgentExecutionError, AgentLoop, AuthenticationError, NotFoundError, PermissionError, RateLimitError, Screenshot, SessionContext, SessionsResource, ValidationError, findBinaryPath, getPlatformId, isBinaryAvailable };
|
|
945
1722
|
//# sourceMappingURL=index.mjs.map
|
|
946
1723
|
//# sourceMappingURL=index.mjs.map
|