@oneshot-agent/sdk 0.38.1 → 0.41.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/dist/approvals.d.ts +107 -0
- package/dist/approvals.d.ts.map +1 -0
- package/dist/approvals.js +103 -0
- package/dist/approvals.js.map +1 -0
- package/dist/errors.d.ts +16 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +25 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +75 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +164 -8
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +100 -0
- 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 physical_mail_1 = require("./physical-mail");
|
|
41
41
|
__exportStar(require("./physical-mail"), exports);
|
|
42
|
+
__exportStar(require("./approvals"), exports);
|
|
42
43
|
const deadline_1 = require("./deadline");
|
|
43
44
|
const errors_1 = require("./errors");
|
|
44
45
|
const read_only_1 = require("./providers/read-only");
|
|
@@ -55,6 +56,7 @@ var swap_1 = require("./swap");
|
|
|
55
56
|
Object.defineProperty(exports, "getSwapQuote", { enumerable: true, get: function () { return swap_1.getSwapQuote; } });
|
|
56
57
|
Object.defineProperty(exports, "executeSwap", { enumerable: true, get: function () { return swap_1.executeSwap; } });
|
|
57
58
|
__exportStar(require("./errors"), exports);
|
|
59
|
+
const approvals_1 = require("./approvals");
|
|
58
60
|
// Receipt verification (`canonicalizeReceipt`, `verifyReceipt`,
|
|
59
61
|
// `receiptFromWire`, and their supporting types `SignableReceipt`,
|
|
60
62
|
// `ReceiptSignatureFields`, `ReceiptJwk`, `ReceiptJwks`,
|
|
@@ -80,7 +82,7 @@ __exportStar(require("./errors"), exports);
|
|
|
80
82
|
// not removing the field. Import `SignableReceipt` from the `./receipt`
|
|
81
83
|
// subpath directly if you need it.
|
|
82
84
|
// Keep in sync with package.json `version`. Guarded by version.test.ts.
|
|
83
|
-
const SDK_VERSION = '0.
|
|
85
|
+
const SDK_VERSION = '0.41.0';
|
|
84
86
|
/** HTTP poll cadence while push is unconfirmed: fast first checks, settling at 2s. */
|
|
85
87
|
const HTTP_POLL_BACKOFF_MS = [300, 600, 1000, 2000];
|
|
86
88
|
/** HTTP poll cadence once the WebSocket has delivered for this request. */
|
|
@@ -226,6 +228,10 @@ class OneShot {
|
|
|
226
228
|
await this.failFromResponse('Physical mail request failed', response);
|
|
227
229
|
return await response.json();
|
|
228
230
|
}, input => this.executeToolRequest('/v1/tools/physical-mail/send', { ...input, wait: false }));
|
|
231
|
+
/** Ask a named human to decide, and read decisions. See ./approvals.ts. */
|
|
232
|
+
this.approvals = new approvals_1.Approvals((path, method, body, scope) => this.agentJson(path, method, body, scope));
|
|
233
|
+
/** Which paid calls run, are denied, or wait for approval. Wallet sessions write it. */
|
|
234
|
+
this.policy = new approvals_1.ActionPolicies((path, method, body, scope) => this.agentJson(path, method, body, scope));
|
|
229
235
|
/** ETH mode: signed payments not yet observed as settled, keyed by reservation id. */
|
|
230
236
|
this._usdcPending = new Map();
|
|
231
237
|
this._usdcReservationSeq = 0;
|
|
@@ -392,6 +398,10 @@ class OneShot {
|
|
|
392
398
|
// Only the send call carries the key — the quote call has no side effects.
|
|
393
399
|
payload.idempotencyKey = options.idempotencyKey;
|
|
394
400
|
}
|
|
401
|
+
if (options.approvalId) {
|
|
402
|
+
// Releases a send the action policy held (see ApprovalRequiredError).
|
|
403
|
+
payload.approvalId = options.approvalId;
|
|
404
|
+
}
|
|
395
405
|
return this.executeToolRequest('/v1/tools/email/send', payload, quote.quote_id);
|
|
396
406
|
}
|
|
397
407
|
/** List the caller's domain pool with warmup and rotation metadata. */
|
|
@@ -581,6 +591,7 @@ class OneShot {
|
|
|
581
591
|
payload,
|
|
582
592
|
signal: options.signal,
|
|
583
593
|
maxCost: options.maxCost,
|
|
594
|
+
approvalId: options.approvalId,
|
|
584
595
|
quoteTimeoutMs: 120000,
|
|
585
596
|
execTimeoutMs: 60000,
|
|
586
597
|
expectMsg: 'Expected 402 for quote',
|
|
@@ -649,6 +660,7 @@ class OneShot {
|
|
|
649
660
|
payload,
|
|
650
661
|
signal: options.signal,
|
|
651
662
|
maxCost: options.maxCost,
|
|
663
|
+
approvalId: options.approvalId,
|
|
652
664
|
expectMsg: 'Expected 402 for quote',
|
|
653
665
|
totalOf: (ctx) => ctx.total,
|
|
654
666
|
onQuote: (ctx) => this.log(`Voice quote: $${ctx.total} for ${ctx.estimated_duration_minutes}min call`),
|
|
@@ -711,6 +723,7 @@ class OneShot {
|
|
|
711
723
|
payload,
|
|
712
724
|
signal: options.signal,
|
|
713
725
|
maxCost: options.maxCost,
|
|
726
|
+
approvalId: options.approvalId,
|
|
714
727
|
expectMsg: 'Expected 402 for quote',
|
|
715
728
|
totalOf: (ctx) => ctx.total,
|
|
716
729
|
onQuote: (ctx) => this.log(`SMS quote: $${ctx.total} for ${ctx.segment_count} segment(s) to ${recipientCount} recipient(s)`),
|
|
@@ -1133,6 +1146,105 @@ class OneShot {
|
|
|
1133
1146
|
}
|
|
1134
1147
|
return response.json();
|
|
1135
1148
|
}
|
|
1149
|
+
// ---------------------------------------------------------------------------
|
|
1150
|
+
// Inbound voice — the agent's number answers its own calls
|
|
1151
|
+
// ---------------------------------------------------------------------------
|
|
1152
|
+
/**
|
|
1153
|
+
* Get this agent a voice number without placing a call. Returns the existing
|
|
1154
|
+
* active number for free; otherwise buys one and charges the one-time phone
|
|
1155
|
+
* registration fee from prepaid credits (see `topUpCredits`). Too little
|
|
1156
|
+
* credit throws a 402 and buys nothing. Then configure it with
|
|
1157
|
+
* `setInboundVoice`.
|
|
1158
|
+
*
|
|
1159
|
+
* @example
|
|
1160
|
+
* ```typescript
|
|
1161
|
+
* const number = await agent.provisionVoiceNumber();
|
|
1162
|
+
* await agent.setInboundVoice(number.id, { prompt: 'You answer for Acme Dental.' });
|
|
1163
|
+
* ```
|
|
1164
|
+
*/
|
|
1165
|
+
async provisionVoiceNumber() {
|
|
1166
|
+
return this.signedJson('POST', '/v1/tools/voice/numbers', {}, 'write', 'Failed to provision a phone number');
|
|
1167
|
+
}
|
|
1168
|
+
/**
|
|
1169
|
+
* List this agent's phone numbers. A number comes from `provisionVoiceNumber`
|
|
1170
|
+
* or the agent's first outbound voice call.
|
|
1171
|
+
*
|
|
1172
|
+
* @example
|
|
1173
|
+
* ```typescript
|
|
1174
|
+
* const { numbers } = await agent.voiceNumbers();
|
|
1175
|
+
* ```
|
|
1176
|
+
*/
|
|
1177
|
+
async voiceNumbers() {
|
|
1178
|
+
return this.signedJson('GET', '/v1/tools/voice/numbers', undefined, 'read', 'Failed to list phone numbers');
|
|
1179
|
+
}
|
|
1180
|
+
/**
|
|
1181
|
+
* Answer calls to one of the agent's numbers. Replaces the whole config.
|
|
1182
|
+
* Inbound calls are paid from prepaid credits (see `topUpCredits`), per
|
|
1183
|
+
* started minute; a call is declined when credits cannot cover the minimum
|
|
1184
|
+
* fee. Keep the returned `webhook_secret` if you set `webhook_url`.
|
|
1185
|
+
*
|
|
1186
|
+
* @example
|
|
1187
|
+
* ```typescript
|
|
1188
|
+
* await agent.setInboundVoice(numberId, {
|
|
1189
|
+
* prompt: 'You answer for Acme Dental. Book cleanings; take a message for anything else.',
|
|
1190
|
+
* business_hours: { timezone: 'America/New_York', windows: [{ days: ['mon','tue','wed','thu','fri'], start: '09:00', end: '17:00' }] },
|
|
1191
|
+
* after_hours: 'take_message',
|
|
1192
|
+
* });
|
|
1193
|
+
* ```
|
|
1194
|
+
*/
|
|
1195
|
+
async setInboundVoice(phoneNumberId, config) {
|
|
1196
|
+
this.validate(phoneNumberId, 'phoneNumberId');
|
|
1197
|
+
return this.signedJson('PUT', `/v1/tools/voice/numbers/${encodeURIComponent(phoneNumberId)}/inbound`, config, 'write', 'Failed to set inbound voice config');
|
|
1198
|
+
}
|
|
1199
|
+
/** Current inbound config for a number (throws 404 when none is set). */
|
|
1200
|
+
async getInboundVoice(phoneNumberId) {
|
|
1201
|
+
this.validate(phoneNumberId, 'phoneNumberId');
|
|
1202
|
+
return this.signedJson('GET', `/v1/tools/voice/numbers/${encodeURIComponent(phoneNumberId)}/inbound`, undefined, 'read', 'Failed to get inbound voice config');
|
|
1203
|
+
}
|
|
1204
|
+
/** Stop answering calls to a number. The settings are kept, disabled. */
|
|
1205
|
+
async disableInboundVoice(phoneNumberId) {
|
|
1206
|
+
this.validate(phoneNumberId, 'phoneNumberId');
|
|
1207
|
+
return this.signedJson('DELETE', `/v1/tools/voice/numbers/${encodeURIComponent(phoneNumberId)}/inbound`, undefined, 'write', 'Failed to disable inbound voice');
|
|
1208
|
+
}
|
|
1209
|
+
/**
|
|
1210
|
+
* List inbound calls, newest first.
|
|
1211
|
+
*
|
|
1212
|
+
* @example
|
|
1213
|
+
* ```typescript
|
|
1214
|
+
* const { calls } = await agent.inboundCalls({ limit: 20 });
|
|
1215
|
+
* for (const c of calls) console.log(c.from, c.summary);
|
|
1216
|
+
* ```
|
|
1217
|
+
*/
|
|
1218
|
+
async inboundCalls(options = {}) {
|
|
1219
|
+
const qs = this.buildQuery({
|
|
1220
|
+
limit: options.limit || undefined,
|
|
1221
|
+
before: options.before || undefined,
|
|
1222
|
+
phone_number_id: options.phone_number_id || undefined,
|
|
1223
|
+
include_transcript: options.include_transcript ? 'true' : undefined,
|
|
1224
|
+
});
|
|
1225
|
+
return this.signedJson('GET', `/v1/tools/voice/inbound${qs ? `?${qs}` : ''}`, undefined, 'read', 'Failed to list inbound calls');
|
|
1226
|
+
}
|
|
1227
|
+
/** One inbound call, with transcript. */
|
|
1228
|
+
async inboundCall(callId) {
|
|
1229
|
+
this.validate(callId, 'callId');
|
|
1230
|
+
return this.signedJson('GET', `/v1/tools/voice/inbound/${encodeURIComponent(callId)}`, undefined, 'read', 'Failed to get inbound call');
|
|
1231
|
+
}
|
|
1232
|
+
/** Signed free request returning the `data` field of `{ success, data }`. */
|
|
1233
|
+
async signedJson(method, path, body, scope, failure) {
|
|
1234
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
1235
|
+
method,
|
|
1236
|
+
headers: {
|
|
1237
|
+
...(body !== undefined ? { 'Content-Type': 'application/json' } : {}),
|
|
1238
|
+
...(await this.signedReadHeaders(scope)),
|
|
1239
|
+
},
|
|
1240
|
+
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
1241
|
+
});
|
|
1242
|
+
if (!response.ok) {
|
|
1243
|
+
throw new errors_2.ToolError(failure, response.status, await response.text());
|
|
1244
|
+
}
|
|
1245
|
+
const json = await response.json();
|
|
1246
|
+
return (json.data ?? json);
|
|
1247
|
+
}
|
|
1136
1248
|
/**
|
|
1137
1249
|
* Get a specific inbound SMS message
|
|
1138
1250
|
*
|
|
@@ -1904,6 +2016,19 @@ class OneShot {
|
|
|
1904
2016
|
* console.log(`${b.spent_today_usdc} of ${b.daily_usdc} spent, resets ${b.resets_at}`);
|
|
1905
2017
|
* ```
|
|
1906
2018
|
*/
|
|
2019
|
+
/** Signed-proof JSON request to a free agent route; returns `data`. Errors map through failFromResponse. */
|
|
2020
|
+
async agentJson(path, method, body, scope = 'read') {
|
|
2021
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
2022
|
+
method,
|
|
2023
|
+
headers: { 'Content-Type': 'application/json', ...(await this.signedReadHeaders(scope)) },
|
|
2024
|
+
...(body === undefined || method === 'GET' ? {} : { body: JSON.stringify(body) }),
|
|
2025
|
+
signal: AbortSignal.timeout(30000),
|
|
2026
|
+
});
|
|
2027
|
+
if (!response.ok)
|
|
2028
|
+
await this.failFromResponse(`${method} ${path.split('?')[0]} failed`, response);
|
|
2029
|
+
const json = await response.json();
|
|
2030
|
+
return (json && typeof json === 'object' && 'data' in json ? json.data : json);
|
|
2031
|
+
}
|
|
1907
2032
|
async budgets() {
|
|
1908
2033
|
const response = await fetch(`${this.baseUrl}/v1/agents/me/budgets`, {
|
|
1909
2034
|
headers: await this.signedReadHeaders(),
|
|
@@ -2018,6 +2143,9 @@ class OneShot {
|
|
|
2018
2143
|
const rejection = [402, 500].includes(response.status) ? this.parsePaymentRejection(text) : undefined;
|
|
2019
2144
|
if (rejection)
|
|
2020
2145
|
throw rejection;
|
|
2146
|
+
const policy = response.status === 403 ? this.parsePolicyRejection(text) : undefined;
|
|
2147
|
+
if (policy)
|
|
2148
|
+
throw policy;
|
|
2021
2149
|
const budget = response.status === 403 ? this.parseBudgetRejection(text) : undefined;
|
|
2022
2150
|
if (budget)
|
|
2023
2151
|
throw budget;
|
|
@@ -2098,6 +2226,23 @@ class OneShot {
|
|
|
2098
2226
|
* "my own budget stopped this" separately from an auth failure or a payment
|
|
2099
2227
|
* rejection. Any other 403 falls through to ToolError.
|
|
2100
2228
|
*/
|
|
2229
|
+
/** 403 from the action-policy gate: held for approval, or denied. */
|
|
2230
|
+
parsePolicyRejection(text) {
|
|
2231
|
+
let body;
|
|
2232
|
+
try {
|
|
2233
|
+
body = JSON.parse(text);
|
|
2234
|
+
}
|
|
2235
|
+
catch {
|
|
2236
|
+
return undefined;
|
|
2237
|
+
}
|
|
2238
|
+
if (body?.error === 'approval_required' && typeof body.approval_id === 'string') {
|
|
2239
|
+
return new errors_2.ApprovalRequiredError(body.message ?? 'This call needs a human approval', body.approval_id, body.approval, body.approval?.policy_rule ?? undefined);
|
|
2240
|
+
}
|
|
2241
|
+
if (body?.error === 'action_denied_by_policy') {
|
|
2242
|
+
return new errors_2.ActionDeniedError(body.message ?? 'Denied by the action policy', body.rule);
|
|
2243
|
+
}
|
|
2244
|
+
return undefined;
|
|
2245
|
+
}
|
|
2101
2246
|
parseBudgetRejection(text) {
|
|
2102
2247
|
try {
|
|
2103
2248
|
const body = JSON.parse(text);
|
|
@@ -2163,6 +2308,12 @@ class OneShot {
|
|
|
2163
2308
|
return undefined;
|
|
2164
2309
|
return { 'Idempotency-Key': idempotencyKey };
|
|
2165
2310
|
}
|
|
2311
|
+
/** X-Approval-Id, sent on both legs; the server reads it only on the paid leg. */
|
|
2312
|
+
approvalHeader(approvalId) {
|
|
2313
|
+
if (!approvalId)
|
|
2314
|
+
return undefined;
|
|
2315
|
+
return { 'X-Approval-Id': approvalId };
|
|
2316
|
+
}
|
|
2166
2317
|
async readReliabilityJson(path, signed, allowDegraded = false) {
|
|
2167
2318
|
const scope = (0, deadline_1.deadlineScope)(undefined, signed ? 10000 : 5000);
|
|
2168
2319
|
try {
|
|
@@ -2216,11 +2367,13 @@ class OneShot {
|
|
|
2216
2367
|
}
|
|
2217
2368
|
}
|
|
2218
2369
|
async executeToolRequestImpl(endpoint, options, quoteId, context = { phase: "initialization" }) {
|
|
2219
|
-
const { totalTimeoutMs, onRequestCreated, onAccepted, signal, onStatusUpdate, wait = true, waitForPhones, phoneTimeoutSec, idempotencyKey, maxCost, ...payload } = options;
|
|
2370
|
+
const { totalTimeoutMs, onRequestCreated, onAccepted, signal, onStatusUpdate, wait = true, waitForPhones, phoneTimeoutSec, idempotencyKey, approvalId, maxCost, ...payload } = options;
|
|
2220
2371
|
const extraHeaders = {
|
|
2221
2372
|
...this.maxCostHeader(maxCost),
|
|
2222
2373
|
...this.idempotencyHeader(idempotencyKey),
|
|
2223
2374
|
};
|
|
2375
|
+
// Releases a call the action policy held (ToolOptions.approvalId).
|
|
2376
|
+
Object.assign(extraHeaders, this.approvalHeader(approvalId));
|
|
2224
2377
|
if (payload.memo !== undefined) {
|
|
2225
2378
|
if (typeof payload.memo !== 'string' || payload.memo.trim().length === 0) {
|
|
2226
2379
|
delete payload.memo; // Drop invalid memo silently
|
|
@@ -2329,7 +2482,8 @@ class OneShot {
|
|
|
2329
2482
|
*/
|
|
2330
2483
|
async runQuoteToPay(cfg) {
|
|
2331
2484
|
await this.ensureBudgetsSynced();
|
|
2332
|
-
const
|
|
2485
|
+
const approval = this.approvalHeader(cfg.approvalId);
|
|
2486
|
+
const quoteResp = await this.makeRequest(cfg.endpoint, cfg.payload, undefined, undefined, cfg.signal, cfg.quoteTimeoutMs, { ...this.maxCostHeader(cfg.maxCost), ...approval });
|
|
2333
2487
|
if (quoteResp.status === 400 && cfg.on400) {
|
|
2334
2488
|
await cfg.on400(quoteResp);
|
|
2335
2489
|
}
|
|
@@ -2344,7 +2498,7 @@ class OneShot {
|
|
|
2344
2498
|
this.assertWithinBudget(cfg.totalOf(quoteData.context));
|
|
2345
2499
|
if (this._accessToken) {
|
|
2346
2500
|
this.checkAbortBeforePayment(cfg.signal);
|
|
2347
|
-
const execResp = await this.retryAsCreditsSession(quoteData, cfg.endpoint, cfg.payload, quoteData.context.quote_id, cfg.signal, cfg.execTimeoutMs);
|
|
2501
|
+
const execResp = await this.retryAsCreditsSession(quoteData, cfg.endpoint, cfg.payload, quoteData.context.quote_id, cfg.signal, cfg.execTimeoutMs, approval);
|
|
2348
2502
|
return { context: quoteData.context, execResp };
|
|
2349
2503
|
}
|
|
2350
2504
|
const paymentInfo = {
|
|
@@ -2357,10 +2511,10 @@ class OneShot {
|
|
|
2357
2511
|
token: { address: quoteData.payment_request.token_address, symbol: 'USDC', decimals: 6 }
|
|
2358
2512
|
};
|
|
2359
2513
|
this.checkAbortBeforePayment(cfg.signal);
|
|
2360
|
-
const { accepted, resource, extensions } = await this.getAcceptedRequirements(quoteResp, cfg.endpoint, cfg.payload, quoteData.context.quote_id, cfg.signal);
|
|
2514
|
+
const { accepted, resource, extensions } = await this.getAcceptedRequirements(quoteResp, cfg.endpoint, cfg.payload, quoteData.context.quote_id, cfg.signal, approval);
|
|
2361
2515
|
paymentInfo.amount = this.chargeAmount(accepted, quoteData.payment_request.amount);
|
|
2362
2516
|
const signed = await this.signPaymentAuthorization(paymentInfo, accepted, resource, extensions);
|
|
2363
|
-
const execResp = await this.makePaidRequest(signed, cfg.endpoint, cfg.payload, quoteData.context.quote_id, cfg.signal, cfg.execTimeoutMs);
|
|
2517
|
+
const execResp = await this.makePaidRequest(signed, cfg.endpoint, cfg.payload, quoteData.context.quote_id, cfg.signal, cfg.execTimeoutMs, approval);
|
|
2364
2518
|
return { context: quoteData.context, execResp };
|
|
2365
2519
|
}
|
|
2366
2520
|
/**
|
|
@@ -2937,13 +3091,15 @@ class OneShot {
|
|
|
2937
3091
|
* Quote-based routes don't include payment-required header on the initial 402.
|
|
2938
3092
|
* If missing, probe with quote ID (no payment) to trigger the x402 middleware's 402.
|
|
2939
3093
|
*/
|
|
2940
|
-
async getAcceptedRequirements(initialResp, endpoint, payload, quoteId, signal) {
|
|
3094
|
+
async getAcceptedRequirements(initialResp, endpoint, payload, quoteId, signal, extraHeaders) {
|
|
2941
3095
|
const header = initialResp.headers.get('payment-required');
|
|
2942
3096
|
if (header) {
|
|
2943
3097
|
return this.parsePaymentRequired(header);
|
|
2944
3098
|
}
|
|
2945
3099
|
// Probe: send quote ID without payment to get x402 middleware's 402
|
|
2946
|
-
|
|
3100
|
+
// Carries X-Approval-Id: a policy-held call's probe must reach the 402,
|
|
3101
|
+
// not be held again.
|
|
3102
|
+
const probeResp = await this.makeRequest(endpoint, payload, undefined, quoteId, signal, undefined, extraHeaders);
|
|
2947
3103
|
return this.parsePaymentRequired(probeResp.headers.get('payment-required'));
|
|
2948
3104
|
}
|
|
2949
3105
|
async signPaymentAuthorization(paymentInfo, accepted, resource, extensions) {
|