@oneshot-agent/sdk 0.29.0 → 0.32.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 +13 -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 +44 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +282 -129
- package/dist/index.js.map +1 -1
- package/dist/physical-mail.d.ts +100 -0
- package/dist/physical-mail.d.ts.map +1 -0
- package/dist/physical-mail.js +44 -0
- package/dist/physical-mail.js.map +1 -0
- package/dist/types.d.ts +197 -3
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -37,9 +37,13 @@ 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 physical_mail_1 = require("./physical-mail");
|
|
41
|
+
__exportStar(require("./physical-mail"), exports);
|
|
42
|
+
const deadline_1 = require("./deadline");
|
|
43
|
+
const errors_1 = require("./errors");
|
|
40
44
|
const ethers_1 = require("ethers");
|
|
41
45
|
const ethers_2 = require("./providers/ethers");
|
|
42
|
-
const
|
|
46
|
+
const errors_2 = require("./errors");
|
|
43
47
|
var ethers_3 = require("./providers/ethers");
|
|
44
48
|
Object.defineProperty(exports, "EthersWalletProvider", { enumerable: true, get: function () { return ethers_3.EthersWalletProvider; } });
|
|
45
49
|
var cdp_1 = require("./providers/cdp");
|
|
@@ -49,7 +53,7 @@ Object.defineProperty(exports, "getSwapQuote", { enumerable: true, get: function
|
|
|
49
53
|
Object.defineProperty(exports, "executeSwap", { enumerable: true, get: function () { return swap_1.executeSwap; } });
|
|
50
54
|
__exportStar(require("./errors"), exports);
|
|
51
55
|
// Keep in sync with package.json `version`. Guarded by version.test.ts.
|
|
52
|
-
const SDK_VERSION = '0.
|
|
56
|
+
const SDK_VERSION = '0.32.0';
|
|
53
57
|
/** HTTP poll cadence while push is unconfirmed: fast first checks, settling at 2s. */
|
|
54
58
|
const HTTP_POLL_BACKOFF_MS = [300, 600, 1000, 2000];
|
|
55
59
|
/** HTTP poll cadence once the WebSocket has delivered for this request. */
|
|
@@ -78,12 +82,14 @@ const USDC_BALANCE_CACHE_MS = 2000;
|
|
|
78
82
|
const USDC_RESERVATION_TTL_MS = 90000;
|
|
79
83
|
const DEFAULT_SWAP_BUFFER_MULTIPLIER = 10;
|
|
80
84
|
const MAX_SWAP_BUFFER_MULTIPLIER = 1000;
|
|
85
|
+
/** Fixed-point scale for the multiplier (6 decimals, matching USDC). */
|
|
86
|
+
const SWAP_MULTIPLIER_SCALE = 1000000;
|
|
81
87
|
const ERC20_BALANCE_ABI = ['function balanceOf(address) view returns (uint256)'];
|
|
82
88
|
function validateSwapBufferMultiplier(m) {
|
|
83
89
|
if (m === undefined)
|
|
84
90
|
return DEFAULT_SWAP_BUFFER_MULTIPLIER;
|
|
85
91
|
if (typeof m !== 'number' || !Number.isFinite(m) || m < 1 || m > MAX_SWAP_BUFFER_MULTIPLIER) {
|
|
86
|
-
throw new
|
|
92
|
+
throw new errors_2.ValidationError(`swapBufferMultiplier must be a finite number between 1 and ${MAX_SWAP_BUFFER_MULTIPLIER}`, 'swapBufferMultiplier');
|
|
87
93
|
}
|
|
88
94
|
return m;
|
|
89
95
|
}
|
|
@@ -116,21 +122,21 @@ function validateBudgetConfig(budgets) {
|
|
|
116
122
|
// A typo'd key (`daliy`) from an untyped caller must not silently mean "no cap".
|
|
117
123
|
for (const key of Object.keys(budgets)) {
|
|
118
124
|
if (!['daily', 'perTransaction', 'alertAt', 'pauseAt'].includes(key)) {
|
|
119
|
-
throw new
|
|
125
|
+
throw new errors_2.ValidationError(`budgets.${key} is not a recognized field`, `budgets.${key}`);
|
|
120
126
|
}
|
|
121
127
|
}
|
|
122
128
|
const positive = (v, field) => {
|
|
123
129
|
if (v === undefined)
|
|
124
130
|
return;
|
|
125
131
|
if (typeof v !== 'number' || !Number.isFinite(v) || v <= 0) {
|
|
126
|
-
throw new
|
|
132
|
+
throw new errors_2.ValidationError(`budgets.${field} must be a positive number`, `budgets.${field}`);
|
|
127
133
|
}
|
|
128
134
|
};
|
|
129
135
|
const fraction = (v, field) => {
|
|
130
136
|
if (v === undefined)
|
|
131
137
|
return;
|
|
132
138
|
if (typeof v !== 'number' || !Number.isFinite(v) || v <= 0 || v > 1) {
|
|
133
|
-
throw new
|
|
139
|
+
throw new errors_2.ValidationError(`budgets.${field} must be a fraction in (0, 1]`, `budgets.${field}`);
|
|
134
140
|
}
|
|
135
141
|
};
|
|
136
142
|
positive(budgets.daily, 'daily');
|
|
@@ -168,13 +174,23 @@ class OneShot {
|
|
|
168
174
|
const walletProvider = new ethers_2.EthersWalletProvider(config.privateKey, rpcProvider);
|
|
169
175
|
return new OneShot(config, walletProvider);
|
|
170
176
|
}
|
|
171
|
-
throw new
|
|
177
|
+
throw new errors_2.ValidationError('Provide one of: privateKey, cdp, or walletProvider', 'config');
|
|
172
178
|
}
|
|
173
179
|
/**
|
|
174
180
|
* Sync constructor — works with privateKey (backwards compatible).
|
|
175
181
|
* For CDP wallets, use OneShot.create() instead.
|
|
176
182
|
*/
|
|
177
183
|
constructor(config, walletProvider) {
|
|
184
|
+
this.physicalMail = new physical_mail_1.PhysicalMail(async (path, method, body, mime) => {
|
|
185
|
+
const response = await fetch(`${this.baseUrl}/v1/tools/physical-mail${path}`, {
|
|
186
|
+
method, headers: { ...await this.signedReadHeaders(method === 'GET' ? 'read' : 'write'), 'Content-Type': mime ?? 'application/json' },
|
|
187
|
+
...(body === undefined ? {} : { body: mime ? body : JSON.stringify(body) }),
|
|
188
|
+
signal: AbortSignal.timeout(60000),
|
|
189
|
+
});
|
|
190
|
+
if (!response.ok)
|
|
191
|
+
await this.failFromResponse('Physical mail request failed', response);
|
|
192
|
+
return await response.json();
|
|
193
|
+
}, input => this.executeToolRequest('/v1/tools/physical-mail/send', { ...input, wait: false }));
|
|
178
194
|
/** ETH mode: signed payments not yet observed as settled, keyed by reservation id. */
|
|
179
195
|
this._usdcPending = new Map();
|
|
180
196
|
this._usdcReservationSeq = 0;
|
|
@@ -196,10 +212,10 @@ class OneShot {
|
|
|
196
212
|
this.provider = new ethers_2.EthersWalletProvider(config.privateKey, this.rpcProvider);
|
|
197
213
|
}
|
|
198
214
|
else {
|
|
199
|
-
throw new
|
|
215
|
+
throw new errors_2.ValidationError('Provide privateKey or use OneShot.create() for CDP/custom wallets', 'config');
|
|
200
216
|
}
|
|
201
217
|
if (this._currency === 'ETH' && !this.provider.sendTransaction) {
|
|
202
|
-
throw new
|
|
218
|
+
throw new errors_2.ValidationError('ETH currency mode requires a wallet provider that supports sendTransaction', 'currency');
|
|
203
219
|
}
|
|
204
220
|
if (this.debug) {
|
|
205
221
|
this.log(`SDK initialized — chain=${CHAIN_ID} currency=${this._currency}`);
|
|
@@ -311,7 +327,7 @@ class OneShot {
|
|
|
311
327
|
headers: this.headers(),
|
|
312
328
|
});
|
|
313
329
|
if (!response.ok) {
|
|
314
|
-
throw new
|
|
330
|
+
throw new errors_2.ToolError('Failed to list domains', response.status, await response.text());
|
|
315
331
|
}
|
|
316
332
|
return response.json();
|
|
317
333
|
}
|
|
@@ -323,7 +339,7 @@ class OneShot {
|
|
|
323
339
|
headers: this.headers(),
|
|
324
340
|
});
|
|
325
341
|
if (!response.ok) {
|
|
326
|
-
throw new
|
|
342
|
+
throw new errors_2.ToolError('Failed to pause domain', response.status, await response.text());
|
|
327
343
|
}
|
|
328
344
|
return response.json();
|
|
329
345
|
}
|
|
@@ -335,7 +351,7 @@ class OneShot {
|
|
|
335
351
|
headers: this.headers(),
|
|
336
352
|
});
|
|
337
353
|
if (!response.ok) {
|
|
338
|
-
throw new
|
|
354
|
+
throw new errors_2.ToolError('Failed to resume domain', response.status, await response.text());
|
|
339
355
|
}
|
|
340
356
|
return response.json();
|
|
341
357
|
}
|
|
@@ -348,7 +364,7 @@ class OneShot {
|
|
|
348
364
|
}
|
|
349
365
|
async enrichProfile(options) {
|
|
350
366
|
if (!options.linkedin_url && !options.email && !options.name) {
|
|
351
|
-
throw new
|
|
367
|
+
throw new errors_2.ValidationError('At least one of linkedin_url, email, or name is required', 'identifier');
|
|
352
368
|
}
|
|
353
369
|
return this.tool('enrich/profile', { ...options });
|
|
354
370
|
}
|
|
@@ -357,14 +373,58 @@ class OneShot {
|
|
|
357
373
|
}
|
|
358
374
|
async enrichCompany(options) {
|
|
359
375
|
if (!options.domain && !options.name && !options.linkedin_url && !options.ticker) {
|
|
360
|
-
throw new
|
|
376
|
+
throw new errors_2.ValidationError('At least one of domain, name, linkedin_url, or ticker is required', 'identifier');
|
|
361
377
|
}
|
|
362
378
|
return this.tool('enrich/company', { ...options });
|
|
363
379
|
}
|
|
380
|
+
/**
|
|
381
|
+
* Discover local businesses (restaurants, contractors, practices) by
|
|
382
|
+
* category/keywords × location. Flat price per search, not per row.
|
|
383
|
+
*/
|
|
384
|
+
async localSearch(options) {
|
|
385
|
+
if (!options.location || options.location.length === 0) {
|
|
386
|
+
throw new errors_2.ValidationError('location is required (e.g. ["Austin, TX"])', 'location');
|
|
387
|
+
}
|
|
388
|
+
if (!(options.category && options.category.length) && !(options.keywords && options.keywords.length)) {
|
|
389
|
+
throw new errors_2.ValidationError('At least one of category or keywords is required', 'category');
|
|
390
|
+
}
|
|
391
|
+
return this.tool('local/search', { ...options, limit: options.limit ?? 100 });
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Resolve a business name + one locating field to its domain, phone,
|
|
395
|
+
* category and operating status. A miss resolves with `found: false`
|
|
396
|
+
* (a completed job), never a rejection.
|
|
397
|
+
*/
|
|
398
|
+
async localResolve(options) {
|
|
399
|
+
this.validate(options.name, 'name');
|
|
400
|
+
if (!options.address && !options.city && !options.postal_code && !options.phone) {
|
|
401
|
+
throw new errors_2.ValidationError('name plus at least one of address, city, postal_code, or phone is required', 'address');
|
|
402
|
+
}
|
|
403
|
+
return this.tool('local/resolve', { ...options });
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Federal contract opportunities (SAM.gov) by NAICS code — Sources Sought
|
|
407
|
+
* and Presolicitation notices with the contracting officer's published
|
|
408
|
+
* contact. Flat price per search; zero notices is a completed result.
|
|
409
|
+
*/
|
|
410
|
+
async govSolicitations(options) {
|
|
411
|
+
if (!options.naics || options.naics.length === 0) {
|
|
412
|
+
throw new errors_2.ValidationError('naics is required (one or more 6-digit codes, e.g. ["541511"])', 'naics');
|
|
413
|
+
}
|
|
414
|
+
const bad = options.naics.find(c => !/^\d{6}$/.test(String(c)));
|
|
415
|
+
if (bad !== undefined) {
|
|
416
|
+
throw new errors_2.ValidationError(`NAICS codes are 6 digits (got ${JSON.stringify(bad)})`, 'naics');
|
|
417
|
+
}
|
|
418
|
+
return this.tool('gov/solicitations', {
|
|
419
|
+
...options,
|
|
420
|
+
notice_types: options.notice_types ?? ['r', 'p'],
|
|
421
|
+
limit: options.limit ?? 100,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
364
424
|
async findEmail(options) {
|
|
365
425
|
this.validate(options.company_domain, 'company_domain');
|
|
366
426
|
if (!options.full_name && !(options.first_name && options.last_name)) {
|
|
367
|
-
throw new
|
|
427
|
+
throw new errors_2.ValidationError('Either full_name or both first_name and last_name required', 'name');
|
|
368
428
|
}
|
|
369
429
|
return this.tool('enrich/email', { ...options });
|
|
370
430
|
}
|
|
@@ -374,13 +434,13 @@ class OneShot {
|
|
|
374
434
|
}
|
|
375
435
|
async deepResearchPerson(options) {
|
|
376
436
|
if (!options.email && !options.social_media_url && !options.name) {
|
|
377
|
-
throw new
|
|
437
|
+
throw new errors_2.ValidationError('At least one of email, social_media_url, or name is required', 'identifier');
|
|
378
438
|
}
|
|
379
439
|
return this.tool('research/person', { ...options });
|
|
380
440
|
}
|
|
381
441
|
async socialProfiles(options) {
|
|
382
442
|
if (!options.email && !options.social_media_url) {
|
|
383
|
-
throw new
|
|
443
|
+
throw new errors_2.ValidationError('At least one of email or social_media_url is required', 'identifier');
|
|
384
444
|
}
|
|
385
445
|
return this.tool('research/social', { ...options });
|
|
386
446
|
}
|
|
@@ -395,7 +455,7 @@ class OneShot {
|
|
|
395
455
|
}
|
|
396
456
|
async personInterests(options) {
|
|
397
457
|
if (!options.email && !options.phone && !options.social_media_url) {
|
|
398
|
-
throw new
|
|
458
|
+
throw new errors_2.ValidationError('At least one of email, phone, or social_media_url is required', 'identifier');
|
|
399
459
|
}
|
|
400
460
|
return this.tool('research/interests', { ...options });
|
|
401
461
|
}
|
|
@@ -413,7 +473,7 @@ class OneShot {
|
|
|
413
473
|
headers: await this.signedReadHeaders()
|
|
414
474
|
});
|
|
415
475
|
if (!response.ok) {
|
|
416
|
-
throw new
|
|
476
|
+
throw new errors_2.ToolError('Failed to list inbox', response.status, await response.text());
|
|
417
477
|
}
|
|
418
478
|
return response.json();
|
|
419
479
|
}
|
|
@@ -423,10 +483,10 @@ class OneShot {
|
|
|
423
483
|
headers: await this.signedReadHeaders()
|
|
424
484
|
});
|
|
425
485
|
if (response.status === 404) {
|
|
426
|
-
throw new
|
|
486
|
+
throw new errors_2.ToolError('Email not found', 404, 'Email not found');
|
|
427
487
|
}
|
|
428
488
|
if (!response.ok) {
|
|
429
|
-
throw new
|
|
489
|
+
throw new errors_2.ToolError('Failed to get email', response.status, await response.text());
|
|
430
490
|
}
|
|
431
491
|
return response.json();
|
|
432
492
|
}
|
|
@@ -491,13 +551,13 @@ class OneShot {
|
|
|
491
551
|
this.validate(options.objective, 'objective');
|
|
492
552
|
this.validate(options.target_number, 'target_number');
|
|
493
553
|
if (Array.isArray(options.target_number) && options.target_number.length === 0) {
|
|
494
|
-
throw new
|
|
554
|
+
throw new errors_2.ValidationError('target_number array cannot be empty', 'target_number');
|
|
495
555
|
}
|
|
496
556
|
if (options.objective.length < 10) {
|
|
497
|
-
throw new
|
|
557
|
+
throw new errors_2.ValidationError('Objective must be at least 10 characters', 'objective');
|
|
498
558
|
}
|
|
499
559
|
if (options.max_duration_minutes !== undefined && (options.max_duration_minutes < 1 || options.max_duration_minutes > 30)) {
|
|
500
|
-
throw new
|
|
560
|
+
throw new errors_2.ValidationError('max_duration_minutes must be between 1 and 30', 'max_duration_minutes');
|
|
501
561
|
}
|
|
502
562
|
const payload = {
|
|
503
563
|
objective: options.objective,
|
|
@@ -520,12 +580,12 @@ class OneShot {
|
|
|
520
580
|
on400: async (resp) => {
|
|
521
581
|
const errorData = await resp.json();
|
|
522
582
|
if (errorData.error === 'content_blocked') {
|
|
523
|
-
throw new
|
|
583
|
+
throw new errors_2.ContentBlockedError(errorData.message, errorData.categories || []);
|
|
524
584
|
}
|
|
525
585
|
if (errorData.error === 'emergency_number_blocked') {
|
|
526
|
-
throw new
|
|
586
|
+
throw new errors_2.EmergencyNumberError(errorData.message, errorData.blocked_number || '');
|
|
527
587
|
}
|
|
528
|
-
throw new
|
|
588
|
+
throw new errors_2.ValidationError(errorData.message || 'Invalid request', 'request');
|
|
529
589
|
},
|
|
530
590
|
});
|
|
531
591
|
if (callResp.status !== 202) {
|
|
@@ -554,17 +614,17 @@ class OneShot {
|
|
|
554
614
|
this.validate(options.message, 'message');
|
|
555
615
|
this.validate(options.to_number, 'to_number');
|
|
556
616
|
if (Array.isArray(options.to_number) && options.to_number.length === 0) {
|
|
557
|
-
throw new
|
|
617
|
+
throw new errors_2.ValidationError('to_number array cannot be empty', 'to_number');
|
|
558
618
|
}
|
|
559
619
|
if (options.message.length < 1) {
|
|
560
|
-
throw new
|
|
620
|
+
throw new errors_2.ValidationError('Message is required', 'message');
|
|
561
621
|
}
|
|
562
622
|
if (options.message.length > 1600) {
|
|
563
|
-
throw new
|
|
623
|
+
throw new errors_2.ValidationError('Message must be 1600 characters or less', 'message');
|
|
564
624
|
}
|
|
565
625
|
const recipientCount = Array.isArray(options.to_number) ? options.to_number.length : 1;
|
|
566
626
|
if (recipientCount > 10) {
|
|
567
|
-
throw new
|
|
627
|
+
throw new errors_2.ValidationError('Maximum 10 recipients allowed', 'to_number');
|
|
568
628
|
}
|
|
569
629
|
const payload = {
|
|
570
630
|
message: options.message,
|
|
@@ -582,12 +642,12 @@ class OneShot {
|
|
|
582
642
|
on400: async (resp) => {
|
|
583
643
|
const errorData = await resp.json();
|
|
584
644
|
if (errorData.error === 'content_blocked') {
|
|
585
|
-
throw new
|
|
645
|
+
throw new errors_2.ContentBlockedError(errorData.message, errorData.categories || []);
|
|
586
646
|
}
|
|
587
647
|
if (errorData.error === 'emergency_number_blocked') {
|
|
588
|
-
throw new
|
|
648
|
+
throw new errors_2.EmergencyNumberError(errorData.message, errorData.blocked_number || '');
|
|
589
649
|
}
|
|
590
|
-
throw new
|
|
650
|
+
throw new errors_2.ValidationError(errorData.message || 'Invalid request', 'request');
|
|
591
651
|
},
|
|
592
652
|
});
|
|
593
653
|
if (sendResp.status !== 202) {
|
|
@@ -621,7 +681,7 @@ class OneShot {
|
|
|
621
681
|
this.validate(options.product?.name, 'product.name');
|
|
622
682
|
this.validate(options.product?.description, 'product.description');
|
|
623
683
|
if (options.product.description.length < 10) {
|
|
624
|
-
throw new
|
|
684
|
+
throw new errors_2.ValidationError('Product description must be at least 10 characters', 'product.description');
|
|
625
685
|
}
|
|
626
686
|
const payload = {
|
|
627
687
|
type: options.type ?? 'saas',
|
|
@@ -656,7 +716,7 @@ class OneShot {
|
|
|
656
716
|
},
|
|
657
717
|
on400: async (resp) => {
|
|
658
718
|
const errorData = await resp.json();
|
|
659
|
-
throw new
|
|
719
|
+
throw new errors_2.ValidationError(errorData.message || 'Invalid request', 'request');
|
|
660
720
|
},
|
|
661
721
|
});
|
|
662
722
|
if (buildResp.status !== 202) {
|
|
@@ -684,10 +744,10 @@ class OneShot {
|
|
|
684
744
|
async browser(options) {
|
|
685
745
|
this.validate(options.task, 'task');
|
|
686
746
|
if (options.task.length < 10) {
|
|
687
|
-
throw new
|
|
747
|
+
throw new errors_2.ValidationError('Task must be at least 10 characters', 'task');
|
|
688
748
|
}
|
|
689
749
|
if (options.max_steps !== undefined && (options.max_steps < 1 || options.max_steps > 100)) {
|
|
690
|
-
throw new
|
|
750
|
+
throw new errors_2.ValidationError('max_steps must be between 1 and 100', 'max_steps');
|
|
691
751
|
}
|
|
692
752
|
const payload = {
|
|
693
753
|
task: options.task,
|
|
@@ -718,7 +778,7 @@ class OneShot {
|
|
|
718
778
|
onQuote: (ctx) => this.log(`Browser quote: $${ctx.estimated_cost} for ~${ctx.estimated_steps} steps`),
|
|
719
779
|
on400: async (resp) => {
|
|
720
780
|
const errorData = await resp.json();
|
|
721
|
-
throw new
|
|
781
|
+
throw new errors_2.ValidationError(errorData.message || 'Invalid request', 'request');
|
|
722
782
|
},
|
|
723
783
|
});
|
|
724
784
|
if (execResp.status !== 202) {
|
|
@@ -748,7 +808,7 @@ class OneShot {
|
|
|
748
808
|
body: JSON.stringify({ name }),
|
|
749
809
|
});
|
|
750
810
|
if (!response.ok) {
|
|
751
|
-
throw new
|
|
811
|
+
throw new errors_2.ToolError('Failed to create browser profile', response.status, await response.text());
|
|
752
812
|
}
|
|
753
813
|
return response.json();
|
|
754
814
|
}
|
|
@@ -768,7 +828,7 @@ class OneShot {
|
|
|
768
828
|
headers: await this.signedReadHeaders(),
|
|
769
829
|
});
|
|
770
830
|
if (!response.ok) {
|
|
771
|
-
throw new
|
|
831
|
+
throw new errors_2.ToolError('Failed to list browser profiles', response.status, await response.text());
|
|
772
832
|
}
|
|
773
833
|
const data = await response.json();
|
|
774
834
|
return data.profiles;
|
|
@@ -788,7 +848,7 @@ class OneShot {
|
|
|
788
848
|
headers: await this.signedReadHeaders(),
|
|
789
849
|
});
|
|
790
850
|
if (!response.ok) {
|
|
791
|
-
throw new
|
|
851
|
+
throw new errors_2.ToolError('Failed to delete browser profile', response.status, await response.text());
|
|
792
852
|
}
|
|
793
853
|
}
|
|
794
854
|
/**
|
|
@@ -834,7 +894,7 @@ class OneShot {
|
|
|
834
894
|
headers: await this.signedReadHeaders()
|
|
835
895
|
});
|
|
836
896
|
if (!response.ok) {
|
|
837
|
-
throw new
|
|
897
|
+
throw new errors_2.ToolError('Failed to list SMS inbox', response.status, await response.text());
|
|
838
898
|
}
|
|
839
899
|
return response.json();
|
|
840
900
|
}
|
|
@@ -853,10 +913,10 @@ class OneShot {
|
|
|
853
913
|
headers: await this.signedReadHeaders()
|
|
854
914
|
});
|
|
855
915
|
if (response.status === 404) {
|
|
856
|
-
throw new
|
|
916
|
+
throw new errors_2.ToolError('SMS message not found', 404, 'Message not found');
|
|
857
917
|
}
|
|
858
918
|
if (!response.ok) {
|
|
859
|
-
throw new
|
|
919
|
+
throw new errors_2.ToolError('Failed to get SMS message', response.status, await response.text());
|
|
860
920
|
}
|
|
861
921
|
return response.json();
|
|
862
922
|
}
|
|
@@ -881,7 +941,7 @@ class OneShot {
|
|
|
881
941
|
headers: await this.signedReadHeaders()
|
|
882
942
|
});
|
|
883
943
|
if (!response.ok) {
|
|
884
|
-
throw new
|
|
944
|
+
throw new errors_2.ToolError('Failed to list notifications', response.status, await response.text());
|
|
885
945
|
}
|
|
886
946
|
return response.json();
|
|
887
947
|
}
|
|
@@ -900,10 +960,10 @@ class OneShot {
|
|
|
900
960
|
headers: await this.signedReadHeaders()
|
|
901
961
|
});
|
|
902
962
|
if (response.status === 404) {
|
|
903
|
-
throw new
|
|
963
|
+
throw new errors_2.ToolError('Notification not found', 404, 'Notification not found');
|
|
904
964
|
}
|
|
905
965
|
if (!response.ok) {
|
|
906
|
-
throw new
|
|
966
|
+
throw new errors_2.ToolError('Failed to mark notification as read', response.status, await response.text());
|
|
907
967
|
}
|
|
908
968
|
}
|
|
909
969
|
async getUnifiedBalance() {
|
|
@@ -911,7 +971,7 @@ class OneShot {
|
|
|
911
971
|
headers: await this.signedReadHeaders()
|
|
912
972
|
});
|
|
913
973
|
if (!response.ok) {
|
|
914
|
-
throw new
|
|
974
|
+
throw new errors_2.ToolError('Failed to fetch balance', response.status, await response.text());
|
|
915
975
|
}
|
|
916
976
|
return response.json();
|
|
917
977
|
}
|
|
@@ -981,9 +1041,9 @@ class OneShot {
|
|
|
981
1041
|
on400: async (resp) => {
|
|
982
1042
|
const errorData = await resp.json();
|
|
983
1043
|
if (errorData.error === 'content_blocked') {
|
|
984
|
-
throw new
|
|
1044
|
+
throw new errors_2.ContentBlockedError(errorData.message, []);
|
|
985
1045
|
}
|
|
986
|
-
throw new
|
|
1046
|
+
throw new errors_2.ValidationError(errorData.message || 'Invalid request', 'request');
|
|
987
1047
|
},
|
|
988
1048
|
});
|
|
989
1049
|
if (createResp.status !== 202) {
|
|
@@ -1006,10 +1066,10 @@ class OneShot {
|
|
|
1006
1066
|
headers: this.headers()
|
|
1007
1067
|
});
|
|
1008
1068
|
if (response.status === 404) {
|
|
1009
|
-
throw new
|
|
1069
|
+
throw new errors_2.ToolError('Goal not found', 404, 'Goal not found');
|
|
1010
1070
|
}
|
|
1011
1071
|
if (!response.ok) {
|
|
1012
|
-
throw new
|
|
1072
|
+
throw new errors_2.ToolError('Failed to get compute goal', response.status, await response.text());
|
|
1013
1073
|
}
|
|
1014
1074
|
const json = await response.json();
|
|
1015
1075
|
return json.data;
|
|
@@ -1031,7 +1091,7 @@ class OneShot {
|
|
|
1031
1091
|
headers: this.headers()
|
|
1032
1092
|
});
|
|
1033
1093
|
if (!response.ok) {
|
|
1034
|
-
throw new
|
|
1094
|
+
throw new errors_2.ToolError('Failed to get compute tasks', response.status, await response.text());
|
|
1035
1095
|
}
|
|
1036
1096
|
const json = await response.json();
|
|
1037
1097
|
return json.data;
|
|
@@ -1051,10 +1111,10 @@ class OneShot {
|
|
|
1051
1111
|
headers: this.headers()
|
|
1052
1112
|
});
|
|
1053
1113
|
if (response.status === 404) {
|
|
1054
|
-
throw new
|
|
1114
|
+
throw new errors_2.ToolError('Budget not found', 404, 'Budget not found for this goal');
|
|
1055
1115
|
}
|
|
1056
1116
|
if (!response.ok) {
|
|
1057
|
-
throw new
|
|
1117
|
+
throw new errors_2.ToolError('Failed to get compute budget', response.status, await response.text());
|
|
1058
1118
|
}
|
|
1059
1119
|
const json = await response.json();
|
|
1060
1120
|
return json.data;
|
|
@@ -1076,10 +1136,10 @@ class OneShot {
|
|
|
1076
1136
|
body: JSON.stringify({ reason })
|
|
1077
1137
|
});
|
|
1078
1138
|
if (response.status === 404) {
|
|
1079
|
-
throw new
|
|
1139
|
+
throw new errors_2.ToolError('Goal not found', 404, 'Goal not found');
|
|
1080
1140
|
}
|
|
1081
1141
|
if (!response.ok) {
|
|
1082
|
-
throw new
|
|
1142
|
+
throw new errors_2.ToolError('Failed to cancel compute goal', response.status, await response.text());
|
|
1083
1143
|
}
|
|
1084
1144
|
const json = await response.json();
|
|
1085
1145
|
return json.data;
|
|
@@ -1105,10 +1165,10 @@ class OneShot {
|
|
|
1105
1165
|
body: JSON.stringify(input)
|
|
1106
1166
|
});
|
|
1107
1167
|
if (response.status === 404) {
|
|
1108
|
-
throw new
|
|
1168
|
+
throw new errors_2.ToolError('Goal or task not found', 404, await response.text());
|
|
1109
1169
|
}
|
|
1110
1170
|
if (!response.ok) {
|
|
1111
|
-
throw new
|
|
1171
|
+
throw new errors_2.ToolError('Failed to respond to compute task', response.status, await response.text());
|
|
1112
1172
|
}
|
|
1113
1173
|
const json = await response.json();
|
|
1114
1174
|
return json.data;
|
|
@@ -1129,7 +1189,7 @@ class OneShot {
|
|
|
1129
1189
|
body: JSON.stringify({ reason })
|
|
1130
1190
|
});
|
|
1131
1191
|
if (!response.ok) {
|
|
1132
|
-
throw new
|
|
1192
|
+
throw new errors_2.ToolError('Failed to pause compute goal', response.status, await response.text());
|
|
1133
1193
|
}
|
|
1134
1194
|
const json = await response.json();
|
|
1135
1195
|
return json.data;
|
|
@@ -1151,7 +1211,7 @@ class OneShot {
|
|
|
1151
1211
|
body: JSON.stringify({})
|
|
1152
1212
|
});
|
|
1153
1213
|
if (!response.ok) {
|
|
1154
|
-
throw new
|
|
1214
|
+
throw new errors_2.ToolError('Failed to resume compute goal', response.status, await response.text());
|
|
1155
1215
|
}
|
|
1156
1216
|
const json = await response.json();
|
|
1157
1217
|
return json.data;
|
|
@@ -1171,7 +1231,7 @@ class OneShot {
|
|
|
1171
1231
|
async fundComputeGoal(goalId, amount) {
|
|
1172
1232
|
this.validate(goalId, 'goalId');
|
|
1173
1233
|
if (!amount || amount <= 0) {
|
|
1174
|
-
throw new
|
|
1234
|
+
throw new errors_2.ValidationError('amount must be a positive number', 'amount');
|
|
1175
1235
|
}
|
|
1176
1236
|
const path = `/v1/compute/${goalId}/fund`;
|
|
1177
1237
|
const payload = { amount };
|
|
@@ -1224,7 +1284,7 @@ class OneShot {
|
|
|
1224
1284
|
headers: this.headers()
|
|
1225
1285
|
});
|
|
1226
1286
|
if (!response.ok) {
|
|
1227
|
-
throw new
|
|
1287
|
+
throw new errors_2.ToolError('Failed to get spend breakdown', response.status, await response.text());
|
|
1228
1288
|
}
|
|
1229
1289
|
return response.json();
|
|
1230
1290
|
}
|
|
@@ -1243,7 +1303,7 @@ class OneShot {
|
|
|
1243
1303
|
headers: this.headers()
|
|
1244
1304
|
});
|
|
1245
1305
|
if (!response.ok) {
|
|
1246
|
-
throw new
|
|
1306
|
+
throw new errors_2.ToolError('Failed to get RoCS', response.status, await response.text());
|
|
1247
1307
|
}
|
|
1248
1308
|
return response.json();
|
|
1249
1309
|
}
|
|
@@ -1279,7 +1339,7 @@ class OneShot {
|
|
|
1279
1339
|
headers: this.headers()
|
|
1280
1340
|
});
|
|
1281
1341
|
if (!response.ok) {
|
|
1282
|
-
throw new
|
|
1342
|
+
throw new errors_2.ToolError('Failed to list receipts', response.status, await response.text());
|
|
1283
1343
|
}
|
|
1284
1344
|
return response.json();
|
|
1285
1345
|
}
|
|
@@ -1318,7 +1378,7 @@ class OneShot {
|
|
|
1318
1378
|
body: JSON.stringify({ goal_id: ref.goalId, ...valueTag }),
|
|
1319
1379
|
});
|
|
1320
1380
|
if (!response.ok) {
|
|
1321
|
-
throw new
|
|
1381
|
+
throw new errors_2.ToolError('Failed to record outcome value', response.status, await response.text());
|
|
1322
1382
|
}
|
|
1323
1383
|
return;
|
|
1324
1384
|
}
|
|
@@ -1330,10 +1390,10 @@ class OneShot {
|
|
|
1330
1390
|
body: JSON.stringify(valueTag)
|
|
1331
1391
|
});
|
|
1332
1392
|
if (response.status === 404) {
|
|
1333
|
-
throw new
|
|
1393
|
+
throw new errors_2.ToolError('Receipt not found', 404, 'Receipt not found or not owned by this agent');
|
|
1334
1394
|
}
|
|
1335
1395
|
if (!response.ok) {
|
|
1336
|
-
throw new
|
|
1396
|
+
throw new errors_2.ToolError('Failed to tag receipt value', response.status, await response.text());
|
|
1337
1397
|
}
|
|
1338
1398
|
}
|
|
1339
1399
|
/**
|
|
@@ -1359,7 +1419,7 @@ class OneShot {
|
|
|
1359
1419
|
headers: this.headers()
|
|
1360
1420
|
});
|
|
1361
1421
|
if (!response.ok) {
|
|
1362
|
-
throw new
|
|
1422
|
+
throw new errors_2.ToolError('Failed to get RoCS by goal', response.status, await response.text());
|
|
1363
1423
|
}
|
|
1364
1424
|
return response.json();
|
|
1365
1425
|
}
|
|
@@ -1372,7 +1432,7 @@ class OneShot {
|
|
|
1372
1432
|
}
|
|
1373
1433
|
validate(value, field) {
|
|
1374
1434
|
if (!value)
|
|
1375
|
-
throw new
|
|
1435
|
+
throw new errors_2.ValidationError(`${field} is required`, field);
|
|
1376
1436
|
}
|
|
1377
1437
|
headers() {
|
|
1378
1438
|
return {
|
|
@@ -1466,11 +1526,11 @@ class OneShot {
|
|
|
1466
1526
|
});
|
|
1467
1527
|
}
|
|
1468
1528
|
catch (err) {
|
|
1469
|
-
throw new
|
|
1529
|
+
throw new errors_2.BudgetSyncError(`Could not sync spend budget (network): ${err}`);
|
|
1470
1530
|
}
|
|
1471
1531
|
if (!response.ok) {
|
|
1472
1532
|
const text = await response.text();
|
|
1473
|
-
throw new
|
|
1533
|
+
throw new errors_2.BudgetSyncError(`Could not sync spend budget (${response.status}): ${text}`, response.status, text);
|
|
1474
1534
|
}
|
|
1475
1535
|
this.log('Budget synced');
|
|
1476
1536
|
})();
|
|
@@ -1498,7 +1558,7 @@ class OneShot {
|
|
|
1498
1558
|
return;
|
|
1499
1559
|
const amount = parseFloat(total);
|
|
1500
1560
|
if (Number.isFinite(amount) && amount > cap) {
|
|
1501
|
-
throw new
|
|
1561
|
+
throw new errors_2.BudgetExceededError(`Quote $${total} exceeds this agent's per-transaction budget of $${cap}`, 'per_transaction', cap, undefined, amount);
|
|
1502
1562
|
}
|
|
1503
1563
|
}
|
|
1504
1564
|
/**
|
|
@@ -1515,7 +1575,7 @@ class OneShot {
|
|
|
1515
1575
|
headers: await this.signedReadHeaders(),
|
|
1516
1576
|
});
|
|
1517
1577
|
if (!response.ok) {
|
|
1518
|
-
throw new
|
|
1578
|
+
throw new errors_2.ToolError('Failed to fetch budgets', response.status, await response.text());
|
|
1519
1579
|
}
|
|
1520
1580
|
const body = await response.json();
|
|
1521
1581
|
return (body.data ?? body);
|
|
@@ -1523,7 +1583,7 @@ class OneShot {
|
|
|
1523
1583
|
/** Local fast-fail guard: throw when a quote total exceeds the caller's cap. */
|
|
1524
1584
|
assertWithinMaxCost(total, maxCost) {
|
|
1525
1585
|
if (maxCost && parseFloat(total) > maxCost) {
|
|
1526
|
-
throw new
|
|
1586
|
+
throw new errors_2.OneShotError(`Quote $${total} exceeds maxCost $${maxCost}`);
|
|
1527
1587
|
}
|
|
1528
1588
|
}
|
|
1529
1589
|
/**
|
|
@@ -1569,7 +1629,7 @@ class OneShot {
|
|
|
1569
1629
|
const budget = response.status === 403 ? this.parseBudgetRejection(text) : undefined;
|
|
1570
1630
|
if (budget)
|
|
1571
1631
|
throw budget;
|
|
1572
|
-
throw new
|
|
1632
|
+
throw new errors_2.ToolError(message, response.status, text);
|
|
1573
1633
|
}
|
|
1574
1634
|
/**
|
|
1575
1635
|
* Map a 403 `budget_exceeded` body onto a typed error, so callers can catch
|
|
@@ -1582,7 +1642,7 @@ class OneShot {
|
|
|
1582
1642
|
if (body.error !== 'budget_exceeded')
|
|
1583
1643
|
return undefined;
|
|
1584
1644
|
const b = body.budget ?? {};
|
|
1585
|
-
return new
|
|
1645
|
+
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
1646
|
}
|
|
1587
1647
|
catch {
|
|
1588
1648
|
return undefined;
|
|
@@ -1605,7 +1665,7 @@ class OneShot {
|
|
|
1605
1665
|
expectedAmount ? `expected $${expectedAmount}` : null,
|
|
1606
1666
|
receivedAmount ? `signed $${receivedAmount}` : null,
|
|
1607
1667
|
].filter(Boolean).join(', ');
|
|
1608
|
-
return new
|
|
1668
|
+
return new errors_2.PaymentError(`payment rejected: ${reason}${detail ? ` — ${detail}` : ''}${data.message ? ` (${data.message})` : ''}`, reason, {
|
|
1609
1669
|
amount: expectedAmount,
|
|
1610
1670
|
asset: data.expected?.asset,
|
|
1611
1671
|
network: data.expected?.network,
|
|
@@ -1641,8 +1701,59 @@ class OneShot {
|
|
|
1641
1701
|
return undefined;
|
|
1642
1702
|
return { 'Idempotency-Key': idempotencyKey };
|
|
1643
1703
|
}
|
|
1704
|
+
async readReliabilityJson(path, signed, allowDegraded = false) {
|
|
1705
|
+
const scope = (0, deadline_1.deadlineScope)(undefined, signed ? 10000 : 5000);
|
|
1706
|
+
try {
|
|
1707
|
+
return await (0, deadline_1.abortable)((async () => {
|
|
1708
|
+
const headers = signed ? await this.signedReadHeaders() : undefined;
|
|
1709
|
+
if (scope.signal.aborted)
|
|
1710
|
+
throw new errors_2.OneShotError('Read deadline exceeded');
|
|
1711
|
+
const response = await fetch(`${this.baseUrl}${path}`, { headers, signal: scope.signal });
|
|
1712
|
+
if (!response.ok && !(allowDegraded && response.status === 503))
|
|
1713
|
+
await this.failFromResponse('Reliability read failed', response);
|
|
1714
|
+
return await response.json();
|
|
1715
|
+
})(), scope.signal);
|
|
1716
|
+
}
|
|
1717
|
+
finally {
|
|
1718
|
+
scope.close();
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
async recoverRequest(options) {
|
|
1722
|
+
const query = new URLSearchParams({ endpoint: options.endpoint, key: options.idempotencyKey });
|
|
1723
|
+
return this.readReliabilityJson(`/v1/submissions/recover?${query}`, true);
|
|
1724
|
+
}
|
|
1725
|
+
async getServiceStatus() {
|
|
1726
|
+
return this.readReliabilityJson('/v1/status', false, true);
|
|
1727
|
+
}
|
|
1644
1728
|
async executeToolRequest(endpoint, options, quoteId) {
|
|
1645
|
-
const
|
|
1729
|
+
const reliable = /(?:^|\/)(enrich\/(profile|email)|verify\/email)$/.test(endpoint);
|
|
1730
|
+
const key = options.idempotencyKey ?? (reliable ? ethers_1.ethers.hexlify(ethers_1.ethers.randomBytes(16)) : undefined);
|
|
1731
|
+
if (options.totalTimeoutMs !== undefined && (!Number.isFinite(options.totalTimeoutMs) || options.totalTimeoutMs <= 0)) {
|
|
1732
|
+
throw new errors_2.ValidationError('totalTimeoutMs must be positive', 'totalTimeoutMs');
|
|
1733
|
+
}
|
|
1734
|
+
const scope = (0, deadline_1.deadlineScope)(options.signal, options.totalTimeoutMs);
|
|
1735
|
+
const context = { phase: 'initialization' };
|
|
1736
|
+
const started = Date.now();
|
|
1737
|
+
try {
|
|
1738
|
+
if (scope.signal.aborted)
|
|
1739
|
+
throw new errors_2.OneShotError('Operation cancelled');
|
|
1740
|
+
if (key)
|
|
1741
|
+
options.onRequestCreated?.({ idempotencyKey: key });
|
|
1742
|
+
return await (0, deadline_1.abortable)(this.executeToolRequestImpl(endpoint, { ...options, idempotencyKey: key, signal: scope.signal }, quoteId, context), scope.signal);
|
|
1743
|
+
}
|
|
1744
|
+
catch (error) {
|
|
1745
|
+
if (scope.timedOut())
|
|
1746
|
+
throw new errors_1.RequestTimeoutError(Date.now() - started, context.phase, key, context.requestId, context.receiptId);
|
|
1747
|
+
if (error instanceof Error)
|
|
1748
|
+
Object.assign(error, { idempotencyKey: key, requestId: context.requestId, receiptId: context.receiptId, phase: context.phase });
|
|
1749
|
+
throw error;
|
|
1750
|
+
}
|
|
1751
|
+
finally {
|
|
1752
|
+
scope.close();
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
async executeToolRequestImpl(endpoint, options, quoteId, context = { phase: "initialization" }) {
|
|
1756
|
+
const { totalTimeoutMs, onRequestCreated, onAccepted, signal, onStatusUpdate, wait = true, waitForPhones, phoneTimeoutSec, idempotencyKey, maxCost, ...payload } = options;
|
|
1646
1757
|
const extraHeaders = {
|
|
1647
1758
|
...this.maxCostHeader(maxCost),
|
|
1648
1759
|
...this.idempotencyHeader(idempotencyKey),
|
|
@@ -1671,11 +1782,18 @@ class OneShot {
|
|
|
1671
1782
|
}
|
|
1672
1783
|
}
|
|
1673
1784
|
if (signal?.aborted) {
|
|
1674
|
-
throw new
|
|
1785
|
+
throw new errors_2.OneShotError('Operation cancelled');
|
|
1675
1786
|
}
|
|
1676
1787
|
// One-time push of config.budgets before the first paid call, so the
|
|
1677
1788
|
// server-side gate knows about them on this very request.
|
|
1678
1789
|
await this.ensureBudgetsSynced();
|
|
1790
|
+
if (signal?.aborted)
|
|
1791
|
+
throw new errors_2.OneShotError('Operation cancelled');
|
|
1792
|
+
if (idempotencyKey)
|
|
1793
|
+
Object.assign(extraHeaders, { 'x-agent-proof': 'required' });
|
|
1794
|
+
if (signal?.aborted)
|
|
1795
|
+
throw new errors_2.OneShotError('Operation cancelled');
|
|
1796
|
+
context.phase = 'submission';
|
|
1679
1797
|
let response = await this.makeRequest(endpoint, payload, undefined, quoteId, signal, undefined, extraHeaders);
|
|
1680
1798
|
// Handle 402 Payment Required
|
|
1681
1799
|
if (response.status === 402) {
|
|
@@ -1696,7 +1814,10 @@ class OneShot {
|
|
|
1696
1814
|
this.log(`Payment required: ${paymentInfo.amount} USDC`);
|
|
1697
1815
|
this.assertWithinBudget(paymentInfo.amount);
|
|
1698
1816
|
this.checkAbortBeforePayment(signal);
|
|
1817
|
+
context.phase = 'payment';
|
|
1699
1818
|
const signed = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
|
|
1819
|
+
this.checkAbortBeforePayment(signal);
|
|
1820
|
+
context.phase = 'submission';
|
|
1700
1821
|
response = await this.makePaidRequest(signed, endpoint, payload, quoteId, signal, undefined, extraHeaders);
|
|
1701
1822
|
}
|
|
1702
1823
|
if (!response.ok) {
|
|
@@ -1705,11 +1826,19 @@ class OneShot {
|
|
|
1705
1826
|
const result = await response.json();
|
|
1706
1827
|
// Handle async jobs
|
|
1707
1828
|
if ((result.status === 'pending' || result.status === 'processing') && result.request_id) {
|
|
1829
|
+
context.requestId = String(result.request_id);
|
|
1830
|
+
context.receiptId = typeof result.receipt_id === 'string' ? result.receipt_id : undefined;
|
|
1831
|
+
onAccepted?.({ request_id: context.requestId, receipt_id: context.receiptId, idempotencyKey });
|
|
1832
|
+
context.phase = 'polling';
|
|
1708
1833
|
this.log(`Job queued: ${result.request_id}`);
|
|
1709
1834
|
if (!wait) {
|
|
1710
|
-
return {
|
|
1835
|
+
return { ...result, idempotencyKey };
|
|
1836
|
+
}
|
|
1837
|
+
const completed = await this.pollJob(result.request_id, options.timeout, signal, onStatusUpdate, waitForPhones ? { waitForPhones, phoneTimeoutSec } : undefined);
|
|
1838
|
+
if (completed && typeof completed === 'object' && !Array.isArray(completed)) {
|
|
1839
|
+
return { ...completed, request_id: context.requestId, receipt_id: completed.receipt_id ?? context.receiptId, idempotencyKey };
|
|
1711
1840
|
}
|
|
1712
|
-
return
|
|
1841
|
+
return completed;
|
|
1713
1842
|
}
|
|
1714
1843
|
return (result.data ?? result);
|
|
1715
1844
|
}
|
|
@@ -1788,7 +1917,7 @@ class OneShot {
|
|
|
1788
1917
|
const wsBranch = this.waitViaWebSocket(requestId, inner.signal, emit, wait).catch((err) => {
|
|
1789
1918
|
// A failed job or a cancellation is a real outcome. Anything else is a
|
|
1790
1919
|
// transport problem: never settle the race on it, HTTP carries on.
|
|
1791
|
-
if (err instanceof
|
|
1920
|
+
if (err instanceof errors_2.OneShotError)
|
|
1792
1921
|
throw err;
|
|
1793
1922
|
this.log(`WebSocket unavailable (${err instanceof Error ? err.message : String(err)}) — relying on HTTP polling`);
|
|
1794
1923
|
return new Promise(() => { });
|
|
@@ -1858,7 +1987,7 @@ class OneShot {
|
|
|
1858
1987
|
let lastResult = initialResult;
|
|
1859
1988
|
while (Date.now() < deadline) {
|
|
1860
1989
|
if (signal?.aborted)
|
|
1861
|
-
throw new
|
|
1990
|
+
throw new errors_2.OneShotError('Operation cancelled');
|
|
1862
1991
|
try {
|
|
1863
1992
|
const resp = await fetch(`${this.baseUrl}/v1/requests/${requestId}`, {
|
|
1864
1993
|
headers: this.headers(),
|
|
@@ -1869,7 +1998,7 @@ class OneShot {
|
|
|
1869
1998
|
// whatever we last had so consumers don't lose the sync result.
|
|
1870
1999
|
if (lastResult !== undefined)
|
|
1871
2000
|
return lastResult;
|
|
1872
|
-
throw new
|
|
2001
|
+
throw new errors_2.ToolError('Failed to check job status', resp.status, await resp.text());
|
|
1873
2002
|
}
|
|
1874
2003
|
const job = await resp.json();
|
|
1875
2004
|
lastResult = (job.result ?? job);
|
|
@@ -1878,7 +2007,7 @@ class OneShot {
|
|
|
1878
2007
|
}
|
|
1879
2008
|
}
|
|
1880
2009
|
catch (err) {
|
|
1881
|
-
if (err instanceof
|
|
2010
|
+
if (err instanceof errors_2.OneShotError)
|
|
1882
2011
|
throw err;
|
|
1883
2012
|
if (lastResult !== undefined)
|
|
1884
2013
|
return lastResult;
|
|
@@ -1904,7 +2033,7 @@ class OneShot {
|
|
|
1904
2033
|
return reject(new Error('WebSocket not available'));
|
|
1905
2034
|
}
|
|
1906
2035
|
if (signal.aborted) {
|
|
1907
|
-
return reject(new
|
|
2036
|
+
return reject(new errors_2.OneShotError('Operation cancelled'));
|
|
1908
2037
|
}
|
|
1909
2038
|
const wsUrl = this.baseUrl.replace(/^http/, 'ws') +
|
|
1910
2039
|
`/v1/requests/subscribe?wallet=${encodeURIComponent(this.provider.address)}`;
|
|
@@ -1932,7 +2061,7 @@ class OneShot {
|
|
|
1932
2061
|
const onAbort = () => {
|
|
1933
2062
|
settle(() => {
|
|
1934
2063
|
cleanup();
|
|
1935
|
-
reject(new
|
|
2064
|
+
reject(new errors_2.OneShotError('Operation cancelled'));
|
|
1936
2065
|
});
|
|
1937
2066
|
};
|
|
1938
2067
|
signal.addEventListener('abort', onAbort, { once: true });
|
|
@@ -1965,7 +2094,7 @@ class OneShot {
|
|
|
1965
2094
|
settle(() => {
|
|
1966
2095
|
cleanup();
|
|
1967
2096
|
wait.via = 'ws';
|
|
1968
|
-
reject(new
|
|
2097
|
+
reject(new errors_2.JobError(`Job failed: ${msg.error ?? 'Unknown'}`, requestId, String(msg.error ?? 'Unknown'), msg.error_code));
|
|
1969
2098
|
});
|
|
1970
2099
|
}
|
|
1971
2100
|
else {
|
|
@@ -1998,6 +2127,21 @@ class OneShot {
|
|
|
1998
2127
|
* Owns the caller's deadline (`JobTimeoutError`).
|
|
1999
2128
|
*/
|
|
2000
2129
|
async pollJobHttp(requestId, timeoutSec, signal, emit, wait) {
|
|
2130
|
+
const scope = (0, deadline_1.deadlineScope)(signal, (timeoutSec ?? 120) * 1000);
|
|
2131
|
+
const started = Date.now();
|
|
2132
|
+
try {
|
|
2133
|
+
return await (0, deadline_1.abortable)(this.pollJobHttpImpl(requestId, timeoutSec, scope.signal, emit, wait), scope.signal);
|
|
2134
|
+
}
|
|
2135
|
+
catch (error) {
|
|
2136
|
+
if (scope.timedOut())
|
|
2137
|
+
throw new errors_2.JobTimeoutError(requestId, Date.now() - started);
|
|
2138
|
+
throw error;
|
|
2139
|
+
}
|
|
2140
|
+
finally {
|
|
2141
|
+
scope.close();
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
async pollJobHttpImpl(requestId, timeoutSec, signal, emit, wait) {
|
|
2001
2145
|
const maxWaitMs = (timeoutSec ?? 120) * 1000;
|
|
2002
2146
|
const startTime = Date.now();
|
|
2003
2147
|
let retries = 0;
|
|
@@ -2005,7 +2149,7 @@ class OneShot {
|
|
|
2005
2149
|
let polls = 0;
|
|
2006
2150
|
while (Date.now() - startTime < maxWaitMs) {
|
|
2007
2151
|
if (signal?.aborted)
|
|
2008
|
-
throw new
|
|
2152
|
+
throw new errors_2.OneShotError('Operation cancelled');
|
|
2009
2153
|
try {
|
|
2010
2154
|
const resp = await fetch(`${this.baseUrl}/v1/requests/${requestId}`, {
|
|
2011
2155
|
headers: this.headers(),
|
|
@@ -2018,7 +2162,7 @@ class OneShot {
|
|
|
2018
2162
|
if (resp.status >= 500 || resp.status === 429) {
|
|
2019
2163
|
throw new Error(`Poll returned ${resp.status}: ${body.slice(0, 200)}`);
|
|
2020
2164
|
}
|
|
2021
|
-
throw new
|
|
2165
|
+
throw new errors_2.ToolError('Failed to check job status', resp.status, body);
|
|
2022
2166
|
}
|
|
2023
2167
|
const job = await resp.json();
|
|
2024
2168
|
if (job.status === 'completed') {
|
|
@@ -2030,12 +2174,18 @@ class OneShot {
|
|
|
2030
2174
|
if (job.request_id && typeof result === 'object' && result !== null && !('request_id' in result)) {
|
|
2031
2175
|
result.request_id = job.request_id;
|
|
2032
2176
|
}
|
|
2177
|
+
if (result && typeof result === 'object' && !Array.isArray(result)) {
|
|
2178
|
+
if (job.receipt_id && !result.receipt_id)
|
|
2179
|
+
result.receipt_id = job.receipt_id;
|
|
2180
|
+
if (job.settlement_status && !result.settlement_status)
|
|
2181
|
+
result.settlement_status = job.settlement_status;
|
|
2182
|
+
}
|
|
2033
2183
|
return result;
|
|
2034
2184
|
}
|
|
2035
2185
|
if (job.status === 'failed') {
|
|
2036
2186
|
if (wait)
|
|
2037
2187
|
wait.via = 'http';
|
|
2038
|
-
throw new
|
|
2188
|
+
throw new errors_2.JobError(`Job failed: ${job.error ?? 'Unknown'}`, requestId, String(job.error ?? 'Unknown'), job.error_code);
|
|
2039
2189
|
}
|
|
2040
2190
|
emit?.(String(job.status));
|
|
2041
2191
|
retries = 0;
|
|
@@ -2049,27 +2199,27 @@ class OneShot {
|
|
|
2049
2199
|
await this.sleep(Math.min(interval, remaining), signal);
|
|
2050
2200
|
}
|
|
2051
2201
|
catch (err) {
|
|
2052
|
-
if (err instanceof
|
|
2202
|
+
if (err instanceof errors_2.OneShotError)
|
|
2053
2203
|
throw err;
|
|
2054
2204
|
if (++retries > maxRetries) {
|
|
2055
|
-
throw new
|
|
2205
|
+
throw new errors_2.OneShotError(`Polling failed after ${maxRetries} retries: ${err}`);
|
|
2056
2206
|
}
|
|
2057
2207
|
const backoff = 2000 * Math.pow(2, retries - 1);
|
|
2058
2208
|
this.log(`Retry ${retries}/${maxRetries} in ${backoff}ms`);
|
|
2059
2209
|
await this.sleep(backoff, signal);
|
|
2060
2210
|
}
|
|
2061
2211
|
}
|
|
2062
|
-
throw new
|
|
2212
|
+
throw new errors_2.JobTimeoutError(requestId, Date.now() - startTime);
|
|
2063
2213
|
}
|
|
2064
2214
|
sleep(ms, signal) {
|
|
2065
2215
|
return new Promise((resolve, reject) => {
|
|
2066
2216
|
if (signal?.aborted) {
|
|
2067
|
-
return reject(new
|
|
2217
|
+
return reject(new errors_2.OneShotError('Operation cancelled'));
|
|
2068
2218
|
}
|
|
2069
|
-
const timer = setTimeout(resolve, ms);
|
|
2219
|
+
const timer = setTimeout(() => { signal?.removeEventListener('abort', onAbort); resolve(); }, ms);
|
|
2070
2220
|
const onAbort = () => {
|
|
2071
2221
|
clearTimeout(timer);
|
|
2072
|
-
reject(new
|
|
2222
|
+
reject(new errors_2.OneShotError('Operation cancelled'));
|
|
2073
2223
|
};
|
|
2074
2224
|
signal?.addEventListener('abort', onAbort, { once: true });
|
|
2075
2225
|
});
|
|
@@ -2078,7 +2228,8 @@ class OneShot {
|
|
|
2078
2228
|
const headers = {
|
|
2079
2229
|
'Content-Type': 'application/json',
|
|
2080
2230
|
...this.headers(),
|
|
2081
|
-
...(extraHeaders ?? {})
|
|
2231
|
+
...(extraHeaders ?? {}),
|
|
2232
|
+
...(extraHeaders?.['x-agent-proof'] ? await this.signedReadHeaders(/(enrich\/(profile|email)|verify\/email|physical-mail\/send)$/.test(endpoint) ? 'submit' : 'read') : {})
|
|
2082
2233
|
};
|
|
2083
2234
|
if (payment) {
|
|
2084
2235
|
const paymentJson = JSON.stringify(payment);
|
|
@@ -2089,29 +2240,27 @@ class OneShot {
|
|
|
2089
2240
|
}
|
|
2090
2241
|
if (quoteId)
|
|
2091
2242
|
headers['x-quote-id'] = quoteId;
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2243
|
+
const transportDeadline = timeoutMs === undefined ? undefined : (0, deadline_1.deadlineScope)(signal, timeoutMs);
|
|
2244
|
+
const fetchSignal = transportDeadline?.signal ?? signal;
|
|
2245
|
+
// Node 18 compatibility: do not require AbortSignal.any. Retain the deadline
|
|
2246
|
+
// through response-body consumption; cleanup occurs when it expires/aborts.
|
|
2247
|
+
transportDeadline?.signal.addEventListener('abort', () => transportDeadline.close(), { once: true });
|
|
2248
|
+
if (fetchSignal?.aborted)
|
|
2249
|
+
throw new errors_2.OneShotError('Operation cancelled');
|
|
2250
|
+
const work = fetch(`${this.baseUrl}${endpoint}`, {
|
|
2251
|
+
method: 'POST', headers, body: JSON.stringify(data), signal: fetchSignal,
|
|
2252
|
+
});
|
|
2099
2253
|
try {
|
|
2100
|
-
return await
|
|
2101
|
-
method: 'POST',
|
|
2102
|
-
headers,
|
|
2103
|
-
body: JSON.stringify(data),
|
|
2104
|
-
signal: fetchSignal
|
|
2105
|
-
});
|
|
2254
|
+
return await (fetchSignal ? (0, deadline_1.abortable)(work, fetchSignal) : work);
|
|
2106
2255
|
}
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2256
|
+
catch (error) {
|
|
2257
|
+
transportDeadline?.close();
|
|
2258
|
+
throw error;
|
|
2110
2259
|
}
|
|
2111
2260
|
}
|
|
2112
2261
|
checkAbortBeforePayment(signal) {
|
|
2113
2262
|
if (signal?.aborted) {
|
|
2114
|
-
throw new
|
|
2263
|
+
throw new errors_2.OneShotError('Operation cancelled before payment');
|
|
2115
2264
|
}
|
|
2116
2265
|
}
|
|
2117
2266
|
// ---------------------------------------------------------------------------
|
|
@@ -2159,11 +2308,11 @@ class OneShot {
|
|
|
2159
2308
|
assertEthModeSupported(paymentInfo) {
|
|
2160
2309
|
const chainId = chainIdFromNetwork(paymentInfo.network) ?? CHAIN_ID;
|
|
2161
2310
|
if (chainId !== CHAIN_ID) {
|
|
2162
|
-
throw new
|
|
2311
|
+
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
2312
|
}
|
|
2164
2313
|
const usdcAddress = paymentInfo.token.address;
|
|
2165
2314
|
if (usdcAddress.toLowerCase() !== USDC_ADDRESS.toLowerCase()) {
|
|
2166
|
-
throw new
|
|
2315
|
+
throw new errors_2.ValidationError(`ETH→USDC swap buys ${USDC_ADDRESS} but this payment requires ${usdcAddress}`, 'currency');
|
|
2167
2316
|
}
|
|
2168
2317
|
return { chainId, usdcAddress };
|
|
2169
2318
|
}
|
|
@@ -2207,15 +2356,18 @@ class OneShot {
|
|
|
2207
2356
|
}
|
|
2208
2357
|
/**
|
|
2209
2358
|
* 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
|
|
2359
|
+
* whatever effective balance is already there. When the wallet provider
|
|
2360
|
+
* exposes `getBalance` and its ETH cannot cover the buffered quote, fall
|
|
2361
|
+
* back to the bare shortfall so an ETH-poor wallet can still make the one
|
|
2362
|
+
* payment in front of it (a send-only provider always gets the buffer).
|
|
2213
2363
|
*/
|
|
2214
2364
|
async sizeSwap(charge, effective, chainId) {
|
|
2215
2365
|
const shortfall = charge - effective;
|
|
2216
|
-
// Integer math on a
|
|
2217
|
-
|
|
2218
|
-
|
|
2366
|
+
// Integer math on a 10^6 scale (multiplier ≤ 1000, so ≤ 10^9 — always a
|
|
2367
|
+
// finite, exact integer): keeps the multiplier's precision, no float→BigInt
|
|
2368
|
+
// on an unbounded value.
|
|
2369
|
+
const mScaled = BigInt(Math.round(this._swapBufferMultiplier * SWAP_MULTIPLIER_SCALE));
|
|
2370
|
+
const target = (charge * mScaled + BigInt(SWAP_MULTIPLIER_SCALE) - 1n) / BigInt(SWAP_MULTIPLIER_SCALE);
|
|
2219
2371
|
let amount = target > effective ? target - effective : shortfall;
|
|
2220
2372
|
if (amount > shortfall && this.provider.getBalance) {
|
|
2221
2373
|
try {
|
|
@@ -2245,8 +2397,8 @@ class OneShot {
|
|
|
2245
2397
|
}
|
|
2246
2398
|
/**
|
|
2247
2399
|
* Send the paid leg of a request. The server settles the authorization only
|
|
2248
|
-
* on
|
|
2249
|
-
*
|
|
2400
|
+
* on acceptance. A transport failure on a durable endpoint is ambiguous;
|
|
2401
|
+
* retain its short-lived ETH-mode reservation until recovery or expiry.
|
|
2250
2402
|
*/
|
|
2251
2403
|
async makePaidRequest(signed, endpoint, data, quoteId, signal, timeoutMs, extraHeaders) {
|
|
2252
2404
|
let resp;
|
|
@@ -2254,10 +2406,11 @@ class OneShot {
|
|
|
2254
2406
|
resp = await this.makeRequest(endpoint, data, signed.auth, quoteId, signal, timeoutMs, extraHeaders);
|
|
2255
2407
|
}
|
|
2256
2408
|
catch (err) {
|
|
2257
|
-
|
|
2409
|
+
if (!/(enrich\/(profile|email)|verify\/email|physical-mail\/send)$/.test(endpoint))
|
|
2410
|
+
this.releaseUsdcReservation(signed.reservation);
|
|
2258
2411
|
throw err;
|
|
2259
2412
|
}
|
|
2260
|
-
if (!resp.ok)
|
|
2413
|
+
if (!resp.ok && !(extraHeaders?.['Idempotency-Key'] && resp.status >= 500))
|
|
2261
2414
|
this.releaseUsdcReservation(signed.reservation);
|
|
2262
2415
|
return resp;
|
|
2263
2416
|
}
|