@oneshot-agent/sdk 0.61.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 +6 -9
- 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 +69 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +233 -57
- 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/tool-timeouts.generated.d.ts +3 -0
- package/dist/tool-timeouts.generated.d.ts.map +1 -0
- package/dist/tool-timeouts.generated.js +9 -0
- package/dist/tool-timeouts.generated.js.map +1 -0
- package/dist/types.d.ts +324 -69
- package/dist/types.d.ts.map +1 -1
- package/dist/untrusted-tools.generated.d.ts.map +1 -1
- package/dist/untrusted-tools.generated.js +128 -0
- package/dist/untrusted-tools.generated.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -38,6 +38,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.OneShot = exports.executeSwap = exports.getSwapQuote = exports.ReadOnlyWalletProvider = exports.CdpWalletProvider = exports.EthersWalletProvider = void 0;
|
|
40
40
|
const untrusted_content_1 = require("./untrusted-content");
|
|
41
|
+
const tool_timeouts_generated_1 = require("./tool-timeouts.generated");
|
|
41
42
|
const hunts_1 = require("./hunts");
|
|
42
43
|
__exportStar(require("./solve"), exports);
|
|
43
44
|
__exportStar(require("./predict"), exports);
|
|
@@ -64,7 +65,7 @@ __exportStar(require("./errors"), exports);
|
|
|
64
65
|
const approvals_1 = require("./approvals");
|
|
65
66
|
// Keep receipt on its subpath: node:crypto breaks browser bundles, and providerCost is not public.
|
|
66
67
|
// Keep in sync with package.json `version`. Guarded by version.test.ts.
|
|
67
|
-
const SDK_VERSION = '0.
|
|
68
|
+
const SDK_VERSION = '0.63.0';
|
|
68
69
|
/** HTTP poll cadence while push is unconfirmed: fast first checks, settling at 2s. */
|
|
69
70
|
const HTTP_POLL_BACKOFF_MS = [300, 600, 1000, 2000];
|
|
70
71
|
/** HTTP poll cadence once the WebSocket has delivered for this request. */
|
|
@@ -319,6 +320,39 @@ class OneShot {
|
|
|
319
320
|
async tool(toolName, options) {
|
|
320
321
|
return this.executeToolRequest(`/v1/tools/${toolName}`, options);
|
|
321
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
|
+
}
|
|
322
356
|
async email(options) {
|
|
323
357
|
// to/subject are server-derived when replying to an inbound email.
|
|
324
358
|
if (!options.reply_to_email_id) {
|
|
@@ -331,59 +365,117 @@ class OneShot {
|
|
|
331
365
|
if (options.from_mailbox && !options.from_domain) {
|
|
332
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');
|
|
333
367
|
}
|
|
334
|
-
const useRotation = !options.from_domain && !options.from_mailbox;
|
|
335
|
-
const fromAddress = useRotation
|
|
336
|
-
? undefined
|
|
337
|
-
: `${options.from_mailbox ?? 'agent'}@${options.from_domain}`;
|
|
338
368
|
// Reuse the confirmed quote so the charge matches the amount shown; true alone confirms no amount.
|
|
339
369
|
const confirmedQuote = typeof options.confirmNewDomain === 'string' && options.confirmNewDomain.trim()
|
|
340
370
|
? options.confirmNewDomain.trim() : undefined;
|
|
341
371
|
if (confirmedQuote) {
|
|
342
|
-
this.
|
|
343
|
-
|
|
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;
|
|
344
408
|
}
|
|
345
|
-
// Mailbox provisioning and domain registration are distinct fees; only a new domain needs confirmation.
|
|
346
|
-
const quote = await this.tool('email/quote', {
|
|
347
|
-
...(fromAddress ? { from_address: fromAddress } : {}),
|
|
348
|
-
...(options.to !== undefined ? { to_address: options.to } : {}),
|
|
349
|
-
...(options.subject !== undefined ? { subject: options.subject } : {}),
|
|
350
|
-
...(options.reply_to_email_id ? { reply_to_email_id: options.reply_to_email_id } : {}),
|
|
351
|
-
...(options.mailbox_mode ? { mailbox_mode: options.mailbox_mode } : {}),
|
|
352
|
-
body: options.body,
|
|
353
|
-
maxCost: options.maxCost,
|
|
354
|
-
});
|
|
355
409
|
this.log(`Email quote: $${quote.total_cost}`);
|
|
356
410
|
// Enforce the cap locally even if the server did not check the header.
|
|
357
411
|
this.assertWithinMaxCost(quote.total_cost, options.maxCost);
|
|
358
|
-
// A new domain must be explicitly authorized against its exact quote before any charge or send.
|
|
359
|
-
if (quote.is_new) {
|
|
360
|
-
const domainName = quote.domain ?? options.from_domain ?? '';
|
|
361
|
-
const breakdown = quote.registration_fee !== undefined && quote.service_fee !== undefined
|
|
362
|
-
? ` (registration $${quote.registration_fee} + service fee $${quote.service_fee}` +
|
|
363
|
-
`${quote.mailbox_provisioning_fee ? ` + mailbox fee $${quote.mailbox_provisioning_fee}` : ''})`
|
|
364
|
-
: '';
|
|
365
|
-
return {
|
|
366
|
-
status: 'confirmation_required',
|
|
367
|
-
cost: parseFloat(quote.total_cost),
|
|
368
|
-
quote_id: quote.quote_id,
|
|
369
|
-
domain: { domain: domainName, status: 'quote_only', was_provisioned: false },
|
|
370
|
-
message: `Registering ${domainName} costs $${quote.total_cost} total${breakdown}. ` +
|
|
371
|
-
`Nothing has been charged yet. Show the user this exact amount; once they agree, call this again with the same ` +
|
|
372
|
-
`arguments and confirm_new_domain: '${quote.quote_id}' (SDK: confirmNewDomain) to register the domain, ` +
|
|
373
|
-
`charge exactly $${quote.total_cost}, and send this email. true only re-quotes; a wrong or expired quote id is refused without charge.`,
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
412
|
return this.executeToolRequest('/v1/tools/email/send', this.emailSendPayload(options, fromAddress ?? quote.from_address), quote.quote_id);
|
|
377
413
|
}
|
|
378
|
-
/**
|
|
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. */
|
|
379
473
|
emailSendPayload(options, resolvedFromAddress) {
|
|
380
474
|
const payload = {
|
|
381
475
|
...(resolvedFromAddress ? { from_address: resolvedFromAddress } : {}),
|
|
382
476
|
...(options.to !== undefined ? { to_address: options.to } : {}),
|
|
383
477
|
...(options.subject !== undefined ? { subject: options.subject } : {}),
|
|
384
478
|
...(options.reply_to_email_id ? { reply_to_email_id: options.reply_to_email_id } : {}),
|
|
385
|
-
// Preserve mailbox provisioning when the new domain does not yet have a stored mode.
|
|
386
|
-
...(options.mailbox_mode ? { mailbox_mode: options.mailbox_mode } : {}),
|
|
387
479
|
body: options.body,
|
|
388
480
|
signal: options.signal,
|
|
389
481
|
onStatusUpdate: options.onStatusUpdate,
|
|
@@ -1139,8 +1231,8 @@ class OneShot {
|
|
|
1139
1231
|
* already has with them, or a new one. Use it once an invitation is accepted,
|
|
1140
1232
|
* or for a connection the account has never messaged. Needs the `reply`
|
|
1141
1233
|
* grant; shares the reply daily cap and pacing. A failed send carries an
|
|
1142
|
-
* error code: `
|
|
1143
|
-
*
|
|
1234
|
+
* error code: `recipient_not_connected` means invite them first, not give
|
|
1235
|
+
* up.
|
|
1144
1236
|
*/
|
|
1145
1237
|
async linkedinMessage(options) {
|
|
1146
1238
|
this.validate(options.accountId, 'accountId');
|
|
@@ -1488,7 +1580,7 @@ class OneShot {
|
|
|
1488
1580
|
}
|
|
1489
1581
|
/**
|
|
1490
1582
|
* The agent's connections: GitHub and business systems, each with `id`, `system`, `account_label` and
|
|
1491
|
-
* `status`. Free. A connected-system read (ledger, CRM, shipping, tax, procurement) 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
|
|
1492
1584
|
* one as `connection_id`; pick it by `system` and `account_label`, then call the read with the generic
|
|
1493
1585
|
* {@link OneShot.route}.
|
|
1494
1586
|
*
|
|
@@ -1533,6 +1625,11 @@ class OneShot {
|
|
|
1533
1625
|
* Uses the quote-then-pay flow: first call gets a 402 with a budget estimate,
|
|
1534
1626
|
* second call (with payment) creates the goal.
|
|
1535
1627
|
*
|
|
1628
|
+
* Name the unit of work, how work starts and what each unit needs in the objective, or pass
|
|
1629
|
+
* `scope`. The quote carries `scope`, `price` (per unit expected and ceiling, overhead, total or
|
|
1630
|
+
* units covered, unpriced, unmeasured) and the `done_check` the goal is held to; `onQuote` reads
|
|
1631
|
+
* them before paying and may throw to stop. A `scope` you pass is priced again in code.
|
|
1632
|
+
*
|
|
1536
1633
|
* @example
|
|
1537
1634
|
* ```typescript
|
|
1538
1635
|
* const goal = await agent.compute({
|
|
@@ -1567,6 +1664,8 @@ class OneShot {
|
|
|
1567
1664
|
payload.approvals = options.approvals;
|
|
1568
1665
|
if (options.done_check)
|
|
1569
1666
|
payload.done_check = options.done_check;
|
|
1667
|
+
if (options.scope)
|
|
1668
|
+
payload.scope = options.scope;
|
|
1570
1669
|
if (options.egress_allowlist)
|
|
1571
1670
|
payload.egress_allowlist = options.egress_allowlist;
|
|
1572
1671
|
// Compute returns the goal directly on 202 - no job polling, unlike the
|
|
@@ -1578,7 +1677,10 @@ class OneShot {
|
|
|
1578
1677
|
maxCost: options.maxCost,
|
|
1579
1678
|
expectMsg: 'Expected 402 for compute quote',
|
|
1580
1679
|
totalOf: (ctx) => ctx.total_budget,
|
|
1581
|
-
onQuote: (ctx) =>
|
|
1680
|
+
onQuote: (ctx) => {
|
|
1681
|
+
this.log(`Compute quote: $${ctx.total_budget} — ${ctx.objective_summary}`);
|
|
1682
|
+
options.onQuote?.(ctx);
|
|
1683
|
+
},
|
|
1582
1684
|
on400: async (resp) => {
|
|
1583
1685
|
const errorData = await resp.json();
|
|
1584
1686
|
if (errorData.error === 'content_blocked') {
|
|
@@ -1625,9 +1727,9 @@ class OneShot {
|
|
|
1625
1727
|
* Poll a compute goal using its goal_id, not its creation request_id.
|
|
1626
1728
|
* Returns the complete goal snapshot on completed; failed/cancelled reject
|
|
1627
1729
|
* with JobError (goal ID, end_reason code, snapshot in partialResult).
|
|
1628
|
-
* Budget exhaustion
|
|
1730
|
+
* Budget exhaustion waits in awaiting_budget for funding; after seven days it ends. After fundComputeGoal revives an ended goal,
|
|
1629
1731
|
* call waitForComputeGoal again with the same ID to observe the new run.
|
|
1630
|
-
* Paused/awaiting_confirmation/awaiting_connection 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
|
|
1631
1733
|
* goal ends or the timeout (seconds, default 120) expires with JobTimeoutError.
|
|
1632
1734
|
* Infinity disables the local deadline.
|
|
1633
1735
|
* onStatusUpdate receives each distinct observed status before settlement.
|
|
@@ -1685,7 +1787,8 @@ class OneShot {
|
|
|
1685
1787
|
}
|
|
1686
1788
|
}
|
|
1687
1789
|
/**
|
|
1688
|
-
* 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.
|
|
1689
1792
|
*
|
|
1690
1793
|
* @example
|
|
1691
1794
|
* ```typescript
|
|
@@ -1695,16 +1798,40 @@ class OneShot {
|
|
|
1695
1798
|
* }
|
|
1696
1799
|
* ```
|
|
1697
1800
|
*/
|
|
1698
|
-
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 = {}) {
|
|
1699
1818
|
this.validate(goalId, 'goalId');
|
|
1700
|
-
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}` : ''}`, {
|
|
1701
1828
|
headers: await this.signedReadHeaders()
|
|
1702
1829
|
});
|
|
1703
1830
|
if (!response.ok) {
|
|
1704
1831
|
throw new errors_2.ToolError('Failed to get compute tasks', response.status, await response.text(), response.headers.get('retry-after'));
|
|
1705
1832
|
}
|
|
1706
1833
|
const json = await response.json();
|
|
1707
|
-
return json.data;
|
|
1834
|
+
return { tasks: json.data, next_cursor: json.page?.next_cursor ?? null };
|
|
1708
1835
|
}
|
|
1709
1836
|
/**
|
|
1710
1837
|
* List what a compute goal produced: the files its sandbox tasks left and
|
|
@@ -1878,7 +2005,31 @@ class OneShot {
|
|
|
1878
2005
|
return json.data;
|
|
1879
2006
|
}
|
|
1880
2007
|
/**
|
|
1881
|
-
*
|
|
2008
|
+
* Continue a goal held in `awaiting_tool` without the tool it requested
|
|
2009
|
+
* (#3173). The goal returns to active and resumes with `message` as what
|
|
2010
|
+
* its owner said, or a default note that it continues without the tool.
|
|
2011
|
+
*
|
|
2012
|
+
* @example
|
|
2013
|
+
* ```typescript
|
|
2014
|
+
* await agent.continueComputeGoalWithoutTool(goalId, { message: 'Use the public filings instead.' });
|
|
2015
|
+
* ```
|
|
2016
|
+
*/
|
|
2017
|
+
async continueComputeGoalWithoutTool(goalId, input = {}) {
|
|
2018
|
+
this.validate(goalId, 'goalId');
|
|
2019
|
+
const response = await this.fetchApi(`${this.baseUrl}/v1/compute/${goalId}/respond`, {
|
|
2020
|
+
method: 'POST',
|
|
2021
|
+
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders('write')) },
|
|
2022
|
+
body: JSON.stringify({ continue_without_tool: true, ...(input.message ? { response: input.message } : {}) }),
|
|
2023
|
+
});
|
|
2024
|
+
if (response.status === 404)
|
|
2025
|
+
throw new errors_2.ToolError('Goal not found', 404, await response.text(), response.headers.get('retry-after'));
|
|
2026
|
+
if (!response.ok)
|
|
2027
|
+
throw new errors_2.ToolError('Failed to continue compute goal without the tool', response.status, await response.text(), response.headers.get('retry-after'));
|
|
2028
|
+
const json = await response.json();
|
|
2029
|
+
return json.data;
|
|
2030
|
+
}
|
|
2031
|
+
/**
|
|
2032
|
+
* Pause a compute goal (one-off goals keep their work and continue where they were on resume)
|
|
1882
2033
|
*
|
|
1883
2034
|
* @example
|
|
1884
2035
|
* ```typescript
|
|
@@ -1899,7 +2050,7 @@ class OneShot {
|
|
|
1899
2050
|
return json.data;
|
|
1900
2051
|
}
|
|
1901
2052
|
/**
|
|
1902
|
-
* Resume a paused
|
|
2053
|
+
* Resume a paused compute goal
|
|
1903
2054
|
*
|
|
1904
2055
|
* @example
|
|
1905
2056
|
* ```typescript
|
|
@@ -1922,7 +2073,7 @@ class OneShot {
|
|
|
1922
2073
|
}
|
|
1923
2074
|
/**
|
|
1924
2075
|
* Top up budget for a recurring compute goal, or for a goal (one-off or recurring) that
|
|
1925
|
-
* ended because it ran out of budget (`end_reason: 'budget_exhausted'`).
|
|
2076
|
+
* is awaiting budget or ended because it ran out of budget (`end_reason: 'budget_exhausted'`).
|
|
1926
2077
|
*
|
|
1927
2078
|
* Uses the quote-then-pay flow: the first call gets a 402 with the top-up price,
|
|
1928
2079
|
* the second call (with payment) credits the budget. The amount IS the price.
|
|
@@ -2505,6 +2656,9 @@ class OneShot {
|
|
|
2505
2656
|
const credits = response.status === 402 ? this.parseInsufficientCredits(text) : undefined;
|
|
2506
2657
|
if (credits)
|
|
2507
2658
|
throw credits;
|
|
2659
|
+
const ambiguous = response.status === 500 ? this.parseSettlementAmbiguous(text) : undefined;
|
|
2660
|
+
if (ambiguous)
|
|
2661
|
+
throw ambiguous;
|
|
2508
2662
|
const rejection = [402, 500].includes(response.status) ? this.parsePaymentRejection(text) : undefined;
|
|
2509
2663
|
if (rejection)
|
|
2510
2664
|
throw rejection;
|
|
@@ -2613,6 +2767,20 @@ class OneShot {
|
|
|
2613
2767
|
return undefined;
|
|
2614
2768
|
}
|
|
2615
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
|
+
}
|
|
2616
2784
|
parsePaymentRejection(text) {
|
|
2617
2785
|
let data;
|
|
2618
2786
|
try {
|
|
@@ -2691,7 +2859,7 @@ class OneShot {
|
|
|
2691
2859
|
}
|
|
2692
2860
|
async executeToolRequest(endpoint, options, quoteId) {
|
|
2693
2861
|
// LinkedIn writes act through a human's account and cannot be recalled: always keyed.
|
|
2694
|
-
const reliable = /(?:^|\/)(lookup\/person-(profile|email|email-status)|lookup\/place-permits|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);
|
|
2695
2863
|
const key = options.idempotencyKey ?? (reliable ? ethers_1.ethers.hexlify(ethers_1.ethers.randomBytes(16)) : undefined);
|
|
2696
2864
|
if (options.totalTimeoutMs !== undefined && (!Number.isFinite(options.totalTimeoutMs) || options.totalTimeoutMs <= 0)) {
|
|
2697
2865
|
throw new errors_2.ValidationError('totalTimeoutMs must be positive', 'totalTimeoutMs');
|
|
@@ -2804,7 +2972,7 @@ class OneShot {
|
|
|
2804
2972
|
if (duplicate?.error === 'duplicate_request' && typeof duplicate.details?.request_id === 'string') {
|
|
2805
2973
|
response = Response.json({ request_id: duplicate.details.request_id, receipt_id: duplicate.details.receipt_id,
|
|
2806
2974
|
status: 'processing', tool: 'linkedin', linkedin: { action_request_id: duplicate.details.action_request_id,
|
|
2807
|
-
invitation_id: duplicate.details.invitation_id } }, { status: 202 });
|
|
2975
|
+
invitation_id: duplicate.details.invitation_id, scheduled_for: null } }, { status: 202 });
|
|
2808
2976
|
}
|
|
2809
2977
|
}
|
|
2810
2978
|
if (!response.ok) {
|
|
@@ -2820,7 +2988,7 @@ class OneShot {
|
|
|
2820
2988
|
if (!wait) {
|
|
2821
2989
|
return { ...result, idempotencyKey };
|
|
2822
2990
|
}
|
|
2823
|
-
const completed = await this.pollJob(result.request_id, timeout, signal, onStatusUpdate, waitForPhones ? { waitForPhones, phoneTimeoutSec } : undefined, endpoint);
|
|
2991
|
+
const completed = await this.pollJob(result.request_id, timeout ?? tool_timeouts_generated_1.TOOL_TIMEOUTS[endpoint], signal, onStatusUpdate, waitForPhones ? { waitForPhones, phoneTimeoutSec } : undefined, endpoint);
|
|
2824
2992
|
if (completed && typeof completed === 'object' && !Array.isArray(completed)) {
|
|
2825
2993
|
return { ...completed, request_id: context.requestId, receipt_id: completed.receipt_id ?? context.receiptId, idempotencyKey };
|
|
2826
2994
|
}
|
|
@@ -3260,7 +3428,7 @@ class OneShot {
|
|
|
3260
3428
|
// Identity Verification requires wallet proof on the payment leg too, to recover keyed submissions.
|
|
3261
3429
|
const provenPayment = /(?:^|\/)identity\/start$/.test(endpoint);
|
|
3262
3430
|
const askedForProof = !!extraHeaders?.['x-agent-proof'];
|
|
3263
|
-
const proofScope = /(lookup\/person-(profile|email|email-status)|lookup\/place-permits|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)
|
|
3264
3432
|
? 'submit'
|
|
3265
3433
|
: askedForProof ? 'read' : 'write';
|
|
3266
3434
|
const headers = {
|
|
@@ -3431,14 +3599,22 @@ class OneShot {
|
|
|
3431
3599
|
}
|
|
3432
3600
|
catch (err) {
|
|
3433
3601
|
// Keyed writes may already be queued server-side after a transport failure: keep the reservation.
|
|
3434
|
-
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))
|
|
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))
|
|
3435
3603
|
this.releaseUsdcReservation(signed.reservation);
|
|
3436
3604
|
throw err;
|
|
3437
3605
|
}
|
|
3438
|
-
|
|
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))) {
|
|
3439
3608
|
this.releaseUsdcReservation(signed.reservation);
|
|
3609
|
+
}
|
|
3440
3610
|
return resp;
|
|
3441
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
|
+
}
|
|
3442
3618
|
/** Parse the PAYMENT-REQUIRED header from a 402 response into the accepted requirements and Bazaar metadata. */
|
|
3443
3619
|
parsePaymentRequired(header) {
|
|
3444
3620
|
if (header) {
|