@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/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 errors_1 = require("./errors");
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.29.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 errors_1.ValidationError(`swapBufferMultiplier must be a finite number between 1 and ${MAX_SWAP_BUFFER_MULTIPLIER}`, 'swapBufferMultiplier');
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 errors_1.ValidationError(`budgets.${key} is not a recognized field`, `budgets.${key}`);
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 errors_1.ValidationError(`budgets.${field} must be a positive number`, `budgets.${field}`);
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 errors_1.ValidationError(`budgets.${field} must be a fraction in (0, 1]`, `budgets.${field}`);
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 errors_1.ValidationError('Provide one of: privateKey, cdp, or walletProvider', 'config');
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 errors_1.ValidationError('Provide privateKey or use OneShot.create() for CDP/custom wallets', 'config');
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 errors_1.ValidationError('ETH currency mode requires a wallet provider that supports sendTransaction', 'currency');
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 errors_1.ToolError('Failed to list domains', response.status, await response.text());
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 errors_1.ToolError('Failed to pause domain', response.status, await response.text());
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 errors_1.ToolError('Failed to resume domain', response.status, await response.text());
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 errors_1.ValidationError('At least one of linkedin_url, email, or name is required', 'identifier');
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 errors_1.ValidationError('At least one of domain, name, linkedin_url, or ticker is required', 'identifier');
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 errors_1.ValidationError('Either full_name or both first_name and last_name required', 'name');
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 errors_1.ValidationError('At least one of email, social_media_url, or name is required', 'identifier');
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 errors_1.ValidationError('At least one of email or social_media_url is required', 'identifier');
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 errors_1.ValidationError('At least one of email, phone, or social_media_url is required', 'identifier');
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 errors_1.ToolError('Failed to list inbox', response.status, await response.text());
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 errors_1.ToolError('Email not found', 404, 'Email not found');
486
+ throw new errors_2.ToolError('Email not found', 404, 'Email not found');
427
487
  }
428
488
  if (!response.ok) {
429
- throw new errors_1.ToolError('Failed to get email', response.status, await response.text());
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 errors_1.ValidationError('target_number array cannot be empty', 'target_number');
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 errors_1.ValidationError('Objective must be at least 10 characters', 'objective');
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 errors_1.ValidationError('max_duration_minutes must be between 1 and 30', 'max_duration_minutes');
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 errors_1.ContentBlockedError(errorData.message, errorData.categories || []);
583
+ throw new errors_2.ContentBlockedError(errorData.message, errorData.categories || []);
524
584
  }
525
585
  if (errorData.error === 'emergency_number_blocked') {
526
- throw new errors_1.EmergencyNumberError(errorData.message, errorData.blocked_number || '');
586
+ throw new errors_2.EmergencyNumberError(errorData.message, errorData.blocked_number || '');
527
587
  }
528
- throw new errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
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 errors_1.ValidationError('to_number array cannot be empty', 'to_number');
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 errors_1.ValidationError('Message is required', 'message');
620
+ throw new errors_2.ValidationError('Message is required', 'message');
561
621
  }
562
622
  if (options.message.length > 1600) {
563
- throw new errors_1.ValidationError('Message must be 1600 characters or less', 'message');
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 errors_1.ValidationError('Maximum 10 recipients allowed', 'to_number');
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 errors_1.ContentBlockedError(errorData.message, errorData.categories || []);
645
+ throw new errors_2.ContentBlockedError(errorData.message, errorData.categories || []);
586
646
  }
587
647
  if (errorData.error === 'emergency_number_blocked') {
588
- throw new errors_1.EmergencyNumberError(errorData.message, errorData.blocked_number || '');
648
+ throw new errors_2.EmergencyNumberError(errorData.message, errorData.blocked_number || '');
589
649
  }
590
- throw new errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
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 errors_1.ValidationError('Product description must be at least 10 characters', 'product.description');
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 errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
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 errors_1.ValidationError('Task must be at least 10 characters', 'task');
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 errors_1.ValidationError('max_steps must be between 1 and 100', 'max_steps');
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 errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
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 errors_1.ToolError('Failed to create browser profile', response.status, await response.text());
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 errors_1.ToolError('Failed to list browser profiles', response.status, await response.text());
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 errors_1.ToolError('Failed to delete browser profile', response.status, await response.text());
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 errors_1.ToolError('Failed to list SMS inbox', response.status, await response.text());
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 errors_1.ToolError('SMS message not found', 404, 'Message not found');
916
+ throw new errors_2.ToolError('SMS message not found', 404, 'Message not found');
857
917
  }
858
918
  if (!response.ok) {
859
- throw new errors_1.ToolError('Failed to get SMS message', response.status, await response.text());
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 errors_1.ToolError('Failed to list notifications', response.status, await response.text());
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 errors_1.ToolError('Notification not found', 404, 'Notification not found');
963
+ throw new errors_2.ToolError('Notification not found', 404, 'Notification not found');
904
964
  }
905
965
  if (!response.ok) {
906
- throw new errors_1.ToolError('Failed to mark notification as read', response.status, await response.text());
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 errors_1.ToolError('Failed to fetch balance', response.status, await response.text());
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 errors_1.ContentBlockedError(errorData.message, []);
1044
+ throw new errors_2.ContentBlockedError(errorData.message, []);
985
1045
  }
986
- throw new errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
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 errors_1.ToolError('Goal not found', 404, 'Goal not found');
1069
+ throw new errors_2.ToolError('Goal not found', 404, 'Goal not found');
1010
1070
  }
1011
1071
  if (!response.ok) {
1012
- throw new errors_1.ToolError('Failed to get compute goal', response.status, await response.text());
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 errors_1.ToolError('Failed to get compute tasks', response.status, await response.text());
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 errors_1.ToolError('Budget not found', 404, 'Budget not found for this goal');
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 errors_1.ToolError('Failed to get compute budget', response.status, await response.text());
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 errors_1.ToolError('Goal not found', 404, 'Goal not found');
1139
+ throw new errors_2.ToolError('Goal not found', 404, 'Goal not found');
1080
1140
  }
1081
1141
  if (!response.ok) {
1082
- throw new errors_1.ToolError('Failed to cancel compute goal', response.status, await response.text());
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 errors_1.ToolError('Goal or task not found', 404, await response.text());
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 errors_1.ToolError('Failed to respond to compute task', response.status, await response.text());
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 errors_1.ToolError('Failed to pause compute goal', response.status, await response.text());
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 errors_1.ToolError('Failed to resume compute goal', response.status, await response.text());
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 errors_1.ValidationError('amount must be a positive number', 'amount');
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 errors_1.ToolError('Failed to get spend breakdown', response.status, await response.text());
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 errors_1.ToolError('Failed to get RoCS', response.status, await response.text());
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 errors_1.ToolError('Failed to list receipts', response.status, await response.text());
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 errors_1.ToolError('Failed to record outcome value', response.status, await response.text());
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 errors_1.ToolError('Receipt not found', 404, 'Receipt not found or not owned by this agent');
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 errors_1.ToolError('Failed to tag receipt value', response.status, await response.text());
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 errors_1.ToolError('Failed to get RoCS by goal', response.status, await response.text());
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 errors_1.ValidationError(`${field} is required`, field);
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 errors_1.BudgetSyncError(`Could not sync spend budget (network): ${err}`);
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 errors_1.BudgetSyncError(`Could not sync spend budget (${response.status}): ${text}`, response.status, text);
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 errors_1.BudgetExceededError(`Quote $${total} exceeds this agent's per-transaction budget of $${cap}`, 'per_transaction', cap, undefined, amount);
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 errors_1.ToolError('Failed to fetch budgets', response.status, await response.text());
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 errors_1.OneShotError(`Quote $${total} exceeds maxCost $${maxCost}`);
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 errors_1.ToolError(message, response.status, text);
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 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);
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 errors_1.PaymentError(`payment rejected: ${reason}${detail ? ` — ${detail}` : ''}${data.message ? ` (${data.message})` : ''}`, reason, {
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 { signal, onStatusUpdate, wait = true, waitForPhones, phoneTimeoutSec, idempotencyKey, maxCost, ...payload } = options;
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 errors_1.OneShotError('Operation cancelled');
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 { request_id: result.request_id, status: result.status };
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 this.pollJob(result.request_id, options.timeout, signal, onStatusUpdate, waitForPhones ? { waitForPhones, phoneTimeoutSec } : undefined);
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 errors_1.OneShotError)
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 errors_1.OneShotError('Operation cancelled');
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 errors_1.ToolError('Failed to check job status', resp.status, await resp.text());
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 errors_1.OneShotError)
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 errors_1.OneShotError('Operation cancelled'));
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 errors_1.OneShotError('Operation cancelled'));
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 errors_1.JobError(`Job failed: ${msg.error ?? 'Unknown'}`, requestId, String(msg.error ?? 'Unknown'), msg.error_code));
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 errors_1.OneShotError('Operation cancelled');
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 errors_1.ToolError('Failed to check job status', resp.status, body);
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 errors_1.JobError(`Job failed: ${job.error ?? 'Unknown'}`, requestId, String(job.error ?? 'Unknown'), job.error_code);
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 errors_1.OneShotError)
2202
+ if (err instanceof errors_2.OneShotError)
2053
2203
  throw err;
2054
2204
  if (++retries > maxRetries) {
2055
- throw new errors_1.OneShotError(`Polling failed after ${maxRetries} retries: ${err}`);
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 errors_1.JobTimeoutError(requestId, Date.now() - startTime);
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 errors_1.OneShotError('Operation cancelled'));
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 errors_1.OneShotError('Operation cancelled'));
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
- 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
- }
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 fetch(`${this.baseUrl}${endpoint}`, {
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
- finally {
2108
- if (timeoutId)
2109
- clearTimeout(timeoutId);
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 errors_1.OneShotError('Operation cancelled before payment');
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 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');
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 errors_1.ValidationError(`ETH→USDC swap buys ${USDC_ADDRESS} but this payment requires ${usdcAddress}`, 'currency');
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. 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.
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 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;
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 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.
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
- this.releaseUsdcReservation(signed.reservation);
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
  }