@agentfield/sdk 0.1.101 → 0.1.102-rc.1
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.d.ts +328 -1
- package/dist/index.js +917 -226
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,6 +6,9 @@ import { spawn } from 'child_process';
|
|
|
6
6
|
import express from 'express';
|
|
7
7
|
import rateLimit from 'express-rate-limit';
|
|
8
8
|
import crypto2, { randomUUID, createHash } from 'crypto';
|
|
9
|
+
import axios5, { isAxiosError } from 'axios';
|
|
10
|
+
import http from 'http';
|
|
11
|
+
import https from 'https';
|
|
9
12
|
import { AsyncLocalStorage } from 'async_hooks';
|
|
10
13
|
import { tool, jsonSchema, generateText, stepCountIs, streamText, embed, embedMany, generateObject } from 'ai';
|
|
11
14
|
import { createOpenAI } from '@ai-sdk/openai';
|
|
@@ -17,9 +20,6 @@ import { createXai } from '@ai-sdk/xai';
|
|
|
17
20
|
import { createDeepSeek } from '@ai-sdk/deepseek';
|
|
18
21
|
import { createCohere } from '@ai-sdk/cohere';
|
|
19
22
|
import os from 'os';
|
|
20
|
-
import axios4, { isAxiosError } from 'axios';
|
|
21
|
-
import http from 'http';
|
|
22
|
-
import https from 'https';
|
|
23
23
|
import WebSocket from 'ws';
|
|
24
24
|
import { Buffer as Buffer$1 } from 'buffer';
|
|
25
25
|
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
@@ -1065,8 +1065,8 @@ var CancelRegistry = class {
|
|
|
1065
1065
|
* so callers always get a usable signal even outside the control-plane
|
|
1066
1066
|
* dispatch path (e.g. local manual invocations).
|
|
1067
1067
|
*/
|
|
1068
|
-
register(executionId) {
|
|
1069
|
-
const controller = new AbortController();
|
|
1068
|
+
register(executionId, existing) {
|
|
1069
|
+
const controller = existing ?? new AbortController();
|
|
1070
1070
|
if (!executionId) {
|
|
1071
1071
|
return { controller, release: () => {
|
|
1072
1072
|
} };
|
|
@@ -1133,6 +1133,298 @@ function installCancelRoute(app, registry, logger) {
|
|
|
1133
1133
|
}
|
|
1134
1134
|
);
|
|
1135
1135
|
}
|
|
1136
|
+
|
|
1137
|
+
// src/agent/pause.ts
|
|
1138
|
+
var ApprovalResult = class {
|
|
1139
|
+
decision;
|
|
1140
|
+
feedback;
|
|
1141
|
+
executionId;
|
|
1142
|
+
approvalRequestId;
|
|
1143
|
+
rawResponse;
|
|
1144
|
+
constructor(params) {
|
|
1145
|
+
this.decision = params.decision;
|
|
1146
|
+
this.feedback = params.feedback ?? "";
|
|
1147
|
+
this.executionId = params.executionId ?? "";
|
|
1148
|
+
this.approvalRequestId = params.approvalRequestId ?? "";
|
|
1149
|
+
this.rawResponse = params.rawResponse;
|
|
1150
|
+
}
|
|
1151
|
+
/** True when the human approved the request. */
|
|
1152
|
+
get approved() {
|
|
1153
|
+
return this.decision === "approved";
|
|
1154
|
+
}
|
|
1155
|
+
/** True when the human asked for changes rather than approving/rejecting. */
|
|
1156
|
+
get changesRequested() {
|
|
1157
|
+
return this.decision === "request_changes";
|
|
1158
|
+
}
|
|
1159
|
+
};
|
|
1160
|
+
var PauseClock = class {
|
|
1161
|
+
totalPausedMs = 0;
|
|
1162
|
+
pauseStartedAt = null;
|
|
1163
|
+
/**
|
|
1164
|
+
* Set by the watchdog when it aborts the reasoner for exceeding the active
|
|
1165
|
+
* budget. Distinguishes a budget-timeout abort from an external cooperative
|
|
1166
|
+
* cancel arriving via the cancel dispatcher.
|
|
1167
|
+
*/
|
|
1168
|
+
timedOut = false;
|
|
1169
|
+
startPause() {
|
|
1170
|
+
if (this.pauseStartedAt === null) {
|
|
1171
|
+
this.pauseStartedAt = Date.now();
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
endPause() {
|
|
1175
|
+
if (this.pauseStartedAt !== null) {
|
|
1176
|
+
this.totalPausedMs += Date.now() - this.pauseStartedAt;
|
|
1177
|
+
this.pauseStartedAt = null;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
/** Cumulative paused milliseconds, including any in-progress pause. */
|
|
1181
|
+
totalPaused() {
|
|
1182
|
+
if (this.pauseStartedAt === null) {
|
|
1183
|
+
return this.totalPausedMs;
|
|
1184
|
+
}
|
|
1185
|
+
return this.totalPausedMs + (Date.now() - this.pauseStartedAt);
|
|
1186
|
+
}
|
|
1187
|
+
};
|
|
1188
|
+
var PauseManager = class {
|
|
1189
|
+
pending = /* @__PURE__ */ new Map();
|
|
1190
|
+
/** execution_id -> approval_request_id, for fallback resolution. */
|
|
1191
|
+
execToRequest = /* @__PURE__ */ new Map();
|
|
1192
|
+
/**
|
|
1193
|
+
* Register a new pending pause and return the promise to await. Idempotent:
|
|
1194
|
+
* a second register() for the same `approvalRequestId` returns the existing
|
|
1195
|
+
* promise rather than replacing it.
|
|
1196
|
+
*/
|
|
1197
|
+
register(approvalRequestId, executionId = "") {
|
|
1198
|
+
const existing = this.pending.get(approvalRequestId);
|
|
1199
|
+
if (existing) {
|
|
1200
|
+
return existing.promise;
|
|
1201
|
+
}
|
|
1202
|
+
let resolveFn;
|
|
1203
|
+
const promise = new Promise((resolve2) => {
|
|
1204
|
+
resolveFn = resolve2;
|
|
1205
|
+
});
|
|
1206
|
+
this.pending.set(approvalRequestId, { resolve: resolveFn, promise });
|
|
1207
|
+
if (executionId) {
|
|
1208
|
+
this.execToRequest.set(executionId, approvalRequestId);
|
|
1209
|
+
}
|
|
1210
|
+
return promise;
|
|
1211
|
+
}
|
|
1212
|
+
/**
|
|
1213
|
+
* Resolve a pending pause by `approvalRequestId`. Returns true if a waiter
|
|
1214
|
+
* was found and resolved, false otherwise.
|
|
1215
|
+
*/
|
|
1216
|
+
resolve(approvalRequestId, result) {
|
|
1217
|
+
const entry = this.pending.get(approvalRequestId);
|
|
1218
|
+
if (!entry) {
|
|
1219
|
+
return false;
|
|
1220
|
+
}
|
|
1221
|
+
this.pending.delete(approvalRequestId);
|
|
1222
|
+
for (const [eid, rid] of this.execToRequest) {
|
|
1223
|
+
if (rid === approvalRequestId) {
|
|
1224
|
+
this.execToRequest.delete(eid);
|
|
1225
|
+
break;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
entry.resolve(result);
|
|
1229
|
+
return true;
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Fallback: resolve by `executionId` when the callback omits the
|
|
1233
|
+
* `approvalRequestId`. Returns true if a waiter was found.
|
|
1234
|
+
*/
|
|
1235
|
+
resolveByExecutionId(executionId, result) {
|
|
1236
|
+
const requestId = this.execToRequest.get(executionId);
|
|
1237
|
+
if (!requestId) {
|
|
1238
|
+
return false;
|
|
1239
|
+
}
|
|
1240
|
+
return this.resolve(requestId, result);
|
|
1241
|
+
}
|
|
1242
|
+
/**
|
|
1243
|
+
* Resolve every pending pause with a `cancelled` result. Used on shutdown so
|
|
1244
|
+
* a reasoner blocked in `ctx.pause()` doesn't hang the process forever.
|
|
1245
|
+
*/
|
|
1246
|
+
cancelAll() {
|
|
1247
|
+
for (const [approvalRequestId, entry] of this.pending) {
|
|
1248
|
+
entry.resolve(
|
|
1249
|
+
new ApprovalResult({
|
|
1250
|
+
decision: "cancelled",
|
|
1251
|
+
feedback: "agent shutting down",
|
|
1252
|
+
approvalRequestId
|
|
1253
|
+
})
|
|
1254
|
+
);
|
|
1255
|
+
}
|
|
1256
|
+
this.pending.clear();
|
|
1257
|
+
this.execToRequest.clear();
|
|
1258
|
+
}
|
|
1259
|
+
/** Number of currently-pending pauses. Useful for tests. */
|
|
1260
|
+
pendingCount() {
|
|
1261
|
+
return this.pending.size;
|
|
1262
|
+
}
|
|
1263
|
+
};
|
|
1264
|
+
function parseRawResponse(raw) {
|
|
1265
|
+
if (raw && typeof raw === "object") {
|
|
1266
|
+
return raw;
|
|
1267
|
+
}
|
|
1268
|
+
if (typeof raw === "string" && raw.trim()) {
|
|
1269
|
+
try {
|
|
1270
|
+
const parsed = JSON.parse(raw);
|
|
1271
|
+
if (parsed && typeof parsed === "object") {
|
|
1272
|
+
return parsed;
|
|
1273
|
+
}
|
|
1274
|
+
} catch {
|
|
1275
|
+
return { text: raw };
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
return void 0;
|
|
1279
|
+
}
|
|
1280
|
+
function installApprovalWebhookRoute(app, manager, logger) {
|
|
1281
|
+
app.post(
|
|
1282
|
+
"/webhooks/approval",
|
|
1283
|
+
(req, res) => {
|
|
1284
|
+
const body = req.body ?? {};
|
|
1285
|
+
const executionId = String(body.execution_id ?? "");
|
|
1286
|
+
const approvalRequestId = String(body.approval_request_id ?? "");
|
|
1287
|
+
const decision = String(body.decision ?? "");
|
|
1288
|
+
const feedback = typeof body.feedback === "string" ? body.feedback : "";
|
|
1289
|
+
const rawResponse = parseRawResponse(body.response);
|
|
1290
|
+
const result = new ApprovalResult({
|
|
1291
|
+
decision,
|
|
1292
|
+
feedback,
|
|
1293
|
+
executionId,
|
|
1294
|
+
approvalRequestId,
|
|
1295
|
+
rawResponse
|
|
1296
|
+
});
|
|
1297
|
+
let resolved = false;
|
|
1298
|
+
if (approvalRequestId) {
|
|
1299
|
+
resolved = manager.resolve(approvalRequestId, result);
|
|
1300
|
+
}
|
|
1301
|
+
if (!resolved && executionId) {
|
|
1302
|
+
resolved = manager.resolveByExecutionId(executionId, result);
|
|
1303
|
+
}
|
|
1304
|
+
logger?.info("approval-webhook received", {
|
|
1305
|
+
executionId,
|
|
1306
|
+
approvalRequestId,
|
|
1307
|
+
decision,
|
|
1308
|
+
resolved
|
|
1309
|
+
});
|
|
1310
|
+
res.status(200).json({ status: "received", resolved });
|
|
1311
|
+
}
|
|
1312
|
+
);
|
|
1313
|
+
}
|
|
1314
|
+
var httpAgent = new http.Agent({
|
|
1315
|
+
keepAlive: true,
|
|
1316
|
+
maxSockets: 10,
|
|
1317
|
+
maxTotalSockets: 50,
|
|
1318
|
+
maxFreeSockets: 5
|
|
1319
|
+
});
|
|
1320
|
+
var httpsAgent = new https.Agent({
|
|
1321
|
+
keepAlive: true,
|
|
1322
|
+
maxSockets: 10,
|
|
1323
|
+
maxTotalSockets: 50,
|
|
1324
|
+
maxFreeSockets: 5
|
|
1325
|
+
});
|
|
1326
|
+
|
|
1327
|
+
// src/approval/ApprovalClient.ts
|
|
1328
|
+
var ApprovalClient = class {
|
|
1329
|
+
http;
|
|
1330
|
+
nodeId;
|
|
1331
|
+
headers;
|
|
1332
|
+
constructor(opts) {
|
|
1333
|
+
this.http = axios5.create({
|
|
1334
|
+
baseURL: opts.baseURL.replace(/\/$/, ""),
|
|
1335
|
+
timeout: 3e4,
|
|
1336
|
+
httpAgent,
|
|
1337
|
+
httpsAgent
|
|
1338
|
+
});
|
|
1339
|
+
this.nodeId = opts.nodeId;
|
|
1340
|
+
const merged = { ...opts.headers ?? {} };
|
|
1341
|
+
if (opts.apiKey) {
|
|
1342
|
+
merged["X-API-Key"] = opts.apiKey;
|
|
1343
|
+
}
|
|
1344
|
+
this.headers = merged;
|
|
1345
|
+
}
|
|
1346
|
+
/**
|
|
1347
|
+
* Request human approval, transitioning the execution to `waiting`.
|
|
1348
|
+
*
|
|
1349
|
+
* Calls `POST /api/v1/agents/{node}/executions/{id}/request-approval`.
|
|
1350
|
+
*/
|
|
1351
|
+
async requestApproval(executionId, payload) {
|
|
1352
|
+
const body = {
|
|
1353
|
+
approval_request_id: payload.approvalRequestId,
|
|
1354
|
+
expires_in_hours: payload.expiresInHours ?? 72
|
|
1355
|
+
};
|
|
1356
|
+
if (payload.approvalRequestUrl) {
|
|
1357
|
+
body.approval_request_url = payload.approvalRequestUrl;
|
|
1358
|
+
}
|
|
1359
|
+
if (payload.callbackUrl) {
|
|
1360
|
+
body.callback_url = payload.callbackUrl;
|
|
1361
|
+
}
|
|
1362
|
+
const res = await this.http.post(
|
|
1363
|
+
`/api/v1/agents/${encodeURIComponent(this.nodeId)}/executions/${encodeURIComponent(executionId)}/request-approval`,
|
|
1364
|
+
body,
|
|
1365
|
+
{ headers: { ...this.headers, "Content-Type": "application/json" } }
|
|
1366
|
+
);
|
|
1367
|
+
return {
|
|
1368
|
+
approvalRequestId: res.data.approval_request_id ?? "",
|
|
1369
|
+
approvalRequestUrl: res.data.approval_request_url ?? ""
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
/**
|
|
1373
|
+
* Get the current approval status for an execution.
|
|
1374
|
+
*
|
|
1375
|
+
* Calls `GET /api/v1/agents/{node}/executions/{id}/approval-status`.
|
|
1376
|
+
*/
|
|
1377
|
+
async getApprovalStatus(executionId) {
|
|
1378
|
+
const res = await this.http.get(
|
|
1379
|
+
`/api/v1/agents/${encodeURIComponent(this.nodeId)}/executions/${encodeURIComponent(executionId)}/approval-status`,
|
|
1380
|
+
{ headers: this.headers }
|
|
1381
|
+
);
|
|
1382
|
+
const data = res.data;
|
|
1383
|
+
return {
|
|
1384
|
+
status: data.status ?? "pending",
|
|
1385
|
+
response: data.response,
|
|
1386
|
+
requestUrl: data.request_url,
|
|
1387
|
+
requestedAt: data.requested_at,
|
|
1388
|
+
respondedAt: data.responded_at
|
|
1389
|
+
};
|
|
1390
|
+
}
|
|
1391
|
+
/**
|
|
1392
|
+
* Poll approval status with exponential backoff until resolved.
|
|
1393
|
+
*
|
|
1394
|
+
* Returns once the status is no longer `pending` (i.e. approved, rejected,
|
|
1395
|
+
* or expired).
|
|
1396
|
+
*/
|
|
1397
|
+
async waitForApproval(executionId, opts) {
|
|
1398
|
+
const pollInterval = opts?.pollIntervalMs ?? 5e3;
|
|
1399
|
+
const maxInterval = opts?.maxIntervalMs ?? 6e4;
|
|
1400
|
+
const timeout = opts?.timeoutMs;
|
|
1401
|
+
const backoffFactor = 2;
|
|
1402
|
+
const startTime = Date.now();
|
|
1403
|
+
let interval = pollInterval;
|
|
1404
|
+
while (true) {
|
|
1405
|
+
if (timeout != null && Date.now() - startTime >= timeout) {
|
|
1406
|
+
throw new Error(
|
|
1407
|
+
`Approval for execution ${executionId} timed out after ${timeout}ms`
|
|
1408
|
+
);
|
|
1409
|
+
}
|
|
1410
|
+
await sleep(interval);
|
|
1411
|
+
let data;
|
|
1412
|
+
try {
|
|
1413
|
+
data = await this.getApprovalStatus(executionId);
|
|
1414
|
+
} catch {
|
|
1415
|
+
interval = Math.min(interval * backoffFactor, maxInterval);
|
|
1416
|
+
continue;
|
|
1417
|
+
}
|
|
1418
|
+
if (data.status !== "pending") {
|
|
1419
|
+
return data;
|
|
1420
|
+
}
|
|
1421
|
+
interval = Math.min(interval * backoffFactor, maxInterval);
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
};
|
|
1425
|
+
function sleep(ms) {
|
|
1426
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1427
|
+
}
|
|
1136
1428
|
var store = new AsyncLocalStorage();
|
|
1137
1429
|
var ExecutionContext = class {
|
|
1138
1430
|
input;
|
|
@@ -1543,6 +1835,19 @@ var ReasonerContext = class {
|
|
|
1543
1835
|
call(target, input) {
|
|
1544
1836
|
return this.agent.call(target, input);
|
|
1545
1837
|
}
|
|
1838
|
+
/**
|
|
1839
|
+
* Pause this execution for external approval / resumption.
|
|
1840
|
+
*
|
|
1841
|
+
* Transitions the execution to `waiting` on the control plane and blocks
|
|
1842
|
+
* until a decision arrives via the agent's approval webhook, or the timeout
|
|
1843
|
+
* elapses (returning `{ decision: 'expired' }`). The caller creates the
|
|
1844
|
+
* approval request on an external service first and passes its
|
|
1845
|
+
* `approvalRequestId`. Delegates to {@link Agent.pause}. See its docs for the
|
|
1846
|
+
* async-execution requirement that lets a pause outlive the dispatch ceiling.
|
|
1847
|
+
*/
|
|
1848
|
+
pause(opts) {
|
|
1849
|
+
return this.agent.pause({ ...opts, executionId: this.executionId });
|
|
1850
|
+
}
|
|
1546
1851
|
discover(options) {
|
|
1547
1852
|
return this.agent.discover(options);
|
|
1548
1853
|
}
|
|
@@ -2224,18 +2529,6 @@ var ExecutionLogger = class {
|
|
|
2224
2529
|
function createExecutionLogger(options = {}) {
|
|
2225
2530
|
return new ExecutionLogger(options);
|
|
2226
2531
|
}
|
|
2227
|
-
var httpAgent = new http.Agent({
|
|
2228
|
-
keepAlive: true,
|
|
2229
|
-
maxSockets: 10,
|
|
2230
|
-
maxTotalSockets: 50,
|
|
2231
|
-
maxFreeSockets: 5
|
|
2232
|
-
});
|
|
2233
|
-
var httpsAgent = new https.Agent({
|
|
2234
|
-
keepAlive: true,
|
|
2235
|
-
maxSockets: 10,
|
|
2236
|
-
maxTotalSockets: 50,
|
|
2237
|
-
maxFreeSockets: 5
|
|
2238
|
-
});
|
|
2239
2532
|
var HEADER_CALLER_DID = "X-Caller-DID";
|
|
2240
2533
|
var HEADER_DID_SIGNATURE = "X-DID-Signature";
|
|
2241
2534
|
var HEADER_DID_TIMESTAMP = "X-DID-Timestamp";
|
|
@@ -2318,6 +2611,67 @@ function parsePrivateKeyJWK(jwkJSON) {
|
|
|
2318
2611
|
});
|
|
2319
2612
|
}
|
|
2320
2613
|
|
|
2614
|
+
// src/status/ExecutionStatus.ts
|
|
2615
|
+
var ExecutionStatus = {
|
|
2616
|
+
PENDING: "pending",
|
|
2617
|
+
QUEUED: "queued",
|
|
2618
|
+
WAITING: "waiting",
|
|
2619
|
+
RUNNING: "running",
|
|
2620
|
+
SUCCEEDED: "succeeded",
|
|
2621
|
+
FAILED: "failed",
|
|
2622
|
+
CANCELLED: "cancelled",
|
|
2623
|
+
TIMEOUT: "timeout",
|
|
2624
|
+
UNKNOWN: "unknown"
|
|
2625
|
+
};
|
|
2626
|
+
var CANONICAL_STATUSES = new Set(
|
|
2627
|
+
Object.values(ExecutionStatus)
|
|
2628
|
+
);
|
|
2629
|
+
var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
|
|
2630
|
+
ExecutionStatus.SUCCEEDED,
|
|
2631
|
+
ExecutionStatus.FAILED,
|
|
2632
|
+
ExecutionStatus.CANCELLED,
|
|
2633
|
+
ExecutionStatus.TIMEOUT
|
|
2634
|
+
]);
|
|
2635
|
+
var ACTIVE_STATUSES = /* @__PURE__ */ new Set([
|
|
2636
|
+
ExecutionStatus.PENDING,
|
|
2637
|
+
ExecutionStatus.QUEUED,
|
|
2638
|
+
ExecutionStatus.WAITING,
|
|
2639
|
+
ExecutionStatus.RUNNING
|
|
2640
|
+
]);
|
|
2641
|
+
var STATUS_ALIASES = {
|
|
2642
|
+
success: "succeeded",
|
|
2643
|
+
successful: "succeeded",
|
|
2644
|
+
completed: "succeeded",
|
|
2645
|
+
complete: "succeeded",
|
|
2646
|
+
done: "succeeded",
|
|
2647
|
+
ok: "succeeded",
|
|
2648
|
+
error: "failed",
|
|
2649
|
+
failure: "failed",
|
|
2650
|
+
errored: "failed",
|
|
2651
|
+
canceled: "cancelled",
|
|
2652
|
+
cancel: "cancelled",
|
|
2653
|
+
timed_out: "timeout",
|
|
2654
|
+
wait: "queued",
|
|
2655
|
+
awaiting_approval: "waiting",
|
|
2656
|
+
awaiting_human: "waiting",
|
|
2657
|
+
approval_pending: "waiting",
|
|
2658
|
+
in_progress: "running",
|
|
2659
|
+
processing: "running"
|
|
2660
|
+
};
|
|
2661
|
+
function normalizeStatus(status) {
|
|
2662
|
+
if (status == null) return "unknown";
|
|
2663
|
+
const normalized = status.trim().toLowerCase();
|
|
2664
|
+
if (!normalized) return "unknown";
|
|
2665
|
+
if (CANONICAL_STATUSES.has(normalized)) return normalized;
|
|
2666
|
+
return STATUS_ALIASES[normalized] ?? "unknown";
|
|
2667
|
+
}
|
|
2668
|
+
function isTerminal(status) {
|
|
2669
|
+
return TERMINAL_STATUSES.has(normalizeStatus(status));
|
|
2670
|
+
}
|
|
2671
|
+
function isActive(status) {
|
|
2672
|
+
return ACTIVE_STATUSES.has(normalizeStatus(status));
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2321
2675
|
// src/client/AgentFieldClient.ts
|
|
2322
2676
|
var AgentFieldClient = class {
|
|
2323
2677
|
http;
|
|
@@ -2326,7 +2680,7 @@ var AgentFieldClient = class {
|
|
|
2326
2680
|
didAuthenticator;
|
|
2327
2681
|
constructor(config) {
|
|
2328
2682
|
const baseURL = (config.agentFieldUrl ?? "http://localhost:8080").replace(/\/$/, "");
|
|
2329
|
-
this.http =
|
|
2683
|
+
this.http = axios5.create({
|
|
2330
2684
|
baseURL,
|
|
2331
2685
|
timeout: 3e4,
|
|
2332
2686
|
httpAgent,
|
|
@@ -2366,21 +2720,7 @@ var AgentFieldClient = class {
|
|
|
2366
2720
|
return res.data;
|
|
2367
2721
|
}
|
|
2368
2722
|
async execute(target, input, metadata) {
|
|
2369
|
-
const headers =
|
|
2370
|
-
if (metadata?.runId) headers["X-Run-ID"] = metadata.runId;
|
|
2371
|
-
if (metadata?.workflowId) headers["X-Workflow-ID"] = metadata.workflowId;
|
|
2372
|
-
if (metadata?.rootWorkflowId) headers["X-Root-Workflow-ID"] = metadata.rootWorkflowId;
|
|
2373
|
-
if (metadata?.parentExecutionId) headers["X-Parent-Execution-ID"] = metadata.parentExecutionId;
|
|
2374
|
-
if (metadata?.reasonerId) headers["X-Reasoner-ID"] = metadata.reasonerId;
|
|
2375
|
-
if (metadata?.sessionId) headers["X-Session-ID"] = metadata.sessionId;
|
|
2376
|
-
if (metadata?.actorId) headers["X-Actor-ID"] = metadata.actorId;
|
|
2377
|
-
if (metadata?.callerDid) headers["X-Caller-DID"] = metadata.callerDid;
|
|
2378
|
-
if (metadata?.targetDid) headers["X-Target-DID"] = metadata.targetDid;
|
|
2379
|
-
if (metadata?.agentNodeDid) headers["X-Agent-Node-DID"] = metadata.agentNodeDid;
|
|
2380
|
-
if (metadata?.agentNodeId) headers["X-Agent-Node-ID"] = metadata.agentNodeId;
|
|
2381
|
-
if (metadata?.replaySourceRunId) headers["X-AgentField-Replay-Source-Run-ID"] = metadata.replaySourceRunId;
|
|
2382
|
-
if (metadata?.replayBeforeExecutionId) headers["X-AgentField-Replay-Before-Execution-ID"] = metadata.replayBeforeExecutionId;
|
|
2383
|
-
if (metadata?.replayMode) headers["X-AgentField-Replay-Mode"] = metadata.replayMode;
|
|
2723
|
+
const headers = this.buildExecuteHeaders(metadata);
|
|
2384
2724
|
const bodyStr = JSON.stringify({ input });
|
|
2385
2725
|
const authHeaders = this.didAuthenticator.signRequest(Buffer.from(bodyStr));
|
|
2386
2726
|
try {
|
|
@@ -2502,6 +2842,219 @@ var AgentFieldClient = class {
|
|
|
2502
2842
|
headers: this.mergeHeaders({ "Content-Type": "application/json", ...authHeaders })
|
|
2503
2843
|
});
|
|
2504
2844
|
}
|
|
2845
|
+
/** Build the `X-*` dispatch headers shared by execute / executeAsync. */
|
|
2846
|
+
buildExecuteHeaders(metadata) {
|
|
2847
|
+
const headers = {};
|
|
2848
|
+
if (metadata?.runId) headers["X-Run-ID"] = metadata.runId;
|
|
2849
|
+
if (metadata?.workflowId) headers["X-Workflow-ID"] = metadata.workflowId;
|
|
2850
|
+
if (metadata?.rootWorkflowId) headers["X-Root-Workflow-ID"] = metadata.rootWorkflowId;
|
|
2851
|
+
if (metadata?.parentExecutionId) headers["X-Parent-Execution-ID"] = metadata.parentExecutionId;
|
|
2852
|
+
if (metadata?.reasonerId) headers["X-Reasoner-ID"] = metadata.reasonerId;
|
|
2853
|
+
if (metadata?.sessionId) headers["X-Session-ID"] = metadata.sessionId;
|
|
2854
|
+
if (metadata?.actorId) headers["X-Actor-ID"] = metadata.actorId;
|
|
2855
|
+
if (metadata?.callerDid) headers["X-Caller-DID"] = metadata.callerDid;
|
|
2856
|
+
if (metadata?.targetDid) headers["X-Target-DID"] = metadata.targetDid;
|
|
2857
|
+
if (metadata?.agentNodeDid) headers["X-Agent-Node-DID"] = metadata.agentNodeDid;
|
|
2858
|
+
if (metadata?.agentNodeId) headers["X-Agent-Node-ID"] = metadata.agentNodeId;
|
|
2859
|
+
if (metadata?.replaySourceRunId) headers["X-AgentField-Replay-Source-Run-ID"] = metadata.replaySourceRunId;
|
|
2860
|
+
if (metadata?.replayBeforeExecutionId) headers["X-AgentField-Replay-Before-Execution-ID"] = metadata.replayBeforeExecutionId;
|
|
2861
|
+
if (metadata?.replayMode) headers["X-AgentField-Replay-Mode"] = metadata.replayMode;
|
|
2862
|
+
return headers;
|
|
2863
|
+
}
|
|
2864
|
+
/**
|
|
2865
|
+
* Submit an async execution and return its `execution_id`.
|
|
2866
|
+
*
|
|
2867
|
+
* POSTs to `/api/v1/execute/async/{target}`, which enqueues the execution
|
|
2868
|
+
* and responds `202 Accepted` immediately (the control plane runs it and
|
|
2869
|
+
* tracks status out-of-band). Use with {@link waitForExecutionResult} to
|
|
2870
|
+
* poll for the terminal result without holding a synchronous connection —
|
|
2871
|
+
* this is what lets a parent await a descendant that legitimately pauses
|
|
2872
|
+
* (WAITING) for a long time without hitting the dispatch ceiling.
|
|
2873
|
+
*/
|
|
2874
|
+
async executeAsync(target, input, metadata) {
|
|
2875
|
+
const headers = this.buildExecuteHeaders(metadata);
|
|
2876
|
+
const bodyStr = JSON.stringify({ input });
|
|
2877
|
+
const authHeaders = this.didAuthenticator.signRequest(Buffer.from(bodyStr));
|
|
2878
|
+
try {
|
|
2879
|
+
const res = await this.http.post(
|
|
2880
|
+
`/api/v1/execute/async/${target}`,
|
|
2881
|
+
bodyStr,
|
|
2882
|
+
{ headers: this.mergeHeaders({ "Content-Type": "application/json", ...headers, ...authHeaders }) }
|
|
2883
|
+
);
|
|
2884
|
+
const executionId = res.data?.execution_id ?? res.data?.executionId ?? (typeof res.headers?.["x-execution-id"] === "string" ? res.headers["x-execution-id"] : void 0);
|
|
2885
|
+
if (!executionId) {
|
|
2886
|
+
throw new Error(`execute async ${target} returned no execution_id`);
|
|
2887
|
+
}
|
|
2888
|
+
return executionId;
|
|
2889
|
+
} catch (err) {
|
|
2890
|
+
const respData = err?.response?.data;
|
|
2891
|
+
if (respData) {
|
|
2892
|
+
const status = err.response.status;
|
|
2893
|
+
const msg = respData.message || respData.error || JSON.stringify(respData);
|
|
2894
|
+
const enriched = Object.assign(
|
|
2895
|
+
new Error(`execute async ${target} failed (${status}): ${msg}`),
|
|
2896
|
+
{ status, responseData: respData }
|
|
2897
|
+
);
|
|
2898
|
+
throw enriched;
|
|
2899
|
+
}
|
|
2900
|
+
throw err;
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
/** Fetch the current status snapshot for an execution. */
|
|
2904
|
+
async getExecutionStatus(executionId) {
|
|
2905
|
+
const res = await this.http.get(
|
|
2906
|
+
`/api/v1/executions/${encodeURIComponent(executionId)}`,
|
|
2907
|
+
{ headers: this.mergeHeaders({}) }
|
|
2908
|
+
);
|
|
2909
|
+
const data = res.data ?? {};
|
|
2910
|
+
return {
|
|
2911
|
+
executionId: data.execution_id ?? executionId,
|
|
2912
|
+
status: data.status ?? "unknown",
|
|
2913
|
+
statusReason: data.status_reason ?? void 0,
|
|
2914
|
+
result: data.result,
|
|
2915
|
+
error: data.error ?? void 0,
|
|
2916
|
+
errorDetails: data.error_details,
|
|
2917
|
+
durationMs: data.duration_ms ?? void 0
|
|
2918
|
+
};
|
|
2919
|
+
}
|
|
2920
|
+
/**
|
|
2921
|
+
* Poll an async execution until it reaches a terminal status, returning its
|
|
2922
|
+
* result (or throwing on failure/cancellation/timeout).
|
|
2923
|
+
*
|
|
2924
|
+
* While polling, this observes the child's status transitions: when the
|
|
2925
|
+
* child enters `waiting` it starts the supplied pause-clock and fires
|
|
2926
|
+
* `onChildWaiting`; when the child leaves `waiting` it stops the clock and
|
|
2927
|
+
* fires `onChildRunning`. Paused time is excluded from `timeoutMs`, so a
|
|
2928
|
+
* parent awaiting a paused descendant does not time out while the descendant
|
|
2929
|
+
* legitimately waits. Mirrors the Python SDK's `wait_for_execution_result`.
|
|
2930
|
+
*/
|
|
2931
|
+
async waitForExecutionResult(executionId, opts = {}) {
|
|
2932
|
+
const pollInterval = opts.pollIntervalMs ?? 1e3;
|
|
2933
|
+
const maxInterval = opts.maxIntervalMs ?? 5e3;
|
|
2934
|
+
const pauseClock = opts.pauseClock;
|
|
2935
|
+
const start = Date.now();
|
|
2936
|
+
let interval = pollInterval;
|
|
2937
|
+
let childWaiting = false;
|
|
2938
|
+
const activeElapsed = () => Date.now() - start - (pauseClock?.totalPaused() ?? 0);
|
|
2939
|
+
try {
|
|
2940
|
+
while (true) {
|
|
2941
|
+
let snapshot;
|
|
2942
|
+
try {
|
|
2943
|
+
snapshot = await this.getExecutionStatus(executionId);
|
|
2944
|
+
} catch {
|
|
2945
|
+
if (opts.timeoutMs != null && activeElapsed() >= opts.timeoutMs) {
|
|
2946
|
+
throw new Error(`waitForExecutionResult(${executionId}) timed out`);
|
|
2947
|
+
}
|
|
2948
|
+
await sleep2(interval);
|
|
2949
|
+
interval = Math.min(interval * 2, maxInterval);
|
|
2950
|
+
continue;
|
|
2951
|
+
}
|
|
2952
|
+
const status = normalizeStatus(snapshot.status);
|
|
2953
|
+
if (status === "waiting" && !childWaiting) {
|
|
2954
|
+
childWaiting = true;
|
|
2955
|
+
pauseClock?.startPause();
|
|
2956
|
+
await safePauseCallback(opts.onChildWaiting);
|
|
2957
|
+
} else if (status !== "waiting" && childWaiting) {
|
|
2958
|
+
childWaiting = false;
|
|
2959
|
+
pauseClock?.endPause();
|
|
2960
|
+
await safePauseCallback(opts.onChildRunning);
|
|
2961
|
+
}
|
|
2962
|
+
if (isTerminal(status)) {
|
|
2963
|
+
if (status === "succeeded") {
|
|
2964
|
+
return snapshot.result?.result ?? snapshot.result;
|
|
2965
|
+
}
|
|
2966
|
+
const detail = snapshot.error || snapshot.statusReason || status;
|
|
2967
|
+
const failure = Object.assign(
|
|
2968
|
+
new Error(`execution ${executionId} ${status}: ${detail}`),
|
|
2969
|
+
{ status: 0, responseData: snapshot.errorDetails ?? snapshot.error }
|
|
2970
|
+
);
|
|
2971
|
+
throw failure;
|
|
2972
|
+
}
|
|
2973
|
+
if (opts.timeoutMs != null && activeElapsed() >= opts.timeoutMs) {
|
|
2974
|
+
throw new Error(`waitForExecutionResult(${executionId}) timed out`);
|
|
2975
|
+
}
|
|
2976
|
+
await sleep2(interval);
|
|
2977
|
+
interval = Math.min(interval * 2, maxInterval);
|
|
2978
|
+
}
|
|
2979
|
+
} finally {
|
|
2980
|
+
if (childWaiting) {
|
|
2981
|
+
pauseClock?.endPause();
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2985
|
+
/**
|
|
2986
|
+
* Notify the control plane that THIS execution is now `waiting` or `running`
|
|
2987
|
+
* because of its awaited child's state — the multi-hop pause propagation
|
|
2988
|
+
* hook. Distinct from request-approval: no approval id, no webhook, no human.
|
|
2989
|
+
* It exists purely so ancestors watching this execution see WAITING
|
|
2990
|
+
* transitively while a descendant is paused, and stop counting wall-clock.
|
|
2991
|
+
*
|
|
2992
|
+
* POSTs to `/api/v1/agents/{node}/executions/{id}/awaiter-status`.
|
|
2993
|
+
*/
|
|
2994
|
+
async notifyAwaiterStatus(executionId, status, reason = "") {
|
|
2995
|
+
if (status !== "waiting" && status !== "running") {
|
|
2996
|
+
throw new Error(`notifyAwaiterStatus: status must be 'waiting' or 'running', got '${status}'`);
|
|
2997
|
+
}
|
|
2998
|
+
const nodeId = this.config.nodeId ?? "";
|
|
2999
|
+
const payload = { status };
|
|
3000
|
+
if (reason) payload.reason = reason;
|
|
3001
|
+
const bodyStr = JSON.stringify(payload);
|
|
3002
|
+
const authHeaders = this.didAuthenticator.signRequest(Buffer.from(bodyStr));
|
|
3003
|
+
const res = await this.http.post(
|
|
3004
|
+
`/api/v1/agents/${encodeURIComponent(nodeId)}/executions/${encodeURIComponent(executionId)}/awaiter-status`,
|
|
3005
|
+
bodyStr,
|
|
3006
|
+
{
|
|
3007
|
+
headers: this.mergeHeaders({ "Content-Type": "application/json", ...authHeaders }),
|
|
3008
|
+
timeout: 1e4
|
|
3009
|
+
}
|
|
3010
|
+
);
|
|
3011
|
+
if (res.status >= 400) {
|
|
3012
|
+
throw new Error(`awaiter-status update failed (${res.status})`);
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
/**
|
|
3016
|
+
* Deliver a terminal execution result to the control plane, with retries.
|
|
3017
|
+
*
|
|
3018
|
+
* This is the out-of-band completion callback used by async-execution
|
|
3019
|
+
* dispatch: after a reasoner has been 202-acked and run detached, its final
|
|
3020
|
+
* status (`succeeded` / `failed` / `cancelled`) is POSTed to
|
|
3021
|
+
* `/api/v1/executions/{id}/status`. Retries with exponential backoff because
|
|
3022
|
+
* a dropped terminal callback would leave the execution stuck forever.
|
|
3023
|
+
*/
|
|
3024
|
+
async reportExecutionResult(executionId, payload, maxRetries = 5) {
|
|
3025
|
+
const body = {
|
|
3026
|
+
status: payload.status,
|
|
3027
|
+
execution_id: executionId,
|
|
3028
|
+
duration_ms: payload.durationMs,
|
|
3029
|
+
completed_at: payload.completedAt,
|
|
3030
|
+
reasoner: payload.reasoner
|
|
3031
|
+
};
|
|
3032
|
+
if (payload.result !== void 0) body.result = wrapResult(payload.result);
|
|
3033
|
+
if (payload.error !== void 0) body.error = payload.error;
|
|
3034
|
+
if (payload.errorDetails !== void 0) body.error_details = payload.errorDetails;
|
|
3035
|
+
const bodyStr = JSON.stringify(body);
|
|
3036
|
+
const authHeaders = this.didAuthenticator.signRequest(Buffer.from(bodyStr));
|
|
3037
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
3038
|
+
try {
|
|
3039
|
+
const res = await this.http.post(
|
|
3040
|
+
`/api/v1/executions/${encodeURIComponent(executionId)}/status`,
|
|
3041
|
+
bodyStr,
|
|
3042
|
+
{
|
|
3043
|
+
headers: this.mergeHeaders({ "Content-Type": "application/json", ...authHeaders }),
|
|
3044
|
+
timeout: 3e4
|
|
3045
|
+
}
|
|
3046
|
+
);
|
|
3047
|
+
if (res.status >= 200 && res.status < 300) {
|
|
3048
|
+
return true;
|
|
3049
|
+
}
|
|
3050
|
+
} catch {
|
|
3051
|
+
}
|
|
3052
|
+
if (attempt < maxRetries - 1) {
|
|
3053
|
+
await sleep2(2 ** attempt * 1e3);
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
return false;
|
|
3057
|
+
}
|
|
2505
3058
|
async discoverCapabilities(options = {}) {
|
|
2506
3059
|
const format = (options.format ?? "json").toLowerCase();
|
|
2507
3060
|
const params = { format };
|
|
@@ -2670,7 +3223,7 @@ var AgentFieldClient = class {
|
|
|
2670
3223
|
...executionHeaders,
|
|
2671
3224
|
...authHeaders
|
|
2672
3225
|
});
|
|
2673
|
-
|
|
3226
|
+
axios5.post(`${uiApiBaseUrl}/executions/note`, bodyStr, {
|
|
2674
3227
|
headers,
|
|
2675
3228
|
timeout: devMode ? 5e3 : 1e4,
|
|
2676
3229
|
httpAgent,
|
|
@@ -2679,11 +3232,30 @@ var AgentFieldClient = class {
|
|
|
2679
3232
|
});
|
|
2680
3233
|
}
|
|
2681
3234
|
};
|
|
3235
|
+
function sleep2(ms) {
|
|
3236
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
3237
|
+
}
|
|
3238
|
+
async function safePauseCallback(cb) {
|
|
3239
|
+
if (!cb) return;
|
|
3240
|
+
try {
|
|
3241
|
+
await Promise.race([
|
|
3242
|
+
Promise.resolve().then(cb),
|
|
3243
|
+
sleep2(2e3)
|
|
3244
|
+
]);
|
|
3245
|
+
} catch {
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
3248
|
+
function wrapResult(result) {
|
|
3249
|
+
if (result !== null && typeof result === "object" && !Array.isArray(result)) {
|
|
3250
|
+
return result;
|
|
3251
|
+
}
|
|
3252
|
+
return { result };
|
|
3253
|
+
}
|
|
2682
3254
|
var MemoryClientBase = class {
|
|
2683
3255
|
http;
|
|
2684
3256
|
defaultHeaders;
|
|
2685
3257
|
constructor(baseUrl, defaultHeaders) {
|
|
2686
|
-
this.http =
|
|
3258
|
+
this.http = axios5.create({
|
|
2687
3259
|
baseURL: baseUrl.replace(/\/$/, ""),
|
|
2688
3260
|
timeout: 3e4,
|
|
2689
3261
|
httpAgent,
|
|
@@ -3111,7 +3683,7 @@ var DidClient = class {
|
|
|
3111
3683
|
http;
|
|
3112
3684
|
defaultHeaders;
|
|
3113
3685
|
constructor(baseUrl, defaultHeaders) {
|
|
3114
|
-
this.http =
|
|
3686
|
+
this.http = axios5.create({
|
|
3115
3687
|
baseURL: baseUrl.replace(/\/$/, ""),
|
|
3116
3688
|
timeout: 3e4,
|
|
3117
3689
|
httpAgent,
|
|
@@ -3554,7 +4126,7 @@ var LocalVerifier = class {
|
|
|
3554
4126
|
}
|
|
3555
4127
|
let success = true;
|
|
3556
4128
|
try {
|
|
3557
|
-
const resp = await
|
|
4129
|
+
const resp = await axios5.get(`${this.agentFieldUrl}/api/v1/policies`, {
|
|
3558
4130
|
headers,
|
|
3559
4131
|
timeout: 1e4
|
|
3560
4132
|
});
|
|
@@ -3567,7 +4139,7 @@ var LocalVerifier = class {
|
|
|
3567
4139
|
success = false;
|
|
3568
4140
|
}
|
|
3569
4141
|
try {
|
|
3570
|
-
const resp = await
|
|
4142
|
+
const resp = await axios5.get(`${this.agentFieldUrl}/api/v1/revocations`, {
|
|
3571
4143
|
headers,
|
|
3572
4144
|
timeout: 1e4
|
|
3573
4145
|
});
|
|
@@ -3580,7 +4152,7 @@ var LocalVerifier = class {
|
|
|
3580
4152
|
success = false;
|
|
3581
4153
|
}
|
|
3582
4154
|
try {
|
|
3583
|
-
const resp = await
|
|
4155
|
+
const resp = await axios5.get(`${this.agentFieldUrl}/api/v1/registered-dids`, {
|
|
3584
4156
|
headers,
|
|
3585
4157
|
timeout: 1e4
|
|
3586
4158
|
});
|
|
@@ -3593,7 +4165,7 @@ var LocalVerifier = class {
|
|
|
3593
4165
|
success = false;
|
|
3594
4166
|
}
|
|
3595
4167
|
try {
|
|
3596
|
-
const resp = await
|
|
4168
|
+
const resp = await axios5.get(`${this.agentFieldUrl}/api/v1/admin/public-key`, {
|
|
3597
4169
|
headers,
|
|
3598
4170
|
timeout: 1e4
|
|
3599
4171
|
});
|
|
@@ -4025,6 +4597,17 @@ var Agent = class {
|
|
|
4025
4597
|
* code that respects `signal.aborted` (fetch, anthropic SDK, openai
|
|
4026
4598
|
* SDK, etc.). See ./cancel.ts. */
|
|
4027
4599
|
cancelRegistry = new CancelRegistry();
|
|
4600
|
+
/** Registry of pending `ctx.pause()` promises, resolved by the
|
|
4601
|
+
* `/webhooks/approval` route when the control plane delivers a decision.
|
|
4602
|
+
* See ./pause.ts. */
|
|
4603
|
+
pauseManager = new PauseManager();
|
|
4604
|
+
/** Per-execution pause clocks, keyed by execution_id. Present only for
|
|
4605
|
+
* detached (async-execution) reasoners; used to exclude pause/await time
|
|
4606
|
+
* from the reasoner's active wall-clock budget and from an awaiter's wait
|
|
4607
|
+
* timeout. See ./pause.ts. */
|
|
4608
|
+
pauseClocks = /* @__PURE__ */ new Map();
|
|
4609
|
+
/** Execution-scoped approval client used by `Agent.pause()`. */
|
|
4610
|
+
approvalClient;
|
|
4028
4611
|
constructor(config) {
|
|
4029
4612
|
this.config = {
|
|
4030
4613
|
port: 8001,
|
|
@@ -4032,12 +4615,19 @@ var Agent = class {
|
|
|
4032
4615
|
host: "0.0.0.0",
|
|
4033
4616
|
...config,
|
|
4034
4617
|
didEnabled: config.didEnabled ?? true,
|
|
4035
|
-
deploymentType: config.deploymentType ?? "long_running"
|
|
4618
|
+
deploymentType: config.deploymentType ?? "long_running",
|
|
4619
|
+
asyncExecution: config.asyncExecution ?? true
|
|
4036
4620
|
};
|
|
4037
4621
|
this.app = express();
|
|
4038
4622
|
this.app.use(express.json());
|
|
4039
4623
|
this.aiClient = new AIClient(this.config.aiConfig);
|
|
4040
4624
|
this.agentFieldClient = new AgentFieldClient(this.config);
|
|
4625
|
+
this.approvalClient = new ApprovalClient({
|
|
4626
|
+
baseURL: this.config.agentFieldUrl ?? "http://localhost:8080",
|
|
4627
|
+
nodeId: this.config.nodeId,
|
|
4628
|
+
apiKey: this.config.apiKey,
|
|
4629
|
+
headers: this.sanitizeDefaultHeaders(this.config.defaultHeaders)
|
|
4630
|
+
});
|
|
4041
4631
|
this.memoryClient = new MemoryClient(this.config.agentFieldUrl, this.config.defaultHeaders);
|
|
4042
4632
|
this.memoryEventClient = new MemoryEventClient(this.config.agentFieldUrl, this.config.defaultHeaders);
|
|
4043
4633
|
this.didClient = new DidClient(this.config.agentFieldUrl, this.config.defaultHeaders);
|
|
@@ -4063,6 +4653,29 @@ var Agent = class {
|
|
|
4063
4653
|
installCancelRoute(this.app, this.cancelRegistry, {
|
|
4064
4654
|
info: (message, meta) => this.executionLogger.system("execution.cancel.received", message, meta ?? {})
|
|
4065
4655
|
});
|
|
4656
|
+
installApprovalWebhookRoute(this.app, this.pauseManager, {
|
|
4657
|
+
info: (message, meta) => this.executionLogger.system("execution.approval.received", message, meta ?? {})
|
|
4658
|
+
});
|
|
4659
|
+
}
|
|
4660
|
+
/** Coerce the loosely-typed `defaultHeaders` config into string headers. */
|
|
4661
|
+
sanitizeDefaultHeaders(headers) {
|
|
4662
|
+
const out = {};
|
|
4663
|
+
for (const [key, value] of Object.entries(headers ?? {})) {
|
|
4664
|
+
if (value !== void 0 && value !== null) {
|
|
4665
|
+
out[key] = String(value);
|
|
4666
|
+
}
|
|
4667
|
+
}
|
|
4668
|
+
return out;
|
|
4669
|
+
}
|
|
4670
|
+
/**
|
|
4671
|
+
* Resolve the externally-reachable base URL for this agent — the URL the
|
|
4672
|
+
* control plane uses to call back (approval webhook, cancel). Mirrors the
|
|
4673
|
+
* value published at registration time.
|
|
4674
|
+
*/
|
|
4675
|
+
resolvePublicUrl() {
|
|
4676
|
+
const port = this.config.port ?? 8001;
|
|
4677
|
+
const hostForUrl = this.config.publicUrl ? void 0 : this.config.host && this.config.host !== "0.0.0.0" ? this.config.host : "127.0.0.1";
|
|
4678
|
+
return this.config.publicUrl ?? `http://${hostForUrl ?? "127.0.0.1"}:${port}`;
|
|
4066
4679
|
}
|
|
4067
4680
|
reasoner(name, handler, options) {
|
|
4068
4681
|
this.reasoners.register(name, handler, options);
|
|
@@ -4185,6 +4798,84 @@ var Agent = class {
|
|
|
4185
4798
|
}
|
|
4186
4799
|
this.agentFieldClient.sendNote(message, tags, this.config.nodeId, execMetadata, uiApiUrl, this.config.devMode);
|
|
4187
4800
|
}
|
|
4801
|
+
/**
|
|
4802
|
+
* Pause the current execution for external approval / resumption.
|
|
4803
|
+
*
|
|
4804
|
+
* Transitions the execution to `waiting` on the control plane, then blocks
|
|
4805
|
+
* until the approval webhook callback resolves it or the timeout elapses.
|
|
4806
|
+
* The caller is responsible for creating the approval request on an external
|
|
4807
|
+
* service (e.g. hax-sdk) *before* calling this and passing the resulting
|
|
4808
|
+
* `approvalRequestId`.
|
|
4809
|
+
*
|
|
4810
|
+
* Requires the agent to be serving (a reachable callback URL) and, to survive
|
|
4811
|
+
* past the control plane's synchronous dispatch ceiling, requires
|
|
4812
|
+
* async-execution dispatch to be enabled (the default). When async dispatch
|
|
4813
|
+
* is disabled the pause still works but is bounded by that ceiling.
|
|
4814
|
+
*
|
|
4815
|
+
* Returns an {@link ApprovalResult}. On timeout it returns
|
|
4816
|
+
* `{ decision: 'expired' }` rather than throwing. Mirrors the Python SDK's
|
|
4817
|
+
* `Agent.pause()`.
|
|
4818
|
+
*/
|
|
4819
|
+
async pause(opts) {
|
|
4820
|
+
const executionId = opts.executionId ?? ExecutionContext.getCurrent()?.metadata.executionId;
|
|
4821
|
+
if (!executionId) {
|
|
4822
|
+
throw new Error("No execution_id available \u2014 cannot pause");
|
|
4823
|
+
}
|
|
4824
|
+
const callbackUrl = `${this.resolvePublicUrl()}/webhooks/approval`;
|
|
4825
|
+
const expiresInHours = opts.expiresInHours ?? 72;
|
|
4826
|
+
const future = this.pauseManager.register(opts.approvalRequestId, executionId);
|
|
4827
|
+
try {
|
|
4828
|
+
await this.approvalClient.requestApproval(executionId, {
|
|
4829
|
+
approvalRequestId: opts.approvalRequestId,
|
|
4830
|
+
approvalRequestUrl: opts.approvalRequestUrl,
|
|
4831
|
+
callbackUrl,
|
|
4832
|
+
expiresInHours
|
|
4833
|
+
});
|
|
4834
|
+
} catch (err) {
|
|
4835
|
+
this.pauseManager.resolve(
|
|
4836
|
+
opts.approvalRequestId,
|
|
4837
|
+
new ApprovalResult({
|
|
4838
|
+
decision: "error",
|
|
4839
|
+
feedback: "failed to notify control plane",
|
|
4840
|
+
executionId,
|
|
4841
|
+
approvalRequestId: opts.approvalRequestId
|
|
4842
|
+
})
|
|
4843
|
+
);
|
|
4844
|
+
throw err;
|
|
4845
|
+
}
|
|
4846
|
+
this.note(`Execution paused \u2014 waiting for approval ${opts.approvalRequestId}`, [
|
|
4847
|
+
"approval",
|
|
4848
|
+
"waiting"
|
|
4849
|
+
]);
|
|
4850
|
+
const timeoutMs = opts.timeoutMs ?? expiresInHours * 36e5;
|
|
4851
|
+
const pauseClock = this.pauseClocks.get(executionId);
|
|
4852
|
+
pauseClock?.startPause();
|
|
4853
|
+
let timer;
|
|
4854
|
+
try {
|
|
4855
|
+
const result = await Promise.race([
|
|
4856
|
+
future,
|
|
4857
|
+
new Promise((resolve2) => {
|
|
4858
|
+
timer = setTimeout(() => {
|
|
4859
|
+
resolve2(
|
|
4860
|
+
new ApprovalResult({
|
|
4861
|
+
decision: "expired",
|
|
4862
|
+
feedback: "timed out waiting for approval",
|
|
4863
|
+
executionId,
|
|
4864
|
+
approvalRequestId: opts.approvalRequestId
|
|
4865
|
+
})
|
|
4866
|
+
);
|
|
4867
|
+
}, timeoutMs);
|
|
4868
|
+
})
|
|
4869
|
+
]);
|
|
4870
|
+
if (result.decision === "expired") {
|
|
4871
|
+
this.pauseManager.resolve(opts.approvalRequestId, result);
|
|
4872
|
+
}
|
|
4873
|
+
return result;
|
|
4874
|
+
} finally {
|
|
4875
|
+
if (timer) clearTimeout(timer);
|
|
4876
|
+
pauseClock?.endPause();
|
|
4877
|
+
}
|
|
4878
|
+
}
|
|
4188
4879
|
buildExecutionLogContext(metadata) {
|
|
4189
4880
|
const current = metadata ?? ExecutionContext.getCurrent()?.metadata;
|
|
4190
4881
|
if (!current) return void 0;
|
|
@@ -4228,6 +4919,7 @@ var Agent = class {
|
|
|
4228
4919
|
if (this.heartbeatTimer) {
|
|
4229
4920
|
clearInterval(this.heartbeatTimer);
|
|
4230
4921
|
}
|
|
4922
|
+
this.pauseManager.cancelAll();
|
|
4231
4923
|
await new Promise((resolve2, reject) => {
|
|
4232
4924
|
this.server?.close((err) => {
|
|
4233
4925
|
if (err) reject(err);
|
|
@@ -4424,23 +5116,24 @@ var Agent = class {
|
|
|
4424
5116
|
rootWorkflowId,
|
|
4425
5117
|
reasonerId: name
|
|
4426
5118
|
});
|
|
5119
|
+
const executeMetadata = {
|
|
5120
|
+
runId,
|
|
5121
|
+
workflowId,
|
|
5122
|
+
rootWorkflowId,
|
|
5123
|
+
parentExecutionId: parentMetadata?.executionId,
|
|
5124
|
+
reasonerId: name,
|
|
5125
|
+
sessionId: parentMetadata?.sessionId,
|
|
5126
|
+
actorId: parentMetadata?.actorId,
|
|
5127
|
+
callerDid: parentMetadata?.callerDid,
|
|
5128
|
+
targetDid: parentMetadata?.targetDid,
|
|
5129
|
+
agentNodeDid: parentMetadata?.agentNodeDid,
|
|
5130
|
+
agentNodeId: this.config.nodeId,
|
|
5131
|
+
replaySourceRunId: parentMetadata?.replaySourceRunId,
|
|
5132
|
+
replayBeforeExecutionId: parentMetadata?.replayBeforeExecutionId,
|
|
5133
|
+
replayMode: parentMetadata?.replayMode
|
|
5134
|
+
};
|
|
4427
5135
|
try {
|
|
4428
|
-
const result = await this.agentFieldClient.execute(target, input,
|
|
4429
|
-
runId,
|
|
4430
|
-
workflowId,
|
|
4431
|
-
rootWorkflowId,
|
|
4432
|
-
parentExecutionId: parentMetadata?.executionId,
|
|
4433
|
-
reasonerId: name,
|
|
4434
|
-
sessionId: parentMetadata?.sessionId,
|
|
4435
|
-
actorId: parentMetadata?.actorId,
|
|
4436
|
-
callerDid: parentMetadata?.callerDid,
|
|
4437
|
-
targetDid: parentMetadata?.targetDid,
|
|
4438
|
-
agentNodeDid: parentMetadata?.agentNodeDid,
|
|
4439
|
-
agentNodeId: this.config.nodeId,
|
|
4440
|
-
replaySourceRunId: parentMetadata?.replaySourceRunId,
|
|
4441
|
-
replayBeforeExecutionId: parentMetadata?.replayBeforeExecutionId,
|
|
4442
|
-
replayMode: parentMetadata?.replayMode
|
|
4443
|
-
});
|
|
5136
|
+
const result = this.config.asyncExecution === false ? await this.agentFieldClient.execute(target, input, executeMetadata) : await this.callRemoteAsync(target, input, executeMetadata, parentMetadata?.executionId);
|
|
4444
5137
|
this.executionLogger.system("agent.call.completed", "Remote agent call completed", {
|
|
4445
5138
|
target,
|
|
4446
5139
|
agentNodeId: agentId,
|
|
@@ -4471,6 +5164,44 @@ var Agent = class {
|
|
|
4471
5164
|
throw err;
|
|
4472
5165
|
}
|
|
4473
5166
|
}
|
|
5167
|
+
/**
|
|
5168
|
+
* Remote call variant that submits the execution asynchronously and polls for
|
|
5169
|
+
* its result, instead of holding a synchronous connection open. This lets a
|
|
5170
|
+
* caller await a descendant that legitimately pauses (WAITING) for a long
|
|
5171
|
+
* time without tripping the control plane's synchronous dispatch ceiling.
|
|
5172
|
+
*
|
|
5173
|
+
* Multi-hop pause propagation: if the caller itself is a detached reasoner
|
|
5174
|
+
* (has a registered pause-clock), then when the awaited child enters WAITING
|
|
5175
|
+
* we push the caller's own execution to WAITING via awaiter-status — so any
|
|
5176
|
+
* ancestor awaiting the caller transitively sees WAITING too and doesn't time
|
|
5177
|
+
* out. Mirrors the propagation wiring in the Python SDK's `Agent.call`.
|
|
5178
|
+
*/
|
|
5179
|
+
async callRemoteAsync(target, input, executeMetadata, parentExecutionId) {
|
|
5180
|
+
const childExecutionId = await this.agentFieldClient.executeAsync(target, input, executeMetadata);
|
|
5181
|
+
const parentPauseClock = parentExecutionId ? this.pauseClocks.get(parentExecutionId) : void 0;
|
|
5182
|
+
let onChildWaiting;
|
|
5183
|
+
let onChildRunning;
|
|
5184
|
+
if (parentExecutionId && parentPauseClock) {
|
|
5185
|
+
const reason = `awaiting child ${childExecutionId}`;
|
|
5186
|
+
onChildWaiting = async () => {
|
|
5187
|
+
try {
|
|
5188
|
+
await this.agentFieldClient.notifyAwaiterStatus(parentExecutionId, "waiting", reason);
|
|
5189
|
+
} catch {
|
|
5190
|
+
}
|
|
5191
|
+
};
|
|
5192
|
+
onChildRunning = async () => {
|
|
5193
|
+
try {
|
|
5194
|
+
await this.agentFieldClient.notifyAwaiterStatus(parentExecutionId, "running", reason);
|
|
5195
|
+
} catch {
|
|
5196
|
+
}
|
|
5197
|
+
};
|
|
5198
|
+
}
|
|
5199
|
+
return this.agentFieldClient.waitForExecutionResult(childExecutionId, {
|
|
5200
|
+
pauseClock: parentPauseClock,
|
|
5201
|
+
onChildWaiting,
|
|
5202
|
+
onChildRunning
|
|
5203
|
+
});
|
|
5204
|
+
}
|
|
4474
5205
|
registerDefaultRoutes() {
|
|
4475
5206
|
this.app.get("/health", (_req, res) => {
|
|
4476
5207
|
res.json(this.health());
|
|
@@ -4604,12 +5335,19 @@ var Agent = class {
|
|
|
4604
5335
|
this.app.post("/execute/:name", (req, res) => this.executeServerlessHttp(req, res, req.params.name));
|
|
4605
5336
|
}
|
|
4606
5337
|
async executeReasoner(req, res, name) {
|
|
5338
|
+
const metadata = this.buildMetadata(req);
|
|
5339
|
+
const reasoner = this.reasoners.get(name);
|
|
5340
|
+
if (reasoner && this.shouldRunAsync(req)) {
|
|
5341
|
+
res.status(202).json({ status: "processing", execution_id: metadata.executionId });
|
|
5342
|
+
void this.runReasonerAsync(reasoner, { targetName: name, input: req.body, metadata });
|
|
5343
|
+
return;
|
|
5344
|
+
}
|
|
4607
5345
|
try {
|
|
4608
5346
|
await this.executeInvocation({
|
|
4609
5347
|
targetName: name,
|
|
4610
5348
|
targetType: "reasoner",
|
|
4611
5349
|
input: req.body,
|
|
4612
|
-
metadata
|
|
5350
|
+
metadata,
|
|
4613
5351
|
req,
|
|
4614
5352
|
res,
|
|
4615
5353
|
respond: true
|
|
@@ -4914,6 +5652,121 @@ var Agent = class {
|
|
|
4914
5652
|
}
|
|
4915
5653
|
throw new TargetNotFoundError(`Reasoner not found: ${params.targetName}`);
|
|
4916
5654
|
}
|
|
5655
|
+
/**
|
|
5656
|
+
* True when an incoming reasoner dispatch should run detached (202-ack).
|
|
5657
|
+
*
|
|
5658
|
+
* Requires async execution to be enabled AND the request to carry BOTH
|
|
5659
|
+
* `X-Execution-ID` and `X-Run-ID`. The `X-Run-ID` header is the marker that
|
|
5660
|
+
* the control plane dispatched this via an async-aware path — the workflow
|
|
5661
|
+
* execute paths (`/execute`, `/execute/async`, agent-to-agent calls, triggers)
|
|
5662
|
+
* always set it (see control-plane callAgent), and those paths wait for the
|
|
5663
|
+
* out-of-band `/status` result. The legacy synchronous invoke endpoint
|
|
5664
|
+
* (`POST /api/v1/reasoners/{node}.{reasoner}`) omits `X-Run-ID` for
|
|
5665
|
+
* long-running agents and forwards the agent's HTTP response verbatim; it
|
|
5666
|
+
* cannot handle a 202, so we must run synchronously there and return the
|
|
5667
|
+
* result inline. Direct HTTP callers / tests without these headers likewise
|
|
5668
|
+
* keep the synchronous request/response contract.
|
|
5669
|
+
*/
|
|
5670
|
+
shouldRunAsync(req) {
|
|
5671
|
+
if (this.config.asyncExecution === false) return false;
|
|
5672
|
+
return this.hasHeader(req, "x-execution-id") && this.hasHeader(req, "x-run-id");
|
|
5673
|
+
}
|
|
5674
|
+
/** True when the request carries a non-empty value for the given header. */
|
|
5675
|
+
hasHeader(req, name) {
|
|
5676
|
+
const raw = req.headers[name];
|
|
5677
|
+
const value = Array.isArray(raw) ? raw[0] : raw;
|
|
5678
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
5679
|
+
}
|
|
5680
|
+
/**
|
|
5681
|
+
* Run a reasoner detached after its dispatch was 202-acked, delivering the
|
|
5682
|
+
* terminal status out-of-band via `POST /executions/{id}/status`.
|
|
5683
|
+
*
|
|
5684
|
+
* A pause-clock is registered for the execution so `ctx.pause()` (and any
|
|
5685
|
+
* awaited paused descendant) can exclude its wait from the active wall-clock
|
|
5686
|
+
* budget. A watchdog aborts the reasoner if active time exceeds the budget,
|
|
5687
|
+
* guaranteeing the control plane always sees a terminal status even if the
|
|
5688
|
+
* reasoner hangs. Mirrors the Python SDK's `_execute_async_with_callback`.
|
|
5689
|
+
*/
|
|
5690
|
+
async runReasonerAsync(reasoner, params) {
|
|
5691
|
+
const executionId = params.metadata.executionId;
|
|
5692
|
+
const reasonerName = params.metadata.reasonerId ?? params.targetName;
|
|
5693
|
+
const start = Date.now();
|
|
5694
|
+
const budgetMs = this.config.executionBudgetMs ?? 72e5;
|
|
5695
|
+
const pauseClock = new PauseClock();
|
|
5696
|
+
this.pauseClocks.set(executionId, pauseClock);
|
|
5697
|
+
const controller = new AbortController();
|
|
5698
|
+
let watchdogTimer;
|
|
5699
|
+
const watchdog = new Promise((_, reject) => {
|
|
5700
|
+
const checkInterval = Math.min(5e3, Math.max(100, budgetMs / 4));
|
|
5701
|
+
watchdogTimer = setInterval(() => {
|
|
5702
|
+
const activeElapsed = Date.now() - start - pauseClock.totalPaused();
|
|
5703
|
+
if (activeElapsed > budgetMs) {
|
|
5704
|
+
pauseClock.timedOut = true;
|
|
5705
|
+
this.cancelRegistry.cancel(executionId, "reasoner_timeout");
|
|
5706
|
+
reject(
|
|
5707
|
+
new Error(
|
|
5708
|
+
`reasoner '${reasonerName}' timed out after ${Math.round(budgetMs / 1e3)}s of active time`
|
|
5709
|
+
)
|
|
5710
|
+
);
|
|
5711
|
+
}
|
|
5712
|
+
}, checkInterval);
|
|
5713
|
+
});
|
|
5714
|
+
watchdog.catch(() => {
|
|
5715
|
+
});
|
|
5716
|
+
const completedAt = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
5717
|
+
try {
|
|
5718
|
+
const result = await Promise.race([
|
|
5719
|
+
this.runReasoner(reasoner, {
|
|
5720
|
+
targetName: params.targetName,
|
|
5721
|
+
input: params.input,
|
|
5722
|
+
metadata: params.metadata,
|
|
5723
|
+
respond: false,
|
|
5724
|
+
controller
|
|
5725
|
+
}),
|
|
5726
|
+
watchdog
|
|
5727
|
+
]);
|
|
5728
|
+
await this.agentFieldClient.reportExecutionResult(executionId, {
|
|
5729
|
+
status: "succeeded",
|
|
5730
|
+
result,
|
|
5731
|
+
durationMs: Date.now() - start,
|
|
5732
|
+
completedAt: completedAt(),
|
|
5733
|
+
reasoner: reasonerName
|
|
5734
|
+
});
|
|
5735
|
+
} catch (err) {
|
|
5736
|
+
const durationMs = Date.now() - start;
|
|
5737
|
+
if (pauseClock.timedOut) {
|
|
5738
|
+
await this.agentFieldClient.reportExecutionResult(executionId, {
|
|
5739
|
+
status: "failed",
|
|
5740
|
+
error: err?.message ?? `reasoner '${reasonerName}' timed out`,
|
|
5741
|
+
errorDetails: { reason: "reasoner_timeout" },
|
|
5742
|
+
durationMs,
|
|
5743
|
+
completedAt: completedAt(),
|
|
5744
|
+
reasoner: reasonerName
|
|
5745
|
+
});
|
|
5746
|
+
} else if (controller.signal.aborted) {
|
|
5747
|
+
await this.agentFieldClient.reportExecutionResult(executionId, {
|
|
5748
|
+
status: "cancelled",
|
|
5749
|
+
error: "cancelled_by_control_plane",
|
|
5750
|
+
errorDetails: { reason: "cancelled" },
|
|
5751
|
+
durationMs,
|
|
5752
|
+
completedAt: completedAt(),
|
|
5753
|
+
reasoner: reasonerName
|
|
5754
|
+
});
|
|
5755
|
+
} else {
|
|
5756
|
+
await this.agentFieldClient.reportExecutionResult(executionId, {
|
|
5757
|
+
status: "failed",
|
|
5758
|
+
error: err?.message ?? "Execution failed",
|
|
5759
|
+
errorDetails: err?.responseData,
|
|
5760
|
+
durationMs,
|
|
5761
|
+
completedAt: completedAt(),
|
|
5762
|
+
reasoner: reasonerName
|
|
5763
|
+
});
|
|
5764
|
+
}
|
|
5765
|
+
} finally {
|
|
5766
|
+
if (watchdogTimer) clearInterval(watchdogTimer);
|
|
5767
|
+
this.pauseClocks.delete(executionId);
|
|
5768
|
+
}
|
|
5769
|
+
}
|
|
4917
5770
|
async runReasoner(reasoner, params) {
|
|
4918
5771
|
const req = params.req ?? {};
|
|
4919
5772
|
const res = params.res ?? {};
|
|
@@ -4930,7 +5783,8 @@ var Agent = class {
|
|
|
4930
5783
|
agent: this
|
|
4931
5784
|
});
|
|
4932
5785
|
const { controller, release } = this.cancelRegistry.register(
|
|
4933
|
-
executionMetadata.executionId
|
|
5786
|
+
executionMetadata.executionId,
|
|
5787
|
+
params.controller
|
|
4934
5788
|
);
|
|
4935
5789
|
return ExecutionContext.run(execCtx, async () => {
|
|
4936
5790
|
this.executionLogger.system("execution.started", "Execution started", {
|
|
@@ -5153,9 +6007,7 @@ var Agent = class {
|
|
|
5153
6007
|
try {
|
|
5154
6008
|
const reasoners = this.reasonerDefinitions();
|
|
5155
6009
|
const skills = this.skillDefinitions();
|
|
5156
|
-
const
|
|
5157
|
-
const hostForUrl = this.config.publicUrl ? void 0 : this.config.host && this.config.host !== "0.0.0.0" ? this.config.host : "127.0.0.1";
|
|
5158
|
-
const publicUrl = this.config.publicUrl ?? `http://${hostForUrl ?? "127.0.0.1"}:${port}`;
|
|
6010
|
+
const publicUrl = this.resolvePublicUrl();
|
|
5159
6011
|
const agentTags = this.config.tags ?? [];
|
|
5160
6012
|
const regResponse = await this.agentFieldClient.register({
|
|
5161
6013
|
id: this.config.nodeId,
|
|
@@ -6288,7 +7140,7 @@ var OpenRouterMediaProvider = class {
|
|
|
6288
7140
|
while (true) {
|
|
6289
7141
|
const remaining = deadline - Date.now();
|
|
6290
7142
|
if (remaining <= 0) break;
|
|
6291
|
-
await
|
|
7143
|
+
await sleep3(Math.min(pollInterval, remaining));
|
|
6292
7144
|
if (Date.now() >= deadline) break;
|
|
6293
7145
|
const pollRes = await this.get(pollEndpoint);
|
|
6294
7146
|
if (!pollRes.ok) {
|
|
@@ -6646,7 +7498,7 @@ var OpenRouterMediaProvider = class {
|
|
|
6646
7498
|
});
|
|
6647
7499
|
}
|
|
6648
7500
|
};
|
|
6649
|
-
function
|
|
7501
|
+
function sleep3(ms) {
|
|
6650
7502
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
6651
7503
|
}
|
|
6652
7504
|
|
|
@@ -6655,167 +7507,6 @@ init_types();
|
|
|
6655
7507
|
init_factory();
|
|
6656
7508
|
init_runner();
|
|
6657
7509
|
|
|
6658
|
-
|
|
6659
|
-
var ExecutionStatus = {
|
|
6660
|
-
PENDING: "pending",
|
|
6661
|
-
QUEUED: "queued",
|
|
6662
|
-
WAITING: "waiting",
|
|
6663
|
-
RUNNING: "running",
|
|
6664
|
-
SUCCEEDED: "succeeded",
|
|
6665
|
-
FAILED: "failed",
|
|
6666
|
-
CANCELLED: "cancelled",
|
|
6667
|
-
TIMEOUT: "timeout",
|
|
6668
|
-
UNKNOWN: "unknown"
|
|
6669
|
-
};
|
|
6670
|
-
var CANONICAL_STATUSES = new Set(
|
|
6671
|
-
Object.values(ExecutionStatus)
|
|
6672
|
-
);
|
|
6673
|
-
var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
|
|
6674
|
-
ExecutionStatus.SUCCEEDED,
|
|
6675
|
-
ExecutionStatus.FAILED,
|
|
6676
|
-
ExecutionStatus.CANCELLED,
|
|
6677
|
-
ExecutionStatus.TIMEOUT
|
|
6678
|
-
]);
|
|
6679
|
-
var ACTIVE_STATUSES = /* @__PURE__ */ new Set([
|
|
6680
|
-
ExecutionStatus.PENDING,
|
|
6681
|
-
ExecutionStatus.QUEUED,
|
|
6682
|
-
ExecutionStatus.WAITING,
|
|
6683
|
-
ExecutionStatus.RUNNING
|
|
6684
|
-
]);
|
|
6685
|
-
var STATUS_ALIASES = {
|
|
6686
|
-
success: "succeeded",
|
|
6687
|
-
successful: "succeeded",
|
|
6688
|
-
completed: "succeeded",
|
|
6689
|
-
complete: "succeeded",
|
|
6690
|
-
done: "succeeded",
|
|
6691
|
-
ok: "succeeded",
|
|
6692
|
-
error: "failed",
|
|
6693
|
-
failure: "failed",
|
|
6694
|
-
errored: "failed",
|
|
6695
|
-
canceled: "cancelled",
|
|
6696
|
-
cancel: "cancelled",
|
|
6697
|
-
timed_out: "timeout",
|
|
6698
|
-
wait: "queued",
|
|
6699
|
-
awaiting_approval: "waiting",
|
|
6700
|
-
awaiting_human: "waiting",
|
|
6701
|
-
approval_pending: "waiting",
|
|
6702
|
-
in_progress: "running",
|
|
6703
|
-
processing: "running"
|
|
6704
|
-
};
|
|
6705
|
-
function normalizeStatus(status) {
|
|
6706
|
-
if (status == null) return "unknown";
|
|
6707
|
-
const normalized = status.trim().toLowerCase();
|
|
6708
|
-
if (!normalized) return "unknown";
|
|
6709
|
-
if (CANONICAL_STATUSES.has(normalized)) return normalized;
|
|
6710
|
-
return STATUS_ALIASES[normalized] ?? "unknown";
|
|
6711
|
-
}
|
|
6712
|
-
function isTerminal(status) {
|
|
6713
|
-
return TERMINAL_STATUSES.has(normalizeStatus(status));
|
|
6714
|
-
}
|
|
6715
|
-
function isActive(status) {
|
|
6716
|
-
return ACTIVE_STATUSES.has(normalizeStatus(status));
|
|
6717
|
-
}
|
|
6718
|
-
var ApprovalClient = class {
|
|
6719
|
-
http;
|
|
6720
|
-
nodeId;
|
|
6721
|
-
headers;
|
|
6722
|
-
constructor(opts) {
|
|
6723
|
-
this.http = axios4.create({
|
|
6724
|
-
baseURL: opts.baseURL.replace(/\/$/, ""),
|
|
6725
|
-
timeout: 3e4,
|
|
6726
|
-
httpAgent,
|
|
6727
|
-
httpsAgent
|
|
6728
|
-
});
|
|
6729
|
-
this.nodeId = opts.nodeId;
|
|
6730
|
-
const merged = { ...opts.headers ?? {} };
|
|
6731
|
-
if (opts.apiKey) {
|
|
6732
|
-
merged["X-API-Key"] = opts.apiKey;
|
|
6733
|
-
}
|
|
6734
|
-
this.headers = merged;
|
|
6735
|
-
}
|
|
6736
|
-
/**
|
|
6737
|
-
* Request human approval, transitioning the execution to `waiting`.
|
|
6738
|
-
*
|
|
6739
|
-
* Calls `POST /api/v1/agents/{node}/executions/{id}/request-approval`.
|
|
6740
|
-
*/
|
|
6741
|
-
async requestApproval(executionId, payload) {
|
|
6742
|
-
const body = {
|
|
6743
|
-
approval_request_id: payload.approvalRequestId,
|
|
6744
|
-
expires_in_hours: payload.expiresInHours ?? 72
|
|
6745
|
-
};
|
|
6746
|
-
if (payload.approvalRequestUrl) {
|
|
6747
|
-
body.approval_request_url = payload.approvalRequestUrl;
|
|
6748
|
-
}
|
|
6749
|
-
if (payload.callbackUrl) {
|
|
6750
|
-
body.callback_url = payload.callbackUrl;
|
|
6751
|
-
}
|
|
6752
|
-
const res = await this.http.post(
|
|
6753
|
-
`/api/v1/agents/${encodeURIComponent(this.nodeId)}/executions/${encodeURIComponent(executionId)}/request-approval`,
|
|
6754
|
-
body,
|
|
6755
|
-
{ headers: { ...this.headers, "Content-Type": "application/json" } }
|
|
6756
|
-
);
|
|
6757
|
-
return {
|
|
6758
|
-
approvalRequestId: res.data.approval_request_id ?? "",
|
|
6759
|
-
approvalRequestUrl: res.data.approval_request_url ?? ""
|
|
6760
|
-
};
|
|
6761
|
-
}
|
|
6762
|
-
/**
|
|
6763
|
-
* Get the current approval status for an execution.
|
|
6764
|
-
*
|
|
6765
|
-
* Calls `GET /api/v1/agents/{node}/executions/{id}/approval-status`.
|
|
6766
|
-
*/
|
|
6767
|
-
async getApprovalStatus(executionId) {
|
|
6768
|
-
const res = await this.http.get(
|
|
6769
|
-
`/api/v1/agents/${encodeURIComponent(this.nodeId)}/executions/${encodeURIComponent(executionId)}/approval-status`,
|
|
6770
|
-
{ headers: this.headers }
|
|
6771
|
-
);
|
|
6772
|
-
const data = res.data;
|
|
6773
|
-
return {
|
|
6774
|
-
status: data.status ?? "pending",
|
|
6775
|
-
response: data.response,
|
|
6776
|
-
requestUrl: data.request_url,
|
|
6777
|
-
requestedAt: data.requested_at,
|
|
6778
|
-
respondedAt: data.responded_at
|
|
6779
|
-
};
|
|
6780
|
-
}
|
|
6781
|
-
/**
|
|
6782
|
-
* Poll approval status with exponential backoff until resolved.
|
|
6783
|
-
*
|
|
6784
|
-
* Returns once the status is no longer `pending` (i.e. approved, rejected,
|
|
6785
|
-
* or expired).
|
|
6786
|
-
*/
|
|
6787
|
-
async waitForApproval(executionId, opts) {
|
|
6788
|
-
const pollInterval = opts?.pollIntervalMs ?? 5e3;
|
|
6789
|
-
const maxInterval = opts?.maxIntervalMs ?? 6e4;
|
|
6790
|
-
const timeout = opts?.timeoutMs;
|
|
6791
|
-
const backoffFactor = 2;
|
|
6792
|
-
const startTime = Date.now();
|
|
6793
|
-
let interval = pollInterval;
|
|
6794
|
-
while (true) {
|
|
6795
|
-
if (timeout != null && Date.now() - startTime >= timeout) {
|
|
6796
|
-
throw new Error(
|
|
6797
|
-
`Approval for execution ${executionId} timed out after ${timeout}ms`
|
|
6798
|
-
);
|
|
6799
|
-
}
|
|
6800
|
-
await sleep2(interval);
|
|
6801
|
-
let data;
|
|
6802
|
-
try {
|
|
6803
|
-
data = await this.getApprovalStatus(executionId);
|
|
6804
|
-
} catch {
|
|
6805
|
-
interval = Math.min(interval * backoffFactor, maxInterval);
|
|
6806
|
-
continue;
|
|
6807
|
-
}
|
|
6808
|
-
if (data.status !== "pending") {
|
|
6809
|
-
return data;
|
|
6810
|
-
}
|
|
6811
|
-
interval = Math.min(interval * backoffFactor, maxInterval);
|
|
6812
|
-
}
|
|
6813
|
-
}
|
|
6814
|
-
};
|
|
6815
|
-
function sleep2(ms) {
|
|
6816
|
-
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
6817
|
-
}
|
|
6818
|
-
|
|
6819
|
-
export { ACTIVE_STATUSES, AIClient, Agent, AgentRouter, ApprovalClient, Audio, CANONICAL_STATUSES, DIDAuthenticator, DidClient, DidInterface, DidManager, ExecutionContext, ExecutionLogger, ExecutionStatus, File, HEADER_CALLER_DID, HEADER_DID_NONCE, HEADER_DID_SIGNATURE, HEADER_DID_TIMESTAMP, HarnessRunner, Image, JWE_ALG, JWE_ENC, KEY_AGREEMENT_TYPE, MediaProviderError, MediaRouter, MemoryClient, MemoryClientBase, MemoryEventClient, MemoryInterface, MultimodalResponse, OpenRouterMediaProvider, PayloadEncryptionError, RateLimitError, RealtimeSession, ReasonerContext, SUPPORTED_PROVIDERS, SUPPORTED_SESSION_TRANSPORTS, SessionTransportError, SkillContext, StatelessRateLimiter, TERMINAL_STATUSES, Text, Video, WorkflowReporter, audioFromBase64, audioFromBuffer, audioFromFile, audioFromUrl, buildProvider, buildSessionDefinition, buildToolConfig, capabilitiesToTools, capabilityToMetadataTool, capabilityToTool, createExecutionLogger, createHarnessResult, createMetrics, createMultimodalResponse, createRawResult, decrypt, decryptToString, encryptForDid, encryptToJwk, executeToolCallLoop, extractKeyAgreementJwk, fileFromBase64, fileFromBuffer, fileFromPath, fileFromUrl, generateX25519KeyPair, getCurrentContext, getCurrentSkillContext, imageFromBase64, imageFromBuffer, imageFromFile, imageFromUrl, isActive, isExecutionLogBatchPayload, isTerminal, normalizeExecutionLogEntry, normalizeSessionTransportValue, normalizeStatus, serializeExecutionLogEntry, text, validateSessionTransport, videoFromBase64, videoFromBuffer, videoFromFile, videoFromUrl };
|
|
7510
|
+
export { ACTIVE_STATUSES, AIClient, Agent, AgentRouter, ApprovalClient, ApprovalResult, Audio, CANONICAL_STATUSES, DIDAuthenticator, DidClient, DidInterface, DidManager, ExecutionContext, ExecutionLogger, ExecutionStatus, File, HEADER_CALLER_DID, HEADER_DID_NONCE, HEADER_DID_SIGNATURE, HEADER_DID_TIMESTAMP, HarnessRunner, Image, JWE_ALG, JWE_ENC, KEY_AGREEMENT_TYPE, MediaProviderError, MediaRouter, MemoryClient, MemoryClientBase, MemoryEventClient, MemoryInterface, MultimodalResponse, OpenRouterMediaProvider, PauseClock, PauseManager, PayloadEncryptionError, RateLimitError, RealtimeSession, ReasonerContext, SUPPORTED_PROVIDERS, SUPPORTED_SESSION_TRANSPORTS, SessionTransportError, SkillContext, StatelessRateLimiter, TERMINAL_STATUSES, Text, Video, WorkflowReporter, audioFromBase64, audioFromBuffer, audioFromFile, audioFromUrl, buildProvider, buildSessionDefinition, buildToolConfig, capabilitiesToTools, capabilityToMetadataTool, capabilityToTool, createExecutionLogger, createHarnessResult, createMetrics, createMultimodalResponse, createRawResult, decrypt, decryptToString, encryptForDid, encryptToJwk, executeToolCallLoop, extractKeyAgreementJwk, fileFromBase64, fileFromBuffer, fileFromPath, fileFromUrl, generateX25519KeyPair, getCurrentContext, getCurrentSkillContext, imageFromBase64, imageFromBuffer, imageFromFile, imageFromUrl, installApprovalWebhookRoute, isActive, isExecutionLogBatchPayload, isTerminal, normalizeExecutionLogEntry, normalizeSessionTransportValue, normalizeStatus, serializeExecutionLogEntry, text, validateSessionTransport, videoFromBase64, videoFromBuffer, videoFromFile, videoFromUrl };
|
|
6820
7511
|
//# sourceMappingURL=index.js.map
|
|
6821
7512
|
//# sourceMappingURL=index.js.map
|