hci-atrium 0.1.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.
Files changed (163) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +182 -0
  3. package/dist/atrium.d.ts +522 -0
  4. package/dist/atrium.d.ts.map +1 -0
  5. package/dist/atrium.js +806 -0
  6. package/dist/atrium.js.map +1 -0
  7. package/dist/auth.d.ts +85 -0
  8. package/dist/auth.d.ts.map +1 -0
  9. package/dist/auth.js +369 -0
  10. package/dist/auth.js.map +1 -0
  11. package/dist/by.d.ts +84 -0
  12. package/dist/by.d.ts.map +1 -0
  13. package/dist/by.js +87 -0
  14. package/dist/by.js.map +1 -0
  15. package/dist/decider.d.ts +40 -0
  16. package/dist/decider.d.ts.map +1 -0
  17. package/dist/decider.js +76 -0
  18. package/dist/decider.js.map +1 -0
  19. package/dist/discovery.d.ts +15 -0
  20. package/dist/discovery.d.ts.map +1 -0
  21. package/dist/discovery.js +38 -0
  22. package/dist/discovery.js.map +1 -0
  23. package/dist/env.d.ts +11 -0
  24. package/dist/env.d.ts.map +1 -0
  25. package/dist/env.js +28 -0
  26. package/dist/env.js.map +1 -0
  27. package/dist/errors.d.ts +67 -0
  28. package/dist/errors.d.ts.map +1 -0
  29. package/dist/errors.js +86 -0
  30. package/dist/errors.js.map +1 -0
  31. package/dist/files.d.ts +7 -0
  32. package/dist/files.d.ts.map +1 -0
  33. package/dist/files.js +2 -0
  34. package/dist/files.js.map +1 -0
  35. package/dist/http.d.ts +11 -0
  36. package/dist/http.d.ts.map +1 -0
  37. package/dist/http.js +21 -0
  38. package/dist/http.js.map +1 -0
  39. package/dist/index.d.ts +17 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +14 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/models.d.ts +456 -0
  44. package/dist/models.d.ts.map +1 -0
  45. package/dist/models.js +487 -0
  46. package/dist/models.js.map +1 -0
  47. package/dist/pkce.d.ts +16 -0
  48. package/dist/pkce.d.ts.map +1 -0
  49. package/dist/pkce.js +40 -0
  50. package/dist/pkce.js.map +1 -0
  51. package/dist/react/index.d.ts +37 -0
  52. package/dist/react/index.d.ts.map +1 -0
  53. package/dist/react/index.js +89 -0
  54. package/dist/react/index.js.map +1 -0
  55. package/dist/rooms/armed-set.d.ts +33 -0
  56. package/dist/rooms/armed-set.d.ts.map +1 -0
  57. package/dist/rooms/armed-set.js +65 -0
  58. package/dist/rooms/armed-set.js.map +1 -0
  59. package/dist/rooms/brokers.d.ts +62 -0
  60. package/dist/rooms/brokers.d.ts.map +1 -0
  61. package/dist/rooms/brokers.js +109 -0
  62. package/dist/rooms/brokers.js.map +1 -0
  63. package/dist/rooms/codec.d.ts +100 -0
  64. package/dist/rooms/codec.d.ts.map +1 -0
  65. package/dist/rooms/codec.js +101 -0
  66. package/dist/rooms/codec.js.map +1 -0
  67. package/dist/rooms/index.d.ts +40 -0
  68. package/dist/rooms/index.d.ts.map +1 -0
  69. package/dist/rooms/index.js +45 -0
  70. package/dist/rooms/index.js.map +1 -0
  71. package/dist/rooms/offer-capture.d.ts +96 -0
  72. package/dist/rooms/offer-capture.d.ts.map +1 -0
  73. package/dist/rooms/offer-capture.js +338 -0
  74. package/dist/rooms/offer-capture.js.map +1 -0
  75. package/dist/rooms/offer-transcription.d.ts +63 -0
  76. package/dist/rooms/offer-transcription.d.ts.map +1 -0
  77. package/dist/rooms/offer-transcription.js +231 -0
  78. package/dist/rooms/offer-transcription.js.map +1 -0
  79. package/dist/rooms/participant.d.ts +35 -0
  80. package/dist/rooms/participant.d.ts.map +1 -0
  81. package/dist/rooms/participant.js +53 -0
  82. package/dist/rooms/participant.js.map +1 -0
  83. package/dist/rooms/recorder.d.ts +74 -0
  84. package/dist/rooms/recorder.d.ts.map +1 -0
  85. package/dist/rooms/recorder.js +157 -0
  86. package/dist/rooms/recorder.js.map +1 -0
  87. package/dist/rooms/room.d.ts +542 -0
  88. package/dist/rooms/room.d.ts.map +1 -0
  89. package/dist/rooms/room.js +1357 -0
  90. package/dist/rooms/room.js.map +1 -0
  91. package/dist/rooms/snippet.d.ts +48 -0
  92. package/dist/rooms/snippet.d.ts.map +1 -0
  93. package/dist/rooms/snippet.js +70 -0
  94. package/dist/rooms/snippet.js.map +1 -0
  95. package/dist/rooms/sse.d.ts +102 -0
  96. package/dist/rooms/sse.d.ts.map +1 -0
  97. package/dist/rooms/sse.js +347 -0
  98. package/dist/rooms/sse.js.map +1 -0
  99. package/dist/rooms/standing-source.d.ts +190 -0
  100. package/dist/rooms/standing-source.d.ts.map +1 -0
  101. package/dist/rooms/standing-source.js +431 -0
  102. package/dist/rooms/standing-source.js.map +1 -0
  103. package/dist/rooms/transcriber.d.ts +81 -0
  104. package/dist/rooms/transcriber.d.ts.map +1 -0
  105. package/dist/rooms/transcriber.js +111 -0
  106. package/dist/rooms/transcriber.js.map +1 -0
  107. package/dist/rooms/transport-shared.d.ts +28 -0
  108. package/dist/rooms/transport-shared.d.ts.map +1 -0
  109. package/dist/rooms/transport-shared.js +42 -0
  110. package/dist/rooms/transport-shared.js.map +1 -0
  111. package/dist/rooms/transport.d.ts +161 -0
  112. package/dist/rooms/transport.d.ts.map +1 -0
  113. package/dist/rooms/transport.js +258 -0
  114. package/dist/rooms/transport.js.map +1 -0
  115. package/dist/shaping/assets.d.ts +42 -0
  116. package/dist/shaping/assets.d.ts.map +1 -0
  117. package/dist/shaping/assets.js +77 -0
  118. package/dist/shaping/assets.js.map +1 -0
  119. package/dist/shaping/chat.d.ts +39 -0
  120. package/dist/shaping/chat.d.ts.map +1 -0
  121. package/dist/shaping/chat.js +62 -0
  122. package/dist/shaping/chat.js.map +1 -0
  123. package/dist/shaping/index.d.ts +14 -0
  124. package/dist/shaping/index.d.ts.map +1 -0
  125. package/dist/shaping/index.js +13 -0
  126. package/dist/shaping/index.js.map +1 -0
  127. package/dist/shaping/library.d.ts +84 -0
  128. package/dist/shaping/library.d.ts.map +1 -0
  129. package/dist/shaping/library.js +288 -0
  130. package/dist/shaping/library.js.map +1 -0
  131. package/dist/shaping/request.d.ts +47 -0
  132. package/dist/shaping/request.d.ts.map +1 -0
  133. package/dist/shaping/request.js +25 -0
  134. package/dist/shaping/request.js.map +1 -0
  135. package/dist/shaping/rooms.d.ts +128 -0
  136. package/dist/shaping/rooms.d.ts.map +1 -0
  137. package/dist/shaping/rooms.js +330 -0
  138. package/dist/shaping/rooms.js.map +1 -0
  139. package/dist/shaping/sources.d.ts +29 -0
  140. package/dist/shaping/sources.d.ts.map +1 -0
  141. package/dist/shaping/sources.js +51 -0
  142. package/dist/shaping/sources.js.map +1 -0
  143. package/dist/shaping/structured.d.ts +47 -0
  144. package/dist/shaping/structured.d.ts.map +1 -0
  145. package/dist/shaping/structured.js +115 -0
  146. package/dist/shaping/structured.js.map +1 -0
  147. package/dist/storage.d.ts +36 -0
  148. package/dist/storage.d.ts.map +1 -0
  149. package/dist/storage.js +86 -0
  150. package/dist/storage.js.map +1 -0
  151. package/dist/transcribe-stream.d.ts +81 -0
  152. package/dist/transcribe-stream.d.ts.map +1 -0
  153. package/dist/transcribe-stream.js +253 -0
  154. package/dist/transcribe-stream.js.map +1 -0
  155. package/dist/transcribe.d.ts +57 -0
  156. package/dist/transcribe.d.ts.map +1 -0
  157. package/dist/transcribe.js +267 -0
  158. package/dist/transcribe.js.map +1 -0
  159. package/dist/types.d.ts +20 -0
  160. package/dist/types.d.ts.map +1 -0
  161. package/dist/types.js +3 -0
  162. package/dist/types.js.map +1 -0
  163. package/package.json +56 -0
package/dist/atrium.js ADDED
@@ -0,0 +1,806 @@
1
+ /** The ``Atrium`` client — the entry point that owns auth, an authenticated transport, and the
2
+ * phase-1 namespaces (``library`` / ``assets`` / ``sources`` / ``sessions`` / ``rooms``) plus the
3
+ * ``chat`` and ``embed`` methods. */
4
+ import { Auth } from "./auth.js";
5
+ import { seedAssetId, toOperand } from "./by.js";
6
+ import { DEFAULT_BASE_URL, readEnv } from "./env.js";
7
+ import { AuthError, InvalidArgumentError, raiseForStatus } from "./errors.js";
8
+ import { httpFetch } from "./http.js";
9
+ import { parseAsset, parseAssetPage, parseAssetVector, parseChatReply, parseCompareResult, parseEmbedding, parseIndexEntry, parseRoom, parseSearchByExamplesHit, parseSearchHit, parseSegment, parseSession, parseSessionEvent, parseSessionEventRow, parseSessionInfo, parseSourceInfo, parseSourceSearchResult, parseSpace, parseTagDefinition, parseTranscript, } from "./models.js";
10
+ import { Room } from "./rooms/index.js";
11
+ import { buildAssetGet, buildAssetIndex, buildAssetList, buildAssetSegments, buildAssetVectors, buildBulkVectors, buildChat, buildCompare, buildEmbed, buildEnrich, buildRoomOpen, buildSearch, buildSearchByExamples, buildSessionEvent, buildSessionEvents, buildSessionExport, buildSessionExportBundle, buildSessionStart, buildSessionsList, buildSourceImport, buildSourcesSearch, buildTagGet, buildTagsList, buildTranscribe, buildUpload, chatBody, distinctExportIds, isZodSchema, JSON_OBJECT, repairMessages, resolveImportTarget, responseFormatParam, structuredError, tryParse, } from "./shaping/index.js";
12
+ import { buildRoomResolve } from "./shaping/rooms.js";
13
+ import { PkceStore, TokenStore } from "./storage.js";
14
+ import { transcribeEngine } from "./transcribe.js";
15
+ export { DEFAULT_BASE_URL };
16
+ // Emitted at most once per page load: an API key in a browser is readable by anyone on the page.
17
+ let browserKeyWarned = false;
18
+ /**
19
+ * Combine two abort signals into one that fires when either does. Returns the linked signal plus a
20
+ * ``dispose`` the caller runs once the request settles — so the listeners on a long-lived signal
21
+ * (the client lifecycle) don't accumulate across every request. Uses ``AbortSignal.any`` where
22
+ * available (no listeners to clean up); the fallback removes both listeners on ``dispose``.
23
+ */
24
+ function linkSignals(a, b) {
25
+ const noop = () => { };
26
+ if (!a)
27
+ return { signal: b, dispose: noop };
28
+ if (!b)
29
+ return { signal: a, dispose: noop };
30
+ const anyOf = AbortSignal.any;
31
+ if (anyOf)
32
+ return { signal: anyOf([a, b]), dispose: noop };
33
+ const ctrl = new AbortController();
34
+ if (a.aborted || b.aborted) {
35
+ ctrl.abort();
36
+ return { signal: ctrl.signal, dispose: noop };
37
+ }
38
+ const onAbort = () => ctrl.abort();
39
+ a.addEventListener("abort", onAbort);
40
+ b.addEventListener("abort", onAbort);
41
+ return {
42
+ signal: ctrl.signal,
43
+ dispose: () => {
44
+ a.removeEventListener("abort", onAbort);
45
+ b.removeEventListener("abort", onAbort);
46
+ },
47
+ };
48
+ }
49
+ /** Resolve a {@link FileSource} to a Blob (fetching a URL string) for a multipart upload. */
50
+ async function toBlob(source) {
51
+ if (typeof source === "string") {
52
+ const res = await httpFetch(source);
53
+ const blob = await res.blob();
54
+ return { blob, filename: source.split("/").pop() || "upload" };
55
+ }
56
+ const filename = source.name ?? "upload";
57
+ return { blob: source, filename };
58
+ }
59
+ function bytesToBase64(bytes) {
60
+ const g = globalThis;
61
+ if (g.Buffer) {
62
+ return g.Buffer.from(bytes).toString("base64");
63
+ }
64
+ let binary = "";
65
+ for (const b of bytes) {
66
+ binary += String.fromCharCode(b);
67
+ }
68
+ return btoa(binary);
69
+ }
70
+ /**
71
+ * A client bound to one Atrium instance and one credential (an OAuth ``clientId`` or an ``apiKey``).
72
+ *
73
+ * Construction is import-safe and side-effect-light. Namespaces (``library`` / ``assets`` /
74
+ * ``sources`` / ``sessions`` / ``rooms``) and the ``chat`` / ``embed`` methods each shape a request,
75
+ * send it over the authenticated transport, and parse the response. ``close()`` (or
76
+ * ``await using``) aborts any in-flight calls.
77
+ */
78
+ export class Atrium {
79
+ /** The auth module: ``signIn``, ``signOut``, ``user``, ``onChange`` (OAuth path). */
80
+ auth;
81
+ baseUrl;
82
+ clientId;
83
+ library;
84
+ assets;
85
+ sources;
86
+ sessions;
87
+ rooms;
88
+ apiKey;
89
+ defaultProject;
90
+ defaultCollection;
91
+ lifecycle = new AbortController();
92
+ spacesPromise = null;
93
+ constructor(opts = {}) {
94
+ const baseUrl = (opts.baseUrl ?? readEnv("ATRIUM_BASE_URL") ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
95
+ this.baseUrl = baseUrl;
96
+ this.clientId = opts.clientId ?? readEnv("ATRIUM_CLIENT_ID");
97
+ this.apiKey = opts.apiKey ?? readEnv("ATRIUM_API_KEY");
98
+ this.defaultProject = opts.project;
99
+ this.defaultCollection = opts.collection;
100
+ if (this.apiKey && typeof window !== "undefined" && !browserKeyWarned) {
101
+ browserKeyWarned = true;
102
+ console.info("Atrium: this page carries an API key — fine for local research setups; " +
103
+ "use Sign in with Atrium for anything hosted.");
104
+ }
105
+ const namespaceId = this.clientId ?? "";
106
+ this.auth = new Auth(baseUrl, this.clientId, new TokenStore(baseUrl, namespaceId), new PkceStore(baseUrl, namespaceId));
107
+ this.library = new Library(this);
108
+ this.assets = new Assets(this);
109
+ this.sources = new Sources(this);
110
+ this.sessions = new Sessions(this);
111
+ this.rooms = new Rooms(this);
112
+ }
113
+ /**
114
+ * ``fetch`` against this instance with the signed-in user's bearer token attached, silent refresh,
115
+ * and a one-shot retry on 401. Relative paths resolve against ``baseUrl``.
116
+ */
117
+ fetch(path, init) {
118
+ return this.auth.apiFetch(path, init);
119
+ }
120
+ /** Resolves once initial auth (callback handling / session resume) has settled. */
121
+ ready() {
122
+ return this.auth.ready();
123
+ }
124
+ /** Abort any in-flight requests. Idempotent; called automatically by ``await using``. */
125
+ async close() {
126
+ this.lifecycle.abort();
127
+ }
128
+ async chat(messages, opts = {}) {
129
+ const { model, responseFormat, strictParams, signal, ...params } = opts;
130
+ if (isZodSchema(responseFormat)) {
131
+ return this.chatParse(messages, model, responseFormat, params, signal, strictParams);
132
+ }
133
+ const p = { ...params };
134
+ if (responseFormat !== undefined) {
135
+ p.response_format = responseFormat;
136
+ }
137
+ if (strictParams) {
138
+ p.strict_params = true;
139
+ }
140
+ const data = await this.request(buildChat(chatBody(messages, model, p)), signal);
141
+ return parseChatReply(data);
142
+ }
143
+ async chatParse(messages, model, schema, params, signal, strictParams) {
144
+ const msgs = typeof messages === "string" ? [{ role: "user", content: messages }] : [...messages];
145
+ const rf = await responseFormatParam(schema);
146
+ const jsonSchema = rf.json_schema.schema;
147
+ const first = (await this.chat(msgs, {
148
+ model,
149
+ responseFormat: rf,
150
+ strictParams,
151
+ signal,
152
+ ...params,
153
+ }));
154
+ let [value, detail] = tryParse(schema, first.content);
155
+ if (value !== null) {
156
+ return value;
157
+ }
158
+ const repaired = repairMessages(msgs, first.content, detail, jsonSchema);
159
+ const second = (await this.chat(repaired, {
160
+ model,
161
+ responseFormat: JSON_OBJECT,
162
+ strictParams,
163
+ signal,
164
+ ...params,
165
+ }));
166
+ [value, detail] = tryParse(schema, second.content);
167
+ if (value !== null) {
168
+ return value;
169
+ }
170
+ throw structuredError(schema.constructor.name, second.content, detail);
171
+ }
172
+ /**
173
+ * Embed text or a media file into a vector (``POST /v1/embeddings``). Pass exactly one of
174
+ * ``text`` / ``image`` / ``audio`` / ``video``. ``model`` is required — an embedding's model is
175
+ * part of your data contract.
176
+ */
177
+ async embed(opts) {
178
+ const { text, model, image, audio, video, format, signal } = opts;
179
+ const medias = [
180
+ ["image", image],
181
+ ["audio", audio],
182
+ ["video", video],
183
+ ].filter(([, src]) => src !== undefined);
184
+ let media;
185
+ if (medias.length === 1) {
186
+ const [kind, src] = medias[0];
187
+ media = await this.encodeMedia(kind, src, format);
188
+ }
189
+ const data = await this.request(buildEmbed(model, text, media), signal);
190
+ return parseEmbedding(data);
191
+ }
192
+ /**
193
+ * Transcribe speech to text (``POST /v1/audio/transcriptions``). ``opts.verbose`` fills
194
+ * ``.language`` / ``.duration`` / ``.segments`` on the result; ``opts.format`` names the media
195
+ * type for nameless bytes.
196
+ */
197
+ async transcribe(audio, opts = {}) {
198
+ const req = buildTranscribe({
199
+ audio,
200
+ model: opts.model,
201
+ language: opts.language,
202
+ prompt: opts.prompt,
203
+ verbose: opts.verbose,
204
+ format: opts.format,
205
+ });
206
+ return parseTranscript(await this.request(req, opts.signal));
207
+ }
208
+ /**
209
+ * Transcribe a live audio stream, yielding a {@link Transcript} per finalized sentence.
210
+ *
211
+ * **True streaming when the backend offers it, with a silent fallback.** The primary path opens the
212
+ * live transcription route (``WS /v1/audio/transcriptions/stream``) and pumps ``chunks`` up while
213
+ * reading transcript events down. If that route can't be reached — an old deploy, a proxy that
214
+ * strips the WebSocket upgrade, a refused connection — it degrades to the batch ``transcribe`` route
215
+ * (each chunk POSTed, finals only). A **rejected credential** (close 4401) is *not* a fallback case
216
+ * — it throws {@link AuthError}, since retrying won't heal it. The fallback is **connect-time only**:
217
+ * once live, a mid-session drop throws rather than silently degrading.
218
+ *
219
+ * By default (``partials`` off) it yields **finals only** — each a settled sentence with
220
+ * ``.final === true`` and ``.raw`` carrying ``start`` / ``end`` timings. ``partials`` additionally
221
+ * yields the live partial hypothesis (``.final === false``), a revisable guess at the current tail —
222
+ * render the latest, don't append.
223
+ *
224
+ * Input comes in two shapes: **container clips** (``format`` = ``"webm"`` / ``"wav"`` / … — each
225
+ * chunk a self-contained clip, the browser ``MediaRecorder`` shape) or **raw PCM**
226
+ * (``format="pcm16"``, a continuous PCM16LE-mono stream, which **requires** ``sampleRate`` because
227
+ * raw PCM is headerless). Passing ``sampleRate`` with a container format is an error.
228
+ */
229
+ async *transcribeLive(chunks, opts = {}) {
230
+ const format = opts.format ?? "wav";
231
+ if (format === "pcm16") {
232
+ if (opts.sampleRate === undefined) {
233
+ throw new InvalidArgumentError("transcribeLive({ format: 'pcm16' }) requires sampleRate: raw PCM is headerless — unlike a " +
234
+ "WAV/WebM clip it carries no embedded sample rate to sniff, so the SDK can't infer the rate " +
235
+ "your mic captured at. Pass it explicitly (e.g. sampleRate: 16000).");
236
+ }
237
+ }
238
+ else if (opts.sampleRate !== undefined) {
239
+ throw new InvalidArgumentError(`sampleRate is only valid with format: 'pcm16' (raw headerless PCM); '${format}' clips are ` +
240
+ "self-describing containers that carry their own rate — drop sampleRate.");
241
+ }
242
+ yield* this.transcribeStream(chunks, {
243
+ model: opts.model,
244
+ language: opts.language,
245
+ format,
246
+ sampleRate: opts.sampleRate,
247
+ partials: opts.partials ?? false,
248
+ skipEmpty: opts.skipEmpty ?? true,
249
+ webSocketCtor: opts.webSocketCtor,
250
+ });
251
+ }
252
+ transcribeStream(clips, opts) {
253
+ return transcribeEngine({
254
+ baseUrl: this.baseUrl,
255
+ token: () => this.streamToken(),
256
+ transcribe: (audio, o) => this.transcribe(audio, o),
257
+ webSocketCtor: opts.webSocketCtor,
258
+ }, clips, opts);
259
+ }
260
+ // A valid credential for the live-transcription WS (its `?api_key=` query — browsers can't set a WS
261
+ // header). An API key is used as-is; otherwise a valid OAuth access token, refreshed if near expiry.
262
+ async streamToken() {
263
+ if (this.apiKey)
264
+ return this.apiKey;
265
+ const token = await this.auth.token();
266
+ if (!token) {
267
+ throw new AuthError(401, "no credential for the transcription stream — sign in or construct with an apiKey");
268
+ }
269
+ return token;
270
+ }
271
+ async encodeMedia(kind, source, format) {
272
+ const { blob } = await toBlob(source);
273
+ const bytes = new Uint8Array(await blob.arrayBuffer());
274
+ const inferred = blob.type ? blob.type.split("/")[1] : undefined;
275
+ return { kind, data: bytesToBase64(bytes), format: format ?? inferred };
276
+ }
277
+ // --- ClientInternal ----------------------------------------------------------------------------
278
+ scope(project, collection) {
279
+ return {
280
+ project: project ?? this.defaultProject,
281
+ collection: collection ?? this.defaultCollection,
282
+ };
283
+ }
284
+ /** Resolve a space name-or-id to its id via the client-cached space list (mirror of Python's
285
+ * ``_resolve_space``): an id passes through; a case-insensitive name maps to its id; an unknown
286
+ * value is returned as-is for the server to report. The list is fetched once and cached. */
287
+ async resolveSpace(nameOrId) {
288
+ const spaces = await this.cachedSpaces();
289
+ if (spaces.some((s) => s.id === nameOrId)) {
290
+ return nameOrId;
291
+ }
292
+ const lower = nameOrId.toLowerCase();
293
+ const named = spaces.find((s) => s.name.toLowerCase() === lower);
294
+ return named ? named.id : nameOrId;
295
+ }
296
+ /** The full space list, fetched once and cached per client (shared with {@link resolveSpace}). */
297
+ cachedSpaces() {
298
+ if (!this.spacesPromise) {
299
+ this.spacesPromise = this.library.spaces();
300
+ }
301
+ return this.spacesPromise;
302
+ }
303
+ /** The client's ``assets`` namespace — the binder threaded into every parsed asset so a bound
304
+ * ``asset.vectors()`` / ``.segments()`` / ``.index()`` resolves through this client. */
305
+ get assetBinder() {
306
+ return this.assets;
307
+ }
308
+ async send(req, signal) {
309
+ const url = this.resolveUrl(req.path, req.query);
310
+ const { init, dispose } = await this.buildInit(req, signal);
311
+ try {
312
+ const res = this.apiKey
313
+ ? await this.keyedFetch(url, init)
314
+ : await this.auth.apiFetch(url, init);
315
+ if (!res.ok) {
316
+ await raiseForStatus(res);
317
+ }
318
+ return res;
319
+ }
320
+ finally {
321
+ dispose();
322
+ }
323
+ }
324
+ async request(req, signal) {
325
+ const res = await this.send(req, signal);
326
+ if (res.status === 204) {
327
+ return undefined;
328
+ }
329
+ return await res.json();
330
+ }
331
+ async requestBytes(req, signal) {
332
+ const res = await this.send(req, signal);
333
+ return new Uint8Array(await res.arrayBuffer());
334
+ }
335
+ keyedFetch(url, init) {
336
+ const headers = new Headers(init.headers);
337
+ headers.set("Authorization", `Bearer ${this.apiKey}`);
338
+ return httpFetch(url, { ...init, headers });
339
+ }
340
+ resolveUrl(path, query) {
341
+ const base = /^https?:\/\//i.test(path)
342
+ ? path
343
+ : `${this.baseUrl}${path.startsWith("/") ? path : `/${path}`}`;
344
+ if (!query) {
345
+ return base;
346
+ }
347
+ const usp = new URLSearchParams();
348
+ for (const [key, value] of Object.entries(query)) {
349
+ if (Array.isArray(value)) {
350
+ for (const item of value) {
351
+ usp.append(key, String(item));
352
+ }
353
+ }
354
+ else {
355
+ usp.append(key, String(value));
356
+ }
357
+ }
358
+ const qs = usp.toString();
359
+ return qs ? `${base}${base.includes("?") ? "&" : "?"}${qs}` : base;
360
+ }
361
+ async buildInit(req, signal) {
362
+ const init = { method: req.method };
363
+ const { signal: linked, dispose } = linkSignals(signal, this.lifecycle.signal);
364
+ if (linked) {
365
+ init.signal = linked;
366
+ }
367
+ if (req.json !== undefined) {
368
+ init.headers = { "Content-Type": "application/json" };
369
+ init.body = JSON.stringify(req.json);
370
+ }
371
+ else if (req.form) {
372
+ // ``fetch`` sets ``application/x-www-form-urlencoded`` for a URLSearchParams body.
373
+ const params = new URLSearchParams();
374
+ for (const [key, value] of Object.entries(req.form)) {
375
+ params.append(key, value);
376
+ }
377
+ init.body = params;
378
+ }
379
+ else if (req.multipart) {
380
+ const form = new FormData();
381
+ for (const [key, value] of Object.entries(req.multipart.fields)) {
382
+ form.append(key, value);
383
+ }
384
+ for (const [field, part] of Object.entries(req.multipart.files)) {
385
+ const { blob, filename } = await toBlob(part.source);
386
+ form.append(field, blob, part.filename ?? filename);
387
+ }
388
+ init.body = form;
389
+ }
390
+ else if (req.content !== undefined) {
391
+ init.headers = { "Content-Type": req.contentType ?? "application/octet-stream" };
392
+ init.body = req.content;
393
+ }
394
+ return { init, dispose };
395
+ }
396
+ }
397
+ if (typeof Symbol.asyncDispose === "symbol") {
398
+ Atrium.prototype[Symbol.asyncDispose] = async function () {
399
+ await this.close();
400
+ };
401
+ }
402
+ // --- namespaces ----------------------------------------------------------------------------------
403
+ function likeSourceId(like) {
404
+ if (typeof like === "string") {
405
+ return like;
406
+ }
407
+ return "asset" in like ? like.asset.id : like.id;
408
+ }
409
+ /** Semantic library search and mood-board (search-by-examples) queries. */
410
+ export class Library {
411
+ c;
412
+ constructor(c) {
413
+ this.c = c;
414
+ }
415
+ /** List the searchable embedding spaces (``GET /v1/library/spaces``). */
416
+ async spaces(opts = {}) {
417
+ const data = await this.c.request({ method: "GET", path: "/v1/library/spaces" }, opts.signal);
418
+ return (Array.isArray(data) ? data : []).map(parseSpace);
419
+ }
420
+ /**
421
+ * Resolve ``modality`` to the deployment's default space (or ``undefined``).
422
+ *
423
+ * So an app never has to hardcode a deployment's space ids — those leak deployment config into
424
+ * caller code. The deployment declares its defaults (``defaultModalities``); the app just asks.
425
+ * Returns the first space whose ``defaultModalities`` include ``modality``, else the first whose
426
+ * ``modalities`` include it, else ``undefined`` — reading the client's cached space list (no
427
+ * per-call refetch). The returned space's ``id`` is what search/scoring take as ``space``.
428
+ */
429
+ async defaultSpace(modality) {
430
+ const spaces = await this.c.cachedSpaces();
431
+ return (spaces.find((s) => s.defaultModalities.includes(modality)) ??
432
+ spaces.find((s) => s.modalities.includes(modality)));
433
+ }
434
+ /** Semantic search — by text, an example asset (``like``), a media file, or a raw ``vector``. */
435
+ async search(query = "", opts = {}) {
436
+ const { project, collection } = this.c.scope(opts.project, opts.collection);
437
+ const media = opts.image ?? opts.audio ?? opts.video;
438
+ const space = opts.space !== undefined ? await this.c.resolveSpace(opts.space) : undefined;
439
+ const includeVectors = typeof opts.includeVectors === "string"
440
+ ? await this.c.resolveSpace(opts.includeVectors)
441
+ : opts.includeVectors;
442
+ const req = buildSearch({
443
+ query,
444
+ like: opts.like !== undefined ? likeSourceId(opts.like) : undefined,
445
+ likeBy: opts.likeBy,
446
+ modality: opts.modality,
447
+ space,
448
+ limit: opts.limit ?? 20,
449
+ tags: opts.tags,
450
+ anyTags: opts.anyTags,
451
+ excludeTags: opts.excludeTags,
452
+ provider: opts.provider,
453
+ filters: opts.filters,
454
+ media,
455
+ vector: opts.vector,
456
+ includeVectors,
457
+ project,
458
+ collection,
459
+ });
460
+ const data = await this.c.request(req, opts.signal);
461
+ return (Array.isArray(data) ? data : []).map((d) => parseSearchHit(d, this.c.baseUrl, this.c.assetBinder));
462
+ }
463
+ /** Find assets similar to a *set* of example assets (a mood board). */
464
+ async searchByExamples(positives, opts = {}) {
465
+ const { project, collection } = this.c.scope(opts.project, opts.collection);
466
+ const spaces = opts.spaces !== undefined
467
+ ? await Promise.all(opts.spaces.map((s) => this.c.resolveSpace(s)))
468
+ : undefined;
469
+ const req = buildSearchByExamples({
470
+ positives: positives.map(seedAssetId),
471
+ negatives: opts.negatives?.map(seedAssetId),
472
+ method: opts.method ?? "cav",
473
+ spaces,
474
+ limit: opts.limit ?? 20,
475
+ contrastSamples: opts.contrastSamples,
476
+ features: opts.features,
477
+ matchBy: opts.matchBy,
478
+ filters: opts.filters,
479
+ project,
480
+ collection,
481
+ });
482
+ const data = await this.c.request(req, opts.signal);
483
+ return (Array.isArray(data) ? data : []).map((d) => parseSearchByExamplesHit(d, this.c.baseUrl, this.c.assetBinder));
484
+ }
485
+ /**
486
+ * Compare two operands and return their similarity (raw + calibrated when available). Each operand
487
+ * is an ``Asset``, a bare text string, or a ``by.*`` marker — ``by.asset(id)`` for a bare id,
488
+ * ``by.vector([…])`` for a raw vector, ``by.file(…)`` for a file, ``by.concept(name)`` for a tag's
489
+ * concept. ``space`` forces the embedding space (required for a raw vector or cross-modal pair).
490
+ */
491
+ async compare(a, b, opts = {}) {
492
+ const space = opts.space !== undefined ? await this.c.resolveSpace(opts.space) : undefined;
493
+ const req = buildCompare(toOperand(a), toOperand(b), space, opts.breakdown);
494
+ return parseCompareResult(await this.c.request(req, opts.signal));
495
+ }
496
+ /**
497
+ * Pull external candidates (Freesound / Openverse / …) for a query into the library. ``preview``
498
+ * is a dry-run that returns the candidates that *would* be pulled. The response is the raw payload
499
+ * (a deliberately unwrapped long tail — preview steps, or the staged/enqueued result).
500
+ */
501
+ async enrich(query, opts) {
502
+ const req = buildEnrich({
503
+ query,
504
+ modality: opts.modality,
505
+ tags: opts.tags,
506
+ total: opts.total,
507
+ preview: opts.preview,
508
+ wait: opts.wait,
509
+ });
510
+ return (await this.c.request(req, opts.signal));
511
+ }
512
+ /**
513
+ * Fetch stored embedding vectors for many assets at once (``POST /v1/library/vectors``,
514
+ * member-gated). ``ids`` elements are ``Asset`` objects, ``SearchHit`` results, or bare id strings.
515
+ * ``space`` (id or human name, one or a list) selects the space(s); ``matchBy`` picks an exact
516
+ * stored signal. **With ``matchBy`` omitted the signal is resolved per asset**, so one call can
517
+ * return mixed ``matchedBy`` values across assets — read {@link AssetVector.matchedBy}. Ids that
518
+ * don't exist or aren't visible land in the server's ``missing`` list: default silently omits them;
519
+ * ``strict: true`` throws, listing them.
520
+ */
521
+ async vectors(ids, opts) {
522
+ const space = await this.resolveSpaceArg(opts.space);
523
+ const req = buildBulkVectors(ids.map(likeSourceId), { space, matchBy: opts.matchBy });
524
+ const data = (await this.c.request(req, opts.signal));
525
+ const missing = Array.isArray(data.missing) ? data.missing : [];
526
+ if (opts.strict && missing.length) {
527
+ throw new InvalidArgumentError(`library.vectors(strict): ${missing.length} id(s) missing: ${missing.join(", ")}`);
528
+ }
529
+ return (Array.isArray(data.vectors) ? data.vectors : []).map((row) => parseAssetVector(row));
530
+ }
531
+ resolveSpaceArg(space) {
532
+ return typeof space === "string"
533
+ ? this.c.resolveSpace(space)
534
+ : Promise.all(space.map((s) => this.c.resolveSpace(s)));
535
+ }
536
+ /** List the tag definitions in scope (``GET /v1/library/tags``) — read-only. Inherits the
537
+ * client's collection scoping (own + inherited tags, else the root Library's vocabulary). Tag
538
+ * create/update/delete stay platform-only. */
539
+ async tags(opts = {}) {
540
+ const { collection } = this.c.scope(undefined, undefined);
541
+ const data = await this.c.request(buildTagsList({ collection }), opts.signal);
542
+ return (Array.isArray(data) ? data : []).map(parseTagDefinition);
543
+ }
544
+ /** Fetch one tag definition by name or id (``GET /v1/library/tags/{ref}``) — read-only. The name
545
+ * is resolved within the client's collection scope. ``includeSnapshot: true`` also fetches the
546
+ * materialized concept boundary onto ``tag.snapshot``. */
547
+ async tag(nameOrId, opts = {}) {
548
+ const { collection } = this.c.scope(undefined, undefined);
549
+ const req = buildTagGet(nameOrId, { includeSnapshot: opts.includeSnapshot, collection });
550
+ return parseTagDefinition(await this.c.request(req, opts.signal));
551
+ }
552
+ }
553
+ /** Browse and fetch asset metadata. */
554
+ export class Assets {
555
+ c;
556
+ constructor(c) {
557
+ this.c = c;
558
+ }
559
+ /** List assets in scope, newest first (``GET /v1/assets``). */
560
+ async list(opts = {}) {
561
+ const { project, collection } = this.c.scope(opts.project, opts.collection);
562
+ const req = buildAssetList({
563
+ modality: opts.modality,
564
+ contentKind: opts.contentKind,
565
+ limit: opts.limit ?? 48,
566
+ cursor: opts.cursor,
567
+ project,
568
+ collection,
569
+ });
570
+ const data = await this.c.request(req, opts.signal);
571
+ return parseAssetPage(data, this.c.baseUrl, this.c.assetBinder);
572
+ }
573
+ /** Fetch a single asset's metadata (``GET /v1/assets/{id}``). */
574
+ async get(assetId, opts = {}) {
575
+ const { collection } = this.c.scope(undefined, opts.collection);
576
+ const data = await this.c.request(buildAssetGet(assetId, { collection }), opts.signal);
577
+ return parseAsset(data, this.c.baseUrl, this.c.assetBinder);
578
+ }
579
+ /**
580
+ * Fetch one asset's stored embedding vectors (``GET /v1/assets/{id}/vectors``, public by-id).
581
+ * ``assetOrId`` is an ``Asset``, a ``SearchHit``, or a bare id. No ``space``/``matchBy`` returns
582
+ * every stored vector; ``space`` (id or human name, one or a list) and ``matchBy`` (a coarse class
583
+ * like ``native``/``manual``, or an exact value like ``bridge:text``/``seg:<id>``) narrow them.
584
+ * Also available as the bound ``asset.vectors(...)``.
585
+ */
586
+ async vectors(assetOrId, opts = {}) {
587
+ const assetId = likeSourceId(assetOrId);
588
+ let space;
589
+ if (opts.space === undefined) {
590
+ space = undefined;
591
+ }
592
+ else if (typeof opts.space === "string") {
593
+ space = await this.c.resolveSpace(opts.space);
594
+ }
595
+ else {
596
+ space = await Promise.all(opts.space.map((s) => this.c.resolveSpace(s)));
597
+ }
598
+ const req = buildAssetVectors(assetId, { space, matchBy: opts.matchBy });
599
+ const data = await this.c.request(req, opts.signal);
600
+ return (Array.isArray(data) ? data : []).map((row) => parseAssetVector(row, assetId));
601
+ }
602
+ /** Fetch one asset's sound-event segments (``GET /v1/assets/{id}/segments``, public by-id). Each
603
+ * {@link SegmentRef} carries a ``contentUrl`` for just that slice's pre-cut bytes. Also available
604
+ * as the bound ``asset.segments()``. */
605
+ async segments(assetOrId) {
606
+ const assetId = likeSourceId(assetOrId);
607
+ const data = await this.c.request(buildAssetSegments(assetId));
608
+ return (Array.isArray(data) ? data : []).map((row) => parseSegment(row, assetId, this.c.baseUrl));
609
+ }
610
+ /** Fetch one asset's per-signal index map (``GET /v1/assets/{id}/index``, authenticated). Each
611
+ * {@link IndexEntry} is one stored signal's processing state; the server's internal ``via``
612
+ * surfaces as ``matchedBy``. Also available as the bound ``asset.index()``. */
613
+ async index(assetOrId) {
614
+ const data = await this.c.request(buildAssetIndex(likeSourceId(assetOrId)));
615
+ return (Array.isArray(data) ? data : []).map(parseIndexEntry);
616
+ }
617
+ /** Upload a file to the library (``POST /v1/assets``); it indexes in the background. */
618
+ async upload(file, opts = {}) {
619
+ const { collection } = this.c.scope(undefined, opts.collection);
620
+ const req = buildUpload({
621
+ file,
622
+ modality: opts.modality,
623
+ description: opts.description,
624
+ collection,
625
+ });
626
+ const data = await this.c.request(req, opts.signal);
627
+ return parseAsset(data, this.c.baseUrl, this.c.assetBinder);
628
+ }
629
+ }
630
+ /** External sources — search several providers at once, then import what you pick. */
631
+ export class Sources {
632
+ c;
633
+ constructor(c) {
634
+ this.c = c;
635
+ }
636
+ /** List the registered external sources (``GET /v1/library/sources``). */
637
+ async list(opts = {}) {
638
+ const data = await this.c.request({ method: "GET", path: "/v1/library/sources" }, opts.signal);
639
+ return (Array.isArray(data) ? data : []).map(parseSourceInfo);
640
+ }
641
+ /** Search external sources for a query (``POST /v1/library/sources/search``) — read-only. */
642
+ async search(query, opts = {}) {
643
+ const req = buildSourcesSearch({
644
+ query,
645
+ providers: opts.providers,
646
+ k: opts.k ?? 12,
647
+ mode: opts.mode,
648
+ modality: opts.modality,
649
+ });
650
+ const data = await this.c.request(req, opts.signal);
651
+ return parseSourceSearchResult(data);
652
+ }
653
+ /** Import one candidate into the shared library (``POST …/{source}/import``). */
654
+ async import(opts) {
655
+ const [sourceId, externalId] = resolveImportTarget(opts.candidate, opts.sourceId, opts.externalId);
656
+ const req = buildSourceImport({
657
+ sourceId,
658
+ externalId,
659
+ modality: opts.modality,
660
+ description: opts.description,
661
+ });
662
+ const data = await this.c.request(req, opts.signal);
663
+ return parseAsset(data, this.c.baseUrl, this.c.assetBinder);
664
+ }
665
+ }
666
+ /** Start / resume sessions and append to their durable log. */
667
+ export class Sessions {
668
+ c;
669
+ constructor(c) {
670
+ this.c = c;
671
+ }
672
+ /** Start a new session — a standalone durable record (``POST /v1/sessions``). */
673
+ async start(opts = {}) {
674
+ const { project } = this.c.scope(opts.project, undefined);
675
+ const req = buildSessionStart({
676
+ project,
677
+ name: opts.name,
678
+ participantRef: opts.participantRef,
679
+ condition: opts.condition,
680
+ meta: opts.meta,
681
+ });
682
+ return parseSession(await this.c.request(req, opts.signal));
683
+ }
684
+ /** Resume an existing session by id (``GET /v1/sessions/{id}``) — the live {@link Session} handle
685
+ * for continued logging. For a read-only analysis view use {@link get}, which GETs the same route
686
+ * but returns a {@link SessionInfo} read model. */
687
+ async resume(sessionId, opts = {}) {
688
+ const data = await this.c.request({ method: "GET", path: `/v1/sessions/${sessionId}` }, opts.signal);
689
+ return parseSession(data);
690
+ }
691
+ /** List a project's sessions, newest first (``GET /v1/sessions``) — the read model. No ``project``
692
+ * → the client's default project. ``phase`` (a string or list) filters by study phase; the empty
693
+ * string ``""`` matches sessions with no phase set. Each row is a {@link SessionInfo};
694
+ * ``hasTranscript`` is ``null`` in list results (only {@link get} populates it). There is no
695
+ * participant filter server-side — filter the returned rows on ``participantRef`` yourself. */
696
+ async list(opts = {}) {
697
+ const { project } = this.c.scope(opts.project, undefined);
698
+ const req = buildSessionsList({ project, phase: opts.phase });
699
+ const data = await this.c.request(req, opts.signal);
700
+ return (Array.isArray(data) ? data : []).map(parseSessionInfo);
701
+ }
702
+ /** Fetch a session's read model for analysis (``GET /v1/sessions/{id}``) — a {@link SessionInfo}
703
+ * with ``hasTranscript`` populated. This is the **read model**; to keep *writing* to the log use
704
+ * {@link resume}, which GETs the same route but returns the live {@link Session} handle. */
705
+ async get(sessionId, opts = {}) {
706
+ const data = await this.c.request({ method: "GET", path: `/v1/sessions/${sessionId}` }, opts.signal);
707
+ return parseSessionInfo(data);
708
+ }
709
+ /** A session's durable event log as flat rows (``GET /v1/sessions/{id}/events/history``). ``after``
710
+ * returns only events with ``seq`` greater than it (a sequence number, not a timestamp); ``limit``
711
+ * caps the page (1..500, server default 100). Each row is a {@link ReadSessionEvent}. */
712
+ async events(sessionId, opts = {}) {
713
+ const req = buildSessionEvents(sessionId, { after: opts.after, limit: opts.limit });
714
+ const data = await this.c.request(req, opts.signal);
715
+ return (Array.isArray(data) ? data : []).map(parseSessionEventRow);
716
+ }
717
+ /**
718
+ * Export one session's data as raw bytes, dispatching ``format`` to its fixed export route.
719
+ * ``"jsonl"`` / ``"csv"`` are the event log; ``"transcript"`` is the plain-text transcript (404s
720
+ * when the session has none); ``"bundle"`` is a zip of everything. There is no ``?format=`` param
721
+ * server-side — each is a distinct path.
722
+ *
723
+ * Returns a ``Uint8Array``: the SDK is browser + Node, and the core never touches the filesystem
724
+ * (``files.ts``), so there is no ``path=`` file-writing (a Node caller writes the bytes itself).
725
+ */
726
+ async export(sessionId, format = "jsonl", opts = {}) {
727
+ return this.c.requestBytes(buildSessionExport(sessionId, format), opts.signal);
728
+ }
729
+ /** Export several sessions as one zip (``GET /v1/sessions/export/bundle``), returned as raw bytes.
730
+ * ``ids`` is required; the server caps a call at 100 **distinct** ids — more (or an empty list)
731
+ * throws (never silently truncated or chunked). Data access is required on every id. */
732
+ async exportBundle(ids, opts = {}) {
733
+ const req = buildSessionExportBundle(distinctExportIds(ids));
734
+ return this.c.requestBytes(req, opts.signal);
735
+ }
736
+ /** Append a durable event to a session's log (``POST /v1/sessions/{id}/events``). */
737
+ async event(sessionId, type, opts = {}) {
738
+ const req = buildSessionEvent(sessionId, {
739
+ type,
740
+ payload: opts.payload,
741
+ source: opts.source,
742
+ via: opts.via,
743
+ });
744
+ return parseSessionEvent(await this.c.request(req, opts.signal));
745
+ }
746
+ }
747
+ /** Open / fetch / join rooms — each returns a live {@link Room} handle (connect, send, on, request,
748
+ * player controls, audio signals). ``connect()`` (called for you by any wire method) opens the socket. */
749
+ export class Rooms {
750
+ c;
751
+ constructor(c) {
752
+ this.c = c;
753
+ }
754
+ wrap(info, live, participant) {
755
+ return new Room({
756
+ id: info.id,
757
+ connectCode: info.connectCode,
758
+ name: info.name,
759
+ projectId: info.projectId,
760
+ baseUrl: this.c.baseUrl,
761
+ // A participant (join-by-code) can't manage the room, nor embed (no credentials of its own);
762
+ // omit the REST caller and the embedding client for it.
763
+ rest: participant ? undefined : (req) => this.c.request(req),
764
+ client: participant
765
+ ? undefined
766
+ : {
767
+ embed: (opts) => this.c.embed(opts),
768
+ chat: (messages, o) => this.c.chat(messages, o),
769
+ library: this.c.library,
770
+ request: (req) => this.c.request(req),
771
+ transcribeStream: (clips, o) => this.c.transcribeStream(clips, o),
772
+ },
773
+ identity: live.identity,
774
+ announce: live.announce,
775
+ requestTimeout: live.requestTimeout,
776
+ reconnect: live.reconnect,
777
+ transport: live.transport,
778
+ WebSocketCtor: live.WebSocketCtor,
779
+ fetchImpl: live.fetchImpl,
780
+ signal: live.signal,
781
+ });
782
+ }
783
+ /** Open a room (``POST /v1/rooms``) and return a live handle. */
784
+ async open(opts = {}) {
785
+ const { project } = this.c.scope(opts.project, undefined);
786
+ const req = buildRoomOpen({ name: opts.name, project, embeddingSpace: opts.embeddingSpace });
787
+ return this.wrap(parseRoom(await this.c.request(req, opts.signal)), opts, false);
788
+ }
789
+ /** Fetch a room by id (``GET /v1/rooms/{id}``) and return a live handle. */
790
+ async get(roomId, opts = {}) {
791
+ const data = await this.c.request({ method: "GET", path: `/v1/rooms/${roomId}` }, opts.signal);
792
+ return this.wrap(parseRoom(data), opts, false);
793
+ }
794
+ /** List the rooms you can see (``GET /v1/rooms``) as live handles. */
795
+ async list(opts = {}) {
796
+ const data = await this.c.request({ method: "GET", path: "/v1/rooms" }, opts.signal);
797
+ return (Array.isArray(data) ? data : []).map((d) => this.wrap(parseRoom(d), {}, false));
798
+ }
799
+ /** Join a room by its connect code (``GET /v1/rooms/resolve``) — a participant handle that can
800
+ * ``send`` / ``on`` / ``request`` / play / consume audio, but not manage the room. */
801
+ async join(code, opts = {}) {
802
+ const data = await this.c.request(buildRoomResolve(code), opts.signal);
803
+ return this.wrap(parseRoom(data), opts, true);
804
+ }
805
+ }
806
+ //# sourceMappingURL=atrium.js.map