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/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 = await response.json();
242
+ errorData = JSON.parse(text);
197
243
  } catch {
198
- errorData = await response.text();
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-0-fast", "agi-1")
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 session_id, vnc_url, status, etc.
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
- export { AGIClient, AGIError, APIError, AgentExecutionError, AuthenticationError, NotFoundError, PermissionError, RateLimitError, Screenshot, SessionContext, SessionsResource, ValidationError };
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