@rool-dev/sdk 0.9.0-dev.10b2f38 → 0.9.0-dev.9cb655b
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 +72 -33
- package/dist/channel.d.ts +9 -27
- package/dist/channel.d.ts.map +1 -1
- package/dist/channel.js +112 -46
- package/dist/channel.js.map +1 -1
- package/dist/client.d.ts +11 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +26 -54
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -4
- package/dist/index.js.map +1 -1
- package/dist/rest.d.ts +18 -0
- package/dist/rest.d.ts.map +1 -0
- package/dist/rest.js +46 -0
- package/dist/rest.js.map +1 -0
- package/dist/space.d.ts +10 -3
- package/dist/space.d.ts.map +1 -1
- package/dist/space.js +19 -39
- package/dist/space.js.map +1 -1
- package/dist/types.d.ts +4 -24
- package/dist/types.d.ts.map +1 -1
- package/dist/webdav.d.ts +159 -0
- package/dist/webdav.d.ts.map +1 -0
- package/dist/webdav.js +483 -0
- package/dist/webdav.js.map +1 -0
- package/package.json +1 -1
- package/dist/media.d.ts +0 -70
- package/dist/media.d.ts.map +0 -1
- package/dist/media.js +0 -228
- package/dist/media.js.map +0 -1
package/README.md
CHANGED
|
@@ -4,13 +4,14 @@ The TypeScript SDK for Rool, a persistent and collaborative environment for orga
|
|
|
4
4
|
|
|
5
5
|
> **Building a new Rool extension?** Start with [`@rool-dev/extension`](/extension/) — it handles hosting, dev server, and gives you a reactive Svelte channel out of the box. This SDK is for advanced use cases: integrating Rool into an existing application, building Node.js scripts, or working outside the extension sandbox.
|
|
6
6
|
|
|
7
|
-
The SDK manages authentication, real-time synchronization, and
|
|
7
|
+
The SDK manages authentication, real-time synchronization, and per-space file storage. Core primitives:
|
|
8
8
|
|
|
9
|
-
- **Spaces** — Containers for objects, schema, metadata, and
|
|
9
|
+
- **Spaces** — Containers for objects, schema, metadata, channels, and files
|
|
10
10
|
- **Channels** — Named contexts within a space. All object and AI operations go through a channel.
|
|
11
11
|
- **Conversations** — Independent interaction histories within a channel.
|
|
12
12
|
- **Objects** — Key-value records with any fields you define. References between objects are data fields whose values are object IDs.
|
|
13
13
|
- **AI operations** — Create, update, or query objects using natural language and `{{placeholders}}`
|
|
14
|
+
- **File storage** — Every space has WebDAV file storage
|
|
14
15
|
|
|
15
16
|
## Installation
|
|
16
17
|
|
|
@@ -82,11 +83,11 @@ channel.close();
|
|
|
82
83
|
|
|
83
84
|
### Spaces and Channels
|
|
84
85
|
|
|
85
|
-
A **space** is a container that holds objects, schema, metadata, and
|
|
86
|
+
A **space** is a container that holds objects, schema, metadata, channels, and files. A **channel** is a named context within a space — it's the handle you use for all object and AI operations. Each channel contains one or more **conversations**, each with independent interaction history.
|
|
86
87
|
|
|
87
88
|
There are two main handles:
|
|
88
|
-
- **`RoolSpace`** — Live handle with SSE subscription for user management, link access, channel management, export, and channel lifecycle events. Extends `EventEmitter`.
|
|
89
|
-
- **`RoolChannel`** — Full real-time handle for objects, AI prompts,
|
|
89
|
+
- **`RoolSpace`** — Live handle with SSE subscription for user management, link access, channel management, file storage, export, and channel lifecycle events. Extends `EventEmitter`.
|
|
90
|
+
- **`RoolChannel`** — Full real-time handle for objects, AI prompts, schema, and undo/redo.
|
|
90
91
|
|
|
91
92
|
```typescript
|
|
92
93
|
// Open a space — live handle with SSE subscription
|
|
@@ -395,7 +396,7 @@ Returns a message (the AI's response) and the list of objects that were created
|
|
|
395
396
|
| `ephemeral` | If true, don't record in interaction history (useful for tab completion) |
|
|
396
397
|
| `readOnly` | If true, disable mutation tools (create, update, delete). Use for questions. |
|
|
397
398
|
| `parentInteractionId` | Parent interaction in the conversation tree. Omit to auto-continue from the active leaf. Pass `null` to start a new root-level branch. Pass a specific ID to branch from that point (edit/reroll). |
|
|
398
|
-
| `attachments` | Files to attach (`File`, `Blob`, or `{ data, contentType }`).
|
|
399
|
+
| `attachments` | Files to attach (`File`, `Blob`, or `{ data, contentType }`). Stored as authenticated space files; resulting `rool-drive:/...` references are stored on the interaction's `attachments` field for UI rendering. The AI can interpret images (JPEG, PNG, GIF, WebP, SVG), PDFs, text-based files (plain text, Markdown, CSV, HTML, XML, JSON), and DOCX documents. Other file types are uploaded and stored but the AI cannot natively consume their contents, only use shell tools on them. |
|
|
399
400
|
| `signal` | `AbortSignal` to stop the prompt mid-flight. When aborted, the agent loop halts and the streaming response closes. Note that any LLM turn already in flight on Vertex keeps generating server-side and is billed. |
|
|
400
401
|
|
|
401
402
|
### Effort Levels
|
|
@@ -593,6 +594,8 @@ const client = new RoolClient({
|
|
|
593
594
|
| `createSpace(name): Promise<RoolSpace>` | Create a new space, returns live handle with SSE subscription |
|
|
594
595
|
| `deleteSpace(id): Promise<void>` | Permanently delete a space (cannot be undone) |
|
|
595
596
|
| `importArchive(name, archive): Promise<RoolSpace>` | Import from a zip archive, creating a new space |
|
|
597
|
+
| `webdav(spaceId): RoolWebDAV` | Open a WebDAV client for a space's file storage |
|
|
598
|
+
| `getSpaceStorageUsage(spaceId): Promise<SpaceFileStorageUsage>` | Get WebDAV quota usage for a space |
|
|
596
599
|
|
|
597
600
|
### Channel Management
|
|
598
601
|
|
|
@@ -710,7 +713,7 @@ client.on('spaceRenamed', (id, name) => {
|
|
|
710
713
|
|
|
711
714
|
## RoolSpace API
|
|
712
715
|
|
|
713
|
-
A space handle with a live SSE subscription. Extends `EventEmitter`. Manages user access, link sharing, channels, and export. The `channels` property auto-updates via SSE, and channel lifecycle events fire in real-time.
|
|
716
|
+
A space handle with a live SSE subscription. Extends `EventEmitter`. Manages user access, link sharing, channels, file storage, and export. The `channels` property auto-updates via SSE, and channel lifecycle events fire in real-time.
|
|
714
717
|
|
|
715
718
|
`openSpace()` caches and reuses open spaces — calling it twice with the same ID returns the same instance. Call `close()` when done to stop the subscription and close all open channels.
|
|
716
719
|
|
|
@@ -724,6 +727,7 @@ A space handle with a live SSE subscription. Extends `EventEmitter`. Manages use
|
|
|
724
727
|
| `linkAccess: LinkAccess` | URL sharing level |
|
|
725
728
|
| `memberCount: number` | Number of users with access to the space |
|
|
726
729
|
| `channels: ChannelInfo[]` | Live channel list (auto-updates via SSE) |
|
|
730
|
+
| `webdav: RoolWebDAV` | WebDAV client for this space's file storage |
|
|
727
731
|
|
|
728
732
|
### Methods
|
|
729
733
|
|
|
@@ -742,6 +746,7 @@ A space handle with a live SSE subscription. Extends `EventEmitter`. Manages use
|
|
|
742
746
|
| `deleteChannel(channelId): Promise<void>` | Delete a channel |
|
|
743
747
|
| `installExtension(extensionId, channelId): Promise<string>` | Install an extension into a channel of this space. If you own it, wires it directly. If it's a marketplace extension, copies and builds a new extension in your library. Returns the channel ID. |
|
|
744
748
|
| `exportArchive(): Promise<Blob>` | Export space as zip archive |
|
|
749
|
+
| `getStorageUsage(): Promise<SpaceFileStorageUsage>` | Get WebDAV quota usage for this space |
|
|
745
750
|
| `refresh(): Promise<void>` | Refresh space data from server |
|
|
746
751
|
|
|
747
752
|
### Space Events
|
|
@@ -885,35 +890,71 @@ Store arbitrary data alongside the Space without it being part of the object dat
|
|
|
885
890
|
| `getMetadata(key): unknown` | Get metadata value, or undefined if key not set |
|
|
886
891
|
| `getAllMetadata(): Record<string, unknown>` | Get all metadata |
|
|
887
892
|
|
|
888
|
-
###
|
|
893
|
+
### Space File Storage
|
|
889
894
|
|
|
890
|
-
|
|
895
|
+
Every space has authenticated file storage. WebDAV is the SDK surface for that storage: paths are relative to the space root, collection operations use WebDAV collection semantics, and `rool-drive:/...` references are the text-safe way to point at files.
|
|
891
896
|
|
|
892
|
-
|
|
893
|
-
|--------|-------------|
|
|
894
|
-
| `uploadMedia(file): Promise<string>` | Upload file, returns URL |
|
|
895
|
-
| `fetchMedia(url, options?): Promise<MediaResponse>` | Fetch any URL, returns headers and blob() method (adds auth for backend URLs, works for external URLs too). Pass `{ forceProxy: true }` to skip the direct fetch and route through the server proxy immediately. |
|
|
896
|
-
| `deleteMedia(url): Promise<void>` | Delete media file by URL |
|
|
897
|
-
| `listMedia(): Promise<MediaInfo[]>` | List all media with metadata |
|
|
897
|
+
Use `client.webdav(spaceId)` when you only have an ID, or `space.webdav` when you already have an open space.
|
|
898
898
|
|
|
899
899
|
```typescript
|
|
900
|
-
|
|
901
|
-
const url = await channel.uploadMedia(file);
|
|
902
|
-
await channel.createObject({ data: { type: 'photo', title: 'Photo', image: url } });
|
|
900
|
+
const webdav = client.webdav('space-id');
|
|
903
901
|
|
|
904
|
-
|
|
905
|
-
await
|
|
906
|
-
|
|
902
|
+
await webdav.mkcol('docs');
|
|
903
|
+
await webdav.put('docs/readme.md', '# Hello', {
|
|
904
|
+
contentType: 'text/markdown',
|
|
905
|
+
ifNoneMatch: '*',
|
|
907
906
|
});
|
|
908
907
|
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
908
|
+
const listing = await webdav.propfind('docs/', {
|
|
909
|
+
depth: '1',
|
|
910
|
+
props: ['displayname', 'getcontentlength', 'getcontenttype', 'getetag'],
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
const file = await webdav.get('docs/readme.md');
|
|
914
|
+
console.log(await file.text());
|
|
915
|
+
|
|
916
|
+
const ref = webdav.ref('docs/read me.md'); // "rool-drive:/docs/read%20me.md"
|
|
917
|
+
const sameFile = await webdav.get(ref);
|
|
918
|
+
|
|
919
|
+
const usage = await space.getStorageUsage();
|
|
920
|
+
console.log(usage.usedBytes);
|
|
921
|
+
console.log(usage.availableBytes); // null means unlimited
|
|
922
|
+
console.log(usage.limitBytes); // null means unlimited
|
|
915
923
|
```
|
|
916
924
|
|
|
925
|
+
Paths are space-relative (`docs/readme.md`, not `/docs/readme.md`). `rool-drive:/...` references percent-encode path segments for use in text; `webdav.path('rool-drive:/docs/read%20me.md')` returns `docs/read me.md`. The WebDAV methods accept either paths or refs and normalize them to paths. `PUT` writes an exact path and does not create parent collections; create parents with `mkcol()` first. Helpers preserve WebDAV status semantics: non-success responses throw `WebDAVError` with `status`, `statusText`, and `body`.
|
|
926
|
+
|
|
927
|
+
| Method | Description |
|
|
928
|
+
|--------|-------------|
|
|
929
|
+
| `client.webdav(spaceId)` | Create a WebDAV client for a space |
|
|
930
|
+
| `client.getSpaceStorageUsage(spaceId)` | Get WebDAV quota usage for a space |
|
|
931
|
+
| `space.webdav` | WebDAV client for an open space |
|
|
932
|
+
| `space.getStorageUsage()` | Get WebDAV quota usage for an open space |
|
|
933
|
+
| `webdav.getStorageUsage()` | Get WebDAV quota usage through the WebDAV client |
|
|
934
|
+
| `webdav.ref(path)` | Create a `rool-drive:/...` reference |
|
|
935
|
+
| `webdav.path(pathOrRef)` | Normalize a path or `rool-drive:/...` reference to a path |
|
|
936
|
+
| `webdav.propfind(pathOrRef, options)` | Read properties/list collections; explicit `depth` required |
|
|
937
|
+
| `webdav.get(pathOrRef, options?)` / `webdav.head(pathOrRef)` | Read a file, including optional byte ranges for `get` |
|
|
938
|
+
| `webdav.put(pathOrRef, body, options?)` | Write an exact file path; parents must already exist |
|
|
939
|
+
| `webdav.mkcol(path)` | Create one collection |
|
|
940
|
+
| `webdav.copy(source, destination, options?)` | Copy a file or collection within the same space |
|
|
941
|
+
| `webdav.move(source, destination, options?)` | Move a file or collection within the same space |
|
|
942
|
+
| `webdav.delete(pathOrRef, options?)` | Delete a file or collection |
|
|
943
|
+
| `webdav.lock(pathOrRef, options)` / `webdav.refreshLock(pathOrRef, token)` / `webdav.unlock(token)` | WebDAV Class 2 write locks |
|
|
944
|
+
| `webdav.request(method, path, init?)` | Raw authenticated WebDAV request escape hatch |
|
|
945
|
+
|
|
946
|
+
#### File references from AI responses
|
|
947
|
+
|
|
948
|
+
When an agent refers to a user-visible file, the SDK contract is `rool-drive:/path/to/file.ext`. That prefix makes a file reference unambiguous without exposing the authenticated WebDAV URL. In free text, ambiguous characters such as spaces are percent-encoded (`rool-drive:/docs/read%20me.md`).
|
|
949
|
+
|
|
950
|
+
```typescript
|
|
951
|
+
const response = await space.webdav.get('rool-drive:/docs/readme.md');
|
|
952
|
+
const blob = await response.blob();
|
|
953
|
+
img.src = URL.createObjectURL(blob);
|
|
954
|
+
```
|
|
955
|
+
|
|
956
|
+
Plain relative strings like `docs/readme.md` are valid WebDAV paths when you already know you are working with file storage. In user text or agent output, use `rool-drive:/docs/readme.md` so clients do not have to guess whether a string is a file. Prefer `webdav.ref(path)` rather than building refs by hand.
|
|
957
|
+
|
|
917
958
|
### Proxied Fetch
|
|
918
959
|
|
|
919
960
|
Fetch external URLs via the server, bypassing CORS restrictions. Requires editor role or above. Private/internal IP ranges are blocked (SSRF protection).
|
|
@@ -994,7 +1035,7 @@ Export and import space data as zip archives for backup, portability, or migrati
|
|
|
994
1035
|
|
|
995
1036
|
| Method | Description |
|
|
996
1037
|
|--------|-------------|
|
|
997
|
-
| `space.exportArchive(): Promise<Blob>` | Export objects, metadata, channels, and
|
|
1038
|
+
| `space.exportArchive(): Promise<Blob>` | Export objects, metadata, channels, and files as a zip archive |
|
|
998
1039
|
| `client.importArchive(name, archive): Promise<RoolSpace>` | Import from a zip archive, creating a new space |
|
|
999
1040
|
|
|
1000
1041
|
**Export:**
|
|
@@ -1011,7 +1052,7 @@ const space = await client.importArchive('Imported Data', archiveBlob);
|
|
|
1011
1052
|
const channel = await space.openChannel('main');
|
|
1012
1053
|
```
|
|
1013
1054
|
|
|
1014
|
-
The archive
|
|
1055
|
+
The archive bundles `data.json` (objects, metadata, and channels) together with the space file storage. File references are rewritten to relative paths within the archive and restored on import.
|
|
1015
1056
|
|
|
1016
1057
|
### Channel Events
|
|
1017
1058
|
|
|
@@ -1220,7 +1261,7 @@ interface Interaction {
|
|
|
1220
1261
|
ai: boolean; // Whether AI was invoked (vs synthetic confirmation)
|
|
1221
1262
|
modifiedObjectIds: string[]; // Objects affected by this interaction
|
|
1222
1263
|
toolCalls: ToolCall[]; // Tools called during this interaction (for AI prompts)
|
|
1223
|
-
attachments?: string[]; //
|
|
1264
|
+
attachments?: string[]; // rool-drive:/... file references attached by the user
|
|
1224
1265
|
}
|
|
1225
1266
|
```
|
|
1226
1267
|
|
|
@@ -1234,8 +1275,6 @@ interface RoolSpaceInfo { id: string; name: string; role: RoolUserRole; ownerId:
|
|
|
1234
1275
|
interface SpaceMember { id: string; email: string; role: RoolUserRole; photoUrl: string | null; }
|
|
1235
1276
|
interface UserResult { id: string; email: string; name: string | null; photoUrl: string | null; }
|
|
1236
1277
|
interface CurrentUser { id: string; email: string; name: string | null; photoUrl: string | null; slug: string; plan: string; creditsBalance: number; totalCreditsUsed: number; createdAt: string; lastActivity: string; processedAt: string; storage: Record<string, unknown>; }
|
|
1237
|
-
interface MediaInfo { url: string; contentType: string; size: number; createdAt: string; }
|
|
1238
|
-
interface MediaResponse { contentType: string; size: number | null; blob(): Promise<Blob>; }
|
|
1239
1278
|
type ChangeSource = 'local_user' | 'remote_user' | 'remote_agent' | 'system';
|
|
1240
1279
|
```
|
|
1241
1280
|
|
|
@@ -1251,7 +1290,7 @@ interface PromptOptions {
|
|
|
1251
1290
|
ephemeral?: boolean; // Don't record in interaction history
|
|
1252
1291
|
readOnly?: boolean; // Disable mutation tools (default: false)
|
|
1253
1292
|
parentInteractionId?: string | null; // Branch from a specific interaction (omit to auto-continue)
|
|
1254
|
-
attachments?: Array<File | Blob | { data: string; contentType: string }>; // Files to attach
|
|
1293
|
+
attachments?: Array<File | Blob | { data: string; contentType: string }>; // Files to attach
|
|
1255
1294
|
}
|
|
1256
1295
|
```
|
|
1257
1296
|
|
package/dist/channel.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { EventEmitter } from './event-emitter.js';
|
|
2
2
|
import type { GraphQLClient } from './graphql.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type { RestClient } from './rest.js';
|
|
4
|
+
import type { RoolWebDAV } from './webdav.js';
|
|
4
5
|
import type { Logger } from './logger.js';
|
|
5
|
-
import type { RoolObject, RoolObjectStat, ChannelEvents, RoolUserRole, PromptOptions, FindObjectsOptions, CreateObjectOptions, UpdateObjectOptions,
|
|
6
|
+
import type { RoolObject, RoolObjectStat, ChannelEvents, RoolUserRole, PromptOptions, FindObjectsOptions, CreateObjectOptions, UpdateObjectOptions, ChannelEvent, Interaction, Channel, ConversationInfo, LinkAccess, SpaceSchema, CollectionDef, FieldDef, ExtensionManifest } from './types.js';
|
|
6
7
|
export declare function generateEntityId(): string;
|
|
7
8
|
export interface ChannelConfig {
|
|
8
9
|
id: string;
|
|
@@ -24,7 +25,8 @@ export interface ChannelConfig {
|
|
|
24
25
|
/** Channel ID for this channel (required). */
|
|
25
26
|
channelId: string;
|
|
26
27
|
graphqlClient: GraphQLClient;
|
|
27
|
-
|
|
28
|
+
restClient: RestClient;
|
|
29
|
+
webdav: RoolWebDAV;
|
|
28
30
|
logger: Logger;
|
|
29
31
|
onClose: () => void;
|
|
30
32
|
}
|
|
@@ -56,7 +58,8 @@ export declare class RoolChannel extends EventEmitter<ChannelEvents> {
|
|
|
56
58
|
private _conversationId;
|
|
57
59
|
private _closed;
|
|
58
60
|
private graphqlClient;
|
|
59
|
-
private
|
|
61
|
+
private restClient;
|
|
62
|
+
private webdav;
|
|
60
63
|
private onCloseCallback;
|
|
61
64
|
private logger;
|
|
62
65
|
private _meta;
|
|
@@ -370,29 +373,6 @@ export declare class RoolChannel extends EventEmitter<ChannelEvents> {
|
|
|
370
373
|
* Rename this channel.
|
|
371
374
|
*/
|
|
372
375
|
rename(newName: string): Promise<void>;
|
|
373
|
-
/**
|
|
374
|
-
* List all media files for this space.
|
|
375
|
-
*/
|
|
376
|
-
listMedia(): Promise<MediaInfo[]>;
|
|
377
|
-
/**
|
|
378
|
-
* Upload a file to this space. Returns the URL.
|
|
379
|
-
*/
|
|
380
|
-
uploadMedia(file: File | Blob | {
|
|
381
|
-
data: string;
|
|
382
|
-
contentType: string;
|
|
383
|
-
}): Promise<string>;
|
|
384
|
-
/**
|
|
385
|
-
* Fetch any URL, returning headers and a blob() method (like fetch Response).
|
|
386
|
-
* Adds auth headers for backend media URLs, fetches external URLs via server proxy if CORS blocks.
|
|
387
|
-
* Pass `{ forceProxy: true }` to skip the direct fetch and go straight through the server proxy.
|
|
388
|
-
*/
|
|
389
|
-
fetchMedia(url: string, options?: {
|
|
390
|
-
forceProxy?: boolean;
|
|
391
|
-
}): Promise<MediaResponse>;
|
|
392
|
-
/**
|
|
393
|
-
* Delete a media file by URL.
|
|
394
|
-
*/
|
|
395
|
-
deleteMedia(url: string): Promise<void>;
|
|
396
376
|
/**
|
|
397
377
|
* Fetch an external URL via the server proxy, bypassing CORS restrictions.
|
|
398
378
|
* Requires editor role or above. Blocked for private/internal IP ranges (SSRF protection).
|
|
@@ -406,6 +386,8 @@ export declare class RoolChannel extends EventEmitter<ChannelEvents> {
|
|
|
406
386
|
headers?: Record<string, string>;
|
|
407
387
|
body?: unknown;
|
|
408
388
|
}): Promise<Response>;
|
|
389
|
+
private uploadAttachment;
|
|
390
|
+
private ensureCollection;
|
|
409
391
|
/**
|
|
410
392
|
* Register a collector that resolves when the object arrives via SSE.
|
|
411
393
|
* If the object is already in the buffer (arrived before collector), resolves immediately.
|
package/dist/channel.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"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"}
|
package/dist/channel.js
CHANGED
|
@@ -40,6 +40,80 @@ function findDefaultLeaf(interactions) {
|
|
|
40
40
|
}
|
|
41
41
|
return best?.id;
|
|
42
42
|
}
|
|
43
|
+
function attachmentBody(file, index) {
|
|
44
|
+
if (isFile(file)) {
|
|
45
|
+
return {
|
|
46
|
+
filename: safeAttachmentFilename(file.name, index, file.type),
|
|
47
|
+
contentType: file.type || 'application/octet-stream',
|
|
48
|
+
body: file,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
if (isBlob(file)) {
|
|
52
|
+
const contentType = file.type || 'application/octet-stream';
|
|
53
|
+
return {
|
|
54
|
+
filename: safeAttachmentFilename(`attachment-${index + 1}`, index, contentType),
|
|
55
|
+
contentType,
|
|
56
|
+
body: file,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
filename: safeAttachmentFilename(`attachment-${index + 1}`, index, file.contentType),
|
|
61
|
+
contentType: file.contentType,
|
|
62
|
+
body: base64Body(file.data),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function isFile(value) {
|
|
66
|
+
return typeof File !== 'undefined' && value instanceof File;
|
|
67
|
+
}
|
|
68
|
+
function isBlob(value) {
|
|
69
|
+
return typeof Blob !== 'undefined' && value instanceof Blob;
|
|
70
|
+
}
|
|
71
|
+
function safeAttachmentFilename(name, index, contentType) {
|
|
72
|
+
const fallback = `attachment.${extensionForContentType(contentType)}`;
|
|
73
|
+
const leaf = name.split(/[/\\]/).pop() || fallback;
|
|
74
|
+
const cleaned = leaf.replace(/[\x00-\x1f\x7f]/g, '').replace(/\s+/g, '_');
|
|
75
|
+
const safe = cleaned.replace(/[^A-Za-z0-9._-]/g, '_').replace(/^\.+$/, '') || fallback;
|
|
76
|
+
return `${index + 1}-${safe}`;
|
|
77
|
+
}
|
|
78
|
+
function extensionForContentType(contentType) {
|
|
79
|
+
if (contentType === 'image/png')
|
|
80
|
+
return 'png';
|
|
81
|
+
if (contentType === 'image/jpeg')
|
|
82
|
+
return 'jpg';
|
|
83
|
+
if (contentType === 'image/gif')
|
|
84
|
+
return 'gif';
|
|
85
|
+
if (contentType === 'image/webp')
|
|
86
|
+
return 'webp';
|
|
87
|
+
if (contentType === 'image/svg+xml')
|
|
88
|
+
return 'svg';
|
|
89
|
+
if (contentType === 'application/pdf')
|
|
90
|
+
return 'pdf';
|
|
91
|
+
if (contentType === 'text/markdown')
|
|
92
|
+
return 'md';
|
|
93
|
+
if (contentType === 'text/plain')
|
|
94
|
+
return 'txt';
|
|
95
|
+
if (contentType === 'text/csv')
|
|
96
|
+
return 'csv';
|
|
97
|
+
if (contentType === 'text/html')
|
|
98
|
+
return 'html';
|
|
99
|
+
if (contentType === 'application/json')
|
|
100
|
+
return 'json';
|
|
101
|
+
if (contentType === 'application/xml')
|
|
102
|
+
return 'xml';
|
|
103
|
+
return 'bin';
|
|
104
|
+
}
|
|
105
|
+
function base64Body(data) {
|
|
106
|
+
const clean = data.includes(',') ? data.slice(data.indexOf(',') + 1) : data;
|
|
107
|
+
if (typeof Buffer !== 'undefined') {
|
|
108
|
+
const buffer = Buffer.from(clean, 'base64');
|
|
109
|
+
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
110
|
+
}
|
|
111
|
+
const binary = atob(clean);
|
|
112
|
+
const bytes = new Uint8Array(binary.length);
|
|
113
|
+
for (let i = 0; i < binary.length; i++)
|
|
114
|
+
bytes[i] = binary.charCodeAt(i);
|
|
115
|
+
return bytes.buffer;
|
|
116
|
+
}
|
|
43
117
|
// Default timeout for waiting on SSE object events (30 seconds)
|
|
44
118
|
const OBJECT_COLLECT_TIMEOUT = 30000;
|
|
45
119
|
/**
|
|
@@ -70,7 +144,8 @@ export class RoolChannel extends EventEmitter {
|
|
|
70
144
|
_conversationId;
|
|
71
145
|
_closed = false;
|
|
72
146
|
graphqlClient;
|
|
73
|
-
|
|
147
|
+
restClient;
|
|
148
|
+
webdav;
|
|
74
149
|
onCloseCallback;
|
|
75
150
|
logger;
|
|
76
151
|
// Local cache for bounded data (schema, metadata, own channel, object IDs, stats)
|
|
@@ -99,7 +174,8 @@ export class RoolChannel extends EventEmitter {
|
|
|
99
174
|
this._channelId = config.channelId;
|
|
100
175
|
this._conversationId = 'default';
|
|
101
176
|
this.graphqlClient = config.graphqlClient;
|
|
102
|
-
this.
|
|
177
|
+
this.restClient = config.restClient;
|
|
178
|
+
this.webdav = config.webdav;
|
|
103
179
|
this.logger = config.logger;
|
|
104
180
|
this.onCloseCallback = config.onClose;
|
|
105
181
|
// Initialize local cache from server data
|
|
@@ -425,22 +501,24 @@ export class RoolChannel extends EventEmitter {
|
|
|
425
501
|
/** @internal */
|
|
426
502
|
async _createObjectImpl(options, conversationId) {
|
|
427
503
|
const { data, ephemeral } = options;
|
|
428
|
-
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
504
|
+
const basename = typeof data.id === 'string' ? data.id : generateEntityId();
|
|
505
|
+
const type = data.type;
|
|
506
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(basename)) {
|
|
507
|
+
throw new Error(`Invalid object ID "${basename}". IDs must contain only alphanumeric characters, hyphens, and underscores.`);
|
|
508
|
+
}
|
|
509
|
+
if (typeof type !== 'string' || !type) {
|
|
510
|
+
throw new Error('createObject: data.type is required');
|
|
433
511
|
}
|
|
434
|
-
|
|
435
|
-
//
|
|
436
|
-
|
|
437
|
-
|
|
512
|
+
// Server's canonical id is "<type>/<basename>" — predict it locally so the
|
|
513
|
+
// optimistic event id matches the SSE echo, no dedup workaround needed.
|
|
514
|
+
const objectId = `${type}/${basename}`;
|
|
515
|
+
const dataForWire = { ...data, id: basename }; // server expects basename in data.id
|
|
516
|
+
const optimisticObject = { ...data, id: objectId }; // SDK tracks path-form
|
|
517
|
+
this._pendingMutations.set(objectId, optimisticObject);
|
|
518
|
+
this.emit('objectCreated', { objectId, object: optimisticObject, source: 'local_user' });
|
|
438
519
|
try {
|
|
439
|
-
// Await mutation — server processes AI placeholders before responding.
|
|
440
|
-
// SSE events arrive during the await and are buffered via _deliverObject.
|
|
441
520
|
const interactionId = generateEntityId();
|
|
442
|
-
const { message } = await this.graphqlClient.createObject(this.id,
|
|
443
|
-
// Collect resolved object from buffer (or wait if not yet arrived)
|
|
521
|
+
const { message } = await this.graphqlClient.createObject(this.id, dataForWire, this._channelId, conversationId, interactionId, ephemeral);
|
|
444
522
|
const object = await this._collectObject(objectId);
|
|
445
523
|
return { object, message };
|
|
446
524
|
}
|
|
@@ -448,7 +526,6 @@ export class RoolChannel extends EventEmitter {
|
|
|
448
526
|
this.logger.error('[RoolChannel] Failed to create object:', error);
|
|
449
527
|
this._pendingMutations.delete(objectId);
|
|
450
528
|
this._cancelCollector(objectId);
|
|
451
|
-
// Emit reset so UI can recover from the optimistic event
|
|
452
529
|
this.emit('syncError', error instanceof Error ? error : new Error(String(error)));
|
|
453
530
|
this.emit('reset', { source: 'system' });
|
|
454
531
|
throw error;
|
|
@@ -769,17 +846,17 @@ export class RoolChannel extends EventEmitter {
|
|
|
769
846
|
}
|
|
770
847
|
/** @internal */
|
|
771
848
|
async _promptImpl(prompt, options, conversationId) {
|
|
772
|
-
//
|
|
849
|
+
// Attachments become rool-drive:/ file references stored on the interaction.
|
|
773
850
|
const { attachments, parentInteractionId: explicitParent, signal, ...rest } = options ?? {};
|
|
851
|
+
const interactionId = generateEntityId();
|
|
774
852
|
let attachmentUrls;
|
|
775
853
|
if (attachments?.length) {
|
|
776
|
-
attachmentUrls = await Promise.all(attachments.map(file => this.
|
|
854
|
+
attachmentUrls = await Promise.all(attachments.map((file, index) => this.uploadAttachment(file, interactionId, index)));
|
|
777
855
|
}
|
|
778
856
|
// Auto-continue from active leaf if no explicit parent provided
|
|
779
857
|
const parentInteractionId = explicitParent !== undefined
|
|
780
858
|
? explicitParent
|
|
781
859
|
: (this._getActiveLeafImpl(conversationId) ?? null);
|
|
782
|
-
const interactionId = generateEntityId();
|
|
783
860
|
// Optimistically set active leaf before the server call.
|
|
784
861
|
this._activeLeaves.set(conversationId, interactionId);
|
|
785
862
|
let onAbort;
|
|
@@ -854,32 +931,6 @@ export class RoolChannel extends EventEmitter {
|
|
|
854
931
|
throw error;
|
|
855
932
|
}
|
|
856
933
|
}
|
|
857
|
-
/**
|
|
858
|
-
* List all media files for this space.
|
|
859
|
-
*/
|
|
860
|
-
async listMedia() {
|
|
861
|
-
return this.mediaClient.list(this._id);
|
|
862
|
-
}
|
|
863
|
-
/**
|
|
864
|
-
* Upload a file to this space. Returns the URL.
|
|
865
|
-
*/
|
|
866
|
-
async uploadMedia(file) {
|
|
867
|
-
return this.mediaClient.upload(this._id, file);
|
|
868
|
-
}
|
|
869
|
-
/**
|
|
870
|
-
* Fetch any URL, returning headers and a blob() method (like fetch Response).
|
|
871
|
-
* Adds auth headers for backend media URLs, fetches external URLs via server proxy if CORS blocks.
|
|
872
|
-
* Pass `{ forceProxy: true }` to skip the direct fetch and go straight through the server proxy.
|
|
873
|
-
*/
|
|
874
|
-
async fetchMedia(url, options) {
|
|
875
|
-
return this.mediaClient.fetch(this._id, url, options);
|
|
876
|
-
}
|
|
877
|
-
/**
|
|
878
|
-
* Delete a media file by URL.
|
|
879
|
-
*/
|
|
880
|
-
async deleteMedia(url) {
|
|
881
|
-
return this.mediaClient.delete(this._id, url);
|
|
882
|
-
}
|
|
883
934
|
/**
|
|
884
935
|
* Fetch an external URL via the server proxy, bypassing CORS restrictions.
|
|
885
936
|
* Requires editor role or above. Blocked for private/internal IP ranges (SSRF protection).
|
|
@@ -889,7 +940,22 @@ export class RoolChannel extends EventEmitter {
|
|
|
889
940
|
* @returns The proxied Response
|
|
890
941
|
*/
|
|
891
942
|
async fetch(url, init) {
|
|
892
|
-
return this.
|
|
943
|
+
return this.restClient.proxyFetch(this._id, url, init);
|
|
944
|
+
}
|
|
945
|
+
async uploadAttachment(file, interactionId, index) {
|
|
946
|
+
await this.ensureCollection('attachments');
|
|
947
|
+
const directory = `attachments/${interactionId}`;
|
|
948
|
+
await this.ensureCollection(directory);
|
|
949
|
+
const attachment = attachmentBody(file, index);
|
|
950
|
+
const path = `${directory}/${attachment.filename}`;
|
|
951
|
+
await this.webdav.put(path, attachment.body, { contentType: attachment.contentType });
|
|
952
|
+
return this.webdav.ref(path);
|
|
953
|
+
}
|
|
954
|
+
async ensureCollection(path) {
|
|
955
|
+
const response = await this.webdav.request('MKCOL', path, { collection: true });
|
|
956
|
+
if (response.status === 201 || response.status === 405)
|
|
957
|
+
return;
|
|
958
|
+
throw new Error(`Failed to create collection ${path}: ${response.status} ${await response.text()}`);
|
|
893
959
|
}
|
|
894
960
|
/**
|
|
895
961
|
* Register a collector that resolves when the object arrives via SSE.
|