@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/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 errors_1 = require("./errors");
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.29.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 errors_1.ValidationError(`swapBufferMultiplier must be a finite number between 1 and ${MAX_SWAP_BUFFER_MULTIPLIER}`, 'swapBufferMultiplier');
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 errors_1.ValidationError(`budgets.${key} is not a recognized field`, `budgets.${key}`);
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 errors_1.ValidationError(`budgets.${field} must be a positive number`, `budgets.${field}`);
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 errors_1.ValidationError(`budgets.${field} must be a fraction in (0, 1]`, `budgets.${field}`);
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 errors_1.ValidationError('Provide one of: privateKey, cdp, or walletProvider', 'config');
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 errors_1.ValidationError('Provide privateKey or use OneShot.create() for CDP/custom wallets', 'config');
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 errors_1.ValidationError('ETH currency mode requires a wallet provider that supports sendTransaction', 'currency');
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 errors_1.ToolError('Failed to list domains', response.status, await response.text());
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 errors_1.ToolError('Failed to pause domain', response.status, await response.text());
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 errors_1.ToolError('Failed to resume domain', response.status, await response.text());
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 errors_1.ValidationError('At least one of linkedin_url, email, or name is required', 'identifier');
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 errors_1.ValidationError('At least one of domain, name, linkedin_url, or ticker is required', 'identifier');
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 errors_1.ValidationError('Either full_name or both first_name and last_name required', 'name');
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 errors_1.ValidationError('At least one of email, social_media_url, or name is required', 'identifier');
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 errors_1.ValidationError('At least one of email or social_media_url is required', 'identifier');
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 errors_1.ValidationError('At least one of email, phone, or social_media_url is required', 'identifier');
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 errors_1.ToolError('Failed to list inbox', response.status, await response.text());
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 errors_1.ToolError('Email not found', 404, 'Email not found');
474
+ throw new errors_2.ToolError('Email not found', 404, 'Email not found');
427
475
  }
428
476
  if (!response.ok) {
429
- throw new errors_1.ToolError('Failed to get email', response.status, await response.text());
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 errors_1.ValidationError('target_number array cannot be empty', 'target_number');
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 errors_1.ValidationError('Objective must be at least 10 characters', 'objective');
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 errors_1.ValidationError('max_duration_minutes must be between 1 and 30', 'max_duration_minutes');
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 errors_1.ContentBlockedError(errorData.message, errorData.categories || []);
571
+ throw new errors_2.ContentBlockedError(errorData.message, errorData.categories || []);
524
572
  }
525
573
  if (errorData.error === 'emergency_number_blocked') {
526
- throw new errors_1.EmergencyNumberError(errorData.message, errorData.blocked_number || '');
574
+ throw new errors_2.EmergencyNumberError(errorData.message, errorData.blocked_number || '');
527
575
  }
528
- throw new errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
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 errors_1.ValidationError('to_number array cannot be empty', 'to_number');
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 errors_1.ValidationError('Message is required', 'message');
608
+ throw new errors_2.ValidationError('Message is required', 'message');
561
609
  }
562
610
  if (options.message.length > 1600) {
563
- throw new errors_1.ValidationError('Message must be 1600 characters or less', 'message');
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 errors_1.ValidationError('Maximum 10 recipients allowed', 'to_number');
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 errors_1.ContentBlockedError(errorData.message, errorData.categories || []);
633
+ throw new errors_2.ContentBlockedError(errorData.message, errorData.categories || []);
586
634
  }
587
635
  if (errorData.error === 'emergency_number_blocked') {
588
- throw new errors_1.EmergencyNumberError(errorData.message, errorData.blocked_number || '');
636
+ throw new errors_2.EmergencyNumberError(errorData.message, errorData.blocked_number || '');
589
637
  }
590
- throw new errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
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 errors_1.ValidationError('Product description must be at least 10 characters', 'product.description');
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 errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
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 errors_1.ValidationError('Task must be at least 10 characters', 'task');
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 errors_1.ValidationError('max_steps must be between 1 and 100', 'max_steps');
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 errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
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 errors_1.ToolError('Failed to create browser profile', response.status, await response.text());
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 errors_1.ToolError('Failed to list browser profiles', response.status, await response.text());
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 errors_1.ToolError('Failed to delete browser profile', response.status, await response.text());
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 errors_1.ToolError('Failed to list SMS inbox', response.status, await response.text());
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 errors_1.ToolError('SMS message not found', 404, 'Message not found');
904
+ throw new errors_2.ToolError('SMS message not found', 404, 'Message not found');
857
905
  }
858
906
  if (!response.ok) {
859
- throw new errors_1.ToolError('Failed to get SMS message', response.status, await response.text());
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 errors_1.ToolError('Failed to list notifications', response.status, await response.text());
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 errors_1.ToolError('Notification not found', 404, 'Notification not found');
951
+ throw new errors_2.ToolError('Notification not found', 404, 'Notification not found');
904
952
  }
905
953
  if (!response.ok) {
906
- throw new errors_1.ToolError('Failed to mark notification as read', response.status, await response.text());
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 errors_1.ToolError('Failed to fetch balance', response.status, await response.text());
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 errors_1.ContentBlockedError(errorData.message, []);
1032
+ throw new errors_2.ContentBlockedError(errorData.message, []);
985
1033
  }
986
- throw new errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
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 errors_1.ToolError('Goal not found', 404, 'Goal not found');
1057
+ throw new errors_2.ToolError('Goal not found', 404, 'Goal not found');
1010
1058
  }
1011
1059
  if (!response.ok) {
1012
- throw new errors_1.ToolError('Failed to get compute goal', response.status, await response.text());
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 errors_1.ToolError('Failed to get compute tasks', response.status, await response.text());
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 errors_1.ToolError('Budget not found', 404, 'Budget not found for this goal');
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 errors_1.ToolError('Failed to get compute budget', response.status, await response.text());
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 errors_1.ToolError('Goal not found', 404, 'Goal not found');
1127
+ throw new errors_2.ToolError('Goal not found', 404, 'Goal not found');
1080
1128
  }
1081
1129
  if (!response.ok) {
1082
- throw new errors_1.ToolError('Failed to cancel compute goal', response.status, await response.text());
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 errors_1.ToolError('Goal or task not found', 404, await response.text());
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 errors_1.ToolError('Failed to respond to compute task', response.status, await response.text());
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 errors_1.ToolError('Failed to pause compute goal', response.status, await response.text());
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 errors_1.ToolError('Failed to resume compute goal', response.status, await response.text());
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 errors_1.ValidationError('amount must be a positive number', 'amount');
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 errors_1.ToolError('Failed to get spend breakdown', response.status, await response.text());
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 errors_1.ToolError('Failed to get RoCS', response.status, await response.text());
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 errors_1.ToolError('Failed to list receipts', response.status, await response.text());
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 errors_1.ToolError('Failed to record outcome value', response.status, await response.text());
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 errors_1.ToolError('Receipt not found', 404, 'Receipt not found or not owned by this agent');
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 errors_1.ToolError('Failed to tag receipt value', response.status, await response.text());
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 errors_1.ToolError('Failed to get RoCS by goal', response.status, await response.text());
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 errors_1.ValidationError(`${field} is required`, field);
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 errors_1.BudgetSyncError(`Could not sync spend budget (network): ${err}`);
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 errors_1.BudgetSyncError(`Could not sync spend budget (${response.status}): ${text}`, response.status, text);
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 errors_1.BudgetExceededError(`Quote $${total} exceeds this agent's per-transaction budget of $${cap}`, 'per_transaction', cap, undefined, amount);
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 errors_1.ToolError('Failed to fetch budgets', response.status, await response.text());
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 errors_1.OneShotError(`Quote $${total} exceeds maxCost $${maxCost}`);
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 errors_1.ToolError(message, response.status, text);
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 errors_1.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);
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 errors_1.PaymentError(`payment rejected: ${reason}${detail ? ` — ${detail}` : ''}${data.message ? ` (${data.message})` : ''}`, reason, {
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 { signal, onStatusUpdate, wait = true, waitForPhones, phoneTimeoutSec, idempotencyKey, maxCost, ...payload } = options;
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 errors_1.OneShotError('Operation cancelled');
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 { request_id: result.request_id, status: result.status };
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 this.pollJob(result.request_id, options.timeout, signal, onStatusUpdate, waitForPhones ? { waitForPhones, phoneTimeoutSec } : undefined);
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 errors_1.OneShotError)
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 errors_1.OneShotError('Operation cancelled');
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 errors_1.ToolError('Failed to check job status', resp.status, await resp.text());
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 errors_1.OneShotError)
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 errors_1.OneShotError('Operation cancelled'));
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 errors_1.OneShotError('Operation cancelled'));
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 errors_1.JobError(`Job failed: ${msg.error ?? 'Unknown'}`, requestId, String(msg.error ?? 'Unknown'), msg.error_code));
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 errors_1.OneShotError('Operation cancelled');
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 errors_1.ToolError('Failed to check job status', resp.status, body);
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 errors_1.JobError(`Job failed: ${job.error ?? 'Unknown'}`, requestId, String(job.error ?? 'Unknown'), job.error_code);
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 errors_1.OneShotError)
2190
+ if (err instanceof errors_2.OneShotError)
2053
2191
  throw err;
2054
2192
  if (++retries > maxRetries) {
2055
- throw new errors_1.OneShotError(`Polling failed after ${maxRetries} retries: ${err}`);
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 errors_1.JobTimeoutError(requestId, Date.now() - startTime);
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 errors_1.OneShotError('Operation cancelled'));
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 errors_1.OneShotError('Operation cancelled'));
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
- let fetchSignal = signal;
2093
- let timeoutId;
2094
- if (timeoutMs && !signal) {
2095
- const controller = new AbortController();
2096
- timeoutId = setTimeout(() => controller.abort(), timeoutMs);
2097
- fetchSignal = controller.signal;
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 fetch(`${this.baseUrl}${endpoint}`, {
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
- finally {
2108
- if (timeoutId)
2109
- clearTimeout(timeoutId);
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 errors_1.OneShotError('Operation cancelled before payment');
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 errors_1.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');
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 errors_1.ValidationError(`ETH→USDC swap buys ${USDC_ADDRESS} but this payment requires ${usdcAddress}`, 'currency');
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. If the wallet's ETH cannot
2211
- * cover the buffered quote, fall back to the bare shortfall so an ETH-poor
2212
- * wallet can still make the one payment in front of it.
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 1000× scale: no float→BigInt on an unbounded value.
2217
- const mScaled = BigInt(Math.round(this._swapBufferMultiplier * 1000));
2218
- const target = (charge * mScaled + 999n) / 1000n;
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 a 2xx, so a thrown request or a non-2xx response means nothing will be
2249
- * debited on-chain — release the ETH-mode reservation in that case.
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
- this.releaseUsdcReservation(signed.reservation);
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
  }