@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.
- package/CHANGELOG.md +10 -2
- package/dist/browser/agent.js +6 -6
- package/dist/browser/meshagent-client.d.ts +10 -1
- package/dist/browser/meshagent-client.js +28 -0
- package/dist/browser/messaging-client.d.ts +74 -1
- package/dist/browser/messaging-client.js +350 -28
- package/dist/browser/participant-token.d.ts +1 -0
- package/dist/browser/participant-token.js +3 -0
- package/dist/browser/room-client.d.ts +5 -2
- package/dist/browser/room-client.js +27 -12
- package/dist/esm/agent.js +6 -6
- package/dist/esm/meshagent-client.d.ts +10 -1
- package/dist/esm/meshagent-client.js +28 -0
- package/dist/esm/messaging-client.d.ts +74 -1
- package/dist/esm/messaging-client.js +349 -28
- package/dist/esm/participant-token.d.ts +1 -0
- package/dist/esm/participant-token.js +3 -0
- package/dist/esm/room-client.d.ts +5 -2
- package/dist/esm/room-client.js +27 -12
- package/dist/node/agent.js +6 -6
- package/dist/node/meshagent-client.d.ts +10 -1
- package/dist/node/meshagent-client.js +28 -0
- package/dist/node/messaging-client.d.ts +74 -1
- package/dist/node/messaging-client.js +350 -28
- package/dist/node/participant-token.d.ts +1 -0
- package/dist/node/participant-token.js +3 -0
- package/dist/node/room-client.d.ts +5 -2
- package/dist/node/room-client.js +27 -12
- package/package.json +1 -1
|
@@ -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 |
|
|
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
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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") {
|