@oneshot-agent/sdk 0.55.0 → 0.57.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 +136 -734
- 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 +292 -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/types.d.ts +53 -25
- 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.57.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
|
}
|
|
@@ -693,6 +720,8 @@ class OneShot {
|
|
|
693
720
|
};
|
|
694
721
|
if (options.caller_persona)
|
|
695
722
|
payload.caller_persona = options.caller_persona;
|
|
723
|
+
if (options.caller_name)
|
|
724
|
+
payload.caller_name = options.caller_name;
|
|
696
725
|
if (options.context)
|
|
697
726
|
payload.context = options.context;
|
|
698
727
|
if (options.max_duration_minutes)
|
|
@@ -723,7 +752,9 @@ class OneShot {
|
|
|
723
752
|
const result = await callResp.json();
|
|
724
753
|
this.log(`Call initiated: ${result.request_id}`);
|
|
725
754
|
if (options.wait !== false && result.request_id) {
|
|
726
|
-
|
|
755
|
+
// Wait for the longest the call may run, plus two minutes to dial and settle.
|
|
756
|
+
const timeout = options.timeout ?? (options.max_duration_minutes ?? 10) * 60 + 120;
|
|
757
|
+
return this.pollJob(result.request_id, timeout, options.signal, options.onStatusUpdate);
|
|
727
758
|
}
|
|
728
759
|
return result;
|
|
729
760
|
}
|
|
@@ -958,14 +989,14 @@ class OneShot {
|
|
|
958
989
|
this.validate(name, 'name');
|
|
959
990
|
if (options.cookies !== undefined && options.storage_state !== undefined)
|
|
960
991
|
throw new errors_2.ValidationError('Provide cookies or storage_state, not both', 'options');
|
|
961
|
-
const response = await
|
|
992
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/browser/profiles`, {
|
|
962
993
|
method: 'POST',
|
|
963
994
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders()) },
|
|
964
995
|
body: JSON.stringify({ name, ...options }),
|
|
965
996
|
signal: AbortSignal.timeout(150000),
|
|
966
997
|
});
|
|
967
998
|
if (!response.ok) {
|
|
968
|
-
throw new errors_2.ToolError('Failed to create browser profile', response.status, await response.text());
|
|
999
|
+
throw new errors_2.ToolError('Failed to create browser profile', response.status, await response.text(), response.headers.get('retry-after'));
|
|
969
1000
|
}
|
|
970
1001
|
return response.json();
|
|
971
1002
|
}
|
|
@@ -994,12 +1025,12 @@ class OneShot {
|
|
|
994
1025
|
}
|
|
995
1026
|
async browserProfileSetup(profileId, action, body = {}) {
|
|
996
1027
|
this.validate(profileId, 'profileId');
|
|
997
|
-
const response = await
|
|
1028
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/browser/profiles/${encodeURIComponent(profileId)}/setup/${action}`, {
|
|
998
1029
|
method: 'POST', headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders()) },
|
|
999
1030
|
body: JSON.stringify(body), signal: AbortSignal.timeout(150000),
|
|
1000
1031
|
});
|
|
1001
1032
|
if (!response.ok)
|
|
1002
|
-
throw new errors_2.ToolError('Browser profile setup failed', response.status, '');
|
|
1033
|
+
throw new errors_2.ToolError('Browser profile setup failed', response.status, '', response.headers.get('retry-after'));
|
|
1003
1034
|
return response.json();
|
|
1004
1035
|
}
|
|
1005
1036
|
/**
|
|
@@ -1014,11 +1045,11 @@ class OneShot {
|
|
|
1014
1045
|
* ```
|
|
1015
1046
|
*/
|
|
1016
1047
|
async listBrowserProfiles() {
|
|
1017
|
-
const response = await
|
|
1048
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/browser/profiles`, {
|
|
1018
1049
|
headers: await this.signedReadHeaders(),
|
|
1019
1050
|
});
|
|
1020
1051
|
if (!response.ok) {
|
|
1021
|
-
throw new errors_2.ToolError('Failed to list browser profiles', response.status, await response.text());
|
|
1052
|
+
throw new errors_2.ToolError('Failed to list browser profiles', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1022
1053
|
}
|
|
1023
1054
|
const data = await response.json();
|
|
1024
1055
|
return data.profiles;
|
|
@@ -1033,12 +1064,12 @@ class OneShot {
|
|
|
1033
1064
|
*/
|
|
1034
1065
|
async deleteBrowserProfile(profileId) {
|
|
1035
1066
|
this.validate(profileId, 'profileId');
|
|
1036
|
-
const response = await
|
|
1067
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/browser/profiles/${profileId}`, {
|
|
1037
1068
|
method: 'DELETE',
|
|
1038
1069
|
headers: await this.signedReadHeaders(),
|
|
1039
1070
|
});
|
|
1040
1071
|
if (!response.ok) {
|
|
1041
|
-
throw new errors_2.ToolError('Failed to delete browser profile', response.status, await response.text());
|
|
1072
|
+
throw new errors_2.ToolError('Failed to delete browser profile', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1042
1073
|
}
|
|
1043
1074
|
}
|
|
1044
1075
|
async linkedinFree(method, path, opts) {
|
|
@@ -1046,7 +1077,7 @@ class OneShot {
|
|
|
1046
1077
|
for (const [k, v] of Object.entries(opts.query ?? {}))
|
|
1047
1078
|
if (v !== undefined)
|
|
1048
1079
|
url.searchParams.set(k, v);
|
|
1049
|
-
const response = await
|
|
1080
|
+
const response = await this.fetchApi(url.toString(), {
|
|
1050
1081
|
method,
|
|
1051
1082
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders(opts.scope ?? 'read')) },
|
|
1052
1083
|
...(opts.body !== undefined ? { body: JSON.stringify(opts.body) } : {}),
|
|
@@ -1061,7 +1092,7 @@ class OneShot {
|
|
|
1061
1092
|
catch { /* Non-JSON upstream error. */ }
|
|
1062
1093
|
if (response.status === 409 && code === 'connect_required')
|
|
1063
1094
|
throw new errors_1.LinkedInConnectRequiredError(body);
|
|
1064
|
-
throw new errors_2.ToolError(`LinkedIn ${opts.what} failed`, response.status, body);
|
|
1095
|
+
throw new errors_2.ToolError(`LinkedIn ${opts.what} failed`, response.status, body, response.headers.get('retry-after'));
|
|
1065
1096
|
}
|
|
1066
1097
|
return response.json();
|
|
1067
1098
|
}
|
|
@@ -1248,11 +1279,11 @@ class OneShot {
|
|
|
1248
1279
|
limit: options.limit || undefined,
|
|
1249
1280
|
from: options.from || undefined,
|
|
1250
1281
|
});
|
|
1251
|
-
const response = await
|
|
1282
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/sms/inbox${qs ? `?${qs}` : ''}`, {
|
|
1252
1283
|
headers: await this.signedReadHeaders()
|
|
1253
1284
|
});
|
|
1254
1285
|
if (!response.ok) {
|
|
1255
|
-
throw new errors_2.ToolError('Failed to list SMS inbox', response.status, await response.text());
|
|
1286
|
+
throw new errors_2.ToolError('Failed to list SMS inbox', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1256
1287
|
}
|
|
1257
1288
|
return response.json();
|
|
1258
1289
|
}
|
|
@@ -1394,7 +1425,7 @@ class OneShot {
|
|
|
1394
1425
|
const scope = (0, deadline_1.deadlineScope)(toolOptions.signal, filled.startsWith('/v1/tools/browser/profiles') ? 150000 : 30000);
|
|
1395
1426
|
try {
|
|
1396
1427
|
const request = (async () => {
|
|
1397
|
-
const response = await
|
|
1428
|
+
const response = await this.fetchApi(`${this.baseUrl}${target}`, {
|
|
1398
1429
|
method,
|
|
1399
1430
|
headers: { ...(sendsBody ? { 'Content-Type': 'application/json' } : {}), ...(await this.signedReadHeaders(method === 'GET' ? 'read' : 'write')) },
|
|
1400
1431
|
...(sendsBody ? { body: JSON.stringify(body ?? {}) } : {}),
|
|
@@ -1427,7 +1458,7 @@ class OneShot {
|
|
|
1427
1458
|
}
|
|
1428
1459
|
/** Signed free request returning the `data` field of `{ success, data }`. */
|
|
1429
1460
|
async signedJson(method, path, body, scope, failure) {
|
|
1430
|
-
const response = await
|
|
1461
|
+
const response = await this.fetchApi(`${this.baseUrl}${path}`, {
|
|
1431
1462
|
method,
|
|
1432
1463
|
headers: {
|
|
1433
1464
|
...(body !== undefined ? { 'Content-Type': 'application/json' } : {}),
|
|
@@ -1436,7 +1467,7 @@ class OneShot {
|
|
|
1436
1467
|
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
1437
1468
|
});
|
|
1438
1469
|
if (!response.ok) {
|
|
1439
|
-
throw new errors_2.ToolError(failure, response.status, await response.text());
|
|
1470
|
+
throw new errors_2.ToolError(failure, response.status, await response.text(), response.headers.get('retry-after'));
|
|
1440
1471
|
}
|
|
1441
1472
|
const json = await response.json();
|
|
1442
1473
|
return (json.data ?? json);
|
|
@@ -1452,14 +1483,14 @@ class OneShot {
|
|
|
1452
1483
|
*/
|
|
1453
1484
|
async smsInboxGet(messageId) {
|
|
1454
1485
|
this.validate(messageId, 'messageId');
|
|
1455
|
-
const response = await
|
|
1486
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/sms/inbox/${messageId}`, {
|
|
1456
1487
|
headers: await this.signedReadHeaders()
|
|
1457
1488
|
});
|
|
1458
1489
|
if (response.status === 404) {
|
|
1459
1490
|
throw new errors_2.ToolError('SMS message not found', 404, 'Message not found');
|
|
1460
1491
|
}
|
|
1461
1492
|
if (!response.ok) {
|
|
1462
|
-
throw new errors_2.ToolError('Failed to get SMS message', response.status, await response.text());
|
|
1493
|
+
throw new errors_2.ToolError('Failed to get SMS message', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1463
1494
|
}
|
|
1464
1495
|
return response.json();
|
|
1465
1496
|
}
|
|
@@ -1480,11 +1511,11 @@ class OneShot {
|
|
|
1480
1511
|
unread: options.unread ? 'true' : undefined,
|
|
1481
1512
|
limit: options.limit || undefined,
|
|
1482
1513
|
});
|
|
1483
|
-
const response = await
|
|
1514
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/notifications${qs ? `?${qs}` : ''}`, {
|
|
1484
1515
|
headers: await this.signedReadHeaders()
|
|
1485
1516
|
});
|
|
1486
1517
|
if (!response.ok) {
|
|
1487
|
-
throw new errors_2.ToolError('Failed to list notifications', response.status, await response.text());
|
|
1518
|
+
throw new errors_2.ToolError('Failed to list notifications', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1488
1519
|
}
|
|
1489
1520
|
return response.json();
|
|
1490
1521
|
}
|
|
@@ -1498,7 +1529,7 @@ class OneShot {
|
|
|
1498
1529
|
*/
|
|
1499
1530
|
async markNotificationRead(notificationId) {
|
|
1500
1531
|
this.validate(notificationId, 'notificationId');
|
|
1501
|
-
const response = await
|
|
1532
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/notifications/${notificationId}/read`, {
|
|
1502
1533
|
method: 'PATCH',
|
|
1503
1534
|
headers: await this.signedReadHeaders('write')
|
|
1504
1535
|
});
|
|
@@ -1506,15 +1537,15 @@ class OneShot {
|
|
|
1506
1537
|
throw new errors_2.ToolError('Notification not found', 404, 'Notification not found');
|
|
1507
1538
|
}
|
|
1508
1539
|
if (!response.ok) {
|
|
1509
|
-
throw new errors_2.ToolError('Failed to mark notification as read', response.status, await response.text());
|
|
1540
|
+
throw new errors_2.ToolError('Failed to mark notification as read', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1510
1541
|
}
|
|
1511
1542
|
}
|
|
1512
1543
|
async getUnifiedBalance() {
|
|
1513
|
-
const response = await
|
|
1544
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/tools/balance`, {
|
|
1514
1545
|
headers: await this.signedReadHeaders()
|
|
1515
1546
|
});
|
|
1516
1547
|
if (!response.ok) {
|
|
1517
|
-
throw new errors_2.ToolError('Failed to fetch balance', response.status, await response.text());
|
|
1548
|
+
throw new errors_2.ToolError('Failed to fetch balance', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1518
1549
|
}
|
|
1519
1550
|
return response.json();
|
|
1520
1551
|
}
|
|
@@ -1604,20 +1635,89 @@ class OneShot {
|
|
|
1604
1635
|
* console.log(status.status, status.budget?.remaining);
|
|
1605
1636
|
* ```
|
|
1606
1637
|
*/
|
|
1607
|
-
async getComputeGoal(goalId) {
|
|
1638
|
+
async getComputeGoal(goalId, options = {}) {
|
|
1608
1639
|
this.validate(goalId, 'goalId');
|
|
1609
|
-
const response = await
|
|
1610
|
-
headers: await this.signedReadHeaders()
|
|
1640
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}`, {
|
|
1641
|
+
headers: await this.signedReadHeaders(),
|
|
1642
|
+
signal: options.signal,
|
|
1611
1643
|
});
|
|
1612
1644
|
if (response.status === 404) {
|
|
1613
1645
|
throw new errors_2.ToolError('Goal not found', 404, 'Goal not found');
|
|
1614
1646
|
}
|
|
1615
1647
|
if (!response.ok) {
|
|
1616
|
-
|
|
1648
|
+
// Keep the HTTP classification even if the error body is interrupted.
|
|
1649
|
+
const body = await response.text().catch(() => {
|
|
1650
|
+
options.signal?.throwIfAborted();
|
|
1651
|
+
return 'Response body unavailable';
|
|
1652
|
+
});
|
|
1653
|
+
throw new errors_2.ToolError('Failed to get compute goal', response.status, body, response.headers.get('retry-after'));
|
|
1617
1654
|
}
|
|
1618
1655
|
const json = await response.json();
|
|
1619
1656
|
return json.data;
|
|
1620
1657
|
}
|
|
1658
|
+
/**
|
|
1659
|
+
* Poll a compute goal using its goal_id, not its creation request_id.
|
|
1660
|
+
* Returns the complete goal snapshot on completed; failed/cancelled reject
|
|
1661
|
+
* with JobError (goal ID, end_reason code, snapshot in partialResult).
|
|
1662
|
+
* Budget exhaustion ends this wait; after fundComputeGoal revives the goal,
|
|
1663
|
+
* call waitForComputeGoal again with the same ID to observe the new run.
|
|
1664
|
+
* Paused/awaiting_confirmation and recurring goals keep waiting until the
|
|
1665
|
+
* goal ends or the timeout (seconds, default 120) expires with JobTimeoutError.
|
|
1666
|
+
* Infinity disables the local deadline.
|
|
1667
|
+
* onStatusUpdate receives each distinct observed status before settlement.
|
|
1668
|
+
* Callback throws/rejections are ignored and promises are not awaited.
|
|
1669
|
+
* Transient reads (5xx, 429, network failures) retry on the polling cadence.
|
|
1670
|
+
*/
|
|
1671
|
+
async waitForComputeGoal(goalId, options = {}) {
|
|
1672
|
+
this.validate(goalId, 'goalId');
|
|
1673
|
+
const started = Date.now();
|
|
1674
|
+
const scope = (0, deadline_1.deadlineScope)(options.signal, (options.timeout ?? 120) * 1000);
|
|
1675
|
+
const poll = async () => {
|
|
1676
|
+
let lastStatus;
|
|
1677
|
+
let polls = 0;
|
|
1678
|
+
while (true) {
|
|
1679
|
+
scope.signal.throwIfAborted();
|
|
1680
|
+
let goal;
|
|
1681
|
+
try {
|
|
1682
|
+
goal = await this.getComputeGoal(goalId, { signal: scope.signal });
|
|
1683
|
+
}
|
|
1684
|
+
catch (error) {
|
|
1685
|
+
scope.signal.throwIfAborted();
|
|
1686
|
+
if (!isTransientComputeReadError(error))
|
|
1687
|
+
throw error;
|
|
1688
|
+
await this.sleep(HTTP_POLL_BACKOFF_MS[Math.min(polls++, HTTP_POLL_BACKOFF_MS.length - 1)], scope.signal);
|
|
1689
|
+
continue;
|
|
1690
|
+
}
|
|
1691
|
+
// A late response from a transport ignoring abort must not emit progress.
|
|
1692
|
+
scope.signal.throwIfAborted();
|
|
1693
|
+
if (goal.status !== lastStatus) {
|
|
1694
|
+
lastStatus = goal.status;
|
|
1695
|
+
try {
|
|
1696
|
+
Promise.resolve(options.onStatusUpdate?.(goal.status, goalId)).catch(() => { });
|
|
1697
|
+
}
|
|
1698
|
+
catch { /* Observers cannot change the goal outcome. */ }
|
|
1699
|
+
}
|
|
1700
|
+
if (goal.status === 'completed')
|
|
1701
|
+
return goal;
|
|
1702
|
+
if (goal.status === 'failed' || goal.status === 'cancelled') {
|
|
1703
|
+
const reason = goal.end_reason ?? goal.status;
|
|
1704
|
+
throw new errors_2.JobError(`Compute goal ${goal.status}: ${reason}`, goalId, reason, reason, { ...goal });
|
|
1705
|
+
}
|
|
1706
|
+
await this.sleep(HTTP_POLL_BACKOFF_MS[Math.min(polls++, HTTP_POLL_BACKOFF_MS.length - 1)], scope.signal);
|
|
1707
|
+
}
|
|
1708
|
+
};
|
|
1709
|
+
try {
|
|
1710
|
+
return await (0, deadline_1.abortable)(poll(), scope.signal);
|
|
1711
|
+
}
|
|
1712
|
+
catch (error) {
|
|
1713
|
+
if (scope.timedOut())
|
|
1714
|
+
throw new errors_2.JobTimeoutError(goalId, Date.now() - started);
|
|
1715
|
+
throw error;
|
|
1716
|
+
}
|
|
1717
|
+
finally {
|
|
1718
|
+
scope.close();
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1621
1721
|
/**
|
|
1622
1722
|
* List tasks under a compute goal
|
|
1623
1723
|
*
|
|
@@ -1631,11 +1731,11 @@ class OneShot {
|
|
|
1631
1731
|
*/
|
|
1632
1732
|
async getComputeTasks(goalId) {
|
|
1633
1733
|
this.validate(goalId, 'goalId');
|
|
1634
|
-
const response = await
|
|
1734
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}/tasks`, {
|
|
1635
1735
|
headers: await this.signedReadHeaders()
|
|
1636
1736
|
});
|
|
1637
1737
|
if (!response.ok) {
|
|
1638
|
-
throw new errors_2.ToolError('Failed to get compute tasks', response.status, await response.text());
|
|
1738
|
+
throw new errors_2.ToolError('Failed to get compute tasks', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1639
1739
|
}
|
|
1640
1740
|
const json = await response.json();
|
|
1641
1741
|
return json.data;
|
|
@@ -1654,11 +1754,11 @@ class OneShot {
|
|
|
1654
1754
|
*/
|
|
1655
1755
|
async getComputeOutputs(goalId) {
|
|
1656
1756
|
this.validate(goalId, 'goalId');
|
|
1657
|
-
const response = await
|
|
1757
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}/outputs`, {
|
|
1658
1758
|
headers: await this.signedReadHeaders()
|
|
1659
1759
|
});
|
|
1660
1760
|
if (!response.ok) {
|
|
1661
|
-
throw new errors_2.ToolError('Failed to list compute outputs', response.status, await response.text());
|
|
1761
|
+
throw new errors_2.ToolError('Failed to list compute outputs', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1662
1762
|
}
|
|
1663
1763
|
const json = await response.json();
|
|
1664
1764
|
return json.data;
|
|
@@ -1669,11 +1769,11 @@ class OneShot {
|
|
|
1669
1769
|
this.validate(key, 'key');
|
|
1670
1770
|
// The key is a path: keep its slashes, escape everything else.
|
|
1671
1771
|
const path = key.split('/').map(encodeURIComponent).join('/');
|
|
1672
|
-
const response = await
|
|
1772
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}/outputs/${path}`, {
|
|
1673
1773
|
headers: await this.signedReadHeaders()
|
|
1674
1774
|
});
|
|
1675
1775
|
if (!response.ok) {
|
|
1676
|
-
throw new errors_2.ToolError('Failed to read compute output', response.status, await response.text());
|
|
1776
|
+
throw new errors_2.ToolError('Failed to read compute output', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1677
1777
|
}
|
|
1678
1778
|
const json = await response.json();
|
|
1679
1779
|
return json.data;
|
|
@@ -1689,14 +1789,14 @@ class OneShot {
|
|
|
1689
1789
|
*/
|
|
1690
1790
|
async getComputeBudget(goalId) {
|
|
1691
1791
|
this.validate(goalId, 'goalId');
|
|
1692
|
-
const response = await
|
|
1792
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}/budget`, {
|
|
1693
1793
|
headers: await this.signedReadHeaders()
|
|
1694
1794
|
});
|
|
1695
1795
|
if (response.status === 404) {
|
|
1696
1796
|
throw new errors_2.ToolError('Budget not found', 404, 'Budget not found for this goal');
|
|
1697
1797
|
}
|
|
1698
1798
|
if (!response.ok) {
|
|
1699
|
-
throw new errors_2.ToolError('Failed to get compute budget', response.status, await response.text());
|
|
1799
|
+
throw new errors_2.ToolError('Failed to get compute budget', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1700
1800
|
}
|
|
1701
1801
|
const json = await response.json();
|
|
1702
1802
|
return json.data;
|
|
@@ -1712,7 +1812,7 @@ class OneShot {
|
|
|
1712
1812
|
*/
|
|
1713
1813
|
async cancelComputeGoal(goalId, reason) {
|
|
1714
1814
|
this.validate(goalId, 'goalId');
|
|
1715
|
-
const response = await
|
|
1815
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}/cancel`, {
|
|
1716
1816
|
method: 'POST',
|
|
1717
1817
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders('write')) },
|
|
1718
1818
|
body: JSON.stringify({ reason })
|
|
@@ -1726,7 +1826,7 @@ class OneShot {
|
|
|
1726
1826
|
return { goal_id: goalId, status: pending.status ?? 'pending_settlement', message: pending.message };
|
|
1727
1827
|
}
|
|
1728
1828
|
if (!response.ok) {
|
|
1729
|
-
throw new errors_2.ToolError('Failed to cancel compute goal', response.status, await response.text());
|
|
1829
|
+
throw new errors_2.ToolError('Failed to cancel compute goal', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1730
1830
|
}
|
|
1731
1831
|
const json = await response.json();
|
|
1732
1832
|
return json.data;
|
|
@@ -1746,16 +1846,16 @@ class OneShot {
|
|
|
1746
1846
|
async respondToComputeTask(goalId, input) {
|
|
1747
1847
|
this.validate(goalId, 'goalId');
|
|
1748
1848
|
this.validate(input.task_id, 'task_id');
|
|
1749
|
-
const response = await
|
|
1849
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}/respond`, {
|
|
1750
1850
|
method: 'POST',
|
|
1751
1851
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders('write')) },
|
|
1752
1852
|
body: JSON.stringify(input)
|
|
1753
1853
|
});
|
|
1754
1854
|
if (response.status === 404) {
|
|
1755
|
-
throw new errors_2.ToolError('Goal or task not found', 404, await response.text());
|
|
1855
|
+
throw new errors_2.ToolError('Goal or task not found', 404, await response.text(), response.headers.get('retry-after'));
|
|
1756
1856
|
}
|
|
1757
1857
|
if (!response.ok) {
|
|
1758
|
-
throw new errors_2.ToolError('Failed to respond to compute task', response.status, await response.text());
|
|
1858
|
+
throw new errors_2.ToolError('Failed to respond to compute task', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1759
1859
|
}
|
|
1760
1860
|
const json = await response.json();
|
|
1761
1861
|
return json.data;
|
|
@@ -1775,15 +1875,15 @@ class OneShot {
|
|
|
1775
1875
|
*/
|
|
1776
1876
|
async confirmComputeGoal(goalId, input) {
|
|
1777
1877
|
this.validate(goalId, 'goalId');
|
|
1778
|
-
const response = await
|
|
1878
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}/respond`, {
|
|
1779
1879
|
method: 'POST',
|
|
1780
1880
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders('write')) },
|
|
1781
1881
|
body: JSON.stringify({ confirm_done: input.approved, ...(input.response ? { response: input.response } : {}) }),
|
|
1782
1882
|
});
|
|
1783
1883
|
if (response.status === 404)
|
|
1784
|
-
throw new errors_2.ToolError('Goal not found', 404, await response.text());
|
|
1884
|
+
throw new errors_2.ToolError('Goal not found', 404, await response.text(), response.headers.get('retry-after'));
|
|
1785
1885
|
if (!response.ok)
|
|
1786
|
-
throw new errors_2.ToolError('Failed to confirm compute goal', response.status, await response.text());
|
|
1886
|
+
throw new errors_2.ToolError('Failed to confirm compute goal', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1787
1887
|
const json = await response.json();
|
|
1788
1888
|
return json.data;
|
|
1789
1889
|
}
|
|
@@ -1797,13 +1897,13 @@ class OneShot {
|
|
|
1797
1897
|
*/
|
|
1798
1898
|
async pauseComputeGoal(goalId, reason) {
|
|
1799
1899
|
this.validate(goalId, 'goalId');
|
|
1800
|
-
const response = await
|
|
1900
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}/pause`, {
|
|
1801
1901
|
method: 'POST',
|
|
1802
1902
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders('write')) },
|
|
1803
1903
|
body: JSON.stringify({ reason })
|
|
1804
1904
|
});
|
|
1805
1905
|
if (!response.ok) {
|
|
1806
|
-
throw new errors_2.ToolError('Failed to pause compute goal', response.status, await response.text());
|
|
1906
|
+
throw new errors_2.ToolError('Failed to pause compute goal', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1807
1907
|
}
|
|
1808
1908
|
const json = await response.json();
|
|
1809
1909
|
return json.data;
|
|
@@ -1819,13 +1919,13 @@ class OneShot {
|
|
|
1819
1919
|
*/
|
|
1820
1920
|
async resumeComputeGoal(goalId) {
|
|
1821
1921
|
this.validate(goalId, 'goalId');
|
|
1822
|
-
const response = await
|
|
1922
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}/resume`, {
|
|
1823
1923
|
method: 'POST',
|
|
1824
1924
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders('write')) },
|
|
1825
1925
|
body: JSON.stringify({})
|
|
1826
1926
|
});
|
|
1827
1927
|
if (!response.ok) {
|
|
1828
|
-
throw new errors_2.ToolError('Failed to resume compute goal', response.status, await response.text());
|
|
1928
|
+
throw new errors_2.ToolError('Failed to resume compute goal', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1829
1929
|
}
|
|
1830
1930
|
const json = await response.json();
|
|
1831
1931
|
return json.data;
|
|
@@ -1955,11 +2055,11 @@ class OneShot {
|
|
|
1955
2055
|
*/
|
|
1956
2056
|
async spendBreakdown(options) {
|
|
1957
2057
|
const qs = this.buildQuery({ period: options?.period || undefined });
|
|
1958
|
-
const response = await
|
|
2058
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/analytics/spend/breakdown${qs ? `?${qs}` : ''}`, {
|
|
1959
2059
|
headers: await this.signedReadHeaders()
|
|
1960
2060
|
});
|
|
1961
2061
|
if (!response.ok) {
|
|
1962
|
-
throw new errors_2.ToolError('Failed to get spend breakdown', response.status, await response.text());
|
|
2062
|
+
throw new errors_2.ToolError('Failed to get spend breakdown', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1963
2063
|
}
|
|
1964
2064
|
return response.json();
|
|
1965
2065
|
}
|
|
@@ -1974,11 +2074,11 @@ class OneShot {
|
|
|
1974
2074
|
*/
|
|
1975
2075
|
async rocs(options) {
|
|
1976
2076
|
const qs = this.buildQuery({ period: options?.period || undefined });
|
|
1977
|
-
const response = await
|
|
2077
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/analytics/rocs${qs ? `?${qs}` : ''}`, {
|
|
1978
2078
|
headers: await this.signedReadHeaders()
|
|
1979
2079
|
});
|
|
1980
2080
|
if (!response.ok) {
|
|
1981
|
-
throw new errors_2.ToolError('Failed to get RoCS', response.status, await response.text());
|
|
2081
|
+
throw new errors_2.ToolError('Failed to get RoCS', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1982
2082
|
}
|
|
1983
2083
|
return response.json();
|
|
1984
2084
|
}
|
|
@@ -2010,11 +2110,11 @@ class OneShot {
|
|
|
2010
2110
|
since: toIso(options?.since),
|
|
2011
2111
|
until: toIso(options?.until),
|
|
2012
2112
|
});
|
|
2013
|
-
const response = await
|
|
2113
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/analytics/receipts${qs ? `?${qs}` : ''}`, {
|
|
2014
2114
|
headers: await this.signedReadHeaders()
|
|
2015
2115
|
});
|
|
2016
2116
|
if (!response.ok) {
|
|
2017
|
-
throw new errors_2.ToolError('Failed to list receipts', response.status, await response.text());
|
|
2117
|
+
throw new errors_2.ToolError('Failed to list receipts', response.status, await response.text(), response.headers.get('retry-after'));
|
|
2018
2118
|
}
|
|
2019
2119
|
return response.json();
|
|
2020
2120
|
}
|
|
@@ -2047,19 +2147,19 @@ class OneShot {
|
|
|
2047
2147
|
this.validate(valueTag.type, 'valueTag.type');
|
|
2048
2148
|
// Cadence-level: route to the outcome ledger by correlation key.
|
|
2049
2149
|
if (typeof ref === 'object' && ref.goalId && !ref.receiptId && !ref.requestId) {
|
|
2050
|
-
const response = await
|
|
2150
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/analytics/outcomes`, {
|
|
2051
2151
|
method: 'POST',
|
|
2052
2152
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders('write')) },
|
|
2053
2153
|
body: JSON.stringify({ goal_id: ref.goalId, ...valueTag }),
|
|
2054
2154
|
});
|
|
2055
2155
|
if (!response.ok) {
|
|
2056
|
-
throw new errors_2.ToolError('Failed to record outcome value', response.status, await response.text());
|
|
2156
|
+
throw new errors_2.ToolError('Failed to record outcome value', response.status, await response.text(), response.headers.get('retry-after'));
|
|
2057
2157
|
}
|
|
2058
2158
|
return;
|
|
2059
2159
|
}
|
|
2060
2160
|
const id = typeof ref === 'string' ? ref : (ref.receiptId ?? ref.requestId);
|
|
2061
2161
|
this.validate(id, 'receiptId/requestId/goalId');
|
|
2062
|
-
const response = await
|
|
2162
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/analytics/receipts/${id}/value`, {
|
|
2063
2163
|
method: 'PATCH',
|
|
2064
2164
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders('write')) },
|
|
2065
2165
|
body: JSON.stringify(valueTag)
|
|
@@ -2068,7 +2168,7 @@ class OneShot {
|
|
|
2068
2168
|
throw new errors_2.ToolError('Receipt not found', 404, 'Receipt not found or not owned by this agent');
|
|
2069
2169
|
}
|
|
2070
2170
|
if (!response.ok) {
|
|
2071
|
-
throw new errors_2.ToolError('Failed to tag receipt value', response.status, await response.text());
|
|
2171
|
+
throw new errors_2.ToolError('Failed to tag receipt value', response.status, await response.text(), response.headers.get('retry-after'));
|
|
2072
2172
|
}
|
|
2073
2173
|
}
|
|
2074
2174
|
/**
|
|
@@ -2090,11 +2190,11 @@ class OneShot {
|
|
|
2090
2190
|
*/
|
|
2091
2191
|
async rocsByGoal(options) {
|
|
2092
2192
|
const qs = this.buildQuery({ period: options?.period || undefined, goal_id: options?.goalId || undefined });
|
|
2093
|
-
const response = await
|
|
2193
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/analytics/rocs/by-goal${qs ? `?${qs}` : ''}`, {
|
|
2094
2194
|
headers: await this.signedReadHeaders()
|
|
2095
2195
|
});
|
|
2096
2196
|
if (!response.ok) {
|
|
2097
|
-
throw new errors_2.ToolError('Failed to get RoCS by goal', response.status, await response.text());
|
|
2197
|
+
throw new errors_2.ToolError('Failed to get RoCS by goal', response.status, await response.text(), response.headers.get('retry-after'));
|
|
2098
2198
|
}
|
|
2099
2199
|
return response.json();
|
|
2100
2200
|
}
|
|
@@ -2135,7 +2235,7 @@ class OneShot {
|
|
|
2135
2235
|
const headers = await this.signedReadHeaders(`staging:${method}:${path}:${digest}`);
|
|
2136
2236
|
if (!headers['x-agent-proof'])
|
|
2137
2237
|
throw new Error('Could not sign staging application');
|
|
2138
|
-
const response = await
|
|
2238
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/staging${path}`, {
|
|
2139
2239
|
method, headers: { ...headers, 'Content-Type': 'application/json' },
|
|
2140
2240
|
signal: AbortSignal.timeout(10000),
|
|
2141
2241
|
...(body ? { body: JSON.stringify(body) } : {}),
|
|
@@ -2170,7 +2270,7 @@ class OneShot {
|
|
|
2170
2270
|
return base;
|
|
2171
2271
|
try {
|
|
2172
2272
|
const agent = this.provider.address;
|
|
2173
|
-
const issuedAt = Math.floor(Date.now() / 1000);
|
|
2273
|
+
const issuedAt = Math.floor(Date.now() / 1000) + (domainName === 'OneShot Agent Auth' ? this.proofClockOffsetSec : 0);
|
|
2174
2274
|
const nonce = ethers_1.ethers.hexlify(ethers_1.ethers.randomBytes(32));
|
|
2175
2275
|
const signature = await this.provider.signTypedData(
|
|
2176
2276
|
// `domainName` lets a different origin (e.g. Soul Hunt, which keeps
|
|
@@ -2205,6 +2305,45 @@ class OneShot {
|
|
|
2205
2305
|
return base;
|
|
2206
2306
|
}
|
|
2207
2307
|
}
|
|
2308
|
+
/** Retry a clock-window refusal once, before the API has dispatched the request.
|
|
2309
|
+
* Only this API's proof clock changes; payments and other services keep their own clocks.
|
|
2310
|
+
* Callers supply replayable JSON bodies and retain their original abort/deadline signal.
|
|
2311
|
+
*/
|
|
2312
|
+
async fetchApi(url, init) {
|
|
2313
|
+
const response = await fetch(url, init);
|
|
2314
|
+
const headers = new Headers(init.headers);
|
|
2315
|
+
const proof = headers.get('x-agent-proof');
|
|
2316
|
+
if (response.status !== 401 || !proof || this._accessToken)
|
|
2317
|
+
return response;
|
|
2318
|
+
const origin = new URL(this.baseUrl).origin;
|
|
2319
|
+
if (new URL(url).origin !== origin || (response.url && new URL(response.url).origin !== origin))
|
|
2320
|
+
return response;
|
|
2321
|
+
let scope;
|
|
2322
|
+
let serverTime;
|
|
2323
|
+
try {
|
|
2324
|
+
const body = await response.clone().json();
|
|
2325
|
+
if (body?.error !== 'proof_required' ||
|
|
2326
|
+
!['proof expired', 'proof issued in the future'].some(reason => body.message === `This endpoint requires a signed agent proof (x-agent-proof): ${reason}`) ||
|
|
2327
|
+
typeof body.server_time !== 'number' || !Number.isSafeInteger(body.server_time) || body.server_time <= 0)
|
|
2328
|
+
return response;
|
|
2329
|
+
const sent = JSON.parse(typeof Buffer !== 'undefined' ? Buffer.from(proof, 'base64').toString('utf-8') : atob(proof));
|
|
2330
|
+
if (typeof sent.scope !== 'string')
|
|
2331
|
+
return response;
|
|
2332
|
+
scope = sent.scope;
|
|
2333
|
+
serverTime = body.server_time;
|
|
2334
|
+
}
|
|
2335
|
+
catch {
|
|
2336
|
+
return response;
|
|
2337
|
+
}
|
|
2338
|
+
init.signal?.throwIfAborted();
|
|
2339
|
+
this.proofClockOffsetSec = serverTime - Math.floor(Date.now() / 1000);
|
|
2340
|
+
const refreshed = await this.signedReadHeaders(scope);
|
|
2341
|
+
init.signal?.throwIfAborted();
|
|
2342
|
+
if (!refreshed['x-agent-proof'])
|
|
2343
|
+
return response;
|
|
2344
|
+
headers.set('x-agent-proof', refreshed['x-agent-proof']);
|
|
2345
|
+
return fetch(url, { ...init, headers });
|
|
2346
|
+
}
|
|
2208
2347
|
/** Build a query string, skipping null/undefined values. Pass falsy-but-valid
|
|
2209
2348
|
* values (0, '') as undefined at the call site to match prior `if (x)` guards. */
|
|
2210
2349
|
buildQuery(params) {
|
|
@@ -2248,7 +2387,7 @@ class OneShot {
|
|
|
2248
2387
|
body.alert_email = this._alertEmail;
|
|
2249
2388
|
let response;
|
|
2250
2389
|
try {
|
|
2251
|
-
response = await
|
|
2390
|
+
response = await this.fetchApi(`${this.baseUrl}/v1/agents/me/budgets`, {
|
|
2252
2391
|
method: 'PUT',
|
|
2253
2392
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders('write')) },
|
|
2254
2393
|
body: JSON.stringify(body),
|
|
@@ -2259,7 +2398,7 @@ class OneShot {
|
|
|
2259
2398
|
}
|
|
2260
2399
|
if (!response.ok) {
|
|
2261
2400
|
const text = await response.text();
|
|
2262
|
-
throw new errors_2.BudgetSyncError(`Could not sync spend budget (${response.status}): ${text}`, response.status, text);
|
|
2401
|
+
throw new errors_2.BudgetSyncError(`Could not sync spend budget (${response.status}): ${text}`, response.status, text, response.headers.get('retry-after'));
|
|
2263
2402
|
}
|
|
2264
2403
|
this.log('Budget synced');
|
|
2265
2404
|
})();
|
|
@@ -2301,7 +2440,7 @@ class OneShot {
|
|
|
2301
2440
|
*/
|
|
2302
2441
|
/** Signed-proof JSON request to a free agent route; returns `data`. Errors map through failFromResponse. */
|
|
2303
2442
|
async agentJson(path, method, body, scope = 'read') {
|
|
2304
|
-
const response = await
|
|
2443
|
+
const response = await this.fetchApi(`${this.baseUrl}${path}`, {
|
|
2305
2444
|
method,
|
|
2306
2445
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders(scope)) },
|
|
2307
2446
|
...(body === undefined || method === 'GET' ? {} : { body: JSON.stringify(body) }),
|
|
@@ -2313,11 +2452,11 @@ class OneShot {
|
|
|
2313
2452
|
return (json && typeof json === 'object' && 'data' in json ? json.data : json);
|
|
2314
2453
|
}
|
|
2315
2454
|
async budgets() {
|
|
2316
|
-
const response = await
|
|
2455
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/agents/me/budgets`, {
|
|
2317
2456
|
headers: await this.signedReadHeaders(),
|
|
2318
2457
|
});
|
|
2319
2458
|
if (!response.ok) {
|
|
2320
|
-
throw new errors_2.ToolError('Failed to fetch budgets', response.status, await response.text());
|
|
2459
|
+
throw new errors_2.ToolError('Failed to fetch budgets', response.status, await response.text(), response.headers.get('retry-after'));
|
|
2321
2460
|
}
|
|
2322
2461
|
const body = await response.json();
|
|
2323
2462
|
return (body.data ?? body);
|
|
@@ -2340,24 +2479,24 @@ class OneShot {
|
|
|
2340
2479
|
*/
|
|
2341
2480
|
async createAccessToken(opts = {}) {
|
|
2342
2481
|
this.assertWalletSession('mint access tokens');
|
|
2343
|
-
const response = await
|
|
2482
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/agents/me/access-tokens`, {
|
|
2344
2483
|
method: 'POST',
|
|
2345
2484
|
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders('write')) },
|
|
2346
2485
|
body: JSON.stringify(opts.name !== undefined ? { name: opts.name } : {}),
|
|
2347
2486
|
});
|
|
2348
2487
|
if (!response.ok)
|
|
2349
|
-
throw new errors_2.ToolError('Failed to create access token', response.status, await response.text());
|
|
2488
|
+
throw new errors_2.ToolError('Failed to create access token', response.status, await response.text(), response.headers.get('retry-after'));
|
|
2350
2489
|
const body = await response.json();
|
|
2351
2490
|
return body.data;
|
|
2352
2491
|
}
|
|
2353
2492
|
/** List this agent's access tokens (prefix, label, last use, spend today — never the token). */
|
|
2354
2493
|
async listAccessTokens(opts = {}) {
|
|
2355
2494
|
this.assertWalletSession('list access tokens');
|
|
2356
|
-
const response = await
|
|
2495
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/agents/me/access-tokens${opts.activeOnly ? '?active=true' : ''}`, {
|
|
2357
2496
|
headers: await this.signedReadHeaders(),
|
|
2358
2497
|
});
|
|
2359
2498
|
if (!response.ok)
|
|
2360
|
-
throw new errors_2.ToolError('Failed to list access tokens', response.status, await response.text());
|
|
2499
|
+
throw new errors_2.ToolError('Failed to list access tokens', response.status, await response.text(), response.headers.get('retry-after'));
|
|
2361
2500
|
const body = await response.json();
|
|
2362
2501
|
return body.data.tokens;
|
|
2363
2502
|
}
|
|
@@ -2365,12 +2504,12 @@ class OneShot {
|
|
|
2365
2504
|
async revokeAccessToken(id) {
|
|
2366
2505
|
this.assertWalletSession('revoke access tokens');
|
|
2367
2506
|
this.validate(id, 'id');
|
|
2368
|
-
const response = await
|
|
2507
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/agents/me/access-tokens/${encodeURIComponent(id)}`, {
|
|
2369
2508
|
method: 'DELETE',
|
|
2370
2509
|
headers: await this.signedReadHeaders('write'),
|
|
2371
2510
|
});
|
|
2372
2511
|
if (!response.ok)
|
|
2373
|
-
throw new errors_2.ToolError('Failed to revoke access token', response.status, await response.text());
|
|
2512
|
+
throw new errors_2.ToolError('Failed to revoke access token', response.status, await response.text(), response.headers.get('retry-after'));
|
|
2374
2513
|
const body = await response.json();
|
|
2375
2514
|
return body.data;
|
|
2376
2515
|
}
|
|
@@ -2429,7 +2568,7 @@ class OneShot {
|
|
|
2429
2568
|
const budget = response.status === 403 ? this.parseBudgetRejection(text) : undefined;
|
|
2430
2569
|
if (budget)
|
|
2431
2570
|
throw budget;
|
|
2432
|
-
throw new errors_2.ToolError(message, response.status, text);
|
|
2571
|
+
throw new errors_2.ToolError(message, response.status, text, response.headers.get('retry-after'));
|
|
2433
2572
|
}
|
|
2434
2573
|
/**
|
|
2435
2574
|
* Map a 402 `insufficient_credits` body (access-token sessions) onto a typed
|
|
@@ -2493,12 +2632,12 @@ class OneShot {
|
|
|
2493
2632
|
throw new errors_2.ToolError(`Could not resolve the access token's agent (network): ${err}`, 0, String(err));
|
|
2494
2633
|
}
|
|
2495
2634
|
if (!response.ok) {
|
|
2496
|
-
throw new errors_2.ToolError('Could not resolve the access token\'s agent', response.status, await response.text());
|
|
2635
|
+
throw new errors_2.ToolError('Could not resolve the access token\'s agent', response.status, await response.text(), response.headers.get('retry-after'));
|
|
2497
2636
|
}
|
|
2498
2637
|
const json = await response.json();
|
|
2499
2638
|
const address = json.data?.wallet_address;
|
|
2500
2639
|
if (!address)
|
|
2501
|
-
throw new errors_2.ToolError('GET /v1/agents/me returned no wallet_address', response.status, JSON.stringify(json));
|
|
2640
|
+
throw new errors_2.ToolError('GET /v1/agents/me returned no wallet_address', response.status, JSON.stringify(json), response.headers.get('retry-after'));
|
|
2502
2641
|
return address;
|
|
2503
2642
|
}
|
|
2504
2643
|
/**
|
|
@@ -2601,7 +2740,7 @@ class OneShot {
|
|
|
2601
2740
|
const headers = signed ? await this.signedReadHeaders() : undefined;
|
|
2602
2741
|
if (scope.signal.aborted)
|
|
2603
2742
|
throw new errors_2.OneShotError('Read deadline exceeded');
|
|
2604
|
-
const response = await
|
|
2743
|
+
const response = await this.fetchApi(`${this.baseUrl}${path}`, { headers, signal: scope.signal });
|
|
2605
2744
|
if (!response.ok && !(allowDegraded && response.status === 503))
|
|
2606
2745
|
await this.failFromResponse('Reliability read failed', response);
|
|
2607
2746
|
return await response.json();
|
|
@@ -2620,7 +2759,7 @@ class OneShot {
|
|
|
2620
2759
|
}
|
|
2621
2760
|
async executeToolRequest(endpoint, options, quoteId) {
|
|
2622
2761
|
// 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);
|
|
2762
|
+
const reliable = /(?:^|\/)(lookup\/person-(profile|email|email-status)|lookup\/place-permits|solve|predict|linkedin\/(reply|message|profile-view|react|invite(?:\/withdraw)?))$/.test(endpoint);
|
|
2624
2763
|
const key = options.idempotencyKey ?? (reliable ? ethers_1.ethers.hexlify(ethers_1.ethers.randomBytes(16)) : undefined);
|
|
2625
2764
|
if (options.totalTimeoutMs !== undefined && (!Number.isFinite(options.totalTimeoutMs) || options.totalTimeoutMs <= 0)) {
|
|
2626
2765
|
throw new errors_2.ValidationError('totalTimeoutMs must be positive', 'totalTimeoutMs');
|
|
@@ -2663,7 +2802,7 @@ class OneShot {
|
|
|
2663
2802
|
this.log('Memo truncated to 1000 chars');
|
|
2664
2803
|
}
|
|
2665
2804
|
}
|
|
2666
|
-
else if (!endpoint.endsWith('/v1/tools/solve') && !endpoint.includes('/inbox') && !endpoint.includes('/notifications') && !endpoint.includes('/balance')) {
|
|
2805
|
+
else if (!endpoint.endsWith('/v1/tools/solve') && !endpoint.endsWith('/v1/tools/predict') && !endpoint.includes('/inbox') && !endpoint.includes('/notifications') && !endpoint.includes('/balance')) {
|
|
2667
2806
|
this.log('No memo provided — consider adding a reason for audit trail');
|
|
2668
2807
|
}
|
|
2669
2808
|
if (payload.decisionContext !== undefined) {
|
|
@@ -2692,7 +2831,7 @@ class OneShot {
|
|
|
2692
2831
|
context.phase = 'submission';
|
|
2693
2832
|
let response = await this.makeRequest(endpoint, payload, undefined, quoteId, signal, undefined, extraHeaders);
|
|
2694
2833
|
// Handle 402 Payment Required
|
|
2695
|
-
if (response.status === 402 && /\/v1\/tools\/(solve|find\/person|lookup\/person-(profile|dossier))$/.test(endpoint)) {
|
|
2834
|
+
if (response.status === 402 && /\/v1\/tools\/(solve|predict|find\/person|lookup\/person-(profile|dossier))$/.test(endpoint)) {
|
|
2696
2835
|
({ execResp: response } = await this.runQuoteToPay({
|
|
2697
2836
|
endpoint, payload, signal, maxCost, quoteResponse: response, extraHeaders,
|
|
2698
2837
|
expectMsg: 'Expected a tool quote', totalOf: quote => quote.total,
|
|
@@ -2813,6 +2952,15 @@ class OneShot {
|
|
|
2813
2952
|
* `wait: false`, or to resume waiting after a client restart. Delivery is the
|
|
2814
2953
|
* same as for blocking calls: WebSocket push when the server offers it, HTTP
|
|
2815
2954
|
* polling of `GET /v1/requests/:id` as the source of truth.
|
|
2955
|
+
* For compute goals, use `waitForComputeGoal(goal_id)` instead: their creation
|
|
2956
|
+
* request_id does not track progress. `onStatusUpdate` observes distinct job
|
|
2957
|
+
* statuses, including the terminal status before settlement.
|
|
2958
|
+
* With waitForPhones, completed is deferred until the phone wait returns.
|
|
2959
|
+
* Callback errors are ignored; returned promises are not awaited.
|
|
2960
|
+
*
|
|
2961
|
+
* `signal` ends the local wait only (in-flight fetch aborted, poll timer
|
|
2962
|
+
* cleared, rejects with `OneShotError('Operation cancelled')`); the job
|
|
2963
|
+
* itself keeps running and can be waited for again.
|
|
2816
2964
|
*/
|
|
2817
2965
|
async waitForResult(requestId, options = {}) {
|
|
2818
2966
|
this.validate(requestId, 'requestId');
|
|
@@ -2825,6 +2973,9 @@ class OneShot {
|
|
|
2825
2973
|
// swallows frames) costs nothing beyond the poll cadence. Previously the
|
|
2826
2974
|
// client waited 60% of its timeout on the socket before polling at all —
|
|
2827
2975
|
// in prod that was ~72s of dead air on every call while the push never came.
|
|
2976
|
+
// An already-aborted signal ends the wait before any proof is signed or sent.
|
|
2977
|
+
if (signal?.aborted)
|
|
2978
|
+
throw new errors_2.OneShotError('Operation cancelled');
|
|
2828
2979
|
const inner = new AbortController();
|
|
2829
2980
|
const onOuterAbort = () => inner.abort();
|
|
2830
2981
|
if (signal?.aborted)
|
|
@@ -2832,12 +2983,24 @@ class OneShot {
|
|
|
2832
2983
|
else
|
|
2833
2984
|
signal?.addEventListener('abort', onOuterAbort, { once: true });
|
|
2834
2985
|
const wait = { pushConfirmed: false, lastStatus: undefined, via: undefined };
|
|
2835
|
-
|
|
2986
|
+
const notify = (status) => {
|
|
2987
|
+
// Progress observers must not alter job outcomes or trigger HTTP retries.
|
|
2988
|
+
try {
|
|
2989
|
+
Promise.resolve(onStatusUpdate?.(status, requestId)).catch(() => { });
|
|
2990
|
+
}
|
|
2991
|
+
catch { /* Ignore synchronous observer errors too. */ }
|
|
2992
|
+
};
|
|
2993
|
+
// Latch terminal transport status immediately, but defer completion for
|
|
2994
|
+
// callers waiting on phones so the losing branch cannot emit stale states.
|
|
2836
2995
|
const emit = (status) => {
|
|
2996
|
+
if (wait.lastStatus === 'completed' || wait.lastStatus === 'failed')
|
|
2997
|
+
return;
|
|
2837
2998
|
if (status === wait.lastStatus)
|
|
2838
2999
|
return;
|
|
2839
3000
|
wait.lastStatus = status;
|
|
2840
|
-
|
|
3001
|
+
if (status === 'completed' && phoneOpts?.waitForPhones)
|
|
3002
|
+
return;
|
|
3003
|
+
notify(status);
|
|
2841
3004
|
};
|
|
2842
3005
|
const startedAt = Date.now();
|
|
2843
3006
|
const wsBranch = this.waitViaWebSocket(requestId, inner.signal, emit, wait).catch((err) => {
|
|
@@ -2873,8 +3036,10 @@ class OneShot {
|
|
|
2873
3036
|
// hit a transient error before any successful refresh, we return what we
|
|
2874
3037
|
// already have (with phones_pending=true still set so consumers know).
|
|
2875
3038
|
if (phoneOpts?.waitForPhones && this._isPhonesPending(result)) {
|
|
2876
|
-
|
|
3039
|
+
result = await this._pollForPhones(requestId, phoneOpts.phoneTimeoutSec ?? 360, signal, result);
|
|
2877
3040
|
}
|
|
3041
|
+
if (phoneOpts?.waitForPhones)
|
|
3042
|
+
notify('completed');
|
|
2878
3043
|
return result;
|
|
2879
3044
|
}
|
|
2880
3045
|
/**
|
|
@@ -2918,7 +3083,7 @@ class OneShot {
|
|
|
2918
3083
|
if (signal?.aborted)
|
|
2919
3084
|
throw new errors_2.OneShotError('Operation cancelled');
|
|
2920
3085
|
try {
|
|
2921
|
-
const resp = await
|
|
3086
|
+
const resp = await this.fetchApi(`${this.baseUrl}/v1/requests/${requestId}`, {
|
|
2922
3087
|
headers: await this.signedReadHeaders(),
|
|
2923
3088
|
signal,
|
|
2924
3089
|
});
|
|
@@ -2927,7 +3092,7 @@ class OneShot {
|
|
|
2927
3092
|
// whatever we last had so consumers don't lose the sync result.
|
|
2928
3093
|
if (lastResult !== undefined)
|
|
2929
3094
|
return lastResult;
|
|
2930
|
-
throw new errors_2.ToolError('Failed to check job status', resp.status, await resp.text());
|
|
3095
|
+
throw new errors_2.ToolError('Failed to check job status', resp.status, await resp.text(), resp.headers.get('retry-after'));
|
|
2931
3096
|
}
|
|
2932
3097
|
const job = await resp.json();
|
|
2933
3098
|
lastResult = (job.result ?? job);
|
|
@@ -2936,6 +3101,8 @@ class OneShot {
|
|
|
2936
3101
|
}
|
|
2937
3102
|
}
|
|
2938
3103
|
catch (err) {
|
|
3104
|
+
if (signal?.aborted)
|
|
3105
|
+
throw new errors_2.OneShotError('Operation cancelled');
|
|
2939
3106
|
if (err instanceof errors_2.OneShotError)
|
|
2940
3107
|
throw err;
|
|
2941
3108
|
if (lastResult !== undefined)
|
|
@@ -3031,6 +3198,7 @@ class OneShot {
|
|
|
3031
3198
|
wait.pushConfirmed = true;
|
|
3032
3199
|
this.log('WebSocket push confirmed — relaxing HTTP polling');
|
|
3033
3200
|
}
|
|
3201
|
+
emit(String(msg.status));
|
|
3034
3202
|
if (msg.status === 'completed') {
|
|
3035
3203
|
this.log('Job completed (WebSocket)');
|
|
3036
3204
|
settle(() => {
|
|
@@ -3050,9 +3218,6 @@ class OneShot {
|
|
|
3050
3218
|
reject(new errors_2.JobError(`Job failed: ${msg.error ?? 'Unknown'}`, requestId, String(msg.error ?? 'Unknown'), msg.error_code, msg.result));
|
|
3051
3219
|
});
|
|
3052
3220
|
}
|
|
3053
|
-
else {
|
|
3054
|
-
emit(String(msg.status));
|
|
3055
|
-
}
|
|
3056
3221
|
}
|
|
3057
3222
|
catch {
|
|
3058
3223
|
// Ignore malformed messages
|
|
@@ -3088,6 +3253,9 @@ class OneShot {
|
|
|
3088
3253
|
catch (error) {
|
|
3089
3254
|
if (scope.timedOut())
|
|
3090
3255
|
throw new errors_2.JobTimeoutError(requestId, Date.now() - started);
|
|
3256
|
+
// An abort mid-fetch surfaces as the transport's AbortError; report it as the cancellation it is.
|
|
3257
|
+
if (signal?.aborted && !(error instanceof errors_2.OneShotError))
|
|
3258
|
+
throw new errors_2.OneShotError('Operation cancelled');
|
|
3091
3259
|
throw error;
|
|
3092
3260
|
}
|
|
3093
3261
|
finally {
|
|
@@ -3104,7 +3272,7 @@ class OneShot {
|
|
|
3104
3272
|
if (signal?.aborted)
|
|
3105
3273
|
throw new errors_2.OneShotError('Operation cancelled');
|
|
3106
3274
|
try {
|
|
3107
|
-
const resp = await
|
|
3275
|
+
const resp = await this.fetchApi(`${this.baseUrl}/v1/requests/${requestId}`, {
|
|
3108
3276
|
headers: await this.signedReadHeaders(),
|
|
3109
3277
|
signal
|
|
3110
3278
|
});
|
|
@@ -3115,9 +3283,10 @@ class OneShot {
|
|
|
3115
3283
|
if (resp.status >= 500 || resp.status === 429) {
|
|
3116
3284
|
throw new Error(`Poll returned ${resp.status}: ${body.slice(0, 200)}`);
|
|
3117
3285
|
}
|
|
3118
|
-
throw new errors_2.ToolError('Failed to check job status', resp.status, body);
|
|
3286
|
+
throw new errors_2.ToolError('Failed to check job status', resp.status, body, resp.headers.get('retry-after'));
|
|
3119
3287
|
}
|
|
3120
3288
|
const job = await resp.json();
|
|
3289
|
+
emit?.(String(job.status));
|
|
3121
3290
|
if (job.status === 'completed') {
|
|
3122
3291
|
this.log('Job completed');
|
|
3123
3292
|
if (wait)
|
|
@@ -3140,7 +3309,6 @@ class OneShot {
|
|
|
3140
3309
|
wait.via = 'http';
|
|
3141
3310
|
throw new errors_2.JobError(`Job failed: ${job.error ?? 'Unknown'}`, requestId, String(job.error ?? 'Unknown'), job.error_code, job.result);
|
|
3142
3311
|
}
|
|
3143
|
-
emit?.(String(job.status));
|
|
3144
3312
|
retries = 0;
|
|
3145
3313
|
const interval = wait?.pushConfirmed
|
|
3146
3314
|
? HTTP_POLL_RELAXED_MS
|
|
@@ -3223,7 +3391,7 @@ class OneShot {
|
|
|
3223
3391
|
// alone (401 proof_required).
|
|
3224
3392
|
const unsignedStub = payment?.payload?.signature === '0x';
|
|
3225
3393
|
const askedForProof = !!extraHeaders?.['x-agent-proof'];
|
|
3226
|
-
const proofScope = /(lookup\/person-(profile|email|email-status)|lookup\/place-permits|physical-mail\/send|solve)$/.test(endpoint)
|
|
3394
|
+
const proofScope = /(lookup\/person-(profile|email|email-status)|lookup\/place-permits|physical-mail\/send|solve|predict)$/.test(endpoint)
|
|
3227
3395
|
? 'submit'
|
|
3228
3396
|
: askedForProof ? 'read' : 'write';
|
|
3229
3397
|
const headers = {
|
|
@@ -3251,7 +3419,7 @@ class OneShot {
|
|
|
3251
3419
|
transportDeadline?.signal.addEventListener('abort', () => transportDeadline.close(), { once: true });
|
|
3252
3420
|
if (fetchSignal?.aborted)
|
|
3253
3421
|
throw new errors_2.OneShotError('Operation cancelled');
|
|
3254
|
-
const work =
|
|
3422
|
+
const work = this.fetchApi(`${this.baseUrl}${endpoint}`, {
|
|
3255
3423
|
method: 'POST', headers, body: JSON.stringify(data), signal: fetchSignal,
|
|
3256
3424
|
});
|
|
3257
3425
|
try {
|
|
@@ -3408,7 +3576,7 @@ class OneShot {
|
|
|
3408
3576
|
}
|
|
3409
3577
|
catch (err) {
|
|
3410
3578
|
// 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))
|
|
3579
|
+
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
3580
|
this.releaseUsdcReservation(signed.reservation);
|
|
3413
3581
|
throw err;
|
|
3414
3582
|
}
|