@oneshot-agent/sdk 0.46.0 → 0.48.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 +1 -1
- package/dist/approvals.d.ts +2 -2
- package/dist/approvals.d.ts.map +1 -1
- package/dist/approvals.js +2 -1
- package/dist/approvals.js.map +1 -1
- package/dist/hunts.d.ts +38 -0
- package/dist/hunts.d.ts.map +1 -0
- package/dist/hunts.js +121 -0
- package/dist/hunts.js.map +1 -0
- package/dist/index.d.ts +46 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +122 -7
- package/dist/index.js.map +1 -1
- package/dist/physical-mail.d.ts +5 -0
- package/dist/physical-mail.d.ts.map +1 -1
- package/dist/physical-mail.js.map +1 -1
- package/dist/types.d.ts +115 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +17 -2
package/dist/index.js
CHANGED
|
@@ -37,6 +37,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
37
37
|
})();
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.OneShot = exports.executeSwap = exports.getSwapQuote = exports.ReadOnlyWalletProvider = exports.CdpWalletProvider = exports.EthersWalletProvider = void 0;
|
|
40
|
+
const hunts_1 = require("./hunts");
|
|
41
|
+
__exportStar(require("./hunts"), exports);
|
|
40
42
|
const physical_mail_1 = require("./physical-mail");
|
|
41
43
|
__exportStar(require("./physical-mail"), exports);
|
|
42
44
|
__exportStar(require("./approvals"), exports);
|
|
@@ -63,7 +65,7 @@ const approvals_1 = require("./approvals");
|
|
|
63
65
|
// stay off this public surface (see tests/unit/sdk-provider-cost-removed.test.ts).
|
|
64
66
|
// Import from the `@oneshot-agent/sdk/receipt` subpath instead.
|
|
65
67
|
// Keep in sync with package.json `version`. Guarded by version.test.ts.
|
|
66
|
-
const SDK_VERSION = '0.
|
|
68
|
+
const SDK_VERSION = '0.48.0';
|
|
67
69
|
/** HTTP poll cadence while push is unconfirmed: fast first checks, settling at 2s. */
|
|
68
70
|
const HTTP_POLL_BACKOFF_MS = [300, 600, 1000, 2000];
|
|
69
71
|
/** HTTP poll cadence once the WebSocket has delivered for this request. */
|
|
@@ -187,6 +189,7 @@ class OneShot {
|
|
|
187
189
|
* For CDP wallets, use OneShot.create() instead.
|
|
188
190
|
*/
|
|
189
191
|
constructor(config, walletProvider) {
|
|
192
|
+
this.hunts = new hunts_1.Hunts(this);
|
|
190
193
|
this.physicalMail = new physical_mail_1.PhysicalMail(async (path, method, body, mime) => {
|
|
191
194
|
const response = await fetch(`${this.baseUrl}/v1/tools/physical-mail${path}`, {
|
|
192
195
|
method, headers: { ...await this.signedReadHeaders(method === 'GET' ? 'read' : 'write'), 'Content-Type': mime ?? 'application/json' },
|
|
@@ -208,6 +211,7 @@ class OneShot {
|
|
|
208
211
|
/** ETH mode: serializes read → decide → swap → reserve per instance (also prevents concurrent swap nonce races). */
|
|
209
212
|
this._usdcLock = Promise.resolve();
|
|
210
213
|
this.baseUrl = config.baseUrl ?? BASE_URL;
|
|
214
|
+
this.huntUrl = config.huntUrl;
|
|
211
215
|
this.debug = config.debug ?? false;
|
|
212
216
|
this.logger = config.logger ?? console.log;
|
|
213
217
|
this._currency = config.currency ?? 'USDC';
|
|
@@ -313,8 +317,24 @@ class OneShot {
|
|
|
313
317
|
const fromAddress = useRotation
|
|
314
318
|
? undefined
|
|
315
319
|
: `${options.from_mailbox ?? 'agent'}@${options.from_domain}`;
|
|
320
|
+
// Confirming a new-domain registration (#1070): the caller passes back the
|
|
321
|
+
// quote_id of the `confirmation_required` result it was shown. That quote
|
|
322
|
+
// is sent as-is — no second quote — so the server charges the stored total
|
|
323
|
+
// of the quote the user saw (x402-legacy validates the id and takes the
|
|
324
|
+
// amount from the stored quote). A bare `true` is not a confirmation: it
|
|
325
|
+
// names no amount, so it falls through to the quote-only path below. An
|
|
326
|
+
// expired or already-used quote is refused by the server, and the next
|
|
327
|
+
// unconfirmed call produces a fresh quote to show again.
|
|
328
|
+
const confirmedQuote = typeof options.confirmNewDomain === 'string' && options.confirmNewDomain.trim()
|
|
329
|
+
? options.confirmNewDomain.trim() : undefined;
|
|
330
|
+
if (confirmedQuote) {
|
|
331
|
+
this.log(`Email: sending against confirmed quote ${confirmedQuote}`);
|
|
332
|
+
return this.executeToolRequest('/v1/tools/email/send', this.emailSendPayload(options, fromAddress), confirmedQuote);
|
|
333
|
+
}
|
|
316
334
|
// `mailbox_provisioning_fee` (>0) means `from_address` is a new address that
|
|
317
335
|
// provisions a mailbox on first send — a one-time fee folded into `total_cost`.
|
|
336
|
+
// `is_new` (true) means this quote would register a brand-new domain, which
|
|
337
|
+
// drives the confirm-before-charge gate below (#1070 round 2).
|
|
318
338
|
const quote = await this.tool('email/quote', {
|
|
319
339
|
...(fromAddress ? { from_address: fromAddress } : {}),
|
|
320
340
|
...(options.to !== undefined ? { to_address: options.to } : {}),
|
|
@@ -334,7 +354,32 @@ class OneShot {
|
|
|
334
354
|
// un-set so the server returned a 200 with the quote and the caller still
|
|
335
355
|
// wants the local guard to apply).
|
|
336
356
|
this.assertWithinMaxCost(quote.total_cost, options.maxCost);
|
|
337
|
-
|
|
357
|
+
// Registering a brand-new domain is charged only after its exact quote has
|
|
358
|
+
// been shown and explicitly authorized (#1070 round 2) — this call stops
|
|
359
|
+
// here, before /email/send, and charges nothing. Pass confirmNewDomain:
|
|
360
|
+
// true (with the same from_domain) to register the domain, pay the quoted
|
|
361
|
+
// total, and send the email in one follow-up call.
|
|
362
|
+
if (quote.is_new) {
|
|
363
|
+
const domainName = quote.domain ?? options.from_domain ?? '';
|
|
364
|
+
const breakdown = quote.registration_fee !== undefined && quote.service_fee !== undefined
|
|
365
|
+
? ` (registration $${quote.registration_fee} + service fee $${quote.service_fee}` +
|
|
366
|
+
`${quote.mailbox_provisioning_fee ? ` + mailbox fee $${quote.mailbox_provisioning_fee}` : ''})`
|
|
367
|
+
: '';
|
|
368
|
+
return {
|
|
369
|
+
status: 'confirmation_required',
|
|
370
|
+
cost: parseFloat(quote.total_cost),
|
|
371
|
+
quote_id: quote.quote_id,
|
|
372
|
+
domain: { domain: domainName, status: 'quote_only', was_provisioned: false },
|
|
373
|
+
message: `Registering ${domainName} costs $${quote.total_cost} total${breakdown}. ` +
|
|
374
|
+
`Nothing has been charged yet. Show the user this exact amount; once they agree, call this again with the same ` +
|
|
375
|
+
`arguments and confirm_new_domain: '${quote.quote_id}' (SDK: confirmNewDomain) to register the domain, ` +
|
|
376
|
+
`charge exactly $${quote.total_cost}, and send this email. true only re-quotes; a wrong or expired quote id is refused without charge.`,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
return this.executeToolRequest('/v1/tools/email/send', this.emailSendPayload(options, fromAddress ?? quote.from_address), quote.quote_id);
|
|
380
|
+
}
|
|
381
|
+
/** The /email/send body for `email()`: the same whether it follows a fresh quote or a confirmed one. */
|
|
382
|
+
emailSendPayload(options, resolvedFromAddress) {
|
|
338
383
|
const payload = {
|
|
339
384
|
// Server replays the locked address from the quote when from_address
|
|
340
385
|
// is absent, but sending it anyway is harmless and forward-compatible
|
|
@@ -366,7 +411,11 @@ class OneShot {
|
|
|
366
411
|
// Releases a send the action policy held (see ApprovalRequiredError).
|
|
367
412
|
payload.approvalId = options.approvalId;
|
|
368
413
|
}
|
|
369
|
-
|
|
414
|
+
// Forward maxCost so the server-side X-Max-Cost-USDC header also guards
|
|
415
|
+
// the send, which matters on a confirmed send that skipped the quote.
|
|
416
|
+
if (options.maxCost !== undefined)
|
|
417
|
+
payload.maxCost = options.maxCost;
|
|
418
|
+
return payload;
|
|
370
419
|
}
|
|
371
420
|
/** List the caller's domain pool with warmup and rotation metadata. */
|
|
372
421
|
async listDomains() {
|
|
@@ -780,6 +829,7 @@ class OneShot {
|
|
|
780
829
|
payload,
|
|
781
830
|
signal: options.signal,
|
|
782
831
|
maxCost: options.maxCost,
|
|
832
|
+
approvalId: options.approvalId,
|
|
783
833
|
// Build analysis can be slow server-side; bound the quote leg client-side.
|
|
784
834
|
quoteTimeoutMs: 120000,
|
|
785
835
|
expectMsg: 'Expected 402 for quote',
|
|
@@ -1065,6 +1115,35 @@ class OneShot {
|
|
|
1065
1115
|
const { accountId, conversationId, text, ...rest } = options;
|
|
1066
1116
|
return this.tool('linkedin/reply', { ...rest, account_id: accountId, conversation_id: conversationId, text, timeout: options.timeout ?? 180 });
|
|
1067
1117
|
}
|
|
1118
|
+
/**
|
|
1119
|
+
* Message a first-degree connection: into the 1:1 conversation the account
|
|
1120
|
+
* already has with them, or a new one. Use it once an invitation is accepted,
|
|
1121
|
+
* or for a connection the account has never messaged. Needs the `reply`
|
|
1122
|
+
* grant; shares the reply daily cap and pacing. A failed send carries an
|
|
1123
|
+
* error code: `not_connected` (invite them first) and `rate_limited` mean
|
|
1124
|
+
* "defer", not "give up".
|
|
1125
|
+
*/
|
|
1126
|
+
async linkedinMessage(options) {
|
|
1127
|
+
this.validate(options.accountId, 'accountId');
|
|
1128
|
+
this.validate(options.profile, 'profile');
|
|
1129
|
+
this.validate(options.text, 'text');
|
|
1130
|
+
const { accountId, profile, text, ...rest } = options;
|
|
1131
|
+
return this.tool('linkedin/message', { ...rest, account_id: accountId, profile, text, timeout: options.timeout ?? 180 });
|
|
1132
|
+
}
|
|
1133
|
+
/** One invitation sent through linkedinInvite: `accepted` (with `accepted_at`) as soon as the person is a connection. Free. */
|
|
1134
|
+
async linkedinInvitation(options) {
|
|
1135
|
+
this.validate(options.accountId, 'accountId');
|
|
1136
|
+
this.validate(options.invitationId, 'invitationId');
|
|
1137
|
+
return this.linkedinFree('GET', `/accounts/${encodeURIComponent(options.accountId)}/invitations/${encodeURIComponent(options.invitationId)}`, { what: 'invitation' });
|
|
1138
|
+
}
|
|
1139
|
+
/** Invitations sent through linkedinInvite, most recently changed first. Poll with `changedSince`. Free. */
|
|
1140
|
+
async linkedinInvitations(options) {
|
|
1141
|
+
this.validate(options.accountId, 'accountId');
|
|
1142
|
+
return this.linkedinFree('GET', `/accounts/${encodeURIComponent(options.accountId)}/invitations`, {
|
|
1143
|
+
what: 'invitations',
|
|
1144
|
+
query: { status: options.status, changed_since: options.changedSince, limit: options.limit?.toString() },
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1068
1147
|
/** View a profile through the connected account. `notify: true` is visible to the target and cannot be undone. */
|
|
1069
1148
|
async linkedinViewProfile(options) {
|
|
1070
1149
|
this.validate(options.accountId, 'accountId');
|
|
@@ -1351,6 +1430,8 @@ class OneShot {
|
|
|
1351
1430
|
payload.schedule = options.schedule;
|
|
1352
1431
|
if (options.approvals)
|
|
1353
1432
|
payload.approvals = options.approvals;
|
|
1433
|
+
if (options.done_check)
|
|
1434
|
+
payload.done_check = options.done_check;
|
|
1354
1435
|
// Compute returns the goal directly on 202 — no job polling, unlike the
|
|
1355
1436
|
// other paid tools.
|
|
1356
1437
|
const { execResp: createResp } = await this.runQuoteToPay({
|
|
@@ -1496,6 +1577,33 @@ class OneShot {
|
|
|
1496
1577
|
const json = await response.json();
|
|
1497
1578
|
return json.data;
|
|
1498
1579
|
}
|
|
1580
|
+
/**
|
|
1581
|
+
* Answer whether a goal in `awaiting_confirmation` is done. A goal with no
|
|
1582
|
+
* done-check completes only when its owner confirms: the agent that ran it
|
|
1583
|
+
* cannot. `approved: false` sends it back with `response` as what is missing.
|
|
1584
|
+
*
|
|
1585
|
+
* @example
|
|
1586
|
+
* ```typescript
|
|
1587
|
+
* const goal = await agent.getComputeGoal(goalId);
|
|
1588
|
+
* if (goal.status === 'awaiting_confirmation') {
|
|
1589
|
+
* await agent.confirmComputeGoal(goalId, { approved: true });
|
|
1590
|
+
* }
|
|
1591
|
+
* ```
|
|
1592
|
+
*/
|
|
1593
|
+
async confirmComputeGoal(goalId, input) {
|
|
1594
|
+
this.validate(goalId, 'goalId');
|
|
1595
|
+
const response = await fetch(`${this.baseUrl}/v1/compute/${goalId}/respond`, {
|
|
1596
|
+
method: 'POST',
|
|
1597
|
+
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders('write')) },
|
|
1598
|
+
body: JSON.stringify({ confirm_done: input.approved, ...(input.response ? { response: input.response } : {}) }),
|
|
1599
|
+
});
|
|
1600
|
+
if (response.status === 404)
|
|
1601
|
+
throw new errors_2.ToolError('Goal not found', 404, await response.text());
|
|
1602
|
+
if (!response.ok)
|
|
1603
|
+
throw new errors_2.ToolError('Failed to confirm compute goal', response.status, await response.text());
|
|
1604
|
+
const json = await response.json();
|
|
1605
|
+
return json.data;
|
|
1606
|
+
}
|
|
1499
1607
|
/**
|
|
1500
1608
|
* Pause a recurring compute goal
|
|
1501
1609
|
*
|
|
@@ -1865,7 +1973,7 @@ class OneShot {
|
|
|
1865
1973
|
* locally. A fresh nonce per call prevents replay. Signing failure falls back
|
|
1866
1974
|
* to plain headers, with a one-time warning.
|
|
1867
1975
|
*/
|
|
1868
|
-
async signedReadHeaders(scope = 'read') {
|
|
1976
|
+
async signedReadHeaders(scope = 'read', domainName = 'OneShot Agent Auth') {
|
|
1869
1977
|
const base = this.headers();
|
|
1870
1978
|
// The bearer token already binds the request to the agent (and is
|
|
1871
1979
|
// revocable); the API accepts it in place of the signed proof.
|
|
@@ -1875,7 +1983,14 @@ class OneShot {
|
|
|
1875
1983
|
const agent = this.provider.address;
|
|
1876
1984
|
const issuedAt = Math.floor(Date.now() / 1000);
|
|
1877
1985
|
const nonce = ethers_1.ethers.hexlify(ethers_1.ethers.randomBytes(32));
|
|
1878
|
-
const signature = await this.provider.signTypedData(
|
|
1986
|
+
const signature = await this.provider.signTypedData(
|
|
1987
|
+
// `domainName` lets a different origin (e.g. Soul Hunt, which keeps
|
|
1988
|
+
// its own nonce store — see hunts.ts) use a DIFFERENT EIP-712 domain
|
|
1989
|
+
// than api-service's. EIP-712 mixes the domain into the signed hash,
|
|
1990
|
+
// so a proof is only valid for the one origin it was signed for and
|
|
1991
|
+
// can't be replayed across service boundaries (security finding
|
|
1992
|
+
// F-t_d4805d6f-3, round 2).
|
|
1993
|
+
{ name: domainName, version: '1' }, {
|
|
1879
1994
|
AgentReadAuth: [
|
|
1880
1995
|
{ name: 'agent', type: 'address' },
|
|
1881
1996
|
{ name: 'scope', type: 'string' },
|
|
@@ -2316,7 +2431,7 @@ class OneShot {
|
|
|
2316
2431
|
}
|
|
2317
2432
|
async executeToolRequest(endpoint, options, quoteId) {
|
|
2318
2433
|
// LinkedIn writes act through a human's account and cannot be recalled: always keyed.
|
|
2319
|
-
const reliable = /(?:^|\/)(enrich\/(profile|email)|verify\/email|linkedin\/(reply|profile-view|react|invite(?:\/withdraw)?))$/.test(endpoint);
|
|
2434
|
+
const reliable = /(?:^|\/)(enrich\/(profile|email)|verify\/email|linkedin\/(reply|message|profile-view|react|invite(?:\/withdraw)?))$/.test(endpoint);
|
|
2320
2435
|
const key = options.idempotencyKey ?? (reliable ? ethers_1.ethers.hexlify(ethers_1.ethers.randomBytes(16)) : undefined);
|
|
2321
2436
|
if (options.totalTimeoutMs !== undefined && (!Number.isFinite(options.totalTimeoutMs) || options.totalTimeoutMs <= 0)) {
|
|
2322
2437
|
throw new errors_2.ValidationError('totalTimeoutMs must be positive', 'totalTimeoutMs');
|
|
@@ -3041,7 +3156,7 @@ class OneShot {
|
|
|
3041
3156
|
}
|
|
3042
3157
|
catch (err) {
|
|
3043
3158
|
// Keyed writes may already be queued server-side after a transport failure: keep the reservation.
|
|
3044
|
-
if (!/(enrich\/(profile|email)|verify\/email|physical-mail\/send|linkedin\/(reply|profile-view|react|invite(?:\/withdraw)?))$/.test(endpoint))
|
|
3159
|
+
if (!/(enrich\/(profile|email)|verify\/email|physical-mail\/send|linkedin\/(reply|message|profile-view|react|invite(?:\/withdraw)?))$/.test(endpoint))
|
|
3045
3160
|
this.releaseUsdcReservation(signed.reservation);
|
|
3046
3161
|
throw err;
|
|
3047
3162
|
}
|