@mixrpay/agent-sdk 0.8.2 → 0.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/index.cjs +897 -15
- package/dist/index.d.cts +722 -5
- package/dist/index.d.ts +722 -5
- package/dist/index.js +897 -15
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -298,6 +298,13 @@ var SessionKey = class _SessionKey {
|
|
|
298
298
|
get privateKeyHex() {
|
|
299
299
|
return this.privateKey.slice(2);
|
|
300
300
|
}
|
|
301
|
+
/**
|
|
302
|
+
* Get the raw private key with 0x prefix.
|
|
303
|
+
* Used internally for signing operations.
|
|
304
|
+
*/
|
|
305
|
+
get rawPrivateKey() {
|
|
306
|
+
return this.privateKey;
|
|
307
|
+
}
|
|
301
308
|
/**
|
|
302
309
|
* Parse a session key string into a SessionKey object.
|
|
303
310
|
*
|
|
@@ -488,7 +495,7 @@ function getAmountUsd(requirements) {
|
|
|
488
495
|
}
|
|
489
496
|
|
|
490
497
|
// src/agent-wallet.ts
|
|
491
|
-
var SDK_VERSION = "0.8.
|
|
498
|
+
var SDK_VERSION = "0.8.3";
|
|
492
499
|
var DEFAULT_BASE_URL = process.env.MIXRPAY_BASE_URL || "https://www.mixrpay.com";
|
|
493
500
|
var DEFAULT_TIMEOUT = 3e4;
|
|
494
501
|
var NETWORKS = {
|
|
@@ -1190,6 +1197,160 @@ var AgentWallet = class {
|
|
|
1190
1197
|
};
|
|
1191
1198
|
}
|
|
1192
1199
|
// ===========================================================================
|
|
1200
|
+
// Nested Budget Delegation Methods
|
|
1201
|
+
// ===========================================================================
|
|
1202
|
+
/**
|
|
1203
|
+
* Spawn a child invite for sub-agents.
|
|
1204
|
+
*
|
|
1205
|
+
* Allows an agent to create an invite code for a sub-agent with a portion
|
|
1206
|
+
* of their remaining budget (max 20%). The child inherits merchant restrictions
|
|
1207
|
+
* and cannot outlive the parent session.
|
|
1208
|
+
*
|
|
1209
|
+
* @param options - Spawn options
|
|
1210
|
+
* @returns The created child invite details
|
|
1211
|
+
*
|
|
1212
|
+
* @throws {MixrPayError} If spawning fails (insufficient budget, depth limit, etc.)
|
|
1213
|
+
*
|
|
1214
|
+
* @example
|
|
1215
|
+
* ```typescript
|
|
1216
|
+
* const wallet = new AgentWallet({ sessionKey: 'sk_live_...' });
|
|
1217
|
+
*
|
|
1218
|
+
* // Spawn a child invite for a sub-agent
|
|
1219
|
+
* const childInvite = await wallet.spawnChildInvite({
|
|
1220
|
+
* budgetUsd: 10.00, // Max 20% of available budget
|
|
1221
|
+
* name: 'Research Sub-Agent',
|
|
1222
|
+
* allowedMerchants: ['firecrawl.dev'], // Must be subset of parent
|
|
1223
|
+
* expiresInDays: 7, // Will be capped to parent's expiry
|
|
1224
|
+
* });
|
|
1225
|
+
*
|
|
1226
|
+
* console.log(`Share with sub-agent: ${childInvite.inviteCode}`);
|
|
1227
|
+
* console.log(`Child can spawn up to: $${childInvite.maxSpawnBudget}`);
|
|
1228
|
+
* ```
|
|
1229
|
+
*/
|
|
1230
|
+
async spawnChildInvite(options) {
|
|
1231
|
+
const { budgetUsd, name, allowedMerchants, expiresInDays } = options;
|
|
1232
|
+
const timestamp = Date.now();
|
|
1233
|
+
const nonce = crypto.randomUUID();
|
|
1234
|
+
const message = `spawn:${timestamp}:${nonce}`;
|
|
1235
|
+
const signature = await signMessage({
|
|
1236
|
+
message,
|
|
1237
|
+
privateKey: this.sessionKey.rawPrivateKey
|
|
1238
|
+
});
|
|
1239
|
+
const response = await fetch(`${this.baseUrl}/api/v1/agent/spawn`, {
|
|
1240
|
+
method: "POST",
|
|
1241
|
+
headers: { "Content-Type": "application/json" },
|
|
1242
|
+
body: JSON.stringify({
|
|
1243
|
+
session_key: this.sessionKey.toString(),
|
|
1244
|
+
signature,
|
|
1245
|
+
message,
|
|
1246
|
+
budget_usd: budgetUsd,
|
|
1247
|
+
name,
|
|
1248
|
+
allowed_merchants: allowedMerchants,
|
|
1249
|
+
expires_in_days: expiresInDays
|
|
1250
|
+
})
|
|
1251
|
+
});
|
|
1252
|
+
if (!response.ok) {
|
|
1253
|
+
const error = await response.json().catch(() => ({}));
|
|
1254
|
+
if (response.status === 409) {
|
|
1255
|
+
throw new MixrPayError("Concurrent modification - please retry");
|
|
1256
|
+
}
|
|
1257
|
+
if (response.status === 429) {
|
|
1258
|
+
throw new MixrPayError("Rate limited - too many spawn attempts");
|
|
1259
|
+
}
|
|
1260
|
+
throw new MixrPayError(error.error || `Failed to spawn child: ${response.status}`);
|
|
1261
|
+
}
|
|
1262
|
+
const data = await response.json();
|
|
1263
|
+
return {
|
|
1264
|
+
inviteCode: data.invite_code,
|
|
1265
|
+
inviteId: data.invite_id,
|
|
1266
|
+
budgetUsd: data.budget_usd,
|
|
1267
|
+
expiresAt: new Date(data.expires_at),
|
|
1268
|
+
depth: data.depth,
|
|
1269
|
+
maxSpawnBudget: data.max_spawn_budget,
|
|
1270
|
+
allowedMerchants: data.allowed_merchants || []
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
/**
|
|
1274
|
+
* Get available budget information for spawning.
|
|
1275
|
+
*
|
|
1276
|
+
* Returns the current budget status including how much can be spawned
|
|
1277
|
+
* to child agents (20% of available).
|
|
1278
|
+
*
|
|
1279
|
+
* @returns Budget information
|
|
1280
|
+
*
|
|
1281
|
+
* @example
|
|
1282
|
+
* ```typescript
|
|
1283
|
+
* const budget = await wallet.getAvailableBudget();
|
|
1284
|
+
*
|
|
1285
|
+
* console.log(`Total budget: $${budget.totalBudget}`);
|
|
1286
|
+
* console.log(`Spent: $${budget.spent}`);
|
|
1287
|
+
* console.log(`Allocated to children: $${budget.allocatedToChildren}`);
|
|
1288
|
+
* console.log(`Available: $${budget.available}`);
|
|
1289
|
+
* console.log(`Max spawn budget: $${budget.maxSpawnBudget}`);
|
|
1290
|
+
*
|
|
1291
|
+
* if (budget.canSpawn && budget.maxSpawnBudget >= 5.00) {
|
|
1292
|
+
* // Safe to spawn a $5 child
|
|
1293
|
+
* }
|
|
1294
|
+
* ```
|
|
1295
|
+
*/
|
|
1296
|
+
async getAvailableBudget() {
|
|
1297
|
+
const response = await fetch(`${this.baseUrl}/api/v1/agent/descendants`, {
|
|
1298
|
+
method: "GET",
|
|
1299
|
+
headers: {
|
|
1300
|
+
"Authorization": `Bearer ${this.sessionKey.toString()}`
|
|
1301
|
+
}
|
|
1302
|
+
});
|
|
1303
|
+
if (!response.ok) {
|
|
1304
|
+
const error = await response.json().catch(() => ({}));
|
|
1305
|
+
throw new MixrPayError(error.error || `Failed to get budget: ${response.status}`);
|
|
1306
|
+
}
|
|
1307
|
+
const data = await response.json();
|
|
1308
|
+
return {
|
|
1309
|
+
totalBudget: data.budget.total_usd,
|
|
1310
|
+
spent: data.budget.spent_usd,
|
|
1311
|
+
allocatedToChildren: data.budget.allocated_to_children_usd,
|
|
1312
|
+
available: data.budget.available_usd,
|
|
1313
|
+
maxSpawnBudget: data.max_spawn_budget,
|
|
1314
|
+
canSpawn: data.can_spawn
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
/**
|
|
1318
|
+
* Get all child sessions spawned by this session.
|
|
1319
|
+
*
|
|
1320
|
+
* Returns a tree of child invites/sessions including their spending status.
|
|
1321
|
+
*
|
|
1322
|
+
* @returns Array of child sessions
|
|
1323
|
+
*
|
|
1324
|
+
* @example
|
|
1325
|
+
* ```typescript
|
|
1326
|
+
* const children = await wallet.getChildSessions();
|
|
1327
|
+
*
|
|
1328
|
+
* for (const child of children) {
|
|
1329
|
+
* console.log(`${child.name}: $${child.spentUsd}/$${child.budgetUsd}`);
|
|
1330
|
+
* console.log(` Status: ${child.status}`);
|
|
1331
|
+
* console.log(` Depth: ${child.depth}`);
|
|
1332
|
+
*
|
|
1333
|
+
* if (child.children) {
|
|
1334
|
+
* console.log(` Has ${child.children.length} grandchildren`);
|
|
1335
|
+
* }
|
|
1336
|
+
* }
|
|
1337
|
+
* ```
|
|
1338
|
+
*/
|
|
1339
|
+
async getChildSessions() {
|
|
1340
|
+
const response = await fetch(`${this.baseUrl}/api/v1/agent/descendants`, {
|
|
1341
|
+
method: "GET",
|
|
1342
|
+
headers: {
|
|
1343
|
+
"Authorization": `Bearer ${this.sessionKey.toString()}`
|
|
1344
|
+
}
|
|
1345
|
+
});
|
|
1346
|
+
if (!response.ok) {
|
|
1347
|
+
const error = await response.json().catch(() => ({}));
|
|
1348
|
+
throw new MixrPayError(error.error || `Failed to get children: ${response.status}`);
|
|
1349
|
+
}
|
|
1350
|
+
const data = await response.json();
|
|
1351
|
+
return data.children || [];
|
|
1352
|
+
}
|
|
1353
|
+
// ===========================================================================
|
|
1193
1354
|
// Core Methods
|
|
1194
1355
|
// ===========================================================================
|
|
1195
1356
|
/**
|
|
@@ -1414,7 +1575,7 @@ var AgentWallet = class {
|
|
|
1414
1575
|
try {
|
|
1415
1576
|
const infoResponse = await fetch(`${this.baseUrl}/api/v1/session-key/info`, {
|
|
1416
1577
|
headers: {
|
|
1417
|
-
"X-Session-Key": this.sessionKey.address
|
|
1578
|
+
"X-Session-Key": this.sessionKey.address.toLowerCase()
|
|
1418
1579
|
}
|
|
1419
1580
|
});
|
|
1420
1581
|
if (!infoResponse.ok) {
|
|
@@ -1492,7 +1653,7 @@ var AgentWallet = class {
|
|
|
1492
1653
|
try {
|
|
1493
1654
|
const response = await fetch(`${this.baseUrl}/api/v1/session-key/info`, {
|
|
1494
1655
|
headers: {
|
|
1495
|
-
"X-Session-Key": this.sessionKey.address
|
|
1656
|
+
"X-Session-Key": this.sessionKey.address.toLowerCase()
|
|
1496
1657
|
}
|
|
1497
1658
|
});
|
|
1498
1659
|
if (response.ok) {
|
|
@@ -1547,7 +1708,7 @@ var AgentWallet = class {
|
|
|
1547
1708
|
try {
|
|
1548
1709
|
const response = await fetch(`${this.baseUrl}/api/v1/session-key/stats`, {
|
|
1549
1710
|
headers: {
|
|
1550
|
-
"X-Session-Key": this.sessionKey.address
|
|
1711
|
+
"X-Session-Key": this.sessionKey.address.toLowerCase()
|
|
1551
1712
|
}
|
|
1552
1713
|
});
|
|
1553
1714
|
if (response.ok) {
|
|
@@ -2398,9 +2559,8 @@ Timestamp: ${timestamp}`;
|
|
|
2398
2559
|
idempotencyKey,
|
|
2399
2560
|
onEvent
|
|
2400
2561
|
} = options;
|
|
2401
|
-
this.logger.debug("runAgent", { sessionId, messageCount: messages.length, config, stream });
|
|
2562
|
+
this.logger.debug("runAgent", { sessionId: sessionId || "(from signature)", messageCount: messages.length, config, stream });
|
|
2402
2563
|
const body = {
|
|
2403
|
-
session_id: sessionId,
|
|
2404
2564
|
messages: messages.map((m) => ({ role: m.role, content: m.content })),
|
|
2405
2565
|
config: {
|
|
2406
2566
|
model: config.model,
|
|
@@ -2411,13 +2571,17 @@ Timestamp: ${timestamp}`;
|
|
|
2411
2571
|
stream,
|
|
2412
2572
|
idempotency_key: idempotencyKey
|
|
2413
2573
|
};
|
|
2574
|
+
if (sessionId) {
|
|
2575
|
+
body.session_id = sessionId;
|
|
2576
|
+
}
|
|
2414
2577
|
const AGENT_RUN_TIMEOUT = 18e4;
|
|
2415
2578
|
if (!stream) {
|
|
2579
|
+
const authHeaders = await this.getSessionAuthHeaders();
|
|
2416
2580
|
const response = await fetch(`${this.baseUrl}/api/v2/agent/run`, {
|
|
2417
2581
|
method: "POST",
|
|
2418
2582
|
headers: {
|
|
2419
2583
|
"Content-Type": "application/json",
|
|
2420
|
-
|
|
2584
|
+
...authHeaders
|
|
2421
2585
|
},
|
|
2422
2586
|
body: JSON.stringify(body),
|
|
2423
2587
|
signal: AbortSignal.timeout(AGENT_RUN_TIMEOUT)
|
|
@@ -2460,18 +2624,19 @@ Timestamp: ${timestamp}`;
|
|
|
2460
2624
|
txHash: data.tx_hash
|
|
2461
2625
|
};
|
|
2462
2626
|
}
|
|
2463
|
-
return this.runAgentStreaming(
|
|
2627
|
+
return this.runAgentStreaming(body, onEvent);
|
|
2464
2628
|
}
|
|
2465
2629
|
/**
|
|
2466
2630
|
* Internal: Handle streaming agent run via SSE
|
|
2467
2631
|
*/
|
|
2468
|
-
async runAgentStreaming(
|
|
2632
|
+
async runAgentStreaming(body, onEvent) {
|
|
2469
2633
|
const STREAMING_TIMEOUT = 3e5;
|
|
2634
|
+
const authHeaders = await this.getSessionAuthHeaders();
|
|
2470
2635
|
const response = await fetch(`${this.baseUrl}/api/v2/agent/run`, {
|
|
2471
2636
|
method: "POST",
|
|
2472
2637
|
headers: {
|
|
2473
2638
|
"Content-Type": "application/json",
|
|
2474
|
-
|
|
2639
|
+
...authHeaders
|
|
2475
2640
|
},
|
|
2476
2641
|
body: JSON.stringify(body),
|
|
2477
2642
|
signal: AbortSignal.timeout(STREAMING_TIMEOUT)
|
|
@@ -2618,12 +2783,11 @@ Timestamp: ${timestamp}`;
|
|
|
2618
2783
|
* }
|
|
2619
2784
|
* ```
|
|
2620
2785
|
*/
|
|
2621
|
-
async getAgentRunStatus(runId,
|
|
2622
|
-
this.logger.debug("getAgentRunStatus", { runId
|
|
2786
|
+
async getAgentRunStatus(runId, _sessionId) {
|
|
2787
|
+
this.logger.debug("getAgentRunStatus", { runId });
|
|
2788
|
+
const authHeaders = await this.getSessionAuthHeaders();
|
|
2623
2789
|
const response = await fetch(`${this.baseUrl}/api/v2/agent/run/${runId}`, {
|
|
2624
|
-
headers:
|
|
2625
|
-
"X-Mixr-Session": sessionId
|
|
2626
|
-
}
|
|
2790
|
+
headers: authHeaders
|
|
2627
2791
|
});
|
|
2628
2792
|
if (!response.ok) {
|
|
2629
2793
|
const error = await response.json().catch(() => ({}));
|
|
@@ -2722,6 +2886,724 @@ Timestamp: ${timestamp}`;
|
|
|
2722
2886
|
latencyMs: mixrpay.latencyMs
|
|
2723
2887
|
};
|
|
2724
2888
|
}
|
|
2889
|
+
// ===========================================================================
|
|
2890
|
+
// Task Board Methods
|
|
2891
|
+
// ===========================================================================
|
|
2892
|
+
/**
|
|
2893
|
+
* Create a new task on the Task Board.
|
|
2894
|
+
*
|
|
2895
|
+
* Users (human or agent) can post tasks with budgets for other agents to complete.
|
|
2896
|
+
* The listing fee is charged when an agent is accepted, not at creation time.
|
|
2897
|
+
*
|
|
2898
|
+
* @param params - Task creation parameters
|
|
2899
|
+
* @returns The created task
|
|
2900
|
+
*
|
|
2901
|
+
* @example
|
|
2902
|
+
* ```typescript
|
|
2903
|
+
* const task = await wallet.createTask({
|
|
2904
|
+
* title: 'Research crypto regulations',
|
|
2905
|
+
* description: 'Research and summarize crypto regulations in the EU...',
|
|
2906
|
+
* budgetUsd: 100,
|
|
2907
|
+
* deliverables: ['PDF report', 'Summary document'],
|
|
2908
|
+
* category: 'research',
|
|
2909
|
+
* });
|
|
2910
|
+
* console.log(`Task created: ${task.id}`);
|
|
2911
|
+
* ```
|
|
2912
|
+
*/
|
|
2913
|
+
async createTask(params) {
|
|
2914
|
+
this.logger.info(`Creating task: ${params.title}`);
|
|
2915
|
+
const response = await this.callApi("/api/v2/tasks", {
|
|
2916
|
+
method: "POST",
|
|
2917
|
+
headers: { "Content-Type": "application/json" },
|
|
2918
|
+
body: JSON.stringify({
|
|
2919
|
+
title: params.title,
|
|
2920
|
+
description: params.description,
|
|
2921
|
+
budget_usd: params.budgetUsd,
|
|
2922
|
+
deliverables: params.deliverables,
|
|
2923
|
+
category: params.category,
|
|
2924
|
+
expires_in_days: params.expiresInDays
|
|
2925
|
+
})
|
|
2926
|
+
});
|
|
2927
|
+
const result = await response.json();
|
|
2928
|
+
if (!response.ok) {
|
|
2929
|
+
throw new MixrPayError(result.error || "Failed to create task");
|
|
2930
|
+
}
|
|
2931
|
+
return this.parseTask(result.task);
|
|
2932
|
+
}
|
|
2933
|
+
/**
|
|
2934
|
+
* List open tasks on the Task Board.
|
|
2935
|
+
*
|
|
2936
|
+
* Browse available tasks that agents can request to work on.
|
|
2937
|
+
*
|
|
2938
|
+
* @param params - Optional filter parameters
|
|
2939
|
+
* @returns List of tasks with pagination
|
|
2940
|
+
*
|
|
2941
|
+
* @example
|
|
2942
|
+
* ```typescript
|
|
2943
|
+
* const { tasks, pagination } = await wallet.listTasks({
|
|
2944
|
+
* minBudget: 50,
|
|
2945
|
+
* category: 'research',
|
|
2946
|
+
* });
|
|
2947
|
+
* console.log(`Found ${pagination.total} matching tasks`);
|
|
2948
|
+
* ```
|
|
2949
|
+
*/
|
|
2950
|
+
async listTasks(params) {
|
|
2951
|
+
const searchParams = new URLSearchParams();
|
|
2952
|
+
if (params?.status) searchParams.set("status", params.status);
|
|
2953
|
+
if (params?.category) searchParams.set("category", params.category);
|
|
2954
|
+
if (params?.minBudget !== void 0) searchParams.set("min_budget", String(params.minBudget));
|
|
2955
|
+
if (params?.maxBudget !== void 0) searchParams.set("max_budget", String(params.maxBudget));
|
|
2956
|
+
if (params?.limit !== void 0) searchParams.set("limit", String(params.limit));
|
|
2957
|
+
if (params?.offset !== void 0) searchParams.set("offset", String(params.offset));
|
|
2958
|
+
const url = `/api/v2/tasks${searchParams.toString() ? `?${searchParams}` : ""}`;
|
|
2959
|
+
const response = await this.callApi(url, { method: "GET" });
|
|
2960
|
+
const result = await response.json();
|
|
2961
|
+
if (!response.ok) {
|
|
2962
|
+
throw new MixrPayError(result.error || "Failed to list tasks");
|
|
2963
|
+
}
|
|
2964
|
+
return {
|
|
2965
|
+
tasks: result.tasks.map((t) => this.parseTask(t)),
|
|
2966
|
+
pagination: result.pagination
|
|
2967
|
+
};
|
|
2968
|
+
}
|
|
2969
|
+
/**
|
|
2970
|
+
* Get details for a specific task.
|
|
2971
|
+
*
|
|
2972
|
+
* @param taskId - The task ID
|
|
2973
|
+
* @returns Task details
|
|
2974
|
+
*/
|
|
2975
|
+
async getTask(taskId) {
|
|
2976
|
+
const response = await this.callApi(`/api/v2/tasks/${taskId}`, { method: "GET" });
|
|
2977
|
+
const result = await response.json();
|
|
2978
|
+
if (!response.ok) {
|
|
2979
|
+
throw new MixrPayError(result.error || "Failed to get task");
|
|
2980
|
+
}
|
|
2981
|
+
return this.parseTask(result.task);
|
|
2982
|
+
}
|
|
2983
|
+
/**
|
|
2984
|
+
* Get tasks created by or assigned to the authenticated user.
|
|
2985
|
+
*
|
|
2986
|
+
* @param options - Filter and pagination options
|
|
2987
|
+
* @returns List of tasks with pagination info
|
|
2988
|
+
*
|
|
2989
|
+
* @example
|
|
2990
|
+
* ```typescript
|
|
2991
|
+
* // Get all my tasks
|
|
2992
|
+
* const { tasks, pagination } = await wallet.getMyTasks();
|
|
2993
|
+
*
|
|
2994
|
+
* // Get only tasks I created
|
|
2995
|
+
* const created = await wallet.getMyTasks({ role: 'creator' });
|
|
2996
|
+
*
|
|
2997
|
+
* // Get only tasks assigned to me
|
|
2998
|
+
* const assigned = await wallet.getMyTasks({ role: 'agent' });
|
|
2999
|
+
*
|
|
3000
|
+
* // Filter by status
|
|
3001
|
+
* const pending = await wallet.getMyTasks({ status: 'submitted' });
|
|
3002
|
+
* ```
|
|
3003
|
+
*/
|
|
3004
|
+
async getMyTasks(options = {}) {
|
|
3005
|
+
const params = new URLSearchParams();
|
|
3006
|
+
if (options.role) params.set("role", options.role);
|
|
3007
|
+
if (options.status) params.set("status", options.status);
|
|
3008
|
+
if (options.page) params.set("page", options.page.toString());
|
|
3009
|
+
if (options.limit) params.set("limit", options.limit.toString());
|
|
3010
|
+
const queryString = params.toString();
|
|
3011
|
+
const url = `/api/v2/tasks/my${queryString ? `?${queryString}` : ""}`;
|
|
3012
|
+
const response = await this.callApi(url, { method: "GET" });
|
|
3013
|
+
const result = await response.json();
|
|
3014
|
+
if (!response.ok) {
|
|
3015
|
+
throw new MixrPayError(result.error || "Failed to get tasks");
|
|
3016
|
+
}
|
|
3017
|
+
return {
|
|
3018
|
+
tasks: result.tasks.map((t) => ({
|
|
3019
|
+
...this.parseTask(t),
|
|
3020
|
+
isCreator: t.is_creator,
|
|
3021
|
+
isAgent: t.is_agent
|
|
3022
|
+
})),
|
|
3023
|
+
pagination: {
|
|
3024
|
+
page: result.pagination.page,
|
|
3025
|
+
limit: result.pagination.limit,
|
|
3026
|
+
total: result.pagination.total,
|
|
3027
|
+
totalPages: result.pagination.total_pages
|
|
3028
|
+
}
|
|
3029
|
+
};
|
|
3030
|
+
}
|
|
3031
|
+
/**
|
|
3032
|
+
* Request to claim a task.
|
|
3033
|
+
*
|
|
3034
|
+
* Agents use this to express interest in completing a task.
|
|
3035
|
+
* The task creator will review requests and accept one agent.
|
|
3036
|
+
*
|
|
3037
|
+
* @param taskId - The task ID to request
|
|
3038
|
+
* @param message - Optional pitch/message to the task creator
|
|
3039
|
+
* @returns The created request
|
|
3040
|
+
*
|
|
3041
|
+
* @example
|
|
3042
|
+
* ```typescript
|
|
3043
|
+
* const request = await wallet.requestTask('task_123',
|
|
3044
|
+
* 'I have experience with crypto research and can deliver within 24 hours.'
|
|
3045
|
+
* );
|
|
3046
|
+
* console.log(`Request submitted: ${request.id}`);
|
|
3047
|
+
* ```
|
|
3048
|
+
*/
|
|
3049
|
+
async requestTask(taskId, message) {
|
|
3050
|
+
this.logger.info(`Requesting task: ${taskId}`);
|
|
3051
|
+
const response = await this.callApi(`/api/v2/tasks/${taskId}/request`, {
|
|
3052
|
+
method: "POST",
|
|
3053
|
+
headers: { "Content-Type": "application/json" },
|
|
3054
|
+
body: message ? JSON.stringify({ message }) : "{}"
|
|
3055
|
+
});
|
|
3056
|
+
const result = await response.json();
|
|
3057
|
+
if (!response.ok) {
|
|
3058
|
+
throw new MixrPayError(result.error || "Failed to request task");
|
|
3059
|
+
}
|
|
3060
|
+
return {
|
|
3061
|
+
id: result.request.id,
|
|
3062
|
+
taskId: result.request.task_id,
|
|
3063
|
+
status: result.request.status,
|
|
3064
|
+
message: result.request.message,
|
|
3065
|
+
createdAt: new Date(result.request.created_at)
|
|
3066
|
+
};
|
|
3067
|
+
}
|
|
3068
|
+
/**
|
|
3069
|
+
* Get requests for a task you created.
|
|
3070
|
+
*
|
|
3071
|
+
* @param taskId - The task ID
|
|
3072
|
+
* @returns List of agent requests
|
|
3073
|
+
*/
|
|
3074
|
+
async getTaskRequests(taskId) {
|
|
3075
|
+
const response = await this.callApi(`/api/v2/tasks/${taskId}/requests`, { method: "GET" });
|
|
3076
|
+
const result = await response.json();
|
|
3077
|
+
if (!response.ok) {
|
|
3078
|
+
throw new MixrPayError(result.error || "Failed to get task requests");
|
|
3079
|
+
}
|
|
3080
|
+
return result.requests.map((r) => ({
|
|
3081
|
+
id: r.id,
|
|
3082
|
+
status: r.status,
|
|
3083
|
+
message: r.message,
|
|
3084
|
+
createdAt: new Date(r.created_at),
|
|
3085
|
+
reviewedAt: r.reviewed_at ? new Date(r.reviewed_at) : void 0,
|
|
3086
|
+
agent: r.agent
|
|
3087
|
+
}));
|
|
3088
|
+
}
|
|
3089
|
+
/**
|
|
3090
|
+
* Accept an agent's request to work on your task.
|
|
3091
|
+
*
|
|
3092
|
+
* This will:
|
|
3093
|
+
* - Charge the listing fee from your wallet
|
|
3094
|
+
* - Create a session for the agent with your task budget
|
|
3095
|
+
* - Mark the task as claimed
|
|
3096
|
+
* - Reject all other pending requests
|
|
3097
|
+
*
|
|
3098
|
+
* @param taskId - The task ID
|
|
3099
|
+
* @param requestId - The request ID to accept
|
|
3100
|
+
* @returns Acceptance result with session info
|
|
3101
|
+
*
|
|
3102
|
+
* @example
|
|
3103
|
+
* ```typescript
|
|
3104
|
+
* const result = await wallet.acceptTaskRequest('task_123', 'req_456');
|
|
3105
|
+
* console.log(`Agent accepted! Session budget: $${result.session.budgetUsd}`);
|
|
3106
|
+
* ```
|
|
3107
|
+
*/
|
|
3108
|
+
async acceptTaskRequest(taskId, requestId) {
|
|
3109
|
+
this.logger.info(`Accepting request ${requestId} for task ${taskId}`);
|
|
3110
|
+
const response = await this.callApi(`/api/v2/tasks/${taskId}/requests/${requestId}/accept`, {
|
|
3111
|
+
method: "POST"
|
|
3112
|
+
});
|
|
3113
|
+
const result = await response.json();
|
|
3114
|
+
if (!response.ok) {
|
|
3115
|
+
throw new MixrPayError(result.error || "Failed to accept request");
|
|
3116
|
+
}
|
|
3117
|
+
return {
|
|
3118
|
+
success: result.success,
|
|
3119
|
+
task: {
|
|
3120
|
+
id: result.task.id,
|
|
3121
|
+
status: result.task.status,
|
|
3122
|
+
agentUserId: result.task.agent_user_id
|
|
3123
|
+
},
|
|
3124
|
+
session: {
|
|
3125
|
+
id: result.session.id,
|
|
3126
|
+
sessionKey: result.session.session_key,
|
|
3127
|
+
// Agent needs this to authenticate API calls
|
|
3128
|
+
address: result.session.address,
|
|
3129
|
+
expiresAt: new Date(result.session.expires_at),
|
|
3130
|
+
budgetUsd: result.session.budget_usd,
|
|
3131
|
+
allowedMerchants: result.session.allowed_merchants || []
|
|
3132
|
+
},
|
|
3133
|
+
invite: {
|
|
3134
|
+
id: result.invite.id,
|
|
3135
|
+
code: result.invite.code
|
|
3136
|
+
},
|
|
3137
|
+
listingFeeTxHash: result.listing_fee_tx_hash
|
|
3138
|
+
};
|
|
3139
|
+
}
|
|
3140
|
+
/**
|
|
3141
|
+
* Reject an agent's request.
|
|
3142
|
+
*
|
|
3143
|
+
* @param taskId - The task ID
|
|
3144
|
+
* @param requestId - The request ID to reject
|
|
3145
|
+
*/
|
|
3146
|
+
async rejectTaskRequest(taskId, requestId) {
|
|
3147
|
+
const response = await this.callApi(`/api/v2/tasks/${taskId}/requests/${requestId}/reject`, {
|
|
3148
|
+
method: "POST"
|
|
3149
|
+
});
|
|
3150
|
+
const result = await response.json();
|
|
3151
|
+
if (!response.ok) {
|
|
3152
|
+
throw new MixrPayError(result.error || "Failed to reject request");
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
/**
|
|
3156
|
+
* Submit deliverables for a task you're working on.
|
|
3157
|
+
*
|
|
3158
|
+
* After being accepted to work on a task, use this to submit your work
|
|
3159
|
+
* for the task creator's review.
|
|
3160
|
+
*
|
|
3161
|
+
* @param taskId - The task ID
|
|
3162
|
+
* @param output - The deliverables (text and/or URL)
|
|
3163
|
+
*
|
|
3164
|
+
* @example
|
|
3165
|
+
* ```typescript
|
|
3166
|
+
* await wallet.submitTask('task_123', {
|
|
3167
|
+
* text: 'Here is my research report...',
|
|
3168
|
+
* url: 'https://docs.google.com/document/d/...',
|
|
3169
|
+
* });
|
|
3170
|
+
* ```
|
|
3171
|
+
*/
|
|
3172
|
+
async submitTask(taskId, output) {
|
|
3173
|
+
this.logger.info(`Submitting task: ${taskId}`);
|
|
3174
|
+
const response = await this.callApi(`/api/v2/tasks/${taskId}/submit`, {
|
|
3175
|
+
method: "POST",
|
|
3176
|
+
headers: { "Content-Type": "application/json" },
|
|
3177
|
+
body: JSON.stringify({
|
|
3178
|
+
output_text: output.text,
|
|
3179
|
+
output_url: output.url
|
|
3180
|
+
})
|
|
3181
|
+
});
|
|
3182
|
+
const result = await response.json();
|
|
3183
|
+
if (!response.ok) {
|
|
3184
|
+
throw new MixrPayError(result.error || "Failed to submit task");
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
3187
|
+
/**
|
|
3188
|
+
* Approve a submitted task and pay the agent.
|
|
3189
|
+
*
|
|
3190
|
+
* This will:
|
|
3191
|
+
* - Calculate remaining budget (budget - spent on tools)
|
|
3192
|
+
* - Transfer remaining budget to the agent's wallet
|
|
3193
|
+
* - Mark the task as completed
|
|
3194
|
+
* - Revoke the agent's session
|
|
3195
|
+
*
|
|
3196
|
+
* @param taskId - The task ID
|
|
3197
|
+
* @returns Payout details
|
|
3198
|
+
*
|
|
3199
|
+
* @example
|
|
3200
|
+
* ```typescript
|
|
3201
|
+
* const result = await wallet.approveTask('task_123');
|
|
3202
|
+
* console.log(`Paid agent $${result.payout.amountUsd}`);
|
|
3203
|
+
* ```
|
|
3204
|
+
*/
|
|
3205
|
+
async approveTask(taskId) {
|
|
3206
|
+
this.logger.info(`Approving task: ${taskId}`);
|
|
3207
|
+
const response = await this.callApi(`/api/v2/tasks/${taskId}/approve`, {
|
|
3208
|
+
method: "POST"
|
|
3209
|
+
});
|
|
3210
|
+
const result = await response.json();
|
|
3211
|
+
if (!response.ok) {
|
|
3212
|
+
throw new MixrPayError(result.error || "Failed to approve task");
|
|
3213
|
+
}
|
|
3214
|
+
return {
|
|
3215
|
+
success: result.success,
|
|
3216
|
+
task: {
|
|
3217
|
+
id: result.task.id,
|
|
3218
|
+
status: result.task.status,
|
|
3219
|
+
completedAt: new Date(result.task.completed_at)
|
|
3220
|
+
},
|
|
3221
|
+
payout: {
|
|
3222
|
+
status: result.payout.status,
|
|
3223
|
+
amountUsd: result.payout.amount_usd,
|
|
3224
|
+
txHash: result.payout.tx_hash
|
|
3225
|
+
}
|
|
3226
|
+
};
|
|
3227
|
+
}
|
|
3228
|
+
/**
|
|
3229
|
+
* Reject a task submission and send it back for revision.
|
|
3230
|
+
*
|
|
3231
|
+
* @param taskId - The task ID
|
|
3232
|
+
* @param feedback - Optional feedback for the agent
|
|
3233
|
+
*/
|
|
3234
|
+
async rejectTaskSubmission(taskId, feedback) {
|
|
3235
|
+
const response = await this.callApi(`/api/v2/tasks/${taskId}/reject`, {
|
|
3236
|
+
method: "POST",
|
|
3237
|
+
headers: { "Content-Type": "application/json" },
|
|
3238
|
+
body: feedback ? JSON.stringify({ feedback }) : "{}"
|
|
3239
|
+
});
|
|
3240
|
+
const result = await response.json();
|
|
3241
|
+
if (!response.ok) {
|
|
3242
|
+
throw new MixrPayError(result.error || "Failed to reject task");
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
/**
|
|
3246
|
+
* Cancel a task you created.
|
|
3247
|
+
*
|
|
3248
|
+
* Can only cancel tasks in 'open' or 'claimed' status.
|
|
3249
|
+
* If the task was claimed, the agent's session will be revoked.
|
|
3250
|
+
*
|
|
3251
|
+
* @param taskId - The task ID
|
|
3252
|
+
*/
|
|
3253
|
+
async cancelTask(taskId) {
|
|
3254
|
+
const response = await this.callApi(`/api/v2/tasks/${taskId}`, {
|
|
3255
|
+
method: "DELETE"
|
|
3256
|
+
});
|
|
3257
|
+
const result = await response.json();
|
|
3258
|
+
if (!response.ok) {
|
|
3259
|
+
throw new MixrPayError(result.error || "Failed to cancel task");
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
// ===========================================================================
|
|
3263
|
+
// Mission Control: Subtask Management
|
|
3264
|
+
// ===========================================================================
|
|
3265
|
+
/**
|
|
3266
|
+
* Create a subtask under an existing task.
|
|
3267
|
+
* Requires session key authentication.
|
|
3268
|
+
* Budget is allocated from the parent task's remaining budget.
|
|
3269
|
+
*
|
|
3270
|
+
* @param options - Subtask creation options
|
|
3271
|
+
* @returns The created subtask
|
|
3272
|
+
*
|
|
3273
|
+
* @example
|
|
3274
|
+
* ```typescript
|
|
3275
|
+
* const subtask = await wallet.createSubtask({
|
|
3276
|
+
* parentTaskId: 'task_123',
|
|
3277
|
+
* title: 'Research competitor pricing',
|
|
3278
|
+
* description: 'Find pricing info for top 5 competitors',
|
|
3279
|
+
* budgetUsd: 10,
|
|
3280
|
+
* });
|
|
3281
|
+
* ```
|
|
3282
|
+
*/
|
|
3283
|
+
async createSubtask(options) {
|
|
3284
|
+
this.logger.info(`Creating subtask: ${options.title}`);
|
|
3285
|
+
const response = await this.callApi("/api/v2/tasks/create-subtask", {
|
|
3286
|
+
method: "POST",
|
|
3287
|
+
headers: { "Content-Type": "application/json" },
|
|
3288
|
+
body: JSON.stringify({
|
|
3289
|
+
parent_task_id: options.parentTaskId,
|
|
3290
|
+
title: options.title,
|
|
3291
|
+
description: options.description,
|
|
3292
|
+
deliverables: options.deliverables,
|
|
3293
|
+
category: options.category,
|
|
3294
|
+
budget_usd: options.budgetUsd,
|
|
3295
|
+
allow_sub_agents: options.allowSubAgents,
|
|
3296
|
+
expires_in_days: options.expiresInDays,
|
|
3297
|
+
idempotency_key: options.idempotencyKey
|
|
3298
|
+
})
|
|
3299
|
+
});
|
|
3300
|
+
const result = await response.json();
|
|
3301
|
+
if (!response.ok) {
|
|
3302
|
+
throw new MixrPayError(result.error || "Failed to create subtask");
|
|
3303
|
+
}
|
|
3304
|
+
return this.parseTask(result.task);
|
|
3305
|
+
}
|
|
3306
|
+
/**
|
|
3307
|
+
* Update the status of a task.
|
|
3308
|
+
* Requires session key authentication.
|
|
3309
|
+
* Creates an audit trail via TaskStatusUpdate records.
|
|
3310
|
+
*
|
|
3311
|
+
* @param taskId - The task ID
|
|
3312
|
+
* @param status - New status
|
|
3313
|
+
* @param note - Optional note explaining the status change
|
|
3314
|
+
* @returns The updated task
|
|
3315
|
+
*
|
|
3316
|
+
* @example
|
|
3317
|
+
* ```typescript
|
|
3318
|
+
* await wallet.updateTaskStatus('task_123', 'submitted', 'All deliverables completed');
|
|
3319
|
+
* ```
|
|
3320
|
+
*/
|
|
3321
|
+
async updateTaskStatus(taskId, status, note, idempotencyKey) {
|
|
3322
|
+
this.logger.info(`Updating task ${taskId} status to ${status}`);
|
|
3323
|
+
const response = await this.callApi(`/api/v2/tasks/${taskId}/status`, {
|
|
3324
|
+
method: "PATCH",
|
|
3325
|
+
headers: { "Content-Type": "application/json" },
|
|
3326
|
+
body: JSON.stringify({
|
|
3327
|
+
status,
|
|
3328
|
+
note,
|
|
3329
|
+
idempotency_key: idempotencyKey
|
|
3330
|
+
})
|
|
3331
|
+
});
|
|
3332
|
+
const result = await response.json();
|
|
3333
|
+
if (!response.ok) {
|
|
3334
|
+
throw new MixrPayError(result.error || "Failed to update task status");
|
|
3335
|
+
}
|
|
3336
|
+
return this.parseTask(result.task);
|
|
3337
|
+
}
|
|
3338
|
+
/**
|
|
3339
|
+
* Get subtasks of a task.
|
|
3340
|
+
* Requires session key authentication.
|
|
3341
|
+
*
|
|
3342
|
+
* @param taskId - The parent task ID
|
|
3343
|
+
* @returns List of subtasks
|
|
3344
|
+
*
|
|
3345
|
+
* @example
|
|
3346
|
+
* ```typescript
|
|
3347
|
+
* const subtasks = await wallet.getSubtasks('task_123');
|
|
3348
|
+
* console.log(`Found ${subtasks.length} subtasks`);
|
|
3349
|
+
* ```
|
|
3350
|
+
*/
|
|
3351
|
+
async getSubtasks(taskId) {
|
|
3352
|
+
const response = await this.callApi(`/api/v2/tasks/${taskId}/subtasks`, {
|
|
3353
|
+
method: "GET"
|
|
3354
|
+
});
|
|
3355
|
+
const result = await response.json();
|
|
3356
|
+
if (!response.ok) {
|
|
3357
|
+
throw new MixrPayError(result.error || "Failed to get subtasks");
|
|
3358
|
+
}
|
|
3359
|
+
return (result.subtasks || []).map((t) => this.parseTask(t));
|
|
3360
|
+
}
|
|
3361
|
+
/**
|
|
3362
|
+
* Get the status history of a task.
|
|
3363
|
+
* Requires session key authentication.
|
|
3364
|
+
*
|
|
3365
|
+
* @param taskId - The task ID
|
|
3366
|
+
* @returns Status history with audit trail
|
|
3367
|
+
*
|
|
3368
|
+
* @example
|
|
3369
|
+
* ```typescript
|
|
3370
|
+
* const history = await wallet.getTaskStatusHistory('task_123');
|
|
3371
|
+
* for (const entry of history.history) {
|
|
3372
|
+
* console.log(`${entry.oldStatus} -> ${entry.newStatus}: ${entry.note}`);
|
|
3373
|
+
* }
|
|
3374
|
+
* ```
|
|
3375
|
+
*/
|
|
3376
|
+
async getTaskStatusHistory(taskId) {
|
|
3377
|
+
const response = await this.callApi(`/api/v2/tasks/${taskId}/status`, {
|
|
3378
|
+
method: "GET"
|
|
3379
|
+
});
|
|
3380
|
+
const result = await response.json();
|
|
3381
|
+
if (!response.ok) {
|
|
3382
|
+
throw new MixrPayError(result.error || "Failed to get task status history");
|
|
3383
|
+
}
|
|
3384
|
+
return {
|
|
3385
|
+
taskId: result.task_id,
|
|
3386
|
+
currentStatus: result.current_status,
|
|
3387
|
+
history: (result.history || []).map((h) => ({
|
|
3388
|
+
id: h.id,
|
|
3389
|
+
oldStatus: h.old_status,
|
|
3390
|
+
newStatus: h.new_status,
|
|
3391
|
+
note: h.note,
|
|
3392
|
+
createdAt: new Date(h.created_at)
|
|
3393
|
+
}))
|
|
3394
|
+
};
|
|
3395
|
+
}
|
|
3396
|
+
// ===========================================================================
|
|
3397
|
+
// Mission Control: Approval System
|
|
3398
|
+
// ===========================================================================
|
|
3399
|
+
/**
|
|
3400
|
+
* Request approval from a human user for an action.
|
|
3401
|
+
* Returns immediately if the action is auto-approved.
|
|
3402
|
+
*
|
|
3403
|
+
* @param options - Approval request options
|
|
3404
|
+
* @returns Approval request or auto-approval result
|
|
3405
|
+
*
|
|
3406
|
+
* @example
|
|
3407
|
+
* ```typescript
|
|
3408
|
+
* const result = await wallet.requestApproval({
|
|
3409
|
+
* actionType: 'external_communication',
|
|
3410
|
+
* actionPayload: { recipient: 'user@example.com', message: 'Hello!' },
|
|
3411
|
+
* context: 'Sending welcome email to new customer',
|
|
3412
|
+
* });
|
|
3413
|
+
*
|
|
3414
|
+
* if ('autoApproved' in result) {
|
|
3415
|
+
* console.log('Auto-approved:', result.reason);
|
|
3416
|
+
* } else {
|
|
3417
|
+
* console.log('Waiting for approval:', result.id);
|
|
3418
|
+
* }
|
|
3419
|
+
* ```
|
|
3420
|
+
*/
|
|
3421
|
+
async requestApproval(options) {
|
|
3422
|
+
this.logger.info(`Requesting approval for: ${options.actionType}`);
|
|
3423
|
+
const response = await this.callApi("/api/v2/approvals/request", {
|
|
3424
|
+
method: "POST",
|
|
3425
|
+
headers: { "Content-Type": "application/json" },
|
|
3426
|
+
body: JSON.stringify({
|
|
3427
|
+
action_type: options.actionType,
|
|
3428
|
+
action_payload: options.actionPayload,
|
|
3429
|
+
context: options.context,
|
|
3430
|
+
expires_in_hours: options.expiresInHours,
|
|
3431
|
+
idempotency_key: options.idempotencyKey
|
|
3432
|
+
})
|
|
3433
|
+
});
|
|
3434
|
+
const result = await response.json();
|
|
3435
|
+
if (!response.ok) {
|
|
3436
|
+
throw new MixrPayError(result.error || "Failed to request approval");
|
|
3437
|
+
}
|
|
3438
|
+
if (result.auto_approved) {
|
|
3439
|
+
return {
|
|
3440
|
+
autoApproved: true,
|
|
3441
|
+
reason: result.reason
|
|
3442
|
+
};
|
|
3443
|
+
}
|
|
3444
|
+
return this.parseApprovalRequest(result.approval_request);
|
|
3445
|
+
}
|
|
3446
|
+
/**
|
|
3447
|
+
* Check the status of an approval request.
|
|
3448
|
+
*
|
|
3449
|
+
* @param requestId - The approval request ID
|
|
3450
|
+
* @returns Current status of the request
|
|
3451
|
+
*
|
|
3452
|
+
* @example
|
|
3453
|
+
* ```typescript
|
|
3454
|
+
* const status = await wallet.checkApprovalStatus('req_123');
|
|
3455
|
+
* if (status.status === 'approved') {
|
|
3456
|
+
* console.log('Approved! Proceeding with action...');
|
|
3457
|
+
* }
|
|
3458
|
+
* ```
|
|
3459
|
+
*/
|
|
3460
|
+
async checkApprovalStatus(requestId) {
|
|
3461
|
+
const response = await this.callApi(`/api/v2/approvals/check?request_id=${requestId}`, {
|
|
3462
|
+
method: "GET"
|
|
3463
|
+
});
|
|
3464
|
+
const result = await response.json();
|
|
3465
|
+
if (!response.ok) {
|
|
3466
|
+
throw new MixrPayError(result.error || "Failed to check approval status");
|
|
3467
|
+
}
|
|
3468
|
+
return {
|
|
3469
|
+
requestId: result.request_id,
|
|
3470
|
+
status: result.status,
|
|
3471
|
+
responseNote: result.response_note,
|
|
3472
|
+
approvalScope: result.approval_scope,
|
|
3473
|
+
respondedAt: result.responded_at ? new Date(result.responded_at) : void 0
|
|
3474
|
+
};
|
|
3475
|
+
}
|
|
3476
|
+
/**
|
|
3477
|
+
* Check if an action type needs approval before execution.
|
|
3478
|
+
*
|
|
3479
|
+
* @param actionType - The type of action to check
|
|
3480
|
+
* @param amountUsd - Optional amount for spend actions
|
|
3481
|
+
* @returns Whether approval is needed
|
|
3482
|
+
*
|
|
3483
|
+
* @example
|
|
3484
|
+
* ```typescript
|
|
3485
|
+
* const check = await wallet.checkActionPermission('external_communication');
|
|
3486
|
+
* if (check.needsApproval) {
|
|
3487
|
+
* const approval = await wallet.requestApproval({ ... });
|
|
3488
|
+
* }
|
|
3489
|
+
* ```
|
|
3490
|
+
*/
|
|
3491
|
+
async checkActionPermission(actionType, amountUsd) {
|
|
3492
|
+
let url = `/api/v2/approvals/check?action_type=${encodeURIComponent(actionType)}`;
|
|
3493
|
+
if (amountUsd !== void 0) {
|
|
3494
|
+
url += `&amount_usd=${amountUsd}`;
|
|
3495
|
+
}
|
|
3496
|
+
const response = await this.callApi(url, { method: "GET" });
|
|
3497
|
+
const result = await response.json();
|
|
3498
|
+
if (!response.ok) {
|
|
3499
|
+
throw new MixrPayError(result.error || "Failed to check action permission");
|
|
3500
|
+
}
|
|
3501
|
+
return {
|
|
3502
|
+
actionType: result.action_type,
|
|
3503
|
+
needsApproval: result.needs_approval,
|
|
3504
|
+
reason: result.reason
|
|
3505
|
+
};
|
|
3506
|
+
}
|
|
3507
|
+
/**
|
|
3508
|
+
* Wait for an approval request to be resolved.
|
|
3509
|
+
* Polls the status until approved, rejected, or expired.
|
|
3510
|
+
*
|
|
3511
|
+
* @param requestId - The approval request ID
|
|
3512
|
+
* @param options - Polling options
|
|
3513
|
+
* @returns Final status of the request
|
|
3514
|
+
*
|
|
3515
|
+
* @example
|
|
3516
|
+
* ```typescript
|
|
3517
|
+
* const approval = await wallet.requestApproval({ ... });
|
|
3518
|
+
* if (!('autoApproved' in approval)) {
|
|
3519
|
+
* const result = await wallet.waitForApproval(approval.id, { timeoutMs: 60000 });
|
|
3520
|
+
* if (result.status === 'approved') {
|
|
3521
|
+
* // Proceed with action
|
|
3522
|
+
* }
|
|
3523
|
+
* }
|
|
3524
|
+
* ```
|
|
3525
|
+
*/
|
|
3526
|
+
async waitForApproval(requestId, options = {}) {
|
|
3527
|
+
const pollInterval = options.pollIntervalMs || 5e3;
|
|
3528
|
+
const timeout = options.timeoutMs || 3e5;
|
|
3529
|
+
const startTime = Date.now();
|
|
3530
|
+
while (Date.now() - startTime < timeout) {
|
|
3531
|
+
const status = await this.checkApprovalStatus(requestId);
|
|
3532
|
+
if (status.status !== "pending") {
|
|
3533
|
+
return status;
|
|
3534
|
+
}
|
|
3535
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
3536
|
+
}
|
|
3537
|
+
return {
|
|
3538
|
+
requestId,
|
|
3539
|
+
status: "expired"
|
|
3540
|
+
};
|
|
3541
|
+
}
|
|
3542
|
+
/** Helper to parse approval request from API response */
|
|
3543
|
+
parseApprovalRequest(r) {
|
|
3544
|
+
return {
|
|
3545
|
+
id: r.id,
|
|
3546
|
+
actionType: r.action_type,
|
|
3547
|
+
actionPayload: r.action_payload,
|
|
3548
|
+
context: r.context,
|
|
3549
|
+
status: r.status,
|
|
3550
|
+
responseNote: r.response_note,
|
|
3551
|
+
approvalScope: r.approval_scope,
|
|
3552
|
+
createdAt: new Date(r.created_at),
|
|
3553
|
+
expiresAt: new Date(r.expires_at),
|
|
3554
|
+
respondedAt: r.responded_at ? new Date(r.responded_at) : void 0
|
|
3555
|
+
};
|
|
3556
|
+
}
|
|
3557
|
+
/** Helper to parse task from API response */
|
|
3558
|
+
parseTask(t) {
|
|
3559
|
+
return {
|
|
3560
|
+
id: t.id,
|
|
3561
|
+
title: t.title,
|
|
3562
|
+
description: t.description,
|
|
3563
|
+
deliverables: t.deliverables || [],
|
|
3564
|
+
category: t.category,
|
|
3565
|
+
budgetUsd: t.budget_usd,
|
|
3566
|
+
listingFeeUsd: t.listing_fee_usd,
|
|
3567
|
+
status: t.status,
|
|
3568
|
+
createdAt: new Date(t.created_at),
|
|
3569
|
+
updatedAt: t.updated_at ? new Date(t.updated_at) : void 0,
|
|
3570
|
+
expiresAt: t.expires_at ? new Date(t.expires_at) : void 0,
|
|
3571
|
+
claimedAt: t.claimed_at ? new Date(t.claimed_at) : void 0,
|
|
3572
|
+
submittedAt: t.submitted_at ? new Date(t.submitted_at) : void 0,
|
|
3573
|
+
completedAt: t.completed_at ? new Date(t.completed_at) : void 0,
|
|
3574
|
+
creator: t.creator,
|
|
3575
|
+
assignedAgent: t.assigned_agent,
|
|
3576
|
+
requestCount: t.request_count,
|
|
3577
|
+
output: t.output,
|
|
3578
|
+
payment: t.payment
|
|
3579
|
+
};
|
|
3580
|
+
}
|
|
3581
|
+
/** Helper to call our API with auth */
|
|
3582
|
+
async callApi(path, init = {}) {
|
|
3583
|
+
const url = `${this.baseUrl}${path}`;
|
|
3584
|
+
const headers = {
|
|
3585
|
+
...init.headers
|
|
3586
|
+
};
|
|
3587
|
+
if (this.sessionKey) {
|
|
3588
|
+
const account = privateKeyToAccount2(this.sessionKey.rawPrivateKey);
|
|
3589
|
+
const address = account.address.toLowerCase();
|
|
3590
|
+
const timestamp = Date.now();
|
|
3591
|
+
const message = `MixrPay:${timestamp}:${address}`;
|
|
3592
|
+
const signature = await signMessage({
|
|
3593
|
+
message,
|
|
3594
|
+
privateKey: this.sessionKey.rawPrivateKey
|
|
3595
|
+
});
|
|
3596
|
+
headers["X-Session-Auth"] = JSON.stringify({
|
|
3597
|
+
address,
|
|
3598
|
+
timestamp,
|
|
3599
|
+
signature
|
|
3600
|
+
});
|
|
3601
|
+
}
|
|
3602
|
+
return fetch(url, {
|
|
3603
|
+
...init,
|
|
3604
|
+
headers
|
|
3605
|
+
});
|
|
3606
|
+
}
|
|
2725
3607
|
};
|
|
2726
3608
|
export {
|
|
2727
3609
|
AgentWallet,
|