@oneshot-agent/sdk 0.29.0 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -2
- package/dist/deadline.d.ts +9 -0
- package/dist/deadline.d.ts.map +1 -0
- package/dist/deadline.js +39 -0
- package/dist/deadline.js.map +1 -0
- package/dist/errors.d.ts +10 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +15 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +41 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +270 -129
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +186 -3
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -37,9 +37,11 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
37
37
|
})();
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.OneShot = exports.executeSwap = exports.getSwapQuote = exports.CdpWalletProvider = exports.EthersWalletProvider = void 0;
|
|
40
|
+
const deadline_1 = require("./deadline");
|
|
41
|
+
const errors_1 = require("./errors");
|
|
40
42
|
const ethers_1 = require("ethers");
|
|
41
43
|
const ethers_2 = require("./providers/ethers");
|
|
42
|
-
const
|
|
44
|
+
const errors_2 = require("./errors");
|
|
43
45
|
var ethers_3 = require("./providers/ethers");
|
|
44
46
|
Object.defineProperty(exports, "EthersWalletProvider", { enumerable: true, get: function () { return ethers_3.EthersWalletProvider; } });
|
|
45
47
|
var cdp_1 = require("./providers/cdp");
|
|
@@ -49,7 +51,7 @@ Object.defineProperty(exports, "getSwapQuote", { enumerable: true, get: function
|
|
|
49
51
|
Object.defineProperty(exports, "executeSwap", { enumerable: true, get: function () { return swap_1.executeSwap; } });
|
|
50
52
|
__exportStar(require("./errors"), exports);
|
|
51
53
|
// Keep in sync with package.json `version`. Guarded by version.test.ts.
|
|
52
|
-
const SDK_VERSION = '0.
|
|
54
|
+
const SDK_VERSION = '0.31.0';
|
|
53
55
|
/** HTTP poll cadence while push is unconfirmed: fast first checks, settling at 2s. */
|
|
54
56
|
const HTTP_POLL_BACKOFF_MS = [300, 600, 1000, 2000];
|
|
55
57
|
/** HTTP poll cadence once the WebSocket has delivered for this request. */
|
|
@@ -78,12 +80,14 @@ const USDC_BALANCE_CACHE_MS = 2000;
|
|
|
78
80
|
const USDC_RESERVATION_TTL_MS = 90000;
|
|
79
81
|
const DEFAULT_SWAP_BUFFER_MULTIPLIER = 10;
|
|
80
82
|
const MAX_SWAP_BUFFER_MULTIPLIER = 1000;
|
|
83
|
+
/** Fixed-point scale for the multiplier (6 decimals, matching USDC). */
|
|
84
|
+
const SWAP_MULTIPLIER_SCALE = 1000000;
|
|
81
85
|
const ERC20_BALANCE_ABI = ['function balanceOf(address) view returns (uint256)'];
|
|
82
86
|
function validateSwapBufferMultiplier(m) {
|
|
83
87
|
if (m === undefined)
|
|
84
88
|
return DEFAULT_SWAP_BUFFER_MULTIPLIER;
|
|
85
89
|
if (typeof m !== 'number' || !Number.isFinite(m) || m < 1 || m > MAX_SWAP_BUFFER_MULTIPLIER) {
|
|
86
|
-
throw new
|
|
90
|
+
throw new errors_2.ValidationError(`swapBufferMultiplier must be a finite number between 1 and ${MAX_SWAP_BUFFER_MULTIPLIER}`, 'swapBufferMultiplier');
|
|
87
91
|
}
|
|
88
92
|
return m;
|
|
89
93
|
}
|
|
@@ -116,21 +120,21 @@ function validateBudgetConfig(budgets) {
|
|
|
116
120
|
// A typo'd key (`daliy`) from an untyped caller must not silently mean "no cap".
|
|
117
121
|
for (const key of Object.keys(budgets)) {
|
|
118
122
|
if (!['daily', 'perTransaction', 'alertAt', 'pauseAt'].includes(key)) {
|
|
119
|
-
throw new
|
|
123
|
+
throw new errors_2.ValidationError(`budgets.${key} is not a recognized field`, `budgets.${key}`);
|
|
120
124
|
}
|
|
121
125
|
}
|
|
122
126
|
const positive = (v, field) => {
|
|
123
127
|
if (v === undefined)
|
|
124
128
|
return;
|
|
125
129
|
if (typeof v !== 'number' || !Number.isFinite(v) || v <= 0) {
|
|
126
|
-
throw new
|
|
130
|
+
throw new errors_2.ValidationError(`budgets.${field} must be a positive number`, `budgets.${field}`);
|
|
127
131
|
}
|
|
128
132
|
};
|
|
129
133
|
const fraction = (v, field) => {
|
|
130
134
|
if (v === undefined)
|
|
131
135
|
return;
|
|
132
136
|
if (typeof v !== 'number' || !Number.isFinite(v) || v <= 0 || v > 1) {
|
|
133
|
-
throw new
|
|
137
|
+
throw new errors_2.ValidationError(`budgets.${field} must be a fraction in (0, 1]`, `budgets.${field}`);
|
|
134
138
|
}
|
|
135
139
|
};
|
|
136
140
|
positive(budgets.daily, 'daily');
|
|
@@ -168,7 +172,7 @@ class OneShot {
|
|
|
168
172
|
const walletProvider = new ethers_2.EthersWalletProvider(config.privateKey, rpcProvider);
|
|
169
173
|
return new OneShot(config, walletProvider);
|
|
170
174
|
}
|
|
171
|
-
throw new
|
|
175
|
+
throw new errors_2.ValidationError('Provide one of: privateKey, cdp, or walletProvider', 'config');
|
|
172
176
|
}
|
|
173
177
|
/**
|
|
174
178
|
* Sync constructor — works with privateKey (backwards compatible).
|
|
@@ -196,10 +200,10 @@ class OneShot {
|
|
|
196
200
|
this.provider = new ethers_2.EthersWalletProvider(config.privateKey, this.rpcProvider);
|
|
197
201
|
}
|
|
198
202
|
else {
|
|
199
|
-
throw new
|
|
203
|
+
throw new errors_2.ValidationError('Provide privateKey or use OneShot.create() for CDP/custom wallets', 'config');
|
|
200
204
|
}
|
|
201
205
|
if (this._currency === 'ETH' && !this.provider.sendTransaction) {
|
|
202
|
-
throw new
|
|
206
|
+
throw new errors_2.ValidationError('ETH currency mode requires a wallet provider that supports sendTransaction', 'currency');
|
|
203
207
|
}
|
|
204
208
|
if (this.debug) {
|
|
205
209
|
this.log(`SDK initialized — chain=${CHAIN_ID} currency=${this._currency}`);
|
|
@@ -311,7 +315,7 @@ class OneShot {
|
|
|
311
315
|
headers: this.headers(),
|
|
312
316
|
});
|
|
313
317
|
if (!response.ok) {
|
|
314
|
-
throw new
|
|
318
|
+
throw new errors_2.ToolError('Failed to list domains', response.status, await response.text());
|
|
315
319
|
}
|
|
316
320
|
return response.json();
|
|
317
321
|
}
|
|
@@ -323,7 +327,7 @@ class OneShot {
|
|
|
323
327
|
headers: this.headers(),
|
|
324
328
|
});
|
|
325
329
|
if (!response.ok) {
|
|
326
|
-
throw new
|
|
330
|
+
throw new errors_2.ToolError('Failed to pause domain', response.status, await response.text());
|
|
327
331
|
}
|
|
328
332
|
return response.json();
|
|
329
333
|
}
|
|
@@ -335,7 +339,7 @@ class OneShot {
|
|
|
335
339
|
headers: this.headers(),
|
|
336
340
|
});
|
|
337
341
|
if (!response.ok) {
|
|
338
|
-
throw new
|
|
342
|
+
throw new errors_2.ToolError('Failed to resume domain', response.status, await response.text());
|
|
339
343
|
}
|
|
340
344
|
return response.json();
|
|
341
345
|
}
|
|
@@ -348,7 +352,7 @@ class OneShot {
|
|
|
348
352
|
}
|
|
349
353
|
async enrichProfile(options) {
|
|
350
354
|
if (!options.linkedin_url && !options.email && !options.name) {
|
|
351
|
-
throw new
|
|
355
|
+
throw new errors_2.ValidationError('At least one of linkedin_url, email, or name is required', 'identifier');
|
|
352
356
|
}
|
|
353
357
|
return this.tool('enrich/profile', { ...options });
|
|
354
358
|
}
|
|
@@ -357,14 +361,58 @@ class OneShot {
|
|
|
357
361
|
}
|
|
358
362
|
async enrichCompany(options) {
|
|
359
363
|
if (!options.domain && !options.name && !options.linkedin_url && !options.ticker) {
|
|
360
|
-
throw new
|
|
364
|
+
throw new errors_2.ValidationError('At least one of domain, name, linkedin_url, or ticker is required', 'identifier');
|
|
361
365
|
}
|
|
362
366
|
return this.tool('enrich/company', { ...options });
|
|
363
367
|
}
|
|
368
|
+
/**
|
|
369
|
+
* Discover local businesses (restaurants, contractors, practices) by
|
|
370
|
+
* category/keywords × location. Flat price per search, not per row.
|
|
371
|
+
*/
|
|
372
|
+
async localSearch(options) {
|
|
373
|
+
if (!options.location || options.location.length === 0) {
|
|
374
|
+
throw new errors_2.ValidationError('location is required (e.g. ["Austin, TX"])', 'location');
|
|
375
|
+
}
|
|
376
|
+
if (!(options.category && options.category.length) && !(options.keywords && options.keywords.length)) {
|
|
377
|
+
throw new errors_2.ValidationError('At least one of category or keywords is required', 'category');
|
|
378
|
+
}
|
|
379
|
+
return this.tool('local/search', { ...options, limit: options.limit ?? 100 });
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Resolve a business name + one locating field to its domain, phone,
|
|
383
|
+
* category and operating status. A miss resolves with `found: false`
|
|
384
|
+
* (a completed job), never a rejection.
|
|
385
|
+
*/
|
|
386
|
+
async localResolve(options) {
|
|
387
|
+
this.validate(options.name, 'name');
|
|
388
|
+
if (!options.address && !options.city && !options.postal_code && !options.phone) {
|
|
389
|
+
throw new errors_2.ValidationError('name plus at least one of address, city, postal_code, or phone is required', 'address');
|
|
390
|
+
}
|
|
391
|
+
return this.tool('local/resolve', { ...options });
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Federal contract opportunities (SAM.gov) by NAICS code — Sources Sought
|
|
395
|
+
* and Presolicitation notices with the contracting officer's published
|
|
396
|
+
* contact. Flat price per search; zero notices is a completed result.
|
|
397
|
+
*/
|
|
398
|
+
async govSolicitations(options) {
|
|
399
|
+
if (!options.naics || options.naics.length === 0) {
|
|
400
|
+
throw new errors_2.ValidationError('naics is required (one or more 6-digit codes, e.g. ["541511"])', 'naics');
|
|
401
|
+
}
|
|
402
|
+
const bad = options.naics.find(c => !/^\d{6}$/.test(String(c)));
|
|
403
|
+
if (bad !== undefined) {
|
|
404
|
+
throw new errors_2.ValidationError(`NAICS codes are 6 digits (got ${JSON.stringify(bad)})`, 'naics');
|
|
405
|
+
}
|
|
406
|
+
return this.tool('gov/solicitations', {
|
|
407
|
+
...options,
|
|
408
|
+
notice_types: options.notice_types ?? ['r', 'p'],
|
|
409
|
+
limit: options.limit ?? 100,
|
|
410
|
+
});
|
|
411
|
+
}
|
|
364
412
|
async findEmail(options) {
|
|
365
413
|
this.validate(options.company_domain, 'company_domain');
|
|
366
414
|
if (!options.full_name && !(options.first_name && options.last_name)) {
|
|
367
|
-
throw new
|
|
415
|
+
throw new errors_2.ValidationError('Either full_name or both first_name and last_name required', 'name');
|
|
368
416
|
}
|
|
369
417
|
return this.tool('enrich/email', { ...options });
|
|
370
418
|
}
|
|
@@ -374,13 +422,13 @@ class OneShot {
|
|
|
374
422
|
}
|
|
375
423
|
async deepResearchPerson(options) {
|
|
376
424
|
if (!options.email && !options.social_media_url && !options.name) {
|
|
377
|
-
throw new
|
|
425
|
+
throw new errors_2.ValidationError('At least one of email, social_media_url, or name is required', 'identifier');
|
|
378
426
|
}
|
|
379
427
|
return this.tool('research/person', { ...options });
|
|
380
428
|
}
|
|
381
429
|
async socialProfiles(options) {
|
|
382
430
|
if (!options.email && !options.social_media_url) {
|
|
383
|
-
throw new
|
|
431
|
+
throw new errors_2.ValidationError('At least one of email or social_media_url is required', 'identifier');
|
|
384
432
|
}
|
|
385
433
|
return this.tool('research/social', { ...options });
|
|
386
434
|
}
|
|
@@ -395,7 +443,7 @@ class OneShot {
|
|
|
395
443
|
}
|
|
396
444
|
async personInterests(options) {
|
|
397
445
|
if (!options.email && !options.phone && !options.social_media_url) {
|
|
398
|
-
throw new
|
|
446
|
+
throw new errors_2.ValidationError('At least one of email, phone, or social_media_url is required', 'identifier');
|
|
399
447
|
}
|
|
400
448
|
return this.tool('research/interests', { ...options });
|
|
401
449
|
}
|
|
@@ -413,7 +461,7 @@ class OneShot {
|
|
|
413
461
|
headers: await this.signedReadHeaders()
|
|
414
462
|
});
|
|
415
463
|
if (!response.ok) {
|
|
416
|
-
throw new
|
|
464
|
+
throw new errors_2.ToolError('Failed to list inbox', response.status, await response.text());
|
|
417
465
|
}
|
|
418
466
|
return response.json();
|
|
419
467
|
}
|
|
@@ -423,10 +471,10 @@ class OneShot {
|
|
|
423
471
|
headers: await this.signedReadHeaders()
|
|
424
472
|
});
|
|
425
473
|
if (response.status === 404) {
|
|
426
|
-
throw new
|
|
474
|
+
throw new errors_2.ToolError('Email not found', 404, 'Email not found');
|
|
427
475
|
}
|
|
428
476
|
if (!response.ok) {
|
|
429
|
-
throw new
|
|
477
|
+
throw new errors_2.ToolError('Failed to get email', response.status, await response.text());
|
|
430
478
|
}
|
|
431
479
|
return response.json();
|
|
432
480
|
}
|
|
@@ -491,13 +539,13 @@ class OneShot {
|
|
|
491
539
|
this.validate(options.objective, 'objective');
|
|
492
540
|
this.validate(options.target_number, 'target_number');
|
|
493
541
|
if (Array.isArray(options.target_number) && options.target_number.length === 0) {
|
|
494
|
-
throw new
|
|
542
|
+
throw new errors_2.ValidationError('target_number array cannot be empty', 'target_number');
|
|
495
543
|
}
|
|
496
544
|
if (options.objective.length < 10) {
|
|
497
|
-
throw new
|
|
545
|
+
throw new errors_2.ValidationError('Objective must be at least 10 characters', 'objective');
|
|
498
546
|
}
|
|
499
547
|
if (options.max_duration_minutes !== undefined && (options.max_duration_minutes < 1 || options.max_duration_minutes > 30)) {
|
|
500
|
-
throw new
|
|
548
|
+
throw new errors_2.ValidationError('max_duration_minutes must be between 1 and 30', 'max_duration_minutes');
|
|
501
549
|
}
|
|
502
550
|
const payload = {
|
|
503
551
|
objective: options.objective,
|
|
@@ -520,12 +568,12 @@ class OneShot {
|
|
|
520
568
|
on400: async (resp) => {
|
|
521
569
|
const errorData = await resp.json();
|
|
522
570
|
if (errorData.error === 'content_blocked') {
|
|
523
|
-
throw new
|
|
571
|
+
throw new errors_2.ContentBlockedError(errorData.message, errorData.categories || []);
|
|
524
572
|
}
|
|
525
573
|
if (errorData.error === 'emergency_number_blocked') {
|
|
526
|
-
throw new
|
|
574
|
+
throw new errors_2.EmergencyNumberError(errorData.message, errorData.blocked_number || '');
|
|
527
575
|
}
|
|
528
|
-
throw new
|
|
576
|
+
throw new errors_2.ValidationError(errorData.message || 'Invalid request', 'request');
|
|
529
577
|
},
|
|
530
578
|
});
|
|
531
579
|
if (callResp.status !== 202) {
|
|
@@ -554,17 +602,17 @@ class OneShot {
|
|
|
554
602
|
this.validate(options.message, 'message');
|
|
555
603
|
this.validate(options.to_number, 'to_number');
|
|
556
604
|
if (Array.isArray(options.to_number) && options.to_number.length === 0) {
|
|
557
|
-
throw new
|
|
605
|
+
throw new errors_2.ValidationError('to_number array cannot be empty', 'to_number');
|
|
558
606
|
}
|
|
559
607
|
if (options.message.length < 1) {
|
|
560
|
-
throw new
|
|
608
|
+
throw new errors_2.ValidationError('Message is required', 'message');
|
|
561
609
|
}
|
|
562
610
|
if (options.message.length > 1600) {
|
|
563
|
-
throw new
|
|
611
|
+
throw new errors_2.ValidationError('Message must be 1600 characters or less', 'message');
|
|
564
612
|
}
|
|
565
613
|
const recipientCount = Array.isArray(options.to_number) ? options.to_number.length : 1;
|
|
566
614
|
if (recipientCount > 10) {
|
|
567
|
-
throw new
|
|
615
|
+
throw new errors_2.ValidationError('Maximum 10 recipients allowed', 'to_number');
|
|
568
616
|
}
|
|
569
617
|
const payload = {
|
|
570
618
|
message: options.message,
|
|
@@ -582,12 +630,12 @@ class OneShot {
|
|
|
582
630
|
on400: async (resp) => {
|
|
583
631
|
const errorData = await resp.json();
|
|
584
632
|
if (errorData.error === 'content_blocked') {
|
|
585
|
-
throw new
|
|
633
|
+
throw new errors_2.ContentBlockedError(errorData.message, errorData.categories || []);
|
|
586
634
|
}
|
|
587
635
|
if (errorData.error === 'emergency_number_blocked') {
|
|
588
|
-
throw new
|
|
636
|
+
throw new errors_2.EmergencyNumberError(errorData.message, errorData.blocked_number || '');
|
|
589
637
|
}
|
|
590
|
-
throw new
|
|
638
|
+
throw new errors_2.ValidationError(errorData.message || 'Invalid request', 'request');
|
|
591
639
|
},
|
|
592
640
|
});
|
|
593
641
|
if (sendResp.status !== 202) {
|
|
@@ -621,7 +669,7 @@ class OneShot {
|
|
|
621
669
|
this.validate(options.product?.name, 'product.name');
|
|
622
670
|
this.validate(options.product?.description, 'product.description');
|
|
623
671
|
if (options.product.description.length < 10) {
|
|
624
|
-
throw new
|
|
672
|
+
throw new errors_2.ValidationError('Product description must be at least 10 characters', 'product.description');
|
|
625
673
|
}
|
|
626
674
|
const payload = {
|
|
627
675
|
type: options.type ?? 'saas',
|
|
@@ -656,7 +704,7 @@ class OneShot {
|
|
|
656
704
|
},
|
|
657
705
|
on400: async (resp) => {
|
|
658
706
|
const errorData = await resp.json();
|
|
659
|
-
throw new
|
|
707
|
+
throw new errors_2.ValidationError(errorData.message || 'Invalid request', 'request');
|
|
660
708
|
},
|
|
661
709
|
});
|
|
662
710
|
if (buildResp.status !== 202) {
|
|
@@ -684,10 +732,10 @@ class OneShot {
|
|
|
684
732
|
async browser(options) {
|
|
685
733
|
this.validate(options.task, 'task');
|
|
686
734
|
if (options.task.length < 10) {
|
|
687
|
-
throw new
|
|
735
|
+
throw new errors_2.ValidationError('Task must be at least 10 characters', 'task');
|
|
688
736
|
}
|
|
689
737
|
if (options.max_steps !== undefined && (options.max_steps < 1 || options.max_steps > 100)) {
|
|
690
|
-
throw new
|
|
738
|
+
throw new errors_2.ValidationError('max_steps must be between 1 and 100', 'max_steps');
|
|
691
739
|
}
|
|
692
740
|
const payload = {
|
|
693
741
|
task: options.task,
|
|
@@ -718,7 +766,7 @@ class OneShot {
|
|
|
718
766
|
onQuote: (ctx) => this.log(`Browser quote: $${ctx.estimated_cost} for ~${ctx.estimated_steps} steps`),
|
|
719
767
|
on400: async (resp) => {
|
|
720
768
|
const errorData = await resp.json();
|
|
721
|
-
throw new
|
|
769
|
+
throw new errors_2.ValidationError(errorData.message || 'Invalid request', 'request');
|
|
722
770
|
},
|
|
723
771
|
});
|
|
724
772
|
if (execResp.status !== 202) {
|
|
@@ -748,7 +796,7 @@ class OneShot {
|
|
|
748
796
|
body: JSON.stringify({ name }),
|
|
749
797
|
});
|
|
750
798
|
if (!response.ok) {
|
|
751
|
-
throw new
|
|
799
|
+
throw new errors_2.ToolError('Failed to create browser profile', response.status, await response.text());
|
|
752
800
|
}
|
|
753
801
|
return response.json();
|
|
754
802
|
}
|
|
@@ -768,7 +816,7 @@ class OneShot {
|
|
|
768
816
|
headers: await this.signedReadHeaders(),
|
|
769
817
|
});
|
|
770
818
|
if (!response.ok) {
|
|
771
|
-
throw new
|
|
819
|
+
throw new errors_2.ToolError('Failed to list browser profiles', response.status, await response.text());
|
|
772
820
|
}
|
|
773
821
|
const data = await response.json();
|
|
774
822
|
return data.profiles;
|
|
@@ -788,7 +836,7 @@ class OneShot {
|
|
|
788
836
|
headers: await this.signedReadHeaders(),
|
|
789
837
|
});
|
|
790
838
|
if (!response.ok) {
|
|
791
|
-
throw new
|
|
839
|
+
throw new errors_2.ToolError('Failed to delete browser profile', response.status, await response.text());
|
|
792
840
|
}
|
|
793
841
|
}
|
|
794
842
|
/**
|
|
@@ -834,7 +882,7 @@ class OneShot {
|
|
|
834
882
|
headers: await this.signedReadHeaders()
|
|
835
883
|
});
|
|
836
884
|
if (!response.ok) {
|
|
837
|
-
throw new
|
|
885
|
+
throw new errors_2.ToolError('Failed to list SMS inbox', response.status, await response.text());
|
|
838
886
|
}
|
|
839
887
|
return response.json();
|
|
840
888
|
}
|
|
@@ -853,10 +901,10 @@ class OneShot {
|
|
|
853
901
|
headers: await this.signedReadHeaders()
|
|
854
902
|
});
|
|
855
903
|
if (response.status === 404) {
|
|
856
|
-
throw new
|
|
904
|
+
throw new errors_2.ToolError('SMS message not found', 404, 'Message not found');
|
|
857
905
|
}
|
|
858
906
|
if (!response.ok) {
|
|
859
|
-
throw new
|
|
907
|
+
throw new errors_2.ToolError('Failed to get SMS message', response.status, await response.text());
|
|
860
908
|
}
|
|
861
909
|
return response.json();
|
|
862
910
|
}
|
|
@@ -881,7 +929,7 @@ class OneShot {
|
|
|
881
929
|
headers: await this.signedReadHeaders()
|
|
882
930
|
});
|
|
883
931
|
if (!response.ok) {
|
|
884
|
-
throw new
|
|
932
|
+
throw new errors_2.ToolError('Failed to list notifications', response.status, await response.text());
|
|
885
933
|
}
|
|
886
934
|
return response.json();
|
|
887
935
|
}
|
|
@@ -900,10 +948,10 @@ class OneShot {
|
|
|
900
948
|
headers: await this.signedReadHeaders()
|
|
901
949
|
});
|
|
902
950
|
if (response.status === 404) {
|
|
903
|
-
throw new
|
|
951
|
+
throw new errors_2.ToolError('Notification not found', 404, 'Notification not found');
|
|
904
952
|
}
|
|
905
953
|
if (!response.ok) {
|
|
906
|
-
throw new
|
|
954
|
+
throw new errors_2.ToolError('Failed to mark notification as read', response.status, await response.text());
|
|
907
955
|
}
|
|
908
956
|
}
|
|
909
957
|
async getUnifiedBalance() {
|
|
@@ -911,7 +959,7 @@ class OneShot {
|
|
|
911
959
|
headers: await this.signedReadHeaders()
|
|
912
960
|
});
|
|
913
961
|
if (!response.ok) {
|
|
914
|
-
throw new
|
|
962
|
+
throw new errors_2.ToolError('Failed to fetch balance', response.status, await response.text());
|
|
915
963
|
}
|
|
916
964
|
return response.json();
|
|
917
965
|
}
|
|
@@ -981,9 +1029,9 @@ class OneShot {
|
|
|
981
1029
|
on400: async (resp) => {
|
|
982
1030
|
const errorData = await resp.json();
|
|
983
1031
|
if (errorData.error === 'content_blocked') {
|
|
984
|
-
throw new
|
|
1032
|
+
throw new errors_2.ContentBlockedError(errorData.message, []);
|
|
985
1033
|
}
|
|
986
|
-
throw new
|
|
1034
|
+
throw new errors_2.ValidationError(errorData.message || 'Invalid request', 'request');
|
|
987
1035
|
},
|
|
988
1036
|
});
|
|
989
1037
|
if (createResp.status !== 202) {
|
|
@@ -1006,10 +1054,10 @@ class OneShot {
|
|
|
1006
1054
|
headers: this.headers()
|
|
1007
1055
|
});
|
|
1008
1056
|
if (response.status === 404) {
|
|
1009
|
-
throw new
|
|
1057
|
+
throw new errors_2.ToolError('Goal not found', 404, 'Goal not found');
|
|
1010
1058
|
}
|
|
1011
1059
|
if (!response.ok) {
|
|
1012
|
-
throw new
|
|
1060
|
+
throw new errors_2.ToolError('Failed to get compute goal', response.status, await response.text());
|
|
1013
1061
|
}
|
|
1014
1062
|
const json = await response.json();
|
|
1015
1063
|
return json.data;
|
|
@@ -1031,7 +1079,7 @@ class OneShot {
|
|
|
1031
1079
|
headers: this.headers()
|
|
1032
1080
|
});
|
|
1033
1081
|
if (!response.ok) {
|
|
1034
|
-
throw new
|
|
1082
|
+
throw new errors_2.ToolError('Failed to get compute tasks', response.status, await response.text());
|
|
1035
1083
|
}
|
|
1036
1084
|
const json = await response.json();
|
|
1037
1085
|
return json.data;
|
|
@@ -1051,10 +1099,10 @@ class OneShot {
|
|
|
1051
1099
|
headers: this.headers()
|
|
1052
1100
|
});
|
|
1053
1101
|
if (response.status === 404) {
|
|
1054
|
-
throw new
|
|
1102
|
+
throw new errors_2.ToolError('Budget not found', 404, 'Budget not found for this goal');
|
|
1055
1103
|
}
|
|
1056
1104
|
if (!response.ok) {
|
|
1057
|
-
throw new
|
|
1105
|
+
throw new errors_2.ToolError('Failed to get compute budget', response.status, await response.text());
|
|
1058
1106
|
}
|
|
1059
1107
|
const json = await response.json();
|
|
1060
1108
|
return json.data;
|
|
@@ -1076,10 +1124,10 @@ class OneShot {
|
|
|
1076
1124
|
body: JSON.stringify({ reason })
|
|
1077
1125
|
});
|
|
1078
1126
|
if (response.status === 404) {
|
|
1079
|
-
throw new
|
|
1127
|
+
throw new errors_2.ToolError('Goal not found', 404, 'Goal not found');
|
|
1080
1128
|
}
|
|
1081
1129
|
if (!response.ok) {
|
|
1082
|
-
throw new
|
|
1130
|
+
throw new errors_2.ToolError('Failed to cancel compute goal', response.status, await response.text());
|
|
1083
1131
|
}
|
|
1084
1132
|
const json = await response.json();
|
|
1085
1133
|
return json.data;
|
|
@@ -1105,10 +1153,10 @@ class OneShot {
|
|
|
1105
1153
|
body: JSON.stringify(input)
|
|
1106
1154
|
});
|
|
1107
1155
|
if (response.status === 404) {
|
|
1108
|
-
throw new
|
|
1156
|
+
throw new errors_2.ToolError('Goal or task not found', 404, await response.text());
|
|
1109
1157
|
}
|
|
1110
1158
|
if (!response.ok) {
|
|
1111
|
-
throw new
|
|
1159
|
+
throw new errors_2.ToolError('Failed to respond to compute task', response.status, await response.text());
|
|
1112
1160
|
}
|
|
1113
1161
|
const json = await response.json();
|
|
1114
1162
|
return json.data;
|
|
@@ -1129,7 +1177,7 @@ class OneShot {
|
|
|
1129
1177
|
body: JSON.stringify({ reason })
|
|
1130
1178
|
});
|
|
1131
1179
|
if (!response.ok) {
|
|
1132
|
-
throw new
|
|
1180
|
+
throw new errors_2.ToolError('Failed to pause compute goal', response.status, await response.text());
|
|
1133
1181
|
}
|
|
1134
1182
|
const json = await response.json();
|
|
1135
1183
|
return json.data;
|
|
@@ -1151,7 +1199,7 @@ class OneShot {
|
|
|
1151
1199
|
body: JSON.stringify({})
|
|
1152
1200
|
});
|
|
1153
1201
|
if (!response.ok) {
|
|
1154
|
-
throw new
|
|
1202
|
+
throw new errors_2.ToolError('Failed to resume compute goal', response.status, await response.text());
|
|
1155
1203
|
}
|
|
1156
1204
|
const json = await response.json();
|
|
1157
1205
|
return json.data;
|
|
@@ -1171,7 +1219,7 @@ class OneShot {
|
|
|
1171
1219
|
async fundComputeGoal(goalId, amount) {
|
|
1172
1220
|
this.validate(goalId, 'goalId');
|
|
1173
1221
|
if (!amount || amount <= 0) {
|
|
1174
|
-
throw new
|
|
1222
|
+
throw new errors_2.ValidationError('amount must be a positive number', 'amount');
|
|
1175
1223
|
}
|
|
1176
1224
|
const path = `/v1/compute/${goalId}/fund`;
|
|
1177
1225
|
const payload = { amount };
|
|
@@ -1224,7 +1272,7 @@ class OneShot {
|
|
|
1224
1272
|
headers: this.headers()
|
|
1225
1273
|
});
|
|
1226
1274
|
if (!response.ok) {
|
|
1227
|
-
throw new
|
|
1275
|
+
throw new errors_2.ToolError('Failed to get spend breakdown', response.status, await response.text());
|
|
1228
1276
|
}
|
|
1229
1277
|
return response.json();
|
|
1230
1278
|
}
|
|
@@ -1243,7 +1291,7 @@ class OneShot {
|
|
|
1243
1291
|
headers: this.headers()
|
|
1244
1292
|
});
|
|
1245
1293
|
if (!response.ok) {
|
|
1246
|
-
throw new
|
|
1294
|
+
throw new errors_2.ToolError('Failed to get RoCS', response.status, await response.text());
|
|
1247
1295
|
}
|
|
1248
1296
|
return response.json();
|
|
1249
1297
|
}
|
|
@@ -1279,7 +1327,7 @@ class OneShot {
|
|
|
1279
1327
|
headers: this.headers()
|
|
1280
1328
|
});
|
|
1281
1329
|
if (!response.ok) {
|
|
1282
|
-
throw new
|
|
1330
|
+
throw new errors_2.ToolError('Failed to list receipts', response.status, await response.text());
|
|
1283
1331
|
}
|
|
1284
1332
|
return response.json();
|
|
1285
1333
|
}
|
|
@@ -1318,7 +1366,7 @@ class OneShot {
|
|
|
1318
1366
|
body: JSON.stringify({ goal_id: ref.goalId, ...valueTag }),
|
|
1319
1367
|
});
|
|
1320
1368
|
if (!response.ok) {
|
|
1321
|
-
throw new
|
|
1369
|
+
throw new errors_2.ToolError('Failed to record outcome value', response.status, await response.text());
|
|
1322
1370
|
}
|
|
1323
1371
|
return;
|
|
1324
1372
|
}
|
|
@@ -1330,10 +1378,10 @@ class OneShot {
|
|
|
1330
1378
|
body: JSON.stringify(valueTag)
|
|
1331
1379
|
});
|
|
1332
1380
|
if (response.status === 404) {
|
|
1333
|
-
throw new
|
|
1381
|
+
throw new errors_2.ToolError('Receipt not found', 404, 'Receipt not found or not owned by this agent');
|
|
1334
1382
|
}
|
|
1335
1383
|
if (!response.ok) {
|
|
1336
|
-
throw new
|
|
1384
|
+
throw new errors_2.ToolError('Failed to tag receipt value', response.status, await response.text());
|
|
1337
1385
|
}
|
|
1338
1386
|
}
|
|
1339
1387
|
/**
|
|
@@ -1359,7 +1407,7 @@ class OneShot {
|
|
|
1359
1407
|
headers: this.headers()
|
|
1360
1408
|
});
|
|
1361
1409
|
if (!response.ok) {
|
|
1362
|
-
throw new
|
|
1410
|
+
throw new errors_2.ToolError('Failed to get RoCS by goal', response.status, await response.text());
|
|
1363
1411
|
}
|
|
1364
1412
|
return response.json();
|
|
1365
1413
|
}
|
|
@@ -1372,7 +1420,7 @@ class OneShot {
|
|
|
1372
1420
|
}
|
|
1373
1421
|
validate(value, field) {
|
|
1374
1422
|
if (!value)
|
|
1375
|
-
throw new
|
|
1423
|
+
throw new errors_2.ValidationError(`${field} is required`, field);
|
|
1376
1424
|
}
|
|
1377
1425
|
headers() {
|
|
1378
1426
|
return {
|
|
@@ -1466,11 +1514,11 @@ class OneShot {
|
|
|
1466
1514
|
});
|
|
1467
1515
|
}
|
|
1468
1516
|
catch (err) {
|
|
1469
|
-
throw new
|
|
1517
|
+
throw new errors_2.BudgetSyncError(`Could not sync spend budget (network): ${err}`);
|
|
1470
1518
|
}
|
|
1471
1519
|
if (!response.ok) {
|
|
1472
1520
|
const text = await response.text();
|
|
1473
|
-
throw new
|
|
1521
|
+
throw new errors_2.BudgetSyncError(`Could not sync spend budget (${response.status}): ${text}`, response.status, text);
|
|
1474
1522
|
}
|
|
1475
1523
|
this.log('Budget synced');
|
|
1476
1524
|
})();
|
|
@@ -1498,7 +1546,7 @@ class OneShot {
|
|
|
1498
1546
|
return;
|
|
1499
1547
|
const amount = parseFloat(total);
|
|
1500
1548
|
if (Number.isFinite(amount) && amount > cap) {
|
|
1501
|
-
throw new
|
|
1549
|
+
throw new errors_2.BudgetExceededError(`Quote $${total} exceeds this agent's per-transaction budget of $${cap}`, 'per_transaction', cap, undefined, amount);
|
|
1502
1550
|
}
|
|
1503
1551
|
}
|
|
1504
1552
|
/**
|
|
@@ -1515,7 +1563,7 @@ class OneShot {
|
|
|
1515
1563
|
headers: await this.signedReadHeaders(),
|
|
1516
1564
|
});
|
|
1517
1565
|
if (!response.ok) {
|
|
1518
|
-
throw new
|
|
1566
|
+
throw new errors_2.ToolError('Failed to fetch budgets', response.status, await response.text());
|
|
1519
1567
|
}
|
|
1520
1568
|
const body = await response.json();
|
|
1521
1569
|
return (body.data ?? body);
|
|
@@ -1523,7 +1571,7 @@ class OneShot {
|
|
|
1523
1571
|
/** Local fast-fail guard: throw when a quote total exceeds the caller's cap. */
|
|
1524
1572
|
assertWithinMaxCost(total, maxCost) {
|
|
1525
1573
|
if (maxCost && parseFloat(total) > maxCost) {
|
|
1526
|
-
throw new
|
|
1574
|
+
throw new errors_2.OneShotError(`Quote $${total} exceeds maxCost $${maxCost}`);
|
|
1527
1575
|
}
|
|
1528
1576
|
}
|
|
1529
1577
|
/**
|
|
@@ -1569,7 +1617,7 @@ class OneShot {
|
|
|
1569
1617
|
const budget = response.status === 403 ? this.parseBudgetRejection(text) : undefined;
|
|
1570
1618
|
if (budget)
|
|
1571
1619
|
throw budget;
|
|
1572
|
-
throw new
|
|
1620
|
+
throw new errors_2.ToolError(message, response.status, text);
|
|
1573
1621
|
}
|
|
1574
1622
|
/**
|
|
1575
1623
|
* Map a 403 `budget_exceeded` body onto a typed error, so callers can catch
|
|
@@ -1582,7 +1630,7 @@ class OneShot {
|
|
|
1582
1630
|
if (body.error !== 'budget_exceeded')
|
|
1583
1631
|
return undefined;
|
|
1584
1632
|
const b = body.budget ?? {};
|
|
1585
|
-
return new
|
|
1633
|
+
return new errors_2.BudgetExceededError(body.message ?? 'Agent spend budget exceeded', b.reason === 'per_transaction' ? 'per_transaction' : 'daily', b.cap !== undefined ? parseFloat(b.cap) : undefined, b.spent !== undefined ? parseFloat(b.spent) : undefined, b.charge !== undefined ? parseFloat(b.charge) : undefined, b.resets_at);
|
|
1586
1634
|
}
|
|
1587
1635
|
catch {
|
|
1588
1636
|
return undefined;
|
|
@@ -1605,7 +1653,7 @@ class OneShot {
|
|
|
1605
1653
|
expectedAmount ? `expected $${expectedAmount}` : null,
|
|
1606
1654
|
receivedAmount ? `signed $${receivedAmount}` : null,
|
|
1607
1655
|
].filter(Boolean).join(', ');
|
|
1608
|
-
return new
|
|
1656
|
+
return new errors_2.PaymentError(`payment rejected: ${reason}${detail ? ` — ${detail}` : ''}${data.message ? ` (${data.message})` : ''}`, reason, {
|
|
1609
1657
|
amount: expectedAmount,
|
|
1610
1658
|
asset: data.expected?.asset,
|
|
1611
1659
|
network: data.expected?.network,
|
|
@@ -1641,8 +1689,59 @@ class OneShot {
|
|
|
1641
1689
|
return undefined;
|
|
1642
1690
|
return { 'Idempotency-Key': idempotencyKey };
|
|
1643
1691
|
}
|
|
1692
|
+
async readReliabilityJson(path, signed, allowDegraded = false) {
|
|
1693
|
+
const scope = (0, deadline_1.deadlineScope)(undefined, signed ? 10000 : 5000);
|
|
1694
|
+
try {
|
|
1695
|
+
return await (0, deadline_1.abortable)((async () => {
|
|
1696
|
+
const headers = signed ? await this.signedReadHeaders() : undefined;
|
|
1697
|
+
if (scope.signal.aborted)
|
|
1698
|
+
throw new errors_2.OneShotError('Read deadline exceeded');
|
|
1699
|
+
const response = await fetch(`${this.baseUrl}${path}`, { headers, signal: scope.signal });
|
|
1700
|
+
if (!response.ok && !(allowDegraded && response.status === 503))
|
|
1701
|
+
await this.failFromResponse('Reliability read failed', response);
|
|
1702
|
+
return await response.json();
|
|
1703
|
+
})(), scope.signal);
|
|
1704
|
+
}
|
|
1705
|
+
finally {
|
|
1706
|
+
scope.close();
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
async recoverRequest(options) {
|
|
1710
|
+
const query = new URLSearchParams({ endpoint: options.endpoint, key: options.idempotencyKey });
|
|
1711
|
+
return this.readReliabilityJson(`/v1/submissions/recover?${query}`, true);
|
|
1712
|
+
}
|
|
1713
|
+
async getServiceStatus() {
|
|
1714
|
+
return this.readReliabilityJson('/v1/status', false, true);
|
|
1715
|
+
}
|
|
1644
1716
|
async executeToolRequest(endpoint, options, quoteId) {
|
|
1645
|
-
const
|
|
1717
|
+
const reliable = /(?:^|\/)(enrich\/(profile|email)|verify\/email)$/.test(endpoint);
|
|
1718
|
+
const key = options.idempotencyKey ?? (reliable ? ethers_1.ethers.hexlify(ethers_1.ethers.randomBytes(16)) : undefined);
|
|
1719
|
+
if (options.totalTimeoutMs !== undefined && (!Number.isFinite(options.totalTimeoutMs) || options.totalTimeoutMs <= 0)) {
|
|
1720
|
+
throw new errors_2.ValidationError('totalTimeoutMs must be positive', 'totalTimeoutMs');
|
|
1721
|
+
}
|
|
1722
|
+
const scope = (0, deadline_1.deadlineScope)(options.signal, options.totalTimeoutMs);
|
|
1723
|
+
const context = { phase: 'initialization' };
|
|
1724
|
+
const started = Date.now();
|
|
1725
|
+
try {
|
|
1726
|
+
if (scope.signal.aborted)
|
|
1727
|
+
throw new errors_2.OneShotError('Operation cancelled');
|
|
1728
|
+
if (key)
|
|
1729
|
+
options.onRequestCreated?.({ idempotencyKey: key });
|
|
1730
|
+
return await (0, deadline_1.abortable)(this.executeToolRequestImpl(endpoint, { ...options, idempotencyKey: key, signal: scope.signal }, quoteId, context), scope.signal);
|
|
1731
|
+
}
|
|
1732
|
+
catch (error) {
|
|
1733
|
+
if (scope.timedOut())
|
|
1734
|
+
throw new errors_1.RequestTimeoutError(Date.now() - started, context.phase, key, context.requestId, context.receiptId);
|
|
1735
|
+
if (error instanceof Error)
|
|
1736
|
+
Object.assign(error, { idempotencyKey: key, requestId: context.requestId, receiptId: context.receiptId, phase: context.phase });
|
|
1737
|
+
throw error;
|
|
1738
|
+
}
|
|
1739
|
+
finally {
|
|
1740
|
+
scope.close();
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
async executeToolRequestImpl(endpoint, options, quoteId, context = { phase: "initialization" }) {
|
|
1744
|
+
const { totalTimeoutMs, onRequestCreated, onAccepted, signal, onStatusUpdate, wait = true, waitForPhones, phoneTimeoutSec, idempotencyKey, maxCost, ...payload } = options;
|
|
1646
1745
|
const extraHeaders = {
|
|
1647
1746
|
...this.maxCostHeader(maxCost),
|
|
1648
1747
|
...this.idempotencyHeader(idempotencyKey),
|
|
@@ -1671,11 +1770,18 @@ class OneShot {
|
|
|
1671
1770
|
}
|
|
1672
1771
|
}
|
|
1673
1772
|
if (signal?.aborted) {
|
|
1674
|
-
throw new
|
|
1773
|
+
throw new errors_2.OneShotError('Operation cancelled');
|
|
1675
1774
|
}
|
|
1676
1775
|
// One-time push of config.budgets before the first paid call, so the
|
|
1677
1776
|
// server-side gate knows about them on this very request.
|
|
1678
1777
|
await this.ensureBudgetsSynced();
|
|
1778
|
+
if (signal?.aborted)
|
|
1779
|
+
throw new errors_2.OneShotError('Operation cancelled');
|
|
1780
|
+
if (idempotencyKey)
|
|
1781
|
+
Object.assign(extraHeaders, { 'x-agent-proof': 'required' });
|
|
1782
|
+
if (signal?.aborted)
|
|
1783
|
+
throw new errors_2.OneShotError('Operation cancelled');
|
|
1784
|
+
context.phase = 'submission';
|
|
1679
1785
|
let response = await this.makeRequest(endpoint, payload, undefined, quoteId, signal, undefined, extraHeaders);
|
|
1680
1786
|
// Handle 402 Payment Required
|
|
1681
1787
|
if (response.status === 402) {
|
|
@@ -1696,7 +1802,10 @@ class OneShot {
|
|
|
1696
1802
|
this.log(`Payment required: ${paymentInfo.amount} USDC`);
|
|
1697
1803
|
this.assertWithinBudget(paymentInfo.amount);
|
|
1698
1804
|
this.checkAbortBeforePayment(signal);
|
|
1805
|
+
context.phase = 'payment';
|
|
1699
1806
|
const signed = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
|
|
1807
|
+
this.checkAbortBeforePayment(signal);
|
|
1808
|
+
context.phase = 'submission';
|
|
1700
1809
|
response = await this.makePaidRequest(signed, endpoint, payload, quoteId, signal, undefined, extraHeaders);
|
|
1701
1810
|
}
|
|
1702
1811
|
if (!response.ok) {
|
|
@@ -1705,11 +1814,19 @@ class OneShot {
|
|
|
1705
1814
|
const result = await response.json();
|
|
1706
1815
|
// Handle async jobs
|
|
1707
1816
|
if ((result.status === 'pending' || result.status === 'processing') && result.request_id) {
|
|
1817
|
+
context.requestId = String(result.request_id);
|
|
1818
|
+
context.receiptId = typeof result.receipt_id === 'string' ? result.receipt_id : undefined;
|
|
1819
|
+
onAccepted?.({ request_id: context.requestId, receipt_id: context.receiptId, idempotencyKey });
|
|
1820
|
+
context.phase = 'polling';
|
|
1708
1821
|
this.log(`Job queued: ${result.request_id}`);
|
|
1709
1822
|
if (!wait) {
|
|
1710
|
-
return {
|
|
1823
|
+
return { ...result, idempotencyKey };
|
|
1824
|
+
}
|
|
1825
|
+
const completed = await this.pollJob(result.request_id, options.timeout, signal, onStatusUpdate, waitForPhones ? { waitForPhones, phoneTimeoutSec } : undefined);
|
|
1826
|
+
if (completed && typeof completed === 'object' && !Array.isArray(completed)) {
|
|
1827
|
+
return { ...completed, request_id: context.requestId, receipt_id: completed.receipt_id ?? context.receiptId, idempotencyKey };
|
|
1711
1828
|
}
|
|
1712
|
-
return
|
|
1829
|
+
return completed;
|
|
1713
1830
|
}
|
|
1714
1831
|
return (result.data ?? result);
|
|
1715
1832
|
}
|
|
@@ -1788,7 +1905,7 @@ class OneShot {
|
|
|
1788
1905
|
const wsBranch = this.waitViaWebSocket(requestId, inner.signal, emit, wait).catch((err) => {
|
|
1789
1906
|
// A failed job or a cancellation is a real outcome. Anything else is a
|
|
1790
1907
|
// transport problem: never settle the race on it, HTTP carries on.
|
|
1791
|
-
if (err instanceof
|
|
1908
|
+
if (err instanceof errors_2.OneShotError)
|
|
1792
1909
|
throw err;
|
|
1793
1910
|
this.log(`WebSocket unavailable (${err instanceof Error ? err.message : String(err)}) — relying on HTTP polling`);
|
|
1794
1911
|
return new Promise(() => { });
|
|
@@ -1858,7 +1975,7 @@ class OneShot {
|
|
|
1858
1975
|
let lastResult = initialResult;
|
|
1859
1976
|
while (Date.now() < deadline) {
|
|
1860
1977
|
if (signal?.aborted)
|
|
1861
|
-
throw new
|
|
1978
|
+
throw new errors_2.OneShotError('Operation cancelled');
|
|
1862
1979
|
try {
|
|
1863
1980
|
const resp = await fetch(`${this.baseUrl}/v1/requests/${requestId}`, {
|
|
1864
1981
|
headers: this.headers(),
|
|
@@ -1869,7 +1986,7 @@ class OneShot {
|
|
|
1869
1986
|
// whatever we last had so consumers don't lose the sync result.
|
|
1870
1987
|
if (lastResult !== undefined)
|
|
1871
1988
|
return lastResult;
|
|
1872
|
-
throw new
|
|
1989
|
+
throw new errors_2.ToolError('Failed to check job status', resp.status, await resp.text());
|
|
1873
1990
|
}
|
|
1874
1991
|
const job = await resp.json();
|
|
1875
1992
|
lastResult = (job.result ?? job);
|
|
@@ -1878,7 +1995,7 @@ class OneShot {
|
|
|
1878
1995
|
}
|
|
1879
1996
|
}
|
|
1880
1997
|
catch (err) {
|
|
1881
|
-
if (err instanceof
|
|
1998
|
+
if (err instanceof errors_2.OneShotError)
|
|
1882
1999
|
throw err;
|
|
1883
2000
|
if (lastResult !== undefined)
|
|
1884
2001
|
return lastResult;
|
|
@@ -1904,7 +2021,7 @@ class OneShot {
|
|
|
1904
2021
|
return reject(new Error('WebSocket not available'));
|
|
1905
2022
|
}
|
|
1906
2023
|
if (signal.aborted) {
|
|
1907
|
-
return reject(new
|
|
2024
|
+
return reject(new errors_2.OneShotError('Operation cancelled'));
|
|
1908
2025
|
}
|
|
1909
2026
|
const wsUrl = this.baseUrl.replace(/^http/, 'ws') +
|
|
1910
2027
|
`/v1/requests/subscribe?wallet=${encodeURIComponent(this.provider.address)}`;
|
|
@@ -1932,7 +2049,7 @@ class OneShot {
|
|
|
1932
2049
|
const onAbort = () => {
|
|
1933
2050
|
settle(() => {
|
|
1934
2051
|
cleanup();
|
|
1935
|
-
reject(new
|
|
2052
|
+
reject(new errors_2.OneShotError('Operation cancelled'));
|
|
1936
2053
|
});
|
|
1937
2054
|
};
|
|
1938
2055
|
signal.addEventListener('abort', onAbort, { once: true });
|
|
@@ -1965,7 +2082,7 @@ class OneShot {
|
|
|
1965
2082
|
settle(() => {
|
|
1966
2083
|
cleanup();
|
|
1967
2084
|
wait.via = 'ws';
|
|
1968
|
-
reject(new
|
|
2085
|
+
reject(new errors_2.JobError(`Job failed: ${msg.error ?? 'Unknown'}`, requestId, String(msg.error ?? 'Unknown'), msg.error_code));
|
|
1969
2086
|
});
|
|
1970
2087
|
}
|
|
1971
2088
|
else {
|
|
@@ -1998,6 +2115,21 @@ class OneShot {
|
|
|
1998
2115
|
* Owns the caller's deadline (`JobTimeoutError`).
|
|
1999
2116
|
*/
|
|
2000
2117
|
async pollJobHttp(requestId, timeoutSec, signal, emit, wait) {
|
|
2118
|
+
const scope = (0, deadline_1.deadlineScope)(signal, (timeoutSec ?? 120) * 1000);
|
|
2119
|
+
const started = Date.now();
|
|
2120
|
+
try {
|
|
2121
|
+
return await (0, deadline_1.abortable)(this.pollJobHttpImpl(requestId, timeoutSec, scope.signal, emit, wait), scope.signal);
|
|
2122
|
+
}
|
|
2123
|
+
catch (error) {
|
|
2124
|
+
if (scope.timedOut())
|
|
2125
|
+
throw new errors_2.JobTimeoutError(requestId, Date.now() - started);
|
|
2126
|
+
throw error;
|
|
2127
|
+
}
|
|
2128
|
+
finally {
|
|
2129
|
+
scope.close();
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
async pollJobHttpImpl(requestId, timeoutSec, signal, emit, wait) {
|
|
2001
2133
|
const maxWaitMs = (timeoutSec ?? 120) * 1000;
|
|
2002
2134
|
const startTime = Date.now();
|
|
2003
2135
|
let retries = 0;
|
|
@@ -2005,7 +2137,7 @@ class OneShot {
|
|
|
2005
2137
|
let polls = 0;
|
|
2006
2138
|
while (Date.now() - startTime < maxWaitMs) {
|
|
2007
2139
|
if (signal?.aborted)
|
|
2008
|
-
throw new
|
|
2140
|
+
throw new errors_2.OneShotError('Operation cancelled');
|
|
2009
2141
|
try {
|
|
2010
2142
|
const resp = await fetch(`${this.baseUrl}/v1/requests/${requestId}`, {
|
|
2011
2143
|
headers: this.headers(),
|
|
@@ -2018,7 +2150,7 @@ class OneShot {
|
|
|
2018
2150
|
if (resp.status >= 500 || resp.status === 429) {
|
|
2019
2151
|
throw new Error(`Poll returned ${resp.status}: ${body.slice(0, 200)}`);
|
|
2020
2152
|
}
|
|
2021
|
-
throw new
|
|
2153
|
+
throw new errors_2.ToolError('Failed to check job status', resp.status, body);
|
|
2022
2154
|
}
|
|
2023
2155
|
const job = await resp.json();
|
|
2024
2156
|
if (job.status === 'completed') {
|
|
@@ -2030,12 +2162,18 @@ class OneShot {
|
|
|
2030
2162
|
if (job.request_id && typeof result === 'object' && result !== null && !('request_id' in result)) {
|
|
2031
2163
|
result.request_id = job.request_id;
|
|
2032
2164
|
}
|
|
2165
|
+
if (result && typeof result === 'object' && !Array.isArray(result)) {
|
|
2166
|
+
if (job.receipt_id && !result.receipt_id)
|
|
2167
|
+
result.receipt_id = job.receipt_id;
|
|
2168
|
+
if (job.settlement_status && !result.settlement_status)
|
|
2169
|
+
result.settlement_status = job.settlement_status;
|
|
2170
|
+
}
|
|
2033
2171
|
return result;
|
|
2034
2172
|
}
|
|
2035
2173
|
if (job.status === 'failed') {
|
|
2036
2174
|
if (wait)
|
|
2037
2175
|
wait.via = 'http';
|
|
2038
|
-
throw new
|
|
2176
|
+
throw new errors_2.JobError(`Job failed: ${job.error ?? 'Unknown'}`, requestId, String(job.error ?? 'Unknown'), job.error_code);
|
|
2039
2177
|
}
|
|
2040
2178
|
emit?.(String(job.status));
|
|
2041
2179
|
retries = 0;
|
|
@@ -2049,27 +2187,27 @@ class OneShot {
|
|
|
2049
2187
|
await this.sleep(Math.min(interval, remaining), signal);
|
|
2050
2188
|
}
|
|
2051
2189
|
catch (err) {
|
|
2052
|
-
if (err instanceof
|
|
2190
|
+
if (err instanceof errors_2.OneShotError)
|
|
2053
2191
|
throw err;
|
|
2054
2192
|
if (++retries > maxRetries) {
|
|
2055
|
-
throw new
|
|
2193
|
+
throw new errors_2.OneShotError(`Polling failed after ${maxRetries} retries: ${err}`);
|
|
2056
2194
|
}
|
|
2057
2195
|
const backoff = 2000 * Math.pow(2, retries - 1);
|
|
2058
2196
|
this.log(`Retry ${retries}/${maxRetries} in ${backoff}ms`);
|
|
2059
2197
|
await this.sleep(backoff, signal);
|
|
2060
2198
|
}
|
|
2061
2199
|
}
|
|
2062
|
-
throw new
|
|
2200
|
+
throw new errors_2.JobTimeoutError(requestId, Date.now() - startTime);
|
|
2063
2201
|
}
|
|
2064
2202
|
sleep(ms, signal) {
|
|
2065
2203
|
return new Promise((resolve, reject) => {
|
|
2066
2204
|
if (signal?.aborted) {
|
|
2067
|
-
return reject(new
|
|
2205
|
+
return reject(new errors_2.OneShotError('Operation cancelled'));
|
|
2068
2206
|
}
|
|
2069
|
-
const timer = setTimeout(resolve, ms);
|
|
2207
|
+
const timer = setTimeout(() => { signal?.removeEventListener('abort', onAbort); resolve(); }, ms);
|
|
2070
2208
|
const onAbort = () => {
|
|
2071
2209
|
clearTimeout(timer);
|
|
2072
|
-
reject(new
|
|
2210
|
+
reject(new errors_2.OneShotError('Operation cancelled'));
|
|
2073
2211
|
};
|
|
2074
2212
|
signal?.addEventListener('abort', onAbort, { once: true });
|
|
2075
2213
|
});
|
|
@@ -2078,7 +2216,8 @@ class OneShot {
|
|
|
2078
2216
|
const headers = {
|
|
2079
2217
|
'Content-Type': 'application/json',
|
|
2080
2218
|
...this.headers(),
|
|
2081
|
-
...(extraHeaders ?? {})
|
|
2219
|
+
...(extraHeaders ?? {}),
|
|
2220
|
+
...(extraHeaders?.['x-agent-proof'] ? await this.signedReadHeaders(/(enrich\/(profile|email)|verify\/email)$/.test(endpoint) ? 'submit' : 'read') : {})
|
|
2082
2221
|
};
|
|
2083
2222
|
if (payment) {
|
|
2084
2223
|
const paymentJson = JSON.stringify(payment);
|
|
@@ -2089,29 +2228,27 @@ class OneShot {
|
|
|
2089
2228
|
}
|
|
2090
2229
|
if (quoteId)
|
|
2091
2230
|
headers['x-quote-id'] = quoteId;
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2231
|
+
const transportDeadline = timeoutMs === undefined ? undefined : (0, deadline_1.deadlineScope)(signal, timeoutMs);
|
|
2232
|
+
const fetchSignal = transportDeadline?.signal ?? signal;
|
|
2233
|
+
// Node 18 compatibility: do not require AbortSignal.any. Retain the deadline
|
|
2234
|
+
// through response-body consumption; cleanup occurs when it expires/aborts.
|
|
2235
|
+
transportDeadline?.signal.addEventListener('abort', () => transportDeadline.close(), { once: true });
|
|
2236
|
+
if (fetchSignal?.aborted)
|
|
2237
|
+
throw new errors_2.OneShotError('Operation cancelled');
|
|
2238
|
+
const work = fetch(`${this.baseUrl}${endpoint}`, {
|
|
2239
|
+
method: 'POST', headers, body: JSON.stringify(data), signal: fetchSignal,
|
|
2240
|
+
});
|
|
2099
2241
|
try {
|
|
2100
|
-
return await
|
|
2101
|
-
method: 'POST',
|
|
2102
|
-
headers,
|
|
2103
|
-
body: JSON.stringify(data),
|
|
2104
|
-
signal: fetchSignal
|
|
2105
|
-
});
|
|
2242
|
+
return await (fetchSignal ? (0, deadline_1.abortable)(work, fetchSignal) : work);
|
|
2106
2243
|
}
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2244
|
+
catch (error) {
|
|
2245
|
+
transportDeadline?.close();
|
|
2246
|
+
throw error;
|
|
2110
2247
|
}
|
|
2111
2248
|
}
|
|
2112
2249
|
checkAbortBeforePayment(signal) {
|
|
2113
2250
|
if (signal?.aborted) {
|
|
2114
|
-
throw new
|
|
2251
|
+
throw new errors_2.OneShotError('Operation cancelled before payment');
|
|
2115
2252
|
}
|
|
2116
2253
|
}
|
|
2117
2254
|
// ---------------------------------------------------------------------------
|
|
@@ -2159,11 +2296,11 @@ class OneShot {
|
|
|
2159
2296
|
assertEthModeSupported(paymentInfo) {
|
|
2160
2297
|
const chainId = chainIdFromNetwork(paymentInfo.network) ?? CHAIN_ID;
|
|
2161
2298
|
if (chainId !== CHAIN_ID) {
|
|
2162
|
-
throw new
|
|
2299
|
+
throw new errors_2.ValidationError(`ETH currency mode is only supported on Base mainnet (eip155:${CHAIN_ID}); this payment is on ${paymentInfo.network}. Fund the wallet with USDC or use currency: 'USDC'.`, 'currency');
|
|
2163
2300
|
}
|
|
2164
2301
|
const usdcAddress = paymentInfo.token.address;
|
|
2165
2302
|
if (usdcAddress.toLowerCase() !== USDC_ADDRESS.toLowerCase()) {
|
|
2166
|
-
throw new
|
|
2303
|
+
throw new errors_2.ValidationError(`ETH→USDC swap buys ${USDC_ADDRESS} but this payment requires ${usdcAddress}`, 'currency');
|
|
2167
2304
|
}
|
|
2168
2305
|
return { chainId, usdcAddress };
|
|
2169
2306
|
}
|
|
@@ -2207,15 +2344,18 @@ class OneShot {
|
|
|
2207
2344
|
}
|
|
2208
2345
|
/**
|
|
2209
2346
|
* How much USDC to buy: top up to `charge × swapBufferMultiplier`, counting
|
|
2210
|
-
* whatever effective balance is already there.
|
|
2211
|
-
*
|
|
2212
|
-
* wallet can still make the one
|
|
2347
|
+
* whatever effective balance is already there. When the wallet provider
|
|
2348
|
+
* exposes `getBalance` and its ETH cannot cover the buffered quote, fall
|
|
2349
|
+
* back to the bare shortfall so an ETH-poor wallet can still make the one
|
|
2350
|
+
* payment in front of it (a send-only provider always gets the buffer).
|
|
2213
2351
|
*/
|
|
2214
2352
|
async sizeSwap(charge, effective, chainId) {
|
|
2215
2353
|
const shortfall = charge - effective;
|
|
2216
|
-
// Integer math on a
|
|
2217
|
-
|
|
2218
|
-
|
|
2354
|
+
// Integer math on a 10^6 scale (multiplier ≤ 1000, so ≤ 10^9 — always a
|
|
2355
|
+
// finite, exact integer): keeps the multiplier's precision, no float→BigInt
|
|
2356
|
+
// on an unbounded value.
|
|
2357
|
+
const mScaled = BigInt(Math.round(this._swapBufferMultiplier * SWAP_MULTIPLIER_SCALE));
|
|
2358
|
+
const target = (charge * mScaled + BigInt(SWAP_MULTIPLIER_SCALE) - 1n) / BigInt(SWAP_MULTIPLIER_SCALE);
|
|
2219
2359
|
let amount = target > effective ? target - effective : shortfall;
|
|
2220
2360
|
if (amount > shortfall && this.provider.getBalance) {
|
|
2221
2361
|
try {
|
|
@@ -2245,8 +2385,8 @@ class OneShot {
|
|
|
2245
2385
|
}
|
|
2246
2386
|
/**
|
|
2247
2387
|
* Send the paid leg of a request. The server settles the authorization only
|
|
2248
|
-
* on
|
|
2249
|
-
*
|
|
2388
|
+
* on acceptance. A transport failure on a durable endpoint is ambiguous;
|
|
2389
|
+
* retain its short-lived ETH-mode reservation until recovery or expiry.
|
|
2250
2390
|
*/
|
|
2251
2391
|
async makePaidRequest(signed, endpoint, data, quoteId, signal, timeoutMs, extraHeaders) {
|
|
2252
2392
|
let resp;
|
|
@@ -2254,10 +2394,11 @@ class OneShot {
|
|
|
2254
2394
|
resp = await this.makeRequest(endpoint, data, signed.auth, quoteId, signal, timeoutMs, extraHeaders);
|
|
2255
2395
|
}
|
|
2256
2396
|
catch (err) {
|
|
2257
|
-
|
|
2397
|
+
if (!/(enrich\/(profile|email)|verify\/email)$/.test(endpoint))
|
|
2398
|
+
this.releaseUsdcReservation(signed.reservation);
|
|
2258
2399
|
throw err;
|
|
2259
2400
|
}
|
|
2260
|
-
if (!resp.ok)
|
|
2401
|
+
if (!resp.ok && !(extraHeaders?.['Idempotency-Key'] && resp.status >= 500))
|
|
2261
2402
|
this.releaseUsdcReservation(signed.reservation);
|
|
2262
2403
|
return resp;
|
|
2263
2404
|
}
|