@rool-dev/sdk 0.12.5 → 2.0.0-dev.0745af4
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/LICENSE +21 -0
- package/README.md +63 -951
- package/assets/rool-logo.svg +5 -0
- package/assets/rool-wordmark-dark.svg +7 -0
- package/assets/rool-wordmark.svg +7 -0
- package/dist/agents.d.ts +143 -0
- package/dist/agents.d.ts.map +1 -0
- package/dist/agents.js +583 -0
- package/dist/agents.js.map +1 -0
- package/dist/auth-base.d.ts +43 -88
- package/dist/auth-base.d.ts.map +1 -1
- package/dist/auth-base.js +178 -302
- package/dist/auth-base.js.map +1 -1
- package/dist/auth-browser.d.ts +7 -25
- package/dist/auth-browser.d.ts.map +1 -1
- package/dist/auth-browser.js +45 -82
- package/dist/auth-browser.js.map +1 -1
- package/dist/auth-native.d.ts +9 -56
- package/dist/auth-native.d.ts.map +1 -1
- package/dist/auth-native.js +88 -177
- package/dist/auth-native.js.map +1 -1
- package/dist/auth-node.d.ts +32 -36
- package/dist/auth-node.d.ts.map +1 -1
- package/dist/auth-node.js +338 -265
- package/dist/auth-node.js.map +1 -1
- package/dist/auth-url.d.ts +6 -0
- package/dist/auth-url.d.ts.map +1 -0
- package/dist/auth-url.js +30 -0
- package/dist/auth-url.js.map +1 -0
- package/dist/client.d.ts +37 -290
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +331 -635
- package/dist/client.js.map +1 -1
- package/dist/events.d.ts +40 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +175 -0
- package/dist/events.js.map +1 -0
- package/dist/files.d.ts +176 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +857 -0
- package/dist/files.js.map +1 -0
- package/dist/index.d.ts +16 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -20
- package/dist/index.js.map +1 -1
- package/dist/machine.d.ts +51 -0
- package/dist/machine.d.ts.map +1 -0
- package/dist/machine.js +142 -0
- package/dist/machine.js.map +1 -0
- package/dist/problem.d.ts +11 -0
- package/dist/problem.d.ts.map +1 -0
- package/dist/problem.js +20 -0
- package/dist/problem.js.map +1 -0
- package/dist/structured.d.ts +84 -0
- package/dist/structured.d.ts.map +1 -0
- package/dist/structured.js +405 -0
- package/dist/structured.js.map +1 -0
- package/dist/types.d.ts +133 -520
- package/dist/types.d.ts.map +1 -1
- package/package.json +24 -22
- package/dist/auth.d.ts +0 -99
- package/dist/auth.d.ts.map +0 -1
- package/dist/auth.js +0 -217
- package/dist/auth.js.map +0 -1
- package/dist/client-info.d.ts +0 -18
- package/dist/client-info.d.ts.map +0 -1
- package/dist/client-info.js +0 -25
- package/dist/client-info.js.map +0 -1
- package/dist/event-emitter.d.ts +0 -45
- package/dist/event-emitter.d.ts.map +0 -1
- package/dist/event-emitter.js +0 -86
- package/dist/event-emitter.js.map +0 -1
- package/dist/graphql.d.ts +0 -90
- package/dist/graphql.d.ts.map +0 -1
- package/dist/graphql.js +0 -471
- package/dist/graphql.js.map +0 -1
- package/dist/logger.d.ts +0 -13
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js +0 -17
- package/dist/logger.js.map +0 -1
- package/dist/path.d.ts +0 -6
- package/dist/path.d.ts.map +0 -1
- package/dist/path.js +0 -47
- package/dist/path.js.map +0 -1
- package/dist/reroute.d.ts +0 -22
- package/dist/reroute.d.ts.map +0 -1
- package/dist/reroute.js +0 -70
- package/dist/reroute.js.map +0 -1
- package/dist/rest.d.ts +0 -54
- package/dist/rest.d.ts.map +0 -1
- package/dist/rest.js +0 -171
- package/dist/rest.js.map +0 -1
- package/dist/router.d.ts +0 -20
- package/dist/router.d.ts.map +0 -1
- package/dist/router.js +0 -54
- package/dist/router.js.map +0 -1
- package/dist/space-session.d.ts +0 -30
- package/dist/space-session.d.ts.map +0 -1
- package/dist/space-session.js +0 -83
- package/dist/space-session.js.map +0 -1
- package/dist/space.d.ts +0 -161
- package/dist/space.d.ts.map +0 -1
- package/dist/space.js +0 -618
- package/dist/space.js.map +0 -1
- package/dist/subscription.d.ts +0 -36
- package/dist/subscription.d.ts.map +0 -1
- package/dist/subscription.js +0 -419
- package/dist/subscription.js.map +0 -1
- package/dist/webdav.d.ts +0 -171
- package/dist/webdav.d.ts.map +0 -1
- package/dist/webdav.js +0 -481
- package/dist/webdav.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center" class="sdk-brand"><a href="https://rool.dev"><img class="sdk-brand-logo" src="./assets/rool-logo.svg" alt="Rool logo" width="44" height="44"><picture><source media="(prefers-color-scheme: dark)" srcset="./assets/rool-wordmark-dark.svg"><img class="sdk-brand-wordmark" src="./assets/rool-wordmark.svg" alt="Rool" width="128"></picture></a></p>
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<h1 align="center" class="sdk-title">TypeScript SDK</h1>
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The official TypeScript SDK for building apps and automations with [Rool Machines](https://rool.dev).
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- **Spaces** — Machines: containers for objects, schema, metadata, conversations, collaborators, and files.
|
|
10
|
-
- **Conversations** — independent interaction histories in a space.
|
|
11
|
-
- **Objects** — JSON records addressed by object paths such as `/space/article/welcome.json`.
|
|
12
|
-
- **Files** — user-visible files stored under `/rool-drive/...` through WebDAV.
|
|
7
|
+
A Rool Machine is a persistent cloud VM with snapshots, files, and embedded AI agents. The SDK provides one typed interface for authentication, machine management, file access, agent conversations, and live updates.
|
|
13
8
|
|
|
14
9
|
## Installation
|
|
15
10
|
|
|
@@ -17,991 +12,108 @@ Core primitives:
|
|
|
17
12
|
npm install @rool-dev/sdk
|
|
18
13
|
```
|
|
19
14
|
|
|
20
|
-
##
|
|
21
|
-
|
|
22
|
-
```typescript
|
|
23
|
-
import { RoolClient } from '@rool-dev/sdk';
|
|
24
|
-
|
|
25
|
-
async function main() {
|
|
26
|
-
const client = new RoolClient();
|
|
27
|
-
|
|
28
|
-
if (!(await client.initialize())) {
|
|
29
|
-
await client.login('My App');
|
|
30
|
-
// Browser auth redirects away. Run startup again after the auth callback.
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const space = await client.createSpace('Solar System');
|
|
35
|
-
const conversation = space.conversation('main');
|
|
36
|
-
|
|
37
|
-
await space.createCollection('body', [
|
|
38
|
-
{ name: 'name', type: { kind: 'string' } },
|
|
39
|
-
{ name: 'mass', type: { kind: 'string' } },
|
|
40
|
-
{ name: 'radius', type: { kind: 'string' } },
|
|
41
|
-
{ name: 'orbits', type: { kind: 'maybe', inner: { kind: 'ref' } } },
|
|
42
|
-
]);
|
|
43
|
-
|
|
44
|
-
const { object: sun } = await space.putObject('/space/body/sun.json', {
|
|
45
|
-
name: 'Sun',
|
|
46
|
-
mass: '1 solar mass',
|
|
47
|
-
radius: '696,340 km',
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
const { object: earth } = await space.putObject('/space/body/earth.json', {
|
|
51
|
-
name: 'Earth',
|
|
52
|
-
mass: '1 Earth mass',
|
|
53
|
-
radius: '6,371 km',
|
|
54
|
-
orbits: sun.path,
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
const { message, objects } = await conversation.prompt(
|
|
58
|
-
'Add the other planets in our solar system, each referencing the Sun.'
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
console.log(message);
|
|
62
|
-
console.log(`Modified ${objects.length} objects`);
|
|
63
|
-
|
|
64
|
-
const loadedEarth = await space.getObject(earth.path);
|
|
65
|
-
console.log(loadedEarth?.body.name);
|
|
66
|
-
|
|
67
|
-
space.close();
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
void main();
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
## Paths and Resource URIs
|
|
74
|
-
|
|
75
|
-
Most SDK methods take plain path strings:
|
|
76
|
-
|
|
77
|
-
- Object paths: `/space/<collection>/<name>.json` (exactly three segments; no dotfile collection or object names)
|
|
78
|
-
- File paths: `/rool-drive/<path/to/file>`
|
|
79
|
-
|
|
80
|
-
`rool-machine:/...` URIs are the user-facing/canonical form for resource references in prompt attachments and interaction history. The exported helpers normalize between these forms when you need them.
|
|
81
|
-
|
|
82
|
-
```typescript
|
|
83
|
-
import { machinePath, machineUri, isObjectPath } from '@rool-dev/sdk';
|
|
84
|
-
|
|
85
|
-
machinePath('rool-machine:/rool-drive/docs/read%20me.md');
|
|
86
|
-
// '/rool-drive/docs/read me.md'
|
|
87
|
-
|
|
88
|
-
machineUri('/space/article/welcome.json');
|
|
89
|
-
// 'rool-machine:/space/article/welcome.json'
|
|
90
|
-
|
|
91
|
-
isObjectPath('/space/article/welcome.json'); // true
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
Object APIs require full object paths. References between objects are ordinary body fields containing object paths:
|
|
95
|
-
|
|
96
|
-
```typescript
|
|
97
|
-
{
|
|
98
|
-
path: '/space/body/earth.json',
|
|
99
|
-
body: { name: 'Earth', orbits: '/space/body/sun.json' },
|
|
100
|
-
}
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
## Authentication
|
|
104
|
-
|
|
105
|
-
### Browser
|
|
106
|
-
|
|
107
|
-
The default auth provider stores tokens in browser storage and redirects to the Rool auth page.
|
|
108
|
-
|
|
109
|
-
```typescript
|
|
110
|
-
async function start() {
|
|
111
|
-
const client = new RoolClient();
|
|
112
|
-
|
|
113
|
-
if (!(await client.initialize())) {
|
|
114
|
-
await client.login('My App');
|
|
115
|
-
// Browser auth redirects; stop startup until the callback reloads the app.
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Use the authenticated client here.
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
void start();
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
### Node.js
|
|
15
|
+
## Connect from Node.js
|
|
126
16
|
|
|
127
|
-
|
|
17
|
+
`NodeAuth` opens the user's browser for login, stores their credentials locally, and refreshes them when needed.
|
|
128
18
|
|
|
129
19
|
```typescript
|
|
130
|
-
import { RoolClient } from
|
|
131
|
-
import {
|
|
20
|
+
import { RoolClient } from "@rool-dev/sdk";
|
|
21
|
+
import { NodeAuth } from "@rool-dev/sdk/node";
|
|
132
22
|
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (!authenticated) {
|
|
137
|
-
await client.login('My CLI Tool');
|
|
138
|
-
// Re-run initialize after the non-redirect login to hydrate currentUser,
|
|
139
|
-
// user storage, and client-level event subscriptions.
|
|
140
|
-
authenticated = await client.initialize();
|
|
23
|
+
const auth = new NodeAuth();
|
|
24
|
+
if (!(await auth.initialize())) {
|
|
25
|
+
await auth.login("My app");
|
|
141
26
|
}
|
|
142
27
|
|
|
143
|
-
if (!authenticated) throw new Error('Login required');
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
### Native (Capacitor, Cordova, Tauri, ...)
|
|
147
|
-
|
|
148
|
-
Use the native PKCE provider for JS app shells that sign in through an external
|
|
149
|
-
system browser. `login()`/`signup()` open the auth server's `/authorize` page
|
|
150
|
-
via the `openExternal` callback you supply; when the deep link returns, feed it
|
|
151
|
-
to `client.handleAuthRedirect(url)` from your platform's deep-link handler. The
|
|
152
|
-
code is exchanged for a session at `/token` and tokens are stored like the
|
|
153
|
-
browser provider.
|
|
154
|
-
|
|
155
|
-
```typescript
|
|
156
|
-
import { RoolClient, NativePkceAuthProvider } from '@rool-dev/sdk';
|
|
157
|
-
import { Browser } from '@capacitor/browser';
|
|
158
|
-
import { App } from '@capacitor/app';
|
|
159
|
-
|
|
160
28
|
const client = new RoolClient({
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
defaultProvider: 'google', // 'google' | 'apple'
|
|
164
|
-
openExternal: (url) => Browser.open({ url }),
|
|
165
|
-
}),
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
// Complete sign-in when the OS hands the app its deep link.
|
|
169
|
-
App.addListener('appUrlOpen', async ({ url }) => {
|
|
170
|
-
if (await client.handleAuthRedirect(url)) {
|
|
171
|
-
await Browser.close();
|
|
172
|
-
// Now authenticated — refresh your UI.
|
|
173
|
-
}
|
|
29
|
+
getTokens: auth.getTokens,
|
|
30
|
+
onAuthInvalidated: auth.logout,
|
|
174
31
|
});
|
|
175
|
-
|
|
176
|
-
if (!(await client.initialize())) {
|
|
177
|
-
// Opens the system browser; completion arrives via the listener above.
|
|
178
|
-
await client.login('My App'); // pass { provider: 'apple' } to override the default
|
|
179
|
-
}
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
#### Email + password and magic links (native)
|
|
183
|
-
|
|
184
|
-
The native provider also supports email/password sign-in and magic links —
|
|
185
|
-
no system browser, the server returns the token set as JSON directly.
|
|
186
|
-
|
|
187
|
-
```typescript
|
|
188
|
-
// Password sign-in
|
|
189
|
-
const result = await client.signInWithPassword(email, password);
|
|
190
|
-
if (result.status === 'signed_in') {
|
|
191
|
-
// authenticated — refresh your UI
|
|
192
|
-
} else {
|
|
193
|
-
// status === 'verify_required': the email isn't verified yet and the server
|
|
194
|
-
// has emailed a magic link. Tell the user to check their inbox.
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Or request a magic link explicitly
|
|
198
|
-
await client.requestMagicLink(email);
|
|
199
32
|
```
|
|
200
33
|
|
|
201
|
-
The
|
|
202
|
-
that token to `client.verify(token)` once the link lands back in the app.
|
|
203
|
-
|
|
204
|
-
> **⚠️ Magic links open the website, not the app, until Universal Links / App
|
|
205
|
-
> Links are configured.** The emailed link is an `https://` URL for the Rool
|
|
206
|
-
> web app. On native, an `https` link only re-opens your app if you've set up
|
|
207
|
-
> [iOS Universal Links](https://developer.apple.com/ios/universal-links/) /
|
|
208
|
-
> [Android App Links](https://developer.android.com/training/app-links) for that
|
|
209
|
-
> domain (custom-scheme deep links don't apply to email links). **Without that
|
|
210
|
-
> setup the magic link completes sign-in in the browser/website, not in the
|
|
211
|
-
> native app** — so for now treat magic links on native as a website hand-off,
|
|
212
|
-
> and prefer password or social sign-in for an in-app experience.
|
|
213
|
-
|
|
214
|
-
### Auth API
|
|
215
|
-
|
|
216
|
-
| Method | Description |
|
|
217
|
-
| --- | --- |
|
|
218
|
-
| `initialize(): Promise<boolean>` | Call on startup. Initializes auth, refreshes user/storage state, and starts client events when authenticated. |
|
|
219
|
-
| `login(appName, params?): Promise<void>` | Start login flow. |
|
|
220
|
-
| `signup(appName, params?): Promise<void>` | Start signup flow. |
|
|
221
|
-
| `verify(token): Promise<boolean>` | Complete email verification token flow; returns `false` when the active auth provider does not implement verification. |
|
|
222
|
-
| `handleAuthRedirect(url): Promise<boolean>` | Complete a native PKCE sign-in from a deep-link callback URL. Returns `false` when the active auth provider does not implement it. |
|
|
223
|
-
| `signInWithPassword(email, password): Promise<PasswordSignInResult>` | Email + password sign-in (native provider). Resolves `{ status: 'signed_in' }`, or `{ status: 'verify_required' }` when the email is unverified (a magic link was emailed). Rejects on bad credentials. |
|
|
224
|
-
| `requestMagicLink(email): Promise<void>` | Email the user a magic sign-in link (native provider). See the caveat below — on native the link opens the **website**, not the app, until Universal Links / App Links are configured. |
|
|
225
|
-
| `logout(): void` | Clear auth state and close open spaces. |
|
|
226
|
-
| `isAuthenticated(): Promise<boolean>` | Whether credentials are held locally. No network call — a server outage does not read as logged out. |
|
|
227
|
-
| `getAuthUser(): AuthUser` | Return auth identity decoded from the token. |
|
|
228
|
-
| `setPassword(password): Promise<void>` | Set/change password and sign out the user's other sessions. |
|
|
229
|
-
| `requestEmailChange(newEmail): Promise<void>` | Start an email address change. The server mails a confirmation link to the new address; the change applies when that link is clicked. |
|
|
230
|
-
|
|
231
|
-
### Changing email
|
|
232
|
-
|
|
233
|
-
`requestEmailChange` starts the flow; nothing changes until the user clicks the confirmation link mailed to the new address. After confirmation the current session no longer belongs to the account — the next API call fails with a 401, which ends the session via `authStateChanged(false)`. Have the user sign in again with the new address.
|
|
34
|
+
The package also includes:
|
|
234
35
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
try {
|
|
239
|
-
await client.requestEmailChange('new@example.com');
|
|
240
|
-
// Tell the user to check the new address's inbox.
|
|
241
|
-
} catch (error) {
|
|
242
|
-
if (error instanceof EmailChangeError) {
|
|
243
|
-
showFormError(error.message); // user-facing message
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
```
|
|
36
|
+
- `BrowserAuth` for web apps using browser redirects and browser storage.
|
|
37
|
+
- `NativeAuth` for mobile apps that open sign-in in the system browser and return through a deep link. It also supports passwords and magic links.
|
|
247
38
|
|
|
248
|
-
|
|
39
|
+
## Put a machine to work
|
|
249
40
|
|
|
250
|
-
|
|
251
|
-
| --- | --- |
|
|
252
|
-
| `invalid_email` | The new address is not a valid email. |
|
|
253
|
-
| `same_email` | The new address is the account's current address. |
|
|
254
|
-
| `email_in_use` | Another account already uses the new address. |
|
|
255
|
-
| `send_failed` | The confirmation email could not be sent. |
|
|
256
|
-
|
|
257
|
-
### Offline behavior
|
|
258
|
-
|
|
259
|
-
A temporarily unreachable server never reads as "logged out". `initialize()` reports authentication from stored credentials, so on an offline start it can return `true` while `currentUser` is still `null` and user storage is empty — the SDK keeps reconnecting in the background and hydrates both automatically once the server is reachable, emitting `currentUserChanged`. Only a hard auth failure ends the session — an invalid or expired refresh token, or a 401 from the API (the identity was retired, e.g. after an email change, or the account deleted) — via `authStateChanged(false)`.
|
|
260
|
-
|
|
261
|
-
## Spaces and Conversations
|
|
262
|
-
|
|
263
|
-
Open a space to receive live events and manage objects, schema, metadata, collaborators, file storage, and conversations. Use a conversation handle for conversation metadata and AI.
|
|
41
|
+
This example creates a machine, gives an agent a job, uploads a receipt, and asks the agent to file it. The file API writes to the machine's WebDAV storage, so the agent and the app see the same files.
|
|
264
42
|
|
|
265
43
|
```typescript
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const conversation = space.conversation('main');
|
|
269
|
-
space.on('filesChanged', () => console.log('files changed'));
|
|
270
|
-
|
|
271
|
-
await conversation.prompt('Summarize this space');
|
|
272
|
-
```
|
|
44
|
+
import { readFile } from "node:fs/promises";
|
|
273
45
|
|
|
274
|
-
|
|
46
|
+
const created = await client.createMachine({ name: "Receipts" });
|
|
47
|
+
const machine = client.machine(created.id);
|
|
275
48
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
```typescript
|
|
281
|
-
await space.createCollection('article', [
|
|
282
|
-
{ name: 'title', type: { kind: 'string' } },
|
|
283
|
-
{ name: 'status', type: { kind: 'string' } },
|
|
284
|
-
]);
|
|
285
|
-
|
|
286
|
-
// Create or replace an exact object path
|
|
287
|
-
const { object } = await space.putObject('/space/article/welcome.json', {
|
|
288
|
-
title: 'Welcome',
|
|
289
|
-
status: 'draft',
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
// Patch fields; null or undefined deletes a field
|
|
293
|
-
await space.patchObject(object.path, {
|
|
294
|
-
data: { status: 'published', obsoleteField: null },
|
|
49
|
+
const agent = await machine.agents.create("receipt-filer", {
|
|
50
|
+
system:
|
|
51
|
+
"File receipt attachments under /rool-drive/receipts. " +
|
|
52
|
+
"Choose useful subfolders based on the receipt's contents.",
|
|
295
53
|
});
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
await space.getObjects([
|
|
300
|
-
'/space/article/welcome.json',
|
|
301
|
-
'/space/article/intro.json',
|
|
302
|
-
]);
|
|
303
|
-
|
|
304
|
-
// Rename or move an object
|
|
305
|
-
await space.moveObject(
|
|
306
|
-
'/space/article/welcome.json',
|
|
307
|
-
'/space/article/hello-world.json'
|
|
308
|
-
);
|
|
309
|
-
|
|
310
|
-
// Delete objects
|
|
311
|
-
await space.deleteObjects(['/space/article/hello-world.json']);
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
| Method | Description |
|
|
315
|
-
| --- | --- |
|
|
316
|
-
| `getObject(path): Promise<RoolObject | undefined>` | Fetch one object by object path. |
|
|
317
|
-
| `getObjects(paths): Promise<GetObjectsResult>` | Fetch objects in bulk; returns `objects` and `missing`. |
|
|
318
|
-
| `space.putObject(path, body): Promise<{ object, message }>` | Create or replace an object at an exact path. |
|
|
319
|
-
| `space.patchObject(path, { data }): Promise<{ object, message }>` | Patch an object's body; `null`/`undefined` deletes fields. |
|
|
320
|
-
| `space.moveObject(from, to, options?): Promise<{ object, message }>` | Rename or relocate an object; `options.body` can replace the body after moving. |
|
|
321
|
-
| `space.deleteObjects(paths): Promise<void>` | Delete object files. |
|
|
322
|
-
|
|
323
|
-
## AI Agent
|
|
324
|
-
|
|
325
|
-
`prompt()` invokes the AI agent. The agent can inspect space context and, unless `readOnly` or a read-only effort is used, create/modify/move/delete objects.
|
|
326
|
-
|
|
327
|
-
```typescript
|
|
328
|
-
const { message, objects } = await conversation.prompt(
|
|
329
|
-
'Create a topic node for the solar system, then child nodes for each planet.'
|
|
330
|
-
);
|
|
331
|
-
|
|
332
|
-
console.log(message);
|
|
333
|
-
console.log(objects.map((object) => object.path));
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
### Prompt Options
|
|
337
|
-
|
|
338
|
-
| Option | Description |
|
|
339
|
-
| --- | --- |
|
|
340
|
-
| `responseSchema` | Request structured JSON text matching a JSON-schema-like shape. |
|
|
341
|
-
| `effort` | `'QUICK'` (fast/read-only), `'STANDARD'` (default), `'REASONING'`, or `'RESEARCH'`. |
|
|
342
|
-
| `ephemeral` | Do not record the prompt in interaction history. |
|
|
343
|
-
| `readOnly` | Disable mutation tools. |
|
|
344
|
-
| `parentInteractionId` | Conversation-tree parent. Omit to fetch and continue from the current default leaf; pass `null` for a new root branch. |
|
|
345
|
-
| `attachments` | Existing object/file paths or `rool-machine:/...` URIs, plus local files (`File`, `Blob`, or `{ data, contentType, filename? }`). |
|
|
346
|
-
| `signal` | AbortSignal used to request that the server stop an in-flight prompt. |
|
|
347
|
-
| `eventName` | Optional telemetry event name. Defaults to `'prompt_user'`. |
|
|
348
|
-
|
|
349
|
-
```typescript
|
|
350
|
-
// Read-only quick question
|
|
351
|
-
await conversation.prompt('What topics are covered?', {
|
|
352
|
-
effort: 'QUICK', // fast/read-only
|
|
54
|
+
const conversation = await agent.createConversation({
|
|
55
|
+
name: "Receipt inbox",
|
|
56
|
+
visibility: "private",
|
|
353
57
|
});
|
|
354
58
|
|
|
355
|
-
|
|
356
|
-
await
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
'rool-machine:/rool-drive/docs/report.pdf',
|
|
360
|
-
],
|
|
59
|
+
const receiptPath = "/rool-drive/receipts/unsorted/receipt.pdf";
|
|
60
|
+
await machine.files.write(receiptPath, await readFile("./receipt.pdf"), {
|
|
61
|
+
contentType: "application/pdf",
|
|
62
|
+
createParents: true,
|
|
361
63
|
});
|
|
362
64
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
attachments: [fileInput.files![0]],
|
|
65
|
+
await conversation.prompt("File this receipt.", {
|
|
66
|
+
attachments: [receiptPath],
|
|
366
67
|
});
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
properties: {
|
|
373
|
-
categories: { type: 'array', items: { type: 'string' } },
|
|
374
|
-
summary: { type: 'string' },
|
|
375
|
-
},
|
|
68
|
+
await conversation.follow({
|
|
69
|
+
onEvent(event) {
|
|
70
|
+
if (event.type === "output.delta" && event.content.type === "text") {
|
|
71
|
+
process.stdout.write(event.content.text);
|
|
72
|
+
}
|
|
376
73
|
},
|
|
377
74
|
});
|
|
378
|
-
const result = JSON.parse(message);
|
|
379
|
-
|
|
380
|
-
// Stop a long prompt with a signal (when the caller holds the controller)
|
|
381
|
-
const ac = new AbortController();
|
|
382
|
-
const promptPromise = conversation.prompt('Do a deep analysis', {
|
|
383
|
-
effort: 'RESEARCH',
|
|
384
|
-
signal: ac.signal,
|
|
385
|
-
});
|
|
386
|
-
ac.abort(); // asks the server to stop the in-flight interaction
|
|
387
|
-
await promptPromise;
|
|
388
75
|
```
|
|
389
76
|
|
|
390
|
-
|
|
77
|
+
Prompting is asynchronous: `prompt()` returns when Rool accepts the work, while the agent keeps running on the machine. An app can follow the current run, leave, and reconnect later. UI clients can instead watch a conversation and receive a current view as its saved turns and live output change.
|
|
391
78
|
|
|
392
|
-
|
|
393
|
-
lives elsewhere — a different component, after a reload, or a prompt another
|
|
394
|
-
client started — stop the conversation itself. A conversation processes one
|
|
395
|
-
run at a time, so no interaction ID is needed. Stopping is best-effort: the
|
|
396
|
-
server halts the agent loop and closes the stream, but an LLM turn already in
|
|
397
|
-
flight keeps generating server-side and is billed.
|
|
79
|
+
## Give agents data your app understands
|
|
398
80
|
|
|
399
|
-
|
|
400
|
-
// Stop whatever is running in a conversation. Returns whether anything was
|
|
401
|
-
// actually running.
|
|
402
|
-
await space.stopConversation('thread-42');
|
|
403
|
-
|
|
404
|
-
// Conversation handles stop their own running work.
|
|
405
|
-
const thread = space.conversation('thread-42');
|
|
406
|
-
await thread.stop();
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
| Method | Description |
|
|
410
|
-
| --- | --- |
|
|
411
|
-
| `stopConversation(conversationId): Promise<boolean>` | Stop whatever is running in a conversation. |
|
|
412
|
-
| `conversation.stop(): Promise<boolean>` | Stop this conversation's running work. |
|
|
413
|
-
|
|
414
|
-
## Conversations
|
|
415
|
-
|
|
416
|
-
Use `space.conversation(id)` for an imperative API scoped to one conversation. The handle retains no conversation contents or branch cursor; fetch contents when needed. With no explicit parent, `prompt()` fetches the current conversation and continues from its default leaf.
|
|
81
|
+
A machine can hold records as well as documents. A collection defines the fields in each record, and Rool rejects writes that do not match. For example, an app can keep an index alongside the receipt files:
|
|
417
82
|
|
|
418
83
|
```typescript
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
const thread = space.conversation('thread-42');
|
|
422
|
-
await thread.prompt('Hello from another thread');
|
|
423
|
-
await thread.setSystemInstruction('Answer in haiku');
|
|
424
|
-
|
|
425
|
-
const conversation = await thread.get();
|
|
426
|
-
const leaf = defaultConversationLeaf(conversation);
|
|
427
|
-
const branch = conversationBranch(conversation, leaf);
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
| Method/property | Description |
|
|
431
|
-
| --- | --- |
|
|
432
|
-
| `space.conversation(id): ConversationHandle` | Get a stateless conversation-scoped API handle. |
|
|
433
|
-
| `conversation.get(): Promise<Conversation | null>` | Fetch current conversation contents. |
|
|
434
|
-
| `conversation.prompt(text, options?)` | Prompt the conversation. Pass `parentInteractionId` to choose a branch explicitly. |
|
|
435
|
-
| `conversation.setSystemInstruction(value)` | Set or clear the system instruction. |
|
|
436
|
-
| `conversation.rename(name)` / `conversation.delete()` / `conversation.stop()` | Manage the scoped conversation. |
|
|
437
|
-
| `listConversations(): Promise<ConversationMeta[]>` | Fetch the conversation roster. |
|
|
438
|
-
| `createConversation(agent, visibility): Promise<string>` | Create a conversation under an agent; returns the server-minted conversation ID. |
|
|
439
|
-
| `deleteConversation(id): Promise<void>` | Delete a conversation by ID. |
|
|
440
|
-
|
|
441
|
-
### Agents
|
|
442
|
-
|
|
443
|
-
Conversations belong to an agent. Every space has the stock agent `rool`; spaces can also host custom agents. `createConversation` creates a conversation under any agent — including `rool` — with a server-minted ID and the visibility you choose; prompt the returned ID through a normal handle. Stock (`rool`) conversations can also spring into existence from a client-minted ID on first prompt (as in the examples above); custom-agent conversations are only created explicitly.
|
|
444
|
-
|
|
445
|
-
Each conversation has a visibility: `'shared'` (every space member), `'private'` (only you), or `'temporary'` (private, and auto-deleted after sitting idle). A conversation's agent and visibility are reported on its `ConversationMeta`.
|
|
446
|
-
|
|
447
|
-
```typescript
|
|
448
|
-
const agents = await space.listAgents(); // always includes 'rool'
|
|
449
|
-
|
|
450
|
-
const id = await space.createConversation('research-bot', 'private');
|
|
451
|
-
await space.conversation(id).prompt('Hello');
|
|
452
|
-
|
|
453
|
-
await space.deleteAgent('research-bot'); // removes the agent and all its conversations
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
| Method | Description |
|
|
457
|
-
| --- | --- |
|
|
458
|
-
| `listAgents(): Promise<string[]>` | The space's agents. Always includes the stock agent `rool`. |
|
|
459
|
-
| `createConversation(agent, visibility): Promise<string>` | Create a conversation under an agent with the given visibility. |
|
|
460
|
-
| `deleteAgent(agent): Promise<void>` | Delete a custom agent and all its conversations. The stock agent `rool` cannot be deleted. |
|
|
461
|
-
|
|
462
|
-
## Schema and Metadata
|
|
463
|
-
|
|
464
|
-
Collections define the schema visible to the AI agent. Hidden body fields whose names start with `_` are useful for app/UI state that should not be considered by AI.
|
|
465
|
-
|
|
466
|
-
```typescript
|
|
467
|
-
await space.createCollection('article', {
|
|
468
|
-
schemaOrgType: 'Article',
|
|
84
|
+
await machine.collections.create("receipts", {
|
|
469
85
|
fields: [
|
|
470
|
-
{ name:
|
|
471
|
-
{ name:
|
|
472
|
-
{ name:
|
|
473
|
-
{ name:
|
|
86
|
+
{ name: "vendor", type: { kind: "string" } },
|
|
87
|
+
{ name: "amount", type: { kind: "number" } },
|
|
88
|
+
{ name: "currency", type: { kind: "string" } },
|
|
89
|
+
{ name: "document", type: { kind: "string" } },
|
|
474
90
|
],
|
|
475
91
|
});
|
|
476
92
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
]);
|
|
483
|
-
|
|
484
|
-
await space.writeMeta({ viewport: { x: 0, y: 0, zoom: 1 } });
|
|
485
|
-
const meta = await space.readMeta();
|
|
486
|
-
```
|
|
487
|
-
|
|
488
|
-
| Method | Description |
|
|
489
|
-
| --- | --- |
|
|
490
|
-
| `readSchema(): Promise<SpaceSchema>` | Collection definitions, read from `/space/<name>/.schema.json`. |
|
|
491
|
-
| `createCollection(name, fieldsOrDef, options?): Promise<CollectionDef>` | Create a collection. |
|
|
492
|
-
| `alterCollection(name, fieldsOrDef, options?): Promise<CollectionDef>` | Replace a collection definition. |
|
|
493
|
-
| `dropCollection(name): Promise<void>` | Remove a collection and its object directory. |
|
|
494
|
-
| `readMeta(): Promise<Record<string, unknown>>` | Read metadata from `/space/.meta.json`. |
|
|
495
|
-
| `writeMeta(meta): Promise<void>` | Write the full metadata blob to `/space/.meta.json`. |
|
|
496
|
-
|
|
497
|
-
Field kinds: `string`, `number`, `boolean`, `ref`, `enum`, `literal`, `array`, and `maybe`.
|
|
498
|
-
|
|
499
|
-
## Undo/Redo
|
|
500
|
-
|
|
501
|
-
Undo/redo works over the whole space. Checkpoints are managed automatically by the server, so you don't need to create them yourself — just call `undo()`/`redo()`.
|
|
502
|
-
|
|
503
|
-
```typescript
|
|
504
|
-
await space.deleteObjects(['/space/article/welcome.json']);
|
|
505
|
-
|
|
506
|
-
if (await space.canUndo()) {
|
|
507
|
-
await space.undo();
|
|
508
|
-
}
|
|
509
|
-
```
|
|
510
|
-
|
|
511
|
-
| Method | Description |
|
|
512
|
-
| --- | --- |
|
|
513
|
-
| `canUndo(): Promise<boolean>` | Check whether undo is available. |
|
|
514
|
-
| `canRedo(): Promise<boolean>` | Check whether redo is available. |
|
|
515
|
-
| `undo(): Promise<boolean>` | Restore the latest checkpoint. |
|
|
516
|
-
| `redo(): Promise<boolean>` | Reapply undone work. |
|
|
517
|
-
|
|
518
|
-
Undo/redo availability is scoped to the space.
|
|
519
|
-
|
|
520
|
-
## File Storage and WebDAV
|
|
521
|
-
|
|
522
|
-
Every space has authenticated WebDAV storage. WebDAV methods take SDK machine paths such as `/space/...`, `/rool-drive/...`, or `/` for the root collection.
|
|
523
|
-
|
|
524
|
-
```typescript
|
|
525
|
-
const webdav = space.webdav;
|
|
526
|
-
|
|
527
|
-
await webdav.mkcol('/rool-drive/docs');
|
|
528
|
-
await webdav.put('/rool-drive/docs/readme.md', '# Hello', {
|
|
529
|
-
contentType: 'text/markdown',
|
|
530
|
-
ifNoneMatch: '*',
|
|
531
|
-
});
|
|
532
|
-
|
|
533
|
-
const listing = await webdav.propfind('/rool-drive/docs', {
|
|
534
|
-
depth: '1',
|
|
535
|
-
props: ['displayname', 'getcontentlength', 'getcontenttype', 'getetag'],
|
|
536
|
-
});
|
|
537
|
-
|
|
538
|
-
const response = await webdav.get('/rool-drive/docs/readme.md');
|
|
539
|
-
console.log(await response.text());
|
|
540
|
-
|
|
541
|
-
const file = await space.fetchPath('/rool-drive/docs/readme.md');
|
|
542
|
-
console.log(file.headers.get('Content-Type'));
|
|
543
|
-
|
|
544
|
-
```
|
|
545
|
-
|
|
546
|
-
### Real-time file sync
|
|
547
|
-
|
|
548
|
-
Object and file changes are announced at the space level. Use WebDAV `syncCollection()` to reconcile changes.
|
|
549
|
-
|
|
550
|
-
```typescript
|
|
551
|
-
let token: string | null = null;
|
|
552
|
-
|
|
553
|
-
async function syncFiles() {
|
|
554
|
-
const result = await space.webdav.syncCollection('/', {
|
|
555
|
-
token,
|
|
556
|
-
level: 'infinite',
|
|
557
|
-
props: ['displayname', 'getetag', 'getlastmodified', 'resourcetype'],
|
|
558
|
-
});
|
|
559
|
-
token = result.token;
|
|
560
|
-
updateFileTree(result.responses);
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
space.on('filesChanged', syncFiles);
|
|
564
|
-
space.on('filesReset', () => {
|
|
565
|
-
token = null;
|
|
566
|
-
void syncFiles();
|
|
93
|
+
await machine.objects.create("/space/receipts/cafe.json", {
|
|
94
|
+
vendor: "Cafe",
|
|
95
|
+
amount: 12,
|
|
96
|
+
currency: "EUR",
|
|
97
|
+
document: receiptPath,
|
|
567
98
|
});
|
|
568
|
-
|
|
569
|
-
await syncFiles();
|
|
570
|
-
```
|
|
571
|
-
|
|
572
|
-
| Method | Description |
|
|
573
|
-
| --- | --- |
|
|
574
|
-
| `webdav.href(path)` / `webdav.url(path)` | Return WebDAV href/URL for an absolute SDK path. |
|
|
575
|
-
| `webdav.options(path)` | Send `OPTIONS`. |
|
|
576
|
-
| `webdav.propfind(path, options)` | Read properties/list collections. `depth` is required. |
|
|
577
|
-
| `webdav.syncCollection(path, options)` | WebDAV `REPORT sync-collection`; returns changed responses and next token. |
|
|
578
|
-
| `webdav.get(path, options?)` / `webdav.head(path)` | Read a file; `get` supports byte ranges. |
|
|
579
|
-
| `webdav.put(path, body, options?)` | Write a file/object at an exact path. Parent collection must exist unless `createParents: true` is passed, which creates missing parent collections atomically (intended for `/rool-drive` paths). |
|
|
580
|
-
| `webdav.mkcol(path)` | Create one collection. |
|
|
581
|
-
| `webdav.copy(source, destination, options?)` | Copy a file or collection. |
|
|
582
|
-
| `webdav.move(source, destination, options?)` | Move a file or collection. |
|
|
583
|
-
| `webdav.delete(path, options?)` | Delete a file or collection. |
|
|
584
|
-
| `webdav.lock(path, options)` / `refreshLock(path, token)` / `unlock(token)` | WebDAV write locks. |
|
|
585
|
-
| `webdav.request(method, path, init?)` | Raw authenticated WebDAV request. |
|
|
586
|
-
| `space.fetchPath(path, options?)` | Fetch a `/rool-drive/...` file path or `rool-machine:` file URI. |
|
|
587
|
-
|
|
588
|
-
High-level WebDAV methods that validate response status throw `WebDAVError` with `status`, `statusText`, and `body`; raw `request()` and `options()` return `Response`.
|
|
589
|
-
|
|
590
|
-
## Collaboration
|
|
591
|
-
|
|
592
|
-
New members join a space by redeeming an invite. Owners and admins mint invites; the returned `url` contains the secret token and is only available at mint time.
|
|
593
|
-
|
|
594
|
-
```typescript
|
|
595
|
-
// Shareable invite link
|
|
596
|
-
const invite = await space.createInvite('editor', { expiresInDays: 7 });
|
|
597
|
-
console.log(invite.url);
|
|
598
|
-
|
|
599
|
-
// Email-guarded invite: single-use, locked to that address, sent by mail
|
|
600
|
-
const emailed = await space.createInvite('viewer', { email: 'colleague@example.com' });
|
|
601
|
-
if (emailed.emailStatus !== 'sent') {
|
|
602
|
-
// Mail did not go out (e.g. no mail provider configured) — share emailed.url yourself
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
// Manage outstanding invites
|
|
606
|
-
const invites = await space.listInvites();
|
|
607
|
-
await space.revokeInvite(invites[0].inviteId);
|
|
608
|
-
|
|
609
|
-
// Change an existing member's role, or remove them
|
|
610
|
-
await space.setUserRole(userId, 'admin');
|
|
611
|
-
await space.removeUser(userId);
|
|
612
|
-
```
|
|
613
|
-
|
|
614
|
-
On the join page, look up the invite before sign-in and redeem it once authenticated:
|
|
615
|
-
|
|
616
|
-
```typescript
|
|
617
|
-
const preview = await client.previewInvite(token); // no auth required
|
|
618
|
-
console.log(preview.spaceName, preview.role, preview.inviterName);
|
|
619
|
-
|
|
620
|
-
const result = await client.redeemInvite(token);
|
|
621
|
-
console.log(result.spaceId, result.status); // 'joined' | 'upgraded' | 'already_member'
|
|
622
|
-
```
|
|
623
|
-
|
|
624
|
-
Invalid, expired, revoked, exhausted, or email-mismatched invites throw `InviteError` with a `code` of `'INVITE_INVALID' | 'INVITE_EXPIRED' | 'INVITE_REVOKED' | 'INVITE_EXHAUSTED' | 'INVITE_EMAIL_MISMATCH'`.
|
|
625
|
-
|
|
626
|
-
```typescript
|
|
627
|
-
import { InviteError } from '@rool-dev/sdk';
|
|
628
|
-
|
|
629
|
-
try {
|
|
630
|
-
await client.redeemInvite(token);
|
|
631
|
-
} catch (error) {
|
|
632
|
-
if (error instanceof InviteError && error.code === 'INVITE_EXPIRED') {
|
|
633
|
-
// Ask for a fresh invite
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
```
|
|
637
|
-
|
|
638
|
-
Roles (invites grant `admin`, `editor`, or `viewer` — never `owner`):
|
|
639
|
-
|
|
640
|
-
| Role | Capabilities |
|
|
641
|
-
| --- | --- |
|
|
642
|
-
| `owner` | Full control. |
|
|
643
|
-
| `admin` | Editor capabilities plus user/link management. |
|
|
644
|
-
| `editor` | Create, modify, move, and delete objects/files. |
|
|
645
|
-
| `viewer` | Read-only access. |
|
|
646
|
-
|
|
647
|
-
## Gifts
|
|
648
|
-
|
|
649
|
-
A gift is something of value carried by a short code, e.g. `K7M2-9QRT`. Users are issued gifts server-side and give them away in their own words; whoever claims one gets what it holds. Claiming your own gift is a legitimate use. A gift is unclaimed or claimed, and claiming is single-use.
|
|
650
|
-
|
|
651
|
-
What a gift grants is in its `gift` field, discriminated by `kind`. Every gift also carries a server-rendered `description` such as `"10,000 AI credits"`, so UI can display any gift without knowing its kind. Prefer `description` unless you need the numbers.
|
|
652
|
-
|
|
653
|
-
Narrow on `kind` before reading any other payload field. Kinds are added over time, and un-narrowed access becomes a compile error when the next one lands.
|
|
654
|
-
|
|
655
|
-
Users hold no gifts by default, so `listGifts()` returning an empty list is the normal state for most accounts, not an error.
|
|
656
|
-
|
|
657
|
-
```typescript
|
|
658
|
-
// The current user's gifts, spent and unspent
|
|
659
|
-
const { gifts } = await client.listGifts();
|
|
660
|
-
for (const gift of gifts) {
|
|
661
|
-
console.log(gift.code, gift.url, gift.description, gift.claimedAt);
|
|
662
|
-
}
|
|
663
99
|
```
|
|
664
100
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
```typescript
|
|
668
|
-
const preview = await client.previewGift(code); // no auth required
|
|
669
|
-
console.log(`${preview.holderName} gave you ${preview.description}`);
|
|
101
|
+
The record is also a normal JSON file at `/space/receipts/cafe.json`. The app can work with it through `machine.objects`, while agents and programs inside the machine can use normal file tools. Changes from either side enter the same watched file tree.
|
|
670
102
|
|
|
671
|
-
|
|
672
|
-
if (gift.kind === 'credits') console.log(`+${gift.credits} credits`);
|
|
673
|
-
```
|
|
103
|
+
Collections can power task lists, catalogues, contact records, or a memory view with links between people, projects, and notes. The app gets predictable fields for its UI without hiding the data from the agent.
|
|
674
104
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
When a gift is claimed, `claimedByName` carries the claimer's display name — the reciprocal of the claim page showing the holder's name to the claimer. The claim page tells the claimer this will happen.
|
|
678
|
-
|
|
679
|
-
A holder can annotate a gift with a note ("sent this to Peter") and archive it to hide it from their default view. Both are bookkeeping only: an archived gift's code still claims.
|
|
680
|
-
|
|
681
|
-
A holder can also rotate a gift's code: a fresh code is minted and the old one stops working. Use it to take back a code that was given away but never claimed — the gift itself is untouched.
|
|
682
|
-
|
|
683
|
-
```typescript
|
|
684
|
-
await client.updateGift(gift.id, { note: 'sent to Peter' });
|
|
685
|
-
await client.updateGift(gift.id, { archived: true });
|
|
686
|
-
await client.updateGift(gift.id, { note: null }); // clear the note
|
|
687
|
-
|
|
688
|
-
const updated = await client.rotateGiftCode(gift.id);
|
|
689
|
-
console.log(updated.code, updated.url); // the old code and link are dead
|
|
690
|
-
```
|
|
105
|
+
## What the SDK handles
|
|
691
106
|
|
|
692
|
-
|
|
107
|
+
- **Machines:** create, configure, duplicate, checkpoint, share, and delete persistent VMs.
|
|
108
|
+
- **Files:** upload, stream, move, and watch files under `/space` and `/rool-drive`.
|
|
109
|
+
- **Agents:** define agents, keep conversation history, attach machine files, stream runs, and request structured output.
|
|
110
|
+
- **Live state:** learn when account data changes, keep a file tree current, and stream conversation updates.
|
|
111
|
+
- **Shared app data:** define records that apps, agents, and programs inside the machine can all read and edit.
|
|
693
112
|
|
|
694
|
-
|
|
695
|
-
| --- | --- |
|
|
696
|
-
| `gift_invalid` | No gift matches that code. |
|
|
697
|
-
| `gift_claimed` | The gift has already been claimed. |
|
|
113
|
+
Long-running work belongs to the machine rather than the client connection. File changes made by the app, a user, or an agent enter the same synchronized file tree, while account events tell the app when to fetch fresh account or machine data.
|
|
698
114
|
|
|
699
|
-
|
|
700
|
-
import { GiftError } from '@rool-dev/sdk';
|
|
701
|
-
|
|
702
|
-
try {
|
|
703
|
-
await client.claimGift(code);
|
|
704
|
-
} catch (error) {
|
|
705
|
-
if (error instanceof GiftError && error.code === 'gift_claimed') {
|
|
706
|
-
// Offer a plain signup instead
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
```
|
|
710
|
-
|
|
711
|
-
## RoolClient API
|
|
712
|
-
|
|
713
|
-
### Constructor config
|
|
714
|
-
|
|
715
|
-
```typescript
|
|
716
|
-
const client = new RoolClient({
|
|
717
|
-
apiUrl: 'https://api.rool.dev',
|
|
718
|
-
authUrl: 'https://rool.dev/auth',
|
|
719
|
-
graphqlUrl: 'https://api.rool.dev/graphql',
|
|
720
|
-
client: {
|
|
721
|
-
appName: 'com.example.app',
|
|
722
|
-
appVersion: '1.4.2',
|
|
723
|
-
osVersion: 'iOS 17.5',
|
|
724
|
-
},
|
|
725
|
-
logger: console,
|
|
726
|
-
});
|
|
727
|
-
```
|
|
728
|
-
|
|
729
|
-
`apiUrl` defaults to `https://api.rool.dev`; `authUrl` is derived by stripping the `api.` hostname prefix unless provided. `baseUrl` is still accepted as a deprecated alias for `apiUrl`. Pass `authProvider` for Node.js, Electron, or custom auth flows.
|
|
730
|
-
|
|
731
|
-
`client` is optional application identity sent on requests alongside the SDK package name/version. Compatibility is based only on the SDK version.
|
|
732
|
-
|
|
733
|
-
| Method/property | Description |
|
|
734
|
-
| --- | --- |
|
|
735
|
-
| `currentUser: CurrentUser | null` | Cached user profile from initialization/fetch. |
|
|
736
|
-
| `getCurrentUser(): Promise<CurrentUser>` | Fetch current user. |
|
|
737
|
-
| `updateCurrentUser(input): Promise<CurrentUser>` | Update `name`, `slug`, or `marketingOptIn`. |
|
|
738
|
-
| `deleteCurrentUser(): Promise<void>` | Mark account for deletion and log out. |
|
|
739
|
-
| `previewInvite(token): Promise<InvitePreview>` | Look up an invite link without redeeming it. No auth required. |
|
|
740
|
-
| `redeemInvite(token): Promise<InviteRedeemResult>` | Redeem an invite, joining (or upgrading in) its space. |
|
|
741
|
-
| `previewGift(code): Promise<GiftPreview>` | Look up a gift without claiming it. No auth required. |
|
|
742
|
-
| `claimGift(code): Promise<GiftClaimResult>` | Claim a gift, granting what it holds to the current account. |
|
|
743
|
-
| `listGifts(): Promise<GiftList>` | The current user's gifts, spent and unspent. |
|
|
744
|
-
| `updateGift(giftId, changes): Promise<Gift>` | Set or clear a gift's note, or change its archived state. |
|
|
745
|
-
| `rotateGiftCode(giftId): Promise<Gift>` | Mint a new code for a gift; the old code and link stop working. |
|
|
746
|
-
| `listSpaces(): Promise<RoolSpaceInfo[]>` | List accessible spaces. |
|
|
747
|
-
| `openSpace(id): Promise<RoolSpace>` | Open/cached live space handle. |
|
|
748
|
-
| `createSpace(name): Promise<RoolSpace>` | Create and open a space. |
|
|
749
|
-
| `duplicateSpace(sourceId, name): Promise<RoolSpace>` | Duplicate a space. |
|
|
750
|
-
| `deleteSpace(id): Promise<void>` | Permanently delete a space. |
|
|
751
|
-
| `importArchive(name, archive): Promise<RoolSpace>` | Import a zip archive as a new space. |
|
|
752
|
-
| `getUserStorage<T>(key): T | undefined` | Sync read from user-storage cache. |
|
|
753
|
-
| `setUserStorage(key, value): void` | Update user storage; `null`/`undefined` deletes. |
|
|
754
|
-
| `getAllUserStorage(): Record<string, unknown>` | Copy all cached user storage. |
|
|
755
|
-
| `reportEvent(event, url?): void` | Fire-and-forget telemetry event. |
|
|
756
|
-
| `destroy(): void` | Close subscriptions, spaces, auth resources, and listeners. |
|
|
757
|
-
| `generateId(): string` | Generate a unique ID suitable for conversation IDs. |
|
|
758
|
-
|
|
759
|
-
### Client events
|
|
760
|
-
|
|
761
|
-
```typescript
|
|
762
|
-
client.on('authStateChanged', (authenticated) => void 0);
|
|
763
|
-
client.on('currentUserChanged', (user) => void 0); // CurrentUser | null; null on sign-out
|
|
764
|
-
client.on('spaceAdded', (space) => void 0);
|
|
765
|
-
client.on('spaceRemoved', (spaceId) => void 0);
|
|
766
|
-
client.on('spaceRenamed', (spaceId, newName) => void 0);
|
|
767
|
-
client.on('userStorageChanged', ({ key, value, source }) => void 0);
|
|
768
|
-
client.on('connectionStateChanged', (state) => void 0);
|
|
769
|
-
client.on('error', (error, context) => void 0);
|
|
770
|
-
client.on('serverInfoChanged', (info) => void 0);
|
|
771
|
-
client.on('unsupported', (info) => void 0); // SDK older than server minimum
|
|
772
|
-
```
|
|
773
|
-
|
|
774
|
-
## RoolSpace API
|
|
775
|
-
|
|
776
|
-
Properties: `id`, `name`, `role`, `memberCount`, `openSpaceResult`, `route`, `webdav`.
|
|
777
|
-
|
|
778
|
-
| Method | Description |
|
|
779
|
-
| --- | --- |
|
|
780
|
-
| `conversation(conversationId): ConversationHandle` | Get a stateless conversation-scoped handle. |
|
|
781
|
-
| `getObject`, `getObjects` | Read object data. |
|
|
782
|
-
| `listConversations`, `createConversation`, `deleteConversation` | Fetch, create, and delete conversations. |
|
|
783
|
-
| `listAgents`, `deleteAgent` | List the space's agents and delete a custom agent. |
|
|
784
|
-
| `readMeta`, `writeMeta`, `readSchema` | Read and write WebDAV-backed metadata and schema. |
|
|
785
|
-
| `canUndo`, `canRedo`, `undo`, `redo` | Space history controls. |
|
|
786
|
-
| `stopConversation(conversationId): Promise<boolean>` | Stop whatever is running in a conversation. |
|
|
787
|
-
| `fetch(url, init?): Promise<Response>` | Proxy an external HTTP request through the server to bypass browser CORS. |
|
|
788
|
-
| `close(): void` | Stop the space subscription. |
|
|
789
|
-
| `rename(newName): Promise<void>` | Rename the space. |
|
|
790
|
-
| `delete(): Promise<void>` | Permanently delete the space. |
|
|
791
|
-
| `listUsers(): Promise<SpaceMember[]>` | List collaborators. |
|
|
792
|
-
| `setUserRole(userId, role): Promise<void>` | Change an existing member's role. |
|
|
793
|
-
| `removeUser(userId): Promise<void>` | Remove collaborator. |
|
|
794
|
-
| `createInvite(role, options?): Promise<SpaceInviteCreated>` | Mint an invite link; `options` takes `email`, `expiresInDays`, `maxUses`. |
|
|
795
|
-
| `listInvites(): Promise<SpaceInvite[]>` | List currently redeemable invites. |
|
|
796
|
-
| `revokeInvite(inviteId): Promise<boolean>` | Revoke an invite so its link stops working. |
|
|
797
|
-
| `exportArchive(): Promise<Blob>` | Export a space archive. |
|
|
798
|
-
| `refresh(): Promise<OpenSpaceResult>` | Refresh identity, access, member count, and conversation metadata. |
|
|
799
|
-
| `fetchPath(path, options?): Promise<Response>` | Fetch a `/rool-drive/...` file. |
|
|
800
|
-
|
|
801
|
-
Events:
|
|
802
|
-
|
|
803
|
-
```typescript
|
|
804
|
-
space.on('conversationUpdated', ({ conversationId, conversation, source, timestamp }) => void 0);
|
|
805
|
-
space.on('syncError', (error) => void 0);
|
|
806
|
-
space.on('filesChanged', ({ spaceId, source, timestamp }) => void 0);
|
|
807
|
-
space.on('filesReset', ({ spaceId, source, timestamp }) => void 0);
|
|
808
|
-
space.on('connectionStateChanged', (state) => void 0);
|
|
809
|
-
```
|
|
810
|
-
## Import/Export
|
|
811
|
-
|
|
812
|
-
```typescript
|
|
813
|
-
const archive = await space.exportArchive();
|
|
814
|
-
const imported = await client.importArchive('Imported Data', archive);
|
|
815
|
-
```
|
|
816
|
-
|
|
817
|
-
Archives include objects, metadata, conversations, and file storage.
|
|
818
|
-
|
|
819
|
-
## Data Types
|
|
820
|
-
|
|
821
|
-
```typescript
|
|
822
|
-
// Outcome of signInWithPassword. 'verify_required' means the account's email
|
|
823
|
-
// isn't verified yet and the server has emailed a magic link.
|
|
824
|
-
type PasswordSignInResult = { status: 'signed_in' | 'verify_required' };
|
|
825
|
-
|
|
826
|
-
type FieldType =
|
|
827
|
-
| { kind: 'string' }
|
|
828
|
-
| { kind: 'number' }
|
|
829
|
-
| { kind: 'boolean' }
|
|
830
|
-
| { kind: 'array'; inner?: FieldType }
|
|
831
|
-
| { kind: 'maybe'; inner: FieldType }
|
|
832
|
-
| { kind: 'enum'; values: string[] }
|
|
833
|
-
| { kind: 'literal'; value: string | number | boolean }
|
|
834
|
-
| { kind: 'ref' };
|
|
835
|
-
|
|
836
|
-
interface FieldDef {
|
|
837
|
-
name: string;
|
|
838
|
-
type: FieldType;
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
interface CollectionDef {
|
|
842
|
-
fields: FieldDef[];
|
|
843
|
-
schemaOrgType?: string;
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
type SpaceSchema = Record<string, CollectionDef>;
|
|
847
|
-
|
|
848
|
-
interface RoolObject {
|
|
849
|
-
path: string;
|
|
850
|
-
body: Record<string, unknown>;
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
interface GetObjectsResult {
|
|
854
|
-
objects: RoolObject[];
|
|
855
|
-
missing: string[];
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
// Who may see a conversation: 'shared' is every space member; 'private' is
|
|
859
|
-
// owner-only; 'temporary' is private plus auto-delete once it sits idle.
|
|
860
|
-
type ConversationVisibility = 'shared' | 'private' | 'temporary';
|
|
861
|
-
|
|
862
|
-
// Lightweight conversation roster entry (no interaction bodies), returned by
|
|
863
|
-
// openSpace/listConversations. `updatedAt` drives last-activity display.
|
|
864
|
-
interface ConversationMeta {
|
|
865
|
-
id: string;
|
|
866
|
-
agent: string; // owning agent ('rool' is the stock agent)
|
|
867
|
-
visibility: ConversationVisibility;
|
|
868
|
-
name: string | null;
|
|
869
|
-
systemInstruction: string | null;
|
|
870
|
-
createdAt: number;
|
|
871
|
-
createdBy: string;
|
|
872
|
-
interactionCount: number;
|
|
873
|
-
updatedAt: number;
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
type InviteRole = 'admin' | 'editor' | 'viewer';
|
|
877
|
-
|
|
878
|
-
interface SpaceInvite {
|
|
879
|
-
inviteId: string;
|
|
880
|
-
spaceId: string;
|
|
881
|
-
role: InviteRole;
|
|
882
|
-
email: string | null;
|
|
883
|
-
createdBy: string;
|
|
884
|
-
createdAt: string;
|
|
885
|
-
expiresAt: string;
|
|
886
|
-
maxUses: number | null;
|
|
887
|
-
useCount: number;
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
// Outcome of the invite email send. Null when no email was involved (open link).
|
|
891
|
-
// The invite is always minted and its `url` is usable regardless of this value;
|
|
892
|
-
// only the email delivery is reflected here.
|
|
893
|
-
// - 'sent': email dispatched
|
|
894
|
-
// - 'not_configured': server has no mail provider (local dev)
|
|
895
|
-
// - 'failed': provider rejected the send
|
|
896
|
-
// - 'cooldown': a recent invite to this same address was already emailed
|
|
897
|
-
// - 'rate_limited': the inviter hit their daily email-invite cap
|
|
898
|
-
// Treat unknown values as not sent.
|
|
899
|
-
type InviteEmailStatus =
|
|
900
|
-
| 'sent'
|
|
901
|
-
| 'not_configured'
|
|
902
|
-
| 'failed'
|
|
903
|
-
| 'cooldown'
|
|
904
|
-
| 'rate_limited'
|
|
905
|
-
| (string & {});
|
|
906
|
-
|
|
907
|
-
interface SpaceInviteCreated {
|
|
908
|
-
inviteId: string;
|
|
909
|
-
spaceId: string;
|
|
910
|
-
role: InviteRole;
|
|
911
|
-
email: string | null;
|
|
912
|
-
expiresAt: string;
|
|
913
|
-
maxUses: number | null;
|
|
914
|
-
url: string; // contains the secret token; only available at mint time
|
|
915
|
-
emailStatus: InviteEmailStatus | null;
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
interface InvitePreview {
|
|
919
|
-
spaceId: string;
|
|
920
|
-
spaceName: string;
|
|
921
|
-
role: InviteRole;
|
|
922
|
-
email: string | null;
|
|
923
|
-
inviterName: string | null;
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
interface InviteRedeemResult {
|
|
927
|
-
spaceId: string;
|
|
928
|
-
role: RoolUserRole;
|
|
929
|
-
status: 'joined' | 'upgraded' | 'already_member';
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
type GiftPayload = { kind: 'credits'; credits: number };
|
|
933
|
-
|
|
934
|
-
interface Gift {
|
|
935
|
-
id: string;
|
|
936
|
-
code: string; // display form, e.g. "K7M2-9QRT"
|
|
937
|
-
url: string; // claim URL carrying the code
|
|
938
|
-
gift: GiftPayload;
|
|
939
|
-
description: string; // e.g. "10,000 AI credits"
|
|
940
|
-
claimedAt: string | null; // null while unspent
|
|
941
|
-
claimedByName: string | null; // claimer's display name, when they have one
|
|
942
|
-
createdAt: string;
|
|
943
|
-
note: string | null; // the holder's own reminder
|
|
944
|
-
archivedAt: string | null; // set when hidden; the code still claims
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
interface GiftUpdate {
|
|
948
|
-
note?: string | null; // undefined leaves as-is, null clears
|
|
949
|
-
archived?: boolean;
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
interface GiftList {
|
|
953
|
-
gifts: Gift[];
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
interface GiftPreview {
|
|
957
|
-
holderName: string | null;
|
|
958
|
-
gift: GiftPayload;
|
|
959
|
-
description: string;
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
interface GiftClaimResult {
|
|
963
|
-
gift: GiftPayload;
|
|
964
|
-
description: string;
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
type PromptAttachment =
|
|
968
|
-
| File
|
|
969
|
-
| Blob
|
|
970
|
-
| { data: string; contentType: string; filename?: string }
|
|
971
|
-
| string;
|
|
972
|
-
|
|
973
|
-
type PromptEffort = 'QUICK' | 'STANDARD' | 'REASONING' | 'RESEARCH';
|
|
974
|
-
|
|
975
|
-
interface PromptOptions {
|
|
976
|
-
responseSchema?: Record<string, unknown>;
|
|
977
|
-
effort?: PromptEffort;
|
|
978
|
-
parentInteractionId?: string | null;
|
|
979
|
-
ephemeral?: boolean;
|
|
980
|
-
readOnly?: boolean;
|
|
981
|
-
attachments?: PromptAttachment[];
|
|
982
|
-
signal?: AbortSignal;
|
|
983
|
-
eventName?: string;
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
type InteractionStatus = 'pending' | 'streaming' | 'done' | 'error';
|
|
987
|
-
|
|
988
|
-
interface Interaction {
|
|
989
|
-
id: string;
|
|
990
|
-
parentId: string | null;
|
|
991
|
-
timestamp: number;
|
|
992
|
-
userId: string;
|
|
993
|
-
userName?: string | null;
|
|
994
|
-
operation: 'prompt' | 'putObject' | 'patchObject' | 'moveObject' | 'deleteObjects' | 'deletePaths' | string;
|
|
995
|
-
input: string;
|
|
996
|
-
output: string | null;
|
|
997
|
-
status: InteractionStatus;
|
|
998
|
-
ai: boolean;
|
|
999
|
-
modifiedObjectPaths: string[];
|
|
1000
|
-
toolCalls: ToolCall[];
|
|
1001
|
-
attachments?: string[];
|
|
1002
|
-
}
|
|
1003
|
-
```
|
|
115
|
+
The package exports its public TypeScript types, so editor autocomplete shows the detailed options and results. See [docs.rool.dev](https://docs.rool.dev/) for the published documentation.
|
|
1004
116
|
|
|
1005
117
|
## License
|
|
1006
118
|
|
|
1007
|
-
MIT
|
|
119
|
+
MIT — see [LICENSE](./LICENSE).
|