@oneshot-agent/sdk 0.16.1 → 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}`);
@@ -212,14 +162,26 @@ class OneShot {
212
162
  this.validate(options.to, 'to');
213
163
  this.validate(options.subject, 'subject');
214
164
  this.validate(options.body, 'body');
215
- const fromAddress = `agent@${options.from_domain ?? 'oneshotagent.com'}`;
165
+ const fromAddress = `${options.from_mailbox ?? 'agent'}@${options.from_domain ?? 'oneshotagent.com'}`;
216
166
  const quote = await this.tool('email/quote', {
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,
@@ -229,6 +191,9 @@ class OneShot {
229
191
  onStatusUpdate: options.onStatusUpdate,
230
192
  wait: options.wait
231
193
  };
194
+ if (options.from_name) {
195
+ payload.from_name = options.from_name;
196
+ }
232
197
  if (options.attachments?.length) {
233
198
  payload.attachments = options.attachments;
234
199
  }
@@ -243,14 +208,14 @@ class OneShot {
243
208
  }
244
209
  async enrichProfile(options) {
245
210
  if (!options.linkedin_url && !options.email && !options.name) {
246
- 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');
247
212
  }
248
213
  return this.tool('enrich/profile', { ...options });
249
214
  }
250
215
  async findEmail(options) {
251
216
  this.validate(options.company_domain, 'company_domain');
252
217
  if (!options.full_name && !(options.first_name && options.last_name)) {
253
- 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');
254
219
  }
255
220
  return this.tool('enrich/email', { ...options });
256
221
  }
@@ -260,13 +225,13 @@ class OneShot {
260
225
  }
261
226
  async deepResearchPerson(options) {
262
227
  if (!options.email && !options.social_media_url && !options.name) {
263
- 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');
264
229
  }
265
230
  return this.tool('research/person', { ...options });
266
231
  }
267
232
  async socialProfiles(options) {
268
233
  if (!options.email && !options.social_media_url) {
269
- 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');
270
235
  }
271
236
  return this.tool('research/social', { ...options });
272
237
  }
@@ -281,7 +246,7 @@ class OneShot {
281
246
  }
282
247
  async personInterests(options) {
283
248
  if (!options.email && !options.phone && !options.social_media_url) {
284
- 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');
285
250
  }
286
251
  return this.tool('research/interests', { ...options });
287
252
  }
@@ -302,7 +267,7 @@ class OneShot {
302
267
  headers: this.headers()
303
268
  });
304
269
  if (!response.ok) {
305
- 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());
306
271
  }
307
272
  return response.json();
308
273
  }
@@ -312,10 +277,10 @@ class OneShot {
312
277
  headers: this.headers()
313
278
  });
314
279
  if (response.status === 404) {
315
- throw new ToolError('Email not found', 404, 'Email not found');
280
+ throw new errors_1.ToolError('Email not found', 404, 'Email not found');
316
281
  }
317
282
  if (!response.ok) {
318
- 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());
319
284
  }
320
285
  return response.json();
321
286
  }
@@ -330,14 +295,14 @@ class OneShot {
330
295
  variant_id: options.variant_id
331
296
  };
332
297
  // Commerce quotes can take up to 90s due to Rye API polling
333
- 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));
334
299
  if (quoteResp.status !== 402) {
335
- 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());
336
301
  }
337
302
  const quoteData = await quoteResp.json();
338
303
  this.log(`Commerce quote: $${quoteData.context.total} for "${quoteData.context.product_title}"`);
339
304
  if (options.maxCost && parseFloat(quoteData.context.total) > options.maxCost) {
340
- 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}`);
341
306
  }
342
307
  const paymentInfo = {
343
308
  protocol: 'x402',
@@ -353,7 +318,7 @@ class OneShot {
353
318
  const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
354
319
  const buyResp = await this.makeRequest('/v1/tools/commerce/buy', payload, auth, quoteData.context.quote_id, options.signal, 60000);
355
320
  if (buyResp.status !== 202) {
356
- 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());
357
322
  }
358
323
  const result = await buyResp.json();
359
324
  this.log(`Order submitted: ${result.request_id}`);
@@ -392,13 +357,13 @@ class OneShot {
392
357
  this.validate(options.target_number, 'target_number');
393
358
  // Check for empty arrays
394
359
  if (Array.isArray(options.target_number) && options.target_number.length === 0) {
395
- 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');
396
361
  }
397
362
  if (options.objective.length < 10) {
398
- 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');
399
364
  }
400
365
  if (options.max_duration_minutes !== undefined && (options.max_duration_minutes < 1 || options.max_duration_minutes > 30)) {
401
- 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');
402
367
  }
403
368
  const payload = {
404
369
  objective: options.objective,
@@ -414,26 +379,26 @@ class OneShot {
414
379
  if (options.max_duration_minutes)
415
380
  payload.max_duration_minutes = options.max_duration_minutes;
416
381
  // Voice uses quote-to-pay flow (402 -> payment -> 202)
417
- 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));
418
383
  // Handle error responses before expecting 402
419
384
  if (quoteResp.status === 400) {
420
385
  const errorData = await quoteResp.json();
421
386
  if (errorData.error === 'content_blocked') {
422
- throw new ContentBlockedError(errorData.message, errorData.categories || []);
387
+ throw new errors_1.ContentBlockedError(errorData.message, errorData.categories || []);
423
388
  }
424
389
  if (errorData.error === 'emergency_number_blocked') {
425
- throw new EmergencyNumberError(errorData.message, errorData.blocked_number || '');
390
+ throw new errors_1.EmergencyNumberError(errorData.message, errorData.blocked_number || '');
426
391
  }
427
- throw new ValidationError(errorData.message || 'Invalid request', 'request');
392
+ throw new errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
428
393
  }
429
394
  if (quoteResp.status !== 402) {
430
- 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());
431
396
  }
432
397
  const quoteData = await quoteResp.json();
433
398
  this.log(`Voice quote: $${quoteData.context.total} for ${quoteData.context.estimated_duration_minutes}min call`);
434
399
  this.log(`Objective summary: ${quoteData.context.objective_summary}`);
435
400
  if (options.maxCost && parseFloat(quoteData.context.total) > options.maxCost) {
436
- 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}`);
437
402
  }
438
403
  const paymentInfo = {
439
404
  protocol: 'x402',
@@ -449,7 +414,7 @@ class OneShot {
449
414
  const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
450
415
  const callResp = await this.makeRequest('/v1/tools/voice/call', payload, auth, quoteData.context.quote_id, options.signal);
451
416
  if (callResp.status !== 202) {
452
- 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());
453
418
  }
454
419
  const result = await callResp.json();
455
420
  this.log(`Call initiated: ${result.request_id}`);
@@ -475,17 +440,17 @@ class OneShot {
475
440
  this.validate(options.to_number, 'to_number');
476
441
  // Check for empty arrays
477
442
  if (Array.isArray(options.to_number) && options.to_number.length === 0) {
478
- 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');
479
444
  }
480
445
  if (options.message.length < 1) {
481
- throw new ValidationError('Message is required', 'message');
446
+ throw new errors_1.ValidationError('Message is required', 'message');
482
447
  }
483
448
  if (options.message.length > 1600) {
484
- 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');
485
450
  }
486
451
  const recipientCount = Array.isArray(options.to_number) ? options.to_number.length : 1;
487
452
  if (recipientCount > 10) {
488
- throw new ValidationError('Maximum 10 recipients allowed', 'to_number');
453
+ throw new errors_1.ValidationError('Maximum 10 recipients allowed', 'to_number');
489
454
  }
490
455
  const payload = {
491
456
  message: options.message,
@@ -495,25 +460,25 @@ class OneShot {
495
460
  wait: options.wait
496
461
  };
497
462
  // SMS uses quote-to-pay flow (402 -> payment -> 202)
498
- 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));
499
464
  // Handle error responses before expecting 402
500
465
  if (quoteResp.status === 400) {
501
466
  const errorData = await quoteResp.json();
502
467
  if (errorData.error === 'content_blocked') {
503
- throw new ContentBlockedError(errorData.message, errorData.categories || []);
468
+ throw new errors_1.ContentBlockedError(errorData.message, errorData.categories || []);
504
469
  }
505
470
  if (errorData.error === 'emergency_number_blocked') {
506
- throw new EmergencyNumberError(errorData.message, errorData.blocked_number || '');
471
+ throw new errors_1.EmergencyNumberError(errorData.message, errorData.blocked_number || '');
507
472
  }
508
- throw new ValidationError(errorData.message || 'Invalid request', 'request');
473
+ throw new errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
509
474
  }
510
475
  if (quoteResp.status !== 402) {
511
- 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());
512
477
  }
513
478
  const quoteData = await quoteResp.json();
514
479
  this.log(`SMS quote: $${quoteData.context.total} for ${quoteData.context.segment_count} segment(s) to ${recipientCount} recipient(s)`);
515
480
  if (options.maxCost && parseFloat(quoteData.context.total) > options.maxCost) {
516
- 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}`);
517
482
  }
518
483
  const paymentInfo = {
519
484
  protocol: 'x402',
@@ -529,7 +494,7 @@ class OneShot {
529
494
  const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
530
495
  const sendResp = await this.makeRequest('/v1/tools/sms/send', payload, auth, quoteData.context.quote_id, options.signal);
531
496
  if (sendResp.status !== 202) {
532
- 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());
533
498
  }
534
499
  const result = await sendResp.json();
535
500
  this.log(`SMS queued: ${result.request_id}`);
@@ -559,7 +524,7 @@ class OneShot {
559
524
  this.validate(options.product?.name, 'product.name');
560
525
  this.validate(options.product?.description, 'product.description');
561
526
  if (options.product.description.length < 10) {
562
- 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');
563
528
  }
564
529
  const payload = {
565
530
  type: options.type ?? 'saas',
@@ -583,19 +548,19 @@ class OneShot {
583
548
  if (options.build_id)
584
549
  payload.build_id = options.build_id;
585
550
  // Build uses quote-to-pay flow (402 -> payment -> 202)
586
- 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));
587
552
  if (quoteResp.status === 400) {
588
553
  const errorData = await quoteResp.json();
589
- throw new ValidationError(errorData.message || 'Invalid request', 'request');
554
+ throw new errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
590
555
  }
591
556
  if (quoteResp.status !== 402) {
592
- 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());
593
558
  }
594
559
  const quoteData = await quoteResp.json();
595
560
  this.log(`Build quote: $${quoteData.context.pricing.total} for "${quoteData.context.product_name}"`);
596
561
  this.log(`Type: ${quoteData.context.analysis.inferred_type}, Sections: ${quoteData.context.analysis.estimated_sections}`);
597
562
  if (options.maxCost && parseFloat(quoteData.context.pricing.total) > options.maxCost) {
598
- 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}`);
599
564
  }
600
565
  const paymentInfo = {
601
566
  protocol: 'x402',
@@ -611,7 +576,7 @@ class OneShot {
611
576
  const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
612
577
  const buildResp = await this.makeRequest('/v1/tools/build', payload, auth, quoteData.context.quote_id, options.signal);
613
578
  if (buildResp.status !== 202) {
614
- 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());
615
580
  }
616
581
  const result = await buildResp.json();
617
582
  this.log(`Build initiated: ${result.request_id}`);
@@ -635,10 +600,10 @@ class OneShot {
635
600
  async browser(options) {
636
601
  this.validate(options.task, 'task');
637
602
  if (options.task.length < 10) {
638
- 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');
639
604
  }
640
605
  if (options.max_steps !== undefined && (options.max_steps < 1 || options.max_steps > 100)) {
641
- 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');
642
607
  }
643
608
  const payload = {
644
609
  task: options.task,
@@ -661,18 +626,18 @@ class OneShot {
661
626
  if (options.max_steps)
662
627
  payload.max_steps = options.max_steps;
663
628
  // Browser uses quote-to-pay flow (402 -> payment -> 202)
664
- 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));
665
630
  if (quoteResp.status === 400) {
666
631
  const errorData = await quoteResp.json();
667
- throw new ValidationError(errorData.message || 'Invalid request', 'request');
632
+ throw new errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
668
633
  }
669
634
  if (quoteResp.status !== 402) {
670
- 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());
671
636
  }
672
637
  const quoteData = await quoteResp.json();
673
638
  this.log(`Browser quote: $${quoteData.context.estimated_cost} for ~${quoteData.context.estimated_steps} steps`);
674
639
  if (options.maxCost && parseFloat(quoteData.context.estimated_cost) > options.maxCost) {
675
- 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}`);
676
641
  }
677
642
  const paymentInfo = {
678
643
  protocol: 'x402',
@@ -688,7 +653,7 @@ class OneShot {
688
653
  const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
689
654
  const execResp = await this.makeRequest('/v1/tools/browser', payload, auth, quoteData.context.quote_id, options.signal);
690
655
  if (execResp.status !== 202) {
691
- 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());
692
657
  }
693
658
  const result = await execResp.json();
694
659
  this.log(`Browser task initiated: ${result.request_id}`);
@@ -714,7 +679,7 @@ class OneShot {
714
679
  body: JSON.stringify({ name }),
715
680
  });
716
681
  if (!response.ok) {
717
- 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());
718
683
  }
719
684
  return response.json();
720
685
  }
@@ -734,7 +699,7 @@ class OneShot {
734
699
  headers: this.headers(),
735
700
  });
736
701
  if (!response.ok) {
737
- 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());
738
703
  }
739
704
  const data = await response.json();
740
705
  return data.profiles;
@@ -754,7 +719,7 @@ class OneShot {
754
719
  headers: this.headers(),
755
720
  });
756
721
  if (!response.ok) {
757
- 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());
758
723
  }
759
724
  }
760
725
  /**
@@ -803,7 +768,7 @@ class OneShot {
803
768
  headers: this.headers()
804
769
  });
805
770
  if (!response.ok) {
806
- 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());
807
772
  }
808
773
  return response.json();
809
774
  }
@@ -822,10 +787,10 @@ class OneShot {
822
787
  headers: this.headers()
823
788
  });
824
789
  if (response.status === 404) {
825
- 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');
826
791
  }
827
792
  if (!response.ok) {
828
- 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());
829
794
  }
830
795
  return response.json();
831
796
  }
@@ -852,7 +817,7 @@ class OneShot {
852
817
  headers: this.headers()
853
818
  });
854
819
  if (!response.ok) {
855
- 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());
856
821
  }
857
822
  return response.json();
858
823
  }
@@ -871,10 +836,10 @@ class OneShot {
871
836
  headers: this.headers()
872
837
  });
873
838
  if (response.status === 404) {
874
- throw new ToolError('Notification not found', 404, 'Notification not found');
839
+ throw new errors_1.ToolError('Notification not found', 404, 'Notification not found');
875
840
  }
876
841
  if (!response.ok) {
877
- 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());
878
843
  }
879
844
  }
880
845
  async getUnifiedBalance() {
@@ -882,7 +847,7 @@ class OneShot {
882
847
  headers: this.headers()
883
848
  });
884
849
  if (!response.ok) {
885
- 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());
886
851
  }
887
852
  return response.json();
888
853
  }
@@ -940,21 +905,21 @@ class OneShot {
940
905
  if (options.schedule)
941
906
  payload.schedule = options.schedule;
942
907
  // First call: get quote (402)
943
- 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));
944
909
  if (quoteResp.status === 400) {
945
910
  const errorData = await quoteResp.json();
946
911
  if (errorData.error === 'content_blocked') {
947
- throw new ContentBlockedError(errorData.message, []);
912
+ throw new errors_1.ContentBlockedError(errorData.message, []);
948
913
  }
949
- throw new ValidationError(errorData.message || 'Invalid request', 'request');
914
+ throw new errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
950
915
  }
951
916
  if (quoteResp.status !== 402) {
952
- 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());
953
918
  }
954
919
  const quoteData = await quoteResp.json();
955
920
  this.log(`Compute quote: $${quoteData.context.total_budget} — ${quoteData.context.objective_summary}`);
956
921
  if (options.maxCost && parseFloat(quoteData.context.total_budget) > options.maxCost) {
957
- 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}`);
958
923
  }
959
924
  const paymentInfo = {
960
925
  protocol: 'x402',
@@ -970,7 +935,7 @@ class OneShot {
970
935
  const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
971
936
  const createResp = await this.makeRequest('/v1/compute', payload, auth, quoteData.context.quote_id, options.signal);
972
937
  if (createResp.status !== 202) {
973
- 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());
974
939
  }
975
940
  return createResp.json();
976
941
  }
@@ -989,10 +954,10 @@ class OneShot {
989
954
  headers: this.headers()
990
955
  });
991
956
  if (response.status === 404) {
992
- throw new ToolError('Goal not found', 404, 'Goal not found');
957
+ throw new errors_1.ToolError('Goal not found', 404, 'Goal not found');
993
958
  }
994
959
  if (!response.ok) {
995
- 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());
996
961
  }
997
962
  const json = await response.json();
998
963
  return json.data;
@@ -1014,7 +979,7 @@ class OneShot {
1014
979
  headers: this.headers()
1015
980
  });
1016
981
  if (!response.ok) {
1017
- 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());
1018
983
  }
1019
984
  const json = await response.json();
1020
985
  return json.data;
@@ -1034,10 +999,10 @@ class OneShot {
1034
999
  headers: this.headers()
1035
1000
  });
1036
1001
  if (response.status === 404) {
1037
- 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');
1038
1003
  }
1039
1004
  if (!response.ok) {
1040
- 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());
1041
1006
  }
1042
1007
  const json = await response.json();
1043
1008
  return json.data;
@@ -1059,10 +1024,10 @@ class OneShot {
1059
1024
  body: JSON.stringify({ reason })
1060
1025
  });
1061
1026
  if (response.status === 404) {
1062
- throw new ToolError('Goal not found', 404, 'Goal not found');
1027
+ throw new errors_1.ToolError('Goal not found', 404, 'Goal not found');
1063
1028
  }
1064
1029
  if (!response.ok) {
1065
- 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());
1066
1031
  }
1067
1032
  const json = await response.json();
1068
1033
  return json.data;
@@ -1088,10 +1053,10 @@ class OneShot {
1088
1053
  body: JSON.stringify(input)
1089
1054
  });
1090
1055
  if (response.status === 404) {
1091
- 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());
1092
1057
  }
1093
1058
  if (!response.ok) {
1094
- 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());
1095
1060
  }
1096
1061
  const json = await response.json();
1097
1062
  return json.data;
@@ -1112,7 +1077,7 @@ class OneShot {
1112
1077
  body: JSON.stringify({ reason })
1113
1078
  });
1114
1079
  if (!response.ok) {
1115
- 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());
1116
1081
  }
1117
1082
  const json = await response.json();
1118
1083
  return json.data;
@@ -1134,13 +1099,16 @@ class OneShot {
1134
1099
  body: JSON.stringify({})
1135
1100
  });
1136
1101
  if (!response.ok) {
1137
- 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());
1138
1103
  }
1139
1104
  const json = await response.json();
1140
1105
  return json.data;
1141
1106
  }
1142
1107
  /**
1143
- * 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.
1144
1112
  *
1145
1113
  * @example
1146
1114
  * ```typescript
@@ -1151,17 +1119,33 @@ class OneShot {
1151
1119
  async fundComputeGoal(goalId, amount) {
1152
1120
  this.validate(goalId, 'goalId');
1153
1121
  if (!amount || amount <= 0) {
1154
- throw new ValidationError('amount must be a positive number', 'amount');
1122
+ throw new errors_1.ValidationError('amount must be a positive number', 'amount');
1155
1123
  }
1156
- const response = await fetch(`${this.baseUrl}/v1/compute/${goalId}/fund`, {
1157
- method: 'POST',
1158
- headers: { 'Content-Type': 'application/json', ...this.headers() },
1159
- body: JSON.stringify({ amount })
1160
- });
1161
- if (!response.ok) {
1162
- 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());
1163
1130
  }
1164
- 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();
1165
1149
  return json.data;
1166
1150
  }
1167
1151
  // ---------------------------------------------------------------------------
@@ -1188,7 +1172,7 @@ class OneShot {
1188
1172
  headers: this.headers()
1189
1173
  });
1190
1174
  if (!response.ok) {
1191
- 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());
1192
1176
  }
1193
1177
  return response.json();
1194
1178
  }
@@ -1210,7 +1194,7 @@ class OneShot {
1210
1194
  headers: this.headers()
1211
1195
  });
1212
1196
  if (!response.ok) {
1213
- 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());
1214
1198
  }
1215
1199
  return response.json();
1216
1200
  }
@@ -1238,7 +1222,7 @@ class OneShot {
1238
1222
  headers: this.headers()
1239
1223
  });
1240
1224
  if (!response.ok) {
1241
- 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());
1242
1226
  }
1243
1227
  return response.json();
1244
1228
  }
@@ -1262,10 +1246,10 @@ class OneShot {
1262
1246
  body: JSON.stringify(valueTag)
1263
1247
  });
1264
1248
  if (response.status === 404) {
1265
- 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');
1266
1250
  }
1267
1251
  if (!response.ok) {
1268
- 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());
1269
1253
  }
1270
1254
  }
1271
1255
  // ---------------------------------------------------------------------------
@@ -1277,7 +1261,7 @@ class OneShot {
1277
1261
  }
1278
1262
  validate(value, field) {
1279
1263
  if (!value)
1280
- throw new ValidationError(`${field} is required`, field);
1264
+ throw new errors_1.ValidationError(`${field} is required`, field);
1281
1265
  }
1282
1266
  headers() {
1283
1267
  return {
@@ -1285,8 +1269,21 @@ class OneShot {
1285
1269
  'X-OneShot-SDK-Version': SDK_VERSION
1286
1270
  };
1287
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
+ }
1288
1284
  async executeToolRequest(endpoint, options, quoteId) {
1289
- 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);
1290
1287
  // Validate memo
1291
1288
  if (payload.memo !== undefined) {
1292
1289
  if (typeof payload.memo !== 'string' || payload.memo.trim().length === 0) {
@@ -1313,9 +1310,9 @@ class OneShot {
1313
1310
  }
1314
1311
  }
1315
1312
  if (signal?.aborted) {
1316
- throw new OneShotError('Operation cancelled');
1313
+ throw new errors_1.OneShotError('Operation cancelled');
1317
1314
  }
1318
- let response = await this.makeRequest(endpoint, payload, undefined, quoteId, signal);
1315
+ let response = await this.makeRequest(endpoint, payload, undefined, quoteId, signal, undefined, extraHeaders);
1319
1316
  // Handle 402 Payment Required
1320
1317
  if (response.status === 402) {
1321
1318
  // Parse x402 v2 PaymentRequired from PAYMENT-REQUIRED header
@@ -1335,10 +1332,10 @@ class OneShot {
1335
1332
  this.log(`Payment required: ${paymentInfo.amount} USDC`);
1336
1333
  this.checkAbortBeforePayment(signal);
1337
1334
  const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
1338
- response = await this.makeRequest(endpoint, payload, auth, quoteId, signal);
1335
+ response = await this.makeRequest(endpoint, payload, auth, quoteId, signal, undefined, extraHeaders);
1339
1336
  }
1340
1337
  if (!response.ok) {
1341
- 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());
1342
1339
  }
1343
1340
  const result = await response.json();
1344
1341
  // Handle async jobs
@@ -1361,12 +1358,12 @@ class OneShot {
1361
1358
  this.log('WebSocket unavailable, falling back to HTTP polling');
1362
1359
  result = await this.pollJobHttp(requestId, timeoutSec, signal, onStatusUpdate);
1363
1360
  }
1364
- // Optional second phase: keep polling for Apollo phone-reveal callbacks.
1361
+ // Optional second phase: keep polling for the async phone-reveal webhook.
1365
1362
  // Only kicks in when the caller explicitly opts in AND the result still
1366
- // has phones_pending=true (set by the worker when Apollo enrichment is
1367
- // pending an async webhook). This is opt-in so existing callers see no
1368
- // behavior change — the WebSocket/HTTP polls return as soon as the worker
1369
- // 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.
1370
1367
  //
1371
1368
  // Pass the existing result as the initial fallback: if the polling GETs
1372
1369
  // hit a transient error before any successful refresh, we return what we
@@ -1377,9 +1374,9 @@ class OneShot {
1377
1374
  return result;
1378
1375
  }
1379
1376
  /**
1380
- * Detects whether a job result is still waiting for Apollo's async phone
1381
- * reveal. The worker sets `result.phones_pending=true` when an enrichment
1382
- * 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
1383
1380
  * handler flips it to false (or removes it) once phones arrive.
1384
1381
  *
1385
1382
  * Tolerates two shapes: the unwrapped result `{phones_pending}` and the
@@ -1397,11 +1394,12 @@ class OneShot {
1397
1394
  return false;
1398
1395
  }
1399
1396
  /**
1400
- * Slow poll loop that waits for Apollo phone-reveal callbacks to arrive
1401
- * via the webhook handler (which UPDATEs jobs.result_data.result.phones).
1402
- * Polls GET /v1/requests/{id} every 5s — Apollo says "several minutes" so
1403
- * a tight loop wastes API calls. Returns the latest snapshot whether or
1404
- * 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`).
1405
1403
  */
1406
1404
  async _pollForPhones(requestId, timeoutSec, signal, initialResult) {
1407
1405
  const deadline = Date.now() + timeoutSec * 1000;
@@ -1411,7 +1409,7 @@ class OneShot {
1411
1409
  let lastResult = initialResult;
1412
1410
  while (Date.now() < deadline) {
1413
1411
  if (signal?.aborted)
1414
- throw new OneShotError('Operation cancelled');
1412
+ throw new errors_1.OneShotError('Operation cancelled');
1415
1413
  try {
1416
1414
  const resp = await fetch(`${this.baseUrl}/v1/requests/${requestId}`, {
1417
1415
  headers: this.headers(),
@@ -1422,7 +1420,7 @@ class OneShot {
1422
1420
  // whatever we last had so consumers don't lose the sync result.
1423
1421
  if (lastResult !== undefined)
1424
1422
  return lastResult;
1425
- 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());
1426
1424
  }
1427
1425
  const job = await resp.json();
1428
1426
  lastResult = (job.result ?? job);
@@ -1431,7 +1429,7 @@ class OneShot {
1431
1429
  }
1432
1430
  }
1433
1431
  catch (err) {
1434
- if (err instanceof OneShotError)
1432
+ if (err instanceof errors_1.OneShotError)
1435
1433
  throw err;
1436
1434
  if (lastResult !== undefined)
1437
1435
  return lastResult;
@@ -1494,7 +1492,7 @@ class OneShot {
1494
1492
  signal.addEventListener('abort', () => {
1495
1493
  settle(() => {
1496
1494
  cleanup();
1497
- reject(new OneShotError('Operation cancelled'));
1495
+ reject(new errors_1.OneShotError('Operation cancelled'));
1498
1496
  });
1499
1497
  }, { once: true });
1500
1498
  }
@@ -1535,7 +1533,7 @@ class OneShot {
1535
1533
  else if (msg.status === 'failed') {
1536
1534
  settle(() => {
1537
1535
  cleanup();
1538
- 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')));
1539
1537
  });
1540
1538
  }
1541
1539
  else {
@@ -1571,14 +1569,14 @@ class OneShot {
1571
1569
  const maxRetries = 3;
1572
1570
  while (Date.now() - startTime < maxWaitMs) {
1573
1571
  if (signal?.aborted)
1574
- throw new OneShotError('Operation cancelled');
1572
+ throw new errors_1.OneShotError('Operation cancelled');
1575
1573
  try {
1576
1574
  const resp = await fetch(`${this.baseUrl}/v1/requests/${requestId}`, {
1577
1575
  headers: this.headers(),
1578
1576
  signal
1579
1577
  });
1580
1578
  if (!resp.ok) {
1581
- 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());
1582
1580
  }
1583
1581
  const job = await resp.json();
1584
1582
  if (job.status === 'completed') {
@@ -1591,42 +1589,43 @@ class OneShot {
1591
1589
  return result;
1592
1590
  }
1593
1591
  if (job.status === 'failed') {
1594
- 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'));
1595
1593
  }
1596
1594
  onStatusUpdate?.(job.status, requestId);
1597
1595
  retries = 0;
1598
1596
  await this.sleep(pollInterval, signal);
1599
1597
  }
1600
1598
  catch (err) {
1601
- if (err instanceof OneShotError)
1599
+ if (err instanceof errors_1.OneShotError)
1602
1600
  throw err;
1603
1601
  if (++retries > maxRetries) {
1604
- throw new OneShotError(`Polling failed after ${maxRetries} retries: ${err}`);
1602
+ throw new errors_1.OneShotError(`Polling failed after ${maxRetries} retries: ${err}`);
1605
1603
  }
1606
1604
  const backoff = pollInterval * Math.pow(2, retries - 1);
1607
1605
  this.log(`Retry ${retries}/${maxRetries} in ${backoff}ms`);
1608
1606
  await this.sleep(backoff, signal);
1609
1607
  }
1610
1608
  }
1611
- throw new JobTimeoutError(requestId, Date.now() - startTime);
1609
+ throw new errors_1.JobTimeoutError(requestId, Date.now() - startTime);
1612
1610
  }
1613
1611
  sleep(ms, signal) {
1614
1612
  return new Promise((resolve, reject) => {
1615
1613
  if (signal?.aborted) {
1616
- return reject(new OneShotError('Operation cancelled'));
1614
+ return reject(new errors_1.OneShotError('Operation cancelled'));
1617
1615
  }
1618
1616
  const timer = setTimeout(resolve, ms);
1619
1617
  const onAbort = () => {
1620
1618
  clearTimeout(timer);
1621
- reject(new OneShotError('Operation cancelled'));
1619
+ reject(new errors_1.OneShotError('Operation cancelled'));
1622
1620
  };
1623
1621
  signal?.addEventListener('abort', onAbort, { once: true });
1624
1622
  });
1625
1623
  }
1626
- async makeRequest(endpoint, data, payment, quoteId, signal, timeoutMs) {
1624
+ async makeRequest(endpoint, data, payment, quoteId, signal, timeoutMs, extraHeaders) {
1627
1625
  const headers = {
1628
1626
  'Content-Type': 'application/json',
1629
- ...this.headers()
1627
+ ...this.headers(),
1628
+ ...(extraHeaders ?? {})
1630
1629
  };
1631
1630
  if (payment) {
1632
1631
  const paymentJson = JSON.stringify(payment);
@@ -1660,7 +1659,7 @@ class OneShot {
1660
1659
  }
1661
1660
  checkAbortBeforePayment(signal) {
1662
1661
  if (signal?.aborted) {
1663
- throw new OneShotError('Operation cancelled before payment');
1662
+ throw new errors_1.OneShotError('Operation cancelled before payment');
1664
1663
  }
1665
1664
  }
1666
1665
  /**