@oneshot-agent/sdk 0.16.2 → 0.18.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 +30 -1052
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +235 -193
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +1040 -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,16 +162,41 @@ class OneShot {
|
|
|
212
162
|
this.validate(options.to, 'to');
|
|
213
163
|
this.validate(options.subject, 'subject');
|
|
214
164
|
this.validate(options.body, 'body');
|
|
215
|
-
|
|
165
|
+
// Rotation mode: when the caller passes neither from_domain nor
|
|
166
|
+
// from_mailbox, omit from_address entirely so the server picks from
|
|
167
|
+
// the agent's domain pool. The chosen address comes back on the
|
|
168
|
+
// quote response (`quote.from_address`) and we replay it on /send.
|
|
169
|
+
// When the caller pins either knob, we keep legacy behavior: build
|
|
170
|
+
// `${mailbox ?? 'agent'}@${domain ?? 'oneshotagent.com'}`.
|
|
171
|
+
const useRotation = !options.from_domain && !options.from_mailbox;
|
|
172
|
+
const fromAddress = useRotation
|
|
173
|
+
? undefined
|
|
174
|
+
: `${options.from_mailbox ?? 'agent'}@${options.from_domain ?? 'oneshotagent.com'}`;
|
|
216
175
|
const quote = await this.tool('email/quote', {
|
|
217
|
-
from_address: fromAddress,
|
|
176
|
+
...(fromAddress ? { from_address: fromAddress } : {}),
|
|
218
177
|
to_address: options.to,
|
|
219
178
|
subject: options.subject,
|
|
220
|
-
body: options.body
|
|
179
|
+
body: options.body,
|
|
180
|
+
// Forward maxCost so the server-side X-Max-Cost-USDC header is set on
|
|
181
|
+
// the quote fetch (executeToolRequest destructures + threads it).
|
|
182
|
+
maxCost: options.maxCost,
|
|
221
183
|
});
|
|
222
184
|
this.log(`Email quote: $${quote.total_cost}`);
|
|
185
|
+
// Local fast-fail — matches the per-tool check on commerce/voice/sms/build/
|
|
186
|
+
// browser/compute. If the server-side header guard fired we'd never reach
|
|
187
|
+
// here; this catches the case where the server's enforcement is bypassed
|
|
188
|
+
// (e.g. a future API revision without the header check) or skipped (cap
|
|
189
|
+
// un-set so the server returned a 200 with the quote and the caller still
|
|
190
|
+
// wants the local guard to apply).
|
|
191
|
+
if (options.maxCost && parseFloat(quote.total_cost) > options.maxCost) {
|
|
192
|
+
throw new errors_1.OneShotError(`Quote $${quote.total_cost} exceeds maxCost $${options.maxCost}`);
|
|
193
|
+
}
|
|
194
|
+
const resolvedFromAddress = fromAddress ?? quote.from_address;
|
|
223
195
|
const payload = {
|
|
224
|
-
from_address
|
|
196
|
+
// Server replays the locked address from the quote when from_address
|
|
197
|
+
// is absent, but sending it anyway is harmless and forward-compatible
|
|
198
|
+
// with future SDK versions that talk directly to /send without quoting.
|
|
199
|
+
...(resolvedFromAddress ? { from_address: resolvedFromAddress } : {}),
|
|
225
200
|
to_address: options.to,
|
|
226
201
|
subject: options.subject,
|
|
227
202
|
body: options.body,
|
|
@@ -237,6 +212,40 @@ class OneShot {
|
|
|
237
212
|
}
|
|
238
213
|
return this.executeToolRequest('/v1/tools/email/send', payload, quote.quote_id);
|
|
239
214
|
}
|
|
215
|
+
/** List the caller's domain pool with warmup and rotation metadata. */
|
|
216
|
+
async listDomains() {
|
|
217
|
+
const response = await fetch(`${this.baseUrl}/v1/tools/email/domains`, {
|
|
218
|
+
headers: this.headers(),
|
|
219
|
+
});
|
|
220
|
+
if (!response.ok) {
|
|
221
|
+
throw new errors_1.ToolError('Failed to list domains', response.status, await response.text());
|
|
222
|
+
}
|
|
223
|
+
return response.json();
|
|
224
|
+
}
|
|
225
|
+
/** Take a domain out of rotation without releasing it. */
|
|
226
|
+
async pauseDomain(domain) {
|
|
227
|
+
this.validate(domain, 'domain');
|
|
228
|
+
const response = await fetch(`${this.baseUrl}/v1/tools/email/domains/${encodeURIComponent(domain)}/pause`, {
|
|
229
|
+
method: 'POST',
|
|
230
|
+
headers: this.headers(),
|
|
231
|
+
});
|
|
232
|
+
if (!response.ok) {
|
|
233
|
+
throw new errors_1.ToolError('Failed to pause domain', response.status, await response.text());
|
|
234
|
+
}
|
|
235
|
+
return response.json();
|
|
236
|
+
}
|
|
237
|
+
/** Put a paused domain back into rotation. */
|
|
238
|
+
async resumeDomain(domain) {
|
|
239
|
+
this.validate(domain, 'domain');
|
|
240
|
+
const response = await fetch(`${this.baseUrl}/v1/tools/email/domains/${encodeURIComponent(domain)}/resume`, {
|
|
241
|
+
method: 'POST',
|
|
242
|
+
headers: this.headers(),
|
|
243
|
+
});
|
|
244
|
+
if (!response.ok) {
|
|
245
|
+
throw new errors_1.ToolError('Failed to resume domain', response.status, await response.text());
|
|
246
|
+
}
|
|
247
|
+
return response.json();
|
|
248
|
+
}
|
|
240
249
|
async research(options) {
|
|
241
250
|
this.validate(options.topic, 'topic');
|
|
242
251
|
return this.tool('research', { ...options });
|
|
@@ -246,14 +255,14 @@ class OneShot {
|
|
|
246
255
|
}
|
|
247
256
|
async enrichProfile(options) {
|
|
248
257
|
if (!options.linkedin_url && !options.email && !options.name) {
|
|
249
|
-
throw new ValidationError('At least one of linkedin_url, email, or name is required', 'identifier');
|
|
258
|
+
throw new errors_1.ValidationError('At least one of linkedin_url, email, or name is required', 'identifier');
|
|
250
259
|
}
|
|
251
260
|
return this.tool('enrich/profile', { ...options });
|
|
252
261
|
}
|
|
253
262
|
async findEmail(options) {
|
|
254
263
|
this.validate(options.company_domain, 'company_domain');
|
|
255
264
|
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');
|
|
265
|
+
throw new errors_1.ValidationError('Either full_name or both first_name and last_name required', 'name');
|
|
257
266
|
}
|
|
258
267
|
return this.tool('enrich/email', { ...options });
|
|
259
268
|
}
|
|
@@ -263,13 +272,13 @@ class OneShot {
|
|
|
263
272
|
}
|
|
264
273
|
async deepResearchPerson(options) {
|
|
265
274
|
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');
|
|
275
|
+
throw new errors_1.ValidationError('At least one of email, social_media_url, or name is required', 'identifier');
|
|
267
276
|
}
|
|
268
277
|
return this.tool('research/person', { ...options });
|
|
269
278
|
}
|
|
270
279
|
async socialProfiles(options) {
|
|
271
280
|
if (!options.email && !options.social_media_url) {
|
|
272
|
-
throw new ValidationError('At least one of email or social_media_url is required', 'identifier');
|
|
281
|
+
throw new errors_1.ValidationError('At least one of email or social_media_url is required', 'identifier');
|
|
273
282
|
}
|
|
274
283
|
return this.tool('research/social', { ...options });
|
|
275
284
|
}
|
|
@@ -284,7 +293,7 @@ class OneShot {
|
|
|
284
293
|
}
|
|
285
294
|
async personInterests(options) {
|
|
286
295
|
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');
|
|
296
|
+
throw new errors_1.ValidationError('At least one of email, phone, or social_media_url is required', 'identifier');
|
|
288
297
|
}
|
|
289
298
|
return this.tool('research/interests', { ...options });
|
|
290
299
|
}
|
|
@@ -305,7 +314,7 @@ class OneShot {
|
|
|
305
314
|
headers: this.headers()
|
|
306
315
|
});
|
|
307
316
|
if (!response.ok) {
|
|
308
|
-
throw new ToolError('Failed to list inbox', response.status, await response.text());
|
|
317
|
+
throw new errors_1.ToolError('Failed to list inbox', response.status, await response.text());
|
|
309
318
|
}
|
|
310
319
|
return response.json();
|
|
311
320
|
}
|
|
@@ -315,10 +324,10 @@ class OneShot {
|
|
|
315
324
|
headers: this.headers()
|
|
316
325
|
});
|
|
317
326
|
if (response.status === 404) {
|
|
318
|
-
throw new ToolError('Email not found', 404, 'Email not found');
|
|
327
|
+
throw new errors_1.ToolError('Email not found', 404, 'Email not found');
|
|
319
328
|
}
|
|
320
329
|
if (!response.ok) {
|
|
321
|
-
throw new ToolError('Failed to get email', response.status, await response.text());
|
|
330
|
+
throw new errors_1.ToolError('Failed to get email', response.status, await response.text());
|
|
322
331
|
}
|
|
323
332
|
return response.json();
|
|
324
333
|
}
|
|
@@ -333,14 +342,14 @@ class OneShot {
|
|
|
333
342
|
variant_id: options.variant_id
|
|
334
343
|
};
|
|
335
344
|
// 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);
|
|
345
|
+
const quoteResp = await this.makeRequest('/v1/tools/commerce/buy', payload, undefined, undefined, options.signal, 120000, this.maxCostHeader(options.maxCost));
|
|
337
346
|
if (quoteResp.status !== 402) {
|
|
338
|
-
throw new ToolError('Expected 402 for quote', quoteResp.status, await quoteResp.text());
|
|
347
|
+
throw new errors_1.ToolError('Expected 402 for quote', quoteResp.status, await quoteResp.text());
|
|
339
348
|
}
|
|
340
349
|
const quoteData = await quoteResp.json();
|
|
341
350
|
this.log(`Commerce quote: $${quoteData.context.total} for "${quoteData.context.product_title}"`);
|
|
342
351
|
if (options.maxCost && parseFloat(quoteData.context.total) > options.maxCost) {
|
|
343
|
-
throw new OneShotError(`Quote $${quoteData.context.total} exceeds maxCost $${options.maxCost}`);
|
|
352
|
+
throw new errors_1.OneShotError(`Quote $${quoteData.context.total} exceeds maxCost $${options.maxCost}`);
|
|
344
353
|
}
|
|
345
354
|
const paymentInfo = {
|
|
346
355
|
protocol: 'x402',
|
|
@@ -356,7 +365,7 @@ class OneShot {
|
|
|
356
365
|
const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
|
|
357
366
|
const buyResp = await this.makeRequest('/v1/tools/commerce/buy', payload, auth, quoteData.context.quote_id, options.signal, 60000);
|
|
358
367
|
if (buyResp.status !== 202) {
|
|
359
|
-
throw new ToolError('Commerce buy failed', buyResp.status, await buyResp.text());
|
|
368
|
+
throw new errors_1.ToolError('Commerce buy failed', buyResp.status, await buyResp.text());
|
|
360
369
|
}
|
|
361
370
|
const result = await buyResp.json();
|
|
362
371
|
this.log(`Order submitted: ${result.request_id}`);
|
|
@@ -395,13 +404,13 @@ class OneShot {
|
|
|
395
404
|
this.validate(options.target_number, 'target_number');
|
|
396
405
|
// Check for empty arrays
|
|
397
406
|
if (Array.isArray(options.target_number) && options.target_number.length === 0) {
|
|
398
|
-
throw new ValidationError('target_number array cannot be empty', 'target_number');
|
|
407
|
+
throw new errors_1.ValidationError('target_number array cannot be empty', 'target_number');
|
|
399
408
|
}
|
|
400
409
|
if (options.objective.length < 10) {
|
|
401
|
-
throw new ValidationError('Objective must be at least 10 characters', 'objective');
|
|
410
|
+
throw new errors_1.ValidationError('Objective must be at least 10 characters', 'objective');
|
|
402
411
|
}
|
|
403
412
|
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');
|
|
413
|
+
throw new errors_1.ValidationError('max_duration_minutes must be between 1 and 30', 'max_duration_minutes');
|
|
405
414
|
}
|
|
406
415
|
const payload = {
|
|
407
416
|
objective: options.objective,
|
|
@@ -417,26 +426,25 @@ class OneShot {
|
|
|
417
426
|
if (options.max_duration_minutes)
|
|
418
427
|
payload.max_duration_minutes = options.max_duration_minutes;
|
|
419
428
|
// Voice uses quote-to-pay flow (402 -> payment -> 202)
|
|
420
|
-
const quoteResp = await this.makeRequest('/v1/tools/voice/call', payload, undefined, undefined, options.signal);
|
|
429
|
+
const quoteResp = await this.makeRequest('/v1/tools/voice/call', payload, undefined, undefined, options.signal, undefined, this.maxCostHeader(options.maxCost));
|
|
421
430
|
// Handle error responses before expecting 402
|
|
422
431
|
if (quoteResp.status === 400) {
|
|
423
432
|
const errorData = await quoteResp.json();
|
|
424
433
|
if (errorData.error === 'content_blocked') {
|
|
425
|
-
throw new ContentBlockedError(errorData.message, errorData.categories || []);
|
|
434
|
+
throw new errors_1.ContentBlockedError(errorData.message, errorData.categories || []);
|
|
426
435
|
}
|
|
427
436
|
if (errorData.error === 'emergency_number_blocked') {
|
|
428
|
-
throw new EmergencyNumberError(errorData.message, errorData.blocked_number || '');
|
|
437
|
+
throw new errors_1.EmergencyNumberError(errorData.message, errorData.blocked_number || '');
|
|
429
438
|
}
|
|
430
|
-
throw new ValidationError(errorData.message || 'Invalid request', 'request');
|
|
439
|
+
throw new errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
|
|
431
440
|
}
|
|
432
441
|
if (quoteResp.status !== 402) {
|
|
433
|
-
throw new ToolError('Expected 402 for quote', quoteResp.status, await quoteResp.text());
|
|
442
|
+
throw new errors_1.ToolError('Expected 402 for quote', quoteResp.status, await quoteResp.text());
|
|
434
443
|
}
|
|
435
444
|
const quoteData = await quoteResp.json();
|
|
436
445
|
this.log(`Voice quote: $${quoteData.context.total} for ${quoteData.context.estimated_duration_minutes}min call`);
|
|
437
|
-
this.log(`Objective summary: ${quoteData.context.objective_summary}`);
|
|
438
446
|
if (options.maxCost && parseFloat(quoteData.context.total) > options.maxCost) {
|
|
439
|
-
throw new OneShotError(`Quote $${quoteData.context.total} exceeds maxCost $${options.maxCost}`);
|
|
447
|
+
throw new errors_1.OneShotError(`Quote $${quoteData.context.total} exceeds maxCost $${options.maxCost}`);
|
|
440
448
|
}
|
|
441
449
|
const paymentInfo = {
|
|
442
450
|
protocol: 'x402',
|
|
@@ -452,7 +460,7 @@ class OneShot {
|
|
|
452
460
|
const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
|
|
453
461
|
const callResp = await this.makeRequest('/v1/tools/voice/call', payload, auth, quoteData.context.quote_id, options.signal);
|
|
454
462
|
if (callResp.status !== 202) {
|
|
455
|
-
throw new ToolError('Voice call initiation failed', callResp.status, await callResp.text());
|
|
463
|
+
throw new errors_1.ToolError('Voice call initiation failed', callResp.status, await callResp.text());
|
|
456
464
|
}
|
|
457
465
|
const result = await callResp.json();
|
|
458
466
|
this.log(`Call initiated: ${result.request_id}`);
|
|
@@ -478,17 +486,17 @@ class OneShot {
|
|
|
478
486
|
this.validate(options.to_number, 'to_number');
|
|
479
487
|
// Check for empty arrays
|
|
480
488
|
if (Array.isArray(options.to_number) && options.to_number.length === 0) {
|
|
481
|
-
throw new ValidationError('to_number array cannot be empty', 'to_number');
|
|
489
|
+
throw new errors_1.ValidationError('to_number array cannot be empty', 'to_number');
|
|
482
490
|
}
|
|
483
491
|
if (options.message.length < 1) {
|
|
484
|
-
throw new ValidationError('Message is required', 'message');
|
|
492
|
+
throw new errors_1.ValidationError('Message is required', 'message');
|
|
485
493
|
}
|
|
486
494
|
if (options.message.length > 1600) {
|
|
487
|
-
throw new ValidationError('Message must be 1600 characters or less', 'message');
|
|
495
|
+
throw new errors_1.ValidationError('Message must be 1600 characters or less', 'message');
|
|
488
496
|
}
|
|
489
497
|
const recipientCount = Array.isArray(options.to_number) ? options.to_number.length : 1;
|
|
490
498
|
if (recipientCount > 10) {
|
|
491
|
-
throw new ValidationError('Maximum 10 recipients allowed', 'to_number');
|
|
499
|
+
throw new errors_1.ValidationError('Maximum 10 recipients allowed', 'to_number');
|
|
492
500
|
}
|
|
493
501
|
const payload = {
|
|
494
502
|
message: options.message,
|
|
@@ -498,25 +506,25 @@ class OneShot {
|
|
|
498
506
|
wait: options.wait
|
|
499
507
|
};
|
|
500
508
|
// SMS uses quote-to-pay flow (402 -> payment -> 202)
|
|
501
|
-
const quoteResp = await this.makeRequest('/v1/tools/sms/send', payload, undefined, undefined, options.signal);
|
|
509
|
+
const quoteResp = await this.makeRequest('/v1/tools/sms/send', payload, undefined, undefined, options.signal, undefined, this.maxCostHeader(options.maxCost));
|
|
502
510
|
// Handle error responses before expecting 402
|
|
503
511
|
if (quoteResp.status === 400) {
|
|
504
512
|
const errorData = await quoteResp.json();
|
|
505
513
|
if (errorData.error === 'content_blocked') {
|
|
506
|
-
throw new ContentBlockedError(errorData.message, errorData.categories || []);
|
|
514
|
+
throw new errors_1.ContentBlockedError(errorData.message, errorData.categories || []);
|
|
507
515
|
}
|
|
508
516
|
if (errorData.error === 'emergency_number_blocked') {
|
|
509
|
-
throw new EmergencyNumberError(errorData.message, errorData.blocked_number || '');
|
|
517
|
+
throw new errors_1.EmergencyNumberError(errorData.message, errorData.blocked_number || '');
|
|
510
518
|
}
|
|
511
|
-
throw new ValidationError(errorData.message || 'Invalid request', 'request');
|
|
519
|
+
throw new errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
|
|
512
520
|
}
|
|
513
521
|
if (quoteResp.status !== 402) {
|
|
514
|
-
throw new ToolError('Expected 402 for quote', quoteResp.status, await quoteResp.text());
|
|
522
|
+
throw new errors_1.ToolError('Expected 402 for quote', quoteResp.status, await quoteResp.text());
|
|
515
523
|
}
|
|
516
524
|
const quoteData = await quoteResp.json();
|
|
517
525
|
this.log(`SMS quote: $${quoteData.context.total} for ${quoteData.context.segment_count} segment(s) to ${recipientCount} recipient(s)`);
|
|
518
526
|
if (options.maxCost && parseFloat(quoteData.context.total) > options.maxCost) {
|
|
519
|
-
throw new OneShotError(`Quote $${quoteData.context.total} exceeds maxCost $${options.maxCost}`);
|
|
527
|
+
throw new errors_1.OneShotError(`Quote $${quoteData.context.total} exceeds maxCost $${options.maxCost}`);
|
|
520
528
|
}
|
|
521
529
|
const paymentInfo = {
|
|
522
530
|
protocol: 'x402',
|
|
@@ -532,7 +540,7 @@ class OneShot {
|
|
|
532
540
|
const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
|
|
533
541
|
const sendResp = await this.makeRequest('/v1/tools/sms/send', payload, auth, quoteData.context.quote_id, options.signal);
|
|
534
542
|
if (sendResp.status !== 202) {
|
|
535
|
-
throw new ToolError('SMS send failed', sendResp.status, await sendResp.text());
|
|
543
|
+
throw new errors_1.ToolError('SMS send failed', sendResp.status, await sendResp.text());
|
|
536
544
|
}
|
|
537
545
|
const result = await sendResp.json();
|
|
538
546
|
this.log(`SMS queued: ${result.request_id}`);
|
|
@@ -562,7 +570,7 @@ class OneShot {
|
|
|
562
570
|
this.validate(options.product?.name, 'product.name');
|
|
563
571
|
this.validate(options.product?.description, 'product.description');
|
|
564
572
|
if (options.product.description.length < 10) {
|
|
565
|
-
throw new ValidationError('Product description must be at least 10 characters', 'product.description');
|
|
573
|
+
throw new errors_1.ValidationError('Product description must be at least 10 characters', 'product.description');
|
|
566
574
|
}
|
|
567
575
|
const payload = {
|
|
568
576
|
type: options.type ?? 'saas',
|
|
@@ -586,19 +594,19 @@ class OneShot {
|
|
|
586
594
|
if (options.build_id)
|
|
587
595
|
payload.build_id = options.build_id;
|
|
588
596
|
// Build uses quote-to-pay flow (402 -> payment -> 202)
|
|
589
|
-
const quoteResp = await this.makeRequest('/v1/tools/build', payload, undefined, undefined, options.signal);
|
|
597
|
+
const quoteResp = await this.makeRequest('/v1/tools/build', payload, undefined, undefined, options.signal, undefined, this.maxCostHeader(options.maxCost));
|
|
590
598
|
if (quoteResp.status === 400) {
|
|
591
599
|
const errorData = await quoteResp.json();
|
|
592
|
-
throw new ValidationError(errorData.message || 'Invalid request', 'request');
|
|
600
|
+
throw new errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
|
|
593
601
|
}
|
|
594
602
|
if (quoteResp.status !== 402) {
|
|
595
|
-
throw new ToolError('Expected 402 for quote', quoteResp.status, await quoteResp.text());
|
|
603
|
+
throw new errors_1.ToolError('Expected 402 for quote', quoteResp.status, await quoteResp.text());
|
|
596
604
|
}
|
|
597
605
|
const quoteData = await quoteResp.json();
|
|
598
606
|
this.log(`Build quote: $${quoteData.context.pricing.total} for "${quoteData.context.product_name}"`);
|
|
599
607
|
this.log(`Type: ${quoteData.context.analysis.inferred_type}, Sections: ${quoteData.context.analysis.estimated_sections}`);
|
|
600
608
|
if (options.maxCost && parseFloat(quoteData.context.pricing.total) > options.maxCost) {
|
|
601
|
-
throw new OneShotError(`Quote $${quoteData.context.pricing.total} exceeds maxCost $${options.maxCost}`);
|
|
609
|
+
throw new errors_1.OneShotError(`Quote $${quoteData.context.pricing.total} exceeds maxCost $${options.maxCost}`);
|
|
602
610
|
}
|
|
603
611
|
const paymentInfo = {
|
|
604
612
|
protocol: 'x402',
|
|
@@ -614,7 +622,7 @@ class OneShot {
|
|
|
614
622
|
const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
|
|
615
623
|
const buildResp = await this.makeRequest('/v1/tools/build', payload, auth, quoteData.context.quote_id, options.signal);
|
|
616
624
|
if (buildResp.status !== 202) {
|
|
617
|
-
throw new ToolError('Build initiation failed', buildResp.status, await buildResp.text());
|
|
625
|
+
throw new errors_1.ToolError('Build initiation failed', buildResp.status, await buildResp.text());
|
|
618
626
|
}
|
|
619
627
|
const result = await buildResp.json();
|
|
620
628
|
this.log(`Build initiated: ${result.request_id}`);
|
|
@@ -638,10 +646,10 @@ class OneShot {
|
|
|
638
646
|
async browser(options) {
|
|
639
647
|
this.validate(options.task, 'task');
|
|
640
648
|
if (options.task.length < 10) {
|
|
641
|
-
throw new ValidationError('Task must be at least 10 characters', 'task');
|
|
649
|
+
throw new errors_1.ValidationError('Task must be at least 10 characters', 'task');
|
|
642
650
|
}
|
|
643
651
|
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');
|
|
652
|
+
throw new errors_1.ValidationError('max_steps must be between 1 and 100', 'max_steps');
|
|
645
653
|
}
|
|
646
654
|
const payload = {
|
|
647
655
|
task: options.task,
|
|
@@ -664,18 +672,18 @@ class OneShot {
|
|
|
664
672
|
if (options.max_steps)
|
|
665
673
|
payload.max_steps = options.max_steps;
|
|
666
674
|
// Browser uses quote-to-pay flow (402 -> payment -> 202)
|
|
667
|
-
const quoteResp = await this.makeRequest('/v1/tools/browser', payload, undefined, undefined, options.signal);
|
|
675
|
+
const quoteResp = await this.makeRequest('/v1/tools/browser', payload, undefined, undefined, options.signal, undefined, this.maxCostHeader(options.maxCost));
|
|
668
676
|
if (quoteResp.status === 400) {
|
|
669
677
|
const errorData = await quoteResp.json();
|
|
670
|
-
throw new ValidationError(errorData.message || 'Invalid request', 'request');
|
|
678
|
+
throw new errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
|
|
671
679
|
}
|
|
672
680
|
if (quoteResp.status !== 402) {
|
|
673
|
-
throw new ToolError('Expected 402 for quote', quoteResp.status, await quoteResp.text());
|
|
681
|
+
throw new errors_1.ToolError('Expected 402 for quote', quoteResp.status, await quoteResp.text());
|
|
674
682
|
}
|
|
675
683
|
const quoteData = await quoteResp.json();
|
|
676
684
|
this.log(`Browser quote: $${quoteData.context.estimated_cost} for ~${quoteData.context.estimated_steps} steps`);
|
|
677
685
|
if (options.maxCost && parseFloat(quoteData.context.estimated_cost) > options.maxCost) {
|
|
678
|
-
throw new OneShotError(`Quote $${quoteData.context.estimated_cost} exceeds maxCost $${options.maxCost}`);
|
|
686
|
+
throw new errors_1.OneShotError(`Quote $${quoteData.context.estimated_cost} exceeds maxCost $${options.maxCost}`);
|
|
679
687
|
}
|
|
680
688
|
const paymentInfo = {
|
|
681
689
|
protocol: 'x402',
|
|
@@ -691,7 +699,7 @@ class OneShot {
|
|
|
691
699
|
const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
|
|
692
700
|
const execResp = await this.makeRequest('/v1/tools/browser', payload, auth, quoteData.context.quote_id, options.signal);
|
|
693
701
|
if (execResp.status !== 202) {
|
|
694
|
-
throw new ToolError('Browser task initiation failed', execResp.status, await execResp.text());
|
|
702
|
+
throw new errors_1.ToolError('Browser task initiation failed', execResp.status, await execResp.text());
|
|
695
703
|
}
|
|
696
704
|
const result = await execResp.json();
|
|
697
705
|
this.log(`Browser task initiated: ${result.request_id}`);
|
|
@@ -717,7 +725,7 @@ class OneShot {
|
|
|
717
725
|
body: JSON.stringify({ name }),
|
|
718
726
|
});
|
|
719
727
|
if (!response.ok) {
|
|
720
|
-
throw new ToolError('Failed to create browser profile', response.status, await response.text());
|
|
728
|
+
throw new errors_1.ToolError('Failed to create browser profile', response.status, await response.text());
|
|
721
729
|
}
|
|
722
730
|
return response.json();
|
|
723
731
|
}
|
|
@@ -737,7 +745,7 @@ class OneShot {
|
|
|
737
745
|
headers: this.headers(),
|
|
738
746
|
});
|
|
739
747
|
if (!response.ok) {
|
|
740
|
-
throw new ToolError('Failed to list browser profiles', response.status, await response.text());
|
|
748
|
+
throw new errors_1.ToolError('Failed to list browser profiles', response.status, await response.text());
|
|
741
749
|
}
|
|
742
750
|
const data = await response.json();
|
|
743
751
|
return data.profiles;
|
|
@@ -757,7 +765,7 @@ class OneShot {
|
|
|
757
765
|
headers: this.headers(),
|
|
758
766
|
});
|
|
759
767
|
if (!response.ok) {
|
|
760
|
-
throw new ToolError('Failed to delete browser profile', response.status, await response.text());
|
|
768
|
+
throw new errors_1.ToolError('Failed to delete browser profile', response.status, await response.text());
|
|
761
769
|
}
|
|
762
770
|
}
|
|
763
771
|
/**
|
|
@@ -806,7 +814,7 @@ class OneShot {
|
|
|
806
814
|
headers: this.headers()
|
|
807
815
|
});
|
|
808
816
|
if (!response.ok) {
|
|
809
|
-
throw new ToolError('Failed to list SMS inbox', response.status, await response.text());
|
|
817
|
+
throw new errors_1.ToolError('Failed to list SMS inbox', response.status, await response.text());
|
|
810
818
|
}
|
|
811
819
|
return response.json();
|
|
812
820
|
}
|
|
@@ -825,10 +833,10 @@ class OneShot {
|
|
|
825
833
|
headers: this.headers()
|
|
826
834
|
});
|
|
827
835
|
if (response.status === 404) {
|
|
828
|
-
throw new ToolError('SMS message not found', 404, 'Message not found');
|
|
836
|
+
throw new errors_1.ToolError('SMS message not found', 404, 'Message not found');
|
|
829
837
|
}
|
|
830
838
|
if (!response.ok) {
|
|
831
|
-
throw new ToolError('Failed to get SMS message', response.status, await response.text());
|
|
839
|
+
throw new errors_1.ToolError('Failed to get SMS message', response.status, await response.text());
|
|
832
840
|
}
|
|
833
841
|
return response.json();
|
|
834
842
|
}
|
|
@@ -855,7 +863,7 @@ class OneShot {
|
|
|
855
863
|
headers: this.headers()
|
|
856
864
|
});
|
|
857
865
|
if (!response.ok) {
|
|
858
|
-
throw new ToolError('Failed to list notifications', response.status, await response.text());
|
|
866
|
+
throw new errors_1.ToolError('Failed to list notifications', response.status, await response.text());
|
|
859
867
|
}
|
|
860
868
|
return response.json();
|
|
861
869
|
}
|
|
@@ -874,10 +882,10 @@ class OneShot {
|
|
|
874
882
|
headers: this.headers()
|
|
875
883
|
});
|
|
876
884
|
if (response.status === 404) {
|
|
877
|
-
throw new ToolError('Notification not found', 404, 'Notification not found');
|
|
885
|
+
throw new errors_1.ToolError('Notification not found', 404, 'Notification not found');
|
|
878
886
|
}
|
|
879
887
|
if (!response.ok) {
|
|
880
|
-
throw new ToolError('Failed to mark notification as read', response.status, await response.text());
|
|
888
|
+
throw new errors_1.ToolError('Failed to mark notification as read', response.status, await response.text());
|
|
881
889
|
}
|
|
882
890
|
}
|
|
883
891
|
async getUnifiedBalance() {
|
|
@@ -885,7 +893,7 @@ class OneShot {
|
|
|
885
893
|
headers: this.headers()
|
|
886
894
|
});
|
|
887
895
|
if (!response.ok) {
|
|
888
|
-
throw new ToolError('Failed to fetch balance', response.status, await response.text());
|
|
896
|
+
throw new errors_1.ToolError('Failed to fetch balance', response.status, await response.text());
|
|
889
897
|
}
|
|
890
898
|
return response.json();
|
|
891
899
|
}
|
|
@@ -943,21 +951,21 @@ class OneShot {
|
|
|
943
951
|
if (options.schedule)
|
|
944
952
|
payload.schedule = options.schedule;
|
|
945
953
|
// First call: get quote (402)
|
|
946
|
-
const quoteResp = await this.makeRequest('/v1/compute', payload, undefined, undefined, options.signal);
|
|
954
|
+
const quoteResp = await this.makeRequest('/v1/compute', payload, undefined, undefined, options.signal, undefined, this.maxCostHeader(options.maxCost));
|
|
947
955
|
if (quoteResp.status === 400) {
|
|
948
956
|
const errorData = await quoteResp.json();
|
|
949
957
|
if (errorData.error === 'content_blocked') {
|
|
950
|
-
throw new ContentBlockedError(errorData.message, []);
|
|
958
|
+
throw new errors_1.ContentBlockedError(errorData.message, []);
|
|
951
959
|
}
|
|
952
|
-
throw new ValidationError(errorData.message || 'Invalid request', 'request');
|
|
960
|
+
throw new errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
|
|
953
961
|
}
|
|
954
962
|
if (quoteResp.status !== 402) {
|
|
955
|
-
throw new ToolError('Expected 402 for compute quote', quoteResp.status, await quoteResp.text());
|
|
963
|
+
throw new errors_1.ToolError('Expected 402 for compute quote', quoteResp.status, await quoteResp.text());
|
|
956
964
|
}
|
|
957
965
|
const quoteData = await quoteResp.json();
|
|
958
966
|
this.log(`Compute quote: $${quoteData.context.total_budget} — ${quoteData.context.objective_summary}`);
|
|
959
967
|
if (options.maxCost && parseFloat(quoteData.context.total_budget) > options.maxCost) {
|
|
960
|
-
throw new OneShotError(`Quote $${quoteData.context.total_budget} exceeds maxCost $${options.maxCost}`);
|
|
968
|
+
throw new errors_1.OneShotError(`Quote $${quoteData.context.total_budget} exceeds maxCost $${options.maxCost}`);
|
|
961
969
|
}
|
|
962
970
|
const paymentInfo = {
|
|
963
971
|
protocol: 'x402',
|
|
@@ -973,7 +981,7 @@ class OneShot {
|
|
|
973
981
|
const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
|
|
974
982
|
const createResp = await this.makeRequest('/v1/compute', payload, auth, quoteData.context.quote_id, options.signal);
|
|
975
983
|
if (createResp.status !== 202) {
|
|
976
|
-
throw new ToolError('Compute goal creation failed', createResp.status, await createResp.text());
|
|
984
|
+
throw new errors_1.ToolError('Compute goal creation failed', createResp.status, await createResp.text());
|
|
977
985
|
}
|
|
978
986
|
return createResp.json();
|
|
979
987
|
}
|
|
@@ -992,10 +1000,10 @@ class OneShot {
|
|
|
992
1000
|
headers: this.headers()
|
|
993
1001
|
});
|
|
994
1002
|
if (response.status === 404) {
|
|
995
|
-
throw new ToolError('Goal not found', 404, 'Goal not found');
|
|
1003
|
+
throw new errors_1.ToolError('Goal not found', 404, 'Goal not found');
|
|
996
1004
|
}
|
|
997
1005
|
if (!response.ok) {
|
|
998
|
-
throw new ToolError('Failed to get compute goal', response.status, await response.text());
|
|
1006
|
+
throw new errors_1.ToolError('Failed to get compute goal', response.status, await response.text());
|
|
999
1007
|
}
|
|
1000
1008
|
const json = await response.json();
|
|
1001
1009
|
return json.data;
|
|
@@ -1017,7 +1025,7 @@ class OneShot {
|
|
|
1017
1025
|
headers: this.headers()
|
|
1018
1026
|
});
|
|
1019
1027
|
if (!response.ok) {
|
|
1020
|
-
throw new ToolError('Failed to get compute tasks', response.status, await response.text());
|
|
1028
|
+
throw new errors_1.ToolError('Failed to get compute tasks', response.status, await response.text());
|
|
1021
1029
|
}
|
|
1022
1030
|
const json = await response.json();
|
|
1023
1031
|
return json.data;
|
|
@@ -1037,10 +1045,10 @@ class OneShot {
|
|
|
1037
1045
|
headers: this.headers()
|
|
1038
1046
|
});
|
|
1039
1047
|
if (response.status === 404) {
|
|
1040
|
-
throw new ToolError('Budget not found', 404, 'Budget not found for this goal');
|
|
1048
|
+
throw new errors_1.ToolError('Budget not found', 404, 'Budget not found for this goal');
|
|
1041
1049
|
}
|
|
1042
1050
|
if (!response.ok) {
|
|
1043
|
-
throw new ToolError('Failed to get compute budget', response.status, await response.text());
|
|
1051
|
+
throw new errors_1.ToolError('Failed to get compute budget', response.status, await response.text());
|
|
1044
1052
|
}
|
|
1045
1053
|
const json = await response.json();
|
|
1046
1054
|
return json.data;
|
|
@@ -1062,10 +1070,10 @@ class OneShot {
|
|
|
1062
1070
|
body: JSON.stringify({ reason })
|
|
1063
1071
|
});
|
|
1064
1072
|
if (response.status === 404) {
|
|
1065
|
-
throw new ToolError('Goal not found', 404, 'Goal not found');
|
|
1073
|
+
throw new errors_1.ToolError('Goal not found', 404, 'Goal not found');
|
|
1066
1074
|
}
|
|
1067
1075
|
if (!response.ok) {
|
|
1068
|
-
throw new ToolError('Failed to cancel compute goal', response.status, await response.text());
|
|
1076
|
+
throw new errors_1.ToolError('Failed to cancel compute goal', response.status, await response.text());
|
|
1069
1077
|
}
|
|
1070
1078
|
const json = await response.json();
|
|
1071
1079
|
return json.data;
|
|
@@ -1091,10 +1099,10 @@ class OneShot {
|
|
|
1091
1099
|
body: JSON.stringify(input)
|
|
1092
1100
|
});
|
|
1093
1101
|
if (response.status === 404) {
|
|
1094
|
-
throw new ToolError('Goal or task not found', 404, await response.text());
|
|
1102
|
+
throw new errors_1.ToolError('Goal or task not found', 404, await response.text());
|
|
1095
1103
|
}
|
|
1096
1104
|
if (!response.ok) {
|
|
1097
|
-
throw new ToolError('Failed to respond to compute task', response.status, await response.text());
|
|
1105
|
+
throw new errors_1.ToolError('Failed to respond to compute task', response.status, await response.text());
|
|
1098
1106
|
}
|
|
1099
1107
|
const json = await response.json();
|
|
1100
1108
|
return json.data;
|
|
@@ -1115,7 +1123,7 @@ class OneShot {
|
|
|
1115
1123
|
body: JSON.stringify({ reason })
|
|
1116
1124
|
});
|
|
1117
1125
|
if (!response.ok) {
|
|
1118
|
-
throw new ToolError('Failed to pause compute goal', response.status, await response.text());
|
|
1126
|
+
throw new errors_1.ToolError('Failed to pause compute goal', response.status, await response.text());
|
|
1119
1127
|
}
|
|
1120
1128
|
const json = await response.json();
|
|
1121
1129
|
return json.data;
|
|
@@ -1137,13 +1145,16 @@ class OneShot {
|
|
|
1137
1145
|
body: JSON.stringify({})
|
|
1138
1146
|
});
|
|
1139
1147
|
if (!response.ok) {
|
|
1140
|
-
throw new ToolError('Failed to resume compute goal', response.status, await response.text());
|
|
1148
|
+
throw new errors_1.ToolError('Failed to resume compute goal', response.status, await response.text());
|
|
1141
1149
|
}
|
|
1142
1150
|
const json = await response.json();
|
|
1143
1151
|
return json.data;
|
|
1144
1152
|
}
|
|
1145
1153
|
/**
|
|
1146
|
-
* Top up budget for a recurring compute goal
|
|
1154
|
+
* Top up budget for a recurring compute goal.
|
|
1155
|
+
*
|
|
1156
|
+
* Uses the quote-then-pay flow: the first call gets a 402 with the top-up price,
|
|
1157
|
+
* the second call (with payment) credits the budget. The amount IS the price.
|
|
1147
1158
|
*
|
|
1148
1159
|
* @example
|
|
1149
1160
|
* ```typescript
|
|
@@ -1154,17 +1165,33 @@ class OneShot {
|
|
|
1154
1165
|
async fundComputeGoal(goalId, amount) {
|
|
1155
1166
|
this.validate(goalId, 'goalId');
|
|
1156
1167
|
if (!amount || amount <= 0) {
|
|
1157
|
-
throw new ValidationError('amount must be a positive number', 'amount');
|
|
1168
|
+
throw new errors_1.ValidationError('amount must be a positive number', 'amount');
|
|
1158
1169
|
}
|
|
1159
|
-
const
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
throw new ToolError('Failed to fund compute goal', response.status, await response.text());
|
|
1170
|
+
const path = `/v1/compute/${goalId}/fund`;
|
|
1171
|
+
const payload = { amount };
|
|
1172
|
+
// First call: get quote (402)
|
|
1173
|
+
const quoteResp = await this.makeRequest(path, payload);
|
|
1174
|
+
if (quoteResp.status !== 402) {
|
|
1175
|
+
throw new errors_1.ToolError('Expected 402 for compute fund quote', quoteResp.status, await quoteResp.text());
|
|
1166
1176
|
}
|
|
1167
|
-
const
|
|
1177
|
+
const quoteData = await quoteResp.json();
|
|
1178
|
+
this.log(`Compute fund quote: $${quoteData.payment_request.amount} to top up ${goalId}`);
|
|
1179
|
+
const paymentInfo = {
|
|
1180
|
+
protocol: 'x402',
|
|
1181
|
+
network: `eip155:${quoteData.payment_request.chain_id}`,
|
|
1182
|
+
payTo: quoteData.payment_request.recipient,
|
|
1183
|
+
amount: quoteData.payment_request.amount,
|
|
1184
|
+
currency: 'USD',
|
|
1185
|
+
facilitator_url: this.baseUrl,
|
|
1186
|
+
token: { address: quoteData.payment_request.token_address, symbol: 'USDC', decimals: 6 }
|
|
1187
|
+
};
|
|
1188
|
+
const { accepted, resource, extensions } = await this.getAcceptedRequirements(quoteResp, path, payload, quoteData.context.quote_id);
|
|
1189
|
+
const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
|
|
1190
|
+
const fundResp = await this.makeRequest(path, payload, auth, quoteData.context.quote_id);
|
|
1191
|
+
if (!fundResp.ok) {
|
|
1192
|
+
throw new errors_1.ToolError('Failed to fund compute goal', fundResp.status, await fundResp.text());
|
|
1193
|
+
}
|
|
1194
|
+
const json = await fundResp.json();
|
|
1168
1195
|
return json.data;
|
|
1169
1196
|
}
|
|
1170
1197
|
// ---------------------------------------------------------------------------
|
|
@@ -1191,7 +1218,7 @@ class OneShot {
|
|
|
1191
1218
|
headers: this.headers()
|
|
1192
1219
|
});
|
|
1193
1220
|
if (!response.ok) {
|
|
1194
|
-
throw new ToolError('Failed to get spend breakdown', response.status, await response.text());
|
|
1221
|
+
throw new errors_1.ToolError('Failed to get spend breakdown', response.status, await response.text());
|
|
1195
1222
|
}
|
|
1196
1223
|
return response.json();
|
|
1197
1224
|
}
|
|
@@ -1213,7 +1240,7 @@ class OneShot {
|
|
|
1213
1240
|
headers: this.headers()
|
|
1214
1241
|
});
|
|
1215
1242
|
if (!response.ok) {
|
|
1216
|
-
throw new ToolError('Failed to get RoCS', response.status, await response.text());
|
|
1243
|
+
throw new errors_1.ToolError('Failed to get RoCS', response.status, await response.text());
|
|
1217
1244
|
}
|
|
1218
1245
|
return response.json();
|
|
1219
1246
|
}
|
|
@@ -1241,7 +1268,7 @@ class OneShot {
|
|
|
1241
1268
|
headers: this.headers()
|
|
1242
1269
|
});
|
|
1243
1270
|
if (!response.ok) {
|
|
1244
|
-
throw new ToolError('Failed to list receipts', response.status, await response.text());
|
|
1271
|
+
throw new errors_1.ToolError('Failed to list receipts', response.status, await response.text());
|
|
1245
1272
|
}
|
|
1246
1273
|
return response.json();
|
|
1247
1274
|
}
|
|
@@ -1265,10 +1292,10 @@ class OneShot {
|
|
|
1265
1292
|
body: JSON.stringify(valueTag)
|
|
1266
1293
|
});
|
|
1267
1294
|
if (response.status === 404) {
|
|
1268
|
-
throw new ToolError('Receipt not found', 404, 'Receipt not found or not owned by this agent');
|
|
1295
|
+
throw new errors_1.ToolError('Receipt not found', 404, 'Receipt not found or not owned by this agent');
|
|
1269
1296
|
}
|
|
1270
1297
|
if (!response.ok) {
|
|
1271
|
-
throw new ToolError('Failed to tag receipt value', response.status, await response.text());
|
|
1298
|
+
throw new errors_1.ToolError('Failed to tag receipt value', response.status, await response.text());
|
|
1272
1299
|
}
|
|
1273
1300
|
}
|
|
1274
1301
|
// ---------------------------------------------------------------------------
|
|
@@ -1280,7 +1307,7 @@ class OneShot {
|
|
|
1280
1307
|
}
|
|
1281
1308
|
validate(value, field) {
|
|
1282
1309
|
if (!value)
|
|
1283
|
-
throw new ValidationError(`${field} is required`, field);
|
|
1310
|
+
throw new errors_1.ValidationError(`${field} is required`, field);
|
|
1284
1311
|
}
|
|
1285
1312
|
headers() {
|
|
1286
1313
|
return {
|
|
@@ -1288,8 +1315,21 @@ class OneShot {
|
|
|
1288
1315
|
'X-OneShot-SDK-Version': SDK_VERSION
|
|
1289
1316
|
};
|
|
1290
1317
|
}
|
|
1318
|
+
/**
|
|
1319
|
+
* Header that asks the API to reject the request when the computed quote
|
|
1320
|
+
* exceeds the caller-supplied cap (commit 8f328a7). The SDK still does its
|
|
1321
|
+
* own local `quote.total > maxCost` check after the 402 returns — this is
|
|
1322
|
+
* a server-side enforcement layer so non-SDK callers (MCP server, custom
|
|
1323
|
+
* integrations) can't ignore the cap.
|
|
1324
|
+
*/
|
|
1325
|
+
maxCostHeader(maxCost) {
|
|
1326
|
+
if (!maxCost || maxCost <= 0)
|
|
1327
|
+
return undefined;
|
|
1328
|
+
return { 'X-Max-Cost-USDC': maxCost.toString() };
|
|
1329
|
+
}
|
|
1291
1330
|
async executeToolRequest(endpoint, options, quoteId) {
|
|
1292
|
-
const { signal, onStatusUpdate, wait = true, waitForPhones, phoneTimeoutSec, ...payload } = options;
|
|
1331
|
+
const { signal, onStatusUpdate, wait = true, waitForPhones, phoneTimeoutSec, maxCost, ...payload } = options;
|
|
1332
|
+
const extraHeaders = this.maxCostHeader(maxCost);
|
|
1293
1333
|
// Validate memo
|
|
1294
1334
|
if (payload.memo !== undefined) {
|
|
1295
1335
|
if (typeof payload.memo !== 'string' || payload.memo.trim().length === 0) {
|
|
@@ -1316,9 +1356,9 @@ class OneShot {
|
|
|
1316
1356
|
}
|
|
1317
1357
|
}
|
|
1318
1358
|
if (signal?.aborted) {
|
|
1319
|
-
throw new OneShotError('Operation cancelled');
|
|
1359
|
+
throw new errors_1.OneShotError('Operation cancelled');
|
|
1320
1360
|
}
|
|
1321
|
-
let response = await this.makeRequest(endpoint, payload, undefined, quoteId, signal);
|
|
1361
|
+
let response = await this.makeRequest(endpoint, payload, undefined, quoteId, signal, undefined, extraHeaders);
|
|
1322
1362
|
// Handle 402 Payment Required
|
|
1323
1363
|
if (response.status === 402) {
|
|
1324
1364
|
// Parse x402 v2 PaymentRequired from PAYMENT-REQUIRED header
|
|
@@ -1338,10 +1378,10 @@ class OneShot {
|
|
|
1338
1378
|
this.log(`Payment required: ${paymentInfo.amount} USDC`);
|
|
1339
1379
|
this.checkAbortBeforePayment(signal);
|
|
1340
1380
|
const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
|
|
1341
|
-
response = await this.makeRequest(endpoint, payload, auth, quoteId, signal);
|
|
1381
|
+
response = await this.makeRequest(endpoint, payload, auth, quoteId, signal, undefined, extraHeaders);
|
|
1342
1382
|
}
|
|
1343
1383
|
if (!response.ok) {
|
|
1344
|
-
throw new ToolError('Tool request failed', response.status, await response.text());
|
|
1384
|
+
throw new errors_1.ToolError('Tool request failed', response.status, await response.text());
|
|
1345
1385
|
}
|
|
1346
1386
|
const result = await response.json();
|
|
1347
1387
|
// Handle async jobs
|
|
@@ -1364,12 +1404,12 @@ class OneShot {
|
|
|
1364
1404
|
this.log('WebSocket unavailable, falling back to HTTP polling');
|
|
1365
1405
|
result = await this.pollJobHttp(requestId, timeoutSec, signal, onStatusUpdate);
|
|
1366
1406
|
}
|
|
1367
|
-
// Optional second phase: keep polling for
|
|
1407
|
+
// Optional second phase: keep polling for the async phone-reveal webhook.
|
|
1368
1408
|
// 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.
|
|
1409
|
+
// has phones_pending=true (set by the worker when the upstream enrichment
|
|
1410
|
+
// has a pending async phone callback). This is opt-in so existing callers
|
|
1411
|
+
// see no behavior change — the WebSocket/HTTP polls return as soon as the
|
|
1412
|
+
// worker sets status=completed, with phones=null.
|
|
1373
1413
|
//
|
|
1374
1414
|
// Pass the existing result as the initial fallback: if the polling GETs
|
|
1375
1415
|
// hit a transient error before any successful refresh, we return what we
|
|
@@ -1380,9 +1420,9 @@ class OneShot {
|
|
|
1380
1420
|
return result;
|
|
1381
1421
|
}
|
|
1382
1422
|
/**
|
|
1383
|
-
* Detects whether a job result is still waiting for
|
|
1384
|
-
*
|
|
1385
|
-
*
|
|
1423
|
+
* Detects whether a job result is still waiting for an async phone reveal.
|
|
1424
|
+
* The worker sets `result.phones_pending=true` when the upstream enrichment
|
|
1425
|
+
* has a pending async webhook and the webhook URL is configured; the webhook
|
|
1386
1426
|
* handler flips it to false (or removes it) once phones arrive.
|
|
1387
1427
|
*
|
|
1388
1428
|
* Tolerates two shapes: the unwrapped result `{phones_pending}` and the
|
|
@@ -1400,11 +1440,12 @@ class OneShot {
|
|
|
1400
1440
|
return false;
|
|
1401
1441
|
}
|
|
1402
1442
|
/**
|
|
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
|
|
1443
|
+
* Slow poll loop that waits for async phone-reveal callbacks to arrive via
|
|
1444
|
+
* the webhook handler (which UPDATEs jobs.result_data.result.phones). Polls
|
|
1445
|
+
* GET /v1/requests/{id} every 5s — the upstream provider can take several
|
|
1446
|
+
* minutes to deliver, so a tight loop just wastes API calls. Returns the
|
|
1447
|
+
* latest snapshot whether or not phones arrived (consumer can re-check
|
|
1448
|
+
* `phones_pending`).
|
|
1408
1449
|
*/
|
|
1409
1450
|
async _pollForPhones(requestId, timeoutSec, signal, initialResult) {
|
|
1410
1451
|
const deadline = Date.now() + timeoutSec * 1000;
|
|
@@ -1414,7 +1455,7 @@ class OneShot {
|
|
|
1414
1455
|
let lastResult = initialResult;
|
|
1415
1456
|
while (Date.now() < deadline) {
|
|
1416
1457
|
if (signal?.aborted)
|
|
1417
|
-
throw new OneShotError('Operation cancelled');
|
|
1458
|
+
throw new errors_1.OneShotError('Operation cancelled');
|
|
1418
1459
|
try {
|
|
1419
1460
|
const resp = await fetch(`${this.baseUrl}/v1/requests/${requestId}`, {
|
|
1420
1461
|
headers: this.headers(),
|
|
@@ -1425,7 +1466,7 @@ class OneShot {
|
|
|
1425
1466
|
// whatever we last had so consumers don't lose the sync result.
|
|
1426
1467
|
if (lastResult !== undefined)
|
|
1427
1468
|
return lastResult;
|
|
1428
|
-
throw new ToolError('Failed to check job status', resp.status, await resp.text());
|
|
1469
|
+
throw new errors_1.ToolError('Failed to check job status', resp.status, await resp.text());
|
|
1429
1470
|
}
|
|
1430
1471
|
const job = await resp.json();
|
|
1431
1472
|
lastResult = (job.result ?? job);
|
|
@@ -1434,7 +1475,7 @@ class OneShot {
|
|
|
1434
1475
|
}
|
|
1435
1476
|
}
|
|
1436
1477
|
catch (err) {
|
|
1437
|
-
if (err instanceof OneShotError)
|
|
1478
|
+
if (err instanceof errors_1.OneShotError)
|
|
1438
1479
|
throw err;
|
|
1439
1480
|
if (lastResult !== undefined)
|
|
1440
1481
|
return lastResult;
|
|
@@ -1497,7 +1538,7 @@ class OneShot {
|
|
|
1497
1538
|
signal.addEventListener('abort', () => {
|
|
1498
1539
|
settle(() => {
|
|
1499
1540
|
cleanup();
|
|
1500
|
-
reject(new OneShotError('Operation cancelled'));
|
|
1541
|
+
reject(new errors_1.OneShotError('Operation cancelled'));
|
|
1501
1542
|
});
|
|
1502
1543
|
}, { once: true });
|
|
1503
1544
|
}
|
|
@@ -1538,7 +1579,7 @@ class OneShot {
|
|
|
1538
1579
|
else if (msg.status === 'failed') {
|
|
1539
1580
|
settle(() => {
|
|
1540
1581
|
cleanup();
|
|
1541
|
-
reject(new JobError(`Job failed: ${msg.error ?? 'Unknown'}`, requestId, String(msg.error ?? 'Unknown')));
|
|
1582
|
+
reject(new errors_1.JobError(`Job failed: ${msg.error ?? 'Unknown'}`, requestId, String(msg.error ?? 'Unknown')));
|
|
1542
1583
|
});
|
|
1543
1584
|
}
|
|
1544
1585
|
else {
|
|
@@ -1574,14 +1615,14 @@ class OneShot {
|
|
|
1574
1615
|
const maxRetries = 3;
|
|
1575
1616
|
while (Date.now() - startTime < maxWaitMs) {
|
|
1576
1617
|
if (signal?.aborted)
|
|
1577
|
-
throw new OneShotError('Operation cancelled');
|
|
1618
|
+
throw new errors_1.OneShotError('Operation cancelled');
|
|
1578
1619
|
try {
|
|
1579
1620
|
const resp = await fetch(`${this.baseUrl}/v1/requests/${requestId}`, {
|
|
1580
1621
|
headers: this.headers(),
|
|
1581
1622
|
signal
|
|
1582
1623
|
});
|
|
1583
1624
|
if (!resp.ok) {
|
|
1584
|
-
throw new ToolError('Failed to check job status', resp.status, await resp.text());
|
|
1625
|
+
throw new errors_1.ToolError('Failed to check job status', resp.status, await resp.text());
|
|
1585
1626
|
}
|
|
1586
1627
|
const job = await resp.json();
|
|
1587
1628
|
if (job.status === 'completed') {
|
|
@@ -1594,42 +1635,43 @@ class OneShot {
|
|
|
1594
1635
|
return result;
|
|
1595
1636
|
}
|
|
1596
1637
|
if (job.status === 'failed') {
|
|
1597
|
-
throw new JobError(`Job failed: ${job.error ?? 'Unknown'}`, requestId, String(job.error ?? 'Unknown'));
|
|
1638
|
+
throw new errors_1.JobError(`Job failed: ${job.error ?? 'Unknown'}`, requestId, String(job.error ?? 'Unknown'));
|
|
1598
1639
|
}
|
|
1599
1640
|
onStatusUpdate?.(job.status, requestId);
|
|
1600
1641
|
retries = 0;
|
|
1601
1642
|
await this.sleep(pollInterval, signal);
|
|
1602
1643
|
}
|
|
1603
1644
|
catch (err) {
|
|
1604
|
-
if (err instanceof OneShotError)
|
|
1645
|
+
if (err instanceof errors_1.OneShotError)
|
|
1605
1646
|
throw err;
|
|
1606
1647
|
if (++retries > maxRetries) {
|
|
1607
|
-
throw new OneShotError(`Polling failed after ${maxRetries} retries: ${err}`);
|
|
1648
|
+
throw new errors_1.OneShotError(`Polling failed after ${maxRetries} retries: ${err}`);
|
|
1608
1649
|
}
|
|
1609
1650
|
const backoff = pollInterval * Math.pow(2, retries - 1);
|
|
1610
1651
|
this.log(`Retry ${retries}/${maxRetries} in ${backoff}ms`);
|
|
1611
1652
|
await this.sleep(backoff, signal);
|
|
1612
1653
|
}
|
|
1613
1654
|
}
|
|
1614
|
-
throw new JobTimeoutError(requestId, Date.now() - startTime);
|
|
1655
|
+
throw new errors_1.JobTimeoutError(requestId, Date.now() - startTime);
|
|
1615
1656
|
}
|
|
1616
1657
|
sleep(ms, signal) {
|
|
1617
1658
|
return new Promise((resolve, reject) => {
|
|
1618
1659
|
if (signal?.aborted) {
|
|
1619
|
-
return reject(new OneShotError('Operation cancelled'));
|
|
1660
|
+
return reject(new errors_1.OneShotError('Operation cancelled'));
|
|
1620
1661
|
}
|
|
1621
1662
|
const timer = setTimeout(resolve, ms);
|
|
1622
1663
|
const onAbort = () => {
|
|
1623
1664
|
clearTimeout(timer);
|
|
1624
|
-
reject(new OneShotError('Operation cancelled'));
|
|
1665
|
+
reject(new errors_1.OneShotError('Operation cancelled'));
|
|
1625
1666
|
};
|
|
1626
1667
|
signal?.addEventListener('abort', onAbort, { once: true });
|
|
1627
1668
|
});
|
|
1628
1669
|
}
|
|
1629
|
-
async makeRequest(endpoint, data, payment, quoteId, signal, timeoutMs) {
|
|
1670
|
+
async makeRequest(endpoint, data, payment, quoteId, signal, timeoutMs, extraHeaders) {
|
|
1630
1671
|
const headers = {
|
|
1631
1672
|
'Content-Type': 'application/json',
|
|
1632
|
-
...this.headers()
|
|
1673
|
+
...this.headers(),
|
|
1674
|
+
...(extraHeaders ?? {})
|
|
1633
1675
|
};
|
|
1634
1676
|
if (payment) {
|
|
1635
1677
|
const paymentJson = JSON.stringify(payment);
|
|
@@ -1663,7 +1705,7 @@ class OneShot {
|
|
|
1663
1705
|
}
|
|
1664
1706
|
checkAbortBeforePayment(signal) {
|
|
1665
1707
|
if (signal?.aborted) {
|
|
1666
|
-
throw new OneShotError('Operation cancelled before payment');
|
|
1708
|
+
throw new errors_1.OneShotError('Operation cancelled before payment');
|
|
1667
1709
|
}
|
|
1668
1710
|
}
|
|
1669
1711
|
/**
|