@oneshot-agent/sdk 0.54.0 → 0.56.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +118 -3
- package/dist/approvals.d.ts +1 -1
- package/dist/approvals.d.ts.map +1 -1
- package/dist/deadline.d.ts.map +1 -1
- package/dist/deadline.js +17 -4
- package/dist/deadline.js.map +1 -1
- package/dist/errors.d.ts +6 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +37 -2
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +56 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +311 -124
- package/dist/index.js.map +1 -1
- package/dist/predict-input.generated.d.ts +59 -0
- package/dist/predict-input.generated.d.ts.map +1 -0
- package/dist/predict-input.generated.js +3 -0
- package/dist/predict-input.generated.js.map +1 -0
- package/dist/predict.d.ts +97 -0
- package/dist/predict.d.ts.map +1 -0
- package/dist/predict.js +3 -0
- package/dist/predict.js.map +1 -0
- package/dist/solve-input.generated.d.ts +143 -0
- package/dist/solve-input.generated.d.ts.map +1 -0
- package/dist/solve-input.generated.js +3 -0
- package/dist/solve-input.generated.js.map +1 -0
- package/dist/solve.d.ts +129 -0
- package/dist/solve.d.ts.map +1 -0
- package/dist/solve.js +7 -0
- package/dist/solve.js.map +1 -0
- package/dist/types.d.ts +46 -22
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -38,6 +38,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.OneShot = exports.executeSwap = exports.getSwapQuote = exports.ReadOnlyWalletProvider = exports.CdpWalletProvider = exports.EthersWalletProvider = void 0;
|
|
40
40
|
const hunts_1 = require("./hunts");
|
|
41
|
+
__exportStar(require("./solve"), exports);
|
|
42
|
+
__exportStar(require("./predict"), exports);
|
|
41
43
|
__exportStar(require("./hunts"), exports);
|
|
42
44
|
const physical_mail_1 = require("./physical-mail");
|
|
43
45
|
__exportStar(require("./physical-mail"), exports);
|
|
@@ -65,11 +67,36 @@ const approvals_1 = require("./approvals");
|
|
|
65
67
|
// stay off this public surface (see tests/unit/sdk-provider-cost-removed.test.ts).
|
|
66
68
|
// Import from the `@oneshot-agent/sdk/receipt` subpath instead.
|
|
67
69
|
// Keep in sync with package.json `version`. Guarded by version.test.ts.
|
|
68
|
-
const SDK_VERSION = '0.
|
|
70
|
+
const SDK_VERSION = '0.56.0';
|
|
69
71
|
/** HTTP poll cadence while push is unconfirmed: fast first checks, settling at 2s. */
|
|
70
72
|
const HTTP_POLL_BACKOFF_MS = [300, 600, 1000, 2000];
|
|
71
73
|
/** HTTP poll cadence once the WebSocket has delivered for this request. */
|
|
72
74
|
const HTTP_POLL_RELAXED_MS = 5000;
|
|
75
|
+
function isTransientComputeReadError(error) {
|
|
76
|
+
if (error instanceof errors_2.ToolError) {
|
|
77
|
+
return error.statusCode === 429 || (error.statusCode >= 500 && error.statusCode < 600);
|
|
78
|
+
}
|
|
79
|
+
// Fetch network failures differ across browsers, Node and Bun. Do not swallow
|
|
80
|
+
// arbitrary TypeErrors (bad URLs/configuration), JSON errors or signing errors.
|
|
81
|
+
if (!(error instanceof Error))
|
|
82
|
+
return false;
|
|
83
|
+
const seen = new Set();
|
|
84
|
+
let current = error;
|
|
85
|
+
while (current !== null && typeof current === 'object' && !seen.has(current)) {
|
|
86
|
+
seen.add(current);
|
|
87
|
+
const { code, cause } = current;
|
|
88
|
+
if ((code !== undefined && [
|
|
89
|
+
'ECONNRESET', 'ECONNREFUSED', 'ECONNABORTED', 'EPIPE', 'ETIMEDOUT',
|
|
90
|
+
'ENOTFOUND', 'EAI_AGAIN', 'ENETUNREACH', 'EHOSTUNREACH',
|
|
91
|
+
'ConnectionRefused', 'ConnectionClosed', 'Timeout',
|
|
92
|
+
'UND_ERR_SOCKET', 'UND_ERR_CONNECT_TIMEOUT', 'UND_ERR_HEADERS_TIMEOUT', 'UND_ERR_BODY_TIMEOUT',
|
|
93
|
+
].includes(code)) || (current instanceof TypeError &&
|
|
94
|
+
/^(fetch failed|Failed to fetch|Load failed|NetworkError when attempting to fetch resource\.)$/i.test(current.message)))
|
|
95
|
+
return true;
|
|
96
|
+
current = cause;
|
|
97
|
+
}
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
73
100
|
const BASE_URL = 'https://win.oneshotagent.com';
|
|
74
101
|
const RPC_URL = 'https://mainnet.base.org';
|
|
75
102
|
const CHAIN_ID = 8453;
|
|
@@ -147,6 +174,10 @@ function validateBudgetConfig(budgets) {
|
|
|
147
174
|
}
|
|
148
175
|
/** The keys executeToolRequestImpl takes out of a request as SDK options, so they never reach the API body. */
|
|
149
176
|
const PAID_ROUTE_OPTION_KEYS = ['timeout', 'totalTimeoutMs', 'onRequestCreated', 'onAccepted', 'signal', 'onStatusUpdate', 'wait', 'waitForPhones', 'phoneTimeoutSec', 'idempotencyKey', 'approvalId', 'maxCost'];
|
|
177
|
+
/** ETH mode is unsupported on CDP wallets (#2153): the swap path needs a confirmed receipt CDP never returns. */
|
|
178
|
+
function cdpEthModeError() {
|
|
179
|
+
return new errors_2.ValidationError("ETH currency mode is not supported with CDP wallets; fund the CDP wallet with USDC or use currency: 'USDC' (or a privateKey / custom walletProvider for ETH mode)", 'currency');
|
|
180
|
+
}
|
|
150
181
|
class OneShot {
|
|
151
182
|
/**
|
|
152
183
|
* Async factory — required for CDP wallets (account creation is async).
|
|
@@ -174,6 +205,9 @@ class OneShot {
|
|
|
174
205
|
return new OneShot(config, config.walletProvider);
|
|
175
206
|
}
|
|
176
207
|
if (config.cdp) {
|
|
208
|
+
// Reject before the CDP SDK is imported or an account is created (#2153).
|
|
209
|
+
if ((config.currency ?? 'USDC') === 'ETH')
|
|
210
|
+
throw cdpEthModeError();
|
|
177
211
|
const { CdpWalletProvider } = await Promise.resolve().then(() => __importStar(require('./providers/cdp')));
|
|
178
212
|
const cdpOpts = typeof config.cdp === 'object' ? config.cdp : undefined;
|
|
179
213
|
const walletProvider = await CdpWalletProvider.create(cdpOpts);
|
|
@@ -193,7 +227,7 @@ class OneShot {
|
|
|
193
227
|
constructor(config, walletProvider) {
|
|
194
228
|
this.hunts = new hunts_1.Hunts(this);
|
|
195
229
|
this.physicalMail = new physical_mail_1.PhysicalMail(async (path, method, body, mime) => {
|
|
196
|
-
const response = await
|
|
230
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/physical-mail${path}`, {
|
|
197
231
|
method, headers: { ...await this.signedReadHeaders(method === 'GET' ? 'read' : 'write'), 'Content-Type': mime ?? 'application/json' },
|
|
198
232
|
...(body === undefined ? {} : { body: mime ? body : JSON.stringify(body) }),
|
|
199
233
|
signal: AbortSignal.timeout(60000),
|
|
@@ -207,6 +241,7 @@ class OneShot {
|
|
|
207
241
|
/** Which paid calls run, are denied, or wait for approval. Wallet sessions write it. */
|
|
208
242
|
this.policy = new approvals_1.ActionPolicies((path, method, body, scope) => this.agentJson(path, method, body, scope));
|
|
209
243
|
this.readProofWarned = false;
|
|
244
|
+
this.proofClockOffsetSec = 0;
|
|
210
245
|
/** ETH mode: signed payments not yet observed as settled, keyed by reservation id. */
|
|
211
246
|
this._usdcPending = new Map();
|
|
212
247
|
this._usdcReservationSeq = 0;
|
|
@@ -262,6 +297,11 @@ class OneShot {
|
|
|
262
297
|
else {
|
|
263
298
|
throw new errors_2.ValidationError('Provide privateKey or use OneShot.create() for CDP/custom wallets', 'config');
|
|
264
299
|
}
|
|
300
|
+
// CDP wallets cannot run ETH mode even though CdpWalletProvider exposes
|
|
301
|
+
// sendTransaction: its wait() never observes a receipt, so the swap path
|
|
302
|
+
// cannot confirm the ETH→USDC trade (#2153).
|
|
303
|
+
if (config.cdp && this._currency === 'ETH')
|
|
304
|
+
throw cdpEthModeError();
|
|
265
305
|
if (this._currency === 'ETH' && !this.provider.sendTransaction) {
|
|
266
306
|
throw new errors_2.ValidationError('ETH currency mode requires a wallet provider that supports sendTransaction', 'currency');
|
|
267
307
|
}
|
|
@@ -421,35 +461,35 @@ class OneShot {
|
|
|
421
461
|
}
|
|
422
462
|
/** List the caller's domain pool with warmup and rotation metadata. */
|
|
423
463
|
async listDomains() {
|
|
424
|
-
const response = await
|
|
464
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/email/domains`, {
|
|
425
465
|
headers: await this.signedReadHeaders(),
|
|
426
466
|
});
|
|
427
467
|
if (!response.ok) {
|
|
428
|
-
throw new errors_2.ToolError('Failed to list domains', response.status, await response.text());
|
|
468
|
+
throw new errors_2.ToolError('Failed to list domains', response.status, await response.text(), response.headers.get('retry-after'));
|
|
429
469
|
}
|
|
430
470
|
return response.json();
|
|
431
471
|
}
|
|
432
472
|
/** Take a domain out of rotation without releasing it. */
|
|
433
473
|
async pauseDomain(domain) {
|
|
434
474
|
this.validate(domain, 'domain');
|
|
435
|
-
const response = await
|
|
475
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/email/domains/${encodeURIComponent(domain)}/pause`, {
|
|
436
476
|
method: 'POST',
|
|
437
477
|
headers: await this.signedReadHeaders('write'),
|
|
438
478
|
});
|
|
439
479
|
if (!response.ok) {
|
|
440
|
-
throw new errors_2.ToolError('Failed to pause domain', response.status, await response.text());
|
|
480
|
+
throw new errors_2.ToolError('Failed to pause domain', response.status, await response.text(), response.headers.get('retry-after'));
|
|
441
481
|
}
|
|
442
482
|
return response.json();
|
|
443
483
|
}
|
|
444
484
|
/** Put a paused domain back into rotation. */
|
|
445
485
|
async resumeDomain(domain) {
|
|
446
486
|
this.validate(domain, 'domain');
|
|
447
|
-
const response = await
|
|
487
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/email/domains/${encodeURIComponent(domain)}/resume`, {
|
|
448
488
|
method: 'POST',
|
|
449
489
|
headers: await this.signedReadHeaders('write'),
|
|
450
490
|
});
|
|
451
491
|
if (!response.ok) {
|
|
452
|
-
throw new errors_2.ToolError('Failed to resume domain', response.status, await response.text());
|
|
492
|
+
throw new errors_2.ToolError('Failed to resume domain', response.status, await response.text(), response.headers.get('retry-after'));
|
|
453
493
|
}
|
|
454
494
|
return response.json();
|
|
455
495
|
}
|
|
@@ -457,14 +497,33 @@ class OneShot {
|
|
|
457
497
|
this.validate(options.topic, 'topic');
|
|
458
498
|
return this.tool('research', { ...options });
|
|
459
499
|
}
|
|
500
|
+
async predict(input) {
|
|
501
|
+
const { memo, valueTag, decisionContext, ...options } = input;
|
|
502
|
+
return this.tool('predict', { ...options, timeout: options.timeout ?? 240 });
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Solve a routing or packing problem with constraint checks and conflicts. Quotes, pays and waits for
|
|
506
|
+
* the job. The result's `solution` is typed by the request's `problem`.
|
|
507
|
+
*/
|
|
508
|
+
async solve(input) {
|
|
509
|
+
// Shared option objects and JavaScript callers may still supply receipt metadata.
|
|
510
|
+
// Solve's strict request schema does not accept it, on either quote or paid requests.
|
|
511
|
+
const { memo, valueTag, decisionContext, ...options } = input;
|
|
512
|
+
return this.tool('solve', { ...options, timeout: options.timeout ?? 420 });
|
|
513
|
+
}
|
|
460
514
|
async personFind(options) {
|
|
461
515
|
const { includePhone, ...payload } = options;
|
|
462
516
|
return this.tool('find/person', { ...payload, include_phone: includePhone, limit: options.limit ?? 100 });
|
|
463
517
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
518
|
+
/** At least one identity (#1899): name, email, phone or a profile URL under any of its names. */
|
|
519
|
+
requirePersonIdentity(options) {
|
|
520
|
+
const hasName = options.name || options.full_name || (options.first_name && options.last_name);
|
|
521
|
+
if (!hasName && !options.email && !options.phone && !options.profile_url && !options.linkedin_url && !options.social_media_url) {
|
|
522
|
+
throw new errors_2.ValidationError('At least one of name, email, phone, or profile_url is required', 'identifier');
|
|
467
523
|
}
|
|
524
|
+
}
|
|
525
|
+
async personProfile(options) {
|
|
526
|
+
this.requirePersonIdentity(options);
|
|
468
527
|
const { includePhone, ...payload } = options;
|
|
469
528
|
return this.tool('lookup/person-profile', { ...payload, include_phone: includePhone });
|
|
470
529
|
}
|
|
@@ -524,10 +583,7 @@ class OneShot {
|
|
|
524
583
|
});
|
|
525
584
|
}
|
|
526
585
|
async personEmail(options) {
|
|
527
|
-
this.
|
|
528
|
-
if (!options.full_name && !(options.first_name && options.last_name)) {
|
|
529
|
-
throw new errors_2.ValidationError('Either full_name or both first_name and last_name required', 'name');
|
|
530
|
-
}
|
|
586
|
+
this.requirePersonIdentity(options);
|
|
531
587
|
return this.tool('lookup/person-email', { ...options });
|
|
532
588
|
}
|
|
533
589
|
async personEmailStatus(options) {
|
|
@@ -535,17 +591,13 @@ class OneShot {
|
|
|
535
591
|
return this.tool('lookup/person-email-status', { ...options });
|
|
536
592
|
}
|
|
537
593
|
async personDossier(options) {
|
|
538
|
-
|
|
539
|
-
throw new errors_2.ValidationError('At least one of email, social_media_url, or name is required', 'identifier');
|
|
540
|
-
}
|
|
594
|
+
this.requirePersonIdentity(options);
|
|
541
595
|
const { includePhone, ...payload } = options;
|
|
542
596
|
// The dossier takes 2 to 5 minutes; the generic 120 second poll gives up on a normal run (#1913).
|
|
543
597
|
return this.tool('lookup/person-dossier', { ...payload, include_phone: includePhone, timeout: options.timeout ?? 300 });
|
|
544
598
|
}
|
|
545
599
|
async personSocialAccounts(options) {
|
|
546
|
-
|
|
547
|
-
throw new errors_2.ValidationError('At least one of email or social_media_url is required', 'identifier');
|
|
548
|
-
}
|
|
600
|
+
this.requirePersonIdentity(options);
|
|
549
601
|
return this.tool('lookup/person-social-accounts', { ...options });
|
|
550
602
|
}
|
|
551
603
|
async personArticles(options) {
|
|
@@ -554,17 +606,15 @@ class OneShot {
|
|
|
554
606
|
return this.tool('lookup/person-articles', { ...options });
|
|
555
607
|
}
|
|
556
608
|
async personPosts(options) {
|
|
557
|
-
this.
|
|
609
|
+
this.requirePersonIdentity(options);
|
|
558
610
|
return this.tool('lookup/person-posts', { ...options });
|
|
559
611
|
}
|
|
560
612
|
async personInterests(options) {
|
|
561
|
-
|
|
562
|
-
throw new errors_2.ValidationError('At least one of email, phone, or social_media_url is required', 'identifier');
|
|
563
|
-
}
|
|
613
|
+
this.requirePersonIdentity(options);
|
|
564
614
|
return this.tool('lookup/person-interests', { ...options });
|
|
565
615
|
}
|
|
566
616
|
async personFollowers(options) {
|
|
567
|
-
this.
|
|
617
|
+
this.requirePersonIdentity(options);
|
|
568
618
|
return this.tool('lookup/person-followers', { ...options });
|
|
569
619
|
}
|
|
570
620
|
async inboxList(options = {}) {
|
|
@@ -573,24 +623,24 @@ class OneShot {
|
|
|
573
623
|
limit: options.limit || undefined,
|
|
574
624
|
include_body: options.include_body ? 'true' : undefined,
|
|
575
625
|
});
|
|
576
|
-
const response = await
|
|
626
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/inbox${qs ? `?${qs}` : ''}`, {
|
|
577
627
|
headers: await this.signedReadHeaders()
|
|
578
628
|
});
|
|
579
629
|
if (!response.ok) {
|
|
580
|
-
throw new errors_2.ToolError('Failed to list inbox', response.status, await response.text());
|
|
630
|
+
throw new errors_2.ToolError('Failed to list inbox', response.status, await response.text(), response.headers.get('retry-after'));
|
|
581
631
|
}
|
|
582
632
|
return response.json();
|
|
583
633
|
}
|
|
584
634
|
async inboxGet(emailId) {
|
|
585
635
|
this.validate(emailId, 'emailId');
|
|
586
|
-
const response = await
|
|
636
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/inbox/${emailId}`, {
|
|
587
637
|
headers: await this.signedReadHeaders()
|
|
588
638
|
});
|
|
589
639
|
if (response.status === 404) {
|
|
590
640
|
throw new errors_2.ToolError('Email not found', 404, 'Email not found');
|
|
591
641
|
}
|
|
592
642
|
if (!response.ok) {
|
|
593
|
-
throw new errors_2.ToolError('Failed to get email', response.status, await response.text());
|
|
643
|
+
throw new errors_2.ToolError('Failed to get email', response.status, await response.text(), response.headers.get('retry-after'));
|
|
594
644
|
}
|
|
595
645
|
return response.json();
|
|
596
646
|
}
|
|
@@ -935,14 +985,14 @@ class OneShot {
|
|
|
935
985
|
this.validate(name, 'name');
|
|
936
986
|
if (options.cookies !== undefined && options.storage_state !== undefined)
|
|
937
987
|
throw new errors_2.ValidationError('Provide cookies or storage_state, not both', 'options');
|
|
938
|
-
const response = await
|
|
988
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/browser/profiles`, {
|
|
939
989
|
method: 'POST',
|
|
940
990
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders()) },
|
|
941
991
|
body: JSON.stringify({ name, ...options }),
|
|
942
992
|
signal: AbortSignal.timeout(150000),
|
|
943
993
|
});
|
|
944
994
|
if (!response.ok) {
|
|
945
|
-
throw new errors_2.ToolError('Failed to create browser profile', response.status, await response.text());
|
|
995
|
+
throw new errors_2.ToolError('Failed to create browser profile', response.status, await response.text(), response.headers.get('retry-after'));
|
|
946
996
|
}
|
|
947
997
|
return response.json();
|
|
948
998
|
}
|
|
@@ -971,12 +1021,12 @@ class OneShot {
|
|
|
971
1021
|
}
|
|
972
1022
|
async browserProfileSetup(profileId, action, body = {}) {
|
|
973
1023
|
this.validate(profileId, 'profileId');
|
|
974
|
-
const response = await
|
|
1024
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/browser/profiles/${encodeURIComponent(profileId)}/setup/${action}`, {
|
|
975
1025
|
method: 'POST', headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders()) },
|
|
976
1026
|
body: JSON.stringify(body), signal: AbortSignal.timeout(150000),
|
|
977
1027
|
});
|
|
978
1028
|
if (!response.ok)
|
|
979
|
-
throw new errors_2.ToolError('Browser profile setup failed', response.status, '');
|
|
1029
|
+
throw new errors_2.ToolError('Browser profile setup failed', response.status, '', response.headers.get('retry-after'));
|
|
980
1030
|
return response.json();
|
|
981
1031
|
}
|
|
982
1032
|
/**
|
|
@@ -991,11 +1041,11 @@ class OneShot {
|
|
|
991
1041
|
* ```
|
|
992
1042
|
*/
|
|
993
1043
|
async listBrowserProfiles() {
|
|
994
|
-
const response = await
|
|
1044
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/browser/profiles`, {
|
|
995
1045
|
headers: await this.signedReadHeaders(),
|
|
996
1046
|
});
|
|
997
1047
|
if (!response.ok) {
|
|
998
|
-
throw new errors_2.ToolError('Failed to list browser profiles', response.status, await response.text());
|
|
1048
|
+
throw new errors_2.ToolError('Failed to list browser profiles', response.status, await response.text(), response.headers.get('retry-after'));
|
|
999
1049
|
}
|
|
1000
1050
|
const data = await response.json();
|
|
1001
1051
|
return data.profiles;
|
|
@@ -1010,12 +1060,12 @@ class OneShot {
|
|
|
1010
1060
|
*/
|
|
1011
1061
|
async deleteBrowserProfile(profileId) {
|
|
1012
1062
|
this.validate(profileId, 'profileId');
|
|
1013
|
-
const response = await
|
|
1063
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/browser/profiles/${profileId}`, {
|
|
1014
1064
|
method: 'DELETE',
|
|
1015
1065
|
headers: await this.signedReadHeaders(),
|
|
1016
1066
|
});
|
|
1017
1067
|
if (!response.ok) {
|
|
1018
|
-
throw new errors_2.ToolError('Failed to delete browser profile', response.status, await response.text());
|
|
1068
|
+
throw new errors_2.ToolError('Failed to delete browser profile', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1019
1069
|
}
|
|
1020
1070
|
}
|
|
1021
1071
|
async linkedinFree(method, path, opts) {
|
|
@@ -1023,7 +1073,7 @@ class OneShot {
|
|
|
1023
1073
|
for (const [k, v] of Object.entries(opts.query ?? {}))
|
|
1024
1074
|
if (v !== undefined)
|
|
1025
1075
|
url.searchParams.set(k, v);
|
|
1026
|
-
const response = await
|
|
1076
|
+
const response = await this.fetchApi(url.toString(), {
|
|
1027
1077
|
method,
|
|
1028
1078
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders(opts.scope ?? 'read')) },
|
|
1029
1079
|
...(opts.body !== undefined ? { body: JSON.stringify(opts.body) } : {}),
|
|
@@ -1038,7 +1088,7 @@ class OneShot {
|
|
|
1038
1088
|
catch { /* Non-JSON upstream error. */ }
|
|
1039
1089
|
if (response.status === 409 && code === 'connect_required')
|
|
1040
1090
|
throw new errors_1.LinkedInConnectRequiredError(body);
|
|
1041
|
-
throw new errors_2.ToolError(`LinkedIn ${opts.what} failed`, response.status, body);
|
|
1091
|
+
throw new errors_2.ToolError(`LinkedIn ${opts.what} failed`, response.status, body, response.headers.get('retry-after'));
|
|
1042
1092
|
}
|
|
1043
1093
|
return response.json();
|
|
1044
1094
|
}
|
|
@@ -1225,11 +1275,11 @@ class OneShot {
|
|
|
1225
1275
|
limit: options.limit || undefined,
|
|
1226
1276
|
from: options.from || undefined,
|
|
1227
1277
|
});
|
|
1228
|
-
const response = await
|
|
1278
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/sms/inbox${qs ? `?${qs}` : ''}`, {
|
|
1229
1279
|
headers: await this.signedReadHeaders()
|
|
1230
1280
|
});
|
|
1231
1281
|
if (!response.ok) {
|
|
1232
|
-
throw new errors_2.ToolError('Failed to list SMS inbox', response.status, await response.text());
|
|
1282
|
+
throw new errors_2.ToolError('Failed to list SMS inbox', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1233
1283
|
}
|
|
1234
1284
|
return response.json();
|
|
1235
1285
|
}
|
|
@@ -1371,7 +1421,7 @@ class OneShot {
|
|
|
1371
1421
|
const scope = (0, deadline_1.deadlineScope)(toolOptions.signal, filled.startsWith('/v1/tools/browser/profiles') ? 150000 : 30000);
|
|
1372
1422
|
try {
|
|
1373
1423
|
const request = (async () => {
|
|
1374
|
-
const response = await
|
|
1424
|
+
const response = await this.fetchApi(`${this.baseUrl}${target}`, {
|
|
1375
1425
|
method,
|
|
1376
1426
|
headers: { ...(sendsBody ? { 'Content-Type': 'application/json' } : {}), ...(await this.signedReadHeaders(method === 'GET' ? 'read' : 'write')) },
|
|
1377
1427
|
...(sendsBody ? { body: JSON.stringify(body ?? {}) } : {}),
|
|
@@ -1404,7 +1454,7 @@ class OneShot {
|
|
|
1404
1454
|
}
|
|
1405
1455
|
/** Signed free request returning the `data` field of `{ success, data }`. */
|
|
1406
1456
|
async signedJson(method, path, body, scope, failure) {
|
|
1407
|
-
const response = await
|
|
1457
|
+
const response = await this.fetchApi(`${this.baseUrl}${path}`, {
|
|
1408
1458
|
method,
|
|
1409
1459
|
headers: {
|
|
1410
1460
|
...(body !== undefined ? { 'Content-Type': 'application/json' } : {}),
|
|
@@ -1413,7 +1463,7 @@ class OneShot {
|
|
|
1413
1463
|
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
1414
1464
|
});
|
|
1415
1465
|
if (!response.ok) {
|
|
1416
|
-
throw new errors_2.ToolError(failure, response.status, await response.text());
|
|
1466
|
+
throw new errors_2.ToolError(failure, response.status, await response.text(), response.headers.get('retry-after'));
|
|
1417
1467
|
}
|
|
1418
1468
|
const json = await response.json();
|
|
1419
1469
|
return (json.data ?? json);
|
|
@@ -1429,14 +1479,14 @@ class OneShot {
|
|
|
1429
1479
|
*/
|
|
1430
1480
|
async smsInboxGet(messageId) {
|
|
1431
1481
|
this.validate(messageId, 'messageId');
|
|
1432
|
-
const response = await
|
|
1482
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/sms/inbox/${messageId}`, {
|
|
1433
1483
|
headers: await this.signedReadHeaders()
|
|
1434
1484
|
});
|
|
1435
1485
|
if (response.status === 404) {
|
|
1436
1486
|
throw new errors_2.ToolError('SMS message not found', 404, 'Message not found');
|
|
1437
1487
|
}
|
|
1438
1488
|
if (!response.ok) {
|
|
1439
|
-
throw new errors_2.ToolError('Failed to get SMS message', response.status, await response.text());
|
|
1489
|
+
throw new errors_2.ToolError('Failed to get SMS message', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1440
1490
|
}
|
|
1441
1491
|
return response.json();
|
|
1442
1492
|
}
|
|
@@ -1457,11 +1507,11 @@ class OneShot {
|
|
|
1457
1507
|
unread: options.unread ? 'true' : undefined,
|
|
1458
1508
|
limit: options.limit || undefined,
|
|
1459
1509
|
});
|
|
1460
|
-
const response = await
|
|
1510
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/notifications${qs ? `?${qs}` : ''}`, {
|
|
1461
1511
|
headers: await this.signedReadHeaders()
|
|
1462
1512
|
});
|
|
1463
1513
|
if (!response.ok) {
|
|
1464
|
-
throw new errors_2.ToolError('Failed to list notifications', response.status, await response.text());
|
|
1514
|
+
throw new errors_2.ToolError('Failed to list notifications', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1465
1515
|
}
|
|
1466
1516
|
return response.json();
|
|
1467
1517
|
}
|
|
@@ -1475,7 +1525,7 @@ class OneShot {
|
|
|
1475
1525
|
*/
|
|
1476
1526
|
async markNotificationRead(notificationId) {
|
|
1477
1527
|
this.validate(notificationId, 'notificationId');
|
|
1478
|
-
const response = await
|
|
1528
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/notifications/${notificationId}/read`, {
|
|
1479
1529
|
method: 'PATCH',
|
|
1480
1530
|
headers: await this.signedReadHeaders('write')
|
|
1481
1531
|
});
|
|
@@ -1483,15 +1533,15 @@ class OneShot {
|
|
|
1483
1533
|
throw new errors_2.ToolError('Notification not found', 404, 'Notification not found');
|
|
1484
1534
|
}
|
|
1485
1535
|
if (!response.ok) {
|
|
1486
|
-
throw new errors_2.ToolError('Failed to mark notification as read', response.status, await response.text());
|
|
1536
|
+
throw new errors_2.ToolError('Failed to mark notification as read', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1487
1537
|
}
|
|
1488
1538
|
}
|
|
1489
1539
|
async getUnifiedBalance() {
|
|
1490
|
-
const response = await
|
|
1540
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/balance`, {
|
|
1491
1541
|
headers: await this.signedReadHeaders()
|
|
1492
1542
|
});
|
|
1493
1543
|
if (!response.ok) {
|
|
1494
|
-
throw new errors_2.ToolError('Failed to fetch balance', response.status, await response.text());
|
|
1544
|
+
throw new errors_2.ToolError('Failed to fetch balance', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1495
1545
|
}
|
|
1496
1546
|
return response.json();
|
|
1497
1547
|
}
|
|
@@ -1581,20 +1631,89 @@ class OneShot {
|
|
|
1581
1631
|
* console.log(status.status, status.budget?.remaining);
|
|
1582
1632
|
* ```
|
|
1583
1633
|
*/
|
|
1584
|
-
async getComputeGoal(goalId) {
|
|
1634
|
+
async getComputeGoal(goalId, options = {}) {
|
|
1585
1635
|
this.validate(goalId, 'goalId');
|
|
1586
|
-
const response = await
|
|
1587
|
-
headers: await this.signedReadHeaders()
|
|
1636
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}`, {
|
|
1637
|
+
headers: await this.signedReadHeaders(),
|
|
1638
|
+
signal: options.signal,
|
|
1588
1639
|
});
|
|
1589
1640
|
if (response.status === 404) {
|
|
1590
1641
|
throw new errors_2.ToolError('Goal not found', 404, 'Goal not found');
|
|
1591
1642
|
}
|
|
1592
1643
|
if (!response.ok) {
|
|
1593
|
-
|
|
1644
|
+
// Keep the HTTP classification even if the error body is interrupted.
|
|
1645
|
+
const body = await response.text().catch(() => {
|
|
1646
|
+
options.signal?.throwIfAborted();
|
|
1647
|
+
return 'Response body unavailable';
|
|
1648
|
+
});
|
|
1649
|
+
throw new errors_2.ToolError('Failed to get compute goal', response.status, body, response.headers.get('retry-after'));
|
|
1594
1650
|
}
|
|
1595
1651
|
const json = await response.json();
|
|
1596
1652
|
return json.data;
|
|
1597
1653
|
}
|
|
1654
|
+
/**
|
|
1655
|
+
* Poll a compute goal using its goal_id, not its creation request_id.
|
|
1656
|
+
* Returns the complete goal snapshot on completed; failed/cancelled reject
|
|
1657
|
+
* with JobError (goal ID, end_reason code, snapshot in partialResult).
|
|
1658
|
+
* Budget exhaustion ends this wait; after fundComputeGoal revives the goal,
|
|
1659
|
+
* call waitForComputeGoal again with the same ID to observe the new run.
|
|
1660
|
+
* Paused/awaiting_confirmation and recurring goals keep waiting until the
|
|
1661
|
+
* goal ends or the timeout (seconds, default 120) expires with JobTimeoutError.
|
|
1662
|
+
* Infinity disables the local deadline.
|
|
1663
|
+
* onStatusUpdate receives each distinct observed status before settlement.
|
|
1664
|
+
* Callback throws/rejections are ignored and promises are not awaited.
|
|
1665
|
+
* Transient reads (5xx, 429, network failures) retry on the polling cadence.
|
|
1666
|
+
*/
|
|
1667
|
+
async waitForComputeGoal(goalId, options = {}) {
|
|
1668
|
+
this.validate(goalId, 'goalId');
|
|
1669
|
+
const started = Date.now();
|
|
1670
|
+
const scope = (0, deadline_1.deadlineScope)(options.signal, (options.timeout ?? 120) * 1000);
|
|
1671
|
+
const poll = async () => {
|
|
1672
|
+
let lastStatus;
|
|
1673
|
+
let polls = 0;
|
|
1674
|
+
while (true) {
|
|
1675
|
+
scope.signal.throwIfAborted();
|
|
1676
|
+
let goal;
|
|
1677
|
+
try {
|
|
1678
|
+
goal = await this.getComputeGoal(goalId, { signal: scope.signal });
|
|
1679
|
+
}
|
|
1680
|
+
catch (error) {
|
|
1681
|
+
scope.signal.throwIfAborted();
|
|
1682
|
+
if (!isTransientComputeReadError(error))
|
|
1683
|
+
throw error;
|
|
1684
|
+
await this.sleep(HTTP_POLL_BACKOFF_MS[Math.min(polls++, HTTP_POLL_BACKOFF_MS.length - 1)], scope.signal);
|
|
1685
|
+
continue;
|
|
1686
|
+
}
|
|
1687
|
+
// A late response from a transport ignoring abort must not emit progress.
|
|
1688
|
+
scope.signal.throwIfAborted();
|
|
1689
|
+
if (goal.status !== lastStatus) {
|
|
1690
|
+
lastStatus = goal.status;
|
|
1691
|
+
try {
|
|
1692
|
+
Promise.resolve(options.onStatusUpdate?.(goal.status, goalId)).catch(() => { });
|
|
1693
|
+
}
|
|
1694
|
+
catch { /* Observers cannot change the goal outcome. */ }
|
|
1695
|
+
}
|
|
1696
|
+
if (goal.status === 'completed')
|
|
1697
|
+
return goal;
|
|
1698
|
+
if (goal.status === 'failed' || goal.status === 'cancelled') {
|
|
1699
|
+
const reason = goal.end_reason ?? goal.status;
|
|
1700
|
+
throw new errors_2.JobError(`Compute goal ${goal.status}: ${reason}`, goalId, reason, reason, { ...goal });
|
|
1701
|
+
}
|
|
1702
|
+
await this.sleep(HTTP_POLL_BACKOFF_MS[Math.min(polls++, HTTP_POLL_BACKOFF_MS.length - 1)], scope.signal);
|
|
1703
|
+
}
|
|
1704
|
+
};
|
|
1705
|
+
try {
|
|
1706
|
+
return await (0, deadline_1.abortable)(poll(), scope.signal);
|
|
1707
|
+
}
|
|
1708
|
+
catch (error) {
|
|
1709
|
+
if (scope.timedOut())
|
|
1710
|
+
throw new errors_2.JobTimeoutError(goalId, Date.now() - started);
|
|
1711
|
+
throw error;
|
|
1712
|
+
}
|
|
1713
|
+
finally {
|
|
1714
|
+
scope.close();
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1598
1717
|
/**
|
|
1599
1718
|
* List tasks under a compute goal
|
|
1600
1719
|
*
|
|
@@ -1608,11 +1727,11 @@ class OneShot {
|
|
|
1608
1727
|
*/
|
|
1609
1728
|
async getComputeTasks(goalId) {
|
|
1610
1729
|
this.validate(goalId, 'goalId');
|
|
1611
|
-
const response = await
|
|
1730
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}/tasks`, {
|
|
1612
1731
|
headers: await this.signedReadHeaders()
|
|
1613
1732
|
});
|
|
1614
1733
|
if (!response.ok) {
|
|
1615
|
-
throw new errors_2.ToolError('Failed to get compute tasks', response.status, await response.text());
|
|
1734
|
+
throw new errors_2.ToolError('Failed to get compute tasks', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1616
1735
|
}
|
|
1617
1736
|
const json = await response.json();
|
|
1618
1737
|
return json.data;
|
|
@@ -1631,11 +1750,11 @@ class OneShot {
|
|
|
1631
1750
|
*/
|
|
1632
1751
|
async getComputeOutputs(goalId) {
|
|
1633
1752
|
this.validate(goalId, 'goalId');
|
|
1634
|
-
const response = await
|
|
1753
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}/outputs`, {
|
|
1635
1754
|
headers: await this.signedReadHeaders()
|
|
1636
1755
|
});
|
|
1637
1756
|
if (!response.ok) {
|
|
1638
|
-
throw new errors_2.ToolError('Failed to list compute outputs', response.status, await response.text());
|
|
1757
|
+
throw new errors_2.ToolError('Failed to list compute outputs', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1639
1758
|
}
|
|
1640
1759
|
const json = await response.json();
|
|
1641
1760
|
return json.data;
|
|
@@ -1646,11 +1765,11 @@ class OneShot {
|
|
|
1646
1765
|
this.validate(key, 'key');
|
|
1647
1766
|
// The key is a path: keep its slashes, escape everything else.
|
|
1648
1767
|
const path = key.split('/').map(encodeURIComponent).join('/');
|
|
1649
|
-
const response = await
|
|
1768
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}/outputs/${path}`, {
|
|
1650
1769
|
headers: await this.signedReadHeaders()
|
|
1651
1770
|
});
|
|
1652
1771
|
if (!response.ok) {
|
|
1653
|
-
throw new errors_2.ToolError('Failed to read compute output', response.status, await response.text());
|
|
1772
|
+
throw new errors_2.ToolError('Failed to read compute output', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1654
1773
|
}
|
|
1655
1774
|
const json = await response.json();
|
|
1656
1775
|
return json.data;
|
|
@@ -1666,14 +1785,14 @@ class OneShot {
|
|
|
1666
1785
|
*/
|
|
1667
1786
|
async getComputeBudget(goalId) {
|
|
1668
1787
|
this.validate(goalId, 'goalId');
|
|
1669
|
-
const response = await
|
|
1788
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}/budget`, {
|
|
1670
1789
|
headers: await this.signedReadHeaders()
|
|
1671
1790
|
});
|
|
1672
1791
|
if (response.status === 404) {
|
|
1673
1792
|
throw new errors_2.ToolError('Budget not found', 404, 'Budget not found for this goal');
|
|
1674
1793
|
}
|
|
1675
1794
|
if (!response.ok) {
|
|
1676
|
-
throw new errors_2.ToolError('Failed to get compute budget', response.status, await response.text());
|
|
1795
|
+
throw new errors_2.ToolError('Failed to get compute budget', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1677
1796
|
}
|
|
1678
1797
|
const json = await response.json();
|
|
1679
1798
|
return json.data;
|
|
@@ -1689,7 +1808,7 @@ class OneShot {
|
|
|
1689
1808
|
*/
|
|
1690
1809
|
async cancelComputeGoal(goalId, reason) {
|
|
1691
1810
|
this.validate(goalId, 'goalId');
|
|
1692
|
-
const response = await
|
|
1811
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}/cancel`, {
|
|
1693
1812
|
method: 'POST',
|
|
1694
1813
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders('write')) },
|
|
1695
1814
|
body: JSON.stringify({ reason })
|
|
@@ -1703,7 +1822,7 @@ class OneShot {
|
|
|
1703
1822
|
return { goal_id: goalId, status: pending.status ?? 'pending_settlement', message: pending.message };
|
|
1704
1823
|
}
|
|
1705
1824
|
if (!response.ok) {
|
|
1706
|
-
throw new errors_2.ToolError('Failed to cancel compute goal', response.status, await response.text());
|
|
1825
|
+
throw new errors_2.ToolError('Failed to cancel compute goal', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1707
1826
|
}
|
|
1708
1827
|
const json = await response.json();
|
|
1709
1828
|
return json.data;
|
|
@@ -1723,16 +1842,16 @@ class OneShot {
|
|
|
1723
1842
|
async respondToComputeTask(goalId, input) {
|
|
1724
1843
|
this.validate(goalId, 'goalId');
|
|
1725
1844
|
this.validate(input.task_id, 'task_id');
|
|
1726
|
-
const response = await
|
|
1845
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}/respond`, {
|
|
1727
1846
|
method: 'POST',
|
|
1728
1847
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders('write')) },
|
|
1729
1848
|
body: JSON.stringify(input)
|
|
1730
1849
|
});
|
|
1731
1850
|
if (response.status === 404) {
|
|
1732
|
-
throw new errors_2.ToolError('Goal or task not found', 404, await response.text());
|
|
1851
|
+
throw new errors_2.ToolError('Goal or task not found', 404, await response.text(), response.headers.get('retry-after'));
|
|
1733
1852
|
}
|
|
1734
1853
|
if (!response.ok) {
|
|
1735
|
-
throw new errors_2.ToolError('Failed to respond to compute task', response.status, await response.text());
|
|
1854
|
+
throw new errors_2.ToolError('Failed to respond to compute task', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1736
1855
|
}
|
|
1737
1856
|
const json = await response.json();
|
|
1738
1857
|
return json.data;
|
|
@@ -1752,15 +1871,15 @@ class OneShot {
|
|
|
1752
1871
|
*/
|
|
1753
1872
|
async confirmComputeGoal(goalId, input) {
|
|
1754
1873
|
this.validate(goalId, 'goalId');
|
|
1755
|
-
const response = await
|
|
1874
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}/respond`, {
|
|
1756
1875
|
method: 'POST',
|
|
1757
1876
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders('write')) },
|
|
1758
1877
|
body: JSON.stringify({ confirm_done: input.approved, ...(input.response ? { response: input.response } : {}) }),
|
|
1759
1878
|
});
|
|
1760
1879
|
if (response.status === 404)
|
|
1761
|
-
throw new errors_2.ToolError('Goal not found', 404, await response.text());
|
|
1880
|
+
throw new errors_2.ToolError('Goal not found', 404, await response.text(), response.headers.get('retry-after'));
|
|
1762
1881
|
if (!response.ok)
|
|
1763
|
-
throw new errors_2.ToolError('Failed to confirm compute goal', response.status, await response.text());
|
|
1882
|
+
throw new errors_2.ToolError('Failed to confirm compute goal', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1764
1883
|
const json = await response.json();
|
|
1765
1884
|
return json.data;
|
|
1766
1885
|
}
|
|
@@ -1774,13 +1893,13 @@ class OneShot {
|
|
|
1774
1893
|
*/
|
|
1775
1894
|
async pauseComputeGoal(goalId, reason) {
|
|
1776
1895
|
this.validate(goalId, 'goalId');
|
|
1777
|
-
const response = await
|
|
1896
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}/pause`, {
|
|
1778
1897
|
method: 'POST',
|
|
1779
1898
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders('write')) },
|
|
1780
1899
|
body: JSON.stringify({ reason })
|
|
1781
1900
|
});
|
|
1782
1901
|
if (!response.ok) {
|
|
1783
|
-
throw new errors_2.ToolError('Failed to pause compute goal', response.status, await response.text());
|
|
1902
|
+
throw new errors_2.ToolError('Failed to pause compute goal', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1784
1903
|
}
|
|
1785
1904
|
const json = await response.json();
|
|
1786
1905
|
return json.data;
|
|
@@ -1796,13 +1915,13 @@ class OneShot {
|
|
|
1796
1915
|
*/
|
|
1797
1916
|
async resumeComputeGoal(goalId) {
|
|
1798
1917
|
this.validate(goalId, 'goalId');
|
|
1799
|
-
const response = await
|
|
1918
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}/resume`, {
|
|
1800
1919
|
method: 'POST',
|
|
1801
1920
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders('write')) },
|
|
1802
1921
|
body: JSON.stringify({})
|
|
1803
1922
|
});
|
|
1804
1923
|
if (!response.ok) {
|
|
1805
|
-
throw new errors_2.ToolError('Failed to resume compute goal', response.status, await response.text());
|
|
1924
|
+
throw new errors_2.ToolError('Failed to resume compute goal', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1806
1925
|
}
|
|
1807
1926
|
const json = await response.json();
|
|
1808
1927
|
return json.data;
|
|
@@ -1932,11 +2051,11 @@ class OneShot {
|
|
|
1932
2051
|
*/
|
|
1933
2052
|
async spendBreakdown(options) {
|
|
1934
2053
|
const qs = this.buildQuery({ period: options?.period || undefined });
|
|
1935
|
-
const response = await
|
|
2054
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/analytics/spend/breakdown${qs ? `?${qs}` : ''}`, {
|
|
1936
2055
|
headers: await this.signedReadHeaders()
|
|
1937
2056
|
});
|
|
1938
2057
|
if (!response.ok) {
|
|
1939
|
-
throw new errors_2.ToolError('Failed to get spend breakdown', response.status, await response.text());
|
|
2058
|
+
throw new errors_2.ToolError('Failed to get spend breakdown', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1940
2059
|
}
|
|
1941
2060
|
return response.json();
|
|
1942
2061
|
}
|
|
@@ -1951,11 +2070,11 @@ class OneShot {
|
|
|
1951
2070
|
*/
|
|
1952
2071
|
async rocs(options) {
|
|
1953
2072
|
const qs = this.buildQuery({ period: options?.period || undefined });
|
|
1954
|
-
const response = await
|
|
2073
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/analytics/rocs${qs ? `?${qs}` : ''}`, {
|
|
1955
2074
|
headers: await this.signedReadHeaders()
|
|
1956
2075
|
});
|
|
1957
2076
|
if (!response.ok) {
|
|
1958
|
-
throw new errors_2.ToolError('Failed to get RoCS', response.status, await response.text());
|
|
2077
|
+
throw new errors_2.ToolError('Failed to get RoCS', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1959
2078
|
}
|
|
1960
2079
|
return response.json();
|
|
1961
2080
|
}
|
|
@@ -1987,11 +2106,11 @@ class OneShot {
|
|
|
1987
2106
|
since: toIso(options?.since),
|
|
1988
2107
|
until: toIso(options?.until),
|
|
1989
2108
|
});
|
|
1990
|
-
const response = await
|
|
2109
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/analytics/receipts${qs ? `?${qs}` : ''}`, {
|
|
1991
2110
|
headers: await this.signedReadHeaders()
|
|
1992
2111
|
});
|
|
1993
2112
|
if (!response.ok) {
|
|
1994
|
-
throw new errors_2.ToolError('Failed to list receipts', response.status, await response.text());
|
|
2113
|
+
throw new errors_2.ToolError('Failed to list receipts', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1995
2114
|
}
|
|
1996
2115
|
return response.json();
|
|
1997
2116
|
}
|
|
@@ -2024,19 +2143,19 @@ class OneShot {
|
|
|
2024
2143
|
this.validate(valueTag.type, 'valueTag.type');
|
|
2025
2144
|
// Cadence-level: route to the outcome ledger by correlation key.
|
|
2026
2145
|
if (typeof ref === 'object' && ref.goalId && !ref.receiptId && !ref.requestId) {
|
|
2027
|
-
const response = await
|
|
2146
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/analytics/outcomes`, {
|
|
2028
2147
|
method: 'POST',
|
|
2029
2148
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders('write')) },
|
|
2030
2149
|
body: JSON.stringify({ goal_id: ref.goalId, ...valueTag }),
|
|
2031
2150
|
});
|
|
2032
2151
|
if (!response.ok) {
|
|
2033
|
-
throw new errors_2.ToolError('Failed to record outcome value', response.status, await response.text());
|
|
2152
|
+
throw new errors_2.ToolError('Failed to record outcome value', response.status, await response.text(), response.headers.get('retry-after'));
|
|
2034
2153
|
}
|
|
2035
2154
|
return;
|
|
2036
2155
|
}
|
|
2037
2156
|
const id = typeof ref === 'string' ? ref : (ref.receiptId ?? ref.requestId);
|
|
2038
2157
|
this.validate(id, 'receiptId/requestId/goalId');
|
|
2039
|
-
const response = await
|
|
2158
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/analytics/receipts/${id}/value`, {
|
|
2040
2159
|
method: 'PATCH',
|
|
2041
2160
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders('write')) },
|
|
2042
2161
|
body: JSON.stringify(valueTag)
|
|
@@ -2045,7 +2164,7 @@ class OneShot {
|
|
|
2045
2164
|
throw new errors_2.ToolError('Receipt not found', 404, 'Receipt not found or not owned by this agent');
|
|
2046
2165
|
}
|
|
2047
2166
|
if (!response.ok) {
|
|
2048
|
-
throw new errors_2.ToolError('Failed to tag receipt value', response.status, await response.text());
|
|
2167
|
+
throw new errors_2.ToolError('Failed to tag receipt value', response.status, await response.text(), response.headers.get('retry-after'));
|
|
2049
2168
|
}
|
|
2050
2169
|
}
|
|
2051
2170
|
/**
|
|
@@ -2067,11 +2186,11 @@ class OneShot {
|
|
|
2067
2186
|
*/
|
|
2068
2187
|
async rocsByGoal(options) {
|
|
2069
2188
|
const qs = this.buildQuery({ period: options?.period || undefined, goal_id: options?.goalId || undefined });
|
|
2070
|
-
const response = await
|
|
2189
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/analytics/rocs/by-goal${qs ? `?${qs}` : ''}`, {
|
|
2071
2190
|
headers: await this.signedReadHeaders()
|
|
2072
2191
|
});
|
|
2073
2192
|
if (!response.ok) {
|
|
2074
|
-
throw new errors_2.ToolError('Failed to get RoCS by goal', response.status, await response.text());
|
|
2193
|
+
throw new errors_2.ToolError('Failed to get RoCS by goal', response.status, await response.text(), response.headers.get('retry-after'));
|
|
2075
2194
|
}
|
|
2076
2195
|
return response.json();
|
|
2077
2196
|
}
|
|
@@ -2112,7 +2231,7 @@ class OneShot {
|
|
|
2112
2231
|
const headers = await this.signedReadHeaders(`staging:${method}:${path}:${digest}`);
|
|
2113
2232
|
if (!headers['x-agent-proof'])
|
|
2114
2233
|
throw new Error('Could not sign staging application');
|
|
2115
|
-
const response = await
|
|
2234
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/staging${path}`, {
|
|
2116
2235
|
method, headers: { ...headers, 'Content-Type': 'application/json' },
|
|
2117
2236
|
signal: AbortSignal.timeout(10000),
|
|
2118
2237
|
...(body ? { body: JSON.stringify(body) } : {}),
|
|
@@ -2147,7 +2266,7 @@ class OneShot {
|
|
|
2147
2266
|
return base;
|
|
2148
2267
|
try {
|
|
2149
2268
|
const agent = this.provider.address;
|
|
2150
|
-
const issuedAt = Math.floor(Date.now() / 1000);
|
|
2269
|
+
const issuedAt = Math.floor(Date.now() / 1000) + (domainName === 'OneShot Agent Auth' ? this.proofClockOffsetSec : 0);
|
|
2151
2270
|
const nonce = ethers_1.ethers.hexlify(ethers_1.ethers.randomBytes(32));
|
|
2152
2271
|
const signature = await this.provider.signTypedData(
|
|
2153
2272
|
// `domainName` lets a different origin (e.g. Soul Hunt, which keeps
|
|
@@ -2182,6 +2301,45 @@ class OneShot {
|
|
|
2182
2301
|
return base;
|
|
2183
2302
|
}
|
|
2184
2303
|
}
|
|
2304
|
+
/** Retry a clock-window refusal once, before the API has dispatched the request.
|
|
2305
|
+
* Only this API's proof clock changes; payments and other services keep their own clocks.
|
|
2306
|
+
* Callers supply replayable JSON bodies and retain their original abort/deadline signal.
|
|
2307
|
+
*/
|
|
2308
|
+
async fetchApi(url, init) {
|
|
2309
|
+
const response = await fetch(url, init);
|
|
2310
|
+
const headers = new Headers(init.headers);
|
|
2311
|
+
const proof = headers.get('x-agent-proof');
|
|
2312
|
+
if (response.status !== 401 || !proof || this._accessToken)
|
|
2313
|
+
return response;
|
|
2314
|
+
const origin = new URL(this.baseUrl).origin;
|
|
2315
|
+
if (new URL(url).origin !== origin || (response.url && new URL(response.url).origin !== origin))
|
|
2316
|
+
return response;
|
|
2317
|
+
let scope;
|
|
2318
|
+
let serverTime;
|
|
2319
|
+
try {
|
|
2320
|
+
const body = await response.clone().json();
|
|
2321
|
+
if (body?.error !== 'proof_required' ||
|
|
2322
|
+
!['proof expired', 'proof issued in the future'].some(reason => body.message === `This endpoint requires a signed agent proof (x-agent-proof): ${reason}`) ||
|
|
2323
|
+
typeof body.server_time !== 'number' || !Number.isSafeInteger(body.server_time) || body.server_time <= 0)
|
|
2324
|
+
return response;
|
|
2325
|
+
const sent = JSON.parse(typeof Buffer !== 'undefined' ? Buffer.from(proof, 'base64').toString('utf-8') : atob(proof));
|
|
2326
|
+
if (typeof sent.scope !== 'string')
|
|
2327
|
+
return response;
|
|
2328
|
+
scope = sent.scope;
|
|
2329
|
+
serverTime = body.server_time;
|
|
2330
|
+
}
|
|
2331
|
+
catch {
|
|
2332
|
+
return response;
|
|
2333
|
+
}
|
|
2334
|
+
init.signal?.throwIfAborted();
|
|
2335
|
+
this.proofClockOffsetSec = serverTime - Math.floor(Date.now() / 1000);
|
|
2336
|
+
const refreshed = await this.signedReadHeaders(scope);
|
|
2337
|
+
init.signal?.throwIfAborted();
|
|
2338
|
+
if (!refreshed['x-agent-proof'])
|
|
2339
|
+
return response;
|
|
2340
|
+
headers.set('x-agent-proof', refreshed['x-agent-proof']);
|
|
2341
|
+
return fetch(url, { ...init, headers });
|
|
2342
|
+
}
|
|
2185
2343
|
/** Build a query string, skipping null/undefined values. Pass falsy-but-valid
|
|
2186
2344
|
* values (0, '') as undefined at the call site to match prior `if (x)` guards. */
|
|
2187
2345
|
buildQuery(params) {
|
|
@@ -2225,7 +2383,7 @@ class OneShot {
|
|
|
2225
2383
|
body.alert_email = this._alertEmail;
|
|
2226
2384
|
let response;
|
|
2227
2385
|
try {
|
|
2228
|
-
response = await
|
|
2386
|
+
response = await this.fetchApi(`${this.baseUrl}/v1/agents/me/budgets`, {
|
|
2229
2387
|
method: 'PUT',
|
|
2230
2388
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders('write')) },
|
|
2231
2389
|
body: JSON.stringify(body),
|
|
@@ -2236,7 +2394,7 @@ class OneShot {
|
|
|
2236
2394
|
}
|
|
2237
2395
|
if (!response.ok) {
|
|
2238
2396
|
const text = await response.text();
|
|
2239
|
-
throw new errors_2.BudgetSyncError(`Could not sync spend budget (${response.status}): ${text}`, response.status, text);
|
|
2397
|
+
throw new errors_2.BudgetSyncError(`Could not sync spend budget (${response.status}): ${text}`, response.status, text, response.headers.get('retry-after'));
|
|
2240
2398
|
}
|
|
2241
2399
|
this.log('Budget synced');
|
|
2242
2400
|
})();
|
|
@@ -2278,7 +2436,7 @@ class OneShot {
|
|
|
2278
2436
|
*/
|
|
2279
2437
|
/** Signed-proof JSON request to a free agent route; returns `data`. Errors map through failFromResponse. */
|
|
2280
2438
|
async agentJson(path, method, body, scope = 'read') {
|
|
2281
|
-
const response = await
|
|
2439
|
+
const response = await this.fetchApi(`${this.baseUrl}${path}`, {
|
|
2282
2440
|
method,
|
|
2283
2441
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders(scope)) },
|
|
2284
2442
|
...(body === undefined || method === 'GET' ? {} : { body: JSON.stringify(body) }),
|
|
@@ -2290,11 +2448,11 @@ class OneShot {
|
|
|
2290
2448
|
return (json && typeof json === 'object' && 'data' in json ? json.data : json);
|
|
2291
2449
|
}
|
|
2292
2450
|
async budgets() {
|
|
2293
|
-
const response = await
|
|
2451
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/agents/me/budgets`, {
|
|
2294
2452
|
headers: await this.signedReadHeaders(),
|
|
2295
2453
|
});
|
|
2296
2454
|
if (!response.ok) {
|
|
2297
|
-
throw new errors_2.ToolError('Failed to fetch budgets', response.status, await response.text());
|
|
2455
|
+
throw new errors_2.ToolError('Failed to fetch budgets', response.status, await response.text(), response.headers.get('retry-after'));
|
|
2298
2456
|
}
|
|
2299
2457
|
const body = await response.json();
|
|
2300
2458
|
return (body.data ?? body);
|
|
@@ -2317,24 +2475,24 @@ class OneShot {
|
|
|
2317
2475
|
*/
|
|
2318
2476
|
async createAccessToken(opts = {}) {
|
|
2319
2477
|
this.assertWalletSession('mint access tokens');
|
|
2320
|
-
const response = await
|
|
2478
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/agents/me/access-tokens`, {
|
|
2321
2479
|
method: 'POST',
|
|
2322
2480
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders('write')) },
|
|
2323
2481
|
body: JSON.stringify(opts.name !== undefined ? { name: opts.name } : {}),
|
|
2324
2482
|
});
|
|
2325
2483
|
if (!response.ok)
|
|
2326
|
-
throw new errors_2.ToolError('Failed to create access token', response.status, await response.text());
|
|
2484
|
+
throw new errors_2.ToolError('Failed to create access token', response.status, await response.text(), response.headers.get('retry-after'));
|
|
2327
2485
|
const body = await response.json();
|
|
2328
2486
|
return body.data;
|
|
2329
2487
|
}
|
|
2330
2488
|
/** List this agent's access tokens (prefix, label, last use, spend today — never the token). */
|
|
2331
2489
|
async listAccessTokens(opts = {}) {
|
|
2332
2490
|
this.assertWalletSession('list access tokens');
|
|
2333
|
-
const response = await
|
|
2491
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/agents/me/access-tokens${opts.activeOnly ? '?active=true' : ''}`, {
|
|
2334
2492
|
headers: await this.signedReadHeaders(),
|
|
2335
2493
|
});
|
|
2336
2494
|
if (!response.ok)
|
|
2337
|
-
throw new errors_2.ToolError('Failed to list access tokens', response.status, await response.text());
|
|
2495
|
+
throw new errors_2.ToolError('Failed to list access tokens', response.status, await response.text(), response.headers.get('retry-after'));
|
|
2338
2496
|
const body = await response.json();
|
|
2339
2497
|
return body.data.tokens;
|
|
2340
2498
|
}
|
|
@@ -2342,12 +2500,12 @@ class OneShot {
|
|
|
2342
2500
|
async revokeAccessToken(id) {
|
|
2343
2501
|
this.assertWalletSession('revoke access tokens');
|
|
2344
2502
|
this.validate(id, 'id');
|
|
2345
|
-
const response = await
|
|
2503
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/agents/me/access-tokens/${encodeURIComponent(id)}`, {
|
|
2346
2504
|
method: 'DELETE',
|
|
2347
2505
|
headers: await this.signedReadHeaders('write'),
|
|
2348
2506
|
});
|
|
2349
2507
|
if (!response.ok)
|
|
2350
|
-
throw new errors_2.ToolError('Failed to revoke access token', response.status, await response.text());
|
|
2508
|
+
throw new errors_2.ToolError('Failed to revoke access token', response.status, await response.text(), response.headers.get('retry-after'));
|
|
2351
2509
|
const body = await response.json();
|
|
2352
2510
|
return body.data;
|
|
2353
2511
|
}
|
|
@@ -2406,7 +2564,7 @@ class OneShot {
|
|
|
2406
2564
|
const budget = response.status === 403 ? this.parseBudgetRejection(text) : undefined;
|
|
2407
2565
|
if (budget)
|
|
2408
2566
|
throw budget;
|
|
2409
|
-
throw new errors_2.ToolError(message, response.status, text);
|
|
2567
|
+
throw new errors_2.ToolError(message, response.status, text, response.headers.get('retry-after'));
|
|
2410
2568
|
}
|
|
2411
2569
|
/**
|
|
2412
2570
|
* Map a 402 `insufficient_credits` body (access-token sessions) onto a typed
|
|
@@ -2470,12 +2628,12 @@ class OneShot {
|
|
|
2470
2628
|
throw new errors_2.ToolError(`Could not resolve the access token's agent (network): ${err}`, 0, String(err));
|
|
2471
2629
|
}
|
|
2472
2630
|
if (!response.ok) {
|
|
2473
|
-
throw new errors_2.ToolError('Could not resolve the access token\'s agent', response.status, await response.text());
|
|
2631
|
+
throw new errors_2.ToolError('Could not resolve the access token\'s agent', response.status, await response.text(), response.headers.get('retry-after'));
|
|
2474
2632
|
}
|
|
2475
2633
|
const json = await response.json();
|
|
2476
2634
|
const address = json.data?.wallet_address;
|
|
2477
2635
|
if (!address)
|
|
2478
|
-
throw new errors_2.ToolError('GET /v1/agents/me returned no wallet_address', response.status, JSON.stringify(json));
|
|
2636
|
+
throw new errors_2.ToolError('GET /v1/agents/me returned no wallet_address', response.status, JSON.stringify(json), response.headers.get('retry-after'));
|
|
2479
2637
|
return address;
|
|
2480
2638
|
}
|
|
2481
2639
|
/**
|
|
@@ -2578,7 +2736,7 @@ class OneShot {
|
|
|
2578
2736
|
const headers = signed ? await this.signedReadHeaders() : undefined;
|
|
2579
2737
|
if (scope.signal.aborted)
|
|
2580
2738
|
throw new errors_2.OneShotError('Read deadline exceeded');
|
|
2581
|
-
const response = await
|
|
2739
|
+
const response = await this.fetchApi(`${this.baseUrl}${path}`, { headers, signal: scope.signal });
|
|
2582
2740
|
if (!response.ok && !(allowDegraded && response.status === 503))
|
|
2583
2741
|
await this.failFromResponse('Reliability read failed', response);
|
|
2584
2742
|
return await response.json();
|
|
@@ -2597,7 +2755,7 @@ class OneShot {
|
|
|
2597
2755
|
}
|
|
2598
2756
|
async executeToolRequest(endpoint, options, quoteId) {
|
|
2599
2757
|
// LinkedIn writes act through a human's account and cannot be recalled: always keyed.
|
|
2600
|
-
const reliable = /(?:^|\/)(lookup\/person-(profile|email|email-status)|lookup\/place-permits|linkedin\/(reply|message|profile-view|react|invite(?:\/withdraw)?))$/.test(endpoint);
|
|
2758
|
+
const reliable = /(?:^|\/)(lookup\/person-(profile|email|email-status)|lookup\/place-permits|solve|predict|linkedin\/(reply|message|profile-view|react|invite(?:\/withdraw)?))$/.test(endpoint);
|
|
2601
2759
|
const key = options.idempotencyKey ?? (reliable ? ethers_1.ethers.hexlify(ethers_1.ethers.randomBytes(16)) : undefined);
|
|
2602
2760
|
if (options.totalTimeoutMs !== undefined && (!Number.isFinite(options.totalTimeoutMs) || options.totalTimeoutMs <= 0)) {
|
|
2603
2761
|
throw new errors_2.ValidationError('totalTimeoutMs must be positive', 'totalTimeoutMs');
|
|
@@ -2640,7 +2798,7 @@ class OneShot {
|
|
|
2640
2798
|
this.log('Memo truncated to 1000 chars');
|
|
2641
2799
|
}
|
|
2642
2800
|
}
|
|
2643
|
-
else if (!endpoint.includes('/inbox') && !endpoint.includes('/notifications') && !endpoint.includes('/balance')) {
|
|
2801
|
+
else if (!endpoint.endsWith('/v1/tools/solve') && !endpoint.endsWith('/v1/tools/predict') && !endpoint.includes('/inbox') && !endpoint.includes('/notifications') && !endpoint.includes('/balance')) {
|
|
2644
2802
|
this.log('No memo provided — consider adding a reason for audit trail');
|
|
2645
2803
|
}
|
|
2646
2804
|
if (payload.decisionContext !== undefined) {
|
|
@@ -2669,10 +2827,10 @@ class OneShot {
|
|
|
2669
2827
|
context.phase = 'submission';
|
|
2670
2828
|
let response = await this.makeRequest(endpoint, payload, undefined, quoteId, signal, undefined, extraHeaders);
|
|
2671
2829
|
// Handle 402 Payment Required
|
|
2672
|
-
if (response.status === 402 && /\/v1\/tools\/(find\/person|lookup\/person-(profile|dossier))$/.test(endpoint)) {
|
|
2830
|
+
if (response.status === 402 && /\/v1\/tools\/(solve|predict|find\/person|lookup\/person-(profile|dossier))$/.test(endpoint)) {
|
|
2673
2831
|
({ execResp: response } = await this.runQuoteToPay({
|
|
2674
2832
|
endpoint, payload, signal, maxCost, quoteResponse: response, extraHeaders,
|
|
2675
|
-
expectMsg: 'Expected a
|
|
2833
|
+
expectMsg: 'Expected a tool quote', totalOf: quote => quote.total,
|
|
2676
2834
|
onQuote: () => { context.phase = 'payment'; },
|
|
2677
2835
|
}));
|
|
2678
2836
|
context.phase = 'submission';
|
|
@@ -2790,6 +2948,15 @@ class OneShot {
|
|
|
2790
2948
|
* `wait: false`, or to resume waiting after a client restart. Delivery is the
|
|
2791
2949
|
* same as for blocking calls: WebSocket push when the server offers it, HTTP
|
|
2792
2950
|
* polling of `GET /v1/requests/:id` as the source of truth.
|
|
2951
|
+
* For compute goals, use `waitForComputeGoal(goal_id)` instead: their creation
|
|
2952
|
+
* request_id does not track progress. `onStatusUpdate` observes distinct job
|
|
2953
|
+
* statuses, including the terminal status before settlement.
|
|
2954
|
+
* With waitForPhones, completed is deferred until the phone wait returns.
|
|
2955
|
+
* Callback errors are ignored; returned promises are not awaited.
|
|
2956
|
+
*
|
|
2957
|
+
* `signal` ends the local wait only (in-flight fetch aborted, poll timer
|
|
2958
|
+
* cleared, rejects with `OneShotError('Operation cancelled')`); the job
|
|
2959
|
+
* itself keeps running and can be waited for again.
|
|
2793
2960
|
*/
|
|
2794
2961
|
async waitForResult(requestId, options = {}) {
|
|
2795
2962
|
this.validate(requestId, 'requestId');
|
|
@@ -2802,6 +2969,9 @@ class OneShot {
|
|
|
2802
2969
|
// swallows frames) costs nothing beyond the poll cadence. Previously the
|
|
2803
2970
|
// client waited 60% of its timeout on the socket before polling at all —
|
|
2804
2971
|
// in prod that was ~72s of dead air on every call while the push never came.
|
|
2972
|
+
// An already-aborted signal ends the wait before any proof is signed or sent.
|
|
2973
|
+
if (signal?.aborted)
|
|
2974
|
+
throw new errors_2.OneShotError('Operation cancelled');
|
|
2805
2975
|
const inner = new AbortController();
|
|
2806
2976
|
const onOuterAbort = () => inner.abort();
|
|
2807
2977
|
if (signal?.aborted)
|
|
@@ -2809,12 +2979,24 @@ class OneShot {
|
|
|
2809
2979
|
else
|
|
2810
2980
|
signal?.addEventListener('abort', onOuterAbort, { once: true });
|
|
2811
2981
|
const wait = { pushConfirmed: false, lastStatus: undefined, via: undefined };
|
|
2812
|
-
|
|
2982
|
+
const notify = (status) => {
|
|
2983
|
+
// Progress observers must not alter job outcomes or trigger HTTP retries.
|
|
2984
|
+
try {
|
|
2985
|
+
Promise.resolve(onStatusUpdate?.(status, requestId)).catch(() => { });
|
|
2986
|
+
}
|
|
2987
|
+
catch { /* Ignore synchronous observer errors too. */ }
|
|
2988
|
+
};
|
|
2989
|
+
// Latch terminal transport status immediately, but defer completion for
|
|
2990
|
+
// callers waiting on phones so the losing branch cannot emit stale states.
|
|
2813
2991
|
const emit = (status) => {
|
|
2992
|
+
if (wait.lastStatus === 'completed' || wait.lastStatus === 'failed')
|
|
2993
|
+
return;
|
|
2814
2994
|
if (status === wait.lastStatus)
|
|
2815
2995
|
return;
|
|
2816
2996
|
wait.lastStatus = status;
|
|
2817
|
-
|
|
2997
|
+
if (status === 'completed' && phoneOpts?.waitForPhones)
|
|
2998
|
+
return;
|
|
2999
|
+
notify(status);
|
|
2818
3000
|
};
|
|
2819
3001
|
const startedAt = Date.now();
|
|
2820
3002
|
const wsBranch = this.waitViaWebSocket(requestId, inner.signal, emit, wait).catch((err) => {
|
|
@@ -2850,8 +3032,10 @@ class OneShot {
|
|
|
2850
3032
|
// hit a transient error before any successful refresh, we return what we
|
|
2851
3033
|
// already have (with phones_pending=true still set so consumers know).
|
|
2852
3034
|
if (phoneOpts?.waitForPhones && this._isPhonesPending(result)) {
|
|
2853
|
-
|
|
3035
|
+
result = await this._pollForPhones(requestId, phoneOpts.phoneTimeoutSec ?? 360, signal, result);
|
|
2854
3036
|
}
|
|
3037
|
+
if (phoneOpts?.waitForPhones)
|
|
3038
|
+
notify('completed');
|
|
2855
3039
|
return result;
|
|
2856
3040
|
}
|
|
2857
3041
|
/**
|
|
@@ -2895,7 +3079,7 @@ class OneShot {
|
|
|
2895
3079
|
if (signal?.aborted)
|
|
2896
3080
|
throw new errors_2.OneShotError('Operation cancelled');
|
|
2897
3081
|
try {
|
|
2898
|
-
const resp = await
|
|
3082
|
+
const resp = await this.fetchApi(`${this.baseUrl}/v1/requests/${requestId}`, {
|
|
2899
3083
|
headers: await this.signedReadHeaders(),
|
|
2900
3084
|
signal,
|
|
2901
3085
|
});
|
|
@@ -2904,7 +3088,7 @@ class OneShot {
|
|
|
2904
3088
|
// whatever we last had so consumers don't lose the sync result.
|
|
2905
3089
|
if (lastResult !== undefined)
|
|
2906
3090
|
return lastResult;
|
|
2907
|
-
throw new errors_2.ToolError('Failed to check job status', resp.status, await resp.text());
|
|
3091
|
+
throw new errors_2.ToolError('Failed to check job status', resp.status, await resp.text(), resp.headers.get('retry-after'));
|
|
2908
3092
|
}
|
|
2909
3093
|
const job = await resp.json();
|
|
2910
3094
|
lastResult = (job.result ?? job);
|
|
@@ -2913,6 +3097,8 @@ class OneShot {
|
|
|
2913
3097
|
}
|
|
2914
3098
|
}
|
|
2915
3099
|
catch (err) {
|
|
3100
|
+
if (signal?.aborted)
|
|
3101
|
+
throw new errors_2.OneShotError('Operation cancelled');
|
|
2916
3102
|
if (err instanceof errors_2.OneShotError)
|
|
2917
3103
|
throw err;
|
|
2918
3104
|
if (lastResult !== undefined)
|
|
@@ -3008,6 +3194,7 @@ class OneShot {
|
|
|
3008
3194
|
wait.pushConfirmed = true;
|
|
3009
3195
|
this.log('WebSocket push confirmed — relaxing HTTP polling');
|
|
3010
3196
|
}
|
|
3197
|
+
emit(String(msg.status));
|
|
3011
3198
|
if (msg.status === 'completed') {
|
|
3012
3199
|
this.log('Job completed (WebSocket)');
|
|
3013
3200
|
settle(() => {
|
|
@@ -3027,9 +3214,6 @@ class OneShot {
|
|
|
3027
3214
|
reject(new errors_2.JobError(`Job failed: ${msg.error ?? 'Unknown'}`, requestId, String(msg.error ?? 'Unknown'), msg.error_code, msg.result));
|
|
3028
3215
|
});
|
|
3029
3216
|
}
|
|
3030
|
-
else {
|
|
3031
|
-
emit(String(msg.status));
|
|
3032
|
-
}
|
|
3033
3217
|
}
|
|
3034
3218
|
catch {
|
|
3035
3219
|
// Ignore malformed messages
|
|
@@ -3065,6 +3249,9 @@ class OneShot {
|
|
|
3065
3249
|
catch (error) {
|
|
3066
3250
|
if (scope.timedOut())
|
|
3067
3251
|
throw new errors_2.JobTimeoutError(requestId, Date.now() - started);
|
|
3252
|
+
// An abort mid-fetch surfaces as the transport's AbortError; report it as the cancellation it is.
|
|
3253
|
+
if (signal?.aborted && !(error instanceof errors_2.OneShotError))
|
|
3254
|
+
throw new errors_2.OneShotError('Operation cancelled');
|
|
3068
3255
|
throw error;
|
|
3069
3256
|
}
|
|
3070
3257
|
finally {
|
|
@@ -3081,7 +3268,7 @@ class OneShot {
|
|
|
3081
3268
|
if (signal?.aborted)
|
|
3082
3269
|
throw new errors_2.OneShotError('Operation cancelled');
|
|
3083
3270
|
try {
|
|
3084
|
-
const resp = await
|
|
3271
|
+
const resp = await this.fetchApi(`${this.baseUrl}/v1/requests/${requestId}`, {
|
|
3085
3272
|
headers: await this.signedReadHeaders(),
|
|
3086
3273
|
signal
|
|
3087
3274
|
});
|
|
@@ -3092,9 +3279,10 @@ class OneShot {
|
|
|
3092
3279
|
if (resp.status >= 500 || resp.status === 429) {
|
|
3093
3280
|
throw new Error(`Poll returned ${resp.status}: ${body.slice(0, 200)}`);
|
|
3094
3281
|
}
|
|
3095
|
-
throw new errors_2.ToolError('Failed to check job status', resp.status, body);
|
|
3282
|
+
throw new errors_2.ToolError('Failed to check job status', resp.status, body, resp.headers.get('retry-after'));
|
|
3096
3283
|
}
|
|
3097
3284
|
const job = await resp.json();
|
|
3285
|
+
emit?.(String(job.status));
|
|
3098
3286
|
if (job.status === 'completed') {
|
|
3099
3287
|
this.log('Job completed');
|
|
3100
3288
|
if (wait)
|
|
@@ -3117,7 +3305,6 @@ class OneShot {
|
|
|
3117
3305
|
wait.via = 'http';
|
|
3118
3306
|
throw new errors_2.JobError(`Job failed: ${job.error ?? 'Unknown'}`, requestId, String(job.error ?? 'Unknown'), job.error_code, job.result);
|
|
3119
3307
|
}
|
|
3120
|
-
emit?.(String(job.status));
|
|
3121
3308
|
retries = 0;
|
|
3122
3309
|
const interval = wait?.pushConfirmed
|
|
3123
3310
|
? HTTP_POLL_RELAXED_MS
|
|
@@ -3200,7 +3387,7 @@ class OneShot {
|
|
|
3200
3387
|
// alone (401 proof_required).
|
|
3201
3388
|
const unsignedStub = payment?.payload?.signature === '0x';
|
|
3202
3389
|
const askedForProof = !!extraHeaders?.['x-agent-proof'];
|
|
3203
|
-
const proofScope = /(lookup\/person-(profile|email|email-status)|lookup\/place-permits|physical-mail\/send)$/.test(endpoint)
|
|
3390
|
+
const proofScope = /(lookup\/person-(profile|email|email-status)|lookup\/place-permits|physical-mail\/send|solve|predict)$/.test(endpoint)
|
|
3204
3391
|
? 'submit'
|
|
3205
3392
|
: askedForProof ? 'read' : 'write';
|
|
3206
3393
|
const headers = {
|
|
@@ -3228,7 +3415,7 @@ class OneShot {
|
|
|
3228
3415
|
transportDeadline?.signal.addEventListener('abort', () => transportDeadline.close(), { once: true });
|
|
3229
3416
|
if (fetchSignal?.aborted)
|
|
3230
3417
|
throw new errors_2.OneShotError('Operation cancelled');
|
|
3231
|
-
const work =
|
|
3418
|
+
const work = this.fetchApi(`${this.baseUrl}${endpoint}`, {
|
|
3232
3419
|
method: 'POST', headers, body: JSON.stringify(data), signal: fetchSignal,
|
|
3233
3420
|
});
|
|
3234
3421
|
try {
|
|
@@ -3385,7 +3572,7 @@ class OneShot {
|
|
|
3385
3572
|
}
|
|
3386
3573
|
catch (err) {
|
|
3387
3574
|
// Keyed writes may already be queued server-side after a transport failure: keep the reservation.
|
|
3388
|
-
if (!/(lookup\/person-(profile|email|email-status)|lookup\/place-permits|physical-mail\/send|linkedin\/(reply|message|profile-view|react|invite(?:\/withdraw)?))$/.test(endpoint))
|
|
3575
|
+
if (!/(lookup\/person-(profile|email|email-status)|lookup\/place-permits|physical-mail\/send|solve|predict|linkedin\/(reply|message|profile-view|react|invite(?:\/withdraw)?))$/.test(endpoint))
|
|
3389
3576
|
this.releaseUsdcReservation(signed.reservation);
|
|
3390
3577
|
throw err;
|
|
3391
3578
|
}
|