@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/errors.d.ts +31 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +68 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +24 -1052
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +185 -189
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +1015 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/package.json +1 -1
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.
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
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
|
|
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
|
|
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
|
|
1370
|
-
// pending
|
|
1371
|
-
// behavior change — the WebSocket/HTTP polls return as soon as the
|
|
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
|
|
1384
|
-
*
|
|
1385
|
-
*
|
|
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
|
|
1404
|
-
*
|
|
1405
|
-
*
|
|
1406
|
-
* a tight loop wastes API calls. Returns the
|
|
1407
|
-
* not phones arrived (consumer can re-check
|
|
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
|
/**
|