@mixrpay/agent-sdk 0.8.3 → 0.8.5
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.
Potentially problematic release.
This version of @mixrpay/agent-sdk might be problematic. Click here for more details.
- package/README.md +12 -0
- package/dist/index.cjs +1094 -12
- package/dist/index.d.cts +894 -5
- package/dist/index.d.ts +894 -5
- package/dist/index.js +1094 -12
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -335,6 +335,13 @@ var SessionKey = class _SessionKey {
|
|
|
335
335
|
get privateKeyHex() {
|
|
336
336
|
return this.privateKey.slice(2);
|
|
337
337
|
}
|
|
338
|
+
/**
|
|
339
|
+
* Get the raw private key with 0x prefix.
|
|
340
|
+
* Used internally for signing operations.
|
|
341
|
+
*/
|
|
342
|
+
get rawPrivateKey() {
|
|
343
|
+
return this.privateKey;
|
|
344
|
+
}
|
|
338
345
|
/**
|
|
339
346
|
* Parse a session key string into a SessionKey object.
|
|
340
347
|
*
|
|
@@ -525,7 +532,7 @@ function getAmountUsd(requirements) {
|
|
|
525
532
|
}
|
|
526
533
|
|
|
527
534
|
// src/agent-wallet.ts
|
|
528
|
-
var SDK_VERSION = "0.8.
|
|
535
|
+
var SDK_VERSION = "0.8.5";
|
|
529
536
|
var DEFAULT_BASE_URL = process.env.MIXRPAY_BASE_URL || "https://www.mixrpay.com";
|
|
530
537
|
var DEFAULT_TIMEOUT = 3e4;
|
|
531
538
|
var NETWORKS = {
|
|
@@ -1227,6 +1234,160 @@ var AgentWallet = class {
|
|
|
1227
1234
|
};
|
|
1228
1235
|
}
|
|
1229
1236
|
// ===========================================================================
|
|
1237
|
+
// Nested Budget Delegation Methods
|
|
1238
|
+
// ===========================================================================
|
|
1239
|
+
/**
|
|
1240
|
+
* Spawn a child invite for sub-agents.
|
|
1241
|
+
*
|
|
1242
|
+
* Allows an agent to create an invite code for a sub-agent with a portion
|
|
1243
|
+
* of their remaining budget (max 20%). The child inherits merchant restrictions
|
|
1244
|
+
* and cannot outlive the parent session.
|
|
1245
|
+
*
|
|
1246
|
+
* @param options - Spawn options
|
|
1247
|
+
* @returns The created child invite details
|
|
1248
|
+
*
|
|
1249
|
+
* @throws {MixrPayError} If spawning fails (insufficient budget, depth limit, etc.)
|
|
1250
|
+
*
|
|
1251
|
+
* @example
|
|
1252
|
+
* ```typescript
|
|
1253
|
+
* const wallet = new AgentWallet({ sessionKey: 'sk_live_...' });
|
|
1254
|
+
*
|
|
1255
|
+
* // Spawn a child invite for a sub-agent
|
|
1256
|
+
* const childInvite = await wallet.spawnChildInvite({
|
|
1257
|
+
* budgetUsd: 10.00, // Max 20% of available budget
|
|
1258
|
+
* name: 'Research Sub-Agent',
|
|
1259
|
+
* allowedMerchants: ['firecrawl.dev'], // Must be subset of parent
|
|
1260
|
+
* expiresInDays: 7, // Will be capped to parent's expiry
|
|
1261
|
+
* });
|
|
1262
|
+
*
|
|
1263
|
+
* console.log(`Share with sub-agent: ${childInvite.inviteCode}`);
|
|
1264
|
+
* console.log(`Child can spawn up to: $${childInvite.maxSpawnBudget}`);
|
|
1265
|
+
* ```
|
|
1266
|
+
*/
|
|
1267
|
+
async spawnChildInvite(options) {
|
|
1268
|
+
const { budgetUsd, name, allowedMerchants, expiresInDays } = options;
|
|
1269
|
+
const timestamp = Date.now();
|
|
1270
|
+
const nonce = crypto.randomUUID();
|
|
1271
|
+
const message = `spawn:${timestamp}:${nonce}`;
|
|
1272
|
+
const signature = await (0, import_accounts2.signMessage)({
|
|
1273
|
+
message,
|
|
1274
|
+
privateKey: this.sessionKey.rawPrivateKey
|
|
1275
|
+
});
|
|
1276
|
+
const response = await fetch(`${this.baseUrl}/api/v1/agent/spawn`, {
|
|
1277
|
+
method: "POST",
|
|
1278
|
+
headers: { "Content-Type": "application/json" },
|
|
1279
|
+
body: JSON.stringify({
|
|
1280
|
+
session_key: this.sessionKey.toString(),
|
|
1281
|
+
signature,
|
|
1282
|
+
message,
|
|
1283
|
+
budget_usd: budgetUsd,
|
|
1284
|
+
name,
|
|
1285
|
+
allowed_merchants: allowedMerchants,
|
|
1286
|
+
expires_in_days: expiresInDays
|
|
1287
|
+
})
|
|
1288
|
+
});
|
|
1289
|
+
if (!response.ok) {
|
|
1290
|
+
const error = await response.json().catch(() => ({}));
|
|
1291
|
+
if (response.status === 409) {
|
|
1292
|
+
throw new MixrPayError("Concurrent modification - please retry");
|
|
1293
|
+
}
|
|
1294
|
+
if (response.status === 429) {
|
|
1295
|
+
throw new MixrPayError("Rate limited - too many spawn attempts");
|
|
1296
|
+
}
|
|
1297
|
+
throw new MixrPayError(error.error || `Failed to spawn child: ${response.status}`);
|
|
1298
|
+
}
|
|
1299
|
+
const data = await response.json();
|
|
1300
|
+
return {
|
|
1301
|
+
inviteCode: data.invite_code,
|
|
1302
|
+
inviteId: data.invite_id,
|
|
1303
|
+
budgetUsd: data.budget_usd,
|
|
1304
|
+
expiresAt: new Date(data.expires_at),
|
|
1305
|
+
depth: data.depth,
|
|
1306
|
+
maxSpawnBudget: data.max_spawn_budget,
|
|
1307
|
+
allowedMerchants: data.allowed_merchants || []
|
|
1308
|
+
};
|
|
1309
|
+
}
|
|
1310
|
+
/**
|
|
1311
|
+
* Get available budget information for spawning.
|
|
1312
|
+
*
|
|
1313
|
+
* Returns the current budget status including how much can be spawned
|
|
1314
|
+
* to child agents (20% of available).
|
|
1315
|
+
*
|
|
1316
|
+
* @returns Budget information
|
|
1317
|
+
*
|
|
1318
|
+
* @example
|
|
1319
|
+
* ```typescript
|
|
1320
|
+
* const budget = await wallet.getAvailableBudget();
|
|
1321
|
+
*
|
|
1322
|
+
* console.log(`Total budget: $${budget.totalBudget}`);
|
|
1323
|
+
* console.log(`Spent: $${budget.spent}`);
|
|
1324
|
+
* console.log(`Allocated to children: $${budget.allocatedToChildren}`);
|
|
1325
|
+
* console.log(`Available: $${budget.available}`);
|
|
1326
|
+
* console.log(`Max spawn budget: $${budget.maxSpawnBudget}`);
|
|
1327
|
+
*
|
|
1328
|
+
* if (budget.canSpawn && budget.maxSpawnBudget >= 5.00) {
|
|
1329
|
+
* // Safe to spawn a $5 child
|
|
1330
|
+
* }
|
|
1331
|
+
* ```
|
|
1332
|
+
*/
|
|
1333
|
+
async getAvailableBudget() {
|
|
1334
|
+
const response = await fetch(`${this.baseUrl}/api/v1/agent/descendants`, {
|
|
1335
|
+
method: "GET",
|
|
1336
|
+
headers: {
|
|
1337
|
+
"Authorization": `Bearer ${this.sessionKey.toString()}`
|
|
1338
|
+
}
|
|
1339
|
+
});
|
|
1340
|
+
if (!response.ok) {
|
|
1341
|
+
const error = await response.json().catch(() => ({}));
|
|
1342
|
+
throw new MixrPayError(error.error || `Failed to get budget: ${response.status}`);
|
|
1343
|
+
}
|
|
1344
|
+
const data = await response.json();
|
|
1345
|
+
return {
|
|
1346
|
+
totalBudget: data.budget.total_usd,
|
|
1347
|
+
spent: data.budget.spent_usd,
|
|
1348
|
+
allocatedToChildren: data.budget.allocated_to_children_usd,
|
|
1349
|
+
available: data.budget.available_usd,
|
|
1350
|
+
maxSpawnBudget: data.max_spawn_budget,
|
|
1351
|
+
canSpawn: data.can_spawn
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
/**
|
|
1355
|
+
* Get all child sessions spawned by this session.
|
|
1356
|
+
*
|
|
1357
|
+
* Returns a tree of child invites/sessions including their spending status.
|
|
1358
|
+
*
|
|
1359
|
+
* @returns Array of child sessions
|
|
1360
|
+
*
|
|
1361
|
+
* @example
|
|
1362
|
+
* ```typescript
|
|
1363
|
+
* const children = await wallet.getChildSessions();
|
|
1364
|
+
*
|
|
1365
|
+
* for (const child of children) {
|
|
1366
|
+
* console.log(`${child.name}: $${child.spentUsd}/$${child.budgetUsd}`);
|
|
1367
|
+
* console.log(` Status: ${child.status}`);
|
|
1368
|
+
* console.log(` Depth: ${child.depth}`);
|
|
1369
|
+
*
|
|
1370
|
+
* if (child.children) {
|
|
1371
|
+
* console.log(` Has ${child.children.length} grandchildren`);
|
|
1372
|
+
* }
|
|
1373
|
+
* }
|
|
1374
|
+
* ```
|
|
1375
|
+
*/
|
|
1376
|
+
async getChildSessions() {
|
|
1377
|
+
const response = await fetch(`${this.baseUrl}/api/v1/agent/descendants`, {
|
|
1378
|
+
method: "GET",
|
|
1379
|
+
headers: {
|
|
1380
|
+
"Authorization": `Bearer ${this.sessionKey.toString()}`
|
|
1381
|
+
}
|
|
1382
|
+
});
|
|
1383
|
+
if (!response.ok) {
|
|
1384
|
+
const error = await response.json().catch(() => ({}));
|
|
1385
|
+
throw new MixrPayError(error.error || `Failed to get children: ${response.status}`);
|
|
1386
|
+
}
|
|
1387
|
+
const data = await response.json();
|
|
1388
|
+
return data.children || [];
|
|
1389
|
+
}
|
|
1390
|
+
// ===========================================================================
|
|
1230
1391
|
// Core Methods
|
|
1231
1392
|
// ===========================================================================
|
|
1232
1393
|
/**
|
|
@@ -2371,6 +2532,206 @@ Timestamp: ${timestamp}`;
|
|
|
2371
2532
|
};
|
|
2372
2533
|
}
|
|
2373
2534
|
// ===========================================================================
|
|
2535
|
+
// JIT MCP Server Methods
|
|
2536
|
+
// ===========================================================================
|
|
2537
|
+
/**
|
|
2538
|
+
* Deploy a JIT MCP server from the Glama directory.
|
|
2539
|
+
*
|
|
2540
|
+
* Deploys any remote-capable MCP server to Cloudflare Workers.
|
|
2541
|
+
* Charges $1 from your session budget.
|
|
2542
|
+
*
|
|
2543
|
+
* @param options - Deployment options including Glama server details and env vars
|
|
2544
|
+
* @returns Deployed instance with private endpoint URL
|
|
2545
|
+
*
|
|
2546
|
+
* @example
|
|
2547
|
+
* ```typescript
|
|
2548
|
+
* const result = await wallet.deployJitMcp({
|
|
2549
|
+
* glamaId: 'notion-mcp',
|
|
2550
|
+
* glamaNamespace: 'notion',
|
|
2551
|
+
* glamaSlug: 'notion-mcp',
|
|
2552
|
+
* toolName: 'My Notion Server',
|
|
2553
|
+
* envVars: { NOTION_API_KEY: 'secret_...' },
|
|
2554
|
+
* ttlHours: 24,
|
|
2555
|
+
* });
|
|
2556
|
+
*
|
|
2557
|
+
* console.log('Endpoint:', result.instance.endpointUrl);
|
|
2558
|
+
* console.log('Expires:', result.instance.expiresAt);
|
|
2559
|
+
* ```
|
|
2560
|
+
*/
|
|
2561
|
+
async deployJitMcp(options) {
|
|
2562
|
+
this.logger.debug("deployJitMcp", { glamaId: options.glamaId, toolName: options.toolName });
|
|
2563
|
+
const authHeaders = await this.getSessionAuthHeaders();
|
|
2564
|
+
const response = await fetch(`${this.baseUrl}/api/v2/jit/deploy`, {
|
|
2565
|
+
method: "POST",
|
|
2566
|
+
headers: {
|
|
2567
|
+
"Content-Type": "application/json",
|
|
2568
|
+
...authHeaders
|
|
2569
|
+
},
|
|
2570
|
+
body: JSON.stringify({
|
|
2571
|
+
glama_id: options.glamaId,
|
|
2572
|
+
glama_namespace: options.glamaNamespace,
|
|
2573
|
+
glama_slug: options.glamaSlug,
|
|
2574
|
+
tool_name: options.toolName,
|
|
2575
|
+
tool_description: options.toolDescription,
|
|
2576
|
+
env_vars: options.envVars,
|
|
2577
|
+
ttl_hours: options.ttlHours || 24
|
|
2578
|
+
// session_id not needed - derived from X-Session-Auth
|
|
2579
|
+
})
|
|
2580
|
+
});
|
|
2581
|
+
if (!response.ok) {
|
|
2582
|
+
const error = await response.json().catch(() => ({}));
|
|
2583
|
+
throw new MixrPayError(error.error || `JIT deploy failed: ${response.status}`);
|
|
2584
|
+
}
|
|
2585
|
+
const data = await response.json();
|
|
2586
|
+
if (data.payment?.amount_usd > 0) {
|
|
2587
|
+
const payment = {
|
|
2588
|
+
amountUsd: data.payment.amount_usd,
|
|
2589
|
+
recipient: "mixrpay-jit-deploy",
|
|
2590
|
+
txHash: data.payment.tx_hash,
|
|
2591
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
2592
|
+
description: `JIT Deploy: ${options.toolName}`,
|
|
2593
|
+
url: `${this.baseUrl}/api/v2/jit/deploy`,
|
|
2594
|
+
requestId: data.request_id || crypto.randomUUID()
|
|
2595
|
+
};
|
|
2596
|
+
this.payments.push(payment);
|
|
2597
|
+
this.totalSpentUsd += data.payment.amount_usd;
|
|
2598
|
+
this.logger.payment(data.payment.amount_usd, "jit-deploy", options.toolName);
|
|
2599
|
+
if (this.onPayment) {
|
|
2600
|
+
this.onPayment(payment);
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
return {
|
|
2604
|
+
instance: this.parseJitInstance(data.instance),
|
|
2605
|
+
payment: {
|
|
2606
|
+
method: data.payment.method,
|
|
2607
|
+
amountUsd: data.payment.amount_usd,
|
|
2608
|
+
txHash: data.payment.tx_hash
|
|
2609
|
+
}
|
|
2610
|
+
};
|
|
2611
|
+
}
|
|
2612
|
+
/**
|
|
2613
|
+
* List your deployed JIT MCP server instances.
|
|
2614
|
+
*
|
|
2615
|
+
* @param options - Optional filters
|
|
2616
|
+
* @returns Array of JIT instances
|
|
2617
|
+
*
|
|
2618
|
+
* @example
|
|
2619
|
+
* ```typescript
|
|
2620
|
+
* const instances = await wallet.listJitInstances({ status: 'active' });
|
|
2621
|
+
* for (const inst of instances) {
|
|
2622
|
+
* console.log(`${inst.toolName}: ${inst.endpointUrl}`);
|
|
2623
|
+
* }
|
|
2624
|
+
* ```
|
|
2625
|
+
*/
|
|
2626
|
+
async listJitInstances(options) {
|
|
2627
|
+
this.logger.debug("listJitInstances", options);
|
|
2628
|
+
const authHeaders = await this.getSessionAuthHeaders();
|
|
2629
|
+
const params = new URLSearchParams();
|
|
2630
|
+
if (options?.status) params.set("status", options.status);
|
|
2631
|
+
const response = await fetch(
|
|
2632
|
+
`${this.baseUrl}/api/v2/jit/instances?${params}`,
|
|
2633
|
+
{ headers: authHeaders }
|
|
2634
|
+
);
|
|
2635
|
+
if (!response.ok) {
|
|
2636
|
+
const error = await response.json().catch(() => ({}));
|
|
2637
|
+
throw new MixrPayError(error.error || `Failed to list JIT instances: ${response.status}`);
|
|
2638
|
+
}
|
|
2639
|
+
const data = await response.json();
|
|
2640
|
+
return (data.instances || []).map((i) => this.parseJitInstance(i));
|
|
2641
|
+
}
|
|
2642
|
+
/**
|
|
2643
|
+
* Stop a running JIT MCP server instance.
|
|
2644
|
+
*
|
|
2645
|
+
* Instance will be marked as stopped and Worker cleaned up.
|
|
2646
|
+
* No refund is given - instances are billed at deploy time.
|
|
2647
|
+
*
|
|
2648
|
+
* @param instanceId - The instance ID to stop
|
|
2649
|
+
*
|
|
2650
|
+
* @example
|
|
2651
|
+
* ```typescript
|
|
2652
|
+
* await wallet.stopJitInstance('inst_abc123');
|
|
2653
|
+
* console.log('Instance stopped');
|
|
2654
|
+
* ```
|
|
2655
|
+
*/
|
|
2656
|
+
async stopJitInstance(instanceId) {
|
|
2657
|
+
this.logger.debug("stopJitInstance", { instanceId });
|
|
2658
|
+
const authHeaders = await this.getSessionAuthHeaders();
|
|
2659
|
+
const response = await fetch(
|
|
2660
|
+
`${this.baseUrl}/api/v2/jit/instances/${instanceId}`,
|
|
2661
|
+
{ method: "DELETE", headers: authHeaders }
|
|
2662
|
+
);
|
|
2663
|
+
if (!response.ok) {
|
|
2664
|
+
const error = await response.json().catch(() => ({}));
|
|
2665
|
+
throw new MixrPayError(error.error || `Failed to stop JIT instance: ${response.status}`);
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
/**
|
|
2669
|
+
* Parse JIT instance response data.
|
|
2670
|
+
*/
|
|
2671
|
+
parseJitInstance(data) {
|
|
2672
|
+
return {
|
|
2673
|
+
id: data.id,
|
|
2674
|
+
endpointUrl: data.endpoint_url,
|
|
2675
|
+
toolName: data.tool_name,
|
|
2676
|
+
glamaId: data.glama_id,
|
|
2677
|
+
glamaNamespace: data.glama_namespace,
|
|
2678
|
+
glamaSlug: data.glama_slug,
|
|
2679
|
+
status: data.status,
|
|
2680
|
+
mode: data.mode,
|
|
2681
|
+
ttlHours: data.ttl_hours,
|
|
2682
|
+
expiresAt: new Date(data.expires_at),
|
|
2683
|
+
createdAt: new Date(data.created_at)
|
|
2684
|
+
};
|
|
2685
|
+
}
|
|
2686
|
+
// ===========================================================================
|
|
2687
|
+
// LLM Completion Methods
|
|
2688
|
+
// ===========================================================================
|
|
2689
|
+
/**
|
|
2690
|
+
* Simple LLM completion - single-turn, no tools.
|
|
2691
|
+
*
|
|
2692
|
+
* This is a convenience method for agents that just need to call an LLM
|
|
2693
|
+
* without the full agentic loop. Useful when Clawdbot spawns agents
|
|
2694
|
+
* that need to make their own LLM calls through MixrPay.
|
|
2695
|
+
*
|
|
2696
|
+
* @param prompt - The user prompt to send
|
|
2697
|
+
* @param options - Optional configuration
|
|
2698
|
+
* @returns The LLM response text and cost
|
|
2699
|
+
*
|
|
2700
|
+
* @example Basic usage
|
|
2701
|
+
* ```typescript
|
|
2702
|
+
* const result = await wallet.complete('Summarize this text: ...');
|
|
2703
|
+
* console.log(result.text);
|
|
2704
|
+
* console.log(`Cost: $${result.costUsd.toFixed(4)}`);
|
|
2705
|
+
* ```
|
|
2706
|
+
*
|
|
2707
|
+
* @example With custom model
|
|
2708
|
+
* ```typescript
|
|
2709
|
+
* const result = await wallet.complete(
|
|
2710
|
+
* 'Write a haiku about coding',
|
|
2711
|
+
* { model: 'gpt-4o', systemPrompt: 'You are a poet.' }
|
|
2712
|
+
* );
|
|
2713
|
+
* ```
|
|
2714
|
+
*/
|
|
2715
|
+
async complete(prompt, options) {
|
|
2716
|
+
this.logger.debug("complete", { promptLength: prompt.length, model: options?.model });
|
|
2717
|
+
const result = await this.runAgent({
|
|
2718
|
+
messages: [{ role: "user", content: prompt }],
|
|
2719
|
+
config: {
|
|
2720
|
+
model: options?.model || "gpt-4o-mini",
|
|
2721
|
+
maxIterations: 1,
|
|
2722
|
+
tools: [],
|
|
2723
|
+
// No tools - pure LLM completion
|
|
2724
|
+
systemPrompt: options?.systemPrompt
|
|
2725
|
+
}
|
|
2726
|
+
});
|
|
2727
|
+
return {
|
|
2728
|
+
text: result.response,
|
|
2729
|
+
costUsd: result.cost.totalUsd,
|
|
2730
|
+
tokens: result.tokens,
|
|
2731
|
+
model: options?.model || "gpt-4o-mini"
|
|
2732
|
+
};
|
|
2733
|
+
}
|
|
2734
|
+
// ===========================================================================
|
|
2374
2735
|
// Agent Runtime API
|
|
2375
2736
|
// ===========================================================================
|
|
2376
2737
|
/**
|
|
@@ -2435,9 +2796,8 @@ Timestamp: ${timestamp}`;
|
|
|
2435
2796
|
idempotencyKey,
|
|
2436
2797
|
onEvent
|
|
2437
2798
|
} = options;
|
|
2438
|
-
this.logger.debug("runAgent", { sessionId, messageCount: messages.length, config, stream });
|
|
2799
|
+
this.logger.debug("runAgent", { sessionId: sessionId || "(from signature)", messageCount: messages.length, config, stream });
|
|
2439
2800
|
const body = {
|
|
2440
|
-
session_id: sessionId,
|
|
2441
2801
|
messages: messages.map((m) => ({ role: m.role, content: m.content })),
|
|
2442
2802
|
config: {
|
|
2443
2803
|
model: config.model,
|
|
@@ -2448,13 +2808,17 @@ Timestamp: ${timestamp}`;
|
|
|
2448
2808
|
stream,
|
|
2449
2809
|
idempotency_key: idempotencyKey
|
|
2450
2810
|
};
|
|
2811
|
+
if (sessionId) {
|
|
2812
|
+
body.session_id = sessionId;
|
|
2813
|
+
}
|
|
2451
2814
|
const AGENT_RUN_TIMEOUT = 18e4;
|
|
2452
2815
|
if (!stream) {
|
|
2816
|
+
const authHeaders = await this.getSessionAuthHeaders();
|
|
2453
2817
|
const response = await fetch(`${this.baseUrl}/api/v2/agent/run`, {
|
|
2454
2818
|
method: "POST",
|
|
2455
2819
|
headers: {
|
|
2456
2820
|
"Content-Type": "application/json",
|
|
2457
|
-
|
|
2821
|
+
...authHeaders
|
|
2458
2822
|
},
|
|
2459
2823
|
body: JSON.stringify(body),
|
|
2460
2824
|
signal: AbortSignal.timeout(AGENT_RUN_TIMEOUT)
|
|
@@ -2497,18 +2861,19 @@ Timestamp: ${timestamp}`;
|
|
|
2497
2861
|
txHash: data.tx_hash
|
|
2498
2862
|
};
|
|
2499
2863
|
}
|
|
2500
|
-
return this.runAgentStreaming(
|
|
2864
|
+
return this.runAgentStreaming(body, onEvent);
|
|
2501
2865
|
}
|
|
2502
2866
|
/**
|
|
2503
2867
|
* Internal: Handle streaming agent run via SSE
|
|
2504
2868
|
*/
|
|
2505
|
-
async runAgentStreaming(
|
|
2869
|
+
async runAgentStreaming(body, onEvent) {
|
|
2506
2870
|
const STREAMING_TIMEOUT = 3e5;
|
|
2871
|
+
const authHeaders = await this.getSessionAuthHeaders();
|
|
2507
2872
|
const response = await fetch(`${this.baseUrl}/api/v2/agent/run`, {
|
|
2508
2873
|
method: "POST",
|
|
2509
2874
|
headers: {
|
|
2510
2875
|
"Content-Type": "application/json",
|
|
2511
|
-
|
|
2876
|
+
...authHeaders
|
|
2512
2877
|
},
|
|
2513
2878
|
body: JSON.stringify(body),
|
|
2514
2879
|
signal: AbortSignal.timeout(STREAMING_TIMEOUT)
|
|
@@ -2655,12 +3020,11 @@ Timestamp: ${timestamp}`;
|
|
|
2655
3020
|
* }
|
|
2656
3021
|
* ```
|
|
2657
3022
|
*/
|
|
2658
|
-
async getAgentRunStatus(runId,
|
|
2659
|
-
this.logger.debug("getAgentRunStatus", { runId
|
|
3023
|
+
async getAgentRunStatus(runId, _sessionId) {
|
|
3024
|
+
this.logger.debug("getAgentRunStatus", { runId });
|
|
3025
|
+
const authHeaders = await this.getSessionAuthHeaders();
|
|
2660
3026
|
const response = await fetch(`${this.baseUrl}/api/v2/agent/run/${runId}`, {
|
|
2661
|
-
headers:
|
|
2662
|
-
"X-Mixr-Session": sessionId
|
|
2663
|
-
}
|
|
3027
|
+
headers: authHeaders
|
|
2664
3028
|
});
|
|
2665
3029
|
if (!response.ok) {
|
|
2666
3030
|
const error = await response.json().catch(() => ({}));
|
|
@@ -2759,6 +3123,724 @@ Timestamp: ${timestamp}`;
|
|
|
2759
3123
|
latencyMs: mixrpay.latencyMs
|
|
2760
3124
|
};
|
|
2761
3125
|
}
|
|
3126
|
+
// ===========================================================================
|
|
3127
|
+
// Task Board Methods
|
|
3128
|
+
// ===========================================================================
|
|
3129
|
+
/**
|
|
3130
|
+
* Create a new task on the Task Board.
|
|
3131
|
+
*
|
|
3132
|
+
* Users (human or agent) can post tasks with budgets for other agents to complete.
|
|
3133
|
+
* The listing fee is charged when an agent is accepted, not at creation time.
|
|
3134
|
+
*
|
|
3135
|
+
* @param params - Task creation parameters
|
|
3136
|
+
* @returns The created task
|
|
3137
|
+
*
|
|
3138
|
+
* @example
|
|
3139
|
+
* ```typescript
|
|
3140
|
+
* const task = await wallet.createTask({
|
|
3141
|
+
* title: 'Research crypto regulations',
|
|
3142
|
+
* description: 'Research and summarize crypto regulations in the EU...',
|
|
3143
|
+
* budgetUsd: 100,
|
|
3144
|
+
* deliverables: ['PDF report', 'Summary document'],
|
|
3145
|
+
* category: 'research',
|
|
3146
|
+
* });
|
|
3147
|
+
* console.log(`Task created: ${task.id}`);
|
|
3148
|
+
* ```
|
|
3149
|
+
*/
|
|
3150
|
+
async createTask(params) {
|
|
3151
|
+
this.logger.info(`Creating task: ${params.title}`);
|
|
3152
|
+
const response = await this.callApi("/api/v2/tasks", {
|
|
3153
|
+
method: "POST",
|
|
3154
|
+
headers: { "Content-Type": "application/json" },
|
|
3155
|
+
body: JSON.stringify({
|
|
3156
|
+
title: params.title,
|
|
3157
|
+
description: params.description,
|
|
3158
|
+
budget_usd: params.budgetUsd,
|
|
3159
|
+
deliverables: params.deliverables,
|
|
3160
|
+
category: params.category,
|
|
3161
|
+
expires_in_days: params.expiresInDays
|
|
3162
|
+
})
|
|
3163
|
+
});
|
|
3164
|
+
const result = await response.json();
|
|
3165
|
+
if (!response.ok) {
|
|
3166
|
+
throw new MixrPayError(result.error || "Failed to create task");
|
|
3167
|
+
}
|
|
3168
|
+
return this.parseTask(result.task);
|
|
3169
|
+
}
|
|
3170
|
+
/**
|
|
3171
|
+
* List open tasks on the Task Board.
|
|
3172
|
+
*
|
|
3173
|
+
* Browse available tasks that agents can request to work on.
|
|
3174
|
+
*
|
|
3175
|
+
* @param params - Optional filter parameters
|
|
3176
|
+
* @returns List of tasks with pagination
|
|
3177
|
+
*
|
|
3178
|
+
* @example
|
|
3179
|
+
* ```typescript
|
|
3180
|
+
* const { tasks, pagination } = await wallet.listTasks({
|
|
3181
|
+
* minBudget: 50,
|
|
3182
|
+
* category: 'research',
|
|
3183
|
+
* });
|
|
3184
|
+
* console.log(`Found ${pagination.total} matching tasks`);
|
|
3185
|
+
* ```
|
|
3186
|
+
*/
|
|
3187
|
+
async listTasks(params) {
|
|
3188
|
+
const searchParams = new URLSearchParams();
|
|
3189
|
+
if (params?.status) searchParams.set("status", params.status);
|
|
3190
|
+
if (params?.category) searchParams.set("category", params.category);
|
|
3191
|
+
if (params?.minBudget !== void 0) searchParams.set("min_budget", String(params.minBudget));
|
|
3192
|
+
if (params?.maxBudget !== void 0) searchParams.set("max_budget", String(params.maxBudget));
|
|
3193
|
+
if (params?.limit !== void 0) searchParams.set("limit", String(params.limit));
|
|
3194
|
+
if (params?.offset !== void 0) searchParams.set("offset", String(params.offset));
|
|
3195
|
+
const url = `/api/v2/tasks${searchParams.toString() ? `?${searchParams}` : ""}`;
|
|
3196
|
+
const response = await this.callApi(url, { method: "GET" });
|
|
3197
|
+
const result = await response.json();
|
|
3198
|
+
if (!response.ok) {
|
|
3199
|
+
throw new MixrPayError(result.error || "Failed to list tasks");
|
|
3200
|
+
}
|
|
3201
|
+
return {
|
|
3202
|
+
tasks: result.tasks.map((t) => this.parseTask(t)),
|
|
3203
|
+
pagination: result.pagination
|
|
3204
|
+
};
|
|
3205
|
+
}
|
|
3206
|
+
/**
|
|
3207
|
+
* Get details for a specific task.
|
|
3208
|
+
*
|
|
3209
|
+
* @param taskId - The task ID
|
|
3210
|
+
* @returns Task details
|
|
3211
|
+
*/
|
|
3212
|
+
async getTask(taskId) {
|
|
3213
|
+
const response = await this.callApi(`/api/v2/tasks/${taskId}`, { method: "GET" });
|
|
3214
|
+
const result = await response.json();
|
|
3215
|
+
if (!response.ok) {
|
|
3216
|
+
throw new MixrPayError(result.error || "Failed to get task");
|
|
3217
|
+
}
|
|
3218
|
+
return this.parseTask(result.task);
|
|
3219
|
+
}
|
|
3220
|
+
/**
|
|
3221
|
+
* Get tasks created by or assigned to the authenticated user.
|
|
3222
|
+
*
|
|
3223
|
+
* @param options - Filter and pagination options
|
|
3224
|
+
* @returns List of tasks with pagination info
|
|
3225
|
+
*
|
|
3226
|
+
* @example
|
|
3227
|
+
* ```typescript
|
|
3228
|
+
* // Get all my tasks
|
|
3229
|
+
* const { tasks, pagination } = await wallet.getMyTasks();
|
|
3230
|
+
*
|
|
3231
|
+
* // Get only tasks I created
|
|
3232
|
+
* const created = await wallet.getMyTasks({ role: 'creator' });
|
|
3233
|
+
*
|
|
3234
|
+
* // Get only tasks assigned to me
|
|
3235
|
+
* const assigned = await wallet.getMyTasks({ role: 'agent' });
|
|
3236
|
+
*
|
|
3237
|
+
* // Filter by status
|
|
3238
|
+
* const pending = await wallet.getMyTasks({ status: 'submitted' });
|
|
3239
|
+
* ```
|
|
3240
|
+
*/
|
|
3241
|
+
async getMyTasks(options = {}) {
|
|
3242
|
+
const params = new URLSearchParams();
|
|
3243
|
+
if (options.role) params.set("role", options.role);
|
|
3244
|
+
if (options.status) params.set("status", options.status);
|
|
3245
|
+
if (options.page) params.set("page", options.page.toString());
|
|
3246
|
+
if (options.limit) params.set("limit", options.limit.toString());
|
|
3247
|
+
const queryString = params.toString();
|
|
3248
|
+
const url = `/api/v2/tasks/my${queryString ? `?${queryString}` : ""}`;
|
|
3249
|
+
const response = await this.callApi(url, { method: "GET" });
|
|
3250
|
+
const result = await response.json();
|
|
3251
|
+
if (!response.ok) {
|
|
3252
|
+
throw new MixrPayError(result.error || "Failed to get tasks");
|
|
3253
|
+
}
|
|
3254
|
+
return {
|
|
3255
|
+
tasks: result.tasks.map((t) => ({
|
|
3256
|
+
...this.parseTask(t),
|
|
3257
|
+
isCreator: t.is_creator,
|
|
3258
|
+
isAgent: t.is_agent
|
|
3259
|
+
})),
|
|
3260
|
+
pagination: {
|
|
3261
|
+
page: result.pagination.page,
|
|
3262
|
+
limit: result.pagination.limit,
|
|
3263
|
+
total: result.pagination.total,
|
|
3264
|
+
totalPages: result.pagination.total_pages
|
|
3265
|
+
}
|
|
3266
|
+
};
|
|
3267
|
+
}
|
|
3268
|
+
/**
|
|
3269
|
+
* Request to claim a task.
|
|
3270
|
+
*
|
|
3271
|
+
* Agents use this to express interest in completing a task.
|
|
3272
|
+
* The task creator will review requests and accept one agent.
|
|
3273
|
+
*
|
|
3274
|
+
* @param taskId - The task ID to request
|
|
3275
|
+
* @param message - Optional pitch/message to the task creator
|
|
3276
|
+
* @returns The created request
|
|
3277
|
+
*
|
|
3278
|
+
* @example
|
|
3279
|
+
* ```typescript
|
|
3280
|
+
* const request = await wallet.requestTask('task_123',
|
|
3281
|
+
* 'I have experience with crypto research and can deliver within 24 hours.'
|
|
3282
|
+
* );
|
|
3283
|
+
* console.log(`Request submitted: ${request.id}`);
|
|
3284
|
+
* ```
|
|
3285
|
+
*/
|
|
3286
|
+
async requestTask(taskId, message) {
|
|
3287
|
+
this.logger.info(`Requesting task: ${taskId}`);
|
|
3288
|
+
const response = await this.callApi(`/api/v2/tasks/${taskId}/request`, {
|
|
3289
|
+
method: "POST",
|
|
3290
|
+
headers: { "Content-Type": "application/json" },
|
|
3291
|
+
body: message ? JSON.stringify({ message }) : "{}"
|
|
3292
|
+
});
|
|
3293
|
+
const result = await response.json();
|
|
3294
|
+
if (!response.ok) {
|
|
3295
|
+
throw new MixrPayError(result.error || "Failed to request task");
|
|
3296
|
+
}
|
|
3297
|
+
return {
|
|
3298
|
+
id: result.request.id,
|
|
3299
|
+
taskId: result.request.task_id,
|
|
3300
|
+
status: result.request.status,
|
|
3301
|
+
message: result.request.message,
|
|
3302
|
+
createdAt: new Date(result.request.created_at)
|
|
3303
|
+
};
|
|
3304
|
+
}
|
|
3305
|
+
/**
|
|
3306
|
+
* Get requests for a task you created.
|
|
3307
|
+
*
|
|
3308
|
+
* @param taskId - The task ID
|
|
3309
|
+
* @returns List of agent requests
|
|
3310
|
+
*/
|
|
3311
|
+
async getTaskRequests(taskId) {
|
|
3312
|
+
const response = await this.callApi(`/api/v2/tasks/${taskId}/requests`, { method: "GET" });
|
|
3313
|
+
const result = await response.json();
|
|
3314
|
+
if (!response.ok) {
|
|
3315
|
+
throw new MixrPayError(result.error || "Failed to get task requests");
|
|
3316
|
+
}
|
|
3317
|
+
return result.requests.map((r) => ({
|
|
3318
|
+
id: r.id,
|
|
3319
|
+
status: r.status,
|
|
3320
|
+
message: r.message,
|
|
3321
|
+
createdAt: new Date(r.created_at),
|
|
3322
|
+
reviewedAt: r.reviewed_at ? new Date(r.reviewed_at) : void 0,
|
|
3323
|
+
agent: r.agent
|
|
3324
|
+
}));
|
|
3325
|
+
}
|
|
3326
|
+
/**
|
|
3327
|
+
* Accept an agent's request to work on your task.
|
|
3328
|
+
*
|
|
3329
|
+
* This will:
|
|
3330
|
+
* - Charge the listing fee from your wallet
|
|
3331
|
+
* - Create a session for the agent with your task budget
|
|
3332
|
+
* - Mark the task as claimed
|
|
3333
|
+
* - Reject all other pending requests
|
|
3334
|
+
*
|
|
3335
|
+
* @param taskId - The task ID
|
|
3336
|
+
* @param requestId - The request ID to accept
|
|
3337
|
+
* @returns Acceptance result with session info
|
|
3338
|
+
*
|
|
3339
|
+
* @example
|
|
3340
|
+
* ```typescript
|
|
3341
|
+
* const result = await wallet.acceptTaskRequest('task_123', 'req_456');
|
|
3342
|
+
* console.log(`Agent accepted! Session budget: $${result.session.budgetUsd}`);
|
|
3343
|
+
* ```
|
|
3344
|
+
*/
|
|
3345
|
+
async acceptTaskRequest(taskId, requestId) {
|
|
3346
|
+
this.logger.info(`Accepting request ${requestId} for task ${taskId}`);
|
|
3347
|
+
const response = await this.callApi(`/api/v2/tasks/${taskId}/requests/${requestId}/accept`, {
|
|
3348
|
+
method: "POST"
|
|
3349
|
+
});
|
|
3350
|
+
const result = await response.json();
|
|
3351
|
+
if (!response.ok) {
|
|
3352
|
+
throw new MixrPayError(result.error || "Failed to accept request");
|
|
3353
|
+
}
|
|
3354
|
+
return {
|
|
3355
|
+
success: result.success,
|
|
3356
|
+
task: {
|
|
3357
|
+
id: result.task.id,
|
|
3358
|
+
status: result.task.status,
|
|
3359
|
+
agentUserId: result.task.agent_user_id
|
|
3360
|
+
},
|
|
3361
|
+
session: {
|
|
3362
|
+
id: result.session.id,
|
|
3363
|
+
sessionKey: result.session.session_key,
|
|
3364
|
+
// Agent needs this to authenticate API calls
|
|
3365
|
+
address: result.session.address,
|
|
3366
|
+
expiresAt: new Date(result.session.expires_at),
|
|
3367
|
+
budgetUsd: result.session.budget_usd,
|
|
3368
|
+
allowedMerchants: result.session.allowed_merchants || []
|
|
3369
|
+
},
|
|
3370
|
+
invite: {
|
|
3371
|
+
id: result.invite.id,
|
|
3372
|
+
code: result.invite.code
|
|
3373
|
+
},
|
|
3374
|
+
listingFeeTxHash: result.listing_fee_tx_hash
|
|
3375
|
+
};
|
|
3376
|
+
}
|
|
3377
|
+
/**
|
|
3378
|
+
* Reject an agent's request.
|
|
3379
|
+
*
|
|
3380
|
+
* @param taskId - The task ID
|
|
3381
|
+
* @param requestId - The request ID to reject
|
|
3382
|
+
*/
|
|
3383
|
+
async rejectTaskRequest(taskId, requestId) {
|
|
3384
|
+
const response = await this.callApi(`/api/v2/tasks/${taskId}/requests/${requestId}/reject`, {
|
|
3385
|
+
method: "POST"
|
|
3386
|
+
});
|
|
3387
|
+
const result = await response.json();
|
|
3388
|
+
if (!response.ok) {
|
|
3389
|
+
throw new MixrPayError(result.error || "Failed to reject request");
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
/**
|
|
3393
|
+
* Submit deliverables for a task you're working on.
|
|
3394
|
+
*
|
|
3395
|
+
* After being accepted to work on a task, use this to submit your work
|
|
3396
|
+
* for the task creator's review.
|
|
3397
|
+
*
|
|
3398
|
+
* @param taskId - The task ID
|
|
3399
|
+
* @param output - The deliverables (text and/or URL)
|
|
3400
|
+
*
|
|
3401
|
+
* @example
|
|
3402
|
+
* ```typescript
|
|
3403
|
+
* await wallet.submitTask('task_123', {
|
|
3404
|
+
* text: 'Here is my research report...',
|
|
3405
|
+
* url: 'https://docs.google.com/document/d/...',
|
|
3406
|
+
* });
|
|
3407
|
+
* ```
|
|
3408
|
+
*/
|
|
3409
|
+
async submitTask(taskId, output) {
|
|
3410
|
+
this.logger.info(`Submitting task: ${taskId}`);
|
|
3411
|
+
const response = await this.callApi(`/api/v2/tasks/${taskId}/submit`, {
|
|
3412
|
+
method: "POST",
|
|
3413
|
+
headers: { "Content-Type": "application/json" },
|
|
3414
|
+
body: JSON.stringify({
|
|
3415
|
+
output_text: output.text,
|
|
3416
|
+
output_url: output.url
|
|
3417
|
+
})
|
|
3418
|
+
});
|
|
3419
|
+
const result = await response.json();
|
|
3420
|
+
if (!response.ok) {
|
|
3421
|
+
throw new MixrPayError(result.error || "Failed to submit task");
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
/**
|
|
3425
|
+
* Approve a submitted task and pay the agent.
|
|
3426
|
+
*
|
|
3427
|
+
* This will:
|
|
3428
|
+
* - Calculate remaining budget (budget - spent on tools)
|
|
3429
|
+
* - Transfer remaining budget to the agent's wallet
|
|
3430
|
+
* - Mark the task as completed
|
|
3431
|
+
* - Revoke the agent's session
|
|
3432
|
+
*
|
|
3433
|
+
* @param taskId - The task ID
|
|
3434
|
+
* @returns Payout details
|
|
3435
|
+
*
|
|
3436
|
+
* @example
|
|
3437
|
+
* ```typescript
|
|
3438
|
+
* const result = await wallet.approveTask('task_123');
|
|
3439
|
+
* console.log(`Paid agent $${result.payout.amountUsd}`);
|
|
3440
|
+
* ```
|
|
3441
|
+
*/
|
|
3442
|
+
async approveTask(taskId) {
|
|
3443
|
+
this.logger.info(`Approving task: ${taskId}`);
|
|
3444
|
+
const response = await this.callApi(`/api/v2/tasks/${taskId}/approve`, {
|
|
3445
|
+
method: "POST"
|
|
3446
|
+
});
|
|
3447
|
+
const result = await response.json();
|
|
3448
|
+
if (!response.ok) {
|
|
3449
|
+
throw new MixrPayError(result.error || "Failed to approve task");
|
|
3450
|
+
}
|
|
3451
|
+
return {
|
|
3452
|
+
success: result.success,
|
|
3453
|
+
task: {
|
|
3454
|
+
id: result.task.id,
|
|
3455
|
+
status: result.task.status,
|
|
3456
|
+
completedAt: new Date(result.task.completed_at)
|
|
3457
|
+
},
|
|
3458
|
+
payout: {
|
|
3459
|
+
status: result.payout.status,
|
|
3460
|
+
amountUsd: result.payout.amount_usd,
|
|
3461
|
+
txHash: result.payout.tx_hash
|
|
3462
|
+
}
|
|
3463
|
+
};
|
|
3464
|
+
}
|
|
3465
|
+
/**
|
|
3466
|
+
* Reject a task submission and send it back for revision.
|
|
3467
|
+
*
|
|
3468
|
+
* @param taskId - The task ID
|
|
3469
|
+
* @param feedback - Optional feedback for the agent
|
|
3470
|
+
*/
|
|
3471
|
+
async rejectTaskSubmission(taskId, feedback) {
|
|
3472
|
+
const response = await this.callApi(`/api/v2/tasks/${taskId}/reject`, {
|
|
3473
|
+
method: "POST",
|
|
3474
|
+
headers: { "Content-Type": "application/json" },
|
|
3475
|
+
body: feedback ? JSON.stringify({ feedback }) : "{}"
|
|
3476
|
+
});
|
|
3477
|
+
const result = await response.json();
|
|
3478
|
+
if (!response.ok) {
|
|
3479
|
+
throw new MixrPayError(result.error || "Failed to reject task");
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
3482
|
+
/**
|
|
3483
|
+
* Cancel a task you created.
|
|
3484
|
+
*
|
|
3485
|
+
* Can only cancel tasks in 'open' or 'claimed' status.
|
|
3486
|
+
* If the task was claimed, the agent's session will be revoked.
|
|
3487
|
+
*
|
|
3488
|
+
* @param taskId - The task ID
|
|
3489
|
+
*/
|
|
3490
|
+
async cancelTask(taskId) {
|
|
3491
|
+
const response = await this.callApi(`/api/v2/tasks/${taskId}`, {
|
|
3492
|
+
method: "DELETE"
|
|
3493
|
+
});
|
|
3494
|
+
const result = await response.json();
|
|
3495
|
+
if (!response.ok) {
|
|
3496
|
+
throw new MixrPayError(result.error || "Failed to cancel task");
|
|
3497
|
+
}
|
|
3498
|
+
}
|
|
3499
|
+
// ===========================================================================
|
|
3500
|
+
// Mission Control: Subtask Management
|
|
3501
|
+
// ===========================================================================
|
|
3502
|
+
/**
|
|
3503
|
+
* Create a subtask under an existing task.
|
|
3504
|
+
* Requires session key authentication.
|
|
3505
|
+
* Budget is allocated from the parent task's remaining budget.
|
|
3506
|
+
*
|
|
3507
|
+
* @param options - Subtask creation options
|
|
3508
|
+
* @returns The created subtask
|
|
3509
|
+
*
|
|
3510
|
+
* @example
|
|
3511
|
+
* ```typescript
|
|
3512
|
+
* const subtask = await wallet.createSubtask({
|
|
3513
|
+
* parentTaskId: 'task_123',
|
|
3514
|
+
* title: 'Research competitor pricing',
|
|
3515
|
+
* description: 'Find pricing info for top 5 competitors',
|
|
3516
|
+
* budgetUsd: 10,
|
|
3517
|
+
* });
|
|
3518
|
+
* ```
|
|
3519
|
+
*/
|
|
3520
|
+
async createSubtask(options) {
|
|
3521
|
+
this.logger.info(`Creating subtask: ${options.title}`);
|
|
3522
|
+
const response = await this.callApi("/api/v2/tasks/create-subtask", {
|
|
3523
|
+
method: "POST",
|
|
3524
|
+
headers: { "Content-Type": "application/json" },
|
|
3525
|
+
body: JSON.stringify({
|
|
3526
|
+
parent_task_id: options.parentTaskId,
|
|
3527
|
+
title: options.title,
|
|
3528
|
+
description: options.description,
|
|
3529
|
+
deliverables: options.deliverables,
|
|
3530
|
+
category: options.category,
|
|
3531
|
+
budget_usd: options.budgetUsd,
|
|
3532
|
+
allow_sub_agents: options.allowSubAgents,
|
|
3533
|
+
expires_in_days: options.expiresInDays,
|
|
3534
|
+
idempotency_key: options.idempotencyKey
|
|
3535
|
+
})
|
|
3536
|
+
});
|
|
3537
|
+
const result = await response.json();
|
|
3538
|
+
if (!response.ok) {
|
|
3539
|
+
throw new MixrPayError(result.error || "Failed to create subtask");
|
|
3540
|
+
}
|
|
3541
|
+
return this.parseTask(result.task);
|
|
3542
|
+
}
|
|
3543
|
+
/**
|
|
3544
|
+
* Update the status of a task.
|
|
3545
|
+
* Requires session key authentication.
|
|
3546
|
+
* Creates an audit trail via TaskStatusUpdate records.
|
|
3547
|
+
*
|
|
3548
|
+
* @param taskId - The task ID
|
|
3549
|
+
* @param status - New status
|
|
3550
|
+
* @param note - Optional note explaining the status change
|
|
3551
|
+
* @returns The updated task
|
|
3552
|
+
*
|
|
3553
|
+
* @example
|
|
3554
|
+
* ```typescript
|
|
3555
|
+
* await wallet.updateTaskStatus('task_123', 'submitted', 'All deliverables completed');
|
|
3556
|
+
* ```
|
|
3557
|
+
*/
|
|
3558
|
+
async updateTaskStatus(taskId, status, note, idempotencyKey) {
|
|
3559
|
+
this.logger.info(`Updating task ${taskId} status to ${status}`);
|
|
3560
|
+
const response = await this.callApi(`/api/v2/tasks/${taskId}/status`, {
|
|
3561
|
+
method: "PATCH",
|
|
3562
|
+
headers: { "Content-Type": "application/json" },
|
|
3563
|
+
body: JSON.stringify({
|
|
3564
|
+
status,
|
|
3565
|
+
note,
|
|
3566
|
+
idempotency_key: idempotencyKey
|
|
3567
|
+
})
|
|
3568
|
+
});
|
|
3569
|
+
const result = await response.json();
|
|
3570
|
+
if (!response.ok) {
|
|
3571
|
+
throw new MixrPayError(result.error || "Failed to update task status");
|
|
3572
|
+
}
|
|
3573
|
+
return this.parseTask(result.task);
|
|
3574
|
+
}
|
|
3575
|
+
/**
|
|
3576
|
+
* Get subtasks of a task.
|
|
3577
|
+
* Requires session key authentication.
|
|
3578
|
+
*
|
|
3579
|
+
* @param taskId - The parent task ID
|
|
3580
|
+
* @returns List of subtasks
|
|
3581
|
+
*
|
|
3582
|
+
* @example
|
|
3583
|
+
* ```typescript
|
|
3584
|
+
* const subtasks = await wallet.getSubtasks('task_123');
|
|
3585
|
+
* console.log(`Found ${subtasks.length} subtasks`);
|
|
3586
|
+
* ```
|
|
3587
|
+
*/
|
|
3588
|
+
async getSubtasks(taskId) {
|
|
3589
|
+
const response = await this.callApi(`/api/v2/tasks/${taskId}/subtasks`, {
|
|
3590
|
+
method: "GET"
|
|
3591
|
+
});
|
|
3592
|
+
const result = await response.json();
|
|
3593
|
+
if (!response.ok) {
|
|
3594
|
+
throw new MixrPayError(result.error || "Failed to get subtasks");
|
|
3595
|
+
}
|
|
3596
|
+
return (result.subtasks || []).map((t) => this.parseTask(t));
|
|
3597
|
+
}
|
|
3598
|
+
/**
|
|
3599
|
+
* Get the status history of a task.
|
|
3600
|
+
* Requires session key authentication.
|
|
3601
|
+
*
|
|
3602
|
+
* @param taskId - The task ID
|
|
3603
|
+
* @returns Status history with audit trail
|
|
3604
|
+
*
|
|
3605
|
+
* @example
|
|
3606
|
+
* ```typescript
|
|
3607
|
+
* const history = await wallet.getTaskStatusHistory('task_123');
|
|
3608
|
+
* for (const entry of history.history) {
|
|
3609
|
+
* console.log(`${entry.oldStatus} -> ${entry.newStatus}: ${entry.note}`);
|
|
3610
|
+
* }
|
|
3611
|
+
* ```
|
|
3612
|
+
*/
|
|
3613
|
+
async getTaskStatusHistory(taskId) {
|
|
3614
|
+
const response = await this.callApi(`/api/v2/tasks/${taskId}/status`, {
|
|
3615
|
+
method: "GET"
|
|
3616
|
+
});
|
|
3617
|
+
const result = await response.json();
|
|
3618
|
+
if (!response.ok) {
|
|
3619
|
+
throw new MixrPayError(result.error || "Failed to get task status history");
|
|
3620
|
+
}
|
|
3621
|
+
return {
|
|
3622
|
+
taskId: result.task_id,
|
|
3623
|
+
currentStatus: result.current_status,
|
|
3624
|
+
history: (result.history || []).map((h) => ({
|
|
3625
|
+
id: h.id,
|
|
3626
|
+
oldStatus: h.old_status,
|
|
3627
|
+
newStatus: h.new_status,
|
|
3628
|
+
note: h.note,
|
|
3629
|
+
createdAt: new Date(h.created_at)
|
|
3630
|
+
}))
|
|
3631
|
+
};
|
|
3632
|
+
}
|
|
3633
|
+
// ===========================================================================
|
|
3634
|
+
// Mission Control: Approval System
|
|
3635
|
+
// ===========================================================================
|
|
3636
|
+
/**
|
|
3637
|
+
* Request approval from a human user for an action.
|
|
3638
|
+
* Returns immediately if the action is auto-approved.
|
|
3639
|
+
*
|
|
3640
|
+
* @param options - Approval request options
|
|
3641
|
+
* @returns Approval request or auto-approval result
|
|
3642
|
+
*
|
|
3643
|
+
* @example
|
|
3644
|
+
* ```typescript
|
|
3645
|
+
* const result = await wallet.requestApproval({
|
|
3646
|
+
* actionType: 'external_communication',
|
|
3647
|
+
* actionPayload: { recipient: 'user@example.com', message: 'Hello!' },
|
|
3648
|
+
* context: 'Sending welcome email to new customer',
|
|
3649
|
+
* });
|
|
3650
|
+
*
|
|
3651
|
+
* if ('autoApproved' in result) {
|
|
3652
|
+
* console.log('Auto-approved:', result.reason);
|
|
3653
|
+
* } else {
|
|
3654
|
+
* console.log('Waiting for approval:', result.id);
|
|
3655
|
+
* }
|
|
3656
|
+
* ```
|
|
3657
|
+
*/
|
|
3658
|
+
async requestApproval(options) {
|
|
3659
|
+
this.logger.info(`Requesting approval for: ${options.actionType}`);
|
|
3660
|
+
const response = await this.callApi("/api/v2/approvals/request", {
|
|
3661
|
+
method: "POST",
|
|
3662
|
+
headers: { "Content-Type": "application/json" },
|
|
3663
|
+
body: JSON.stringify({
|
|
3664
|
+
action_type: options.actionType,
|
|
3665
|
+
action_payload: options.actionPayload,
|
|
3666
|
+
context: options.context,
|
|
3667
|
+
expires_in_hours: options.expiresInHours,
|
|
3668
|
+
idempotency_key: options.idempotencyKey
|
|
3669
|
+
})
|
|
3670
|
+
});
|
|
3671
|
+
const result = await response.json();
|
|
3672
|
+
if (!response.ok) {
|
|
3673
|
+
throw new MixrPayError(result.error || "Failed to request approval");
|
|
3674
|
+
}
|
|
3675
|
+
if (result.auto_approved) {
|
|
3676
|
+
return {
|
|
3677
|
+
autoApproved: true,
|
|
3678
|
+
reason: result.reason
|
|
3679
|
+
};
|
|
3680
|
+
}
|
|
3681
|
+
return this.parseApprovalRequest(result.approval_request);
|
|
3682
|
+
}
|
|
3683
|
+
/**
|
|
3684
|
+
* Check the status of an approval request.
|
|
3685
|
+
*
|
|
3686
|
+
* @param requestId - The approval request ID
|
|
3687
|
+
* @returns Current status of the request
|
|
3688
|
+
*
|
|
3689
|
+
* @example
|
|
3690
|
+
* ```typescript
|
|
3691
|
+
* const status = await wallet.checkApprovalStatus('req_123');
|
|
3692
|
+
* if (status.status === 'approved') {
|
|
3693
|
+
* console.log('Approved! Proceeding with action...');
|
|
3694
|
+
* }
|
|
3695
|
+
* ```
|
|
3696
|
+
*/
|
|
3697
|
+
async checkApprovalStatus(requestId) {
|
|
3698
|
+
const response = await this.callApi(`/api/v2/approvals/check?request_id=${requestId}`, {
|
|
3699
|
+
method: "GET"
|
|
3700
|
+
});
|
|
3701
|
+
const result = await response.json();
|
|
3702
|
+
if (!response.ok) {
|
|
3703
|
+
throw new MixrPayError(result.error || "Failed to check approval status");
|
|
3704
|
+
}
|
|
3705
|
+
return {
|
|
3706
|
+
requestId: result.request_id,
|
|
3707
|
+
status: result.status,
|
|
3708
|
+
responseNote: result.response_note,
|
|
3709
|
+
approvalScope: result.approval_scope,
|
|
3710
|
+
respondedAt: result.responded_at ? new Date(result.responded_at) : void 0
|
|
3711
|
+
};
|
|
3712
|
+
}
|
|
3713
|
+
/**
|
|
3714
|
+
* Check if an action type needs approval before execution.
|
|
3715
|
+
*
|
|
3716
|
+
* @param actionType - The type of action to check
|
|
3717
|
+
* @param amountUsd - Optional amount for spend actions
|
|
3718
|
+
* @returns Whether approval is needed
|
|
3719
|
+
*
|
|
3720
|
+
* @example
|
|
3721
|
+
* ```typescript
|
|
3722
|
+
* const check = await wallet.checkActionPermission('external_communication');
|
|
3723
|
+
* if (check.needsApproval) {
|
|
3724
|
+
* const approval = await wallet.requestApproval({ ... });
|
|
3725
|
+
* }
|
|
3726
|
+
* ```
|
|
3727
|
+
*/
|
|
3728
|
+
async checkActionPermission(actionType, amountUsd) {
|
|
3729
|
+
let url = `/api/v2/approvals/check?action_type=${encodeURIComponent(actionType)}`;
|
|
3730
|
+
if (amountUsd !== void 0) {
|
|
3731
|
+
url += `&amount_usd=${amountUsd}`;
|
|
3732
|
+
}
|
|
3733
|
+
const response = await this.callApi(url, { method: "GET" });
|
|
3734
|
+
const result = await response.json();
|
|
3735
|
+
if (!response.ok) {
|
|
3736
|
+
throw new MixrPayError(result.error || "Failed to check action permission");
|
|
3737
|
+
}
|
|
3738
|
+
return {
|
|
3739
|
+
actionType: result.action_type,
|
|
3740
|
+
needsApproval: result.needs_approval,
|
|
3741
|
+
reason: result.reason
|
|
3742
|
+
};
|
|
3743
|
+
}
|
|
3744
|
+
/**
|
|
3745
|
+
* Wait for an approval request to be resolved.
|
|
3746
|
+
* Polls the status until approved, rejected, or expired.
|
|
3747
|
+
*
|
|
3748
|
+
* @param requestId - The approval request ID
|
|
3749
|
+
* @param options - Polling options
|
|
3750
|
+
* @returns Final status of the request
|
|
3751
|
+
*
|
|
3752
|
+
* @example
|
|
3753
|
+
* ```typescript
|
|
3754
|
+
* const approval = await wallet.requestApproval({ ... });
|
|
3755
|
+
* if (!('autoApproved' in approval)) {
|
|
3756
|
+
* const result = await wallet.waitForApproval(approval.id, { timeoutMs: 60000 });
|
|
3757
|
+
* if (result.status === 'approved') {
|
|
3758
|
+
* // Proceed with action
|
|
3759
|
+
* }
|
|
3760
|
+
* }
|
|
3761
|
+
* ```
|
|
3762
|
+
*/
|
|
3763
|
+
async waitForApproval(requestId, options = {}) {
|
|
3764
|
+
const pollInterval = options.pollIntervalMs || 5e3;
|
|
3765
|
+
const timeout = options.timeoutMs || 3e5;
|
|
3766
|
+
const startTime = Date.now();
|
|
3767
|
+
while (Date.now() - startTime < timeout) {
|
|
3768
|
+
const status = await this.checkApprovalStatus(requestId);
|
|
3769
|
+
if (status.status !== "pending") {
|
|
3770
|
+
return status;
|
|
3771
|
+
}
|
|
3772
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
3773
|
+
}
|
|
3774
|
+
return {
|
|
3775
|
+
requestId,
|
|
3776
|
+
status: "expired"
|
|
3777
|
+
};
|
|
3778
|
+
}
|
|
3779
|
+
/** Helper to parse approval request from API response */
|
|
3780
|
+
parseApprovalRequest(r) {
|
|
3781
|
+
return {
|
|
3782
|
+
id: r.id,
|
|
3783
|
+
actionType: r.action_type,
|
|
3784
|
+
actionPayload: r.action_payload,
|
|
3785
|
+
context: r.context,
|
|
3786
|
+
status: r.status,
|
|
3787
|
+
responseNote: r.response_note,
|
|
3788
|
+
approvalScope: r.approval_scope,
|
|
3789
|
+
createdAt: new Date(r.created_at),
|
|
3790
|
+
expiresAt: new Date(r.expires_at),
|
|
3791
|
+
respondedAt: r.responded_at ? new Date(r.responded_at) : void 0
|
|
3792
|
+
};
|
|
3793
|
+
}
|
|
3794
|
+
/** Helper to parse task from API response */
|
|
3795
|
+
parseTask(t) {
|
|
3796
|
+
return {
|
|
3797
|
+
id: t.id,
|
|
3798
|
+
title: t.title,
|
|
3799
|
+
description: t.description,
|
|
3800
|
+
deliverables: t.deliverables || [],
|
|
3801
|
+
category: t.category,
|
|
3802
|
+
budgetUsd: t.budget_usd,
|
|
3803
|
+
listingFeeUsd: t.listing_fee_usd,
|
|
3804
|
+
status: t.status,
|
|
3805
|
+
createdAt: new Date(t.created_at),
|
|
3806
|
+
updatedAt: t.updated_at ? new Date(t.updated_at) : void 0,
|
|
3807
|
+
expiresAt: t.expires_at ? new Date(t.expires_at) : void 0,
|
|
3808
|
+
claimedAt: t.claimed_at ? new Date(t.claimed_at) : void 0,
|
|
3809
|
+
submittedAt: t.submitted_at ? new Date(t.submitted_at) : void 0,
|
|
3810
|
+
completedAt: t.completed_at ? new Date(t.completed_at) : void 0,
|
|
3811
|
+
creator: t.creator,
|
|
3812
|
+
assignedAgent: t.assigned_agent,
|
|
3813
|
+
requestCount: t.request_count,
|
|
3814
|
+
output: t.output,
|
|
3815
|
+
payment: t.payment
|
|
3816
|
+
};
|
|
3817
|
+
}
|
|
3818
|
+
/** Helper to call our API with auth */
|
|
3819
|
+
async callApi(path, init = {}) {
|
|
3820
|
+
const url = `${this.baseUrl}${path}`;
|
|
3821
|
+
const headers = {
|
|
3822
|
+
...init.headers
|
|
3823
|
+
};
|
|
3824
|
+
if (this.sessionKey) {
|
|
3825
|
+
const account = (0, import_accounts2.privateKeyToAccount)(this.sessionKey.rawPrivateKey);
|
|
3826
|
+
const address = account.address.toLowerCase();
|
|
3827
|
+
const timestamp = Date.now();
|
|
3828
|
+
const message = `MixrPay:${timestamp}:${address}`;
|
|
3829
|
+
const signature = await (0, import_accounts2.signMessage)({
|
|
3830
|
+
message,
|
|
3831
|
+
privateKey: this.sessionKey.rawPrivateKey
|
|
3832
|
+
});
|
|
3833
|
+
headers["X-Session-Auth"] = JSON.stringify({
|
|
3834
|
+
address,
|
|
3835
|
+
timestamp,
|
|
3836
|
+
signature
|
|
3837
|
+
});
|
|
3838
|
+
}
|
|
3839
|
+
return fetch(url, {
|
|
3840
|
+
...init,
|
|
3841
|
+
headers
|
|
3842
|
+
});
|
|
3843
|
+
}
|
|
2762
3844
|
};
|
|
2763
3845
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2764
3846
|
0 && (module.exports = {
|