@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/README.md +17 -0
- 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 -1048
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +189 -190
- 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}`);
|
|
@@ -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 =
|
|
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
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
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
|
|
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
|
|
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
|
|
1367
|
-
// pending
|
|
1368
|
-
// behavior change — the WebSocket/HTTP polls return as soon as the
|
|
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
|
|
1381
|
-
*
|
|
1382
|
-
*
|
|
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
|
|
1401
|
-
*
|
|
1402
|
-
*
|
|
1403
|
-
* a tight loop wastes API calls. Returns the
|
|
1404
|
-
* 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`).
|
|
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
|
/**
|