@oneshot-agent/sdk 0.62.0 → 0.63.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 +4 -8
- package/dist/approvals.d.ts +1 -1
- package/dist/approvals.d.ts.map +1 -1
- package/dist/errors.d.ts +11 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +16 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +46 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +194 -53
- package/dist/index.js.map +1 -1
- package/dist/predict-input.generated.d.ts +11 -0
- package/dist/predict-input.generated.d.ts.map +1 -1
- package/dist/predict.d.ts +40 -1
- package/dist/predict.d.ts.map +1 -1
- package/dist/recovery.d.ts +2 -2
- package/dist/recovery.d.ts.map +1 -1
- package/dist/types.d.ts +200 -67
- package/dist/types.d.ts.map +1 -1
- package/dist/untrusted-tools.generated.d.ts.map +1 -1
- package/dist/untrusted-tools.generated.js +22 -0
- package/dist/untrusted-tools.generated.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -65,7 +65,7 @@ __exportStar(require("./errors"), exports);
|
|
|
65
65
|
const approvals_1 = require("./approvals");
|
|
66
66
|
// Keep receipt on its subpath: node:crypto breaks browser bundles, and providerCost is not public.
|
|
67
67
|
// Keep in sync with package.json `version`. Guarded by version.test.ts.
|
|
68
|
-
const SDK_VERSION = '0.
|
|
68
|
+
const SDK_VERSION = '0.63.0';
|
|
69
69
|
/** HTTP poll cadence while push is unconfirmed: fast first checks, settling at 2s. */
|
|
70
70
|
const HTTP_POLL_BACKOFF_MS = [300, 600, 1000, 2000];
|
|
71
71
|
/** HTTP poll cadence once the WebSocket has delivered for this request. */
|
|
@@ -320,6 +320,39 @@ class OneShot {
|
|
|
320
320
|
async tool(toolName, options) {
|
|
321
321
|
return this.executeToolRequest(`/v1/tools/${toolName}`, options);
|
|
322
322
|
}
|
|
323
|
+
/**
|
|
324
|
+
* Price account setup without a recipient or message; this does not buy or send. Choose the mode
|
|
325
|
+
* (`relay` by default, or `mailbox`) and the addresses (default `['agent']`). On a verified domain
|
|
326
|
+
* the agent already has, this prices only the named addresses it does not have yet.
|
|
327
|
+
*/
|
|
328
|
+
async quoteDomain(domain, options = {}) {
|
|
329
|
+
this.validate(domain, 'domain');
|
|
330
|
+
const { mailbox_mode, addresses, ...rest } = options;
|
|
331
|
+
if (mailbox_mode !== undefined && mailbox_mode !== 'relay' && mailbox_mode !== 'mailbox') {
|
|
332
|
+
throw new errors_2.ValidationError("mailbox_mode must be 'relay' or 'mailbox'", 'mailbox_mode');
|
|
333
|
+
}
|
|
334
|
+
if (addresses !== undefined && (!Array.isArray(addresses) || addresses.length < 1 || addresses.length > 10
|
|
335
|
+
|| addresses.some((a) => typeof a !== 'string' || !a.trim()))) {
|
|
336
|
+
throw new errors_2.ValidationError('addresses must name 1 to 10 local parts, such as ["sales"]', 'addresses');
|
|
337
|
+
}
|
|
338
|
+
const quote = await this.executeToolRequest('/v1/domains/quote', {
|
|
339
|
+
...rest, domain,
|
|
340
|
+
...(mailbox_mode ? { mailbox_mode } : {}),
|
|
341
|
+
...(addresses ? { addresses: addresses.map((a) => a.trim().toLowerCase()) } : {}),
|
|
342
|
+
});
|
|
343
|
+
if (quote.total_cost !== undefined)
|
|
344
|
+
this.assertWithinMaxCost(String(quote.total_cost), options.maxCost);
|
|
345
|
+
return quote;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Owner/API purchase of the exact setup quote. The mode and addresses are the quote's; the result
|
|
349
|
+
* names them. Registration, adding addresses and email are separate actions.
|
|
350
|
+
*/
|
|
351
|
+
async buyDomain(domain, quoteId, options = {}) {
|
|
352
|
+
this.validate(domain, 'domain');
|
|
353
|
+
this.validate(quoteId?.trim(), 'quoteId');
|
|
354
|
+
return this.executeToolRequest('/v1/domains/buy', { ...options, domain }, quoteId.trim());
|
|
355
|
+
}
|
|
323
356
|
async email(options) {
|
|
324
357
|
// to/subject are server-derived when replying to an inbound email.
|
|
325
358
|
if (!options.reply_to_email_id) {
|
|
@@ -332,59 +365,117 @@ class OneShot {
|
|
|
332
365
|
if (options.from_mailbox && !options.from_domain) {
|
|
333
366
|
throw new errors_2.ValidationError('from_mailbox was pinned without from_domain. Pass from_domain (a domain you own) as well, or omit both to rotate across your warmed domains.', 'from_domain');
|
|
334
367
|
}
|
|
335
|
-
const useRotation = !options.from_domain && !options.from_mailbox;
|
|
336
|
-
const fromAddress = useRotation
|
|
337
|
-
? undefined
|
|
338
|
-
: `${options.from_mailbox ?? 'agent'}@${options.from_domain}`;
|
|
339
368
|
// Reuse the confirmed quote so the charge matches the amount shown; true alone confirms no amount.
|
|
340
369
|
const confirmedQuote = typeof options.confirmNewDomain === 'string' && options.confirmNewDomain.trim()
|
|
341
370
|
? options.confirmNewDomain.trim() : undefined;
|
|
342
371
|
if (confirmedQuote) {
|
|
343
|
-
this.
|
|
344
|
-
|
|
372
|
+
this.validate(options.from_domain, 'from_domain');
|
|
373
|
+
const setup = await this.buyDomain(options.from_domain, confirmedQuote, {
|
|
374
|
+
maxCost: options.maxCost, signal: options.signal, wait: false,
|
|
375
|
+
});
|
|
376
|
+
return { status: 'setup_pending', setup, request_id: setup.request_id, receipt_id: setup.receipt_id,
|
|
377
|
+
message: 'Setup purchased; no email was sent. Wait for it to finish, then call email without confirmNewDomain.' };
|
|
378
|
+
}
|
|
379
|
+
let fromAddress;
|
|
380
|
+
if (options.from_domain) {
|
|
381
|
+
const domainName = options.from_domain.trim().toLowerCase().replace(/\.$/, '');
|
|
382
|
+
// An unavailable ownership read leaves the email quote to decide whether setup is needed.
|
|
383
|
+
const owned = await this.listDomains().catch(() => undefined);
|
|
384
|
+
const entry = owned?.domains.find(d => d.domain.toLowerCase() === domainName);
|
|
385
|
+
// A new name reaches the setup quote without an email quote.
|
|
386
|
+
if (owned && !entry)
|
|
387
|
+
return this.emailDomainSetup(domainName, options);
|
|
388
|
+
fromAddress = options.from_mailbox
|
|
389
|
+
? `${options.from_mailbox}@${options.from_domain}`
|
|
390
|
+
: entry?.default_from ?? `agent@${options.from_domain}`;
|
|
391
|
+
}
|
|
392
|
+
let quote;
|
|
393
|
+
try {
|
|
394
|
+
quote = await this.tool('email/quote', {
|
|
395
|
+
...(fromAddress ? { from_address: fromAddress } : {}),
|
|
396
|
+
...(options.to !== undefined ? { to_address: options.to } : {}),
|
|
397
|
+
...(options.subject !== undefined ? { subject: options.subject } : {}),
|
|
398
|
+
...(options.reply_to_email_id ? { reply_to_email_id: options.reply_to_email_id } : {}),
|
|
399
|
+
body: options.body,
|
|
400
|
+
maxCost: options.maxCost,
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
catch (error) {
|
|
404
|
+
const setup = await this.emailSetupFromRefusal(error, options);
|
|
405
|
+
if (setup)
|
|
406
|
+
return setup;
|
|
407
|
+
throw error;
|
|
345
408
|
}
|
|
346
|
-
// Mailbox provisioning and domain registration are distinct fees; only a new domain needs confirmation.
|
|
347
|
-
const quote = await this.tool('email/quote', {
|
|
348
|
-
...(fromAddress ? { from_address: fromAddress } : {}),
|
|
349
|
-
...(options.to !== undefined ? { to_address: options.to } : {}),
|
|
350
|
-
...(options.subject !== undefined ? { subject: options.subject } : {}),
|
|
351
|
-
...(options.reply_to_email_id ? { reply_to_email_id: options.reply_to_email_id } : {}),
|
|
352
|
-
...(options.mailbox_mode ? { mailbox_mode: options.mailbox_mode } : {}),
|
|
353
|
-
body: options.body,
|
|
354
|
-
maxCost: options.maxCost,
|
|
355
|
-
});
|
|
356
409
|
this.log(`Email quote: $${quote.total_cost}`);
|
|
357
410
|
// Enforce the cap locally even if the server did not check the header.
|
|
358
411
|
this.assertWithinMaxCost(quote.total_cost, options.maxCost);
|
|
359
|
-
// A new domain must be explicitly authorized against its exact quote before any charge or send.
|
|
360
|
-
if (quote.is_new) {
|
|
361
|
-
const domainName = quote.domain ?? options.from_domain ?? '';
|
|
362
|
-
const breakdown = quote.registration_fee !== undefined && quote.service_fee !== undefined
|
|
363
|
-
? ` (registration $${quote.registration_fee} + service fee $${quote.service_fee}` +
|
|
364
|
-
`${quote.mailbox_provisioning_fee ? ` + mailbox fee $${quote.mailbox_provisioning_fee}` : ''})`
|
|
365
|
-
: '';
|
|
366
|
-
return {
|
|
367
|
-
status: 'confirmation_required',
|
|
368
|
-
cost: parseFloat(quote.total_cost),
|
|
369
|
-
quote_id: quote.quote_id,
|
|
370
|
-
domain: { domain: domainName, status: 'quote_only', was_provisioned: false },
|
|
371
|
-
message: `Registering ${domainName} costs $${quote.total_cost} total${breakdown}. ` +
|
|
372
|
-
`Nothing has been charged yet. Show the user this exact amount; once they agree, call this again with the same ` +
|
|
373
|
-
`arguments and confirm_new_domain: '${quote.quote_id}' (SDK: confirmNewDomain) to register the domain, ` +
|
|
374
|
-
`charge exactly $${quote.total_cost}, and send this email. true only re-quotes; a wrong or expired quote id is refused without charge.`,
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
412
|
return this.executeToolRequest('/v1/tools/email/send', this.emailSendPayload(options, fromAddress ?? quote.from_address), quote.quote_id);
|
|
378
413
|
}
|
|
379
|
-
/**
|
|
414
|
+
/**
|
|
415
|
+
* A send never sets up a domain or an address: the email quote refuses with `no_sending_domain` or
|
|
416
|
+
* `address_not_set_up` and names the setup. Route that to the setup quote, so the caller reviews its
|
|
417
|
+
* price, rather than surfacing a bare error. Any other error is the caller's.
|
|
418
|
+
*/
|
|
419
|
+
async emailSetupFromRefusal(error, options) {
|
|
420
|
+
if (!(error instanceof errors_2.ToolError) || error.statusCode !== 400)
|
|
421
|
+
return undefined;
|
|
422
|
+
let body;
|
|
423
|
+
try {
|
|
424
|
+
body = JSON.parse(error.responseBody);
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
return undefined;
|
|
428
|
+
}
|
|
429
|
+
const requirements = Array.isArray(body.requirements) ? body.requirements : undefined;
|
|
430
|
+
if (body.error === 'address_not_set_up' && body.domain && body.from_address) {
|
|
431
|
+
const local = body.from_address.split('@')[0];
|
|
432
|
+
try {
|
|
433
|
+
return await this.emailDomainSetup(body.domain, options, [local]);
|
|
434
|
+
}
|
|
435
|
+
catch (quoteError) {
|
|
436
|
+
// The address is already being added by an earlier purchase.
|
|
437
|
+
if (quoteError instanceof errors_2.ToolError && quoteError.statusCode === 409 && /addresses_already_set_up/.test(quoteError.responseBody)) {
|
|
438
|
+
return { status: 'setup_pending', error: 'address_not_set_up', requirements,
|
|
439
|
+
message: `${body.from_address} is being set up; no email was sent. Send again once it is active in listDomains().` };
|
|
440
|
+
}
|
|
441
|
+
throw quoteError;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
if (body.error !== 'no_sending_domain')
|
|
445
|
+
return undefined;
|
|
446
|
+
const domain = body.domain ?? options.from_domain?.trim().toLowerCase();
|
|
447
|
+
if (body.reason === 'not_owned' && domain)
|
|
448
|
+
return this.emailDomainSetup(domain, options);
|
|
449
|
+
const pending = body.reason === 'not_verified';
|
|
450
|
+
return {
|
|
451
|
+
status: pending ? 'setup_pending' : 'setup_required', error: 'no_sending_domain', requirements,
|
|
452
|
+
message: body.message ?? 'This agent has no verified sending domain. Buy one with quoteDomain and buyDomain; no email was sent.',
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
async emailDomainSetup(domainName, options, addresses) {
|
|
456
|
+
const named = addresses ?? (options.from_mailbox ? [options.from_mailbox] : undefined);
|
|
457
|
+
const setup = await this.quoteDomain(domainName, { maxCost: options.maxCost, signal: options.signal, ...(named ? { addresses: named } : {}) });
|
|
458
|
+
const addingAddresses = setup.setup === 'domain_addresses';
|
|
459
|
+
return {
|
|
460
|
+
status: setup.purchasable ? 'confirmation_required' : 'setup_unavailable',
|
|
461
|
+
cost: setup.total_cost,
|
|
462
|
+
quote_id: setup.quote_id,
|
|
463
|
+
setup,
|
|
464
|
+
domain: { domain: domainName, status: 'quote_only', was_provisioned: false },
|
|
465
|
+
message: !setup.purchasable
|
|
466
|
+
? 'This name cannot be purchased. Choose another name for account setup.'
|
|
467
|
+
: addingAddresses
|
|
468
|
+
? 'This address is not on the domain yet. No purchase or email send has occurred. The owner or API caller must review setup.total_cost, then buy this exact quote at POST /v1/domains/buy (SDK: confirmNewDomain). Send separately once the address is active.'
|
|
469
|
+
: 'No setup purchase or email send has occurred. The owner or API caller must review setup.total_cost and setup.billing, then buy this exact quote at POST /v1/domains/buy (SDK: confirmNewDomain). No email is included; send separately after verification.',
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
/** Keep setup-only fields out of the paid send body. */
|
|
380
473
|
emailSendPayload(options, resolvedFromAddress) {
|
|
381
474
|
const payload = {
|
|
382
475
|
...(resolvedFromAddress ? { from_address: resolvedFromAddress } : {}),
|
|
383
476
|
...(options.to !== undefined ? { to_address: options.to } : {}),
|
|
384
477
|
...(options.subject !== undefined ? { subject: options.subject } : {}),
|
|
385
478
|
...(options.reply_to_email_id ? { reply_to_email_id: options.reply_to_email_id } : {}),
|
|
386
|
-
// Preserve mailbox provisioning when the new domain does not yet have a stored mode.
|
|
387
|
-
...(options.mailbox_mode ? { mailbox_mode: options.mailbox_mode } : {}),
|
|
388
479
|
body: options.body,
|
|
389
480
|
signal: options.signal,
|
|
390
481
|
onStatusUpdate: options.onStatusUpdate,
|
|
@@ -1140,8 +1231,8 @@ class OneShot {
|
|
|
1140
1231
|
* already has with them, or a new one. Use it once an invitation is accepted,
|
|
1141
1232
|
* or for a connection the account has never messaged. Needs the `reply`
|
|
1142
1233
|
* grant; shares the reply daily cap and pacing. A failed send carries an
|
|
1143
|
-
* error code: `
|
|
1144
|
-
*
|
|
1234
|
+
* error code: `recipient_not_connected` means invite them first, not give
|
|
1235
|
+
* up.
|
|
1145
1236
|
*/
|
|
1146
1237
|
async linkedinMessage(options) {
|
|
1147
1238
|
this.validate(options.accountId, 'accountId');
|
|
@@ -1489,7 +1580,7 @@ class OneShot {
|
|
|
1489
1580
|
}
|
|
1490
1581
|
/**
|
|
1491
1582
|
* The agent's connections: GitHub and business systems, each with `id`, `system`, `account_label` and
|
|
1492
|
-
* `status`. Free. A connected-system read (ledger, CRM, shipping, tax, procurement, meetings) takes the `id` of an active
|
|
1583
|
+
* `status`. Free. A connected-system read (ledger, CRM, shipping, tax, procurement, meetings, field service) takes the `id` of an active
|
|
1493
1584
|
* one as `connection_id`; pick it by `system` and `account_label`, then call the read with the generic
|
|
1494
1585
|
* {@link OneShot.route}.
|
|
1495
1586
|
*
|
|
@@ -1638,7 +1729,7 @@ class OneShot {
|
|
|
1638
1729
|
* with JobError (goal ID, end_reason code, snapshot in partialResult).
|
|
1639
1730
|
* Budget exhaustion waits in awaiting_budget for funding; after seven days it ends. After fundComputeGoal revives an ended goal,
|
|
1640
1731
|
* call waitForComputeGoal again with the same ID to observe the new run.
|
|
1641
|
-
* Paused/awaiting_confirmation/awaiting_connection/awaiting_budget/awaiting_tool and recurring goals keep waiting until the
|
|
1732
|
+
* Paused/awaiting_confirmation/awaiting_connection/awaiting_budget/awaiting_tool/awaiting_setup and recurring goals keep waiting until the
|
|
1642
1733
|
* goal ends or the timeout (seconds, default 120) expires with JobTimeoutError.
|
|
1643
1734
|
* Infinity disables the local deadline.
|
|
1644
1735
|
* onStatusUpdate receives each distinct observed status before settlement.
|
|
@@ -1696,7 +1787,8 @@ class OneShot {
|
|
|
1696
1787
|
}
|
|
1697
1788
|
}
|
|
1698
1789
|
/**
|
|
1699
|
-
* List tasks under a compute goal
|
|
1790
|
+
* List tasks under a compute goal, newest first: one page of `limit` tasks (default 100, at most 500).
|
|
1791
|
+
* Use `getComputeTasksPage` to read further pages.
|
|
1700
1792
|
*
|
|
1701
1793
|
* @example
|
|
1702
1794
|
* ```typescript
|
|
@@ -1706,16 +1798,40 @@ class OneShot {
|
|
|
1706
1798
|
* }
|
|
1707
1799
|
* ```
|
|
1708
1800
|
*/
|
|
1709
|
-
async getComputeTasks(goalId) {
|
|
1801
|
+
async getComputeTasks(goalId, options = {}) {
|
|
1802
|
+
return (await this.getComputeTasksPage(goalId, options)).tasks;
|
|
1803
|
+
}
|
|
1804
|
+
/**
|
|
1805
|
+
* One page of a compute goal's tasks, newest first, with the cursor of the next page (null on the last).
|
|
1806
|
+
*
|
|
1807
|
+
* @example
|
|
1808
|
+
* ```typescript
|
|
1809
|
+
* let cursor: string | undefined;
|
|
1810
|
+
* do {
|
|
1811
|
+
* const page = await agent.getComputeTasksPage('goal_01HX...', { cursor });
|
|
1812
|
+
* for (const t of page.tasks) console.log(t.id, t.status);
|
|
1813
|
+
* cursor = page.next_cursor ?? undefined;
|
|
1814
|
+
* } while (cursor);
|
|
1815
|
+
* ```
|
|
1816
|
+
*/
|
|
1817
|
+
async getComputeTasksPage(goalId, options = {}) {
|
|
1710
1818
|
this.validate(goalId, 'goalId');
|
|
1711
|
-
const
|
|
1819
|
+
const query = new URLSearchParams();
|
|
1820
|
+
if (options.status)
|
|
1821
|
+
query.set('status', options.status);
|
|
1822
|
+
if (options.limit !== undefined)
|
|
1823
|
+
query.set('limit', String(options.limit));
|
|
1824
|
+
if (options.cursor)
|
|
1825
|
+
query.set('cursor', options.cursor);
|
|
1826
|
+
const qs = query.toString();
|
|
1827
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}/tasks${qs ? `?${qs}` : ''}`, {
|
|
1712
1828
|
headers: await this.signedReadHeaders()
|
|
1713
1829
|
});
|
|
1714
1830
|
if (!response.ok) {
|
|
1715
1831
|
throw new errors_2.ToolError('Failed to get compute tasks', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1716
1832
|
}
|
|
1717
1833
|
const json = await response.json();
|
|
1718
|
-
return json.data;
|
|
1834
|
+
return { tasks: json.data, next_cursor: json.page?.next_cursor ?? null };
|
|
1719
1835
|
}
|
|
1720
1836
|
/**
|
|
1721
1837
|
* List what a compute goal produced: the files its sandbox tasks left and
|
|
@@ -1913,7 +2029,7 @@ class OneShot {
|
|
|
1913
2029
|
return json.data;
|
|
1914
2030
|
}
|
|
1915
2031
|
/**
|
|
1916
|
-
* Pause a
|
|
2032
|
+
* Pause a compute goal (one-off goals keep their work and continue where they were on resume)
|
|
1917
2033
|
*
|
|
1918
2034
|
* @example
|
|
1919
2035
|
* ```typescript
|
|
@@ -1934,7 +2050,7 @@ class OneShot {
|
|
|
1934
2050
|
return json.data;
|
|
1935
2051
|
}
|
|
1936
2052
|
/**
|
|
1937
|
-
* Resume a paused
|
|
2053
|
+
* Resume a paused compute goal
|
|
1938
2054
|
*
|
|
1939
2055
|
* @example
|
|
1940
2056
|
* ```typescript
|
|
@@ -2540,6 +2656,9 @@ class OneShot {
|
|
|
2540
2656
|
const credits = response.status === 402 ? this.parseInsufficientCredits(text) : undefined;
|
|
2541
2657
|
if (credits)
|
|
2542
2658
|
throw credits;
|
|
2659
|
+
const ambiguous = response.status === 500 ? this.parseSettlementAmbiguous(text) : undefined;
|
|
2660
|
+
if (ambiguous)
|
|
2661
|
+
throw ambiguous;
|
|
2543
2662
|
const rejection = [402, 500].includes(response.status) ? this.parsePaymentRejection(text) : undefined;
|
|
2544
2663
|
if (rejection)
|
|
2545
2664
|
throw rejection;
|
|
@@ -2648,6 +2767,20 @@ class OneShot {
|
|
|
2648
2767
|
return undefined;
|
|
2649
2768
|
}
|
|
2650
2769
|
}
|
|
2770
|
+
/** A 500 `settlement_ambiguous`: the payment may be spent, so the caller must not sign another for this call. */
|
|
2771
|
+
parseSettlementAmbiguous(text) {
|
|
2772
|
+
let body;
|
|
2773
|
+
try {
|
|
2774
|
+
body = JSON.parse(text);
|
|
2775
|
+
}
|
|
2776
|
+
catch {
|
|
2777
|
+
return undefined;
|
|
2778
|
+
}
|
|
2779
|
+
if (body?.error !== 'settlement_ambiguous')
|
|
2780
|
+
return undefined;
|
|
2781
|
+
const settlementId = typeof body.settlement_id === 'string' ? body.settlement_id : undefined;
|
|
2782
|
+
return new errors_1.SettlementAmbiguousError(`Payment may have gone through. Don't pay again; if it landed and the call did not run, it is credited to your account.${settlementId ? ` [settlement ${settlementId}]` : ''}`, settlementId);
|
|
2783
|
+
}
|
|
2651
2784
|
parsePaymentRejection(text) {
|
|
2652
2785
|
let data;
|
|
2653
2786
|
try {
|
|
@@ -2726,7 +2859,7 @@ class OneShot {
|
|
|
2726
2859
|
}
|
|
2727
2860
|
async executeToolRequest(endpoint, options, quoteId) {
|
|
2728
2861
|
// LinkedIn writes act through a human's account and cannot be recalled: always keyed.
|
|
2729
|
-
const reliable = /(?:^|\/)(lookup\/person-(profile|email|email-status)|lookup\/place-permits|lookup\/company-private-raises|solve|predict|linkedin\/(reply|message|profile-view|react|invite(?:\/withdraw)?))$/.test(endpoint);
|
|
2862
|
+
const reliable = /(?:^|\/)(lookup\/person-(profile|email|email-status)|lookup\/place-permits|lookup\/company-private-raises|lookup\/company-fund-holdings|solve|predict|linkedin\/(reply|message|profile-view|react|invite(?:\/withdraw)?))$/.test(endpoint);
|
|
2730
2863
|
const key = options.idempotencyKey ?? (reliable ? ethers_1.ethers.hexlify(ethers_1.ethers.randomBytes(16)) : undefined);
|
|
2731
2864
|
if (options.totalTimeoutMs !== undefined && (!Number.isFinite(options.totalTimeoutMs) || options.totalTimeoutMs <= 0)) {
|
|
2732
2865
|
throw new errors_2.ValidationError('totalTimeoutMs must be positive', 'totalTimeoutMs');
|
|
@@ -2839,7 +2972,7 @@ class OneShot {
|
|
|
2839
2972
|
if (duplicate?.error === 'duplicate_request' && typeof duplicate.details?.request_id === 'string') {
|
|
2840
2973
|
response = Response.json({ request_id: duplicate.details.request_id, receipt_id: duplicate.details.receipt_id,
|
|
2841
2974
|
status: 'processing', tool: 'linkedin', linkedin: { action_request_id: duplicate.details.action_request_id,
|
|
2842
|
-
invitation_id: duplicate.details.invitation_id } }, { status: 202 });
|
|
2975
|
+
invitation_id: duplicate.details.invitation_id, scheduled_for: null } }, { status: 202 });
|
|
2843
2976
|
}
|
|
2844
2977
|
}
|
|
2845
2978
|
if (!response.ok) {
|
|
@@ -3295,7 +3428,7 @@ class OneShot {
|
|
|
3295
3428
|
// Identity Verification requires wallet proof on the payment leg too, to recover keyed submissions.
|
|
3296
3429
|
const provenPayment = /(?:^|\/)identity\/start$/.test(endpoint);
|
|
3297
3430
|
const askedForProof = !!extraHeaders?.['x-agent-proof'];
|
|
3298
|
-
const proofScope = /(lookup\/person-(profile|email|email-status)|lookup\/place-permits|lookup\/company-private-raises|physical-mail\/send|solve|predict)$/.test(endpoint)
|
|
3431
|
+
const proofScope = /(lookup\/person-(profile|email|email-status)|lookup\/place-permits|lookup\/company-private-raises|lookup\/company-fund-holdings|physical-mail\/send|solve|predict)$/.test(endpoint)
|
|
3299
3432
|
? 'submit'
|
|
3300
3433
|
: askedForProof ? 'read' : 'write';
|
|
3301
3434
|
const headers = {
|
|
@@ -3466,14 +3599,22 @@ class OneShot {
|
|
|
3466
3599
|
}
|
|
3467
3600
|
catch (err) {
|
|
3468
3601
|
// Keyed writes may already be queued server-side after a transport failure: keep the reservation.
|
|
3469
|
-
if (!/(lookup\/person-(profile|email|email-status)|lookup\/place-permits|lookup\/company-private-raises|physical-mail\/send|solve|predict|linkedin\/(reply|message|profile-view|react|invite(?:\/withdraw)?))$/.test(endpoint))
|
|
3602
|
+
if (!/(lookup\/person-(profile|email|email-status)|lookup\/place-permits|lookup\/company-private-raises|lookup\/company-fund-holdings|physical-mail\/send|solve|predict|linkedin\/(reply|message|profile-view|react|invite(?:\/withdraw)?))$/.test(endpoint))
|
|
3470
3603
|
this.releaseUsdcReservation(signed.reservation);
|
|
3471
3604
|
throw err;
|
|
3472
3605
|
}
|
|
3473
|
-
|
|
3606
|
+
// A settlement_ambiguous payment may be spent: keep its reservation until it expires.
|
|
3607
|
+
if (!resp.ok && !(extraHeaders?.['Idempotency-Key'] && resp.status >= 500) && !(await this.isSettlementAmbiguous(resp))) {
|
|
3474
3608
|
this.releaseUsdcReservation(signed.reservation);
|
|
3609
|
+
}
|
|
3475
3610
|
return resp;
|
|
3476
3611
|
}
|
|
3612
|
+
async isSettlementAmbiguous(resp) {
|
|
3613
|
+
if (resp.status !== 500)
|
|
3614
|
+
return false;
|
|
3615
|
+
const text = await resp.clone().text().catch(() => '');
|
|
3616
|
+
return this.parseSettlementAmbiguous(text) !== undefined;
|
|
3617
|
+
}
|
|
3477
3618
|
/** Parse the PAYMENT-REQUIRED header from a 402 response into the accepted requirements and Bazaar metadata. */
|
|
3478
3619
|
parsePaymentRequired(header) {
|
|
3479
3620
|
if (header) {
|