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.
Files changed (77) hide show
  1. package/README.md +75 -24
  2. package/dist/atrium.d.ts +126 -42
  3. package/dist/atrium.d.ts.map +1 -1
  4. package/dist/atrium.js +192 -87
  5. package/dist/atrium.js.map +1 -1
  6. package/dist/dash.d.ts +3 -3
  7. package/dist/dash.d.ts.map +1 -1
  8. package/dist/dash.js +1 -2
  9. package/dist/dash.js.map +1 -1
  10. package/dist/env.d.ts +14 -0
  11. package/dist/env.d.ts.map +1 -1
  12. package/dist/env.js +22 -0
  13. package/dist/env.js.map +1 -1
  14. package/dist/index.d.ts +4 -4
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +11 -3
  17. package/dist/index.js.map +1 -1
  18. package/dist/models.d.ts +84 -31
  19. package/dist/models.d.ts.map +1 -1
  20. package/dist/models.js +97 -25
  21. package/dist/models.js.map +1 -1
  22. package/dist/player/play-command.d.ts +6 -6
  23. package/dist/player/play-command.d.ts.map +1 -1
  24. package/dist/player/play-intent.d.ts.map +1 -1
  25. package/dist/player/play-intent.js +2 -1
  26. package/dist/player/play-intent.js.map +1 -1
  27. package/dist/player/runtime.d.ts +7 -3
  28. package/dist/player/runtime.d.ts.map +1 -1
  29. package/dist/player/runtime.js.map +1 -1
  30. package/dist/react/use-room-connection.d.ts +4 -1
  31. package/dist/react/use-room-connection.d.ts.map +1 -1
  32. package/dist/react/use-room-connection.js +5 -1
  33. package/dist/react/use-room-connection.js.map +1 -1
  34. package/dist/rooms/index.d.ts +3 -1
  35. package/dist/rooms/index.d.ts.map +1 -1
  36. package/dist/rooms/index.js +4 -3
  37. package/dist/rooms/index.js.map +1 -1
  38. package/dist/rooms/offer-transcription.d.ts +1 -1
  39. package/dist/rooms/offer-transcription.js +1 -1
  40. package/dist/rooms/owned-room.d.ts +251 -0
  41. package/dist/rooms/owned-room.d.ts.map +1 -0
  42. package/dist/rooms/owned-room.js +844 -0
  43. package/dist/rooms/owned-room.js.map +1 -0
  44. package/dist/rooms/recorder.d.ts +1 -1
  45. package/dist/rooms/recorder.js +1 -1
  46. package/dist/rooms/room.d.ts +86 -277
  47. package/dist/rooms/room.d.ts.map +1 -1
  48. package/dist/rooms/room.js +63 -853
  49. package/dist/rooms/room.js.map +1 -1
  50. package/dist/rooms/standing-source.d.ts +1 -1
  51. package/dist/rooms/standing-source.js +1 -1
  52. package/dist/rooms/transcriber.d.ts +3 -3
  53. package/dist/rooms/transcriber.d.ts.map +1 -1
  54. package/dist/rooms/transcriber.js +2 -2
  55. package/dist/rooms/transport.d.ts +1 -1
  56. package/dist/rooms/transport.js +2 -2
  57. package/dist/shaping/assets.d.ts +13 -0
  58. package/dist/shaping/assets.d.ts.map +1 -1
  59. package/dist/shaping/assets.js +27 -0
  60. package/dist/shaping/assets.js.map +1 -1
  61. package/dist/shaping/chat.d.ts +3 -1
  62. package/dist/shaping/chat.d.ts.map +1 -1
  63. package/dist/shaping/chat.js +4 -2
  64. package/dist/shaping/chat.js.map +1 -1
  65. package/dist/shaping/rooms.d.ts +80 -1
  66. package/dist/shaping/rooms.d.ts.map +1 -1
  67. package/dist/shaping/rooms.js +100 -9
  68. package/dist/shaping/rooms.js.map +1 -1
  69. package/dist/shaping/sources.d.ts +7 -3
  70. package/dist/shaping/sources.d.ts.map +1 -1
  71. package/dist/shaping/sources.js +9 -10
  72. package/dist/shaping/sources.js.map +1 -1
  73. package/dist/transcribe-stream.d.ts +1 -1
  74. package/dist/transcribe-stream.js +1 -1
  75. package/dist/transcribe.d.ts +2 -2
  76. package/dist/transcribe.js +2 -2
  77. 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, buildUpload, chatBody, distinctExportIds, isZodSchema, JSON_OBJECT, repairMessages, resolveImportTarget, responseFormatParam, sameJsonBody, structuredError, tryParse, } from "./shaping/index.js";
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 ?? readEnv("ATRIUM_BASE_URL") ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
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
- /** The chat body for one completion: the caller's passthrough params plus the SDK-owned keys. */
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 = stripChatOptions(opts);
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 *chatStream(messages, opts) {
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, { model, tools: schemas, signal, ...params });
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, { model, signal, ...params });
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
- /** Semantic search by text, an example asset (``like``), a media file, or a raw ``vector``. */
664
- async search(query = "", opts = {}) {
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
- async upload(file, opts = {}) {
852
- const { collection } = this.c.scope(undefined, opts.collection);
853
- const req = buildUpload({
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
- async import(opts) {
888
- const [sourceId, externalId] = resolveImportTarget(opts.candidate, opts.sourceId, opts.externalId);
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
- async event(sessionId, type, opts = {}) {
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: opts.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(template, opts) {
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
- /** Open / fetch / join rooms — each returns a live {@link Room} handle (connect, send, on, request,
1115
- * player controls, audio signals). ``connect()`` (called for you by any wire method) opens the socket. */
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
- wrap(info, live, participant) {
1122
- return new Room({
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
- WebSocketCtor: live.WebSocketCtor,
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.wrap(parseRoom(await this.c.request(req, opts.signal)), opts, false);
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.wrap(parseRoom(data), opts, false);
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.wrap(parseRoom(d), {}, false));
1266
+ return (Array.isArray(data) ? data : []).map((d) => this.own(parseRoom(d), {}));
1169
1267
  }
1170
- /** Join a room by its connect code (``GET /v1/rooms/resolve``) — a participant handle that can
1171
- * ``send`` / ``on`` / ``request`` / play / consume audio, but not manage the room. */
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.wrap(parseRoom(data), opts, true);
1279
+ return new Room(this.liveDeps(parseRoom(data), opts));
1175
1280
  }
1176
1281
  }
1177
1282
  //# sourceMappingURL=atrium.js.map