@oneshot-agent/sdk 0.18.0 → 0.19.1
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 +84 -0
- package/dist/errors.d.ts +2 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +7 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +223 -245
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +28 -3
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -48,7 +48,8 @@ var swap_1 = require("./swap");
|
|
|
48
48
|
Object.defineProperty(exports, "getSwapQuote", { enumerable: true, get: function () { return swap_1.getSwapQuote; } });
|
|
49
49
|
Object.defineProperty(exports, "executeSwap", { enumerable: true, get: function () { return swap_1.executeSwap; } });
|
|
50
50
|
__exportStar(require("./errors"), exports);
|
|
51
|
-
|
|
51
|
+
// Keep in sync with package.json `version`. Guarded by version.test.ts.
|
|
52
|
+
const SDK_VERSION = '0.19.1';
|
|
52
53
|
// ============================================================================
|
|
53
54
|
// Environment Configuration
|
|
54
55
|
// ============================================================================
|
|
@@ -159,8 +160,11 @@ class OneShot {
|
|
|
159
160
|
return this.executeToolRequest(`/v1/tools/${toolName}`, options);
|
|
160
161
|
}
|
|
161
162
|
async email(options) {
|
|
162
|
-
|
|
163
|
-
|
|
163
|
+
// to/subject are server-derived when replying to an inbound email.
|
|
164
|
+
if (!options.reply_to_email_id) {
|
|
165
|
+
this.validate(options.to, 'to');
|
|
166
|
+
this.validate(options.subject, 'subject');
|
|
167
|
+
}
|
|
164
168
|
this.validate(options.body, 'body');
|
|
165
169
|
// Rotation mode: when the caller passes neither from_domain nor
|
|
166
170
|
// from_mailbox, omit from_address entirely so the server picks from
|
|
@@ -174,8 +178,9 @@ class OneShot {
|
|
|
174
178
|
: `${options.from_mailbox ?? 'agent'}@${options.from_domain ?? 'oneshotagent.com'}`;
|
|
175
179
|
const quote = await this.tool('email/quote', {
|
|
176
180
|
...(fromAddress ? { from_address: fromAddress } : {}),
|
|
177
|
-
to_address: options.to,
|
|
178
|
-
subject: options.subject,
|
|
181
|
+
...(options.to !== undefined ? { to_address: options.to } : {}),
|
|
182
|
+
...(options.subject !== undefined ? { subject: options.subject } : {}),
|
|
183
|
+
...(options.reply_to_email_id ? { reply_to_email_id: options.reply_to_email_id } : {}),
|
|
179
184
|
body: options.body,
|
|
180
185
|
// Forward maxCost so the server-side X-Max-Cost-USDC header is set on
|
|
181
186
|
// the quote fetch (executeToolRequest destructures + threads it).
|
|
@@ -188,17 +193,16 @@ class OneShot {
|
|
|
188
193
|
// (e.g. a future API revision without the header check) or skipped (cap
|
|
189
194
|
// un-set so the server returned a 200 with the quote and the caller still
|
|
190
195
|
// wants the local guard to apply).
|
|
191
|
-
|
|
192
|
-
throw new errors_1.OneShotError(`Quote $${quote.total_cost} exceeds maxCost $${options.maxCost}`);
|
|
193
|
-
}
|
|
196
|
+
this.assertWithinMaxCost(quote.total_cost, options.maxCost);
|
|
194
197
|
const resolvedFromAddress = fromAddress ?? quote.from_address;
|
|
195
198
|
const payload = {
|
|
196
199
|
// Server replays the locked address from the quote when from_address
|
|
197
200
|
// is absent, but sending it anyway is harmless and forward-compatible
|
|
198
201
|
// with future SDK versions that talk directly to /send without quoting.
|
|
199
202
|
...(resolvedFromAddress ? { from_address: resolvedFromAddress } : {}),
|
|
200
|
-
to_address: options.to,
|
|
201
|
-
subject: options.subject,
|
|
203
|
+
...(options.to !== undefined ? { to_address: options.to } : {}),
|
|
204
|
+
...(options.subject !== undefined ? { subject: options.subject } : {}),
|
|
205
|
+
...(options.reply_to_email_id ? { reply_to_email_id: options.reply_to_email_id } : {}),
|
|
202
206
|
body: options.body,
|
|
203
207
|
signal: options.signal,
|
|
204
208
|
onStatusUpdate: options.onStatusUpdate,
|
|
@@ -210,6 +214,10 @@ class OneShot {
|
|
|
210
214
|
if (options.attachments?.length) {
|
|
211
215
|
payload.attachments = options.attachments;
|
|
212
216
|
}
|
|
217
|
+
if (options.idempotencyKey) {
|
|
218
|
+
// Only the send call carries the key — the quote call has no side effects.
|
|
219
|
+
payload.idempotencyKey = options.idempotencyKey;
|
|
220
|
+
}
|
|
213
221
|
return this.executeToolRequest('/v1/tools/email/send', payload, quote.quote_id);
|
|
214
222
|
}
|
|
215
223
|
/** List the caller's domain pool with warmup and rotation metadata. */
|
|
@@ -302,14 +310,11 @@ class OneShot {
|
|
|
302
310
|
return this.tool('research/interactions', { ...options });
|
|
303
311
|
}
|
|
304
312
|
async inboxList(options = {}) {
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
if (options.include_body)
|
|
311
|
-
params.set('include_body', 'true');
|
|
312
|
-
const qs = params.toString();
|
|
313
|
+
const qs = this.buildQuery({
|
|
314
|
+
since: options.since || undefined,
|
|
315
|
+
limit: options.limit || undefined,
|
|
316
|
+
include_body: options.include_body ? 'true' : undefined,
|
|
317
|
+
});
|
|
313
318
|
const response = await fetch(`${this.baseUrl}/v1/tools/inbox${qs ? `?${qs}` : ''}`, {
|
|
314
319
|
headers: this.headers()
|
|
315
320
|
});
|
|
@@ -342,28 +347,17 @@ class OneShot {
|
|
|
342
347
|
variant_id: options.variant_id
|
|
343
348
|
};
|
|
344
349
|
// Commerce quotes can take up to 90s due to Rye API polling
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
network: `eip155:${quoteData.payment_request.chain_id}`,
|
|
357
|
-
payTo: quoteData.payment_request.recipient,
|
|
358
|
-
amount: quoteData.payment_request.amount,
|
|
359
|
-
currency: 'USD',
|
|
360
|
-
facilitator_url: this.baseUrl,
|
|
361
|
-
token: { address: quoteData.payment_request.token_address, symbol: 'USDC', decimals: 6 }
|
|
362
|
-
};
|
|
363
|
-
this.checkAbortBeforePayment(options.signal);
|
|
364
|
-
const { accepted, resource, extensions } = await this.getAcceptedRequirements(quoteResp, '/v1/tools/commerce/buy', payload, quoteData.context.quote_id, options.signal);
|
|
365
|
-
const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
|
|
366
|
-
const buyResp = await this.makeRequest('/v1/tools/commerce/buy', payload, auth, quoteData.context.quote_id, options.signal, 60000);
|
|
350
|
+
const { execResp: buyResp } = await this.runQuoteToPay({
|
|
351
|
+
endpoint: '/v1/tools/commerce/buy',
|
|
352
|
+
payload,
|
|
353
|
+
signal: options.signal,
|
|
354
|
+
maxCost: options.maxCost,
|
|
355
|
+
quoteTimeoutMs: 120000,
|
|
356
|
+
execTimeoutMs: 60000,
|
|
357
|
+
expectMsg: 'Expected 402 for quote',
|
|
358
|
+
totalOf: (ctx) => ctx.total,
|
|
359
|
+
onQuote: (ctx) => this.log(`Commerce quote: $${ctx.total} for "${ctx.product_title}"`),
|
|
360
|
+
});
|
|
367
361
|
if (buyResp.status !== 202) {
|
|
368
362
|
throw new errors_1.ToolError('Commerce buy failed', buyResp.status, await buyResp.text());
|
|
369
363
|
}
|
|
@@ -415,9 +409,6 @@ class OneShot {
|
|
|
415
409
|
const payload = {
|
|
416
410
|
objective: options.objective,
|
|
417
411
|
target_number: options.target_number,
|
|
418
|
-
signal: options.signal,
|
|
419
|
-
onStatusUpdate: options.onStatusUpdate,
|
|
420
|
-
wait: options.wait
|
|
421
412
|
};
|
|
422
413
|
if (options.caller_persona)
|
|
423
414
|
payload.caller_persona = options.caller_persona;
|
|
@@ -425,40 +416,25 @@ class OneShot {
|
|
|
425
416
|
payload.context = options.context;
|
|
426
417
|
if (options.max_duration_minutes)
|
|
427
418
|
payload.max_duration_minutes = options.max_duration_minutes;
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
throw new errors_1.OneShotError(`Quote $${quoteData.context.total} exceeds maxCost $${options.maxCost}`);
|
|
448
|
-
}
|
|
449
|
-
const paymentInfo = {
|
|
450
|
-
protocol: 'x402',
|
|
451
|
-
network: `eip155:${quoteData.payment_request.chain_id}`,
|
|
452
|
-
payTo: quoteData.payment_request.recipient,
|
|
453
|
-
amount: quoteData.payment_request.amount,
|
|
454
|
-
currency: 'USD',
|
|
455
|
-
facilitator_url: this.baseUrl,
|
|
456
|
-
token: { address: quoteData.payment_request.token_address, symbol: 'USDC', decimals: 6 }
|
|
457
|
-
};
|
|
458
|
-
this.checkAbortBeforePayment(options.signal);
|
|
459
|
-
const { accepted, resource, extensions } = await this.getAcceptedRequirements(quoteResp, '/v1/tools/voice/call', payload, quoteData.context.quote_id, options.signal);
|
|
460
|
-
const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
|
|
461
|
-
const callResp = await this.makeRequest('/v1/tools/voice/call', payload, auth, quoteData.context.quote_id, options.signal);
|
|
419
|
+
const { execResp: callResp } = await this.runQuoteToPay({
|
|
420
|
+
endpoint: '/v1/tools/voice/call',
|
|
421
|
+
payload,
|
|
422
|
+
signal: options.signal,
|
|
423
|
+
maxCost: options.maxCost,
|
|
424
|
+
expectMsg: 'Expected 402 for quote',
|
|
425
|
+
totalOf: (ctx) => ctx.total,
|
|
426
|
+
onQuote: (ctx) => this.log(`Voice quote: $${ctx.total} for ${ctx.estimated_duration_minutes}min call`),
|
|
427
|
+
on400: async (resp) => {
|
|
428
|
+
const errorData = await resp.json();
|
|
429
|
+
if (errorData.error === 'content_blocked') {
|
|
430
|
+
throw new errors_1.ContentBlockedError(errorData.message, errorData.categories || []);
|
|
431
|
+
}
|
|
432
|
+
if (errorData.error === 'emergency_number_blocked') {
|
|
433
|
+
throw new errors_1.EmergencyNumberError(errorData.message, errorData.blocked_number || '');
|
|
434
|
+
}
|
|
435
|
+
throw new errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
|
|
436
|
+
},
|
|
437
|
+
});
|
|
462
438
|
if (callResp.status !== 202) {
|
|
463
439
|
throw new errors_1.ToolError('Voice call initiation failed', callResp.status, await callResp.text());
|
|
464
440
|
}
|
|
@@ -501,44 +477,27 @@ class OneShot {
|
|
|
501
477
|
const payload = {
|
|
502
478
|
message: options.message,
|
|
503
479
|
to_number: options.to_number,
|
|
504
|
-
signal: options.signal,
|
|
505
|
-
onStatusUpdate: options.onStatusUpdate,
|
|
506
|
-
wait: options.wait
|
|
507
480
|
};
|
|
508
481
|
// SMS uses quote-to-pay flow (402 -> payment -> 202)
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
}
|
|
529
|
-
const paymentInfo = {
|
|
530
|
-
protocol: 'x402',
|
|
531
|
-
network: `eip155:${quoteData.payment_request.chain_id}`,
|
|
532
|
-
payTo: quoteData.payment_request.recipient,
|
|
533
|
-
amount: quoteData.payment_request.amount,
|
|
534
|
-
currency: 'USD',
|
|
535
|
-
facilitator_url: this.baseUrl,
|
|
536
|
-
token: { address: quoteData.payment_request.token_address, symbol: 'USDC', decimals: 6 }
|
|
537
|
-
};
|
|
538
|
-
this.checkAbortBeforePayment(options.signal);
|
|
539
|
-
const { accepted, resource, extensions } = await this.getAcceptedRequirements(quoteResp, '/v1/tools/sms/send', payload, quoteData.context.quote_id, options.signal);
|
|
540
|
-
const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
|
|
541
|
-
const sendResp = await this.makeRequest('/v1/tools/sms/send', payload, auth, quoteData.context.quote_id, options.signal);
|
|
482
|
+
const { execResp: sendResp } = await this.runQuoteToPay({
|
|
483
|
+
endpoint: '/v1/tools/sms/send',
|
|
484
|
+
payload,
|
|
485
|
+
signal: options.signal,
|
|
486
|
+
maxCost: options.maxCost,
|
|
487
|
+
expectMsg: 'Expected 402 for quote',
|
|
488
|
+
totalOf: (ctx) => ctx.total,
|
|
489
|
+
onQuote: (ctx) => this.log(`SMS quote: $${ctx.total} for ${ctx.segment_count} segment(s) to ${recipientCount} recipient(s)`),
|
|
490
|
+
on400: async (resp) => {
|
|
491
|
+
const errorData = await resp.json();
|
|
492
|
+
if (errorData.error === 'content_blocked') {
|
|
493
|
+
throw new errors_1.ContentBlockedError(errorData.message, errorData.categories || []);
|
|
494
|
+
}
|
|
495
|
+
if (errorData.error === 'emergency_number_blocked') {
|
|
496
|
+
throw new errors_1.EmergencyNumberError(errorData.message, errorData.blocked_number || '');
|
|
497
|
+
}
|
|
498
|
+
throw new errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
|
|
499
|
+
},
|
|
500
|
+
});
|
|
542
501
|
if (sendResp.status !== 202) {
|
|
543
502
|
throw new errors_1.ToolError('SMS send failed', sendResp.status, await sendResp.text());
|
|
544
503
|
}
|
|
@@ -575,9 +534,6 @@ class OneShot {
|
|
|
575
534
|
const payload = {
|
|
576
535
|
type: options.type ?? 'saas',
|
|
577
536
|
product: options.product,
|
|
578
|
-
signal: options.signal,
|
|
579
|
-
onStatusUpdate: options.onStatusUpdate,
|
|
580
|
-
wait: options.wait
|
|
581
537
|
};
|
|
582
538
|
if (options.source_url)
|
|
583
539
|
payload.source_url = options.source_url;
|
|
@@ -593,34 +549,24 @@ class OneShot {
|
|
|
593
549
|
payload.domain = options.domain;
|
|
594
550
|
if (options.build_id)
|
|
595
551
|
payload.build_id = options.build_id;
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
payTo: quoteData.payment_request.recipient,
|
|
615
|
-
amount: quoteData.payment_request.amount,
|
|
616
|
-
currency: 'USD',
|
|
617
|
-
facilitator_url: this.baseUrl,
|
|
618
|
-
token: { address: quoteData.payment_request.token_address, symbol: 'USDC', decimals: 6 }
|
|
619
|
-
};
|
|
620
|
-
this.checkAbortBeforePayment(options.signal);
|
|
621
|
-
const { accepted, resource, extensions } = await this.getAcceptedRequirements(quoteResp, '/v1/tools/build', payload, quoteData.context.quote_id, options.signal);
|
|
622
|
-
const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
|
|
623
|
-
const buildResp = await this.makeRequest('/v1/tools/build', payload, auth, quoteData.context.quote_id, options.signal);
|
|
552
|
+
const { execResp: buildResp } = await this.runQuoteToPay({
|
|
553
|
+
endpoint: '/v1/tools/build',
|
|
554
|
+
payload,
|
|
555
|
+
signal: options.signal,
|
|
556
|
+
maxCost: options.maxCost,
|
|
557
|
+
// Build analysis can be slow server-side; bound the quote leg client-side.
|
|
558
|
+
quoteTimeoutMs: 120000,
|
|
559
|
+
expectMsg: 'Expected 402 for quote',
|
|
560
|
+
totalOf: (ctx) => ctx.pricing.total,
|
|
561
|
+
onQuote: (ctx) => {
|
|
562
|
+
this.log(`Build quote: $${ctx.pricing.total} for "${ctx.product_name}"`);
|
|
563
|
+
this.log(`Type: ${ctx.analysis.inferred_type}, Sections: ${ctx.analysis.estimated_sections}`);
|
|
564
|
+
},
|
|
565
|
+
on400: async (resp) => {
|
|
566
|
+
const errorData = await resp.json();
|
|
567
|
+
throw new errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
|
|
568
|
+
},
|
|
569
|
+
});
|
|
624
570
|
if (buildResp.status !== 202) {
|
|
625
571
|
throw new errors_1.ToolError('Build initiation failed', buildResp.status, await buildResp.text());
|
|
626
572
|
}
|
|
@@ -653,9 +599,6 @@ class OneShot {
|
|
|
653
599
|
}
|
|
654
600
|
const payload = {
|
|
655
601
|
task: options.task,
|
|
656
|
-
signal: options.signal,
|
|
657
|
-
onStatusUpdate: options.onStatusUpdate,
|
|
658
|
-
wait: options.wait
|
|
659
602
|
};
|
|
660
603
|
if (options.output_schema)
|
|
661
604
|
payload.output_schema = options.output_schema;
|
|
@@ -671,33 +614,21 @@ class OneShot {
|
|
|
671
614
|
payload.secrets = options.secrets;
|
|
672
615
|
if (options.max_steps)
|
|
673
616
|
payload.max_steps = options.max_steps;
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
protocol: 'x402',
|
|
690
|
-
network: `eip155:${quoteData.payment_request.chain_id}`,
|
|
691
|
-
payTo: quoteData.payment_request.recipient,
|
|
692
|
-
amount: quoteData.payment_request.amount,
|
|
693
|
-
currency: 'USD',
|
|
694
|
-
facilitator_url: this.baseUrl,
|
|
695
|
-
token: { address: quoteData.payment_request.token_address, symbol: 'USDC', decimals: 6 }
|
|
696
|
-
};
|
|
697
|
-
this.checkAbortBeforePayment(options.signal);
|
|
698
|
-
const { accepted, resource, extensions } = await this.getAcceptedRequirements(quoteResp, '/v1/tools/browser', payload, quoteData.context.quote_id, options.signal);
|
|
699
|
-
const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
|
|
700
|
-
const execResp = await this.makeRequest('/v1/tools/browser', payload, auth, quoteData.context.quote_id, options.signal);
|
|
617
|
+
const { execResp } = await this.runQuoteToPay({
|
|
618
|
+
endpoint: '/v1/tools/browser',
|
|
619
|
+
payload,
|
|
620
|
+
signal: options.signal,
|
|
621
|
+
maxCost: options.maxCost,
|
|
622
|
+
// Browser analysis can be slow server-side; bound the quote leg client-side.
|
|
623
|
+
quoteTimeoutMs: 120000,
|
|
624
|
+
expectMsg: 'Expected 402 for quote',
|
|
625
|
+
totalOf: (ctx) => ctx.estimated_cost,
|
|
626
|
+
onQuote: (ctx) => this.log(`Browser quote: $${ctx.estimated_cost} for ~${ctx.estimated_steps} steps`),
|
|
627
|
+
on400: async (resp) => {
|
|
628
|
+
const errorData = await resp.json();
|
|
629
|
+
throw new errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
|
|
630
|
+
},
|
|
631
|
+
});
|
|
701
632
|
if (execResp.status !== 202) {
|
|
702
633
|
throw new errors_1.ToolError('Browser task initiation failed', execResp.status, await execResp.text());
|
|
703
634
|
}
|
|
@@ -721,7 +652,7 @@ class OneShot {
|
|
|
721
652
|
this.validate(name, 'name');
|
|
722
653
|
const response = await fetch(`${this.baseUrl}/v1/tools/browser/profiles`, {
|
|
723
654
|
method: 'POST',
|
|
724
|
-
headers:
|
|
655
|
+
headers: this.jsonHeaders(),
|
|
725
656
|
body: JSON.stringify({ name }),
|
|
726
657
|
});
|
|
727
658
|
if (!response.ok) {
|
|
@@ -802,14 +733,11 @@ class OneShot {
|
|
|
802
733
|
* ```
|
|
803
734
|
*/
|
|
804
735
|
async smsInboxList(options = {}) {
|
|
805
|
-
const
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
if (options.from)
|
|
811
|
-
params.set('from', options.from);
|
|
812
|
-
const qs = params.toString();
|
|
736
|
+
const qs = this.buildQuery({
|
|
737
|
+
since: options.since || undefined,
|
|
738
|
+
limit: options.limit || undefined,
|
|
739
|
+
from: options.from || undefined,
|
|
740
|
+
});
|
|
813
741
|
const response = await fetch(`${this.baseUrl}/v1/tools/sms/inbox${qs ? `?${qs}` : ''}`, {
|
|
814
742
|
headers: this.headers()
|
|
815
743
|
});
|
|
@@ -853,12 +781,10 @@ class OneShot {
|
|
|
853
781
|
* ```
|
|
854
782
|
*/
|
|
855
783
|
async notifications(options = {}) {
|
|
856
|
-
const
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
params.set('limit', String(options.limit));
|
|
861
|
-
const qs = params.toString();
|
|
784
|
+
const qs = this.buildQuery({
|
|
785
|
+
unread: options.unread ? 'true' : undefined,
|
|
786
|
+
limit: options.limit || undefined,
|
|
787
|
+
});
|
|
862
788
|
const response = await fetch(`${this.baseUrl}/v1/tools/notifications${qs ? `?${qs}` : ''}`, {
|
|
863
789
|
headers: this.headers()
|
|
864
790
|
});
|
|
@@ -950,36 +876,24 @@ class OneShot {
|
|
|
950
876
|
payload.soul_service_slug = options.soul_service_slug;
|
|
951
877
|
if (options.schedule)
|
|
952
878
|
payload.schedule = options.schedule;
|
|
953
|
-
//
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
protocol: 'x402',
|
|
972
|
-
network: `eip155:${quoteData.payment_request.chain_id}`,
|
|
973
|
-
payTo: quoteData.payment_request.recipient,
|
|
974
|
-
amount: quoteData.payment_request.amount,
|
|
975
|
-
currency: 'USD',
|
|
976
|
-
facilitator_url: this.baseUrl,
|
|
977
|
-
token: { address: quoteData.payment_request.token_address, symbol: 'USDC', decimals: 6 }
|
|
978
|
-
};
|
|
979
|
-
this.checkAbortBeforePayment(options.signal);
|
|
980
|
-
const { accepted, resource, extensions } = await this.getAcceptedRequirements(quoteResp, '/v1/compute', payload, quoteData.context.quote_id, options.signal);
|
|
981
|
-
const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
|
|
982
|
-
const createResp = await this.makeRequest('/v1/compute', payload, auth, quoteData.context.quote_id, options.signal);
|
|
879
|
+
// Compute returns the goal directly on 202 — no job polling, unlike the
|
|
880
|
+
// other paid tools.
|
|
881
|
+
const { execResp: createResp } = await this.runQuoteToPay({
|
|
882
|
+
endpoint: '/v1/compute',
|
|
883
|
+
payload,
|
|
884
|
+
signal: options.signal,
|
|
885
|
+
maxCost: options.maxCost,
|
|
886
|
+
expectMsg: 'Expected 402 for compute quote',
|
|
887
|
+
totalOf: (ctx) => ctx.total_budget,
|
|
888
|
+
onQuote: (ctx) => this.log(`Compute quote: $${ctx.total_budget} — ${ctx.objective_summary}`),
|
|
889
|
+
on400: async (resp) => {
|
|
890
|
+
const errorData = await resp.json();
|
|
891
|
+
if (errorData.error === 'content_blocked') {
|
|
892
|
+
throw new errors_1.ContentBlockedError(errorData.message, []);
|
|
893
|
+
}
|
|
894
|
+
throw new errors_1.ValidationError(errorData.message || 'Invalid request', 'request');
|
|
895
|
+
},
|
|
896
|
+
});
|
|
983
897
|
if (createResp.status !== 202) {
|
|
984
898
|
throw new errors_1.ToolError('Compute goal creation failed', createResp.status, await createResp.text());
|
|
985
899
|
}
|
|
@@ -1066,7 +980,7 @@ class OneShot {
|
|
|
1066
980
|
this.validate(goalId, 'goalId');
|
|
1067
981
|
const response = await fetch(`${this.baseUrl}/v1/compute/${goalId}/cancel`, {
|
|
1068
982
|
method: 'POST',
|
|
1069
|
-
headers:
|
|
983
|
+
headers: this.jsonHeaders(),
|
|
1070
984
|
body: JSON.stringify({ reason })
|
|
1071
985
|
});
|
|
1072
986
|
if (response.status === 404) {
|
|
@@ -1095,7 +1009,7 @@ class OneShot {
|
|
|
1095
1009
|
this.validate(input.task_id, 'task_id');
|
|
1096
1010
|
const response = await fetch(`${this.baseUrl}/v1/compute/${goalId}/respond`, {
|
|
1097
1011
|
method: 'POST',
|
|
1098
|
-
headers:
|
|
1012
|
+
headers: this.jsonHeaders(),
|
|
1099
1013
|
body: JSON.stringify(input)
|
|
1100
1014
|
});
|
|
1101
1015
|
if (response.status === 404) {
|
|
@@ -1119,7 +1033,7 @@ class OneShot {
|
|
|
1119
1033
|
this.validate(goalId, 'goalId');
|
|
1120
1034
|
const response = await fetch(`${this.baseUrl}/v1/compute/${goalId}/pause`, {
|
|
1121
1035
|
method: 'POST',
|
|
1122
|
-
headers:
|
|
1036
|
+
headers: this.jsonHeaders(),
|
|
1123
1037
|
body: JSON.stringify({ reason })
|
|
1124
1038
|
});
|
|
1125
1039
|
if (!response.ok) {
|
|
@@ -1141,7 +1055,7 @@ class OneShot {
|
|
|
1141
1055
|
this.validate(goalId, 'goalId');
|
|
1142
1056
|
const response = await fetch(`${this.baseUrl}/v1/compute/${goalId}/resume`, {
|
|
1143
1057
|
method: 'POST',
|
|
1144
|
-
headers:
|
|
1058
|
+
headers: this.jsonHeaders(),
|
|
1145
1059
|
body: JSON.stringify({})
|
|
1146
1060
|
});
|
|
1147
1061
|
if (!response.ok) {
|
|
@@ -1210,10 +1124,7 @@ class OneShot {
|
|
|
1210
1124
|
* ```
|
|
1211
1125
|
*/
|
|
1212
1126
|
async spendBreakdown(options) {
|
|
1213
|
-
const
|
|
1214
|
-
if (options?.period)
|
|
1215
|
-
params.set('period', String(options.period));
|
|
1216
|
-
const qs = params.toString();
|
|
1127
|
+
const qs = this.buildQuery({ period: options?.period || undefined });
|
|
1217
1128
|
const response = await fetch(`${this.baseUrl}/v1/analytics/spend/breakdown${qs ? `?${qs}` : ''}`, {
|
|
1218
1129
|
headers: this.headers()
|
|
1219
1130
|
});
|
|
@@ -1232,10 +1143,7 @@ class OneShot {
|
|
|
1232
1143
|
* ```
|
|
1233
1144
|
*/
|
|
1234
1145
|
async rocs(options) {
|
|
1235
|
-
const
|
|
1236
|
-
if (options?.period)
|
|
1237
|
-
params.set('period', String(options.period));
|
|
1238
|
-
const qs = params.toString();
|
|
1146
|
+
const qs = this.buildQuery({ period: options?.period || undefined });
|
|
1239
1147
|
const response = await fetch(`${this.baseUrl}/v1/analytics/rocs${qs ? `?${qs}` : ''}`, {
|
|
1240
1148
|
headers: this.headers()
|
|
1241
1149
|
});
|
|
@@ -1256,14 +1164,11 @@ class OneShot {
|
|
|
1256
1164
|
* ```
|
|
1257
1165
|
*/
|
|
1258
1166
|
async receiptsList(options) {
|
|
1259
|
-
const
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
if (options?.limit)
|
|
1265
|
-
params.set('limit', String(options.limit));
|
|
1266
|
-
const qs = params.toString();
|
|
1167
|
+
const qs = this.buildQuery({
|
|
1168
|
+
period: options?.period || undefined,
|
|
1169
|
+
category: options?.category || undefined,
|
|
1170
|
+
limit: options?.limit || undefined,
|
|
1171
|
+
});
|
|
1267
1172
|
const response = await fetch(`${this.baseUrl}/v1/analytics/receipts${qs ? `?${qs}` : ''}`, {
|
|
1268
1173
|
headers: this.headers()
|
|
1269
1174
|
});
|
|
@@ -1315,6 +1220,26 @@ class OneShot {
|
|
|
1315
1220
|
'X-OneShot-SDK-Version': SDK_VERSION
|
|
1316
1221
|
};
|
|
1317
1222
|
}
|
|
1223
|
+
/** Auth headers plus Content-Type for JSON-body requests. */
|
|
1224
|
+
jsonHeaders() {
|
|
1225
|
+
return { 'Content-Type': 'application/json', ...this.headers() };
|
|
1226
|
+
}
|
|
1227
|
+
/** Build a query string, skipping null/undefined values. Pass falsy-but-valid
|
|
1228
|
+
* values (0, '') as undefined at the call site to match prior `if (x)` guards. */
|
|
1229
|
+
buildQuery(params) {
|
|
1230
|
+
const qs = new URLSearchParams();
|
|
1231
|
+
for (const [k, v] of Object.entries(params)) {
|
|
1232
|
+
if (v != null)
|
|
1233
|
+
qs.set(k, String(v));
|
|
1234
|
+
}
|
|
1235
|
+
return qs.toString();
|
|
1236
|
+
}
|
|
1237
|
+
/** Local fast-fail guard: throw when a quote total exceeds the caller's cap. */
|
|
1238
|
+
assertWithinMaxCost(total, maxCost) {
|
|
1239
|
+
if (maxCost && parseFloat(total) > maxCost) {
|
|
1240
|
+
throw new errors_1.OneShotError(`Quote $${total} exceeds maxCost $${maxCost}`);
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1318
1243
|
/**
|
|
1319
1244
|
* Header that asks the API to reject the request when the computed quote
|
|
1320
1245
|
* exceeds the caller-supplied cap (commit 8f328a7). The SDK still does its
|
|
@@ -1327,9 +1252,22 @@ class OneShot {
|
|
|
1327
1252
|
return undefined;
|
|
1328
1253
|
return { 'X-Max-Cost-USDC': maxCost.toString() };
|
|
1329
1254
|
}
|
|
1255
|
+
/**
|
|
1256
|
+
* Idempotency-Key header, sent on both legs (pre-402 and paid retry) so
|
|
1257
|
+
* the server can replay a cached result instead of double-charging and
|
|
1258
|
+
* double-executing on a client retry.
|
|
1259
|
+
*/
|
|
1260
|
+
idempotencyHeader(idempotencyKey) {
|
|
1261
|
+
if (!idempotencyKey)
|
|
1262
|
+
return undefined;
|
|
1263
|
+
return { 'Idempotency-Key': idempotencyKey };
|
|
1264
|
+
}
|
|
1330
1265
|
async executeToolRequest(endpoint, options, quoteId) {
|
|
1331
|
-
const { signal, onStatusUpdate, wait = true, waitForPhones, phoneTimeoutSec, maxCost, ...payload } = options;
|
|
1332
|
-
const extraHeaders =
|
|
1266
|
+
const { signal, onStatusUpdate, wait = true, waitForPhones, phoneTimeoutSec, idempotencyKey, maxCost, ...payload } = options;
|
|
1267
|
+
const extraHeaders = {
|
|
1268
|
+
...this.maxCostHeader(maxCost),
|
|
1269
|
+
...this.idempotencyHeader(idempotencyKey),
|
|
1270
|
+
};
|
|
1333
1271
|
// Validate memo
|
|
1334
1272
|
if (payload.memo !== undefined) {
|
|
1335
1273
|
if (typeof payload.memo !== 'string' || payload.memo.trim().length === 0) {
|
|
@@ -1394,15 +1332,55 @@ class OneShot {
|
|
|
1394
1332
|
}
|
|
1395
1333
|
return (result.data ?? result);
|
|
1396
1334
|
}
|
|
1335
|
+
/**
|
|
1336
|
+
* Shared x402 quote-to-pay flow for paid tools. Fetches the 402 quote, runs
|
|
1337
|
+
* any per-tool 400 handling, enforces maxCost, signs the payment auth, and
|
|
1338
|
+
* POSTs the paid request. Returns the parsed quote context plus the raw paid
|
|
1339
|
+
* Response — the caller owns the post-202 handling (poll a job vs. return the
|
|
1340
|
+
* body directly), since that differs per tool.
|
|
1341
|
+
*/
|
|
1342
|
+
async runQuoteToPay(cfg) {
|
|
1343
|
+
const quoteResp = await this.makeRequest(cfg.endpoint, cfg.payload, undefined, undefined, cfg.signal, cfg.quoteTimeoutMs, this.maxCostHeader(cfg.maxCost));
|
|
1344
|
+
if (quoteResp.status === 400 && cfg.on400) {
|
|
1345
|
+
await cfg.on400(quoteResp);
|
|
1346
|
+
}
|
|
1347
|
+
if (quoteResp.status !== 402) {
|
|
1348
|
+
throw new errors_1.ToolError(cfg.expectMsg, quoteResp.status, await quoteResp.text());
|
|
1349
|
+
}
|
|
1350
|
+
const quoteData = await quoteResp.json();
|
|
1351
|
+
cfg.onQuote(quoteData.context);
|
|
1352
|
+
this.assertWithinMaxCost(cfg.totalOf(quoteData.context), cfg.maxCost);
|
|
1353
|
+
const paymentInfo = {
|
|
1354
|
+
protocol: 'x402',
|
|
1355
|
+
network: `eip155:${quoteData.payment_request.chain_id}`,
|
|
1356
|
+
payTo: quoteData.payment_request.recipient,
|
|
1357
|
+
amount: quoteData.payment_request.amount,
|
|
1358
|
+
currency: 'USD',
|
|
1359
|
+
facilitator_url: this.baseUrl,
|
|
1360
|
+
token: { address: quoteData.payment_request.token_address, symbol: 'USDC', decimals: 6 }
|
|
1361
|
+
};
|
|
1362
|
+
this.checkAbortBeforePayment(cfg.signal);
|
|
1363
|
+
const { accepted, resource, extensions } = await this.getAcceptedRequirements(quoteResp, cfg.endpoint, cfg.payload, quoteData.context.quote_id, cfg.signal);
|
|
1364
|
+
const auth = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
|
|
1365
|
+
const execResp = await this.makeRequest(cfg.endpoint, cfg.payload, auth, quoteData.context.quote_id, cfg.signal, cfg.execTimeoutMs);
|
|
1366
|
+
return { context: quoteData.context, execResp };
|
|
1367
|
+
}
|
|
1397
1368
|
async pollJob(requestId, timeoutSec, signal, onStatusUpdate, phoneOpts) {
|
|
1398
|
-
// Try WebSocket push first, fall back to HTTP polling
|
|
1369
|
+
// Try WebSocket push first, fall back to HTTP polling. Both phases share a
|
|
1370
|
+
// single deadline so the combined wait never exceeds the caller's timeout —
|
|
1371
|
+
// previously the WS cap (≤180s) plus a fresh full HTTP timeout could total
|
|
1372
|
+
// up to ~1.6× timeoutSec (e.g. ~480s for a 300s request).
|
|
1373
|
+
const deadline = timeoutSec != null ? Date.now() + timeoutSec * 1000 : undefined;
|
|
1399
1374
|
let result;
|
|
1400
1375
|
try {
|
|
1401
1376
|
result = await this.waitViaWebSocket(requestId, timeoutSec, signal, onStatusUpdate);
|
|
1402
1377
|
}
|
|
1403
1378
|
catch {
|
|
1404
1379
|
this.log('WebSocket unavailable, falling back to HTTP polling');
|
|
1405
|
-
|
|
1380
|
+
const remainingSec = deadline != null
|
|
1381
|
+
? Math.max(1, Math.ceil((deadline - Date.now()) / 1000))
|
|
1382
|
+
: undefined;
|
|
1383
|
+
result = await this.pollJobHttp(requestId, remainingSec, signal, onStatusUpdate);
|
|
1406
1384
|
}
|
|
1407
1385
|
// Optional second phase: keep polling for the async phone-reveal webhook.
|
|
1408
1386
|
// Only kicks in when the caller explicitly opts in AND the result still
|
|
@@ -1579,7 +1557,7 @@ class OneShot {
|
|
|
1579
1557
|
else if (msg.status === 'failed') {
|
|
1580
1558
|
settle(() => {
|
|
1581
1559
|
cleanup();
|
|
1582
|
-
reject(new errors_1.JobError(`Job failed: ${msg.error ?? 'Unknown'}`, requestId, String(msg.error ?? 'Unknown')));
|
|
1560
|
+
reject(new errors_1.JobError(`Job failed: ${msg.error ?? 'Unknown'}`, requestId, String(msg.error ?? 'Unknown'), msg.error_code));
|
|
1583
1561
|
});
|
|
1584
1562
|
}
|
|
1585
1563
|
else {
|
|
@@ -1635,7 +1613,7 @@ class OneShot {
|
|
|
1635
1613
|
return result;
|
|
1636
1614
|
}
|
|
1637
1615
|
if (job.status === 'failed') {
|
|
1638
|
-
throw new errors_1.JobError(`Job failed: ${job.error ?? 'Unknown'}`, requestId, String(job.error ?? 'Unknown'));
|
|
1616
|
+
throw new errors_1.JobError(`Job failed: ${job.error ?? 'Unknown'}`, requestId, String(job.error ?? 'Unknown'), job.error_code);
|
|
1639
1617
|
}
|
|
1640
1618
|
onStatusUpdate?.(job.status, requestId);
|
|
1641
1619
|
retries = 0;
|