@oneshot-agent/sdk 0.16.2 → 0.17.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
@@ -15,6 +15,9 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
15
15
  }) : function(o, v) {
16
16
  o["default"] = v;
17
17
  });
18
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
19
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
20
+ };
18
21
  var __importStar = (this && this.__importStar) || (function () {
19
22
  var ownKeys = function(o) {
20
23
  ownKeys = Object.getOwnPropertyNames || function (o) {
@@ -33,9 +36,10 @@ var __importStar = (this && this.__importStar) || (function () {
33
36
  };
34
37
  })();
35
38
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.OneShot = exports.EmergencyNumberError = exports.ContentBlockedError = exports.ValidationError = exports.JobTimeoutError = exports.JobError = exports.ToolError = exports.OneShotError = exports.executeSwap = exports.getSwapQuote = exports.CdpWalletProvider = exports.EthersWalletProvider = void 0;
39
+ exports.OneShot = exports.executeSwap = exports.getSwapQuote = exports.CdpWalletProvider = exports.EthersWalletProvider = void 0;
37
40
  const ethers_1 = require("ethers");
38
41
  const ethers_2 = require("./providers/ethers");
42
+ const errors_1 = require("./errors");
39
43
  var ethers_3 = require("./providers/ethers");
40
44
  Object.defineProperty(exports, "EthersWalletProvider", { enumerable: true, get: function () { return ethers_3.EthersWalletProvider; } });
41
45
  var cdp_1 = require("./providers/cdp");
@@ -43,7 +47,8 @@ Object.defineProperty(exports, "CdpWalletProvider", { enumerable: true, get: fun
43
47
  var swap_1 = require("./swap");
44
48
  Object.defineProperty(exports, "getSwapQuote", { enumerable: true, get: function () { return swap_1.getSwapQuote; } });
45
49
  Object.defineProperty(exports, "executeSwap", { enumerable: true, get: function () { return swap_1.executeSwap; } });
46
- const SDK_VERSION = '0.13.1';
50
+ __exportStar(require("./errors"), exports);
51
+ const SDK_VERSION = '0.16.2';
47
52
  // ============================================================================
48
53
  // Environment Configuration
49
54
  // ============================================================================
@@ -52,67 +57,12 @@ const RPC_URL = 'https://mainnet.base.org';
52
57
  const CHAIN_ID = 8453;
53
58
  const USDC_ADDRESS = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
54
59
  // ============================================================================
55
- // Error Classes
60
+ // Public types — defined in ./types.ts. Re-exported below so existing
61
+ // consumer imports (`import { EmailToolOptions, ... } from '@oneshot-agent/sdk'`)
62
+ // keep resolving unchanged. The OneShot class below pulls the names it needs
63
+ // for internal use via the import block.
56
64
  // ============================================================================
57
- class OneShotError extends Error {
58
- constructor(message) {
59
- super(message);
60
- this.name = 'OneShotError';
61
- Object.setPrototypeOf(this, new.target.prototype);
62
- }
63
- }
64
- exports.OneShotError = OneShotError;
65
- class ToolError extends OneShotError {
66
- constructor(message, statusCode, responseBody) {
67
- super(message);
68
- this.statusCode = statusCode;
69
- this.responseBody = responseBody;
70
- this.name = 'ToolError';
71
- }
72
- }
73
- exports.ToolError = ToolError;
74
- class JobError extends OneShotError {
75
- constructor(message, jobId, jobError) {
76
- super(message);
77
- this.jobId = jobId;
78
- this.jobError = jobError;
79
- this.name = 'JobError';
80
- }
81
- }
82
- exports.JobError = JobError;
83
- class JobTimeoutError extends OneShotError {
84
- constructor(jobId, elapsedMs) {
85
- super(`Job ${jobId} timed out after ${elapsedMs / 1000}s`);
86
- this.jobId = jobId;
87
- this.elapsedMs = elapsedMs;
88
- this.name = 'JobTimeoutError';
89
- }
90
- }
91
- exports.JobTimeoutError = JobTimeoutError;
92
- class ValidationError extends OneShotError {
93
- constructor(message, field) {
94
- super(message);
95
- this.field = field;
96
- this.name = 'ValidationError';
97
- }
98
- }
99
- exports.ValidationError = ValidationError;
100
- class ContentBlockedError extends OneShotError {
101
- constructor(message, categories) {
102
- super(message);
103
- this.categories = categories;
104
- this.name = 'ContentBlockedError';
105
- }
106
- }
107
- exports.ContentBlockedError = ContentBlockedError;
108
- class EmergencyNumberError extends OneShotError {
109
- constructor(message, blockedNumber) {
110
- super(message);
111
- this.blockedNumber = blockedNumber;
112
- this.name = 'EmergencyNumberError';
113
- }
114
- }
115
- exports.EmergencyNumberError = EmergencyNumberError;
65
+ __exportStar(require("./types"), exports);
116
66
  // ============================================================================
117
67
  // OneShot SDK
118
68
  // ============================================================================
@@ -154,7 +104,7 @@ class OneShot {
154
104
  const walletProvider = new ethers_2.EthersWalletProvider(config.privateKey, rpcProvider);
155
105
  return new OneShot(config, walletProvider);
156
106
  }
157
- throw new ValidationError('Provide one of: privateKey, cdp, or walletProvider', 'config');
107
+ throw new errors_1.ValidationError('Provide one of: privateKey, cdp, or walletProvider', 'config');
158
108
  }
159
109
  /**
160
110
  * Sync constructor — works with privateKey (backwards compatible).
@@ -174,11 +124,11 @@ class OneShot {
174
124
  this.provider = new ethers_2.EthersWalletProvider(config.privateKey, this.rpcProvider);
175
125
  }
176
126
  else {
177
- throw new ValidationError('Provide privateKey or use OneShot.create() for CDP/custom wallets', 'config');
127
+ throw new errors_1.ValidationError('Provide privateKey or use OneShot.create() for CDP/custom wallets', 'config');
178
128
  }
179
129
  // Validate ETH mode requirements
180
130
  if (this._currency === 'ETH' && !this.provider.sendTransaction) {
181
- throw new ValidationError('ETH currency mode requires a wallet provider that supports sendTransaction', 'currency');
131
+ throw new errors_1.ValidationError('ETH currency mode requires a wallet provider that supports sendTransaction', 'currency');
182
132
  }
183
133
  if (this.debug) {
184
134
  this.log(`SDK initialized — chain=${CHAIN_ID} currency=${this._currency}`);
@@ -217,9 +167,21 @@ class OneShot {
217
167
  from_address: fromAddress,
218
168
  to_address: options.to,
219
169
  subject: options.subject,
220
- body: options.body
170
+ body: options.body,
171
+ // Forward maxCost so the server-side X-Max-Cost-USDC header is set on
172
+ // the quote fetch (executeToolRequest destructures + threads it).
173
+ maxCost: options.maxCost,
221
174
  });
222
175
  this.log(`Email quote: $${quote.total_cost}`);
176
+ // Local fast-fail — matches the per-tool check on commerce/voice/sms/build/
177
+ // browser/compute. If the server-side header guard fired we'd never reach
178
+ // here; this catches the case where the server's enforcement is bypassed
179
+ // (e.g. a future API revision without the header check) or skipped (cap
180
+ // un-set so the server returned a 200 with the quote and the caller still
181
+ // wants the local guard to apply).
182
+ if (options.maxCost && parseFloat(quote.total_cost) > options.maxCost) {
183
+ throw new errors_1.OneShotError(`Quote $${quote.total_cost} exceeds maxCost $${options.maxCost}`);
184
+ }
223
185
  const payload = {
224
186
  from_address: fromAddress,
225
187
  to_address: options.to,
@@ -246,14 +208,14 @@ class OneShot {
246
208
  }
247
209
  async enrichProfile(options) {
248
210
  if (!options.linkedin_url && !options.email && !options.name) {
249
- throw new ValidationError('At least one of linkedin_url, email, or name is required', 'identifier');
211
+ throw new errors_1.ValidationError('At least one of linkedin_url, email, or name is required', 'identifier');
250
212
  }
251
213
  return this.tool('enrich/profile', { ...options });
252
214
  }
253
215
  async findEmail(options) {
254
216
  this.validate(options.company_domain, 'company_domain');
255
217
  if (!options.full_name && !(options.first_name && options.last_name)) {
256
- throw new ValidationError('Either full_name or both first_name and last_name required', 'name');
218
+ throw new errors_1.ValidationError('Either full_name or both first_name and last_name required', 'name');
257
219
  }
258
220
  return this.tool('enrich/email', { ...options });
259
221
  }
@@ -263,13 +225,13 @@ class OneShot {
263
225
  }
264
226
  async deepResearchPerson(options) {
265
227
  if (!options.email && !options.social_media_url && !options.name) {
266
- throw new ValidationError('At least one of email, social_media_url, or name is required', 'identifier');
228
+ throw new errors_1.ValidationError('At least one of email, social_media_url, or name is required', 'identifier');
267
229
  }
268
230
  return this.tool('research/person', { ...options });
269
231
  }
270
232
  async socialProfiles(options) {
271
233
  if (!options.email && !options.social_media_url) {
272
- throw new ValidationError('At least one of email or social_media_url is required', 'identifier');
234
+ throw new errors_1.ValidationError('At least one of email or social_media_url is required', 'identifier');
273
235
  }
274
236
  return this.tool('research/social', { ...options });
275
237
  }
@@ -284,7 +246,7 @@ class OneShot {
284
246
  }
285
247
  async personInterests(options) {
286
248
  if (!options.email && !options.phone && !options.social_media_url) {
287
- throw new ValidationError('At least one of email, phone, or social_media_url is required', 'identifier');
249
+ throw new errors_1.ValidationError('At least one of email, phone, or social_media_url is required', 'identifier');
288
250
  }
289
251
  return this.tool('research/interests', { ...options });
290
252
  }
@@ -305,7 +267,7 @@ class OneShot {
305
267
  headers: this.headers()
306
268
  });
307
269
  if (!response.ok) {
308
- throw new ToolError('Failed to list inbox', response.status, await response.text());
270
+ throw new errors_1.ToolError('Failed to list inbox', response.status, await response.text());
309
271
  }
310
272
  return response.json();
311
273
  }
@@ -315,10 +277,10 @@ class OneShot {
315
277
  headers: this.headers()
316
278
  });
317
279
  if (response.status === 404) {
318
- throw new ToolError('Email not found', 404, 'Email not found');
280
+ throw new errors_1.ToolError('Email not found', 404, 'Email not found');
319
281
  }
320
282
  if (!response.ok) {
321
- throw new ToolError('Failed to get email', response.status, await response.text());
283
+ throw new errors_1.ToolError('Failed to get email', response.status, await response.text());
322
284
  }
323
285
  return response.json();
324
286
  }
@@ -333,14 +295,14 @@ class OneShot {
333
295
  variant_id: options.variant_id
334
296
  };
335
297
  // Commerce quotes can take up to 90s due to Rye API polling
336
- const quoteResp = await this.makeRequest('/v1/tools/commerce/buy', payload, undefined, undefined, options.signal, 120000);
298
+ const quoteResp = await this.makeRequest('/v1/tools/commerce/buy', payload, undefined, undefined, options.signal, 120000, this.maxCostHeader(options.maxCost));
337
299
  if (quoteResp.status !== 402) {
338
- throw new ToolError('Expected 402 for quote', quoteResp.status, await quoteResp.text());
300
+ throw new errors_1.ToolError('Expected 402 for quote', quoteResp.status, await quoteResp.text());
339
301
  }
340
302
  const quoteData = await quoteResp.json();
341
303
  this.log(`Commerce quote: $${quoteData.context.total} for "${quoteData.context.product_title}"`);
342
304
  if (options.maxCost && parseFloat(quoteData.context.total) > options.maxCost) {
343
- throw new OneShotError(`Quote $${quoteData.context.total} exceeds maxCost $${options.maxCost}`);
305
+ throw new errors_1.OneShotError(`Quote $${quoteData.context.total} exceeds maxCost $${options.maxCost}`);
344
306
  }
345
307
  const paymentInfo = {
346
308
  protocol: 'x402',
@@ -356,7 +318,7 @@ class OneShot {
356
318
  const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
357
319
  const buyResp = await this.makeRequest('/v1/tools/commerce/buy', payload, auth, quoteData.context.quote_id, options.signal, 60000);
358
320
  if (buyResp.status !== 202) {
359
- throw new ToolError('Commerce buy failed', buyResp.status, await buyResp.text());
321
+ throw new errors_1.ToolError('Commerce buy failed', buyResp.status, await buyResp.text());
360
322
  }
361
323
  const result = await buyResp.json();
362
324
  this.log(`Order submitted: ${result.request_id}`);
@@ -395,13 +357,13 @@ class OneShot {
395
357
  this.validate(options.target_number, 'target_number');
396
358
  // Check for empty arrays
397
359
  if (Array.isArray(options.target_number) && options.target_number.length === 0) {
398
- throw new ValidationError('target_number array cannot be empty', 'target_number');
360
+ throw new errors_1.ValidationError('target_number array cannot be empty', 'target_number');
399
361
  }
400
362
  if (options.objective.length < 10) {
401
- throw new ValidationError('Objective must be at least 10 characters', 'objective');
363
+ throw new errors_1.ValidationError('Objective must be at least 10 characters', 'objective');
402
364
  }
403
365
  if (options.max_duration_minutes !== undefined && (options.max_duration_minutes < 1 || options.max_duration_minutes > 30)) {
404
- throw new ValidationError('max_duration_minutes must be between 1 and 30', 'max_duration_minutes');
366
+ throw new errors_1.ValidationError('max_duration_minutes must be between 1 and 30', 'max_duration_minutes');
405
367
  }
406
368
  const payload = {
407
369
  objective: options.objective,
@@ -417,26 +379,26 @@ class OneShot {
417
379
  if (options.max_duration_minutes)
418
380
  payload.max_duration_minutes = options.max_duration_minutes;
419
381
  // Voice uses quote-to-pay flow (402 -> payment -> 202)
420
- const quoteResp = await this.makeRequest('/v1/tools/voice/call', payload, undefined, undefined, options.signal);
382
+ const quoteResp = await this.makeRequest('/v1/tools/voice/call', payload, undefined, undefined, options.signal, undefined, this.maxCostHeader(options.maxCost));
421
383
  // Handle error responses before expecting 402
422
384
  if (quoteResp.status === 400) {
423
385
  const errorData = await quoteResp.json();
424
386
  if (errorData.error === 'content_blocked') {
425
- throw new ContentBlockedError(errorData.message, errorData.categories || []);
387
+ throw new errors_1.ContentBlockedError(errorData.message, errorData.categories || []);
426
388
  }
427
389
  if (errorData.error === 'emergency_number_blocked') {
428
- throw new EmergencyNumberError(errorData.message, errorData.blocked_number || '');
390
+ throw new errors_1.EmergencyNumberError(errorData.message, errorData.blocked_number || '');
429
391
  }
430
- throw new ValidationError(errorData.message || 'Invalid request', 'request');
392
+ throw new errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
431
393
  }
432
394
  if (quoteResp.status !== 402) {
433
- throw new ToolError('Expected 402 for quote', quoteResp.status, await quoteResp.text());
395
+ throw new errors_1.ToolError('Expected 402 for quote', quoteResp.status, await quoteResp.text());
434
396
  }
435
397
  const quoteData = await quoteResp.json();
436
398
  this.log(`Voice quote: $${quoteData.context.total} for ${quoteData.context.estimated_duration_minutes}min call`);
437
399
  this.log(`Objective summary: ${quoteData.context.objective_summary}`);
438
400
  if (options.maxCost && parseFloat(quoteData.context.total) > options.maxCost) {
439
- throw new OneShotError(`Quote $${quoteData.context.total} exceeds maxCost $${options.maxCost}`);
401
+ throw new errors_1.OneShotError(`Quote $${quoteData.context.total} exceeds maxCost $${options.maxCost}`);
440
402
  }
441
403
  const paymentInfo = {
442
404
  protocol: 'x402',
@@ -452,7 +414,7 @@ class OneShot {
452
414
  const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
453
415
  const callResp = await this.makeRequest('/v1/tools/voice/call', payload, auth, quoteData.context.quote_id, options.signal);
454
416
  if (callResp.status !== 202) {
455
- throw new ToolError('Voice call initiation failed', callResp.status, await callResp.text());
417
+ throw new errors_1.ToolError('Voice call initiation failed', callResp.status, await callResp.text());
456
418
  }
457
419
  const result = await callResp.json();
458
420
  this.log(`Call initiated: ${result.request_id}`);
@@ -478,17 +440,17 @@ class OneShot {
478
440
  this.validate(options.to_number, 'to_number');
479
441
  // Check for empty arrays
480
442
  if (Array.isArray(options.to_number) && options.to_number.length === 0) {
481
- throw new ValidationError('to_number array cannot be empty', 'to_number');
443
+ throw new errors_1.ValidationError('to_number array cannot be empty', 'to_number');
482
444
  }
483
445
  if (options.message.length < 1) {
484
- throw new ValidationError('Message is required', 'message');
446
+ throw new errors_1.ValidationError('Message is required', 'message');
485
447
  }
486
448
  if (options.message.length > 1600) {
487
- throw new ValidationError('Message must be 1600 characters or less', 'message');
449
+ throw new errors_1.ValidationError('Message must be 1600 characters or less', 'message');
488
450
  }
489
451
  const recipientCount = Array.isArray(options.to_number) ? options.to_number.length : 1;
490
452
  if (recipientCount > 10) {
491
- throw new ValidationError('Maximum 10 recipients allowed', 'to_number');
453
+ throw new errors_1.ValidationError('Maximum 10 recipients allowed', 'to_number');
492
454
  }
493
455
  const payload = {
494
456
  message: options.message,
@@ -498,25 +460,25 @@ class OneShot {
498
460
  wait: options.wait
499
461
  };
500
462
  // SMS uses quote-to-pay flow (402 -> payment -> 202)
501
- const quoteResp = await this.makeRequest('/v1/tools/sms/send', payload, undefined, undefined, options.signal);
463
+ const quoteResp = await this.makeRequest('/v1/tools/sms/send', payload, undefined, undefined, options.signal, undefined, this.maxCostHeader(options.maxCost));
502
464
  // Handle error responses before expecting 402
503
465
  if (quoteResp.status === 400) {
504
466
  const errorData = await quoteResp.json();
505
467
  if (errorData.error === 'content_blocked') {
506
- throw new ContentBlockedError(errorData.message, errorData.categories || []);
468
+ throw new errors_1.ContentBlockedError(errorData.message, errorData.categories || []);
507
469
  }
508
470
  if (errorData.error === 'emergency_number_blocked') {
509
- throw new EmergencyNumberError(errorData.message, errorData.blocked_number || '');
471
+ throw new errors_1.EmergencyNumberError(errorData.message, errorData.blocked_number || '');
510
472
  }
511
- throw new ValidationError(errorData.message || 'Invalid request', 'request');
473
+ throw new errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
512
474
  }
513
475
  if (quoteResp.status !== 402) {
514
- throw new ToolError('Expected 402 for quote', quoteResp.status, await quoteResp.text());
476
+ throw new errors_1.ToolError('Expected 402 for quote', quoteResp.status, await quoteResp.text());
515
477
  }
516
478
  const quoteData = await quoteResp.json();
517
479
  this.log(`SMS quote: $${quoteData.context.total} for ${quoteData.context.segment_count} segment(s) to ${recipientCount} recipient(s)`);
518
480
  if (options.maxCost && parseFloat(quoteData.context.total) > options.maxCost) {
519
- throw new OneShotError(`Quote $${quoteData.context.total} exceeds maxCost $${options.maxCost}`);
481
+ throw new errors_1.OneShotError(`Quote $${quoteData.context.total} exceeds maxCost $${options.maxCost}`);
520
482
  }
521
483
  const paymentInfo = {
522
484
  protocol: 'x402',
@@ -532,7 +494,7 @@ class OneShot {
532
494
  const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
533
495
  const sendResp = await this.makeRequest('/v1/tools/sms/send', payload, auth, quoteData.context.quote_id, options.signal);
534
496
  if (sendResp.status !== 202) {
535
- throw new ToolError('SMS send failed', sendResp.status, await sendResp.text());
497
+ throw new errors_1.ToolError('SMS send failed', sendResp.status, await sendResp.text());
536
498
  }
537
499
  const result = await sendResp.json();
538
500
  this.log(`SMS queued: ${result.request_id}`);
@@ -562,7 +524,7 @@ class OneShot {
562
524
  this.validate(options.product?.name, 'product.name');
563
525
  this.validate(options.product?.description, 'product.description');
564
526
  if (options.product.description.length < 10) {
565
- throw new ValidationError('Product description must be at least 10 characters', 'product.description');
527
+ throw new errors_1.ValidationError('Product description must be at least 10 characters', 'product.description');
566
528
  }
567
529
  const payload = {
568
530
  type: options.type ?? 'saas',
@@ -586,19 +548,19 @@ class OneShot {
586
548
  if (options.build_id)
587
549
  payload.build_id = options.build_id;
588
550
  // Build uses quote-to-pay flow (402 -> payment -> 202)
589
- const quoteResp = await this.makeRequest('/v1/tools/build', payload, undefined, undefined, options.signal);
551
+ const quoteResp = await this.makeRequest('/v1/tools/build', payload, undefined, undefined, options.signal, undefined, this.maxCostHeader(options.maxCost));
590
552
  if (quoteResp.status === 400) {
591
553
  const errorData = await quoteResp.json();
592
- throw new ValidationError(errorData.message || 'Invalid request', 'request');
554
+ throw new errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
593
555
  }
594
556
  if (quoteResp.status !== 402) {
595
- throw new ToolError('Expected 402 for quote', quoteResp.status, await quoteResp.text());
557
+ throw new errors_1.ToolError('Expected 402 for quote', quoteResp.status, await quoteResp.text());
596
558
  }
597
559
  const quoteData = await quoteResp.json();
598
560
  this.log(`Build quote: $${quoteData.context.pricing.total} for "${quoteData.context.product_name}"`);
599
561
  this.log(`Type: ${quoteData.context.analysis.inferred_type}, Sections: ${quoteData.context.analysis.estimated_sections}`);
600
562
  if (options.maxCost && parseFloat(quoteData.context.pricing.total) > options.maxCost) {
601
- throw new OneShotError(`Quote $${quoteData.context.pricing.total} exceeds maxCost $${options.maxCost}`);
563
+ throw new errors_1.OneShotError(`Quote $${quoteData.context.pricing.total} exceeds maxCost $${options.maxCost}`);
602
564
  }
603
565
  const paymentInfo = {
604
566
  protocol: 'x402',
@@ -614,7 +576,7 @@ class OneShot {
614
576
  const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
615
577
  const buildResp = await this.makeRequest('/v1/tools/build', payload, auth, quoteData.context.quote_id, options.signal);
616
578
  if (buildResp.status !== 202) {
617
- throw new ToolError('Build initiation failed', buildResp.status, await buildResp.text());
579
+ throw new errors_1.ToolError('Build initiation failed', buildResp.status, await buildResp.text());
618
580
  }
619
581
  const result = await buildResp.json();
620
582
  this.log(`Build initiated: ${result.request_id}`);
@@ -638,10 +600,10 @@ class OneShot {
638
600
  async browser(options) {
639
601
  this.validate(options.task, 'task');
640
602
  if (options.task.length < 10) {
641
- throw new ValidationError('Task must be at least 10 characters', 'task');
603
+ throw new errors_1.ValidationError('Task must be at least 10 characters', 'task');
642
604
  }
643
605
  if (options.max_steps !== undefined && (options.max_steps < 1 || options.max_steps > 100)) {
644
- throw new ValidationError('max_steps must be between 1 and 100', 'max_steps');
606
+ throw new errors_1.ValidationError('max_steps must be between 1 and 100', 'max_steps');
645
607
  }
646
608
  const payload = {
647
609
  task: options.task,
@@ -664,18 +626,18 @@ class OneShot {
664
626
  if (options.max_steps)
665
627
  payload.max_steps = options.max_steps;
666
628
  // Browser uses quote-to-pay flow (402 -> payment -> 202)
667
- const quoteResp = await this.makeRequest('/v1/tools/browser', payload, undefined, undefined, options.signal);
629
+ const quoteResp = await this.makeRequest('/v1/tools/browser', payload, undefined, undefined, options.signal, undefined, this.maxCostHeader(options.maxCost));
668
630
  if (quoteResp.status === 400) {
669
631
  const errorData = await quoteResp.json();
670
- throw new ValidationError(errorData.message || 'Invalid request', 'request');
632
+ throw new errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
671
633
  }
672
634
  if (quoteResp.status !== 402) {
673
- throw new ToolError('Expected 402 for quote', quoteResp.status, await quoteResp.text());
635
+ throw new errors_1.ToolError('Expected 402 for quote', quoteResp.status, await quoteResp.text());
674
636
  }
675
637
  const quoteData = await quoteResp.json();
676
638
  this.log(`Browser quote: $${quoteData.context.estimated_cost} for ~${quoteData.context.estimated_steps} steps`);
677
639
  if (options.maxCost && parseFloat(quoteData.context.estimated_cost) > options.maxCost) {
678
- throw new OneShotError(`Quote $${quoteData.context.estimated_cost} exceeds maxCost $${options.maxCost}`);
640
+ throw new errors_1.OneShotError(`Quote $${quoteData.context.estimated_cost} exceeds maxCost $${options.maxCost}`);
679
641
  }
680
642
  const paymentInfo = {
681
643
  protocol: 'x402',
@@ -691,7 +653,7 @@ class OneShot {
691
653
  const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
692
654
  const execResp = await this.makeRequest('/v1/tools/browser', payload, auth, quoteData.context.quote_id, options.signal);
693
655
  if (execResp.status !== 202) {
694
- throw new ToolError('Browser task initiation failed', execResp.status, await execResp.text());
656
+ throw new errors_1.ToolError('Browser task initiation failed', execResp.status, await execResp.text());
695
657
  }
696
658
  const result = await execResp.json();
697
659
  this.log(`Browser task initiated: ${result.request_id}`);
@@ -717,7 +679,7 @@ class OneShot {
717
679
  body: JSON.stringify({ name }),
718
680
  });
719
681
  if (!response.ok) {
720
- throw new ToolError('Failed to create browser profile', response.status, await response.text());
682
+ throw new errors_1.ToolError('Failed to create browser profile', response.status, await response.text());
721
683
  }
722
684
  return response.json();
723
685
  }
@@ -737,7 +699,7 @@ class OneShot {
737
699
  headers: this.headers(),
738
700
  });
739
701
  if (!response.ok) {
740
- throw new ToolError('Failed to list browser profiles', response.status, await response.text());
702
+ throw new errors_1.ToolError('Failed to list browser profiles', response.status, await response.text());
741
703
  }
742
704
  const data = await response.json();
743
705
  return data.profiles;
@@ -757,7 +719,7 @@ class OneShot {
757
719
  headers: this.headers(),
758
720
  });
759
721
  if (!response.ok) {
760
- throw new ToolError('Failed to delete browser profile', response.status, await response.text());
722
+ throw new errors_1.ToolError('Failed to delete browser profile', response.status, await response.text());
761
723
  }
762
724
  }
763
725
  /**
@@ -806,7 +768,7 @@ class OneShot {
806
768
  headers: this.headers()
807
769
  });
808
770
  if (!response.ok) {
809
- throw new ToolError('Failed to list SMS inbox', response.status, await response.text());
771
+ throw new errors_1.ToolError('Failed to list SMS inbox', response.status, await response.text());
810
772
  }
811
773
  return response.json();
812
774
  }
@@ -825,10 +787,10 @@ class OneShot {
825
787
  headers: this.headers()
826
788
  });
827
789
  if (response.status === 404) {
828
- throw new ToolError('SMS message not found', 404, 'Message not found');
790
+ throw new errors_1.ToolError('SMS message not found', 404, 'Message not found');
829
791
  }
830
792
  if (!response.ok) {
831
- throw new ToolError('Failed to get SMS message', response.status, await response.text());
793
+ throw new errors_1.ToolError('Failed to get SMS message', response.status, await response.text());
832
794
  }
833
795
  return response.json();
834
796
  }
@@ -855,7 +817,7 @@ class OneShot {
855
817
  headers: this.headers()
856
818
  });
857
819
  if (!response.ok) {
858
- throw new ToolError('Failed to list notifications', response.status, await response.text());
820
+ throw new errors_1.ToolError('Failed to list notifications', response.status, await response.text());
859
821
  }
860
822
  return response.json();
861
823
  }
@@ -874,10 +836,10 @@ class OneShot {
874
836
  headers: this.headers()
875
837
  });
876
838
  if (response.status === 404) {
877
- throw new ToolError('Notification not found', 404, 'Notification not found');
839
+ throw new errors_1.ToolError('Notification not found', 404, 'Notification not found');
878
840
  }
879
841
  if (!response.ok) {
880
- throw new ToolError('Failed to mark notification as read', response.status, await response.text());
842
+ throw new errors_1.ToolError('Failed to mark notification as read', response.status, await response.text());
881
843
  }
882
844
  }
883
845
  async getUnifiedBalance() {
@@ -885,7 +847,7 @@ class OneShot {
885
847
  headers: this.headers()
886
848
  });
887
849
  if (!response.ok) {
888
- throw new ToolError('Failed to fetch balance', response.status, await response.text());
850
+ throw new errors_1.ToolError('Failed to fetch balance', response.status, await response.text());
889
851
  }
890
852
  return response.json();
891
853
  }
@@ -943,21 +905,21 @@ class OneShot {
943
905
  if (options.schedule)
944
906
  payload.schedule = options.schedule;
945
907
  // First call: get quote (402)
946
- const quoteResp = await this.makeRequest('/v1/compute', payload, undefined, undefined, options.signal);
908
+ const quoteResp = await this.makeRequest('/v1/compute', payload, undefined, undefined, options.signal, undefined, this.maxCostHeader(options.maxCost));
947
909
  if (quoteResp.status === 400) {
948
910
  const errorData = await quoteResp.json();
949
911
  if (errorData.error === 'content_blocked') {
950
- throw new ContentBlockedError(errorData.message, []);
912
+ throw new errors_1.ContentBlockedError(errorData.message, []);
951
913
  }
952
- throw new ValidationError(errorData.message || 'Invalid request', 'request');
914
+ throw new errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
953
915
  }
954
916
  if (quoteResp.status !== 402) {
955
- throw new ToolError('Expected 402 for compute quote', quoteResp.status, await quoteResp.text());
917
+ throw new errors_1.ToolError('Expected 402 for compute quote', quoteResp.status, await quoteResp.text());
956
918
  }
957
919
  const quoteData = await quoteResp.json();
958
920
  this.log(`Compute quote: $${quoteData.context.total_budget} — ${quoteData.context.objective_summary}`);
959
921
  if (options.maxCost && parseFloat(quoteData.context.total_budget) > options.maxCost) {
960
- throw new OneShotError(`Quote $${quoteData.context.total_budget} exceeds maxCost $${options.maxCost}`);
922
+ throw new errors_1.OneShotError(`Quote $${quoteData.context.total_budget} exceeds maxCost $${options.maxCost}`);
961
923
  }
962
924
  const paymentInfo = {
963
925
  protocol: 'x402',
@@ -973,7 +935,7 @@ class OneShot {
973
935
  const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
974
936
  const createResp = await this.makeRequest('/v1/compute', payload, auth, quoteData.context.quote_id, options.signal);
975
937
  if (createResp.status !== 202) {
976
- throw new ToolError('Compute goal creation failed', createResp.status, await createResp.text());
938
+ throw new errors_1.ToolError('Compute goal creation failed', createResp.status, await createResp.text());
977
939
  }
978
940
  return createResp.json();
979
941
  }
@@ -992,10 +954,10 @@ class OneShot {
992
954
  headers: this.headers()
993
955
  });
994
956
  if (response.status === 404) {
995
- throw new ToolError('Goal not found', 404, 'Goal not found');
957
+ throw new errors_1.ToolError('Goal not found', 404, 'Goal not found');
996
958
  }
997
959
  if (!response.ok) {
998
- throw new ToolError('Failed to get compute goal', response.status, await response.text());
960
+ throw new errors_1.ToolError('Failed to get compute goal', response.status, await response.text());
999
961
  }
1000
962
  const json = await response.json();
1001
963
  return json.data;
@@ -1017,7 +979,7 @@ class OneShot {
1017
979
  headers: this.headers()
1018
980
  });
1019
981
  if (!response.ok) {
1020
- throw new ToolError('Failed to get compute tasks', response.status, await response.text());
982
+ throw new errors_1.ToolError('Failed to get compute tasks', response.status, await response.text());
1021
983
  }
1022
984
  const json = await response.json();
1023
985
  return json.data;
@@ -1037,10 +999,10 @@ class OneShot {
1037
999
  headers: this.headers()
1038
1000
  });
1039
1001
  if (response.status === 404) {
1040
- throw new ToolError('Budget not found', 404, 'Budget not found for this goal');
1002
+ throw new errors_1.ToolError('Budget not found', 404, 'Budget not found for this goal');
1041
1003
  }
1042
1004
  if (!response.ok) {
1043
- throw new ToolError('Failed to get compute budget', response.status, await response.text());
1005
+ throw new errors_1.ToolError('Failed to get compute budget', response.status, await response.text());
1044
1006
  }
1045
1007
  const json = await response.json();
1046
1008
  return json.data;
@@ -1062,10 +1024,10 @@ class OneShot {
1062
1024
  body: JSON.stringify({ reason })
1063
1025
  });
1064
1026
  if (response.status === 404) {
1065
- throw new ToolError('Goal not found', 404, 'Goal not found');
1027
+ throw new errors_1.ToolError('Goal not found', 404, 'Goal not found');
1066
1028
  }
1067
1029
  if (!response.ok) {
1068
- throw new ToolError('Failed to cancel compute goal', response.status, await response.text());
1030
+ throw new errors_1.ToolError('Failed to cancel compute goal', response.status, await response.text());
1069
1031
  }
1070
1032
  const json = await response.json();
1071
1033
  return json.data;
@@ -1091,10 +1053,10 @@ class OneShot {
1091
1053
  body: JSON.stringify(input)
1092
1054
  });
1093
1055
  if (response.status === 404) {
1094
- throw new ToolError('Goal or task not found', 404, await response.text());
1056
+ throw new errors_1.ToolError('Goal or task not found', 404, await response.text());
1095
1057
  }
1096
1058
  if (!response.ok) {
1097
- throw new ToolError('Failed to respond to compute task', response.status, await response.text());
1059
+ throw new errors_1.ToolError('Failed to respond to compute task', response.status, await response.text());
1098
1060
  }
1099
1061
  const json = await response.json();
1100
1062
  return json.data;
@@ -1115,7 +1077,7 @@ class OneShot {
1115
1077
  body: JSON.stringify({ reason })
1116
1078
  });
1117
1079
  if (!response.ok) {
1118
- throw new ToolError('Failed to pause compute goal', response.status, await response.text());
1080
+ throw new errors_1.ToolError('Failed to pause compute goal', response.status, await response.text());
1119
1081
  }
1120
1082
  const json = await response.json();
1121
1083
  return json.data;
@@ -1137,13 +1099,16 @@ class OneShot {
1137
1099
  body: JSON.stringify({})
1138
1100
  });
1139
1101
  if (!response.ok) {
1140
- throw new ToolError('Failed to resume compute goal', response.status, await response.text());
1102
+ throw new errors_1.ToolError('Failed to resume compute goal', response.status, await response.text());
1141
1103
  }
1142
1104
  const json = await response.json();
1143
1105
  return json.data;
1144
1106
  }
1145
1107
  /**
1146
- * Top up budget for a recurring compute goal
1108
+ * Top up budget for a recurring compute goal.
1109
+ *
1110
+ * Uses the quote-then-pay flow: the first call gets a 402 with the top-up price,
1111
+ * the second call (with payment) credits the budget. The amount IS the price.
1147
1112
  *
1148
1113
  * @example
1149
1114
  * ```typescript
@@ -1154,17 +1119,33 @@ class OneShot {
1154
1119
  async fundComputeGoal(goalId, amount) {
1155
1120
  this.validate(goalId, 'goalId');
1156
1121
  if (!amount || amount <= 0) {
1157
- throw new ValidationError('amount must be a positive number', 'amount');
1122
+ throw new errors_1.ValidationError('amount must be a positive number', 'amount');
1158
1123
  }
1159
- const response = await fetch(`${this.baseUrl}/v1/compute/${goalId}/fund`, {
1160
- method: 'POST',
1161
- headers: { 'Content-Type': 'application/json', ...this.headers() },
1162
- body: JSON.stringify({ amount })
1163
- });
1164
- if (!response.ok) {
1165
- throw new ToolError('Failed to fund compute goal', response.status, await response.text());
1124
+ const path = `/v1/compute/${goalId}/fund`;
1125
+ const payload = { amount };
1126
+ // First call: get quote (402)
1127
+ const quoteResp = await this.makeRequest(path, payload);
1128
+ if (quoteResp.status !== 402) {
1129
+ throw new errors_1.ToolError('Expected 402 for compute fund quote', quoteResp.status, await quoteResp.text());
1166
1130
  }
1167
- const json = await response.json();
1131
+ const quoteData = await quoteResp.json();
1132
+ this.log(`Compute fund quote: $${quoteData.payment_request.amount} to top up ${goalId}`);
1133
+ const paymentInfo = {
1134
+ protocol: 'x402',
1135
+ network: `eip155:${quoteData.payment_request.chain_id}`,
1136
+ payTo: quoteData.payment_request.recipient,
1137
+ amount: quoteData.payment_request.amount,
1138
+ currency: 'USD',
1139
+ facilitator_url: this.baseUrl,
1140
+ token: { address: quoteData.payment_request.token_address, symbol: 'USDC', decimals: 6 }
1141
+ };
1142
+ const { accepted, resource, extensions } = await this.getAcceptedRequirements(quoteResp, path, payload, quoteData.context.quote_id);
1143
+ const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
1144
+ const fundResp = await this.makeRequest(path, payload, auth, quoteData.context.quote_id);
1145
+ if (!fundResp.ok) {
1146
+ throw new errors_1.ToolError('Failed to fund compute goal', fundResp.status, await fundResp.text());
1147
+ }
1148
+ const json = await fundResp.json();
1168
1149
  return json.data;
1169
1150
  }
1170
1151
  // ---------------------------------------------------------------------------
@@ -1191,7 +1172,7 @@ class OneShot {
1191
1172
  headers: this.headers()
1192
1173
  });
1193
1174
  if (!response.ok) {
1194
- throw new ToolError('Failed to get spend breakdown', response.status, await response.text());
1175
+ throw new errors_1.ToolError('Failed to get spend breakdown', response.status, await response.text());
1195
1176
  }
1196
1177
  return response.json();
1197
1178
  }
@@ -1213,7 +1194,7 @@ class OneShot {
1213
1194
  headers: this.headers()
1214
1195
  });
1215
1196
  if (!response.ok) {
1216
- throw new ToolError('Failed to get RoCS', response.status, await response.text());
1197
+ throw new errors_1.ToolError('Failed to get RoCS', response.status, await response.text());
1217
1198
  }
1218
1199
  return response.json();
1219
1200
  }
@@ -1241,7 +1222,7 @@ class OneShot {
1241
1222
  headers: this.headers()
1242
1223
  });
1243
1224
  if (!response.ok) {
1244
- throw new ToolError('Failed to list receipts', response.status, await response.text());
1225
+ throw new errors_1.ToolError('Failed to list receipts', response.status, await response.text());
1245
1226
  }
1246
1227
  return response.json();
1247
1228
  }
@@ -1265,10 +1246,10 @@ class OneShot {
1265
1246
  body: JSON.stringify(valueTag)
1266
1247
  });
1267
1248
  if (response.status === 404) {
1268
- throw new ToolError('Receipt not found', 404, 'Receipt not found or not owned by this agent');
1249
+ throw new errors_1.ToolError('Receipt not found', 404, 'Receipt not found or not owned by this agent');
1269
1250
  }
1270
1251
  if (!response.ok) {
1271
- throw new ToolError('Failed to tag receipt value', response.status, await response.text());
1252
+ throw new errors_1.ToolError('Failed to tag receipt value', response.status, await response.text());
1272
1253
  }
1273
1254
  }
1274
1255
  // ---------------------------------------------------------------------------
@@ -1280,7 +1261,7 @@ class OneShot {
1280
1261
  }
1281
1262
  validate(value, field) {
1282
1263
  if (!value)
1283
- throw new ValidationError(`${field} is required`, field);
1264
+ throw new errors_1.ValidationError(`${field} is required`, field);
1284
1265
  }
1285
1266
  headers() {
1286
1267
  return {
@@ -1288,8 +1269,21 @@ class OneShot {
1288
1269
  'X-OneShot-SDK-Version': SDK_VERSION
1289
1270
  };
1290
1271
  }
1272
+ /**
1273
+ * Header that asks the API to reject the request when the computed quote
1274
+ * exceeds the caller-supplied cap (commit 8f328a7). The SDK still does its
1275
+ * own local `quote.total > maxCost` check after the 402 returns — this is
1276
+ * a server-side enforcement layer so non-SDK callers (MCP server, custom
1277
+ * integrations) can't ignore the cap.
1278
+ */
1279
+ maxCostHeader(maxCost) {
1280
+ if (!maxCost || maxCost <= 0)
1281
+ return undefined;
1282
+ return { 'X-Max-Cost-USDC': maxCost.toString() };
1283
+ }
1291
1284
  async executeToolRequest(endpoint, options, quoteId) {
1292
- const { signal, onStatusUpdate, wait = true, waitForPhones, phoneTimeoutSec, ...payload } = options;
1285
+ const { signal, onStatusUpdate, wait = true, waitForPhones, phoneTimeoutSec, maxCost, ...payload } = options;
1286
+ const extraHeaders = this.maxCostHeader(maxCost);
1293
1287
  // Validate memo
1294
1288
  if (payload.memo !== undefined) {
1295
1289
  if (typeof payload.memo !== 'string' || payload.memo.trim().length === 0) {
@@ -1316,9 +1310,9 @@ class OneShot {
1316
1310
  }
1317
1311
  }
1318
1312
  if (signal?.aborted) {
1319
- throw new OneShotError('Operation cancelled');
1313
+ throw new errors_1.OneShotError('Operation cancelled');
1320
1314
  }
1321
- let response = await this.makeRequest(endpoint, payload, undefined, quoteId, signal);
1315
+ let response = await this.makeRequest(endpoint, payload, undefined, quoteId, signal, undefined, extraHeaders);
1322
1316
  // Handle 402 Payment Required
1323
1317
  if (response.status === 402) {
1324
1318
  // Parse x402 v2 PaymentRequired from PAYMENT-REQUIRED header
@@ -1338,10 +1332,10 @@ class OneShot {
1338
1332
  this.log(`Payment required: ${paymentInfo.amount} USDC`);
1339
1333
  this.checkAbortBeforePayment(signal);
1340
1334
  const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
1341
- response = await this.makeRequest(endpoint, payload, auth, quoteId, signal);
1335
+ response = await this.makeRequest(endpoint, payload, auth, quoteId, signal, undefined, extraHeaders);
1342
1336
  }
1343
1337
  if (!response.ok) {
1344
- throw new ToolError('Tool request failed', response.status, await response.text());
1338
+ throw new errors_1.ToolError('Tool request failed', response.status, await response.text());
1345
1339
  }
1346
1340
  const result = await response.json();
1347
1341
  // Handle async jobs
@@ -1364,12 +1358,12 @@ class OneShot {
1364
1358
  this.log('WebSocket unavailable, falling back to HTTP polling');
1365
1359
  result = await this.pollJobHttp(requestId, timeoutSec, signal, onStatusUpdate);
1366
1360
  }
1367
- // Optional second phase: keep polling for Apollo phone-reveal callbacks.
1361
+ // Optional second phase: keep polling for the async phone-reveal webhook.
1368
1362
  // Only kicks in when the caller explicitly opts in AND the result still
1369
- // has phones_pending=true (set by the worker when Apollo enrichment is
1370
- // pending an async webhook). This is opt-in so existing callers see no
1371
- // behavior change — the WebSocket/HTTP polls return as soon as the worker
1372
- // sets status=completed, with phones=null.
1363
+ // has phones_pending=true (set by the worker when the upstream enrichment
1364
+ // has a pending async phone callback). This is opt-in so existing callers
1365
+ // see no behavior change — the WebSocket/HTTP polls return as soon as the
1366
+ // worker sets status=completed, with phones=null.
1373
1367
  //
1374
1368
  // Pass the existing result as the initial fallback: if the polling GETs
1375
1369
  // hit a transient error before any successful refresh, we return what we
@@ -1380,9 +1374,9 @@ class OneShot {
1380
1374
  return result;
1381
1375
  }
1382
1376
  /**
1383
- * Detects whether a job result is still waiting for Apollo's async phone
1384
- * reveal. The worker sets `result.phones_pending=true` when an enrichment
1385
- * was Apollo-sourced and Apollo's webhook URL is configured. The webhook
1377
+ * Detects whether a job result is still waiting for an async phone reveal.
1378
+ * The worker sets `result.phones_pending=true` when the upstream enrichment
1379
+ * has a pending async webhook and the webhook URL is configured; the webhook
1386
1380
  * handler flips it to false (or removes it) once phones arrive.
1387
1381
  *
1388
1382
  * Tolerates two shapes: the unwrapped result `{phones_pending}` and the
@@ -1400,11 +1394,12 @@ class OneShot {
1400
1394
  return false;
1401
1395
  }
1402
1396
  /**
1403
- * Slow poll loop that waits for Apollo phone-reveal callbacks to arrive
1404
- * via the webhook handler (which UPDATEs jobs.result_data.result.phones).
1405
- * Polls GET /v1/requests/{id} every 5s — Apollo says "several minutes" so
1406
- * a tight loop wastes API calls. Returns the latest snapshot whether or
1407
- * not phones arrived (consumer can re-check `phones_pending`).
1397
+ * Slow poll loop that waits for async phone-reveal callbacks to arrive via
1398
+ * the webhook handler (which UPDATEs jobs.result_data.result.phones). Polls
1399
+ * GET /v1/requests/{id} every 5s — the upstream provider can take several
1400
+ * minutes to deliver, so a tight loop just wastes API calls. Returns the
1401
+ * latest snapshot whether or not phones arrived (consumer can re-check
1402
+ * `phones_pending`).
1408
1403
  */
1409
1404
  async _pollForPhones(requestId, timeoutSec, signal, initialResult) {
1410
1405
  const deadline = Date.now() + timeoutSec * 1000;
@@ -1414,7 +1409,7 @@ class OneShot {
1414
1409
  let lastResult = initialResult;
1415
1410
  while (Date.now() < deadline) {
1416
1411
  if (signal?.aborted)
1417
- throw new OneShotError('Operation cancelled');
1412
+ throw new errors_1.OneShotError('Operation cancelled');
1418
1413
  try {
1419
1414
  const resp = await fetch(`${this.baseUrl}/v1/requests/${requestId}`, {
1420
1415
  headers: this.headers(),
@@ -1425,7 +1420,7 @@ class OneShot {
1425
1420
  // whatever we last had so consumers don't lose the sync result.
1426
1421
  if (lastResult !== undefined)
1427
1422
  return lastResult;
1428
- throw new ToolError('Failed to check job status', resp.status, await resp.text());
1423
+ throw new errors_1.ToolError('Failed to check job status', resp.status, await resp.text());
1429
1424
  }
1430
1425
  const job = await resp.json();
1431
1426
  lastResult = (job.result ?? job);
@@ -1434,7 +1429,7 @@ class OneShot {
1434
1429
  }
1435
1430
  }
1436
1431
  catch (err) {
1437
- if (err instanceof OneShotError)
1432
+ if (err instanceof errors_1.OneShotError)
1438
1433
  throw err;
1439
1434
  if (lastResult !== undefined)
1440
1435
  return lastResult;
@@ -1497,7 +1492,7 @@ class OneShot {
1497
1492
  signal.addEventListener('abort', () => {
1498
1493
  settle(() => {
1499
1494
  cleanup();
1500
- reject(new OneShotError('Operation cancelled'));
1495
+ reject(new errors_1.OneShotError('Operation cancelled'));
1501
1496
  });
1502
1497
  }, { once: true });
1503
1498
  }
@@ -1538,7 +1533,7 @@ class OneShot {
1538
1533
  else if (msg.status === 'failed') {
1539
1534
  settle(() => {
1540
1535
  cleanup();
1541
- reject(new JobError(`Job failed: ${msg.error ?? 'Unknown'}`, requestId, String(msg.error ?? 'Unknown')));
1536
+ reject(new errors_1.JobError(`Job failed: ${msg.error ?? 'Unknown'}`, requestId, String(msg.error ?? 'Unknown')));
1542
1537
  });
1543
1538
  }
1544
1539
  else {
@@ -1574,14 +1569,14 @@ class OneShot {
1574
1569
  const maxRetries = 3;
1575
1570
  while (Date.now() - startTime < maxWaitMs) {
1576
1571
  if (signal?.aborted)
1577
- throw new OneShotError('Operation cancelled');
1572
+ throw new errors_1.OneShotError('Operation cancelled');
1578
1573
  try {
1579
1574
  const resp = await fetch(`${this.baseUrl}/v1/requests/${requestId}`, {
1580
1575
  headers: this.headers(),
1581
1576
  signal
1582
1577
  });
1583
1578
  if (!resp.ok) {
1584
- throw new ToolError('Failed to check job status', resp.status, await resp.text());
1579
+ throw new errors_1.ToolError('Failed to check job status', resp.status, await resp.text());
1585
1580
  }
1586
1581
  const job = await resp.json();
1587
1582
  if (job.status === 'completed') {
@@ -1594,42 +1589,43 @@ class OneShot {
1594
1589
  return result;
1595
1590
  }
1596
1591
  if (job.status === 'failed') {
1597
- throw new JobError(`Job failed: ${job.error ?? 'Unknown'}`, requestId, String(job.error ?? 'Unknown'));
1592
+ throw new errors_1.JobError(`Job failed: ${job.error ?? 'Unknown'}`, requestId, String(job.error ?? 'Unknown'));
1598
1593
  }
1599
1594
  onStatusUpdate?.(job.status, requestId);
1600
1595
  retries = 0;
1601
1596
  await this.sleep(pollInterval, signal);
1602
1597
  }
1603
1598
  catch (err) {
1604
- if (err instanceof OneShotError)
1599
+ if (err instanceof errors_1.OneShotError)
1605
1600
  throw err;
1606
1601
  if (++retries > maxRetries) {
1607
- throw new OneShotError(`Polling failed after ${maxRetries} retries: ${err}`);
1602
+ throw new errors_1.OneShotError(`Polling failed after ${maxRetries} retries: ${err}`);
1608
1603
  }
1609
1604
  const backoff = pollInterval * Math.pow(2, retries - 1);
1610
1605
  this.log(`Retry ${retries}/${maxRetries} in ${backoff}ms`);
1611
1606
  await this.sleep(backoff, signal);
1612
1607
  }
1613
1608
  }
1614
- throw new JobTimeoutError(requestId, Date.now() - startTime);
1609
+ throw new errors_1.JobTimeoutError(requestId, Date.now() - startTime);
1615
1610
  }
1616
1611
  sleep(ms, signal) {
1617
1612
  return new Promise((resolve, reject) => {
1618
1613
  if (signal?.aborted) {
1619
- return reject(new OneShotError('Operation cancelled'));
1614
+ return reject(new errors_1.OneShotError('Operation cancelled'));
1620
1615
  }
1621
1616
  const timer = setTimeout(resolve, ms);
1622
1617
  const onAbort = () => {
1623
1618
  clearTimeout(timer);
1624
- reject(new OneShotError('Operation cancelled'));
1619
+ reject(new errors_1.OneShotError('Operation cancelled'));
1625
1620
  };
1626
1621
  signal?.addEventListener('abort', onAbort, { once: true });
1627
1622
  });
1628
1623
  }
1629
- async makeRequest(endpoint, data, payment, quoteId, signal, timeoutMs) {
1624
+ async makeRequest(endpoint, data, payment, quoteId, signal, timeoutMs, extraHeaders) {
1630
1625
  const headers = {
1631
1626
  'Content-Type': 'application/json',
1632
- ...this.headers()
1627
+ ...this.headers(),
1628
+ ...(extraHeaders ?? {})
1633
1629
  };
1634
1630
  if (payment) {
1635
1631
  const paymentJson = JSON.stringify(payment);
@@ -1663,7 +1659,7 @@ class OneShot {
1663
1659
  }
1664
1660
  checkAbortBeforePayment(signal) {
1665
1661
  if (signal?.aborted) {
1666
- throw new OneShotError('Operation cancelled before payment');
1662
+ throw new errors_1.OneShotError('Operation cancelled before payment');
1667
1663
  }
1668
1664
  }
1669
1665
  /**