@oneshot-agent/sdk 0.56.0 → 0.58.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 +145 -842
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -2
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +59 -3
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,912 +1,215 @@
|
|
|
1
1
|
# @oneshot-agent/sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Your agent can think. This lets it act: send email, make calls, research,
|
|
4
|
+
buy, build sites. It pays per action, gets a signed receipt, and spends
|
|
5
|
+
nothing you did not allow.
|
|
4
6
|
|
|
5
|
-
##
|
|
7
|
+
## Install
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
10
|
npm install @oneshot-agent/sdk
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
No code? Use the MCP server in Claude, Cursor or Claude Code:
|
|
14
|
+
`npx -y @oneshot-agent/mcp-server` ([setup](https://docs.oneshotagent.com/sdk/mcp)).
|
|
15
|
+
Teach your coding agent the tools: `npx skills add oneshot-agent/agent-skills`
|
|
16
|
+
([skills](https://github.com/oneshot-agent/agent-skills)).
|
|
12
17
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
npx -y @oneshot-agent/mcp-server
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
See the [MCP Server documentation](https://docs.oneshotagent.com/sdk/mcp) for setup instructions.
|
|
20
|
-
|
|
21
|
-
### Teach your coding agent to use OneShot
|
|
22
|
-
|
|
23
|
-
Install the OneShot [Agent Skills](https://skills.sh) into Claude Code, Cursor, Codex, and
|
|
24
|
-
[70+ other agents](https://github.com/vercel-labs/skills#supported-agents) so they know how to
|
|
25
|
-
call the SDK and MCP tools:
|
|
26
|
-
|
|
27
|
-
```bash
|
|
28
|
-
# All OneShot skills
|
|
29
|
-
npx skills add oneshot-agent/agent-skills
|
|
30
|
-
|
|
31
|
-
# Or just what you need (e.g. setup + email)
|
|
32
|
-
npx skills add oneshot-agent/agent-skills --skill oneshot --skill oneshot-email
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Skills available: `oneshot` (setup/auth), `oneshot-email`, `oneshot-messaging`,
|
|
36
|
-
`oneshot-research`, `oneshot-enrichment`, `oneshot-commerce`, `oneshot-browser`,
|
|
37
|
-
`oneshot-build`, `oneshot-compute`, `soul-markets`. Source:
|
|
38
|
-
[oneshot-agent/agent-skills](https://github.com/oneshot-agent/agent-skills).
|
|
39
|
-
|
|
40
|
-
## Quick Start
|
|
18
|
+
## Start
|
|
41
19
|
|
|
42
20
|
```typescript
|
|
43
21
|
import { OneShot } from '@oneshot-agent/sdk';
|
|
44
22
|
|
|
45
|
-
const agent = new OneShot({
|
|
46
|
-
privateKey: process.env.AGENT_PRIVATE_KEY!
|
|
47
|
-
});
|
|
23
|
+
const agent = new OneShot({ privateKey: process.env.AGENT_PRIVATE_KEY! });
|
|
48
24
|
|
|
49
|
-
|
|
50
|
-
await agent.email({
|
|
51
|
-
to: 'user@example.com',
|
|
52
|
-
subject: 'Hello',
|
|
53
|
-
body: 'Hello World!'
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
// Make a voice call
|
|
57
|
-
const call = await agent.voice({
|
|
58
|
-
objective: 'Call the restaurant to make a reservation for 2 at 7pm',
|
|
59
|
-
target_number: '+14155551234'
|
|
60
|
-
});
|
|
61
|
-
console.log(call.transcript);
|
|
62
|
-
|
|
63
|
-
// Send SMS
|
|
64
|
-
await agent.sms({
|
|
65
|
-
message: 'Your order has shipped!',
|
|
66
|
-
to_number: '+14155551234'
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
// Build a website
|
|
70
|
-
const site = await agent.build({
|
|
71
|
-
type: 'saas',
|
|
72
|
-
product: {
|
|
73
|
-
name: 'TaskFlow',
|
|
74
|
-
description: 'AI-powered task management for remote teams'
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
console.log('Live at:', site.production_url);
|
|
78
|
-
|
|
79
|
-
// Research
|
|
25
|
+
await agent.email({ to: 'user@example.com', subject: 'Hello', body: 'Hello world.' });
|
|
80
26
|
const report = await agent.research({ topic: 'AI agents', depth: 'deep' });
|
|
81
|
-
|
|
82
|
-
// Check balance
|
|
83
|
-
const balance = await agent.getBalance(agent.usdcAddress);
|
|
27
|
+
const usdc = await agent.getBalance(agent.usdcAddress);
|
|
84
28
|
```
|
|
85
29
|
|
|
86
|
-
|
|
30
|
+
Paid calls quote, pay and wait for the result. You write one line.
|
|
87
31
|
|
|
88
|
-
|
|
32
|
+
## What it does
|
|
89
33
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
34
|
+
| Area | Methods |
|
|
35
|
+
|------|---------|
|
|
36
|
+
| [Talk](https://docs.oneshotagent.com/api-reference/email/send) | `email` `voice` `sms` `inboxList` `inboxGet` `smsInboxList` `smsInboxGet` `notifications` `markNotificationRead` |
|
|
37
|
+
| [Send from your domains](https://docs.oneshotagent.com/api-reference/email/domains) | `listDomains` `pauseDomain` `resumeDomain` |
|
|
38
|
+
| [Find people and companies](https://docs.oneshotagent.com/api-reference/research/person-find) | `personFind` `personProfile` `personEmail` `personEmailStatus` `personDossier` `personSocialAccounts` `personArticles` `personPosts` `personInterests` `personFollowers` `companyFind` `companyProfile` `businessFind` `businessResolve` `recordSolicitationsFind` |
|
|
39
|
+
| [Think](https://docs.oneshotagent.com/api-reference/research) | `research` `predict` `solve` |
|
|
40
|
+
| [Web and buying](https://docs.oneshotagent.com/api-reference/web-search) | `webSearch` `webRead` `productFind` `commerceBuy` `physicalMail` |
|
|
41
|
+
| [Browser](https://docs.oneshotagent.com/api-reference/browser/create) | `browser` `createBrowserProfile` `startBrowserProfileSetup` `createBrowserProfileSetupLink` `getBrowserProfileSetup` `finishBrowserProfileSetup` `listBrowserProfiles` `deleteBrowserProfile` |
|
|
42
|
+
| [Connected systems](https://docs.oneshotagent.com/api-reference/connections/overview) | `listConnections`, then `route('POST', '/v1/tools/ledger/invoices', { paid: true, body: { connection_id } })` for each read |
|
|
43
|
+
| [LinkedIn](https://docs.oneshotagent.com/api-reference/linkedin/overview) | `linkedinConnect` `linkedinInvite` `linkedinWithdrawInvitation` `linkedinMessage` `linkedinReply` `reconnectLinkedInAccount` and more |
|
|
44
|
+
| [Build](https://docs.oneshotagent.com/api-reference/build/create) | `build` `updateBuild` |
|
|
45
|
+
| [Goals](https://docs.oneshotagent.com/api-reference/compute/create) | `compute` `waitForComputeGoal` `getComputeGoal` `getComputeTasks` `getComputeOutputs` `respondToComputeTask` `pauseComputeGoal` `resumeComputeGoal` `cancelComputeGoal` `fundComputeGoal` |
|
|
46
|
+
| [Money and receipts](https://docs.oneshotagent.com/api-reference/analytics/overview) | `getBalance` `getUnifiedBalance` `budgets` `topUpCredits` `spendBreakdown` `rocs` `receiptsList` `tagReceiptValue` |
|
|
47
|
+
| Anything else | `tool(name, input)` for any tool, `route(method, path, { params, query, body, paid })` for any declared route |
|
|
94
48
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
Set `currency: 'ETH'` and the SDK keeps the wallet funded in USDC for you:
|
|
98
|
-
|
|
99
|
-
- Before each payment it reads the wallet's USDC and subtracts payments it has
|
|
100
|
-
signed in the last ~90s that may not have settled yet. If that covers the
|
|
101
|
-
charge, no swap happens — the call costs one `eth_call`.
|
|
102
|
-
- Otherwise it swaps ETH→USDC on Uniswap V3 (Base mainnet) for
|
|
103
|
-
`swapBufferMultiplier` × the charge (default 10, range 1–1000), so one swap
|
|
104
|
-
covers many calls. If your provider exposes `getBalance` and the wallet's ETH
|
|
105
|
-
cannot cover the buffer, it swaps just the shortfall (a send-only provider
|
|
106
|
-
always gets the buffered amount). Set `swapBufferMultiplier: 1` for
|
|
107
|
-
exact-shortfall swaps.
|
|
108
|
-
- Concurrent calls from one instance are serialized around the swap, so a burst
|
|
109
|
-
never double-swaps or races the wallet nonce.
|
|
110
|
-
|
|
111
|
-
Run one process per wallet in ETH mode: each process keeps its own in-flight
|
|
112
|
-
ledger. ETH mode requires a wallet provider with `sendTransaction` and is Base
|
|
113
|
-
mainnet only. CDP wallets are not supported in ETH mode: `cdp` combined with
|
|
114
|
-
`currency: 'ETH'` throws a `ValidationError` (fund the CDP wallet with USDC).
|
|
115
|
-
|
|
116
|
-
## Available Methods
|
|
117
|
-
|
|
118
|
-
| Method | Description |
|
|
119
|
-
|--------|-------------|
|
|
120
|
-
| **Communication** | |
|
|
121
|
-
| `email()` | Send emails with attachments |
|
|
122
|
-
| `voice()` | Make phone calls |
|
|
123
|
-
| `sms()` | Send SMS messages |
|
|
124
|
-
| `inboxList()` | List inbound emails |
|
|
125
|
-
| `inboxGet()` | Get email by ID |
|
|
126
|
-
| `smsInboxList()` | List inbound SMS |
|
|
127
|
-
| `smsInboxGet()` | Get SMS by ID |
|
|
128
|
-
| `notifications()` | List agent notifications |
|
|
129
|
-
| `markNotificationRead()` | Mark notification as read |
|
|
130
|
-
| **Domain Pool** | |
|
|
131
|
-
| `listDomains()` | List your sending domains with warmup/rotation state |
|
|
132
|
-
| `pauseDomain()` | Take a domain out of rotation |
|
|
133
|
-
| `resumeDomain()` | Put a paused domain back into rotation |
|
|
134
|
-
| **Research & People** | |
|
|
135
|
-
| `research()` | Deep web research |
|
|
136
|
-
| `predict(input)` | Simulate uncertain outcomes or forecast a time series with uncertainty ranges and consistency checks |
|
|
137
|
-
| `solve(input)` | Solve a routing or packing problem with constraint checks and conflicts; `solution` is typed by `problem` |
|
|
138
|
-
| `personFind()` | Search people by criteria |
|
|
139
|
-
| `personProfile()` | Enrich from name+company, email, phone or a LinkedIn/X/GitHub URL |
|
|
140
|
-
| `personEmail()` | Find email for a person |
|
|
141
|
-
| `personEmailStatus()` | Verify email deliverability |
|
|
142
|
-
| `companyFind()` | Search companies by name, domain, industry, location, size, funding |
|
|
143
|
-
| `companyProfile()` | Enrich a company from domain, name, LinkedIn URL, or ticker |
|
|
144
|
-
| `businessFind()` | Discover local businesses (restaurants, contractors, practices) by category × location |
|
|
145
|
-
| `businessResolve()` | Business name + address → website domain, phone, status, confidence |
|
|
146
|
-
| `recordSolicitationsFind()` | Federal Sources Sought / Presolicitation notices by NAICS with the contracting officer's contact |
|
|
147
|
-
| `personDossier()` | Person profile, known social accounts, articles, posts and written summary |
|
|
148
|
-
| `personSocialAccounts()` | Find all social accounts |
|
|
149
|
-
| `personArticles()` | Find articles about a person |
|
|
150
|
-
| `personPosts()` | Recent social posts with engagement |
|
|
151
|
-
| `personInterests()` | Analyze interests across categories |
|
|
152
|
-
| `personFollowers()` | Map followers, following, replies |
|
|
153
|
-
| **Web & Commerce** | |
|
|
154
|
-
| `webSearch()` | Search the web |
|
|
155
|
-
| `webRead()` | Read any URL as markdown + screenshot |
|
|
156
|
-
| `commerceBuy()` | Purchase products |
|
|
157
|
-
| `productFind()` | Search products |
|
|
158
|
-
| **Browser** | |
|
|
159
|
-
| `browser()` | Autonomous browser — navigate, click, extract |
|
|
160
|
-
| `createBrowserProfile()` | Create a persistent browser profile |
|
|
161
|
-
| `startBrowserProfileSetup(id, loginUrl)` | Start interactive account login / 2FA; returns the single-use `setup_url` |
|
|
162
|
-
| `createBrowserProfileSetupLink(id, startUrl?)` | Mint a fresh single-use `setup_url` for an open setup |
|
|
163
|
-
| `getBrowserProfileSetup(id)` | Get setup status; `completed_at` once the person pressed Done |
|
|
164
|
-
| `finishBrowserProfileSetup(id)` | Save profile and inspect stored cookie metadata |
|
|
165
|
-
| `listBrowserProfiles()` | List saved browser profiles |
|
|
166
|
-
| `deleteBrowserProfile()` | Delete a browser profile |
|
|
167
|
-
| **Build** | |
|
|
168
|
-
| `build()` | Build and deploy production websites |
|
|
169
|
-
| `updateBuild()` | Update an existing website |
|
|
170
|
-
| **Compute (Agentic Goals)** | |
|
|
171
|
-
| `compute()` | Launch autonomous multi-step goal |
|
|
172
|
-
| `getComputeGoal()` | Get goal status and progress |
|
|
173
|
-
| `waitForComputeGoal()` | Wait for a goal with optional status callbacks |
|
|
174
|
-
| `getComputeTasks()` | List tasks for a goal |
|
|
175
|
-
| `getComputeOutputs()` / `getComputeOutput()` | List what a goal's tasks produced, and read one output by key |
|
|
176
|
-
| `getComputeBudget()` | Check remaining budget for a goal |
|
|
177
|
-
| `respondToComputeTask()` | Provide input to a pending task |
|
|
178
|
-
| `pauseComputeGoal()` | Pause a running goal |
|
|
179
|
-
| `resumeComputeGoal()` | Resume a paused goal |
|
|
180
|
-
| `cancelComputeGoal()` | Cancel a goal |
|
|
181
|
-
| `fundComputeGoal()` | Add funds to a goal's budget |
|
|
182
|
-
| **Analytics & Balance** | |
|
|
183
|
-
| `getBalance()` | Check token balance |
|
|
184
|
-
| `getUnifiedBalance()` | Get balance across all chains |
|
|
185
|
-
| `spendBreakdown()` | Spending breakdown by tool/period |
|
|
186
|
-
| `rocs()` | Return on compute stats |
|
|
187
|
-
| `receiptsList()` | List payment receipts (`limit`/`offset` or `since`/`until` window) |
|
|
188
|
-
| `tagReceiptValue()` | Tag a receipt with business value (by `receipt_id` or `request_id`) |
|
|
189
|
-
| **Any route** | |
|
|
190
|
-
| `route(method, path, { params, query, body, paid })` | Call a declared route by method and path, with `{name}` path parameters, a query and a JSON body (an object field is sent as it is). A free route is signed like the other free calls (read proof for `GET`, write proof otherwise; the bearer token in an access-token session). `paid: true` (POST) goes through the quote-to-pay flow of `tool()`. The generated MCP route tools use it |
|
|
191
|
-
|
|
192
|
-
## Configuration
|
|
49
|
+
## Money
|
|
193
50
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
defaultHeaders?: Record<string, string>; // Sent on every request
|
|
202
|
-
baseUrl?: string; // Override API URL
|
|
203
|
-
rpcUrl?: string; // Override RPC URL
|
|
204
|
-
debug?: boolean; // Enable logging
|
|
205
|
-
logger?: (msg: string) => void;
|
|
206
|
-
budgets?: { // Spend budget (see below)
|
|
207
|
-
daily?: number;
|
|
208
|
-
perTransaction?: number;
|
|
209
|
-
alertAt?: number;
|
|
210
|
-
pauseAt?: number;
|
|
211
|
-
};
|
|
212
|
-
alerts?: { email?: string };
|
|
213
|
-
}
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
## Spend Budgets
|
|
51
|
+
- It pays in USDC on Base mainnet (`agent.chainId` is `8453`). Fund the wallet first.
|
|
52
|
+
- A call costs what the quote says, or it does not run.
|
|
53
|
+
- `maxCost` sets your ceiling. Above it, the SDK refuses to sign.
|
|
54
|
+
- `currency: 'ETH'` pays from ETH. The SDK swaps into USDC only when the wallet
|
|
55
|
+
is short, buys `swapBufferMultiplier` × the charge (default 10) so one swap
|
|
56
|
+
covers many calls. Calls in one process never double-swap. Run one process
|
|
57
|
+
per wallet. Not with `cdp`.
|
|
217
58
|
|
|
218
|
-
|
|
59
|
+
**Budgets** cap what a runaway loop can spend.
|
|
219
60
|
|
|
220
61
|
```typescript
|
|
221
62
|
const agent = await OneShot.create({
|
|
222
63
|
cdp: true,
|
|
223
|
-
budgets: {
|
|
224
|
-
daily: 50, // max USDC per UTC day
|
|
225
|
-
perTransaction: 5, // max USDC for any single call
|
|
226
|
-
alertAt: 0.8, // warn at 80% of daily
|
|
227
|
-
pauseAt: 1.0, // stop paying at 100%
|
|
228
|
-
},
|
|
64
|
+
budgets: { daily: 50, perTransaction: 5, alertAt: 0.8, pauseAt: 1.0 },
|
|
229
65
|
alerts: { email: 'you@example.com' },
|
|
230
66
|
});
|
|
231
67
|
```
|
|
232
68
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
At `alertAt` you get a notification (in-app via `agent.notifications()`, plus email
|
|
239
|
-
if you set one). At `pauseAt` paid calls are rejected with a `BudgetExceededError`
|
|
240
|
-
until the window resets at the next UTC midnight:
|
|
241
|
-
|
|
242
|
-
```typescript
|
|
243
|
-
try {
|
|
244
|
-
await agent.research({ topic: '...' });
|
|
245
|
-
} catch (err) {
|
|
246
|
-
if (err instanceof BudgetExceededError) {
|
|
247
|
-
console.log(`${err.reason} budget hit — $${err.spent} spent, resumes ${err.resetsAt}`);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
Check utilization any time:
|
|
253
|
-
|
|
254
|
-
```typescript
|
|
255
|
-
const b = await agent.budgets();
|
|
256
|
-
// { daily_usdc: 50, spent_today_usdc: '41.20', pct_used: 0.824, resets_at: '...' }
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
Omit `budgets` entirely to leave whatever is stored server-side untouched — agents
|
|
260
|
-
without a budget are unlimited, which is the default.
|
|
261
|
-
|
|
262
|
-
The sync **fails closed**: if the budget can't be confirmed with the server (network
|
|
263
|
-
error, 5xx, rate limit, rejected config) the paid call throws `BudgetSyncError` and is
|
|
264
|
-
not made — proceeding would silently drop the guardrail you asked for. It retries on
|
|
265
|
-
the next paid call. An invalid config (`daily: -1`, `alertAt: 2`) throws `ValidationError`
|
|
266
|
-
at construction.
|
|
267
|
-
|
|
268
|
-
## Authentication
|
|
269
|
-
|
|
270
|
-
Every request identifies you by your wallet address (`X-Agent-ID`). Paid tools additionally settle a signed [x402](https://x402.org) USDC payment.
|
|
271
|
-
|
|
272
|
-
**Read endpoints** — `inboxList` / `inboxGet`, `smsInboxList` / `smsInboxGet`, `notifications` / `markNotificationRead`, `getUnifiedBalance`, and `createBrowserProfile` / `listBrowserProfiles` / `deleteBrowserProfile` — return private, per-agent data. Since a wallet address is public, the SDK proves you actually control it: on each read it signs a short-lived **EIP-712 read proof** and sends it as the `x-agent-proof` header. The API verifies the signature locally and rejects any request whose proof doesn't match the `X-Agent-ID` wallet, so no one can read your inbox/balance by supplying your address.
|
|
273
|
-
|
|
274
|
-
This is fully automatic — you don't do anything beyond providing your `privateKey`. **Requires SDK ≥ 0.25.0.** Older SDKs (or raw HTTP callers) will keep working until the API enables enforcement, after which they must send a valid `x-agent-proof`; upgrade to stay ahead of it.
|
|
275
|
-
|
|
276
|
-
If the API rejects a proof as expired or future-dated with `401 proof_required` and a valid `server_time`, the TypeScript SDK corrects this client's API proof clock and retries once with a fresh nonce and the same scope. Subsequent proofs use that correction. A second refusal or any other authentication error is returned to the caller. Access-token sessions do not sign or retry proofs; payment timestamps and other services' proof clocks are unchanged. This recovery requires a release containing the #2240 fix; SDK 0.53.1 does not include it.
|
|
277
|
-
|
|
278
|
-
### Access tokens (credits-only sessions)
|
|
279
|
-
|
|
280
|
-
Some code can't hold your wallet key — a hosted MCP client such as Grok Bot, a cloud
|
|
281
|
-
runner, a third-party agent platform. For those, mint an **access token** from a wallet
|
|
282
|
-
session and hand it over instead:
|
|
283
|
-
|
|
284
|
-
```typescript
|
|
285
|
-
// From a wallet session (once):
|
|
286
|
-
const { token } = await agent.createAccessToken({ name: 'grok-bot' });
|
|
287
|
-
|
|
288
|
-
// In the hosted environment:
|
|
289
|
-
const hosted = await OneShot.create({ accessToken: token }); // resolves the wallet address
|
|
290
|
-
await hosted.webSearch({ query: '…' }); // billed to the agent's credits
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
An access-token session identifies the agent with the token (no `x-agent-proof`), **pays
|
|
294
|
-
every call from the agent's prepaid credit balance**, and never signs x402. When credits
|
|
295
|
-
can't cover a call it throws `InsufficientCreditsError` (`required`, `balance`,
|
|
296
|
-
`shortfall`) — top up from the wallet session with `agent.topUpCredits(amount)` (pays
|
|
297
|
-
the same USDC via x402) and retry. Budgets set on the agent still apply, with credit-funded
|
|
298
|
-
spend counted; they are **read-only** from a token session (`agent.budgets()` works,
|
|
299
|
-
passing `budgets` to the constructor throws). A token cannot mint, list, or revoke
|
|
300
|
-
tokens; do that from the wallet session (`listAccessTokens()`, `revokeAccessToken(id)`).
|
|
301
|
-
`accessToken` is exclusive with `privateKey` / `cdp` / `walletProvider` and with
|
|
302
|
-
`currency: 'ETH'`. **Requires SDK ≥ 0.33.0.**
|
|
303
|
-
|
|
304
|
-
## Tool Options
|
|
69
|
+
- Budgets live on the server. Every process using the wallet shares them.
|
|
70
|
+
- If the server cannot confirm them, nothing is paid: `BudgetSyncError`.
|
|
71
|
+
- At `pauseAt`, paid calls throw `BudgetExceededError` until UTC midnight.
|
|
72
|
+
- Omit `budgets` and the stored ones stay as they are. No budget means no limit.
|
|
305
73
|
|
|
306
|
-
|
|
74
|
+
**Access tokens** are for code that must not hold the key: a hosted MCP client,
|
|
75
|
+
a cloud runner, someone else's platform.
|
|
307
76
|
|
|
308
77
|
```typescript
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
signal?: AbortSignal; // Cancel before payment (see Cancellation section)
|
|
313
|
-
wait?: boolean; // Wait for async jobs (default: true)
|
|
314
|
-
onStatusUpdate?: (status: string, requestId: string) => void;
|
|
315
|
-
}
|
|
78
|
+
const { token } = await agent.createAccessToken({ name: 'grok-bot' }); // from the wallet session
|
|
79
|
+
const hosted = await OneShot.create({ accessToken: token });
|
|
80
|
+
await hosted.webSearch({ query: '…' }); // paid from credits
|
|
316
81
|
```
|
|
317
82
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
`
|
|
321
|
-
|
|
322
|
-
creation `request_id` is an accounting job and does not track compute progress.
|
|
83
|
+
- A token spends prepaid credits only. It never signs a payment.
|
|
84
|
+
- Short of credits, it throws `InsufficientCreditsError` (`required`, `balance`,
|
|
85
|
+
`shortfall`). Top up from the wallet session with `topUpCredits(amount)`.
|
|
86
|
+
- A token reads budgets but cannot change them, and cannot mint or revoke tokens.
|
|
323
87
|
|
|
324
|
-
|
|
325
|
-
const goal = await agent.compute({ objective: 'Research battery recycling', budget_usdc: 5 });
|
|
326
|
-
const result = await agent.waitForComputeGoal(goal.goal_id, {
|
|
327
|
-
timeout: 300,
|
|
328
|
-
onStatusUpdate: (status, goalId) => {
|
|
329
|
-
console.log(`${goalId}: ${status}`);
|
|
330
|
-
},
|
|
331
|
-
});
|
|
332
|
-
```
|
|
88
|
+
## Trust
|
|
333
89
|
|
|
334
|
-
|
|
335
|
-
status and each change from `getComputeGoal`. Consecutive identical statuses are
|
|
336
|
-
suppressed. `completed` is delivered before resolving with the full goal snapshot;
|
|
337
|
-
`failed` or `cancelled` is delivered before rejecting with `JobError`, whose
|
|
338
|
-
`jobId` is the goal ID, `code` is its `end_reason` (or status), and `partialResult`
|
|
339
|
-
contains the snapshot. Callback throws and rejected promises are ignored, and
|
|
340
|
-
async callbacks are not awaited. Omitting the callback preserves these outcomes.
|
|
341
|
-
|
|
342
|
-
`failed` ends the current wait, but `end_reason: 'budget_exhausted'` can be
|
|
343
|
-
reversed by funding. After `fundComputeGoal(goalId, amount)` succeeds, call
|
|
344
|
-
`waitForComputeGoal(goalId, options)` again with the same goal ID to observe the
|
|
345
|
-
revived goal. The rejected wait does not resume automatically.
|
|
346
|
-
|
|
347
|
-
Status strings come directly from the API: goals currently use `active`,
|
|
348
|
-
`awaiting_confirmation`, `paused`, `completed`, `failed`, and `cancelled`, rather
|
|
349
|
-
than job queue/running states. Intermediate changes may be missed between polls.
|
|
350
|
-
`awaiting_confirmation` requires the owner's decision through `confirmComputeGoal`;
|
|
351
|
-
`paused` goals require resumption. Neither ends the wait. Recurring goals are
|
|
352
|
-
observed until the overall goal ends, not just one run. The timeout defaults to
|
|
353
|
-
120 seconds and rejects with `JobTimeoutError`; `Infinity` disables the local
|
|
354
|
-
deadline, and longer finite timeouts retain their full duration. An optional `signal` cancels the
|
|
355
|
-
wait. Timeout or cancellation stops local polling without cancelling the goal.
|
|
356
|
-
Transient goal reads (HTTP 5xx, 429, and network failures) retry on the existing
|
|
357
|
-
300 ms, 600 ms, 1 s, then 2 s polling cadence until the deadline or cancellation.
|
|
358
|
-
Other errors, including 401, 403, and 404, reject immediately.
|
|
359
|
-
|
|
360
|
-
### `maxCost` Support
|
|
361
|
-
|
|
362
|
-
The `maxCost` option is a client-side guard — the SDK compares the quoted price against your limit and throws before signing any payment if it exceeds it. This is supported on tools with variable pricing:
|
|
363
|
-
|
|
364
|
-
| Method | `maxCost` |
|
|
365
|
-
|--------|:---------:|
|
|
366
|
-
| `commerceBuy()` | Supported |
|
|
367
|
-
| `voice()` | Supported |
|
|
368
|
-
| `sms()` | Supported |
|
|
369
|
-
| `build()` / `updateBuild()` | Supported |
|
|
370
|
-
| `browser()` | Supported |
|
|
371
|
-
| All other tools | Not applicable (fixed low-cost pricing) |
|
|
372
|
-
|
|
373
|
-
## Verifying Receipts
|
|
374
|
-
|
|
375
|
-
Signed receipts use Ed25519 over the canonical public receipt fields, the `revision` counter and a SHA-256 digest of `metadata` — nothing private. The response also carries the `signature`, `digest`, `key_id` and `alg` needed to verify; those are not part of the signed bytes, and neither is the caller-supplied `value_tag`. Unsigned receipts return `malformed_receipt`. `verifyReceipt` works offline after fetching the public keys:
|
|
90
|
+
Every paid call returns a receipt, signed with Ed25519. Check it offline:
|
|
376
91
|
|
|
377
92
|
```typescript
|
|
378
93
|
import { verifyReceipt, receiptFromWire } from '@oneshot-agent/sdk/receipt';
|
|
379
94
|
|
|
380
|
-
// `wireReceipt` is whatever GET /v1/analytics/receipts (or the audit export)
|
|
381
|
-
// returned for one receipt — snake_case field names.
|
|
382
|
-
const receipt = receiptFromWire(wireReceipt);
|
|
383
95
|
const jwks = await fetch('https://win.oneshotagent.com/.well-known/oneshot-receipts.json').then(r => r.json());
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
if (result.valid) {
|
|
387
|
-
console.log(`Verified against key ${result.keyId}`);
|
|
388
|
-
} else {
|
|
389
|
-
// reason is one of: 'malformed_receipt' | 'key_not_found' | 'digest_mismatch' | 'signature_invalid'
|
|
390
|
-
console.log(`Verification failed (${result.reason}): ${result.message}`);
|
|
391
|
-
}
|
|
96
|
+
const result = verifyReceipt(receiptFromWire(wireReceipt), jwks);
|
|
97
|
+
// result.valid, or result.reason: 'malformed_receipt' | 'key_not_found' | 'digest_mismatch' | 'signature_invalid'
|
|
392
98
|
```
|
|
393
99
|
|
|
394
|
-
|
|
100
|
+
Or from a shell: `npx @oneshot-agent/sdk receipt.json` (exit `0` valid, `1` invalid, `2` bad input).
|
|
101
|
+
Old keys stay in the key set, so old receipts keep verifying.
|
|
395
102
|
|
|
396
|
-
|
|
103
|
+
Reads of private data (inbox, notifications, balances, browser profiles) carry a
|
|
104
|
+
signed proof that you hold the wallet. A wallet address is public; your inbox is not.
|
|
105
|
+
The SDK signs it for you. If the server says your clock is off, it corrects once and retries.
|
|
397
106
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
```bash
|
|
401
|
-
npx @oneshot-agent/sdk receipt.json
|
|
402
|
-
# or, after installing the package:
|
|
403
|
-
oneshot-verify-receipt receipt.json [jwksUrlOrPath]
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
`npx <package> <args...>` runs that package's single bin directly, passing the trailing args straight to it — so the package name itself is never one of `argv`. `receipt.json` is a receipt object as returned by the API. `jwksUrlOrPath` is an http(s) URL (fetched) or a local file path; it defaults to the production well-known path. Exit code `0` on a valid signature (with version and key printed), `1` on a verification failure (with the reason printed), `2` on a usage/IO or input-shape error. The audit export uses separate `receipt_signature*` fields; the adapter distinguishes those from its settlement-proof `signature`.
|
|
407
|
-
|
|
408
|
-
## Error Handling
|
|
107
|
+
## Failure
|
|
409
108
|
|
|
410
109
|
```typescript
|
|
411
|
-
import {
|
|
412
|
-
ValidationError,
|
|
413
|
-
ToolError,
|
|
414
|
-
JobError,
|
|
415
|
-
JobTimeoutError,
|
|
416
|
-
ContentBlockedError,
|
|
417
|
-
EmergencyNumberError
|
|
418
|
-
} from '@oneshot-agent/sdk';
|
|
110
|
+
import { JobError, JobTimeoutError, ToolError, ValidationError, ContentBlockedError } from '@oneshot-agent/sdk';
|
|
419
111
|
|
|
420
112
|
try {
|
|
421
|
-
await agent.voice({
|
|
422
|
-
objective: 'Make a call',
|
|
423
|
-
target_number: '+14155551234'
|
|
424
|
-
});
|
|
113
|
+
await agent.voice({ objective: 'Book a table for 2 at 7pm', target_number: '+14155551234' });
|
|
425
114
|
} catch (error) {
|
|
426
|
-
if (error instanceof
|
|
427
|
-
//
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
} else if (error instanceof
|
|
431
|
-
//
|
|
432
|
-
console.log(`Emergency number blocked: ${error.blockedNumber}`);
|
|
433
|
-
} else if (error instanceof ValidationError) {
|
|
434
|
-
console.log(`Invalid: ${error.field}`);
|
|
435
|
-
} else if (error instanceof ToolError) {
|
|
436
|
-
console.log(`API error: ${error.statusCode}`);
|
|
437
|
-
} else if (error instanceof JobTimeoutError) {
|
|
438
|
-
console.log(`Timeout: ${error.jobId}`);
|
|
439
|
-
} else if (error instanceof JobError) {
|
|
440
|
-
// An async job failed. `error.code` is a STABLE, machine-readable code —
|
|
441
|
-
// branch on it rather than parsing `error.message`.
|
|
442
|
-
switch (error.code) {
|
|
443
|
-
case 'insufficient_funds':
|
|
444
|
-
console.log('Fund your wallet — do not blind-retry');
|
|
445
|
-
break;
|
|
446
|
-
case 'invalid_input':
|
|
447
|
-
console.log('Fix the request — retrying as-is will not help');
|
|
448
|
-
break;
|
|
449
|
-
case 'rate_limited':
|
|
450
|
-
case 'service_unavailable':
|
|
451
|
-
case 'internal_error':
|
|
452
|
-
console.log('Transient — safe to retry');
|
|
453
|
-
break;
|
|
454
|
-
default:
|
|
455
|
-
console.log(`Job failed (${error.code}): ${error.message}`);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
```
|
|
460
|
-
|
|
461
|
-
### Retry timing for HTTP errors
|
|
462
|
-
|
|
463
|
-
`ToolError.retryAfterMs` and `BudgetSyncError.retryAfterMs` expose the server's
|
|
464
|
-
`Retry-After` header as an optional delay in milliseconds. Non-negative integer
|
|
465
|
-
seconds are multiplied by 1,000; HTTP dates (including the two legacy formats)
|
|
466
|
-
are measured against the client's clock when the error is constructed. Expired
|
|
467
|
-
dates yield `0`. Missing, malformed, or numerically unsafe values yield
|
|
468
|
-
`undefined`. Existing status (`statusCode` on `ToolError`, `status` on
|
|
469
|
-
`BudgetSyncError`) and `responseBody` fields are preserved.
|
|
470
|
-
|
|
471
|
-
```typescript
|
|
472
|
-
try {
|
|
473
|
-
await agent.webSearch({ query: 'latest news' });
|
|
474
|
-
} catch (error) {
|
|
475
|
-
if (error instanceof ToolError && error.statusCode === 429) {
|
|
476
|
-
console.log('Retry delay in ms:', error.retryAfterMs);
|
|
477
|
-
// Schedule a later call according to your application's retry policy.
|
|
115
|
+
if (error instanceof JobError) {
|
|
116
|
+
// Branch on error.code, never on the message.
|
|
117
|
+
if (['rate_limited', 'service_unavailable', 'internal_error'].includes(error.code)) { /* retry */ }
|
|
118
|
+
else { /* fix the input or the funding; retrying as is will not help */ }
|
|
119
|
+
} else if (error instanceof ToolError && error.statusCode === 429) {
|
|
120
|
+
// error.retryAfterMs is the server's hint. The SDK never retries for you.
|
|
478
121
|
}
|
|
479
122
|
throw error;
|
|
480
123
|
}
|
|
481
124
|
```
|
|
482
125
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
`
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
When a `JobError` is thrown, `error.code` is one of:
|
|
491
|
-
`insufficient_funds`, `payment_failed`, `invalid_input`, `content_blocked`,
|
|
492
|
-
`rate_limited`, `service_unavailable`, `service_auth`, `enrichment_exhausted`,
|
|
493
|
-
`target_unreachable`, `source_unsupported`, `checkout_failed`,
|
|
494
|
-
`internal_error`. See the
|
|
495
|
-
[Check Job Status](https://docs.oneshotagent.com/api-reference/status#error-codes)
|
|
496
|
-
reference for the full meaning and recommended reaction for each.
|
|
497
|
-
|
|
498
|
-
## Examples
|
|
499
|
-
|
|
500
|
-
### Build Websites
|
|
501
|
-
|
|
502
|
-
```typescript
|
|
503
|
-
// Build a SaaS landing page
|
|
504
|
-
const site = await agent.build({
|
|
505
|
-
type: 'saas',
|
|
506
|
-
product: {
|
|
507
|
-
name: 'TaskFlow',
|
|
508
|
-
description: 'AI-powered task management for remote teams. Automate workflows, track progress, and collaborate seamlessly.',
|
|
509
|
-
industry: 'Productivity',
|
|
510
|
-
pricing: 'Free tier, Pro $12/mo, Team $29/mo'
|
|
511
|
-
},
|
|
512
|
-
lead_capture: { enabled: true },
|
|
513
|
-
brand: {
|
|
514
|
-
primary_color: '#4F46E5',
|
|
515
|
-
tone: 'professional'
|
|
516
|
-
}
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
console.log('Website URL:', site.production_url);
|
|
520
|
-
console.log('Preview:', site.preview_url);
|
|
521
|
-
|
|
522
|
-
// Update existing website
|
|
523
|
-
const updated = await agent.updateBuild({
|
|
524
|
-
build_id: site.request_id,
|
|
525
|
-
product: {
|
|
526
|
-
name: 'TaskFlow 2.0',
|
|
527
|
-
description: 'Now with AI automation! Task management reimagined.',
|
|
528
|
-
pricing: 'Free tier, Pro $15/mo, Team $35/mo'
|
|
529
|
-
}
|
|
530
|
-
});
|
|
531
|
-
```
|
|
532
|
-
|
|
533
|
-
Build types: `saas`, `portfolio`, `agency`, `personal`, `product`, `funnel`, `restaurant`, `event`
|
|
534
|
-
|
|
535
|
-
### Voice Calls
|
|
536
|
-
|
|
537
|
-
```typescript
|
|
538
|
-
// Simple call
|
|
539
|
-
const call = await agent.voice({
|
|
540
|
-
objective: 'Call to schedule a dentist appointment for next Tuesday',
|
|
541
|
-
target_number: '+14155551234',
|
|
542
|
-
caller_persona: 'A polite assistant scheduling an appointment',
|
|
543
|
-
context: 'Patient prefers morning appointments',
|
|
544
|
-
maxCost: 5
|
|
545
|
-
});
|
|
546
|
-
|
|
547
|
-
console.log('Transcript:', call.transcript);
|
|
548
|
-
console.log('Summary:', call.summary);
|
|
549
|
-
console.log('Success:', call.success_evaluation);
|
|
550
|
-
|
|
551
|
-
// Conference call (multiple numbers)
|
|
552
|
-
const conference = await agent.voice({
|
|
553
|
-
objective: 'Connect the buyer and seller to negotiate the final price',
|
|
554
|
-
target_number: ['+14155551234', '+14155555678'],
|
|
555
|
-
caller_persona: 'A professional meeting facilitator'
|
|
556
|
-
});
|
|
557
|
-
```
|
|
558
|
-
|
|
559
|
-
### SMS
|
|
560
|
-
|
|
561
|
-
```typescript
|
|
562
|
-
// Single recipient
|
|
563
|
-
await agent.sms({
|
|
564
|
-
message: 'Your appointment is confirmed for tomorrow at 10am',
|
|
565
|
-
to_number: '+14155551234'
|
|
566
|
-
});
|
|
567
|
-
|
|
568
|
-
// Multiple recipients (up to 10)
|
|
569
|
-
await agent.sms({
|
|
570
|
-
message: 'Team meeting moved to 3pm',
|
|
571
|
-
to_number: ['+14155551234', '+14155555678', '+14155559012']
|
|
572
|
-
});
|
|
573
|
-
|
|
574
|
-
// Check SMS inbox
|
|
575
|
-
const inbox = await agent.smsInboxList({ limit: 10 });
|
|
576
|
-
for (const msg of inbox.messages) {
|
|
577
|
-
console.log(`From ${msg.from}: ${msg.body}`);
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
// Get specific message
|
|
581
|
-
const msg = await agent.smsInboxGet('msg_abc123');
|
|
582
|
-
```
|
|
583
|
-
|
|
584
|
-
### Email with Attachments
|
|
585
|
-
|
|
586
|
-
```typescript
|
|
587
|
-
await agent.email({
|
|
588
|
-
to: ['alice@example.com', 'bob@example.com'],
|
|
589
|
-
subject: 'Report',
|
|
590
|
-
body: 'See attached.',
|
|
591
|
-
attachments: [{
|
|
592
|
-
filename: 'report.pdf',
|
|
593
|
-
content: base64Content,
|
|
594
|
-
content_type: 'application/pdf'
|
|
595
|
-
}]
|
|
596
|
-
});
|
|
597
|
-
```
|
|
598
|
-
|
|
599
|
-
### Custom Sender (name, mailbox, domain)
|
|
600
|
-
|
|
601
|
-
```typescript
|
|
602
|
-
// Renders as: From: Jane Doe <jane@acme.com>
|
|
603
|
-
await agent.email({
|
|
604
|
-
to: 'user@example.com',
|
|
605
|
-
subject: 'Hello',
|
|
606
|
-
body: 'Sent from a custom sender.',
|
|
607
|
-
from_name: 'Jane Doe', // display name (optional)
|
|
608
|
-
from_mailbox: 'jane', // local-part, defaults to "agent"; requires from_domain
|
|
609
|
-
from_domain: 'acme.com' // must be a domain you own
|
|
610
|
-
});
|
|
611
|
-
```
|
|
612
|
-
|
|
613
|
-
`from_domain` must be a domain you've provisioned through OneShot. Pinning
|
|
614
|
-
`from_mailbox` without `from_domain` throws a client-side `ValidationError` —
|
|
615
|
-
there is no default domain to fall back to; omit both to rotate instead.
|
|
616
|
-
|
|
617
|
-
**Dedicated mailboxes:** pass `mailbox_mode: 'mailbox'` when **first provisioning
|
|
618
|
-
a new `from_domain`** to give each address a real dedicated mailbox (better
|
|
619
|
-
deliverability + per-address warmup) instead of the default `relay` (header-only)
|
|
620
|
-
send; it adds a one-time `mailbox_provisioning_fee` to the quote. It only applies
|
|
621
|
-
at first setup — an already-provisioned domain keeps the mode it was created with,
|
|
622
|
-
and requesting `mailbox` on an existing relay domain returns
|
|
623
|
-
`400 mailbox_mode_conflict`.
|
|
624
|
-
|
|
625
|
-
### Domain Pool & Warmup
|
|
626
|
-
|
|
627
|
-
OneShot runs a per-agent **domain pool** with server-side warmup so your cold
|
|
628
|
-
email lands in the inbox. Understanding pin-vs-rotate is the key concept:
|
|
629
|
-
|
|
630
|
-
- **Rotate (recommended for cold outreach):** omit both `from_domain` and
|
|
631
|
-
`from_mailbox`. The server picks a warmed, under-cap domain **from your own
|
|
632
|
-
pool**, applies warmup-score and daily-limit gates, and returns the chosen
|
|
633
|
-
address on the quote (`quote.from_address`). This is the only mode that gets
|
|
634
|
-
warmup protection. If you own no eligible domain, the quote returns
|
|
635
|
-
**`400 no_sending_domain`** — there is **no shared fallback sender**; you must
|
|
636
|
-
provision/own a domain to send.
|
|
637
|
-
- **Pin:** set the required `from_domain` and optionally `from_mailbox` to select a sender. `from_mailbox` cannot be used alone.
|
|
638
|
-
Rotation is bypassed — **and so are the warmup-score and daily-limit gates.**
|
|
639
|
-
Pinning a still-warming or over-cap domain will hurt deliverability; the send
|
|
640
|
-
still goes out, but the response carries a non-blocking `warning` (see below).
|
|
641
|
-
|
|
642
|
-
**Reputation is per-domain**, shared by every mailbox on it. Running multiple
|
|
643
|
-
mailboxes on one domain (`jane@`, `sales@`, …) does **not** improve
|
|
644
|
-
deliverability or raise capacity — they share the same warmup score and the same
|
|
645
|
-
`daily_send_limit`.
|
|
646
|
-
|
|
647
|
-
**Provisioning:** a domain enters your pool the first time you reference it in a
|
|
648
|
-
send/quote (auto-provisioned, then `provisioning → verified → warming → active`
|
|
649
|
-
once its warmup score crosses the activation threshold). There is no separate
|
|
650
|
-
provisioning call today. New domains start in `warming` and aren't rotation-
|
|
651
|
-
eligible until they graduate.
|
|
652
|
-
|
|
653
|
-
**The `warning` field** (non-blocking — the send still happens; read it to defer):
|
|
654
|
-
|
|
655
|
-
| `warning` | Meaning |
|
|
656
|
-
|-----------|---------|
|
|
657
|
-
| `pinned_domain_warming` | Your pinned domain is still warming (low warmup score) — poor deliverability likely. |
|
|
658
|
-
| `pinned_over_limit` | Your pinned domain is over its `daily_send_limit` for today. |
|
|
659
|
-
|
|
660
|
-
Pinned sends are never blocked by warmup/limit — branch on `warning` and defer.
|
|
661
|
-
(The one hard error is `400 no_sending_domain` on an **un-pinned** send when you
|
|
662
|
-
own no eligible domain — provision or pin one.)
|
|
663
|
-
|
|
664
|
-
```typescript
|
|
665
|
-
// Inspect and manage the pool
|
|
666
|
-
const { domains } = await agent.listDomains();
|
|
667
|
-
// each: { domain, pool_status, warmup_score, daily_send_limit, daily_sent_count, ... }
|
|
668
|
-
|
|
669
|
-
await agent.pauseDomain('acme.com'); // take out of rotation
|
|
670
|
-
await agent.resumeDomain('acme.com'); // put back (only from 'paused')
|
|
671
|
-
|
|
672
|
-
const res = await agent.email({ to: 'lead@example.com', subject: 'Hi', body: '…' });
|
|
673
|
-
if (res.warning) {
|
|
674
|
-
// e.g. 'pinned_domain_warming' — back off and let warmup finish
|
|
675
|
-
}
|
|
676
|
-
```
|
|
677
|
-
|
|
678
|
-
### People Search & Enrichment
|
|
679
|
-
|
|
680
|
-
```typescript
|
|
681
|
-
const results = await agent.personFind({
|
|
682
|
-
job_titles: ['CEO', 'CTO'],
|
|
683
|
-
companies: ['Stripe'],
|
|
684
|
-
limit: 10
|
|
685
|
-
});
|
|
686
|
-
|
|
687
|
-
const profile = await agent.personProfile({
|
|
688
|
-
linkedin_url: results.results[0].linkedin_url
|
|
689
|
-
});
|
|
690
|
-
```
|
|
691
|
-
|
|
692
|
-
**Filters:** `seniority` takes any of `owner`, `founder`, `c_suite`, `partner`, `vp`, `head`, `director`, `manager`, `senior`, `entry`, `intern`.
|
|
693
|
-
Common variants (`'Vice President'`, `'C-Level'`, `'executive'`) are accepted;
|
|
694
|
-
a value that matches no level fails the search with `invalid_input`. `industry`
|
|
695
|
-
is free text (`['fintech', 'software']`).
|
|
696
|
-
|
|
697
|
-
**Empty vs. failed:** an empty `results` array means the search ran and nobody
|
|
698
|
-
matched. If the search could not be run, the call throws a `JobError` with a
|
|
699
|
-
retryable upstream code (`service_unavailable`, `service_auth`,
|
|
700
|
-
`rate_limited`) instead of returning an empty result.
|
|
701
|
-
|
|
702
|
-
**Pagination:** `limit` (default 100, max 500) and `offset` (default 0,
|
|
703
|
-
0-indexed) page through the result set — `{ limit: 100, offset: 100 }` gets
|
|
704
|
-
the second page of 100. Large requests are paginated server-side, so one
|
|
705
|
-
response can hold up to `limit` rows. The search runs under a time limit: if it
|
|
706
|
-
is reached, you get the rows found so far (possibly fewer than `limit`) and can
|
|
707
|
-
continue with `offset`. Rows whose phone lookup did not finish in time are
|
|
708
|
-
returned with `_phone_enriched: false` and
|
|
709
|
-
`_phone_enrich_reason: 'time_budget_exhausted'` rather than dropped.
|
|
710
|
-
|
|
711
|
-
```typescript
|
|
712
|
-
const page2 = await agent.personFind({
|
|
713
|
-
job_titles: ['CEO'],
|
|
714
|
-
limit: 100,
|
|
715
|
-
offset: 100
|
|
716
|
-
});
|
|
717
|
-
```
|
|
718
|
-
|
|
719
|
-
### Commerce
|
|
720
|
-
|
|
721
|
-
```typescript
|
|
722
|
-
const order = await agent.commerceBuy({
|
|
723
|
-
product_url: 'https://amazon.com/dp/B07ZPC9QD4',
|
|
724
|
-
shipping_address: {
|
|
725
|
-
first_name: 'John',
|
|
726
|
-
last_name: 'Doe',
|
|
727
|
-
street: '123 Main St',
|
|
728
|
-
city: 'San Francisco',
|
|
729
|
-
state: 'CA',
|
|
730
|
-
zip_code: '94102',
|
|
731
|
-
country: 'US',
|
|
732
|
-
phone: '4155550100'
|
|
733
|
-
},
|
|
734
|
-
maxCost: 100
|
|
735
|
-
});
|
|
736
|
-
```
|
|
737
|
-
|
|
738
|
-
### Physical mail
|
|
739
|
-
|
|
740
|
-
`agent.physicalMail` exposes `uploadArtwork`, `validateAddress`, `preview`, `getQuote`, `approve`, `send`, `getOrder`, `recover`, and `cancel` for U.S. letters and 4×6 postcards. Inspect the proof and price before explicitly approving each quote. Persist `idempotencyKey` before `send`; use `recover(key)` after uncertain responses. Approval is never inferred by automatic payment retries. Payment receipts and postal events are separate; delivery does not prove readership. See `docs/physical-mail.md` in the OneShot repository for configuration and examples.
|
|
741
|
-
|
|
742
|
-
### Account login and 2FA (SDK 0.34.0+; `createBrowserProfileSetupLink` from 0.54.0)
|
|
743
|
-
|
|
744
|
-
Create a browser profile, then:
|
|
745
|
-
|
|
746
|
-
1. Start: `startBrowserProfileSetup(profile.id, loginUrl)`.
|
|
747
|
-
2. Give the returned `setup_url` to the person whose account it is. It opens once and expires after 10 minutes; `createBrowserProfileSetupLink(profile.id)` mints a fresh one, and with a `startUrl` it also starts a setup when none is open.
|
|
748
|
-
3. The person signs in on the page, completes the site's verification, and presses **Done**. **Cancel** ends the setup without marking it complete, so `completed_at` stays `null`.
|
|
749
|
-
4. Poll `getBrowserProfileSetup(profile.id)` until `completed_at` is set, or until `status` is `expired`, `no_setup` or `stopped` (the setup ended after Cancel or the 15-minute expiry; `completed_at` stays `null`, so stop polling and start setup again). Once `completed_at` is set, store the profile ID for later `browser({ profile_id, task })` calls.
|
|
750
|
-
|
|
751
|
-
Done (or `finishBrowserProfileSetup(profile.id)`, when your application saves on the person's behalf) stops the session and inspects cookies in a fresh browser before website navigation. It returns cookie names/domains/paths, never values; a subsequent task must check whether the site accepts the login. Setup expires after 15 minutes and abandoned sessions are closed by the lifecycle sweep. Profiles are retained. Start setup again to reconnect an expired site login.
|
|
752
|
-
|
|
753
|
-
Treat `setup_url` as a secret: give it only to that person, never open it yourself, and keep it, passwords and codes out of logs and agent prompts. The page is not cached and sends no referrer. `live_url` is deprecated and is removed in the next release.
|
|
754
|
-
|
|
755
|
-
Browser tasks reject the legacy `secrets` field and `max_steps` below 25 before payment. Use interactive setup or `createBrowserProfile(name, { cookies })` / `{ storage_state }` for authentication. `cost` is the provider cost; `billed_cost` is the final customer charge after reconciliation (null while pending). Failed `JobError`s expose `partialResult` for diagnostics.
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
### LinkedIn invitations and withdrawal
|
|
759
|
-
|
|
760
|
-
Available in `@oneshot-agent/sdk` 0.38.0 and later. Both operations require a connected account with the `invite` grant.
|
|
761
|
-
|
|
762
|
-
```typescript
|
|
763
|
-
const invitation = await agent.linkedinInvite({
|
|
764
|
-
accountId,
|
|
765
|
-
profile: 'https://www.linkedin.com/in/jane-doe/',
|
|
766
|
-
note: 'Enjoyed your presentation.',
|
|
767
|
-
idempotencyKey: 'event-jane-invite',
|
|
768
|
-
});
|
|
769
|
-
|
|
770
|
-
// To withdraw that pending invitation later:
|
|
771
|
-
await agent.linkedinWithdrawInvitation({
|
|
772
|
-
accountId,
|
|
773
|
-
invitationId: invitation.invitation_id,
|
|
774
|
-
idempotencyKey: 'event-jane-withdraw',
|
|
775
|
-
});
|
|
776
|
-
```
|
|
777
|
-
|
|
778
|
-
Use the OneShot `invitation_id`, not the provider invitation ID. Withdrawal can also cancel a queued invitation; it never disconnects an established connection. Invitations default to 25 per account per UTC day, and withdrawing a sent invitation does not restore that day's capacity. Reuse the same idempotency key when retrying an operation.
|
|
779
|
-
|
|
780
|
-
### Recovering a missing LinkedIn connection
|
|
781
|
-
|
|
782
|
-
Use `reconnectLinkedInAccount(id)` for an existing upstream connection whose session needs renewal. A deleted upstream account needs a new `linkedinConnect()` intent and fresh human authorization.
|
|
783
|
-
|
|
784
|
-
Account reads refresh disconnected accounts against the provider. Once an upstream account-read confirms `404`, the old connection is marked `connection_deleted`, its grant is ended, and `reconnect_required` is false. It is omitted from the default active account list; use `includeRevoked: true` or fetch it by ID to inspect its terminal status. Authentication failures, timeouts, and provider outages do not trigger this transition.
|
|
785
|
-
|
|
786
|
-
```ts
|
|
787
|
-
import { LinkedInConnectRequiredError } from '@oneshot-agent/sdk';
|
|
788
|
-
|
|
789
|
-
try {
|
|
790
|
-
const intent = await agent.reconnectLinkedInAccount(account.id);
|
|
791
|
-
// Show intent.url to the human.
|
|
792
|
-
} catch (error) {
|
|
793
|
-
if (error instanceof LinkedInConnectRequiredError) {
|
|
794
|
-
// Show “Connect LinkedIn” and ask the human to choose grants again.
|
|
795
|
-
// On confirmation, call agent.linkedinConnect({ requestedActions: ['read'] }).
|
|
796
|
-
} else {
|
|
797
|
-
throw error;
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
```
|
|
801
|
-
|
|
802
|
-
The HTTP equivalent is `409` with `error: "connect_required"` and `details.next_action: "connect"`. Existing SDK versions can inspect `ToolError.responseBody` for this contract.
|
|
803
|
-
### Notifications
|
|
126
|
+
- `JobError.code` is one of `insufficient_funds` `payment_failed` `invalid_input`
|
|
127
|
+
`content_blocked` `rate_limited` `service_unavailable` `service_auth`
|
|
128
|
+
`enrichment_exhausted` `target_unreachable` `source_unsupported`
|
|
129
|
+
`checkout_failed` `internal_error`. [What each means](https://docs.oneshotagent.com/api-reference/status#error-codes).
|
|
130
|
+
- Input the SDK can check throws `ValidationError` before anything is paid.
|
|
131
|
+
- Emergency numbers are refused: `EmergencyNumberError`.
|
|
804
132
|
|
|
805
|
-
|
|
806
|
-
// List notifications
|
|
807
|
-
const notifications = await agent.notifications({ unread: true, limit: 20 });
|
|
808
|
-
for (const n of notifications.notifications) {
|
|
809
|
-
console.log(`[${n.type}] ${n.title}: ${n.message}`);
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
// Mark as read
|
|
813
|
-
await agent.markNotificationRead('notification-uuid');
|
|
814
|
-
```
|
|
133
|
+
## Waiting and stopping
|
|
815
134
|
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
135
|
+
- Paid calls wait by default. `wait: false` returns the `request_id`;
|
|
136
|
+
`waitForResult(requestId)` picks the result up later.
|
|
137
|
+
- An `AbortSignal` stops the call **before payment** and nothing is charged.
|
|
138
|
+
After payment, the work happens and is billed. Aborting only stops your wait.
|
|
139
|
+
- An abort rejects with `OneShotError('Operation cancelled')` for `commerceBuy`,
|
|
140
|
+
`voice`, `sms`, `build`, `refineBuild`, `updateBuild` and `browser`; the other
|
|
141
|
+
calls reject with the signal's `reason`. Handle both.
|
|
142
|
+
- `timeout` is how long you wait, in seconds. It never limits the job.
|
|
143
|
+
- Goals run longer than calls. Watch one with `waitForComputeGoal`:
|
|
819
144
|
|
|
820
145
|
```typescript
|
|
821
|
-
const
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
try {
|
|
872
|
-
const { request_id } = await agent.research({ topic: 'EU battery regulation', wait: false });
|
|
873
|
-
const report = await agent.waitForResult(request_id, { timeout: 300, signal: controller.signal });
|
|
874
|
-
res.json(report);
|
|
875
|
-
} catch (error) {
|
|
876
|
-
if (error instanceof OneShotError && error.message === 'Operation cancelled') {
|
|
877
|
-
// Stopped waiting locally; request_id can be resumed later.
|
|
878
|
-
} else throw error;
|
|
879
|
-
} finally {
|
|
880
|
-
res.off('close', onClose);
|
|
881
|
-
}
|
|
882
|
-
```
|
|
146
|
+
const goal = await agent.compute({ objective: 'Research battery recycling', budget_usdc: 5 });
|
|
147
|
+
const done = await agent.waitForComputeGoal(goal.goal_id, {
|
|
148
|
+
timeout: 300,
|
|
149
|
+
onStatusUpdate: (status) => console.log(status),
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
- It resolves on `completed` and throws `JobError` on `failed` or `cancelled`
|
|
154
|
+
(`code` is the `end_reason`). `awaiting_confirmation` and `paused` wait for you;
|
|
155
|
+
they do not end the wait.
|
|
156
|
+
- A goal that failed with `budget_exhausted` comes back after `fundComputeGoal`.
|
|
157
|
+
Call `waitForComputeGoal` again; the old wait does not resume.
|
|
158
|
+
- Timeout defaults to 120 seconds and throws `JobTimeoutError`. It stops your
|
|
159
|
+
polling, not the goal. Stop the goal with `cancelComputeGoal`.
|
|
160
|
+
|
|
161
|
+
## Tool notes
|
|
162
|
+
|
|
163
|
+
- **Email.** Omit `from_domain` and the server rotates across your warmed
|
|
164
|
+
domains. Pin one and you skip the warmup gates; the send goes out with a
|
|
165
|
+
`warning`. Own no eligible domain and the quote fails with `no_sending_domain`.
|
|
166
|
+
There is no shared sender. `mailbox_mode: 'mailbox'` applies only when a domain
|
|
167
|
+
is first set up. [Details](https://docs.oneshotagent.com/api-reference/email/send).
|
|
168
|
+
- **People search.** An empty result means nobody matched. A failed search
|
|
169
|
+
throws; it never pretends to be empty. Page with `limit` (default 100, max 500)
|
|
170
|
+
and `offset`. A search that hits its time limit returns what it found; rows
|
|
171
|
+
whose phone lookup ran out of time say `_phone_enrich_reason: 'time_budget_exhausted'`.
|
|
172
|
+
[Details](https://docs.oneshotagent.com/api-reference/research/person-find).
|
|
173
|
+
- **Phone lookup** is off by default. `includePhone: true` is charged for each person
|
|
174
|
+
you ask for (the search `limit`), found or not; a failed job is refunded. `waitForPhones: true` waits
|
|
175
|
+
for numbers still arriving. It never buys any. Check `phones_pending`.
|
|
176
|
+
[Price](https://docs.oneshotagent.com/pricing).
|
|
177
|
+
- **Account login.** `startBrowserProfileSetup` returns a `setup_url`. It opens
|
|
178
|
+
once and expires in 10 minutes. Give it only to the account's owner, never open
|
|
179
|
+
it yourself, keep it out of logs and prompts. Poll `getBrowserProfileSetup`
|
|
180
|
+
until `completed_at` is set. You get cookie names back, never values.
|
|
181
|
+
[Details](https://docs.oneshotagent.com/api-reference/browser/profiles).
|
|
182
|
+
- **LinkedIn.** Invitations are capped at 25 per account per UTC day;
|
|
183
|
+
withdrawing one does not give the slot back. Use OneShot's `invitation_id`.
|
|
184
|
+
A session that expired comes back with `reconnectLinkedInAccount`; a deleted
|
|
185
|
+
account throws `LinkedInConnectRequiredError` and needs `linkedinConnect` again.
|
|
186
|
+
Reuse your idempotency key on retries. [Details](https://docs.oneshotagent.com/api-reference/linkedin/overview).
|
|
187
|
+
- **Connected systems.** The reads of your accounting, CRM, tax, shipping and spend
|
|
188
|
+
systems have no typed method: call them with `route('POST', <path>, { paid: true, body })`.
|
|
189
|
+
Each takes `connection_id`; `listConnections()` lists the agent's connections, pick one by
|
|
190
|
+
`system` and `account_label`. Another agent's connection is refused before payment.
|
|
191
|
+
[Details](https://docs.oneshotagent.com/api-reference/connections/overview).
|
|
192
|
+
- **Physical mail.** U.S. letters and postcards. Look at the proof and the price,
|
|
193
|
+
then approve each quote yourself. Nothing approves it for you. Persist the
|
|
194
|
+
`idempotencyKey` before `send`. [Details](https://docs.oneshotagent.com/api-reference/physical-mail/overview).
|
|
883
195
|
|
|
884
196
|
## Links
|
|
885
197
|
|
|
886
198
|
- [Documentation](https://docs.oneshotagent.com)
|
|
887
|
-
- [MCP Server](https://www.npmjs.com/package/@oneshot-agent/mcp-server)
|
|
888
|
-
- [Python SDK (LangChain)](https://pypi.org/project/langchain-oneshot/)
|
|
889
|
-
- [Python SDK (Core)](https://pypi.org/project/oneshot-python/)
|
|
199
|
+
- [MCP Server](https://www.npmjs.com/package/@oneshot-agent/mcp-server): local, or hosted at `https://win.oneshotagent.com/mcp` with an access token
|
|
200
|
+
- [Python SDK (LangChain)](https://pypi.org/project/langchain-oneshot/): 54 tools plus the lookup tools as LangChain BaseTool
|
|
201
|
+
- [Python SDK (Core)](https://pypi.org/project/oneshot-python/)
|
|
890
202
|
- [Pricing](https://docs.oneshotagent.com/pricing)
|
|
891
203
|
- [GitHub](https://github.com/oneshot-agent/sdk)
|
|
892
204
|
|
|
893
|
-
##
|
|
894
|
-
|
|
895
|
-
MIT
|
|
205
|
+
## Discover tools
|
|
896
206
|
|
|
897
|
-
|
|
207
|
+
`await agent.discoverTools({ query: "send a printed letter" })` searches the public
|
|
208
|
+
catalog for free. Omit options for category counts, or pass `{ category: "email" }`
|
|
209
|
+
to browse five tools at a time. Pass `nextCursor` as `cursor` with the same query
|
|
210
|
+
and category. Results use MCP names and include purposes and required inputs;
|
|
211
|
+
your token grants filter the catalog before search.
|
|
898
212
|
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
`maxCost` and per-transaction budgets apply to the full quote before authorization, even when credits cover it. The SDK obtains the quote without execution, retries with its quote ID and payment or credit session, and returns the result and receipt.
|
|
902
|
-
|
|
903
|
-
```typescript
|
|
904
|
-
const people = await agent.personFind({ job_titles: ['CTO'], limit: 3, includePhone: true, maxCost: 1, waitForPhones: true });
|
|
905
|
-
const profile = await agent.personProfile({ email: 'jane@example.com', includePhone: true, maxCost: 1, waitForPhones: true });
|
|
906
|
-
const dossier = await agent.personDossier({ email: 'jane@example.com', includePhone: true, maxCost: 1, waitForPhones: true, timeout: 600 });
|
|
907
|
-
```
|
|
213
|
+
## License
|
|
908
214
|
|
|
909
|
-
|
|
910
|
-
With `waitForPhones: true`, `onStatusUpdate('completed', requestId)` is delivered
|
|
911
|
-
after the phone wait returns, immediately before the result resolves. It means
|
|
912
|
-
the local wait finished; check `phones_pending` to see whether phones arrived.
|
|
215
|
+
MIT
|