@oneshot-agent/sdk 0.55.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 +115 -2
- 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 +49 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +287 -123
- 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/types.d.ts +45 -24
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -39,6 +39,7 @@ 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
41
|
__exportStar(require("./solve"), exports);
|
|
42
|
+
__exportStar(require("./predict"), exports);
|
|
42
43
|
__exportStar(require("./hunts"), exports);
|
|
43
44
|
const physical_mail_1 = require("./physical-mail");
|
|
44
45
|
__exportStar(require("./physical-mail"), exports);
|
|
@@ -66,11 +67,36 @@ const approvals_1 = require("./approvals");
|
|
|
66
67
|
// stay off this public surface (see tests/unit/sdk-provider-cost-removed.test.ts).
|
|
67
68
|
// Import from the `@oneshot-agent/sdk/receipt` subpath instead.
|
|
68
69
|
// Keep in sync with package.json `version`. Guarded by version.test.ts.
|
|
69
|
-
const SDK_VERSION = '0.
|
|
70
|
+
const SDK_VERSION = '0.56.0';
|
|
70
71
|
/** HTTP poll cadence while push is unconfirmed: fast first checks, settling at 2s. */
|
|
71
72
|
const HTTP_POLL_BACKOFF_MS = [300, 600, 1000, 2000];
|
|
72
73
|
/** HTTP poll cadence once the WebSocket has delivered for this request. */
|
|
73
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
|
+
}
|
|
74
100
|
const BASE_URL = 'https://win.oneshotagent.com';
|
|
75
101
|
const RPC_URL = 'https://mainnet.base.org';
|
|
76
102
|
const CHAIN_ID = 8453;
|
|
@@ -201,7 +227,7 @@ class OneShot {
|
|
|
201
227
|
constructor(config, walletProvider) {
|
|
202
228
|
this.hunts = new hunts_1.Hunts(this);
|
|
203
229
|
this.physicalMail = new physical_mail_1.PhysicalMail(async (path, method, body, mime) => {
|
|
204
|
-
const response = await
|
|
230
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/physical-mail${path}`, {
|
|
205
231
|
method, headers: { ...await this.signedReadHeaders(method === 'GET' ? 'read' : 'write'), 'Content-Type': mime ?? 'application/json' },
|
|
206
232
|
...(body === undefined ? {} : { body: mime ? body : JSON.stringify(body) }),
|
|
207
233
|
signal: AbortSignal.timeout(60000),
|
|
@@ -215,6 +241,7 @@ class OneShot {
|
|
|
215
241
|
/** Which paid calls run, are denied, or wait for approval. Wallet sessions write it. */
|
|
216
242
|
this.policy = new approvals_1.ActionPolicies((path, method, body, scope) => this.agentJson(path, method, body, scope));
|
|
217
243
|
this.readProofWarned = false;
|
|
244
|
+
this.proofClockOffsetSec = 0;
|
|
218
245
|
/** ETH mode: signed payments not yet observed as settled, keyed by reservation id. */
|
|
219
246
|
this._usdcPending = new Map();
|
|
220
247
|
this._usdcReservationSeq = 0;
|
|
@@ -434,35 +461,35 @@ class OneShot {
|
|
|
434
461
|
}
|
|
435
462
|
/** List the caller's domain pool with warmup and rotation metadata. */
|
|
436
463
|
async listDomains() {
|
|
437
|
-
const response = await
|
|
464
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/email/domains`, {
|
|
438
465
|
headers: await this.signedReadHeaders(),
|
|
439
466
|
});
|
|
440
467
|
if (!response.ok) {
|
|
441
|
-
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'));
|
|
442
469
|
}
|
|
443
470
|
return response.json();
|
|
444
471
|
}
|
|
445
472
|
/** Take a domain out of rotation without releasing it. */
|
|
446
473
|
async pauseDomain(domain) {
|
|
447
474
|
this.validate(domain, 'domain');
|
|
448
|
-
const response = await
|
|
475
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/email/domains/${encodeURIComponent(domain)}/pause`, {
|
|
449
476
|
method: 'POST',
|
|
450
477
|
headers: await this.signedReadHeaders('write'),
|
|
451
478
|
});
|
|
452
479
|
if (!response.ok) {
|
|
453
|
-
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'));
|
|
454
481
|
}
|
|
455
482
|
return response.json();
|
|
456
483
|
}
|
|
457
484
|
/** Put a paused domain back into rotation. */
|
|
458
485
|
async resumeDomain(domain) {
|
|
459
486
|
this.validate(domain, 'domain');
|
|
460
|
-
const response = await
|
|
487
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/email/domains/${encodeURIComponent(domain)}/resume`, {
|
|
461
488
|
method: 'POST',
|
|
462
489
|
headers: await this.signedReadHeaders('write'),
|
|
463
490
|
});
|
|
464
491
|
if (!response.ok) {
|
|
465
|
-
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'));
|
|
466
493
|
}
|
|
467
494
|
return response.json();
|
|
468
495
|
}
|
|
@@ -470,6 +497,10 @@ class OneShot {
|
|
|
470
497
|
this.validate(options.topic, 'topic');
|
|
471
498
|
return this.tool('research', { ...options });
|
|
472
499
|
}
|
|
500
|
+
async predict(input) {
|
|
501
|
+
const { memo, valueTag, decisionContext, ...options } = input;
|
|
502
|
+
return this.tool('predict', { ...options, timeout: options.timeout ?? 240 });
|
|
503
|
+
}
|
|
473
504
|
/**
|
|
474
505
|
* Solve a routing or packing problem with constraint checks and conflicts. Quotes, pays and waits for
|
|
475
506
|
* the job. The result's `solution` is typed by the request's `problem`.
|
|
@@ -484,10 +515,15 @@ class OneShot {
|
|
|
484
515
|
const { includePhone, ...payload } = options;
|
|
485
516
|
return this.tool('find/person', { ...payload, include_phone: includePhone, limit: options.limit ?? 100 });
|
|
486
517
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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');
|
|
490
523
|
}
|
|
524
|
+
}
|
|
525
|
+
async personProfile(options) {
|
|
526
|
+
this.requirePersonIdentity(options);
|
|
491
527
|
const { includePhone, ...payload } = options;
|
|
492
528
|
return this.tool('lookup/person-profile', { ...payload, include_phone: includePhone });
|
|
493
529
|
}
|
|
@@ -547,10 +583,7 @@ class OneShot {
|
|
|
547
583
|
});
|
|
548
584
|
}
|
|
549
585
|
async personEmail(options) {
|
|
550
|
-
this.
|
|
551
|
-
if (!options.full_name && !(options.first_name && options.last_name)) {
|
|
552
|
-
throw new errors_2.ValidationError('Either full_name or both first_name and last_name required', 'name');
|
|
553
|
-
}
|
|
586
|
+
this.requirePersonIdentity(options);
|
|
554
587
|
return this.tool('lookup/person-email', { ...options });
|
|
555
588
|
}
|
|
556
589
|
async personEmailStatus(options) {
|
|
@@ -558,17 +591,13 @@ class OneShot {
|
|
|
558
591
|
return this.tool('lookup/person-email-status', { ...options });
|
|
559
592
|
}
|
|
560
593
|
async personDossier(options) {
|
|
561
|
-
|
|
562
|
-
throw new errors_2.ValidationError('At least one of email, social_media_url, or name is required', 'identifier');
|
|
563
|
-
}
|
|
594
|
+
this.requirePersonIdentity(options);
|
|
564
595
|
const { includePhone, ...payload } = options;
|
|
565
596
|
// The dossier takes 2 to 5 minutes; the generic 120 second poll gives up on a normal run (#1913).
|
|
566
597
|
return this.tool('lookup/person-dossier', { ...payload, include_phone: includePhone, timeout: options.timeout ?? 300 });
|
|
567
598
|
}
|
|
568
599
|
async personSocialAccounts(options) {
|
|
569
|
-
|
|
570
|
-
throw new errors_2.ValidationError('At least one of email or social_media_url is required', 'identifier');
|
|
571
|
-
}
|
|
600
|
+
this.requirePersonIdentity(options);
|
|
572
601
|
return this.tool('lookup/person-social-accounts', { ...options });
|
|
573
602
|
}
|
|
574
603
|
async personArticles(options) {
|
|
@@ -577,17 +606,15 @@ class OneShot {
|
|
|
577
606
|
return this.tool('lookup/person-articles', { ...options });
|
|
578
607
|
}
|
|
579
608
|
async personPosts(options) {
|
|
580
|
-
this.
|
|
609
|
+
this.requirePersonIdentity(options);
|
|
581
610
|
return this.tool('lookup/person-posts', { ...options });
|
|
582
611
|
}
|
|
583
612
|
async personInterests(options) {
|
|
584
|
-
|
|
585
|
-
throw new errors_2.ValidationError('At least one of email, phone, or social_media_url is required', 'identifier');
|
|
586
|
-
}
|
|
613
|
+
this.requirePersonIdentity(options);
|
|
587
614
|
return this.tool('lookup/person-interests', { ...options });
|
|
588
615
|
}
|
|
589
616
|
async personFollowers(options) {
|
|
590
|
-
this.
|
|
617
|
+
this.requirePersonIdentity(options);
|
|
591
618
|
return this.tool('lookup/person-followers', { ...options });
|
|
592
619
|
}
|
|
593
620
|
async inboxList(options = {}) {
|
|
@@ -596,24 +623,24 @@ class OneShot {
|
|
|
596
623
|
limit: options.limit || undefined,
|
|
597
624
|
include_body: options.include_body ? 'true' : undefined,
|
|
598
625
|
});
|
|
599
|
-
const response = await
|
|
626
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/inbox${qs ? `?${qs}` : ''}`, {
|
|
600
627
|
headers: await this.signedReadHeaders()
|
|
601
628
|
});
|
|
602
629
|
if (!response.ok) {
|
|
603
|
-
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'));
|
|
604
631
|
}
|
|
605
632
|
return response.json();
|
|
606
633
|
}
|
|
607
634
|
async inboxGet(emailId) {
|
|
608
635
|
this.validate(emailId, 'emailId');
|
|
609
|
-
const response = await
|
|
636
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/inbox/${emailId}`, {
|
|
610
637
|
headers: await this.signedReadHeaders()
|
|
611
638
|
});
|
|
612
639
|
if (response.status === 404) {
|
|
613
640
|
throw new errors_2.ToolError('Email not found', 404, 'Email not found');
|
|
614
641
|
}
|
|
615
642
|
if (!response.ok) {
|
|
616
|
-
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'));
|
|
617
644
|
}
|
|
618
645
|
return response.json();
|
|
619
646
|
}
|
|
@@ -958,14 +985,14 @@ class OneShot {
|
|
|
958
985
|
this.validate(name, 'name');
|
|
959
986
|
if (options.cookies !== undefined && options.storage_state !== undefined)
|
|
960
987
|
throw new errors_2.ValidationError('Provide cookies or storage_state, not both', 'options');
|
|
961
|
-
const response = await
|
|
988
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/browser/profiles`, {
|
|
962
989
|
method: 'POST',
|
|
963
990
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders()) },
|
|
964
991
|
body: JSON.stringify({ name, ...options }),
|
|
965
992
|
signal: AbortSignal.timeout(150000),
|
|
966
993
|
});
|
|
967
994
|
if (!response.ok) {
|
|
968
|
-
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'));
|
|
969
996
|
}
|
|
970
997
|
return response.json();
|
|
971
998
|
}
|
|
@@ -994,12 +1021,12 @@ class OneShot {
|
|
|
994
1021
|
}
|
|
995
1022
|
async browserProfileSetup(profileId, action, body = {}) {
|
|
996
1023
|
this.validate(profileId, 'profileId');
|
|
997
|
-
const response = await
|
|
1024
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/browser/profiles/${encodeURIComponent(profileId)}/setup/${action}`, {
|
|
998
1025
|
method: 'POST', headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders()) },
|
|
999
1026
|
body: JSON.stringify(body), signal: AbortSignal.timeout(150000),
|
|
1000
1027
|
});
|
|
1001
1028
|
if (!response.ok)
|
|
1002
|
-
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'));
|
|
1003
1030
|
return response.json();
|
|
1004
1031
|
}
|
|
1005
1032
|
/**
|
|
@@ -1014,11 +1041,11 @@ class OneShot {
|
|
|
1014
1041
|
* ```
|
|
1015
1042
|
*/
|
|
1016
1043
|
async listBrowserProfiles() {
|
|
1017
|
-
const response = await
|
|
1044
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/browser/profiles`, {
|
|
1018
1045
|
headers: await this.signedReadHeaders(),
|
|
1019
1046
|
});
|
|
1020
1047
|
if (!response.ok) {
|
|
1021
|
-
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'));
|
|
1022
1049
|
}
|
|
1023
1050
|
const data = await response.json();
|
|
1024
1051
|
return data.profiles;
|
|
@@ -1033,12 +1060,12 @@ class OneShot {
|
|
|
1033
1060
|
*/
|
|
1034
1061
|
async deleteBrowserProfile(profileId) {
|
|
1035
1062
|
this.validate(profileId, 'profileId');
|
|
1036
|
-
const response = await
|
|
1063
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/browser/profiles/${profileId}`, {
|
|
1037
1064
|
method: 'DELETE',
|
|
1038
1065
|
headers: await this.signedReadHeaders(),
|
|
1039
1066
|
});
|
|
1040
1067
|
if (!response.ok) {
|
|
1041
|
-
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'));
|
|
1042
1069
|
}
|
|
1043
1070
|
}
|
|
1044
1071
|
async linkedinFree(method, path, opts) {
|
|
@@ -1046,7 +1073,7 @@ class OneShot {
|
|
|
1046
1073
|
for (const [k, v] of Object.entries(opts.query ?? {}))
|
|
1047
1074
|
if (v !== undefined)
|
|
1048
1075
|
url.searchParams.set(k, v);
|
|
1049
|
-
const response = await
|
|
1076
|
+
const response = await this.fetchApi(url.toString(), {
|
|
1050
1077
|
method,
|
|
1051
1078
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders(opts.scope ?? 'read')) },
|
|
1052
1079
|
...(opts.body !== undefined ? { body: JSON.stringify(opts.body) } : {}),
|
|
@@ -1061,7 +1088,7 @@ class OneShot {
|
|
|
1061
1088
|
catch { /* Non-JSON upstream error. */ }
|
|
1062
1089
|
if (response.status === 409 && code === 'connect_required')
|
|
1063
1090
|
throw new errors_1.LinkedInConnectRequiredError(body);
|
|
1064
|
-
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'));
|
|
1065
1092
|
}
|
|
1066
1093
|
return response.json();
|
|
1067
1094
|
}
|
|
@@ -1248,11 +1275,11 @@ class OneShot {
|
|
|
1248
1275
|
limit: options.limit || undefined,
|
|
1249
1276
|
from: options.from || undefined,
|
|
1250
1277
|
});
|
|
1251
|
-
const response = await
|
|
1278
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/sms/inbox${qs ? `?${qs}` : ''}`, {
|
|
1252
1279
|
headers: await this.signedReadHeaders()
|
|
1253
1280
|
});
|
|
1254
1281
|
if (!response.ok) {
|
|
1255
|
-
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'));
|
|
1256
1283
|
}
|
|
1257
1284
|
return response.json();
|
|
1258
1285
|
}
|
|
@@ -1394,7 +1421,7 @@ class OneShot {
|
|
|
1394
1421
|
const scope = (0, deadline_1.deadlineScope)(toolOptions.signal, filled.startsWith('/v1/tools/browser/profiles') ? 150000 : 30000);
|
|
1395
1422
|
try {
|
|
1396
1423
|
const request = (async () => {
|
|
1397
|
-
const response = await
|
|
1424
|
+
const response = await this.fetchApi(`${this.baseUrl}${target}`, {
|
|
1398
1425
|
method,
|
|
1399
1426
|
headers: { ...(sendsBody ? { 'Content-Type': 'application/json' } : {}), ...(await this.signedReadHeaders(method === 'GET' ? 'read' : 'write')) },
|
|
1400
1427
|
...(sendsBody ? { body: JSON.stringify(body ?? {}) } : {}),
|
|
@@ -1427,7 +1454,7 @@ class OneShot {
|
|
|
1427
1454
|
}
|
|
1428
1455
|
/** Signed free request returning the `data` field of `{ success, data }`. */
|
|
1429
1456
|
async signedJson(method, path, body, scope, failure) {
|
|
1430
|
-
const response = await
|
|
1457
|
+
const response = await this.fetchApi(`${this.baseUrl}${path}`, {
|
|
1431
1458
|
method,
|
|
1432
1459
|
headers: {
|
|
1433
1460
|
...(body !== undefined ? { 'Content-Type': 'application/json' } : {}),
|
|
@@ -1436,7 +1463,7 @@ class OneShot {
|
|
|
1436
1463
|
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
1437
1464
|
});
|
|
1438
1465
|
if (!response.ok) {
|
|
1439
|
-
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'));
|
|
1440
1467
|
}
|
|
1441
1468
|
const json = await response.json();
|
|
1442
1469
|
return (json.data ?? json);
|
|
@@ -1452,14 +1479,14 @@ class OneShot {
|
|
|
1452
1479
|
*/
|
|
1453
1480
|
async smsInboxGet(messageId) {
|
|
1454
1481
|
this.validate(messageId, 'messageId');
|
|
1455
|
-
const response = await
|
|
1482
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/sms/inbox/${messageId}`, {
|
|
1456
1483
|
headers: await this.signedReadHeaders()
|
|
1457
1484
|
});
|
|
1458
1485
|
if (response.status === 404) {
|
|
1459
1486
|
throw new errors_2.ToolError('SMS message not found', 404, 'Message not found');
|
|
1460
1487
|
}
|
|
1461
1488
|
if (!response.ok) {
|
|
1462
|
-
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'));
|
|
1463
1490
|
}
|
|
1464
1491
|
return response.json();
|
|
1465
1492
|
}
|
|
@@ -1480,11 +1507,11 @@ class OneShot {
|
|
|
1480
1507
|
unread: options.unread ? 'true' : undefined,
|
|
1481
1508
|
limit: options.limit || undefined,
|
|
1482
1509
|
});
|
|
1483
|
-
const response = await
|
|
1510
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/notifications${qs ? `?${qs}` : ''}`, {
|
|
1484
1511
|
headers: await this.signedReadHeaders()
|
|
1485
1512
|
});
|
|
1486
1513
|
if (!response.ok) {
|
|
1487
|
-
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'));
|
|
1488
1515
|
}
|
|
1489
1516
|
return response.json();
|
|
1490
1517
|
}
|
|
@@ -1498,7 +1525,7 @@ class OneShot {
|
|
|
1498
1525
|
*/
|
|
1499
1526
|
async markNotificationRead(notificationId) {
|
|
1500
1527
|
this.validate(notificationId, 'notificationId');
|
|
1501
|
-
const response = await
|
|
1528
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/notifications/${notificationId}/read`, {
|
|
1502
1529
|
method: 'PATCH',
|
|
1503
1530
|
headers: await this.signedReadHeaders('write')
|
|
1504
1531
|
});
|
|
@@ -1506,15 +1533,15 @@ class OneShot {
|
|
|
1506
1533
|
throw new errors_2.ToolError('Notification not found', 404, 'Notification not found');
|
|
1507
1534
|
}
|
|
1508
1535
|
if (!response.ok) {
|
|
1509
|
-
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'));
|
|
1510
1537
|
}
|
|
1511
1538
|
}
|
|
1512
1539
|
async getUnifiedBalance() {
|
|
1513
|
-
const response = await
|
|
1540
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/balance`, {
|
|
1514
1541
|
headers: await this.signedReadHeaders()
|
|
1515
1542
|
});
|
|
1516
1543
|
if (!response.ok) {
|
|
1517
|
-
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'));
|
|
1518
1545
|
}
|
|
1519
1546
|
return response.json();
|
|
1520
1547
|
}
|
|
@@ -1604,20 +1631,89 @@ class OneShot {
|
|
|
1604
1631
|
* console.log(status.status, status.budget?.remaining);
|
|
1605
1632
|
* ```
|
|
1606
1633
|
*/
|
|
1607
|
-
async getComputeGoal(goalId) {
|
|
1634
|
+
async getComputeGoal(goalId, options = {}) {
|
|
1608
1635
|
this.validate(goalId, 'goalId');
|
|
1609
|
-
const response = await
|
|
1610
|
-
headers: await this.signedReadHeaders()
|
|
1636
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}`, {
|
|
1637
|
+
headers: await this.signedReadHeaders(),
|
|
1638
|
+
signal: options.signal,
|
|
1611
1639
|
});
|
|
1612
1640
|
if (response.status === 404) {
|
|
1613
1641
|
throw new errors_2.ToolError('Goal not found', 404, 'Goal not found');
|
|
1614
1642
|
}
|
|
1615
1643
|
if (!response.ok) {
|
|
1616
|
-
|
|
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'));
|
|
1617
1650
|
}
|
|
1618
1651
|
const json = await response.json();
|
|
1619
1652
|
return json.data;
|
|
1620
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
|
+
}
|
|
1621
1717
|
/**
|
|
1622
1718
|
* List tasks under a compute goal
|
|
1623
1719
|
*
|
|
@@ -1631,11 +1727,11 @@ class OneShot {
|
|
|
1631
1727
|
*/
|
|
1632
1728
|
async getComputeTasks(goalId) {
|
|
1633
1729
|
this.validate(goalId, 'goalId');
|
|
1634
|
-
const response = await
|
|
1730
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}/tasks`, {
|
|
1635
1731
|
headers: await this.signedReadHeaders()
|
|
1636
1732
|
});
|
|
1637
1733
|
if (!response.ok) {
|
|
1638
|
-
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'));
|
|
1639
1735
|
}
|
|
1640
1736
|
const json = await response.json();
|
|
1641
1737
|
return json.data;
|
|
@@ -1654,11 +1750,11 @@ class OneShot {
|
|
|
1654
1750
|
*/
|
|
1655
1751
|
async getComputeOutputs(goalId) {
|
|
1656
1752
|
this.validate(goalId, 'goalId');
|
|
1657
|
-
const response = await
|
|
1753
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}/outputs`, {
|
|
1658
1754
|
headers: await this.signedReadHeaders()
|
|
1659
1755
|
});
|
|
1660
1756
|
if (!response.ok) {
|
|
1661
|
-
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'));
|
|
1662
1758
|
}
|
|
1663
1759
|
const json = await response.json();
|
|
1664
1760
|
return json.data;
|
|
@@ -1669,11 +1765,11 @@ class OneShot {
|
|
|
1669
1765
|
this.validate(key, 'key');
|
|
1670
1766
|
// The key is a path: keep its slashes, escape everything else.
|
|
1671
1767
|
const path = key.split('/').map(encodeURIComponent).join('/');
|
|
1672
|
-
const response = await
|
|
1768
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}/outputs/${path}`, {
|
|
1673
1769
|
headers: await this.signedReadHeaders()
|
|
1674
1770
|
});
|
|
1675
1771
|
if (!response.ok) {
|
|
1676
|
-
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'));
|
|
1677
1773
|
}
|
|
1678
1774
|
const json = await response.json();
|
|
1679
1775
|
return json.data;
|
|
@@ -1689,14 +1785,14 @@ class OneShot {
|
|
|
1689
1785
|
*/
|
|
1690
1786
|
async getComputeBudget(goalId) {
|
|
1691
1787
|
this.validate(goalId, 'goalId');
|
|
1692
|
-
const response = await
|
|
1788
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}/budget`, {
|
|
1693
1789
|
headers: await this.signedReadHeaders()
|
|
1694
1790
|
});
|
|
1695
1791
|
if (response.status === 404) {
|
|
1696
1792
|
throw new errors_2.ToolError('Budget not found', 404, 'Budget not found for this goal');
|
|
1697
1793
|
}
|
|
1698
1794
|
if (!response.ok) {
|
|
1699
|
-
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'));
|
|
1700
1796
|
}
|
|
1701
1797
|
const json = await response.json();
|
|
1702
1798
|
return json.data;
|
|
@@ -1712,7 +1808,7 @@ class OneShot {
|
|
|
1712
1808
|
*/
|
|
1713
1809
|
async cancelComputeGoal(goalId, reason) {
|
|
1714
1810
|
this.validate(goalId, 'goalId');
|
|
1715
|
-
const response = await
|
|
1811
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}/cancel`, {
|
|
1716
1812
|
method: 'POST',
|
|
1717
1813
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders('write')) },
|
|
1718
1814
|
body: JSON.stringify({ reason })
|
|
@@ -1726,7 +1822,7 @@ class OneShot {
|
|
|
1726
1822
|
return { goal_id: goalId, status: pending.status ?? 'pending_settlement', message: pending.message };
|
|
1727
1823
|
}
|
|
1728
1824
|
if (!response.ok) {
|
|
1729
|
-
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'));
|
|
1730
1826
|
}
|
|
1731
1827
|
const json = await response.json();
|
|
1732
1828
|
return json.data;
|
|
@@ -1746,16 +1842,16 @@ class OneShot {
|
|
|
1746
1842
|
async respondToComputeTask(goalId, input) {
|
|
1747
1843
|
this.validate(goalId, 'goalId');
|
|
1748
1844
|
this.validate(input.task_id, 'task_id');
|
|
1749
|
-
const response = await
|
|
1845
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}/respond`, {
|
|
1750
1846
|
method: 'POST',
|
|
1751
1847
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders('write')) },
|
|
1752
1848
|
body: JSON.stringify(input)
|
|
1753
1849
|
});
|
|
1754
1850
|
if (response.status === 404) {
|
|
1755
|
-
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'));
|
|
1756
1852
|
}
|
|
1757
1853
|
if (!response.ok) {
|
|
1758
|
-
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'));
|
|
1759
1855
|
}
|
|
1760
1856
|
const json = await response.json();
|
|
1761
1857
|
return json.data;
|
|
@@ -1775,15 +1871,15 @@ class OneShot {
|
|
|
1775
1871
|
*/
|
|
1776
1872
|
async confirmComputeGoal(goalId, input) {
|
|
1777
1873
|
this.validate(goalId, 'goalId');
|
|
1778
|
-
const response = await
|
|
1874
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}/respond`, {
|
|
1779
1875
|
method: 'POST',
|
|
1780
1876
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders('write')) },
|
|
1781
1877
|
body: JSON.stringify({ confirm_done: input.approved, ...(input.response ? { response: input.response } : {}) }),
|
|
1782
1878
|
});
|
|
1783
1879
|
if (response.status === 404)
|
|
1784
|
-
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'));
|
|
1785
1881
|
if (!response.ok)
|
|
1786
|
-
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'));
|
|
1787
1883
|
const json = await response.json();
|
|
1788
1884
|
return json.data;
|
|
1789
1885
|
}
|
|
@@ -1797,13 +1893,13 @@ class OneShot {
|
|
|
1797
1893
|
*/
|
|
1798
1894
|
async pauseComputeGoal(goalId, reason) {
|
|
1799
1895
|
this.validate(goalId, 'goalId');
|
|
1800
|
-
const response = await
|
|
1896
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}/pause`, {
|
|
1801
1897
|
method: 'POST',
|
|
1802
1898
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders('write')) },
|
|
1803
1899
|
body: JSON.stringify({ reason })
|
|
1804
1900
|
});
|
|
1805
1901
|
if (!response.ok) {
|
|
1806
|
-
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'));
|
|
1807
1903
|
}
|
|
1808
1904
|
const json = await response.json();
|
|
1809
1905
|
return json.data;
|
|
@@ -1819,13 +1915,13 @@ class OneShot {
|
|
|
1819
1915
|
*/
|
|
1820
1916
|
async resumeComputeGoal(goalId) {
|
|
1821
1917
|
this.validate(goalId, 'goalId');
|
|
1822
|
-
const response = await
|
|
1918
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}/resume`, {
|
|
1823
1919
|
method: 'POST',
|
|
1824
1920
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders('write')) },
|
|
1825
1921
|
body: JSON.stringify({})
|
|
1826
1922
|
});
|
|
1827
1923
|
if (!response.ok) {
|
|
1828
|
-
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'));
|
|
1829
1925
|
}
|
|
1830
1926
|
const json = await response.json();
|
|
1831
1927
|
return json.data;
|
|
@@ -1955,11 +2051,11 @@ class OneShot {
|
|
|
1955
2051
|
*/
|
|
1956
2052
|
async spendBreakdown(options) {
|
|
1957
2053
|
const qs = this.buildQuery({ period: options?.period || undefined });
|
|
1958
|
-
const response = await
|
|
2054
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/analytics/spend/breakdown${qs ? `?${qs}` : ''}`, {
|
|
1959
2055
|
headers: await this.signedReadHeaders()
|
|
1960
2056
|
});
|
|
1961
2057
|
if (!response.ok) {
|
|
1962
|
-
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'));
|
|
1963
2059
|
}
|
|
1964
2060
|
return response.json();
|
|
1965
2061
|
}
|
|
@@ -1974,11 +2070,11 @@ class OneShot {
|
|
|
1974
2070
|
*/
|
|
1975
2071
|
async rocs(options) {
|
|
1976
2072
|
const qs = this.buildQuery({ period: options?.period || undefined });
|
|
1977
|
-
const response = await
|
|
2073
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/analytics/rocs${qs ? `?${qs}` : ''}`, {
|
|
1978
2074
|
headers: await this.signedReadHeaders()
|
|
1979
2075
|
});
|
|
1980
2076
|
if (!response.ok) {
|
|
1981
|
-
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'));
|
|
1982
2078
|
}
|
|
1983
2079
|
return response.json();
|
|
1984
2080
|
}
|
|
@@ -2010,11 +2106,11 @@ class OneShot {
|
|
|
2010
2106
|
since: toIso(options?.since),
|
|
2011
2107
|
until: toIso(options?.until),
|
|
2012
2108
|
});
|
|
2013
|
-
const response = await
|
|
2109
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/analytics/receipts${qs ? `?${qs}` : ''}`, {
|
|
2014
2110
|
headers: await this.signedReadHeaders()
|
|
2015
2111
|
});
|
|
2016
2112
|
if (!response.ok) {
|
|
2017
|
-
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'));
|
|
2018
2114
|
}
|
|
2019
2115
|
return response.json();
|
|
2020
2116
|
}
|
|
@@ -2047,19 +2143,19 @@ class OneShot {
|
|
|
2047
2143
|
this.validate(valueTag.type, 'valueTag.type');
|
|
2048
2144
|
// Cadence-level: route to the outcome ledger by correlation key.
|
|
2049
2145
|
if (typeof ref === 'object' && ref.goalId && !ref.receiptId && !ref.requestId) {
|
|
2050
|
-
const response = await
|
|
2146
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/analytics/outcomes`, {
|
|
2051
2147
|
method: 'POST',
|
|
2052
2148
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders('write')) },
|
|
2053
2149
|
body: JSON.stringify({ goal_id: ref.goalId, ...valueTag }),
|
|
2054
2150
|
});
|
|
2055
2151
|
if (!response.ok) {
|
|
2056
|
-
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'));
|
|
2057
2153
|
}
|
|
2058
2154
|
return;
|
|
2059
2155
|
}
|
|
2060
2156
|
const id = typeof ref === 'string' ? ref : (ref.receiptId ?? ref.requestId);
|
|
2061
2157
|
this.validate(id, 'receiptId/requestId/goalId');
|
|
2062
|
-
const response = await
|
|
2158
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/analytics/receipts/${id}/value`, {
|
|
2063
2159
|
method: 'PATCH',
|
|
2064
2160
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders('write')) },
|
|
2065
2161
|
body: JSON.stringify(valueTag)
|
|
@@ -2068,7 +2164,7 @@ class OneShot {
|
|
|
2068
2164
|
throw new errors_2.ToolError('Receipt not found', 404, 'Receipt not found or not owned by this agent');
|
|
2069
2165
|
}
|
|
2070
2166
|
if (!response.ok) {
|
|
2071
|
-
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'));
|
|
2072
2168
|
}
|
|
2073
2169
|
}
|
|
2074
2170
|
/**
|
|
@@ -2090,11 +2186,11 @@ class OneShot {
|
|
|
2090
2186
|
*/
|
|
2091
2187
|
async rocsByGoal(options) {
|
|
2092
2188
|
const qs = this.buildQuery({ period: options?.period || undefined, goal_id: options?.goalId || undefined });
|
|
2093
|
-
const response = await
|
|
2189
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/analytics/rocs/by-goal${qs ? `?${qs}` : ''}`, {
|
|
2094
2190
|
headers: await this.signedReadHeaders()
|
|
2095
2191
|
});
|
|
2096
2192
|
if (!response.ok) {
|
|
2097
|
-
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'));
|
|
2098
2194
|
}
|
|
2099
2195
|
return response.json();
|
|
2100
2196
|
}
|
|
@@ -2135,7 +2231,7 @@ class OneShot {
|
|
|
2135
2231
|
const headers = await this.signedReadHeaders(`staging:${method}:${path}:${digest}`);
|
|
2136
2232
|
if (!headers['x-agent-proof'])
|
|
2137
2233
|
throw new Error('Could not sign staging application');
|
|
2138
|
-
const response = await
|
|
2234
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/staging${path}`, {
|
|
2139
2235
|
method, headers: { ...headers, 'Content-Type': 'application/json' },
|
|
2140
2236
|
signal: AbortSignal.timeout(10000),
|
|
2141
2237
|
...(body ? { body: JSON.stringify(body) } : {}),
|
|
@@ -2170,7 +2266,7 @@ class OneShot {
|
|
|
2170
2266
|
return base;
|
|
2171
2267
|
try {
|
|
2172
2268
|
const agent = this.provider.address;
|
|
2173
|
-
const issuedAt = Math.floor(Date.now() / 1000);
|
|
2269
|
+
const issuedAt = Math.floor(Date.now() / 1000) + (domainName === 'OneShot Agent Auth' ? this.proofClockOffsetSec : 0);
|
|
2174
2270
|
const nonce = ethers_1.ethers.hexlify(ethers_1.ethers.randomBytes(32));
|
|
2175
2271
|
const signature = await this.provider.signTypedData(
|
|
2176
2272
|
// `domainName` lets a different origin (e.g. Soul Hunt, which keeps
|
|
@@ -2205,6 +2301,45 @@ class OneShot {
|
|
|
2205
2301
|
return base;
|
|
2206
2302
|
}
|
|
2207
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
|
+
}
|
|
2208
2343
|
/** Build a query string, skipping null/undefined values. Pass falsy-but-valid
|
|
2209
2344
|
* values (0, '') as undefined at the call site to match prior `if (x)` guards. */
|
|
2210
2345
|
buildQuery(params) {
|
|
@@ -2248,7 +2383,7 @@ class OneShot {
|
|
|
2248
2383
|
body.alert_email = this._alertEmail;
|
|
2249
2384
|
let response;
|
|
2250
2385
|
try {
|
|
2251
|
-
response = await
|
|
2386
|
+
response = await this.fetchApi(`${this.baseUrl}/v1/agents/me/budgets`, {
|
|
2252
2387
|
method: 'PUT',
|
|
2253
2388
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders('write')) },
|
|
2254
2389
|
body: JSON.stringify(body),
|
|
@@ -2259,7 +2394,7 @@ class OneShot {
|
|
|
2259
2394
|
}
|
|
2260
2395
|
if (!response.ok) {
|
|
2261
2396
|
const text = await response.text();
|
|
2262
|
-
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'));
|
|
2263
2398
|
}
|
|
2264
2399
|
this.log('Budget synced');
|
|
2265
2400
|
})();
|
|
@@ -2301,7 +2436,7 @@ class OneShot {
|
|
|
2301
2436
|
*/
|
|
2302
2437
|
/** Signed-proof JSON request to a free agent route; returns `data`. Errors map through failFromResponse. */
|
|
2303
2438
|
async agentJson(path, method, body, scope = 'read') {
|
|
2304
|
-
const response = await
|
|
2439
|
+
const response = await this.fetchApi(`${this.baseUrl}${path}`, {
|
|
2305
2440
|
method,
|
|
2306
2441
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders(scope)) },
|
|
2307
2442
|
...(body === undefined || method === 'GET' ? {} : { body: JSON.stringify(body) }),
|
|
@@ -2313,11 +2448,11 @@ class OneShot {
|
|
|
2313
2448
|
return (json && typeof json === 'object' && 'data' in json ? json.data : json);
|
|
2314
2449
|
}
|
|
2315
2450
|
async budgets() {
|
|
2316
|
-
const response = await
|
|
2451
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/agents/me/budgets`, {
|
|
2317
2452
|
headers: await this.signedReadHeaders(),
|
|
2318
2453
|
});
|
|
2319
2454
|
if (!response.ok) {
|
|
2320
|
-
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'));
|
|
2321
2456
|
}
|
|
2322
2457
|
const body = await response.json();
|
|
2323
2458
|
return (body.data ?? body);
|
|
@@ -2340,24 +2475,24 @@ class OneShot {
|
|
|
2340
2475
|
*/
|
|
2341
2476
|
async createAccessToken(opts = {}) {
|
|
2342
2477
|
this.assertWalletSession('mint access tokens');
|
|
2343
|
-
const response = await
|
|
2478
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/agents/me/access-tokens`, {
|
|
2344
2479
|
method: 'POST',
|
|
2345
2480
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders('write')) },
|
|
2346
2481
|
body: JSON.stringify(opts.name !== undefined ? { name: opts.name } : {}),
|
|
2347
2482
|
});
|
|
2348
2483
|
if (!response.ok)
|
|
2349
|
-
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'));
|
|
2350
2485
|
const body = await response.json();
|
|
2351
2486
|
return body.data;
|
|
2352
2487
|
}
|
|
2353
2488
|
/** List this agent's access tokens (prefix, label, last use, spend today — never the token). */
|
|
2354
2489
|
async listAccessTokens(opts = {}) {
|
|
2355
2490
|
this.assertWalletSession('list access tokens');
|
|
2356
|
-
const response = await
|
|
2491
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/agents/me/access-tokens${opts.activeOnly ? '?active=true' : ''}`, {
|
|
2357
2492
|
headers: await this.signedReadHeaders(),
|
|
2358
2493
|
});
|
|
2359
2494
|
if (!response.ok)
|
|
2360
|
-
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'));
|
|
2361
2496
|
const body = await response.json();
|
|
2362
2497
|
return body.data.tokens;
|
|
2363
2498
|
}
|
|
@@ -2365,12 +2500,12 @@ class OneShot {
|
|
|
2365
2500
|
async revokeAccessToken(id) {
|
|
2366
2501
|
this.assertWalletSession('revoke access tokens');
|
|
2367
2502
|
this.validate(id, 'id');
|
|
2368
|
-
const response = await
|
|
2503
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/agents/me/access-tokens/${encodeURIComponent(id)}`, {
|
|
2369
2504
|
method: 'DELETE',
|
|
2370
2505
|
headers: await this.signedReadHeaders('write'),
|
|
2371
2506
|
});
|
|
2372
2507
|
if (!response.ok)
|
|
2373
|
-
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'));
|
|
2374
2509
|
const body = await response.json();
|
|
2375
2510
|
return body.data;
|
|
2376
2511
|
}
|
|
@@ -2429,7 +2564,7 @@ class OneShot {
|
|
|
2429
2564
|
const budget = response.status === 403 ? this.parseBudgetRejection(text) : undefined;
|
|
2430
2565
|
if (budget)
|
|
2431
2566
|
throw budget;
|
|
2432
|
-
throw new errors_2.ToolError(message, response.status, text);
|
|
2567
|
+
throw new errors_2.ToolError(message, response.status, text, response.headers.get('retry-after'));
|
|
2433
2568
|
}
|
|
2434
2569
|
/**
|
|
2435
2570
|
* Map a 402 `insufficient_credits` body (access-token sessions) onto a typed
|
|
@@ -2493,12 +2628,12 @@ class OneShot {
|
|
|
2493
2628
|
throw new errors_2.ToolError(`Could not resolve the access token's agent (network): ${err}`, 0, String(err));
|
|
2494
2629
|
}
|
|
2495
2630
|
if (!response.ok) {
|
|
2496
|
-
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'));
|
|
2497
2632
|
}
|
|
2498
2633
|
const json = await response.json();
|
|
2499
2634
|
const address = json.data?.wallet_address;
|
|
2500
2635
|
if (!address)
|
|
2501
|
-
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'));
|
|
2502
2637
|
return address;
|
|
2503
2638
|
}
|
|
2504
2639
|
/**
|
|
@@ -2601,7 +2736,7 @@ class OneShot {
|
|
|
2601
2736
|
const headers = signed ? await this.signedReadHeaders() : undefined;
|
|
2602
2737
|
if (scope.signal.aborted)
|
|
2603
2738
|
throw new errors_2.OneShotError('Read deadline exceeded');
|
|
2604
|
-
const response = await
|
|
2739
|
+
const response = await this.fetchApi(`${this.baseUrl}${path}`, { headers, signal: scope.signal });
|
|
2605
2740
|
if (!response.ok && !(allowDegraded && response.status === 503))
|
|
2606
2741
|
await this.failFromResponse('Reliability read failed', response);
|
|
2607
2742
|
return await response.json();
|
|
@@ -2620,7 +2755,7 @@ class OneShot {
|
|
|
2620
2755
|
}
|
|
2621
2756
|
async executeToolRequest(endpoint, options, quoteId) {
|
|
2622
2757
|
// LinkedIn writes act through a human's account and cannot be recalled: always keyed.
|
|
2623
|
-
const reliable = /(?:^|\/)(lookup\/person-(profile|email|email-status)|lookup\/place-permits|solve|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);
|
|
2624
2759
|
const key = options.idempotencyKey ?? (reliable ? ethers_1.ethers.hexlify(ethers_1.ethers.randomBytes(16)) : undefined);
|
|
2625
2760
|
if (options.totalTimeoutMs !== undefined && (!Number.isFinite(options.totalTimeoutMs) || options.totalTimeoutMs <= 0)) {
|
|
2626
2761
|
throw new errors_2.ValidationError('totalTimeoutMs must be positive', 'totalTimeoutMs');
|
|
@@ -2663,7 +2798,7 @@ class OneShot {
|
|
|
2663
2798
|
this.log('Memo truncated to 1000 chars');
|
|
2664
2799
|
}
|
|
2665
2800
|
}
|
|
2666
|
-
else if (!endpoint.endsWith('/v1/tools/solve') && !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')) {
|
|
2667
2802
|
this.log('No memo provided — consider adding a reason for audit trail');
|
|
2668
2803
|
}
|
|
2669
2804
|
if (payload.decisionContext !== undefined) {
|
|
@@ -2692,7 +2827,7 @@ class OneShot {
|
|
|
2692
2827
|
context.phase = 'submission';
|
|
2693
2828
|
let response = await this.makeRequest(endpoint, payload, undefined, quoteId, signal, undefined, extraHeaders);
|
|
2694
2829
|
// Handle 402 Payment Required
|
|
2695
|
-
if (response.status === 402 && /\/v1\/tools\/(solve|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)) {
|
|
2696
2831
|
({ execResp: response } = await this.runQuoteToPay({
|
|
2697
2832
|
endpoint, payload, signal, maxCost, quoteResponse: response, extraHeaders,
|
|
2698
2833
|
expectMsg: 'Expected a tool quote', totalOf: quote => quote.total,
|
|
@@ -2813,6 +2948,15 @@ class OneShot {
|
|
|
2813
2948
|
* `wait: false`, or to resume waiting after a client restart. Delivery is the
|
|
2814
2949
|
* same as for blocking calls: WebSocket push when the server offers it, HTTP
|
|
2815
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.
|
|
2816
2960
|
*/
|
|
2817
2961
|
async waitForResult(requestId, options = {}) {
|
|
2818
2962
|
this.validate(requestId, 'requestId');
|
|
@@ -2825,6 +2969,9 @@ class OneShot {
|
|
|
2825
2969
|
// swallows frames) costs nothing beyond the poll cadence. Previously the
|
|
2826
2970
|
// client waited 60% of its timeout on the socket before polling at all —
|
|
2827
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');
|
|
2828
2975
|
const inner = new AbortController();
|
|
2829
2976
|
const onOuterAbort = () => inner.abort();
|
|
2830
2977
|
if (signal?.aborted)
|
|
@@ -2832,12 +2979,24 @@ class OneShot {
|
|
|
2832
2979
|
else
|
|
2833
2980
|
signal?.addEventListener('abort', onOuterAbort, { once: true });
|
|
2834
2981
|
const wait = { pushConfirmed: false, lastStatus: undefined, via: undefined };
|
|
2835
|
-
|
|
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.
|
|
2836
2991
|
const emit = (status) => {
|
|
2992
|
+
if (wait.lastStatus === 'completed' || wait.lastStatus === 'failed')
|
|
2993
|
+
return;
|
|
2837
2994
|
if (status === wait.lastStatus)
|
|
2838
2995
|
return;
|
|
2839
2996
|
wait.lastStatus = status;
|
|
2840
|
-
|
|
2997
|
+
if (status === 'completed' && phoneOpts?.waitForPhones)
|
|
2998
|
+
return;
|
|
2999
|
+
notify(status);
|
|
2841
3000
|
};
|
|
2842
3001
|
const startedAt = Date.now();
|
|
2843
3002
|
const wsBranch = this.waitViaWebSocket(requestId, inner.signal, emit, wait).catch((err) => {
|
|
@@ -2873,8 +3032,10 @@ class OneShot {
|
|
|
2873
3032
|
// hit a transient error before any successful refresh, we return what we
|
|
2874
3033
|
// already have (with phones_pending=true still set so consumers know).
|
|
2875
3034
|
if (phoneOpts?.waitForPhones && this._isPhonesPending(result)) {
|
|
2876
|
-
|
|
3035
|
+
result = await this._pollForPhones(requestId, phoneOpts.phoneTimeoutSec ?? 360, signal, result);
|
|
2877
3036
|
}
|
|
3037
|
+
if (phoneOpts?.waitForPhones)
|
|
3038
|
+
notify('completed');
|
|
2878
3039
|
return result;
|
|
2879
3040
|
}
|
|
2880
3041
|
/**
|
|
@@ -2918,7 +3079,7 @@ class OneShot {
|
|
|
2918
3079
|
if (signal?.aborted)
|
|
2919
3080
|
throw new errors_2.OneShotError('Operation cancelled');
|
|
2920
3081
|
try {
|
|
2921
|
-
const resp = await
|
|
3082
|
+
const resp = await this.fetchApi(`${this.baseUrl}/v1/requests/${requestId}`, {
|
|
2922
3083
|
headers: await this.signedReadHeaders(),
|
|
2923
3084
|
signal,
|
|
2924
3085
|
});
|
|
@@ -2927,7 +3088,7 @@ class OneShot {
|
|
|
2927
3088
|
// whatever we last had so consumers don't lose the sync result.
|
|
2928
3089
|
if (lastResult !== undefined)
|
|
2929
3090
|
return lastResult;
|
|
2930
|
-
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'));
|
|
2931
3092
|
}
|
|
2932
3093
|
const job = await resp.json();
|
|
2933
3094
|
lastResult = (job.result ?? job);
|
|
@@ -2936,6 +3097,8 @@ class OneShot {
|
|
|
2936
3097
|
}
|
|
2937
3098
|
}
|
|
2938
3099
|
catch (err) {
|
|
3100
|
+
if (signal?.aborted)
|
|
3101
|
+
throw new errors_2.OneShotError('Operation cancelled');
|
|
2939
3102
|
if (err instanceof errors_2.OneShotError)
|
|
2940
3103
|
throw err;
|
|
2941
3104
|
if (lastResult !== undefined)
|
|
@@ -3031,6 +3194,7 @@ class OneShot {
|
|
|
3031
3194
|
wait.pushConfirmed = true;
|
|
3032
3195
|
this.log('WebSocket push confirmed — relaxing HTTP polling');
|
|
3033
3196
|
}
|
|
3197
|
+
emit(String(msg.status));
|
|
3034
3198
|
if (msg.status === 'completed') {
|
|
3035
3199
|
this.log('Job completed (WebSocket)');
|
|
3036
3200
|
settle(() => {
|
|
@@ -3050,9 +3214,6 @@ class OneShot {
|
|
|
3050
3214
|
reject(new errors_2.JobError(`Job failed: ${msg.error ?? 'Unknown'}`, requestId, String(msg.error ?? 'Unknown'), msg.error_code, msg.result));
|
|
3051
3215
|
});
|
|
3052
3216
|
}
|
|
3053
|
-
else {
|
|
3054
|
-
emit(String(msg.status));
|
|
3055
|
-
}
|
|
3056
3217
|
}
|
|
3057
3218
|
catch {
|
|
3058
3219
|
// Ignore malformed messages
|
|
@@ -3088,6 +3249,9 @@ class OneShot {
|
|
|
3088
3249
|
catch (error) {
|
|
3089
3250
|
if (scope.timedOut())
|
|
3090
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');
|
|
3091
3255
|
throw error;
|
|
3092
3256
|
}
|
|
3093
3257
|
finally {
|
|
@@ -3104,7 +3268,7 @@ class OneShot {
|
|
|
3104
3268
|
if (signal?.aborted)
|
|
3105
3269
|
throw new errors_2.OneShotError('Operation cancelled');
|
|
3106
3270
|
try {
|
|
3107
|
-
const resp = await
|
|
3271
|
+
const resp = await this.fetchApi(`${this.baseUrl}/v1/requests/${requestId}`, {
|
|
3108
3272
|
headers: await this.signedReadHeaders(),
|
|
3109
3273
|
signal
|
|
3110
3274
|
});
|
|
@@ -3115,9 +3279,10 @@ class OneShot {
|
|
|
3115
3279
|
if (resp.status >= 500 || resp.status === 429) {
|
|
3116
3280
|
throw new Error(`Poll returned ${resp.status}: ${body.slice(0, 200)}`);
|
|
3117
3281
|
}
|
|
3118
|
-
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'));
|
|
3119
3283
|
}
|
|
3120
3284
|
const job = await resp.json();
|
|
3285
|
+
emit?.(String(job.status));
|
|
3121
3286
|
if (job.status === 'completed') {
|
|
3122
3287
|
this.log('Job completed');
|
|
3123
3288
|
if (wait)
|
|
@@ -3140,7 +3305,6 @@ class OneShot {
|
|
|
3140
3305
|
wait.via = 'http';
|
|
3141
3306
|
throw new errors_2.JobError(`Job failed: ${job.error ?? 'Unknown'}`, requestId, String(job.error ?? 'Unknown'), job.error_code, job.result);
|
|
3142
3307
|
}
|
|
3143
|
-
emit?.(String(job.status));
|
|
3144
3308
|
retries = 0;
|
|
3145
3309
|
const interval = wait?.pushConfirmed
|
|
3146
3310
|
? HTTP_POLL_RELAXED_MS
|
|
@@ -3223,7 +3387,7 @@ class OneShot {
|
|
|
3223
3387
|
// alone (401 proof_required).
|
|
3224
3388
|
const unsignedStub = payment?.payload?.signature === '0x';
|
|
3225
3389
|
const askedForProof = !!extraHeaders?.['x-agent-proof'];
|
|
3226
|
-
const proofScope = /(lookup\/person-(profile|email|email-status)|lookup\/place-permits|physical-mail\/send|solve)$/.test(endpoint)
|
|
3390
|
+
const proofScope = /(lookup\/person-(profile|email|email-status)|lookup\/place-permits|physical-mail\/send|solve|predict)$/.test(endpoint)
|
|
3227
3391
|
? 'submit'
|
|
3228
3392
|
: askedForProof ? 'read' : 'write';
|
|
3229
3393
|
const headers = {
|
|
@@ -3251,7 +3415,7 @@ class OneShot {
|
|
|
3251
3415
|
transportDeadline?.signal.addEventListener('abort', () => transportDeadline.close(), { once: true });
|
|
3252
3416
|
if (fetchSignal?.aborted)
|
|
3253
3417
|
throw new errors_2.OneShotError('Operation cancelled');
|
|
3254
|
-
const work =
|
|
3418
|
+
const work = this.fetchApi(`${this.baseUrl}${endpoint}`, {
|
|
3255
3419
|
method: 'POST', headers, body: JSON.stringify(data), signal: fetchSignal,
|
|
3256
3420
|
});
|
|
3257
3421
|
try {
|
|
@@ -3408,7 +3572,7 @@ class OneShot {
|
|
|
3408
3572
|
}
|
|
3409
3573
|
catch (err) {
|
|
3410
3574
|
// Keyed writes may already be queued server-side after a transport failure: keep the reservation.
|
|
3411
|
-
if (!/(lookup\/person-(profile|email|email-status)|lookup\/place-permits|physical-mail\/send|solve|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))
|
|
3412
3576
|
this.releaseUsdcReservation(signed.reservation);
|
|
3413
3577
|
throw err;
|
|
3414
3578
|
}
|