hci-atrium 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -24
- package/dist/atrium.d.ts +126 -42
- package/dist/atrium.d.ts.map +1 -1
- package/dist/atrium.js +192 -87
- package/dist/atrium.js.map +1 -1
- package/dist/dash.d.ts +3 -3
- package/dist/dash.d.ts.map +1 -1
- package/dist/dash.js +1 -2
- package/dist/dash.js.map +1 -1
- package/dist/env.d.ts +14 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +22 -0
- package/dist/env.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -3
- package/dist/index.js.map +1 -1
- package/dist/models.d.ts +84 -31
- package/dist/models.d.ts.map +1 -1
- package/dist/models.js +97 -25
- package/dist/models.js.map +1 -1
- package/dist/player/play-command.d.ts +6 -6
- package/dist/player/play-command.d.ts.map +1 -1
- package/dist/player/play-intent.d.ts.map +1 -1
- package/dist/player/play-intent.js +2 -1
- package/dist/player/play-intent.js.map +1 -1
- package/dist/player/runtime.d.ts +7 -3
- package/dist/player/runtime.d.ts.map +1 -1
- package/dist/player/runtime.js.map +1 -1
- package/dist/react/use-room-connection.d.ts +4 -1
- package/dist/react/use-room-connection.d.ts.map +1 -1
- package/dist/react/use-room-connection.js +5 -1
- package/dist/react/use-room-connection.js.map +1 -1
- package/dist/rooms/index.d.ts +3 -1
- package/dist/rooms/index.d.ts.map +1 -1
- package/dist/rooms/index.js +4 -3
- package/dist/rooms/index.js.map +1 -1
- package/dist/rooms/offer-transcription.d.ts +1 -1
- package/dist/rooms/offer-transcription.js +1 -1
- package/dist/rooms/owned-room.d.ts +251 -0
- package/dist/rooms/owned-room.d.ts.map +1 -0
- package/dist/rooms/owned-room.js +844 -0
- package/dist/rooms/owned-room.js.map +1 -0
- package/dist/rooms/recorder.d.ts +1 -1
- package/dist/rooms/recorder.js +1 -1
- package/dist/rooms/room.d.ts +86 -277
- package/dist/rooms/room.d.ts.map +1 -1
- package/dist/rooms/room.js +63 -853
- package/dist/rooms/room.js.map +1 -1
- package/dist/rooms/standing-source.d.ts +1 -1
- package/dist/rooms/standing-source.js +1 -1
- package/dist/rooms/transcriber.d.ts +3 -3
- package/dist/rooms/transcriber.d.ts.map +1 -1
- package/dist/rooms/transcriber.js +2 -2
- package/dist/rooms/transport.d.ts +1 -1
- package/dist/rooms/transport.js +2 -2
- package/dist/shaping/assets.d.ts +13 -0
- package/dist/shaping/assets.d.ts.map +1 -1
- package/dist/shaping/assets.js +27 -0
- package/dist/shaping/assets.js.map +1 -1
- package/dist/shaping/chat.d.ts +3 -1
- package/dist/shaping/chat.d.ts.map +1 -1
- package/dist/shaping/chat.js +4 -2
- package/dist/shaping/chat.js.map +1 -1
- package/dist/shaping/rooms.d.ts +80 -1
- package/dist/shaping/rooms.d.ts.map +1 -1
- package/dist/shaping/rooms.js +100 -9
- package/dist/shaping/rooms.js.map +1 -1
- package/dist/shaping/sources.d.ts +7 -3
- package/dist/shaping/sources.d.ts.map +1 -1
- package/dist/shaping/sources.js +9 -10
- package/dist/shaping/sources.js.map +1 -1
- package/dist/transcribe-stream.d.ts +1 -1
- package/dist/transcribe-stream.js +1 -1
- package/dist/transcribe.d.ts +2 -2
- package/dist/transcribe.js +2 -2
- package/package.json +11 -2
package/dist/atrium.js
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
import { Auth } from "./auth.js";
|
|
5
5
|
import { seedAssetId, toOperand } from "./by.js";
|
|
6
6
|
import { DirectEndpoint, DirectRegistry, directFetch, parseModelNames, } from "./direct.js";
|
|
7
|
-
import { DEFAULT_BASE_URL, readEnv } from "./env.js";
|
|
7
|
+
import { DEFAULT_BASE_URL, readEnv, resolveBaseUrl } from "./env.js";
|
|
8
8
|
import { APIError, AtriumError, AuthError, InvalidArgumentError, raiseForStatus } from "./errors.js";
|
|
9
9
|
import { httpFetch } from "./http.js";
|
|
10
10
|
import { parseAsset, parseAssetPage, parseAssetVector, parseChatReply, parseCompareResult, parseEmbedding, parseIndexEntry, parseRoom, parseSearchByExamplesHit, parseSearchHit, parseSegment, parseSession, parseSessionEvent, parseSessionEventRow, parseSessionInfo, parseSourceInfo, parseSourceSearchResult, parseSpace, parseSurvey, parseSurveyResponse, parseSurveyTemplate, parseTagDefinition, parseTranscript, } from "./models.js";
|
|
11
|
-
import { Room } from "./rooms/index.js";
|
|
11
|
+
import { OwnedRoom, Room, } from "./rooms/index.js";
|
|
12
12
|
import { sseFrames } from "./rooms/sse.js";
|
|
13
|
-
import { buildAssetGet, buildAssetIndex, buildAssetList, buildAssetSegments, buildAssetVectors, buildBulkVectors, buildChat, buildCompare, buildEmbed, buildEnrich, buildProjectFileUpload, buildRoomOpen, buildSearch, buildSearchByExamples, buildSessionEvent, buildSessionEvents, buildSessionExport, buildSessionExportBundle, buildSessionStart, buildSessionsList, buildSourceImport, buildSourcesSearch, buildSurveyResponses, buildSurveyStart, buildTagGet, buildTagsList, buildTemplateCreate, buildTemplateDefault, buildTemplateGet, buildTemplatesList, buildTemplateVersion, buildTranscribe,
|
|
13
|
+
import { buildAdd, buildAssetGet, buildAssetIndex, buildAssetList, buildAssetSegments, buildAssetVectors, buildBulkVectors, buildChat, buildCompare, buildEmbed, buildEnrich, buildProjectFileUpload, buildRoomOpen, buildSearch, buildSearchByExamples, buildSessionEvent, buildSessionEvents, buildSessionExport, buildSessionExportBundle, buildSessionStart, buildSessionsList, buildSourceImport, buildSourcesSearch, buildSurveyResponses, buildSurveyStart, buildTagGet, buildTagsList, buildTemplateCreate, buildTemplateDefault, buildTemplateGet, buildTemplatesList, buildTemplateVersion, buildTranscribe, chatBody, distinctExportIds, isZodSchema, JSON_OBJECT, repairMessages, resolveImportTarget, responseFormatParam, sameJsonBody, structuredError, tryParse, } from "./shaping/index.js";
|
|
14
14
|
import { buildRoomResolve } from "./shaping/rooms.js";
|
|
15
15
|
import { PkceStore, TokenStore } from "./storage.js";
|
|
16
16
|
import { parseToolArguments, readToolCalls, toolSchema } from "./tools.js";
|
|
@@ -71,14 +71,6 @@ function bytesToBase64(bytes) {
|
|
|
71
71
|
}
|
|
72
72
|
return btoa(binary);
|
|
73
73
|
}
|
|
74
|
-
/** The OpenAI passthrough params of a {@link ChatOptions} — everything the SDK doesn't own itself. */
|
|
75
|
-
function stripChatOptions(opts) {
|
|
76
|
-
const params = { ...opts };
|
|
77
|
-
for (const key of ["model", "responseFormat", "strictParams", "signal", "stream"]) {
|
|
78
|
-
delete params[key];
|
|
79
|
-
}
|
|
80
|
-
return params;
|
|
81
|
-
}
|
|
82
74
|
/**
|
|
83
75
|
* Yield the content deltas of a streamed chat completion. The wire is SSE — ``data:`` frames of
|
|
84
76
|
* OpenAI chunk JSON, terminated by ``data: [DONE]`` — read through the room transport's
|
|
@@ -159,7 +151,7 @@ export class Atrium {
|
|
|
159
151
|
/** Model name → OpenAI-compatible endpoint; consulted by ``chat`` / ``agent`` only. */
|
|
160
152
|
direct;
|
|
161
153
|
constructor(opts = {}) {
|
|
162
|
-
const baseUrl = (opts.baseUrl
|
|
154
|
+
const baseUrl = resolveBaseUrl(opts.baseUrl);
|
|
163
155
|
this.baseUrl = baseUrl;
|
|
164
156
|
this.clientId = opts.clientId ?? readEnv("ATRIUM_CLIENT_ID");
|
|
165
157
|
this.apiKey = opts.apiKey ?? readEnv("ATRIUM_API_KEY");
|
|
@@ -280,28 +272,46 @@ export class Atrium {
|
|
|
280
272
|
}
|
|
281
273
|
}
|
|
282
274
|
chat(messages, opts = {}) {
|
|
283
|
-
if (opts.stream) {
|
|
284
|
-
if (isZodSchema(opts.responseFormat)) {
|
|
285
|
-
throw new InvalidArgumentError("chat(responseFormat: <Zod schema>) cannot be combined with stream: true — structured " +
|
|
286
|
-
"output validates a complete reply");
|
|
287
|
-
}
|
|
288
|
-
const chunks = this.chatStream(messages, opts);
|
|
289
|
-
// Python's decorator emits the trace when the generator is handed back (nothing is consumed
|
|
290
|
-
// yet), so a streamed call has no reply preview and a ~0 duration. Mirror that rather than
|
|
291
|
-
// deferring the emit until the stream drains — a `trace.chat` should appear when the call is
|
|
292
|
-
// made. Fire-and-forget: `traced` swallows its own failures and the fn resolves immediately.
|
|
293
|
-
void traced(this.traceSinks, "chat", { model: opts.model }, summarizeChat, async () => chunks);
|
|
294
|
-
return chunks;
|
|
295
|
-
}
|
|
296
275
|
// One logical call, one `trace.chat` — the structured path recurses through the UNTRACED
|
|
297
276
|
// `chatOnce` below, which is how TS gets Python's re-entrancy guard for free.
|
|
298
277
|
return traced(this.traceSinks, "chat", { model: opts.model }, summarizeChat, () => isZodSchema(opts.responseFormat)
|
|
299
278
|
? this.chatParse(messages, opts.responseFormat, opts)
|
|
300
279
|
: this.chatOnce(messages, opts));
|
|
301
280
|
}
|
|
302
|
-
/**
|
|
281
|
+
/**
|
|
282
|
+
* Stream a chat completion's content deltas — an ``AsyncIterable<string>`` returned
|
|
283
|
+
* **synchronously**, so there is nothing to ``await`` before the ``for await`` loop.
|
|
284
|
+
*
|
|
285
|
+
* The streaming sibling of {@link chat}, and its own method rather than a ``stream: true`` flag:
|
|
286
|
+
* the return type differs completely, and a flag that changes it makes every call site narrow a
|
|
287
|
+
* union the caller already knows the answer to. Structured output has no meaning here (a schema
|
|
288
|
+
* validates a *complete* reply) — that's {@link chat}. Works the same on a direct endpoint.
|
|
289
|
+
*/
|
|
290
|
+
chatStream(messages, opts = {}) {
|
|
291
|
+
const chunks = this.streamDeltas(messages, opts);
|
|
292
|
+
// Python's decorator emits the trace when the generator is handed back (nothing is consumed
|
|
293
|
+
// yet), so a streamed call has no reply preview and a ~0 duration. Mirror that rather than
|
|
294
|
+
// deferring the emit until the stream drains — a `trace.chat` should appear when the call is
|
|
295
|
+
// made. Fire-and-forget: `traced` swallows its own failures and the fn resolves immediately.
|
|
296
|
+
void traced(this.traceSinks, "chat", { model: opts.model }, summarizeChat, async () => chunks);
|
|
297
|
+
return chunks;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* The chat body's params for one completion: the caller's passthrough bag plus the SDK-owned keys
|
|
301
|
+
* (applied last, so they win a collision).
|
|
302
|
+
*
|
|
303
|
+
* ``stream`` **throws** — it would swap the response for an SSE body that ``chat`` then parses as
|
|
304
|
+
* JSON, so the only honest answer names {@link chatStream}. ``messages`` is dropped: the messages
|
|
305
|
+
* argument is the messages, and a second copy in the bag can only be an accident.
|
|
306
|
+
*/
|
|
303
307
|
chatParams(opts) {
|
|
304
|
-
const p =
|
|
308
|
+
const p = { ...opts.params };
|
|
309
|
+
if ("stream" in p) {
|
|
310
|
+
throw new InvalidArgumentError("chat params: `stream` is not a passthrough key — the SDK owns it. For token deltas call " +
|
|
311
|
+
"atrium.chatStream(messages, opts), which returns an AsyncIterable<string> and sets it " +
|
|
312
|
+
"itself; chat() always resolves one complete reply");
|
|
313
|
+
}
|
|
314
|
+
delete p.messages;
|
|
305
315
|
if (opts.responseFormat !== undefined) {
|
|
306
316
|
p.response_format = opts.responseFormat;
|
|
307
317
|
}
|
|
@@ -323,7 +333,7 @@ export class Atrium {
|
|
|
323
333
|
}
|
|
324
334
|
/** Content deltas of a streamed completion (SSE, ``[DONE]``-terminated) — the Atrium route or,
|
|
325
335
|
* for a registered model, the direct endpoint. Mirror of Python's ``_chat_stream``. */
|
|
326
|
-
async *
|
|
336
|
+
async *streamDeltas(messages, opts) {
|
|
327
337
|
const body = chatBody(messages, opts.model, { ...this.chatParams(opts), stream: true });
|
|
328
338
|
const endpoint = this.direct.resolve(opts.model);
|
|
329
339
|
const res = endpoint
|
|
@@ -359,7 +369,7 @@ export class Atrium {
|
|
|
359
369
|
throw structuredError(schema.constructor.name, second.content, detail);
|
|
360
370
|
}
|
|
361
371
|
async agent(prompt, opts) {
|
|
362
|
-
const { tools, model, maxTurns = 6, responseFormat, signal } = opts;
|
|
372
|
+
const { tools, model, maxTurns = 6, responseFormat, signal, strictParams } = opts;
|
|
363
373
|
if (responseFormat !== undefined && !isZodSchema(responseFormat)) {
|
|
364
374
|
// Unlike chat(), a raw dict has nowhere sensible to go: json-mode conflicts with the
|
|
365
375
|
// tool-calling turns, so forwarding it would break the loop and ignoring it would silently do
|
|
@@ -367,15 +377,18 @@ export class Atrium {
|
|
|
367
377
|
throw new InvalidArgumentError("agent(responseFormat) takes a Zod schema (it structures the final answer); a json-mode " +
|
|
368
378
|
"dict isn't applied to the tool-calling turns");
|
|
369
379
|
}
|
|
370
|
-
const params = { ...opts };
|
|
371
|
-
for (const key of ["tools", "model", "maxTurns", "responseFormat", "signal"])
|
|
372
|
-
delete params[key];
|
|
380
|
+
const params = { ...opts.params };
|
|
373
381
|
const byName = new Map(tools.map((tool) => [tool.name, tool]));
|
|
374
382
|
const schemas = tools.map(toolSchema);
|
|
375
383
|
const messages = [{ role: "user", content: prompt }];
|
|
376
384
|
let reply = { content: "", model: "", raw: {} };
|
|
377
385
|
for (let turn = 0; turn < maxTurns; turn++) {
|
|
378
|
-
reply = await this.chat(messages, {
|
|
386
|
+
reply = await this.chat(messages, {
|
|
387
|
+
model,
|
|
388
|
+
signal,
|
|
389
|
+
strictParams,
|
|
390
|
+
params: { ...params, tools: schemas },
|
|
391
|
+
});
|
|
379
392
|
const choices = reply.raw.choices;
|
|
380
393
|
const message = (Array.isArray(choices) ? choices[0] : undefined);
|
|
381
394
|
const assistant = message?.message;
|
|
@@ -394,7 +407,12 @@ export class Atrium {
|
|
|
394
407
|
}
|
|
395
408
|
if (isZodSchema(responseFormat)) {
|
|
396
409
|
// Structure the FINAL answer only — json-mode conflicts with the tool-calling turns.
|
|
397
|
-
return this.parseOrRepair(messages, reply, responseFormat, {
|
|
410
|
+
return this.parseOrRepair(messages, reply, responseFormat, {
|
|
411
|
+
model,
|
|
412
|
+
signal,
|
|
413
|
+
strictParams,
|
|
414
|
+
params,
|
|
415
|
+
});
|
|
398
416
|
}
|
|
399
417
|
return reply;
|
|
400
418
|
}
|
|
@@ -629,6 +647,25 @@ if (typeof Symbol.asyncDispose === "symbol") {
|
|
|
629
647
|
};
|
|
630
648
|
}
|
|
631
649
|
// --- namespaces ----------------------------------------------------------------------------------
|
|
650
|
+
/**
|
|
651
|
+
* Refuse an operand handed to ``search`` where its options object belongs.
|
|
652
|
+
*
|
|
653
|
+
* ``SearchOptions`` has no required field, so TypeScript's weak-type check is the only thing
|
|
654
|
+
* standing between ``search(hit)`` and a compile error — and an {@link Asset} / {@link SearchHit} /
|
|
655
|
+
* ``by.*`` operand *does* share a property name with it, which satisfies that check. The call would
|
|
656
|
+
* then search for the empty string with no filters: a full-library dump, silently. Runtime is where
|
|
657
|
+
* this can still be caught, so it is caught here, naming the fix.
|
|
658
|
+
*/
|
|
659
|
+
function rejectOperandAsSearchOptions(opts) {
|
|
660
|
+
const o = opts;
|
|
661
|
+
const isAsset = typeof o.id === "string" && (o.facts !== undefined || o.modality !== undefined);
|
|
662
|
+
const isHit = o.asset !== undefined && typeof o.score === "number";
|
|
663
|
+
const isOperand = typeof o.kind === "string";
|
|
664
|
+
if (isAsset || isHit || isOperand) {
|
|
665
|
+
throw new InvalidArgumentError("search(): the object form takes search OPTIONS, not the thing to search by — pass " +
|
|
666
|
+
"{ like: hit } (or { like: asset }, { like: id }) instead of the operand itself");
|
|
667
|
+
}
|
|
668
|
+
}
|
|
632
669
|
function likeSourceId(like) {
|
|
633
670
|
if (typeof like === "string") {
|
|
634
671
|
return like;
|
|
@@ -641,6 +678,28 @@ export class Library {
|
|
|
641
678
|
constructor(c) {
|
|
642
679
|
this.c = c;
|
|
643
680
|
}
|
|
681
|
+
/**
|
|
682
|
+
* Add an asset to the library (``POST /v1/assets``); it indexes in the background — the front door
|
|
683
|
+
* for putting something in (mirror of Python's ``library.add``).
|
|
684
|
+
*
|
|
685
|
+
* Pass a ``file`` to upload it (a ``Blob``/``File`` or a URL string — never a filesystem path, the
|
|
686
|
+
* core is browser-safe), or omit it and pass only a ``description`` to create a **fileless** asset
|
|
687
|
+
* whose description *is* the content (the web UI's "Add other" — a mood note, a data-only record).
|
|
688
|
+
* One of the two is required. ``modality`` is a free-form label for search/display only; for a file
|
|
689
|
+
* the server derives the real ``contentKind`` from the bytes, and a fileless asset defaults to
|
|
690
|
+
* ``"other"``. {@link Assets.upload} is the file-only twin.
|
|
691
|
+
*/
|
|
692
|
+
async add(opts = {}) {
|
|
693
|
+
const { collection } = this.c.scope(undefined, opts.collection);
|
|
694
|
+
const req = buildAdd({
|
|
695
|
+
file: opts.file,
|
|
696
|
+
modality: opts.modality,
|
|
697
|
+
description: opts.description,
|
|
698
|
+
collection,
|
|
699
|
+
});
|
|
700
|
+
const data = await this.c.request(req, opts.signal);
|
|
701
|
+
return parseAsset(data, this.c.baseUrl, this.c.assetBinder);
|
|
702
|
+
}
|
|
644
703
|
/** List the searchable embedding spaces (``GET /v1/library/spaces``). */
|
|
645
704
|
async spaces(opts = {}) {
|
|
646
705
|
const data = await this.c.request({ method: "GET", path: "/v1/library/spaces" }, opts.signal);
|
|
@@ -660,8 +719,19 @@ export class Library {
|
|
|
660
719
|
return (spaces.find((s) => s.defaultModalities.includes(modality)) ??
|
|
661
720
|
spaces.find((s) => s.modalities.includes(modality)));
|
|
662
721
|
}
|
|
663
|
-
|
|
664
|
-
|
|
722
|
+
async search(queryOrOpts = "", maybeOpts = {}) {
|
|
723
|
+
// String-vs-object discriminant: only the string form takes a second argument.
|
|
724
|
+
const isStringForm = typeof queryOrOpts === "string";
|
|
725
|
+
const opts = isStringForm ? maybeOpts : queryOrOpts;
|
|
726
|
+
if (!isStringForm) {
|
|
727
|
+
// `SearchOptions` is all-optional, so an Asset / SearchHit / `by.*` operand is weak-type
|
|
728
|
+
// compatible with it and would silently search for nothing. Name the fix instead.
|
|
729
|
+
rejectOperandAsSearchOptions(opts);
|
|
730
|
+
}
|
|
731
|
+
if (isStringForm && opts.query !== undefined) {
|
|
732
|
+
throw new InvalidArgumentError("search(): pass the text either as the first argument or as opts.query, not both");
|
|
733
|
+
}
|
|
734
|
+
const query = isStringForm ? queryOrOpts : (opts.query ?? "");
|
|
665
735
|
const { project, collection } = this.c.scope(opts.project, opts.collection);
|
|
666
736
|
const media = opts.image ?? opts.audio ?? opts.video;
|
|
667
737
|
const space = opts.space !== undefined ? await this.c.resolveSpace(opts.space) : undefined;
|
|
@@ -847,17 +917,10 @@ export class Assets {
|
|
|
847
917
|
const data = await this.c.request(buildAssetIndex(likeSourceId(assetOrId)));
|
|
848
918
|
return (Array.isArray(data) ? data : []).map(parseIndexEntry);
|
|
849
919
|
}
|
|
850
|
-
/** Upload a file to the library (``POST /v1/assets``); it indexes in the background.
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
file,
|
|
855
|
-
modality: opts.modality,
|
|
856
|
-
description: opts.description,
|
|
857
|
-
collection,
|
|
858
|
-
});
|
|
859
|
-
const data = await this.c.request(req, opts.signal);
|
|
860
|
-
return parseAsset(data, this.c.baseUrl, this.c.assetBinder);
|
|
920
|
+
/** Upload a file to the library (``POST /v1/assets``); it indexes in the background. The file-only
|
|
921
|
+
* twin of {@link Library.add}, which it delegates to — same request, same result. */
|
|
922
|
+
upload(file, opts = {}) {
|
|
923
|
+
return this.c.library.add({ ...opts, file });
|
|
861
924
|
}
|
|
862
925
|
}
|
|
863
926
|
/** External sources — search several providers at once, then import what you pick. */
|
|
@@ -883,9 +946,10 @@ export class Sources {
|
|
|
883
946
|
const data = await this.c.request(req, opts.signal);
|
|
884
947
|
return parseSourceSearchResult(data);
|
|
885
948
|
}
|
|
886
|
-
/** Import one candidate into the shared library (``POST …/{source}/import``)
|
|
887
|
-
|
|
888
|
-
|
|
949
|
+
/** Import one candidate into the shared library (``POST …/{source}/import``) — ``target`` is a
|
|
950
|
+
* {@link Candidate} from {@link search}, or an explicit ``{ sourceId, externalId }`` pair. */
|
|
951
|
+
async import(target, opts = {}) {
|
|
952
|
+
const [sourceId, externalId] = resolveImportTarget(target);
|
|
889
953
|
const req = buildSourceImport({
|
|
890
954
|
sourceId,
|
|
891
955
|
externalId,
|
|
@@ -896,6 +960,26 @@ export class Sources {
|
|
|
896
960
|
return parseAsset(data, this.c.baseUrl, this.c.assetBinder);
|
|
897
961
|
}
|
|
898
962
|
}
|
|
963
|
+
/**
|
|
964
|
+
* Refuse the pre-0.4 options bag passed where the event payload now goes.
|
|
965
|
+
*
|
|
966
|
+
* ``{ payload: {...}, source: "app" }`` is a perfectly valid ``Record<string, unknown>``, so the old
|
|
967
|
+
* call shape type-checks as the new third argument and would be logged verbatim — a corrupted event,
|
|
968
|
+
* silently. It is only ambiguous in theory: a real payload carrying a ``payload`` key alongside
|
|
969
|
+
* anything other than ``source`` / ``via`` passes untouched.
|
|
970
|
+
*/
|
|
971
|
+
function rejectLegacyEventBag(payload) {
|
|
972
|
+
if (!payload)
|
|
973
|
+
return;
|
|
974
|
+
const nested = payload.payload;
|
|
975
|
+
if (!nested || typeof nested !== "object")
|
|
976
|
+
return;
|
|
977
|
+
const rest = Object.keys(payload).filter((key) => key !== "payload");
|
|
978
|
+
if (rest.every((key) => key === "source" || key === "via")) {
|
|
979
|
+
throw new InvalidArgumentError("session event: this looks like the pre-0.4 options bag — pass the event payload directly " +
|
|
980
|
+
"(sessions.event(id, type, payload)); source/via ride the 4th argument");
|
|
981
|
+
}
|
|
982
|
+
}
|
|
899
983
|
/** Start / resume sessions and append to their durable log. */
|
|
900
984
|
export class Sessions {
|
|
901
985
|
c;
|
|
@@ -912,14 +996,14 @@ export class Sessions {
|
|
|
912
996
|
condition: opts.condition,
|
|
913
997
|
meta: opts.meta,
|
|
914
998
|
});
|
|
915
|
-
return parseSession(await this.c.request(req, opts.signal));
|
|
999
|
+
return parseSession(await this.c.request(req, opts.signal), this);
|
|
916
1000
|
}
|
|
917
1001
|
/** Resume an existing session by id (``GET /v1/sessions/{id}``) — the live {@link Session} handle
|
|
918
1002
|
* for continued logging. For a read-only analysis view use {@link get}, which GETs the same route
|
|
919
1003
|
* but returns a {@link SessionInfo} read model. */
|
|
920
1004
|
async resume(sessionId, opts = {}) {
|
|
921
1005
|
const data = await this.c.request({ method: "GET", path: `/v1/sessions/${sessionId}` }, opts.signal);
|
|
922
|
-
return parseSession(data);
|
|
1006
|
+
return parseSession(data, this);
|
|
923
1007
|
}
|
|
924
1008
|
/** List a project's sessions, newest first (``GET /v1/sessions``) — the read model. No ``project``
|
|
925
1009
|
* → the client's default project. ``phase`` (a string or list) filters by study phase; the empty
|
|
@@ -966,11 +1050,14 @@ export class Sessions {
|
|
|
966
1050
|
const req = buildSessionExportBundle(distinctExportIds(ids));
|
|
967
1051
|
return this.c.requestBytes(req, opts.signal);
|
|
968
1052
|
}
|
|
969
|
-
/** Append a durable event to a session's log (``POST /v1/sessions/{id}/events``)
|
|
970
|
-
|
|
1053
|
+
/** Append a durable event to a session's log (``POST /v1/sessions/{id}/events``) — the id-based
|
|
1054
|
+
* form. A {@link Session} handle from {@link start} / {@link resume} carries the bound
|
|
1055
|
+
* ``session.event(type, payload?)``, which is the same call with the id threaded for you. */
|
|
1056
|
+
async event(sessionId, type, payload, opts = {}) {
|
|
1057
|
+
rejectLegacyEventBag(payload);
|
|
971
1058
|
const req = buildSessionEvent(sessionId, {
|
|
972
1059
|
type,
|
|
973
|
-
payload
|
|
1060
|
+
payload,
|
|
974
1061
|
source: opts.source,
|
|
975
1062
|
via: opts.via,
|
|
976
1063
|
});
|
|
@@ -1009,9 +1096,11 @@ export class Surveys {
|
|
|
1009
1096
|
/**
|
|
1010
1097
|
* Start a survey on a session: creates the data object (pinning the template's current version —
|
|
1011
1098
|
* or the project's DEFAULT template when ``template`` is omitted) and returns the handle with its
|
|
1012
|
-
* once-only token.
|
|
1099
|
+
* once-only token. Options-only — ``start({ template: "post-study", into: session })``, or just
|
|
1100
|
+
* ``start({ into: session })`` for the default template.
|
|
1013
1101
|
*/
|
|
1014
|
-
async start(
|
|
1102
|
+
async start(opts) {
|
|
1103
|
+
const { template } = opts;
|
|
1015
1104
|
const sessionId = typeof opts.into === "string" ? opts.into : opts.into.id;
|
|
1016
1105
|
const req = buildSurveyStart(sessionId, {
|
|
1017
1106
|
name: opts.name ?? template ?? "survey",
|
|
@@ -1111,67 +1200,83 @@ export class Surveys {
|
|
|
1111
1200
|
};
|
|
1112
1201
|
}
|
|
1113
1202
|
}
|
|
1114
|
-
/**
|
|
1115
|
-
*
|
|
1203
|
+
/**
|
|
1204
|
+
* Open / fetch / join rooms. ``open`` / ``get`` / ``list`` return an {@link OwnedRoom} — the live
|
|
1205
|
+
* handle *plus* the host-side surfaces the account credential unlocks (``record`` / ``transcribe`` /
|
|
1206
|
+
* ``audioVectors`` / ``dashboard`` / ``trace`` / the standing offers / ``rotateCode`` / ``delete``).
|
|
1207
|
+
* ``join(code)`` returns a plain {@link Room}: a code grants live transport, not the account, so the
|
|
1208
|
+
* host-side members are absent from its **type** rather than throwing at runtime.
|
|
1209
|
+
* ``connect()`` (called for you by any wire method) opens the socket.
|
|
1210
|
+
*/
|
|
1116
1211
|
export class Rooms {
|
|
1117
1212
|
c;
|
|
1118
1213
|
constructor(c) {
|
|
1119
1214
|
this.c = c;
|
|
1120
1215
|
}
|
|
1121
|
-
|
|
1122
|
-
|
|
1216
|
+
/** The participant deps every handle gets — the live-connection knobs, no credential. */
|
|
1217
|
+
liveDeps(info, live) {
|
|
1218
|
+
return {
|
|
1123
1219
|
id: info.id,
|
|
1124
1220
|
connectCode: info.connectCode,
|
|
1125
1221
|
name: info.name,
|
|
1126
1222
|
projectId: info.projectId,
|
|
1127
1223
|
baseUrl: this.c.baseUrl,
|
|
1128
|
-
// A participant (join-by-code) can't manage the room, nor embed (no credentials of its own);
|
|
1129
|
-
// omit the REST caller and the embedding client for it.
|
|
1130
|
-
rest: participant ? undefined : (req) => this.c.request(req),
|
|
1131
|
-
client: participant
|
|
1132
|
-
? undefined
|
|
1133
|
-
: {
|
|
1134
|
-
embed: (opts) => this.c.embed(opts),
|
|
1135
|
-
chat: (messages, o) => this.c.chat(messages, o),
|
|
1136
|
-
library: this.c.library,
|
|
1137
|
-
request: (req) => this.c.request(req),
|
|
1138
|
-
transcribeStream: (clips, o) => this.c.transcribeStream(clips, o),
|
|
1139
|
-
transcribe: (audio, o) => this.c.transcribe(audio, o),
|
|
1140
|
-
// The client's shared sink list — `room.trace()` registers on it, so the client's own
|
|
1141
|
-
// library/model calls mirror into this room.
|
|
1142
|
-
traceSinks: this.c.traceSinks,
|
|
1143
|
-
},
|
|
1144
1224
|
identity: live.identity,
|
|
1145
1225
|
announce: live.announce,
|
|
1146
1226
|
requestTimeout: live.requestTimeout,
|
|
1147
1227
|
reconnect: live.reconnect,
|
|
1148
1228
|
transport: live.transport,
|
|
1149
|
-
|
|
1229
|
+
webSocketCtor: live.webSocketCtor,
|
|
1150
1230
|
fetchImpl: live.fetchImpl,
|
|
1151
1231
|
signal: live.signal,
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
/** The owned handle: the participant deps plus this client's REST caller and capability slice. */
|
|
1235
|
+
own(info, live) {
|
|
1236
|
+
return new OwnedRoom({
|
|
1237
|
+
...this.liveDeps(info, live),
|
|
1238
|
+
rest: (req) => this.c.request(req),
|
|
1239
|
+
client: {
|
|
1240
|
+
embed: (opts) => this.c.embed(opts),
|
|
1241
|
+
chat: (messages, o) => this.c.chat(messages, o),
|
|
1242
|
+
library: this.c.library,
|
|
1243
|
+
request: (req) => this.c.request(req),
|
|
1244
|
+
transcribeStream: (clips, o) => this.c.transcribeStream(clips, o),
|
|
1245
|
+
transcribe: (audio, o) => this.c.transcribe(audio, o),
|
|
1246
|
+
// The client's shared sink list — `room.trace()` registers on it, so the client's own
|
|
1247
|
+
// library/model calls mirror into this room.
|
|
1248
|
+
traceSinks: this.c.traceSinks,
|
|
1249
|
+
},
|
|
1152
1250
|
});
|
|
1153
1251
|
}
|
|
1154
|
-
/** Open a room (``POST /v1/rooms``) and return a live handle. */
|
|
1252
|
+
/** Open a room (``POST /v1/rooms``) and return a live owned handle. */
|
|
1155
1253
|
async open(opts = {}) {
|
|
1156
1254
|
const { project } = this.c.scope(opts.project, undefined);
|
|
1157
1255
|
const req = buildRoomOpen({ name: opts.name, project, embeddingSpace: opts.embeddingSpace });
|
|
1158
|
-
return this.
|
|
1256
|
+
return this.own(parseRoom(await this.c.request(req, opts.signal)), opts);
|
|
1159
1257
|
}
|
|
1160
|
-
/** Fetch a room by id (``GET /v1/rooms/{id}``) and return a live handle. */
|
|
1258
|
+
/** Fetch a room by id (``GET /v1/rooms/{id}``) and return a live owned handle. */
|
|
1161
1259
|
async get(roomId, opts = {}) {
|
|
1162
1260
|
const data = await this.c.request({ method: "GET", path: `/v1/rooms/${roomId}` }, opts.signal);
|
|
1163
|
-
return this.
|
|
1261
|
+
return this.own(parseRoom(data), opts);
|
|
1164
1262
|
}
|
|
1165
|
-
/** List the rooms you can see (``GET /v1/rooms``) as live handles. */
|
|
1263
|
+
/** List the rooms you can see (``GET /v1/rooms``) as live owned handles. */
|
|
1166
1264
|
async list(opts = {}) {
|
|
1167
1265
|
const data = await this.c.request({ method: "GET", path: "/v1/rooms" }, opts.signal);
|
|
1168
|
-
return (Array.isArray(data) ? data : []).map((d) => this.
|
|
1266
|
+
return (Array.isArray(data) ? data : []).map((d) => this.own(parseRoom(d), {}));
|
|
1169
1267
|
}
|
|
1170
|
-
/**
|
|
1171
|
-
*
|
|
1268
|
+
/**
|
|
1269
|
+
* Join a room by its connect code (``GET /v1/rooms/resolve``) — a **participant** handle that can
|
|
1270
|
+
* ``send`` / ``on`` / ``request`` / play / consume audio, but not manage the room.
|
|
1271
|
+
*
|
|
1272
|
+
* Deliberately a {@link Room}, not an {@link OwnedRoom}, even though the resolve call itself is
|
|
1273
|
+
* authenticated: a join-by-code handle is built without the REST caller and the capability slice,
|
|
1274
|
+
* so the host-side surfaces would throw. The type says so instead. Need them? The room is yours —
|
|
1275
|
+
* use {@link get} with its id.
|
|
1276
|
+
*/
|
|
1172
1277
|
async join(code, opts = {}) {
|
|
1173
1278
|
const data = await this.c.request(buildRoomResolve(code), opts.signal);
|
|
1174
|
-
return this.
|
|
1279
|
+
return new Room(this.liveDeps(parseRoom(data), opts));
|
|
1175
1280
|
}
|
|
1176
1281
|
}
|
|
1177
1282
|
//# sourceMappingURL=atrium.js.map
|