@meshagent/meshagent 0.46.4 → 0.47.1

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.
@@ -1,9 +1,10 @@
1
1
  import { ApiScope } from "./participant-token.js";
2
2
  export type ProjectRole = "member" | "admin" | "developer" | "room_creator" | "room_inventory" | "room_manager" | "session_inventory" | "agent_creator" | "agent_inventory" | "agent_manager" | "repository_creator" | "repository_inventory" | "repository_manager" | "feed_creator" | "feed_inventory" | "feed_manager" | "oauth_client_creator" | "oauth_client_inventory" | "oauth_client_manager" | "api_key_creator" | "api_key_inventory" | "api_key_manager" | "service_creator" | "service_inventory" | "service_manager" | "service_account_creator" | "service_account_inventory" | "service_account_manager" | "participant_token_creator" | "mailbox_creator" | "mailbox_inventory" | "mailbox_manager" | "route_creator" | "route_inventory" | "route_manager" | "scheduled_task_creator" | "scheduled_task_inventory" | "scheduled_task_manager" | "feed_subscription_creator" | "feed_subscription_inventory" | "feed_subscription_manager" | "llm_logger_creator" | "llm_logger_inventory" | "llm_logger_manager" | "llm_proxy_user" | "usage_reporter" | "billing_manager" | "group_manager";
3
3
  export type ResourceRole = "viewer" | "operator" | "developer" | "admin";
4
+ export type RoomRole = "site_user" | ResourceRole;
4
5
  export type FeedRole = "reader" | "subscriber" | "publisher" | "manager";
5
6
  export type SecretRole = "use_proxy";
6
- export type AccessRole = ProjectRole | ResourceRole | FeedRole | SecretRole | "list";
7
+ export type AccessRole = ProjectRole | RoomRole | FeedRole | SecretRole | "list";
7
8
  export type AccessSubjectType = "user" | "group" | "agent" | "service_account" | "userset";
8
9
  export type AccessResourceType = "project" | "room" | "agent" | "group" | "repository" | "feed" | "secret";
9
10
  export interface AccessSubject {
@@ -29,6 +30,12 @@ export interface RoomConnectionInfo {
29
30
  projectId: string;
30
31
  roomUrl: string;
31
32
  }
33
+ export interface AgentConnectionInfo {
34
+ jwt: string;
35
+ agentName: string;
36
+ projectId: string;
37
+ agentUrl: string;
38
+ }
32
39
  export declare class RoomSession {
33
40
  readonly id: string;
34
41
  readonly roomId?: string | null;
@@ -559,6 +566,7 @@ export declare class Meshagent {
559
566
  private parseTransaction;
560
567
  private parseOAuthClient;
561
568
  private parseRoomConnectionInfo;
569
+ private parseAgentConnectionInfo;
562
570
  private encodePathComponent;
563
571
  upload({ projectId, path, data }: {
564
572
  projectId: string;
@@ -908,6 +916,7 @@ export declare class Meshagent {
908
916
  subjectId: string;
909
917
  }): Promise<void>;
910
918
  connectRoom(projectId: string, room: string): Promise<RoomConnectionInfo>;
919
+ connectAgent(projectId: string, agentName: string): Promise<AgentConnectionInfo>;
911
920
  listRooms(projectId: string, options?: {
912
921
  pageSize?: number;
913
922
  continuationToken?: string;
@@ -654,6 +654,7 @@ export class Meshagent {
654
654
  const parsedSubject = this.parseAccessSubject(data.subject);
655
655
  const parsedDirectRoles = Array.isArray(data.direct_roles)
656
656
  ? data.direct_roles.filter((item) => item === "member" ||
657
+ item === "site_user" ||
657
658
  item === "viewer" ||
658
659
  item === "operator" ||
659
660
  item === "developer" ||
@@ -817,6 +818,25 @@ export class Meshagent {
817
818
  }
818
819
  return { jwt, roomName: roomNameValue, projectId: projectIdValue, roomUrl: roomUrlValue };
819
820
  }
821
+ parseAgentConnectionInfo(data) {
822
+ if (!data || typeof data !== "object") {
823
+ throw new RoomException("Invalid agent connection payload");
824
+ }
825
+ const { jwt, agent_name: agentNameRaw, agentName, project_id: projectIdRaw, projectId, agent_url: agentUrlRaw, agentUrl } = data;
826
+ const agentNameValue = typeof agentName === "string" ? agentName : agentNameRaw;
827
+ const projectIdValue = typeof projectId === "string" ? projectId : projectIdRaw;
828
+ const agentUrlValue = typeof agentUrl === "string" ? agentUrl : agentUrlRaw;
829
+ if (typeof jwt !== "string" || typeof agentNameValue !== "string" || typeof projectIdValue !== "string" || typeof agentUrlValue !== "string") {
830
+ throw new RoomException("Invalid agent connection payload: missing fields");
831
+ }
832
+ const parsedUrl = new URL(agentUrlValue);
833
+ const legacySuffix = `/accounts/projects/${encodeURIComponent(projectIdValue)}/agents/${encodeURIComponent(agentNameValue)}/messages`;
834
+ if (parsedUrl.pathname.endsWith(legacySuffix)) {
835
+ const prefix = parsedUrl.pathname.slice(0, -legacySuffix.length);
836
+ parsedUrl.pathname = `${prefix}/agents/${encodeURIComponent(projectIdValue)}/${encodeURIComponent(agentNameValue)}/messages`;
837
+ }
838
+ return { jwt, agentName: agentNameValue, projectId: projectIdValue, agentUrl: parsedUrl.toString() };
839
+ }
820
840
  encodePathComponent(value) {
821
841
  return encodeURIComponent(value);
822
842
  }
@@ -1842,6 +1862,14 @@ export class Meshagent {
1842
1862
  });
1843
1863
  return this.parseRoomConnectionInfo(data);
1844
1864
  }
1865
+ async connectAgent(projectId, agentName) {
1866
+ const data = await this.request(`/accounts/projects/${projectId}/agents/${encodeURIComponent(agentName)}/connect`, {
1867
+ method: "POST",
1868
+ json: {},
1869
+ action: "connect agent",
1870
+ });
1871
+ return this.parseAgentConnectionInfo(data);
1872
+ }
1845
1873
  async listRooms(projectId, options = {}) {
1846
1874
  const { pageSize = 50, continuationToken, filter, view } = options;
1847
1875
  const data = await this.request(`/accounts/projects/${projectId}/rooms`, {
@@ -1,8 +1,72 @@
1
1
  import { EventEmitter } from "./event-emitter.js";
2
2
  import { Participant, RemoteParticipant } from "./participant.js";
3
+ import { Content } from "./response.js";
3
4
  import { RoomClient } from "./room-client.js";
4
- import { RoomMessageEvent } from "./room-event.js";
5
+ import { RoomMessage, RoomMessageEvent } from "./room-event.js";
5
6
  type MessagePayload = Record<string, unknown>;
7
+ export type MessagingStreamEvent = {
8
+ kind: "message";
9
+ message: RoomMessage;
10
+ } | {
11
+ kind: "client_disconnected";
12
+ streamId: string;
13
+ participantId: string;
14
+ } | {
15
+ kind: "closed";
16
+ streamId: string;
17
+ reason: string;
18
+ message?: string;
19
+ };
20
+ declare class MessagingStreamInput {
21
+ private readonly items;
22
+ private signal;
23
+ private active;
24
+ private closed;
25
+ private error;
26
+ enqueue(content: Content, { wait }?: {
27
+ wait?: boolean;
28
+ }): Promise<void>;
29
+ close(error?: unknown): void;
30
+ fail(error: unknown): void;
31
+ stream(): AsyncIterable<Content>;
32
+ }
33
+ export declare class MessagingStream implements AsyncIterable<MessagingStreamEvent> {
34
+ readonly streamId: string;
35
+ readonly remoteParticipantId: string;
36
+ private readonly input;
37
+ private readonly output;
38
+ private readonly inputDone;
39
+ private readonly onClosed;
40
+ private readonly events;
41
+ private eventSignal;
42
+ private state;
43
+ constructor({ streamId, remoteParticipantId, input, output, inputClosed, onClosed, }: {
44
+ streamId: string;
45
+ remoteParticipantId: string;
46
+ input: MessagingStreamInput;
47
+ output: AsyncIterator<Content>;
48
+ inputClosed?: Promise<void>;
49
+ onClosed: () => void;
50
+ });
51
+ get closed(): boolean;
52
+ private push;
53
+ private finish;
54
+ private consumeOutput;
55
+ private content;
56
+ sendMessageNowait(params: {
57
+ type: string;
58
+ message: MessagePayload;
59
+ attachment?: Uint8Array;
60
+ }): void;
61
+ sendMessage(params: {
62
+ type: string;
63
+ message: MessagePayload;
64
+ attachment?: Uint8Array;
65
+ }): Promise<void>;
66
+ close(): Promise<void>;
67
+ clientDisconnected(participantId: string): void;
68
+ [Symbol.asyncIterator](): AsyncIterator<MessagingStreamEvent>;
69
+ }
6
70
  export declare class MessagingClient extends EventEmitter<RoomMessageEvent> {
7
71
  private readonly client;
8
72
  private readonly _messageHandler;
@@ -10,6 +74,8 @@ export declare class MessagingClient extends EventEmitter<RoomMessageEvent> {
10
74
  private readonly _messageQueue;
11
75
  private _messageQueued;
12
76
  private _sendTask;
77
+ private readonly _sendOperations;
78
+ private readonly _streams;
13
79
  private _messageQueueClosed;
14
80
  private _desiredEnabled;
15
81
  private _online;
@@ -42,6 +108,7 @@ export declare class MessagingClient extends EventEmitter<RoomMessageEvent> {
42
108
  private _dropQueuedMessage;
43
109
  private _drainQueuedMessages;
44
110
  private _sendMessages;
111
+ private _sendQueuedMessage;
45
112
  sendMessage({ to, type, message, attachment, ignoreOffline, }: {
46
113
  to: Participant;
47
114
  type: string;
@@ -62,6 +129,12 @@ export declare class MessagingClient extends EventEmitter<RoomMessageEvent> {
62
129
  message: MessagePayload;
63
130
  attachment?: Uint8Array;
64
131
  }): Promise<void>;
132
+ stream({ to, type, message, attachment, }: {
133
+ to: Participant;
134
+ type: string;
135
+ message: MessagePayload;
136
+ attachment?: Uint8Array;
137
+ }): Promise<MessagingStream>;
65
138
  getParticipants(): RemoteParticipant[];
66
139
  getParticipant(id: string): RemoteParticipant | null;
67
140
  getParticipantByName(name: string): RemoteParticipant | null;
@@ -1,7 +1,7 @@
1
1
  import { Completer } from "./completer.js";
2
2
  import { EventEmitter } from "./event-emitter.js";
3
3
  import { RemoteParticipant } from "./participant.js";
4
- import { JsonContent } from "./response.js";
4
+ import { ControlContent, JsonContent } from "./response.js";
5
5
  import { RoomMessage, RoomMessageEvent } from "./room-event.js";
6
6
  import { RoomServerException } from "./room-server-client.js";
7
7
  import { splitMessageHeader, splitMessagePayload } from "./utils.js";
@@ -19,6 +19,255 @@ function bytesToBase64(bytes) {
19
19
  }
20
20
  return globalScope.btoa(binary);
21
21
  }
22
+ function base64ToBytes(value) {
23
+ if (globalScope.Buffer != null) {
24
+ return new Uint8Array(globalScope.Buffer.from(value, "base64"));
25
+ }
26
+ const atob = globalThis.atob;
27
+ if (atob == null) {
28
+ throw new Error("base64 decoding is not available in this runtime");
29
+ }
30
+ const binary = atob(value);
31
+ return Uint8Array.from(binary, (character) => character.charCodeAt(0));
32
+ }
33
+ class MessagingStreamInput {
34
+ constructor() {
35
+ this.items = [];
36
+ this.signal = null;
37
+ this.active = null;
38
+ this.closed = false;
39
+ this.error = null;
40
+ }
41
+ enqueue(content, { wait = false } = {}) {
42
+ if (this.closed) {
43
+ return Promise.reject(this.error ?? new RoomServerException("the messaging stream is closed"));
44
+ }
45
+ const sent = wait ? new Completer() : undefined;
46
+ this.items.push({ content, sent });
47
+ this.signal?.resolve();
48
+ this.signal = null;
49
+ return sent?.fut ?? Promise.resolve();
50
+ }
51
+ close(error = new RoomServerException("the messaging stream is closed")) {
52
+ if (this.closed) {
53
+ return;
54
+ }
55
+ this.closed = true;
56
+ this.error = error;
57
+ this.active?.sent?.reject(error);
58
+ this.active = null;
59
+ for (const item of this.items.splice(0)) {
60
+ item.sent?.reject(error);
61
+ }
62
+ this.signal?.resolve();
63
+ this.signal = null;
64
+ }
65
+ fail(error) {
66
+ this.close(error);
67
+ }
68
+ async *stream() {
69
+ try {
70
+ while (true) {
71
+ while (this.items.length === 0 && !this.closed) {
72
+ this.signal ?? (this.signal = new Completer());
73
+ await this.signal.fut;
74
+ }
75
+ const item = this.items.shift();
76
+ if (item == null) {
77
+ return;
78
+ }
79
+ this.active = item;
80
+ yield item.content;
81
+ item.sent?.resolve();
82
+ this.active = null;
83
+ }
84
+ }
85
+ finally {
86
+ if (this.active != null && this.closed) {
87
+ this.active.sent?.reject(this.error ?? new RoomServerException("the messaging stream is closed"));
88
+ this.active = null;
89
+ }
90
+ }
91
+ }
92
+ }
93
+ export class MessagingStream {
94
+ constructor({ streamId, remoteParticipantId, input, output, inputClosed, onClosed, }) {
95
+ this.events = [];
96
+ this.eventSignal = null;
97
+ this.state = "accepted";
98
+ this.streamId = streamId;
99
+ this.remoteParticipantId = remoteParticipantId;
100
+ this.input = input;
101
+ this.output = output;
102
+ this.inputDone = Promise.resolve(inputClosed).catch((error) => {
103
+ this.input.fail(error);
104
+ this.push({
105
+ kind: "closed",
106
+ streamId: this.streamId,
107
+ reason: "error",
108
+ message: error instanceof Error ? error.message : String(error),
109
+ });
110
+ void this.output.return?.();
111
+ this.finish();
112
+ });
113
+ this.onClosed = onClosed;
114
+ void this.consumeOutput();
115
+ }
116
+ get closed() {
117
+ return this.state === "terminal";
118
+ }
119
+ push(event) {
120
+ if (this.state !== "accepted") {
121
+ return;
122
+ }
123
+ this.events.push(event);
124
+ this.eventSignal?.resolve();
125
+ this.eventSignal = null;
126
+ }
127
+ finish() {
128
+ if (this.state === "terminal") {
129
+ return;
130
+ }
131
+ this.state = "terminal";
132
+ this.input.close();
133
+ this.eventSignal?.resolve();
134
+ this.eventSignal = null;
135
+ void this.inputDone.finally(this.onClosed);
136
+ }
137
+ async consumeOutput() {
138
+ try {
139
+ while (true) {
140
+ const next = await this.output.next();
141
+ if (next.done) {
142
+ return;
143
+ }
144
+ if (next.value instanceof ControlContent && next.value.method === "close") {
145
+ this.push({
146
+ kind: "client_disconnected",
147
+ streamId: this.streamId,
148
+ participantId: this.remoteParticipantId,
149
+ });
150
+ return;
151
+ }
152
+ if (!(next.value instanceof JsonContent)) {
153
+ throw new RoomServerException("unexpected chunk from messaging.stream");
154
+ }
155
+ const value = next.value.json;
156
+ const kind = value["kind"];
157
+ if (kind === "message") {
158
+ const type = value["type"];
159
+ const message = value["message"];
160
+ if (typeof type !== "string" || typeof message !== "object" || message == null || Array.isArray(message)) {
161
+ throw new RoomServerException("invalid message chunk from messaging.stream");
162
+ }
163
+ const encodedAttachment = value["attachment_base64"];
164
+ this.push({
165
+ kind: "message",
166
+ message: new RoomMessage({
167
+ fromParticipantId: this.remoteParticipantId,
168
+ type,
169
+ message: message,
170
+ attachment: typeof encodedAttachment === "string" && encodedAttachment !== ""
171
+ ? base64ToBytes(encodedAttachment)
172
+ : undefined,
173
+ }),
174
+ });
175
+ }
176
+ else if (kind === "client_disconnected" && typeof value["participant_id"] === "string") {
177
+ this.push({
178
+ kind,
179
+ streamId: this.streamId,
180
+ participantId: value["participant_id"],
181
+ });
182
+ return;
183
+ }
184
+ else if (kind === "closed") {
185
+ this.push({
186
+ kind,
187
+ streamId: this.streamId,
188
+ reason: typeof value["reason"] === "string" ? value["reason"] : "closed",
189
+ message: typeof value["message"] === "string" ? value["message"] : undefined,
190
+ });
191
+ return;
192
+ }
193
+ else {
194
+ throw new RoomServerException("unexpected chunk from messaging.stream");
195
+ }
196
+ }
197
+ }
198
+ catch (error) {
199
+ this.input.fail(error);
200
+ this.push({
201
+ kind: "closed",
202
+ streamId: this.streamId,
203
+ reason: "error",
204
+ message: error instanceof Error ? error.message : String(error),
205
+ });
206
+ }
207
+ finally {
208
+ this.finish();
209
+ }
210
+ }
211
+ content({ type, message, attachment, }) {
212
+ return new JsonContent({
213
+ json: {
214
+ type,
215
+ message_json: JSON.stringify(message),
216
+ ...(attachment == null ? {} : { attachment_base64: bytesToBase64(attachment) }),
217
+ },
218
+ });
219
+ }
220
+ sendMessageNowait(params) {
221
+ if (this.state !== "accepted") {
222
+ throw new RoomServerException("the messaging stream is closed");
223
+ }
224
+ void this.input.enqueue(this.content(params));
225
+ }
226
+ async sendMessage(params) {
227
+ if (this.state !== "accepted") {
228
+ throw new RoomServerException("the messaging stream is closed");
229
+ }
230
+ await this.input.enqueue(this.content(params), { wait: true });
231
+ }
232
+ async close() {
233
+ if (this.state === "terminal") {
234
+ await this.inputDone;
235
+ return;
236
+ }
237
+ this.input.close();
238
+ try {
239
+ await this.inputDone;
240
+ }
241
+ finally {
242
+ await this.output.return?.();
243
+ this.finish();
244
+ }
245
+ }
246
+ clientDisconnected(participantId) {
247
+ if (this.state === "terminal") {
248
+ return;
249
+ }
250
+ this.push({
251
+ kind: "client_disconnected",
252
+ streamId: this.streamId,
253
+ participantId,
254
+ });
255
+ this.finish();
256
+ }
257
+ async *[Symbol.asyncIterator]() {
258
+ while (true) {
259
+ while (this.events.length === 0 && this.state !== "terminal") {
260
+ this.eventSignal ?? (this.eventSignal = new Completer());
261
+ await this.eventSignal.fut;
262
+ }
263
+ const event = this.events.shift();
264
+ if (event == null) {
265
+ return;
266
+ }
267
+ yield event;
268
+ }
269
+ }
270
+ }
22
271
  export class MessagingClient extends EventEmitter {
23
272
  constructor({ room }) {
24
273
  super();
@@ -27,6 +276,8 @@ export class MessagingClient extends EventEmitter {
27
276
  this._messageQueue = [];
28
277
  this._messageQueued = null;
29
278
  this._sendTask = null;
279
+ this._sendOperations = new Set();
280
+ this._streams = new Set();
30
281
  this._messageQueueClosed = false;
31
282
  this._desiredEnabled = false;
32
283
  this._online = false;
@@ -56,11 +307,12 @@ export class MessagingClient extends EventEmitter {
56
307
  }
57
308
  return input;
58
309
  }
59
- async _invoke({ operation, input, }) {
310
+ async _invoke({ operation, input, afterSend, }) {
60
311
  await this.client.invokeContent({
61
312
  toolkit: "messaging",
62
313
  tool: operation,
63
314
  input: new JsonContent({ json: input }),
315
+ afterSend,
64
316
  });
65
317
  }
66
318
  _invokeNowait({ operation, input, }) {
@@ -90,6 +342,8 @@ export class MessagingClient extends EventEmitter {
90
342
  if (sendTask != null) {
91
343
  await sendTask;
92
344
  }
345
+ await Promise.all(this._sendOperations);
346
+ await Promise.all([...this._streams].map(async (stream) => stream.close()));
93
347
  this._desiredEnabled = false;
94
348
  this._clearCurrentConnectionState();
95
349
  }
@@ -153,6 +407,9 @@ export class MessagingClient extends EventEmitter {
153
407
  }
154
408
  }
155
409
  _onRoomDisconnect({ reason: _reason }) {
410
+ for (const stream of [...this._streams]) {
411
+ stream.clientDisconnected(this.client.localParticipant?.id ?? stream.remoteParticipantId);
412
+ }
156
413
  this._clearCurrentConnectionState();
157
414
  }
158
415
  _onRoomReconnect() {
@@ -235,34 +492,52 @@ export class MessagingClient extends EventEmitter {
235
492
  });
236
493
  continue;
237
494
  }
238
- try {
239
- await this._invoke({
240
- operation: "send",
241
- input: this._messageInput({
242
- toParticipantId: resolvedTo.id,
243
- type: message.type,
244
- message: message.message,
245
- attachment: message.attachment,
246
- }),
247
- });
248
- if (message.completer != null && !message.completer.completed) {
249
- message.completer.complete();
250
- }
495
+ const dispatched = new Completer();
496
+ const operation = this._sendQueuedMessage({
497
+ message,
498
+ resolvedTo,
499
+ afterSend: () => dispatched.resolve(),
500
+ });
501
+ this._sendOperations.add(operation);
502
+ void operation.then(() => {
503
+ this._sendOperations.delete(operation);
504
+ if (!dispatched.completed)
505
+ dispatched.resolve();
506
+ }, () => {
507
+ this._sendOperations.delete(operation);
508
+ if (!dispatched.completed)
509
+ dispatched.resolve();
510
+ });
511
+ await dispatched.fut;
512
+ }
513
+ }
514
+ async _sendQueuedMessage({ message, resolvedTo, afterSend, }) {
515
+ try {
516
+ await this._invoke({
517
+ operation: "send",
518
+ input: this._messageInput({
519
+ toParticipantId: resolvedTo.id,
520
+ type: message.type,
521
+ message: message.message,
522
+ attachment: message.attachment,
523
+ }),
524
+ afterSend,
525
+ });
526
+ if (message.completer != null && !message.completer.completed) {
527
+ message.completer.complete();
251
528
  }
252
- catch (error) {
253
- if (error instanceof RoomServerException) {
254
- const wrapped = this.client._coerceMessageSendError(error);
255
- if (wrapped.message === "the participant was not found") {
256
- this._markParticipantOffline(message.to);
257
- this._dropQueuedMessage({ message, error: wrapped });
258
- continue;
259
- }
260
- this._dropQueuedMessage({ message, error: wrapped });
261
- continue;
262
- }
263
- if (message.completer != null && !message.completer.completed) {
264
- message.completer.completeError(error);
529
+ }
530
+ catch (error) {
531
+ if (error instanceof RoomServerException) {
532
+ const wrapped = this.client._coerceMessageSendError(error);
533
+ if (wrapped.message === "the participant was not found") {
534
+ this._markParticipantOffline(message.to);
265
535
  }
536
+ this._dropQueuedMessage({ message, error: wrapped });
537
+ return;
538
+ }
539
+ if (message.completer != null && !message.completer.completed) {
540
+ message.completer.completeError(error);
266
541
  }
267
542
  }
268
543
  }
@@ -327,6 +602,49 @@ export class MessagingClient extends EventEmitter {
327
602
  throw error;
328
603
  }
329
604
  }
605
+ async stream({ to, type, message, attachment, }) {
606
+ const input = new MessagingStreamInput();
607
+ void input.enqueue(new JsonContent({
608
+ json: {
609
+ to_participant_id: to.id,
610
+ type,
611
+ message_json: JSON.stringify(message),
612
+ ...(attachment == null ? {} : { attachment_base64: bytesToBase64(attachment) }),
613
+ },
614
+ }));
615
+ const outputResult = await this.client.invokeToolCall({
616
+ toolkit: "messaging",
617
+ tool: "stream",
618
+ input: input.stream(),
619
+ streamInput: true,
620
+ });
621
+ if (outputResult.kind !== "stream") {
622
+ input.close();
623
+ throw new RoomServerException("unexpected return type from messaging.stream");
624
+ }
625
+ const outputIterable = outputResult.stream;
626
+ const output = outputIterable[Symbol.asyncIterator]();
627
+ const accepted = await output.next();
628
+ if (accepted.done
629
+ || !(accepted.value instanceof JsonContent)
630
+ || accepted.value.json["kind"] !== "accepted"
631
+ || typeof accepted.value.json["stream_id"] !== "string") {
632
+ input.close();
633
+ await output.return?.();
634
+ throw new RoomServerException("messaging.stream did not acknowledge acceptance");
635
+ }
636
+ let stream;
637
+ stream = new MessagingStream({
638
+ streamId: accepted.value.json["stream_id"],
639
+ remoteParticipantId: to.id,
640
+ input,
641
+ output,
642
+ inputClosed: outputResult.inputClosed,
643
+ onClosed: () => this._streams.delete(stream),
644
+ });
645
+ this._streams.add(stream);
646
+ return stream;
647
+ }
330
648
  getParticipants() {
331
649
  return this.remoteParticipants;
332
650
  }
@@ -420,6 +738,9 @@ export class MessagingClient extends EventEmitter {
420
738
  this._wakeMessageQueue();
421
739
  this._drainQueuedMessages({ error });
422
740
  this._desiredEnabled = false;
741
+ for (const stream of [...this._streams]) {
742
+ stream.clientDisconnected(this.client.localParticipant?.id ?? stream.remoteParticipantId);
743
+ }
423
744
  this._clearCurrentConnectionState();
424
745
  this.client.protocol.removeHandler("messaging.send", this._messageHandler);
425
746
  super.dispose();
@@ -404,6 +404,7 @@ export declare class ParticipantToken {
404
404
  addTunnelGrant(ports: number[]): void;
405
405
  addRoleGrant(role: string): void;
406
406
  addRoomGrant(roomName: string): void;
407
+ addSiteGrant(roomName: string): void;
407
408
  addApiGrant(grant: ApiScope): void;
408
409
  grantScope(name: string): string | ApiScope | undefined;
409
410
  getApiGrant(): ApiScope | undefined;
@@ -1294,6 +1294,9 @@ export class ParticipantToken {
1294
1294
  addRoomGrant(roomName) {
1295
1295
  this.grants.push(new ParticipantGrant({ name: "room", scope: roomName }));
1296
1296
  }
1297
+ addSiteGrant(roomName) {
1298
+ this.grants.push(new ParticipantGrant({ name: "site", scope: roomName }));
1299
+ }
1297
1300
  addApiGrant(grant) {
1298
1301
  for (const existingGrant of this.grants) {
1299
1302
  if (existingGrant.name === "api") {